@libraz/libcantus 0.9.2 → 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.
Files changed (126) hide show
  1. package/README.md +4 -3
  2. package/dist/analyze/index.cjs +315 -42
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +11 -99
  5. package/dist/analyze/index.d.ts +11 -99
  6. package/dist/analyze/index.js +6 -6
  7. package/dist/chunk-2ZWMSJH6.js +30 -0
  8. package/dist/chunk-2ZWMSJH6.js.map +1 -0
  9. package/dist/{chunk-R3S2LUBH.js → chunk-4MHA432G.js} +316 -184
  10. package/dist/chunk-4MHA432G.js.map +1 -0
  11. package/dist/chunk-62QZKCJQ.js +49 -0
  12. package/dist/chunk-62QZKCJQ.js.map +1 -0
  13. package/dist/chunk-AKLYPWIN.js +83 -0
  14. package/dist/chunk-AKLYPWIN.js.map +1 -0
  15. package/dist/{chunk-XTC3X6MS.js → chunk-C5XYKUVG.js} +100 -12
  16. package/dist/chunk-C5XYKUVG.js.map +1 -0
  17. package/dist/{chunk-LN7ZZMD2.js → chunk-CHMXELDS.js} +33 -14
  18. package/dist/chunk-CHMXELDS.js.map +1 -0
  19. package/dist/{chunk-A4532ZFR.js → chunk-EH6EM37U.js} +60 -4
  20. package/dist/chunk-EH6EM37U.js.map +1 -0
  21. package/dist/chunk-FBQK3ZQN.js +131 -0
  22. package/dist/chunk-FBQK3ZQN.js.map +1 -0
  23. package/dist/{chunk-5HTNMQW4.js → chunk-GOAW4VYL.js} +87 -21
  24. package/dist/chunk-GOAW4VYL.js.map +1 -0
  25. package/dist/{chunk-MSWFZF7J.js → chunk-I5R7ZBJS.js} +90 -6
  26. package/dist/chunk-I5R7ZBJS.js.map +1 -0
  27. package/dist/{chunk-GQKN5KDT.js → chunk-IT2O4XFE.js} +4 -9
  28. package/dist/chunk-IT2O4XFE.js.map +1 -0
  29. package/dist/{chunk-7Y7WF64D.js → chunk-MIWO425M.js} +6 -4
  30. package/dist/chunk-MIWO425M.js.map +1 -0
  31. package/dist/chunk-N6IHQUOX.js +1 -0
  32. package/dist/chunk-N6IHQUOX.js.map +1 -0
  33. package/dist/{chunk-COKTX2G5.js → chunk-SGU7SPLK.js} +29 -12
  34. package/dist/chunk-SGU7SPLK.js.map +1 -0
  35. package/dist/core/index.cjs +230 -14
  36. package/dist/core/index.cjs.map +1 -1
  37. package/dist/core/index.d.cts +56 -4
  38. package/dist/core/index.d.ts +56 -4
  39. package/dist/core/index.js +24 -4
  40. package/dist/{function-2bJW1Zp_.d.ts → function-DVH4Eusr.d.ts} +1 -1
  41. package/dist/{function-r2sh-jOO.d.cts → function-DYt4iKsn.d.cts} +1 -1
  42. package/dist/generate/index.cjs +830 -494
  43. package/dist/generate/index.cjs.map +1 -1
  44. package/dist/generate/index.d.cts +11 -5
  45. package/dist/generate/index.d.ts +11 -5
  46. package/dist/generate/index.js +9 -9
  47. package/dist/{index-BOBXBNwP.d.cts → index-BNlGP86A.d.cts} +1 -1
  48. package/dist/{index-CP3wAfEH.d.ts → index-Bg5dYMmZ.d.ts} +1 -1
  49. package/dist/{index-CM5W4rcm.d.ts → index-Bt4tOUD9.d.ts} +1 -1
  50. package/dist/index-CnZCo29u.d.cts +108 -0
  51. package/dist/{index-D_ZTUNKp.d.cts → index-CpOsP1y9.d.cts} +2 -2
  52. package/dist/{index-DZ1M21do.d.cts → index-CxqBQtH1.d.cts} +2 -1
  53. package/dist/{index-BdeQ1r_p.d.cts → index-Dikst44v.d.cts} +1 -1
  54. package/dist/{index-BMPxwjwV.d.ts → index-DxvARIP2.d.ts} +2 -2
  55. package/dist/{index-BreoJRw0.d.ts → index-Jc0Ryi79.d.ts} +2 -1
  56. package/dist/index-JnF4v9bx.d.ts +108 -0
  57. package/dist/index.cjs +818 -283
  58. package/dist/index.cjs.map +1 -1
  59. package/dist/index.d.cts +9 -8
  60. package/dist/index.d.ts +9 -8
  61. package/dist/index.js +34 -13
  62. package/dist/model/index.cjs +170 -31
  63. package/dist/model/index.cjs.map +1 -1
  64. package/dist/model/index.d.cts +6 -5
  65. package/dist/model/index.d.ts +6 -5
  66. package/dist/model/index.js +6 -6
  67. package/dist/{styled-D7BAx3S3.d.ts → styled-DZ7GjShb.d.ts} +1 -1
  68. package/dist/{styled-BjXtOFNk.d.cts → styled-ZTbdaVFM.d.cts} +1 -1
  69. package/dist/theory/index.cjs +180 -34
  70. package/dist/theory/index.cjs.map +1 -1
  71. package/dist/theory/index.d.cts +6 -6
  72. package/dist/theory/index.d.ts +6 -6
  73. package/dist/theory/index.js +6 -5
  74. package/package.json +50 -19
  75. package/src/analyze/arrange/internal.ts +3 -1
  76. package/src/analyze/arrange/tension.ts +15 -3
  77. package/src/analyze/arrange/tracks.ts +36 -3
  78. package/src/analyze/detect/index.ts +46 -9
  79. package/src/analyze/functional/roman.ts +14 -5
  80. package/src/analyze/index.ts +1 -1
  81. package/src/analyze/timeline/index.ts +43 -7
  82. package/src/analyze/voice/index.ts +11 -2
  83. package/src/core/event-index/index.ts +104 -0
  84. package/src/core/index.ts +14 -0
  85. package/src/core/meter/index.ts +24 -4
  86. package/src/core/random/index.ts +26 -3
  87. package/src/core/tuning/index.ts +36 -6
  88. package/src/core/validation/index.ts +119 -0
  89. package/src/generate/bass/index.ts +23 -0
  90. package/src/generate/countermelody/index.ts +167 -76
  91. package/src/generate/drums/euclid.ts +10 -3
  92. package/src/generate/drums/index.ts +56 -24
  93. package/src/generate/drums/percussion.ts +1 -1
  94. package/src/generate/groove/index.ts +25 -0
  95. package/src/generate/harmonize/index.ts +55 -44
  96. package/src/generate/index.ts +0 -1
  97. package/src/generate/motif/index.ts +45 -21
  98. package/src/generate/progression/index.ts +8 -16
  99. package/src/generate/reharmony/index.ts +1 -8
  100. package/src/generate/rhythm/index.ts +11 -1
  101. package/src/index.ts +13 -247
  102. package/src/model/chord.ts +29 -10
  103. package/src/theory/chord/index.ts +4 -2
  104. package/src/theory/harmony/index.ts +6 -21
  105. package/src/theory/index.ts +6 -1
  106. package/src/theory/safety/index.ts +71 -2
  107. package/src/theory/spelling/index.ts +70 -3
  108. package/src/theory/voicing/satb.ts +25 -4
  109. package/src/theory/voicing/styled.ts +26 -3
  110. package/dist/chunk-5HTNMQW4.js.map +0 -1
  111. package/dist/chunk-7Y7WF64D.js.map +0 -1
  112. package/dist/chunk-A4532ZFR.js.map +0 -1
  113. package/dist/chunk-BHUT2S5G.js +0 -22
  114. package/dist/chunk-BHUT2S5G.js.map +0 -1
  115. package/dist/chunk-CCBLVAPK.js +0 -42
  116. package/dist/chunk-CCBLVAPK.js.map +0 -1
  117. package/dist/chunk-COKTX2G5.js.map +0 -1
  118. package/dist/chunk-GQKN5KDT.js.map +0 -1
  119. package/dist/chunk-LN7ZZMD2.js.map +0 -1
  120. package/dist/chunk-MSWFZF7J.js.map +0 -1
  121. package/dist/chunk-R3S2LUBH.js.map +0 -1
  122. package/dist/chunk-SDM3VJJ5.js +0 -41
  123. package/dist/chunk-SDM3VJJ5.js.map +0 -1
  124. package/dist/chunk-XTC3X6MS.js.map +0 -1
  125. package/dist/chunk-ZXW5YIWK.js +0 -55
  126. package/dist/chunk-ZXW5YIWK.js.map +0 -1
