Max裡的for循環
/***先說點題外的,只是些個人感受。上周二去fruityspace看了個演出,其中一曲是Tomas Korber和Konus Quartett合作的《給一個場地的音樂》。一臺電腦加四個薩克斯。聽了幾分鐘以後我覺得這個哥們兒(tomas)似乎在用Max,因為他輪番的freez sample(薩克斯的),做layer,被觸發,包括那些噪音和波形的生產邏輯,讓我似乎能看見他屏幕背面的patch了。下來一問,確實猜對了。雖然曲子現在聽來十分的古典和學院,但是Max作為現場樂器,和四個原聲樂器的搭配以及選擇的效果,處理的方式都不錯。交流了一會,我們似乎都有共同的嘆息,就是類似編程這樣生產聲音的方式,有無限的可能,很容易就完全迷失在技術當中,比如我就完全不知道如何和原聲樂器配合(除了採樣處理和觸發之外,但這並不是它未來應該一直做的事情)。tomas的觀點是還是要以「聽」為主,我的觀點當然完全不一樣,但是作為抑鬱症患者,這種情況下(或者任何情況下)是不會表達與人相左的意見的。尤其現場效果還不錯。但恰恰就是這個「效果」,或者換句話來說「音樂的好壞」,其實完全不重要,至少它已經不再是努力的目標。拋開廣場舞、古典、濫俗情愛流行、周杰倫、民族、EDM、前衛實驗這些風格/逼格不論(其實就是要「論」也行),中間這些品味啊,格調啊,涵養啊,欣賞和接受能力等等的差別,一點意義都沒有。重要的是你/我為什麼要做/聽音樂呢?為什麼去生產出這麼些過剩的聲音和圖像呢?我覺得四五線城鎮的苦悶青年通過快手、抖音傳播和消費到的快樂和我從巴赫、arvo part音樂裡聽來的感受是完全等價的;音樂節上的吉他英雄被後來扭推子的DJ代替,噼裡啪啦閃的聲光電和農村紅白喜事、墳頭蹦迪、髮廊的麥可傑克遜刺激出的難道不是相同的荷爾蒙嗎?當我把心悸、驚恐發作說出來也只是消遣舊景觀當中的陳詞濫調罷了,就像那些為人類已徹底經歷夠了的事情硬加上新的設問,著急引用朗西埃,以及抱著哈貝馬斯看不懂就沮喪··· ··· 的人一樣,都困在這個球形的屏幕之外 ***/
--既然這樣,只能說一點不涉及價值的技術----
Max和寫代碼最明顯的區別是緯度。代碼是一維的,從上到下一行行執行,遇到 loop()一類的命令再從頭往下順,從一眼看過去的邏輯和簡潔來說很方便。而Max是二維的,上下左右都有Object/Function/message/UI,如果純粹說可讀性,大多數情況不如代碼。但是對於原本的藝術、設計、音樂人來說,可讀性上的犧牲(也不一定絕對)換來UI和進入編程的可能還是很值。尤其是聲音/DSP角度來說,中途要看各種圖表實在是很方便。
但是Max一直有些讓coder不適應的思路,比如遞歸、迭代、for循環,這些在代碼裡邊比較容易解決的事情在Max裡找不到對應。當然你可以在js這個object裡邊寫Javascript,但這就算不上純粹的人。其實說起來,Max的歪門邪道有點多,比如可以寫JS,有jit.gl.slab之類跑GPU,physics物理「引擎」,有Gen~和jit.gen這些玩採樣點和像素點的,現在又來了Node,坑越來越多,都值得好好踩一遍。不過這裡還是說回最基礎的坑——Max,怎麼用最質樸的連線來做個for循環。比如畫滿一窗口的圓(只是2D圖形,因為3D的有jit.gl.multiple又是另一回事了)。
一。如果先畫一個圓,processing大概這樣寫:
size(600,600); background(0); noStroke(); ellipseMode(CORNERS); ellipse(200,200,400,400);代碼很直白,Max是這樣:
<pre><code>begin_max5_patcher583.3ocqTssiaCBD8Y6uBDO6FAXG639T62QU0JhMIKQ1PDFmjsq1+8BC3MMY2lKqhhfXFlqm4L7ZZBdo9fX.i9N5WnjjWSSR.QdAIwyI3d9glN9.nFd4n0pU3rvU5QamvZeYqH3C7RtZMF8630C1W5favSFnF6kJmIfynQga41lmkp0OYDM1finKlQxPrb+dIYZ+cG6bSHzm3GYKDK8xMeKu.6k8VZpeK6FKtdwv.esXJYshCP53RPoxp2w6PLBAVEwEa97oUFBJ9+GxbITYkVYGj+ADRoylWOmtn5yPL10PLJgA.WQE7G8pXlOzJdeHe9oQx6veDMqy+JnoRr2Y7G.yMR6r8RUqdO5G9zGQb+JIv5x7prGDJdcdGMeFiNmvpbHZf9cAnjcOPoqA8fwxtlVTAp4Yt4pnnS8m54Vi7vcgkdJ0WAFok.Yj5wNGBxdPH3IjQvi3No57mwfrxK+TTcPOZZlviH2NCcLwZECVohaktG4NpDMpzm14t0H4dS55Qp9QDI5MUSNl34gJfl7sa2ILCQ0gn3XiazF+QXhB6nAgi4vQiXmbR+RPB233DVGgXzDZmGVThClpaEF0nDnPo95KMx.OqCdjIL1J08dRaZnbVwG6rmhAKWuR100n6BoUx623lVhC.30FdqTnrQRUBFTmFhJYVdUYQEMy+0hhbZg+K21+L3GsfMYAqlPqKAKH0KHEgubhnelY2sUt27BikrJxzHniDXza0loloyzZ3BnCFPSfoXbE54y03dGtA.OI1tSeK8u.9ohz0B-end_max5_patcher-</code></pre>參數有點擠在一行的意思,可讀性略差,可以調整成這樣:
<pre><code>begin_max5_patcher706.3ocqVssaiCBD8Y6uBDO6MBv22m186X0pJ7kjRkMXgwsoaU+2Wt4z1zKNN0JBRXXfgyYFNjmBCfUhisiPvOA+ADD7TXPf0jwPfeb.rmdrtiNZcCxaePTcGLxMkp8nxZtBDOaSLo5ZUpGGZc6KrhxO.iN6ave8tOpdry5IbdC3S8LtdKrAD6MNPU02x3GtQ1VqbaLoXGJBTlZ5KhM8DxNzoMVuMtihceh8VYM1XowvORgFSOGFZ5htP722NNROz9NBXu7PEfjlN29T13hQN4KPtAnQ.bhE6XDdAvieO3iuFvWMoTB9Wmn2rDKwBtLzb+JvVxVlYGnLtRbOsCPPHaKw0tpT7dAWMx9m0HFuKsLEWjuxzuiSly9I4V1BuHCYBMm16NO+Vxncv2yckWUgwmnJbGSs6AFuQ7.3WliO.o+jgrsEkK1DVDuDKFiJ2QvoHRtlJcl9BpjrFpDiHaLW1U2.R.02RkKxhZ2uompjriqhKM5JWCMRRs2YwjkjhVGC9lhQ6NB6X7yewxdpL1eKqNJlj0y7gu1NB7xAqocTw3TESKo8hSXuSeXl6RijV9GfWHPkaUfHKDnsJNWJy4cpVzIjtoskHncYE4n77n4gkmbsm0LHzpr97Zb9tTSQjS5u7zfuIFzOKrLHR2.xJd8j0YL.IFaQcR1qn.yfu4ICeQGMst04jf6tGcX39V4n2caTzZW24RyV8WMP3tg1+vET1dOa1+LqEpTqfnzxGSR2k+iEYP2REMsR9DyJ3DZvWnWu5r66unaL0vD8FItPGb1Sm5TukCpNrm00cpXL3zLZsUubI7fj1vZ4JuDju1EOW7FmmkjiMkrwEIw3Dyuzcu5YB+JHyqfThvkY1UfJKPIteoMg+nks5UoegzIhSxQyB15h.oXPHmSl5kVZmvlAcrosRQpA54uB.607lk3Q9zc3yg+GY77tPA-end_max5_patcher-</code></pre>單把顏色分出來,可以避免一條message裡邊信息太多,分不清。
二。如果是做一排圓形,processing:
size(600, 600); background(0); noStroke(); ellipseMode(CORNER); for (int i=0; i<width; i+=20) { ellipse(i, height/2, 18, 18); }for (int i=0; i<width; i+=20) 意思就是i 從零開始,每次加20,直到它大於窗口的長度,那麼i 是這麼發展的:0,20,40,60,80 ··· ··· 最後一個是580,作為圓形的坐標平均分布一排。
Max的一排:
<pre><code>begin_max5_patcher788.3ocwVssaiCBD8Y6uBDpOsaZDfuuOs62wpUUDa2T5Zahrwsosp+66.X2KoowNITsJJXafgyLmANCO46gWI2V1gQ+.8ajm2S9ddltzc3M7sGtluMuh2YlFdUuRIavKrCI6UUkJ0CaJsqAdEuYMF8mgg6TOTYFAOZPSesnALwrXzgN2vU42HZVeUaYtxtPAgKIKPATcKKbr8kEFVFKzuacDEFrjqt8RFV20y995lEyL1ZJuGrczUUkaMNC9RD8SiWQiZ1gK6yCWJgoiQJ0FpYKifV1wDvzLGFwa34+EIPhOMrOTLesrQ0IdzzIUm.OIpfEax8gQlOnSREZTa30VW4WsBdE9ijTF4THo5xtN95x8vRP1WdGuBcAEEPHnKXvN1TDKJZ7uKHvnrHZZxIxhYolGCe4JZLvg60tUnVdunoPdO5mZ+GQfewDy+CKyrvQzHcRZLLJbIiFQXIvYzXxDbI6X3R.AGSlU4EnPT9M71IoQX5WUyUshsGEYx1+g5o4wfLywYJypqybDE5xsieGQSOU09yU4iYOrxRLbkSOxxhbHG8MPP4+EGMTnzVcHf3RNJwgTT+iBv6lT+Z74WKoYuLUhoMMYBJK3Xnr32RYlEDWIZ18FkFWR2+64wNYea9HiLTWYA5U+pnrSIZ3JAbeyWmDcXR6MWMajHy.oLWfTxb.58dirsnr0jp+xQFjE1KxjyB4XXQYSkFyb.4pAhNy8KCSpVTrQBG2F1hljnupcPRh852zW95L8rcH1Cj2omKRy.nXGv0iIrCBThK.ZVp.D1Gfxp+v2r4tx1tgoaPATruUZ1Wat+GrGnw9oQqC2VdmXb9wld3sfDpBzO6as5eaSiwVSkvIjldwvwSH97GDr2Qy6Uoy9BgrVeCKea3bMuuR8dNX05qEUU4xJqa48xHPEkghF30s7BQITmXwvHloSsnRVFjDGlPWneKMLfFpeCZdiN+fErQKXYDZVrwBRVJIz9FzEcelczVAU1rUwXIjwJVvlfV4FY6XxDLMyLfICZYSyNkVHP2sLHtF3Mwn5jNc6+r++.bpiHqH-end_max5_patcher-</code></pre>主要就是uzi了,這個object是Max裡邊比較特別的東西,我甚至懷疑它是為了寫loop而誕生的,怎麼說都比較抽象,但是看例子又很直觀:
或者看幫助文檔,比這個詳細。總之,這裡需要的只是它右輸出出來的數字,每次乘以20(間距)來作為圓形左上角位置。而出來之後立即減1:
是為了從窗口最左邊開始畫,不然左邊就會空出20個像素點。其實除了思路上有點不同,這裡proessing的sketch和Max的patch,幾乎就是等價的。每個東西都能找到對應,除了:
1.Max的循環沒有終止條件,也就是少了 i<width 。
2.Max的jit.lcd沒有辦法選畫圓的模式,只能以圓左上角xy和右下角xy來進行,有時候就會有點不方便,比如計算兩個圓的距離,就得用左上角的xy分別加半徑來找圓心,然後勾股。
三。畫一窗口的圓,processing就再套一個for:
而Max的uzi就要多忙一件事情:
<pre><code>begin_max5_patcher908.3oc2X00aaBCE8Y3WgEJOskFYaLesm19cLMU4.zT2A3HioMsU8+9L1lrlzz.owQSqBgSvec843iu2K7ruWvR9lx1.v2.+D348rummtp9J7rO6ETS2jWQa0cKno7A9x6BlaZRVtQpqthSKVRaVMz.uSVUJkOttzL4A5FA+x1bq7wJcKACCnoql0nFh1JHakqox7aYMqtVTlKMSDIdAbtsLF1WhwKfamX0zXL8NyCqPaK0B+JTZPecu362WL+7P8xiC24tF1IQ8kYnQfM9.vNwgv9J.5cANqQNY7hee7hRR6AIJISi0rEQm5FMF9eFhCyNSDixbHhWSy+MfYtdWneLbeCuQ1xdRWIpWwd.5fLJcDh0BeTn9GLZT9n2rMzZyZ4GBFsJ3sLU1GRaTW11RWUd.pRIA32Sq.yPfYXvrPvLB.GEMb6B9KJKBkl7wzTgwgZRLMyojXnCka2wjKdf0Tve.7890O.pthg56Qcx5DZDMJMFgIKvnHHNQwk1XOnQcBOItDAwNlLqxK.DP9sTwnznp6WWSkB1lShLwG9L837HIVGFCgmZbroQgtTN9U0gkOpC+o336HmYwnDS..nw+mKOxhi9jvQQwWNNh3PN5K.L7eDGYOrgMAPCgtjibYxjcOwTqtokH87KNogLocZcTMFoEdJjV7mDckIy7Khth7YWWA05pjLWpqPulzzSXPEqY+2pWun5qeWlrk2IxG3DaVcyA+ccUT1JYMTIi27pNgrc5f6VS1RvIXoLWXojoXncWMbQQoPuYewsrJn7AsL7rrbrZRwisMl4.xkLUxE4ZxcJVFStDj6daYGAz3yjcwSldCOWKMkiiDGHXFTcG0PItvPoSwUlSrTxI3zz1obdEWXiNzGN.ts30wEpYEq4pXSVu4QQ8eVHh5UDT+fHoaexE..MF.bASMovKp2F1AlZBVJ1Q1YLOsCGtdSjZ5502WJZs8VaCU1M2YTF5uSgRBzXdTmUPfn7d1P+i00PEpzMjpbM5DlLE1jFGXFJW4tqoiY80pPmuM4l8xN3uIYzUv308eI.eCZtg1UI2kAVt5FVU0V8q21VTYeYSvJXkfVvJU4TM21ht6HiUgKBShIInd8dXJIDQ5+GYGkucD3gQndWOTVrdDvrTHw7OUUnCMrSdTpr.MY7gSfCY2oj.B9ZtXXuTMzLcC5cPCap0IBEP2OkwfZEuwFB0zuc6+h+e.zOO6JC-end_max5_patcher-</code></pre>上邊的uzi最左的輸出也連上了,當uzi每次從右輸出發出一個數字之後,左輸出緊跟也發出一個bang給下邊的uzi,畫一橫排的圓。因為和代碼不同,Max要靠事件來驅動,也就是要有信息(這裡信息是bang)傳遞到object(這裡是下邊的uzi)才能繼續下一步,所以叫做Dataflow/數據流。
四。加點交互。如果要用滑鼠橫向移動來控制圓形大小,豎向移動改變圓形的顏色,那麼processing改動也挺多,至少要加setup(),draw(),和一些變量聲明:
float bcolor=random(255); void setup() { size(600, 600);}void draw() { float rad=map(mouseX, 0, 600, 0, 70); float b=map(mouseY, 0, 600, 255, 0); background(bcolor); noStroke(); for (int i=0; i<width+20; i+=20) { for (int j=0; j<height+20; j+=20) { fill(255, 255, b, 80); ellipse(i, j, rad, rad); } }}Max是這樣:
<pre><code>begin_max5_patcher1316.3oc0ZksbhiCE8Y3qPkq7zLDFs4s4od9N5pqtDXEZkwairIIc5p+2GsXGfzBPz1FpTjvh7xQmy8pqt2K7i4yBVU8BuI.72fOClM6GymMyLjdfYcedVPA6k04rFyoETxetZ0iAKrGpk+RqY3+Bf6GSjYFQcV2SH8CVtsPTlyaM2D7tAq111OJpazl1umyM2h9K1dRseulamoAhREpeo6n0r10eSTt4qR95V6IPhvKgK.3Dn4kzkgpmUCA9h9R94745mVLLFeuK9huQ7EQ0DkfoWY9htM7EGGN4780BVM.BhfP0ywPGrGm3j8gCk8mj5Tng5Xpg5Hn0Ce53NNLD3j7w2.xGBizzMjPrjOdJHuZh2vaZYsbWz1cDMjSZGdYd7KNwKdnJnvT8KwjAIJE7lF1F9unJOH2rx3Ln++NDHwkSAJYZhG3kSgMlOBglBmBIqLqpPQ9HWzN5FE1ONcukC1XhiMwyqXYqXkabQ6vKXsvEPaCb9vanIFXDbRb3Woc3UN5l+bwd5MvWum4Ve8jIwh2BVodrNmyjNncxEXyIWnMewtWsvexb.BMV+Xyx8HxTnD+WAuUVAvPvmXqaEOwAtbDlnb9N6pfNAfZC6gmjvd26lwvaZZtXa7tIJMWmLFkdSSzcRYbMa8+BD1GNndpaiMsavGpJaKYEVd9ORAKOX7h.pu4MhWMGvru94DJJzlYHwjRfdMwntmPMSYvpdhka1U.Cti.ti5RyNccuWSMKLMDkDedgiP2OLBZbcwdTzt7YgJCpmAeRCM.1Ucg5eWq1f3StKiu5G9x2+Yn5XjJIbLJDhiUhYWlISfXluNCPAq+FSdJYjbsUQ0T6qErVo3kSnkXOVGGgimvc09SvtBU1easvq8p1CB+6evNRnIJGIpqqOSgW1wzH5GEMJEO4Zze.vtV2E+AQh5Stv1kTxjDrZ6qB.wUqgiFEQZ.UX76JZ1rtw1FuQgWQ+pOLK85RS+l3Wg9X6W00DuA5WUyJ44tDG7ET8N7nZvJUECajUaKy1+pcPJDLIRWxBMEYiBa12JDmn4U2kUTkw2GNkLZgCGCeSvVsYcUdkzdWgKIIvzP7B86H3vzXy6fzzjDkBt28tVVUWIaEUkFDVRR2SLMmUPtn78eAXFhnG+PEtoZqbcuUuOKevNvx3MshRVOZedWRXfiYE8EI0Nw.rm.0Kqhr5JkuXG2HvXsgPkYp0wxld0Aehlbb2Memn5bFtNRht8aWIj7xLqKYY3PotGnAYmww1vH1Ebc8M3vOQ0EGOvIp9qA4ryTbxHHIXe7nzYkt2zoRlwMwJfWAnCcCMZ5glbnmvaPiGDzDeb2Inof09AMdJr0FB4ksFdSfV2ENWPOLAO1WS8PWESv9tJd7kWrux6n6OS80cdvAIo9xQxPQJzWjvCDo24weDjFqrqN6dYiEPmMQhQ.mHeRWLcL767I.BZTRCf3Am5mNCCIe76niAPdfyXHc8V5SBzXnb5eFDm2aXLjNjOQg5mNCNi7yAzXgyYcug+BP1ZXY00OwkMcmsAifB1i15kMemEpxDJsezzYi.I+IQ+4GYFgIUEu2ppbeqzVr+KI1VzYJPWVtUzsenhcy6ZJv6padWiU1lIpJzeI.ysr4A1171CUfUadPjm+VU8yd6HyB5atRvFIKSv0+Hn5Nh4zQu0Ef3HZLxT6eBkfn52QOnA.cWAt+JvoPTZjsaAoIPp8cpgPttrK9pb05hi0DBqEzplF+DohnuuUKAEJcSzmCj1bO+my+eDP0OWB-end_max5_patcher-</code></pre>因為是基於事件的,所以信息有個先來後到,從qmetro出來就要用trigger 來規定一下處理順序。另外由於Max的jit.lcd只能以左上角xy和右下角xy的辦法來畫圓,所以需要坐標減去半徑又加上直徑來實現變大變小,這樣動起來就會抖動。當然,一般正兒八經的東西,都不會用lcd來做,文檔裡邊,jit.lcd的介紹是:
這裡用它只是舉例uzi來實現for循環。
就是這麼一點小東西,Happy patching!