@libraz/libcantus 0.9.5 → 1.0.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.
Files changed (185) hide show
  1. package/README.md +61 -270
  2. package/README_ja.md +61 -214
  3. package/dist/analyze/index.cjs +54 -7
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +200 -353
  6. package/dist/analyze/index.d.ts +200 -353
  7. package/dist/analyze/index.js +56 -9
  8. package/dist/chunk-3JXCW6DX.cjs +3220 -0
  9. package/dist/chunk-3JXCW6DX.cjs.map +1 -0
  10. package/dist/chunk-4A2RMUCC.js +7647 -0
  11. package/dist/chunk-4A2RMUCC.js.map +1 -0
  12. package/dist/chunk-6XPTBE7Y.cjs +49 -0
  13. package/dist/chunk-6XPTBE7Y.cjs.map +1 -0
  14. package/dist/chunk-AFM2ULJH.cjs +5121 -0
  15. package/dist/chunk-AFM2ULJH.cjs.map +1 -0
  16. package/dist/chunk-E3F4AHAH.cjs +690 -0
  17. package/dist/chunk-E3F4AHAH.cjs.map +1 -0
  18. package/dist/{chunk-2PTFGVD7.js → chunk-ELCTZUJJ.js} +250 -132
  19. package/dist/chunk-ELCTZUJJ.js.map +1 -0
  20. package/dist/chunk-FVSI36UV.js +690 -0
  21. package/dist/chunk-FVSI36UV.js.map +1 -0
  22. package/dist/chunk-GEDTJHAC.cjs +1 -0
  23. package/dist/chunk-GEDTJHAC.cjs.map +1 -0
  24. package/dist/chunk-GOZG57ER.js +3220 -0
  25. package/dist/chunk-GOZG57ER.js.map +1 -0
  26. package/dist/chunk-H3VORETI.js +2243 -0
  27. package/dist/chunk-H3VORETI.js.map +1 -0
  28. package/dist/chunk-H42UHPYW.js +1577 -0
  29. package/dist/chunk-H42UHPYW.js.map +1 -0
  30. package/dist/chunk-HFPS7JFD.js +1 -0
  31. package/dist/chunk-INHVXP6Z.js +3053 -0
  32. package/dist/chunk-INHVXP6Z.js.map +1 -0
  33. package/dist/chunk-K4ZJUGF4.cjs +379 -0
  34. package/dist/chunk-K4ZJUGF4.cjs.map +1 -0
  35. package/dist/chunk-KNJEKLKV.js +49 -0
  36. package/dist/chunk-KNJEKLKV.js.map +1 -0
  37. package/dist/chunk-M4CWTJWJ.cjs +7647 -0
  38. package/dist/chunk-M4CWTJWJ.cjs.map +1 -0
  39. package/dist/chunk-MXO72PLY.cjs +211 -0
  40. package/dist/chunk-MXO72PLY.cjs.map +1 -0
  41. package/dist/chunk-NLEU4QSE.cjs +360 -0
  42. package/dist/chunk-NLEU4QSE.cjs.map +1 -0
  43. package/dist/chunk-OE6WK2Y2.cjs +1577 -0
  44. package/dist/chunk-OE6WK2Y2.cjs.map +1 -0
  45. package/dist/chunk-Q2WGKJNN.cjs +1064 -0
  46. package/dist/chunk-Q2WGKJNN.cjs.map +1 -0
  47. package/dist/chunk-QYQQ5J6E.js +1 -0
  48. package/dist/chunk-QYQQ5J6E.js.map +1 -0
  49. package/dist/chunk-RHIJ3S4I.cjs +1 -0
  50. package/dist/chunk-RHIJ3S4I.cjs.map +1 -0
  51. package/dist/chunk-TTCLJX6D.js +211 -0
  52. package/dist/chunk-TTCLJX6D.js.map +1 -0
  53. package/dist/chunk-UADYAYAT.cjs +3053 -0
  54. package/dist/chunk-UADYAYAT.cjs.map +1 -0
  55. package/dist/chunk-VOHZB45S.js +5121 -0
  56. package/dist/chunk-VOHZB45S.js.map +1 -0
  57. package/dist/chunk-VTI6FOLB.js +1064 -0
  58. package/dist/chunk-VTI6FOLB.js.map +1 -0
  59. package/dist/chunk-WWURE4XT.cjs +2243 -0
  60. package/dist/chunk-WWURE4XT.cjs.map +1 -0
  61. package/dist/chunk-YH3BIKHH.js +360 -0
  62. package/dist/chunk-YH3BIKHH.js.map +1 -0
  63. package/dist/core/index.cjs +93 -6
  64. package/dist/core/index.cjs.map +1 -1
  65. package/dist/core/index.d.cts +87 -346
  66. package/dist/core/index.d.ts +87 -346
  67. package/dist/core/index.js +94 -7
  68. package/dist/generate/index.cjs +89 -10
  69. package/dist/generate/index.cjs.map +1 -1
  70. package/dist/generate/index.d.cts +436 -1094
  71. package/dist/generate/index.d.ts +436 -1094
  72. package/dist/generate/index.js +94 -15
  73. package/dist/index-9wcMhG73.d.ts +1095 -0
  74. package/dist/{index-Cfx8xgH0.d.ts → index-B4o55NDr.d.cts} +13 -9
  75. package/dist/index-B9MNZboQ.d.cts +770 -0
  76. package/dist/index-B9MNZboQ.d.ts +770 -0
  77. package/dist/index-BGFW-8P2.d.cts +2307 -0
  78. package/dist/index-BO6xE9YU.d.cts +1804 -0
  79. package/dist/index-BkGmlyI3.d.cts +470 -0
  80. package/dist/index-CCNJRG-P.d.cts +1095 -0
  81. package/dist/index-CGpRv5sl.d.cts +643 -0
  82. package/dist/index-C_8o--VE.d.cts +1242 -0
  83. package/dist/index-CgHqxdps.d.ts +2307 -0
  84. package/dist/index-Chbst3Jx.d.ts +1343 -0
  85. package/dist/index-Cj49F892.d.ts +470 -0
  86. package/dist/{index-x2vcsduE.d.cts → index-CunYKLiu.d.ts} +13 -9
  87. package/dist/index-D57QRbVn.d.ts +1804 -0
  88. package/dist/index-DInQqT8Q.d.cts +66 -0
  89. package/dist/index-DInQqT8Q.d.ts +66 -0
  90. package/dist/index-Dzr13q3d.d.cts +1343 -0
  91. package/dist/index-G1uwGGWQ.d.ts +643 -0
  92. package/dist/index-szBv3496.d.ts +1242 -0
  93. package/dist/index.cjs +348 -15
  94. package/dist/index.cjs.map +1 -1
  95. package/dist/index.d.cts +17 -15
  96. package/dist/index.d.ts +17 -15
  97. package/dist/index.js +397 -64
  98. package/dist/masks-DYbE485J.d.cts +445 -0
  99. package/dist/masks-DYbE485J.d.ts +445 -0
  100. package/dist/model/index.cjs +39 -7
  101. package/dist/model/index.cjs.map +1 -1
  102. package/dist/model/index.d.cts +4659 -249
  103. package/dist/model/index.d.ts +4659 -249
  104. package/dist/model/index.js +40 -8
  105. package/dist/positional-Cptlj-xs.d.ts +287 -0
  106. package/dist/positional-DvH_sd8u.d.cts +287 -0
  107. package/dist/theory/index.cjs +101 -7
  108. package/dist/theory/index.cjs.map +1 -1
  109. package/dist/theory/index.d.cts +511 -171
  110. package/dist/theory/index.d.ts +511 -171
  111. package/dist/theory/index.js +118 -24
  112. package/package.json +6 -4
  113. package/dist/chunk-2A2QMA74.cjs +0 -261
  114. package/dist/chunk-2A2QMA74.cjs.map +0 -1
  115. package/dist/chunk-2PTFGVD7.js.map +0 -1
  116. package/dist/chunk-357ZDPVU.cjs +0 -162
  117. package/dist/chunk-357ZDPVU.cjs.map +0 -1
  118. package/dist/chunk-3KX76RLG.js +0 -1548
  119. package/dist/chunk-3KX76RLG.js.map +0 -1
  120. package/dist/chunk-4VZ5MZAM.cjs +0 -889
  121. package/dist/chunk-4VZ5MZAM.cjs.map +0 -1
  122. package/dist/chunk-7EONYVI5.js +0 -1204
  123. package/dist/chunk-7EONYVI5.js.map +0 -1
  124. package/dist/chunk-7JEP3MFY.js +0 -162
  125. package/dist/chunk-7JEP3MFY.js.map +0 -1
  126. package/dist/chunk-AID75RN3.js +0 -3176
  127. package/dist/chunk-AID75RN3.js.map +0 -1
  128. package/dist/chunk-BSLTLGM3.cjs +0 -497
  129. package/dist/chunk-BSLTLGM3.cjs.map +0 -1
  130. package/dist/chunk-CU6VG2YM.cjs +0 -109
  131. package/dist/chunk-CU6VG2YM.cjs.map +0 -1
  132. package/dist/chunk-DZMHIGI4.js +0 -337
  133. package/dist/chunk-DZMHIGI4.js.map +0 -1
  134. package/dist/chunk-EDOTEABK.js +0 -109
  135. package/dist/chunk-EDOTEABK.js.map +0 -1
  136. package/dist/chunk-HN2OEUN2.js +0 -33
  137. package/dist/chunk-HN2OEUN2.js.map +0 -1
  138. package/dist/chunk-KBTTEX7Q.js +0 -497
  139. package/dist/chunk-KBTTEX7Q.js.map +0 -1
  140. package/dist/chunk-KQN2ZYND.cjs +0 -724
  141. package/dist/chunk-KQN2ZYND.cjs.map +0 -1
  142. package/dist/chunk-MPTVJIVG.cjs +0 -3176
  143. package/dist/chunk-MPTVJIVG.cjs.map +0 -1
  144. package/dist/chunk-MTMZTMWM.cjs +0 -53
  145. package/dist/chunk-MTMZTMWM.cjs.map +0 -1
  146. package/dist/chunk-N6IHQUOX.js +0 -1
  147. package/dist/chunk-NNB67XA2.js +0 -724
  148. package/dist/chunk-NNB67XA2.js.map +0 -1
  149. package/dist/chunk-OL3IHHSJ.cjs +0 -1204
  150. package/dist/chunk-OL3IHHSJ.cjs.map +0 -1
  151. package/dist/chunk-PI3UXRWK.cjs +0 -528
  152. package/dist/chunk-PI3UXRWK.cjs.map +0 -1
  153. package/dist/chunk-R2VVFAIN.js +0 -528
  154. package/dist/chunk-R2VVFAIN.js.map +0 -1
  155. package/dist/chunk-T64C7ZD7.cjs +0 -1
  156. package/dist/chunk-T64C7ZD7.cjs.map +0 -1
  157. package/dist/chunk-WFDXOLUB.cjs +0 -337
  158. package/dist/chunk-WFDXOLUB.cjs.map +0 -1
  159. package/dist/chunk-WTAN4ACG.cjs +0 -1548
  160. package/dist/chunk-WTAN4ACG.cjs.map +0 -1
  161. package/dist/chunk-YFJXS3AU.cjs +0 -33
  162. package/dist/chunk-YFJXS3AU.cjs.map +0 -1
  163. package/dist/chunk-YUE6TJ4M.js +0 -889
  164. package/dist/chunk-YUE6TJ4M.js.map +0 -1
  165. package/dist/chunk-ZGLJM4A4.js +0 -53
  166. package/dist/chunk-ZGLJM4A4.js.map +0 -1
  167. package/dist/function-CxixTB02.d.cts +0 -299
  168. package/dist/function-DTCt675z.d.ts +0 -299
  169. package/dist/index-B2Ulxxpo.d.cts +0 -144
  170. package/dist/index-BkYJ0xMq.d.cts +0 -152
  171. package/dist/index-BuaB-ypt.d.ts +0 -144
  172. package/dist/index-CAN7v-kw.d.ts +0 -199
  173. package/dist/index-COw1Ky9g.d.ts +0 -242
  174. package/dist/index-DRtIhlJj.d.ts +0 -152
  175. package/dist/index-DXd9JikN.d.cts +0 -246
  176. package/dist/index-DXd9JikN.d.ts +0 -246
  177. package/dist/index-alrRninU.d.cts +0 -199
  178. package/dist/index-oTQbYYIs.d.cts +0 -242
  179. package/dist/index-tR9jTn26.d.cts +0 -275
  180. package/dist/index-tR9jTn26.d.ts +0 -275
  181. package/dist/styled-Bl0ZK28h.d.ts +0 -543
  182. package/dist/styled-Crv6SA4S.d.cts +0 -543
  183. package/dist/types-BN6_Q9Kl.d.cts +0 -39
  184. package/dist/types-BN6_Q9Kl.d.ts +0 -39
  185. /package/dist/{chunk-N6IHQUOX.js.map → chunk-HFPS7JFD.js.map} +0 -0