@@ -1,10 +1,11 @@
1
1
  import { T as TimeSignature } from '../index-Cl7mhOFM.cjs';
2
2
  import { K as KeyScale, N as NoteEvent } from '../types-MGP29r3t.cjs';
3
- import { V as VoiceSnapshot, N as NoteSafety, S as SafetyProfile } from '../index-DZ1M21do.cjs';
4
- import { b as ChordTimeline, C as CadenceHit } from '../index-D_ZTUNKp.cjs';
5
- export { a as ChordSegment, c as ChordTimelineOptions, d as ChordTimelineResult, e as chordTimelineFromChords, f as chordTimelineFromNotes, g as detectCadences } from '../index-D_ZTUNKp.cjs';
6
- import { C as Chord, a as ChordQuality } from '../index-BOBXBNwP.cjs';
7
- export { B as BorrowedSource, C as Cadence, a as ChordAnalysis, H as HarmonicFunction, b as analyzeChord, c as borrowedSource, d as detectCadence, f as functionOf, i as isBorrowedChord, e as isDiatonic, g as isMinorKey, p as parallelKey, s as secondaryDominant } from '../function-r2sh-jOO.cjs';
3
+ import { V as VoiceSnapshot, N as NoteSafety, S as SafetyProfile } from '../index-CxqBQtH1.cjs';
4
+ import { b as ChordTimeline, C as CadenceHit } from '../index-CpOsP1y9.cjs';
5
+ export { a as ChordSegment, c as ChordTimelineOptions, d as ChordTimelineResult, e as chordTimelineFromChords, f as chordTimelineFromNotes, g as detectCadences } from '../index-CpOsP1y9.cjs';
6
+ import { C as Chord } from '../index-BNlGP86A.cjs';
7
+ export { C as ChordMatch, D as DetectChordOptions, K as KeyMatch, d as detectChord, a as detectChordBest, b as detectKey } from '../index-CnZCo29u.cjs';
8
+ export { B as BorrowedSource, C as Cadence, a as ChordAnalysis, H as HarmonicFunction, b as analyzeChord, c as borrowedSource, d as detectCadence, f as functionOf, i as isBorrowedChord, e as isDiatonic, g as isMinorKey, p as parallelKey, s as secondaryDominant } from '../function-DYt4iKsn.cjs';
8
9
 
