探究算法编程在节奏创作领域中的辅助作用

2019-07-17 08:09:22
音乐文化研究 2019年2期
关键词:长句乐段谱例

高 弘

内容提要:编程技术对作曲家实现创作的局部自动化具有重要意义。当代,人们对于作曲家的综合素质也提出了时代性的新要求,即应该积极掌握计算机辅助创作领域的各项技能,成为计算机与作曲两个交叉领域的综合型人才。利用编程技术,作曲家把个人需求通过计算机算法来实现,再把算法植入在程序里,从而达到“人机合一”的理想效果。本文着重在节奏这一音乐参数上探究,如何在避免机械性的同时,提炼个性化的创作需求,最终通过算法的控制来形成程序。以此论证,相较于单纯依靠人力来进行音乐创作的传统方法,算法作曲在未来有着很大的优势和潜能。

前 言

近几年来,国内外对于计算机技术介入作曲领域的研究呈现出不断加速、扩大的趋势。这主要得益于现今人们获取数据的方式已经变得更为方便,计算机CPU 硬件等性能较以前也得到了大幅度提高。基于这一现象,人们可以试验各种各样的算法来训练计算机不断优化模型。无疑,音乐这一人类引以为豪且极富创造性的领域,也遭到了计算机这一“不速之客”的介入。其实,这一趋势并不是在最近几年才开始出现的。因为计算机介入作曲呈现出的方式不同,其差异性主要取决于人类对它的定位,或者说,在何种范围赋予计算机一定的“自主权”。计算机是作曲家的助手、提高其创作效率与分析的工具,如何去利用好这一工具,才能在既贴近作曲家的想法,又不干扰作曲家艺术构思的前提下,提高创作效率,甚至达到能反哺创作思维的效果?

本文试图在节奏这一领域,探究如何把作曲家对音乐的控制思路,演化为对应的算法,并把这些算法纳入到程序的设计里,使作曲家的想法可以迅速地“变现”,反馈给作曲家,进一步把程序作为模块根据功能的不同而分类、存储,以便创作时可以随需求的变化加以调整和复用,从而极大地扩展作曲家的创造能力。

一、编程风格的思考

我们首先需要思考几个问题。第一,程序设计的结构是什么? 我们最终需要以什么数据形式来产出? 如果要得到需要产出的这种数据形式,需要进行什么操作? 第二,有哪些参数是需要我们在过程中键入的? 有哪些是可以预设,以备程序随时调取使用的? 第三,假设前两步能够完成,我们要如何进一步优化代码,使其中一些模块可以被其他作品再次使用? 或是如何改善容错性,增强其在适用范围以外的韧度,更加灵活地贴近随时变更的创作需求?

在编程的领域中,这种理念就被提炼为是“高内聚、低耦合”。意思就是,首先要有模块化的思维,使诸多模块尽量聚焦于一个单一功能来实现,也就是提高内聚性,作为一个模块,我们要尽量使得一个模块内的各个子功能、子模块或子元素之间紧密联系起来,以增强代码彼此之间的关联性,而且这也是模块复用的基础。而低耦合是指在高内聚的基础上,尽量削弱不同功能模块之间的重叠甚至彼此依赖的程度。即在不同功能的模块之间,其中一个模块的使用并不强制依赖另一个模块的介入与调用。简单来说,就是使模块与模块间尽可能独立地存在,让每个模块单独完成某个特定的子功能。而模块与模块之间的接口部分,尽量纯粹和不依赖其他模块的介入。这样就可以允许作曲家根据不同的需求来独立调用不同功能的模块,或是加以组合、拼接以达到更加丰富的创作功能。所以,在接下来讲述代码实现的章节中,笔者将根据结构的种类来划分为不同的函数模块,并针对各类模块分别来进行代码实现。

二、一些结构体在节奏层面的代码实现

(一)自由平行乐段

自由平行乐段,意指由四个乐句构成的一个结构乐段。其内部分别由第一乐句与第三乐句相互对应,而第二乐句与第四乐句相互对应,见谱例1。

谱例1