@@ -1,411 +1,258 @@
1
- import { T as TimeSignature } from '../index-DXd9JikN.js';
2
- import { N as NoteEvent, K as KeyScale } from '../types-BN6_Q9Kl.js';
3
- import { d as ChordToneRole, C as Chord } from '../index-COw1Ky9g.js';
4
- export { a as ChordQuality, b as ChordSegment, c as ChordSpan, P as PitchSpelling } from '../index-COw1Ky9g.js';
5
- import { V as VoiceSnapshot, N as NoteSafety, S as SafetyProfile } from '../index-BuaB-ypt.js';
6
- import { a as ChordTimeline, C as CadenceHit } from '../index-DRtIhlJj.js';
7
- export { b as ChordTimelineOptions, c as ChordTimelineResult, d as chordTimelineFromChords, e as chordTimelineFromNotes, f as detectCadences } from '../index-DRtIhlJj.js';
8
- export { C as ChordMatch, D as DetectChordOptions, a as DetectKeyOptions, K as KeyMatch, b as KeyVariant, d as detectChord, c as detectChordBest, e as detectKey, f as detectKeyBest, g as detectKeyFromNotes } from '../index-CAN7v-kw.js';
9
- export { B as BorrowedSource, C as Cadence, a as ChordAnalysis, b as ChordToRomanOptions, H as HarmonicFunction, c as analyzeChord, d as borrowedSource, e as chordToRoman, f as detectCadence, g as functionOf, i as isBorrowedChord, h as isDiatonic, j as isMinorKey, p as parallelKey, r as romanToChord, s as secondaryDominant } from '../function-DTCt675z.js';
1
+ import { h as NoteLike, f as Note, g as NoteEvent } from '../index-B9MNZboQ.js';
2
+ export { a as Articulation, b as IntervalQualityLabel, d as KeyScale, S as SpelledInterval } from '../index-B9MNZboQ.js';
3
+ export { M as MeterChange, b as MeterLike, c as MeterMap, T as TimeSignature } from '../index-Cj49F892.js';
4
+ import { K as KeyLike, b as Chord, c as ChordLike } from '../index-szBv3496.js';
5
+ export { d as ChordQuality, e as ChordSegment, g as ChordSpan, j as ChordToneRole, k as KeyRelation, N as NoteSafety, l as PitchSpelling, S as SafetyProfile, V as VoiceSnapshot } from '../index-szBv3496.js';
6
+ export { r as ScaleName } from '../masks-DYbE485J.js';
7
+ export { A as ArrangementAnalysis, a as ArrangementOptions, b as ArrangementSession, c as ArrangementTrack, C as ChordMatch, d as Conflict, e as ContourDirection, D as DetectChordOptions, f as DetectKeyOptions, E as ExtractMotifsOptions, F as FormSection, g as FormSectionOptions, H as Hypermeter, h as HypermeterOptions, K as KeyMatch, i as KeyVariant, M as MODAL_SCALE_NAMES, j as MelodicComparison, k as MelodicContour, l as MelodicContourShape, m as MelodicPhrase, n as ModalCandidate, o as ModalScaleName, p as MotifData, q as MotifOccurrence, r as MotifRelation, s as MotifRelationKind, P as Phrase, t as PhraseOptions, u as PhraseSignal, R as ReduceProgressionOptions, v as ReducedChord, w as ReductionBasis, x as ReductionLevel, S as StructuralCadence, T as TensionPoint, y as TrackAnalysis, z as TrackEdit, B as TrackRole, G as analyzeArrangement, I as compareMelodies, J as createArrangementSession, L as detectChord, N as detectChordBest, O as detectKey, Q as detectKeyBest, U as detectKeyFromNotes, V as extractMotifs, W as hypermeter, X as melodicContour, Y as melodicSimilarity, Z as motifFromNotes, _ as phrasesFromTimeline, $ as reduceProgression, a0 as relateMotifs, a1 as sectionsFromNotes, a2 as structuralCadences, a3 as tensionCurve, a4 as tensionCurveFrom } from '../index-D57QRbVn.js';
8
+ import { e as ChordTimeline } from '../index-Chbst3Jx.js';
9
+ export { A as AnalyzeChordOptions, a as AnalyzedNote, B as BorrowedSource, C as CadenceHit, b as CadenceResult, c as ChordAnalysis, d as ChordSegmentation, f as ChordTimelineOptions, g as ChordTimelineResult, h as ChordToRomanOptions, D as DetectCadenceOptions, E as ExplainRomanOptions, H as HarmonicFunction, I as IdentifiedVoiceNote, K as KeyContext, i as KeyProfileName, j as KeyProfilePair, k as KeyRegion, l as KeyTimelineOptions, P as PivotChord, R as RejectedCandidate, m as RomanExplanation, S as SuspensionFigure, T as TheoryLabel, V as VoiceNote, n as analyzeChord, o as analyzeVoice, p as borrowedSource, q as chordTimelineFromChords, r as chordTimelineFromNotes, s as chordToRoman, t as detectCadence, u as detectCadences, v as detectModulations, w as explainRoman, x as functionOf, y as isBorrowedChord, z as isDiatonic, F as isMinorKey, G as keyTimelineFromNotes, J as pivotChords, L as prevailingKeyOf, M as romanToChord, N as secondaryDominant, O as secondaryDominantOf, Q as toVoiceNotes } from '../index-Chbst3Jx.js';
10
10
 