9
10
  /**
10
11
  * Roman-numeral parsing and formatting.
@@ -203,6 +204,10 @@ type Conflict = {
203
204
  safety: NoteSafety;
204
205
  /** {@link import('../safety/index.js').SafetyResult.reasons} bitmask. */
205
206
  reasons: number;
207
+ /** Preferred stepwise resolution, when the safety evaluator provides one. */
208
+ resolveTo?: number;
209
+ /** Nearby fully safe replacement pitches, nearest first. */
210
+ suggestions?: number[];
206
211
  rationale?: string;
207
212
  };
208
213
  /**
@@ -333,97 +338,4 @@ declare function tensionCurve(tracks: ArrangementTrack[], opts?: ArrangementOpti
333
338
  step?: number;
334
339
  }): TensionPoint[];
335
340
 
336
- /**
337
- * Recognition: infer a chord name from a set of pitches, or a key from a set of
338
- * pitch classes. This is the inverse direction of the chord/scale builders,
339
- * which only go name -> notes.
340
- */
341
-
342
- /**
343
- * A candidate chord interpretation of a pitch set.
344
- *
345
- * @category Recognition
346
- */
347
- type ChordMatch = {
348
- rootPc: number;
349
- quality: ChordQuality;
350
- /** Chord tones absent from the input (an incomplete voicing). */
351
- missingPcs: number[];
352
- /** Input pitch classes not belonging to the chord. */
353
- extraPcs: number[];
354
- /** True when the input pitch-class set equals the chord exactly. */
355
- exact: boolean;
356
- /** Inversion implied by the lowest note: 0 root position, 1 first, ... */
357
- inversion: number;
358
- /** Bass pitch class when inverted (the lowest note is not the root). */
359
- bassPc?: number;
360
- };
361
- /**
362
- * A candidate key interpretation of a pitch-class set.
363
- *
364
- * @category Recognition
365
- */
366
- type KeyMatch = {
367
- key: KeyScale;
368
- mode: 'major' | 'minor';
369
- /** Fraction of input pitch classes that are in the scale, in [0, 1]. */
370
- fit: number;
371
- };
372
- /**
373
- * Identify chords matching a set of pitches.
374
- *
375
- * Every input pitch class is tried as a root against every known chord quality.
376
- * A match is reported when all of the chord's tones are present in the input, or
377
- * when the only absent tone is the perfect fifth (a common omission in shell
378
- * voicings); matches are ranked best-first by fewest extra notes, then fewest
379
- * missing notes, then most specific (largest) chord. An exact match (no extras,
380
- * no missing) is flagged and ranked first.
381
- *
382
- * @param pitches MIDI pitches or bare pitch classes (octave-agnostic).
383
- * @returns Ranked chord interpretations (may be empty).
384
- * @example
385
- * ```ts
386
- * import { detectChord } from '@libraz/libcantus';
387
- * const matches = detectChord([60, 64, 67]); // C E G
388
- * matches[0]; // { rootPc: 0, quality: 'maj', exact: true, ... }
389
- * ```
390
- * @category Recognition
391
- */
392
- declare function detectChord(pitches: number[]): ChordMatch[];
393
- /**
394
- * The single best chord interpretation of a pitch set, or null if none.
395
- *
396
- * @param pitches MIDI pitches or bare pitch classes.
397
- * @returns The top-ranked chord, or null when nothing matches.
398
- * @example
399
- * ```ts
400
- * import { detectChordBest } from '@libraz/libcantus';
401
- * detectChordBest([60, 64, 67]); // C major triad: { rootPc: 0, quality: 'maj', ... }
402
- * ```
403
- * @category Recognition
404
- */
405
- declare function detectChordBest(pitches: number[]): Chord | null;
406
- /**
407
- * Rank major and minor keys by how well they contain a set of pitch classes.
408
- *
409
- * The tonic is weighted so that, among equally-fitting keys, the one whose root
410
- * appears in the input is preferred. Minor candidates are scored against the
411
- * natural, harmonic, and melodic minor variants and keep the best of the three,
412
- * so a minor cadence containing the leading tone still resolves to its own
413
- * tonic; the returned key is always the natural-minor scale. Returns all 24
414
- * keys ranked best-first, or an empty array for an empty input (mirroring
415
- * {@link detectChord}).
416
- *
417
- * @param pitches MIDI pitches or bare pitch classes.
418
- * @returns Ranked key interpretations (empty for an empty input).
419
- * @example
420
- * ```ts
421
- * import { detectKey } from '@libraz/libcantus';
422
- * const keys = detectKey([60, 62, 64, 65, 67, 69, 71]); // C major scale
423
- * keys[0].mode; // 'major', with keys[0].key.rootPc === 0
424
- * ```
425
- * @category Recognition
426
- */
427
- declare function detectKey(pitches: number[]): KeyMatch[];
428
-
429
- export { type AnalyzedNote, type ArrangementAnalysis, type ArrangementOptions, type ArrangementTrack, CadenceHit, type ChordMatch, ChordTimeline, type Conflict, type KeyMatch, type TensionPoint, type TheoryLabel, type TrackAnalysis, type TrackRole, type VoiceNote, analyzeArrangement, analyzeVoice, chordToRoman, detectChord, detectChordBest, detectKey, romanToChord, tensionCurve };
341
+ export { type AnalyzedNote, type ArrangementAnalysis, type ArrangementOptions, type ArrangementTrack, CadenceHit, ChordTimeline, type Conflict, type TensionPoint, type TheoryLabel, type TrackAnalysis, type TrackRole, type VoiceNote, analyzeArrangement, analyzeVoice, chordToRoman, romanToChord, tensionCurve };
@@ -1,10 +1,11 @@
1
1
  import { T as TimeSignature } from '../index-Cl7mhOFM.js';