以上谱例是一段由大提琴所演奏的低音声部,我们先集中解决结构与节奏部分,所以需要暂时剔除所有的音高信息。如谱例所示,这是一个长度为16小节的规整的平行乐段,共4句,每句4小节。其中,第1句与第3句成对应关系,第2句与第4句成对应关系。

那么接下来,首先需要考虑的是,我们需要计算机产出什么形式的数据? 为了得到这种数据,我们又需要如何给予一个程序参数?

面对第一个问题,我们首先需要的是一组列表,它能够储存计算机产生的节奏数据。为方便计算机能“读懂”我们的节奏信息,我们可以用4来代表一个全音符,2代表一个二分音符,以此类推,见表1。

表1

当然,也可以根据自身需要新建其他节奏型,上表只是为达到我们的章节目标而展示出的部分。

接着,我们把谱例1中的节奏信息“翻译”为能够在程序中存储的列表,如下:

'(4 4 4 4)

'(2 1 1 4 4 2.5 .5 .5 .5)

'(4 4 4 4)

'(2.5 .5 .5 .5 2 1 .5 .5 2 2 4)

第一个目标是借助程序内提前设定的算法,最后产出类似于这样的一个列表,但是并不需要如此“精确”,否则只是实现了一个打谱功能而已。真正需要达到的目标,是在实现同样乐段结构的基础上,有一定程度的“自由”,以保证创作的个性化。如果每次的调用都只能产生同一结果,那也就失去了设计此算法的意义。

那如何保证,在拥有一定程度的个性化时,又能展示出预期的结构呢? 首先,就要对乐段结构有深刻的理解,然后使这些理解在与为之设计的代码过程中间有所体现。从谱例1中可以很明显地看出,这是一个典型的平行乐段。第1句与第3句完全对应,第2句与第4句虽然也是对应关系,但是并不完全相同,它们之间的对应,更多的是体现在过程中,其中有节奏变化元素的彼此对应,但这并不影响他们之间的对应关系,从曲式角度来说,它们不是彼此独立存在的。

为此,需要把我们头脑中的曲式概念在编程环境里加以模拟,从而“告诉”计算机,这4句里实际上只有2句是需要调用“生产”的模块,即需要产生一个新的乐句,而后2句因为和前2句是对应关系,我们只需要重复(当然是变化的重复)已得到的前2句的数据即可。那么我们需要一个专门用来控制曲式结构的列表,在上方这个谱例中,它会显示为如下:

'(1 2 1 2)

有了控制曲式的列表之后,我们需要关心的是每个乐句的内容。本质上来讲,也是曲式,只不过是更低一层次的结构。显然,第1句虽然只是一个初步的展示,但我们发现它的节奏过于单一,甚至几乎都是全音符。这是因为这个声部是低音声部,难免在节奏的丰富程度上无法与旋律声部相比拟。也就是说,同样是第1句,它是否是低音声部、旋律声部,或是中间填充的和声声部,对于节奏元素的产生有着决定性的意义。而这在我们的实际创作过程中也是很常见的情况。但对于计算机来说,我们需要“告知”它,需要按哪一种声部类型来产生乐句中的节奏。所以,我们还需要另外构建一个品种列表,如低音、主旋律、副旋律、填充声部等,用来对应不同的节奏生成策略。

至此,有了两组数据,一个是曲式列表'(1 2 1 2),另一个是声部列表'(bass),接下来要深入到结构内部。这时会发现,第2句虽然属于新产生的一个独立乐句,但也和第1句有着紧密的内在联系。譬如,第2、第3小节,也都是由全音符组成,而第1小节可以看成是一个全音符的两次自我分裂。一个全音符,理论上可以不断地进行自我分裂,如谱例2所示。

谱例2

不难发现,每次分裂都是把最后一个音符作为被分裂对象,而前面的值都予以保留(第一个原始的全音符也可看为自己的最后一个音符),三次分裂后的状态会出现在第2乐句和第4乐句中。所以,自我分裂是众多实现新乐句方法的一种,为此我们可以构建代码(见图1)。

图1

这个名为division的函数模块解决的就是这个问题。关于分裂次数方面为避免每次都需人工给予参数,笔者在内部把它交给了随机数字处理,但不会超过分裂三次的情况,我们可以看到在给定参数值4(意味着是全音符)的情况下,多次运算后在控制台显示的结果情况,与我们所预期的完全符合。