11
11
  /**
12
- * A suspension figure, named by the interval above the bass and the interval it
13
- * resolves to.
12
+ * Augmented-sixth chords: the Italian, French, and German sixths.
14
13
  *
15
- * @category Arrangement & Analysis
16
- */
17
- type SuspensionFigure = 'sus4-3' | 'sus6-5' | 'sus7-6' | 'sus9-8' | 'sus2-3';
18
- /**
19
- * A theory annotation attached to a note.
14
+ * All three stand on the lowered submediant (b6) in the bass and carry an
15
+ * augmented sixth above it, and all three are predominants whose two outer
16
+ * voices resolve outward onto the dominant. The family is defined by that bass
17
+ * and that interval rather than by a stack of thirds, so a chord is recognized
18
+ * here from the tones sounding over that bass rather than through the
19
+ * Roman-numeral degree tables.
20
20
  *
21
- * @category Arrangement & Analysis
22
- */
23
- type TheoryLabel = {
24
- kind: 'chordTone';
25
- role: ChordToneRole;
26
- } | {
27
- kind: 'tension';
28
- degree: 9 | 11 | 13;
29
- } | {
30
- kind: 'avoid';
31
- } | {
32
- kind: 'passing';
33
- } | {
34
- kind: 'neighbor';
35
- } | {
36
- kind: 'suspension';
37
- type: SuspensionFigure;
38
- resolveTo: number;
39
- } | {
40
- kind: 'anticipation';
41
- } | {
42
- kind: 'escape';
43
- } | {
44
- kind: 'needsResolution';
45
- resolveTo: number;
46
- } | {
47
- kind: 'leadingTone';
48
- resolveTo: number;
49
- };
50
- /**
51
- * A note with its theory labels and a short rationale.
52
- *
53
- * The labels describe the note against the chord sounding at its **onset**;
54
- * they are not re-derived for the later chords a long note sustains across.
55
- * `analyzeArrangement` does re-check every crossed chord change, and reports
56
- * what it finds as conflicts, so a note labelled a chord tone here can still
57
- * appear in `conflicts` at a later beat.
58
- *
59
- * The identity fields make an annotation mappable back to the caller's input:
60
- * `originalIndex` is the note's position in the array that was passed in, and
61
- * `trackIndex` is set by {@link analyzeArrangement}.
62
- *
63
- * @category Arrangement & Analysis
21
+ * The interval is spelled, so recognition reads letters and not only pitch
22
+ * classes: over an Ab bass the augmented sixth is F# and the minor seventh of
23
+ * the bVI7 it otherwise sounds exactly like is Gb. A caller holding pitch
24
+ * classes alone — an analysis of a MIDI track, which names no accidentals —
25
+ * makes that reading with {@link augmentedSixthFromPitchClasses}, and the chord
26
+ * it hands back carries the spelling that decided it.
64
27
  */
