@libraz/libcantus 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +63 -0
- package/README.md +7 -1
- package/README_ja.md +17 -11
- package/dist/analyze/index.cjs +31 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +161 -13
- package/dist/analyze/index.d.ts +161 -13
- package/dist/analyze/index.js +37 -13
- package/dist/{chunk-NB25P3EH.js → chunk-2G5MCGYU.js} +33 -11
- package/dist/chunk-2G5MCGYU.js.map +1 -0
- package/dist/{chunk-NM7UAMC3.cjs → chunk-3FDBMIPR.cjs} +64 -64
- package/dist/chunk-3FDBMIPR.cjs.map +1 -0
- package/dist/{chunk-HPVSH7SF.cjs → chunk-3MDOEFIQ.cjs} +618 -954
- package/dist/chunk-3MDOEFIQ.cjs.map +1 -0
- package/dist/{chunk-FBH75DAY.js → chunk-5MWRBYAT.js} +560 -159
- package/dist/chunk-5MWRBYAT.js.map +1 -0
- package/dist/{chunk-J7FHVZII.cjs → chunk-6TTBEON3.cjs} +3074 -740
- package/dist/chunk-6TTBEON3.cjs.map +1 -0
- package/dist/{chunk-UMWZ4ZET.js → chunk-7NQ6AL3H.js} +4 -4
- package/dist/chunk-7NQ6AL3H.js.map +1 -0
- package/dist/{chunk-HWHGOVO6.cjs → chunk-E3QO3UET.cjs} +31 -23
- package/dist/chunk-E3QO3UET.cjs.map +1 -0
- package/dist/{chunk-H2KJHBW4.js → chunk-FJFULJZE.js} +2375 -191
- package/dist/chunk-FJFULJZE.js.map +1 -0
- package/dist/{chunk-Q6LFSAKV.js → chunk-FK3DGAIZ.js} +2657 -323
- package/dist/chunk-FK3DGAIZ.js.map +1 -0
- package/dist/chunk-GEDTJHAC.cjs.map +1 -1
- package/dist/{chunk-RFIBF2RR.js → chunk-H7FVMBUC.js} +138 -65
- package/dist/chunk-H7FVMBUC.js.map +1 -0
- package/dist/{chunk-DSMJRXCZ.cjs → chunk-HYSVSFNI.cjs} +931 -530
- package/dist/chunk-HYSVSFNI.cjs.map +1 -0
- package/dist/{chunk-35NBV2FT.cjs → chunk-KMYGRFZ6.cjs} +47 -24
- package/dist/chunk-KMYGRFZ6.cjs.map +1 -0
- package/dist/chunk-LQZQDBCD.cjs +5513 -0
- package/dist/chunk-LQZQDBCD.cjs.map +1 -0
- package/dist/{chunk-PM4XJE2Y.cjs → chunk-P3CPXAJ5.cjs} +76 -54
- package/dist/chunk-P3CPXAJ5.cjs.map +1 -0
- package/dist/{chunk-S5BKZZGM.cjs → chunk-S3QELJUE.cjs} +72 -56
- package/dist/chunk-S3QELJUE.cjs.map +1 -0
- package/dist/{chunk-PUKJP2Z5.js → chunk-TXFWSU5D.js} +31 -8
- package/dist/chunk-TXFWSU5D.js.map +1 -0
- package/dist/{chunk-76ZBTRG2.cjs → chunk-U4EQTDGN.cjs} +258 -218
- package/dist/chunk-U4EQTDGN.cjs.map +1 -0
- package/dist/chunk-UGLFDG7L.cjs.map +1 -1
- package/dist/{chunk-HYJJ2V2I.js → chunk-UTE5A36R.js} +483 -819
- package/dist/chunk-UTE5A36R.js.map +1 -0
- package/dist/{chunk-NFJ4P6ZZ.js → chunk-UZKQISRF.js} +17 -9
- package/dist/chunk-UZKQISRF.js.map +1 -0
- package/dist/{chunk-7KG3RTYS.js → chunk-VL5NXVCN.js} +281 -155
- package/dist/chunk-VL5NXVCN.js.map +1 -0
- package/dist/{chunk-CGBADIO5.cjs → chunk-WOMCXN2W.cjs} +328 -202
- package/dist/chunk-WOMCXN2W.cjs.map +1 -0
- package/dist/{chunk-3R3QVKY5.cjs → chunk-YDGMWH43.cjs} +397 -324
- package/dist/chunk-YDGMWH43.cjs.map +1 -0
- package/dist/{chunk-AGOVW7U6.js → chunk-YYVXRV3V.js} +72 -56
- package/dist/chunk-YYVXRV3V.js.map +1 -0
- package/dist/{chunk-M5RYMAFI.js → chunk-ZLNSH4UL.js} +71 -31
- package/dist/chunk-ZLNSH4UL.js.map +1 -0
- package/dist/{coerce-DRERzdve.d.cts → coerce-CUhHc3Tu.d.ts} +16 -8
- package/dist/{coerce-DP0vYu-x.d.ts → coerce-Ytt2Eiq9.d.cts} +16 -8
- package/dist/core/index.cjs +5 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +9 -8
- package/dist/core/index.d.ts +9 -8
- package/dist/core/index.js +4 -4
- package/dist/generate/index.cjs +20 -8
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +105 -17
- package/dist/generate/index.d.ts +105 -17
- package/dist/generate/index.js +19 -7
- package/dist/{index-CL4b4gUQ.d.ts → index-5VJgKz_Q.d.cts} +6 -2
- package/dist/{index-B9QLziDe.d.cts → index-BLuDesVG.d.ts} +20 -13
- package/dist/{index-BHIUyjhZ.d.ts → index-BV6APVa6.d.cts} +20 -13
- package/dist/{index-BMweUlm5.d.cts → index-Bf0Sv8a9.d.ts} +6 -2
- package/dist/{index-Dw2DP7vm.d.ts → index-BkT21EMv.d.cts} +3 -2
- package/dist/{index-BL5N2Oyz.d.cts → index-C7KCaTRz.d.ts} +3 -2
- package/dist/{index-Cy15oTvh.d.ts → index-C9MYbuKS.d.cts} +7 -3
- package/dist/{index-Cy15oTvh.d.cts → index-C9MYbuKS.d.ts} +7 -3
- package/dist/{index-BNSM-DSk.d.cts → index-DVInzCDS.d.cts} +533 -28
- package/dist/{index-9eoQs2C1.d.ts → index-Dg-SAfhz.d.ts} +6 -4
- package/dist/{index-BJgP97N1.d.ts → index-jgeug9EA.d.ts} +533 -28
- package/dist/{index-cHvbYgJI.d.cts → index-pQihL4z7.d.cts} +6 -4
- package/dist/index.cjs +49 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -13
- package/dist/index.d.ts +13 -13
- package/dist/index.js +55 -19
- package/dist/{masks-Cc7-MzzY.d.ts → masks-BnzXnBj0.d.cts} +3 -2
- package/dist/{masks-pADfTOiD.d.cts → masks-tycmkrgs.d.ts} +3 -2
- package/dist/model/index.cjs +12 -12
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +259 -48
- package/dist/model/index.d.ts +259 -48
- package/dist/model/index.js +11 -11
- package/dist/{positional-DJgs6ltD.d.ts → positional-CscpxtlE.d.cts} +3 -2
- package/dist/{positional-DMZer-FM.d.cts → positional-DLnfUgqG.d.ts} +3 -2
- package/dist/{index-BRMbPkHE.d.ts → profile-B4WqcM4H.d.ts} +85 -610
- package/dist/{index-DGCNKAUr.d.cts → profile-BJd1XkPB.d.cts} +85 -610
- package/dist/theory/index.cjs +5 -5
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +18 -14
- package/dist/theory/index.d.ts +18 -14
- package/dist/theory/index.js +4 -4
- package/dist/{index-CJBBiAmU.d.ts → types-7mGFwvMm.d.ts} +1210 -31
- package/dist/{index-BBWNZS0f.d.cts → types-_ofpb2be.d.cts} +1210 -31
- package/package.json +13 -13
- package/dist/chunk-35NBV2FT.cjs.map +0 -1
- package/dist/chunk-3R3QVKY5.cjs.map +0 -1
- package/dist/chunk-76ZBTRG2.cjs.map +0 -1
- package/dist/chunk-7KG3RTYS.js.map +0 -1
- package/dist/chunk-AGOVW7U6.js.map +0 -1
- package/dist/chunk-CGBADIO5.cjs.map +0 -1
- package/dist/chunk-DSMJRXCZ.cjs.map +0 -1
- package/dist/chunk-FBH75DAY.js.map +0 -1
- package/dist/chunk-H2KJHBW4.js.map +0 -1
- package/dist/chunk-HPVSH7SF.cjs.map +0 -1
- package/dist/chunk-HWHGOVO6.cjs.map +0 -1
- package/dist/chunk-HYJJ2V2I.js.map +0 -1
- package/dist/chunk-J7FHVZII.cjs.map +0 -1
- package/dist/chunk-M5RYMAFI.js.map +0 -1
- package/dist/chunk-NB25P3EH.js.map +0 -1
- package/dist/chunk-NFJ4P6ZZ.js.map +0 -1
- package/dist/chunk-NM7UAMC3.cjs.map +0 -1
- package/dist/chunk-PM4XJE2Y.cjs.map +0 -1
- package/dist/chunk-PUKJP2Z5.js.map +0 -1
- package/dist/chunk-Q6LFSAKV.js.map +0 -1
- package/dist/chunk-RFIBF2RR.js.map +0 -1
- package/dist/chunk-S5BKZZGM.cjs.map +0 -1
- package/dist/chunk-SGLEQVJH.cjs +0 -3329
- package/dist/chunk-SGLEQVJH.cjs.map +0 -1
- package/dist/chunk-UMWZ4ZET.js.map +0 -1
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
analyzeArrangement,
|
|
3
3
|
analyzePolyphony,
|
|
4
|
+
analyzeReference,
|
|
5
|
+
arrangementProfile,
|
|
6
|
+
assertTrackNotes,
|
|
7
|
+
boundaryCadences,
|
|
4
8
|
createArrangementSession,
|
|
5
|
-
|
|
9
|
+
harmonyTrackSet,
|
|
6
10
|
hypermeter,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
motifFromNotes,
|
|
11
|
+
hypermeterOver,
|
|
12
|
+
keyContextFor,
|
|
10
13
|
phrasesFromTimeline,
|
|
11
|
-
|
|
12
|
-
relateMotifs,
|
|
14
|
+
referenceFromReadings,
|
|
13
15
|
sectionsFromNotes,
|
|
14
16
|
structuralCadences,
|
|
15
17
|
tensionCurveFrom,
|
|
16
18
|
trackRoleOf
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-UTE5A36R.js";
|
|
18
20
|
import {
|
|
19
21
|
TWELVE_TET,
|
|
20
22
|
beatsToDuration,
|
|
@@ -38,22 +40,25 @@ import {
|
|
|
38
40
|
tempoAt,
|
|
39
41
|
tempoReader,
|
|
40
42
|
ticksToBeats
|
|
41
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-7NQ6AL3H.js";
|
|
42
44
|
import {
|
|
43
45
|
STEP_BEATS,
|
|
44
46
|
applyGrooveTemplate,
|
|
45
47
|
cellSpan,
|
|
46
|
-
|
|
48
|
+
deformFigure,
|
|
49
|
+
deriveCompositionPlan,
|
|
47
50
|
developMotif,
|
|
48
|
-
|
|
51
|
+
doubleTimeFigure,
|
|
52
|
+
evaluateComposition,
|
|
49
53
|
extractGrooveTemplate,
|
|
50
54
|
generateBassLine,
|
|
51
55
|
generateCounterMelody,
|
|
52
56
|
generateDrums,
|
|
57
|
+
generateMelody,
|
|
53
58
|
generateMotif,
|
|
54
59
|
generateProgression,
|
|
55
60
|
generateRhythm,
|
|
56
|
-
|
|
61
|
+
halfTimeFigure,
|
|
57
62
|
harmonizeMelody,
|
|
58
63
|
humanize,
|
|
59
64
|
modalInterchangePalette,
|
|
@@ -62,6 +67,7 @@ import {
|
|
|
62
67
|
ornament,
|
|
63
68
|
ornamentBy,
|
|
64
69
|
resolveContext,
|
|
70
|
+
resolveDials,
|
|
65
71
|
rhythmDensity,
|
|
66
72
|
rhythmToNoteEvents,
|
|
67
73
|
substituteChord,
|
|
@@ -69,9 +75,11 @@ import {
|
|
|
69
75
|
thin,
|
|
70
76
|
transformMotif,
|
|
71
77
|
withinCeiling
|
|
72
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-FK3DGAIZ.js";
|
|
73
79
|
import {
|
|
74
80
|
analyzeChord,
|
|
81
|
+
analyzeRhythm,
|
|
82
|
+
assertSpansInOrder,
|
|
75
83
|
augmentedSixthChord,
|
|
76
84
|
borrowedSource,
|
|
77
85
|
chordAtBeat,
|
|
@@ -87,17 +95,25 @@ import {
|
|
|
87
95
|
detectKeyFromNotes,
|
|
88
96
|
detectModulations,
|
|
89
97
|
explainRoman,
|
|
98
|
+
extractMotifs,
|
|
90
99
|
functionOf,
|
|
91
100
|
isBorrowedChord,
|
|
92
101
|
isMinorKey,
|
|
93
102
|
keyLookup,
|
|
94
103
|
keyTimelineFromNotes,
|
|
104
|
+
melodicContour,
|
|
105
|
+
melodicSimilarity,
|
|
106
|
+
motifFromNotes,
|
|
107
|
+
motifGraph,
|
|
95
108
|
pivotChords,
|
|
96
109
|
prevailingKeyOf,
|
|
110
|
+
reduceProgression,
|
|
111
|
+
relateMotifs,
|
|
97
112
|
romanToChord,
|
|
98
113
|
secondaryDominantOf,
|
|
114
|
+
statedKeyRegions,
|
|
99
115
|
toVoiceNotes
|
|
100
|
-
} from "./chunk-
|
|
116
|
+
} from "./chunk-FJFULJZE.js";
|
|
101
117
|
import {
|
|
102
118
|
BASS_4_STRING,
|
|
103
119
|
BASS_5_STRING,
|
|
@@ -110,10 +126,10 @@ import {
|
|
|
110
126
|
soundsIn,
|
|
111
127
|
toInstrumentProfile,
|
|
112
128
|
toStringedProfile
|
|
113
|
-
} from "./chunk-
|
|
129
|
+
} from "./chunk-VL5NXVCN.js";
|
|
114
130
|
import {
|
|
115
131
|
createNoteEventIndex
|
|
116
|
-
} from "./chunk-
|
|
132
|
+
} from "./chunk-TXFWSU5D.js";
|
|
117
133
|
import {
|
|
118
134
|
SATB_RANGES,
|
|
119
135
|
checkPartWriting,
|
|
@@ -128,14 +144,14 @@ import {
|
|
|
128
144
|
voiceChordStyled,
|
|
129
145
|
voiceLeadingCost,
|
|
130
146
|
voiceProgression
|
|
131
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-ZLNSH4UL.js";
|
|
132
148
|
import {
|
|
133
149
|
availableTensions,
|
|
134
150
|
avoidNotes,
|
|
135
151
|
chordScaleReport,
|
|
136
152
|
chordScales,
|
|
137
153
|
scalesForChanges
|
|
138
|
-
} from "./chunk-
|
|
154
|
+
} from "./chunk-2G5MCGYU.js";
|
|
139
155
|
import {
|
|
140
156
|
NAMED_SCALES,
|
|
141
157
|
assertChordQuality,
|
|
@@ -146,6 +162,7 @@ import {
|
|
|
146
162
|
chordSpecOf,
|
|
147
163
|
chordToneRole,
|
|
148
164
|
chordToneSpellings,
|
|
165
|
+
classifySpelledInterval,
|
|
149
166
|
diatonicPitchClasses,
|
|
150
167
|
diatonicSeventh,
|
|
151
168
|
diatonicTriad,
|
|
@@ -191,10 +208,11 @@ import {
|
|
|
191
208
|
tryResolveKeyName,
|
|
192
209
|
variantOfMask,
|
|
193
210
|
voiceIndependence
|
|
194
|
-
} from "./chunk-
|
|
211
|
+
} from "./chunk-H7FVMBUC.js";
|
|
195
212
|
import {
|
|
196
213
|
InvalidInputError,
|
|
197
214
|
MAX_NAME_ACCIDENTALS,
|
|
215
|
+
assertArray,
|
|
198
216
|
assertFiniteNumber,
|
|
199
217
|
assertInteger,
|
|
200
218
|
assertMidiPitch,
|
|
@@ -202,9 +220,11 @@ import {
|
|
|
202
220
|
assertOneOf,
|
|
203
221
|
assertPositiveInt,
|
|
204
222
|
assertRange,
|
|
223
|
+
assertRecord,
|
|
205
224
|
assertTimeSignature,
|
|
206
225
|
beatToBarPosition,
|
|
207
226
|
beatsPerBar,
|
|
227
|
+
beatsPerBarAt,
|
|
208
228
|
describeRejected,
|
|
209
229
|
formatBarPosition,
|
|
210
230
|
formatKeyName,
|
|
@@ -212,7 +232,6 @@ import {
|
|
|
212
232
|
formatTimeSignature,
|
|
213
233
|
intervalSemitones,
|
|
214
234
|
isCompound,
|
|
215
|
-
isConsonantInterval,
|
|
216
235
|
isStrongBeat,
|
|
217
236
|
meterAt,
|
|
218
237
|
metricWeight,
|
|
@@ -235,7 +254,7 @@ import {
|
|
|
235
254
|
tryParseTimeSignature,
|
|
236
255
|
tuplet,
|
|
237
256
|
unwrapParse
|
|
238
|
-
} from "./chunk-
|
|
257
|
+
} from "./chunk-YYVXRV3V.js";
|
|
239
258
|
|
|
240
259
|
// src/model/interval.ts
|
|
241
260
|
var INVERSION_SUM = 9;
|
|
@@ -244,6 +263,14 @@ function simpleDegree(numberValue) {
|
|
|
244
263
|
const reduced = (numberValue - 1) % 7 + 1;
|
|
245
264
|
return reduced === 1 && numberValue > 1 ? OCTAVE_DEGREES : reduced;
|
|
246
265
|
}
|
|
266
|
+
function checkedDirection(descending) {
|
|
267
|
+
if (typeof descending !== "boolean") {
|
|
268
|
+
throw new InvalidInputError(
|
|
269
|
+
`interval.descending must be a boolean; received ${describeRejected(descending)}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
return descending;
|
|
273
|
+
}
|
|
247
274
|
function invertQuality(quality) {
|
|
248
275
|
if (quality === "P") {
|
|
249
276
|
return "P";
|
|
@@ -275,6 +302,12 @@ var Interval = class _Interval {
|
|
|
275
302
|
* @returns The interval from `a` to `b`.
|
|
276
303
|
*/
|
|
277
304
|
static between(a, b) {
|
|
305
|
+
if (typeof a !== "object" || a === null || typeof a.intervalTo !== "function") {
|
|
306
|
+
throw new InvalidInputError(`interval a must be a Note; received ${describeRejected(a)}`);
|
|
307
|
+
}
|
|
308
|
+
if (typeof b !== "object" || b === null || typeof b.data !== "object" || b.data === null) {
|
|
309
|
+
throw new InvalidInputError(`interval b must be a Note; received ${describeRejected(b)}`);
|
|
310
|
+
}
|
|
278
311
|
return a.intervalTo(b);
|
|
279
312
|
}
|
|
280
313
|
/**
|
|
@@ -305,7 +338,12 @@ var Interval = class _Interval {
|
|
|
305
338
|
*/
|
|
306
339
|
static fromData(data) {
|
|
307
340
|
const checked = toSpelledInterval(data);
|
|
308
|
-
return new _Interval(
|
|
341
|
+
return new _Interval(
|
|
342
|
+
checked.number,
|
|
343
|
+
checked.quality,
|
|
344
|
+
checked.semitones,
|
|
345
|
+
checkedDirection(checked.descending)
|
|
346
|
+
);
|
|
309
347
|
}
|
|
310
348
|
/**
|
|
311
349
|
* Rebuild an interval from its {@link Interval.toJSON} output.
|
|
@@ -430,12 +468,15 @@ var Interval = class _Interval {
|
|
|
430
468
|
/**
|
|
431
469
|
* Whether the interval is consonant.
|
|
432
470
|
*
|
|
471
|
+
* The spelling decides, not the span: a diminished fourth is a dissonance
|
|
472
|
+
* though it sounds like a major third.
|
|
473
|
+
*
|
|
433
474
|
* @param twoVoice When true, the perfect fourth counts as a dissonance,
|
|
434
475
|
* matching two-voice counterpoint.
|
|
435
476
|
* @returns True if the interval is consonant in that context.
|
|
436
477
|
*/
|
|
437
478
|
isConsonant(twoVoice = true) {
|
|
438
|
-
return
|
|
479
|
+
return classifySpelledInterval(this.toJSON(), twoVoice) !== 2 /* Dissonance */;
|
|
439
480
|
}
|
|
440
481
|
/**
|
|
441
482
|
* Whether another interval is spelled identically.
|
|
@@ -482,7 +523,6 @@ var Interval = class _Interval {
|
|
|
482
523
|
};
|
|
483
524
|
|
|
484
525
|
// src/model/shared.ts
|
|
485
|
-
var STATED_KEY_CONFIDENCE = 1;
|
|
486
526
|
function describeShape(value) {
|
|
487
527
|
if (value === null) {
|
|
488
528
|
return "null";
|
|
@@ -1065,6 +1105,22 @@ var Note = class _Note {
|
|
|
1065
1105
|
};
|
|
1066
1106
|
|
|
1067
1107
|
// src/model/timeline.ts
|
|
1108
|
+
var KEY_RELATIONS = [
|
|
1109
|
+
"same",
|
|
1110
|
+
"enharmonic",
|
|
1111
|
+
"relative",
|
|
1112
|
+
"parallel",
|
|
1113
|
+
"dominant",
|
|
1114
|
+
"subdominant",
|
|
1115
|
+
"relativeOfDominant",
|
|
1116
|
+
"relativeOfSubdominant"
|
|
1117
|
+
];
|
|
1118
|
+
function assertNumeral(numeral, name) {
|
|
1119
|
+
if (typeof numeral !== "string") {
|
|
1120
|
+
throw new InvalidInputError(`${name} must be a string; received ${describeRejected(numeral)}`);
|
|
1121
|
+
}
|
|
1122
|
+
return numeral;
|
|
1123
|
+
}
|
|
1068
1124
|
function copySegment(segment) {
|
|
1069
1125
|
assertDataObject(segment, "timeline segment");
|
|
1070
1126
|
return {
|
|
@@ -1087,14 +1143,14 @@ function copyKeyRegion(region) {
|
|
|
1087
1143
|
confidence: assertFiniteNumber(region.confidence, "key region confidence")
|
|
1088
1144
|
};
|
|
1089
1145
|
if (region.modulation !== void 0) {
|
|
1090
|
-
copy.modulation = region.modulation;
|
|
1146
|
+
copy.modulation = assertOneOf(region.modulation, KEY_RELATIONS, "key region modulation");
|
|
1091
1147
|
}
|
|
1092
1148
|
if (region.pivot !== void 0) {
|
|
1093
1149
|
assertDataObject(region.pivot, "key region pivot");
|
|
1094
1150
|
copy.pivot = {
|
|
1095
1151
|
chord: new Chord(region.pivot.chord).toJSON(),
|
|
1096
|
-
romanFrom: region.pivot.romanFrom,
|
|
1097
|
-
romanTo: region.pivot.romanTo
|
|
1152
|
+
romanFrom: assertNumeral(region.pivot.romanFrom, "key region pivot romanFrom"),
|
|
1153
|
+
romanTo: assertNumeral(region.pivot.romanTo, "key region pivot romanTo")
|
|
1098
1154
|
};
|
|
1099
1155
|
}
|
|
1100
1156
|
return copy;
|
|
@@ -1113,6 +1169,10 @@ function copyConfidence(confidence, segments) {
|
|
|
1113
1169
|
(value, index) => assertRange(value, 0, 1, `timeline segmentConfidence[${index}]`)
|
|
1114
1170
|
);
|
|
1115
1171
|
}
|
|
1172
|
+
function validateTimeline(segments, keys) {
|
|
1173
|
+
assertSpansInOrder(segments, "timeline segments");
|
|
1174
|
+
assertSpansInOrder(keys, "timeline keys");
|
|
1175
|
+
}
|
|
1116
1176
|
function clipSpan(span, fromBeat, toBeat) {
|
|
1117
1177
|
const startBeat = Math.max(span.startBeat, fromBeat);
|
|
1118
1178
|
const endBeat = Math.min(span.endBeat, toBeat);
|
|
@@ -1128,8 +1188,9 @@ var Timeline = class _Timeline {
|
|
|
1128
1188
|
*
|
|
1129
1189
|
* @param data The segments and the span they cover; copied, never retained.
|
|
1130
1190
|
* @throws If the span, a segment, or a key region carries a number it cannot
|
|
1131
|
-
* hold — a beat that is not finite, a mode mask that names no scale —
|
|
1132
|
-
* the
|
|
1191
|
+
* hold — a beat that is not finite, a mode mask that names no scale — if
|
|
1192
|
+
* the segments or the key regions are not disjoint, in beat order and of
|
|
1193
|
+
* positive length, or if the confidences do not run one per segment.
|
|
1133
1194
|
*/
|
|
1134
1195
|
constructor(data) {
|
|
1135
1196
|
assertDataObject(data, "timeline data");
|
|
@@ -1148,6 +1209,7 @@ var Timeline = class _Timeline {
|
|
|
1148
1209
|
this.#segmentConfidence = Object.freeze(
|
|
1149
1210
|
copyConfidence(data.segmentConfidence, this.#segments.length)
|
|
1150
1211
|
);
|
|
1212
|
+
validateTimeline(this.#segments, this.#keys);
|
|
1151
1213
|
}
|
|
1152
1214
|
/**
|
|
1153
1215
|
* Build a timeline from placed chords.
|
|
@@ -1155,7 +1217,8 @@ var Timeline = class _Timeline {
|
|
|
1155
1217
|
* @param spans The chords with their onsets, in any order.
|
|
1156
1218
|
* @param totalBeats Where the last chord stops sounding.
|
|
1157
1219
|
* @param key The key the chords are read in, when it is already known. It
|
|
1158
|
-
* becomes the one key region
|
|
1220
|
+
* becomes the one key region from the first chord to the end of the span;
|
|
1221
|
+
* without it, or without a chord for it to be in force over, the timeline
|
|
1159
1222
|
* carries no key, and the members that need one say so.
|
|
1160
1223
|
* @returns The timeline.
|
|
1161
1224
|
*/
|
|
@@ -1163,16 +1226,9 @@ var Timeline = class _Timeline {
|
|
|
1163
1226
|
const timeline = chordTimelineFromChords(spans, totalBeats);
|
|
1164
1227
|
const data = { segments: timeline.segments, totalBeats };
|
|
1165
1228
|
if (key !== void 0) {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
// so a chord placed in a pickup is covered by the key it sounds in.
|
|
1170
|
-
startBeat: timeline.segments[0]?.startBeat ?? 0,
|
|
1171
|
-
endBeat: totalBeats,
|
|
1172
|
-
key: keyIdentity(toKey(key)),
|
|
1173
|
-
confidence: STATED_KEY_CONFIDENCE
|
|
1174
|
-
}
|
|
1175
|
-
];
|
|
1229
|
+
const stated = keyIdentity(toKey(key));
|
|
1230
|
+
const first = timeline.segments[0];
|
|
1231
|
+
data.keys = first === void 0 ? [] : statedKeyRegions(stated, first.startBeat, totalBeats);
|
|
1176
1232
|
}
|
|
1177
1233
|
return new _Timeline(data);
|
|
1178
1234
|
}
|
|
@@ -1203,11 +1259,25 @@ var Timeline = class _Timeline {
|
|
|
1203
1259
|
* @throws If `beatsEach` is not a positive finite number of beats.
|
|
1204
1260
|
*/
|
|
1205
1261
|
static fromProgression(progression, beatsEach) {
|
|
1262
|
+
const read = progression;
|
|
1263
|
+
if (typeof read !== "object" || read === null || !Array.isArray(read.chords)) {
|
|
1264
|
+
throw new InvalidInputError(
|
|
1265
|
+
`timeline progression must be a Progression; received ${describeRejected(progression)}`
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1206
1268
|
assertRange(beatsEach, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "timeline beatsEach");
|
|
1207
|
-
const chords =
|
|
1208
|
-
const spans = chords.map((chord, index) =>
|
|
1269
|
+
const chords = read.chords;
|
|
1270
|
+
const spans = chords.map((chord, index) => {
|
|
1271
|
+
const data = chord?.data;
|
|
1272
|
+
if (typeof data !== "object" || data === null) {
|
|
1273
|
+
throw new InvalidInputError(
|
|
1274
|
+
`timeline progression chords[${index}] must be a Chord; received ${describeRejected(chord)}`
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
return spanFromChord(data, index * beatsEach);
|
|
1278
|
+
});
|
|
1209
1279
|
const totalBeats = chords.length * beatsEach;
|
|
1210
|
-
const key =
|
|
1280
|
+
const key = read.key;
|
|
1211
1281
|
return key === void 0 ? _Timeline.fromChords(spans, totalBeats) : _Timeline.fromChords(spans, totalBeats, key);
|
|
1212
1282
|
}
|
|
1213
1283
|
/** Rebuild a timeline from the plain data {@link Timeline.data} hands out. */
|
|
@@ -1599,7 +1669,14 @@ var Progression = class _Progression {
|
|
|
1599
1669
|
*/
|
|
1600
1670
|
static #attachKey(chords, key) {
|
|
1601
1671
|
const given = assertDataObjects(chords, "progression chords");
|
|
1602
|
-
return
|
|
1672
|
+
return given.map((chord, index) => {
|
|
1673
|
+
if (typeof chord.toJSON !== "function") {
|
|
1674
|
+
throw new InvalidInputError(
|
|
1675
|
+
`progression chords[${index}] must be a Chord; received ${describeRejected(chord)}`
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
return new Chord(chord.toJSON(), key ?? chord.key);
|
|
1679
|
+
});
|
|
1603
1680
|
}
|
|
1604
1681
|
/**
|
|
1605
1682
|
* Start from a chord sequence, matching the `of` factory on the other
|
|
@@ -1647,19 +1724,34 @@ var Progression = class _Progression {
|
|
|
1647
1724
|
return new _Progression(chords, key);
|
|
1648
1725
|
}
|
|
1649
1726
|
/**
|
|
1650
|
-
* Rebuild a progression from its {@link Progression.toJSON} output
|
|
1727
|
+
* Rebuild a progression from its {@link Progression.toJSON} output, with the
|
|
1728
|
+
* carried key or, without one, each chord's own key.
|
|
1651
1729
|
*
|
|
1652
|
-
* @param data The serialized chords and
|
|
1730
|
+
* @param data The serialized chords and keys.
|
|
1653
1731
|
* @returns The progression.
|
|
1732
|
+
* @throws If `chordKeys` does not hold one entry per chord.
|
|
1654
1733
|
*/
|
|
1655
1734
|
static fromJSON(data) {
|
|
1656
1735
|
assertDataObject(data, "progression data");
|
|
1657
1736
|
const key = data.key === void 0 ? void 0 : Key.fromJSON(data.key);
|
|
1658
|
-
const
|
|
1659
|
-
|
|
1660
|
-
)
|
|
1737
|
+
const given = assertDataArray(data.chords, "progression chords");
|
|
1738
|
+
const chordKeys = key === void 0 && data.chordKeys !== void 0 ? _Progression.#readChordKeys(data.chordKeys, given.length) : void 0;
|
|
1739
|
+
const chords = given.map((chord, index) => {
|
|
1740
|
+
const own = chordKeys?.[index];
|
|
1741
|
+
return own === void 0 ? Chord.fromJSON(chord) : Chord.fromJSON(chord).withKey(own);
|
|
1742
|
+
});
|
|
1661
1743
|
return key === void 0 ? new _Progression(chords) : new _Progression(chords, key);
|
|
1662
1744
|
}
|
|
1745
|
+
/** Read the per-chord keys of {@link ProgressionData}, one entry per chord. */
|
|
1746
|
+
static #readChordKeys(value, count) {
|
|
1747
|
+
const entries = assertDataArray(value, "progression chord keys");
|
|
1748
|
+
if (entries.length !== count) {
|
|
1749
|
+
throw new InvalidInputError(
|
|
1750
|
+
`progression chord keys must hold one entry per chord (${count}); received ${entries.length}`
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
return entries.map((entry) => entry === null ? void 0 : Key.fromJSON(entry));
|
|
1754
|
+
}
|
|
1663
1755
|
/**
|
|
1664
1756
|
* Wrap plain progression data, matching the `fromData` factory on the other
|
|
1665
1757
|
* classes.
|
|
@@ -1870,10 +1962,8 @@ var Progression = class _Progression {
|
|
|
1870
1962
|
*/
|
|
1871
1963
|
voice(opts) {
|
|
1872
1964
|
const key = opts?.key ?? this.#key;
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
key === void 0 ? opts : { ...opts, key }
|
|
1876
|
-
);
|
|
1965
|
+
const views = opts?.key === void 0 ? this.#chords.map((chord) => chord.data) : this.#viewsIn(toKey(opts.key));
|
|
1966
|
+
return voiceProgression(views, key === void 0 ? opts : { ...opts, key });
|
|
1877
1967
|
}
|
|
1878
1968
|
/**
|
|
1879
1969
|
* The Roman numeral of each chord in a key.
|
|
@@ -1934,12 +2024,13 @@ var Progression = class _Progression {
|
|
|
1934
2024
|
const resolved = this.#resolveKey(key);
|
|
1935
2025
|
const chords = this.#chords.map((chord) => chord.analyze(resolved, opts));
|
|
1936
2026
|
const { voicing, ...cadenceOpts } = opts ?? {};
|
|
1937
|
-
const
|
|
1938
|
-
const
|
|
1939
|
-
const
|
|
1940
|
-
const
|
|
2027
|
+
const views = this.#viewsIn(resolved);
|
|
2028
|
+
const from = views[views.length - 2];
|
|
2029
|
+
const to = views[views.length - 1];
|
|
2030
|
+
const approach = views[views.length - 3];
|
|
2031
|
+
const cadence = from !== void 0 && to !== void 0 ? detectCadence(from, to, resolved, {
|
|
1941
2032
|
...cadenceOpts,
|
|
1942
|
-
...approach === void 0 ? {} : { approach
|
|
2033
|
+
...approach === void 0 ? {} : { approach },
|
|
1943
2034
|
...voicing === void 0 ? {} : { voicing: this.#closingVoicing(voicing) }
|
|
1944
2035
|
}) : null;
|
|
1945
2036
|
return { chords, cadence };
|
|
@@ -2004,17 +2095,18 @@ var Progression = class _Progression {
|
|
|
2004
2095
|
*/
|
|
2005
2096
|
cadences(key, opts) {
|
|
2006
2097
|
const resolved = this.#resolveKey(key);
|
|
2098
|
+
const views = this.#viewsIn(resolved);
|
|
2007
2099
|
const found = [];
|
|
2008
|
-
for (let index = 1; index <
|
|
2009
|
-
const from =
|
|
2010
|
-
const to =
|
|
2100
|
+
for (let index = 1; index < views.length; index += 1) {
|
|
2101
|
+
const from = views[index - 1];
|
|
2102
|
+
const to = views[index];
|
|
2011
2103
|
if (from === void 0 || to === void 0) {
|
|
2012
2104
|
continue;
|
|
2013
2105
|
}
|
|
2014
|
-
const approach = index >= 2 ?
|
|
2106
|
+
const approach = index >= 2 ? views[index - 2] : opts?.approach;
|
|
2015
2107
|
const pair = opts?.voicing;
|
|
2016
2108
|
found.push(
|
|
2017
|
-
detectCadence(from
|
|
2109
|
+
detectCadence(from, to, resolved, {
|
|
2018
2110
|
...opts?.alternatives === void 0 ? {} : { alternatives: opts.alternatives },
|
|
2019
2111
|
...approach === void 0 ? {} : { approach },
|
|
2020
2112
|
...pair === void 0 ? {} : { voicing: [pair[index - 1] ?? [], pair[index] ?? []] }
|
|
@@ -2175,16 +2267,26 @@ var Progression = class _Progression {
|
|
|
2175
2267
|
/**
|
|
2176
2268
|
* The plain progression data, for JSON serialization.
|
|
2177
2269
|
*
|
|
2178
|
-
* Private class fields do not serialize, so this preserves
|
|
2179
|
-
*
|
|
2270
|
+
* Private class fields do not serialize, so this preserves the chord data,
|
|
2271
|
+
* any carried key, and — for a progression with no key of its own — each
|
|
2272
|
+
* chord's own key in `JSON.stringify(progression)`.
|
|
2180
2273
|
*
|
|
2181
|
-
* @returns The chord data sequence
|
|
2274
|
+
* @returns The chord data sequence, the carried key, if any, and the
|
|
2275
|
+
* per-chord keys when they are what spells the chords.
|
|
2182
2276
|
*/
|
|
2183
2277
|
toJSON() {
|
|
2184
|
-
|
|
2278
|
+
const out = {
|
|
2185
2279
|
chords: this.#chords.map((chord) => chord.toJSON()),
|
|
2186
2280
|
key: this.#key?.toJSON()
|
|
2187
2281
|
};
|
|
2282
|
+
if (this.#key === void 0 && this.#chords.some((chord) => chord.key !== void 0)) {
|
|
2283
|
+
out.chordKeys = this.#chords.map((chord) => chord.key?.toJSON() ?? null);
|
|
2284
|
+
}
|
|
2285
|
+
return out;
|
|
2286
|
+
}
|
|
2287
|
+
/** The members read in `key`: spellings the caller gave, the rest derived from it. */
|
|
2288
|
+
#viewsIn(key) {
|
|
2289
|
+
return this.#chords.map((chord) => chord.withKey(key).data);
|
|
2188
2290
|
}
|
|
2189
2291
|
/**
|
|
2190
2292
|
* Resolve the key for an analysis method: explicit first, then carried.
|
|
@@ -2617,7 +2719,8 @@ var Chord = class _Chord {
|
|
|
2617
2719
|
* @throws If no key is given and none is carried.
|
|
2618
2720
|
*/
|
|
2619
2721
|
roman(key, opts) {
|
|
2620
|
-
|
|
2722
|
+
const resolved = this.#resolveKey(key);
|
|
2723
|
+
return chordToRoman(this.#dataWithKey(resolved), resolved, opts);
|
|
2621
2724
|
}
|
|
2622
2725
|
/**
|
|
2623
2726
|
* The chord's Roman numeral together with the reasoning behind it: the degree
|
|
@@ -2645,7 +2748,8 @@ var Chord = class _Chord {
|
|
|
2645
2748
|
* ```
|
|
2646
2749
|
*/
|
|
2647
2750
|
explain(key, opts) {
|
|
2648
|
-
|
|
2751
|
+
const resolved = this.#resolveKey(key);
|
|
2752
|
+
return explainRoman(this.#dataWithKey(resolved), resolved, opts);
|
|
2649
2753
|
}
|
|
2650
2754
|
/**
|
|
2651
2755
|
* The chord's harmonic function (tonic / subdominant / dominant) in a key.
|
|
@@ -2656,7 +2760,8 @@ var Chord = class _Chord {
|
|
|
2656
2760
|
* @throws If no key is given and none is carried.
|
|
2657
2761
|
*/
|
|
2658
2762
|
function(key) {
|
|
2659
|
-
|
|
2763
|
+
const resolved = this.#resolveKey(key);
|
|
2764
|
+
return functionOf(this.#dataWithKey(resolved), resolved);
|
|
2660
2765
|
}
|
|
2661
2766
|
/**
|
|
2662
2767
|
* Full functional analysis: function, borrowing, Roman numeral, and the
|
|
@@ -2676,7 +2781,8 @@ var Chord = class _Chord {
|
|
|
2676
2781
|
* ```
|
|
2677
2782
|
*/
|
|
2678
2783
|
analyze(key, opts) {
|
|
2679
|
-
|
|
2784
|
+
const resolved = this.#resolveKey(key);
|
|
2785
|
+
return analyzeChord(this.#dataWithKey(resolved), resolved, opts);
|
|
2680
2786
|
}
|
|
2681
2787
|
/**
|
|
2682
2788
|
* Whether the chord is borrowed from the parallel mode (modal interchange).
|
|
@@ -2687,7 +2793,8 @@ var Chord = class _Chord {
|
|
|
2687
2793
|
* @throws If no key is given and none is carried.
|
|
2688
2794
|
*/
|
|
2689
2795
|
isBorrowed(key) {
|
|
2690
|
-
|
|
2796
|
+
const resolved = this.#resolveKey(key);
|
|
2797
|
+
return isBorrowedChord(this.#dataWithKey(resolved), resolved);
|
|
2691
2798
|
}
|
|
2692
2799
|
/**
|
|
2693
2800
|
* The origin of a non-diatonic chord (parallel mode or Neapolitan), or null.
|
|
@@ -2698,7 +2805,8 @@ var Chord = class _Chord {
|
|
|
2698
2805
|
* @throws If no key is given and none is carried.
|
|
2699
2806
|
*/
|
|
2700
2807
|
borrowedSource(key) {
|
|
2701
|
-
|
|
2808
|
+
const resolved = this.#resolveKey(key);
|
|
2809
|
+
return borrowedSource(this.#dataWithKey(resolved), resolved);
|
|
2702
2810
|
}
|
|
2703
2811
|
/**
|
|
2704
2812
|
* The chord written as the figures a bass would carry under it: `6`, `64`,
|
|
@@ -2723,7 +2831,8 @@ var Chord = class _Chord {
|
|
|
2723
2831
|
* ```
|
|
2724
2832
|
*/
|
|
2725
2833
|
figuredBass(key) {
|
|
2726
|
-
|
|
2834
|
+
const resolved = this.#resolveKey(key);
|
|
2835
|
+
return figuredBassOf(this.#dataWithKey(resolved), resolved);
|
|
2727
2836
|
}
|
|
2728
2837
|
/**
|
|
2729
2838
|
* The chords that can stand in for this one in a key: its tritone substitute,
|
|
@@ -2750,7 +2859,8 @@ var Chord = class _Chord {
|
|
|
2750
2859
|
* ```
|
|
2751
2860
|
*/
|
|
2752
2861
|
substitutions(key, opts) {
|
|
2753
|
-
|
|
2862
|
+
const resolved = this.#resolveKey(key);
|
|
2863
|
+
return substituteChord(this.#dataWithKey(resolved), resolved, opts);
|
|
2754
2864
|
}
|
|
2755
2865
|
/**
|
|
2756
2866
|
* The modal-interchange palette of the chord's key: the parallel mode's
|
|
@@ -2804,7 +2914,8 @@ var Chord = class _Chord {
|
|
|
2804
2914
|
*/
|
|
2805
2915
|
voice(opts) {
|
|
2806
2916
|
const key = opts?.key ?? this.#key;
|
|
2807
|
-
|
|
2917
|
+
const data = opts?.key === void 0 ? this.#data : this.#dataWithKey(toKey(opts.key));
|
|
2918
|
+
return voiceChord(data, key === void 0 ? opts : { ...opts, key });
|
|
2808
2919
|
}
|
|
2809
2920
|
/**
|
|
2810
2921
|
* Realize the chord as a single styled voicing (`close`, `drop2`, `drop3`,
|
|
@@ -2827,7 +2938,7 @@ var Chord = class _Chord {
|
|
|
2827
2938
|
*/
|
|
2828
2939
|
negativeHarmony(key) {
|
|
2829
2940
|
const resolved = this.#resolveKey(key);
|
|
2830
|
-
return new _Chord(negativeHarmonyMirror(this.#
|
|
2941
|
+
return new _Chord(negativeHarmonyMirror(this.#dataWithKey(resolved), resolved), resolved);
|
|
2831
2942
|
}
|
|
2832
2943
|
/**
|
|
2833
2944
|
* The V7 that tonicizes this chord: a dominant seventh a perfect fifth above
|
|
@@ -3217,17 +3328,18 @@ var Key = class _Key {
|
|
|
3217
3328
|
`tonic must be the class API's Note; received ${describeTonic(tonic)}; wrap plain note data with Note.fromData, or omit the tonic to have one chosen`
|
|
3218
3329
|
);
|
|
3219
3330
|
}
|
|
3331
|
+
const note = tonic instanceof Note ? tonic : new Note(tonic.data);
|
|
3220
3332
|
const rootPc = pitchClassOf(scale.rootPc);
|
|
3221
|
-
if (
|
|
3333
|
+
if (note.pitchClass !== rootPc) {
|
|
3222
3334
|
throw new InvalidInputError(
|
|
3223
|
-
`tonic ${
|
|
3335
|
+
`tonic ${note.name} does not match the scale root pitch class ${rootPc}; pass a tonic that spells the scale root, or omit it to have one chosen`
|
|
3224
3336
|
);
|
|
3225
3337
|
}
|
|
3226
3338
|
if (variant !== void 0) {
|
|
3227
3339
|
assertKeyVariant(variant, scale.modeMask12);
|
|
3228
3340
|
}
|
|
3229
3341
|
this.#scale = { rootPc, modeMask12: scale.modeMask12 };
|
|
3230
|
-
this.#tonic =
|
|
3342
|
+
this.#tonic = note;
|
|
3231
3343
|
this.#variant = variant ?? variantOfMask(scale.modeMask12);
|
|
3232
3344
|
}
|
|
3233
3345
|
/** Preserve detection metadata while replacing its plain key with this API's Key. */
|
|
@@ -3533,7 +3645,7 @@ var Key = class _Key {
|
|
|
3533
3645
|
get data() {
|
|
3534
3646
|
return this.toJSON();
|
|
3535
3647
|
}
|
|
3536
|
-
/** The
|
|
3648
|
+
/** The scale form this key stands in: natural, harmonic or melodic, read from the mask unless named. */
|
|
3537
3649
|
get variant() {
|
|
3538
3650
|
return this.#variant;
|
|
3539
3651
|
}
|
|
@@ -3778,7 +3890,7 @@ var Key = class _Key {
|
|
|
3778
3890
|
*/
|
|
3779
3891
|
pivotsTo(other) {
|
|
3780
3892
|
assertKeyArgument(other, "key");
|
|
3781
|
-
return pivotChords(this
|
|
3893
|
+
return pivotChords(this, resolveKey(other)).map(({ chord, romanFrom, romanTo }) => ({
|
|
3782
3894
|
chord: new Chord(chord, this),
|
|
3783
3895
|
romanFrom,
|
|
3784
3896
|
romanTo
|
|
@@ -3887,7 +3999,7 @@ var Key = class _Key {
|
|
|
3887
3999
|
if (this.pitchClasses().length !== HEPTATONIC_DEGREES) {
|
|
3888
4000
|
return this.isMinor ? "minor" : "major";
|
|
3889
4001
|
}
|
|
3890
|
-
const { quality } = diatonicTriad(n, this
|
|
4002
|
+
const { quality } = diatonicTriad(n, this);
|
|
3891
4003
|
return quality === "min" || quality === "dim" ? "minor" : "major";
|
|
3892
4004
|
}
|
|
3893
4005
|
/**
|
|
@@ -3896,7 +4008,7 @@ var Key = class _Key {
|
|
|
3896
4008
|
*
|
|
3897
4009
|
* Unlike {@link Key.transpose}, which picks a letter from the semitone count,
|
|
3898
4010
|
* the interval's diatonic number decides the tonic's letter: G# minor down an
|
|
3899
|
-
* augmented fourth is D minor, while down a diminished fifth it is
|
|
4011
|
+
* augmented fourth is D minor, while down a diminished fifth it is C## minor.
|
|
3900
4012
|
* The mode mask is unchanged, so the scale keeps its shape.
|
|
3901
4013
|
*
|
|
3902
4014
|
* @param interval The interval to apply, as a name (`'A4'`, `'-A4'`), plain
|
|
@@ -4109,6 +4221,15 @@ var Key = class _Key {
|
|
|
4109
4221
|
const anchor = tonic === void 0 ? this.#tonic.data : toNoteData(tonic);
|
|
4110
4222
|
return spellPitchClasses([...pcs], anchor, this.#scale).map((note) => new Note(note));
|
|
4111
4223
|
}
|
|
4224
|
+
/**
|
|
4225
|
+
* A degree chord without the spelling its builder attached.
|
|
4226
|
+
*
|
|
4227
|
+
* The chord carries this key, which spells its root on read, so a chord
|
|
4228
|
+
* re-keyed later follows its new key instead of keeping these letters.
|
|
4229
|
+
*/
|
|
4230
|
+
#withoutSpelling(chord) {
|
|
4231
|
+
return makeChord(chord.rootPc, chord.quality);
|
|
4232
|
+
}
|
|
4112
4233
|
/**
|
|
4113
4234
|
* Build a chord on a scale degree, carrying this key as context.
|
|
4114
4235
|
*
|
|
@@ -4126,8 +4247,8 @@ var Key = class _Key {
|
|
|
4126
4247
|
* an explicit `quality` for those.
|
|
4127
4248
|
*/
|
|
4128
4249
|
chord(degree, quality) {
|
|
4129
|
-
const data = quality === void 0 ? diatonicTriad(degree, this
|
|
4130
|
-
return new Chord(data, this);
|
|
4250
|
+
const data = quality === void 0 ? diatonicTriad(degree, this) : chordFromDegree(degree, quality, this);
|
|
4251
|
+
return new Chord(this.#withoutSpelling(data), this);
|
|
4131
4252
|
}
|
|
4132
4253
|
/**
|
|
4133
4254
|
* The diatonic triad on a scale degree, carrying this key as context.
|
|
@@ -4139,7 +4260,7 @@ var Key = class _Key {
|
|
|
4139
4260
|
* degrees. Use {@link Key.chord} with an explicit quality instead.
|
|
4140
4261
|
*/
|
|
4141
4262
|
diatonicTriad(degree) {
|
|
4142
|
-
return new Chord(diatonicTriad(degree, this
|
|
4263
|
+
return new Chord(this.#withoutSpelling(diatonicTriad(degree, this)), this);
|
|
4143
4264
|
}
|
|
4144
4265
|
/**
|
|
4145
4266
|
* The diatonic seventh chord on a scale degree, carrying this key as context.
|
|
@@ -4151,7 +4272,7 @@ var Key = class _Key {
|
|
|
4151
4272
|
* degrees. Use {@link Key.chord} with an explicit quality instead.
|
|
4152
4273
|
*/
|
|
4153
4274
|
diatonicSeventh(degree) {
|
|
4154
|
-
return new Chord(diatonicSeventh(degree, this
|
|
4275
|
+
return new Chord(this.#withoutSpelling(diatonicSeventh(degree, this)), this);
|
|
4155
4276
|
}
|
|
4156
4277
|
/**
|
|
4157
4278
|
* Build the chord denoted by a Roman numeral in this key (including applied
|
|
@@ -4261,12 +4382,11 @@ var Key = class _Key {
|
|
|
4261
4382
|
*
|
|
4262
4383
|
* Private class fields do not serialize, so an explicit `toJSON` keeps
|
|
4263
4384
|
* `JSON.stringify(key)` from collapsing to `{}`. The result pairs the
|
|
4264
|
-
* `KeyScale` with the spelled tonic and
|
|
4265
|
-
*
|
|
4266
|
-
*
|
|
4385
|
+
* `KeyScale` with the spelled tonic and the scale form — everything
|
|
4386
|
+
* {@link Key.fromJSON} needs to rebuild a key that reads and prints as this
|
|
4387
|
+
* one.
|
|
4267
4388
|
*
|
|
4268
|
-
* @returns The key/scale, its spelled tonic, and its scale form
|
|
4269
|
-
* one.
|
|
4389
|
+
* @returns The key/scale, its spelled tonic, and its scale form.
|
|
4270
4390
|
*/
|
|
4271
4391
|
toJSON() {
|
|
4272
4392
|
return { scale: this.scale, tonic: this.#tonic.data, variant: this.#variant };
|
|
@@ -4377,21 +4497,9 @@ function tempoFrom(tempo) {
|
|
|
4377
4497
|
}
|
|
4378
4498
|
return typeof tempo === "number" ? [{ startBeat: 0, bpm: tempo }] : tempo;
|
|
4379
4499
|
}
|
|
4380
|
-
function
|
|
4500
|
+
function scoreKeyRegions(key, notes, totalBeats) {
|
|
4381
4501
|
const first = notes[0];
|
|
4382
|
-
|
|
4383
|
-
return [];
|
|
4384
|
-
}
|
|
4385
|
-
return [
|
|
4386
|
-
{
|
|
4387
|
-
// The region starts where the music does, as an inferred one does, so a
|
|
4388
|
-
// pickup is covered by the key it sounds in.
|
|
4389
|
-
startBeat: first.startBeat,
|
|
4390
|
-
endBeat: totalBeats,
|
|
4391
|
-
key: keyIdentity(key),
|
|
4392
|
-
confidence: STATED_KEY_CONFIDENCE
|
|
4393
|
-
}
|
|
4394
|
-
];
|
|
4502
|
+
return first === void 0 ? [] : statedKeyRegions(keyIdentity(key), first.startBeat, totalBeats);
|
|
4395
4503
|
}
|
|
4396
4504
|
function scoreData(notes, opts) {
|
|
4397
4505
|
const data = {
|
|
@@ -4411,6 +4519,8 @@ var Score = class _Score {
|
|
|
4411
4519
|
#chords;
|
|
4412
4520
|
/** The key regions, made on the first member that needs them. */
|
|
4413
4521
|
#regions;
|
|
4522
|
+
/** The reference profile asked for with no options, made on first read. */
|
|
4523
|
+
#profile;
|
|
4414
4524
|
/**
|
|
4415
4525
|
* Wrap plain score data.
|
|
4416
4526
|
*
|
|
@@ -4644,43 +4754,42 @@ var Score = class _Score {
|
|
|
4644
4754
|
/**
|
|
4645
4755
|
* The notes pushed off the grid the way a player would.
|
|
4646
4756
|
*
|
|
4647
|
-
* The metric accents are read
|
|
4648
|
-
* {@link
|
|
4649
|
-
* to read a later stretch of a score that changes meter.
|
|
4757
|
+
* The metric accents are read against the score's meter map, exactly as
|
|
4758
|
+
* {@link humanize} reads that map; pass `ts` to read against another meter.
|
|
4650
4759
|
*
|
|
4651
4760
|
* @param opts Jitter amounts, accent depth and the seed; see
|
|
4652
4761
|
* {@link HumanizeOptions}.
|
|
4653
4762
|
* @returns The humanized score.
|
|
4654
4763
|
*/
|
|
4655
4764
|
humanize(opts) {
|
|
4656
|
-
return this.#withNotes(humanize(this.#data.notes, { ts: this.
|
|
4765
|
+
return this.#withNotes(humanize(this.#data.notes, { ts: this.#data.meters, ...opts }));
|
|
4657
4766
|
}
|
|
4658
4767
|
/**
|
|
4659
4768
|
* The notes with ornaments added.
|
|
4660
4769
|
*
|
|
4661
|
-
* Strong positions are read
|
|
4662
|
-
* {@link
|
|
4770
|
+
* Strong positions are read against the score's meter map, exactly as
|
|
4771
|
+
* {@link ornament} reads that map.
|
|
4663
4772
|
*
|
|
4664
4773
|
* @param opts The ornament style, how much is decorated, and the seed; see
|
|
4665
4774
|
* {@link OrnamentOptions}.
|
|
4666
4775
|
* @returns The ornamented score.
|
|
4667
4776
|
*/
|
|
4668
4777
|
ornament(opts) {
|
|
4669
|
-
return this.#withNotes(ornament(this.#data.notes, { ts: this.
|
|
4778
|
+
return this.#withNotes(ornament(this.#data.notes, { ts: this.#data.meters, ...opts }));
|
|
4670
4779
|
}
|
|
4671
4780
|
/**
|
|
4672
4781
|
* The notes placed on a groove template's slots.
|
|
4673
4782
|
*
|
|
4674
|
-
* The template's per-bar grid is laid out against the score's
|
|
4675
|
-
*
|
|
4676
|
-
* under
|
|
4783
|
+
* The template's per-bar grid is laid out against the score's meter map,
|
|
4784
|
+
* exactly as {@link applyGrooveTemplate} reads that map; a template extracted
|
|
4785
|
+
* under another meter is refused rather than drifting.
|
|
4677
4786
|
*
|
|
4678
4787
|
* @param template The groove template, from `extractGrooveTemplate`.
|
|
4679
4788
|
* @returns The score with the template's feel imposed.
|
|
4680
4789
|
* @throws If the template was extracted under another meter.
|
|
4681
4790
|
*/
|
|
4682
4791
|
groove(template) {
|
|
4683
|
-
return this.#withNotes(applyGrooveTemplate(this.#data.notes, template, this.
|
|
4792
|
+
return this.#withNotes(applyGrooveTemplate(this.#data.notes, template, this.#data.meters));
|
|
4684
4793
|
}
|
|
4685
4794
|
/**
|
|
4686
4795
|
* The feel of the score's own timing, as a template another score can take on.
|
|
@@ -4689,8 +4798,8 @@ var Score = class _Score {
|
|
|
4689
4798
|
* slot by slot for how far ahead of or behind the grid it sits and how hard
|
|
4690
4799
|
* it is struck, and the reading is handed back as a template. Only the
|
|
4691
4800
|
* subdivision is named, because the score already holds the meter the grid is
|
|
4692
|
-
* laid out against — its
|
|
4693
|
-
*
|
|
4801
|
+
* laid out against — its meter map, read exactly as
|
|
4802
|
+
* {@link extractGrooveTemplate} reads that map.
|
|
4694
4803
|
*
|
|
4695
4804
|
* @param subdivision Grid steps per quarter-note beat; a sixteenth-note grid
|
|
4696
4805
|
* by default, which is where swing, push and drag are audible.
|
|
@@ -4704,7 +4813,7 @@ var Score = class _Score {
|
|
|
4704
4813
|
* ```
|
|
4705
4814
|
*/
|
|
4706
4815
|
grooveTemplate(subdivision) {
|
|
4707
|
-
return extractGrooveTemplate(this.#data.notes, this.
|
|
4816
|
+
return extractGrooveTemplate(this.#data.notes, this.#data.meters, subdivision);
|
|
4708
4817
|
}
|
|
4709
4818
|
/**
|
|
4710
4819
|
* The harmony the notes spell out, in time.
|
|
@@ -4758,7 +4867,8 @@ var Score = class _Score {
|
|
|
4758
4867
|
keys(opts) {
|
|
4759
4868
|
if (opts !== void 0) {
|
|
4760
4869
|
if (this.#key !== void 0) {
|
|
4761
|
-
|
|
4870
|
+
const totalBeats = opts.totalBeats === void 0 || opts.totalBeats === null ? this.totalBeats : assertRange(opts.totalBeats, 0, Number.MAX_SAFE_INTEGER, "key timeline totalBeats");
|
|
4871
|
+
return scoreKeyRegions(this.#key, this.#data.notes, totalBeats);
|
|
4762
4872
|
}
|
|
4763
4873
|
return keyTimelineFromNotes(this.#data.notes, { meters: this.#data.meters, ...opts });
|
|
4764
4874
|
}
|
|
@@ -4842,10 +4952,16 @@ var Score = class _Score {
|
|
|
4842
4952
|
* @returns The grouping, its downbeats, a confidence, and a rationale.
|
|
4843
4953
|
*/
|
|
4844
4954
|
hypermeter(opts) {
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
}
|
|
4955
|
+
const asked = { ...opts };
|
|
4956
|
+
if ((asked.cadenceBeats ?? null) !== null) {
|
|
4957
|
+
return hypermeter(this.#data.notes, this.#data.meters, asked);
|
|
4958
|
+
}
|
|
4959
|
+
return hypermeterOver(
|
|
4960
|
+
this.#data.notes,
|
|
4961
|
+
this.#data.meters,
|
|
4962
|
+
asked,
|
|
4963
|
+
() => this.#cadenceSpans(asked.budget)
|
|
4964
|
+
);
|
|
4849
4965
|
}
|
|
4850
4966
|
/**
|
|
4851
4967
|
* The motifs the melody repeats.
|
|
@@ -4860,8 +4976,9 @@ var Score = class _Score {
|
|
|
4860
4976
|
*
|
|
4861
4977
|
* A melody is one line, so a score holding several is read as its top voice:
|
|
4862
4978
|
* notes struck together are one event and the highest of them stands for it.
|
|
4863
|
-
* A
|
|
4864
|
-
*
|
|
4979
|
+
* A block-chord part read whole therefore answers for its top voice rather
|
|
4980
|
+
* than for a line assembled from the insides of its chords; a lower note
|
|
4981
|
+
* struck while a higher one still sounds stays in the line, though. Split the
|
|
4865
4982
|
* score into lines first to have each of them answered for.
|
|
4866
4983
|
*
|
|
4867
4984
|
* @param opts Cell-length bounds and the recurrence threshold; see
|
|
@@ -4898,6 +5015,143 @@ var Score = class _Score {
|
|
|
4898
5015
|
contour() {
|
|
4899
5016
|
return melodicContour(this.#data.notes);
|
|
4900
5017
|
}
|
|
5018
|
+
/**
|
|
5019
|
+
* The score's structural fingerprint: form, phrase shape, harmonic function
|
|
5020
|
+
* and rhythm, and how its motifs derive from one another.
|
|
5021
|
+
*
|
|
5022
|
+
* The harmony is the score's own kept chord analysis, read once whether this
|
|
5023
|
+
* is asked before or after {@link Score.timeline} — unless `opts.timeline`
|
|
5024
|
+
* gives the harmony directly, in which case the reading runs fresh through
|
|
5025
|
+
* {@link analyzeReference} rather than the harmony this score already holds.
|
|
5026
|
+
* The meter, the key and the span are the score's own and are not on offer
|
|
5027
|
+
* here: a caller wanting another meter reads through {@link
|
|
5028
|
+
* Score.withMeters}, and one wanting another key or span calls {@link
|
|
5029
|
+
* analyzeReference} directly.
|
|
5030
|
+
*
|
|
5031
|
+
* Asked with no options, the answer is kept and a copy of it handed back, as
|
|
5032
|
+
* {@link Score.timeline} keeps its own reading; asked with options, the
|
|
5033
|
+
* answer is read afresh and not kept.
|
|
5034
|
+
*
|
|
5035
|
+
* @param opts The harmony, the melody, and the budget; see {@link
|
|
5036
|
+
* ReferenceProfileOptions}. Meter, key and span are the score's own.
|
|
5037
|
+
* @returns The profile.
|
|
5038
|
+
* @example
|
|
5039
|
+
* ```ts
|
|
5040
|
+
* import { Score } from '@libraz/libcantus';
|
|
5041
|
+
* const triad = (pitches: number[], startBeat: number) =>
|
|
5042
|
+
* pitches.map((pitch) => ({ pitch, startBeat, durationBeat: 4 }));
|
|
5043
|
+
* const score = Score.of(
|
|
5044
|
+
* [
|
|
5045
|
+
* ...triad([60, 64, 67], 0),
|
|
5046
|
+
* ...triad([65, 69, 72], 4),
|
|
5047
|
+
* ...triad([67, 71, 74], 8),
|
|
5048
|
+
* ...triad([60, 64, 67], 12),
|
|
5049
|
+
* ],
|
|
5050
|
+
* { key: 'C major' },
|
|
5051
|
+
* );
|
|
5052
|
+
* score.reference().harmony.chords.map((chord) => chord.roman); // ['I', 'IV', 'V', 'I']
|
|
5053
|
+
* ```
|
|
5054
|
+
*/
|
|
5055
|
+
reference(opts) {
|
|
5056
|
+
if (opts === void 0 && this.#profile !== void 0) {
|
|
5057
|
+
return copyPlain(this.#profile, "reference profile");
|
|
5058
|
+
}
|
|
5059
|
+
const keyed = this.#key === void 0 ? {} : { key: keyIdentity(this.#key) };
|
|
5060
|
+
const asked = {
|
|
5061
|
+
meters: this.#data.meters,
|
|
5062
|
+
totalBeats: this.totalBeats,
|
|
5063
|
+
...keyed,
|
|
5064
|
+
...opts
|
|
5065
|
+
};
|
|
5066
|
+
const profile = opts?.timeline === void 0 ? referenceFromReadings(this.#data.notes, this.#chordAnalysis(), asked) : analyzeReference(this.#data.notes, asked);
|
|
5067
|
+
if (opts === void 0) {
|
|
5068
|
+
this.#profile = profile;
|
|
5069
|
+
return copyPlain(profile, "reference profile");
|
|
5070
|
+
}
|
|
5071
|
+
return profile;
|
|
5072
|
+
}
|
|
5073
|
+
/**
|
|
5074
|
+
* The onset placement of the score's melodic line.
|
|
5075
|
+
*
|
|
5076
|
+
* A melody is one line, so a score holding several is read as its top voice,
|
|
5077
|
+
* as {@link Score.motifs} and {@link Score.contour} are.
|
|
5078
|
+
*
|
|
5079
|
+
* @param opts The span's end and the budget; see {@link
|
|
5080
|
+
* RhythmAnalysisOptions}. The meter is the score's own.
|
|
5081
|
+
* @returns The rhythm reading.
|
|
5082
|
+
* @example
|
|
5083
|
+
* ```ts
|
|
5084
|
+
* import { Score } from '@libraz/libcantus';
|
|
5085
|
+
* const score = Score.of([
|
|
5086
|
+
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
5087
|
+
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
5088
|
+
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
5089
|
+
* { pitch: 65, startBeat: 3, durationBeat: 1 },
|
|
5090
|
+
* ]);
|
|
5091
|
+
* score.rhythm().onsetDensity; // 4
|
|
5092
|
+
* ```
|
|
5093
|
+
*/
|
|
5094
|
+
rhythm(opts) {
|
|
5095
|
+
return analyzeRhythm(this.#data.notes, {
|
|
5096
|
+
meters: this.#data.meters,
|
|
5097
|
+
totalBeats: this.totalBeats,
|
|
5098
|
+
...opts
|
|
5099
|
+
});
|
|
5100
|
+
}
|
|
5101
|
+
/**
|
|
5102
|
+
* How the score's motifs derive from one another: which statement grew out
|
|
5103
|
+
* of which, and by what transformation.
|
|
5104
|
+
*
|
|
5105
|
+
* {@link Score.motifs} finds the cells; this reads across their occurrences
|
|
5106
|
+
* and names, for every statement after the first, the earlier one it most
|
|
5107
|
+
* plausibly grew out of. A melody is one line, so a score holding several is
|
|
5108
|
+
* read as its top voice, as {@link Score.motifs} is.
|
|
5109
|
+
*
|
|
5110
|
+
* @param opts Cell-length bounds and the recurrence threshold {@link
|
|
5111
|
+
* Score.motifs} takes, plus the variation threshold and the budget; see
|
|
5112
|
+
* {@link MotifGraphOptions} and {@link ExtractMotifsOptions}.
|
|
5113
|
+
* @returns The derivation forest.
|
|
5114
|
+
* @example
|
|
5115
|
+
* ```ts
|
|
5116
|
+
* import { Score } from '@libraz/libcantus';
|
|
5117
|
+
* const score = Score.of([
|
|
5118
|
+
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
5119
|
+
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
5120
|
+
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
5121
|
+
* { pitch: 60, startBeat: 3, durationBeat: 1 },
|
|
5122
|
+
* { pitch: 62, startBeat: 4, durationBeat: 1 },
|
|
5123
|
+
* { pitch: 64, startBeat: 5, durationBeat: 1 },
|
|
5124
|
+
* ]);
|
|
5125
|
+
* score.motifGraph().edges.length; // 1
|
|
5126
|
+
* ```
|
|
5127
|
+
*/
|
|
5128
|
+
motifGraph(opts) {
|
|
5129
|
+
return motifGraph(this.#data.notes, extractMotifs(this.#data.notes, opts), {
|
|
5130
|
+
key: this.key(),
|
|
5131
|
+
...opts
|
|
5132
|
+
});
|
|
5133
|
+
}
|
|
5134
|
+
/**
|
|
5135
|
+
* How this score, read as a melody, keeps to a composition plan.
|
|
5136
|
+
*
|
|
5137
|
+
* @param plan The plan the melody was written for.
|
|
5138
|
+
* @param opts The budget; see {@link EvaluateCompositionOptions}.
|
|
5139
|
+
* @returns The violations found and the fit reading; see {@link
|
|
5140
|
+
* evaluateComposition}.
|
|
5141
|
+
* @example
|
|
5142
|
+
* ```ts
|
|
5143
|
+
* import { Composer, Score } from '@libraz/libcantus';
|
|
5144
|
+
* const notes = [60, 62, 64, 65, 67, 65, 64, 62, 60, 62, 64, 65, 67, 65, 64, 60].map(
|
|
5145
|
+
* (pitch, startBeat) => ({ pitch, startBeat, durationBeat: 1 }),
|
|
5146
|
+
* );
|
|
5147
|
+
* const composer = Composer.of({ key: 'C major', seed: 5 });
|
|
5148
|
+
* const plan = composer.plan(Score.of(notes, { key: 'C major' }).reference());
|
|
5149
|
+
* composer.melody(plan).evaluate(plan).violations.some((v) => v.severity === 'error'); // false
|
|
5150
|
+
* ```
|
|
5151
|
+
*/
|
|
5152
|
+
evaluate(plan, opts) {
|
|
5153
|
+
return evaluateComposition(this.#data.notes, plan, opts);
|
|
5154
|
+
}
|
|
4901
5155
|
/**
|
|
4902
5156
|
* Each note's role in the harmony sounding under it.
|
|
4903
5157
|
*
|
|
@@ -5125,15 +5379,24 @@ var Score = class _Score {
|
|
|
5125
5379
|
* Kept on first reading for the same reason the chord timeline is.
|
|
5126
5380
|
*/
|
|
5127
5381
|
#keyRegions() {
|
|
5128
|
-
const regions = this.#regions ?? (this.#key === void 0 ? keyTimelineFromNotes(this.#data.notes, { meters: this.#data.meters }) :
|
|
5382
|
+
const regions = this.#regions ?? (this.#key === void 0 ? keyTimelineFromNotes(this.#data.notes, { meters: this.#data.meters }) : scoreKeyRegions(this.#key, this.#data.notes, this.totalBeats));
|
|
5129
5383
|
this.#regions = regions;
|
|
5130
5384
|
return regions;
|
|
5131
5385
|
}
|
|
5132
|
-
/**
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5386
|
+
/**
|
|
5387
|
+
* Where the score's own harmony closes, as the spans its arrival chords sound
|
|
5388
|
+
* through, read in the key {@link Score.phrases} reads them in.
|
|
5389
|
+
*/
|
|
5390
|
+
#cadenceSpans(budget) {
|
|
5391
|
+
const timeline = this.#chordTimeline();
|
|
5392
|
+
const key = keyContextFor(
|
|
5393
|
+
this.#key === void 0 ? void 0 : keyIdentity(this.#key),
|
|
5394
|
+
timeline,
|
|
5395
|
+
this.#data.meters,
|
|
5396
|
+
void 0,
|
|
5397
|
+
budget
|
|
5136
5398
|
);
|
|
5399
|
+
return boundaryCadences(timeline, key).spans;
|
|
5137
5400
|
}
|
|
5138
5401
|
/** The score's context as chord-timeline options, with the caller's on top. */
|
|
5139
5402
|
#analysisOptions(opts) {
|
|
@@ -5209,7 +5472,7 @@ function copySegments(segments) {
|
|
|
5209
5472
|
function chordTimelineOf(segments) {
|
|
5210
5473
|
return new Timeline({ segments: [...segments], totalBeats: spanEnd(segments) }).chordTimeline;
|
|
5211
5474
|
}
|
|
5212
|
-
function copySettings(settings,
|
|
5475
|
+
function copySettings(settings, tracks) {
|
|
5213
5476
|
if (settings === void 0) {
|
|
5214
5477
|
return void 0;
|
|
5215
5478
|
}
|
|
@@ -5236,8 +5499,9 @@ function copySettings(settings, trackCount) {
|
|
|
5236
5499
|
settings.harmonyTracks,
|
|
5237
5500
|
"arrangement harmonyTracks"
|
|
5238
5501
|
).map(
|
|
5239
|
-
(track, index) => assertInteger(track, `arrangement harmonyTracks[${index}]`, 0,
|
|
5502
|
+
(track, index) => assertInteger(track, `arrangement harmonyTracks[${index}]`, 0, tracks.length - 1)
|
|
5240
5503
|
);
|
|
5504
|
+
harmonyTrackSet(copy.harmonyTracks, tracks, "arrangement harmonyTracks");
|
|
5241
5505
|
}
|
|
5242
5506
|
if (settings.minSeverity !== void 0) {
|
|
5243
5507
|
copy.minSeverity = assertInteger(
|
|
@@ -5254,6 +5518,11 @@ function copySettings(settings, trackCount) {
|
|
|
5254
5518
|
Number.MAX_SAFE_INTEGER,
|
|
5255
5519
|
"arrangement pickupBeats"
|
|
5256
5520
|
);
|
|
5521
|
+
assertTrackNotes(tracks, {
|
|
5522
|
+
allowNonPositiveDuration: true,
|
|
5523
|
+
minStartBeat: -copy.pickupBeats,
|
|
5524
|
+
budget: settings.budget
|
|
5525
|
+
});
|
|
5257
5526
|
}
|
|
5258
5527
|
if (settings.harmonicRhythm !== void 0) {
|
|
5259
5528
|
copy.harmonicRhythm = assertRange(
|
|
@@ -5264,7 +5533,7 @@ function copySettings(settings, trackCount) {
|
|
|
5264
5533
|
);
|
|
5265
5534
|
}
|
|
5266
5535
|
if (settings.profile !== void 0) {
|
|
5267
|
-
copy.profile = settings.profile;
|
|
5536
|
+
copy.profile = arrangementProfile(settings.profile);
|
|
5268
5537
|
}
|
|
5269
5538
|
if (settings.budget !== void 0) {
|
|
5270
5539
|
copy.budget = assertPositiveInt(settings.budget, "arrangement budget", Number.MAX_SAFE_INTEGER);
|
|
@@ -5277,7 +5546,7 @@ function copyArrangement(data) {
|
|
|
5277
5546
|
const tracks = assertDataArray(data.tracks, "arrangement tracks").map(
|
|
5278
5547
|
(track, index) => copyTrack(track, index, budget)
|
|
5279
5548
|
);
|
|
5280
|
-
const settings = copySettings(data.settings, tracks
|
|
5549
|
+
const settings = copySettings(data.settings, tracks);
|
|
5281
5550
|
return settings === void 0 ? { tracks } : { tracks, settings };
|
|
5282
5551
|
}
|
|
5283
5552
|
function arrangementData(tracks, settings) {
|
|
@@ -5532,6 +5801,71 @@ var Arrangement = class _Arrangement {
|
|
|
5532
5801
|
keys: [...keys]
|
|
5533
5802
|
});
|
|
5534
5803
|
}
|
|
5804
|
+
/**
|
|
5805
|
+
* The arrangement's structural fingerprint: form, phrase shape, harmonic
|
|
5806
|
+
* function and rhythm, and how its melody's motifs derive from one another.
|
|
5807
|
+
*
|
|
5808
|
+
* The harmony reads the arrangement's own session analysis directly — the
|
|
5809
|
+
* tracks `settings.harmonyTracks` names, or else every track but the
|
|
5810
|
+
* percussion ones — so the harmony is never inferred a second time. The
|
|
5811
|
+
* melody is the notes of the tracks played with `role: 'melody'`, joined in
|
|
5812
|
+
* the order the tracks were given; an arrangement naming none reads the top
|
|
5813
|
+
* line of the harmony notes instead, as {@link analyzeReference} does
|
|
5814
|
+
* without a `melody` option. Meter, key, harmony and melody are the
|
|
5815
|
+
* arrangement's own and are not on offer here: a caller wanting others calls
|
|
5816
|
+
* {@link analyzeReference} directly, with the same notes {@link
|
|
5817
|
+
* Arrangement.tracks} hands out.
|
|
5818
|
+
*
|
|
5819
|
+
* @param opts The budget; see {@link ReferenceProfileOptions}.
|
|
5820
|
+
* @returns The profile.
|
|
5821
|
+
* @example
|
|
5822
|
+
* ```ts
|
|
5823
|
+
* import { Arrangement } from '@libraz/libcantus';
|
|
5824
|
+
* const triad = (pitches: number[], startBeat: number) =>
|
|
5825
|
+
* pitches.map((pitch) => ({ pitch, startBeat, durationBeat: 4 }));
|
|
5826
|
+
* const arrangement = Arrangement.of(
|
|
5827
|
+
* [
|
|
5828
|
+
* {
|
|
5829
|
+
* name: 'lead',
|
|
5830
|
+
* role: 'melody',
|
|
5831
|
+
* notes: [
|
|
5832
|
+
* ...triad([60, 64, 67], 0),
|
|
5833
|
+
* ...triad([65, 69, 72], 4),
|
|
5834
|
+
* ...triad([67, 71, 74], 8),
|
|
5835
|
+
* ...triad([60, 64, 67], 12),
|
|
5836
|
+
* ],
|
|
5837
|
+
* },
|
|
5838
|
+
* ],
|
|
5839
|
+
* { key: 'C major' },
|
|
5840
|
+
* );
|
|
5841
|
+
* arrangement.reference().harmony.chords.map((chord) => chord.roman); // ['I', 'IV', 'V', 'I']
|
|
5842
|
+
* ```
|
|
5843
|
+
*/
|
|
5844
|
+
reference(opts) {
|
|
5845
|
+
const tracks = this.#data.tracks;
|
|
5846
|
+
const harmonyTracks = this.#data.settings?.harmonyTracks;
|
|
5847
|
+
const harmony = [];
|
|
5848
|
+
const melody = [];
|
|
5849
|
+
for (let index = 0; index < tracks.length; index += 1) {
|
|
5850
|
+
const track = tracks[index];
|
|
5851
|
+
if (track === void 0) {
|
|
5852
|
+
continue;
|
|
5853
|
+
}
|
|
5854
|
+
if (track.role !== "drums" && (harmonyTracks === void 0 || harmonyTracks.includes(index))) {
|
|
5855
|
+
harmony.push(...track.notes);
|
|
5856
|
+
}
|
|
5857
|
+
if (track.role === "melody") {
|
|
5858
|
+
melody.push(...track.notes);
|
|
5859
|
+
}
|
|
5860
|
+
}
|
|
5861
|
+
return referenceFromReadings(harmony, this.#session().analysis, {
|
|
5862
|
+
// The meter the arrangement's own analysis reads, `ts` included.
|
|
5863
|
+
meters: this.#data.settings?.meters ?? this.#data.settings?.ts,
|
|
5864
|
+
totalBeats: this.timeline().totalBeats,
|
|
5865
|
+
...melody.length === 0 ? {} : { melody },
|
|
5866
|
+
...opts
|
|
5867
|
+
});
|
|
5868
|
+
}
|
|
5535
5869
|
/**
|
|
5536
5870
|
* Whether another arrangement holds the same tracks under the same settings.
|
|
5537
5871
|
*
|
|
@@ -5637,7 +5971,9 @@ function copyProfile(profile) {
|
|
|
5637
5971
|
}
|
|
5638
5972
|
const reach = {};
|
|
5639
5973
|
for (const [pitch, limbs] of Object.entries(profile.reach)) {
|
|
5640
|
-
|
|
5974
|
+
if (String(Number(pitch)) === pitch) {
|
|
5975
|
+
reach[Number(pitch)] = [...limbs];
|
|
5976
|
+
}
|
|
5641
5977
|
}
|
|
5642
5978
|
const percussion = {
|
|
5643
5979
|
kind: "percussion",
|
|
@@ -6149,7 +6485,7 @@ var Composer = class _Composer {
|
|
|
6149
6485
|
...instrument === void 0 ? {} : { instrument: toStringedProfile(instrument, "bass instrument") },
|
|
6150
6486
|
segments,
|
|
6151
6487
|
key,
|
|
6152
|
-
ts: this.#
|
|
6488
|
+
ts: this.#meters,
|
|
6153
6489
|
ctx: this.context
|
|
6154
6490
|
});
|
|
6155
6491
|
return Score.of(notes, this.#scoreOptions(key));
|
|
@@ -6181,7 +6517,7 @@ var Composer = class _Composer {
|
|
|
6181
6517
|
...timeline === void 0 ? {} : { timeline: toChordTimeline(timeline) },
|
|
6182
6518
|
melody: melody.notes,
|
|
6183
6519
|
key,
|
|
6184
|
-
ts: this.#
|
|
6520
|
+
ts: this.#meters,
|
|
6185
6521
|
ctx: this.context
|
|
6186
6522
|
});
|
|
6187
6523
|
return Score.of(notes, this.#scoreOptions(key));
|
|
@@ -6211,14 +6547,15 @@ var Composer = class _Composer {
|
|
|
6211
6547
|
...opts,
|
|
6212
6548
|
melody: melody.notes,
|
|
6213
6549
|
...this.#key === void 0 ? {} : { key: this.#key },
|
|
6214
|
-
ts: this.#
|
|
6550
|
+
ts: this.#meters,
|
|
6215
6551
|
ctx: this.context
|
|
6216
6552
|
});
|
|
6217
6553
|
const moved = melody.transpose(result.transposeSemitones);
|
|
6554
|
+
const last = result.chords[result.chords.length - 1];
|
|
6218
6555
|
const totalBeats = harmonizedSpan(
|
|
6219
6556
|
moved.totalBeats,
|
|
6220
6557
|
result.chords,
|
|
6221
|
-
|
|
6558
|
+
beatsPerBarAt(last?.startBeat ?? 0, this.#meters)
|
|
6222
6559
|
);
|
|
6223
6560
|
return {
|
|
6224
6561
|
chords: Timeline.fromChords(result.chords, totalBeats, result.key),
|
|
@@ -6228,6 +6565,73 @@ var Composer = class _Composer {
|
|
|
6228
6565
|
transposeSemitones: result.transposeSemitones
|
|
6229
6566
|
};
|
|
6230
6567
|
}
|
|
6568
|
+
/**
|
|
6569
|
+
* A composition plan derived from a reference profile.
|
|
6570
|
+
*
|
|
6571
|
+
* The composer's key is the plan's home key unless `opts.key` names another,
|
|
6572
|
+
* and its seed and algorithm version are the ones the plan records, so the
|
|
6573
|
+
* plan is written under the same recipe as the composer's other parts.
|
|
6574
|
+
*
|
|
6575
|
+
* @param reference The reference profile to derive from.
|
|
6576
|
+
* @param opts Everything {@link deriveCompositionPlan} takes but the context,
|
|
6577
|
+
* which is the composer's.
|
|
6578
|
+
* @returns The plan.
|
|
6579
|
+
* @throws If the composer draws from a caller-held `rng`: a plan records its
|
|
6580
|
+
* seed, and a source cannot be written into it.
|
|
6581
|
+
* @example
|
|
6582
|
+
* ```ts
|
|
6583
|
+
* import { Composer, Score } from '@libraz/libcantus';
|
|
6584
|
+
* const notes = [60, 62, 64, 65, 67, 65, 64, 62, 60, 62, 64, 65, 67, 65, 64, 60].map(
|
|
6585
|
+
* (pitch, startBeat) => ({ pitch, startBeat, durationBeat: 1 }),
|
|
6586
|
+
* );
|
|
6587
|
+
* const reference = Score.of(notes, { key: 'C major' }).reference();
|
|
6588
|
+
* const plan = Composer.of({ key: 'D major', seed: 5 }).plan(reference);
|
|
6589
|
+
* plan.seed; // 5
|
|
6590
|
+
* plan.keys[0].scale.rootPc; // 2
|
|
6591
|
+
* ```
|
|
6592
|
+
*/
|
|
6593
|
+
plan(reference, opts) {
|
|
6594
|
+
const ctx = this.context;
|
|
6595
|
+
if (ctx.rng !== void 0) {
|
|
6596
|
+
throw new InvalidInputError(
|
|
6597
|
+
"composer plan cannot record a caller-held rng; give the composer a seed instead"
|
|
6598
|
+
);
|
|
6599
|
+
}
|
|
6600
|
+
return deriveCompositionPlan(reference, {
|
|
6601
|
+
...this.#key === void 0 ? {} : { key: this.#key },
|
|
6602
|
+
...opts,
|
|
6603
|
+
ctx
|
|
6604
|
+
});
|
|
6605
|
+
}
|
|
6606
|
+
/**
|
|
6607
|
+
* A melody written to a composition plan.
|
|
6608
|
+
*
|
|
6609
|
+
* The plan is the reproduction recipe, so its seed and algorithm version are
|
|
6610
|
+
* the ones the melody is drawn under, not the composer's. The score is read
|
|
6611
|
+
* in the plan's meter and home key, at the composer's tempo.
|
|
6612
|
+
*
|
|
6613
|
+
* @param plan The plan to follow.
|
|
6614
|
+
* @param opts The budget; see {@link MelodyOptions}.
|
|
6615
|
+
* @returns The melody, as a score.
|
|
6616
|
+
* @example
|
|
6617
|
+
* ```ts
|
|
6618
|
+
* import { Composer, Score } from '@libraz/libcantus';
|
|
6619
|
+
* const notes = [60, 62, 64, 65, 67, 65, 64, 62, 60, 62, 64, 65, 67, 65, 64, 60].map(
|
|
6620
|
+
* (pitch, startBeat) => ({ pitch, startBeat, durationBeat: 1 }),
|
|
6621
|
+
* );
|
|
6622
|
+
* const composer = Composer.of({ key: 'C major', seed: 5 });
|
|
6623
|
+
* const plan = composer.plan(Score.of(notes, { key: 'C major' }).reference());
|
|
6624
|
+
* composer.melody(plan).notes.length > 0; // true
|
|
6625
|
+
* ```
|
|
6626
|
+
*/
|
|
6627
|
+
melody(plan, opts) {
|
|
6628
|
+
const notes = generateMelody(plan, opts);
|
|
6629
|
+
const score = { meters: plan.meters, key: plan.keys[0] };
|
|
6630
|
+
if (this.#options.bpm !== void 0) {
|
|
6631
|
+
score.tempo = this.#options.bpm;
|
|
6632
|
+
}
|
|
6633
|
+
return Score.of(notes, score);
|
|
6634
|
+
}
|
|
6231
6635
|
/**
|
|
6232
6636
|
* Whether another composer holds the same settings.
|
|
6233
6637
|
*
|
|
@@ -6286,16 +6690,6 @@ var Composer = class _Composer {
|
|
|
6286
6690
|
}
|
|
6287
6691
|
return this.#key;
|
|
6288
6692
|
}
|
|
6289
|
-
/**
|
|
6290
|
-
* The signature the parts are written against.
|
|
6291
|
-
*
|
|
6292
|
-
* Each generator names one time signature rather than a map, so a composer
|
|
6293
|
-
* whose meter changes hands over the signature its music opens on — the same
|
|
6294
|
-
* reading {@link Score.humanize} and its siblings take.
|
|
6295
|
-
*/
|
|
6296
|
-
#openingMeter() {
|
|
6297
|
-
return meterAt(0, this.#meters);
|
|
6298
|
-
}
|
|
6299
6693
|
/**
|
|
6300
6694
|
* The one signature a part laid out in bars is written against.
|
|
6301
6695
|
*
|
|
@@ -6310,7 +6704,7 @@ var Composer = class _Composer {
|
|
|
6310
6704
|
`composer ${part} needs one meter for the whole part; the composer changes meter`
|
|
6311
6705
|
);
|
|
6312
6706
|
}
|
|
6313
|
-
return this.#
|
|
6707
|
+
return meterAt(0, this.#meters);
|
|
6314
6708
|
}
|
|
6315
6709
|
/** The context every score a composer hands back is read against. */
|
|
6316
6710
|
#scoreOptions(key) {
|
|
@@ -6807,6 +7201,7 @@ var Motif = class _Motif {
|
|
|
6807
7201
|
* ```
|
|
6808
7202
|
*/
|
|
6809
7203
|
static generate(opts) {
|
|
7204
|
+
assertRecord(opts, "motif options");
|
|
6810
7205
|
return new _Motif(generateMotif({ ...opts, chord: chordFrom(opts.chord) }));
|
|
6811
7206
|
}
|
|
6812
7207
|
/**
|
|
@@ -6821,7 +7216,7 @@ var Motif = class _Motif {
|
|
|
6821
7216
|
* @throws If a note carries a value the transforms cannot hold.
|
|
6822
7217
|
*/
|
|
6823
7218
|
static fromNotes(notes) {
|
|
6824
|
-
return new _Motif({ notes: [...notes] });
|
|
7219
|
+
return new _Motif({ notes: [...assertArray(notes, "motif notes")] });
|
|
6825
7220
|
}
|
|
6826
7221
|
/** Rebuild a motif from the plain data {@link Motif.data} hands out. */
|
|
6827
7222
|
static fromData(cell) {
|
|
@@ -7140,7 +7535,7 @@ var Rhythm = class _Rhythm {
|
|
|
7140
7535
|
* @returns The compressed pattern.
|
|
7141
7536
|
*/
|
|
7142
7537
|
doubleTime() {
|
|
7143
|
-
return this.#withGrid(
|
|
7538
|
+
return this.#withGrid(doubleTimeFigure(toGrid(this.#data.events), this.#spanSteps()));
|
|
7144
7539
|
}
|
|
7145
7540
|
/**
|
|
7146
7541
|
* Stretch the pattern to twice its length: everything falls half as often.
|
|
@@ -7151,7 +7546,7 @@ var Rhythm = class _Rhythm {
|
|
|
7151
7546
|
* @returns The stretched pattern.
|
|
7152
7547
|
*/
|
|
7153
7548
|
halfTime() {
|
|
7154
|
-
return this.#withGrid(
|
|
7549
|
+
return this.#withGrid(halfTimeFigure(toGrid(this.#data.events), this.#spanSteps()));
|
|
7155
7550
|
}
|
|
7156
7551
|
/**
|
|
7157
7552
|
* Keep as much decoration as the ornament dial asks for.
|
|
@@ -7198,14 +7593,16 @@ var Rhythm = class _Rhythm {
|
|
|
7198
7593
|
rhythmic: resolved.rhythmic,
|
|
7199
7594
|
ornament: resolved.ornament,
|
|
7200
7595
|
rate: opts?.rate,
|
|
7201
|
-
spanSteps: this.#spanSteps(),
|
|
7202
7596
|
ts: this.#data.ts,
|
|
7203
7597
|
// The transform hands the predicate the events it was given, which carry
|
|
7204
7598
|
// the onset each was built from; the parameter is declared over the bare
|
|
7205
7599
|
// grid event the rule reads, so the field is named back here.
|
|
7206
7600
|
isOrnament: isOrnament === void 0 ? void 0 : (grid) => isOrnament(grid.source)
|
|
7207
7601
|
};
|
|
7208
|
-
|
|
7602
|
+
const dials = resolveDials(options);
|
|
7603
|
+
return this.#withGrid(
|
|
7604
|
+
deformFigure(toGrid(this.#data.events), this.#spanSteps(), dials, resolved.part(PART), [])
|
|
7605
|
+
);
|
|
7209
7606
|
}
|
|
7210
7607
|
/**
|
|
7211
7608
|
* Whether one player can sustain the pattern at the context's tempo.
|
|
@@ -7266,7 +7663,11 @@ var Rhythm = class _Rhythm {
|
|
|
7266
7663
|
toJSON() {
|
|
7267
7664
|
return this.data;
|
|
7268
7665
|
}
|
|
7269
|
-
/**
|
|
7666
|
+
/**
|
|
7667
|
+
* The pattern's span as a count of sixteenths, for the transforms: whatever
|
|
7668
|
+
* span the pattern covers, an empty one or one ending off the straight grid
|
|
7669
|
+
* included.
|
|
7670
|
+
*/
|
|
7270
7671
|
#spanSteps() {
|
|
7271
7672
|
return spanOf(this.#data.events) / STEP_BEATS;
|
|
7272
7673
|
}
|
|
@@ -8250,4 +8651,4 @@ export {
|
|
|
8250
8651
|
Tuning,
|
|
8251
8652
|
Voicing
|
|
8252
8653
|
};
|
|
8253
|
-
//# sourceMappingURL=chunk-
|
|
8654
|
+
//# sourceMappingURL=chunk-5MWRBYAT.js.map
|