<bdo id="4g88a"><xmp id="4g88a">
  • <legend id="4g88a"><code id="4g88a"></code></legend>

    Web Audio API 第2章 完美的播放時機控制

    Web Audio API 第2章 完美的播放時機控制

    相較于

    低延時對于游戲或交互式應用來說非常重要,因為交互操作時要快速響應給用戶的聽覺。如果響應的不及時,用戶就會察覺到延時,這種體驗相當不好。

    在實踐中,由于人類聽覺的不完美,延遲的余地可達20毫秒左右,但具體延遲多少取決于許多因素。精確的可控時間使得能夠在特定時間安排事件。這對于腳本場景和音樂應用來說非常重要

    時間模型

    其中一個重要的點是,音頻上下文 AudioContext 提供了一致的計時模型和時間的幀率。重要的是此模型有別于我們常用的 Javascript 腳本所用的計時器 如 setTimeout, setInterval, new Date()。也有別于 window.performance.now() 提供的性能分析時鐘

    在 Web Audio API 音頻上下文系統坐標中所有你打交道的的絕對時間單位是秒而不是毫秒。當前時間可通過音頻上下文的 currentTime 屬性獲取。同樣它也是秒為單位,時間存儲為高精度的浮點數存儲。

    精確的播放與復播

    在游戲或其它需要精確時間控制的應用中 start() 方法用于控制安排精確的播放。為了保證正確運行,需要確保緩沖已提前加載。如果沒有提前緩沖, 為了 Web Audio API 能解碼,需要等等瀏覽器完成加載音頻文件。如果沒有加載或解碼完畢就去播放或精準的控制播放那么很有可能會失敗。

    start() 方法的第一個參數可用于聲音精確定位控制在哪里開始播放。此參數是 AudioContext 音頻上下文坐標系內的 currentTime, 如果傳參小于 currentTIme, 則它會立即播放。因為 start(0) 就是直接開始播放的意思 ,如果想要控制延遲 5 秒后播放,則需要 start(context.currentTime + 5)。

    聲音的緩沖也可以從特定位置開始播放,使用 start() 方法的第二個參數控制,第三個可選參數用于時長特殊限制。舉個例子,如果我們想暫停后在暫停的位置重新開始恢復播放,我們需要實現記錄聲音在當前 session 播放了多久并追蹤偏移量用于后面恢復播放

    start 方法即 AudioBufferSourceNode.start([when][, offset][, duration]);

    可參考 https://developer.mozilla.org/zh-CN/docs/Web/API/AudioBufferSourceNode/start

    // 假定 context 是網頁 audio context 上下文
    var startOffset = 0; 
    var startTime = 0;
    function pause() {
      source.stop();
      // 計算距離上次播放暫停時過去了多久
      startOffset += context.currentTime - startTime;
    }
    

    一旦源節點播放完畢,它無法再重播。為了重播底層的緩沖區,你需要新建一個新的源節點(AudioBufferSourceNode) 并調用 start():

    function play() {
      startTime = context.currentTime;
      var source = context.createBufferSource();
      source.buffer = this.buffer;
      source.loop = true;
      source.connect(context.destination);
      // 開始播放,但確保我們限定在 buffer 緩沖區的范圍內 
      source.start(0, startOffset % buffer.duration);
      
    }
    

    盡管重新新建一個源節點看起來非常的低效,牢記,這種模式下源節點被著重優化過了。請記住,如果你在處理 AudioBuffer, 播放同一個聲音你無需重新請求資源。當 AudioBuffer 緩沖區與播放功能被分拆后,就可以實現同一時間內播放不同版本的緩沖區。如果你感覺需要重復這樣的方式調用 ,那么你可以在將它封裝成一個簡單的方法函數比如 playSound(buffer) 就像在第一章代碼片斷中有提到過的。

    以上代碼實現 demo 可參考 examples/ch02/index1.html

    規劃精確的節奏

    Web Audio API 允許開發人員在精確地規劃播放。為了演示,讓我們先設置一個簡單的節奏軌道。也許最簡單的要屬廣為人知的 爵士鼓模式(drumkit pattern) 如圖 2-1,hihat每8個音符演奏一次,kick和snare在四分音符上交替演奏

    注: kick 是底鼓,就是架子鼓組里面最下面最大的那個鼓,聲音是咚咚咚的;

    hihat是鼓手左邊兩片合在一起的镲片 閉镲 是次次次的聲音,開镲是擦擦擦的聲音

    snare 是離鼓手最*的*放的小鼓,叫軍鼓,打上去是咔咔咔的聲音;

    image

    假定我們已搞定了 kick, snare,和 hihat 緩沖,那么代碼實現就比較簡單:

    for (var bar = 0; bar < 2; bar++) {
      var time = startTime + bar * 8 * eighthNoteTime; 
      // Play the bass (kick) drum on beats 1, 5 
      playSound(kick, time);
      playSound(kick, time + 4 * eighthNoteTime);
      // Play the snare drum on beats 3, 7
      playSound(snare, time + 2 * eighthNoteTime);
      playSound(snare, time + 6 * eighthNoteTime);
      // Play the hihat every eighth note.
      for (var i = 0; i < 8; ++i) {
        playSound(hihat, time + i * eighthNoteTime);
      } 
    }
    

    代碼中對時間進行硬編碼是不明智的。所以如果你正在處理一個快速變化的應用程序,那是不可取的。處理此問題的一個好方法是使用JavaScript計時器和事件隊列創建自己的調度器。這種方法在《雙鐘的故事》中有描述

    譯者注:《雙鐘的故事》即 《A Tale of Two Clocks》 寓言故事大致告訴人們不能依靠單獨一種方式,需要依靠多種方式方法解決問題

    譯者注:具體音樂原理不重要,重要的是反應出可對音頻延時播放, 聽的就是個“動次打次”

    以上代碼實現 demo 可參考 https://github.com/willian12345/WebAudioAPI/tree/master/examples/ch02/index2.html

    更改音頻參數

    很多音頻節點類形的參數都是可配的。舉個例子,GainNode 擁有 gain 參數用于控制通過 gain 節點的聲音音量乘數。特別的一點是,參數如果是1則不影響幅度,0.5 降一半,2 則是雙倍。讓我們設置一個:

    譯者注: gain節點或稱增益節點通常用于調節音頻信號的音量

      // 創建 gain node.
      var gainNode = context.createGain();
      // 連接  source 到 gain node. 
      source.connect(gainNode);
      // 連接  gain node 至  destination. 
      gainNode.connect(context.destination);
    

    在 context API 中,音頻參數用音頻實例表示。這些值可通過節點直接變更:

    // 減小音量
    gainNode.gain.value = 0.5;
    

    當然也可以晚一點修改值,通過精確安排在后續更改。我們也可以使用 setTimeout 來延時修改,但它不夠精確,原因有幾點:

    1. 毫秒基數的計時可能不夠精確
    2. 主 JS 進程可能很忙需要處理更高優先級的任務比如頁面布局,垃圾回收以及其它 API 可能導致延時的回調函數隊列等
    3. JS 計時器可能會受到瀏覽器 tab 的狀態影響。舉個例子,interval 計時器相比于 tab 在前臺運行時,tab 在后臺運行時觸發的更慢

    我們可以直接調用 setValueAtTime() 方法來代替直接設值,它需要一個值與開始時間作為參數。舉個例子,下面的代碼片斷一秒就搞定了 GainNode的 gain 值設置

    gainNode.gain.setValueAtTime(0.5, context.currentTime + 1);
    

    以上代碼實現 demo 可參考 https://github.com/willian12345/WebAudioAPI/tree/master/examples/ch02/index3.html

    漸變的音頻參數

    在很多例子中,相較于直接硬生生設置一個參數,你可能更傾向于漸變設值。舉個例子,當開發音樂應用時,我們希望當前的聲音軌道漸隱,然后新的聲音軌道漸入而避免生硬的切換。當然你也可以利用多次調用 setValueAtTime 函數的方法實現類似的效果,但顯然這種方法不太方便。

    Web Audio API 提供了一個堆方便的 RampToValue 方法,能夠漸變任何參數。 它們是 linearRampToValueAtTime() 和 exponentialRampToValueAtTime()。兩者的區別在于發生變換的方式。在一些用例中,exponential 變換更加敏感,因為我們以指數方式感知聲音的許多方面。

    讓我們用一個例子來展示交叉變換吧。給定一個播放列表,我們可以在音軌間安排變換降低當前播放的音軌音量并且增加下一條音軌的音量。兩者都發生在當前曲目結束播放之前稍早的時候:

    function createSource(buffer) {
      var source = context.createBufferSource(); 
      var gainNode = context.createGainNode(); 
      source.buffer = buffer;
      // Connect source to gain. 
      source.connect(gainNode);
      // Connect gain to destination. 
      gainNode.connect(context.destination);
      return {
        source: source, 
        gainNode: gainNode
      }; 
    }
    
    function playHelper(buffers, iterations, fadeTime) { 
      var currTime = context.currentTime;
      for (var i = 0; i < iterations; i++) {
        for (var j = 0; j < buffers.length; j++) { 
          var buffer = buffers[j];
          var duration = buffer.duration;
          var info = createSource(buffer);
          var source = info.source;
          var gainNode = info.gainNode;
          // 漸入
          gainNode.gain.linearRampToValueAtTime(0, currTime); 
          gainNode.gain.linearRampToValueAtTime(1, currTime + fadeTime);
          // 漸出
          gainNode.gain.linearRampToValueAtTime(1, currTime + duration-fadeTime);
          gainNode.gain.linearRampToValueAtTime(0, currTime + duration);
          // 播放當前音頻.
          source.noteOn(currTime);
          // 為下次迭代累加時間
          currTime += duration - fadeTime;
        }
      } 
    }
    

    譯者注: 原文中的代碼過時了, 實際實現請參考我的 demo 實現

    標準 https://webaudio.github.io/web-audio-api/#dom-gainnode-gain

    以上代碼實現 demo 可參考 https://github.com/willian12345/WebAudioAPI/tree/master/examples/ch02/index4.html

    定制時間曲線

    如果線性曲線和指數曲線都無法滿足你的需求,你也可以自己定制自己的曲線值通過傳遞一個數組給 setValueCurveAtTime 函數實現。有了這個函數,你可以通過傳遞數組實現自定義時間曲線。它是創建一堆 setValueAtTime 函數調用的快捷調用。舉個例子,如果我們想創建顫音效果,我們可以通過傳遞振蕩曲線作為 GainNode 的 gain 參數值,如圖 2-2

    image

    上圖的振蕩曲線實現代碼如下:

    
    var DURATION = 2; 
    var FREQUENCY = 1; 
    var SCALE = 0.4;
    // Split the time into valueCount discrete steps.
    var valueCount = 4096;
    // Create a sinusoidal value curve.
    var values = new Float32Array(valueCount); 
    for (var i = 0; i < valueCount; i++) {
      var percent = (i / valueCount) * DURATION*FREQUENCY;
      values[i] = 1 + (Math.sin(percent * 2*Math.PI) * SCALE);
      // Set the last value to one, to restore playbackRate to normal at the end. 
      if (i == valueCount - 1) {
        values[i] = 1;
      }
    }
    // Apply it to the gain node immediately, and make it last for 2 seconds.
    this.gainNode.gain.setValueCurveAtTime(values, context.currentTime, DURATION);
    

    上面的代碼片斷我們手動計算出了正弦曲線并將其設置到 gain 的參數內創造出顫音效果。好吧,它用了一點點數學..

    這給我們帶來了 Web Audio API 的一個非常重要的特性, 它使得我們創建像顫音這樣的特效變的非常容易。這個重要的點子是很多音頻特效的基礎。上述的代碼實際上是被稱為低頻振蕩(LFO)效果應用的一個例子, LFO 經常用于創建特效,如 vibrato 震動 phasing 分隊 和 tremolo 顫音。通過對音頻節點應用振蕩,我們很容易重寫之前的例子:

    // Create oscillator.
    var osc = context.createOscillator(); 
    osc.frequency.value = FREQUENCY;
    var gain = context.createGain(); 
    gain.gain.value = SCALE; 
    osc.connect(gain); 
    gain.connect(this.gainNode.gain);
    // Start immediately, and stop in 2 seconds.
    osc.start(0);
    osc.stop(context.currentTime + DURATION);
    

    createOscillator https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode

    相比于我們之前創建的自定義曲線后面的代碼要更高效,重現了效果但它幫我們省了用手動循環創建正弦函數

    以上振蕩器節點的代碼實現 demo 可參考 https://github.com/willian12345/WebAudioAPI/tree/master/examples/ch02/index5.html


    注:轉載請注明出處博客園:王二狗Sheldon池中物 (willian12345@126.com)

    posted @ 2024-03-23 15:36  池中物王二狗  閱讀(175)  評論(0編輯  收藏  舉報
    轉載入注明博客園 王二狗Sheldon Email: willian12345@126.com https://github.com/willian12345
    免费视频精品一区二区_日韩一区二区三区精品_aaa在线观看免费完整版_世界一级真人片
    <bdo id="4g88a"><xmp id="4g88a">
  • <legend id="4g88a"><code id="4g88a"></code></legend>