JAM with Chrome (link:http://www.jamwithchrome.com/)是由 Google 创建的基于网络的音乐项目。 JAM with Chrome 可以让来自世界各地的人们在浏览器中实时组成乐队并即兴演奏。 我们 DinahMoe (link:http://www.dinahmoe.com/)很高兴成为这个项目的一部分。 我们的职责是为应用程序制作音乐,并设计和开发音乐组件。 开发包括三个主要领域:一个 音乐工作站(link:http://en.wikipedia.org/wiki/Music_Workstation),包括 MIDI 播放、软件采样器、音频效果、路由和混音; 实时交互控制音乐的音乐逻辑引擎; 以及一个同步组件,可确保会话中的所有玩家同时听到音乐,这是能够一起玩的先决条件。
为了实现最高级别的真实性、准确性和音频质量,我们选择使用 Web Audio API。 本案例研究将讨论我们遇到的一些挑战,以及我们如何解决这些挑战。 HTML5Rocks 上已经有许多很棒的介绍性文章可以帮助您开始使用 Web Audio,因此我们将直接跳入池的深处。
Web Audio API 在规范中包含许多有用的效果,但我们需要为 JAM with Chrome 中的乐器提供更精细的效果。 有很多种 例如 Web Audio 中有一个本地延迟节点,但延迟 (link:http://en.wikipedia.org/wiki/Delay_(audio_effect))——立体声延迟、乒乓延迟、回弹延迟等等。 幸运的是,所有这些都可以使用原生效果节点和一些想象力在网络音频中创建。
由于我们希望能够以尽可能透明的方式使用本机节点和我们自己的自定义效果,因此我们决定需要创建一种包装格式来实现这一点。 Web Audio 中的本地节点使用其连接方法将节点链接在一起,因此我们需要模拟这种行为。 这就是基本的想法:
var MyCustomNode = function(){
this.input = audioContext.createGain();
var output = audioContext.createGain();
this.connect = function(target){
output.connect(target);
};
};
通过这种模式,我们真的很接近本地节点。 让我们看看如何使用它。
//create a couple of native nodes and our custom node
var gain = audioContext.createGain(),
customNode = new MyCustomNode(),
anotherGain = audioContext.createGain();
//connect our custom node to the native nodes and send to the output
gain.connect(customNode.input);
customNode.connect(anotherGain);
anotherGain.connect(audioContext.destination);

我们的自定义节点和本机节点之间的唯一区别是我们必须连接到自定义节点输入属性。 我敢肯定有办法规避这一点,但这对我们的目的来说已经足够接近了。 可以进一步开发此模式以模拟本机 AudioNode 的断开连接方法,以及在连接时容纳用户定义的输入/输出等。 查看 规范 (link:https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html)以了解本机节点可以做什么。
现在我们有了创建自定义效果的基本模式,下一步是实际为自定义节点提供一些自定义行为。 让我们看一下 slapback 延迟节点。
slapback 延迟,有时称为 slapback 回声,是一种用于多种乐器的经典效果,从 50 年代风格的人声到冲浪吉他。 该效果采用传入的声音并播放声音的副本,并略微延迟大约 75-250 毫秒。 这给人一种声音被拍回来的感觉,因此得名。 我们可以像这样创建效果:
var SlapbackDelayNode = function(){
//create the nodes we’ll use
this.input = audioContext.createGain();
var output = audioContext.createGain(),
delay = audioContext.createDelay(),
feedback = audioContext.createGain(),
wetLevel = audioContext.createGain();
//set some decent values
delay.delayTime.value = 0.15; //150 ms delay
feedback.gain.value = 0.25;
wetLevel.gain.value = 0.25;
//set up the routing
this.input.connect(delay);
this.input.connect(output);
delay.connect(feedback);
delay.connect(wetLevel);
feedback.connect(delay);
wetLevel.connect(output);
this.connect = function(target){
output.connect(target);
};
};

正如你们中的一些人可能已经意识到的那样,这种延迟也可以用于更大的延迟时间,从而成为带有反馈的常规单声道延迟。 这是一个使用此延迟的示例,让您听到它的声音。
这个简单的效果可以做很多事情。 它是其他效果的基础,如合唱和移相器,但它也可以通过例如插入仅影响延迟信号的其他效果来进一步发展。
在专业音频应用程序中使用不同的乐器和音乐部分时,必须有一个灵活的路由系统,让您能够以有效的方式混合和调制声音。 在带有 Chrome 的 JAM 中,我们开发了一个音频总线系统,类似于物理混音板中的系统。 这使我们能够将所有需要混响效果的乐器连接到公共总线或通道,然后将混响添加到该总线,而不是将混响添加到每个单独的乐器。 这是一项重大优化,强烈建议您在开始执行更复杂的应用程序后立即执行类似的操作。

幸运的是,这在网络音频中很容易实现。 我们基本上可以使用我们为效果定义的骨骼并以相同的方式使用它。
var AudioBus = function(){
this.input = audioContext.createGain();
var output = audioContext.createGain();
//create effect nodes (Convolver and Equalizer are other custom effects from the library presented at the end of the article)
var delay = new SlapbackDelayNode(),
convolver = new tuna.Convolver(),
equalizer = new tuna.Equalizer();
//route ‘em
//equalizer -> delay -> convolver
this.input.connect(equalizer);
equalizer.connect(delay.input);
delay.connect(convolver);
convolver.connect(output);
this.connect = function(target){
output.connect(target);
};
};
这将像这样使用:
//create some native oscillators and our custom audio bus
var bus = new AudioBus(),
instrument1 = audioContext.createOscillator(),
instrument2 = audioContext.createOscillator(),
instrument3 = audioContext.createOscillator();
//connect our instruments to the same bus
instrument1.connect(bus.input);
instrument2.connect(bus.input);
instrument3.connect(bus.input);
bus.connect(audioContext.destination);
我们以一半的成本应用了延迟、均衡和混响(这是一种相当昂贵的效果,性能明智),就好像我们将效果应用于每个单独的乐器一样。 如果我们想给总线添加一些额外的趣味,我们可以添加两个新的增益节点 - preGain 和 postGain - 这将允许我们以两种不同的方式关闭或淡化总线中的声音。 preGain 放在效果之前,postGain 放在链的末尾。 如果我们随后淡化 preGain,则在增益达到底部后效果仍会产生共鸣,但如果我们淡化 postGain,所有声音将同时静音。
我在这里描述的这些方法可以而且应该得到进一步发展。 诸如自定义节点的输入和输出以及连接方法之类的东西可以/应该使用基于原型的继承来实现。 总线应该能够通过传递效果列表来动态创建效果。
为了庆祝 JAM with Chrome 的发布,我们决定将我们 的效果框架开源 (link:https://github.com/Dinahmoe/tuna)。 如果这个简短的介绍引起了您的兴趣,请看一下并随意贡献。 有一个 这里 (link:https://github.com/h5bp/lazyweb-requests/issues/82)关于标准化自定义网络音频实体格式的讨论。

