鸟鸣、具体音乐和 Web Audio API

2015 年 1 月,我的朋友兼合作者 Brian Belet 和我在第一届 Web Audio 大会 上展示了 Oiseaux de Même —— 一款基于鸟类录音创建的音频音景应用。在这篇文章中,我想描述一下我使用 Web Audio APITwitter Bootstrap、Node.js 和 REST API 实现此应用的经验。

Screenshot showing Birds of a Feather, a soundscape created with field recordings of birds that are being seen in your vicinity.

截图显示“Birds of a Feather”,一个使用您附近鸟类的实地录音创建的音景。

它是什么?具体音乐和公民科学

我们希望创建一个基于 Web 的 具体音乐,通过处理实地录音来构建艺术化的声音体验。我们决定使用 xeno-canto —— 一个包含超过 200,000 个 9,000 种不同鸟类的录音库 —— 作为我们的录音来源。几乎所有录音都由慷慨的录音师在知识共享许可下发布。我们根据来自 eBird 的数据从这个库中选择录音,eBird 是一个包含数千万条来自世界各地观鸟者贡献的鸟类目击信息的数据库。通过使用 地理位置 API 检索听众位置附近的 eBird 目击信息,我们的音景可以由观鸟者最近在听众附近报告的鸟类的录音组成 —— 每个用户都会获得一个每天都会变化的个性化音景。

Web Audio API 的使用

我们使用浏览器的 Web Audio API 播放来自 xeno-canto 的声音。Web Audio API 允许开发人员通过创建相互连接的 AudioNodes(就像旧的 模块化合成器)来播放、录制、分析和处理声音。

我们的音景使用四个 AudioBuffer 节点实现,每个节点循环播放一个实地录音。这些循环使用 Panner 节点放置在立体声场中,并在发送到听众的扬声器或耳机之前混合在一起。

控件

在所有声音加载并开始播放后,我们为用户提供了一些控件来在播放时操纵声音

  • **Pan** 按钮随机化声音在 3D 空间中的空间位置。
  • **Rate** 按钮随机化播放速度。
  • **Reverse** 按钮反转声音播放方向。
  • 最后,**Share** 按钮允许您捕获音景的状态并保存该快照以供以后使用。

上面描述的控件作为典型的 JavaScript 事件处理程序实现。例如,当按下**Pan** 按钮时,我们会运行此处理程序

// sets the X,Y,Z position of the Panner to random values between -1 and +1
BirdSongPlayer.prototype.randomizePanner = function() {
  this.resetLastActionTime();
  // NOTE: x = -1 is LEFT
  this.panPosition = { x: 2 * Math.random() - 1, y: 2 * Math.random() - 1, z: 2 * Math.random() - 1}
  this.panner.setPosition( this.panPosition.x, this.panPosition.y, this.panPosition.z);
}

Web Audio API 的某些部分是只写模式

我遇到了一些小问题,我不得不绕过 Web Audio API 的一些不足之处。其他作者已经记录了类似的经验;我将在本文中简要总结一下我的经验

  • **无法读取 Panner 位置**: 在**Share** 按钮的事件处理程序中,我想检索并存储当前 Audio Buffer 播放速率和 Panner 位置。但是,当前的 Panner 节点在设置位置后不允许检索位置。因此,除了调用 setPosition() 之外,我还将新的 Panner 位置存储在实例变量中。

    到目前为止,这对我的代码的影响很小。我的长期担忧是,我宁愿将位置存储在 Panner 中并从那里检索它,而不是将副本存储在其他地方。以我的经验,随着代码变得越来越大越来越复杂,同一信息的多个副本会成为可读性和可维护性问题。

  • **无法读取 AudioBuffer 的播放速率**: 上面描述的**Rate** 按钮在 playbackRate AudioParam 上调用 linearRampToValueAtTime()。据我所知,在调用 linearRampToValueAtTime() 后,AudioParams 不允许我检索其值,因此我必须在我的 JS 对象中保留此值的副本。
  • **无法读取 AudioBuffer 播放位置**: 我想向用户显示每个声音循环的当前播放位置,但是 API 没有提供此信息。我可以自己计算吗?不幸的是,在将 AudioBuffer 的 playbackRate 在随机值之间进行几次斜坡迭代后,很难计算缓冲区中的当前播放位置。与某些 API 用户不同,我不需要高度精确的位置,我只想在我的用户面前可视化当前声音循环何时重新开始。

