diff options
| author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
|---|---|---|
| committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
| commit | 33058f2b292b3a581333bdfb21b8f671898c5060 (patch) | |
| tree | 51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/web/api/web_audio_api | |
| parent | 8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff) | |
| download | translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2 translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip | |
initial commit
Diffstat (limited to 'files/zh-cn/web/api/web_audio_api')
6 files changed, 2152 insertions, 0 deletions
diff --git a/files/zh-cn/web/api/web_audio_api/basic_concepts_behind_web_audio_api/index.html b/files/zh-cn/web/api/web_audio_api/basic_concepts_behind_web_audio_api/index.html new file mode 100644 index 0000000000..4e4de574cb --- /dev/null +++ b/files/zh-cn/web/api/web_audio_api/basic_concepts_behind_web_audio_api/index.html @@ -0,0 +1,432 @@ +--- +title: 网页音频接口的基本概念 +slug: Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API +tags: + - 声音 + - 媒体 + - 指南 + - 概念 + - 网页音频接口 +translation_of: Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API +--- +<div class="summary"> +<p><span class="seoSummary">这篇文章解释了 网页音频接口(Web Audio API) 运作过程中的部分音频处理概念。本文并不会将你变为一名音频处理大师,但它可以给你足够的背景知识来理解 网页音频接口 的运行原理,并能让你在使用它时做出更好的决策。</span></p> +</div> + +<h2 id="音频节点:模块化连接">音频节点:模块化连接</h2> + +<p>网页音频接口(Web Audio API) 主要在 <strong>音频环境(audio context)</strong> 中进行音频处理,它也允许模块间的连接。基本的音频处理在 <strong>音频节点(audio node)</strong> 当中进行,这些节点连接在一起形成了 <strong>音频导向图(audio routing graph)</strong>。多个声源,甚至包含不同种类的声道,都可以在一个音频环境中进行处理。这种模块化的设计使得人们在创建复杂多变的声音特效时可以更加灵活。</p> + +<p>音频节点可以通过各自的输入与输出相连,形成一个 从一个或多个声源开始,经过处理节点,终止于末节点 的链式结构(有时你不需要末节点,比如你只是想数字化处理某些音频数据的时候)。一个简单、典型的网页音频接口的操作流程可以是这样的:</p> + +<ol> + <li>创建一个音频环境</li> + <li>在音频环境中,创建声源——例如{{HTMLElement("audio")}} 标签,振动发声器(oscillator),音频流</li> + <li>创建特效节点——例如混响,双二阶滤波,声相控制,音频振幅压缩</li> + <li>选择音频的终点——例如系统的扬声器</li> + <li>连接声源和特效,以及特效和终点。</li> +</ol> + +<p><img alt="A simple box diagram with an outer box labeled Audio context, and three inner boxes labeled Sources, Effects and Destination. The three inner boxes have arrow between them pointing from left to right, indicating the flow of audio information." src="https://mdn.mozillademos.org/files/12237/webaudioAPI_en.svg" style="display: block; height: 143px; margin: 0px auto; width: 643px;"></p> + +<p>每个输入和输出都可以包括几个声道,声道代表了一个特定的音效通道。各种声道分离结构都可以使用,包括<em>单声道</em>,<em>立体声</em>,<em>四声道</em>,<em>5.1</em>等等。</p> + +<p><img alt="Show the ability of AudioNodes to connect via their inputs and outputs and the channels inside these inputs/outputs." src="https://mdn.mozillademos.org/files/12239/audionodes_en.svg" style="display: block; height: 360px; margin: 0px auto; width: 630px;"></p> + +<p>声源可以来自不同的地方:</p> + +<ul> + <li>直接通过 Javascript 生成声音节点产生(例如一个振动发声器)</li> + <li>由脉冲编码调制产生的原始数据(音频环境中可以调用一些方法来解码部分支持的格式)</li> + <li>从 HTML 元素中(例如 {{HTMLElement("video")}} 或者 {{HTMLElement("audio")}} 标签)</li> + <li>直接通过一个 <a href="/en-US/docs/WebRTC" title="/en-US/docs/WebRTC">WebRTC</a> {{domxref("MediaStream")}} 获取流媒体(例如一个摄像头或麦克风)</li> +</ul> + +<h2 id="音频数据:什么是样本">音频数据:什么是样本</h2> + +<p>当一个音频信号被处理时,取样意味着从一个<a href="http://wikipedia.org/wiki/Continuous_signal">连续的信号</a>转化为<a href="http://wikipedia.org/wiki/Discrete_signal">离散的信号</a>;更具体地说,一个连续的声波(例如一个正在演奏的乐队发出的声音)会被转化成一系列的样本点(一个时间上离散的信号),计算机只可以处理这些离散的样本块。</p> + +<p>更多的细节可以查看维基百科的<a href="http://wikipedia.org/wiki/Sampling_%28signal_processing%29">采样</a>页面。</p> + +<h2 id="音频片段:帧,样本和声道">音频片段:帧,样本和声道</h2> + +<p>一个 音频片段({{ domxref("AudioBuffer") }}) 会包含几个组成参数:一个或几个声道(1代表<em>单声道</em>,2代表<em>立体声</em>等等),一个长度(代表片段中采样帧的数目)和一个采样率(是每秒钟采样帧的个数)。</p> + +<p>每个样本点都是一个 代表着该音频流在特定时间特定声道上的数值的 单精度浮点数。一个帧,或者一个采样帧是由一组在特定时间上的所有声道的样本点组成的——即所有声道在同一时间的样本点(<em>立体声</em>有2个,<em>5.1</em>有6个,等等,每个帧包含的样本点个数和声道数相同)。</p> + +<p>采样率就是一秒钟内获取帧的个数,单位是赫兹(Hz)。采样率越高,音频效果越好。</p> + +<p>现在让我们来看一下通道,一个单声道和一个立体声的音频片段,每个都是1秒钟,播放频率(采样率)为44100赫兹:</p> + +<ul> + <li>单声道片段会有 44100 个样本点和 44100 个帧。长度属性为 44100 。</li> + <li>立体声片段会有 88200 个样本点和 44100 个帧。长度属性依旧为 44100 ,因为长度总和帧的个数相同。</li> +</ul> + +<p><img alt="A diagram showing several frames in an audio buffer in a long line, each one containing two samples, as the buffer has two channels, it is stereo." src="https://mdn.mozillademos.org/files/12519/sampleframe.svg" style="height: 110px; width: 622px;"></p> + +<p>当一个音频片段开始播放时,你将会听到最左侧的样本帧,之后是他右侧相邻的一帧,以此类推。在立体声中,你将会同时听到两个声道。样本帧的概念在此时非常有用,因为每个样本帧代表特定的播放时间,而和声道个数无关,这种方式很有利于精确的多声道同步处理。</p> + +<div class="note"> +<p><strong>注意:</strong> 只需用帧的数目除以采样率即可得到播放时间(单位为秒)。用样本点数目除以声道个数即可得到帧的数目。</p> +</div> + +<p>下面我们将展示几个浅显易懂的示例:</p> + +<pre class="brush: js">var context = new AudioContext(); +var buffer = context.createBuffer(2, 22050, 44100); +</pre> + +<p>如果你使用上面的方法调用,你将会得到一个立体声(两个声道)的音频片段(Buffer),当它在一个频率为44100赫兹(这是目前大部分声卡处理声音的频率)的音频环境中播放的时候,会持续0.5秒:22050帧 / 44100赫兹 = 0.5 秒。</p> + +<div class="note"> +<p><strong>注意:</strong> 在 <a href="https://zh.wikipedia.org/zh-cn/數位音訊" title="Digital audio">数字音频</a> 中,<strong>44,100 <a href="https://wikipedia.org/wiki/Hertz" title="Hertz">赫兹</a></strong> (有时也写作 <strong>44.1 kHz</strong>)是一个常见的 <a href="https://wikipedia.org/wiki/Sampling_frequency" title="Sampling frequency">取样频率</a>。 为什么选取44.1kHz呢?首先,因为 <a href="https://wikipedia.org/wiki/Hearing_range" title="Hearing range">人耳的接收频率</a> 大约在 20 Hz 到 20,000 Hz 之间,根据 <a href="https://zh.wikipedia.org/wiki/采样定理" title="Nyquist–Shannon sampling theorem">采样定理</a>,采样频率一定要大于最终生成数据最大频率的二倍,因此就一定要大于 40,000 Hz (即 40kHz)。不仅如此,在采样之前信号还必须通过 <a href="https://zh.wikipedia.org/zh-cn/低通滤波器" title="Low-pass filter">低通滤波器</a> ,否则 会发生<a href="https://zh.wikipedia.org/zh-cn/混疊" title="Aliasing">混叠</a>现象,一个理想低通滤波器会完全留下低于20kHz的信号(且没有使它衰减)并完美阻拦一切高于20kHz的信号,而事实上 <a href="https://wikipedia.org/wiki/Transition_band" title="Transition band">过度频带(英文)</a>总是存在,在这个区域内信号会被部分衰减。这个频带越宽,建立一个 <a href="https://zh.wikipedia.org/zh-cn/抗混疊濾波器" title="Anti-aliasing filter">抗混叠滤波器</a> 才越容易。因此我们选取44.1kHz允许我们有2.05kHz的空间预留给过度频带。</p> +</div> + +<pre class="brush: js">var context = new AudioContext(); +var buffer = context.createBuffer(1, 22050, 22050);</pre> + +<p>如果你这样调用,你将会得到一个单声道的音频片段(Buffer),当它在一个频率为44100赫兹的音频环境中播放的时候,将会被自动按照44100赫兹*重采样*(因此也会转化为44100赫兹的片段),并持续1秒:44100帧 / 44100赫兹 = 1秒。</p> + +<div class="note"> +<p><strong>注意:</strong> <font face="Consolas, Liberation Mono, Courier, monospace">音频重采样与图片的缩放非常类似:比如你有一个</font>16 x 16的图像,但是你想把它填充到一个32 x 32大小的区域,你就要对它进行缩放(重采样)。得到的结果会是一个较低品质的(图像会模糊或者有锯齿形的边缘,这取决于缩放采用的算法),但它却是能将原图形缩放,并且缩放后的图像占用空间比相同大小的普通图像要小。重新采样的音频道理相同——你会节约一些空间,但事实上你无法产出高频率的声音(高音区)。</p> +</div> + +<h3 id="分离式与交错式音频片段">分离式与交错式音频片段</h3> + +<p>网页音频接口使用了分离式的片段储存方式:左(L)右(R)声道像这样存储:</p> + +<pre>LLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRR (对于一个有16帧的音频片段)</pre> + +<p>这种储存方式在音频处理中非常常见:这种方式允许对每个声道单独处理。</p> + +<p>另一种储存方式是使用交错式的片段储存方式:</p> + +<pre>LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLR (对于一个有16帧的音频片段)</pre> + +<p>这种方式在存储以及播放音频等不需要音频加工的操作中非常常见,例如一个解码后的MP3流媒体。<br> + <br> + 在开发者接触到的网页音频接口中<u><strong>只有</strong></u>分离式音频片段,因为它主要用于音频加工。在处理过程中它使用分离式,但是当它传送到声卡中用于播放时,音频片段会被转化为交错式。反过来,当MP3被解码时,初始状态它是交错式的,但是他会被转化成分离式以用于音频加工。</p> + +<h2 id="声道">声道</h2> + +<p>不同的音频片段可能包含不同数量的声道个数,从基本的<em>单声道</em>(只有一个声道),<em>立体声</em>(左右两个声道),到更加复杂的<em>四声道</em>,<em>5.1</em>。由于每个声道中包含的音频数据可以不同,因此声道越多听觉效果越好。在不同声道模式中,表示特定声道的缩写如下表所示:</p> + +<table class="standard-table"> + <tbody> + <tr> + <td><em>单声道</em></td> + <td><code>0: M: 唯一声道</code></td> + <td><em>Mono</em></td> + <td><code>0: M: mono</code></td> + </tr> + <tr> + <td><em>立体声</em></td> + <td><code>0: L: 左<br> + 1: R: 右</code></td> + <td><em>Stereo</em></td> + <td><code>0: L: left<br> + 1: R: right</code></td> + </tr> + <tr> + <td><em>四声道</em></td> + <td><code>0: L: 左<br> + 1: R: 右<br> + 2: SL: 环绕左<br> + 3: SR: 环绕右</code></td> + <td><em>Quad</em></td> + <td><code>0: L: left<br> + 1: R: right<br> + 2: SL: surround left<br> + 3: SR: surround right</code></td> + </tr> + <tr> + <td><em>5.1</em></td> + <td><code>0: L: 左<br> + 1: R: 右<br> + 2: C: 中央<br> + 3: LFE: 低音炮<br> + 4: SL: 环绕左<br> + 5: SR: 环绕右</code></td> + <td><em>5.1</em></td> + <td><code>0: L: left<br> + 1: R: right<br> + 2: C: center<br> + 3: LFE: subwoofer<br> + 4: SL: surround left<br> + 5: SR: surround right</code></td> + </tr> + </tbody> +</table> + +<div class="note"> +<p><strong>注意:</strong> 由于缩写来自英文,因此保留英文作对照。</p> +</div> + +<h3 id="向上和向下混频">向上和向下混频</h3> + +<p>当输入与输出的声道数不同时,我们就需要按照如下方法进行混频。这些封装好的方法可以通过设置声音节点的 {{domxref("AudioNode.channelInterpretation")}} 属性为 <code>"speakers"</code>(扬声器) 或 <code>"discrete"</code>(离散声道) 进行混频。</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="row">混频方式(Interpretation)</th> + <th scope="col">输入声道模式</th> + <th scope="col">输出声道模式</th> + <th scope="col">混频详细规则</th> + </tr> + </thead> + <tbody> + <tr> + <th colspan="1" rowspan="13" scope="row"> + <p><code>speakers</code></p> + + <p>(扬声器)</p> + </th> + <td> + <p><code>1</code><br> + <em>(单声道)</em></p> + </td> + <td><code>2</code><br> + <em>(立体声)</em></td> + <td><em>从单声道到立体声的向上混频。</em><br> + 唯一的输入声道(M)会被同时用于立体声的两个声道(L和R)。<br> + <code>output.L = input.M<br> + output.R = input.M</code></td> + </tr> + <tr> + <td><code>1</code><br> + <em>(单声道)</em></td> + <td><font face="Consolas, Liberation Mono, Courier, monospace">4</font><br> + <em>(四声道)</em></td> + <td><em>从单声道到四声道的向上混频。</em><br> + 唯一的输入声道(M)会被同时用于非环绕声的两个声道(L和R)。<br> + 环绕声道(SL和SR)将为静音。<br> + <code>output.L = input.M<br> + output.R = input.M<br> + output.SL = 0<br> + output.SR = 0</code></td> + </tr> + <tr> + <td><code>1</code><br> + <em>(单声道)</em></td> + <td><font face="Consolas, Liberation Mono, Courier, monospace">6</font><br> + <em>(5.1)</em></td> + <td> + <p><em>从单声道到5.1的向上混频。</em><br> + 唯一的输入声道(M)会被同时用于中央声道(C)。<br> + 其余所有声道(L,R,LFE,SL,SR)都将保持静音。<br> + <code>output.L = 0<br> + output.R = 0</code><br> + <code>output.C = input.M<br> + output.LFE = 0<br> + output.SL = 0<br> + output.SR = 0</code></p> + </td> + </tr> + <tr> + <td><code>2</code><br> + <em>(立体声)</em></td> + <td><code>1</code><br> + <em>(单声道)</em></td> + <td><em>从立体声到单声道的向下混频。</em><br> + 两个输入声道(L和R)将会被均等的合并到唯一的输出声道(M)当中。<br> + <code>output.M = 0.5 * (input.L + input.R)</code></td> + </tr> + <tr> + <td><code>2</code><br> + <em>(立体声)</em></td> + <td><font face="Consolas, Liberation Mono, Courier, monospace">4</font><br> + <em>(四声道)</em></td> + <td><em>从立体声到四声道的向上混频。</em><br> + 输入的左右声道(L和R)分别对应输出的非环绕左右声道(L和R)。<br> + 环绕左右声道(SL和SR)将保持静音。<br> + <code>output.L = input.L<br> + output.R = input.R<br> + output.SL = 0<br> + output.SR = 0</code></td> + </tr> + <tr> + <td><code>2</code><br> + <em>(立体声)</em></td> + <td><font face="Consolas, Liberation Mono, Courier, monospace">6</font><br> + <em>(5.1)</em></td> + <td><em>从立体声到5.1的向上混频。</em><br> + 输入的左右声道(L和R)分别对应输出的非环绕左右声道(L和R)。<br> + 其余所有输出声道(C,SL,SR和LFE)将保持静音。<br> + <code>output.L = input.L<br> + output.R = input.R<br> + output.C = 0<br> + output.LFE = 0<br> + output.SL = 0<br> + output.SR = 0</code></td> + </tr> + <tr> + <td><font face="Consolas, Liberation Mono, Courier, monospace">4</font><br> + <em>(四声道)</em></td> + <td><code>1</code><br> + <em>(单声道)</em></td> + <td><em>从四声道到单声道的向下混频。</em><br> + 四个输入声道(L,R,SL和SR)将会被均等的合并到唯一的输出声道(M)当中。<br> + <code>output.M = 0.25 * (input.L + input.R + </code><code>input.SL + input.SR</code><code>)</code></td> + </tr> + <tr> + <td><font face="Consolas, Liberation Mono, Courier, monospace">4</font><br> + <em>(四声道)</em></td> + <td><code>2</code><br> + <em>(立体声)</em></td> + <td><em>从四声道到立体声的向下混频。</em><br> + 两个输入左声道(L和SL)将会被均等的合并到输出的左声道(L)当中。<br> + 相似的,两个输入右声道(R和SR)将会被均等的合并到输出的右声道(R)当中。<br> + <code>output.L = 0.5 * (input.L + input.SL</code><code>)</code><br> + <code>output.R = 0.5 * (input.R + input.SR</code><code>)</code></td> + </tr> + <tr> + <td><font face="Consolas, Liberation Mono, Courier, monospace">4</font><br> + <em>(四声道)</em></td> + <td><font face="Consolas, Liberation Mono, Courier, monospace">6</font><br> + <em>(5.1)</em></td> + <td><em>从四声道到5.1的向上混频。</em><br> + 四个输入声道(L,R,SL和SR)将会分别进入它们对应的输出声道(L,R,SL和SR)当中。<br> + 其余输出声道(C和LFE)将保持静音。<br> + <code>output.L = input.L<br> + output.R = input.R<br> + output.C = 0<br> + output.LFE = 0<br> + output.SL = input.SL<br> + output.SR = input.SR</code></td> + </tr> + <tr> + <td><font face="Consolas, Liberation Mono, Courier, monospace">6</font><br> + <em>(5.1)</em></td> + <td><code>1</code><br> + <em>(单声道)</em></td> + <td><em>从5.1到单声道的向下混频。</em><br> + 输入的低音炮声道(LFE)将会被抛弃。<br> + 其余声道按不同权值混合到唯一的输出声道(M):<br> + 输入的中央声道(C)权值为1,<br> + 输入的非环绕侧声道(L和R)有所减弱,权值为<code>√2/2(即1/√2</code>,<code>约等于0.7071)</code>,<br> + 输入的环绕声道(SL和SR)进一步衰减,权值为0.5。<br> + <code>output.M = 0.7071 * (input.L + input.R) + input.C + 0.5 * (input.SL + input.SR)</code></td> + </tr> + <tr> + <td><font face="Consolas, Liberation Mono, Courier, monospace">6</font><br> + <em>(5.1)</em></td> + <td><code>2</code><br> + <em>(立体声)</em></td> + <td><em>从5.1到立体声的向下混频。</em><br> + 输入的低音炮声道(LFE)将会被抛弃。<br> + 对于每侧的输出声道(L或R):由输入的中央声道(C)先与同侧环绕声道(SL或SR)混合,加权(权值为<code>√2/2</code>)后与输入的同侧非环绕声道(L或R)混合得到。<br> + <code>output.L = input.L + 0.7071 * (input.C + input.SL)<br> + output.R = input.R + 0.7071 * (input.C + input.SR)</code></td> + </tr> + <tr> + <td><font face="Consolas, Liberation Mono, Courier, monospace">6</font><br> + <em>(5.1)</em></td> + <td><font face="Consolas, Liberation Mono, Courier, monospace">4</font><br> + <em>(四声道)</em></td> + <td><em>从5.1到四声道的向下混频。</em><br> + 输入的低音炮声道(LFE)将会被抛弃。<br> + 对于每侧的输出声道(L或R):由输入的中央声道(C)加权(权值为√2/2)后,与输入的同侧非环绕声道(L或R)混合后得到。<br> + 对于每侧的输出环绕声道(SL或SR):由同侧输入环绕声道(SL或SR)会不经改变直接传入。<br> + <code>output.L = input.L + 0.7071 * input.C<br> + output.R = input.R + 0.7071 * input.C<br> + <code>output.SL = input.SL<br> + output.SR = input.SR</code></code></td> + </tr> + <tr> + <td colspan="2" rowspan="1">其他,非标准声道配置</td> + <td> + <p>非标准的声道配置输入将会被按照 <code>channelInterpretation</code> 属性设置为 <code>discrete</code> 时的情况处理。</p> + + <div class="note"> + <p>W3C规范中明确指出允许未来定义新的声道配置标准,因此未来在浏览器中使用此项的输出结果可能与现在不相同。</p> + </div> + </td> + </tr> + <tr> + <th colspan="1" rowspan="2" scope="row"> + <p><code>discrete</code></p> + + <p>(离散声道)</p> + </th> + <td rowspan="1">任意<br> + (x个声道)</td> + <td rowspan="1">任意<br> + (y个声道)<br> + 其中<code>x<y</code></td> + <td><em>向上混频离散的声道。</em><br> + 根据相应的频道序号,将输入声道一对一的填入到输出声道中。对于没有输入声道能够对应的,该输出声道将保持静音。</td> + </tr> + <tr> + <td rowspan="1">任意<br> + (x个声道)</td> + <td rowspan="1">任意<br> + (y个声道)<br> + 其中<code>x>y</code></td> + <td><em>向下混频离散的声道。</em><br> + 根据相应的频道序号,将输入声道一对一的填入到输出声道中。对于没有输出声道能够对应的,该输入声道将被抛弃。</td> + </tr> + </tbody> +</table> + +<h2 id="可视化">可视化</h2> + +<p>一般来说,可视化是通过获取各个时间上的音频数据(通常是振幅或频率),之后运用图像技术将其处理为视觉输出(例如一个图像)来实现的。网页音频接口提供了一个不会改变输入信号的音频节点 {{domxref("AnalyserNode")}},通过它可以获取声音数据并传递到像 {{htmlelement("canvas")}} 等等一样的可视化工具。</p> + +<p><img alt="Without modifying the audio stream, the node allows to get the frequency and time-domain data associated to it, using a FFT." src="https://mdn.mozillademos.org/files/12521/fttaudiodata_en.svg" style="height: 206px; width: 693px;"></p> + +<p>你可以通过如下方法获取需要的音频数据:</p> + +<dl> + <dt>{{domxref("AnalyserNode.getFloatFrequencyData()")}}</dt> + <dd>返回一个{{domxref("Float32Array")}} 数组,其中包含传递到此音频节点声音的实时频率数据。</dd> +</dl> + +<dl> + <dt>{{domxref("AnalyserNode.getByteFrequencyData()")}}</dt> + <dd>返回一个{{domxref("Uint8Array")}} 无符号字节数组(unsigned byte array),其中包含传递到此音频节点声音的实时频率数据。</dd> +</dl> + +<dl> + <dt>{{domxref("AnalyserNode.getFloatTimeDomainData()")}}</dt> + <dd>返回一个{{domxref("Float32Array")}} 数组,其中包含传递到此音频节点声音的实时波形,时间数据。</dd> + <dt>{{domxref("AnalyserNode.getByteTimeDomainData()")}}</dt> + <dd>返回一个{{domxref("Uint8Array")}} 无符号字节数组(unsigned byte array),其中包含传递到此音频节点声音的实时波形,时间数据。</dd> +</dl> + +<div class="note"> +<p><strong>注意:</strong> 更多信息可以参考我们的这篇文章:<a href="/zh-CN/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API">基于Web Audio API实现音频可视化效果</a> 。</p> +</div> + +<h2 id="空间位置化">空间位置化</h2> + +<div> +<p>音频的空间化(由网页音频接口的 {{domxref("PannerNode")}} 和 {{domxref("AudioListener")}} 节点处理)允许我们对空间中某一点的音频信号,以及这一信号的接听者建立位置和行为模型。</p> + +<p>声相控制器的位置可以通过笛卡尔坐标系进行描述,控制器的运动可以由速度向量来表示,这会引起多普勒效应,它的传播方向可以用一个方向圆锥来表示,当它是一个全方向声源时,圆锥会变得非常大。</p> + +<p> </p> +</div> + +<p><img alt="The PannerNode brings a spatial position and velocity and a directionality for a given signal." src="https://mdn.mozillademos.org/files/12511/pannernode_en.svg" style="height: 340px; width: 799px;"></p> + +<div> +<p>接听者的位置可以用笛卡尔坐标系来表示;他的运动可以用方向向量表示;头部姿态可以用两个向量表示:一个向上向量表示头顶正对的方向,一个向前向量表示鼻子所指向的方向(面向的方向),这两个向量应该互相垂直。</p> +</div> + +<p><img alt="The PannerNode brings a spatial position and velocity and a directionality for a given signal." src="https://mdn.mozillademos.org/files/12513/listener.svg" style="height: 249px; width: 720px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 更多信息可以参考我们的这篇文章:<a href="/en-US/docs/Web/API/Web_Audio_API/Web_audio_spatialization_basics">网络音频位置空间化入门(英文)</a>。</p> +</div> + +<h2 id="扇入与扇出">扇入与扇出</h2> + +<p>对于音频来说,<strong>扇入</strong>是指 {{domxref("ChannelMergerNode")}} 节点接收一系列单声道输入声源,并将它们整合输出为一个多声道音频信号的过程:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/12517/fanin.svg" style="height: 258px; width: 325px;"></p> + +<p><strong>扇出</strong>恰恰相反,是指一个{{domxref("ChannelSplitterNode")}} 节点接收一个多声道输入声源并将它分离成多个单声道音频信号的过程:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/12515/fanout.svg" style="height: 258px; width: 325px;"></p> diff --git a/files/zh-cn/web/api/web_audio_api/index.html b/files/zh-cn/web/api/web_audio_api/index.html new file mode 100644 index 0000000000..f4f25afdfa --- /dev/null +++ b/files/zh-cn/web/api/web_audio_api/index.html @@ -0,0 +1,500 @@ +--- +title: Web Audio API +slug: Web/API/Web_Audio_API +tags: + - HTML5音频 + - Web Audio API +translation_of: Web/API/Web_Audio_API +--- +<div> +<p>Web Audio API 提供了在Web上控制音频的一个非常有效通用的系统,允许开发者来自选音频源,对音频添加特效,使音频可视化,添加空间效果 (如平移),等等。</p> +</div> + +<h2 id="Web_audio_概念与使用">Web audio 概念与使用</h2> + +<p>Web Audio API使用户可以在<strong>音频上下文</strong>(AudioContext)中进行音频操作,具有<strong>模块化路由</strong>的特点。在<strong>音频节点</strong>上操作进行基础的音频, 它们连接在一起构成<strong>音频路由图</strong>。即使在单个上下文中也支持多源,尽管这些音频源具有多种不同类型通道布局。这种模块化设计提供了灵活创建动态效果的复合音频的方法。</p> + +<p>音频节点通过它们的输入输出相互连接,形成一个链或者一个简单的网。一般来说,这个链或网起始于一个或多个音频源。音频源可以提供一个片段一个片段的音频采样数据(以数组的方式),一般,一秒钟的音频数据可以被切分成几万个这样的片段。这些片段可以是经过一些数学运算得到 (比如{{domxref("OscillatorNode")}}),也可以是音频或视频的文件读出来的(比如{{domxref("AudioBufferSourceNode")}}和{{domxref("MediaElementAudioSourceNode")}}),又或者是音频流({{domxref("MediaStreamAudioSourceNode")}})。其实,音频文件本身就是声音的采样数据,这些采样数据可以来自麦克风,也可以来自电子乐器,然后混合成一个单一的复杂的波形。</p> + +<p>这些节点的输出可以连接到其它节点的输入上,然后新节点可以对接收到的采样数据再进行其它的处理,再形成一个结果流。一个最常见的操作是通过把输入的采样数据放大来达到扩音器的作用(缩小就是一个弱音器)(参见{{domxref("GainNode")}})。声音处理完成之后,可以连接到一个目的地({{domxref("AudioContext.destination")}}),这个目的地负责把声音数据传输给扬声器或者耳机。注意,只有当用户期望听到声音时,才需要进行最后一个这个连接。</p> + +<p>一个简单而典型的web audio流程如下:</p> + +<ol> + <li>创建音频上下文</li> + <li>在音频上下文里创建源 — 例如 <code><audio></code>, 振荡器, 流</li> + <li>创建效果节点,例如混响、双二阶滤波器、平移、压缩</li> + <li>为音频选择一个目的地,例如你的系统扬声器</li> + <li>连接源到效果器,对目的地进行效果输出</li> +</ol> + +<p><img alt="A simple box diagram with an outer box labeled Audio context, and three inner boxes labeled Sources, Effects and Destination. The three inner boxes have arrow between them pointing from left to right, indicating the flow of audio information." src="https://mdn.mozillademos.org/files/12241/webaudioAPI_en.svg"></p> + +<p>使用这个API,时间可以被非常精确地控制,几乎没有延迟,这样开发人员可以准确地响应事件,并且可以针对采样数据进行编程,甚至是较高的采样率。这样,鼓点和节拍是准确无误的。</p> + +<p>Web Audio API 也使我们能够控制音频的<em>空间化</em>。在基于<em>源 - 侦听器模型</em>的系统中,它允许控制<em>平移模型</em>和处理<em>距离引起的衰减</em>或移动源(移动侦听)引起的<em>多普勒效应</em>。</p> + +<div class="note"> +<p><strong>注意</strong>: 你可以阅读我们关于 Web Audio API 的文章来了解更多细节 <a href="/en-US/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API">Web Audio API背后的基本概念</a>。</p> +</div> + +<h2 id="Web_Audio_API_接口">Web Audio API 接口</h2> + +<p>Web Audio API共有一系列接口和相关的事件,我们已经把它们分成了九类功能。</p> + +<h3 id="通用音频图定义">通用音频图定义</h3> + +<p>Web Audio API 中与生成音频图相关的定义与通用容器。</p> + +<dl> + <dt>{{domxref("AudioContext")}}</dt> + <dd><strong><code>AudioContext</code></strong>接口代表由音频模块构成的音频处理图。音频上下文控制其所包含节点的创建和音频处理、解码。使用其它接口前你必需创建一个<code>音频上下文</code>,一切操作都在这个环境里进行。</dd> + <dt>{{domxref("AudioNode")}}</dt> + <dd><strong><code>音频节点</code></strong><strong> </strong>接口是一个音频处理模块, 例如音频源({{HTMLElement("audio")}}或{{HTMLElement("video")}}),音频输出、中间处理模块(例如:滤波器 {{domxref("BiquadFilterNode")}} 或者音量控制器 {{domxref("GainNode")}})。</dd> + <dt>{{domxref("AudioParam")}}</dt> + <dd><strong><code>AudioParam</code></strong><strong> </strong>接口代表音频相关的参数,比如一个 {{domxref("AudioNode")}}的参数。它可以设置为特定值或值的变化,并且可以在指定的时间之后以指定模式变更。</dd> + <dt>{{event("ended")}}结束事件</dt> + <dd>当媒体播放停止时,会触发<code>ended</code>事件。</dd> +</dl> + +<h3 id="定义音频源">定义音频源</h3> + +<p>Web Audio API 使用的音频源接口。</p> + +<dl> + <dt>{{domxref("OscillatorNode")}}</dt> + <dd><strong><code style="font-size: 14px;">OscillatorNode</code></strong>接口代表一种随时间变化的波形,比如正弦波形或三角波形。类型是{{domxref("AudioNode")}},功能是音频处理模块,可以产生指定<em>频率</em>的波形。</dd> + <dt>{{domxref("AudioBuffer")}}</dt> + <dd><strong><code>AudioBuffer</code></strong>代表内存中的一段音频数据,可以通过{{ domxref("AudioContext.decodeAudioData()") }}方法从音频文件创建,也可以通过{{ domxref("AudioContext.createBuffer()") }}方法从原始数据创建。当音频数据被解码成这种格式之后,就可以被放入一个{{ domxref("AudioBufferSourceNode") }}中使用。</dd> + <dt>{{domxref("AudioBufferSourceNode")}}</dt> + <dd><strong><code>AudioBufferSourceNode</code></strong>表示由内存音频数据组成的音频源,音频数据存储在{{domxref("AudioBuffer")}}中。这是一个作为音频源的{{domxref("AudioNode")}}。</dd> + <dt>{{domxref("MediaElementAudioSourceNode")}}</dt> + <dd><code><strong>MediaElementAudio</strong></code><strong><code>SourceNode</code></strong>接口表示由HTML5 {{ htmlelement("audio") }}或{{ htmlelement("video") }}元素生成的音频源。这是一个作为音频源的{{domxref("AudioNode")}}。</dd> + <dt>{{domxref("MediaStreamAudioSourceNode")}}</dt> + <dd><code><strong>MediaStreamAudio</strong></code><strong><code>SourceNode</code></strong>接口表示由 WebRTC {{domxref("MediaStream")}}(如网络摄像头或麦克风)生成的音频源。这是一个作为音频源的{{domxref("AudioNode")}}。</dd> +</dl> + +<h3 id="定义音效">定义音效</h3> + +<p>应用到音频源上的音效。</p> + +<dl> + <dt>{{domxref("BiquadFilterNode")}}</dt> + <dd><strong><code>BiquadFilterNode</code></strong><strong> </strong>接口表示一个简单的低频滤波器。它是一个{{domxref("AudioNode")}},可以表示不同种类的滤波器、调音器或图形均衡器。<code>BiquadFilterNode</code> 总是只有一个输入和一个输出。</dd> + <dt>{{domxref("ConvolverNode")}}</dt> + <dd><code><strong>Convolver</strong></code><strong><code>Node</code></strong><strong> </strong>接口是一个<span style="line-height: 1.5;">{{domxref("AudioNode")}}</span>,<span style="line-height: 1.5;">对给定的 AudioBuffer 执行线性卷积,通常用于实现混响效果</span><span style="line-height: 1.5;">。</span></dd> + <dt>{{domxref("DelayNode")}}</dt> + <dd><strong><code>DelayNode</code></strong><strong> </strong>接口表示<a href="http://en.wikipedia.org/wiki/Digital_delay_line" title="http://en.wikipedia.org/wiki/Digital_delay_line">延迟线</a>;是{{domxref("AudioNode")}} 类型的音频处理模块,使输入的数据延时输出。</dd> + <dt>{{domxref("DynamicsCompressorNode")}}</dt> + <dd><strong><code>DynamicsCompressorNode</code></strong> 提供了一个压缩效果,当多个音频在同时播放并且混合的时候,可以通过它降低音量最大的部分的音量来帮助避免发生削波和失真。</dd> + <dt>{{domxref("GainNode")}}</dt> + <dd><strong><code>GainNode</code></strong><strong> </strong>接口用于音量变化。它是一个 {{domxref("AudioNode")}} 类型的音频处理模块,输入后应用<em>增益</em> 效果,然后输出。</dd> + <dt>{{domxref("StereoPannerNode")}}</dt> + <dd><code><strong>StereoPannerNode</strong></code> 接口表示一个简单立体声控制节点,用来左右移动音频流。</dd> + <dt>{{domxref("WaveShaperNode")}}</dt> + <dd><strong><code>WaveShaperNode</code></strong>接口表示一个非线性的扭曲。它是{{domxref("AudioNode")}}类型,可以利用曲线来对信号进行扭曲。除了一些效果明显的扭曲,还常被用来给声音添加温暖的感觉。</dd> + <dt>{{domxref("PeriodicWave")}}</dt> + <dd>用来定义周期性的波形,可被用来重塑 {{ domxref("OscillatorNode") }}的输出.</dd> +</dl> + +<h3 id="定义音频目的地">定义音频目的地</h3> + +<p>在你处理完音频之后,这些接口定义了输出到哪里。</p> + +<dl> + <dt>{{domxref("AudioDestinationNode")}}</dt> + <dd><strong><code>AudioDestinationNode</code></strong> 定义了最后音频要输出到哪里,通常是输出到你的扬声器。</dd> + <dt>{{domxref("MediaStreamAudioDestinationNode")}}</dt> + <dd><code><strong>MediaStreamAudio</strong></code><strong><code>DestinationNode</code></strong>定义了使用 <a href="https://developer.mozilla.org/en-US/docs/WebRTC" title="/en-US/docs/WebRTC">WebRTC</a> 的{{domxref("MediaStream")}} (只包含单个AudioMediaStreamTrack)应该连接的目的地,AudioMediaStreamTrack的使用方式和从{{ domxref("MediaDevices.getUserMedia", "getUserMedia()") }}中得到{{domxref("MediaStream")}}相似。这个接口是{{domxref("AudioNode")}}类型的音频目的地。</dd> +</dl> + +<h3 id="数据分析和可视化">数据分析和可视化</h3> + +<p>如果你想从音频里提取时间、频率或者其它数据,你需要 AnalyserNode。</p> + +<dl> + <dt>{{domxref("AnalyserNode")}}</dt> + <dd><strong><code>AnalyserNode</code></strong>表示一个可以提供实时频率分析与时域分析的切点,这些分析数据可以用做数据分析和可视化。</dd> +</dl> + +<h3 id="分离、合并声道">分离、合并声道</h3> + +<p>你将使用这些接口来拆分、合并声道。</p> + +<dl> + <dt>{{domxref("ChannelSplitterNode")}}</dt> + <dd><code><strong>ChannelSplitterNode</strong></code>可以把输入流的每个声道输出到一个独立的输出流。</dd> + <dt>{{domxref("ChannelMergerNode")}}</dt> + <dd><code><strong>ChannelMergerNode</strong></code>用于把一组输入流合成到一个输出流。输出流的每一个声道对应一个输入流。</dd> +</dl> + +<h3 id="声音空间效果">声音空间效果</h3> + +<p>这些接口用来添加空间平移效果到音频源。</p> + +<dl> + <dt>{{domxref("AudioListener")}}</dt> + <dd><strong><code>AudioListener</code></strong>代表场景中正在听声音的人的位置和朝向。</dd> + <dt>{{domxref("PannerNode")}}</dt> + <dd><strong><code>PannerNode</code></strong>用于表示场景是声音的空间行为。它是{{domxref("AudioNode")}}类型的音频处理模块,这个节点用于表示右手笛卡尔坐标系里声源的位置信息,运动信息(通过一个速度向量表示),方向信息(通过一个方向圆锥表示)。</dd> +</dl> + +<h3 id="使用_JavaScript_处理音频">使用 JavaScript 处理音频</h3> + +<p>可以编写JavaScript代码来处理音频数据。当然,这需要用到下面的接口和事件。</p> + +<div class="note"> +<p><strong>注意:</strong>这些功能在Web Audio API的2014年8月9日版本中已经标记为不推荐的,这些功能很快会被{{ anch("Audio_Workers") }}代替。</p> +</div> + +<dl> + <dt>{{domxref("ScriptProcessorNode")}}</dt> + <dd><strong><code>ScriptProcessorNode</code></strong>接口用于通过JavaScript代码生成,处理,分析音频。它是一个{{domxref("AudioNode")}}类型的音频处理模块,但是它与两个缓冲区相连接,一个缓冲区里包含当前的输入数据,另一个缓冲区里包含着输出数据。每当新的音频数据被放入输入缓冲区,就会产生一个{{domxref("AudioProcessingEvent")}}事件,当这个事件处理结束时,输出缓冲区里应该写好了新数据。</dd> + <dt>{{event("audioprocess")}} (event)</dt> + <dd>当一个Web Audio API {{domxref("ScriptProcessorNode")}}已经准备好进行处理时,这个事件回调会被调用。</dd> + <dt>{{domxref("AudioProcessingEvent")}}</dt> + <dd>当{{domxref("ScriptProcessorNode")}}的输入流数据准备好了时,<code>AudioProcessingEvent</code>事件会产生。</dd> +</dl> + +<h3 id="离线(后台)音频处理">离线(后台)音频处理</h3> + +<p>在后台进行音频的快速处理也是可以的。仅仅生成包含音频数据的{{domxref("AudioBuffer")}},而不在扬声器里播放它。</p> + +<dl> + <dt>{{domxref("OfflineAudioContext")}}</dt> + <dd><strong><code>OfflineAudioContext</code></strong>离线音频上下文也是音频上下文{{domxref("AudioContext")}},也表示把{{domxref("AudioNode")}}连接到一起的一个音频处理图。但是,与一个标准的音频上下文相比,离线上下文不能把音频渲染到扬声器,仅仅是把音频渲染到一个缓冲区。</dd> + <dt>{{event("complete")}} (event)</dt> + <dd>Complete事件,当离线音频上下文被终止时产生。</dd> + <dt>{{domxref("OfflineAudioCompletionEvent")}}</dt> + <dd><code>OfflineAudioCompletionEvent</code>表示上下文被终止时的事件。</dd> +</dl> + +<h3 id="Audio_Workers" name="Audio_Workers">音频工作者</h3> + +<p>在了解这一部分内容之前,你可以先了解一个新的WebWorker方面的内容。音频工作者提供了一种可以在一个<a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">WebWorker</a>上下文中直接进行音频处理的方式。现在已经定义了一些这部分功能的新接口,接口定义是在2014年的8月29日文档中。到目前为止,还没有浏览器已经对这些接口进行了实现。当这些接口被实现后,{{domxref("ScriptProcessorNode")}}和<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Audio_API$edit#Audio_processing_via_JavaScript">前文</a>中提到的其它接口都会被替代。</p> + +<dl> + <dt>{{domxref("AudioWorkerNode")}}</dt> + <dd>AudioWorkerNode也是{{domxref("AudioNode")}}类型,但是它用于与工作者线程合作来直接完成音频的生成,处理或分析等操作。</dd> + <dt>{{domxref("AudioWorkerGlobalScope")}}</dt> + <dd><code>AudioWorkerGlobalScope<font face="Open Sans, arial, sans-serif">继承于</font></code><code>DedicatedWorkerGlobalScope</code>。代表一个工作者上下文。这个工作者上下文里运行着对音频进行处理的脚本。设计这个接口的目的,是为了直接通过编写JavaScript代码,来完成对音频数据的生成,处理,分析工作。</dd> + <dt>{{domxref("AudioProcessEvent")}}</dt> + <dd>这是一个事件对象。这个对象会被分发给{{domxref("AudioWorkerGlobalScope")}}对象来进行处理。</dd> +</dl> + +<h2 id="Example" name="Example">过时的接口</h2> + +<p>下面介绍到的这些接口定义于比较老的Web Audio API标准,现在已经过时了,而且已经被新接口取代。</p> + +<dl> + <dt>{{domxref("JavaScriptNode")}}</dt> + <dd>用于通过编写JavaScript代码直接处理音频数据,现在已经被{{domxref("ScriptProcessorNode")}}取代。</dd> + <dt>{{domxref("WaveTableNode")}}</dt> + <dd>用于定义一个周期性波形,现在已经被{{domxref("PeriodicWave")}}取代。</dd> +</dl> + +<h2 id="Example" name="Example">示例</h2> + +<p>下面的这个示例使用了较多的Web Audio API接口,可以通过访问<a href="https://mdn.github.io/voice-change-o-matic/">网址</a>来查看它的时时运行情况,也可以访问GitHub上它的<a href="https://github.com/mdn/voice-change-o-matic">源代码</a>。这个示例里会对声音的音量进行改变,打开页面时,可以先把扬声器的音量调小一些。</p> + +<p>Web Audio API接口在下面的代码里已经高亮显示。</p> + +<pre class="brush: js; highlight:[1,2,9,10,11,12,36,37,38,39,40,41,62,63,72,114,115,121,123,124,125,147,151]">var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); // define audio context +// Webkit/blink browsers need prefix, Safari won't work without window. + +var voiceSelect = document.getElementById("voice"); // select box for selecting voice effect options +var visualSelect = document.getElementById("visual"); // select box for selecting audio visualization options +var mute = document.querySelector('.mute'); // mute button +var drawVisual; // requestAnimationFrame + +var analyser = audioCtx.createAnalyser(); +var distortion = audioCtx.createWaveShaper(); +var gainNode = audioCtx.createGain(); +var biquadFilter = audioCtx.createBiquadFilter(); + +function makeDistortionCurve(amount) { // function to make curve shape for distortion/wave shaper node to use + var k = typeof amount === 'number' ? amount : 50, + n_samples = 44100, + curve = new Float32Array(n_samples), + deg = Math.PI / 180, + i = 0, + x; + for ( ; i < n_samples; ++i ) { + x = i * 2 / n_samples - 1; + curve[i] = ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) ); + } + return curve; +}; + +navigator.getUserMedia ( + // constraints - only audio needed for this app + { + audio: true + }, + + // Success callback + function(stream) { + source = audioCtx.createMediaStreamSource(stream); + source.connect(analyser); + analyser.connect(distortion); + distortion.connect(biquadFilter); + biquadFilter.connect(gainNode); + gainNode.connect(audioCtx.destination); // connecting the different audio graph nodes together + + visualize(stream); + voiceChange(); + + }, + + // Error callback + function(err) { + console.log('The following gUM error occured: ' + err); + } +); + +function visualize(stream) { + WIDTH = canvas.width; + HEIGHT = canvas.height; + + var visualSetting = visualSelect.value; + console.log(visualSetting); + + if(visualSetting == "sinewave") { + analyser.fftSize = 2048; + var bufferLength = analyser.frequencyBinCount; // half the FFT value + var dataArray = new Uint8Array(bufferLength); // create an array to store the data + + canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); + + function draw() { + + drawVisual = requestAnimationFrame(draw); + + analyser.getByteTimeDomainData(dataArray); // get waveform data and put it into the array created above + + canvasCtx.fillStyle = 'rgb(200, 200, 200)'; // draw wave with canvas + canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); + + canvasCtx.lineWidth = 2; + canvasCtx.strokeStyle = 'rgb(0, 0, 0)'; + + canvasCtx.beginPath(); + + var sliceWidth = WIDTH * 1.0 / bufferLength; + var x = 0; + + for(var i = 0; i < bufferLength; i++) { + + var v = dataArray[i] / 128.0; + var y = v * HEIGHT/2; + + if(i === 0) { + canvasCtx.moveTo(x, y); + } else { + canvasCtx.lineTo(x, y); + } + + x += sliceWidth; + } + + canvasCtx.lineTo(canvas.width, canvas.height/2); + canvasCtx.stroke(); + }; + + draw(); + + } else if(visualSetting == "off") { + canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); + canvasCtx.fillStyle = "red"; + canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); + } + +} + +function voiceChange() { + distortion.curve = new Float32Array; + biquadFilter.gain.value = 0; // reset the effects each time the voiceChange function is run + + var voiceSetting = voiceSelect.value; + console.log(voiceSetting); + + if(voiceSetting == "distortion") { + distortion.curve = makeDistortionCurve(400); // apply distortion to sound using waveshaper node + } else if(voiceSetting == "biquad") { + biquadFilter.type = "lowshelf"; + biquadFilter.frequency.value = 1000; + biquadFilter.gain.value = 25; // apply lowshelf filter to sounds using biquad + } else if(voiceSetting == "off") { + console.log("Voice settings turned off"); // do nothing, as off option was chosen + } + +} + +// event listeners to change visualize and voice settings + +visualSelect.onchange = function() { + window.cancelAnimationFrame(drawVisual); + visualize(stream); +} + +voiceSelect.onchange = function() { + voiceChange(); +} + +mute.onclick = voiceMute; + +function voiceMute() { // toggle to mute and unmute sound + if(mute.id == "") { + gainNode.gain.value = 0; // gain set to 0 to mute sound + mute.id = "activated"; + mute.innerHTML = "Unmute"; + } else { + gainNode.gain.value = 1; // gain set to 1 to unmute sound + mute.id = ""; + mute.innerHTML = "Mute"; + } +} +</pre> + +<h2 id="规范">规范</h2> + +<table class="standard-table"> + <tbody> + <tr> + <th scope="col">Specification</th> + <th scope="col">Status</th> + <th scope="col">Comment</th> + </tr> + <tr> + <td>{{SpecName('Web Audio API')}}</td> + <td>{{Spec2('Web Audio API')}}</td> + <td></td> + </tr> + </tbody> +</table> + +<h2 id="浏览器兼容性">浏览器兼容性</h2> + +<div>{{CompatibilityTable}}</div> + +<div id="compat-desktop"> +<table class="compat-table"> + <tbody> + <tr> + <th>Feature</th> + <th>Chrome</th> + <th>Firefox (Gecko)</th> + <th>Internet Explorer</th> + <th>Opera</th> + <th>Safari (WebKit)</th> + </tr> + <tr> + <td>Basic support</td> + <td>14 {{property_prefix("webkit")}}</td> + <td>23</td> + <td>{{CompatNo}}</td> + <td>15 {{property_prefix("webkit")}}<br> + 22 (unprefixed)</td> + <td>6 {{property_prefix("webkit")}}</td> + </tr> + </tbody> +</table> +</div> + +<div id="compat-mobile"> +<table class="compat-table"> + <tbody> + <tr> + <th>Feature</th> + <th>Android</th> + <th>Chrome</th> + <th>Firefox Mobile (Gecko)</th> + <th>Firefox OS</th> + <th>IE Phone</th> + <th>Opera Mobile</th> + <th>Safari Mobile</th> + </tr> + <tr> + <td>Basic support</td> + <td>{{CompatNo}}</td> + <td>28 {{property_prefix("webkit")}}</td> + <td>25</td> + <td>1.2</td> + <td>{{CompatNo}}</td> + <td>{{CompatNo}}</td> + <td>6 {{property_prefix("webkit")}}</td> + </tr> + </tbody> +</table> +</div> + +<h2 id="相关链接">相关链接</h2> + +<ul> + <li><a href="/en-US/docs/Web/API/Web_Audio_API/Using_Web_Audio_API">Using the Web Audio API</a></li> + <li><a href="/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API">Visualizations with Web Audio API</a></li> + <li><a href="http://mdn.github.io/voice-change-o-matic/">Voice-change-O-matic example</a></li> + <li><a href="http://mdn.github.io/violent-theremin/">Violent Theremin example</a></li> + <li><a href="/en-US/docs/Web/API/Web_Audio_API/Web_audio_spatialisation_basics">Web audio spatialisation basics</a></li> + <li><a href="http://www.html5rocks.com/tutorials/webaudio/positional_audio/" title="http://www.html5rocks.com/tutorials/webaudio/positional_audio/">Mixing Positional Audio and WebGL</a></li> + <li><a href="http://www.html5rocks.com/tutorials/webaudio/games/" title="http://www.html5rocks.com/tutorials/webaudio/games/">Developing Game Audio with the Web Audio API</a></li> + <li><a href="/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext" title="/en-US/docs/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext">Porting webkitAudioContext code to standards based AudioContext</a></li> + <li><a href="https://github.com/bit101/tones">Tones</a>: a simple library for playing specific tones/notes using the Web Audio API.</li> + <li><a href="https://github.com/goldfire/howler.js/">howler.js</a>: a JS audio library that defaults to <a href="https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html">Web Audio API</a> and falls back to <a href="http://www.whatwg.org/specs/web-apps/current-work/#the-audio-element">HTML5 Audio</a>, as well as providing other useful features.</li> +</ul> + +<section id="Quick_Links"> +<h3 id="Quicklinks">Quicklinks</h3> + +<ol> + <li data-default-state="open"><strong><a href="#">Guides</a></strong> + + <ol> + <li><a href="/zh-CN/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API">Basic concepts behind Web Audio API</a></li> + <li><a href="/zh-CN/docs/Web/API/Web_Audio_API/Using_Web_Audio_API">Using the Web Audio API</a></li> + <li><a href="/zh-CN/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API">Visualizations with Web Audio API</a></li> + <li><a href="/zh-CN/docs/Web/API/Web_Audio_API/Web_audio_spatialisation_basics">Web audio spatialisation basics</a></li> + <li><a href="/zh-CN/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext">Porting webkitAudioContext code to standards based AudioContext</a></li> + </ol> + </li> + <li data-default-state="open"><strong><a href="#">Examples</a></strong> + <ol> + <li><a href="http://mdn.github.io/voice-change-o-matic/">Voice-change-O-matic</a></li> + <li><a href="http://mdn.github.io/violent-theremin/">Violent Theremin</a></li> + </ol> + </li> + <li data-default-state="open"><strong><a href="#">Interfaces</a></strong> + <ol> + <li>{{domxref("AnalyserNode")}}</li> + <li>{{domxref("AudioBuffer")}}</li> + <li>{{domxref("AudioBufferSourceNode")}}</li> + <li>{{domxref("AudioContext")}}</li> + <li>{{domxref("AudioDestinationNode")}}</li> + <li>{{domxref("AudioListener")}}</li> + <li>{{domxref("AudioNode")}}</li> + <li>{{domxref("AudioParam")}}</li> + <li>{{event("audioprocess")}} (event)</li> + <li>{{domxref("AudioProcessingEvent")}}</li> + <li>{{domxref("BiquadFilterNode")}}</li> + <li>{{domxref("ChannelMergerNode")}}</li> + <li>{{domxref("ChannelSplitterNode")}}</li> + <li>{{event("complete")}} (event)</li> + <li>{{domxref("ConvolverNode")}}</li> + <li>{{domxref("DelayNode")}}</li> + <li>{{domxref("DynamicsCompressorNode")}}</li> + <li>{{event("ended_(Web_Audio)", "ended")}} (event)</li> + <li>{{domxref("GainNode")}}</li> + <li>{{domxref("MediaElementAudioSourceNode")}}</li> + <li>{{domxref("MediaStreamAudioDestinationNode")}}</li> + <li>{{domxref("MediaStreamAudioSourceNode")}}</li> + <li>{{domxref("OfflineAudioCompletionEvent")}}</li> + <li>{{domxref("OfflineAudioContext")}}</li> + <li>{{domxref("OscillatorNode")}}</li> + <li>{{domxref("PannerNode")}}</li> + <li>{{domxref("PeriodicWaveNode")}}</li> + <li>{{domxref("ScriptProcessorNode")}}</li> + <li>{{domxref("WaveShaperNode")}}</li> + </ol> + </li> +</ol> +</section> diff --git a/files/zh-cn/web/api/web_audio_api/using_web_audio_api/index.html b/files/zh-cn/web/api/web_audio_api/using_web_audio_api/index.html new file mode 100644 index 0000000000..6feaaee6fb --- /dev/null +++ b/files/zh-cn/web/api/web_audio_api/using_web_audio_api/index.html @@ -0,0 +1,518 @@ +--- +title: Web Audio API的运用 +slug: Web/API/Web_Audio_API/Using_Web_Audio_API +tags: + - API + - Web Audio API + - 回放 + - 声音 + - 指南 + - 网络 +translation_of: Web/API/Web_Audio_API/Using_Web_Audio_API +--- +<div class="summary"> +<p>让我们来看看 <a href="/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a> 入门。我们将简要介绍一些概念,然后学习一个简单的允许我们加载音轨,播放暂停,改变音量和立体声声像的音箱例子。</p> +</div> + +<div> +<p>Web Audio API并不会取代<audio>音频元素,倒不如说它是<audio>的补充更好,就好比如<canvas>与<img>共存的关系。你使用来实现音频的方式取决于你的使用情况。如果你只是想控制一个简单的音轨的播放,<audio>或许是一个更好更快的选择。如果你想实现更多复杂的音频处理,以及播放,Web Audio API提供了更多的优势以及控制。</p> + +<p>Web Audio API的一个强大之处在于,它没有任何严格的声音呼叫控制。比如说,在同一时间它没有呼叫32或64的声音的限制。如果你的处理器性能好的话,同一时间播放1000多的声音不卡顿也是有可能的。这充分显示真正的进步,要知道几年前中高频的声卡仅能处理小部分的负载。</p> +</div> + +<h2 id="例子">例子</h2> + +<p>我们的音箱看起来像这样:</p> + +<p><img alt="A boombox with play, pan, and volume controls" src="https://mdn.mozillademos.org/files/16197/boombox.png"></p> + +<p>注意带有播放按钮的复古磁带卡座,及用于改变音量和立体声声像的平移滑块。我们可以使其更复杂,但这是该阶段进行简单学习的理想选择。</p> + +<p>查看最终demo代码<a href="https://codepen.io/Rumyra/pen/qyMzqN/"> here on Codepen</a>,或者在 <a href="https://github.com/mdn/webaudio-examples/tree/master/audio-basics">GitHub查看源代码on GitHub</a>。</p> + +<h2 id="浏览器支持">浏览器支持</h2> + +<p>现代浏览器的 Web Audio API 对的大多数功能都有很好的支持。API有很多的功能,因此要获得更准确的信息,你必须检查每个参考页面底部的浏览器兼容表。</p> + +<h2 id="音频图">音频图</h2> + +<p>Web Audio API 中的所有内容都是基于音频图的概念,音频图由节点组成。</p> + +<p>Web Audio API 在 <strong>audio context(音频上下文)</strong> 内处理音频,而且被设计为允许模块化路由。基本的音频操作是基于 <strong>audio nodes </strong>进行的,音频节点连接起来形成一个音频路由图。你拥有输入节点,你要操作的声音源,根据设计需要被修改的节点,和输出节点(目的地),它们允许你保存或者听取这些声音。</p> + +<p>支持拥有不同通道布局的多个的音频源,即使是在单个上下文。因为模块化设计,你可以创建具有动态效果的复杂的音频功能。</p> + +<h2 id="音频上下文">音频上下文</h2> + +<p>为了能通过Web Audio API执行任何操作,我们需要创建音频上下文实例。这能让我们访问API所有的特性和功能。</p> + +<pre class="brush: js"><code>// for legacy browsers +const AudioContext = window.AudioContext || window.webkitAudioContext; + +const audioContext = new AudioContext();</code> +</pre> + +<p>所以当我们这样做时会发生什么?为我们自动创建一个 {{domxref("BaseAudioContext")}} 并自动扩展到在线音频上下文。我们希望如此,因为我们想要播放在线声音。</p> + +<div class="note"> +<p><strong>注意</strong>:如果你只是想处理音频数据,举个例子,缓存和流式传输而不播放它,你可能想要考虑创建一个 {{domxref("OfflineAudioContext")}}。</p> +</div> + +<h2 id="加载声音">加载声音</h2> + +<p>现在,需要通过我们创建的音频上下文播放一些声音。Web Audio API中有几种方法可以实现这一点。让我们通过一个简单的方法开始 — 因为我们有一个音箱,我们可能想播放一首完整的歌曲。 此外,为了便于访问,我们可以在在DOM中暴露该音轨。我们将使用 {{htmlelement("audio")}} 元素在页面上暴露这首歌曲。</p> + +<pre class="brush: js"><code><audio src="myCoolTrack.mp3" type="audio/mpeg"></audio></code> +</pre> + +<div class="note"> +<p><strong>注意</strong>:如果你要加载的声音文件保留在其他域中,则需要使用 <code>crossorigin </code>属性;查看 <a href="/en-US/docs/Web/HTTP/CORS">Cross Origin Resource Sharing (CORS)</a> 取得更多信息。</p> +</div> + +<p>为了使用 Web Audio API的优秀特性,我们需要从该元素中获取源并将其传入我们创建的上下文中。幸运的是,有一个方法可以让我们做到这一点 — {{domxref("AudioContext.createMediaElementSource")}}:</p> + +<pre class="brush: js"><code>// get the audio element +const audioElement = document.querySelector('audio'); + +// pass it into the audio context +const track = audioContext.createMediaElementSource(audioElement);</code> +</pre> + +<div class="note"> +<p><strong>注意</strong>:上面的 <code><audio></code> 元素在DOM中代表了一个{{domxref("HTMLMediaElement")}} 类型的对象,拥有其自身的一组功能。这一切都将保持不变。我们只是让Web Audio API能够访问到声音。</p> +</div> + +<h2 id="控制声音">控制声音</h2> + +<p>当在网页上播放声音时,让用户能控制它是很重要的。根据使用场景,有无数的选项可用,但这我们将提供播放/暂停声音,改变音轨音量及从左到右平移声音的功能。</p> + +<p>通过JavaScript代码控制声音会受到浏览器的自动播放策略的影响(autoplay support policies),因此在未经用户(或白名单)许可的情况下脚本对声音的控制会被阻止。浏览器的自动播放策略通常要求显式权限或者用户与页面产生互动后,才允许脚本触发音频播放。</p> + +<p>这些特殊的要求基本上是因为意外的声音可能会打扰到用户,令人厌烦,并且可能导致可访问性问题。你可以在文章 <a href="/en-US/docs/Web/Media/Autoplay_guide">媒体与Web音频API自动播放指南</a> 了解更多相关信息。</p> + +<p>因为我们的脚本正响应用户输入(例如,点击播放按钮)进行播放音频,我们状态良好且应该没有自动播放阻止的问题。所以,让我们看看我们的播放和暂停功能。我们有一个当音频播放时变为暂停按钮的播放按钮:</p> + +<pre class="brush: html"><code><button data-playing="false" role="switch" aria-checked="false"> + <span>Play/Pause</span> +</button></code> +</pre> + +<p>在我们可以播放音频前我们需要将我们的音频图从音频源/输入节点连接到目的地。</p> + +<p>我们已经通过把音频元素传入API生成一个输入节点。在大多数情况下,你不需要生成一个输出节点,你只需要将其他节点连接到可以为你处理这种情况的 {{domxref("BaseAudioContext.destination")}}:</p> + +<pre class="brush: js"><code>track.connect(audioContext.destination);</code> +</pre> + +<p>可视化这些节点的一个好方法是绘制音频图形以便可视化它。这是我们当前的音频图:</p> + +<p><img alt="an audio graph with an audio element source connected to the default destination" src="https://mdn.mozillademos.org/files/16195/graph1.jpg"></p> + +<p>现在我们可以添加播放和暂停功能。</p> + +<pre class="brush: js"><code>// select our play button +const playButton = document.querySelector('button'); + +playButton.addEventListener('click', function() { + + // check if context is in suspended state (autoplay policy) + if (audioContext.state === 'suspended') { + audioContext.resume(); + } + + // play or pause track depending on state + if (this.dataset.playing === 'false') { + audioElement.play(); + this.dataset.playing = 'true'; + } else if (this.dataset.playing === 'true') { + audioElement.pause(); + this.dataset.playing = 'false'; + } + +}, false);</code> +</pre> + +<p>我们也需要考虑到当音频播放完毕后做什么。我们的 <code>HTMLMediaElement</code> 一旦播放完毕会触发一个 <code>ended</code> 事件,所以我们可以监听它并运行相应代码:</p> + +<pre class="brush: js"><code>audioElement.addEventListener('ended', () => { + playButton.dataset.playing = 'false'; +}, false);</code> +</pre> + +<h2 id="关于Web_Audio编辑器">关于Web Audio编辑器</h2> + +<p>Firefox有一个名为 <a href="/en-US/docs/Tools/Web_Audio_Editor">Web Audio editor</a> 的工具。在其上运行音频图的任何页面上,你可以打开开发者工具,使用 Web Audio选项卡查看音频图,可查看每个节点的可用属性,并可以修改这些属性来查看会有什么效果。</p> + +<p><img alt="The Firefox web audio editor showing an audio graph with AudioBufferSource, IIRFilter, and AudioDestination" src="https://mdn.mozillademos.org/files/16198/web-audio-editor.png"></p> + +<div class="note"> +<p><strong>注意</strong>:Web Audio编辑器默认不是开启的,你需要打开 Firefox developer tools 设置,选中Default Developer Tools部分中的Web Audio复选框来显示它。</p> +</div> + +<h2 id="修改声音">修改声音</h2> + +<p>让我们深入研究一些基本的修改节点以改变我们的声音。这就是Web Audio API真正开始派上用场的地方。首先,让我们改变音量。这可以通过 {{domxref("GainNode")}} 实现,它表示我们的声波有多大。</p> + +<p>使用 Web Audio API 可以通过2个方法创建节点。你可以使用上下文本身的工厂方法(例如, <code>audioContext.createGain()</code> )或者通过节点的构造函数(例如, <code>new GainNode()</code> ),我们将使用工厂方法:</p> + +<pre class="brush: js"><code>const gainNode = audioContext.createGain();</code> +</pre> + +<p>现在我们需要在原先音频图基础上更新音频图,所以输入连接到增益,然后增益节点连接到目标:</p> + +<pre class="brush: js"><code>track.connect(gainNode).connect(audioContext.destination);</code> +</pre> + +<p>这会让我们的音频图看起来如下:</p> + +<p><img alt="an audio graph with an audio element source, connected to a gain node that modifies the audio source, and then going to the default destination" src="https://mdn.mozillademos.org/files/16196/graph2.jpg"></p> + +<p>默认增益为1;这使当前音量保持不变。增益可以设置的最小值约-3.4,最大约3.4。这里我们将允许音箱增益可以设置到2(2倍的原音量)和降低到0(这可以有效的静音)。</p> + +<p>让我们给用户这样的控制 — 我们将会使用 <a href="/en-US/docs/Web/HTML/Element/input/range">range input</a> :</p> + +<pre class="brush: js"><code><input type="range" id="volume" min="0" max="2" value="1" step="0.01"></code> +</pre> + +<div class="note"> +<p><strong>注意</strong>:范围输入(Range Input)是更新音频节点值非常方便的输入类型。你可以指定特定的范围值同时直接将它们作为音频参数一起使用。</p> +</div> + +<p>所以当用户更改输入节点值时,获取此输入值并更新增益值:</p> + +<pre class="brush: js"><code>const volumeControl = document.querySelector('#volume'); + +volumeControl.addEventListener('input', function() { + gainNode.gain.value = this.value; +}, false);</code> +</pre> + +<div class="note"> +<p><strong>注意</strong>:节点对象的值(例如, <code>GainNode.gain</code> )不是简单值;它们实际上是 {{domxref("AudioParam")}} 类型对象 — 这些被称为参数。这也是为什么我们需要设置 <code>GainNode.gain</code> 的 <code>value</code> 属性,而不是直接设置 <code>gain</code> 的值。这使得它们更加的灵活,允许传入一系列特定的值以在例如一段时间内改变。</p> +</div> + +<p>好的,现在用户可以更新音频的音量!如果你要增加静音功能,增益节点是可使用的完美节点。</p> + +<h2 id="为应用程序增加立体声平移">为应用程序增加立体声平移</h2> + +<p>让我们添加另一个修改阶段来练习我们刚刚学过的。</p> + +<p>如果用户拥有立体声功能,可用 {{domxref("StereoPannerNode")}} 节点改变左右扬声器的平衡。</p> + +<div class="note"> +<p><strong>注意</strong>: <code>StereoPannerNode</code> 用于你只想从左到右进行立体声平移的简单情况。还有一个 {{domxref("PannerNode")}}, 它允许对3D空间或声音空间化进行大量控制以创建更复杂的效果。这在游戏和3D应用程序中生成小鸟飞过头顶或者来自用户身后的声音。</p> +</div> + +<p>为了使其可视化,我们将使我们的音频图如下:</p> + +<p><img alt="An image showing the audio graph showing an input node, two modification nodes (a gain node and a stereo panner node) and a destination node." src="https://mdn.mozillademos.org/files/16229/graphPan.jpg"></p> + +<p>这次让我们使用构造函数来生成节点。当我们这样做,我们需要传入上下文及该特定节点可能采用的任何选项:</p> + +<pre class="brush: js"><code>const pannerOptions = { pan: 0 }; +const panner = new StereoPannerNode(audioContext, pannerOptions);</code> +</pre> + +<div class="note"> +<p><strong>注意</strong>:目前生成节点的构造函数不是每个浏览器都支持的。旧工厂函数支持更为广泛。</p> +</div> + +<p>这里我们的范围从-1(最左边)和1(最右边)。再次让我们使用范围类型的input来改变这个参数:</p> + +<pre class="brush: js"><code><input type="range" id="panner" min="-1" max="1" value="0" step="0.01"></code> +</pre> + +<p>与我们之前一样,我们使用来自这个input的值来调整我们的panner的值:</p> + +<pre class="brush: js"><code>const pannerControl = document.querySelector('#panner'); + +pannerControl.addEventListener('input', function() { + panner.pan.value = this.value; +}, false);</code> +</pre> + +<p>让我们再次调整我们的音频图,将所有节点连接在一起:</p> + +<pre class="brush: js"><code>track.connect(gainNode).connect(panner).connect(audioContext.destination);</code> +</pre> + +<p>剩下要做的就是试试这个应用程序:<a href="https://codepen.io/Rumyra/pen/qyMzqN/">查看Codepen上的最终演示</a>。</p> + +<h2 id="摘要">摘要</h2> + +<p>好的,我们拥有一个音箱播放我们的“磁带”,我们可以调整音量和立体声声像,给我们提供了一个相当基本的工作音频图表。</p> + +<p>这构成了开始向你的网站或Web应用添加音频所需的很少的几个基础知识。Web Audio API还有很多功能,但一旦你掌握了节点的概念及将音频节点图联系在一起,我们可以继续研究更加复杂的功能。</p> + +<h2 id="更多例子">更多例子</h2> + +<p>还有其他示例可以了解有关Web Audio API的更多信息。</p> + +<p> <a href="https://github.com/mdn/voice-change-o-matic">Voice-change-O-matic</a> 是一个有趣的语音操纵器和音频可视化web应用程序,允许你选择不同的效果和可视化。该应用程序相当初级,但它演示了同时使用多个 Web Audio API特性(<a href="https://mdn.github.io/voice-change-o-matic/">运行 Voice-change-O-matic live</a>)。</p> + +<p><img alt="A UI with a sound wave being shown, and options for choosing voice effects and visualizations." src="https://mdn.mozillademos.org/files/7921/voice-change-o-matic.png" style="display: block; height: 500px; margin: 0px auto; width: 640px;"></p> + +<p>另一个专门用于演示 Web Audio API的例子是 <a href="http://mdn.github.io/violent-theremin/">Violent Theremin</a>, 一个允许你通过移动鼠标来改变它的音调音量的简单的应用程序。它还提供了一个迷幻的灯光秀(<a href="https://github.com/mdn/violent-theremin">查看Violent Theremin 源代码</a>)</p> + +<p><img alt="A page full of rainbow colours, with two buttons labeled Clear screen and mute. " src="https://mdn.mozillademos.org/files/7919/violent-theremin.png" style="display: block; height: 458px; margin: 0px auto; width: 640px;"></p> + +<p>另参阅我们的 <a href="https://github.com/mdn/webaudio-examples">webaudio-examples repo</a> 以获取更多示例。</p> + +<h4 id="注:以下为旧文档,因较完整,此处暂不删除,方便开发者查看。"><strong><em>注:以下为旧文档,因较完整,此处暂不删除,方便开发者查看。</em></strong></h4> + +<h2 id="基础概念">基础概念</h2> + +<div class="note"> +<p><strong>注释</strong>: 很多的代码碎片来自于这个例子 <a href="https://github.com/mdn/violent-theremin">Violent Theremin example</a>.</p> +</div> + +<p>Web Audio API包含在音频上下文的处理音频操作,以及已被设计允许模块化路由。基本音频操作可通过音频节点进行,这些节点连接在一起,组成一个音频的路由表。多个音源——带有不同类型的频道配置——甚至可以被一个上下文支持。这个模块设计提供了创造带有动态效果的复杂音频功能的灵活性。</p> + +<p>音频节点通过输入与输出进行连接,形成一个链,从一个或多个源出发,通过一个或更多的节点,最终到输出终端(你也可以不提供输出终端,换句话说,如果只是想使一些音频数据可视化)。一个简单经典的web Audio的工作流程如下:</p> + +<p>1. 构建音频上下文AudioContext对象;</p> + +<p>2. 在AudioContext对象内,构建音源,比如<audio>,oscillator,stream</p> + +<p>3. 构建效果节点effectNode,比如混响,双二阶滤波器,声相,压限器</p> + +<p>4. 选择最终的音频目的地,比如说你的系统扬声器</p> + +<p>5. 连接源到效果,效果到输出终端</p> + +<h3 id="构建AudioContext对象">构建AudioContext对象</h3> + +<p>首先,你需要构建一个AudioContext实例,来创建一个音频图。最简单的方法就像这样:</p> + +<pre class="brush: js">var audioCtx = new AudioContext(); +</pre> + +<div class="note"> +<p><strong>注释</strong>: 同样一个文档是可以存在多个audioContext对象的,但是比较浪费。</p> +</div> + +<p>然而,提供一个版本前缀对于webkit/Blink浏览器是很重要的,对于Firefox(桌面版/手机版/OS版)是不需要的。如下:</p> + +<pre class="brush: js">var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); +</pre> + +<div class="note"> +<p><strong>注释</strong>:当创建一个新的conText对象时,如果你不提示window对象,Safari会无效。</p> +</div> + +<h3 id="创建AudioSource">创建AudioSource</h3> + +<p>现在我们有了AudioContext,可以用这个来做很多事。第一件我们需要做的事是玩音乐。音频可以来自于多样的地方:</p> + +<ul> + <li>通过JavaScript直接生成一个音频节点比如oscillator. 一个 {{ domxref("OscillatorNode") }}是利用{{ domxref("AudioContext.createOscillator") }} 方法来构建。</li> + <li>从原PCM数据构建: AudioContext有解密被支持的音频格式的多种方法。 看 {{ domxref("AudioContext.createBuffer()") }}, {{ domxref("AudioContext.createBufferSource()") }}, 以及 {{ domxref("AudioContext.decodeAudioData()") }}.</li> + <li>来自于HTML音频元素例如 {{HTMLElement("video")}} 或者{{HTMLElement("audio")}}: 可以看 {{ domxref("AudioContext.createMediaElementSource()") }}.</li> + <li>直接来自于 <a href="https://developer.mozilla.org/en-US/docs/WebRTC" title="/en-US/docs/WebRTC">WebRTC</a>,{{domxref("MediaStream")}} 例如来自于摄像头或麦克风. 可以看{{ domxref("AudioContext.createMediaStreamSource()") }}.</li> +</ul> + +<p>对于这些特殊的例子,我们将会为我们的源构建一个 oscillator来提供简单的音调,以及gain node来控制音频音量:</p> + +<pre class="brush: js">var oscillator = audioCtx.createOscillator(); +var gainNode = audioCtx.createGain(); +</pre> + +<div class="note"> +<p><strong>注释</strong>: 为了直接播放一个音乐文件,你通常通过XHR来加载文件,通过Buffer来解码,创建BufferSource. 看这个 <a href="https://github.com/mdn/voice-change-o-matic/blob/gh-pages/scripts/app.js#L48-L68">例子来自于 Voice-change-O-matic</a>.</p> +</div> + +<div class="note"> +<p><span style="font-size: 14px; line-height: 21px;"><strong>注释:</strong></span> Scott Michaud 已经写了一个有用的库来加载和解码一个或多个音频实例, 被称为 <a href="https://github.com/ScottMichaud/AudioSampleLoader">AudioSampleLoader</a>. 这个可以帮助简化XHR/buffering的处理操作。</p> +</div> + +<h3 id="连接输入输出">连接输入输出</h3> + +<p>为了通过你的扬声器来实际输出音质,你需要将它们连接起来。这个被称为节点连接方法,节点来自于很多可获得的不同节点类型。你想要连接的节点都提供了这个方法。</p> + +<p>你的设备的默认输出结构(通常是你的设备扬声器),通过{{ domxref("AudioContext.destination") }}来允许进入。为了连接oscillator,gain node以及输出端,如以下运用:</p> + +<pre class="brush: js">oscillator.connect(gainNode); +gainNode.connect(audioCtx.destination); +</pre> + +<p>一个更复杂的例子,(比如 <a href="http://mdn.github.io/voice-change-o-matic/">Voice-change-O-matic</a>), 你可以链接很多你想要的节点在一起,例如:</p> + +<pre class="brush: js">source = audioCtx.createMediaStreamSource(stream); +source.connect(analyser); +analyser.connect(distortion); +distortion.connect(biquadFilter); +biquadFilter.connect(convolver); +convolver.connect(gainNode); +gainNode.connect(audioCtx.destination); +</pre> + +<p>这个将会创造一个如下音频节点图:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/7949/voice-change-o-matic-graph.png" style="display: block; height: 563px; margin: 0px auto; width: 232px;">你也可以链接多个节点到一个节点,比如说你想要混合多个音频源在一起,就让它们都通过一个效果节点,比如gain node。</p> + +<div class="note"> +<p><strong>注释</strong>:Firefox32以上版本已有完整的firefox开发者工具包括 <a href="/en-US/docs/Tools/Web_Audio_Editor">Web Audio Editor</a>, 一个对测试web audio 表的bug非常有用的东西.</p> +</div> + +<h3 id="播放音乐及设置音调">播放音乐及设置音调</h3> + +<p>现在audio节点图已经建立,我们可以设置属性值及调用音频节点的方法来调节想要的音效。在这个简单的例子,我们可以设置特殊的音调,以赫兹为单位,设置为特殊类型,以及指示音乐播放:</p> + +<pre class="brush: js">oscillator.type = 'sine'; // sine wave — other values are 'square', 'sawtooth', 'triangle' and 'custom' +oscillator.frequency.value = 2500; // value in hertz +oscillator.start(); +</pre> + +<p>在我们的 Violent Theremin例子,设定了一个最大gain以及frequency(频率)值:</p> + +<pre class="brush: js">var WIDTH = window.innerWidth; +var HEIGHT = window.innerHeight; + +var maxFreq = 6000; +var maxVol = 1; + +var initialFreq = 3000; +var initialVol = 0.5; + +// set options for the oscillator + +oscillator.type = 'sine'; // sine wave — other values are 'square', 'sawtooth', 'triangle' and 'custom' +oscillator.frequency.value = initialFreq; // value in hertz +oscillator.start(); + +gainNode.gain.value = initialVol; +</pre> + +<p>然后我们设置了一个frequency的新的值,以及设置每个时间鼠标的移动,基于目前的鼠标坐标值作为frequency和gain的最大值百分比。</p> + +<pre class="brush: js">// Mouse pointer coordinates + +var CurX; +var CurY; + +// Get new mouse pointer coordinates when mouse is moved +// then set new gain and pitch values + +document.onmousemove = updatePage; + +function updatePage(e) { + CurX = (window.Event) ? e.pageX : event.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft); + CurY = (window.Event) ? e.pageY : event.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop); + + oscillator.frequency.value = (CurX/WIDTH) * maxFreq; + gainNode.gain.value = (CurY/HEIGHT) * maxVol; + + canvasDraw(); +} +</pre> + +<h3 id="简单的canvas可视化">简单的canvas可视化</h3> + +<p>每次鼠标的移动,canvasDraw()方法会被调用,鼠标停留的位置会画出一个多圆圈组成的小簇,它的大小以及颜色会基于frequency/gain的值。</p> + +<pre class="brush: js">function random(number1,number2) { + var randomNo = number1 + (Math.floor(Math.random() * (number2 - number1)) + 1); + return randomNo; +} + +var canvas = document.querySelector('.canvas'); +canvas.width = WIDTH; +canvas.height = HEIGHT; + +var canvasCtx = canvas.getContext('2d'); + +function canvasDraw() { + rX = CurX; + rY = CurY; + rC = Math.floor((gainNode.gain.value/maxVol)*30); + + canvasCtx.globalAlpha = 0.2; + + for(i=1;i<=15;i=i+2) { + canvasCtx.beginPath(); + canvasCtx.fillStyle = 'rgb(' + 100+(i*10) + ',' + Math.floor((gainNode.gain.value/maxVol)*255) + ',' + Math.floor((oscillator.frequency.value/maxFreq)*255) + ')'; + canvasCtx.arc(rX+random(0,50),rY+random(0,50),rC/2+i,(Math.PI/180)*0,(Math.PI/180)*360,false); + canvasCtx.fill(); + canvasCtx.closePath(); + } +}</pre> + +<h3 id="theremin的静音">theremin的静音</h3> + +<p>当静音按钮点击,以下方法会被调用,disconnect方法,将切断gain node与destination节点的链接,有效阻止了节点图的链接,所以没有声音会被产生。再次点击效果相反。</p> + +<pre class="brush: js">var mute = document.querySelector('.mute'); + +mute.onclick = function() { + if(mute.id == "") { + gainNode.disconnect(audioCtx.destination); + mute.id = "activated"; + mute.innerHTML = "Unmute"; + } else { + gainNode.connect(audioCtx.destination); + mute.id = ""; + mute.innerHTML = "Mute"; + } +} +</pre> + +<h2 id="其他节点选择">其他节点选择</h2> + +<p>这里有许多通过Web Audio API来构建的节点,一个好消息就是,总体来说,正如我们所见,他们用同一种方法工作:构建节点,连接到图表的另一个节点,然后处理节点属性以及方法来作用于你想要的音源。</p> + +<p>我们并不希望通过所有可获得的效果等;你可以在{{ domxref("Web_Audio_API") }}不同的参考接口找到如何使用每一个的详述。我们现在来浏览下不同的设置。</p> + +<h3 id="Wave_shaper_节点">Wave shaper 节点</h3> + +<p>利用 {{ domxref("AudioContext.createWaveShaper") }} 方法,你可以构建一个 wave shaper node:</p> + +<pre class="brush: js">var distortion = audioCtx.createWaveShaper(); +</pre> + +<p>这个对象一定会数学化的定义wave shape,一个被应用于基础声音波来创造扭曲的效果。这些波并不好被计算,最好的开始方法是搜索web算法。比如,我们可以从 <a href="http://stackoverflow.com/questions/22312841/waveshaper-node-in-webaudio-how-to-emulate-distortion">Stack Overflow</a> 找到:</p> + +<pre class="brush: js">function makeDistortionCurve(amount) { + var k = typeof amount === 'number' ? amount : 50, + n_samples = 44100, + curve = new Float32Array(n_samples), + deg = Math.PI / 180, + i = 0, + x; + for ( ; i < n_samples; ++i ) { + x = i * 2 / n_samples - 1; + curve[i] = ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) ); + } + return curve; +}; +</pre> + +<p>在 Voice-change-O-matic 的演示中, 我们连接到audio图表上的ditortion节点,当需要的时候可以运用:</p> + +<pre class="brush: js">source.connect(analyser); +analyser.connect(distortion); +distortion.connect(biquadFilter); + +... + +distortion.curve = makeDistortionCurve(400); +</pre> + +<h3 id="Biquad_filter">Biquad filter</h3> + +<p>biquad filter 拥有很多可选择的方法, 通过 {{ domxref("AudioContext.createBiquadFilter") }} 方法来构建:</p> + +<pre class="brush: js">var biquadFilter = audioCtx.createBiquadFilter(); +</pre> + +<p>在Voice-change-o-matic的演示中,运用的制定选项是“lowshelf”过滤器,它提供了低音的基本增幅方法:</p> + +<pre class="brush: js">biquadFilter.type = "lowshelf"; +biquadFilter.frequency.value = 1000; +biquadFilter.gain.value = 25; +</pre> + +<p>我们在这里详述了过滤器的类型,频率值,增幅值。在lowshelf过滤器情况,所有的指定频率拥有25分贝的增幅值。</p> + +<h2 id="Web_Audio_API的其他"> Web Audio API的其他</h2> + +<p>Web Audio API 可以做不仅仅音频可视化及专业化(如panning)的事情。我们将会在之后的文章涉及其他的更多内容。</p> diff --git a/files/zh-cn/web/api/web_audio_api/visualizations_with_web_audio_api/index.html b/files/zh-cn/web/api/web_audio_api/visualizations_with_web_audio_api/index.html new file mode 100644 index 0000000000..4fa89e70f0 --- /dev/null +++ b/files/zh-cn/web/api/web_audio_api/visualizations_with_web_audio_api/index.html @@ -0,0 +1,189 @@ +--- +title: 基于Web Audio API实现音频可视化效果 +slug: Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API +tags: + - 分析器 + - 可视化 + - 教程 + - 波形 +translation_of: Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API +--- +<div class="summary"> +<p><strong style="background-color: #f4f7f8; font-weight: bold;">网页音频接口最有趣的特性之一它就是可以获取频率、波形和其它来自声源的数据,这些数据可以被用作音频可视化。这篇文章将解释如何做到可视化,并提供了一些基础使用案例。</strong></p> +</div> + +<div class="note"> +<p><strong>提示</strong>:你可以在<a href="http://mdn.github.io/voice-change-o-matic/" style="font-style: italic; background-color: rgba(231, 228, 157, 0.247059);">Voice-change-O-matic</a>演示里找到本文出现的所有代码片段。</p> +</div> + +<h2 id="基本概念">基本概念</h2> + +<p>要从你的音频源获取数据,你需要一个 {{ domxref("AnalyserNode") }}节点,它可以用 {{ domxref("AudioContext.createAnalyser()") }} 方法创建,比如:</p> + +<pre class="brush: js">var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); +var analyser = audioCtx.createAnalyser();</pre> + +<p>然后把这个节点(node)连接到你的声源:</p> + +<pre class="brush: js">source = audioCtx.createMediaStreamSource(stream); +source.connect(analyser); +analyser.connect(distortion); +// etc.</pre> + +<div class="note"> +<p><strong>注意:</strong> 分析器节点(Analyser Node) 不一定输出到另一个节点,不输出时也可以正常使用。但前提是它必须与一个声源相连(直接或者通过其他节点间接相连都可以)。</p> +</div> + +<p>分析器节点(Analyser Node) 将在一个特定的频率域里使用<a href="https://zh.wikipedia.org/wiki/%E5%BF%AB%E9%80%9F%E5%82%85%E9%87%8C%E5%8F%B6%E5%8F%98%E6%8D%A2">快速傅立叶变换</a>(Fast Fourier Transform (FFT) )来捕获音频数据,这取决于你给 {{ domxref("AnalyserNode.fftSize") }} 属性赋的值(如果没有赋值,默认值为2048)。</p> + +<div class="note"> +<p><strong>注意:</strong> 你也可以为FFT数据缩放范围指定一个最小值和最大值,使用{{ domxref("AnalyserNode.minDecibels") }} 和{{ domxref("AnalyserNode.maxDecibels") }}进行设置,要获得不同数据的平均常量,使用 {{ domxref("AnalyserNode.smoothingTimeConstant") }}。阅读这些页面以获得更多如何使用它们的信息。</p> +</div> + +<p>要捕获数据,你需要使用 {{ domxref("AnalyserNode.getFloatFrequencyData()") }} 或 {{ domxref("AnalyserNode.getByteFrequencyData()") }} 方法来获取频率数据,用 {{ domxref("AnalyserNode.getByteTimeDomainData()") }} 或 {{ domxref("AnalyserNode.getFloatTimeDomainData()") }} 来获取波形数据。</p> + +<p>这些方法把数据复制进了一个特定的数组当中,所以你在调用它们之前要先创建一个新数组。第一个方法会产生一个32位浮点数组,第二个和第三个方法会产生8位无符号整型数组,因此一个标准的JavaScript数组就不能使用 —— 你需要用一个 {{ domxref("Float32Array") }} 或者 {{ domxref("Uint8Array") }} 数组,具体需要哪个视情况而定。</p> + +<p>那么让我们来看看例子,比如我们正在处理一个2048尺寸的FFT。我们返回 {{ domxref("AnalyserNode.frequencyBinCount") }} 值,它是FFT的一半,然后调用Uint8Array(),把frequencyBinCount作为它的长度参数 —— 这代表我们将对这个尺寸的FFT收集多少数据点。</p> + +<pre class="brush: js">analyser.fftSize = 2048; +var bufferLength = analyser.frequencyBinCount; +var dataArray = new Uint8Array(bufferLength);</pre> + +<p>要正确检索数据并把它复制到我们的数组里,就要调用我们想要的数据收集方法,把数组作为参数传递给它,例如:</p> + +<pre class="brush: js">analyser.getByteTimeDomainData(dataArray);</pre> + +<p>现在我们就获取了那时的音频数据,并存到了我们的数组里,而且可以把它做成我们喜欢的可视化效果了,比如把它画在一个HTML5 {{ htmlelement("canvas") }} 画布上。</p> + +<p>下面让我们来看一些具体的例子。</p> + +<h2 id="创建一个波形示波器">创建一个波形/示波器</h2> + +<p>要创建一个示波器视觉效果(感谢 <a href="http://soledadpenades.com/">Soledad Penadés</a> 在 <a href="https://github.com/mdn/voice-change-o-matic/blob/gh-pages/scripts/app.js#L123-L167">Voice-change-O-matic</a> 中提供的源码),我们首先用下面代码框中的代码为标准设置一个buffer:</p> + +<pre class="brush: js">analyser.fftSize = 2048; +var bufferLength = analyser.fftSize; +var dataArray = new Uint8Array(bufferLength);</pre> + +<p>接下来,我们清空画布为绘制新的可视化效果做准备:</p> + +<pre class="brush: js">canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);</pre> + +<p>现在我们来定义 <code>draw()</code> 函数:</p> + +<pre class="brush: js">function draw() {</pre> + +<p>这里我们用 <code>requestAnimationFrame()</code> 来保持绘图持续更新:</p> + +<pre class="brush: js"> drawVisual = requestAnimationFrame(draw);</pre> + +<p>接下来我们获取时间域上的数据并将它复制到数组当中:</p> + +<pre class="brush: js"> analyser.getByteTimeDomainData(dataArray);</pre> + +<p>接下来把canvas用纯色填满作为背景:</p> + +<pre class="brush: js"> canvasCtx.fillStyle = 'rgb(200, 200, 200)'; + canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);</pre> + +<p>为我们要画的波形设置好线宽和线的颜色,然后开始绘制路径:</p> + +<pre class="brush: js"> canvasCtx.lineWidth = 2; + canvasCtx.strokeStyle = 'rgb(0, 0, 0)'; + + canvasCtx.beginPath();</pre> + +<p>用canvas画布的总宽度除以数组的长度(与之前定义的 FrequencyBinCount 相等)来决定要花上的每段线条的宽度,之后设置横坐标 (x) 为0,将画笔移动到起始位置:</p> + +<pre class="brush: js"> var sliceWidth = WIDTH * 1.0 / bufferLength; + var x = 0;</pre> + +<p>接下来我们进入循环,遍历数组,通过其中的数据来确定每段线条的高度,之后改变横坐标将画笔移动到下一段线条开始的地方:</p> + +<pre class="brush: js"> for(var i = 0; i < bufferLength; i++) { + + var v = dataArray[i] / 128.0; + var y = v * HEIGHT/2; + + if(i === 0) { + canvasCtx.moveTo(x, y); + } else { + canvasCtx.lineTo(x, y); + } + + x += sliceWidth; + }</pre> + +<p>最后,我们把线连到右边的中央,然后画出来:</p> + +<pre class="brush: js"> canvasCtx.lineTo(canvas.width, canvas.height/2); + canvasCtx.stroke(); + };</pre> + +<p>在这块代码最后,我们调用 <code>draw()</code> 函数来开始整个过程:</p> + +<pre class="brush: js"> draw();</pre> + +<p>这个演示画出了一个每秒会刷新几次并且看起来还不错的波形图:</p> + +<p><img alt="a black oscilloscope line, showing the waveform of an audio signal" src="https://mdn.mozillademos.org/files/7977/wave.png" style="display: block; height: 96px; margin: 0px auto; width: 640px;"></p> + +<h2 id="创建一个频率条形图">创建一个频率条形图</h2> + +<p>另一种小巧的可视化方法是创建频率条形图,在 <a href="https://github.com/mdn/voice-change-o-matic/blob/gh-pages/scripts/app.js#L123-L167">Voice-change-O-matic</a> 中已经有一个做好的,现在让我们来看看它是如何实现的。</p> + +<p>首先,我们设置好解析器和空数组,之后用 <a href="/zh-CN/docs/Web/API/CanvasRenderingContext2D/clearRect">clearRect()</a> 清空画布。与之前的唯一区别是我们这次大大减小了FFT的大小,这样做的原因是为了使得每个频率条足够宽,让它们看着像“条”而不是“细杆”。</p> + +<pre class="brush: js"> analyser.fftSize = 256; + var bufferLength = analyser.frequencyBinCount; + console.log(bufferLength); + var dataArray = new Uint8Array(bufferLength); + + canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);</pre> + +<p>接下来我们写好 <code>draw()</code> 函数,再一次用 <code>requestAnimationFrame()</code> 设置一个循环,这样显示的数据就可以保持刷新,并且每一帧都清空一次画布。</p> + +<pre class="brush: js"> function draw() { + drawVisual = requestAnimationFrame(draw); + + analyser.getByteFrequencyData(dataArray); + + canvasCtx.fillStyle = 'rgb(0, 0, 0)'; + canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);</pre> + +<p>现在我们来设置一个 <code>barWidth</code> 变量,它等于每一个条形的宽度。理论上用花布宽度除以条的个数就可以得到它,但是在这里我们还要乘以2.5。这是因为有很多返回的频率区域中是没有声音的,我们每天听到的大多数声音也只是在一个很小的频率区域当中。在条形图中我们肯定不想看到大片的空白条,所以我们就把一些能正常显示的条形拉宽来填充这些空白区域。</p> + +<p>我们还要设置一个条高度变量 <code>barHeight</code>,还有一个 <code>x</code> 变量来记录当前条形的位置。</p> + +<pre class="brush: js"> var barWidth = (WIDTH / bufferLength) * 2.5; + var barHeight; + var x = 0;</pre> + +<p>像之前一样,我们进入循环来遍历 <code>dataArray</code> 数组中的数据。在每一次循环过程中,我们让条形的高度 <code>barHeight</code> 等于数组的数值,之后根据高度设置条形的填充色(条形越高,填充色越亮),然后在横坐标 <code>x</code> 处按照设置的宽度和高度的一半把条形画出来(我们最后决定只画高度的一半因为这样条形看起来更美观)。</p> + +<p>需要多加解释的一点是每个条形竖直方向的位置,我们在 <code>HEIGHT-barHeight/2</code> 的位置画每一条,这是因为我想让每个条形从底部向上伸出,而不是从顶部向下(如果我们把竖直位置设置为0它就会这样画)。所以,我们把竖直位置设置为画布高度减去条形高度的一半,这样每个条形就会从中间向下画,直到画布最底部。</p> + +<pre class="brush: js"> for(var i = 0; i < bufferLength; i++) { + barHeight = dataArray[i]/2; + + canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ',50,50)'; + canvasCtx.fillRect(x,HEIGHT-barHeight/2,barWidth,barHeight); + + x += barWidth + 1; + } + };</pre> + +<p>和刚才一样,我们在最后调用 draw() 函数来开启整个可视化过程。</p> + +<pre class="brush: js"> draw();</pre> + +<p>这些代码会带来下面的效果:</p> + +<p><img alt="a series of red bars in a bar graph, showing intensity of different frequencies in an audio signal" src="https://mdn.mozillademos.org/files/7975/bar-graph.png" style="height: 157px; width: 1260px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 本文中的案例展现了 {{ domxref("AnalyserNode.getByteFrequencyData()") }} 和 {{ domxref("AnalyserNode.getByteTimeDomainData()") }} 的用法。如果想要查看 {{ domxref("AnalyserNode.getFloatFrequencyData()") }} 和 {{ domxref("AnalyserNode.getFloatTimeDomainData()") }} 的用法,请参考我们的 <a href="http://mdn.github.io/voice-change-o-matic-float-data/">Voice-change-O-matic-float-data</a> 演示(也能看到 <a href="https://github.com/mdn/voice-change-o-matic-float-data">源代码</a> )——它和本文中出现的 <a href="http://mdn.github.io/voice-change-o-matic/">Voice-change-O-matic</a> 功能完全相同,唯一区别就是它使用的是浮点数作数据,而不是本文中的无符号整型数。</p> +</div> + +<p> </p> diff --git a/files/zh-cn/web/api/web_audio_api/web_audio_spatialization_basics/index.html b/files/zh-cn/web/api/web_audio_api/web_audio_spatialization_basics/index.html new file mode 100644 index 0000000000..e93bfe479d --- /dev/null +++ b/files/zh-cn/web/api/web_audio_api/web_audio_spatialization_basics/index.html @@ -0,0 +1,413 @@ +--- +title: Web audio 空间化基础 +slug: Web/API/Web_Audio_API/Web_audio_spatialization_basics +translation_of: Web/API/Web_Audio_API/Web_audio_spatialization_basics +--- +<div class="summary"> +<p><span class="seoSummary">正如大量的各种声音处理(或者其他)选择是不够的,WebAduioAPI也包含了一些工具,可以让你模仿听众在声源周围移动时的声音差异,例如当你在3D游戏声源中移动时声音的平移。官方名词称为 <strong>空间化</strong>,这篇文章将会介绍如何实现这样一个系统的基础知识。</span></p> +</div> + +<h2 id="空间化的基础知识">空间化的基础知识</h2> + +<p>在 Web Audio 中,复杂的 3D 空间化是使用 {{domxref("PannerNode")}} 创建的,用外行人的话来说就是一个使音频出现在3D空间中的很酷的数学。想象一下声音从你头上飞过,爬到你身后,在你面前移动。诸如此类的事情。</p> + +<p>它对 WebXR 和游戏非常有用。在 3D 空间中,它是实现逼真的音频效果的唯一方式。像 <a href="https://threejs.org/">three.js</a> 和 <a href="https://aframe.io/">A-frame</a> 这样的库在处理声音时就利用了它的潜力。值得注意的是,你不必在完整的 3D 空间中移动声音 - 你可以只使用2D平面,因此如果你计划实现一个 2D 游戏,这依然是你要寻找的节点。</p> + +<div class="note"> +<p><strong>注意:</strong>还有一个设计用于处理创建简单的左右立体声平移效果的 {{domxref("StereoPannerNode")}} 。这使用起来更简单,但显然无处可用。如果你只想要一个简单的立体声平移效果,我们的 <a href="https://mdn.github.io/webaudio-examples/stereo-panner-node/">StereoPannerNode 示例</a>(<a href="https://github.com/mdn/webaudio-examples/tree/master/stereo-panner-node">请参阅源码</a>)应该可以为你提供所需的一切。</p> +</div> + +<h2 id="3D_boombox_演示">3D boombox 演示</h2> + +<p>为了演示 3D 空间化,我们在 <a href="/en-US/docs/Web/API/Web_Audio_API/Using_Web_Audio_API">Using the Web Audio API</a> 指南中的 boombox 演示的基础上创建一个修改版本。参见 <a href="https://mdn.github.io/webaudio-examples/spacialization/">3D spatialization demo live</a> (同时也可以看 <a href="https://github.com/mdn/webaudio-examples/tree/master/spacialization">source code</a>)</p> + +<p><img alt="A simple UI with a rotated boombox and controls to move it left and right and in and out, and rotate it." src="https://mdn.mozillademos.org/files/16232/web-audio-spatialization.png"></p> + +<p>音箱放置于房间中(由浏览器视区边缘定义),在本 demo 中我们可以通过提供的控件移动和旋转它。当我们移动音箱时,它产生的声音会相应的改变,当它在移动到房间的左侧或右侧时声音平移,或当它远离用户时变得安静,或旋转使得扬声器背离它们等。这是通过给 <code>PannerNode</code> 对象实例设置不同的与该运动有关的属性来实现模拟空间化。</p> + +<div class="note"> +<p><strong>注意:</strong>如果你使用耳机或者其他某种环绕声系统连接计算机,体验会更好。</p> +</div> + +<h2 id="创建audio收听者">创建audio收听者</h2> + +<p>让我们开始! {{domxref("BaseAudioContext")}}( {{domxref("AudioContext")}} 扩展自该接口)有一个listener属性,返回一个 {{domxref("AudioListener")}} 对象。这代表着场景收听者,通常是使用者(用户)。你可以定义他(她)们在空间中的位置和他(她)们面向的方向。他(她)们保持静止。 <code>pannerNode</code> 可以计算出声音相对于收听者位置的位置。</p> + +<p>让我们创建我们的上下文和监听器,并设置收听者的位置来模拟一个看向(探索)我们房间的人:</p> + +<pre class="brush: js">const AudioContext = window.AudioContext || window.webkitAudioContext; +const audioCtx = new AudioContext(); +const listener = audioCtx.listener; + +const posX = window.innerWidth/2; +const posY = window.innerHeight/2; +const posZ = 300; + +listener.positionX.value = posX; +listener.positionY.value = posY; +listener.positionZ.value = posZ-5;</pre> + +<p>我们可以使用 <code>positionX</code> 将收听者向左/右移动,使用 <code>positionY</code> 向上/下移动,或使用 <code>positionZ</code> 移出/入房间。在这里,我们将收听者设置在视口中间同时稍微位于音箱的前方。我们还可以设置收听者面对的方向。这些默认值工作良好:</p> + +<pre class="brush: js"><code>listener.forwardX.value = 0; +listener.forwardY.value = 0; +listener.forwardZ.value = -1; +listener.upX.value = 0; +listener.upY.value = 1; +listener.upZ.value = 0;</code></pre> + +<p>这些forward属性代表了收听者前进方向的3D坐标位置(例如他/她们面向的方向),而 up 属性表示了收听者头顶的3D坐标位置。这两种属性值可以很好的设定方位。</p> + +<h2 id="创建panner节点">创建panner节点</h2> + +<p>让我们创建 {{domxref("PannerNode")}}节点,这有很多与之相关的属性。让我们来一一看看:</p> + +<p>首先我们可以设置 <a href="https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/panningModel"><code>panningModel</code></a>。这是用于在3D空间中定位音频的空间化算法。我们可以将其设置为:</p> + +<p><code>equalpower</code> — 计算出默认和一般方式的平移。</p> + +<p><code>HRTF</code> — 这代表 'Head-related transfer function' ,在弄清楚声音的位置时,会考虑人脑(对声音的处理)。</p> + +<p>非常聪明的东西,让我们使用 <code>HRTF</code> 模型!</p> + +<pre class="brush: js"><code>const pannerModel = 'HRTF';</code></pre> + +<p>属性 <a href="https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/coneInnerAngle"><code>coneInnerAngle</code></a> 和 <a href="https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/coneOuterAngle"><code>coneOuterAngle</code></a> 指定音量发出的位置。默认情况下,两者都是360度。我们可以定义音箱扬声器拥有较小的锥体。内锥是总是模拟增益(音量)最大值的地方,外锥是增益开始下降的地方。</p> + +<p>增益通过 <a href="https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/coneOuterGain"><code>coneOuterGain</code></a> 值来减少。让我们创建之后将会用于这些参数的常量:</p> + +<pre class="brush: js"><code>const innerCone = 60; +const outerCone = 90; +const outerGain = 0.3;</code> +</pre> + +<p>下一个参数是 <a href="https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/distanceModel"><code>distanceModel</code></a> — 这只能设置为 <code>linear</code>, <code>inverse</code>, 或者 <code>exponential</code>。这些是不同的算法,用于在音频源远离收听者时减小音频源的音量。<br> + 我们将使用<code>linear</code>,因为它很简单:</p> + +<pre class="brush: js"><code>const distanceModel = 'linear';</code> +</pre> + +<p>我们可以设置源和收听者之间的最大距离 (<a href="https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/maxDistance"><code>maxDistance</code></a>) — 如果源距离此点更远,则音量将不再减小。这可能很有用,因为你可能会发现你想要模拟距离,但是音量会下降,而实际上并不是你想要的。默认情况下,它是10,000(无单位的相对值)。我们可以像这样保持它:</p> + +<pre class="brush: js"><code>const maxDistance = 10000;</code> +</pre> + +<p>还有一个参考距离 (<code><a href="/en-US/docs/Web/API/PannerNode/refDistance">refDistance</a></code>),由距离模型使用。我们也可以将其保持为默认值 <code>1</code>:</p> + +<pre class="brush: js"><code>const refDistance = 1;</code></pre> + +<p>然后就是 roll-off 因子 (<a href="https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/rolloffFactor"><code>rolloffFactor</code></a>) — 描述随着panner远离收听者,音量减小的速度有多快。默认值为1;让我们使其大一些以放大我们的动作。</p> + +<pre><code>const rollOff = 10;</code></pre> + +<p>现在我们可以开始设置我们 boombox 的位置和方向。这与我们如何设置收听者很像。<br> + 这些也是我们在使用界面上的控件时要改变的参数。</p> + +<pre class="brush: js"><code>const positionX = posX; +const positionY = posY; +const positionZ = posZ; + +const orientationX = 0.0; +const orientationY = 0.0; +const orientationZ = -1.0;</code> +</pre> + +<p>注意z方向的负值 - 这会将 boombox 设置为面向我们。正值会使声源背离我们。 <br> + 让我们使用相关的构造函数来创建我们的panner节点,并传入我们在上面设置的所有参数:</p> + +<pre class="brush: js"><code>const panner = new PannerNode(audioCtx, { + panningModel: pannerModel, + distanceModel: distanceModel, + positionX: positionX, + positionY: positionY, + positionZ: positionZ, + orientationX: orientationX, + orientationY: orientationY, + orientationZ: orientationZ, + refDistance: refDistance, + maxDistance: maxDistance, + rolloffFactor: rollOff, + coneInnerAngle: innerCone, + coneOuterAngle: outerCone, + coneOuterGain: outerGain +})</code> +</pre> + +<ul> +</ul> + +<h2 id="移动boombox">移动boombox</h2> + +<p>现在我们将在我们的“房间”中四处移动 boombox。我们已经设置了一些控件来执行此操作。我们可以左右移动,上下移动,来回移动;我们也可以旋转它。声音方向来自前面的 boombox 的扬声器,因此当我们旋转它时,我们可以改变声音的方向 - 即当音箱旋转180度并背向我们时,使其向后投射。 <br> + 我们需要为界面设置一些东西。首先,我们将获得我们想要移动的元素的引用,然后存储我们在设置 <a href="/en-US/docs/Web/CSS/CSS_Transforms">CSS transforms</a> 来实际执行移动时将要更改的值的引用。<br> + 最后,我们将设置一些边界值,以便我们的 boombox 在任何方向上都不会走得太远:</p> + +<pre class="brush: js"><code>const moveControls = document.querySelector('#move-controls').querySelectorAll('button'); +const boombox = document.querySelector('.boombox-body'); + +// the values for our css transforms +let transform = { + xAxis: 0, + yAxis: 0, + zAxis: 0.8, + rotateX: 0, + rotateY: 0 +} + +// set our bounds +const topBound = -posY; +const bottomBound = posY; +const rightBound = posX; +const leftBound = -posX; +const innerBound = 0.1; +const outerBound = 1.5;</code></pre> + +<p>让我们创建一个函数,将我们想要移动的方向作为参数,并且修改CSS变换及更新我们的panner节点的位置和方向属性值以适当地更改声音。 <br> + 首先让我们看看左,右,上,下值,因为这些非常简单。我们将沿着这些轴移动boombox,并更新合适的位置。</p> + +<pre class="brush: js"><code>function moveBoombox(direction) { + switch (direction) { + case 'left': + if (transform.xAxis > leftBound) { + transform.xAxis -= 5; + panner.positionX.value -= 0.1; + } + break; + case 'up': + if (transform.yAxis > topBound) { + transform.yAxis -= 5; + panner.positionY.value -= 0.3; + } + break; + case 'right': + if (transform.xAxis < rightBound) { + transform.xAxis += 5; + panner.positionX.value += 0.1; + } + break; + case 'down': + if (transform.yAxis < bottomBound) { + transform.yAxis += 5; + panner.positionY.value += 0.3; + } + break; + } +}</code></pre> + +<p>移入和移出也是类似的故事:</p> + +<pre class="brush: js"><code>case 'back': + if (transform.zAxis > innerBound) { + transform.zAxis -= 0.01; + panner.positionZ.value += 40; + } +break; +case 'forward': + if (transform.zAxis < outerBound) { + transform.zAxis += 0.01; + panner.positionZ.value -= 40; + } +break;</code></pre> + +<p>然而,我们的旋转值稍为复杂,因为我们需要在周围移动声音。我们不仅需要更新两个轴值(例如,如果围绕x轴旋转对象,则更新该对象的y和z坐标),还需要为此进行更多的数学运算。旋转是一个圆圈,我们需要 <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin">Math.sin</a></code> 和 <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos">Math.cos</a></code> 来帮助我们绘制这个圆圈。 <br> + 让我们设置一个旋转速率,我们将会将它转换为弧度范围的值,以便稍后在 <code>Math.sin</code> 和 <code>Math.cos</code> 中使用,当我们在旋转我们的 boombox ,需要计算出新的坐标时:</p> + +<pre class="brush: js"><code>// set up rotation constants +const rotationRate = 60; // bigger number equals slower sound rotation + +const q = Math.PI/rotationRate; //rotation increment in radians</code> +</pre> + +<p>我们也可以使用它来计算旋转度,这将有助于我们即将必须创建的CSS变换(注意我们需要用于CSS变换的x和y轴):</p> + +<pre class="brush: js"><code>// get degrees for css +const degreesX = (q * 180)/Math.PI; +const degreesY = (q * 180)/Math.PI;</code> +</pre> + +<p>我们以左旋转为例看一看。我们需要更改panner x方向和z方向的坐标,以围绕y轴移动进行左旋转:</p> + +<pre class="brush: js"><code>case 'rotate-left': + transform.rotateY -= degreesY; + + // 'left' is rotation about y-axis with negative angle increment + z = panner.orientationZ.value*Math.cos(q) - panner.orientationX.value*Math.sin(q); + x = panner.orientationZ.value*Math.sin(q) + panner.orientationX.value*Math.cos(q); + y = panner.orientationY.value; + + panner.orientationX.value = x; + panner.orientationY.value = y; + panner.orientationZ.value = z; +break;</code></pre> + +<p>这有点令人困惑,但我们正在做的是使用sin和cos来帮助我们计算出旋转 boombox 所需的圆周运动的坐标。 <br> + 我们可以为所有轴做到这一点。只需要选择正确的方向进行更新,以及我们是想要正增量还是负增量。</p> + +<pre class="brush: js"><code>case 'rotate-right': + transform.rotateY += degreesY; + // 'right' is rotation about y-axis with positive angle increment + z = panner.orientationZ.value*Math.cos(-q) - panner.orientationX.value*Math.sin(-q); + x = panner.orientationZ.value*Math.sin(-q) + panner.orientationX.value*Math.cos(-q); + y = panner.orientationY.value; + panner.orientationX.value = x; + panner.orientationY.value = y; + panner.orientationZ.value = z; +break; +case 'rotate-up': + transform.rotateX += degreesX; + // 'up' is rotation about x-axis with negative angle increment + z = panner.orientationZ.value*Math.cos(-q) - panner.orientationY.value*Math.sin(-q); + y = panner.orientationZ.value*Math.sin(-q) + panner.orientationY.value*Math.cos(-q); + x = panner.orientationX.value; + panner.orientationX.value = x; + panner.orientationY.value = y; + panner.orientationZ.value = z; +break; +case 'rotate-down': + transform.rotateX -= degreesX; + // 'down' is rotation about x-axis with positive angle increment + z = panner.orientationZ.value*Math.cos(q) - panner.orientationY.value*Math.sin(q); + y = panner.orientationZ.value*Math.sin(q) + panner.orientationY.value*Math.cos(q); + x = panner.orientationX.value; + panner.orientationX.value = x; + panner.orientationY.value = y; + panner.orientationZ.value = z; +break;</code></pre> + +<p>最后一件事 - 我们需要更新CSS并保留鼠标事件最后一步的引用。<br> + 这是最终的 <code>moveBoombox</code> 函数。</p> + +<pre class="brush: js"><code>function moveBoombox(direction, prevMove) { + switch (direction) { + case 'left': + if (transform.xAxis > leftBound) { + transform.xAxis -= 5; + panner.positionX.value -= 0.1; + } + break; + case 'up': + if (transform.yAxis > topBound) { + transform.yAxis -= 5; + panner.positionY.value -= 0.3; + } + break; + case 'right': + if (transform.xAxis < rightBound) { + transform.xAxis += 5; + panner.positionX.value += 0.1; + } + break; + case 'down': + if (transform.yAxis < bottomBound) { + transform.yAxis += 5; + panner.positionY.value += 0.3; + } + break; + case 'back': + if (transform.zAxis > innerBound) { + transform.zAxis -= 0.01; + panner.positionZ.value += 40; + } + break; + case 'forward': + if (transform.zAxis < outerBound) { + transform.zAxis += 0.01; + panner.positionZ.value -= 40; + } + break; + case 'rotate-left': + transform.rotateY -= degreesY; + + // 'left' is rotation about y-axis with negative angle increment + z = panner.orientationZ.value*Math.cos(q) - panner.orientationX.value*Math.sin(q); + x = panner.orientationZ.value*Math.sin(q) + panner.orientationX.value*Math.cos(q); + y = panner.orientationY.value; + + panner.orientationX.value = x; + panner.orientationY.value = y; + panner.orientationZ.value = z; + break; + case 'rotate-right': + transform.rotateY += degreesY; + // 'right' is rotation about y-axis with positive angle increment + z = panner.orientationZ.value*Math.cos(-q) - panner.orientationX.value*Math.sin(-q); + x = panner.orientationZ.value*Math.sin(-q) + panner.orientationX.value*Math.cos(-q); + y = panner.orientationY.value; + panner.orientationX.value = x; + panner.orientationY.value = y; + panner.orientationZ.value = z; + break; + case 'rotate-up': + transform.rotateX += degreesX; + // 'up' is rotation about x-axis with negative angle increment + z = panner.orientationZ.value*Math.cos(-q) - panner.orientationY.value*Math.sin(-q); + y = panner.orientationZ.value*Math.sin(-q) + panner.orientationY.value*Math.cos(-q); + x = panner.orientationX.value; + panner.orientationX.value = x; + panner.orientationY.value = y; + panner.orientationZ.value = z; + break; + case 'rotate-down': + transform.rotateX -= degreesX; + // 'down' is rotation about x-axis with positive angle increment + z = panner.orientationZ.value*Math.cos(q) - panner.orientationY.value*Math.sin(q); + y = panner.orientationZ.value*Math.sin(q) + panner.orientationY.value*Math.cos(q); + x = panner.orientationX.value; + panner.orientationX.value = x; + panner.orientationY.value = y; + panner.orientationZ.value = z; + break; + } + + boombox.style.transform = 'translateX('+transform.xAxis+'px) translateY('+transform.yAxis+'px) scale('+transform.zAxis+') rotateY('+transform.rotateY+'deg) rotateX('+transform.rotateX+'deg)'; + + const move = prevMove || {}; + move.frameId = requestAnimationFrame(() => moveBoombox(direction, move)); + return move; +}</code> +</pre> + +<ul> +</ul> + +<h2 id="连接我们的控件">连接我们的控件</h2> + +<p>连接控制按钮相对简单 - 现在我们可以在控件上监听鼠标事件并运行此方法,并在释放鼠标时停止它:</p> + +<pre class="brush: js"><code>// for each of our controls, move the boombox and change the position values +moveControls.forEach(function(el) { + + let moving; + el.addEventListener('mousedown', function() { + + let direction = this.dataset.control; + if (moving && moving.frameId) { + window.cancelAnimationFrame(moving.frameId); + } + moving = moveBoombox(direction); + + }, false); + + window.addEventListener('mouseup', function() { + if (moving && moving.frameId) { + window.cancelAnimationFrame(moving.frameId); + } + }, false) + +})</code> +</pre> + +<ul> +</ul> + +<h2 id="概述">概述</h2> + +<p>希望本文能让你深入了解 Web Audio 空间化的工作原理,以及每个{{domxref("PannerNode")}} 属性的作用(其中有很多属性)。这些值有时难以操作,根据你的使用情况,可能需要一些时间才能使它们正确。</p> + +<div class="note"> +<p><strong>注意:</strong>音频空间化在不同浏览器中的听起来略有不同。 panner节点在底层做了一些非常复杂的数学运算;这里有 <a href="https://wpt.fyi/results/webaudio/the-audio-api/the-pannernode-interface?label=stable&aligned=true">许多测试</a>,因此你可以跟踪不同平台上此节点的内部工作状态。</p> +</div> + +<p>再次,您可以在 <a href="https://mdn.github.io/webaudio-examples/spacialization/">这里查看最终的演示</a>,同时<a href="https://github.com/mdn/webaudio-examples/tree/master/spacialization">最终的源代码在这里</a>。还有一个 <a href="https://codepen.io/Rumyra/pen/MqayoK?editors=0100">Codepen 的演示</a>。</p> + +<p>如果你正在使用 3D 游戏和/或 WebXR,最好利用 3D 库来创建此类功能,而不是尝试从最初的规则完成所有这些操作。我们在本文中提出了自己的想法,让你了解它是如何工作的,但是通过利用别人在你之前所做的工作,你将节省大量时间。</p> diff --git a/files/zh-cn/web/api/web_audio_api/最佳实践/index.html b/files/zh-cn/web/api/web_audio_api/最佳实践/index.html new file mode 100644 index 0000000000..f0a241a557 --- /dev/null +++ b/files/zh-cn/web/api/web_audio_api/最佳实践/index.html @@ -0,0 +1,100 @@ +--- +title: Web Audio API 最佳实践 +slug: Web/API/Web_Audio_API/最佳实践 +tags: + - Web Audio API + - 指导 + - 最佳实践 + - 音频 +translation_of: Web/API/Web_Audio_API/Best_practices +--- +<div>{{apiref("Web Audio API")}}</div> + +<p class="summary">在创意编程中(creative coding)没有严格的对错之分。 只要你充分考虑安全性、性能和accessibility,你可以用自己的办法来编写代码。在这篇文章中,我们会分享一些最佳实践——包含使用Web Audio API的指导、小知识和小技巧。</p> + +<h2 id="加载声音声音文件">加载声音/声音文件</h2> + +<p>使用Web Audio API加载声音的主要方式有四种,你可能会对于应当使用哪种方式有些困惑。</p> + +<p>在从文件中加载声音时,你可能会采取从{{domxref("HTMLMediaElement")}} (即 {{htmlelement("audio")}} 或{{htmlelement("video")}} )中抓取的方式,或提取文件并将其解码到缓冲区。两种工作方式都是合理的,然而,在处理全长音轨时,前一种方法会更加常见。而后一种方法更常见于处理更短的(例如样本)的音轨。</p> + +<p>多媒体类HTML元素有开箱即用的媒体流支持。音频会在浏览器判断可以在播放完成之前加载文件的剩余部分时进行播放(when the browser determines it can load the rest of the file before playing finishes.)。你可以在<a href="/en-US/docs/Web/API/Web_Audio_API/Using_Web_Audio_API">Using the Web Audio API tutorial</a>这篇文档中看到一个把多媒体类HTML元素与Web Audio API结合使用的例子。</p> + +<p>如果你使用缓冲节点(buffer node)来加载音频,你将会有更多的控制权。虽然你需要请求这个文件,然后等待它加载完成(<a href="/en-US/docs/Web/API/Web_Audio_API/Advanced_techniques#Dial_up_%E2%80%94_loading_a_sound_sample">我们的这篇进阶文章中的这一节</a>介绍了一个好办法)。但是,随后您可以直接访问数据,这意味着你能进行更精确,更精确的操作。</p> + +<p>对于来自用户的摄像头或麦克风的音频,你可以考虑通过<a href="/en-US/docs/Web/API/Media_Streams_API">Media Stream API</a>和{{domxref("MediaStreamAudioSourceNode")}}接口来访问。这在与WebRTC协作以及你想录制或分析音频的场合下很管用。</p> + +<p>最后一个要介绍的方法时如何生成声音。这可以通过{{domxref("OscillatorNode")}}和创建一个缓冲区(buffer)然后向其中填充你的数据来完成。你可以在<a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Advanced_techniques">这篇指导你如何创建自己的乐器的文章</a>中学习到用这两个工具创建声音的知识。</p> + +<h2 id="Cross_browser_legacy_support">Cross browser & legacy support</h2> + +<p>The Web Audio API specification is constantly evolving and like most things on the web, there are some issues with it working consistently across browsers. Here we'll look at options for getting around cross-browser problems.</p> + +<p>There's the <a href="https://github.com/chrisguttandin/standardized-audio-context"><code>standardised-audio-context</code></a> npm package, which creates API functionality consistently across browsers, full holes as they are found. It's constantly in development and endeavours to keep up with the current specification.</p> + +<p>There is also the option of libraries, of which there are a few depending on your use case. For a good all-rounder, <a href="https://howlerjs.com/">howler.js</a> is a good choice. It has cross-browser support and, provides a useful subset of functionality. Although it doesn't harness the full gamut of filters and other effects the Web Audio API comes with, you can do most of what you'd want to do.</p> + +<p>If you are looking for sound creation or a more instrument-based option, <a href="https://tonejs.github.io/">tone.js</a> is a great library. It provides advanced scheduling capabilities, synths, and effects, and intuitive musical abstractions built on top of the Web Audio API.</p> + +<p><a href="https://github.com/bbc/r-audio">R-audio</a>, from the <a href="https://medium.com/bbc-design-engineering/r-audio-declarative-reactive-and-flexible-web-audio-graphs-in-react-102c44a1c69c">BBC's Research & Development department</a>, is a library of React components aiming to provide a "more intuitive, declarative interface to Web Audio". If you're used to writing JSX it might be worth looking at.</p> + +<h2 id="自动播放策略">自动播放策略</h2> + +<p>浏览器已经开始实施自动播放策略,这一策略通常可以概括为:</p> + +<blockquote> +<p>"Create or resume context from inside a user gesture".</p> +</blockquote> + +<p>这在实践中意味着什么呢?user gesture可以解释为用户触发的事件(一般来说,是<code>click</code>事件)。浏览器厂商判定不应该允许Web Audio上下文自动播放音频,而应该由用户开始播放。这是因为自动播放音频非常烦人且令人讨厌。那么,我们该如何处理(handle)呢?</p> + +<p>无论是offline还是online,当你创建了一个音频上下文(audio context),它会有一个内部状态(<code>state</code>),这个状态有<code>暂停(suspend)、播放(running)、关闭(closed)</code>三种可能。</p> + +<p>(When you create an audio context (either offline or online) it is created with a <code>state</code>, which can be <code>suspended</code>, <code>running</code>, or <code>closed</code>.)</p> + +<p>例如,在使用 {{domxref("AudioContext")}}时,如果你在<code>click</code>事件中创建了音频上下文,它的内部状态应该会被自动设置成<code>播放(running)</code>。这里有一个在<code>click</code>事件中创建音频上下文简单的例子:</p> + +<pre class="brush: js">const button = document.querySelector('button'); +button.addEventListener('click', function() { + const audioCtx = new AudioContext(); +}, false); +</pre> + +<p>如果你在用户动作之外创建上下文(create the context outside of a user gesture),它的内部状态会被设置为<code>暂停(suspend)</code>。这里我们可以同样用click事件的例子。我们会检查这个上下文的状态,并且启动它。如果它是<code>暂停(suspend)</code>的状态,使用<code><a href="https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/resume">resume()</a></code>方法来恢复。</p> + +<pre class="brush: js">const audioCtx = new AudioContext(); +const button = document.querySelector('button'); + +button.addEventListener('click', function() { + // check if context is in suspended state (autoplay policy) + if (audioCtx.state === 'suspended') { + audioCtx.resume(); + } +}, false); +</pre> + +<p>对于{{domxref("OfflineAudioContext")}},你也可以使用<a href="https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext/startRendering"><code>startRendering()</code></a>方法来恢复到播放状态。</p> + +<h2 id="User_control">User control</h2> + +<p>If your website or application contains sound, you should allow the user control over it, otherwise again, it will become annoying. This can be achieved by play/stop and volume/mute controls. The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Using_Web_Audio_API">Using the Web Audio API</a> tutorial goes over how to do this.</p> + +<p>If you have buttons that switch audio on and off, using the ARIA <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Switch_role"><code>role="switch"</code></a> attribute on them is a good option for signalling to assistive technology what the button's exact purpose is, and therefore making the app more accessible. There's a <a href="https://codepen.io/Wilto/pen/ZoGoQm?editors=1100">demo of how to use it here</a>.</p> + +<p>As you work with a lot of changing values within the Web Audio API and will want to provide users with control over these, the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range"><code>range input</code></a> is often a good choice of control to use. It's a good option as you can set minimum and maximum values, as well as increments with the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-step"><code>step</code></a> attribute.</p> + +<h2 id="Setting_AudioParam_values">Setting AudioParam values</h2> + +<p>There are two ways to manipulate {{domxref("AudioNode")}} values, which are themselves objects of type {{domxref("AudioParam")}} interface. The first is to set the value directly via the property. So for instance if we want to change the <code>gain</code> value of a {{domxref("GainNode")}} we would do so thus:</p> + +<pre class="brush: js">gainNode.gain.value = 0.5; +</pre> + +<p>This will set our volume to half. However, if you're using any of the <code>AudioParam</code>'s defined methods to set these values, they will take precedence over the above property setting. If for example, you want the <code>gain</code> value to be raised to 1 in 2 seconds time, you can do this:</p> + +<pre class="brush: js">gainNode.gain.setValueAtTime(1, audioCtx.currentTime + 2); +</pre> + +<p>It will override the previous example (as it should), even if it were to come later in your code.</p> + +<p>Bearing this in mind, if your website or application requires timing and scheduling, it's best to stick with the {{domxref("AudioParam")}} methods for setting values. If you're sure it doesn't, setting it with the <code>value</code> property is fine.</p> |
