@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.
Files changed (56) hide show
  1. package/README.md +4 -0
  2. package/README_ja.md +4 -0
  3. package/dist/analyze/index.cjs +28 -4
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +151 -5
  6. package/dist/analyze/index.d.ts +151 -5
  7. package/dist/analyze/index.js +34 -10
  8. package/dist/{chunk-DSMJRXCZ.cjs → chunk-4R2LWV7E.cjs} +384 -107
  9. package/dist/chunk-4R2LWV7E.cjs.map +1 -0
  10. package/dist/{chunk-J7FHVZII.cjs → chunk-B2PW6EHN.cjs} +2000 -30
  11. package/dist/chunk-B2PW6EHN.cjs.map +1 -0
  12. package/dist/{chunk-Q6LFSAKV.js → chunk-CPMB7P75.js} +1973 -3
  13. package/dist/chunk-CPMB7P75.js.map +1 -0
  14. package/dist/{chunk-H2KJHBW4.js → chunk-GNVN4PPJ.js} +1871 -5
  15. package/dist/chunk-GNVN4PPJ.js.map +1 -0
  16. package/dist/{chunk-SGLEQVJH.cjs → chunk-IFZDWIKM.cjs} +1868 -2
  17. package/dist/chunk-IFZDWIKM.cjs.map +1 -0
  18. package/dist/{chunk-FBH75DAY.js → chunk-IPEF6NWB.js} +287 -10
  19. package/dist/chunk-IPEF6NWB.js.map +1 -0
  20. package/dist/{chunk-HYJJ2V2I.js → chunk-MLBPXA4Z.js} +300 -720
  21. package/dist/chunk-MLBPXA4Z.js.map +1 -0
  22. package/dist/{chunk-HWHGOVO6.cjs → chunk-PWC4CRWT.cjs} +1 -1
  23. package/dist/{chunk-HWHGOVO6.cjs.map → chunk-PWC4CRWT.cjs.map} +1 -1
  24. package/dist/{chunk-HPVSH7SF.cjs → chunk-YFA7X2F6.cjs} +350 -770
  25. package/dist/chunk-YFA7X2F6.cjs.map +1 -0
  26. package/dist/{chunk-NFJ4P6ZZ.js → chunk-ZBUROTJQ.js} +1 -1
  27. package/dist/generate/index.cjs +15 -3
  28. package/dist/generate/index.cjs.map +1 -1
  29. package/dist/generate/index.d.cts +84 -6
  30. package/dist/generate/index.d.ts +84 -6
  31. package/dist/generate/index.js +14 -2
  32. package/dist/{index-BNSM-DSk.d.cts → index-CY-3NVfy.d.cts} +484 -3
  33. package/dist/{index-BJgP97N1.d.ts → index-zaZdmnU4.d.ts} +484 -3
  34. package/dist/index.cjs +43 -7
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +5 -5
  37. package/dist/index.d.ts +5 -5
  38. package/dist/index.js +48 -12
  39. package/dist/model/index.cjs +5 -5
  40. package/dist/model/index.d.cts +203 -7
  41. package/dist/model/index.d.ts +203 -7
  42. package/dist/model/index.js +4 -4
  43. package/dist/{index-DGCNKAUr.d.cts → profile-CQMEqdH9.d.cts} +65 -598
  44. package/dist/{index-BRMbPkHE.d.ts → profile-DLdlIpS9.d.ts} +65 -598
  45. package/dist/{index-BBWNZS0f.d.cts → types-D-XgxGvp.d.cts} +1169 -13
  46. package/dist/{index-CJBBiAmU.d.ts → types-Dh7C064C.d.ts} +1169 -13
  47. package/package.json +5 -5
  48. package/dist/chunk-DSMJRXCZ.cjs.map +0 -1
  49. package/dist/chunk-FBH75DAY.js.map +0 -1
  50. package/dist/chunk-H2KJHBW4.js.map +0 -1
  51. package/dist/chunk-HPVSH7SF.cjs.map +0 -1
  52. package/dist/chunk-HYJJ2V2I.js.map +0 -1
  53. package/dist/chunk-J7FHVZII.cjs.map +0 -1
  54. package/dist/chunk-Q6LFSAKV.js.map +0 -1
  55. package/dist/chunk-SGLEQVJH.cjs.map +0 -1
  56. /package/dist/{chunk-NFJ4P6ZZ.js.map → chunk-ZBUROTJQ.js.map} +0 -0
