web-tuner/frontend/dist/app.js

235 lines
7.4 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
const navLinks = document.querySelectorAll('.nav-link');
const views = document.querySelectorAll('.view');
const filterSection = document.querySelector('.filter-section');
const themeSelect = document.getElementById('theme-select');
const themeLink = document.getElementById('theme-stylesheet');
const btnExport = document.getElementById('btn-export');
const btnApply = document.getElementById('btn-apply');
const btnSave = document.getElementById('btn-save');
const btnReset = document.getElementById('btn-reset');
const presetSelect = document.getElementById('preset-select');
const tuningGrid = document.getElementById('tuning-grid');
const fretsInput = document.getElementById('frets-input');
const fingersInput = document.getElementById('fingers-input');
const loading = document.getElementById('chord-loading');
const allNotes = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];
// --- Audio ---
const noteToSemitone = {C:0,'C#':1,D:2,'D#':3,E:4,F:5,'F#':6,G:7,'G#':8,A:9,'A#':10,B:11};
const standardMIDI = [40, 45, 50, 55, 59, 64];
function tuningNamesToMIDI(names) {
return names.map((n, i) => {
const pc = noteToSemitone[n];
if (pc === undefined) return standardMIDI[i] || 40;
const std = standardMIDI[i] || 40;
for (let m = std - 6; m <= std + 6; m++) {
if (((m % 12) + 12) % 12 === pc) return m;
}
return std;
});
}
window.currentTuningMIDI = standardMIDI.slice();
let polySynth = null;
window.playChord = function(midiNotes) {
Tone.start();
if (!polySynth) {
polySynth = new Tone.PolySynth(Tone.Synth, {maxPolyphony: 8}).toDestination();
polySynth.set({
oscillator: {type: 'triangle'},
envelope: {attack: 0.01, decay: 0.3, sustain: 0.4, release: 1.0}
});
}
polySynth.releaseAll();
const now = Tone.now();
midiNotes.forEach((m, i) => {
const freq = 440 * Math.pow(2, (m - 69) / 12);
polySynth.triggerAttackRelease(freq, '1.5s', now + i * 0.03);
});
};
const presets = {
'Standard': ['E','A','D','G','B','E'],
'Drop D': ['D','A','D','G','B','E'],
'DADGAD': ['D','A','D','G','A','D'],
'Open G': ['D','G','B','D','G','B'],
'Open D': ['D','A','D','F#','A','D'],
'Open C': ['C','G','C','G','C','E'],
'Half Step Down': ['D#','G#','C#','F#','A#','D#'],
'Full Step Down': ['D','G','C','F','A','D'],
'Custom': null
};
const shapesSection = document.getElementById('shapes-section');
let currentConfig = null;
let chordsLoaded = false;
let shapesInited = false;
// --- Navigation ---
navLinks.forEach(link => {
link.addEventListener('click', () => {
const target = link.dataset.view;
navLinks.forEach(l => l.classList.remove('active'));
views.forEach(v => v.classList.remove('active'));
link.classList.add('active');
document.getElementById('view-' + target).classList.add('active');
filterSection.style.display = target === 'chords' ? '' : 'none';
shapesSection.style.display = target === 'shapes' ? '' : 'none';
if (target === 'chords' && !chordsLoaded) {
loadChords();
}
if (target === 'shapes' && !shapesInited) {
shapesInited = true;
if (window.initShapeExplorer) window.initShapeExplorer();
}
});
});
// --- Theme ---
themeSelect.addEventListener('change', () => {
themeLink.href = 'chords-' + themeSelect.value + '.css';
});
// --- PDF Export ---
btnExport.addEventListener('click', () => window.print());
// --- Config Panel ---
function populatePresets() {
presetSelect.innerHTML = '';
for (const name of Object.keys(presets)) {
const opt = document.createElement('option');
opt.value = name;
opt.textContent = name;
presetSelect.appendChild(opt);
}
}
function buildTuningGrid(tuning) {
tuningGrid.innerHTML = '';
tuning.forEach((note, i) => {
const sel = document.createElement('select');
allNotes.forEach(n => {
const opt = document.createElement('option');
opt.value = n;
opt.textContent = n;
if (n === note) opt.selected = true;
sel.appendChild(opt);
});
sel.addEventListener('change', () => {
presetSelect.value = 'Custom';
});
tuningGrid.appendChild(sel);
});
}
function readTuningFromGrid() {
return Array.from(tuningGrid.querySelectorAll('select')).map(s => s.value);
}
function syncConfigPanel(cfg) {
fretsInput.value = cfg.frets;
fingersInput.value = cfg.max_fingers;
buildTuningGrid(cfg.tuning);
let matched = false;
for (const [name, notes] of Object.entries(presets)) {
if (notes && notes.length === cfg.tuning.length &&
notes.every((n, i) => n === cfg.tuning[i])) {
presetSelect.value = name;
matched = true;
break;
}
}
if (!matched) presetSelect.value = 'Custom';
}
presetSelect.addEventListener('change', () => {
const notes = presets[presetSelect.value];
if (notes) {
buildTuningGrid(notes);
}
});
btnApply.addEventListener('click', () => {
if (!window.go) return;
const cfg = {
instrument: currentConfig ? currentConfig.instrument : 'guitar',
tuning: readTuningFromGrid(),
frets: parseInt(fretsInput.value) || 4,
max_fingers: parseInt(fingersInput.value) || 4
};
loading.style.display = '';
loading.textContent = 'Regenerating chords...';
window.go.main.App.UpdateConfig(cfg).then(chords => {
currentConfig = cfg;
window.currentTuningMIDI = tuningNamesToMIDI(cfg.tuning);
loading.style.display = 'none';
chordsLoaded = true;
if (window.buildChordCards) {
window.buildChordCards(chords || [], cfg.frets, cfg.tuning.length);
}
}).catch(err => {
loading.textContent = 'Error: ' + err;
});
});
btnSave.addEventListener('click', () => {
if (!window.go) return;
window.go.main.App.SaveConfig().then(() => {
btnSave.textContent = 'Saved';
setTimeout(() => { btnSave.textContent = 'Save'; }, 1500);
}).catch(err => {
btnSave.textContent = 'Error';
setTimeout(() => { btnSave.textContent = 'Save'; }, 1500);
});
});
btnReset.addEventListener('click', () => {
if (!window.go) return;
window.go.main.App.ResetConfig().then(cfg => {
currentConfig = cfg;
window.currentTuningMIDI = tuningNamesToMIDI(cfg.tuning);
syncConfigPanel(cfg);
loadChords();
});
});
// --- Load Chords ---
function loadChords() {
if (!window.go) {
loading.textContent = 'Wails runtime not available.';
return;
}
loading.style.display = '';
loading.textContent = 'Loading chord fingerings...';
window.go.main.App.FindChordFingerings().then(chords => {
loading.style.display = 'none';
chordsLoaded = true;
if (window.buildChordCards) {
window.buildChordCards(chords || [], currentConfig.frets, currentConfig.tuning.length);
}
});
}
// --- Init ---
populatePresets();
if (window.go && window.go.main && window.go.main.App) {
window.go.main.App.GetConfig().then(cfg => {
currentConfig = cfg;
window.currentTuningMIDI = tuningNamesToMIDI(cfg.tuning);
syncConfigPanel(cfg);
});
} else {
loading.textContent = 'Wails runtime not available.';
}
});