@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 +0,0 @@
1
- {"version":3,"sources":["../src/core/tuning/index.ts"],"sourcesContent":["/**\n * Tuning and microtonality: frequency conversion, cents, arbitrary equal\n * temperaments (EDO), and just-intonation ratios.\n *\n * The rest of the library reasons in twelve-tone pitch classes, which is a\n * deliberate scope choice for tonal theory. This module is the escape hatch for\n * anyone who needs actual frequencies, cents, non-12 equal temperaments, or the\n * acoustic (just) tuning behind the tempered intervals.\n */\n\nimport { InvalidInputError } from '../errors/index.js';\nimport {\n assertFiniteNumber,\n assertInteger,\n assertPositiveInt,\n assertRange,\n} from '../validation/index.js';\n\n/**\n * An equal-tempered tuning: a reference pitch and a number of equal divisions of\n * the octave. With `divisions === 12` a step index is an ordinary MIDI number.\n *\n * @category Pitch & Intervals\n */\nexport type Tuning = {\n /** Step index (MIDI number when `divisions` is 12) whose frequency is `refFreq`. */\n readonly refStep: number;\n /** Frequency in Hz of `refStep`. */\n readonly refFreq: number;\n /** Equal divisions of the octave (12 standard; e.g. 19 or 31 for microtonal). */\n readonly divisions: number;\n};\n\n/**\n * Standard twelve-tone equal temperament, A4 (MIDI 69) = 440 Hz.\n *\n * @category Pitch & Intervals\n */\nexport const TWELVE_TET: Tuning = Object.freeze({ refStep: 69, refFreq: 440, divisions: 12 });\n\nfunction assertTuning(tuning: Tuning): Tuning {\n assertFiniteNumber(tuning.refStep, 'tuning.refStep');\n assertRange(tuning.refFreq, Number.MIN_VALUE, Number.MAX_VALUE, 'tuning.refFreq');\n assertPositiveInt(tuning.divisions, 'tuning.divisions');\n return tuning;\n}\n\n/**\n * Build an equal temperament with `n` divisions of the octave.\n *\n * @param n Divisions of the octave (e.g. 19, 24, 31).\n * @param refFreq Reference frequency in Hz (default 440).\n * @param refStep Step index of the reference (default 69).\n * @returns The tuning.\n * @example\n * ```ts\n * import { edo, frequencyOf } from '@libraz/libcantus';\n * const et19 = edo(19); // { refStep: 69, refFreq: 440, divisions: 19 }\n * frequencyOf(70, et19); // one 19-EDO step above A4\n * ```\n * @category Pitch & Intervals\n */\nexport function edo(n: number, refFreq = 440, refStep = 69): Tuning {\n assertPositiveInt(n, 'EDO divisions');\n assertRange(refFreq, Number.MIN_VALUE, Number.MAX_VALUE, 'reference frequency');\n assertFiniteNumber(refStep, 'reference step');\n return { refStep, refFreq, divisions: n };\n}\n\n/**\n * Frequency in Hz of a step index under a tuning.\n *\n * @param step Step index (a MIDI number under 12-EDO).\n * @param tuning The tuning (default 12-TET).\n * @returns The frequency in Hz.\n * @example\n * ```ts\n * import { frequencyOf } from '@libraz/libcantus';\n * frequencyOf(69); // 440 (A4 in 12-TET)\n * frequencyOf(60); // middle C in Hz\n * ```\n * @category Pitch & Intervals\n */\nexport function frequencyOf(step: number, tuning: Tuning = TWELVE_TET): number {\n assertFiniteNumber(step, 'step');\n assertTuning(tuning);\n const result = tuning.refFreq * 2 ** ((step - tuning.refStep) / tuning.divisions);\n return assertRange(result, Number.MIN_VALUE, Number.MAX_VALUE, 'frequency result');\n}\n\n/**\n * Nearest step index to a frequency under a tuning (the inverse of\n * {@link frequencyOf}, rounded).\n *\n * @param freq Frequency in Hz.\n * @param tuning The tuning (default 12-TET).\n * @returns The nearest step index.\n * @category Pitch & Intervals\n */\nexport function nearestStep(freq: number, tuning: Tuning = TWELVE_TET): number {\n assertRange(freq, Number.MIN_VALUE, Number.MAX_VALUE, 'frequency');\n assertTuning(tuning);\n return Math.round(tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq));\n}\n\n/**\n * Step index of a frequency under a tuning, unrounded.\n *\n * The exact inverse of {@link frequencyOf}. {@link nearestStep} rounds it,\n * which discards how far off the pitch actually is — the figure a tuner\n * display, a pitch-bend amount, or an intonation check is built on.\n *\n * @param freq Frequency in Hz.\n * @param tuning The tuning (default 12-TET).\n * @returns The fractional step index.\n * @example\n * ```ts\n * import { stepOf } from '@libraz/libcantus';\n * stepOf(442); // slightly above 69, the step for A440\n * ```\n * @category Pitch & Intervals\n */\nexport function stepOf(freq: number, tuning: Tuning = TWELVE_TET): number {\n assertRange(freq, Number.MIN_VALUE, Number.MAX_VALUE, 'frequency');\n assertTuning(tuning);\n return tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq);\n}\n\n/**\n * How far a frequency sits from its nearest step, in cents.\n *\n * Positive means sharp of the step, negative flat.\n *\n * @param freq Frequency in Hz.\n * @param tuning The tuning (default 12-TET).\n * @returns The signed deviation in cents.\n * @example\n * ```ts\n * import { centsFromNearestStep } from '@libraz/libcantus';\n * Math.round(centsFromNearestStep(442)); // 8\n * ```\n * @category Pitch & Intervals\n */\nexport function centsFromNearestStep(freq: number, tuning: Tuning = TWELVE_TET): number {\n const exact = stepOf(freq, tuning);\n return centsOfSteps(exact - Math.round(exact), tuning);\n}\n\n/**\n * Steps spanned by an interval in cents, unrounded — the inverse of\n * {@link centsOfSteps}.\n *\n * @param cents The interval in cents.\n * @param tuning The tuning (default 12-TET).\n * @returns The fractional step count.\n * @category Pitch & Intervals\n */\nexport function stepsOfCents(cents: number, tuning: Tuning = TWELVE_TET): number {\n assertFiniteNumber(cents, 'cents');\n assertTuning(tuning);\n return assertFiniteNumber((cents * tuning.divisions) / 1200, 'steps result');\n}\n\n/**\n * The frequency ratio an interval in cents spans — the inverse of\n * {@link ratioToCents}.\n *\n * Multiply a frequency by this to move it by that many cents, which is how a\n * cents offset (a pitch-bend spec, a tuning table entry) is applied.\n *\n * @param cents The interval in cents.\n * @returns The frequency ratio.\n * @example\n * ```ts\n * import { centsToRatio } from '@libraz/libcantus';\n * 440 * centsToRatio(14); // A440 raised by 14 cents\n * ```\n * @category Pitch & Intervals\n */\nexport function centsToRatio(cents: number): number {\n assertFiniteNumber(cents, 'cents');\n return 2 ** (cents / 1200);\n}\n\n/**\n * Interval in cents between two frequencies.\n *\n * @param a Lower/first frequency in Hz.\n * @param b Upper/second frequency in Hz.\n * @returns Cents from `a` to `b` (negative if `b` is lower).\n * @category Pitch & Intervals\n */\nexport function centsBetweenFreq(a: number, b: number): number {\n assertRange(a, Number.MIN_VALUE, Number.MAX_VALUE, 'first frequency');\n assertRange(b, Number.MIN_VALUE, Number.MAX_VALUE, 'second frequency');\n return 1200 * (Math.log2(b) - Math.log2(a));\n}\n\n/**\n * Cents spanned by a number of equal-temperament steps.\n *\n * @param steps Number of steps.\n * @param tuning The tuning (default 12-TET).\n * @returns The cents.\n * @category Pitch & Intervals\n */\nexport function centsOfSteps(steps: number, tuning: Tuning = TWELVE_TET): number {\n assertFiniteNumber(steps, 'steps');\n assertTuning(tuning);\n return assertFiniteNumber((steps * 1200) / tuning.divisions, 'cents result');\n}\n\n/**\n * Cents of a frequency ratio, e.g. `ratioToCents(3, 2)` ≈ 701.955 for the just\n * perfect fifth.\n *\n * @param numerator Ratio numerator.\n * @param denominator Ratio denominator.\n * @returns The interval in cents.\n * @category Pitch & Intervals\n */\nexport function ratioToCents(numerator: number, denominator: number): number {\n assertRange(numerator, Number.MIN_VALUE, Number.MAX_VALUE, 'ratio numerator');\n assertRange(denominator, Number.MIN_VALUE, Number.MAX_VALUE, 'ratio denominator');\n return 1200 * (Math.log2(numerator) - Math.log2(denominator));\n}\n\n/**\n * Five-limit just-intonation ratios for the twelve interval classes above a\n * unison, indexed by semitone class (0..12).\n *\n * @category Pitch & Intervals\n */\nfunction justRatio(numerator: number, denominator: number): readonly [number, number] {\n return Object.freeze([numerator, denominator] as [number, number]);\n}\n\n/**\n * Five-limit just-intonation ratios for the twelve interval classes above a\n * unison, indexed by semitone class (0..12).\n *\n * @category Pitch & Intervals\n */\nexport const JUST_RATIOS: Readonly<Record<number, readonly [number, number]>> = Object.freeze({\n 0: justRatio(1, 1),\n 1: justRatio(16, 15),\n 2: justRatio(9, 8),\n 3: justRatio(6, 5),\n 4: justRatio(5, 4),\n 5: justRatio(4, 3),\n 6: justRatio(45, 32),\n 7: justRatio(3, 2),\n 8: justRatio(8, 5),\n 9: justRatio(5, 3),\n 10: justRatio(9, 5),\n 11: justRatio(15, 8),\n 12: justRatio(2, 1),\n});\n\n/**\n * Cents by which a five-limit just interval departs from its 12-TET tempering\n * (positive means the just interval is wider).\n *\n * @param semitoneClass Semitone class in [0, 12].\n * @returns The deviation in cents.\n * @throws If `semitoneClass` is not an integer in [0, 12].\n * @category Pitch & Intervals\n */\nexport function justDeviationCents(semitoneClass: number): number {\n assertInteger(semitoneClass, 'semitone class', 0, 12);\n const ratio = JUST_RATIOS[semitoneClass];\n if (!ratio) {\n throw new InvalidInputError(`semitone class has no just ratio: ${semitoneClass}`);\n }\n return ratioToCents(ratio[0], ratio[1]) - semitoneClass * 100;\n}\n"],"mappings":"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","names":[]}
@@ -1,33 +0,0 @@
1
- import {
2
- chordToneRole
3
- } from "./chunk-KBTTEX7Q.js";
4
- import {
5
- pitchClassOf
6
- } from "./chunk-R2VVFAIN.js";
7
-
8
- // src/theory/harmony/index.ts
9
- function roleOf(pitch, chord, chordId = 0) {
10
- const interval = (pitchClassOf(pitch) - pitchClassOf(chord.rootPc) + 12) % 12;
11
- const tones = new Set(chord.intervals.map(pitchClassOf));
12
- const hasThird = tones.has(3) || tones.has(4);
13
- const isSuspendedTone = !hasThird && (interval === 5 && tones.has(5) || interval === 2 && tones.has(2));
14
- const chordRole = chordToneRole(pitch, chord);
15
- let role;
16
- let lock;
17
- if (isSuspendedTone) {
18
- role = "third";
19
- lock = "quality";
20
- } else if (chordRole !== null) {
21
- role = chordRole;
22
- lock = chordRole === "root" ? "identity" : chordRole === "third" ? "quality" : "voicing";
23
- } else {
24
- role = "tension";
25
- lock = "voicing";
26
- }
27
- return { role, lock, belongsToChordId: chordId };
28
- }
29
-
30
- export {
31
- roleOf
32
- };
33
- //# sourceMappingURL=chunk-HN2OEUN2.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/theory/harmony/index.ts"],"sourcesContent":["import { pitchClassOf } from '../../core/pitch/index.js';\nimport { type Chord, type ChordToneRole, 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 = ChordToneRole | '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, read\n * against the chord's own template: root (0), third (3/4), fifth (6/7/8),\n * sixth or seventh (9/10/11). Every pitch the template does not claim is\n * `'tension'` — including one that is simply foreign to the chord, which this\n * single-pitch query cannot tell from a colour tone. The root locks the chord\n * identity, the third locks its quality, and everything else is free voicing.\n * In a suspended chord (a chord whose intervals include a 4th (5) or 2nd (2)\n * but no third), the suspended tone takes the third's place as the\n * quality-defining tone and is locked to `quality`, since moving it changes the\n * chord. Detecting an octave doubling requires the surrounding voicing, which\n * this query does not carry, so `'doubling'` is part of the type — the shared\n * vocabulary of harmonic roles — but is never 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 = (pitchClassOf(pitch) - pitchClassOf(chord.rootPc) + 12) % 12;\n const tones = new Set(chord.intervals.map(pitchClassOf));\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":";;;;;;;;AA4DO,SAAS,OAAO,OAAe,OAAc,UAAU,GAAe;AAC3E,QAAM,YAAY,aAAa,KAAK,IAAI,aAAa,MAAM,MAAM,IAAI,MAAM;AAC3E,QAAM,QAAQ,IAAI,IAAI,MAAM,UAAU,IAAI,YAAY,CAAC;AACvD,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":[]}
@@ -1,497 +0,0 @@
1
- import {
2
- InvalidInputError,
3
- assertFiniteNumber,
4
- assertInteger,
5
- clampToMidi,
6
- isConsonantInterval,
7
- pitchClassOf
8
- } from "./chunk-R2VVFAIN.js";
9
-
10
- // src/theory/scale/degrees.ts
11
- function scaleOffset(pitch, key) {
12
- return (pitchClassOf(pitch) - pitchClassOf(key.rootPc) + 12) % 12;
13
- }
14
- function isScaleTone(pitch, key) {
15
- return (key.modeMask12 >> scaleOffset(pitch, key) & 1) === 1;
16
- }
17
- function nearestScaleTone(pitch, key) {
18
- const base = clampToMidi(Math.round(pitch), "pitch");
19
- for (let distance = 0; distance < 12; distance += 1) {
20
- const lower = base - distance;
21
- if (lower >= 0 && isScaleTone(lower, key)) {
22
- return lower;
23
- }
24
- const higher = base + distance;
25
- if (higher <= 127 && isScaleTone(higher, key)) {
26
- return higher;
27
- }
28
- }
29
- return base;
30
- }
31
- function pitchToScaleDegree(pitch, key) {
32
- const offset = scaleOffset(pitch, key);
33
- if ((key.modeMask12 >> offset & 1) === 0) {
34
- return -1;
35
- }
36
- let degree = 0;
37
- for (let n = 0; n < offset; n += 1) {
38
- if ((key.modeMask12 >> n & 1) === 1) {
39
- degree += 1;
40
- }
41
- }
42
- return degree;
43
- }
44
- function diatonicPitchClasses(key) {
45
- const root = pitchClassOf(key.rootPc);
46
- const pcs = [];
47
- for (let n = 0; n < 12; n += 1) {
48
- if ((key.modeMask12 >> n & 1) === 1) {
49
- pcs.push((root + n) % 12);
50
- }
51
- }
52
- return pcs.sort((a, b) => a - b);
53
- }
54
- function scaleTonesInDegreeOrder(key) {
55
- const root = pitchClassOf(key.rootPc);
56
- const pcs = [];
57
- for (let n = 0; n < 12; n += 1) {
58
- if ((key.modeMask12 >> n & 1) === 1) {
59
- pcs.push((root + n) % 12);
60
- }
61
- }
62
- return pcs;
63
- }
64
-
65
- // src/theory/scale/masks.ts
66
- function maskFromOffsets(offsets) {
67
- let mask = 1;
68
- for (const [index, offset] of offsets.entries()) {
69
- assertInteger(offset, `offsets[${index}]`);
70
- mask |= 1 << (offset % 12 + 12) % 12;
71
- }
72
- return mask;
73
- }
74
- var MAJOR_MASK = 2741;
75
- var NATURAL_MINOR_MASK = 1453;
76
- var HARMONIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 8, 11]);
77
- var MELODIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 11]);
78
- var DORIAN_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 10]);
79
- var PHRYGIAN_MASK = maskFromOffsets([0, 1, 3, 5, 7, 8, 10]);
80
- var LYDIAN_MASK = maskFromOffsets([0, 2, 4, 6, 7, 9, 11]);
81
- var MIXOLYDIAN_MASK = maskFromOffsets([0, 2, 4, 5, 7, 9, 10]);
82
- var LOCRIAN_MASK = maskFromOffsets([0, 1, 3, 5, 6, 8, 10]);
83
- var MAJOR_PENTATONIC_MASK = maskFromOffsets([0, 2, 4, 7, 9]);
84
- var MINOR_PENTATONIC_MASK = maskFromOffsets([0, 3, 5, 7, 10]);
85
- var BLUES_MASK = maskFromOffsets([0, 3, 5, 6, 7, 10]);
86
- var WHOLE_TONE_MASK = maskFromOffsets([0, 2, 4, 6, 8, 10]);
87
- var OCTATONIC_HALF_WHOLE_MASK = maskFromOffsets([0, 1, 3, 4, 6, 7, 9, 10]);
88
- var OCTATONIC_WHOLE_HALF_MASK = maskFromOffsets([0, 2, 3, 5, 6, 8, 9, 11]);
89
- var CHROMATIC_MASK = 4095;
90
- var LYDIAN_DOMINANT_MASK = maskFromOffsets([0, 2, 4, 6, 7, 9, 10]);
91
- var MIXOLYDIAN_B13_MASK = maskFromOffsets([0, 2, 4, 5, 7, 8, 10]);
92
- var LOCRIAN_NATURAL2_MASK = maskFromOffsets([0, 2, 3, 5, 6, 8, 10]);
93
- var ALTERED_MASK = maskFromOffsets([0, 1, 3, 4, 6, 8, 10]);
94
- var PHRYGIAN_DOMINANT_MASK = maskFromOffsets([0, 1, 4, 5, 7, 8, 10]);
95
- var NAMED_SCALES = Object.freeze({
96
- major: MAJOR_MASK,
97
- ionian: MAJOR_MASK,
98
- naturalMinor: NATURAL_MINOR_MASK,
99
- aeolian: NATURAL_MINOR_MASK,
100
- harmonicMinor: HARMONIC_MINOR_MASK,
101
- melodicMinor: MELODIC_MINOR_MASK,
102
- dorian: DORIAN_MASK,
103
- phrygian: PHRYGIAN_MASK,
104
- lydian: LYDIAN_MASK,
105
- mixolydian: MIXOLYDIAN_MASK,
106
- locrian: LOCRIAN_MASK,
107
- lydianDominant: LYDIAN_DOMINANT_MASK,
108
- mixolydianB13: MIXOLYDIAN_B13_MASK,
109
- locrianNatural2: LOCRIAN_NATURAL2_MASK,
110
- altered: ALTERED_MASK,
111
- phrygianDominant: PHRYGIAN_DOMINANT_MASK,
112
- majorPentatonic: MAJOR_PENTATONIC_MASK,
113
- minorPentatonic: MINOR_PENTATONIC_MASK,
114
- blues: BLUES_MASK,
115
- wholeTone: WHOLE_TONE_MASK,
116
- octatonicHalfWhole: OCTATONIC_HALF_WHOLE_MASK,
117
- octatonicWholeHalf: OCTATONIC_WHOLE_HALF_MASK,
118
- chromatic: CHROMATIC_MASK
119
- });
120
- function namedScaleMask(name) {
121
- return Object.hasOwn(NAMED_SCALES, name) ? NAMED_SCALES[name] : void 0;
122
- }
123
- function requireScaleMask(name, label = "scale") {
124
- const mask = namedScaleMask(name);
125
- if (mask === void 0) {
126
- throw new InvalidInputError(`Unknown ${label}: ${String(name)}`);
127
- }
128
- return mask;
129
- }
130
-
131
- // src/theory/scale/key.ts
132
- function majorKey(rootPc) {
133
- return { rootPc: pitchClassOf(rootPc), modeMask12: MAJOR_MASK };
134
- }
135
- function minorKey(rootPc) {
136
- return { rootPc: pitchClassOf(rootPc), modeMask12: NATURAL_MINOR_MASK };
137
- }
138
- function scaleByName(name, rootPc) {
139
- return { rootPc: pitchClassOf(rootPc), modeMask12: requireScaleMask(name) };
140
- }
141
-
142
- // src/theory/chord/index.ts
143
- var QUALITY_INTERVALS = {
144
- maj: [0, 4, 7],
145
- min: [0, 3, 7],
146
- dim: [0, 3, 6],
147
- aug: [0, 4, 8],
148
- majb5: [0, 4, 6],
149
- maj7: [0, 4, 7, 11],
150
- min7: [0, 3, 7, 10],
151
- dom7: [0, 4, 7, 10],
152
- dim7: [0, 3, 6, 9],
153
- m7b5: [0, 3, 6, 10],
154
- minMaj7: [0, 3, 7, 11],
155
- minMaj9: [0, 3, 7, 11, 14],
156
- minMaj11: [0, 3, 7, 11, 14, 17],
157
- minMaj13: [0, 3, 7, 11, 14, 21],
158
- aug7: [0, 4, 8, 10],
159
- augMaj7: [0, 4, 8, 11],
160
- "6": [0, 4, 7, 9],
161
- min6: [0, 3, 7, 9],
162
- "6/9": [0, 4, 7, 9, 14],
163
- sus2: [0, 2, 7],
164
- sus4: [0, 5, 7],
165
- add9: [0, 4, 7, 14],
166
- add11: [0, 4, 7, 17],
167
- maj9: [0, 4, 7, 11, 14],
168
- min9: [0, 3, 7, 10, 14],
169
- dom9: [0, 4, 7, 10, 14],
170
- "7b9": [0, 4, 7, 10, 13],
171
- "7#9": [0, 4, 7, 10, 15],
172
- "7#11": [0, 4, 7, 10, 18],
173
- "7b13": [0, 4, 7, 10, 20],
174
- "11": [0, 7, 10, 14, 17],
175
- "13": [0, 4, 7, 10, 14, 21],
176
- "5": [0, 7],
177
- "7sus4": [0, 5, 7, 10],
178
- "7b5": [0, 4, 6, 10],
179
- // The altered dominant as it is voiced from a lead sheet: a dominant seventh
180
- // with a raised fifth and a raised ninth. The full seven-note altered stack
181
- // has no seven-letter spelling, so it is not what this symbol denotes.
182
- "7alt": [0, 4, 8, 10, 15],
183
- "13b9": [0, 4, 7, 10, 13, 21],
184
- maj13: [0, 4, 7, 11, 14, 21],
185
- "maj7#11": [0, 4, 7, 11, 18],
186
- min11: [0, 3, 7, 10, 14, 17],
187
- min13: [0, 3, 7, 10, 14, 21],
188
- minAdd9: [0, 3, 7, 14],
189
- "min6/9": [0, 3, 7, 9, 14]
190
- };
191
- function chordQualities() {
192
- return Object.keys(QUALITY_INTERVALS);
193
- }
194
- function chordFromDegree(degree, ext, key) {
195
- assertInteger(degree, "chord degree", -1e3, 1e3);
196
- if (!Object.hasOwn(QUALITY_INTERVALS, ext)) {
197
- throw new InvalidInputError(`Unknown chord quality: ${String(ext)}`);
198
- }
199
- const tones = scaleTonesInDegreeOrder(key);
200
- const length = tones.length;
201
- const index = length > 0 ? (degree % length + length) % length : 0;
202
- const rootPc = tones[index] ?? pitchClassOf(key.rootPc);
203
- return { rootPc, quality: ext, intervals: [...QUALITY_INTERVALS[ext]] };
204
- }
205
- function makeChord(rootPc, quality, bassPc) {
206
- assertFiniteNumber(rootPc, "chord rootPc");
207
- if (bassPc !== void 0) {
208
- assertFiniteNumber(bassPc, "chord bassPc");
209
- }
210
- if (!Object.hasOwn(QUALITY_INTERVALS, quality)) {
211
- throw new InvalidInputError(`Unknown chord quality: ${String(quality)}`);
212
- }
213
- const chord = {
214
- rootPc: pitchClassOf(rootPc),
215
- quality,
216
- intervals: [...QUALITY_INTERVALS[quality] ?? []]
217
- };
218
- if (bassPc !== void 0) {
219
- chord.bassPc = pitchClassOf(bassPc);
220
- }
221
- return chord;
222
- }
223
- function transposeChord(chord, semitones) {
224
- assertFiniteNumber(semitones, "semitones");
225
- const steps = Math.round(semitones);
226
- const moved = {
227
- rootPc: pitchClassOf(chord.rootPc + steps),
228
- quality: chord.quality,
229
- intervals: [...chord.intervals]
230
- };
231
- if (chord.bassPc !== void 0) {
232
- moved.bassPc = pitchClassOf(chord.bassPc + steps);
233
- }
234
- return moved;
235
- }
236
- function chordPitchClasses(chord, opts = {}) {
237
- const set = /* @__PURE__ */ new Set();
238
- for (const interval of chord.intervals) {
239
- set.add(pitchClassOf(chord.rootPc + interval));
240
- }
241
- if (opts.includeBass !== false && chord.bassPc !== void 0) {
242
- set.add(pitchClassOf(chord.bassPc));
243
- }
244
- return [...set].sort((a, b) => a - b);
245
- }
246
- function intervalAboveRoot(pitch, chord) {
247
- return ((pitchClassOf(pitch) - pitchClassOf(chord.rootPc)) % 12 + 12) % 12;
248
- }
249
- function isChordMember(pitch, chord) {
250
- return chord === null ? false : chordPitchClasses(chord).includes(pitchClassOf(pitch));
251
- }
252
- function chordToneOffsets(chord) {
253
- return new Set(chord.intervals.map((i) => (i % 12 + 12) % 12));
254
- }
255
- function chordToneRole(pitch, chord) {
256
- const interval = (pitchClassOf(pitch) - pitchClassOf(chord.rootPc) + 12) % 12;
257
- const tones = chordToneOffsets(chord);
258
- if (interval === 0) {
259
- return "root";
260
- }
261
- if (interval === 3 || interval === 4) {
262
- return tones.has(interval) && (interval !== 3 || !tones.has(4)) ? "third" : null;
263
- }
264
- if (interval === 7) {
265
- return tones.has(7) ? "fifth" : null;
266
- }
267
- if (interval === 6) {
268
- return tones.has(6) && !tones.has(7) ? "fifth" : null;
269
- }
270
- if (interval === 8) {
271
- return tones.has(8) && !tones.has(7) ? "fifth" : null;
272
- }
273
- if (interval === 9) {
274
- const hasHigherSeventh = tones.has(10) || tones.has(11);
275
- const isDiminishedSeventh = tones.has(3) && tones.has(6) && !hasHigherSeventh;
276
- if (tones.has(9) && isDiminishedSeventh) {
277
- return "seventh";
278
- }
279
- if (tones.has(9) && !hasHigherSeventh) {
280
- return "sixth";
281
- }
282
- return null;
283
- }
284
- if (interval === 10 || interval === 11) {
285
- return tones.has(interval) ? "seventh" : null;
286
- }
287
- return null;
288
- }
289
- function classifyTriad(thirdIc, fifthIc) {
290
- if (thirdIc === 4 && fifthIc === 8) {
291
- return "aug";
292
- }
293
- if (thirdIc === 4 && fifthIc === 6) {
294
- return "majb5";
295
- }
296
- if (thirdIc === 3 && fifthIc === 6) {
297
- return "dim";
298
- }
299
- if (thirdIc === 3) {
300
- return "min";
301
- }
302
- return "maj";
303
- }
304
- function classifySeventh(thirdIc, fifthIc, seventhIc) {
305
- if (thirdIc === 3 && fifthIc === 6 && seventhIc === 9) {
306
- return "dim7";
307
- }
308
- if (thirdIc === 3 && fifthIc === 6 && seventhIc === 10) {
309
- return "m7b5";
310
- }
311
- if (thirdIc === 3 && seventhIc === 11) {
312
- return "minMaj7";
313
- }
314
- if (thirdIc === 3) {
315
- return "min7";
316
- }
317
- if (fifthIc === 8 && seventhIc === 10) {
318
- return "aug7";
319
- }
320
- if (fifthIc === 8 && seventhIc === 11) {
321
- return "augMaj7";
322
- }
323
- if (fifthIc === 6 && seventhIc === 10) {
324
- return "7b5";
325
- }
326
- if (seventhIc === 11) {
327
- return "maj7";
328
- }
329
- return "dom7";
330
- }
331
- function matchesQualityIntervals(quality, intervals) {
332
- const template = QUALITY_INTERVALS[quality];
333
- return template.length === intervals.length && template.every((interval, index) => interval === intervals[index]);
334
- }
335
- function stackThirds(degree, key, size) {
336
- assertInteger(degree, "chord degree", -1e3, 1e3);
337
- const tones = scaleTonesInDegreeOrder(key);
338
- const length = tones.length;
339
- if (length !== 7) {
340
- throw new InvalidInputError(
341
- `diatonic chord stacking requires a heptatonic scale (received ${length} tones)`
342
- );
343
- }
344
- const idx = (step) => ((degree + step) % length + length) % length;
345
- const rootPc = tones[idx(0)] ?? pitchClassOf(key.rootPc);
346
- const offsets = [0, 2, 4, 6].slice(0, size).map((step) => {
347
- const pc = tones[idx(step)] ?? rootPc;
348
- return ((pc - rootPc) % 12 + 12) % 12;
349
- });
350
- const thirdIc = offsets[1] ?? 4;
351
- const fifthIc = offsets[2] ?? 7;
352
- const quality = size === 3 ? classifyTriad(thirdIc, fifthIc) : classifySeventh(thirdIc, fifthIc, offsets[3] ?? 11);
353
- if (!matchesQualityIntervals(quality, offsets)) {
354
- throw new InvalidInputError(
355
- `scale thirds at degree ${degree} produce unsupported intervals [${offsets.join(", ")}]`
356
- );
357
- }
358
- return { rootPc, quality, intervals: offsets };
359
- }
360
- function diatonicTriad(degree, key) {
361
- return stackThirds(degree, key, 3);
362
- }
363
- function diatonicSeventh(degree, key) {
364
- return stackThirds(degree, key, 4);
365
- }
366
-
367
- // src/theory/counterpoint/index.ts
368
- function createsVoiceCrossing(upperPitch, lowerPitch) {
369
- return upperPitch < lowerPitch;
370
- }
371
- function createsVerticalDissonance(a, b, twoVoice) {
372
- return !isConsonantInterval(a - b, twoVoice);
373
- }
374
- function isForbiddenMelodicLeap(prev, cur) {
375
- const semis = Math.abs(cur - prev);
376
- return semis === 6 || semis === 10 || semis === 11 || semis > 12;
377
- }
378
- function simpleClass(semitones) {
379
- return pitchClassOf(Math.abs(semitones));
380
- }
381
- function isPerfectClass(cls) {
382
- return cls === 0 || cls === 7;
383
- }
384
- function similarMotion(aMove, bMove) {
385
- return aMove !== 0 && bMove !== 0 && aMove > 0 === bMove > 0;
386
- }
387
- function bothVoicesMove(aMove, bMove) {
388
- return aMove !== 0 && bMove !== 0;
389
- }
390
- function createsParallelPerfect(aPrev, aCur, bPrev, bCur) {
391
- if (!bothVoicesMove(aCur - aPrev, bCur - bPrev)) {
392
- return false;
393
- }
394
- const nowClass = simpleClass(aCur - bCur);
395
- const prevClass = simpleClass(aPrev - bPrev);
396
- return isPerfectClass(nowClass) && nowClass === prevClass;
397
- }
398
- function createsParallelOctave(aPrev, aCur, bPrev, bCur) {
399
- if (!similarMotion(aCur - aPrev, bCur - bPrev)) {
400
- return false;
401
- }
402
- return simpleClass(aCur - bCur) === 0 && simpleClass(aPrev - bPrev) === 0;
403
- }
404
- function createsParallelUnison(aPrev, aCur, bPrev, bCur) {
405
- if (aCur === aPrev || bCur === bPrev) {
406
- return false;
407
- }
408
- return aCur === bCur && aPrev === bPrev;
409
- }
410
- function createsHiddenParallelPerfect(aPrev, aCur, bPrev, bCur) {
411
- const aMove = aCur - aPrev;
412
- const bMove = bCur - bPrev;
413
- if (!similarMotion(aMove, bMove)) {
414
- return false;
415
- }
416
- const nowClass = simpleClass(aCur - bCur);
417
- const prevClass = simpleClass(aPrev - bPrev);
418
- if (!isPerfectClass(nowClass) || prevClass === nowClass) {
419
- return false;
420
- }
421
- const upperMove = aCur >= bCur ? aMove : bMove;
422
- if (Math.abs(upperMove) <= 2) {
423
- return false;
424
- }
425
- return true;
426
- }
427
- function createsVoiceOverlap(upperPrev, upperCur, lowerPrev, lowerCur) {
428
- return upperCur < lowerPrev || lowerCur > upperPrev;
429
- }
430
- function exceedsSpacing(upperPitch, lowerPitch, maxSemitones = 12) {
431
- return Math.abs(upperPitch - lowerPitch) > maxSemitones;
432
- }
433
- function isLeadingToneResolution(prev, cur, key) {
434
- const tonic = pitchClassOf(key.rootPc);
435
- const leading = (tonic + 11) % 12;
436
- if (pitchClassOf(prev) !== leading || pitchClassOf(cur) !== tonic) {
437
- return false;
438
- }
439
- return cur > prev && cur - prev <= 2;
440
- }
441
-
442
- export {
443
- isScaleTone,
444
- nearestScaleTone,
445
- pitchToScaleDegree,
446
- diatonicPitchClasses,
447
- scaleTonesInDegreeOrder,
448
- maskFromOffsets,
449
- MAJOR_MASK,
450
- NATURAL_MINOR_MASK,
451
- HARMONIC_MINOR_MASK,
452
- MELODIC_MINOR_MASK,
453
- DORIAN_MASK,
454
- PHRYGIAN_MASK,
455
- LYDIAN_MASK,
456
- MIXOLYDIAN_MASK,
457
- LOCRIAN_MASK,
458
- MAJOR_PENTATONIC_MASK,
459
- MINOR_PENTATONIC_MASK,
460
- BLUES_MASK,
461
- WHOLE_TONE_MASK,
462
- OCTATONIC_HALF_WHOLE_MASK,
463
- OCTATONIC_WHOLE_HALF_MASK,
464
- CHROMATIC_MASK,
465
- LYDIAN_DOMINANT_MASK,
466
- MIXOLYDIAN_B13_MASK,
467
- LOCRIAN_NATURAL2_MASK,
468
- ALTERED_MASK,
469
- PHRYGIAN_DOMINANT_MASK,
470
- NAMED_SCALES,
471
- namedScaleMask,
472
- requireScaleMask,
473
- majorKey,
474
- minorKey,
475
- scaleByName,
476
- chordQualities,
477
- chordFromDegree,
478
- makeChord,
479
- transposeChord,
480
- chordPitchClasses,
481
- intervalAboveRoot,
482
- isChordMember,
483
- chordToneRole,
484
- diatonicTriad,
485
- diatonicSeventh,
486
- createsVoiceCrossing,
487
- createsVerticalDissonance,
488
- isForbiddenMelodicLeap,
489
- createsParallelPerfect,
490
- createsParallelOctave,
491
- createsParallelUnison,
492
- createsHiddenParallelPerfect,
493
- createsVoiceOverlap,
494
- exceedsSpacing,
495
- isLeadingToneResolution
496
- };
497
- //# sourceMappingURL=chunk-KBTTEX7Q.js.map