声音构建器,Web Audio 合成器





5.00/5 (26投票s)
浏览器内合成器创建可用于音乐应用程序的乐器,提供高级加法和减法合成技术
引言如果某事被禁止,但你极度渴望它,那么它就是……被允许的
- 民间智慧
目录
引言
动机
高级功能
警告与免责声明
音频图
主振荡器
波形FFT
振荡器控制
调制
包络
音量包络
失谐包络
调制包络
过滤器。
增益补偿
在应用程序中使用API和生成的乐器
API
有趣的实现细节
具有私有成员的类
初始化
兼容性
现场演奏
致谢
结论
引言
这是关于屏幕键盘(包括微音调键盘)音乐研究系列文章的第三篇
本文及前文描述了单一项目 Microtonal Fabric 的不同应用。本文描述了所有 Microtonal Fabric 应用程序使用的声音合成引擎。Sound Builder 应用程序提供了一种方法,用于合成不同乐器的声音并创建声音库。
另请参阅我在微音调社区网站 Xenharmonic Wiki 上的 页面。除了 Microtonal Fabric 链接外,还有一些关于不同微音调主题和人物的有用链接。
动机
这项工作的主要驱动力是巨大的需求。
与此主题相关的两篇文章分别发表为 使用同构电脑键盘进行音乐研究 和 使用半音阶键盘进行微音调音乐研究。尽管第一篇文章与微音调系统无关,但它描述了该主题的一些基本理论解释。
第二款键盘是*微半音阶*的,非常创新,并利用 Web Audio API 在 Web 浏览器中工作。最近,它使用了我大量修改的第三方开源库的一个分支,该库存在非常严重的缺陷,我不想在这里讨论。无论如何,当概念验证目标更重要时,它已经发挥了作用。如今,我继续研究和开发,无法容忍这些缺陷和维护不足。后来,我决定开发自己的声音合成引擎和工具。换句话说,我开始这项工作仅仅是因为我非常渴望得到结果。我向一些杰出的音乐家、微音调音乐专家、理论家和教育家介绍了我的成果,并得到了非常积极的反馈。我认为这个工具足够先进、准确和有趣,值得分享。
高级功能
该工具是一个合成器合成器,或乐器实例生成器,可以导出为单个 JSON 文件,嵌入到其他 JavaScript 代码中,并用于实现基于 Web Audio API 的浏览器内乐器,或用于在网页上生成音乐的其他工具。该工具面向通用实践以及微音调或异音程音乐应用程序。
代码的任何部分都不使用任何服务器端操作,因此代码的任何部分都可以在各种设备上本地播放,即使没有互联网连接。
该工具的一些功能颇具创新性。总的来说,它通过方便的用户界面以图形方式定义高级效果,有助于生成接近真实的乐器声音。用户无需掌握音频节点操作,无需绘制任何图形,也无需理解 Web Audio API。相反,乐器设计过程基于填充多个表格,可能会采用试错法并聆听合成的中间结果。
乐器创作过程始于一个由傅里叶频谱定义的单一振荡器。这样的频谱可以从现有代码示例的频谱分析仪导入,但通常需要额外编辑。或者,传统的信号波形,如锯齿波或三角波,也可使用,但更真实的声音需要基于傅里叶的方法。
在此基础上,用户可以定义无限数量的调制器。调制器可用于频率或幅度调制,并且每个调制器都可以是绝对频率或相对频率调制器。对于绝对调制,用户可以为每个调制器定义一个固定频率,但对于相对频率调制,调制信号的频率取决于每个音调的基频。
在此之上,合成结果可以通过包络进行塑形。与传统合成系统不同,除了常用的音量包络外,还有三种额外的包络:一种用于临时失谐,两种用于调制,分别用于频率和幅度调制。
在此之上,还有一套带有用户定义参数的滤波器。任何子集滤波器都可以被使用或排除。
最后,在此之上,还有一个增益补偿系统。
在工作中,乐器作者使用一套交互式工具来监听中间结果。有一个测试 Jankó 风格的键盘,对应于标准 88 键钢琴,缺少最高音。键盘可以用鼠标或用所有 10 根手指通过触摸屏演奏。对于这两种演奏方法,滑音也是可能的,这是一个不那么简单的技术方面。另一个重要特性是:在试奏期间,任何一类效果都可以暂时打开,这对于比较很重要。此外,试奏可以控制音量、(附加)延音和移调。这些控制不是最终乐器的一部分,而是专门为试奏设计的。
警告与免责声明
合成是这样一回事……如果您使用此工具,请务必小心操作,以保护您的听觉免受相当大的冲击。在某些情况下,参数中的意外错误可能会产生可怕的、几乎创伤性的声音。我看不到自动过滤掉坏东西的可能性,此外,那也是不公平的。
我不能对产生糟糕或不愉快的音效承担任何责任;这完全是用户的责任。我只能保证产生相当悦耳的音效是完全可能的。无论如何,我提供了一些乐器样本的库。
音频图
首先,最重要的设计特点是:屏幕键盘的所有按键都应该能够同时发声。有人可能会问:如果只用10根手指演奏,为什么需要所有按键呢?很简单:这是因为乐器的*延音*值。例如,如果踩下钢琴的延音踏板并保持踩下,原则上我们可以快速敲击所有88根琴弦,这样在某个时刻所有琴弦都可以发声,无论听起来多么疯狂。
为了实现这一点,我们必须为每个乐器键分配大量的节点。这些图的部分由`Tone`类型的对象实现。但是,我们也尽量减少每个键的节点数量。这可以通过在`Instrument`类型的另一个对象中共享一些音频效果来完成。
首先,让我们看看我们可以共享什么。滤波器组可以共享。一些调制可以共享,但有些应该按键区分。我开发了四种调制。我们可以应用无限数量的频率调制器(FM)和幅度调制器(AM)。每个调制器都可以是绝对频率或相对频率。所有绝对频率调制器都可以放置在单一实例的`Instrument`中,并且`Tone`实例可以共享它们。但相对频率调制器在每个音调的不同频率下进行调制。在工具中,实际频率是每个音调的基频,简单地乘以某个因子,调制深度固定,每个调制器独立。
因此,`Instrument`和`Tone`都基于名为`ModulatorSet`的相同类型。对于调制器集的实现,它扮演的角色(绝对频率或相对频率)无关紧要,实现是相同的。
图的另一个按音色划分的部分是一组用作“包络”目标的增益节点。包络是一种用于编程音色在其起奏和衰减中的动态行为的机制。习惯的合成技术通常只实现控制音量的包络,且具有固定数量的阶段。通常的包络称为ADSR(起奏、衰减、延音和释放),但我甚至不想在这里讨论它,因为它不能满足我。Sound Builder 具有一个统一的包络创作系统,可创建具有无限数量阶段的多种类型包络。每个阶段的特点是其时间、增益和三种功能中的一种选择。一个节点有四个特性,每个特性都可以通过包络编程:音量、失谐、AM 和 FM。
现在,我们准备好呈现一个图,从它的音调部分开始。首先,让我们介绍一些图形约定。
振荡器节点
增益节点
包络,增益节点,其增益由包络函数控制
调制器集
节点链
输出节点
该图表分左右两部分示意。左侧是`Instrument`的单例实例,连接着一组`Tone`实例,左侧只显示一个。请记住,`Tone`实例有一整套,所有实例都有不同的基频。每个`Tone`实例以三种方式连接到乐器实例。`Instrument`实例提供两组FM和AM调制器信号,这些信号对所有音色都是通用的,并从每个音色接收由包络功能完全塑形的声音信号,经过调制和控制。
有四种振荡器。第一种为每个音调提供基频信号。每个音调图中的两组振荡器提供取决于基频的FM和AM信号,另外两组振荡器为所有音调图提供固定频率的FM和AM信号。
在操作的最开始,一旦整个图表被填充并连接,所有振荡器都会启动并永久提供其信号,即使没有发出声音。音调图的声音信号被其增益包络阻挡。振荡器仅在需要根据新的乐器数据重新填充图表时才停止。
我们首先考虑主要类型的振荡器,即提供声音基础的振荡器。当然,在所有情况下,它都提供基于其基频的整个频谱。
主振荡器
此节点由 Web Audio 构造函数 `OscillatorNode` 创建。
用户通过给属性`type`赋值来选择节点的频谱,可以是字符串值“sawtooth”、“triangle”或“custom”。在“custom”的情况下,不应赋值此字符串。相反,应调用方法setPeriodicWave。在 Sound Builder UI 中,它对应于选择“Fourier”。
这个调用是合成的核心。波形数据由`Instrument`实例提供,并由所有音调共享。这是因为傅里叶频谱的分量是相对于基频的。
在 Sound Builder UI 中,频谱不通过数组或复数表示,而是通过等效的振幅和相位数组表示,用户可以在滑块表格上用鼠标“绘制”。每个滑块都是一个`type="range"`的输入元素,其行为经过修改,允许类似绘制的鼠标操作。
波形FFT
另一种输入数据的方式是使用一个独立的应用程序“WaveFFT.exe”(WAV文件到快速傅里叶变换)。它可以加载一个WAV文件,观察其波形,选择样本序列的一个片段(FFT支持样本数量为2的自然幂,这由U支持),执行FFT,观察结果频谱,并将这些数据保存为Sound Builder乐器数据文件的格式。
通常,使用 WaveFFT 创建的数据文件作为起点。创建乐器数据的另一种方法是使用本文可下载的示例文件。
这个应用程序可以作为一篇独立的文章的主题。简而言之,这是一个 .NET WPF 应用程序,因此可以在不同平台无需重新构建即可执行。需要安装相应的 框架。
构建设置为 .NET v. 5.0。它应该可以构建并适用于更高版本的 .NET。要更改目标 .NET 版本,只需更改“WaveFFT/Directory.Build.props”中的一行代码。
振荡器控制
通过 Web Audio API,一个节点可以连接到另一个节点或一个节点的属性对象,如果该对象支持 AudioParam 接口。这是一种调制与一个或另一个音频参数对应的值的方法。
振荡器节点 有两个支持 AudioParam 接口的 a-rate 属性:频率 和 失谐。
频率 AudioParam 输入可用于 FM,可选择进行包络。对失谐也可以这样做,但调制失谐参数意义不大。相反,它可以通过包络进行修改。
调制
两种调制方式的实现方式不同:FM信号连接到某些振荡器的频率输入,而AF需要在输出端进行,以调制已经生成并经过频率调制的信号增益。
因此,FM 信号连接到振荡器节点频率 AudioParam,如上文所述,AM 信号连接到某个增益节点。
在两种情况下,FM 和 AM 信号都连接到某个增益节点,在图中分别显示为“FM 包络”和“AM 包络”。
每个增益模式从两个来源接收FM和AM信号:来自`Instrument`实例的绝对频率调制信号,以及来自同一`Tone`实例内部的相对频率调制信号。
除此之外,我们还应记住,“FM 包络”和“AM 包络”增益节点也是包络。这意味着这些节点的增益值在演奏者按下琴键之前保持为零。当这种情况发生时,包络函数将应用于增益,以将信号输出。让我们看看它是如何工作的。
包络
包络的操作依赖于 AudioParam 的功能。每个 AudioParam 实例都可以编程为在未来自动更改值。当演奏者在某个乐器键盘上按下琴键时,增益和其他一些音频参数就会被编程以实现某些动态。
包络定义了一个时间函数,控制`AudioParam`实例的值。这是一个分段平滑函数,由3种类型的函数组成:指数函数、线性函数,以及在一定程度上具有约定性、较少使用的Heaviside函数。对于每个分段,用户向包络表添加一行,并定义每个步骤的持续时间和目标值。
这是“sound/envelope.js”中包络实现的核心
for (let element of this.#implementation.data) {
switch (element.type) {
case this.#implementation.typeHeaviside:
audioParameter.setValueAtTime(element.gain, currentTime + element.time);
break;
case this.#implementation.typeExponential:
if (element.gain == 0 || element.isLast)
audioParameter.setTargetAtTime(element.gain, currentTime, element.time);
else
audioParameter.exponentialRampToValueAtTime(
element.gain,
currentTime + element.time);
break;
case this.#implementation.typeLinear:
audioParameter.linearRampToValueAtTime(element.gain, currentTime + element.time);
break;
} //switch
currentTime += element.time;
}
需要注意的是,指数函数以两种不同的方式实现:通过 setTargetAtTime 或 exponentialRampToValueAtTime。第一种情况用于目标值为零且阶段为最后一个阶段时。这样做是因为,出于显而易见的数学原因,指数的目标值不能为零。然后该函数表示 AudioParam 值“无限”接近目标值的过程。自然地,对于最后一个阶段,这种行为表示乐器声音的自然衰减或参数增长到与稳定声音对应的值,无论是否为零。在这两种情况下,“无限”指数行为都是最合适的。
通过 Sound Builder,包络可以可选地应用于声音的三个特性:音量、失谐、FM 和 AM。
让我们讨论这些技术的应用。
音量包络
这是最传统的包络类型,例如在 ADSR 中使用。区别不仅在于无限数量的阶段。额外的音量包络特征是其衰减延音。声音不会瞬间停止。如果试图产生这样的瞬间停止,电子设备不会瞬间,而是非常迅速地停止,这自然会产生如此宽的频谱,以至于被感知为非常不愉快的噼啪声。如果起音太快,也会发生同样的事情。我认为任何快于 10 毫秒的时间都无法实际使用,因此包络阶段时间受此最大持续时间限制。衰减延音发生在我们演奏时,例如弹奏钢琴并松开琴键。但是当我们按住琴键或使用*延音*键时会发生什么呢?
在这种情况下,行为由包络的最后阶段定义。我们基本上可以定义两种不同类型的乐器。第一种类型适用于具有自然阻尼行为的乐器,如钢琴或钟琴。对于这些乐器,最后阶段增益的目标值应为零,但该阶段本身可以延长,例如,长达数秒。如果我们将此时间设置为约10毫秒或可能再长几倍,我们可以实现更接近某些旋律打击乐器的效果。第二种类型可以模拟“无限”声音的乐器,如管乐器或弓弦乐器。最后包络阶段的目标增益应为最大值或接近最大值。在这种情况下,声音仅在演奏者松开琴键时才被阻尼。
失谐包络
通常,乐器会演奏一个音高,在有限的时间内,尤其是在起奏阶段,会略微失谐。例如,在响亮的指弹吉他演奏中就会发生这种情况。在拨弦的第一个阶段,琴弦会被拉长,因此听起来比它调准的音高更尖。此外,起初琴弦可能会被更大的力按压在品丝上。这种效果可以通过控制振荡器的某个失谐值来实现。自然地,与音量的情况不同,最后一个阶段的失谐值应该为零。
失谐包络是唯一未在音频图上表示为包络控制增益模式的包络。它的目标是每个音调主振荡器的失谐AudioParam。此功能不需要增益节点。效果的主增益只是一个数值因子;所有包络阶段的所有目标值都乘以该因子。
调制包络
剩下的两个包络分别控制 AM 和 FM,但我并没有区分绝对频率 FM/AM 和相对频率 FM/AM。相反,一个包络控制所有 AM,另一个控制所有 FM。让我们考虑一个典型的包络情况。大号或萨克斯风,以及许多其他管乐器,在演奏者开始吹奏时会表现出极其强烈的振动,但随着声音稳定下来,振动变得几乎难以察觉。这可以通过这两种包络来实现。
过滤器。
合成的减法部分由一组二阶双二阶滤波器表示。在大多数情况下,都需要进行一些滤波。
滤波器参数通过滑块在表格中填写。每个提供的滤波器类型都可以包含在乐器图中,也可以排除。在大多数情况下,只有一个低通滤波器就足够了。
要学习滤波器调谐,BiquadFilterNode 文档 是一个很好的起点。
增益补偿
乐器合成的两种设计特点,即基于傅里叶频谱的加法合成和基于滤波器的减法合成,使得每个音色的最终音量难以预测。最糟糕的是:所有音色根据频率产生非常不同的主观音量水平。乐器的整体音量也可能显著不同。因此,迫切需要一些依赖于频率的增益补偿。我尝试了大量的研究,使用了各种“聪明”的函数,但最终采用了最简单的方法:由三个点定义的二次样条曲线:某个低频及其补偿水平,某个高频及其补偿水平,以及某个中频点,在该点补偿被认为等于 1。自然地,两个二次函数在该点连接。这个简单的函数具有以下优点:可以补偿该点右侧频谱的一部分,然后单独补偿频谱左侧的一部分。当左侧或右侧部分完成后,对另一部分的补偿不会破坏之前的工作。
此过程需要一个包含三个参数的表格:低频和高频补偿,以及中频的位置,再加上总补偿因子的值。低频和高频本身的两个值未在表格中呈现,因为它们很少(如果有的话)需要修改;它们对应于标准 88 键钢琴的最高或最低频率。然而,它们在数据文件中已规定(根据代码定义集写入),并且可以由一些过于聪明的用户修改。:-)
这是这个简单样条函数的实现
const compensation = (() => {
const compensation = (f) => {
const shift = f - this.#implementation.gainCompensation.middleFrequency;
const g0 = this.#implementation.gainCompensation.lowFrequencyCompensationGain;
const g1 = this.#implementation.gainCompensation.highFrequencyCompensationGain;
const f0 = this.#implementation.gainCompensation.lowFrequency;
const f1 = this.#implementation.gainCompensation.lowFrequency;
const factor0 = (g0 - 1) / Math.pow(
f0 - this.#implementation.gainCompensation.middleFrequency, 2);
const factor1 = (g1 - 1) /
Math.pow(f1 - this.#implementation.gainCompensation.middleFrequency, 2);
if (shift < 0)
return 1 + factor0 * Math.pow(shift, 2);
else
return 1 + factor1 * Math.pow(shift, 2);
} //compensation
return compensation;
})(); //setupGain
拼接发生在中频点,零导数点,但二次导数有阶跃。这种不完美的*平滑度*可能听起来令人质疑,但经过一些实验后,似乎能产生平滑的声音,双关语非本意 ;-)。
补偿函数取决于每个主振荡器的基频,增益补偿值是许多增益节点(每个`Tone`实例一个“补偿”增益节点,加上`Instrument`实例的一个节点“增益补偿”)操作的结果,如图中所示。
在应用程序中使用API和生成的乐器
自然,Sound Builder 如果不用于其他应用程序,就发挥不了多大作用。请参阅 https://SAKryukov.github.io/microtonal-chromatic-lattice-keyboard/SoundBuilder/API.html 上的说明。
基本上,用户需要制作一套所需的乐器并进行测试。
- 制作一套乐器,测试并保存它们,每种乐器都是一个单独的 JSON 文件;
- 使用“乐器列表”元素,“加载”按钮加载已创建的乐器,“保存”所有乐器到一个单独的 .js 文件中;
- 在应用程序中包含 API,所有 sound/*.js 文件;
- 此外,还包括之前生成的 .js 文件;
- 在应用程序中,创建一个 `Instrument` 的实例;
- 要应用乐器数据,请将 `instrumentInstance.data = instrumentList[someIndex]` 赋值;
- 要播放单个声音,请调用 `instrumentInstance.play`;
- 享受吧 :-)。
现在,让我们详细看看API的样子。
API
`Instrument` 构造函数需要 2 个参数:`constructor(frequencySet, tonalSystem)`。
首先,需要为生成的声音定义一组基频。这可以是一个以赫兹为单位的频率数组,也可以是一个对象`{first, last, startingFrequency}`,其中`first`始终为零(保留用于高级功能),`last`是频率数组的最后一个索引,`startingFrequency`是最低的绝对频率(以赫兹为单位,标准88键钢琴为27.5)。第二个参数应指定音调系统,即八度音程中的音调数量。在这种情况下,音调集将自动填充十二平均律(EDO)频率。
例如
`const piano = new Instrument({ first: 0, last: 87, 25.7 }, 12)` 将创建一个标准的 88 键钢琴调音,而典型的微音调系统可以使用 19、29、31、41 等作为第二个参数。
`const perfect = new Instrument([100, 133.333, 150])` 将创建一个只有 3 个音高的乐器:100 Hz,以及另外两个音高,即纯律中的纯四度和纯五度。
第二个参数`tonalSystem`,即使在使用固定频率值数组时也会使用——这对于实现`transposition`属性很重要。如果未指定此参数,则假定为该数组的长度。
方法`play(on, index)`启动(如果`on==true`)或停止(如果`on==false`)指定`index`音调的阻尼。阻尼的启动绝不是瞬时的,它由增益包络和阻尼延音参数定义。时间受到一些最小值的限制。
属性:`volume`(0 到 1),`sustain`(以秒为单位的时间),以及`transposition`(以八度音阶对数等分单位计,取决于`Instrument`构造函数的`tonalSystem`参数)。属性`frequencies`是只读的,它返回表示当前频率集的数组。
要包含API,请使用此代码示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <script src="../sound/definition-set.js"></script> <script src="../sound/chain-node.js"></script> <script src="../sound/envelope.js"></script> <script src="../sound/modulator.js"></script> <script src="../sound/modulator-set.js"></script> <script src="../sound/tone.js"></script> <script src="../sound/instrument.js"></script> <!-- ... ---> </head> <body> <!-- ... ---> </body> </html>
有趣的实现细节
不需要太多的代码来解释操作原理——无论如何,图表以最好的方式解释了这项技术的精髓,但一些技术特点也值得分享。
具有私有成员的类
经过一些实验和思考,我开发了一套方便的JavaScript类使用规范,只在方便时使用,并分离公共成员和私有成员。为了减少可能的混乱,我决定将所有对象的私有成员统一集中命名为`#implementation`。
为了演示,我展示了最简单的声音类`Modulator`。这是一个非常简单的事情,由OscillatorNode和GainNode实例组成的连接对。
class Modulator {
#implementation = { isEnabled: true };
constructor(audioContext) {
this.#implementation.oscillator = new OscillatorNode(audioContext);
this.#implementation.depthNode = new GainNode(audioContext);
this.#implementation.depthNode.gain.value = 100;
this.#implementation.oscillator.connect(this.#implementation.depthNode);
this.#implementation.oscillator.start();
this.#implementation.output = this.#implementation.depthNode;
this.#implementation.deactivate = _ => {
this.#implementation.oscillator.stop();
this.#implementation.oscillator.disconnect();
this.#implementation.depthNode.disconnect();
}; //this.#implementation.deactivate
} //constructor
get frequency() { return this.#implementation.oscillator.frequency.value; }
set frequency(value) { this.#implementation.oscillator.frequency.value = value; }
get depth() { return this.#implementation.depthNode.gain.value; }
set depth(value) { this.#implementation.depthNode.gain.value = value; }
connect(audioParameter) {
this.#implementation.output.connect(audioParameter);
return this;
} //connect
disconnect() { this.#implementation.output.disconnect(); return this; }
deactivate() { this.#implementation.deactivate(); }
}
希望这段代码能够自我解释。振荡器集合用于实现`ModulatorSet`,它是`Instrument`和`Tone`类的基类。
初始化
有人可能会问,为什么 Microtonal Music Study project 的所有应用程序都以屏幕上的“开始”键或电源按钮开始。
如果尝试在页面加载时初始化Web Audio子系统,它将失败。根据浏览器,生成声音可能仍然可行,或者需要恢复AudioContext。在两种情况下,第一个声音都会丢失,如果初始化是在首次按下乐器键时延迟执行的,则可能会延迟。因此,这两种做法都不妥。
Web Audio 只有在网页用户在该页面上提供任何输入后才能完全发挥作用。为什么 Web Audio 会以如此奇怪的方式运行?我认为这非常好。它试图保护网页用户。用户合理地期望良好的行为。尽管许多网站违反了良好实践,但人们通常不期望一个好网站在未经用户明确同意的情况下发出任何声音。想象一下,有时在半夜工作的人如果意外加载了一个网页,可能会唤醒整个家庭,他会作何感受!Web 浏览器试图保护用户免受此类事故的影响。
因此,Web Audio 应该尽早初始化,但只在用户输入后。
为了解决这个问题,使用了`initializationController`对象
const initializationController = {
badJavaScriptEngine: () => {
if (!goodJavaScriptEngine) {
const title = `This application requires JavaScript engine better conforming to the standard`;
const advice =
`Browsers based on V8 engine are recommended, such as ` +
`Chromium, Chrome, Opera, Vivaldi, Microsoft Edge v. 80.0.361.111 or later, and more`;
document.body.style.padding = "1em";
document.body.innerHTML = //...
return true;
} //goodJavaScriptEngine
}, //badJavaScriptEngine
initialize: function (hiddenControls, startControl, startControlParent, startHandler) {
for (let control of hiddenControls) {
const style = window.getComputedStyle(control);
const display = style.getPropertyValue("display");
this.hiddenControlMap.set(control, display);
control.style.display = "none";
} //loop
startControl.focus();
const restore = () => {
startControlParent.style.display = "none";
for (let control of hiddenControls) {
control.style.visibility = "visible";
control.style.display = this.hiddenControlMap.get(control);
} //loop
}; //restore
startControl.onclick = event => {
document.body.style.cursor = "wait";
startHandler();
restore();
document.body.style.cursor = "auto";
} //startControl.onclick
}, //initialize
hiddenControlMap: new Map()
}; //initializationController
此对象与页面控件处理得相当巧妙。其`initialize`方法接受一个要隐藏的元素数组、一个用作初始化命令输入的元素、其父元素以及名为`startHandler`的初始化方法。在初始阶段,要隐藏的元素会被隐藏,但其`style.display`属性值会被记住,以便在初始化完成后恢复。其他一切都无需解释。
注意`goodJavaScriptEngine`部分。这样做是为了过滤掉过时的JavaScript引擎,那些不支持私有类成员的引擎。不幸的是,Mozilla不支持它,但这只对这个应用程序有用,因为Mozilla Gecko声音渲染存在问题。另请参阅兼容性部分。
兼容性
该工具已在大量浏览器和一些不同的系统上进行测试。
我发现在撰写本文时,只有一种浏览器类型对 Web Audio API 的支持足够好:那些基于 V8 + Blink 引擎组合的浏览器。幸运的是,这类浏览器的数量相当广泛。
不幸的是,Mozilla 浏览器虽然形式上实现了完整的 Web Audio API,但会产生相当糟糕的噼啪声,无法消除,而在 V8 + Blink 组合操作正常的情况下。目前,该应用程序会阻止使用这些以及其他一些浏览器。
以下是当浏览器无法完成任务时页面显示的建议文本
此应用程序需要更符合标准的 JavaScript 引擎。
推荐使用基于 V8 引擎的浏览器,如 Chromium、Chrome、Opera、Vivaldi、Microsoft Edge v. 80.0.361.111 或更高版本等等…
顺便说一下,祝贺微软放弃了在2020年之前用于Edge的主要失效的EdgeHTML。:-)
现场演奏
该应用程序可以在Microtonal Fabric 网站的此页面上运行。在该网站上,可以找到几个基于 Sound Builder 的音乐键盘应用程序以及一些使用 Sound Builder 创建的合成乐器。所有这些都可以在其网页上进行现场演奏。
此外,还可以演奏基于 Sound Builder 创建的数据的不同(微调)乐器。请参阅 Microtonal Fabric 主文档页面上的“现场演奏应用程序”部分。
再次强调,不使用服务器部分和网络,因此该应用程序可以在本地系统上下载和使用。
很可能,要开始使用,您可能需要一套样本数据文件,可以从本文页面下载。
致谢
Wave FFT 使用 Chris Lomont 的快速傅里叶变换 C# 实现。
瓦列里·布赖宁(Valeri Brainin),一位杰出的音乐学家、音乐经理、作曲家和诗人,著名的教学系统布赖宁方法的作者,以概念的早期作者、某些微音键盘设计(最近基于 Sound Builder 实现)的发明者或共同作者的身份参与了微音音乐研究项目。他开始在他的教学实践中使用这些键盘,并报告了非常有希望的结果。他还对乐器的性能进行了大量的测试,并在开发过程中对声音进行了评估,提供了帮助我解决一些问题以及一些已实现或将在未来版本中实现的想法。
结论
现在它运行良好,我认为这个音乐声音合成实验可以称得上成功。
如果有人能尝试操作并给我一些反馈,我将不胜感激;我也将非常感谢任何问题、评论、建议,尤其是批评。
享受吧! :-)