@libraz/libcantus 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -65
- package/README_ja.md +28 -66
- package/dist/analyze/index.cjs +11 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +79 -144
- package/dist/analyze/index.d.ts +79 -144
- package/dist/analyze/index.js +20 -16
- package/dist/chunk-35NBV2FT.cjs +145 -0
- package/dist/chunk-35NBV2FT.cjs.map +1 -0
- package/dist/{chunk-UADYAYAT.cjs → chunk-3R3QVKY5.cjs} +1353 -580
- package/dist/chunk-3R3QVKY5.cjs.map +1 -0
- package/dist/{chunk-WWURE4XT.cjs → chunk-76ZBTRG2.cjs} +624 -729
- package/dist/chunk-76ZBTRG2.cjs.map +1 -0
- package/dist/{chunk-FVSI36UV.js → chunk-7KG3RTYS.js} +197 -110
- package/dist/chunk-7KG3RTYS.js.map +1 -0
- package/dist/{chunk-VTI6FOLB.js → chunk-AGOVW7U6.js} +709 -133
- package/dist/chunk-AGOVW7U6.js.map +1 -0
- package/dist/{chunk-E3F4AHAH.cjs → chunk-CGBADIO5.cjs} +221 -134
- package/dist/chunk-CGBADIO5.cjs.map +1 -0
- package/dist/{chunk-6XH6QZYQ.cjs → chunk-DSMJRXCZ.cjs} +1455 -849
- package/dist/chunk-DSMJRXCZ.cjs.map +1 -0
- package/dist/{chunk-PKFNUHDY.js → chunk-FBH75DAY.js} +1218 -612
- package/dist/chunk-FBH75DAY.js.map +1 -0
- package/dist/chunk-H2KJHBW4.js +3329 -0
- package/dist/chunk-H2KJHBW4.js.map +1 -0
- package/dist/chunk-HPVSH7SF.cjs +2294 -0
- package/dist/chunk-HPVSH7SF.cjs.map +1 -0
- package/dist/{chunk-MXO72PLY.cjs → chunk-HWHGOVO6.cjs} +21 -19
- package/dist/chunk-HWHGOVO6.cjs.map +1 -0
- package/dist/chunk-HYJJ2V2I.js +2294 -0
- package/dist/chunk-HYJJ2V2I.js.map +1 -0
- package/dist/{chunk-AFM2ULJH.cjs → chunk-J7FHVZII.cjs} +1567 -1067
- package/dist/chunk-J7FHVZII.cjs.map +1 -0
- package/dist/chunk-K6OZXIR6.js +1 -0
- package/dist/{chunk-H3VORETI.js → chunk-M5RYMAFI.js} +487 -592
- package/dist/chunk-M5RYMAFI.js.map +1 -0
- package/dist/chunk-NB25P3EH.js +363 -0
- package/dist/chunk-NB25P3EH.js.map +1 -0
- package/dist/{chunk-TTCLJX6D.js → chunk-NFJ4P6ZZ.js} +10 -8
- package/dist/chunk-NFJ4P6ZZ.js.map +1 -0
- package/dist/chunk-NM7UAMC3.cjs +457 -0
- package/dist/chunk-NM7UAMC3.cjs.map +1 -0
- package/dist/chunk-PM4XJE2Y.cjs +363 -0
- package/dist/chunk-PM4XJE2Y.cjs.map +1 -0
- package/dist/chunk-PUKJP2Z5.js +145 -0
- package/dist/chunk-PUKJP2Z5.js.map +1 -0
- package/dist/{chunk-VOHZB45S.js → chunk-Q6LFSAKV.js} +1321 -821
- package/dist/chunk-Q6LFSAKV.js.map +1 -0
- package/dist/{chunk-INHVXP6Z.js → chunk-RFIBF2RR.js} +1203 -430
- package/dist/chunk-RFIBF2RR.js.map +1 -0
- package/dist/{chunk-Q2WGKJNN.cjs → chunk-S5BKZZGM.cjs} +699 -123
- package/dist/chunk-S5BKZZGM.cjs.map +1 -0
- package/dist/chunk-SGLEQVJH.cjs +3329 -0
- package/dist/chunk-SGLEQVJH.cjs.map +1 -0
- package/dist/chunk-UGLFDG7L.cjs +1 -0
- package/dist/chunk-UGLFDG7L.cjs.map +1 -0
- package/dist/{chunk-YH3BIKHH.js → chunk-UMWZ4ZET.js} +146 -49
- package/dist/chunk-UMWZ4ZET.js.map +1 -0
- package/dist/{index-szBv3496.d.ts → coerce-DP0vYu-x.d.ts} +286 -199
- package/dist/{index-C_8o--VE.d.cts → coerce-DRERzdve.d.cts} +286 -199
- package/dist/core/index.cjs +9 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +122 -108
- package/dist/core/index.d.ts +122 -108
- package/dist/core/index.js +32 -28
- package/dist/generate/index.cjs +8 -8
- package/dist/generate/index.d.cts +168 -84
- package/dist/generate/index.d.ts +168 -84
- package/dist/generate/index.js +7 -7
- package/dist/{index-CGpRv5sl.d.cts → index-9eoQs2C1.d.ts} +34 -19
- package/dist/{index-CCNJRG-P.d.cts → index-B9QLziDe.d.cts} +293 -56
- package/dist/{index-Dzr13q3d.d.cts → index-BBWNZS0f.d.cts} +135 -58
- package/dist/{index-9wcMhG73.d.ts → index-BHIUyjhZ.d.ts} +293 -56
- package/dist/{index-BGFW-8P2.d.cts → index-BJgP97N1.d.ts} +196 -114
- package/dist/{index-CunYKLiu.d.ts → index-BL5N2Oyz.d.cts} +1 -1
- package/dist/{index-Cj49F892.d.ts → index-BMweUlm5.d.cts} +63 -23
- package/dist/{index-CgHqxdps.d.ts → index-BNSM-DSk.d.cts} +196 -114
- package/dist/{index-D57QRbVn.d.ts → index-BRMbPkHE.d.ts} +480 -254
- package/dist/{index-Chbst3Jx.d.ts → index-CJBBiAmU.d.ts} +135 -58
- package/dist/{index-BkGmlyI3.d.cts → index-CL4b4gUQ.d.ts} +63 -23
- package/dist/{index-B9MNZboQ.d.ts → index-Cy15oTvh.d.cts} +52 -22
- package/dist/{index-B9MNZboQ.d.cts → index-Cy15oTvh.d.ts} +52 -22
- package/dist/{index-BO6xE9YU.d.cts → index-DGCNKAUr.d.cts} +480 -254
- package/dist/{index-B4o55NDr.d.cts → index-Dw2DP7vm.d.ts} +1 -1
- package/dist/{index-G1uwGGWQ.d.ts → index-cHvbYgJI.d.cts} +34 -19
- package/dist/index.cjs +42 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +82 -54
- package/dist/{masks-DYbE485J.d.cts → masks-Cc7-MzzY.d.ts} +49 -2
- package/dist/{masks-DYbE485J.d.ts → masks-pADfTOiD.d.cts} +49 -2
- package/dist/model/index.cjs +19 -12
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +504 -182
- package/dist/model/index.d.ts +504 -182
- package/dist/model/index.js +18 -11
- package/dist/{positional-Cptlj-xs.d.ts → positional-DJgs6ltD.d.ts} +85 -8
- package/dist/{positional-DvH_sd8u.d.cts → positional-DMZer-FM.d.cts} +85 -8
- package/dist/theory/index.cjs +35 -6
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +257 -62
- package/dist/theory/index.d.ts +257 -62
- package/dist/theory/index.js +41 -12
- package/package.json +7 -5
- package/dist/chunk-2LCPM3EG.js +0 -3224
- package/dist/chunk-2LCPM3EG.js.map +0 -1
- package/dist/chunk-6XH6QZYQ.cjs.map +0 -1
- package/dist/chunk-6XPTBE7Y.cjs +0 -49
- package/dist/chunk-6XPTBE7Y.cjs.map +0 -1
- package/dist/chunk-AFM2ULJH.cjs.map +0 -1
- package/dist/chunk-B6XNKWZV.cjs +0 -3224
- package/dist/chunk-B6XNKWZV.cjs.map +0 -1
- package/dist/chunk-E3F4AHAH.cjs.map +0 -1
- package/dist/chunk-ELCTZUJJ.js +0 -379
- package/dist/chunk-ELCTZUJJ.js.map +0 -1
- package/dist/chunk-FVSI36UV.js.map +0 -1
- package/dist/chunk-H3VORETI.js.map +0 -1
- package/dist/chunk-H42UHPYW.js +0 -1577
- package/dist/chunk-H42UHPYW.js.map +0 -1
- package/dist/chunk-HFPS7JFD.js +0 -1
- package/dist/chunk-INHVXP6Z.js.map +0 -1
- package/dist/chunk-K4ZJUGF4.cjs +0 -379
- package/dist/chunk-K4ZJUGF4.cjs.map +0 -1
- package/dist/chunk-KNJEKLKV.js +0 -49
- package/dist/chunk-KNJEKLKV.js.map +0 -1
- package/dist/chunk-MXO72PLY.cjs.map +0 -1
- package/dist/chunk-NLEU4QSE.cjs +0 -360
- package/dist/chunk-NLEU4QSE.cjs.map +0 -1
- package/dist/chunk-OE6WK2Y2.cjs +0 -1577
- package/dist/chunk-OE6WK2Y2.cjs.map +0 -1
- package/dist/chunk-PKFNUHDY.js.map +0 -1
- package/dist/chunk-Q2WGKJNN.cjs.map +0 -1
- package/dist/chunk-RHIJ3S4I.cjs +0 -1
- package/dist/chunk-RHIJ3S4I.cjs.map +0 -1
- package/dist/chunk-TTCLJX6D.js.map +0 -1
- package/dist/chunk-UADYAYAT.cjs.map +0 -1
- package/dist/chunk-VOHZB45S.js.map +0 -1
- package/dist/chunk-VTI6FOLB.js.map +0 -1
- package/dist/chunk-WWURE4XT.cjs.map +0 -1
- package/dist/chunk-YH3BIKHH.js.map +0 -1
- /package/dist/{chunk-HFPS7JFD.js.map → chunk-K6OZXIR6.js.map} +0 -0
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
analyzeArrangement,
|
|
3
|
-
|
|
4
|
-
chordTimelineFromChords,
|
|
5
|
-
chordTimelineFromNotes,
|
|
3
|
+
analyzePolyphony,
|
|
6
4
|
createArrangementSession,
|
|
7
|
-
detectCadences,
|
|
8
5
|
extractMotifs,
|
|
9
6
|
hypermeter,
|
|
10
|
-
keyLookup,
|
|
11
|
-
keyTimelineFromNotes,
|
|
12
7
|
melodicContour,
|
|
13
8
|
melodicSimilarity,
|
|
14
9
|
motifFromNotes,
|
|
15
10
|
phrasesFromTimeline,
|
|
16
|
-
prevailingKeyOf,
|
|
17
11
|
reduceProgression,
|
|
18
12
|
relateMotifs,
|
|
19
13
|
sectionsFromNotes,
|
|
20
14
|
structuralCadences,
|
|
21
15
|
tensionCurveFrom,
|
|
22
|
-
|
|
23
|
-
} from "./chunk-
|
|
16
|
+
trackRoleOf
|
|
17
|
+
} from "./chunk-HYJJ2V2I.js";
|
|
24
18
|
import {
|
|
25
19
|
TWELVE_TET,
|
|
26
20
|
beatsToDuration,
|
|
@@ -42,11 +36,13 @@ import {
|
|
|
42
36
|
stepOf,
|
|
43
37
|
stepsOfCents,
|
|
44
38
|
tempoAt,
|
|
39
|
+
tempoReader,
|
|
45
40
|
ticksToBeats
|
|
46
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-UMWZ4ZET.js";
|
|
47
42
|
import {
|
|
48
43
|
STEP_BEATS,
|
|
49
44
|
applyGrooveTemplate,
|
|
45
|
+
cellSpan,
|
|
50
46
|
deform,
|
|
51
47
|
developMotif,
|
|
52
48
|
doubleTime,
|
|
@@ -73,67 +69,58 @@ import {
|
|
|
73
69
|
thin,
|
|
74
70
|
transformMotif,
|
|
75
71
|
withinCeiling
|
|
76
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-Q6LFSAKV.js";
|
|
77
73
|
import {
|
|
78
74
|
analyzeChord,
|
|
75
|
+
augmentedSixthChord,
|
|
79
76
|
borrowedSource,
|
|
77
|
+
chordAtBeat,
|
|
78
|
+
chordTimelineFromChords,
|
|
79
|
+
chordTimelineFromNotes,
|
|
80
80
|
chordToRoman,
|
|
81
81
|
detectCadence,
|
|
82
|
+
detectCadences,
|
|
82
83
|
detectChord,
|
|
83
84
|
detectChordBest,
|
|
84
85
|
detectKey,
|
|
85
86
|
detectKeyBest,
|
|
86
87
|
detectKeyFromNotes,
|
|
88
|
+
detectModulations,
|
|
87
89
|
explainRoman,
|
|
88
90
|
functionOf,
|
|
89
91
|
isBorrowedChord,
|
|
90
92
|
isMinorKey,
|
|
93
|
+
keyLookup,
|
|
94
|
+
keyTimelineFromNotes,
|
|
95
|
+
pivotChords,
|
|
96
|
+
prevailingKeyOf,
|
|
91
97
|
romanToChord,
|
|
92
|
-
secondaryDominantOf
|
|
93
|
-
|
|
98
|
+
secondaryDominantOf,
|
|
99
|
+
toVoiceNotes
|
|
100
|
+
} from "./chunk-H2KJHBW4.js";
|
|
94
101
|
import {
|
|
95
102
|
BASS_4_STRING,
|
|
96
103
|
BASS_5_STRING,
|
|
97
104
|
GUITAR_DROP_D,
|
|
98
105
|
GUITAR_STANDARD,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
foldIntoRange,
|
|
102
|
-
instrumentRange,
|
|
106
|
+
fingeringsIn,
|
|
107
|
+
foldIn,
|
|
103
108
|
playability,
|
|
109
|
+
rangeIn,
|
|
110
|
+
soundsIn,
|
|
104
111
|
toInstrumentProfile,
|
|
105
112
|
toStringedProfile
|
|
106
|
-
} from "./chunk-
|
|
113
|
+
} from "./chunk-7KG3RTYS.js";
|
|
107
114
|
import {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
createNoteEventIndex,
|
|
111
|
-
formatBarPosition,
|
|
112
|
-
formatTimeSignature,
|
|
113
|
-
isCompound,
|
|
114
|
-
isStrongBeat,
|
|
115
|
-
meterAt,
|
|
116
|
-
metricWeight,
|
|
117
|
-
parseTimeSignature,
|
|
118
|
-
pulseBeats,
|
|
119
|
-
pulsesPerBar,
|
|
120
|
-
resolveMeters,
|
|
121
|
-
toMeterData,
|
|
122
|
-
tryParseTimeSignature,
|
|
123
|
-
tuplet
|
|
124
|
-
} from "./chunk-ELCTZUJJ.js";
|
|
115
|
+
createNoteEventIndex
|
|
116
|
+
} from "./chunk-PUKJP2Z5.js";
|
|
125
117
|
import {
|
|
126
118
|
SATB_RANGES,
|
|
127
|
-
availableTensions,
|
|
128
|
-
avoidNotes,
|
|
129
119
|
checkPartWriting,
|
|
130
120
|
checkSpecies,
|
|
131
|
-
chordScaleReport,
|
|
132
|
-
chordScales,
|
|
133
121
|
figuredBassOf,
|
|
134
122
|
nextVoicing,
|
|
135
123
|
realizeFiguredBass,
|
|
136
|
-
scalesForChanges,
|
|
137
124
|
spellVoicing,
|
|
138
125
|
toSoundingPitch,
|
|
139
126
|
toWrittenPitch,
|
|
@@ -141,9 +128,18 @@ import {
|
|
|
141
128
|
voiceChordStyled,
|
|
142
129
|
voiceLeadingCost,
|
|
143
130
|
voiceProgression
|
|
144
|
-
} from "./chunk-
|
|
131
|
+
} from "./chunk-M5RYMAFI.js";
|
|
132
|
+
import {
|
|
133
|
+
availableTensions,
|
|
134
|
+
avoidNotes,
|
|
135
|
+
chordScaleReport,
|
|
136
|
+
chordScales,
|
|
137
|
+
scalesForChanges
|
|
138
|
+
} from "./chunk-NB25P3EH.js";
|
|
145
139
|
import {
|
|
146
140
|
NAMED_SCALES,
|
|
141
|
+
assertChordQuality,
|
|
142
|
+
assertKeyVariant,
|
|
147
143
|
chordFromDegree,
|
|
148
144
|
chordFromSpan,
|
|
149
145
|
chordPitchClasses,
|
|
@@ -160,7 +156,7 @@ import {
|
|
|
160
156
|
formatChordSymbol,
|
|
161
157
|
isChordMember,
|
|
162
158
|
isScaleTone,
|
|
163
|
-
|
|
159
|
+
isWrittenTonic,
|
|
164
160
|
keyFromFifths,
|
|
165
161
|
keyRelationBetween,
|
|
166
162
|
keySignatureFifths,
|
|
@@ -172,7 +168,10 @@ import {
|
|
|
172
168
|
pitchToScaleDegree,
|
|
173
169
|
relatedKeysOf,
|
|
174
170
|
relativeKeyOf,
|
|
171
|
+
resolveKey,
|
|
175
172
|
scaleByName,
|
|
173
|
+
scaleNameOfMask,
|
|
174
|
+
scaleOf,
|
|
176
175
|
scaleSystemOf,
|
|
177
176
|
scaleTonesInDegreeOrder,
|
|
178
177
|
spanFromChord,
|
|
@@ -182,43 +181,61 @@ import {
|
|
|
182
181
|
spellPitchClass,
|
|
183
182
|
spellPitchClasses,
|
|
184
183
|
spellScale,
|
|
185
|
-
spelledKeyOf,
|
|
186
184
|
subdominantKeyOf,
|
|
187
185
|
supportsFunctionalHarmony,
|
|
188
186
|
toChordData,
|
|
189
187
|
toKeyScale,
|
|
190
188
|
transposeChord,
|
|
189
|
+
transposeChordSpellings,
|
|
191
190
|
tryParseChordSymbol,
|
|
191
|
+
tryResolveKeyName,
|
|
192
|
+
variantOfMask,
|
|
192
193
|
voiceIndependence
|
|
193
|
-
} from "./chunk-
|
|
194
|
+
} from "./chunk-RFIBF2RR.js";
|
|
194
195
|
import {
|
|
195
196
|
InvalidInputError,
|
|
197
|
+
MAX_NAME_ACCIDENTALS,
|
|
196
198
|
assertFiniteNumber,
|
|
197
199
|
assertInteger,
|
|
198
200
|
assertMidiPitch,
|
|
199
|
-
|
|
201
|
+
assertNoteEvents,
|
|
200
202
|
assertOneOf,
|
|
201
203
|
assertPositiveInt,
|
|
202
204
|
assertRange,
|
|
203
205
|
assertTimeSignature,
|
|
206
|
+
beatToBarPosition,
|
|
207
|
+
beatsPerBar,
|
|
208
|
+
describeRejected,
|
|
209
|
+
formatBarPosition,
|
|
204
210
|
formatKeyName,
|
|
205
211
|
formatNote,
|
|
212
|
+
formatTimeSignature,
|
|
206
213
|
intervalSemitones,
|
|
214
|
+
isCompound,
|
|
207
215
|
isConsonantInterval,
|
|
216
|
+
isStrongBeat,
|
|
217
|
+
meterAt,
|
|
218
|
+
metricWeight,
|
|
208
219
|
midiToNote,
|
|
209
220
|
noteToMidi,
|
|
210
221
|
noteToPitchClass,
|
|
222
|
+
parseTimeSignature,
|
|
211
223
|
pitchClassOf,
|
|
224
|
+
pulseBeats,
|
|
225
|
+
pulsesPerBar,
|
|
226
|
+
resolveMeters,
|
|
212
227
|
spelledInterval,
|
|
228
|
+
toMeterData,
|
|
213
229
|
toNoteData,
|
|
214
230
|
toSpelledInterval,
|
|
215
231
|
transposeByInterval,
|
|
216
232
|
transposeNote,
|
|
217
233
|
tryParseInterval,
|
|
218
|
-
tryParseKeyName,
|
|
219
234
|
tryParseNote,
|
|
235
|
+
tryParseTimeSignature,
|
|
236
|
+
tuplet,
|
|
220
237
|
unwrapParse
|
|
221
|
-
} from "./chunk-
|
|
238
|
+
} from "./chunk-AGOVW7U6.js";
|
|
222
239
|
|
|
223
240
|
// src/model/interval.ts
|
|
224
241
|
var INVERSION_SUM = 9;
|
|
@@ -288,12 +305,7 @@ var Interval = class _Interval {
|
|
|
288
305
|
*/
|
|
289
306
|
static fromData(data) {
|
|
290
307
|
const checked = toSpelledInterval(data);
|
|
291
|
-
return new _Interval(
|
|
292
|
-
checked.number,
|
|
293
|
-
checked.quality,
|
|
294
|
-
checked.semitones,
|
|
295
|
-
checked.descending ?? false
|
|
296
|
-
);
|
|
308
|
+
return new _Interval(checked.number, checked.quality, checked.semitones, checked.descending);
|
|
297
309
|
}
|
|
298
310
|
/**
|
|
299
311
|
* Rebuild an interval from its {@link Interval.toJSON} output.
|
|
@@ -357,9 +369,16 @@ var Interval = class _Interval {
|
|
|
357
369
|
get isDescending() {
|
|
358
370
|
return this.#descending;
|
|
359
371
|
}
|
|
360
|
-
/**
|
|
372
|
+
/**
|
|
373
|
+
* A readable label composed of quality and number, e.g. `'P5'` or `'M3'`,
|
|
374
|
+
* prefixed with `'-'` when the interval descends.
|
|
375
|
+
*
|
|
376
|
+
* The name is written in the grammar {@link Interval.parse} reads, so an
|
|
377
|
+
* interval that has been logged, written to a column, or interpolated into a
|
|
378
|
+
* template comes back as the same interval — direction included.
|
|
379
|
+
*/
|
|
361
380
|
get name() {
|
|
362
|
-
return `${this.#quality}${this.#number}`;
|
|
381
|
+
return `${this.#descending ? "-" : ""}${this.#quality}${this.#number}`;
|
|
363
382
|
}
|
|
364
383
|
/** A copy of the underlying plain interval data. */
|
|
365
384
|
get data() {
|
|
@@ -397,7 +416,7 @@ var Interval = class _Interval {
|
|
|
397
416
|
* @example
|
|
398
417
|
* ```ts
|
|
399
418
|
* import { Interval } from '@libraz/libcantus';
|
|
400
|
-
* Interval.parse('M3').negate().toString(); // 'M3'
|
|
419
|
+
* Interval.parse('M3').negate().toString(); // '-M3'
|
|
401
420
|
* ```
|
|
402
421
|
*/
|
|
403
422
|
negate() {
|
|
@@ -435,34 +454,168 @@ var Interval = class _Interval {
|
|
|
435
454
|
*
|
|
436
455
|
* Private class fields do not serialize, so an explicit `toJSON` keeps
|
|
437
456
|
* `JSON.stringify(interval)` from collapsing to `{}`. The result is the
|
|
438
|
-
* canonical shape the pitch module produces: `descending`
|
|
439
|
-
*
|
|
440
|
-
*
|
|
457
|
+
* canonical shape the pitch module produces: `descending` is always there, so
|
|
458
|
+
* a measured interval, a parsed name, and this method all serialize to the
|
|
459
|
+
* same object and none of them leaves the direction to be guessed.
|
|
441
460
|
*
|
|
442
|
-
* @returns The diatonic number, quality, and semitone span, with
|
|
443
|
-
*
|
|
461
|
+
* @returns The diatonic number, quality, and semitone span, with `descending`
|
|
462
|
+
* telling which way the interval goes.
|
|
444
463
|
*/
|
|
445
464
|
toJSON() {
|
|
446
|
-
return
|
|
465
|
+
return {
|
|
447
466
|
number: this.#number,
|
|
448
467
|
quality: this.#quality,
|
|
449
468
|
semitones: this.#semitones,
|
|
450
|
-
descending:
|
|
451
|
-
}
|
|
469
|
+
descending: this.#descending
|
|
470
|
+
};
|
|
452
471
|
}
|
|
453
472
|
/**
|
|
454
473
|
* The interval's name, so a template literal or a log line reads as the
|
|
455
474
|
* interval.
|
|
456
475
|
*
|
|
457
|
-
* @returns The name, e.g. `'M3'
|
|
476
|
+
* @returns The name, e.g. `'M3'` or `'-M3'` for the same third taken
|
|
477
|
+
* downward.
|
|
458
478
|
*/
|
|
459
479
|
toString() {
|
|
460
480
|
return this.name;
|
|
461
481
|
}
|
|
462
482
|
};
|
|
463
483
|
|
|
484
|
+
// src/model/shared.ts
|
|
485
|
+
var STATED_KEY_CONFIDENCE = 1;
|
|
486
|
+
function describeShape(value) {
|
|
487
|
+
if (value === null) {
|
|
488
|
+
return "null";
|
|
489
|
+
}
|
|
490
|
+
return Array.isArray(value) ? "an array" : typeof value;
|
|
491
|
+
}
|
|
492
|
+
function assertDataObject(value, name) {
|
|
493
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
494
|
+
throw new InvalidInputError(`${name} must be an object; received ${describeShape(value)}`);
|
|
495
|
+
}
|
|
496
|
+
return value;
|
|
497
|
+
}
|
|
498
|
+
function assertDataArray(value, name) {
|
|
499
|
+
if (!Array.isArray(value)) {
|
|
500
|
+
throw new InvalidInputError(`${name} must be an array; received ${describeShape(value)}`);
|
|
501
|
+
}
|
|
502
|
+
return value;
|
|
503
|
+
}
|
|
504
|
+
function assertDataObjects(value, name) {
|
|
505
|
+
const items = assertDataArray(value, name);
|
|
506
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
507
|
+
assertDataObject(items[index], `${name}[${index}]`);
|
|
508
|
+
}
|
|
509
|
+
return items;
|
|
510
|
+
}
|
|
511
|
+
function assertNoteEventArray(notes, name, options) {
|
|
512
|
+
return assertNoteEvents(assertDataArray(notes, name), name, options);
|
|
513
|
+
}
|
|
514
|
+
function assertKeyArgument(value, name) {
|
|
515
|
+
if (value === void 0 || typeof value === "string" || isKeyShaped(value)) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const received = value === null ? "null" : typeof value === "object" ? "an object" : `a ${typeof value}`;
|
|
519
|
+
throw new InvalidInputError(
|
|
520
|
+
`${name} must be a Key; received ${received}. A method that also takes options takes them after the key, not in its place`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
function isKeyShaped(value) {
|
|
524
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
return "scale" in value || "toJSON" in value || "rootPc" in value && "modeMask12" in value;
|
|
528
|
+
}
|
|
529
|
+
function withoutNegativeZero(value) {
|
|
530
|
+
return value === 0 ? 0 : value;
|
|
531
|
+
}
|
|
532
|
+
function spanEnd(...runs) {
|
|
533
|
+
let end = 0;
|
|
534
|
+
for (const run of runs) {
|
|
535
|
+
for (const span of run) {
|
|
536
|
+
end = Math.max(end, span.endBeat);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return end;
|
|
540
|
+
}
|
|
541
|
+
function copyNoteEvent(note) {
|
|
542
|
+
const copy = {
|
|
543
|
+
pitch: withoutNegativeZero(note.pitch),
|
|
544
|
+
startBeat: withoutNegativeZero(note.startBeat),
|
|
545
|
+
durationBeat: withoutNegativeZero(note.durationBeat)
|
|
546
|
+
};
|
|
547
|
+
if (note.velocity !== void 0) {
|
|
548
|
+
copy.velocity = withoutNegativeZero(note.velocity);
|
|
549
|
+
}
|
|
550
|
+
if (note.articulation !== void 0) {
|
|
551
|
+
copy.articulation = note.articulation;
|
|
552
|
+
}
|
|
553
|
+
return copy;
|
|
554
|
+
}
|
|
555
|
+
function copyTimeSignature(ts, name) {
|
|
556
|
+
assertDataObject(ts, name ?? "time signature");
|
|
557
|
+
assertTimeSignature(ts, name);
|
|
558
|
+
const copy = { numerator: ts.numerator, denominator: ts.denominator };
|
|
559
|
+
if (ts.grouping !== void 0) {
|
|
560
|
+
copy.grouping = [...ts.grouping];
|
|
561
|
+
}
|
|
562
|
+
return copy;
|
|
563
|
+
}
|
|
564
|
+
var MAX_PLAIN_DEPTH = 64;
|
|
565
|
+
function copyPlain(value, name) {
|
|
566
|
+
return copyPlainAt(value, name, /* @__PURE__ */ new WeakSet(), 0);
|
|
567
|
+
}
|
|
568
|
+
function copyPlainAt(value, name, seen, depth) {
|
|
569
|
+
if (typeof value === "number") {
|
|
570
|
+
return assertFiniteNumber(value, name);
|
|
571
|
+
}
|
|
572
|
+
if (typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") {
|
|
573
|
+
throw new InvalidInputError(`${name} must be plain data; received ${typeof value}`);
|
|
574
|
+
}
|
|
575
|
+
if (typeof value !== "object" || value === null) {
|
|
576
|
+
return value;
|
|
577
|
+
}
|
|
578
|
+
if (seen.has(value)) {
|
|
579
|
+
throw new InvalidInputError(`${name} must be plain data; received a value referring to itself`);
|
|
580
|
+
}
|
|
581
|
+
if (depth >= MAX_PLAIN_DEPTH) {
|
|
582
|
+
throw new InvalidInputError(
|
|
583
|
+
`${name} must be plain data; received a value nested more than ${MAX_PLAIN_DEPTH} levels deep`
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
seen.add(value);
|
|
587
|
+
try {
|
|
588
|
+
if (Array.isArray(value)) {
|
|
589
|
+
return value.map(
|
|
590
|
+
(item, index) => copyPlainAt(item, `${name}[${index}]`, seen, depth + 1)
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
const copy = {};
|
|
594
|
+
for (const [key, item] of Object.entries(value)) {
|
|
595
|
+
if (item !== void 0) {
|
|
596
|
+
copy[key] = copyPlainAt(item, `${name}.${key}`, seen, depth + 1);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return copy;
|
|
600
|
+
} finally {
|
|
601
|
+
seen.delete(value);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function samePlain(value, other) {
|
|
605
|
+
if (Array.isArray(value) || Array.isArray(other)) {
|
|
606
|
+
return Array.isArray(value) && Array.isArray(other) && value.length === other.length && value.every((item, index) => samePlain(item, other[index]));
|
|
607
|
+
}
|
|
608
|
+
if (typeof value === "object" && value !== null && typeof other === "object" && other !== null) {
|
|
609
|
+
const mine = Object.entries(value);
|
|
610
|
+
const theirs = other;
|
|
611
|
+
return mine.length === Object.keys(theirs).length && mine.every(([key, item]) => key in theirs && samePlain(item, theirs[key]));
|
|
612
|
+
}
|
|
613
|
+
return value === other;
|
|
614
|
+
}
|
|
615
|
+
|
|
464
616
|
// src/model/note.ts
|
|
465
617
|
function copyNote(data) {
|
|
618
|
+
assertDataObject(data, "note data");
|
|
466
619
|
if (!Number.isInteger(data.letter) || data.letter < 0 || data.letter > 6) {
|
|
467
620
|
throw new InvalidInputError(
|
|
468
621
|
`note letter must be an integer in [0, 6]; received ${data.letter}`
|
|
@@ -482,13 +635,17 @@ function letterNumberOf(letter) {
|
|
|
482
635
|
}
|
|
483
636
|
if (!NATURAL_LETTER.test(letter)) {
|
|
484
637
|
throw new InvalidInputError(
|
|
485
|
-
`Note.of takes a letter number 0..6 or a bare letter name 'C'..'B'; received ${
|
|
638
|
+
`Note.of takes a letter number 0..6 or a bare letter name 'C'..'B'; received ${describeRejected(letter)}. Use Note.parse(${describeRejected(letter)}) to read a note name with an accidental or an octave.`
|
|
486
639
|
);
|
|
487
640
|
}
|
|
488
641
|
return unwrapParse(tryParseNote(letter)).letter;
|
|
489
642
|
}
|
|
490
|
-
var
|
|
491
|
-
|
|
643
|
+
var DIMINISHED_SECOND = {
|
|
644
|
+
number: 2,
|
|
645
|
+
quality: "d",
|
|
646
|
+
semitones: 0,
|
|
647
|
+
descending: false
|
|
648
|
+
};
|
|
492
649
|
var DESCENDING_DIMINISHED_SECOND = {
|
|
493
650
|
...DIMINISHED_SECOND,
|
|
494
651
|
descending: true
|
|
@@ -809,7 +966,7 @@ var Note = class _Note {
|
|
|
809
966
|
* ```
|
|
810
967
|
*/
|
|
811
968
|
enharmonic() {
|
|
812
|
-
return [DIMINISHED_SECOND, DESCENDING_DIMINISHED_SECOND].map((interval) => transposeByInterval(this.#data, interval)).filter((note) => Math.abs(note.alter) <=
|
|
969
|
+
return [DIMINISHED_SECOND, DESCENDING_DIMINISHED_SECOND].map((interval) => transposeByInterval(this.#data, interval)).filter((note) => Math.abs(note.alter) <= MAX_NAME_ACCIDENTALS).map((note) => new _Note(note));
|
|
813
970
|
}
|
|
814
971
|
/**
|
|
815
972
|
* The scale degree this note occupies in a key, counted from 1 at the tonic.
|
|
@@ -830,7 +987,7 @@ var Note = class _Note {
|
|
|
830
987
|
* ```
|
|
831
988
|
*/
|
|
832
989
|
degreeIn(key) {
|
|
833
|
-
const degree = pitchToScaleDegree(this.pitchClass,
|
|
990
|
+
const degree = pitchToScaleDegree(this.pitchClass, key);
|
|
834
991
|
return degree === -1 ? null : degree;
|
|
835
992
|
}
|
|
836
993
|
/**
|
|
@@ -907,65 +1064,9 @@ var Note = class _Note {
|
|
|
907
1064
|
}
|
|
908
1065
|
};
|
|
909
1066
|
|
|
910
|
-
// src/model/shared.ts
|
|
911
|
-
function assertKeyArgument(value, name) {
|
|
912
|
-
if (value === void 0 || typeof value === "object" && value !== null && "scale" in value) {
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
const received = typeof value === "object" && value !== null ? "an object" : `a ${typeof value}`;
|
|
916
|
-
throw new InvalidInputError(
|
|
917
|
-
`${name} must be a Key; received ${received}. A method that also takes options takes them after the key, not in its place`
|
|
918
|
-
);
|
|
919
|
-
}
|
|
920
|
-
function withoutNegativeZero(value) {
|
|
921
|
-
return value === 0 ? 0 : value;
|
|
922
|
-
}
|
|
923
|
-
function spanEnd(...runs) {
|
|
924
|
-
let end = 0;
|
|
925
|
-
for (const run of runs) {
|
|
926
|
-
for (const span of run) {
|
|
927
|
-
end = Math.max(end, span.endBeat);
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
return end;
|
|
931
|
-
}
|
|
932
|
-
function copyNoteEvent(note) {
|
|
933
|
-
const copy = {
|
|
934
|
-
pitch: withoutNegativeZero(note.pitch),
|
|
935
|
-
startBeat: withoutNegativeZero(note.startBeat),
|
|
936
|
-
durationBeat: withoutNegativeZero(note.durationBeat)
|
|
937
|
-
};
|
|
938
|
-
if (note.velocity !== void 0) {
|
|
939
|
-
copy.velocity = withoutNegativeZero(note.velocity);
|
|
940
|
-
}
|
|
941
|
-
if (note.articulation !== void 0) {
|
|
942
|
-
copy.articulation = note.articulation;
|
|
943
|
-
}
|
|
944
|
-
return copy;
|
|
945
|
-
}
|
|
946
|
-
function copyTimeSignature(ts, name) {
|
|
947
|
-
assertTimeSignature(ts, name);
|
|
948
|
-
const copy = { numerator: ts.numerator, denominator: ts.denominator };
|
|
949
|
-
if (ts.grouping !== void 0) {
|
|
950
|
-
copy.grouping = [...ts.grouping];
|
|
951
|
-
}
|
|
952
|
-
return copy;
|
|
953
|
-
}
|
|
954
|
-
function samePlain(value, other) {
|
|
955
|
-
if (Array.isArray(value) || Array.isArray(other)) {
|
|
956
|
-
return Array.isArray(value) && Array.isArray(other) && value.length === other.length && value.every((item, index) => samePlain(item, other[index]));
|
|
957
|
-
}
|
|
958
|
-
if (typeof value === "object" && value !== null && typeof other === "object" && other !== null) {
|
|
959
|
-
const mine = Object.entries(value);
|
|
960
|
-
const theirs = other;
|
|
961
|
-
return mine.length === Object.keys(theirs).length && mine.every(([key, item]) => key in theirs && samePlain(item, theirs[key]));
|
|
962
|
-
}
|
|
963
|
-
return value === other;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
1067
|
// src/model/timeline.ts
|
|
967
|
-
var GIVEN_KEY_CONFIDENCE = 1;
|
|
968
1068
|
function copySegment(segment) {
|
|
1069
|
+
assertDataObject(segment, "timeline segment");
|
|
969
1070
|
return {
|
|
970
1071
|
startBeat: assertFiniteNumber(segment.startBeat, "timeline segment startBeat"),
|
|
971
1072
|
endBeat: assertFiniteNumber(segment.endBeat, "timeline segment endBeat"),
|
|
@@ -976,16 +1077,20 @@ function copySegment(segment) {
|
|
|
976
1077
|
};
|
|
977
1078
|
}
|
|
978
1079
|
function copyKeyRegion(region) {
|
|
1080
|
+
assertDataObject(region, "key region");
|
|
979
1081
|
const copy = {
|
|
980
1082
|
startBeat: assertFiniteNumber(region.startBeat, "key region startBeat"),
|
|
981
1083
|
endBeat: assertFiniteNumber(region.endBeat, "key region endBeat"),
|
|
982
|
-
key
|
|
1084
|
+
// Read through the key itself, so a tonic that does not spell the region's
|
|
1085
|
+
// own root is refused here rather than spelling later notes wrongly.
|
|
1086
|
+
key: keyIdentity(toKey(region.key)),
|
|
983
1087
|
confidence: assertFiniteNumber(region.confidence, "key region confidence")
|
|
984
1088
|
};
|
|
985
1089
|
if (region.modulation !== void 0) {
|
|
986
1090
|
copy.modulation = region.modulation;
|
|
987
1091
|
}
|
|
988
1092
|
if (region.pivot !== void 0) {
|
|
1093
|
+
assertDataObject(region.pivot, "key region pivot");
|
|
989
1094
|
copy.pivot = {
|
|
990
1095
|
chord: new Chord(region.pivot.chord).toJSON(),
|
|
991
1096
|
romanFrom: region.pivot.romanFrom,
|
|
@@ -998,6 +1103,7 @@ function copyConfidence(confidence, segments) {
|
|
|
998
1103
|
if (confidence === void 0) {
|
|
999
1104
|
return [];
|
|
1000
1105
|
}
|
|
1106
|
+
assertDataArray(confidence, "timeline segmentConfidence");
|
|
1001
1107
|
if (confidence.length !== segments) {
|
|
1002
1108
|
throw new InvalidInputError(
|
|
1003
1109
|
`timeline segmentConfidence must hold one value per segment; received ${confidence.length} for ${segments}`
|
|
@@ -1007,21 +1113,6 @@ function copyConfidence(confidence, segments) {
|
|
|
1007
1113
|
(value, index) => assertRange(value, 0, 1, `timeline segmentConfidence[${index}]`)
|
|
1008
1114
|
);
|
|
1009
1115
|
}
|
|
1010
|
-
function chordAtBeat(segments, beat) {
|
|
1011
|
-
assertFiniteNumber(beat, "timeline query beat");
|
|
1012
|
-
let low = 0;
|
|
1013
|
-
let high = segments.length;
|
|
1014
|
-
while (low < high) {
|
|
1015
|
-
const middle = Math.floor((low + high) / 2);
|
|
1016
|
-
if ((segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {
|
|
1017
|
-
low = middle + 1;
|
|
1018
|
-
} else {
|
|
1019
|
-
high = middle;
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
const candidate = segments[low - 1];
|
|
1023
|
-
return candidate !== void 0 && beat >= candidate.startBeat && beat < candidate.endBeat ? candidate.chord : null;
|
|
1024
|
-
}
|
|
1025
1116
|
function clipSpan(span, fromBeat, toBeat) {
|
|
1026
1117
|
const startBeat = Math.max(span.startBeat, fromBeat);
|
|
1027
1118
|
const endBeat = Math.min(span.endBeat, toBeat);
|
|
@@ -1041,14 +1132,19 @@ var Timeline = class _Timeline {
|
|
|
1041
1132
|
* the confidences do not run one per segment.
|
|
1042
1133
|
*/
|
|
1043
1134
|
constructor(data) {
|
|
1044
|
-
|
|
1135
|
+
assertDataObject(data, "timeline data");
|
|
1136
|
+
this.#segments = Object.freeze(
|
|
1137
|
+
assertDataArray(data.segments, "timeline segments").map(copySegment)
|
|
1138
|
+
);
|
|
1045
1139
|
this.#totalBeats = assertRange(
|
|
1046
1140
|
data.totalBeats,
|
|
1047
1141
|
0,
|
|
1048
1142
|
Number.MAX_SAFE_INTEGER,
|
|
1049
1143
|
"timeline totalBeats"
|
|
1050
1144
|
);
|
|
1051
|
-
this.#keys = Object.freeze(
|
|
1145
|
+
this.#keys = Object.freeze(
|
|
1146
|
+
assertDataArray(data.keys ?? [], "timeline keys").map(copyKeyRegion)
|
|
1147
|
+
);
|
|
1052
1148
|
this.#segmentConfidence = Object.freeze(
|
|
1053
1149
|
copyConfidence(data.segmentConfidence, this.#segments.length)
|
|
1054
1150
|
);
|
|
@@ -1073,8 +1169,8 @@ var Timeline = class _Timeline {
|
|
|
1073
1169
|
// so a chord placed in a pickup is covered by the key it sounds in.
|
|
1074
1170
|
startBeat: timeline.segments[0]?.startBeat ?? 0,
|
|
1075
1171
|
endBeat: totalBeats,
|
|
1076
|
-
key:
|
|
1077
|
-
confidence:
|
|
1172
|
+
key: keyIdentity(toKey(key)),
|
|
1173
|
+
confidence: STATED_KEY_CONFIDENCE
|
|
1078
1174
|
}
|
|
1079
1175
|
];
|
|
1080
1176
|
}
|
|
@@ -1096,8 +1192,10 @@ var Timeline = class _Timeline {
|
|
|
1096
1192
|
* Place a progression's chords on a regular grid.
|
|
1097
1193
|
*
|
|
1098
1194
|
* The chords cross over as harmony alone: a spelling one of them carried is
|
|
1099
|
-
* dropped,
|
|
1100
|
-
*
|
|
1195
|
+
* dropped, since a chord placed on the grid is a `ChordSpan` and a span
|
|
1196
|
+
* carries none. A carried key becomes the one key region under the span,
|
|
1197
|
+
* spelled tonic and scale form included — an Ab minor comes back Ab minor,
|
|
1198
|
+
* which is what the numerals over the region are read from.
|
|
1101
1199
|
*
|
|
1102
1200
|
* @param progression The chords, in order.
|
|
1103
1201
|
* @param beatsEach How long each chord sounds.
|
|
@@ -1151,9 +1249,18 @@ var Timeline = class _Timeline {
|
|
|
1151
1249
|
/** The key held longest across the span, when the analysis found one. */
|
|
1152
1250
|
get key() {
|
|
1153
1251
|
const prevailing = prevailingKeyOf(this.#keys);
|
|
1154
|
-
return prevailing === null ? void 0 :
|
|
1252
|
+
return prevailing === null ? void 0 : toKey(prevailing);
|
|
1155
1253
|
}
|
|
1156
|
-
/**
|
|
1254
|
+
/**
|
|
1255
|
+
* Every key region under the span, in time order.
|
|
1256
|
+
*
|
|
1257
|
+
* These are the regions the timeline was built with: the ones
|
|
1258
|
+
* {@link Timeline.fromNotes} read off the notes, or the single region a key
|
|
1259
|
+
* given to {@link Timeline.fromChords} describes. A timeline holding chords
|
|
1260
|
+
* alone carries none until it is asked to read them —
|
|
1261
|
+
* {@link Timeline.modulations} is where the chords are searched for the keys
|
|
1262
|
+
* they imply.
|
|
1263
|
+
*/
|
|
1157
1264
|
get keys() {
|
|
1158
1265
|
return this.#keys.map(copyKeyRegion);
|
|
1159
1266
|
}
|
|
@@ -1228,6 +1335,38 @@ var Timeline = class _Timeline {
|
|
|
1228
1335
|
cadences() {
|
|
1229
1336
|
return detectCadences(this.chordTimeline, this.#keyContext());
|
|
1230
1337
|
}
|
|
1338
|
+
/**
|
|
1339
|
+
* The keys the chords themselves imply, in time order.
|
|
1340
|
+
*
|
|
1341
|
+
* The chord route to key regions, for a timeline that holds harmony without
|
|
1342
|
+
* the notes it was played from — a lead sheet, or a progression laid out in
|
|
1343
|
+
* time. A chord argues for a key far more strongly than its three or four
|
|
1344
|
+
* pitch classes do, so a modulation is read off the chords rather than off a
|
|
1345
|
+
* pitch-class profile, and the chord that reads in both keys is reported as
|
|
1346
|
+
* the pivot the modulation turned on.
|
|
1347
|
+
*
|
|
1348
|
+
* The regions are searched for here rather than kept on the timeline:
|
|
1349
|
+
* {@link Timeline.keys} answers with what the timeline was built with, which
|
|
1350
|
+
* for placed chords is the key the caller stated or nothing at all.
|
|
1351
|
+
*
|
|
1352
|
+
* @param opts Analysis options; see {@link KeyTimelineOptions}.
|
|
1353
|
+
* @returns The key regions the chords imply; empty when there are no chords.
|
|
1354
|
+
* @example
|
|
1355
|
+
* ```ts
|
|
1356
|
+
* import { Chord, Key, Progression, Timeline } from '@libraz/libcantus';
|
|
1357
|
+
* const progression = new Progression(
|
|
1358
|
+
* [Chord.parse('C'), Chord.parse('G7'), Chord.parse('C')],
|
|
1359
|
+
* Key.major('C'),
|
|
1360
|
+
* );
|
|
1361
|
+
* Timeline.fromProgression(progression, 4).modulations().length; // 1
|
|
1362
|
+
* ```
|
|
1363
|
+
*/
|
|
1364
|
+
modulations(opts) {
|
|
1365
|
+
return detectModulations(this.#copySegments(), {
|
|
1366
|
+
totalBeats: this.#totalBeats,
|
|
1367
|
+
...opts
|
|
1368
|
+
}).map(copyKeyRegion);
|
|
1369
|
+
}
|
|
1231
1370
|
/**
|
|
1232
1371
|
* The chords as roman numerals in a key.
|
|
1233
1372
|
*
|
|
@@ -1240,8 +1379,9 @@ var Timeline = class _Timeline {
|
|
|
1240
1379
|
* @throws If no key is given and the timeline carries none.
|
|
1241
1380
|
*/
|
|
1242
1381
|
roman(key, opts) {
|
|
1243
|
-
|
|
1244
|
-
const
|
|
1382
|
+
assertKeyArgument(key, "timeline key");
|
|
1383
|
+
const given = key === void 0 ? void 0 : resolveKey(key);
|
|
1384
|
+
const keyAt = given === void 0 ? this.#resolvedKeyAt() : () => given;
|
|
1245
1385
|
return this.#segments.map((segment) => ({
|
|
1246
1386
|
startBeat: segment.startBeat,
|
|
1247
1387
|
endBeat: segment.endBeat,
|
|
@@ -1369,16 +1509,19 @@ var Timeline = class _Timeline {
|
|
|
1369
1509
|
/** The key in force at a beat, or undefined when the timeline knows none. */
|
|
1370
1510
|
#keyAt(beat) {
|
|
1371
1511
|
const prevailing = prevailingKeyOf(this.#keys);
|
|
1372
|
-
return prevailing === null ? void 0 :
|
|
1512
|
+
return prevailing === null ? void 0 : toKey(keyLookup(this.#keys, prevailing)(beat));
|
|
1373
1513
|
}
|
|
1374
1514
|
/**
|
|
1375
|
-
* The key every beat of the timeline is read against.
|
|
1515
|
+
* The key every beat of the timeline is read against, spelling and all.
|
|
1376
1516
|
*
|
|
1377
1517
|
* A lookup rather than one key, so a timeline that modulates has each chord
|
|
1378
|
-
*
|
|
1379
|
-
*
|
|
1518
|
+
* read in the key it sounds in; beats outside every region fall back to the
|
|
1519
|
+
* prevailing key. The regions carry a spelled tonic and a scale form, and
|
|
1520
|
+
* this hands them on as they are: an Ab minor is where a German sixth is
|
|
1521
|
+
* named from — `Fb Ab Cb D` — and reading it through its pitch classes alone
|
|
1522
|
+
* would number it as the G# minor those read best as.
|
|
1380
1523
|
*/
|
|
1381
|
-
#
|
|
1524
|
+
#resolvedKeyAt() {
|
|
1382
1525
|
const prevailing = prevailingKeyOf(this.#keys);
|
|
1383
1526
|
if (prevailing === null) {
|
|
1384
1527
|
throw new InvalidInputError(
|
|
@@ -1387,6 +1530,18 @@ var Timeline = class _Timeline {
|
|
|
1387
1530
|
}
|
|
1388
1531
|
return keyLookup(this.#keys, prevailing);
|
|
1389
1532
|
}
|
|
1533
|
+
/**
|
|
1534
|
+
* The pitch classes of that key, for the readings that want them.
|
|
1535
|
+
*
|
|
1536
|
+
* A reduction and a cadence are read from which notes are in the key rather
|
|
1537
|
+
* than from how the key is written, so they take the scale; the reduction is
|
|
1538
|
+
* where the drop is stated rather than in the lookup, which is what let the
|
|
1539
|
+
* numerals lose the spelling by sharing it.
|
|
1540
|
+
*/
|
|
1541
|
+
#keyContext() {
|
|
1542
|
+
const keyAt = this.#resolvedKeyAt();
|
|
1543
|
+
return (beat) => scaleOf(keyAt(beat));
|
|
1544
|
+
}
|
|
1390
1545
|
/** Move every chord and every key region by the same step. */
|
|
1391
1546
|
#moved(moveChord, moveKey) {
|
|
1392
1547
|
const segments = this.#segments.map((segment) => ({
|
|
@@ -1395,7 +1550,10 @@ var Timeline = class _Timeline {
|
|
|
1395
1550
|
chord: moveChord(new Chord(segment.chord)).toJSON()
|
|
1396
1551
|
}));
|
|
1397
1552
|
const keys = this.#keys.map((region) => {
|
|
1398
|
-
const moved = {
|
|
1553
|
+
const moved = {
|
|
1554
|
+
...region,
|
|
1555
|
+
key: keyIdentity(moveKey(toKey(region.key)))
|
|
1556
|
+
};
|
|
1399
1557
|
if (region.pivot !== void 0) {
|
|
1400
1558
|
moved.pivot = {
|
|
1401
1559
|
...region.pivot,
|
|
@@ -1420,11 +1578,14 @@ var Progression = class _Progression {
|
|
|
1420
1578
|
* Wrap a chord sequence.
|
|
1421
1579
|
*
|
|
1422
1580
|
* @param chords The chords in order; the array is copied.
|
|
1423
|
-
* @param key Optional key context for analysis methods
|
|
1581
|
+
* @param key Optional key context for analysis methods, in whatever form the
|
|
1582
|
+
* caller holds it: a key name, a plain key/scale, or a {@link Key}.
|
|
1424
1583
|
*/
|
|
1425
1584
|
constructor(chords, key) {
|
|
1426
|
-
|
|
1427
|
-
|
|
1585
|
+
assertKeyArgument(key, "progression key");
|
|
1586
|
+
const resolved = key === void 0 ? void 0 : toKey(key);
|
|
1587
|
+
this.#chords = Object.freeze(_Progression.#attachKey(chords, resolved));
|
|
1588
|
+
this.#key = resolved;
|
|
1428
1589
|
}
|
|
1429
1590
|
/**
|
|
1430
1591
|
* Attach the progression key to every chord member.
|
|
@@ -1437,7 +1598,8 @@ var Progression = class _Progression {
|
|
|
1437
1598
|
* number of times a key was attached.
|
|
1438
1599
|
*/
|
|
1439
1600
|
static #attachKey(chords, key) {
|
|
1440
|
-
|
|
1601
|
+
const given = assertDataObjects(chords, "progression chords");
|
|
1602
|
+
return key === void 0 ? [...given] : given.map((chord) => chord.withKey(key));
|
|
1441
1603
|
}
|
|
1442
1604
|
/**
|
|
1443
1605
|
* Start from a chord sequence, matching the `of` factory on the other
|
|
@@ -1474,12 +1636,14 @@ var Progression = class _Progression {
|
|
|
1474
1636
|
* ```ts
|
|
1475
1637
|
* import { generateProgression, Key, Progression } from '@libraz/libcantus';
|
|
1476
1638
|
* const key = Key.major('C');
|
|
1477
|
-
* const spans = generateProgression({ key
|
|
1639
|
+
* const spans = generateProgression({ key, style: 'dance', bars: 4 });
|
|
1478
1640
|
* Progression.fromSpans(spans, key).roman();
|
|
1479
1641
|
* ```
|
|
1480
1642
|
*/
|
|
1481
1643
|
static fromSpans(spans, key) {
|
|
1482
|
-
const chords = spans
|
|
1644
|
+
const chords = assertDataObjects(spans, "progression spans").map(
|
|
1645
|
+
(span) => Chord.fromData(chordFromSpan(span))
|
|
1646
|
+
);
|
|
1483
1647
|
return new _Progression(chords, key);
|
|
1484
1648
|
}
|
|
1485
1649
|
/**
|
|
@@ -1489,8 +1653,11 @@ var Progression = class _Progression {
|
|
|
1489
1653
|
* @returns The progression.
|
|
1490
1654
|
*/
|
|
1491
1655
|
static fromJSON(data) {
|
|
1656
|
+
assertDataObject(data, "progression data");
|
|
1492
1657
|
const key = data.key === void 0 ? void 0 : Key.fromJSON(data.key);
|
|
1493
|
-
const chords = data.chords
|
|
1658
|
+
const chords = assertDataArray(data.chords, "progression chords").map(
|
|
1659
|
+
(chord) => Chord.fromJSON(chord)
|
|
1660
|
+
);
|
|
1494
1661
|
return key === void 0 ? new _Progression(chords) : new _Progression(chords, key);
|
|
1495
1662
|
}
|
|
1496
1663
|
/**
|
|
@@ -1687,7 +1854,8 @@ var Progression = class _Progression {
|
|
|
1687
1854
|
/**
|
|
1688
1855
|
* A copy of this progression carrying the given key context.
|
|
1689
1856
|
*
|
|
1690
|
-
* @param key The key context to attach
|
|
1857
|
+
* @param key The key context to attach; a key name, a plain key/scale, or a
|
|
1858
|
+
* {@link Key}.
|
|
1691
1859
|
* @returns The new progression.
|
|
1692
1860
|
*/
|
|
1693
1861
|
withKey(key) {
|
|
@@ -1701,7 +1869,7 @@ var Progression = class _Progression {
|
|
|
1701
1869
|
* @throws If any chord admits no voicing within the given ranges.
|
|
1702
1870
|
*/
|
|
1703
1871
|
voice(opts) {
|
|
1704
|
-
const key = opts?.key ?? this.#key
|
|
1872
|
+
const key = opts?.key ?? this.#key;
|
|
1705
1873
|
return voiceProgression(
|
|
1706
1874
|
this.#chords.map((chord) => chord.data),
|
|
1707
1875
|
key === void 0 ? opts : { ...opts, key }
|
|
@@ -1710,7 +1878,8 @@ var Progression = class _Progression {
|
|
|
1710
1878
|
/**
|
|
1711
1879
|
* The Roman numeral of each chord in a key.
|
|
1712
1880
|
*
|
|
1713
|
-
* @param key Key to analyze in
|
|
1881
|
+
* @param key Key to analyze in, as a key name, a plain key/scale, or a
|
|
1882
|
+
* {@link Key}; falls back to the carried context.
|
|
1714
1883
|
* @param opts Applied-numeral rendering options.
|
|
1715
1884
|
* @returns One numeral per chord.
|
|
1716
1885
|
* @throws If no key is given and none is carried.
|
|
@@ -1722,7 +1891,8 @@ var Progression = class _Progression {
|
|
|
1722
1891
|
/**
|
|
1723
1892
|
* The harmonic function of each chord in a key.
|
|
1724
1893
|
*
|
|
1725
|
-
* @param key Key to analyze in
|
|
1894
|
+
* @param key Key to analyze in, as a key name, a plain key/scale, or a
|
|
1895
|
+
* {@link Key}; falls back to the carried context.
|
|
1726
1896
|
* @returns One function per chord.
|
|
1727
1897
|
* @throws If no key is given and none is carried.
|
|
1728
1898
|
*/
|
|
@@ -1737,12 +1907,15 @@ var Progression = class _Progression {
|
|
|
1737
1907
|
* progression has fewer than two chords.
|
|
1738
1908
|
*
|
|
1739
1909
|
* The options reach both analyses: `applied` and `alternatives` go to every
|
|
1740
|
-
* chord, and `voicing` — the pitches
|
|
1741
|
-
* {@link Progression.voice} produces them — to the
|
|
1742
|
-
* a perfect authentic cadence from an imperfect
|
|
1743
|
-
* soprano.
|
|
1744
|
-
*
|
|
1745
|
-
*
|
|
1910
|
+
* chord, and `voicing` — the pitches sounding under the progression, one
|
|
1911
|
+
* voicing per chord as {@link Progression.voice} produces them — to the
|
|
1912
|
+
* cadence, which cannot tell a perfect authentic cadence from an imperfect
|
|
1913
|
+
* one without knowing the soprano. It is the same shape
|
|
1914
|
+
* {@link Progression.cadences} takes, and the closing pair is cut from it
|
|
1915
|
+
* here; a two-element array is read as that pair itself.
|
|
1916
|
+
*
|
|
1917
|
+
* @param key Key to analyze in, as a key name, a plain key/scale, or a
|
|
1918
|
+
* {@link Key}; falls back to the carried context.
|
|
1746
1919
|
* @param opts Applied-numeral rendering options, `alternatives` for the
|
|
1747
1920
|
* readings both analyses turned down, and `voicing` for the cadence's
|
|
1748
1921
|
* voice-leading detail; see {@link AnalyzeChordOptions} and
|
|
@@ -1753,19 +1926,41 @@ var Progression = class _Progression {
|
|
|
1753
1926
|
* ```ts
|
|
1754
1927
|
* import { Chord, Key, Progression } from '@libraz/libcantus';
|
|
1755
1928
|
* const progression = new Progression([Chord.parse('G7'), Chord.parse('C')], Key.major('C'));
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
1758
|
-
* ?.strength; // 'perfect'
|
|
1929
|
+
* progression.analyze(undefined, { voicing: progression.voice() }).cadence?.strength;
|
|
1930
|
+
* // 'perfect'
|
|
1759
1931
|
* ```
|
|
1760
1932
|
*/
|
|
1761
1933
|
analyze(key, opts) {
|
|
1762
1934
|
const resolved = this.#resolveKey(key);
|
|
1763
1935
|
const chords = this.#chords.map((chord) => chord.analyze(resolved, opts));
|
|
1936
|
+
const { voicing, ...cadenceOpts } = opts ?? {};
|
|
1764
1937
|
const from = this.#chords[this.#chords.length - 2];
|
|
1765
1938
|
const to = this.#chords[this.#chords.length - 1];
|
|
1766
|
-
const
|
|
1939
|
+
const approach = this.#chords[this.#chords.length - 3];
|
|
1940
|
+
const cadence = from !== void 0 && to !== void 0 ? detectCadence(from.data, to.data, resolved, {
|
|
1941
|
+
...cadenceOpts,
|
|
1942
|
+
...approach === void 0 ? {} : { approach: approach.data },
|
|
1943
|
+
...voicing === void 0 ? {} : { voicing: this.#closingVoicing(voicing) }
|
|
1944
|
+
}) : null;
|
|
1767
1945
|
return { chords, cadence };
|
|
1768
1946
|
}
|
|
1947
|
+
/**
|
|
1948
|
+
* The two voicings of the closing pair, cut from the progression's own.
|
|
1949
|
+
*
|
|
1950
|
+
* `cadences` takes one voicing per chord and gives each pair its own two, so
|
|
1951
|
+
* this entry takes that same array rather than a shape only it understands: a
|
|
1952
|
+
* caller holding what `voice()` returned can hand it to either method. A
|
|
1953
|
+
* two-element array is the closing pair already, which is what a progression
|
|
1954
|
+
* of two chords produces either way.
|
|
1955
|
+
*/
|
|
1956
|
+
#closingVoicing(voicing) {
|
|
1957
|
+
if (voicing.length === this.#chords.length || voicing.length === 2) {
|
|
1958
|
+
return [voicing[voicing.length - 2] ?? [], voicing[voicing.length - 1] ?? []];
|
|
1959
|
+
}
|
|
1960
|
+
throw new InvalidInputError(
|
|
1961
|
+
`voicing must hold one voicing per chord (${this.#chords.length}) or the closing pair; received ${voicing.length}`
|
|
1962
|
+
);
|
|
1963
|
+
}
|
|
1769
1964
|
/**
|
|
1770
1965
|
* Choose one compatible scale for every chord, favoring smooth changes.
|
|
1771
1966
|
*
|
|
@@ -1786,7 +1981,8 @@ var Progression = class _Progression {
|
|
|
1786
1981
|
* The chord before each pair is supplied from the progression itself, so a
|
|
1787
1982
|
* cadential six-four is recognized as one wherever it stands.
|
|
1788
1983
|
*
|
|
1789
|
-
* @param key Key to analyze in
|
|
1984
|
+
* @param key Key to analyze in, as a key name, a plain key/scale, or a
|
|
1985
|
+
* {@link Key}; falls back to the carried context.
|
|
1790
1986
|
* @param opts `voicing` is the pitches sounding under the whole progression,
|
|
1791
1987
|
* one voicing per chord as {@link Progression.voice} produces them, from
|
|
1792
1988
|
* which each pair takes its own two — without it no authentic cadence can be
|
|
@@ -1815,12 +2011,12 @@ var Progression = class _Progression {
|
|
|
1815
2011
|
if (from === void 0 || to === void 0) {
|
|
1816
2012
|
continue;
|
|
1817
2013
|
}
|
|
1818
|
-
const approach = index >= 2 ? this.#chords[index - 2] : opts?.approach;
|
|
2014
|
+
const approach = index >= 2 ? this.#chords[index - 2]?.data : opts?.approach;
|
|
1819
2015
|
const pair = opts?.voicing;
|
|
1820
2016
|
found.push(
|
|
1821
|
-
detectCadence(from.data, to.data, resolved
|
|
2017
|
+
detectCadence(from.data, to.data, resolved, {
|
|
1822
2018
|
...opts?.alternatives === void 0 ? {} : { alternatives: opts.alternatives },
|
|
1823
|
-
...approach === void 0 ? {} : { approach
|
|
2019
|
+
...approach === void 0 ? {} : { approach },
|
|
1824
2020
|
...pair === void 0 ? {} : { voicing: [pair[index - 1] ?? [], pair[index] ?? []] }
|
|
1825
2021
|
})
|
|
1826
2022
|
);
|
|
@@ -1860,7 +2056,7 @@ var Progression = class _Progression {
|
|
|
1860
2056
|
);
|
|
1861
2057
|
}
|
|
1862
2058
|
const key = this.#resolveKey();
|
|
1863
|
-
const chosen = substituteChord(target.data, key
|
|
2059
|
+
const chosen = substituteChord(target.data, key, opts).find(
|
|
1864
2060
|
(candidate) => candidate.type === type
|
|
1865
2061
|
);
|
|
1866
2062
|
if (chosen === void 0) {
|
|
@@ -1943,7 +2139,8 @@ var Progression = class _Progression {
|
|
|
1943
2139
|
* a target in another mode — a relative, parallel, or modal key — is the key
|
|
1944
2140
|
* the progression is then analyzed in.
|
|
1945
2141
|
*
|
|
1946
|
-
* @param target The key the transposed progression should be in
|
|
2142
|
+
* @param target The key the transposed progression should be in, as a key
|
|
2143
|
+
* name, a plain key/scale, or a {@link Key}.
|
|
1947
2144
|
* @returns The transposed progression, carrying `target` as its key.
|
|
1948
2145
|
* @throws If the progression carries no key context to measure from.
|
|
1949
2146
|
* @example
|
|
@@ -1960,9 +2157,11 @@ var Progression = class _Progression {
|
|
|
1960
2157
|
"progression has no key context; attach one with withKey() before transposing to another key"
|
|
1961
2158
|
);
|
|
1962
2159
|
}
|
|
1963
|
-
|
|
2160
|
+
assertKeyArgument(target, "progression key");
|
|
2161
|
+
const resolved = toKey(target);
|
|
2162
|
+
const interval = from.intervalTo(resolved);
|
|
1964
2163
|
const chords = this.#chords.map((chord) => chord.transposeBy(interval));
|
|
1965
|
-
return new _Progression(chords,
|
|
2164
|
+
return new _Progression(chords, resolved);
|
|
1966
2165
|
}
|
|
1967
2166
|
/**
|
|
1968
2167
|
* The chord symbols separated by spaces, so a template literal or a log line
|
|
@@ -1987,10 +2186,15 @@ var Progression = class _Progression {
|
|
|
1987
2186
|
key: this.#key?.toJSON()
|
|
1988
2187
|
};
|
|
1989
2188
|
}
|
|
1990
|
-
/**
|
|
2189
|
+
/**
|
|
2190
|
+
* Resolve the key for an analysis method: explicit first, then carried.
|
|
2191
|
+
*
|
|
2192
|
+
* Every method that takes a key comes through here, so the shape check and
|
|
2193
|
+
* the fallback to the carried context are the same wherever a key is read.
|
|
2194
|
+
*/
|
|
1991
2195
|
#resolveKey(key) {
|
|
1992
2196
|
assertKeyArgument(key, "progression key");
|
|
1993
|
-
const resolved = key
|
|
2197
|
+
const resolved = key === void 0 ? this.#key : toKey(key);
|
|
1994
2198
|
if (resolved === void 0) {
|
|
1995
2199
|
throw new InvalidInputError(
|
|
1996
2200
|
"progression has no key context; pass a Key or attach one with withKey()"
|
|
@@ -2004,13 +2208,6 @@ var Progression = class _Progression {
|
|
|
2004
2208
|
function hintMatches(hint, pc) {
|
|
2005
2209
|
return hint !== void 0 && pc !== void 0 && pitchClassOf(noteToPitchClass(hint)) === pitchClassOf(pc);
|
|
2006
2210
|
}
|
|
2007
|
-
function transposeHint(hint, semitones) {
|
|
2008
|
-
if (hint === void 0) {
|
|
2009
|
-
return void 0;
|
|
2010
|
-
}
|
|
2011
|
-
const moved = transposeNote(hint, semitones);
|
|
2012
|
-
return { letter: moved.letter, alter: moved.alter };
|
|
2013
|
-
}
|
|
2014
2211
|
function transposeHintByInterval(hint, interval) {
|
|
2015
2212
|
if (hint === void 0) {
|
|
2016
2213
|
return void 0;
|
|
@@ -2018,12 +2215,6 @@ function transposeHintByInterval(hint, interval) {
|
|
|
2018
2215
|
const moved = transposeByInterval(hint, interval);
|
|
2019
2216
|
return { letter: moved.letter, alter: moved.alter };
|
|
2020
2217
|
}
|
|
2021
|
-
function transposeToneHints(hints, semitones) {
|
|
2022
|
-
return hints?.map((hint) => {
|
|
2023
|
-
const moved = transposeNote(hint, semitones);
|
|
2024
|
-
return { letter: moved.letter, alter: moved.alter };
|
|
2025
|
-
});
|
|
2026
|
-
}
|
|
2027
2218
|
function transposeToneHintsByInterval(hints, interval) {
|
|
2028
2219
|
return hints?.map((hint) => {
|
|
2029
2220
|
const moved = transposeByInterval(hint, interval);
|
|
@@ -2031,18 +2222,22 @@ function transposeToneHintsByInterval(hints, interval) {
|
|
|
2031
2222
|
});
|
|
2032
2223
|
}
|
|
2033
2224
|
function copyChord(data) {
|
|
2225
|
+
assertDataObject(data, "chord data");
|
|
2226
|
+
assertChordQuality(data.quality);
|
|
2227
|
+
const rootPc = pitchClassOf(data.rootPc);
|
|
2034
2228
|
const copy = {
|
|
2035
|
-
rootPc
|
|
2229
|
+
rootPc,
|
|
2036
2230
|
quality: data.quality,
|
|
2037
2231
|
// Each offset is checked rather than copied blind: a chord holding a NaN
|
|
2038
2232
|
// interval voices, spells and formats as a chord that looks real, and the
|
|
2039
2233
|
// failure surfaces wherever the number is finally used.
|
|
2040
|
-
intervals: data.intervals.map(
|
|
2234
|
+
intervals: assertDataArray(data.intervals, "chord intervals").map(
|
|
2041
2235
|
(interval, index) => assertFiniteNumber(interval, `chord intervals[${index}]`)
|
|
2042
2236
|
)
|
|
2043
2237
|
};
|
|
2044
|
-
|
|
2045
|
-
|
|
2238
|
+
const bassPc = data.bassPc === void 0 ? void 0 : pitchClassOf(data.bassPc);
|
|
2239
|
+
if (bassPc !== void 0 && bassPc !== rootPc) {
|
|
2240
|
+
copy.bassPc = bassPc;
|
|
2046
2241
|
}
|
|
2047
2242
|
const tones = chordToneSpellings(data);
|
|
2048
2243
|
if (tones !== void 0) {
|
|
@@ -2051,7 +2246,7 @@ function copyChord(data) {
|
|
|
2051
2246
|
if (hintMatches(data.rootSpelling, data.rootPc) && data.rootSpelling !== void 0) {
|
|
2052
2247
|
copy.rootSpelling = { letter: data.rootSpelling.letter, alter: data.rootSpelling.alter };
|
|
2053
2248
|
}
|
|
2054
|
-
if (hintMatches(data.bassSpelling,
|
|
2249
|
+
if (hintMatches(data.bassSpelling, copy.bassPc) && data.bassSpelling !== void 0) {
|
|
2055
2250
|
copy.bassSpelling = { letter: data.bassSpelling.letter, alter: data.bassSpelling.alter };
|
|
2056
2251
|
}
|
|
2057
2252
|
return copy;
|
|
@@ -2072,7 +2267,7 @@ function deriveBassSpelling(chord, key) {
|
|
|
2072
2267
|
if (key === void 0) {
|
|
2073
2268
|
return void 0;
|
|
2074
2269
|
}
|
|
2075
|
-
const spelled = spellPitchClass(bassPc, key.tonic.data, key
|
|
2270
|
+
const spelled = spellPitchClass(bassPc, key.tonic.data, key);
|
|
2076
2271
|
return { letter: spelled.letter, alter: spelled.alter };
|
|
2077
2272
|
}
|
|
2078
2273
|
var Chord = class _Chord {
|
|
@@ -2083,11 +2278,13 @@ var Chord = class _Chord {
|
|
|
2083
2278
|
* Wrap a plain chord object.
|
|
2084
2279
|
*
|
|
2085
2280
|
* @param data The chord; it is copied, never retained or mutated.
|
|
2086
|
-
* @param key Optional key context for analysis methods
|
|
2281
|
+
* @param key Optional key context for analysis methods, in whatever form the
|
|
2282
|
+
* caller holds it: a key name, a plain key/scale, or a {@link Key}.
|
|
2087
2283
|
*/
|
|
2088
2284
|
constructor(data, key) {
|
|
2089
2285
|
this.#given = copyChord(data);
|
|
2090
|
-
|
|
2286
|
+
assertKeyArgument(key, "chord key");
|
|
2287
|
+
this.#key = key === void 0 ? void 0 : toKey(key);
|
|
2091
2288
|
}
|
|
2092
2289
|
/**
|
|
2093
2290
|
* The chord data with any missing spelling hint filled in from the key.
|
|
@@ -2103,7 +2300,7 @@ var Chord = class _Chord {
|
|
|
2103
2300
|
#dataWithKey(key) {
|
|
2104
2301
|
const out = copyChord(this.#given);
|
|
2105
2302
|
if (out.rootSpelling === void 0 && key !== void 0) {
|
|
2106
|
-
out.rootSpelling = spellPitchClass(out.rootPc, key.tonic.data, key
|
|
2303
|
+
out.rootSpelling = spellPitchClass(out.rootPc, key.tonic.data, key);
|
|
2107
2304
|
}
|
|
2108
2305
|
if (out.bassSpelling === void 0) {
|
|
2109
2306
|
const bass = deriveBassSpelling(out, key);
|
|
@@ -2188,11 +2385,11 @@ var Chord = class _Chord {
|
|
|
2188
2385
|
* ```ts
|
|
2189
2386
|
* import { Chord } from '@libraz/libcantus';
|
|
2190
2387
|
* Chord.fromFiguredBass('B', '6', 'C major').symbol(); // 'G/B'
|
|
2191
|
-
* Chord.fromFiguredBass('G', '
|
|
2388
|
+
* Chord.fromFiguredBass('G', 'n', 'C minor').symbol(); // 'G'
|
|
2192
2389
|
* ```
|
|
2193
2390
|
*/
|
|
2194
2391
|
static fromFiguredBass(bass, figures, key) {
|
|
2195
|
-
return new _Chord(realizeFiguredBass(toNoteData(bass), figures,
|
|
2392
|
+
return new _Chord(realizeFiguredBass(toNoteData(bass), figures, key));
|
|
2196
2393
|
}
|
|
2197
2394
|
/**
|
|
2198
2395
|
* Parse a lead-sheet chord symbol (e.g. `'Cmaj7'`, `'F#m7b5'`, `'C/G'`).
|
|
@@ -2344,7 +2541,8 @@ var Chord = class _Chord {
|
|
|
2344
2541
|
* during a modulation does not keep the old key's letters and the order of
|
|
2345
2542
|
* `withKey` calls does not affect the result.
|
|
2346
2543
|
*
|
|
2347
|
-
* @param key The key context to attach
|
|
2544
|
+
* @param key The key context to attach; a key name, a plain key/scale, or a
|
|
2545
|
+
* {@link Key}.
|
|
2348
2546
|
* @returns The new chord.
|
|
2349
2547
|
*/
|
|
2350
2548
|
withKey(key) {
|
|
@@ -2412,13 +2610,14 @@ var Chord = class _Chord {
|
|
|
2412
2610
|
/**
|
|
2413
2611
|
* The chord's Roman numeral in a key.
|
|
2414
2612
|
*
|
|
2415
|
-
* @param key Key to analyze in
|
|
2613
|
+
* @param key Key to analyze in, as a key name, a plain key/scale, or a
|
|
2614
|
+
* {@link Key}; falls back to the carried context.
|
|
2416
2615
|
* @param opts Applied-numeral rendering options.
|
|
2417
2616
|
* @returns The Roman numeral string.
|
|
2418
2617
|
* @throws If no key is given and none is carried.
|
|
2419
2618
|
*/
|
|
2420
2619
|
roman(key, opts) {
|
|
2421
|
-
return chordToRoman(this.#data, this.#resolveKey(key)
|
|
2620
|
+
return chordToRoman(this.#data, this.#resolveKey(key), opts);
|
|
2422
2621
|
}
|
|
2423
2622
|
/**
|
|
2424
2623
|
* The chord's Roman numeral together with the reasoning behind it: the degree
|
|
@@ -2446,23 +2645,25 @@ var Chord = class _Chord {
|
|
|
2446
2645
|
* ```
|
|
2447
2646
|
*/
|
|
2448
2647
|
explain(key, opts) {
|
|
2449
|
-
return explainRoman(this.#data, this.#
|
|
2648
|
+
return explainRoman(this.#data, this.#resolveKey(key), opts);
|
|
2450
2649
|
}
|
|
2451
2650
|
/**
|
|
2452
2651
|
* The chord's harmonic function (tonic / subdominant / dominant) in a key.
|
|
2453
2652
|
*
|
|
2454
|
-
* @param key Key to analyze in
|
|
2653
|
+
* @param key Key to analyze in, as a key name, a plain key/scale, or a
|
|
2654
|
+
* {@link Key}; falls back to the carried context.
|
|
2455
2655
|
* @returns The harmonic function.
|
|
2456
2656
|
* @throws If no key is given and none is carried.
|
|
2457
2657
|
*/
|
|
2458
2658
|
function(key) {
|
|
2459
|
-
return functionOf(this.#data, this.#resolveKey(key)
|
|
2659
|
+
return functionOf(this.#data, this.#resolveKey(key));
|
|
2460
2660
|
}
|
|
2461
2661
|
/**
|
|
2462
2662
|
* Full functional analysis: function, borrowing, Roman numeral, and the
|
|
2463
2663
|
* rationale behind them.
|
|
2464
2664
|
*
|
|
2465
|
-
* @param key Key to analyze in
|
|
2665
|
+
* @param key Key to analyze in, as a key name, a plain key/scale, or a
|
|
2666
|
+
* {@link Key}; falls back to the carried context.
|
|
2466
2667
|
* @param opts Applied-numeral rendering options, plus `alternatives` to
|
|
2467
2668
|
* collect the readings this analysis turned down; see
|
|
2468
2669
|
* {@link AnalyzeChordOptions}.
|
|
@@ -2475,27 +2676,29 @@ var Chord = class _Chord {
|
|
|
2475
2676
|
* ```
|
|
2476
2677
|
*/
|
|
2477
2678
|
analyze(key, opts) {
|
|
2478
|
-
return analyzeChord(this.#data, this.#resolveKey(key)
|
|
2679
|
+
return analyzeChord(this.#data, this.#resolveKey(key), opts);
|
|
2479
2680
|
}
|
|
2480
2681
|
/**
|
|
2481
2682
|
* Whether the chord is borrowed from the parallel mode (modal interchange).
|
|
2482
2683
|
*
|
|
2483
|
-
* @param key Key to analyze in
|
|
2684
|
+
* @param key Key to analyze in, as a key name, a plain key/scale, or a
|
|
2685
|
+
* {@link Key}; falls back to the carried context.
|
|
2484
2686
|
* @returns True if the chord is borrowed.
|
|
2485
2687
|
* @throws If no key is given and none is carried.
|
|
2486
2688
|
*/
|
|
2487
2689
|
isBorrowed(key) {
|
|
2488
|
-
return isBorrowedChord(this.#data, this.#resolveKey(key)
|
|
2690
|
+
return isBorrowedChord(this.#data, this.#resolveKey(key));
|
|
2489
2691
|
}
|
|
2490
2692
|
/**
|
|
2491
2693
|
* The origin of a non-diatonic chord (parallel mode or Neapolitan), or null.
|
|
2492
2694
|
*
|
|
2493
|
-
* @param key Key to analyze in
|
|
2695
|
+
* @param key Key to analyze in, as a key name, a plain key/scale, or a
|
|
2696
|
+
* {@link Key}; falls back to the carried context.
|
|
2494
2697
|
* @returns The borrowing source, or null.
|
|
2495
2698
|
* @throws If no key is given and none is carried.
|
|
2496
2699
|
*/
|
|
2497
2700
|
borrowedSource(key) {
|
|
2498
|
-
return borrowedSource(this.#data, this.#resolveKey(key)
|
|
2701
|
+
return borrowedSource(this.#data, this.#resolveKey(key));
|
|
2499
2702
|
}
|
|
2500
2703
|
/**
|
|
2501
2704
|
* The chord written as the figures a bass would carry under it: `6`, `64`,
|
|
@@ -2520,7 +2723,7 @@ var Chord = class _Chord {
|
|
|
2520
2723
|
* ```
|
|
2521
2724
|
*/
|
|
2522
2725
|
figuredBass(key) {
|
|
2523
|
-
return figuredBassOf(this.#data, this.#
|
|
2726
|
+
return figuredBassOf(this.#data, this.#resolveKey(key));
|
|
2524
2727
|
}
|
|
2525
2728
|
/**
|
|
2526
2729
|
* The chords that can stand in for this one in a key: its tritone substitute,
|
|
@@ -2547,7 +2750,7 @@ var Chord = class _Chord {
|
|
|
2547
2750
|
* ```
|
|
2548
2751
|
*/
|
|
2549
2752
|
substitutions(key, opts) {
|
|
2550
|
-
return substituteChord(this.#data, this.#
|
|
2753
|
+
return substituteChord(this.#data, this.#resolveKey(key), opts);
|
|
2551
2754
|
}
|
|
2552
2755
|
/**
|
|
2553
2756
|
* The modal-interchange palette of the chord's key: the parallel mode's
|
|
@@ -2572,7 +2775,7 @@ var Chord = class _Chord {
|
|
|
2572
2775
|
* ```
|
|
2573
2776
|
*/
|
|
2574
2777
|
modalInterchange(key) {
|
|
2575
|
-
return modalInterchangePalette(this.#
|
|
2778
|
+
return modalInterchangePalette(this.#resolveKey(key));
|
|
2576
2779
|
}
|
|
2577
2780
|
/**
|
|
2578
2781
|
* The chord rendered as a lead-sheet symbol (e.g. `'Cmaj7'`, `'F#m7'`, `'C/G'`).
|
|
@@ -2600,7 +2803,7 @@ var Chord = class _Chord {
|
|
|
2600
2803
|
* @throws If no voicing fits the given ranges.
|
|
2601
2804
|
*/
|
|
2602
2805
|
voice(opts) {
|
|
2603
|
-
const key = opts?.key ?? this.#key
|
|
2806
|
+
const key = opts?.key ?? this.#key;
|
|
2604
2807
|
return voiceChord(this.#data, key === void 0 ? opts : { ...opts, key });
|
|
2605
2808
|
}
|
|
2606
2809
|
/**
|
|
@@ -2617,14 +2820,14 @@ var Chord = class _Chord {
|
|
|
2617
2820
|
* The negative-harmony mirror of the chord about the key's tonic–dominant
|
|
2618
2821
|
* axis (major becomes minor and vice versa).
|
|
2619
2822
|
*
|
|
2620
|
-
* @param key Key providing the reflection axis
|
|
2621
|
-
* context.
|
|
2823
|
+
* @param key Key providing the reflection axis, as a key name, a plain
|
|
2824
|
+
* key/scale, or a {@link Key}; falls back to the carried context.
|
|
2622
2825
|
* @returns The mirrored chord, keeping any key context.
|
|
2623
2826
|
* @throws If no key is given and none is carried.
|
|
2624
2827
|
*/
|
|
2625
2828
|
negativeHarmony(key) {
|
|
2626
2829
|
const resolved = this.#resolveKey(key);
|
|
2627
|
-
return new _Chord(negativeHarmonyMirror(this.#data, resolved
|
|
2830
|
+
return new _Chord(negativeHarmonyMirror(this.#data, resolved), resolved);
|
|
2628
2831
|
}
|
|
2629
2832
|
/**
|
|
2630
2833
|
* The V7 that tonicizes this chord: a dominant seventh a perfect fifth above
|
|
@@ -2690,9 +2893,10 @@ var Chord = class _Chord {
|
|
|
2690
2893
|
*/
|
|
2691
2894
|
transpose(semitones) {
|
|
2692
2895
|
const moved = transposeChord(this.#given, semitones);
|
|
2693
|
-
const rootSpelling =
|
|
2694
|
-
|
|
2695
|
-
|
|
2896
|
+
const { rootSpelling, bassSpelling, toneSpellings } = transposeChordSpellings(
|
|
2897
|
+
this.#given,
|
|
2898
|
+
semitones
|
|
2899
|
+
);
|
|
2696
2900
|
if (rootSpelling !== void 0) {
|
|
2697
2901
|
moved.rootSpelling = rootSpelling;
|
|
2698
2902
|
}
|
|
@@ -2744,6 +2948,10 @@ var Chord = class _Chord {
|
|
|
2744
2948
|
* The named scales that fit over this chord, best fit first, rooted on the
|
|
2745
2949
|
* chord root.
|
|
2746
2950
|
*
|
|
2951
|
+
* Only the scales of {@link NAMED_SCALES} are ranked: chord-scale theory is a
|
|
2952
|
+
* Western practice, so the scales of {@link WORLD_SCALES} are left out rather
|
|
2953
|
+
* than offered as answers over a chord.
|
|
2954
|
+
*
|
|
2747
2955
|
* @returns The matching scales.
|
|
2748
2956
|
*/
|
|
2749
2957
|
scales() {
|
|
@@ -2775,12 +2983,16 @@ var Chord = class _Chord {
|
|
|
2775
2983
|
* The available tensions (usable non-chord, non-avoid scale tones) of a
|
|
2776
2984
|
* scale over this chord.
|
|
2777
2985
|
*
|
|
2778
|
-
* @param scaleName A
|
|
2986
|
+
* @param scaleName A scale of {@link NAMED_SCALES}, or one of its aliases,
|
|
2987
|
+
* rooted on the chord root. The scales of {@link WORLD_SCALES} are refused
|
|
2988
|
+
* here as they are by {@link Chord.scales}: chord-scale theory is a Western
|
|
2989
|
+
* practice, and a scale from another tradition offered over a chord would
|
|
2990
|
+
* read as an answer while being a category error.
|
|
2779
2991
|
* @param opts Set `resolvesTo` to the plain chord this one resolves to, so a
|
|
2780
2992
|
* dominant resolving down a fifth onto a minor tonic takes the tensions
|
|
2781
2993
|
* that resolution makes available; see {@link AvailableTensionsOptions}.
|
|
2782
2994
|
* @returns Tension pitch classes, ascending in [0, 11].
|
|
2783
|
-
* @throws If `scaleName` is not
|
|
2995
|
+
* @throws If `scaleName` is not one of the named scales or their aliases.
|
|
2784
2996
|
* @example
|
|
2785
2997
|
* ```ts
|
|
2786
2998
|
* import { Chord } from '@libraz/libcantus';
|
|
@@ -2796,12 +3008,16 @@ var Chord = class _Chord {
|
|
|
2796
3008
|
* The avoid notes (scale tones a semitone above a chord tone) of a scale
|
|
2797
3009
|
* over this chord.
|
|
2798
3010
|
*
|
|
2799
|
-
* @param scaleName A
|
|
3011
|
+
* @param scaleName A scale of {@link NAMED_SCALES}, or one of its aliases,
|
|
3012
|
+
* rooted on the chord root. The scales of {@link WORLD_SCALES} are refused
|
|
3013
|
+
* here as they are by {@link Chord.scales}: chord-scale theory is a Western
|
|
3014
|
+
* practice, and a scale from another tradition offered over a chord would
|
|
3015
|
+
* read as an answer while being a category error.
|
|
2800
3016
|
* @param opts Set `use: 'melodic'` to judge a line rather than a voicing, so
|
|
2801
3017
|
* a tone a line may pass through is not counted; see
|
|
2802
3018
|
* {@link AvoidNotesOptions}.
|
|
2803
3019
|
* @returns Avoid-note pitch classes, ascending in [0, 11].
|
|
2804
|
-
* @throws If `scaleName` is not
|
|
3020
|
+
* @throws If `scaleName` is not one of the named scales or their aliases.
|
|
2805
3021
|
* @example
|
|
2806
3022
|
* ```ts
|
|
2807
3023
|
* import { Chord } from '@libraz/libcantus';
|
|
@@ -2824,15 +3040,18 @@ var Chord = class _Chord {
|
|
|
2824
3040
|
* are the notes the chord sounds — the same set {@link Chord.pitchClasses}
|
|
2825
3041
|
* reports.
|
|
2826
3042
|
*
|
|
2827
|
-
* @param key Key providing the spelled tonic
|
|
2828
|
-
*
|
|
3043
|
+
* @param key Key providing the spelled tonic, as a key name, a plain
|
|
3044
|
+
* key/scale, or a {@link Key} — the tonic its letters are spelled from is
|
|
3045
|
+
* derived from the key rather than asked of the caller. Falls back to the
|
|
3046
|
+
* carried context, then to the chord's own root spelling.
|
|
2829
3047
|
* @returns Spelled octave-less notes in the chord's own (tertian) order,
|
|
2830
3048
|
* with any foreign slash bass last.
|
|
2831
3049
|
* @throws If no key is given, none is carried, and the chord has no root
|
|
2832
3050
|
* spelling of its own.
|
|
2833
3051
|
*/
|
|
2834
3052
|
spell(key) {
|
|
2835
|
-
|
|
3053
|
+
assertKeyArgument(key, "chord key");
|
|
3054
|
+
const resolved = key === void 0 ? this.#key : toKey(key);
|
|
2836
3055
|
const data = this.#dataWithKey(resolved);
|
|
2837
3056
|
let tones;
|
|
2838
3057
|
if (resolved === void 0) {
|
|
@@ -2844,7 +3063,7 @@ var Chord = class _Chord {
|
|
|
2844
3063
|
}
|
|
2845
3064
|
tones = spellChordFromRoot(data, root);
|
|
2846
3065
|
} else {
|
|
2847
|
-
tones = spellChord(data, resolved.tonic.data, resolved
|
|
3066
|
+
tones = spellChord(data, resolved.tonic.data, resolved);
|
|
2848
3067
|
}
|
|
2849
3068
|
const bassPc = data.bassPc;
|
|
2850
3069
|
const bass = data.bassSpelling;
|
|
@@ -2913,16 +3132,14 @@ var Chord = class _Chord {
|
|
|
2913
3132
|
return this.symbol();
|
|
2914
3133
|
}
|
|
2915
3134
|
/**
|
|
2916
|
-
* Resolve the key
|
|
2917
|
-
*
|
|
3135
|
+
* Resolve the key for an analysis method: explicit first, then carried.
|
|
3136
|
+
*
|
|
3137
|
+
* Every method that takes a key comes through here, so the shape check and
|
|
3138
|
+
* the fallback to the carried context are the same wherever a key is read.
|
|
2918
3139
|
*/
|
|
2919
|
-
#resolveScale(key) {
|
|
2920
|
-
return key === void 0 ? this.#resolveKey().scale : toKeyScale(key);
|
|
2921
|
-
}
|
|
2922
|
-
/** Resolve the key for an analysis method: explicit first, then carried. */
|
|
2923
3140
|
#resolveKey(key) {
|
|
2924
3141
|
assertKeyArgument(key, "chord key");
|
|
2925
|
-
const resolved = key
|
|
3142
|
+
const resolved = key === void 0 ? this.#key : toKey(key);
|
|
2926
3143
|
if (resolved === void 0) {
|
|
2927
3144
|
throw new InvalidInputError(
|
|
2928
3145
|
"chord has no key context; pass a Key or attach one with withKey()"
|
|
@@ -2933,8 +3150,23 @@ var Chord = class _Chord {
|
|
|
2933
3150
|
};
|
|
2934
3151
|
|
|
2935
3152
|
// src/model/key.ts
|
|
2936
|
-
|
|
2937
|
-
|
|
3153
|
+
function detectedKeyMatch(match) {
|
|
3154
|
+
const key = { ...resolveKey(match.key), variant: match.variant };
|
|
3155
|
+
return { ...match, key: toKey(key) };
|
|
3156
|
+
}
|
|
3157
|
+
function scaleWord(mask, isMinor) {
|
|
3158
|
+
const name = scaleNameOfMask(mask);
|
|
3159
|
+
if (name === void 0) {
|
|
3160
|
+
return isMinor ? "minor" : "major";
|
|
3161
|
+
}
|
|
3162
|
+
if (name === "major") {
|
|
3163
|
+
return "major";
|
|
3164
|
+
}
|
|
3165
|
+
if (name === "naturalMinor") {
|
|
3166
|
+
return "minor";
|
|
3167
|
+
}
|
|
3168
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
|
|
3169
|
+
}
|
|
2938
3170
|
var HEPTATONIC_DEGREES = 7;
|
|
2939
3171
|
var REFERENCE_TONIC = { letter: 0, alter: 0 };
|
|
2940
3172
|
function bareInterval(from, to) {
|
|
@@ -2944,7 +3176,7 @@ function bareInterval(from, to) {
|
|
|
2944
3176
|
);
|
|
2945
3177
|
}
|
|
2946
3178
|
function spelledTonicFor(scale) {
|
|
2947
|
-
return new Note(
|
|
3179
|
+
return new Note(resolveKey(scale).tonic);
|
|
2948
3180
|
}
|
|
2949
3181
|
function isNoteInstance(value) {
|
|
2950
3182
|
if (value === null || typeof value !== "object") {
|
|
@@ -2969,9 +3201,12 @@ var Key = class _Key {
|
|
|
2969
3201
|
*
|
|
2970
3202
|
* @param scale The key/scale; its root is normalized to a pitch class.
|
|
2971
3203
|
* @param tonic The spelled tonic anchoring letter-name spelling.
|
|
2972
|
-
* @param variant
|
|
3204
|
+
* @param variant The scale form. Read from the mask when none is named: a
|
|
3205
|
+
* major mask is a major key whoever built it, and a key with no form at all
|
|
3206
|
+
* is a state a reader would have to guess about.
|
|
2973
3207
|
*/
|
|
2974
3208
|
constructor(scale, tonic, variant) {
|
|
3209
|
+
assertDataObject(scale, "scale");
|
|
2975
3210
|
assertInteger(scale.rootPc, "scale.rootPc");
|
|
2976
3211
|
assertInteger(scale.modeMask12, "scale.modeMask12", 1, 4095);
|
|
2977
3212
|
if ((scale.modeMask12 & 1) === 0) {
|
|
@@ -2988,14 +3223,16 @@ var Key = class _Key {
|
|
|
2988
3223
|
`tonic ${tonic.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`
|
|
2989
3224
|
);
|
|
2990
3225
|
}
|
|
3226
|
+
if (variant !== void 0) {
|
|
3227
|
+
assertKeyVariant(variant, scale.modeMask12);
|
|
3228
|
+
}
|
|
2991
3229
|
this.#scale = { rootPc, modeMask12: scale.modeMask12 };
|
|
2992
3230
|
this.#tonic = tonic;
|
|
2993
|
-
this.#variant = variant;
|
|
3231
|
+
this.#variant = variant ?? variantOfMask(scale.modeMask12);
|
|
2994
3232
|
}
|
|
2995
3233
|
/** Preserve detection metadata while replacing its plain key with this API's Key. */
|
|
2996
3234
|
static #fromMatch(match) {
|
|
2997
|
-
|
|
2998
|
-
return { ...match, key };
|
|
3235
|
+
return detectedKeyMatch(match);
|
|
2999
3236
|
}
|
|
3000
3237
|
/**
|
|
3001
3238
|
* Wrap a key the theory layer spelled, keeping its tonic exactly as given.
|
|
@@ -3005,7 +3242,7 @@ var Key = class _Key {
|
|
|
3005
3242
|
* would throw that spelling away (Bb minor would come back as A# minor).
|
|
3006
3243
|
*/
|
|
3007
3244
|
static #fromSpelled(spelled) {
|
|
3008
|
-
return new _Key(spelled.
|
|
3245
|
+
return new _Key(spelled.scale, new Note(spelled.tonic), spelled.variant);
|
|
3009
3246
|
}
|
|
3010
3247
|
/**
|
|
3011
3248
|
* A major key.
|
|
@@ -3067,8 +3304,12 @@ var Key = class _Key {
|
|
|
3067
3304
|
* is G sharp major and `'gis'` G sharp minor; see {@link parseKeyName} for
|
|
3068
3305
|
* how a name whose case and mode word disagree is read.
|
|
3069
3306
|
*
|
|
3070
|
-
*
|
|
3071
|
-
*
|
|
3307
|
+
* An English name may also name a built-in scale instead of a mode —
|
|
3308
|
+
* `'D harmonic minor'`, `'D dorian'`, `'C major pentatonic'` — which is what
|
|
3309
|
+
* {@link Key.toString} writes for a key that is neither a plain major nor a
|
|
3310
|
+
* plain minor, so a key survives being written down and read back. Only the
|
|
3311
|
+
* tonic's spelling survives from the name, so `'ges dur'` is G flat major,
|
|
3312
|
+
* not F sharp major.
|
|
3072
3313
|
*
|
|
3073
3314
|
* @param text The key name.
|
|
3074
3315
|
* @param opts `system` reads the name in that notation system instead of
|
|
@@ -3107,15 +3348,18 @@ var Key = class _Key {
|
|
|
3107
3348
|
* ```
|
|
3108
3349
|
*/
|
|
3109
3350
|
static tryParse(text, opts) {
|
|
3110
|
-
const parsed =
|
|
3111
|
-
return parsed.ok ? {
|
|
3351
|
+
const parsed = tryResolveKeyName(text, opts);
|
|
3352
|
+
return parsed.ok ? {
|
|
3353
|
+
ok: true,
|
|
3354
|
+
value: new _Key(parsed.value.scale, new Note(parsed.value.tonic), parsed.value.variant)
|
|
3355
|
+
} : parsed;
|
|
3112
3356
|
}
|
|
3113
3357
|
/**
|
|
3114
3358
|
* The key a key signature denotes: the inverse of {@link Key.fifths}.
|
|
3115
3359
|
*
|
|
3116
3360
|
* A signature names two keys — three sharps is both A major and F# minor — so
|
|
3117
3361
|
* the mode decides which one is built. The tonic is spelled the way that key
|
|
3118
|
-
* is written, including the theoretical keys past ±7 (nine sharps is
|
|
3362
|
+
* is written, including the theoretical keys past ±7 (nine sharps is D#
|
|
3119
3363
|
* major).
|
|
3120
3364
|
*
|
|
3121
3365
|
* @param fifths The signed number of sharps (positive) or flats (negative).
|
|
@@ -3142,13 +3386,20 @@ var Key = class _Key {
|
|
|
3142
3386
|
* This is what keeps `Key.of(detectKey(...).scale)` from handing every
|
|
3143
3387
|
* downstream chord a double-sharp spelling.
|
|
3144
3388
|
*
|
|
3145
|
-
*
|
|
3146
|
-
*
|
|
3389
|
+
* A value that already carries a spelling keeps it — a key region's key, the
|
|
3390
|
+
* data a key serializes to — so wrapping one is not where an Ab minor becomes
|
|
3391
|
+
* a G# minor. Only a bare key/scale has a tonic chosen for it, because only a
|
|
3392
|
+
* bare key/scale is missing one.
|
|
3393
|
+
*
|
|
3394
|
+
* @param key The key to wrap, in any form a key is held in.
|
|
3395
|
+
* @param tonic Optional spelled tonic, overriding any the value carries; must
|
|
3396
|
+
* spell the scale's root pitch class.
|
|
3147
3397
|
* @returns The key.
|
|
3148
3398
|
* @throws If the given tonic is not the scale's root pitch class.
|
|
3149
3399
|
*/
|
|
3150
|
-
static of(
|
|
3151
|
-
|
|
3400
|
+
static of(key, tonic) {
|
|
3401
|
+
const resolved = resolveKey(key);
|
|
3402
|
+
return new _Key(resolved.scale, tonic ?? new Note(resolved.tonic), resolved.variant);
|
|
3152
3403
|
}
|
|
3153
3404
|
/**
|
|
3154
3405
|
* Rebuild a key from its {@link Key.toJSON} output.
|
|
@@ -3157,6 +3408,7 @@ var Key = class _Key {
|
|
|
3157
3408
|
* @returns The key.
|
|
3158
3409
|
*/
|
|
3159
3410
|
static fromJSON(data) {
|
|
3411
|
+
assertDataObject(data, "key data");
|
|
3160
3412
|
return new _Key(data.scale, new Note(data.tonic), data.variant);
|
|
3161
3413
|
}
|
|
3162
3414
|
/**
|
|
@@ -3355,12 +3607,18 @@ var Key = class _Key {
|
|
|
3355
3607
|
return supportsFunctionalHarmony(this.#scale);
|
|
3356
3608
|
}
|
|
3357
3609
|
/**
|
|
3358
|
-
* The relative key:
|
|
3610
|
+
* The relative key: for a plain major or minor key, the same key signature
|
|
3611
|
+
* read in the other mode.
|
|
3359
3612
|
*
|
|
3360
3613
|
* Because the new tonic is read off the circle of fifths rather than
|
|
3361
3614
|
* transposed by semitones, the relative of Db major is Bb minor and not its
|
|
3362
3615
|
* enharmonic A# minor.
|
|
3363
3616
|
*
|
|
3617
|
+
* A key that is not a plain major or minor is read through the mode its third
|
|
3618
|
+
* names and stepped from where its tonic stands on the circle: the relative of
|
|
3619
|
+
* D dorian is F major. The shared signature does not carry over to those —
|
|
3620
|
+
* D dorian is written with no accidentals and F major with one.
|
|
3621
|
+
*
|
|
3364
3622
|
* @returns The relative key, always a plain major or minor key.
|
|
3365
3623
|
* @throws If this key's signature falls outside [-12, 12] fifths.
|
|
3366
3624
|
* @example
|
|
@@ -3443,10 +3701,16 @@ var Key = class _Key {
|
|
|
3443
3701
|
*
|
|
3444
3702
|
* These are the keys the German and Japanese teaching tradition counts as a
|
|
3445
3703
|
* key's near relations: the relative and parallel keys, the dominant and the
|
|
3446
|
-
* subdominant, and the relatives of those two.
|
|
3447
|
-
* within one accidental of this key's signature — the
|
|
3448
|
-
* exactly and the other four stand one away; the parallel
|
|
3449
|
-
* away and belongs to the set for the tonic it shares
|
|
3704
|
+
* subdominant, and the relatives of those two. For a plain major or minor key
|
|
3705
|
+
* five of them are written within one accidental of this key's signature — the
|
|
3706
|
+
* relative shares it exactly and the other four stand one away; the parallel
|
|
3707
|
+
* key stands three away and belongs to the set for the tonic it shares
|
|
3708
|
+
* instead.
|
|
3709
|
+
*
|
|
3710
|
+
* A key that is not a plain major or minor is read through the mode its third
|
|
3711
|
+
* names, so its relations are walked from where its tonic stands on the circle
|
|
3712
|
+
* rather than from its own signature, and those accidental counts do not
|
|
3713
|
+
* describe them.
|
|
3450
3714
|
*
|
|
3451
3715
|
* @returns The related keys in relation order.
|
|
3452
3716
|
* @throws If a neighbouring signature falls outside [-12, 12] fifths.
|
|
@@ -3487,10 +3751,38 @@ var Key = class _Key {
|
|
|
3487
3751
|
* ```
|
|
3488
3752
|
*/
|
|
3489
3753
|
relationTo(other) {
|
|
3490
|
-
return keyRelationBetween(
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3754
|
+
return keyRelationBetween(this.toJSON(), other.toJSON());
|
|
3755
|
+
}
|
|
3756
|
+
/**
|
|
3757
|
+
* The chords a modulation from this key into another can pivot on.
|
|
3758
|
+
*
|
|
3759
|
+
* A pivot belongs to both keys at once, so it is heard as a degree of this
|
|
3760
|
+
* key and reinterpreted as a degree of the key being entered. The candidates
|
|
3761
|
+
* are this key's diatonic triads, kept when every one of their pitch classes
|
|
3762
|
+
* is also a scale tone of `other`; each is reported with its numeral in both
|
|
3763
|
+
* keys. A key that stacks no diatonic triads offers nothing to pivot on and
|
|
3764
|
+
* yields an empty list rather than an error.
|
|
3765
|
+
*
|
|
3766
|
+
* @param other The key being entered, as a key name, a plain key/scale, or a
|
|
3767
|
+
* {@link Key}.
|
|
3768
|
+
* @returns The shared triads, ascending by their degree in this key, each
|
|
3769
|
+
* chord carrying this key as its context.
|
|
3770
|
+
* @example
|
|
3771
|
+
* ```ts
|
|
3772
|
+
* import { Key } from '@libraz/libcantus';
|
|
3773
|
+
* Key.major('C')
|
|
3774
|
+
* .pivotsTo('G major')
|
|
3775
|
+
* .map((pivot) => `${pivot.romanFrom}=${pivot.romanTo}`);
|
|
3776
|
+
* // ['I=IV', 'iii=vi', 'V=I', 'vi=ii']
|
|
3777
|
+
* ```
|
|
3778
|
+
*/
|
|
3779
|
+
pivotsTo(other) {
|
|
3780
|
+
assertKeyArgument(other, "key");
|
|
3781
|
+
return pivotChords(this.#scale, other).map(({ chord, romanFrom, romanTo }) => ({
|
|
3782
|
+
chord: new Chord(chord, this),
|
|
3783
|
+
romanFrom,
|
|
3784
|
+
romanTo
|
|
3785
|
+
}));
|
|
3494
3786
|
}
|
|
3495
3787
|
/**
|
|
3496
3788
|
* A scale degree, counted from 1 the way musicians name degrees: `degree(1)`
|
|
@@ -3520,6 +3812,11 @@ var Key = class _Key {
|
|
|
3520
3812
|
* of C major gives F major. A scale with no diatonic triads — anything that
|
|
3521
3813
|
* is not heptatonic — falls back to this key's own mode.
|
|
3522
3814
|
*
|
|
3815
|
+
* The tonic is derived rather than given, so the result is respelled to its
|
|
3816
|
+
* enharmonic key whenever the letter arithmetic lands past the signatures keys
|
|
3817
|
+
* are written with: the fourth degree of a C blues scale reads as F# minor,
|
|
3818
|
+
* not as the Gb minor and its nine flats.
|
|
3819
|
+
*
|
|
3523
3820
|
* @param n The 1-based scale degree, as {@link Key.degree} counts them.
|
|
3524
3821
|
* @param mode The mode of the resulting key; inferred when omitted.
|
|
3525
3822
|
* @returns The key on that degree.
|
|
@@ -3569,10 +3866,17 @@ var Key = class _Key {
|
|
|
3569
3866
|
);
|
|
3570
3867
|
return _Key.#modeOn(resolved, tonic);
|
|
3571
3868
|
}
|
|
3572
|
-
/**
|
|
3869
|
+
/**
|
|
3870
|
+
* A plain major or minor key on a spelled tonic.
|
|
3871
|
+
*
|
|
3872
|
+
* The tonic is one this class derived — a scale degree of another key — so the
|
|
3873
|
+
* result is held to the signatures keys are written with: the fourth degree of
|
|
3874
|
+
* a C blues scale is written as an F# minor rather than as the Gb minor its
|
|
3875
|
+
* letter arithmetic gives, which would be nine flats.
|
|
3876
|
+
*/
|
|
3573
3877
|
static #modeOn(mode, tonic) {
|
|
3574
3878
|
const rootPc = tonic.pitchClass;
|
|
3575
|
-
return new _Key(mode === "minor" ? minorKey(rootPc) : majorKey(rootPc), tonic);
|
|
3879
|
+
return new _Key(mode === "minor" ? minorKey(rootPc) : majorKey(rootPc), tonic).#asWritten();
|
|
3576
3880
|
}
|
|
3577
3881
|
/**
|
|
3578
3882
|
* The mode a scale degree implies, read off its diatonic triad. Scales that
|
|
@@ -3661,27 +3965,45 @@ var Key = class _Key {
|
|
|
3661
3965
|
assertFiniteNumber(semitones, "semitones");
|
|
3662
3966
|
const rootPc = pitchClassOf(this.#scale.rootPc + Math.round(semitones));
|
|
3663
3967
|
const scale = { rootPc, modeMask12: this.#scale.modeMask12 };
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3968
|
+
return new _Key(scale, this.#tonic.transpose(semitones), this.#variant).#asWritten();
|
|
3969
|
+
}
|
|
3970
|
+
/**
|
|
3971
|
+
* This key respelled to the enharmonic key it would actually be written as,
|
|
3972
|
+
* or itself when it is already written that way.
|
|
3973
|
+
*
|
|
3974
|
+
* What every method that derives a tonic of its own ends on. A derived tonic
|
|
3975
|
+
* can land past the signatures music is written with — nine flats, a scale
|
|
3976
|
+
* needing double accidentals — and a key nobody would hand a player is not an
|
|
3977
|
+
* answer to "which key is this". A key the caller spelled is never put through
|
|
3978
|
+
* this: only a tonic the library chose is the library's to choose again.
|
|
3979
|
+
*
|
|
3980
|
+
* The enharmonic key supplies the tonic spelling only: the scale keeps this
|
|
3981
|
+
* key's own mask, so a harmonic minor or a pentatonic is respelled rather than
|
|
3982
|
+
* flattened into a plain major or minor key. A respelling that does not sound
|
|
3983
|
+
* the same root is no respelling at all, so the key stands — deriving a key is
|
|
3984
|
+
* total, and no spelling question may turn it into a throw.
|
|
3985
|
+
*/
|
|
3986
|
+
#asWritten() {
|
|
3987
|
+
if (this.#isWritten()) {
|
|
3988
|
+
return this;
|
|
3667
3989
|
}
|
|
3668
|
-
const other = enharmonicKeyOf(
|
|
3990
|
+
const other = enharmonicKeyOf(this.#tonic.data, this.#scale);
|
|
3669
3991
|
const respelled = other === null ? null : new Note(other.tonic);
|
|
3670
|
-
return respelled === null || respelled.pitchClass !== rootPc ?
|
|
3992
|
+
return respelled === null || respelled.pitchClass !== pitchClassOf(this.#scale.rootPc) ? this : new _Key(this.#scale, respelled, this.#variant);
|
|
3671
3993
|
}
|
|
3672
3994
|
/**
|
|
3673
3995
|
* Whether this key is spelled the way keys are actually written, which is
|
|
3674
|
-
* what decides whether
|
|
3996
|
+
* what decides whether a key this class derived a tonic for keeps that
|
|
3997
|
+
* spelling — {@link Key.transpose}, {@link Key.keyOnDegree} and
|
|
3998
|
+
* {@link Key.forInstrument} all end on it.
|
|
3675
3999
|
*
|
|
3676
|
-
*
|
|
3677
|
-
*
|
|
3678
|
-
*
|
|
3679
|
-
*
|
|
3680
|
-
* Db all the same — so what counts there is that no note needs a double
|
|
3681
|
-
* accidental.
|
|
4000
|
+
* The theory layer owns the answer and this asks it, so the class and the
|
|
4001
|
+
* functions name the same written spelling — including at the ends of the
|
|
4002
|
+
* church modes, where a window drawn from the major and minor keys and one
|
|
4003
|
+
* drawn from the mode itself disagree.
|
|
3682
4004
|
*/
|
|
3683
4005
|
#isWritten() {
|
|
3684
|
-
return
|
|
4006
|
+
return isWrittenTonic(this.#tonic.data, this.#scale);
|
|
3685
4007
|
}
|
|
3686
4008
|
/**
|
|
3687
4009
|
* The key a transposing instrument's part is written in, for this key at
|
|
@@ -3692,7 +4014,11 @@ var Key = class _Key {
|
|
|
3692
4014
|
* lower than it reads — has its part written a major second higher, and a
|
|
3693
4015
|
* concert C major becomes D major. The tonic is spelled by the interval, so
|
|
3694
4016
|
* a concert E flat major reads as F major on that instrument rather than as
|
|
3695
|
-
* E sharp major, and the mode mask is untouched.
|
|
4017
|
+
* E sharp major, and the mode mask is untouched. What the interval spells is
|
|
4018
|
+
* then respelled to its enharmonic key whenever it would not be written that
|
|
4019
|
+
* way, since a part is what a player is actually handed: a concert F# major
|
|
4020
|
+
* on a B flat clarinet reads as A flat major rather than as a G# major with an
|
|
4021
|
+
* F double sharp in its signature.
|
|
3696
4022
|
*
|
|
3697
4023
|
* The opposite reading, a written key back to the key it sounds in, is
|
|
3698
4024
|
* {@link Key.transposeBy} applied to {@link instrumentTransposition} — the
|
|
@@ -3707,6 +4033,7 @@ var Key = class _Key {
|
|
|
3707
4033
|
* import { Key, instrumentTransposition } from '@libraz/libcantus';
|
|
3708
4034
|
* Key.major('C').forInstrument('clarinetBb').toString(); // 'D major'
|
|
3709
4035
|
* Key.major('C').forInstrument('hornF').toString(); // 'G major'
|
|
4036
|
+
* Key.major('F#').forInstrument('clarinetBb').toString(); // 'Ab major'
|
|
3710
4037
|
* // And back: what a part written in C major on a clarinet in A sounds as.
|
|
3711
4038
|
* Key.major('C').transposeBy(instrumentTransposition('clarinetA')).toString(); // 'A major'
|
|
3712
4039
|
* ```
|
|
@@ -3717,7 +4044,7 @@ var Key = class _Key {
|
|
|
3717
4044
|
{ rootPc: tonic.pitchClass, modeMask12: this.#scale.modeMask12 },
|
|
3718
4045
|
tonic,
|
|
3719
4046
|
this.#variant
|
|
3720
|
-
);
|
|
4047
|
+
).#asWritten();
|
|
3721
4048
|
}
|
|
3722
4049
|
/**
|
|
3723
4050
|
* The spelled scale, one note per degree (e.g. C D E F G A B for C major).
|
|
@@ -3835,7 +4162,28 @@ var Key = class _Key {
|
|
|
3835
4162
|
* @throws If the numeral is not valid.
|
|
3836
4163
|
*/
|
|
3837
4164
|
roman(text) {
|
|
3838
|
-
return new Chord(romanToChord(text, this
|
|
4165
|
+
return new Chord(romanToChord(text, this), this);
|
|
4166
|
+
}
|
|
4167
|
+
/**
|
|
4168
|
+
* The augmented sixth chord of a kind, built in this key.
|
|
4169
|
+
*
|
|
4170
|
+
* The three kinds share the augmented sixth between the lowered sixth degree
|
|
4171
|
+
* and the raised fourth and differ in what fills it: the Italian doubles the
|
|
4172
|
+
* tonic, the French adds the second degree, and the German the third. The
|
|
4173
|
+
* chord is spelled as the interval demands rather than as its enharmonic
|
|
4174
|
+
* dominant seventh, which is what keeps its outward resolution readable.
|
|
4175
|
+
*
|
|
4176
|
+
* @param kind Which augmented sixth to build.
|
|
4177
|
+
* @returns The chord, with this key attached.
|
|
4178
|
+
* @throws If the kind names none of the three.
|
|
4179
|
+
* @example
|
|
4180
|
+
* ```ts
|
|
4181
|
+
* import { Key } from '@libraz/libcantus';
|
|
4182
|
+
* Key.major('C').augmentedSixth('german').rootPc; // 8 (the lowered sixth degree)
|
|
4183
|
+
* ```
|
|
4184
|
+
*/
|
|
4185
|
+
augmentedSixth(kind) {
|
|
4186
|
+
return new Chord(augmentedSixthChord(kind, keyIdentity(this)), this);
|
|
3839
4187
|
}
|
|
3840
4188
|
/**
|
|
3841
4189
|
* Build a progression from Roman numerals in this key.
|
|
@@ -3921,16 +4269,23 @@ var Key = class _Key {
|
|
|
3921
4269
|
* one.
|
|
3922
4270
|
*/
|
|
3923
4271
|
toJSON() {
|
|
3924
|
-
return
|
|
4272
|
+
return { scale: this.scale, tonic: this.#tonic.data, variant: this.#variant };
|
|
3925
4273
|
}
|
|
3926
4274
|
/**
|
|
3927
|
-
* The key's tonic and
|
|
3928
|
-
*
|
|
4275
|
+
* The key's tonic and the scale it is read in, so a template literal or a log
|
|
4276
|
+
* line reads as the key.
|
|
4277
|
+
*
|
|
4278
|
+
* The scale word is a function of the scale alone — its root pitch class and
|
|
4279
|
+
* mode mask — rather than of how the key was built, so two keys holding the
|
|
4280
|
+
* same scale print the same name: a harmonic minor names itself one whether
|
|
4281
|
+
* it was detected or asked for by name, and a mode names its mode instead of
|
|
4282
|
+
* the major or minor key its third would make it. What is printed is what
|
|
4283
|
+
* {@link Key.parse} reads back.
|
|
3929
4284
|
*
|
|
3930
4285
|
* A `system` names the key the way that notation system writes it, including
|
|
3931
|
-
* German's case convention. The scale
|
|
3932
|
-
* the other systems have no word for it — so a
|
|
3933
|
-
*
|
|
4286
|
+
* German's case convention. The scale word is an English-only qualifier —
|
|
4287
|
+
* the other systems have no word for it — so a harmonic minor names its
|
|
4288
|
+
* parallel plain minor there.
|
|
3934
4289
|
*
|
|
3935
4290
|
* @param opts `system` writes the name in that notation system instead of
|
|
3936
4291
|
* English.
|
|
@@ -3939,8 +4294,8 @@ var Key = class _Key {
|
|
|
3939
4294
|
* ```ts
|
|
3940
4295
|
* import { Key } from '@libraz/libcantus';
|
|
3941
4296
|
* Key.minor('G#').toString(); // 'G# minor'
|
|
4297
|
+
* Key.named('dorian', 'D').toString(); // 'D dorian'
|
|
3942
4298
|
* Key.minor('G#').toString({ system: 'german' }); // 'gis moll'
|
|
3943
|
-
* Key.minor('G#').toString({ system: 'japanese' }); // '嬰ト短調'
|
|
3944
4299
|
* ```
|
|
3945
4300
|
*/
|
|
3946
4301
|
toString(opts) {
|
|
@@ -3950,32 +4305,40 @@ var Key = class _Key {
|
|
|
3950
4305
|
opts
|
|
3951
4306
|
);
|
|
3952
4307
|
}
|
|
3953
|
-
|
|
3954
|
-
return `${this.#tonic.name} ${this.#variant} minor`;
|
|
3955
|
-
}
|
|
3956
|
-
return `${this.#tonic.name} ${this.isMinor ? "minor" : "major"}`;
|
|
4308
|
+
return `${this.#tonic.name} ${scaleWord(this.#scale.modeMask12, this.isMinor)}`;
|
|
3957
4309
|
}
|
|
3958
4310
|
};
|
|
4311
|
+
function toKey(value) {
|
|
4312
|
+
if (typeof value === "string") {
|
|
4313
|
+
return Key.parse(value);
|
|
4314
|
+
}
|
|
4315
|
+
if (typeof value === "object" && value !== null) {
|
|
4316
|
+
const data = "toJSON" in value && typeof value.toJSON === "function" ? value.toJSON() : value;
|
|
4317
|
+
if (typeof data === "object" && data !== null) {
|
|
4318
|
+
const record = data;
|
|
4319
|
+
if (record.scale === void 0) {
|
|
4320
|
+
const scale = toKeyScale(data);
|
|
4321
|
+
return record.tonic === void 0 ? new Key(scale, spelledTonicFor(scale), record.variant) : new Key(scale, new Note(record.tonic), record.variant);
|
|
4322
|
+
}
|
|
4323
|
+
return record.tonic === void 0 ? Key.of(toKeyScale(record.scale)) : Key.fromJSON(record);
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
throw new InvalidInputError(`key must be a key name or a key/scale; received ${typeof value}`);
|
|
4327
|
+
}
|
|
4328
|
+
function keyIdentity(key) {
|
|
4329
|
+
return { scale: key.scale, tonic: key.tonic.data, variant: key.variant };
|
|
4330
|
+
}
|
|
3959
4331
|
|
|
3960
4332
|
// src/model/score.ts
|
|
3961
4333
|
var DEFAULT_BPM = 120;
|
|
3962
4334
|
function orderNotes(notes, name) {
|
|
3963
|
-
|
|
3964
|
-
throw new InvalidInputError(`${name} must be an array; received ${typeof notes}`);
|
|
3965
|
-
}
|
|
3966
|
-
return notes.map((note, index) => {
|
|
3967
|
-
if (note === void 0) {
|
|
3968
|
-
throw new InvalidInputError(`${name}[${index}] must be a note event; received undefined`);
|
|
3969
|
-
}
|
|
3970
|
-
return copyNoteEvent(
|
|
3971
|
-
assertNoteEvent(note, `${name}[${index}]`, { allowNonPositiveDuration: true })
|
|
3972
|
-
);
|
|
3973
|
-
}).sort(
|
|
4335
|
+
return assertNoteEventArray(notes, name, { allowNonPositiveDuration: true }).map((note) => copyNoteEvent(note)).sort(
|
|
3974
4336
|
(a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch || a.durationBeat - b.durationBeat
|
|
3975
4337
|
);
|
|
3976
4338
|
}
|
|
3977
4339
|
function copyMeters(meters) {
|
|
3978
|
-
return meters.map((change) => {
|
|
4340
|
+
return assertDataObjects(meters, "score meters").map((change) => {
|
|
4341
|
+
assertDataObject(change.ts, "score meters ts");
|
|
3979
4342
|
const ts = {
|
|
3980
4343
|
numerator: change.ts.numerator,
|
|
3981
4344
|
denominator: change.ts.denominator
|
|
@@ -3987,6 +4350,7 @@ function copyMeters(meters) {
|
|
|
3987
4350
|
});
|
|
3988
4351
|
}
|
|
3989
4352
|
function copyTempo(tempo) {
|
|
4353
|
+
assertDataObjects(tempo, "score tempo");
|
|
3990
4354
|
tempoAt(0, tempo);
|
|
3991
4355
|
return tempo.map((event) => ({
|
|
3992
4356
|
startBeat: withoutNegativeZero(event.startBeat),
|
|
@@ -3994,6 +4358,7 @@ function copyTempo(tempo) {
|
|
|
3994
4358
|
}));
|
|
3995
4359
|
}
|
|
3996
4360
|
function copyScore(data) {
|
|
4361
|
+
assertDataObject(data, "score data");
|
|
3997
4362
|
const copy = {
|
|
3998
4363
|
notes: orderNotes(data.notes, "score notes"),
|
|
3999
4364
|
// The map is validated by the same resolver every meter-aware entry point
|
|
@@ -4006,33 +4371,46 @@ function copyScore(data) {
|
|
|
4006
4371
|
}
|
|
4007
4372
|
return copy;
|
|
4008
4373
|
}
|
|
4009
|
-
function metersFrom(meters) {
|
|
4010
|
-
if (meters === void 0) {
|
|
4011
|
-
return resolveMeters({}, "score meters");
|
|
4012
|
-
}
|
|
4013
|
-
const meter = toMeterData(meters, "score meters");
|
|
4014
|
-
return Array.isArray(meter) ? resolveMeters({ meters: meter }, "score meters") : resolveMeters({ ts: meter }, "score meters");
|
|
4015
|
-
}
|
|
4016
4374
|
function tempoFrom(tempo) {
|
|
4017
4375
|
if (tempo === void 0) {
|
|
4018
4376
|
return [{ startBeat: 0, bpm: DEFAULT_BPM }];
|
|
4019
4377
|
}
|
|
4020
4378
|
return typeof tempo === "number" ? [{ startBeat: 0, bpm: tempo }] : tempo;
|
|
4021
4379
|
}
|
|
4380
|
+
function statedKeyRegions(key, notes, totalBeats) {
|
|
4381
|
+
const first = notes[0];
|
|
4382
|
+
if (first === void 0 || totalBeats <= first.startBeat) {
|
|
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
|
+
];
|
|
4395
|
+
}
|
|
4022
4396
|
function scoreData(notes, opts) {
|
|
4023
4397
|
const data = {
|
|
4024
|
-
notes: [...notes],
|
|
4025
|
-
meters:
|
|
4398
|
+
notes: [...assertDataArray(notes, "score notes")],
|
|
4399
|
+
meters: resolveMeters({ meters: opts?.meters }, "score meters"),
|
|
4026
4400
|
tempo: tempoFrom(opts?.tempo)
|
|
4027
4401
|
};
|
|
4028
4402
|
if (opts?.key !== void 0) {
|
|
4029
|
-
data.key =
|
|
4403
|
+
data.key = toKey(opts.key).toJSON();
|
|
4030
4404
|
}
|
|
4031
4405
|
return data;
|
|
4032
4406
|
}
|
|
4033
4407
|
var Score = class _Score {
|
|
4034
4408
|
#data;
|
|
4035
4409
|
#key;
|
|
4410
|
+
/** The chord reading, made on the first member that needs it. */
|
|
4411
|
+
#chords;
|
|
4412
|
+
/** The key regions, made on the first member that needs them. */
|
|
4413
|
+
#regions;
|
|
4036
4414
|
/**
|
|
4037
4415
|
* Wrap plain score data.
|
|
4038
4416
|
*
|
|
@@ -4091,7 +4469,7 @@ var Score = class _Score {
|
|
|
4091
4469
|
* ```
|
|
4092
4470
|
*/
|
|
4093
4471
|
static fromTicks(events, ppq, opts) {
|
|
4094
|
-
const notes = events.map((event) => ({
|
|
4472
|
+
const notes = assertDataObjects(events, "score notes").map((event) => ({
|
|
4095
4473
|
...event,
|
|
4096
4474
|
startBeat: ticksToBeats(event.startBeat, ppq),
|
|
4097
4475
|
durationBeat: ticksToBeats(event.durationBeat, ppq)
|
|
@@ -4184,14 +4562,20 @@ var Score = class _Score {
|
|
|
4184
4562
|
/**
|
|
4185
4563
|
* The score moved by a number of semitones.
|
|
4186
4564
|
*
|
|
4565
|
+
* A carried key moves with the notes, so the score that comes back is read in
|
|
4566
|
+
* the key it now sounds in and every analysis taken from it — its timeline,
|
|
4567
|
+
* its numerals, its phrases — is taken against that key rather than against
|
|
4568
|
+
* the one it was written in.
|
|
4569
|
+
*
|
|
4187
4570
|
* @param semitones The signed semitone offset.
|
|
4188
4571
|
* @returns The transposed score.
|
|
4189
4572
|
* @throws If a note would leave the MIDI range.
|
|
4190
4573
|
*/
|
|
4191
4574
|
transpose(semitones) {
|
|
4192
4575
|
assertFiniteNumber(semitones, "transpose semitones");
|
|
4193
|
-
return this.#
|
|
4194
|
-
this.#data.notes.map((note) => ({ ...note, pitch: note.pitch + semitones }))
|
|
4576
|
+
return this.#moved(
|
|
4577
|
+
this.#data.notes.map((note) => ({ ...note, pitch: note.pitch + semitones })),
|
|
4578
|
+
(key) => key.transpose(semitones)
|
|
4195
4579
|
);
|
|
4196
4580
|
}
|
|
4197
4581
|
/**
|
|
@@ -4199,18 +4583,27 @@ var Score = class _Score {
|
|
|
4199
4583
|
*
|
|
4200
4584
|
* Note events carry a MIDI pitch and no spelling of their own, so the
|
|
4201
4585
|
* interval contributes its size and its direction; a carried key is what
|
|
4202
|
-
* keeps the spelling, and it moves with the notes
|
|
4586
|
+
* keeps the spelling, and it moves with the notes — by the interval itself,
|
|
4587
|
+
* so an augmented fourth and a diminished fifth land on differently spelled
|
|
4588
|
+
* keys.
|
|
4203
4589
|
*
|
|
4204
4590
|
* @param interval An interval name (e.g. `'A4'`, `'-m3'`), plain interval
|
|
4205
4591
|
* data, or an {@link Interval}; a descending interval moves down.
|
|
4206
4592
|
* @returns The transposed score.
|
|
4207
4593
|
*/
|
|
4208
4594
|
transposeBy(interval) {
|
|
4209
|
-
|
|
4595
|
+
const step = toSpelledInterval(interval);
|
|
4596
|
+
return this.#moved(
|
|
4597
|
+
this.#data.notes.map((note) => ({ ...note, pitch: note.pitch + step.semitones })),
|
|
4598
|
+
// Moved by the interval rather than by its semitone count, so a key taken
|
|
4599
|
+
// up an augmented fourth and one taken up a diminished fifth are spelled
|
|
4600
|
+
// the way each interval demands.
|
|
4601
|
+
(key) => key.transposeBy(step)
|
|
4602
|
+
);
|
|
4210
4603
|
}
|
|
4211
4604
|
/** The same notes read against a different meter. */
|
|
4212
4605
|
withMeters(meters) {
|
|
4213
|
-
return new _Score({ ...this.#data, meters:
|
|
4606
|
+
return new _Score({ ...this.#data, meters: resolveMeters({ meters }, "score meters") });
|
|
4214
4607
|
}
|
|
4215
4608
|
/** The same notes read against a different tempo. */
|
|
4216
4609
|
withTempo(tempo) {
|
|
@@ -4218,7 +4611,7 @@ var Score = class _Score {
|
|
|
4218
4611
|
}
|
|
4219
4612
|
/** The same notes read in a different key. */
|
|
4220
4613
|
withKey(key) {
|
|
4221
|
-
return new _Score({ ...this.#data, key:
|
|
4614
|
+
return new _Score({ ...this.#data, key: toKey(key).toJSON() });
|
|
4222
4615
|
}
|
|
4223
4616
|
/**
|
|
4224
4617
|
* The notes pulled onto a grid.
|
|
@@ -4324,7 +4717,7 @@ var Score = class _Score {
|
|
|
4324
4717
|
* and how sure it is of each segment.
|
|
4325
4718
|
*/
|
|
4326
4719
|
timeline(opts) {
|
|
4327
|
-
const result = chordTimelineFromNotes(this.#data.notes, this.#analysisOptions(opts));
|
|
4720
|
+
const result = opts === void 0 ? this.#chordAnalysis() : chordTimelineFromNotes(this.#data.notes, this.#analysisOptions(opts));
|
|
4328
4721
|
return Timeline.fromData({
|
|
4329
4722
|
segments: result.timeline.segments,
|
|
4330
4723
|
totalBeats: opts?.totalBeats ?? this.totalBeats,
|
|
@@ -4347,16 +4740,29 @@ var Score = class _Score {
|
|
|
4347
4740
|
return this.#key;
|
|
4348
4741
|
}
|
|
4349
4742
|
const prevailing = prevailingKeyOf(this.#keyRegions());
|
|
4350
|
-
return prevailing === null ? void 0 :
|
|
4743
|
+
return prevailing === null ? void 0 : toKey(prevailing);
|
|
4351
4744
|
}
|
|
4352
4745
|
/**
|
|
4353
4746
|
* Every key region the score passes through, in time order.
|
|
4354
4747
|
*
|
|
4748
|
+
* A score that was told its key answers with that key across the whole of it,
|
|
4749
|
+
* as {@link Score.key} and {@link Score.timeline} do: the caller has already
|
|
4750
|
+
* answered the question, and searching for a modulation away from a stated
|
|
4751
|
+
* key would make the option read as a hint where it was given as an
|
|
4752
|
+
* instruction. Only `totalBeats` is read from the options there, since the
|
|
4753
|
+
* rest tune a search that is not run.
|
|
4754
|
+
*
|
|
4355
4755
|
* @param opts Analysis options; see {@link KeyTimelineOptions}.
|
|
4356
4756
|
* @returns The regions; empty when nothing sounds.
|
|
4357
4757
|
*/
|
|
4358
4758
|
keys(opts) {
|
|
4359
|
-
|
|
4759
|
+
if (opts !== void 0) {
|
|
4760
|
+
if (this.#key !== void 0) {
|
|
4761
|
+
return statedKeyRegions(this.#key, this.#data.notes, opts.totalBeats ?? this.totalBeats);
|
|
4762
|
+
}
|
|
4763
|
+
return keyTimelineFromNotes(this.#data.notes, { meters: this.#data.meters, ...opts });
|
|
4764
|
+
}
|
|
4765
|
+
return this.#keyRegions().map(copyKeyRegion);
|
|
4360
4766
|
}
|
|
4361
4767
|
/**
|
|
4362
4768
|
* Every key the score could be in, ranked, read as one key across the whole
|
|
@@ -4382,7 +4788,7 @@ var Score = class _Score {
|
|
|
4382
4788
|
* ```
|
|
4383
4789
|
*/
|
|
4384
4790
|
detectKeys(opts) {
|
|
4385
|
-
return detectKeyFromNotes(this.#data.notes, opts);
|
|
4791
|
+
return detectKeyFromNotes(this.#data.notes, opts).map(detectedKeyMatch);
|
|
4386
4792
|
}
|
|
4387
4793
|
/**
|
|
4388
4794
|
* The phrases the notes fall into.
|
|
@@ -4396,7 +4802,7 @@ var Score = class _Score {
|
|
|
4396
4802
|
phrases(opts) {
|
|
4397
4803
|
return phrasesFromTimeline(this.#chordTimeline(), this.#data.notes, {
|
|
4398
4804
|
meters: this.#data.meters,
|
|
4399
|
-
...this.#key === void 0 ? {} : { key: this.#key
|
|
4805
|
+
...this.#key === void 0 ? {} : { key: keyIdentity(this.#key) },
|
|
4400
4806
|
...opts
|
|
4401
4807
|
});
|
|
4402
4808
|
}
|
|
@@ -4428,11 +4834,18 @@ var Score = class _Score {
|
|
|
4428
4834
|
/**
|
|
4429
4835
|
* The bar-level pulse above the meter.
|
|
4430
4836
|
*
|
|
4837
|
+
* The cadences come from the score's own harmony unless the caller passes
|
|
4838
|
+
* their own, so this reads the same grouping {@link Score.phrases} builds its
|
|
4839
|
+
* phrases on rather than a second one that ignores where the music closes.
|
|
4840
|
+
*
|
|
4431
4841
|
* @param opts Analysis options; see {@link HypermeterOptions}.
|
|
4432
4842
|
* @returns The grouping, its downbeats, a confidence, and a rationale.
|
|
4433
4843
|
*/
|
|
4434
4844
|
hypermeter(opts) {
|
|
4435
|
-
return hypermeter(this.#data.notes, this.#data.meters,
|
|
4845
|
+
return hypermeter(this.#data.notes, this.#data.meters, {
|
|
4846
|
+
cadenceBeats: this.#cadenceBeats(),
|
|
4847
|
+
...opts
|
|
4848
|
+
});
|
|
4436
4849
|
}
|
|
4437
4850
|
/**
|
|
4438
4851
|
* The motifs the melody repeats.
|
|
@@ -4445,6 +4858,12 @@ var Score = class _Score {
|
|
|
4445
4858
|
* A record is a cell, so `Motif.fromData(found)` reaches the
|
|
4446
4859
|
* transformations without anything being rebuilt by hand.
|
|
4447
4860
|
*
|
|
4861
|
+
* A melody is one line, so a score holding several is read as its top voice:
|
|
4862
|
+
* notes struck together are one event and the highest of them stands for it.
|
|
4863
|
+
* A piano part read whole therefore answers for the voice a listener follows
|
|
4864
|
+
* rather than for a line assembled from the insides of its chords. Split the
|
|
4865
|
+
* score into lines first to have each of them answered for.
|
|
4866
|
+
*
|
|
4448
4867
|
* @param opts Cell-length bounds and the recurrence threshold; see
|
|
4449
4868
|
* {@link ExtractMotifsOptions}.
|
|
4450
4869
|
* @returns The motifs found, longest first.
|
|
@@ -4467,7 +4886,15 @@ var Score = class _Score {
|
|
|
4467
4886
|
motifs(opts) {
|
|
4468
4887
|
return extractMotifs(this.#data.notes, opts);
|
|
4469
4888
|
}
|
|
4470
|
-
/**
|
|
4889
|
+
/**
|
|
4890
|
+
* The shape the melody traces.
|
|
4891
|
+
*
|
|
4892
|
+
* A contour is the shape of one line, so a score holding several is read as
|
|
4893
|
+
* its top voice: notes struck together are one event and the highest of them
|
|
4894
|
+
* stands for it, which keeps a chord from being measured as a climb through
|
|
4895
|
+
* its own notes. Split the score into lines first to have each of them
|
|
4896
|
+
* answered for; {@link Score.voices} is what reads the polyphony as polyphony.
|
|
4897
|
+
*/
|
|
4471
4898
|
contour() {
|
|
4472
4899
|
return melodicContour(this.#data.notes);
|
|
4473
4900
|
}
|
|
@@ -4481,12 +4908,19 @@ var Score = class _Score {
|
|
|
4481
4908
|
* chord — reads every note against C major and no chord at all, which labels
|
|
4482
4909
|
* the notes as the non-chord tones they are rather than failing.
|
|
4483
4910
|
*
|
|
4484
|
-
*
|
|
4911
|
+
* A score holds a whole piece rather than a single line, so the notes are read
|
|
4912
|
+
* as the polyphony they are: each note is classified in its own voice, against
|
|
4913
|
+
* everything else sounding under it. That is what a suspension needs — a
|
|
4914
|
+
* dissonance is dissonant against something — and it is what keeps a note of
|
|
4915
|
+
* one voice from being heard as the passing tone of another.
|
|
4916
|
+
*
|
|
4917
|
+
* @param key The key to read the notes in, as a key name, a plain key/scale,
|
|
4918
|
+
* or a {@link Key}; defaults to the score's own.
|
|
4485
4919
|
* @returns One annotation per note, in the score's own time order.
|
|
4486
4920
|
*/
|
|
4487
4921
|
voices(key) {
|
|
4488
4922
|
const timeline = this.#chordTimeline();
|
|
4489
|
-
return
|
|
4923
|
+
return analyzePolyphony(this.#data.notes, timeline.at, this.#keyContext(key));
|
|
4490
4924
|
}
|
|
4491
4925
|
/**
|
|
4492
4926
|
* The notes carrying the ids the voice analysis reports them back under.
|
|
@@ -4505,7 +4939,11 @@ var Score = class _Score {
|
|
|
4505
4939
|
/**
|
|
4506
4940
|
* Whether an instrument can play the score, and what makes it hard.
|
|
4507
4941
|
*
|
|
4508
|
-
* The
|
|
4942
|
+
* The third layer asks how much time separates two notes, and the score
|
|
4943
|
+
* answers it from its whole tempo map: a passage under an accelerando is
|
|
4944
|
+
* judged at the tempo actually in force across it, the same integral
|
|
4945
|
+
* {@link Score.secondsAt} reports. The beats the issues name are the score's
|
|
4946
|
+
* own.
|
|
4509
4947
|
*
|
|
4510
4948
|
* @param instrument The instrument to play it on: a plain
|
|
4511
4949
|
* {@link InstrumentProfile} or an {@link Instrument}.
|
|
@@ -4520,11 +4958,33 @@ var Score = class _Score {
|
|
|
4520
4958
|
* ```
|
|
4521
4959
|
*/
|
|
4522
4960
|
playability(instrument) {
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4961
|
+
const profile = toInstrumentProfile(instrument);
|
|
4962
|
+
const tempo = this.#data.tempo;
|
|
4963
|
+
const openingBpm = tempoAt(0, tempo);
|
|
4964
|
+
if (tempo.length < 2) {
|
|
4965
|
+
return playability(this.#data.notes, profile, openingBpm);
|
|
4966
|
+
}
|
|
4967
|
+
const played = tempoReader(tempo);
|
|
4968
|
+
const evenly = tempoReader([{ startBeat: 0, bpm: openingBpm }]);
|
|
4969
|
+
const restate = (beat) => evenly.beatAt(played.secondsAt(beat));
|
|
4970
|
+
const scoreBeats = /* @__PURE__ */ new Map();
|
|
4971
|
+
const evenlyPaced = this.#data.notes.map((note) => {
|
|
4972
|
+
const startBeat = restate(note.startBeat);
|
|
4973
|
+
scoreBeats.set(startBeat, note.startBeat);
|
|
4974
|
+
return {
|
|
4975
|
+
...note,
|
|
4976
|
+
startBeat,
|
|
4977
|
+
durationBeat: restate(note.startBeat + note.durationBeat) - startBeat
|
|
4978
|
+
};
|
|
4979
|
+
});
|
|
4980
|
+
const report = playability(evenlyPaced, profile, openingBpm);
|
|
4981
|
+
return {
|
|
4982
|
+
...report,
|
|
4983
|
+
issues: report.issues.map((issue) => ({
|
|
4984
|
+
...issue,
|
|
4985
|
+
startBeat: scoreBeats.get(issue.startBeat) ?? issue.startBeat
|
|
4986
|
+
}))
|
|
4987
|
+
};
|
|
4528
4988
|
}
|
|
4529
4989
|
/**
|
|
4530
4990
|
* An index for repeated lookups over the notes.
|
|
@@ -4622,24 +5082,64 @@ var Score = class _Score {
|
|
|
4622
5082
|
return new _Score({ ...this.#data, notes: [...notes] });
|
|
4623
5083
|
}
|
|
4624
5084
|
/**
|
|
4625
|
-
* The
|
|
5085
|
+
* The same context carrying another set of notes, with a carried key moved
|
|
5086
|
+
* the same way the notes were.
|
|
5087
|
+
*
|
|
5088
|
+
* Every path that moves the pitches goes through here, so a score cannot come
|
|
5089
|
+
* back sounding in one key and analysed in another.
|
|
5090
|
+
*/
|
|
5091
|
+
#moved(notes, moveKey) {
|
|
5092
|
+
const data = { ...this.#data, notes: [...notes] };
|
|
5093
|
+
if (this.#key !== void 0) {
|
|
5094
|
+
data.key = moveKey(this.#key).toJSON();
|
|
5095
|
+
}
|
|
5096
|
+
return new _Score(data);
|
|
5097
|
+
}
|
|
5098
|
+
/**
|
|
5099
|
+
* The chord timeline the score's own harmony describes, read once.
|
|
4626
5100
|
*
|
|
4627
5101
|
* Private, because the options belong to the members that expose them: the
|
|
4628
5102
|
* analyses that read harmony read the score's, and a caller who wants
|
|
4629
|
-
* another builds it through {@link Score.timeline}.
|
|
5103
|
+
* another builds it through {@link Score.timeline}. The reading is kept
|
|
5104
|
+
* because a score is immutable and this takes no options: chord inference is
|
|
5105
|
+
* the most expensive thing the class does, and the natural order — timeline,
|
|
5106
|
+
* phrases, cadences, voices — would otherwise ask for it once per member.
|
|
4630
5107
|
*/
|
|
4631
5108
|
#chordTimeline() {
|
|
4632
|
-
return
|
|
5109
|
+
return this.#chordAnalysis().timeline;
|
|
5110
|
+
}
|
|
5111
|
+
/**
|
|
5112
|
+
* The whole reading, not only the timeline it carries: the key regions and
|
|
5113
|
+
* the per-segment confidence come from the same pass, and {@link
|
|
5114
|
+
* Score.timeline} hands all three to the class it builds.
|
|
5115
|
+
*/
|
|
5116
|
+
#chordAnalysis() {
|
|
5117
|
+
const analysis = this.#chords ?? chordTimelineFromNotes(this.#data.notes, this.#analysisOptions());
|
|
5118
|
+
this.#chords = analysis;
|
|
5119
|
+
return analysis;
|
|
4633
5120
|
}
|
|
4634
|
-
/**
|
|
5121
|
+
/**
|
|
5122
|
+
* The key regions under the score: the key it was told it is in across the
|
|
5123
|
+
* whole of it, or the ones read from the notes against its own meter.
|
|
5124
|
+
*
|
|
5125
|
+
* Kept on first reading for the same reason the chord timeline is.
|
|
5126
|
+
*/
|
|
4635
5127
|
#keyRegions() {
|
|
4636
|
-
|
|
5128
|
+
const regions = this.#regions ?? (this.#key === void 0 ? keyTimelineFromNotes(this.#data.notes, { meters: this.#data.meters }) : statedKeyRegions(this.#key, this.#data.notes, this.totalBeats));
|
|
5129
|
+
this.#regions = regions;
|
|
5130
|
+
return regions;
|
|
5131
|
+
}
|
|
5132
|
+
/** Where the score's own harmony closes, as beats. */
|
|
5133
|
+
#cadenceBeats() {
|
|
5134
|
+
return detectCadences(this.#chordTimeline(), this.#keyContext(void 0)).map(
|
|
5135
|
+
(hit) => hit.atBeat
|
|
5136
|
+
);
|
|
4637
5137
|
}
|
|
4638
5138
|
/** The score's context as chord-timeline options, with the caller's on top. */
|
|
4639
5139
|
#analysisOptions(opts) {
|
|
4640
5140
|
return {
|
|
4641
5141
|
meters: this.#data.meters,
|
|
4642
|
-
...this.#key === void 0 ? {} : { key: this.#key
|
|
5142
|
+
...this.#key === void 0 ? {} : { key: keyIdentity(this.#key) },
|
|
4643
5143
|
...opts
|
|
4644
5144
|
};
|
|
4645
5145
|
}
|
|
@@ -4648,15 +5148,17 @@ var Score = class _Score {
|
|
|
4648
5148
|
* then the key in force at the beat, and C major where nothing sounds.
|
|
4649
5149
|
*/
|
|
4650
5150
|
#keyContext(key) {
|
|
5151
|
+
assertKeyArgument(key, "score key");
|
|
4651
5152
|
if (key !== void 0) {
|
|
4652
|
-
return
|
|
5153
|
+
return keyIdentity(toKey(key));
|
|
4653
5154
|
}
|
|
4654
5155
|
if (this.#key !== void 0) {
|
|
4655
|
-
return this.#key
|
|
5156
|
+
return keyIdentity(this.#key);
|
|
4656
5157
|
}
|
|
4657
5158
|
const regions = this.#keyRegions();
|
|
4658
|
-
const prevailing = prevailingKeyOf(regions) ?? Key.major("C")
|
|
4659
|
-
|
|
5159
|
+
const prevailing = prevailingKeyOf(regions) ?? keyIdentity(Key.major("C"));
|
|
5160
|
+
const keyAt = keyLookup(regions, prevailing);
|
|
5161
|
+
return (beat) => scaleOf(keyAt(beat));
|
|
4660
5162
|
}
|
|
4661
5163
|
};
|
|
4662
5164
|
function sameNote(note, other) {
|
|
@@ -4678,52 +5180,31 @@ function sameKey(key, other) {
|
|
|
4678
5180
|
}
|
|
4679
5181
|
|
|
4680
5182
|
// src/model/arrangement.ts
|
|
4681
|
-
function
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
if (typeof value === "object" && value !== null) {
|
|
4686
|
-
const copy = {};
|
|
4687
|
-
for (const [key, item] of Object.entries(value)) {
|
|
4688
|
-
copy[key] = copyPlain(item);
|
|
4689
|
-
}
|
|
4690
|
-
return copy;
|
|
4691
|
-
}
|
|
4692
|
-
return value;
|
|
4693
|
-
}
|
|
4694
|
-
function copyNote2(note, name) {
|
|
4695
|
-
assertNoteEvent(note, name, { allowNonPositiveDuration: true });
|
|
4696
|
-
return copyNoteEvent(note);
|
|
4697
|
-
}
|
|
4698
|
-
function copyNotes(notes, name) {
|
|
4699
|
-
if (!Array.isArray(notes)) {
|
|
4700
|
-
throw new InvalidInputError(`${name} must be an array; received ${typeof notes}`);
|
|
4701
|
-
}
|
|
4702
|
-
return notes.map((note, index) => {
|
|
4703
|
-
if (note === void 0) {
|
|
4704
|
-
throw new InvalidInputError(`${name}[${index}] must be a note event; received undefined`);
|
|
4705
|
-
}
|
|
4706
|
-
return copyNote2(note, `${name}[${index}]`);
|
|
4707
|
-
});
|
|
5183
|
+
function copyNotes(notes, name, budget) {
|
|
5184
|
+
return assertNoteEventArray(notes, name, { allowNonPositiveDuration: true, budget }).map(
|
|
5185
|
+
copyNoteEvent
|
|
5186
|
+
);
|
|
4708
5187
|
}
|
|
4709
|
-
function copyTrack(track, index) {
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
5188
|
+
function copyTrack(track, index, budget) {
|
|
5189
|
+
assertDataObject(track, `tracks[${index}]`);
|
|
5190
|
+
const copy = {
|
|
5191
|
+
notes: copyNotes(track.notes, `tracks[${index}].notes`, budget)
|
|
5192
|
+
};
|
|
4714
5193
|
if (track.name !== void 0) {
|
|
4715
5194
|
copy.name = track.name;
|
|
4716
5195
|
}
|
|
4717
5196
|
if (track.role !== void 0) {
|
|
4718
|
-
copy.role = track.role;
|
|
5197
|
+
copy.role = trackRoleOf(track.role, `tracks[${index}].role`);
|
|
4719
5198
|
}
|
|
4720
5199
|
return copy;
|
|
4721
5200
|
}
|
|
4722
5201
|
function copyKeyRegions(keys) {
|
|
4723
|
-
|
|
5202
|
+
const given = assertDataObjects(keys, "arrangement keys");
|
|
5203
|
+
return [...new Timeline({ segments: [], totalBeats: 0, keys: [...given] }).keys];
|
|
4724
5204
|
}
|
|
4725
5205
|
function copySegments(segments) {
|
|
4726
|
-
|
|
5206
|
+
const given = assertDataObjects(segments, "arrangement timeline");
|
|
5207
|
+
return [...new Timeline({ segments: [...given], totalBeats: spanEnd(given) }).segments];
|
|
4727
5208
|
}
|
|
4728
5209
|
function chordTimelineOf(segments) {
|
|
4729
5210
|
return new Timeline({ segments: [...segments], totalBeats: spanEnd(segments) }).chordTimeline;
|
|
@@ -4732,16 +5213,17 @@ function copySettings(settings, trackCount) {
|
|
|
4732
5213
|
if (settings === void 0) {
|
|
4733
5214
|
return void 0;
|
|
4734
5215
|
}
|
|
5216
|
+
assertDataObject(settings, "arrangement settings");
|
|
4735
5217
|
const copy = {};
|
|
4736
5218
|
const meters = resolveMeters({ ts: settings.ts, meters: settings.meters }, "arrangement meters");
|
|
4737
5219
|
if (settings.ts !== void 0) {
|
|
4738
|
-
copy.ts = copyPlain(settings.ts);
|
|
5220
|
+
copy.ts = copyPlain(toMeterData(settings.ts, "arrangement ts"), "arrangement ts");
|
|
4739
5221
|
}
|
|
4740
5222
|
if (settings.meters !== void 0) {
|
|
4741
|
-
copy.meters = copyPlain(meters);
|
|
5223
|
+
copy.meters = copyPlain(meters, "arrangement meters");
|
|
4742
5224
|
}
|
|
4743
5225
|
if (settings.key !== void 0) {
|
|
4744
|
-
copy.key =
|
|
5226
|
+
copy.key = resolveKey(settings.key);
|
|
4745
5227
|
}
|
|
4746
5228
|
if (settings.keys !== void 0) {
|
|
4747
5229
|
copy.keys = copyKeyRegions(settings.keys);
|
|
@@ -4750,7 +5232,10 @@ function copySettings(settings, trackCount) {
|
|
|
4750
5232
|
copy.timeline = copySegments(settings.timeline);
|
|
4751
5233
|
}
|
|
4752
5234
|
if (settings.harmonyTracks !== void 0) {
|
|
4753
|
-
copy.harmonyTracks =
|
|
5235
|
+
copy.harmonyTracks = assertDataArray(
|
|
5236
|
+
settings.harmonyTracks,
|
|
5237
|
+
"arrangement harmonyTracks"
|
|
5238
|
+
).map(
|
|
4754
5239
|
(track, index) => assertInteger(track, `arrangement harmonyTracks[${index}]`, 0, trackCount - 1)
|
|
4755
5240
|
);
|
|
4756
5241
|
}
|
|
@@ -4787,40 +5272,41 @@ function copySettings(settings, trackCount) {
|
|
|
4787
5272
|
return Object.keys(copy).length === 0 ? void 0 : copy;
|
|
4788
5273
|
}
|
|
4789
5274
|
function copyArrangement(data) {
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
)
|
|
4794
|
-
|
|
4795
|
-
const tracks = data.tracks.map(copyTrack);
|
|
5275
|
+
assertDataObject(data, "arrangement data");
|
|
5276
|
+
const budget = data.settings?.budget;
|
|
5277
|
+
const tracks = assertDataArray(data.tracks, "arrangement tracks").map(
|
|
5278
|
+
(track, index) => copyTrack(track, index, budget)
|
|
5279
|
+
);
|
|
4796
5280
|
const settings = copySettings(data.settings, tracks.length);
|
|
4797
5281
|
return settings === void 0 ? { tracks } : { tracks, settings };
|
|
4798
5282
|
}
|
|
4799
5283
|
function arrangementData(tracks, settings) {
|
|
4800
|
-
const data = {
|
|
5284
|
+
const data = {
|
|
5285
|
+
tracks: [...assertDataArray(tracks, "tracks")]
|
|
5286
|
+
};
|
|
4801
5287
|
if (settings !== void 0) {
|
|
4802
5288
|
data.settings = settings;
|
|
4803
5289
|
}
|
|
4804
5290
|
return data;
|
|
4805
5291
|
}
|
|
4806
|
-
function metersFrom2(meters) {
|
|
4807
|
-
const meter = toMeterData(meters, "arrangement meters");
|
|
4808
|
-
return Array.isArray(meter) ? resolveMeters({ meters: meter }, "arrangement meters") : resolveMeters({ ts: meter }, "arrangement meters");
|
|
4809
|
-
}
|
|
4810
5292
|
function settingsFrom(setup) {
|
|
4811
5293
|
if (setup === void 0) {
|
|
4812
5294
|
return void 0;
|
|
4813
5295
|
}
|
|
5296
|
+
assertDataObject(setup, "arrangement options");
|
|
4814
5297
|
const { key, meters, timeline, ...rest } = setup;
|
|
4815
5298
|
const settings = { ...rest };
|
|
4816
5299
|
if (key !== void 0) {
|
|
4817
|
-
settings.key =
|
|
5300
|
+
settings.key = resolveKey(key);
|
|
4818
5301
|
}
|
|
4819
5302
|
if (meters !== void 0) {
|
|
4820
|
-
settings.meters =
|
|
5303
|
+
settings.meters = resolveMeters({ meters }, "arrangement meters");
|
|
4821
5304
|
}
|
|
4822
5305
|
if (timeline !== void 0) {
|
|
4823
|
-
|
|
5306
|
+
assertDataObject(timeline, "arrangement timeline");
|
|
5307
|
+
settings.timeline = [
|
|
5308
|
+
...assertDataArray(timeline.segments, "arrangement timeline segments")
|
|
5309
|
+
];
|
|
4824
5310
|
}
|
|
4825
5311
|
return settings;
|
|
4826
5312
|
}
|
|
@@ -4828,13 +5314,14 @@ function analysisOptionsOf(setup) {
|
|
|
4828
5314
|
if (setup === void 0) {
|
|
4829
5315
|
return {};
|
|
4830
5316
|
}
|
|
5317
|
+
assertDataObject(setup, "arrangement options");
|
|
4831
5318
|
const { key, meters, ...rest } = setup;
|
|
4832
5319
|
const opts = { ...rest };
|
|
4833
5320
|
if (key !== void 0) {
|
|
4834
|
-
opts.key =
|
|
5321
|
+
opts.key = resolveKey(key);
|
|
4835
5322
|
}
|
|
4836
5323
|
if (meters !== void 0) {
|
|
4837
|
-
opts.meters =
|
|
5324
|
+
opts.meters = resolveMeters({ meters }, "arrangement meters");
|
|
4838
5325
|
}
|
|
4839
5326
|
return opts;
|
|
4840
5327
|
}
|
|
@@ -4897,7 +5384,7 @@ var Arrangement = class _Arrangement {
|
|
|
4897
5384
|
}
|
|
4898
5385
|
/** The tracks as they were given, in that order. */
|
|
4899
5386
|
get tracks() {
|
|
4900
|
-
return copyPlain(this.#data.tracks);
|
|
5387
|
+
return copyPlain(this.#data.tracks, "arrangement tracks");
|
|
4901
5388
|
}
|
|
4902
5389
|
/**
|
|
4903
5390
|
* The reading of the whole arrangement: the key regions, the harmony, the
|
|
@@ -4907,7 +5394,7 @@ var Arrangement = class _Arrangement {
|
|
|
4907
5394
|
*/
|
|
4908
5395
|
get analysis() {
|
|
4909
5396
|
const { timeline, ...rest } = this.#session().analysis;
|
|
4910
|
-
return { ...copyPlain(rest), timeline: this.timeline().chordTimeline };
|
|
5397
|
+
return { ...copyPlain(rest, "arrangement analysis"), timeline: this.timeline().chordTimeline };
|
|
4911
5398
|
}
|
|
4912
5399
|
/**
|
|
4913
5400
|
* The notes clashing with the harmony sounding beneath them, worst severity
|
|
@@ -4919,7 +5406,7 @@ var Arrangement = class _Arrangement {
|
|
|
4919
5406
|
* labels to tell those apart.
|
|
4920
5407
|
*/
|
|
4921
5408
|
get conflicts() {
|
|
4922
|
-
return copyPlain(this.#session().analysis.conflicts);
|
|
5409
|
+
return copyPlain(this.#session().analysis.conflicts, "arrangement conflicts");
|
|
4923
5410
|
}
|
|
4924
5411
|
/**
|
|
4925
5412
|
* One track's notes as a score, or undefined when no track carries the name.
|
|
@@ -4976,7 +5463,7 @@ var Arrangement = class _Arrangement {
|
|
|
4976
5463
|
}
|
|
4977
5464
|
copied.push({
|
|
4978
5465
|
trackIndex: edit.trackIndex,
|
|
4979
|
-
notes: copyNotes(edit.notes, `edits[${index}].notes
|
|
5466
|
+
notes: copyNotes(edit.notes, `edits[${index}].notes`, this.#data.settings?.budget)
|
|
4980
5467
|
});
|
|
4981
5468
|
}
|
|
4982
5469
|
const session = this.#session().update(copied);
|
|
@@ -4991,6 +5478,10 @@ var Arrangement = class _Arrangement {
|
|
|
4991
5478
|
* annotations describe the same chords rather than two readings of the same
|
|
4992
5479
|
* notes.
|
|
4993
5480
|
*
|
|
5481
|
+
* Naming a key, its regions, or the tracks the harmony is read from asks for
|
|
5482
|
+
* a harmony this arrangement did not find, so the curve is read under the one
|
|
5483
|
+
* named rather than over the one it holds.
|
|
5484
|
+
*
|
|
4994
5485
|
* @param opts The sampling step, and any setting to lay over the
|
|
4995
5486
|
* arrangement's own; a meter named here replaces the arrangement's, since
|
|
4996
5487
|
* `ts` and `meters` name the same thing.
|
|
@@ -5057,7 +5548,7 @@ var Arrangement = class _Arrangement {
|
|
|
5057
5548
|
}
|
|
5058
5549
|
/** A copy of the underlying plain data. */
|
|
5059
5550
|
get data() {
|
|
5060
|
-
return copyPlain(this.#data);
|
|
5551
|
+
return copyPlain(this.#data, "arrangement data");
|
|
5061
5552
|
}
|
|
5062
5553
|
/** The plain form of the arrangement, for `JSON.stringify`. */
|
|
5063
5554
|
toJSON() {
|
|
@@ -5148,23 +5639,19 @@ function copyProfile(profile) {
|
|
|
5148
5639
|
for (const [pitch, limbs] of Object.entries(profile.reach)) {
|
|
5149
5640
|
reach[Number(pitch)] = [...limbs];
|
|
5150
5641
|
}
|
|
5151
|
-
|
|
5642
|
+
const percussion = {
|
|
5643
|
+
kind: "percussion",
|
|
5644
|
+
...common,
|
|
5645
|
+
limbs: [...profile.limbs],
|
|
5646
|
+
reach
|
|
5647
|
+
};
|
|
5648
|
+
if (profile.overdub !== void 0) {
|
|
5649
|
+
percussion.overdub = [...profile.overdub];
|
|
5650
|
+
}
|
|
5651
|
+
return percussion;
|
|
5152
5652
|
}
|
|
5153
5653
|
function checkedProfile(profile) {
|
|
5154
|
-
|
|
5155
|
-
assertPositiveInt(copy.polyphony, `${copy.name} polyphony`);
|
|
5156
|
-
if (copy.kind === "stringed") {
|
|
5157
|
-
for (const [index, open] of copy.tuning.entries()) {
|
|
5158
|
-
assertInteger(open, `${copy.name} tuning[${index}]`);
|
|
5159
|
-
}
|
|
5160
|
-
assertInteger(copy.maxStretch, `${copy.name} maxStretch`, 0);
|
|
5161
|
-
} else {
|
|
5162
|
-
for (const pitch of Object.keys(copy.reach)) {
|
|
5163
|
-
assertInteger(Number(pitch), `${copy.name} reach pitch`);
|
|
5164
|
-
}
|
|
5165
|
-
}
|
|
5166
|
-
instrumentRange(copy);
|
|
5167
|
-
return copy;
|
|
5654
|
+
return copyProfile(toInstrumentProfile(profile));
|
|
5168
5655
|
}
|
|
5169
5656
|
var Instrument = class _Instrument {
|
|
5170
5657
|
#profile;
|
|
@@ -5186,7 +5673,7 @@ var Instrument = class _Instrument {
|
|
|
5186
5673
|
* tuning, stretch or voice count that is not finite.
|
|
5187
5674
|
* @example
|
|
5188
5675
|
* ```ts
|
|
5189
|
-
* import { GUITAR_DROP_D } from '
|
|
5676
|
+
* import { GUITAR_DROP_D } from '@libraz/libcantus/core';
|
|
5190
5677
|
* import { Instrument } from '@libraz/libcantus';
|
|
5191
5678
|
* Instrument.of(GUITAR_DROP_D).range().low; // 38
|
|
5192
5679
|
* ```
|
|
@@ -5288,7 +5775,7 @@ var Instrument = class _Instrument {
|
|
|
5288
5775
|
* ```
|
|
5289
5776
|
*/
|
|
5290
5777
|
range() {
|
|
5291
|
-
return
|
|
5778
|
+
return rangeIn(this.#profile);
|
|
5292
5779
|
}
|
|
5293
5780
|
/**
|
|
5294
5781
|
* Whether the instrument has this pitch at all.
|
|
@@ -5298,7 +5785,8 @@ var Instrument = class _Instrument {
|
|
|
5298
5785
|
* produces it.
|
|
5299
5786
|
*/
|
|
5300
5787
|
canSound(pitch) {
|
|
5301
|
-
|
|
5788
|
+
assertMidiPitch(pitch, "pitch");
|
|
5789
|
+
return soundsIn(this.#profile, pitch);
|
|
5302
5790
|
}
|
|
5303
5791
|
/**
|
|
5304
5792
|
* Every position on the neck that sounds a pitch, lowest string first.
|
|
@@ -5312,15 +5800,17 @@ var Instrument = class _Instrument {
|
|
|
5312
5800
|
if (profile.kind !== "stringed") {
|
|
5313
5801
|
throw new InvalidInputError(`${profile.name} has no strings to finger`);
|
|
5314
5802
|
}
|
|
5315
|
-
|
|
5803
|
+
assertMidiPitch(pitch, "pitch");
|
|
5804
|
+
return fingeringsIn(profile, pitch);
|
|
5316
5805
|
}
|
|
5317
5806
|
/**
|
|
5318
5807
|
* Move a pitch by whole octaves until the instrument can sound it, which is
|
|
5319
5808
|
* what a player does with a line written below the instrument.
|
|
5320
5809
|
*
|
|
5321
5810
|
* @param pitch MIDI pitch to place.
|
|
5322
|
-
* @returns The nearest octave transposition the instrument sounds,
|
|
5323
|
-
*
|
|
5811
|
+
* @returns The nearest octave transposition the instrument sounds, taking the
|
|
5812
|
+
* upper one when two are equally near; the pitch unchanged when no
|
|
5813
|
+
* transposition is available.
|
|
5324
5814
|
* @example
|
|
5325
5815
|
* ```ts
|
|
5326
5816
|
* import { Instrument } from '@libraz/libcantus';
|
|
@@ -5328,7 +5818,8 @@ var Instrument = class _Instrument {
|
|
|
5328
5818
|
* ```
|
|
5329
5819
|
*/
|
|
5330
5820
|
foldIntoRange(pitch) {
|
|
5331
|
-
|
|
5821
|
+
assertMidiPitch(pitch, "pitch");
|
|
5822
|
+
return foldIn(pitch, this.#profile);
|
|
5332
5823
|
}
|
|
5333
5824
|
/**
|
|
5334
5825
|
* The pitch this instrument sounds for a note written in its part.
|
|
@@ -5407,7 +5898,7 @@ var Instrument = class _Instrument {
|
|
|
5407
5898
|
if (a.kind === "stringed") {
|
|
5408
5899
|
return b.kind === "stringed" && a.frets === b.frets && a.maxStretch === b.maxStretch && (a.fretless ?? false) === (b.fretless ?? false) && sameList(a.tuning, b.tuning);
|
|
5409
5900
|
}
|
|
5410
|
-
return b.kind === "percussion" && sameList(a.limbs, b.limbs) && sameReach(a.reach, b.reach);
|
|
5901
|
+
return b.kind === "percussion" && sameList(a.limbs, b.limbs) && sameReach(a.reach, b.reach) && sameList(a.overdub ?? [], b.overdub ?? []);
|
|
5411
5902
|
}
|
|
5412
5903
|
/**
|
|
5413
5904
|
* The plain profile, for JSON serialization.
|
|
@@ -5437,40 +5928,11 @@ var Instrument = class _Instrument {
|
|
|
5437
5928
|
};
|
|
5438
5929
|
|
|
5439
5930
|
// src/model/composer.ts
|
|
5440
|
-
var PROGRESSION_BAR_BEATS = 4;
|
|
5441
|
-
var DEFAULT_KEY = majorKey(0);
|
|
5442
|
-
function copyPlain2(value, name) {
|
|
5443
|
-
if (typeof value === "number") {
|
|
5444
|
-
return assertFiniteNumber(value, name);
|
|
5445
|
-
}
|
|
5446
|
-
if (Array.isArray(value)) {
|
|
5447
|
-
return value.map((item, index) => copyPlain2(item, `${name}[${index}]`));
|
|
5448
|
-
}
|
|
5449
|
-
if (typeof value === "object" && value !== null) {
|
|
5450
|
-
const copy = {};
|
|
5451
|
-
for (const [key, item] of Object.entries(value)) {
|
|
5452
|
-
if (item !== void 0) {
|
|
5453
|
-
copy[key] = copyPlain2(item, `${name}.${key}`);
|
|
5454
|
-
}
|
|
5455
|
-
}
|
|
5456
|
-
return copy;
|
|
5457
|
-
}
|
|
5458
|
-
if (typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") {
|
|
5459
|
-
throw new InvalidInputError(`${name} must be plain data; received ${typeof value}`);
|
|
5460
|
-
}
|
|
5461
|
-
return value;
|
|
5462
|
-
}
|
|
5463
5931
|
function toChordTimeline(harmony) {
|
|
5464
5932
|
return "chordTimeline" in harmony ? harmony.chordTimeline : harmony;
|
|
5465
5933
|
}
|
|
5466
|
-
function metersFrom3(meters) {
|
|
5467
|
-
if (meters === void 0) {
|
|
5468
|
-
return resolveMeters({}, "composer meters");
|
|
5469
|
-
}
|
|
5470
|
-
const meter = toMeterData(meters, "composer meters");
|
|
5471
|
-
return Array.isArray(meter) ? resolveMeters({ meters: meter }, "composer meters") : resolveMeters({ ts: meter }, "composer meters");
|
|
5472
|
-
}
|
|
5473
5934
|
function copyComplexity(complexity) {
|
|
5935
|
+
assertDataObject(complexity, "composer complexity");
|
|
5474
5936
|
const copy = {};
|
|
5475
5937
|
for (const dial of ["rhythmic", "harmonic", "ornament", "difficulty"]) {
|
|
5476
5938
|
const value = complexity[dial];
|
|
@@ -5481,6 +5943,7 @@ function copyComplexity(complexity) {
|
|
|
5481
5943
|
return copy;
|
|
5482
5944
|
}
|
|
5483
5945
|
function copyInstruments(instruments) {
|
|
5946
|
+
assertDataObject(instruments, "composer instruments");
|
|
5484
5947
|
const copy = {};
|
|
5485
5948
|
for (const [part, profile] of Object.entries(instruments)) {
|
|
5486
5949
|
copy[part] = Instrument.of(profile).data;
|
|
@@ -5488,9 +5951,12 @@ function copyInstruments(instruments) {
|
|
|
5488
5951
|
return copy;
|
|
5489
5952
|
}
|
|
5490
5953
|
function copyOptions(options) {
|
|
5491
|
-
|
|
5954
|
+
assertDataObject(options, "composer options");
|
|
5955
|
+
const copy = {
|
|
5956
|
+
meters: resolveMeters({ meters: options.meters }, "composer meters")
|
|
5957
|
+
};
|
|
5492
5958
|
if (options.key !== void 0) {
|
|
5493
|
-
copy.key =
|
|
5959
|
+
copy.key = resolveKey(options.key);
|
|
5494
5960
|
}
|
|
5495
5961
|
if (options.bpm !== void 0) {
|
|
5496
5962
|
copy.bpm = options.bpm;
|
|
@@ -5498,6 +5964,9 @@ function copyOptions(options) {
|
|
|
5498
5964
|
if (options.seed !== void 0) {
|
|
5499
5965
|
copy.seed = options.seed;
|
|
5500
5966
|
}
|
|
5967
|
+
if (options.algorithmVersion !== void 0) {
|
|
5968
|
+
copy.algorithmVersion = options.algorithmVersion;
|
|
5969
|
+
}
|
|
5501
5970
|
if (options.complexity !== void 0) {
|
|
5502
5971
|
copy.complexity = copyComplexity(options.complexity);
|
|
5503
5972
|
}
|
|
@@ -5505,10 +5974,17 @@ function copyOptions(options) {
|
|
|
5505
5974
|
copy.instruments = copyInstruments(options.instruments);
|
|
5506
5975
|
}
|
|
5507
5976
|
if (options.vocabulary !== void 0) {
|
|
5508
|
-
copy.vocabulary =
|
|
5509
|
-
|
|
5510
|
-
|
|
5977
|
+
copy.vocabulary = assertDataObjects(
|
|
5978
|
+
options.vocabulary,
|
|
5979
|
+
"composer vocabulary"
|
|
5980
|
+
).map((entry, index) => copyPlain(entry, `composer vocabulary[${index}]`));
|
|
5511
5981
|
}
|
|
5982
|
+
if (options.rng !== void 0) {
|
|
5983
|
+
copy.rng = options.rng;
|
|
5984
|
+
}
|
|
5985
|
+
const resolved = resolveContext(contextOf(copy));
|
|
5986
|
+
copy.seed = resolved.seed;
|
|
5987
|
+
copy.algorithmVersion = resolved.algorithmVersion;
|
|
5512
5988
|
return copy;
|
|
5513
5989
|
}
|
|
5514
5990
|
function contextOf(options) {
|
|
@@ -5516,6 +5992,12 @@ function contextOf(options) {
|
|
|
5516
5992
|
if (options.seed !== void 0) {
|
|
5517
5993
|
ctx.seed = options.seed;
|
|
5518
5994
|
}
|
|
5995
|
+
if (options.algorithmVersion !== void 0) {
|
|
5996
|
+
ctx.algorithmVersion = options.algorithmVersion;
|
|
5997
|
+
}
|
|
5998
|
+
if (options.rng !== void 0) {
|
|
5999
|
+
ctx.rng = options.rng;
|
|
6000
|
+
}
|
|
5519
6001
|
if (options.bpm !== void 0) {
|
|
5520
6002
|
ctx.bpm = options.bpm;
|
|
5521
6003
|
}
|
|
@@ -5526,7 +6008,10 @@ function contextOf(options) {
|
|
|
5526
6008
|
ctx.instruments = copyInstruments(options.instruments);
|
|
5527
6009
|
}
|
|
5528
6010
|
if (options.vocabulary !== void 0) {
|
|
5529
|
-
ctx.vocabulary =
|
|
6011
|
+
ctx.vocabulary = assertDataObjects(
|
|
6012
|
+
options.vocabulary,
|
|
6013
|
+
"composer vocabulary"
|
|
6014
|
+
).map((entry, index) => copyPlain(entry, `composer vocabulary[${index}]`));
|
|
5530
6015
|
}
|
|
5531
6016
|
return ctx;
|
|
5532
6017
|
}
|
|
@@ -5554,9 +6039,8 @@ var Composer = class _Composer {
|
|
|
5554
6039
|
*/
|
|
5555
6040
|
constructor(options) {
|
|
5556
6041
|
this.#options = copyOptions(options);
|
|
5557
|
-
|
|
5558
|
-
this.#
|
|
5559
|
-
this.#key = this.#options.key === void 0 ? void 0 : toKeyScale(this.#options.key);
|
|
6042
|
+
this.#meters = resolveMeters({ meters: this.#options.meters }, "composer meters");
|
|
6043
|
+
this.#key = this.#options.key === void 0 ? void 0 : resolveKey(this.#options.key);
|
|
5560
6044
|
}
|
|
5561
6045
|
/**
|
|
5562
6046
|
* Start from a set of settings.
|
|
@@ -5594,18 +6078,27 @@ var Composer = class _Composer {
|
|
|
5594
6078
|
/**
|
|
5595
6079
|
* A chord progression, in time.
|
|
5596
6080
|
*
|
|
5597
|
-
* The chords are laid out one per
|
|
5598
|
-
*
|
|
5599
|
-
*
|
|
6081
|
+
* The chords are laid out one per bar of the composer's own meter, so a chord
|
|
6082
|
+
* change falls on a bar line of the piece and the timeline spans `bars` of it:
|
|
6083
|
+
* four beats each in 4/4, three in 3/4, and three in 6/8. Every other part is
|
|
6084
|
+
* written against that grid, and harmony that is not on it drifts further from
|
|
6085
|
+
* the bar line with every repeat.
|
|
5600
6086
|
*
|
|
5601
|
-
* @param opts Everything the progression generator takes but the key
|
|
5602
|
-
* context, which are the composer's.
|
|
6087
|
+
* @param opts Everything the progression generator takes but the key, the
|
|
6088
|
+
* meter and the context, which are the composer's.
|
|
5603
6089
|
* @returns The chords over the beats they sound for.
|
|
6090
|
+
* @throws If the composer names no key: the chords are degrees of one, and
|
|
6091
|
+
* there is no melody here to read a key from the way
|
|
6092
|
+
* {@link Composer.harmonize} does.
|
|
6093
|
+
* @throws If the composer's meter changes: one chord per bar has no single bar
|
|
6094
|
+
* length to be laid out on, and the chords would leave the bar lines at the
|
|
6095
|
+
* first change rather than follow them.
|
|
5604
6096
|
*/
|
|
5605
6097
|
progression(opts) {
|
|
5606
|
-
const key = this.#
|
|
5607
|
-
const
|
|
5608
|
-
|
|
6098
|
+
const key = this.#requireKey("progression");
|
|
6099
|
+
const ts = this.#unchangingMeter("progression");
|
|
6100
|
+
const chords = generateProgression({ ...opts, key, ts, ctx: this.context });
|
|
6101
|
+
return Timeline.fromChords(chords, opts.bars * beatsPerBar(ts), key);
|
|
5609
6102
|
}
|
|
5610
6103
|
/**
|
|
5611
6104
|
* A drum part.
|
|
@@ -5618,11 +6111,13 @@ var Composer = class _Composer {
|
|
|
5618
6111
|
* @param opts Everything the drum generator takes but the meter and the
|
|
5619
6112
|
* context, which are the composer's.
|
|
5620
6113
|
* @returns The hits, as a score.
|
|
5621
|
-
* @throws If the composer's meter is not 4/4, which is the only
|
|
5622
|
-
* drum patterns are written against
|
|
6114
|
+
* @throws If the composer's meter is not 4/4 throughout, which is the only
|
|
6115
|
+
* meter the drum patterns are written against: a piece that opens in 4/4
|
|
6116
|
+
* and changes later has bars the patterns cannot be laid out on, so the
|
|
6117
|
+
* request is refused rather than answered with 4/4 bars over the change.
|
|
5623
6118
|
*/
|
|
5624
6119
|
drums(opts) {
|
|
5625
|
-
const hits = generateDrums({ ...opts, ts: this.#
|
|
6120
|
+
const hits = generateDrums({ ...opts, ts: this.#unchangingMeter("drums"), ctx: this.context });
|
|
5626
6121
|
return Score.of(hits, this.#scoreOptions());
|
|
5627
6122
|
}
|
|
5628
6123
|
/**
|
|
@@ -5633,6 +6128,8 @@ var Composer = class _Composer {
|
|
|
5633
6128
|
* @param opts Everything the bass generator takes but the segments, the key,
|
|
5634
6129
|
* the meter and the context; see {@link BassLineSettings}.
|
|
5635
6130
|
* @returns The line, as a score in the composer's key.
|
|
6131
|
+
* @throws If the composer names no key: the line is written in one, and a
|
|
6132
|
+
* default key would put the part in a key the caller never asked for.
|
|
5636
6133
|
* @throws If the instrument names a kit, which has no strings for a bass line
|
|
5637
6134
|
* to be placed on.
|
|
5638
6135
|
* @example
|
|
@@ -5645,7 +6142,7 @@ var Composer = class _Composer {
|
|
|
5645
6142
|
*/
|
|
5646
6143
|
bass(source, opts) {
|
|
5647
6144
|
const segments = Array.isArray(source) ? source : source.segments;
|
|
5648
|
-
const key = this.#
|
|
6145
|
+
const key = this.#requireKey("bass");
|
|
5649
6146
|
const { instrument, ...rest } = opts ?? {};
|
|
5650
6147
|
const notes = generateBassLine({
|
|
5651
6148
|
...rest,
|
|
@@ -5672,9 +6169,12 @@ var Composer = class _Composer {
|
|
|
5672
6169
|
* @param opts Everything the counter-melody generator takes but the melody,
|
|
5673
6170
|
* the key, the meter and the context; see {@link CounterMelodySettings}.
|
|
5674
6171
|
* @returns The counter line, as a score in the composer's key.
|
|
6172
|
+
* @throws If the composer names no key: the line is written in one, and the
|
|
6173
|
+
* melody it answers is not read for a key here — {@link Composer.harmonize}
|
|
6174
|
+
* is the member that does that.
|
|
5675
6175
|
*/
|
|
5676
6176
|
counterMelody(melody, opts) {
|
|
5677
|
-
const key = this.#
|
|
6177
|
+
const key = this.#requireKey("counterMelody");
|
|
5678
6178
|
const { timeline, ...rest } = opts ?? {};
|
|
5679
6179
|
const notes = generateCounterMelody({
|
|
5680
6180
|
...rest,
|
|
@@ -5700,8 +6200,7 @@ var Composer = class _Composer {
|
|
|
5700
6200
|
* that melody and the one handed in.
|
|
5701
6201
|
* @example
|
|
5702
6202
|
* ```ts
|
|
5703
|
-
* import { Composer } from '@libraz/libcantus';
|
|
5704
|
-
* import { Score } from '../../src/model/score.js';
|
|
6203
|
+
* import { Composer, Score } from '@libraz/libcantus';
|
|
5705
6204
|
* const composer = Composer.of({ key: 'C major' });
|
|
5706
6205
|
* const melody = Score.of([{ pitch: 60, startBeat: 0, durationBeat: 4 }]);
|
|
5707
6206
|
* composer.harmonize(melody).transposeSemitones; // 0
|
|
@@ -5733,29 +6232,59 @@ var Composer = class _Composer {
|
|
|
5733
6232
|
* Whether another composer holds the same settings.
|
|
5734
6233
|
*
|
|
5735
6234
|
* The comparison is made through the other composer's public data, so two
|
|
5736
|
-
* composers built by different copies of the module still compare.
|
|
6235
|
+
* composers built by different copies of the module still compare. Both sides
|
|
6236
|
+
* are projected the same way: comparing the settings held here against the
|
|
6237
|
+
* data the other hands out answered "different" for a composer holding a
|
|
6238
|
+
* source and "same" for two that draw from different ones.
|
|
6239
|
+
*
|
|
6240
|
+
* A source is compared by identity, being a handle on a stream rather than a
|
|
6241
|
+
* setting: two composers drawing from different sources write different
|
|
6242
|
+
* parts, and the numbers a source will hand out cannot be read from it.
|
|
5737
6243
|
*
|
|
5738
6244
|
* @param other The composer to compare.
|
|
5739
6245
|
* @returns True when both would generate the same parts.
|
|
5740
6246
|
*/
|
|
5741
6247
|
equals(other) {
|
|
5742
|
-
return samePlain(this
|
|
6248
|
+
return samePlain(this.data, other.data) && this.context.rng === other.context.rng;
|
|
5743
6249
|
}
|
|
5744
6250
|
/** The resolved context, for handing back to the function API. */
|
|
5745
6251
|
get context() {
|
|
5746
6252
|
return contextOf(this.#options);
|
|
5747
6253
|
}
|
|
5748
|
-
/**
|
|
6254
|
+
/**
|
|
6255
|
+
* A copy of the settings this composer holds: the seed, the resolved
|
|
6256
|
+
* algorithm version and the rest of the recipe, so a piece can be written
|
|
6257
|
+
* again from what a project file stored.
|
|
6258
|
+
*
|
|
6259
|
+
* The source a composer was handed is left out, being a live handle rather
|
|
6260
|
+
* than plain data; a composer built with one keeps it across
|
|
6261
|
+
* {@link Composer.with} and its siblings.
|
|
6262
|
+
*/
|
|
5749
6263
|
get data() {
|
|
5750
|
-
return copyOptions(this.#options);
|
|
6264
|
+
return copyOptions({ ...this.#options, rng: void 0 });
|
|
5751
6265
|
}
|
|
5752
6266
|
/** The plain form of the settings, for `JSON.stringify`. */
|
|
5753
6267
|
toJSON() {
|
|
5754
6268
|
return this.data;
|
|
5755
6269
|
}
|
|
5756
|
-
/**
|
|
5757
|
-
|
|
5758
|
-
|
|
6270
|
+
/**
|
|
6271
|
+
* The key a pitched part is written in.
|
|
6272
|
+
*
|
|
6273
|
+
* A composer that names none is refused rather than written in C major: a
|
|
6274
|
+
* default key is an answer the caller never gave, and the part would come
|
|
6275
|
+
* back sounding like a piece nobody asked for with nothing to say it had
|
|
6276
|
+
* been substituted. {@link Composer.harmonize} is the one member that can
|
|
6277
|
+
* answer without a key, because it has a melody to read one from; the key it
|
|
6278
|
+
* found comes back with the harmonization, and `with({ key })` is how it is
|
|
6279
|
+
* handed to the parts written next.
|
|
6280
|
+
*/
|
|
6281
|
+
#requireKey(part) {
|
|
6282
|
+
if (this.#key === void 0) {
|
|
6283
|
+
throw new InvalidInputError(
|
|
6284
|
+
`composer ${part} needs a key for the part it writes; the composer names none \u2014 give it one, or take the key harmonize reads off a melody and pass it on with with({ key })`
|
|
6285
|
+
);
|
|
6286
|
+
}
|
|
6287
|
+
return this.#key;
|
|
5759
6288
|
}
|
|
5760
6289
|
/**
|
|
5761
6290
|
* The signature the parts are written against.
|
|
@@ -5767,6 +6296,22 @@ var Composer = class _Composer {
|
|
|
5767
6296
|
#openingMeter() {
|
|
5768
6297
|
return meterAt(0, this.#meters);
|
|
5769
6298
|
}
|
|
6299
|
+
/**
|
|
6300
|
+
* The one signature a part laid out in bars is written against.
|
|
6301
|
+
*
|
|
6302
|
+
* A part that places material bar by bar has to know how long a bar is, and a
|
|
6303
|
+
* piece that changes meter has no one answer: the request is refused rather
|
|
6304
|
+
* than answered on the bar the piece opens in, which would put every bar after
|
|
6305
|
+
* the change somewhere the piece has no bar line.
|
|
6306
|
+
*/
|
|
6307
|
+
#unchangingMeter(part) {
|
|
6308
|
+
if (this.#meters.length > 1) {
|
|
6309
|
+
throw new InvalidInputError(
|
|
6310
|
+
`composer ${part} needs one meter for the whole part; the composer changes meter`
|
|
6311
|
+
);
|
|
6312
|
+
}
|
|
6313
|
+
return this.#openingMeter();
|
|
6314
|
+
}
|
|
5770
6315
|
/** The context every score a composer hands back is read against. */
|
|
5771
6316
|
#scoreOptions(key) {
|
|
5772
6317
|
const opts = { meters: this.#meters };
|
|
@@ -6072,7 +6617,7 @@ var Meter = class _Meter {
|
|
|
6072
6617
|
get beatsPerBar() {
|
|
6073
6618
|
return beatsPerBar(this.#ts);
|
|
6074
6619
|
}
|
|
6075
|
-
/** Main pulses (felt beats) in a bar:
|
|
6620
|
+
/** Main pulses (felt beats) in a bar: 2 in 6/8, 4 in 4/4. */
|
|
6076
6621
|
get pulsesPerBar() {
|
|
6077
6622
|
return pulsesPerBar(this.#ts);
|
|
6078
6623
|
}
|
|
@@ -6088,13 +6633,19 @@ var Meter = class _Meter {
|
|
|
6088
6633
|
* additive grouping and one is asked for.
|
|
6089
6634
|
*
|
|
6090
6635
|
* @param opts Set `grouping: true` to render an additive grouping. A
|
|
6091
|
-
* grouping counted in main pulses
|
|
6092
|
-
*
|
|
6636
|
+
* grouping counted in main pulses has no additive spelling: one whose
|
|
6637
|
+
* groups are all the same length states the division the bare signature
|
|
6638
|
+
* already has and falls back to the plain form, and one whose groups differ
|
|
6639
|
+
* is refused rather than printed as a bar that weighs its pulses otherwise.
|
|
6093
6640
|
* @returns The formatted signature.
|
|
6641
|
+
* @throws If `grouping: true` is asked of a grouping that no signature text
|
|
6642
|
+
* can spell: one counted in main pulses, on a compound numerator, whose
|
|
6643
|
+
* groups are not all the same length — 12/8 as `[1, 1, 2]`.
|
|
6094
6644
|
* @example
|
|
6095
6645
|
* ```ts
|
|
6096
6646
|
* import { Meter } from '@libraz/libcantus';
|
|
6097
6647
|
* Meter.of(7, 8, [2, 2, 3]).format({ grouping: true }); // '2+2+3/8'
|
|
6648
|
+
* Meter.of(12, 8, [1, 1, 1, 1]).format({ grouping: true }); // '12/8'
|
|
6098
6649
|
* ```
|
|
6099
6650
|
*/
|
|
6100
6651
|
format(opts = {}) {
|
|
@@ -6140,13 +6691,18 @@ var Meter = class _Meter {
|
|
|
6140
6691
|
*
|
|
6141
6692
|
* @param beatInQuarters Position in quarter-note beats.
|
|
6142
6693
|
* @param decimals Digits of the fractional beat to keep.
|
|
6143
|
-
* @returns The formatted position
|
|
6694
|
+
* @returns The formatted position in one of two forms: `bar.beat` on a felt
|
|
6695
|
+
* beat, e.g. `'3.2'` for bar 3, felt beat 2; and `bar.beat+fraction`
|
|
6696
|
+
* between felt beats, e.g. `'3.2+0.5'` halfway from the second felt beat to
|
|
6697
|
+
* the third. A reader of these strings has to take both, since most onsets
|
|
6698
|
+
* of an ordinary piece fall between pulses.
|
|
6144
6699
|
* @throws If the position is not finite or `decimals` is not an integer in
|
|
6145
6700
|
* 0..100.
|
|
6146
6701
|
* @example
|
|
6147
6702
|
* ```ts
|
|
6148
6703
|
* import { Meter } from '@libraz/libcantus';
|
|
6149
6704
|
* Meter.parse('6/8').formatPosition(7.5); // '3.2'
|
|
6705
|
+
* Meter.parse('6/8').formatPosition(8.25); // '3.2+0.5'
|
|
6150
6706
|
* ```
|
|
6151
6707
|
*/
|
|
6152
6708
|
formatPosition(beatInQuarters, decimals = 2) {
|
|
@@ -6207,7 +6763,7 @@ var Meter = class _Meter {
|
|
|
6207
6763
|
};
|
|
6208
6764
|
|
|
6209
6765
|
// src/model/motif.ts
|
|
6210
|
-
function
|
|
6766
|
+
function copyNote2(note) {
|
|
6211
6767
|
return {
|
|
6212
6768
|
pitch: withoutNegativeZero(note.pitch),
|
|
6213
6769
|
startBeat: withoutNegativeZero(note.startBeat),
|
|
@@ -6215,17 +6771,8 @@ function copyNote3(note) {
|
|
|
6215
6771
|
};
|
|
6216
6772
|
}
|
|
6217
6773
|
function copyCell(cell) {
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
}
|
|
6221
|
-
return {
|
|
6222
|
-
notes: cell.notes.map((note, index) => {
|
|
6223
|
-
if (note === void 0) {
|
|
6224
|
-
throw new InvalidInputError(`motif notes[${index}] must be a note; received undefined`);
|
|
6225
|
-
}
|
|
6226
|
-
return copyNote3(assertNoteEvent(note, `motif notes[${index}]`));
|
|
6227
|
-
})
|
|
6228
|
-
};
|
|
6774
|
+
assertDataObject(cell, "motif cell");
|
|
6775
|
+
return { notes: assertNoteEventArray(cell.notes, "motif notes").map(copyNote2) };
|
|
6229
6776
|
}
|
|
6230
6777
|
function chordFrom(chord) {
|
|
6231
6778
|
return chord === void 0 || chord === null ? chord : toChordData(chord);
|
|
@@ -6260,9 +6807,7 @@ var Motif = class _Motif {
|
|
|
6260
6807
|
* ```
|
|
6261
6808
|
*/
|
|
6262
6809
|
static generate(opts) {
|
|
6263
|
-
return new _Motif(
|
|
6264
|
-
generateMotif({ ...opts, key: toKeyScale(opts.key), chord: chordFrom(opts.chord) })
|
|
6265
|
-
);
|
|
6810
|
+
return new _Motif(generateMotif({ ...opts, chord: chordFrom(opts.chord) }));
|
|
6266
6811
|
}
|
|
6267
6812
|
/**
|
|
6268
6813
|
* Read a run of notes as a motif.
|
|
@@ -6288,30 +6833,29 @@ var Motif = class _Motif {
|
|
|
6288
6833
|
}
|
|
6289
6834
|
/** The notes of the cell, in the order the transforms read them. */
|
|
6290
6835
|
get notes() {
|
|
6291
|
-
return this.#cell.notes.map(
|
|
6836
|
+
return this.#cell.notes.map(copyNote2);
|
|
6292
6837
|
}
|
|
6293
6838
|
/** Where the cell stops sounding, measured from its own first onset. */
|
|
6294
6839
|
get totalBeats() {
|
|
6295
|
-
|
|
6296
|
-
return 0;
|
|
6297
|
-
}
|
|
6298
|
-
const start = Math.min(...this.#cell.notes.map((note) => note.startBeat));
|
|
6299
|
-
const end = Math.max(...this.#cell.notes.map((note) => note.startBeat + note.durationBeat));
|
|
6300
|
-
return end - start;
|
|
6840
|
+
return cellSpan(this.#cell);
|
|
6301
6841
|
}
|
|
6302
6842
|
/** A copy of the underlying plain cell. */
|
|
6303
6843
|
get data() {
|
|
6304
|
-
return { notes: this.#cell.notes.map(
|
|
6844
|
+
return { notes: this.#cell.notes.map(copyNote2) };
|
|
6305
6845
|
}
|
|
6306
6846
|
/**
|
|
6307
6847
|
* The cell put through one of the classical transformations.
|
|
6308
6848
|
*
|
|
6309
|
-
* `invert` reflects the pitches about the
|
|
6310
|
-
* the onsets about the cell's span, both self-inverse; `augment` and
|
|
6849
|
+
* `invert` reflects the pitches about the earliest note and `retrograde`
|
|
6850
|
+
* mirrors the onsets about the cell's span, both self-inverse; `augment` and
|
|
6311
6851
|
* `diminish` scale the note values by `amount` and its reciprocal;
|
|
6312
6852
|
* `transposeChromatic` moves by semitones; `transposeDiatonic` and `sequence`
|
|
6313
6853
|
* move by scale degrees with a key in hand, and by semitones without one.
|
|
6314
6854
|
*
|
|
6855
|
+
* The result's notes run in time order whichever way the source cell was
|
|
6856
|
+
* written, so an inversion of a retrograde reflects about the note that now
|
|
6857
|
+
* sounds first.
|
|
6858
|
+
*
|
|
6315
6859
|
* @param kind The transformation.
|
|
6316
6860
|
* @param amount Semitones, scale degrees, or the time factor, as the
|
|
6317
6861
|
* transformation reads it.
|
|
@@ -6330,9 +6874,7 @@ var Motif = class _Motif {
|
|
|
6330
6874
|
* ```
|
|
6331
6875
|
*/
|
|
6332
6876
|
transform(kind, amount, key) {
|
|
6333
|
-
return new _Motif(
|
|
6334
|
-
transformMotif(this.#cell, kind, amount, key === void 0 ? void 0 : toKeyScale(key))
|
|
6335
|
-
);
|
|
6877
|
+
return new _Motif(transformMotif(this.#cell, kind, amount, key));
|
|
6336
6878
|
}
|
|
6337
6879
|
/**
|
|
6338
6880
|
* The cell laid across a span and snapped to the harmony under it.
|
|
@@ -6345,29 +6887,30 @@ var Motif = class _Motif {
|
|
|
6345
6887
|
* plain chord timeline the analysis layer hands out.
|
|
6346
6888
|
* @param key The key the passing notes are kept in.
|
|
6347
6889
|
* @param bars How many bars to fill.
|
|
6348
|
-
* @param ts Meter the bars are counted in
|
|
6890
|
+
* @param ts Meter the bars are counted in, in any form that names one. A
|
|
6891
|
+
* motif carries no meter of its own — it is a run of notes — so there is
|
|
6892
|
+
* nothing to fall back on, and a development laid on the wrong bar length
|
|
6893
|
+
* drifts off the part it plays under.
|
|
6349
6894
|
* @returns The developed motif.
|
|
6350
6895
|
* @throws If the bar count is not a positive integer.
|
|
6351
6896
|
*/
|
|
6352
6897
|
develop(timeline, key, bars, ts) {
|
|
6353
6898
|
const chords = "chordTimeline" in timeline ? timeline.chordTimeline : timeline;
|
|
6354
|
-
return new _Motif(developMotif(this.#cell, chords,
|
|
6899
|
+
return new _Motif(developMotif(this.#cell, chords, key, bars, ts));
|
|
6355
6900
|
}
|
|
6356
6901
|
/**
|
|
6357
6902
|
* How this motif stands to another: the transformation that turns one into
|
|
6358
6903
|
* the other, named.
|
|
6359
6904
|
*
|
|
6360
6905
|
* @param other The statement to name against this one.
|
|
6361
|
-
* @param key Key context for the tonal reading
|
|
6362
|
-
* only
|
|
6906
|
+
* @param key Key context for the tonal reading. Without one an answer holding
|
|
6907
|
+
* only diatonically is left unnamed unless its interval pattern also fits
|
|
6908
|
+
* the retrograde family, in which case it is named there; a motif carries no
|
|
6909
|
+
* key of its own, so this is the only way to have the tonal reading offered.
|
|
6363
6910
|
* @returns The relation, or null when the two stand in none.
|
|
6364
6911
|
*/
|
|
6365
6912
|
relateTo(other, key) {
|
|
6366
|
-
return relateMotifs(
|
|
6367
|
-
motifFromNotes(this.#cell.notes),
|
|
6368
|
-
motifFromNotes(other.notes),
|
|
6369
|
-
key === void 0 ? void 0 : toKeyScale(key)
|
|
6370
|
-
);
|
|
6913
|
+
return relateMotifs(motifFromNotes(this.#cell.notes), motifFromNotes(other.notes), key);
|
|
6371
6914
|
}
|
|
6372
6915
|
/**
|
|
6373
6916
|
* How alike two lines are, in [0, 1].
|
|
@@ -6418,7 +6961,6 @@ function sameNote2(note, other) {
|
|
|
6418
6961
|
}
|
|
6419
6962
|
|
|
6420
6963
|
// src/model/rhythm.ts
|
|
6421
|
-
var DEFAULT_TS = { numerator: 4, denominator: 4 };
|
|
6422
6964
|
var GRID_VELOCITY = 1;
|
|
6423
6965
|
var PART = "rhythm";
|
|
6424
6966
|
function copyEvent(event, name) {
|
|
@@ -6432,17 +6974,10 @@ function copyEvent(event, name) {
|
|
|
6432
6974
|
};
|
|
6433
6975
|
}
|
|
6434
6976
|
function copyEvents(events, name) {
|
|
6435
|
-
|
|
6436
|
-
throw new InvalidInputError(`${name} must be an array; received ${typeof events}`);
|
|
6437
|
-
}
|
|
6438
|
-
return events.map((event, index) => {
|
|
6439
|
-
if (event === void 0) {
|
|
6440
|
-
throw new InvalidInputError(`${name}[${index}] must be an onset; received undefined`);
|
|
6441
|
-
}
|
|
6442
|
-
return copyEvent(event, `${name}[${index}]`);
|
|
6443
|
-
}).sort((a, b) => a.position - b.position || a.duration - b.duration);
|
|
6977
|
+
return assertDataObjects(events, name).map((event, index) => copyEvent(event, `${name}[${index}]`)).sort((a, b) => a.position - b.position || a.duration - b.duration);
|
|
6444
6978
|
}
|
|
6445
6979
|
function copyRhythm(data) {
|
|
6980
|
+
assertDataObject(data, "rhythm data");
|
|
6446
6981
|
return {
|
|
6447
6982
|
events: copyEvents(data.events, "rhythm events"),
|
|
6448
6983
|
ts: copyTimeSignature(data.ts, "rhythm ts")
|
|
@@ -6482,16 +7017,16 @@ var Rhythm = class _Rhythm {
|
|
|
6482
7017
|
/**
|
|
6483
7018
|
* Generate a pattern over a meter.
|
|
6484
7019
|
*
|
|
6485
|
-
* @param ts The time signature the bars are counted in
|
|
7020
|
+
* @param ts The time signature the bars are counted in, in any form that
|
|
7021
|
+
* names one; a meter map is read as the signature it opens in.
|
|
6486
7022
|
* @param opts Length, grid resolution and the context; see
|
|
6487
7023
|
* {@link RhythmOptions}. The context's `complexity.rhythmic` is the onset
|
|
6488
7024
|
* density, and its `seed` fixes which grid slots are taken.
|
|
6489
7025
|
* @returns The generated pattern.
|
|
6490
7026
|
* @example
|
|
6491
7027
|
* ```ts
|
|
6492
|
-
* import { parseTimeSignature } from '@libraz/libcantus';
|
|
6493
7028
|
* import { Rhythm } from '@libraz/libcantus';
|
|
6494
|
-
* const rhythm = Rhythm.generate(
|
|
7029
|
+
* const rhythm = Rhythm.generate('4/4', {
|
|
6495
7030
|
* bars: 2,
|
|
6496
7031
|
* ctx: { seed: 7, complexity: { rhythmic: 0.6 } },
|
|
6497
7032
|
* });
|
|
@@ -6499,18 +7034,29 @@ var Rhythm = class _Rhythm {
|
|
|
6499
7034
|
* ```
|
|
6500
7035
|
*/
|
|
6501
7036
|
static generate(ts, opts) {
|
|
6502
|
-
|
|
7037
|
+
const meter = meterAt(0, toMeterData(ts, "ts"));
|
|
7038
|
+
return new _Rhythm({ events: generateRhythm(meter, opts), ts: meter });
|
|
6503
7039
|
}
|
|
6504
7040
|
/**
|
|
6505
7041
|
* Build a pattern from onsets that already exist.
|
|
6506
7042
|
*
|
|
7043
|
+
* The meter is asked for rather than assumed. A pattern in 6/8 read as 4/4
|
|
7044
|
+
* answers every metric question — which beats are strong, where the bars
|
|
7045
|
+
* fall, how dense it is — on the wrong pulse, and nothing downstream can tell
|
|
7046
|
+
* that reading from a meter the caller meant. Its two siblings,
|
|
7047
|
+
* {@link Rhythm.generate} and `generateRhythm`, ask for it the same way.
|
|
7048
|
+
*
|
|
6507
7049
|
* @param events The onsets, in any order.
|
|
6508
|
-
* @param ts The meter they are counted in
|
|
6509
|
-
* meter-aware function in the library assumes.
|
|
7050
|
+
* @param ts The meter they are counted in, in any form that names one.
|
|
6510
7051
|
* @returns The pattern.
|
|
7052
|
+
* @throws If the meter names no signature, or an onset carries a value a
|
|
7053
|
+
* pattern cannot hold.
|
|
6511
7054
|
*/
|
|
6512
|
-
static of(events, ts
|
|
6513
|
-
return new _Rhythm({
|
|
7055
|
+
static of(events, ts) {
|
|
7056
|
+
return new _Rhythm({
|
|
7057
|
+
events: [...assertDataArray(events, "rhythm events")],
|
|
7058
|
+
ts: meterAt(0, toMeterData(ts, "ts"))
|
|
7059
|
+
});
|
|
6514
7060
|
}
|
|
6515
7061
|
/** Rebuild a pattern from the plain data {@link Rhythm.data} hands out. */
|
|
6516
7062
|
static fromData(data) {
|
|
@@ -6550,19 +7096,26 @@ var Rhythm = class _Rhythm {
|
|
|
6550
7096
|
/**
|
|
6551
7097
|
* Drop the onsets carrying the least of the metre.
|
|
6552
7098
|
*
|
|
7099
|
+
* The metre is the pattern's own: the ranks are read from {@link Rhythm.ts},
|
|
7100
|
+
* so a pattern in 6/8 keeps its dotted-quarter pulses and one in 3/4 keeps its
|
|
7101
|
+
* three-beat downbeats rather than the beats a four-beat bar would have.
|
|
7102
|
+
*
|
|
6553
7103
|
* @param amount How much to thin, in [0, 1]; at 0 nothing goes, and a full
|
|
6554
7104
|
* turn leaves the downbeats alone.
|
|
6555
7105
|
* @returns The thinned pattern.
|
|
6556
7106
|
* @throws If the amount is outside [0, 1].
|
|
6557
7107
|
*/
|
|
6558
7108
|
thin(amount) {
|
|
6559
|
-
return this.#withGrid(thin(toGrid(this.#data.events), amount));
|
|
7109
|
+
return this.#withGrid(thin(toGrid(this.#data.events), amount, this.#data.ts));
|
|
6560
7110
|
}
|
|
6561
7111
|
/**
|
|
6562
7112
|
* Anticipate beats, one sixteenth early.
|
|
6563
7113
|
*
|
|
6564
|
-
* The
|
|
6565
|
-
*
|
|
7114
|
+
* The beats anticipated are the ones {@link Rhythm.ts} counts as main pulses
|
|
7115
|
+
* or stronger, so a pattern is not syncopated against accents its own meter
|
|
7116
|
+
* does not have. The anticipations are added rather than displaced, so raising
|
|
7117
|
+
* the amount only ever adds onsets and the ones already sounding stay where
|
|
7118
|
+
* they are.
|
|
6566
7119
|
*
|
|
6567
7120
|
* @param amount How much syncopation, in [0, 1].
|
|
6568
7121
|
* @param ctx The context, or the seed alone; it fixes which beats are
|
|
@@ -6572,7 +7125,11 @@ var Rhythm = class _Rhythm {
|
|
|
6572
7125
|
*/
|
|
6573
7126
|
syncopate(amount, ctx) {
|
|
6574
7127
|
return this.#withGrid(
|
|
6575
|
-
syncopate(
|
|
7128
|
+
syncopate(
|
|
7129
|
+
toGrid(this.#data.events),
|
|
7130
|
+
{ amount, ts: this.#data.ts },
|
|
7131
|
+
resolveContext(ctx).part(PART)
|
|
7132
|
+
)
|
|
6576
7133
|
);
|
|
6577
7134
|
}
|
|
6578
7135
|
/**
|
|
@@ -6642,6 +7199,7 @@ var Rhythm = class _Rhythm {
|
|
|
6642
7199
|
ornament: resolved.ornament,
|
|
6643
7200
|
rate: opts?.rate,
|
|
6644
7201
|
spanSteps: this.#spanSteps(),
|
|
7202
|
+
ts: this.#data.ts,
|
|
6645
7203
|
// The transform hands the predicate the events it was given, which carry
|
|
6646
7204
|
// the onset each was built from; the parameter is declared over the bare
|
|
6647
7205
|
// grid event the rule reads, so the field is named back here.
|
|
@@ -6674,7 +7232,9 @@ var Rhythm = class _Rhythm {
|
|
|
6674
7232
|
* @param pitch The MIDI pitch to give every onset.
|
|
6675
7233
|
* @param velocity Velocity for every onset; a mezzo-forte 96 by default.
|
|
6676
7234
|
* @returns The score holding the pattern.
|
|
6677
|
-
* @throws If the pitch is not
|
|
7235
|
+
* @throws If the pitch or the velocity is not a whole MIDI value in [0, 127];
|
|
7236
|
+
* both are the numbers a note event carries, and a fractional one is not a
|
|
7237
|
+
* note anything downstream can sound.
|
|
6678
7238
|
* @example
|
|
6679
7239
|
* ```ts
|
|
6680
7240
|
* import { parseTimeSignature } from '@libraz/libcantus';
|
|
@@ -6729,10 +7289,11 @@ function sameSignature(ts, other) {
|
|
|
6729
7289
|
|
|
6730
7290
|
// src/model/tempo.ts
|
|
6731
7291
|
var MAX_BPM = 1e3;
|
|
7292
|
+
var MIN_BPM = 0.1;
|
|
6732
7293
|
var Tempo = class _Tempo {
|
|
6733
7294
|
#bpm;
|
|
6734
7295
|
constructor(bpm) {
|
|
6735
|
-
this.#bpm = assertRange(bpm,
|
|
7296
|
+
this.#bpm = assertRange(bpm, MIN_BPM, MAX_BPM, "bpm");
|
|
6736
7297
|
}
|
|
6737
7298
|
/**
|
|
6738
7299
|
* The one-event tempo map every conversion here is measured against.
|
|
@@ -6761,7 +7322,7 @@ var Tempo = class _Tempo {
|
|
|
6761
7322
|
* @throws If the marking is not a positive, finite number the library holds.
|
|
6762
7323
|
*/
|
|
6763
7324
|
static fromData(data) {
|
|
6764
|
-
return new _Tempo(data.bpm);
|
|
7325
|
+
return new _Tempo(assertDataObject(data, "tempo data").bpm);
|
|
6765
7326
|
}
|
|
6766
7327
|
/**
|
|
6767
7328
|
* Rebuild a tempo from its {@link Tempo.toJSON} output.
|
|
@@ -6771,7 +7332,7 @@ var Tempo = class _Tempo {
|
|
|
6771
7332
|
* @throws If the marking is not a positive, finite number the library holds.
|
|
6772
7333
|
*/
|
|
6773
7334
|
static fromJSON(data) {
|
|
6774
|
-
return new _Tempo(data.bpm);
|
|
7335
|
+
return new _Tempo(assertDataObject(data, "tempo data").bpm);
|
|
6775
7336
|
}
|
|
6776
7337
|
/** Quarter-note beats per minute. */
|
|
6777
7338
|
get bpm() {
|
|
@@ -6885,23 +7446,29 @@ var Tuning = class _Tuning {
|
|
|
6885
7446
|
/**
|
|
6886
7447
|
* Wrap a plain tuning table.
|
|
6887
7448
|
*
|
|
6888
|
-
*
|
|
6889
|
-
*
|
|
6890
|
-
*
|
|
7449
|
+
* Every field is read off the table, so a deserialized or hand-written one
|
|
7450
|
+
* cannot enter carrying a division count or a reference frequency no
|
|
7451
|
+
* frequency follows from. None of the three has a default here, unlike
|
|
7452
|
+
* {@link Tuning.edo}: a table that lost its reference frequency on the way
|
|
7453
|
+
* through storage is refused rather than quietly retuned to A=440.
|
|
6891
7454
|
*
|
|
6892
7455
|
* @param table The plain tuning table.
|
|
6893
7456
|
* @returns The wrapped tuning.
|
|
6894
|
-
* @throws If the divisions are not a positive integer,
|
|
6895
|
-
*
|
|
7457
|
+
* @throws If a field is missing, the divisions are not a positive integer,
|
|
7458
|
+
* the reference frequency is not a finite positive number, or the reference
|
|
7459
|
+
* step is not finite.
|
|
6896
7460
|
* @example
|
|
6897
7461
|
* ```ts
|
|
6898
|
-
* import { TWELVE_TET } from '
|
|
6899
|
-
* import { Tuning } from '@libraz/libcantus';
|
|
7462
|
+
* import { TWELVE_TET, Tuning } from '@libraz/libcantus';
|
|
6900
7463
|
* Tuning.of(TWELVE_TET).divisions; // 12
|
|
6901
7464
|
* ```
|
|
6902
7465
|
*/
|
|
6903
7466
|
static of(table) {
|
|
6904
|
-
|
|
7467
|
+
const given = assertDataObject(table, "tuning table");
|
|
7468
|
+
assertFiniteNumber(given.divisions, "tuning table.divisions");
|
|
7469
|
+
assertFiniteNumber(given.refFreq, "tuning table.refFreq");
|
|
7470
|
+
assertFiniteNumber(given.refStep, "tuning table.refStep");
|
|
7471
|
+
return new _Tuning(edo(given.divisions, given.refFreq, given.refStep));
|
|
6905
7472
|
}
|
|
6906
7473
|
/**
|
|
6907
7474
|
* Build an equal temperament with `divisions` divisions of the octave.
|
|
@@ -6911,8 +7478,10 @@ var Tuning = class _Tuning {
|
|
|
6911
7478
|
* @param refStep Step index of the reference; 69 by default, the MIDI number
|
|
6912
7479
|
* of A4.
|
|
6913
7480
|
* @returns The tuning.
|
|
6914
|
-
* @throws If the divisions are not a positive integer,
|
|
6915
|
-
*
|
|
7481
|
+
* @throws If the divisions are not a positive integer, the reference
|
|
7482
|
+
* frequency is not a finite positive number, or the reference step is not
|
|
7483
|
+
* finite. A negative or fractional reference step is accepted: it is what a
|
|
7484
|
+
* tuner reading or a pitch-bend calibration lands on.
|
|
6916
7485
|
* @example
|
|
6917
7486
|
* ```ts
|
|
6918
7487
|
* import { Tuning } from '@libraz/libcantus';
|
|
@@ -6981,7 +7550,8 @@ var Tuning = class _Tuning {
|
|
|
6981
7550
|
*
|
|
6982
7551
|
* @param cents The interval in cents.
|
|
6983
7552
|
* @returns The frequency ratio.
|
|
6984
|
-
* @throws If `cents` is not finite
|
|
7553
|
+
* @throws If `cents` is not finite, or the ratio it spans falls outside the
|
|
7554
|
+
* range a number holds.
|
|
6985
7555
|
* @example
|
|
6986
7556
|
* ```ts
|
|
6987
7557
|
* import { Tuning } from '@libraz/libcantus';
|
|
@@ -7219,20 +7789,34 @@ var Tuning = class _Tuning {
|
|
|
7219
7789
|
|
|
7220
7790
|
// src/model/voicing.ts
|
|
7221
7791
|
function copyPitches(pitches) {
|
|
7792
|
+
assertDataArray(pitches, "voicing pitches");
|
|
7222
7793
|
if (pitches.length === 0) {
|
|
7223
7794
|
throw new InvalidInputError("a voicing must contain at least one pitch");
|
|
7224
7795
|
}
|
|
7225
7796
|
return pitches.map((pitch, index) => assertMidiPitch(pitch, `pitches[${index}]`));
|
|
7226
7797
|
}
|
|
7227
7798
|
function spellLine(line, key) {
|
|
7228
|
-
const { tonic } = spelledKeyOf(key);
|
|
7229
7799
|
return line.map(
|
|
7230
|
-
(value) => typeof value === "number" ? spellPitch(value, tonic, key) : toNoteData(value)
|
|
7800
|
+
(value) => typeof value === "number" ? spellPitch(value, key.tonic, key) : toNoteData(value)
|
|
7231
7801
|
);
|
|
7232
7802
|
}
|
|
7233
7803
|
function lineOf(pitches, key) {
|
|
7234
7804
|
return key === void 0 ? pitches.map((pitch) => toNoteData(pitch)) : spellLine(pitches, key);
|
|
7235
7805
|
}
|
|
7806
|
+
function carriedKeyOf(chord) {
|
|
7807
|
+
if (typeof chord !== "object" || chord === null) {
|
|
7808
|
+
return void 0;
|
|
7809
|
+
}
|
|
7810
|
+
const key = chord.key;
|
|
7811
|
+
return typeof key === "object" && key !== null ? key : void 0;
|
|
7812
|
+
}
|
|
7813
|
+
function withCarriedKey(chord, opts) {
|
|
7814
|
+
if (opts?.key !== void 0) {
|
|
7815
|
+
return opts;
|
|
7816
|
+
}
|
|
7817
|
+
const key = carriedKeyOf(chord);
|
|
7818
|
+
return key === void 0 ? opts : { ...opts, key };
|
|
7819
|
+
}
|
|
7236
7820
|
var Voicing = class _Voicing {
|
|
7237
7821
|
#pitches;
|
|
7238
7822
|
/**
|
|
@@ -7273,7 +7857,7 @@ var Voicing = class _Voicing {
|
|
|
7273
7857
|
* ```
|
|
7274
7858
|
*/
|
|
7275
7859
|
static forChord(chord, opts) {
|
|
7276
|
-
return new _Voicing(voiceChordStyled(
|
|
7860
|
+
return new _Voicing(voiceChordStyled(chord, opts));
|
|
7277
7861
|
}
|
|
7278
7862
|
/**
|
|
7279
7863
|
* Realize a chord as one pitch per voice inside the voice ranges, defaulting
|
|
@@ -7282,6 +7866,10 @@ var Voicing = class _Voicing {
|
|
|
7282
7866
|
* The bass takes the chord's slash bass when it has one, otherwise the root;
|
|
7283
7867
|
* the result is compact, centred in its ranges, and free of voice crossing.
|
|
7284
7868
|
*
|
|
7869
|
+
* A chord that carries a key passes it to the voicer, so the leading tone is
|
|
7870
|
+
* not doubled; an explicit `opts.key` overrides it. That is the order
|
|
7871
|
+
* {@link Chord.voice} resolves them in, and the two answer alike.
|
|
7872
|
+
*
|
|
7285
7873
|
* @param chord A chord symbol, plain chord data, or a {@link Chord}.
|
|
7286
7874
|
* @param opts Voicing options; defaults to four voices in {@link SATB_RANGES}.
|
|
7287
7875
|
* @returns The voicing, ascending.
|
|
@@ -7293,7 +7881,7 @@ var Voicing = class _Voicing {
|
|
|
7293
7881
|
* ```
|
|
7294
7882
|
*/
|
|
7295
7883
|
static satb(chord, opts) {
|
|
7296
|
-
return new _Voicing(voiceChord(toChordData(chord), opts));
|
|
7884
|
+
return new _Voicing(voiceChord(toChordData(chord), withCarriedKey(chord, opts)));
|
|
7297
7885
|
}
|
|
7298
7886
|
/**
|
|
7299
7887
|
* Wrap a plain pitch array, as {@link Voicing.data} hands it out.
|
|
@@ -7353,7 +7941,7 @@ var Voicing = class _Voicing {
|
|
|
7353
7941
|
* @param key A key name, a plain key/scale, or a {@link Key}.
|
|
7354
7942
|
* @param chord The chord sounding, supplying the enharmonic evidence; without
|
|
7355
7943
|
* it the key alone decides.
|
|
7356
|
-
* @returns One spelled note per pitch,
|
|
7944
|
+
* @returns One spelled note per pitch, in voice order.
|
|
7357
7945
|
* @throws If the value names no key or no chord.
|
|
7358
7946
|
* @example
|
|
7359
7947
|
* ```ts
|
|
@@ -7363,7 +7951,8 @@ var Voicing = class _Voicing {
|
|
|
7363
7951
|
* ```
|
|
7364
7952
|
*/
|
|
7365
7953
|
spell(key, chord) {
|
|
7366
|
-
|
|
7954
|
+
assertKeyArgument(key, "voicing key");
|
|
7955
|
+
return this.#spelled(resolveKey(key), chord).map((note) => new Note(note));
|
|
7367
7956
|
}
|
|
7368
7957
|
/**
|
|
7369
7958
|
* Voice a chord to follow smoothly from this voicing.
|
|
@@ -7372,7 +7961,13 @@ var Voicing = class _Voicing {
|
|
|
7372
7961
|
* `voices` nor `ranges` is given — within a one-octave window around each of
|
|
7373
7962
|
* these pitches, so the answer keeps this voicing's voice count. Each is
|
|
7374
7963
|
* scored by structural quality, motion from here, and a large penalty per
|
|
7375
|
-
* counterpoint violation.
|
|
7964
|
+
* counterpoint violation. A chord that carries a key passes it to the voicer
|
|
7965
|
+
* unless `opts.key` names another one.
|
|
7966
|
+
*
|
|
7967
|
+
* The rules read from the chord being left — a chordal seventh's resolution,
|
|
7968
|
+
* a leading tone's resolution, and the cross relation between the two chords
|
|
7969
|
+
* — are scored only when `opts.previousChord` names that chord: a voicing
|
|
7970
|
+
* does not say what it was written on.
|
|
7376
7971
|
*
|
|
7377
7972
|
* @param chord A chord symbol, plain chord data, or a {@link Chord}.
|
|
7378
7973
|
* @param opts Voicing options; when omitted, the ranges follow this voicing.
|
|
@@ -7387,12 +7982,12 @@ var Voicing = class _Voicing {
|
|
|
7387
7982
|
* ```
|
|
7388
7983
|
*/
|
|
7389
7984
|
next(chord, opts) {
|
|
7390
|
-
return new _Voicing(nextVoicing(this.pitches, toChordData(chord), opts));
|
|
7985
|
+
return new _Voicing(nextVoicing(this.pitches, toChordData(chord), withCarriedKey(chord, opts)));
|
|
7391
7986
|
}
|
|
7392
7987
|
/**
|
|
7393
7988
|
* The voice-leading cost from this voicing to another: the summed absolute
|
|
7394
|
-
* semitone motion across voices,
|
|
7395
|
-
*
|
|
7989
|
+
* semitone motion across voices, and nothing besides. What the motion breaks
|
|
7990
|
+
* is {@link Voicing.checkTo}'s question.
|
|
7396
7991
|
*
|
|
7397
7992
|
* @param other The voicing moved to.
|
|
7398
7993
|
* @returns The cost, or `Infinity` when the two hold a different number of
|
|
@@ -7429,14 +8024,15 @@ var Voicing = class _Voicing {
|
|
|
7429
8024
|
* ```
|
|
7430
8025
|
*/
|
|
7431
8026
|
checkTo(other, chords, key, opts) {
|
|
7432
|
-
|
|
8027
|
+
assertKeyArgument(key, "voicing key");
|
|
8028
|
+
const resolved = resolveKey(key);
|
|
7433
8029
|
const from = toChordData(chords[0]);
|
|
7434
8030
|
const to = toChordData(chords[1]);
|
|
7435
8031
|
const voicings = [
|
|
7436
|
-
spellVoicing(this.pitches, from,
|
|
7437
|
-
spellVoicing(other.pitches, to,
|
|
8032
|
+
spellVoicing(this.pitches, from, resolved),
|
|
8033
|
+
spellVoicing(other.pitches, to, resolved)
|
|
7438
8034
|
];
|
|
7439
|
-
return checkPartWriting(voicings, [from, to],
|
|
8035
|
+
return checkPartWriting(voicings, [from, to], resolved, opts);
|
|
7440
8036
|
}
|
|
7441
8037
|
/**
|
|
7442
8038
|
* Mark this voicing, read as a written counterpoint line, against a cantus
|
|
@@ -7465,12 +8061,13 @@ var Voicing = class _Voicing {
|
|
|
7465
8061
|
* ```
|
|
7466
8062
|
*/
|
|
7467
8063
|
species(cantusFirmus, species, mode, opts) {
|
|
7468
|
-
|
|
8064
|
+
assertKeyArgument(mode, "voicing mode");
|
|
8065
|
+
const resolved = resolveKey(mode);
|
|
7469
8066
|
return checkSpecies(
|
|
7470
|
-
spellLine(cantusFirmus,
|
|
7471
|
-
spellLine(this.#pitches,
|
|
8067
|
+
spellLine(cantusFirmus, resolved),
|
|
8068
|
+
spellLine(this.#pitches, resolved),
|
|
7472
8069
|
species,
|
|
7473
|
-
|
|
8070
|
+
resolved,
|
|
7474
8071
|
opts
|
|
7475
8072
|
);
|
|
7476
8073
|
}
|
|
@@ -7492,8 +8089,12 @@ var Voicing = class _Voicing {
|
|
|
7492
8089
|
*/
|
|
7493
8090
|
independence(other, opts) {
|
|
7494
8091
|
const { key, ...rest } = opts ?? {};
|
|
7495
|
-
const
|
|
7496
|
-
return voiceIndependence(
|
|
8092
|
+
const resolved = key === void 0 ? void 0 : resolveKey(key);
|
|
8093
|
+
return voiceIndependence(
|
|
8094
|
+
lineOf(this.#pitches, resolved),
|
|
8095
|
+
lineOf(other.pitches, resolved),
|
|
8096
|
+
rest
|
|
8097
|
+
);
|
|
7497
8098
|
}
|
|
7498
8099
|
/**
|
|
7499
8100
|
* Judge a candidate pitch placed against these voices.
|
|
@@ -7505,7 +8106,8 @@ var Voicing = class _Voicing {
|
|
|
7505
8106
|
* @param pitch The candidate MIDI pitch.
|
|
7506
8107
|
* @param query The candidate's harmonic and voice-leading context.
|
|
7507
8108
|
* @param opts Set `suggestions: false` to skip the search for safe
|
|
7508
|
-
* replacements when the verdict is not safe
|
|
8109
|
+
* replacements when the verdict is not safe, and `partners: true` to learn
|
|
8110
|
+
* which voice of this voicing, by index, raised each pairwise flag.
|
|
7509
8111
|
* @returns The verdict, reason bitmask, and optional resolution guidance.
|
|
7510
8112
|
* @throws If the profile is unknown, a value names no chord or key, or a
|
|
7511
8113
|
* pitch is not a MIDI pitch in 0..127.
|
|
@@ -7541,13 +8143,14 @@ var Voicing = class _Voicing {
|
|
|
7541
8143
|
* times over.
|
|
7542
8144
|
*
|
|
7543
8145
|
* A negative `n` takes the highest voice down an octave as many times, and
|
|
7544
|
-
* `invert(0)` is the voicing itself
|
|
7545
|
-
* rotates a chord's interval template, this moves
|
|
7546
|
-
* four-voice texture four times leaves the same chord
|
|
7547
|
-
*
|
|
8146
|
+
* `invert(0)` is the voicing itself, voice order and all. Unlike
|
|
8147
|
+
* {@link Chord.invert}, which rotates a chord's interval template, this moves
|
|
8148
|
+
* real voices: inverting a four-voice texture four times leaves the same chord
|
|
8149
|
+
* an octave higher. A voicing that actually inverts comes back ascending,
|
|
8150
|
+
* since the voice that moved is no longer where it was.
|
|
7548
8151
|
*
|
|
7549
8152
|
* @param n How many voices to move, upward when positive.
|
|
7550
|
-
* @returns The inverted voicing, ascending.
|
|
8153
|
+
* @returns The inverted voicing, ascending; this voicing itself when `n` is 0.
|
|
7551
8154
|
* @throws If `n` is not an integer in [-128, 128], or a voice would leave
|
|
7552
8155
|
* MIDI 0..127.
|
|
7553
8156
|
* @example
|
|
@@ -7558,6 +8161,9 @@ var Voicing = class _Voicing {
|
|
|
7558
8161
|
*/
|
|
7559
8162
|
invert(n) {
|
|
7560
8163
|
assertInteger(n, "voicing inversion", -128, 128);
|
|
8164
|
+
if (n === 0) {
|
|
8165
|
+
return this;
|
|
8166
|
+
}
|
|
7561
8167
|
const pitches = [...this.#pitches].sort((a, b) => a - b);
|
|
7562
8168
|
for (let moved = 0; moved < Math.abs(n); moved += 1) {
|
|
7563
8169
|
const index = n > 0 ? 0 : pitches.length - 1;
|
|
@@ -7587,7 +8193,7 @@ var Voicing = class _Voicing {
|
|
|
7587
8193
|
* Private class fields do not serialize, so an explicit `toJSON` keeps
|
|
7588
8194
|
* `JSON.stringify(voicing)` from collapsing to `{}`.
|
|
7589
8195
|
*
|
|
7590
|
-
* @returns A copy of the sounding MIDI pitches,
|
|
8196
|
+
* @returns A copy of the sounding MIDI pitches, in voice order.
|
|
7591
8197
|
*/
|
|
7592
8198
|
toJSON() {
|
|
7593
8199
|
return [...this.#pitches];
|
|
@@ -7596,7 +8202,7 @@ var Voicing = class _Voicing {
|
|
|
7596
8202
|
* The pitches separated by spaces, so a template literal or a log line reads
|
|
7597
8203
|
* as the voicing.
|
|
7598
8204
|
*
|
|
7599
|
-
* @returns The pitches
|
|
8205
|
+
* @returns The pitches in voice order, e.g. `'60 64 67'`.
|
|
7600
8206
|
*/
|
|
7601
8207
|
toString() {
|
|
7602
8208
|
return this.#pitches.join(" ");
|
|
@@ -7615,7 +8221,7 @@ var Voicing = class _Voicing {
|
|
|
7615
8221
|
return {
|
|
7616
8222
|
profile: query.profile,
|
|
7617
8223
|
chord: query.chord === null ? null : toChordData(query.chord),
|
|
7618
|
-
key:
|
|
8224
|
+
key: resolveKey(query.key).scale,
|
|
7619
8225
|
otherVoices,
|
|
7620
8226
|
strongBeat: query.strongBeat,
|
|
7621
8227
|
prevPitch: query.prevPitch,
|
|
@@ -7644,4 +8250,4 @@ export {
|
|
|
7644
8250
|
Tuning,
|
|
7645
8251
|
Voicing
|
|
7646
8252
|
};
|
|
7647
|
-
//# sourceMappingURL=chunk-
|
|
8253
|
+
//# sourceMappingURL=chunk-FBH75DAY.js.map
|