在第2句的第4小节,我们发现了一个并不能通过分裂模型得到的结果(见谱例3)。

谱例3

它同样也出现在了第4乐句的第1小节,正如前面笔者所做的,我们必须从具体的事物出发,然后再提炼出具有模型化的抽象。那谱例3的这个动机本质上是什么逻辑?

从这个小节第三拍的后半拍开始的三个连续八分音符,本质上是一种连接,它起到了一个连接句的功能,也就是说,如果在抽象层面上来理解,它也可以是这种形式(见谱例4)。

谱例4

或者:

上面几个谱例所展示的,从根本上来说都是具有向后面平滑过渡的性质。也就是说,笔者需要构建一个可供连接句选择的节奏元素列表,在需要时调取即可。至于调取的究竟是哪个元素,其实并不重要。那么,之所以在最后一小节出现带有连接性质的节奏元素,是为了更好地进入到下一乐句,我们可以把第2乐句看成是一个既保留第1句节奏元素的同时,拥有自我分裂模型与连接模型的乐句,只要同时实现这三个功能,即便一些具体的时值每次运算会有不同,也不会影响到第2句在曲式结构中的功能。下面请看在以上思路通过代码实现之后,多次运算的第2句的数据情况(见图2):(代表第2句的模块里只拥有一个参数入口,就是被运算的节奏列表,此处为第1句的节奏列表)。

图2

以上经过运算的结果正是我们想要的:1.在第2乐句中,第1乐句的节奏元素予以了部分保留;2.拥有自我裂变;3.顺利完成最后一个小节具有连接性质的乐句。

在一个平行乐段中,第1乐句与第2乐句相对较为核心,能够表达乐段的思想,而第3乐句与第4乐句则视为前2句的一个变奏,也就是说,接下来的核心就是如何实现“变化”的功能。相对较为“简单”地通过调整顺序以达到变化的效果,此效果只需要一个简单的函数,几乎市面上所有的编程语言都封装了这样一个可以实现随机排列的函数,所以就不在本文讲了。笔者认为,随机排列虽然可以起到变化的效果,但是难以实现相较之原对象情绪上的增减起伏,作为平行乐段的后半部分,只拥有随机排列这个功能肯定是不够的。一个现实可行的思路是,在随机排列的基础上,结合原有的自我分裂模型或自我合并模型(对应乐曲情绪的相对起伏)实现变化。此种思路的根本原理建立在“节奏在音乐中起到调节情绪的基本功能”这一观点上。听密集的节奏,会让人感觉活泼;听稀疏的节奏,音乐则会显得舒缓。而唯一需要注意的是,第4乐句最后一个节奏时值要相对够长,以达到结束整个乐段的目的。

在理清了思路之后,笔者开始实现第3乐句与第4乐句的代码。

谱例中,第3乐句与第1乐句完全一致,我们暂且可以只用随机排列函数(因为全部一致,所以随机排列在此处与完全重复并无区别)。第4乐句要同时实现3个功能:1.随机排列;2.在原有基础上继续自我裂变;3.尾音需要较长的时值已获得段落稳定感。

按照此要求来设计代码后的多次运算结果如图3所示:

图3

实现第4乐句的代码模块(fourthphrase)也只有一个参数入口,由于我们是在第2乐句的基础上去实现功能的,这里的参数就是第2乐句的节奏列表。经过多次运算后我们发现,已经实现了对第4乐句的预期功能。

下面笔者需要把之前所做的功能整合在一起,同时要时刻关注起初提出的问题,即我们最后需要产出的数据形式是什么? 我们因此需要暴露出哪些参数接口,以方便我们操作?

毫无疑问,我们需要得到的数据形式为一段完整的平行乐段(当然只涉及节奏,音高将会在下一个章节中涉及),里面分别清晰地展示4个乐句,每一乐句都拥有自己的节奏列表。第二个问题是我们需要制作出何种程序形式便于我们操作? 也就是以什么样的形式去操作这一组代码模块,笔者认为需要提供一个节奏列表,也就是第1句的节奏列表作为操作对象,或者,即提供第1乐句的节奏,然后代码自动生成剩余3个乐句。图4是这组代码的运行结果,我们可以为这组代码起个名字,叫parallel,中文即平行的意思。

