Повторяйте осциллятор audioContext каждые 5 секунд - PullRequest
0 голосов
/ 08 января 2019

Я пытаюсь написать инструктор азбуки Морзе, который генерирует случайный двухбуквенный шаблон каждые 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 голосов
/ 08 января 2019

Похоже, что некоторые проблемы связаны с определением объема и управлением состоянием генератора. Я не смог воспроизвести ошибку, которую вы видели, но функция 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);

Заданный интервал возвращает идентификатор, который можно передать на 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);
...