【问题标题】:Generating sound on the fly with javascript/html5使用 javascript/html5 即时生成声音
【发布时间】:2011-09-14 16:00:03
【问题描述】:

是否可以使用 javascript/html5 生成恒定的声音流?例如,要生成一个永久正弦波,我需要一个回调函数,只要输出缓冲区即将变空,就会调用该函数:

function getSampleAt(timestep)
{
    return Math.sin(timestep);
}

(想法是用这个来做一个交互式合成器。我不知道一个键会被按下多长时间,所以我不能使用固定长度的缓冲区)

【问题讨论】:

  • 自从提出这个问题以来,很多事情都发生了变化。如果您仍然感兴趣,我在这里写了一篇文章:marcgg.com/blog/2016/11/01/javascript-audio
  • 试试Tone.js。用于在浏览器中制作交互式音乐的 Web 音频框架。该库根据您的要求“即时”生成音频。祝你好运!

标签: javascript html audio


【解决方案1】:

您现在可以在大多数浏览器中使用Web Audio API (excepting IE and Opera Mini)。

试试这个代码:

// one context per document
var context = new (window.AudioContext || window.webkitAudioContext)();
var osc = context.createOscillator(); // instantiate an oscillator
osc.type = 'sine'; // this is the default - also square, sawtooth, triangle
osc.frequency.value = 440; // Hz
osc.connect(context.destination); // connect it to the destination
osc.start(); // start the oscillator
osc.stop(context.currentTime + 2); // stop 2 seconds after the current time

如果你想降低音量,你可以这样做:

var context = new webkitAudioContext();
var osc = context.createOscillator();
var vol = context.createGain();

vol.gain.value = 0.1; // from 0 to 1, 1 full volume, 0 is muted
osc.connect(vol); // connect osc to vol
vol.connect(context.destination); // connect vol to context destination
osc.start(context.currentTime + 3); // start it three seconds from now

我在阅读Web Audio API Working Draft(我从@brainjam 的链接中找到)时对铬进行了实验。

我希望这会有所帮助。最后,在 chrome 检查器 (ctrl-shift-i) 中检查各种对象非常有帮助。

【讨论】:

  • 停止后如何重新开始?它说无法在“AudioScheduledSourceNode”上执行“启动”:不能多次调用启动。如果我想根据某些情况启动和停止,是否可以使用替代功能? resume() 和 suspend() 似乎不起作用
  • @devN 这是设计使然。但是,您可以创建一个每次都创建新节点的函数。
【解决方案2】:

使用 HTML5 音频元素

目前无法使用 JavaScript 和 audio 元素跨浏览器生成持续音频,正如 Steven Wittens notes in a blog post on creating a JavaScript synth:

“...没有办法将合成音频块排队以进行无缝播放”。

使用网络音频 API

Web Audio API 旨在促进 JavaScript 音频合成。 Mozilla 开发者网络有一个在 Firefox 4+ [demo 1] 中工作的Web Based Tone Generator。将这两行代码添加到该代码中,您就有了一个可以在按键时生成持续音频的工作合成器 [demo 2 - 仅适用于 Firefox 4,首先单击“结果”区域,然后按任意键]:

window.onkeydown = start;  
window.onkeyup = stop;

BBC 的page on the Web Audio API 也值得一看。不幸的是,对 Web Audio API 的支持还没有扩展到其他浏览器。

可能的解决方法

目前要创建跨浏览器合成器,您可能必须通过以下方式使用预先录制的音频:

  1. 使用预先录制的长 ogg/mp3 样本音调,将它们嵌入单独的 audio 元素中,并在按键时启动和停止它们。
  2. 嵌入包含音频元素的 swf 文件并通过 JavaScript 控制播放。 (这似乎是Google Les Paul Doodle 采用的方法。)