在实际运用中,我们并不需要print功能(控制台显示为橘黄色的数据就是print功能输出的),因为经由print功能输出的数据,无法继续传递或输出到下一个函数。这里使用它,是为了较清晰地展示哪些数据是第1句,哪些数据是第2句,以此类推。

图4

(二)综合句法

这也是除了平行乐段外,最常见的一种结构,较为通俗地称呼为短短长。它主要表现为:首先出现两个非常相似的短句,紧接着再来一个长度为两个短句之和的长句。它通常可以独立存在,也可以嵌入在某些结构体当中,如平行乐段。作为一种结构,它也有各种相应的变体,其变化主要体现在其长句的特征上:

1.长句的素材和短句的素材并无明显相关;

2.长句的主体,尤其是长句的前半部分,和短句的素材高度相关;

3.长句内部,又嵌套了一个短短长的结构。

笔者要做的就是把这三个版本的短短长结构全部整合在一个函数里,然后交给计算机来随机选择,而创作者则只需指定一共占用多少时值即可,这也是该函数接收的唯一参数。同时,笔者在函数内部存储16为参数的默认值,即当创作者不给予函数参数的时候,自动调取16为函数参数,然后执行函数。当给予函数参数时,函数以参数实际值执行函数。笔者把这个函数命名为ssl(意思即short-short-lang,中文意为“短短长”)。

效果见图5。

图5

由于此处没有给予函数任何参数,函数假定了参数值为16调取执行了ssl函数(16这个数值意为整个短短长乐段时值为16拍,这个数值被笔者封装在函数内部)。笔者连续执行了三次,可以从图5中看出,第一次执行ssl函数的结果显示是一个普通的短短长结构,其中第一个短句占时值4拍,第二个短句与第一个短句完全相同,长句占时值8拍。第二次执行ssl函数的结果显示是一个短短长结构,其中的长句里又嵌套了一个短短长结构,即在长句所占的8拍的节奏空间内,又实现了一个2+2+4的短短长结构。第三次执行出的结果同第二次相同,在这三次执行的结果中并没有显示出第二种变体,即长句的主体,尤其是长句的前半部分,和短句的素材高度相关,那么可以再多执行几次,看看是否会有这类结构出现(图6)。

图6

这次执行的第一次结果就符合预期:长句保留了短句的节奏素材,构成了长句的前半部分。

(三)节奏数据的人性化处理

不难发现,这些数据虽然体现了一些结构的特征,但是从细节上来说,它们还是不够丰富,或者说不具备足够的人性化特点。固然,根据不同的创作需求,或许作曲家也可以接受这些节奏形态。但是笔者认为,应基于更加音乐性的角度来对这些节奏的数据进行二次处理,用来满足差异化的创作需求,则更为合理。下面参见一组节奏数据的对比:

谱例5

在谱例5中,标注为原型的节奏列表为:

(.5 .5 .5 .5 1.5 .5 .5 .5 .5 .5 2)

从乐句结构上来说,这是一个长度为2小节的长句,停留音为2拍,即占乐句总长度的四分之一。无疑,这样的一组节奏,有时候并不能满足作曲家对于节奏丰富性的需求。所以,需要构建一个对这些节奏数据进行二次处理的函数,为的是在不破坏或显著更改乐句结构的前提下,丰富节奏内容。为此笔者构建了一个名为humaniz的函数,来达到此目的。

笔者把这组数据,当成函数参数并以此调动humaniz函数,然后控制台中显示了这样一组节奏数据:

(0.5 0.5 0.5 0.25 1.75 0.25 0.25 0.5 0.5 0.5 1.0 1.5)。

这也就是在谱例5中标注为“处理后”的节奏,与“原型”对比之后可以发现,原本在“原型”中平均化的节奏(主要表现为连续的八分音符进行)被打破,其手段是针对某一个八分音符进行分裂处理(分裂为两个十六分音符)。而对于停留音的出现,并不是简单地用二分音符来展示,取而代之的是迟滞一个八分音符而出现,并且这个八分音符与之前的时值相互合并,造成了一个延留音,避免造成了听众过于直接突兀的印象。事实上,笔者在humaniz函数内部封装了三种对于停留音的处理办法,迟滞出现只是其中一种,这样就可以保证每次调用humaniz函数时对于停留音过于单一的处理情况。

