声音生成器,网络音频合成器

目录

介绍

赋予动机

高级功能

一句警告和免责声明

音频图表

主振荡器

波FFT

振荡器控制

调制

信封

音量信封

失谐信封

调制信封

过滤器

增益补偿

在应用程序中使用API和生成的仪器

应用程序接口

有趣的实现细节

具有私有成员的类

初始化

兼容性

现场播放

Credits

结论


这种浏览器内合成器创建用于音乐应用的乐器,提供先进的加法和减法合成技术。它基于Web Audio API,仅依赖于浏览器,不需要任何服务器端部分。它创造了乐器的合成器。合成器可以导出并嵌入到Web应用程序中。

介绍

这是该系列的第三篇文章,专门讨论使用屏幕键盘(包括微调键盘)进行音乐研究:

  1. 同构计算机键盘的音乐学习
  2. 使用半音格键盘进行微调音乐研究
  3. 当前文章
  4. 支持十指弹奏的多点触控​​​​​​​

本文和上一篇文章描述了单个项目微调织物的不同应用。本文介绍了所有微调织物应用程序使用的声音合成引擎。Sound Bilder应用程序提供了一种用于合成不同乐器的声音和创建声音库的方法。

另请参阅我在微调社区网站Xenharmonic Wiki上的页面。除了微调结构链接外,还有一些关于不同微调主题和个性的有用链接。

赋予动机

这项工作的主要推动力是迫切需要。

与此主题相关的两篇文章发表为使用同构计算机键盘进行音乐研究使用半音格子键盘进行微调音乐研究。尽管第一篇文章与微调系统无关,但它描述了该主题的一些基本理论解释。

第二个键盘是微色的,非常创新,并使用Web音频API在Web浏览器中工作。最近,它使用了我经过大量修改的fork一个具有非常严重缺陷的第三方开源库,我不打算在这里讨论。无论如何,当概念验证目标更重要时,它已经发挥了作用。这些天,我继续研究和开发,不能容忍这些缺陷和可维护性不足。后来,我决定开发自己的声音合成引擎和工具。换句话说,我开始这项工作只是因为我非常想得到结果。我向一些著名的音乐家、微调音乐专家、理论家和教育家介绍了我的成果,并得到了非常积极的反馈。我认为该工具先进、准确且有趣,足以分享它。

高级功能

该工具是一个合成器合成器,或乐器实例的生成器,可以以单个JSON文件的形式导出嵌入到其他一些JavaScript代码中,并用于实现基于Web Audio API的浏览器内乐器,或用于在网页上生成音乐的其他工具。该工具面向普通实践和微调谐波音乐应用。

代码的任何部分都不使用任何服务器站点操作,因此即使没有连接到Internet,代码的任何部分也可以在各种设备上本地播放。

该工具的某些功能有些创新。总体而言,它有助于生成近乎逼真的乐器声音,使用方便的UI以图形方式定义高级效果。用户不需要能够使用音频节点、绘制任何图形或理解Web音频API。相反,仪器的设计过程是基于在几个表格中填写数据,可能是一些反复试验的方法,听取综合的中间结果。

仪器创作过程从由傅里叶频谱定义的单个振荡器开始。这种频谱可以从可用代码样本的频谱分析仪导入,但通常需要额外的版本。或者,也可以使用锯齿波或三角形等传统信号波形,但更逼真的声音需要基于傅里叶的方法。

除此之外,用户可以定义无限数量的调制器。调制器可用于频率或幅度调制,每个调制器可以是绝对频率或相对频率调制器。对于绝对调制,用户可以为每个频率定义一个固定频率,但对于相对频率调制,调制信号的频率取决于每个音调的基频。

最重要的是,合成的结果可以使用包络进行成型。与传统的合成系统不同,除了通常的音量包络外,还有三种额外的包络:一种用于临时失谐,两种用于调制,分别用于频率和幅度调制。

除此之外,还有一组带有用户定义参数的过滤器。可以使用或排除筛选器的任何子集。

最后,最重要的是增益补偿系统。

在工作过程中,乐器的作者使用一组用于收听中间结果的交互式工具。有一个测试Jankó风格的键盘,对应于标准的88键钢琴,没有一个最高音。键盘可以用鼠标演奏,也可以用触摸屏用所有10个手指弹奏。对于这两种玩法,glissando也是可能的,这是有点不太微不足道的技术方面。另一个重要功能是:在试玩期间,可以暂时打开效果的任何类别,这对于比较很重要。此外,可以通过控制音量、(额外)延音和换位来完成试奏。这些控件不是最终乐器的一部分,而是专门为试奏而设计的。

