import { MajorScale, MinorScale, Note, chromaticScale } from "./scale.mjs"; function scaleFrom(tonic, scale) { switch (scale) { case 'major': return MajorScale(tonic); case 'minor': return MinorScale(tonic); default: throw new Error('how this happen') } } function handleNoteEnter(e) { const elt = e.target; const n = Note.fromString(elt.innerText).toString(); // todo: this should be delegated. the key selector shouldn't know // about the fretboard at all. elt.classList.add('hover'); document.querySelectorAll(`#fretboard [x-data-note="${n}"]`).forEach(elt => { elt.classList.add('hover'); }) } function handleNoteLeave(e) { const elt = e.target; const n = Note.fromString(elt.innerText).toString(); // ibid. elt.classList.remove('hover'); document.querySelectorAll(`#fretboard [x-data-note="${n}"]`).forEach(elt => { elt.classList.remove('hover'); }) } function noteClicked(elt, justClear=false) { const n = Note.fromString(elt.innerText).toString(); const count = Number(elt.getAttribute('x-data-clicked')) || 0; const next = (count + 1) % 4; elt.classList.remove(`click${count}`); if (!justClear) { elt.classList.add(`click${next}`); } // ibid. document.querySelectorAll(`#fretboard [x-data-note="${n}"]`).forEach(elt => { elt.classList.remove(`click${count}`); if (!justClear) { elt.classList.add(`click${next}`); } }) if (justClear) { elt.setAttribute('x-data-clicked', '0'); } else { elt.setAttribute('x-data-clicked', next.toString()); } } function handleNoteClick(e) { noteClicked(e.target, false); } function formChanged(form) { // clear the selection first, since it relies on state in the dom. form.querySelectorAll('.notes li').forEach(elt => { noteClicked(elt, true); }); const formData = new FormData(form); const scale = scaleFrom(formData.get('tonic'), formData.get('scale')); ['tonic', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh'].forEach((c, i) => { Array.from(form.getElementsByClassName(c)).forEach(elt => elt.innerText = scale[i]); }); // todo: memoize this or put it in the scales module. const allScales = chromaticScale.flatMap(tonic => { return [MajorScale(tonic), MinorScale(tonic)]; }) const availableScales = allScales.reduce((acc, s, i) => { const suffix = i % 2 == 0 ? '' : 'm'; if (scale.includes(s.tonic) && scale.includes(s.third) && scale.includes(s.fifth)) { return acc.concat(`${s.tonic}${suffix}`); } return acc; }, []); Array.from(form.getElementsByClassName('chords')).forEach(list => { const items = availableScales.map(s => { const elt = document.createElement('li'); elt.innerText = s; return elt; }); list.replaceChildren(); items.forEach(item => list.appendChild(item)); }); } function handleFormChanged(e) { formChanged(e.target.form); } export default function KeyPicker(form) { console.debug('KeyPicker()', form); form.onchange = handleFormChanged; form.querySelectorAll('.notes li').forEach(elt => { elt.onmouseenter = handleNoteEnter; elt.onmouseleave = handleNoteLeave; elt.onclick = handleNoteClick; }); formChanged(form); }