web-tuner/frontend/dist/tuner.js

157 lines
6.5 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],
"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`;
});
});