249 lines
7.8 KiB
JavaScript
249 lines
7.8 KiB
JavaScript
(() => {
|
|
const container = document.getElementById('chord-container');
|
|
const rootFilters = document.getElementById('root-filters');
|
|
const qualityFilters = document.getElementById('quality-filters');
|
|
|
|
const roots = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];
|
|
let activeRoots = new Set();
|
|
let activeQualities = new Set();
|
|
let allChords = [];
|
|
let maxFret = 4;
|
|
let numStrings = 6;
|
|
|
|
function initFilters() {
|
|
roots.forEach(r => {
|
|
const pill = document.createElement('button');
|
|
pill.className = 'filter-pill';
|
|
pill.textContent = r;
|
|
pill.addEventListener('click', () => {
|
|
if (activeRoots.has(r)) {
|
|
activeRoots.delete(r);
|
|
pill.classList.remove('active');
|
|
} else {
|
|
activeRoots.add(r);
|
|
pill.classList.add('active');
|
|
}
|
|
applyFilters();
|
|
});
|
|
rootFilters.appendChild(pill);
|
|
});
|
|
|
|
if (window.go && window.go.main && window.go.main.App) {
|
|
window.go.main.App.GetChordDefinitions().then(defs => {
|
|
const qualities = new Set();
|
|
for (const cat of Object.keys(defs)) {
|
|
for (const q of Object.keys(defs[cat])) {
|
|
qualities.add(q);
|
|
}
|
|
}
|
|
Array.from(qualities).sort().forEach(q => {
|
|
const pill = document.createElement('button');
|
|
pill.className = 'filter-pill';
|
|
pill.textContent = q;
|
|
pill.addEventListener('click', () => {
|
|
if (activeQualities.has(q)) {
|
|
activeQualities.delete(q);
|
|
pill.classList.remove('active');
|
|
} else {
|
|
activeQualities.add(q);
|
|
pill.classList.add('active');
|
|
}
|
|
applyFilters();
|
|
});
|
|
qualityFilters.appendChild(pill);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
function applyFilters() {
|
|
const cards = container.querySelectorAll('.chord-card');
|
|
cards.forEach((card, i) => {
|
|
const chord = allChords[i];
|
|
if (!chord) return;
|
|
|
|
let show = true;
|
|
if (activeRoots.size > 0 && !activeRoots.has(chord.root)) {
|
|
show = false;
|
|
}
|
|
if (activeQualities.size > 0 && !activeQualities.has(chord.quality)) {
|
|
show = false;
|
|
}
|
|
card.style.display = show ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
function buildChordCards(chords, mf, ns) {
|
|
allChords = chords;
|
|
maxFret = mf || maxFret;
|
|
numStrings = ns || numStrings;
|
|
container.innerHTML = '';
|
|
|
|
chords.forEach(match => {
|
|
const card = document.createElement('div');
|
|
card.className = 'chord-card';
|
|
|
|
const h2 = document.createElement('h2');
|
|
h2.textContent = match.chord;
|
|
card.appendChild(h2);
|
|
|
|
const fb = document.createElement('div');
|
|
fb.className = 'fretboard';
|
|
fb.dataset.fingering = JSON.stringify(match.fingering);
|
|
card.appendChild(fb);
|
|
|
|
if (match.alternatives && match.alternatives.length > 0) {
|
|
const altSection = document.createElement('div');
|
|
altSection.className = 'alternatives';
|
|
const h3 = document.createElement('h3');
|
|
h3.textContent = 'Alternatives:';
|
|
altSection.appendChild(h3);
|
|
|
|
const altContainer = document.createElement('div');
|
|
altContainer.className = 'alternatives-container';
|
|
match.alternatives.forEach(alt => {
|
|
const altFb = document.createElement('div');
|
|
altFb.className = 'fretboard alternative-fretboard';
|
|
altFb.dataset.fingering = JSON.stringify(alt);
|
|
altContainer.appendChild(altFb);
|
|
});
|
|
altSection.appendChild(altContainer);
|
|
card.appendChild(altSection);
|
|
}
|
|
|
|
container.appendChild(card);
|
|
});
|
|
|
|
renderFretboards();
|
|
applyFilters();
|
|
}
|
|
|
|
function renderFretboards() {
|
|
const fretboards = document.querySelectorAll('.fretboard');
|
|
fretboards.forEach(fb => {
|
|
const fingering = JSON.parse(fb.dataset.fingering);
|
|
const wrapper = document.createElement('div');
|
|
wrapper.className = 'fretboard';
|
|
|
|
fb.innerHTML = '';
|
|
fb.style.display = 'inline-block';
|
|
fb.style.marginBottom = '1rem';
|
|
|
|
const fretCounts = {};
|
|
fingering.forEach(f => {
|
|
if (!isNaN(f)) fretCounts[f] = (fretCounts[f] || 0) + 1;
|
|
});
|
|
|
|
const entries = Object.entries(fretCounts)
|
|
.filter(([, count]) => count >= 2)
|
|
.map(([f]) => parseInt(f));
|
|
|
|
let barreFretNum = null;
|
|
for (const f of entries.sort((a, b) => a - b)) {
|
|
if (fingering.every(x => x === 'x' || isNaN(x) || parseInt(x) >= f)) {
|
|
barreFretNum = f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
const fretMatrix = [];
|
|
for (let s = 0; s < numStrings; s++) {
|
|
const stringRow = [];
|
|
for (let f = 1; f <= maxFret; f++) {
|
|
const fret = document.createElement('div');
|
|
fret.className = 'fret';
|
|
fret.dataset.row = s;
|
|
fret.dataset.col = f;
|
|
|
|
const fretValue = fingering[s];
|
|
const numericFret = parseInt(fretValue, 10);
|
|
if (fretValue === 'x' && f === 1) {
|
|
fret.setAttribute('muted', '');
|
|
fret.textContent = 'x';
|
|
} else if (fretValue !== 'x' && numericFret === f) {
|
|
fret.dataset.dot = 'true';
|
|
if (barreFretNum !== null && numericFret === barreFretNum) {
|
|
fret.classList.add('barre');
|
|
}
|
|
}
|
|
stringRow.push(fret);
|
|
}
|
|
fretMatrix.push(stringRow);
|
|
}
|
|
|
|
const barreCols = [];
|
|
if (barreFretNum !== null) {
|
|
for (let s = 0; s < numStrings; s++) {
|
|
if (parseInt(fingering[s]) === barreFretNum) barreCols.push(s);
|
|
}
|
|
}
|
|
|
|
for (let s = numStrings - 1; s >= 0; s--) {
|
|
const stringRow = document.createElement('div');
|
|
stringRow.className = 'fret-row';
|
|
for (let f = 1; f <= maxFret; f++) {
|
|
stringRow.appendChild(fretMatrix[s][f - 1]);
|
|
}
|
|
wrapper.appendChild(stringRow);
|
|
}
|
|
|
|
fb.appendChild(wrapper);
|
|
|
|
if (barreFretNum !== null && barreCols.length >= 2) {
|
|
const start = Math.min(...barreCols);
|
|
const end = Math.max(...barreCols);
|
|
const line = document.createElement('div');
|
|
line.className = 'barre-line';
|
|
|
|
requestAnimationFrame(() => {
|
|
let totalDotCenter = 0;
|
|
let dotCount = 0;
|
|
|
|
for (let s = start; s <= end; s++) {
|
|
const dotFret = fretMatrix[s][barreFretNum - 1];
|
|
if (dotFret) {
|
|
const rect = dotFret.getBoundingClientRect();
|
|
totalDotCenter += (rect.left + rect.right) / 2;
|
|
dotCount++;
|
|
}
|
|
}
|
|
const avgDotCenter = totalDotCenter / dotCount;
|
|
const parentRect = wrapper.getBoundingClientRect();
|
|
const dotCenter = avgDotCenter - parentRect.left;
|
|
|
|
const firstDot = fretMatrix[start][barreFretNum - 1];
|
|
const lastDot = fretMatrix[end][barreFretNum - 1];
|
|
const rect1 = firstDot.getBoundingClientRect();
|
|
const rect2 = lastDot.getBoundingClientRect();
|
|
|
|
const top = Math.min(rect1.top, rect2.top) - parentRect.top;
|
|
const bottom = Math.max(rect1.bottom, rect2.bottom) - parentRect.top;
|
|
const height = bottom - top;
|
|
|
|
line.style.top = Math.round(top) + 'px';
|
|
line.style.height = Math.round(height) + 'px';
|
|
line.style.left = Math.round(dotCenter) + 'px';
|
|
line.textContent = '|';
|
|
|
|
wrapper.appendChild(line);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
container.addEventListener('click', (e) => {
|
|
const fb = e.target.closest('[data-fingering]');
|
|
if (!fb || !window.playChord || !window.currentTuningMIDI) return;
|
|
const fing = JSON.parse(fb.dataset.fingering);
|
|
const midi = [];
|
|
fing.forEach((f, i) => {
|
|
if (f === 'x') return;
|
|
midi.push(window.currentTuningMIDI[i] + parseInt(f));
|
|
});
|
|
if (midi.length) window.playChord(midi);
|
|
});
|
|
|
|
window.buildChordCards = buildChordCards;
|
|
initFilters();
|
|
})();
|