案例研究 - JAM with Chrome

我们如何打造出震撼人心的音效

Oskar Eriksson
Oskar Eriksson

简介

Chrome 音乐创作是 Google 推出的一项基于网络的音乐项目。借助 Chrome 的 JAM 功能,世界各地的用户都可以组建乐队,并在浏览器中实时即兴演奏。DinahMoe 很荣幸参与此项目。我们的工作是为应用制作音乐,并设计和开发音乐组件。开发工作分为三个主要方面:包括 MIDI 播放、软件采样器、音效、路由和混音的“音乐工作站”;用于实时互动控制音乐的音乐逻辑引擎;以及用于确保会话中的所有玩家都能同时听到音乐的同步组件,这是能够一起演奏的前提条件。

为了尽可能提高真实性、准确性和音质,我们选择使用 Web Audio API。本案例研究将讨论我们遇到的一些挑战,以及我们如何解决这些挑战。HTML5Rocks 上已经有许多非常棒的入门文章,可帮助您开始使用 Web Audio,因此我们将直接进入深水区。

编写自定义音效

Web Audio API 规范中包含许多实用效果,但我们需要为 JAM with Chrome 中的乐器提供更精致的效果。例如,Web Audio 中有一个原生延迟节点,但延迟有很多类型,例如立体声延迟、乒乓延迟、回弹延迟等等。幸运的是,所有这些效果都可以使用原生效果节点和一些想象力在 Web Audio 中创建。

由于我们希望能够尽可能以透明的方式使用原生节点和我们自己的自定义效果,因此决定创建一种能够实现此目的的封装容器格式。Web Audio 中的原生节点使用其 connect 方法将节点链接在一起,因此我们需要模拟此行为。基本思路如下所示:

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 的断开连接方法,以及在连接时适应用户定义的输入/输出等。请参阅规范,了解原生节点可以执行哪些操作。

现在,我们已经有了创建自定义效果的基本模式,接下来要做的是真正为自定义节点提供一些自定义行为。我们来看看回弹延迟节点。

用力拍打

回弹延迟(有时称为回弹回声)是一种经典效果,可用于多种乐器,从 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);
    };
};
Slapback 节点的内部路由

有些人可能已经意识到,这种延迟也可以用于更长的延迟时间,从而成为带有反馈的常规单声道延迟。下面的示例展示了如何使用此延迟,以便您听到其声音。

路由音频

在专业音频应用中处理不同的乐器和乐曲部分时,必须拥有灵活的路由系统,以便以有效的方式混音和调制声音。在 JAM with Chrome 中,我们开发了一套音频总线系统,类似于实体调音板中的系统。这样,我们就可以将需要混响效果的所有乐器连接到一个公共总线或声道,然后将混响效果添加到该总线,而不是将混响效果添加到每个单独的乐器。这是一次重大优化,我们强烈建议您在开始处理更复杂的应用时立即执行类似操作。

AudioBus 的路由

幸运的是,在 Web Audio 中实现这一点非常简单。我们基本上可以使用为特效定义的框架,并以相同的方式使用它。

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,则所有声音都会同时静音。

接下来该怎么做?

我在这里介绍的方法可以并且应该进一步发展。自定义节点的输入和输出以及连接方法等内容可以/应使用基于原型的继承来实现。通过传递效果列表,巴士应能够动态创建效果。

为了庆祝“Chrome 音乐节奏”功能的发布,我们决定将特效框架开源。如果您对此简短介绍感兴趣,欢迎随时查看并贡献内容。此处正在讨论如何为自定义 Web Audio 实体标准化格式。参与进来!