【讨论】:

    【解决方案3】:

    当然!您可以在此演示中使用音调合成器:

    audioCtx = new(window.AudioContext || window.webkitAudioContext)();
    
    show();
    
    function show() {
      frequency = document.getElementById("fIn").value;
      document.getElementById("fOut").innerHTML = frequency + ' Hz';
    
      switch (document.getElementById("tIn").value * 1) {
        case 0: type = 'sine'; break;
        case 1: type = 'square'; break;
        case 2: type = 'sawtooth'; break;
        case 3: type = 'triangle'; break;
      }
      document.getElementById("tOut").innerHTML = type;
    
      volume = document.getElementById("vIn").value / 100;
      document.getElementById("vOut").innerHTML = volume;
    
      duration = document.getElementById("dIn").value;
      document.getElementById("dOut").innerHTML = duration + ' ms';
    }
    
    function beep() {
      var oscillator = audioCtx.createOscillator();
      var gainNode = audioCtx.createGain();
    
      oscillator.connect(gainNode);
      gainNode.connect(audioCtx.destination);
    
      gainNode.gain.value = volume;
      oscillator.frequency.value = frequency;
      oscillator.type = type;
    
      oscillator.start();
    
      setTimeout(
        function() {
          oscillator.stop();
        },
        duration
      );
    };
    frequency
    <input type="range" id="fIn" min="40" max="6000" oninput="show()" />
    <span id="fOut"></span><br>
    type
    <input type="range" id="tIn" min="0" max="3" oninput="show()" />
    <span id="tOut"></span><br>
    volume
    <input type="range" id="vIn" min="0" max="100" oninput="show()" />
    <span id="vOut"></span><br>
    duration
    <input type="range" id="dIn" min="1" max="5000" oninput="show()" />
    <span id="dOut"></span>
    <br>
    <button onclick='beep();'>Play</button>

    玩得开心!

    我从 Houshalter 那里得到了解决方案: How do I make Javascript beep?

    您可以在此处克隆和调整代码: Tone synthesizer demo on JS Bin

    兼容的浏览器:

    • Chrome 移动版和桌面版
    • Firefox 移动和桌面 Opera 移动、迷你和桌面
    • 安卓浏览器
    • Microsoft Edge 浏览器
    • iPhone 或 iPad 上的 Safari

    不兼容

    • Internet Explorer 版本 11(但可以在 Edge 浏览器上运行)

    【讨论】:

      【解决方案4】:

      Web Audio API 即将登陆 Chrome。见http://googlechrome.github.io/web-audio-samples/samples/audio/index.html

      按照“入门”中的说明进行操作,然后查看令人印象深刻的演示。

      更新(2017):现在这是一个更加成熟的界面。该 API 记录在 https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API

      【讨论】:

        【解决方案5】:

        您可以即时生成 wav-e 文件并播放 (src)

        // Legend
        // DUR - duration in seconds   SPS - sample per second (default 44100)
        // NCH - number of channels    BPS - bytes per sample
        
        // t - is number from range [0, DUR), return number in range [0, 1]
        function getSampleAt(t,DUR,SPS)
        {
            return Math.sin(6000*t); 
        }
        
        function genWAVUrl(fun, DUR=1, NCH=1, SPS=44100, BPS=1) {
          let size = DUR*NCH*SPS*BPS; 
          let put = (n,l=4) => [(n<<24),(n<<16),(n<<8),n].filter((x,i)=>i<l).map(x=> String.fromCharCode(x>>>24)).join('');
          let p = (...a) => a.map( b=> put(...[b].flat()) ).join(''); 
          let data = `RIFF${put(44+size)}WAVEfmt ${p(16,[1,2],[NCH,2],SPS,NCH*BPS*SPS,[NCH*BPS,2],[BPS*8,2])}data${put(size)}`
          
          for (let i = 0; i < DUR*SPS; i++) {
            let f= Math.min(Math.max(fun(i/SPS,DUR,SPS),0),1);
            data += put(Math.floor( f * (2**(BPS*8)-1)), BPS);
          }
          
          return "data:Audio/WAV;base64," + btoa(data);
        }
        
        
        var WAV = new Audio( genWAVUrl(getSampleAt,5) ); // 5s
        WAV.setAttribute("controls", "controls");
        document.body.appendChild(WAV);
        //WAV.play()

        这里是可视化

        function getSampleAt(t,DUR,SPS)
        {
            return 0.5+Math.sin(15*t)/(1+t*t); 
        }
        
        
        // ----------------------------------------------
        
        function genWAVUrl(fun, DUR=1, NCH=1, SPS=44100, BPS=1) {
          let size = DUR*NCH*SPS*BPS; 
          let put = (n,l=4) => [(n<<24),(n<<16),(n<<8),n].filter((x,i)=>i<l).map(x=> String.fromCharCode(x>>>24)).join('');
          let p = (...a) => a.map( b=> put(...[b].flat()) ).join(''); 
          let data = `RIFF${put(44+size)}WAVEfmt ${p(16,[1,2],[NCH,2],SPS,NCH*BPS*SPS,[NCH*BPS,2],[BPS*8,2])}data${put(size)}`
          
          for (let i = 0; i < DUR*SPS; i++) {
            let f= Math.min(Math.max(fun(i/SPS,DUR,SPS),0),1);
            data += put(Math.floor( f * (2**(BPS*8)-1)), BPS);
          }
          
          return "data:Audio/WAV;base64," + btoa(data);
        }
        
        function draw(fun, DUR=1, NCH=1, SPS=44100, BPS=1) {
          time.innerHTML=DUR+'s';
          time.setAttribute('x',DUR-0.3);
          svgCh.setAttribute('viewBox',`0 0 ${DUR} 1`);
          let p='', n=100; // n how many points to ommit
          for (let i = 0; i < DUR*SPS/n; i++) p+= ` ${DUR*(n*i/SPS)/DUR}, ${1-fun(n*i/SPS, DUR,SPS)}`;
          chart.setAttribute('points', p);
        }
        
        function frame() {
          let t=WAV.currentTime;
          point.setAttribute('cx',t)
          point.setAttribute('cy',1-getSampleAt(t))
          window.requestAnimationFrame(frame);
        }
        
        function changeStart(e) {
          var r = e.target.getBoundingClientRect();
          var x = e.clientX - r.left;
          WAV.currentTime = dur*x/r.width;
          WAV.play()
        }
        
        var dur=5; // seconds 
        var WAV = new Audio(genWAVUrl(getSampleAt,dur));
        draw(getSampleAt,dur);
        frame();
        .chart { border: 1px dashed #ccc; }
        .axis { font-size: 0.2px}
        audio { outline: none; }
        Click at blue line (make volume to max):
        <svg class="chart" id="svgCh" onclick="changeStart(event)">    
          <circle cx="0" cy="-1" r="0.05" style="fill: rgba(255,0,0,1)" id="point"></circle>
          <polyline id="chart" fill="none" stroke="#0074d9" stroke-width="0.01" points=""/>
          <text x="0.03" y="0.9" class="axis">0</text>
          <text x="0.03" y="0.2" class="axis">1</text>
          <text x="4.8" y="0.9" class="axis" id="time"></text>
        </svg><br>

        【讨论】:

          【解决方案6】:

          这不是您问题的真正答案,因为您要求使用 JavaScript 解决方案,但您可以使用 ActionScript。它应该可以在所有主流浏览器上运行。

          您可以在 JavaScript 中调用 ActionScript 函数。

          通过这种方式,您可以包装 ActionScript 声音生成函数并制作它们的 JavaScript 实现。只需使用 Adob​​e Flex 构建一个小型 swf,然后将其用作 JavaScript 代码的后端。

          【讨论】:

            【解决方案7】:

            这就是我一直在寻找的东西,最终我设法按照自己的意愿做到了。也许你也会喜欢它。 带有频率和推开/关闭的简单滑块:

            buttonClickResult = function () {
            	var button = document.getElementById('btn1');
            
            	button.onclick = function buttonClicked()  {
            
            		if(button.className=="off")  {
            			button.className="on";
            			oscOn ();
            		}
            
            		else if(button.className=="on")  {
            			button.className="off";
            			oscillator.disconnect();
            		}
            	}
            };
            
            buttonClickResult();
            
            var oscOn = function(){
            
            	window.AudioContext = window.AudioContext || window.webkitAudioContext;
            	var context = new AudioContext();
            	var gainNode = context.createGain ? context.createGain() : context.createGainNode();
            
            	//context = new window.AudioContext();
            	oscillator = context.createOscillator(),
            			oscillator.type ='sine';
            
            	oscillator.frequency.value = document.getElementById("fIn").value;
            	//gainNode = createGainNode();
            	oscillator.connect(gainNode);
            	gainNode.connect(context.destination);
            	gainNode.gain.value = 1;
            	oscillator.start(0);
            };
            <p class="texts">Frekvence [Hz]</p>
            <input type="range" id="fIn" min="20" max="20000" step="100" value="1234" oninput="show()" />
            <span id="fOut"></span><br>
            <input class="off" type="button" id="btn1" value="Start / Stop" />

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-04-03
              相关资源
              最近更新 更多