summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--fretboard.mjs172
-rw-r--r--index.html100
-rw-r--r--main.css22
-rw-r--r--main.mjs17
-rw-r--r--scale.mjs2
5 files changed, 123 insertions, 190 deletions
diff --git a/fretboard.mjs b/fretboard.mjs
index ef9e0b5..7ba0dab 100644
--- a/fretboard.mjs
+++ b/fretboard.mjs
@@ -1,22 +1,14 @@
-import { Note } from "./scale.mjs";
-
// open string notes, starting from the deepest string.
-const strings = {
- string1: 'E',
- string2: 'A',
- string3: 'D',
- string4: 'G',
- string5: 'B',
- string6: 'E'
-};
+const openStrings = ['E', 'A', 'D', 'G', 'B', 'E'];
// convert ‘string1’ ‘fret2’ in ‘form’ to F#
export function fretToNote(form, stringName, fretName) {
- const string = strings[stringName];
- if (!string) {
- return null;
+ 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')) {
@@ -26,64 +18,9 @@ export function fretToNote(form, stringName, fretName) {
}
}
-function formChanged(form) {
- const formData = new FormData(form);
- form.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(form, string, val));
- if (note.isSharp) {
- elt.innerText = `${note} / ${note.toAlternateString()}`;
- } else {
- elt.innerText = note;
- }
- });
-}
-
-function handleFormChanged(e) {
- formChanged(e.target.form);
-}
-
-let mousedownVal = undefined;
-
-function fretMousedown(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 fretClick(e) {
- const elt = e.target;
- // if this element was selected at mousedown time, deselect it
- // now.
- if (mousedownVal) {
- elt.checked = false;
- // doesn't get called automatically.
- formChanged(elt.form);
- }
-}
-
-function openClick(e) {
- const elt = e.target.parentNode;
- if (elt.classList.contains('muted')) {
- elt.classList.remove('muted');
- elt.querySelectorAll('input[type="radio"]').forEach(input => {
- input.disabled = false;
- })
- } else {
- elt.classList.add('muted');
- elt.querySelectorAll('input[type="radio"]').forEach(input => {
- input.checked = false;
- input.disabled = true;
- })
- }
- formChanged(elt.closest('form'));
-}
-
export default class extends HTMLElement {
+ static observedAttributes = ['strings', 'frets'];
+
static name = 'x-fretboard';
static register() {
console.debug('Registering Element', this.name, this);
@@ -98,22 +35,15 @@ export default class extends HTMLElement {
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);
-
- // TODO: don't trawl the dom, custom element means we know
- // what's inside.
- this.form = this.querySelector('form');
- this.form.onchange = handleFormChanged;
- this.form.querySelectorAll('.open').forEach(elt => {
- elt.onclick = openClick;
- });
- this.form.querySelectorAll('input[type="radio"]').forEach(elt => {
- elt.onmousedown = fretMousedown;
- elt.onclick = fretClick;
- });
-
- this.form.querySelectorAll('.save').forEach(elt => {
+ this.querySelectorAll('.save').forEach(elt => {
elt.onclick = (e) => {
console.log('Fretboard#onSave');
e.preventDefault();
@@ -125,16 +55,78 @@ export default class extends HTMLElement {
this.dispatchEvent(event);
};
})
- formChanged(this.form);
+ this.formChanged();
}
get notes() {
console.debug('Fretboard#notes', this);
- const formData = new FormData(this.form);
- return Array.from(this.form.querySelectorAll('tbody .selected')).map(elt => {
- const string = Array.from(elt.parentNode.classList).filter(x => x.startsWith('string'))[0];
- const val = formData.get(string);
- return fretToNote(this.form, string, val);
+ return [];
+ // const formData = new FormData(this.form);
+ // return Array.from(this.form.querySelectorAll('tbody .selected')).map(elt => {
+ // const string = Array.from(elt.parentNode.classList).filter(x => x.startsWith('string'))[0];
+ // const val = formData.get(string);
+ // return fretToNote(this.form, string, val);
+ // })
+ }
+
+ 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];
+ item.querySelectorAll('[slot="string"]').forEach(s => {
+ console.debug(' -- setting tonic', tonic, 'on', s);
+ s.setAttribute('tonic', tonic);
+ s.setAttribute('selected', tonic);
+ s.setAttribute('frets', frets.toString());
+ })
+ tmpl.parentNode.insertBefore(item, tmpl);
+ }
+ this.#frets = frets;
+ this.#strings = strings;
+ }
+ }
}
diff --git a/index.html b/index.html
index 72fd0f5..52c6ca8 100644
--- a/index.html
+++ b/index.html
@@ -8,95 +8,19 @@
</head>
<body>
- <x-fretboard>
- <form id='fretboard'>
- <table>
- <thead>
- <tr>
- <td class='open'>open</td>
- <td class='fret1'></td>
- <td class='fret2'></td>
- <td class='fret3'>•</td>
- <td class='fret4'></td>
- <td class='fret5'>••</td>
- <td class='fret6'></td>
- <td class='fret7'>•</td>
- <td class='selected'>selected</td>
- </tr>
- </thead>
+ <x-fretboard strings='6' frets='7'>
+ <template class='string'>
+ <x-string tonic='' frets='' slot='string'>
+ <li><input type='checkbox' name='muted'></li>
+ <li class='fret open'><slot name='open'></slot></li>
+ <template>
+ <li class='fret'><slot name='fret'></slot></li>
+ </template>
+ <li class='selected'><slot name='selected'>selected</slot></li>
+ </x-string>
+ </template>
- <tbody>
- <tr class='string1'>
- <td class='open' x-data-note='E'>E</td>
- <td x-data-note='F'><input type='radio' name='string1' value='fret1'></td>
- <td x-data-note='F♯'><input type='radio' name='string1' value='fret2'></td>
- <td x-data-note='G'><input type='radio' name='string1' value='fret3'></td>
- <td x-data-note='G♯'><input type='radio' name='string1' value='fret4'></td>
- <td x-data-note='A'><input type='radio' name='string1' value='fret5'></td>
- <td x-data-note='A♯'><input type='radio' name='string1' value='fret6'></td>
- <td x-data-note='B'><input type='radio' name='string1' value='fret7'></td>
- <td class='selected'></td>
- </tr>
- <tr class='string2'>
- <td class='open' x-data-note='A'>A</td>
- <td x-data-note='A♯'><input type='radio' name='string2' value='fret1'></td>
- <td x-data-note='B'><input type='radio' name='string2' value='fret2'></td>
- <td x-data-note='C'><input type='radio' name='string2' value='fret3'></td>
- <td x-data-note='C♯'><input type='radio' name='string2' value='fret4'></td>
- <td x-data-note='D'><input type='radio' name='string2' value='fret5'></td>
- <td x-data-note='D♯'><input type='radio' name='string2' value='fret6'></td>
- <td x-data-note='E'><input type='radio' name='string2' value='fret7'></td>
- <td class='selected'></td>
- </tr>
- <tr class='string3'>
- <td class='open' x-data-note='D'>D</td>
- <td x-data-note='D♯'><input type='radio' name='string3' value='fret1'></td>
- <td x-data-note='E'><input type='radio' name='string3' value='fret2'></td>
- <td x-data-note='F'><input type='radio' name='string3' value='fret3'></td>
- <td x-data-note='F♯'><input type='radio' name='string3' value='fret4'></td>
- <td x-data-note='G'><input type='radio' name='string3' value='fret5'></td>
- <td x-data-note='G♯'><input type='radio' name='string3' value='fret6'></td>
- <td x-data-note='A'><input type='radio' name='string3' value='fret7'></td>
- <td class='selected'></td>
- </tr>
- <tr class='string4'>
- <td class='open' x-data-note='G'>G</td>
- <td x-data-note='G♯'><input type='radio' name='string4' value='fret1'></td>
- <td x-data-note='A'><input type='radio' name='string4' value='fret2'></td>
- <td x-data-note='A♯'><input type='radio' name='string4' value='fret3'></td>
- <td x-data-note='B'><input type='radio' name='string4' value='fret4'></td>
- <td x-data-note='C'><input type='radio' name='string4' value='fret5'></td>
- <td x-data-note='C♯'><input type='radio' name='string4' value='fret6'></td>
- <td x-data-note='D'><input type='radio' name='string4' value='fret7'></td>
- <td class='selected'></td>
- </tr>
- <tr class='string5'>
- <td class='open' x-data-note='B'>B</td>
- <td x-data-note='C'><input type='radio' name='string5' value='fret1'></td>
- <td x-data-note='C♯'><input type='radio' name='string5' value='fret2'></td>
- <td x-data-note='D'><input type='radio' name='string5' value='fret3'></td>
- <td x-data-note='D♯'><input type='radio' name='string5' value='fret4'></td>
- <td x-data-note='E'><input type='radio' name='string5' value='fret5'></td>
- <td x-data-note='F'><input type='radio' name='string5' value='fret6'></td>
- <td x-data-note='F♯'><input type='radio' name='string5' value='fret7'></td>
- <td class='selected'></td>
- </tr>
- <tr class='string6'>
- <td class='open' x-data-note='E'>E</td>
- <td x-data-note='F'><input type='radio' name='string6' value='fret1'></td>
- <td x-data-note='F♯'><input type='radio' name='string6' value='fret2'></td>
- <td x-data-note='G'><input type='radio' name='string6' value='fret3'></td>
- <td x-data-note='G♯'><input type='radio' name='string6' value='fret4'></td>
- <td x-data-note='A'><input type='radio' name='string6' value='fret5'></td>
- <td x-data-note='A♯'><input type='radio' name='string6' value='fret6'></td>
- <td x-data-note='B'><input type='radio' name='string6' value='fret7'></td>
- <td class='selected'></td>
- </tr>
- </tbody>
- </table>
-
- <button class='save'>+</button>
- </form>
+ <button class='save'>+</button>
</x-fretboard>
<x-key-picker>
diff --git a/main.css b/main.css
index a2b4deb..f739fba 100644
--- a/main.css
+++ b/main.css
@@ -38,6 +38,24 @@ body {
background-color: var(--fret-bg-color-3);
}
+x-string {
+ display: grid;
+ padding: 5px;
+ cursor: pointer;
+ list-style: none;
+ grid-template-rows: 1ex;
+ /* TODO: this should be put on the element when it's attached. */
+ grid-template-columns: repeat(10, 1fr);
+}
+
+x-string li {
+ text-align: center;
+}
+
+x-string .selected {
+ border-left: 1px solid black;
+}
+
x-fretboard {
display: block;
float: left;
@@ -45,6 +63,10 @@ x-fretboard {
margin-right: 1em;
}
+x-fretboard .save {
+ position: right;
+}
+
x-fretboard table {
border-collapse: collapse;
}
diff --git a/main.mjs b/main.mjs
index 5f49097..4108f5d 100644
--- a/main.mjs
+++ b/main.mjs
@@ -1,20 +1,15 @@
-import Fretboard, { fretToNote } from './fretboard.mjs';
-import KeyPicker from './key-picker.mjs';
+import Fretboard from './fretboard.mjs';
import History from './history.mjs';
-
-function notes(form) {
- const strings = ['string1', 'string2', 'string3', 'string4', 'string5', 'string6'];
- const formData = new FormData(form);
- console.debug('notes', form, formData);
- return strings.map((klass) => fretToNote(form, klass, formData.get(klass)));
-}
+import KeyPicker from './key-picker.mjs';
+import String from './string.mjs';
function init() {
console.debug('init()', this);
+ String.register();
Fretboard.register();
- KeyPicker.register();
- History.register();
+ // KeyPicker.register();
+ // History.register();
// todo: maybe just attach the listener to document?
document.querySelectorAll(Fretboard.name).forEach(f => {
diff --git a/scale.mjs b/scale.mjs
index b364a3e..5b04831 100644
--- a/scale.mjs
+++ b/scale.mjs
@@ -103,7 +103,7 @@ const ringHandler = {
}
};
-const chromaticScale = new Proxy(
+export const chromaticScale = new Proxy(
['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B'],
ringHandler);