一句警告和免责声明

合成就是这样一种东西...如果您使用此工具,请确保小心行事,以保护您的音乐听力免受相当大的冲击。在某些情况下,参数中的意外错误会产生可怕的几乎是创伤性的声音。我看不到自动过滤掉坏事的机会,此外,这是不公平的。

我不对产生不良或不愉快的声音承担任何责任;这是用户的唯一责任。我只能保证产生相当好听的声音是很有可能的。无论如何,我提供了一个包含一些仪器样本的库。

音频图表

首先,最重要的设计特点是:屏幕键盘的所有键应该能够同时产生声音。有人可能会问:为什么我们需要所有这些?如果我们只用10根手指玩?很简单:这是因为工具的持续价值。例如,如果我们按下钢琴的延音踏板并将其压低,原则上我们可以快速敲击所有88根弦,因此在某个时刻,无论听起来多么疯狂,它们都可以产生声音。

为了实现这一点,我们必须为每个乐器键专用相当数量的节点。图形的这些部分由Tone类型的对象实现。但是,我们也会尽量减少每个密钥节点的数量。可以通过在以下Instrument类型的另一个对象中共享我们的一些音频效果来完成。

首先,让我们看看我们可以分享什么。可以共享筛选器集。有些调制可以共享,但有些调制应该是每个密钥的。我开发了四种调制。我们可以应用无限数量的频率调制器(FM)和幅度调制器(AM)。这些调制器中的每一个都可以是绝对频率或相对频率。所有绝对频率调制器都可以放置在单实例Instrument中,Tone实例可以共享它们。但是相对频率调制器在某些频率上调制,这些频率对于每个音调都是不同的。在该工具中,实际频率是每个音调的基频,只需乘以某个因素,具有固定的调制深度,每个调制器单独。

因此,InstrumentTone两者都基于名为ModulatorSet的相同类型。对于一组调制器的实现,无论它扮演什么角色,绝对频率还是相对频率,实现都是一样的。

图中另一个每音部分是一组增益节点,用作包络的目标。包络是一种机制,用于对音调在其起音和阻尼中的动态行为进行编程。常规合成技术通常仅实现包络控制体积,具有固定数量的级数。通常的信封称为ADSR(攻击,衰减,维持和释放),但我什至不想在这里讨论它,因为它不能满足我。Sound Builder具有创作多种类型的信封的统一系统,可以创建具有无限个阶段的信封。每个阶段的特点是其时间、增益和三个功能之一的选择。节点有四个特征,每个特征都可以进行包络编程:音量、失谐、AMFM

现在,我们准备呈现一个图表,从它的音调部分开始。首先,让我们介绍一些图形约定。

振荡器节点

增益节点

信封,增益节点,增益由包络功能控制

调制器组

节点链

输出节点

该图以示意图方式分为两部分,左侧和右侧。在左侧,有一个单例实例Instrument 已连接,左边显示的一组Tone 实例仅显示一个实例。让我们记住,有一整套实例,它们都具有不同的基频。每个Tone 实例都以三种方式连接到Instrument 的实例。Instrument 实例提供来自FMAM调制器集的两个信号,这些信号对所有音调都是通用的,并从每个音调接收一个全形状的声音信号,由包络的功能调制和控制。

振荡器有四种。第一个为每个音调提供基频信号。每个音调图中的两组振荡器根据基频提供频率的FMAM信号,另外两组振荡器向所有音调图提供固定频率的FMAM信号。

在操作开始时,一旦填充并连接了整个图形,所有振荡器就会启动并永久提供其信号,即使没有发出声音。来自音调图的声音信号被其增益包络阻挡。振荡器仅在需要根据新的仪器数据重新填充图形时才停止。

让我们首先考虑主要的振荡器类型,即提供声音基础的振荡器。当然,在绝对所有情况下,它都会根据其基本频率提供整个频率频谱。

主振荡器

此节点由Web音频构造函数 OscillatorNode创建。

用户通过为type属性分配值来选择节点的频谱,这是字符串值、锯齿三角形或自定义。在“custom”的情况下,不应分配此字符串。相反,调用方法setPeriodicWave。在声音生成器UI中,它对应于傅立叶选项

