diff options
| author | Brian Cully <bjc@spork.org> | 2025-07-28 08:52:51 -0400 |
|---|---|---|
| committer | Brian Cully <bjc@spork.org> | 2025-07-28 08:52:51 -0400 |
| commit | 77f5e81d0a1adc497040eaa89095635f3de3924e (patch) | |
| tree | 4ad6a00edf5cfe3c5c2bf0090ba80f8d13c143cc | |
| parent | 2d12e407ccac850dd2e1ce133a2f335677a2ef29 (diff) | |
| download | chords-77f5e81d0a1adc497040eaa89095635f3de3924e.tar.gz chords-77f5e81d0a1adc497040eaa89095635f3de3924e.zip | |
attach octave values to frets
sometimes just do the simple thing
| -rw-r--r-- | fretboard.mjs | 9 | ||||
| -rw-r--r-- | history.mjs | 7 | ||||
| -rw-r--r-- | index.html | 2 | ||||
| -rw-r--r-- | main.mjs | 70 | ||||
| -rw-r--r-- | scale.mjs | 17 | ||||
| -rw-r--r-- | string.mjs | 7 | ||||
| -rw-r--r-- | tests.mjs | 10 |
7 files changed, 79 insertions, 43 deletions
diff --git a/fretboard.mjs b/fretboard.mjs index 9c25ff6..cae94ec 100644 --- a/fretboard.mjs +++ b/fretboard.mjs @@ -82,6 +82,12 @@ 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')); @@ -140,8 +146,9 @@ export default class extends HTMLElement { item.querySelectorAll('[slot="string"]').forEach(s => { console.debug(' -- setting tonic', tonic, 'on', s); s.setAttribute('tonic', tonic); - s.setAttribute('octave', octave); + s.setAttribute('tonic-octave', octave); s.setAttribute('value', tonic); + s.setAttribute('octave', octave); s.setAttribute('frets', frets.toString()); }) tmpl.parentNode.insertBefore(item, tmpl); diff --git a/history.mjs b/history.mjs index b7d49ed..e09073a 100644 --- a/history.mjs +++ b/history.mjs @@ -18,7 +18,10 @@ export default class extends HTMLElement { console.debug('History#constructor', this); this.onClick = e => console.debug('history click', e); + } + connectedCallback() { + console.debug('History#connectedCallback', this); this.template = this.querySelector('template'); this.list = this.querySelector('ol'); @@ -26,10 +29,6 @@ export default class extends HTMLElement { this._internals = this.attachInternals(); } - connectedCallback() { - console.debug('History#connectedCallback', this); - } - add(fretboard) { console.debug('History#add', this, fretboard); this._internals.states.add('fresh'); @@ -10,7 +10,7 @@ <body> <x-fretboard strings='6' frets='7'> <template class='string'> - <x-string tonic='' frets='' slot='string' value=''> + <x-string tonic='' tonic-octave='' frets='' slot='string' value='' octave=''> <form> <input type='checkbox' name='muted'> <li class='fret open'><slot name='open'></slot></li> @@ -5,6 +5,41 @@ import String from './string.mjs'; import Player from './player.mjs'; import { Note, toCents } from './scale.mjs'; +let player = undefined; + +function save(e) { + document.querySelectorAll(History.name).forEach(h => { + console.debug('h is', h, 'e is', e.detail); + h.add(e.detail); + }); +} + +function play(e) { + console.debug('got playEvent', e, e.detail.notes); + if (player) { + player.stop(); + } + + const played = e.detail.notes.map((n, i) => { + if (n !== 'x') { + return [Note.fromString(n), e.detail.octaves[i]]; + } + }).filter(n => n); + + const a = Note.fromString('A'); + player = new Player(played.map(([n, o]) => { + return toCents([a, 4], [n, o]); + })); + player.start(); +} + +function stop(e) { + console.debug('got stopEvent', e); + if (player) { + player.stop(); + } +} + function init() { console.debug('init()', this); @@ -13,40 +48,11 @@ function init() { KeyPicker.register(); History.register(); - let player = undefined; - // todo: maybe just attach the listener to document? document.querySelectorAll(Fretboard.name).forEach(f => { - f.addEventListener(f.saveEvent, e => { - document.querySelectorAll(History.name).forEach(h => { - console.debug('h is', h, 'e is', e.detail); - h.add(e.detail); - }); - }); - - f.addEventListener(f.playEvent, e => { - console.debug('got playEvent', e, e.detail.notes); - const a = Note.fromString('A'); - if (player) { - player.stop(); - } - const played = e.detail.notes.map((n, i) => { - if (n !== 'x') { - return [Note.fromString(n), e.detail.octaves[i]] - } - }).filter(n => n); - player = new Player(played.map(([n, o]) => { - return toCents([a, 4], [n, o]); - })); - player.start(); - }); - - f.addEventListener(f.stopEvent, e => { - console.debug('got stopEvent', e); - if (player) { - player.stop(); - } - }); + f.addEventListener(f.saveEvent, save); + f.addEventListener(f.playEvent, play); + f.addEventListener(f.stopEvent, stop); }); } document.addEventListener('DOMContentLoaded', init); @@ -1,4 +1,4 @@ -function notesBetween(a, b) { +export function naturalNotesBetween(a, b) { const [tonicA, tonicB] = [a[0], b[0]] const abs = tonicB.charCodeAt() - tonicA.charCodeAt(); if (abs < 0) { @@ -8,7 +8,20 @@ function notesBetween(a, b) { } } +export function chromaticNotesBetween(a, b) { + console.log('chromaticNotesBetween', a, b); + const [aNote, bNote] = [a, b].map(x => Note.fromString(x)); + console.log(' -- aNote', aNote); + console.log(' -- bNote', bNote); + const aIndex = Note.noteRange.findIndex(x => aNote.toString() == x.toString()); + const bIndex = Note.noteRange.findIndex(x => bNote.toString() == x.toString()); + console.log(' -- aNote', aIndex); + console.log(' -- bNote', bIndex); + return aIndex - bIndex; +} + export function toCents([aNote, aChord], [bNote, bChord]) { + console.debug('toCents', [aNote, aChord], [bNote, bChord]); console.debug('- a', [aNote, aChord]); console.debug('- b', [bNote, bChord]); const offset = (aNote.distanceTo(bNote)) * 100; @@ -150,7 +163,7 @@ function scaleFromIntervals(tonic, intervals) { const note = chromaticScale[scaleIndex + steps]; // don't display two base notes in a row by changing // accidentals. - const delta = notesBetween(lastBase, note); + const delta = naturalNotesBetween(lastBase, note); if (delta === 0) { const nextBase = chromaticScale[scaleIndex + steps + 1][0]; //console.log('0', 'prev', lastBase, 'next', nextBase, 'note', note) @@ -68,6 +68,7 @@ export default class extends HTMLElement { console.debug('String#fretClicked', this, e); console.debug(' -- note', e.target.note); this.setAttribute('value', e.target.getAttribute('value')); + this.setAttribute('octave', e.target.getAttribute('octave')); } render() { @@ -81,11 +82,13 @@ export default class extends HTMLElement { this.zot(); const chromIndex = chromaticScale.indexOf(this.getAttribute('tonic')); + let octave = this.getAttribute('tonic-octave'); const tmpl = this.querySelector('template'); this.querySelectorAll('slot[name="open"]').forEach(s => { const note = chromaticScale[chromIndex] s.setAttribute('value', note); + s.setAttribute('octave', octave); s.textContent = note; s.parentNode.onclick = this.fretClicked.bind(this); }) @@ -103,6 +106,10 @@ export default class extends HTMLElement { } else { s.checked = false; } + if (value === 'C') { + octave++; + } + s.setAttribute('octave', octave); s.parentNode.onclick = this.fretClicked.bind(this); }) tmpl.parentNode.insertBefore(item, tmpl); @@ -5,12 +5,16 @@ customElements.define('x-string', class extends HTMLElement { constructor() { super(); console.debug('x-string#constructor', this); - const template = this.querySelector('template').content; - const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(template.cloneNode(true)); - console.debug('attached', template, 'to shadow root', shadowRoot); this.muted.onclick = this.muteClicked.bind(this); this.tonic.onclick = this.fretClicked.bind(this); + this.attachShadow({mode: 'open'}); + } + + connectedCallback() { + const template = this.querySelector('template').content; + this.shadowRoot.appendChild(template.cloneNode(true)); + console.debug('attached', template, 'to shadow root', shadowRoot); } get tonic() { |
