tooner/index.js

93 lines
2.4 KiB
JavaScript

const kNumKeys = 49;
const kFirstKey = 'C2';
const kNotes = ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'G#', 'A', 'Bb', 'B'];
const kA0Frequency = 27.5;
const kSemiToneMultiplier = Math.pow(2, 1/12);
const audioCtx = new AudioContext();
function playNote(frequency, duration) {
let currentGain = 1.0;
const bassBoost = f => {
const t = f / 20000;
const a = 4.0;
const b = 0.2;
return b * t + a * (1 - t);
};
while (frequency < 20000) {
const oscillator = new OscillatorNode(audioCtx);
const gain = new GainNode(audioCtx);
oscillator.type = 'sine';
oscillator.frequency.value = frequency; // value in hertz
oscillator.connect(gain);
gain.gain.setValueAtTime(0, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(bassBoost(frequency) * currentGain, audioCtx.currentTime + 0.010);
gain.gain.linearRampToValueAtTime(1e-10, audioCtx.currentTime + 0.500);
gain.connect(audioCtx.destination);
oscillator.start();
setTimeout(() => {oscillator.stop();}, duration);
frequency *= 2;
currentGain *= 0.3;
}
}
function noteFrequency(key) {
let [note, octave, ] = key.split(/(\d)/);
octave = parseInt(octave);
const interval = kNotes.indexOf(note) - kNotes.indexOf('A') + octave * 12;
let freq = kA0Frequency * Math.pow(kSemiToneMultiplier, interval);
return freq;
}
function onKeyClick(event) {
const note = event.srcElement.id;
const freq = noteFrequency(note);
playNote(freq, 500);
}
function makeKey(note) {
const key = document.createElement('span');
if (note.includes('#') || note.includes('b')) {
key.classList.add('blackKey');
} else {
key.classList.add('whiteKey');
}
key.id = note;
key.addEventListener('click', onKeyClick);
return key;
}
function setupKeyboard() {
const keyboard = document.getElementById('keyboard');
keyboard.innerHTML = '';
let curIndex = kNotes.indexOf(kFirstKey.charAt(0));
let octave = parseInt(kFirstKey.charAt(1));
for (let i = 0; i < kNumKeys; i++) {
const note = kNotes[curIndex] + octave;
const key = makeKey(note)
keyboard.appendChild(key);
curIndex += 1;
if (curIndex >= kNotes.length) {
octave += 1;
curIndex = curIndex % kNotes.length;
}
}
}
window.addEventListener('load', e => {
setupKeyboard();
});