这个调用是合成的工作马。波形的数据由Instrument 的实例提供,并由所有音调共享。这是因为傅里叶频谱的分量是相对于基频的。

在声音生成器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音频API,节点可以连接到另一个节点或作为节点属性的对象(如果该对象支持接口AudioParam)。这是一种调制对应于一个或另一个音频参数的值的方法。

OscillatorNode有两个a-rate属性,支持接口AudioParam频率和解

频率音频参数输入可用于FM,可以选择包络。同样可以对失谐执行,但对失谐参数进行调制将毫无意义。相反,可以通过信封对其进行修改。

调制

两种调制以不同的方式实现:FM信号连接到某个振荡器的频率输入,而AF需要在输出端完成,以调制已经生成并调制频率的信号的增益。

因此,如上所述,FM信号连接到频率AudioParam的振荡器节点,而AM信号连接到某个增益节点。

在这两种情况下,FMAM信号都连接到某个增益节点,在图表上分别显示为“FM包络“AM包络

这些增益模式中的每一个都从两个源接收FMAM信号,来自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;
}

重要的是要注意,指数函数以两种不同的方式实现:通过setTargetAtTimeexponentialRampToValueAtTime。当目标值为零时使用第一种情况,而阶段是最后一个。这样做是因为,出于明显的数学原因,指数的目标值不能为零。然后,该函数表示AudioParam 值对目标值的无限方法。当然,在最后阶段,这种行为代表乐器声音的自然阻尼或参数增长到对应于稳定声音的值,无论是否为零。在这两种情况下,无限指数行为都是最合适的。

使用声音生成器,包络可以选择应用于声音的三个特征:音量、失谐、FMAM

让我们讨论这些技术的应用。

音量信封

这是最传统的信封类型,例如在ADSR中使用。区别不仅在于无限数量的阶段。另一个体积包络特性是其倾倒可持续性。没有声音立即停止这样的事情。如果我们试图产生这样的瞬时停止,电子设备不是瞬时,而是非常快,自然会产生如此宽的频谱,这被认为是一种非常令人不快的噼啪声。如果攻击太快,也会发生同样的事情。我认为实际上不能使用任何快于10毫秒的时序,因此包络阶段时间受此最大持续时间的限制。例如,当我们弹奏钢琴并释放琴键时,就会发生倾倒延音。但是,当我们保持键下或使用维持键时会发生什么?

在这种情况下,行为由信封的最后阶段定义。我们基本上可以定义两种不同类型的工具。第一种类型适用于具有自然阻尼行为的乐器,例如钢琴或铃铛。对于这些仪器,最后一级增益的目标值应为零,但级本身可以延长,例如,长达几秒钟。如果我们把这个时间大约10毫秒或可能大几倍,我们可以达到更接近一些旋律打击乐的效果。第二种类型可以模拟无限声音的乐器,例如管乐器或弓弦乐器。最后一个包络级的目标增益应为最大值,或接近最大值。在这种情况下,仅当表演者释放琴键时,声音才会被阻尼。

失谐信封

乐器演奏的音调并不罕见,该音调在某些有限的时间内失谐,尤其是在攻击阶段。例如,它发生在响亮的指弹吉他演奏中。在弹拨琴弦的第一阶段,它相当细长,因此听起来比调音更尖锐。此外,起初,可以用更大的力将绳子压在品格上。这种效果可以通过控制振荡器的某些失谐值来实现。当然,与音量的情况不同,最后一级的失谐值应为零。

失谐包络是音频图形上唯一未表示为包络控制增益模式的包络。它的目标是音调的每个主振荡器的失谐 AudioParam。此功能不需要增益节点。效应的主增益只是一个数值因子;所有包络阶段的所有目标值均乘以此因子。

调制信封

