@libraz/libcantus 1.2.0 → 1.3.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/README.md +4 -0
- package/README_ja.md +4 -0
- package/dist/analyze/index.cjs +28 -4
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +151 -5
- package/dist/analyze/index.d.ts +151 -5
- package/dist/analyze/index.js +34 -10
- package/dist/{chunk-DSMJRXCZ.cjs → chunk-4R2LWV7E.cjs} +384 -107
- package/dist/chunk-4R2LWV7E.cjs.map +1 -0
- package/dist/{chunk-J7FHVZII.cjs → chunk-B2PW6EHN.cjs} +2000 -30
- package/dist/chunk-B2PW6EHN.cjs.map +1 -0
- package/dist/{chunk-Q6LFSAKV.js → chunk-CPMB7P75.js} +1973 -3
- package/dist/chunk-CPMB7P75.js.map +1 -0
- package/dist/{chunk-H2KJHBW4.js → chunk-GNVN4PPJ.js} +1871 -5
- package/dist/chunk-GNVN4PPJ.js.map +1 -0
- package/dist/{chunk-SGLEQVJH.cjs → chunk-IFZDWIKM.cjs} +1868 -2
- package/dist/chunk-IFZDWIKM.cjs.map +1 -0
- package/dist/{chunk-FBH75DAY.js → chunk-IPEF6NWB.js} +287 -10
- package/dist/chunk-IPEF6NWB.js.map +1 -0
- package/dist/{chunk-HYJJ2V2I.js → chunk-MLBPXA4Z.js} +300 -720
- package/dist/chunk-MLBPXA4Z.js.map +1 -0
- package/dist/{chunk-HWHGOVO6.cjs → chunk-PWC4CRWT.cjs} +1 -1
- package/dist/{chunk-HWHGOVO6.cjs.map → chunk-PWC4CRWT.cjs.map} +1 -1
- package/dist/{chunk-HPVSH7SF.cjs → chunk-YFA7X2F6.cjs} +350 -770
- package/dist/chunk-YFA7X2F6.cjs.map +1 -0
- package/dist/{chunk-NFJ4P6ZZ.js → chunk-ZBUROTJQ.js} +1 -1
- package/dist/generate/index.cjs +15 -3
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +84 -6
- package/dist/generate/index.d.ts +84 -6
- package/dist/generate/index.js +14 -2
- package/dist/{index-BNSM-DSk.d.cts → index-CY-3NVfy.d.cts} +484 -3
- package/dist/{index-BJgP97N1.d.ts → index-zaZdmnU4.d.ts} +484 -3
- package/dist/index.cjs +43 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +48 -12
- package/dist/model/index.cjs +5 -5
- package/dist/model/index.d.cts +203 -7
- package/dist/model/index.d.ts +203 -7
- package/dist/model/index.js +4 -4
- package/dist/{index-DGCNKAUr.d.cts → profile-CQMEqdH9.d.cts} +65 -598
- package/dist/{index-BRMbPkHE.d.ts → profile-DLdlIpS9.d.ts} +65 -598
- package/dist/{index-BBWNZS0f.d.cts → types-D-XgxGvp.d.cts} +1169 -13
- package/dist/{index-CJBBiAmU.d.ts → types-Dh7C064C.d.ts} +1169 -13
- package/package.json +5 -5
- package/dist/chunk-DSMJRXCZ.cjs.map +0 -1
- package/dist/chunk-FBH75DAY.js.map +0 -1
- package/dist/chunk-H2KJHBW4.js.map +0 -1
- package/dist/chunk-HPVSH7SF.cjs.map +0 -1
- package/dist/chunk-HYJJ2V2I.js.map +0 -1
- package/dist/chunk-J7FHVZII.cjs.map +0 -1
- package/dist/chunk-Q6LFSAKV.js.map +0 -1
- package/dist/chunk-SGLEQVJH.cjs.map +0 -1
- /package/dist/{chunk-NFJ4P6ZZ.js.map → chunk-ZBUROTJQ.js.map} +0 -0
|
@@ -8,7 +8,31 @@
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
var _chunkIFZDWIKMcjs = require('./chunk-IFZDWIKM.cjs');
|
|
34
|
+
|
|
35
|
+
|
|
12
36
|
|
|
13
37
|
|
|
14
38
|
|
|
@@ -55,6 +79,8 @@ var _chunkPM4XJE2Ycjs = require('./chunk-PM4XJE2Y.cjs');
|
|
|
55
79
|
|
|
56
80
|
|
|
57
81
|
|
|
82
|
+
|
|
83
|
+
|
|
58
84
|
|
|
59
85
|
|
|
60
86
|
|
|
@@ -85,6 +111,15 @@ var _chunk3R3QVKY5cjs = require('./chunk-3R3QVKY5.cjs');
|
|
|
85
111
|
|
|
86
112
|
|
|
87
113
|
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
88
123
|
|
|
89
124
|
|
|
90
125
|
|
|
@@ -3897,7 +3932,7 @@ function applyGrooveTemplate(events, template, ts) {
|
|
|
3897
3932
|
// src/generate/harmonize/nct.ts
|
|
3898
3933
|
function snapToPulse(beat, pulse2) {
|
|
3899
3934
|
const snapped = Math.round(beat / pulse2) * pulse2;
|
|
3900
|
-
return Math.abs(snapped - beat) <=
|
|
3935
|
+
return Math.abs(snapped - beat) <= _chunkIFZDWIKMcjs.HUMANIZE_ADJACENCY ? snapped : beat;
|
|
3901
3936
|
}
|
|
3902
3937
|
var STEP_SEMITONES = 2;
|
|
3903
3938
|
function isStep(a, b) {
|
|
@@ -3928,7 +3963,7 @@ function roleOfNote(melody, index, ts, pulse2) {
|
|
|
3928
3963
|
}
|
|
3929
3964
|
const intoNote = Math.abs(prev.startBeat + prev.durationBeat - note2.startBeat);
|
|
3930
3965
|
const outOfNote = Math.abs(note2.startBeat + note2.durationBeat - next.startBeat);
|
|
3931
|
-
if (intoNote >
|
|
3966
|
+
if (intoNote > _chunkIFZDWIKMcjs.HUMANIZE_ADJACENCY || outOfNote > _chunkIFZDWIKMcjs.HUMANIZE_ADJACENCY) {
|
|
3932
3967
|
return "structural";
|
|
3933
3968
|
}
|
|
3934
3969
|
const weight = _chunkS5BKZZGMcjs.metricWeight.call(void 0, snapToPulse(note2.startBeat, pulse2), ts);
|
|
@@ -3979,7 +4014,7 @@ var DEFAULT_PLACEMENT = {
|
|
|
3979
4014
|
var DEFAULT_METER = { numerator: 4, denominator: 4 };
|
|
3980
4015
|
var TIE_BREAK_JITTER2 = 1e-6;
|
|
3981
4016
|
function cadencesThroughRaisedSeventh(key) {
|
|
3982
|
-
return
|
|
4017
|
+
return _chunkIFZDWIKMcjs.isMinorKey.call(void 0, key) && _chunk3R3QVKY5cjs.scaleSystemOf.call(void 0, key) === "common-practice";
|
|
3983
4018
|
}
|
|
3984
4019
|
function isKeyTone(pitch, key) {
|
|
3985
4020
|
return _chunk3R3QVKY5cjs.isScaleTone.call(void 0, pitch, key) || cadencesThroughRaisedSeventh(key) && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitch) === _chunk3R3QVKY5cjs.leadingTonePcOf.call(void 0, key);
|
|
@@ -4390,12 +4425,12 @@ function buildCandidates(key, harmonic) {
|
|
|
4390
4425
|
pcs: _chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, _chunk3R3QVKY5cjs.makeChord.call(void 0, rootPc, "dom7"))
|
|
4391
4426
|
});
|
|
4392
4427
|
}
|
|
4393
|
-
const parallel =
|
|
4428
|
+
const parallel = _chunkIFZDWIKMcjs.parallelKey.call(void 0, key);
|
|
4394
4429
|
const parallelTones = _chunk3R3QVKY5cjs.scaleTonesInDegreeOrder.call(void 0, parallel);
|
|
4395
4430
|
const borrowed = [];
|
|
4396
4431
|
for (let degree = 1; degree <= parallelTones.length; degree += 1) {
|
|
4397
4432
|
const chord = _chunk3R3QVKY5cjs.diatonicTriad.call(void 0, degree, parallel);
|
|
4398
|
-
if (!
|
|
4433
|
+
if (!_chunkIFZDWIKMcjs.isDiatonic.call(void 0, chord, key)) {
|
|
4399
4434
|
borrowed.push(chord);
|
|
4400
4435
|
}
|
|
4401
4436
|
}
|
|
@@ -4799,7 +4834,7 @@ function scaleTime(cell, factor) {
|
|
|
4799
4834
|
}
|
|
4800
4835
|
function developMotif(cell, timeline, key, bars, ts) {
|
|
4801
4836
|
const meter = _chunkS5BKZZGMcjs.meterAt.call(void 0, 0, _chunkS5BKZZGMcjs.toMeterData.call(void 0, ts, "ts"));
|
|
4802
|
-
|
|
4837
|
+
_chunkIFZDWIKMcjs.assertChordTimeline.call(void 0, timeline);
|
|
4803
4838
|
_chunkS5BKZZGMcjs.assertPositiveInt.call(void 0, bars, "development bars");
|
|
4804
4839
|
const cellNotes = assertCellNotes(cell, "motif notes");
|
|
4805
4840
|
const scale = _chunk3R3QVKY5cjs.toKeyScale.call(void 0, key);
|
|
@@ -4837,6 +4872,1063 @@ function developMotif(cell, timeline, key, bars, ts) {
|
|
|
4837
4872
|
return inTimeOrder({ notes: out });
|
|
4838
4873
|
}
|
|
4839
4874
|
|
|
4875
|
+
// src/generate/plan/internal.ts
|
|
4876
|
+
var STRONG_WEIGHT = 2;
|
|
4877
|
+
function positionClass(beat, meters) {
|
|
4878
|
+
const weight = _chunkS5BKZZGMcjs.metricWeight.call(void 0, beat, meters);
|
|
4879
|
+
return weight >= STRONG_WEIGHT ? "strong" : weight > 0 ? "weak" : "off";
|
|
4880
|
+
}
|
|
4881
|
+
|
|
4882
|
+
// src/generate/plan/types.ts
|
|
4883
|
+
var COMPOSITION_PLAN_VERSION = 1;
|
|
4884
|
+
function tent(t, apex) {
|
|
4885
|
+
if (t <= apex) {
|
|
4886
|
+
return apex === 0 ? 1 : -1 + 2 * t / apex;
|
|
4887
|
+
}
|
|
4888
|
+
return apex === 1 ? 1 : 1 - 2 * (t - apex) / (1 - apex);
|
|
4889
|
+
}
|
|
4890
|
+
function plannedContourAt(shape, peakPosition, t) {
|
|
4891
|
+
switch (shape) {
|
|
4892
|
+
case "arch":
|
|
4893
|
+
return tent(t, peakPosition);
|
|
4894
|
+
case "ascending":
|
|
4895
|
+
return -1 + 2 * t;
|
|
4896
|
+
case "descending":
|
|
4897
|
+
return 1 - 2 * t;
|
|
4898
|
+
case "wave":
|
|
4899
|
+
return t < 0.5 ? tent(2 * t, 0.5) : tent(2 * t - 1, 0.5);
|
|
4900
|
+
case "static":
|
|
4901
|
+
return 0;
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4904
|
+
|
|
4905
|
+
// src/generate/plan/validate.ts
|
|
4906
|
+
var MASS_TOLERANCE = 1e-9;
|
|
4907
|
+
function assertString(value, name) {
|
|
4908
|
+
if (typeof value !== "string") {
|
|
4909
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name} must be a string; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, value)}`);
|
|
4910
|
+
}
|
|
4911
|
+
return value;
|
|
4912
|
+
}
|
|
4913
|
+
function assertSpanOrder(startBeat, endBeat, name) {
|
|
4914
|
+
if (endBeat < startBeat) {
|
|
4915
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
4916
|
+
`${name}.endBeat must be at least ${name}.startBeat; received ${endBeat} < ${startBeat}`
|
|
4917
|
+
);
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
function assertNullable(value, name, validate) {
|
|
4921
|
+
if (value === void 0) {
|
|
4922
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name} must be present; received undefined`);
|
|
4923
|
+
}
|
|
4924
|
+
return value === null ? null : validate(value, name);
|
|
4925
|
+
}
|
|
4926
|
+
function assertMassDistribution(value, length, name) {
|
|
4927
|
+
const shares = _chunkS5BKZZGMcjs.assertArray.call(void 0, value, name);
|
|
4928
|
+
if (shares.length !== length) {
|
|
4929
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name} must have length ${length}; received ${shares.length}`);
|
|
4930
|
+
}
|
|
4931
|
+
let sum = 0;
|
|
4932
|
+
for (let index = 0; index < shares.length; index += 1) {
|
|
4933
|
+
const share = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, shares[index], `${name}[${index}]`);
|
|
4934
|
+
if (share < 0) {
|
|
4935
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name}[${index}] must be at least 0; received ${share}`);
|
|
4936
|
+
}
|
|
4937
|
+
sum += share;
|
|
4938
|
+
}
|
|
4939
|
+
if (Math.abs(sum - 1) > MASS_TOLERANCE) {
|
|
4940
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name} must sum to 1; received ${sum}`);
|
|
4941
|
+
}
|
|
4942
|
+
return shares;
|
|
4943
|
+
}
|
|
4944
|
+
function assertResolvedKeyField(value, name) {
|
|
4945
|
+
const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, value, name);
|
|
4946
|
+
try {
|
|
4947
|
+
_chunk3R3QVKY5cjs.resolveKey.call(void 0, read);
|
|
4948
|
+
} catch (error) {
|
|
4949
|
+
if (error instanceof _chunkS5BKZZGMcjs.InvalidInputError) {
|
|
4950
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name} must be a valid key: ${error.message}`);
|
|
4951
|
+
}
|
|
4952
|
+
throw error;
|
|
4953
|
+
}
|
|
4954
|
+
return read;
|
|
4955
|
+
}
|
|
4956
|
+
function assertPlannedSection(value, name) {
|
|
4957
|
+
const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, value, name);
|
|
4958
|
+
assertString(read.label, `${name}.label`);
|
|
4959
|
+
const startBeat = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.startBeat, `${name}.startBeat`);
|
|
4960
|
+
const endBeat = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.endBeat, `${name}.endBeat`);
|
|
4961
|
+
assertSpanOrder(startBeat, endBeat, name);
|
|
4962
|
+
return read;
|
|
4963
|
+
}
|
|
4964
|
+
function assertPlannedPhrase(value, name, sectionCount, motifCount) {
|
|
4965
|
+
const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, value, name);
|
|
4966
|
+
const startBeat = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.startBeat, `${name}.startBeat`);
|
|
4967
|
+
const endBeat = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.endBeat, `${name}.endBeat`);
|
|
4968
|
+
assertSpanOrder(startBeat, endBeat, name);
|
|
4969
|
+
assertNullable(
|
|
4970
|
+
read.section,
|
|
4971
|
+
`${name}.section`,
|
|
4972
|
+
(v, n) => _chunkS5BKZZGMcjs.assertInteger.call(void 0, v, n, 0, Math.max(sectionCount - 1, 0))
|
|
4973
|
+
);
|
|
4974
|
+
assertNullable(read.cadence, `${name}.cadence`, (v, n) => _chunkS5BKZZGMcjs.assertOneOf.call(void 0, v, _chunkIFZDWIKMcjs.CADENCE_TYPES, n));
|
|
4975
|
+
_chunkS5BKZZGMcjs.assertOneOf.call(void 0, read.shape, _chunkIFZDWIKMcjs.MELODIC_CONTOUR_SHAPES, `${name}.shape`);
|
|
4976
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, read.peakPosition, 0, 1, `${name}.peakPosition`);
|
|
4977
|
+
const register = _chunkS5BKZZGMcjs.assertRecord.call(void 0,
|
|
4978
|
+
read.register,
|
|
4979
|
+
`${name}.register`
|
|
4980
|
+
);
|
|
4981
|
+
_chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, register.low, `${name}.register.low`);
|
|
4982
|
+
_chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, register.high, `${name}.register.high`);
|
|
4983
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, register.mean, 0, 127, `${name}.register.mean`);
|
|
4984
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, read.onsetDensity, 0, Number.MAX_SAFE_INTEGER, `${name}.onsetDensity`);
|
|
4985
|
+
const motifs = _chunkS5BKZZGMcjs.assertArray.call(void 0, read.motifs, `${name}.motifs`);
|
|
4986
|
+
for (let index = 0; index < motifs.length; index += 1) {
|
|
4987
|
+
_chunkS5BKZZGMcjs.assertInteger.call(void 0,
|
|
4988
|
+
motifs[index],
|
|
4989
|
+
`${name}.motifs[${index}]`,
|
|
4990
|
+
0,
|
|
4991
|
+
Math.max(motifCount - 1, 0)
|
|
4992
|
+
);
|
|
4993
|
+
}
|
|
4994
|
+
return read;
|
|
4995
|
+
}
|
|
4996
|
+
function assertPlannedChord(value, name, keys) {
|
|
4997
|
+
const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, value, name);
|
|
4998
|
+
const startBeat = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.startBeat, `${name}.startBeat`);
|
|
4999
|
+
const endBeat = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.endBeat, `${name}.endBeat`);
|
|
5000
|
+
assertSpanOrder(startBeat, endBeat, name);
|
|
5001
|
+
const keyIndex = _chunkS5BKZZGMcjs.assertInteger.call(void 0, read.key, `${name}.key`, 0, Math.max(keys.length - 1, 0));
|
|
5002
|
+
const roman = assertString(read.roman, `${name}.roman`);
|
|
5003
|
+
try {
|
|
5004
|
+
_chunkIFZDWIKMcjs.romanToChord.call(void 0, roman, keys[keyIndex]);
|
|
5005
|
+
} catch (error) {
|
|
5006
|
+
if (error instanceof _chunkS5BKZZGMcjs.InvalidInputError) {
|
|
5007
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
5008
|
+
`${name}.roman must be readable by romanToChord; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, roman)}: ${error.message}`
|
|
5009
|
+
);
|
|
5010
|
+
}
|
|
5011
|
+
throw error;
|
|
5012
|
+
}
|
|
5013
|
+
return read;
|
|
5014
|
+
}
|
|
5015
|
+
function assertPlannedMotifRelation(value, name) {
|
|
5016
|
+
const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, value, name);
|
|
5017
|
+
_chunkS5BKZZGMcjs.assertOneOf.call(void 0, read.kind, _chunkIFZDWIKMcjs.MOTIF_RELATION_KINDS, `${name}.kind`);
|
|
5018
|
+
_chunkS5BKZZGMcjs.assertFlag.call(void 0, read.sequence, `${name}.sequence`);
|
|
5019
|
+
_chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.semitones, `${name}.semitones`);
|
|
5020
|
+
if (read.kind === "tonalTransposition") {
|
|
5021
|
+
_chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.degrees, `${name}.degrees`);
|
|
5022
|
+
} else if (read.degrees !== void 0) {
|
|
5023
|
+
_chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.degrees, `${name}.degrees`);
|
|
5024
|
+
}
|
|
5025
|
+
const timeRatio = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.timeRatio, `${name}.timeRatio`);
|
|
5026
|
+
if (timeRatio <= 0) {
|
|
5027
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name}.timeRatio must be positive; received ${timeRatio}`);
|
|
5028
|
+
}
|
|
5029
|
+
return read;
|
|
5030
|
+
}
|
|
5031
|
+
function assertPlannedMotif(value, name, ownIndex, motifCount, phraseCount) {
|
|
5032
|
+
const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, value, name);
|
|
5033
|
+
_chunkS5BKZZGMcjs.assertInteger.call(void 0, read.phrase, `${name}.phrase`, 0, Math.max(phraseCount - 1, 0));
|
|
5034
|
+
const startBeat = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.startBeat, `${name}.startBeat`);
|
|
5035
|
+
const endBeat = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.endBeat, `${name}.endBeat`);
|
|
5036
|
+
assertSpanOrder(startBeat, endBeat, name);
|
|
5037
|
+
_chunkS5BKZZGMcjs.assertPositiveInt.call(void 0, read.notes, `${name}.notes`);
|
|
5038
|
+
const from = assertNullable(
|
|
5039
|
+
read.from,
|
|
5040
|
+
`${name}.from`,
|
|
5041
|
+
(v, n) => _chunkS5BKZZGMcjs.assertInteger.call(void 0, v, n, 0, Math.max(motifCount - 1, 0))
|
|
5042
|
+
);
|
|
5043
|
+
if (from !== null && from >= ownIndex) {
|
|
5044
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
5045
|
+
`${name}.from must be less than this motif's own index (${ownIndex}); received ${from}`
|
|
5046
|
+
);
|
|
5047
|
+
}
|
|
5048
|
+
const relation = assertNullable(read.relation, `${name}.relation`, assertPlannedMotifRelation);
|
|
5049
|
+
if (from === null && relation !== null) {
|
|
5050
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
5051
|
+
`${name}.relation must be null for a root motif (from = null); received a relation`
|
|
5052
|
+
);
|
|
5053
|
+
}
|
|
5054
|
+
const rhythm = assertNullable(read.rhythm, `${name}.rhythm`, (v, n) => _chunkS5BKZZGMcjs.assertArray.call(void 0, v, n));
|
|
5055
|
+
if (rhythm !== null) {
|
|
5056
|
+
if (from !== null) {
|
|
5057
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name}.rhythm must be null for a derived statement`);
|
|
5058
|
+
}
|
|
5059
|
+
if (rhythm.length !== read.notes - 1) {
|
|
5060
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
5061
|
+
`${name}.rhythm must hold notes - 1 (${read.notes - 1}) gaps; received ${rhythm.length}`
|
|
5062
|
+
);
|
|
5063
|
+
}
|
|
5064
|
+
let total = 0;
|
|
5065
|
+
for (let index = 0; index < rhythm.length; index += 1) {
|
|
5066
|
+
const gap = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, rhythm[index], `${name}.rhythm[${index}]`);
|
|
5067
|
+
if (gap <= 0) {
|
|
5068
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name}.rhythm[${index}] must be positive; received ${gap}`);
|
|
5069
|
+
}
|
|
5070
|
+
total += gap;
|
|
5071
|
+
}
|
|
5072
|
+
if (total >= endBeat - startBeat - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
5073
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
5074
|
+
`${name}.rhythm must sum to less than the statement's length (${endBeat - startBeat}); received ${total}`
|
|
5075
|
+
);
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
return read;
|
|
5079
|
+
}
|
|
5080
|
+
function assertPlannedRhythm(value, name) {
|
|
5081
|
+
const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, value, name);
|
|
5082
|
+
assertMassDistribution(read.onsetLevels, 6, `${name}.onsetLevels`);
|
|
5083
|
+
assertMassDistribution(read.interOnsetShares, _chunkIFZDWIKMcjs.RHYTHM_IOI_BINS.length, `${name}.interOnsetShares`);
|
|
5084
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, read.syncopation, 0, 1, `${name}.syncopation`);
|
|
5085
|
+
return read;
|
|
5086
|
+
}
|
|
5087
|
+
function assertCompositionPlan(value, name = "composition plan") {
|
|
5088
|
+
const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, value, name);
|
|
5089
|
+
if (read.planVersion !== COMPOSITION_PLAN_VERSION) {
|
|
5090
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
5091
|
+
`${name}.planVersion must be ${COMPOSITION_PLAN_VERSION}; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, read.planVersion)}`
|
|
5092
|
+
);
|
|
5093
|
+
}
|
|
5094
|
+
_chunkS5BKZZGMcjs.assertInteger.call(void 0, read.seed, `${name}.seed`, 0, 4294967295);
|
|
5095
|
+
_chunkS5BKZZGMcjs.assertInteger.call(void 0,
|
|
5096
|
+
read.algorithmVersion,
|
|
5097
|
+
`${name}.algorithmVersion`,
|
|
5098
|
+
_chunkCGBADIO5cjs.MIN_ALGORITHM_VERSION,
|
|
5099
|
+
_chunkCGBADIO5cjs.ALGORITHM_VERSION
|
|
5100
|
+
);
|
|
5101
|
+
const keys = _chunkS5BKZZGMcjs.assertArray.call(void 0, read.keys, `${name}.keys`);
|
|
5102
|
+
if (keys.length === 0) {
|
|
5103
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name}.keys must hold at least one key; received an empty array`);
|
|
5104
|
+
}
|
|
5105
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
5106
|
+
assertResolvedKeyField(keys[index], `${name}.keys[${index}]`);
|
|
5107
|
+
}
|
|
5108
|
+
_chunkS5BKZZGMcjs.assertMeterMap.call(void 0, read.meters, `${name}.meters`);
|
|
5109
|
+
const span = _chunkS5BKZZGMcjs.assertRecord.call(void 0,
|
|
5110
|
+
read.span,
|
|
5111
|
+
`${name}.span`
|
|
5112
|
+
);
|
|
5113
|
+
const spanStart = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, span.startBeat, `${name}.span.startBeat`);
|
|
5114
|
+
const spanEnd = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, span.endBeat, `${name}.span.endBeat`);
|
|
5115
|
+
assertSpanOrder(spanStart, spanEnd, `${name}.span`);
|
|
5116
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, span.bars, 0, Number.MAX_SAFE_INTEGER, `${name}.span.bars`);
|
|
5117
|
+
const sections = _chunkS5BKZZGMcjs.assertArray.call(void 0, read.sections, `${name}.sections`);
|
|
5118
|
+
for (let index = 0; index < sections.length; index += 1) {
|
|
5119
|
+
assertPlannedSection(sections[index], `${name}.sections[${index}]`);
|
|
5120
|
+
}
|
|
5121
|
+
const phrases = _chunkS5BKZZGMcjs.assertArray.call(void 0, read.phrases, `${name}.phrases`);
|
|
5122
|
+
const motifs = _chunkS5BKZZGMcjs.assertArray.call(void 0, read.motifs, `${name}.motifs`);
|
|
5123
|
+
for (let index = 0; index < phrases.length; index += 1) {
|
|
5124
|
+
assertPlannedPhrase(
|
|
5125
|
+
phrases[index],
|
|
5126
|
+
`${name}.phrases[${index}]`,
|
|
5127
|
+
sections.length,
|
|
5128
|
+
motifs.length
|
|
5129
|
+
);
|
|
5130
|
+
}
|
|
5131
|
+
const harmony = _chunkS5BKZZGMcjs.assertArray.call(void 0, read.harmony, `${name}.harmony`);
|
|
5132
|
+
for (let index = 0; index < harmony.length; index += 1) {
|
|
5133
|
+
assertPlannedChord(harmony[index], `${name}.harmony[${index}]`, keys);
|
|
5134
|
+
}
|
|
5135
|
+
for (let index = 0; index < motifs.length; index += 1) {
|
|
5136
|
+
assertPlannedMotif(
|
|
5137
|
+
motifs[index],
|
|
5138
|
+
`${name}.motifs[${index}]`,
|
|
5139
|
+
index,
|
|
5140
|
+
motifs.length,
|
|
5141
|
+
phrases.length
|
|
5142
|
+
);
|
|
5143
|
+
}
|
|
5144
|
+
assertPlannedRhythm(read.rhythm, `${name}.rhythm`);
|
|
5145
|
+
return read;
|
|
5146
|
+
}
|
|
5147
|
+
|
|
5148
|
+
// src/generate/plan/timeline.ts
|
|
5149
|
+
function planTimeline(plan) {
|
|
5150
|
+
return planChordTimeline(assertCompositionPlan(plan));
|
|
5151
|
+
}
|
|
5152
|
+
function planChordTimeline(plan) {
|
|
5153
|
+
const spans = plan.harmony.map((chord) => {
|
|
5154
|
+
const key = plan.keys[chord.key];
|
|
5155
|
+
return _chunk3R3QVKY5cjs.spanFromChord.call(void 0, _chunkIFZDWIKMcjs.romanToChord.call(void 0, chord.roman, key), chord.startBeat);
|
|
5156
|
+
});
|
|
5157
|
+
return _chunkIFZDWIKMcjs.chordTimelineFromChords.call(void 0, spans, plan.span.endBeat);
|
|
5158
|
+
}
|
|
5159
|
+
|
|
5160
|
+
// src/generate/plan/harmony.ts
|
|
5161
|
+
var HARMONY_OK_KINDS = /* @__PURE__ */ new Set([
|
|
5162
|
+
"chordTone",
|
|
5163
|
+
"tension",
|
|
5164
|
+
"suspension",
|
|
5165
|
+
"appoggiatura",
|
|
5166
|
+
"anticipation",
|
|
5167
|
+
"passing",
|
|
5168
|
+
"neighbor"
|
|
5169
|
+
]);
|
|
5170
|
+
var CADENCE_DEGREES = {
|
|
5171
|
+
authentic: [1, 3],
|
|
5172
|
+
plagal: [1, 3],
|
|
5173
|
+
half: [2, 5, 7],
|
|
5174
|
+
phrygian: [2, 5, 7],
|
|
5175
|
+
modal: [1]
|
|
5176
|
+
};
|
|
5177
|
+
function planKeyAt(plan) {
|
|
5178
|
+
const regions = [...plan.harmony].sort((a, b) => a.startBeat - b.startBeat).map((chord) => ({
|
|
5179
|
+
startBeat: chord.startBeat,
|
|
5180
|
+
endBeat: chord.endBeat,
|
|
5181
|
+
key: plan.keys[chord.key],
|
|
5182
|
+
confidence: 1
|
|
5183
|
+
}));
|
|
5184
|
+
return _chunkIFZDWIKMcjs.keyLookup.call(void 0, regions, plan.keys[0]);
|
|
5185
|
+
}
|
|
5186
|
+
function cadencePitchClasses(plan, timeline, cadence, beat) {
|
|
5187
|
+
if (cadence === "deceptive") {
|
|
5188
|
+
const chord = timeline.at(beat);
|
|
5189
|
+
return chord === null ? null : _chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, chord);
|
|
5190
|
+
}
|
|
5191
|
+
const entry = plan.harmony.find((chord) => beat >= chord.startBeat && beat < chord.endBeat);
|
|
5192
|
+
const tones = _chunk3R3QVKY5cjs.scaleTonesInDegreeOrder.call(void 0, _chunk3R3QVKY5cjs.scaleOf.call(void 0, plan.keys[_nullishCoalesce(_optionalChain([entry, 'optionalAccess', _59 => _59.key]), () => ( 0))]));
|
|
5193
|
+
return CADENCE_DEGREES[cadence].map((degree) => tones[degree - 1]).filter((pc) => pc !== void 0);
|
|
5194
|
+
}
|
|
5195
|
+
function planHarmonyMisfits(plan, timeline = planChordTimeline(plan)) {
|
|
5196
|
+
const keyAt = planKeyAt(plan);
|
|
5197
|
+
const scaleAt2 = (beat) => _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat));
|
|
5198
|
+
return (line) => {
|
|
5199
|
+
const analyzed = _chunkIFZDWIKMcjs.analyzeVoice.call(void 0, line, timeline.at, scaleAt2);
|
|
5200
|
+
return line.map((note2, index) => {
|
|
5201
|
+
const position = positionClass(note2.startBeat, plan.meters);
|
|
5202
|
+
if (position === "off") {
|
|
5203
|
+
return null;
|
|
5204
|
+
}
|
|
5205
|
+
const kinds = (_nullishCoalesce(_optionalChain([analyzed, 'access', _60 => _60[index], 'optionalAccess', _61 => _61.labels]), () => ( []))).map((label) => label.kind);
|
|
5206
|
+
if (kinds.some((kind) => HARMONY_OK_KINDS.has(kind))) {
|
|
5207
|
+
return null;
|
|
5208
|
+
}
|
|
5209
|
+
return position === "weak" && _chunk3R3QVKY5cjs.isScaleTone.call(void 0, note2.pitch, scaleAt2(note2.startBeat)) ? null : kinds;
|
|
5210
|
+
});
|
|
5211
|
+
};
|
|
5212
|
+
}
|
|
5213
|
+
|
|
5214
|
+
// src/generate/melody/pitch-dp.ts
|
|
5215
|
+
var NO_LEAP = 0;
|
|
5216
|
+
var LEAP_UP = 1;
|
|
5217
|
+
var LEAP_DOWN = 2;
|
|
5218
|
+
var LEAP_STATES = 3;
|
|
5219
|
+
var SEARCH_LABEL = "melody pitch search";
|
|
5220
|
+
var WIDEST_THIRD = 4;
|
|
5221
|
+
var LEAP_CONTINUATION_COST = 0.5;
|
|
5222
|
+
function intervalCost(size) {
|
|
5223
|
+
if (size === 0) {
|
|
5224
|
+
return 0.2;
|
|
5225
|
+
}
|
|
5226
|
+
if (size <= 2) {
|
|
5227
|
+
return 0;
|
|
5228
|
+
}
|
|
5229
|
+
if (size <= WIDEST_THIRD) {
|
|
5230
|
+
return 0.3;
|
|
5231
|
+
}
|
|
5232
|
+
if (size <= 7) {
|
|
5233
|
+
return 0.6;
|
|
5234
|
+
}
|
|
5235
|
+
return 1;
|
|
5236
|
+
}
|
|
5237
|
+
function leapOf(delta) {
|
|
5238
|
+
if (Math.abs(delta) <= WIDEST_THIRD) {
|
|
5239
|
+
return NO_LEAP;
|
|
5240
|
+
}
|
|
5241
|
+
return delta > 0 ? LEAP_UP : LEAP_DOWN;
|
|
5242
|
+
}
|
|
5243
|
+
function stepCost(from, to, leap, bothFixed) {
|
|
5244
|
+
const delta = to - from;
|
|
5245
|
+
const size = Math.abs(delta);
|
|
5246
|
+
if (!bothFixed && _chunk3R3QVKY5cjs.isForbiddenMelodicLeap.call(void 0, from, to)) {
|
|
5247
|
+
return Number.POSITIVE_INFINITY;
|
|
5248
|
+
}
|
|
5249
|
+
let cost = intervalCost(size);
|
|
5250
|
+
if (leap === LEAP_UP && delta > 0 || leap === LEAP_DOWN && delta < 0) {
|
|
5251
|
+
cost += LEAP_CONTINUATION_COST;
|
|
5252
|
+
}
|
|
5253
|
+
return cost;
|
|
5254
|
+
}
|
|
5255
|
+
function searchPitches(slots, budget, extra) {
|
|
5256
|
+
const first = slots[0];
|
|
5257
|
+
if (first === void 0) {
|
|
5258
|
+
return [];
|
|
5259
|
+
}
|
|
5260
|
+
let previous = _chunkS5BKZZGMcjs.allocateCandidateTable.call(void 0,
|
|
5261
|
+
first.candidates.length,
|
|
5262
|
+
LEAP_STATES,
|
|
5263
|
+
SEARCH_LABEL,
|
|
5264
|
+
budget
|
|
5265
|
+
).fill(Number.POSITIVE_INFINITY);
|
|
5266
|
+
for (let j = 0; j < first.candidates.length; j += 1) {
|
|
5267
|
+
previous[j * LEAP_STATES + NO_LEAP] = first.cost(first.candidates[j]);
|
|
5268
|
+
}
|
|
5269
|
+
const backs = [];
|
|
5270
|
+
for (let k = 1; k < slots.length; k += 1) {
|
|
5271
|
+
const before = slots[k - 1];
|
|
5272
|
+
const slot = slots[k];
|
|
5273
|
+
const unary = slot.candidates.map((pitch) => slot.cost(pitch));
|
|
5274
|
+
const current = _chunkS5BKZZGMcjs.allocateCandidateTable.call(void 0,
|
|
5275
|
+
slot.candidates.length,
|
|
5276
|
+
LEAP_STATES,
|
|
5277
|
+
SEARCH_LABEL,
|
|
5278
|
+
budget
|
|
5279
|
+
).fill(Number.POSITIVE_INFINITY);
|
|
5280
|
+
const back = _chunkS5BKZZGMcjs.allocateChoiceTable.call(void 0,
|
|
5281
|
+
slot.candidates.length,
|
|
5282
|
+
LEAP_STATES,
|
|
5283
|
+
SEARCH_LABEL,
|
|
5284
|
+
budget
|
|
5285
|
+
).fill(-1);
|
|
5286
|
+
const bothFixed = before.fixed && slot.fixed;
|
|
5287
|
+
for (let state2 = 0; state2 < previous.length; state2 += 1) {
|
|
5288
|
+
const reached = previous[state2];
|
|
5289
|
+
if (reached === Number.POSITIVE_INFINITY) {
|
|
5290
|
+
continue;
|
|
5291
|
+
}
|
|
5292
|
+
const from = before.candidates[Math.floor(state2 / LEAP_STATES)];
|
|
5293
|
+
const leap = state2 % LEAP_STATES;
|
|
5294
|
+
for (let j = 0; j < slot.candidates.length; j += 1) {
|
|
5295
|
+
const to = slot.candidates[j];
|
|
5296
|
+
const step = stepCost(from, to, leap, bothFixed);
|
|
5297
|
+
if (step === Number.POSITIVE_INFINITY) {
|
|
5298
|
+
continue;
|
|
5299
|
+
}
|
|
5300
|
+
const total = reached + step + unary[j] + (extra ? extra(k, from, to) : 0);
|
|
5301
|
+
const index = j * LEAP_STATES + leapOf(to - from);
|
|
5302
|
+
if (total < current[index]) {
|
|
5303
|
+
current[index] = total;
|
|
5304
|
+
back[index] = state2;
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
}
|
|
5308
|
+
backs.push(back);
|
|
5309
|
+
previous = current;
|
|
5310
|
+
}
|
|
5311
|
+
let best = -1;
|
|
5312
|
+
for (let state2 = 0; state2 < previous.length; state2 += 1) {
|
|
5313
|
+
if (previous[state2] < Number.POSITIVE_INFINITY && (best < 0 || previous[state2] < previous[best])) {
|
|
5314
|
+
best = state2;
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
if (best < 0) {
|
|
5318
|
+
return null;
|
|
5319
|
+
}
|
|
5320
|
+
const pitches = new Array(slots.length);
|
|
5321
|
+
let state = best;
|
|
5322
|
+
for (let k = slots.length - 1; k >= 0; k -= 1) {
|
|
5323
|
+
pitches[k] = slots[k].candidates[Math.floor(state / LEAP_STATES)];
|
|
5324
|
+
if (k > 0) {
|
|
5325
|
+
state = backs[k - 1][state];
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
return pitches;
|
|
5329
|
+
}
|
|
5330
|
+
|
|
5331
|
+
// src/generate/melody/derive.ts
|
|
5332
|
+
var VARIATION_STEP_COST = 0.2;
|
|
5333
|
+
var OCTAVE = 12;
|
|
5334
|
+
var LEVEL_TARGET_COST = 0.1;
|
|
5335
|
+
var LEVEL_PLAN_COST = 0.02;
|
|
5336
|
+
var LEVEL_REACH = 24;
|
|
5337
|
+
var TONAL_LEVEL_REACH = 14;
|
|
5338
|
+
function stretched(cell, timeRatio) {
|
|
5339
|
+
return timeRatio === 1 ? cell : transformMotif(cell, "augment", timeRatio);
|
|
5340
|
+
}
|
|
5341
|
+
function shaped(cell, relation) {
|
|
5342
|
+
switch (relation.kind) {
|
|
5343
|
+
case "repetition":
|
|
5344
|
+
case "tonalTransposition":
|
|
5345
|
+
return cell;
|
|
5346
|
+
case "transposition":
|
|
5347
|
+
return stretched(cell, relation.timeRatio);
|
|
5348
|
+
case "inversion":
|
|
5349
|
+
return stretched(transformMotif(cell, "invert"), relation.timeRatio);
|
|
5350
|
+
case "retrograde":
|
|
5351
|
+
return transformMotif(cell, "retrograde");
|
|
5352
|
+
case "retrogradeInversion":
|
|
5353
|
+
return transformMotif(transformMotif(cell, "invert"), "retrograde");
|
|
5354
|
+
case "augmentation":
|
|
5355
|
+
return transformMotif(cell, "augment", relation.timeRatio);
|
|
5356
|
+
case "diminution":
|
|
5357
|
+
return transformMotif(cell, "diminish", 1 / relation.timeRatio);
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5360
|
+
function relationLevels(kind, low = -LEVEL_REACH, high = LEVEL_REACH) {
|
|
5361
|
+
const between = (from, to, skipZero) => {
|
|
5362
|
+
const levels = [];
|
|
5363
|
+
for (let level = Math.ceil(from); level <= to; level += 1) {
|
|
5364
|
+
if (!(skipZero && level === 0)) levels.push(level);
|
|
5365
|
+
}
|
|
5366
|
+
return levels;
|
|
5367
|
+
};
|
|
5368
|
+
switch (kind) {
|
|
5369
|
+
case "repetition":
|
|
5370
|
+
case "augmentation":
|
|
5371
|
+
case "diminution":
|
|
5372
|
+
return [-OCTAVE, 0, OCTAVE];
|
|
5373
|
+
case "transposition":
|
|
5374
|
+
return between(low, high, true);
|
|
5375
|
+
case "tonalTransposition":
|
|
5376
|
+
return between(-TONAL_LEVEL_REACH, TONAL_LEVEL_REACH, true);
|
|
5377
|
+
case "inversion":
|
|
5378
|
+
case "retrograde":
|
|
5379
|
+
case "retrogradeInversion":
|
|
5380
|
+
return between(low, high, false);
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
function replayAt(source, relation, level, startBeat, key) {
|
|
5384
|
+
const head = source[0];
|
|
5385
|
+
const cell = shaped({ notes: source.map((n) => ({ ...n })) }, relation);
|
|
5386
|
+
const first = cell.notes[0];
|
|
5387
|
+
const offset = startBeat - first.startBeat;
|
|
5388
|
+
const shift = head.pitch + level - first.pitch;
|
|
5389
|
+
return cell.notes.map((n) => ({
|
|
5390
|
+
...n,
|
|
5391
|
+
pitch: relation.kind === "tonalTransposition" ? _chunk3R3QVKY5cjs.shiftByScaleDegrees.call(void 0, n.pitch, level, key) : n.pitch + shift,
|
|
5392
|
+
startBeat: n.startBeat + offset
|
|
5393
|
+
}));
|
|
5394
|
+
}
|
|
5395
|
+
function clipStatement(notes, endBeat) {
|
|
5396
|
+
return notes.filter((n) => n.startBeat < endBeat - _chunkS5BKZZGMcjs.BEAT_EPS).map((n) => ({ ...n, durationBeat: Math.min(n.durationBeat, endBeat - n.startBeat) }));
|
|
5397
|
+
}
|
|
5398
|
+
function outsideCount(notes, frame) {
|
|
5399
|
+
return notes.filter((n) => n.pitch < frame.low || n.pitch > frame.high).length;
|
|
5400
|
+
}
|
|
5401
|
+
function nearerLevel(level, best, planned) {
|
|
5402
|
+
const distance = Math.abs(level - planned);
|
|
5403
|
+
const bestDistance = Math.abs(best - planned);
|
|
5404
|
+
return distance < bestDistance || distance === bestDistance && level < best;
|
|
5405
|
+
}
|
|
5406
|
+
function repairStatement(notes, forced, frame) {
|
|
5407
|
+
const slots = notes.map(
|
|
5408
|
+
(n, index) => forced[index] ? {
|
|
5409
|
+
candidates: frame.candidatesAt(n.startBeat),
|
|
5410
|
+
cost: frame.costAt(n.startBeat),
|
|
5411
|
+
fixed: false
|
|
5412
|
+
} : { candidates: [n.pitch], cost: () => 0, fixed: true }
|
|
5413
|
+
);
|
|
5414
|
+
const pitches = searchPitches(slots, frame.budget);
|
|
5415
|
+
if (pitches === null) {
|
|
5416
|
+
throw new (0, _chunkS5BKZZGMcjs.NoSolutionError)(
|
|
5417
|
+
`no repair fits the statement into the register ${frame.low}..${frame.high}`,
|
|
5418
|
+
{ at: frame.at }
|
|
5419
|
+
);
|
|
5420
|
+
}
|
|
5421
|
+
return notes.map((n, index) => ({ ...n, pitch: pitches[index] }));
|
|
5422
|
+
}
|
|
5423
|
+
function placeDerived(source, relation, startBeat, endBeat, key, frame, before) {
|
|
5424
|
+
const planned = relation.kind === "tonalTransposition" ? _nullishCoalesce(relation.degrees, () => ( 0)) : relation.semitones;
|
|
5425
|
+
const replay = (level) => clipStatement(replayAt(source, relation, level, startBeat, key), endBeat);
|
|
5426
|
+
const base = replay(0).map((n) => n.pitch);
|
|
5427
|
+
const levels = relationLevels(
|
|
5428
|
+
relation.kind,
|
|
5429
|
+
Math.max(-LEVEL_REACH, frame.low - Math.min(...base)),
|
|
5430
|
+
Math.min(LEVEL_REACH, frame.high - Math.max(...base))
|
|
5431
|
+
);
|
|
5432
|
+
let best = null;
|
|
5433
|
+
for (const level of levels) {
|
|
5434
|
+
const notes2 = replay(level);
|
|
5435
|
+
if (outsideCount(notes2, frame) > 0) {
|
|
5436
|
+
continue;
|
|
5437
|
+
}
|
|
5438
|
+
const distance = notes2.reduce((sum, n) => sum + Math.abs(n.pitch - frame.targetAt(n.startBeat)), 0) / notes2.length;
|
|
5439
|
+
const cost = frame.misfits(notes2, before) + LEVEL_TARGET_COST * distance + LEVEL_PLAN_COST * Math.abs(level - planned);
|
|
5440
|
+
if (best === null || cost < best.cost || cost === best.cost && nearerLevel(level, best.level, planned)) {
|
|
5441
|
+
best = { level, notes: notes2, cost };
|
|
5442
|
+
}
|
|
5443
|
+
}
|
|
5444
|
+
if (best !== null) {
|
|
5445
|
+
return best.notes;
|
|
5446
|
+
}
|
|
5447
|
+
let fallback = null;
|
|
5448
|
+
for (const level of relationLevels(relation.kind)) {
|
|
5449
|
+
const notes2 = replay(level);
|
|
5450
|
+
const outside = outsideCount(notes2, frame);
|
|
5451
|
+
if (fallback === null || outside < fallback.outside || outside === fallback.outside && nearerLevel(level, fallback.level, planned)) {
|
|
5452
|
+
fallback = { level, notes: notes2, outside };
|
|
5453
|
+
}
|
|
5454
|
+
}
|
|
5455
|
+
const notes = fallback.notes;
|
|
5456
|
+
return repairStatement(
|
|
5457
|
+
notes,
|
|
5458
|
+
notes.map((n) => n.pitch < frame.low || n.pitch > frame.high),
|
|
5459
|
+
frame
|
|
5460
|
+
);
|
|
5461
|
+
}
|
|
5462
|
+
function varyStatement(source, startBeat, frame) {
|
|
5463
|
+
const offset = startBeat - source[0].startBeat;
|
|
5464
|
+
const last = source.length - 1;
|
|
5465
|
+
const inside = (pitch) => pitch >= frame.low && pitch <= frame.high;
|
|
5466
|
+
const outer = [source[0].pitch, source[last].pitch];
|
|
5467
|
+
const shift = [0, OCTAVE, -OCTAVE].find((s) => outer.every((pitch) => inside(pitch + s)));
|
|
5468
|
+
const notes = source.map((n) => ({ ...n, startBeat: n.startBeat + offset }));
|
|
5469
|
+
const slots = notes.map(
|
|
5470
|
+
(n, index) => (index === 0 || index === last) && shift !== void 0 ? { candidates: [n.pitch + shift], cost: () => 0, fixed: true } : {
|
|
5471
|
+
candidates: frame.candidatesAt(n.startBeat),
|
|
5472
|
+
cost: frame.costAt(n.startBeat),
|
|
5473
|
+
fixed: false
|
|
5474
|
+
}
|
|
5475
|
+
);
|
|
5476
|
+
const pitches = searchPitches(
|
|
5477
|
+
slots,
|
|
5478
|
+
frame.budget,
|
|
5479
|
+
(index, from, to) => VARIATION_STEP_COST * Math.abs(
|
|
5480
|
+
to - from - (source[index].pitch - source[index - 1].pitch)
|
|
5481
|
+
)
|
|
5482
|
+
);
|
|
5483
|
+
if (pitches === null) {
|
|
5484
|
+
throw new (0, _chunkS5BKZZGMcjs.NoSolutionError)(`no variation line fits the register ${frame.low}..${frame.high}`, {
|
|
5485
|
+
at: frame.at
|
|
5486
|
+
});
|
|
5487
|
+
}
|
|
5488
|
+
return notes.map((n, index) => ({ ...n, pitch: pitches[index] }));
|
|
5489
|
+
}
|
|
5490
|
+
|
|
5491
|
+
// src/generate/melody/index.ts
|
|
5492
|
+
var RHYTHM_SUBDIVISION = 4;
|
|
5493
|
+
var OFF_SCALE_COST = 1;
|
|
5494
|
+
var TARGET_COST = 0.1;
|
|
5495
|
+
var TIE_BREAK = 1e-6;
|
|
5496
|
+
var SEARCH_STATES_SQUARED = 9;
|
|
5497
|
+
function melodyContext(plan, input) {
|
|
5498
|
+
const fromPlan = { seed: plan.seed, algorithmVersion: plan.algorithmVersion };
|
|
5499
|
+
if (input === void 0) {
|
|
5500
|
+
return resolveContext(fromPlan);
|
|
5501
|
+
}
|
|
5502
|
+
if (typeof input === "number") {
|
|
5503
|
+
return resolveContext({ ...fromPlan, seed: input });
|
|
5504
|
+
}
|
|
5505
|
+
const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, input, "ctx");
|
|
5506
|
+
if (read.rng !== void 0) {
|
|
5507
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
5508
|
+
"ctx.rng cannot drive a melody: every onset and tie-break is drawn at an address of phrase and beat under a seed; pass a seed instead"
|
|
5509
|
+
);
|
|
5510
|
+
}
|
|
5511
|
+
return resolveContext({
|
|
5512
|
+
...read,
|
|
5513
|
+
seed: _nullishCoalesce(read.seed, () => ( plan.seed)),
|
|
5514
|
+
algorithmVersion: _nullishCoalesce(read.algorithmVersion, () => ( plan.algorithmVersion))
|
|
5515
|
+
});
|
|
5516
|
+
}
|
|
5517
|
+
function chordEntryAt(plan, beat) {
|
|
5518
|
+
return plan.harmony.find((chord) => beat >= chord.startBeat && beat < chord.endBeat);
|
|
5519
|
+
}
|
|
5520
|
+
function scaleAt(scene, beat) {
|
|
5521
|
+
return scene.scales[_nullishCoalesce(_optionalChain([chordEntryAt, 'call', _62 => _62(scene.plan, beat), 'optionalAccess', _63 => _63.key]), () => ( 0))];
|
|
5522
|
+
}
|
|
5523
|
+
function targetPitch(phrase, beat) {
|
|
5524
|
+
const length = phrase.endBeat - phrase.startBeat;
|
|
5525
|
+
const t = length > 0 ? Math.min(Math.max((beat - phrase.startBeat) / length, 0), 1) : 0;
|
|
5526
|
+
const { low, high, mean } = phrase.register;
|
|
5527
|
+
return mean + (high - low) / 2 * plannedContourAt(phrase.shape, phrase.peakPosition, t);
|
|
5528
|
+
}
|
|
5529
|
+
function registerPitches(phrase) {
|
|
5530
|
+
const pitches = [];
|
|
5531
|
+
for (let pitch = phrase.register.low; pitch <= phrase.register.high; pitch += 1) {
|
|
5532
|
+
pitches.push(pitch);
|
|
5533
|
+
}
|
|
5534
|
+
return pitches;
|
|
5535
|
+
}
|
|
5536
|
+
function frameOf(scene, index) {
|
|
5537
|
+
const phrase = scene.plan.phrases[index];
|
|
5538
|
+
const { low, high } = phrase.register;
|
|
5539
|
+
const register = registerPitches(phrase);
|
|
5540
|
+
const draw = scene.ctx.part("melody");
|
|
5541
|
+
return {
|
|
5542
|
+
low,
|
|
5543
|
+
high,
|
|
5544
|
+
at: phrase.startBeat,
|
|
5545
|
+
budget: scene.budget,
|
|
5546
|
+
candidatesAt: (beat) => {
|
|
5547
|
+
const position = positionClass(beat, scene.plan.meters);
|
|
5548
|
+
const chord = scene.timeline.at(beat);
|
|
5549
|
+
const tones = position === "strong" && chord !== null ? _chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, chord) : null;
|
|
5550
|
+
const scale = scaleAt(scene, beat);
|
|
5551
|
+
const candidates = position === "off" ? register : tones !== null ? register.filter((pitch) => tones.includes(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitch))) : register.filter((pitch) => _chunk3R3QVKY5cjs.isScaleTone.call(void 0, pitch, scale));
|
|
5552
|
+
if (candidates.length === 0) {
|
|
5553
|
+
throw new (0, _chunkS5BKZZGMcjs.NoSolutionError)(
|
|
5554
|
+
`no ${position} pitch at beat ${beat} fits the register ${low}..${high}`,
|
|
5555
|
+
{ at: phrase.startBeat }
|
|
5556
|
+
);
|
|
5557
|
+
}
|
|
5558
|
+
return candidates;
|
|
5559
|
+
},
|
|
5560
|
+
costAt: (beat) => {
|
|
5561
|
+
const off = positionClass(beat, scene.plan.meters) === "off";
|
|
5562
|
+
const scale = scaleAt(scene, beat);
|
|
5563
|
+
const target = targetPitch(phrase, beat);
|
|
5564
|
+
return (pitch) => {
|
|
5565
|
+
let cost = TARGET_COST * Math.abs(pitch - target);
|
|
5566
|
+
if (off && !_chunk3R3QVKY5cjs.isScaleTone.call(void 0, pitch, scale)) {
|
|
5567
|
+
cost += OFF_SCALE_COST;
|
|
5568
|
+
}
|
|
5569
|
+
return cost + draw.float(0, TIE_BREAK, index, "tie", beat, pitch);
|
|
5570
|
+
};
|
|
5571
|
+
},
|
|
5572
|
+
targetAt: (beat) => targetPitch(phrase, beat),
|
|
5573
|
+
misfits: (notes, before) => scene.harmonyMisfits([...before, ...notes]).slice(before.length).filter((kinds) => kinds !== null).length
|
|
5574
|
+
};
|
|
5575
|
+
}
|
|
5576
|
+
function membersOf(plan) {
|
|
5577
|
+
return plan.phrases.map((phrase, index) => {
|
|
5578
|
+
const found = new Set(phrase.motifs);
|
|
5579
|
+
plan.motifs.forEach((motif, m) => {
|
|
5580
|
+
if (motif.phrase === index) {
|
|
5581
|
+
found.add(m);
|
|
5582
|
+
}
|
|
5583
|
+
});
|
|
5584
|
+
return [...found].sort(
|
|
5585
|
+
(a, b) => (_nullishCoalesce(_optionalChain([plan, 'access', _64 => _64.motifs, 'access', _65 => _65[a], 'optionalAccess', _66 => _66.startBeat]), () => ( 0))) - (_nullishCoalesce(_optionalChain([plan, 'access', _67 => _67.motifs, 'access', _68 => _68[b], 'optionalAccess', _69 => _69.startBeat]), () => ( 0))) || a - b
|
|
5586
|
+
);
|
|
5587
|
+
});
|
|
5588
|
+
}
|
|
5589
|
+
function segmentsOf(scene, index) {
|
|
5590
|
+
const phrase = scene.plan.phrases[index];
|
|
5591
|
+
const segments = [];
|
|
5592
|
+
let cursor = phrase.startBeat;
|
|
5593
|
+
for (const m of scene.members[index]) {
|
|
5594
|
+
const motif = scene.plan.motifs[m];
|
|
5595
|
+
if (motif === void 0) {
|
|
5596
|
+
continue;
|
|
5597
|
+
}
|
|
5598
|
+
if (motif.startBeat > cursor + _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
5599
|
+
segments.push({ startBeat: cursor, endBeat: motif.startBeat, motif: null });
|
|
5600
|
+
}
|
|
5601
|
+
segments.push({ startBeat: motif.startBeat, endBeat: motif.endBeat, motif: m });
|
|
5602
|
+
cursor = Math.max(cursor, motif.endBeat);
|
|
5603
|
+
}
|
|
5604
|
+
if (phrase.endBeat > cursor + _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
5605
|
+
segments.push({ startBeat: cursor, endBeat: phrase.endBeat, motif: null });
|
|
5606
|
+
}
|
|
5607
|
+
return segments;
|
|
5608
|
+
}
|
|
5609
|
+
function notesAt(onsets, endBeat) {
|
|
5610
|
+
return onsets.map((startBeat, i) => ({
|
|
5611
|
+
pitch: 0,
|
|
5612
|
+
startBeat,
|
|
5613
|
+
durationBeat: (_nullishCoalesce(onsets[i + 1], () => ( endBeat))) - startBeat
|
|
5614
|
+
}));
|
|
5615
|
+
}
|
|
5616
|
+
function onsetSlots(scene, startBeat, endBeat) {
|
|
5617
|
+
const { meters } = scene.plan;
|
|
5618
|
+
const slots = [startBeat];
|
|
5619
|
+
let bar2 = _chunkS5BKZZGMcjs.barStartBeat.call(void 0, startBeat, meters);
|
|
5620
|
+
while (bar2 < endBeat - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
5621
|
+
const barEnd = bar2 + _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, bar2, meters);
|
|
5622
|
+
for (let k = 0; bar2 + k / RHYTHM_SUBDIVISION < barEnd - _chunkS5BKZZGMcjs.BEAT_EPS; k += 1) {
|
|
5623
|
+
const beat = bar2 + k / RHYTHM_SUBDIVISION;
|
|
5624
|
+
if (beat > startBeat + _chunkS5BKZZGMcjs.BEAT_EPS && beat < endBeat - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
5625
|
+
slots.push(beat);
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
bar2 = barEnd;
|
|
5629
|
+
}
|
|
5630
|
+
return slots;
|
|
5631
|
+
}
|
|
5632
|
+
function drawOnsets(scene, phraseIndex, startBeat, endBeat, count) {
|
|
5633
|
+
const slots = onsetSlots(scene, startBeat, endBeat);
|
|
5634
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, slots.length * Math.max(1, count - 1), "melody onset slots", scene.budget);
|
|
5635
|
+
if (count - 1 > slots.length - 1) {
|
|
5636
|
+
throw new (0, _chunkS5BKZZGMcjs.NoSolutionError)(
|
|
5637
|
+
`${count} onsets do not fit the sixteenth grid of beats ${startBeat}..${endBeat}`,
|
|
5638
|
+
{ at: scene.plan.phrases[phraseIndex].startBeat }
|
|
5639
|
+
);
|
|
5640
|
+
}
|
|
5641
|
+
const levels = slots.map((beat) => _chunkIFZDWIKMcjs.rhythmLevel.call(void 0, beat, scene.plan.meters));
|
|
5642
|
+
const perLevel = /* @__PURE__ */ new Map();
|
|
5643
|
+
for (const level of levels) {
|
|
5644
|
+
perLevel.set(level, (_nullishCoalesce(perLevel.get(level), () => ( 0))) + 1);
|
|
5645
|
+
}
|
|
5646
|
+
const levelWeight = levels.map(
|
|
5647
|
+
(level) => (_nullishCoalesce(scene.plan.rhythm.onsetLevels[level], () => ( 0))) / perLevel.get(level)
|
|
5648
|
+
);
|
|
5649
|
+
const draw = scene.ctx.part("melody");
|
|
5650
|
+
const onsets = [startBeat];
|
|
5651
|
+
let current = 0;
|
|
5652
|
+
for (let k = 1; k < count; k += 1) {
|
|
5653
|
+
const last = slots.length - 1 - (count - 1 - k);
|
|
5654
|
+
const candidates = [];
|
|
5655
|
+
for (let j = current + 1; j <= last; j += 1) candidates.push(j);
|
|
5656
|
+
const gapShare = (j) => _nullishCoalesce(scene.plan.rhythm.interOnsetShares[_chunkIFZDWIKMcjs.ioiBinIndex.call(void 0, slots[j] - slots[current])], () => ( 0));
|
|
5657
|
+
let weights = candidates.map((j) => levelWeight[j] * gapShare(j));
|
|
5658
|
+
if (!weights.some((w) => w > 0)) weights = candidates.map((j) => levelWeight[j]);
|
|
5659
|
+
if (!weights.some((w) => w > 0)) weights = candidates.map(() => 1);
|
|
5660
|
+
const total = weights.reduce((sum, w) => sum + w, 0);
|
|
5661
|
+
let target = draw.float(0, 1, phraseIndex, "onset", startBeat, k) * total;
|
|
5662
|
+
let pick = candidates.length - 1;
|
|
5663
|
+
for (let c = 0; c < candidates.length; c += 1) {
|
|
5664
|
+
target -= weights[c];
|
|
5665
|
+
if (target < 0) {
|
|
5666
|
+
pick = c;
|
|
5667
|
+
break;
|
|
5668
|
+
}
|
|
5669
|
+
}
|
|
5670
|
+
current = candidates[pick];
|
|
5671
|
+
onsets.push(slots[current]);
|
|
5672
|
+
}
|
|
5673
|
+
return notesAt(onsets, endBeat);
|
|
5674
|
+
}
|
|
5675
|
+
function placeRhythm(scene, motif) {
|
|
5676
|
+
const { rhythm, startBeat, endBeat, notes } = motif;
|
|
5677
|
+
if (rhythm === null) {
|
|
5678
|
+
return drawOnsets(scene, motif.phrase, startBeat, endBeat, notes);
|
|
5679
|
+
}
|
|
5680
|
+
const onsets = [startBeat];
|
|
5681
|
+
for (const gap of rhythm) {
|
|
5682
|
+
onsets.push(onsets.at(-1) + gap);
|
|
5683
|
+
}
|
|
5684
|
+
return notesAt(onsets, endBeat);
|
|
5685
|
+
}
|
|
5686
|
+
function searchedLine(rhythm, frame) {
|
|
5687
|
+
const slots = rhythm.map((note2) => ({
|
|
5688
|
+
candidates: frame.candidatesAt(note2.startBeat),
|
|
5689
|
+
cost: frame.costAt(note2.startBeat),
|
|
5690
|
+
fixed: false
|
|
5691
|
+
}));
|
|
5692
|
+
const pitches = searchPitches(slots, frame.budget);
|
|
5693
|
+
if (pitches === null) {
|
|
5694
|
+
throw new (0, _chunkS5BKZZGMcjs.NoSolutionError)(`no pitch line fits the register ${frame.low}..${frame.high}`, {
|
|
5695
|
+
at: frame.at
|
|
5696
|
+
});
|
|
5697
|
+
}
|
|
5698
|
+
return rhythm.map((note2, i) => ({ ...note2, pitch: pitches[i] }));
|
|
5699
|
+
}
|
|
5700
|
+
function statementOf(scene, m) {
|
|
5701
|
+
const known = scene.statements.get(m);
|
|
5702
|
+
if (known !== void 0) {
|
|
5703
|
+
return known;
|
|
5704
|
+
}
|
|
5705
|
+
scene.inProgress.add(m);
|
|
5706
|
+
const motif = scene.plan.motifs[m];
|
|
5707
|
+
const frame = scene.frames[motif.phrase];
|
|
5708
|
+
let notes;
|
|
5709
|
+
if (motif.from === null) {
|
|
5710
|
+
if (motif.endBeat - motif.startBeat <= _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
5711
|
+
throw new (0, _chunkS5BKZZGMcjs.NoSolutionError)(`root motif ${m} spans no time to place its notes in`, {
|
|
5712
|
+
at: frame.at
|
|
5713
|
+
});
|
|
5714
|
+
}
|
|
5715
|
+
notes = searchedLine(placeRhythm(scene, motif), frame);
|
|
5716
|
+
} else {
|
|
5717
|
+
const source = statementOf(scene, motif.from);
|
|
5718
|
+
notes = motif.relation === null ? varyStatement(source, motif.startBeat, frame) : placeDerived(
|
|
5719
|
+
source,
|
|
5720
|
+
motif.relation,
|
|
5721
|
+
motif.startBeat,
|
|
5722
|
+
motif.endBeat,
|
|
5723
|
+
scaleAt(scene, motif.startBeat),
|
|
5724
|
+
frame,
|
|
5725
|
+
lineBefore(scene, motif.phrase, motif.startBeat)
|
|
5726
|
+
);
|
|
5727
|
+
}
|
|
5728
|
+
scene.inProgress.delete(m);
|
|
5729
|
+
scene.statements.set(m, notes);
|
|
5730
|
+
return notes;
|
|
5731
|
+
}
|
|
5732
|
+
function freeNoteCount(scene, index, segment) {
|
|
5733
|
+
const phrase = scene.plan.phrases[index];
|
|
5734
|
+
const bars = _chunkIFZDWIKMcjs.barSpanOf.call(void 0, segment.startBeat, segment.endBeat, scene.plan.meters);
|
|
5735
|
+
return Math.max(1, Math.round(phrase.onsetDensity * bars));
|
|
5736
|
+
}
|
|
5737
|
+
function freeSpan(scene, index, segment) {
|
|
5738
|
+
const key = `${index}:${segment.startBeat}`;
|
|
5739
|
+
const known = scene.free.get(key);
|
|
5740
|
+
if (known !== void 0) {
|
|
5741
|
+
return known;
|
|
5742
|
+
}
|
|
5743
|
+
const rhythm = drawOnsets(
|
|
5744
|
+
scene,
|
|
5745
|
+
index,
|
|
5746
|
+
segment.startBeat,
|
|
5747
|
+
segment.endBeat,
|
|
5748
|
+
freeNoteCount(scene, index, segment)
|
|
5749
|
+
);
|
|
5750
|
+
const notes = searchedLine(rhythm, scene.frames[index]);
|
|
5751
|
+
scene.free.set(key, notes);
|
|
5752
|
+
return notes;
|
|
5753
|
+
}
|
|
5754
|
+
function lineBefore(scene, index, beat) {
|
|
5755
|
+
const line = [];
|
|
5756
|
+
for (const segment of segmentsOf(scene, index)) {
|
|
5757
|
+
if (segment.endBeat > beat + _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
5758
|
+
break;
|
|
5759
|
+
}
|
|
5760
|
+
if (segment.motif === null) {
|
|
5761
|
+
line.push(...freeSpan(scene, index, segment));
|
|
5762
|
+
} else if (!scene.inProgress.has(segment.motif)) {
|
|
5763
|
+
line.push(...statementOf(scene, segment.motif));
|
|
5764
|
+
}
|
|
5765
|
+
}
|
|
5766
|
+
return line.sort((a, b) => a.startBeat - b.startBeat);
|
|
5767
|
+
}
|
|
5768
|
+
function finalDownbeat(scene, index) {
|
|
5769
|
+
const phrase = scene.plan.phrases[index];
|
|
5770
|
+
const { meters } = scene.plan;
|
|
5771
|
+
let downbeat = _chunkS5BKZZGMcjs.barStartBeat.call(void 0, phrase.startBeat, meters);
|
|
5772
|
+
while (downbeat + _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, downbeat, meters) < phrase.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
5773
|
+
downbeat += _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, downbeat, meters);
|
|
5774
|
+
}
|
|
5775
|
+
return Math.max(downbeat, phrase.startBeat);
|
|
5776
|
+
}
|
|
5777
|
+
function heldNoteIndex(scene, index, line, downbeat) {
|
|
5778
|
+
const phrase = scene.plan.phrases[index];
|
|
5779
|
+
const sounding2 = line.findIndex(
|
|
5780
|
+
(note2) => note2.startBeat <= downbeat + _chunkS5BKZZGMcjs.BEAT_EPS && note2.startBeat + note2.durationBeat > downbeat + _chunkS5BKZZGMcjs.BEAT_EPS
|
|
5781
|
+
);
|
|
5782
|
+
return sounding2 >= 0 ? sounding2 : line.findIndex(
|
|
5783
|
+
(note2) => note2.startBeat >= downbeat - _chunkS5BKZZGMcjs.BEAT_EPS && note2.startBeat < phrase.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS
|
|
5784
|
+
);
|
|
5785
|
+
}
|
|
5786
|
+
function repairCadence(scene, index, line, held) {
|
|
5787
|
+
const phrase = scene.plan.phrases[index];
|
|
5788
|
+
if (phrase.cadence === null) {
|
|
5789
|
+
return;
|
|
5790
|
+
}
|
|
5791
|
+
let target = held;
|
|
5792
|
+
for (let i = line.length - 1; target < 0 && i >= 0; i -= 1) {
|
|
5793
|
+
if (positionClass(line[i].startBeat, scene.plan.meters) !== "off") {
|
|
5794
|
+
target = i;
|
|
5795
|
+
}
|
|
5796
|
+
}
|
|
5797
|
+
if (target < 0) {
|
|
5798
|
+
return;
|
|
5799
|
+
}
|
|
5800
|
+
const note2 = line[target];
|
|
5801
|
+
const allowed = cadencePitchClasses(scene.plan, scene.timeline, phrase.cadence, note2.startBeat);
|
|
5802
|
+
if (allowed === null) {
|
|
5803
|
+
return;
|
|
5804
|
+
}
|
|
5805
|
+
const frame = scene.frames[index];
|
|
5806
|
+
const candidates = registerPitches(phrase).filter(
|
|
5807
|
+
(pitch) => allowed.includes(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitch))
|
|
5808
|
+
);
|
|
5809
|
+
if (candidates.length === 0) {
|
|
5810
|
+
throw new (0, _chunkS5BKZZGMcjs.NoSolutionError)(
|
|
5811
|
+
`no pitch in the register ${frame.low}..${frame.high} can end the ${phrase.cadence} cadence`,
|
|
5812
|
+
{ at: frame.at }
|
|
5813
|
+
);
|
|
5814
|
+
}
|
|
5815
|
+
if (allowed.includes(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, note2.pitch))) {
|
|
5816
|
+
return;
|
|
5817
|
+
}
|
|
5818
|
+
const slots = line.map(
|
|
5819
|
+
(n, i) => i === target ? { candidates, cost: frame.costAt(n.startBeat), fixed: false } : { candidates: [n.pitch], cost: () => 0, fixed: true }
|
|
5820
|
+
);
|
|
5821
|
+
const pitches = searchPitches(slots, frame.budget);
|
|
5822
|
+
if (pitches === null) {
|
|
5823
|
+
throw new (0, _chunkS5BKZZGMcjs.NoSolutionError)(`no cadence pitch joins its neighbours without an augmented step`, {
|
|
5824
|
+
at: frame.at
|
|
5825
|
+
});
|
|
5826
|
+
}
|
|
5827
|
+
note2.pitch = pitches[target];
|
|
5828
|
+
}
|
|
5829
|
+
function repickCutOnset(scene, index, line, held) {
|
|
5830
|
+
const note2 = line[held];
|
|
5831
|
+
const derived = scene.plan.motifs.some(
|
|
5832
|
+
(motif) => motif.from !== null && note2.startBeat >= motif.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && note2.startBeat < motif.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS
|
|
5833
|
+
);
|
|
5834
|
+
if (scene.plan.phrases[index].cadence !== null || derived) {
|
|
5835
|
+
return;
|
|
5836
|
+
}
|
|
5837
|
+
const frame = scene.frames[index];
|
|
5838
|
+
const candidates = frame.candidatesAt(note2.startBeat);
|
|
5839
|
+
if (candidates.includes(note2.pitch)) {
|
|
5840
|
+
return;
|
|
5841
|
+
}
|
|
5842
|
+
const slots = line.map(
|
|
5843
|
+
(n, i) => i === held ? { candidates, cost: frame.costAt(n.startBeat), fixed: false } : { candidates: [n.pitch], cost: () => 0, fixed: true }
|
|
5844
|
+
);
|
|
5845
|
+
const pitches = searchPitches(slots, frame.budget);
|
|
5846
|
+
if (pitches === null) {
|
|
5847
|
+
throw new (0, _chunkS5BKZZGMcjs.NoSolutionError)(`no pitch at the final downbeat joins its neighbours`, {
|
|
5848
|
+
at: frame.at
|
|
5849
|
+
});
|
|
5850
|
+
}
|
|
5851
|
+
line[held] = { ...note2, pitch: pitches[held] };
|
|
5852
|
+
}
|
|
5853
|
+
function phraseLine(scene, index) {
|
|
5854
|
+
const line = [];
|
|
5855
|
+
for (const segment of segmentsOf(scene, index)) {
|
|
5856
|
+
line.push(
|
|
5857
|
+
...segment.motif === null ? freeSpan(scene, index, segment) : statementOf(scene, segment.motif)
|
|
5858
|
+
);
|
|
5859
|
+
}
|
|
5860
|
+
return line.sort((a, b) => a.startBeat - b.startBeat);
|
|
5861
|
+
}
|
|
5862
|
+
function chargePitchSearch(scene) {
|
|
5863
|
+
const { plan } = scene;
|
|
5864
|
+
let estimate = 0;
|
|
5865
|
+
plan.phrases.forEach((phrase, index) => {
|
|
5866
|
+
let searched = 0;
|
|
5867
|
+
for (const segment of segmentsOf(scene, index)) {
|
|
5868
|
+
const motif = segment.motif === null ? void 0 : plan.motifs[segment.motif];
|
|
5869
|
+
if (motif === void 0) {
|
|
5870
|
+
searched += freeNoteCount(scene, index, segment);
|
|
5871
|
+
} else if (motif.relation === null) {
|
|
5872
|
+
searched += motif.notes;
|
|
5873
|
+
} else {
|
|
5874
|
+
estimate += motif.notes * relationLevels(motif.relation.kind).length;
|
|
5875
|
+
}
|
|
5876
|
+
}
|
|
5877
|
+
const register = Math.max(0, phrase.register.high - phrase.register.low + 1);
|
|
5878
|
+
estimate += searched * SEARCH_STATES_SQUARED * register * register;
|
|
5879
|
+
});
|
|
5880
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, estimate, "melody pitch search", scene.budget);
|
|
5881
|
+
}
|
|
5882
|
+
function generateMelody(plan, opts) {
|
|
5883
|
+
assertCompositionPlan(plan);
|
|
5884
|
+
const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
|
|
5885
|
+
if (asked.budget !== void 0) {
|
|
5886
|
+
_chunkS5BKZZGMcjs.assertPositiveInt.call(void 0, asked.budget, "budget", Number.MAX_SAFE_INTEGER);
|
|
5887
|
+
}
|
|
5888
|
+
const timeline = planChordTimeline(plan);
|
|
5889
|
+
const scene = {
|
|
5890
|
+
plan,
|
|
5891
|
+
ctx: melodyContext(plan, asked.ctx),
|
|
5892
|
+
budget: asked.budget,
|
|
5893
|
+
timeline,
|
|
5894
|
+
harmonyMisfits: planHarmonyMisfits(plan, timeline),
|
|
5895
|
+
scales: plan.keys.map((key) => _chunk3R3QVKY5cjs.scaleOf.call(void 0, key)),
|
|
5896
|
+
members: membersOf(plan),
|
|
5897
|
+
frames: [],
|
|
5898
|
+
statements: /* @__PURE__ */ new Map(),
|
|
5899
|
+
free: /* @__PURE__ */ new Map(),
|
|
5900
|
+
inProgress: /* @__PURE__ */ new Set()
|
|
5901
|
+
};
|
|
5902
|
+
scene.frames = plan.phrases.map((_, index) => frameOf(scene, index));
|
|
5903
|
+
chargePitchSearch(scene);
|
|
5904
|
+
const melody = [];
|
|
5905
|
+
plan.phrases.forEach((_, index) => {
|
|
5906
|
+
const line = phraseLine(scene, index);
|
|
5907
|
+
const downbeat = finalDownbeat(scene, index);
|
|
5908
|
+
let held = heldNoteIndex(scene, index, line, downbeat);
|
|
5909
|
+
if (held >= 0) {
|
|
5910
|
+
line.length = held + 1;
|
|
5911
|
+
const note2 = line[held];
|
|
5912
|
+
if (note2.startBeat < downbeat - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
5913
|
+
const cut = downbeat - note2.startBeat;
|
|
5914
|
+
line[held] = { ...note2, durationBeat: cut };
|
|
5915
|
+
line.push({ ...note2, startBeat: downbeat, durationBeat: note2.durationBeat - cut });
|
|
5916
|
+
held += 1;
|
|
5917
|
+
repickCutOnset(scene, index, line, held);
|
|
5918
|
+
}
|
|
5919
|
+
}
|
|
5920
|
+
repairCadence(scene, index, line, held);
|
|
5921
|
+
if (held >= 0) {
|
|
5922
|
+
const note2 = line[held];
|
|
5923
|
+
const phraseEnd = plan.phrases[index].endBeat;
|
|
5924
|
+
line[held] = { ...note2, durationBeat: phraseEnd - note2.startBeat };
|
|
5925
|
+
}
|
|
5926
|
+
melody.push(...line);
|
|
5927
|
+
});
|
|
5928
|
+
melody.sort((a, b) => a.startBeat - b.startBeat);
|
|
5929
|
+
return motifToNoteEvents({ notes: melody });
|
|
5930
|
+
}
|
|
5931
|
+
|
|
4840
5932
|
// src/generate/ornament/index.ts
|
|
4841
5933
|
var ORNAMENT_STYLES = Object.freeze(["ghost", "flam", "drag", "slide", "accent"]);
|
|
4842
5934
|
var DEFAULT_TS5 = { numerator: 4, denominator: 4 };
|
|
@@ -4961,6 +6053,878 @@ function strokeGaps(ordered) {
|
|
|
4961
6053
|
return gaps;
|
|
4962
6054
|
}
|
|
4963
6055
|
|
|
6056
|
+
// src/generate/plan/derive.ts
|
|
6057
|
+
var PRESERVE_ASPECTS = [
|
|
6058
|
+
"form",
|
|
6059
|
+
"phraseLengths",
|
|
6060
|
+
"harmonicFunction",
|
|
6061
|
+
"harmonicRhythm",
|
|
6062
|
+
"motifRelations",
|
|
6063
|
+
"registerShape",
|
|
6064
|
+
"rhythm"
|
|
6065
|
+
];
|
|
6066
|
+
var NEUTRAL_SHAPE = "arch";
|
|
6067
|
+
var NEUTRAL_PEAK_POSITION = 0.6;
|
|
6068
|
+
var SHAPE_KEEP_THRESHOLD = 0.5;
|
|
6069
|
+
var NEUTRAL_ONSET_LEVELS = Object.freeze([0, 1, 2, 3, 4, 5].map((level) => level / 15));
|
|
6070
|
+
var FUNCTION_DEGREES = {
|
|
6071
|
+
tonic: [1, 6, 3],
|
|
6072
|
+
subdominant: [4, 2],
|
|
6073
|
+
dominant: [5, 7]
|
|
6074
|
+
};
|
|
6075
|
+
var STRONG_WEIGHT2 = 2;
|
|
6076
|
+
var HEPTATONIC_DEGREES = 7;
|
|
6077
|
+
function mod12(value) {
|
|
6078
|
+
return (value % 12 + 12) % 12;
|
|
6079
|
+
}
|
|
6080
|
+
function lerp(weight, reference, neutral) {
|
|
6081
|
+
return weight * reference + (1 - weight) * neutral;
|
|
6082
|
+
}
|
|
6083
|
+
function readPreserve(value) {
|
|
6084
|
+
const read = value === void 0 ? {} : _chunkS5BKZZGMcjs.assertRecord.call(void 0, value, "opts.preserve");
|
|
6085
|
+
const weights = {};
|
|
6086
|
+
for (const aspect of PRESERVE_ASPECTS) {
|
|
6087
|
+
const weight = read[aspect];
|
|
6088
|
+
weights[aspect] = weight === void 0 ? 1 : _chunkS5BKZZGMcjs.assertRange.call(void 0, weight, 0, 1, `opts.preserve.${aspect}`);
|
|
6089
|
+
}
|
|
6090
|
+
return weights;
|
|
6091
|
+
}
|
|
6092
|
+
function readRegister(value) {
|
|
6093
|
+
const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, value, "opts.register");
|
|
6094
|
+
const low = _chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, read.low, "opts.register.low");
|
|
6095
|
+
const high = _chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, read.high, "opts.register.high");
|
|
6096
|
+
if (low > high) {
|
|
6097
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
6098
|
+
`opts.register.low must not exceed opts.register.high; received ${low} > ${high}`
|
|
6099
|
+
);
|
|
6100
|
+
}
|
|
6101
|
+
return { low, high };
|
|
6102
|
+
}
|
|
6103
|
+
function rejectSuppliedRng(ctx) {
|
|
6104
|
+
if (typeof ctx === "object" && ctx !== null && ctx.rng !== void 0) {
|
|
6105
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
6106
|
+
"opts.ctx.rng is not accepted: a composition plan records its seed, not a source"
|
|
6107
|
+
);
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6110
|
+
function plannedKeys(profile, requested) {
|
|
6111
|
+
const regions = profile.harmony.keys.map(
|
|
6112
|
+
({ key }) => ({
|
|
6113
|
+
scale: { ...key.scale },
|
|
6114
|
+
tonic: { ...key.tonic },
|
|
6115
|
+
variant: key.variant
|
|
6116
|
+
})
|
|
6117
|
+
);
|
|
6118
|
+
const home = regions[0];
|
|
6119
|
+
if (requested === void 0) {
|
|
6120
|
+
return regions;
|
|
6121
|
+
}
|
|
6122
|
+
const target = _chunk3R3QVKY5cjs.resolveKey.call(void 0, requested);
|
|
6123
|
+
if (target.variant !== home.variant || target.scale.modeMask12 !== home.scale.modeMask12) {
|
|
6124
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
6125
|
+
"opts.key must be in the same mode as the reference's home key; a key change moves the tonic only"
|
|
6126
|
+
);
|
|
6127
|
+
}
|
|
6128
|
+
const step = _chunkS5BKZZGMcjs.spelledInterval.call(void 0,
|
|
6129
|
+
{ letter: home.tonic.letter, alter: home.tonic.alter },
|
|
6130
|
+
{ letter: target.tonic.letter, alter: target.tonic.alter }
|
|
6131
|
+
);
|
|
6132
|
+
const moved = regions.map(
|
|
6133
|
+
(key) => ({
|
|
6134
|
+
scale: {
|
|
6135
|
+
rootPc: mod12(key.scale.rootPc + step.semitones),
|
|
6136
|
+
modeMask12: key.scale.modeMask12
|
|
6137
|
+
},
|
|
6138
|
+
tonic: _chunkS5BKZZGMcjs.transposeByInterval.call(void 0, { letter: key.tonic.letter, alter: key.tonic.alter }, step),
|
|
6139
|
+
variant: key.variant
|
|
6140
|
+
})
|
|
6141
|
+
);
|
|
6142
|
+
moved[0] = target;
|
|
6143
|
+
return moved;
|
|
6144
|
+
}
|
|
6145
|
+
function nearestBoundary(beat, boundaries) {
|
|
6146
|
+
let best = beat;
|
|
6147
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
6148
|
+
for (const boundary of boundaries) {
|
|
6149
|
+
const distance = Math.abs(boundary - beat);
|
|
6150
|
+
if (distance < bestDistance - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
6151
|
+
best = boundary;
|
|
6152
|
+
bestDistance = distance;
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6155
|
+
return best;
|
|
6156
|
+
}
|
|
6157
|
+
function plannedSections(profile, weight, draw) {
|
|
6158
|
+
const sections = profile.form.sections;
|
|
6159
|
+
const boundaries = [profile.span.startBeat, profile.span.endBeat];
|
|
6160
|
+
for (const phrase of profile.form.phrases) {
|
|
6161
|
+
boundaries.push(phrase.startBeat, phrase.endBeat);
|
|
6162
|
+
}
|
|
6163
|
+
boundaries.sort((a, b) => a - b);
|
|
6164
|
+
const planned = [];
|
|
6165
|
+
sections.forEach((section, index) => {
|
|
6166
|
+
const keep = sections.length < 2 || draw.prob(weight, "form", index);
|
|
6167
|
+
const neighbour = index + 1 < sections.length ? index + 1 : index - 1;
|
|
6168
|
+
const startBeat = nearestBoundary(section.startBeat, boundaries);
|
|
6169
|
+
const endBeat = nearestBoundary(section.endBeat, boundaries);
|
|
6170
|
+
if (endBeat - startBeat > _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
6171
|
+
planned.push({
|
|
6172
|
+
label: keep ? section.label : _nullishCoalesce(_optionalChain([sections, 'access', _70 => _70[neighbour], 'optionalAccess', _71 => _71.label]), () => ( section.label)),
|
|
6173
|
+
startBeat,
|
|
6174
|
+
endBeat
|
|
6175
|
+
});
|
|
6176
|
+
}
|
|
6177
|
+
});
|
|
6178
|
+
return planned;
|
|
6179
|
+
}
|
|
6180
|
+
function beatAfterBars(startBeat, bars, meters) {
|
|
6181
|
+
let beat = startBeat;
|
|
6182
|
+
let left = bars;
|
|
6183
|
+
while (left > _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
6184
|
+
const step = Math.min(1, left);
|
|
6185
|
+
beat += step * _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, beat, meters);
|
|
6186
|
+
left -= step;
|
|
6187
|
+
}
|
|
6188
|
+
return beat;
|
|
6189
|
+
}
|
|
6190
|
+
function barsBetween(startBeat, endBeat, meters) {
|
|
6191
|
+
let beat = startBeat;
|
|
6192
|
+
let bars = 0;
|
|
6193
|
+
while (beat < endBeat - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
6194
|
+
const barBeats = _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, beat, meters);
|
|
6195
|
+
const step = Math.min(_chunkS5BKZZGMcjs.barStartBeat.call(void 0, beat, meters) + barBeats, endBeat) - beat;
|
|
6196
|
+
bars += step / barBeats;
|
|
6197
|
+
beat += step;
|
|
6198
|
+
}
|
|
6199
|
+
return bars;
|
|
6200
|
+
}
|
|
6201
|
+
function spanSegments(profile, sections) {
|
|
6202
|
+
const { startBeat, endBeat } = profile.span;
|
|
6203
|
+
const cuts = [startBeat, endBeat];
|
|
6204
|
+
for (const section of sections) {
|
|
6205
|
+
for (const beat of [section.startBeat, section.endBeat]) {
|
|
6206
|
+
if (beat > startBeat + _chunkS5BKZZGMcjs.BEAT_EPS && beat < endBeat - _chunkS5BKZZGMcjs.BEAT_EPS) cuts.push(beat);
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
cuts.sort((a, b) => a - b);
|
|
6210
|
+
const segments = [];
|
|
6211
|
+
for (let index = 1; index < cuts.length; index += 1) {
|
|
6212
|
+
const from = cuts[index - 1];
|
|
6213
|
+
const to = cuts[index];
|
|
6214
|
+
if (to - from > _chunkS5BKZZGMcjs.BEAT_EPS) segments.push([from, to]);
|
|
6215
|
+
}
|
|
6216
|
+
return segments;
|
|
6217
|
+
}
|
|
6218
|
+
function plannedPhraseSpans(profile, sections, weight, draw) {
|
|
6219
|
+
const segments = spanSegments(profile, sections);
|
|
6220
|
+
const pieces = profile.form.phrases.map((phrase, source) => ({
|
|
6221
|
+
startBeat: phrase.startBeat,
|
|
6222
|
+
endBeat: phrase.endBeat,
|
|
6223
|
+
source,
|
|
6224
|
+
cadence: _nullishCoalesce(_optionalChain([phrase, 'access', _72 => _72.cadence, 'optionalAccess', _73 => _73.type]), () => ( null))
|
|
6225
|
+
}));
|
|
6226
|
+
const replaced = pieces.map((_, index) => !draw.prob(weight, "phraseLengths", index));
|
|
6227
|
+
const spans = [];
|
|
6228
|
+
for (const [segmentStart, segmentEnd] of segments) {
|
|
6229
|
+
const members = pieces.map((piece, index) => ({ piece, index })).filter(
|
|
6230
|
+
({ piece }) => piece.startBeat >= segmentStart - _chunkS5BKZZGMcjs.BEAT_EPS && piece.startBeat < segmentEnd - _chunkS5BKZZGMcjs.BEAT_EPS
|
|
6231
|
+
);
|
|
6232
|
+
const laid = [];
|
|
6233
|
+
let cursor = segmentStart;
|
|
6234
|
+
for (const { piece, index } of members) {
|
|
6235
|
+
const lengths = [];
|
|
6236
|
+
if (replaced[index]) {
|
|
6237
|
+
const bars = barsBetween(piece.startBeat, piece.endBeat, profile.meters);
|
|
6238
|
+
const rounded = 2 ** Math.round(Math.log2(bars));
|
|
6239
|
+
const overflows = beatAfterBars(cursor, rounded, profile.meters) > segmentEnd + _chunkS5BKZZGMcjs.BEAT_EPS;
|
|
6240
|
+
let partStart = cursor;
|
|
6241
|
+
for (const part of overflows ? [rounded / 2, rounded / 2] : [rounded]) {
|
|
6242
|
+
const partEnd = beatAfterBars(partStart, part, profile.meters);
|
|
6243
|
+
lengths.push(partEnd - partStart);
|
|
6244
|
+
partStart = partEnd;
|
|
6245
|
+
}
|
|
6246
|
+
} else {
|
|
6247
|
+
lengths.push(piece.endBeat - piece.startBeat);
|
|
6248
|
+
}
|
|
6249
|
+
lengths.forEach((length, part) => {
|
|
6250
|
+
if (cursor >= segmentEnd - _chunkS5BKZZGMcjs.BEAT_EPS) return;
|
|
6251
|
+
const end = cursor + length;
|
|
6252
|
+
laid.push({
|
|
6253
|
+
startBeat: cursor,
|
|
6254
|
+
endBeat: Math.min(end, segmentEnd),
|
|
6255
|
+
source: piece.source,
|
|
6256
|
+
cadence: part === lengths.length - 1 ? piece.cadence : null
|
|
6257
|
+
});
|
|
6258
|
+
cursor = end;
|
|
6259
|
+
});
|
|
6260
|
+
}
|
|
6261
|
+
const last = laid.at(-1);
|
|
6262
|
+
if (last) {
|
|
6263
|
+
last.endBeat = segmentEnd;
|
|
6264
|
+
} else {
|
|
6265
|
+
laid.push({
|
|
6266
|
+
startBeat: segmentStart,
|
|
6267
|
+
endBeat: segmentEnd,
|
|
6268
|
+
source: _nullishCoalesce(_optionalChain([spans, 'access', _74 => _74.at, 'call', _75 => _75(-1), 'optionalAccess', _76 => _76.source]), () => ( (profile.form.phrases.length > 0 ? 0 : -1))),
|
|
6269
|
+
cadence: null
|
|
6270
|
+
});
|
|
6271
|
+
}
|
|
6272
|
+
spans.push(...laid);
|
|
6273
|
+
}
|
|
6274
|
+
return spans;
|
|
6275
|
+
}
|
|
6276
|
+
function sectionAt(sections, beat) {
|
|
6277
|
+
const index = sections.findIndex(
|
|
6278
|
+
(section) => beat >= section.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && beat < section.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS
|
|
6279
|
+
);
|
|
6280
|
+
return index < 0 ? null : index;
|
|
6281
|
+
}
|
|
6282
|
+
function phraseHolding(phrases, startBeat, endBeat) {
|
|
6283
|
+
return phrases.findIndex(
|
|
6284
|
+
(phrase) => startBeat >= phrase.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && endBeat <= phrase.endBeat + _chunkS5BKZZGMcjs.BEAT_EPS
|
|
6285
|
+
);
|
|
6286
|
+
}
|
|
6287
|
+
function better(a, b) {
|
|
6288
|
+
for (let field = 0; field < a.length; field += 1) {
|
|
6289
|
+
if (a[field] !== b[field]) return a[field] > b[field];
|
|
6290
|
+
}
|
|
6291
|
+
return false;
|
|
6292
|
+
}
|
|
6293
|
+
function scheduleMotifs(candidates, meters) {
|
|
6294
|
+
const order = [...candidates].sort((a, b) => a.startBeat - b.startBeat || a.node - b.node);
|
|
6295
|
+
const count = order.length;
|
|
6296
|
+
const firstFrom = (from, beat) => {
|
|
6297
|
+
let low = from;
|
|
6298
|
+
let high = count;
|
|
6299
|
+
while (low < high) {
|
|
6300
|
+
const mid = low + high >> 1;
|
|
6301
|
+
if (order[mid].startBeat < beat - _chunkS5BKZZGMcjs.BEAT_EPS) low = mid + 1;
|
|
6302
|
+
else high = mid;
|
|
6303
|
+
}
|
|
6304
|
+
return low;
|
|
6305
|
+
};
|
|
6306
|
+
const next = order.map((candidate, index2) => firstFrom(index2 + 1, candidate.endBeat));
|
|
6307
|
+
const own = order.map(
|
|
6308
|
+
(candidate) => [
|
|
6309
|
+
candidate.notes,
|
|
6310
|
+
_chunkS5BKZZGMcjs.metricWeight.call(void 0, candidate.startBeat, meters) < STRONG_WEIGHT2 ? -1 : 0,
|
|
6311
|
+
1
|
|
6312
|
+
]
|
|
6313
|
+
);
|
|
6314
|
+
const taking = (index2) => {
|
|
6315
|
+
const rest = best[next[index2]];
|
|
6316
|
+
const mine = own[index2];
|
|
6317
|
+
return [mine[0] + rest[0], mine[1] + rest[1], mine[2] + rest[2]];
|
|
6318
|
+
};
|
|
6319
|
+
const best = new Array(count + 1).fill([0, 0, 0]);
|
|
6320
|
+
for (let index2 = count - 1; index2 >= 0; index2 -= 1) {
|
|
6321
|
+
const taken = taking(index2);
|
|
6322
|
+
const skipped = best[index2 + 1];
|
|
6323
|
+
best[index2] = better(taken, skipped) ? taken : skipped;
|
|
6324
|
+
}
|
|
6325
|
+
const chosen = [];
|
|
6326
|
+
let index = 0;
|
|
6327
|
+
while (index < count) {
|
|
6328
|
+
if (!better(best[index], taking(index))) {
|
|
6329
|
+
chosen.push(order[index]);
|
|
6330
|
+
index = next[index];
|
|
6331
|
+
} else {
|
|
6332
|
+
index += 1;
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6335
|
+
return chosen.sort((a, b) => a.node - b.node);
|
|
6336
|
+
}
|
|
6337
|
+
function selectedDerivation(graph, parentEdge, selectedAt, node) {
|
|
6338
|
+
let plain = true;
|
|
6339
|
+
let semitones = 0;
|
|
6340
|
+
let steps = 0;
|
|
6341
|
+
for (let edge = parentEdge.get(node); edge !== void 0; edge = parentEdge.get(edge.from)) {
|
|
6342
|
+
const relation = edge.relation;
|
|
6343
|
+
steps += 1;
|
|
6344
|
+
plain &&= relation !== null && (relation.kind === "repetition" || relation.kind === "transposition" && Math.abs(relation.timeRatio - 1) <= _chunkS5BKZZGMcjs.BEAT_EPS);
|
|
6345
|
+
semitones += _nullishCoalesce(_optionalChain([relation, 'optionalAccess', _77 => _77.semitones]), () => ( 0));
|
|
6346
|
+
const from = selectedAt.get(edge.from);
|
|
6347
|
+
if (from === void 0) continue;
|
|
6348
|
+
if (steps === 1) {
|
|
6349
|
+
return { from, relation: relation === null ? null : { ...relation } };
|
|
6350
|
+
}
|
|
6351
|
+
if (!plain) {
|
|
6352
|
+
return { from: null, relation: null };
|
|
6353
|
+
}
|
|
6354
|
+
const gap = (_nullishCoalesce(_optionalChain([graph, 'access', _78 => _78.nodes, 'access', _79 => _79[node], 'optionalAccess', _80 => _80.startBeat]), () => ( 0))) - (_nullishCoalesce(_optionalChain([graph, 'access', _81 => _81.nodes, 'access', _82 => _82[edge.from], 'optionalAccess', _83 => _83.endBeat]), () => ( 0)));
|
|
6355
|
+
return {
|
|
6356
|
+
from,
|
|
6357
|
+
relation: {
|
|
6358
|
+
kind: semitones === 0 ? "repetition" : "transposition",
|
|
6359
|
+
sequence: gap >= -_chunkIFZDWIKMcjs.HUMANIZE_ADJACENCY && gap <= _chunkIFZDWIKMcjs.HUMANIZE_ADJACENCY,
|
|
6360
|
+
semitones,
|
|
6361
|
+
timeRatio: 1
|
|
6362
|
+
}
|
|
6363
|
+
};
|
|
6364
|
+
}
|
|
6365
|
+
return { from: null, relation: null };
|
|
6366
|
+
}
|
|
6367
|
+
function functionCandidates(fn, key) {
|
|
6368
|
+
if (_chunk3R3QVKY5cjs.scaleTonesInDegreeOrder.call(void 0, key).length !== HEPTATONIC_DEGREES) {
|
|
6369
|
+
return null;
|
|
6370
|
+
}
|
|
6371
|
+
return FUNCTION_DEGREES[fn].map((degree) => _chunkIFZDWIKMcjs.chordToRoman.call(void 0, _chunk3R3QVKY5cjs.diatonicTriad.call(void 0, degree, key), key));
|
|
6372
|
+
}
|
|
6373
|
+
function plannedHarmony(profile, keys, preserve, draw) {
|
|
6374
|
+
const chords = profile.harmony.chords;
|
|
6375
|
+
const romans = chords.map((chord, index) => {
|
|
6376
|
+
if (chord.level !== "structural" || draw.prob(preserve.harmonicFunction, "harmonicFunction", index)) {
|
|
6377
|
+
return chord.roman;
|
|
6378
|
+
}
|
|
6379
|
+
const candidates = functionCandidates(chord.function, keys[chord.key]);
|
|
6380
|
+
if (candidates === null) {
|
|
6381
|
+
return chord.roman;
|
|
6382
|
+
}
|
|
6383
|
+
const pick = draw.range(0, candidates.length - 1, "harmonicFunction", index, "pick");
|
|
6384
|
+
return candidates[pick];
|
|
6385
|
+
});
|
|
6386
|
+
const starts = [];
|
|
6387
|
+
chords.forEach((chord, index) => {
|
|
6388
|
+
const keep = index === 0 || draw.prob(preserve.harmonicRhythm, "harmonicRhythm", index);
|
|
6389
|
+
const start = keep ? chord.startBeat : _chunkS5BKZZGMcjs.barStartBeat.call(void 0, chord.startBeat, profile.meters);
|
|
6390
|
+
starts.push(index === 0 ? start : Math.max(start, starts[index - 1]));
|
|
6391
|
+
});
|
|
6392
|
+
const planned = [];
|
|
6393
|
+
chords.forEach((chord, index) => {
|
|
6394
|
+
const next = chords[index + 1];
|
|
6395
|
+
const nextStart = starts[index + 1];
|
|
6396
|
+
let endBeat = chord.endBeat;
|
|
6397
|
+
if (next !== void 0 && nextStart !== void 0) {
|
|
6398
|
+
endBeat = Math.abs(chord.endBeat - next.startBeat) <= _chunkS5BKZZGMcjs.BEAT_EPS ? nextStart : Math.min(chord.endBeat, nextStart);
|
|
6399
|
+
}
|
|
6400
|
+
const startBeat = starts[index];
|
|
6401
|
+
if (endBeat - startBeat > _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
6402
|
+
planned.push({ startBeat, endBeat, key: chord.key, roman: romans[index] });
|
|
6403
|
+
}
|
|
6404
|
+
});
|
|
6405
|
+
return planned;
|
|
6406
|
+
}
|
|
6407
|
+
function plannedRhythm(profile, weight) {
|
|
6408
|
+
const reference = profile.melody.rhythm;
|
|
6409
|
+
return {
|
|
6410
|
+
onsetLevels: reference.onsetLevels.map(
|
|
6411
|
+
(share, level) => lerp(weight, share, NEUTRAL_ONSET_LEVELS[level])
|
|
6412
|
+
),
|
|
6413
|
+
interOnsetShares: [...reference.interOnsetShares],
|
|
6414
|
+
syncopation: lerp(weight, reference.syncopation, 0)
|
|
6415
|
+
};
|
|
6416
|
+
}
|
|
6417
|
+
function deriveCompositionPlan(reference, opts) {
|
|
6418
|
+
const profile = _chunkIFZDWIKMcjs.assertReferenceProfile.call(void 0, reference, "reference");
|
|
6419
|
+
const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
|
|
6420
|
+
const preserve = readPreserve(asked.preserve);
|
|
6421
|
+
const requestedRegister = asked.register === void 0 ? void 0 : readRegister(asked.register);
|
|
6422
|
+
rejectSuppliedRng(asked.ctx);
|
|
6423
|
+
const ctx = resolveContext(asked.ctx);
|
|
6424
|
+
const draw = ctx.part("plan");
|
|
6425
|
+
const melodyRegister = profile.melody.register;
|
|
6426
|
+
if (profile.melody.rhythm.onsets === 0 || melodyRegister === null) {
|
|
6427
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)("reference must carry melody onsets to derive a plan from");
|
|
6428
|
+
}
|
|
6429
|
+
const keys = plannedKeys(profile, asked.key);
|
|
6430
|
+
const homeShift = mod12(
|
|
6431
|
+
keys[0].scale.rootPc - (_nullishCoalesce(_optionalChain([profile, 'access', _84 => _84.harmony, 'access', _85 => _85.keys, 'access', _86 => _86[0], 'optionalAccess', _87 => _87.key, 'access', _88 => _88.scale, 'access', _89 => _89.rootPc]), () => ( 0)))
|
|
6432
|
+
);
|
|
6433
|
+
const shift = homeShift >= 6 ? homeShift - 12 : homeShift;
|
|
6434
|
+
const source = { low: melodyRegister.low + shift, high: melodyRegister.high + shift };
|
|
6435
|
+
if (source.low < 0 || source.high > 127) {
|
|
6436
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
6437
|
+
`the reference melody range moved to the new key leaves the MIDI range; received ${source.low}..${source.high}`
|
|
6438
|
+
);
|
|
6439
|
+
}
|
|
6440
|
+
const target = _nullishCoalesce(requestedRegister, () => ( source));
|
|
6441
|
+
const scale = source.high === source.low ? 0 : (target.high - target.low) / (source.high - source.low);
|
|
6442
|
+
const offset = source.high === source.low ? (target.low + target.high) / 2 : target.low - source.low * scale;
|
|
6443
|
+
const mapPitch = (pitch) => (pitch + shift) * scale + offset;
|
|
6444
|
+
const neutralRegister = {
|
|
6445
|
+
low: target.low,
|
|
6446
|
+
high: target.high,
|
|
6447
|
+
mean: (target.low + target.high) / 2
|
|
6448
|
+
};
|
|
6449
|
+
const sections = plannedSections(profile, preserve.form, draw);
|
|
6450
|
+
const shapeWeight = preserve.registerShape;
|
|
6451
|
+
const spans = plannedPhraseSpans(profile, sections, preserve.phraseLengths, draw);
|
|
6452
|
+
const phrases = spans.map((span) => {
|
|
6453
|
+
const melody = _nullishCoalesce(_optionalChain([profile, 'access', _90 => _90.form, 'access', _91 => _91.phrases, 'access', _92 => _92[span.source], 'optionalAccess', _93 => _93.melody]), () => ( null));
|
|
6454
|
+
const register = melody ? {
|
|
6455
|
+
low: Math.round(lerp(shapeWeight, mapPitch(melody.low), neutralRegister.low)),
|
|
6456
|
+
high: Math.round(lerp(shapeWeight, mapPitch(melody.high), neutralRegister.high)),
|
|
6457
|
+
mean: lerp(shapeWeight, mapPitch(melody.mean), neutralRegister.mean)
|
|
6458
|
+
} : { ...neutralRegister };
|
|
6459
|
+
return {
|
|
6460
|
+
startBeat: span.startBeat,
|
|
6461
|
+
endBeat: span.endBeat,
|
|
6462
|
+
section: sectionAt(sections, span.startBeat),
|
|
6463
|
+
cadence: span.cadence,
|
|
6464
|
+
shape: melody && shapeWeight >= SHAPE_KEEP_THRESHOLD ? melody.shape : NEUTRAL_SHAPE,
|
|
6465
|
+
peakPosition: melody ? lerp(shapeWeight, melody.peakPosition, NEUTRAL_PEAK_POSITION) : NEUTRAL_PEAK_POSITION,
|
|
6466
|
+
register,
|
|
6467
|
+
onsetDensity: melody ? melody.onsetDensity : profile.melody.rhythm.onsetDensity,
|
|
6468
|
+
motifs: []
|
|
6469
|
+
};
|
|
6470
|
+
});
|
|
6471
|
+
const graph = profile.melody.graph;
|
|
6472
|
+
const noteCount = (node) => (_nullishCoalesce(_optionalChain([profile, 'access', _94 => _94.melody, 'access', _95 => _95.motifs, 'access', _96 => _96[_nullishCoalesce(_optionalChain([graph, 'access', _97 => _97.nodes, 'access', _98 => _98[node], 'optionalAccess', _99 => _99.motif]), () => ( -1))], 'optionalAccess', _100 => _100.intervals, 'access', _101 => _101.length]), () => ( 0))) + 1;
|
|
6473
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, graph.nodes.length * phrases.length, "plan motif selection", asked.budget);
|
|
6474
|
+
const candidates = [];
|
|
6475
|
+
graph.nodes.forEach((node, index) => {
|
|
6476
|
+
const phrase = phraseHolding(phrases, node.startBeat, node.endBeat);
|
|
6477
|
+
if (phrase >= 0) {
|
|
6478
|
+
candidates.push({
|
|
6479
|
+
node: index,
|
|
6480
|
+
phrase,
|
|
6481
|
+
startBeat: node.startBeat,
|
|
6482
|
+
endBeat: node.endBeat,
|
|
6483
|
+
notes: noteCount(index)
|
|
6484
|
+
});
|
|
6485
|
+
}
|
|
6486
|
+
});
|
|
6487
|
+
const selected = scheduleMotifs(candidates, profile.meters);
|
|
6488
|
+
const selectedAt = new Map(selected.map((candidate, index) => [candidate.node, index]));
|
|
6489
|
+
const parentEdge = new Map(graph.edges.map((edge) => [edge.to, edge]));
|
|
6490
|
+
const motifs = selected.map((candidate, index) => {
|
|
6491
|
+
const { from, relation } = selectedDerivation(graph, parentEdge, selectedAt, candidate.node);
|
|
6492
|
+
const keepRelation = draw.prob(preserve.motifRelations, "motifRelations", index);
|
|
6493
|
+
const keepRhythm = from === null && draw.prob(preserve.rhythm, "rhythm", index);
|
|
6494
|
+
const cell = profile.melody.motifs[_nullishCoalesce(_optionalChain([graph, 'access', _102 => _102.nodes, 'access', _103 => _103[candidate.node], 'optionalAccess', _104 => _104.motif]), () => ( -1))];
|
|
6495
|
+
_optionalChain([phrases, 'access', _105 => _105[candidate.phrase], 'optionalAccess', _106 => _106.motifs, 'access', _107 => _107.push, 'call', _108 => _108(index)]);
|
|
6496
|
+
return {
|
|
6497
|
+
phrase: candidate.phrase,
|
|
6498
|
+
startBeat: candidate.startBeat,
|
|
6499
|
+
endBeat: candidate.endBeat,
|
|
6500
|
+
notes: candidate.notes,
|
|
6501
|
+
rhythm: keepRhythm && cell !== void 0 ? cell.rhythm.map((ratio) => ratio * cell.unitBeats) : null,
|
|
6502
|
+
from,
|
|
6503
|
+
relation: keepRelation ? relation : null
|
|
6504
|
+
};
|
|
6505
|
+
});
|
|
6506
|
+
return {
|
|
6507
|
+
planVersion: COMPOSITION_PLAN_VERSION,
|
|
6508
|
+
seed: ctx.seed,
|
|
6509
|
+
algorithmVersion: ctx.algorithmVersion,
|
|
6510
|
+
keys,
|
|
6511
|
+
meters: profile.meters.map((entry) => ({
|
|
6512
|
+
startBeat: entry.startBeat,
|
|
6513
|
+
ts: entry.ts.grouping === void 0 ? { ...entry.ts } : { ...entry.ts, grouping: [...entry.ts.grouping] }
|
|
6514
|
+
})),
|
|
6515
|
+
span: { ...profile.span },
|
|
6516
|
+
sections,
|
|
6517
|
+
phrases,
|
|
6518
|
+
harmony: plannedHarmony(profile, keys, preserve, draw),
|
|
6519
|
+
motifs,
|
|
6520
|
+
rhythm: plannedRhythm(profile, preserve.rhythm)
|
|
6521
|
+
};
|
|
6522
|
+
}
|
|
6523
|
+
|
|
6524
|
+
// src/generate/plan/evaluate.ts
|
|
6525
|
+
var PEAK_POSITION_TOLERANCE = 0.2;
|
|
6526
|
+
var OUTLINE_POINTS = 8;
|
|
6527
|
+
var HELD_NOTE_BEATS = 1;
|
|
6528
|
+
function interiorBoundaries(plan) {
|
|
6529
|
+
const beats = /* @__PURE__ */ new Set();
|
|
6530
|
+
for (const phrase of plan.phrases) {
|
|
6531
|
+
for (const beat of [phrase.startBeat, phrase.endBeat]) {
|
|
6532
|
+
if (beat > plan.span.startBeat + _chunkS5BKZZGMcjs.BEAT_EPS && beat < plan.span.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
6533
|
+
beats.add(beat);
|
|
6534
|
+
}
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
return [...beats].sort((a, b) => a - b);
|
|
6538
|
+
}
|
|
6539
|
+
function restsOf(melody, plan) {
|
|
6540
|
+
const rests = [];
|
|
6541
|
+
let reach = plan.span.startBeat;
|
|
6542
|
+
for (const note2 of [...melody].sort((a, b) => a.startBeat - b.startBeat)) {
|
|
6543
|
+
if (note2.startBeat > reach + _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
6544
|
+
rests.push({ startBeat: reach, endBeat: note2.startBeat });
|
|
6545
|
+
}
|
|
6546
|
+
reach = Math.max(reach, note2.startBeat + note2.durationBeat);
|
|
6547
|
+
}
|
|
6548
|
+
if (plan.span.endBeat > reach + _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
6549
|
+
rests.push({ startBeat: reach, endBeat: plan.span.endBeat });
|
|
6550
|
+
}
|
|
6551
|
+
return rests;
|
|
6552
|
+
}
|
|
6553
|
+
function phraseBoundaryViolations(melody, plan) {
|
|
6554
|
+
const rests = restsOf(melody, plan);
|
|
6555
|
+
const violations = [];
|
|
6556
|
+
for (const beat of interiorBoundaries(plan)) {
|
|
6557
|
+
const pulse2 = _chunkS5BKZZGMcjs.pulseBeats.call(void 0, _chunkS5BKZZGMcjs.meterAt.call(void 0, beat, plan.meters));
|
|
6558
|
+
const held = melody.some(
|
|
6559
|
+
(note2) => note2.durationBeat >= HELD_NOTE_BEATS - _chunkS5BKZZGMcjs.BEAT_EPS && Math.abs(note2.startBeat + note2.durationBeat - beat) <= pulse2 + _chunkS5BKZZGMcjs.BEAT_EPS
|
|
6560
|
+
);
|
|
6561
|
+
const rested = rests.some(
|
|
6562
|
+
(rest) => rest.endBeat - rest.startBeat >= pulse2 - _chunkS5BKZZGMcjs.BEAT_EPS && rest.startBeat <= beat + pulse2 + _chunkS5BKZZGMcjs.BEAT_EPS && rest.endBeat >= beat - pulse2 - _chunkS5BKZZGMcjs.BEAT_EPS
|
|
6563
|
+
);
|
|
6564
|
+
if (!held && !rested) {
|
|
6565
|
+
violations.push({
|
|
6566
|
+
kind: "phraseBoundary",
|
|
6567
|
+
severity: "error",
|
|
6568
|
+
atBeat: beat,
|
|
6569
|
+
expected: `a note of at least ${HELD_NOTE_BEATS} beat(s) ending, or a rest of at least ${pulse2} beat(s), within ${pulse2} beat(s)`,
|
|
6570
|
+
actual: "neither",
|
|
6571
|
+
rationale: `the plan places a phrase boundary at beat ${beat}, which the candidate does not mark`
|
|
6572
|
+
});
|
|
6573
|
+
}
|
|
6574
|
+
}
|
|
6575
|
+
return violations;
|
|
6576
|
+
}
|
|
6577
|
+
function spanViolation(melody, plan) {
|
|
6578
|
+
const end = melody.reduce((max, note2) => Math.max(max, note2.startBeat + note2.durationBeat), 0);
|
|
6579
|
+
if (end <= plan.span.endBeat + _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
6580
|
+
return [];
|
|
6581
|
+
}
|
|
6582
|
+
return [
|
|
6583
|
+
{
|
|
6584
|
+
kind: "span",
|
|
6585
|
+
severity: "error",
|
|
6586
|
+
atBeat: plan.span.endBeat,
|
|
6587
|
+
expected: `ending by beat ${plan.span.endBeat}`,
|
|
6588
|
+
actual: `ending at beat ${end}`,
|
|
6589
|
+
rationale: "the candidate's last note runs past the plan's span"
|
|
6590
|
+
}
|
|
6591
|
+
];
|
|
6592
|
+
}
|
|
6593
|
+
function phraseLines(melody, plan) {
|
|
6594
|
+
return plan.phrases.map(
|
|
6595
|
+
(phrase) => melody.flatMap(
|
|
6596
|
+
(note2, index) => note2.startBeat >= phrase.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && note2.startBeat < phrase.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS ? [index] : []
|
|
6597
|
+
)
|
|
6598
|
+
);
|
|
6599
|
+
}
|
|
6600
|
+
function cadenceViolations(melody, plan, timeline, lines) {
|
|
6601
|
+
const violations = [];
|
|
6602
|
+
plan.phrases.forEach((phrase, index) => {
|
|
6603
|
+
if (phrase.cadence === null) {
|
|
6604
|
+
return;
|
|
6605
|
+
}
|
|
6606
|
+
const last = _optionalChain([lines, 'access', _109 => _109[index], 'optionalAccess', _110 => _110.at, 'call', _111 => _111(-1)]);
|
|
6607
|
+
const note2 = last === void 0 ? void 0 : melody[last];
|
|
6608
|
+
const allowed = note2 === void 0 ? [] : cadencePitchClasses(plan, timeline, phrase.cadence, note2.startBeat);
|
|
6609
|
+
if (note2 !== void 0 && (allowed === null || allowed.includes(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, note2.pitch)))) {
|
|
6610
|
+
return;
|
|
6611
|
+
}
|
|
6612
|
+
violations.push({
|
|
6613
|
+
kind: "cadence",
|
|
6614
|
+
severity: "error",
|
|
6615
|
+
atBeat: phrase.endBeat,
|
|
6616
|
+
expected: `a closing note on pitch class ${(_nullishCoalesce(allowed, () => ( []))).join(" or ")} for a ${phrase.cadence} cadence`,
|
|
6617
|
+
actual: note2 === void 0 ? "no note" : `pitch ${note2.pitch}`,
|
|
6618
|
+
rationale: `the phrase ending at beat ${phrase.endBeat} is planned to close on a ${phrase.cadence} cadence`
|
|
6619
|
+
});
|
|
6620
|
+
});
|
|
6621
|
+
return violations;
|
|
6622
|
+
}
|
|
6623
|
+
function harmonyViolations(melody, plan, timeline, lines) {
|
|
6624
|
+
const misfits = planHarmonyMisfits(plan, timeline);
|
|
6625
|
+
const violations = [];
|
|
6626
|
+
for (const line of lines) {
|
|
6627
|
+
const read = misfits(line.map((index) => melody[index]));
|
|
6628
|
+
read.forEach((kinds, at2) => {
|
|
6629
|
+
if (kinds === null) {
|
|
6630
|
+
return;
|
|
6631
|
+
}
|
|
6632
|
+
const note2 = melody[line[at2]];
|
|
6633
|
+
const derived = plan.motifs.some(
|
|
6634
|
+
(motif) => motif.from !== null && note2.startBeat >= motif.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && note2.startBeat < motif.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS
|
|
6635
|
+
);
|
|
6636
|
+
const strong = positionClass(note2.startBeat, plan.meters) === "strong";
|
|
6637
|
+
violations.push({
|
|
6638
|
+
kind: "harmony",
|
|
6639
|
+
severity: derived ? "warning" : "error",
|
|
6640
|
+
atBeat: note2.startBeat,
|
|
6641
|
+
expected: strong ? "a chord tone or an ornamental tone" : "a chord tone, an ornamental tone, or a scale tone",
|
|
6642
|
+
actual: kinds.length === 0 ? "no harmonic label" : kinds.join(", "),
|
|
6643
|
+
rationale: derived ? `the note at beat ${note2.startBeat} sits in a derived statement, whose transformation takes precedence over the harmony` : `the note at beat ${note2.startBeat} does not fit the planned harmony`
|
|
6644
|
+
});
|
|
6645
|
+
});
|
|
6646
|
+
}
|
|
6647
|
+
return violations;
|
|
6648
|
+
}
|
|
6649
|
+
function notesInRange(melody, startBeat, endBeat) {
|
|
6650
|
+
return melody.filter(
|
|
6651
|
+
(note2) => note2.startBeat >= startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && note2.startBeat < endBeat - _chunkS5BKZZGMcjs.BEAT_EPS
|
|
6652
|
+
);
|
|
6653
|
+
}
|
|
6654
|
+
function replayLevel(source, derived, statement, sourceEnd, plan, key, closing) {
|
|
6655
|
+
const relation = statement.relation;
|
|
6656
|
+
const planned = relation.kind === "tonalTransposition" ? _nullishCoalesce(relation.degrees, () => ( 0)) : relation.semitones;
|
|
6657
|
+
const { low, high } = plan.phrases[statement.phrase].register;
|
|
6658
|
+
const outside = (pitch) => pitch < low || pitch > high;
|
|
6659
|
+
const backward = relation.kind === "retrograde" || relation.kind === "retrogradeInversion";
|
|
6660
|
+
const cell = clipStatement(
|
|
6661
|
+
source.map((note2) => ({
|
|
6662
|
+
pitch: note2.pitch,
|
|
6663
|
+
startBeat: note2.startBeat,
|
|
6664
|
+
durationBeat: note2.durationBeat
|
|
6665
|
+
})),
|
|
6666
|
+
sourceEnd
|
|
6667
|
+
);
|
|
6668
|
+
const replays = relationLevels(relation.kind).map((level) => {
|
|
6669
|
+
const notes = clipStatement(
|
|
6670
|
+
replayAt(cell, relation, level, statement.startBeat, key),
|
|
6671
|
+
statement.endBeat
|
|
6672
|
+
);
|
|
6673
|
+
return { level, notes, outside: notes.filter((note2) => outside(note2.pitch)).length };
|
|
6674
|
+
});
|
|
6675
|
+
const fewestOutside = Math.min(...replays.map((replay) => replay.outside));
|
|
6676
|
+
let best = null;
|
|
6677
|
+
for (const { level, notes: expected, outside: away } of replays) {
|
|
6678
|
+
if (expected.length !== derived.length) {
|
|
6679
|
+
continue;
|
|
6680
|
+
}
|
|
6681
|
+
let forced = 0;
|
|
6682
|
+
let holds = true;
|
|
6683
|
+
for (let i = 0; holds && i < expected.length; i += 1) {
|
|
6684
|
+
const want = expected[i];
|
|
6685
|
+
const got = derived[i];
|
|
6686
|
+
if (i > 0) {
|
|
6687
|
+
const gap = got.startBeat - derived[i - 1].startBeat;
|
|
6688
|
+
holds = Math.abs(gap - (want.startBeat - expected[i - 1].startBeat)) <= _chunkS5BKZZGMcjs.BEAT_EPS;
|
|
6689
|
+
}
|
|
6690
|
+
if (!holds || got.pitch === want.pitch) {
|
|
6691
|
+
continue;
|
|
6692
|
+
}
|
|
6693
|
+
const origin = source[backward ? cell.length - 1 - i : i];
|
|
6694
|
+
forced += 1;
|
|
6695
|
+
holds = closing.has(got) || origin !== void 0 && closing.has(origin) || away === fewestOutside && outside(want.pitch);
|
|
6696
|
+
}
|
|
6697
|
+
const distance = Math.abs(level - planned);
|
|
6698
|
+
const nearer = best === null || distance < Math.abs(best.level - planned) || distance === Math.abs(best.level - planned) && level < best.level;
|
|
6699
|
+
if (holds && nearer) {
|
|
6700
|
+
best = { level, forced };
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
return best;
|
|
6704
|
+
}
|
|
6705
|
+
function motifDerivationViolations(melody, plan, closing) {
|
|
6706
|
+
const keyAt = planKeyAt(plan);
|
|
6707
|
+
const errors = [];
|
|
6708
|
+
const warnings = [];
|
|
6709
|
+
for (const statement of plan.motifs) {
|
|
6710
|
+
if (statement.from === null) {
|
|
6711
|
+
continue;
|
|
6712
|
+
}
|
|
6713
|
+
const source = plan.motifs[statement.from];
|
|
6714
|
+
const sourceNotes = notesInRange(melody, source.startBeat, source.endBeat);
|
|
6715
|
+
const derivedNotes = notesInRange(melody, statement.startBeat, statement.endBeat);
|
|
6716
|
+
const relation = statement.relation;
|
|
6717
|
+
const rationale = `the statement at beat ${statement.startBeat} does not derive from its source the way the plan names`;
|
|
6718
|
+
if (sourceNotes.length === 0 || derivedNotes.length === 0) {
|
|
6719
|
+
errors.push({
|
|
6720
|
+
kind: "motifDerivation",
|
|
6721
|
+
severity: "error",
|
|
6722
|
+
atBeat: statement.startBeat,
|
|
6723
|
+
expected: relation === null ? "a variation" : relation.kind,
|
|
6724
|
+
actual: "no notes to compare",
|
|
6725
|
+
rationale
|
|
6726
|
+
});
|
|
6727
|
+
continue;
|
|
6728
|
+
}
|
|
6729
|
+
if (relation === null) {
|
|
6730
|
+
const similarity = _chunkIFZDWIKMcjs.melodicSimilarity.call(void 0, sourceNotes, derivedNotes);
|
|
6731
|
+
if (similarity < _chunkIFZDWIKMcjs.DEFAULT_VARIATION_THRESHOLD) {
|
|
6732
|
+
errors.push({
|
|
6733
|
+
kind: "motifDerivation",
|
|
6734
|
+
severity: "error",
|
|
6735
|
+
atBeat: statement.startBeat,
|
|
6736
|
+
expected: `a variation (melodicSimilarity >= ${_chunkIFZDWIKMcjs.DEFAULT_VARIATION_THRESHOLD})`,
|
|
6737
|
+
actual: `melodicSimilarity ${similarity.toFixed(3)}`,
|
|
6738
|
+
rationale
|
|
6739
|
+
});
|
|
6740
|
+
}
|
|
6741
|
+
continue;
|
|
6742
|
+
}
|
|
6743
|
+
const replay = replayLevel(
|
|
6744
|
+
sourceNotes,
|
|
6745
|
+
derivedNotes,
|
|
6746
|
+
statement,
|
|
6747
|
+
source.endBeat,
|
|
6748
|
+
plan,
|
|
6749
|
+
_chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(statement.startBeat)),
|
|
6750
|
+
closing
|
|
6751
|
+
);
|
|
6752
|
+
const planned = relation.kind === "tonalTransposition" ? _nullishCoalesce(relation.degrees, () => ( 0)) : relation.semitones;
|
|
6753
|
+
if (replay === null) {
|
|
6754
|
+
errors.push({
|
|
6755
|
+
kind: "motifDerivation",
|
|
6756
|
+
severity: "error",
|
|
6757
|
+
atBeat: statement.startBeat,
|
|
6758
|
+
expected: relation.kind,
|
|
6759
|
+
actual: "no pitch level replays the source",
|
|
6760
|
+
rationale
|
|
6761
|
+
});
|
|
6762
|
+
} else if (replay.level !== planned || replay.forced > 0) {
|
|
6763
|
+
warnings.push({
|
|
6764
|
+
kind: "motifDisplaced",
|
|
6765
|
+
severity: "warning",
|
|
6766
|
+
atBeat: statement.startBeat,
|
|
6767
|
+
expected: `${relation.kind} at level ${planned} with every pitch replayed`,
|
|
6768
|
+
actual: `level ${replay.level}, ${replay.forced} excused note(s)`,
|
|
6769
|
+
rationale: `the statement at beat ${statement.startBeat} carries out its planned derivation at another pitch level or with notes excused`
|
|
6770
|
+
});
|
|
6771
|
+
}
|
|
6772
|
+
}
|
|
6773
|
+
return { errors, warnings };
|
|
6774
|
+
}
|
|
6775
|
+
function phraseAt(plan, beat) {
|
|
6776
|
+
return plan.phrases.find(
|
|
6777
|
+
(phrase) => beat >= phrase.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && beat < phrase.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS
|
|
6778
|
+
);
|
|
6779
|
+
}
|
|
6780
|
+
function registerViolations(melody, plan) {
|
|
6781
|
+
const violations = [];
|
|
6782
|
+
for (const note2 of melody) {
|
|
6783
|
+
const phrase = phraseAt(plan, note2.startBeat);
|
|
6784
|
+
if (phrase === void 0) {
|
|
6785
|
+
continue;
|
|
6786
|
+
}
|
|
6787
|
+
if (note2.pitch < phrase.register.low || note2.pitch > phrase.register.high) {
|
|
6788
|
+
violations.push({
|
|
6789
|
+
kind: "register",
|
|
6790
|
+
severity: "error",
|
|
6791
|
+
atBeat: note2.startBeat,
|
|
6792
|
+
expected: `a pitch within [${phrase.register.low}, ${phrase.register.high}]`,
|
|
6793
|
+
actual: `pitch ${note2.pitch}`,
|
|
6794
|
+
rationale: `the note at beat ${note2.startBeat} falls outside its phrase's planned register`
|
|
6795
|
+
});
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6798
|
+
return violations;
|
|
6799
|
+
}
|
|
6800
|
+
function planOutline(phrase) {
|
|
6801
|
+
const amplitude = (phrase.register.high - phrase.register.low) / 2;
|
|
6802
|
+
const outline = [];
|
|
6803
|
+
for (let k = 0; k < OUTLINE_POINTS; k += 1) {
|
|
6804
|
+
const t = (k + 0.5) / OUTLINE_POINTS;
|
|
6805
|
+
outline.push(
|
|
6806
|
+
phrase.register.mean + plannedContourAt(phrase.shape, phrase.peakPosition, t) * amplitude
|
|
6807
|
+
);
|
|
6808
|
+
}
|
|
6809
|
+
return outline;
|
|
6810
|
+
}
|
|
6811
|
+
function planContourPhrase(phrase) {
|
|
6812
|
+
return {
|
|
6813
|
+
shape: phrase.shape,
|
|
6814
|
+
low: phrase.register.low,
|
|
6815
|
+
high: phrase.register.high,
|
|
6816
|
+
outline: planOutline(phrase)
|
|
6817
|
+
};
|
|
6818
|
+
}
|
|
6819
|
+
function planRegisterPlan(plan) {
|
|
6820
|
+
const phrases = plan.phrases.map((phrase) => ({
|
|
6821
|
+
low: phrase.register.low,
|
|
6822
|
+
high: phrase.register.high,
|
|
6823
|
+
mean: phrase.register.mean,
|
|
6824
|
+
peakPosition: phrase.peakPosition
|
|
6825
|
+
}));
|
|
6826
|
+
let weighted = 0;
|
|
6827
|
+
let weight = 0;
|
|
6828
|
+
for (const phrase of plan.phrases) {
|
|
6829
|
+
const length = phrase.endBeat - phrase.startBeat;
|
|
6830
|
+
weighted += phrase.register.mean * length;
|
|
6831
|
+
weight += length;
|
|
6832
|
+
}
|
|
6833
|
+
return { phrases, mean: weight > 0 ? weighted / weight : null };
|
|
6834
|
+
}
|
|
6835
|
+
function planRhythmReading(plan) {
|
|
6836
|
+
const hasOnsets = plan.rhythm.onsetLevels.some((share) => share > 0);
|
|
6837
|
+
return {
|
|
6838
|
+
onsets: hasOnsets ? 1 : 0,
|
|
6839
|
+
onsetLevels: plan.rhythm.onsetLevels,
|
|
6840
|
+
barPositions: [],
|
|
6841
|
+
interOnsetShares: plan.rhythm.interOnsetShares,
|
|
6842
|
+
syncopation: plan.rhythm.syncopation
|
|
6843
|
+
};
|
|
6844
|
+
}
|
|
6845
|
+
function peakPositionWarnings(plan, readings) {
|
|
6846
|
+
const violations = [];
|
|
6847
|
+
plan.phrases.forEach((phrase, index) => {
|
|
6848
|
+
const reading = readings[index];
|
|
6849
|
+
if (reading == null) {
|
|
6850
|
+
return;
|
|
6851
|
+
}
|
|
6852
|
+
const drift = Math.abs(reading.peakPosition - phrase.peakPosition);
|
|
6853
|
+
if (drift > PEAK_POSITION_TOLERANCE) {
|
|
6854
|
+
violations.push({
|
|
6855
|
+
kind: "peakPosition",
|
|
6856
|
+
severity: "warning",
|
|
6857
|
+
atBeat: phrase.startBeat,
|
|
6858
|
+
expected: `a peak position within ${PEAK_POSITION_TOLERANCE} of ${phrase.peakPosition}`,
|
|
6859
|
+
actual: `${reading.peakPosition}`,
|
|
6860
|
+
rationale: `the phrase starting at beat ${phrase.startBeat} places its melodic peak further from the planned position than the tolerance allows`
|
|
6861
|
+
});
|
|
6862
|
+
}
|
|
6863
|
+
});
|
|
6864
|
+
return violations;
|
|
6865
|
+
}
|
|
6866
|
+
function lineMeanOf(melody) {
|
|
6867
|
+
let weighted = 0;
|
|
6868
|
+
let total = 0;
|
|
6869
|
+
for (const note2 of melody) {
|
|
6870
|
+
weighted += note2.pitch * note2.durationBeat;
|
|
6871
|
+
total += note2.durationBeat;
|
|
6872
|
+
}
|
|
6873
|
+
return total > 0 ? weighted / total : null;
|
|
6874
|
+
}
|
|
6875
|
+
function planDensity(plan) {
|
|
6876
|
+
let weighted = 0;
|
|
6877
|
+
let bars = 0;
|
|
6878
|
+
for (const phrase of plan.phrases) {
|
|
6879
|
+
const span = _chunkIFZDWIKMcjs.barSpanOf.call(void 0, phrase.startBeat, phrase.endBeat, plan.meters);
|
|
6880
|
+
weighted += phrase.onsetDensity * span;
|
|
6881
|
+
bars += span;
|
|
6882
|
+
}
|
|
6883
|
+
return { onsets: 1, onsetDensity: bars > 0 ? weighted / bars : 0 };
|
|
6884
|
+
}
|
|
6885
|
+
function evaluateComposition(melody, plan, opts) {
|
|
6886
|
+
assertCompositionPlan(plan, "evaluated plan");
|
|
6887
|
+
const { budget } = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "evaluate options");
|
|
6888
|
+
const notes = [
|
|
6889
|
+
..._chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, melody, "evaluated melody", { budget, allowNonPositiveDuration: true })
|
|
6890
|
+
].sort((a, b) => a.startBeat - b.startBeat);
|
|
6891
|
+
const timeline = planChordTimeline(plan);
|
|
6892
|
+
const lines = phraseLines(notes, plan);
|
|
6893
|
+
const closing = new Set(
|
|
6894
|
+
lines.flatMap((line) => line.length === 0 ? [] : [notes[line.at(-1)]])
|
|
6895
|
+
);
|
|
6896
|
+
const derivations = motifDerivationViolations(notes, plan, closing);
|
|
6897
|
+
const lineMean = lineMeanOf(notes);
|
|
6898
|
+
const readings = plan.phrases.map(
|
|
6899
|
+
(phrase) => _chunkIFZDWIKMcjs.phraseMelody.call(void 0, notes, phrase.startBeat, phrase.endBeat, _nullishCoalesce(lineMean, () => ( 0)), plan.meters, budget)
|
|
6900
|
+
);
|
|
6901
|
+
const violations = [
|
|
6902
|
+
...spanViolation(notes, plan),
|
|
6903
|
+
...phraseBoundaryViolations(notes, plan),
|
|
6904
|
+
...cadenceViolations(notes, plan, timeline, lines),
|
|
6905
|
+
...harmonyViolations(notes, plan, timeline, lines),
|
|
6906
|
+
...registerViolations(notes, plan),
|
|
6907
|
+
...derivations.errors,
|
|
6908
|
+
...derivations.warnings,
|
|
6909
|
+
...peakPositionWarnings(plan, readings)
|
|
6910
|
+
];
|
|
6911
|
+
const rhythm = _chunkIFZDWIKMcjs.analyzeRhythm.call(void 0, notes, {
|
|
6912
|
+
meters: plan.meters,
|
|
6913
|
+
totalBeats: plan.span.endBeat,
|
|
6914
|
+
budget
|
|
6915
|
+
});
|
|
6916
|
+
const target = planRhythmReading(plan);
|
|
6917
|
+
const fit = {
|
|
6918
|
+
contour: _chunkIFZDWIKMcjs.contourSimilarity.call(void 0, plan.phrases.map(planContourPhrase), readings),
|
|
6919
|
+
register: _chunkIFZDWIKMcjs.registerSimilarity.call(void 0, planRegisterPlan(plan), { phrases: readings, mean: lineMean }),
|
|
6920
|
+
onset: _chunkIFZDWIKMcjs.onsetSimilarity.call(void 0, target, rhythm),
|
|
6921
|
+
duration: _chunkIFZDWIKMcjs.durationSimilarity.call(void 0, target, rhythm),
|
|
6922
|
+
syncopation: _chunkIFZDWIKMcjs.syncopationSimilarity.call(void 0, target, rhythm),
|
|
6923
|
+
density: _chunkIFZDWIKMcjs.densitySimilarity.call(void 0, planDensity(plan), rhythm)
|
|
6924
|
+
};
|
|
6925
|
+
return { violations, fit };
|
|
6926
|
+
}
|
|
6927
|
+
|
|
4964
6928
|
// src/generate/progression/index.ts
|
|
4965
6929
|
var DEFAULT_TS6 = { numerator: 4, denominator: 4 };
|
|
4966
6930
|
var PROG_STYLES = Object.freeze(["minimal", "dance", "idol", "rock"]);
|
|
@@ -5160,7 +7124,7 @@ function degreeToRootPc(degree, key) {
|
|
|
5160
7124
|
}
|
|
5161
7125
|
function autoQuality(degree, key, harmonicDominant) {
|
|
5162
7126
|
if (degree >= 1 && degree <= 7) {
|
|
5163
|
-
if (harmonicDominant &&
|
|
7127
|
+
if (harmonicDominant && _chunkIFZDWIKMcjs.isMinorKey.call(void 0, key) && degree === 5) {
|
|
5164
7128
|
return "maj";
|
|
5165
7129
|
}
|
|
5166
7130
|
return _chunk3R3QVKY5cjs.diatonicTriad.call(void 0, degree, _chunk3R3QVKY5cjs.heptatonicFrameOf.call(void 0, key)).quality;
|
|
@@ -5243,21 +7207,21 @@ function generateProgression(opts) {
|
|
|
5243
7207
|
preset ??= presetFor(opts.style, ctx);
|
|
5244
7208
|
const ext = opts.ext === void 0 ? "auto" : _chunkS5BKZZGMcjs.assertOneOf.call(void 0, opts.ext, ["auto", ..._chunk3R3QVKY5cjs.chordQualities.call(void 0, )], "progression extension");
|
|
5245
7209
|
const cycle = resolveCycle(
|
|
5246
|
-
_nullishCoalesce(_optionalChain([preset, 'optionalAccess',
|
|
7210
|
+
_nullishCoalesce(_optionalChain([preset, 'optionalAccess', _112 => _112.degrees]), () => ( [0])),
|
|
5247
7211
|
key,
|
|
5248
7212
|
ext,
|
|
5249
|
-
_optionalChain([preset, 'optionalAccess',
|
|
7213
|
+
_optionalChain([preset, 'optionalAccess', _113 => _113.functional]) === "cadenceStrong"
|
|
5250
7214
|
);
|
|
5251
7215
|
const barBeats = _chunkS5BKZZGMcjs.beatsPerBar.call(void 0, _nullishCoalesce(opts.ts, () => ( DEFAULT_TS6)));
|
|
5252
7216
|
const chords = [];
|
|
5253
7217
|
for (let bar2 = 0; bar2 < opts.bars; bar2 += 1) {
|
|
5254
7218
|
const step = cycle[bar2 % cycle.length];
|
|
5255
7219
|
const chord = {
|
|
5256
|
-
rootPc: _nullishCoalesce(_optionalChain([step, 'optionalAccess',
|
|
5257
|
-
quality: _nullishCoalesce(_optionalChain([step, 'optionalAccess',
|
|
7220
|
+
rootPc: _nullishCoalesce(_optionalChain([step, 'optionalAccess', _114 => _114.rootPc]), () => ( 0)),
|
|
7221
|
+
quality: _nullishCoalesce(_optionalChain([step, 'optionalAccess', _115 => _115.quality]), () => ( "maj")),
|
|
5258
7222
|
startBeat: bar2 * barBeats
|
|
5259
7223
|
};
|
|
5260
|
-
if (_optionalChain([step, 'optionalAccess',
|
|
7224
|
+
if (_optionalChain([step, 'optionalAccess', _116 => _116.degree]) !== void 0) {
|
|
5261
7225
|
chord.degree = step.degree;
|
|
5262
7226
|
}
|
|
5263
7227
|
chords.push(chord);
|
|
@@ -5278,7 +7242,7 @@ function generateProgression(opts) {
|
|
|
5278
7242
|
const targetsTonic = (next.rootPc - tonicPc + 12) % 12 === 0;
|
|
5279
7243
|
const isLastTonicStatement = cur.rootPc === tonicPc && tonicStatements <= 1;
|
|
5280
7244
|
const alreadySecondary = cur.rootPc === domRoot && cur.quality === "dom7";
|
|
5281
|
-
const isResolutionTarget = _optionalChain([chords, 'access',
|
|
7245
|
+
const isResolutionTarget = _optionalChain([chords, 'access', _117 => _117[i - 1], 'optionalAccess', _118 => _118.secondaryDominant]) === true;
|
|
5282
7246
|
const takesNoDominant = !canBeTonicized(next, tonicizable);
|
|
5283
7247
|
if (targetsTonic || isLastTonicStatement || alreadySecondary || isResolutionTarget || takesNoDominant) {
|
|
5284
7248
|
continue;
|
|
@@ -5375,22 +7339,22 @@ function substituteChord(chord, key, opts) {
|
|
|
5375
7339
|
candidates.push({ chord: spelledInKey(triad, tonic, scale), type: "relative" });
|
|
5376
7340
|
}
|
|
5377
7341
|
}
|
|
5378
|
-
const targetFunction =
|
|
5379
|
-
const parallel =
|
|
7342
|
+
const targetFunction = _chunkIFZDWIKMcjs.functionOf.call(void 0, source, scale);
|
|
7343
|
+
const parallel = _chunkIFZDWIKMcjs.parallelKey.call(void 0, scale);
|
|
5380
7344
|
for (const triad of diatonicTriadsOf(parallel)) {
|
|
5381
|
-
if (
|
|
7345
|
+
if (_chunkIFZDWIKMcjs.functionOf.call(void 0, triad, scale) === targetFunction && !_chunkIFZDWIKMcjs.isDiatonic.call(void 0, triad, scale)) {
|
|
5382
7346
|
candidates.push({ chord: spelledInKey(triad, tonic, parallel), type: "borrowed" });
|
|
5383
7347
|
}
|
|
5384
7348
|
}
|
|
5385
7349
|
for (const offset of THIRD_OFFSETS) {
|
|
5386
7350
|
for (const quality of ["maj", "min"]) {
|
|
5387
7351
|
const mediant = _chunk3R3QVKY5cjs.makeChord.call(void 0, _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, source.rootPc + offset), quality);
|
|
5388
|
-
if (commonToneCount(originalTriad, _chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, mediant)) === 1 && !
|
|
7352
|
+
if (commonToneCount(originalTriad, _chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, mediant)) === 1 && !_chunkIFZDWIKMcjs.isDiatonic.call(void 0, mediant, scale)) {
|
|
5389
7353
|
candidates.push({ chord: spelledInKey(mediant, tonic, scale), type: "chromaticMediant" });
|
|
5390
7354
|
}
|
|
5391
7355
|
}
|
|
5392
7356
|
}
|
|
5393
|
-
const melodyPcs = _optionalChain([opts, 'optionalAccess',
|
|
7357
|
+
const melodyPcs = _optionalChain([opts, 'optionalAccess', _119 => _119.melodyPcs, 'optionalAccess', _120 => _120.map, 'call', _121 => _121(_chunkS5BKZZGMcjs.pitchClassOf)]);
|
|
5394
7358
|
const seen = /* @__PURE__ */ new Set();
|
|
5395
7359
|
const results = [];
|
|
5396
7360
|
for (const candidate of candidates) {
|
|
@@ -5411,8 +7375,8 @@ function substituteChord(chord, key, opts) {
|
|
|
5411
7375
|
results.push({
|
|
5412
7376
|
chord: candidate.chord,
|
|
5413
7377
|
type: candidate.type,
|
|
5414
|
-
roman:
|
|
5415
|
-
function:
|
|
7378
|
+
roman: _chunkIFZDWIKMcjs.chordToRoman.call(void 0, candidate.chord, scale),
|
|
7379
|
+
function: _chunkIFZDWIKMcjs.functionOf.call(void 0, candidate.chord, scale)
|
|
5416
7380
|
});
|
|
5417
7381
|
}
|
|
5418
7382
|
return results;
|
|
@@ -5429,18 +7393,18 @@ function modalInterchangePalette(key) {
|
|
|
5429
7393
|
seen.add(dedupeKey);
|
|
5430
7394
|
palette.push({
|
|
5431
7395
|
chord,
|
|
5432
|
-
roman:
|
|
5433
|
-
source:
|
|
7396
|
+
roman: _chunkIFZDWIKMcjs.chordToRoman.call(void 0, chord, scale),
|
|
7397
|
+
source: _chunkIFZDWIKMcjs.borrowedSource.call(void 0, chord, scale)
|
|
5434
7398
|
});
|
|
5435
7399
|
};
|
|
5436
|
-
const parallel =
|
|
7400
|
+
const parallel = _chunkIFZDWIKMcjs.parallelKey.call(void 0, scale);
|
|
5437
7401
|
for (const triad of diatonicTriadsOf(parallel)) {
|
|
5438
|
-
if (!
|
|
7402
|
+
if (!_chunkIFZDWIKMcjs.isDiatonic.call(void 0, triad, scale) && _chunkIFZDWIKMcjs.borrowedSource.call(void 0, triad, scale) !== null) {
|
|
5439
7403
|
add(spelledInKey(triad, tonic, parallel));
|
|
5440
7404
|
}
|
|
5441
7405
|
}
|
|
5442
7406
|
const neapolitan = _chunk3R3QVKY5cjs.makeChord.call(void 0, _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, scale.rootPc + 1), "maj");
|
|
5443
|
-
if (!
|
|
7407
|
+
if (!_chunkIFZDWIKMcjs.isDiatonic.call(void 0, neapolitan, scale)) {
|
|
5444
7408
|
add(spelledOnRoot(neapolitan, _chunkS5BKZZGMcjs.transposeNote.call(void 0, tonic, 1), tonic, scale));
|
|
5445
7409
|
}
|
|
5446
7410
|
return palette;
|
|
@@ -5453,7 +7417,7 @@ function negativeHarmonyMirror(chord, key) {
|
|
|
5453
7417
|
const pcs = _chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, source).map(mirror);
|
|
5454
7418
|
const bassPc = source.bassPc !== void 0 ? mirror(source.bassPc) : void 0;
|
|
5455
7419
|
const pitches = bassPc !== void 0 ? [bassPc, ...pcs.map((pc) => pc + 12)] : pcs;
|
|
5456
|
-
const detected =
|
|
7420
|
+
const detected = _chunkIFZDWIKMcjs.detectChordBest.call(void 0, pitches);
|
|
5457
7421
|
if (detected) {
|
|
5458
7422
|
if (bassPc === void 0 && detected.bassPc !== void 0) {
|
|
5459
7423
|
return spelledInKey(_chunk3R3QVKY5cjs.makeChord.call(void 0, detected.rootPc, detected.quality), tonic, scale);
|
|
@@ -5543,7 +7507,7 @@ function rhythmDensity(events, ts) {
|
|
|
5543
7507
|
return 0;
|
|
5544
7508
|
}
|
|
5545
7509
|
const last = events[events.length - 1];
|
|
5546
|
-
const spanBeats = (_nullishCoalesce(_optionalChain([last, 'optionalAccess',
|
|
7510
|
+
const spanBeats = (_nullishCoalesce(_optionalChain([last, 'optionalAccess', _122 => _122.position]), () => ( 0))) + (_nullishCoalesce(_optionalChain([last, 'optionalAccess', _123 => _123.duration]), () => ( 0)));
|
|
5547
7511
|
const bars = spanBeats / barBeats;
|
|
5548
7512
|
return bars > 0 ? events.length / bars : 0;
|
|
5549
7513
|
}
|
|
@@ -5617,5 +7581,11 @@ function rhythmDensity(events, ts) {
|
|
|
5617
7581
|
|
|
5618
7582
|
|
|
5619
7583
|
|
|
5620
|
-
|
|
5621
|
-
|
|
7584
|
+
|
|
7585
|
+
|
|
7586
|
+
|
|
7587
|
+
|
|
7588
|
+
|
|
7589
|
+
|
|
7590
|
+
exports.GENRES = GENRES; exports.PROVENANCE_BASES = PROVENANCE_BASES; exports.fitsQuery = fitsQuery; exports.selectVocabulary = selectVocabulary; exports.pickVocabulary = pickVocabulary; exports.mergeVocabulary = mergeVocabulary; exports.vocabularyOfKind = vocabularyOfKind; exports.assertVocabulary = assertVocabulary; exports.MIN_DIFFICULTY = MIN_DIFFICULTY; exports.MAX_DIFFICULTY = MAX_DIFFICULTY; exports.sustainsStrokes = sustainsStrokes; exports.sustainsShift = sustainsShift; exports.resolveContext = resolveContext; exports.BAR_STEPS = BAR_STEPS; exports.BEAT_STEPS = BEAT_STEPS; exports.STEP_BEATS = STEP_BEATS; exports.gridMetricWeight = gridMetricWeight; exports.thin = thin; exports.double = double; exports.halfTime = halfTime; exports.doubleTime = doubleTime; exports.syncopate = syncopate; exports.ornamentBy = ornamentBy; exports.deform = deform; exports.withinCeiling = withinCeiling; exports.isLickMaterial = isLickMaterial; exports.BASS_LICKS = BASS_LICKS; exports.placeLicks = placeLicks; exports.BASS_STYLES = BASS_STYLES; exports.generateBassLine = generateBassLine; exports.imitate = imitate; exports.generateCounterMelody = generateCounterMelody; exports.FILL_TYPES = FILL_TYPES; exports.FILL_ARCHETYPES = FILL_ARCHETYPES; exports.isFillArchetype = isFillArchetype; exports.DRUM_NOTES = DRUM_NOTES; exports.drumVoiceOf = drumVoiceOf; exports.KICK_STEPS = KICK_STEPS; exports.KICK_FIGURES = KICK_FIGURES; exports.DRUM_KIT = DRUM_KIT; exports.isDrumPattern = isDrumPattern; exports.DRUM_PATTERNS = DRUM_PATTERNS; exports.placeDrumPattern = placeDrumPattern; exports.generateDrums = generateDrums; exports.humanize = humanize; exports.extractGrooveTemplate = extractGrooveTemplate; exports.applyGrooveTemplate = applyGrooveTemplate; exports.classifyMelodyTones = classifyMelodyTones; exports.harmonizeMelody = harmonizeMelody; exports.cellSpan = cellSpan; exports.generateMotif = generateMotif; exports.motifToNoteEvents = motifToNoteEvents; exports.transformMotif = transformMotif; exports.developMotif = developMotif; exports.COMPOSITION_PLAN_VERSION = COMPOSITION_PLAN_VERSION; exports.assertCompositionPlan = assertCompositionPlan; exports.planTimeline = planTimeline; exports.generateMelody = generateMelody; exports.ORNAMENT_STYLES = ORNAMENT_STYLES; exports.ornament = ornament; exports.deriveCompositionPlan = deriveCompositionPlan; exports.evaluateComposition = evaluateComposition; exports.BORROWED_DEGREES = BORROWED_DEGREES; exports.progressions = progressions; exports.progressionsByStyle = progressionsByStyle; exports.pickProgressionPreset = pickProgressionPreset; exports.generateProgression = generateProgression; exports.substituteChord = substituteChord; exports.modalInterchangePalette = modalInterchangePalette; exports.negativeHarmonyMirror = negativeHarmonyMirror; exports.rhythmToNoteEvents = rhythmToNoteEvents; exports.onsetWeightCurve = onsetWeightCurve; exports.generateRhythm = generateRhythm; exports.rhythmDensity = rhythmDensity;
|
|
7591
|
+
//# sourceMappingURL=chunk-B2PW6EHN.cjs.map
|