65
- type AnalyzedNote = {
66
- noteId: number;
67
- /** The note's index in the caller's own array, when the caller supplied one. */
68
- originalIndex?: number;
69
- /** The note's track, set by `analyzeArrangement`. */
70
- trackIndex?: number;
71
- pitch: number;
72
- startBeat: number;
73
- durationBeat: number;
74
- labels: TheoryLabel[];
75
- rationale?: string;
76
- };
28
+
77
29
  /**
78
- * A single note in a monophonic voice: a {@link NoteEvent} with a stable id and,
79
- * optionally, its index in the caller's original array.
30
+ * The three augmented-sixth chords, under the nationalities convention gives
31
+ * them.
80
32
  *
81
- * @category Arrangement & Analysis
82
- */
83
- type VoiceNote = NoteEvent & {
84
- id?: number;
85
- originalIndex?: number;
86
- };
87
- /**
88
- * A voice note whose id has been assigned, as {@link toVoiceNotes} returns and
89
- * {@link analyzeArrangement} works in.
33
+ * Over an Ab bass in C: the Italian sixth is Ab C F#, the French sixth adds the
34
+ * supertonic (Ab C D F#), and the German sixth adds the lowered mediant
35
+ * (Ab C Eb F#).
90
36
  *
91
- * @category Arrangement & Analysis
37
+ * @category Functional Harmony
92
38
  */