图7

(四)不同结构体在宏观层次上的运用

目前为止,虽然已经实现了一些不同的结构,但是还是处于比较微观的层次,无法在一个统一的平台上综合地使用。因为它们相互之间没有联系的,处于彼此孤立的状态。就拿前文中已经实现的一些结构来举例:假设,创作者需要一个片段,这个片段是由两个时长为64拍的平行乐段、一个时长为16拍的短短长,再加上一个时长为32拍的半平行乐段。那么这时候,只能依次地按照如下的顺序运算——parallel函数、parallel函数、ssl函数、halfparallel函数。虽然这样的方式最后可以满足想要的结构,但是仍不可避免会有以下三个缺点:

1.创作者需要进行频繁的操作,此处至少需要操作四次,并且每次都要需要输入每个函数相匹配的参数才能运行函数,这无疑加大了创作者的工作负担,不利于此文讲述的半自动化辅助功能的宗旨。

2.每个函数之间并没有内在的联系。从作曲创作的角度来讲这是不能接受的,按照上例中的操作方式,每个函数运行的时候,并不依赖于任何其他函数产生的结果,而是独立运行的,这就说明从音乐素材的角度来看,四个函数之间的结果没有建立起任何关联。

3.无法追溯已经存在过的结果。举一个例子,当使用者第一次运行parallel函数,产生了一个平行乐段,而在之后的某个时刻,他需要重复这个平行乐段,那么即使他再次运行parallel函数,由于parallel函数内置的随机性,也无法得到与之前完全相同的结果。

综上,我们还需要设计一个可以统一调取不同功能的函数的平台。它应当支持创作者尽可能以极简的方式输入“必要的信息”,从而产生指定的结构。这里面包含每个结构是否是独立运行或是取材于哪个部分,以及只支持追溯。那么,究竟什么才是“必要的信息”? 对于不依赖于其他结构的独立产生声部的函数来说,总时长、结构名称以及其中第一乐句的内容素材(可缺省)是必要的信息;而对于需要依赖其他结构内容从而才能产生声部的函数来说,需要提供被依赖的声部,或者说是被锚定的声部,以及结构名称作为必要的信息;最后,对于追溯功能来说,只需提供需要被追溯的位置,就能作为必要信息。

理清了上述思路之后,开始实现结构函数rhysglobal。

函数rhysglobal的假想内部结构为:

〈函数名〉〈时长列表〉〈函数列表〉

rhysglobal函数的运行逻辑如下:函数名设置为为rhysglobal,rhysglobal函数会首先读取〈函数列表〉里的函数,根据它们的类型,决定是否读取〈时长列表〉中的元素。如果该函数是独立运行的,那么直接读取〈时长列表〉中的元素来限定该函数产生节奏的总长度;如果该函数是依赖于其他结构内容的,则在该函数后需直接输入被依赖内容的位置,这种情况下,不需要读取〈时长列表〉中的元素。以下展示一个结构函数rhysglobal的实例(见图8):

图8

接下来详细分析一下这个结果的产生过程:首先rhysglobal函数读取其中〈函数列表〉的第一个元素——ssl,这是一个独立运行的负责产生短短长结构的函数,因此,它需要读取〈时长列表〉,而〈时长列表〉的第一个元素是4,即4小节的意思,那么rhysglobal产生了一个时长为4小节的短短长结构——((2 1 1)(2 1 1)(2 1 1 4))。继续,rhysglobal函数开始读取〈函数列表〉的第二个元素——(fssl 0),第二个元素自己本身是一个列表。这是由于fssl函数本身的特点决定的,不同于ssl,fssl虽然也产生短短长结构,但是它需要依赖于锚定声部提供的素材,才能运行函数,而且不支持使用者手动输入。从这一点上来说,fssl才像是一个自动化的函数。(为了方便,在我的使用习惯中,所有带“f”子母开头的函数,全部为自动化函数。f为function-automated的首字母。)所以,它需要提供给它锚定声部的素材内容,这就是列表中第二个元素0的功能。0即代表定位出rhysglobal函数的第一个结果,也就是刚刚经由ssl函数产生的结果,以它作为素材,运行fssl。从结果上不难看出,两者之间关联程度比较高。

