@libraz/libcantus 0.9.1 → 0.9.3
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 +19 -3
- package/dist/analyze/index.cjs +1970 -0
- package/dist/analyze/index.cjs.map +1 -0
- package/dist/analyze/index.d.cts +341 -0
- package/dist/analyze/index.d.ts +341 -0
- package/dist/analyze/index.js +51 -0
- package/dist/analyze/index.js.map +1 -0
- package/dist/chunk-2ZWMSJH6.js +30 -0
- package/dist/chunk-2ZWMSJH6.js.map +1 -0
- package/dist/chunk-4MHA432G.js +2854 -0
- package/dist/chunk-4MHA432G.js.map +1 -0
- package/dist/chunk-62QZKCJQ.js +49 -0
- package/dist/chunk-62QZKCJQ.js.map +1 -0
- package/dist/chunk-AKLYPWIN.js +83 -0
- package/dist/chunk-AKLYPWIN.js.map +1 -0
- package/dist/chunk-C5XYKUVG.js +904 -0
- package/dist/chunk-C5XYKUVG.js.map +1 -0
- package/dist/chunk-CHMXELDS.js +549 -0
- package/dist/chunk-CHMXELDS.js.map +1 -0
- package/dist/chunk-DYWFNC4A.js +152 -0
- package/dist/chunk-DYWFNC4A.js.map +1 -0
- package/dist/chunk-EH6EM37U.js +315 -0
- package/dist/chunk-EH6EM37U.js.map +1 -0
- package/dist/chunk-FBQK3ZQN.js +131 -0
- package/dist/chunk-FBQK3ZQN.js.map +1 -0
- package/dist/chunk-GOAW4VYL.js +719 -0
- package/dist/chunk-GOAW4VYL.js.map +1 -0
- package/dist/chunk-I5R7ZBJS.js +202 -0
- package/dist/chunk-I5R7ZBJS.js.map +1 -0
- package/dist/chunk-IT2O4XFE.js +154 -0
- package/dist/chunk-IT2O4XFE.js.map +1 -0
- package/dist/chunk-MIWO425M.js +412 -0
- package/dist/chunk-MIWO425M.js.map +1 -0
- package/dist/chunk-N6IHQUOX.js +1 -0
- package/dist/chunk-N6IHQUOX.js.map +1 -0
- package/dist/chunk-SGU7SPLK.js +924 -0
- package/dist/chunk-SGU7SPLK.js.map +1 -0
- package/dist/core/index.cjs +648 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +262 -0
- package/dist/core/index.d.ts +262 -0
- package/dist/core/index.js +93 -0
- package/dist/core/index.js.map +1 -0
- package/dist/function-DVH4Eusr.d.ts +187 -0
- package/dist/function-DYt4iKsn.d.cts +187 -0
- package/dist/generate/index.cjs +4123 -0
- package/dist/generate/index.cjs.map +1 -0
- package/dist/generate/index.d.cts +986 -0
- package/dist/generate/index.d.ts +986 -0
- package/dist/generate/index.js +52 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/index-BNlGP86A.d.cts +157 -0
- package/dist/index-Bg5dYMmZ.d.ts +56 -0
- package/dist/index-Bt4tOUD9.d.ts +157 -0
- package/dist/index-CGMZNCx6.d.cts +140 -0
- package/dist/index-CGMZNCx6.d.ts +140 -0
- package/dist/index-Cl7mhOFM.d.cts +164 -0
- package/dist/index-Cl7mhOFM.d.ts +164 -0
- package/dist/index-CnZCo29u.d.cts +108 -0
- package/dist/index-CpOsP1y9.d.cts +151 -0
- package/dist/index-CxqBQtH1.d.cts +128 -0
- package/dist/index-Dikst44v.d.cts +56 -0
- package/dist/index-DxvARIP2.d.ts +151 -0
- package/dist/index-Jc0Ryi79.d.ts +128 -0
- package/dist/index-JnF4v9bx.d.ts +108 -0
- package/dist/index.cjs +3710 -3143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -3982
- package/dist/index.d.ts +15 -3982
- package/dist/index.js +191 -6504
- package/dist/index.js.map +1 -1
- package/dist/model/index.cjs +2574 -0
- package/dist/model/index.cjs.map +1 -0
- package/dist/model/index.d.cts +631 -0
- package/dist/model/index.d.ts +631 -0
- package/dist/model/index.js +21 -0
- package/dist/model/index.js.map +1 -0
- package/dist/styled-DZ7GjShb.d.ts +282 -0
- package/dist/styled-ZTbdaVFM.d.cts +282 -0
- package/dist/theory/index.cjs +1823 -0
- package/dist/theory/index.cjs.map +1 -0
- package/dist/theory/index.d.cts +535 -0
- package/dist/theory/index.d.ts +535 -0
- package/dist/theory/index.js +150 -0
- package/dist/theory/index.js.map +1 -0
- package/dist/types-MGP29r3t.d.cts +30 -0
- package/dist/types-MGP29r3t.d.ts +30 -0
- package/package.json +61 -5
- package/src/analyze/arrange/index.ts +21 -0
- package/src/analyze/arrange/internal.ts +127 -0
- package/src/analyze/arrange/tension.ts +199 -0
- package/src/analyze/arrange/tracks.ts +312 -0
- package/src/{detect → analyze/detect}/index.ts +50 -13
- package/src/analyze/functional/borrowed.ts +84 -0
- package/src/analyze/functional/cadence.ts +66 -0
- package/src/analyze/functional/function.ts +184 -0
- package/src/analyze/functional/index.ts +23 -0
- package/src/analyze/functional/internal.ts +29 -0
- package/src/{functional/index.ts → analyze/functional/roman.ts} +23 -332
- package/src/analyze/index.ts +49 -0
- package/src/{timeline → analyze/timeline}/index.ts +50 -18
- package/src/{analysis → analyze/voice}/index.ts +19 -7
- package/src/core/event-index/index.ts +104 -0
- package/src/core/index.ts +61 -0
- package/src/{meter → core/meter}/index.ts +24 -4
- package/src/{random → core/random}/index.ts +26 -3
- package/src/{tuning → core/tuning}/index.ts +36 -6
- package/src/core/validation/index.ts +119 -0
- package/src/{bass → generate/bass}/index.ts +28 -5
- package/src/{countermelody → generate/countermelody}/index.ts +176 -85
- package/src/{drums → generate/drums}/euclid.ts +10 -3
- package/src/{drums → generate/drums}/index.ts +58 -26
- package/src/{drums → generate/drums}/percussion.ts +1 -1
- package/src/{drums → generate/drums}/rng.ts +2 -2
- package/src/{groove → generate/groove}/index.ts +28 -3
- package/src/{harmonize → generate/harmonize}/index.ts +68 -57
- package/src/generate/index.ts +58 -0
- package/src/{motif → generate/motif}/index.ts +52 -28
- package/src/{progression → generate/progression}/index.ts +19 -41
- package/src/{reharmony → generate/reharmony}/index.ts +12 -19
- package/src/{rhythm → generate/rhythm}/index.ts +13 -3
- package/src/index.ts +13 -234
- package/src/model/chord.ts +442 -0
- package/src/model/index.ts +5 -1096
- package/src/model/interval.ts +82 -0
- package/src/model/key.ts +266 -0
- package/src/model/note.ts +176 -0
- package/src/model/progression.ts +161 -0
- package/src/model/shared.ts +12 -0
- package/src/{chord → theory/chord}/index.ts +23 -3
- package/src/{counterpoint → theory/counterpoint}/index.ts +2 -2
- package/src/{harmony → theory/harmony}/index.ts +6 -21
- package/src/theory/index.ts +112 -0
- package/src/{safety → theory/safety}/index.ts +72 -3
- package/src/theory/scale/degrees.ts +123 -0
- package/src/theory/scale/index.ts +28 -0
- package/src/theory/scale/key.ts +56 -0
- package/src/theory/scale/masks.ts +153 -0
- package/src/{spelling → theory/spelling}/index.ts +73 -6
- package/src/{symbol → theory/symbol}/index.ts +8 -1
- package/src/theory/voicing/index.ts +5 -0
- package/src/theory/voicing/internal.ts +168 -0
- package/src/theory/voicing/leading.ts +112 -0
- package/src/theory/voicing/satb.ts +216 -0
- package/src/theory/voicing/styled.ts +199 -0
- package/src/arrange/index.ts +0 -571
- package/src/scale/index.ts +0 -331
- package/src/voicing/index.ts +0 -623
- /package/src/{interval → core/interval}/index.ts +0 -0
- /package/src/{pitch → core/pitch}/index.ts +0 -0
- /package/src/{types.ts → core/types.ts} +0 -0
- /package/src/{drums → generate/drums}/beat.ts +0 -0
- /package/src/{drums → generate/drums}/fills.ts +0 -0
- /package/src/{drums → generate/drums}/ghost.ts +0 -0
- /package/src/{drums → generate/drums}/hihat.ts +0 -0
- /package/src/{drums → generate/drums}/hit.ts +0 -0
- /package/src/{drums → generate/drums}/internal.ts +0 -0
- /package/src/{drums → generate/drums}/kick.ts +0 -0
- /package/src/{drums → generate/drums}/swing.ts +0 -0
- /package/src/{chordscale → theory/chordscale}/index.ts +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { T as TimeSignature } from './index-Cl7mhOFM.cjs';
|
|
2
|
+
import { K as KeyScale, N as NoteEvent } from './types-MGP29r3t.cjs';
|
|
3
|
+
import { C as Chord, b as ChordSpan } from './index-BNlGP86A.cjs';
|
|
4
|
+
import { C as Cadence } from './function-DYt4iKsn.cjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A chord occupying a half-open beat span.
|
|
8
|
+
*
|
|
9
|
+
* @category Arrangement & Analysis
|
|
10
|
+
*/
|
|
11
|
+
type ChordSegment = {
|
|
12
|
+
startBeat: number;
|
|
13
|
+
endBeat: number;
|
|
14
|
+
chord: Chord;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* A beat-indexed sequence of chord segments.
|
|
18
|
+
*
|
|
19
|
+
* @category Arrangement & Analysis
|
|
20
|
+
*/
|
|
21
|
+
type ChordTimeline = {
|
|
22
|
+
/** The chord sounding at a beat, or null when no segment covers it. */
|
|
23
|
+
at: (beat: number) => Chord | null;
|
|
24
|
+
segments: ChordSegment[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Build a chord timeline from placed chords.
|
|
28
|
+
*
|
|
29
|
+
* Each chord spans from its `startBeat` to the next chord's `startBeat`; the last
|
|
30
|
+
* chord runs to `totalBeats`. Segments with no positive length (from duplicate
|
|
31
|
+
* onsets or a last chord at or past `totalBeats`) are dropped. `at(beat)` returns
|
|
32
|
+
* the covering segment's chord, or null when the beat lies outside every segment.
|
|
33
|
+
*
|
|
34
|
+
* @param chords Placed chords in time order.
|
|
35
|
+
* @param totalBeats End of the timeline in beats.
|
|
36
|
+
* @returns A queryable chord timeline.
|
|
37
|
+
* @category Arrangement & Analysis
|
|
38
|
+
*/
|
|
39
|
+
declare function chordTimelineFromChords(chords: ChordSpan[], totalBeats: number): ChordTimeline;
|
|
40
|
+
/**
|
|
41
|
+
* Options controlling {@link chordTimelineFromNotes}.
|
|
42
|
+
*
|
|
43
|
+
* @category Arrangement & Analysis
|
|
44
|
+
*/
|
|
45
|
+
type ChordTimelineOptions = {
|
|
46
|
+
/** Key context; inferred from the notes with {@link detectKey} when omitted. */
|
|
47
|
+
key?: KeyScale;
|
|
48
|
+
/**
|
|
49
|
+
* Time signature used for metric accents; defaults to 4/4.
|
|
50
|
+
*
|
|
51
|
+
* @defaultValue `4/4`
|
|
52
|
+
*/
|
|
53
|
+
ts?: TimeSignature;
|
|
54
|
+
/**
|
|
55
|
+
* Window length in beats per chord slot; defaults to one bar of `ts`.
|
|
56
|
+
*
|
|
57
|
+
* @defaultValue one bar of `ts`
|
|
58
|
+
*/
|
|
59
|
+
harmonicRhythm?: number;
|
|
60
|
+
/**
|
|
61
|
+
* End of the analyzed span in beats; defaults to the end of the last note.
|
|
62
|
+
*
|
|
63
|
+
* @defaultValue the end of the last note
|
|
64
|
+
*/
|
|
65
|
+
totalBeats?: number;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* The result of {@link chordTimelineFromNotes}.
|
|
69
|
+
*
|
|
70
|
+
* @category Arrangement & Analysis
|
|
71
|
+
*/
|
|
72
|
+
type ChordTimelineResult = {
|
|
73
|
+
/** The inferred timeline, with adjacent identical chords merged. */
|
|
74
|
+
timeline: ChordTimeline;
|
|
75
|
+
/** The key used for the analysis (given or inferred). */
|
|
76
|
+
key: KeyScale;
|
|
77
|
+
/** One confidence value in [0, 1] per segment, in segment order. */
|
|
78
|
+
segmentConfidence: number[];
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Infer a chord timeline from raw multi-track notes.
|
|
82
|
+
*
|
|
83
|
+
* The span `[0, totalBeats)` is sliced into `harmonicRhythm`-beat windows; each
|
|
84
|
+
* window's chord is inferred from a pitch-class weight histogram of the notes
|
|
85
|
+
* overlapping it (weight = overlap duration x velocity x metric-accent bonus
|
|
86
|
+
* for onsets in the window). Adjacent windows carrying the identical chord are
|
|
87
|
+
* merged into one segment; windows with no notes produce no segment, so
|
|
88
|
+
* `at(beat)` returns null there. Each segment carries a confidence in [0, 1]:
|
|
89
|
+
* the fraction of the window weight explained by chord tones, reduced when the
|
|
90
|
+
* match is inexact, and duration-weighted across merged windows.
|
|
91
|
+
*
|
|
92
|
+
* Notes with a zero or negative duration never sound, so they are dropped at
|
|
93
|
+
* ingest: they contribute to neither the key inference, the span, nor any
|
|
94
|
+
* window's histogram.
|
|
95
|
+
*
|
|
96
|
+
* @param notes The notes to analyze (any number of tracks, flattened).
|
|
97
|
+
* @param opts Analysis options; see {@link ChordTimelineOptions}.
|
|
98
|
+
* @returns The inferred timeline, the key used, and per-segment confidences.
|
|
99
|
+
* @throws If `harmonicRhythm` is not positive.
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* import { chordTimelineFromNotes } from '@libraz/libcantus';
|
|
103
|
+
* const notes = [
|
|
104
|
+
* { pitch: 60, startBeat: 0, durationBeat: 2 }, // C
|
|
105
|
+
* { pitch: 64, startBeat: 0, durationBeat: 2 }, // E
|
|
106
|
+
* { pitch: 67, startBeat: 0, durationBeat: 2 }, // G
|
|
107
|
+
* ];
|
|
108
|
+
* const { timeline, key } = chordTimelineFromNotes(notes);
|
|
109
|
+
* timeline.at(0); // the chord inferred over beat 0, or null
|
|
110
|
+
* ```
|
|
111
|
+
* @category Arrangement & Analysis
|
|
112
|
+
*/
|
|
113
|
+
declare function chordTimelineFromNotes(notes: NoteEvent[], opts?: ChordTimelineOptions): ChordTimelineResult;
|
|
114
|
+
/**
|
|
115
|
+
* A cadence found between two consecutive timeline segments.
|
|
116
|
+
*
|
|
117
|
+
* @category Arrangement & Analysis
|
|
118
|
+
*/
|
|
119
|
+
type CadenceHit = {
|
|
120
|
+
/** The beat where the cadence arrives (the second chord's onset). */
|
|
121
|
+
atBeat: number;
|
|
122
|
+
type: Exclude<Cadence, null>;
|
|
123
|
+
from: Chord;
|
|
124
|
+
to: Chord;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Find cadences between consecutive segments of a chord timeline.
|
|
128
|
+
*
|
|
129
|
+
* Each temporally adjacent segment pair is classified with
|
|
130
|
+
* {@link detectCadence}; pairs forming no cadence are skipped. Segments
|
|
131
|
+
* separated by a gap (a rest in the timeline) are not a chord-to-chord
|
|
132
|
+
* progression, so they are never paired.
|
|
133
|
+
*
|
|
134
|
+
* @param timeline The chord timeline to scan.
|
|
135
|
+
* @param key The prevailing key.
|
|
136
|
+
* @returns The cadences found, in time order.
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';
|
|
140
|
+
* const notes = [
|
|
141
|
+
* { pitch: 67, startBeat: 0, durationBeat: 4 }, // G, a dominant
|
|
142
|
+
* { pitch: 60, startBeat: 4, durationBeat: 4 }, // C, the tonic
|
|
143
|
+
* ];
|
|
144
|
+
* const { timeline, key } = chordTimelineFromNotes(notes);
|
|
145
|
+
* const cadences = detectCadences(timeline, key);
|
|
146
|
+
* ```
|
|
147
|
+
* @category Arrangement & Analysis
|
|
148
|
+
*/
|
|
149
|
+
declare function detectCadences(timeline: ChordTimeline, key: KeyScale): CadenceHit[];
|
|
150
|
+
|
|
151
|
+
export { type CadenceHit as C, type ChordSegment as a, type ChordTimeline as b, type ChordTimelineOptions as c, type ChordTimelineResult as d, chordTimelineFromChords as e, chordTimelineFromNotes as f, detectCadences as g };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { K as KeyScale } from './types-MGP29r3t.cjs';
|
|
2
|
+
import { C as Chord } from './index-BNlGP86A.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Severity policy: `strict` counterpoint vs. lenient `pop` voice-leading.
|
|
6
|
+
*
|
|
7
|
+
* @category Arrangement & Analysis
|
|
8
|
+
*/
|
|
9
|
+
type SafetyProfile = 'strict' | 'pop';
|
|
10
|
+
/**
|
|
11
|
+
* Overall placeability verdict for a candidate pitch.
|
|
12
|
+
*
|
|
13
|
+
* @category Arrangement & Analysis
|
|
14
|
+
*/
|
|
15
|
+
declare enum NoteSafety {
|
|
16
|
+
Safe = 0,
|
|
17
|
+
Warning = 1,
|
|
18
|
+
Dissonant = 2
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Bit flags describing why a pitch received its verdict.
|
|
22
|
+
*
|
|
23
|
+
* @category Arrangement & Analysis
|
|
24
|
+
*/
|
|
25
|
+
declare enum ReasonFlag {
|
|
26
|
+
ChordTone = 1,
|
|
27
|
+
Tension = 2,
|
|
28
|
+
AvoidNote = 4,
|
|
29
|
+
ScaleTone = 8,
|
|
30
|
+
NonScale = 16,
|
|
31
|
+
OutOfRange = 32,
|
|
32
|
+
Tritone = 64,
|
|
33
|
+
LargeLeap = 128,
|
|
34
|
+
MinorSecond = 256,
|
|
35
|
+
MajorSeventh = 512,
|
|
36
|
+
VerticalDissonance = 1024,
|
|
37
|
+
ParallelPerfect = 2048,
|
|
38
|
+
HiddenParallel = 4096,
|
|
39
|
+
VoiceCrossing = 8192,
|
|
40
|
+
Suspension = 16384,
|
|
41
|
+
NeedsResolution = 32768
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A neighbouring voice's current and previous pitch.
|
|
45
|
+
*
|
|
46
|
+
* @category Arrangement & Analysis
|
|
47
|
+
*/
|
|
48
|
+
type VoiceSnapshot = {
|
|
49
|
+
pitch: number;
|
|
50
|
+
prevPitch?: number;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Everything needed to evaluate one candidate pitch.
|
|
54
|
+
*
|
|
55
|
+
* @category Arrangement & Analysis
|
|
56
|
+
*/
|
|
57
|
+
type SafetyQuery = {
|
|
58
|
+
profile: SafetyProfile;
|
|
59
|
+
candidatePitch: number;
|
|
60
|
+
prevPitch?: number;
|
|
61
|
+
chord: Chord | null;
|
|
62
|
+
key: KeyScale;
|
|
63
|
+
otherVoices: VoiceSnapshot[];
|
|
64
|
+
strongBeat: boolean;
|
|
65
|
+
vocalLow?: number;
|
|
66
|
+
vocalHigh?: number;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The verdict, reason flags, and optional resolution guidance.
|
|
70
|
+
*
|
|
71
|
+
* @category Arrangement & Analysis
|
|
72
|
+
*/
|
|
73
|
+
type SafetyResult = {
|
|
74
|
+
safety: NoteSafety;
|
|
75
|
+
reasons: number;
|
|
76
|
+
resolveTo?: number;
|
|
77
|
+
suggestions?: number[];
|
|
78
|
+
rationale?: string;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Evaluate the safety of placing a candidate pitch in a voice.
|
|
82
|
+
*
|
|
83
|
+
* Reasons are merged from harmonic context (chord tone / tension / avoid / scale)
|
|
84
|
+
* and counterpoint (vertical dissonance, parallels, voice crossing). The `strict`
|
|
85
|
+
* profile treats avoid notes and parallels as dissonant; `pop` treats them as
|
|
86
|
+
* warnings. The final verdict is the worst severity among the reasons.
|
|
87
|
+
*
|
|
88
|
+
* @param q The candidate and its harmonic/voice-leading context.
|
|
89
|
+
* @returns The verdict, reason bitmask, and optional resolution guidance.
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* import { evaluateSafety, makeChord, majorKey, NoteSafety } from '@libraz/libcantus';
|
|
93
|
+
* const result = evaluateSafety({
|
|
94
|
+
* profile: 'pop',
|
|
95
|
+
* candidatePitch: 60, // C over a C major chord
|
|
96
|
+
* chord: makeChord(0, 'maj'),
|
|
97
|
+
* key: majorKey(0),
|
|
98
|
+
* otherVoices: [],
|
|
99
|
+
* strongBeat: true,
|
|
100
|
+
* });
|
|
101
|
+
* result.safety === NoteSafety.Safe; // true — C is a chord tone
|
|
102
|
+
* ```
|
|
103
|
+
* @category Arrangement & Analysis
|
|
104
|
+
*/
|
|
105
|
+
declare function evaluateSafety(q: SafetyQuery): SafetyResult;
|
|
106
|
+
/**
|
|
107
|
+
* Enumerate placeable pitches in a range, chord tones first, descending.
|
|
108
|
+
*
|
|
109
|
+
* @param q The safety context minus the candidate pitch.
|
|
110
|
+
* @param pitchLow Lowest MIDI pitch to consider (inclusive).
|
|
111
|
+
* @param pitchHigh Highest MIDI pitch to consider (inclusive).
|
|
112
|
+
* @returns Placeable pitches (non-dissonant), chord tones before others, each group descending.
|
|
113
|
+
* @throws If either bound is non-finite/non-integral, reversed, or exceeds the generation budget.
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { enumerateSafePitches, makeChord, majorKey } from '@libraz/libcantus';
|
|
117
|
+
* const pitches = enumerateSafePitches(
|
|
118
|
+
* { profile: 'pop', chord: makeChord(0, 'maj'), key: majorKey(0), otherVoices: [], strongBeat: true },
|
|
119
|
+
* 60,
|
|
120
|
+
* 72,
|
|
121
|
+
* );
|
|
122
|
+
* pitches; // placeable pitches in [60, 72], chord tones first, each group descending
|
|
123
|
+
* ```
|
|
124
|
+
* @category Arrangement & Analysis
|
|
125
|
+
*/
|
|
126
|
+
declare function enumerateSafePitches(q: Omit<SafetyQuery, 'candidatePitch'>, pitchLow: number, pitchHigh: number): number[];
|
|
127
|
+
|
|
128
|
+
export { NoteSafety as N, ReasonFlag as R, type SafetyProfile as S, type VoiceSnapshot as V, type SafetyQuery as a, type SafetyResult as b, evaluateSafety as c, enumerateSafePitches as e };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { C as Chord } from './index-BNlGP86A.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Harmonic role a pitch plays within a chord.
|
|
5
|
+
*
|
|
6
|
+
* @category Functional Harmony
|
|
7
|
+
*/
|
|
8
|
+
type HarmonyRole = 'root' | 'third' | 'fifth' | 'sixth' | 'seventh' | 'tension' | 'doubling';
|
|
9
|
+
/**
|
|
10
|
+
* How firmly a pitch is locked to the chord's identity.
|
|
11
|
+
*
|
|
12
|
+
* - `identity`: moving it produces a different chord (the root).
|
|
13
|
+
* - `quality`: moving it flips the chord quality (the third).
|
|
14
|
+
* - `voicing`: it can move freely without changing chord identity or quality.
|
|
15
|
+
*
|
|
16
|
+
* @category Functional Harmony
|
|
17
|
+
*/
|
|
18
|
+
type LockLevel = 'identity' | 'quality' | 'voicing';
|
|
19
|
+
/**
|
|
20
|
+
* A pitch's role, lock level, and owning chord.
|
|
21
|
+
*
|
|
22
|
+
* @category Functional Harmony
|
|
23
|
+
*/
|
|
24
|
+
type VoicedRole = {
|
|
25
|
+
role: HarmonyRole;
|
|
26
|
+
lock: LockLevel;
|
|
27
|
+
belongsToChordId: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Classify a pitch's harmonic role and lock level within a chord.
|
|
31
|
+
*
|
|
32
|
+
* The role comes from the pitch's interval class above the chord root: root (0),
|
|
33
|
+
* third (3/4), fifth (6/7/8), seventh (10/11), and 9/11/13 tensions (2/5/9, and
|
|
34
|
+
* the flat ninth 1). The root locks the chord identity, the third locks its
|
|
35
|
+
* quality, and everything else is free voicing. In a suspended chord (a chord
|
|
36
|
+
* whose intervals include a 4th (5) or 2nd (2) but no third), the suspended tone
|
|
37
|
+
* takes the third's place as the quality-defining tone and is locked to
|
|
38
|
+
* `quality`, since moving it changes the chord. Detecting an octave doubling
|
|
39
|
+
* requires the surrounding voicing, which this single-pitch query does not carry,
|
|
40
|
+
* so `doubling` is part of the type but not returned here.
|
|
41
|
+
*
|
|
42
|
+
* @param pitch MIDI pitch or bare pitch class.
|
|
43
|
+
* @param chord The chord providing the root reference.
|
|
44
|
+
* @param chordId Identifier stored on the result (defaults to 0).
|
|
45
|
+
* @returns The pitch's role, lock level, and owning chord id.
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { roleOf, makeChord } from '@libraz/libcantus';
|
|
49
|
+
* roleOf(64, makeChord(0, 'maj')); // E over C major
|
|
50
|
+
* // { role: 'third', lock: 'quality', belongsToChordId: 0 }
|
|
51
|
+
* ```
|
|
52
|
+
* @category Functional Harmony
|
|
53
|
+
*/
|
|
54
|
+
declare function roleOf(pitch: number, chord: Chord, chordId?: number): VoicedRole;
|
|
55
|
+
|
|
56
|
+
export { type HarmonyRole as H, type LockLevel as L, type VoicedRole as V, roleOf as r };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { T as TimeSignature } from './index-Cl7mhOFM.js';
|
|
2
|
+
import { K as KeyScale, N as NoteEvent } from './types-MGP29r3t.js';
|
|
3
|
+
import { C as Chord, b as ChordSpan } from './index-Bt4tOUD9.js';
|
|
4
|
+
import { C as Cadence } from './function-DVH4Eusr.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A chord occupying a half-open beat span.
|
|
8
|
+
*
|
|
9
|
+
* @category Arrangement & Analysis
|
|
10
|
+
*/
|
|
11
|
+
type ChordSegment = {
|
|
12
|
+
startBeat: number;
|
|
13
|
+
endBeat: number;
|
|
14
|
+
chord: Chord;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* A beat-indexed sequence of chord segments.
|
|
18
|
+
*
|
|
19
|
+
* @category Arrangement & Analysis
|
|
20
|
+
*/
|
|
21
|
+
type ChordTimeline = {
|
|
22
|
+
/** The chord sounding at a beat, or null when no segment covers it. */
|
|
23
|
+
at: (beat: number) => Chord | null;
|
|
24
|
+
segments: ChordSegment[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Build a chord timeline from placed chords.
|
|
28
|
+
*
|
|
29
|
+
* Each chord spans from its `startBeat` to the next chord's `startBeat`; the last
|
|
30
|
+
* chord runs to `totalBeats`. Segments with no positive length (from duplicate
|
|
31
|
+
* onsets or a last chord at or past `totalBeats`) are dropped. `at(beat)` returns
|
|
32
|
+
* the covering segment's chord, or null when the beat lies outside every segment.
|
|
33
|
+
*
|
|
34
|
+
* @param chords Placed chords in time order.
|
|
35
|
+
* @param totalBeats End of the timeline in beats.
|
|
36
|
+
* @returns A queryable chord timeline.
|
|
37
|
+
* @category Arrangement & Analysis
|
|
38
|
+
*/
|
|
39
|
+
declare function chordTimelineFromChords(chords: ChordSpan[], totalBeats: number): ChordTimeline;
|
|
40
|
+
/**
|
|
41
|
+
* Options controlling {@link chordTimelineFromNotes}.
|
|
42
|
+
*
|
|
43
|
+
* @category Arrangement & Analysis
|
|
44
|
+
*/
|
|
45
|
+
type ChordTimelineOptions = {
|
|
46
|
+
/** Key context; inferred from the notes with {@link detectKey} when omitted. */
|
|
47
|
+
key?: KeyScale;
|
|
48
|
+
/**
|
|
49
|
+
* Time signature used for metric accents; defaults to 4/4.
|
|
50
|
+
*
|
|
51
|
+
* @defaultValue `4/4`
|
|
52
|
+
*/
|
|
53
|
+
ts?: TimeSignature;
|
|
54
|
+
/**
|
|
55
|
+
* Window length in beats per chord slot; defaults to one bar of `ts`.
|
|
56
|
+
*
|
|
57
|
+
* @defaultValue one bar of `ts`
|
|
58
|
+
*/
|
|
59
|
+
harmonicRhythm?: number;
|
|
60
|
+
/**
|
|
61
|
+
* End of the analyzed span in beats; defaults to the end of the last note.
|
|
62
|
+
*
|
|
63
|
+
* @defaultValue the end of the last note
|
|
64
|
+
*/
|
|
65
|
+
totalBeats?: number;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* The result of {@link chordTimelineFromNotes}.
|
|
69
|
+
*
|
|
70
|
+
* @category Arrangement & Analysis
|
|
71
|
+
*/
|
|
72
|
+
type ChordTimelineResult = {
|
|
73
|
+
/** The inferred timeline, with adjacent identical chords merged. */
|
|
74
|
+
timeline: ChordTimeline;
|
|
75
|
+
/** The key used for the analysis (given or inferred). */
|
|
76
|
+
key: KeyScale;
|
|
77
|
+
/** One confidence value in [0, 1] per segment, in segment order. */
|
|
78
|
+
segmentConfidence: number[];
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Infer a chord timeline from raw multi-track notes.
|
|
82
|
+
*
|
|
83
|
+
* The span `[0, totalBeats)` is sliced into `harmonicRhythm`-beat windows; each
|
|
84
|
+
* window's chord is inferred from a pitch-class weight histogram of the notes
|
|
85
|
+
* overlapping it (weight = overlap duration x velocity x metric-accent bonus
|
|
86
|
+
* for onsets in the window). Adjacent windows carrying the identical chord are
|
|
87
|
+
* merged into one segment; windows with no notes produce no segment, so
|
|
88
|
+
* `at(beat)` returns null there. Each segment carries a confidence in [0, 1]:
|
|
89
|
+
* the fraction of the window weight explained by chord tones, reduced when the
|
|
90
|
+
* match is inexact, and duration-weighted across merged windows.
|
|
91
|
+
*
|
|
92
|
+
* Notes with a zero or negative duration never sound, so they are dropped at
|
|
93
|
+
* ingest: they contribute to neither the key inference, the span, nor any
|
|
94
|
+
* window's histogram.
|
|
95
|
+
*
|
|
96
|
+
* @param notes The notes to analyze (any number of tracks, flattened).
|
|
97
|
+
* @param opts Analysis options; see {@link ChordTimelineOptions}.
|
|
98
|
+
* @returns The inferred timeline, the key used, and per-segment confidences.
|
|
99
|
+
* @throws If `harmonicRhythm` is not positive.
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* import { chordTimelineFromNotes } from '@libraz/libcantus';
|
|
103
|
+
* const notes = [
|
|
104
|
+
* { pitch: 60, startBeat: 0, durationBeat: 2 }, // C
|
|
105
|
+
* { pitch: 64, startBeat: 0, durationBeat: 2 }, // E
|
|
106
|
+
* { pitch: 67, startBeat: 0, durationBeat: 2 }, // G
|
|
107
|
+
* ];
|
|
108
|
+
* const { timeline, key } = chordTimelineFromNotes(notes);
|
|
109
|
+
* timeline.at(0); // the chord inferred over beat 0, or null
|
|
110
|
+
* ```
|
|
111
|
+
* @category Arrangement & Analysis
|
|
112
|
+
*/
|
|
113
|
+
declare function chordTimelineFromNotes(notes: NoteEvent[], opts?: ChordTimelineOptions): ChordTimelineResult;
|
|
114
|
+
/**
|
|
115
|
+
* A cadence found between two consecutive timeline segments.
|
|
116
|
+
*
|
|
117
|
+
* @category Arrangement & Analysis
|
|
118
|
+
*/
|
|
119
|
+
type CadenceHit = {
|
|
120
|
+
/** The beat where the cadence arrives (the second chord's onset). */
|
|
121
|
+
atBeat: number;
|
|
122
|
+
type: Exclude<Cadence, null>;
|
|
123
|
+
from: Chord;
|
|
124
|
+
to: Chord;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Find cadences between consecutive segments of a chord timeline.
|
|
128
|
+
*
|
|
129
|
+
* Each temporally adjacent segment pair is classified with
|
|
130
|
+
* {@link detectCadence}; pairs forming no cadence are skipped. Segments
|
|
131
|
+
* separated by a gap (a rest in the timeline) are not a chord-to-chord
|
|
132
|
+
* progression, so they are never paired.
|
|
133
|
+
*
|
|
134
|
+
* @param timeline The chord timeline to scan.
|
|
135
|
+
* @param key The prevailing key.
|
|
136
|
+
* @returns The cadences found, in time order.
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';
|
|
140
|
+
* const notes = [
|
|
141
|
+
* { pitch: 67, startBeat: 0, durationBeat: 4 }, // G, a dominant
|
|
142
|
+
* { pitch: 60, startBeat: 4, durationBeat: 4 }, // C, the tonic
|
|
143
|
+
* ];
|
|
144
|
+
* const { timeline, key } = chordTimelineFromNotes(notes);
|
|
145
|
+
* const cadences = detectCadences(timeline, key);
|
|
146
|
+
* ```
|
|
147
|
+
* @category Arrangement & Analysis
|
|
148
|
+
*/
|
|
149
|
+
declare function detectCadences(timeline: ChordTimeline, key: KeyScale): CadenceHit[];
|
|
150
|
+
|
|
151
|
+
export { type CadenceHit as C, type ChordSegment as a, type ChordTimeline as b, type ChordTimelineOptions as c, type ChordTimelineResult as d, chordTimelineFromChords as e, chordTimelineFromNotes as f, detectCadences as g };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { K as KeyScale } from './types-MGP29r3t.js';
|
|
2
|
+
import { C as Chord } from './index-Bt4tOUD9.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Severity policy: `strict` counterpoint vs. lenient `pop` voice-leading.
|
|
6
|
+
*
|
|
7
|
+
* @category Arrangement & Analysis
|
|
8
|
+
*/
|
|
9
|
+
type SafetyProfile = 'strict' | 'pop';
|
|
10
|
+
/**
|
|
11
|
+
* Overall placeability verdict for a candidate pitch.
|
|
12
|
+
*
|
|
13
|
+
* @category Arrangement & Analysis
|
|
14
|
+
*/
|
|
15
|
+
declare enum NoteSafety {
|
|
16
|
+
Safe = 0,
|
|
17
|
+
Warning = 1,
|
|
18
|
+
Dissonant = 2
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Bit flags describing why a pitch received its verdict.
|
|
22
|
+
*
|
|
23
|
+
* @category Arrangement & Analysis
|
|
24
|
+
*/
|
|
25
|
+
declare enum ReasonFlag {
|
|
26
|
+
ChordTone = 1,
|
|
27
|
+
Tension = 2,
|
|
28
|
+
AvoidNote = 4,
|
|
29
|
+
ScaleTone = 8,
|
|
30
|
+
NonScale = 16,
|
|
31
|
+
OutOfRange = 32,
|
|
32
|
+
Tritone = 64,
|
|
33
|
+
LargeLeap = 128,
|
|
34
|
+
MinorSecond = 256,
|
|
35
|
+
MajorSeventh = 512,
|
|
36
|
+
VerticalDissonance = 1024,
|
|
37
|
+
ParallelPerfect = 2048,
|
|
38
|
+
HiddenParallel = 4096,
|
|
39
|
+
VoiceCrossing = 8192,
|
|
40
|
+
Suspension = 16384,
|
|
41
|
+
NeedsResolution = 32768
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A neighbouring voice's current and previous pitch.
|
|
45
|
+
*
|
|
46
|
+
* @category Arrangement & Analysis
|
|
47
|
+
*/
|
|
48
|
+
type VoiceSnapshot = {
|
|
49
|
+
pitch: number;
|
|
50
|
+
prevPitch?: number;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Everything needed to evaluate one candidate pitch.
|
|
54
|
+
*
|
|
55
|
+
* @category Arrangement & Analysis
|
|
56
|
+
*/
|
|
57
|
+
type SafetyQuery = {
|
|
58
|
+
profile: SafetyProfile;
|
|
59
|
+
candidatePitch: number;
|
|
60
|
+
prevPitch?: number;
|
|
61
|
+
chord: Chord | null;
|
|
62
|
+
key: KeyScale;
|
|
63
|
+
otherVoices: VoiceSnapshot[];
|
|
64
|
+
strongBeat: boolean;
|
|
65
|
+
vocalLow?: number;
|
|
66
|
+
vocalHigh?: number;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The verdict, reason flags, and optional resolution guidance.
|
|
70
|
+
*
|
|
71
|
+
* @category Arrangement & Analysis
|
|
72
|
+
*/
|
|
73
|
+
type SafetyResult = {
|
|
74
|
+
safety: NoteSafety;
|
|
75
|
+
reasons: number;
|
|
76
|
+
resolveTo?: number;
|
|
77
|
+
suggestions?: number[];
|
|
78
|
+
rationale?: string;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Evaluate the safety of placing a candidate pitch in a voice.
|
|
82
|
+
*
|
|
83
|
+
* Reasons are merged from harmonic context (chord tone / tension / avoid / scale)
|
|
84
|
+
* and counterpoint (vertical dissonance, parallels, voice crossing). The `strict`
|
|
85
|
+
* profile treats avoid notes and parallels as dissonant; `pop` treats them as
|
|
86
|
+
* warnings. The final verdict is the worst severity among the reasons.
|
|
87
|
+
*
|
|
88
|
+
* @param q The candidate and its harmonic/voice-leading context.
|
|
89
|
+
* @returns The verdict, reason bitmask, and optional resolution guidance.
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* import { evaluateSafety, makeChord, majorKey, NoteSafety } from '@libraz/libcantus';
|
|
93
|
+
* const result = evaluateSafety({
|
|
94
|
+
* profile: 'pop',
|
|
95
|
+
* candidatePitch: 60, // C over a C major chord
|
|
96
|
+
* chord: makeChord(0, 'maj'),
|
|
97
|
+
* key: majorKey(0),
|
|
98
|
+
* otherVoices: [],
|
|
99
|
+
* strongBeat: true,
|
|
100
|
+
* });
|
|
101
|
+
* result.safety === NoteSafety.Safe; // true — C is a chord tone
|
|
102
|
+
* ```
|
|
103
|
+
* @category Arrangement & Analysis
|
|
104
|
+
*/
|
|
105
|
+
declare function evaluateSafety(q: SafetyQuery): SafetyResult;
|
|
106
|
+
/**
|
|
107
|
+
* Enumerate placeable pitches in a range, chord tones first, descending.
|
|
108
|
+
*
|
|
109
|
+
* @param q The safety context minus the candidate pitch.
|
|
110
|
+
* @param pitchLow Lowest MIDI pitch to consider (inclusive).
|
|
111
|
+
* @param pitchHigh Highest MIDI pitch to consider (inclusive).
|
|
112
|
+
* @returns Placeable pitches (non-dissonant), chord tones before others, each group descending.
|
|
113
|
+
* @throws If either bound is non-finite/non-integral, reversed, or exceeds the generation budget.
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { enumerateSafePitches, makeChord, majorKey } from '@libraz/libcantus';
|
|
117
|
+
* const pitches = enumerateSafePitches(
|
|
118
|
+
* { profile: 'pop', chord: makeChord(0, 'maj'), key: majorKey(0), otherVoices: [], strongBeat: true },
|
|
119
|
+
* 60,
|
|
120
|
+
* 72,
|
|
121
|
+
* );
|
|
122
|
+
* pitches; // placeable pitches in [60, 72], chord tones first, each group descending
|
|
123
|
+
* ```
|
|
124
|
+
* @category Arrangement & Analysis
|
|
125
|
+
*/
|
|
126
|
+
declare function enumerateSafePitches(q: Omit<SafetyQuery, 'candidatePitch'>, pitchLow: number, pitchHigh: number): number[];
|
|
127
|
+
|
|
128
|
+
export { NoteSafety as N, ReasonFlag as R, type SafetyProfile as S, type VoiceSnapshot as V, type SafetyQuery as a, type SafetyResult as b, evaluateSafety as c, enumerateSafePitches as e };
|