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
|
const ringHandler = {
get(target, prop) {
return target[prop % target.length] || Reflect.get(...arguments);
}
};
export 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) {
if (prop in target) {
return target[prop];
} else {
return target.scale[prop] || Reflect.get(...arguments);
}
}
});
}
get root() {
return this.scale[0];
}
get third() {
return this.scale[2];
}
get fifth() {
return this.scale[4];
}
get length() {
console.debug(`get length`, this.scale.length);
return this.scale.length;
}
toString() {
return `[${this.scale.join(", ")}]`;
}
}
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);
}
|