Warning: mysql_real_escape_string(): No such file or directory in /nfs/c10/h05/mnt/145305/domains/learn.travelchinawith.me/html/wp-content/plugins/syntax-highlighter/easy-google-syntax-highlighter.php on line 50

Warning: mysql_real_escape_string(): A link to the server could not be established in /nfs/c10/h05/mnt/145305/domains/learn.travelchinawith.me/html/wp-content/plugins/syntax-highlighter/easy-google-syntax-highlighter.php on line 50

Warning: Cannot modify header information - headers already sent by (output started at /nfs/c10/h05/mnt/145305/domains/learn.travelchinawith.me/html/wp-content/plugins/syntax-highlighter/easy-google-syntax-highlighter.php:50) in /nfs/c10/h05/mnt/145305/domains/learn.travelchinawith.me/html/wp-includes/feed-rss2.php on line 8
互动软件学习笔记 http://learn.travelchinawith.me Processing, MaxMSP, AudioMulch, Field, SuperCollider Tue, 03 Sep 2013 03:50:54 +0000 en-US hourly 1 https://wordpress.org/?v=4.6.6 34760399 SuperCollider 经验分享 http://learn.travelchinawith.me/?p=1338 http://learn.travelchinawith.me/?p=1338#comments Fri, 26 Apr 2013 09:35:50 +0000 http://learn.travelchinawith.me/?p=1338 Continue reading SuperCollider 经验分享 ]]> 你想象SC有多牛逼,它就比你想象的牛逼一万倍。

-∞. 想到什么说什么,绝对没关联,铁定有遗漏。记住,一切貌似神乎其神的高级玩意当你真正用心去了解学习之后,都不过如此。

-8. 从版本3.6开始,载入一个音频文件得这么写:

~chooston = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

之前的代码在3.6中是无效的。

-7. 从版本3.6开始,把鼠标输入位置放在代码块的圆括号里按下 command + enter 会自动执行括号内的全部内容,无需选中全部代码再执行。

-6. 搞清楚各个Ugen输出的值是多少是很重要的。这样你才好将它们当作控制来使用。

-5. SC对应 OS X 10.6 和 10.5 有不同的两个版本,下载前看清楚。3.6 版本后已通杀。

-4. 不要以为SC只能合成声音,它也可以做很屌的视频,玩音视互动并非必需需要借助第三方(比如Processing)来实现。因此,软件自带的范例有些是不会响的,不要为此感到诧异。

-3. 目前看来,与其说SC是一门“编程语言”,不如说它是代码化填空游戏,每个部件(在SC中被称为Unit Generator)的引数都可以用其他部件代替。代码看起来像模像样,难以辨识,但是一课课学下来你会发觉它比什么Processing之流简单很多。SC玩的是嵌套的艺术。思路清晰即可,不需要你有多高的智商。解读一段长代码更像是做一个长句翻译(比如说有很多of的英文长句,你需要把每个“of”的部分分块翻译出来先)。但是,为创建一个有趣的声音或音序,你需要扎实的声音原理理论知识的支撑。

-2. 如果你从别处粘贴了一堆代码到你本地的SC中,运行后出现错误,试着cmd+’ 高亮你的代码(3.6版本后代码已实现自动高亮),很多时候你会发觉SC把很多注释后的代码部分当作了注释。。这是由于换行不当或粘贴时的格式错误引起的。SC的代码高亮有四种颜色:红色是注释;蓝色是UGen;绿色是合成器定义可变参数;灰色是合成器定义的名称。3.6版本后,代码会自动高亮。

-1. 请在英文Mac系统下使用SC,因为在中文系统下,如果你的代码有bug,post窗口不但不会报错,而且将从此以后不再列印任何信息,除非你重启SC。另外我还发现一些功能在中文系统下运行异常。3.6版本后没有此限制。

-0.9. 版本3.6开启server两种方式:1. Command + B;2. 菜单栏:Language -> Boot Server

0. 在Win下使用SC,你可能需要配置几个东西,否则会无法使用,具体看本组置顶贴(3.6版本未测试)。

1. 最好的一本学习教材是一个名为 David Michael Cottle 的同志写的,现在正在这个博客连载翻译。英文原版 PDF 下载

2. 第一本SC书籍于2011年4月由MIT出版发行,可通过 Amazon 购买。但在阅读这本书之前,还是推荐先阅读完上一条推荐的教材,这样读起来会更好理解一些。

3. 加入SC的邮件列表(具体见官网),里边有很多牛人和热心人会回答我们的各色愚蠢或高深的提问。这是一个非常活跃的邮件列表,自从加入它以后,我每天的邮箱都是满满的。

4. 再次强调运行一段代码使用fn+return(Mac)/Ctrl+回车(Win)而不是return。停止一段代码用cmd+句号键(Mac)/Alt+句号键(Win)。

5. SC面向对象的表现非常明显,你可以把每个UGen的引数看作一个个音频输入接口,你可以将每个UGen的引数用其他任意的UGen进行任意的插入替换。当你慢慢对它感到舒服之后,你就可以随意的瞎插乱插,捣鼓奇怪的声音了。这说起来很方便,但写出来,越来越多的嵌套同时也会慢慢让你崩溃,所以初学者可以用注明引数关键字的方式增加代码的可辨识度,例如:

{SinOsc.ar(400, 0, 0.5, 0)}.play;
//等同于
{SinOsc.ar(freq:400, phase:0, mul:0.5, add:0)}.play;

但后者相对前者更容易辨识,尤其是在加入很多层嵌套之后。

6. 用硬件或具备GUI的软件合成器比对学习,你可以更直观的掌握诸如sustain, attack, release, decay等等关键参数的作用,虽说没有GUI的环境会逼迫你去掌握更牢固的合成器知识,但我认为“看得见”也是很重要的。当然,学到一定程度,你可以用SC自带的一些GUI来做这个事。不要认为SC就只有一个煞白的文本编辑窗口,它也有一些基本的GUI对象可供使用。

7. SC默认音频录制采样率是44.1KHz,比特深度32bit,立体声。如果你在向其他DAW导入这个文件时出现问题,有两个解决方案:i. 用格式转换软件转换其格式(Win下推荐Foobar,Mac下推荐Max,挺好用);ii. 修改Server.sc源文件里的比特深度为常规值(比如16bit)。

8. 选中对象后cmd+d(Mac)/F1(Win)可以查看它的帮助文档(在日后的学习中,你会发现你按下这个组合键的频率会非常的高),cmd+j可以查看它的源码。

9. ar = audio rate 音频率;kr = control rate 控制率。在需要生成音频时使用ar,因为它具有44.1K的采样率;在进行控制的时候,尽量使用kr,因为它的分辨率仅为ar的1/64,所以它更高效(更节省系统资源)。

10. 无论如何,搞清楚声音的一些基本原理,比如频率、振幅、相位等,尽管枯燥,但却对你理解和塑造声音大有裨益。

11. mul改变频率的振幅范围(scale),add控制频率的偏移率(offset),为什么要偏移一个频率?因为它的输出值可以作为某些UGen的引数,比如说频率。但如果不进行频偏的话,可能会得到负值。

12. 上边第五条中说的瞎插乱插是可以,但请注意不要插入负值。一个简单的方式是保持让mul的值小于add的值,或者这么来一下,保证得到的都是正值:

max(0, LFNoise1.kr(12))

13. EnvGen创建的值在0~1之间。

14. 用scope查看波形,用plot查看包络线。

15. 控制指的是通过一个UGen控制另一个UGen。

16. a~z为全局变量,并在SC启动后便被自动申明(因此你无需再重新申明它们),但是在你的patch慢慢变得复杂之后,你一定会后悔使用了它们,因为你没有使用一些更具指向性的变量名。

17. 一个函数将返回最后一行的结果,不管函数其他部分写了什么:

(
1 + 25;
1000 * 13 / 4;
1 + 1;
7; // 只会返回这行的值。其他的并无意义。
)

18. { }:函数,( ):代码块,[ ]:数组

19. 滤波:x轴控制截止频率,y轴控制滤波的范围(共鸣 = 频宽 / 截止频率)。

20. 一个做立体声极佳的方式就是将两个通道的声音分别写到一个数组中。这个数组有两个元素,第一个元素是左声道的声音,第二个元素是右声道的声音。如果你的声卡和外放支持更多的声道,你当然可以把它们都写到这个数组中。这手法有个专业名词:多通道扩展。

21. .scope:在sethoscope窗口内显示所有声道的波形;.scope(1):显示一个声道的波形;.scope(2):显示两个声道的波形;.scope(n):显示n个声道的波形。这是我自己尝试出来的,SC邮件列表里那帮逼没有简洁地回答我这个问题- –

22. 控制scope是蓝色的,音频scope是黄色的。

23. 执行顺序:输入然后输出。节点顺序:输入在尾,输出在头。

24. 在if表达式中,* 相当于和(and)。

25. arg x; 可以简写为|x|“fuck” 可简写为 \fuckdup(10) 可以简写为 !10

26. 引用二维数组中某数组的方式(以索引4,5为例):

nPitchProb.at(4).at(5)

27. midicps 消息 可以将MIDI数转换为对应频率;cpsmidi 消息 可以将频率转换为对应MIDI数。

28. round 消息可以产生非连续的值。比如 round(100) 产生的值将保持为100的倍数,round(0.01) 产生的值将均为小数点后有两位的分数。

29. Win版停止一个patch的热键(Alt+.)与飞信打开新信息的热键冲突。解决办法,关闭飞信热键或改变那个热键的组合方式。

30. SC做算术的顺序是从左至右,完全忽略数学规则。因此在SC中:5+10*4=60,5+(10*4)=45。

31. 用波浪号(~)开头的变量为环境变量,比如 ~kbus1,是由用户自定义的。它可以在一个patch的任何地方,甚至其他patch,其他窗口里被使用。

32. SC 的布尔表达式:if(条件, {如果为真做什么}, {如果为假做什么})

33. 用 .poll 可以查看任意 UGen 的输出值。例如:

FSinOsc.kr(2).poll
]]>
http://learn.travelchinawith.me/?feed=rss2&p=1338 2 1338
SC:马尔可夫链,数值数据文件 http://learn.travelchinawith.me/?p=1288 http://learn.travelchinawith.me/?p=1288#comments Fri, 27 May 2011 07:45:52 +0000 http://www.ccttours.com/blog/?p=1288 Continue reading SC:马尔可夫链,数值数据文件 ]]> 人工智能即用计算机听得懂的语言向其描述人类现象:数字、可能性和规则。任何时候,紧缩计算机在音乐框架内的选择,从某种意义上来说,是在教它们某些关于音乐的东西,并让它们做出“聪明的”或见多识广的选择。对随机游走来说也是一样的:如果你紧缩MIDI音的选择,例如说,你教会了patch(通过将MIDI值转为cps)音阶、比率、音程和平均律。如果你将随机选择限制在C大调音阶,那么cpu便对一个音节中全音程与半音程的关系变得“聪明”。如果我们倾向于C比其他音有更多被选择的机会,那么cpu就有一点点懂得调性(tonality)了。这种倾向映射经常以比率或可能性的形式出现。在简单的倾向性随机选择里仅有一级可能性:在音阶内每个可能选择的可能性。这被称为零阶可能性马尔可夫过程(Markov Process with a zeroth order probability)。

零可能性系统将不会给我们逻辑连续的感觉,因为音乐根本上是依靠音与音之间的关系,而不是单独的音本身或音的整体分布。我们通过当前音和下一个音的、最后三个音的、一分钟前听到的音的关系来感知旋律和音乐进行。为了描述一段旋律,你不得不去描述音与音之间的联系,例如音程。

要得到音之间的关系,我们需要使用一个高阶马尔可夫链:至少一阶或二阶。这个技术在Charles Dodge写的Computer Music(page 283)和Moore写的Elements of Computer Music (page 429)中都有描述。我建议你阅读那几章,但我也会在这进行解释。

描述两个音之间联系的方式是将一个下一个可能音图表(chart)给到当前音。用C键的G音举例。如果你想依据可能性描述调性,你会说C跟随G(结果是V-I关系)的机会大于F跟随G(逆向的V-IV)的。如果说当前音是F,那么下一个音是E(IV的分辨)的机会便大于是C(逆向)的机会。马尔可夫链并不打算只做调性音乐。在非调性音乐中,你可能同样通过避免G和C的联系来描述关系。因此如果当前的音是G并避免调性关系,那么下一个音是C的机会就很小,但下一个音是升D或降A的机会就更大。

你可以用马尔可夫链描述任何类型的音乐。你甚至可以基于现存作品的分析模仿它作曲家的风格。例如,你可以分析所有Stephen Foster写的曲调,检查G音(或与之同调的)以及伴随每个G音的音符。然后你可以生成一个所有跟随G的可能性的图表。计数他音乐中每个跟随音的每次发生并将那个数字输入图表。这是一个精准的Stephen Foster对待G音(或音阶的五分之一步)的可能性图表。

如果我们基于我们的分析断定了一个音的可能性,下一步便是为所有可能的当前音计算同样的可能性并将它们合并入图表。这被称为转换表。要创建曲子”Frere Jacque”的如上分析,你需要首先在第一列创建所有当前可能音的图表,在每列之上用所有下一个音标注一行。第一个音是C,跟着是D。我们通过在D列下C行写1表述这种可能性组合。

p1
p1

接下来,我们汇总D跟随C的次数并在那列输入那个数字(2)。接下来检测C,计数C, E, F, G跟随C的次数,然后输入那些值。然后顺序为音D, E等等照搬这个程序。

p2
p2

每一行的汇总都被列出。每行的可能性计算:每列实际发生数 除以 全部可能结果,因此C列的值将是1/4, 2/4, 1/4。

p3
p3

这是一个一阶(first order)转换表。因为我们仅使用前一个和下一个音符(一个连接),因此将失去旋律进行的令人信服的感觉。要模拟一段旋律,我们真的需要着眼于两或三个音符的模式。这将我们带至二阶(secod order)马尔可夫链。二阶增加了一级音序。即,给最后两个音,下一个音是C, D等等的可能性是什么。这是扩大到涵盖整个”Frere Jacque”的同样的图表,以及一个二阶可能性。有36种组合,但并非它们所有都会发生(比如C-A),无需将它们涵盖入图表,因此我从那些组合中移除了它们。

p4
p4

这有几条指导方针:注意我是在底部汇总了所有组合。这是一个快速检查总链接数是否正确的方式。总数应当是这个片段音符数减二(因为前两个没有三个项目(或二阶)的连接)。另外一件你需要关注的事情是死链接。死链接是在图表中不可能的一行的连接。以C-C组合为例。如果你在C-C行输入一个F列的可能性,那么组合C, C, F会产生。但却没有可能的C-F行,这种情况下程序将返回一个nil值并崩溃(或陷入一个循环)。我没有一个快速或聪明的方式检查你是否有坏的倾向。你只能靠自己细心校对。

百分比图表。

p5
p5

这种体系最大的问题在于内存需求。如果,尼汝说,你要做一个韦伯恩钢琴作品的图表,假设一个四个八度的范围,每个八度12个音,二阶可能性会为每个单独的音要求一个110592引用(reference)的矩阵。如果你将这个模型扩展到涵盖节奏和乐器选择,动态与结合,你会马上处于无数中。因此需要一个更高效的描述矩阵的方式。这也是在下边Froster例子中我做了一些易混淆的动作的原因,但空间节约了捷径(but space saving short cuts)。上边”Frere Jacque”的图表在文件Simple Markov.中进行论证。接下来是一些代码的解释。

29.1. 转换表

//使用的音的集合 

legalPitches = [60, 62, 64, 65, 67, 69]; 

//一个二维数组, 代表每对之前可能的组合 

transTable = [ 
   [0, 0], //C, C 
   [0, 1], //C, D 
   [0, 2], //C, E 
   [0, 4], //C, G 
   [1, 2], //D, E 
   [2, 0], //E, C 
   [2, 3], //E, F 
   [3, 2], //F, E 
   [3, 4], //F, G 
   [4, 0], //G, C 
   [4, 2], //G, E 
   [4, 3], //G, F 
   [4, 4], //G, G 
   [4, 5], //G, A 
   [5, 4] //A, G 
];

使用真实的midi值是低效的,因为如此多的midi值跳入了一个调性计划(tonal scheme)。因此legalPitches被用于描述我所有要用的音,并且实际代码寻找并围绕数组位置工作,不是midi值。(即,包含midi值的数组位置)

变量transTable描述我转换表的第一列。每个可能的之前音被存储于二维数组中。

用来对比与储存当前两个音的值是currentPair。这是一个包含两个元素的单独数组,组合中的第一、二个音我将用于链中。程序之初,它们被设置为0,0或C, C。

下一步,我需要将currentPair与数组transTable匹配。这由do循环搞定。在这个函数内,每个二位(two position)数组将被与变量currentPair(同样是一个二维数组)进行比较。当一个匹配被发现,那个匹配(或者在数组中被发现的位置)的索引便被储存到nextIndex。换句话说,我发现了currenPair的索引位置。这是必要的,因为我将表削减为仅包括我需要用到的组合。

29.2. 剖析转换表

transTable.do({arg index, i; if(index == currentPair, 
{nextIndex = i; true;}, {false})});

