文章作者:Soledad Penadés,编辑:Angelina Fabbro。
上周,我们发布了 Brick 1.0,我们精心挑选的一套用于快速开发的 Web Components。使用组件可以轻松地将这些 UI 小部件与现有代码和框架集成。
而本周,我们为您带来的是 *音频标签*,这是一个构建 Web Components 的实验,这些组件代表 Web Audio 块,使我们能够构建一个完整的乐器并提供一个界面来演奏它。通过可重复使用的音频块,开发者可以在无需编写大量样板代码的情况下,体验 Web Audio。
让我们构建一个简单的合成器来演示不同的标签如何协同工作!
音频上下文
首先,我们需要一个音频上下文。如果您之前做过任何 Canvas 编程,这听起来会很熟悉。上下文类似于一个工具箱:它包含了您需要的函数(工具),也是所有操作发生的地方。所有其他音频标签都将放置在上下文中。
以下是如何在使用音频标签时呈现音频上下文
就是这样!
振荡器
虽然能够只通过键入一个标签声明来创建音频上下文很棒,但如果我们无法获得任何可听的输出,那它就没有那么令人兴奋。我们需要一些东西来生成声音,为此,我们将从简单的事情开始,并使用一个 *振荡器*。顾名思义,输出是一个在两个值之间振荡的信号:-1 和 1,生成周期性波形。我们将它放在音频上下文中,以便其输出通过上下文的输出自动路由到计算机的扬声器
(查看在线演示).
在现实世界中,振荡器 可以生成不同的信号形状。同样,在 Web Audio 世界中,我们也有类似的波形类型可以使用:正弦波、方波、锯齿波和三角波。由于 Web Components 是第一类 DOM 元素,因此我们可以使用属性指定所需的波形类型
您甚至可以通过打开控制台并键入以下内容来 *实时* 更改它
document.querySelector('audio-oscillator').type = 'square';
同样,您还可以通过设置 frequency
属性来更改振荡器运行的频率
混音器
运行一个振荡器仅仅是第一步。大多数可用的合成器都同时播放多个振荡器,以使声音更复杂、更细腻。我们需要某种方法并行播放两种或多种声音,同时将其组合成单个输出。
这通常被称为音频混音,因此我们需要一个 *混音器*
混音器将获取其每个子元素的输出,并将它们组合在一起形成其自己的输出,然后将其连接到上下文的输出。还要注意,由于我们正在处理 DOM 元素,所以当我们说“子元素”时,我们实际上指的是混音器的 DOM 子元素。
(示例).
链(和示波器)
当您开始添加多个声音时,能够 *查看* 合成器中发生了什么非常有用。如果我们能够以某种方式在某个子元素的输出和另一个子元素的输入之间插入一个组件,并显示该点处的声波看起来是什么样子?
我们无法使用混音器做到这一点,因为它只是将所有输出组合在一起。我们需要一个新的抽象结构:*链*。音频链将连接其第一个子元素的输出到第二个子元素的输入,以及第二个子元素的输出到第三个子元素的输入,依此类推,直到我们到达最后一个子元素,并将它的输出连接到链的输出。
或者换句话说:**混音器并行连接事物,而链串行连接事物**。
让我们使用一个链将一个新元素——示波器——连接到振荡器的输出。示波器将只显示连接到其输入的内容,并且信号将通过其输出而不被修改。您可以将振荡器的波形类型更改为 square
,并查看示波器如何相应地更改其显示。
(示例).
滤波器
合成器不仅限于同时运行多个振荡器。它们通常会在此原始生成的音频中添加后处理单元,从而赋予合成器自身独特的音色。
有许多类型的后处理效果,其中一些最流行的是 *滤波器*,它们大致通过突出显示某些频率或去除其他频率来工作。例如,我们可以将一个低通滤波器连接到振荡器的输出,这将只允许较低的频率通过。这会产生一种阻尼效果,就像我们戴上了耳罩一样,因为高频通过空气传播,我们用耳廓 *听到* 它们,而低频则倾向于通过地球和物体传播。因此,与其说我们听到它们,不如说我们用身体 *感觉到* 它们,无论你是否戴着东西遮住耳朵都没有关系。
(示例).
Web Audio 本身实现了 双二阶极点滤波器,并且与 audio-oscillator
标签一样,您可以通过设置其 type
属性来更改滤波器的行为。例如
您甚至可以插入多个示波器:一个在滤波器之前,另一个在滤波器之后,以查看滤波器对信号的影响
(示例).
最后,是迷你合成器
我们现在拥有足够的组件来构建一个合成器了!我们希望两个振荡器一起演奏(一个比另一个高八度),并使用一个滤波器使声音不那么刺耳,更“自成一体”。因此,毫不犹豫,这是使用我们迄今为止介绍的组件来表示我们的最小合成器 <mini-synth>
的结构
为了进行比较,这或多或少是我们如何使用原始的 Web Audio API 对象和函数组装类似的设置
var mixerGain = context.createGain();
var osc1 = context.createOscillator();
var osc2 = context.createOscillator();
osc1.connect(mixerGain);
osc2.connect(mixerGain);
var filter = context.createBiquadFilter();
mixerGain.connect(filter);
// and the actual output is at *filter*
代码本身并不复杂,只是它没有声明式语法的良好视觉层次结构。语法中的视觉提示使理解元素之间的关系变得快速而简单。
我们仍然需要几行 JavaScript 代码才能使 <mini-synth>
组件像合成器一样工作:它必须同时启动和停止两个振荡器。我们可以利用 AudioTag 原型 有一些通用的基本方法,我们可以重载这些方法以在自定义组件中获得特定的行为。
在这种特定情况下,我们将重载 start
和 stop
方法,以便在我们在合成器中调用这些方法时,分别使振荡器开始和停止播放。这样,我们就将合成器的内部抽象出来,同时仍然公开一个一致的接口。
start: function(when) {
// We want to make sure we don't clip (i.e. go under -1 or over 1),
// so we'll divide the gain by the number of oscillators in the synth
var oscGain = this.oscillators.length > 0 ? 1.0 / this.oscillators.length : 1.0;
this.oscillators.forEach(function(osc) {
osc.gain = oscGain;
osc.start(when);
});
}
stop: function(when) {
this.oscillators.forEach(function(osc) {
osc.stop(when);
});
}
该 实现 应该很容易理解。
您可能想知道 when
参数。它用于告诉浏览器何时实际开始操作,以便您可以以精确的时序安排未来的各种事件。它表示“在 when
毫秒时执行此代码”。在我们的例子中,我们只使用值 0,这意味着“立即执行”。我建议您在 Web Audio 规范 中了解更多关于 when
的信息。
我们还需要实现一个方法来实际告诉合成器要演奏哪个音符,或者换句话说,每个振荡器应该以什么频率运行。所以让我们实现 noteOn
noteOn: function(noteNumber) {
this.oscillators.forEach(function(osc, index) {
// Each oscillator should play in a higher octave
// Each octave is composed of 12 notes
var oscNoteNumber = noteNumber + 12 * index;
// We're using a library to convert note numbers to frequencies
var frequency = MIDIUtils.noteNumberToFrequency(oscNoteNumber);
osc.frequency = frequency;
});
}
您 *不需要* 使用 MIDIUtils,但如果您想在浏览器中与乐器一起 *即兴演奏*,而其他人使用的是更传统的 MIDI 乐器,那么它会派上用场。通过使用标准频率,您可以确保您的乐器都调到相同的音高,这 *很好*。
我们还需要一种触发合成器中音符的方法,那么还有什么比拥有一个屏幕键盘组件更好的方法呢?
将插入一个带有 2 个八度的键盘组件。一旦键盘获得焦点(通过单击它),您就可以敲击计算机键盘上的键,它将发出 noteon
事件。如果我们监听这些事件,我们就可以将它们发送到合成器。noteoff
事件也是如此
keyboard.addEventListener('noteon', function(e) {
var noteIndex = e.detail.index;
// 48 is the base note here = C-3
minisynth.noteOn(parseInt(noteIndex, 10) + 48);
minisynth.start();
}, false);
keyboard.addEventListener('noteoff', function(e) {
minisynth.noteOff();
}, false);
所以现在是 演示 时间了!
现在我们有了合成器,可以说我们是摇滚明星了!但摇滚明星需要看起来 *很酷*。现实生活中的摇滚明星有他们自己的签名吉他以及定制的音箱。而我们有……CSS!我们可以使用 CSS 尽情发挥,所以只需点击演示中的 *成为摇滚明星* 按钮,然后观看合成器如何凭借 CSS 的魔力变成 *其他东西*。
幕后一瞥
到目前为止,我们只讨论了这些花哨的新音频标签,并假设它们在您的浏览器中 *神奇地* 可用,尽管很明显它们是非标准元素。我们还没有解释它们的来源。好吧,如果您已经读到这里,那么您理应了解王国中的秘密!
如果您查看任何示例的源代码,您会注意到我们始终包含 AudioTags.bundle.js
(第 18 行) 和 AudioTags.bundle.css
(第 6 行) 文件。CSS 并不特别令人兴奋,真正的魔法发生在 JavaScript 中。此文件包含几个实用程序库,使我们能够在浏览器中定义自定义标签,然后是定义和使这些新标签在浏览器中可用的代码。
在实用程序方面,我们首先包含 AudioContext-MonkeyPatch,用于统一浏览器之间的 Web Audio API 差异,并使我们能够使用现代且一致的语法。如果您想了解有关编写可移植 Web Audio 代码的更多信息,可以查看 这篇文章。
我们包含的第二个库是 X-Tag,更具体地说,是它的最 内部核心。X-Tag 是一个自定义元素 polyfill,而自定义元素是新兴的 Web Components 规范 的一部分,这意味着这些内容很快就会内置到浏览器中。X-Tag 是 Mozilla Brick 使用的同一个库。您可以学习如何使用 这篇文章 创建自己的自定义元素。
也就是说,如果您计划在同一个项目中使用 Brick 和 Audio Tags,可能会发生灾难,因为 Brick 和 Audio Tags 都在其分发包中包含了 X-Tag 的核心。这两个库的作者正在讨论如何处理这个问题的最佳方法,但我们还没有达成任何行动,因为 Audio Tags 是 *由 X-Tag 提供支持的* 库场景中的新手。无论如何,最可能的结果是我们将提供一个选项来构建 Brick 和 Audio Tags *而无需* 包含 X-Tag 核心。
此外,这里有一个关于相同内容的视频,可在 CascadiaJS 上观看,因此您可以观看某人当面构建它。这可能有助于您理解主题
音频标签的下一步是什么?
许多人一直在问我音频标签的 *下一步* 是什么。即将推出的功能是什么?我计划了什么?如何贡献?我们如何添加新标签?
说实话?我不知道!但这就是它的魅力所在。这只是一个起点,一个邀请人们思考、玩耍和讨论声明式音频组件概念的机会。当然,在 Audio Tags 的 README 文件中,有一个尚不工作的列表、一些随机的想法以及可能的功能。我可能会继续扩展它并填补空白——这是一个在不变得太混乱的情况下试验音频的好场所,也是对超越通常的“增强型封装 UI 小部件”概念的 Web Components 的良好测试。
有些人发现这个项目本身很有启发性;另一些人认为它对教授信号处理很有用,另一些人将其与加速度计数据混合以创建物理控制的合成器,还有一些人决定放弃它的音频方面,而只是为 WebRTC 目的构建自定义组件。如果您想这样做,您可以贡献您的力量!
关于 Soledad Penadés
Sole 在 Mozilla 的开发者工具团队工作,帮助人们在 Web 上创造惊人的东西,最好是实时创造。在 irc.mozilla.org 上的 #devtools 频道找到她
关于 Angelina Fabbro
我是一位来自加拿大不列颠哥伦比亚省温哥华的开发者,在 Mozilla 担任技术布道师和 Firefox OS 的开发者倡导者。我喜欢 JavaScript、Web Components、Node.js、移动应用开发,以及这个我经常光顾的酷地方——万维网。哦,还有 Firefox OS。在我的业余时间,我上声乐课,玩万智牌,教人们编程,并与科学家合作,以促进程序员与科学家之间的互动。
15 条评论