LoginSignup
4
5

More than 1 year has passed since last update.

Web Audio APIのOscillatorNodeを使用した簡易テストトーン発生器

Last updated at Posted at 2021-10-01

楽器の簡易チューナーとして使えるかもしれません。

ss.jpg

動作デモ


oscillator.html
<!DOCTYPE html>
<html lang='ja'>
  <head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width,initial-scale=1'>
    <title>Web Audio API OscillatorNode sample</title>
    <style>
      .unit {
        display: inline-block;
        border: 1px solid gray;
        border-radius: 5px;
        margin-top: 10px;
        padding: 8px;
        font-size: 12px;
      }
      button, input, select {
        background-color: #fff;
        color: #000;
        border: 1px solid #555;
      }
      button, select {
        border-radius: 5px;
        height: 25px;
      }
      .start {
        width: 100%;
      }
      input[type=range] {
        width: 220px;
      }
      .vt {
        margin-bottom: 8px;
      }
      .vf {
        width: 100%;
      }
      .vb {
        display: flex;
      }
    </style>
  </head>
  <body>
    <button class='add'>ADD Oscillator</button>
    <div class='container'>
    </div>
    <script>
      'use strict';
      const addButton = document.querySelector('.add');
      const container = document.querySelector('.container');

      addButton.addEventListener('click', () => addUnit());
      window.addEventListener('DOMContentLoaded', () => addUnit());

      document.addEventListener('click', e => {
        const t = e.target;
        const unit = t.closest('.unit');
        if(t.classList.contains('start')) {
          setAudioContext(unit);
        }
        if(t.classList.contains('delete')) {
          const id = unit.querySelector('.id').value;
          if(obj[id]) stop(obj, id);
          unit.remove();
        }
      });

      document.addEventListener('input', e => {
        const t = e.target;
        const unit = t.closest('.unit');
        const id = unit.querySelector('.id').value;
        const freq = unit.querySelector('.freq').value;
        const freq2 = unit.querySelector('.freq2').value;
        const tcl = t.classList;

        const f = freq * Math.pow(2, freq2 / 120);

        if(tcl.contains('freq') || tcl.contains('freq2')) {
          unit.querySelector('.freq_').textContent = f.toFixed(2);
          if(tcl.contains('freq2')) {
            unit.querySelector('.detune').textContent =
              (freq2 > 0 ? '+' : '') + (freq2 / 10).toFixed(2);
          }
        }
        if(tcl.contains('gain')) {
          unit.querySelector('.gv').textContent = (+unit.querySelector('.gain').value).toFixed(2);
        }

        if(!obj[id]) return;
        const a = obj[id].a;
        if(tcl.contains('type')) {
          a.osc.type = t.value;
        }
        if(tcl.contains('freq') || tcl.contains('freq2')) {
          a.osc.frequency.value = f;
        }
        if(tcl.contains('gain')) {
          a.gain.gain.value = t.value;
        }
      });

      const obj = {};
      const AudioContext = window.AudioContext || window.webkitAudioContext;

      const setAudioContext = e => {
        const id = e.querySelector('.id').value;

        e.style.backgroundColor = obj[id] ? '' : 'lightgreen';
        e.querySelector('.start').textContent = obj[id] ? 'START' : 'STOP';

        if(obj[id]) {
          stop(obj, id);
          return;
        }
        obj[id] = {
          type : e.querySelector('.type').value,
          freq : e.querySelector('.freq').value,
          freq2: e.querySelector('.freq2').value,
          gain : e.querySelector('.gain').value,
        };
        const o = obj[id];

        o.a = {};
        const a = o.a;

        a.context = new AudioContext();
        a.osc = a.context.createOscillator();
        a.osc.type = o.type;
        a.gain = a.context.createGain();
        a.osc.connect(a.gain);
        a.gain.connect(a.context.destination);
        a.osc.frequency.value = o.freq * Math.pow(2, o.freq2 / 120);
        a.gain.gain.value = o.gain;
        a.osc.start();
      };

      const stop = (o, id) => {
        o[id].a.osc.stop();
        o[id].a.osc.disconnect();
        o[id].a.context.close().then(() => delete o[id]);
      };

      const addUnit = () => {
        container.insertAdjacentHTML('beforeend',`
          <div class='unit'>
            <input type='hidden' class='id' value='u${Date.now()}'>
            <div class='vt'>
              Type: <select class='type'>
                <option value='sine'>Sine</option>
                <option value='square'>Square</option>
                <option value='sawtooth'>Sawtooth</option>
                <option value='triangle'>Triangle</option>
              </select>
            </div>
            <div class='vf'>
              Key: <select class='freq'></select>
              Freq: <span class='freq_'>440.00</span>Hz<br>
              Detune: <span class='detune'>0.00</span><br>
              <input type='range' class='freq2' value='0' min='-10' max='10' step='0.5'><br>
              Gain: <span class='gv'>0.20</span><br>
              <input type='range' class='gain' value='0.2' min='0' max='1' step='0.01'>
            </div>
            <br>
            <div class='vb'>
              <button class='start'>START</button>
              <button class='delete'>X</button>
            </div>
          </div>
        `);
        const keys = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
        const select = document.querySelector('.unit:last-of-type .freq');
        for(let o = 0; o <= 9; o++) {
          for(let k = 0; k < keys.length; k++) {
            const f = 440 * Math.pow(2, (o * 12 + k - 57) / 12);
            select.insertAdjacentHTML('beforeend',
              `<option value='${f}' ${f === 440 ? 'selected' : ''}>O${o}${keys[k]}</option>`);
          }
        }
      };
    </script>
  </body>
</html>
4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5