93
- type IdentifiedVoiceNote = VoiceNote & {
94
- id: number;
95
- };
39
+ type AugmentedSixthKind = 'italian' | 'french' | 'german';
96
40
  /**
97
- * Give plain note events the ids {@link analyzeVoice} reports back.
41
+ * Build an augmented-sixth chord in a key.
98
42
  *
99
- * The id is only an identity handle, so the array position serves; this exists
100
- * so the mapping is a named call rather than a `map` every caller writes.
101
- *
102
- * @param events The note events, in time order.
103
- * @returns The same notes as voice notes, each carrying its index as its id.
104
- * @category Arrangement & Analysis
105
- */
106
- declare function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote[];
107
- /**
108
- * Label every note of a voice with its theory roles.
43
+ * The chord always carries an explicit `bassPc` on the lowered submediant,
44
+ * which is what makes it an augmented sixth rather than the dominant seventh or
45
+ * the altered supertonic seventh it shares pitch classes with, and an explicit
46
+ * interval template naming exactly the tones that sound.
109
47
  *
110
- * Each note is classified against the chord sounding at its beat. Chord tones
111
- * get a role label; non-chord tones are matched, in order, as suspensions
112
- * (prepared by an identical consonant pitch and resolving by step), passing
113
- * tones, neighbors, anticipations, and escape tones, then fall back to tension,
114
- * avoid, or an unresolved-dissonance label. Leading-tone resolutions are noted
115
- * additionally.
48
+ * It also carries its spelling: the tones as `toneSpellings`, and the root and
49
+ * bass as the usual hints. Those are what let the ordinary chord speller and
50
+ * the symbol formatter name the chord — `Ab C Eb F#` and `Ab7` for the German
51
+ * sixth of C major — since a stack of thirds over that root would put a Gb
52
+ * where the augmented sixth belongs. The spelling is the one the key is written
53
+ * on, so a flat-side key takes the double flats it genuinely needs.
116
54
  *
117
- * The voice is expected to be monophonic (one note at a time). Notes sharing an
118
- * onset are treated as simultaneous cluster members, not melodic neighbors, so
119
- * they receive no melodic labels (suspension, passing, neighbor, anticipation,
120
- * escape) — only harmonic ones. Callers with truly polyphonic material should
121
- * split it into monophonic sub-voices first, as `analyzeArrangement` does.
55
+ * The lowered submediant is taken as eight semitones above the tonic in every
56
+ * mode, since the chord is a chromatic alteration rather than a degree of the
57
+ * prevailing scale.
122
58
  *
123
- * @param voice The monophonic voice, in time order. A note without an `id` is
124
- * identified by its position, so plain note events can be passed straight in.
125
- * @param chordAtBeat Chord sounding at a given beat, or null.
126
- * @param key Key context for leading-tone detection.
127
- * @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to
128
- * none, which is the whole story for a solo line.
129
- * @returns One annotation per input note.
59
+ * @param kind Which of the three augmented sixths to build.
60
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
61
+ * @returns The chord, with its bass on the lowered submediant.
62
+ * @throws If `kind` is not one of the three, or the key's root is not finite.
63
+ * @see {@link spellAugmentedSixth} to spell the family from a tonic without
64
+ * building a chord at all.
130
65
  * @example
131
66
  * ```ts
132
- * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';
133
- * const voice = [
134
- * { pitch: 60, startBeat: 0, durationBeat: 1 },
135
- * { pitch: 62, startBeat: 1, durationBeat: 1 },
136
- * { pitch: 64, startBeat: 2, durationBeat: 1 },
137
- * ];
138
- * const cMajor = makeChord(0, 'maj');
139
- * const labels = analyzeVoice(voice, () => cMajor, majorKey(0));
140
- * labels; // one AnalyzedNote per input note, in the same order
67
+ * import { augmentedSixthChord, majorKey } from '@libraz/libcantus';
68
+ * augmentedSixthChord('german', majorKey(0));
69
+ * // Ab C Eb F# in C: { rootPc: 8, quality: 'dom7', intervals: [0, 4, 7, 10], bassPc: 8, ... }
141
70
  * ```
142
- * @category Arrangement & Analysis
71
+ * @category Functional Harmony
143
72
  */
144
- declare function analyzeVoice(voice: readonly VoiceNote[], chordAtBeat: (beat: number) => Chord | null, key: KeyScale, otherVoicesAtBeat?: (beat: number) => VoiceSnapshot[]): AnalyzedNote[];
145
-
73
+ declare function augmentedSixthChord(kind: AugmentedSixthKind, key: KeyLike): Chord;
146
74
  /**
147
- * Arrangement track-role analysis: a single harmony is inferred from every
148
- * track's notes pooled together, each track is then analysed against that shared
149
- * harmony, and notes that clash with the sounding chord are collected as
150
- * conflicts.
151
- */
152
-
153
- /**
154
- * The musical role a track plays in the arrangement.
75
+ * Identify a chord as one of the augmented sixths, or null when it is none.
155
76
  *
156
- * Only `drums` changes the analysis: its pitches select instruments rather than
157
- * naming harmony, so such a track is excluded from chord and key inference and
158
- * from every voice-leading comparison, and is reported back with no
159
- * annotations. `harmony` is meaningful through
160
- * {@link ArrangementOptions.harmonyTracks}, which names the tracks the chords
161
- * are inferred from. The rest are labels carried through to
162
- * {@link TrackAnalysis.role} for the caller's own use; nothing is inferred, so
163
- * a track left unlabelled reports `'other'` rather than a guess.
77
+ * A chord qualifies when it sounds the lowered submediant of `key` in the bass,
78
+ * its pitch classes are exactly one of the three sets built over that bass, and
79
+ * its own letters spell the augmented sixth above it. All three are needed: the
80
+ * same pitch classes over the same bass are an ordinary bVI7 when the tone ten
81
+ * semitones above the bass is written as a minor seventh, and the letters are
82
+ * the only place that distinction lives.
164
83
  *
165
- * @category Arrangement & Analysis
166
- */
167
- type TrackRole = 'melody' | 'harmony' | 'bass' | 'drums' | 'other';
168
- /**
169
- * One input track: its notes plus optional name and role.
84
+ * A chord naming no `bassPc` sounds its root lowest, which is how the two
85
+ * conventions for that field — the sounding bass always, or only a bass that
86
+ * differs from the root — are read alike here.
170
87
  *
171
- * @category Arrangement & Analysis
172
- */
173
- type ArrangementTrack = {
174
- name?: string;
175
- role?: TrackRole;
176
- notes: readonly NoteEvent[];
177
- };
178
- /**
179
- * A track after analysis: its resolved name, role, and per-note annotations.
88
+ * The root the chord is measured from is not consulted, so a chord voiced or
89
+ * rebuilt around any of its own tones still identifies as long as it keeps its
90
+ * spelling.
180
91
  *
181
- * @category Arrangement & Analysis
92
+ * @param chord The chord to test, as a chord symbol, chord data, or a `Chord`.
93
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
94
+ * @returns The kind of augmented sixth, or null.
95
+ * @see {@link augmentedSixthFromPitchClasses} to make the reading from pitch
96
+ * classes that carry no spelling at all.
97
+ * @example
98
+ * ```ts
99
+ * import { augmentedSixthKind, majorKey, romanToChord } from '@libraz/libcantus';
100
+ * augmentedSixthKind(romanToChord('Fr6', majorKey(0)), majorKey(0)); // 'french'
101
+ * ```
102
+ * @category Functional Harmony
182
103
  */
