diff options
| author | Brian Cully <bjc@spork.org> | 2025-07-28 16:29:50 -0400 |
|---|---|---|
| committer | Brian Cully <bjc@spork.org> | 2025-07-28 16:29:50 -0400 |
| commit | 21e0744d3d21ecae21e61718de4ac353ce19c28a (patch) | |
| tree | ed876f0c4a1f93b5e0f8b2b29b516bbfc489fba9 | |
| parent | 9620d3750c54b21c3da0460cea117b57fbeb9eac (diff) | |
| download | chords-21e0744d3d21ecae21e61718de4ac353ce19c28a.tar.gz chords-21e0744d3d21ecae21e61718de4ac353ce19c28a.zip | |
add realtime note selection
| -rw-r--r-- | fretboard.mjs | 33 | ||||
| -rw-r--r-- | main.mjs | 20 | ||||
| -rw-r--r-- | player.mjs | 18 | ||||
| -rw-r--r-- | string.mjs | 17 |
4 files changed, 60 insertions, 28 deletions
diff --git a/fretboard.mjs b/fretboard.mjs index d3671f0..d9be758 100644 --- a/fretboard.mjs +++ b/fretboard.mjs @@ -29,6 +29,7 @@ export default class extends HTMLElement { } saveEvent = 'x-fretboard-save'; + changedEvent = 'x-fretboard-changed'; playEvent = 'x-fretboard-play'; stopEvent = 'x-fretboard-stop'; @@ -74,6 +75,7 @@ export default class extends HTMLElement { } }); this.formChanged(); + this.postChangedEvent(); } get notes() { @@ -82,12 +84,6 @@ export default class extends HTMLElement { }); } - get tonics() { - return Array.from(this.querySelectorAll('x-string')).map(elt => { - return elt.getAttribute('tonic'); - }); - } - get octaves() { return Array.from(this.querySelectorAll('x-string')).map(elt => { return Number(elt.getAttribute('octave')); @@ -95,12 +91,9 @@ export default class extends HTMLElement { } 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); }) } @@ -110,6 +103,21 @@ export default class extends HTMLElement { this.zotStrings(); } + postChangedEvent() { + console.debug('Fretboard#postChangedEvent', this, this.changedEvent); + const event = new CustomEvent(this.changedEvent, { + bubbles: true, + cancelable: true, + detail: this + }); + this.dispatchEvent(event); + } + + stringChanged(e) { + console.debug('Fretboard#stringChanged', this, e); + this.postChangedEvent(); + } + #strings; #frets; formChanged() { console.debug('Fretboard#formChanged', this); @@ -118,20 +126,16 @@ export default class extends HTMLElement { // 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('tonic-octave', octave); s.setAttribute('value', tonic); @@ -140,6 +144,9 @@ export default class extends HTMLElement { }) tmpl.parentNode.insertBefore(item, tmpl); } + this.querySelectorAll('[slot="string"]').forEach(s => { + s.addEventListener(s.changedEvent, this.stringChanged.bind(this)); + }); this.#frets = frets; this.#strings = strings; } @@ -16,12 +16,8 @@ function save({ detail }) { }); } -function play({ detail }) { - console.debug('got playEvent', detail.notes); - if (player) { - player.stop(); - } - +function changed({ detail }) { + console.debug('got changedEvent', detail.notes); const played = detail.notes.map((n, i) => { if (n !== 'x') { return [Note.fromString(n), detail.octaves[i]]; @@ -33,7 +29,14 @@ function play({ detail }) { } player = document.querySelector('x-player'); player.getOffsets = getOffsets; - player.isPlaying = true; + player.update(); +} + +function play({ detail }) { + console.debug('got playEvent', detail.notes); + if (player) { + player.isPlaying = true; + } } function stop(e) { @@ -69,8 +72,11 @@ function init() { // todo: maybe just attach the listener to document? document.querySelectorAll(Fretboard.name).forEach(f => { f.addEventListener(f.saveEvent, save); + f.addEventListener(f.changedEvent, changed); f.addEventListener(f.playEvent, play); f.addEventListener(f.stopEvent, stop); + + changed({ detail: f }); }); document.querySelectorAll(KeyPicker.name).forEach(kp => { @@ -73,8 +73,7 @@ export default class extends HTMLElement { } } - start() { - console.debug('Player#start', this); + update() { for (let i = 0; i < this.offsets.length; i++) { if (!this.notes[i]) { const note = new OscillatorNode(audioCtx, { @@ -83,21 +82,30 @@ export default class extends HTMLElement { periodicWave: tromboneWave, }); note.connect(this.globalGain); - note.start(); + if (this.isPlaying) { + note.start(); + } this.notes[i] = note; } const note = this.notes[i]; note.detune.setValueAtTime(this.offsets[i], audioCtx.currentTime); } for (let j = this.notes.length-1; j >= this.offsets.length; j--) { - this.notes[j].stop(); + const note = this.notes[j]; + if (note && this.isPlaying) { + note.stop(); + } delete this.notes[j]; } } + start() { + console.debug('Player#start', this); + this.notes.forEach(n => n.start()); + } + stop() { console.debug('Player#stop', this); this.notes.forEach(n => n.stop()); - this.notes = []; } } @@ -23,6 +23,8 @@ export default class extends HTMLElement { customElements.define(this.name, this); } + changedEvent = 'x-string-changed'; + // TODO: this is a horrible hack, but i don't know how to make // each string's input unique. maybe wrap them in their own form? myName() { @@ -43,10 +45,7 @@ export default class extends HTMLElement { zot() { console.debug('String#zot', this); - const tmpl = this.querySelector('template'); - console.log(' -- tmpl', tmpl); this.querySelectorAll('input[slot="fret"]').forEach(elt => { - console.debug(' -- del', elt, parent); // TODO: this is precisely what i was trying to avoid: the // code has to know exactly what the template looks like. elt.parentNode.parentNode.removeChild(elt.parentNode); @@ -66,6 +65,16 @@ export default class extends HTMLElement { }); } + postChangedEvent() { + console.debug('String#postChangedEvent', this); + const event = new CustomEvent(this.changedEvent, { + bubbles: true, + cancelable: true, + detail: this + }); + this.dispatchEvent(event); + } + muteClicked(e) { console.debug('String#muteClicked', this, e); if (this.getAttribute('value') !== 'x') { @@ -77,6 +86,7 @@ export default class extends HTMLElement { this.classList.remove('muted'); } + this.postChangedEvent(); this.updateSelected(); } @@ -107,6 +117,7 @@ export default class extends HTMLElement { } this.setAttribute('value', v); this.setAttribute('octave', o); + this.postChangedEvent(); this.updateSelected(); } |
