170 lines
7.4 KiB
JavaScript
170 lines
7.4 KiB
JavaScript
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`;
|
|
});
|
|
}); |