183
- type TrackAnalysis = {
184
- name: string;
185
- role: TrackRole;
186
- /** One {@link AnalyzedNote} per note, labelled against the inferred chords. */
187
- notes: AnalyzedNote[];
188
- };
104
+ declare function augmentedSixthKind(chord: ChordLike, key: KeyLike): AugmentedSixthKind | null;
189
105
  /**
190
- * A note that clashes with the harmony sounding beneath it.
106
+ * Read a set of sounding pitch classes over a sounding bass as an augmented
107
+ * sixth, or null when they are none.
191
108
  *
192
- * @category Arrangement & Analysis
193
- */
194
- type Conflict = {
195
- beat: number;
196
- trackName: string;
197
- /** The clashing track's index in the caller's input array. */
198
- trackIndex: number;
199
- /** The note's index in that track's own note array. */
200
- originalIndex?: number;
201
- /** The id the note carries in {@link TrackAnalysis.notes}. */
202
- noteId: number;
203
- pitch: number;
204
- /**
205
- * The note's theory labels at its onset, so a caller can tell a genuine clash
206
- * from a passing tone, a neighbour, or a prepared suspension — all of which
207
- * are ordinary non-chord tones that still evaluate as unsafe.
208
- */
209
- labels: TheoryLabel[];
210
- /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */
211
- safety: NoteSafety;
212
- /** {@link SafetyResult.reasons} bitmask. */
213
- reasons: number;
214
- /** Preferred stepwise resolution, when the safety evaluator provides one. */
215
- resolveTo?: number;
216
- /** Nearby fully safe replacement pitches, nearest first. */
217
- suggestions?: number[];
218
- rationale?: string;
219
- };
220
- /**
221
- * The full result of {@link analyzeArrangement}.
109
+ * The reading a caller holding no spelling has to make for itself: pitch
110
+ * classes name no accidentals, so nothing downstream can tell the German sixth
111
+ * from the bVI7 it sounds like once the letters are gone. The chord returned is
112
+ * the one {@link augmentedSixthChord} builds, spelling and all, so it keeps
113
+ * identifying as an augmented sixth everywhere it travels.
222
114
  *
223
- * @category Arrangement & Analysis
224
- */
225
- type ArrangementAnalysis = {
226
- key: KeyScale;
227
- timeline: ChordTimeline;
228
- segmentConfidence: number[];
229
- cadences: CadenceHit[];
230
- tracks: TrackAnalysis[];
231
- /** Notes clashing with the sounding harmony, worst severity first. */
232
- conflicts: Conflict[];
233
- };
234
- /**
235
- * Options controlling {@link analyzeArrangement} and {@link tensionCurve}.
115
+ * Not every b6-1-b3-#4 over a b6 bass is an augmented sixth, and this cannot
116
+ * know which one is: it answers what the tones would spell, and the caller
117
+ * decides whether that reading is the one the music supports.
236
118
  *
237
- * @category Arrangement & Analysis
119
+ * @param pcs The sounding pitch classes; duplicates and unreduced values are
120
+ * fine.
121
+ * @param bassPc The pitch class sounding in the bass.
122
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
123
+ * @returns The augmented-sixth chord, or null.
124
+ * @see {@link augmentedSixthKind} to read a chord that already carries its
125
+ * spelling.
126
+ * @example
127
+ * ```ts
128
+ * import { augmentedSixthFromPitchClasses, majorKey } from '@libraz/libcantus';
129
+ * // Ab C Eb Gb sounding over Ab in C: the German sixth, spelled with its F#.
130
+ * augmentedSixthFromPitchClasses([8, 0, 3, 6], 8, majorKey(0))?.quality; // 'dom7'
131
+ * ```
132
+ * @category Functional Harmony
238
133
  */
239
- type ArrangementOptions = {
240
- /** Key context; inferred from the pooled notes when omitted. */
241
- key?: KeyScale;
242
- /**
243
- * A chord timeline to analyse against, instead of inferring one from the
244
- * notes. Supply it to analyse a hand-written progression, or to reuse the
245
- * timeline of an earlier call rather than re-running chord inference.
246
- */
247
- timeline?: ChordTimeline;
248
- /**
249
- * Indices of the tracks the harmony is inferred from. Defaults to every
250
- * pitched track. Percussion tracks are excluded either way.
251
- */
252
- harmonyTracks?: number[];
253
- /**
254
- * Lowest severity reported in `conflicts`.
255
- *
256
- * @defaultValue {@link NoteSafety.Warning}
257
- */
258
- minSeverity?: NoteSafety;
259
- /**
260
- * Time signature; defaults to 4/4.
261
- *
262
- * @defaultValue `4/4`
263
- */
264
- ts?: TimeSignature;
265
- /**
266
- * Chord-slot length in beats; defaults to one bar of `ts`.
267
- *
268
- * @defaultValue one bar of `ts`
269
- */
270
- harmonicRhythm?: number;
271
- /**
272
- * Safety profile used for conflict detection; defaults to `pop`.
273
- *
274
- * @defaultValue `'pop'`
275
- */
276
- profile?: SafetyProfile;
277
- /**
278
- * Upper bound on the work this call may do — note counts, windows, and
279
- * candidate counts are each checked against it before anything is allocated.
280
- *
281
- * Raise it to analyse a piece larger than the default allows; the default is
282
- * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast
283
- * rather than blocking the thread.
284
- *
285
- * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
286
- */
287
- budget?: number;
288
- };
134
+ declare function augmentedSixthFromPitchClasses(pcs: readonly number[], bassPc: number, key: KeyLike): Chord | null;
289
135
  /**
290
- * Analyse a whole arrangement against a single inferred harmony.
291
- *
292
- * The chord timeline and key are inferred from the pooled notes of every
293
- * pitched track (see {@link chordTimelineFromNotes}); pooling all voices is
294
- * robust even when roles are absent or a track doubles the harmony, so it is
295
- * preferred over deriving the harmony from a subset. A track marked
296
- * `role: 'drums'` is excluded — its pitches name instruments, not harmony, and
297
- * pooling them would corrupt the key, the timeline and every reading built on
298
- * them. Pass `harmonyTracks` to restrict inference further, or `timeline` to
299
- * analyse against a progression you already have. Zero- and negative-length
300
- * notes never sound, so they are dropped at ingest and appear in neither the
301
- * annotations nor the conflicts.
136
+ * Spell an augmented-sixth chord from a spelled tonic, lowest note first.
302
137
  *
303
- * Voice analysis assumes one monophonic voice at a time, so each track is
304
- * first partitioned into monophonic sub-voices (a polyphonic block-chord track
305
- * fans out into one sub-voice per simultaneous note; a monophonic track is
306
- * unaffected). Each sub-voice is labelled note-by-note against the harmony
307
- * with {@link analyzeVoice}, seeing every other sub-voice — including siblings
308
- * within the same track — as its accompaniment. Every note is then re-checked
309
- * with {@link evaluateSafety} at its onset and again at each chord change it
310
- * sustains across, so a held note that clashes with a later chord is caught:
311
- * evaluations that are not {@link NoteSafety.Safe} become conflicts, one per
312
- * clashing beat, sorted worst severity first and then by beat.
138
+ * This is the spelled counterpart of {@link augmentedSixthChord}, and the table
139
+ * the chord's own spelling is built from: every tone is placed by a named
140
+ * interval above the bass, so the outer interval is an augmented sixth in every
141
+ * key rather than the minor seventh its ten semitones would otherwise imply.
142
+ * Reach for it when a spelled tonic is all there is; spelling the chord itself
143
+ * with {@link spellChord} names the same notes, in the chord's own order.
313
144
  *
314
- * Every ordinary non-chord tone — a passing note, a neighbour, a prepared
315
- * suspension — is by definition unsafe against the chord under it, so the
316
- * conflict list is a report, not a fault list. Each conflict carries the note's
317
- * `labels` and its `trackIndex`/`originalIndex`, so a caller can tell those
318
- * apart and map a conflict back to the note it passed in; `minSeverity` narrows
319
- * the list to outright dissonance.
145
+ * A tonic carrying an octave yields notes carrying octaves, ascending from the
146
+ * bass; an octave-less tonic yields octave-less notes.
320
147
  *
321
- * @param tracks The tracks to analyse.
322
- * @param opts Analysis options; see {@link ArrangementOptions}.
323
- * @returns The inferred harmony, per-track annotations, cadences, and conflicts.
148
+ * @param kind Which of the three augmented sixths to spell.
149
+ * @param tonic The spelled tonic of the key, as a note name, a MIDI number,
150
+ * note data, or a `Note`.
151
+ * @returns The spelled tones, bass first and ascending.
152
+ * @throws If `kind` is not one of the three, or `tonic` is not a note.
324
153
  * @example
325
154
  * ```ts
326
- * import { analyzeArrangement } from '@libraz/libcantus';
327
- * const melody = [
328
- * { pitch: 60, startBeat: 0, durationBeat: 2 },
329
- * { pitch: 67, startBeat: 2, durationBeat: 2 },
330
- * ];
331
- * const { key, conflicts } = analyzeArrangement([{ role: 'melody', notes: melody }]);
332
- * conflicts; // notes clashing with the inferred harmony, worst severity first
155
+ * import { noteNames, parseNote, spellAugmentedSixth } from '@libraz/libcantus';
156
+ * noteNames(spellAugmentedSixth('german', parseNote('C'))); // ['Ab', 'C', 'Eb', 'F#']
157
+ * noteNames(spellAugmentedSixth('italian', 'E')); // ['C', 'E', 'A#']
333
158
  * ```
334
- * @category Arrangement & Analysis
159
+ * @category Functional Harmony
335
160
  */
