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(); });