你好,今天,我们会继续我们的音乐之旅。
动机
上周我们展示了异星工厂太空时代音乐(FFF-406)的一般方法。我们还提到,我们有一些新技术不仅可以覆盖这5个小时的音乐,而且还可以超越它们。这种自动创作音乐的方式是我很久以前就开始尝试的,在异星工厂之前。我在随机的低音部分上演奏了很多随机的旋律,随机的节奏基础,都是用动作脚本编程的(是的,很旧)。结果相当激烈,但从来没有好到足以认为它们是完成的曲目。当《法托里奥太空时代》五个小时的配乐项目开始时,我立刻想到了那些旧的实验。现在有了彼得作曲和多尼昂编程,事情看起来不一样了。我只是敢走这条路。现在我确信这是一个好决定。
可变的音乐曲目
每次选择这些曲目时,它们的播放方式都不同,它们是一种程序生成的音乐。但是我们不想太疯狂地随机化,可变曲目更像是单个曲目的一组变体(不需要全部录制)。这些曲目取代了在主曲目之间播放的插曲(除非你在隐藏的设置中寻找)。目标是在游戏中花费数十或数百个小时后提供一些多样化的音乐,常规音乐仍然是主要焦点,也是大部分配乐。可变音乐曲目在原型中定义,完全可供改装者使用。这些是用于定义可变曲目的组件:
⚙ 样本
这些是最小的积木。它们是根据其他规则播放的单独音乐片段。样本一个接一个地播放,因此当一个样本完成时,播放将与下一个样本无缝地继续。
⚙ 图层
样本被分组到图层中。图层决定了单个样本如何组合在一起。它可以像随机选择样本一样简单,洗牌所有可用的样本,这样每个样本只能播放一次,或者根据当前在不同图层中播放的样本来选择样本可能更复杂。图层还可以包含样本以特定方式重叠的子层。图层内的进一步变化可以使用许多属性来完成,定义延迟开始、洗牌图层的重复次数和重复之间的暂停次数、重叠子层的偏移量等。这些属性要么来自图层本身,要么来自轨道的当前状态。
图层及其样本的播放与每个轨道为自己定义的最小时间单位对齐,从而创建一种时间网格。
图层的组成方式是变化的主要来源。
⚙ 节
部分是层的集合。轨道中可以有一个部分或多个部分。使用哪个部分由轨道状态决定。此外,部分可以重叠。当只有一个部分时,它可以重叠自己。最后,一个部分可以包含一个间奏曲,作为普通音乐播放,提供了一个混合轨道的选项:部分可变,部分静态。
⚙ 状态
状态和状态之间的转换是定义变量轨道如何组成的高级方法。他们选择应该播放哪个部分以及它是否应该与前一个部分重叠,他们定义了许多应用于当前部分层的层属性。
状态之间的转换可以基于经过的时间,也可以与特定的图层完成相关联。可以用不同的权重定义多个可能的下一个状态,因此某些转换比其他转换更有可能。下一个状态候选可以定义额外的条件,这些条件必须满足才能考虑转换的状态。例如,可以将转换设置为仅在下一个状态选择时特定样本正在特定图层中播放时才发生。
现在我们知道了可变轨道是由什么组成的,让我们看几个轨道是如何组成的例子。请理解这些示例是一个技术演示,仍在进行中。一些细节可能会改变。音乐本身并不代表游戏发布时的内容,这些样本很旧,是为了说明目的而制作的!第一个例子是包含三个部分的曲目。每个部分有三层,低音层由两个子层组成,中间层也由两个子层组成,旋律层和中间音。状态之间的转换基于旋律层的整理。
这首歌的一个实例听起来可是这样:
星期五报道#407 - 自动化配乐 (音频#1)
图片仅供参考,与录音无关。这是一个实际的时间表:
0:01:曲目在开始状态下开始,第二部分是随机选择的。
0:01:低音层开始。
0:12:中音层开始。
0:17:旋律层开始。
0:44:旋律层完成第一次重复,在第二次重复之前排按暂停。
0:51:旋律层开始第二次重复。
1:19:旋律层完成第二次也是最后一次的重复,在完成播放之前排按暂停。
1:23:旋律层播放结束,触发过渡到帧间状态,随机选择与前一个不同的部分,本例中为第0部分。1:23:低音层和中音层继续播放,现在播放来自第 0 部分的样本。
1:23:旋律层再次开始播放。
1:42:旋律层完成第一次也是唯一一次重复播放。这次没有暂停,立即触发转换到继续状态。
1:42:再次使用在状态开始时选择的部分。
1:42:旋律层使用与开始状态相同的样本洗牌。
2:09:旋律层完成第一次也是唯一一次重复播放。之后再次没有暂停,立即触发到完成状态的转换。2:09:第一部分被选择为唯一尚未使用的部分。
2:35:旋律层完成第一次重复,继续第二次重复,没有停顿。
3:03:旋律层完成第二次也是最后一次重复。
第二个例子显示了一个只有一个部分的轨道,但是当以固定的时间间隔在状态之间转换时,它会重叠。该部分有三层。如果在第一层播放特定样本,也有机会播放间奏曲。
这听起来可能是这样的:
星期五报道#407 - 自动化配乐 (音频#2)
技术挑战
事实证明,当涉及到音乐时,正确的时机和过渡非常重要。我知道,我自己也很震惊。
样本排按
样本需要一个接一个地播放,没有任何间隙,以保持轨道的整体节奏并避免音频伪影,正如我们论坛上的某个人最近发现的那样(https://forums.factorio.com/112836)。音乐播放器每秒更新60次,与游戏逻辑的其余部分相同。简单地检查当前样本是否完成播放以开始播放下一个样本是不够的,因为它们之间可能有高达16.67ms(1s/60)的差距,破坏了节奏。将常规更新逻辑之外的检查放入单独的线程中,或者在样本完成播放时使用回调也不起作用,因为我们正在使用的SDL_Mixer库(版本2.0.4)(https://wiki.libsdl.org/SDL2_mixer/FrontPage) 将音频数据混合在一起。
在我们当前的设置中,音频以512个样本的块混合(这些是音频信号样本,而不是音乐样本),采样频率为44.1kHz,混合间隔约为11.6ms。即使我们检测到样本完成播放的确切时刻,我们也无法在那一刻开始播放下一个样本。会有一个长达11.6ms的间隙。我们真正需要的是一种方法来排队我们的音乐样本。
SDL_Mixer库不提供这样的功能,我需要自己在SDL_Mixer的基础上构建它,并进行一些修改以SDL_Mixer。这不是我第一次需要在音频后端添加功能,所以我没有退缩,让一个排队系统工作得相当快。现在音乐播放器可以在其悠闲的16.67毫秒窗口中排队样本,一个单独的进纸线程负责将样本正确拼接在一起,而SDL_Mixer甚至不知道发生了这种情况。
样本之间的转换
正如您在上面的图片中看到的,相同的样本可以播放不同的长度。例如,在图5的第一个示例中,首先为网格的三个单元播放3号样本(黄色),然后为四个单元播放。除非我们想以多种长度保存相同样本的变体(我们不想),否则我们通常需要在播放下一个样本之前将样本剪短。当你这样做时,你可能会得到令人不快的音频伪影或点击。如果你想自己尝试的话,当你在音乐或视频播放器中改变播放位置时,也会发生类似的事情。发生的情况是,音频信号的电平有一个很大的跳跃,听起来像是点击或弹出。如果我们想使用大词,信号就会变得不连续。
你可以在这段使用旧版本拍摄的录音中听到剪辑伪影(你可能需要调高音量)。很明显,必须对此采取措施。
星期五报道#407 - 自动化配乐 (音频#3)
解决这个问题的方法是在短时间内淡出和/或淡入样本,假设它们一个接一个地出现10毫秒。这样过渡就很好,很流畅(连续)。您可能会发现一些音频处理应用程序默认情况下会自动执行此类操作。听起来很简单,SDL_Mixer提供了一种淡入和淡出样本的方法,有什么问题吗?SDL_Mixer的内置淡入功能计算在11.6毫秒间隔内混合的整个块的相同目标音量。这意味着整个淡入将适合一个混合间隔,我们最终只有一个音量级别,因此根本没有淡入。更不用说不可能准确地计时淡出。
幸运的是SDL_Mixer提供了对样本附加过滤器(效果)的支持。在过滤器中,我们可以对音频数据做任何我们想做的事情。编写我们自己的具有样本级精度的推子(同样,这些是音频信号样本)作为过滤器是微不足道的。添加一个选项,将淡入淡出延迟到我们需要的确切时刻,瞧,不再有烦人的点击。
对齐图层
当涉及到所有层和子层一起演奏时,时间也很重要。它们需要对齐(同步)到由给定轨道的最小时间单位定义的时间网格中。第一个示例使用~286ms(12,600个样本)时间单位。
当SDL的音频线程混合时,它需要锁定音频设备以避免与其他线程(游戏的其余部分)竞争条件。在任何给定时间,只有一个线程应该更改活动的音频资源。出于同样的原因,当我们想开始播放样本时,音频设备被锁定。
即使在轨道本身启动后的一段时间内,可变轨道的某些层没有开始播放,我们也不能等待启动该层,因为我们无法在确切的时间启动它,没有“在x毫秒内开始播放”的功能,即使有,仍然会有我已经提到的混合块的问题。所以我们需要同时启动所有图层以使它们对齐,不活动的图层将播放静音。
音频线程可以随时跳入并锁定音频设备。例如,如果我们在启动总共五层中的前两层时运气不好,剩下的三层将在大约11.6毫秒后启动,不对齐。SDL_Mixer不提供显式锁定功能。SDL本身确实提供了它们,但是SDL_Mixer试图为大多数操作获取锁,所以无论如何都需要一些小调整,但添加起来并不难。
当一个图层在轨道中间没有播放任何东西时,也需要解决类似的问题。它不能被停止,因为我们无法在我们再次需要它的精确时刻重新启动它。它只是静默播放,与时间网格对齐。
上周常见问题
⚙ 音乐是否可以变得更有活力并且对游戏环境做出反应吗?
为了不涉及上周的技术细节,我们并不完全准确地说音乐播放器对游戏状态一无所知。显然,如果它能对正在观看的表面做出反应,它就会知道一些事情。然而,这是音乐播放器考虑的唯一游戏状态变量。
有这些限制的引擎并不意味着不可能将更多的信息传递到音乐播放器中,这是容易的部分。但是音乐本身必须从一开始就考虑到这样的系统,几年前。我们需要提前定义好所有这些条件,这样彼得才能以一种协同工作的方式作曲。这些是困难的部分。我不是说这是不可能的,但我们走了一条不同的路。
长话短说,如果现在还没有更有活力的音乐,它很可能不会发生。
⚙ 我们可以很好地控制音乐吗?
引擎有这些限制并不意味着不可能向音乐播放器传递更多信息,这是容易的部分。但音乐本身的创作必须在多年前就考虑到这一系统。我们需要事先明确所有这些条件,这样彼得就能以所有条件都能配合的方式进行创作。这些都是比较困难的部分。我并不是说这不可能做到,但我们走了一条不同的路。
⚙ 音乐会一直播放吗?
长话短说,如果现在还没有更有活力的音乐,那就很可能不会有了。
⚙ 太空时代原声带会单独出售吗?
是的,电子版本的配乐将可供购买,与基础游戏的配乐相同。