336
- declare function analyzeArrangement(tracks: ArrangementTrack[], opts?: ArrangementOptions): ArrangementAnalysis;
161
+ declare function spellAugmentedSixth(kind: AugmentedSixthKind, tonic: NoteLike): Note[];
337
162
 
338
163
  /**
339
- * Arrangement tension analysis: a coarse harmonic-tension curve sampled at
340
- * regular beats across a whole multi-track piece, combining the sounding
341
- * chord's harmonic function, the dissonance of the sounding notes, and their
342
- * registral span into a single normalized reading.
164
+ * Line spelling: name every note of a melodic line in one pass.
165
+ *
166
+ * {@link spellPitch} decides one note from the key, the root of the chord under
167
+ * it and its two immediate neighbours. That is enough for a single note, but a
168
+ * line spelled note by note can zig-zag between the two readings of one idea —
169
+ * a rising whole-tone figure comes out F# Ab Bb, with a diminished third in the
170
+ * middle of it.
171
+ *
172
+ * This module solves the whole line at once. The theory layer's own judgment
173
+ * becomes the cost of emitting a spelling, the melodic rules become the cost of
174
+ * moving from one spelling to the next, and a dynamic program takes the
175
+ * cheapest path across the line. Nothing here re-decides what
176
+ * {@link spellPitchClass} and {@link spellChord} already decide: it only weighs
177
+ * their answers against the shape of the line they sit in.
343
178
  */
344
179
 
345
180
  /**
346
- * A tension reading sampled at a beat.
181
+ * Options for {@link spellLine}.
347
182
  *
348
183
  * @category Arrangement & Analysis
349
184
  */
