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
Be Sociable, Share!

Published by

ww1way

http://about.me/ww1way

Leave a Reply

Your email address will not be published. Required fields are marked *