接下来我为每个之前的组合描述了索引。如果,例如,当前组合为D, E。它们在transTable中的值应为[1, 2],之上的代码应在数组索引4(记得从0数起)发现这个匹配。这意味着,我应在下边的图表中使用可能性数组的第四位。在这个图表中,我有100%的机会在currentPair用C跟随D, E。我修改(已注明)了Dodge原始图表的可能性。

29.3. 可能性图表

nPitchProb = 
[ 
   //C D E F G A 
   [0.00, 0.33, 0.00, 0.00, 0.66, 0.00], //C, C 
   [0.00, 0.00, 1.00, 0.00, 0.00, 0.00], //C, D 
   [0.00, 0.00, 0.00, 1.00, 0.00, 0.00], //C, E 
   [0.66, 0.00, 0.00, 0.00, 0.00, 0.33], //C, G 
   [1.00, 0.00, 0.00, 0.00, 0.00, 0.00], //D, E 
   [0.50, 0.00, 0.25, 0.00, 0.25, 0.00], //E, C 
   [0.00, 0.00, 0.00, 0.00, 1.00, 0.00], //E, F 
   [1.00, 0.00, 0.00, 0.00, 0.00, 0.00], //F, E 
   [0.00, 0.00, 0.50, 0.00, 0.50, 0.00], //F, G 
   [1.00, 0.00, 0.00, 0.00, 0.00, 0.00], //G, C 
   [0.00, 0.00, 0.00, 1.00, 0.00, 0.00], //G, E 
   [0.00, 0.00, 1.00, 0.00, 0.00, 0.00], //G, F 
   [0.00, 0.00, 0.00, 0.00, 0.00, 1.00], //G, G 
   [0.00, 0.00, 0.00, 0.00, 1.00, 0.00], //G, A 
   [0.00, 0.00, 0.00, 1.00, 0.00, 0.00] //A, G 
];

选择实际上由windex完成。函数windex(加权索引)使用可能性数组作为其第一引数。数组nPitchProb是个二维数组,我想要用那些数组中的一个作为我的可能性数组,比如说索引4。我识别数组中的数组的方式是nPitchProb.at(4)。因为这是一个多维数组,我不得不引用其两个,例如nPitchProb.at(4).at(5)。