ssl-〉((2 1 1)(2 1 1)(2 1 1 4))

(fssl 0)-〉((2 1 1)(2 1 1)(1 0.5 2 2 0.5 0.25 0.25 0.5 1))

最后,rhysglobal函数读取〈函数列表〉的最后一个元素——0,有别于〈函数列表〉中的前两个元素都是函数,0并不是一个函数,只是一个数值。但这并没有让计算机报错,原因就在于rhysglobal函数内置了定位函数,当识别出元素是数字的时候,内置的定位函数马上开始遍历(for-each函数)已经存在于rhysglobal函数中的结果,并按照数字把相应的结果取出来,再返还给rhysglobal函数。按照Scheme的计数方式,0即代表列表中的第一个元素,那么对于rhysglobal函数来说第一个元素就是函数ssl产生的((2 1 1)(2 1 1)(2 1 1 4)),所以,结果中又一次出现了((2 1 1)(2 1 1)(2 1 1 4))。

结 语

本文中实现的一些算法,主要是处于节奏这一音乐参数上面,其范围涵盖了自由平行乐段、综合型句法这两个基本结构元素的构建,以及设计结构函数来支持创作者对结构进行定制。并且,对于如何更加人性化处理一些节奏数据进行了一些探索。需要说明的是,在这条道路上需要作曲家不断地付出与探索,就像一款软件需要维护一样,因为需求总是在不断变化的,当有新的创作需求时,就需要完善、重构甚至重新设计对应的算法,来保障程序对创作的辅助作用。在这一过程中,作曲家也积累了越来越多的编程经验,懂得如何利用编程技术来达到自己的创作目的,从而会设计出更为贴近自身创作特征的程序。最后,笔者概括利用算法对创作进行辅助所带来的优势有三点:1.加速作曲过程。虽然前期的程序设计工作较为耗时,但一旦顺利完成,剩余工作将会轻松很多;2.会“反哺”并优化作曲家的创造思维。确立算法,并且植入程序后,所带来的效果就可以直接反馈给作曲家,这会给作曲家带来更多的灵感,也会给分析者带来更清楚的思路。3.由于不同功能的是以一个个模块的形式来储存的,这样有助于催生作曲家形成模块化的创作思维模式。

综上,本文的核心观点,就是作曲家首先需要把自己对于作品的艺术构想,转换为某种算法,或是算法的集合,然后把这些算法植入到程序的编写中,以求达到计算机辅助作曲的目的。其实在世界各地已经有相当一部分具有编程知识的作曲家在某种程度上进行了实践,但是运用范围还不够广泛,依然不成体系,而且实验性质的实践居多。作曲家可以根据自己的创作体系特点,把实现过的函数按照不同的属性予以归类,也可以把一些常用的作曲技术,编写进函数功能里,根据需要随时调取,以辅助自己的创作。

猜你喜欢
长句乐段谱例
“传术音乐”吉他教学(五)
乐器(2024年4期)2024-05-11 06:21:11
论豫剧唱腔中调式及调式运用分类与设计逻辑
美学视角之乐段辨析
黄河之声(2019年24期)2019-12-16 01:11:52
江南丝竹器乐合奏曲《三六》
长江丛刊(2018年28期)2018-10-31 08:51:22
武侠意境交响化:阿镗《神雕侠侣交响乐》本体论
艺术探索(2017年2期)2017-04-10 08:04:06
论曲式学中复乐段的辨析问题
北方音乐(2017年11期)2017-01-28 22:52:47
这样分析含同位语的长句
吼唱在关中大地上的“秦腔”——论小说《白鹿原》中长句和排比句的秦腔韵味
现代语文(2016年21期)2016-05-25 13:13:46
英语长句译法新探
——意群—动态对等法
音乐作品分析教学内容中的“非常规”曲式类型
——以乐段范畴为例
文教资料(2012年13期)2012-08-15 00:42:55