1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
import { AugmentedScale, DiminishedScale, MajorScale, MinorScale, Note, allDiatonicScales } from "./scale.mjs";
function scaleFrom(tonic, scale) {
switch (scale) {
case 'major':
return MajorScale(tonic);
case 'minor':
return MinorScale(tonic);
case 'aug':
return AugmentedScale(tonic);
case 'dim':
return DiminishedScale(tonic);
default:
throw new Error('how this happen')
}
}
function handleNoteEnter(e) {
const elt = e.target;
const n = Note.fromString(elt.textContent).toString();
// todo: this should be delegated. the key selector shouldn't know
// about the fretboard at all.
elt.classList.add('hover');
document.querySelectorAll(`#fretboard [x-data-note="${n}"]`).forEach(elt => {
elt.classList.add('hover');
})
}
function handleNoteLeave(e) {
const elt = e.target;
const n = Note.fromString(elt.textContent).toString();
// ibid.
elt.classList.remove('hover');
document.querySelectorAll(`#fretboard [x-data-note="${n}"]`).forEach(elt => {
elt.classList.remove('hover');
})
}
function noteClicked(elt, justClear=false) {
const n = Note.fromString(elt.textContent).toString();
const count = Number(elt.getAttribute('x-data-clicked')) || 0;
const next = (count + 1) % 4;
elt.classList.remove(`click${count}`);
if (!justClear) {
elt.classList.add(`click${next}`);
}
// ibid.
document.querySelectorAll(`#fretboard [x-data-note="${n}"]`).forEach(elt => {
elt.classList.remove(`click${count}`);
if (!justClear) {
elt.classList.add(`click${next}`);
}
})
if (justClear) {
elt.setAttribute('x-data-clicked', '0');
} else {
elt.setAttribute('x-data-clicked', next.toString());
}
}
function handleNoteClick(e) {
noteClicked(e.target, false);
}
function formChanged(form) {
// clear the selection first, since it relies on state in the dom.
form.querySelectorAll('.notes li').forEach(elt => {
noteClicked(elt, true);
});
const formData = new FormData(form);
const scale = scaleFrom(formData.get('tonic'), formData.get('scale'));
['tonic', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh'].forEach((c, i) => {
Array.from(form.getElementsByClassName(c)).forEach(elt =>
elt.textContent = scale[i]);
});
function suffixFromIndex(i) {
switch (i % 2) {
case 0:
return '';
case 1:
return 'm';
}
}
const availableScales =
allDiatonicScales.reduce((acc, s, i) => {
const suffix = suffixFromIndex(i);
if (scale.includes(s.tonic) && scale.includes(s.third) && scale.includes(s.fifth)) {
return acc.concat(`${s.tonic}${suffix}`);
}
return acc;
}, []);
Array.from(form.getElementsByClassName('chords')).forEach(list => {
const items = availableScales.map(s => {
const elt = document.createElement('li');
elt.textContent = s;
return elt;
});
list.replaceChildren();
items.forEach(item => list.appendChild(item));
});
}
function handleFormChanged(e) {
formChanged(e.target.form);
}
export default class extends HTMLElement {
static name = 'x-key-picker'
static register() {
console.debug('Registering Element', this.name, this);
customElements.define(this.name, this);
}
constructor() {
super();
console.debug('KeyPicker#constructor', this);
}
connectedCallback() {
console.debug('KeyPicker#connectedCallback', this);
// TODO: don't trawl the dom, custom element means we know
// what's inside.
const form = this.querySelector('form');
form.onchange = handleFormChanged;
form.querySelectorAll('.notes li').forEach(elt => {
elt.onmouseenter = handleNoteEnter;
elt.onmouseleave = handleNoteLeave;
elt.onclick = handleNoteClick;
});
formChanged(form);
console.log('bjc', 'Cdim', DiminishedScale('C').toString());
}
}
|