使用变量就变为:(nPitchProb.at(prevPitch).at(nextIndex)。windex数组总合为1,我并不记得为什么我输入值的总合为16,但这可以用normalizeSum搞定。windex返回值是一个数组位置,这我将存储于nextIndex,并且告诉我将哪个数组应用于nextPitchProbility的变量也是nextIndex。

变量nextPitch是一个数组位置,它可以之后与legalPitches协同返回正确音的midi值:legalPitches.at(nextPitch)。它同样被用于一些必需的记录。我需要重新排列currentPair来反映我新的选择。数组currentPair第二位置的值需要被移至第一位置,nextPitch值需要被储存于数组currentPair的第二位置。(换句话说,currentPair是D, E,或数组位置1,2,我选取了C,或对照表,一个0。因此,[1, 2]需要在下次经过函数时变为[2, 0]。)

currentPair.put(0, currentPair.at(1));
currentPair.put(1, nextPitch);

下例展示了一个更复杂的转换表和马尔可夫过程。使用了Dodge写的Computer Music第287页Stephen Foster曲调的详细内容,我不久前将它写成,并且我想还有更高效的制表方法(我搞了一些易混淆的捷径),但无论如何,它起作用了。

持续时间你可以使用第一行:0.125。这可能是一个更精确的实现,因为我们未讨论节奏。但为了给它给多的旋律的感受,我加入了一些随机的节奏单元。我们没有给这个系统任何关于语法结构或韵律的信息,并且你可以听到那些缺失的成分。尽管如此,它还是非常Foster。

29.4. Foster马尔可夫

( 
var wchoose, legalPitches, previousIndex, prevPitch, 
currentPitch, nextIndex, nextPitch, nPitchProb, 
pchoose, blipInst, envelope, pClass, count, resopluck; 

prevPitch = 3; 
currentPitch = 1; 
count = 1; 
pClass = #["A3", "B3", "C4", "D4", "E4", "F4", "F#4", 
         "G4", "A4", "B4", "C5", "D5"]; 

//pchoose是选取下一个值的机制. 

pchoose = 
{ 
legalPitches = [57, 59, 60, 62, 64, 65, 66, 67, 69, 71, 72, 74]; 

//prevPitch和nextPitch都不是音, 而是数组位置. 

previousIndex = [ 
   [2], //之前是0或A3 
   [2], //1或B3 
   [0, 1, 2, 3, 4, 5, 7, 9, 10], //2: C4 
   [1, 2, 3, 4, 7, 10], //3: D4 
   [2, 3, 4, 5, 7, 8], //4: E4 
   [4, 5, 7, 8], //5: F4 
   [7], //6: F#4 
   [2, 4, 5, 6, 7, 8, 10], //7: G4 
   [2, 4, 5, 6, 7, 8, 10], //8: A4 
   [8, 10], //9: B5 
   [7, 8, 9, 10, 11], //10: C5 
   [7, 9] //11: D5 
]; 

previousIndex.at(prevPitch).do({arg index, i; if(index == currentPitch, 
                {nextIndex = i; true;}, {false})}); 

nPitchProb = 
[ 
// [00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11] array position 
// A, B, C, D, E, F, F#, G, A, B, C, D 
[ //A3数组 
   [00, 00, 16, 00, 00, 00, 00, 00, 00, 00, 00, 00] // 一个数组: C4 
], 
[ //B3数组 
   [00, 00, 05, 06, 00, 00, 00, 05, 00, 00, 00, 00] // 仅是C4 
], 
[ //C4数组 
   [00, 00, 16, 00, 00, 00, 00, 00, 00, 00, 00, 00], // A3 
   [00, 00, 16, 00, 00, 00, 00, 00, 00, 00, 00, 00], // B3 
// [00, 02, 02, 09, 02, 10, 00, 00, 00, 00, 00, 00], 初始的C4 
   [00, 06, 02, 09, 02, 06, 00, 00, 00, 00, 00, 00], // C4 
   [00, 00, 03, 04, 08, 00, 00, 01, 00, 00, 00, 00], // D4 
   [00, 00, 00, 07, 03, 02, 00, 04, 00, 00, 00, 00], // E4 
   [00, 00, 00, 00, 11, 00, 00, 00, 05, 00, 00, 00], // F4 
   [00, 00, 00, 00, 04, 00, 00, 12, 00, 00, 00, 00], // G4 
   [00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 16, 00], // A4 
   [00, 00, 00, 00, 00, 00, 00, 02, 11, 03, 00, 00] // C5 
], 
// A, B, C, D, E, F, F#, G, A, B, C, D 
[ //D4数组 
   [00, 00, 16, 00, 00, 00, 00, 00, 00, 00, 00, 00], // B4 
// [01, 00, 01, 04, 05, 00, 00, 01, 00, 01, 03, 00], 初始的C4 
   [05, 00, 01, 04, 01, 00, 00, 01, 00, 01, 03, 00], // C4 
// [00, 01, 12, 01, 02, 00, 00, 00, 00, 00, 00, 00], 初始的D4 
   [00, 06, 07, 01, 02, 00, 00, 00, 00, 00, 00, 00], // D4 
   [00, 00, 01, 03, 06, 04, 00, 01, 01, 00, 00, 00], // E4 
   [00, 00, 00, 00, 00, 00, 05, 08, 03, 00, 00, 00], // G4 
   [00, 00, 00, 00, 00, 00, 00, 00, 00, 16, 00, 00] // C5 
], 
[ //E4数组 
   [00, 00, 00, 12, 03, 01, 00, 00, 00, 00, 00, 00], // C4 
// [00, 02, 07, 03, 02, 00, 00, 01, 00, 01, 00, 00], 初始的D4 
   [00, 05, 04, 03, 02, 00, 00, 01, 00, 01, 00, 00], // D4 
   [00, 00, 03, 04, 06, 02, 00, 01, 00, 00, 00, 00], // E4 
   [00, 00, 00, 00, 04, 03, 00, 06, 03, 00, 00, 00], // F4 
   [00, 00, 00, 00, 02, 00, 00, 10, 03, 00, 01, 00], // G4 
   [00, 00, 00, 00, 00, 00, 00, 16, 00, 00, 00, 00] // A4, 
], 
// A, B, C, D, E, F, F#, G, A, B, C, D 
[ //F4数组 
   [00, 00, 00, 08, 00, 08, 00, 00, 00, 00, 00, 00], // E4 
   [00, 00, 00, 00, 00, 08, 00, 08, 00, 00, 00, 00], // F4 
   [00, 00, 02, 00, 00, 00, 00, 10, 00, 00, 04, 00], // G4 
   [00, 00, 00, 00, 00, 00, 00, 16, 00, 00, 00, 00] // A4, 
], 
[ //F#4数组 
   [00, 00, 00, 00, 00, 00, 00, 00, 16, 00, 00, 00] // G4, 
], 
[ //G4数组 
   [00, 00, 00, 11, 05, 00, 00, 00, 00, 00, 00, 00], // C4 
   [00, 00, 05, 04, 03, 01, 00, 02, 01, 00, 00, 00], // E4 
   [00, 00, 00, 00, 16, 00, 00, 00, 00, 00, 00, 00], // F4 
   [00, 00, 00, 00, 00, 00, 00, 16, 00, 00, 00, 00], // F#4 
   [00, 00, 00, 00, 04, 01, 04, 04, 03, 00, 00, 00], // G4 
   [00, 00, 01, 00, 01, 00, 05, 07, 01, 00, 01, 00], // A4 
   [00, 00, 00, 00, 00, 00, 00, 06, 05, 03, 02, 00] // C5 
], 
// A, B, C, D, E, F, F#, G, A, B, C, D 
[ //A4数组 
   [00, 00, 16, 00, 00, 00, 00, 00, 00, 00, 00, 00], // C4 
   [00, 00, 00, 11, 05, 00, 00, 00, 00, 00, 00, 00], // E4 
   [00, 00, 00, 00, 00, 00, 00, 00, 16, 00, 00, 00], // F4 
   [00, 00, 00, 00, 00, 00, 00, 16, 00, 00, 00, 00], // F#4 
   [00, 00, 01, 00, 09, 01, 00, 02, 01, 00, 02, 00], // G4 
   [00, 00, 00, 00, 02, 00, 00, 12, 00, 00, 02, 00], // A4 
   [00, 00, 00, 00, 00, 00, 00, 09, 02, 05, 00, 00] // C5 
], 
[ //B5数组 
   [00, 00, 00, 00, 00, 00, 00, 16, 00, 00, 00, 00], // A4 
   [00, 00, 00, 00, 00, 00, 00, 00, 06, 00, 00, 10] // C5 
], 
// A, B, C, D, E, F, F#, G, A, B, C, D 
[ //C5数组 
   [00, 00, 00, 00, 14, 00, 00, 02, 00, 00, 00, 00], // G4 
   [00, 00, 00, 00, 00, 01, 00, 05, 06, 00, 04, 00], // A4 
   [00, 00, 00, 00, 00, 00, 00, 00, 12, 00, 04, 00], // B4 
   [00, 00, 00, 00, 00, 00, 00, 00, 16, 00, 00, 00], // C5 
   [00, 00, 00, 00, 00, 00, 00, 05, 00, 11, 00, 00] //D5 
], 
[ //D5数组 
   [00, 00, 00, 00, 00, 00, 00, 16, 00, 00, 00, 00], // G4 
   [00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 16, 00] // B4 
] 
]; 

nextPitch = (nPitchProb.at(prevPitch).at(nextIndex).normalizeSum).windex; 

//当前的被设置为之前的, 下一个是下一轮的当前. 
//实际的音是从nextPitch返回的合法音 

[pClass.at(nextPitch), legalPitches.at(nextPitch)].post; 
// if((count%10) == 0, {"".postln};); 
count = count + 1; 
prevPitch = currentPitch; 
currentPitch = nextPitch; 
legalPitches.at(nextPitch) 
}; 

Pbind( 
   \dur, 0.125, 
   \dur, Prand([ 
            Pseq(#[1]), 
            Pseq(#[0.5, 0.5]), 
            Pseq(#[0.5, 0.5]), 
            Pseq(#[0.25, 0.25, 0.25, 0.25]), 
            Pseq(#[0.5, 0.25, 0.25]), 
            Pseq(#[0.25, 0.25, 0.5]), 
            Pseq(#[0.25, 0.5, 0.25]) 
            ], inf), 
   \midinote, Pfunc(pchoose), 
   \db, -10, 
// \instrument, "SimpleTone", 
   \pan, 0.5 
).play 
)

数据文件,数据类型

像之前章节说的,把转换表存入一个文件可能是很有用的,这样一来,程序就可与不同创作的特定表独立开来。数据文件之后可以被用作基础马尔可夫patch的模块组件。

文本文件,包含字符。但计算机是通过数字(ascii数字)来认知它们。你使用的文本编辑程序将数字转化为字符。你可以使用SimpleText创建一个包含字符代表一个转换表的文件,但那些数字不仅是数字,相当于字符。对cpu来说,“102”不是整数102,而是代表102的三个字符(它们的ascii数字为49,48和50)。下边的图表(见原书P321)展示了ascii数字以及它们对应的字符。低于32的是非打印字符,比如回车,tab,beep和段落标记。空格(32)的ascii数也被包括在了这里,因为它如此普遍。这个图表停止于127(8位数字最大值),但仍有大于127的ascii字符。类似的字符通常是可区别的组合和拉丁字母。

p6
p6

如果你想测试的话,用BBEdit,SimpleText或MS Word(仅保存文本)创建一个文本文档并运行这几行代码,它们检索文本文档内每个8位整数然后作为整数列印,之后是ascii对应的。

29.5. ascii测试

var fp; 
fp = File("Testascii.rtf", "r"); //打开一个文本文档 
fp.length.do({a = fp.getInt8; [a, a.ascii].postln}); //以整数读取文件

适合做转换表(整数或浮点)的数据可以被文本编辑器打开,但可能显示乱码,而不是转换数据。因此问题是,该如何创建一个数据文件?真并不像一个文本文件那么简单。这必须由一个读写不仅仅是字符还包括数据流的程序完成。SC可以创建这样的文件。(但还请继续读下去,因为还有更简单的方法。)

上边的转换表使用整数,但可能性表示用浮点数。区分两者很重要。下边是读写包含整数和浮点值文件的代码。对于整数,有三个消息:putInt8, putInt16, 和 putInt32 分别用于8位(一个字节),16比特(两个字节),和32字节(四个字节)。每个大小都有个限制容量。一个8位整数最长到128,16位可以长到32768,32位有多于20亿的容量。对于浮点数,有两条信息:putFloat 和 putDouble。“Double”更大因此更精确,但它们占据两倍空间,其实Float对我们正在做的东西也足够了。对于字符来说,消息putChar和putString可被使用。

理解这些数据类型很重要,因为你需要读取和你所写的同样的类型。如果你以16位整数写入但却以8位整数读取它们,数字将会不同。接下来是读写浮点值和整数的代码段。

29.6. 数据文件

var fp, data; 
fp = File("TestInt", "w"); //打开一个文件 
data = [65, 66, 67, 68, 69, 70, 71]; 
data.do({arg eachInt; fp.putInt16(eachInt)}); //将每个整数放入文件 
fp.close; 

var fp, data; 
fp = File("TestInt", "r"); //打开一个文件 
data = fp.readAllInt16; //以整数数组读取一切 
data.postln; 
fp.close; 

var fp, data; 
fp = File("TestFloat", "w"); //打开一个文件 
data = [6.5, 6.6, 6.7, 6.8, 6.9, 7.0, 7.1]; 
data.do({arg eachFloat; fp.putFloat(eachFloat)}); 
fp.close; 

var fp, data; 
fp = File("TestFloat", "r"); //打开一个文件 
data = fp.readAllFloat; //以数组读取一切 
data.postln; 
fp.close;

我选择写整数65~71,因为它们与ASCII字符A~G一致。位证实这点,用SimpleText, MS Word, 或 SC打开TestInt文件(应该仅含整数,没有字符),证实这些程序已将它们转换为文本字符。

解释字符串

包含整数和浮点数的文件仍将呈现一个问题。管理数据比较困难,尤其它们被集成到二维数组,比如马尔可夫链。的确没有简单便捷的方法来检查文件的数据正确有否。你无法以一个文本文件来读它。你不得不以8,16,32位或浮点值的形式读取它。如果你碰到任何不对的数据类型,或者如果一个值在错误的位置,结构和数组将被关闭。(别误会:这不可能完成,它将仅是一个麻烦(hassle)和错误请求(invites error)。)

如果使用一个文本编辑器或SC创建文件将更简单一些,但以数据文件的方式读取它们。或者以字符串方式读取它们但将它们分析为正确的数据。

对于C的用户来说,你们知道管理和分析数据意味着大量的编程。一如往常,在SC中这很简单。interpret消息将一个字符串翻译为SC懂得的代码。你可以保存全部函数、数据结构、列表、错误信息,以及可以用SC和任何文本编辑器编辑的文件中的宏指令,但在SC patch中被用作代码。

欲测试这点,首先打开一个新窗口(仅文本,非富文本)并输入代表二维数组的这几行,包含不同的数据类型(注意我并未以一个分号结尾):

[ 
   [1, 2, 3], 
   [2.34, 5.12], 
   ["C4", "D4", "E4"], 
   Array.fill(3, {rrand(1.0, 6.0)}) 
]

我人为地使用若干不同数据类型填充数组,包括字符串,以此阐明在SC中这多么简单,而在其他更古老的语言中有多复杂。如果我是使用C编译器来管理文本,我必须保持对下列的持续捕捉:每种数据类型、每个数组中的项目数、每行的总大小和长度,等等。总之很麻烦。

运行下述代码,注意,当读取文件的初始,第一个postln显示那个数组确实是一个字符串,但在后来的代码中,它却被当作代码对待。

29.7. 解释字符串

var fp, array; 
fp = File("arrayfile", "r"); 
array = fp.readAllString; //将文档读入一个字符串 
array.postln; //列印它,以证明它是一个字符串 
array = array.interpret; //再次解释并储存它 
array.at(0).postln; //正是它是代码而非字符串 
array.at(1).sum.postln; 
array.at(2).at(0).postln; 
array.at(3).postln;

这个方法的好处是,我可以用BBEdit或其他任何文本编辑器打开arrayfile,并将数据当作文本来修改。更有前途的:我可以从数据库例如FileMaker Pro或Excel将数据当作一个文本文件导入。任何可以被存为文本的数据源都可以被读入SC程序。现在,便可以为马尔可夫patch使用含不同可能性数据的模块文件了。

[延伸讨论:遗传机率(genetic probabilities)]

]]>
http://learn.travelchinawith.me/?feed=rss2&p=1288 2 1288
SC:额外音乐准则驱动音乐,数据文件 http://learn.travelchinawith.me/?p=1286 http://learn.travelchinawith.me/?p=1286#respond Tue, 17 May 2011 08:26:56 +0000 http://www.ccttours.com/blog/?p=1286 Continue reading SC:额外音乐准则驱动音乐,数据文件 ]]> 本章,我们将测试与额外音乐准则相连的音乐选择的可能性。几乎所有音乐都对创作者的选择具有音乐性上的影响,这里,我们将强化那个联系。这些准则可能包含诸如植物的电子脉冲、星星的位置、一个山的轮廓、或者雨雪的模式等等自然数据;它们可能包含类似其他音乐、文字、发车时间表等人类体系;或者掩饰的输入(masked input),比如在演出空间中,动作被映射到音高、振幅、音色等等之上的一个听众。

任何数据流,无论是从一个现存的文件读取,还是来自一个实时的输入源(比如鼠标或视频链接),可以被用于影响音乐的参数。SC提供了若干外部控制源,诸如MouseX和Y、Wacom手写板控制、MIDI输入、读取文档数据等等。

为什么要用其他数据源?因为模式和结构可以从额外音乐源传送至创作。一副星图,举个例子,用X、Y位置可以生成一组均匀分布的事件;一个山的轮廓将提供一个平滑的连续事件流。

除了外部标准提供的结构,还有一个为创作带来额外意义的哲学联系,例如,从声纹(voice print)中提取的音色,或一场演出中最喜欢的那首诗。

历史提供了很多关于这种类型主题连接的例证,特别是文字对创作的影响。例子不仅仅包括音乐化一个词汇,还包括对词汇精准的音乐符号化复制,就像巴赫的二重赋格,从四个音Bb, A, C, B(在德国是:B, A, C, H)开始。文字是一个普遍性选择,因此让我们从这开始吧。

最简单的将文字与音乐联系的方法是给每个字母一个MIDI值:a=0,b=1,z=23等等。这将提供两个八度的音程。字母表的字母在计算机代码内被描述为ascii值。即,计算机将字母“a”理解为一个整数。这对我们来说是一个方便的巧合。ascii数字已经存在于计算机里,我们仅需要调整它们到合适的范围即可。

文字转换

digit或ascii消息可以被用于将一个字符转换为数字。在digit下,”a” 和 “A” = 10, “z” 和 “Z” = 35。消息ascii返回实际的ascii值:A = 65, a = 97, Z = 90, z = 122。再次尝试运行下边代码以证实ascii的关联。

28.1. ascii值

a = "Test string"; 
a.at(2).ascii.postln; 
a.at(2).digit.postln; 
a.do({arg each; each.post; " ".post; each.ascii.postln;})

ascii值(大写字母的)的范围同样便利地与midi值匹配:65(F4的midi数)至90(F6的midi数)。但小写字母就有一点点超过了这个范围。因此你可以仅使用大写字母,或者你可以简单的加减缩放(scale)小写字母,或者全部。

这个直接关联(A = 60, Z = 90)带来的问题很明显:你会被字母的值给绊住。字母a和e都将始终是低音。又由于a和e在字母中出现的频率都很高,因此这些音产生的概率将会更高。

映射(mapping)

映射将允许我们对音符和字母间的关联进行更好地控制。为一个输入赋值,不管流程内每个项目的内在规定(intrinsic value)。这样,你便可以在控制创作本质(the nature of the composition)的同时,保留流程内模式的连接。在字母中,比如,我们知道元音几乎每隔一个字母就出现。有时两个成一行,偶尔又被四个辅音隔开。在这个认知下,我们可以使用一个映射,分配特殊的音高到元音上,并获得(举个例子)一种音调(tonality)的感觉:a = C4, e = E4, i = G4, o = C5 (a = 60, e = 64, i = 67, o = 72)等等。你也可以限制你的映射到使用中的字母,省略(或包括)类似spaces、punctation等等词。

SC使用IdentityDictionary创建一个映射,如下所示。变量pitchMap被分配到包含一对元素(初始字母和它的关联值)的数组IdentityDictianary。关联是通过语法”originalValue -> associatedValue” 或在当前情况下 “$b -> 4” 的方式实现的。意思是,让字母b等于整数4。

28.2. pitchMap

pitchMap = IdentityDictionary[ 
   $H -> 6, $x -> 6, $b -> 6, $T -> 6, $W -> 6, 
   $e -> 11, $o -> 11, $c -> 11, $, -> 11, $. -> 11, 
   $n -> 3, $y -> 3, 
   $m -> 4, $p -> 8, $l -> 9 
      ];

与字母关联的数字之后被用到MIDI音上。另一种选择,它们可以被用作MIDI音程。

在为若干项目使用IdentityDictionary后,我发现它很难处理数值改变(需要输入太多)。所以我选择使用一个二维数组。第二维的第一元素是组成一个字符串的字母列表,第二元素是关联的值。它们经由一个do函数分析,这个函数遍历每个第一元素,如果找到匹配的(使用includes),则将变量mappedValue设置到第二元素。

28.3. 映射数组

var mappedValue, intervMap; 

intervMap = [ 
   ["ae", 2], ["io", 4], [" pst", 5], ["Hrn", -2], 
   ["xmp", -1], ["lfg", -4], ["Th", -5], [".bdvu", 1] 
]; 

intervMap.do({arg item; 
   if(item.at(0).includes($o), 
      {mappedValue = item.at(1)}) 
   });

这个patch仅控制在绝对关联(即,实际的音而非音程)内被音化(pitched)的元素。

28.4. 额外音乐准则, 仅音

( 
var noteFunc, blipInst, midiInst, channel = 0, port = 0, prog = 0, 
   intervMap, count = 0, ifNilInt = 0, midin = 0, inputString; 
 
//输入流. 

inputString = "Here is an example of mapping. The, them, there, these," 
   "there, then, that, should have similar musical interpretations." 
   "Exact repetition; thatthatthatthatthatthat will also" 
   "be similar."; 

//intervMap包含字母和值的集合的数组。 
//在下边的函数中,字母字符串与数字相连 

intervMap = [ 
   ["ae", 2], ["io", 4], [" pst", 5], ["Hrn", 7], 
   ["xmp", 1], ["lfg", 3], ["Th", 6], [".bdvu", 11] 
]; 

"// [Char, Interval, ifNilInt, midi interval, octave, midi]".postln; 

noteFunc = Pfunc({var parseInt, octave; 

   //intervMap内的每个数组都在检查是否包含字母(inputString.wrapAt(count)) 
   //如果包含,parseInt被设置为在item.at(1)的值 

   intervMap.do({arg item; 
      if(item.at(0).includes(inputString.wrapAt(count)), 
         {parseInt = item.at(1)}) 
      }); 

   //如果parseInt是notNil, midin就被设置为那样. 
   //ifNilInt用来储存每个要被使用的parseInt 
   //如果没有匹配被发现并且下次的parseInt是nil。 

   if(parseInt.notNil, 
      {midin = parseInt; ifNilInt = parseInt}, 
      {midin = ifNilInt} 
   ); 

   octave = 60; 

   "//".post; [inputString.wrapAt(count), parseInt, 
      ifNilInt, midin, octave/12, midin + octave].postln; 

   count = count + 1; 

   midin + octave 
}); 

Pbind( 
   \midinote, noteFunc, 
   \dur, 0.125, 
   \amp, 0.8, 
   \instrument, "SimpleTone" 
).play; 
)

我承认这并不十分有趣。这有部分因为独自的音值并不对可辨认的风格作出解释。我们更习惯于不和谐的一个特定级别,而并非单独的音序。不谐和是音程分布的作用,而不是音的分布。为得到一个音程选择的特定平衡,intervMap应包含比例音程值而非绝对音。数字可能看上去完全一致,但在实际分析中,我们将使用midin = parseInt + midin%12(因此parseInt变为一个音程)而不是midin = parseInt。有趣的东西同样可以因将音映射到若干个八度,或映射八度选择到字母上而出现。

28.5. 额外音乐准则, 完全控制

( 
var noteFunc, blipInst, midiInst, channel = 0, port = 0, prog = 0, 
   intervMap, count = 0, ifNilInt = 0, midin = 0, ifNilDur = 1, 
   durMap, durFunc, ifNilSus = 1, susMap, susFunc, ifNilAmp = 0.5, 
   curAmp = 0.5, ampMap, ampFunc, inputString; 

//输入流 

inputString = "Here is an example of mapping. The, them, there, these," 
   "there, then, that, should have similar musical interpretations." 
   "Exact repetition; thatthatthatthatthatthat will also" 
   "be similar."; 

//intervMap包含字母和值的集合的数组。 
//在下边的函数中,字母字符串与数字相连 

intervMap = [ 
   ["ae", 6], ["io", 9], [" pst", 1], ["Hrn", -3], 
   ["xmp", -1], ["lfg", -4], ["Th", -5], [".bdvu", 1] 
]; 

durMap = [ 
   ["aeiouHhrsnx", 0.125], ["mplf", 0.5], ["g.T,t", 0.25], 
   ["dvc", 2], [" ", 0] 
]; 

susMap = [ 
   ["aei ", 1.0], ["ouHh", 2.0], ["rsnx", 0.5], ["mplf", 2.0], ["g.T,t", 4.0], 
   ["dvc", 1.0] 
]; 

susMap = [ 
   ["aei ", 1.0], ["ouHh", 2.0], ["rsnx", 0.5], ["mplf", 2.0], ["g.T,t", 4.0], 
   ["dvc", 1.0] 
]; 

ampMap = [ 
   ["aeHhrsnx ", 0.8], ["ioumplfg.T,tdvc", 1.25] 
]; 

noteFunc = Pfunc({var parseInt, octave = 48; 

   //intervMap内的每个数组都在检查是否包含字母(inputString.wrapAt(count)) 
   //如果包含,parseInt被设置为在item.at(1)的值 

   intervMap.do({arg item; 
      if(item.at(0).includes(inputString.wrapAt(count)), 
         {parseInt = item.at(1)}) 
      }); 

   //如果parseInt是notNil, midin就被设置为那样. 
   //ifNilInt用来储存每个要被使用的parseInt 
   //如果没有匹配被发现并且下次的parseInt是nil。 

   if(parseInt.notNil, 
      {midin = parseInt + midin%48; ifNilInt = parseInt}, 
      {midin = ifNilInt + midin%48} 
   ); 

   [inputString.wrapAt(count)].post; 
   ["pitch", parseInt, midin, octave/12, midin + octave].post; 

   midin + octave 
}); 

durFunc = Pfunc({var parseDur, nextDur; 

   durMap.do({arg item; 
      if(item.at(0).includes(inputString.wrapAt(count)), 
         {parseDur = item.at(1)}) 
      }); 

   if(parseDur.notNil, 
      {nextDur = parseDur; ifNilDur = parseDur}, 
      {nextDur = ifNilDur} 
   ); 
   ["dur", nextDur].post; 
   nextDur 
}); 

susFunc = Pfunc({var parseSus, nextSus; 

   susMap.do({arg item; 
      if(item.at(0).includes(inputString.wrapAt(count)), 
         {parseSus = item.at(1)}) 
      }); 

   if(parseSus.notNil, 
      {nextSus = parseSus; ifNilSus = parseSus}, 
      {nextSus = ifNilSus} 
   ); 
   ["sustain", nextSus.round(0.01)].post; 
   nextSus 
}); 

ampFunc = Pfunc({var parseAmp; 

   ampMap.do({arg item; 
      if(item.at(0).includes(inputString.wrapAt(count)), 
         {parseAmp = item.at(1)}) 
      }); 

   if(parseAmp.notNil, 
      {curAmp = curAmp*parseAmp; ifNilAmp = parseAmp}, 
      {curAmp = curAmp*ifNilAmp} 
   ); 

   count = count + 1; 
   if(0.5.coin, {curAmp = rrand(0.2, 0.9)}); 
   ["amp", curAmp.round(0.01)].postln; 

   curAmp.wrap(0.4, 0.9) 
}); 

Pbind( 
   \midinote, noteFunc, 
   \dur, durFunc, 
   \legato, susFunc, 
   \amp, ampFunc, 
   \instrument, "SimpleTone" 
).play; 
)

与文件协同工作

将你想用的文本或数据值输入实际代码文件内往往是不实际的。一旦你设计了一个可接受的文本映射,你便可以将映射、创作和文本想做一个驱动音乐的模块组件。在这种情况下,一个将文本作为数据流读入正在运作程序的方法便是必须的。随着这个功能的到位,映射创作便可与储存欲读数据的文件分离。SC具有标准的文件管理工具可被用于这一目的。

与我们协同工作的文件类型是文本文档(看下边的数据类型),因此我们应当创建文本文件。但MS Word文档,甚至一个rtf文档,都含有非文本格式化信息,这是一种干扰。我尝试用SimpleText, BBEdit, SC 编辑器 和 Word 创建“文本”文件,Word和BBEdit是唯一两个不产生不需要东西的编辑器。

路径名同样需要一点点的解释。当SC(以及绝大多数程序)打开一个文件时,它会首先在SC所在目录内寻找这个文件。因此,如果你使用的文件与SC位于同一文件夹内,那么路径可以仅是文件名。如果文件位于别处,一个文件夹层级必须包含进去。层级的写法类似超链接,各个文件夹间用斜杠分隔开。如果数据文件位于SC文件夹内的某文件夹内,那么路径名可以由那个文件夹开始。如果文件位于SC文件夹之外,那么你必须给出整个文件的路径。因此,一个位于SC文件夹内的文件即简单的“我MyFile”,如果位于子文件夹,它可能是”/Data Files/MyFile”,如果在其他区域,那么可能是”/Users/Students/Documents/Computer Music/Data Files/MyFile”。

要打开或读取文件,首先申明一个承载文件的变量。然后使用File()打开文件并表明你要用的模式(read, write, append, 等等)。在本例中,我们使用读取模式,或者“r”。

一旦打开了文件,便可使用getChar一次性检索全部字符,但我想建议读取整个文档并将它存储到一个数组,因为在之前的例子中,文本被存储在一个数组内。这里是代码,假设文本文档被命名为“Text File”。这部分可以被插入上例input = “Here is . . . 等等的部分。

28.6 读取一个文件

( 
var input, filePointer; //申明变量 
filePointer = File("Text File", "r"); 
input = filePointer.readAllString; 
filePointer.close; 
input.postln; 
)

更长,但也更对用户友好的方法,使用openDiaglog。我通常只用一个文件工作,因此这是我并不需要的额外一步。但在很多情况下,它去掉了路径名的猜测。它同样可被用于指出任何给定文件的确切路径名。有一个小故障:要么一切都在openDialog里,要么使用一个全局变量并分别运行代码的两个部分。

28.7. 读取一个文件

// 列印全部路径名备用 

File.openDialog("", { arg pathName; pathName.postln}); 

// 用dialog打开 
( 
var input, filePointer; //申明变量 
File.openDialog("", {arg pathname; 
   filePointer = File(pathname, "r"); 
   input = filePointer.readAllString; 
   input.postln; 
   // 所有东西都应在这个函数内 
}, 
{"File not found".postln}); 
) 

// 或打开文件并存入全局 

( 
var filePointer; //申明变量 
File.openDialog("", {arg pathname; 
   filePointer = File(pathname, "r"); 
   ~input = filePointer.readAllString; 
}, 
{"File not found".postln}); 
) 

// 然后 

~input.postln; 

// 或者将~input含入patch
]]>
http://learn.travelchinawith.me/?feed=rss2&p=1286 0 1286
SC:继续完全序列化,特殊考量 http://learn.travelchinawith.me/?p=1284 http://learn.travelchinawith.me/?p=1284#respond Wed, 11 May 2011 08:26:03 +0000 http://www.ccttours.com/blog/?p=1284 Continue reading SC:继续完全序列化,特殊考量 ]]> 绝对值 vs. 比例值,节奏反向

[本章绝大多数偏向创作,并无太多SC的东西]

做序列化的一个选择是选择绝对或比例值。绝对值将一成不变,一个绝对的C4将永远是C4。比例值基于一个公式或上一个值计算。使用同样的比例,却返回不同的值。五度(fifth)音程是比例值一个很好的例子。较弱这样,实际返回的值可能是C4或D-flat3(不懂如何翻译..D降3?),但它将永远比上一个值大1/5。比例值对音高、下一事件、持续时间和振幅起作用。它在振幅方面尤为有用,允许序列化在音量间渐变。

比例事件之于音乐非常有意义,因为它让我们从关系的角度认知音乐:这一事件与上一事件相关。旋律的逻辑依靠每两个音符间的比率(ratio)决定。节奏基于一拍的比率:这一拍的事件是上一拍事件的两倍。

当选择绝对音高时,你也可基于另一个音高控制一个音高的概率。例如,你可以偏重选择C和G,感觉就很主音(tonic)。但使用比例值,你可以在和谐音程(consonant interval)上偏重选择,或更多不和谐音程(dissonant intervals),从而控制不谐和的水平。

在使用比例值时,超越边界的危险性更大。因为你无法在一个范围内指定绝对音高,因此你将受系统支配,并会定期走出范围。解决方式是使用一个缓冲(buffer),或像之前例子中那样折回(wrap around)。

对绝大多数参数来说,一个比例值通常是一个浮点数字。在0.0~1.0间的比例选择将产生一个比当前值小的新值。(比如说,持续时间为2,比例值为0.75,那最终的下一个值将是1.5)如果比例值大于1.0,那结果值将更大。(持续时间为2,比例值为1.25,那最终的下一个值将是2.5)

音高

在使用比例音高体系时,有两件重要的事需要考虑。如果你使用频率工作,你可以将音程描述为当前值的一部分。比如说,如果你正演奏A 440并想为下一事件一个八度的比例值,那么比率是2:1(值*2)。注意音程比率比如2.0(八度音阶),1.5(五度),1.333(四度),1.25(三度)等等,仅仅是音程。我们在绝大多数现代音乐中使用等程音阶(平均律音阶)。

如果,另一方面,你要平均律,然后使用MIDI数字。每个MIDI数字代表钢琴上的一个键,音色由合成器决定。为使用MIDI音色的比例系统运用的数学也不一样。除非你想增减值,否则无需乘法。给一个从C4(MIDI数字60)开始的起点,C4上五度是G4(MIDI数字67)。为得到音程,你为五度加7,四度加5,三度加4,三度以下-4,等等。一系列MIDI音程的反向很简单:midiArray.neg。它取得值并反向全部正负号,比如将5变成-5,-10变成10。

27.1. 比例的MIDI反向

// 如果使用MIDI音程,这是同度的,4th上, M3rd上, 
// 5th上, M2下, M6th下, 2nd上, ttone down 

o = [0, 5, 4, 7, -2, -9, 2, -6]; 

o.neg; 
// 结果一致, 4th下, M3rd下, 等等 
[ 0, -5, -4, -7, 2, 9, -2, 6 ]

持续时间和下一事件

你可能会认为由于我们的系统基于比例值(一拍的商或积)标记,那么比例持续时间将非常有意义。实际上,它很快就变得非常复杂。对持续时间来说,一系列附加到比例的分数在传统标记法下很难表述。这是一串令人迷惑的简单比例值:1, 1.5, 1.5, 1.5, 0.5, 0.5。以1秒作为起点,或四分音符的一拍 = 60bpm:1×1=1(四分音符)×1.5=1.5(附点四分音符)×1.5=2.25(half tied to a sixteenth,ok,我又不知道该怎么翻了)×1.5=3.375(dotted half tied to sixteenth tied to thirty-second,还能再麻烦一点吗?)×0.5=1.6875(??)×0.5=0.84375(??)。这么一堆如何可能用传统标记法表述?这是计算机依人类体系思考的失败例证,或我们应该思考的另一问题?

测量音乐的细分没有固有的错误。我们喜欢它,因为这是一个易于上手的简单模式。比例策划(proportioanl scheme)仍可被每拍使用:这拍比上拍有更多分部(division)。这拍有上拍1/2的事件。

比例持续时间计划的另一问题是值0。在SC中,为下一事件使用0是合法的,代表一个和弦或一个同时事件。但一旦你日到0便无法逃亡,因为0乘任何数皆为0。解决方式是测试值、为下一事件提供非0值(但之后你将不再能在一个和弦拥有超过两个音符),或更佳的,将和弦想做一个单独事件,然后决定这个事件是否是一个和弦,以及和弦里有多少个元素,与下一事件区分。比如说,判定每个事件是否是休息的(rest),单独的音符,或者一个和弦。如果是和弦,判定和弦里有多少个音。将它们全部作为当前持续时间,然后判定下一事件仍未一个和弦,一个休止,或一个单独音高。

另一个可能是永远不允许将0作为一个选择,但设想同时事件(simultaneous events)为一个对位法(conuterpoint)函数:设计两或三行有时会同时出现为一个和弦的代码。这个表述的难点在于持续捕捉来自两个独立系统的音程结果。

下一事件

另一个调节休息(rest)和同时事件的方法是分别计算持续时间和下一事件。若这样的话,即使当前事件设置为4秒下一事件也可是1秒。或下一事件可以是0,生成同时事件,同时每个同时事件的持续时间可以不同。当然下一事件长于当前事件的持续时间时,休息便会发生。

非时序事件

并不需要循序(sequentially)计算事件。我们太频繁的假设那个事件应该是线性和连续的。(一个很好的理由:我们循序的认知音乐,像一系列关系。)另一途径是选取一个音发声的时间点,而不是选取下一个音。比如说,如果你首选决定一个作品的时间(比如四分钟),你将在这个连续统一体的时间线的某些地方放置项目。音1可能别放置在2分23秒,持续3秒,音2可能被放置在1分13秒。这在连续性上出现了问题。像在之前指出的,我们循序的认知央视,因此如果事件不是循序计算的,那么维持主旋律一致性将变得困难。一个解决之道是包含一个音序和非音序事件的混合体。主题和动机可能被循序的充实,但向上边描述的那样被植入时空。然而,我认为时序与非时序的方法更多属于学术范畴。

振幅

在振幅的情况下,比例系统也可以很好的工作,因为振幅常是之前值(渐强或渐弱)的一个函数。

节奏反向

最后一点关于序列化节奏:反向。原始以及反向版本的节奏系列(series)是明显不同的,但要如何反向它呢?绝大多数我在其他地方阅读到的解决方式并未抓到节奏反向的要义。状态应被转化到反面,慢到快,长到短,等等。我常使用两种办法来做这个。但两种方法都是有问题的。

首先是密度的反向,这是对行的二进制观测角度。要求你首先决定最小的结合(articulation)。以这为例,让我们使用一个八分音符。这意味着密度测量将由八分音符填满,最少的密度将是一个单独的全音符。你可以使用多个持续时间0和1描述一个测量,于是每个可能的结合点(每个八分音符)便是0或1,0意味着没有链接,1意味着一个单独的链接。使用这个方法,四个四分音符将是1, 0, 1, 0, 1, 0, 1, 0。两个二分音符将是1, 0, 0, 0, 1, 0, 0, 0。逻辑的反向然后将变为简单的0和1的交换。四分音符测量将转换到0, 1, 0, 1, 0, 1, 0, 1,或一个切分八分音符通路(passage)。两个二分音符将转化为0, 1, 1, 1, 0, 1, 1, 1,或一个八分休止符,接着三个八分音符,然后另一个八分休止符,接着三个八分音符。

这种方法的第一个问题是你被限制于仅可用申明了的合法值的小盒子里。第二个问题是,倒置可能生成一个结合不等的数字,并将因此要求音高或动态系列的不同大小。(例如,设想你正在使用一个8个音的序列,和一个由两个二分音符、四个四分音符以及两个全音符组成的节奏序列。初始的节奏序列使用8个结合点,但反向将产生24个结合点。你打算如何处理这些多出来的音符?)一个解决方式是确保仅使用全部结合点的一半,这样反向便将是另外一半。值的总量将始终如一。但这看起来像是一个有限的约束。

另一办法,是将每个持续表述为一个给定脉动的比例。四分音符是1.0,二分音符是2.0,八分音符是0.5,等等。这是节奏反向将是倒数:2转化为1/2,1/2转化为2。这个计划(scheme)维持了一个系列内结合的数量并满足了一个反向的逻辑(快到慢,慢到快,密到稀薄:一个活跃的密度线,比如说,全部是十六分音符,将转化为一条松散的长线,全部为全音符)。这个系统唯一的毛病是,反向的时间总合可能会与初始的行(row)完全不同。如果你与若干声音协同工作,这将会使行内其他元素失去同步。

你可以使用normalizeSum消息强制比例适应规定期限(prescribed time)。打个比方,数组[1, 1.5, 0.25, 4, 0.25],总共持续6秒。“倒数”反向将是[1, 0.67, 4, 0.25, 4 ],总共持续9.92秒。使用normalizeSum,减小数组中的每个值,使其总合为1.0。([1, 0.67, 4, 0.25, 4 ].normalizeSum.round(0.01))的结果是[ 0.1, 0.07, 0.4, 0.03, 0.4 ]。那些值总合为1.0,如果我们希望它们持续6秒,那很简单,乘以6就可以了:[ 0.1, 0.07, 0.4, 0.03, 0.4 ]*6 = [ 0.6, 0.42, 2.4, 0.18, 2.4 ]。

结果常是非常复杂的,并再次迅速从绝对多数创作者关于节奏可能性的狭窄概念中走出来,也许是件好事。

Ex. 21.1

var rhythmArray, orLength, inversion; 

rhythmArray = [1, 1.5, 2, 1.25, 0.25, 0.25, 1.5, 0.333]; 
orLength = rhythmArray.sum; 

inversion = rhythmArray.reciprocal.normalizeSum*orLength; 
inversion.postln; 
rhythmArray.sum.postln; 
inversion.sum.postln;

Terry Lee,之前的一个学生,相信我对于保存总持续时间的常是一个卷积(convolution),并且当你反向时间的时候你应当得到一个不同的总持续时间。我才如果你持有这样的观点话,你需要确保你一次性反转了全部声音。那样,或不在意其他声音输出的不匹配。

]]>
http://learn.travelchinawith.me/?feed=rss2&p=1284 0 1284
SC:Pbind, 变异, Pfunc, Prand, Pwrand, Pseries, Pseq, 序列化 http://learn.travelchinawith.me/?p=1281 http://learn.travelchinawith.me/?p=1281#comments Tue, 10 May 2011 08:43:58 +0000 http://www.ccttours.com/blog/?p=1281 Continue reading SC:Pbind, 变异, Pfunc, Prand, Pwrand, Pseries, Pseq, 序列化 ]]> Pbind将音乐事件的不同参数联系在一起。它使用一个模式将那些值流化(stream)到当前的环境。环境(Environment)是另一个我们之前无需考虑的幕后结构。它包含了一组附属于符号(symbol)的全局默认值。下边是一个不完整的列表。你会发现它们常是以不同方式表述的同样的值:freq和midinote,amp和db。

\amp = 0.1, \db = -20, \degree = 0, \dur = 1, \freq = 261.62, \legato = 0.8, \midinote = 60, \note = 0, \octave = 5, \out = 0, \pan = 0, \root = 0, \scale = [0, 2, 4, 5, 7, 9, 11], \server =default, \velocity = 64, \instrument = default, \out = 0, \group = 0

Pblind通过匹配一个符号(比如 \freq)到一个值(比如400),或一个返回一个值的函数(比如rrand(400, 900)),可以将值传递到环境。如果什么也不提供,那么将使用默认值。默认值很有有,因为它们允许你集中于你创作的一个或两个元素上。如果你想在音高上做文章,便没有必要指定振幅、持续时间或乐器,等等:用默认值就行了。

在使用Pbind前,需要载入合成器描述库(synth description library)。因此对于本章的例子们来说,我假设你已运行过下边的代码。

26.1. 读取全局库

SynthDescLib.global.read

最普通的Pbind只设置一个值。比如下例中的频率。 符号是\freq,值是600。

26.2. 基础Pbind

Pbind(\freq, 600).play

当你使用其他控制流程后会变得更有趣。第一例用Pfunc评估任意函数,在这里是音高随机值。第二例增加持续时间。第三例用符号 \degree和 \octave的方式传递MIDI音。degree是音程度,octave是8度,5是C4八度。

26.3. Pbind与频率函数

Pbind(\freq, Pfunc({rrand(100, 900)})).play; 

Pbind( 
   \freq, Pfunc({rrand(100, 900)}), 
   \dur, Pfunc({rrand(0.1, 1.5)})).play 

Pbind( 
   \degree, Pfunc({8.rand}), 
   \oct, Pfunc({rrand(3, 7)}), //或者试试 \octave? 
   \dur, 0.2).play 

Pbind( 
   \scale, [0, 2, 4, 6, 8, 10], 
   \degree, Pfunc({6.rand}), 
   \oct, Pfunc({rrand(3, 7)}), //或者试试 \octave 
   \dur, 0.2).play 

Pbind( 
   \scale, [0, 2, 3, 5, 6, 8, 10, 11], 
   \degree, Pfunc({8.rand}), 
   \oct, Pfunc({rrand(3, 7)}), //或者试试 \octave 
   \dur, 0.2).play

目前为止,你设计的所有乐器在环境中都是可用的。所有与那些乐器结合创建的引数都是可以与值相搭配的符号。如果碰巧你使用了像midinote这样的引数,它们将正好与现存环境相合。如果你将它们命名为其他名字,比如midiPitch,为结合(articulation)使用art,你只需要使用那些符号。

26.4. Pbind与先前的乐器定义

Pbind( 
   \instrument, "KSpluck3", 
   \midiPitch, Pfunc({rrand(34, 72)}), 
   \art, Pfunc({rrand(1.0, 2.0)}), 
   \dur, 0.1 
).play

dur, legato, nextEnvent

我常将一个事件持续的时间与到下一个事件的持续时间加以区分。Pbind中默认的\dur被用于两者,但真正意味的是到下一个事件的持续时间。一个事件实际的持续时间由legato决定,它是dur的百分比。默认值是0.8。因此,如果两个事件间的持续时间为4,那这个事件的实际持续时间将是0.8×4 = 3.2。因此legato(连奏)可被用于实际的持续时间(2.5连奏 + 4 持续时间 = 10 实际持续时间)。

在我创建KSpluck3时,引数art(结合)真正意味持续时间,但也代表结合。在这个例子中,\dur意味着两个事件间的持续时间。嘿,它们只是变量和引数。随意命名它们就好。

你可以使用Pbind载入一个效果。但因为这是一个单一事件,所以实际没有必要:首先运行它(记住,先输入后输出),然后在Pbind里运行一个路由到它的合成器。

26.5. Pbind与之前的效果

Synth("delay1"); 

Pbind( 
   \instrument, "bells", 
   \freq, Pseq([100, 400, 1000, 1500, 2000]) 
).play;

Pbind在篡改(interplotion)中很有用:用少量的调整转化一个现存的风格(style)或模式(pattern)。我首先定义一个有着适于连续学习(serial studies)的简单乐器。记住,我们不关注乐器的特质。我们的焦点在音高、持续时间、振幅…上。记住你每新建一个乐器,必须重读DescLib

26.6. 简单串行乐器

( 
SynthDef("SimpleTone", 
{arg midinote = 60, amp = 0.9, dur = 1, pan = 0, legato = 0.8; 
   Out.ar(0, 
      Pan2.ar( 
         SinOsc.ar(midinote.midicps, mul: amp) 
         * 
         EnvGen.kr(Env.perc(0, dur*legato), doneAction: 2), 
         pan 
) 
) 
}).load(s); 

SynthDescLib.global.read 
)

接下来是在音高篡改研究中起作用的一个模型。MIDI音符由一个例程(routine)决定:一个能记录之前状态的函数,比如计数器。旋律是巴赫的。如果你熟悉原作的话,我想要指出以下几点:旋律被移调到一个八度上(当我们熟悉了这个转变,效果是一样的),我重复了一些音符以闪避节奏值。同样的,效果是一样的。

相比直接输入MIDI值的做法,我选择使用degree和scale数组,它们协同工作。Scale是和声小调的整/半音级方向。Degree是scale使用的音级(scale step)。二者结合使用的好处是,它允许我在不打断演奏重新输入所有音符的前提下改变模式(例如,从和声小调到里底安调式(Lydian))。数组degree保持原样,我可以改变音阶。

degree数组被-1调整,仅为迎合本人的口味。我常为音阶数组使用0,1,2,为音级使用1,2,3。但如果用乐理思考的话,改写旋律的音符将更简单,即,1是音阶的第一级,[1, 3, 5]是主音琶音,[1, 4, 6]是第一转位(inversion)。

26.7. 变化音高模型

( 
var degreeSeq, nextPitch, scale; 

scale = [0, 2, 3, 5, 7, 8, 11]; 

degreeSeq = [1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 
   3, 3, 5, 5, 1, 1, 4, 4, 7, 7, 2, 2] - 1; 

nextPitch = Routine({ 
   inf.do({arg count; 
      scale.wrapAt(degreeSeq.wrapAt(count)).yield; 
   }) 
}); 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, nextPitch + 60, 
   \dur, 0.1 
   ).play 
)