2
2
  import { K as KeyScale, N as NoteEvent } from '../types-MGP29r3t.js';
3
- import { V as VoiceSnapshot, N as NoteSafety, S as SafetyProfile } from '../index-BreoJRw0.js';
4
- import { b as ChordTimeline, C as CadenceHit } from '../index-BMPxwjwV.js';
5
- export { a as ChordSegment, c as ChordTimelineOptions, d as ChordTimelineResult, e as chordTimelineFromChords, f as chordTimelineFromNotes, g as detectCadences } from '../index-BMPxwjwV.js';
6
- import { C as Chord, a as ChordQuality } from '../index-CM5W4rcm.js';
7
- export { B as BorrowedSource, C as Cadence, a as ChordAnalysis, H as HarmonicFunction, b as analyzeChord, c as borrowedSource, d as detectCadence, f as functionOf, i as isBorrowedChord, e as isDiatonic, g as isMinorKey, p as parallelKey, s as secondaryDominant } from '../function-2bJW1Zp_.js';
3
+ import { V as VoiceSnapshot, N as NoteSafety, S as SafetyProfile } from '../index-Jc0Ryi79.js';
4
+ import { b as ChordTimeline, C as CadenceHit } from '../index-DxvARIP2.js';
5
+ export { a as ChordSegment, c as ChordTimelineOptions, d as ChordTimelineResult, e as chordTimelineFromChords, f as chordTimelineFromNotes, g as detectCadences } from '../index-DxvARIP2.js';
6
+ import { C as Chord } from '../index-Bt4tOUD9.js';
7
+ export { C as ChordMatch, D as DetectChordOptions, K as KeyMatch, d as detectChord, a as detectChordBest, b as detectKey } from '../index-JnF4v9bx.js';
8
+ export { B as BorrowedSource, C as Cadence, a as ChordAnalysis, H as HarmonicFunction, b as analyzeChord, c as borrowedSource, d as detectCadence, f as functionOf, i as isBorrowedChord, e as isDiatonic, g as isMinorKey, p as parallelKey, s as secondaryDominant } from '../function-DVH4Eusr.js';
8
9
 