剩下的两个包络是分别控制AMFM的包络,但我没有区分绝对频率的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,所有声音/*.js文件;
  • 此外,包括之前生成的.js文件;
  • 在应用程序中,创建一个Instrument实例;
  • 要应用仪器数据,请分配instrumentInstance.data = instrumentList[someIndex];
  • 要播放单个声音,请调用instrumentInstance.play;
  • 享受:-)。

现在,让我们详细看看API的外观。

应用程序接口

Instrument 构造函数需要2个参数:constructor(frequencySet, tonalSystem)

首先,需要为生成的声音定义一组基本频率。这要么是以赫兹为单位的频率数组,要么是一个{first, last, startingFrequency}对象,其中first 始终为零(保留用于高级功能),last        是频率阵列的最后一个索引,并且startingFrequency 是以赫兹为单位的最低绝对频率(标准27键钢琴为5.88)。第二个参数应指定音调系统,即八度音阶的音调数。在这种情况下,音调集将自动填充倍频程的等分频(EDO)频率。

例如:

const piano = new Instrument({ first: 0, last: 87, 25.7 }, 12) const piano = new Instrument({ first: 0, last: 87, 25.7 }, 12) 将创建一个标准的88键钢琴调音,典型的微调系统可以使用19293141等作为第二个参数。

const perfect = new Instrument([100, 133.333, 150]) 将创建一个只有3个音调、100 Hz和另外两个音调的乐器,完美的第四和完美的第五音调。

即使使用固定频率值数组,也会使用第二个参数tonalSystem这对于transposition属性的实现很重要。如果未指定此参数,则假定它是此数组的长度。

方法play(on, index)开始(如果on==true)或停止(如果on==false)指定index音调的阻尼。转储启动从来都不是瞬时的,它由增益包络和阻尼延时参数定义。时序受到一些最小值的限制。

属性:volume01)、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 类的基类。

初始化

有人可能想知道,为什么微调音乐研究项目的所有应用程序都以屏幕上的开始键或电源按钮开始。

如果在加载页面时尝试初始化Web音频子系统,它将失败。根据浏览器的不同,可能仍然可以生成声音,或者需要恢复 AudioContext。在这两种情况下,第一个声音都会丢失,如果在第一次按下乐器键时懒惰地执行初始化,则可能会延迟。因此,这两种做法都是肮脏的。

只有在网页的用户在此页面上提供任何输入后,Web音频才会开始完全运行。为什么网络音频的行为如此奇怪。我认为这很好。它试图保护网页的用户。用户合理地期望良好的行为。尽管许多网站违反了良好的做法,但人们通常不希望一个好的网站在没有用户明确同意的情况下产生任何声音。想象一下,如果这个人不小心加载了一个网页,有时会在半夜工作的用户会有什么感觉,这可能会唤醒整个家庭!Web浏览器试图保护用户免受此类事故的影响。

因此,Web音频应尽早初始化,但仅在某些用户的输入下初始化。

为了解决这个问题,使用了该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 引擎组合的浏览器。幸运的是,此类浏览器的设置非常广泛。

不幸的是,正式实现完整Web Audio APIMozilla浏览器在V8 + Blink组合的操作很好的情况下,会产生非常糟糕的噼啪声,无法消除。目前,该应用程序阻止使用这些浏览器和其他一些浏览器。

以下是浏览器无法处理任务时页面显示的建议文本:

此应用程序需要一个更符合标准的JavaScript引擎。

建议使用V8引擎的浏览器,例如ChromiumChromeOperaVivaldiMicrosoft Edge v. 80.0.361.111或更高版本,以及更多...

顺便说一下,我祝贺Microsoft人们放弃了在2020年之前用于Edge基本上已失效的EdgeHTML:-)

现场播放

该应用程序可以在Microtonal Fabric网站的此页面上播放。在这个网站上,人们可以找到几个基于声音生成器的音乐键盘应用程序以及一些使用声音生成器创建的合成乐器。所有这些都可以在他们的网页上直播。

此外,还可以根据使用声音生成器创建的数据演奏不同的(微调)乐器。请参阅微调织物主文档页面上实时播放应用程序部分。

同样,不使用服务器部分和网络,因此可以下载应用程序并在本地系统上使用。

最有可能的是,要开始使用,可能需要一组示例数据文件,可以从本文页面下载。

Credits

Wave FFT使用Chris LomontFast Fourier TransformC#实现

Valeri Brainin是一位著名的音乐学家,音乐经理,作曲家和诗人,着名的教学系统Brainin方法的作者,作为概念的早期作者,发明者或共同作者参加了微调音乐研究项目,最近基于Sound Builder实现了一些微调键盘设计。他开始在他的教学实践中使用这些键盘,并报告了非常有希望的结果。他还对乐器的性能进行了大量测试,并在开发过程中对声音进行了评估,提供了帮助我解决一些问题的反馈,以及一些已经实施或将在未来版本中实现的想法。

结论

它现在有效,我认为这个音乐声音合成的实验可以称为成功。

https://www.codeproject.com/Articles/5268512/Sound-Builder

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值