@libraz/libcantus 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +63 -0
- package/README.md +3 -1
- package/README_ja.md +13 -11
- package/dist/analyze/index.cjs +7 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +14 -12
- package/dist/analyze/index.d.ts +14 -12
- package/dist/analyze/index.js +6 -6
- package/dist/{chunk-NB25P3EH.js → chunk-2G5MCGYU.js} +33 -11
- package/dist/chunk-2G5MCGYU.js.map +1 -0
- package/dist/{chunk-NM7UAMC3.cjs → chunk-3FDBMIPR.cjs} +64 -64
- package/dist/chunk-3FDBMIPR.cjs.map +1 -0
- package/dist/{chunk-YFA7X2F6.cjs → chunk-3MDOEFIQ.cjs} +411 -327
- package/dist/chunk-3MDOEFIQ.cjs.map +1 -0
- package/dist/{chunk-IPEF6NWB.js → chunk-5MWRBYAT.js} +278 -154
- package/dist/chunk-5MWRBYAT.js.map +1 -0
- package/dist/{chunk-B2PW6EHN.cjs → chunk-6TTBEON3.cjs} +1253 -889
- package/dist/chunk-6TTBEON3.cjs.map +1 -0
- package/dist/{chunk-UMWZ4ZET.js → chunk-7NQ6AL3H.js} +4 -4
- package/dist/chunk-7NQ6AL3H.js.map +1 -0
- package/dist/{chunk-PWC4CRWT.cjs → chunk-E3QO3UET.cjs} +31 -23
- package/dist/chunk-E3QO3UET.cjs.map +1 -0
- package/dist/{chunk-GNVN4PPJ.js → chunk-FJFULJZE.js} +531 -213
- package/dist/chunk-FJFULJZE.js.map +1 -0
- package/dist/{chunk-CPMB7P75.js → chunk-FK3DGAIZ.js} +608 -244
- package/dist/chunk-FK3DGAIZ.js.map +1 -0
- package/dist/chunk-GEDTJHAC.cjs.map +1 -1
- package/dist/{chunk-RFIBF2RR.js → chunk-H7FVMBUC.js} +138 -65
- package/dist/chunk-H7FVMBUC.js.map +1 -0
- package/dist/{chunk-4R2LWV7E.cjs → chunk-HYSVSFNI.cjs} +664 -540
- package/dist/chunk-HYSVSFNI.cjs.map +1 -0
- package/dist/{chunk-35NBV2FT.cjs → chunk-KMYGRFZ6.cjs} +47 -24
- package/dist/chunk-KMYGRFZ6.cjs.map +1 -0
- package/dist/{chunk-IFZDWIKM.cjs → chunk-LQZQDBCD.cjs} +1048 -730
- package/dist/chunk-LQZQDBCD.cjs.map +1 -0
- package/dist/{chunk-PM4XJE2Y.cjs → chunk-P3CPXAJ5.cjs} +76 -54
- package/dist/chunk-P3CPXAJ5.cjs.map +1 -0
- package/dist/{chunk-S5BKZZGM.cjs → chunk-S3QELJUE.cjs} +72 -56
- package/dist/chunk-S3QELJUE.cjs.map +1 -0
- package/dist/{chunk-PUKJP2Z5.js → chunk-TXFWSU5D.js} +31 -8
- package/dist/chunk-TXFWSU5D.js.map +1 -0
- package/dist/{chunk-76ZBTRG2.cjs → chunk-U4EQTDGN.cjs} +258 -218
- package/dist/chunk-U4EQTDGN.cjs.map +1 -0
- package/dist/chunk-UGLFDG7L.cjs.map +1 -1
- package/dist/{chunk-MLBPXA4Z.js → chunk-UTE5A36R.js} +198 -114
- package/dist/chunk-UTE5A36R.js.map +1 -0
- package/dist/{chunk-ZBUROTJQ.js → chunk-UZKQISRF.js} +17 -9
- package/dist/chunk-UZKQISRF.js.map +1 -0
- package/dist/{chunk-7KG3RTYS.js → chunk-VL5NXVCN.js} +281 -155
- package/dist/chunk-VL5NXVCN.js.map +1 -0
- package/dist/{chunk-CGBADIO5.cjs → chunk-WOMCXN2W.cjs} +328 -202
- package/dist/chunk-WOMCXN2W.cjs.map +1 -0
- package/dist/{chunk-3R3QVKY5.cjs → chunk-YDGMWH43.cjs} +397 -324
- package/dist/chunk-YDGMWH43.cjs.map +1 -0
- package/dist/{chunk-AGOVW7U6.js → chunk-YYVXRV3V.js} +72 -56
- package/dist/chunk-YYVXRV3V.js.map +1 -0
- package/dist/{chunk-M5RYMAFI.js → chunk-ZLNSH4UL.js} +71 -31
- package/dist/chunk-ZLNSH4UL.js.map +1 -0
- package/dist/{coerce-DRERzdve.d.cts → coerce-CUhHc3Tu.d.ts} +16 -8
- package/dist/{coerce-DP0vYu-x.d.ts → coerce-Ytt2Eiq9.d.cts} +16 -8
- package/dist/core/index.cjs +5 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +9 -8
- package/dist/core/index.d.ts +9 -8
- package/dist/core/index.js +4 -4
- package/dist/generate/index.cjs +8 -8
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +28 -18
- package/dist/generate/index.d.ts +28 -18
- package/dist/generate/index.js +7 -7
- package/dist/{index-BMweUlm5.d.cts → index-5VJgKz_Q.d.cts} +6 -2
- package/dist/{index-BHIUyjhZ.d.ts → index-BLuDesVG.d.ts} +20 -13
- package/dist/{index-B9QLziDe.d.cts → index-BV6APVa6.d.cts} +20 -13
- package/dist/{index-CL4b4gUQ.d.ts → index-Bf0Sv8a9.d.ts} +6 -2
- package/dist/{index-Dw2DP7vm.d.ts → index-BkT21EMv.d.cts} +3 -2
- package/dist/{index-BL5N2Oyz.d.cts → index-C7KCaTRz.d.ts} +3 -2
- package/dist/{index-Cy15oTvh.d.ts → index-C9MYbuKS.d.cts} +7 -3
- package/dist/{index-Cy15oTvh.d.cts → index-C9MYbuKS.d.ts} +7 -3
- package/dist/{index-CY-3NVfy.d.cts → index-DVInzCDS.d.cts} +53 -29
- package/dist/{index-9eoQs2C1.d.ts → index-Dg-SAfhz.d.ts} +6 -4
- package/dist/{index-zaZdmnU4.d.ts → index-jgeug9EA.d.ts} +53 -29
- package/dist/{index-cHvbYgJI.d.cts → index-pQihL4z7.d.cts} +6 -4
- package/dist/index.cjs +13 -13
- 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 +12 -12
- package/dist/{masks-Cc7-MzzY.d.ts → masks-BnzXnBj0.d.cts} +3 -2
- package/dist/{masks-pADfTOiD.d.cts → masks-tycmkrgs.d.ts} +3 -2
- package/dist/model/index.cjs +12 -12
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +63 -48
- package/dist/model/index.d.ts +63 -48
- package/dist/model/index.js +11 -11
- package/dist/{positional-DJgs6ltD.d.ts → positional-CscpxtlE.d.cts} +3 -2
- package/dist/{positional-DMZer-FM.d.cts → positional-DLnfUgqG.d.ts} +3 -2
- package/dist/{profile-DLdlIpS9.d.ts → profile-B4WqcM4H.d.ts} +25 -17
- package/dist/{profile-CQMEqdH9.d.cts → profile-BJd1XkPB.d.cts} +25 -17
- package/dist/theory/index.cjs +5 -5
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +18 -14
- package/dist/theory/index.d.ts +18 -14
- package/dist/theory/index.js +4 -4
- package/dist/{types-D-XgxGvp.d.cts → types-7mGFwvMm.d.ts} +45 -22
- package/dist/{types-Dh7C064C.d.ts → types-_ofpb2be.d.cts} +45 -22
- package/package.json +13 -13
- package/dist/chunk-35NBV2FT.cjs.map +0 -1
- package/dist/chunk-3R3QVKY5.cjs.map +0 -1
- package/dist/chunk-4R2LWV7E.cjs.map +0 -1
- package/dist/chunk-76ZBTRG2.cjs.map +0 -1
- package/dist/chunk-7KG3RTYS.js.map +0 -1
- package/dist/chunk-AGOVW7U6.js.map +0 -1
- package/dist/chunk-B2PW6EHN.cjs.map +0 -1
- package/dist/chunk-CGBADIO5.cjs.map +0 -1
- package/dist/chunk-CPMB7P75.js.map +0 -1
- package/dist/chunk-GNVN4PPJ.js.map +0 -1
- package/dist/chunk-IFZDWIKM.cjs.map +0 -1
- package/dist/chunk-IPEF6NWB.js.map +0 -1
- package/dist/chunk-M5RYMAFI.js.map +0 -1
- package/dist/chunk-MLBPXA4Z.js.map +0 -1
- package/dist/chunk-NB25P3EH.js.map +0 -1
- package/dist/chunk-NM7UAMC3.cjs.map +0 -1
- package/dist/chunk-PM4XJE2Y.cjs.map +0 -1
- package/dist/chunk-PUKJP2Z5.js.map +0 -1
- package/dist/chunk-PWC4CRWT.cjs.map +0 -1
- package/dist/chunk-RFIBF2RR.js.map +0 -1
- package/dist/chunk-S5BKZZGM.cjs.map +0 -1
- package/dist/chunk-UMWZ4ZET.js.map +0 -1
- package/dist/chunk-YFA7X2F6.cjs.map +0 -1
- package/dist/chunk-ZBUROTJQ.js.map +0 -1
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
functionOf,
|
|
21
21
|
ioiBinIndex,
|
|
22
22
|
isDiatonic,
|
|
23
|
-
isMinorKey,
|
|
24
23
|
keyLookup,
|
|
25
24
|
melodicSimilarity,
|
|
26
25
|
onsetSimilarity,
|
|
@@ -30,7 +29,7 @@ import {
|
|
|
30
29
|
rhythmLevel,
|
|
31
30
|
romanToChord,
|
|
32
31
|
syncopationSimilarity
|
|
33
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-FJFULJZE.js";
|
|
34
33
|
import {
|
|
35
34
|
ALGORITHM_VERSION,
|
|
36
35
|
LIMBS,
|
|
@@ -42,16 +41,20 @@ import {
|
|
|
42
41
|
includeAt,
|
|
43
42
|
instrumentRange,
|
|
44
43
|
resolveAlgorithmVersion,
|
|
44
|
+
scaleUnitDraw,
|
|
45
45
|
toStringedProfile
|
|
46
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-VL5NXVCN.js";
|
|
47
47
|
import {
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
canonicalNoteOrder,
|
|
49
|
+
createNoteEventIndex,
|
|
50
|
+
soundingIndices
|
|
51
|
+
} from "./chunk-TXFWSU5D.js";
|
|
50
52
|
import {
|
|
51
53
|
chordScales,
|
|
52
54
|
roleOf
|
|
53
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-2G5MCGYU.js";
|
|
54
56
|
import {
|
|
57
|
+
cadencesThroughRaisedSeventh,
|
|
55
58
|
chordPitchClasses,
|
|
56
59
|
chordQualities,
|
|
57
60
|
chordToneRole,
|
|
@@ -63,6 +66,7 @@ import {
|
|
|
63
66
|
fifthPcOf,
|
|
64
67
|
heptatonicFrameOf,
|
|
65
68
|
isForbiddenMelodicLeap,
|
|
69
|
+
isNativeDominantSeventh,
|
|
66
70
|
isScaleTone,
|
|
67
71
|
leadingTonePcOf,
|
|
68
72
|
majorKey,
|
|
@@ -75,7 +79,6 @@ import {
|
|
|
75
79
|
scaleLadderPitch,
|
|
76
80
|
scaleLadderPosition,
|
|
77
81
|
scaleOf,
|
|
78
|
-
scaleSystemOf,
|
|
79
82
|
scaleTonesInDegreeOrder,
|
|
80
83
|
shiftByScaleDegrees,
|
|
81
84
|
soundsDominantSeventh,
|
|
@@ -84,7 +87,7 @@ import {
|
|
|
84
87
|
toChordData,
|
|
85
88
|
toKeyScale,
|
|
86
89
|
tonicizableDegrees
|
|
87
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-H7FVMBUC.js";
|
|
88
91
|
import {
|
|
89
92
|
ARTICULATIONS,
|
|
90
93
|
BEAT_EPS,
|
|
@@ -109,6 +112,7 @@ import {
|
|
|
109
112
|
assertRange,
|
|
110
113
|
assertRecord,
|
|
111
114
|
assertTimeSignature,
|
|
115
|
+
barIndexAt,
|
|
112
116
|
barStartBeat,
|
|
113
117
|
beatsPerBar,
|
|
114
118
|
beatsPerBarAt,
|
|
@@ -130,7 +134,7 @@ import {
|
|
|
130
134
|
toSpelledInterval,
|
|
131
135
|
transposeByInterval,
|
|
132
136
|
transposeNote
|
|
133
|
-
} from "./chunk-
|
|
137
|
+
} from "./chunk-YYVXRV3V.js";
|
|
134
138
|
|
|
135
139
|
// src/generate/context/draw.ts
|
|
136
140
|
var DRAW_METHODS = ["at", "prob", "range", "float"];
|
|
@@ -185,7 +189,7 @@ function drawsFrom(rng, ...prefix) {
|
|
|
185
189
|
);
|
|
186
190
|
}
|
|
187
191
|
assertFiniteNumber(hi - lo, "float span");
|
|
188
|
-
return
|
|
192
|
+
return scaleUnitDraw(at2(...path), lo, hi);
|
|
189
193
|
}
|
|
190
194
|
};
|
|
191
195
|
}
|
|
@@ -410,7 +414,9 @@ var GENRES = Object.freeze([
|
|
|
410
414
|
]);
|
|
411
415
|
var PROVENANCE_BASES = Object.freeze(["idiom", "construction", "traditional"]);
|
|
412
416
|
function sameTs(a, b) {
|
|
413
|
-
|
|
417
|
+
const grouping = a.grouping;
|
|
418
|
+
const theirs = b.grouping;
|
|
419
|
+
return a.numerator === b.numerator && a.denominator === b.denominator && (grouping === void 0 || theirs === void 0 ? grouping === theirs : Array.isArray(grouping) && grouping.length === theirs.length && grouping.every((entry, index) => entry === theirs[index]));
|
|
414
420
|
}
|
|
415
421
|
function fitsQuery(entry, query) {
|
|
416
422
|
const candidate = assertRecord(entry, "vocabulary entry");
|
|
@@ -571,6 +577,14 @@ function sustainsShift(semitones, stepBeats, bpm, difficulty) {
|
|
|
571
577
|
|
|
572
578
|
// src/generate/context/index.ts
|
|
573
579
|
var DEFAULT_SEED = 0;
|
|
580
|
+
function nullish(value) {
|
|
581
|
+
return value === void 0 || value === null;
|
|
582
|
+
}
|
|
583
|
+
function assertPositionalSource(rng) {
|
|
584
|
+
const source = assertRecord(rng, "ctx.rng");
|
|
585
|
+
assertFunction(source.at, "ctx.rng.at");
|
|
586
|
+
return source;
|
|
587
|
+
}
|
|
574
588
|
var VERSION_SEGMENT = "v";
|
|
575
589
|
function resolveContext(input) {
|
|
576
590
|
if (input !== null && typeof input !== "number" && input !== void 0) {
|
|
@@ -581,18 +595,16 @@ function resolveContext(input) {
|
|
|
581
595
|
}
|
|
582
596
|
const ctx = typeof input === "number" ? { seed: input } : input ?? { seed: DEFAULT_SEED };
|
|
583
597
|
const seed = assertInteger(ctx.seed ?? DEFAULT_SEED, "seed", 0, 4294967295);
|
|
584
|
-
const complexity = ctx.complexity
|
|
598
|
+
const complexity = nullish(ctx.complexity) ? {} : assertRecord(ctx.complexity, "ctx.complexity");
|
|
585
599
|
const rhythmic = complexity.rhythmic === void 0 ? void 0 : assertRange(complexity.rhythmic, 0, 1, "complexity rhythmic");
|
|
586
600
|
const harmonic = complexity.harmonic === void 0 ? void 0 : assertRange(complexity.harmonic, 0, 1, "complexity harmonic");
|
|
587
601
|
const ornament2 = complexity.ornament === void 0 ? void 0 : assertRange(complexity.ornament, 0, 1, "complexity ornament");
|
|
588
602
|
const difficulty = complexity.difficulty === void 0 ? void 0 : assertDifficulty(complexity.difficulty);
|
|
589
603
|
const bpm = ctx.bpm === void 0 ? void 0 : assertRange(ctx.bpm, Number.MIN_VALUE, 1e3, "bpm");
|
|
590
604
|
const algorithmVersion = resolveAlgorithmVersion(ctx.algorithmVersion);
|
|
591
|
-
const supplied = ctx.rng;
|
|
592
|
-
const instruments = ctx.instruments;
|
|
593
|
-
const vocabulary = (ctx.vocabulary
|
|
594
|
-
(entry, index) => assertVocabulary(entry, `vocabulary[${index}]`)
|
|
595
|
-
);
|
|
605
|
+
const supplied = ctx.rng ? assertPositionalSource(ctx.rng) : void 0;
|
|
606
|
+
const instruments = nullish(ctx.instruments) ? void 0 : assertRecord(ctx.instruments, "ctx.instruments");
|
|
607
|
+
const vocabulary = (nullish(ctx.vocabulary) ? [] : assertArray(ctx.vocabulary, "ctx.vocabulary")).map((entry, index) => assertVocabulary(entry, `vocabulary[${index}]`));
|
|
596
608
|
return {
|
|
597
609
|
seed,
|
|
598
610
|
rhythmic,
|
|
@@ -601,7 +613,7 @@ function resolveContext(input) {
|
|
|
601
613
|
difficulty,
|
|
602
614
|
bpm,
|
|
603
615
|
algorithmVersion,
|
|
604
|
-
instrument: (part) => instruments
|
|
616
|
+
instrument: (part) => instruments !== void 0 && Object.hasOwn(instruments, part) ? instruments[part] : void 0,
|
|
605
617
|
vocabulary,
|
|
606
618
|
part: (name) => supplied ? drawsFrom(supplied, name, VERSION_SEGMENT, algorithmVersion) : drawsFrom(createPositionalRng(deriveSeed(seed, name, VERSION_SEGMENT, algorithmVersion)))
|
|
607
619
|
};
|
|
@@ -753,6 +765,7 @@ function bassRegister(named, given, octave) {
|
|
|
753
765
|
var BAR_STEPS = 16;
|
|
754
766
|
var BEAT_STEPS = 4;
|
|
755
767
|
var STEP_BEATS = 1 / BEAT_STEPS;
|
|
768
|
+
var FIGURE_RATES = ["straight", "half", "double"];
|
|
756
769
|
function assertGridEvents(events) {
|
|
757
770
|
assertArray(events, "figure events");
|
|
758
771
|
events.forEach((event, index) => {
|
|
@@ -767,6 +780,16 @@ function assertSpanSteps(spanSteps) {
|
|
|
767
780
|
function streamOf(event) {
|
|
768
781
|
return event.limb ?? event.voice ?? "";
|
|
769
782
|
}
|
|
783
|
+
function positionKey(event, step) {
|
|
784
|
+
return `${streamOf(event)}@${step}`;
|
|
785
|
+
}
|
|
786
|
+
function drawKey(event) {
|
|
787
|
+
const stream = `${streamOf(event)}`;
|
|
788
|
+
return stream === "" ? [event.step] : [stream, event.step];
|
|
789
|
+
}
|
|
790
|
+
function halvedStep(step) {
|
|
791
|
+
return Number.isInteger(step) ? Math.round(step / 2) : step / 2;
|
|
792
|
+
}
|
|
770
793
|
var FIGURE_TS = { numerator: 4, denominator: 4 };
|
|
771
794
|
function subdivisionBeats(ts) {
|
|
772
795
|
const pulse2 = pulseBeats(ts);
|
|
@@ -789,46 +812,57 @@ function stepWeight(step, ts) {
|
|
|
789
812
|
function thin(events, amount, ts) {
|
|
790
813
|
const figure = assertGridEvents(events);
|
|
791
814
|
assertRange(amount, 0, 1, "thin amount");
|
|
792
|
-
|
|
815
|
+
return thinFigure(figure, amount, meterAt(0, toMeterData(ts, "ts")));
|
|
816
|
+
}
|
|
817
|
+
function thinFigure(figure, amount, ts) {
|
|
793
818
|
const floor = Math.min(4, Math.floor(amount * 5));
|
|
794
|
-
return figure.filter((event) => stepWeight(event.step,
|
|
819
|
+
return figure.filter((event) => stepWeight(event.step, ts) >= floor);
|
|
795
820
|
}
|
|
796
821
|
function double(events, spanSteps = BAR_STEPS) {
|
|
797
822
|
assertSpanSteps(spanSteps);
|
|
798
823
|
const sorted = [...assertGridEvents(events)].sort((a, b) => a.step - b.step);
|
|
799
|
-
const
|
|
800
|
-
|
|
824
|
+
const nextStep = [];
|
|
825
|
+
const following = /* @__PURE__ */ new Map();
|
|
826
|
+
for (let i = sorted.length - 1; i >= 0; i -= 1) {
|
|
801
827
|
const event = sorted[i];
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
828
|
+
const stream = streamOf(event);
|
|
829
|
+
nextStep[i] = following.get(stream) ?? spanSteps;
|
|
830
|
+
following.set(stream, event.step);
|
|
831
|
+
}
|
|
832
|
+
const out = [];
|
|
833
|
+
sorted.forEach((event, i) => {
|
|
805
834
|
out.push(event);
|
|
806
|
-
const next =
|
|
835
|
+
const next = nextStep[i] ?? spanSteps;
|
|
807
836
|
const mid = event.step + (next - event.step) / 2;
|
|
808
837
|
if (Number.isInteger(mid) && mid > event.step && mid < spanSteps) {
|
|
809
838
|
out.push({ ...event, step: mid, velocity: event.velocity * ADDED_NOTE_VELOCITY });
|
|
810
839
|
}
|
|
811
|
-
}
|
|
812
|
-
return out;
|
|
840
|
+
});
|
|
841
|
+
return out.sort((a, b) => a.step - b.step);
|
|
813
842
|
}
|
|
814
843
|
var ADDED_NOTE_VELOCITY = 0.75;
|
|
815
844
|
function halfTime(events, spanSteps = BAR_STEPS) {
|
|
816
845
|
assertSpanSteps(spanSteps);
|
|
817
|
-
return assertGridEvents(events)
|
|
846
|
+
return halfTimeFigure(assertGridEvents(events), spanSteps);
|
|
847
|
+
}
|
|
848
|
+
function halfTimeFigure(figure, spanSteps) {
|
|
849
|
+
return figure.map((event) => ({ ...event, step: event.step * 2 })).filter((event) => event.step < spanSteps);
|
|
818
850
|
}
|
|
819
851
|
function doubleTime(events, spanSteps = BAR_STEPS) {
|
|
820
852
|
assertSpanSteps(spanSteps);
|
|
821
|
-
assertGridEvents(events);
|
|
853
|
+
return doubleTimeFigure(assertGridEvents(events), spanSteps);
|
|
854
|
+
}
|
|
855
|
+
function doubleTimeFigure(figure, spanSteps) {
|
|
822
856
|
const merged = /* @__PURE__ */ new Map();
|
|
823
857
|
const place = (event, step, velocity) => {
|
|
824
|
-
const key =
|
|
858
|
+
const key = positionKey(event, step);
|
|
825
859
|
const held = merged.get(key);
|
|
826
860
|
if (held === void 0 || velocity > held.velocity) {
|
|
827
861
|
merged.set(key, { ...event, step, velocity });
|
|
828
862
|
}
|
|
829
863
|
};
|
|
830
|
-
for (const event of
|
|
831
|
-
const step =
|
|
864
|
+
for (const event of figure) {
|
|
865
|
+
const step = halvedStep(event.step);
|
|
832
866
|
place(event, step, event.velocity);
|
|
833
867
|
const repeated = step + spanSteps / 2;
|
|
834
868
|
if (repeated < spanSteps) {
|
|
@@ -845,14 +879,17 @@ function syncopate(events, amount, draw, ...path) {
|
|
|
845
879
|
const dial = asked === void 0 ? amount : asked.amount;
|
|
846
880
|
const signature = meterAt(0, toMeterData(asked?.ts ?? FIGURE_TS, "ts"));
|
|
847
881
|
assertRange(dial, 0, 1, "syncopate amount");
|
|
848
|
-
|
|
882
|
+
return syncopateFigure(figure, dial, signature, sampler, path);
|
|
883
|
+
}
|
|
884
|
+
function syncopateFigure(figure, dial, ts, sampler, path) {
|
|
885
|
+
const occupied = new Set(figure.map((event) => positionKey(event, event.step)));
|
|
849
886
|
const out = [...figure];
|
|
850
887
|
for (const event of figure) {
|
|
851
888
|
const anticipated = event.step - 1;
|
|
852
|
-
if (stepWeight(event.step,
|
|
889
|
+
if (stepWeight(event.step, ts) < 2 || anticipated < 1 || occupied.has(positionKey(event, anticipated))) {
|
|
853
890
|
continue;
|
|
854
891
|
}
|
|
855
|
-
if (!sampler.prob(dial, ...path, "syncopate", event
|
|
892
|
+
if (!sampler.prob(dial, ...path, "syncopate", ...drawKey(event))) {
|
|
856
893
|
continue;
|
|
857
894
|
}
|
|
858
895
|
out.push({ ...event, step: anticipated, velocity: event.velocity * ADDED_NOTE_VELOCITY });
|
|
@@ -865,35 +902,47 @@ function ornamentBy(events, isOrnament, amount, draw, ...path) {
|
|
|
865
902
|
const sampler = assertDraw(draw);
|
|
866
903
|
assertSeedPath(path);
|
|
867
904
|
assertRange(amount, 0, 1, "ornament amount");
|
|
905
|
+
return ornamentFigure(figure, marks, amount, sampler, path);
|
|
906
|
+
}
|
|
907
|
+
function ornamentFigure(figure, marks, amount, sampler, path) {
|
|
868
908
|
return figure.filter(
|
|
869
|
-
(event) => !marks(event) || sampler.prob(amount, ...path, "ornament", event
|
|
909
|
+
(event) => !marks(event) || sampler.prob(amount, ...path, "ornament", ...drawKey(event))
|
|
870
910
|
);
|
|
871
911
|
}
|
|
872
912
|
var NEUTRAL_RHYTHMIC = 0.5;
|
|
873
913
|
function deform(events, opts, draw, ...path) {
|
|
874
914
|
const figure = assertGridEvents(events);
|
|
875
915
|
const asked = assertRecord(opts, "opts");
|
|
876
|
-
assertDraw(draw);
|
|
916
|
+
const sampler = assertDraw(draw);
|
|
877
917
|
assertSeedPath(path);
|
|
878
918
|
const spanSteps = assertSpanSteps(asked.spanSteps === void 0 ? BAR_STEPS : asked.spanSteps);
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
919
|
+
return deformFigure(figure, spanSteps, resolveDials(asked), sampler, path);
|
|
920
|
+
}
|
|
921
|
+
function resolveDials(asked) {
|
|
922
|
+
const rate = assertOneOf(asked.rate ?? "straight", FIGURE_RATES, "rate");
|
|
923
|
+
const rhythmic = asked.rhythmic === void 0 ? void 0 : assertRange(asked.rhythmic, 0, 1, "complexity rhythmic");
|
|
924
|
+
const ts = meterAt(0, toMeterData(asked.ts ?? FIGURE_TS, "ts"));
|
|
925
|
+
const ornament2 = asked.ornament === void 0 ? void 0 : assertRange(asked.ornament, 0, 1, "ornament amount");
|
|
926
|
+
const isOrnament = asked.isOrnament ? assertFunction(asked.isOrnament, "opts.isOrnament") : void 0;
|
|
927
|
+
return { rate, rhythmic, ts, ornament: ornament2, isOrnament };
|
|
928
|
+
}
|
|
929
|
+
function deformFigure(figure, spanSteps, dials, sampler, path) {
|
|
930
|
+
let out = dials.rate === "half" ? halfTimeFigure(figure, spanSteps) : dials.rate === "double" ? doubleTimeFigure(figure, spanSteps) : [...figure];
|
|
931
|
+
const { rhythmic } = dials;
|
|
932
|
+
if (rhythmic !== void 0 && rhythmic < NEUTRAL_RHYTHMIC) {
|
|
933
|
+
out = thinFigure(out, (NEUTRAL_RHYTHMIC - rhythmic) / NEUTRAL_RHYTHMIC, dials.ts);
|
|
934
|
+
} else if (rhythmic !== void 0 && rhythmic > NEUTRAL_RHYTHMIC) {
|
|
935
|
+
out = syncopateFigure(
|
|
936
|
+
out,
|
|
937
|
+
(rhythmic - NEUTRAL_RHYTHMIC) / NEUTRAL_RHYTHMIC,
|
|
938
|
+
dials.ts,
|
|
939
|
+
sampler,
|
|
940
|
+
path
|
|
941
|
+
);
|
|
893
942
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
out =
|
|
943
|
+
const { ornament: ornament2, isOrnament } = dials;
|
|
944
|
+
if (ornament2 !== void 0 && isOrnament !== void 0) {
|
|
945
|
+
out = ornamentFigure(out, (event) => isOrnament(event), ornament2, sampler, path);
|
|
897
946
|
}
|
|
898
947
|
return out.sort((a, b) => a.step - b.step);
|
|
899
948
|
}
|
|
@@ -1010,6 +1059,32 @@ function isLickMaterial(material) {
|
|
|
1010
1059
|
const candidate = material;
|
|
1011
1060
|
return typeof candidate === "object" && candidate !== null && typeof candidate.lengthSteps === "number" && Array.isArray(candidate.notes);
|
|
1012
1061
|
}
|
|
1062
|
+
function assertLickMaterial(material, label) {
|
|
1063
|
+
material.notes.forEach((given, index) => {
|
|
1064
|
+
const name = `${label} notes[${index}]`;
|
|
1065
|
+
const lickNote = assertRecord(given, name);
|
|
1066
|
+
assertDegree(lickNote.degree, `${name}.degree`);
|
|
1067
|
+
assertFiniteNumber(lickNote.step, `${name}.step`);
|
|
1068
|
+
assertFiniteNumber(lickNote.velocity, `${name}.velocity`);
|
|
1069
|
+
if (lickNote.alter !== void 0) {
|
|
1070
|
+
assertInteger(lickNote.alter, `${name}.alter`);
|
|
1071
|
+
}
|
|
1072
|
+
if (lickNote.lengthSteps !== void 0) {
|
|
1073
|
+
assertFiniteNumber(lickNote.lengthSteps, `${name}.lengthSteps`);
|
|
1074
|
+
}
|
|
1075
|
+
if (lickNote.articulation !== void 0) {
|
|
1076
|
+
assertOneOf(lickNote.articulation, ARTICULATIONS, `${name}.articulation`);
|
|
1077
|
+
}
|
|
1078
|
+
for (const field of ["voice", "limb"]) {
|
|
1079
|
+
const value = lickNote[field];
|
|
1080
|
+
if (value !== void 0 && typeof value !== "string") {
|
|
1081
|
+
throw new InvalidInputError(
|
|
1082
|
+
`${name}.${field} must be a string; received ${describeRejected(value)}`
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1013
1088
|
function note(degree, step, velocity, extra) {
|
|
1014
1089
|
return { degree, step, velocity, ...extra };
|
|
1015
1090
|
}
|
|
@@ -1237,10 +1312,11 @@ function placeLicks(timeline, key, opts) {
|
|
|
1237
1312
|
const supplied = asked.difficulty === void 0 ? void 0 : assertDifficulty(asked.difficulty, "lick difficulty");
|
|
1238
1313
|
const difficulty = resolved.difficulty ?? supplied;
|
|
1239
1314
|
const { instrument, low } = bassRegister(resolved.instrument("bass"), asked.instrument, octave);
|
|
1240
|
-
const
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
);
|
|
1315
|
+
const callerLicks = vocabularyOfKind(resolved.vocabulary, isLickMaterial);
|
|
1316
|
+
callerLicks.forEach((entry, index) => {
|
|
1317
|
+
assertLickMaterial(entry.material, `lick vocabulary[${index}]`);
|
|
1318
|
+
});
|
|
1319
|
+
const dictionary = mergeVocabulary(BASS_LICKS, callerLicks);
|
|
1244
1320
|
const playable = instrument === void 0 ? {} : { articulations: instrument.articulations };
|
|
1245
1321
|
const raw = [];
|
|
1246
1322
|
for (let index = 0; index < segments.length; index += 1) {
|
|
@@ -1672,9 +1748,10 @@ function imitate(lead, opts) {
|
|
|
1672
1748
|
let offset = 0;
|
|
1673
1749
|
if (invert) {
|
|
1674
1750
|
if (tonal) {
|
|
1675
|
-
const
|
|
1676
|
-
|
|
1677
|
-
|
|
1751
|
+
const axis = scaleLadderPosition(pivot, key);
|
|
1752
|
+
const here = scaleLadderPosition(pitch - axis.offset, key);
|
|
1753
|
+
pitch = scaleLadderPitch(2 * axis.rung - here.rung, key);
|
|
1754
|
+
offset = axis.offset - here.offset;
|
|
1678
1755
|
} else {
|
|
1679
1756
|
pitch = 2 * pivot - pitch;
|
|
1680
1757
|
}
|
|
@@ -1880,7 +1957,7 @@ function generateCounterMelody(opts) {
|
|
|
1880
1957
|
throw new InvalidInputError("countermelody needs a timeline or a chordAt callback");
|
|
1881
1958
|
}
|
|
1882
1959
|
const chordChangeBeats = timeline === void 0 ? opts.chordChangeBeats ?? [] : timeline.segments.flatMap((segment) => [segment.startBeat, segment.endBeat]);
|
|
1883
|
-
const register = opts.register ?? "below";
|
|
1960
|
+
const register = assertOneOf(opts.register ?? "below", ["above", "below"], "register");
|
|
1884
1961
|
assertNoteEvents(opts.melody, "countermelody melody", { allowNonPositiveDuration: true });
|
|
1885
1962
|
const melody = createNoteEventIndex(soundingNotesOnly(opts.melody), {
|
|
1886
1963
|
tieBreak: register === "below" ? "highest" : "lowest"
|
|
@@ -2113,13 +2190,14 @@ function denser(level) {
|
|
|
2113
2190
|
}
|
|
2114
2191
|
return "sixteenth";
|
|
2115
2192
|
}
|
|
2193
|
+
var LEVEL_ORDER = ["quarter", "eighth", "sixteenth"];
|
|
2116
2194
|
function hiHatStep(level) {
|
|
2117
2195
|
if (level === "sixteenth") {
|
|
2118
2196
|
return 0.25;
|
|
2119
2197
|
}
|
|
2120
2198
|
return level === "eighth" ? 0.5 : 1;
|
|
2121
2199
|
}
|
|
2122
|
-
function
|
|
2200
|
+
function getHiHatBar(section, style, rhythmic, bpm, draw, difficulty, bar2) {
|
|
2123
2201
|
const allow16th = bpm < HH_16TH_BPM_THRESHOLD;
|
|
2124
2202
|
let base = "eighth";
|
|
2125
2203
|
let dialed = true;
|
|
@@ -2159,22 +2237,41 @@ function getHiHatLevel(section, style, rhythmic, bpm, draw, difficulty, bar2) {
|
|
|
2159
2237
|
break;
|
|
2160
2238
|
}
|
|
2161
2239
|
}
|
|
2240
|
+
const playable = (proposed) => {
|
|
2241
|
+
let level2 = !allow16th && proposed === "sixteenth" ? "eighth" : proposed;
|
|
2242
|
+
while (level2 !== "quarter" && !sustainsStrokes(hiHatStep(level2), bpm, difficulty)) {
|
|
2243
|
+
level2 = sparser(level2);
|
|
2244
|
+
}
|
|
2245
|
+
return level2;
|
|
2246
|
+
};
|
|
2162
2247
|
let level = base;
|
|
2248
|
+
let thinnedUpTo = Number.NEGATIVE_INFINITY;
|
|
2249
|
+
let filledAbove = Number.POSITIVE_INFINITY;
|
|
2163
2250
|
if (dialed) {
|
|
2164
|
-
|
|
2251
|
+
const thin2 = draw.at("hhThin", bar2);
|
|
2252
|
+
if (thin2 >= 2 * rhythmic) {
|
|
2165
2253
|
level = sparser(level);
|
|
2166
2254
|
}
|
|
2167
|
-
|
|
2255
|
+
const fill2 = draw.at("hhFill", bar2);
|
|
2256
|
+
if (fill2 < 2 * rhythmic - 1) {
|
|
2168
2257
|
level = denser(level);
|
|
2169
2258
|
}
|
|
2259
|
+
thinnedUpTo = thin2 / 2;
|
|
2260
|
+
filledAbove = (fill2 + 1) / 2;
|
|
2170
2261
|
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2262
|
+
const rank = (candidate) => LEVEL_ORDER.indexOf(candidate);
|
|
2263
|
+
const sparsest = rank(playable(dialed ? sparser(base) : base));
|
|
2264
|
+
const middle = rank(playable(base));
|
|
2265
|
+
const densest = rank(playable(dialed ? denser(base) : base));
|
|
2266
|
+
const entryOf = (grid) => sparsest >= rank(grid) ? Number.NEGATIVE_INFINITY : middle >= rank(grid) ? thinnedUpTo : densest >= rank(grid) ? filledAbove : Number.POSITIVE_INFINITY;
|
|
2267
|
+
return {
|
|
2268
|
+
level: playable(level),
|
|
2269
|
+
entries: {
|
|
2270
|
+
quarter: entryOf("quarter"),
|
|
2271
|
+
eighth: entryOf("eighth"),
|
|
2272
|
+
sixteenth: entryOf("sixteenth")
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2178
2275
|
}
|
|
2179
2276
|
function hiHatVelocityMultiplier(sixteenth, jitter) {
|
|
2180
2277
|
switch (sixteenth) {
|
|
@@ -2385,11 +2482,12 @@ function generateKickForBeat(ctx, sec, kick) {
|
|
|
2385
2482
|
const sixteenths = kick.length / 4;
|
|
2386
2483
|
for (let step = 0; step < sixteenths; step += 1) {
|
|
2387
2484
|
const index = ctx.beat * sixteenths + step;
|
|
2388
|
-
|
|
2485
|
+
const entry = kick[index];
|
|
2486
|
+
if (entry === void 0) {
|
|
2389
2487
|
continue;
|
|
2390
2488
|
}
|
|
2391
2489
|
if (step === 0) {
|
|
2392
|
-
ctx.track.add(GM.BD, ctx.beatTick, EIGHTH, ctx.velocity);
|
|
2490
|
+
ctx.track.add(GM.BD, ctx.beatTick, EIGHTH, ctx.velocity, void 0, entry);
|
|
2393
2491
|
continue;
|
|
2394
2492
|
}
|
|
2395
2493
|
const offBeat = step % 2 === 1;
|
|
@@ -2397,7 +2495,7 @@ function generateKickForBeat(ctx, sec, kick) {
|
|
|
2397
2495
|
continue;
|
|
2398
2496
|
}
|
|
2399
2497
|
const tick = swing16(ctx.beatTick + step * SIXTEENTH, sec, ctx.swingAmount);
|
|
2400
|
-
ctx.track.add(GM.BD, tick, offBeat ? SIXTEENTH : EIGHTH, ctx.velocity * 0.85);
|
|
2498
|
+
ctx.track.add(GM.BD, tick, offBeat ? SIXTEENTH : EIGHTH, ctx.velocity * 0.85, void 0, entry);
|
|
2401
2499
|
}
|
|
2402
2500
|
}
|
|
2403
2501
|
function roleBackbeatWeight(role) {
|
|
@@ -2450,16 +2548,19 @@ function generateGhostNotesForBeat(ctx, sec) {
|
|
|
2450
2548
|
return;
|
|
2451
2549
|
}
|
|
2452
2550
|
const positions = selectGhostPositions(sec.ghostMood);
|
|
2453
|
-
|
|
2551
|
+
const ghostDensity = getGhostDensity(sec.ghostMood, ctx.section, ctx.bpm);
|
|
2552
|
+
let ghostProb = leanedBy(ghostDensity, sec.ornament);
|
|
2454
2553
|
if (sec.ghostBoost) {
|
|
2455
2554
|
ghostProb = Math.min(1, ghostProb * 1.4);
|
|
2456
2555
|
}
|
|
2457
2556
|
for (const pos of positions) {
|
|
2458
2557
|
const sixteenthInBeat = pos === "e" ? 1 : 3;
|
|
2459
2558
|
const posProb = getGhostProbabilityAtPosition(ctx.beat, sixteenthInBeat, leadIns);
|
|
2460
|
-
|
|
2559
|
+
const at2 = ctx.draw.at("ghost", ctx.bar, ctx.beat, pos);
|
|
2560
|
+
if (!(at2 < ghostProb * posProb)) {
|
|
2461
2561
|
continue;
|
|
2462
2562
|
}
|
|
2563
|
+
const entry = sec.ornamentFollowsRhythmic ? at2 / (posProb * (sec.ghostBoost ? 1.4 : 1) * ghostDensity) - 0.5 : Number.NEGATIVE_INFINITY;
|
|
2463
2564
|
const variation = ctx.draw.float(0.85, 1.15, "ghostVelocity", ctx.bar, ctx.beat, pos);
|
|
2464
2565
|
const ghostBase = getGhostVelocity(ctx.section, ctx.beat / 2);
|
|
2465
2566
|
let ghostVel = ctx.velocity * ghostBase * variation;
|
|
@@ -2468,7 +2569,7 @@ function generateGhostNotesForBeat(ctx, sec) {
|
|
|
2468
2569
|
}
|
|
2469
2570
|
const offset = pos === "e" ? SIXTEENTH : 3 * SIXTEENTH;
|
|
2470
2571
|
const tick = swing16(ctx.beatTick + offset, sec, ctx.swingAmount);
|
|
2471
|
-
ctx.track.add(GM.SD, tick, SIXTEENTH, ghostVel);
|
|
2572
|
+
ctx.track.add(GM.SD, tick, SIXTEENTH, ghostVel, void 0, entry);
|
|
2472
2573
|
}
|
|
2473
2574
|
}
|
|
2474
2575
|
function generatePreChorusBuildup(ctx, sec, isSectionLastBar) {
|
|
@@ -2526,17 +2627,25 @@ function generateHiHatForBeat(ctx, sec) {
|
|
|
2526
2627
|
const allowsOpenHiHat = sec.role !== "ambient" && sec.role !== "minimal";
|
|
2527
2628
|
const isDynamicOpen = ctx.barHasOpenHh && ctx.beat === ctx.openHhBeat && !usesOffbeatOpenHiHat(sec.style);
|
|
2528
2629
|
const dm = sec.densityMult;
|
|
2630
|
+
const beatEntry = ctx.section === "intro" && ctx.beat !== 0 ? ctx.hhEntries.eighth : ctx.hhEntries.quarter;
|
|
2529
2631
|
if (ctx.hhLevel === "quarter") {
|
|
2530
2632
|
if (ctx.section === "intro" && ctx.beat !== 0) {
|
|
2531
2633
|
return;
|
|
2532
2634
|
}
|
|
2533
2635
|
const hhVel = Math.max(20, ctx.velocity * dm * 0.75 * typeMult);
|
|
2534
2636
|
if (allowsOpenHiHat && isDynamicOpen) {
|
|
2535
|
-
ctx.track.add(GM.OHH, ctx.beatTick, EIGHTH, hhVel + OHH_VEL_BOOST);
|
|
2637
|
+
ctx.track.add(GM.OHH, ctx.beatTick, EIGHTH, hhVel + OHH_VEL_BOOST, void 0, beatEntry);
|
|
2536
2638
|
} else if (allowsOpenHiHat && opensOnDownbeat(ctx, sec)) {
|
|
2537
|
-
ctx.track.add(
|
|
2639
|
+
ctx.track.add(
|
|
2640
|
+
hiHatNote("open"),
|
|
2641
|
+
ctx.beatTick,
|
|
2642
|
+
EIGHTH,
|
|
2643
|
+
Math.max(20, hhVel * 1.1),
|
|
2644
|
+
void 0,
|
|
2645
|
+
beatEntry
|
|
2646
|
+
);
|
|
2538
2647
|
} else {
|
|
2539
|
-
ctx.track.add(hhInstrument, ctx.beatTick, EIGHTH, hhVel);
|
|
2648
|
+
ctx.track.add(hhInstrument, ctx.beatTick, EIGHTH, hhVel, void 0, beatEntry);
|
|
2540
2649
|
}
|
|
2541
2650
|
return;
|
|
2542
2651
|
}
|
|
@@ -2546,22 +2655,37 @@ function generateHiHatForBeat(ctx, sec) {
|
|
|
2546
2655
|
if (eighth === 1) {
|
|
2547
2656
|
hhTick = swing16(hhTick, sec, ctx.swingAmount);
|
|
2548
2657
|
}
|
|
2658
|
+
const entry = eighth === 0 ? beatEntry : ctx.hhEntries.eighth;
|
|
2549
2659
|
if (ctx.section === "intro" && eighth === 1) {
|
|
2550
2660
|
if (sec.useFootHh && ctx.beat % 2 === 0) {
|
|
2551
|
-
ctx.track.add(
|
|
2661
|
+
ctx.track.add(
|
|
2662
|
+
GM.FHH,
|
|
2663
|
+
hhTick,
|
|
2664
|
+
EIGHTH,
|
|
2665
|
+
footHiHatVelocity(ctx.draw, ctx.bar, ctx.beat),
|
|
2666
|
+
void 0,
|
|
2667
|
+
entry
|
|
2668
|
+
);
|
|
2552
2669
|
}
|
|
2553
2670
|
continue;
|
|
2554
2671
|
}
|
|
2555
2672
|
const hhVel = Math.max(20, ctx.velocity * dm * typeMult * (eighth === 0 ? 0.9 : 0.65));
|
|
2556
2673
|
if (isDynamicOpen && allowsOpenHiHat && eighth === 0) {
|
|
2557
|
-
ctx.track.add(GM.OHH, hhTick, EIGHTH, hhVel + OHH_VEL_BOOST);
|
|
2674
|
+
ctx.track.add(GM.OHH, hhTick, EIGHTH, hhVel + OHH_VEL_BOOST, void 0, entry);
|
|
2558
2675
|
continue;
|
|
2559
2676
|
}
|
|
2560
2677
|
const useOpen = eighth === 0 ? opensOnDownbeat(ctx, sec) : opensOffbeat(ctx, sec);
|
|
2561
2678
|
if (useOpen && allowsOpenHiHat) {
|
|
2562
|
-
ctx.track.add(
|
|
2679
|
+
ctx.track.add(
|
|
2680
|
+
hiHatNote("open"),
|
|
2681
|
+
hhTick,
|
|
2682
|
+
EIGHTH,
|
|
2683
|
+
Math.max(20, hhVel * 1.1),
|
|
2684
|
+
void 0,
|
|
2685
|
+
entry
|
|
2686
|
+
);
|
|
2563
2687
|
} else {
|
|
2564
|
-
ctx.track.add(hhInstrument, hhTick, EIGHTH / 2, hhVel);
|
|
2688
|
+
ctx.track.add(hhInstrument, hhTick, EIGHTH / 2, hhVel, void 0, entry);
|
|
2565
2689
|
}
|
|
2566
2690
|
}
|
|
2567
2691
|
return;
|
|
@@ -2576,26 +2700,41 @@ function generateHiHatForBeat(ctx, sec) {
|
|
|
2576
2700
|
ctx.draw.float(0.95, 1.05, "hatVelocity", ctx.bar, ctx.beat, sixteenth)
|
|
2577
2701
|
);
|
|
2578
2702
|
const hhVel = Math.max(20, ctx.velocity * dm * typeMult * metricVel);
|
|
2703
|
+
const entry = sixteenth === 0 ? beatEntry : sixteenth === 2 ? ctx.hhEntries.eighth : ctx.hhEntries.sixteenth;
|
|
2579
2704
|
if (isDynamicOpen && allowsOpenHiHat && sixteenth === 0) {
|
|
2580
|
-
ctx.track.add(GM.OHH, hhTick, SIXTEENTH, hhVel + OHH_VEL_BOOST);
|
|
2705
|
+
ctx.track.add(GM.OHH, hhTick, SIXTEENTH, hhVel + OHH_VEL_BOOST, void 0, entry);
|
|
2581
2706
|
continue;
|
|
2582
2707
|
}
|
|
2583
2708
|
if (allowsOpenHiHat && sixteenth === 0 && opensOnDownbeat(ctx, sec)) {
|
|
2584
|
-
ctx.track.add(
|
|
2709
|
+
ctx.track.add(
|
|
2710
|
+
hiHatNote("open"),
|
|
2711
|
+
hhTick,
|
|
2712
|
+
SIXTEENTH,
|
|
2713
|
+
Math.max(20, hhVel * 1.1),
|
|
2714
|
+
void 0,
|
|
2715
|
+
entry
|
|
2716
|
+
);
|
|
2585
2717
|
continue;
|
|
2586
2718
|
}
|
|
2587
2719
|
if (allowsOpenHiHat && sixteenth === 2 && opensOffbeat(ctx, sec)) {
|
|
2588
|
-
ctx.track.add(
|
|
2720
|
+
ctx.track.add(
|
|
2721
|
+
hiHatNote("open"),
|
|
2722
|
+
hhTick,
|
|
2723
|
+
SIXTEENTH,
|
|
2724
|
+
Math.max(20, hhVel * 1.1),
|
|
2725
|
+
void 0,
|
|
2726
|
+
entry
|
|
2727
|
+
);
|
|
2589
2728
|
continue;
|
|
2590
2729
|
}
|
|
2591
2730
|
if (allowsOpenHiHat && ctx.beat === 3 && sixteenth === 3) {
|
|
2592
2731
|
const openProb = Math.max(0.1, Math.min(0.4, 30 / ctx.bpm));
|
|
2593
2732
|
if (ctx.draw.prob(openProb, "openHat", ctx.bar, ctx.beat, sixteenth)) {
|
|
2594
|
-
ctx.track.add(GM.OHH, hhTick, SIXTEENTH, Math.max(20, hhVel * 1.2));
|
|
2733
|
+
ctx.track.add(GM.OHH, hhTick, SIXTEENTH, Math.max(20, hhVel * 1.2), void 0, entry);
|
|
2595
2734
|
continue;
|
|
2596
2735
|
}
|
|
2597
2736
|
}
|
|
2598
|
-
ctx.track.add(hhInstrument, hhTick, SIXTEENTH / 2, hhVel);
|
|
2737
|
+
ctx.track.add(hhInstrument, hhTick, SIXTEENTH / 2, hhVel, void 0, entry);
|
|
2599
2738
|
}
|
|
2600
2739
|
}
|
|
2601
2740
|
|
|
@@ -2622,7 +2761,7 @@ function bjorklund(pulses, steps) {
|
|
|
2622
2761
|
}
|
|
2623
2762
|
let head = Array.from({ length: pulses }, () => [true]);
|
|
2624
2763
|
let remainder = Array.from({ length: steps - pulses }, () => [false]);
|
|
2625
|
-
|
|
2764
|
+
do {
|
|
2626
2765
|
const count = Math.min(head.length, remainder.length);
|
|
2627
2766
|
const next = [];
|
|
2628
2767
|
for (let i = 0; i < count; i += 1) {
|
|
@@ -2630,7 +2769,7 @@ function bjorklund(pulses, steps) {
|
|
|
2630
2769
|
}
|
|
2631
2770
|
remainder = head.length > count ? head.slice(count) : remainder.slice(count);
|
|
2632
2771
|
head = next;
|
|
2633
|
-
}
|
|
2772
|
+
} while (remainder.length > 1);
|
|
2634
2773
|
return [...head.flat(), ...remainder.flat()];
|
|
2635
2774
|
}
|
|
2636
2775
|
|
|
@@ -2650,6 +2789,7 @@ var FILL_TYPES = Object.freeze([
|
|
|
2650
2789
|
"flamsAndDrags",
|
|
2651
2790
|
"halfTimeFill"
|
|
2652
2791
|
]);
|
|
2792
|
+
var FILL_VELOCITY_BASES = Object.freeze(["fill", "accent"]);
|
|
2653
2793
|
var S = SIXTEENTH;
|
|
2654
2794
|
var E = EIGHTH;
|
|
2655
2795
|
function fill(scale, offset) {
|
|
@@ -2773,6 +2913,29 @@ var FILL_ARCHETYPES = deepFreeze({
|
|
|
2773
2913
|
function isFillArchetype(material) {
|
|
2774
2914
|
return typeof material === "object" && material !== null && Array.isArray(material.atBeat);
|
|
2775
2915
|
}
|
|
2916
|
+
function assertFillArchetype(material, label) {
|
|
2917
|
+
material.atBeat.forEach((beat, beatIndex) => {
|
|
2918
|
+
const strokes = assertArray(beat, `${label} atBeat[${beatIndex}]`);
|
|
2919
|
+
strokes.forEach((given, strokeIndex) => {
|
|
2920
|
+
const name = `${label} atBeat[${beatIndex}][${strokeIndex}]`;
|
|
2921
|
+
const stroke = assertRecord(given, name);
|
|
2922
|
+
assertMidiPitch(stroke.voice, `${name}.voice`);
|
|
2923
|
+
assertFiniteNumber(stroke.offset, `${name}.offset`);
|
|
2924
|
+
assertFiniteNumber(stroke.duration, `${name}.duration`);
|
|
2925
|
+
const velocity = assertRecord(stroke.velocity, `${name}.velocity`);
|
|
2926
|
+
assertOneOf(velocity.base, FILL_VELOCITY_BASES, `${name}.velocity.base`);
|
|
2927
|
+
if (velocity.scale !== void 0) {
|
|
2928
|
+
assertFiniteNumber(velocity.scale, `${name}.velocity.scale`);
|
|
2929
|
+
}
|
|
2930
|
+
if (velocity.offset !== void 0) {
|
|
2931
|
+
assertRange(velocity.offset, -127, 127, `${name}.velocity.offset`);
|
|
2932
|
+
}
|
|
2933
|
+
if (stroke.articulation !== void 0) {
|
|
2934
|
+
assertOneOf(stroke.articulation, ARTICULATIONS, `${name}.articulation`);
|
|
2935
|
+
}
|
|
2936
|
+
});
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2776
2939
|
function fillArchetypeFor(id, supplied) {
|
|
2777
2940
|
return supplied?.get(id) ?? FILL_ARCHETYPES[id];
|
|
2778
2941
|
}
|
|
@@ -2947,6 +3110,8 @@ var HitList = class {
|
|
|
2947
3110
|
hits = [];
|
|
2948
3111
|
/** Voices already struck at each quantized beat. */
|
|
2949
3112
|
#onsets = /* @__PURE__ */ new Map();
|
|
3113
|
+
/** The rhythmic-dial setting each onset enters at, by onset key; absent is always. */
|
|
3114
|
+
#entries = /* @__PURE__ */ new Map();
|
|
2950
3115
|
/**
|
|
2951
3116
|
* Append a hit, clamping velocity and dropping non-positive positions.
|
|
2952
3117
|
*
|
|
@@ -2954,12 +3119,23 @@ var HitList = class {
|
|
|
2954
3119
|
* sounds once however many voices ask for it, and a second entry at the same
|
|
2955
3120
|
* pitch and position is a note-on a reader answers with no note-off, heard as
|
|
2956
3121
|
* a hung or cut stroke rather than as a louder one.
|
|
3122
|
+
*
|
|
3123
|
+
* `entry` is the rhythmic-dial setting above which the stroke sounds, for a
|
|
3124
|
+
* stroke the dial adds; a stroke asked for twice sounds from the lower one.
|
|
2957
3125
|
*/
|
|
2958
|
-
add(pitch, startBeat, durationBeat, velocity, articulation) {
|
|
3126
|
+
add(pitch, startBeat, durationBeat, velocity, articulation, entry = Number.NEGATIVE_INFINITY) {
|
|
2959
3127
|
const beat = Math.max(0, startBeat);
|
|
3128
|
+
const key = onsetKey(pitch, beat);
|
|
3129
|
+
const held = this.#entries.get(key);
|
|
2960
3130
|
if (this.hasOnset(pitch, beat)) {
|
|
3131
|
+
if (held !== void 0 && entry < held) {
|
|
3132
|
+
this.#entries.set(key, entry);
|
|
3133
|
+
}
|
|
2961
3134
|
return;
|
|
2962
3135
|
}
|
|
3136
|
+
if (entry !== Number.NEGATIVE_INFINITY) {
|
|
3137
|
+
this.#entries.set(key, entry);
|
|
3138
|
+
}
|
|
2963
3139
|
const hit2 = {
|
|
2964
3140
|
pitch,
|
|
2965
3141
|
startBeat: beat,
|
|
@@ -2977,6 +3153,13 @@ var HitList = class {
|
|
|
2977
3153
|
this.#onsets.set(beatSlot(beat), /* @__PURE__ */ new Set([pitch]));
|
|
2978
3154
|
}
|
|
2979
3155
|
}
|
|
3156
|
+
/**
|
|
3157
|
+
* The rhythmic-dial setting above which a written stroke sounds: minus
|
|
3158
|
+
* infinity for one that sounds at every setting.
|
|
3159
|
+
*/
|
|
3160
|
+
entryOf(pitch, startBeat) {
|
|
3161
|
+
return this.#entries.get(onsetKey(pitch, startBeat)) ?? Number.NEGATIVE_INFINITY;
|
|
3162
|
+
}
|
|
2980
3163
|
/** Whether a hit of this pitch already starts exactly at `startBeat`. */
|
|
2981
3164
|
hasOnset(pitch, startBeat) {
|
|
2982
3165
|
return this.#onsets.get(beatSlot(startBeat))?.has(pitch) ?? false;
|
|
@@ -3073,17 +3256,14 @@ var KICK_FIGURES = deepFreeze({
|
|
|
3073
3256
|
],
|
|
3074
3257
|
standard: DEFAULT_FIGURE
|
|
3075
3258
|
});
|
|
3076
|
-
function emptyPattern() {
|
|
3077
|
-
return Array.from({ length: KICK_STEPS }, () => false);
|
|
3078
|
-
}
|
|
3079
3259
|
function isInPreChorusLift(section, bar2, sectionBars, nextIsChorus) {
|
|
3080
3260
|
if (section !== "b" || !nextIsChorus || sectionBars < 3) {
|
|
3081
3261
|
return false;
|
|
3082
3262
|
}
|
|
3083
3263
|
return bar2 >= sectionBars - 2;
|
|
3084
3264
|
}
|
|
3085
|
-
function
|
|
3086
|
-
const
|
|
3265
|
+
function realiseKickEntries(figure, section, bar2, draw, rhythmic) {
|
|
3266
|
+
const entries = Array.from({ length: KICK_STEPS }, () => void 0);
|
|
3087
3267
|
for (const slot of figure) {
|
|
3088
3268
|
if (slot.step < 0 || slot.step >= KICK_STEPS) {
|
|
3089
3269
|
continue;
|
|
@@ -3095,19 +3275,23 @@ function realiseKickFigure(figure, section, bar2, draw, rhythmic) {
|
|
|
3095
3275
|
continue;
|
|
3096
3276
|
}
|
|
3097
3277
|
if (slot.probability === void 0) {
|
|
3098
|
-
|
|
3278
|
+
entries[slot.step] = Number.NEGATIVE_INFINITY;
|
|
3099
3279
|
continue;
|
|
3100
3280
|
}
|
|
3101
|
-
|
|
3102
|
-
|
|
3281
|
+
const at2 = draw.at("kick", bar2, slot.slot ?? slot.step);
|
|
3282
|
+
if (at2 < leanedBy(slot.probability, rhythmic)) {
|
|
3283
|
+
entries[slot.step] = Math.min(
|
|
3284
|
+
entries[slot.step] ?? Number.POSITIVE_INFINITY,
|
|
3285
|
+
at2 / slot.probability - 0.5
|
|
3286
|
+
);
|
|
3103
3287
|
}
|
|
3104
3288
|
}
|
|
3105
|
-
return
|
|
3289
|
+
return entries;
|
|
3106
3290
|
}
|
|
3107
|
-
function
|
|
3291
|
+
function getKickEntries(section, style, bar2, draw, rhythmic) {
|
|
3108
3292
|
const own = KICK_FIGURES[style] ?? DEFAULT_FIGURE;
|
|
3109
3293
|
const figure = section === "outro" ? [...OUTRO_FIGURE, ...statedSlots(own)] : own;
|
|
3110
|
-
return
|
|
3294
|
+
return realiseKickEntries(figure, section, bar2, draw, rhythmic);
|
|
3111
3295
|
}
|
|
3112
3296
|
|
|
3113
3297
|
// src/generate/drums/percussion.ts
|
|
@@ -3144,6 +3328,8 @@ var PERC_TABLE = [
|
|
|
3144
3328
|
/* rock */
|
|
3145
3329
|
[F, F, F, [false, false, true], F, F, F, F, [false, false, true]]
|
|
3146
3330
|
];
|
|
3331
|
+
var SHAKER_SIXTEENTH_CURVE = [0.75, 0.45, 0.6, 0.45];
|
|
3332
|
+
var SHAKER_EIGHTH_CURVE = [0.75, 0.55];
|
|
3147
3333
|
function getPercussionConfig(mood, section) {
|
|
3148
3334
|
const row = PERC_TABLE[mood] ?? PERC_TABLE[1 /* Standard */];
|
|
3149
3335
|
const act = row?.[sectionIndex(section)] ?? F;
|
|
@@ -3168,10 +3354,11 @@ function generateAuxPercussionForBar(track, barStart, config, role, densityMult,
|
|
|
3168
3354
|
if (config.shaker) {
|
|
3169
3355
|
const use16th = config.shaker16th && bpm < HH_16TH_BPM_THRESHOLD && sustainsStrokes(SIXTEENTH, bpm, sec.difficulty);
|
|
3170
3356
|
if (use16th) {
|
|
3171
|
-
const velCurve =
|
|
3357
|
+
const velCurve = SHAKER_SIXTEENTH_CURVE;
|
|
3172
3358
|
for (let beat = 0; beat < barBeats; beat += 1) {
|
|
3173
3359
|
for (let sub = 0; sub < 4; sub += 1) {
|
|
3174
|
-
|
|
3360
|
+
const entry = sub % 2 === 1 ? draw.at("shaker16", barStart, beat, sub) : Number.NEGATIVE_INFINITY;
|
|
3361
|
+
if (!(entry < sec.rhythmic)) {
|
|
3175
3362
|
continue;
|
|
3176
3363
|
}
|
|
3177
3364
|
const raw = 80 * (velCurve[sub] ?? 0.5) * densityMult * draw.float(0.9, 1.1, "shaker", barStart, beat, sub);
|
|
@@ -3179,12 +3366,14 @@ function generateAuxPercussionForBar(track, barStart, config, role, densityMult,
|
|
|
3179
3366
|
GM.SHAKER,
|
|
3180
3367
|
swing16(barStart + beat + sub * SIXTEENTH, sec, swingAmount),
|
|
3181
3368
|
SIXTEENTH,
|
|
3182
|
-
Math.max(25, Math.min(85, raw))
|
|
3369
|
+
Math.max(25, Math.min(85, raw)),
|
|
3370
|
+
void 0,
|
|
3371
|
+
entry
|
|
3183
3372
|
);
|
|
3184
3373
|
}
|
|
3185
3374
|
}
|
|
3186
3375
|
} else {
|
|
3187
|
-
const velCurve =
|
|
3376
|
+
const velCurve = SHAKER_EIGHTH_CURVE;
|
|
3188
3377
|
for (let beat = 0; beat < barBeats; beat += 1) {
|
|
3189
3378
|
for (let sub = 0; sub < 2; sub += 1) {
|
|
3190
3379
|
const raw = 80 * (velCurve[sub] ?? 0.6) * densityMult * draw.float(0.9, 1.1, "shaker", barStart, beat, sub);
|
|
@@ -3470,6 +3659,7 @@ function placeDrumPattern(opts) {
|
|
|
3470
3659
|
assertRange(opts.velocity, 1, 127, "drum pattern velocity");
|
|
3471
3660
|
}
|
|
3472
3661
|
const feel = opts.feel === void 0 ? "straight" : assertOneOf(opts.feel, DRUM_FEELS, "feel");
|
|
3662
|
+
const rate = assertOneOf(opts.rate ?? "straight", FIGURE_RATES, "rate");
|
|
3473
3663
|
const resolved = resolveContext(opts.ctx);
|
|
3474
3664
|
const bpm = resolved.bpm ?? DEFAULT_BPM2;
|
|
3475
3665
|
const draw = resolved.part("drums");
|
|
@@ -3515,7 +3705,7 @@ function placeDrumPattern(opts) {
|
|
|
3515
3705
|
...resolved.rhythmic === void 0 ? {} : { rhythmic: resolved.rhythmic },
|
|
3516
3706
|
...resolved.ornament === void 0 ? {} : { ornament: resolved.ornament },
|
|
3517
3707
|
isOrnament: (stroke) => stroke.articulation === "ghost",
|
|
3518
|
-
rate
|
|
3708
|
+
rate,
|
|
3519
3709
|
spanSteps: entry.material.steps,
|
|
3520
3710
|
// The dials rank the figure's positions against the bar it is written
|
|
3521
3711
|
// in, which is the bar this call is placing it in.
|
|
@@ -3602,6 +3792,7 @@ function generateDrums(opts) {
|
|
|
3602
3792
|
densityMult,
|
|
3603
3793
|
rhythmic,
|
|
3604
3794
|
ornament: ornamentDial,
|
|
3795
|
+
ornamentFollowsRhythmic: resolved.ornament === void 0,
|
|
3605
3796
|
difficulty,
|
|
3606
3797
|
ghostBoost: mapping.ghostBoost,
|
|
3607
3798
|
useRide: shouldUseRideForSection(section, style),
|
|
@@ -3617,10 +3808,15 @@ function generateDrums(opts) {
|
|
|
3617
3808
|
const percMood = percMoodCategory(style);
|
|
3618
3809
|
const nextSection = opts.nextSection ? mapSection(opts.nextSection) : section;
|
|
3619
3810
|
const nextEnergy = sectionEnergy(nextSection);
|
|
3620
|
-
const
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
);
|
|
3811
|
+
const suppliedFills = vocabularyOfKind(resolved.vocabulary, isFillArchetype);
|
|
3812
|
+
suppliedFills.forEach((entry, index) => {
|
|
3813
|
+
assertFillArchetype(entry.material, `drum fill vocabulary[${index}]`);
|
|
3814
|
+
});
|
|
3815
|
+
const callerFillEntries = selectVocabulary(suppliedFills, {
|
|
3816
|
+
section: publicSection,
|
|
3817
|
+
bpm,
|
|
3818
|
+
difficulty
|
|
3819
|
+
});
|
|
3624
3820
|
const callerFills = new Map(callerFillEntries.map((entry) => [entry.id, entry.material]));
|
|
3625
3821
|
const callerFillIds = callerFillEntries.map((entry) => entry.id);
|
|
3626
3822
|
const euclidSteps = opts.euclideanKick?.steps ?? 16;
|
|
@@ -3631,7 +3827,7 @@ function generateDrums(opts) {
|
|
|
3631
3827
|
for (let bar2 = 0; bar2 < opts.bars; bar2 += 1) {
|
|
3632
3828
|
const barStart = bar2 * barBeats;
|
|
3633
3829
|
const isLastBar = bar2 === opts.bars - 1;
|
|
3634
|
-
const
|
|
3830
|
+
const hiHat = getHiHatBar(section, style, rhythmic, bpm, draw, difficulty, bar2);
|
|
3635
3831
|
if (bar2 === 0 && section === "chorus") {
|
|
3636
3832
|
track.add(GM.CRASH, barStart, 0.5, 100 * densityMult);
|
|
3637
3833
|
}
|
|
@@ -3643,10 +3839,10 @@ function generateDrums(opts) {
|
|
|
3643
3839
|
}
|
|
3644
3840
|
let kick;
|
|
3645
3841
|
if (!euclidKick && reuseSectionKick) {
|
|
3646
|
-
sectionKick ??=
|
|
3842
|
+
sectionKick ??= getKickEntries(section, style, 0, draw, rhythmic);
|
|
3647
3843
|
kick = sectionKick;
|
|
3648
3844
|
} else if (!euclidKick) {
|
|
3649
|
-
kick =
|
|
3845
|
+
kick = getKickEntries(section, style, bar2, draw, rhythmic);
|
|
3650
3846
|
}
|
|
3651
3847
|
const inLift = isInPreChorusLift(section, bar2, opts.bars, nextSection === "chorus");
|
|
3652
3848
|
let currentFill = FILL_ARCHETYPES.snareRoll;
|
|
@@ -3702,7 +3898,8 @@ function generateDrums(opts) {
|
|
|
3702
3898
|
swingAmount,
|
|
3703
3899
|
barHasOpenHh,
|
|
3704
3900
|
openHhBeat: openHhBeatIndex,
|
|
3705
|
-
hhLevel,
|
|
3901
|
+
hhLevel: hiHat.level,
|
|
3902
|
+
hhEntries: hiHat.entries,
|
|
3706
3903
|
draw
|
|
3707
3904
|
};
|
|
3708
3905
|
if (playMainVoices) {
|
|
@@ -3763,24 +3960,44 @@ function generateDrums(opts) {
|
|
|
3763
3960
|
const endBeat = opts.bars * barBeats;
|
|
3764
3961
|
const kit = resolved.instrument("drums");
|
|
3765
3962
|
const written = track.hits.filter((hit2) => hit2.startBeat < endBeat && playableOn(kit, hit2.pitch)).sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);
|
|
3766
|
-
return underCeiling(written, bpm, difficulty, exemptOnsets);
|
|
3963
|
+
return underCeiling(written, bpm, difficulty, exemptOnsets, track);
|
|
3767
3964
|
}
|
|
3768
|
-
function underCeiling(hits, bpm, difficulty, exempt) {
|
|
3965
|
+
function underCeiling(hits, bpm, difficulty, exempt, track) {
|
|
3769
3966
|
if (difficulty === void 0) {
|
|
3770
3967
|
return [...hits];
|
|
3771
3968
|
}
|
|
3772
|
-
const
|
|
3773
|
-
const
|
|
3774
|
-
|
|
3775
|
-
const
|
|
3776
|
-
|
|
3969
|
+
const entries = hits.map((hit2) => track.entryOf(hit2.pitch, hit2.startBeat));
|
|
3970
|
+
const order = hits.map((_, index) => index).sort((a, b) => {
|
|
3971
|
+
const left = entries[a];
|
|
3972
|
+
const right = entries[b];
|
|
3973
|
+
return left < right ? -1 : left > right ? 1 : a - b;
|
|
3974
|
+
});
|
|
3975
|
+
const keptBeats = /* @__PURE__ */ new Map();
|
|
3976
|
+
const kept = hits.map(() => false);
|
|
3977
|
+
for (const index of order) {
|
|
3978
|
+
const hit2 = hits[index];
|
|
3979
|
+
const beats = keptBeats.get(hit2.pitch) ?? [];
|
|
3980
|
+
let at2 = 0;
|
|
3981
|
+
let end = beats.length;
|
|
3982
|
+
while (at2 < end) {
|
|
3983
|
+
const mid = at2 + end >>> 1;
|
|
3984
|
+
if (beats[mid] > hit2.startBeat) {
|
|
3985
|
+
end = mid;
|
|
3986
|
+
} else {
|
|
3987
|
+
at2 = mid + 1;
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
const previous = beats[at2 - 1];
|
|
3991
|
+
const next = beats[at2];
|
|
3992
|
+
const reachable = exempt.has(onsetKey(hit2.pitch, hit2.startBeat)) || (previous === void 0 || sustainsStrokes(hit2.startBeat - previous, bpm, difficulty)) && (next === void 0 || sustainsStrokes(next - hit2.startBeat, bpm, difficulty));
|
|
3777
3993
|
if (!reachable) {
|
|
3778
3994
|
continue;
|
|
3779
3995
|
}
|
|
3780
|
-
kept
|
|
3781
|
-
|
|
3996
|
+
kept[index] = true;
|
|
3997
|
+
beats.splice(at2, 0, hit2.startBeat);
|
|
3998
|
+
keptBeats.set(hit2.pitch, beats);
|
|
3782
3999
|
}
|
|
3783
|
-
return kept;
|
|
4000
|
+
return hits.filter((_, index) => kept[index]);
|
|
3784
4001
|
}
|
|
3785
4002
|
function playableOn(kit, pitch) {
|
|
3786
4003
|
return kit === void 0 || canSound(kit, pitch);
|
|
@@ -3799,7 +4016,7 @@ var MIN_VELOCITY = 1;
|
|
|
3799
4016
|
var MAX_VELOCITY = 127;
|
|
3800
4017
|
function humanize(events, opts = {}) {
|
|
3801
4018
|
const asked = assertOptions(opts, "opts");
|
|
3802
|
-
const
|
|
4019
|
+
const meter = toMeterData(asked.ts ?? DEFAULT_TS3, "ts");
|
|
3803
4020
|
assertNoteEvents(events, "humanize events", { allowNonPositiveDuration: true });
|
|
3804
4021
|
const sounding2 = dropSilentNotes(events);
|
|
3805
4022
|
const timing = asked.timing ?? DEFAULT_TIMING;
|
|
@@ -3815,7 +4032,7 @@ function humanize(events, opts = {}) {
|
|
|
3815
4032
|
return sounding2.map((event) => {
|
|
3816
4033
|
const timingOffset = draw.float(-timing, timing, "timing", event.startBeat, event.pitch);
|
|
3817
4034
|
const startBeat = event.startBeat + timingOffset;
|
|
3818
|
-
const weight = metricWeight(event.startBeat,
|
|
4035
|
+
const weight = metricWeight(event.startBeat, meter);
|
|
3819
4036
|
const accentBoost = weight / MAX_METRIC_WEIGHT * accent2;
|
|
3820
4037
|
const velocityOffset = draw.float(
|
|
3821
4038
|
-velocityJitter,
|
|
@@ -3829,6 +4046,21 @@ function humanize(events, opts = {}) {
|
|
|
3829
4046
|
return { ...event, startBeat, velocity };
|
|
3830
4047
|
});
|
|
3831
4048
|
}
|
|
4049
|
+
function grooveSignature(ts) {
|
|
4050
|
+
const meter = toMeterData(ts, "ts");
|
|
4051
|
+
if (!Array.isArray(meter)) {
|
|
4052
|
+
return meter;
|
|
4053
|
+
}
|
|
4054
|
+
const opening = meterAt(0, meter);
|
|
4055
|
+
for (const change of meter) {
|
|
4056
|
+
if (change.ts.numerator !== opening.numerator || change.ts.denominator !== opening.denominator) {
|
|
4057
|
+
throw new InvalidInputError(
|
|
4058
|
+
`groove time signature must be one signature throughout; received ${change.ts.numerator}/${change.ts.denominator} at beat ${change.startBeat} in a meter that is ${opening.numerator}/${opening.denominator} at beat 0`
|
|
4059
|
+
);
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
return opening;
|
|
4063
|
+
}
|
|
3832
4064
|
function quantizeToGrid(beat, barBeats, subdivision, slotsPerBar) {
|
|
3833
4065
|
const barIndex = Math.floor(beat / barBeats);
|
|
3834
4066
|
const modBeat = beat - barIndex * barBeats;
|
|
@@ -3844,7 +4076,7 @@ function quantizeToGrid(beat, barBeats, subdivision, slotsPerBar) {
|
|
|
3844
4076
|
};
|
|
3845
4077
|
}
|
|
3846
4078
|
function extractGrooveTemplate(events, ts, subdivision = DEFAULT_GROOVE_SUBDIVISION) {
|
|
3847
|
-
const meter =
|
|
4079
|
+
const meter = grooveSignature(ts);
|
|
3848
4080
|
assertNoteEvents(events, "groove source events", { allowNonPositiveDuration: true });
|
|
3849
4081
|
assertPositiveInt(subdivision, "groove subdivision");
|
|
3850
4082
|
const barBeats = beatsPerBar(meter);
|
|
@@ -3885,7 +4117,7 @@ function extractGrooveTemplate(events, ts, subdivision = DEFAULT_GROOVE_SUBDIVIS
|
|
|
3885
4117
|
return { subdivision, slotsPerBar, slots, ts: meter };
|
|
3886
4118
|
}
|
|
3887
4119
|
function applyGrooveTemplate(events, template, ts) {
|
|
3888
|
-
const meter =
|
|
4120
|
+
const meter = grooveSignature(ts);
|
|
3889
4121
|
assertNoteEvents(events, "groove target events", { allowNonPositiveDuration: true });
|
|
3890
4122
|
assertPositiveInt(template.subdivision, "template subdivision");
|
|
3891
4123
|
assertPositiveInt(template.slotsPerBar, "template slotsPerBar");
|
|
@@ -3949,15 +4181,27 @@ function classifyMelodyTones(melody, ts) {
|
|
|
3949
4181
|
const meter = meterAt(0, toMeterData(ts, "ts"));
|
|
3950
4182
|
assertNoteEvents(melody, "melody notes", { allowNonPositiveDuration: true });
|
|
3951
4183
|
const pulse2 = pulseBeats(meter);
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
4184
|
+
const sounding2 = new Set(soundingIndices(melody));
|
|
4185
|
+
const line = canonicalNoteOrder(melody).filter((index) => sounding2.has(index));
|
|
4186
|
+
const roles = melody.map(() => "structural");
|
|
4187
|
+
line.forEach((index, position) => {
|
|
4188
|
+
const before = line[position - 1];
|
|
4189
|
+
const after = line[position + 1];
|
|
4190
|
+
roles[index] = roleOfNote(
|
|
4191
|
+
before === void 0 ? void 0 : melody[before],
|
|
4192
|
+
melody[index],
|
|
4193
|
+
after === void 0 ? void 0 : melody[after],
|
|
4194
|
+
meter,
|
|
4195
|
+
pulse2
|
|
4196
|
+
);
|
|
3955
4197
|
});
|
|
4198
|
+
return roles.map((role, index) => ({
|
|
4199
|
+
noteIndex: index,
|
|
4200
|
+
role,
|
|
4201
|
+
ornamental: role !== "structural"
|
|
4202
|
+
}));
|
|
3956
4203
|
}
|
|
3957
|
-
function roleOfNote(
|
|
3958
|
-
const note2 = melody[index];
|
|
3959
|
-
const prev = melody[index - 1];
|
|
3960
|
-
const next = melody[index + 1];
|
|
4204
|
+
function roleOfNote(prev, note2, next, ts, pulse2) {
|
|
3961
4205
|
if (!note2 || !prev || !next) {
|
|
3962
4206
|
return "structural";
|
|
3963
4207
|
}
|
|
@@ -4013,13 +4257,11 @@ var DEFAULT_PLACEMENT = {
|
|
|
4013
4257
|
};
|
|
4014
4258
|
var DEFAULT_METER = { numerator: 4, denominator: 4 };
|
|
4015
4259
|
var TIE_BREAK_JITTER2 = 1e-6;
|
|
4016
|
-
function cadencesThroughRaisedSeventh(key) {
|
|
4017
|
-
return isMinorKey(key) && scaleSystemOf(key) === "common-practice";
|
|
4018
|
-
}
|
|
4019
4260
|
function isKeyTone(pitch, key) {
|
|
4020
4261
|
return isScaleTone(pitch, key) || cadencesThroughRaisedSeventh(key) && pitchClassOf(pitch) === leadingTonePcOf(key);
|
|
4021
4262
|
}
|
|
4022
4263
|
function inferKey(melody) {
|
|
4264
|
+
const line = inOnsetOrder(melody);
|
|
4023
4265
|
let best = majorKey(0);
|
|
4024
4266
|
let bestScore = Number.NEGATIVE_INFINITY;
|
|
4025
4267
|
const candidates = [];
|
|
@@ -4029,7 +4271,7 @@ function inferKey(melody) {
|
|
|
4029
4271
|
for (const key of candidates) {
|
|
4030
4272
|
const tonic = pitchClassOf(key.rootPc);
|
|
4031
4273
|
let score = 0;
|
|
4032
|
-
for (const n of
|
|
4274
|
+
for (const n of line) {
|
|
4033
4275
|
const w = Math.max(0.25, n.durationBeat);
|
|
4034
4276
|
const pc = pitchClassOf(n.pitch);
|
|
4035
4277
|
if (isKeyTone(n.pitch, key)) {
|
|
@@ -4046,6 +4288,12 @@ function inferKey(melody) {
|
|
|
4046
4288
|
}
|
|
4047
4289
|
return best;
|
|
4048
4290
|
}
|
|
4291
|
+
function inOnsetOrder(melody) {
|
|
4292
|
+
return canonicalNoteOrder(melody).flatMap((index) => {
|
|
4293
|
+
const note2 = melody[index];
|
|
4294
|
+
return note2 === void 0 ? [] : [note2];
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4049
4297
|
|
|
4050
4298
|
// src/generate/harmonize/cost.ts
|
|
4051
4299
|
var NON_CHORD_TONE_STRONG = 4;
|
|
@@ -4342,15 +4590,23 @@ function buildSegments(spans, melody, ts, grid, phraseEnds, budget) {
|
|
|
4342
4590
|
}
|
|
4343
4591
|
|
|
4344
4592
|
// src/generate/harmonize/candidates.ts
|
|
4345
|
-
var ROOT_WEIGHT_BY_SEMITONE = [
|
|
4593
|
+
var ROOT_WEIGHT_BY_SEMITONE = [
|
|
4594
|
+
0,
|
|
4595
|
+
0.9,
|
|
4596
|
+
0.3,
|
|
4597
|
+
0.45,
|
|
4598
|
+
0.45,
|
|
4599
|
+
0.15,
|
|
4600
|
+
0.9,
|
|
4601
|
+
0.1,
|
|
4602
|
+
0.3,
|
|
4603
|
+
0.3,
|
|
4604
|
+
0.3,
|
|
4605
|
+
0.9
|
|
4606
|
+
];
|
|
4346
4607
|
var UNSTABLE_TRIAD = 0.9;
|
|
4347
|
-
var
|
|
4348
|
-
|
|
4349
|
-
"aug",
|
|
4350
|
-
"dim7",
|
|
4351
|
-
"m7b5",
|
|
4352
|
-
"aug7"
|
|
4353
|
-
]);
|
|
4608
|
+
var UNSTABLE_QUALITY_NAMES = ["dim", "aug", "dim7", "m7b5", "aug7"];
|
|
4609
|
+
var UNSTABLE_QUALITIES = new Set(UNSTABLE_QUALITY_NAMES);
|
|
4354
4610
|
function vocabularyBase(rootPc, quality, tonicPc) {
|
|
4355
4611
|
const above = (pitchClassOf(rootPc) - tonicPc + 12) % 12;
|
|
4356
4612
|
const root = ROOT_WEIGHT_BY_SEMITONE[above] ?? UNSTABLE_TRIAD;
|
|
@@ -4364,7 +4620,7 @@ function secondaryDominantTargets(key) {
|
|
|
4364
4620
|
tonicizableDegrees(key).map((degree) => [degree.degreeNumber, degree])
|
|
4365
4621
|
);
|
|
4366
4622
|
return SECONDARY_DOMINANT_TARGETS.map((degree) => tonicizable.get(degree)).filter(
|
|
4367
|
-
(degree) => degree !== void 0
|
|
4623
|
+
(degree) => degree !== void 0 && !isNativeDominantSeventh(degree.rootPc + 7, key)
|
|
4368
4624
|
);
|
|
4369
4625
|
}
|
|
4370
4626
|
var SECONDARY_DOMINANT_ENTRY_ORDER = [5, 6, 2, 4];
|
|
@@ -4379,13 +4635,14 @@ var REHARMONIZE_DIAL = {
|
|
|
4379
4635
|
function admittedCount(fraction, size) {
|
|
4380
4636
|
return Math.max(0, Math.min(size, Math.ceil(fraction * size)));
|
|
4381
4637
|
}
|
|
4382
|
-
function buildCandidates(
|
|
4638
|
+
function buildCandidates(keyLike, harmonic) {
|
|
4639
|
+
const { scale: key, tonic } = resolveKey(keyLike);
|
|
4383
4640
|
const tonicPc = pitchClassOf(key.rootPc);
|
|
4384
4641
|
const frame = heptatonicFrameOf(key);
|
|
4385
4642
|
const tones = scaleTonesInDegreeOrder(frame);
|
|
4386
4643
|
const candidates = tones.map((rootPc, index) => {
|
|
4387
4644
|
const degree = index + 1;
|
|
4388
|
-
const quality = diatonicTriad(degree, frame).quality;
|
|
4645
|
+
const quality = diatonicTriad(degree, { ...frame, tonic }).quality;
|
|
4389
4646
|
return {
|
|
4390
4647
|
rootPc,
|
|
4391
4648
|
quality,
|
|
@@ -4429,7 +4686,7 @@ function buildCandidates(key, harmonic) {
|
|
|
4429
4686
|
const parallelTones = scaleTonesInDegreeOrder(parallel);
|
|
4430
4687
|
const borrowed = [];
|
|
4431
4688
|
for (let degree = 1; degree <= parallelTones.length; degree += 1) {
|
|
4432
|
-
const chord = diatonicTriad(degree, parallel);
|
|
4689
|
+
const chord = diatonicTriad(degree, { ...parallel, tonic });
|
|
4433
4690
|
if (!isDiatonic(chord, key)) {
|
|
4434
4691
|
borrowed.push(chord);
|
|
4435
4692
|
}
|
|
@@ -4475,11 +4732,11 @@ function harmonizeMelody(opts) {
|
|
|
4475
4732
|
allowNonPositiveDuration: true,
|
|
4476
4733
|
budget: opts.budget
|
|
4477
4734
|
});
|
|
4478
|
-
const phraseEnds = opts.phraseEnds ?? [];
|
|
4735
|
+
const phraseEnds = assertArray(opts.phraseEnds ?? [], "harmonize phrase ends");
|
|
4479
4736
|
for (const end of phraseEnds) {
|
|
4480
4737
|
assertFiniteNumber(end, "harmonize phrase end");
|
|
4481
4738
|
}
|
|
4482
|
-
const soundingMelody = soundingNotesOnly(opts.melody);
|
|
4739
|
+
const soundingMelody = inOnsetOrder(soundingNotesOnly(opts.melody));
|
|
4483
4740
|
const ts = meterAt(0, toMeterData(opts.ts ?? DEFAULT_METER, "ts"));
|
|
4484
4741
|
assertTimeSignature(ts);
|
|
4485
4742
|
const requestedKey = opts.key ?? "infer";
|
|
@@ -4487,7 +4744,10 @@ function harmonizeMelody(opts) {
|
|
|
4487
4744
|
requestedKey === "infer" ? inferKey(soundingMelody) : requestedKey
|
|
4488
4745
|
);
|
|
4489
4746
|
const key = resolvedKey.scale;
|
|
4490
|
-
const placement =
|
|
4747
|
+
const placement = assertRecord(
|
|
4748
|
+
opts.placement ?? DEFAULT_PLACEMENT,
|
|
4749
|
+
"harmonize placement"
|
|
4750
|
+
);
|
|
4491
4751
|
const ctx = resolveContext(opts.ctx);
|
|
4492
4752
|
const reharmonize = assertOneOf(
|
|
4493
4753
|
opts.reharmonize ?? "diatonic",
|
|
@@ -4498,7 +4758,7 @@ function harmonizeMelody(opts) {
|
|
|
4498
4758
|
if (soundingMelody.length === 0) {
|
|
4499
4759
|
return { transposeSemitones: 0, key: resolvedKey, chords: [], melodyRoles: [] };
|
|
4500
4760
|
}
|
|
4501
|
-
const candidates = buildCandidates(
|
|
4761
|
+
const candidates = buildCandidates(resolvedKey, harmonic);
|
|
4502
4762
|
const candAt = (i) => candidates[i] ?? FALLBACK;
|
|
4503
4763
|
const draw = ctx.part("harmony");
|
|
4504
4764
|
const jitter = candidates.map((_, index) => draw.at("jitter", index) * TIE_BREAK_JITTER2);
|
|
@@ -4549,6 +4809,7 @@ function harmonizeMelody(opts) {
|
|
|
4549
4809
|
"harmonization placement search",
|
|
4550
4810
|
opts.budget
|
|
4551
4811
|
);
|
|
4812
|
+
let bestFit = Number.POSITIVE_INFINITY;
|
|
4552
4813
|
let bestCost = Number.POSITIVE_INFINITY;
|
|
4553
4814
|
let bestTs = 0;
|
|
4554
4815
|
let bestPath = [];
|
|
@@ -4563,8 +4824,10 @@ function harmonizeMelody(opts) {
|
|
|
4563
4824
|
closingSegments,
|
|
4564
4825
|
articulatedSegments
|
|
4565
4826
|
);
|
|
4566
|
-
const
|
|
4567
|
-
|
|
4827
|
+
const fit = keyFitCost(shifted, key);
|
|
4828
|
+
const total = cost + tessituraCost(shifted);
|
|
4829
|
+
if (fit < bestFit || fit === bestFit && total < bestCost) {
|
|
4830
|
+
bestFit = fit;
|
|
4568
4831
|
bestCost = total;
|
|
4569
4832
|
bestTs = shift;
|
|
4570
4833
|
bestPath = path;
|
|
@@ -4699,7 +4962,11 @@ function contourOffsets(contour, count) {
|
|
|
4699
4962
|
}
|
|
4700
4963
|
function generateMotif(opts) {
|
|
4701
4964
|
assertRecord(opts, "motif options");
|
|
4702
|
-
const contour =
|
|
4965
|
+
const contour = assertOneOf(
|
|
4966
|
+
opts.contour ?? "arch",
|
|
4967
|
+
["arch", "ascending", "descending", "wave"],
|
|
4968
|
+
"motif contour"
|
|
4969
|
+
);
|
|
4703
4970
|
const bars = assertPositiveInt(opts.bars, "motif bars");
|
|
4704
4971
|
const barBeats = beatsPerBar(meterAt(0, toMeterData(opts.ts ?? DEFAULT_TS4, "ts")));
|
|
4705
4972
|
const totalBeats = bars * barBeats;
|
|
@@ -4846,6 +5113,10 @@ function developMotif(cell, timeline, key, bars, ts) {
|
|
|
4846
5113
|
if (span <= 0) {
|
|
4847
5114
|
return inTimeOrder(cell);
|
|
4848
5115
|
}
|
|
5116
|
+
const line = canonicalNoteOrder(cellNotes).flatMap((index) => {
|
|
5117
|
+
const note2 = cellNotes[index];
|
|
5118
|
+
return note2 === void 0 ? [] : [note2];
|
|
5119
|
+
});
|
|
4849
5120
|
const tileSpan = Math.max(span, MIN_TILE_SPAN);
|
|
4850
5121
|
const tileCount = Math.ceil(totalBeats / tileSpan);
|
|
4851
5122
|
assertGenerationBudget(tileCount * cellNotes.length, "developed motif notes");
|
|
@@ -4853,7 +5124,7 @@ function developMotif(cell, timeline, key, bars, ts) {
|
|
|
4853
5124
|
const offset = k * tileSpan;
|
|
4854
5125
|
const taken = /* @__PURE__ */ new Map();
|
|
4855
5126
|
let previous;
|
|
4856
|
-
for (const n of
|
|
5127
|
+
for (const n of line) {
|
|
4857
5128
|
const startBeat = offset + (n.startBeat - origin);
|
|
4858
5129
|
if (startBeat >= totalBeats) {
|
|
4859
5130
|
continue;
|
|
@@ -4866,7 +5137,11 @@ function developMotif(cell, timeline, key, bars, ts) {
|
|
|
4866
5137
|
const pitch = developedPitch(n.pitch, allows, direction, taken);
|
|
4867
5138
|
taken.set(pitch, n.pitch);
|
|
4868
5139
|
previous = n.pitch;
|
|
4869
|
-
out.push({
|
|
5140
|
+
out.push({
|
|
5141
|
+
pitch,
|
|
5142
|
+
startBeat,
|
|
5143
|
+
durationBeat: Math.min(n.durationBeat, totalBeats - startBeat)
|
|
5144
|
+
});
|
|
4870
5145
|
}
|
|
4871
5146
|
}
|
|
4872
5147
|
return inTimeOrder({ notes: out });
|
|
@@ -4878,6 +5153,13 @@ function positionClass(beat, meters) {
|
|
|
4878
5153
|
const weight = metricWeight(beat, meters);
|
|
4879
5154
|
return weight >= STRONG_WEIGHT ? "strong" : weight > 0 ? "weak" : "off";
|
|
4880
5155
|
}
|
|
5156
|
+
function finalDownbeat(phrase, meters) {
|
|
5157
|
+
let downbeat = barStartBeat(phrase.startBeat, meters);
|
|
5158
|
+
while (downbeat + beatsPerBarAt(downbeat, meters) < phrase.endBeat - BEAT_EPS) {
|
|
5159
|
+
downbeat += beatsPerBarAt(downbeat, meters);
|
|
5160
|
+
}
|
|
5161
|
+
return Math.max(downbeat, phrase.startBeat);
|
|
5162
|
+
}
|
|
4881
5163
|
|
|
4882
5164
|
// src/generate/plan/types.ts
|
|
4883
5165
|
var COMPOSITION_PLAN_VERSION = 1;
|
|
@@ -5158,7 +5440,7 @@ function planChordTimeline(plan) {
|
|
|
5158
5440
|
}
|
|
5159
5441
|
|
|
5160
5442
|
// src/generate/plan/harmony.ts
|
|
5161
|
-
var
|
|
5443
|
+
var HARMONY_OK_KIND_NAMES = [
|
|
5162
5444
|
"chordTone",
|
|
5163
5445
|
"tension",
|
|
5164
5446
|
"suspension",
|
|
@@ -5166,7 +5448,8 @@ var HARMONY_OK_KINDS = /* @__PURE__ */ new Set([
|
|
|
5166
5448
|
"anticipation",
|
|
5167
5449
|
"passing",
|
|
5168
5450
|
"neighbor"
|
|
5169
|
-
]
|
|
5451
|
+
];
|
|
5452
|
+
var HARMONY_OK_KINDS = new Set(HARMONY_OK_KIND_NAMES);
|
|
5170
5453
|
var CADENCE_DEGREES = {
|
|
5171
5454
|
authentic: [1, 3],
|
|
5172
5455
|
plagal: [1, 3],
|
|
@@ -5189,8 +5472,13 @@ function cadencePitchClasses(plan, timeline, cadence, beat) {
|
|
|
5189
5472
|
return chord === null ? null : chordPitchClasses(chord);
|
|
5190
5473
|
}
|
|
5191
5474
|
const entry = plan.harmony.find((chord) => beat >= chord.startBeat && beat < chord.endBeat);
|
|
5192
|
-
const
|
|
5193
|
-
|
|
5475
|
+
const scale = scaleOf(plan.keys[entry?.key ?? 0]);
|
|
5476
|
+
const tones = scaleTonesInDegreeOrder(scale);
|
|
5477
|
+
const raised = cadencesThroughRaisedSeventh(scale);
|
|
5478
|
+
return CADENCE_DEGREES[cadence].map((degree) => {
|
|
5479
|
+
const tone = tones[degree - 1];
|
|
5480
|
+
return raised && degree === 7 && tone !== void 0 ? leadingTonePcOf(scale) : tone;
|
|
5481
|
+
}).filter((pc) => pc !== void 0);
|
|
5194
5482
|
}
|
|
5195
5483
|
function planHarmonyMisfits(plan, timeline = planChordTimeline(plan)) {
|
|
5196
5484
|
const keyAt = planKeyAt(plan);
|
|
@@ -5206,7 +5494,8 @@ function planHarmonyMisfits(plan, timeline = planChordTimeline(plan)) {
|
|
|
5206
5494
|
if (kinds.some((kind) => HARMONY_OK_KINDS.has(kind))) {
|
|
5207
5495
|
return null;
|
|
5208
5496
|
}
|
|
5209
|
-
|
|
5497
|
+
const lenient = position === "weak" || timeline.at(note2.startBeat) === null;
|
|
5498
|
+
return lenient && isScaleTone(note2.pitch, scaleAt2(note2.startBeat)) ? null : kinds;
|
|
5210
5499
|
});
|
|
5211
5500
|
};
|
|
5212
5501
|
}
|
|
@@ -5341,16 +5630,19 @@ function stretched(cell, timeRatio) {
|
|
|
5341
5630
|
function shaped(cell, relation) {
|
|
5342
5631
|
switch (relation.kind) {
|
|
5343
5632
|
case "repetition":
|
|
5344
|
-
case "tonalTransposition":
|
|
5345
5633
|
return cell;
|
|
5346
5634
|
case "transposition":
|
|
5635
|
+
case "tonalTransposition":
|
|
5347
5636
|
return stretched(cell, relation.timeRatio);
|
|
5348
5637
|
case "inversion":
|
|
5349
5638
|
return stretched(transformMotif(cell, "invert"), relation.timeRatio);
|
|
5350
5639
|
case "retrograde":
|
|
5351
|
-
return transformMotif(cell, "retrograde");
|
|
5640
|
+
return stretched(transformMotif(cell, "retrograde"), relation.timeRatio);
|
|
5352
5641
|
case "retrogradeInversion":
|
|
5353
|
-
return
|
|
5642
|
+
return stretched(
|
|
5643
|
+
transformMotif(transformMotif(cell, "invert"), "retrograde"),
|
|
5644
|
+
relation.timeRatio
|
|
5645
|
+
);
|
|
5354
5646
|
case "augmentation":
|
|
5355
5647
|
return transformMotif(cell, "augment", relation.timeRatio);
|
|
5356
5648
|
case "diminution":
|
|
@@ -5625,6 +5917,7 @@ function onsetSlots(scene, startBeat, endBeat) {
|
|
|
5625
5917
|
slots.push(beat);
|
|
5626
5918
|
}
|
|
5627
5919
|
}
|
|
5920
|
+
assertGenerationBudget(slots.length, "melody onset slots", scene.budget);
|
|
5628
5921
|
bar2 = barEnd;
|
|
5629
5922
|
}
|
|
5630
5923
|
return slots;
|
|
@@ -5765,15 +6058,6 @@ function lineBefore(scene, index, beat) {
|
|
|
5765
6058
|
}
|
|
5766
6059
|
return line.sort((a, b) => a.startBeat - b.startBeat);
|
|
5767
6060
|
}
|
|
5768
|
-
function finalDownbeat(scene, index) {
|
|
5769
|
-
const phrase = scene.plan.phrases[index];
|
|
5770
|
-
const { meters } = scene.plan;
|
|
5771
|
-
let downbeat = barStartBeat(phrase.startBeat, meters);
|
|
5772
|
-
while (downbeat + beatsPerBarAt(downbeat, meters) < phrase.endBeat - BEAT_EPS) {
|
|
5773
|
-
downbeat += beatsPerBarAt(downbeat, meters);
|
|
5774
|
-
}
|
|
5775
|
-
return Math.max(downbeat, phrase.startBeat);
|
|
5776
|
-
}
|
|
5777
6061
|
function heldNoteIndex(scene, index, line, downbeat) {
|
|
5778
6062
|
const phrase = scene.plan.phrases[index];
|
|
5779
6063
|
const sounding2 = line.findIndex(
|
|
@@ -5904,7 +6188,7 @@ function generateMelody(plan, opts) {
|
|
|
5904
6188
|
const melody = [];
|
|
5905
6189
|
plan.phrases.forEach((_, index) => {
|
|
5906
6190
|
const line = phraseLine(scene, index);
|
|
5907
|
-
const downbeat = finalDownbeat(
|
|
6191
|
+
const downbeat = finalDownbeat(plan.phrases[index], plan.meters);
|
|
5908
6192
|
let held = heldNoteIndex(scene, index, line, downbeat);
|
|
5909
6193
|
if (held >= 0) {
|
|
5910
6194
|
line.length = held + 1;
|
|
@@ -5940,8 +6224,8 @@ var DEFAULT_VELOCITY2 = 80;
|
|
|
5940
6224
|
var MIN_VELOCITY2 = 1;
|
|
5941
6225
|
var MAX_VELOCITY2 = 127;
|
|
5942
6226
|
var SLIDE_MIN_SEMITONES = 1;
|
|
5943
|
-
function eligible(style, note2, previous, next,
|
|
5944
|
-
const strong = isStrongBeat(note2.startBeat,
|
|
6227
|
+
function eligible(style, note2, previous, next, meter) {
|
|
6228
|
+
const strong = isStrongBeat(note2.startBeat, meter);
|
|
5945
6229
|
switch (style) {
|
|
5946
6230
|
case "ghost":
|
|
5947
6231
|
return !strong;
|
|
@@ -5949,7 +6233,7 @@ function eligible(style, note2, previous, next, ts) {
|
|
|
5949
6233
|
case "accent":
|
|
5950
6234
|
return strong;
|
|
5951
6235
|
case "drag":
|
|
5952
|
-
return !strong && next !== void 0 && isStrongBeat(next.startBeat,
|
|
6236
|
+
return !strong && next !== void 0 && isStrongBeat(next.startBeat, meter);
|
|
5953
6237
|
case "slide":
|
|
5954
6238
|
return previous !== void 0 && Math.abs(note2.pitch - previous.pitch) >= SLIDE_MIN_SEMITONES;
|
|
5955
6239
|
}
|
|
@@ -5977,8 +6261,7 @@ function ornament(notes, opts = {}) {
|
|
|
5977
6261
|
const asked = assertOptions(opts, "opts");
|
|
5978
6262
|
assertNoteEvents(notes, "ornament notes", { allowNonPositiveDuration: true });
|
|
5979
6263
|
const style = assertOneOf(asked.style ?? DEFAULT_STYLE2, ORNAMENT_STYLES, "ornament style");
|
|
5980
|
-
const
|
|
5981
|
-
assertTimeSignature(ts);
|
|
6264
|
+
const meter = toMeterData(asked.ts ?? DEFAULT_TS5, "ts");
|
|
5982
6265
|
if (asked.amount !== void 0) {
|
|
5983
6266
|
assertRange(asked.amount, 0, 1, "ornament amount");
|
|
5984
6267
|
}
|
|
@@ -5992,7 +6275,7 @@ function ornament(notes, opts = {}) {
|
|
|
5992
6275
|
const gaps = strokeGaps(ordered);
|
|
5993
6276
|
return sounding2.map((note2) => {
|
|
5994
6277
|
const decorated = { ...note2 };
|
|
5995
|
-
if (note2.articulation !== void 0 || !eligible(style, note2, previousOf.get(note2), nextOf.get(note2),
|
|
6278
|
+
if (note2.articulation !== void 0 || !eligible(style, note2, previousOf.get(note2), nextOf.get(note2), meter)) {
|
|
5996
6279
|
return decorated;
|
|
5997
6280
|
}
|
|
5998
6281
|
if (!sustainsStrokes(gaps.get(note2) ?? Number.POSITIVE_INFINITY, ctx.bpm, ctx.difficulty)) {
|
|
@@ -6108,13 +6391,14 @@ function rejectSuppliedRng(ctx) {
|
|
|
6108
6391
|
}
|
|
6109
6392
|
}
|
|
6110
6393
|
function plannedKeys(profile, requested) {
|
|
6111
|
-
const regions = profile.harmony.keys.map(
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6394
|
+
const regions = profile.harmony.keys.map(({ key }) => {
|
|
6395
|
+
const resolved = resolveKey(key);
|
|
6396
|
+
return {
|
|
6397
|
+
scale: { ...resolved.scale },
|
|
6398
|
+
tonic: { ...resolved.tonic },
|
|
6399
|
+
variant: resolved.variant
|
|
6400
|
+
};
|
|
6401
|
+
});
|
|
6118
6402
|
const home = regions[0];
|
|
6119
6403
|
if (requested === void 0) {
|
|
6120
6404
|
return regions;
|
|
@@ -6364,11 +6648,23 @@ function selectedDerivation(graph, parentEdge, selectedAt, node) {
|
|
|
6364
6648
|
}
|
|
6365
6649
|
return { from: null, relation: null };
|
|
6366
6650
|
}
|
|
6651
|
+
function functionTriad(fn, degree, key) {
|
|
6652
|
+
if (fn === "dominant" && cadencesThroughRaisedSeventh(key.scale)) {
|
|
6653
|
+
if (degree === 5) {
|
|
6654
|
+
return makeChord(pitchClassOf(key.scale.rootPc + 7), "maj");
|
|
6655
|
+
}
|
|
6656
|
+
if (degree === 7) {
|
|
6657
|
+
return makeChord(leadingTonePcOf(key.scale), "dim");
|
|
6658
|
+
}
|
|
6659
|
+
}
|
|
6660
|
+
return diatonicTriad(degree, key);
|
|
6661
|
+
}
|
|
6367
6662
|
function functionCandidates(fn, key) {
|
|
6368
6663
|
if (scaleTonesInDegreeOrder(key).length !== HEPTATONIC_DEGREES) {
|
|
6369
6664
|
return null;
|
|
6370
6665
|
}
|
|
6371
|
-
|
|
6666
|
+
const numerals = FUNCTION_DEGREES[fn].map((degree) => functionTriad(fn, degree, key)).filter((chord) => functionOf(chord, key) === fn).map((chord) => chordToRoman(chord, key));
|
|
6667
|
+
return numerals.length === 0 ? null : numerals;
|
|
6372
6668
|
}
|
|
6373
6669
|
function plannedHarmony(profile, keys, preserve, draw) {
|
|
6374
6670
|
const chords = profile.harmony.chords;
|
|
@@ -6448,6 +6744,11 @@ function deriveCompositionPlan(reference, opts) {
|
|
|
6448
6744
|
};
|
|
6449
6745
|
const sections = plannedSections(profile, preserve.form, draw);
|
|
6450
6746
|
const shapeWeight = preserve.registerShape;
|
|
6747
|
+
const phraseBars = profile.form.phrases.reduce(
|
|
6748
|
+
(bars, phrase) => bars + barIndexAt(phrase.endBeat, profile.meters) - barIndexAt(phrase.startBeat, profile.meters),
|
|
6749
|
+
0
|
|
6750
|
+
);
|
|
6751
|
+
assertGenerationBudget(phraseBars, "plan phrase bars");
|
|
6451
6752
|
const spans = plannedPhraseSpans(profile, sections, preserve.phraseLengths, draw);
|
|
6452
6753
|
const phrases = spans.map((span) => {
|
|
6453
6754
|
const melody = profile.form.phrases[span.source]?.melody ?? null;
|
|
@@ -6633,7 +6934,7 @@ function harmonyViolations(melody, plan, timeline, lines) {
|
|
|
6633
6934
|
const derived = plan.motifs.some(
|
|
6634
6935
|
(motif) => motif.from !== null && note2.startBeat >= motif.startBeat - BEAT_EPS && note2.startBeat < motif.endBeat - BEAT_EPS
|
|
6635
6936
|
);
|
|
6636
|
-
const strong = positionClass(note2.startBeat, plan.meters) === "strong";
|
|
6937
|
+
const strong = positionClass(note2.startBeat, plan.meters) === "strong" && timeline.at(note2.startBeat) !== null;
|
|
6637
6938
|
violations.push({
|
|
6638
6939
|
kind: "harmony",
|
|
6639
6940
|
severity: derived ? "warning" : "error",
|
|
@@ -6651,7 +6952,16 @@ function notesInRange(melody, startBeat, endBeat) {
|
|
|
6651
6952
|
(note2) => note2.startBeat >= startBeat - BEAT_EPS && note2.startBeat < endBeat - BEAT_EPS
|
|
6652
6953
|
);
|
|
6653
6954
|
}
|
|
6654
|
-
function
|
|
6955
|
+
function keptEnd(statement, melody, plan) {
|
|
6956
|
+
const phrase = phraseAt(plan, statement.startBeat);
|
|
6957
|
+
if (phrase === void 0) {
|
|
6958
|
+
return statement.endBeat;
|
|
6959
|
+
}
|
|
6960
|
+
const downbeat = finalDownbeat(phrase, plan.meters);
|
|
6961
|
+
const held = notesInRange(melody, downbeat, phrase.endBeat).length === 1;
|
|
6962
|
+
return held ? Math.min(statement.endBeat, downbeat) : statement.endBeat;
|
|
6963
|
+
}
|
|
6964
|
+
function replayLevel(source, derived, statement, sourceEnd, derivedEnd, sourceCut, plan, key, closing) {
|
|
6655
6965
|
const relation = statement.relation;
|
|
6656
6966
|
const planned = relation.kind === "tonalTransposition" ? relation.degrees ?? 0 : relation.semitones;
|
|
6657
6967
|
const { low, high } = plan.phrases[statement.phrase].register;
|
|
@@ -6668,23 +6978,24 @@ function replayLevel(source, derived, statement, sourceEnd, plan, key, closing)
|
|
|
6668
6978
|
const replays = relationLevels(relation.kind).map((level) => {
|
|
6669
6979
|
const notes = clipStatement(
|
|
6670
6980
|
replayAt(cell, relation, level, statement.startBeat, key),
|
|
6671
|
-
|
|
6981
|
+
derivedEnd
|
|
6672
6982
|
);
|
|
6673
6983
|
return { level, notes, outside: notes.filter((note2) => outside(note2.pitch)).length };
|
|
6674
6984
|
});
|
|
6675
6985
|
const fewestOutside = Math.min(...replays.map((replay) => replay.outside));
|
|
6676
6986
|
let best = null;
|
|
6677
6987
|
for (const { level, notes: expected, outside: away } of replays) {
|
|
6678
|
-
|
|
6988
|
+
const compared = sourceCut ? derived.slice(0, expected.length) : derived;
|
|
6989
|
+
if (expected.length !== compared.length) {
|
|
6679
6990
|
continue;
|
|
6680
6991
|
}
|
|
6681
6992
|
let forced = 0;
|
|
6682
6993
|
let holds = true;
|
|
6683
6994
|
for (let i = 0; holds && i < expected.length; i += 1) {
|
|
6684
6995
|
const want = expected[i];
|
|
6685
|
-
const got =
|
|
6996
|
+
const got = compared[i];
|
|
6686
6997
|
if (i > 0) {
|
|
6687
|
-
const gap = got.startBeat -
|
|
6998
|
+
const gap = got.startBeat - compared[i - 1].startBeat;
|
|
6688
6999
|
holds = Math.abs(gap - (want.startBeat - expected[i - 1].startBeat)) <= BEAT_EPS;
|
|
6689
7000
|
}
|
|
6690
7001
|
if (!holds || got.pitch === want.pitch) {
|
|
@@ -6715,6 +7026,11 @@ function motifDerivationViolations(melody, plan, closing) {
|
|
|
6715
7026
|
const derivedNotes = notesInRange(melody, statement.startBeat, statement.endBeat);
|
|
6716
7027
|
const relation = statement.relation;
|
|
6717
7028
|
const rationale = `the statement at beat ${statement.startBeat} does not derive from its source the way the plan names`;
|
|
7029
|
+
const sourceKept = keptEnd(source, melody, plan);
|
|
7030
|
+
const derivedKept = keptEnd(statement, melody, plan);
|
|
7031
|
+
if (sourceKept <= source.startBeat + BEAT_EPS || derivedKept <= statement.startBeat + BEAT_EPS) {
|
|
7032
|
+
continue;
|
|
7033
|
+
}
|
|
6718
7034
|
if (sourceNotes.length === 0 || derivedNotes.length === 0) {
|
|
6719
7035
|
errors.push({
|
|
6720
7036
|
kind: "motifDerivation",
|
|
@@ -6726,8 +7042,17 @@ function motifDerivationViolations(melody, plan, closing) {
|
|
|
6726
7042
|
});
|
|
6727
7043
|
continue;
|
|
6728
7044
|
}
|
|
7045
|
+
const sourceCut = sourceKept < source.endBeat - BEAT_EPS;
|
|
7046
|
+
const derivedCut = derivedKept < statement.endBeat - BEAT_EPS;
|
|
6729
7047
|
if (relation === null) {
|
|
6730
|
-
const
|
|
7048
|
+
const sourceEnd = derivedCut ? Math.min(sourceKept, source.startBeat + derivedKept - statement.startBeat) : sourceKept;
|
|
7049
|
+
const derivedEnd = sourceCut ? Math.min(derivedKept, statement.startBeat + sourceKept - source.startBeat) : derivedKept;
|
|
7050
|
+
const sourceLine = notesInRange(melody, source.startBeat, sourceEnd);
|
|
7051
|
+
const derivedLine = notesInRange(melody, statement.startBeat, derivedEnd);
|
|
7052
|
+
if (sourceLine.length === 0 || derivedLine.length === 0) {
|
|
7053
|
+
continue;
|
|
7054
|
+
}
|
|
7055
|
+
const similarity = melodicSimilarity(sourceLine, derivedLine);
|
|
6731
7056
|
if (similarity < DEFAULT_VARIATION_THRESHOLD) {
|
|
6732
7057
|
errors.push({
|
|
6733
7058
|
kind: "motifDerivation",
|
|
@@ -6740,11 +7065,18 @@ function motifDerivationViolations(melody, plan, closing) {
|
|
|
6740
7065
|
}
|
|
6741
7066
|
continue;
|
|
6742
7067
|
}
|
|
7068
|
+
const kept = notesInRange(melody, source.startBeat, sourceKept);
|
|
7069
|
+
const backward = relation.kind === "retrograde" || relation.kind === "retrogradeInversion";
|
|
7070
|
+
if (kept.length === 0 || sourceCut && backward) {
|
|
7071
|
+
continue;
|
|
7072
|
+
}
|
|
6743
7073
|
const replay = replayLevel(
|
|
6744
|
-
|
|
6745
|
-
|
|
7074
|
+
kept,
|
|
7075
|
+
notesInRange(melody, statement.startBeat, derivedKept),
|
|
6746
7076
|
statement,
|
|
6747
|
-
|
|
7077
|
+
sourceKept,
|
|
7078
|
+
derivedKept,
|
|
7079
|
+
sourceCut,
|
|
6748
7080
|
plan,
|
|
6749
7081
|
scaleOf(keyAt(statement.startBeat)),
|
|
6750
7082
|
closing
|
|
@@ -7122,12 +7454,12 @@ function degreeToRootPc(degree, key) {
|
|
|
7122
7454
|
const offset = BORROWED_OFFSET[degree] ?? 0;
|
|
7123
7455
|
return ((key.rootPc % 12 + offset) % 12 + 12) % 12;
|
|
7124
7456
|
}
|
|
7125
|
-
function autoQuality(degree, key, harmonicDominant) {
|
|
7457
|
+
function autoQuality(degree, key, tonic, harmonicDominant) {
|
|
7126
7458
|
if (degree >= 1 && degree <= 7) {
|
|
7127
|
-
if (harmonicDominant &&
|
|
7459
|
+
if (harmonicDominant && cadencesThroughRaisedSeventh(key) && degree === 5) {
|
|
7128
7460
|
return "maj";
|
|
7129
7461
|
}
|
|
7130
|
-
return diatonicTriad(degree, heptatonicFrameOf(key)).quality;
|
|
7462
|
+
return diatonicTriad(degree, { ...heptatonicFrameOf(key), tonic }).quality;
|
|
7131
7463
|
}
|
|
7132
7464
|
if (degree === 14) {
|
|
7133
7465
|
return "dim";
|
|
@@ -7137,11 +7469,11 @@ function autoQuality(degree, key, harmonicDominant) {
|
|
|
7137
7469
|
}
|
|
7138
7470
|
return "maj";
|
|
7139
7471
|
}
|
|
7140
|
-
function resolveCycle(degrees, key, ext, harmonicDominant) {
|
|
7472
|
+
function resolveCycle(degrees, key, tonic, ext, harmonicDominant) {
|
|
7141
7473
|
const steps = degrees.map((degree) => {
|
|
7142
7474
|
const step = {
|
|
7143
7475
|
rootPc: degreeToRootPc(degree, key),
|
|
7144
|
-
quality: ext !== void 0 && ext !== "auto" ? ext : autoQuality(degree, key, harmonicDominant)
|
|
7476
|
+
quality: ext !== void 0 && ext !== "auto" ? ext : autoQuality(degree, key, tonic, harmonicDominant)
|
|
7145
7477
|
};
|
|
7146
7478
|
if (degree >= 1 && degree <= 7) {
|
|
7147
7479
|
step.degree = degree;
|
|
@@ -7174,17 +7506,22 @@ function generateProgression(opts) {
|
|
|
7174
7506
|
assertRecord(opts, "progression options");
|
|
7175
7507
|
assertPositiveInt(opts.bars, "progression bars");
|
|
7176
7508
|
assertGenerationBudget(opts.bars, "progression chords");
|
|
7177
|
-
const key =
|
|
7509
|
+
const { scale: key, tonic } = resolveKey(opts.key);
|
|
7178
7510
|
const ctx = resolveContext(opts.ctx);
|
|
7179
7511
|
let preset;
|
|
7180
7512
|
if (opts.preset !== void 0) {
|
|
7181
|
-
|
|
7513
|
+
const custom = assertRecord(
|
|
7514
|
+
opts.preset,
|
|
7515
|
+
"progression preset"
|
|
7516
|
+
);
|
|
7517
|
+
const degrees = assertArray(custom.degrees, "progression preset degrees");
|
|
7518
|
+
if (degrees.length === 0) {
|
|
7182
7519
|
throw new InvalidInputError("progression preset must name at least one degree");
|
|
7183
7520
|
}
|
|
7184
7521
|
preset = {
|
|
7185
|
-
id:
|
|
7186
|
-
name:
|
|
7187
|
-
degrees:
|
|
7522
|
+
id: custom.id ?? "custom",
|
|
7523
|
+
name: custom.name ?? "Custom",
|
|
7524
|
+
degrees: degrees.map((degree, index) => {
|
|
7188
7525
|
assertDegree(degree, `progression preset degrees[${index}]`);
|
|
7189
7526
|
const supported = degree >= 1 && degree <= 7 || Object.values(BORROWED_DEGREES).includes(degree);
|
|
7190
7527
|
if (!supported) {
|
|
@@ -7194,8 +7531,12 @@ function generateProgression(opts) {
|
|
|
7194
7531
|
}
|
|
7195
7532
|
return degree;
|
|
7196
7533
|
}),
|
|
7197
|
-
functional:
|
|
7198
|
-
|
|
7534
|
+
functional: assertOneOf(
|
|
7535
|
+
custom.functional ?? "loop",
|
|
7536
|
+
["loop", "tensionBuild", "cadenceStrong", "stable"],
|
|
7537
|
+
"progression preset functional"
|
|
7538
|
+
),
|
|
7539
|
+
styles: custom.styles ?? [opts.style]
|
|
7199
7540
|
};
|
|
7200
7541
|
}
|
|
7201
7542
|
if (preset === void 0 && opts.presetId !== void 0) {
|
|
@@ -7209,6 +7550,7 @@ function generateProgression(opts) {
|
|
|
7209
7550
|
const cycle = resolveCycle(
|
|
7210
7551
|
preset?.degrees ?? [0],
|
|
7211
7552
|
key,
|
|
7553
|
+
tonic,
|
|
7212
7554
|
ext,
|
|
7213
7555
|
preset?.functional === "cadenceStrong"
|
|
7214
7556
|
);
|
|
@@ -7241,10 +7583,11 @@ function generateProgression(opts) {
|
|
|
7241
7583
|
const domRoot = (next.rootPc + 7) % 12;
|
|
7242
7584
|
const targetsTonic = (next.rootPc - tonicPc + 12) % 12 === 0;
|
|
7243
7585
|
const isLastTonicStatement = cur.rootPc === tonicPc && tonicStatements <= 1;
|
|
7244
|
-
const alreadySecondary = cur.rootPc === domRoot && cur.quality
|
|
7586
|
+
const alreadySecondary = cur.rootPc === domRoot && soundsDominantSeventh(makeChord(cur.rootPc, cur.quality));
|
|
7587
|
+
const isNativeDominant = isNativeDominantSeventh(domRoot, key);
|
|
7245
7588
|
const isResolutionTarget = chords[i - 1]?.secondaryDominant === true;
|
|
7246
7589
|
const takesNoDominant = !canBeTonicized(next, tonicizable);
|
|
7247
|
-
if (targetsTonic || isLastTonicStatement || alreadySecondary || isResolutionTarget || takesNoDominant) {
|
|
7590
|
+
if (targetsTonic || isLastTonicStatement || alreadySecondary || isResolutionTarget || takesNoDominant || isNativeDominant) {
|
|
7248
7591
|
continue;
|
|
7249
7592
|
}
|
|
7250
7593
|
if (draw.prob(harmonic, "reharmonize", i)) {
|
|
@@ -7282,11 +7625,11 @@ function triadQualityOf(chord) {
|
|
|
7282
7625
|
}
|
|
7283
7626
|
return hasThird;
|
|
7284
7627
|
}
|
|
7285
|
-
function diatonicTriadsOf(key) {
|
|
7628
|
+
function diatonicTriadsOf(key, tonic) {
|
|
7286
7629
|
const degrees = scaleTonesInDegreeOrder(key).length;
|
|
7287
7630
|
const triads = [];
|
|
7288
7631
|
for (let degree = 1; degree <= degrees; degree += 1) {
|
|
7289
|
-
triads.push(diatonicTriad(degree, key));
|
|
7632
|
+
triads.push(diatonicTriad(degree, { ...key, tonic }));
|
|
7290
7633
|
}
|
|
7291
7634
|
return triads;
|
|
7292
7635
|
}
|
|
@@ -7320,10 +7663,22 @@ function tritoneSubstituteRoot(dominant, tonic, key) {
|
|
|
7320
7663
|
const resolution = spellPitchClass(pitchClassOf(dominant.rootPc + 5), tonic, key);
|
|
7321
7664
|
return transposeNote(resolution, 1);
|
|
7322
7665
|
}
|
|
7666
|
+
var MEDIANT_THIRDS = {
|
|
7667
|
+
3: "m3",
|
|
7668
|
+
4: "M3",
|
|
7669
|
+
8: "-M3",
|
|
7670
|
+
9: "-m3"
|
|
7671
|
+
};
|
|
7672
|
+
function mediantRoot(source, offset, tonic, key) {
|
|
7673
|
+
const hint = source.rootSpelling;
|
|
7674
|
+
const root = hint !== void 0 && noteToPitchClass(hint) === pitchClassOf(source.rootPc) ? hint : spellPitchClass(source.rootPc, tonic, key);
|
|
7675
|
+
return transposeByInterval(bareSpelling(root), MEDIANT_THIRDS[offset]);
|
|
7676
|
+
}
|
|
7323
7677
|
function substituteChord(chord, key, opts) {
|
|
7324
7678
|
assertOptions(opts, "opts");
|
|
7325
7679
|
const source = toChordData(chord);
|
|
7326
|
-
const
|
|
7680
|
+
const resolved = resolveKey(key);
|
|
7681
|
+
const { scale, tonic } = resolved;
|
|
7327
7682
|
const originalTriad = chordPitchClasses(makeChord(source.rootPc, triadQualityOf(source)));
|
|
7328
7683
|
const candidates = [];
|
|
7329
7684
|
if (soundsDominantSeventh(source)) {
|
|
@@ -7333,24 +7688,27 @@ function substituteChord(chord, key, opts) {
|
|
|
7333
7688
|
type: "tritone"
|
|
7334
7689
|
});
|
|
7335
7690
|
}
|
|
7336
|
-
for (const triad of diatonicTriadsOf(scale)) {
|
|
7691
|
+
for (const triad of diatonicTriadsOf(scale, tonic)) {
|
|
7337
7692
|
const offset = pitchClassOf(triad.rootPc - source.rootPc);
|
|
7338
7693
|
if (THIRD_OFFSETS.includes(offset) && commonToneCount(originalTriad, chordPitchClasses(triad)) === 2) {
|
|
7339
7694
|
candidates.push({ chord: spelledInKey(triad, tonic, scale), type: "relative" });
|
|
7340
7695
|
}
|
|
7341
7696
|
}
|
|
7342
|
-
const targetFunction = functionOf(source,
|
|
7697
|
+
const targetFunction = functionOf(source, resolved);
|
|
7343
7698
|
const parallel = parallelKey(scale);
|
|
7344
|
-
for (const triad of diatonicTriadsOf(parallel)) {
|
|
7345
|
-
if (functionOf(triad,
|
|
7699
|
+
for (const triad of diatonicTriadsOf(parallel, tonic)) {
|
|
7700
|
+
if (functionOf(triad, resolved) === targetFunction && !isDiatonic(triad, resolved)) {
|
|
7346
7701
|
candidates.push({ chord: spelledInKey(triad, tonic, parallel), type: "borrowed" });
|
|
7347
7702
|
}
|
|
7348
7703
|
}
|
|
7349
7704
|
for (const offset of THIRD_OFFSETS) {
|
|
7350
7705
|
for (const quality of ["maj", "min"]) {
|
|
7351
7706
|
const mediant = makeChord(pitchClassOf(source.rootPc + offset), quality);
|
|
7352
|
-
if (commonToneCount(originalTriad, chordPitchClasses(mediant)) === 1 && !isDiatonic(mediant,
|
|
7353
|
-
candidates.push({
|
|
7707
|
+
if (commonToneCount(originalTriad, chordPitchClasses(mediant)) === 1 && !isDiatonic(mediant, resolved)) {
|
|
7708
|
+
candidates.push({
|
|
7709
|
+
chord: spelledOnRoot(mediant, mediantRoot(source, offset, tonic, scale), tonic, scale),
|
|
7710
|
+
type: "chromaticMediant"
|
|
7711
|
+
});
|
|
7354
7712
|
}
|
|
7355
7713
|
}
|
|
7356
7714
|
}
|
|
@@ -7375,14 +7733,15 @@ function substituteChord(chord, key, opts) {
|
|
|
7375
7733
|
results.push({
|
|
7376
7734
|
chord: candidate.chord,
|
|
7377
7735
|
type: candidate.type,
|
|
7378
|
-
roman: chordToRoman(candidate.chord,
|
|
7379
|
-
function: functionOf(candidate.chord,
|
|
7736
|
+
roman: chordToRoman(candidate.chord, resolved),
|
|
7737
|
+
function: functionOf(candidate.chord, resolved)
|
|
7380
7738
|
});
|
|
7381
7739
|
}
|
|
7382
7740
|
return results;
|
|
7383
7741
|
}
|
|
7384
7742
|
function modalInterchangePalette(key) {
|
|
7385
|
-
const
|
|
7743
|
+
const resolved = resolveKey(key);
|
|
7744
|
+
const { scale, tonic } = resolved;
|
|
7386
7745
|
const palette = [];
|
|
7387
7746
|
const seen = /* @__PURE__ */ new Set();
|
|
7388
7747
|
const add = (chord) => {
|
|
@@ -7393,18 +7752,18 @@ function modalInterchangePalette(key) {
|
|
|
7393
7752
|
seen.add(dedupeKey);
|
|
7394
7753
|
palette.push({
|
|
7395
7754
|
chord,
|
|
7396
|
-
roman: chordToRoman(chord,
|
|
7397
|
-
source: borrowedSource(chord,
|
|
7755
|
+
roman: chordToRoman(chord, resolved),
|
|
7756
|
+
source: borrowedSource(chord, resolved)
|
|
7398
7757
|
});
|
|
7399
7758
|
};
|
|
7400
7759
|
const parallel = parallelKey(scale);
|
|
7401
|
-
for (const triad of diatonicTriadsOf(parallel)) {
|
|
7402
|
-
if (!isDiatonic(triad,
|
|
7760
|
+
for (const triad of diatonicTriadsOf(parallel, tonic)) {
|
|
7761
|
+
if (!isDiatonic(triad, resolved) && borrowedSource(triad, resolved) !== null) {
|
|
7403
7762
|
add(spelledInKey(triad, tonic, parallel));
|
|
7404
7763
|
}
|
|
7405
7764
|
}
|
|
7406
7765
|
const neapolitan = makeChord(pitchClassOf(scale.rootPc + 1), "maj");
|
|
7407
|
-
if (!isDiatonic(neapolitan,
|
|
7766
|
+
if (!isDiatonic(neapolitan, resolved)) {
|
|
7408
7767
|
add(spelledOnRoot(neapolitan, transposeNote(tonic, 1), tonic, scale));
|
|
7409
7768
|
}
|
|
7410
7769
|
return palette;
|
|
@@ -7433,12 +7792,13 @@ var DEFAULT_RHYTHM_VELOCITY = 96;
|
|
|
7433
7792
|
function rhythmToNoteEvents(events, pitch, velocity = DEFAULT_RHYTHM_VELOCITY) {
|
|
7434
7793
|
assertMidiPitch(pitch, "pitch");
|
|
7435
7794
|
assertInteger(velocity, "velocity", 0, 127);
|
|
7436
|
-
return assertArray(events, "events").map((
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7795
|
+
return assertArray(events, "events").map((given, index) => {
|
|
7796
|
+
const event = assertRecord(given, `events[${index}]`);
|
|
7797
|
+
const bound = Number.MAX_SAFE_INTEGER;
|
|
7798
|
+
assertRange(event.position, -bound, bound, `events[${index}].position`);
|
|
7799
|
+
assertRange(event.duration, -bound, bound, `events[${index}].duration`);
|
|
7800
|
+
return { pitch, startBeat: event.position, durationBeat: event.duration, velocity };
|
|
7801
|
+
});
|
|
7442
7802
|
}
|
|
7443
7803
|
var DEFAULT_SUBDIVISION = 2;
|
|
7444
7804
|
var DEFAULT_DENSITY = 0.5;
|
|
@@ -7533,10 +7893,14 @@ export {
|
|
|
7533
7893
|
thin,
|
|
7534
7894
|
double,
|
|
7535
7895
|
halfTime,
|
|
7896
|
+
halfTimeFigure,
|
|
7536
7897
|
doubleTime,
|
|
7898
|
+
doubleTimeFigure,
|
|
7537
7899
|
syncopate,
|
|
7538
7900
|
ornamentBy,
|
|
7539
7901
|
deform,
|
|
7902
|
+
resolveDials,
|
|
7903
|
+
deformFigure,
|
|
7540
7904
|
withinCeiling,
|
|
7541
7905
|
isLickMaterial,
|
|
7542
7906
|
BASS_LICKS,
|
|
@@ -7588,4 +7952,4 @@ export {
|
|
|
7588
7952
|
generateRhythm,
|
|
7589
7953
|
rhythmDensity
|
|
7590
7954
|
};
|
|
7591
|
-
//# sourceMappingURL=chunk-
|
|
7955
|
+
//# sourceMappingURL=chunk-FK3DGAIZ.js.map
|