@libraz/libcantus 0.9.4 → 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 +27 -18
- package/README_ja.md +268 -0
- package/dist/analyze/index.cjs +7 -7
- package/dist/analyze/index.d.cts +9 -99
- package/dist/analyze/index.d.ts +9 -99
- package/dist/analyze/index.js +6 -6
- package/dist/{chunk-36N73KZ3.cjs → chunk-2A2QMA74.cjs} +80 -65
- package/dist/chunk-2A2QMA74.cjs.map +1 -0
- package/dist/{chunk-YLEMGEFH.js → chunk-2PTFGVD7.js} +50 -35
- package/dist/chunk-2PTFGVD7.js.map +1 -0
- package/dist/{chunk-ZEJR4TKL.cjs → chunk-357ZDPVU.cjs} +36 -47
- package/dist/chunk-357ZDPVU.cjs.map +1 -0
- package/dist/{chunk-EHVEBWM5.js → chunk-3KX76RLG.js} +136 -37
- package/dist/chunk-3KX76RLG.js.map +1 -0
- package/dist/{chunk-RC6GBMCD.cjs → chunk-4VZ5MZAM.cjs} +155 -111
- package/dist/chunk-4VZ5MZAM.cjs.map +1 -0
- package/dist/{chunk-RK35RIN7.js → chunk-7EONYVI5.js} +115 -28
- package/dist/chunk-7EONYVI5.js.map +1 -0
- package/dist/{chunk-GQSGS2N4.js → chunk-7JEP3MFY.js} +5 -16
- package/dist/chunk-7JEP3MFY.js.map +1 -0
- package/dist/{chunk-7NKDPF74.js → chunk-AID75RN3.js} +203 -104
- package/dist/chunk-AID75RN3.js.map +1 -0
- package/dist/{chunk-FNDWXRRI.cjs → chunk-BSLTLGM3.cjs} +55 -37
- package/dist/chunk-BSLTLGM3.cjs.map +1 -0
- package/dist/{chunk-NP4NA2FB.cjs → chunk-CU6VG2YM.cjs} +39 -36
- package/dist/chunk-CU6VG2YM.cjs.map +1 -0
- package/dist/{chunk-NYYWMV2C.js → chunk-DZMHIGI4.js} +27 -17
- package/dist/chunk-DZMHIGI4.js.map +1 -0
- package/dist/{chunk-I24MV65U.js → chunk-EDOTEABK.js} +18 -15
- package/dist/{chunk-I24MV65U.js.map → chunk-EDOTEABK.js.map} +1 -1
- package/dist/{chunk-NKIBQ6PT.js → chunk-HN2OEUN2.js} +7 -4
- package/dist/chunk-HN2OEUN2.js.map +1 -0
- package/dist/{chunk-D2FAJBYL.js → chunk-KBTTEX7Q.js} +30 -12
- package/dist/chunk-KBTTEX7Q.js.map +1 -0
- package/dist/{chunk-CAN76NCU.cjs → chunk-KQN2ZYND.cjs} +147 -102
- package/dist/chunk-KQN2ZYND.cjs.map +1 -0
- package/dist/{chunk-4HLLZPXT.cjs → chunk-MPTVJIVG.cjs} +350 -251
- package/dist/chunk-MPTVJIVG.cjs.map +1 -0
- package/dist/{chunk-RUMU3UA6.cjs → chunk-MTMZTMWM.cjs} +12 -12
- package/dist/{chunk-RUMU3UA6.cjs.map → chunk-MTMZTMWM.cjs.map} +1 -1
- package/dist/{chunk-KMHOICIY.js → chunk-NNB67XA2.js} +90 -45
- package/dist/chunk-NNB67XA2.js.map +1 -0
- package/dist/{chunk-LW53AHQU.cjs → chunk-OL3IHHSJ.cjs} +228 -141
- package/dist/chunk-OL3IHHSJ.cjs.map +1 -0
- package/dist/{chunk-7MGZ7D62.cjs → chunk-PI3UXRWK.cjs} +43 -13
- package/dist/chunk-PI3UXRWK.cjs.map +1 -0
- package/dist/{chunk-XEBG3G53.js → chunk-R2VVFAIN.js} +42 -12
- package/dist/chunk-R2VVFAIN.js.map +1 -0
- package/dist/{chunk-Q6OP4U43.cjs → chunk-WFDXOLUB.cjs} +50 -40
- package/dist/chunk-WFDXOLUB.cjs.map +1 -0
- package/dist/{chunk-NVE34YGZ.cjs → chunk-WTAN4ACG.cjs} +212 -113
- package/dist/chunk-WTAN4ACG.cjs.map +1 -0
- package/dist/{chunk-FCANXBAT.cjs → chunk-YFJXS3AU.cjs} +8 -5
- package/dist/chunk-YFJXS3AU.cjs.map +1 -0
- package/dist/{chunk-Q2XUP227.js → chunk-YUE6TJ4M.js} +89 -45
- package/dist/chunk-YUE6TJ4M.js.map +1 -0
- package/dist/{chunk-HKGYCQ34.js → chunk-ZGLJM4A4.js} +2 -2
- package/dist/core/index.cjs +15 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +14 -4
- package/dist/core/index.d.ts +14 -4
- package/dist/core/index.js +14 -4
- package/dist/{function-DNugAOrt.d.cts → function-CxixTB02.d.cts} +98 -7
- package/dist/{function-BtiZb9i9.d.ts → function-DTCt675z.d.ts} +98 -7
- package/dist/generate/index.cjs +10 -10
- package/dist/generate/index.d.cts +21 -21
- package/dist/generate/index.d.ts +21 -21
- package/dist/generate/index.js +9 -9
- package/dist/{index-tJhkmQgE.d.cts → index-B2Ulxxpo.d.cts} +3 -2
- package/dist/{index-Be74mcon.d.cts → index-BkYJ0xMq.d.cts} +2 -2
- package/dist/{index-6VR8-kit.d.ts → index-BuaB-ypt.d.ts} +3 -2
- package/dist/{index-70p168VM.d.ts → index-CAN7v-kw.d.ts} +9 -2
- package/dist/{index-C83mZI1P.d.ts → index-COw1Ky9g.d.ts} +1 -1
- package/dist/{index-tSQh-xwY.d.ts → index-Cfx8xgH0.d.ts} +1 -1
- package/dist/{index-C5UBbQ4G.d.ts → index-DRtIhlJj.d.ts} +2 -2
- package/dist/{index-BtXT6UQ0.d.cts → index-alrRninU.d.cts} +9 -2
- package/dist/{index-y_ak9_Hi.d.cts → index-oTQbYYIs.d.cts} +1 -1
- package/dist/{index-OG65ocOr.d.ts → index-tR9jTn26.d.cts} +4 -1
- package/dist/{index-OG65ocOr.d.cts → index-tR9jTn26.d.ts} +4 -1
- package/dist/{index-D6b6na69.d.cts → index-x2vcsduE.d.cts} +1 -1
- package/dist/index.cjs +24 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +23 -13
- package/dist/model/index.cjs +7 -7
- package/dist/model/index.d.cts +48 -16
- package/dist/model/index.d.ts +48 -16
- package/dist/model/index.js +6 -6
- package/dist/{styled-DIRkk9Uz.d.ts → styled-Bl0ZK28h.d.ts} +40 -3
- package/dist/{styled-DJbkJQfj.d.cts → styled-Crv6SA4S.d.cts} +40 -3
- package/dist/theory/index.cjs +6 -6
- package/dist/theory/index.d.cts +9 -9
- package/dist/theory/index.d.ts +9 -9
- package/dist/theory/index.js +5 -5
- package/package.json +12 -9
- package/dist/chunk-36N73KZ3.cjs.map +0 -1
- package/dist/chunk-4HLLZPXT.cjs.map +0 -1
- package/dist/chunk-7MGZ7D62.cjs.map +0 -1
- package/dist/chunk-7NKDPF74.js.map +0 -1
- package/dist/chunk-CAN76NCU.cjs.map +0 -1
- package/dist/chunk-D2FAJBYL.js.map +0 -1
- package/dist/chunk-EHVEBWM5.js.map +0 -1
- package/dist/chunk-FCANXBAT.cjs.map +0 -1
- package/dist/chunk-FNDWXRRI.cjs.map +0 -1
- package/dist/chunk-GQSGS2N4.js.map +0 -1
- package/dist/chunk-KMHOICIY.js.map +0 -1
- package/dist/chunk-LW53AHQU.cjs.map +0 -1
- package/dist/chunk-NKIBQ6PT.js.map +0 -1
- package/dist/chunk-NP4NA2FB.cjs.map +0 -1
- package/dist/chunk-NVE34YGZ.cjs.map +0 -1
- package/dist/chunk-NYYWMV2C.js.map +0 -1
- package/dist/chunk-Q2XUP227.js.map +0 -1
- package/dist/chunk-Q6OP4U43.cjs.map +0 -1
- package/dist/chunk-RC6GBMCD.cjs.map +0 -1
- package/dist/chunk-RK35RIN7.js.map +0 -1
- package/dist/chunk-XEBG3G53.js.map +0 -1
- package/dist/chunk-YLEMGEFH.js.map +0 -1
- package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
- /package/dist/{chunk-HKGYCQ34.js.map → chunk-ZGLJM4A4.js.map} +0 -0
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createRng
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZGLJM4A4.js";
|
|
4
4
|
import {
|
|
5
5
|
beatsPerBar,
|
|
6
6
|
createNoteEventIndex,
|
|
7
7
|
formatTimeSignature,
|
|
8
8
|
isStrongBeat,
|
|
9
|
-
metricWeight
|
|
10
|
-
|
|
9
|
+
metricWeight,
|
|
10
|
+
pulseBeats
|
|
11
|
+
} from "./chunk-2PTFGVD7.js";
|
|
11
12
|
import {
|
|
12
13
|
isDiatonic,
|
|
13
14
|
parallelKey
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-NNB67XA2.js";
|
|
15
16
|
import {
|
|
16
17
|
roleOf
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-HN2OEUN2.js";
|
|
18
19
|
import {
|
|
19
20
|
enumerateSafePitches,
|
|
20
21
|
evaluateSafety
|
|
21
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-DZMHIGI4.js";
|
|
22
23
|
import {
|
|
23
24
|
NATURAL_MINOR_MASK,
|
|
24
25
|
chordPitchClasses,
|
|
26
|
+
chordQualities,
|
|
25
27
|
chordToneRole,
|
|
26
28
|
createsParallelPerfect,
|
|
27
29
|
diatonicTriad,
|
|
@@ -32,9 +34,10 @@ import {
|
|
|
32
34
|
minorKey,
|
|
33
35
|
nearestScaleTone,
|
|
34
36
|
scaleTonesInDegreeOrder
|
|
35
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-KBTTEX7Q.js";
|
|
36
38
|
import {
|
|
37
39
|
InvalidInputError,
|
|
40
|
+
assertDegree,
|
|
38
41
|
assertFiniteNumber,
|
|
39
42
|
assertGenerationBudget,
|
|
40
43
|
assertInteger,
|
|
@@ -43,12 +46,20 @@ import {
|
|
|
43
46
|
assertPositiveInt,
|
|
44
47
|
assertRange,
|
|
45
48
|
assertTimeSignature,
|
|
49
|
+
clampToMidi,
|
|
46
50
|
dropSilentNotes,
|
|
47
|
-
pitchClassOf
|
|
48
|
-
|
|
51
|
+
pitchClassOf,
|
|
52
|
+
soundingNotesOnly
|
|
53
|
+
} from "./chunk-R2VVFAIN.js";
|
|
49
54
|
|
|
50
55
|
// src/generate/bass/index.ts
|
|
51
|
-
var BASS_STYLES = [
|
|
56
|
+
var BASS_STYLES = Object.freeze([
|
|
57
|
+
"root",
|
|
58
|
+
"rootFifth",
|
|
59
|
+
"pop",
|
|
60
|
+
"walking",
|
|
61
|
+
"arpeggio"
|
|
62
|
+
]);
|
|
52
63
|
var DEFAULT_TS = { numerator: 4, denominator: 4 };
|
|
53
64
|
var DEFAULT_STYLE = "root";
|
|
54
65
|
var DEFAULT_OCTAVE = 2;
|
|
@@ -90,10 +101,24 @@ function fifthPcOf(chord) {
|
|
|
90
101
|
function bassPcOf(chord) {
|
|
91
102
|
return pitchClassOf(chord.bassPc ?? chord.rootPc);
|
|
92
103
|
}
|
|
93
|
-
function beatPositions(start, end) {
|
|
104
|
+
function beatPositions(start, end, ts) {
|
|
94
105
|
const positions = [];
|
|
95
|
-
|
|
96
|
-
|
|
106
|
+
const barBeats = beatsPerBar(ts);
|
|
107
|
+
const pulse = pulseBeats(ts);
|
|
108
|
+
const firstBar = Math.floor(start / barBeats);
|
|
109
|
+
const lastBar = Math.floor((end - EPS) / barBeats);
|
|
110
|
+
for (let bar = firstBar; bar <= lastBar; bar += 1) {
|
|
111
|
+
const barStart = bar * barBeats;
|
|
112
|
+
const firstPulse = Math.max(0, Math.ceil((start - barStart - EPS) / pulse));
|
|
113
|
+
for (let index = firstPulse; ; index += 1) {
|
|
114
|
+
const position = barStart + index * pulse;
|
|
115
|
+
if (position >= barStart + barBeats - EPS || position >= end - EPS) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
if (position >= start - EPS) {
|
|
119
|
+
positions.push(position);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
97
122
|
}
|
|
98
123
|
if (positions.length === 0) {
|
|
99
124
|
positions.push(start);
|
|
@@ -101,7 +126,10 @@ function beatPositions(start, end) {
|
|
|
101
126
|
return positions;
|
|
102
127
|
}
|
|
103
128
|
function emit(ctx, pos, pc, midiOverride) {
|
|
104
|
-
const midi =
|
|
129
|
+
const midi = clampToMidi(
|
|
130
|
+
midiOverride ?? placePc(pc, ctx.prevMidi, ctx.low),
|
|
131
|
+
"generated bass pitch"
|
|
132
|
+
);
|
|
105
133
|
const velocity = isStrongBeat(pos, ctx.ts) ? STRONG_VELOCITY : WEAK_VELOCITY;
|
|
106
134
|
ctx.notes.push({ startBeat: pos, pitch: midi, velocity });
|
|
107
135
|
return midi;
|
|
@@ -117,7 +145,7 @@ function buildRootFifth(ctx, seg) {
|
|
|
117
145
|
}
|
|
118
146
|
}
|
|
119
147
|
function buildPop(ctx, seg) {
|
|
120
|
-
const positions = beatPositions(seg.startBeat, seg.endBeat);
|
|
148
|
+
const positions = beatPositions(seg.startBeat, seg.endBeat, ctx.ts);
|
|
121
149
|
const rootPc = bassPcOf(seg.chord);
|
|
122
150
|
let emitted = false;
|
|
123
151
|
for (const pos of positions) {
|
|
@@ -140,7 +168,7 @@ function buildPop(ctx, seg) {
|
|
|
140
168
|
}
|
|
141
169
|
function buildArpeggio(ctx, seg) {
|
|
142
170
|
const tones = chordTonePcs(seg.chord);
|
|
143
|
-
const positions = beatPositions(seg.startBeat, seg.endBeat);
|
|
171
|
+
const positions = beatPositions(seg.startBeat, seg.endBeat, ctx.ts);
|
|
144
172
|
positions.forEach((pos, i) => {
|
|
145
173
|
const pc = tones[i % tones.length] ?? bassPcOf(seg.chord);
|
|
146
174
|
ctx.prevMidi = emit(ctx, pos, pc);
|
|
@@ -168,7 +196,7 @@ function foldIntoBand(midi, low) {
|
|
|
168
196
|
}
|
|
169
197
|
function buildWalking(ctx, seg, next) {
|
|
170
198
|
const tones = chordTonePcs(seg.chord);
|
|
171
|
-
const positions = beatPositions(seg.startBeat, seg.endBeat);
|
|
199
|
+
const positions = beatPositions(seg.startBeat, seg.endBeat, ctx.ts);
|
|
172
200
|
const count = positions.length;
|
|
173
201
|
positions.forEach((pos, i) => {
|
|
174
202
|
if (i === 0) {
|
|
@@ -197,6 +225,15 @@ function generateBassLine(opts) {
|
|
|
197
225
|
}
|
|
198
226
|
}
|
|
199
227
|
const segments = [...opts.segments].sort((a, b) => a.startBeat - b.startBeat);
|
|
228
|
+
for (let index = 1; index < segments.length; index += 1) {
|
|
229
|
+
const previous = segments[index - 1];
|
|
230
|
+
const current = segments[index];
|
|
231
|
+
if (previous !== void 0 && current !== void 0 && current.startBeat < previous.endBeat) {
|
|
232
|
+
throw new InvalidInputError(
|
|
233
|
+
`bass segments must not overlap: segments ${index - 1} and ${index} overlap`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
200
237
|
if (segments.length === 0) {
|
|
201
238
|
return [];
|
|
202
239
|
}
|
|
@@ -204,7 +241,7 @@ function generateBassLine(opts) {
|
|
|
204
241
|
assertTimeSignature(ts);
|
|
205
242
|
const style = assertOneOf(opts.style ?? DEFAULT_STYLE, BASS_STYLES, "bass style");
|
|
206
243
|
const octave = opts.octave ?? DEFAULT_OCTAVE;
|
|
207
|
-
assertInteger(octave, "bass octave", -1,
|
|
244
|
+
assertInteger(octave, "bass octave", -1, 8);
|
|
208
245
|
const estimatedNotes = segments.reduce(
|
|
209
246
|
(count, segment) => count + Math.max(1, Math.ceil(segment.endBeat - segment.startBeat)),
|
|
210
247
|
0
|
|
@@ -256,7 +293,7 @@ function generateBassLine(opts) {
|
|
|
256
293
|
continue;
|
|
257
294
|
}
|
|
258
295
|
out.push({
|
|
259
|
-
pitch: note.pitch,
|
|
296
|
+
pitch: clampToMidi(note.pitch, "generated bass pitch"),
|
|
260
297
|
startBeat: note.startBeat,
|
|
261
298
|
durationBeat,
|
|
262
299
|
velocity: note.velocity
|
|
@@ -429,8 +466,8 @@ function generateCounterMelody(opts) {
|
|
|
429
466
|
}
|
|
430
467
|
const chordChangeBeats = timeline === void 0 ? opts.chordChangeBeats ?? [] : timeline.segments.flatMap((segment) => [segment.startBeat, segment.endBeat]);
|
|
431
468
|
const register = opts.register ?? "below";
|
|
432
|
-
|
|
433
|
-
|
|
469
|
+
assertNoteEvents(opts.melody, "countermelody melody", { allowNonPositiveDuration: true });
|
|
470
|
+
const melody = createNoteEventIndex(soundingNotesOnly(opts.melody), {
|
|
434
471
|
tieBreak: register === "below" ? "highest" : "lowest"
|
|
435
472
|
});
|
|
436
473
|
const ts = opts.ts ?? DEFAULT_TS2;
|
|
@@ -438,13 +475,7 @@ function generateCounterMelody(opts) {
|
|
|
438
475
|
const rhythm = assertOneOf(opts.rhythm ?? "complement", ["complement", "follow"], "rhythm");
|
|
439
476
|
const profile = assertOneOf(opts.profile ?? "pop", ["strict", "pop"], "safety profile");
|
|
440
477
|
const rng = createRng(opts.seed ?? 0);
|
|
441
|
-
if (
|
|
442
|
-
assertInteger(opts.pitchLow, "countermelody pitchLow");
|
|
443
|
-
}
|
|
444
|
-
if (opts.pitchHigh !== void 0) {
|
|
445
|
-
assertInteger(opts.pitchHigh, "countermelody pitchHigh");
|
|
446
|
-
}
|
|
447
|
-
if (melody.notes.every((indexed) => indexed.note.durationBeat <= 0)) {
|
|
478
|
+
if (melody.notes.length === 0) {
|
|
448
479
|
return [];
|
|
449
480
|
}
|
|
450
481
|
const spanStart = melody.notes[0]?.note.startBeat ?? 0;
|
|
@@ -452,10 +483,14 @@ function generateCounterMelody(opts) {
|
|
|
452
483
|
assertGenerationBudget(Math.ceil((spanEnd - spanStart) / GRID_STEP), "countermelody grid");
|
|
453
484
|
const meanPitch = melody.notes.reduce((sum, n) => sum + n.note.pitch, 0) / melody.notes.length;
|
|
454
485
|
const defaultCenter = register === "below" ? meanPitch - REGISTER_OFFSET : meanPitch + REGISTER_OFFSET;
|
|
455
|
-
const low =
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
486
|
+
const low = clampToMidi(
|
|
487
|
+
opts.pitchLow ?? Math.round(defaultCenter) - DEFAULT_HALF_RANGE,
|
|
488
|
+
"countermelody pitchLow"
|
|
489
|
+
);
|
|
490
|
+
const high = clampToMidi(
|
|
491
|
+
opts.pitchHigh ?? Math.round(defaultCenter) + DEFAULT_HALF_RANGE,
|
|
492
|
+
"countermelody pitchHigh"
|
|
493
|
+
);
|
|
459
494
|
if (low > high) {
|
|
460
495
|
throw new InvalidInputError(
|
|
461
496
|
`countermelody pitchLow must not exceed pitchHigh; received ${low} > ${high}`
|
|
@@ -547,7 +582,12 @@ function generateCounterMelody(opts) {
|
|
|
547
582
|
1,
|
|
548
583
|
Math.min(127, Math.round((melNote?.velocity ?? DEFAULT_MELODY_VELOCITY) - VELOCITY_DROP))
|
|
549
584
|
);
|
|
550
|
-
out.push({
|
|
585
|
+
out.push({
|
|
586
|
+
pitch: clampToMidi(pitch),
|
|
587
|
+
startBeat: beat,
|
|
588
|
+
durationBeat: endBeat - beat,
|
|
589
|
+
velocity
|
|
590
|
+
});
|
|
551
591
|
prevPitch = pitch;
|
|
552
592
|
}
|
|
553
593
|
return out;
|
|
@@ -819,13 +859,10 @@ function getGhostProbabilityAtPosition(beat, sixteenthInBeat) {
|
|
|
819
859
|
const leadsIntoBackbeat = (beat === 0 || beat === 2) && sixteenthInBeat === 3;
|
|
820
860
|
return leadsIntoBackbeat ? 0.6 : 0.25;
|
|
821
861
|
}
|
|
822
|
-
function selectGhostPositions(mood
|
|
862
|
+
function selectGhostPositions(mood) {
|
|
823
863
|
if (mood === 2 /* Energetic */) {
|
|
824
864
|
return ["e", "a"];
|
|
825
865
|
}
|
|
826
|
-
if (mood === 0 /* Calm */) {
|
|
827
|
-
return rng.prob(0.5) ? ["e"] : [];
|
|
828
|
-
}
|
|
829
866
|
return ["e"];
|
|
830
867
|
}
|
|
831
868
|
|
|
@@ -841,6 +878,9 @@ function roleHiHatInstrument(role, useRide) {
|
|
|
841
878
|
if (role === "ambient") {
|
|
842
879
|
return GM.RIDE;
|
|
843
880
|
}
|
|
881
|
+
if (role === "minimal") {
|
|
882
|
+
return GM.FHH;
|
|
883
|
+
}
|
|
844
884
|
return useRide ? GM.RIDE : GM.CHH;
|
|
845
885
|
}
|
|
846
886
|
function sparser(level) {
|
|
@@ -1161,7 +1201,7 @@ function generateGhostNotesForBeat(ctx, sec) {
|
|
|
1161
1201
|
if (ctx.beat !== 0 && ctx.beat !== 2) {
|
|
1162
1202
|
return;
|
|
1163
1203
|
}
|
|
1164
|
-
const positions = selectGhostPositions(sec.ghostMood
|
|
1204
|
+
const positions = selectGhostPositions(sec.ghostMood);
|
|
1165
1205
|
let ghostProb = getGhostDensity(sec.ghostMood, ctx.section, sec.backingDensity, ctx.bpm);
|
|
1166
1206
|
if (sec.ghostBoost) {
|
|
1167
1207
|
ghostProb = Math.min(1, ghostProb * 1.4);
|
|
@@ -1196,16 +1236,18 @@ function generatePreChorusBuildup(ctx, sec, isSectionLastBar) {
|
|
|
1196
1236
|
const progress = (barInLift * 4 + ctx.beat) / (barsInLift * 4);
|
|
1197
1237
|
const buildupVel = ctx.velocity * (0.5 + 0.5 * progress);
|
|
1198
1238
|
ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, buildupVel);
|
|
1199
|
-
ctx.track.add(
|
|
1239
|
+
ctx.track.add(
|
|
1240
|
+
GM.SD,
|
|
1241
|
+
swing16(ctx.beatTick + EIGHTH, sec, ctx.swingAmount),
|
|
1242
|
+
EIGHTH,
|
|
1243
|
+
buildupVel * 0.85
|
|
1244
|
+
);
|
|
1200
1245
|
if (isSectionLastBar && ctx.beat === 3) {
|
|
1201
1246
|
ctx.track.add(GM.CRASH, ctx.beatTick + EIGHTH + SIXTEENTH, SIXTEENTH, ctx.velocity * 1.1);
|
|
1202
1247
|
}
|
|
1203
1248
|
}
|
|
1204
1249
|
function generateHiHatForBeat(ctx, sec) {
|
|
1205
1250
|
if (!shouldPlayHiHat(sec.role)) {
|
|
1206
|
-
if (sec.useFootHh && (ctx.beat === 0 || ctx.beat === 2)) {
|
|
1207
|
-
ctx.track.add(GM.FHH, ctx.beatTick, EIGHTH, footHiHatVelocity(ctx.rng));
|
|
1208
|
-
}
|
|
1209
1251
|
return;
|
|
1210
1252
|
}
|
|
1211
1253
|
const crossStick = sec.useRide && shouldUseBridgeCrossStick(ctx.section, ctx.beat);
|
|
@@ -1213,11 +1255,12 @@ function generateHiHatForBeat(ctx, sec) {
|
|
|
1213
1255
|
const hhType = sectionHiHatType(ctx.section, sec.role);
|
|
1214
1256
|
const typeMult = hiHatTypeVelocityMultiplier(hhType);
|
|
1215
1257
|
const isDynamicOpen = ctx.barHasOpenHh && ctx.beat === ctx.openHhBeat;
|
|
1258
|
+
const allowsOpenHiHat = sec.role !== "ambient" && sec.role !== "minimal";
|
|
1216
1259
|
const dm = sec.densityMult;
|
|
1217
1260
|
if (sec.hhLevel === "quarter") {
|
|
1218
1261
|
const introRest = ctx.section === "intro" && ctx.beat !== 0;
|
|
1219
1262
|
if (!introRest) {
|
|
1220
|
-
if (isDynamicOpen) {
|
|
1263
|
+
if (isDynamicOpen && allowsOpenHiHat) {
|
|
1221
1264
|
ctx.track.add(
|
|
1222
1265
|
GM.OHH,
|
|
1223
1266
|
ctx.beatTick,
|
|
@@ -1250,7 +1293,7 @@ function generateHiHatForBeat(ctx, sec) {
|
|
|
1250
1293
|
continue;
|
|
1251
1294
|
}
|
|
1252
1295
|
const hhVel = Math.max(20, ctx.velocity * dm * typeMult * (eighth === 0 ? 0.9 : 0.65));
|
|
1253
|
-
if (isDynamicOpen && eighth === 0) {
|
|
1296
|
+
if (isDynamicOpen && allowsOpenHiHat && eighth === 0) {
|
|
1254
1297
|
ctx.track.add(GM.OHH, hhTick, EIGHTH, hhVel + OHH_VEL_BOOST);
|
|
1255
1298
|
continue;
|
|
1256
1299
|
}
|
|
@@ -1261,7 +1304,7 @@ function generateHiHatForBeat(ctx, sec) {
|
|
|
1261
1304
|
} else if (eighth === 0) {
|
|
1262
1305
|
useOpen = shouldAddOpenHHAccent(ctx.section, ctx.beat, ctx.bar, ctx.rng);
|
|
1263
1306
|
}
|
|
1264
|
-
if (useOpen) {
|
|
1307
|
+
if (useOpen && allowsOpenHiHat) {
|
|
1265
1308
|
ctx.track.add(hiHatNote("open"), hhTick, EIGHTH, Math.max(20, hhVel * 1.1));
|
|
1266
1309
|
} else {
|
|
1267
1310
|
ctx.track.add(hhInstrument, hhTick, EIGHTH / 2, hhVel);
|
|
@@ -1276,11 +1319,11 @@ function generateHiHatForBeat(ctx, sec) {
|
|
|
1276
1319
|
}
|
|
1277
1320
|
const metricVel = hiHatVelocityMultiplier(sixteenth, ctx.rng);
|
|
1278
1321
|
const hhVel = Math.max(20, ctx.velocity * dm * typeMult * metricVel);
|
|
1279
|
-
if (isDynamicOpen && sixteenth === 0) {
|
|
1322
|
+
if (isDynamicOpen && allowsOpenHiHat && sixteenth === 0) {
|
|
1280
1323
|
ctx.track.add(GM.OHH, hhTick, SIXTEENTH, hhVel + OHH_VEL_BOOST);
|
|
1281
1324
|
continue;
|
|
1282
1325
|
}
|
|
1283
|
-
if (ctx.beat === 3 && sixteenth === 3) {
|
|
1326
|
+
if (allowsOpenHiHat && ctx.beat === 3 && sixteenth === 3) {
|
|
1284
1327
|
const openProb = Math.max(0.1, Math.min(0.4, 30 / ctx.bpm));
|
|
1285
1328
|
if (ctx.rng.prob(openProb)) {
|
|
1286
1329
|
ctx.track.add(GM.OHH, hhTick, SIXTEENTH, Math.max(20, hhVel * 1.2));
|
|
@@ -1335,7 +1378,7 @@ function getFillStartBeat(energy) {
|
|
|
1335
1378
|
return 2;
|
|
1336
1379
|
case "high":
|
|
1337
1380
|
case "peak":
|
|
1338
|
-
return
|
|
1381
|
+
return 2;
|
|
1339
1382
|
}
|
|
1340
1383
|
}
|
|
1341
1384
|
function selectFillType(from, to, style, nextEnergy, rng) {
|
|
@@ -1600,7 +1643,7 @@ function generateFill(track, beatTick, beat, fillType, velocity) {
|
|
|
1600
1643
|
}
|
|
1601
1644
|
|
|
1602
1645
|
// src/generate/drums/hit.ts
|
|
1603
|
-
var DRUM_NOTES = {
|
|
1646
|
+
var DRUM_NOTES = Object.freeze({
|
|
1604
1647
|
kick: GM.BD,
|
|
1605
1648
|
snare: GM.SD,
|
|
1606
1649
|
sideStick: GM.SIDESTICK,
|
|
@@ -1615,7 +1658,7 @@ var DRUM_NOTES = {
|
|
|
1615
1658
|
midTom: GM.TOM_M,
|
|
1616
1659
|
lowTom: GM.TOM_L,
|
|
1617
1660
|
shaker: GM.SHAKER
|
|
1618
|
-
};
|
|
1661
|
+
});
|
|
1619
1662
|
var VOICE_BY_NOTE = new Map(
|
|
1620
1663
|
Object.entries(DRUM_NOTES).map(([voice, note]) => [note, voice])
|
|
1621
1664
|
);
|
|
@@ -1794,12 +1837,12 @@ function getPercussionConfig(mood, section) {
|
|
|
1794
1837
|
shaker16th: act[1]
|
|
1795
1838
|
};
|
|
1796
1839
|
}
|
|
1797
|
-
function generateAuxPercussionForBar(track, barStart, config, role, densityMult, rng, bpm) {
|
|
1840
|
+
function generateAuxPercussionForBar(track, barStart, config, role, densityMult, rng, bpm, sec, swingAmount, barBeats = 4) {
|
|
1798
1841
|
if (role === "minimal") {
|
|
1799
1842
|
return;
|
|
1800
1843
|
}
|
|
1801
1844
|
if (config.tambourine) {
|
|
1802
|
-
for (let beat = 1; beat
|
|
1845
|
+
for (let beat = 1; beat < barBeats; beat += 2) {
|
|
1803
1846
|
const raw = 70 * densityMult * rng.float(0.9, 1.1);
|
|
1804
1847
|
track.add(GM.TAMBOURINE, barStart + beat, EIGHTH, Math.max(40, Math.min(90, raw)));
|
|
1805
1848
|
}
|
|
@@ -1808,12 +1851,12 @@ function generateAuxPercussionForBar(track, barStart, config, role, densityMult,
|
|
|
1808
1851
|
const use16th = config.shaker16th && bpm < HH_16TH_BPM_THRESHOLD;
|
|
1809
1852
|
if (use16th) {
|
|
1810
1853
|
const velCurve = [0.75, 0.45, 0.6, 0.45];
|
|
1811
|
-
for (let beat = 0; beat <
|
|
1854
|
+
for (let beat = 0; beat < barBeats; beat += 1) {
|
|
1812
1855
|
for (let sub = 0; sub < 4; sub += 1) {
|
|
1813
1856
|
const raw = 80 * (velCurve[sub] ?? 0.5) * densityMult * rng.float(0.9, 1.1);
|
|
1814
1857
|
track.add(
|
|
1815
1858
|
GM.SHAKER,
|
|
1816
|
-
barStart + beat + sub * SIXTEENTH,
|
|
1859
|
+
swing16(barStart + beat + sub * SIXTEENTH, sec, swingAmount),
|
|
1817
1860
|
SIXTEENTH,
|
|
1818
1861
|
Math.max(25, Math.min(85, raw))
|
|
1819
1862
|
);
|
|
@@ -1821,12 +1864,12 @@ function generateAuxPercussionForBar(track, barStart, config, role, densityMult,
|
|
|
1821
1864
|
}
|
|
1822
1865
|
} else {
|
|
1823
1866
|
const velCurve = [0.75, 0.55];
|
|
1824
|
-
for (let beat = 0; beat <
|
|
1867
|
+
for (let beat = 0; beat < barBeats; beat += 1) {
|
|
1825
1868
|
for (let sub = 0; sub < 2; sub += 1) {
|
|
1826
1869
|
const raw = 80 * (velCurve[sub] ?? 0.6) * densityMult * rng.float(0.9, 1.1);
|
|
1827
1870
|
track.add(
|
|
1828
1871
|
GM.SHAKER,
|
|
1829
|
-
barStart + beat + sub * EIGHTH,
|
|
1872
|
+
swing16(barStart + beat + sub * EIGHTH, sec, swingAmount),
|
|
1830
1873
|
EIGHTH,
|
|
1831
1874
|
Math.max(25, Math.min(85, raw))
|
|
1832
1875
|
);
|
|
@@ -1835,7 +1878,7 @@ function generateAuxPercussionForBar(track, barStart, config, role, densityMult,
|
|
|
1835
1878
|
}
|
|
1836
1879
|
}
|
|
1837
1880
|
if (config.handclap) {
|
|
1838
|
-
for (let beat = 1; beat
|
|
1881
|
+
for (let beat = 1; beat < barBeats; beat += 2) {
|
|
1839
1882
|
const raw = 85 * densityMult * rng.float(0.9, 1.1);
|
|
1840
1883
|
track.add(GM.HANDCLAP, barStart + beat, EIGHTH, Math.max(50, Math.min(100, raw)));
|
|
1841
1884
|
}
|
|
@@ -1866,6 +1909,9 @@ function generateDrums(opts) {
|
|
|
1866
1909
|
const style = mapping.style;
|
|
1867
1910
|
const feel = opts.feel === void 0 ? mapping.feel : assertOneOf(opts.feel, DRUM_FEELS, "drum feel");
|
|
1868
1911
|
const role = opts.role === void 0 ? "full" : assertOneOf(opts.role, DRUM_ROLES, "drum role");
|
|
1912
|
+
const ts = opts.ts ?? { numerator: 4, denominator: 4 };
|
|
1913
|
+
assertTimeSignature(ts, "drum time signature");
|
|
1914
|
+
const barBeats = beatsPerBar(ts);
|
|
1869
1915
|
const playMainVoices = role !== "fxOnly";
|
|
1870
1916
|
const section = mapSection(publicSection);
|
|
1871
1917
|
const backingDensity = mapDensity(opts.density);
|
|
@@ -1901,7 +1947,7 @@ function generateDrums(opts) {
|
|
|
1901
1947
|
const reuseSectionKick = (section === "b" || section === "chorus") && style !== "sparse";
|
|
1902
1948
|
let sectionKick;
|
|
1903
1949
|
for (let bar = 0; bar < opts.bars; bar += 1) {
|
|
1904
|
-
const barStart = bar *
|
|
1950
|
+
const barStart = bar * barBeats;
|
|
1905
1951
|
const isLastBar = bar === opts.bars - 1;
|
|
1906
1952
|
if (bar === 0 && section === "chorus") {
|
|
1907
1953
|
track.add(GM.CRASH, barStart, 0.5, 100 * densityMult);
|
|
@@ -1921,7 +1967,7 @@ function generateDrums(opts) {
|
|
|
1921
1967
|
}
|
|
1922
1968
|
const inLift = isInPreChorusLift(section, bar, opts.bars, nextSection === "chorus");
|
|
1923
1969
|
let currentFill = "snareRoll";
|
|
1924
|
-
for (let beat = 0; beat <
|
|
1970
|
+
for (let beat = 0; beat < barBeats; beat += 1) {
|
|
1925
1971
|
const beatTick = barStart + beat;
|
|
1926
1972
|
const velocity = calculateVelocity(section, beat);
|
|
1927
1973
|
if (opts.fills && isLastBar && !inLift && beat >= fillStartBeat) {
|
|
@@ -1958,14 +2004,14 @@ function generateDrums(opts) {
|
|
|
1958
2004
|
generatePreChorusBuildup(ctx, sec, isLastBar);
|
|
1959
2005
|
}
|
|
1960
2006
|
if (euclidKick && !inLift) {
|
|
1961
|
-
const stepLength =
|
|
2007
|
+
const stepLength = barBeats / euclidSteps;
|
|
1962
2008
|
for (let step = 0; step < euclidKick.length; step += 1) {
|
|
1963
2009
|
const onset = step * stepLength;
|
|
1964
2010
|
if (euclidKick[step] && Math.floor(onset) === beat) {
|
|
1965
2011
|
const wholeBeat = Math.abs(onset - Math.round(onset)) < 1e-9;
|
|
1966
2012
|
track.add(
|
|
1967
2013
|
GM.BD,
|
|
1968
|
-
barStart + onset,
|
|
2014
|
+
swing16(barStart + onset, sec, swingAmount),
|
|
1969
2015
|
Math.min(0.5, stepLength),
|
|
1970
2016
|
velocity * (wholeBeat ? 1 : 0.85)
|
|
1971
2017
|
);
|
|
@@ -1982,7 +2028,7 @@ function generateDrums(opts) {
|
|
|
1982
2028
|
generateHiHatForBeat(ctx, sec);
|
|
1983
2029
|
}
|
|
1984
2030
|
if (sec.useFootHh && shouldPlayHiHat(role)) {
|
|
1985
|
-
for (let fhhBeat = 0; fhhBeat <
|
|
2031
|
+
for (let fhhBeat = 0; fhhBeat < barBeats; fhhBeat += 2) {
|
|
1986
2032
|
const fhhTick = barStart + fhhBeat;
|
|
1987
2033
|
if (!track.hasOnset(GM.FHH, fhhTick)) {
|
|
1988
2034
|
track.add(GM.FHH, fhhTick, 0.5, footHiHatVelocity(rng));
|
|
@@ -2005,10 +2051,14 @@ function generateDrums(opts) {
|
|
|
2005
2051
|
role,
|
|
2006
2052
|
densityMult,
|
|
2007
2053
|
rng,
|
|
2008
|
-
opts.bpm
|
|
2054
|
+
opts.bpm,
|
|
2055
|
+
sec,
|
|
2056
|
+
swingAmount,
|
|
2057
|
+
barBeats
|
|
2009
2058
|
);
|
|
2010
2059
|
}
|
|
2011
|
-
|
|
2060
|
+
const endBeat = opts.bars * barBeats;
|
|
2061
|
+
return track.hits.filter((hit) => hit.startBeat < endBeat).sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);
|
|
2012
2062
|
}
|
|
2013
2063
|
|
|
2014
2064
|
// src/generate/groove/index.ts
|
|
@@ -2070,7 +2120,8 @@ function extractGrooveTemplate(events, ts, subdivision = DEFAULT_GROOVE_SUBDIVIS
|
|
|
2070
2120
|
assertNoteEvents(events, "groove source events", { allowNonPositiveDuration: true });
|
|
2071
2121
|
assertPositiveInt(subdivision, "groove subdivision");
|
|
2072
2122
|
const barBeats = beatsPerBar(ts);
|
|
2073
|
-
const slotsPerBar = Math.
|
|
2123
|
+
const slotsPerBar = Math.ceil(barBeats * subdivision);
|
|
2124
|
+
assertPositiveInt(slotsPerBar, "groove template slots");
|
|
2074
2125
|
assertGenerationBudget(slotsPerBar, "groove template slots");
|
|
2075
2126
|
const offsetSums = new Array(slotsPerBar).fill(0);
|
|
2076
2127
|
const velocitySums = new Array(slotsPerBar).fill(0);
|
|
@@ -2128,7 +2179,7 @@ function applyGrooveTemplate(events, template, ts) {
|
|
|
2128
2179
|
template.slotsPerBar
|
|
2129
2180
|
);
|
|
2130
2181
|
const slot = template.slots[slotIndex];
|
|
2131
|
-
const startBeat = quantizedBeat + (slot?.timingOffset ?? 0);
|
|
2182
|
+
const startBeat = Math.max(0, quantizedBeat + (slot?.timingOffset ?? 0));
|
|
2132
2183
|
const velocity = slot && slot.velocity !== null && slot.velocity !== void 0 ? Math.round(slot.velocity) : event.velocity;
|
|
2133
2184
|
return {
|
|
2134
2185
|
pitch: event.pitch,
|
|
@@ -2338,41 +2389,47 @@ function harmonizeOnce(melody, key, candidates, segments, jitter, ts) {
|
|
|
2338
2389
|
return { cost: bestCost, path };
|
|
2339
2390
|
}
|
|
2340
2391
|
function harmonizeMelody(opts) {
|
|
2341
|
-
|
|
2392
|
+
assertNoteEvents(opts.melody, "harmonize melody", { allowNonPositiveDuration: true });
|
|
2393
|
+
const soundingMelody = soundingNotesOnly(opts.melody);
|
|
2394
|
+
const noteIndex = createNoteEventIndex(soundingMelody);
|
|
2342
2395
|
const ts = opts.ts ?? DEFAULT_METER;
|
|
2343
2396
|
assertTimeSignature(ts);
|
|
2344
2397
|
const requestedKey = opts.key ?? "infer";
|
|
2345
|
-
const key = requestedKey === "infer" ? inferKey(
|
|
2398
|
+
const key = requestedKey === "infer" ? inferKey(soundingMelody) : requestedKey;
|
|
2346
2399
|
const placement = opts.placement ?? DEFAULT_PLACEMENT;
|
|
2347
|
-
if (noteIndex.notes.
|
|
2400
|
+
if (noteIndex.notes.length === 0) {
|
|
2348
2401
|
return { transposeSemitones: 0, key, chords: [], melodyRoles: [] };
|
|
2349
2402
|
}
|
|
2350
2403
|
const candidates = buildCandidates(key, opts.reharmonize ?? "diatonic");
|
|
2351
2404
|
const candAt = (i) => candidates[i] ?? FALLBACK;
|
|
2352
2405
|
const rng = createRng(opts.seed ?? 0);
|
|
2353
2406
|
const jitter = candidates.map(() => rng.next() * TIE_BREAK_JITTER2);
|
|
2354
|
-
const
|
|
2407
|
+
const melodyStart = noteIndex.notes.reduce(
|
|
2408
|
+
(start, indexed) => Math.min(start, indexed.note.startBeat),
|
|
2409
|
+
Number.POSITIVE_INFINITY
|
|
2410
|
+
);
|
|
2411
|
+
const melodyEnd = noteIndex.notes.reduce((end, indexed) => Math.max(end, indexed.endBeat), 0);
|
|
2355
2412
|
const hr = assertRange(
|
|
2356
2413
|
opts.harmonicRhythm ?? DEFAULT_HARMONIC_RHYTHM,
|
|
2357
2414
|
Number.MIN_VALUE,
|
|
2358
2415
|
Number.MAX_SAFE_INTEGER,
|
|
2359
2416
|
"harmonic rhythm"
|
|
2360
2417
|
);
|
|
2361
|
-
const
|
|
2418
|
+
const segmentStart = Math.floor(melodyStart / hr) * hr;
|
|
2419
|
+
const segCount = Math.max(1, Math.ceil((melodyEnd - segmentStart) / hr));
|
|
2362
2420
|
assertGenerationBudget(segCount, "harmonic segments");
|
|
2363
|
-
assertGenerationBudget(segCount * candidates.length * candidates.length, "harmonization search");
|
|
2364
2421
|
const segments = Array.from({ length: segCount }, (_, s) => ({
|
|
2365
|
-
startBeat: s * hr,
|
|
2366
|
-
endBeat: (s + 1) * hr,
|
|
2422
|
+
startBeat: segmentStart + s * hr,
|
|
2423
|
+
endBeat: segmentStart + (s + 1) * hr,
|
|
2367
2424
|
noteIndices: []
|
|
2368
2425
|
}));
|
|
2369
2426
|
let memberships = 0;
|
|
2370
2427
|
for (const indexed of noteIndex.notes) {
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2428
|
+
const first = Math.max(0, Math.floor((indexed.note.startBeat - segmentStart) / hr));
|
|
2429
|
+
const lastExclusive = Math.min(
|
|
2430
|
+
segCount,
|
|
2431
|
+
Math.ceil((indexed.endBeat - segmentStart) / hr - Number.EPSILON)
|
|
2432
|
+
);
|
|
2376
2433
|
memberships += Math.max(0, lastExclusive - first);
|
|
2377
2434
|
assertGenerationBudget(memberships, "note-to-segment memberships");
|
|
2378
2435
|
for (let segment = first; segment < lastExclusive; segment += 1) {
|
|
@@ -2394,6 +2451,10 @@ function harmonizeMelody(opts) {
|
|
|
2394
2451
|
}
|
|
2395
2452
|
}
|
|
2396
2453
|
}
|
|
2454
|
+
assertGenerationBudget(
|
|
2455
|
+
transposes.length * segCount * candidates.length * candidates.length,
|
|
2456
|
+
"harmonization placement search"
|
|
2457
|
+
);
|
|
2397
2458
|
let bestCost = Number.POSITIVE_INFINITY;
|
|
2398
2459
|
let bestTs = 0;
|
|
2399
2460
|
let bestPath = [];
|
|
@@ -2414,7 +2475,7 @@ function harmonizeMelody(opts) {
|
|
|
2414
2475
|
const chord = {
|
|
2415
2476
|
rootPc: cand.rootPc,
|
|
2416
2477
|
quality: cand.quality,
|
|
2417
|
-
startBeat: segments[s]?.startBeat ?? s * hr
|
|
2478
|
+
startBeat: segments[s]?.startBeat ?? segmentStart + s * hr
|
|
2418
2479
|
};
|
|
2419
2480
|
if (cand.degree !== void 0) {
|
|
2420
2481
|
chord.degree = cand.degree;
|
|
@@ -2546,7 +2607,11 @@ function generateMotif(opts) {
|
|
|
2546
2607
|
if (opts.chord && isDownbeat(startBeat, barBeats)) {
|
|
2547
2608
|
pitch = nearestChordTone(pitch, opts.chord);
|
|
2548
2609
|
}
|
|
2549
|
-
notes.push({
|
|
2610
|
+
notes.push({
|
|
2611
|
+
pitch: clampToMidi(pitch, "generated motif pitch"),
|
|
2612
|
+
startBeat,
|
|
2613
|
+
durationBeat: beatsPerNote
|
|
2614
|
+
});
|
|
2550
2615
|
}
|
|
2551
2616
|
return { notes };
|
|
2552
2617
|
}
|
|
@@ -2571,8 +2636,11 @@ function transformUnchecked(cell, t, amount, key) {
|
|
|
2571
2636
|
if (amount !== void 0) {
|
|
2572
2637
|
assertFiniteNumber(amount, "motif transform amount");
|
|
2573
2638
|
}
|
|
2574
|
-
if ((t === "transposeDiatonic" || t === "sequence") && amount !== void 0) {
|
|
2575
|
-
assertInteger(
|
|
2639
|
+
if ((t === "transposeChromatic" || t === "transposeDiatonic" || t === "sequence") && amount !== void 0) {
|
|
2640
|
+
assertInteger(
|
|
2641
|
+
amount,
|
|
2642
|
+
`${t === "transposeChromatic" ? "chromatic" : "diatonic"} transform amount`
|
|
2643
|
+
);
|
|
2576
2644
|
}
|
|
2577
2645
|
if ((t === "augment" || t === "diminish") && amount !== void 0 && amount <= 0) {
|
|
2578
2646
|
throw new InvalidInputError("time transform amount must be positive");
|
|
@@ -2670,7 +2738,7 @@ function developMotif(cell, timeline, key, bars, ts = DEFAULT_TS4) {
|
|
|
2670
2738
|
}
|
|
2671
2739
|
|
|
2672
2740
|
// src/generate/progression/index.ts
|
|
2673
|
-
var BORROWED_DEGREES = {
|
|
2741
|
+
var BORROWED_DEGREES = Object.freeze({
|
|
2674
2742
|
/** Flat submediant: bVI. */
|
|
2675
2743
|
bVI: 8,
|
|
2676
2744
|
/** Flat subtonic: bVII. */
|
|
@@ -2683,7 +2751,7 @@ var BORROWED_DEGREES = {
|
|
|
2683
2751
|
bII: 13,
|
|
2684
2752
|
/** Sharp subdominant, the diminished #IV. */
|
|
2685
2753
|
sharpIV: 14
|
|
2686
|
-
};
|
|
2754
|
+
});
|
|
2687
2755
|
var PRESETS = [
|
|
2688
2756
|
{
|
|
2689
2757
|
id: "fourChordPop",
|
|
@@ -2862,8 +2930,12 @@ function degreeToRootPc(degree, key) {
|
|
|
2862
2930
|
const offset = BORROWED_OFFSET[degree] ?? 0;
|
|
2863
2931
|
return ((key.rootPc % 12 + offset) % 12 + 12) % 12;
|
|
2864
2932
|
}
|
|
2865
|
-
function autoQuality(degree, key) {
|
|
2933
|
+
function autoQuality(degree, key, harmonicDominant) {
|
|
2866
2934
|
if (degree >= 0 && degree <= 6) {
|
|
2935
|
+
const isMinor = (key.modeMask12 >> 3 & 1) === 1 && (key.modeMask12 >> 4 & 1) === 0;
|
|
2936
|
+
if (harmonicDominant && isMinor && degree === 4) {
|
|
2937
|
+
return "maj";
|
|
2938
|
+
}
|
|
2867
2939
|
return diatonicTriad(degree, key).quality;
|
|
2868
2940
|
}
|
|
2869
2941
|
if (degree === 14) {
|
|
@@ -2874,13 +2946,13 @@ function autoQuality(degree, key) {
|
|
|
2874
2946
|
}
|
|
2875
2947
|
return "maj";
|
|
2876
2948
|
}
|
|
2877
|
-
function resolveCycle(degrees, key, ext) {
|
|
2949
|
+
function resolveCycle(degrees, key, ext, harmonicDominant) {
|
|
2878
2950
|
const steps = [];
|
|
2879
2951
|
for (const degree of degrees) {
|
|
2880
2952
|
const step = {
|
|
2881
2953
|
source: degree,
|
|
2882
2954
|
rootPc: degreeToRootPc(degree, key),
|
|
2883
|
-
quality: ext !== void 0 && ext !== "auto" ? ext : autoQuality(degree, key)
|
|
2955
|
+
quality: ext !== void 0 && ext !== "auto" ? ext : autoQuality(degree, key, harmonicDominant)
|
|
2884
2956
|
};
|
|
2885
2957
|
if (degree >= 0 && degree <= 6) {
|
|
2886
2958
|
step.degree = degree;
|
|
@@ -2922,7 +2994,16 @@ function generateProgression(opts) {
|
|
|
2922
2994
|
preset = {
|
|
2923
2995
|
id: opts.preset.id ?? "custom",
|
|
2924
2996
|
name: opts.preset.name ?? "Custom",
|
|
2925
|
-
degrees: opts.preset.degrees,
|
|
2997
|
+
degrees: opts.preset.degrees.map((degree, index) => {
|
|
2998
|
+
assertDegree(degree, `progression preset degrees[${index}]`);
|
|
2999
|
+
const supported = degree >= 0 && degree <= 6 || Object.values(BORROWED_DEGREES).includes(degree);
|
|
3000
|
+
if (!supported) {
|
|
3001
|
+
throw new InvalidInputError(
|
|
3002
|
+
`progression preset degrees[${index}] is not a supported progression degree; received ${degree}`
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
return degree;
|
|
3006
|
+
}),
|
|
2926
3007
|
functional: opts.preset.functional ?? "loop",
|
|
2927
3008
|
styles: opts.preset.styles ?? [opts.style]
|
|
2928
3009
|
};
|
|
@@ -2934,7 +3015,13 @@ function generateProgression(opts) {
|
|
|
2934
3015
|
}
|
|
2935
3016
|
}
|
|
2936
3017
|
preset ??= pickProgressionPreset(opts.style, seed);
|
|
2937
|
-
const
|
|
3018
|
+
const ext = opts.ext === void 0 ? "auto" : assertOneOf(opts.ext, ["auto", ...chordQualities()], "progression extension");
|
|
3019
|
+
const cycle = resolveCycle(
|
|
3020
|
+
preset?.degrees ?? [0],
|
|
3021
|
+
opts.key,
|
|
3022
|
+
ext,
|
|
3023
|
+
preset?.functional === "cadenceStrong"
|
|
3024
|
+
);
|
|
2938
3025
|
const chords = [];
|
|
2939
3026
|
for (let bar = 0; bar < opts.bars; bar += 1) {
|
|
2940
3027
|
const step = cycle[bar % cycle.length];
|
|
@@ -2951,6 +3038,7 @@ function generateProgression(opts) {
|
|
|
2951
3038
|
if (opts.reharmonize) {
|
|
2952
3039
|
const tonicPc = (opts.key.rootPc % 12 + 12) % 12;
|
|
2953
3040
|
const rrng = createRng((seed ^ 2654435769) >>> 0);
|
|
3041
|
+
let tonicStatements = chords.filter((chord) => chord.rootPc === tonicPc).length;
|
|
2954
3042
|
for (let i = 0; i < chords.length - 1; i += 1) {
|
|
2955
3043
|
const cur = chords[i];
|
|
2956
3044
|
const next = chords[i + 1];
|
|
@@ -2959,12 +3047,16 @@ function generateProgression(opts) {
|
|
|
2959
3047
|
}
|
|
2960
3048
|
const domRoot = (next.rootPc + 7) % 12;
|
|
2961
3049
|
const targetsTonic = (next.rootPc - tonicPc + 12) % 12 === 0;
|
|
3050
|
+
const isLastTonicStatement = cur.rootPc === tonicPc && tonicStatements <= 1;
|
|
2962
3051
|
const alreadySecondary = cur.rootPc === domRoot && cur.quality === "dom7";
|
|
2963
3052
|
const isResolutionTarget = chords[i - 1]?.secondaryDominant === true;
|
|
2964
|
-
if (targetsTonic || alreadySecondary || isResolutionTarget) {
|
|
3053
|
+
if (targetsTonic || isLastTonicStatement || alreadySecondary || isResolutionTarget) {
|
|
2965
3054
|
continue;
|
|
2966
3055
|
}
|
|
2967
3056
|
if (rrng.next() < 0.5) {
|
|
3057
|
+
if (cur.rootPc === tonicPc) {
|
|
3058
|
+
tonicStatements -= 1;
|
|
3059
|
+
}
|
|
2968
3060
|
chords[i] = {
|
|
2969
3061
|
rootPc: domRoot,
|
|
2970
3062
|
quality: "dom7",
|
|
@@ -3014,21 +3106,28 @@ function generateRhythm(ts, opts = {}) {
|
|
|
3014
3106
|
const barBeats = beatsPerBar(ts);
|
|
3015
3107
|
const spanBeats = barBeats * bars;
|
|
3016
3108
|
const step = 1 / subdivision;
|
|
3017
|
-
const
|
|
3109
|
+
const slotsPerBar = Math.ceil(barBeats * subdivision);
|
|
3110
|
+
const slotCount = slotsPerBar * bars;
|
|
3018
3111
|
assertGenerationBudget(slotCount, "rhythm grid slots");
|
|
3019
|
-
const slotsPerBar = Math.round(barBeats * subdivision);
|
|
3020
3112
|
const rng = createRng(seed);
|
|
3021
3113
|
const positions = [];
|
|
3022
|
-
for (let
|
|
3023
|
-
const
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3114
|
+
for (let bar = 0; bar < bars; bar += 1) {
|
|
3115
|
+
const barStart = bar * barBeats;
|
|
3116
|
+
const barEnd = barStart + barBeats;
|
|
3117
|
+
for (let slot = 0; slot < slotsPerBar; slot += 1) {
|
|
3118
|
+
const position = barStart + slot * step;
|
|
3119
|
+
if (position >= barEnd - Number.EPSILON) {
|
|
3120
|
+
break;
|
|
3121
|
+
}
|
|
3122
|
+
if (slot === 0) {
|
|
3123
|
+
positions.push(position);
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
const weight = metricWeight(position, ts);
|
|
3127
|
+
const probability = onsetWeightCurve(weight) * density;
|
|
3128
|
+
if (rng.prob(probability)) {
|
|
3129
|
+
positions.push(position);
|
|
3130
|
+
}
|
|
3032
3131
|
}
|
|
3033
3132
|
}
|
|
3034
3133
|
const events = [];
|
|
@@ -3074,4 +3173,4 @@ export {
|
|
|
3074
3173
|
generateRhythm,
|
|
3075
3174
|
rhythmDensity
|
|
3076
3175
|
};
|
|
3077
|
-
//# sourceMappingURL=chunk-
|
|
3176
|
+
//# sourceMappingURL=chunk-AID75RN3.js.map
|