@libraz/libcantus 0.9.3 → 0.9.5
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/README.md +29 -20
- package/README_ja.md +268 -0
- package/dist/analyze/index.cjs +39 -1947
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +161 -91
- package/dist/analyze/index.d.ts +161 -91
- package/dist/analyze/index.js +19 -8
- package/dist/chunk-2A2QMA74.cjs +261 -0
- package/dist/chunk-2A2QMA74.cjs.map +1 -0
- package/dist/chunk-2PTFGVD7.js +261 -0
- package/dist/chunk-2PTFGVD7.js.map +1 -0
- package/dist/chunk-357ZDPVU.cjs +162 -0
- package/dist/chunk-357ZDPVU.cjs.map +1 -0
- package/dist/chunk-3KX76RLG.js +1548 -0
- package/dist/chunk-3KX76RLG.js.map +1 -0
- package/dist/chunk-4VZ5MZAM.cjs +889 -0
- package/dist/chunk-4VZ5MZAM.cjs.map +1 -0
- package/dist/{chunk-C5XYKUVG.js → chunk-7EONYVI5.js} +471 -171
- package/dist/chunk-7EONYVI5.js.map +1 -0
- package/dist/{chunk-IT2O4XFE.js → chunk-7JEP3MFY.js} +42 -34
- package/dist/chunk-7JEP3MFY.js.map +1 -0
- package/dist/{chunk-4MHA432G.js → chunk-AID75RN3.js} +572 -250
- package/dist/chunk-AID75RN3.js.map +1 -0
- package/dist/chunk-BSLTLGM3.cjs +497 -0
- package/dist/chunk-BSLTLGM3.cjs.map +1 -0
- package/dist/chunk-CU6VG2YM.cjs +109 -0
- package/dist/chunk-CU6VG2YM.cjs.map +1 -0
- package/dist/{chunk-EH6EM37U.js → chunk-DZMHIGI4.js} +81 -59
- package/dist/chunk-DZMHIGI4.js.map +1 -0
- package/dist/{chunk-AKLYPWIN.js → chunk-EDOTEABK.js} +45 -19
- package/dist/chunk-EDOTEABK.js.map +1 -0
- package/dist/{chunk-2ZWMSJH6.js → chunk-HN2OEUN2.js} +7 -4
- package/dist/chunk-HN2OEUN2.js.map +1 -0
- package/dist/{chunk-MIWO425M.js → chunk-KBTTEX7Q.js} +128 -43
- package/dist/chunk-KBTTEX7Q.js.map +1 -0
- package/dist/chunk-KQN2ZYND.cjs +724 -0
- package/dist/chunk-KQN2ZYND.cjs.map +1 -0
- package/dist/chunk-MPTVJIVG.cjs +3176 -0
- package/dist/chunk-MPTVJIVG.cjs.map +1 -0
- package/dist/chunk-MTMZTMWM.cjs +53 -0
- package/dist/chunk-MTMZTMWM.cjs.map +1 -0
- package/dist/{chunk-CHMXELDS.js → chunk-NNB67XA2.js} +372 -197
- package/dist/chunk-NNB67XA2.js.map +1 -0
- package/dist/chunk-OL3IHHSJ.cjs +1204 -0
- package/dist/chunk-OL3IHHSJ.cjs.map +1 -0
- package/dist/chunk-PI3UXRWK.cjs +528 -0
- package/dist/chunk-PI3UXRWK.cjs.map +1 -0
- package/dist/chunk-R2VVFAIN.js +528 -0
- package/dist/chunk-R2VVFAIN.js.map +1 -0
- package/dist/chunk-T64C7ZD7.cjs +1 -0
- package/dist/chunk-T64C7ZD7.cjs.map +1 -0
- package/dist/chunk-WFDXOLUB.cjs +337 -0
- package/dist/chunk-WFDXOLUB.cjs.map +1 -0
- package/dist/chunk-WTAN4ACG.cjs +1548 -0
- package/dist/chunk-WTAN4ACG.cjs.map +1 -0
- package/dist/chunk-YFJXS3AU.cjs +33 -0
- package/dist/chunk-YFJXS3AU.cjs.map +1 -0
- package/dist/{chunk-GOAW4VYL.js → chunk-YUE6TJ4M.js} +315 -145
- package/dist/chunk-YUE6TJ4M.js.map +1 -0
- package/dist/{chunk-62QZKCJQ.js → chunk-ZGLJM4A4.js} +10 -6
- package/dist/chunk-ZGLJM4A4.js.map +1 -0
- package/dist/core/index.cjs +140 -647
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +313 -41
- package/dist/core/index.d.ts +313 -41
- package/dist/core/index.js +64 -16
- package/dist/{function-DVH4Eusr.d.ts → function-CxixTB02.d.cts} +122 -10
- package/dist/{function-DYt4iKsn.d.cts → function-DTCt675z.d.ts} +122 -10
- package/dist/generate/index.cjs +28 -4085
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +374 -66
- package/dist/generate/index.d.ts +374 -66
- package/dist/generate/index.js +23 -9
- package/dist/{index-Jc0Ryi79.d.ts → index-B2Ulxxpo.d.cts} +21 -5
- package/dist/{index-CpOsP1y9.d.cts → index-BkYJ0xMq.d.cts} +18 -17
- package/dist/{index-CxqBQtH1.d.cts → index-BuaB-ypt.d.ts} +21 -5
- package/dist/index-CAN7v-kw.d.ts +199 -0
- package/dist/{index-Bt4tOUD9.d.ts → index-COw1Ky9g.d.ts} +93 -8
- package/dist/{index-Dikst44v.d.cts → index-Cfx8xgH0.d.ts} +14 -11
- package/dist/{index-DxvARIP2.d.ts → index-DRtIhlJj.d.ts} +18 -17
- package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
- package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
- package/dist/index-alrRninU.d.cts +199 -0
- package/dist/{index-BNlGP86A.d.cts → index-oTQbYYIs.d.cts} +93 -8
- package/dist/index-tR9jTn26.d.cts +275 -0
- package/dist/index-tR9jTn26.d.ts +275 -0
- package/dist/{index-Bg5dYMmZ.d.ts → index-x2vcsduE.d.cts} +14 -11
- package/dist/index.cjs +429 -7383
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -15
- package/dist/index.d.ts +15 -15
- package/dist/index.js +121 -27
- package/dist/model/index.cjs +8 -2562
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +471 -81
- package/dist/model/index.d.ts +471 -81
- package/dist/model/index.js +6 -7
- package/dist/styled-Bl0ZK28h.d.ts +543 -0
- package/dist/styled-Crv6SA4S.d.cts +543 -0
- package/dist/theory/index.cjs +172 -1822
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +151 -233
- package/dist/theory/index.d.ts +151 -233
- package/dist/theory/index.js +30 -7
- package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
- package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
- package/package.json +34 -13
- package/dist/chunk-2ZWMSJH6.js.map +0 -1
- package/dist/chunk-4MHA432G.js.map +0 -1
- package/dist/chunk-62QZKCJQ.js.map +0 -1
- package/dist/chunk-AKLYPWIN.js.map +0 -1
- package/dist/chunk-C5XYKUVG.js.map +0 -1
- package/dist/chunk-CHMXELDS.js.map +0 -1
- package/dist/chunk-DYWFNC4A.js +0 -152
- package/dist/chunk-DYWFNC4A.js.map +0 -1
- package/dist/chunk-EH6EM37U.js.map +0 -1
- package/dist/chunk-FBQK3ZQN.js +0 -131
- package/dist/chunk-FBQK3ZQN.js.map +0 -1
- package/dist/chunk-GOAW4VYL.js.map +0 -1
- package/dist/chunk-I5R7ZBJS.js +0 -202
- package/dist/chunk-I5R7ZBJS.js.map +0 -1
- package/dist/chunk-IT2O4XFE.js.map +0 -1
- package/dist/chunk-MIWO425M.js.map +0 -1
- package/dist/chunk-SGU7SPLK.js +0 -924
- package/dist/chunk-SGU7SPLK.js.map +0 -1
- package/dist/index-CGMZNCx6.d.cts +0 -140
- package/dist/index-CGMZNCx6.d.ts +0 -140
- package/dist/index-CnZCo29u.d.cts +0 -108
- package/dist/index-JnF4v9bx.d.ts +0 -108
- package/dist/styled-DZ7GjShb.d.ts +0 -282
- package/dist/styled-ZTbdaVFM.d.cts +0 -282
- package/src/analyze/arrange/index.ts +0 -21
- package/src/analyze/arrange/internal.ts +0 -127
- package/src/analyze/arrange/tension.ts +0 -199
- package/src/analyze/arrange/tracks.ts +0 -312
- package/src/analyze/detect/index.ts +0 -272
- package/src/analyze/functional/borrowed.ts +0 -84
- package/src/analyze/functional/cadence.ts +0 -66
- package/src/analyze/functional/function.ts +0 -184
- package/src/analyze/functional/index.ts +0 -23
- package/src/analyze/functional/internal.ts +0 -29
- package/src/analyze/functional/roman.ts +0 -402
- package/src/analyze/index.ts +0 -49
- package/src/analyze/timeline/index.ts +0 -451
- package/src/analyze/voice/index.ts +0 -339
- package/src/core/event-index/index.ts +0 -104
- package/src/core/index.ts +0 -61
- package/src/core/interval/index.ts +0 -81
- package/src/core/meter/index.ts +0 -302
- package/src/core/pitch/index.ts +0 -311
- package/src/core/random/index.ts +0 -76
- package/src/core/tuning/index.ts +0 -186
- package/src/core/types.ts +0 -29
- package/src/core/validation/index.ts +0 -119
- package/src/generate/bass/index.ts +0 -373
- package/src/generate/countermelody/index.ts +0 -470
- package/src/generate/drums/beat.ts +0 -266
- package/src/generate/drums/euclid.ts +0 -65
- package/src/generate/drums/fills.ts +0 -356
- package/src/generate/drums/ghost.ts +0 -119
- package/src/generate/drums/hihat.ts +0 -285
- package/src/generate/drums/hit.ts +0 -31
- package/src/generate/drums/index.ts +0 -361
- package/src/generate/drums/internal.ts +0 -257
- package/src/generate/drums/kick.ts +0 -160
- package/src/generate/drums/percussion.ts +0 -121
- package/src/generate/drums/rng.ts +0 -7
- package/src/generate/drums/swing.ts +0 -71
- package/src/generate/groove/index.ts +0 -351
- package/src/generate/harmonize/index.ts +0 -451
- package/src/generate/index.ts +0 -58
- package/src/generate/motif/index.ts +0 -422
- package/src/generate/progression/index.ts +0 -362
- package/src/generate/reharmony/index.ts +0 -323
- package/src/generate/rhythm/index.ts +0 -175
- package/src/index.ts +0 -13
- package/src/model/chord.ts +0 -442
- package/src/model/index.ts +0 -15
- package/src/model/interval.ts +0 -82
- package/src/model/key.ts +0 -266
- package/src/model/note.ts +0 -176
- package/src/model/progression.ts +0 -161
- package/src/model/shared.ts +0 -12
- package/src/theory/chord/index.ts +0 -375
- package/src/theory/chordscale/index.ts +0 -374
- package/src/theory/counterpoint/index.ts +0 -230
- package/src/theory/harmony/index.ts +0 -77
- package/src/theory/index.ts +0 -112
- package/src/theory/safety/index.ts +0 -488
- package/src/theory/scale/degrees.ts +0 -123
- package/src/theory/scale/index.ts +0 -28
- package/src/theory/scale/key.ts +0 -56
- package/src/theory/scale/masks.ts +0 -153
- package/src/theory/spelling/index.ts +0 -249
- package/src/theory/symbol/index.ts +0 -268
- package/src/theory/voicing/index.ts +0 -5
- package/src/theory/voicing/internal.ts +0 -168
- package/src/theory/voicing/leading.ts +0 -112
- package/src/theory/voicing/satb.ts +0 -216
- package/src/theory/voicing/styled.ts +0 -199
- package/tsconfig.json +0 -23
package/dist/generate/index.cjs
CHANGED
|
@@ -1,4123 +1,66 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
19
2
|
|
|
20
|
-
// src/generate/index.ts
|
|
21
|
-
var generate_exports = {};
|
|
22
|
-
__export(generate_exports, {
|
|
23
|
-
applyGrooveTemplate: () => applyGrooveTemplate,
|
|
24
|
-
developMotif: () => developMotif,
|
|
25
|
-
extractGrooveTemplate: () => extractGrooveTemplate,
|
|
26
|
-
generateBassLine: () => generateBassLine,
|
|
27
|
-
generateCounterMelody: () => generateCounterMelody,
|
|
28
|
-
generateDrums: () => generateDrums,
|
|
29
|
-
generateMotif: () => generateMotif,
|
|
30
|
-
generateProgression: () => generateProgression,
|
|
31
|
-
generateRhythm: () => generateRhythm,
|
|
32
|
-
harmonizeMelody: () => harmonizeMelody,
|
|
33
|
-
humanize: () => humanize,
|
|
34
|
-
modalInterchangePalette: () => modalInterchangePalette,
|
|
35
|
-
negativeHarmonyMirror: () => negativeHarmonyMirror,
|
|
36
|
-
onsetWeightCurve: () => onsetWeightCurve,
|
|
37
|
-
progressions: () => progressions,
|
|
38
|
-
progressionsByStyle: () => progressionsByStyle,
|
|
39
|
-
rhythmDensity: () => rhythmDensity,
|
|
40
|
-
substituteChord: () => substituteChord,
|
|
41
|
-
transformMotif: () => transformMotif
|
|
42
|
-
});
|
|
43
|
-
module.exports = __toCommonJS(generate_exports);
|
|
44
3
|
|
|
45
|
-
// src/core/validation/index.ts
|
|
46
|
-
var DEFAULT_GENERATION_BUDGET = 1e6;
|
|
47
|
-
function assertFiniteNumber(value, name) {
|
|
48
|
-
if (!Number.isFinite(value)) {
|
|
49
|
-
throw new RangeError(`${name} must be finite; received ${value}`);
|
|
50
|
-
}
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
function assertInteger(value, name, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
|
|
54
|
-
assertFiniteNumber(value, name);
|
|
55
|
-
if (!Number.isSafeInteger(value) || value < min || value > max) {
|
|
56
|
-
throw new RangeError(`${name} must be an integer in [${min}, ${max}]; received ${value}`);
|
|
57
|
-
}
|
|
58
|
-
return value;
|
|
59
|
-
}
|
|
60
|
-
function assertPositiveInt(value, name, max = DEFAULT_GENERATION_BUDGET) {
|
|
61
|
-
return assertInteger(value, name, 1, max);
|
|
62
|
-
}
|
|
63
|
-
function assertRange(value, min, max, name) {
|
|
64
|
-
assertFiniteNumber(value, name);
|
|
65
|
-
if (value < min || value > max) {
|
|
66
|
-
throw new RangeError(`${name} must be in [${min}, ${max}]; received ${value}`);
|
|
67
|
-
}
|
|
68
|
-
return value;
|
|
69
|
-
}
|
|
70
|
-
function assertGenerationBudget(estimated, name, limit = DEFAULT_GENERATION_BUDGET) {
|
|
71
|
-
assertFiniteNumber(estimated, name);
|
|
72
|
-
assertPositiveInt(limit, `${name} limit`, Number.MAX_SAFE_INTEGER);
|
|
73
|
-
if (estimated < 0 || estimated > limit) {
|
|
74
|
-
throw new RangeError(`${name} exceeds the generation budget ${limit}; received ${estimated}`);
|
|
75
|
-
}
|
|
76
|
-
return estimated;
|
|
77
|
-
}
|
|
78
|
-
function assertTimeSignature(ts, name = "time signature") {
|
|
79
|
-
assertPositiveInt(ts.numerator, `${name}.numerator`);
|
|
80
|
-
assertPositiveInt(ts.denominator, `${name}.denominator`);
|
|
81
|
-
if (ts.grouping !== void 0) {
|
|
82
|
-
if (ts.grouping.length === 0) {
|
|
83
|
-
throw new RangeError(`${name}.grouping must not be empty`);
|
|
84
|
-
}
|
|
85
|
-
let sum = 0;
|
|
86
|
-
for (let index = 0; index < ts.grouping.length; index += 1) {
|
|
87
|
-
sum += assertPositiveInt(ts.grouping[index] ?? Number.NaN, `${name}.grouping[${index}]`);
|
|
88
|
-
}
|
|
89
|
-
const compound = ts.numerator % 3 === 0 && ts.numerator > 3;
|
|
90
|
-
const pulses = compound ? ts.numerator / 3 : ts.numerator;
|
|
91
|
-
if (sum !== pulses) {
|
|
92
|
-
throw new RangeError(`${name}.grouping must sum to ${pulses}; received ${sum}`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return ts;
|
|
96
|
-
}
|
|
97
|
-
function assertNoteEvent(event, name = "note event", options = {}) {
|
|
98
|
-
assertFiniteNumber(event.pitch, `${name}.pitch`);
|
|
99
|
-
assertRange(event.startBeat, 0, Number.MAX_SAFE_INTEGER, `${name}.startBeat`);
|
|
100
|
-
assertRange(
|
|
101
|
-
event.durationBeat,
|
|
102
|
-
options.allowNonPositiveDuration ? -Number.MAX_SAFE_INTEGER : Number.MIN_VALUE,
|
|
103
|
-
Number.MAX_SAFE_INTEGER,
|
|
104
|
-
`${name}.durationBeat`
|
|
105
|
-
);
|
|
106
|
-
if (!options.allowNonPositiveDuration && event.durationBeat <= 0) {
|
|
107
|
-
throw new RangeError(`${name}.durationBeat must be positive; received ${event.durationBeat}`);
|
|
108
|
-
}
|
|
109
|
-
if (event.velocity !== void 0) {
|
|
110
|
-
assertRange(event.velocity, 0, 127, `${name}.velocity`);
|
|
111
|
-
}
|
|
112
|
-
return event;
|
|
113
|
-
}
|
|
114
|
-
function assertNoteEvents(events, name = "note events", options = {}) {
|
|
115
|
-
assertGenerationBudget(events.length, `${name} count`, options.budget);
|
|
116
|
-
for (let index = 0; index < events.length; index += 1) {
|
|
117
|
-
const event = events[index];
|
|
118
|
-
if (event !== void 0) {
|
|
119
|
-
assertNoteEvent(event, `${name}[${index}]`, options);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return events;
|
|
123
|
-
}
|
|
124
4
|
|
|
125
|
-
// src/core/meter/index.ts
|
|
126
|
-
var EPS = 1e-9;
|
|
127
|
-
function isMultiple(value, unit) {
|
|
128
|
-
if (unit === 0) {
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
const ratio = value / unit;
|
|
132
|
-
return Math.abs(ratio - Math.round(ratio)) < EPS;
|
|
133
|
-
}
|
|
134
|
-
function formatTimeSignature(ts) {
|
|
135
|
-
assertTimeSignature(ts);
|
|
136
|
-
return `${ts.numerator}/${ts.denominator}`;
|
|
137
|
-
}
|
|
138
|
-
function isCompound(ts) {
|
|
139
|
-
assertTimeSignature(ts);
|
|
140
|
-
return ts.numerator % 3 === 0 && ts.numerator > 3;
|
|
141
|
-
}
|
|
142
|
-
function unitBeats(ts) {
|
|
143
|
-
return 4 / ts.denominator;
|
|
144
|
-
}
|
|
145
|
-
function beatsPerBar(ts) {
|
|
146
|
-
assertTimeSignature(ts);
|
|
147
|
-
return ts.numerator * unitBeats(ts);
|
|
148
|
-
}
|
|
149
|
-
function pulseBeats(ts) {
|
|
150
|
-
return isCompound(ts) ? 3 * unitBeats(ts) : unitBeats(ts);
|
|
151
|
-
}
|
|
152
|
-
function pulsesPerBar(ts) {
|
|
153
|
-
return beatsPerBar(ts) / pulseBeats(ts);
|
|
154
|
-
}
|
|
155
|
-
function isValidGrouping(grouping, pulses) {
|
|
156
|
-
if (grouping.length === 0) {
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
let sum = 0;
|
|
160
|
-
for (const g of grouping) {
|
|
161
|
-
if (!Number.isInteger(g) || g <= 0) {
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
sum += g;
|
|
165
|
-
}
|
|
166
|
-
return sum === pulses;
|
|
167
|
-
}
|
|
168
|
-
function isGroupHead(grouping, pulseIndex) {
|
|
169
|
-
let acc = 0;
|
|
170
|
-
for (const g of grouping) {
|
|
171
|
-
if (acc === pulseIndex) {
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
acc += g;
|
|
175
|
-
}
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
function beatToBarPosition(beatInQuarters, ts) {
|
|
179
|
-
assertFiniteNumber(beatInQuarters, "beat");
|
|
180
|
-
const barLen = beatsPerBar(ts);
|
|
181
|
-
const bar = Math.floor(beatInQuarters / barLen);
|
|
182
|
-
return { bar, beat: beatInQuarters - bar * barLen };
|
|
183
|
-
}
|
|
184
|
-
function metricWeight(beatInQuarters, ts) {
|
|
185
|
-
assertFiniteNumber(beatInQuarters, "beat");
|
|
186
|
-
assertTimeSignature(ts);
|
|
187
|
-
const { beat } = beatToBarPosition(beatInQuarters, ts);
|
|
188
|
-
const pulse = pulseBeats(ts);
|
|
189
|
-
if (!isMultiple(beat, pulse)) {
|
|
190
|
-
return 0;
|
|
191
|
-
}
|
|
192
|
-
const pulses = pulsesPerBar(ts);
|
|
193
|
-
const pulseIndex = Math.round(beat / pulse) % pulses;
|
|
194
|
-
if (pulseIndex === 0) {
|
|
195
|
-
return 3;
|
|
196
|
-
}
|
|
197
|
-
const grouping = ts.grouping;
|
|
198
|
-
if (grouping !== void 0) {
|
|
199
|
-
if (!isValidGrouping(grouping, pulses)) {
|
|
200
|
-
throw new Error(
|
|
201
|
-
`Invalid grouping [${grouping.join(", ")}] for ${formatTimeSignature(ts)}: entries must be positive integers summing to ${pulses}`
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
return isGroupHead(grouping, pulseIndex) ? 2 : 1;
|
|
205
|
-
}
|
|
206
|
-
if (pulses % 2 === 0 && pulseIndex === pulses / 2) {
|
|
207
|
-
return 2;
|
|
208
|
-
}
|
|
209
|
-
return 1;
|
|
210
|
-
}
|
|
211
|
-
function isStrongBeat(beatInQuarters, ts) {
|
|
212
|
-
return metricWeight(beatInQuarters, ts) >= 2;
|
|
213
|
-
}
|
|
214
5
|
|
|
215
|
-
// src/core/random/index.ts
|
|
216
|
-
function createRng(seed) {
|
|
217
|
-
assertFiniteNumber(seed, "seed");
|
|
218
|
-
let state = seed >>> 0;
|
|
219
|
-
const next = () => {
|
|
220
|
-
state = state + 1831565813 >>> 0;
|
|
221
|
-
let t = state;
|
|
222
|
-
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
223
|
-
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
224
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
225
|
-
};
|
|
226
|
-
return {
|
|
227
|
-
next,
|
|
228
|
-
prob: (p) => next() < assertRange(p, 0, 1, "probability"),
|
|
229
|
-
range: (lo, hi) => {
|
|
230
|
-
assertInteger(lo, "range lower bound");
|
|
231
|
-
assertInteger(hi, "range upper bound");
|
|
232
|
-
if (lo > hi) {
|
|
233
|
-
throw new RangeError(
|
|
234
|
-
`range lower bound must not exceed upper bound; received ${lo} > ${hi}`
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
assertInteger(hi - lo, "range span", 0);
|
|
238
|
-
return lo + Math.floor(next() * (hi - lo + 1));
|
|
239
|
-
},
|
|
240
|
-
float: (lo, hi) => {
|
|
241
|
-
assertFiniteNumber(lo, "float lower bound");
|
|
242
|
-
assertFiniteNumber(hi, "float upper bound");
|
|
243
|
-
if (lo > hi) {
|
|
244
|
-
throw new RangeError(
|
|
245
|
-
`float lower bound must not exceed upper bound; received ${lo} > ${hi}`
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
assertFiniteNumber(hi - lo, "float span");
|
|
249
|
-
return lo + next() * (hi - lo);
|
|
250
|
-
}
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
6
|
|
|
254
|
-
// src/theory/scale/degrees.ts
|
|
255
|
-
function pitchClass(pitch) {
|
|
256
|
-
return (Math.trunc(pitch) % 12 + 12) % 12;
|
|
257
|
-
}
|
|
258
|
-
function scaleOffset(pitch, key) {
|
|
259
|
-
return (pitchClass(pitch) - pitchClass(key.rootPc) + 12) % 12;
|
|
260
|
-
}
|
|
261
|
-
function isScaleTone(pitch, key) {
|
|
262
|
-
return (key.modeMask12 >> scaleOffset(pitch, key) & 1) === 1;
|
|
263
|
-
}
|
|
264
|
-
function nearestScaleTone(pitch, key) {
|
|
265
|
-
const base = Math.round(pitch);
|
|
266
|
-
for (let distance = 0; distance < 12; distance += 1) {
|
|
267
|
-
const lower = base - distance;
|
|
268
|
-
if (isScaleTone(lower, key)) {
|
|
269
|
-
return lower;
|
|
270
|
-
}
|
|
271
|
-
const higher = base + distance;
|
|
272
|
-
if (isScaleTone(higher, key)) {
|
|
273
|
-
return higher;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return base;
|
|
277
|
-
}
|
|
278
|
-
function scaleTonesInDegreeOrder(key) {
|
|
279
|
-
const root = pitchClass(key.rootPc);
|
|
280
|
-
const pcs = [];
|
|
281
|
-
for (let n = 0; n < 12; n += 1) {
|
|
282
|
-
if ((key.modeMask12 >> n & 1) === 1) {
|
|
283
|
-
pcs.push((root + n) % 12);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
return pcs;
|
|
287
|
-
}
|
|
288
7
|
|
|
289
|
-
// src/theory/scale/masks.ts
|
|
290
|
-
function maskFromOffsets(offsets) {
|
|
291
|
-
let mask = 1;
|
|
292
|
-
for (const offset of offsets) {
|
|
293
|
-
mask |= 1 << (offset % 12 + 12) % 12;
|
|
294
|
-
}
|
|
295
|
-
return mask;
|
|
296
|
-
}
|
|
297
|
-
var MAJOR_MASK = 2741;
|
|
298
|
-
var NATURAL_MINOR_MASK = 1453;
|
|
299
|
-
var HARMONIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 8, 11]);
|
|
300
|
-
var MELODIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 11]);
|
|
301
|
-
var DORIAN_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 10]);
|
|
302
|
-
var PHRYGIAN_MASK = maskFromOffsets([0, 1, 3, 5, 7, 8, 10]);
|
|
303
|
-
var LYDIAN_MASK = maskFromOffsets([0, 2, 4, 6, 7, 9, 11]);
|
|
304
|
-
var MIXOLYDIAN_MASK = maskFromOffsets([0, 2, 4, 5, 7, 9, 10]);
|
|
305
|
-
var LOCRIAN_MASK = maskFromOffsets([0, 1, 3, 5, 6, 8, 10]);
|
|
306
|
-
var MAJOR_PENTATONIC_MASK = maskFromOffsets([0, 2, 4, 7, 9]);
|
|
307
|
-
var MINOR_PENTATONIC_MASK = maskFromOffsets([0, 3, 5, 7, 10]);
|
|
308
|
-
var BLUES_MASK = maskFromOffsets([0, 3, 5, 6, 7, 10]);
|
|
309
|
-
var WHOLE_TONE_MASK = maskFromOffsets([0, 2, 4, 6, 8, 10]);
|
|
310
|
-
var OCTATONIC_HALF_WHOLE_MASK = maskFromOffsets([0, 1, 3, 4, 6, 7, 9, 10]);
|
|
311
|
-
var OCTATONIC_WHOLE_HALF_MASK = maskFromOffsets([0, 2, 3, 5, 6, 8, 9, 11]);
|
|
312
8
|
|
|
313
|
-
// src/theory/scale/key.ts
|
|
314
|
-
function majorKey(rootPc) {
|
|
315
|
-
return { rootPc: (rootPc % 12 + 12) % 12, modeMask12: MAJOR_MASK };
|
|
316
|
-
}
|
|
317
|
-
function minorKey(rootPc) {
|
|
318
|
-
return { rootPc: (rootPc % 12 + 12) % 12, modeMask12: NATURAL_MINOR_MASK };
|
|
319
|
-
}
|
|
320
9
|
|
|
321
|
-
// src/generate/bass/index.ts
|
|
322
|
-
var DEFAULT_TS = { numerator: 4, denominator: 4 };
|
|
323
|
-
var DEFAULT_STYLE = "root";
|
|
324
|
-
var DEFAULT_OCTAVE = 2;
|
|
325
|
-
var STRONG_VELOCITY = 100;
|
|
326
|
-
var WEAK_VELOCITY = 80;
|
|
327
|
-
var PICKUP_PROB = 0.35;
|
|
328
|
-
var EPS2 = 1e-9;
|
|
329
|
-
function pitchClass2(value) {
|
|
330
|
-
return (Math.trunc(value) % 12 + 12) % 12;
|
|
331
|
-
}
|
|
332
|
-
function placePc(pc, anchor, low) {
|
|
333
|
-
const high = low + 12;
|
|
334
|
-
let midi = pc + 12 * Math.round((anchor - pc) / 12);
|
|
335
|
-
while (midi < low) {
|
|
336
|
-
midi += 12;
|
|
337
|
-
}
|
|
338
|
-
while (midi > high) {
|
|
339
|
-
midi -= 12;
|
|
340
|
-
}
|
|
341
|
-
return midi;
|
|
342
|
-
}
|
|
343
|
-
function chordTonePcs(chord) {
|
|
344
|
-
const seen = /* @__PURE__ */ new Set();
|
|
345
|
-
const pcs = [];
|
|
346
|
-
for (const interval of chord.intervals) {
|
|
347
|
-
const pc = pitchClass2(chord.rootPc + interval);
|
|
348
|
-
if (!seen.has(pc)) {
|
|
349
|
-
seen.add(pc);
|
|
350
|
-
pcs.push(pc);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (pcs.length === 0) {
|
|
354
|
-
pcs.push(pitchClass2(chord.rootPc));
|
|
355
|
-
}
|
|
356
|
-
return pcs;
|
|
357
|
-
}
|
|
358
|
-
function fifthPcOf(chord) {
|
|
359
|
-
const fifths = new Set(chord.intervals.map((i) => pitchClass2(i)));
|
|
360
|
-
const chosen = fifths.has(7) ? 7 : fifths.has(6) ? 6 : fifths.has(8) ? 8 : void 0;
|
|
361
|
-
return chosen === void 0 ? pitchClass2(chord.rootPc) : pitchClass2(chord.rootPc + chosen);
|
|
362
|
-
}
|
|
363
|
-
function bassPcOf(chord) {
|
|
364
|
-
return pitchClass2(chord.bassPc ?? chord.rootPc);
|
|
365
|
-
}
|
|
366
|
-
function beatPositions(start, end) {
|
|
367
|
-
const positions = [];
|
|
368
|
-
for (let p = start; p < end - EPS2; p += 1) {
|
|
369
|
-
positions.push(p);
|
|
370
|
-
}
|
|
371
|
-
if (positions.length === 0) {
|
|
372
|
-
positions.push(start);
|
|
373
|
-
}
|
|
374
|
-
return positions;
|
|
375
|
-
}
|
|
376
|
-
function emit(ctx, pos, pc, midiOverride) {
|
|
377
|
-
const midi = midiOverride ?? placePc(pc, ctx.prevMidi, ctx.low);
|
|
378
|
-
const velocity = isStrongBeat(pos, ctx.ts) ? STRONG_VELOCITY : WEAK_VELOCITY;
|
|
379
|
-
ctx.notes.push({ startBeat: pos, pitch: midi, velocity });
|
|
380
|
-
return midi;
|
|
381
|
-
}
|
|
382
|
-
function buildRoot(ctx, seg) {
|
|
383
|
-
ctx.prevMidi = emit(ctx, seg.startBeat, bassPcOf(seg.chord));
|
|
384
|
-
}
|
|
385
|
-
function buildRootFifth(ctx, seg) {
|
|
386
|
-
ctx.prevMidi = emit(ctx, seg.startBeat, bassPcOf(seg.chord));
|
|
387
|
-
const mid = (seg.startBeat + seg.endBeat) / 2;
|
|
388
|
-
if (mid > seg.startBeat + EPS2 && mid < seg.endBeat - EPS2) {
|
|
389
|
-
ctx.prevMidi = emit(ctx, mid, fifthPcOf(seg.chord));
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
function buildPop(ctx, seg) {
|
|
393
|
-
const positions = beatPositions(seg.startBeat, seg.endBeat);
|
|
394
|
-
const rootPc = bassPcOf(seg.chord);
|
|
395
|
-
let emitted = false;
|
|
396
|
-
for (const pos of positions) {
|
|
397
|
-
if (isStrongBeat(pos, ctx.ts)) {
|
|
398
|
-
ctx.prevMidi = emit(ctx, pos, rootPc);
|
|
399
|
-
emitted = true;
|
|
400
|
-
} else if (ctx.rng.prob(PICKUP_PROB)) {
|
|
401
|
-
if (ctx.rng.prob(0.5)) {
|
|
402
|
-
const base = placePc(rootPc, ctx.prevMidi, ctx.low);
|
|
403
|
-
emit(ctx, pos, rootPc, Math.max(ctx.low, base - 12));
|
|
404
|
-
} else {
|
|
405
|
-
ctx.prevMidi = emit(ctx, pos, fifthPcOf(seg.chord));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
if (!emitted) {
|
|
410
|
-
ctx.prevMidi = emit(ctx, seg.startBeat, rootPc);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
function buildArpeggio(ctx, seg) {
|
|
414
|
-
const tones = chordTonePcs(seg.chord);
|
|
415
|
-
const positions = beatPositions(seg.startBeat, seg.endBeat);
|
|
416
|
-
positions.forEach((pos, i) => {
|
|
417
|
-
const pc = tones[i % tones.length] ?? bassPcOf(seg.chord);
|
|
418
|
-
ctx.prevMidi = emit(ctx, pos, pc);
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
function approachNote(ctx, target, from) {
|
|
422
|
-
const dir = from <= target ? -1 : 1;
|
|
423
|
-
if (ctx.rng.prob(0.5)) {
|
|
424
|
-
return target + dir;
|
|
425
|
-
}
|
|
426
|
-
const cand = nearestScaleTone(target + dir * 2, ctx.key);
|
|
427
|
-
const step = Math.abs(cand - target);
|
|
428
|
-
return step >= 1 && step <= 2 ? cand : target + dir;
|
|
429
|
-
}
|
|
430
|
-
function buildWalking(ctx, seg, next) {
|
|
431
|
-
const tones = chordTonePcs(seg.chord);
|
|
432
|
-
const positions = beatPositions(seg.startBeat, seg.endBeat);
|
|
433
|
-
const count = positions.length;
|
|
434
|
-
positions.forEach((pos, i) => {
|
|
435
|
-
if (i === 0) {
|
|
436
|
-
ctx.prevMidi = emit(ctx, pos, bassPcOf(seg.chord));
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
if (next && count > 1 && i === count - 1) {
|
|
440
|
-
const nextBass = placePc(bassPcOf(next.chord), ctx.prevMidi, ctx.low);
|
|
441
|
-
const midi = approachNote(ctx, nextBass, ctx.prevMidi);
|
|
442
|
-
ctx.prevMidi = emit(ctx, pos, pitchClass2(midi), midi);
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
const pc = tones[i % tones.length] ?? bassPcOf(seg.chord);
|
|
446
|
-
ctx.prevMidi = emit(ctx, pos, pc);
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
function generateBassLine(opts) {
|
|
450
|
-
assertGenerationBudget(opts.segments.length, "bass segments");
|
|
451
|
-
for (let index = 0; index < opts.segments.length; index += 1) {
|
|
452
|
-
const segment = opts.segments[index];
|
|
453
|
-
if (!segment) continue;
|
|
454
|
-
assertRange(segment.startBeat, 0, Number.MAX_SAFE_INTEGER, `segments[${index}].startBeat`);
|
|
455
|
-
assertRange(segment.endBeat, 0, Number.MAX_SAFE_INTEGER, `segments[${index}].endBeat`);
|
|
456
|
-
if (segment.endBeat <= segment.startBeat) {
|
|
457
|
-
throw new RangeError(`segments[${index}] must have a positive duration`);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
const segments = [...opts.segments].sort((a, b) => a.startBeat - b.startBeat);
|
|
461
|
-
if (segments.length === 0) {
|
|
462
|
-
return [];
|
|
463
|
-
}
|
|
464
|
-
const ts = opts.ts ?? DEFAULT_TS;
|
|
465
|
-
assertTimeSignature(ts);
|
|
466
|
-
const style = opts.style ?? DEFAULT_STYLE;
|
|
467
|
-
const octave = opts.octave ?? DEFAULT_OCTAVE;
|
|
468
|
-
assertInteger(octave, "bass octave", -1, 9);
|
|
469
|
-
const estimatedNotes = segments.reduce(
|
|
470
|
-
(count, segment) => count + Math.max(1, Math.ceil(segment.endBeat - segment.startBeat)),
|
|
471
|
-
0
|
|
472
|
-
);
|
|
473
|
-
assertGenerationBudget(estimatedNotes, "bass notes");
|
|
474
|
-
const low = octave * 12 + 12;
|
|
475
|
-
const ctx = {
|
|
476
|
-
ts,
|
|
477
|
-
low,
|
|
478
|
-
key: opts.key,
|
|
479
|
-
rng: createRng(opts.seed ?? 0),
|
|
480
|
-
notes: [],
|
|
481
|
-
prevMidi: low
|
|
482
|
-
};
|
|
483
|
-
for (let s = 0; s < segments.length; s += 1) {
|
|
484
|
-
const seg = segments[s];
|
|
485
|
-
if (!seg) {
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
switch (style) {
|
|
489
|
-
case "rootFifth":
|
|
490
|
-
buildRootFifth(ctx, seg);
|
|
491
|
-
break;
|
|
492
|
-
case "pop":
|
|
493
|
-
buildPop(ctx, seg);
|
|
494
|
-
break;
|
|
495
|
-
case "walking":
|
|
496
|
-
buildWalking(ctx, seg, segments[s + 1]);
|
|
497
|
-
break;
|
|
498
|
-
case "arpeggio":
|
|
499
|
-
buildArpeggio(ctx, seg);
|
|
500
|
-
break;
|
|
501
|
-
default:
|
|
502
|
-
buildRoot(ctx, seg);
|
|
503
|
-
break;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
ctx.notes.sort((a, b) => a.startBeat - b.startBeat);
|
|
507
|
-
const lastEnd = segments.reduce((m, seg) => Math.max(m, seg.endBeat), Number.NEGATIVE_INFINITY);
|
|
508
|
-
const out = [];
|
|
509
|
-
for (let i = 0; i < ctx.notes.length; i += 1) {
|
|
510
|
-
const note = ctx.notes[i];
|
|
511
|
-
if (!note) {
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
const nextStart = ctx.notes[i + 1]?.startBeat ?? lastEnd;
|
|
515
|
-
const durationBeat = nextStart - note.startBeat;
|
|
516
|
-
if (durationBeat <= EPS2) {
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
out.push({
|
|
520
|
-
pitch: note.pitch,
|
|
521
|
-
startBeat: note.startBeat,
|
|
522
|
-
durationBeat,
|
|
523
|
-
velocity: note.velocity
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
return out;
|
|
527
|
-
}
|
|
528
10
|
|
|
529
|
-
// src/core/event-index/index.ts
|
|
530
|
-
var EPS3 = 1e-9;
|
|
531
|
-
function upperBound(values, beat) {
|
|
532
|
-
let low = 0;
|
|
533
|
-
let high = values.length;
|
|
534
|
-
while (low < high) {
|
|
535
|
-
const middle = Math.floor((low + high) / 2);
|
|
536
|
-
if ((values[middle]?.note.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {
|
|
537
|
-
low = middle + 1;
|
|
538
|
-
} else {
|
|
539
|
-
high = middle;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return low;
|
|
543
|
-
}
|
|
544
|
-
function createNoteEventIndex(events, options = {}) {
|
|
545
|
-
assertNoteEvents(events, "note events", options);
|
|
546
|
-
const notes = events.map((note, originalIndex) => ({
|
|
547
|
-
note,
|
|
548
|
-
originalIndex,
|
|
549
|
-
endBeat: note.startBeat + note.durationBeat
|
|
550
|
-
})).sort((a, b) => a.note.startBeat - b.note.startBeat || a.originalIndex - b.originalIndex);
|
|
551
|
-
const prefixMaxEnd = [];
|
|
552
|
-
let maxEnd = Number.NEGATIVE_INFINITY;
|
|
553
|
-
for (const indexed of notes) {
|
|
554
|
-
maxEnd = Math.max(maxEnd, indexed.endBeat);
|
|
555
|
-
prefixMaxEnd.push(maxEnd);
|
|
556
|
-
}
|
|
557
|
-
return {
|
|
558
|
-
notes,
|
|
559
|
-
at(beat) {
|
|
560
|
-
let index = upperBound(notes, beat + EPS3) - 1;
|
|
561
|
-
while (index >= 0) {
|
|
562
|
-
if ((prefixMaxEnd[index] ?? Number.NEGATIVE_INFINITY) <= beat + EPS3) {
|
|
563
|
-
return void 0;
|
|
564
|
-
}
|
|
565
|
-
const indexed = notes[index];
|
|
566
|
-
if (indexed && indexed.note.durationBeat > 0 && indexed.note.startBeat - EPS3 <= beat && beat < indexed.endBeat - EPS3) {
|
|
567
|
-
return indexed;
|
|
568
|
-
}
|
|
569
|
-
index -= 1;
|
|
570
|
-
}
|
|
571
|
-
return void 0;
|
|
572
|
-
},
|
|
573
|
-
attacksAt(beat) {
|
|
574
|
-
const firstAfter = upperBound(notes, beat + EPS3);
|
|
575
|
-
const candidate = notes[firstAfter - 1];
|
|
576
|
-
return candidate !== void 0 && Math.abs(candidate.note.startBeat - beat) < EPS3;
|
|
577
|
-
},
|
|
578
|
-
onsetsBetween(startBeat, endBeat) {
|
|
579
|
-
const result = [];
|
|
580
|
-
let index = upperBound(notes, startBeat + EPS3);
|
|
581
|
-
while (index < notes.length) {
|
|
582
|
-
const onset = notes[index]?.note.startBeat;
|
|
583
|
-
if (onset === void 0 || onset >= endBeat - EPS3) {
|
|
584
|
-
break;
|
|
585
|
-
}
|
|
586
|
-
if (result[result.length - 1] !== onset) {
|
|
587
|
-
result.push(onset);
|
|
588
|
-
}
|
|
589
|
-
index += 1;
|
|
590
|
-
}
|
|
591
|
-
return result;
|
|
592
|
-
}
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
11
|
|
|
596
|
-
// src/theory/chord/index.ts
|
|
597
|
-
var QUALITY_INTERVALS = {
|
|
598
|
-
maj: [0, 4, 7],
|
|
599
|
-
min: [0, 3, 7],
|
|
600
|
-
dim: [0, 3, 6],
|
|
601
|
-
aug: [0, 4, 8],
|
|
602
|
-
majb5: [0, 4, 6],
|
|
603
|
-
maj7: [0, 4, 7, 11],
|
|
604
|
-
min7: [0, 3, 7, 10],
|
|
605
|
-
dom7: [0, 4, 7, 10],
|
|
606
|
-
dim7: [0, 3, 6, 9],
|
|
607
|
-
m7b5: [0, 3, 6, 10],
|
|
608
|
-
minMaj7: [0, 3, 7, 11],
|
|
609
|
-
aug7: [0, 4, 8, 10],
|
|
610
|
-
augMaj7: [0, 4, 8, 11],
|
|
611
|
-
"6": [0, 4, 7, 9],
|
|
612
|
-
min6: [0, 3, 7, 9],
|
|
613
|
-
"6/9": [0, 4, 7, 9, 14],
|
|
614
|
-
sus2: [0, 2, 7],
|
|
615
|
-
sus4: [0, 5, 7],
|
|
616
|
-
add9: [0, 4, 7, 14],
|
|
617
|
-
add11: [0, 4, 7, 17],
|
|
618
|
-
maj9: [0, 4, 7, 11, 14],
|
|
619
|
-
min9: [0, 3, 7, 10, 14],
|
|
620
|
-
dom9: [0, 4, 7, 10, 14],
|
|
621
|
-
"7b9": [0, 4, 7, 10, 13],
|
|
622
|
-
"7#9": [0, 4, 7, 10, 15],
|
|
623
|
-
"7#11": [0, 4, 7, 10, 18],
|
|
624
|
-
"7b13": [0, 4, 7, 10, 20],
|
|
625
|
-
"11": [0, 7, 10, 14, 17],
|
|
626
|
-
"13": [0, 4, 7, 10, 14, 21],
|
|
627
|
-
"5": [0, 7]
|
|
628
|
-
};
|
|
629
|
-
function pitchClass3(value) {
|
|
630
|
-
return (Math.trunc(value) % 12 + 12) % 12;
|
|
631
|
-
}
|
|
632
|
-
function chordQualities() {
|
|
633
|
-
return Object.keys(QUALITY_INTERVALS);
|
|
634
|
-
}
|
|
635
|
-
function makeChord(rootPc, quality, bassPc) {
|
|
636
|
-
const chord = {
|
|
637
|
-
rootPc: pitchClass3(rootPc),
|
|
638
|
-
quality,
|
|
639
|
-
intervals: [...QUALITY_INTERVALS[quality]]
|
|
640
|
-
};
|
|
641
|
-
if (bassPc !== void 0) {
|
|
642
|
-
chord.bassPc = pitchClass3(bassPc);
|
|
643
|
-
}
|
|
644
|
-
return chord;
|
|
645
|
-
}
|
|
646
|
-
function chordPitchClasses(chord) {
|
|
647
|
-
const set = /* @__PURE__ */ new Set();
|
|
648
|
-
for (const interval of chord.intervals) {
|
|
649
|
-
set.add(pitchClass3(chord.rootPc + interval));
|
|
650
|
-
}
|
|
651
|
-
return [...set].sort((a, b) => a - b);
|
|
652
|
-
}
|
|
653
|
-
function chordToneOffsets(chord) {
|
|
654
|
-
return new Set(chord.intervals.map((i) => (i % 12 + 12) % 12));
|
|
655
|
-
}
|
|
656
|
-
function chordToneRole(pitch, chord) {
|
|
657
|
-
const interval = (pitchClass3(pitch) - pitchClass3(chord.rootPc) + 12) % 12;
|
|
658
|
-
const tones = chordToneOffsets(chord);
|
|
659
|
-
if (interval === 0) {
|
|
660
|
-
return "root";
|
|
661
|
-
}
|
|
662
|
-
if (interval === 3 || interval === 4) {
|
|
663
|
-
return tones.has(interval) ? "third" : null;
|
|
664
|
-
}
|
|
665
|
-
if (interval === 7) {
|
|
666
|
-
return tones.has(7) ? "fifth" : null;
|
|
667
|
-
}
|
|
668
|
-
if (interval === 6) {
|
|
669
|
-
return tones.has(6) && !tones.has(7) ? "fifth" : null;
|
|
670
|
-
}
|
|
671
|
-
if (interval === 8) {
|
|
672
|
-
return tones.has(8) && !tones.has(7) ? "fifth" : null;
|
|
673
|
-
}
|
|
674
|
-
if (interval === 9) {
|
|
675
|
-
const hasHigherSeventh = tones.has(10) || tones.has(11);
|
|
676
|
-
const isDiminishedSeventh = tones.has(3) && tones.has(6) && !hasHigherSeventh;
|
|
677
|
-
if (isDiminishedSeventh) {
|
|
678
|
-
return "seventh";
|
|
679
|
-
}
|
|
680
|
-
if (tones.has(9) && !hasHigherSeventh) {
|
|
681
|
-
return "sixth";
|
|
682
|
-
}
|
|
683
|
-
return null;
|
|
684
|
-
}
|
|
685
|
-
if (interval === 10 || interval === 11) {
|
|
686
|
-
return tones.has(interval) ? "seventh" : null;
|
|
687
|
-
}
|
|
688
|
-
return null;
|
|
689
|
-
}
|
|
690
|
-
function classifyTriad(thirdIc, fifthIc) {
|
|
691
|
-
if (thirdIc === 4 && fifthIc === 8) {
|
|
692
|
-
return "aug";
|
|
693
|
-
}
|
|
694
|
-
if (thirdIc === 4 && fifthIc === 6) {
|
|
695
|
-
return "majb5";
|
|
696
|
-
}
|
|
697
|
-
if (thirdIc === 3 && fifthIc === 6) {
|
|
698
|
-
return "dim";
|
|
699
|
-
}
|
|
700
|
-
if (thirdIc === 3) {
|
|
701
|
-
return "min";
|
|
702
|
-
}
|
|
703
|
-
return "maj";
|
|
704
|
-
}
|
|
705
|
-
function classifySeventh(thirdIc, fifthIc, seventhIc) {
|
|
706
|
-
if (thirdIc === 3 && fifthIc === 6 && seventhIc === 9) {
|
|
707
|
-
return "dim7";
|
|
708
|
-
}
|
|
709
|
-
if (thirdIc === 3 && fifthIc === 6 && seventhIc === 10) {
|
|
710
|
-
return "m7b5";
|
|
711
|
-
}
|
|
712
|
-
if (thirdIc === 3 && seventhIc === 11) {
|
|
713
|
-
return "minMaj7";
|
|
714
|
-
}
|
|
715
|
-
if (thirdIc === 3) {
|
|
716
|
-
return "min7";
|
|
717
|
-
}
|
|
718
|
-
if (fifthIc === 8 && seventhIc === 10) {
|
|
719
|
-
return "aug7";
|
|
720
|
-
}
|
|
721
|
-
if (fifthIc === 8 && seventhIc === 11) {
|
|
722
|
-
return "augMaj7";
|
|
723
|
-
}
|
|
724
|
-
if (seventhIc === 11) {
|
|
725
|
-
return "maj7";
|
|
726
|
-
}
|
|
727
|
-
return "dom7";
|
|
728
|
-
}
|
|
729
|
-
function stackThirds(degree, key, size) {
|
|
730
|
-
const tones = scaleTonesInDegreeOrder(key);
|
|
731
|
-
const length = tones.length;
|
|
732
|
-
if (length !== 7) {
|
|
733
|
-
throw new Error(
|
|
734
|
-
`diatonic chord stacking requires a heptatonic scale (received ${length} tones)`
|
|
735
|
-
);
|
|
736
|
-
}
|
|
737
|
-
const idx = (step) => ((degree + step) % length + length) % length;
|
|
738
|
-
const rootPc = tones[idx(0)] ?? pitchClass3(key.rootPc);
|
|
739
|
-
const offsets = [0, 2, 4, 6].slice(0, size).map((step) => {
|
|
740
|
-
const pc = tones[idx(step)] ?? rootPc;
|
|
741
|
-
return ((pc - rootPc) % 12 + 12) % 12;
|
|
742
|
-
});
|
|
743
|
-
const thirdIc = offsets[1] ?? 4;
|
|
744
|
-
const fifthIc = offsets[2] ?? 7;
|
|
745
|
-
const quality = size === 3 ? classifyTriad(thirdIc, fifthIc) : classifySeventh(thirdIc, fifthIc, offsets[3] ?? 11);
|
|
746
|
-
return { rootPc, quality, intervals: offsets };
|
|
747
|
-
}
|
|
748
|
-
function diatonicTriad(degree, key) {
|
|
749
|
-
return stackThirds(degree, key, 3);
|
|
750
|
-
}
|
|
751
12
|
|
|
752
|
-
// src/core/interval/index.ts
|
|
753
|
-
function simpleInterval(semitones) {
|
|
754
|
-
return Math.abs(semitones) % 12;
|
|
755
|
-
}
|
|
756
|
-
function isConsonantInterval(semitones, twoVoice = true) {
|
|
757
|
-
const pc = simpleInterval(semitones);
|
|
758
|
-
if (pc === 5) {
|
|
759
|
-
return !twoVoice;
|
|
760
|
-
}
|
|
761
|
-
return pc === 0 || pc === 3 || pc === 4 || pc === 7 || pc === 8 || pc === 9;
|
|
762
|
-
}
|
|
763
13
|
|
|
764
|
-
// src/theory/counterpoint/index.ts
|
|
765
|
-
function createsVerticalDissonance(a, b, twoVoice) {
|
|
766
|
-
return !isConsonantInterval(a - b, twoVoice);
|
|
767
|
-
}
|
|
768
|
-
function isForbiddenMelodicLeap(prev, cur) {
|
|
769
|
-
const semis = Math.abs(cur - prev);
|
|
770
|
-
return semis === 6 || semis === 10 || semis === 11 || semis > 12;
|
|
771
|
-
}
|
|
772
|
-
function simpleClass(semitones) {
|
|
773
|
-
return Math.abs(semitones) % 12;
|
|
774
|
-
}
|
|
775
|
-
function isPerfectClass(cls) {
|
|
776
|
-
return cls === 0 || cls === 7;
|
|
777
|
-
}
|
|
778
|
-
function similarMotion(aMove, bMove) {
|
|
779
|
-
return aMove !== 0 && bMove !== 0 && aMove > 0 === bMove > 0;
|
|
780
|
-
}
|
|
781
|
-
function bothVoicesMove(aMove, bMove) {
|
|
782
|
-
return aMove !== 0 && bMove !== 0;
|
|
783
|
-
}
|
|
784
|
-
function createsParallelPerfect(aPrev, aCur, bPrev, bCur) {
|
|
785
|
-
if (!bothVoicesMove(aCur - aPrev, bCur - bPrev)) {
|
|
786
|
-
return false;
|
|
787
|
-
}
|
|
788
|
-
const nowClass = simpleClass(aCur - bCur);
|
|
789
|
-
const prevClass = simpleClass(aPrev - bPrev);
|
|
790
|
-
return isPerfectClass(nowClass) && nowClass === prevClass;
|
|
791
|
-
}
|
|
792
|
-
function createsHiddenParallelPerfect(aPrev, aCur, bPrev, bCur) {
|
|
793
|
-
const aMove = aCur - aPrev;
|
|
794
|
-
const bMove = bCur - bPrev;
|
|
795
|
-
if (!similarMotion(aMove, bMove)) {
|
|
796
|
-
return false;
|
|
797
|
-
}
|
|
798
|
-
const nowClass = simpleClass(aCur - bCur);
|
|
799
|
-
const prevClass = simpleClass(aPrev - bPrev);
|
|
800
|
-
if (!isPerfectClass(nowClass) || prevClass === nowClass) {
|
|
801
|
-
return false;
|
|
802
|
-
}
|
|
803
|
-
const upperMove = aCur >= bCur ? aMove : bMove;
|
|
804
|
-
if (Math.abs(upperMove) <= 2) {
|
|
805
|
-
return false;
|
|
806
|
-
}
|
|
807
|
-
return true;
|
|
808
|
-
}
|
|
809
14
|
|
|
810
|
-
// src/theory/safety/index.ts
|
|
811
|
-
var SUGGESTION_WINDOW = 12;
|
|
812
|
-
var MAX_SUGGESTIONS = 3;
|
|
813
|
-
var REASON_SEVERITY = [
|
|
814
|
-
[2 /* Tension */, 1 /* Warning */, 1 /* Warning */],
|
|
815
|
-
[4 /* AvoidNote */, 1 /* Warning */, 2 /* Dissonant */],
|
|
816
|
-
[8 /* ScaleTone */, 1 /* Warning */, 1 /* Warning */],
|
|
817
|
-
[16 /* NonScale */, 2 /* Dissonant */, 2 /* Dissonant */],
|
|
818
|
-
[32 /* OutOfRange */, 1 /* Warning */, 1 /* Warning */],
|
|
819
|
-
[64 /* Tritone */, 2 /* Dissonant */, 2 /* Dissonant */],
|
|
820
|
-
[128 /* LargeLeap */, 1 /* Warning */, 2 /* Dissonant */],
|
|
821
|
-
[1024 /* VerticalDissonance */, 2 /* Dissonant */, 2 /* Dissonant */],
|
|
822
|
-
[2048 /* ParallelPerfect */, 1 /* Warning */, 2 /* Dissonant */],
|
|
823
|
-
[4096 /* HiddenParallel */, 1 /* Warning */, 2 /* Dissonant */],
|
|
824
|
-
[8192 /* VoiceCrossing */, 1 /* Warning */, 2 /* Dissonant */],
|
|
825
|
-
[16384 /* Suspension */, 2 /* Dissonant */, 2 /* Dissonant */],
|
|
826
|
-
[32768 /* NeedsResolution */, 1 /* Warning */, 2 /* Dissonant */]
|
|
827
|
-
];
|
|
828
|
-
function minimumSafetyForReasons(reasons, profile) {
|
|
829
|
-
let minimum = 0 /* Safe */;
|
|
830
|
-
const severityIndex = profile === "strict" ? 2 : 1;
|
|
831
|
-
for (const entry of REASON_SEVERITY) {
|
|
832
|
-
if (reasons & entry[0]) {
|
|
833
|
-
minimum = Math.max(minimum, entry[severityIndex]);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
return minimum;
|
|
837
|
-
}
|
|
838
|
-
function pitchClass4(pitch) {
|
|
839
|
-
return (Math.trunc(pitch) % 12 + 12) % 12;
|
|
840
|
-
}
|
|
841
|
-
function intervalAboveRoot(pitch, chord) {
|
|
842
|
-
return ((pitchClass4(pitch) - pitchClass4(chord.rootPc)) % 12 + 12) % 12;
|
|
843
|
-
}
|
|
844
|
-
function isChordMember(pitch, chord) {
|
|
845
|
-
return chordPitchClasses(chord).includes(pitchClass4(pitch));
|
|
846
|
-
}
|
|
847
|
-
function avoidsFourth(chord) {
|
|
848
|
-
return chord.intervals.includes(4) && !chord.intervals.includes(5);
|
|
849
|
-
}
|
|
850
|
-
function avoidsMajorSeventh(chord) {
|
|
851
|
-
return chord.intervals.includes(4) && chord.intervals.includes(10);
|
|
852
|
-
}
|
|
853
|
-
function stepResolution(pitch, chord) {
|
|
854
|
-
for (let delta = 1; delta <= 2; delta += 1) {
|
|
855
|
-
if (isChordMember(pitch - delta, chord)) {
|
|
856
|
-
return pitch - delta;
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
return void 0;
|
|
860
|
-
}
|
|
861
|
-
function evaluateSafety(q) {
|
|
862
|
-
assertSafetyContext(q);
|
|
863
|
-
assertFiniteNumber(q.candidatePitch, "candidatePitch");
|
|
864
|
-
return evaluateInternal(q, true);
|
|
865
|
-
}
|
|
866
|
-
function assertSafetyContext(q) {
|
|
867
|
-
if (q.prevPitch !== void 0) assertFiniteNumber(q.prevPitch, "prevPitch");
|
|
868
|
-
if (q.vocalLow !== void 0) assertFiniteNumber(q.vocalLow, "vocalLow");
|
|
869
|
-
if (q.vocalHigh !== void 0) assertFiniteNumber(q.vocalHigh, "vocalHigh");
|
|
870
|
-
if (q.vocalLow !== void 0 && q.vocalHigh !== void 0 && q.vocalLow > q.vocalHigh) {
|
|
871
|
-
throw new RangeError("vocalLow must not exceed vocalHigh");
|
|
872
|
-
}
|
|
873
|
-
assertGenerationBudget(q.otherVoices.length, "other voices");
|
|
874
|
-
for (let index = 0; index < q.otherVoices.length; index += 1) {
|
|
875
|
-
const voice = q.otherVoices[index];
|
|
876
|
-
if (!voice) continue;
|
|
877
|
-
assertFiniteNumber(voice.pitch, `otherVoices[${index}].pitch`);
|
|
878
|
-
if (voice.prevPitch !== void 0) {
|
|
879
|
-
assertFiniteNumber(voice.prevPitch, `otherVoices[${index}].prevPitch`);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
function evaluateInternal(q, collectSuggestions) {
|
|
884
|
-
let reasons = 0;
|
|
885
|
-
let level = 0 /* Safe */;
|
|
886
|
-
let resolveTo;
|
|
887
|
-
const raise = (l) => {
|
|
888
|
-
if (l > level) {
|
|
889
|
-
level = l;
|
|
890
|
-
}
|
|
891
|
-
};
|
|
892
|
-
const avoidSeverity = q.profile === "strict" ? 2 /* Dissonant */ : 1 /* Warning */;
|
|
893
|
-
const parallelSeverity = avoidSeverity;
|
|
894
|
-
const pitch = q.candidatePitch;
|
|
895
|
-
if (q.vocalLow !== void 0 && pitch < q.vocalLow || q.vocalHigh !== void 0 && pitch > q.vocalHigh) {
|
|
896
|
-
reasons |= 32 /* OutOfRange */;
|
|
897
|
-
raise(1 /* Warning */);
|
|
898
|
-
}
|
|
899
|
-
const inScale = isScaleTone(pitch, q.key);
|
|
900
|
-
const chord = q.chord;
|
|
901
|
-
if (chord && isChordMember(pitch, chord)) {
|
|
902
|
-
reasons |= 1 /* ChordTone */;
|
|
903
|
-
} else if (chord) {
|
|
904
|
-
const ic = intervalAboveRoot(pitch, chord);
|
|
905
|
-
const isTension = ic === 2 || ic === 5 || ic === 9;
|
|
906
|
-
const avoid = ic === 5 && avoidsFourth(chord) || ic === 11 && avoidsMajorSeventh(chord);
|
|
907
|
-
if (avoid) {
|
|
908
|
-
reasons |= 4 /* AvoidNote */;
|
|
909
|
-
if (isTension) {
|
|
910
|
-
reasons |= 2 /* Tension */;
|
|
911
|
-
}
|
|
912
|
-
reasons |= 32768 /* NeedsResolution */;
|
|
913
|
-
resolveTo = stepResolution(pitch, chord);
|
|
914
|
-
raise(avoidSeverity);
|
|
915
|
-
} else if (isTension && inScale) {
|
|
916
|
-
reasons |= 2 /* Tension */;
|
|
917
|
-
raise(1 /* Warning */);
|
|
918
|
-
} else if (inScale) {
|
|
919
|
-
reasons |= 8 /* ScaleTone */;
|
|
920
|
-
raise(1 /* Warning */);
|
|
921
|
-
} else {
|
|
922
|
-
reasons |= 16 /* NonScale */;
|
|
923
|
-
raise(2 /* Dissonant */);
|
|
924
|
-
}
|
|
925
|
-
} else if (inScale) {
|
|
926
|
-
reasons |= 8 /* ScaleTone */;
|
|
927
|
-
raise(1 /* Warning */);
|
|
928
|
-
} else {
|
|
929
|
-
reasons |= 16 /* NonScale */;
|
|
930
|
-
raise(2 /* Dissonant */);
|
|
931
|
-
}
|
|
932
|
-
if (chord && !(chord.intervals.includes(4) && chord.intervals.includes(10))) {
|
|
933
|
-
const ic = intervalAboveRoot(pitch, chord);
|
|
934
|
-
const toneOffsets = chord.intervals.map((i) => (i % 12 + 12) % 12);
|
|
935
|
-
if (toneOffsets.some((t) => Math.abs(ic - t) === 6)) {
|
|
936
|
-
reasons |= 64 /* Tritone */;
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
if (q.prevPitch !== void 0) {
|
|
940
|
-
const pc = Math.abs(pitch - q.prevPitch) % 12;
|
|
941
|
-
if (pc === 6) {
|
|
942
|
-
reasons |= 64 /* Tritone */;
|
|
943
|
-
raise(2 /* Dissonant */);
|
|
944
|
-
}
|
|
945
|
-
if (isForbiddenMelodicLeap(q.prevPitch, pitch)) {
|
|
946
|
-
reasons |= 128 /* LargeLeap */;
|
|
947
|
-
raise(parallelSeverity);
|
|
948
|
-
}
|
|
949
|
-
if (pc === 1) {
|
|
950
|
-
reasons |= 256 /* MinorSecond */;
|
|
951
|
-
}
|
|
952
|
-
if (pc === 11) {
|
|
953
|
-
reasons |= 512 /* MajorSeventh */;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
if (q.strongBeat) {
|
|
957
|
-
const twoVoice = q.otherVoices.length === 1;
|
|
958
|
-
const prev = q.prevPitch;
|
|
959
|
-
for (const ov of q.otherVoices) {
|
|
960
|
-
if (!createsVerticalDissonance(pitch, ov.pitch, twoVoice)) {
|
|
961
|
-
continue;
|
|
962
|
-
}
|
|
963
|
-
reasons |= 1024 /* VerticalDissonance */;
|
|
964
|
-
raise(2 /* Dissonant */);
|
|
965
|
-
if (prev !== void 0 && pitch === prev && ov.prevPitch !== void 0 && !createsVerticalDissonance(prev, ov.prevPitch, twoVoice)) {
|
|
966
|
-
reasons |= 16384 /* Suspension */;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
if (q.prevPitch !== void 0) {
|
|
971
|
-
const prev = q.prevPitch;
|
|
972
|
-
for (const ov of q.otherVoices) {
|
|
973
|
-
if (ov.prevPitch === void 0) {
|
|
974
|
-
continue;
|
|
975
|
-
}
|
|
976
|
-
if (createsParallelPerfect(prev, pitch, ov.prevPitch, ov.pitch)) {
|
|
977
|
-
reasons |= 2048 /* ParallelPerfect */;
|
|
978
|
-
raise(parallelSeverity);
|
|
979
|
-
}
|
|
980
|
-
if (createsHiddenParallelPerfect(prev, pitch, ov.prevPitch, ov.pitch)) {
|
|
981
|
-
reasons |= 4096 /* HiddenParallel */;
|
|
982
|
-
raise(parallelSeverity);
|
|
983
|
-
}
|
|
984
|
-
const crossedNow = pitch - ov.pitch;
|
|
985
|
-
const crossedPrev = prev - ov.prevPitch;
|
|
986
|
-
if (crossedNow !== 0 && crossedPrev !== 0 && crossedNow > 0 !== crossedPrev > 0) {
|
|
987
|
-
reasons |= 8192 /* VoiceCrossing */;
|
|
988
|
-
raise(parallelSeverity);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
raise(minimumSafetyForReasons(reasons, q.profile));
|
|
993
|
-
const result = { safety: level, reasons, rationale: describe(reasons, q) };
|
|
994
|
-
if (resolveTo !== void 0) {
|
|
995
|
-
result.resolveTo = resolveTo;
|
|
996
|
-
}
|
|
997
|
-
if (collectSuggestions && level !== 0 /* Safe */) {
|
|
998
|
-
const suggestions = findSafeNearby(q, pitch);
|
|
999
|
-
if (suggestions.length > 0) {
|
|
1000
|
-
result.suggestions = suggestions;
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
return result;
|
|
1004
|
-
}
|
|
1005
|
-
function findSafeNearby(q, candidate) {
|
|
1006
|
-
const out = [];
|
|
1007
|
-
for (let d = 1; d <= SUGGESTION_WINDOW; d += 1) {
|
|
1008
|
-
for (const p of [candidate - d, candidate + d]) {
|
|
1009
|
-
if (evaluateInternal({ ...q, candidatePitch: p }, false).safety === 0 /* Safe */) {
|
|
1010
|
-
out.push(p);
|
|
1011
|
-
if (out.length >= MAX_SUGGESTIONS) {
|
|
1012
|
-
return out;
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
return out;
|
|
1018
|
-
}
|
|
1019
|
-
function describe(reasons, q) {
|
|
1020
|
-
if (reasons & 32 /* OutOfRange */) {
|
|
1021
|
-
return "Outside the target vocal range";
|
|
1022
|
-
}
|
|
1023
|
-
if (reasons & 16384 /* Suspension */) {
|
|
1024
|
-
return "Prepared suspension \u2014 a held consonance now dissonant, awaiting resolution";
|
|
1025
|
-
}
|
|
1026
|
-
if (reasons & 1024 /* VerticalDissonance */) {
|
|
1027
|
-
return "Dissonant against a sounding voice on a strong beat";
|
|
1028
|
-
}
|
|
1029
|
-
if (reasons & 4 /* AvoidNote */) {
|
|
1030
|
-
return "Avoid note \u2014 clashes with a chord tone a semitone away";
|
|
1031
|
-
}
|
|
1032
|
-
if (reasons & 2048 /* ParallelPerfect */) {
|
|
1033
|
-
return "Parallel perfect interval with another voice";
|
|
1034
|
-
}
|
|
1035
|
-
if (reasons & 4096 /* HiddenParallel */) {
|
|
1036
|
-
return "Hidden perfect interval by similar motion";
|
|
1037
|
-
}
|
|
1038
|
-
if (reasons & 8192 /* VoiceCrossing */) {
|
|
1039
|
-
return "Crosses another voice";
|
|
1040
|
-
}
|
|
1041
|
-
if (reasons & 1 /* ChordTone */) {
|
|
1042
|
-
return "Chord tone";
|
|
1043
|
-
}
|
|
1044
|
-
if (reasons & 2 /* Tension */) {
|
|
1045
|
-
return "Chord tension";
|
|
1046
|
-
}
|
|
1047
|
-
if (reasons & 16 /* NonScale */) {
|
|
1048
|
-
return "Outside the key";
|
|
1049
|
-
}
|
|
1050
|
-
if (reasons & 8 /* ScaleTone */) {
|
|
1051
|
-
return "Scale tone, not in the chord";
|
|
1052
|
-
}
|
|
1053
|
-
return q.chord ? "Placeable" : "No chord context";
|
|
1054
|
-
}
|
|
1055
|
-
function enumerateSafePitches(q, pitchLow, pitchHigh) {
|
|
1056
|
-
assertSafetyContext(q);
|
|
1057
|
-
const chordTones = [];
|
|
1058
|
-
const others = [];
|
|
1059
|
-
assertInteger(pitchLow, "pitchLow");
|
|
1060
|
-
assertInteger(pitchHigh, "pitchHigh");
|
|
1061
|
-
if (pitchLow > pitchHigh) {
|
|
1062
|
-
throw new RangeError(`pitchLow must not exceed pitchHigh; received ${pitchLow} > ${pitchHigh}`);
|
|
1063
|
-
}
|
|
1064
|
-
assertGenerationBudget(pitchHigh - pitchLow + 1, "safe pitch candidates");
|
|
1065
|
-
for (let pitch = pitchHigh; pitch >= pitchLow; pitch -= 1) {
|
|
1066
|
-
const result = evaluateInternal({ ...q, candidatePitch: pitch }, false);
|
|
1067
|
-
if (result.safety === 2 /* Dissonant */) {
|
|
1068
|
-
continue;
|
|
1069
|
-
}
|
|
1070
|
-
if (result.reasons & 1 /* ChordTone */) {
|
|
1071
|
-
chordTones.push(pitch);
|
|
1072
|
-
} else {
|
|
1073
|
-
others.push(pitch);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
return [...chordTones, ...others];
|
|
1077
|
-
}
|
|
1078
15
|
|
|
1079
|
-
// src/generate/countermelody/index.ts
|
|
1080
|
-
var DEFAULT_TS2 = { numerator: 4, denominator: 4 };
|
|
1081
|
-
var EPS4 = 1e-9;
|
|
1082
|
-
var GRID_STEP = 0.5;
|
|
1083
|
-
var MIN_ONSET_GAP = 1;
|
|
1084
|
-
var REGISTER_OFFSET = 12;
|
|
1085
|
-
var DEFAULT_HALF_RANGE = 10;
|
|
1086
|
-
var HOLD_ON_BEAT_PROB = 0.55;
|
|
1087
|
-
var HOLD_OFF_BEAT_PROB = 0.2;
|
|
1088
|
-
var REINFORCE_PROB = 0.35;
|
|
1089
|
-
var DEFAULT_MELODY_VELOCITY = 96;
|
|
1090
|
-
var VELOCITY_DROP = 16;
|
|
1091
|
-
function isWholeBeat(beat) {
|
|
1092
|
-
return Math.abs(beat - Math.round(beat)) < EPS4;
|
|
1093
|
-
}
|
|
1094
|
-
function melodyNoteAt(melody, beat) {
|
|
1095
|
-
return melody.at(beat)?.note;
|
|
1096
|
-
}
|
|
1097
|
-
function melodyAttacksAt(melody, beat) {
|
|
1098
|
-
return melody.attacksAt(beat);
|
|
1099
|
-
}
|
|
1100
|
-
function melodySnapshotAt(melody, beat) {
|
|
1101
|
-
const current = melodyNoteAt(melody, beat);
|
|
1102
|
-
if (current === void 0) {
|
|
1103
|
-
return void 0;
|
|
1104
|
-
}
|
|
1105
|
-
const attacked = Math.abs(current.startBeat - beat) < EPS4;
|
|
1106
|
-
const previous = attacked ? melodyNoteAt(melody, beat - EPS4 * 2) : current;
|
|
1107
|
-
const snapshot = { pitch: current.pitch };
|
|
1108
|
-
if (previous !== void 0) {
|
|
1109
|
-
snapshot.prevPitch = previous.pitch;
|
|
1110
|
-
}
|
|
1111
|
-
return snapshot;
|
|
1112
|
-
}
|
|
1113
|
-
function followOnsets(melody) {
|
|
1114
|
-
return [...new Set(melody.notes.map(({ note }) => note.startBeat))];
|
|
1115
|
-
}
|
|
1116
|
-
function complementOnsets(melody, ts, spanStart, spanEnd, rng) {
|
|
1117
|
-
const onsets = [];
|
|
1118
|
-
const gridStart = Math.floor(spanStart / GRID_STEP) * GRID_STEP;
|
|
1119
|
-
let last = Number.NEGATIVE_INFINITY;
|
|
1120
|
-
for (let beat = gridStart; beat < spanEnd - EPS4; beat += GRID_STEP) {
|
|
1121
|
-
const attacked = melodyAttacksAt(melody, beat);
|
|
1122
|
-
const sounding = melodyNoteAt(melody, beat) !== void 0;
|
|
1123
|
-
let place = false;
|
|
1124
|
-
if (!attacked && !sounding) {
|
|
1125
|
-
place = isWholeBeat(beat);
|
|
1126
|
-
} else if (!attacked) {
|
|
1127
|
-
place = rng.prob(isWholeBeat(beat) ? HOLD_ON_BEAT_PROB : HOLD_OFF_BEAT_PROB);
|
|
1128
|
-
} else if (isStrongBeat(beat, ts)) {
|
|
1129
|
-
place = rng.prob(REINFORCE_PROB);
|
|
1130
|
-
}
|
|
1131
|
-
if (place && beat - last >= MIN_ONSET_GAP - EPS4) {
|
|
1132
|
-
onsets.push(beat);
|
|
1133
|
-
last = beat;
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
return onsets;
|
|
1137
|
-
}
|
|
1138
|
-
function scoreCandidate(pitch, melPitch, melPrev, prevPitch, chord, center) {
|
|
1139
|
-
let score = 0;
|
|
1140
|
-
if (melPitch !== void 0) {
|
|
1141
|
-
const ic = Math.abs(pitch - melPitch) % 12;
|
|
1142
|
-
if (ic === 3 || ic === 4 || ic === 8 || ic === 9) {
|
|
1143
|
-
score += 2;
|
|
1144
|
-
} else if (ic === 0 || ic === 7) {
|
|
1145
|
-
score += 0.5;
|
|
1146
|
-
} else if (ic === 1 || ic === 2 || ic === 6 || ic === 10 || ic === 11) {
|
|
1147
|
-
score -= 2;
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
if (chord && chordToneRole(pitch, chord) !== null) {
|
|
1151
|
-
score += 1;
|
|
1152
|
-
}
|
|
1153
|
-
if (prevPitch !== void 0) {
|
|
1154
|
-
const move = pitch - prevPitch;
|
|
1155
|
-
const dist = Math.abs(move);
|
|
1156
|
-
score -= dist * 0.3;
|
|
1157
|
-
if (dist > 0 && dist <= 2) {
|
|
1158
|
-
score += 1;
|
|
1159
|
-
}
|
|
1160
|
-
if (isForbiddenMelodicLeap(prevPitch, pitch)) {
|
|
1161
|
-
score -= 6;
|
|
1162
|
-
}
|
|
1163
|
-
if (melPitch !== void 0 && melPrev !== void 0) {
|
|
1164
|
-
const melMove = melPitch - melPrev;
|
|
1165
|
-
if (move > 0 && melMove < 0 || move < 0 && melMove > 0) {
|
|
1166
|
-
score += 2;
|
|
1167
|
-
} else if (move === 0 || melMove === 0) {
|
|
1168
|
-
score += 1;
|
|
1169
|
-
} else {
|
|
1170
|
-
score -= 1;
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
score -= Math.abs(pitch - center) * 0.02;
|
|
1175
|
-
return score;
|
|
1176
|
-
}
|
|
1177
|
-
function heldNoteBoundaries(melody, startBeat, endBeat, chordChangeBeats) {
|
|
1178
|
-
const boundaries = /* @__PURE__ */ new Set([startBeat]);
|
|
1179
|
-
for (const onset of melody.onsetsBetween(startBeat, endBeat)) {
|
|
1180
|
-
boundaries.add(onset);
|
|
1181
|
-
}
|
|
1182
|
-
for (const beat of chordChangeBeats) {
|
|
1183
|
-
if (beat > startBeat + EPS4 && beat < endBeat - EPS4) {
|
|
1184
|
-
boundaries.add(beat);
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
const firstGrid = Math.floor(startBeat / GRID_STEP + 1) * GRID_STEP;
|
|
1188
|
-
for (let beat = firstGrid; beat < endBeat - EPS4; beat += GRID_STEP) {
|
|
1189
|
-
boundaries.add(beat);
|
|
1190
|
-
}
|
|
1191
|
-
return [...boundaries].sort((a, b) => a - b);
|
|
1192
|
-
}
|
|
1193
|
-
function heldPitchSafety(pitch, startBeat, endBeat, prevPitch, melody, opts, profile, register, low, high, ts) {
|
|
1194
|
-
let worst = 0 /* Safe */;
|
|
1195
|
-
const boundaries = heldNoteBoundaries(melody, startBeat, endBeat, opts.chordChangeBeats ?? []);
|
|
1196
|
-
for (const boundary of boundaries) {
|
|
1197
|
-
const melodyVoice = melodySnapshotAt(melody, boundary);
|
|
1198
|
-
if (melodyVoice !== void 0 && (register === "below" && pitch >= melodyVoice.pitch || register === "above" && pitch <= melodyVoice.pitch)) {
|
|
1199
|
-
return null;
|
|
1200
|
-
}
|
|
1201
|
-
const atCounterOnset = Math.abs(boundary - startBeat) < EPS4;
|
|
1202
|
-
const result = evaluateSafety({
|
|
1203
|
-
profile,
|
|
1204
|
-
candidatePitch: pitch,
|
|
1205
|
-
prevPitch: atCounterOnset ? prevPitch : pitch,
|
|
1206
|
-
chord: opts.chordAt(boundary),
|
|
1207
|
-
key: opts.key,
|
|
1208
|
-
otherVoices: melodyVoice ? [melodyVoice] : [],
|
|
1209
|
-
strongBeat: isStrongBeat(boundary, ts),
|
|
1210
|
-
vocalLow: low,
|
|
1211
|
-
vocalHigh: high
|
|
1212
|
-
});
|
|
1213
|
-
worst = Math.max(worst, result.safety);
|
|
1214
|
-
if (worst === 2 /* Dissonant */) {
|
|
1215
|
-
return worst;
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
return worst;
|
|
1219
|
-
}
|
|
1220
|
-
function generateCounterMelody(opts) {
|
|
1221
|
-
const melody = createNoteEventIndex(opts.melody);
|
|
1222
|
-
if (melody.notes.length === 0) {
|
|
1223
|
-
return [];
|
|
1224
|
-
}
|
|
1225
|
-
const ts = opts.ts ?? DEFAULT_TS2;
|
|
1226
|
-
assertTimeSignature(ts);
|
|
1227
|
-
const register = opts.register ?? "below";
|
|
1228
|
-
const rhythm = opts.rhythm ?? "complement";
|
|
1229
|
-
const profile = opts.profile ?? "pop";
|
|
1230
|
-
const rng = createRng(opts.seed ?? 0);
|
|
1231
|
-
const spanStart = melody.notes[0]?.note.startBeat ?? 0;
|
|
1232
|
-
const spanEnd = melody.notes.reduce((end, n) => Math.max(end, n.endBeat), spanStart);
|
|
1233
|
-
assertGenerationBudget(Math.ceil((spanEnd - spanStart) / GRID_STEP), "countermelody grid");
|
|
1234
|
-
const meanPitch = melody.notes.reduce((sum, n) => sum + n.note.pitch, 0) / melody.notes.length;
|
|
1235
|
-
const defaultCenter = register === "below" ? meanPitch - REGISTER_OFFSET : meanPitch + REGISTER_OFFSET;
|
|
1236
|
-
const low = opts.pitchLow ?? Math.round(defaultCenter) - DEFAULT_HALF_RANGE;
|
|
1237
|
-
const high = opts.pitchHigh ?? Math.round(defaultCenter) + DEFAULT_HALF_RANGE;
|
|
1238
|
-
assertInteger(low, "countermelody pitchLow");
|
|
1239
|
-
assertInteger(high, "countermelody pitchHigh");
|
|
1240
|
-
if (low > high) {
|
|
1241
|
-
throw new RangeError(
|
|
1242
|
-
`countermelody pitchLow must not exceed pitchHigh; received ${low} > ${high}`
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
|
-
assertGenerationBudget(high - low + 1, "countermelody pitch candidates");
|
|
1246
|
-
assertGenerationBudget(opts.chordChangeBeats?.length ?? 0, "chord change beats");
|
|
1247
|
-
for (let index = 0; index < (opts.chordChangeBeats?.length ?? 0); index += 1) {
|
|
1248
|
-
assertRange(
|
|
1249
|
-
opts.chordChangeBeats?.[index] ?? Number.NaN,
|
|
1250
|
-
0,
|
|
1251
|
-
Number.MAX_SAFE_INTEGER,
|
|
1252
|
-
`chordChangeBeats[${index}]`
|
|
1253
|
-
);
|
|
1254
|
-
}
|
|
1255
|
-
const center = (low + high) / 2;
|
|
1256
|
-
const onsets = rhythm === "follow" ? followOnsets(melody) : complementOnsets(melody, ts, spanStart, spanEnd, rng);
|
|
1257
|
-
assertGenerationBudget(onsets.length * (high - low + 1), "countermelody search");
|
|
1258
|
-
const out = [];
|
|
1259
|
-
let prevPitch;
|
|
1260
|
-
for (let onsetIndex = 0; onsetIndex < onsets.length; onsetIndex += 1) {
|
|
1261
|
-
const beat = onsets[onsetIndex];
|
|
1262
|
-
if (beat === void 0) {
|
|
1263
|
-
continue;
|
|
1264
|
-
}
|
|
1265
|
-
const nextOnset = onsets[onsetIndex + 1];
|
|
1266
|
-
const endBeat = nextOnset ?? Math.max(spanEnd, beat + GRID_STEP);
|
|
1267
|
-
const melNote = melodyNoteAt(melody, beat);
|
|
1268
|
-
const melPitch = melNote?.pitch;
|
|
1269
|
-
const melodyVoice = melodySnapshotAt(melody, beat);
|
|
1270
|
-
const melPrev = melodyVoice?.prevPitch;
|
|
1271
|
-
const chord = opts.chordAt(beat);
|
|
1272
|
-
const otherVoices = melodyVoice !== void 0 ? [melodyVoice] : [];
|
|
1273
|
-
const candidates = enumerateSafePitches(
|
|
1274
|
-
{
|
|
1275
|
-
profile,
|
|
1276
|
-
prevPitch,
|
|
1277
|
-
chord,
|
|
1278
|
-
key: opts.key,
|
|
1279
|
-
otherVoices,
|
|
1280
|
-
strongBeat: isStrongBeat(beat, ts),
|
|
1281
|
-
vocalLow: low,
|
|
1282
|
-
vocalHigh: high
|
|
1283
|
-
},
|
|
1284
|
-
low,
|
|
1285
|
-
high
|
|
1286
|
-
);
|
|
1287
|
-
let bestPitch;
|
|
1288
|
-
let bestWorstSafety = 2 /* Dissonant */;
|
|
1289
|
-
let bestScore = Number.NEGATIVE_INFINITY;
|
|
1290
|
-
for (const candidate of candidates) {
|
|
1291
|
-
const worstSafety = heldPitchSafety(
|
|
1292
|
-
candidate,
|
|
1293
|
-
beat,
|
|
1294
|
-
endBeat,
|
|
1295
|
-
prevPitch,
|
|
1296
|
-
melody,
|
|
1297
|
-
opts,
|
|
1298
|
-
profile,
|
|
1299
|
-
register,
|
|
1300
|
-
low,
|
|
1301
|
-
high,
|
|
1302
|
-
ts
|
|
1303
|
-
);
|
|
1304
|
-
if (worstSafety === null || worstSafety === 2 /* Dissonant */) {
|
|
1305
|
-
continue;
|
|
1306
|
-
}
|
|
1307
|
-
if (melPitch !== void 0) {
|
|
1308
|
-
if (prevPitch !== void 0 && melPrev !== void 0 && // Parallel octaves are the perfect-class-zero case of this predicate,
|
|
1309
|
-
// so a single check covers both parallel fifths and octaves.
|
|
1310
|
-
createsParallelPerfect(prevPitch, candidate, melPrev, melPitch)) {
|
|
1311
|
-
continue;
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
const score = scoreCandidate(candidate, melPitch, melPrev, prevPitch, chord, center);
|
|
1315
|
-
const tie = Math.abs(score - bestScore) <= EPS4 && candidate < (bestPitch ?? Number.POSITIVE_INFINITY);
|
|
1316
|
-
if (worstSafety < bestWorstSafety || worstSafety === bestWorstSafety && (score > bestScore + EPS4 || tie)) {
|
|
1317
|
-
bestWorstSafety = worstSafety;
|
|
1318
|
-
bestScore = score;
|
|
1319
|
-
bestPitch = candidate;
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
if (bestPitch === void 0) {
|
|
1323
|
-
prevPitch = void 0;
|
|
1324
|
-
continue;
|
|
1325
|
-
}
|
|
1326
|
-
const pitch = bestPitch;
|
|
1327
|
-
const velocity = Math.max(
|
|
1328
|
-
1,
|
|
1329
|
-
Math.min(127, Math.round((melNote?.velocity ?? DEFAULT_MELODY_VELOCITY) - VELOCITY_DROP))
|
|
1330
|
-
);
|
|
1331
|
-
out.push({ pitch, startBeat: beat, durationBeat: endBeat - beat, velocity });
|
|
1332
|
-
prevPitch = pitch;
|
|
1333
|
-
}
|
|
1334
|
-
return out;
|
|
1335
|
-
}
|
|
1336
16
|
|
|
1337
|
-
// src/generate/drums/internal.ts
|
|
1338
|
-
var GM = {
|
|
1339
|
-
BD: 36,
|
|
1340
|
-
SD: 38,
|
|
1341
|
-
SIDESTICK: 37,
|
|
1342
|
-
HANDCLAP: 39,
|
|
1343
|
-
CHH: 42,
|
|
1344
|
-
FHH: 44,
|
|
1345
|
-
OHH: 46,
|
|
1346
|
-
CRASH: 49,
|
|
1347
|
-
RIDE: 51,
|
|
1348
|
-
TAMBOURINE: 54,
|
|
1349
|
-
TOM_H: 50,
|
|
1350
|
-
TOM_M: 47,
|
|
1351
|
-
TOM_L: 45,
|
|
1352
|
-
SHAKER: 82
|
|
1353
|
-
};
|
|
1354
|
-
var EIGHTH = 0.5;
|
|
1355
|
-
var SIXTEENTH = 0.25;
|
|
1356
|
-
var STYLE_MAP = {
|
|
1357
|
-
// Standard pop pulse: kick on beats 1 and 3, backbeat snare on 2 and 4.
|
|
1358
|
-
standard: { style: "standard", feel: "straight", ghostBoost: false, snareBeat3: false },
|
|
1359
|
-
funk: { style: "upbeat", feel: "straight", ghostBoost: true, snareBeat3: false },
|
|
1360
|
-
shuffle: { style: "standard", feel: "shuffle", ghostBoost: false, snareBeat3: false },
|
|
1361
|
-
bossa: { style: "latin", feel: "straight", ghostBoost: false, snareBeat3: false },
|
|
1362
|
-
trap: { style: "trap", feel: "straight", ghostBoost: false, snareBeat3: true },
|
|
1363
|
-
halftime: { style: "sparse", feel: "straight", ghostBoost: false, snareBeat3: true },
|
|
1364
|
-
breakbeat: { style: "rock", feel: "straight", ghostBoost: false, snareBeat3: false },
|
|
1365
|
-
// Four-on-the-floor dance kick with idol-style auxiliary percussion.
|
|
1366
|
-
house: { style: "fourOnFloor", feel: "straight", ghostBoost: false, snareBeat3: false },
|
|
1367
|
-
// Synth-pop pulse with denser 16th hi-hats and busy kick syncopation.
|
|
1368
|
-
synthpop: { style: "synth", feel: "straight", ghostBoost: true, snareBeat3: false }
|
|
1369
|
-
};
|
|
1370
|
-
function mapStyle(style) {
|
|
1371
|
-
return STYLE_MAP[style];
|
|
1372
|
-
}
|
|
1373
|
-
function mapSection(section) {
|
|
1374
|
-
switch (section) {
|
|
1375
|
-
case "intro":
|
|
1376
|
-
return "intro";
|
|
1377
|
-
case "verse":
|
|
1378
|
-
return "a";
|
|
1379
|
-
case "prechorus":
|
|
1380
|
-
return "b";
|
|
1381
|
-
case "chorus":
|
|
1382
|
-
return "chorus";
|
|
1383
|
-
case "bridge":
|
|
1384
|
-
return "bridge";
|
|
1385
|
-
case "outro":
|
|
1386
|
-
return "outro";
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
function mapDensity(density) {
|
|
1390
|
-
if (density < 0.34) {
|
|
1391
|
-
return "thin";
|
|
1392
|
-
}
|
|
1393
|
-
if (density < 0.67) {
|
|
1394
|
-
return "normal";
|
|
1395
|
-
}
|
|
1396
|
-
return "thick";
|
|
1397
|
-
}
|
|
1398
|
-
function feelSwingAmount(feel) {
|
|
1399
|
-
if (feel === "swing") {
|
|
1400
|
-
return 0.33;
|
|
1401
|
-
}
|
|
1402
|
-
if (feel === "shuffle") {
|
|
1403
|
-
return 0.5;
|
|
1404
|
-
}
|
|
1405
|
-
return 0;
|
|
1406
|
-
}
|
|
1407
|
-
function ghostMoodCategory(style) {
|
|
1408
|
-
if (style === "sparse") {
|
|
1409
|
-
return 0 /* Calm */;
|
|
1410
|
-
}
|
|
1411
|
-
if (style === "trap" || style === "upbeat") {
|
|
1412
|
-
return 2 /* Energetic */;
|
|
1413
|
-
}
|
|
1414
|
-
return 1 /* Standard */;
|
|
1415
|
-
}
|
|
1416
|
-
function percMoodCategory(style) {
|
|
1417
|
-
switch (style) {
|
|
1418
|
-
case "sparse":
|
|
1419
|
-
return 0 /* Calm */;
|
|
1420
|
-
case "upbeat":
|
|
1421
|
-
case "latin":
|
|
1422
|
-
return 2 /* Energetic */;
|
|
1423
|
-
case "fourOnFloor":
|
|
1424
|
-
return 3 /* Idol */;
|
|
1425
|
-
case "trap":
|
|
1426
|
-
case "rock":
|
|
1427
|
-
return 4 /* RockDark */;
|
|
1428
|
-
default:
|
|
1429
|
-
return 1 /* Standard */;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
function sectionIndex(section) {
|
|
1433
|
-
switch (section) {
|
|
1434
|
-
case "intro":
|
|
1435
|
-
return 0;
|
|
1436
|
-
case "a":
|
|
1437
|
-
return 1;
|
|
1438
|
-
case "b":
|
|
1439
|
-
return 2;
|
|
1440
|
-
case "chorus":
|
|
1441
|
-
return 3;
|
|
1442
|
-
case "bridge":
|
|
1443
|
-
return 4;
|
|
1444
|
-
case "outro":
|
|
1445
|
-
return 6;
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
function sectionEnergy(section) {
|
|
1449
|
-
switch (section) {
|
|
1450
|
-
case "intro":
|
|
1451
|
-
case "outro":
|
|
1452
|
-
return "low";
|
|
1453
|
-
case "a":
|
|
1454
|
-
case "bridge":
|
|
1455
|
-
return "medium";
|
|
1456
|
-
case "b":
|
|
1457
|
-
return "high";
|
|
1458
|
-
case "chorus":
|
|
1459
|
-
return "peak";
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
function sectionVelocityMultiplier(section) {
|
|
1463
|
-
switch (section) {
|
|
1464
|
-
case "intro":
|
|
1465
|
-
case "outro":
|
|
1466
|
-
return 0.8;
|
|
1467
|
-
case "a":
|
|
1468
|
-
return 0.9;
|
|
1469
|
-
case "b":
|
|
1470
|
-
return 1;
|
|
1471
|
-
case "chorus":
|
|
1472
|
-
return 1.1;
|
|
1473
|
-
case "bridge":
|
|
1474
|
-
return 0.85;
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
function sectionDensityMultiplier(section) {
|
|
1478
|
-
switch (section) {
|
|
1479
|
-
case "intro":
|
|
1480
|
-
return 0.5;
|
|
1481
|
-
case "a":
|
|
1482
|
-
return 0.7;
|
|
1483
|
-
case "b":
|
|
1484
|
-
return 0.85;
|
|
1485
|
-
case "chorus":
|
|
1486
|
-
return 1;
|
|
1487
|
-
case "bridge":
|
|
1488
|
-
return 0.6;
|
|
1489
|
-
case "outro":
|
|
1490
|
-
return 0.6;
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
function calculateVelocity(section, beat) {
|
|
1494
|
-
const base = 80;
|
|
1495
|
-
const beatAdj = beat === 0 ? 10 : beat === 2 ? 5 : 0;
|
|
1496
|
-
const velocity = (base + beatAdj) * sectionVelocityMultiplier(section);
|
|
1497
|
-
return clampVel(velocity);
|
|
1498
|
-
}
|
|
1499
|
-
function clampVel(velocity) {
|
|
1500
|
-
return Math.max(1, Math.min(127, Math.round(velocity)));
|
|
1501
|
-
}
|
|
1502
17
|
|
|
1503
|
-
// src/generate/drums/ghost.ts
|
|
1504
|
-
var GHOST_DENSITY_TABLE = [
|
|
1505
|
-
/* intro */
|
|
1506
|
-
[0, 1, 1],
|
|
1507
|
-
/* a */
|
|
1508
|
-
[0, 1, 2],
|
|
1509
|
-
/* b */
|
|
1510
|
-
[1, 2, 2],
|
|
1511
|
-
/* chorus */
|
|
1512
|
-
[1, 2, 3],
|
|
1513
|
-
/* bridge */
|
|
1514
|
-
[1, 1, 2],
|
|
1515
|
-
/* interlude */
|
|
1516
|
-
[0, 1, 1],
|
|
1517
|
-
/* outro */
|
|
1518
|
-
[0, 1, 1]
|
|
1519
|
-
];
|
|
1520
|
-
function levelToProbability(level) {
|
|
1521
|
-
switch (level) {
|
|
1522
|
-
case 0:
|
|
1523
|
-
return 0;
|
|
1524
|
-
case 1:
|
|
1525
|
-
return 0.15;
|
|
1526
|
-
case 2:
|
|
1527
|
-
return 0.3;
|
|
1528
|
-
case 3:
|
|
1529
|
-
return 0.45;
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
function clampLevel(level) {
|
|
1533
|
-
return Math.max(0, Math.min(3, level));
|
|
1534
|
-
}
|
|
1535
|
-
function adjustForBpm(level, bpm) {
|
|
1536
|
-
if (bpm >= 160 && level !== 0) {
|
|
1537
|
-
return clampLevel(level - 1);
|
|
1538
|
-
}
|
|
1539
|
-
if (bpm <= 90 && level !== 3) {
|
|
1540
|
-
return clampLevel(level + 1);
|
|
1541
|
-
}
|
|
1542
|
-
return level;
|
|
1543
|
-
}
|
|
1544
|
-
function getGhostDensity(mood, section, backingDensity, bpm) {
|
|
1545
|
-
const row = GHOST_DENSITY_TABLE[sectionIndex(section)] ?? GHOST_DENSITY_TABLE[1];
|
|
1546
|
-
const level = adjustForBpm(row?.[mood] ?? 0, bpm);
|
|
1547
|
-
if (backingDensity === "thin" && level !== 0) {
|
|
1548
|
-
return levelToProbability(clampLevel(level - 1));
|
|
1549
|
-
}
|
|
1550
|
-
if (backingDensity === "thick" && level !== 3) {
|
|
1551
|
-
return levelToProbability(clampLevel(level + 1));
|
|
1552
|
-
}
|
|
1553
|
-
return levelToProbability(level);
|
|
1554
|
-
}
|
|
1555
|
-
function getGhostVelocity(section, beatPosition, isAfterSnare) {
|
|
1556
|
-
let base = 0.4;
|
|
1557
|
-
const even = beatPosition % 2 === 0 ? 0.05 : 0;
|
|
1558
|
-
switch (section) {
|
|
1559
|
-
case "a":
|
|
1560
|
-
base = 0.35 + even;
|
|
1561
|
-
break;
|
|
1562
|
-
case "chorus":
|
|
1563
|
-
base = 0.5 + even;
|
|
1564
|
-
break;
|
|
1565
|
-
case "bridge":
|
|
1566
|
-
base = 0.25 + even;
|
|
1567
|
-
break;
|
|
1568
|
-
case "b":
|
|
1569
|
-
base = 0.4 + even;
|
|
1570
|
-
break;
|
|
1571
|
-
case "intro":
|
|
1572
|
-
case "outro":
|
|
1573
|
-
base = 0.38;
|
|
1574
|
-
break;
|
|
1575
|
-
}
|
|
1576
|
-
if (isAfterSnare) {
|
|
1577
|
-
base += 0.1;
|
|
1578
|
-
}
|
|
1579
|
-
return Math.max(0.25, Math.min(0.65, base));
|
|
1580
|
-
}
|
|
1581
|
-
function getGhostProbabilityAtPosition(beat, sixteenthInBeat) {
|
|
1582
|
-
const nearSnare = beat === 0 && sixteenthInBeat === 3 || beat === 1 && sixteenthInBeat === 1 || beat === 2 && sixteenthInBeat === 3 || beat === 3 && sixteenthInBeat === 1;
|
|
1583
|
-
return nearSnare ? 0.6 : 0.25;
|
|
1584
|
-
}
|
|
1585
|
-
function selectGhostPositions(mood, rng) {
|
|
1586
|
-
if (mood === 2 /* Energetic */) {
|
|
1587
|
-
return ["e", "a"];
|
|
1588
|
-
}
|
|
1589
|
-
if (mood === 0 /* Calm */) {
|
|
1590
|
-
return rng.prob(0.5) ? ["e"] : [];
|
|
1591
|
-
}
|
|
1592
|
-
return ["e"];
|
|
1593
|
-
}
|
|
1594
18
|
|
|
1595
|
-
// src/generate/drums/hihat.ts
|
|
1596
|
-
var HH_16TH_BPM_THRESHOLD = 150;
|
|
1597
|
-
var FHH_VEL_MIN = 45;
|
|
1598
|
-
var FHH_VEL_MAX = 60;
|
|
1599
|
-
var OHH_VEL_BOOST = 7;
|
|
1600
|
-
function shouldPlayHiHat(role) {
|
|
1601
|
-
return role !== "fxOnly";
|
|
1602
|
-
}
|
|
1603
|
-
function roleHiHatInstrument(role, useRide) {
|
|
1604
|
-
if (role === "ambient") {
|
|
1605
|
-
return GM.RIDE;
|
|
1606
|
-
}
|
|
1607
|
-
return useRide ? GM.RIDE : GM.CHH;
|
|
1608
|
-
}
|
|
1609
|
-
function sparser(level) {
|
|
1610
|
-
if (level === "sixteenth") {
|
|
1611
|
-
return "eighth";
|
|
1612
|
-
}
|
|
1613
|
-
if (level === "eighth") {
|
|
1614
|
-
return "quarter";
|
|
1615
|
-
}
|
|
1616
|
-
return "quarter";
|
|
1617
|
-
}
|
|
1618
|
-
function denser(level) {
|
|
1619
|
-
if (level === "quarter") {
|
|
1620
|
-
return "eighth";
|
|
1621
|
-
}
|
|
1622
|
-
if (level === "eighth") {
|
|
1623
|
-
return "sixteenth";
|
|
1624
|
-
}
|
|
1625
|
-
return "sixteenth";
|
|
1626
|
-
}
|
|
1627
|
-
function getHiHatLevel(section, style, backingDensity, bpm, rng) {
|
|
1628
|
-
const allow16th = bpm < HH_16TH_BPM_THRESHOLD;
|
|
1629
|
-
let base = "eighth";
|
|
1630
|
-
if (style === "sparse") {
|
|
1631
|
-
base = section === "chorus" ? "eighth" : "quarter";
|
|
1632
|
-
} else if (style === "fourOnFloor") {
|
|
1633
|
-
if (allow16th && section === "chorus" && rng.prob(0.25)) {
|
|
1634
|
-
return "sixteenth";
|
|
1635
|
-
}
|
|
1636
|
-
return "eighth";
|
|
1637
|
-
} else if (style === "synth") {
|
|
1638
|
-
if (!allow16th) {
|
|
1639
|
-
return "eighth";
|
|
1640
|
-
}
|
|
1641
|
-
if (section === "a" && rng.prob(0.2)) {
|
|
1642
|
-
return "eighth";
|
|
1643
|
-
}
|
|
1644
|
-
return "sixteenth";
|
|
1645
|
-
} else if (style === "trap") {
|
|
1646
|
-
return allow16th ? "sixteenth" : "eighth";
|
|
1647
|
-
} else if (style === "latin") {
|
|
1648
|
-
if (allow16th && section === "chorus" && rng.prob(0.3)) {
|
|
1649
|
-
return "sixteenth";
|
|
1650
|
-
}
|
|
1651
|
-
return "eighth";
|
|
1652
|
-
} else {
|
|
1653
|
-
switch (section) {
|
|
1654
|
-
case "intro":
|
|
1655
|
-
base = "quarter";
|
|
1656
|
-
break;
|
|
1657
|
-
case "outro":
|
|
1658
|
-
base = "eighth";
|
|
1659
|
-
break;
|
|
1660
|
-
case "a":
|
|
1661
|
-
base = rng.prob(0.3) ? "quarter" : "eighth";
|
|
1662
|
-
break;
|
|
1663
|
-
case "b":
|
|
1664
|
-
base = allow16th && rng.prob(0.25) ? "sixteenth" : "eighth";
|
|
1665
|
-
break;
|
|
1666
|
-
case "chorus":
|
|
1667
|
-
base = allow16th && rng.prob(0.35) ? "sixteenth" : "eighth";
|
|
1668
|
-
break;
|
|
1669
|
-
case "bridge":
|
|
1670
|
-
base = "eighth";
|
|
1671
|
-
break;
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
if (backingDensity === "thin") {
|
|
1675
|
-
base = sparser(base);
|
|
1676
|
-
} else if (backingDensity === "thick") {
|
|
1677
|
-
base = denser(base);
|
|
1678
|
-
}
|
|
1679
|
-
if (!allow16th && base === "sixteenth") {
|
|
1680
|
-
base = "eighth";
|
|
1681
|
-
}
|
|
1682
|
-
return base;
|
|
1683
|
-
}
|
|
1684
|
-
function hiHatVelocityMultiplier(sixteenth, rng) {
|
|
1685
|
-
let baseValue;
|
|
1686
|
-
switch (sixteenth) {
|
|
1687
|
-
case 0:
|
|
1688
|
-
baseValue = 0.95;
|
|
1689
|
-
break;
|
|
1690
|
-
case 2:
|
|
1691
|
-
baseValue = 0.75;
|
|
1692
|
-
break;
|
|
1693
|
-
case 1:
|
|
1694
|
-
baseValue = 0.55;
|
|
1695
|
-
break;
|
|
1696
|
-
default:
|
|
1697
|
-
baseValue = 0.5;
|
|
1698
|
-
break;
|
|
1699
|
-
}
|
|
1700
|
-
return baseValue * rng.float(0.95, 1.05);
|
|
1701
|
-
}
|
|
1702
|
-
function openHiHatBarInterval(section, style) {
|
|
1703
|
-
if (style === "sparse") {
|
|
1704
|
-
return section === "chorus" ? 4 : 0;
|
|
1705
|
-
}
|
|
1706
|
-
switch (section) {
|
|
1707
|
-
case "intro":
|
|
1708
|
-
return style === "fourOnFloor" ? 4 : 0;
|
|
1709
|
-
case "a":
|
|
1710
|
-
return style === "fourOnFloor" || style === "upbeat" ? 2 : 4;
|
|
1711
|
-
case "b":
|
|
1712
|
-
return 2;
|
|
1713
|
-
case "chorus":
|
|
1714
|
-
return style === "rock" || style === "fourOnFloor" ? 1 : 2;
|
|
1715
|
-
case "bridge":
|
|
1716
|
-
return 0;
|
|
1717
|
-
case "outro":
|
|
1718
|
-
return 4;
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
function openHiHatBeat(section, rng) {
|
|
1722
|
-
if (section === "chorus") {
|
|
1723
|
-
const choice = rng.range(0, 3);
|
|
1724
|
-
if (choice < 2) {
|
|
1725
|
-
return 3;
|
|
1726
|
-
}
|
|
1727
|
-
if (choice < 3) {
|
|
1728
|
-
return 1;
|
|
1729
|
-
}
|
|
1730
|
-
return 2;
|
|
1731
|
-
}
|
|
1732
|
-
return 3;
|
|
1733
|
-
}
|
|
1734
|
-
function shouldUseFootHiHat(section, role) {
|
|
1735
|
-
if (role === "fxOnly") {
|
|
1736
|
-
return false;
|
|
1737
|
-
}
|
|
1738
|
-
switch (section) {
|
|
1739
|
-
case "intro":
|
|
1740
|
-
case "bridge":
|
|
1741
|
-
case "outro":
|
|
1742
|
-
return true;
|
|
1743
|
-
default:
|
|
1744
|
-
return role === "ambient" || role === "minimal";
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
function sectionHiHatType(section, role) {
|
|
1748
|
-
if (role === "ambient") {
|
|
1749
|
-
return "ride";
|
|
1750
|
-
}
|
|
1751
|
-
if (role === "minimal") {
|
|
1752
|
-
return "pedal";
|
|
1753
|
-
}
|
|
1754
|
-
switch (section) {
|
|
1755
|
-
case "intro":
|
|
1756
|
-
case "a":
|
|
1757
|
-
return "pedal";
|
|
1758
|
-
case "b":
|
|
1759
|
-
return "closed";
|
|
1760
|
-
case "chorus":
|
|
1761
|
-
return "open";
|
|
1762
|
-
case "bridge":
|
|
1763
|
-
return "ride";
|
|
1764
|
-
case "outro":
|
|
1765
|
-
return "halfOpen";
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
function hiHatNote(type) {
|
|
1769
|
-
switch (type) {
|
|
1770
|
-
case "pedal":
|
|
1771
|
-
return GM.FHH;
|
|
1772
|
-
case "open":
|
|
1773
|
-
return GM.OHH;
|
|
1774
|
-
case "ride":
|
|
1775
|
-
return GM.RIDE;
|
|
1776
|
-
default:
|
|
1777
|
-
return GM.CHH;
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
function hiHatTypeVelocityMultiplier(type) {
|
|
1781
|
-
switch (type) {
|
|
1782
|
-
case "halfOpen":
|
|
1783
|
-
return 0.75;
|
|
1784
|
-
case "pedal":
|
|
1785
|
-
return 0.65;
|
|
1786
|
-
case "open":
|
|
1787
|
-
return 1;
|
|
1788
|
-
case "ride":
|
|
1789
|
-
return 0.9;
|
|
1790
|
-
default:
|
|
1791
|
-
return 0.85;
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
function shouldAddOpenHHAccent(section, beat, bar, rng) {
|
|
1795
|
-
if (section !== "chorus" && section !== "b") {
|
|
1796
|
-
return false;
|
|
1797
|
-
}
|
|
1798
|
-
if (section === "chorus") {
|
|
1799
|
-
if (beat === 1 || beat === 3) {
|
|
1800
|
-
return rng.prob(0.6);
|
|
1801
|
-
}
|
|
1802
|
-
return false;
|
|
1803
|
-
}
|
|
1804
|
-
if (beat === 3 && bar % 2 === 1) {
|
|
1805
|
-
return rng.prob(0.4);
|
|
1806
|
-
}
|
|
1807
|
-
return false;
|
|
1808
|
-
}
|
|
1809
|
-
function footHiHatVelocity(rng) {
|
|
1810
|
-
return rng.range(FHH_VEL_MIN, FHH_VEL_MAX);
|
|
1811
|
-
}
|
|
1812
|
-
function shouldUseRideForSection(section, style) {
|
|
1813
|
-
if (style === "rock" && section === "chorus") {
|
|
1814
|
-
return true;
|
|
1815
|
-
}
|
|
1816
|
-
if (style === "sparse") {
|
|
1817
|
-
return false;
|
|
1818
|
-
}
|
|
1819
|
-
return section === "bridge";
|
|
1820
|
-
}
|
|
1821
|
-
function shouldUseBridgeCrossStick(section, beat) {
|
|
1822
|
-
return section === "bridge" && (beat === 1 || beat === 3);
|
|
1823
|
-
}
|
|
1824
19
|
|
|
1825
|
-
// src/generate/drums/swing.ts
|
|
1826
|
-
var SIXTEENTH2 = 0.25;
|
|
1827
|
-
var EIGHTH2 = 0.5;
|
|
1828
|
-
var HALF_16TH = 0.125;
|
|
1829
|
-
var DELTA_16 = 1 / 3 - 1 / 4;
|
|
1830
|
-
var DELTA_8 = (2 / 3 - 1 / 2) / 2 + (2 / 3 - 1 / 2) / 2;
|
|
1831
|
-
function effectiveSwing(feel, swingAmount) {
|
|
1832
|
-
if (feel === "straight") {
|
|
1833
|
-
return 0;
|
|
1834
|
-
}
|
|
1835
|
-
if (feel === "shuffle") {
|
|
1836
|
-
return Math.min(1, swingAmount * 1.5);
|
|
1837
|
-
}
|
|
1838
|
-
return swingAmount;
|
|
1839
|
-
}
|
|
1840
|
-
function quantizeSwing(startBeat, swing, resolution) {
|
|
1841
|
-
const clamped = Math.max(0, Math.min(1, swing));
|
|
1842
|
-
if (clamped <= 0) {
|
|
1843
|
-
return startBeat;
|
|
1844
|
-
}
|
|
1845
|
-
const base = Math.floor(startBeat);
|
|
1846
|
-
const offset = startBeat - base;
|
|
1847
|
-
if (resolution === "sixteenth") {
|
|
1848
|
-
if (offset >= HALF_16TH && offset < SIXTEENTH2 + HALF_16TH) {
|
|
1849
|
-
return base + SIXTEENTH2 + DELTA_16 * clamped;
|
|
1850
|
-
}
|
|
1851
|
-
if (offset >= SIXTEENTH2 + HALF_16TH && offset < EIGHTH2 + HALF_16TH) {
|
|
1852
|
-
return base + EIGHTH2 + DELTA_8 * clamped;
|
|
1853
|
-
}
|
|
1854
|
-
if (offset >= EIGHTH2 + HALF_16TH && offset < 3 * SIXTEENTH2 + HALF_16TH) {
|
|
1855
|
-
return base + 3 * SIXTEENTH2 + DELTA_16 * clamped;
|
|
1856
|
-
}
|
|
1857
|
-
return startBeat;
|
|
1858
|
-
}
|
|
1859
|
-
if (offset >= SIXTEENTH2 && offset < 3 * SIXTEENTH2) {
|
|
1860
|
-
return base + EIGHTH2 + DELTA_8 * clamped;
|
|
1861
|
-
}
|
|
1862
|
-
return startBeat;
|
|
1863
|
-
}
|
|
1864
20
|
|
|
1865
|
-
// src/generate/drums/beat.ts
|
|
1866
|
-
function swing16(tick, feel, swingAmount) {
|
|
1867
|
-
return quantizeSwing(tick, effectiveSwing(feel, swingAmount), "sixteenth");
|
|
1868
|
-
}
|
|
1869
|
-
function hatSwingFactor(style) {
|
|
1870
|
-
if (style === "trap") {
|
|
1871
|
-
return 0;
|
|
1872
|
-
}
|
|
1873
|
-
if (style === "latin") {
|
|
1874
|
-
return 0.35;
|
|
1875
|
-
}
|
|
1876
|
-
return 0.5;
|
|
1877
|
-
}
|
|
1878
|
-
function generateKickForBeat(ctx, sec, kick) {
|
|
1879
|
-
if (ctx.inPrechorusLift) {
|
|
1880
|
-
return;
|
|
1881
|
-
}
|
|
1882
|
-
const on = [kick.beat1, kick.beat2, kick.beat3, kick.beat4][ctx.beat] ?? false;
|
|
1883
|
-
const and = [kick.beat1and, kick.beat2and, kick.beat3and, kick.beat4and][ctx.beat] ?? false;
|
|
1884
|
-
if (on) {
|
|
1885
|
-
ctx.track.add(GM.BD, ctx.beatTick, EIGHTH, ctx.velocity);
|
|
1886
|
-
}
|
|
1887
|
-
if (and) {
|
|
1888
|
-
const andTick = swing16(ctx.beatTick + EIGHTH, sec.feel, ctx.swingAmount);
|
|
1889
|
-
ctx.track.add(GM.BD, andTick, EIGHTH, ctx.velocity * 0.85);
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
function generateSnareForBeat(ctx, sec, isIntroFirst) {
|
|
1893
|
-
if (ctx.inPrechorusLift) {
|
|
1894
|
-
return;
|
|
1895
|
-
}
|
|
1896
|
-
const snareOn = sec.snareBeat3 ? ctx.beat === 2 : ctx.beat === 1 || ctx.beat === 3;
|
|
1897
|
-
if (!snareOn || isIntroFirst) {
|
|
1898
|
-
return;
|
|
1899
|
-
}
|
|
1900
|
-
if (sec.useRide && shouldUseBridgeCrossStick(ctx.section, ctx.beat)) {
|
|
1901
|
-
return;
|
|
1902
|
-
}
|
|
1903
|
-
const backbeatVel = Math.min(127, ctx.velocity + 16);
|
|
1904
|
-
const promoteSparseChorus = sec.style === "sparse" && ctx.section === "chorus" && sec.role === "full";
|
|
1905
|
-
if (promoteSparseChorus) {
|
|
1906
|
-
ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, backbeatVel);
|
|
1907
|
-
} else if (sec.style === "sparse" || sec.role === "ambient") {
|
|
1908
|
-
if (sec.role !== "fxOnly" && sec.role !== "minimal") {
|
|
1909
|
-
ctx.track.add(GM.SIDESTICK, ctx.beatTick, EIGHTH, ctx.velocity * 0.8);
|
|
1910
|
-
}
|
|
1911
|
-
} else {
|
|
1912
|
-
ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, backbeatVel);
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
function generateGhostNotesForBeat(ctx, sec) {
|
|
1916
|
-
if (ctx.beat !== 0 && ctx.beat !== 2) {
|
|
1917
|
-
return;
|
|
1918
|
-
}
|
|
1919
|
-
const positions = selectGhostPositions(sec.ghostMood, ctx.rng);
|
|
1920
|
-
let ghostProb = getGhostDensity(sec.ghostMood, ctx.section, sec.backingDensity, ctx.bpm);
|
|
1921
|
-
if (sec.ghostBoost) {
|
|
1922
|
-
ghostProb = Math.min(1, ghostProb * 1.4);
|
|
1923
|
-
}
|
|
1924
|
-
for (const pos of positions) {
|
|
1925
|
-
const sixteenthInBeat = pos === "e" ? 1 : 3;
|
|
1926
|
-
const posProb = getGhostProbabilityAtPosition(ctx.beat, sixteenthInBeat);
|
|
1927
|
-
if (!ctx.rng.prob(ghostProb * posProb)) {
|
|
1928
|
-
continue;
|
|
1929
|
-
}
|
|
1930
|
-
const variation = ctx.rng.float(0.85, 1.15);
|
|
1931
|
-
const ghostBase = getGhostVelocity(ctx.section, ctx.beat / 2, false);
|
|
1932
|
-
let ghostVel = ctx.velocity * ghostBase * variation;
|
|
1933
|
-
if (pos === "a") {
|
|
1934
|
-
ghostVel *= 0.9;
|
|
1935
|
-
}
|
|
1936
|
-
const offset = pos === "e" ? SIXTEENTH : 3 * SIXTEENTH;
|
|
1937
|
-
const tick = swing16(ctx.beatTick + offset, sec.feel, ctx.swingAmount);
|
|
1938
|
-
ctx.track.add(GM.SD, tick, SIXTEENTH, ghostVel);
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
function generatePreChorusBuildup(ctx, sec, isSectionLastBar) {
|
|
1942
|
-
if (sec.style === "sparse") {
|
|
1943
|
-
if (isSectionLastBar && ctx.beat === 3) {
|
|
1944
|
-
ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, Math.max(45, ctx.velocity * 0.75));
|
|
1945
|
-
ctx.track.add(GM.CRASH, ctx.beatTick + EIGHTH + SIXTEENTH, SIXTEENTH, ctx.velocity * 0.9);
|
|
1946
|
-
}
|
|
1947
|
-
return;
|
|
1948
|
-
}
|
|
1949
|
-
const barsInLift = 2;
|
|
1950
|
-
const barInLift = ctx.bar - (ctx.sectionBars - barsInLift);
|
|
1951
|
-
const progress = (barInLift * 4 + ctx.beat) / (barsInLift * 4);
|
|
1952
|
-
const buildupVel = ctx.velocity * (0.5 + 0.5 * progress);
|
|
1953
|
-
ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, buildupVel);
|
|
1954
|
-
ctx.track.add(GM.SD, ctx.beatTick + EIGHTH, EIGHTH, buildupVel * 0.85);
|
|
1955
|
-
if (isSectionLastBar && ctx.beat === 3) {
|
|
1956
|
-
ctx.track.add(GM.CRASH, ctx.beatTick + EIGHTH + SIXTEENTH, SIXTEENTH, ctx.velocity * 1.1);
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
function generateHiHatForBeat(ctx, sec) {
|
|
1960
|
-
if (!shouldPlayHiHat(sec.role)) {
|
|
1961
|
-
if (sec.useFootHh && (ctx.beat === 0 || ctx.beat === 2)) {
|
|
1962
|
-
ctx.track.add(GM.FHH, ctx.beatTick, EIGHTH, footHiHatVelocity(ctx.rng));
|
|
1963
|
-
}
|
|
1964
|
-
return;
|
|
1965
|
-
}
|
|
1966
|
-
const crossStick = sec.useRide && shouldUseBridgeCrossStick(ctx.section, ctx.beat);
|
|
1967
|
-
const hhInstrument = crossStick ? GM.SIDESTICK : roleHiHatInstrument(sec.role, sec.useRide);
|
|
1968
|
-
const hhType = sectionHiHatType(ctx.section, sec.role);
|
|
1969
|
-
const typeMult = hiHatTypeVelocityMultiplier(hhType);
|
|
1970
|
-
const isDynamicOpen = ctx.barHasOpenHh && ctx.beat === ctx.openHhBeat;
|
|
1971
|
-
const dm = sec.densityMult;
|
|
1972
|
-
if (sec.hhLevel === "quarter") {
|
|
1973
|
-
const introRest = ctx.section === "intro" && ctx.beat !== 0;
|
|
1974
|
-
if (!introRest) {
|
|
1975
|
-
if (isDynamicOpen) {
|
|
1976
|
-
ctx.track.add(
|
|
1977
|
-
GM.OHH,
|
|
1978
|
-
ctx.beatTick,
|
|
1979
|
-
EIGHTH,
|
|
1980
|
-
ctx.velocity * dm * 0.75 * typeMult + OHH_VEL_BOOST
|
|
1981
|
-
);
|
|
1982
|
-
} else {
|
|
1983
|
-
ctx.track.add(
|
|
1984
|
-
hhInstrument,
|
|
1985
|
-
ctx.beatTick,
|
|
1986
|
-
EIGHTH,
|
|
1987
|
-
Math.max(20, ctx.velocity * dm * 0.75 * typeMult)
|
|
1988
|
-
);
|
|
1989
|
-
}
|
|
1990
|
-
} else if (sec.useFootHh) {
|
|
1991
|
-
ctx.track.add(GM.FHH, ctx.beatTick, EIGHTH, footHiHatVelocity(ctx.rng));
|
|
1992
|
-
}
|
|
1993
|
-
return;
|
|
1994
|
-
}
|
|
1995
|
-
if (sec.hhLevel === "eighth") {
|
|
1996
|
-
for (let eighth = 0; eighth < 2; eighth += 1) {
|
|
1997
|
-
let hhTick = ctx.beatTick + eighth * EIGHTH;
|
|
1998
|
-
if (eighth === 1) {
|
|
1999
|
-
hhTick = swing16(hhTick, sec.feel, ctx.swingAmount);
|
|
2000
|
-
}
|
|
2001
|
-
if (ctx.section === "intro" && eighth === 1) {
|
|
2002
|
-
if (sec.useFootHh && ctx.beat % 2 === 0) {
|
|
2003
|
-
ctx.track.add(GM.FHH, hhTick, EIGHTH, footHiHatVelocity(ctx.rng));
|
|
2004
|
-
}
|
|
2005
|
-
continue;
|
|
2006
|
-
}
|
|
2007
|
-
const hhVel = Math.max(20, ctx.velocity * dm * typeMult * (eighth === 0 ? 0.9 : 0.65));
|
|
2008
|
-
if (isDynamicOpen && eighth === 0) {
|
|
2009
|
-
ctx.track.add(GM.OHH, hhTick, EIGHTH, hhVel + OHH_VEL_BOOST);
|
|
2010
|
-
continue;
|
|
2011
|
-
}
|
|
2012
|
-
let useOpen = false;
|
|
2013
|
-
if (sec.style === "fourOnFloor" && eighth === 1) {
|
|
2014
|
-
const openProb = Math.max(0.15, Math.min(0.8, 45 / ctx.bpm));
|
|
2015
|
-
useOpen = (ctx.beat === 1 || ctx.beat === 3) && ctx.rng.prob(openProb);
|
|
2016
|
-
} else if (eighth === 0) {
|
|
2017
|
-
useOpen = shouldAddOpenHHAccent(ctx.section, ctx.beat, ctx.bar, ctx.rng);
|
|
2018
|
-
}
|
|
2019
|
-
if (useOpen) {
|
|
2020
|
-
ctx.track.add(hiHatNote("open"), hhTick, EIGHTH, Math.max(20, hhVel * 1.1));
|
|
2021
|
-
} else {
|
|
2022
|
-
ctx.track.add(hhInstrument, hhTick, EIGHTH / 2, hhVel);
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
return;
|
|
2026
|
-
}
|
|
2027
|
-
for (let sixteenth = 0; sixteenth < 4; sixteenth += 1) {
|
|
2028
|
-
let hhTick = ctx.beatTick + sixteenth * SIXTEENTH;
|
|
2029
|
-
if (sixteenth === 1 || sixteenth === 3) {
|
|
2030
|
-
const swung = effectiveSwing(sec.feel, ctx.swingAmount) * hatSwingFactor(sec.style);
|
|
2031
|
-
hhTick = quantizeSwing(hhTick, swung, "sixteenth");
|
|
2032
|
-
}
|
|
2033
|
-
const metricVel = hiHatVelocityMultiplier(sixteenth, ctx.rng);
|
|
2034
|
-
const hhVel = Math.max(20, ctx.velocity * dm * typeMult * metricVel);
|
|
2035
|
-
if (isDynamicOpen && sixteenth === 0) {
|
|
2036
|
-
ctx.track.add(GM.OHH, hhTick, SIXTEENTH, hhVel + OHH_VEL_BOOST);
|
|
2037
|
-
continue;
|
|
2038
|
-
}
|
|
2039
|
-
if (ctx.beat === 3 && sixteenth === 3) {
|
|
2040
|
-
const openProb = Math.max(0.1, Math.min(0.4, 30 / ctx.bpm));
|
|
2041
|
-
if (ctx.rng.prob(openProb)) {
|
|
2042
|
-
ctx.track.add(GM.OHH, hhTick, SIXTEENTH, Math.max(20, hhVel * 1.2));
|
|
2043
|
-
continue;
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
ctx.track.add(hhInstrument, hhTick, SIXTEENTH / 2, hhVel);
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
21
|
|
|
2050
|
-
// src/generate/drums/euclid.ts
|
|
2051
|
-
function euclideanRhythm(pulses, steps, rotation = 0) {
|
|
2052
|
-
assertPositiveInt(steps, "euclidean steps");
|
|
2053
|
-
assertInteger(pulses, "euclidean pulses");
|
|
2054
|
-
assertInteger(rotation, "euclidean rotation");
|
|
2055
|
-
assertGenerationBudget(steps, "euclidean pattern steps");
|
|
2056
|
-
const p = Math.max(0, Math.min(pulses, steps));
|
|
2057
|
-
const base = bjorklund(p, steps);
|
|
2058
|
-
const shift = (rotation % steps + steps) % steps;
|
|
2059
|
-
if (shift === 0) {
|
|
2060
|
-
return base;
|
|
2061
|
-
}
|
|
2062
|
-
return base.map((_, i) => base[(i - shift + steps) % steps] ?? false);
|
|
2063
|
-
}
|
|
2064
|
-
function bjorklund(pulses, steps) {
|
|
2065
|
-
if (pulses === 0) {
|
|
2066
|
-
return Array.from({ length: steps }, () => false);
|
|
2067
|
-
}
|
|
2068
|
-
if (pulses >= steps) {
|
|
2069
|
-
return Array.from({ length: steps }, () => true);
|
|
2070
|
-
}
|
|
2071
|
-
let head = Array.from({ length: pulses }, () => [true]);
|
|
2072
|
-
let remainder = Array.from({ length: steps - pulses }, () => [false]);
|
|
2073
|
-
while (remainder.length > 1) {
|
|
2074
|
-
const count = Math.min(head.length, remainder.length);
|
|
2075
|
-
const next = [];
|
|
2076
|
-
for (let i = 0; i < count; i += 1) {
|
|
2077
|
-
next.push([...head[i] ?? [], ...remainder[i] ?? []]);
|
|
2078
|
-
}
|
|
2079
|
-
remainder = head.length > count ? head.slice(count) : remainder.slice(count);
|
|
2080
|
-
head = next;
|
|
2081
|
-
}
|
|
2082
|
-
return [...head.flat(), ...remainder.flat()];
|
|
2083
|
-
}
|
|
2084
22
|
|
|
2085
|
-
// src/generate/drums/fills.ts
|
|
2086
|
-
function getFillStartBeat(energy) {
|
|
2087
|
-
switch (energy) {
|
|
2088
|
-
case "low":
|
|
2089
|
-
return 3;
|
|
2090
|
-
case "medium":
|
|
2091
|
-
return 2;
|
|
2092
|
-
case "high":
|
|
2093
|
-
case "peak":
|
|
2094
|
-
return 0;
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
function selectFillType(from, to, style, nextEnergy, rng) {
|
|
2098
|
-
if (style === "sparse") {
|
|
2099
|
-
return rng.range(0, 1) === 0 ? "simpleCrash" : "breakdownFill";
|
|
2100
|
-
}
|
|
2101
|
-
const toChorus = to === "chorus";
|
|
2102
|
-
const fromIntro = from === "intro";
|
|
2103
|
-
const highEnergy = style === "rock" || style === "fourOnFloor";
|
|
2104
|
-
if (nextEnergy === "low") {
|
|
2105
|
-
switch (rng.range(0, 2)) {
|
|
2106
|
-
case 0:
|
|
2107
|
-
return "simpleCrash";
|
|
2108
|
-
case 1:
|
|
2109
|
-
return "breakdownFill";
|
|
2110
|
-
default:
|
|
2111
|
-
return "halfTimeFill";
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
|
-
if (toChorus) {
|
|
2115
|
-
const choice2 = rng.range(0, 7);
|
|
2116
|
-
if (highEnergy) {
|
|
2117
|
-
switch (choice2) {
|
|
2118
|
-
case 0:
|
|
2119
|
-
case 1:
|
|
2120
|
-
return "tomDescend";
|
|
2121
|
-
case 2:
|
|
2122
|
-
return "snareRoll";
|
|
2123
|
-
case 3:
|
|
2124
|
-
return "linearFill";
|
|
2125
|
-
case 4:
|
|
2126
|
-
return "bdSnareAlternate";
|
|
2127
|
-
case 5:
|
|
2128
|
-
return "flamsAndDrags";
|
|
2129
|
-
case 6:
|
|
2130
|
-
return "tomShuffle";
|
|
2131
|
-
default:
|
|
2132
|
-
return "ghostToAccent";
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
switch (choice2) {
|
|
2136
|
-
case 0:
|
|
2137
|
-
case 1:
|
|
2138
|
-
return "snareTomCombo";
|
|
2139
|
-
case 2:
|
|
2140
|
-
return "tomDescend";
|
|
2141
|
-
case 3:
|
|
2142
|
-
return "ghostToAccent";
|
|
2143
|
-
case 4:
|
|
2144
|
-
return "hiHatChoke";
|
|
2145
|
-
case 5:
|
|
2146
|
-
return "linearFill";
|
|
2147
|
-
default:
|
|
2148
|
-
return "snareRoll";
|
|
2149
|
-
}
|
|
2150
|
-
}
|
|
2151
|
-
if (fromIntro) {
|
|
2152
|
-
switch (rng.range(0, 5)) {
|
|
2153
|
-
case 0:
|
|
2154
|
-
case 1:
|
|
2155
|
-
return "snareRoll";
|
|
2156
|
-
case 2:
|
|
2157
|
-
return "simpleCrash";
|
|
2158
|
-
case 3:
|
|
2159
|
-
return "ghostToAccent";
|
|
2160
|
-
case 4:
|
|
2161
|
-
return "breakdownFill";
|
|
2162
|
-
default:
|
|
2163
|
-
return "halfTimeFill";
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
const choice = rng.range(0, 7);
|
|
2167
|
-
if (highEnergy) {
|
|
2168
|
-
switch (choice) {
|
|
2169
|
-
case 0:
|
|
2170
|
-
return "tomDescend";
|
|
2171
|
-
case 1:
|
|
2172
|
-
return "snareRoll";
|
|
2173
|
-
case 2:
|
|
2174
|
-
return "tomAscend";
|
|
2175
|
-
case 3:
|
|
2176
|
-
return "snareTomCombo";
|
|
2177
|
-
case 4:
|
|
2178
|
-
return "linearFill";
|
|
2179
|
-
case 5:
|
|
2180
|
-
return "bdSnareAlternate";
|
|
2181
|
-
case 6:
|
|
2182
|
-
return "flamsAndDrags";
|
|
2183
|
-
default:
|
|
2184
|
-
return "tomShuffle";
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
switch (choice) {
|
|
2188
|
-
case 0:
|
|
2189
|
-
case 1:
|
|
2190
|
-
return "snareRoll";
|
|
2191
|
-
case 2:
|
|
2192
|
-
return "snareTomCombo";
|
|
2193
|
-
case 3:
|
|
2194
|
-
return "ghostToAccent";
|
|
2195
|
-
case 4:
|
|
2196
|
-
return "hiHatChoke";
|
|
2197
|
-
case 5:
|
|
2198
|
-
return "halfTimeFill";
|
|
2199
|
-
default:
|
|
2200
|
-
return "breakdownFill";
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
function generateFill(track, beatTick, beat, fillType, velocity) {
|
|
2204
|
-
const fillVel = velocity * 0.9;
|
|
2205
|
-
const accentVel = velocity * 0.95;
|
|
2206
|
-
const S = SIXTEENTH;
|
|
2207
|
-
const E = EIGHTH;
|
|
2208
|
-
if (beat === 0) {
|
|
2209
|
-
track.add(GM.BD, beatTick, E, fillVel);
|
|
2210
|
-
track.add(GM.SD, beatTick + E, E, fillVel - 5);
|
|
2211
|
-
return;
|
|
2212
|
-
}
|
|
2213
|
-
if (beat === 1) {
|
|
2214
|
-
track.add(GM.SD, beatTick, E, fillVel);
|
|
2215
|
-
track.add(GM.TOM_H, beatTick + E, E, fillVel - 3);
|
|
2216
|
-
return;
|
|
2217
|
-
}
|
|
2218
|
-
switch (fillType) {
|
|
2219
|
-
case "snareRoll":
|
|
2220
|
-
if (beat === 2) {
|
|
2221
|
-
for (let i = 0; i < 4; i += 1) {
|
|
2222
|
-
track.add(GM.SD, beatTick + i * S, S, fillVel * (0.6 + 0.1 * i));
|
|
2223
|
-
}
|
|
2224
|
-
} else {
|
|
2225
|
-
for (let i = 0; i < 3; i += 1) {
|
|
2226
|
-
track.add(GM.SD, beatTick + i * S, S, fillVel * (0.7 + 0.1 * i));
|
|
2227
|
-
}
|
|
2228
|
-
track.add(GM.SD, beatTick + 3 * S, S, accentVel);
|
|
2229
|
-
}
|
|
2230
|
-
break;
|
|
2231
|
-
case "tomDescend":
|
|
2232
|
-
if (beat === 2) {
|
|
2233
|
-
track.add(GM.SD, beatTick, E, fillVel);
|
|
2234
|
-
track.add(GM.TOM_H, beatTick + E, E, fillVel - 5);
|
|
2235
|
-
} else {
|
|
2236
|
-
track.add(GM.TOM_H, beatTick, S, fillVel);
|
|
2237
|
-
track.add(GM.TOM_M, beatTick + S, S, fillVel - 3);
|
|
2238
|
-
track.add(GM.TOM_M, beatTick + E, S, fillVel - 5);
|
|
2239
|
-
track.add(GM.TOM_L, beatTick + E + S, S, accentVel);
|
|
2240
|
-
}
|
|
2241
|
-
break;
|
|
2242
|
-
case "tomAscend":
|
|
2243
|
-
if (beat === 2) {
|
|
2244
|
-
track.add(GM.SD, beatTick, E, fillVel);
|
|
2245
|
-
track.add(GM.TOM_L, beatTick + E, E, fillVel - 5);
|
|
2246
|
-
} else {
|
|
2247
|
-
track.add(GM.TOM_L, beatTick, S, fillVel);
|
|
2248
|
-
track.add(GM.TOM_M, beatTick + S, S, fillVel + 3);
|
|
2249
|
-
track.add(GM.TOM_M, beatTick + E, S, fillVel + 5);
|
|
2250
|
-
track.add(GM.TOM_H, beatTick + E + S, S, accentVel);
|
|
2251
|
-
}
|
|
2252
|
-
break;
|
|
2253
|
-
case "snareTomCombo":
|
|
2254
|
-
if (beat === 2) {
|
|
2255
|
-
track.add(GM.SD, beatTick, E, fillVel);
|
|
2256
|
-
track.add(GM.SD, beatTick + E, S, fillVel - 5);
|
|
2257
|
-
track.add(GM.TOM_H, beatTick + E + S, S, fillVel);
|
|
2258
|
-
} else {
|
|
2259
|
-
track.add(GM.TOM_M, beatTick, S, fillVel);
|
|
2260
|
-
track.add(GM.SD, beatTick + S, S, fillVel - 3);
|
|
2261
|
-
track.add(GM.TOM_L, beatTick + E, S, fillVel + 2);
|
|
2262
|
-
track.add(GM.BD, beatTick + E + S, S, accentVel);
|
|
2263
|
-
}
|
|
2264
|
-
break;
|
|
2265
|
-
case "simpleCrash":
|
|
2266
|
-
if (beat === 3) {
|
|
2267
|
-
track.add(GM.BD, beatTick + E + S, S, accentVel);
|
|
2268
|
-
track.add(GM.CRASH, beatTick + E + S, E, accentVel);
|
|
2269
|
-
}
|
|
2270
|
-
break;
|
|
2271
|
-
case "linearFill":
|
|
2272
|
-
if (beat === 2) {
|
|
2273
|
-
track.add(GM.BD, beatTick, S, fillVel);
|
|
2274
|
-
track.add(GM.SD, beatTick + S, S, fillVel);
|
|
2275
|
-
track.add(GM.TOM_H, beatTick + 2 * S, S, fillVel);
|
|
2276
|
-
track.add(GM.TOM_M, beatTick + 3 * S, S, fillVel);
|
|
2277
|
-
} else {
|
|
2278
|
-
track.add(GM.TOM_L, beatTick, S, fillVel + 3);
|
|
2279
|
-
track.add(GM.SD, beatTick + S, S, fillVel + 5);
|
|
2280
|
-
track.add(GM.BD, beatTick + 2 * S, S, fillVel + 7);
|
|
2281
|
-
track.add(GM.SD, beatTick + 3 * S, S, accentVel);
|
|
2282
|
-
}
|
|
2283
|
-
break;
|
|
2284
|
-
case "ghostToAccent":
|
|
2285
|
-
if (beat === 2) {
|
|
2286
|
-
const ghost = fillVel * 0.4;
|
|
2287
|
-
track.add(GM.SD, beatTick, S, ghost);
|
|
2288
|
-
track.add(GM.SD, beatTick + S, S, ghost + 10);
|
|
2289
|
-
track.add(GM.SD, beatTick + 2 * S, S, ghost + 20);
|
|
2290
|
-
track.add(GM.SD, beatTick + 3 * S, S, ghost + 30);
|
|
2291
|
-
} else {
|
|
2292
|
-
track.add(GM.SD, beatTick, E, fillVel);
|
|
2293
|
-
track.add(GM.SD, beatTick + E, E, accentVel);
|
|
2294
|
-
}
|
|
2295
|
-
break;
|
|
2296
|
-
case "bdSnareAlternate":
|
|
2297
|
-
if (beat === 2) {
|
|
2298
|
-
track.add(GM.BD, beatTick, S, fillVel);
|
|
2299
|
-
track.add(GM.SD, beatTick + S, S, fillVel);
|
|
2300
|
-
track.add(GM.BD, beatTick + 2 * S, S, fillVel + 3);
|
|
2301
|
-
track.add(GM.SD, beatTick + 3 * S, S, fillVel + 3);
|
|
2302
|
-
} else {
|
|
2303
|
-
track.add(GM.BD, beatTick, S, fillVel + 5);
|
|
2304
|
-
track.add(GM.SD, beatTick + S, S, fillVel + 5);
|
|
2305
|
-
track.add(GM.BD, beatTick + 2 * S, S, accentVel);
|
|
2306
|
-
track.add(GM.SD, beatTick + 3 * S, S, accentVel);
|
|
2307
|
-
}
|
|
2308
|
-
break;
|
|
2309
|
-
case "hiHatChoke":
|
|
2310
|
-
if (beat === 2) {
|
|
2311
|
-
track.add(GM.OHH, beatTick, E, fillVel);
|
|
2312
|
-
track.add(GM.OHH, beatTick + E, E, fillVel + 5);
|
|
2313
|
-
} else {
|
|
2314
|
-
track.add(GM.OHH, beatTick, S, fillVel + 8);
|
|
2315
|
-
track.add(GM.CHH, beatTick + S, S, accentVel);
|
|
2316
|
-
track.add(GM.SD, beatTick + E, E, accentVel);
|
|
2317
|
-
}
|
|
2318
|
-
break;
|
|
2319
|
-
case "tomShuffle":
|
|
2320
|
-
if (beat === 2) {
|
|
2321
|
-
track.add(GM.TOM_H, beatTick, E, fillVel);
|
|
2322
|
-
track.add(GM.TOM_M, beatTick + E + S / 2, S, fillVel - 5);
|
|
2323
|
-
} else {
|
|
2324
|
-
track.add(GM.TOM_M, beatTick, E, fillVel);
|
|
2325
|
-
track.add(GM.TOM_L, beatTick + E + S / 2, S, fillVel + 5);
|
|
2326
|
-
}
|
|
2327
|
-
break;
|
|
2328
|
-
case "breakdownFill":
|
|
2329
|
-
if (beat === 3) {
|
|
2330
|
-
track.add(GM.SD, beatTick + E, S, accentVel);
|
|
2331
|
-
}
|
|
2332
|
-
break;
|
|
2333
|
-
case "flamsAndDrags":
|
|
2334
|
-
if (beat === 2) {
|
|
2335
|
-
track.add(GM.SD, beatTick - S / 4, S / 4, fillVel * 0.5);
|
|
2336
|
-
track.add(GM.SD, beatTick, E, fillVel);
|
|
2337
|
-
track.add(GM.SD, beatTick + E, S / 2, fillVel * 0.6);
|
|
2338
|
-
track.add(GM.SD, beatTick + E + S / 2, S / 2, fillVel * 0.6);
|
|
2339
|
-
track.add(GM.SD, beatTick + E + S, E, fillVel);
|
|
2340
|
-
} else {
|
|
2341
|
-
track.add(GM.SD, beatTick - S / 4, S / 4, fillVel * 0.5);
|
|
2342
|
-
track.add(GM.SD, beatTick, 1, accentVel);
|
|
2343
|
-
}
|
|
2344
|
-
break;
|
|
2345
|
-
case "halfTimeFill":
|
|
2346
|
-
if (beat === 2) {
|
|
2347
|
-
track.add(GM.SD, beatTick, 1, accentVel);
|
|
2348
|
-
track.add(GM.BD, beatTick, 1, fillVel);
|
|
2349
|
-
} else if (beat === 3) {
|
|
2350
|
-
track.add(GM.SD, beatTick, EIGHTH, accentVel);
|
|
2351
|
-
track.add(GM.SD, beatTick + E, S, fillVel * 0.6);
|
|
2352
|
-
track.add(GM.SD, beatTick + E + S, S, accentVel);
|
|
2353
|
-
}
|
|
2354
|
-
break;
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
23
|
|
|
2358
|
-
// src/generate/drums/hit.ts
|
|
2359
|
-
var HitList = class {
|
|
2360
|
-
hits = [];
|
|
2361
|
-
/** Append a hit, clamping velocity and dropping non-positive positions. */
|
|
2362
|
-
add(pitch, startBeat, durationBeat, velocity) {
|
|
2363
|
-
this.hits.push({
|
|
2364
|
-
pitch,
|
|
2365
|
-
startBeat: Math.max(0, startBeat),
|
|
2366
|
-
durationBeat,
|
|
2367
|
-
velocity: clampVel(velocity)
|
|
2368
|
-
});
|
|
2369
|
-
}
|
|
2370
|
-
/** True when a crash already sits within a 16th of `startBeat`. */
|
|
2371
|
-
hasCrashNear(startBeat) {
|
|
2372
|
-
return this.hits.some(
|
|
2373
|
-
(h) => h.pitch === GM.CRASH && h.startBeat >= startBeat && h.startBeat < startBeat + 0.25
|
|
2374
|
-
);
|
|
2375
|
-
}
|
|
2376
|
-
};
|
|
2377
24
|
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
beat1and: false,
|
|
2382
|
-
beat2: false,
|
|
2383
|
-
beat2and: false,
|
|
2384
|
-
beat3: false,
|
|
2385
|
-
beat3and: false,
|
|
2386
|
-
beat4: false,
|
|
2387
|
-
beat4and: false
|
|
2388
|
-
};
|
|
2389
|
-
function isInPreChorusLift(section, bar, sectionBars, nextIsChorus) {
|
|
2390
|
-
if (section !== "b" || !nextIsChorus || sectionBars < 3) {
|
|
2391
|
-
return false;
|
|
2392
|
-
}
|
|
2393
|
-
return bar >= sectionBars - 2;
|
|
2394
|
-
}
|
|
2395
|
-
function getKickPattern(section, style, bar, rng) {
|
|
2396
|
-
const p = { ...EMPTY };
|
|
2397
|
-
if (section === "outro") {
|
|
2398
|
-
p.beat1 = true;
|
|
2399
|
-
p.beat3 = true;
|
|
2400
|
-
return p;
|
|
2401
|
-
}
|
|
2402
|
-
switch (style) {
|
|
2403
|
-
case "sparse":
|
|
2404
|
-
p.beat1 = true;
|
|
2405
|
-
if (section === "chorus" && bar % 2 === 1) {
|
|
2406
|
-
p.beat3 = true;
|
|
2407
|
-
}
|
|
2408
|
-
break;
|
|
2409
|
-
case "fourOnFloor":
|
|
2410
|
-
p.beat1 = p.beat2 = p.beat3 = p.beat4 = true;
|
|
2411
|
-
if (section === "chorus" && rng.prob(0.2)) {
|
|
2412
|
-
p.beat2and = true;
|
|
2413
|
-
}
|
|
2414
|
-
break;
|
|
2415
|
-
case "upbeat":
|
|
2416
|
-
p.beat1 = true;
|
|
2417
|
-
p.beat3 = true;
|
|
2418
|
-
if (section === "b" || section === "chorus") {
|
|
2419
|
-
p.beat2and = rng.prob(0.7);
|
|
2420
|
-
}
|
|
2421
|
-
if (section === "chorus") {
|
|
2422
|
-
p.beat4and = rng.prob(0.6);
|
|
2423
|
-
}
|
|
2424
|
-
break;
|
|
2425
|
-
case "rock":
|
|
2426
|
-
p.beat1 = true;
|
|
2427
|
-
p.beat3 = true;
|
|
2428
|
-
if (section === "chorus") {
|
|
2429
|
-
p.beat2and = rng.prob(0.65);
|
|
2430
|
-
p.beat4and = rng.prob(0.4);
|
|
2431
|
-
} else if (section === "b") {
|
|
2432
|
-
p.beat2and = rng.prob(0.3);
|
|
2433
|
-
}
|
|
2434
|
-
break;
|
|
2435
|
-
case "synth":
|
|
2436
|
-
p.beat1 = true;
|
|
2437
|
-
p.beat3 = true;
|
|
2438
|
-
if (section === "b" || section === "chorus") {
|
|
2439
|
-
p.beat2and = rng.prob(0.75);
|
|
2440
|
-
}
|
|
2441
|
-
if (section === "chorus") {
|
|
2442
|
-
p.beat4and = rng.prob(0.65);
|
|
2443
|
-
}
|
|
2444
|
-
break;
|
|
2445
|
-
case "trap":
|
|
2446
|
-
p.beat1 = true;
|
|
2447
|
-
p.beat2and = rng.prob(0.8);
|
|
2448
|
-
p.beat3 = rng.prob(0.3);
|
|
2449
|
-
p.beat4and = rng.prob(0.7);
|
|
2450
|
-
break;
|
|
2451
|
-
case "latin":
|
|
2452
|
-
p.beat1 = true;
|
|
2453
|
-
p.beat2and = true;
|
|
2454
|
-
p.beat3 = true;
|
|
2455
|
-
p.beat4and = rng.prob(0.5);
|
|
2456
|
-
break;
|
|
2457
|
-
default:
|
|
2458
|
-
p.beat1 = true;
|
|
2459
|
-
p.beat3 = true;
|
|
2460
|
-
if (section === "b") {
|
|
2461
|
-
p.beat2and = rng.prob(0.5);
|
|
2462
|
-
} else if (section === "chorus") {
|
|
2463
|
-
p.beat2and = rng.prob(0.55);
|
|
2464
|
-
p.beat4and = rng.prob(0.35);
|
|
2465
|
-
}
|
|
2466
|
-
break;
|
|
2467
|
-
}
|
|
2468
|
-
return p;
|
|
2469
|
-
}
|
|
25
|
+
var _chunkMPTVJIVGcjs = require('../chunk-MPTVJIVG.cjs');
|
|
26
|
+
require('../chunk-MTMZTMWM.cjs');
|
|
27
|
+
require('../chunk-2A2QMA74.cjs');
|
|
2470
28
|
|
|
2471
|
-
// src/generate/drums/percussion.ts
|
|
2472
|
-
var F = [false, false, false];
|
|
2473
|
-
var PERC_TABLE = [
|
|
2474
|
-
/* calm */
|
|
2475
|
-
[F, F, F, F, F, F, F, F, F],
|
|
2476
|
-
/* std */
|
|
2477
|
-
[F, F, [false, true, false], [true, false, true], F, F, F, F, [true, false, true]],
|
|
2478
|
-
/* ener */
|
|
2479
|
-
[
|
|
2480
|
-
F,
|
|
2481
|
-
[false, true, false],
|
|
2482
|
-
[false, true, false],
|
|
2483
|
-
[true, true, true],
|
|
2484
|
-
F,
|
|
2485
|
-
F,
|
|
2486
|
-
F,
|
|
2487
|
-
F,
|
|
2488
|
-
[true, true, true]
|
|
2489
|
-
],
|
|
2490
|
-
/* idol */
|
|
2491
|
-
[
|
|
2492
|
-
F,
|
|
2493
|
-
[false, true, false],
|
|
2494
|
-
[false, true, false],
|
|
2495
|
-
[true, true, true],
|
|
2496
|
-
F,
|
|
2497
|
-
F,
|
|
2498
|
-
F,
|
|
2499
|
-
F,
|
|
2500
|
-
[true, true, true]
|
|
2501
|
-
],
|
|
2502
|
-
/* rock */
|
|
2503
|
-
[F, F, F, [false, false, true], F, F, F, F, [false, false, true]]
|
|
2504
|
-
];
|
|
2505
|
-
function getPercussionConfig(mood, section) {
|
|
2506
|
-
const row = PERC_TABLE[mood] ?? PERC_TABLE[1 /* Standard */];
|
|
2507
|
-
const act = row?.[sectionIndex(section)] ?? F;
|
|
2508
|
-
return {
|
|
2509
|
-
tambourine: act[0],
|
|
2510
|
-
shaker: act[1],
|
|
2511
|
-
handclap: act[2],
|
|
2512
|
-
shaker16th: act[1]
|
|
2513
|
-
};
|
|
2514
|
-
}
|
|
2515
|
-
function generateAuxPercussionForBar(track, barStart, config, role, densityMult, rng, bpm) {
|
|
2516
|
-
if (role === "minimal") {
|
|
2517
|
-
return;
|
|
2518
|
-
}
|
|
2519
|
-
if (config.tambourine) {
|
|
2520
|
-
for (let beat = 1; beat <= 3; beat += 2) {
|
|
2521
|
-
const raw = 70 * densityMult * rng.float(0.9, 1.1);
|
|
2522
|
-
track.add(GM.TAMBOURINE, barStart + beat, EIGHTH, Math.max(40, Math.min(90, raw)));
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
if (config.shaker) {
|
|
2526
|
-
const use16th = config.shaker16th && bpm < HH_16TH_BPM_THRESHOLD;
|
|
2527
|
-
if (use16th) {
|
|
2528
|
-
const velCurve = [0.75, 0.45, 0.6, 0.45];
|
|
2529
|
-
for (let beat = 0; beat < 4; beat += 1) {
|
|
2530
|
-
for (let sub = 0; sub < 4; sub += 1) {
|
|
2531
|
-
const raw = 80 * (velCurve[sub] ?? 0.5) * densityMult * rng.float(0.9, 1.1);
|
|
2532
|
-
track.add(
|
|
2533
|
-
GM.SHAKER,
|
|
2534
|
-
barStart + beat + sub * SIXTEENTH,
|
|
2535
|
-
SIXTEENTH,
|
|
2536
|
-
Math.max(25, Math.min(85, raw))
|
|
2537
|
-
);
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2540
|
-
} else {
|
|
2541
|
-
const velCurve = [0.75, 0.55];
|
|
2542
|
-
for (let beat = 0; beat < 4; beat += 1) {
|
|
2543
|
-
for (let sub = 0; sub < 2; sub += 1) {
|
|
2544
|
-
const raw = 80 * (velCurve[sub] ?? 0.6) * densityMult * rng.float(0.9, 1.1);
|
|
2545
|
-
track.add(
|
|
2546
|
-
GM.SHAKER,
|
|
2547
|
-
barStart + beat + sub * EIGHTH,
|
|
2548
|
-
EIGHTH,
|
|
2549
|
-
Math.max(25, Math.min(85, raw))
|
|
2550
|
-
);
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
if (config.handclap) {
|
|
2556
|
-
for (let beat = 1; beat <= 3; beat += 2) {
|
|
2557
|
-
const raw = 85 * densityMult * rng.float(0.9, 1.1);
|
|
2558
|
-
track.add(GM.HANDCLAP, barStart + beat, EIGHTH, Math.max(50, Math.min(100, raw)));
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2562
29
|
|
|
2563
|
-
// src/generate/drums/index.ts
|
|
2564
|
-
function generateDrums(opts) {
|
|
2565
|
-
assertPositiveInt(opts.bars, "drum bars");
|
|
2566
|
-
assertRange(opts.bpm, Number.MIN_VALUE, 1e3, "drum bpm");
|
|
2567
|
-
assertRange(opts.density, 0, 1, "drum density");
|
|
2568
|
-
assertGenerationBudget(opts.bars * 128, "drum hits");
|
|
2569
|
-
if (opts.euclideanKick) {
|
|
2570
|
-
const steps = opts.euclideanKick.steps ?? 16;
|
|
2571
|
-
assertInteger(steps, "euclidean steps", 1, 16);
|
|
2572
|
-
assertInteger(opts.euclideanKick.pulses, "euclidean pulses");
|
|
2573
|
-
assertInteger(opts.euclideanKick.rotation ?? 0, "euclidean rotation");
|
|
2574
|
-
}
|
|
2575
|
-
const track = new HitList();
|
|
2576
|
-
const rng = createRng(opts.seed ?? 0);
|
|
2577
|
-
const mapping = mapStyle(opts.style);
|
|
2578
|
-
const style = mapping.style;
|
|
2579
|
-
const feel = opts.feel ?? mapping.feel;
|
|
2580
|
-
const role = opts.role ?? "full";
|
|
2581
|
-
const playMainVoices = role !== "fxOnly";
|
|
2582
|
-
const section = mapSection(opts.section);
|
|
2583
|
-
const backingDensity = mapDensity(opts.density);
|
|
2584
|
-
const swingAmount = feelSwingAmount(feel);
|
|
2585
|
-
let densityMult = sectionDensityMultiplier(section);
|
|
2586
|
-
if (backingDensity === "thin") {
|
|
2587
|
-
densityMult *= 0.75;
|
|
2588
|
-
} else if (backingDensity === "thick") {
|
|
2589
|
-
densityMult *= 1.15;
|
|
2590
|
-
}
|
|
2591
|
-
const sec = {
|
|
2592
|
-
style,
|
|
2593
|
-
feel,
|
|
2594
|
-
densityMult,
|
|
2595
|
-
backingDensity,
|
|
2596
|
-
hhLevel: getHiHatLevel(section, style, backingDensity, opts.bpm, rng),
|
|
2597
|
-
useGhostNotes: (section === "b" || section === "chorus" || section === "bridge") && style !== "sparse",
|
|
2598
|
-
ghostBoost: mapping.ghostBoost,
|
|
2599
|
-
useRide: shouldUseRideForSection(section, style),
|
|
2600
|
-
useFootHh: shouldUseFootHiHat(section, role),
|
|
2601
|
-
role,
|
|
2602
|
-
ghostMood: ghostMoodCategory(style),
|
|
2603
|
-
snareBeat3: mapping.snareBeat3
|
|
2604
|
-
};
|
|
2605
|
-
const ohhBarInterval = openHiHatBarInterval(section, style);
|
|
2606
|
-
const energy = sectionEnergy(section);
|
|
2607
|
-
const fillStartBeat = getFillStartBeat(energy);
|
|
2608
|
-
const percMood = percMoodCategory(style);
|
|
2609
|
-
const nextSection = opts.nextSection ? mapSection(opts.nextSection) : section;
|
|
2610
|
-
const nextEnergy = sectionEnergy(nextSection);
|
|
2611
|
-
const euclidSteps = opts.euclideanKick?.steps ?? 16;
|
|
2612
|
-
const euclidKick = opts.euclideanKick ? euclideanRhythm(opts.euclideanKick.pulses, euclidSteps, opts.euclideanKick.rotation ?? 0) : void 0;
|
|
2613
|
-
const reuseSectionKick = (section === "b" || section === "chorus") && style !== "sparse";
|
|
2614
|
-
let sectionKick;
|
|
2615
|
-
for (let bar = 0; bar < opts.bars; bar += 1) {
|
|
2616
|
-
const barStart = bar * 4;
|
|
2617
|
-
const isLastBar = bar === opts.bars - 1;
|
|
2618
|
-
if (bar === 0 && section === "chorus") {
|
|
2619
|
-
track.add(GM.CRASH, barStart, 0.5, 100 * densityMult);
|
|
2620
|
-
}
|
|
2621
|
-
let barHasOpenHh = false;
|
|
2622
|
-
let openHhBeatIndex = 3;
|
|
2623
|
-
if (ohhBarInterval > 0 && bar % ohhBarInterval === ohhBarInterval - 1) {
|
|
2624
|
-
openHhBeatIndex = openHiHatBeat(section, rng);
|
|
2625
|
-
barHasOpenHh = !track.hasCrashNear(barStart + openHhBeatIndex);
|
|
2626
|
-
}
|
|
2627
|
-
let kick;
|
|
2628
|
-
if (!euclidKick && reuseSectionKick) {
|
|
2629
|
-
sectionKick ??= getKickPattern(section, style, 0, rng);
|
|
2630
|
-
kick = sectionKick;
|
|
2631
|
-
} else if (!euclidKick) {
|
|
2632
|
-
kick = getKickPattern(section, style, bar, rng);
|
|
2633
|
-
}
|
|
2634
|
-
const inLift = isInPreChorusLift(section, bar, opts.bars, section === "b");
|
|
2635
|
-
let currentFill = "snareRoll";
|
|
2636
|
-
for (let beat = 0; beat < 4; beat += 1) {
|
|
2637
|
-
const beatTick = barStart + beat;
|
|
2638
|
-
const velocity = calculateVelocity(section, beat);
|
|
2639
|
-
if (opts.fills && isLastBar && !inLift && beat >= fillStartBeat) {
|
|
2640
|
-
if (beat === fillStartBeat) {
|
|
2641
|
-
currentFill = selectFillType(section, nextSection, style, nextEnergy, rng);
|
|
2642
|
-
}
|
|
2643
|
-
if (playMainVoices) {
|
|
2644
|
-
const before = track.hits.length;
|
|
2645
|
-
generateFill(track, beatTick, beat, currentFill, velocity);
|
|
2646
|
-
if (track.hits.length > before) {
|
|
2647
|
-
continue;
|
|
2648
|
-
}
|
|
2649
|
-
} else {
|
|
2650
|
-
continue;
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
const ctx = {
|
|
2654
|
-
track,
|
|
2655
|
-
beatTick,
|
|
2656
|
-
beat,
|
|
2657
|
-
velocity,
|
|
2658
|
-
section,
|
|
2659
|
-
bpm: opts.bpm,
|
|
2660
|
-
bar,
|
|
2661
|
-
sectionBars: opts.bars,
|
|
2662
|
-
inPrechorusLift: inLift,
|
|
2663
|
-
swingAmount,
|
|
2664
|
-
barHasOpenHh,
|
|
2665
|
-
openHhBeat: openHhBeatIndex,
|
|
2666
|
-
rng
|
|
2667
|
-
};
|
|
2668
|
-
if (playMainVoices) {
|
|
2669
|
-
if (inLift) {
|
|
2670
|
-
generatePreChorusBuildup(ctx, sec, isLastBar);
|
|
2671
|
-
}
|
|
2672
|
-
if (euclidKick && !inLift) {
|
|
2673
|
-
const stepLength = 4 / euclidSteps;
|
|
2674
|
-
for (let step = 0; step < euclidKick.length; step += 1) {
|
|
2675
|
-
const onset = step * stepLength;
|
|
2676
|
-
if (euclidKick[step] && Math.floor(onset) === beat) {
|
|
2677
|
-
const wholeBeat = Math.abs(onset - Math.round(onset)) < 1e-9;
|
|
2678
|
-
track.add(
|
|
2679
|
-
GM.BD,
|
|
2680
|
-
barStart + onset,
|
|
2681
|
-
Math.min(0.5, stepLength),
|
|
2682
|
-
velocity * (wholeBeat ? 1 : 0.85)
|
|
2683
|
-
);
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
} else if (kick) {
|
|
2687
|
-
generateKickForBeat(ctx, sec, kick);
|
|
2688
|
-
}
|
|
2689
|
-
generateSnareForBeat(ctx, sec, section === "intro" && bar === 0);
|
|
2690
|
-
if (sec.useGhostNotes && !inLift) {
|
|
2691
|
-
generateGhostNotesForBeat(ctx, sec);
|
|
2692
|
-
}
|
|
2693
|
-
}
|
|
2694
|
-
generateHiHatForBeat(ctx, sec);
|
|
2695
|
-
}
|
|
2696
|
-
if (sec.useFootHh && shouldPlayHiHat(role)) {
|
|
2697
|
-
for (let fhhBeat = 0; fhhBeat < 4; fhhBeat += 2) {
|
|
2698
|
-
const fhhTick = barStart + fhhBeat;
|
|
2699
|
-
const occupied = track.hits.some((h) => h.pitch === GM.FHH && h.startBeat === fhhTick);
|
|
2700
|
-
if (!occupied) {
|
|
2701
|
-
track.add(GM.FHH, fhhTick, 0.5, footHiHatVelocity(rng));
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
2705
|
-
let percussionConfig = getPercussionConfig(percMood, section);
|
|
2706
|
-
if (role === "fxOnly" && !percussionConfig.tambourine && !percussionConfig.shaker && !percussionConfig.handclap) {
|
|
2707
|
-
percussionConfig = {
|
|
2708
|
-
tambourine: false,
|
|
2709
|
-
shaker: true,
|
|
2710
|
-
handclap: false,
|
|
2711
|
-
shaker16th: false
|
|
2712
|
-
};
|
|
2713
|
-
}
|
|
2714
|
-
generateAuxPercussionForBar(
|
|
2715
|
-
track,
|
|
2716
|
-
barStart,
|
|
2717
|
-
percussionConfig,
|
|
2718
|
-
role,
|
|
2719
|
-
densityMult,
|
|
2720
|
-
rng,
|
|
2721
|
-
opts.bpm
|
|
2722
|
-
);
|
|
2723
|
-
}
|
|
2724
|
-
return track.hits;
|
|
2725
|
-
}
|
|
2726
30
|
|
|
2727
|
-
// src/generate/groove/index.ts
|
|
2728
|
-
var DEFAULT_TS3 = { numerator: 4, denominator: 4 };
|
|
2729
|
-
var DEFAULT_TIMING = 0.02;
|
|
2730
|
-
var DEFAULT_VELOCITY_JITTER = 8;
|
|
2731
|
-
var DEFAULT_ACCENT = 12;
|
|
2732
|
-
var DEFAULT_BASE_VELOCITY = 80;
|
|
2733
|
-
var MAX_METRIC_WEIGHT = 3;
|
|
2734
|
-
var MIN_VELOCITY = 1;
|
|
2735
|
-
var MAX_VELOCITY = 127;
|
|
2736
|
-
function humanize(events, opts = {}) {
|
|
2737
|
-
const ts = opts.ts ?? DEFAULT_TS3;
|
|
2738
|
-
assertTimeSignature(ts);
|
|
2739
|
-
assertNoteEvents(events, "humanize events");
|
|
2740
|
-
const timing = opts.timing ?? DEFAULT_TIMING;
|
|
2741
|
-
const velocityJitter = opts.velocity ?? DEFAULT_VELOCITY_JITTER;
|
|
2742
|
-
const accent = opts.accent ?? DEFAULT_ACCENT;
|
|
2743
|
-
const baseVelocity = opts.baseVelocity ?? DEFAULT_BASE_VELOCITY;
|
|
2744
|
-
assertRange(timing, 0, Number.MAX_SAFE_INTEGER, "humanize timing");
|
|
2745
|
-
assertRange(velocityJitter, 0, 127, "humanize velocity jitter");
|
|
2746
|
-
assertRange(accent, 0, 127, "humanize accent");
|
|
2747
|
-
assertRange(baseVelocity, 0, 127, "humanize base velocity");
|
|
2748
|
-
const rng = createRng(opts.seed ?? 0);
|
|
2749
|
-
return events.map((event) => {
|
|
2750
|
-
const timingOffset = rng.float(-timing, timing);
|
|
2751
|
-
const startBeat = Math.max(0, event.startBeat + timingOffset);
|
|
2752
|
-
const weight = metricWeight(event.startBeat, ts);
|
|
2753
|
-
const accentBoost = weight / MAX_METRIC_WEIGHT * accent;
|
|
2754
|
-
const velocityOffset = rng.float(-velocityJitter, velocityJitter);
|
|
2755
|
-
const rawVelocity = (event.velocity ?? baseVelocity) + accentBoost + velocityOffset;
|
|
2756
|
-
const velocity = Math.min(MAX_VELOCITY, Math.max(MIN_VELOCITY, Math.round(rawVelocity)));
|
|
2757
|
-
return {
|
|
2758
|
-
pitch: event.pitch,
|
|
2759
|
-
startBeat,
|
|
2760
|
-
durationBeat: event.durationBeat,
|
|
2761
|
-
velocity
|
|
2762
|
-
};
|
|
2763
|
-
});
|
|
2764
|
-
}
|
|
2765
|
-
function quantizeToGrid(beat, barBeats, subdivision, slotsPerBar) {
|
|
2766
|
-
const barIndex = Math.floor(beat / barBeats);
|
|
2767
|
-
const modBeat = beat - barIndex * barBeats;
|
|
2768
|
-
let slotIndex = Math.round(modBeat * subdivision);
|
|
2769
|
-
let effectiveBarIndex = barIndex;
|
|
2770
|
-
if (slotIndex >= slotsPerBar) {
|
|
2771
|
-
slotIndex = 0;
|
|
2772
|
-
effectiveBarIndex += 1;
|
|
2773
|
-
}
|
|
2774
|
-
return {
|
|
2775
|
-
quantizedBeat: effectiveBarIndex * barBeats + slotIndex / subdivision,
|
|
2776
|
-
slotIndex
|
|
2777
|
-
};
|
|
2778
|
-
}
|
|
2779
|
-
function extractGrooveTemplate(events, ts, subdivision) {
|
|
2780
|
-
assertTimeSignature(ts);
|
|
2781
|
-
assertNoteEvents(events, "groove source events");
|
|
2782
|
-
assertPositiveInt(subdivision, "groove subdivision");
|
|
2783
|
-
const barBeats = beatsPerBar(ts);
|
|
2784
|
-
const slotsPerBar = Math.round(barBeats * subdivision);
|
|
2785
|
-
assertGenerationBudget(slotsPerBar, "groove template slots");
|
|
2786
|
-
const offsetSums = new Array(slotsPerBar).fill(0);
|
|
2787
|
-
const velocitySums = new Array(slotsPerBar).fill(0);
|
|
2788
|
-
const counts = new Array(slotsPerBar).fill(0);
|
|
2789
|
-
const velocityCounts = new Array(slotsPerBar).fill(0);
|
|
2790
|
-
for (const event of events) {
|
|
2791
|
-
const { quantizedBeat, slotIndex } = quantizeToGrid(
|
|
2792
|
-
event.startBeat,
|
|
2793
|
-
barBeats,
|
|
2794
|
-
subdivision,
|
|
2795
|
-
slotsPerBar
|
|
2796
|
-
);
|
|
2797
|
-
offsetSums[slotIndex] = (offsetSums[slotIndex] ?? 0) + (event.startBeat - quantizedBeat);
|
|
2798
|
-
counts[slotIndex] = (counts[slotIndex] ?? 0) + 1;
|
|
2799
|
-
if (event.velocity !== void 0) {
|
|
2800
|
-
velocitySums[slotIndex] = (velocitySums[slotIndex] ?? 0) + event.velocity;
|
|
2801
|
-
velocityCounts[slotIndex] = (velocityCounts[slotIndex] ?? 0) + 1;
|
|
2802
|
-
}
|
|
2803
|
-
}
|
|
2804
|
-
const slots = [];
|
|
2805
|
-
for (let i = 0; i < slotsPerBar; i += 1) {
|
|
2806
|
-
const count = counts[i] ?? 0;
|
|
2807
|
-
if (count === 0) {
|
|
2808
|
-
slots.push({ timingOffset: 0, velocity: null });
|
|
2809
|
-
continue;
|
|
2810
|
-
}
|
|
2811
|
-
const velocityCount = velocityCounts[i] ?? 0;
|
|
2812
|
-
slots.push({
|
|
2813
|
-
timingOffset: (offsetSums[i] ?? 0) / count,
|
|
2814
|
-
velocity: velocityCount > 0 ? (velocitySums[i] ?? 0) / velocityCount : null
|
|
2815
|
-
});
|
|
2816
|
-
}
|
|
2817
|
-
return { subdivision, slotsPerBar, slots, ts };
|
|
2818
|
-
}
|
|
2819
|
-
function applyGrooveTemplate(events, template, ts) {
|
|
2820
|
-
assertTimeSignature(ts);
|
|
2821
|
-
assertNoteEvents(events, "groove target events");
|
|
2822
|
-
assertPositiveInt(template.subdivision, "template subdivision");
|
|
2823
|
-
assertPositiveInt(template.slotsPerBar, "template slotsPerBar");
|
|
2824
|
-
assertGenerationBudget(template.slots.length, "template slots");
|
|
2825
|
-
if (template.slots.length !== template.slotsPerBar) {
|
|
2826
|
-
throw new RangeError("template slots length must equal slotsPerBar");
|
|
2827
|
-
}
|
|
2828
|
-
if (template.ts && (template.ts.numerator !== ts.numerator || template.ts.denominator !== ts.denominator)) {
|
|
2829
|
-
throw new Error(
|
|
2830
|
-
`Groove template meter ${formatTimeSignature(template.ts)} does not match apply-time meter ${formatTimeSignature(ts)}`
|
|
2831
|
-
);
|
|
2832
|
-
}
|
|
2833
|
-
const barBeats = beatsPerBar(ts);
|
|
2834
|
-
return events.map((event) => {
|
|
2835
|
-
const { quantizedBeat, slotIndex } = quantizeToGrid(
|
|
2836
|
-
event.startBeat,
|
|
2837
|
-
barBeats,
|
|
2838
|
-
template.subdivision,
|
|
2839
|
-
template.slotsPerBar
|
|
2840
|
-
);
|
|
2841
|
-
const slot = template.slots[slotIndex];
|
|
2842
|
-
const startBeat = quantizedBeat + (slot?.timingOffset ?? 0);
|
|
2843
|
-
const velocity = slot && slot.velocity !== null && slot.velocity !== void 0 ? Math.round(slot.velocity) : event.velocity;
|
|
2844
|
-
return {
|
|
2845
|
-
pitch: event.pitch,
|
|
2846
|
-
startBeat,
|
|
2847
|
-
durationBeat: event.durationBeat,
|
|
2848
|
-
velocity
|
|
2849
|
-
};
|
|
2850
|
-
});
|
|
2851
|
-
}
|
|
2852
31
|
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
}
|
|
32
|
+
var _chunk357ZDPVUcjs = require('../chunk-357ZDPVU.cjs');
|
|
33
|
+
require('../chunk-KQN2ZYND.cjs');
|
|
34
|
+
require('../chunk-YFJXS3AU.cjs');
|
|
35
|
+
require('../chunk-WFDXOLUB.cjs');
|
|
36
|
+
require('../chunk-BSLTLGM3.cjs');
|
|
37
|
+
require('../chunk-PI3UXRWK.cjs');
|
|
2860
38
|
|
|
2861
|
-
// src/analyze/functional/roman.ts
|
|
2862
|
-
var ROMAN = ["I", "II", "III", "IV", "V", "VI", "VII"];
|
|
2863
|
-
var OFFSET_SPELLING = [
|
|
2864
|
-
[1, ""],
|
|
2865
|
-
[2, "b"],
|
|
2866
|
-
[2, ""],
|
|
2867
|
-
[3, "b"],
|
|
2868
|
-
[3, ""],
|
|
2869
|
-
[4, ""],
|
|
2870
|
-
[4, "#"],
|
|
2871
|
-
[5, ""],
|
|
2872
|
-
[6, "b"],
|
|
2873
|
-
[6, ""],
|
|
2874
|
-
[7, "b"],
|
|
2875
|
-
[7, ""]
|
|
2876
|
-
];
|
|
2877
|
-
var ROMAN_STYLE = {
|
|
2878
|
-
maj: { lower: false, suffix: "" },
|
|
2879
|
-
min: { lower: true, suffix: "" },
|
|
2880
|
-
dim: { lower: true, suffix: "o" },
|
|
2881
|
-
aug: { lower: false, suffix: "+" },
|
|
2882
|
-
maj7: { lower: false, suffix: "maj7" },
|
|
2883
|
-
min7: { lower: true, suffix: "7" },
|
|
2884
|
-
dom7: { lower: false, suffix: "7" },
|
|
2885
|
-
dim7: { lower: true, suffix: "o7" },
|
|
2886
|
-
m7b5: { lower: true, suffix: "\xF87" },
|
|
2887
|
-
minMaj7: { lower: true, suffix: "maj7" },
|
|
2888
|
-
aug7: { lower: false, suffix: "+7" },
|
|
2889
|
-
augMaj7: { lower: false, suffix: "+maj7" },
|
|
2890
|
-
majb5: { lower: false, suffix: "b5" },
|
|
2891
|
-
"6": { lower: false, suffix: "add6" },
|
|
2892
|
-
min6: { lower: true, suffix: "add6" },
|
|
2893
|
-
"6/9": { lower: false, suffix: "69" },
|
|
2894
|
-
sus2: { lower: false, suffix: "sus2" },
|
|
2895
|
-
sus4: { lower: false, suffix: "sus4" },
|
|
2896
|
-
add9: { lower: false, suffix: "add9" },
|
|
2897
|
-
add11: { lower: false, suffix: "add11" },
|
|
2898
|
-
maj9: { lower: false, suffix: "maj9" },
|
|
2899
|
-
min9: { lower: true, suffix: "9" },
|
|
2900
|
-
dom9: { lower: false, suffix: "9" },
|
|
2901
|
-
"7b9": { lower: false, suffix: "7b9" },
|
|
2902
|
-
"7#9": { lower: false, suffix: "7#9" },
|
|
2903
|
-
"7#11": { lower: false, suffix: "7#11" },
|
|
2904
|
-
"7b13": { lower: false, suffix: "7b13" },
|
|
2905
|
-
"11": { lower: false, suffix: "11" },
|
|
2906
|
-
"13": { lower: false, suffix: "13" },
|
|
2907
|
-
"5": { lower: false, suffix: "5" }
|
|
2908
|
-
};
|
|
2909
|
-
var SUFFIX_QUALITY = new Map(
|
|
2910
|
-
Object.entries(ROMAN_STYLE).map(
|
|
2911
|
-
([quality, style]) => [`${style.lower ? "l" : "u"}:${style.suffix}`, quality]
|
|
2912
|
-
)
|
|
2913
|
-
);
|
|
2914
|
-
function romanStyle(quality) {
|
|
2915
|
-
return ROMAN_STYLE[quality];
|
|
2916
|
-
}
|
|
2917
|
-
function romanSpelling(rootPc, key) {
|
|
2918
|
-
const tones = scaleTonesInDegreeOrder(key);
|
|
2919
|
-
const diatonic = tones.indexOf(mod12(rootPc));
|
|
2920
|
-
if (diatonic >= 0) {
|
|
2921
|
-
return { degreeNumber: diatonic + 1, accidental: "" };
|
|
2922
|
-
}
|
|
2923
|
-
for (let i = 0; i < tones.length; i += 1) {
|
|
2924
|
-
const degreeNumber2 = i + 1;
|
|
2925
|
-
if (degreeNumber2 === 1 || degreeNumber2 === 5) {
|
|
2926
|
-
continue;
|
|
2927
|
-
}
|
|
2928
|
-
if (mod12((tones[i] ?? 0) - 1) === mod12(rootPc)) {
|
|
2929
|
-
return { degreeNumber: degreeNumber2, accidental: "b" };
|
|
2930
|
-
}
|
|
2931
|
-
}
|
|
2932
|
-
for (let i = 0; i < tones.length; i += 1) {
|
|
2933
|
-
if (mod12((tones[i] ?? 0) + 1) === mod12(rootPc)) {
|
|
2934
|
-
return { degreeNumber: i + 1, accidental: "#" };
|
|
2935
|
-
}
|
|
2936
|
-
}
|
|
2937
|
-
const [degreeNumber, accidental] = OFFSET_SPELLING[mod12(rootPc - key.rootPc)] ?? [1, ""];
|
|
2938
|
-
return { degreeNumber, accidental };
|
|
2939
|
-
}
|
|
2940
|
-
function baseMarker(quality) {
|
|
2941
|
-
if (quality === "dim" || quality === "dim7") {
|
|
2942
|
-
return "o";
|
|
2943
|
-
}
|
|
2944
|
-
if (quality === "m7b5") {
|
|
2945
|
-
return "\xF8";
|
|
2946
|
-
}
|
|
2947
|
-
if (quality === "aug" || quality === "aug7") {
|
|
2948
|
-
return "+";
|
|
2949
|
-
}
|
|
2950
|
-
if (quality === "augMaj7") {
|
|
2951
|
-
return "+maj7";
|
|
2952
|
-
}
|
|
2953
|
-
if (quality === "maj7" || quality === "minMaj7") {
|
|
2954
|
-
return "maj7";
|
|
2955
|
-
}
|
|
2956
|
-
return "";
|
|
2957
|
-
}
|
|
2958
|
-
var TRIAD_FIGURES = { 1: "6", 2: "64" };
|
|
2959
|
-
var SEVENTH_FIGURES = { 1: "65", 2: "43", 3: "42" };
|
|
2960
|
-
var TRIAD_FIGURE_QUALITIES = /* @__PURE__ */ new Set(["maj", "min", "dim", "aug"]);
|
|
2961
|
-
var SEVENTH_FIGURE_QUALITIES = /* @__PURE__ */ new Set([
|
|
2962
|
-
"maj7",
|
|
2963
|
-
"min7",
|
|
2964
|
-
"dom7",
|
|
2965
|
-
"dim7",
|
|
2966
|
-
"m7b5",
|
|
2967
|
-
"minMaj7",
|
|
2968
|
-
"aug7",
|
|
2969
|
-
"augMaj7"
|
|
2970
|
-
]);
|
|
2971
|
-
function chordToRoman(chord, key) {
|
|
2972
|
-
const { degreeNumber, accidental } = romanSpelling(chord.rootPc, key);
|
|
2973
|
-
const { lower, suffix } = romanStyle(chord.quality);
|
|
2974
|
-
const numeral = ROMAN[degreeNumber - 1] ?? "I";
|
|
2975
|
-
const cased = lower ? numeral.toLowerCase() : numeral;
|
|
2976
|
-
let inversion = 0;
|
|
2977
|
-
if (chord.bassPc !== void 0) {
|
|
2978
|
-
const idx = chord.intervals.findIndex((iv) => mod12(chord.rootPc + iv) === chord.bassPc);
|
|
2979
|
-
if (idx > 0) {
|
|
2980
|
-
inversion = idx;
|
|
2981
|
-
}
|
|
2982
|
-
}
|
|
2983
|
-
if (inversion > 0) {
|
|
2984
|
-
const figure = SEVENTH_FIGURE_QUALITIES.has(chord.quality) ? SEVENTH_FIGURES[inversion] : TRIAD_FIGURE_QUALITIES.has(chord.quality) ? TRIAD_FIGURES[inversion] : void 0;
|
|
2985
|
-
if (figure !== void 0) {
|
|
2986
|
-
return `${accidental}${cased}${baseMarker(chord.quality)}${figure}`;
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
return `${accidental}${cased}${suffix}`;
|
|
2990
|
-
}
|
|
2991
39
|
|
|
2992
|
-
// src/analyze/functional/function.ts
|
|
2993
|
-
var FUNCTION_BY_OFFSET = [
|
|
2994
|
-
"tonic",
|
|
2995
|
-
// 0 I
|
|
2996
|
-
"subdominant",
|
|
2997
|
-
// 1 bII (Neapolitan)
|
|
2998
|
-
"subdominant",
|
|
2999
|
-
// 2 ii
|
|
3000
|
-
"tonic",
|
|
3001
|
-
// 3 bIII
|
|
3002
|
-
"tonic",
|
|
3003
|
-
// 4 iii
|
|
3004
|
-
"subdominant",
|
|
3005
|
-
// 5 IV
|
|
3006
|
-
"dominant",
|
|
3007
|
-
// 6 #IV / bV
|
|
3008
|
-
"dominant",
|
|
3009
|
-
// 7 V
|
|
3010
|
-
"subdominant",
|
|
3011
|
-
// 8 bVI
|
|
3012
|
-
"tonic",
|
|
3013
|
-
// 9 vi
|
|
3014
|
-
"subdominant",
|
|
3015
|
-
// 10 bVII
|
|
3016
|
-
"dominant"
|
|
3017
|
-
// 11 vii
|
|
3018
|
-
];
|
|
3019
|
-
function isMinorKey(key) {
|
|
3020
|
-
const hasMinorThird = key.modeMask12 >> 3 & 1;
|
|
3021
|
-
const hasMajorThird = key.modeMask12 >> 4 & 1;
|
|
3022
|
-
return Boolean(hasMinorThird) && !hasMajorThird;
|
|
3023
|
-
}
|
|
3024
|
-
function functionOf(chord, key) {
|
|
3025
|
-
return FUNCTION_BY_OFFSET[mod12(chord.rootPc - key.rootPc)] ?? "tonic";
|
|
3026
|
-
}
|
|
3027
|
-
function isDiatonic(chord, key) {
|
|
3028
|
-
return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));
|
|
3029
|
-
}
|
|
3030
|
-
function parallelKey(key) {
|
|
3031
|
-
return {
|
|
3032
|
-
rootPc: mod12(key.rootPc),
|
|
3033
|
-
modeMask12: isMinorKey(key) ? MAJOR_MASK : NATURAL_MINOR_MASK
|
|
3034
|
-
};
|
|
3035
|
-
}
|
|
3036
40
|
|
|
3037
|
-
// src/analyze/functional/borrowed.ts
|
|
3038
|
-
function isHarmonicMinorAlteration(chord, key) {
|
|
3039
|
-
return isMinorKey(key) && isDiatonic(chord, { rootPc: mod12(key.rootPc), modeMask12: HARMONIC_MINOR_MASK });
|
|
3040
|
-
}
|
|
3041
|
-
function isBorrowedChord(chord, key) {
|
|
3042
|
-
if (isDiatonic(chord, key)) {
|
|
3043
|
-
return false;
|
|
3044
|
-
}
|
|
3045
|
-
if (isHarmonicMinorAlteration(chord, key)) {
|
|
3046
|
-
return false;
|
|
3047
|
-
}
|
|
3048
|
-
return isDiatonic(chord, parallelKey(key));
|
|
3049
|
-
}
|
|
3050
|
-
function borrowedSource(chord, key) {
|
|
3051
|
-
if (isDiatonic(chord, key)) {
|
|
3052
|
-
return null;
|
|
3053
|
-
}
|
|
3054
|
-
if (isNeapolitan(chord, key)) {
|
|
3055
|
-
return "neapolitan";
|
|
3056
|
-
}
|
|
3057
|
-
if (isBorrowedChord(chord, key)) {
|
|
3058
|
-
return isMinorKey(key) ? "parallel-major" : "parallel-minor";
|
|
3059
|
-
}
|
|
3060
|
-
return null;
|
|
3061
|
-
}
|
|
3062
41
|
|
|
3063
|
-
// src/theory/harmony/index.ts
|
|
3064
|
-
function roleOf(pitch, chord, chordId = 0) {
|
|
3065
|
-
const interval = ((Math.trunc(pitch) - chord.rootPc) % 12 + 12) % 12;
|
|
3066
|
-
const tones = new Set(chord.intervals.map((i) => (i % 12 + 12) % 12));
|
|
3067
|
-
const hasThird = tones.has(3) || tones.has(4);
|
|
3068
|
-
const isSuspendedTone = !hasThird && (interval === 5 && tones.has(5) || interval === 2 && tones.has(2));
|
|
3069
|
-
const chordRole = chordToneRole(pitch, chord);
|
|
3070
|
-
let role;
|
|
3071
|
-
let lock;
|
|
3072
|
-
if (isSuspendedTone) {
|
|
3073
|
-
role = "third";
|
|
3074
|
-
lock = "quality";
|
|
3075
|
-
} else if (chordRole !== null) {
|
|
3076
|
-
role = chordRole;
|
|
3077
|
-
lock = chordRole === "root" ? "identity" : chordRole === "third" ? "quality" : "voicing";
|
|
3078
|
-
} else {
|
|
3079
|
-
role = "tension";
|
|
3080
|
-
lock = "voicing";
|
|
3081
|
-
}
|
|
3082
|
-
return { role, lock, belongsToChordId: chordId };
|
|
3083
|
-
}
|
|
3084
42
|
|
|
3085
|
-
// src/generate/harmonize/index.ts
|
|
3086
|
-
var FALLBACK = { rootPc: 0, quality: "maj", secondaryDominant: false, base: 0 };
|
|
3087
|
-
var DEFAULT_METER = { numerator: 4, denominator: 4 };
|
|
3088
|
-
var COMFORT_LOW = 55;
|
|
3089
|
-
var COMFORT_HIGH = 79;
|
|
3090
|
-
var TESSITURA_WEIGHT = 1e-3;
|
|
3091
|
-
var TIE_BREAK_JITTER = 1e-6;
|
|
3092
|
-
function pitchClass5(pitch) {
|
|
3093
|
-
return (Math.trunc(pitch) % 12 + 12) % 12;
|
|
3094
|
-
}
|
|
3095
|
-
function inferKey(melody) {
|
|
3096
|
-
let best = majorKey(0);
|
|
3097
|
-
let bestScore = Number.NEGATIVE_INFINITY;
|
|
3098
|
-
const candidates = [];
|
|
3099
|
-
for (let tonic = 0; tonic < 12; tonic += 1) {
|
|
3100
|
-
candidates.push(majorKey(tonic), minorKey(tonic));
|
|
3101
|
-
}
|
|
3102
|
-
for (const key of candidates) {
|
|
3103
|
-
const tonic = pitchClass5(key.rootPc);
|
|
3104
|
-
const isMinor = key.modeMask12 === NATURAL_MINOR_MASK;
|
|
3105
|
-
const leadingTone = (tonic + 11) % 12;
|
|
3106
|
-
let score = 0;
|
|
3107
|
-
for (const n of melody) {
|
|
3108
|
-
const w = Math.max(0.25, n.durationBeat);
|
|
3109
|
-
const pc = pitchClass5(n.pitch);
|
|
3110
|
-
if (isScaleTone(n.pitch, key) || isMinor && pc === leadingTone) {
|
|
3111
|
-
score += w;
|
|
3112
|
-
}
|
|
3113
|
-
if (pc === tonic) {
|
|
3114
|
-
score += 0.5 * w;
|
|
3115
|
-
}
|
|
3116
|
-
}
|
|
3117
|
-
if (score > bestScore) {
|
|
3118
|
-
bestScore = score;
|
|
3119
|
-
best = key;
|
|
3120
|
-
}
|
|
3121
|
-
}
|
|
3122
|
-
return best;
|
|
3123
|
-
}
|
|
3124
|
-
function buildCandidates(key, reharmonize) {
|
|
3125
|
-
const tones = scaleTonesInDegreeOrder(key);
|
|
3126
|
-
const candidates = tones.map((rootPc, degree) => ({
|
|
3127
|
-
rootPc,
|
|
3128
|
-
quality: diatonicTriad(degree, key).quality,
|
|
3129
|
-
degree,
|
|
3130
|
-
secondaryDominant: false,
|
|
3131
|
-
base: 0
|
|
3132
|
-
}));
|
|
3133
|
-
if (reharmonize !== "diatonic") {
|
|
3134
|
-
for (const target of [1, 3, 4, 5]) {
|
|
3135
|
-
const targetRoot = tones[target] ?? 0;
|
|
3136
|
-
candidates.push({
|
|
3137
|
-
rootPc: (targetRoot + 7) % 12,
|
|
3138
|
-
quality: "dom7",
|
|
3139
|
-
secondaryDominant: true,
|
|
3140
|
-
targetDegree: target,
|
|
3141
|
-
base: 1.5
|
|
3142
|
-
});
|
|
3143
|
-
}
|
|
3144
|
-
}
|
|
3145
|
-
if (reharmonize === "borrowed") {
|
|
3146
|
-
const parallel = parallelKey(key);
|
|
3147
|
-
const parallelTones = scaleTonesInDegreeOrder(parallel);
|
|
3148
|
-
for (let degree = 0; degree < parallelTones.length; degree += 1) {
|
|
3149
|
-
const chord = diatonicTriad(degree, parallel);
|
|
3150
|
-
if (!isDiatonic(chord, key)) {
|
|
3151
|
-
candidates.push({
|
|
3152
|
-
rootPc: chord.rootPc,
|
|
3153
|
-
quality: chord.quality,
|
|
3154
|
-
secondaryDominant: false,
|
|
3155
|
-
base: 1.25
|
|
3156
|
-
});
|
|
3157
|
-
}
|
|
3158
|
-
}
|
|
3159
|
-
}
|
|
3160
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3161
|
-
return candidates.filter((candidate) => {
|
|
3162
|
-
const id = `${candidate.rootPc}:${candidate.quality}`;
|
|
3163
|
-
if (seen.has(id)) {
|
|
3164
|
-
return false;
|
|
3165
|
-
}
|
|
3166
|
-
seen.add(id);
|
|
3167
|
-
return true;
|
|
3168
|
-
});
|
|
3169
|
-
}
|
|
3170
|
-
function emissionCost(seg, cand, melody, key) {
|
|
3171
|
-
const pcs = chordPitchClasses(makeChord(cand.rootPc, cand.quality));
|
|
3172
|
-
let cost = cand.base;
|
|
3173
|
-
for (const idx of seg.noteIndices) {
|
|
3174
|
-
const note = melody[idx];
|
|
3175
|
-
if (!note) {
|
|
3176
|
-
continue;
|
|
3177
|
-
}
|
|
3178
|
-
const overlapStart = Math.max(note.startBeat, seg.startBeat);
|
|
3179
|
-
const overlap = Math.min(note.startBeat + note.durationBeat, seg.endBeat) - overlapStart;
|
|
3180
|
-
if (overlap <= 0) {
|
|
3181
|
-
continue;
|
|
3182
|
-
}
|
|
3183
|
-
const w = Math.max(0.25, overlap);
|
|
3184
|
-
if (pcs.includes(pitchClass5(note.pitch))) {
|
|
3185
|
-
continue;
|
|
3186
|
-
}
|
|
3187
|
-
const strong = isStrongBeat(overlapStart, DEFAULT_METER);
|
|
3188
|
-
cost += (strong ? 10 : 2) * w;
|
|
3189
|
-
if (!isScaleTone(note.pitch, key)) {
|
|
3190
|
-
cost += w;
|
|
3191
|
-
}
|
|
3192
|
-
}
|
|
3193
|
-
return cost;
|
|
3194
|
-
}
|
|
3195
|
-
function tessituraCost(melody) {
|
|
3196
|
-
let cost = 0;
|
|
3197
|
-
for (const n of melody) {
|
|
3198
|
-
if (n.pitch < COMFORT_LOW) {
|
|
3199
|
-
cost += (COMFORT_LOW - n.pitch) * TESSITURA_WEIGHT;
|
|
3200
|
-
} else if (n.pitch > COMFORT_HIGH) {
|
|
3201
|
-
cost += (n.pitch - COMFORT_HIGH) * TESSITURA_WEIGHT;
|
|
3202
|
-
}
|
|
3203
|
-
}
|
|
3204
|
-
return cost;
|
|
3205
|
-
}
|
|
3206
|
-
function transitionCost(prev, cur, tonicPc) {
|
|
3207
|
-
let cost = 0;
|
|
3208
|
-
if ((prev.rootPc - cur.rootPc + 12) % 12 === 7) {
|
|
3209
|
-
cost -= 2;
|
|
3210
|
-
}
|
|
3211
|
-
if (prev.rootPc === (tonicPc + 7) % 12 && (prev.quality === "maj" || prev.quality === "dom7") && cur.rootPc === tonicPc) {
|
|
3212
|
-
cost -= 3;
|
|
3213
|
-
}
|
|
3214
|
-
if (prev.secondaryDominant) {
|
|
3215
|
-
cost += cur.degree === prev.targetDegree ? -3 : 2;
|
|
3216
|
-
}
|
|
3217
|
-
const dist = Math.min((prev.rootPc - cur.rootPc + 12) % 12, (cur.rootPc - prev.rootPc + 12) % 12);
|
|
3218
|
-
cost += dist * 0.3;
|
|
3219
|
-
return cost;
|
|
3220
|
-
}
|
|
3221
|
-
function harmonizeOnce(melody, key, candidates, segments, jitter) {
|
|
3222
|
-
const tonicPc = pitchClass5(key.rootPc);
|
|
3223
|
-
const n = candidates.length;
|
|
3224
|
-
const candAt = (i) => candidates[i] ?? FALLBACK;
|
|
3225
|
-
const seg0 = segments[0];
|
|
3226
|
-
let dp = candidates.map(
|
|
3227
|
-
(c, ci) => (seg0 ? emissionCost(seg0, c, melody, key) : 0) + (jitter[ci] ?? 0)
|
|
3228
|
-
);
|
|
3229
|
-
const back = [];
|
|
3230
|
-
for (let s = 1; s < segments.length; s += 1) {
|
|
3231
|
-
const seg = segments[s];
|
|
3232
|
-
if (!seg) {
|
|
3233
|
-
continue;
|
|
3234
|
-
}
|
|
3235
|
-
const next = [];
|
|
3236
|
-
const ptr = [];
|
|
3237
|
-
for (let c = 0; c < n; c += 1) {
|
|
3238
|
-
let best = Number.POSITIVE_INFINITY;
|
|
3239
|
-
let bestPrev = 0;
|
|
3240
|
-
for (let p = 0; p < n; p += 1) {
|
|
3241
|
-
const cost = (dp[p] ?? Number.POSITIVE_INFINITY) + transitionCost(candAt(p), candAt(c), tonicPc);
|
|
3242
|
-
if (cost < best) {
|
|
3243
|
-
best = cost;
|
|
3244
|
-
bestPrev = p;
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
next[c] = best + emissionCost(seg, candAt(c), melody, key) + (jitter[c] ?? 0);
|
|
3248
|
-
ptr[c] = bestPrev;
|
|
3249
|
-
}
|
|
3250
|
-
dp = next;
|
|
3251
|
-
back.push(ptr);
|
|
3252
|
-
}
|
|
3253
|
-
let bestCost = Number.POSITIVE_INFINITY;
|
|
3254
|
-
let bestEnd = 0;
|
|
3255
|
-
for (let c = 0; c < n; c += 1) {
|
|
3256
|
-
const cadence = candAt(c).rootPc === tonicPc ? -2 : 0;
|
|
3257
|
-
const total = (dp[c] ?? Number.POSITIVE_INFINITY) + cadence;
|
|
3258
|
-
if (total < bestCost) {
|
|
3259
|
-
bestCost = total;
|
|
3260
|
-
bestEnd = c;
|
|
3261
|
-
}
|
|
3262
|
-
}
|
|
3263
|
-
const path = new Array(segments.length).fill(bestEnd);
|
|
3264
|
-
for (let s = segments.length - 2; s >= 0; s -= 1) {
|
|
3265
|
-
const ptr = back[s];
|
|
3266
|
-
const nextIdx = path[s + 1] ?? bestEnd;
|
|
3267
|
-
path[s] = ptr ? ptr[nextIdx] ?? bestEnd : bestEnd;
|
|
3268
|
-
}
|
|
3269
|
-
return { cost: bestCost, path };
|
|
3270
|
-
}
|
|
3271
|
-
function harmonizeMelody(opts) {
|
|
3272
|
-
const noteIndex = createNoteEventIndex(opts.melody);
|
|
3273
|
-
const key = opts.key === "infer" ? inferKey(opts.melody) : opts.key;
|
|
3274
|
-
const candidates = buildCandidates(key, opts.reharmonize);
|
|
3275
|
-
const candAt = (i) => candidates[i] ?? FALLBACK;
|
|
3276
|
-
const rng = createRng(opts.seed ?? 0);
|
|
3277
|
-
const jitter = candidates.map(() => rng.next() * TIE_BREAK_JITTER);
|
|
3278
|
-
const melodyEnd = noteIndex.notes.reduce((m, n) => Math.max(m, n.endBeat), 0);
|
|
3279
|
-
const requestedHr = assertRange(
|
|
3280
|
-
opts.harmonicRhythm,
|
|
3281
|
-
Number.MIN_VALUE,
|
|
3282
|
-
Number.MAX_SAFE_INTEGER,
|
|
3283
|
-
"harmonic rhythm"
|
|
3284
|
-
);
|
|
3285
|
-
const hr = Math.max(0.25, requestedHr);
|
|
3286
|
-
const segCount = Math.max(1, Math.ceil(melodyEnd / hr));
|
|
3287
|
-
assertGenerationBudget(segCount, "harmonic segments");
|
|
3288
|
-
assertGenerationBudget(segCount * candidates.length * candidates.length, "harmonization search");
|
|
3289
|
-
const segments = Array.from({ length: segCount }, (_, s) => ({
|
|
3290
|
-
startBeat: s * hr,
|
|
3291
|
-
endBeat: (s + 1) * hr,
|
|
3292
|
-
noteIndices: []
|
|
3293
|
-
}));
|
|
3294
|
-
let memberships = 0;
|
|
3295
|
-
for (const indexed of noteIndex.notes) {
|
|
3296
|
-
const first = Math.max(0, Math.floor(indexed.note.startBeat / hr));
|
|
3297
|
-
const lastExclusive = Math.min(segCount, Math.ceil(indexed.endBeat / hr - Number.EPSILON));
|
|
3298
|
-
memberships += Math.max(0, lastExclusive - first);
|
|
3299
|
-
assertGenerationBudget(memberships, "note-to-segment memberships");
|
|
3300
|
-
for (let segment = first; segment < lastExclusive; segment += 1) {
|
|
3301
|
-
segments[segment]?.noteIndices.push(indexed.originalIndex);
|
|
3302
|
-
}
|
|
3303
|
-
}
|
|
3304
|
-
const transposes = [0];
|
|
3305
|
-
if (opts.placement.transposeSearch) {
|
|
3306
|
-
for (let s = -6; s <= 6; s += 1) {
|
|
3307
|
-
if (s !== 0) {
|
|
3308
|
-
transposes.push(s);
|
|
3309
|
-
}
|
|
3310
|
-
}
|
|
3311
|
-
}
|
|
3312
|
-
if (opts.placement.octaveSearch) {
|
|
3313
|
-
for (const o of [-12, 12]) {
|
|
3314
|
-
if (!transposes.includes(o)) {
|
|
3315
|
-
transposes.push(o);
|
|
3316
|
-
}
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
let bestCost = Number.POSITIVE_INFINITY;
|
|
3320
|
-
let bestTs = 0;
|
|
3321
|
-
let bestPath = [];
|
|
3322
|
-
let bestMelody = opts.melody;
|
|
3323
|
-
for (const ts of transposes) {
|
|
3324
|
-
const shifted = opts.melody.map((n) => ({ ...n, pitch: n.pitch + ts }));
|
|
3325
|
-
const { cost, path } = harmonizeOnce(shifted, key, candidates, segments, jitter);
|
|
3326
|
-
const total = cost + tessituraCost(shifted);
|
|
3327
|
-
if (total < bestCost) {
|
|
3328
|
-
bestCost = total;
|
|
3329
|
-
bestTs = ts;
|
|
3330
|
-
bestPath = path;
|
|
3331
|
-
bestMelody = shifted;
|
|
3332
|
-
}
|
|
3333
|
-
}
|
|
3334
|
-
const chords = bestPath.map((ci, s) => {
|
|
3335
|
-
const cand = candAt(ci);
|
|
3336
|
-
const chord = {
|
|
3337
|
-
rootPc: cand.rootPc,
|
|
3338
|
-
quality: cand.quality,
|
|
3339
|
-
startBeat: segments[s]?.startBeat ?? s * hr
|
|
3340
|
-
};
|
|
3341
|
-
if (cand.degree !== void 0) {
|
|
3342
|
-
chord.degree = cand.degree;
|
|
3343
|
-
}
|
|
3344
|
-
if (cand.secondaryDominant) {
|
|
3345
|
-
chord.secondaryDominant = true;
|
|
3346
|
-
}
|
|
3347
|
-
return chord;
|
|
3348
|
-
});
|
|
3349
|
-
const melodyRoles = bestMelody.map((note, noteIndex2) => {
|
|
3350
|
-
const segIdx = Math.min(segments.length - 1, Math.max(0, Math.floor(note.startBeat / hr)));
|
|
3351
|
-
const cand = candAt(bestPath[segIdx] ?? 0);
|
|
3352
|
-
const chord = makeChord(cand.rootPc, cand.quality);
|
|
3353
|
-
return { noteIndex: noteIndex2, role: roleOf(note.pitch, chord).role };
|
|
3354
|
-
});
|
|
3355
|
-
return { transposeSemitones: bestTs, key, chords, melodyRoles };
|
|
3356
|
-
}
|
|
3357
43
|
|
|
3358
|
-
// src/generate/motif/index.ts
|
|
3359
|
-
function pitchClass6(pitch) {
|
|
3360
|
-
return (Math.trunc(pitch) % 12 + 12) % 12;
|
|
3361
|
-
}
|
|
3362
|
-
var MIN_TILE_SPAN = 1 / 256;
|
|
3363
|
-
function upScaleTone(pitch, key) {
|
|
3364
|
-
for (let d = 1; d <= 12; d += 1) {
|
|
3365
|
-
if (isScaleTone(pitch + d, key)) {
|
|
3366
|
-
return pitch + d;
|
|
3367
|
-
}
|
|
3368
|
-
}
|
|
3369
|
-
return pitch + 12;
|
|
3370
|
-
}
|
|
3371
|
-
function downScaleTone(pitch, key) {
|
|
3372
|
-
for (let d = 1; d <= 12; d += 1) {
|
|
3373
|
-
if (isScaleTone(pitch - d, key)) {
|
|
3374
|
-
return pitch - d;
|
|
3375
|
-
}
|
|
3376
|
-
}
|
|
3377
|
-
return pitch - 12;
|
|
3378
|
-
}
|
|
3379
|
-
function stepDiatonic(pitch, degrees, key) {
|
|
3380
|
-
let p = nearestScaleTone(pitch, key);
|
|
3381
|
-
const steps = Math.abs(Math.trunc(degrees));
|
|
3382
|
-
for (let i = 0; i < steps; i += 1) {
|
|
3383
|
-
p = degrees >= 0 ? upScaleTone(p, key) : downScaleTone(p, key);
|
|
3384
|
-
}
|
|
3385
|
-
return p;
|
|
3386
|
-
}
|
|
3387
|
-
function nearestChordTone(pitch, chord) {
|
|
3388
|
-
const pcs = chordPitchClasses(chord);
|
|
3389
|
-
for (let d = 0; d <= 12; d += 1) {
|
|
3390
|
-
if (pcs.includes(pitchClass6(pitch - d))) {
|
|
3391
|
-
return pitch - d;
|
|
3392
|
-
}
|
|
3393
|
-
if (pcs.includes(pitchClass6(pitch + d))) {
|
|
3394
|
-
return pitch + d;
|
|
3395
|
-
}
|
|
3396
|
-
}
|
|
3397
|
-
return pitch;
|
|
3398
|
-
}
|
|
3399
|
-
function cellOrigin(notes) {
|
|
3400
|
-
let origin = Number.POSITIVE_INFINITY;
|
|
3401
|
-
for (const note of notes) {
|
|
3402
|
-
origin = Math.min(origin, note.startBeat);
|
|
3403
|
-
}
|
|
3404
|
-
return Number.isFinite(origin) ? origin : 0;
|
|
3405
|
-
}
|
|
3406
|
-
function cellEnd(notes) {
|
|
3407
|
-
let end = Number.NEGATIVE_INFINITY;
|
|
3408
|
-
for (const note of notes) {
|
|
3409
|
-
end = Math.max(end, note.startBeat + note.durationBeat);
|
|
3410
|
-
}
|
|
3411
|
-
return Number.isFinite(end) ? end : 0;
|
|
3412
|
-
}
|
|
3413
|
-
function cellSpan(cell) {
|
|
3414
|
-
if (cell.notes.length === 0) {
|
|
3415
|
-
return 0;
|
|
3416
|
-
}
|
|
3417
|
-
const start = cellOrigin(cell.notes);
|
|
3418
|
-
const end = cellEnd(cell.notes);
|
|
3419
|
-
return end - start;
|
|
3420
|
-
}
|
|
3421
|
-
function clone(cell) {
|
|
3422
|
-
return { notes: cell.notes.map((n) => ({ ...n })) };
|
|
3423
|
-
}
|
|
3424
|
-
function contourOffsets(contour, count) {
|
|
3425
|
-
const offsets = [];
|
|
3426
|
-
for (let i = 0; i < count; i += 1) {
|
|
3427
|
-
switch (contour) {
|
|
3428
|
-
case "ascending":
|
|
3429
|
-
offsets.push(i);
|
|
3430
|
-
break;
|
|
3431
|
-
case "descending":
|
|
3432
|
-
offsets.push(-i);
|
|
3433
|
-
break;
|
|
3434
|
-
case "arch":
|
|
3435
|
-
offsets.push(Math.min(i, count - 1 - i));
|
|
3436
|
-
break;
|
|
3437
|
-
case "wave":
|
|
3438
|
-
offsets.push([0, 1, 0, -1][i % 4] ?? 0);
|
|
3439
|
-
break;
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
3442
|
-
return offsets;
|
|
3443
|
-
}
|
|
3444
|
-
function generateMotif(opts) {
|
|
3445
|
-
const contour = opts.contour ?? "arch";
|
|
3446
|
-
const bars = assertPositiveInt(opts.bars, "motif bars");
|
|
3447
|
-
const totalBeats = bars * 4;
|
|
3448
|
-
const noteCount = Math.max(3, bars * 2);
|
|
3449
|
-
assertGenerationBudget(noteCount, "motif notes");
|
|
3450
|
-
const beatsPerNote = totalBeats / noteCount;
|
|
3451
|
-
const rng = createRng(opts.seed ?? 0);
|
|
3452
|
-
const requestedJitter = assertFiniteNumber(opts.jitter ?? 0, "motif jitter");
|
|
3453
|
-
const jitterProb = Math.min(1, Math.max(0, requestedJitter));
|
|
3454
|
-
const tonic = pitchClass6(opts.key.rootPc) + 60;
|
|
3455
|
-
const offsets = contourOffsets(contour, noteCount);
|
|
3456
|
-
const notes = [];
|
|
3457
|
-
for (let i = 0; i < noteCount; i += 1) {
|
|
3458
|
-
let jitter = 0;
|
|
3459
|
-
if (jitterProb > 0 && rng.next() < jitterProb) {
|
|
3460
|
-
jitter = rng.next() < 0.5 ? 1 : -1;
|
|
3461
|
-
}
|
|
3462
|
-
let pitch = stepDiatonic(tonic, (offsets[i] ?? 0) + jitter, opts.key);
|
|
3463
|
-
const startBeat = i * beatsPerNote;
|
|
3464
|
-
if (opts.chord && startBeat % 4 === 0) {
|
|
3465
|
-
pitch = nearestChordTone(pitch, opts.chord);
|
|
3466
|
-
}
|
|
3467
|
-
notes.push({ pitch, startBeat, durationBeat: beatsPerNote });
|
|
3468
|
-
}
|
|
3469
|
-
return { notes };
|
|
3470
|
-
}
|
|
3471
|
-
function transformMotif(cell, t, amount, key) {
|
|
3472
|
-
assertNoteEvents(cell.notes, "motif notes");
|
|
3473
|
-
if (amount !== void 0) {
|
|
3474
|
-
assertFiniteNumber(amount, "motif transform amount");
|
|
3475
|
-
}
|
|
3476
|
-
if ((t === "transposeDiatonic" || t === "sequence") && amount !== void 0) {
|
|
3477
|
-
assertInteger(amount, "diatonic transform amount");
|
|
3478
|
-
}
|
|
3479
|
-
if ((t === "augment" || t === "diminish") && amount !== void 0 && amount <= 0) {
|
|
3480
|
-
throw new RangeError("time transform amount must be positive");
|
|
3481
|
-
}
|
|
3482
|
-
const notes = cell.notes;
|
|
3483
|
-
switch (t) {
|
|
3484
|
-
case "transposeChromatic": {
|
|
3485
|
-
const semis = amount ?? 0;
|
|
3486
|
-
return { notes: notes.map((n) => ({ ...n, pitch: n.pitch + semis })) };
|
|
3487
|
-
}
|
|
3488
|
-
case "transposeDiatonic": {
|
|
3489
|
-
const degrees = amount ?? 1;
|
|
3490
|
-
return {
|
|
3491
|
-
notes: notes.map((n) => ({
|
|
3492
|
-
...n,
|
|
3493
|
-
// With a key, shift by scale degrees; without one, fall back to a
|
|
3494
|
-
// chromatic shift of `degrees` semitones.
|
|
3495
|
-
pitch: key ? stepDiatonic(n.pitch, degrees, key) : n.pitch + degrees
|
|
3496
|
-
}))
|
|
3497
|
-
};
|
|
3498
|
-
}
|
|
3499
|
-
case "invert": {
|
|
3500
|
-
const pivot = notes[0]?.pitch ?? 0;
|
|
3501
|
-
return { notes: notes.map((n) => ({ ...n, pitch: 2 * pivot - n.pitch })) };
|
|
3502
|
-
}
|
|
3503
|
-
case "retrograde": {
|
|
3504
|
-
if (notes.length === 0) {
|
|
3505
|
-
return { notes: [] };
|
|
3506
|
-
}
|
|
3507
|
-
const start = cellOrigin(notes);
|
|
3508
|
-
const end = cellEnd(notes);
|
|
3509
|
-
return {
|
|
3510
|
-
notes: notes.map((n) => ({
|
|
3511
|
-
pitch: n.pitch,
|
|
3512
|
-
startBeat: start + (end - (n.startBeat + n.durationBeat)),
|
|
3513
|
-
durationBeat: n.durationBeat
|
|
3514
|
-
}))
|
|
3515
|
-
};
|
|
3516
|
-
}
|
|
3517
|
-
case "augment":
|
|
3518
|
-
return scaleTime(cell, amount ?? 2);
|
|
3519
|
-
case "diminish":
|
|
3520
|
-
return scaleTime(cell, 1 / (amount ?? 2));
|
|
3521
|
-
case "sequence": {
|
|
3522
|
-
const degrees = amount ?? 2;
|
|
3523
|
-
const span = cellSpan(cell);
|
|
3524
|
-
const copy = transformMotif(cell, "transposeDiatonic", degrees, key);
|
|
3525
|
-
const shifted = copy.notes.map((n) => ({ ...n, startBeat: n.startBeat + span }));
|
|
3526
|
-
return { notes: [...clone(cell).notes, ...shifted] };
|
|
3527
|
-
}
|
|
3528
|
-
}
|
|
3529
|
-
}
|
|
3530
|
-
function scaleTime(cell, factor) {
|
|
3531
|
-
const origin = cellOrigin(cell.notes);
|
|
3532
|
-
return {
|
|
3533
|
-
notes: cell.notes.map((n) => ({
|
|
3534
|
-
pitch: n.pitch,
|
|
3535
|
-
startBeat: origin + (n.startBeat - origin) * factor,
|
|
3536
|
-
durationBeat: n.durationBeat * factor
|
|
3537
|
-
}))
|
|
3538
|
-
};
|
|
3539
|
-
}
|
|
3540
|
-
function developMotif(cell, timeline, key, bars) {
|
|
3541
|
-
assertPositiveInt(bars, "development bars");
|
|
3542
|
-
assertNoteEvents(cell.notes, "motif notes");
|
|
3543
|
-
const span = cellSpan(cell);
|
|
3544
|
-
const totalBeats = bars * 4;
|
|
3545
|
-
const origin = cellOrigin(cell.notes);
|
|
3546
|
-
const out = [];
|
|
3547
|
-
if (span <= 0) {
|
|
3548
|
-
return clone(cell);
|
|
3549
|
-
}
|
|
3550
|
-
const tileSpan = Math.max(span, MIN_TILE_SPAN);
|
|
3551
|
-
const tileCount = Math.ceil(totalBeats / tileSpan);
|
|
3552
|
-
assertGenerationBudget(tileCount * cell.notes.length, "developed motif notes");
|
|
3553
|
-
for (let k = 0; k < tileCount; k += 1) {
|
|
3554
|
-
const offset = k * tileSpan;
|
|
3555
|
-
for (const n of cell.notes) {
|
|
3556
|
-
const startBeat = offset + (n.startBeat - origin);
|
|
3557
|
-
if (startBeat >= totalBeats) {
|
|
3558
|
-
continue;
|
|
3559
|
-
}
|
|
3560
|
-
const chord = timeline.at(startBeat);
|
|
3561
|
-
let pitch = n.pitch;
|
|
3562
|
-
if (chord) {
|
|
3563
|
-
const isMember = chordPitchClasses(chord).includes(pitchClass6(pitch));
|
|
3564
|
-
pitch = isMember ? pitch : nearestChordTone(pitch, chord);
|
|
3565
|
-
} else {
|
|
3566
|
-
pitch = nearestScaleTone(pitch, key);
|
|
3567
|
-
}
|
|
3568
|
-
out.push({ pitch, startBeat, durationBeat: n.durationBeat });
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
|
-
return { notes: out };
|
|
3572
|
-
}
|
|
3573
44
|
|
|
3574
|
-
// src/generate/progression/index.ts
|
|
3575
|
-
var PRESETS = [
|
|
3576
|
-
{
|
|
3577
|
-
id: "fourChordPop",
|
|
3578
|
-
name: "Four Chord Pop",
|
|
3579
|
-
degrees: [0, 4, 5, 3],
|
|
3580
|
-
functional: "loop",
|
|
3581
|
-
styles: ["minimal", "dance", "idol"]
|
|
3582
|
-
},
|
|
3583
|
-
{
|
|
3584
|
-
id: "pop1",
|
|
3585
|
-
name: "Pop 1",
|
|
3586
|
-
degrees: [0, 5, 3, 4],
|
|
3587
|
-
functional: "loop",
|
|
3588
|
-
styles: ["minimal", "dance", "idol"]
|
|
3589
|
-
},
|
|
3590
|
-
{
|
|
3591
|
-
id: "axis",
|
|
3592
|
-
name: "Axis",
|
|
3593
|
-
degrees: [5, 3, 0, 4],
|
|
3594
|
-
functional: "loop",
|
|
3595
|
-
styles: ["minimal", "dance", "idol", "rock"]
|
|
3596
|
-
},
|
|
3597
|
-
{
|
|
3598
|
-
id: "pop2",
|
|
3599
|
-
name: "Pop 2",
|
|
3600
|
-
degrees: [3, 0, 4, 5],
|
|
3601
|
-
functional: "loop",
|
|
3602
|
-
styles: ["minimal", "dance", "idol"]
|
|
3603
|
-
},
|
|
3604
|
-
{
|
|
3605
|
-
id: "classic",
|
|
3606
|
-
name: "Classic",
|
|
3607
|
-
degrees: [0, 3, 4, 0],
|
|
3608
|
-
functional: "cadenceStrong",
|
|
3609
|
-
styles: ["dance", "idol", "rock"]
|
|
3610
|
-
},
|
|
3611
|
-
{
|
|
3612
|
-
id: "pop3",
|
|
3613
|
-
name: "Pop 3",
|
|
3614
|
-
degrees: [0, 3, 5, 4],
|
|
3615
|
-
functional: "loop",
|
|
3616
|
-
styles: ["minimal", "dance", "idol"]
|
|
3617
|
-
},
|
|
3618
|
-
{
|
|
3619
|
-
id: "royalRoad",
|
|
3620
|
-
name: "Royal Road",
|
|
3621
|
-
degrees: [3, 4, 2, 5],
|
|
3622
|
-
functional: "tensionBuild",
|
|
3623
|
-
styles: ["dance", "idol"]
|
|
3624
|
-
},
|
|
3625
|
-
{
|
|
3626
|
-
id: "minor1",
|
|
3627
|
-
name: "Minor 1",
|
|
3628
|
-
degrees: [5, 4, 3, 4],
|
|
3629
|
-
functional: "tensionBuild",
|
|
3630
|
-
styles: ["idol", "rock"]
|
|
3631
|
-
},
|
|
3632
|
-
{
|
|
3633
|
-
id: "minor2",
|
|
3634
|
-
name: "Minor 2",
|
|
3635
|
-
degrees: [5, 3, 4, 0],
|
|
3636
|
-
functional: "tensionBuild",
|
|
3637
|
-
styles: ["idol", "rock"]
|
|
3638
|
-
},
|
|
3639
|
-
{
|
|
3640
|
-
id: "pop4",
|
|
3641
|
-
name: "Pop 4",
|
|
3642
|
-
degrees: [0, 4, 2, 3],
|
|
3643
|
-
functional: "loop",
|
|
3644
|
-
styles: ["minimal", "dance", "idol"]
|
|
3645
|
-
},
|
|
3646
|
-
{
|
|
3647
|
-
id: "pop5",
|
|
3648
|
-
name: "Pop 5",
|
|
3649
|
-
degrees: [0, 2, 3, 4],
|
|
3650
|
-
functional: "stable",
|
|
3651
|
-
styles: ["minimal", "dance", "idol"]
|
|
3652
|
-
},
|
|
3653
|
-
{
|
|
3654
|
-
id: "rock1",
|
|
3655
|
-
name: "Rock 1",
|
|
3656
|
-
degrees: [0, 10, 3, 0],
|
|
3657
|
-
functional: "tensionBuild",
|
|
3658
|
-
styles: ["rock"]
|
|
3659
|
-
},
|
|
3660
|
-
{
|
|
3661
|
-
id: "rock2",
|
|
3662
|
-
name: "Rock 2",
|
|
3663
|
-
degrees: [0, 3, 10, 0],
|
|
3664
|
-
functional: "tensionBuild",
|
|
3665
|
-
styles: ["rock"]
|
|
3666
|
-
},
|
|
3667
|
-
{
|
|
3668
|
-
id: "extended4",
|
|
3669
|
-
name: "Extended 4",
|
|
3670
|
-
degrees: [0, 4, 5, 2],
|
|
3671
|
-
functional: "stable",
|
|
3672
|
-
styles: ["minimal", "dance"]
|
|
3673
|
-
},
|
|
3674
|
-
{
|
|
3675
|
-
id: "minor3",
|
|
3676
|
-
name: "Minor 3",
|
|
3677
|
-
degrees: [5, 0, 4, 3],
|
|
3678
|
-
functional: "loop",
|
|
3679
|
-
styles: ["dance", "idol"]
|
|
3680
|
-
},
|
|
3681
|
-
{
|
|
3682
|
-
id: "aeolianPop",
|
|
3683
|
-
name: "Aeolian Pop",
|
|
3684
|
-
degrees: [5, 8, 10, 0],
|
|
3685
|
-
functional: "tensionBuild",
|
|
3686
|
-
styles: ["minimal", "dance", "idol", "rock"]
|
|
3687
|
-
},
|
|
3688
|
-
{
|
|
3689
|
-
id: "animeHighEnergy1",
|
|
3690
|
-
name: "Anime High Energy 1",
|
|
3691
|
-
degrees: [5, 2, 3, 0],
|
|
3692
|
-
functional: "loop",
|
|
3693
|
-
styles: ["dance", "idol"]
|
|
3694
|
-
},
|
|
3695
|
-
{
|
|
3696
|
-
id: "jazzPop",
|
|
3697
|
-
name: "Jazz Pop",
|
|
3698
|
-
degrees: [1, 4, 0, 5],
|
|
3699
|
-
functional: "cadenceStrong",
|
|
3700
|
-
styles: ["minimal", "dance"]
|
|
3701
|
-
},
|
|
3702
|
-
{
|
|
3703
|
-
id: "animeHighEnergy2",
|
|
3704
|
-
name: "Anime High Energy 2",
|
|
3705
|
-
degrees: [5, 1, 4, 0],
|
|
3706
|
-
functional: "cadenceStrong",
|
|
3707
|
-
styles: ["dance", "idol"]
|
|
3708
|
-
},
|
|
3709
|
-
{
|
|
3710
|
-
id: "cityPop",
|
|
3711
|
-
name: "City Pop",
|
|
3712
|
-
degrees: [0, 5, 1, 4],
|
|
3713
|
-
functional: "stable",
|
|
3714
|
-
styles: ["minimal", "dance"]
|
|
3715
|
-
},
|
|
3716
|
-
{
|
|
3717
|
-
id: "extended5",
|
|
3718
|
-
name: "Extended 5",
|
|
3719
|
-
degrees: [0, 4, 5, 2, 3],
|
|
3720
|
-
functional: "loop",
|
|
3721
|
-
styles: ["minimal", "dance", "idol"]
|
|
3722
|
-
},
|
|
3723
|
-
{
|
|
3724
|
-
id: "neapolitanPop",
|
|
3725
|
-
name: "Neapolitan Pop",
|
|
3726
|
-
degrees: [5, 12, 13, 4, 0],
|
|
3727
|
-
functional: "cadenceStrong",
|
|
3728
|
-
styles: ["minimal", "dance", "idol"]
|
|
3729
|
-
}
|
|
3730
|
-
];
|
|
3731
|
-
var BORROWED_OFFSET = {
|
|
3732
|
-
8: 8,
|
|
3733
|
-
// bVI
|
|
3734
|
-
10: 10,
|
|
3735
|
-
// bVII
|
|
3736
|
-
11: 3,
|
|
3737
|
-
// bIII
|
|
3738
|
-
12: 5,
|
|
3739
|
-
// iv
|
|
3740
|
-
13: 1,
|
|
3741
|
-
// bII
|
|
3742
|
-
14: 6
|
|
3743
|
-
// #IV
|
|
3744
|
-
};
|
|
3745
|
-
function progressions() {
|
|
3746
|
-
return PRESETS.map((p) => ({ ...p, degrees: [...p.degrees], styles: [...p.styles] }));
|
|
3747
|
-
}
|
|
3748
|
-
function progressionsByStyle(style) {
|
|
3749
|
-
return progressions().filter((p) => p.styles.includes(style));
|
|
3750
|
-
}
|
|
3751
|
-
function degreeToRootPc(degree, key) {
|
|
3752
|
-
if (degree >= 0 && degree <= 6) {
|
|
3753
|
-
const tones = scaleTonesInDegreeOrder(key);
|
|
3754
|
-
return tones.length > 0 ? tones[degree % tones.length] ?? key.rootPc % 12 : key.rootPc % 12;
|
|
3755
|
-
}
|
|
3756
|
-
const offset = BORROWED_OFFSET[degree] ?? 0;
|
|
3757
|
-
return ((key.rootPc % 12 + offset) % 12 + 12) % 12;
|
|
3758
|
-
}
|
|
3759
|
-
function autoQuality(degree, key) {
|
|
3760
|
-
if (degree >= 0 && degree <= 6) {
|
|
3761
|
-
return diatonicTriad(degree, key).quality;
|
|
3762
|
-
}
|
|
3763
|
-
if (degree === 14) {
|
|
3764
|
-
return "dim";
|
|
3765
|
-
}
|
|
3766
|
-
if (degree === 12) {
|
|
3767
|
-
return "min";
|
|
3768
|
-
}
|
|
3769
|
-
return "maj";
|
|
3770
|
-
}
|
|
3771
|
-
function generateProgression(opts) {
|
|
3772
|
-
assertPositiveInt(opts.bars, "progression bars");
|
|
3773
|
-
assertGenerationBudget(opts.bars, "progression chords");
|
|
3774
|
-
const seed = opts.seed ?? 0;
|
|
3775
|
-
let preset;
|
|
3776
|
-
if (opts.presetId !== void 0) {
|
|
3777
|
-
preset = PRESETS.find((p) => p.id === opts.presetId);
|
|
3778
|
-
if (preset === void 0) {
|
|
3779
|
-
throw new Error(`Unknown progression preset: ${opts.presetId}`);
|
|
3780
|
-
}
|
|
3781
|
-
}
|
|
3782
|
-
if (preset === void 0) {
|
|
3783
|
-
const pool = PRESETS.filter((p) => p.styles.includes(opts.style));
|
|
3784
|
-
const candidates = pool.length > 0 ? pool : PRESETS;
|
|
3785
|
-
const rng = createRng(seed);
|
|
3786
|
-
const index = Math.floor(rng.next() * candidates.length) % candidates.length;
|
|
3787
|
-
preset = candidates[index] ?? PRESETS[0];
|
|
3788
|
-
}
|
|
3789
|
-
const degrees = preset?.degrees ?? [0];
|
|
3790
|
-
const chords = [];
|
|
3791
|
-
for (let bar = 0; bar < opts.bars; bar += 1) {
|
|
3792
|
-
const degree = degrees[bar % degrees.length] ?? 0;
|
|
3793
|
-
const quality = opts.ext !== void 0 && opts.ext !== "auto" ? opts.ext : autoQuality(degree, opts.key);
|
|
3794
|
-
const chord = {
|
|
3795
|
-
rootPc: degreeToRootPc(degree, opts.key),
|
|
3796
|
-
quality,
|
|
3797
|
-
startBeat: bar * 4,
|
|
3798
|
-
degree: degree >= 0 && degree <= 6 ? degree : void 0
|
|
3799
|
-
};
|
|
3800
|
-
chords.push(chord);
|
|
3801
|
-
}
|
|
3802
|
-
if (opts.reharmonize) {
|
|
3803
|
-
const tonicPc = (opts.key.rootPc % 12 + 12) % 12;
|
|
3804
|
-
const rrng = createRng((seed ^ 2654435769) >>> 0);
|
|
3805
|
-
for (let i = 0; i < chords.length - 1; i += 1) {
|
|
3806
|
-
const cur = chords[i];
|
|
3807
|
-
const next = chords[i + 1];
|
|
3808
|
-
if (!cur || !next) {
|
|
3809
|
-
continue;
|
|
3810
|
-
}
|
|
3811
|
-
const domRoot = (next.rootPc + 7) % 12;
|
|
3812
|
-
const targetsTonic = (next.rootPc - tonicPc + 12) % 12 === 0;
|
|
3813
|
-
const alreadySecondary = cur.rootPc === domRoot && cur.quality === "dom7";
|
|
3814
|
-
const isResolutionTarget = chords[i - 1]?.secondaryDominant === true;
|
|
3815
|
-
if (targetsTonic || alreadySecondary || isResolutionTarget) {
|
|
3816
|
-
continue;
|
|
3817
|
-
}
|
|
3818
|
-
if (rrng.next() < 0.5) {
|
|
3819
|
-
chords[i] = {
|
|
3820
|
-
rootPc: domRoot,
|
|
3821
|
-
quality: "dom7",
|
|
3822
|
-
startBeat: cur.startBeat,
|
|
3823
|
-
secondaryDominant: true
|
|
3824
|
-
};
|
|
3825
|
-
}
|
|
3826
|
-
}
|
|
3827
|
-
}
|
|
3828
|
-
return chords;
|
|
3829
|
-
}
|
|
3830
45
|
|
|
3831
|
-
// src/analyze/detect/index.ts
|
|
3832
|
-
function pitchClass7(value) {
|
|
3833
|
-
return (Math.trunc(value) % 12 + 12) % 12;
|
|
3834
|
-
}
|
|
3835
|
-
function uniquePitchClasses(pitches) {
|
|
3836
|
-
return [...new Set(pitches.map(pitchClass7))].sort((a, b) => a - b);
|
|
3837
|
-
}
|
|
3838
|
-
function assertPitches(pitches) {
|
|
3839
|
-
assertGenerationBudget(pitches.length, "detection pitches");
|
|
3840
|
-
for (let index = 0; index < pitches.length; index += 1) {
|
|
3841
|
-
assertFiniteNumber(pitches[index] ?? Number.NaN, `pitches[${index}]`);
|
|
3842
|
-
}
|
|
3843
|
-
}
|
|
3844
|
-
function detectChord(pitches, opts = {}) {
|
|
3845
|
-
assertPitches(pitches);
|
|
3846
|
-
if (opts.bassPc !== void 0) assertFiniteNumber(opts.bassPc, "bassPc");
|
|
3847
|
-
const input = uniquePitchClasses(pitches);
|
|
3848
|
-
if (input.length === 0) {
|
|
3849
|
-
return [];
|
|
3850
|
-
}
|
|
3851
|
-
const inputKind = opts.input === void 0 || opts.input === "auto" ? pitches.every((pitch) => Number.isInteger(pitch) && pitch >= 0 && pitch <= 11) ? "pitchClass" : "midi" : opts.input;
|
|
3852
|
-
const bassPc = opts.bassPc !== void 0 ? pitchClass7(opts.bassPc) : inputKind === "midi" ? pitchClass7(pitches.reduce((lowest, pitch) => Math.min(lowest, pitch), Infinity)) : void 0;
|
|
3853
|
-
const inputSet = new Set(input);
|
|
3854
|
-
const matches = [];
|
|
3855
|
-
const qualities = chordQualities();
|
|
3856
|
-
for (const rootPc of input) {
|
|
3857
|
-
for (const quality of qualities) {
|
|
3858
|
-
const chord = makeChord(rootPc, quality);
|
|
3859
|
-
const tones = chordPitchClasses(chord);
|
|
3860
|
-
const toneSet = new Set(tones);
|
|
3861
|
-
const missingPcs = tones.filter((pc) => !inputSet.has(pc));
|
|
3862
|
-
const fifthPc = pitchClass7(rootPc + 7);
|
|
3863
|
-
if (missingPcs.length > 1 || missingPcs.length === 1 && missingPcs[0] !== fifthPc) {
|
|
3864
|
-
continue;
|
|
3865
|
-
}
|
|
3866
|
-
const extraPcs = input.filter((pc) => !toneSet.has(pc));
|
|
3867
|
-
const bassIndex = bassPc === void 0 ? -1 : chord.intervals.findIndex((iv) => pitchClass7(rootPc + iv) === bassPc);
|
|
3868
|
-
const inversion = bassPc === void 0 ? null : bassIndex > 0 ? bassIndex : 0;
|
|
3869
|
-
const match = {
|
|
3870
|
-
rootPc,
|
|
3871
|
-
quality,
|
|
3872
|
-
missingPcs,
|
|
3873
|
-
extraPcs,
|
|
3874
|
-
exact: extraPcs.length === 0 && missingPcs.length === 0,
|
|
3875
|
-
inversion
|
|
3876
|
-
};
|
|
3877
|
-
if (bassPc !== void 0 && bassPc !== rootPc) {
|
|
3878
|
-
match.bassPc = bassPc;
|
|
3879
|
-
}
|
|
3880
|
-
matches.push(match);
|
|
3881
|
-
}
|
|
3882
|
-
}
|
|
3883
|
-
matches.sort((a, b) => {
|
|
3884
|
-
if (a.extraPcs.length !== b.extraPcs.length) {
|
|
3885
|
-
return a.extraPcs.length - b.extraPcs.length;
|
|
3886
|
-
}
|
|
3887
|
-
if (a.missingPcs.length !== b.missingPcs.length) {
|
|
3888
|
-
return a.missingPcs.length - b.missingPcs.length;
|
|
3889
|
-
}
|
|
3890
|
-
if (a.inversion === 0 !== (b.inversion === 0)) {
|
|
3891
|
-
return a.inversion === 0 ? -1 : 1;
|
|
3892
|
-
}
|
|
3893
|
-
const aSize = chordPitchClasses(makeChord(a.rootPc, a.quality)).length;
|
|
3894
|
-
const bSize = chordPitchClasses(makeChord(b.rootPc, b.quality)).length;
|
|
3895
|
-
return bSize - aSize;
|
|
3896
|
-
});
|
|
3897
|
-
return matches;
|
|
3898
|
-
}
|
|
3899
|
-
function detectChordBest(pitches, opts = {}) {
|
|
3900
|
-
const best = detectChord(pitches, opts)[0];
|
|
3901
|
-
if (!best) {
|
|
3902
|
-
return null;
|
|
3903
|
-
}
|
|
3904
|
-
return makeChord(best.rootPc, best.quality, best.bassPc);
|
|
3905
|
-
}
|
|
3906
46
|
|
|
3907
|
-
// src/generate/reharmony/index.ts
|
|
3908
|
-
function mod122(n) {
|
|
3909
|
-
return (n % 12 + 12) % 12;
|
|
3910
|
-
}
|
|
3911
|
-
function hasInterval(chord, semitone) {
|
|
3912
|
-
return chord.intervals.some((iv) => mod122(iv) === semitone);
|
|
3913
|
-
}
|
|
3914
|
-
function isDominantType(chord) {
|
|
3915
|
-
return hasInterval(chord, 4) && hasInterval(chord, 10);
|
|
3916
|
-
}
|
|
3917
|
-
function isStableTarget(chord) {
|
|
3918
|
-
const hasFifth = hasInterval(chord, 7);
|
|
3919
|
-
const hasThird = hasInterval(chord, 3) || hasInterval(chord, 4);
|
|
3920
|
-
return hasFifth && hasThird && !isDominantType(chord);
|
|
3921
|
-
}
|
|
3922
|
-
function commonToneCount(a, b) {
|
|
3923
|
-
const set = new Set(b);
|
|
3924
|
-
return a.filter((pc) => set.has(pc)).length;
|
|
3925
|
-
}
|
|
3926
|
-
function diatonicTriadsOf(key) {
|
|
3927
|
-
const degrees = scaleTonesInDegreeOrder(key).length;
|
|
3928
|
-
const triads = [];
|
|
3929
|
-
for (let degree = 0; degree < degrees; degree += 1) {
|
|
3930
|
-
triads.push(diatonicTriad(degree, key));
|
|
3931
|
-
}
|
|
3932
|
-
return triads;
|
|
3933
|
-
}
|
|
3934
|
-
function sameChord(a, b) {
|
|
3935
|
-
return mod122(a.rootPc) === mod122(b.rootPc) && a.quality === b.quality;
|
|
3936
|
-
}
|
|
3937
|
-
var THIRD_OFFSETS = [3, 4, 8, 9];
|
|
3938
|
-
function substituteChord(chord, key, opts) {
|
|
3939
|
-
const original = chordPitchClasses(chord);
|
|
3940
|
-
const candidates = [];
|
|
3941
|
-
if (isDominantType(chord)) {
|
|
3942
|
-
candidates.push({ chord: makeChord(mod122(chord.rootPc + 6), "dom7"), type: "tritone" });
|
|
3943
|
-
}
|
|
3944
|
-
if (isStableTarget(chord)) {
|
|
3945
|
-
candidates.push({
|
|
3946
|
-
chord: makeChord(mod122(chord.rootPc + 7), "dom7"),
|
|
3947
|
-
type: "secondary-dominant"
|
|
3948
|
-
});
|
|
3949
|
-
}
|
|
3950
|
-
for (const triad of diatonicTriadsOf(key)) {
|
|
3951
|
-
const offset = mod122(triad.rootPc - chord.rootPc);
|
|
3952
|
-
if (THIRD_OFFSETS.includes(offset) && commonToneCount(original, chordPitchClasses(triad)) === 2) {
|
|
3953
|
-
candidates.push({ chord: triad, type: "relative" });
|
|
3954
|
-
}
|
|
3955
|
-
}
|
|
3956
|
-
const targetFunction = functionOf(chord, key);
|
|
3957
|
-
for (const triad of diatonicTriadsOf(parallelKey(key))) {
|
|
3958
|
-
if (functionOf(triad, key) === targetFunction && !isDiatonic(triad, key)) {
|
|
3959
|
-
candidates.push({ chord: triad, type: "borrowed" });
|
|
3960
|
-
}
|
|
3961
|
-
}
|
|
3962
|
-
for (const offset of THIRD_OFFSETS) {
|
|
3963
|
-
for (const quality of ["maj", "min"]) {
|
|
3964
|
-
const mediant = makeChord(mod122(chord.rootPc + offset), quality);
|
|
3965
|
-
if (commonToneCount(original, chordPitchClasses(mediant)) === 1 && !isDiatonic(mediant, key)) {
|
|
3966
|
-
candidates.push({ chord: mediant, type: "chromatic-mediant" });
|
|
3967
|
-
}
|
|
3968
|
-
}
|
|
3969
|
-
}
|
|
3970
|
-
const melodyPcs = opts?.melodyPcs?.map(mod122);
|
|
3971
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3972
|
-
const results = [];
|
|
3973
|
-
for (const candidate of candidates) {
|
|
3974
|
-
if (sameChord(candidate.chord, chord)) {
|
|
3975
|
-
continue;
|
|
3976
|
-
}
|
|
3977
|
-
const dedupeKey = `${mod122(candidate.chord.rootPc)}:${candidate.chord.quality}`;
|
|
3978
|
-
if (seen.has(dedupeKey)) {
|
|
3979
|
-
continue;
|
|
3980
|
-
}
|
|
3981
|
-
seen.add(dedupeKey);
|
|
3982
|
-
if (melodyPcs !== void 0) {
|
|
3983
|
-
const pcs = new Set(chordPitchClasses(candidate.chord));
|
|
3984
|
-
if (!melodyPcs.every((pc) => pcs.has(pc))) {
|
|
3985
|
-
continue;
|
|
3986
|
-
}
|
|
3987
|
-
}
|
|
3988
|
-
results.push({
|
|
3989
|
-
chord: candidate.chord,
|
|
3990
|
-
type: candidate.type,
|
|
3991
|
-
roman: chordToRoman(candidate.chord, key),
|
|
3992
|
-
function: functionOf(candidate.chord, key)
|
|
3993
|
-
});
|
|
3994
|
-
}
|
|
3995
|
-
return results;
|
|
3996
|
-
}
|
|
3997
|
-
function modalInterchangePalette(key) {
|
|
3998
|
-
const palette = [];
|
|
3999
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4000
|
-
const add = (chord) => {
|
|
4001
|
-
const dedupeKey = `${mod122(chord.rootPc)}:${chord.quality}`;
|
|
4002
|
-
if (seen.has(dedupeKey)) {
|
|
4003
|
-
return;
|
|
4004
|
-
}
|
|
4005
|
-
seen.add(dedupeKey);
|
|
4006
|
-
palette.push({
|
|
4007
|
-
chord,
|
|
4008
|
-
roman: chordToRoman(chord, key),
|
|
4009
|
-
source: borrowedSource(chord, key)
|
|
4010
|
-
});
|
|
4011
|
-
};
|
|
4012
|
-
for (const triad of diatonicTriadsOf(parallelKey(key))) {
|
|
4013
|
-
if (!isDiatonic(triad, key)) {
|
|
4014
|
-
add(triad);
|
|
4015
|
-
}
|
|
4016
|
-
}
|
|
4017
|
-
const neapolitan = makeChord(mod122(key.rootPc + 1), "maj");
|
|
4018
|
-
if (!isDiatonic(neapolitan, key)) {
|
|
4019
|
-
add(neapolitan);
|
|
4020
|
-
}
|
|
4021
|
-
return palette;
|
|
4022
|
-
}
|
|
4023
|
-
function negativeHarmonyMirror(chord, key) {
|
|
4024
|
-
const tonic = mod122(key.rootPc);
|
|
4025
|
-
const mirror = (p) => mod122(2 * tonic + 7 - p);
|
|
4026
|
-
const pcs = chordPitchClasses(chord).map(mirror);
|
|
4027
|
-
const bassPc = chord.bassPc !== void 0 ? mirror(chord.bassPc) : void 0;
|
|
4028
|
-
const pitches = bassPc !== void 0 ? [bassPc - 12, ...pcs] : pcs;
|
|
4029
|
-
const detected = detectChordBest(pitches);
|
|
4030
|
-
if (detected) {
|
|
4031
|
-
if (bassPc === void 0 && detected.bassPc !== void 0) {
|
|
4032
|
-
return makeChord(detected.rootPc, detected.quality);
|
|
4033
|
-
}
|
|
4034
|
-
return detected;
|
|
4035
|
-
}
|
|
4036
|
-
const quality = chord.quality;
|
|
4037
|
-
return makeChord(mirror(chord.rootPc), quality, bassPc);
|
|
4038
|
-
}
|
|
4039
47
|
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
const bars = opts.bars ?? DEFAULT_BARS;
|
|
4059
|
-
const subdivision = opts.subdivision ?? DEFAULT_SUBDIVISION;
|
|
4060
|
-
const requestedDensity = assertFiniteNumber(opts.density ?? DEFAULT_DENSITY, "rhythm density");
|
|
4061
|
-
const density = Math.min(1, Math.max(0, requestedDensity));
|
|
4062
|
-
assertPositiveInt(bars, "rhythm bars");
|
|
4063
|
-
assertPositiveInt(subdivision, "rhythm subdivision");
|
|
4064
|
-
const barBeats = beatsPerBar(ts);
|
|
4065
|
-
const spanBeats = barBeats * bars;
|
|
4066
|
-
const step = 1 / subdivision;
|
|
4067
|
-
const slotCount = Math.round(spanBeats * subdivision);
|
|
4068
|
-
assertGenerationBudget(slotCount, "rhythm grid slots");
|
|
4069
|
-
const slotsPerBar = Math.round(barBeats * subdivision);
|
|
4070
|
-
const rng = createRng(seed);
|
|
4071
|
-
const positions = [];
|
|
4072
|
-
for (let i = 0; i < slotCount; i += 1) {
|
|
4073
|
-
const position = i * step;
|
|
4074
|
-
if (i % slotsPerBar === 0) {
|
|
4075
|
-
positions.push(position);
|
|
4076
|
-
continue;
|
|
4077
|
-
}
|
|
4078
|
-
const weight = metricWeight(position, ts);
|
|
4079
|
-
const probability = onsetWeightCurve(weight) * density;
|
|
4080
|
-
if (rng.prob(probability)) {
|
|
4081
|
-
positions.push(position);
|
|
4082
|
-
}
|
|
4083
|
-
}
|
|
4084
|
-
const events = [];
|
|
4085
|
-
for (let i = 0; i < positions.length; i += 1) {
|
|
4086
|
-
const position = positions[i] ?? 0;
|
|
4087
|
-
const nextPosition = positions[i + 1] ?? spanBeats;
|
|
4088
|
-
events.push({ position, duration: nextPosition - position });
|
|
4089
|
-
}
|
|
4090
|
-
return events;
|
|
4091
|
-
}
|
|
4092
|
-
function rhythmDensity(events, ts) {
|
|
4093
|
-
if (events.length === 0) {
|
|
4094
|
-
return 0;
|
|
4095
|
-
}
|
|
4096
|
-
const last = events[events.length - 1];
|
|
4097
|
-
const spanBeats = (last?.position ?? 0) + (last?.duration ?? 0);
|
|
4098
|
-
const bars = spanBeats / beatsPerBar(ts);
|
|
4099
|
-
return bars > 0 ? events.length / bars : 0;
|
|
4100
|
-
}
|
|
4101
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
4102
|
-
0 && (module.exports = {
|
|
4103
|
-
applyGrooveTemplate,
|
|
4104
|
-
developMotif,
|
|
4105
|
-
extractGrooveTemplate,
|
|
4106
|
-
generateBassLine,
|
|
4107
|
-
generateCounterMelody,
|
|
4108
|
-
generateDrums,
|
|
4109
|
-
generateMotif,
|
|
4110
|
-
generateProgression,
|
|
4111
|
-
generateRhythm,
|
|
4112
|
-
harmonizeMelody,
|
|
4113
|
-
humanize,
|
|
4114
|
-
modalInterchangePalette,
|
|
4115
|
-
negativeHarmonyMirror,
|
|
4116
|
-
onsetWeightCurve,
|
|
4117
|
-
progressions,
|
|
4118
|
-
progressionsByStyle,
|
|
4119
|
-
rhythmDensity,
|
|
4120
|
-
substituteChord,
|
|
4121
|
-
transformMotif
|
|
4122
|
-
});
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
exports.BASS_STYLES = _chunkMPTVJIVGcjs.BASS_STYLES; exports.BORROWED_DEGREES = _chunkMPTVJIVGcjs.BORROWED_DEGREES; exports.DRUM_NOTES = _chunkMPTVJIVGcjs.DRUM_NOTES; exports.applyGrooveTemplate = _chunkMPTVJIVGcjs.applyGrooveTemplate; exports.developMotif = _chunkMPTVJIVGcjs.developMotif; exports.drumVoiceOf = _chunkMPTVJIVGcjs.drumVoiceOf; exports.extractGrooveTemplate = _chunkMPTVJIVGcjs.extractGrooveTemplate; exports.generateBassLine = _chunkMPTVJIVGcjs.generateBassLine; exports.generateCounterMelody = _chunkMPTVJIVGcjs.generateCounterMelody; exports.generateDrums = _chunkMPTVJIVGcjs.generateDrums; exports.generateMotif = _chunkMPTVJIVGcjs.generateMotif; exports.generateProgression = _chunkMPTVJIVGcjs.generateProgression; exports.generateRhythm = _chunkMPTVJIVGcjs.generateRhythm; exports.harmonizeMelody = _chunkMPTVJIVGcjs.harmonizeMelody; exports.humanize = _chunkMPTVJIVGcjs.humanize; exports.modalInterchangePalette = _chunk357ZDPVUcjs.modalInterchangePalette; exports.motifToNoteEvents = _chunkMPTVJIVGcjs.motifToNoteEvents; exports.negativeHarmonyMirror = _chunk357ZDPVUcjs.negativeHarmonyMirror; exports.onsetWeightCurve = _chunkMPTVJIVGcjs.onsetWeightCurve; exports.pickProgressionPreset = _chunkMPTVJIVGcjs.pickProgressionPreset; exports.progressions = _chunkMPTVJIVGcjs.progressions; exports.progressionsByStyle = _chunkMPTVJIVGcjs.progressionsByStyle; exports.rhythmDensity = _chunkMPTVJIVGcjs.rhythmDensity; exports.rhythmToNoteEvents = _chunkMPTVJIVGcjs.rhythmToNoteEvents; exports.substituteChord = _chunk357ZDPVUcjs.substituteChord; exports.transformMotif = _chunkMPTVJIVGcjs.transformMotif;
|
|
4123
66
|
//# sourceMappingURL=index.cjs.map
|