此处,你可以用实际回放快速满足你创作的好奇心。如果反向(count.neg)演奏的话会怎样呢?每隔一个音呢(count*2)?每隔两个,隔三个,等等。一个转位会怎样(12 — scale.wrapetc.)?均分间隔(scale = [etc.]/2),两倍间隔,不同的模式(scale = [0, 2, 3, 5, 7, 8, 9]))?

26.8. 实验

// 增加倍数 
   var mul = 1; 
   inf.do({arg count; 
      if(count%20 == 19, {mul = mul + 1}); 

// 偶发随机值 
   inf.do({arg count; 
      if(count%6 == 5, {12.rand.yield}, 
      {(scale.wrapAt(degreeSeq.wrapAt(count*mul))).yield}); 

// 偶发闪避音序 
   if(count%6 == 5, {scale.choose.yield}, 
   {(scale.wrapAt(degreeSeq.wrapAt(count*mul))).yield}); 

// 逐渐转变音阶 
   if(count%6 == 5, {scale.put(scale.size.rand, 12.rand)}); 
   (scale.wrapAt(degreeSeq.wrapAt(count))).yield;

Prand, Pseries, Pseq

其实有一堆有用的模式(看Streams的帮助文档)可用。我将在此展示三个。Prand从一个数组返回一个随机选取(另请参阅Pwrand,或者加权随机模式),Pseries返回一组具备引数start,step和length的值。Pseq看起来被使用的最多。它遍历数组的值。它们可以全部被嵌套,如下例所示。

26.9. 模式

( 
f = 100; 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pfunc({ 
      f = ([3/2, 4/3].choose) * f; 
      if(f > 1000, {f = f/8}); //.fold 或 .wrap 没有做到我想做的 
      f.cpsmidi 
      }), 
   \dur, 0.2 
   ).play 
) 

( 
Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Prand([60, 62, 64, 65, 67, 69, 70], inf), 
   \dur, 0.1 
   ).play 
) 

( 
// 数组dur由多个1和2组成, 代表八分音符. 
Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([70, 58, 60, 62, 60, 58, 65, 62, 70, 
      65, 62, 65, 63, 62, 63, 65, 58, 62, 65, 69], inf), 
   \dur, Pseq([2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 
      2, 1, 1, 1, 1, 2, 2, 2, 2, 2] * 0.2, inf) 
   ).play 
)

当然,你可以运行若干个Pbind(巴赫可能会想要这么干)。为dur 2和3尝试以1或0.5替代。Pbind同样响应mute(静音)和unmute(取消静音)。聆听下例足够长时间,以听到相移:

26.10. 平行Pbind

( 
a = Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([70, 58, 60, 62, 60, 58, 65, 62, 70, 
      65, 62, 65, 63, 62, 63, 65, 58, 62, 65, 69], inf), 
   \dur, Pseq([2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 
      2, 1, 1, 1, 1, 2, 2, 2, 2, 2] * 0.1, inf), 
   \pan, -1 
   ).play; 

b = Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([70, 58, 60, 62, 60, 58, 65, 62, 70, 
      65, 62, 65, 63, 62, 63, 65, 58, 62, 65, 69, 0], inf), 
   \dur, Pseq([2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 
      2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2] * 0.1, inf), 
   \pan, 0 
   ).play; 

c = Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([70, 58, 60, 62, 60, 58, 65, 62, 70, 
      65, 62, 65, 63, 62, 63, 65, 58, 62, 65, 69, 0, 0], inf), 
   \dur, Pseq([2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 
      2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2] * 0.1, inf), 
   \pan, 1 
   ).play; 
) 

a.mute; 
b.mute; 
a.unmute; 
c.mute; 
b.unmute; 
c.unmute;
// Riley? 
( 
p = Array.fill(20, {[0, 2, 4, 7, 9].choose + [60, 72].choose}).postln; 
q = p.copyRange(0, p.size - 2).postln; 
Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([Pseq(p), Pseq(p), Pseq(p)], inf), 
   \dur, 0.1, 
   \pan, -1 
   ).play; 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq([Pseq(p), Pseq(p), Pseq(q)], inf), 
   \dur, 0.1, 
   \pan, 1 
   ).play; 
)
// 或平缓相位 
( 
p = Array.fill(20, {[0, 2, 4, 7, 9].choose + [60, 72].choose}).postln; 
Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq(p, inf), 
   \dur, 0.1, 
   \pan, -1 
   ).play; 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq(p, inf), 
   \dur, 0.101, 
   \pan, 1 
   ).play; 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Pseq(p, inf), 
   \dur, 0.102, 
   \pan, 0 
   ).play; 
)

这儿是一些嵌套的模式。Pbind是一个做序列化(serialization)的优秀工具。下边就是一个部分节奏被序列化(即兴缩混)的12音例子,动态级(dynamic levels)同样是半序列化半随机的。

26.11. 序列主义(Serialism)

( 
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].scramble.postln; 
r = [0.1, 0.1, 1.0, 0.2, 0.3, 0.166, 0.166]; 
o = [48, 60, 72]; 

Pbind( 
   \instrument, "SimpleTone", 
   \midinote, Prand( 
      [ //P, R, I, IR 
         Pseq(a) + o.choose, 
         Pseq(a.reverse) + o.choose, 
         Pseq(12 - a) + o.choose, 
         Pseq((12 - a).reverse) + o.choose 
      ], inf), 
   \dur, Pseq([Prand([0.1, 0.2, 0.5, 1.0], 7), 
            Prand([Pseq(r), Pseq(r.reverse)], 1)], inf), 
   \amp, Prand([ 
            Pseries(0.1, 0.1, 5), // 渐强 
            Pseries(0.9, -0.1, 6), // 减弱 
            Prand([0.1, 0.3, 0.5, 0.7], 5) 
            ], inf) 
).play; 
)
( 
// 恩,当然, 也可以一次三个 
// 如果是除0外的种子,那粒种子将被使用。 
// 如果是0, 一个随机的种子将被选取和列印。 
// 用它重复一个演奏 

var seed = 0; 

if(seed !=0, {thisThread.randSeed = seed}, 
   {thisThread.randSeed = Date.seed.postln}); 

a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].scramble.postln; 
r = [0.1, 0.1, 1.0, 0.2, 0.3, 0.166, 0.166]; 
o = [48, 60, 72]; 

Pbind( 
   \instrument, "SimpleTone", 
   \pan, -1, 
   \midinote, Prand( 
      [ //P, R, I, IR 
         Pseq(a) + o.choose, 
         Pseq(a.reverse) + o.choose, 
         Pseq(12 - a) + o.choose, 
         Pseq((12 - a).reverse) + o.choose 
      ], inf), 
   \dur, Pseq([Prand([0.1, 0.2, 0.5, 1.0], 7), 
            Prand([Pseq(r), Pseq(r.reverse)], 1)], inf), 
   \amp, Prand([ 
            Pseries(0.1, 0.1, 5), // 渐强 
            Pseries(0.9, -0.1, 6), // 减弱 
            Prand([0.1, 0.3, 0.5, 0.7], 5) 
            ], inf) 
).play; 

Pbind( 
   \instrument, "SimpleTone", 
   \pan, 0, 
   \midinote, Prand( 
      [ //P, R, I, IR 
         Pseq(a) + o.choose, 
         Pseq(a.reverse) + o.choose, 
         Pseq(12 - a) + o.choose, 
         Pseq((12 - a).reverse) + o.choose 
      ], inf), 
   \dur, Pseq([Prand([0.1, 0.2, 0.5, 1.0], 7), 
            Prand([Pseq(r), Pseq(r.reverse)], 1)], inf), 
   \amp, Prand([ 
            Pseries(0.1, 0.1, 5), // cresc 
            Pseries(0.9, -0.1, 6), // decresc 
            Prand([0.1, 0.3, 0.5, 0.7], 5) 
            ], inf) 
).play; 

Pbind( 
   \instrument, "SimpleTone", 
   \pan, 1, 
   \midinote, Prand( 
      [ //P, R, I, IR 
         Pseq(a) + o.choose, 
         Pseq(a.reverse) + o.choose, 
         Pseq(12 - a) + o.choose, 
         Pseq((12 - a).reverse) + o.choose 
      ], inf), 
   \dur, Pseq([Prand([0.1, 0.2, 0.5, 1.0], 7), 
            Prand([Pseq(r), Pseq(r.reverse)], 1)], inf), 
   \amp, Prand([ 
            Pseries(0.1, 0.1, 5), // cresc 
            Pseries(0.9, -0.1, 6), // decresc 
            Prand([0.1, 0.3, 0.5, 0.7], 5) 
            ], inf) 
).play; 
)

好吧,这不是韦伯恩(Webern)。但经过20分钟的代码输入后已经非常接近了。要点是这个模型具有一切你可以用手写出的序列作品的潜力。像我之前说过的,代码无需是作品本身(尽管这是我的偏爱)。它可以是一个测试场。一旦你找到一个你喜欢的变奏(为不同的变奏换随机的种子),将它拷贝出来然后交给演奏者。

使用MIDIout而不是合成与服务器做序列化

Pbind与服务器交流。我发现MIDI在序列化学习中更有用,因为那些创作通常是被指定给现场演出者的,用MIDI的话,我可以选择无数的乐器。MIDI也更高效,因为服务器不需要为乐器设计和回放进行任何计算。你可以保持服务器运行,或使用quit停止它们。或者直接无需开启它们。

下一个例子不是最严格意义上的完全序列化。仅仅是音高数组依据12音规范被序列化掉:在每个引用(12个音符,或者count%12)后,一个新行被选中。其他的序列设置被跟随一阵子然后被搅拌,大约10%的时间(0.1.coin)。我没有在每个系列中加入太多思考,但你懂的。

在r.stop之后,4.do在四条通道内停止我们正在使用的全部音符。

练习:仅使用MIDI的完全序列化

26.12. 巴比特(Babbitt): 完全序列化 (某种程度)

( 
MIDIClient.init; 
m = MIDIOut(0, MIDIClient.destinations.at(0).uid); 
) 

( 
var pitchSeries, octaveSeries, durationSeries, nextSeries, dynSeries, instSeries; 
pitchSeries = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].scramble.postln; 
octaveSeries = Array.fill(6, {[36, 48, 60, 72].choose}).postln; 
durationSeries = Array.fill(23, {rrand(0.1, 3.0).round(0.1)}).postln; 
nextSeries = Array.fill(30, {[0, 0, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.4, 0.4, 
2.0].choose}).postln; 
dynSeries = Array.fill(24, {rrand(40, 120).round(0.1)}).postln; 
instSeries = Array.fill(20, {4.rand}).postln; 

r = Task({ 
   inf.do({arg count; 
   var note; 
   note = pitchSeries.wrapAt(count) + octaveSeries.wrapAt(count); 
   if(count%12 == 0, { 
      pitchSeries = // 选择一个新行 
         [pitchSeries.reverse, // 倒退 
         (12 - pitchSeries).reverse, // 反向倒退 
         12 - pitchSeries, // 反向 
         pitchSeries // 同等音(prime) 
         ].choose; 
      // 选择一个行移调(transposition of the row) 
      pitchSeries = (pitchSeries + 12.rand)%12; 
      pitchSeries.postln;}); 
   if(0.1.coin, { 
      durationSeries = durationSeries.scramble.postln; 
      nextSeries = nextSeries.scramble.postln; 
      dynSeries = dynSeries.scramble.postln; 
      instSeries = instSeries.scramble.postln; 
      }); 
   m.noteOn(instSeries.wrapAt(count), note, dynSeries.wrapAt(count)); 
   thisThread.clock.sched(durationSeries.wrapAt(count), 
      {m.noteOff(instSeries.wrapAt(count), note); nil}); 
   nextSeries.wrapAt(count).wait 
   }) 
}); 

r.start; 
) 

r.stop; 4.do({arg j; 127.do({arg i; m.noteOff(j, i, 0)})})

使用Pbind的MIDI

Pbind相较Task提供更多的功能。要与Pbind协同使用MIDI,你首先需要创制一个可以处理MIDI乐器的环境。这个模型由Julian Rohrhuber提供,我并未对其进行任何改动。

26.13. Pbind 和 MIDI, Julian Rohrhuber提供

(
var f;
f = (
   noteOn: #{ arg chan, midinote, amp;
               [chan, midinote, asInteger((amp * 255).clip(0, 255))]
                 },
   noteOff:#{ arg chan, midinote, amp;
               [ chan, midinote, asInteger((amp * 255).clip(0, 255))]
                 },
   polyTouch: #{ arg chan, midinote, polyTouch=125;
                         [ chan, midinote, polyTouch]
                 },
   control: #{ arg chan, ctlNum, control=125;
                         [chan, ctlNum, control]
                 },
   program: #{ arg chan, progNum=1;
                         [ chan, progNum]
                 }
     touch ( chan, val )
   bend ( chan, val )
   allNotesOff ( chan )
   smpte ( frames, seconds, minutes, hours, frameRate )
   songPtr ( songPtr )
   songSelect ( song )
   midiClock ( )
   startClock ( )
   continueClock ( )
   stopClock ( )
   reset ( )
   sysex ( uid, Int8Array )

   */
);

~midiEnv = (
            chan: 1,
            msgFuncs: f,
            hasGate: true,
            midicmd: \noteOn,
            play: #{
               var freqs, lag, dur, sustain, strum;
               var tempo, bndl, midiout, hasHate, midicmd;

               freqs = ~freq = ~freq.value + ~detune;

               tempo = ~tempo;
               if (tempo.notNil) {
                  thisThread.clock.tempo = tempo;
               };

               if (freqs.isKindOf(Symbol).not) {
                  ~finish.value;
                  ~amp = ~amp.value;
                  strum = ~strum;
                  lag = ~lag;
                  sustain = ~sustain = ~sustain.value;
                  midiout = ~midiout;
                  hasHate = ~hasGate;
                  midicmd = ~midicmd;
                  bndl = ~msgFuncs[midicmd].valueEnvir;

                  bndl = bndl.flop;

                  bndl.do {|msgArgs, i|
                        var latency;

                        latency = i * strum + lag;

                        midiout.performList(midicmd, msgArgs);
                        if(hasHate and: { midicmd === \noteOn }) {
                           thisThread.clock.sched(sustain) {
                              midiout.noteOff(*msgArgs)
                           };
                        };
                  };
                  }
               }
           ).putAll(
              Event.partialEvents.pitchEvent,
              Event.partialEvents.ampEvent,
              Event.partialEvents.durEvent
           )
);

// 初始化midiout
(
MIDIClient.init;
m = MIDIOut(0, MIDIClient.destinations.at(0).uid);
)

// 我修改了Pbind以显示更多的值 [它们不是非常好]
(
Pbind(
   \parent, ~midiEnv,
   \midiout, m,
   \chan, Pseq([0, 1, 2, 3], 60), //total number of events
   \amp, Prand([
      Pseq([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]),
      Pseq([0.8, 0.7, 0.5, 0.3, 0.1]),
      Prand([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], 10)
      ], inf),
   \dur, Prand([0.1, 0.1, 0.1, 0.2, 0.2,1.0, 2.0], inf),
   \sustain, Pfunc({rrand(0.1, 2.0)}),
   \midinote, Prand([36, 38, 40, 42, 43, 45, 47, 49, //synthetic scale
      50, 52, 54, 56, 57, 59, 61, 63, 64, 66, 68, 70], inf)
).play;
)
]]>
http://learn.travelchinawith.me/?feed=rss2&p=1281 1 1281
SC:电脑音乐美学 http://learn.travelchinawith.me/?p=1277 http://learn.travelchinawith.me/?p=1277#respond Fri, 29 Apr 2011 08:15:29 +0000 http://www.ccttours.com/blog/?p=1277 Continue reading SC:电脑音乐美学 ]]> 译者:yang2 | 编辑:ww1way

为什么用电脑做音乐?

你已经学了这么长时间的电脑音乐课,无疑你会有一些想法. 这里是我为什么用电脑作曲的原因: 它们迅速 fast, 精确 accurate, 复杂 complex, 全面 thorough, 顺从 obedient, 而且不带个人意见. 它们会完全按照你的意思来进行,完全顺从你的意见,不抱怨也不提问.它们永远不会用它们的想法来添加或者篡改你的作品.

电脑引发实验. 这是很明显的变化(或者 改写, 替代, 变形); 使用一个已有的 pattern或者音乐作品去转变为一个新的事物. 一个例子是我们重新制作巴赫的 “Come Sweet Death.” 这是一个简单的好主意. 结果也很惊人. 原来赞美诗中每个合唱部分都在新规则下转变; 每个表演者换气的时候他们将移动到下一个音高. 尽管作曲家对整个有了大概的构想但是直到他邀请唱诗班视唱时他才可以了解全局.

另一个例子包括播放选中的音高段落(每个其他音调,每个第三方音调), 混合类型 (用Webern的模进或者倒影Bach中的每个音程), 调率(减少所有音程的1/2,纯率,不同的类型),倒向,倒置,降音,等等. 用纸笔来做这个,要花掉表演者很多的时间并且容易令人气馁 .学习一种电脑语言也要花时间,但结果,你可以仅仅变换两三个数字就能立即听到你的构思.

精确

我被微分音microtones和调律吸引. 这是, 恕我直言, 这是艺术音乐的下一大步. 但是几百年来微音程的交流几乎等于零. 它的爱好者受到排挤并且被看作住在另一个星球上的怪物. 他们不得不制作他们自己的乐器.他们的乐谱上落满了灰尘,被填满了难以忍受的做作的符号.

