楽器の簡易チューナーとして使えるかもしれません。
動作デモ
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;
}
option:checked {
background-color: #484;
color: #bfb;
}
.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');
const baseFreq = 440;
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_'>${baseFreq.toFixed(2)}</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');
const l = keys.length;
for (let o = 0; o <= 9; o++) keys.forEach((v, k) =>
select.insertAdjacentHTML('beforeend', `
<option value='${baseFreq * Math.pow(2, (o * l + k - 57) / l)}'>O${o}${v}</option>
`));
select.value = baseFreq;
};
</script>
</body>
</html>