@libraz/libcantus 0.9.4 → 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 (186) hide show
  1. package/README.md +61 -261
  2. package/README_ja.md +115 -0
  3. package/dist/analyze/index.cjs +54 -7
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +213 -456
  6. package/dist/analyze/index.d.ts +213 -456
  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-ELCTZUJJ.js +379 -0
  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 +103 -6
  64. package/dist/core/index.cjs.map +1 -1
  65. package/dist/core/index.d.cts +96 -345
  66. package/dist/core/index.d.ts +96 -345
  67. package/dist/core/index.js +104 -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 +437 -1095
  71. package/dist/generate/index.d.ts +437 -1095
  72. package/dist/generate/index.js +94 -15
  73. package/dist/index-9wcMhG73.d.ts +1095 -0
  74. package/dist/{index-D6b6na69.d.cts → 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-tSQh-xwY.d.ts → 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 +358 -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 +407 -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 +4685 -243
  103. package/dist/model/index.d.ts +4685 -243
  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 +513 -173
  110. package/dist/theory/index.d.ts +513 -173
  111. package/dist/theory/index.js +118 -24
  112. package/package.json +16 -11
  113. package/dist/chunk-36N73KZ3.cjs +0 -246
  114. package/dist/chunk-36N73KZ3.cjs.map +0 -1
  115. package/dist/chunk-4HLLZPXT.cjs +0 -3077
  116. package/dist/chunk-4HLLZPXT.cjs.map +0 -1
  117. package/dist/chunk-7MGZ7D62.cjs +0 -498
  118. package/dist/chunk-7MGZ7D62.cjs.map +0 -1
  119. package/dist/chunk-7NKDPF74.js +0 -3077
  120. package/dist/chunk-7NKDPF74.js.map +0 -1
  121. package/dist/chunk-CAN76NCU.cjs +0 -679
  122. package/dist/chunk-CAN76NCU.cjs.map +0 -1
  123. package/dist/chunk-D2FAJBYL.js +0 -479
  124. package/dist/chunk-D2FAJBYL.js.map +0 -1
  125. package/dist/chunk-EHVEBWM5.js +0 -1449
  126. package/dist/chunk-EHVEBWM5.js.map +0 -1
  127. package/dist/chunk-FCANXBAT.cjs +0 -30
  128. package/dist/chunk-FCANXBAT.cjs.map +0 -1
  129. package/dist/chunk-FNDWXRRI.cjs +0 -479
  130. package/dist/chunk-FNDWXRRI.cjs.map +0 -1
  131. package/dist/chunk-GQSGS2N4.js +0 -173
  132. package/dist/chunk-GQSGS2N4.js.map +0 -1
  133. package/dist/chunk-HKGYCQ34.js +0 -53
  134. package/dist/chunk-HKGYCQ34.js.map +0 -1
  135. package/dist/chunk-I24MV65U.js +0 -106
  136. package/dist/chunk-I24MV65U.js.map +0 -1
  137. package/dist/chunk-KMHOICIY.js +0 -679
  138. package/dist/chunk-KMHOICIY.js.map +0 -1
  139. package/dist/chunk-LW53AHQU.cjs +0 -1117
  140. package/dist/chunk-LW53AHQU.cjs.map +0 -1
  141. package/dist/chunk-N6IHQUOX.js +0 -1
  142. package/dist/chunk-NKIBQ6PT.js +0 -30
  143. package/dist/chunk-NKIBQ6PT.js.map +0 -1
  144. package/dist/chunk-NP4NA2FB.cjs +0 -106
  145. package/dist/chunk-NP4NA2FB.cjs.map +0 -1
  146. package/dist/chunk-NVE34YGZ.cjs +0 -1449
  147. package/dist/chunk-NVE34YGZ.cjs.map +0 -1
  148. package/dist/chunk-NYYWMV2C.js +0 -327
  149. package/dist/chunk-NYYWMV2C.js.map +0 -1
  150. package/dist/chunk-Q2XUP227.js +0 -845
  151. package/dist/chunk-Q2XUP227.js.map +0 -1
  152. package/dist/chunk-Q6OP4U43.cjs +0 -327
  153. package/dist/chunk-Q6OP4U43.cjs.map +0 -1
  154. package/dist/chunk-RC6GBMCD.cjs +0 -845
  155. package/dist/chunk-RC6GBMCD.cjs.map +0 -1
  156. package/dist/chunk-RK35RIN7.js +0 -1117
  157. package/dist/chunk-RK35RIN7.js.map +0 -1
  158. package/dist/chunk-RUMU3UA6.cjs +0 -53
  159. package/dist/chunk-RUMU3UA6.cjs.map +0 -1
  160. package/dist/chunk-T64C7ZD7.cjs +0 -1
  161. package/dist/chunk-T64C7ZD7.cjs.map +0 -1
  162. package/dist/chunk-XEBG3G53.js +0 -498
  163. package/dist/chunk-XEBG3G53.js.map +0 -1
  164. package/dist/chunk-YLEMGEFH.js +0 -246
  165. package/dist/chunk-YLEMGEFH.js.map +0 -1
  166. package/dist/chunk-ZEJR4TKL.cjs +0 -173
  167. package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
  168. package/dist/function-BtiZb9i9.d.ts +0 -208
  169. package/dist/function-DNugAOrt.d.cts +0 -208
  170. package/dist/index-6VR8-kit.d.ts +0 -143
  171. package/dist/index-70p168VM.d.ts +0 -192
  172. package/dist/index-Be74mcon.d.cts +0 -152
  173. package/dist/index-BtXT6UQ0.d.cts +0 -192
  174. package/dist/index-C5UBbQ4G.d.ts +0 -152
  175. package/dist/index-C83mZI1P.d.ts +0 -242
  176. package/dist/index-DXd9JikN.d.cts +0 -246
  177. package/dist/index-DXd9JikN.d.ts +0 -246
  178. package/dist/index-OG65ocOr.d.cts +0 -272
  179. package/dist/index-OG65ocOr.d.ts +0 -272
  180. package/dist/index-tJhkmQgE.d.cts +0 -143
  181. package/dist/index-y_ak9_Hi.d.cts +0 -242
  182. package/dist/styled-DIRkk9Uz.d.ts +0 -506
  183. package/dist/styled-DJbkJQfj.d.cts +0 -506
  184. package/dist/types-BN6_Q9Kl.d.cts +0 -39
  185. package/dist/types-BN6_Q9Kl.d.ts +0 -39
  186. /package/dist/{chunk-N6IHQUOX.js.map → chunk-HFPS7JFD.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-E3F4AHAH.cjs","../src/core/instrument/articulation.ts","../src/core/instrument/profile.ts","../src/core/instrument/playability.ts","../src/core/instrument/profiles.ts","../src/core/random/internal.ts","../src/core/random/positional.ts","../src/core/random/seed.ts","../src/core/random/version.ts","../src/core/random/index.ts"],"names":["high"],"mappings":"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AA;AACH,EAAA;AAGG,EAAA;AACA,IAAA;AAC9B,EAAA;AACc,EAAA;AAChB;AN6fiC;AACA;AO5iBW;AAGtB,EAAA;AACS,EAAA;AAC/B;AP4iBiC;AACA;AQlkBA;AAOI;AAuBG;AACT,EAAA;AACpB,IAAA;AACT,EAAA;AACyB,EAAA;AACT,EAAA;AACJ,IAAA;AACa,MAAA;AAEvB,IAAA;AACF,EAAA;AACO,EAAA;AACT;ARuiBiC;AACA;AS/jBY;AAGZ,EAAA;AACV,EAAA;AACF,EAAA;AACA,IAAA;AACT,IAAA;AACsB,IAAA;AACA,IAAA;AACD,IAAA;AAC/B,EAAA;AACO,EAAA;AACL,IAAA;AACa,IAAA;AAIU,MAAA;AACL,MAAA;AAClB,IAAA;AACmB,IAAA;AACC,MAAA;AACA,MAAA;AACL,MAAA;AACD,QAAA;AACR,UAAA;AACF,QAAA;AACF,MAAA;AACuB,MAAA;AACA,MAAA;AACzB,IAAA;AACmB,IAAA;AACM,MAAA;AACA,MAAA;AACV,MAAA;AACD,QAAA;AACR,UAAA;AACF,QAAA;AACF,MAAA;AACwB,MAAA;AACG,MAAA;AAC7B,IAAA;AACF,EAAA;AACF;AT4jBiC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/libcantus/libcantus/dist/chunk-E3F4AHAH.cjs","sourcesContent":[null,"/**\n * Every playing technique a note can carry, in declaration order.\n *\n * These name intent, not a rendering: how `flam` becomes a grace stroke or\n * `slide` becomes pitch bend is the caller's decision, because it depends on the\n * sampler, key switches, and controller layout in use. Carrying the intent\n * instead of a baked note pair is what lets any phrase be flammed, ghosted, or\n * muted without the generator having to know how it will be voiced.\n *\n * @category Core\n */\nexport const ARTICULATIONS = Object.freeze([\n 'accent',\n 'ghost',\n 'staccato',\n 'legato',\n 'slide',\n 'hammer',\n 'mute',\n 'flam',\n 'drag',\n 'roll',\n 'choke',\n 'open',\n] as const);\n\n/**\n * How a note is played, independent of its pitch and length.\n *\n * @category Core\n */\nexport type Articulation = (typeof ARTICULATIONS)[number];\n","import { InvalidInputError } from '../errors/index.js';\nimport type { Articulation } from './articulation.js';\n\n/**\n * Every limb a percussionist strikes with, in declaration order.\n *\n * @category Core\n */\nexport const LIMBS = Object.freeze(['rightHand', 'leftHand', 'rightFoot', 'leftFoot'] as const);\n\n/**\n * One limb of a percussionist.\n *\n * @category Core\n */\nexport type Limb = (typeof LIMBS)[number];\n\n/**\n * Fields every instrument profile carries, whatever its family.\n *\n * @category Core\n */\nexport type InstrumentProfileCommon = {\n /** Instrument name, for issue messages and for the caller's own display. */\n name: string;\n /** Techniques this instrument can produce; anything else it cannot play. */\n articulations: readonly Articulation[];\n /** Greatest number of notes that can sound at once. */\n polyphony: number;\n};\n\n/**\n * A fretted or fretless string instrument, described by what it is rather than\n * by the range it happens to have.\n *\n * The sounding range follows from `tuning` and `frets`: string `s` sounds\n * `tuning[s] .. tuning[s] + frets`. Storing `[min, max]` instead would make a\n * five-string bass, a drop tuning, a seven-string guitar and an extended-range\n * neck four unrelated special cases, and would leave \"the neck is not long\n * enough\" indistinguishable from \"the note is below the instrument\".\n *\n * @category Core\n */\nexport type StringedProfile = InstrumentProfileCommon & {\n kind: 'stringed';\n /** Open-string MIDI pitch per string, lowest-sounding string first. */\n tuning: readonly number[];\n /** Highest fret number; fret 0 is the open string. */\n frets: number;\n /** Greatest fret span one hand can hold down at once. */\n maxStretch: number;\n /**\n * The neck has no frets. Positions are continuous, so intonation is the\n * player's and a slide is unbroken; in twelve-tone equal temperament the\n * positions coincide with fret numbers, so range and stretch are read the\n * same way.\n */\n fretless?: boolean;\n};\n\n/**\n * A percussion setup, described by the voices within reach of each limb.\n *\n * A kit's constraint is not range but limbs: a voice no limb can reach is not\n * on the instrument, and two voices needing the same limb at the same instant\n * cannot both sound.\n *\n * @category Core\n */\nexport type PercussionProfile = InstrumentProfileCommon & {\n kind: 'percussion';\n /** The limbs the player has available. */\n limbs: readonly Limb[];\n /** Limbs that can strike each voice, in preference order, keyed by MIDI note. */\n reach: Readonly<Record<number, readonly Limb[]>>;\n};\n\n/**\n * An instrument the library can check a passage against.\n *\n * @category Core\n */\nexport type InstrumentProfile = StringedProfile | PercussionProfile;\n\n/**\n * Anything that names an instrument: a plain {@link InstrumentProfile}, or a\n * value that serializes to one such as the `Instrument` class.\n *\n * Every entry point that takes an instrument takes this, so a caller holding\n * the class hands it over as it is instead of unwrapping it for one call and\n * wrapping it again for the next.\n *\n * @category Core\n */\nexport type InstrumentProfileLike =\n | InstrumentProfile\n | {\n /** The profile data this value stands for. */\n toJSON(): InstrumentProfile;\n };\n\n/**\n * Resolve any instrument-shaped value to a plain {@link InstrumentProfile}.\n *\n * The counterpart of {@link toNoteData} for instruments: an entry point takes\n * whatever form the caller has — the profile a project file holds, one of the\n * built-in profiles, or an `Instrument` instance — and gets one shape back. An\n * instance is accepted through its `toJSON` method rather than by its type,\n * because the core layer cannot import the model layer that defines the class.\n *\n * @param value A plain profile, or a value whose `toJSON` returns one.\n * @param name What the instrument is called in an error message, so a caller\n * holding several of them hears which one was malformed.\n * @returns The validated profile.\n * @throws If the value names no instrument, or the instrument it names is\n * contradictory — a neck with no strings, a fret count that is no count, or a\n * kit no limb reaches.\n * @category Core\n */\nexport function toInstrumentProfile(\n value: InstrumentProfileLike,\n name = 'instrument',\n): InstrumentProfile {\n if (typeof value !== 'object' || value === null) {\n throw new InvalidInputError(`${name} must be an instrument profile; received ${typeof value}`);\n }\n const data =\n 'toJSON' in value && typeof value.toJSON === 'function'\n ? value.toJSON()\n : (value as InstrumentProfile);\n const kind: unknown = data?.kind;\n if (kind !== 'stringed' && kind !== 'percussion') {\n throw new InvalidInputError(\n `${name} must be a stringed or percussion profile; received kind ${String(kind)}`,\n );\n }\n assertProfile(data);\n return data;\n}\n\n/**\n * Resolve any instrument-shaped value to a plain {@link StringedProfile}.\n *\n * What {@link toInstrumentProfile} is for an entry point that reads any\n * instrument, this is for one that only has a neck to work with: a bass line\n * placed on strings and frets cannot be written for a kit, and saying so by\n * name is more use than a missing `tuning` surfacing later as a note the\n * generator could not place.\n *\n * @param value A plain profile, or a value whose `toJSON` returns one.\n * @param name What the instrument is called in an error message.\n * @returns The validated profile, narrowed to the stringed family.\n * @throws If the value names no instrument, or names one with no strings.\n * @category Core\n */\nexport function toStringedProfile(\n value: InstrumentProfileLike,\n name = 'instrument',\n): StringedProfile {\n const profile = toInstrumentProfile(value, name);\n if (profile.kind !== 'stringed') {\n throw new InvalidInputError(\n `${name} must be a stringed instrument; ${profile.name} has no strings`,\n );\n }\n return profile;\n}\n\n/**\n * A fretting-hand position: which string, and how far up the neck.\n *\n * @category Core\n */\nexport type StringFingering = {\n /** Index into the `tuning` of a {@link StringedProfile}. */\n string: number;\n /** Fret number; 0 is the open string. */\n fret: number;\n};\n\n/** Reject a profile whose own description is contradictory. */\nfunction assertProfile(profile: InstrumentProfile): void {\n if (profile.kind === 'stringed') {\n if (profile.tuning.length === 0) {\n throw new InvalidInputError(`${profile.name} must have at least one string`);\n }\n if (!Number.isInteger(profile.frets) || profile.frets < 0) {\n throw new InvalidInputError(`${profile.name} frets must be a non-negative integer`);\n }\n return;\n }\n if (Object.keys(profile.reach).length === 0) {\n throw new InvalidInputError(`${profile.name} must have at least one voice within reach`);\n }\n}\n\n/**\n * The limbs a kit can actually strike a voice with.\n *\n * A `reach` entry names the limbs that are placed to hit a voice; `limbs` names\n * the limbs the player has. A voice reachable only by a limb the player has not\n * got is not on the instrument, so the two are read together everywhere rather\n * than the reach alone.\n *\n * @param profile The kit.\n * @param pitch MIDI pitch, or `undefined` for a note that is not there.\n * @returns The available limbs in preference order; empty when none can strike.\n * @category Core\n */\nexport function reachOf(profile: PercussionProfile, pitch: number | undefined): readonly Limb[] {\n if (pitch === undefined) {\n return [];\n }\n return (profile.reach[pitch] ?? []).filter((limb) => profile.limbs.includes(limb));\n}\n\n/**\n * The lowest and highest pitch an instrument sounds.\n *\n * For a string instrument this is derived from the tuning and the fret count,\n * never stored; for a kit it is the extent of the voices the player's limbs\n * reach. The range is not necessarily gapless — use {@link canSound} to ask\n * about one pitch.\n *\n * @param profile The instrument.\n * @returns The extreme sounding pitches, inclusive.\n *\n * @example\n * ```ts\n * import { BASS_5_STRING, instrumentRange } from '@libraz/libcantus';\n * instrumentRange(BASS_5_STRING).low; // 23, low B\n * ```\n * @category Core\n */\nexport function instrumentRange(profile: InstrumentProfile): { low: number; high: number } {\n assertProfile(profile);\n if (profile.kind === 'stringed') {\n let low = Number.POSITIVE_INFINITY;\n let high = Number.NEGATIVE_INFINITY;\n for (const open of profile.tuning) {\n low = Math.min(low, open);\n high = Math.max(high, open + profile.frets);\n }\n return { low, high };\n }\n let low = Number.POSITIVE_INFINITY;\n let high = Number.NEGATIVE_INFINITY;\n for (const key of Object.keys(profile.reach)) {\n const pitch = Number(key);\n // A voice only an absent limb reaches is not on this kit, so it does not\n // stretch the range either — otherwise the range would promise a pitch\n // {@link canSound} denies.\n if (reachOf(profile, pitch).length === 0) {\n continue;\n }\n low = Math.min(low, pitch);\n high = Math.max(high, pitch);\n }\n return { low, high };\n}\n\n/**\n * Whether the instrument has this pitch at all.\n *\n * This is the first of the three playability layers: no amount of skill or\n * rehearsal puts a note on an instrument that has no position for it.\n *\n * @param profile The instrument.\n * @param pitch MIDI pitch.\n * @returns True when some string and fret, or some limb the player has,\n * produces it.\n * @category Core\n */\nexport function canSound(profile: InstrumentProfile, pitch: number): boolean {\n assertProfile(profile);\n if (profile.kind === 'stringed') {\n return profile.tuning.some((open) => pitch >= open && pitch - open <= profile.frets);\n }\n return reachOf(profile, pitch).length > 0;\n}\n\n/**\n * Every position on the neck that sounds a pitch, lowest string first.\n *\n * @param profile The instrument.\n * @param pitch MIDI pitch.\n * @returns One entry per string that reaches the pitch; empty when none does.\n * @category Core\n */\nexport function fingeringsFor(profile: StringedProfile, pitch: number): StringFingering[] {\n assertProfile(profile);\n const out: StringFingering[] = [];\n for (let string = 0; string < profile.tuning.length; string += 1) {\n const open = profile.tuning[string];\n if (open === undefined) {\n continue;\n }\n const fret = pitch - open;\n if (fret >= 0 && fret <= profile.frets) {\n out.push({ string, fret });\n }\n }\n return out;\n}\n\n/**\n * Move a pitch by whole octaves until the instrument can sound it.\n *\n * This is what a player does with a line written below the instrument: the note\n * comes back an octave up, keeping its pitch class and its place in the phrase.\n * Substituting a different scale degree, or refusing the passage outright, is\n * the wrong answer for a bass line.\n *\n * @param pitch MIDI pitch to place.\n * @param profile The instrument.\n * @returns The nearest octave transposition the instrument sounds, searching\n * upward first; the pitch unchanged when no transposition of it is available.\n *\n * @example\n * ```ts\n * import { BASS_4_STRING, foldIntoRange } from '@libraz/libcantus';\n * foldIntoRange(27, BASS_4_STRING); // 39 — Eb1 is under the low E\n * ```\n * @category Core\n */\nexport function foldIntoRange(pitch: number, profile: InstrumentProfile): number {\n if (canSound(profile, pitch)) {\n return pitch;\n }\n const { low, high } = instrumentRange(profile);\n for (let candidate = pitch + 12; candidate <= high; candidate += 12) {\n if (canSound(profile, candidate)) {\n return candidate;\n }\n }\n for (let candidate = pitch - 12; candidate >= low; candidate -= 12) {\n if (canSound(profile, candidate)) {\n return candidate;\n }\n }\n return pitch;\n}\n","import type { NoteEvent } from '../types.js';\nimport { assertNoteEvents, assertRange } from '../validation/index.js';\nimport {\n canSound,\n fingeringsFor,\n type InstrumentProfile,\n type Limb,\n reachOf,\n type StringFingering,\n} from './profile.js';\n\n/**\n * What makes a passage hard or impossible on an instrument.\n *\n * `noteOutOfRange` and `articulationUnavailable` belong to the first layer: the\n * instrument has no position for what was written. `stringConflict`,\n * `stretchTooWide`, `limbConflict` and `polyphonyExceeded` belong to the second:\n * the notes exist but the arrangement cannot be held or struck. `tooFast`\n * belongs to the third, the only one that depends on tempo.\n *\n * @category Core\n */\nexport type PlayabilityIssueType =\n | 'noteOutOfRange'\n | 'articulationUnavailable'\n | 'stringConflict'\n | 'stretchTooWide'\n | 'limbConflict'\n | 'polyphonyExceeded'\n | 'tooFast';\n\n/**\n * Which playability layer an issue belongs to: 1 the note does not exist, 2 the\n * arrangement does not hold together, 3 there is not enough time.\n *\n * @category Core\n */\nexport type PlayabilityLayer = 1 | 2 | 3;\n\n/**\n * One thing standing between a passage and a performance of it.\n *\n * @category Core\n */\nexport type PlayabilityIssue = {\n type: PlayabilityIssueType;\n layer: PlayabilityLayer;\n /** Indices into the analysed note array, ascending. */\n notes: readonly number[];\n /** Where the problem occurs, in quarter-note beats. */\n startBeat: number;\n /**\n * True when practice cannot fix it. Layers 1 and 2 depend on the instrument\n * alone, so they are impossible however skilled the player; only a layer-3\n * issue is a matter of degree.\n */\n impossible: boolean;\n message: string;\n};\n\n/**\n * How one note is actually produced: the string and fret it is stopped at, or\n * the limb that strikes it.\n *\n * This is reported for every note that has a position at all, whether or not\n * the passage raises issues, so a caller can apply its own standard rather than\n * take this module's.\n *\n * @category Core\n */\nexport type NotePlacement = {\n /** Index into the analysed note array. */\n note: number;\n /** String instruments: where the note is stopped. */\n fingering?: StringFingering;\n /** Percussion: which limb plays the stroke. */\n limb?: Limb;\n};\n\n/**\n * The verdict on a passage: how hard it is, what stands in the way, and how\n * each note would be produced.\n *\n * @category Core\n */\nexport type PlayabilityReport = {\n /**\n * Movement per unit time, mapped onto 1 (trivial) to 5 (at the limit).\n * Without a tempo the passage is measured at 120 BPM, so the number stays\n * comparable between passages; with one it rises as the tempo does.\n */\n difficulty: number;\n issues: PlayabilityIssue[];\n placements: NotePlacement[];\n};\n\n/** Tempo the difficulty is measured at when the caller gives none. */\nconst REFERENCE_BPM = 120;\n/** Fastest the fretting hand shifts along the neck, in frets per second. */\nconst MAX_FRET_SHIFT_PER_SECOND = 100;\n/** Shortest gap between two strokes of one limb, in seconds. */\nconst MIN_LIMB_STROKE_SECONDS = 0.06;\n/** Movement rate, in units per second, that reads as a demanding passage. */\nconst DIFFICULTY_SCALE = 12;\nconst MIN_DIFFICULTY = 1;\nconst MAX_DIFFICULTY = 5;\n/** Cost of crossing one string, relative to moving one fret. */\nconst STRING_CROSSING_COST = 2;\n/** Onsets closer than this fraction of a beat count as simultaneous. */\nconst ONSET_GRID = 128;\n\nconst EPS = 1e-9;\n\n/** Working state shared by both instrument families. */\ntype Analysis = {\n notes: readonly NoteEvent[];\n /** Note indices in onset order, ties broken by pitch. */\n order: number[];\n placements: NotePlacement[];\n issues: PlayabilityIssue[];\n /** Accumulated movement, in frets or strokes. */\n movement: number;\n /** Seconds per quarter-note beat, or undefined when no tempo was given. */\n secondsPerBeat: number | undefined;\n};\n\n/**\n * Judge whether a passage can be played on an instrument, and how hard it is.\n *\n * Inspection is deliberately separate from generation: this answers \"can this\n * be played\" for a chart handed to a player or a practice tool, and never\n * rewrites or rejects anything. The three layers are reported together but stay\n * distinct — an out-of-range note or an impossible stretch is not the bottom of\n * the difficulty scale but a different axis, since no amount of skill puts a\n * note on an instrument that does not have it. Only the third layer consults\n * `bpm`, so omitting the tempo reports what the instrument alone decides.\n *\n * @param notes The passage, in any order. Drum hits qualify as note events.\n * @param profile The instrument to play it on.\n * @param bpm Tempo in quarter-note beats per minute; enables the third layer.\n * @returns Difficulty, the issues found, and each note's string/fret or limb.\n *\n * @example\n * ```ts\n * import { BASS_4_STRING, playability } from '@libraz/libcantus';\n * const report = playability([{ pitch: 27, startBeat: 0, durationBeat: 1 }], BASS_4_STRING);\n * report.issues[0]?.type; // 'noteOutOfRange'\n * ```\n * @category Core\n */\nexport function playability(\n notes: readonly NoteEvent[],\n profile: InstrumentProfile,\n bpm?: number,\n): PlayabilityReport {\n assertNoteEvents(notes, 'playability notes', { allowNonPositiveDuration: true });\n if (bpm !== undefined) {\n assertRange(bpm, Number.MIN_VALUE, 1000, 'playability bpm');\n }\n\n const order = notes\n .map((_, index) => index)\n .sort((a, b) => {\n const left = notes[a];\n const right = notes[b];\n if (!left || !right) {\n return a - b;\n }\n return left.startBeat - right.startBeat || left.pitch - right.pitch || a - b;\n });\n\n const state: Analysis = {\n notes,\n order,\n placements: notes.map((_, index) => ({ note: index })),\n issues: [],\n movement: 0,\n secondsPerBeat: bpm === undefined ? undefined : 60 / bpm,\n };\n\n checkExistence(state, profile);\n if (profile.kind === 'stringed') {\n placeOnNeck(state, profile);\n } else {\n placeOnKit(state, profile);\n }\n\n return {\n difficulty: difficultyOf(state, bpm),\n issues: state.issues,\n placements: state.placements,\n };\n}\n\n/** Layer 1: the instrument has no position for the note, or no such technique. */\nfunction checkExistence(state: Analysis, profile: InstrumentProfile): void {\n for (const index of state.order) {\n const note = state.notes[index];\n if (!note) {\n continue;\n }\n if (!canSound(profile, note.pitch)) {\n state.issues.push({\n type: 'noteOutOfRange',\n layer: 1,\n notes: [index],\n startBeat: note.startBeat,\n impossible: true,\n message: `${profile.name} cannot sound MIDI ${note.pitch}`,\n });\n }\n const articulation = note.articulation;\n if (articulation !== undefined && !profile.articulations.includes(articulation)) {\n state.issues.push({\n type: 'articulationUnavailable',\n layer: 1,\n notes: [index],\n startBeat: note.startBeat,\n impossible: true,\n message: `${profile.name} cannot play ${articulation}`,\n });\n }\n }\n}\n\n/** Note indices sounding at each distinct onset, in onset order. */\nfunction soundingGroups(state: Analysis): { startBeat: number; members: number[] }[] {\n const groups: { startBeat: number; members: number[] }[] = [];\n let active: number[] = [];\n let cursor = 0;\n let previousKey = Number.NaN;\n for (const index of state.order) {\n const note = state.notes[index];\n if (!note) {\n continue;\n }\n const key = Math.round(note.startBeat * ONSET_GRID);\n if (key === previousKey) {\n continue;\n }\n previousKey = key;\n const at = note.startBeat;\n active = active.filter((member) => {\n const held = state.notes[member];\n return held !== undefined && held.startBeat + held.durationBeat > at + EPS;\n });\n while (cursor < state.order.length) {\n const candidateIndex = state.order[cursor];\n const candidate = candidateIndex === undefined ? undefined : state.notes[candidateIndex];\n if (!candidate || candidate.startBeat > at + EPS) {\n break;\n }\n if (candidateIndex !== undefined && candidate.startBeat + candidate.durationBeat > at + EPS) {\n active.push(candidateIndex);\n }\n cursor += 1;\n }\n groups.push({ startBeat: at, members: [...active] });\n }\n return groups;\n}\n\n/** Layers 2 and 3 for a string instrument, and the fingering that goes with them. */\nfunction placeOnNeck(state: Analysis, profile: InstrumentProfile & { kind: 'stringed' }): void {\n /** Beat each string becomes free again. */\n const stringFreeAt = profile.tuning.map(() => Number.NEGATIVE_INFINITY);\n /** Note currently holding each string. */\n const stringHeldBy = profile.tuning.map<number | undefined>(() => undefined);\n let previous: StringFingering | undefined;\n let previousIndex: number | undefined;\n\n for (const index of state.order) {\n const note = state.notes[index];\n const placement = state.placements[index];\n if (!note || !placement) {\n continue;\n }\n const candidates = fingeringsFor(profile, note.pitch);\n if (candidates.length === 0) {\n continue;\n }\n const free = candidates.filter(\n (candidate) => (stringFreeAt[candidate.string] ?? 0) <= note.startBeat + EPS,\n );\n if (free.length === 0) {\n const blocked = candidates\n .map((candidate) => stringHeldBy[candidate.string])\n .filter((held): held is number => held !== undefined);\n state.issues.push({\n type: 'stringConflict',\n layer: 2,\n notes: [...new Set([...blocked, index])].sort((a, b) => a - b),\n startBeat: note.startBeat,\n impossible: true,\n message: `MIDI ${note.pitch} needs a string already sounding on ${profile.name}`,\n });\n }\n const pool = free.length > 0 ? free : candidates;\n const chosen = pool.reduce((best, candidate) =>\n fingeringCost(candidate, previous) < fingeringCost(best, previous) ? candidate : best,\n );\n placement.fingering = chosen;\n stringFreeAt[chosen.string] = note.startBeat + note.durationBeat;\n stringHeldBy[chosen.string] = index;\n\n state.movement += 1;\n if (previous) {\n state.movement +=\n Math.abs(chosen.fret - previous.fret) + Math.abs(chosen.string - previous.string);\n checkShiftSpeed(state, profile, previousIndex, index, previous, chosen);\n }\n previous = chosen;\n previousIndex = index;\n }\n\n for (const group of soundingGroups(state)) {\n checkStretch(state, profile, group);\n checkPolyphony(state, profile, group);\n }\n}\n\n/** Cost of taking a position, counting a string crossing as several frets. */\nfunction fingeringCost(candidate: StringFingering, previous: StringFingering | undefined): number {\n if (!previous) {\n // With nothing to move from, the low frets are the resting position.\n return candidate.fret + candidate.string / 100;\n }\n return (\n Math.abs(candidate.fret - previous.fret) +\n STRING_CROSSING_COST * Math.abs(candidate.string - previous.string) +\n candidate.string / 100\n );\n}\n\n/** Layer 3: the fretting hand cannot travel that far in the time available. */\nfunction checkShiftSpeed(\n state: Analysis,\n profile: InstrumentProfile,\n fromIndex: number | undefined,\n toIndex: number,\n from: StringFingering,\n to: StringFingering,\n): void {\n const secondsPerBeat = state.secondsPerBeat;\n if (secondsPerBeat === undefined || fromIndex === undefined) {\n return;\n }\n const previousNote = state.notes[fromIndex];\n const note = state.notes[toIndex];\n if (!previousNote || !note) {\n return;\n }\n const seconds = (note.startBeat - previousNote.startBeat) * secondsPerBeat;\n const shift = Math.abs(to.fret - from.fret);\n if (seconds <= EPS || shift === 0) {\n return;\n }\n if (shift / seconds > MAX_FRET_SHIFT_PER_SECOND) {\n state.issues.push({\n type: 'tooFast',\n layer: 3,\n notes: [fromIndex, toIndex].sort((a, b) => a - b),\n startBeat: note.startBeat,\n impossible: false,\n message: `a ${shift}-fret shift in ${seconds.toFixed(3)}s is beyond ${profile.name}`,\n });\n }\n}\n\n/** Layer 2: one hand cannot span that many frets at once. */\nfunction checkStretch(\n state: Analysis,\n profile: InstrumentProfile & { kind: 'stringed' },\n group: { startBeat: number; members: number[] },\n): void {\n const frets = group.members\n .map((member) => state.placements[member]?.fingering)\n .filter(\n (fingering): fingering is StringFingering => fingering !== undefined && fingering.fret > 0,\n )\n .map((fingering) => fingering.fret);\n if (frets.length < 2) {\n return;\n }\n const span = Math.max(...frets) - Math.min(...frets);\n if (span > profile.maxStretch) {\n state.issues.push({\n type: 'stretchTooWide',\n layer: 2,\n notes: [...group.members].sort((a, b) => a - b),\n startBeat: group.startBeat,\n impossible: true,\n message: `a ${span}-fret stretch exceeds the ${profile.maxStretch} frets one hand spans on ${profile.name}`,\n });\n }\n}\n\n/** Layer 2: more notes sounding at once than the instrument has voices. */\nfunction checkPolyphony(\n state: Analysis,\n profile: InstrumentProfile,\n group: { startBeat: number; members: number[] },\n): void {\n if (group.members.length <= profile.polyphony) {\n return;\n }\n state.issues.push({\n type: 'polyphonyExceeded',\n layer: 2,\n notes: [...group.members].sort((a, b) => a - b),\n startBeat: group.startBeat,\n impossible: true,\n message: `${group.members.length} notes sound at once; ${profile.name} has ${profile.polyphony}`,\n });\n}\n\n/** Layers 2 and 3 for a kit, and the limb assignment that goes with them. */\nfunction placeOnKit(state: Analysis, profile: InstrumentProfile & { kind: 'percussion' }): void {\n /** Limb that last struck each voice, so a voice keeps its stick. */\n const limbForVoice = new Map<number, Limb>();\n /** Last stroke of each limb, as a note index. */\n const lastStroke = new Map<Limb, number>();\n\n for (const group of onsetGroups(state)) {\n const taken = new Set<Limb>();\n let conflicted = false;\n // The most constrained voice picks first: a kick only the right foot\n // reaches must not lose it to a snare that either hand could have taken.\n const members = [...group.members].sort((a, b) => {\n const left = reachOf(profile, state.notes[a]?.pitch);\n const right = reachOf(profile, state.notes[b]?.pitch);\n return left.length - right.length || a - b;\n });\n for (const index of members) {\n const note = state.notes[index];\n const placement = state.placements[index];\n if (!note || !placement) {\n continue;\n }\n const candidates = reachOf(profile, note.pitch);\n if (candidates.length === 0) {\n continue;\n }\n const preferred = limbForVoice.get(note.pitch);\n const free = candidates.filter((limb) => !taken.has(limb));\n const chosen =\n free.length === 0\n ? candidates[0]\n : preferred !== undefined && free.includes(preferred)\n ? preferred\n : free[0];\n if (free.length === 0) {\n conflicted = true;\n }\n if (chosen === undefined) {\n continue;\n }\n taken.add(chosen);\n placement.limb = chosen;\n limbForVoice.set(note.pitch, chosen);\n\n state.movement += 1;\n const previousIndex = lastStroke.get(chosen);\n if (previousIndex !== undefined) {\n const previousNote = state.notes[previousIndex];\n if (previousNote && previousNote.pitch !== note.pitch) {\n // The limb had to travel across the kit rather than repeat a voice.\n state.movement += 1;\n }\n checkStrokeSpeed(state, profile, previousIndex, index, chosen);\n }\n lastStroke.set(chosen, index);\n }\n if (conflicted) {\n state.issues.push({\n type: 'limbConflict',\n layer: 2,\n notes: [...group.members].sort((a, b) => a - b),\n startBeat: group.startBeat,\n impossible: true,\n message: `${group.members.length} simultaneous strokes cannot be shared among the limbs of ${profile.name}`,\n });\n } else {\n checkPolyphony(state, profile, group);\n }\n }\n}\n\n/** Layer 3: one limb cannot strike twice that close together. */\nfunction checkStrokeSpeed(\n state: Analysis,\n profile: InstrumentProfile,\n fromIndex: number,\n toIndex: number,\n limb: Limb,\n): void {\n const secondsPerBeat = state.secondsPerBeat;\n if (secondsPerBeat === undefined) {\n return;\n }\n const previousNote = state.notes[fromIndex];\n const note = state.notes[toIndex];\n if (!previousNote || !note) {\n return;\n }\n const seconds = (note.startBeat - previousNote.startBeat) * secondsPerBeat;\n if (seconds <= EPS || seconds >= MIN_LIMB_STROKE_SECONDS) {\n return;\n }\n state.issues.push({\n type: 'tooFast',\n layer: 3,\n notes: [fromIndex, toIndex].sort((a, b) => a - b),\n startBeat: note.startBeat,\n impossible: false,\n message: `${limb} strikes twice ${seconds.toFixed(3)}s apart on ${profile.name}`,\n });\n}\n\n/** Note indices sharing each distinct onset, in onset order. */\nfunction onsetGroups(state: Analysis): { startBeat: number; members: number[] }[] {\n const groups: { startBeat: number; members: number[] }[] = [];\n let key = Number.NaN;\n let current: { startBeat: number; members: number[] } | undefined;\n for (const index of state.order) {\n const note = state.notes[index];\n if (!note) {\n continue;\n }\n const onsetKey = Math.round(note.startBeat * ONSET_GRID);\n if (!current || onsetKey !== key) {\n current = { startBeat: note.startBeat, members: [] };\n groups.push(current);\n key = onsetKey;\n }\n current.members.push(index);\n }\n return groups;\n}\n\n/**\n * Movement per unit time, on a 1..5 scale.\n *\n * The rate is what a passage demands of the player: strokes and shifts divided\n * by the seconds available for them. It is mapped through a saturating curve so\n * the scale has a fixed top a ceiling can be compared against — a faster tempo\n * over the same notes never reads easier, and 5 is reached only where the\n * passage has already left what the instrument allows.\n */\nfunction difficultyOf(state: Analysis, bpm: number | undefined): number {\n let first = Number.POSITIVE_INFINITY;\n let last = Number.NEGATIVE_INFINITY;\n for (const note of state.notes) {\n if (!note) {\n continue;\n }\n first = Math.min(first, note.startBeat);\n last = Math.max(last, note.startBeat + Math.max(0, note.durationBeat));\n }\n const spanBeats = last - first;\n if (!Number.isFinite(spanBeats) || spanBeats <= EPS) {\n return MIN_DIFFICULTY;\n }\n const seconds = (spanBeats * 60) / (bpm ?? REFERENCE_BPM);\n const rate = state.movement / seconds;\n const scaled =\n MIN_DIFFICULTY + (MAX_DIFFICULTY - MIN_DIFFICULTY) * (1 - Math.exp(-rate / DIFFICULTY_SCALE));\n return Math.round(scaled * 1e4) / 1e4;\n}\n","import type { Articulation } from './articulation.js';\nimport type { StringedProfile } from './profile.js';\n\n/** Techniques common to every fretted string instrument here. */\nconst STRINGED_ARTICULATIONS: readonly Articulation[] = Object.freeze([\n 'accent',\n 'ghost',\n 'staccato',\n 'legato',\n 'slide',\n 'hammer',\n 'mute',\n 'open',\n] as const);\n\n/**\n * Electric bass in standard tuning: E1 A1 D2 G2 over a 24-fret neck.\n *\n * @category Core\n */\nexport const BASS_4_STRING: StringedProfile = Object.freeze({\n kind: 'stringed',\n name: '4-string bass',\n tuning: Object.freeze([28, 33, 38, 43]),\n frets: 24,\n maxStretch: 4,\n articulations: STRINGED_ARTICULATIONS,\n polyphony: 4,\n});\n\n/**\n * Five-string bass with the low B: B0 E1 A1 D2 G2.\n *\n * @category Core\n */\nexport const BASS_5_STRING: StringedProfile = Object.freeze({\n kind: 'stringed',\n name: '5-string bass',\n tuning: Object.freeze([23, 28, 33, 38, 43]),\n frets: 24,\n maxStretch: 4,\n articulations: STRINGED_ARTICULATIONS,\n polyphony: 5,\n});\n\n/**\n * Six-string guitar in standard tuning: E2 A2 D3 G3 B3 E4.\n *\n * @category Core\n */\nexport const GUITAR_STANDARD: StringedProfile = Object.freeze({\n kind: 'stringed',\n name: 'guitar',\n tuning: Object.freeze([40, 45, 50, 55, 59, 64]),\n frets: 24,\n maxStretch: 5,\n articulations: STRINGED_ARTICULATIONS,\n polyphony: 6,\n});\n\n/**\n * Six-string guitar in drop D: the lowest string down a tone to D2, the rest\n * standard. Only the tuning differs, which is the whole point of deriving the\n * range from it.\n *\n * @category Core\n */\nexport const GUITAR_DROP_D: StringedProfile = Object.freeze({\n kind: 'stringed',\n name: 'guitar (drop D)',\n tuning: Object.freeze([38, 45, 50, 55, 59, 64]),\n frets: 24,\n maxStretch: 5,\n articulations: STRINGED_ARTICULATIONS,\n polyphony: 6,\n});\n","/**\n * The hash behind hierarchical seeds and position-addressed draws.\n *\n * Both public entry points reduce a root seed plus a path to one 32-bit word,\n * so the mixing lives here once. The construction is FNV-1a (32-bit) over a\n * type-tagged byte encoding of the path, finished with the MurmurHash3 fmix32\n * avalanche step: FNV-1a alone leaves neighbouring inputs — `'drums'` against\n * `'drum'`, bar 1 against bar 2 — differing in only a few bits, and fmix32\n * spreads a one-bit input difference across the whole word so sibling paths\n * behave as unrelated seeds.\n *\n * Nothing here reads the platform: only code units, IEEE 754 bytes and 32-bit\n * integer arithmetic, so the same path yields the same word in every runtime\n * and every locale.\n */\n\nimport { InvalidInputError } from '../errors/index.js';\nimport { assertFiniteNumber } from '../validation/index.js';\n\n/** Number of distinct values a 32-bit word takes; the divisor for a unit float. */\nexport const UINT32_RANGE = 4294967296;\n\n/** FNV-1a 32-bit offset basis. */\nconst FNV_OFFSET_BASIS = 0x811c9dc5;\n\n/** FNV-1a 32-bit prime. */\nconst FNV_PRIME = 0x01000193;\n\n/**\n * Keyspace a hash belongs to.\n *\n * Derived seeds and positional draws are asked for at the same paths, so they\n * are hashed under different tags; without that, `at(...path)` would be the\n * unit-scaled `deriveSeed(seed, ...path)` and a generator using both at one\n * position would draw the same number twice.\n */\nexport const DOMAIN_SEED = 0x01;\nexport const DOMAIN_POSITION = 0x02;\n\n/** Segment tags, so `'3'` and `3` never encode to the same bytes. */\nconst TAG_STRING = 0x01;\nconst TAG_NUMBER = 0x02;\n\n/**\n * Scratch view for the IEEE 754 encoding of a numeric segment.\n *\n * Written and read within a single synchronous call, so the reuse is not\n * observable; it keeps the hot path — one draw per bar, beat and voice — free\n * of per-call allocation.\n */\nconst numberBytes = new DataView(new ArrayBuffer(8));\n\n/** Absorb one byte into a running FNV-1a hash. */\nfunction absorb(hash: number, byte: number): number {\n return Math.imul(hash ^ (byte & 0xff), FNV_PRIME) >>> 0;\n}\n\n/** Absorb a 32-bit word, least significant byte first. */\nfunction absorbUint32(hash: number, value: number): number {\n let next = hash;\n for (let shift = 0; shift < 32; shift += 8) {\n next = absorb(next, (value >>> shift) & 0xff);\n }\n return next;\n}\n\n/**\n * MurmurHash3 32-bit finalizer.\n *\n * @param value The accumulated hash.\n * @returns The avalanched word.\n */\nfunction fmix32(value: number): number {\n let mixed = value >>> 0;\n mixed ^= mixed >>> 16;\n mixed = Math.imul(mixed, 0x85ebca6b);\n mixed ^= mixed >>> 13;\n mixed = Math.imul(mixed, 0xc2b2ae35);\n mixed ^= mixed >>> 16;\n return mixed >>> 0;\n}\n\n/**\n * Absorb one path segment, tagged and length-prefixed.\n *\n * The length prefix is what keeps `['ab', 'c']` apart from `['a', 'bc']`: a\n * plain separator byte could itself appear inside a segment.\n */\nfunction absorbSegment(hash: number, segment: string | number, index: number): number {\n if (typeof segment === 'string') {\n let next = absorb(hash, TAG_STRING);\n next = absorbUint32(next, segment.length);\n for (let i = 0; i < segment.length; i += 1) {\n // UTF-16 code units, low byte first: a fixed encoding of the string,\n // with no case folding or normalisation that a locale could steer.\n const unit = segment.charCodeAt(i);\n next = absorb(next, unit & 0xff);\n next = absorb(next, (unit >>> 8) & 0xff);\n }\n return next;\n }\n if (typeof segment === 'number') {\n // A path segment that is not a number at all would otherwise hash as one\n // fixed word (NaN) or two (the infinities), aliasing unrelated positions.\n assertFiniteNumber(segment, `seed path segment ${index}`);\n // Negative zero carries different bytes from zero while comparing equal,\n // which would make two paths a caller reads as identical derive apart.\n numberBytes.setFloat64(0, segment === 0 ? 0 : segment, true);\n let next = absorb(hash, TAG_NUMBER);\n for (let i = 0; i < 8; i += 1) {\n next = absorb(next, numberBytes.getUint8(i));\n }\n return next;\n }\n throw new InvalidInputError(\n `seed path segment ${index} must be a string or a number; received ${typeof segment}`,\n );\n}\n\n/**\n * Hash a root seed and a path to a 32-bit word within one keyspace.\n *\n * @param domain The keyspace tag.\n * @param root The validated root seed.\n * @param path The path segments.\n * @returns The mixed word in `[0, 0xffffffff]`.\n */\nexport function hashPath(domain: number, root: number, path: readonly (string | number)[]): number {\n let hash = absorb(FNV_OFFSET_BASIS, domain);\n hash = absorbUint32(hash, root);\n for (let index = 0; index < path.length; index += 1) {\n const segment = path[index];\n if (segment === undefined) {\n throw new InvalidInputError(\n `seed path segment ${index} must be a string or a number; received undefined`,\n );\n }\n hash = absorbSegment(hash, segment, index);\n }\n return fmix32(hash);\n}\n","/**\n * Position-addressed randomness and threshold sampling.\n *\n * Drawing in call order makes a generator's output depend on how many draws\n * preceded it, so editing a parameter in the middle of a piece redraws\n * everything after it. Addressing each draw by where it happens — bar, beat,\n * voice — removes that coupling: the value at a position depends on the\n * position alone, so a change anywhere leaves every other position as it was.\n *\n * Deciding inclusion by comparing a fixed positional draw against a complexity\n * dial gives the property a slider needs: raising the dial only adds events,\n * and everything already sounding stays where it was.\n */\n\nimport { InvalidInputError } from '../errors/index.js';\nimport { assertInteger, assertRange } from '../validation/index.js';\nimport { DOMAIN_POSITION, hashPath, UINT32_RANGE } from './internal.js';\nimport type { SeedPath } from './seed.js';\n\n/**\n * A random source addressed by position rather than by call order.\n *\n * @category Utilities\n */\nexport type PositionalRng = {\n /**\n * The draw belonging to a position: a float in [0, 1) that depends only on\n * the seed and the path, so it is the same however often and in whatever\n * order it is asked for.\n */\n at: (...path: SeedPath) => number;\n};\n\n/**\n * Create a position-addressed random source.\n *\n * `at` holds no cursor: it is a pure function of the seed and the path, safe to\n * call in any order, any number of times, from anywhere in a generator. Its\n * draws are independent of {@link deriveSeed} at the same path, so a generator\n * may seed a sub-part and take a positional draw at one position without\n * getting the same number twice.\n *\n * @param seed The seed; an integer in 0..0xffffffff, typically from\n * {@link deriveSeed}.\n * @returns The positional source.\n * @throws If the seed is not a 32-bit integer.\n * @example\n * ```ts\n * import { createPositionalRng, deriveSeed } from '@libraz/libcantus';\n * const rng = createPositionalRng(deriveSeed(42, 'drums'));\n * rng.at('ghost', 4, 2.5); // the same value however often it is asked for\n * ```\n * @category Utilities\n */\nexport function createPositionalRng(seed: number): PositionalRng {\n assertInteger(seed, 'seed', 0, 0xffffffff);\n return {\n at: (...path: SeedPath) => hashPath(DOMAIN_POSITION, seed, path) / UINT32_RANGE,\n };\n}\n\n/**\n * Whether the event at a position belongs in the output at a given complexity.\n *\n * The test is `at(...path) < complexity`, and because the draw is fixed by the\n * position, the selected set grows monotonically with complexity: for any\n * `c1 < c2` the events at `c1` are a subset of the events at `c2` over the same\n * seed and the same path space. Complexity 0 selects nothing and complexity 1\n * selects everything.\n *\n * @param rng The positional source; may be a caller-supplied one.\n * @param complexity The dial, in [0, 1].\n * @param path The position to test.\n * @returns True when the position is included.\n * @throws If the complexity is outside [0, 1], or the source returns a value\n * outside [0, 1).\n * @example\n * ```ts\n * import { createPositionalRng, includeAt } from '@libraz/libcantus';\n * const rng = createPositionalRng(7);\n * includeAt(rng, 0.3, 'ghost', 4, 2.5); // raising 0.3 only adds hits\n * ```\n * @category Utilities\n */\nexport function includeAt(rng: PositionalRng, complexity: number, ...path: SeedPath): boolean {\n assertRange(complexity, 0, 1, 'complexity');\n const draw = rng.at(...path);\n // A substituted source is part of the contract, and one that returns 1, a\n // negative value or NaN would quietly break monotonicity rather than fail.\n if (!(draw >= 0 && draw < 1)) {\n throw new InvalidInputError(`positional draw must be in [0, 1); received ${draw}`);\n }\n return draw < complexity;\n}\n","/**\n * Hierarchical seeds: one project number that addresses every part.\n *\n * A flat seed per generator makes the caller keep a table of numbers and,\n * worse, makes \"reroll the drums\" mean \"pick a number nobody else is using\".\n * Deriving instead — `deriveSeed(project, 'drums', 'chorus', 2)` — turns a\n * reroll into an extra path segment, and every other part keeps the seed it\n * had because its path did not change.\n */\n\nimport { assertInteger } from '../validation/index.js';\nimport { DOMAIN_SEED, hashPath } from './internal.js';\n\n/**\n * The address of one derivation: a namespace, and whatever names or indices\n * locate the thing being seeded.\n *\n * Strings and numbers are kept apart by the hash, so `'3'` and `3` address\n * different things, as do `['ab', 'c']` and `['a', 'bc']`.\n *\n * @category Utilities\n */\nexport type SeedPath = readonly (string | number)[];\n\n/**\n * Derive a child seed from a root seed and a path.\n *\n * The result is a 32-bit integer, so it can be passed straight to\n * {@link createRng}. Derivation is a pure function of its arguments — the same\n * root and path give the same seed in every runtime — and near-identical paths\n * (`'drums'` against `'drum'`, bar 1 against bar 2) give unrelated seeds: the\n * path is hashed with FNV-1a and finished with the MurmurHash3 fmix32\n * avalanche step.\n *\n * @param root The project seed; an integer in 0..0xffffffff.\n * @param path The address to derive at; string or finite number segments.\n * @returns The derived seed, an integer in 0..0xffffffff.\n * @throws If the root is not a 32-bit integer, or a segment is neither a\n * string nor a finite number.\n * @example\n * ```ts\n * import { createRng, deriveSeed } from '@libraz/libcantus';\n * const drums = deriveSeed(42, 'drums', 'chorus', 3);\n * const rng = createRng(drums); // rerolling only the drums: bump the 3\n * ```\n * @category Utilities\n */\nexport function deriveSeed(root: number, ...path: SeedPath): number {\n // The root is held to the seed range createRng accepts, so a derived seed is\n // always a legal root for a further derivation.\n assertInteger(root, 'root seed', 0, 0xffffffff);\n return hashPath(DOMAIN_SEED, root, path);\n}\n","/**\n * The reproducibility contract for generated output.\n *\n * A saved project is a seed plus parameters, and it only reopens as the same\n * piece if the library still turns them into the same notes. Package versions\n * cannot carry that promise: a bug fix inside a generator is a patch release\n * and still moves every note. The algorithm version is the separate number\n * that does carry it, declared here and recorded by the caller alongside the\n * seed.\n */\n\nimport { InvalidInputError } from '../errors/index.js';\nimport { assertInteger } from '../validation/index.js';\n\n/**\n * The generator algorithm version this build produces by default.\n *\n * The guarantee it carries: for a fixed algorithm version, the same inputs —\n * seed, version and every documented parameter — produce the same generated\n * output from any build of this library that accepts that version. It covers\n * what the generators return, and nothing else: analysis results, error\n * messages and output taken under a different version are outside it.\n *\n * Changing what a generator returns for a version it already accepts is a\n * defect. New behaviour raises this constant instead, and the older version\n * keeps producing what it produced. Withdrawing an accepted version is a\n * breaking change, and a project pinned to a withdrawn or not-yet-known\n * version is rejected rather than reinterpreted.\n *\n * @category Utilities\n */\nexport const ALGORITHM_VERSION = 1;\n\n/**\n * The oldest algorithm version this build still produces.\n *\n * @category Utilities\n */\nexport const MIN_ALGORITHM_VERSION = 1;\n\n/**\n * Resolve the algorithm version a request runs under.\n *\n * An unpinned request runs at {@link ALGORITHM_VERSION}, which is what a\n * caller who wants the current behaviour means; a caller who wants the piece\n * back records the resolved number and passes it next time. A version this\n * build does not produce is rejected — a project saved by a newer build cannot\n * be reproduced by an older one, and quietly rendering it at another version\n * would return a different piece under the same name.\n *\n * @param requested The pinned version, or undefined for the current one.\n * @returns The version to generate under.\n * @throws If the version is not an integer this build produces.\n * @example\n * ```ts\n * import { ALGORITHM_VERSION, resolveAlgorithmVersion } from '@libraz/libcantus';\n * resolveAlgorithmVersion(undefined); // ALGORITHM_VERSION\n * resolveAlgorithmVersion(ALGORITHM_VERSION); // the pinned value, unchanged\n * ```\n * @category Utilities\n */\nexport function resolveAlgorithmVersion(requested?: number): number {\n if (requested === undefined) {\n return ALGORITHM_VERSION;\n }\n assertInteger(requested, 'algorithm version');\n if (requested < MIN_ALGORITHM_VERSION || requested > ALGORITHM_VERSION) {\n throw new InvalidInputError(\n `algorithm version ${requested} is not produced by this build; supported versions are ` +\n `${MIN_ALGORITHM_VERSION}..${ALGORITHM_VERSION}`,\n );\n }\n return requested;\n}\n","/**\n * Deterministic seeded pseudo-random number generation shared across the\n * generative modules (rhythm, drums, bass, groove, counter-melody). A single\n * mulberry32 core exposes the small set of samplers those modules need; given a\n * seed the stream is fully reproducible.\n *\n * Around that core sit the pieces that make a whole project reproducible: seeds\n * derived hierarchically from one project number, draws addressed by position\n * rather than by call order, and the algorithm version the output is pinned to.\n */\n\nimport { InvalidInputError } from '../errors/index.js';\nimport { assertFiniteNumber, assertInteger, assertRange } from '../validation/index.js';\n\nexport type { PositionalRng } from './positional.js';\nexport { createPositionalRng, includeAt } from './positional.js';\nexport type { SeedPath } from './seed.js';\nexport { deriveSeed } from './seed.js';\nexport { ALGORITHM_VERSION, MIN_ALGORITHM_VERSION, resolveAlgorithmVersion } from './version.js';\n\n/**\n * A deterministic PRNG with the sampling helpers the generators need.\n *\n * @category Utilities\n */\nexport type Rng = {\n /** Next float in [0, 1). */\n next: () => number;\n /** True with probability `p`. */\n prob: (p: number) => boolean;\n /** Integer in the inclusive range [lo, hi]. */\n range: (lo: number, hi: number) => number;\n /** Float in [lo, hi). */\n float: (lo: number, hi: number) => number;\n};\n\n/**\n * Create a seeded PRNG (mulberry32) exposing the shared samplers.\n *\n * @param seed The 32-bit seed; the same seed always yields the same stream.\n * @returns The seeded generator.\n * @example\n * ```ts\n * import { createRng } from '@libraz/libcantus';\n * const rng = createRng(42);\n * rng.next(); // deterministic float in [0, 1)\n * rng.range(1, 6); // deterministic integer in [1, 6]\n * ```\n * @category Utilities\n */\nexport function createRng(seed: number): Rng {\n // The state is 32 bits, so a wider or fractional seed would be truncated —\n // silently mapping distinct seeds onto one sequence.\n assertInteger(seed, 'seed', 0, 0xffffffff);\n let state = seed >>> 0;\n const next = () => {\n state = (state + 0x6d2b79f5) >>> 0;\n let t = state;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n return {\n next,\n prob: (p) => {\n // Validate first: a draw consumed by a rejected call shifts the stream,\n // so a caller who catches the error gets a different sequence than one\n // who never made the call.\n assertRange(p, 0, 1, 'probability');\n return next() < p;\n },\n range: (lo, hi) => {\n assertInteger(lo, 'range lower bound');\n assertInteger(hi, 'range upper bound');\n if (lo > hi) {\n throw new InvalidInputError(\n `range lower bound must not exceed upper bound; received ${lo} > ${hi}`,\n );\n }\n assertInteger(hi - lo, 'range span', 0);\n return lo + Math.floor(next() * (hi - lo + 1));\n },\n float: (lo, hi) => {\n assertFiniteNumber(lo, 'float lower bound');\n assertFiniteNumber(hi, 'float upper bound');\n if (lo > hi) {\n throw new InvalidInputError(\n `float lower bound must not exceed upper bound; received ${lo} > ${hi}`,\n );\n }\n assertFiniteNumber(hi - lo, 'float span');\n return lo + next() * (hi - lo);\n },\n };\n}\n"]}
@@ -0,0 +1,379 @@
1
+ import {
2
+ InvalidInputError,
3
+ assertFiniteNumber,
4
+ assertInteger,
5
+ assertMeterMap,
6
+ assertNoteEvents,
7
+ assertPositiveInt,
8
+ assertRange,
9
+ assertTimeSignature,
10
+ barBeatsOf,
11
+ barIndexOf,
12
+ barLengthOf,
13
+ barStartOf,
14
+ beatOfBarIndex,
15
+ entryIndexOf,
16
+ groupingSumOf,
17
+ isAdditiveReading,
18
+ isCompoundNumerator,
19
+ isMeterMap,
20
+ parseFailure,
21
+ pulseBeatsOf,
22
+ pulseCountOf,
23
+ pulseGroupingOf,
24
+ unwrapParse
25
+ } from "./chunk-VTI6FOLB.js";
26
+
27
+ // src/core/event-index/index.ts
28
+ var EPS = 1e-9;
29
+ function upperBound(values, beat) {
30
+ let low = 0;
31
+ let high = values.length;
32
+ while (low < high) {
33
+ const middle = Math.floor((low + high) / 2);
34
+ if ((values[middle]?.note.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {
35
+ low = middle + 1;
36
+ } else {
37
+ high = middle;
38
+ }
39
+ }
40
+ return low;
41
+ }
42
+ function createNoteEventIndex(events, options = {}) {
43
+ assertNoteEvents(events, options.name ?? "note events", options);
44
+ const tieBreak = options.tieBreak ?? "highest";
45
+ const notes = Object.freeze(
46
+ events.map((note, originalIndex) => ({
47
+ note: Object.freeze({ ...note }),
48
+ originalIndex,
49
+ endBeat: note.startBeat + note.durationBeat
50
+ })).sort((a, b) => a.note.startBeat - b.note.startBeat || a.originalIndex - b.originalIndex)
51
+ );
52
+ let treeSize = 1;
53
+ while (treeSize < notes.length) treeSize *= 2;
54
+ const maxEndTree = new Array(treeSize * 2).fill(Number.NEGATIVE_INFINITY);
55
+ for (let index = 0; index < notes.length; index += 1) {
56
+ maxEndTree[treeSize + index] = notes[index]?.endBeat ?? Number.NEGATIVE_INFINITY;
57
+ }
58
+ for (let index = treeSize - 1; index > 0; index -= 1) {
59
+ maxEndTree[index] = Math.max(
60
+ maxEndTree[index * 2] ?? Number.NEGATIVE_INFINITY,
61
+ maxEndTree[index * 2 + 1] ?? Number.NEGATIVE_INFINITY
62
+ );
63
+ }
64
+ const latestActiveIndex = (exclusive, beat) => {
65
+ const find = (node, start, end) => {
66
+ if (start >= exclusive || (maxEndTree[node] ?? Number.NEGATIVE_INFINITY) <= beat + EPS) {
67
+ return -1;
68
+ }
69
+ if (end - start === 1) return start < notes.length ? start : -1;
70
+ const mid = Math.floor((start + end) / 2);
71
+ const right = find(node * 2 + 1, mid, end);
72
+ return right >= 0 ? right : find(node * 2, start, mid);
73
+ };
74
+ return find(1, 0, treeSize);
75
+ };
76
+ return {
77
+ notes,
78
+ at(beat) {
79
+ assertFiniteNumber(beat, "beat");
80
+ const index = latestActiveIndex(upperBound(notes, beat + EPS), beat);
81
+ if (index < 0) return void 0;
82
+ const onset = notes[index]?.note.startBeat;
83
+ let best;
84
+ for (let candidateIndex = index; candidateIndex >= 0; candidateIndex -= 1) {
85
+ const indexed = notes[candidateIndex];
86
+ if (indexed !== void 0 && indexed.note.startBeat !== onset) break;
87
+ if (indexed === void 0 || indexed.note.durationBeat <= 0 || indexed.note.startBeat - EPS > beat || beat >= indexed.endBeat - EPS) {
88
+ continue;
89
+ }
90
+ if (best === void 0) {
91
+ best = indexed;
92
+ continue;
93
+ }
94
+ if (tieBreak === "last") continue;
95
+ const higher = indexed.note.pitch > best.note.pitch;
96
+ if (tieBreak === "highest" ? higher : !higher) {
97
+ best = indexed;
98
+ }
99
+ }
100
+ return best;
101
+ },
102
+ attacksAt(beat) {
103
+ assertFiniteNumber(beat, "beat");
104
+ let index = upperBound(notes, beat + EPS) - 1;
105
+ while (index >= 0) {
106
+ const candidate = notes[index];
107
+ if (candidate === void 0 || Math.abs(candidate.note.startBeat - beat) >= EPS) {
108
+ return false;
109
+ }
110
+ if (candidate.note.durationBeat > 0) {
111
+ return true;
112
+ }
113
+ index -= 1;
114
+ }
115
+ return false;
116
+ },
117
+ onsetsBetween(startBeat, endBeat) {
118
+ assertFiniteNumber(startBeat, "startBeat");
119
+ assertFiniteNumber(endBeat, "endBeat");
120
+ const result = [];
121
+ let index = upperBound(notes, startBeat + EPS);
122
+ while (index < notes.length) {
123
+ const indexed = notes[index];
124
+ const onset = indexed?.note.startBeat;
125
+ if (indexed === void 0 || onset === void 0 || onset >= endBeat - EPS) {
126
+ break;
127
+ }
128
+ index += 1;
129
+ if (indexed.note.durationBeat <= 0) {
130
+ continue;
131
+ }
132
+ const previous = result[result.length - 1];
133
+ if (previous === void 0 || Math.abs(previous - onset) >= EPS) {
134
+ result.push(onset);
135
+ }
136
+ }
137
+ return result;
138
+ }
139
+ };
140
+ }
141
+
142
+ // src/core/meter/index.ts
143
+ function toMeterData(value, name = "meter") {
144
+ if (typeof value === "string") {
145
+ return parseTimeSignature(value);
146
+ }
147
+ if (typeof value === "object" && value !== null) {
148
+ const data = !Array.isArray(value) && "toJSON" in value && typeof value.toJSON === "function" ? value.toJSON() : value;
149
+ return isMeterMap(data) ? assertMeterMap(data, name) : assertTimeSignature(data, name);
150
+ }
151
+ throw new InvalidInputError(
152
+ `${name} must be a time signature, a meter map, or a signature name; received ${typeof value}`
153
+ );
154
+ }
155
+ var EPS2 = 1e-9;
156
+ var DEFAULT_TS = { numerator: 4, denominator: 4 };
157
+ function isMultiple(value, unit) {
158
+ if (unit === 0) {
159
+ return false;
160
+ }
161
+ const ratio = value / unit;
162
+ return Math.abs(ratio - Math.round(ratio)) < EPS2;
163
+ }
164
+ function parseTimeSignature(text) {
165
+ return unwrapParse(tryParseTimeSignature(text));
166
+ }
167
+ function tryParseTimeSignature(text) {
168
+ try {
169
+ if (typeof text !== "string") {
170
+ throw new InvalidInputError(`time signature must be a string; received ${typeof text}`);
171
+ }
172
+ return { ok: true, value: readTimeSignature(text) };
173
+ } catch (error) {
174
+ return parseFailure(error);
175
+ }
176
+ }
177
+ function readTimeSignature(text) {
178
+ const match = /^\s*((?:\d+\s*\+\s*)*\d+)\s*\/\s*(\d+)\s*$/.exec(text);
179
+ if (!match) {
180
+ throw new InvalidInputError(`Invalid time signature: ${text}`);
181
+ }
182
+ const groupTokens = (match[1] ?? "").split("+").map((token) => Number.parseInt(token.trim(), 10));
183
+ const numerator = groupTokens.reduce((sum, value) => sum + value, 0);
184
+ const denominator = Number.parseInt(match[2] ?? "", 10);
185
+ if (!Number.isSafeInteger(numerator) || !Number.isSafeInteger(denominator) || numerator <= 0 || denominator <= 0) {
186
+ throw new InvalidInputError(`Invalid time signature: ${text}`);
187
+ }
188
+ const grouping = groupTokens.length > 1 ? groupTokens : void 0;
189
+ return assertTimeSignature({ numerator, denominator, grouping }, `time signature ${text}`);
190
+ }
191
+ function formatTimeSignature(ts, opts = {}) {
192
+ assertTimeSignature(ts);
193
+ if (opts.grouping === true && ts.grouping !== void 0 && groupingSumOf(ts) === ts.numerator) {
194
+ return `${ts.grouping.join("+")}/${ts.denominator}`;
195
+ }
196
+ return `${ts.numerator}/${ts.denominator}`;
197
+ }
198
+ function isCompound(ts) {
199
+ assertTimeSignature(ts);
200
+ return isCompoundNumerator(ts.numerator) && !isAdditiveReading(ts);
201
+ }
202
+ function beatsPerBar(ts) {
203
+ assertTimeSignature(ts);
204
+ return barBeatsOf(ts);
205
+ }
206
+ function meterAt(beatInQuarters, meter) {
207
+ assertFiniteNumber(beatInQuarters, "beat");
208
+ return meterAtChecked(beatInQuarters, toMeterData(meter));
209
+ }
210
+ function meterAtChecked(beatInQuarters, meter) {
211
+ if (!isMeterMap(meter)) {
212
+ return meter;
213
+ }
214
+ return meter[entryIndexOf(meter, beatInQuarters)]?.ts ?? DEFAULT_TS;
215
+ }
216
+ function barStartBeat(beatInQuarters, meter) {
217
+ assertFiniteNumber(beatInQuarters, "beat");
218
+ return barStartChecked(beatInQuarters, toMeterData(meter));
219
+ }
220
+ function barStartChecked(beatInQuarters, meter) {
221
+ if (isMeterMap(meter)) {
222
+ return barStartOf(meter, beatInQuarters);
223
+ }
224
+ const barLen = barBeatsOf(meter);
225
+ return Math.floor(beatInQuarters / barLen + EPS2) * barLen;
226
+ }
227
+ function barIndexAt(beatInQuarters, meter) {
228
+ assertFiniteNumber(beatInQuarters, "beat");
229
+ return barIndexChecked(beatInQuarters, toMeterData(meter));
230
+ }
231
+ function barIndexChecked(beatInQuarters, meter) {
232
+ if (isMeterMap(meter)) {
233
+ return barIndexOf(meter, beatInQuarters);
234
+ }
235
+ return Math.floor(beatInQuarters / barBeatsOf(meter) + EPS2);
236
+ }
237
+ function beatsPerBarAt(beatInQuarters, meter) {
238
+ return barBeatsOf(meterAt(beatInQuarters, meter));
239
+ }
240
+ function resolveMeters(opts, name = "meters") {
241
+ if (opts.ts !== void 0 && opts.meters !== void 0) {
242
+ throw new InvalidInputError(
243
+ `${name} and ts name the same thing; give one or the other, not both`
244
+ );
245
+ }
246
+ if (opts.meters !== void 0) {
247
+ return assertMeterMap(opts.meters, name);
248
+ }
249
+ const ts = opts.ts ?? DEFAULT_TS;
250
+ assertTimeSignature(ts);
251
+ return [{ startBeat: 0, ts }];
252
+ }
253
+ function pulsesPerBar(ts) {
254
+ assertTimeSignature(ts);
255
+ return pulseCountOf(ts);
256
+ }
257
+ function isGroupHead(grouping, pulseIndex) {
258
+ let acc = 0;
259
+ for (const g of grouping) {
260
+ if (acc === pulseIndex) {
261
+ return true;
262
+ }
263
+ acc += g;
264
+ }
265
+ return false;
266
+ }
267
+ function isUniformGrouping(grouping) {
268
+ return grouping.every((entry) => entry === grouping[0]);
269
+ }
270
+ function beatToBarPosition(beatInQuarters, meter) {
271
+ assertFiniteNumber(beatInQuarters, "beat");
272
+ return barPositionChecked(beatInQuarters, toMeterData(meter));
273
+ }
274
+ function barPositionChecked(beatInQuarters, meter) {
275
+ return {
276
+ bar: barIndexChecked(beatInQuarters, meter),
277
+ beat: beatInQuarters - barStartChecked(beatInQuarters, meter)
278
+ };
279
+ }
280
+ function pulseBeats(ts) {
281
+ assertTimeSignature(ts);
282
+ return pulseBeatsOf(ts);
283
+ }
284
+ function barPositionToPulse(pos, meter) {
285
+ assertFiniteNumber(pos.beat, "position.beat");
286
+ return pulseChecked(pos, toMeterData(meter));
287
+ }
288
+ function pulseChecked(pos, meter) {
289
+ return pos.beat / pulseBeatsOf(meterAtBarChecked(pos.bar, meter)) + 1;
290
+ }
291
+ function meterAtBarChecked(bar, meter) {
292
+ if (!isMeterMap(meter)) {
293
+ return meter;
294
+ }
295
+ assertFiniteNumber(bar, "position.bar");
296
+ return meterAtChecked(beatOfBarIndex(meter, bar), meter);
297
+ }
298
+ function formatBarPosition(beatInQuarters, meter, decimals = 2) {
299
+ assertInteger(decimals, "decimals", 0, 100);
300
+ assertFiniteNumber(beatInQuarters, "beat");
301
+ const resolved = toMeterData(meter);
302
+ const position = barPositionChecked(beatInQuarters, resolved);
303
+ const ts = meterAtChecked(beatInQuarters, resolved);
304
+ const pulse = pulseChecked(position, ts);
305
+ const rounded = Number(pulse.toFixed(decimals));
306
+ const barLength = isMeterMap(resolved) ? barLengthOf(resolved, beatInQuarters) : barBeatsOf(resolved);
307
+ const pulsesInBar = Math.ceil(barLength / pulseBeatsOf(ts) - EPS2);
308
+ if (rounded >= pulsesInBar + 1) {
309
+ return `${barIndexChecked(barStartChecked(beatInQuarters, resolved) + barLength, resolved) + 1}.1`;
310
+ }
311
+ const wholePulse = Math.floor(rounded);
312
+ const fraction = Number((rounded - wholePulse).toFixed(decimals));
313
+ return fraction === 0 ? `${position.bar + 1}.${wholePulse}` : `${position.bar + 1}.${wholePulse}+${fraction}`;
314
+ }
315
+ function barPositionToBeat(pos, meter) {
316
+ assertInteger(pos.bar, "bar position bar");
317
+ assertFiniteNumber(pos.beat, "bar position beat");
318
+ const resolved = toMeterData(meter);
319
+ if (isMeterMap(resolved)) {
320
+ return beatOfBarIndex(resolved, pos.bar) + pos.beat;
321
+ }
322
+ return pos.bar * barBeatsOf(resolved) + pos.beat;
323
+ }
324
+ function metricWeight(beatInQuarters, meter) {
325
+ assertFiniteNumber(beatInQuarters, "beat");
326
+ const resolved = toMeterData(meter);
327
+ const ts = meterAtChecked(beatInQuarters, resolved);
328
+ const beat = beatInQuarters - barStartChecked(beatInQuarters, resolved);
329
+ const pulse = pulseBeatsOf(ts);
330
+ if (!isMultiple(beat, pulse)) {
331
+ return 0;
332
+ }
333
+ const pulses = pulseCountOf(ts);
334
+ const pulseIndex = Math.round(beat / pulse) % pulses;
335
+ if (pulseIndex === 0) {
336
+ return 3;
337
+ }
338
+ const grouping = pulseGroupingOf(ts);
339
+ if (grouping !== void 0 && !isUniformGrouping(grouping)) {
340
+ return isGroupHead(grouping, pulseIndex) ? 2 : 1;
341
+ }
342
+ if (pulses % 2 === 0 && pulseIndex === pulses / 2) {
343
+ return 2;
344
+ }
345
+ return 1;
346
+ }
347
+ function isStrongBeat(beatInQuarters, meter) {
348
+ return metricWeight(beatInQuarters, meter) >= 2;
349
+ }
350
+ function tuplet(totalBeats, count) {
351
+ assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, "tuplet total beats");
352
+ assertPositiveInt(count, "tuplet count");
353
+ return new Array(count).fill(totalBeats / count);
354
+ }
355
+
356
+ export {
357
+ createNoteEventIndex,
358
+ toMeterData,
359
+ parseTimeSignature,
360
+ tryParseTimeSignature,
361
+ formatTimeSignature,
362
+ isCompound,
363
+ beatsPerBar,
364
+ meterAt,
365
+ barStartBeat,
366
+ barIndexAt,
367
+ beatsPerBarAt,
368
+ resolveMeters,
369
+ pulsesPerBar,
370
+ beatToBarPosition,
371
+ pulseBeats,
372
+ barPositionToPulse,
373
+ formatBarPosition,
374
+ barPositionToBeat,
375
+ metricWeight,
376
+ isStrongBeat,
377
+ tuplet
378
+ };
379
+ //# sourceMappingURL=chunk-ELCTZUJJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/event-index/index.ts","../src/core/meter/index.ts"],"sourcesContent":["import type { NoteEvent } from '../types.js';\nimport { assertFiniteNumber, assertNoteEvents } from '../validation/index.js';\n\n/**\n * A validated note retaining its position in the caller's original array.\n *\n * @category Core\n */\nexport type IndexedNoteEvent = {\n note: Readonly<NoteEvent>;\n originalIndex: number;\n endBeat: number;\n};\n\n/**\n * Which note wins when several sound from the same onset.\n *\n * `'highest'` and `'lowest'` name a voice, so the answer does not depend on the\n * order the caller happened to store the chord in; `'last'` takes the one\n * latest in the input array.\n *\n * @category Core\n */\nexport type OnsetTieBreak = 'highest' | 'lowest' | 'last';\n\n/**\n * Binary-searchable, stable-sorted index over note onsets and active spans.\n *\n * @category Core\n */\nexport type NoteEventIndex = {\n notes: readonly IndexedNoteEvent[];\n /**\n * Latest-onset note sounding at `beat`. Simultaneous onsets are resolved by\n * the index's {@link OnsetTieBreak}.\n *\n * @throws If `beat` is not finite.\n */\n at: (beat: number) => IndexedNoteEvent | undefined;\n /**\n * Whether one or more sounding notes attack at `beat`.\n *\n * @throws If `beat` is not finite.\n */\n attacksAt: (beat: number) => boolean;\n /**\n * Unique sounding-note attack beats strictly inside `(startBeat, endBeat)`.\n *\n * @throws If either bound is not finite.\n */\n onsetsBetween: (startBeat: number, endBeat: number) => number[];\n};\n\nconst EPS = 1e-9;\n\nfunction upperBound(values: readonly IndexedNoteEvent[], beat: number): number {\n let low = 0;\n let high = values.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((values[middle]?.note.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low;\n}\n\n/**\n * How an index reads the events it is built from.\n *\n * @category Core\n */\nexport type NoteEventIndexOptions = {\n allowNonPositiveDuration?: boolean;\n tieBreak?: OnsetTieBreak;\n budget?: number;\n /** What the events are, for the validation error message. */\n name?: string;\n};\n\n/**\n * Validate and stable-sort note events once, then expose logarithmic onset and\n * active-note lookups. Non-positive-duration notes may be retained for callers\n * that intentionally filter them later, but never count as sounding.\n *\n * @category Core\n */\nexport function createNoteEventIndex(\n events: readonly NoteEvent[],\n options: NoteEventIndexOptions = {},\n): NoteEventIndex {\n assertNoteEvents(events, options.name ?? 'note events', options);\n const tieBreak = options.tieBreak ?? 'highest';\n const notes = Object.freeze(\n events\n .map((note, originalIndex) => ({\n note: Object.freeze({ ...note }),\n originalIndex,\n endBeat: note.startBeat + note.durationBeat,\n }))\n .sort((a, b) => a.note.startBeat - b.note.startBeat || a.originalIndex - b.originalIndex),\n );\n // Segment-tree maxima find the latest active onset in logarithmic time even\n // when an early, very long note would defeat a prefix-max backwards scan.\n let treeSize = 1;\n while (treeSize < notes.length) treeSize *= 2;\n const maxEndTree = new Array<number>(treeSize * 2).fill(Number.NEGATIVE_INFINITY);\n for (let index = 0; index < notes.length; index += 1) {\n maxEndTree[treeSize + index] = notes[index]?.endBeat ?? Number.NEGATIVE_INFINITY;\n }\n for (let index = treeSize - 1; index > 0; index -= 1) {\n maxEndTree[index] = Math.max(\n maxEndTree[index * 2] ?? Number.NEGATIVE_INFINITY,\n maxEndTree[index * 2 + 1] ?? Number.NEGATIVE_INFINITY,\n );\n }\n const latestActiveIndex = (exclusive: number, beat: number): number => {\n const find = (node: number, start: number, end: number): number => {\n if (start >= exclusive || (maxEndTree[node] ?? Number.NEGATIVE_INFINITY) <= beat + EPS) {\n return -1;\n }\n if (end - start === 1) return start < notes.length ? start : -1;\n const mid = Math.floor((start + end) / 2);\n const right = find(node * 2 + 1, mid, end);\n return right >= 0 ? right : find(node * 2, start, mid);\n };\n return find(1, 0, treeSize);\n };\n\n return {\n notes,\n at(beat) {\n // A query beat comes from wherever the host got it — a scrub position, a\n // parsed field — so it is checked like any other entry argument. `NaN`\n // compares false against every bound, which would read as an open window\n // rather than as the missing value it is.\n assertFiniteNumber(beat, 'beat');\n const index = latestActiveIndex(upperBound(notes, beat + EPS), beat);\n if (index < 0) return undefined;\n const onset = notes[index]?.note.startBeat;\n let best: IndexedNoteEvent | undefined;\n for (let candidateIndex = index; candidateIndex >= 0; candidateIndex -= 1) {\n const indexed = notes[candidateIndex];\n if (indexed !== undefined && indexed.note.startBeat !== onset) break;\n if (\n indexed === undefined ||\n indexed.note.durationBeat <= 0 ||\n indexed.note.startBeat - EPS > beat ||\n beat >= indexed.endBeat - EPS\n ) {\n continue;\n }\n if (best === undefined) {\n best = indexed;\n continue;\n }\n if (tieBreak === 'last') continue;\n const higher = indexed.note.pitch > best.note.pitch;\n if (tieBreak === 'highest' ? higher : !higher) {\n best = indexed;\n }\n }\n return best;\n },\n attacksAt(beat) {\n assertFiniteNumber(beat, 'beat');\n // Only a sounding note attacks: a zero-length artefact must not make\n // `attacksAt(b)` true on a beat where `at(b)` finds nothing.\n let index = upperBound(notes, beat + EPS) - 1;\n while (index >= 0) {\n const candidate = notes[index];\n if (candidate === undefined || Math.abs(candidate.note.startBeat - beat) >= EPS) {\n return false;\n }\n if (candidate.note.durationBeat > 0) {\n return true;\n }\n index -= 1;\n }\n return false;\n },\n onsetsBetween(startBeat, endBeat) {\n assertFiniteNumber(startBeat, 'startBeat');\n assertFiniteNumber(endBeat, 'endBeat');\n const result: number[] = [];\n let index = upperBound(notes, startBeat + EPS);\n while (index < notes.length) {\n const indexed = notes[index];\n const onset = indexed?.note.startBeat;\n if (indexed === undefined || onset === undefined || onset >= endBeat - EPS) {\n break;\n }\n index += 1;\n if (indexed.note.durationBeat <= 0) {\n continue;\n }\n const previous = result[result.length - 1];\n if (previous === undefined || Math.abs(previous - onset) >= EPS) {\n result.push(onset);\n }\n }\n return result;\n },\n };\n}\n","/**\n * Meter and time signatures: bar/beat arithmetic, simple vs. compound meters,\n * metric-accent hierarchy, and tuplet subdivision.\n *\n * Positions and durations are measured in quarter-note beats, matching the rest\n * of the library's beat convention (four quarter-note beats per bar in 4/4).\n * Beat 0 is the first downbeat, so a pickup sounds at negative beats, and a\n * piece that changes meter is described by a {@link MeterMap} rather than by\n * one signature.\n */\n\nimport { InvalidInputError, type ParseResult, parseFailure, unwrapParse } from '../errors/index.js';\nimport {\n assertFiniteNumber,\n assertInteger,\n assertMeterMap,\n assertPositiveInt,\n assertRange,\n assertTimeSignature,\n} from '../validation/index.js';\nimport type { MeterData } from './internal.js';\nimport {\n barBeatsOf,\n barIndexOf,\n barLengthOf,\n barStartOf,\n beatOfBarIndex,\n entryIndexOf,\n groupingSumOf,\n isAdditiveReading,\n isCompoundNumerator,\n isMeterMap,\n pulseBeatsOf,\n pulseCountOf,\n pulseGroupingOf,\n} from './internal.js';\n\n/**\n * A time signature as a numerator over a note-value denominator.\n *\n * @category Rhythm & Meter\n */\nexport type TimeSignature = {\n numerator: number;\n denominator: number;\n /**\n * Optional grouping of the bar into felt beats, as positive integers. The\n * entries are group lengths, written either in the meter's main pulses —\n * `[2, 2, 3]` for a 2+2+3 reading of 7/8, `[3, 2]` for 5/8 — or, for a\n * compound numerator, in denominator units. They must sum to\n * {@link pulsesPerBar} in the first spelling and to the numerator in the\n * second; a pulse is one unit outside compound meters, where the two\n * spellings coincide.\n *\n * A compound numerator accepts both readings, and the shape of the grouping\n * says which is meant. Groups of nothing but threes spell the compound\n * division itself, so 9/8 as `[3, 3, 3]` (equivalently `[1, 1, 1]`) is the\n * ordinary three dotted-quarter pulses; any other grouping summing to the\n * numerator reads additively, so 9/8 as `[2, 2, 2, 3]` is nine quaver pulses\n * grouped aksak-fashion. {@link pulsesPerBar}, {@link pulseBeats} and\n * {@link isCompound} follow whichever reading the grouping selects.\n *\n * The head pulse of each group other than the downbeat is a secondary strong\n * pulse to {@link metricWeight}/{@link isStrongBeat}. A grouping whose groups\n * are all the same length states no accent the meter does not already have,\n * so it weighs exactly as an ungrouped bar does.\n */\n grouping?: number[];\n};\n\n/**\n * One time signature and the beat at which it takes effect.\n *\n * @category Rhythm & Meter\n */\nexport type MeterChange = {\n /** Absolute quarter-note beat the signature takes effect at. */\n startBeat: number;\n /** The signature in force from that beat until the next change. */\n ts: TimeSignature;\n};\n\n/**\n * A piece's meter over time: the signatures in force, in beat order.\n *\n * A meter change starts a new bar, so a span that does not divide evenly into\n * its own bars ends with a short bar rather than spilling across the change.\n * The first entry also governs everything before it, which is what reads a\n * pickup written at negative beats in the signature the piece opens in.\n *\n * @category Rhythm & Meter\n */\nexport type MeterMap = MeterChange[];\n\nexport type { MeterData } from './internal.js';\n\n/**\n * Anything that names a meter: a signature text such as `'6/8'`, a plain\n * {@link TimeSignature}, a full {@link MeterMap}, or a value that serializes to\n * one of those such as the `Meter` class.\n *\n * Every meter-aware function takes this, so a piece in one meter reads as\n * `'4/4'` and a piece that changes meter reads as the map, without the caller\n * choosing a different function for each. Only a signature has a text form — a\n * meter map is a run of changes and is given as the data it is.\n *\n * @category Rhythm & Meter\n */\nexport type MeterLike =\n | string\n | TimeSignature\n | MeterMap\n | {\n /** The meter data this value stands for. */\n toJSON(): TimeSignature | MeterMap;\n };\n\n/**\n * Resolve any meter-shaped value to the plain meter data the library reads.\n *\n * The counterpart of {@link toNoteData} for meters: an entry point takes\n * whatever form the caller has — the signature a meter field holds, the data\n * the meter module returns, the map of a piece that changes meter, or a `Meter`\n * instance — and gets one of two shapes back. An instance is accepted through\n * its `toJSON` method rather than by its type, so the core layer can read a\n * class it cannot import.\n *\n * Text is read by {@link parseTimeSignature} and nothing else, so every entry\n * point that takes a meter accepts exactly the signatures that parser accepts,\n * the additive `'2+2+3/8'` included and nothing besides. A meter map has no text\n * form — it is a run of changes, each with the beat it starts at — so text\n * always names a single signature.\n *\n * @param value A signature text, a plain time signature, a meter map, or a\n * value whose `toJSON` returns one of those.\n * @param name What the meter is called in an error message, so a caller that\n * holds several of them hears which one was malformed.\n * @returns The validated signature, or the validated map.\n * @throws If the value names no meter, or the meter it names is malformed.\n */\nexport function toMeterData(value: MeterLike, name = 'meter'): MeterData {\n if (typeof value === 'string') {\n return parseTimeSignature(value);\n }\n if (typeof value === 'object' && value !== null) {\n const data =\n !Array.isArray(value) && 'toJSON' in value && typeof value.toJSON === 'function'\n ? value.toJSON()\n : (value as MeterData);\n return isMeterMap(data) ? assertMeterMap(data, name) : assertTimeSignature(data, name);\n }\n throw new InvalidInputError(\n `${name} must be a time signature, a meter map, or a signature name; received ${typeof value}`,\n );\n}\n\n/**\n * A position expressed as a bar index and a quarter-note offset within the bar.\n *\n * @category Rhythm & Meter\n */\nexport type BarPosition = {\n /**\n * 0-based bar index, counting the first full bar as 0.\n * {@link formatBarPosition} renders it 1-based. A pickup sounds before that\n * bar, so its notes report bar -1.\n */\n bar: number;\n /**\n * Quarter-note offset from the start of the bar — not the felt beat. In 6/8\n * the second felt beat is at `beat: 1.5`; {@link barPositionToPulse} converts\n * to felt-beat numbering.\n */\n beat: number;\n};\n\nconst EPS = 1e-9;\n\n/** The meter assumed when a caller names none. */\nconst DEFAULT_TS: TimeSignature = { numerator: 4, denominator: 4 };\n\n/** Whether `value` is an integer multiple of `unit` (within a float tolerance). */\nfunction isMultiple(value: number, unit: number): boolean {\n if (unit === 0) {\n return false;\n }\n const ratio = value / unit;\n return Math.abs(ratio - Math.round(ratio)) < EPS;\n}\n\n/**\n * Parse a time signature such as `\"4/4\"` or `\"6/8\"`, or the additive `\"2+2+3/8\"`.\n *\n * @param text The signature text.\n * @returns The parsed time signature.\n * @throws If the text is not `n/d` with positive integers, or names a signature\n * the library rejects. Use {@link tryParseTimeSignature} where failure is\n * ordinary, such as a meter field read on every keystroke.\n * @example\n * ```ts\n * import { parseTimeSignature } from '@libraz/libcantus';\n * parseTimeSignature('6/8'); // { numerator: 6, denominator: 8 }\n * ```\n * @category Rhythm & Meter\n */\nexport function parseTimeSignature(text: string): TimeSignature {\n return unwrapParse(tryParseTimeSignature(text));\n}\n\n/**\n * Parse a time signature, reporting failure instead of throwing it.\n *\n * The same reading as {@link parseTimeSignature} — that function is this one\n * with its error thrown — for the callers where text that does not parse yet is\n * the normal state of the input rather than a fault.\n *\n * @param text The signature text.\n * @returns The signature, or the error explaining why the text is not one.\n * @example\n * ```ts\n * import { tryParseTimeSignature } from '@libraz/libcantus';\n * const result = tryParseTimeSignature('7/8');\n * result.ok ? result.value.numerator : result.error.message;\n * ```\n * @category Rhythm & Meter\n */\nexport function tryParseTimeSignature(text: string): ParseResult<TimeSignature> {\n try {\n if (typeof text !== 'string') {\n throw new InvalidInputError(`time signature must be a string; received ${typeof text}`);\n }\n return { ok: true, value: readTimeSignature(text) };\n } catch (error) {\n return parseFailure(error);\n }\n}\n\n/** The reading both parsers share, throwing on anything it cannot read. */\nfunction readTimeSignature(text: string): TimeSignature {\n const match = /^\\s*((?:\\d+\\s*\\+\\s*)*\\d+)\\s*\\/\\s*(\\d+)\\s*$/.exec(text);\n if (!match) {\n throw new InvalidInputError(`Invalid time signature: ${text}`);\n }\n const groupTokens = (match[1] ?? '').split('+').map((token) => Number.parseInt(token.trim(), 10));\n const numerator = groupTokens.reduce((sum, value) => sum + value, 0);\n const denominator = Number.parseInt(match[2] ?? '', 10);\n if (\n !Number.isSafeInteger(numerator) ||\n !Number.isSafeInteger(denominator) ||\n numerator <= 0 ||\n denominator <= 0\n ) {\n throw new InvalidInputError(`Invalid time signature: ${text}`);\n }\n // The same signature every consumer validates, validated here too: parsing\n // a signature the rest of the library rejects only moves the failure.\n const grouping = groupTokens.length > 1 ? groupTokens : undefined;\n return assertTimeSignature({ numerator, denominator, grouping }, `time signature ${text}`);\n}\n\n/**\n * Render a time signature as `\"n/d\"`, or as `\"a+b+c/d\"` when it carries an\n * additive grouping and `grouping` is requested.\n *\n * The additive form is what {@link parseTimeSignature} reads back, and it adds\n * its terms to get the numerator — so only a grouping written in denominator\n * units can be rendered that way. A grouping counted in main pulses (9/8 as\n * `[1, 1, 1]`, 12/8 as `[2, 2]`) would come back as a different bar, so it\n * falls back to the plain form, which those groupings cost nothing: they select\n * the same reading the bare signature does.\n *\n * The plain form drops the grouping, so a round trip through it reads 7/8 as\n * flat rather than as 2+2+3; ask for the additive form to keep it.\n *\n * @param ts The time signature.\n * @param opts Set `grouping: true` to render an additive grouping.\n * @returns The formatted signature.\n * @example\n * ```ts\n * import { formatTimeSignature } from '@libraz/libcantus';\n * const aksak = { numerator: 7, denominator: 8, grouping: [2, 2, 3] };\n * formatTimeSignature(aksak); // '7/8'\n * formatTimeSignature(aksak, { grouping: true }); // '2+2+3/8'\n * ```\n * @category Rhythm & Meter\n */\nexport function formatTimeSignature(ts: TimeSignature, opts: { grouping?: boolean } = {}): string {\n assertTimeSignature(ts);\n if (opts.grouping === true && ts.grouping !== undefined && groupingSumOf(ts) === ts.numerator) {\n return `${ts.grouping.join('+')}/${ts.denominator}`;\n }\n return `${ts.numerator}/${ts.denominator}`;\n}\n\n/**\n * Whether a meter is compound: its main pulses each divide into three, as in\n * 6/8, 9/8, 12/8, or 6/4. Compound meters are those whose numerator is a\n * multiple of three greater than three, independent of the denominator. Meters\n * like 3/8 and 3/4 (simple triples, numerator 3) are not compound, and neither\n * is a signature whose {@link TimeSignature.grouping} selects the additive\n * reading (9/8 as 2+2+2+3).\n *\n * @param ts The time signature.\n * @returns True for compound meters.\n * @category Rhythm & Meter\n */\nexport function isCompound(ts: TimeSignature): boolean {\n assertTimeSignature(ts);\n return isCompoundNumerator(ts.numerator) && !isAdditiveReading(ts);\n}\n\n/**\n * Length of a bar in quarter-note beats.\n *\n * @param ts The time signature.\n * @returns The bar length in quarter notes.\n * @category Rhythm & Meter\n */\nexport function beatsPerBar(ts: TimeSignature): number {\n assertTimeSignature(ts);\n return barBeatsOf(ts);\n}\n\n/**\n * The time signature in force at a beat.\n *\n * @param beatInQuarters Absolute position in quarter-note beats.\n * @param meter A single signature, or the piece's meter map.\n * @returns The signature governing that beat.\n * @example\n * ```ts\n * import { meterAt, parseTimeSignature } from '@libraz/libcantus';\n * const meters = [\n * { startBeat: 0, ts: parseTimeSignature('4/4') },\n * { startBeat: 8, ts: parseTimeSignature('3/4') },\n * ];\n * meterAt(9, meters); // { numerator: 3, denominator: 4 }\n * ```\n * @category Rhythm & Meter\n */\nexport function meterAt(beatInQuarters: number, meter: MeterLike): TimeSignature {\n assertFiniteNumber(beatInQuarters, 'beat');\n return meterAtChecked(beatInQuarters, toMeterData(meter));\n}\n\n/** {@link meterAt} without re-reading an already resolved meter. */\nfunction meterAtChecked(beatInQuarters: number, meter: MeterData): TimeSignature {\n if (!isMeterMap(meter)) {\n return meter;\n }\n return meter[entryIndexOf(meter, beatInQuarters)]?.ts ?? DEFAULT_TS;\n}\n\n/**\n * Absolute beat at which the bar containing `beatInQuarters` begins.\n *\n * @param beatInQuarters Absolute position in quarter-note beats.\n * @param meter A single signature, or the piece's meter map.\n * @returns The bar's first beat, negative inside a pickup.\n * @category Rhythm & Meter\n */\nexport function barStartBeat(beatInQuarters: number, meter: MeterLike): number {\n assertFiniteNumber(beatInQuarters, 'beat');\n return barStartChecked(beatInQuarters, toMeterData(meter));\n}\n\n/** {@link barStartBeat} without re-reading an already resolved meter. */\nfunction barStartChecked(beatInQuarters: number, meter: MeterData): number {\n if (isMeterMap(meter)) {\n return barStartOf(meter, beatInQuarters);\n }\n const barLen = barBeatsOf(meter);\n return Math.floor(beatInQuarters / barLen + EPS) * barLen;\n}\n\n/**\n * Bar index of a beat: 0 for the first full bar, negative inside a pickup.\n *\n * Bars accumulate across meter changes, so the bar after a 4/4-to-3/4 change is\n * the next bar and not the first bar of a new count.\n *\n * @param beatInQuarters Absolute position in quarter-note beats.\n * @param meter A single signature, or the piece's meter map.\n * @returns The 0-based bar index.\n * @example\n * ```ts\n * import { barIndexAt, parseTimeSignature } from '@libraz/libcantus';\n * const meters = [\n * { startBeat: 0, ts: parseTimeSignature('4/4') },\n * { startBeat: 8, ts: parseTimeSignature('3/4') },\n * ];\n * barIndexAt(11, meters); // 3\n * ```\n * @category Rhythm & Meter\n */\nexport function barIndexAt(beatInQuarters: number, meter: MeterLike): number {\n assertFiniteNumber(beatInQuarters, 'beat');\n return barIndexChecked(beatInQuarters, toMeterData(meter));\n}\n\n/** {@link barIndexAt} without re-reading an already resolved meter. */\nfunction barIndexChecked(beatInQuarters: number, meter: MeterData): number {\n if (isMeterMap(meter)) {\n return barIndexOf(meter, beatInQuarters);\n }\n return Math.floor(beatInQuarters / barBeatsOf(meter) + EPS);\n}\n\n/**\n * Length in quarter-note beats of the bar containing a beat.\n *\n * The meter-map counterpart of {@link beatsPerBar}, which answers for one\n * signature and so cannot be asked about a piece that changes meter.\n *\n * @param beatInQuarters Absolute position in quarter-note beats.\n * @param meter A single signature, or the piece's meter map.\n * @returns The bar length in quarter notes.\n * @category Rhythm & Meter\n */\nexport function beatsPerBarAt(beatInQuarters: number, meter: MeterLike): number {\n return barBeatsOf(meterAt(beatInQuarters, meter));\n}\n\n/**\n * Read the meter out of a pair of options that accept either form.\n *\n * Entry points take `meters` for a piece that changes meter and keep `ts` as\n * sugar for a piece that does not, so this is the one place that resolves the\n * pair — including the rule that naming both is an input error rather than one\n * of them silently winning.\n *\n * @param opts The options carrying `ts`, `meters`, or neither.\n * @param name What the options belong to, for the error message.\n * @returns The meter map to analyse against; 4/4 throughout when neither is given.\n * @throws If both `ts` and `meters` are given, or the map is malformed.\n * @example\n * ```ts\n * import { parseTimeSignature, resolveMeters } from '@libraz/libcantus';\n * resolveMeters({ ts: parseTimeSignature('3/4') });\n * // [{ startBeat: 0, ts: { numerator: 3, denominator: 4 } }]\n * ```\n * @category Rhythm & Meter\n */\nexport function resolveMeters(\n opts: { ts?: TimeSignature; meters?: MeterMap },\n name = 'meters',\n): MeterMap {\n if (opts.ts !== undefined && opts.meters !== undefined) {\n throw new InvalidInputError(\n `${name} and ts name the same thing; give one or the other, not both`,\n );\n }\n if (opts.meters !== undefined) {\n return assertMeterMap(opts.meters, name);\n }\n const ts = opts.ts ?? DEFAULT_TS;\n assertTimeSignature(ts);\n return [{ startBeat: 0, ts }];\n}\n\n/**\n * Number of main pulses (felt beats) per bar: the numerator for simple meters,\n * a third of it for compound meters.\n *\n * For additive/irregular meters such as 7/8 or 5/8 this counts the raw pulses\n * (7 and 5 respectively), which are otherwise felt as flat, evenly divided and\n * equally accented pulses. To recover a 2+2+3 or 3+2 felt-beat grouping, set\n * {@link TimeSignature.grouping}; {@link metricWeight} then accents each\n * group's head pulse.\n *\n * @param ts The time signature.\n * @returns The pulse count per bar.\n * @category Rhythm & Meter\n */\nexport function pulsesPerBar(ts: TimeSignature): number {\n assertTimeSignature(ts);\n return pulseCountOf(ts);\n}\n\n/** Whether `pulseIndex` is the head pulse of one of the groups, counted in pulses. */\nfunction isGroupHead(grouping: number[], pulseIndex: number): boolean {\n let acc = 0;\n for (const g of grouping) {\n if (acc === pulseIndex) {\n return true;\n }\n acc += g;\n }\n return false;\n}\n\n/**\n * Whether every group is the same length.\n *\n * Such a grouping divides the bar the way its own meter already divides it, so\n * it names no accent structure: 9/8 as `[1, 1, 1]` is the plain compound bar,\n * not three pulses of equal strength.\n */\nfunction isUniformGrouping(grouping: number[]): boolean {\n return grouping.every((entry) => entry === grouping[0]);\n}\n\n/**\n * Convert an absolute quarter-note position to a bar index and in-bar offset.\n *\n * @param beatInQuarters Absolute position in quarter-note beats.\n * @param meter A single signature, or the piece's meter map.\n * @returns The bar and in-bar quarter-note offset.\n * @example\n * ```ts\n * import { parseTimeSignature, beatToBarPosition } from '@libraz/libcantus';\n * const ts = parseTimeSignature('4/4');\n * beatToBarPosition(5, ts); // { bar: 1, beat: 1 }\n * ```\n * @category Rhythm & Meter\n */\nexport function beatToBarPosition(beatInQuarters: number, meter: MeterLike): BarPosition {\n assertFiniteNumber(beatInQuarters, 'beat');\n return barPositionChecked(beatInQuarters, toMeterData(meter));\n}\n\n/** {@link beatToBarPosition} without re-reading an already resolved meter. */\nfunction barPositionChecked(beatInQuarters: number, meter: MeterData): BarPosition {\n return {\n bar: barIndexChecked(beatInQuarters, meter),\n beat: beatInQuarters - barStartChecked(beatInQuarters, meter),\n };\n}\n\n/**\n * Length of one felt beat (main pulse) in quarter-note beats.\n *\n * A quarter note in simple meters, a dotted quarter in compound ones, and one\n * denominator unit in an additive metre. Position displays, click tracks, and\n * score export are all written against this length.\n *\n * @param ts The time signature.\n * @returns The pulse length in quarter-note beats.\n * @example\n * ```ts\n * import { parseTimeSignature, pulseBeats } from '@libraz/libcantus';\n * pulseBeats(parseTimeSignature('6/8')); // 1.5\n * ```\n * @category Rhythm & Meter\n */\nexport function pulseBeats(ts: TimeSignature): number {\n assertTimeSignature(ts);\n return pulseBeatsOf(ts);\n}\n\n/**\n * The felt-beat number of a bar position, 1-based.\n *\n * {@link BarPosition.beat} is a quarter-note offset, so in 6/8 the second felt\n * beat reads as 1.5. This is the number a musician says and a position display\n * shows.\n *\n * @param pos The bar position.\n * @param meter A single signature, or the piece's meter map.\n * @returns The 1-based felt-beat number, fractional between pulses.\n * @example\n * ```ts\n * import { barPositionToPulse, parseTimeSignature } from '@libraz/libcantus';\n * barPositionToPulse({ bar: 0, beat: 1.5 }, parseTimeSignature('6/8')); // 2\n * ```\n * @category Rhythm & Meter\n */\nexport function barPositionToPulse(pos: BarPosition, meter: MeterLike): number {\n assertFiniteNumber(pos.beat, 'position.beat');\n return pulseChecked(pos, toMeterData(meter));\n}\n\n/** {@link barPositionToPulse} without re-reading an already resolved meter. */\nfunction pulseChecked(pos: BarPosition, meter: MeterData): number {\n return pos.beat / pulseBeatsOf(meterAtBarChecked(pos.bar, meter)) + 1;\n}\n\n/** The signature governing a bar index, on an already resolved meter. */\nfunction meterAtBarChecked(bar: number, meter: MeterData): TimeSignature {\n if (!isMeterMap(meter)) {\n return meter;\n }\n assertFiniteNumber(bar, 'position.bar');\n return meterAtChecked(beatOfBarIndex(meter, bar), meter);\n}\n\n/**\n * Render an absolute position as the `bar.beat` a DAW or a score shows:\n * 1-based bar, 1-based felt beat.\n *\n * The felt beats counted are the ones the bar actually has. A meter change\n * starts a new bar, so the bar it interrupts is short, and a position that\n * rounds past the end of a short bar reads as the downbeat of the next one\n * rather than as a beat the bar never reached.\n *\n * @param beatInQuarters Absolute position in quarter-note beats.\n * @param meter A single signature, or the piece's meter map.\n * @param decimals Digits of the fractional beat to keep.\n * @returns The formatted position, e.g. `'3.2'` for bar 3, felt beat 2.\n * @example\n * ```ts\n * import { formatBarPosition, parseTimeSignature } from '@libraz/libcantus';\n * formatBarPosition(7.5, parseTimeSignature('6/8')); // '3.2'\n * ```\n * @category Rhythm & Meter\n */\nexport function formatBarPosition(beatInQuarters: number, meter: MeterLike, decimals = 2): string {\n assertInteger(decimals, 'decimals', 0, 100);\n assertFiniteNumber(beatInQuarters, 'beat');\n const resolved = toMeterData(meter);\n const position = barPositionChecked(beatInQuarters, resolved);\n const ts = meterAtChecked(beatInQuarters, resolved);\n const pulse = pulseChecked(position, ts);\n const rounded = Number(pulse.toFixed(decimals));\n const barLength = isMeterMap(resolved)\n ? barLengthOf(resolved, beatInQuarters)\n : barBeatsOf(resolved);\n // A short bar has a whole final pulse only in the sense that the pulse it\n // started is cut off, so the count rounds up: 2 beats of 4/4 hold felt beats\n // 1 and 2, and anything past those belongs to the next bar.\n const pulsesInBar = Math.ceil(barLength / pulseBeatsOf(ts) - EPS);\n if (rounded >= pulsesInBar + 1) {\n return `${barIndexChecked(barStartChecked(beatInQuarters, resolved) + barLength, resolved) + 1}.1`;\n }\n const wholePulse = Math.floor(rounded);\n const fraction = Number((rounded - wholePulse).toFixed(decimals));\n return fraction === 0\n ? `${position.bar + 1}.${wholePulse}`\n : `${position.bar + 1}.${wholePulse}+${fraction}`;\n}\n\n/**\n * Convert a bar index and in-bar offset back to an absolute quarter-note\n * position.\n *\n * @param pos The bar position.\n * @param meter A single signature, or the piece's meter map.\n * @returns The absolute position in quarter-note beats.\n * @category Rhythm & Meter\n */\nexport function barPositionToBeat(pos: BarPosition, meter: MeterLike): number {\n assertInteger(pos.bar, 'bar position bar');\n assertFiniteNumber(pos.beat, 'bar position beat');\n const resolved = toMeterData(meter);\n if (isMeterMap(resolved)) {\n return beatOfBarIndex(resolved, pos.bar) + pos.beat;\n }\n return pos.bar * barBeatsOf(resolved) + pos.beat;\n}\n\n/**\n * Metric weight of a position within its bar, on a 0–3 scale:\n * 3 the downbeat, 2 a secondary strong pulse, 1 any other main pulse, and 0 an\n * off-pulse subdivision.\n *\n * The secondary strong pulse is the bar's midpoint in even simple/compound\n * meters. For an additive/irregular meter with {@link TimeSignature.grouping}\n * set (e.g. `[2, 2, 3]` for 7/8), each group's head pulse other than the\n * downbeat is the secondary strong pulse instead; without a grouping every\n * non-downbeat main pulse of such a meter weighs 1 (flat, evenly divided\n * pulses). A grouping of equal-length groups (9/8 as `[1, 1, 1]`, 4/4 as\n * `[1, 1, 1, 1]`) states the division the meter already has, so it weighs as\n * an ungrouped bar rather than accenting every group head.\n *\n * Given a meter map, the weight follows the signature in force at that beat, so\n * beat 5 of a piece is a mid-bar accent in 4/4 and a plain main pulse in 3/4.\n * The downbeat is beat 0 however much pickup precedes it, so a note in the\n * pickup weighs as the upbeat it is rather than as a downbeat.\n *\n * @param beatInQuarters Absolute quarter-note position, or an in-bar offset\n * when the meter is a single signature.\n * @param meter A single signature, or the piece's meter map.\n * @returns The metric weight (0–3).\n * @throws If the meter is malformed, including a grouping that is not a\n * positive-integer list summing to the pulse count or the numerator.\n * @example\n * ```ts\n * import { parseTimeSignature, metricWeight } from '@libraz/libcantus';\n * const ts = parseTimeSignature('4/4');\n * metricWeight(0, ts); // 3 (the downbeat)\n * ```\n * @category Rhythm & Meter\n */\nexport function metricWeight(beatInQuarters: number, meter: MeterLike): number {\n assertFiniteNumber(beatInQuarters, 'beat');\n const resolved = toMeterData(meter);\n const ts = meterAtChecked(beatInQuarters, resolved);\n const beat = beatInQuarters - barStartChecked(beatInQuarters, resolved);\n const pulse = pulseBeatsOf(ts);\n if (!isMultiple(beat, pulse)) {\n return 0;\n }\n const pulses = pulseCountOf(ts);\n const pulseIndex = Math.round(beat / pulse) % pulses;\n if (pulseIndex === 0) {\n return 3;\n }\n const grouping = pulseGroupingOf(ts);\n if (grouping !== undefined && !isUniformGrouping(grouping)) {\n return isGroupHead(grouping, pulseIndex) ? 2 : 1;\n }\n if (pulses % 2 === 0 && pulseIndex === pulses / 2) {\n return 2;\n }\n return 1;\n}\n\n/**\n * Whether a position is metrically accented (weight 2 or more — a downbeat or a\n * secondary strong pulse).\n *\n * @param beatInQuarters Absolute quarter-note position, or an in-bar offset\n * when the meter is a single signature.\n * @param meter A single signature, or the piece's meter map.\n * @returns True on strong beats.\n * @category Rhythm & Meter\n */\nexport function isStrongBeat(beatInQuarters: number, meter: MeterLike): boolean {\n return metricWeight(beatInQuarters, meter) >= 2;\n}\n\n/**\n * Subdivide a span into `count` equal tuplet durations (e.g. an eighth-note\n * triplet is `tuplet(1, 3)`).\n *\n * @param totalBeats Total span in quarter-note beats.\n * @param count Number of equal parts.\n * @returns `count` equal durations summing to `totalBeats`.\n * @throws If `count` is not a positive integer.\n * @category Rhythm & Meter\n */\nexport function tuplet(totalBeats: number, count: number): number[] {\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'tuplet total beats');\n assertPositiveInt(count, 'tuplet count');\n return new Array<number>(count).fill(totalBeats / count);\n}\n"],"mappings":"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","names":["EPS"]}