每5秒重复一次audioContext振荡器

时间:2019-01-08 16:36:27

标签: javascript ios timer audiocontext

我正在尝试编写一个摩尔斯电码教练,该教练每5秒钟产生一个随机的两个字母模式,并在每个循环中重新创建音频上下文,但是我无法弄清楚如何添加需要重复循环的代码。我已经尝试过setTimeout() setInterval(),但是它们都消除了音频。

此外,在以下代码上按五次按钮之后。  我得到了错误

  

“ TypeError:null不是一个对象(正在评估'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>

1 个答案:

答案 0 :(得分:0)

看起来有些问题与振荡器的作用域和状态管理有关。我无法重现您看到的错误,但是stopIt函数当然无法访问在ctx中创建的startIt

另一种选择是,而不是在每次运行时重新创建上下文,振荡器和增益节点,而是一次创建它们,然后重新使用它们。此处演示: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);

例如,设置间隔返回的ID可以传递给clearInterval以防止以后运行,例如,播放按钮可能会启动间隔,而停止按钮可能会清除它。

对于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);