summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian Cully <bjc@spork.org>2025-07-28 16:29:50 -0400
committerBrian Cully <bjc@spork.org>2025-07-28 16:29:50 -0400
commit21e0744d3d21ecae21e61718de4ac353ce19c28a (patch)
treeed876f0c4a1f93b5e0f8b2b29b516bbfc489fba9
parent9620d3750c54b21c3da0460cea117b57fbeb9eac (diff)
downloadchords-21e0744d3d21ecae21e61718de4ac353ce19c28a.tar.gz
chords-21e0744d3d21ecae21e61718de4ac353ce19c28a.zip
add realtime note selection
-rw-r--r--fretboard.mjs33
-rw-r--r--main.mjs20
-rw-r--r--player.mjs18
-rw-r--r--string.mjs17
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;
}
diff --git a/main.mjs b/main.mjs
index ad45d90..bee25cb 100644
--- a/main.mjs
+++ b/main.mjs
@@ -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 => {
diff --git a/player.mjs b/player.mjs
index c8707b2..02feb6c 100644
--- a/player.mjs
+++ b/player.mjs
@@ -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 = [];
}
}
diff --git a/string.mjs b/string.mjs
index 2e496be..7bbfd69 100644
--- a/string.mjs
+++ b/string.mjs
@@ -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();
}