@libraz/libcantus 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/README_ja.md +4 -0
- package/dist/analyze/index.cjs +31 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +155 -9
- package/dist/analyze/index.d.ts +155 -9
- package/dist/analyze/index.js +37 -13
- package/dist/{chunk-TT6PKMPR.cjs → chunk-35NBV2FT.cjs} +16 -16
- package/dist/{chunk-TT6PKMPR.cjs.map → chunk-35NBV2FT.cjs.map} +1 -1
- package/dist/{chunk-YPB3A3EY.cjs → chunk-3R3QVKY5.cjs} +314 -285
- package/dist/chunk-3R3QVKY5.cjs.map +1 -0
- package/dist/{chunk-GDKE6RQW.cjs → chunk-4R2LWV7E.cjs} +691 -413
- package/dist/chunk-4R2LWV7E.cjs.map +1 -0
- package/dist/{chunk-RVS4G4MR.cjs → chunk-76ZBTRG2.cjs} +198 -198
- package/dist/{chunk-RVS4G4MR.cjs.map → chunk-76ZBTRG2.cjs.map} +1 -1
- package/dist/{chunk-X6RJAHII.js → chunk-7KG3RTYS.js} +2 -2
- package/dist/{chunk-PQL7E5F6.js → chunk-AGOVW7U6.js} +1 -1
- package/dist/{chunk-PQL7E5F6.js.map → chunk-AGOVW7U6.js.map} +1 -1
- package/dist/{chunk-5KW7DNCJ.cjs → chunk-B2PW6EHN.cjs} +2423 -453
- package/dist/chunk-B2PW6EHN.cjs.map +1 -0
- package/dist/{chunk-J772DL3B.cjs → chunk-CGBADIO5.cjs} +53 -53
- package/dist/{chunk-J772DL3B.cjs.map → chunk-CGBADIO5.cjs.map} +1 -1
- package/dist/{chunk-R5VUXQMP.js → chunk-CPMB7P75.js} +1978 -8
- package/dist/chunk-CPMB7P75.js.map +1 -0
- package/dist/{chunk-VLE5R7PF.js → chunk-GNVN4PPJ.js} +1874 -8
- package/dist/chunk-GNVN4PPJ.js.map +1 -0
- package/dist/{chunk-Q6R3QAZD.cjs → chunk-IFZDWIKM.cjs} +2199 -333
- package/dist/chunk-IFZDWIKM.cjs.map +1 -0
- package/dist/{chunk-DKICR7AN.js → chunk-IPEF6NWB.js} +296 -18
- package/dist/chunk-IPEF6NWB.js.map +1 -0
- package/dist/{chunk-AWEMD747.js → chunk-M5RYMAFI.js} +3 -3
- package/dist/{chunk-HSY2VQXZ.js → chunk-MLBPXA4Z.js} +330 -728
- package/dist/chunk-MLBPXA4Z.js.map +1 -0
- package/dist/{chunk-H66ARFU5.js → chunk-NB25P3EH.js} +3 -3
- package/dist/{chunk-5JJ6EHEZ.cjs → chunk-NM7UAMC3.cjs} +62 -62
- package/dist/{chunk-5JJ6EHEZ.cjs.map → chunk-NM7UAMC3.cjs.map} +1 -1
- package/dist/{chunk-R7B5N2JK.cjs → chunk-PM4XJE2Y.cjs} +47 -47
- package/dist/{chunk-R7B5N2JK.cjs.map → chunk-PM4XJE2Y.cjs.map} +1 -1
- package/dist/{chunk-7GOKQJTQ.js → chunk-PUKJP2Z5.js} +2 -2
- package/dist/{chunk-QCXYKSZS.cjs → chunk-PWC4CRWT.cjs} +19 -19
- package/dist/{chunk-QCXYKSZS.cjs.map → chunk-PWC4CRWT.cjs.map} +1 -1
- package/dist/{chunk-FPBZFNCD.js → chunk-RFIBF2RR.js} +47 -18
- package/dist/{chunk-FPBZFNCD.js.map → chunk-RFIBF2RR.js.map} +1 -1
- package/dist/{chunk-XKSO2V5I.cjs → chunk-S5BKZZGM.cjs} +1 -1
- package/dist/{chunk-XKSO2V5I.cjs.map → chunk-S5BKZZGM.cjs.map} +1 -1
- package/dist/{chunk-HOHFYGKW.js → chunk-UMWZ4ZET.js} +2 -2
- package/dist/{chunk-XVMJO3QF.cjs → chunk-YFA7X2F6.cjs} +489 -887
- package/dist/chunk-YFA7X2F6.cjs.map +1 -0
- package/dist/{chunk-57WB2EDF.js → chunk-ZBUROTJQ.js} +3 -3
- package/dist/{coerce-wwdELNYS.d.ts → coerce-DP0vYu-x.d.ts} +77 -1
- package/dist/{coerce-CJlgULrQ.d.cts → coerce-DRERzdve.d.cts} +77 -1
- package/dist/core/index.cjs +5 -5
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +4 -4
- package/dist/generate/index.cjs +20 -8
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +87 -9
- package/dist/generate/index.d.ts +87 -9
- package/dist/generate/index.js +19 -7
- package/dist/{index-BaoKVVhE.d.cts → index-B9QLziDe.d.cts} +2 -2
- package/dist/{index-CnHQgqNh.d.ts → index-BHIUyjhZ.d.ts} +2 -2
- package/dist/{index-CBCP84rv.d.cts → index-BL5N2Oyz.d.cts} +1 -1
- package/dist/{index-D_hzxD8w.d.cts → index-BMweUlm5.d.cts} +3 -0
- package/dist/{index-Ci3xyows.d.ts → index-CL4b4gUQ.d.ts} +3 -0
- package/dist/{index-BzLNkmA7.d.cts → index-CY-3NVfy.d.cts} +486 -5
- package/dist/{index-CnIdP4EZ.d.ts → index-Dw2DP7vm.d.ts} +1 -1
- package/dist/{index-DpC-mDlV.d.ts → index-zaZdmnU4.d.ts} +486 -5
- package/dist/index.cjs +49 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +55 -19
- package/dist/{masks-O73Rhj5d.d.ts → masks-Cc7-MzzY.d.ts} +1 -1
- package/dist/{masks-C8TWLFIi.d.cts → masks-pADfTOiD.d.cts} +1 -1
- package/dist/model/index.cjs +12 -12
- package/dist/model/index.d.cts +213 -16
- package/dist/model/index.d.ts +213 -16
- package/dist/model/index.js +11 -11
- package/dist/{index-DTsKaPaC.d.cts → profile-CQMEqdH9.d.cts} +88 -601
- package/dist/{index-BUtIf2go.d.ts → profile-DLdlIpS9.d.ts} +88 -601
- package/dist/theory/index.cjs +5 -5
- package/dist/theory/index.d.cts +7 -7
- package/dist/theory/index.d.ts +7 -7
- package/dist/theory/index.js +4 -4
- package/dist/{index-BpWOhkkE.d.ts → types-D-XgxGvp.d.cts} +1170 -14
- package/dist/{index-B3WCj_Hm.d.cts → types-Dh7C064C.d.ts} +1170 -14
- package/package.json +6 -5
- package/dist/chunk-5KW7DNCJ.cjs.map +0 -1
- package/dist/chunk-DKICR7AN.js.map +0 -1
- package/dist/chunk-GDKE6RQW.cjs.map +0 -1
- package/dist/chunk-HSY2VQXZ.js.map +0 -1
- package/dist/chunk-Q6R3QAZD.cjs.map +0 -1
- package/dist/chunk-R5VUXQMP.js.map +0 -1
- package/dist/chunk-VLE5R7PF.js.map +0 -1
- package/dist/chunk-XVMJO3QF.cjs.map +0 -1
- package/dist/chunk-YPB3A3EY.cjs.map +0 -1
- /package/dist/{chunk-X6RJAHII.js.map → chunk-7KG3RTYS.js.map} +0 -0
- /package/dist/{chunk-AWEMD747.js.map → chunk-M5RYMAFI.js.map} +0 -0
- /package/dist/{chunk-H66ARFU5.js.map → chunk-NB25P3EH.js.map} +0 -0
- /package/dist/{chunk-7GOKQJTQ.js.map → chunk-PUKJP2Z5.js.map} +0 -0
- /package/dist/{chunk-HOHFYGKW.js.map → chunk-UMWZ4ZET.js.map} +0 -0
- /package/dist/{chunk-57WB2EDF.js.map → chunk-ZBUROTJQ.js.map} +0 -0
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
var _chunkQ6R3QAZDcjs = require('./chunk-Q6R3QAZD.cjs');
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
|
|
@@ -41,7 +40,6 @@ var _chunkQ6R3QAZDcjs = require('./chunk-Q6R3QAZD.cjs');
|
|
|
41
40
|
|
|
42
41
|
|
|
43
42
|
|
|
44
|
-
var _chunkYPB3A3EYcjs = require('./chunk-YPB3A3EY.cjs');
|
|
45
43
|
|
|
46
44
|
|
|
47
45
|
|
|
@@ -54,6 +52,7 @@ var _chunkYPB3A3EYcjs = require('./chunk-YPB3A3EY.cjs');
|
|
|
54
52
|
|
|
55
53
|
|
|
56
54
|
|
|
55
|
+
var _chunkIFZDWIKMcjs = require('./chunk-IFZDWIKM.cjs');
|
|
57
56
|
|
|
58
57
|
|
|
59
58
|
|
|
@@ -61,16 +60,34 @@ var _chunkYPB3A3EYcjs = require('./chunk-YPB3A3EY.cjs');
|
|
|
61
60
|
|
|
62
61
|
|
|
63
62
|
|
|
63
|
+
var _chunk3R3QVKY5cjs = require('./chunk-3R3QVKY5.cjs');
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
var _chunkS5BKZZGMcjs = require('./chunk-S5BKZZGM.cjs');
|
|
69
86
|
|
|
70
87
|
// src/analyze/arrange/internal.ts
|
|
71
88
|
var MAX_LANE_LEAP = 24;
|
|
72
89
|
function covers(note, beat) {
|
|
73
|
-
return beat >= note.startBeat -
|
|
90
|
+
return beat >= note.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && beat < note.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS;
|
|
74
91
|
}
|
|
75
92
|
function isPercussion(role) {
|
|
76
93
|
return role === "drums";
|
|
@@ -84,31 +101,31 @@ var TRACK_ROLE_TABLE = {
|
|
|
84
101
|
};
|
|
85
102
|
var TRACK_ROLES = Object.keys(TRACK_ROLE_TABLE);
|
|
86
103
|
function trackRoleOf(role, name) {
|
|
87
|
-
return role === void 0 ? "other" :
|
|
104
|
+
return role === void 0 ? "other" : _chunkS5BKZZGMcjs.assertOneOf.call(void 0, role, TRACK_ROLES, name);
|
|
88
105
|
}
|
|
89
106
|
var DEFAULT_PROFILE = "pop";
|
|
90
|
-
var SAFETY_PROFILES = Object.keys(
|
|
107
|
+
var SAFETY_PROFILES = Object.keys(_chunk3R3QVKY5cjs.PROFILE_WEIGHTS);
|
|
91
108
|
function arrangementProfile(profile) {
|
|
92
|
-
return
|
|
109
|
+
return _chunkS5BKZZGMcjs.assertOneOf.call(void 0, _nullishCoalesce(profile, () => ( DEFAULT_PROFILE)), SAFETY_PROFILES, "arrangement profile");
|
|
93
110
|
}
|
|
94
111
|
function assertTrackNotes(tracks, options) {
|
|
95
|
-
|
|
112
|
+
_chunkS5BKZZGMcjs.assertArray.call(void 0, tracks, "arrangement tracks");
|
|
96
113
|
let noteCount = 0;
|
|
97
114
|
for (let index = 0; index < tracks.length; index += 1) {
|
|
98
115
|
const notes = _optionalChain([tracks, 'access', _2 => _2[index], 'optionalAccess', _3 => _3.notes]);
|
|
99
116
|
if (!Array.isArray(notes)) {
|
|
100
|
-
throw new (0,
|
|
117
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
101
118
|
`tracks[${index}].notes must be an array; received ${typeof notes}`
|
|
102
119
|
);
|
|
103
120
|
}
|
|
104
|
-
|
|
121
|
+
_chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, `tracks[${index}].notes`, options);
|
|
105
122
|
noteCount += notes.length;
|
|
106
123
|
}
|
|
107
124
|
return noteCount;
|
|
108
125
|
}
|
|
109
126
|
function assertGivenTimeline(timeline, name) {
|
|
110
127
|
if (timeline !== void 0) {
|
|
111
|
-
|
|
128
|
+
_chunkIFZDWIKMcjs.assertChordTimeline.call(void 0, timeline, name);
|
|
112
129
|
}
|
|
113
130
|
}
|
|
114
131
|
function assertGivenKeys(keys, name) {
|
|
@@ -116,25 +133,25 @@ function assertGivenKeys(keys, name) {
|
|
|
116
133
|
return;
|
|
117
134
|
}
|
|
118
135
|
if (!Array.isArray(keys)) {
|
|
119
|
-
throw new (0,
|
|
120
|
-
`${name} must be an array of key regions; received ${
|
|
136
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
137
|
+
`${name} must be an array of key regions; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, keys)}`
|
|
121
138
|
);
|
|
122
139
|
}
|
|
123
140
|
for (let index = 0; index < keys.length; index += 1) {
|
|
124
141
|
const region = keys[index];
|
|
125
142
|
if (typeof region !== "object" || region === null) {
|
|
126
|
-
throw new (0,
|
|
127
|
-
`${name}[${index}] must be a key region; received ${
|
|
143
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
144
|
+
`${name}[${index}] must be a key region; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, region)}`
|
|
128
145
|
);
|
|
129
146
|
}
|
|
130
|
-
|
|
131
|
-
|
|
147
|
+
_chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, region.startBeat, `${name}[${index}].startBeat`);
|
|
148
|
+
_chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, region.endBeat, `${name}[${index}].endBeat`);
|
|
132
149
|
if (region.key === void 0 || region.key === null || typeof region.key !== "string" && typeof region.key !== "object") {
|
|
133
|
-
throw new (0,
|
|
134
|
-
`${name}[${index}].key must name a key; received ${
|
|
150
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
151
|
+
`${name}[${index}].key must name a key; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, region.key)}`
|
|
135
152
|
);
|
|
136
153
|
}
|
|
137
|
-
|
|
154
|
+
_chunk3R3QVKY5cjs.resolveKey.call(void 0, region.key);
|
|
138
155
|
}
|
|
139
156
|
}
|
|
140
157
|
function harmonyTrackSet(harmonyTracks, tracks, name = "harmonyTracks") {
|
|
@@ -145,16 +162,16 @@ function harmonyTrackSet(harmonyTracks, tracks, name = "harmonyTracks") {
|
|
|
145
162
|
for (let index = 0; index < harmonyTracks.length; index += 1) {
|
|
146
163
|
const value = harmonyTracks[index];
|
|
147
164
|
if (value === void 0 || !Number.isFinite(value)) {
|
|
148
|
-
throw new (0,
|
|
165
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name}[${index}] must be a track index; received ${value}`);
|
|
149
166
|
}
|
|
150
167
|
const track = Math.trunc(value);
|
|
151
168
|
if (track < 0 || track >= tracks.length) {
|
|
152
|
-
throw new (0,
|
|
169
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
153
170
|
`${name}[${index}] ${track} is outside the arrangement's ${tracks.length} tracks`
|
|
154
171
|
);
|
|
155
172
|
}
|
|
156
173
|
if (isPercussion(_optionalChain([tracks, 'access', _4 => _4[track], 'optionalAccess', _5 => _5.role]))) {
|
|
157
|
-
throw new (0,
|
|
174
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
158
175
|
`${name}[${index}] ${track} names a percussion track, whose pitches state no harmony`
|
|
159
176
|
);
|
|
160
177
|
}
|
|
@@ -195,7 +212,7 @@ function splitIntoSubVoices(ordered) {
|
|
|
195
212
|
continue;
|
|
196
213
|
}
|
|
197
214
|
const end = last.startBeat + last.durationBeat;
|
|
198
|
-
if (!
|
|
215
|
+
if (!_chunkIFZDWIKMcjs.hasEnded.call(void 0, end, note.startBeat)) {
|
|
199
216
|
continue;
|
|
200
217
|
}
|
|
201
218
|
const distance = Math.abs(last.pitch - note.pitch);
|
|
@@ -216,13 +233,13 @@ function splitIntoSubVoices(ordered) {
|
|
|
216
233
|
for (let start = 0; start < ordered.length; ) {
|
|
217
234
|
const onset = _nullishCoalesce(_optionalChain([ordered, 'access', _13 => _13[start], 'optionalAccess', _14 => _14.startBeat]), () => ( 0));
|
|
218
235
|
let end = start + 1;
|
|
219
|
-
while (end < ordered.length &&
|
|
236
|
+
while (end < ordered.length && _chunkIFZDWIKMcjs.sameInstant.call(void 0, _nullishCoalesce(_optionalChain([ordered, 'access', _15 => _15[end], 'optionalAccess', _16 => _16.startBeat]), () => ( 0)), onset)) {
|
|
220
237
|
end += 1;
|
|
221
238
|
}
|
|
222
239
|
const block = ordered.slice(start, end);
|
|
223
240
|
if (block.length > 1) {
|
|
224
241
|
const freeLanes = lanes.map((lane, index) => ({ index, last: lane[lane.length - 1] })).filter(
|
|
225
|
-
(entry) => entry.last !== void 0 &&
|
|
242
|
+
(entry) => entry.last !== void 0 && _chunkIFZDWIKMcjs.hasEnded.call(void 0, entry.last.startBeat + entry.last.durationBeat, onset)
|
|
226
243
|
).sort((a, b) => a.last.pitch - b.last.pitch);
|
|
227
244
|
const assignment = pairWithFreeLanes(block, freeLanes);
|
|
228
245
|
const taken = new Set(
|
|
@@ -310,7 +327,7 @@ function lastStartingAtOrBefore(sounding, beat) {
|
|
|
310
327
|
let high = sounding.length;
|
|
311
328
|
while (low < high) {
|
|
312
329
|
const middle = Math.floor((low + high) / 2);
|
|
313
|
-
if ((_nullishCoalesce(_optionalChain([sounding, 'access', _22 => _22[middle], 'optionalAccess', _23 => _23.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat +
|
|
330
|
+
if ((_nullishCoalesce(_optionalChain([sounding, 'access', _22 => _22[middle], 'optionalAccess', _23 => _23.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
314
331
|
low = middle + 1;
|
|
315
332
|
} else {
|
|
316
333
|
high = middle;
|
|
@@ -326,8 +343,10 @@ function prepareTracks(tracks) {
|
|
|
326
343
|
const sounding = voice.map((note, i) => {
|
|
327
344
|
const prev = i > 0 ? voice[i - 1] : void 0;
|
|
328
345
|
const prevEnd = prev === void 0 ? void 0 : prev.startBeat + prev.durationBeat;
|
|
329
|
-
const contiguous = prevEnd !== void 0 &&
|
|
346
|
+
const contiguous = prevEnd !== void 0 && _chunkIFZDWIKMcjs.adjacent.call(void 0, prevEnd, note.startBeat);
|
|
330
347
|
return {
|
|
348
|
+
id: note.id,
|
|
349
|
+
originalIndex: note.originalIndex,
|
|
331
350
|
pitch: note.pitch,
|
|
332
351
|
prevPitch: contiguous ? _optionalChain([prev, 'optionalAccess', _24 => _24.pitch]) : void 0,
|
|
333
352
|
startBeat: note.startBeat,
|
|
@@ -357,7 +376,7 @@ function withGivenKeys(result, given) {
|
|
|
357
376
|
if (given === void 0) {
|
|
358
377
|
return result;
|
|
359
378
|
}
|
|
360
|
-
return { ...result, keys: given, prevailingKey: _nullishCoalesce(
|
|
379
|
+
return { ...result, keys: given, prevailingKey: _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, given), () => ( result.prevailingKey)) };
|
|
361
380
|
}
|
|
362
381
|
function callerTimeline(timeline, key, given, pooled, meters, budget) {
|
|
363
382
|
const chartEnd = timeline.segments.reduce((end, segment) => Math.max(end, segment.endBeat), 0);
|
|
@@ -366,15 +385,15 @@ function callerTimeline(timeline, key, given, pooled, meters, budget) {
|
|
|
366
385
|
0
|
|
367
386
|
);
|
|
368
387
|
const totalBeats = Math.max(chartEnd, soundingEnd);
|
|
369
|
-
const { startBeat: musicStart } =
|
|
370
|
-
const keys = _nullishCoalesce(given, () => (
|
|
371
|
-
key !== void 0 ? [{ startBeat: musicStart, endBeat: totalBeats, key:
|
|
388
|
+
const { startBeat: musicStart } = _chunkIFZDWIKMcjs.gridForNotes.call(void 0, pooled, _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, 0, meters));
|
|
389
|
+
const keys = _nullishCoalesce(given, () => ( _chunkIFZDWIKMcjs.attachPivots.call(void 0,
|
|
390
|
+
key !== void 0 ? [{ startBeat: musicStart, endBeat: totalBeats, key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, key), confidence: 1 }] : _chunkIFZDWIKMcjs.keyTimelineFromNotes.call(void 0, pooled, { meters, totalBeats, budget }),
|
|
372
391
|
timeline.segments
|
|
373
392
|
)));
|
|
374
393
|
return {
|
|
375
394
|
timeline,
|
|
376
395
|
keys,
|
|
377
|
-
prevailingKey: _nullishCoalesce(
|
|
396
|
+
prevailingKey: _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, keys), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0)))),
|
|
378
397
|
segmentConfidence: timeline.segments.map(() => 0)
|
|
379
398
|
};
|
|
380
399
|
}
|
|
@@ -391,11 +410,11 @@ function soundingVoicesAt(prepared, beat) {
|
|
|
391
410
|
continue;
|
|
392
411
|
}
|
|
393
412
|
const snap = { pitch: note.pitch };
|
|
394
|
-
const previous =
|
|
413
|
+
const previous = _chunkIFZDWIKMcjs.sameInstant.call(void 0, note.startBeat, beat) ? note.prevPitch : note.pitch;
|
|
395
414
|
if (previous !== void 0) {
|
|
396
415
|
snap.prevPitch = previous;
|
|
397
416
|
}
|
|
398
|
-
out.push({ track: t, voice: v, snapshot: snap });
|
|
417
|
+
out.push({ track: t, voice: v, trackIndex: track.trackIndex, note, snapshot: snap });
|
|
399
418
|
}
|
|
400
419
|
}
|
|
401
420
|
return out;
|
|
@@ -403,14 +422,28 @@ function soundingVoicesAt(prepared, beat) {
|
|
|
403
422
|
function otherVoicesSounding(prepared, excludeTrack, excludeVoice, beat, cache) {
|
|
404
423
|
const sounding = _nullishCoalesce(cache.get(beat), () => ( soundingVoicesAt(prepared, beat)));
|
|
405
424
|
cache.set(beat, sounding);
|
|
406
|
-
return sounding.filter(({ track, voice }) => track !== excludeTrack || voice !== excludeVoice)
|
|
425
|
+
return sounding.filter(({ track, voice }) => track !== excludeTrack || voice !== excludeVoice);
|
|
426
|
+
}
|
|
427
|
+
function conflictPartner(sounding, reasons) {
|
|
428
|
+
const partner = {
|
|
429
|
+
trackIndex: sounding.trackIndex,
|
|
430
|
+
noteId: sounding.note.id,
|
|
431
|
+
reasons
|
|
432
|
+
};
|
|
433
|
+
if (sounding.note.originalIndex !== void 0) {
|
|
434
|
+
partner.originalIndex = sounding.note.originalIndex;
|
|
435
|
+
}
|
|
436
|
+
return partner;
|
|
437
|
+
}
|
|
438
|
+
function snapshotsOf(voices) {
|
|
439
|
+
return voices.map(({ snapshot }) => snapshot);
|
|
407
440
|
}
|
|
408
441
|
function analyzePolyphony(notes, chordAtBeat, key) {
|
|
409
|
-
const passage =
|
|
410
|
-
|
|
442
|
+
const passage = _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "notes", { allowNonPositiveDuration: true });
|
|
443
|
+
_chunkS5BKZZGMcjs.assertFunction.call(void 0, chordAtBeat, "chordAtBeat");
|
|
411
444
|
const prepared = prepareTracks([{ notes: [...passage] }]);
|
|
412
445
|
const track = prepared[0];
|
|
413
|
-
|
|
446
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0,
|
|
414
447
|
passage.length * (_nullishCoalesce(_optionalChain([track, 'optionalAccess', _29 => _29.voices, 'access', _30 => _30.length]), () => ( 0))),
|
|
415
448
|
"polyphony note-voice comparisons"
|
|
416
449
|
);
|
|
@@ -421,11 +454,11 @@ function analyzePolyphony(notes, chordAtBeat, key) {
|
|
|
421
454
|
if (subVoice === void 0) {
|
|
422
455
|
continue;
|
|
423
456
|
}
|
|
424
|
-
const analyzed =
|
|
457
|
+
const analyzed = _chunkIFZDWIKMcjs.analyzeVoice.call(void 0,
|
|
425
458
|
subVoice.voice,
|
|
426
459
|
chordAtBeat,
|
|
427
460
|
key,
|
|
428
|
-
(beat) => otherVoicesSounding(prepared, 0, v, beat, soundingCache)
|
|
461
|
+
(beat) => snapshotsOf(otherVoicesSounding(prepared, 0, v, beat, soundingCache))
|
|
429
462
|
);
|
|
430
463
|
for (const note of analyzed) {
|
|
431
464
|
if (note.originalIndex !== void 0) {
|
|
@@ -439,7 +472,7 @@ function analyzePolyphony(notes, chordAtBeat, key) {
|
|
|
439
472
|
if (analyzed !== void 0) {
|
|
440
473
|
return analyzed;
|
|
441
474
|
}
|
|
442
|
-
const [only] =
|
|
475
|
+
const [only] = _chunkIFZDWIKMcjs.analyzeVoice.call(void 0, [{ ...note, id: index }], chordAtBeat, key);
|
|
443
476
|
return _nullishCoalesce(only, () => ( {
|
|
444
477
|
noteId: index,
|
|
445
478
|
pitch: note.pitch,
|
|
@@ -454,7 +487,7 @@ function firstSegmentAfter(timeline, beat) {
|
|
|
454
487
|
let high = timeline.segments.length;
|
|
455
488
|
while (low < high) {
|
|
456
489
|
const middle = Math.floor((low + high) / 2);
|
|
457
|
-
if ((_nullishCoalesce(_optionalChain([timeline, 'access', _35 => _35.segments, 'access', _36 => _36[middle], 'optionalAccess', _37 => _37.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat +
|
|
490
|
+
if ((_nullishCoalesce(_optionalChain([timeline, 'access', _35 => _35.segments, 'access', _36 => _36[middle], 'optionalAccess', _37 => _37.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
458
491
|
low = middle + 1;
|
|
459
492
|
} else {
|
|
460
493
|
high = middle;
|
|
@@ -467,7 +500,7 @@ function evaluationBeats(note, timeline) {
|
|
|
467
500
|
const beats = [note.startBeat];
|
|
468
501
|
for (let i = firstSegmentAfter(timeline, note.startBeat); i < timeline.segments.length; i += 1) {
|
|
469
502
|
const start = _optionalChain([timeline, 'access', _38 => _38.segments, 'access', _39 => _39[i], 'optionalAccess', _40 => _40.startBeat]);
|
|
470
|
-
if (start === void 0 || start >= noteEnd -
|
|
503
|
+
if (start === void 0 || start >= noteEnd - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
471
504
|
break;
|
|
472
505
|
}
|
|
473
506
|
beats.push(start);
|
|
@@ -478,19 +511,19 @@ function analyzeArrangement(tracks, opts = {}) {
|
|
|
478
511
|
return analyzeArrangementWith(tracks, opts).analysis;
|
|
479
512
|
}
|
|
480
513
|
function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
|
|
481
|
-
const meters =
|
|
514
|
+
const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "arrangement meters");
|
|
482
515
|
const budget = opts.budget;
|
|
483
516
|
const noteOptions = { allowNonPositiveDuration: true, budget };
|
|
484
517
|
if (opts.pickupBeats !== void 0) {
|
|
485
|
-
|
|
518
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, opts.pickupBeats, 0, Number.MAX_SAFE_INTEGER, "arrangement pickupBeats");
|
|
486
519
|
noteOptions.minStartBeat = -opts.pickupBeats;
|
|
487
520
|
}
|
|
488
|
-
|
|
489
|
-
|
|
521
|
+
_chunkS5BKZZGMcjs.assertArray.call(void 0, tracks, "arrangement tracks");
|
|
522
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
|
|
490
523
|
const noteCount = assertTrackNotes(tracks, noteOptions);
|
|
491
|
-
|
|
524
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, noteCount, "arrangement notes", budget);
|
|
492
525
|
const profile = arrangementProfile(opts.profile);
|
|
493
|
-
const minSeverity = opts.minSeverity === void 0 ? 1 /* Warning */ :
|
|
526
|
+
const minSeverity = opts.minSeverity === void 0 ? 1 /* Warning */ : _chunkS5BKZZGMcjs.assertInteger.call(void 0,
|
|
494
527
|
opts.minSeverity,
|
|
495
528
|
"arrangement minSeverity",
|
|
496
529
|
0 /* Safe */,
|
|
@@ -501,12 +534,12 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
|
|
|
501
534
|
const harmonyTracks = harmonyTrackSet(opts.harmonyTracks, tracks);
|
|
502
535
|
const prepared = prepareTracks(tracks);
|
|
503
536
|
const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);
|
|
504
|
-
|
|
537
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, noteCount * voiceCount, "arrangement note-voice comparisons", budget);
|
|
505
538
|
const pooled = poolNotes(tracks, harmonyTracks);
|
|
506
539
|
let evidence;
|
|
507
540
|
let inferred;
|
|
508
541
|
if (opts.timeline === void 0) {
|
|
509
|
-
const run =
|
|
542
|
+
const run = _chunkIFZDWIKMcjs.analyzeTimeline.call(void 0,
|
|
510
543
|
pooled,
|
|
511
544
|
{
|
|
512
545
|
key: opts.key,
|
|
@@ -524,9 +557,9 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
|
|
|
524
557
|
inferred = callerTimeline(opts.timeline, opts.key, opts.keys, pooled, meters, budget);
|
|
525
558
|
}
|
|
526
559
|
const { timeline, keys, prevailingKey, segmentConfidence } = inferred;
|
|
527
|
-
const keyAt =
|
|
528
|
-
const scaleAt = (beat) =>
|
|
529
|
-
const cadences =
|
|
560
|
+
const keyAt = _chunkIFZDWIKMcjs.keyLookup.call(void 0, keys, prevailingKey);
|
|
561
|
+
const scaleAt = (beat) => _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat));
|
|
562
|
+
const cadences = _chunkIFZDWIKMcjs.detectCadences.call(void 0, timeline, scaleAt);
|
|
530
563
|
const soundingCache = /* @__PURE__ */ new Map();
|
|
531
564
|
const trackAnalyses = [];
|
|
532
565
|
const conflicts = [];
|
|
@@ -545,11 +578,11 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
|
|
|
545
578
|
if (!subVoice) {
|
|
546
579
|
continue;
|
|
547
580
|
}
|
|
548
|
-
const analyzed =
|
|
581
|
+
const analyzed = _chunkIFZDWIKMcjs.analyzeVoice.call(void 0,
|
|
549
582
|
subVoice.voice,
|
|
550
583
|
timeline.at,
|
|
551
584
|
scaleAt,
|
|
552
|
-
(beat) => otherVoicesSounding(prepared, t, v, beat, soundingCache)
|
|
585
|
+
(beat) => snapshotsOf(otherVoicesSounding(prepared, t, v, beat, soundingCache))
|
|
553
586
|
);
|
|
554
587
|
for (const note of analyzed) {
|
|
555
588
|
notes.push({ ...note, trackIndex: track.trackIndex });
|
|
@@ -562,19 +595,20 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
|
|
|
562
595
|
continue;
|
|
563
596
|
}
|
|
564
597
|
for (const beat of evaluationBeats(note, timeline)) {
|
|
565
|
-
const atOnset =
|
|
598
|
+
const atOnset = _chunkIFZDWIKMcjs.sameInstant.call(void 0, beat, note.startBeat);
|
|
599
|
+
const others = otherVoicesSounding(prepared, t, v, beat, soundingCache);
|
|
566
600
|
const safetyQuery = {
|
|
567
601
|
profile,
|
|
568
602
|
candidatePitch: note.pitch,
|
|
569
603
|
prevPitch: atOnset ? preparedNote.prevPitch : note.pitch,
|
|
570
604
|
chord: timeline.at(beat),
|
|
571
605
|
key: scaleAt(beat),
|
|
572
|
-
otherVoices:
|
|
573
|
-
strongBeat:
|
|
606
|
+
otherVoices: snapshotsOf(others),
|
|
607
|
+
strongBeat: _chunkS5BKZZGMcjs.isStrongBeat.call(void 0, beat, meters)
|
|
574
608
|
};
|
|
575
|
-
let result =
|
|
609
|
+
let result = _chunk3R3QVKY5cjs.evaluateSafety.call(void 0, safetyQuery, { suggestions: false });
|
|
576
610
|
if (result.safety !== 0 /* Safe */ && result.safety >= minSeverity) {
|
|
577
|
-
result =
|
|
611
|
+
result = _chunk3R3QVKY5cjs.evaluateSafety.call(void 0, safetyQuery, { partners: true });
|
|
578
612
|
const conflict = {
|
|
579
613
|
beat,
|
|
580
614
|
trackName: track.name,
|
|
@@ -597,6 +631,11 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
|
|
|
597
631
|
if (result.suggestions !== void 0) {
|
|
598
632
|
conflict.suggestions = [...result.suggestions];
|
|
599
633
|
}
|
|
634
|
+
if (result.partners !== void 0) {
|
|
635
|
+
conflict.partners = result.partners.map(
|
|
636
|
+
({ index, reasons }) => conflictPartner(others[index], reasons)
|
|
637
|
+
);
|
|
638
|
+
}
|
|
600
639
|
conflicts.push(conflict);
|
|
601
640
|
}
|
|
602
641
|
}
|
|
@@ -682,7 +721,7 @@ function sessionOf(tracks, opts, analysis, evidence) {
|
|
|
682
721
|
tracks,
|
|
683
722
|
analysis,
|
|
684
723
|
update(edits) {
|
|
685
|
-
|
|
724
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, edits.length, "arrangement edits", opts.budget);
|
|
686
725
|
const next = [...tracks];
|
|
687
726
|
let edited = null;
|
|
688
727
|
let reordered = false;
|
|
@@ -693,16 +732,16 @@ function sessionOf(tracks, opts, analysis, evidence) {
|
|
|
693
732
|
}
|
|
694
733
|
const target = next[edit.trackIndex];
|
|
695
734
|
if (target === void 0) {
|
|
696
|
-
throw new (0,
|
|
735
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
697
736
|
`edits[${index}].trackIndex ${edit.trackIndex} is outside the session's ${tracks.length} tracks`
|
|
698
737
|
);
|
|
699
738
|
}
|
|
700
739
|
if (!Array.isArray(edit.notes)) {
|
|
701
|
-
throw new (0,
|
|
740
|
+
throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
|
|
702
741
|
`edits[${index}].notes must be an array; received ${typeof edit.notes}`
|
|
703
742
|
);
|
|
704
743
|
}
|
|
705
|
-
|
|
744
|
+
_chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, edit.notes, `edits[${index}].notes`, {
|
|
706
745
|
allowNonPositiveDuration: true,
|
|
707
746
|
budget: opts.budget
|
|
708
747
|
});
|
|
@@ -713,7 +752,7 @@ function sessionOf(tracks, opts, analysis, evidence) {
|
|
|
713
752
|
if (edited === null && !reordered) {
|
|
714
753
|
return sessionOf(next, opts, analysis, evidence);
|
|
715
754
|
}
|
|
716
|
-
const dirty = evidence === void 0 ? void 0 : edited === null ? { from: 0, to: 0 } :
|
|
755
|
+
const dirty = evidence === void 0 ? void 0 : edited === null ? { from: 0, to: 0 } : _chunkIFZDWIKMcjs.dirtySlotsFor.call(void 0, evidence, edited.startBeat, edited.endBeat);
|
|
717
756
|
const run = analyzeArrangementWith(next, opts, evidence, dirty);
|
|
718
757
|
return sessionOf(next, opts, run.analysis, run.evidence);
|
|
719
758
|
}
|
|
@@ -738,22 +777,22 @@ function clamp012(value) {
|
|
|
738
777
|
return Math.min(1, Math.max(0, value));
|
|
739
778
|
}
|
|
740
779
|
function tensionCurve(tracks, opts = {}) {
|
|
741
|
-
const meters =
|
|
780
|
+
const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "arrangement meters");
|
|
742
781
|
const budget = opts.budget;
|
|
743
782
|
const noteOptions = { allowNonPositiveDuration: true, budget };
|
|
744
783
|
if (opts.pickupBeats !== void 0) {
|
|
745
|
-
|
|
784
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, opts.pickupBeats, 0, Number.MAX_SAFE_INTEGER, "arrangement pickupBeats");
|
|
746
785
|
noteOptions.minStartBeat = -opts.pickupBeats;
|
|
747
786
|
}
|
|
748
|
-
|
|
749
|
-
|
|
787
|
+
_chunkS5BKZZGMcjs.assertArray.call(void 0, tracks, "arrangement tracks");
|
|
788
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
|
|
750
789
|
const noteCount = assertTrackNotes(tracks, noteOptions);
|
|
751
|
-
|
|
790
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, noteCount, "arrangement notes", budget);
|
|
752
791
|
const profile = arrangementProfile(opts.profile);
|
|
753
792
|
const step = _nullishCoalesce(opts.step, () => ( 1));
|
|
754
|
-
|
|
793
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, step, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "tension sampling step");
|
|
755
794
|
if (opts.minSeverity !== void 0) {
|
|
756
|
-
|
|
795
|
+
_chunkS5BKZZGMcjs.assertInteger.call(void 0,
|
|
757
796
|
opts.minSeverity,
|
|
758
797
|
"arrangement minSeverity",
|
|
759
798
|
0 /* Safe */,
|
|
@@ -766,7 +805,7 @@ function tensionCurve(tracks, opts = {}) {
|
|
|
766
805
|
const pooled = poolNotes(tracks, harmonyTracks);
|
|
767
806
|
const all = poolNotes(tracks);
|
|
768
807
|
const totalBeats = all.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
|
|
769
|
-
const { origin: sampleStart, startBeat: musicStart } =
|
|
808
|
+
const { origin: sampleStart, startBeat: musicStart } = _chunkIFZDWIKMcjs.gridForNotes.call(void 0, all, step);
|
|
770
809
|
const suppliedKeys = opts.timeline === void 0 ? void 0 : _nullishCoalesce(opts.keys, () => ( (opts.key !== void 0 ? [
|
|
771
810
|
{
|
|
772
811
|
// The one region a named key holds starts where the music does,
|
|
@@ -774,11 +813,11 @@ function tensionCurve(tracks, opts = {}) {
|
|
|
774
813
|
// a key cannot be in force over beats nothing sounds in.
|
|
775
814
|
startBeat: musicStart,
|
|
776
815
|
endBeat: totalBeats,
|
|
777
|
-
key:
|
|
816
|
+
key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, opts.key),
|
|
778
817
|
confidence: 1
|
|
779
818
|
}
|
|
780
|
-
] :
|
|
781
|
-
const { timeline, keys, prevailingKey } = opts.timeline === void 0 || suppliedKeys === void 0 ?
|
|
819
|
+
] : _chunkIFZDWIKMcjs.keyTimelineFromNotes.call(void 0, pooled, { meters, totalBeats, budget }))));
|
|
820
|
+
const { timeline, keys, prevailingKey } = opts.timeline === void 0 || suppliedKeys === void 0 ? _chunkIFZDWIKMcjs.chordTimelineFromNotes.call(void 0, pooled, {
|
|
782
821
|
key: opts.key,
|
|
783
822
|
meters,
|
|
784
823
|
pickupBeats: opts.pickupBeats,
|
|
@@ -787,16 +826,16 @@ function tensionCurve(tracks, opts = {}) {
|
|
|
787
826
|
}) : {
|
|
788
827
|
timeline: opts.timeline,
|
|
789
828
|
keys: suppliedKeys,
|
|
790
|
-
prevailingKey: _nullishCoalesce(
|
|
829
|
+
prevailingKey: _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, suppliedKeys), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0))))
|
|
791
830
|
};
|
|
792
831
|
const regions = _nullishCoalesce(opts.keys, () => ( keys));
|
|
793
|
-
const keyAt =
|
|
832
|
+
const keyAt = _chunkIFZDWIKMcjs.keyLookup.call(void 0, regions, _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, regions), () => ( prevailingKey)));
|
|
794
833
|
const prepared = prepareTracks(tracks);
|
|
795
834
|
const points = [];
|
|
796
|
-
const sampleCount = Math.max(0, Math.ceil((totalBeats - sampleStart) / step -
|
|
797
|
-
|
|
835
|
+
const sampleCount = Math.max(0, Math.ceil((totalBeats - sampleStart) / step - _chunkS5BKZZGMcjs.BEAT_EPS));
|
|
836
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, sampleCount, "tension samples", budget);
|
|
798
837
|
const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);
|
|
799
|
-
|
|
838
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0,
|
|
800
839
|
sampleCount * Math.min(128, voiceCount) * voiceCount,
|
|
801
840
|
"tension sample-voice comparisons",
|
|
802
841
|
budget
|
|
@@ -807,14 +846,14 @@ function tensionCurve(tracks, opts = {}) {
|
|
|
807
846
|
beat,
|
|
808
847
|
// The pitch classes alone: tension is scored from what sounds against
|
|
809
848
|
// the tonic, and no reading below here asks how the key is written.
|
|
810
|
-
tension: sampleTension(prepared, timeline,
|
|
849
|
+
tension: sampleTension(prepared, timeline, _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat)), meters, profile, beat)
|
|
811
850
|
});
|
|
812
851
|
}
|
|
813
852
|
return points;
|
|
814
853
|
}
|
|
815
854
|
function tensionCurveFrom(tracks, analysis, opts = {}) {
|
|
816
|
-
const asked =
|
|
817
|
-
const carried = carriedHarmony(
|
|
855
|
+
const asked = _chunkS5BKZZGMcjs.assertRecord.call(void 0, opts, "opts");
|
|
856
|
+
const carried = carriedHarmony(_chunkS5BKZZGMcjs.assertRecord.call(void 0, analysis, "analysis"), asked);
|
|
818
857
|
return tensionCurve(tracks, { ...carried, ...asked });
|
|
819
858
|
}
|
|
820
859
|
function carriedHarmony(analysis, opts) {
|
|
@@ -848,9 +887,9 @@ function sampleTension(prepared, timeline, key, meters, profile, beat) {
|
|
|
848
887
|
return 0;
|
|
849
888
|
}
|
|
850
889
|
const chord = timeline.at(beat);
|
|
851
|
-
const functionScore = chord ? FUNCTION_TENSION[
|
|
852
|
-
const chordPcs = chord ? new Set(
|
|
853
|
-
const strongBeat =
|
|
890
|
+
const functionScore = chord ? FUNCTION_TENSION[_chunkIFZDWIKMcjs.functionOf.call(void 0, chord, key)] : 0;
|
|
891
|
+
const chordPcs = chord ? new Set(_chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, chord)) : null;
|
|
892
|
+
const strongBeat = _chunkS5BKZZGMcjs.isStrongBeat.call(void 0, beat, meters);
|
|
854
893
|
let nonChord = 0;
|
|
855
894
|
let dissonant = 0;
|
|
856
895
|
const pitches = /* @__PURE__ */ new Map();
|
|
@@ -866,7 +905,7 @@ function sampleTension(prepared, timeline, key, meters, profile, beat) {
|
|
|
866
905
|
}
|
|
867
906
|
}
|
|
868
907
|
for (const [pitch, count] of pitches) {
|
|
869
|
-
if (chordPcs && !chordPcs.has(
|
|
908
|
+
if (chordPcs && !chordPcs.has(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitch))) {
|
|
870
909
|
nonChord += count;
|
|
871
910
|
}
|
|
872
911
|
const at = _nullishCoalesce(firstOf.get(pitch), () => ( 0));
|
|
@@ -877,7 +916,7 @@ function sampleTension(prepared, timeline, key, meters, profile, beat) {
|
|
|
877
916
|
others[at] = tail;
|
|
878
917
|
others.pop();
|
|
879
918
|
}
|
|
880
|
-
const result =
|
|
919
|
+
const result = _chunk3R3QVKY5cjs.evaluateSafety.call(void 0,
|
|
881
920
|
{ profile, candidatePitch: pitch, chord, key, otherVoices: others, strongBeat },
|
|
882
921
|
{ suggestions: false }
|
|
883
922
|
);
|
|
@@ -928,7 +967,7 @@ function accentContrast(novelty, slices, groupBars, phase) {
|
|
|
928
967
|
continue;
|
|
929
968
|
}
|
|
930
969
|
const value = _nullishCoalesce(novelty[i], () => ( 0));
|
|
931
|
-
if (
|
|
970
|
+
if (_chunkIFZDWIKMcjs.floorMod.call(void 0, slice.index - phase, groupBars) === 0) {
|
|
932
971
|
headSum += value;
|
|
933
972
|
headCount += 1;
|
|
934
973
|
} else {
|
|
@@ -946,7 +985,7 @@ function cadenceFitOf(cadenceBars, slices, groupBars, phase) {
|
|
|
946
985
|
let ends = 0;
|
|
947
986
|
let fit = 0;
|
|
948
987
|
for (const slice of slices) {
|
|
949
|
-
if (slice.index < 0 ||
|
|
988
|
+
if (slice.index < 0 || _chunkIFZDWIKMcjs.floorMod.call(void 0, slice.index - phase, groupBars) !== groupBars - 1) {
|
|
950
989
|
continue;
|
|
951
990
|
}
|
|
952
991
|
ends += 1;
|
|
@@ -956,7 +995,7 @@ function cadenceFitOf(cadenceBars, slices, groupBars, phase) {
|
|
|
956
995
|
fit += ELIDED_FIT;
|
|
957
996
|
}
|
|
958
997
|
}
|
|
959
|
-
return ends === 0 ? 0 :
|
|
998
|
+
return ends === 0 ? 0 : _chunkIFZDWIKMcjs.clamp01.call(void 0, fit / ends);
|
|
960
999
|
}
|
|
961
1000
|
function undecidedReading(slices) {
|
|
962
1001
|
const fullBars = slices.filter((slice) => slice.index >= 0);
|
|
@@ -970,31 +1009,31 @@ function undecidedReading(slices) {
|
|
|
970
1009
|
};
|
|
971
1010
|
}
|
|
972
1011
|
function hypermeter(notes, meter, opts = {}) {
|
|
973
|
-
const asked =
|
|
974
|
-
const meters =
|
|
975
|
-
|
|
1012
|
+
const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
|
|
1013
|
+
const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, { meters: meter }, "meter");
|
|
1014
|
+
_chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "hypermeter notes", {
|
|
976
1015
|
allowNonPositiveDuration: true,
|
|
977
1016
|
budget: asked.budget
|
|
978
1017
|
});
|
|
979
1018
|
const sounding = notes.filter((note) => note.durationBeat > 0);
|
|
980
|
-
const firstOnset =
|
|
1019
|
+
const firstOnset = _chunkIFZDWIKMcjs.firstSoundingBeat.call(void 0, sounding);
|
|
981
1020
|
const lastEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
|
|
982
1021
|
const spanEnd = _nullishCoalesce(asked.totalBeats, () => ( lastEnd));
|
|
983
|
-
|
|
984
|
-
const spanStart =
|
|
985
|
-
const slices =
|
|
1022
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, spanEnd, 0, Number.MAX_SAFE_INTEGER, "hypermeter totalBeats");
|
|
1023
|
+
const spanStart = _chunkIFZDWIKMcjs.barGridStart.call(void 0, firstOnset, meters);
|
|
1024
|
+
const slices = _chunkIFZDWIKMcjs.sliceBars.call(void 0, sounding, meters, spanStart, spanEnd, asked.budget);
|
|
986
1025
|
const fullBars = slices.reduce((count, slice) => slice.index >= 0 ? count + 1 : count, 0);
|
|
987
1026
|
const candidates = GROUP_CANDIDATES.filter((groupBars) => fullBars >= groupBars * MIN_GROUPS);
|
|
988
1027
|
if (candidates.length === 0) {
|
|
989
1028
|
return undecidedReading(slices);
|
|
990
1029
|
}
|
|
991
|
-
|
|
1030
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0,
|
|
992
1031
|
candidates.reduce((sum, groupBars) => sum + groupBars * slices.length, 0),
|
|
993
1032
|
"hypermeter readings",
|
|
994
1033
|
asked.budget
|
|
995
1034
|
);
|
|
996
|
-
const novelty = slices.map((_, index) =>
|
|
997
|
-
const cadenceBars = new Set((_nullishCoalesce(asked.cadenceBeats, () => ( []))).map((beat) =>
|
|
1035
|
+
const novelty = slices.map((_, index) => _chunkIFZDWIKMcjs.harmonicNovelty.call(void 0, slices, index));
|
|
1036
|
+
const cadenceBars = new Set((_nullishCoalesce(asked.cadenceBeats, () => ( []))).map((beat) => _chunkS5BKZZGMcjs.barIndexAt.call(void 0, beat, meters)));
|
|
998
1037
|
const hasCadences = cadenceBars.size > 0;
|
|
999
1038
|
const readings = [];
|
|
1000
1039
|
for (const groupBars of candidates) {
|
|
@@ -1005,17 +1044,17 @@ function hypermeter(notes, meter, opts = {}) {
|
|
|
1005
1044
|
}));
|
|
1006
1045
|
const bestFit = phases.reduce((best2, reading) => Math.max(best2, reading.cadenceFit), 0);
|
|
1007
1046
|
for (const { phase, contrast, cadenceFit } of phases) {
|
|
1008
|
-
if (hasCadences && cadenceFit < bestFit -
|
|
1047
|
+
if (hasCadences && cadenceFit < bestFit - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
1009
1048
|
continue;
|
|
1010
1049
|
}
|
|
1011
|
-
const evidence =
|
|
1050
|
+
const evidence = _chunkIFZDWIKMcjs.clamp01.call(void 0,
|
|
1012
1051
|
hasCadences ? (
|
|
1013
1052
|
// Floored at zero rather than summed raw: group heads that restate
|
|
1014
1053
|
// the harmony their group opened with — the commonest way a phrase
|
|
1015
1054
|
// begins — change less than the bars between them, and a negative
|
|
1016
1055
|
// contrast subtracted from the cadence term would count that
|
|
1017
1056
|
// restatement as evidence against the very phase it confirms.
|
|
1018
|
-
CONTRAST_SHARE *
|
|
1057
|
+
CONTRAST_SHARE * _chunkIFZDWIKMcjs.clamp01.call(void 0, contrast) + CADENCE_SHARE * cadenceFit
|
|
1019
1058
|
) : contrast
|
|
1020
1059
|
);
|
|
1021
1060
|
readings.push({
|
|
@@ -1036,8 +1075,8 @@ function hypermeter(notes, meter, opts = {}) {
|
|
|
1036
1075
|
return undecidedReading(slices);
|
|
1037
1076
|
}
|
|
1038
1077
|
const runnerUp = readings.find((reading) => reading.groupBars !== best.groupBars);
|
|
1039
|
-
const margin = runnerUp === void 0 || best.score <=
|
|
1040
|
-
const downbeats = slices.filter((slice) => slice.index >= 0 &&
|
|
1078
|
+
const margin = runnerUp === void 0 || best.score <= _chunkS5BKZZGMcjs.BEAT_EPS ? 1 : _chunkIFZDWIKMcjs.clamp01.call(void 0, (best.score - runnerUp.score) / best.score);
|
|
1079
|
+
const downbeats = slices.filter((slice) => slice.index >= 0 && _chunkIFZDWIKMcjs.floorMod.call(void 0, slice.index - best.phase, best.groupBars) === 0).map((slice) => slice.startBeat);
|
|
1041
1080
|
const cadencePart = hasCadences ? `${Math.round(best.cadenceFit * 100)}% of group ends closed by a cadence` : "no cadences given";
|
|
1042
1081
|
return {
|
|
1043
1082
|
groupBars: best.groupBars,
|
|
@@ -1047,554 +1086,11 @@ function hypermeter(notes, meter, opts = {}) {
|
|
|
1047
1086
|
// runner-up is nearly always a multiple or a divisor of the winner — four
|
|
1048
1087
|
// bars nest inside eight — and a reading of the same accents at another
|
|
1049
1088
|
// level is weaker counter-evidence than an unrelated rival would be.
|
|
1050
|
-
confidence:
|
|
1089
|
+
confidence: _chunkIFZDWIKMcjs.clamp01.call(void 0, best.evidence * (MARGIN_FLOOR + (1 - MARGIN_FLOOR) * margin)),
|
|
1051
1090
|
rationale: `${best.groupBars}-bar groups from bar ${best.phase}: harmonic change ${Math.round(best.contrast * 100)}% stronger on group heads, ${cadencePart}`
|
|
1052
1091
|
};
|
|
1053
1092
|
}
|
|
1054
1093
|
|
|
1055
|
-
// src/analyze/melody/internal.ts
|
|
1056
|
-
var RHYTHM_GRID = 64;
|
|
1057
|
-
function phraseNotes(phrase) {
|
|
1058
|
-
if (Array.isArray(phrase)) {
|
|
1059
|
-
return phrase;
|
|
1060
|
-
}
|
|
1061
|
-
return _chunkXKSO2V5Icjs.assertRecord.call(void 0, phrase, "melodic phrase").notes;
|
|
1062
|
-
}
|
|
1063
|
-
function orderedNotes(notes, name, budget) {
|
|
1064
|
-
_chunkXKSO2V5Icjs.assertNoteEvents.call(void 0, notes, name, { allowNonPositiveDuration: true, budget });
|
|
1065
|
-
const sounding = notes.filter((note) => note.durationBeat > 0).sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);
|
|
1066
|
-
const line = [];
|
|
1067
|
-
for (const note of sounding) {
|
|
1068
|
-
const previous = line[line.length - 1];
|
|
1069
|
-
if (previous !== void 0 && Math.abs(note.startBeat - previous.startBeat) <= _chunkXKSO2V5Icjs.BEAT_EPS) {
|
|
1070
|
-
line[line.length - 1] = note;
|
|
1071
|
-
continue;
|
|
1072
|
-
}
|
|
1073
|
-
line.push(note);
|
|
1074
|
-
}
|
|
1075
|
-
return line;
|
|
1076
|
-
}
|
|
1077
|
-
function intervalsOf(notes) {
|
|
1078
|
-
const out = [];
|
|
1079
|
-
for (let i = 1; i < notes.length; i += 1) {
|
|
1080
|
-
out.push((_nullishCoalesce(_optionalChain([notes, 'access', _43 => _43[i], 'optionalAccess', _44 => _44.pitch]), () => ( 0))) - (_nullishCoalesce(_optionalChain([notes, 'access', _45 => _45[i - 1], 'optionalAccess', _46 => _46.pitch]), () => ( 0))));
|
|
1081
|
-
}
|
|
1082
|
-
return out;
|
|
1083
|
-
}
|
|
1084
|
-
function onsetGaps(notes) {
|
|
1085
|
-
const out = [];
|
|
1086
|
-
for (let i = 1; i < notes.length; i += 1) {
|
|
1087
|
-
out.push((_nullishCoalesce(_optionalChain([notes, 'access', _47 => _47[i], 'optionalAccess', _48 => _48.startBeat]), () => ( 0))) - (_nullishCoalesce(_optionalChain([notes, 'access', _49 => _49[i - 1], 'optionalAccess', _50 => _50.startBeat]), () => ( 0))));
|
|
1088
|
-
}
|
|
1089
|
-
return out;
|
|
1090
|
-
}
|
|
1091
|
-
function retrogradeGaps(notes) {
|
|
1092
|
-
const out = [];
|
|
1093
|
-
for (let i = notes.length - 1; i >= 1; i -= 1) {
|
|
1094
|
-
const later = notes[i];
|
|
1095
|
-
const earlier = notes[i - 1];
|
|
1096
|
-
out.push(
|
|
1097
|
-
(_nullishCoalesce(_optionalChain([later, 'optionalAccess', _51 => _51.startBeat]), () => ( 0))) + (_nullishCoalesce(_optionalChain([later, 'optionalAccess', _52 => _52.durationBeat]), () => ( 0))) - ((_nullishCoalesce(_optionalChain([earlier, 'optionalAccess', _53 => _53.startBeat]), () => ( 0))) + (_nullishCoalesce(_optionalChain([earlier, 'optionalAccess', _54 => _54.durationBeat]), () => ( 0))))
|
|
1098
|
-
);
|
|
1099
|
-
}
|
|
1100
|
-
return out;
|
|
1101
|
-
}
|
|
1102
|
-
function rhythmProfile(gaps) {
|
|
1103
|
-
const unit = _nullishCoalesce(gaps[0], () => ( 0));
|
|
1104
|
-
if (!(unit > _chunkXKSO2V5Icjs.BEAT_EPS)) {
|
|
1105
|
-
return gaps.length === 0 ? [] : null;
|
|
1106
|
-
}
|
|
1107
|
-
return gaps.map((gap) => Math.round(gap / unit * RHYTHM_GRID) / RHYTHM_GRID);
|
|
1108
|
-
}
|
|
1109
|
-
function sameNumbers(a, b) {
|
|
1110
|
-
if (a.length !== b.length) {
|
|
1111
|
-
return false;
|
|
1112
|
-
}
|
|
1113
|
-
for (let i = 0; i < a.length; i += 1) {
|
|
1114
|
-
if (Math.abs((_nullishCoalesce(a[i], () => ( 0))) - (_nullishCoalesce(b[i], () => ( 0)))) > _chunkXKSO2V5Icjs.BEAT_EPS) {
|
|
1115
|
-
return false;
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
return true;
|
|
1119
|
-
}
|
|
1120
|
-
function roundTo(value, digits) {
|
|
1121
|
-
const scale = 10 ** digits;
|
|
1122
|
-
return Math.round(value * scale) / scale;
|
|
1123
|
-
}
|
|
1124
|
-
function endBeatOf(notes) {
|
|
1125
|
-
let end = 0;
|
|
1126
|
-
for (const note of notes) {
|
|
1127
|
-
end = Math.max(end, note.startBeat + note.durationBeat);
|
|
1128
|
-
}
|
|
1129
|
-
return end;
|
|
1130
|
-
}
|
|
1131
|
-
function onsetSpan(notes) {
|
|
1132
|
-
if (notes.length < 2) {
|
|
1133
|
-
return 0;
|
|
1134
|
-
}
|
|
1135
|
-
return (_nullishCoalesce(_optionalChain([notes, 'access', _55 => _55[notes.length - 1], 'optionalAccess', _56 => _56.startBeat]), () => ( 0))) - (_nullishCoalesce(_optionalChain([notes, 'access', _57 => _57[0], 'optionalAccess', _58 => _58.startBeat]), () => ( 0)));
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
// src/analyze/melody/contour.ts
|
|
1139
|
-
var DIRECTIONAL_SHARE = 0.75;
|
|
1140
|
-
function extremes(notes) {
|
|
1141
|
-
if (notes.length === 0) {
|
|
1142
|
-
return { peak: -1, trough: -1 };
|
|
1143
|
-
}
|
|
1144
|
-
let peak = 0;
|
|
1145
|
-
let trough = 0;
|
|
1146
|
-
for (let i = 1; i < notes.length; i += 1) {
|
|
1147
|
-
if ((_nullishCoalesce(_optionalChain([notes, 'access', _59 => _59[i], 'optionalAccess', _60 => _60.pitch]), () => ( 0))) > (_nullishCoalesce(_optionalChain([notes, 'access', _61 => _61[peak], 'optionalAccess', _62 => _62.pitch]), () => ( 0)))) {
|
|
1148
|
-
peak = i;
|
|
1149
|
-
}
|
|
1150
|
-
if ((_nullishCoalesce(_optionalChain([notes, 'access', _63 => _63[i], 'optionalAccess', _64 => _64.pitch]), () => ( 0))) < (_nullishCoalesce(_optionalChain([notes, 'access', _65 => _65[trough], 'optionalAccess', _66 => _66.pitch]), () => ( 0)))) {
|
|
1151
|
-
trough = i;
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
return { peak, trough };
|
|
1155
|
-
}
|
|
1156
|
-
function melodicContour(notes) {
|
|
1157
|
-
const line = orderedNotes(phraseNotes(notes), "contour notes");
|
|
1158
|
-
const intervals = intervalsOf(line);
|
|
1159
|
-
const directions = intervals.map(
|
|
1160
|
-
(step) => step > 0 ? "up" : step < 0 ? "down" : "same"
|
|
1161
|
-
);
|
|
1162
|
-
const { peak, trough } = extremes(line);
|
|
1163
|
-
const range = (_nullishCoalesce(_optionalChain([line, 'access', _67 => _67[peak], 'optionalAccess', _68 => _68.pitch]), () => ( 0))) - (_nullishCoalesce(_optionalChain([line, 'access', _69 => _69[trough], 'optionalAccess', _70 => _70.pitch]), () => ( 0)));
|
|
1164
|
-
const motion = intervals.reduce((sum, step) => sum + Math.abs(step), 0);
|
|
1165
|
-
if (motion === 0) {
|
|
1166
|
-
return {
|
|
1167
|
-
directions,
|
|
1168
|
-
shape: "static",
|
|
1169
|
-
peakIndex: peak,
|
|
1170
|
-
troughIndex: trough,
|
|
1171
|
-
range: 0,
|
|
1172
|
-
rationale: line.length < 2 ? "Too short to have a direction, so the line is static" : "Every note at the same pitch, so the line is static"
|
|
1173
|
-
};
|
|
1174
|
-
}
|
|
1175
|
-
const first = _nullishCoalesce(_optionalChain([line, 'access', _71 => _71[0], 'optionalAccess', _72 => _72.pitch]), () => ( 0));
|
|
1176
|
-
const last = _nullishCoalesce(_optionalChain([line, 'access', _73 => _73[line.length - 1], 'optionalAccess', _74 => _74.pitch]), () => ( 0));
|
|
1177
|
-
const net = last - first;
|
|
1178
|
-
const directness = Math.abs(net) / motion;
|
|
1179
|
-
const share = Math.round(directness * 100);
|
|
1180
|
-
if (directness >= DIRECTIONAL_SHARE) {
|
|
1181
|
-
const shape = net > 0 ? "ascending" : "descending";
|
|
1182
|
-
return {
|
|
1183
|
-
directions,
|
|
1184
|
-
shape,
|
|
1185
|
-
peakIndex: peak,
|
|
1186
|
-
troughIndex: trough,
|
|
1187
|
-
range,
|
|
1188
|
-
rationale: `${share}% of the motion runs ${net > 0 ? "up" : "down"}, so the line is ${shape}`
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
|
-
const peakPitch = _nullishCoalesce(_optionalChain([line, 'access', _75 => _75[peak], 'optionalAccess', _76 => _76.pitch]), () => ( 0));
|
|
1192
|
-
const archMotion = peakPitch - first + (peakPitch - last);
|
|
1193
|
-
if (peak > 0 && peak < line.length - 1 && archMotion >= DIRECTIONAL_SHARE * motion) {
|
|
1194
|
-
return {
|
|
1195
|
-
directions,
|
|
1196
|
-
shape: "arch",
|
|
1197
|
-
peakIndex: peak,
|
|
1198
|
-
troughIndex: trough,
|
|
1199
|
-
range,
|
|
1200
|
-
rationale: `Rises to its peak at note ${peak + 1} of ${line.length} and falls back, so the line is an arch`
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
|
-
return {
|
|
1204
|
-
directions,
|
|
1205
|
-
shape: "wave",
|
|
1206
|
-
peakIndex: peak,
|
|
1207
|
-
troughIndex: trough,
|
|
1208
|
-
range,
|
|
1209
|
-
rationale: `Changes direction without settling either way (${share}% of the motion nets out), so the line is a wave`
|
|
1210
|
-
};
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
// src/analyze/melody/motifs.ts
|
|
1214
|
-
var MIN_CELL_NOTES = 3;
|
|
1215
|
-
var MAX_CELL_NOTES = 8;
|
|
1216
|
-
var MIN_OCCURRENCES = 2;
|
|
1217
|
-
function windowSignature(notes) {
|
|
1218
|
-
const profile = rhythmProfile(onsetGaps(notes));
|
|
1219
|
-
if (profile === null) {
|
|
1220
|
-
return null;
|
|
1221
|
-
}
|
|
1222
|
-
return `${notes.length}|${intervalsOf(notes).join(",")}|${profile.join(",")}`;
|
|
1223
|
-
}
|
|
1224
|
-
function nonOverlapping(starts, length) {
|
|
1225
|
-
const picked = [];
|
|
1226
|
-
let lastEnd = Number.NEGATIVE_INFINITY;
|
|
1227
|
-
for (const start of starts) {
|
|
1228
|
-
if (start > lastEnd) {
|
|
1229
|
-
picked.push(start);
|
|
1230
|
-
lastEnd = start + length - 1;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
return picked;
|
|
1234
|
-
}
|
|
1235
|
-
function subsumes(outer, inner) {
|
|
1236
|
-
let at = 0;
|
|
1237
|
-
for (const start of inner.starts) {
|
|
1238
|
-
while (at + 1 < outer.starts.length && (_nullishCoalesce(outer.starts[at + 1], () => ( 0))) <= start) {
|
|
1239
|
-
at += 1;
|
|
1240
|
-
}
|
|
1241
|
-
const from = outer.starts[at];
|
|
1242
|
-
if (from === void 0 || start < from || start + inner.length > from + outer.length) {
|
|
1243
|
-
return false;
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
return true;
|
|
1247
|
-
}
|
|
1248
|
-
function listBeats(occurrences) {
|
|
1249
|
-
const shown = occurrences.slice(0, 5).map((occurrence) => roundTo(occurrence.startBeat, 3));
|
|
1250
|
-
return occurrences.length > shown.length ? `${shown.join(", ")}, and ${occurrences.length - shown.length} more` : shown.join(", ");
|
|
1251
|
-
}
|
|
1252
|
-
function motifFromGroup(group, notes) {
|
|
1253
|
-
const first = _nullishCoalesce(group.starts[0], () => ( 0));
|
|
1254
|
-
const prime = notes.slice(first, first + group.length).map((note) => ({ ...note }));
|
|
1255
|
-
const primePitch = _nullishCoalesce(_optionalChain([prime, 'access', _77 => _77[0], 'optionalAccess', _78 => _78.pitch]), () => ( 0));
|
|
1256
|
-
const primeSpan = onsetSpan(prime);
|
|
1257
|
-
const occurrences = group.starts.map((start) => {
|
|
1258
|
-
const statement = notes.slice(start, start + group.length);
|
|
1259
|
-
const span = onsetSpan(statement);
|
|
1260
|
-
return {
|
|
1261
|
-
noteIndex: start,
|
|
1262
|
-
startBeat: _nullishCoalesce(_optionalChain([statement, 'access', _79 => _79[0], 'optionalAccess', _80 => _80.startBeat]), () => ( 0)),
|
|
1263
|
-
endBeat: endBeatOf(statement),
|
|
1264
|
-
transpose: (_nullishCoalesce(_optionalChain([statement, 'access', _81 => _81[0], 'optionalAccess', _82 => _82.pitch]), () => ( 0))) - primePitch,
|
|
1265
|
-
timeRatio: primeSpan > _chunkXKSO2V5Icjs.BEAT_EPS ? span / primeSpan : 1
|
|
1266
|
-
};
|
|
1267
|
-
});
|
|
1268
|
-
const shape = melodicContour(prime).shape;
|
|
1269
|
-
const transposed = occurrences.some((occurrence) => occurrence.transpose !== 0);
|
|
1270
|
-
const stretched = occurrences.some((occurrence) => Math.abs(occurrence.timeRatio - 1) > _chunkXKSO2V5Icjs.BEAT_EPS);
|
|
1271
|
-
const detail = [
|
|
1272
|
-
transposed ? "at more than one pitch level" : "at the same pitch level",
|
|
1273
|
-
stretched ? "and in more than one set of note values" : ""
|
|
1274
|
-
].filter((part) => part !== "").join(" ");
|
|
1275
|
-
return {
|
|
1276
|
-
notes: prime,
|
|
1277
|
-
intervals: intervalsOf(prime),
|
|
1278
|
-
rhythm: _nullishCoalesce(rhythmProfile(onsetGaps(prime)), () => ( [])),
|
|
1279
|
-
occurrences,
|
|
1280
|
-
rationale: `${group.length}-note ${shape} cell stated ${occurrences.length} ${occurrences.length === 1 ? "time" : "times"} (beats ${listBeats(occurrences)}), ${detail}`
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
function extractMotifs(notes, opts = {}) {
|
|
1284
|
-
const asked = _chunkXKSO2V5Icjs.assertOptions.call(void 0, opts, "opts");
|
|
1285
|
-
const minNotes = _chunkXKSO2V5Icjs.assertInteger.call(void 0, _nullishCoalesce(asked.minNotes, () => ( MIN_CELL_NOTES)), "motif minNotes", 2, 64);
|
|
1286
|
-
const maxNotes = _chunkXKSO2V5Icjs.assertInteger.call(void 0, _nullishCoalesce(asked.maxNotes, () => ( MAX_CELL_NOTES)), "motif maxNotes", 2, 64);
|
|
1287
|
-
if (maxNotes < minNotes) {
|
|
1288
|
-
throw new (0, _chunkXKSO2V5Icjs.InvalidInputError)(
|
|
1289
|
-
`motif maxNotes must be at least minNotes ${minNotes}; received ${maxNotes}`
|
|
1290
|
-
);
|
|
1291
|
-
}
|
|
1292
|
-
const minOccurrences = _chunkXKSO2V5Icjs.assertInteger.call(void 0,
|
|
1293
|
-
_nullishCoalesce(asked.minOccurrences, () => ( MIN_OCCURRENCES)),
|
|
1294
|
-
"motif minOccurrences",
|
|
1295
|
-
2
|
|
1296
|
-
);
|
|
1297
|
-
const sounding = orderedNotes(notes, "melody notes", asked.budget);
|
|
1298
|
-
if (sounding.length < minNotes) {
|
|
1299
|
-
return [];
|
|
1300
|
-
}
|
|
1301
|
-
_chunkXKSO2V5Icjs.assertGenerationBudget.call(void 0,
|
|
1302
|
-
sounding.length * (maxNotes - minNotes + 1),
|
|
1303
|
-
"motif windows",
|
|
1304
|
-
asked.budget
|
|
1305
|
-
);
|
|
1306
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1307
|
-
for (let length = minNotes; length <= maxNotes; length += 1) {
|
|
1308
|
-
for (let start = 0; start + length <= sounding.length; start += 1) {
|
|
1309
|
-
const signature = windowSignature(sounding.slice(start, start + length));
|
|
1310
|
-
if (signature === null) {
|
|
1311
|
-
continue;
|
|
1312
|
-
}
|
|
1313
|
-
const group = groups.get(signature);
|
|
1314
|
-
if (group === void 0) {
|
|
1315
|
-
groups.set(signature, { signature, length, starts: [start] });
|
|
1316
|
-
} else {
|
|
1317
|
-
group.starts.push(start);
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
const candidates = [...groups.values()].map((group) => ({ ...group, starts: nonOverlapping(group.starts, group.length) })).filter((group) => group.starts.length >= minOccurrences).sort(
|
|
1322
|
-
(a, b) => b.length - a.length || b.starts.length - a.starts.length || (_nullishCoalesce(a.starts[0], () => ( 0))) - (_nullishCoalesce(b.starts[0], () => ( 0))) || (a.signature < b.signature ? -1 : a.signature > b.signature ? 1 : 0)
|
|
1323
|
-
);
|
|
1324
|
-
const keptByLength = /* @__PURE__ */ new Map();
|
|
1325
|
-
const kept = [];
|
|
1326
|
-
let comparisons = 0;
|
|
1327
|
-
for (const candidate of candidates) {
|
|
1328
|
-
const first = _nullishCoalesce(candidate.starts[0], () => ( 0));
|
|
1329
|
-
let covered = false;
|
|
1330
|
-
for (let length = candidate.length + 1; length <= maxNotes && !covered; length += 1) {
|
|
1331
|
-
const byStart = keptByLength.get(length);
|
|
1332
|
-
if (byStart === void 0) {
|
|
1333
|
-
continue;
|
|
1334
|
-
}
|
|
1335
|
-
for (let from = Math.max(0, first - (length - candidate.length)); from <= first; from += 1) {
|
|
1336
|
-
comparisons += 1;
|
|
1337
|
-
const outer = byStart.get(from);
|
|
1338
|
-
if (outer === void 0 || outer.starts.length < candidate.starts.length) {
|
|
1339
|
-
continue;
|
|
1340
|
-
}
|
|
1341
|
-
comparisons += outer.starts.length + candidate.starts.length;
|
|
1342
|
-
if (subsumes(outer, candidate)) {
|
|
1343
|
-
covered = true;
|
|
1344
|
-
break;
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
_chunkXKSO2V5Icjs.assertGenerationBudget.call(void 0, comparisons, "motif subsumption comparisons", asked.budget);
|
|
1349
|
-
if (!covered) {
|
|
1350
|
-
kept.push(candidate);
|
|
1351
|
-
let byStart = keptByLength.get(candidate.length);
|
|
1352
|
-
if (byStart === void 0) {
|
|
1353
|
-
byStart = /* @__PURE__ */ new Map();
|
|
1354
|
-
keptByLength.set(candidate.length, byStart);
|
|
1355
|
-
}
|
|
1356
|
-
for (const start of candidate.starts) {
|
|
1357
|
-
byStart.set(start, candidate);
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
return kept.map((group) => motifFromGroup(group, sounding));
|
|
1362
|
-
}
|
|
1363
|
-
function motifFromNotes(notes) {
|
|
1364
|
-
const sounding = orderedNotes(notes, "motif notes").map((note) => ({ ...note }));
|
|
1365
|
-
return motifFromGroup(
|
|
1366
|
-
{ signature: _nullishCoalesce(windowSignature(sounding), () => ( "")), length: sounding.length, starts: [0] },
|
|
1367
|
-
sounding
|
|
1368
|
-
);
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
// src/analyze/melody/relation.ts
|
|
1372
|
-
function spellingOf(key) {
|
|
1373
|
-
if (key === void 0) {
|
|
1374
|
-
return "sharp";
|
|
1375
|
-
}
|
|
1376
|
-
return _chunkYPB3A3EYcjs.keySignatureFifths.call(void 0, key.tonic, key.scale) < 0 ? "flat" : "sharp";
|
|
1377
|
-
}
|
|
1378
|
-
function intervalBetween(from, to, key) {
|
|
1379
|
-
const spelling = spellingOf(key);
|
|
1380
|
-
return _chunkXKSO2V5Icjs.spelledInterval.call(void 0, _chunkXKSO2V5Icjs.midiToNote.call(void 0, from, spelling), _chunkXKSO2V5Icjs.midiToNote.call(void 0, to, spelling));
|
|
1381
|
-
}
|
|
1382
|
-
function intervalName(interval) {
|
|
1383
|
-
return `${interval.descending && interval.semitones !== 0 ? "-" : ""}${interval.quality}${interval.number}`;
|
|
1384
|
-
}
|
|
1385
|
-
function ladderIndices(notes, key) {
|
|
1386
|
-
const root = _chunkXKSO2V5Icjs.pitchClassOf.call(void 0, key.rootPc);
|
|
1387
|
-
const offsets = _chunkYPB3A3EYcjs.scaleTonesInDegreeOrder.call(void 0, key).map((pc) => _chunkXKSO2V5Icjs.pitchClassOf.call(void 0, pc - root));
|
|
1388
|
-
if (offsets.length === 0) {
|
|
1389
|
-
return null;
|
|
1390
|
-
}
|
|
1391
|
-
const out = [];
|
|
1392
|
-
for (const note of notes) {
|
|
1393
|
-
const semitones = note.pitch - root;
|
|
1394
|
-
const octave = Math.floor(semitones / 12);
|
|
1395
|
-
const degree = offsets.indexOf(semitones - octave * 12);
|
|
1396
|
-
if (degree < 0) {
|
|
1397
|
-
return null;
|
|
1398
|
-
}
|
|
1399
|
-
out.push(octave * offsets.length + degree);
|
|
1400
|
-
}
|
|
1401
|
-
return out;
|
|
1402
|
-
}
|
|
1403
|
-
function ladderSteps(indices) {
|
|
1404
|
-
const out = [];
|
|
1405
|
-
for (let i = 1; i < indices.length; i += 1) {
|
|
1406
|
-
out.push((_nullishCoalesce(indices[i], () => ( 0))) - (_nullishCoalesce(indices[i - 1], () => ( 0))));
|
|
1407
|
-
}
|
|
1408
|
-
return out;
|
|
1409
|
-
}
|
|
1410
|
-
function pitchShapes(a, b) {
|
|
1411
|
-
const shapes = [];
|
|
1412
|
-
if (sameNumbers(a, b)) {
|
|
1413
|
-
shapes.push("exact");
|
|
1414
|
-
}
|
|
1415
|
-
if (sameNumbers(
|
|
1416
|
-
b,
|
|
1417
|
-
a.map((step) => -step)
|
|
1418
|
-
)) {
|
|
1419
|
-
shapes.push("inversion");
|
|
1420
|
-
}
|
|
1421
|
-
const reversed = [...a].reverse();
|
|
1422
|
-
if (sameNumbers(
|
|
1423
|
-
b,
|
|
1424
|
-
reversed.map((step) => -step)
|
|
1425
|
-
)) {
|
|
1426
|
-
shapes.push("retrograde");
|
|
1427
|
-
}
|
|
1428
|
-
if (sameNumbers(b, reversed)) {
|
|
1429
|
-
shapes.push("retrogradeInversion");
|
|
1430
|
-
}
|
|
1431
|
-
return shapes;
|
|
1432
|
-
}
|
|
1433
|
-
function totalGap(gaps) {
|
|
1434
|
-
return gaps.reduce((sum, gap) => sum + gap, 0);
|
|
1435
|
-
}
|
|
1436
|
-
function stretchRatio(modelGaps, answerGaps) {
|
|
1437
|
-
const span = totalGap(modelGaps);
|
|
1438
|
-
return span > _chunkXKSO2V5Icjs.BEAT_EPS ? totalGap(answerGaps) / span : 1;
|
|
1439
|
-
}
|
|
1440
|
-
function stretchPhrase(ratio) {
|
|
1441
|
-
return `note values ${roundTo(ratio, 3)}x the model's`;
|
|
1442
|
-
}
|
|
1443
|
-
function relateMotifs(a, b, keyLike) {
|
|
1444
|
-
const key = keyLike === void 0 ? void 0 : _chunkYPB3A3EYcjs.resolveKey.call(void 0, keyLike);
|
|
1445
|
-
const model = _chunkXKSO2V5Icjs.assertArray.call(void 0, _chunkXKSO2V5Icjs.assertRecord.call(void 0, a, "a").notes, "a.notes");
|
|
1446
|
-
const answer = _chunkXKSO2V5Icjs.assertArray.call(void 0, _chunkXKSO2V5Icjs.assertRecord.call(void 0, b, "b").notes, "b.notes");
|
|
1447
|
-
if (model.length === 0 || model.length !== answer.length) {
|
|
1448
|
-
return null;
|
|
1449
|
-
}
|
|
1450
|
-
const modelGaps = onsetGaps(model);
|
|
1451
|
-
const answerGaps = onsetGaps(answer);
|
|
1452
|
-
const modelProfile = rhythmProfile(modelGaps);
|
|
1453
|
-
const answerProfile = rhythmProfile(answerGaps);
|
|
1454
|
-
const retroGaps = retrogradeGaps(model);
|
|
1455
|
-
const retroProfile = rhythmProfile(retroGaps);
|
|
1456
|
-
if (modelProfile === null || answerProfile === null) {
|
|
1457
|
-
return null;
|
|
1458
|
-
}
|
|
1459
|
-
const forward = sameNumbers(modelProfile, answerProfile);
|
|
1460
|
-
const backward = retroProfile !== null && sameNumbers(retroProfile, answerProfile);
|
|
1461
|
-
const semitones = (_nullishCoalesce(_optionalChain([answer, 'access', _83 => _83[0], 'optionalAccess', _84 => _84.pitch]), () => ( 0))) - (_nullishCoalesce(_optionalChain([model, 'access', _85 => _85[0], 'optionalAccess', _86 => _86.pitch]), () => ( 0)));
|
|
1462
|
-
const interval = intervalBetween(_nullishCoalesce(_optionalChain([model, 'access', _87 => _87[0], 'optionalAccess', _88 => _88.pitch]), () => ( 0)), _nullishCoalesce(_optionalChain([answer, 'access', _89 => _89[0], 'optionalAccess', _90 => _90.pitch]), () => ( 0)), key);
|
|
1463
|
-
const gap = (_nullishCoalesce(_optionalChain([answer, 'access', _91 => _91[0], 'optionalAccess', _92 => _92.startBeat]), () => ( 0))) - endBeatOf(model);
|
|
1464
|
-
const sequence = gap >= -_chunkQ6R3QAZDcjs.HUMANIZE_ADJACENCY && gap <= _chunkQ6R3QAZDcjs.HUMANIZE_ADJACENCY;
|
|
1465
|
-
const timeRatio = stretchRatio(modelGaps, answerGaps);
|
|
1466
|
-
const stretched = Math.abs(timeRatio - 1) > _chunkXKSO2V5Icjs.BEAT_EPS;
|
|
1467
|
-
const shapes = pitchShapes(intervalsOf(model), intervalsOf(answer));
|
|
1468
|
-
const base = { sequence, semitones, interval, timeRatio };
|
|
1469
|
-
const backwardBase = { ...base, timeRatio: stretchRatio(retroGaps, answerGaps) };
|
|
1470
|
-
if (forward && shapes.includes("exact")) {
|
|
1471
|
-
if (semitones === 0) {
|
|
1472
|
-
if (!stretched) {
|
|
1473
|
-
return {
|
|
1474
|
-
...base,
|
|
1475
|
-
kind: "repetition",
|
|
1476
|
-
rationale: "Exact repetition: the same pitches in the same rhythm"
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
const augmented = timeRatio > 1;
|
|
1480
|
-
return {
|
|
1481
|
-
...base,
|
|
1482
|
-
kind: augmented ? "augmentation" : "diminution",
|
|
1483
|
-
rationale: `${augmented ? "Augmentation" : "Diminution"}: the same pitches in ${stretchPhrase(timeRatio)}`
|
|
1484
|
-
};
|
|
1485
|
-
}
|
|
1486
|
-
const name = intervalName(interval);
|
|
1487
|
-
return {
|
|
1488
|
-
...base,
|
|
1489
|
-
kind: "transposition",
|
|
1490
|
-
rationale: sequence ? `Real sequence: the cell restated ${name} away with every interval kept literally` + (stretched ? `, in ${stretchPhrase(timeRatio)}` : "") : `Transposition by ${name}: every interval kept literally` + (stretched ? `, in ${stretchPhrase(timeRatio)}` : "")
|
|
1491
|
-
};
|
|
1492
|
-
}
|
|
1493
|
-
if (forward && key !== void 0) {
|
|
1494
|
-
const modelLadder = ladderIndices(model, key.scale);
|
|
1495
|
-
const answerLadder = ladderIndices(answer, key.scale);
|
|
1496
|
-
if (modelLadder !== null && answerLadder !== null) {
|
|
1497
|
-
const degrees = (_nullishCoalesce(answerLadder[0], () => ( 0))) - (_nullishCoalesce(modelLadder[0], () => ( 0)));
|
|
1498
|
-
if (degrees !== 0 && sameNumbers(ladderSteps(modelLadder), ladderSteps(answerLadder))) {
|
|
1499
|
-
const direction = degrees > 0 ? "higher" : "lower";
|
|
1500
|
-
const count = Math.abs(degrees);
|
|
1501
|
-
return {
|
|
1502
|
-
...base,
|
|
1503
|
-
kind: "tonalTransposition",
|
|
1504
|
-
degrees,
|
|
1505
|
-
rationale: `${sequence ? "Tonal sequence" : "Tonal transposition"}: the cell restated ${count} scale ${count === 1 ? "degree" : "degrees"} ${direction} inside the key, so the key resizes its intervals \u2014 a real one would have kept them literal`
|
|
1506
|
-
};
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
if (forward && shapes.includes("inversion")) {
|
|
1511
|
-
return {
|
|
1512
|
-
...base,
|
|
1513
|
-
kind: "inversion",
|
|
1514
|
-
rationale: "Inversion: every interval turned upside down about the first note" + (stretched ? `, in ${stretchPhrase(timeRatio)}` : "")
|
|
1515
|
-
};
|
|
1516
|
-
}
|
|
1517
|
-
if (backward && shapes.includes("retrograde")) {
|
|
1518
|
-
return {
|
|
1519
|
-
...backwardBase,
|
|
1520
|
-
kind: "retrograde",
|
|
1521
|
-
rationale: "Retrograde: the cell read back to front, rhythm included"
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1524
|
-
if (backward && shapes.includes("retrogradeInversion")) {
|
|
1525
|
-
return {
|
|
1526
|
-
...backwardBase,
|
|
1527
|
-
kind: "retrogradeInversion",
|
|
1528
|
-
rationale: "Retrograde inversion: the cell read back to front and turned upside down"
|
|
1529
|
-
};
|
|
1530
|
-
}
|
|
1531
|
-
return null;
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
// src/analyze/melody/similarity.ts
|
|
1535
|
-
var INTERVAL_TOLERANCE = 6;
|
|
1536
|
-
var PITCH_WEIGHT = 0.75;
|
|
1537
|
-
var RHYTHM_WEIGHT = 0.25;
|
|
1538
|
-
function gradedEditDistance(a, b, cost) {
|
|
1539
|
-
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
1540
|
-
for (let i = 1; i <= a.length; i += 1) {
|
|
1541
|
-
const row = new Array(b.length + 1);
|
|
1542
|
-
row[0] = i;
|
|
1543
|
-
for (let j = 1; j <= b.length; j += 1) {
|
|
1544
|
-
const substitution = (_nullishCoalesce(previous[j - 1], () => ( 0))) + cost(_nullishCoalesce(a[i - 1], () => ( 0)), _nullishCoalesce(b[j - 1], () => ( 0)));
|
|
1545
|
-
const deletion = (_nullishCoalesce(previous[j], () => ( 0))) + 1;
|
|
1546
|
-
const insertion = (_nullishCoalesce(row[j - 1], () => ( 0))) + 1;
|
|
1547
|
-
row[j] = Math.min(substitution, deletion, insertion);
|
|
1548
|
-
}
|
|
1549
|
-
previous = row;
|
|
1550
|
-
}
|
|
1551
|
-
return _nullishCoalesce(previous[b.length], () => ( 0));
|
|
1552
|
-
}
|
|
1553
|
-
function distanceToSimilarity(distance, a, b) {
|
|
1554
|
-
const longest = Math.max(a, b);
|
|
1555
|
-
if (longest === 0) {
|
|
1556
|
-
return 1;
|
|
1557
|
-
}
|
|
1558
|
-
return Math.min(1, Math.max(0, 1 - distance / longest));
|
|
1559
|
-
}
|
|
1560
|
-
function intervalCost(x, y) {
|
|
1561
|
-
return Math.min(1, Math.abs(x - y) / INTERVAL_TOLERANCE);
|
|
1562
|
-
}
|
|
1563
|
-
function gapCost(x, y) {
|
|
1564
|
-
if (!(x > _chunkXKSO2V5Icjs.BEAT_EPS) || !(y > _chunkXKSO2V5Icjs.BEAT_EPS)) {
|
|
1565
|
-
return Math.abs(x - y) > _chunkXKSO2V5Icjs.BEAT_EPS ? 1 : 0;
|
|
1566
|
-
}
|
|
1567
|
-
return Math.min(1, Math.abs(Math.log2(x / y)));
|
|
1568
|
-
}
|
|
1569
|
-
function compareMelodies(a, b) {
|
|
1570
|
-
const left = orderedNotes(phraseNotes(a), "melody a");
|
|
1571
|
-
const right = orderedNotes(phraseNotes(b), "melody b");
|
|
1572
|
-
const leftIntervals = intervalsOf(left);
|
|
1573
|
-
const rightIntervals = intervalsOf(right);
|
|
1574
|
-
const pitchSimilarity = distanceToSimilarity(
|
|
1575
|
-
gradedEditDistance(leftIntervals, rightIntervals, intervalCost),
|
|
1576
|
-
leftIntervals.length,
|
|
1577
|
-
rightIntervals.length
|
|
1578
|
-
);
|
|
1579
|
-
const leftGaps = _nullishCoalesce(rhythmProfile(onsetGaps(left)), () => ( onsetGaps(left)));
|
|
1580
|
-
const rightGaps = _nullishCoalesce(rhythmProfile(onsetGaps(right)), () => ( onsetGaps(right)));
|
|
1581
|
-
const rhythmSimilarity = distanceToSimilarity(
|
|
1582
|
-
gradedEditDistance(leftGaps, rightGaps, gapCost),
|
|
1583
|
-
leftGaps.length,
|
|
1584
|
-
rightGaps.length
|
|
1585
|
-
);
|
|
1586
|
-
const similarity = PITCH_WEIGHT * pitchSimilarity + RHYTHM_WEIGHT * rhythmSimilarity;
|
|
1587
|
-
return {
|
|
1588
|
-
similarity,
|
|
1589
|
-
pitchSimilarity,
|
|
1590
|
-
rhythmSimilarity,
|
|
1591
|
-
rationale: `Interval shapes ${Math.round(pitchSimilarity * 100)}% alike, rhythms ${Math.round(rhythmSimilarity * 100)}% alike, weighted ${PITCH_WEIGHT}/${RHYTHM_WEIGHT} into ${roundTo(similarity, 3)}`
|
|
1592
|
-
};
|
|
1593
|
-
}
|
|
1594
|
-
function melodicSimilarity(a, b) {
|
|
1595
|
-
return compareMelodies(a, b).similarity;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
1094
|
// src/analyze/form/phrase.ts
|
|
1599
1095
|
var CADENCE_BOUNDARY_STRENGTH = {
|
|
1600
1096
|
authentic: 0.9,
|
|
@@ -1632,7 +1128,7 @@ var SIGNAL_ORDER = [
|
|
|
1632
1128
|
"hypermeter"
|
|
1633
1129
|
];
|
|
1634
1130
|
function addSignal(boundaries, beat, signal, strength, cadence = null) {
|
|
1635
|
-
const key = Math.round(beat /
|
|
1131
|
+
const key = Math.round(beat / _chunkS5BKZZGMcjs.BEAT_EPS);
|
|
1636
1132
|
const existing = boundaries.get(key);
|
|
1637
1133
|
const boundary = _nullishCoalesce(existing, () => ( { beat, strengths: /* @__PURE__ */ new Map(), cadence: null }));
|
|
1638
1134
|
boundary.strengths.set(signal, Math.max(_nullishCoalesce(boundary.strengths.get(signal), () => ( 0)), strength));
|
|
@@ -1646,7 +1142,7 @@ function readBoundary(boundary) {
|
|
|
1646
1142
|
(a, b) => b[1] - a[1] || SIGNAL_ORDER.indexOf(a[0]) - SIGNAL_ORDER.indexOf(b[0])
|
|
1647
1143
|
);
|
|
1648
1144
|
return {
|
|
1649
|
-
strength:
|
|
1145
|
+
strength: _chunkIFZDWIKMcjs.combineEvidence.call(void 0, signals.map(([, strength]) => strength)),
|
|
1650
1146
|
signals: signals.map(([signal]) => signal)
|
|
1651
1147
|
};
|
|
1652
1148
|
}
|
|
@@ -1667,7 +1163,7 @@ function choosePhrasePath(beats, strengths, minPhraseBeats, expected) {
|
|
|
1667
1163
|
continue;
|
|
1668
1164
|
}
|
|
1669
1165
|
const length = endBeat - (_nullishCoalesce(beats[i], () => ( 0)));
|
|
1670
|
-
if (length < minPhraseBeats -
|
|
1166
|
+
if (length < minPhraseBeats - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
1671
1167
|
continue;
|
|
1672
1168
|
}
|
|
1673
1169
|
const gain = (_nullishCoalesce(value[i], () => ( 0))) + (_nullishCoalesce(strengths[j], () => ( 0))) + lengthFit(length, expected) - PHRASE_CUT_COST;
|
|
@@ -1690,43 +1186,32 @@ function choosePhrasePath(beats, strengths, minPhraseBeats, expected) {
|
|
|
1690
1186
|
return path;
|
|
1691
1187
|
}
|
|
1692
1188
|
function arrivalEnd(timeline, atBeat) {
|
|
1693
|
-
return _nullishCoalesce(_optionalChain([
|
|
1694
|
-
}
|
|
1695
|
-
function barSpanOf(startBeat, endBeat, meters) {
|
|
1696
|
-
const firstBar = _chunkXKSO2V5Icjs.barIndexAt.call(void 0, startBeat, meters);
|
|
1697
|
-
const lastBar = _chunkXKSO2V5Icjs.barIndexAt.call(void 0, endBeat, meters);
|
|
1698
|
-
const partOf = (beat, bar) => {
|
|
1699
|
-
const barStart = _chunkXKSO2V5Icjs.barPositionToBeat.call(void 0, { bar, beat: 0 }, meters);
|
|
1700
|
-
const barLength = _chunkXKSO2V5Icjs.beatsPerBarAt.call(void 0, barStart, meters);
|
|
1701
|
-
return barLength > 0 ? (beat - barStart) / barLength : 0;
|
|
1702
|
-
};
|
|
1703
|
-
const bars = lastBar - firstBar + partOf(endBeat, lastBar) - partOf(startBeat, firstBar);
|
|
1704
|
-
return Math.round(bars * 100) / 100;
|
|
1189
|
+
return _nullishCoalesce(_optionalChain([_chunkIFZDWIKMcjs.segmentStartingAt.call(void 0, timeline.segments, atBeat), 'optionalAccess', _43 => _43.endBeat]), () => ( atBeat));
|
|
1705
1190
|
}
|
|
1706
1191
|
function keyContextFor(opts, notes, meters, totalBeats) {
|
|
1707
1192
|
if (opts.key !== void 0) {
|
|
1708
1193
|
return opts.key;
|
|
1709
1194
|
}
|
|
1710
|
-
const regions =
|
|
1195
|
+
const regions = _chunkIFZDWIKMcjs.keyTimelineFromNotes.call(void 0, notes, {
|
|
1711
1196
|
meters,
|
|
1712
1197
|
totalBeats,
|
|
1713
1198
|
budget: opts.budget
|
|
1714
1199
|
});
|
|
1715
|
-
const keyAt =
|
|
1716
|
-
return (beat) =>
|
|
1200
|
+
const keyAt = _chunkIFZDWIKMcjs.keyLookup.call(void 0, regions, _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, regions), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0)))));
|
|
1201
|
+
return (beat) => _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat));
|
|
1717
1202
|
}
|
|
1718
1203
|
function restBoundaries(boundaries, notes, meters, restBeats) {
|
|
1719
1204
|
const ordered = [...notes].sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);
|
|
1720
1205
|
let covered = Number.NEGATIVE_INFINITY;
|
|
1721
1206
|
for (const note of ordered) {
|
|
1722
1207
|
const gap = note.startBeat - covered;
|
|
1723
|
-
if (Number.isFinite(covered) && gap >= restBeats -
|
|
1724
|
-
const bar =
|
|
1208
|
+
if (Number.isFinite(covered) && gap >= restBeats - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
1209
|
+
const bar = _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, note.startBeat, meters);
|
|
1725
1210
|
addSignal(
|
|
1726
1211
|
boundaries,
|
|
1727
1212
|
note.startBeat,
|
|
1728
1213
|
"rest",
|
|
1729
|
-
REST_STRENGTH *
|
|
1214
|
+
REST_STRENGTH * _chunkIFZDWIKMcjs.clamp01.call(void 0, bar > 0 ? gap / bar : 1)
|
|
1730
1215
|
);
|
|
1731
1216
|
}
|
|
1732
1217
|
covered = Math.max(covered, note.startBeat + note.durationBeat);
|
|
@@ -1736,13 +1221,13 @@ function repetitionBoundaries(boundaries, notes, meters, spanStart, spanEnd, win
|
|
|
1736
1221
|
if (!(windowBeats > 0)) {
|
|
1737
1222
|
return;
|
|
1738
1223
|
}
|
|
1739
|
-
const slices =
|
|
1740
|
-
|
|
1741
|
-
const onsetsIn = (from, to) => notes.filter((note) => note.startBeat >= from -
|
|
1224
|
+
const slices = _chunkIFZDWIKMcjs.sliceBars.call(void 0, notes, meters, spanStart, spanEnd, budget);
|
|
1225
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, slices.length * notes.length, "form repetition comparisons", budget);
|
|
1226
|
+
const onsetsIn = (from, to) => notes.filter((note) => note.startBeat >= from - _chunkS5BKZZGMcjs.BEAT_EPS && note.startBeat < to - _chunkS5BKZZGMcjs.BEAT_EPS);
|
|
1742
1227
|
let comparisons = 0;
|
|
1743
1228
|
for (const slice of slices) {
|
|
1744
1229
|
const beat = slice.startBeat;
|
|
1745
|
-
if (beat - windowBeats < spanStart -
|
|
1230
|
+
if (beat - windowBeats < spanStart - _chunkS5BKZZGMcjs.BEAT_EPS || beat + windowBeats > spanEnd + _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
1746
1231
|
continue;
|
|
1747
1232
|
}
|
|
1748
1233
|
const before = onsetsIn(beat - windowBeats, beat);
|
|
@@ -1751,8 +1236,8 @@ function repetitionBoundaries(boundaries, notes, meters, spanStart, spanEnd, win
|
|
|
1751
1236
|
continue;
|
|
1752
1237
|
}
|
|
1753
1238
|
comparisons += before.length * after.length;
|
|
1754
|
-
|
|
1755
|
-
const similarity = melodicSimilarity(before, after);
|
|
1239
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, comparisons, "form repetition melody comparisons", budget);
|
|
1240
|
+
const similarity = _chunkIFZDWIKMcjs.melodicSimilarity.call(void 0, before, after);
|
|
1756
1241
|
if (similarity >= REPETITION_THRESHOLD) {
|
|
1757
1242
|
addSignal(boundaries, beat, "repetition", REPETITION_STRENGTH * similarity);
|
|
1758
1243
|
}
|
|
@@ -1781,7 +1266,7 @@ function structuralWeightOf(cadence, confidence, aligned, isLast) {
|
|
|
1781
1266
|
if (aligned) {
|
|
1782
1267
|
weight += HYPERMETRIC_CLOSE_BONUS;
|
|
1783
1268
|
}
|
|
1784
|
-
return
|
|
1269
|
+
return _chunkIFZDWIKMcjs.clamp01.call(void 0, weight);
|
|
1785
1270
|
}
|
|
1786
1271
|
function describeCadence(cadence) {
|
|
1787
1272
|
if (cadence === null || cadence.cadence.type === null) {
|
|
@@ -1792,15 +1277,15 @@ function describeCadence(cadence) {
|
|
|
1792
1277
|
return `a ${graded} cadence at beat ${cadence.atBeat}`;
|
|
1793
1278
|
}
|
|
1794
1279
|
function phrasesFromTimeline(timeline, notes, opts = {}) {
|
|
1795
|
-
|
|
1796
|
-
const meters =
|
|
1797
|
-
|
|
1280
|
+
_chunkIFZDWIKMcjs.assertChordTimeline.call(void 0, timeline);
|
|
1281
|
+
const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "phrase meters");
|
|
1282
|
+
_chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "phrase notes", {
|
|
1798
1283
|
allowNonPositiveDuration: true,
|
|
1799
1284
|
budget: opts.budget
|
|
1800
1285
|
});
|
|
1801
1286
|
if (opts.hypermeter !== void 0) {
|
|
1802
|
-
|
|
1803
|
-
|
|
1287
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, opts.hypermeter.confidence, 0, 1, "phrase hypermeter confidence");
|
|
1288
|
+
_chunkS5BKZZGMcjs.assertPositiveInt.call(void 0, opts.hypermeter.groupBars, "phrase hypermeter groupBars");
|
|
1804
1289
|
}
|
|
1805
1290
|
const sounding = notes.filter((note) => note.durationBeat > 0);
|
|
1806
1291
|
const segments = timeline.segments;
|
|
@@ -1808,39 +1293,39 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
|
|
|
1808
1293
|
const lastSegment = segments[segments.length - 1];
|
|
1809
1294
|
const firstOnset = sounding.reduce(
|
|
1810
1295
|
(first, n) => Math.min(first, n.startBeat),
|
|
1811
|
-
_nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess',
|
|
1296
|
+
_nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess', _44 => _44.startBeat]), () => ( 0))
|
|
1812
1297
|
);
|
|
1813
|
-
const spanStart =
|
|
1298
|
+
const spanStart = _chunkIFZDWIKMcjs.barGridStart.call(void 0, firstOnset, meters);
|
|
1814
1299
|
const notesEnd = sounding.reduce(
|
|
1815
1300
|
(end, n) => Math.max(end, n.startBeat + n.durationBeat),
|
|
1816
|
-
_nullishCoalesce(_optionalChain([lastSegment, 'optionalAccess',
|
|
1301
|
+
_nullishCoalesce(_optionalChain([lastSegment, 'optionalAccess', _45 => _45.endBeat]), () => ( 0))
|
|
1817
1302
|
);
|
|
1818
1303
|
const spanEnd = Math.max(spanStart, _nullishCoalesce(opts.totalBeats, () => ( notesEnd)));
|
|
1819
|
-
|
|
1820
|
-
if (spanEnd <= spanStart +
|
|
1304
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, spanEnd, 0, Number.MAX_SAFE_INTEGER, "phrase totalBeats");
|
|
1305
|
+
if (spanEnd <= spanStart + _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
1821
1306
|
return [];
|
|
1822
1307
|
}
|
|
1823
|
-
const barBeats =
|
|
1308
|
+
const barBeats = _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, Math.max(0, spanStart), meters);
|
|
1824
1309
|
const restBeats = _nullishCoalesce(opts.restBeats, () => ( barBeats / 2));
|
|
1825
|
-
|
|
1310
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, restBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase restBeats");
|
|
1826
1311
|
const longNoteBeats = _nullishCoalesce(opts.longNoteBeats, () => ( barBeats));
|
|
1827
|
-
|
|
1312
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, longNoteBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase longNoteBeats");
|
|
1828
1313
|
const minPhraseBeats = _nullishCoalesce(opts.minPhraseBeats, () => ( barBeats));
|
|
1829
|
-
|
|
1314
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, minPhraseBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase minPhraseBeats");
|
|
1830
1315
|
const key = keyContextFor(opts, sounding, meters, spanEnd);
|
|
1831
|
-
const cadences =
|
|
1316
|
+
const cadences = _chunkIFZDWIKMcjs.detectCadences.call(void 0, timeline, key);
|
|
1832
1317
|
const grouping = _nullishCoalesce(opts.hypermeter, () => ( hypermeter(sounding, meters, {
|
|
1833
1318
|
totalBeats: spanEnd,
|
|
1834
1319
|
cadenceBeats: cadences.map((hit) => hit.atBeat),
|
|
1835
1320
|
budget: opts.budget
|
|
1836
1321
|
})));
|
|
1837
|
-
const firstBar =
|
|
1322
|
+
const firstBar = _chunkS5BKZZGMcjs.barIndexAt.call(void 0, Math.max(0, spanStart), meters);
|
|
1838
1323
|
const hyperBeats = Math.max(
|
|
1839
1324
|
barBeats,
|
|
1840
|
-
|
|
1325
|
+
_chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar: firstBar + grouping.groupBars, beat: 0 }, meters) - _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar: firstBar, beat: 0 }, meters)
|
|
1841
1326
|
);
|
|
1842
1327
|
const expected = _nullishCoalesce(opts.expectedPhraseBeats, () => ( hyperBeats));
|
|
1843
|
-
|
|
1328
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, expected, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase expectedPhraseBeats");
|
|
1844
1329
|
const boundaries = /* @__PURE__ */ new Map();
|
|
1845
1330
|
for (const hit of cadences) {
|
|
1846
1331
|
const type = hit.cadence.type;
|
|
@@ -1850,14 +1335,14 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
|
|
|
1850
1335
|
const strength = CADENCE_BOUNDARY_STRENGTH[type];
|
|
1851
1336
|
const chordEnd = arrivalEnd(timeline, hit.atBeat);
|
|
1852
1337
|
addSignal(boundaries, chordEnd, "cadence", strength, hit);
|
|
1853
|
-
const barEnd =
|
|
1854
|
-
if (chordEnd > barEnd +
|
|
1338
|
+
const barEnd = _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar: _chunkS5BKZZGMcjs.barIndexAt.call(void 0, hit.atBeat, meters) + 1, beat: 0 }, meters);
|
|
1339
|
+
if (chordEnd > barEnd + _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
1855
1340
|
addSignal(boundaries, barEnd, "cadence", strength, hit);
|
|
1856
1341
|
}
|
|
1857
1342
|
}
|
|
1858
1343
|
restBoundaries(boundaries, sounding, meters, restBeats);
|
|
1859
1344
|
for (const note of sounding) {
|
|
1860
|
-
if (note.durationBeat >= longNoteBeats -
|
|
1345
|
+
if (note.durationBeat >= longNoteBeats - _chunkS5BKZZGMcjs.BEAT_EPS) {
|
|
1861
1346
|
addSignal(boundaries, note.startBeat + note.durationBeat, "longNote", LONG_NOTE_STRENGTH);
|
|
1862
1347
|
}
|
|
1863
1348
|
}
|
|
@@ -1867,9 +1352,9 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
|
|
|
1867
1352
|
}
|
|
1868
1353
|
addSignal(boundaries, spanEnd, "pieceEnd", PIECE_END_STRENGTH);
|
|
1869
1354
|
const candidates = [...boundaries.values()].filter(
|
|
1870
|
-
(boundary) => boundary.beat > spanStart +
|
|
1355
|
+
(boundary) => boundary.beat > spanStart + _chunkS5BKZZGMcjs.BEAT_EPS && boundary.beat < spanEnd - _chunkS5BKZZGMcjs.BEAT_EPS
|
|
1871
1356
|
).filter((boundary) => readBoundary(boundary).strength > 0).sort((a, b) => a.beat - b.beat);
|
|
1872
|
-
const endBoundary = _nullishCoalesce(boundaries.get(Math.round(spanEnd /
|
|
1357
|
+
const endBoundary = _nullishCoalesce(boundaries.get(Math.round(spanEnd / _chunkS5BKZZGMcjs.BEAT_EPS)), () => ( {
|
|
1873
1358
|
beat: spanEnd,
|
|
1874
1359
|
strengths: /* @__PURE__ */ new Map(),
|
|
1875
1360
|
cadence: null
|
|
@@ -1879,7 +1364,7 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
|
|
|
1879
1364
|
...candidates,
|
|
1880
1365
|
endBoundary
|
|
1881
1366
|
];
|
|
1882
|
-
|
|
1367
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, stops.length * stops.length, "form phrase boundary pairs", opts.budget);
|
|
1883
1368
|
const readings = stops.map(readBoundary);
|
|
1884
1369
|
const path = choosePhrasePath(
|
|
1885
1370
|
stops.map((stop) => stop.beat),
|
|
@@ -1888,31 +1373,31 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
|
|
|
1888
1373
|
expected
|
|
1889
1374
|
);
|
|
1890
1375
|
const downbeatKeys = new Set(
|
|
1891
|
-
grouping.downbeats.map((downbeat) => Math.round(downbeat /
|
|
1376
|
+
grouping.downbeats.map((downbeat) => Math.round(downbeat / _chunkS5BKZZGMcjs.BEAT_EPS))
|
|
1892
1377
|
);
|
|
1893
1378
|
const phrases = [];
|
|
1894
1379
|
let from = 0;
|
|
1895
1380
|
for (let n = 0; n < path.length; n += 1) {
|
|
1896
1381
|
const to = _nullishCoalesce(path[n], () => ( stops.length - 1));
|
|
1897
|
-
const startBeat = _nullishCoalesce(_optionalChain([stops, 'access',
|
|
1382
|
+
const startBeat = _nullishCoalesce(_optionalChain([stops, 'access', _46 => _46[from], 'optionalAccess', _47 => _47.beat]), () => ( spanStart));
|
|
1898
1383
|
const stop = stops[to];
|
|
1899
|
-
const endBeat = _nullishCoalesce(_optionalChain([stop, 'optionalAccess',
|
|
1384
|
+
const endBeat = _nullishCoalesce(_optionalChain([stop, 'optionalAccess', _48 => _48.beat]), () => ( spanEnd));
|
|
1900
1385
|
const reading = _nullishCoalesce(readings[to], () => ( { strength: 0, signals: [] }));
|
|
1901
1386
|
const length = endBeat - startBeat;
|
|
1902
|
-
const confidence =
|
|
1387
|
+
const confidence = _chunkIFZDWIKMcjs.clamp01.call(void 0,
|
|
1903
1388
|
CLOSING_SHARE * reading.strength + (1 - CLOSING_SHARE) * Math.max(0, lengthFit(length, expected))
|
|
1904
1389
|
);
|
|
1905
1390
|
const isLast = n === path.length - 1;
|
|
1906
|
-
const closing = _nullishCoalesce(_optionalChain([stop, 'optionalAccess',
|
|
1907
|
-
const cadence = closing !== null && closing.atBeat >= startBeat -
|
|
1391
|
+
const closing = _nullishCoalesce(_optionalChain([stop, 'optionalAccess', _49 => _49.cadence]), () => ( null));
|
|
1392
|
+
const cadence = closing !== null && closing.atBeat >= startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && closing.atBeat < endBeat - _chunkS5BKZZGMcjs.BEAT_EPS ? closing : null;
|
|
1908
1393
|
const structuralWeight = structuralWeightOf(
|
|
1909
1394
|
cadence,
|
|
1910
1395
|
confidence,
|
|
1911
|
-
downbeatKeys.has(Math.round(endBeat /
|
|
1396
|
+
downbeatKeys.has(Math.round(endBeat / _chunkS5BKZZGMcjs.BEAT_EPS)),
|
|
1912
1397
|
isLast
|
|
1913
1398
|
);
|
|
1914
1399
|
const signals = cadence === null ? reading.signals.filter((signal) => signal !== "cadence") : reading.signals;
|
|
1915
|
-
const bars = barSpanOf(startBeat, endBeat, meters);
|
|
1400
|
+
const bars = Math.round(_chunkIFZDWIKMcjs.barSpanOf.call(void 0, startBeat, endBeat, meters) * 100) / 100;
|
|
1916
1401
|
phrases.push({
|
|
1917
1402
|
startBeat,
|
|
1918
1403
|
endBeat,
|
|
@@ -1927,10 +1412,10 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
|
|
|
1927
1412
|
return phrases;
|
|
1928
1413
|
}
|
|
1929
1414
|
function structuralCadences(phrases) {
|
|
1930
|
-
|
|
1415
|
+
_chunkS5BKZZGMcjs.assertArray.call(void 0, phrases, "phrases");
|
|
1931
1416
|
const ranked = [];
|
|
1932
1417
|
for (let index = 0; index < phrases.length; index += 1) {
|
|
1933
|
-
const phrase =
|
|
1418
|
+
const phrase = _chunkS5BKZZGMcjs.assertRecord.call(void 0, phrases[index], `phrases[${index}]`);
|
|
1934
1419
|
if (phrase.cadence === null || phrase.cadence === void 0 || phrase.cadence.cadence.type === null) {
|
|
1935
1420
|
continue;
|
|
1936
1421
|
}
|
|
@@ -1964,16 +1449,16 @@ function isSilent(unit) {
|
|
|
1964
1449
|
return unit.notes.length === 0;
|
|
1965
1450
|
}
|
|
1966
1451
|
function unitSimilarity(a, b) {
|
|
1967
|
-
const harmony =
|
|
1452
|
+
const harmony = _chunkIFZDWIKMcjs.weightSimilarity.call(void 0, a.weights, b.weights);
|
|
1968
1453
|
if (a.notes.length < 2 || b.notes.length < 2) {
|
|
1969
1454
|
return a.notes.length === b.notes.length ? harmony : 0;
|
|
1970
1455
|
}
|
|
1971
|
-
const melody = melodicSimilarity(a.notes, b.notes);
|
|
1972
|
-
return
|
|
1456
|
+
const melody = _chunkIFZDWIKMcjs.melodicSimilarity.call(void 0, a.notes, b.notes);
|
|
1457
|
+
return _chunkIFZDWIKMcjs.clamp01.call(void 0, MELODY_SHARE * melody + HARMONY_SHARE * harmony);
|
|
1973
1458
|
}
|
|
1974
1459
|
function sectionsFromNotes(notes, opts = {}) {
|
|
1975
|
-
const meters =
|
|
1976
|
-
|
|
1460
|
+
const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "section meters");
|
|
1461
|
+
_chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "section notes", {
|
|
1977
1462
|
allowNonPositiveDuration: true,
|
|
1978
1463
|
budget: opts.budget
|
|
1979
1464
|
});
|
|
@@ -1981,33 +1466,33 @@ function sectionsFromNotes(notes, opts = {}) {
|
|
|
1981
1466
|
if (sounding.length === 0) {
|
|
1982
1467
|
return [];
|
|
1983
1468
|
}
|
|
1984
|
-
const firstOnset =
|
|
1469
|
+
const firstOnset = _chunkIFZDWIKMcjs.barGridStart.call(void 0, _chunkIFZDWIKMcjs.firstSoundingBeat.call(void 0, sounding), meters);
|
|
1985
1470
|
const lastEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
|
|
1986
1471
|
const spanEnd = Math.max(firstOnset, _nullishCoalesce(opts.totalBeats, () => ( lastEnd)));
|
|
1987
|
-
|
|
1472
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, spanEnd, 0, Number.MAX_SAFE_INTEGER, "section totalBeats");
|
|
1988
1473
|
const threshold = _nullishCoalesce(opts.threshold, () => ( DEFAULT_THRESHOLD));
|
|
1989
|
-
|
|
1474
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, threshold, 0, 1, "section threshold");
|
|
1990
1475
|
const unitBars = _nullishCoalesce(opts.unitBars, () => ( hypermeter(sounding, meters, { totalBeats: spanEnd, budget: opts.budget }).groupBars));
|
|
1991
|
-
|
|
1992
|
-
const firstBar =
|
|
1993
|
-
const lastBar =
|
|
1476
|
+
_chunkS5BKZZGMcjs.assertPositiveInt.call(void 0, unitBars, "section unitBars");
|
|
1477
|
+
const firstBar = _chunkS5BKZZGMcjs.barIndexAt.call(void 0, firstOnset, meters);
|
|
1478
|
+
const lastBar = _chunkIFZDWIKMcjs.lastBarOf.call(void 0, meters, firstOnset, spanEnd);
|
|
1994
1479
|
const startBar = Math.max(0, firstBar);
|
|
1995
1480
|
const unitCount = Math.max(1, Math.ceil((lastBar - startBar + 1) / unitBars));
|
|
1996
|
-
|
|
1997
|
-
|
|
1481
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, unitCount * unitCount, "form section comparisons", opts.budget);
|
|
1482
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, unitCount * sounding.length, "form section unit notes", opts.budget);
|
|
1998
1483
|
const units = [];
|
|
1999
1484
|
for (let index = 0; index < unitCount; index += 1) {
|
|
2000
1485
|
const barStart = startBar + index * unitBars;
|
|
2001
|
-
const startBeat = index === 0 ? firstOnset :
|
|
2002
|
-
const endBeat = index === unitCount - 1 ? spanEnd :
|
|
1486
|
+
const startBeat = index === 0 ? firstOnset : _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar: barStart, beat: 0 }, meters);
|
|
1487
|
+
const endBeat = index === unitCount - 1 ? spanEnd : _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar: barStart + unitBars, beat: 0 }, meters);
|
|
2003
1488
|
const unitNotes = sounding.filter(
|
|
2004
|
-
(note) => note.startBeat >= startBeat -
|
|
1489
|
+
(note) => note.startBeat >= startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && note.startBeat < endBeat - _chunkS5BKZZGMcjs.BEAT_EPS
|
|
2005
1490
|
).sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);
|
|
2006
1491
|
units.push({
|
|
2007
1492
|
startBeat,
|
|
2008
1493
|
endBeat,
|
|
2009
1494
|
notes: unitNotes,
|
|
2010
|
-
weights:
|
|
1495
|
+
weights: _chunkIFZDWIKMcjs.windowWeights.call(void 0, unitNotes, startBeat, endBeat, meters).weights
|
|
2011
1496
|
});
|
|
2012
1497
|
}
|
|
2013
1498
|
let comparisons = 0;
|
|
@@ -2016,7 +1501,7 @@ function sectionsFromNotes(notes, opts = {}) {
|
|
|
2016
1501
|
comparisons += unit.notes.length * earlierNotes;
|
|
2017
1502
|
earlierNotes += unit.notes.length;
|
|
2018
1503
|
}
|
|
2019
|
-
|
|
1504
|
+
_chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, comparisons, "form section melody comparisons", opts.budget);
|
|
2020
1505
|
const labels = [];
|
|
2021
1506
|
const scores = [];
|
|
2022
1507
|
let distinct = 0;
|
|
@@ -2071,8 +1556,8 @@ function sectionsFromNotes(notes, opts = {}) {
|
|
|
2071
1556
|
while (end < units.length && labels[end] === null) {
|
|
2072
1557
|
end += 1;
|
|
2073
1558
|
}
|
|
2074
|
-
const startBeat = _nullishCoalesce(_optionalChain([units, 'access',
|
|
2075
|
-
const endBeat = _nullishCoalesce(_optionalChain([units, 'access',
|
|
1559
|
+
const startBeat = _nullishCoalesce(_optionalChain([units, 'access', _50 => _50[index], 'optionalAccess', _51 => _51.startBeat]), () => ( 0));
|
|
1560
|
+
const endBeat = _nullishCoalesce(_optionalChain([units, 'access', _52 => _52[end - 1], 'optionalAccess', _53 => _53.endBeat]), () => ( spanEnd));
|
|
2076
1561
|
const sectionFirstBar = startBar + index * unitBars;
|
|
2077
1562
|
const sectionLastBar = Math.max(
|
|
2078
1563
|
sectionFirstBar,
|
|
@@ -2083,7 +1568,7 @@ function sectionsFromNotes(notes, opts = {}) {
|
|
|
2083
1568
|
if (first === void 0) {
|
|
2084
1569
|
firstByLabel.set(label, sectionIndex);
|
|
2085
1570
|
}
|
|
2086
|
-
const similarity = first === void 0 ? 1 :
|
|
1571
|
+
const similarity = first === void 0 ? 1 : _chunkIFZDWIKMcjs.clamp01.call(void 0, scoreSum / Math.max(1, sounded));
|
|
2087
1572
|
sections.push({
|
|
2088
1573
|
label,
|
|
2089
1574
|
startBeat,
|
|
@@ -2091,163 +1576,284 @@ function sectionsFromNotes(notes, opts = {}) {
|
|
|
2091
1576
|
bars: sectionLastBar - sectionFirstBar + 1,
|
|
2092
1577
|
firstOccurrence: _nullishCoalesce(first, () => ( sectionIndex)),
|
|
2093
1578
|
similarity,
|
|
2094
|
-
rationale: first === void 0 ? `Section ${label}: material first heard at beat ${startBeat}` : `Section ${label}: ${Math.round(similarity * 100)}% alike the ${label} at beat ${_nullishCoalesce(_optionalChain([sections, 'access',
|
|
1579
|
+
rationale: first === void 0 ? `Section ${label}: material first heard at beat ${startBeat}` : `Section ${label}: ${Math.round(similarity * 100)}% alike the ${label} at beat ${_nullishCoalesce(_optionalChain([sections, 'access', _54 => _54[first], 'optionalAccess', _55 => _55.startBeat]), () => ( 0))}`
|
|
2095
1580
|
});
|
|
2096
1581
|
index = end;
|
|
2097
1582
|
}
|
|
2098
1583
|
return sections;
|
|
2099
1584
|
}
|
|
2100
1585
|
|
|
2101
|
-
// src/analyze/
|
|
2102
|
-
var
|
|
2103
|
-
function
|
|
2104
|
-
|
|
1586
|
+
// src/analyze/reference/compare.ts
|
|
1587
|
+
var RATIONALE_FIELDS = 2;
|
|
1588
|
+
function rationaleOf(comparison) {
|
|
1589
|
+
const measured = [];
|
|
1590
|
+
for (const group of [comparison.form, comparison.harmony, comparison.melody, comparison.rhythm]) {
|
|
1591
|
+
for (const [name, value] of Object.entries(group)) {
|
|
1592
|
+
if (value !== null) {
|
|
1593
|
+
measured.push({ name, value, order: measured.length });
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
if (measured.length === 0) {
|
|
1598
|
+
return "Nothing to compare: neither profile has material for any field";
|
|
1599
|
+
}
|
|
1600
|
+
const describe = (entries) => entries.map((entry) => `${entry.name} ${Math.round(entry.value * 100)}%`).join(", ");
|
|
1601
|
+
const highest = [...measured].sort((x, y) => y.value - x.value || x.order - y.order);
|
|
1602
|
+
const lowest = [...measured].sort((x, y) => x.value - y.value || x.order - y.order);
|
|
1603
|
+
return `Most alike: ${describe(highest.slice(0, RATIONALE_FIELDS))}; least alike: ${describe(lowest.slice(0, RATIONALE_FIELDS))}`;
|
|
1604
|
+
}
|
|
1605
|
+
function compareReferences(a, b, opts) {
|
|
1606
|
+
_chunkIFZDWIKMcjs.assertReferenceProfile.call(void 0, a, "reference profile a");
|
|
1607
|
+
_chunkIFZDWIKMcjs.assertReferenceProfile.call(void 0, b, "reference profile b");
|
|
1608
|
+
const { budget } = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "compare options");
|
|
1609
|
+
const structural = (profile) => profile.harmony.chords.filter((chord) => chord.level === "structural");
|
|
1610
|
+
const cadences = (profile) => profile.form.phrases.map((phrase) => _nullishCoalesce(_optionalChain([phrase, 'access', _56 => _56.cadence, 'optionalAccess', _57 => _57.type]), () => ( null)));
|
|
1611
|
+
const phraseMelodies = (profile) => profile.form.phrases.map((phrase) => phrase.melody);
|
|
1612
|
+
const comparison = {
|
|
1613
|
+
form: {
|
|
1614
|
+
sectionSequenceSimilarity: _chunkIFZDWIKMcjs.sectionSequenceSimilarity.call(void 0,
|
|
1615
|
+
a.form.sections.map((section) => section.label),
|
|
1616
|
+
b.form.sections.map((section) => section.label)
|
|
1617
|
+
),
|
|
1618
|
+
phraseLengthSimilarity: _chunkIFZDWIKMcjs.phraseLengthSimilarity.call(void 0,
|
|
1619
|
+
a.form.phrases.map((phrase) => phrase.bars),
|
|
1620
|
+
b.form.phrases.map((phrase) => phrase.bars)
|
|
1621
|
+
)
|
|
1622
|
+
},
|
|
1623
|
+
harmony: {
|
|
1624
|
+
progressionSimilarity: _chunkIFZDWIKMcjs.progressionSimilarity.call(void 0, structural(a), structural(b), budget),
|
|
1625
|
+
functionSimilarity: _chunkIFZDWIKMcjs.functionSimilarity.call(void 0, a.harmony.chords, b.harmony.chords),
|
|
1626
|
+
cadenceSimilarity: _chunkIFZDWIKMcjs.cadenceSimilarity.call(void 0, cadences(a), cadences(b)),
|
|
1627
|
+
keyPlanSimilarity: _chunkIFZDWIKMcjs.keyPlanSimilarity.call(void 0,
|
|
1628
|
+
a.harmony.keys.map((region) => region.key),
|
|
1629
|
+
b.harmony.keys.map((region) => region.key)
|
|
1630
|
+
),
|
|
1631
|
+
harmonicRhythmOnsetSimilarity: _chunkIFZDWIKMcjs.onsetSimilarity.call(void 0, a.harmony.rhythm, b.harmony.rhythm),
|
|
1632
|
+
harmonicRhythmDensitySimilarity: _chunkIFZDWIKMcjs.densitySimilarity.call(void 0, a.harmony.rhythm, b.harmony.rhythm)
|
|
1633
|
+
},
|
|
1634
|
+
melody: {
|
|
1635
|
+
surfaceSimilarity: _chunkIFZDWIKMcjs.surfaceSimilarity.call(void 0, a.melody.motifs, b.melody.motifs),
|
|
1636
|
+
motifStructureSimilarity: _chunkIFZDWIKMcjs.motifStructureSimilarity.call(void 0,
|
|
1637
|
+
{ graph: a.melody.graph, spanBeats: a.span.endBeat - a.span.startBeat },
|
|
1638
|
+
{ graph: b.melody.graph, spanBeats: b.span.endBeat - b.span.startBeat }
|
|
1639
|
+
),
|
|
1640
|
+
contourSimilarity: _chunkIFZDWIKMcjs.contourSimilarity.call(void 0, phraseMelodies(a), phraseMelodies(b)),
|
|
1641
|
+
registerSimilarity: _chunkIFZDWIKMcjs.registerSimilarity.call(void 0,
|
|
1642
|
+
{ phrases: phraseMelodies(a), mean: _nullishCoalesce(_optionalChain([a, 'access', _58 => _58.melody, 'access', _59 => _59.register, 'optionalAccess', _60 => _60.mean]), () => ( null)) },
|
|
1643
|
+
{ phrases: phraseMelodies(b), mean: _nullishCoalesce(_optionalChain([b, 'access', _61 => _61.melody, 'access', _62 => _62.register, 'optionalAccess', _63 => _63.mean]), () => ( null)) }
|
|
1644
|
+
)
|
|
1645
|
+
},
|
|
1646
|
+
rhythm: {
|
|
1647
|
+
onsetSimilarity: _chunkIFZDWIKMcjs.onsetSimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm),
|
|
1648
|
+
densitySimilarity: _chunkIFZDWIKMcjs.densitySimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm),
|
|
1649
|
+
durationSimilarity: _chunkIFZDWIKMcjs.durationSimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm),
|
|
1650
|
+
syncopationSimilarity: _chunkIFZDWIKMcjs.syncopationSimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm)
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
return { ...comparison, rationale: rationaleOf(comparison) };
|
|
2105
1654
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
1655
|
+
|
|
1656
|
+
// src/analyze/reference/profile.ts
|
|
1657
|
+
function spanOf(sounding, timeline, meters, totalBeats) {
|
|
1658
|
+
const segments = timeline.segments;
|
|
1659
|
+
const firstSegment = segments[0];
|
|
1660
|
+
const lastSegment = segments[segments.length - 1];
|
|
1661
|
+
const firstOnset = _chunkIFZDWIKMcjs.firstSoundingBeat.call(void 0, sounding, _nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess', _64 => _64.startBeat]), () => ( 0)));
|
|
1662
|
+
const startBeat = _chunkIFZDWIKMcjs.barGridStart.call(void 0,
|
|
1663
|
+
Math.min(firstOnset, _nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess', _65 => _65.startBeat]), () => ( firstOnset))),
|
|
1664
|
+
meters
|
|
1665
|
+
);
|
|
1666
|
+
const notesEnd = sounding.reduce(
|
|
1667
|
+
(end, note) => Math.max(end, note.startBeat + note.durationBeat),
|
|
1668
|
+
_nullishCoalesce(_optionalChain([lastSegment, 'optionalAccess', _66 => _66.endBeat]), () => ( 0))
|
|
1669
|
+
);
|
|
1670
|
+
return { startBeat, endBeat: Math.max(startBeat, _nullishCoalesce(totalBeats, () => ( notesEnd))) };
|
|
1671
|
+
}
|
|
1672
|
+
function sameKey(a, b) {
|
|
1673
|
+
return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, a.scale.rootPc) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, b.scale.rootPc) && a.scale.modeMask12 === b.scale.modeMask12;
|
|
2109
1674
|
}
|
|
2110
|
-
function
|
|
2111
|
-
|
|
1675
|
+
function keyIndexAt(regions, beat, prevailing) {
|
|
1676
|
+
const holding = regions.findIndex((region) => region.startBeat <= beat && beat < region.endBeat);
|
|
1677
|
+
if (holding >= 0) {
|
|
1678
|
+
return holding;
|
|
1679
|
+
}
|
|
1680
|
+
return Math.max(
|
|
1681
|
+
0,
|
|
1682
|
+
regions.findIndex((region) => sameKey(region.key, prevailing))
|
|
1683
|
+
);
|
|
2112
1684
|
}
|
|
2113
|
-
function
|
|
2114
|
-
const
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
1685
|
+
function sectionAt(sections, beat) {
|
|
1686
|
+
const index = sections.findIndex(
|
|
1687
|
+
(section) => beat >= section.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && beat < section.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS
|
|
1688
|
+
);
|
|
1689
|
+
return index < 0 ? null : index;
|
|
1690
|
+
}
|
|
1691
|
+
function readingsFor(notes, opts = {}) {
|
|
1692
|
+
const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "reference options");
|
|
1693
|
+
const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, asked, "reference meters");
|
|
1694
|
+
const budget = asked.budget;
|
|
1695
|
+
if (asked.timeline === void 0) {
|
|
1696
|
+
return _chunkIFZDWIKMcjs.chordTimelineFromNotes.call(void 0, notes, {
|
|
1697
|
+
meters,
|
|
1698
|
+
key: asked.key,
|
|
1699
|
+
totalBeats: asked.totalBeats,
|
|
1700
|
+
budget
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
const timeline = _chunkIFZDWIKMcjs.assertChordTimeline.call(void 0, asked.timeline, "reference timeline");
|
|
1704
|
+
let keys;
|
|
1705
|
+
if (asked.key === void 0) {
|
|
1706
|
+
keys = _chunkIFZDWIKMcjs.keyTimelineFromNotes.call(void 0, notes, { meters, totalBeats: asked.totalBeats, budget });
|
|
1707
|
+
} else {
|
|
1708
|
+
const sounding = _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "reference notes", {
|
|
1709
|
+
allowNonPositiveDuration: true,
|
|
1710
|
+
budget
|
|
1711
|
+
}).filter((note) => note.durationBeat > 0);
|
|
1712
|
+
const span = spanOf(sounding, timeline, meters, asked.totalBeats);
|
|
1713
|
+
keys = [
|
|
1714
|
+
{
|
|
1715
|
+
startBeat: span.startBeat,
|
|
1716
|
+
endBeat: span.endBeat,
|
|
1717
|
+
key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, asked.key),
|
|
1718
|
+
confidence: 1
|
|
1719
|
+
}
|
|
1720
|
+
];
|
|
1721
|
+
}
|
|
1722
|
+
return { timeline, keys, prevailingKey: _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, keys), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0)))) };
|
|
1723
|
+
}
|
|
1724
|
+
function referenceFromReadings(notes, readings, opts = {}) {
|
|
1725
|
+
const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "reference options");
|
|
1726
|
+
const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, asked, "reference meters");
|
|
1727
|
+
const budget = asked.budget;
|
|
1728
|
+
const sounding = _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "reference notes", {
|
|
1729
|
+
allowNonPositiveDuration: true,
|
|
1730
|
+
budget
|
|
1731
|
+
}).filter((note) => note.durationBeat > 0);
|
|
1732
|
+
if (asked.totalBeats !== void 0) {
|
|
1733
|
+
_chunkS5BKZZGMcjs.assertRange.call(void 0, asked.totalBeats, 0, Number.MAX_SAFE_INTEGER, "reference totalBeats");
|
|
1734
|
+
}
|
|
1735
|
+
const line = _chunkIFZDWIKMcjs.orderedNotes.call(void 0, _nullishCoalesce(asked.melody, () => ( notes)), "reference melody", budget);
|
|
1736
|
+
const { timeline, prevailingKey } = readings;
|
|
1737
|
+
const span = spanOf(sounding, timeline, meters, asked.totalBeats);
|
|
1738
|
+
const spanEnd = span.endBeat;
|
|
1739
|
+
const keyAt = _chunkIFZDWIKMcjs.keyLookup.call(void 0, readings.keys, prevailingKey);
|
|
1740
|
+
const scaleAt = (beat) => _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat));
|
|
1741
|
+
const cadenceBeats = _chunkIFZDWIKMcjs.detectCadences.call(void 0, timeline, scaleAt).map((hit) => hit.atBeat);
|
|
1742
|
+
const grouping = hypermeter(sounding, meters, { cadenceBeats, totalBeats: spanEnd, budget });
|
|
1743
|
+
const phrases = phrasesFromTimeline(timeline, notes, {
|
|
1744
|
+
meters,
|
|
1745
|
+
key: scaleAt,
|
|
1746
|
+
hypermeter: grouping,
|
|
1747
|
+
totalBeats: spanEnd,
|
|
1748
|
+
budget
|
|
2118
1749
|
});
|
|
2119
|
-
const
|
|
2120
|
-
for (
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
continue;
|
|
2129
|
-
}
|
|
2130
|
-
const skipped = _nullishCoalesce(rankOf[index], () => ( 0));
|
|
2131
|
-
const at = (position) => _nullishCoalesce(durations[_nullishCoalesce(order[position < skipped ? position : position + 1], () => ( 0))], () => ( 0));
|
|
2132
|
-
const middle = remaining >> 1;
|
|
2133
|
-
floors.push(remaining % 2 === 1 ? at(middle) : (at(middle - 1) + at(middle)) / 2);
|
|
2134
|
-
}
|
|
2135
|
-
return floors;
|
|
2136
|
-
}
|
|
2137
|
-
function isTonicChord(chord, key) {
|
|
2138
|
-
return _chunkXKSO2V5Icjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc) === 0 && !_chunkQ6R3QAZDcjs.isAppliedDominant.call(void 0, chord, key);
|
|
2139
|
-
}
|
|
2140
|
-
function functionalReason(segment, before, after, keyAt) {
|
|
2141
|
-
const key = keyAt(segment.startBeat);
|
|
2142
|
-
if (after !== void 0 && _chunkQ6R3QAZDcjs.isCadentialSixFour.call(void 0, segment.chord, after.chord, key)) {
|
|
2143
|
-
return { kind: "cadentialSixFour" };
|
|
2144
|
-
}
|
|
2145
|
-
if (isTonicChord(segment.chord, key)) {
|
|
2146
|
-
return { kind: "tonic" };
|
|
2147
|
-
}
|
|
2148
|
-
if (_chunkYPB3A3EYcjs.isDominantChordOf.call(void 0, segment.chord, key)) {
|
|
2149
|
-
return { kind: "dominant" };
|
|
2150
|
-
}
|
|
2151
|
-
if (before !== void 0) {
|
|
2152
|
-
const arrival = _chunkQ6R3QAZDcjs.detectCadence.call(void 0, before.chord, segment.chord, key).type;
|
|
2153
|
-
if (arrival !== null) {
|
|
2154
|
-
return { kind: "cadenceArrival", cadence: arrival };
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
if (after !== void 0) {
|
|
2158
|
-
const agent = _chunkQ6R3QAZDcjs.detectCadence.call(void 0, segment.chord, after.chord, keyAt(after.startBeat)).type;
|
|
2159
|
-
if (agent !== null && TONIC_ARRIVING.has(agent)) {
|
|
2160
|
-
return { kind: "cadenceAgent", cadence: agent };
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
return null;
|
|
2164
|
-
}
|
|
2165
|
-
function figureReason(segment, before, after) {
|
|
2166
|
-
if (before === void 0 || after === void 0) {
|
|
2167
|
-
return null;
|
|
2168
|
-
}
|
|
2169
|
-
const incoming = signedStep(before.chord.rootPc, segment.chord.rootPc);
|
|
2170
|
-
const outgoing = signedStep(segment.chord.rootPc, after.chord.rootPc);
|
|
2171
|
-
if (!isStep(incoming) || !isStep(outgoing)) {
|
|
2172
|
-
return null;
|
|
2173
|
-
}
|
|
2174
|
-
if (incoming > 0 === outgoing > 0) {
|
|
2175
|
-
return { kind: "passing" };
|
|
2176
|
-
}
|
|
2177
|
-
return _chunkXKSO2V5Icjs.pitchClassOf.call(void 0, before.chord.rootPc) === _chunkXKSO2V5Icjs.pitchClassOf.call(void 0, after.chord.rootPc) ? { kind: "neighbor" } : null;
|
|
2178
|
-
}
|
|
2179
|
-
function describe(reason) {
|
|
2180
|
-
switch (reason.kind) {
|
|
2181
|
-
case "tonic":
|
|
2182
|
-
return "Structural: the tonic of the key, which the progression is heard against";
|
|
2183
|
-
case "dominant":
|
|
2184
|
-
return "Structural: the dominant of the key, the chord its tonic is approached from";
|
|
2185
|
-
case "cadentialSixFour":
|
|
2186
|
-
return "Structural: a tonic six-four on the bass of the dominant that follows, which prolongs that dominant rather than framing the tonic";
|
|
2187
|
-
case "cadenceArrival":
|
|
2188
|
-
return `Structural: the arrival of a ${reason.cadence} cadence`;
|
|
2189
|
-
case "cadenceAgent":
|
|
2190
|
-
return `Structural: the chord that drives a ${reason.cadence} cadence`;
|
|
2191
|
-
case "salient":
|
|
2192
|
-
return "Structural: it holds the harmony longer than the chords around it";
|
|
2193
|
-
case "first":
|
|
2194
|
-
return "Structural: the first chord of the progression frames it";
|
|
2195
|
-
case "last":
|
|
2196
|
-
return "Structural: the last chord of the progression frames it";
|
|
2197
|
-
case "unfigured":
|
|
2198
|
-
return "Structural: its root neither steps through to the next chord nor returns to the last, so it embellishes nothing";
|
|
2199
|
-
case "passing":
|
|
2200
|
-
return "Passing: its root steps in from the previous chord and on to the next in the same direction";
|
|
2201
|
-
case "neighbor":
|
|
2202
|
-
return "Neighbor: its root steps away from the surrounding harmony and returns to it";
|
|
2203
|
-
}
|
|
2204
|
-
}
|
|
2205
|
-
var BASIS_READINGS = {
|
|
2206
|
-
function: ({ segment, before, after, keyAt }) => functionalReason(segment, before, after, keyAt),
|
|
2207
|
-
duration: ({ segment, salienceFloor }) => segment.endBeat - segment.startBeat > salienceFloor ? { kind: "salient" } : null
|
|
2208
|
-
};
|
|
2209
|
-
var REDUCTION_BASES = Object.keys(BASIS_READINGS);
|
|
2210
|
-
function levelOf(reason) {
|
|
2211
|
-
if (reason.kind === "passing") {
|
|
2212
|
-
return "passing";
|
|
2213
|
-
}
|
|
2214
|
-
return reason.kind === "neighbor" ? "neighbor" : "structural";
|
|
2215
|
-
}
|
|
2216
|
-
function reduceProgression(timeline, key, opts = {}) {
|
|
2217
|
-
_chunkQ6R3QAZDcjs.assertChordTimeline.call(void 0, timeline);
|
|
2218
|
-
const asked = _chunkXKSO2V5Icjs.assertOptions.call(void 0, opts, "opts");
|
|
2219
|
-
const keyAt = _chunkQ6R3QAZDcjs.keyScaleAt.call(void 0, key);
|
|
2220
|
-
const basis = _chunkXKSO2V5Icjs.assertOneOf.call(void 0, _nullishCoalesce(asked.basis, () => ( "function")), REDUCTION_BASES, "reduction basis");
|
|
2221
|
-
const reading = BASIS_READINGS[basis];
|
|
2222
|
-
const segments = timeline.segments;
|
|
2223
|
-
const floors = basis === "duration" ? salienceFloors(segments.map((segment) => segment.endBeat - segment.startBeat)) : [];
|
|
2224
|
-
const result = [];
|
|
2225
|
-
for (let index = 0; index < segments.length; index += 1) {
|
|
2226
|
-
const segment = segments[index];
|
|
2227
|
-
if (segment === void 0) {
|
|
2228
|
-
continue;
|
|
1750
|
+
const cadences = /* @__PURE__ */ new Map();
|
|
1751
|
+
for (const closing of structuralCadences(phrases)) {
|
|
1752
|
+
const type = closing.cadence.type;
|
|
1753
|
+
if (type !== null) {
|
|
1754
|
+
cadences.set(closing.phraseIndex, {
|
|
1755
|
+
atBeat: closing.atBeat,
|
|
1756
|
+
type,
|
|
1757
|
+
weight: closing.weight
|
|
1758
|
+
});
|
|
2229
1759
|
}
|
|
2230
|
-
const previous = segments[index - 1];
|
|
2231
|
-
const following = segments[index + 1];
|
|
2232
|
-
const before = previous !== void 0 && touches(previous, segment) ? previous : void 0;
|
|
2233
|
-
const after = following !== void 0 && touches(segment, following) ? following : void 0;
|
|
2234
|
-
let reason = reading({ segment, before, after, keyAt, salienceFloor: _nullishCoalesce(floors[index], () => ( 0)) });
|
|
2235
|
-
if (reason === null && index === 0) {
|
|
2236
|
-
reason = { kind: "first" };
|
|
2237
|
-
}
|
|
2238
|
-
if (reason === null && index === segments.length - 1) {
|
|
2239
|
-
reason = { kind: "last" };
|
|
2240
|
-
}
|
|
2241
|
-
reason ??= _nullishCoalesce(figureReason(segment, before, after), () => ( { kind: "unfigured" }));
|
|
2242
|
-
result.push({
|
|
2243
|
-
chord: segment.chord,
|
|
2244
|
-
startBeat: segment.startBeat,
|
|
2245
|
-
endBeat: segment.endBeat,
|
|
2246
|
-
level: levelOf(reason),
|
|
2247
|
-
rationale: describe(reason)
|
|
2248
|
-
});
|
|
2249
1760
|
}
|
|
2250
|
-
|
|
1761
|
+
const sections = sectionsFromNotes(notes, {
|
|
1762
|
+
meters,
|
|
1763
|
+
unitBars: grouping.groupBars,
|
|
1764
|
+
totalBeats: spanEnd,
|
|
1765
|
+
budget
|
|
1766
|
+
});
|
|
1767
|
+
const keys = readings.keys.length === 0 && timeline.segments.length > 0 ? [
|
|
1768
|
+
{
|
|
1769
|
+
startBeat: span.startBeat,
|
|
1770
|
+
endBeat: spanEnd,
|
|
1771
|
+
key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, prevailingKey),
|
|
1772
|
+
confidence: 0
|
|
1773
|
+
}
|
|
1774
|
+
] : readings.keys.map((region) => ({
|
|
1775
|
+
startBeat: region.startBeat,
|
|
1776
|
+
endBeat: region.endBeat,
|
|
1777
|
+
key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, region.key),
|
|
1778
|
+
confidence: region.confidence
|
|
1779
|
+
}));
|
|
1780
|
+
const chords = _chunkIFZDWIKMcjs.reduceProgression.call(void 0, timeline, scaleAt).map((reduced) => {
|
|
1781
|
+
const index = keyIndexAt(keys, (reduced.startBeat + reduced.endBeat) / 2, prevailingKey);
|
|
1782
|
+
const key = keys[index].key;
|
|
1783
|
+
return {
|
|
1784
|
+
startBeat: reduced.startBeat,
|
|
1785
|
+
endBeat: reduced.endBeat,
|
|
1786
|
+
key: index,
|
|
1787
|
+
roman: _chunkIFZDWIKMcjs.chordToRoman.call(void 0, reduced.chord, key),
|
|
1788
|
+
function: _chunkIFZDWIKMcjs.functionOf.call(void 0, reduced.chord, key),
|
|
1789
|
+
level: reduced.level
|
|
1790
|
+
};
|
|
1791
|
+
});
|
|
1792
|
+
const chordOnsets = timeline.segments.map((segment) => ({
|
|
1793
|
+
pitch: 60,
|
|
1794
|
+
startBeat: segment.startBeat,
|
|
1795
|
+
durationBeat: segment.endBeat - segment.startBeat
|
|
1796
|
+
}));
|
|
1797
|
+
const harmonyRhythm = _chunkIFZDWIKMcjs.analyzeRhythm.call(void 0, chordOnsets, { meters, totalBeats: spanEnd, budget });
|
|
1798
|
+
const motifs = _chunkIFZDWIKMcjs.extractMotifs.call(void 0, line, { budget });
|
|
1799
|
+
const graph = _chunkIFZDWIKMcjs.motifGraph.call(void 0, line, motifs, { key: prevailingKey, budget });
|
|
1800
|
+
let register = null;
|
|
1801
|
+
if (line.length > 0) {
|
|
1802
|
+
const pitches = line.map((note) => note.pitch);
|
|
1803
|
+
register = {
|
|
1804
|
+
low: Math.min(...pitches),
|
|
1805
|
+
high: Math.max(...pitches),
|
|
1806
|
+
mean: _chunkIFZDWIKMcjs.weightedMean.call(void 0, line)
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
const lineMean = _nullishCoalesce(_optionalChain([register, 'optionalAccess', _67 => _67.mean]), () => ( 0));
|
|
1810
|
+
const referenceMotifs = motifs.map((motif) => {
|
|
1811
|
+
const first = motif.occurrences[0];
|
|
1812
|
+
return {
|
|
1813
|
+
intervals: motif.intervals,
|
|
1814
|
+
rhythm: motif.rhythm,
|
|
1815
|
+
unitBeats: motif.notes.length < 2 ? 0 : motif.notes[1].startBeat - motif.notes[0].startBeat,
|
|
1816
|
+
spanBeats: first === void 0 ? 0 : first.endBeat - first.startBeat,
|
|
1817
|
+
occurrences: motif.occurrences.length
|
|
1818
|
+
};
|
|
1819
|
+
});
|
|
1820
|
+
const referencePhrases = phrases.map((phrase, index) => ({
|
|
1821
|
+
startBeat: phrase.startBeat,
|
|
1822
|
+
endBeat: phrase.endBeat,
|
|
1823
|
+
bars: _chunkIFZDWIKMcjs.barSpanOf.call(void 0, phrase.startBeat, phrase.endBeat, meters),
|
|
1824
|
+
section: sectionAt(sections, phrase.startBeat),
|
|
1825
|
+
confidence: phrase.confidence,
|
|
1826
|
+
cadence: _nullishCoalesce(cadences.get(index), () => ( null)),
|
|
1827
|
+
melody: _chunkIFZDWIKMcjs.phraseMelody.call(void 0, line, phrase.startBeat, phrase.endBeat, lineMean, meters, budget),
|
|
1828
|
+
motifNodes: graph.nodes.flatMap(
|
|
1829
|
+
(node, nodeIndex) => node.startBeat >= phrase.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && node.startBeat < phrase.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS ? [nodeIndex] : []
|
|
1830
|
+
)
|
|
1831
|
+
}));
|
|
1832
|
+
return {
|
|
1833
|
+
profileVersion: _chunkIFZDWIKMcjs.REFERENCE_PROFILE_VERSION,
|
|
1834
|
+
span: {
|
|
1835
|
+
startBeat: span.startBeat,
|
|
1836
|
+
endBeat: spanEnd,
|
|
1837
|
+
bars: _chunkIFZDWIKMcjs.barSpanOf.call(void 0, span.startBeat, spanEnd, meters)
|
|
1838
|
+
},
|
|
1839
|
+
meters,
|
|
1840
|
+
form: {
|
|
1841
|
+
hypermeter: { groupBars: grouping.groupBars, confidence: grouping.confidence },
|
|
1842
|
+
sections,
|
|
1843
|
+
phrases: referencePhrases
|
|
1844
|
+
},
|
|
1845
|
+
harmony: { keys, chords, rhythm: harmonyRhythm },
|
|
1846
|
+
melody: {
|
|
1847
|
+
register,
|
|
1848
|
+
shape: line.length < 2 ? null : _chunkIFZDWIKMcjs.melodicContour.call(void 0, line).shape,
|
|
1849
|
+
motifs: referenceMotifs,
|
|
1850
|
+
graph,
|
|
1851
|
+
rhythm: _chunkIFZDWIKMcjs.analyzeRhythm.call(void 0, line, { meters, totalBeats: spanEnd, budget })
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
function analyzeReference(notes, opts) {
|
|
1856
|
+
return referenceFromReadings(notes, readingsFor(notes, opts), opts);
|
|
2251
1857
|
}
|
|
2252
1858
|
|
|
2253
1859
|
|
|
@@ -2264,9 +1870,5 @@ function reduceProgression(timeline, key, opts = {}) {
|
|
|
2264
1870
|
|
|
2265
1871
|
|
|
2266
1872
|
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
exports.trackRoleOf = trackRoleOf; exports.analyzePolyphony = analyzePolyphony; exports.analyzeArrangement = analyzeArrangement; exports.createArrangementSession = createArrangementSession; exports.tensionCurve = tensionCurve; exports.tensionCurveFrom = tensionCurveFrom; exports.hypermeter = hypermeter; exports.melodicContour = melodicContour; exports.extractMotifs = extractMotifs; exports.motifFromNotes = motifFromNotes; exports.relateMotifs = relateMotifs; exports.compareMelodies = compareMelodies; exports.melodicSimilarity = melodicSimilarity; exports.phrasesFromTimeline = phrasesFromTimeline; exports.structuralCadences = structuralCadences; exports.sectionsFromNotes = sectionsFromNotes; exports.reduceProgression = reduceProgression;
|
|
2272
|
-
//# sourceMappingURL=chunk-XVMJO3QF.cjs.map
|
|
1873
|
+
exports.trackRoleOf = trackRoleOf; exports.analyzePolyphony = analyzePolyphony; exports.analyzeArrangement = analyzeArrangement; exports.createArrangementSession = createArrangementSession; exports.tensionCurve = tensionCurve; exports.tensionCurveFrom = tensionCurveFrom; exports.hypermeter = hypermeter; exports.phrasesFromTimeline = phrasesFromTimeline; exports.structuralCadences = structuralCadences; exports.sectionsFromNotes = sectionsFromNotes; exports.compareReferences = compareReferences; exports.referenceFromReadings = referenceFromReadings; exports.analyzeReference = analyzeReference;
|
|
1874
|
+
//# sourceMappingURL=chunk-YFA7X2F6.cjs.map
|