一台计算机对于440和440.001没有区别.目前我不推荐用计算机来作为表演者 .也许它的角色是老师;来训练表演者区分微音程之间细微的区别.它最终还是来自未来的符号.为什么是符号而不是作者向表演者传达意图的简单交流?我们使用纸笔因为这是大多数表演者了解的一种科技.为什么所有东西都用纸? 我们不能说0和1这些来自计算机的符号吗? 如果我们可以用某种方法格式化它使表演者可以理解,那么我们会使用这些符号. 那用音乐本身怎么样,最高级的语言?表演者对这种形式理解的最好.之前我们不得不设计一些附属的东西比如提示某些特殊音程的内码,是因为我们要用纸来工作?为什么不直接把那些音程交给表演者?其实这样的事情我们经常做;现在谱子总是和CD一起捆绑以便于可以更好的练习.

总的来说,我们的编曲总是被某些科技限制住了,我们使用平均律是因为钢琴,可能对钢琴家正好合适. 因为无法处理它的按键发出弦乐里面的降A或者升G.所以我们编曲的时候把它们看作同一音高.而并非如此.难道作曲者用不到降A和升B吗?正如论证的那样,很多都可以,比如弦乐,人声甚至brass都可以作出区别.但我们无法分辨.

对于节奏来说也是同理.我们记下乐曲每个部分的节拍不仅是为了令人满意,还因为我们不得不把他们记录在纸上.必须有一个令你和表演者达成一致的参照物:节拍 .有时作曲者敢于冒险创作出一个不匀称的部分(7:8).但是很少出现一个不匀称和正规节拍地混合体(一个7:8的1/4,1/16,1/8,符点1/4)或者其他更加复杂的作品.如果你创作了更复杂的(7/8衍生出来11/8)谁能来表演它??你知道谁可以?谁可以正确的演奏出来?

一台计算机对于处理节奏加速0.0001秒没有任何难度. 再一次强调,我不建议计算机在所有情况下都作为演奏者出现.也许它是是谱子.为什么不直接给予表演者节拍呢: 当听见嘀嗒声(或者看到屏幕固定在某一小节51)他们开始演唱.计算机是谱子和指挥.

复杂和全面: I Dig You Don’t Work

想像一下这个复杂的主意: 32个部分, 每个播放一个节奏乘更高的倍数的拍子: 1 到 1, 2 到 1, 3 到 1, 4 到 1, 等等. (类似谐波序列), 所有的方式都是 32 到 1. 表演者? 毫无机会. 计算机? 小菜一碟. 事情正如Jon Appleton曾经说过的那样,限制不在于机器本身,而在我们感知的能力.

计算机也将尝试各种组合, 一些由于我们的偏见被忽视.拿出一些纸然后用”I dig you don’t work.”重新组合成一首诗.”我们发现了30多个或者更多的有实际意思的组合. 但是计算机能够更快的显示每个组合. 你被鼓励去考虑每一个变量的有效性因为它服从于设计或者系统(全部可能迭代). 你可能会意想不到地碰到曾经在纸上列出过的有实际意义的组合.下面是这个练习的SC 版本. 这是一个详尽的迭代法,但是没有重复使用的单词.有多少拥有实际意义? Nearly all except those where you and I are consecutive.

空间和时间轴的示谱从来不令我信服.表演者估算时间导致了很多偶然的因素.而一个滚动的谱子则能精确完整的匹配.

25.1. I Dig You Don’t Work

var text;
text = ["I", "DON'T", "DIG", "YOU", "WORK"];
121.do({arg i; i.post; text.permute(i).postln;})

或者更棒

Task({
i = 0; t = ["i ", "don't ", "dig ", "you ", "work"];
{
u = "";
t.permute(i).do({|e| u = u ++ e});
u.speak;
i = i + 1;
3.0.wait
}.loop
}).play

顺从和迟钝

计算机是一个顺从的没有个人意见的愚笨的仆人. 它们不会观察不会给出结论. 可是对于人来说,你永远不能阻止他们观察和总结.看一下这一列字母: C C B G A B C C A G.你不可能不在它们里面寻找有意义的暗示. 你可能认为它们是个音阶(但是我说过么?) .大部分人都会问我谁写的这些东西.计算机,不会作出任何假设.如果我让你给出下一段字母,你可能可以做到.而计算机只能揉揉他的数码脑袋.

上述的事情在两种方法中非常实用.第一,它可以改进你的作曲方法.I如果你用代码写了一段不是你想要的东西,那么错误不在于表演者(cpu)而是在于你的系统(代码).有时学生们听到一段重复的音程或者琶音和弦之后他们会放弃当前的随机程序,因为它们认为它并不是随机的.重复音高听起来确实不是随机的,但是它可能是一个随机程序的结果.计算机按照要求做事情. 如果你要一个人们听起来很令人惊讶的结果,你应该设计一个不同的规则. 你作曲的模式达不到你的要求.

人类明显的适合偶然作曲. 你多久表演或者观看表演一次,一些含混的指令或者图形符号作为一个偶然的乐章结束?表演者演奏他设想作曲者可能喜欢的东西;那些实验派假装制造出一些随机,抽象的bee-blonkish那样的东西.我最后一次的经历(也许这就是为什么最后一次)是一张空白的纸上有一个”play”播放图形的按键.它是用一个画图程序制作的,想法粗糙低劣,作者想复制你期待从孩子那里得到的礼物那种感觉(比如一个万花筒,但是远没有那么酷). 我决定”播放”这个程序:劣质的电子艺术赝品. 在我的心理暗示下,我来到了一个令人厌烦正在播放电子板本的莫扎特的VHS倒带机前.作曲家应该去做克塞.我试着变得顺从和愚笨.这是我的错误吗?或者是使用方法的?

计算机也会做一些你意料之外的事情.这是它们的顺从带来的第二个好处.你可能会找到一个你忘记了的主意.一次,我尝试回忆在做”I Don’t Dig You Work”这个练习的时候,犯了一个编码错误.计算机显示出”I”,然后是”I,I”,最后是”I,I,I”.计算机正在按我的要求运行但并不是我想要的结果. 这个时候我有两个选择;作为一个新主意来考虑接受这个反常的结果,或者检查我的命令使它按我的想法来运行.

摒弃偏见

四年以前我在为一个制作儿歌的公司工作. 制作人构思出音乐的概念, 描述他们希望得到的声音,然后我按照他们的描述和我的想像凭直觉写出来. 这些旋律十分的老套和常见; 但却是老板希望得到的. 这个方法本身没有错误. 在那个工作中它也是重要的. 它仅仅是令现在的我不感兴趣.

作曲家直觉的想像一个事件, 描述在这个事件中将会发生的结果, 将这个描述告诉表演者,然后调整这个描述和表演者的理解情况直到这个想像的事件定型. 我喜欢用倒退的方式考虑作曲. 你朝着你最初的想象创作. 我认为这和艺术类比的话更多的是技艺. 你重新制作一些早已在概念里存在的东西. (更接近真理的是作曲家工作在系统和直觉混合的情况下. 我在这个讨论中分开谈论他们.)

更加体系化的创作者们用完全截然不同的方法工作。.创作者在描述的环境中找到结果,而不是想象结果然后描述环境。我认为这才是向前创作的构成(Herbert Br╪ 可能会说区别是在政治上的).我坚信所有真正的革新者这样创作.他们会问:”如果我这样做了,我会得到什么声音”而不是”我想知道我如何得到这种声音”.我仅仅是做出解释,但是我最近读到了一篇在Forbidden Planet工作的作曲家的文章.他们被问到按照什么样的过程或者计划来实现他们的构思.没有人回答.他们的方法是发现而不是发明.他们仅仅是修改好连线,拧一拧旋钮,休息一下,然后对结果惊呼’我想知道如果我这么做会发生什么?’

你如何逃避你自身的偏见? 你可以使用一个重复的方法 (比如尝试调整每一个音程直到有一个让你觉得可用) 或者使用一个随机系统 (为了一个新想法而砰砰作响的弹奏你的钢琴). 上述两者都是系统.(当一个按照直觉创作的人没有了构思,他们也会用这个系统来工作,这就是真理.)

我是一个失败者,但是是因为作曲之外的一些原因.

David Cope的 Virtual Music 被提问到这种作曲方法自从它可以机械的不断制造之后,是创造品还是工艺品.

一种抛弃你偏见的方法就是听一听其它编曲者的作品. 一种新的类型可能会是一个出乎意外的惊喜. 听过一段之后你会适应它,然后你会喜欢它,它会影响你自己的创作类型. 它会变成你偏见中的一个. 但是如果你的脑子被老师们灌输之后会怎么样呢? 在你已经掌握了所有类型之后你能怎么做? 如何继续向前寻找更新的让你和你的老师吃惊的主意? 你可以成为下一个挑战和扩展你的主意的作曲家么?

你用系统。你先提问。

“我的下一个作品,是我目前还未学会喜欢的.” –Herbert Brün

“到达真正自由和从局部依赖[人类偏见]中脱离的钥匙是通过科学的方法. . . . 创新是是知识的产物而不是猜测的结果.在艺术中使用科学的方法会提供出令人难以置信数量的创意,技术上的简便,完美,会给你带来最终的真正自由的感觉,满意和成就感.’ –Joseph Schillinger

对系统诚实

你对于新创意的责任是什么? 当计算机显示出 “I”, “I, I”, “I, I, I” 这是一首诗么? 当然不.

有个态度的范围来衡量一个人向实验前进了多少.第一”这个是我的创作,它对系统有效,是我下一段即将学习并喜欢的部分” .我变的喜欢和著名的作曲家创作,尽管一开始显得笨拙和违反直觉.它让我感到好奇,是否所有的音乐口味都是一个素材的陈列.人们可以对我们的音程和音调系统-从高比率或者不和谐移动到低比率或者和谐的-是否基于科学原理提出争论.但是 Javanese Gamelan把乐曲的音程变为无理性的.它们的旋律却是音乐。环境还是自然?可能两者皆是.

在我们这里工作的秘书派来了一位交流的俄罗斯学生到我办公室,他要对大家展示他所发现的’纯’音乐.经过几分钟的解释,我想起来他曾经说过钢琴没有按照本身的音程来调音而是在计算后按照正确的方法调音(显然他亲自做的) . 他用了一台计算机使用纯比率计算来演奏巴赫一首作品.我不情愿的推倒了Grove,然后打开到描述他真正音乐的那页(他呆在一旁,至少他的数字是正确的.)

我问他关于音高偏移音.他并没有考虑过. 我说:”旋律最后不会偏移出原始音高吗?”他想了一会儿说是的. “你不觉得对耳朵是一种刺激么?” 想了更久,他说:”不会是一种刺激,因为它是有效的[对系统来说].” 我不得不同意,我没有资格评论什么是美的.他对系统的诚实要比实际的声音更重要.我们适应了平静的没有旋律的乐律.100年之内,我们能否接受音高在纯率的声调中偏移变化?

所以最基本要求就是”这是一个作品,因为它对于系统有效.”

下一点要考虑给系统一个生成码在成百上千种变奏都可以使用的地方。我经常不适应同一个特殊的”演奏” 的生成工作.其他的”演奏”都如同一颗宝石.这种情况下系统是一颗被塞满成百上千个花朵的种子.你可以捡起一颗来赞美,也可以赞美他们全部.我曾试着实现Webern’s Kinderstuck(他使用P0时候的一个早期作品) 来替换新版本中每个引用部分的那行.我觉得他们都是一个作品的合理变调.Webern写下了这个遗传密码,我种了许多花朵.但是这个就是他的意图么?在那个超出实际经验的时候(Could that have been his intention, but at the time out of practical reach?)

当我想使用生成这种方法而不是用其他特殊的办法,相对于我强加在每个音符我的想法的时候我感觉更加和谐.

“因为我常偏爱制订计划然后执行它们,因此我更被如下所述的情况和系统所吸引:一旦开始实施操作,可以不借助或很少借助我的介入生成音乐。也就是说,我更趋向于计划者和程序师的角色,而对于结果,我则更像是一个观众” -Brian Eno (引自 Alpern).

“当一个人拥有了正确的艺术概念之后,科学和富于灵感的创作之间便没有差别.进行得越远,每样东西就会变得更加相似,最后会完全抛弃人为的创作,而回归自然.” –Webern

最后,最终一层的诚实是计算机创作的结果可以按照直觉进行,也可以完全扔掉,你要做决定.

现在,进入系统.

]]>
http://learn.travelchinawith.me/?feed=rss2&p=1277 0 1277
SC:更随机的数字 http://learn.travelchinawith.me/?p=1264 http://learn.travelchinawith.me/?p=1264#respond Thu, 28 Apr 2011 08:30:22 +0000 http://www.ccttours.com/blog/?p=1264 Continue reading SC:更随机的数字 ]]> 在这一节,我们将谈谈不同类型的随机进程。并不会有很多人去写纯的随机音乐(尽管看起来像是)。总之这不是非常有趣的。通常会有一些过滤、倾斜(skew)或倾向(bias)。即使看起来选择的面很宽,所有可被听到的频率(30Hz~20000Hz),对于那些频率都有一个倾向。在之前的“随机”音乐范例中,我们倾向于一个MIDI值的范围(而不是连续的频率),可能的持续时间的范围,等等。即使凯奇(John Cage)的作品常被认为是随机的,但他们仍由倾向塑造。4’33″便具有一个强烈的倾向:没有来自于钢琴的音符。

随机是什么?一致是什么?设想一个从完全一致走向彻底随机的音阶。两者最后会是什么?在数字音频方面,一个声音由一个16比特的数字表述,彻底随机意味着一个在一个新采样生成同时在16比特范围内数字选取的平等机会。这样的一个系统将生成噪音。彻底的一致我猜将会全部是0。

我喜欢去思考随机的程度。韦伯听上去比莫扎特更随机。我的意思是韦伯的模式更难辨识。我们在音乐体验中寻找的,是相似点与多样化间那个舒适的平衡点。这随人、心情或环境(比如,音乐厅VS影院(在这里似乎更有耐受力))的不同而不同。有两种接近这个平衡点的方式。一是增加复杂的程度(像在加法合成中做的那样),另一个是过滤复杂的程序(像在减法合成中那样)。在本章中,我们将做过滤。

倾向性随机选取

倾向性随机选取限制或滤除了一系列宽泛可能出现的结果。我们见过它多次,比如随机范围:rrand(60,72)。另一个方法是加重(weight)一个值或一组值出现的可能性。在这些系统中,每个可能性的比重被表述为0~1之间的值,在这里所有可能性最多加到1。在上边那个随机的例子中,任何在60~71之间的值都有平等的可能性被选中,因此每个独立值有1/12或0.08333的机会被选中。

平均分布的可能性图表
平均分布的可能性图表

同样的,在[60, 65, 69, 54].choose中,每个值都有0.25的机会被选中。因为只存在四个值(0.25*4=1.0)。

一个想向单一值倾斜的方式是重复选择:

24.1. 老千骰子

[60, 60, 65, 69, 54].choose

(至于为什么上例60出现的机会会比较高就不用说了吧。)

在10.0.rand的情况下,选择并非离散的整数,但却是可能的浮点值。展示这些机率的图表应是横跨顶部的一条直线,表明每个低值(0.0)到高值(10.0)间的数组都有被平均选取的机会。

一切皆有可能的可能性图表分布
一切皆有可能的可能性图表分布

对这个模型做有倾向的输出需要一点数学知识。

关于随机倾向,这里是一个很好的例子。三个人为得到最大的数字转一个转盘。如果你转到小的数字那么就继续转。结果倾向于较大的数字,因为数字最大代表获胜。

将这个过程用一对骰子展示:两颗骰子一起摇,但取两个数字里最大的那个。可能的组合方式有:
[ 1, 1 ] [ 1, 2 ] [ 1, 3 ] [ 1, 4 ] [ 1, 5 ] [ 1, 6 ]
[ 2, 1 ] [ 2, 2 ] [ 2, 3 ] [ 2, 4 ] [ 2, 5 ] [ 2, 6 ]
[ 3, 1 ] [ 3, 2 ] [ 3, 3 ] [ 3, 4 ] [ 3, 5 ] [ 3, 6 ]
[ 4, 1 ] [ 4, 2 ] [ 4, 3 ] [ 4, 4 ] [ 4, 5 ] [ 4, 6 ]
[ 5, 1 ] [ 5, 2 ] [ 5, 3 ] [ 5, 4 ] [ 5, 5 ] [ 5, 6 ]
[ 6, 1 ] [ 6, 2 ] [ 6, 3 ] [ 6, 4 ] [ 6, 5 ] [ 6, 6 ]

本例可以用这行代码生成:6.do({arg i;6.do({arg j; [i + 1, j + 1].post}); “”.postln;})

每对含6的组合将返回6。但仅有一对会返回1:[1, 1],依此类推。

.
.

有11种组合的结果是6(11/36=0.3),9种组合的结果是5(0.25),7种4(0.19),3种2(0.08),然后仅1种1(0.03)。下边是这些值的可能性分布图表。

可能性分布图表
可能性分布图表

用100面的骰子将给出更多的数字,但趋势将是完全相同的。100和其他任何数将返回100。但仅有1和1的组合将返回1。每个数字返回结果总数可以用公式n*2-1计算。每个数字的可能性由(n*2-1)/total来计算。total是最大的那个值。

