diff options
author | Brian Cully <bjc@spork.org> | 2025-03-08 12:05:53 -0500 |
---|---|---|
committer | Brian Cully <bjc@spork.org> | 2025-03-08 12:05:53 -0500 |
commit | ddc2818c6a096db5ff4db91f6538bf829043e563 (patch) | |
tree | 7db75e099280a9a86ce86337a703dcc974847e86 | |
parent | daeace070ee8687fceb77ee5e9f7bce507669639 (diff) | |
download | chords-ddc2818c6a096db5ff4db91f6538bf829043e563.tar.gz chords-ddc2818c6a096db5ff4db91f6538bf829043e563.zip |
js: add bare bones js stuff
-rw-r--r-- | fretboard.mjs | 32 | ||||
-rw-r--r-- | main.mjs | 13 | ||||
-rw-r--r-- | scale.mjs | 75 |
3 files changed, 120 insertions, 0 deletions
diff --git a/fretboard.mjs b/fretboard.mjs new file mode 100644 index 0000000..c58543c --- /dev/null +++ b/fretboard.mjs @@ -0,0 +1,32 @@ +function fretChanged(e) { + console.log('fret changed', e); +} + +let mousedownVal = undefined; + +function mousedown(e) { + // at mousedown time, the form hasn't yet been changed. to support + // deselecting elements, we need to know what the previous + // selection was, so store it here. + // + // it'd be nice to be able to do this just in the click handler. + mousedownVal = e.target.checked; +} + +function click(e) { + const elt = e.target; + // if this element was selected at mousedown time, deselect it + // now. + if (mousedownVal) { + elt.checked = false; + } +} + +export default function Fretboard(form) { + console.debug('Fretboard()', form); + form.onchange = fretChanged; + form.querySelectorAll('input[type="radio"]').forEach(elt => { + elt.onmousedown = mousedown; + elt.onclick = click; + }); +} diff --git a/main.mjs b/main.mjs new file mode 100644 index 0000000..97ba06f --- /dev/null +++ b/main.mjs @@ -0,0 +1,13 @@ +import Fretboard from './fretboard.mjs' +import { MajorScale, MinorScale } from './scale.mjs'; + +Fretboard(document.getElementById('fretboard')); +const cMaj = MajorScale('C'); +console.log('test major scale', cMaj.length); +console.log(`test fifth ${cMaj.root}`, cMaj.fifth); +const cMin = MinorScale('C'); +console.log('test major scale', cMin.length); +console.log(`test fifth ${cMin.root}`, cMin.fifth); +const bMaj = MajorScale('B'); +console.log('test major scale', bMaj.length); +console.log(`test fifth ${bMaj.root}`, bMaj.fifth); diff --git a/scale.mjs b/scale.mjs new file mode 100644 index 0000000..61319df --- /dev/null +++ b/scale.mjs @@ -0,0 +1,75 @@ +const ringHandler = { + get(target, prop) { + return target[prop % target.length] || Reflect.get(...arguments); + } +}; + +const chromaticScale = new Proxy( + ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'], + ringHandler); + +function scaleFromIntervals(root, intervals) { + const scaleIndex = chromaticScale.indexOf(root); + if (scaleIndex < 0) { + return undefined; + } + + let scale = [root]; + let steps = 0; + let lastBase = root[0]; + for (let i = 0; i < intervals.length; i++) { + steps += intervals[i]; + const note = chromaticScale[scaleIndex + steps]; + // don't display two base notes in a row by changing + // accidentals. + if (note[0] === lastBase) { + const nextBase = chromaticScale[scaleIndex + steps + 1][0]; + scale.push(`${nextBase}b`) + lastBase = nextBase[0]; + } else { + scale.push(note); + lastBase = note[0]; + } + } + return new Proxy(scale, ringHandler); +} + +class Scale { + constructor(root, intervals) { + this.scale = scaleFromIntervals(root, intervals); + return new Proxy(this, { + get(target, prop) { + return target.scale[prop] || Reflect.get(...arguments); + } + }); + } + + get root() { + return this[0]; + } + + get third() { + return this[2]; + } + + get fifth() { + return this[4]; + } + + get length() { + console.debug(`get length`, this.scale.length); + return this.intervals.length; + } +} + +export function MajorScale(root) { + console.debug(`MajorScale(${root})`); + const intervals = [2, 2, 1, 2, 2, 2]; + return new Scale(root, intervals); +} + +export function MinorScale(root) { + console.debug(`MinorScale(${root})`); + const intervals = [2, 1, 2, 2, 1, 2]; + return new Scale(root, intervals); +} |