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], "low-g": [55 + 12, 60 + 12, 64 + 12, 69 + 12], "harmonic-minor": [67 + 12, 58 + 12, 62 + 12, 67 + 12], "suspended-fourth": [67 + 12, 60 + 12, 53 + 12, 60 + 12], "lydian": [67 + 12, 60 + 12, 64 + 12, 66 + 12], "diminished": [67 + 12, 59 + 12, 62 + 12, 65 + 12], "augmented": [67 + 12, 61 + 12, 64 + 12, 68 + 12], "open-fifths": [67 + 12, 62 + 12, 69 + 12, 62 + 12], "double-unison": [67 + 12, 67 + 12, 60 + 12, 60 + 12], "ionian": [67 + 12, 60 + 12, 64 + 12, 69 + 12], "dorian": [67 + 12, 58 + 12, 62 + 12, 69 + 12], "mixo-dorian": [65 + 12, 58 + 12, 67 + 12, 69 + 12], "phrygian": [67 + 12, 56 + 12, 62 + 12, 69 + 12], "mixolydian": [67 + 12, 60 + 12, 62 + 12, 69 + 12], "aeolian": [67 + 12, 58 + 12, 62 + 12, 67 + 12], "locrian": [67 + 12, 56 + 12, 60 + 12, 67 + 12] }, "guitar": { "standard": [40, 45, 50, 55, 59, 64], "drop-d": [38, 45, 50, 55, 59, 64], "dadgad": [38, 45, 50, 55, 57, 64], "open-g": [38, 43, 47, 50, 55, 59], "open-d": [38, 43, 50, 54, 57, 64], "open-c": [36, 40, 43, 48, 52, 57], "half-step-down": [39, 43, 48, 52, 55, 60], "full-step-down": [38, 43, 48, 53, 57, 62], "double-drop-d": [38, 43, 48, 50, 55, 59], "new-standard": [36, 40, 45, 50, 54, 59], "nashville-high-strung": [40, 45, 50, 55, 59, 64], "orkney": [36, 40, 43, 36, 40, 43], "modal-tuning-1": [40, 45, 39, 50, 45, 64], "modal-tuning-2": [40, 45, 37, 50, 45, 64], "db-custom": [49, 54, 59, 56, 71, 63] } }; let currentTuning = []; let currentA440 = 440; let currentTranspose = 0; let tuningMode = "equal"; 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 = ""; 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(); } function updateTuning() { const selectedInstrument = instrumentSelect.value; const selectedTuning = tuningSelect.value; currentTuning = instrumentTunings[selectedInstrument][selectedTuning]; updateStringButtons(); } function updateStringButtons() { stringsDiv.innerHTML = ""; 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(); currentTuning.forEach((midiNote) => { Tone.Transport.scheduleOnce(() => { playNote(midiNote); }, `+${delay}`); delay += 0.2; }); Tone.Transport.start(); }); instrumentSelect.addEventListener('change', updateInstrument); tuningSelect.addEventListener('change', updateTuning); tuningModeSelect.addEventListener('change', () => { tuningMode = tuningModeSelect.value; }); updateInstrument(); a440Input.addEventListener('change', () => { outputDiv.textContent = `A4 Reference set to ${a440Input.value} Hz`; }); transposeInput.addEventListener('change', () => { outputDiv.textContent = `Transpose set to ${transposeInput.value} semitones`; }); });