@@ -8,7 +8,31 @@
8
8
 
9
9
 
10
10
 
11
- var _chunkSGLEQVJHcjs = require('./chunk-SGLEQVJH.cjs');
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) <= _chunkSGLEQVJHcjs.HUMANIZE_ADJACENCY ? 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 > _chunkSGLEQVJHcjs.HUMANIZE_ADJACENCY || outOfNote > _chunkSGLEQVJHcjs.HUMANIZE_ADJACENCY) {
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 _chunkSGLEQVJHcjs.isMinorKey.call(void 0, key) && _chunk3R3QVKY5cjs.scaleSystemOf.call(void 0, key) === "common-practice";
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 = _chunkSGLEQVJHcjs.parallelKey.call(void 0, key);
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 (!_chunkSGLEQVJHcjs.isDiatonic.call(void 0, chord, key)) {
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
- _chunkSGLEQVJHcjs.assertChordTimeline.call(void 0, timeline);
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 && _chunkSGLEQVJHcjs.isMinorKey.call(void 0, key) && degree === 5) {
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', _59 => _59.degrees]), () => ( [0])),
7210
+ _nullishCoalesce(_optionalChain([preset, 'optionalAccess', _112 => _112.degrees]), () => ( [0])),
5247
7211
  key,
5248
7212
  ext,
5249
- _optionalChain([preset, 'optionalAccess', _60 => _60.functional]) === "cadenceStrong"
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', _61 => _61.rootPc]), () => ( 0)),
5257
- quality: _nullishCoalesce(_optionalChain([step, 'optionalAccess', _62 => _62.quality]), () => ( "maj")),
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', _63 => _63.degree]) !== void 0) {
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', _64 => _64[i - 1], 'optionalAccess', _65 => _65.secondaryDominant]) === true;
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 = _chunkSGLEQVJHcjs.functionOf.call(void 0, source, scale);
5379
- const parallel = _chunkSGLEQVJHcjs.parallelKey.call(void 0, scale);
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 (_chunkSGLEQVJHcjs.functionOf.call(void 0, triad, scale) === targetFunction && !_chunkSGLEQVJHcjs.isDiatonic.call(void 0, triad, scale)) {
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 && !_chunkSGLEQVJHcjs.isDiatonic.call(void 0, mediant, scale)) {
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', _66 => _66.melodyPcs, 'optionalAccess', _67 => _67.map, 'call', _68 => _68(_chunkS5BKZZGMcjs.pitchClassOf)]);
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: _chunkSGLEQVJHcjs.chordToRoman.call(void 0, candidate.chord, scale),
5415
- function: _chunkSGLEQVJHcjs.functionOf.call(void 0, candidate.chord, scale)
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: _chunkSGLEQVJHcjs.chordToRoman.call(void 0, chord, scale),
5433
- source: _chunkSGLEQVJHcjs.borrowedSource.call(void 0, chord, scale)
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 = _chunkSGLEQVJHcjs.parallelKey.call(void 0, scale);
7400
+ const parallel = _chunkIFZDWIKMcjs.parallelKey.call(void 0, scale);
5437
7401
  for (const triad of diatonicTriadsOf(parallel)) {
5438
- if (!_chunkSGLEQVJHcjs.isDiatonic.call(void 0, triad, scale) && _chunkSGLEQVJHcjs.borrowedSource.call(void 0, triad, scale) !== null) {
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 (!_chunkSGLEQVJHcjs.isDiatonic.call(void 0, neapolitan, scale)) {
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 = _chunkSGLEQVJHcjs.detectChordBest.call(void 0, pitches);
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', _69 => _69.position]), () => ( 0))) + (_nullishCoalesce(_optionalChain([last, 'optionalAccess', _70 => _70.duration]), () => ( 0)));
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
- 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.ORNAMENT_STYLES = ORNAMENT_STYLES; exports.ornament = ornament; 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;
5621
- //# sourceMappingURL=chunk-J7FHVZII.cjs.map
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