@libraz/libcantus 0.9.2 → 0.9.4
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 +6 -5
- package/dist/analyze/index.cjs +41 -1676
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +194 -122
- package/dist/analyze/index.d.ts +194 -122
- package/dist/analyze/index.js +19 -8
- package/dist/chunk-36N73KZ3.cjs +246 -0
- package/dist/chunk-36N73KZ3.cjs.map +1 -0
- package/dist/chunk-4HLLZPXT.cjs +3077 -0
- package/dist/chunk-4HLLZPXT.cjs.map +1 -0
- package/dist/chunk-7MGZ7D62.cjs +498 -0
- package/dist/chunk-7MGZ7D62.cjs.map +1 -0
- package/dist/{chunk-R3S2LUBH.js → chunk-7NKDPF74.js} +660 -305
- package/dist/chunk-7NKDPF74.js.map +1 -0
- package/dist/chunk-CAN76NCU.cjs +679 -0
- package/dist/chunk-CAN76NCU.cjs.map +1 -0
- package/dist/{chunk-7Y7WF64D.js → chunk-D2FAJBYL.js} +104 -35
- package/dist/chunk-D2FAJBYL.js.map +1 -0
- package/dist/{chunk-COKTX2G5.js → chunk-EHVEBWM5.js} +1052 -510
- package/dist/chunk-EHVEBWM5.js.map +1 -0
- package/dist/chunk-FCANXBAT.cjs +30 -0
- package/dist/chunk-FCANXBAT.cjs.map +1 -0
- package/dist/chunk-FNDWXRRI.cjs +479 -0
- package/dist/chunk-FNDWXRRI.cjs.map +1 -0
- package/dist/{chunk-GQKN5KDT.js → chunk-GQSGS2N4.js} +43 -29
- package/dist/chunk-GQSGS2N4.js.map +1 -0
- package/dist/chunk-HKGYCQ34.js +53 -0
- package/dist/chunk-HKGYCQ34.js.map +1 -0
- package/dist/chunk-I24MV65U.js +106 -0
- package/dist/chunk-I24MV65U.js.map +1 -0
- package/dist/{chunk-LN7ZZMD2.js → chunk-KMHOICIY.js} +322 -173
- package/dist/chunk-KMHOICIY.js.map +1 -0
- package/dist/chunk-LW53AHQU.cjs +1117 -0
- package/dist/chunk-LW53AHQU.cjs.map +1 -0
- package/dist/chunk-N6IHQUOX.js +1 -0
- package/dist/chunk-N6IHQUOX.js.map +1 -0
- package/dist/chunk-NKIBQ6PT.js +30 -0
- package/dist/chunk-NKIBQ6PT.js.map +1 -0
- package/dist/chunk-NP4NA2FB.cjs +106 -0
- package/dist/chunk-NP4NA2FB.cjs.map +1 -0
- package/dist/chunk-NVE34YGZ.cjs +1449 -0
- package/dist/chunk-NVE34YGZ.cjs.map +1 -0
- package/dist/{chunk-A4532ZFR.js → chunk-NYYWMV2C.js} +115 -47
- package/dist/chunk-NYYWMV2C.js.map +1 -0
- package/dist/{chunk-5HTNMQW4.js → chunk-Q2XUP227.js} +310 -118
- package/dist/chunk-Q2XUP227.js.map +1 -0
- package/dist/chunk-Q6OP4U43.cjs +327 -0
- package/dist/chunk-Q6OP4U43.cjs.map +1 -0
- package/dist/chunk-RC6GBMCD.cjs +845 -0
- package/dist/chunk-RC6GBMCD.cjs.map +1 -0
- package/dist/{chunk-XTC3X6MS.js → chunk-RK35RIN7.js} +453 -152
- package/dist/chunk-RK35RIN7.js.map +1 -0
- package/dist/chunk-RUMU3UA6.cjs +53 -0
- package/dist/chunk-RUMU3UA6.cjs.map +1 -0
- package/dist/chunk-T64C7ZD7.cjs +1 -0
- package/dist/chunk-T64C7ZD7.cjs.map +1 -0
- package/dist/chunk-XEBG3G53.js +498 -0
- package/dist/chunk-XEBG3G53.js.map +1 -0
- package/dist/chunk-YLEMGEFH.js +246 -0
- package/dist/chunk-YLEMGEFH.js.map +1 -0
- package/dist/chunk-ZEJR4TKL.cjs +173 -0
- package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
- package/dist/core/index.cjs +130 -431
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +331 -17
- package/dist/core/index.d.ts +331 -17
- package/dist/core/index.js +71 -13
- package/dist/{function-2bJW1Zp_.d.ts → function-BtiZb9i9.d.ts} +28 -7
- package/dist/{function-r2sh-jOO.d.cts → function-DNugAOrt.d.cts} +28 -7
- package/dist/generate/index.cjs +30 -3751
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +373 -59
- package/dist/generate/index.d.ts +373 -59
- package/dist/generate/index.js +23 -9
- package/dist/{index-DZ1M21do.d.cts → index-6VR8-kit.d.ts} +20 -4
- package/dist/index-70p168VM.d.ts +192 -0
- package/dist/{index-D_ZTUNKp.d.cts → index-Be74mcon.d.cts} +18 -17
- package/dist/index-BtXT6UQ0.d.cts +192 -0
- package/dist/{index-BMPxwjwV.d.ts → index-C5UBbQ4G.d.ts} +18 -17
- package/dist/{index-BOBXBNwP.d.cts → index-C83mZI1P.d.ts} +93 -8
- package/dist/{index-BdeQ1r_p.d.cts → index-D6b6na69.d.cts} +14 -11
- package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
- package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
- package/dist/index-OG65ocOr.d.cts +272 -0
- package/dist/index-OG65ocOr.d.ts +272 -0
- package/dist/{index-BreoJRw0.d.ts → index-tJhkmQgE.d.cts} +20 -4
- package/dist/{index-CP3wAfEH.d.ts → index-tSQh-xwY.d.ts} +14 -11
- package/dist/{index-CM5W4rcm.d.ts → index-y_ak9_Hi.d.cts} +93 -8
- package/dist/index.cjs +419 -6848
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -13
- package/dist/index.d.ts +14 -13
- package/dist/index.js +132 -27
- package/dist/model/index.cjs +8 -2423
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +433 -74
- package/dist/model/index.d.ts +433 -74
- package/dist/model/index.js +6 -7
- package/dist/styled-DIRkk9Uz.d.ts +506 -0
- package/dist/styled-DJbkJQfj.d.cts +506 -0
- package/dist/theory/index.cjs +172 -1676
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +151 -233
- package/dist/theory/index.d.ts +151 -233
- package/dist/theory/index.js +31 -7
- package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
- package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
- package/package.json +73 -24
- package/dist/chunk-5HTNMQW4.js.map +0 -1
- package/dist/chunk-7Y7WF64D.js.map +0 -1
- package/dist/chunk-A4532ZFR.js.map +0 -1
- package/dist/chunk-BHUT2S5G.js +0 -22
- package/dist/chunk-BHUT2S5G.js.map +0 -1
- package/dist/chunk-CCBLVAPK.js +0 -42
- package/dist/chunk-CCBLVAPK.js.map +0 -1
- package/dist/chunk-COKTX2G5.js.map +0 -1
- package/dist/chunk-DYWFNC4A.js +0 -152
- package/dist/chunk-DYWFNC4A.js.map +0 -1
- package/dist/chunk-GQKN5KDT.js.map +0 -1
- package/dist/chunk-LN7ZZMD2.js.map +0 -1
- package/dist/chunk-MSWFZF7J.js +0 -118
- package/dist/chunk-MSWFZF7J.js.map +0 -1
- package/dist/chunk-R3S2LUBH.js.map +0 -1
- package/dist/chunk-SDM3VJJ5.js +0 -41
- package/dist/chunk-SDM3VJJ5.js.map +0 -1
- package/dist/chunk-XTC3X6MS.js.map +0 -1
- package/dist/chunk-ZXW5YIWK.js +0 -55
- package/dist/chunk-ZXW5YIWK.js.map +0 -1
- package/dist/index-CGMZNCx6.d.cts +0 -140
- package/dist/index-CGMZNCx6.d.ts +0 -140
- package/dist/styled-BjXtOFNk.d.cts +0 -282
- package/dist/styled-D7BAx3S3.d.ts +0 -282
- package/src/analyze/arrange/index.ts +0 -21
- package/src/analyze/arrange/internal.ts +0 -125
- package/src/analyze/arrange/tension.ts +0 -187
- package/src/analyze/arrange/tracks.ts +0 -279
- package/src/analyze/detect/index.ts +0 -235
- package/src/analyze/functional/borrowed.ts +0 -84
- package/src/analyze/functional/cadence.ts +0 -66
- package/src/analyze/functional/function.ts +0 -184
- package/src/analyze/functional/index.ts +0 -23
- package/src/analyze/functional/internal.ts +0 -29
- package/src/analyze/functional/roman.ts +0 -393
- package/src/analyze/index.ts +0 -49
- package/src/analyze/timeline/index.ts +0 -415
- package/src/analyze/voice/index.ts +0 -330
- package/src/core/index.ts +0 -47
- package/src/core/interval/index.ts +0 -81
- package/src/core/meter/index.ts +0 -282
- package/src/core/pitch/index.ts +0 -311
- package/src/core/random/index.ts +0 -53
- package/src/core/tuning/index.ts +0 -156
- package/src/core/types.ts +0 -29
- package/src/generate/bass/index.ts +0 -350
- package/src/generate/countermelody/index.ts +0 -379
- package/src/generate/drums/beat.ts +0 -266
- package/src/generate/drums/euclid.ts +0 -58
- package/src/generate/drums/fills.ts +0 -356
- package/src/generate/drums/ghost.ts +0 -119
- package/src/generate/drums/hihat.ts +0 -285
- package/src/generate/drums/hit.ts +0 -31
- package/src/generate/drums/index.ts +0 -329
- package/src/generate/drums/internal.ts +0 -257
- package/src/generate/drums/kick.ts +0 -160
- package/src/generate/drums/percussion.ts +0 -121
- package/src/generate/drums/rng.ts +0 -7
- package/src/generate/drums/swing.ts +0 -71
- package/src/generate/groove/index.ts +0 -326
- package/src/generate/harmonize/index.ts +0 -440
- package/src/generate/index.ts +0 -59
- package/src/generate/motif/index.ts +0 -398
- package/src/generate/progression/index.ts +0 -370
- package/src/generate/reharmony/index.ts +0 -330
- package/src/generate/rhythm/index.ts +0 -165
- package/src/index.ts +0 -247
- package/src/model/chord.ts +0 -423
- package/src/model/index.ts +0 -15
- package/src/model/interval.ts +0 -82
- package/src/model/key.ts +0 -266
- package/src/model/note.ts +0 -176
- package/src/model/progression.ts +0 -161
- package/src/model/shared.ts +0 -12
- package/src/theory/chord/index.ts +0 -373
- package/src/theory/chordscale/index.ts +0 -374
- package/src/theory/counterpoint/index.ts +0 -230
- package/src/theory/harmony/index.ts +0 -92
- package/src/theory/index.ts +0 -107
- package/src/theory/safety/index.ts +0 -419
- package/src/theory/scale/degrees.ts +0 -123
- package/src/theory/scale/index.ts +0 -28
- package/src/theory/scale/key.ts +0 -56
- package/src/theory/scale/masks.ts +0 -153
- package/src/theory/spelling/index.ts +0 -182
- package/src/theory/symbol/index.ts +0 -268
- package/src/theory/voicing/index.ts +0 -5
- package/src/theory/voicing/internal.ts +0 -168
- package/src/theory/voicing/leading.ts +0 -112
- package/src/theory/voicing/satb.ts +0 -195
- package/src/theory/voicing/styled.ts +0 -176
- package/tsconfig.json +0 -23
|
@@ -1,35 +1,58 @@
|
|
|
1
1
|
import {
|
|
2
2
|
beatsPerBar,
|
|
3
|
+
createNoteEventIndex,
|
|
3
4
|
isStrongBeat,
|
|
4
5
|
metricWeight,
|
|
5
6
|
parseTimeSignature
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-YLEMGEFH.js";
|
|
7
8
|
import {
|
|
8
9
|
detectCadence,
|
|
9
10
|
detectChord,
|
|
10
11
|
detectChordBest,
|
|
11
|
-
|
|
12
|
+
detectKeyFromNotes,
|
|
12
13
|
functionOf
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-KMHOICIY.js";
|
|
14
15
|
import {
|
|
15
16
|
evaluateSafety
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-NYYWMV2C.js";
|
|
17
18
|
import {
|
|
18
19
|
chordPitchClasses,
|
|
19
20
|
chordToneRole,
|
|
20
21
|
createsVerticalDissonance,
|
|
22
|
+
intervalAboveRoot,
|
|
23
|
+
isChordMember,
|
|
21
24
|
isLeadingToneResolution,
|
|
22
25
|
isScaleTone,
|
|
23
26
|
majorKey,
|
|
24
27
|
makeChord
|
|
25
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-D2FAJBYL.js";
|
|
29
|
+
import {
|
|
30
|
+
InvalidInputError,
|
|
31
|
+
assertFiniteNumber,
|
|
32
|
+
assertGenerationBudget,
|
|
33
|
+
assertNoteEvents,
|
|
34
|
+
assertRange,
|
|
35
|
+
assertTimeSignature,
|
|
36
|
+
pitchClassOf
|
|
37
|
+
} from "./chunk-XEBG3G53.js";
|
|
26
38
|
|
|
27
39
|
// src/analyze/timeline/index.ts
|
|
28
40
|
function chordTimelineFromChords(chords, totalBeats) {
|
|
29
|
-
|
|
41
|
+
assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, "timeline totalBeats");
|
|
42
|
+
assertGenerationBudget(chords.length, "timeline chords");
|
|
43
|
+
for (let index = 0; index < chords.length; index += 1) {
|
|
44
|
+
assertRange(
|
|
45
|
+
chords[index]?.startBeat ?? Number.NaN,
|
|
46
|
+
0,
|
|
47
|
+
Number.MAX_SAFE_INTEGER,
|
|
48
|
+
`chords[${index}].startBeat`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const sorted = [...chords].filter((chord) => chord.startBeat < totalBeats).sort((a, b) => a.startBeat - b.startBeat);
|
|
30
52
|
const segments = sorted.map((gc, i) => {
|
|
31
53
|
const next = sorted[i + 1];
|
|
32
|
-
const
|
|
54
|
+
const nextBeat = next ? next.startBeat : totalBeats;
|
|
55
|
+
const endBeat = Math.min(totalBeats, Math.max(gc.startBeat, nextBeat));
|
|
33
56
|
return {
|
|
34
57
|
startBeat: gc.startBeat,
|
|
35
58
|
endBeat,
|
|
@@ -40,12 +63,19 @@ function chordTimelineFromChords(chords, totalBeats) {
|
|
|
40
63
|
}
|
|
41
64
|
function segmentLookup(segments) {
|
|
42
65
|
return (beat) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
66
|
+
assertFiniteNumber(beat, "timeline query beat");
|
|
67
|
+
let low = 0;
|
|
68
|
+
let high = segments.length;
|
|
69
|
+
while (low < high) {
|
|
70
|
+
const middle = Math.floor((low + high) / 2);
|
|
71
|
+
if ((segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {
|
|
72
|
+
low = middle + 1;
|
|
73
|
+
} else {
|
|
74
|
+
high = middle;
|
|
46
75
|
}
|
|
47
76
|
}
|
|
48
|
-
|
|
77
|
+
const candidate = segments[low - 1];
|
|
78
|
+
return candidate !== void 0 && beat >= candidate.startBeat && beat < candidate.endBeat ? candidate.chord : null;
|
|
49
79
|
};
|
|
50
80
|
}
|
|
51
81
|
var EPS = 1e-9;
|
|
@@ -56,9 +86,6 @@ var INEXACT_CONFIDENCE_FACTOR = 0.85;
|
|
|
56
86
|
var DIATONIC_BONUS = 0.5;
|
|
57
87
|
var EXACT_BONUS = 0.5;
|
|
58
88
|
var MISMATCH_PENALTY = 0.3;
|
|
59
|
-
function pitchClass(value) {
|
|
60
|
-
return (Math.trunc(value) % 12 + 12) % 12;
|
|
61
|
-
}
|
|
62
89
|
function scoreMatch(match, weights, maxWeight, key) {
|
|
63
90
|
const rootWeight = maxWeight > 0 ? (weights[match.rootPc] ?? 0) / maxWeight : 0;
|
|
64
91
|
const tones = chordPitchClasses(makeChord(match.rootPc, match.quality));
|
|
@@ -96,7 +123,7 @@ function analyzeWindow(notes, windowStart, windowEnd, ts, key) {
|
|
|
96
123
|
const velocityFactor = note.velocity !== void 0 ? note.velocity / 127 : 1;
|
|
97
124
|
const onsetInWindow = note.startBeat >= windowStart - EPS && note.startBeat < windowEnd - EPS;
|
|
98
125
|
const accent = onsetInWindow ? 1 + metricWeight(note.startBeat, ts) / 3 : 1;
|
|
99
|
-
const pc =
|
|
126
|
+
const pc = pitchClassOf(note.pitch);
|
|
100
127
|
weights[pc] = (weights[pc] ?? 0) + overlap * velocityFactor * accent;
|
|
101
128
|
if (note.pitch < lowestPitch) {
|
|
102
129
|
lowestPitch = note.pitch;
|
|
@@ -113,7 +140,7 @@ function analyzeWindow(notes, windowStart, windowEnd, ts, key) {
|
|
|
113
140
|
}
|
|
114
141
|
const ranked = weights.map((weight, pc) => ({ pc, weight })).filter(({ weight }) => weight > 0).sort((a, b) => b.weight - a.weight || a.pc - b.pc);
|
|
115
142
|
const selected = ranked.filter(({ weight }) => weight >= maxWeight * NOISE_THRESHOLD_RATIO).slice(0, MAX_DETECTION_PCS).map(({ pc }) => pc);
|
|
116
|
-
const bassPc =
|
|
143
|
+
const bassPc = pitchClassOf(lowestPitch);
|
|
117
144
|
const anchorPc = selected.includes(bassPc) ? bassPc : selected[0] ?? bassPc;
|
|
118
145
|
const detectionPitches = selected.map((pc) => pc === anchorPc ? pc : pc + 12);
|
|
119
146
|
const matches = detectChord(detectionPitches);
|
|
@@ -141,21 +168,39 @@ function sameChord(a, b) {
|
|
|
141
168
|
}
|
|
142
169
|
function chordTimelineFromNotes(notes, opts = {}) {
|
|
143
170
|
const ts = opts.ts ?? parseTimeSignature("4/4");
|
|
171
|
+
assertTimeSignature(ts);
|
|
144
172
|
const harmonicRhythm = opts.harmonicRhythm ?? beatsPerBar(ts);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
const
|
|
173
|
+
assertRange(harmonicRhythm, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "harmonic rhythm");
|
|
174
|
+
const budget = opts.budget;
|
|
175
|
+
assertNoteEvents(notes, "timeline notes", { allowNonPositiveDuration: true, budget });
|
|
176
|
+
const soundingIndex = createNoteEventIndex(
|
|
177
|
+
notes.filter((note) => note.durationBeat > 0),
|
|
178
|
+
{ name: "timeline notes", budget }
|
|
179
|
+
);
|
|
180
|
+
const sounding = soundingIndex.notes.map(({ note }) => note);
|
|
149
181
|
const lastNoteEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
|
|
150
182
|
const totalBeats = opts.totalBeats ?? lastNoteEnd;
|
|
151
|
-
|
|
183
|
+
assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, "timeline totalBeats");
|
|
184
|
+
const key = opts.key ?? detectKeyFromNotes(sounding)[0]?.key ?? majorKey(0);
|
|
152
185
|
const segments = [];
|
|
153
186
|
const segmentConfidence = [];
|
|
154
187
|
const windowCount = Math.max(0, Math.ceil(totalBeats / harmonicRhythm - EPS));
|
|
188
|
+
assertGenerationBudget(windowCount, "timeline windows", budget);
|
|
189
|
+
const windowNotes = Array.from({ length: windowCount }, () => []);
|
|
190
|
+
let memberships = 0;
|
|
191
|
+
for (const indexed of soundingIndex.notes) {
|
|
192
|
+
const first = Math.max(0, Math.floor(indexed.note.startBeat / harmonicRhythm));
|
|
193
|
+
const lastExclusive = Math.min(windowCount, Math.ceil(indexed.endBeat / harmonicRhythm));
|
|
194
|
+
memberships += Math.max(0, lastExclusive - first);
|
|
195
|
+
assertGenerationBudget(memberships, "timeline note-to-window memberships", budget);
|
|
196
|
+
for (let window = first; window < lastExclusive; window += 1) {
|
|
197
|
+
windowNotes[window]?.push(indexed.note);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
155
200
|
for (let i = 0; i < windowCount; i += 1) {
|
|
156
201
|
const start = i * harmonicRhythm;
|
|
157
202
|
const end = Math.min(start + harmonicRhythm, totalBeats);
|
|
158
|
-
const inferred = analyzeWindow(
|
|
203
|
+
const inferred = analyzeWindow(windowNotes[i] ?? [], start, end, ts, key);
|
|
159
204
|
if (!inferred) {
|
|
160
205
|
continue;
|
|
161
206
|
}
|
|
@@ -198,12 +243,23 @@ function detectCadences(timeline, key) {
|
|
|
198
243
|
|
|
199
244
|
// src/analyze/arrange/internal.ts
|
|
200
245
|
var EPS2 = 1e-9;
|
|
246
|
+
var MAX_LANE_LEAP = 24;
|
|
201
247
|
function covers(note, beat) {
|
|
202
248
|
return beat >= note.startBeat - EPS2 && beat < note.endBeat - EPS2;
|
|
203
249
|
}
|
|
204
|
-
function
|
|
250
|
+
function isPercussion(role) {
|
|
251
|
+
return role === "drums";
|
|
252
|
+
}
|
|
253
|
+
function poolNotes(tracks, only) {
|
|
205
254
|
const notes = [];
|
|
206
|
-
for (
|
|
255
|
+
for (let index = 0; index < tracks.length; index += 1) {
|
|
256
|
+
const track = tracks[index];
|
|
257
|
+
if (track === void 0 || isPercussion(track.role)) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (only !== void 0 && !only.has(index)) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
207
263
|
for (const note of track.notes) {
|
|
208
264
|
if (note.durationBeat > 0) {
|
|
209
265
|
notes.push(note);
|
|
@@ -215,37 +271,78 @@ function poolNotes(tracks) {
|
|
|
215
271
|
function splitIntoSubVoices(ordered) {
|
|
216
272
|
const lanes = [];
|
|
217
273
|
for (const note of ordered) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
274
|
+
let best = -1;
|
|
275
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
276
|
+
let bestEnd = Number.NEGATIVE_INFINITY;
|
|
277
|
+
for (let lane = 0; lane < lanes.length; lane += 1) {
|
|
278
|
+
const last = lanes[lane]?.[(lanes[lane]?.length ?? 0) - 1];
|
|
279
|
+
if (last === void 0) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const end = last.startBeat + last.durationBeat;
|
|
283
|
+
if (end > note.startBeat + EPS2) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const distance = Math.abs(last.pitch - note.pitch);
|
|
287
|
+
if (distance > MAX_LANE_LEAP) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (distance < bestDistance || distance === bestDistance && end > bestEnd) {
|
|
291
|
+
best = lane;
|
|
292
|
+
bestDistance = distance;
|
|
293
|
+
bestEnd = end;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (best >= 0) {
|
|
297
|
+
lanes[best]?.push(note);
|
|
224
298
|
} else {
|
|
225
299
|
lanes.push([note]);
|
|
226
300
|
}
|
|
227
301
|
}
|
|
228
302
|
return lanes;
|
|
229
303
|
}
|
|
304
|
+
function lastStartingAtOrBefore(sounding, beat) {
|
|
305
|
+
let low = 0;
|
|
306
|
+
let high = sounding.length;
|
|
307
|
+
while (low < high) {
|
|
308
|
+
const middle = Math.floor((low + high) / 2);
|
|
309
|
+
if ((sounding[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat + EPS2) {
|
|
310
|
+
low = middle + 1;
|
|
311
|
+
} else {
|
|
312
|
+
high = middle;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return low - 1;
|
|
316
|
+
}
|
|
230
317
|
function prepareTracks(tracks) {
|
|
231
318
|
let nextId = 0;
|
|
232
319
|
return tracks.map((track, index) => {
|
|
233
|
-
const ordered = track.notes.filter((note) => note.durationBeat > 0).sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch).map((note) => ({ ...note, id: nextId
|
|
234
|
-
const voices = splitIntoSubVoices(ordered).map((voice) =>
|
|
235
|
-
voice,
|
|
236
|
-
sounding: voice.map((note, i) => {
|
|
320
|
+
const ordered = track.notes.map((note, originalIndex) => ({ note, originalIndex })).filter(({ note }) => note.durationBeat > 0).sort((a, b) => a.note.startBeat - b.note.startBeat || a.note.pitch - b.note.pitch).map(({ note, originalIndex }) => ({ ...note, id: nextId++, originalIndex }));
|
|
321
|
+
const voices = splitIntoSubVoices(ordered).map((voice) => {
|
|
322
|
+
const sounding = voice.map((note, i) => {
|
|
237
323
|
const prev = i > 0 ? voice[i - 1] : void 0;
|
|
324
|
+
const prevEnd = prev === void 0 ? void 0 : prev.startBeat + prev.durationBeat;
|
|
325
|
+
const contiguous = prevEnd !== void 0 && Math.abs(prevEnd - note.startBeat) <= EPS2;
|
|
238
326
|
return {
|
|
239
327
|
pitch: note.pitch,
|
|
240
|
-
prevPitch: prev?.pitch,
|
|
328
|
+
prevPitch: contiguous ? prev?.pitch : void 0,
|
|
241
329
|
startBeat: note.startBeat,
|
|
242
330
|
endBeat: note.startBeat + note.durationBeat
|
|
243
331
|
};
|
|
244
|
-
})
|
|
245
|
-
|
|
332
|
+
});
|
|
333
|
+
return {
|
|
334
|
+
voice,
|
|
335
|
+
sounding,
|
|
336
|
+
at: (beat) => {
|
|
337
|
+
const candidate = sounding[lastStartingAtOrBefore(sounding, beat)];
|
|
338
|
+
return candidate !== void 0 && covers(candidate, beat) ? candidate : void 0;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
});
|
|
246
342
|
return {
|
|
247
343
|
name: track.name ?? `track ${index + 1}`,
|
|
248
344
|
role: track.role ?? "other",
|
|
345
|
+
trackIndex: index,
|
|
249
346
|
voices
|
|
250
347
|
};
|
|
251
348
|
});
|
|
@@ -261,47 +358,64 @@ var FUNCTION_WEIGHT = 0.5;
|
|
|
261
358
|
var DISSONANCE_WEIGHT = 0.35;
|
|
262
359
|
var SPAN_WEIGHT = 0.15;
|
|
263
360
|
var SPAN_SATURATION = 24;
|
|
264
|
-
function pitchClass2(pitch) {
|
|
265
|
-
return (Math.trunc(pitch) % 12 + 12) % 12;
|
|
266
|
-
}
|
|
267
361
|
function clamp01(value) {
|
|
268
362
|
return Math.min(1, Math.max(0, value));
|
|
269
363
|
}
|
|
270
364
|
function tensionCurve(tracks, opts = {}) {
|
|
271
365
|
const ts = opts.ts ?? parseTimeSignature("4/4");
|
|
366
|
+
assertTimeSignature(ts);
|
|
367
|
+
const budget = opts.budget;
|
|
368
|
+
assertGenerationBudget(tracks.length, "arrangement tracks", budget);
|
|
369
|
+
for (let index = 0; index < tracks.length; index += 1) {
|
|
370
|
+
assertNoteEvents(tracks[index]?.notes ?? [], `tracks[${index}].notes`, {
|
|
371
|
+
allowNonPositiveDuration: true,
|
|
372
|
+
budget
|
|
373
|
+
});
|
|
374
|
+
}
|
|
272
375
|
const profile = opts.profile ?? "pop";
|
|
273
376
|
const step = opts.step ?? 1;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
377
|
+
assertRange(step, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "tension sampling step");
|
|
378
|
+
const harmonyTracks = opts.harmonyTracks === void 0 ? void 0 : new Set(opts.harmonyTracks.map(Math.trunc));
|
|
379
|
+
const pooled = poolNotes(tracks, harmonyTracks);
|
|
380
|
+
const totalBeats = poolNotes(tracks).reduce(
|
|
381
|
+
(end, n) => Math.max(end, n.startBeat + n.durationBeat),
|
|
382
|
+
0
|
|
383
|
+
);
|
|
384
|
+
const { timeline, key } = opts.timeline === void 0 ? chordTimelineFromNotes(pooled, {
|
|
280
385
|
key: opts.key,
|
|
281
386
|
ts,
|
|
282
|
-
harmonicRhythm: opts.harmonicRhythm
|
|
283
|
-
|
|
387
|
+
harmonicRhythm: opts.harmonicRhythm,
|
|
388
|
+
budget
|
|
389
|
+
}) : {
|
|
390
|
+
timeline: opts.timeline,
|
|
391
|
+
key: opts.key ?? detectKeyFromNotes(pooled)[0]?.key ?? majorKey(0)
|
|
392
|
+
};
|
|
284
393
|
const prepared = prepareTracks(tracks);
|
|
285
394
|
const points = [];
|
|
286
395
|
const sampleCount = Math.max(0, Math.ceil(totalBeats / step - EPS2));
|
|
396
|
+
assertGenerationBudget(sampleCount, "tension samples", budget);
|
|
397
|
+
const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);
|
|
398
|
+
assertGenerationBudget(sampleCount * voiceCount, "tension sample-voice lookups", budget);
|
|
287
399
|
for (let i = 0; i < sampleCount; i += 1) {
|
|
288
400
|
const beat = i * step;
|
|
289
401
|
points.push({ beat, tension: sampleTension(prepared, timeline, key, ts, profile, beat) });
|
|
290
402
|
}
|
|
291
403
|
return points;
|
|
292
404
|
}
|
|
405
|
+
function tensionCurveFrom(tracks, analysis, opts = {}) {
|
|
406
|
+
return tensionCurve(tracks, { ...opts, timeline: analysis.timeline, key: analysis.key });
|
|
407
|
+
}
|
|
293
408
|
function allSounding(prepared, beat) {
|
|
294
409
|
const out = [];
|
|
295
410
|
for (let t = 0; t < prepared.length; t += 1) {
|
|
296
411
|
const track = prepared[t];
|
|
297
|
-
if (!track) {
|
|
412
|
+
if (!track || isPercussion(track.role)) {
|
|
298
413
|
continue;
|
|
299
414
|
}
|
|
300
415
|
for (const subVoice of track.voices) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
416
|
+
const note = subVoice.at(beat);
|
|
417
|
+
if (note !== void 0) {
|
|
418
|
+
out.push({ pitch: note.pitch, track: t });
|
|
305
419
|
}
|
|
306
420
|
}
|
|
307
421
|
}
|
|
@@ -323,18 +437,21 @@ function sampleTension(prepared, timeline, key, ts, profile, beat) {
|
|
|
323
437
|
if (!voice) {
|
|
324
438
|
continue;
|
|
325
439
|
}
|
|
326
|
-
if (chordPcs && !chordPcs.has(
|
|
440
|
+
if (chordPcs && !chordPcs.has(pitchClassOf(voice.pitch))) {
|
|
327
441
|
nonChord += 1;
|
|
328
442
|
}
|
|
329
443
|
const others = sounding.filter((_, j) => j !== i).map((s) => ({ pitch: s.pitch }));
|
|
330
|
-
const result = evaluateSafety(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
444
|
+
const result = evaluateSafety(
|
|
445
|
+
{
|
|
446
|
+
profile,
|
|
447
|
+
candidatePitch: voice.pitch,
|
|
448
|
+
chord,
|
|
449
|
+
key,
|
|
450
|
+
otherVoices: others,
|
|
451
|
+
strongBeat
|
|
452
|
+
},
|
|
453
|
+
{ suggestions: false }
|
|
454
|
+
);
|
|
338
455
|
if (result.safety === 2 /* Dissonant */) {
|
|
339
456
|
dissonant += 1;
|
|
340
457
|
}
|
|
@@ -353,13 +470,10 @@ function sampleTension(prepared, timeline, key, ts, profile, beat) {
|
|
|
353
470
|
}
|
|
354
471
|
|
|
355
472
|
// src/analyze/voice/index.ts
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
return (Math.trunc(pitch) % 12 + 12) % 12;
|
|
359
|
-
}
|
|
360
|
-
function intervalAboveRoot(pitch, chord) {
|
|
361
|
-
return ((pitchClass3(pitch) - pitchClass3(chord.rootPc)) % 12 + 12) % 12;
|
|
473
|
+
function toVoiceNotes(events) {
|
|
474
|
+
return events.map((event, index) => ({ ...event, id: index, originalIndex: index }));
|
|
362
475
|
}
|
|
476
|
+
var EPS3 = 1e-9;
|
|
363
477
|
function intervalAboveBass(pitch, others, chord) {
|
|
364
478
|
let bass = Number.POSITIVE_INFINITY;
|
|
365
479
|
for (const ov of others) {
|
|
@@ -369,7 +483,7 @@ function intervalAboveBass(pitch, others, chord) {
|
|
|
369
483
|
return ((pitch - bass) % 12 + 12) % 12;
|
|
370
484
|
}
|
|
371
485
|
const ref = chord.bassPc ?? chord.rootPc;
|
|
372
|
-
return ((
|
|
486
|
+
return ((pitchClassOf(pitch) - pitchClassOf(ref)) % 12 + 12) % 12;
|
|
373
487
|
}
|
|
374
488
|
function tensionDegree(ic) {
|
|
375
489
|
if (ic === 1 || ic === 2) {
|
|
@@ -380,25 +494,27 @@ function tensionDegree(ic) {
|
|
|
380
494
|
}
|
|
381
495
|
return 13;
|
|
382
496
|
}
|
|
383
|
-
function isChordMember(pitch, chord) {
|
|
384
|
-
return chord ? chordPitchClasses(chord).includes(pitchClass3(pitch)) : false;
|
|
385
|
-
}
|
|
386
497
|
function isStep(a, b) {
|
|
387
498
|
const d = Math.abs(a - b);
|
|
388
499
|
return d === 1 || d === 2;
|
|
389
500
|
}
|
|
390
501
|
function suspensionType(ic, delta) {
|
|
391
|
-
let type;
|
|
392
502
|
if (delta > 0) {
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
|
|
503
|
+
return "sus2-3";
|
|
504
|
+
}
|
|
505
|
+
if (ic === 2) {
|
|
506
|
+
return "sus9-8";
|
|
507
|
+
}
|
|
508
|
+
if (ic === 10 || ic === 11) {
|
|
509
|
+
return "sus7-6";
|
|
510
|
+
}
|
|
511
|
+
if (ic === 5 || ic === 6) {
|
|
512
|
+
return "sus4-3";
|
|
513
|
+
}
|
|
514
|
+
if (ic === 8 || ic === 9) {
|
|
515
|
+
return "sus6-5";
|
|
516
|
+
}
|
|
517
|
+
return null;
|
|
402
518
|
}
|
|
403
519
|
function stepResolution(pitch, chord) {
|
|
404
520
|
for (let delta = 1; delta <= 2; delta += 1) {
|
|
@@ -408,7 +524,8 @@ function stepResolution(pitch, chord) {
|
|
|
408
524
|
}
|
|
409
525
|
return void 0;
|
|
410
526
|
}
|
|
411
|
-
function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat) {
|
|
527
|
+
function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat = () => []) {
|
|
528
|
+
assertNoteEvents(voice, "voice notes", { allowNonPositiveDuration: true });
|
|
412
529
|
const result = [];
|
|
413
530
|
for (let i = 0; i < voice.length; i += 1) {
|
|
414
531
|
const note = voice[i];
|
|
@@ -417,8 +534,10 @@ function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat) {
|
|
|
417
534
|
}
|
|
418
535
|
const prevNote = i > 0 ? voice[i - 1] : void 0;
|
|
419
536
|
const nextNote = i + 1 < voice.length ? voice[i + 1] : void 0;
|
|
420
|
-
const
|
|
421
|
-
const
|
|
537
|
+
const prevEnd = prevNote === void 0 ? Number.NEGATIVE_INFINITY : prevNote.startBeat + prevNote.durationBeat;
|
|
538
|
+
const noteEnd = note.startBeat + note.durationBeat;
|
|
539
|
+
const prev = prevNote !== void 0 && Math.abs(prevEnd - note.startBeat) <= EPS3 ? prevNote : void 0;
|
|
540
|
+
const next = nextNote !== void 0 && Math.abs(noteEnd - nextNote.startBeat) <= EPS3 ? nextNote : void 0;
|
|
422
541
|
const chord = chordAtBeat(note.startBeat);
|
|
423
542
|
const labels = [];
|
|
424
543
|
const member = isChordMember(note.pitch, chord);
|
|
@@ -443,10 +562,11 @@ function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat) {
|
|
|
443
562
|
);
|
|
444
563
|
if (prepared && verticallyDissonant && resolves) {
|
|
445
564
|
const ic = intervalAboveBass(note.pitch, others, chord);
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
565
|
+
const type = suspensionType(ic, next.pitch - note.pitch);
|
|
566
|
+
if (type !== null) {
|
|
567
|
+
labels.push({ kind: "suspension", type, resolveTo: next.pitch });
|
|
568
|
+
handled = true;
|
|
569
|
+
}
|
|
450
570
|
}
|
|
451
571
|
}
|
|
452
572
|
if (!handled && chord && prev && next) {
|
|
@@ -504,7 +624,18 @@ function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat) {
|
|
|
504
624
|
if (next && isLeadingToneResolution(note.pitch, next.pitch, key)) {
|
|
505
625
|
labels.push({ kind: "leadingTone", resolveTo: next.pitch });
|
|
506
626
|
}
|
|
507
|
-
|
|
627
|
+
const analyzed = {
|
|
628
|
+
noteId: note.id ?? i,
|
|
629
|
+
pitch: note.pitch,
|
|
630
|
+
startBeat: note.startBeat,
|
|
631
|
+
durationBeat: note.durationBeat,
|
|
632
|
+
labels,
|
|
633
|
+
rationale: describe(labels)
|
|
634
|
+
};
|
|
635
|
+
if (note.originalIndex !== void 0) {
|
|
636
|
+
analyzed.originalIndex = note.originalIndex;
|
|
637
|
+
}
|
|
638
|
+
result.push(analyzed);
|
|
508
639
|
}
|
|
509
640
|
return result;
|
|
510
641
|
}
|
|
@@ -542,50 +673,86 @@ function otherVoicesSounding(prepared, excludeTrack, excludeVoice, beat) {
|
|
|
542
673
|
const out = [];
|
|
543
674
|
for (let t = 0; t < prepared.length; t += 1) {
|
|
544
675
|
const track = prepared[t];
|
|
545
|
-
if (!track) {
|
|
676
|
+
if (!track || isPercussion(track.role)) {
|
|
546
677
|
continue;
|
|
547
678
|
}
|
|
548
679
|
for (let v = 0; v < track.voices.length; v += 1) {
|
|
549
680
|
if (t === excludeTrack && v === excludeVoice) {
|
|
550
681
|
continue;
|
|
551
682
|
}
|
|
552
|
-
const
|
|
553
|
-
if (
|
|
683
|
+
const note = track.voices[v]?.at(beat);
|
|
684
|
+
if (note === void 0) {
|
|
554
685
|
continue;
|
|
555
686
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const snap = { pitch: note.pitch };
|
|
561
|
-
if (note.prevPitch !== void 0) {
|
|
562
|
-
snap.prevPitch = note.prevPitch;
|
|
563
|
-
}
|
|
564
|
-
out.push(snap);
|
|
687
|
+
const snap = { pitch: note.pitch };
|
|
688
|
+
const previous = Math.abs(note.startBeat - beat) <= EPS2 ? note.prevPitch : note.pitch;
|
|
689
|
+
if (previous !== void 0) {
|
|
690
|
+
snap.prevPitch = previous;
|
|
565
691
|
}
|
|
692
|
+
out.push(snap);
|
|
566
693
|
}
|
|
567
694
|
}
|
|
568
695
|
return out;
|
|
569
696
|
}
|
|
697
|
+
function firstSegmentAfter(timeline, beat) {
|
|
698
|
+
let low = 0;
|
|
699
|
+
let high = timeline.segments.length;
|
|
700
|
+
while (low < high) {
|
|
701
|
+
const middle = Math.floor((low + high) / 2);
|
|
702
|
+
if ((timeline.segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat + EPS2) {
|
|
703
|
+
low = middle + 1;
|
|
704
|
+
} else {
|
|
705
|
+
high = middle;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return low;
|
|
709
|
+
}
|
|
570
710
|
function evaluationBeats(note, timeline) {
|
|
571
711
|
const noteEnd = note.startBeat + note.durationBeat;
|
|
572
712
|
const beats = [note.startBeat];
|
|
573
|
-
for (
|
|
574
|
-
|
|
575
|
-
|
|
713
|
+
for (let i = firstSegmentAfter(timeline, note.startBeat); i < timeline.segments.length; i += 1) {
|
|
714
|
+
const start = timeline.segments[i]?.startBeat;
|
|
715
|
+
if (start === void 0 || start >= noteEnd - EPS2) {
|
|
716
|
+
break;
|
|
576
717
|
}
|
|
718
|
+
beats.push(start);
|
|
577
719
|
}
|
|
578
720
|
return beats;
|
|
579
721
|
}
|
|
580
722
|
function analyzeArrangement(tracks, opts = {}) {
|
|
581
723
|
const ts = opts.ts ?? parseTimeSignature("4/4");
|
|
724
|
+
assertTimeSignature(ts);
|
|
725
|
+
const budget = opts.budget;
|
|
726
|
+
assertGenerationBudget(tracks.length, "arrangement tracks", budget);
|
|
727
|
+
let noteCount = 0;
|
|
728
|
+
for (let index = 0; index < tracks.length; index += 1) {
|
|
729
|
+
const notes = tracks[index]?.notes;
|
|
730
|
+
if (!Array.isArray(notes)) {
|
|
731
|
+
throw new InvalidInputError(
|
|
732
|
+
`tracks[${index}].notes must be an array; received ${typeof notes}`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
assertNoteEvents(notes, `tracks[${index}].notes`, {
|
|
736
|
+
allowNonPositiveDuration: true,
|
|
737
|
+
budget
|
|
738
|
+
});
|
|
739
|
+
noteCount += notes.length;
|
|
740
|
+
}
|
|
741
|
+
assertGenerationBudget(noteCount, "arrangement notes", budget);
|
|
582
742
|
const profile = opts.profile ?? "pop";
|
|
583
|
-
const
|
|
584
|
-
const
|
|
743
|
+
const minSeverity = opts.minSeverity ?? 1 /* Warning */;
|
|
744
|
+
const harmonyTracks = opts.harmonyTracks === void 0 ? void 0 : new Set(opts.harmonyTracks.map(Math.trunc));
|
|
745
|
+
const pooled = poolNotes(tracks, harmonyTracks);
|
|
746
|
+
const inferred = opts.timeline === void 0 ? chordTimelineFromNotes(pooled, {
|
|
585
747
|
key: opts.key,
|
|
586
748
|
ts,
|
|
587
749
|
harmonicRhythm: opts.harmonicRhythm
|
|
588
|
-
})
|
|
750
|
+
}) : {
|
|
751
|
+
timeline: opts.timeline,
|
|
752
|
+
key: opts.key ?? detectKeyFromNotes(pooled)[0]?.key ?? majorKey(0),
|
|
753
|
+
segmentConfidence: opts.timeline.segments.map(() => 1)
|
|
754
|
+
};
|
|
755
|
+
const { timeline, key, segmentConfidence } = inferred;
|
|
589
756
|
const cadences = detectCadences(timeline, key);
|
|
590
757
|
const prepared = prepareTracks(tracks);
|
|
591
758
|
const trackAnalyses = [];
|
|
@@ -595,41 +762,64 @@ function analyzeArrangement(tracks, opts = {}) {
|
|
|
595
762
|
if (!track) {
|
|
596
763
|
continue;
|
|
597
764
|
}
|
|
765
|
+
if (isPercussion(track.role)) {
|
|
766
|
+
trackAnalyses.push({ name: track.name, role: track.role, notes: [] });
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
598
769
|
const notes = [];
|
|
599
770
|
for (let v = 0; v < track.voices.length; v += 1) {
|
|
600
771
|
const subVoice = track.voices[v];
|
|
601
772
|
if (!subVoice) {
|
|
602
773
|
continue;
|
|
603
774
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
(beat) => otherVoicesSounding(prepared, t, v, beat)
|
|
610
|
-
)
|
|
775
|
+
const analyzed = analyzeVoice(
|
|
776
|
+
subVoice.voice,
|
|
777
|
+
timeline.at,
|
|
778
|
+
key,
|
|
779
|
+
(beat) => otherVoicesSounding(prepared, t, v, beat)
|
|
611
780
|
);
|
|
612
|
-
|
|
781
|
+
notes.push(...analyzed.map((note) => ({ ...note, trackIndex: track.trackIndex })));
|
|
782
|
+
const labelsById = new Map(analyzed.map((note) => [note.noteId, note.labels]));
|
|
783
|
+
for (let noteIndex = 0; noteIndex < subVoice.voice.length; noteIndex += 1) {
|
|
784
|
+
const note = subVoice.voice[noteIndex];
|
|
785
|
+
const preparedNote = subVoice.sounding[noteIndex];
|
|
786
|
+
if (note === void 0 || preparedNote === void 0) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
613
789
|
for (const beat of evaluationBeats(note, timeline)) {
|
|
790
|
+
const atOnset = Math.abs(beat - note.startBeat) <= EPS2;
|
|
614
791
|
const result = evaluateSafety({
|
|
615
792
|
profile,
|
|
616
793
|
candidatePitch: note.pitch,
|
|
794
|
+
prevPitch: atOnset ? preparedNote.prevPitch : note.pitch,
|
|
617
795
|
chord: timeline.at(beat),
|
|
618
796
|
key,
|
|
619
797
|
otherVoices: otherVoicesSounding(prepared, t, v, beat),
|
|
620
798
|
strongBeat: isStrongBeat(beat, ts)
|
|
621
799
|
});
|
|
622
|
-
if (result.safety !== 0 /* Safe */) {
|
|
800
|
+
if (result.safety !== 0 /* Safe */ && result.safety >= minSeverity) {
|
|
623
801
|
const conflict = {
|
|
624
802
|
beat,
|
|
625
803
|
trackName: track.name,
|
|
804
|
+
trackIndex: track.trackIndex,
|
|
805
|
+
noteId: note.id,
|
|
626
806
|
pitch: note.pitch,
|
|
807
|
+
labels: labelsById.get(note.id) ?? [],
|
|
627
808
|
safety: result.safety,
|
|
628
809
|
reasons: result.reasons
|
|
629
810
|
};
|
|
811
|
+
if (note.originalIndex !== void 0) {
|
|
812
|
+
conflict.originalIndex = note.originalIndex;
|
|
813
|
+
}
|
|
630
814
|
if (result.rationale !== void 0) {
|
|
631
815
|
conflict.rationale = result.rationale;
|
|
632
816
|
}
|
|
817
|
+
if (result.resolveTo !== void 0) {
|
|
818
|
+
conflict.resolveTo = result.resolveTo;
|
|
819
|
+
}
|
|
820
|
+
if (result.suggestions !== void 0) {
|
|
821
|
+
conflict.suggestions = [...result.suggestions];
|
|
822
|
+
}
|
|
633
823
|
conflicts.push(conflict);
|
|
634
824
|
}
|
|
635
825
|
}
|
|
@@ -647,7 +837,9 @@ export {
|
|
|
647
837
|
chordTimelineFromNotes,
|
|
648
838
|
detectCadences,
|
|
649
839
|
tensionCurve,
|
|
840
|
+
tensionCurveFrom,
|
|
841
|
+
toVoiceNotes,
|
|
650
842
|
analyzeVoice,
|
|
651
843
|
analyzeArrangement
|
|
652
844
|
};
|
|
653
|
-
//# sourceMappingURL=chunk-
|
|
845
|
+
//# sourceMappingURL=chunk-Q2XUP227.js.map
|