document.addEventListener('DOMContentLoaded', () => { Tone.start(); const synth = new Tone.Synth().toDestination(); const a440Input = document.getElementById('a440'); const transposeInput = document.getElementById('transpose'); const instrumentSelect = document.getElementById('instrument'); const instlabel = document.getElementById('instrument-label'); const tuningSelect = document.getElementById('tuning'); const tuningModeSelect = document.getElementById('tuning-mode'); const stringsDiv = document.getElementById('strings'); const playAllButton = document.getElementById('play-all'); const outputDiv = document.getElementById('output'); const instrumentTunings = { "ukulele": { "standard": [67 + 12, 60 + 12, 64 + 12, 69 + 12], // G5, C5, E5, A5 "low-g": [55 + 12, 60 + 12, 64 + 12, 69 + 12], // G4, C5, E5, A5 "harmonic-minor": [67 + 12, 58 + 12, 62 + 12, 67 + 12], // G5, Bb5, D5, G5 "suspended-fourth": [67 + 12, 60 + 12, 53 + 12, 60 + 12], // G5, C5, F5, C5 "lydian": [67 + 12, 60 + 12, 64 + 12, 66 + 12], // G5, C5, E5, F#5 "diminished": [67 + 12, 59 + 12, 62 + 12, 65 + 12], // G5, B5, D5, F5 "augmented": [67 + 12, 61 + 12, 64 + 12, 68 + 12], // G5, C#5, E5, G#5 "open-fifths": [67 + 12, 62 + 12, 69 + 12, 62 + 12], // G5, D5, A5, D5 "double-unison": [67 + 12, 67 + 12, 60 + 12, 60 + 12], // G5, G5, C5, C5 "ionian": [67 + 12, 60 + 12, 64 + 12, 69 + 12], // G C E A "dorian": [67 + 12, 58 + 12, 62 + 12, 69 + 12], // G Bb D A "mixo-dorian": [65 + 12, 58 + 12, 67 + 12, 69 + 12], // F A# G A "phrygian": [67 + 12, 56 + 12, 62 + 12, 69 + 12], // G Ab D A "mixolydian": [67 + 12, 60 + 12, 62 + 12, 69 + 12], // G C D A "aeolian": [67 + 12, 58 + 12, 62 + 12, 67 + 12], // G Bb D G "locrian": [67 + 12, 56 + 12, 60 + 12, 67 + 12] // G Ab C G }, "guitar": { "standard": [40, 45, 50, 55, 59, 64], // EADGBE "drop-d": [38, 45, 50, 55, 59, 64], // DADGBE "dadgad": [38, 45, 50, 55, 57, 64], // DADGAD "open-g": [38, 43, 47, 50, 55, 59], // DGDGBD "open-d": [38, 43, 50, 54, 57, 64], // DADF#AD "open-c": [36, 40, 43, 48, 52, 57], // CGCGCE "half-step-down": [39, 43, 48, 52, 55, 60], // Eb Ab Db Gb Bb Eb "full-step-down": [38, 43, 48, 53, 57, 62], // D G C F A D "double-drop-d": [38, 43, 48, 50, 55, 59], // DADGBD "new-standard": [36, 40, 45, 50, 54, 59], // CGDAEG "nashville-high-strung": [40, 45, 50, 55, 59, 64], // EADGBE but with lighter strings "orkney": [36, 40, 43, 36, 40, 43], // CGDGCD "modal-tuning-1": [40, 45, 39, 50, 45, 64], // CGDGBE "modal-tuning-2": [40, 45, 37, 50, 45, 64], // EAEAC#E "db-custom": [49, 54, 59, 56, 71, 63] // Db Gb B Ab B (oct) Eb } }; let currentTuning = []; let currentA440 = 440; let currentTranspose = 0; let tuningMode = "equal"; // Default tuning mode const harmonicFrequencyRatios = { "C": 1.0, "Db": 17/16, "D": 9/8, "Eb": 19/16, "E": 5/4, "F": 21/16, "Gb": 11/8, "G": 3/2, "Ab": 13/8, "A": 5/3, "Bb": 7/4, "B": 15/8, "C_octave": 2.0 }; function updateInstrument() { const selectedInstrument = instrumentSelect.value; tuningSelect.innerHTML = ""; // Clear previous tuning options let inst = instrumentSelect.value; instlabel.innerText = inst.charAt(0).toUpperCase() + inst.slice(1) + " Tuner"; Object.keys(instrumentTunings[selectedInstrument]).forEach(tuning => { let option = document.createElement("option"); option.value = tuning; option.textContent = tuning.replace(/-/g, " ").toUpperCase(); tuningSelect.appendChild(option); }); updateTuning(); // Apply default tuning for new instrument } function updateTuning() { const selectedInstrument = instrumentSelect.value; const selectedTuning = tuningSelect.value; currentTuning = instrumentTunings[selectedInstrument][selectedTuning]; updateStringButtons(); // Update button labels when tuning changes } function updateStringButtons() { stringsDiv.innerHTML = ""; // Clear existing buttons currentTuning.forEach((midiNote, index) => { let button = document.createElement("button"); button.classList.add("string-button"); button.textContent = `String ${index + 1}`; button.addEventListener("click", () => playNote(midiNote)); stringsDiv.appendChild(button); }); } function calculateFrequency(midiNote, tuningMode) { const referenceNote = 60; const referenceFreq = currentA440 * Math.pow(2, (referenceNote - 69) / 12); if (tuningMode === "harmonic") { const noteNames = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"]; const octave = Math.floor(midiNote / 12) - 5; const note = noteNames[midiNote % 12]; return referenceFreq * harmonicFrequencyRatios[note] * Math.pow(2, octave); } else { return 440 * Math.pow(2, (midiNote - 69) / 12); } } function playNote(midiNote) { const a440Value = parseFloat(a440Input.value); const transposeValue = parseInt(transposeInput.value); if (!isNaN(a440Value)) { currentA440 = a440Value; } if (!isNaN(transposeValue)) { currentTranspose = transposeValue; } const adjustedMidiNote = midiNote + currentTranspose; const frequency = calculateFrequency(adjustedMidiNote, tuningMode); const referenceFrequencyRatio = currentA440 / 440; const adjustedFrequency = frequency * referenceFrequencyRatio; synth.set({ oscillator: { type: 'sine' } }); synth.triggerAttackRelease(adjustedFrequency, "1.75s"); outputDiv.textContent = `Playing: ${Tone.Frequency(adjustedFrequency).toNote()} (Freq: ${adjustedFrequency.toFixed(2)} Hz, A4 Ref: ${currentA440} Hz, Transpose: ${currentTranspose} semitones)`; } playAllButton.addEventListener('click', () => { let delay = 0; Tone.Transport.stop(); Tone.Transport.cancel(); // Clear all scheduled events currentTuning.forEach((midiNote, index) => { Tone.Transport.scheduleOnce(time => { playNote(midiNote); }, `+${delay}`); // Small delay between notes delay += 0.2; // Increase delay for each note }); Tone.Transport.start(); // Start Tone.Transport }); instrumentSelect.addEventListener('change', updateInstrument); tuningSelect.addEventListener('change', updateTuning); tuningModeSelect.addEventListener('change', () => { tuningMode = tuningModeSelect.value; }); updateInstrument(); // Initialize instrument and tuning on page load a440Input.addEventListener('change', () => { outputDiv.textContent = `A4 Reference set to ${a440Input.value} Hz`; }); transposeInput.addEventListener('change', () => { outputDiv.textContent = `Transpose set to ${transposeInput.value} semitones`; }); });