// open string notes, starting from the deepest string. const openStrings = ['E', 'A', 'D', 'G', 'B', 'E']; const stringOctaves = [3, 3, 4, 4, 4, 5]; // convert ‘string1’ ‘fret2’ in ‘form’ to F# export function fretToNote(form, stringName, fretName) { if (!form) { console.error('fretToNote with no form!'); return 'C'; } console.log('lookingforfret', stringName, form, form.querySelector(`.${stringName} .open`)); if (form.querySelector(`.${stringName}.muted`)) { return 'x'; } else if (!fretName?.startsWith('fret')) { return form.querySelector(`.${stringName} .open`).getAttribute("x-data-note"); } else { return form.querySelector(`.${stringName} [value="${fretName}"]`).parentNode.getAttribute("x-data-note"); } } export default class extends HTMLElement { static observedAttributes = ['strings', 'frets']; static name = 'x-fretboard'; static register() { console.debug('Registering Element', this.name, this); customElements.define(this.name, this); } saveEvent = 'x-fretboard-save'; playEvent = 'x-fretboard-play'; stopEvent = 'x-fretboard-stop'; isPlaying = false; constructor() { super(); console.debug('Fretboard#constructor', this); } attributeChangedCallback(name, old, v) { console.debug('Fretboard#attributeChangedCallback', this, name, old, v); this.formChanged(); } connectedCallback() { console.debug('Fretboard#connectedCallback', this); const postEvent = (elt, eventName) => { const res = (e) => { e.preventDefault(); const event = new CustomEvent(eventName, { bubbles: true, cancelable: true, detail: this }); this.dispatchEvent(event); }; return res.bind(this); } this.querySelectorAll('.save').forEach(elt => elt.onclick = postEvent(elt, this.saveEvent)); this.querySelectorAll('.play').forEach(elt => elt.onclick = e => { if (this.isPlaying) { (postEvent(elt, this.stopEvent))(e); elt.innerText = '▶️ play'; this.isPlaying = false; } else { (postEvent(elt, this.playEvent))(e); elt.innerText = '⏹️ stop'; this.isPlaying = true; } }); this.formChanged(); } get notes() { return Array.from(this.querySelectorAll('x-string')).map(elt => { return elt.getAttribute('value'); }); } get octaves() { return Array.from(this.querySelectorAll('x-string')).map(elt => { return Number(elt.getAttribute('octave')); }); } updateSelected(formData) { // this.querySelectorAll('tbody .selected').forEach(elt => { // const string = Array.from(elt.parentNode.classList).filter(x => x.startsWith('string'))[0]; // const val = formData.get(string); // const note = Note.fromString(fretToNote(this, string, val)); // if (note.isSharp) { // elt.innerText = `${note} / ${note.toAlternateString()}`; // } else { // elt.innerText = note; // } // }); } zotStrings() { console.debug('Fretboard#zotStrings', this); const tmpl = this.querySelector('template.string'); console.log(' -- tmpl', tmpl); const parent = tmpl.parentNode; parent.querySelectorAll('[slot="string"]').forEach(elt => { console.debug(' -- del', elt); parent.removeChild(elt); }) } zotForm() { console.debug('Fretboard#zotForm', this); this.zotStrings(); } #strings; #frets; formChanged() { console.debug('Fretboard#formChanged', this); // TODO: this is inelegant. ideally, none of this stuff is // triggered until the form is set, and doing it here is not // robust. const [strings, frets] = [this.getAttribute('strings'), this.getAttribute('frets')].map(s => Number(s)); console.debug(' -- strings', strings, 'frets', frets); if (strings && frets && strings !== this.#strings && frets != this.#frets) { console.debug(' -- rerender table'); this.zotForm(); const tmpl = this.querySelector('template.string'); for (let i = 0; i < strings; i++) { console.debug(' -- appending string', i, tmpl); const item = tmpl.content.cloneNode(true); console.debug(' -- item', item); const tonic = openStrings[i]; const octave = stringOctaves[i]; item.querySelectorAll('[slot="string"]').forEach(s => { console.debug(' -- setting tonic', tonic, 'on', s); s.setAttribute('tonic', tonic); s.setAttribute('octave', octave); s.setAttribute('value', tonic); s.setAttribute('frets', frets.toString()); }) tmpl.parentNode.insertBefore(item, tmpl); } this.#frets = frets; this.#strings = strings; } } }