使用 Web Audio 检查器进行调试

Firefox’s Web Audio inspector shows how Audio Nodes are connected to one another.

Firefox 的 Web Audio 检查器显示了 Audio Nodes 如何相互连接。



我非常成功地使用 Firefox 的 Web Audio 检查器来观察我的 Audio Nodes 在我的代码运行时如何创建和互连。

在上面的屏幕截图中,您可以看到四个 AudioBufferSource,每个 AudioBufferSource 在被 AudioDestination 求和之前都通过 GainNodePannerNode 传输。请注意,每个录音也连接到一个 AnalyzerNode;分析器用于为每个循环创建滚动幅度图。

可视化声音循环

随着音景的发展,用户通常想知道哪种鸟类负责他们在混合中听到的特定声音。我们为每个循环使用一个滚动可视化来显示瞬时幅度,创建您可以与您听到的内容关联的独特形状。该可视化使用 Analyzer 节点对声音执行 快速傅里叶变换 (FFT),这会产生每个频率的声音幅度。我们计算所有这些幅度的平均值,然后在 Canvas 的右侧绘制该幅度。当 Canvas 的内容在每个动画帧上向侧面移动时,结果是一个水平滚动的幅度图。

BirdSongPlayer.prototype.initializeVUMeter = function() {
  // set up VU meter
  var myAnalyser = this.analyser;
  var volumeMeterCanvas = $(this.playerSelector).find('canvas')[0];
  var graphicsContext = volumeMeterCanvas.getContext('2d');
  var previousVolume = 0;

  requestAnimationFrame(function vuMeter() {
    // get the average, bincount is fftsize / 2
    var array =  new Uint8Array(myAnalyser.frequencyBinCount);
    myAnalyser.getByteFrequencyData(array);
    var average = getAverageVolume(array);
    average = Math.max(Math.min(average, 128), 0);

    // draw the rightmost line in black right before shifting
    graphicsContext.fillStyle = 'rgb(0,0,0)'
    graphicsContext.fillRect(258, 128 - previousVolume, 2, previousVolume);

    // shift the drawing over one pixel
    graphicsContext.drawImage(volumeMeterCanvas, -1, 0);

    // clear the rightmost column state
    graphicsContext.fillStyle = 'rgb(245,245,245)'
    graphicsContext.fillRect(259, 0, 1, 130);

    // set the fill style for the last line (matches bootstrap button)
    graphicsContext.fillStyle = '#5BC0DE'
    graphicsContext.fillRect(258, 128 - average, 2, average);

    requestAnimationFrame(vuMeter);
    previousVolume = average;
  });
}

接下来是什么

我正在继续清理此项目的 JavaScript 代码。我的 Mozilla 同事提出了一些用户界面改进建议,我想尝试一下。Belet 教授和我正在考虑我们可以使用哪些其他地理标记声音来源来制作更多音景。在此期间,请 亲自尝试 Oiseaux de Même 并告诉我们您的想法!


6 条评论

  1. Bill Walker

    如果您在第一段中遇到链接问题,请重试——我遇到了一些服务器问题,现在已修复。

    2015 年 2 月 26 日 18:54

    1. shavounet

      您好,我仍然无法访问!我进入 http://birdwalker.com:9090/quartet.html,连接失败

      2015 年 2 月 27 日 00:39

      1. Havi Hoffman [编辑]

        @shavounet 我现在早上(太平洋标准时间)正在听鸟鸣。希望您能够连接。

        2015 年 2 月 27 日 08:19

      2. Bill Walker

        我遇到了服务器问题,我在调查期间提交了此问题
        https://github.com/wfwalker/loco-xeno-canto/issues/29

        请重试!谢谢,-Bill

        2015 年 2 月 27 日 17:14

  2. Andrew

    链接已损坏,重定向到 http://birdwalker.com:9090/quartet.html,但无法加载

    2015 年 2 月 26 日 23:43

    1. Bill Walker

      抱歉,我之前没有看到您的评论——我对服务器进行了一些修复,请重试,现在似乎可以工作了!

      2015 年 3 月 11 日 15:22

本文评论已关闭。