24.2. 高倾向计算

n = 100; n.do({arg i; i = i + 1; ((i*2-1)/(n.squared)).postln})

下边是一个基于“两者中较大值”倾向做的50个选择图表。选择越多,可能性图表曲线越平滑。

基于50个选择的可能性图表
基于50个选择的可能性图表

浮点数的随机选取有更多的可能性(尽管仍是有限的)。趋势图是一样的。

在SC中,你可以使用max和min函数。

24.3. 浮点倾向

max(6.0.rand.round(0.01), 6.0.rand.round(0.01));

任何数字和6将返回6,但只有0和0返回0。这个功能的图表将是:

可能性图表
可能性图表

这段代码的可能性图表将是怎样的?

24.4. 倾向

min(100.0.rand, 100.rand);

一个有点点难的问题:这段代码如何改变输出(注意第二个随机选择不是一个误写,我使用100.rand表明这是一个证书选择而不是浮点):

24.5. 倾向

max(200.0.rand, 100.0.rand);

这个怎么样(可能的结果是0~100,但问问你自己有多少组合的结果是0?多少种结果是50?多少种是100?):

24.6. 倾向

(100.rand + 100.rand)/2

如何做一个反向的三角?-10~0间的一个选择?一个对于-10的倾向?在0~10间倾向于7.5的选择?如何将一个选取机率分配给每个值,使得60有0.25的机率被选上,61有0.1的机率,62有0.5,等等?

复杂吗?过去曾经是。SC有一堆很好的随机选取数字倾向函数。在我们着眼它们前,我要示范一个做测试的系统。我惯常使用的方法是申明一个能保持记录随机选取的计数数组,然后依选择的出现增加数组的位置。最后,我使用plot信息,它将绘制这个数组的平面图。运行下边第一行看结果。

24.7. 测试倾向

[0, 1, 0.5, 0.178, 0.9, 0].plot; // plot一个数组 

( 
a = Array.fill(100, {0}); //用0填充一个数组 
1000.do( //做1000次反复; 更多选择, 更平滑的图表 
   { 
      b = 1000.rand; //从0~999间选取一个随机数 
      a.put(b, a.at(b) + 1); //在数组中增加那个位置 
   } 
); 
a.plot; //平面画结果. 
)

随机的选择在0~999间。这个数字被用做一个参考的索引并使用put增加数组a的位置。put的第一引数为数组的索引,第二个是你要储存的值。它在索引b处改变值(通过增大1(a.at(b)+1))。用这个方法便可以测试我们上边讨论的每个随机进程,或列在下边的函数。

在上边的例子中使用某些倾向随机选取会返回浮点数。这时,在将它用于数组索引前,你必须将它转化为整数。

24.8. 测试浮点倾向

( 
a = Array.fill(100, {0}); //用0填充数组 
1000.do( //做1000次反复 
   { 
      b = (100.rand + 100.rand)/2.div(1); 
      a.put(b, a.at(b) + 1); 
   } 
); 
a.plot; //plot结果. 
)

随机函数及测试。

24.9. 随机测试

( 
a = Array.fill(100, {0}); 
1000.do({ 
b = 100.rand; // 0 和这个, rand2 生成负值 (负的这个到这个) 
   a.put(b, a.at(b) + 1); 
}); 
a.plot; 
) 

( 
a = Array.fill(100, {0}); 
1000.do({ 
b = 100.linrand; // 线性分布, bilinrand adds - values 
   a.put(b, a.at(b) + 1); 
}); 
a.plot; 
) 

( 
a = Array.fill(100, {0}); 
1000.do({ 
b = rrand(30, 75); // random range 
   a.put(b, a.at(b) + 1); 
}); 
a.plot; 
) 

( 
a = Array.fill(100, {0}); 
1000.do({ 
b = 100.exprand(1).div(1); 
   a.put(b, a.at(b) + 1); 
}); 
a.plot; 
) 

( 
a = Array.fill(100, {0}); 
1000.do({ 
b = max(100.rand, 100.rand) 
   a.put(b, a.at(b) + 1); 
}); 
a.plot; 
) 

( 
a = Array.fill(100, {0}); 
1000.do({ 
b = max(100.rand, 100.rand, 100.rand); 
   a.put(b, a.at(b) + 1); 
}); 
a.plot; 
) 

( 
a = Array.fill(100, {0}); 
1000.do({ 
//this.coin returns true this percent of the time 
b = if(0.2.coin, {max(20.rand, 20.rand)}, {min(80.rand, 80.rand) + 20}); 
   a.put(b, a.at(b) + 1); 
}); 
a.plot; 
)
]]>
http://learn.travelchinawith.me/?feed=rss2&p=1264 0 1264
SC:字符串,字符串集合 http://learn.travelchinawith.me/?p=1262 http://learn.travelchinawith.me/?p=1262#respond Tue, 26 Apr 2011 07:37:44 +0000 http://www.ccttours.com/blog/?p=1262 Continue reading SC:字符串,字符串集合 ]]> 一个字符串被以一个数组的形式储存。数组最后一个项目是0。0,或终止,代表了字符串的结束。字符串可以是任何用引号包围的字符组合(字词或数字),比如”my 4 strings”。SC内部,将数组中每个字符以ascii整数形式存储。第一位是’m’ (176),第二位’y’ (121),第三位’ ‘ (空格或 32),第四位也是一个字符,不是数字,’4’ (111)。因为是数组的关系,它可以被含入do函数内。

23.1. 作为数组的字符串

"CharacterArray".at(0) // 尝试将索引数在 0 至 13 间改变

"CharacterArray".at(0).ascii

"This is a string that is actually an array".do(
   {arg each, count;
      [count, each.ascii, each].postln;
   })

一个字符串数组是数组的数组或多维数组。数组[“one”, “two” “three”]由三个数组构成。第一个数组包含字符’o’, ‘n’, ‘e’, 和0。第二个数组是’t’, ‘w’, ‘o’, 和0。

数组懂数学。字符串,即使数组懂数学,但也不是你想象的那种。如果你想对一组音移调,你可能会尝试向字符串或字符串数组加一个数字(比如,”C#” + 5),如下例:

23.2. “C” + 5?

("C#" + 5)

(
a = ["C#", "D", "Eb", "F", "G"];
a = a + 5;
a.postln;
)

这个例子能跑,但结果不是你想要的。如果我们需要程序生成数字,但列印字符串,该怎么做呢?可以通过将字符串数组用作参照(reference)来实现。

这是一个字符串数组,其中一行代码列印一个跟随一个随机选择的字符串之一,以及一个随机选择的do:

23.3. pitch array index

(
a = ["C", "D", "E", "F", "G"];
a.at(3).postln; //post item at index position 3 in the array a
)

(
a = ["C", "D", "E", "F", "G"];
a.at(5.rand).postln;
)

(
a = ["C", "D", "E", "F", "G", "A", "B"]; //pitch class array
"count\trandom\tpitch at index:".postln; //header
10.do( //do 10 items
   {arg item, count; //use arguments item and count
      var pick;
      pick = a.size.rand;
      count.post; "\t\t".post; //print the number of this iteration
      pick.post; "\t\t".post; //print the number I picked
      a.at(pick).postln; //print the item at that array position
   })
)

你可以用连结消息++保存postln消息。这用于同时输出两个字符串。因此”this ” ++ “string” 将变为 “this string”。另一个联合数据的方式是列印整个包含一组项目的数组,例如[count, b, a.at(b)].postln;。最后,你可以使用postf,它通过向一个字符串插入一串引数替代”%”字符来格式化一条列印消息。反复运行下例以确定它的确在选取不同的值。第二例是更简明版本。

23.4. 被连结的字符串

(
a = ["C", "D", "E", "F", "G", "A", "B"];
10.do(
   {arg item, count; var b;
      b = a.size.rand;
      ("Item " ++ count ++ " : " ++ b ++ " = " ++ a.at(b)).postln;
      // 或
      // postf("Item % : % = %\n", count, b, a.at(b))
   })
)

更简洁

do(10, { ["C", "D", "E", "F", "G", "A", "B"].wrapAt(100.rand).postln;})

现在我便能用12音旋律结合字符串数组了。

23.5. Every 12-tone row with pitch class strings

(
//初始化 MIDI, 先运行它
var original, total, begin, next, art, pcstrings, count;
original = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
pcstrings = ["C ", "C# ", "D ", "Eb ",
   "E ", "F ", "F# ", "G ", "Ab ", "A ", "Bb ", "B "];
total = 479001600;
count = 0;
begin = total.rand;
next = 0.125;
art = 0.6;
("Total playback time = " ++ (total*next/3600).asString ++ " hours.").postln;
r = Task({
   inf.do({
      var thisVar;
      thisVar = original.permute(begin + count);
      thisVar.do({arg e; pcstrings.at(e).post});
      "".postln;
      (thisVar + 60).do({arg note;
         m.noteOn(1, note, 100);
         thisThread.clock.sched(next*art, {m.noteOff(1, note, 100); nil});
         next.wait
      });
      count = count + 1;
   })
})
)

//然后这些
r.start;
r.stop; 127.do({arg i; m.noteOff(1, i, 0)})

观点一刻

对我来说,计算机辅助创作的目标是机器为我做一切繁琐细节的部分,然后我继续干那些创造性的有趣的部分。下例的结果仅列印音高,而不是音乐。即使如此,我们可以停在这,并且它将在创作练习中很有帮助。这接近于40年前如何做这个事。创作者开始于你现在可以做的事:至少让计算机去处理数字。但占用了四页的代码或上百张穿孔卡片(punch card)。对SC来说,这只用一行。刚好有这样一个例子(第一个值在八分之一内循环,第二个是下一个距八分之一的时间,下一个是MIDI音,下一个是音量):

23.6. 伊利亚克组曲(Illiac suite)?

60.do({[8.rand, 8.rand, (rrand(36, 72)), 10.rand].postln;})

这比希勒(Hiller)50年代的程序先进一大截。但我仍无法将这些东西抄作手稿,然后让其他人演奏它。当今科技的好处是快速的回放。即使我最后的成品是打算为真正的音乐家所用,我仍可以用计算机先做尝试。我可以对比一分钟四次和数月四次的区别。

十年前,我们让CPU能够产生实际的声音,但却要花掉一整夜的时间。在2000年,那段时间被削减到5分钟,但却仍要用上几页代码和两个程序(一个压碎(crunch)数字,另一个生成声音)。今天,声音是实时生成的,而且仅需大约十行代码。

练习,随机学习

这有一个范例。基于几个简单点子的完整创作:三个乐器,一套事件数字,随机选择音高、持续时间、下一事件以及振幅。设置你的MIDI回放,这样通道1,2,3才能有三个不同的乐器。

我需要补充,这个实验并不那么有趣。但我们的目标是快速实验转向。所以听一会儿这个例子,然后操一下那些随机选项再听一会儿。改变持续时间、下一事件、音高选择等等的范围。在随机范围外(比如 duration = rrand(0.5, 2.0)),试试为选择数组设定一个随机索引(比如 duration = durationArray.at(10.rand))。在一个音高数组内尝试不同的scale。用频率替代MIDI值。相比一个无限的do,建立一系列为正式设计定义的do循环,改变每个的参数(20个长循环然后5个短循环,高音然后低音,等等)。

23.7. (倾向性) 随机学习

( 
a = Task({ 
   inf.do({arg i; 
   var note, dur, next, amp, inst; 
   note = rrand(24, 84); 
   dur = rrand(0.1, 0.5); 
   amp = rrand(30, 127); 
   next = rrand(0.1, 0.5); 
   m.noteOn(1, note, amp); 
   thisThread.clock.sched(dur, {m.noteOff(1, note); nil}); 
   next.wait 
   }) 
}); 

b = Task({ 
   inf.do({arg i; 
   var note, dur, next, amp, inst; 
   note = rrand(24, 84); 
   dur = rrand(0.1, 0.5); 
   amp = rrand(30, 127); 
   next = rrand(0.1, 0.5); 
   m.noteOn(2, note, amp); 
   thisThread.clock.sched(dur, {m.noteOff(2, note); nil}); 
   next.wait 
   }) 
}); 

c = Task({ 
   inf.do({arg i; 
   var note, dur, next, amp, inst; 
   note = rrand(24, 84); 
   dur = rrand(0.1, 0.5); 
   amp = rrand(30, 127); 
   next = rrand(0.1, 0.5); 
   m.noteOn(3, note, amp); 
   thisThread.clock.sched(dur, {m.noteOff(3, note); nil}); 
   next.wait 
   }) 
}); 
) 

a.start; 
b.start; 
c.start; 
a.stop; 127.do({arg i; m.noteOff(1, i)}) 
b.stop; 127.do({arg i; m.noteOff(2, i)}) 
c.stop; 127.do({arg i; m.noteOff(3, i)})
]]>
http://learn.travelchinawith.me/?feed=rss2&p=1262 0 1262
SC:集合,数组,索引,数组消息 http://learn.travelchinawith.me/?p=1260 http://learn.travelchinawith.me/?p=1260#respond Wed, 20 Apr 2011 07:45:12 +0000 http://www.ccttours.com/blog/?p=1260 Continue reading SC:集合,数组,索引,数组消息 ]]> 一个集合或数组即一个项目组。数组用方括号包围,每个项目由逗号隔开。下边是一个整数数组。

[1, 4, 6, 23, 45]

字符串也可以被做进数组。字符串即一个字符的组,计算机将之视为一个单独的对象。

["One", "Two", "Three", "Four"]

或者你可以做个混合体。注意“34”(在引号内)并不会被SC识别为整数34,而是一个包含字符3和4的字符串。但1,56和3都是整数。

[1, "one", "34", 56, 3]

你同样可以用这个数组进行数学计算。即,数组懂得数学信息。

22.1. 数组算术

(
a = [1, 2, 3, 4]; //申明一个数组
b = (a + 12)*10; //为数组每个项目加12然后乘10
        //然后将结果存入b
b.postln;
)

你能预见下面各例的结果吗?

22.2. array.do 和数学

(
a = [60, 45, 68, 33, 90, 25, 10];
5.do(
   {
      a = a + 3;
      a.postln;
   }
)
)

22.3. 数组 + 每个项目

(
a = [60, 45, 68, 33, 90, 25, 10];
5.do(
   {arg item;
      a = a + item;
      a.postln;
   }
)
)

这有点儿难:在运行下例时预计输出。我正使用两个数组。第一个被存入变量a并被用在函数do里。第二个是被do函数使用的对象。因此项目引数将在第一次迭代时是1,第二次14,19,等等。

22.4. 两个数组

(
a = [60, 45, 68, 33, 90, 25, 10];
b = [2, 14, 19, 42, 3, 6, 31, 9];
b.do(
   {arg item;
      item.post; " plus ".post; a.post; " = ".post;
      a = a + item;
      a.postln;
   }
)
)

同样可以在一个数组中测试一个值的存在。需要用到的消息是includes。当数组包含某个项目或对象时这个消息回答真。它有一个引数:你要找的对象。因此[1, 2, 3, 4].includes(3)将返回真,而[1, 2, 3, 4].includes(10)将返回假。这些真或假的返回可以被用到if函数内(向我们之前章节中看到的)。

22.5. 测试一个数组

(
a = [60, 45, 68, 33, 90, 25, 10];
b = [25, 14, 19, 42, 33, 6, 31, 9];

100.do(
   {arg item;
      if(a.includes(item), {item.post; " is in a ".postln});
      if(b.includes(item), {item.post; " is in b ".postln});
   }
)
)

数组可被用于存储音、频率、持续时间、发音(articulation)或乐器选择的集合。它们可被用于调性、单元格(cell)、集合、序列等与音乐有关的地方。

欲在一个数组内检索一个单独的值,用at消息。at的引数是索引编号。记住计算机是从0开始计数。因此数组[1, 2, 3, 4]有四个项目,索引编号0,1,2和3。数组[9, 12, “this”, 7],索引1是12,索引2是“this”。如果索引数大于数组容量的话你会得到一个nil。这被称作野指示器(wild pointer)。但我们常会有超过数组大小的计数器,在这样的情况下,我们可以用 wrapAt 回到索引模(modulo)数组大小的值的位置。

22.6. 引用数组中的一个项目

[12, 4, 871, 9, 23].at(3) // 索引3是"9"
[12, 4, 871, 9, 23].at(124) // 野指示器, 将返回nil
[12, 4, 871, 9, 23].wrapAt(124) // 将wrap回去(到索引4)并返回23

数组消息

一下是数组消息集合。

22.7. 数组消息

a = [1, 2, 3, 4]; // 将数组分配到变量"a"

a.post; // 列印数组

a + 5; // 为数组每个项目加5

a*3; // 乘

a.do({arg item; function}) //迭代过每个项目,然后将每个项目传递给函数

a.at(index) // 在给定索引引用项目

// 下边是一些新东西。分别运行每行看看它们是干嘛的:

[1, 2, 3, 4].reverse.postln; // 反转数组

[1, 2, 3, 4].rand.postln;

[1, 2, 3, 4].scramble.postln; // 打乱数组

[1, 2, 3, 4].size.postln;// 返回数组大小 (项目数字)

Array.fill(size, function); //fills an array with "size" number of arguments using function

a = Array.fill(5, {10.rand}); a.postln;

