discombobulator 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +16 -0
- package/src/articulation.mjs +61 -0
- package/src/articulationset.mjs +20 -0
- package/src/chord.mjs +82 -0
- package/src/cross.mjs +133 -0
- package/src/disco.mjs +14 -0
- package/src/element.mjs +51 -0
- package/src/group.mjs +70 -0
- package/src/groupset.mjs +259 -0
- package/src/instrument.mjs +105 -0
- package/src/instrumentset.mjs +20 -0
- package/src/irregulargroupset.mjs +224 -0
- package/src/note.mjs +155 -0
- package/src/noteset.mjs +21 -0
- package/src/pattern.mjs +52 -0
- package/src/patternset.mjs +20 -0
- package/src/patternsource.mjs +21 -0
- package/src/permute.mjs +63 -0
- package/src/regulargroupset.mjs +120 -0
- package/src/schedule.mjs +88 -0
- package/src/schema.mjs +108 -0
- package/src/set.mjs +1159 -0
- package/src/test/articulation.mjs +66 -0
- package/src/test/articulationset.mjs +43 -0
- package/src/test/binaryoperator.mjs +210 -0
- package/src/test/chord.mjs +292 -0
- package/src/test/concolour.mjs +70 -0
- package/src/test/difference.mjs +492 -0
- package/src/test/element.mjs +66 -0
- package/src/test/group.mjs +79 -0
- package/src/test/groupset.mjs +43 -0
- package/src/test/instrument.mjs +66 -0
- package/src/test/instrumentset.mjs +43 -0
- package/src/test/intersection.mjs +493 -0
- package/src/test/irregulargroupset.mjs +461 -0
- package/src/test/note.mjs +367 -0
- package/src/test/noteset.mjs +43 -0
- package/src/test/operator.mjs +63 -0
- package/src/test/pattern.mjs +113 -0
- package/src/test/patternset.mjs +43 -0
- package/src/test/patternsource.mjs +43 -0
- package/src/test/regulargroupset.mjs +290 -0
- package/src/test/repl.mjs +63 -0
- package/src/test/replinit.mjs +134 -0
- package/src/test/rotation.mjs +753 -0
- package/src/test/schedule.mjs +275 -0
- package/src/test/schema.mjs +333 -0
- package/src/test/set.mjs +1081 -0
- package/src/test/symmetricdifference.mjs +495 -0
- package/src/test/test.mjs +208 -0
- package/src/test/testing.mjs +63 -0
- package/src/test/union.mjs +495 -0
- package/src/util.mjs +21 -0
package/src/note.mjs
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Note.
|
|
2
|
+
//
|
|
3
|
+
// A note is a tuple of an instrument and a set of articulations that affect how it's played.
|
|
4
|
+
|
|
5
|
+
import util from "util";
|
|
6
|
+
|
|
7
|
+
import { Element } from "./element.mjs";
|
|
8
|
+
import { Instrument } from "./instrument.mjs";
|
|
9
|
+
import { Articulation } from "./articulation.mjs";
|
|
10
|
+
import { ArticulationSet } from "./articulationset.mjs";
|
|
11
|
+
|
|
12
|
+
export class Note extends Element {
|
|
13
|
+
#instrument = undefined;
|
|
14
|
+
#articulations = undefined;
|
|
15
|
+
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
options = { add: [ undefined, undefined ], ...options };
|
|
18
|
+
const add = options.add;
|
|
19
|
+
const instrument = Array.isArray(add) ? (add.length >= 1 ? add[0] : undefined) : add;
|
|
20
|
+
let articulations = Array.isArray(add) && add.length > 1 ? add[1] : undefined;
|
|
21
|
+
|
|
22
|
+
if (!(instrument === undefined || instrument instanceof Instrument)) {
|
|
23
|
+
throw new Error("InvalidArgumentExpression: instrument must be undefined or a valid Instrument");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
!(articulations === undefined || articulations instanceof ArticulationSet)
|
|
28
|
+
) {
|
|
29
|
+
// As a last resort, see if an ArticulationSet can be constructed by promoting whatever was passed.
|
|
30
|
+
articulations = new ArticulationSet({ add: articulations });
|
|
31
|
+
|
|
32
|
+
if (articulations === undefined) {
|
|
33
|
+
throw new Error("InvalidArgumentExpression: articulations must be undefined or a valid Articulation or ArticulationSet");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
super();
|
|
38
|
+
|
|
39
|
+
this.#instrument = instrument;
|
|
40
|
+
this.#articulations = articulations;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Return a clone of this note.
|
|
44
|
+
//
|
|
45
|
+
// If deep is true, the notes articulation set is also cloned, if it exists,
|
|
46
|
+
// otherwise the articulation set is copied by reference.
|
|
47
|
+
clone(deep = false) {
|
|
48
|
+
let articulations = this.#articulations;
|
|
49
|
+
|
|
50
|
+
if (deep && !!articulations && articulations.clone !== undefined) {
|
|
51
|
+
articulations = articulations.clone(deep);
|
|
52
|
+
}
|
|
53
|
+
return new this.constructor({ add: [ this.#instrument, articulations ]});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get [Symbol.toStringTag]() {
|
|
57
|
+
return "Note";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Pseudo-implementation to allow Note to act like a set of two elements.
|
|
61
|
+
elementAt(index = 0) {
|
|
62
|
+
if (index === 0) return this.#instrument;
|
|
63
|
+
if (index === 1) return this.#articulations;
|
|
64
|
+
|
|
65
|
+
throw new Error("Invalid index");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
promote(element) {
|
|
69
|
+
if (this.constructor === element.constructor) {
|
|
70
|
+
return element;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (element instanceof Instrument) {
|
|
74
|
+
this.#instrument = element;
|
|
75
|
+
this.#articulations = undefined;
|
|
76
|
+
} else if (element instanceof ArticulationSet) {
|
|
77
|
+
this.#instrument = undefined;
|
|
78
|
+
this.#articulations = element;
|
|
79
|
+
} else if (element instanceof Articulation) {
|
|
80
|
+
this.#instrument = undefined;
|
|
81
|
+
this.#articulations = new ArticulationSet();
|
|
82
|
+
this.articulations.add(element);
|
|
83
|
+
} else {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
[util.inspect.custom](depth, opts) {
|
|
91
|
+
return `${this[Symbol.toStringTag]} {${
|
|
92
|
+
!!this.#instrument ? util.inspect(this.#instrument) : "undefined"
|
|
93
|
+
}, {${
|
|
94
|
+
!!this.#articulations ? util.inspect(this.#articulations) : "undefined"
|
|
95
|
+
}}}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get instrument() {
|
|
99
|
+
return this.#instrument;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get articulations() {
|
|
103
|
+
return this.#articulations;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get isRest() {
|
|
107
|
+
return this.instrument === undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Equality operator overide.
|
|
111
|
+
//
|
|
112
|
+
// Two notes are equal if they have the same instrument and, if strict is true, the same
|
|
113
|
+
// articulations. If strict is false the articulation set is ignored.
|
|
114
|
+
//
|
|
115
|
+
// Note: When a Note is added to a Chord that contains a Note with the same instrument
|
|
116
|
+
// (ie. equal Notes), the note already in the Chord has its articulation set changed to be
|
|
117
|
+
// the union of its existing articulation set and the articulation set of the other Note.
|
|
118
|
+
isEqual(other, strict = false) {
|
|
119
|
+
const typesMatch = super.isEqual(other);
|
|
120
|
+
|
|
121
|
+
const bothAreRests = this.isRest && other.isRest;
|
|
122
|
+
const sameInstruments = !this.isRest && this.instrument.isEqual(other.instrument);
|
|
123
|
+
|
|
124
|
+
const instrumentsMatch = bothAreRests || sameInstruments;
|
|
125
|
+
|
|
126
|
+
const neitherHasArticulations = this.ariticulations === undefined && other.articulations === undefined;
|
|
127
|
+
const sameArticulations = this.articulations !== undefined && this.articulations.isEqual(other.articulations);
|
|
128
|
+
|
|
129
|
+
const articulationsMatch = neitherHasArticulations || sameArticulations;
|
|
130
|
+
|
|
131
|
+
let result = typesMatch && instrumentsMatch && (!strict || articulationsMatch);
|
|
132
|
+
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Merge two Notes. Note identity is determined by the Instrument. So an exception is thrown
|
|
137
|
+
// if the other Note does not have the same Instrument as this one. Merging makes the ArticulationSet
|
|
138
|
+
// of this Note be the union of it's existing ArticulationSet and that of the other Note.
|
|
139
|
+
merge(other) {
|
|
140
|
+
if (!(other instanceof Note)) {
|
|
141
|
+
throw new Error("A note can only be merged with another note");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (this.#instrument !== other.#instrument)
|
|
145
|
+
throw new Error("Attempt to merge Notes with differing instruments");
|
|
146
|
+
|
|
147
|
+
if (this.#articulations === undefined) {
|
|
148
|
+
this.#articulations = other.#articulations;
|
|
149
|
+
} else if (other.articulations !== undefined) {
|
|
150
|
+
this.#articulations = this.#articulations.union(other.#articulations);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
}
|
package/src/noteset.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// NoteSet.
|
|
2
|
+
//
|
|
3
|
+
// A set of notes. Unlike a Chord, which merges equivalent notes,
|
|
4
|
+
// a note set behaves like a normal set and treats notes as independent.
|
|
5
|
+
|
|
6
|
+
import { PatternSource } from "./patternsource.mjs";
|
|
7
|
+
import { Chord } from "./chord.mjs";
|
|
8
|
+
|
|
9
|
+
export class NoteSet extends PatternSource {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
super(options);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get [Symbol.toStringTag]() {
|
|
15
|
+
return "NoteSet";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
toChord() {
|
|
19
|
+
return new Chord({add: this});
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/pattern.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Pattern.
|
|
2
|
+
//
|
|
3
|
+
// A pattern is a sequence of chords.
|
|
4
|
+
|
|
5
|
+
import { Set } from "./set.mjs";
|
|
6
|
+
import { PatternSource } from "./patternsource.mjs";
|
|
7
|
+
|
|
8
|
+
export class Pattern extends Set {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
super({ allowDuplicates: true, ...options });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get allowedElements() {
|
|
14
|
+
return [PatternSource];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get [Symbol.toStringTag]() {
|
|
18
|
+
return "Pattern";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Equality operator overide.
|
|
22
|
+
//
|
|
23
|
+
// Two Patterns are equal if they contain all of the same Chords in the same order.
|
|
24
|
+
isEqual(other) {
|
|
25
|
+
if (!super.isEqual(other)) return false;
|
|
26
|
+
|
|
27
|
+
if (this.size != other.size) return false;
|
|
28
|
+
|
|
29
|
+
let a = this[Symbol.iterator]();
|
|
30
|
+
let b = other[Symbol.iterator]();
|
|
31
|
+
|
|
32
|
+
let element = a.next();
|
|
33
|
+
|
|
34
|
+
while (!element.done) {
|
|
35
|
+
if (!element.value.isEqual(b.next().value)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
element = a.next();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// rotate(options = {}) {
|
|
46
|
+
// return new Set.Rotation({
|
|
47
|
+
// ...options,
|
|
48
|
+
// setClass: this.constructor,
|
|
49
|
+
// set: this,
|
|
50
|
+
// });
|
|
51
|
+
// }
|
|
52
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// PatternSet.
|
|
2
|
+
//
|
|
3
|
+
// A pattern set is a set of patterns. Duh!
|
|
4
|
+
|
|
5
|
+
import { Set } from "./set.mjs";
|
|
6
|
+
import { Pattern } from "./pattern.mjs";
|
|
7
|
+
|
|
8
|
+
export class PatternSet extends Set {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
super(options);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get allowedElements() {
|
|
14
|
+
return [Pattern];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get [Symbol.toStringTag]() {
|
|
18
|
+
return "PatternSet";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// PatternSource.
|
|
2
|
+
//
|
|
3
|
+
// Base class for set classes that may be used as sources to a Pattern.
|
|
4
|
+
// Specifically, NoteSet and Chord are pattern sources.
|
|
5
|
+
|
|
6
|
+
import { Set } from "./set.mjs";
|
|
7
|
+
import { Note } from "./note.mjs";
|
|
8
|
+
|
|
9
|
+
export class PatternSource extends Set {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
super(options);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get allowedElements() {
|
|
15
|
+
return [ Note ];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get [Symbol.toStringTag]() {
|
|
19
|
+
return "PatternSource";
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/permute.mjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Permutation helper functions.
|
|
2
|
+
|
|
3
|
+
import { factorial } from './util.mjs'
|
|
4
|
+
|
|
5
|
+
function productRange(a, b) {
|
|
6
|
+
var prd = a;
|
|
7
|
+
var i = a;
|
|
8
|
+
|
|
9
|
+
while (i++ < b) {
|
|
10
|
+
prd *= i;
|
|
11
|
+
}
|
|
12
|
+
return prd;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function combinations(n, r) {
|
|
16
|
+
if (n == r || r == 0) {
|
|
17
|
+
return 1;
|
|
18
|
+
} else {
|
|
19
|
+
r = r < n - r ? n - r : r;
|
|
20
|
+
return productRange(r + 1, n) / productRange(1, n - r);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function permutations(n, r) {
|
|
25
|
+
return factorial(n) / factorial(n -r );
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function ithBinaryCombination(n, r, i) {
|
|
29
|
+
const mask = 1 << n - 1;
|
|
30
|
+
|
|
31
|
+
let ibc = 0;
|
|
32
|
+
let ones = combinations(n, r);
|
|
33
|
+
|
|
34
|
+
if (0 > i || i >= ones) return undefined;
|
|
35
|
+
|
|
36
|
+
while (n-- > 0) {
|
|
37
|
+
ibc >>= 1;
|
|
38
|
+
ones = combinations(n, r-1);
|
|
39
|
+
|
|
40
|
+
if (r > 0 && i < ones) {
|
|
41
|
+
ibc |= mask;
|
|
42
|
+
r--;
|
|
43
|
+
} else {
|
|
44
|
+
i -= ones;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return ibc;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Generator function to generate the permutations of r indexes where
|
|
52
|
+
// each index value must be in the range [ 0, n - 1 ].
|
|
53
|
+
export function* permutationIndexGenerator(r = 1, n = 1, allowRepeats = true, indices = []) {
|
|
54
|
+
for (let index = 0; index < n; ++index) {
|
|
55
|
+
if (allowRepeats || !indices.includes(index)) {
|
|
56
|
+
if (indices.length + 1 < r) {
|
|
57
|
+
yield* permutationIndexGenerator(r, n, allowRepeats, [ ...indices, index ]);
|
|
58
|
+
} else {
|
|
59
|
+
yield [ ...indices, index ];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// RegularGroupGenerator is a Group generator that generates fully active
|
|
2
|
+
// groups in a range determined by boundaries and a step rate.
|
|
3
|
+
|
|
4
|
+
import util from "util";
|
|
5
|
+
|
|
6
|
+
import { bitCount } from "./util.mjs";
|
|
7
|
+
import { Group } from "./group.mjs";
|
|
8
|
+
import { GroupSet } from "./groupset.mjs";
|
|
9
|
+
|
|
10
|
+
export class RegularGroupSet extends GroupSet {
|
|
11
|
+
#start = 1;
|
|
12
|
+
#end = 1;
|
|
13
|
+
#step = 1;
|
|
14
|
+
|
|
15
|
+
// Construct a RegularGroupSet with args:
|
|
16
|
+
// start The minimum number of chords in the generated groups. Default is 1.
|
|
17
|
+
// end The maximum number of chords in the generated groups. Must be >= start.
|
|
18
|
+
// If undefined then uses start. Default is undefined.
|
|
19
|
+
// step Step rate to increase group size by. Must be > 0. Default is 1.
|
|
20
|
+
constructor(options) {
|
|
21
|
+
super({ isAbstract: true });
|
|
22
|
+
|
|
23
|
+
const defaults = { start: 1, end: undefined, step: 1 };
|
|
24
|
+
options = { ...defaults, ...options };
|
|
25
|
+
let { start, end, step } = options;
|
|
26
|
+
|
|
27
|
+
if (end === undefined) end = start;
|
|
28
|
+
|
|
29
|
+
if (1 > start || start > Group.MAX_LENGTH)
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Invalid group minimum length - must be from 1 to ${Group.MAX_LENGTH}`
|
|
32
|
+
);
|
|
33
|
+
if (1 > end || end > Group.MAX_LENGTH)
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Invalid group maximum length - must be from 1 to ${Group.MAX_LENGTH}`
|
|
36
|
+
);
|
|
37
|
+
if (end < start)
|
|
38
|
+
throw new Error(
|
|
39
|
+
"Invalid group maximum length - can't be less than the minimum length"
|
|
40
|
+
);
|
|
41
|
+
if (1 > step || step > Group.MAX_LENGTH)
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Invalid step size - must be from 1 to ${Group.MAX_LENGTH - 1}`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
this.#start = start;
|
|
47
|
+
this.#end = end;
|
|
48
|
+
this.#step = step;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Overide Set.clone.
|
|
52
|
+
clone() {
|
|
53
|
+
let clone = new this.constructor(
|
|
54
|
+
{
|
|
55
|
+
start: this.#start,
|
|
56
|
+
end: this.#end,
|
|
57
|
+
step: this.#step
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return clone;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get start() {
|
|
65
|
+
return this.#start;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get end() {
|
|
69
|
+
return this.#end;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get step() {
|
|
73
|
+
return this.#step;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get [Symbol.toStringTag]() {
|
|
77
|
+
return "RegularGroupSet";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
[util.inspect.custom](depth, opts) {
|
|
81
|
+
let groups = "";
|
|
82
|
+
|
|
83
|
+
for (const group of this) {
|
|
84
|
+
groups += (groups.length == 0 ? "" : ", ") + util.inspect(group);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return `${this[Symbol.toStringTag]} {[${groups}]}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
*[Symbol.iterator]() {
|
|
91
|
+
for (let length = this.#start; length <= this.#end; length += this.#step) {
|
|
92
|
+
yield new Group(0xffffffff, length);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
elementAt(index = 0) {
|
|
97
|
+
if (index >= 0 && index < this.size) {
|
|
98
|
+
return new Group(0xffffffff, this.#start + this.#step * index);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error("Invalid index");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get size() {
|
|
105
|
+
return Math.floor((this.end - this.start) / this.step + 1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Returns whether the given element exists in this Set.
|
|
109
|
+
has(element) {
|
|
110
|
+
if (element === undefined || !(element instanceof Group)) {
|
|
111
|
+
throw new Error("Argument must be a Group");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const length = element.length;
|
|
115
|
+
|
|
116
|
+
if (length < this.start || this.end < length) return false;
|
|
117
|
+
|
|
118
|
+
return (length === this.start) || ((length - this.start) % this.step === 0);
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/schedule.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Schedule.
|
|
2
|
+
//
|
|
3
|
+
// A schedule is a set of patterns and/or pattern sets and an associated duration.
|
|
4
|
+
// A schedule may be enumerated so that each pattern it contains is returned, with
|
|
5
|
+
// PatternSets being- expanded into the Patterns they contain.
|
|
6
|
+
//
|
|
7
|
+
// The duration is used by Schema to calculate the duration of each chord in each
|
|
8
|
+
// pattern.
|
|
9
|
+
|
|
10
|
+
import util from "util";
|
|
11
|
+
import Fraction from "fraction.js"
|
|
12
|
+
|
|
13
|
+
import { Set } from "./set.mjs";
|
|
14
|
+
import { Pattern } from "./pattern.mjs";
|
|
15
|
+
import { PatternSet } from "./patternset.mjs";
|
|
16
|
+
|
|
17
|
+
export class Schedule extends Set {
|
|
18
|
+
#duration;
|
|
19
|
+
|
|
20
|
+
// Construct a Schedule.
|
|
21
|
+
//
|
|
22
|
+
// options may contain the usual Set options, as well as:
|
|
23
|
+
// duration A Fraction specifying the duration of the schedule.
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
const defaults = { duration: new Fraction(1, 4), patterns: undefined };
|
|
26
|
+
options = { ...defaults, ...options, allowDuplicates: true, isAbstract: false };
|
|
27
|
+
|
|
28
|
+
super(options);
|
|
29
|
+
|
|
30
|
+
this.#duration = options.duration;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get allowedElements() {
|
|
34
|
+
return [ Pattern, PatternSet ];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get [Symbol.toStringTag]() {
|
|
38
|
+
return "Schedule";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#describe() {
|
|
42
|
+
let description = "";
|
|
43
|
+
|
|
44
|
+
for (let patterns of this) {
|
|
45
|
+
description += util.inspect(patterns);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return description;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
[util.inspect.custom](depth, opts) {
|
|
52
|
+
return `${this[Symbol.toStringTag]} {${this.#duration.toString()}, {${this.#describe()}}}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get duration() {
|
|
56
|
+
return this.#duration;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Returns the total number of Patterns in the Schedule (PatternSets are expanded).
|
|
60
|
+
// NOTE: size still returns the number of elements (Pattens and PatternSets) in
|
|
61
|
+
// the Schedule.
|
|
62
|
+
get length() {
|
|
63
|
+
let length = 0;
|
|
64
|
+
|
|
65
|
+
for (let pattern of this) {
|
|
66
|
+
if (pattern instanceof Pattern) {
|
|
67
|
+
length++;
|
|
68
|
+
} else {
|
|
69
|
+
length += pattern.size
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return length;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Iterate over each element, yielding each Pattern (PatternSets are expanded).
|
|
77
|
+
*#patternGenerator() {
|
|
78
|
+
for (let pattern of this) {
|
|
79
|
+
if (pattern instanceof Pattern) {
|
|
80
|
+
yield pattern;
|
|
81
|
+
} else {
|
|
82
|
+
yield* pattern;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get patterns() { return this.#patternGenerator(); }
|
|
88
|
+
}
|
package/src/schema.mjs
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Schema.
|
|
2
|
+
//
|
|
3
|
+
// A schema is a sequence of schedules to be played in order. It allows each permutation
|
|
4
|
+
// of the sequence to be enumerated, and each such generator to be iterated to obtain the
|
|
5
|
+
// resultant sequece of chord/duration that expresses the permutation.
|
|
6
|
+
|
|
7
|
+
import { Set } from "./set.mjs";
|
|
8
|
+
import { Schedule } from "./schedule.mjs";
|
|
9
|
+
|
|
10
|
+
export class Schema extends Set {
|
|
11
|
+
#timeSignature; // Fraction indicating the time signature of the piece.
|
|
12
|
+
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
const defaults = { timeSignature: [ 4, 4 ] };
|
|
15
|
+
options = { ...defaults, ...options, allowDuplicates: true, isAbstract: false };
|
|
16
|
+
|
|
17
|
+
super(options);
|
|
18
|
+
|
|
19
|
+
this.#timeSignature = options.timeSignature;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get allowedElements() {
|
|
23
|
+
return [ Schedule ];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get [Symbol.toStringTag]() {
|
|
27
|
+
return "Schema";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get timeSignature() {
|
|
31
|
+
return this.#timeSignature;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Returns the total number of expressions in the Schema.
|
|
35
|
+
get length() {
|
|
36
|
+
let length = 0;
|
|
37
|
+
let scheduleLength = 0;
|
|
38
|
+
|
|
39
|
+
for (let schedule of this) {
|
|
40
|
+
scheduleLength = schedule.length;
|
|
41
|
+
|
|
42
|
+
if (scheduleLength > 0) {
|
|
43
|
+
length = length === 0 ? scheduleLength : length * scheduleLength;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return length;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Return a generator that yields each chord and its duration for the patterns in an
|
|
51
|
+
// expression instance, as an object of the form:
|
|
52
|
+
//
|
|
53
|
+
// {
|
|
54
|
+
// chord: <chord>
|
|
55
|
+
// duration: <duration>
|
|
56
|
+
// }
|
|
57
|
+
*#patternGenerator(patterns) {
|
|
58
|
+
for (let schedule of patterns) {
|
|
59
|
+
const duration = schedule.duration;
|
|
60
|
+
const pattern = schedule.patterns.next().value;
|
|
61
|
+
const numChords = pattern.size;
|
|
62
|
+
|
|
63
|
+
if (numChords > 0) {
|
|
64
|
+
const chordDuration = duration.div(numChords);
|
|
65
|
+
|
|
66
|
+
for (let chord of pattern) {
|
|
67
|
+
yield { chord: chord, duration: chordDuration };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Recursively returns a generator that yields each expression of the schedules in the schema.
|
|
74
|
+
*#expressionGenerator(schedules, index, patterns) {
|
|
75
|
+
const schedule = schedules[index];
|
|
76
|
+
|
|
77
|
+
let nextPatterns;
|
|
78
|
+
|
|
79
|
+
for (let pattern of schedule.patterns) {
|
|
80
|
+
nextPatterns = [ ...patterns, (new Schedule({duration: schedule.duration})).add(pattern)];
|
|
81
|
+
|
|
82
|
+
if (index == schedules.length - 1) {
|
|
83
|
+
yield this.#patternGenerator(nextPatterns)
|
|
84
|
+
} else {
|
|
85
|
+
yield* this.#expressionGenerator(schedules, index + 1, nextPatterns);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Returns a generator that yields each expression of the schedules in the schema.
|
|
91
|
+
*#expressionsGenerator() {
|
|
92
|
+
let schedules = [];
|
|
93
|
+
|
|
94
|
+
for (let schedule of this) {
|
|
95
|
+
let length = schedule.length;
|
|
96
|
+
|
|
97
|
+
if (length > 0) {
|
|
98
|
+
schedules.push(schedule);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (schedules.length > 0) {
|
|
103
|
+
yield* this.#expressionGenerator(schedules, 0, []);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get expressions() { return this.#expressionsGenerator(); }
|
|
108
|
+
}
|