9
10
  /**
10
11
  * Roman-numeral parsing and formatting.
@@ -203,6 +204,10 @@ type Conflict = {
203
204
  safety: NoteSafety;
204
205
  /** {@link import('../safety/index.js').SafetyResult.reasons} bitmask. */
205
206
  reasons: number;
207
+ /** Preferred stepwise resolution, when the safety evaluator provides one. */
208
+ resolveTo?: number;
209
+ /** Nearby fully safe replacement pitches, nearest first. */
210
+ suggestions?: number[];
206
211
  rationale?: string;
207
212
  };
208
213
  /**
@@ -333,97 +338,4 @@ declare function tensionCurve(tracks: ArrangementTrack[], opts?: ArrangementOpti
333
338
  step?: number;
334
339
  }): TensionPoint[];
335
340
 
336
- /**
337
- * Recognition: infer a chord name from a set of pitches, or a key from a set of
338
- * pitch classes. This is the inverse direction of the chord/scale builders,
339
- * which only go name -> notes.
340
- */
341
-
342
- /**
343
- * A candidate chord interpretation of a pitch set.
344
- *
345
- * @category Recognition
346
- */
347
- type ChordMatch = {
348
- rootPc: number;
349
- quality: ChordQuality;
350
- /** Chord tones absent from the input (an incomplete voicing). */
351
- missingPcs: number[];
352
- /** Input pitch classes not belonging to the chord. */
353
- extraPcs: number[];
354
- /** True when the input pitch-class set equals the chord exactly. */
355
- exact: boolean;
356
- /** Inversion implied by the lowest note: 0 root position, 1 first, ... */
357
- inversion: number;
358
- /** Bass pitch class when inverted (the lowest note is not the root). */
359
- bassPc?: number;
360
- };
361
- /**
362
- * A candidate key interpretation of a pitch-class set.
363
- *
364
- * @category Recognition
365
- */
366
- type KeyMatch = {
367
- key: KeyScale;
368
- mode: 'major' | 'minor';
369
- /** Fraction of input pitch classes that are in the scale, in [0, 1]. */
370
- fit: number;
371
- };
372
- /**
373
- * Identify chords matching a set of pitches.
374
- *
375
- * Every input pitch class is tried as a root against every known chord quality.
376
- * A match is reported when all of the chord's tones are present in the input, or
377
- * when the only absent tone is the perfect fifth (a common omission in shell
378
- * voicings); matches are ranked best-first by fewest extra notes, then fewest
379
- * missing notes, then most specific (largest) chord. An exact match (no extras,
380
- * no missing) is flagged and ranked first.
381
- *
382
- * @param pitches MIDI pitches or bare pitch classes (octave-agnostic).
383
- * @returns Ranked chord interpretations (may be empty).
384
- * @example
385
- * ```ts
386
- * import { detectChord } from '@libraz/libcantus';
387
- * const matches = detectChord([60, 64, 67]); // C E G
388
- * matches[0]; // { rootPc: 0, quality: 'maj', exact: true, ... }
389
- * ```
390
- * @category Recognition
391
- */
392
- declare function detectChord(pitches: number[]): ChordMatch[];
393
- /**
394
- * The single best chord interpretation of a pitch set, or null if none.
395
- *
396
- * @param pitches MIDI pitches or bare pitch classes.
397
- * @returns The top-ranked chord, or null when nothing matches.
398
- * @example
399
- * ```ts
400
- * import { detectChordBest } from '@libraz/libcantus';
401
- * detectChordBest([60, 64, 67]); // C major triad: { rootPc: 0, quality: 'maj', ... }
402
- * ```
403
- * @category Recognition
404
- */
405
- declare function detectChordBest(pitches: number[]): Chord | null;
406
- /**
407
- * Rank major and minor keys by how well they contain a set of pitch classes.
408
- *
409
- * The tonic is weighted so that, among equally-fitting keys, the one whose root
410
- * appears in the input is preferred. Minor candidates are scored against the
411
- * natural, harmonic, and melodic minor variants and keep the best of the three,
412
- * so a minor cadence containing the leading tone still resolves to its own
413
- * tonic; the returned key is always the natural-minor scale. Returns all 24
414
- * keys ranked best-first, or an empty array for an empty input (mirroring
415
- * {@link detectChord}).
416
- *
417
- * @param pitches MIDI pitches or bare pitch classes.
418
- * @returns Ranked key interpretations (empty for an empty input).
419
- * @example
420
- * ```ts
421
- * import { detectKey } from '@libraz/libcantus';
422
- * const keys = detectKey([60, 62, 64, 65, 67, 69, 71]); // C major scale
423
- * keys[0].mode; // 'major', with keys[0].key.rootPc === 0
424
- * ```
425
- * @category Recognition
426
- */
427
- declare function detectKey(pitches: number[]): KeyMatch[];
428
-
429
- export { type AnalyzedNote, type ArrangementAnalysis, type ArrangementOptions, type ArrangementTrack, CadenceHit, type ChordMatch, ChordTimeline, type Conflict, type KeyMatch, type TensionPoint, type TheoryLabel, type TrackAnalysis, type TrackRole, type VoiceNote, analyzeArrangement, analyzeVoice, chordToRoman, detectChord, detectChordBest, detectKey, romanToChord, tensionCurve };
341
+ export { type AnalyzedNote, type ArrangementAnalysis, type ArrangementOptions, type ArrangementTrack, CadenceHit, ChordTimeline, type Conflict, type TensionPoint, type TheoryLabel, type TrackAnalysis, type TrackRole, type VoiceNote, analyzeArrangement, analyzeVoice, chordToRoman, romanToChord, tensionCurve };
@@ -5,8 +5,8 @@ import {
5
5
  chordTimelineFromNotes,
6
6
  detectCadences,
7
7
  tensionCurve
8
- } from "../chunk-5HTNMQW4.js";
9
- import "../chunk-MSWFZF7J.js";
8
+ } from "../chunk-GOAW4VYL.js";
9
+ import "../chunk-I5R7ZBJS.js";
10
10
  import {
11
11
  analyzeChord,
12
12
  borrowedSource,
@@ -22,10 +22,10 @@ import {
22
22
  parallelKey,
23
23
  romanToChord,
24
24
  secondaryDominant
25
- } from "../chunk-LN7ZZMD2.js";
26
- import "../chunk-A4532ZFR.js";
27
- import "../chunk-7Y7WF64D.js";
28
- import "../chunk-CCBLVAPK.js";
25
+ } from "../chunk-CHMXELDS.js";
26
+ import "../chunk-EH6EM37U.js";
27
+ import "../chunk-MIWO425M.js";
28
+ import "../chunk-FBQK3ZQN.js";
29
29
  export {
30
30
  analyzeArrangement,
31
31
  analyzeChord,
@@ -0,0 +1,30 @@
1
+ import {
2
+ chordToneRole
3
+ } from "./chunk-MIWO425M.js";
4
+
5
+ // src/theory/harmony/index.ts
6
+ function roleOf(pitch, chord, chordId = 0) {
7
+ const interval = ((Math.trunc(pitch) - chord.rootPc) % 12 + 12) % 12;
8
+ const tones = new Set(chord.intervals.map((i) => (i % 12 + 12) % 12));
9
+ const hasThird = tones.has(3) || tones.has(4);
10
+ const isSuspendedTone = !hasThird && (interval === 5 && tones.has(5) || interval === 2 && tones.has(2));
11
+ const chordRole = chordToneRole(pitch, chord);
12
+ let role;
13
+ let lock;
14
+ if (isSuspendedTone) {
15
+ role = "third";
16
+ lock = "quality";
17
+ } else if (chordRole !== null) {
18
+ role = chordRole;
19
+ lock = chordRole === "root" ? "identity" : chordRole === "third" ? "quality" : "voicing";
20
+ } else {
21
+ role = "tension";
22
+ lock = "voicing";
23
+ }
24
+ return { role, lock, belongsToChordId: chordId };
25
+ }
26
+
27
+ export {
28
+ roleOf
29
+ };
30
+ //# sourceMappingURL=chunk-2ZWMSJH6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/theory/harmony/index.ts"],"sourcesContent":["import { type Chord, chordToneRole } from '../chord/index.js';\n\n/**\n * Harmonic role a pitch plays within a chord.\n *\n * @category Functional Harmony\n */\nexport type HarmonyRole = 'root' | 'third' | 'fifth' | 'sixth' | 'seventh' | 'tension' | 'doubling';\n\n/**\n * How firmly a pitch is locked to the chord's identity.\n *\n * - `identity`: moving it produces a different chord (the root).\n * - `quality`: moving it flips the chord quality (the third).\n * - `voicing`: it can move freely without changing chord identity or quality.\n *\n * @category Functional Harmony\n */\nexport type LockLevel = 'identity' | 'quality' | 'voicing';\n\n/**\n * A pitch's role, lock level, and owning chord.\n *\n * @category Functional Harmony\n */\nexport type VoicedRole = {\n role: HarmonyRole;\n lock: LockLevel;\n belongsToChordId: number;\n};\n\n/**\n * Classify a pitch's harmonic role and lock level within a chord.\n *\n * The role comes from the pitch's interval class above the chord root: root (0),\n * third (3/4), fifth (6/7/8), seventh (10/11), and 9/11/13 tensions (2/5/9, and\n * the flat ninth 1). The root locks the chord identity, the third locks its\n * quality, and everything else is free voicing. In a suspended chord (a chord\n * whose intervals include a 4th (5) or 2nd (2) but no third), the suspended tone\n * takes the third's place as the quality-defining tone and is locked to\n * `quality`, since moving it changes the chord. Detecting an octave doubling\n * requires the surrounding voicing, which this single-pitch query does not carry,\n * so `doubling` is part of the type but not returned here.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param chord The chord providing the root reference.\n * @param chordId Identifier stored on the result (defaults to 0).\n * @returns The pitch's role, lock level, and owning chord id.\n * @example\n * ```ts\n * import { roleOf, makeChord } from '@libraz/libcantus';\n * roleOf(64, makeChord(0, 'maj')); // E over C major\n * // { role: 'third', lock: 'quality', belongsToChordId: 0 }\n * ```\n * @category Functional Harmony\n */\nexport function roleOf(pitch: number, chord: Chord, chordId = 0): VoicedRole {\n const interval = (((Math.trunc(pitch) - chord.rootPc) % 12) + 12) % 12;\n const tones = new Set(chord.intervals.map((i) => ((i % 12) + 12) % 12));\n const hasThird = tones.has(3) || tones.has(4);\n const isSuspendedTone =\n !hasThird && ((interval === 5 && tones.has(5)) || (interval === 2 && tones.has(2)));\n const chordRole = chordToneRole(pitch, chord);\n let role: HarmonyRole;\n let lock: LockLevel;\n if (isSuspendedTone) {\n role = 'third'; // suspended tone occupies the third's quality-defining slot\n lock = 'quality';\n } else if (chordRole !== null) {\n role = chordRole;\n lock = chordRole === 'root' ? 'identity' : chordRole === 'third' ? 'quality' : 'voicing';\n } else {\n role = 'tension';\n lock = 'voicing';\n }\n return { role, lock, belongsToChordId: chordId };\n}\n"],"mappings":";;;;;AAwDO,SAAS,OAAO,OAAe,OAAc,UAAU,GAAe;AAC3E,QAAM,aAAc,KAAK,MAAM,KAAK,IAAI,MAAM,UAAU,KAAM,MAAM;AACpE,QAAM,QAAQ,IAAI,IAAI,MAAM,UAAU,IAAI,CAAC,OAAQ,IAAI,KAAM,MAAM,EAAE,CAAC;AACtE,QAAM,WAAW,MAAM,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC;AAC5C,QAAM,kBACJ,CAAC,aAAc,aAAa,KAAK,MAAM,IAAI,CAAC,KAAO,aAAa,KAAK,MAAM,IAAI,CAAC;AAClF,QAAM,YAAY,cAAc,OAAO,KAAK;AAC5C,MAAI;AACJ,MAAI;AACJ,MAAI,iBAAiB;AACnB,WAAO;AACP,WAAO;AAAA,EACT,WAAW,cAAc,MAAM;AAC7B,WAAO;AACP,WAAO,cAAc,SAAS,aAAa,cAAc,UAAU,YAAY;AAAA,EACjF,OAAO;AACL,WAAO;AACP,WAAO;AAAA,EACT;AACA,SAAO,EAAE,MAAM,MAAM,kBAAkB,QAAQ;AACjD;","names":[]}