每 5 秒重复一次 audioContext 振荡器

Repeat an audioContext oscillator every 5 seconds

我正在尝试编写一个莫尔斯电码训练器,它每 5 秒生成一个随机的两个字母模式,并且每个循环都重新创建 audiocontext,但我不知道如何添加将调用重复循环的代码。我试过 setTimeout() setInterval(),但它们都消除了音频。

此外,在以下代码中按五次按钮后。 我收到错误

" TypeError: null is not an object (evaluating 'ctx.currentTime')"

 <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
    <button onclick = "startIt()">Play</button>
    <button onclick = "stopIt()">Stop</button>
    <h2>Morse Code</h2>

    <h1 id="demo"></h1>
    <h1 id="demo2"></h1>

    <script>
    var codeStream = '';
    var dot = 1.2 / 15;
    var text = "";
    var display = "";
    var k = 0;
    var alphabet = [["A",".-"],["B","-..."],["C","-.-."],["D","-.."],["E","."],["F","..-."],["G","--."],["H","...."],["I",".."],["J",".---"],
        ["K","-.-"],["L",".-.."],["M","--"],["N","-."],["O","---"],["P",".--."],["Q","--.-"],["R",".-."],["S","..."],["T","-"],["U","..-"],
        ["V","...-"],["W",".--"],["X","-..-"],["Y","-.--"],["Z","--.."],["1",".----"],["2","..---"],["3","...--"],["4","....-"],["5","....."],
        ["6","-...."],["7","--..."],["8","---.."],["9","----."],["0","-----"],[".",".-.-.-"],[",","--..--"],["?","..--.."],["'",".----."],["!","-.-.--"],
        ["/","-..-."],[":","---..."],[";","-.-.-."],["=","-...-"],["-","-....-"],["_","..--.-"],["\"",".-..-."],["@",".--.-."],["(","-.--.-"],[" ",""]];

    stopIt = function(){
                ctx.close();
                location.reload();
            }

    function nextGroup() {
            for (i = 0; i < 2; i++){                
                var randomLetter = Math.floor(Math.random() * 26);
                var code = alphabet[randomLetter][1] + " ";
                var character = alphabet[randomLetter][0];      
                display += code;                    
                text += character;                  
            }
        codeStream = display;       
    }

    function startIt(){     
            var AudioContext = window.AudioContext || window.webkitAudioContext;
            var ctx = new AudioContext();
            var t = ctx.currentTime;
            var oscillator = ctx.createOscillator();        
            oscillator.type = "sine";
            oscillator.frequency.value = 600;
            oscillator.start();
            var gainNode = ctx.createGain();

            nextGroup();
            console.log(codeStream);
            document.getElementById("demo").innerHTML = text;
            document.getElementById("demo2").innerHTML = codeStream;
            display = "";
            text = "";                      
            gainNode.gain.setValueAtTime(0, t);

            for (var i = 0; i < codeStream.length; i++) {
                switch(codeStream.charAt(i)) {
                    case ".":
                        gainNode.gain.setValueAtTime(1, t);
                        t += dot;
                        gainNode.gain.setValueAtTime(0, t);
                        t += dot;
                        break;
                    case "-":
                        gainNode.gain.setValueAtTime(1, t);
                        t += 3 * dot;
                        gainNode.gain.setValueAtTime(0, t);
                        t += dot;
                        break;
                    case " ":
                        t += 7 * dot;
                        break;
                }           
            }

                gainNode.gain.setValueAtTime(0, t);
                t += 50 * dot;          

            oscillator.connect(gainNode);
            gainNode.connect(ctx.destination);          
            codeStream = '';                    
        oscillator.stop(t);         
        }                   
    </script>   
    </body>
</html>

看起来有些问题与振荡器的范围界定和状态管理有关。我无法重现您看到的错误,但 stopIt 函数肯定无法访问在 startIt.

中创建的 ctx

替代方案可能是,与其在每个 运行 上重新创建上下文、振荡器和增益节点,不如创建它们一次并重新使用它们。此处演示:http://jsfiddle.net/kts74g0x/

代码:

const ALPHABET = [
  ["A", ".-"],
  ...
  [" ",""]
];
const DOT = 1;
const DASH = 3;
const NEXT = DOT;
const SPACE = 7;
const SPEED = 1.2 / 15;

const AudioContext = window.AudioContext || window.webkitAudioContext;

/**
 * Create a single audio context, oscillator and gain node and repeatedly
 * use them instead of creating a new one each time. The gain is just
 * silent most of the time.
 */
const ctx = new AudioContext();
const oscillator = ctx.createOscillator();
const gainNode = ctx.createGain();
oscillator.type = "sine";
oscillator.frequency.value = 600;
oscillator.connect(gainNode);
oscillator.start();
gainNode.connect(ctx.destination);
gainNode.gain.value = 0;

function playCodeStream(stream) {
  let t = ctx.currentTime;
  gainNode.gain.setValueAtTime(0, t);
  for (var i = 0; i < stream.length; i++) {
    switch(stream.charAt(i)) {
      case ".":
        gainNode.gain.setValueAtTime(1, t);
        t += DOT * SPEED;
        gainNode.gain.setValueAtTime(0, t);
        t += NEXT * SPEED;
        break;
      case "-":
        gainNode.gain.setValueAtTime(1, t);
        t += DASH * SPEED;
        gainNode.gain.setValueAtTime(0, t);
        t += NEXT * SPEED;
        break;
      case " ":
        t += SPACE * SPEED;
        break;
    }  
  }
}

/**
 * Set interval will wait initially for the period of
 * time before first triggering the function.
 */
setInterval(() => { playCodeStream([
  ALPHABET.filter(v => v[0] === "H"),
  ALPHABET.filter(v => v[0] === "E"),
  ALPHABET.filter(v => v[0] === "L"),
  ALPHABET.filter(v => v[0] === "L"),
  ALPHABET.filter(v => v[0] === "O")
].join(" ")); }, 10000);

设置间隔 returns 可以传递给 clearInterval 的 ID 以防止将来 运行s,播放按钮可能会启动间隔,停止按钮可以清除它,因为例如。

对于 iOS 存在一些限制,因此 AudioContext 无法播放声音,除非它响应用户交互 (https://hackernoon.com/unlocking-web-audio-the-smarter-way-8858218c0e09)。我们可以通过添加一个按钮来解决这个问题。

<button id="go">Go</button>

并检查音频上下文的状态/启动间隔以响应单击此按钮(演示:http://jsfiddle.net/7gfnrubc/)。更新后的代码:

function next() {
  playCodeStream([
    ALPHABET.filter(v => v[0] === "H"),
    ALPHABET.filter(v => v[0] === "E"),
    ALPHABET.filter(v => v[0] === "L"),
    ALPHABET.filter(v => v[0] === "L"),
    ALPHABET.filter(v => v[0] === "O")
  ].join(" "));
}

function go() {
  if (ctx.state === 'suspended') {
    ctx.resume();
  }
  /**
   * Set interval will wait initially for the period of
   * time before first triggering the function. Can call
   * the function initially to start off.
   */
  next();
  setInterval(next, 10000);
}

const button = document.getElementById("go");
button.addEventListener("click", go);