a = Array.rand(12, 0, 12)

[1, 2, 3, 4].add(34).postln; //为数组增加一个项目

//注意add。你需要将之传递给另一个数组变量
//以确保项目已被增加了。 So the code would have to be:

a = [1, 2, 3];
b = a.add(10);
a = b;

[1, 2, 3, 4].choose; //选择其中一个值

[1, 2, 3, 4].put(2, 34).postln; // 将第二引数放在第一引数的索引位置

[1, 2, 3, 4].wrapAt(index) // 返回wrap后的索引位置

//范例:

30.do({arg item; [1, 2, 3, 4].wrapAt(item).postln});

数组引用的一个实际用途即定义变量选择。先前我们用[array].choose,它能将选择限制在数组项目内。接下俩的例子用消息rand做同样的事。第一个返回了一系列数字,0到10。但如果rand被放到at中,那么它的值会变为数组的索引,并且返回在那个索引位置的值。

如我之前提到过的,索引数不能大于数组大小。我们用 wrapAt 解决了一个例子。另一方法是含进消息size,它返回数组的大小,并可被与rand配合使用。size返回的数字将比实际索引数大1。数组[3, 5, 7, 1, 8, 12]的大小为6,但索引是从0到5。这没问题,因为rand从0选取一个数字(未完全翻译,原文为“because rand chooses a number from 0 to, but not including, its receiver.”,我暂时没搞懂他这句话什么意思,很明显这哥们不喜欢用简单易懂的方式写作)。

22.8. 合法音数组

20.do({12.rand.postln;}) // 0 到 11 的随机数

// 随机数选取, 但数组仅从大音阶返回音

20.do({[0, 2, 4, 5, 7, 9, 11].at(6.rand).postln})

// 确保rand的范围不太大

20.do({[0, 2, 4, 5, 7, 9, 11].at(12.rand).postln})

// 未避免范围太大, 使用 array.size 或 wrapAt

a = [0, 2, 4, 5, 7, 9, 11];
20.do({a.at((a.size).rand).postln})

有给at、wrapAt、折叠(fold)和修剪(clip)索引的简写符号。用”@” 和 “|”。|i| 是 arg i 的简写。

22.9. 数组索引简写

[45, 37, 99, 367, 9] @ 3 // "在(at)" 索引 3

[45, 37, 99, 367, 9] @@ 25 // "wrapAt" 索引 25

[45, 37, 99, 367, 9] @|@ 25 // 在索引 25 折叠

[45, 37, 99, 367, 9] |@| 25 // 在索引 25 修剪

30.do({[0, 2, 4, 5, 7, 9, 11] @ (12.rand).postln})

30.do({ |i| ([0, 2, 4, 5, 7, 9, 11] @@ i).postln})

30.do({ |i| ([0, 2, 4, 5, 7, 9, 11] @|@ i).postln})

30.do({ |i| ([0, 2, 4, 5, 7, 9, 11] |@| i).postln})

练习,巴赫变异

消息at同样允许你在不同的度上引用数组项目:每隔一个,每隔两个,等等,就像在对巴赫的作品进行极简篡改所做的那样。

22.10. 合法音符数组

// (预定midi)
(
var pitch;

r = Task({
// 尝试别的版本
pitch = [12, 0, 2, 4, 2, 0, 7, 4, 12, 7, 4, 7, 5, 4, 5, 7, 0, 4, 7, 11] + 60;
inf.do({arg h, i;
   pitch.size.do({arg j;
   var n;
      //逐个, 然后每隔一个, 然后每隔两个,等等.
      n = pitch.wrapAt(j*(i+1));
      if((j%20 == 19), {n.postln}, {n.post; " ".post});
      m.noteOn(1, n, 100);
      thisThread.clock.sched(0.1, {m.noteOff(1, n, 100); nil});
      0.1.wait;
   });
});
})
)

r.start;
r.stop; 127.do({arg i; m.noteOff(1, i)});
]]>
http://learn.travelchinawith.me/?feed=rss2&p=1260 0 1260
SC:用if做控制,接着do,Arrays,MIDIIn,计算机辅助分析 http://learn.travelchinawith.me/?p=1255 http://learn.travelchinawith.me/?p=1255#comments Thu, 14 Apr 2011 09:47:33 +0000 http://www.ccttours.com/blog/?p=1255 Continue reading SC:用if做控制,接着do,Arrays,MIDIIn,计算机辅助分析 ]]> 控制消息“if”

人工智能和计算机辅助创作始于逻辑控制。即,告诉机器在特定的情况下做什么。如果你饿了,do打开冰箱。如果里边没有面包,那么do去商店。如果你钱够多,do买四块烤肉。回家并再次打开冰箱。如果里边有果冻或果酱,do选其一做个三明治。不要用腌豆腐。有很多诸如while, for, 和 forBy 等迭代和控制的方法,但是do和if是最普遍的。

if消息或函数有三个引数:一个被评估的表达式,一个true函数,以及一个false函数。它评估表达式是真是假并返回结果。

if(表达式, {真函数}, {假函数})

真、假常由运算符比如大于、小于来判断(注意一个等号和两个等号间的区别。“=”意思是将某个值存入变量,”==”意思是“相等吗?”)。运行下例每一行。第一行评估并返回真(因为1的确等于1)因此它运行第一个函数。第二例是假,因此假函数被运行。

21.1. if 范例

if(1 == 1, {"true statement";},{"false statement";}) 

if(1 == 4, {"true statement";},{"false statement";}) 

// 注释版: 
if( 
   1 == 1, //被评估的表达式; "1等于1" 对还是错? 
   {"true statement";}, //如果是对,运行此行 
   {"false statement";} //如果是错,运行此行 
)

下边是其他的布尔运算符
< 小于
> 大于
<= 小于或等于
>= 大于或等于
!= 不等于
== 等于

消息or 合并两个声明,如果两者之一是对的则返回真:or(a > 20, b > 100)。消息and 合并两个声明,只有当两者都对时返回真:and(a > 20, b > 100)。单词true是真,false是假。

21.2. if 范例

if((1 == 1).and(5 < 7), {"全部都对"},{"只有一个对";}) 

if((1 == 20).and(5 < 7), {"全部都对";},{"一个错或全错";}) 

if((1 == 4).or(true), {"对永远是对";},{"1不等于4";}) 

if(false.or(true), {"对永远是对";},{"对用或者(or)赢了";}) 

if(false.and(true), {"对永远是对";},{"但错用和(and)赢了";}) 

if(or(10 > 0, 10 < 0), {34},{78}) 

if((1 == 1).and((10 > 0).or((5 < 0).or(100 < 200))), {78},{88})

这些孤立的数例在没有上下文关系对照时显得莫名其妙(比如,为什么我会用到表达式 10>0?为什么你仅仅是列印“真”或“假”?)if函数常被用于与一些迭代过程比如do结合使用。下边是一个真实的音乐范例。下边的代码由MIDI值60(C4)开始。然后它选取一个新的MIDI音程,将它加到m并返回那个新值。观察结果。

21.3. do 50 MIDI音程

( 
m = 60; 
50.do( 
   { 
   m = m + [6, 7, 4, 2, 11, 8, -2, -6, -1, -3].choose; 
   m.postln; 
   } 
) 
)

我倾向于更多高低音程起伏的选择。因此最后MIDI值超过了一个对于绝大多数乐器来说合理的范围。即使我已小心的平衡了这个选择,但在一列内一个正值将被选择20次是很明显的。下边的if表述检查每次循环后的值,如果超过84则减去两个八度,如果低于36则增加两个八度。

21.4. do 50 MIDI音程

( 
m = 60; 
50.do( 
   { 
   var next; 
   next = [6, 17, 14, 2, 11, 8, -12, -16, -1, -3].choose; 
   "next interval is : ".post; next.postln; 
   m = m + next; 
   "before being fixed: ".post; m.post; 
   if(m > 72, {m = m - 24}); 
   if(m < 48, {m = m + 24}); 
   "after being fixed: ".post; m.postln; 
   } 
) 
)

当写类似这样的小片段时,在SC里边闲逛一下,看看是否已经有某个函数存在并能做你想做的事情是值得的。wrap,它会将一个值包裹起来,但在本例中,我们做的是缓冲(buffering)而不是包裹(wrapping)。因此在这个状况下,我们必须得要加入额外的代码。

下例展示了在一个音类数组中的do循环与if协同测试查找C,D或E。电脑不可能按实际的音去理解这些(看下边弦乐的讨论),它们看到的只是文字。即使如此,它仍能比较它们是否相等。

21.5. 音类 do

( 
["C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"].do( 
   {arg item, count; 
      if((item == "C").or(item == "E").or(item == "G"), //布尔测试 
         {item.post; " is part of a C chord.".postln;}, //对函数 
         {item.post; " is not part of a C chord".postln;} //错函数 
         ) 
      } 
   ) 
)

你可以说我们将会了计算机认识C和弦。这便是人工智能开始的地方。

if表达式也可用于在荧幕上定义一个区域。当鼠标进入指定的区域生成一个正值,或一个触发器。在本例中,* 相当于和(and)。这里没有对错,只有值1(开启)和0(关闭)。

21.6. 鼠标区域触发器

( 
{ 
var aenv, fenv, mgate, mx, my; 
mx = MouseX.kr(0, 1); 
my = MouseY.kr(0, 1); 
mgate = if((mx>0.3) * (mx<0.5) * (my>0.3) * (my<0.7), 1, 0); 
aenv = EnvGen.kr(Env.asr(0.1, 0.5, 2), mgate); 
fenv = EnvGen.kr(Env.asr(1, 1, 2), mgate, 1000, 100); 
RLPF.ar(Saw.ar(100)*aenv, fenv, 0.1) 
}.play 
)

一个使用if很棒的小技巧控制何时postln而不是post,这并不列印新的一行:

21.7. 新行

( 
100.do( 
   { 
      arg count; 
      100.rand.post; 
      if(count%10 == 9, //每个第9次 
         {" new line: ".postln;}, //列印一个回车 
         {" : ".post;} //仅是" * ",没有回车 
         ); 
   } 
) 
)

while

while函数在第一个引数(一个评估函数)返回错误前重复第二引数(一个函数)。它对新手来说是一个危险的工具。如果你写一个永远不会返回假的条件,SC将永远运行下去(这是多数情况下造成程序锁死的原因)。下边第一例展示了一个常见的错误。代码在第二个函数在变量a中存储变量10前会持续重复。但10.rand只能选取0到9间的数字,因此它永远选不到10,因此在你强行停止前会无限运行下去。也正因此,也可以利用它给一个而外的条件使程序停止。

最后一行展示了while的典型用法。这行代码英告诉你它从100中选到数字13所用的时间长短。

21.8. while

// 典型的错误,因为a永远不会等于10 
a = 0; while({a != 10}, {a = 10.rand}) // 死循环 

// 在特定次数后停止显得更安全一些? 
// 注意and的写法 
c = 0; a = 0; 
while({and(a != 10, c < 100)}, {a = 10.rand; c = c + 1;}) 

//while的典型用法。告诉你从100中挑到13需要多长时间。 
( 
a = 0; c = 0; 
while({(a != 13).and(c < 10000)}, {a = 100.rand; c = c + 1;}); 
c 
)

for, forBy

for允许你指定循环的开始与结束点,forBy包含一个步进值引数。在之前的章节,我们用奇数谐波建立了一个patch。计数器用于计算每个谐波,但由于它从0开始,我们不得不加1(counter+1).我们同样不得不仅为奇数谐波做一点数学计算。但有了forBy,这个计算可以被整合。

21.9. for, forBy

34.for(84, {|i| i.midicps.postln}); 

34.forBy(84, 5, {|i| [i, i.midicps].postln}); 

1.forBy(24, 2, {|i| [i, i*500].postln}); // 奇数谐波 

( // 加法方波 
{ 
o = 0; 
1.forBy(24, 2, {|i| o = o + SinOsc.ar(i*500, mul: 1/i)}); 
o*0.4 
}.scope 
)

MIDIIn

MIDIIn从一个MIDI输入读取数据。有很多可用的方法。首先是触发一个你之前设计的乐器:

21.10. MIDI输入触发SynthDef

( 
// 先定义一个乐器 
SynthDef("simpleInst", 
{ 
arg midi = 60, vel = 60, inst; 
inst = { 
   SinOsc.ar( 
      freq: (midi.midicps)*(LFNoise0.kr(12) + 2).round(1), 
      mul: vel/256) 
   }.dup(5) * 
   EnvGen.kr(Env.perc(0, 3), doneAction: 2); 
Out.ar(0, inst) 
} 
).play 
) 

MIDIIn.connect; 

( 
// 然后将它链到MIDI输入 
MIDIIn.noteOn = { 
arg src, chan, midiNum, midiVelo; 
Synth("simpleInst", [\midi, midiNum, \vel, midiVelo]); 
}; 
)

要控制其他SynthDef乐器,只需用乐器名替换"simpleInst"并确保挂上该乐器对应的引数即可。

实时篡改(Real-Time Interpolation)

MIDI输入的另一用法是在经过某些修改之后,将输入反映到你的MIDI设备(或其他设备)。乍一看,这看起来像一个很好的恶作剧,但同样可以用于篡改(interplotion)。篡改利用现存组件比如一个即兴器的结构或直觉的成就,将之映射到一部分或全部新材料的音乐元素上。比如说,一个钢琴家可能在即兴时用爵士共有的全音阶展示技巧,但如果被要求包含不同的音阶或构思、12音体系、合成音阶,或复杂的轮唱技术时就傻逼了。通过读取他使用他所熟知的材料进行的实时即兴,你可以将这流畅的直觉思维映射到更复杂的实验上。

在篡改数据时,你常不得不使用一个if表达式添加一个逻辑过滤器,允许一些材料不作改变就通过,同时改变其他的。下例用一种新奇的方式使用了过滤器。第一是一个简单的延迟,但它仅在力度高于60的情况下被添加,使用一个if表达式作为过滤器。力度60以下不作改变然后通过。

第二取MIDI输入并在C4轴反向后将之反射到MIDI设备。然而,if表达式只搜寻白键([0, 2, 4, 5, 7, 9, 11]%12)并仅将反向添加到那些MIDI值上。黑键不被反向。因此布鲁斯音阶C4, D4, Eb4, E4, F4, F#4, G4, A4, Bb4, C5 将变为 C4, Bb3, Eb4, Ab3, G3, F#4, F3, Eb3, Bb4, C3。不消说,这真的可以摆脱一个演奏者,因此为了效率,他们应该脱离监听正确MIDI信息的耳机,在听众仅听到篡改版的时候,激发精确反馈的直觉即兴。用多层、古典、即兴、不寻常音阶、调谐、改良的节奏等等尝试这个。可能性是无限的。

21.11. 通过if() 过滤器进行MIDI输入篡改

// 为力度高于60的做简单延迟 
( 
MIDIIn.noteOn = {arg src, chan, num, vel; 
var delay = 0.5; 
thisThread.clock.sched(delay, {m.noteOn(1, num, vel)}); 
}; 
MIDIIn.noteOff = {arg src, chan, num, vel; 
var delay = 0.5; 
thisThread.clock.sched(delay, {m.noteOff(1, num, vel)}); 
}; 
) 

// 篡改: 仅反向白键 
( 
var white; 
white = [0, 2, 4, 5, 7, 9, 11]; 

MIDIIn.noteOn = {arg src, chan, num, vel; 

if(white.includes(num%12), 
   {m.noteOn(1, (60 - num) + 60, vel);}, 
   {m.noteOn(1, num, vel)}); 
}; 

MIDIIn.noteOff = {arg src, chan, num, vel; 

if(white.includes(num%12), 
   {m.noteOff(1, (60 - num) + 60, vel);}, 
   {m.noteOff(1, num, vel)}); 
}; 
)

分析

MIDI输入最后的用处是做分析。下例读取MIDI值,计算距上个值的音程,然后将那个值存入一个数组,因此可以持续记录一个音乐家或即兴演奏家使用每个音程的频度。有两个版本。第一个以移动到任何音的动作来计数音程,不管是升是降,即使倒置(上边这段话的原文:The first counts an interval as motion to any pitch, whether up or down, as the same interval since they are inversions。翻译至此,忽然一阵头晕,再加上Cottle说话爱绕的写作风格,因此不确定能忠实反映原文意思。先放着,想到好的翻译再修正。):C4升到G4(五度)被记录得与C4降到G3(降4度)一样。第二版本,在例子中被注释了,以相等计数音程升降:C4升到G4(五度)与C4降到F3(降5度)一样。

21.12. 用if()过滤器做MIDI输入篡改

(
// 运行此以开始读取输入

var previousPitch = 62, thisInterval;
~intervals = Array.fill(12, {0});

MIDIIn.noteOn = {arg src, chan, num;
// 计数倒置为一样 Counts inversions as the same
thisInterval = (12 - (previousPitch - num))%12;
// 计数音程升降为一样 Counts intervals up or down as the same
// thisInterval = abs(previousPitch - num);

// 反注释以监视值
// [previousPitch, num, thisInterval].postln;

~intervals.put(thisInterval, ~intervals.at(thisInterval) + 1);
previousPitch = num;

// 反注释以实时查看
// ~intervals.postln;
};
)

// 一切就绪后,运行此行
~intervals;
]]>
http://learn.travelchinawith.me/?feed=rss2&p=1255 3 1255