350
- type TensionPoint = {
351
- beat: number;
352
- /** Combined tension in [0, 1]. */
353
- tension: number;
185
+ type SpellLineOptions = {
186
+ /**
187
+ * Tonic spelling to anchor the key, for a caller that already knows how the
188
+ * piece is written. It must sound the key's own root pitch class; anything
189
+ * else is rejected rather than spelled. Defaults to the conventional
190
+ * spelling `spelledKeyOf` gives the key. Accepted as a note name, a MIDI
191
+ * number, note data, or a `Note`.
192
+ */
193
+ tonic?: NoteLike;
194
+ /** Upper bound on the work the line may cost; defaults to the generation budget. */
195
+ budget?: number;
354
196
  };
355
197
  /**
356
- * Sample the harmonic tension of an arrangement at regular beats.
198
+ * Spell a whole melodic line at once, so its notes agree with each other.
357
199
  *
358
- * At each sample beat the tension combines three normalized terms:
359
- * - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,
360
- * tonic or no chord `0`), weighted `FUNCTION_WEIGHT`;
361
- * - the dissonance of the sounding notes — the larger of the share of sounding
362
- * pitches that are not chord tones and the share that {@link evaluateSafety}
363
- * rates {@link NoteSafety.Dissonant} — weighted `DISSONANCE_WEIGHT`;
364
- * - the registral span of the sounding notes, saturating at
365
- * `SPAN_SATURATION` semitones, weighted `SPAN_WEIGHT`.
200
+ * Every note is spelled together with the rest of the line rather than one at a
201
+ * time: each pitch offers the three spellings a double accidental allows, the
202
+ * chord and the key price them, the intervals between neighbours price the
203
+ * pairs, and the cheapest reading of the whole line wins. Ties fall to the
204
+ * spelling {@link spellPitchClass} would have chosen alone, so the answer never
205
+ * drifts from the theory layer without a reason.
366
206
  *
367
- * The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks
368
- * pooled together, so the result is deterministic and self-contained.
207
+ * Four rules decide, in this order:
369
208
  *
370
- * @param tracks The tracks to sample.
371
- * @param opts Analysis options plus an optional `step` (default one beat).
372
- * @returns One {@link TensionPoint} per sampled beat, in beat order.
373
- * @throws If `step` is not positive.
374
- * @example
375
- * ```ts
376
- * import { tensionCurve } from '@libraz/libcantus';
377
- * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];
378
- * const curve = tensionCurve([{ notes }], { step: 1 });
379
- * curve; // one { beat, tension } sample per beat, in beat order
380
- * ```
381
- * @category Arrangement & Analysis
382
- */
383
- declare function tensionCurve(tracks: ArrangementTrack[], opts?: ArrangementOptions & {
384
- step?: number;
385
- }): TensionPoint[];
386
- /**
387
- * Sample the tension of an arrangement already analysed by
388
- * {@link analyzeArrangement}, reusing its harmony instead of inferring it again.
209
+ * 1. A note the sounding chord names takes that chord's spelling of it, even
210
+ * when the key spells the pitch otherwise — the seventh of a Db7 reads Cb in
211
+ * C major, not B.
212
+ * 2. Anything else the key's scale spells keeps its key spelling, exactly as
213
+ * the note-by-note path keeps it.
214
+ * 3. A chromatic note is spelled so the written intervals around it match the
215
+ * sounding ones, and then so a rising step carries a sharp and a falling
216
+ * step a flat.
217
+ * 4. Failing all of that, the key's own chromatic spelling stands, and the
218
+ * fewest accidentals win.
389
219
  *
390
- * `analyzeArrangement` followed by `tensionCurve` runs chord and key inference
391
- * twice over the same notes. This takes the analysis it already produced, so
392
- * the curve and the annotations describe the same harmony by construction.
220
+ * Without a timeline — pass `null` — rules 2 to 4 still apply in full, so a
221
+ * diatonic line and a chromatic run come out the same. What is lost is rule 1:
222
+ * a chromatic chord can no longer name its own tones, so the tones of a
223
+ * borrowed or altered chord fall back to the key's convention (that Cb becomes
224
+ * B), and an enharmonically ambiguous tone under a chord that would have fixed
225
+ * it is decided by the line's direction instead.
393
226
  *
394
- * @param tracks The same tracks that were analysed.
395
- * @param analysis The result of {@link analyzeArrangement} for those tracks.
396
- * @param opts Analysis options plus an optional `step` (default one beat).
397
- * @returns One {@link TensionPoint} per sampled beat, in beat order.
227
+ * @param notes The line, monophonic and in time order. Notes sharing an onset
228
+ * are read as consecutive, so split polyphonic material into voices first.
229
+ * @param timeline The chords sounding under the line, or null when only the key
230
+ * is known. Each note is judged against the chord at its own onset.
231
+ * @param key The key the line is written in, as a key name, a key/scale, or a
232
+ * `Key`.
233
+ * @param opts Optional tonic spelling and work budget.
234
+ * @returns One spelled note per input note, in input order, each carrying the
235
+ * octave that reproduces its pitch.
236
+ * @throws If `opts.tonic` does not sound the key's root pitch class, if a note
237
+ * event is malformed, or if the line is longer than the budget allows.
398
238
  * @example
399
239
  * ```ts
400
- * import { analyzeArrangement, tensionCurveFrom } from '@libraz/libcantus';
401
- * const tracks = [{ notes: [{ pitch: 60, startBeat: 0, durationBeat: 4 }] }];
402
- * const analysis = analyzeArrangement(tracks);
403
- * tensionCurveFrom(tracks, analysis, { step: 1 });
240
+ * import { chordTimelineFromChords, majorKey, noteNames, spellLine } from '@libraz/libcantus';
241
+ * // A chromatic ascent spells with sharps, whatever the key would say alone.
242
+ * const rising = [60, 61, 62, 63, 64].map((pitch, index) => ({
243
+ * pitch,
244
+ * startBeat: index,
245
+ * durationBeat: 1,
246
+ * }));
247
+ * noteNames(spellLine(rising, null, majorKey(0)));
248
+ * // ['C4', 'C#4', 'D4', 'D#4', 'E4']
249
+ * // Under a Db7 the same B sounds as the chord's seventh.
250
+ * const timeline = chordTimelineFromChords([{ rootPc: 1, quality: 'dom7', startBeat: 0 }], 4);
251
+ * noteNames(spellLine([{ pitch: 71, startBeat: 0, durationBeat: 1 }], timeline, majorKey(0)));
252
+ * // ['Cb5']
404
253
  * ```
405
254
  * @category Arrangement & Analysis
406
255
  */
407
- declare function tensionCurveFrom(tracks: ArrangementTrack[], analysis: ArrangementAnalysis, opts?: ArrangementOptions & {
408
- step?: number;
409
- }): TensionPoint[];
256
+ declare function spellLine(notes: readonly NoteEvent[], timeline: ChordTimeline | null, key: KeyLike, opts?: SpellLineOptions): Note[];
410
257
 
411
- export { type AnalyzedNote, type ArrangementAnalysis, type ArrangementOptions, type ArrangementTrack, CadenceHit, Chord as ChordData, ChordTimeline, ChordToneRole, type Conflict, type IdentifiedVoiceNote, KeyScale, NoteEvent, NoteSafety, SafetyProfile, type SuspensionFigure, type TensionPoint, type TheoryLabel, TimeSignature, type TrackAnalysis, type TrackRole, type VoiceNote, VoiceSnapshot, analyzeArrangement, analyzeVoice, tensionCurve, tensionCurveFrom, toVoiceNotes };
258
+ export { type AugmentedSixthKind, Chord as ChordData, ChordLike, ChordTimeline, KeyLike, Note as NoteData, NoteEvent, NoteLike, type SpellLineOptions, augmentedSixthChord, augmentedSixthFromPitchClasses, augmentedSixthKind, spellAugmentedSixth, spellLine };