V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yrq110
V2EX  ›  前端开发

[译]把浏览器变成钢琴! Web Audio API 入门

  •  
  •   yrq110 ·
    yrq110 · 2016-08-23 11:40:42 +08:00 · 3958 次点击
    这是一个创建于 2811 天前的主题,其中的信息可能已经有所发展或是发生改变。

    把浏览器变成钢琴! Web Audio API 入门

    web 浏览器现在有各种各样、五花八门的功能,不乏一些比较有趣的,比如使用 canvas 元素的一些游戏与 3DCG ,使用 WebRTC 进行浏览器间的 P2P 通信等。

    在那其中,对音频的操作在最近引起了我的兴趣。那么今天就来说说在 web 浏览器上处理音频的「 WebAudioAPI 」。

    1.所谓「 WebAudioAPI 」?

    “ a high-level JavaScript API for processing and synthesizing audio in web applications ” ----Web Audio API – W3C

    翻译过来就是「为处理与合成音频的 web 应用所准备的高级 JavaScript API 」。

    2.三个使用「 Web Audio API 」的示例网站

    2.1 Javascript Drone

    http://matt-diamond.com/drone.html

    播放令人舒服的嗡嗡声(持续的声音)。通过调整 Base Note 与 Number of Generators 两个参数来调整声音,可以进行音频文件的录制与下载。

    2.2 HTML5 Drum Machine

    http://www.html5drummachine.com

    一台鼓机,设置不同乐器的发音时间,自动进行电子鼓的演奏。可以改变节拍(Tempo)与音色类型,同样可以进行音频文件的录制与下载。

    2.3 Loop Waveform Visualizer(PS:这个很酷炫)

    https://airtightinteractive.com/demos/js/reactive

    将音乐可视化的工具。将 mp3 文件拖入界面中,或者点击「 load sample mp3 」开始播放示例音乐,各种颜色的波纹,配合音乐不断振动不断扩大。

    这些全部是使用「 Web Audio API 」在 web 浏览器上实现的!是不是有一些想法了,这次的目标是作出一个可以在 web 浏览器上弹的钢琴,首先从产生声音开始吧。

    3.使用「 Web Audio API 」之前

    开始使用 Web Audio API 之前,有两个需要注意的地方。

    第一 WebAudioAPI 处于 W3C 发布标准的“工作草案( Working Draft )”阶段( 2016 年 6 月)

    距离到最终阶段为止很有可能有巨大的内容变化。

    参考:https://www.w3.org/TR/#tr_Audio

    第二 不支持 Internet Explorer 11

    Web Audio API 不支持 IE11 与 Android4.4 以下的自带的浏览器,支持 Edge, Android 版 Chrome 。

    参考:http://caniuse.com/#feat=audio-api

    能尽快无所顾虑的使用就好了...

    3.使用「 Web Audio API 」在浏览器中制作钢琴

    那么现在开始吧~

    3.1 产生声音

    Web Audio API 以 AudioContext 为主,衍生出表示音源、中间处理、最终输出等的 AudioNode 。

    // 初始化 AudioContext
    var ctx = new AudioContext();
     
    // 从 AudioContext 中生成 AudioNode
    // AudioBufferSourceNode: 处理一次性短音的 AudioNode
    var bufferSource = ctx.createBufferSource();
    

    对于音源、中间处理与最终输出等,根据细分出的不同内容,都有对应的继承自 AudioNode 的对象,并且 AudioNode 具有与其他 AudioNode 对象相连接的 connect 方法。

    // 保存音源
    bufferSource.buffer = data;
     
    // 与最后输出的音源连接
    // AudioDestinationNode: 表示最后的输出
    //   可以参考 AudioContext 的 destination 属性
    bufferSource.connect(ctx.destination);
     
    // 播放音源
    bufferSource.start(0);
    

    如例子所示,将表示音源的 AudioNode 与表示最终输出的 AudioNode 相连接,就准备好播放音源了。在这次的应用中,不进行任何中间处理,直接将音源与最终输出连接起来使用。

    使用短音来模拟钢琴键盘的单次音效。

    // 获取指定音源文件的二进制数据
    var xml = new XMLHttpRequest();
    xml.responseType = 'arraybuffer';
    xml.open('GET', 'media/piano.wav', true);
    xml.onload = function() {
        // 获取二进制数据并解码
        ctx.decodeAudioData(
            xml.response,
            function(_data) {
                data = _data;
            },
            function(err) {
                // error
            }
        );
    };
    

    处理短音源的 AudioNode 很适合作为 AudioBufferSourceNode ,因为一次性使用后不能再次利用的部分,为了释放内存会被立即释放。

    一般情况下 AudioBufferSourceNode 使用的是从二进制数据解码得到的音源,可以使用 XHRHttpRequest 来获取音源的二进制数据。

    组合这些代码,在画面上点击时会有声音。

    GitHub 源代码

    demo

    声音响啦!

    3.2 改变音调

    AudioNode 有可以改变自身状态的可变属性。

    这次使用的 AudioBufferSourceNode ,有一个可以改变自身播放速度的 playbackRate 属性,加快或减慢播放速度时声音的频率会随着改变,利用这个来改变声音的音调。

    通过 AudioBufferSourceNode 的 playbackRate 属性的 value 值来增减播放速度,不改变的情况下设定为 1 。

    bufferSource.buffer = data;
    // 设定为 2 倍的播放速度,频率会变为之前的 2 倍,音调会升高
    bufferSource.playbackRate.value = 2;
    bufferSource.connect(ctx.destination);
    bufferSource.start(0);
    

    由于加快播放速度时,频率会随其等比例增大,因此音调会升高。

    bufferSource.buffer = data;
    // 设定为 0.5 倍的播放速度,频率会变为之前的一般,音调会降低
    bufferSource.playbackRate.value = 0.5;
    bufferSource.connect(ctx.destination);
    bufferSource.start(0);
    

    由于减慢播放速度时,频率会随其等比例减小,因此音调会降低。

    在下面这个例子中可以在一定范围内改变音调。

    源代码

    demo

    音调变化了!

    3.3 弹奏钢琴

    那么是时候来弹奏钢琴了。

    利用播放速度的改变来调整音调,使用这个方法时也会改变音质,由于频率较高时音质变得特别刺耳,因此我们需要降低一下音调。

    首先使用 HTML 与 CSS 来制作键盘,这次使用的音源是 C ( do )音。

    决定了降低音调后,将这个 C 音作为最高的音,也就是键盘最右端的 C ,其余按顺序摆放白键与黑键。

    HTML:

    <ul>
        <li class="keyboard keyboard-white"></li>
        <li class="keyboard keyboard-black"></li>
        <li class="keyboard keyboard-white"></li>
        <li class="keyboard keyboard-black"></li>
        <li class="keyboard keyboard-white"></li>
        <li class="keyboard keyboard-white"></li>
        <li class="keyboard keyboard-black"></li>
        <li class="keyboard keyboard-white"></li>
        <li class="keyboard keyboard-black"></li>
        <li class="keyboard keyboard-white"></li>
        <li class="keyboard keyboard-black"></li>
        <li class="keyboard keyboard-white"></li>
        <li class="keyboard keyboard-white"></li>
    </ul>
    

    CSS:

    html, body, ul, li {
        box-sizing: border-box;
    }
    html, body, ul {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
    }
    li {
        list-style: none;
    }
    .keyboard {
        transition: background-color .1s ease 0s;
    }
    .keyboard:active {
        background-color: #DFFF30;
    }
    .keyboard-white {
        float: left;
        width: calc(100% / 8);
        height: 100%;
        border: 1px solid black;
    }
    .keyboard-black {
        height: 60%;
        position: absolute;
        top: 0;
        width: calc(100% / 12);
        background-color: black;
        border: 2px solid black;
    }
    .keyboard:nth-of-type(2) {left: 8%}
    .keyboard:nth-of-type(4) {left: 21%}
    .keyboard:nth-of-type(7) {left: 45%}
    .keyboard:nth-of-type(9) {left: 58%}
    .keyboard:nth-of-type(11) {left: 71%}
    

    var isSP = typeof window.ontouchstart !== 'undefined';
     
    // 将所有键整合成数组
    var keyboards = Array.prototype.slice.call(
        document.getElementsByClassName('keyboard')
    );
    // 从右向左来依次处理键盘上的键
    keyboards.reverse().map(function(keyboard, index) {
         
        // 处理键
     
    });
    

    以最右端的 C 为基准,从右往左来处理每个键的音调。

    钢琴是以平均律为基础的,每个音与右邻音的频率比例大约为 1 : 1.059463 ,与左邻音的频率比例大约为 1.059463 : 1 。

    利用这个来计算每个音调与与最右端 C 音的音调频率比。

    // 根据平均律得出的相邻音调之间的频率比(近似値)
    var frequencyRatioTempered = 1.059463;
     
    // 〜 省略 〜
     
    keyboards.reverse().map(function(keyboard, index) {
        // 根据基准音 C 求得其他音调相对于它的频率比例
        var frequencyRatio = 1;
        for (var i = 0; i < index; i++) {
            frequencyRatio /= frequencyRatioTempered;
        }
    }
    

    前面已经说过,频率与播放速度是同比增减的,因此直接将上面算出的频率比设定为播放速度。

    bufferSource.buffer = data;
    bufferSource.playbackRate.value = frequencyRatio;
    bufferSource.connect(ctx.destination);
    bufferSource.start(0);
    

    接下来是总结。

    源代码

    demo

    钢琴可以弹了唉╮(╯▽╰)╭! 搞定!

    4.与 audio 元素的不同

    使用 HTML5 中新增的 audio 元素来播放音源也可以实现,不过有一些不同:

    • 根据浏览器的不同对同时发声的个数有限制
    • 不能进行音源播放之前的中间处理

    5.总结

    这次使用 Web Audio API 做出了一个可以弹奏钢琴的应用。在浏览器上就可以演奏是不是挺棒?用手机访问的话,可以进行 2 指与 3 指的和弦演奏,请务必尝试一下(PS:我用 iOS 的 Chrome 试了下不行...)。

    那么回见!

    若有翻译不当之处请谅解,恳请指出。

    1 条回复    2021-03-14 17:05:20 +08:00
    sunziren
        1
    sunziren  
       2021-03-14 17:05:20 +08:00
    洛阳铲,此文有用
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2408 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 10:09 · PVG 18:09 · LAX 03:09 · JFK 06:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.