许多计算机程序员都是有成就的音乐家。 这是罕见的软件公司,无法组建像样的室内乐队。 但是,许多程序员/音乐家可能没有意识到他们的职业和业余爱好相交的有趣领域:算法音乐创作。 算法音乐创作是将严格定义明确的算法应用于音乐创作过程。 元胞自动机 (CAs)是一类随时间演变的数学结构,它为算法音乐创作提供了诱人的途径。 计算机是计算元胞自动机(CA)演变并以图形方式显示它们的理想选择。 您还可以用声音(包括音乐)来表示演变。 但是,找到将CA演变映射为令人愉悦和有趣的音乐的技术是一个非常重要的问题。 本文介绍了一些用Java语言进行基于CA的音乐创作的技术,并探讨了产生特别好的结果的特定映射。
细胞自动机概述
CA包括:
- 单元的矩阵或网格,每个单元可以处于有限数量的状态之一
- 定义单元状态如何随时间更新的规则
单元矩阵可以具有任意数量的维度。 给定一个单元的状态及其在时间t的邻居状态,该规则将确定在时间t +1的该单元的状态。 (在查看一些具体示例后,这将变得更加清楚。)
基本细胞自动机
我将在本文中集中讨论一维元胞自动机,其细胞可以处于两种状态之一,即0或1。由于CA是一维的,因此可以将其视为一行细胞。 在时间t +1处的一个单元格的值将仅取决于该单元格及其在时间t左右两侧的直接邻居的值。 这种CA被称为基本细胞自动机 。
CA图使用白色表示0,使用黑色表示1。第一行显示一个单元格及其左右邻居可以具有的八种颜色组合。 底行显示了下一步中中心单元格的颜色。 例如,考虑图1左侧的第四个正方形。在这个正方形中,您可以看到,如果一个单元格是白色,则其左邻居是黑色,而其右邻居是白色,则下一步该单元格将是黑色。 习惯上将这个规则称为150 :如果您认为白色和黑色单元格分别代表二进制数字0和1,那么最下面一行将二进制形式的小数点编码为150。 图1是规则150的直观表示。当您使用声音来表示CA时,规则150会生成一些有趣的音调,因此在本文中我将以它为示例。
图1.规则150
现在,考虑一个规则150 CA,该规则以所有白色单元格开始,除了中心的单个黑色单元格。 然后,CA通过图2所示的步骤序列演变。
图2.规则150的步骤顺序
请注意,尽管自动机是一维的,但是您可以在页面下方的连续行中显示其演化的连续步骤。 图2显示了CA演变的前五个步骤(包括初始状态)。 您可以看到,根据规则150,上一行中的每个单元格的颜色均由其自身的颜色以及其相邻邻居的颜色决定。此外,请注意,您要考虑该行中所有单元格的值在每个行同时更新进化的一步。
图3显示了CA经过100个演进步骤后的外观:
图3. 100步骤后的规则150
图3中的CA演变恰好是对称的,但并非所有CA演变都是对称的。
Wolfram对细胞自动机的研究
在过去的半个多世纪中,CA一直是研究的主题。 Stanislaw Ulam和John von Neumann在1940年代发明了CA的概念,并在1940和1950年代取得了许多重要发现。 约翰·霍顿·康威(John Horton Conway)和比尔·高斯珀(Bill Gosper)在1970年代对康威发明的一种特殊的二维CA(称为生命)进行了进一步的研究。 Stephen Wolfram在1980年代开始进行CA研究(请参阅参考资料 )。
通过研究基本的细胞自动机,Wolfram发现复杂的行为可能源于简单的机制。 例如,考虑规则30。与所有基本细胞自动机一样,其定义(如图4所示)非常简单-一张小图完全定义了它。
图4.规则30
但是,规则30的后续演变非常复杂。 图5显示了使用规则30在100个步骤之后CA的演变。
图5. 100个步骤后的规则30
在检查了256个基本CA和其他更复杂的CA的行为之后,Wolfram发现CA可以分为四类。 数学家和作家Rudy Rucker在他的演讲“计算机科学告诉我们有关哲学的知识”中简要而准确地描述了这四个类(请参阅参考资料 ):
- 第1类 : 常数。 (任何种子“死亡”。)
- 第2类 : 重复。 (条纹)
- 2A类 :嵌套。 (常规分形)
- 第3类 :( 伪)随机。 (沸腾)
- 第4类 : 复杂。 (“ Gnarly”。滑翔机。通用计算。)
Wolfram提出了一个合理的主张,即大多数第3类和第4类CA可能在计算上是不可约的 :给定初始状态,要在步骤n中查找特定单元格的值,必须从最初的步骤开始执行所有n个步骤的计算组态。 也就是说,没有公式或捷径可让您预测CA的未来状态。
CA的计算能力
此外,Wolfram和Matthew Cook已证明规则110在计算上等效于通用图灵机。 (Conway在一生中就证明了这一点。)也就是说,您可以使用规则110计算通用Turing机可以计算的任何函数。 类别4中的其他基本CA可能也是如此。 即,尽管某些CA具有简单的定义,但可以对其进行编程以执行任何所需的计算。
一个开放的问题
当然,CA只是纯粹的数学构造。 CA的视觉表示只是帮助我们理解和谈论它们。 Wolfram在“开放式问题与项目”中提出了这个问题:是否有可能提出CA演变的音频表示,从而提供视觉表示所不能提供的见解? 这是一个有趣的问题。 您可以在闲暇时检查图像,并在兴趣点上徘徊,但是随着时间的流逝,您会体验到声音。 当结束时,最好的办法就是再次播放。 Wolfram认为,由于这个原因和其他原因,不可能提出这样的音频表示形式。
音乐问题
我将关注一个稍微相关的问题:是否可以使用CA提出有趣且令人愉悦的音乐? 一些CA的图形表示形式可能非常醒目且精美。 音乐中也可以呈现这种美吗? 同样,最好利用CA的通用计算能力来创作音乐-特别是找到一种将CA演变映射到令人愉悦或至少令人感兴趣的音乐中的方法。
使用Java语言进行音乐创作
由Andrew Sorensen和Andrew Brown创建的jMusic是一个开放源代码框架,用于以Java语言编写音乐程序(请参阅参考资料 )。 它基于Java Sound API构建,使作曲家可以在音乐层面上进行思考,而不必担心底层音频编程细节。 jMusic中乐谱的构建既优雅又直观。 它反映了纸上乐谱的结构:音符组成一个短语,短语组成一个部分,而部分组成一个乐谱。 清单1中的示例说明了这种简单性。
清单1. jMusic中的Harry Dacre的“两人骑自行车”
int[] pitches = { C5, A4, F4, C4, D4, E4, F4, D4, F4, C4 };
double[] rhythmValues =
{
DOTTED_HALF_NOTE,
DOTTED_HALF_NOTE,
DOTTED_HALF_NOTE,
DOTTED_HALF_NOTE,
QUARTER_NOTE,
QUARTER_NOTE,
QUARTER_NOTE,
HALF_NOTE,
QUARTER_NOTE,
2 * DOTTED_HALF_NOTE };
Note[] notes = new Note[pitches.length];
for (int i = 0; i < notes.length; i++) {
// A note is made up of a pitch and duration
notes[i] = new Note(pitches[i], rhythmValues[i]);
}
// A phrase is made up of notes
Phrase phrase = new Phrase(notes);
Part pianoPart = new Part("Piano", PIANO);
// A part is made up of phrases
pianoPart.add(phrase);
// A score is made up of part(s)
int tempo = 180;
Score daisy = new Score("A Bicycle Built For Two", tempo, pianoPart);
// In key of F: 1 flat
daisy.setKeySignature(-1);
// In 3/4 time
daisy.setNumerator(3);
daisy.setDenominator(4);
// Display score in standard musical notation
View.notate(daisy);
// Write out score to MIDI file
Write.midi(daisy, "C:\\Daisy.mid");
现在, 聆听生成的MIDI文件。
除MIDI功能外,jMusic还具有许多其他不错的功能。 例如,您可以用常规音乐符号显示乐谱。 同样, mod
包使您可以对短语执行转换,例如移调或倒置。
自动僧
Automatous Monk(又名Monk )以强大的爵士钢琴家和作曲家Thelonious Monk的名字命名,是我用Java语言编写的一个开源程序,它从CA演变中产生旋律(请参阅参考资料 )。 Monk使用jMusic框架将生成的音乐表示为jMusic乐谱,您可以将其保存并播放为MIDI文件。 我将使用来自Monk的代码示例以及由Monk创建的简短MIDI文件示例来说明我的观点。
用Java语言表示CA
我不会详细介绍如何用Java语言完全表示CA,但是了解如何从其当前状态及其邻居的当前状态计算出单元的下一个状态很有帮助。 如您所见,二进制数可用于表示规则,整数可用于表示给定步骤中CA单元的状态。 清单2中的代码计算单元格的下一个状态。
清单2.计算单元格的下一个状态
/**
* Computes the next state of a cell.
* @param nWCellState State of the cell to the left of the current cell in
* preceding generation.
* @param nCellState State of the current cell in preceding generation.
* @param nECellState State of the cell to the right of the current cell in
* preceding generation.
* @return Next state of the cell.
*/
int getNextStateOfCell(int nWCellState, int nCellState, int nECellState) {
// Find the index of the current state in the rule pattern
int index = 4 * nWCellState + 2 * nCellState + nECellState;
// Get the value of the digit in that place
int nextState = (rule >> index) % 2;
return nextState;
}
CA演变的音乐表现形式
现在来了困难的部分:如何从CA演变中构建音乐? 一个事实似乎很明显:使用CA演变的可视化表示,随着浏览页面的时间增加。 这对于音乐表现也是有意义的。 因此,CA演变的每一行将代表一个节拍。
我将向您展示可在Monk中获得良好音乐效果的一些映射。 嵌套CA往往会产生悦耳的音乐。 您可以听到音乐中反映出的视觉规律性。 嵌套规则150是一个很好的例子。 Wolfram的书A New Kind of Science简要概述了通过音乐表示CA的一些方法(请参阅参考资料 )。 我的映射基于这些想法。
“键盘”映射
这种“键盘”映射可能是最明显的映射。 CA中的每个单元格(或列)对应于一个特定的音高。 您可以将每个单元视为映射到钢琴键。 但是,我的实现使计算变得有些复杂,因为它考虑到了您使用的音阶类型(例如,半音阶,大音阶或次音阶)以及该音阶类型中音符之间的间隔。 清单3中的映射计算给定单元的音高。
清单3.计算单元的音高
/**
* @param cellPos Position of the cell. The position of the center cell is
* 0, while positions to the right are positive and positions
* to the left are negative.
* @param normalize If true, make sure return value is a valid
* MIDI pitch. That is, make sure that it is between 0 and
* 128.
* @return The starting pitch of the cell.
*/
int getStartingPitch(int cellPos, boolean normalize) {
int[] scale = scaleType.getScale();
int interval;
int scaleWidth = scale.length;
// Compute the interval from middle C to this pitch.
if (cellPos >= 0) {
interval =
(cellPos / scaleWidth) * HALF_STEPS_IN_OCTAVE + scale[cellPos % scaleWidth];
} else {
interval = (-cellPos / scaleWidth) * HALF_STEPS_IN_OCTAVE;
if (-cellPos % scaleWidth != 0) {
interval += 12 - scale[scaleWidth - (-cellPos % scaleWidth)];
}
interval *= -1;
}
// Add interval to middle C, C4.
int pitch = C4 + interval;
if (normalize) {
pitch %= 128;
if (pitch < 0) {
pitch += 128;
}
}
return pitch;
}
现在,当您将此映射应用于规则150时,请听听结果。
如您所见,结果有点刺耳(尽管您可以粗略地听到其嵌套结构)。 “和弦”简直太密集了。 在本文的其余部分中,我将向您展示产生简单旋律而没有多个同时音符的映射。 这些类型的映射与正确的规则相结合,可以产生令人惊讶且引人入胜的旋律。
“行到二进制数”映射
“行到二进制数”映射将每一行视为代表间距的二进制数。 但是,您会遇到一个微妙的问题:如何排序数字? 通常,您从右到左对它们进行排序,最右边的数字是最低有效数字。 但是,一维CA没有最右边(或最左边)的像元。 我们认为CA的“宇宙”在左右方向上都是无限的。 因此,就像在笛卡尔平面中使用x轴一样,您将一个特定像元视为原点。 原点右侧的单元格编号为正,原点左侧的单元格编号为负。 本文中所有示例CA均以单个黑色单元格开头,因此自然可以将此单元格视为来源。
为了使二进制数的位数从最低到最高有效,请从原点开始,然后向外进行计算。 您可以通过两种方式之一来执行此操作,具体取决于您是偏爱原点左侧的单元格还是偏爱原点右侧的单元格:
- 最喜欢的左:0,-1、1,-2、2,-3、3,...
- 偏爱权:0、1,-1、2,-2、3,-3,...
无论哪种情况,您都可以将CA的一行编码为二进制数。 因为只有0到128之间的数字指定了MIDI音高,所以您可以通过将其取值模128将此数字转换为MIDI音高。清单4包含来自Automatous Monk的相应代码。
清单4.重新排列一行并计算音高
/**
* Reorders a row according to significance of digits.
* @param rowIndex The generation number of the row
* @param cA The CA containing the row
* @param bias Left- or right-side bias
* @return The reordered row with least-significant digits to right
*/
int[] reorderRow(
int rowIndex,
CellularAutomaton cA,
HorizontalBias bias) {
int[][] cAHistory = cA.getGenerations();
int[] row = cAHistory[rowIndex];
int len = row.length;
int[] reordered = new int[len];
int mid = len / 2;
reordered[len - 1] = row[mid];
for (int i = 0; i < (len - 1) / 2; i++) {
// Note that this favors one side of the CA over the
// other side, but there seems to be no way to get around this.
if (bias.equals(HorizontalBias.LEFT)) {
reordered[len - (2 * i + 1) - 1] = row[mid - (i + 1)];
reordered[len - (2 * i + 2) - 1] = row[mid + i + 1];
} else {
reordered[len - (2 * i + 1) - 1] = row[mid + i + 1];
reordered[len - (2 * i + 2) - 1] = row[mid - (i + 1)];
}
}
return reordered;
}
/**
* Computes a MIDI pitch from a row of 0s and 1s.
* @param rowIndex The generation number of the row
* @param clllrAutmtn The CA containing the row
* @param bias Left- or right-side bias
* @return A valid MIDI pitch
*/
int getPitchFromRow(
int rowIndex,
InfiniteCellularAutomaton clllrAutmtn,
HorizontalBias bias) {
int[] reorderedRow = reorderRow(rowIndex, clllrAutmtn, bias);
int pitch = 0;
for (int i = 0; i < reorderedRow.length; i++) {
pitch = (2 * pitch + reorderedRow[i]) % 128;
}
return pitch;
}
现在, 聆听通过将此映射应用于规则150生成的旋律。请注意,无论使用左偏还是右偏,您都会得到相同的结果,因为规则150是对称的。
“累积行到二进制数”映射
“累积行到二进制数”的映射与先前的映射相同,但是保留行值的累积总和(运行总计)。 映射代码在清单5中。
清单5.连续排列连续的行距
/**
* Converts the successive rows of a CA to a musical phrase.
* @param cA A cellular automaton
* @param bias Favor left- or right-side in generating pitches
* @return A musical phrase corresponding to cA
*/
public Phrase convertCAHistoryToPhrase(
CellularAutomaton cA,
HorizontalBias bias) {
Phrase phr = new Phrase();
int cumulativePitch = 0;
for (int i = 0; i < cA.getGenerationCnt(); i++) {
int pitch = getPitchFromRow(i, cA, bias);
// Need to take mod 128 to keep it a valid MIDI pitch.
cumulativePitch = (cumulativePitch + pitch) % 128;
Note n = new Note(cumulativePitch, CAConstants.DEFAULT_NOTE);
phr.add(n);
}
return phr;
}
现在, 听听这听起来像什么。
这个结果比前面的示例听起来更加旋律,后者听起来有点像节奏部分。
映射组合
组合映射可以产生良好的结果。 例如,您可以将规则150旋律与节奏部分相结合 。 您还可以组合应用于规则60的二进制和累积二进制映射 。 请注意,映射的左和右偏移版本不同,因为规则60不是对称的。 这样就产生了四种声音和更丰富的声音。
我必须承认,我认为规则60相当吸引人! 我已经花了很多时间(肯定比大多数人更多)来收听CA,而且我经常发现规则60在我脑海中发挥作用。 我认为这是CA世界中的Hanson或Jackson 5。
把事情简单化
如前所述,我发现最好将精力集中在产生单声旋律的映射上(也许像我对二进制映射和累积二进制映射所做的那样对这些旋律进行分层),并且本质上忽略和声和节奏。 这并没有看起来那么大的限制。 许多巴洛克音乐,例如JS Bach的音乐,就是这种类型。 甚至还有一个词: 复音 。 (当然,我在很大程度上简化了事情:在复音中,和声的确来自不同的声音,并且节奏并未被完全忽略。)映射的复音组合是从CA制作音乐的很好的第一步。
我还发现,简单的映射会产生最令人满意的结果。 但是,这并不奇怪,因为即使是简单的视觉映射(将1映射到黑色正方形,将0映射到白色正方形)也可以生成非常漂亮而复杂的图案。 此外,请考虑以下事实:某些CA(例如规则110)能够进行通用计算。 并暂时假设音乐创作是一种纯粹的理性活动-您可以对计算机进行编程以创作音乐。 这样,通用CA(例如规则110)应该可以自己产生音乐,而无需我们在映射本身中“编码”任何音乐情报或知识。 当然,我只使用了非常简单的起始状态,例如,规则110可能需要一个更复杂的程序,并以起始状态编码的输入数据才能产生真正优美的音乐。 关键是从CA状态到注释的映射应该相当“透明”; CA应完成所有组成工作。
试图使映射复杂化的另一个危险是,您倾向于在这些映射中编写自己的音乐偏见(无论是东方的还是西方的,音调的或非音调的)。 编写一种可以构成肖邦或巴赫风格几乎与肖邦或巴赫风格一样的计算机程序可能是一个难题,也是一项令人钦佩的成就。 毕竟,有另一个“新”肖邦或巴赫作品总是很高兴。 但是为什么要限制自己呢? 我认为算法音乐创作不会真正成功,除非它不仅可以产生优美的音乐,而且还可以产生与我们之前听到的完全不同类型的优美音乐。 肖邦和巴赫之所以表现不佳,不仅仅是因为他们创作了出色的音乐。 它们之所以很棒,是因为它们引入了全新的音乐思维方式。 我认为最好的结果将来自使用简单的映射并让CA产生任何音乐灵感。
纳尔
鲁迪·鲁克(Rudy Rucker)定义了一个概念,他称之为“ 纳尔(Gnarl)” :具有一定程度混乱的事物,可以调整到有序和无序之间。
当我第一次开始听Monk制作的CA音乐时,在我看来,一些听起来最好的CA是Gnarl的例子。 后来,我在Rucker的“计算机科学告诉我们的哲学”一书中读到,他认为第4类CA是Gnarl的例子。
粗糙的CA听起来像什么? 听规则225。
当我听取规则225时,我觉得我几乎可以掌握规则的结构,但不是很清楚。 这样可以长时间聆听而不会感到无聊。 另一方面,规则60(这是一个嵌套模式)有时会很令人愉快,但不会引起我的兴趣。 规则60的结构太容易掌握; 它可能是不错的,耐嚼的,酥脆的思维糖果,但这不是纳格尔的例子。 即使规则225产生了具有我指定的起始状态的常规嵌套模式-因此不在第4类中-对我来说听起来还是很粗糙。 (为什么CA听起来通常比看起来听起来更粗糙)是整篇文章的主题。)
那么,真正的4类CA听起来像什么? 规则110位于第4类; 听 。
听起来确实好像我们已经进入了纳尔巢穴。 与许多好音乐一样,您需要先听几次,然后才能完全欣赏它。
其他算法音乐创作项目
关于算法音乐创作的更多研究超出了您的预期。 使用计算机进行音乐创作的方法基本上有两种:自上而下和自下而上。 使用计算机程序作曲的作曲家大多以自上而下的方式使用它们-将音乐积木排列成较大的结构。 自动和尚采用自下而上的方法:它在音符级别创建音乐。
加缪
由Eduardo Reck Miranda撰写的CAMUS是另一种自下而上的方法,它使用CA来生成音乐(请参阅参考资料 )。 它使用生命和恶魔循环空间生成音乐。 与我向您展示的一维CA不同,“生命”和“恶魔循环空间”都是二维CA。 即,单元以二维网格布置。 生活特别有趣(编程和观看都很有趣)。 CAMUS使用Life生成音符组,同时使用Demon Cyclic Space生成单个音符的音色(或纹理)。
音乐草绘器
Music Sketcher是一个IBM alphaWorks项目(请参阅参考资料 )。 与Monk和CAMUS不同,Music Sketcher采用自上而下的方法,它使用户可以将乐句或即兴组合到更大的音乐结构中。 Music Sketcher的特别有趣之处在于,它使您可以为乐曲创建和弦进行,而程序会执行必要的转换以确保构件符合指定的和弦进行。 (通过这种方式,它是Apple的新GarageBand应用程序的先驱。)这是一个有趣的程序,我鼓励您尝试一下。
结论
使用CA进行算法音乐创作的许多有趣方法仍有待探索。 我提供的示例仅使用一种简单的开始状态。 通过以更复杂的起始状态“播种” CA可以找到哪些类型的音乐? 同样,大多数西方音乐都有和弦进行的旋律。 设计一种使CA产生符合用户指定和弦变化的旋律的映射技术(换句话说,就是CA Charlie Parker),尽管这可能会通过使CA符合音乐偏见来限制CA,这将是很有意思的。
当前版本的僧侣的一个限制是它仅使用传统的西方音高,即128个MIDI音调。 当您将CA行解释为二进制数mod 128时,实际上仅查看该行的7位数字或单元格。 七个单元格是整个CA的非常细的垂直条; 很多信息丢失了。 我们可以通过使用微音而不是西方音乐中使用的半音来保留此信息。 这会导致听起来更丰富的音乐还是仅仅是不和谐? 我计划扩展Monk来尝试其中的一些想法。
SourceForge.net上免费提供Automatous Monk和jMusic,我鼓励您尝试自己的实验。 您可能还想看看Wolfram的“开放式问题与项目”论文以获取想法。 从高中阶段到研究生阶段,细胞自动机科学的方法可以有很多角度,涉及不同程度的技术复杂性。 用鲁迪·鲁克(Rudy Rucker)的话说:“请搜寻纳尔!”
翻译自: https://www.ibm.com/developerworks/java/library/j-camusic/index.html