@sudobility/music_types 0.11.30 → 0.11.32

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 (251) hide show
  1. package/dist/domain/commands/edit-commands.d.ts +3 -3
  2. package/dist/domain/commands/edit-commands.d.ts.map +1 -1
  3. package/dist/domain/commands/edit-commands.js +25 -25
  4. package/dist/domain/commands/edit-commands.js.map +1 -1
  5. package/dist/domain/commands/history.d.ts +32 -0
  6. package/dist/domain/commands/history.d.ts.map +1 -0
  7. package/dist/domain/commands/history.js +63 -0
  8. package/dist/domain/commands/history.js.map +1 -0
  9. package/dist/domain/commands/note-commands.d.ts +9 -12
  10. package/dist/domain/commands/note-commands.d.ts.map +1 -1
  11. package/dist/domain/commands/note-commands.js +42 -72
  12. package/dist/domain/commands/note-commands.js.map +1 -1
  13. package/dist/domain/commands/note-marks.d.ts +171 -0
  14. package/dist/domain/commands/note-marks.d.ts.map +1 -0
  15. package/dist/domain/commands/note-marks.js +427 -0
  16. package/dist/domain/commands/note-marks.js.map +1 -0
  17. package/dist/domain/commands/reflow.d.ts +1 -1
  18. package/dist/domain/commands/reflow.js +17 -17
  19. package/dist/domain/commands/reflow.js.map +1 -1
  20. package/dist/domain/commands/region-commands.d.ts +4 -4
  21. package/dist/domain/commands/region-commands.d.ts.map +1 -1
  22. package/dist/domain/commands/region-commands.js +7 -7
  23. package/dist/domain/commands/region-commands.js.map +1 -1
  24. package/dist/domain/commands/relocate-commands.d.ts +3 -3
  25. package/dist/domain/commands/relocate-commands.js +15 -15
  26. package/dist/domain/commands/relocate-commands.js.map +1 -1
  27. package/dist/domain/commands/ripple-commands.d.ts +2 -2
  28. package/dist/domain/commands/ripple-commands.js +13 -13
  29. package/dist/domain/commands/ripple-commands.js.map +1 -1
  30. package/dist/domain/commands/snapshot.d.ts +3 -3
  31. package/dist/domain/commands/snapshot.js +5 -5
  32. package/dist/domain/commands/snapshot.js.map +1 -1
  33. package/dist/domain/commands/structure-commands.d.ts +86 -5
  34. package/dist/domain/commands/structure-commands.d.ts.map +1 -1
  35. package/dist/domain/commands/structure-commands.js +272 -36
  36. package/dist/domain/commands/structure-commands.js.map +1 -1
  37. package/dist/domain/commands/track-commands.d.ts +2 -2
  38. package/dist/domain/commands/track-commands.js +7 -7
  39. package/dist/domain/commands/track-commands.js.map +1 -1
  40. package/dist/domain/commands/types.d.ts +2 -2
  41. package/dist/domain/generation/replacement-region.d.ts +32 -0
  42. package/dist/domain/generation/replacement-region.d.ts.map +1 -0
  43. package/dist/domain/generation/replacement-region.js +79 -0
  44. package/dist/domain/generation/replacement-region.js.map +1 -0
  45. package/dist/domain/instruments/gm-icon.d.ts +12 -0
  46. package/dist/domain/instruments/gm-icon.d.ts.map +1 -0
  47. package/dist/domain/instruments/gm-icon.js +327 -0
  48. package/dist/domain/instruments/gm-icon.js.map +1 -0
  49. package/dist/domain/instruments/gm-kit.js +9 -9
  50. package/dist/domain/instruments/gm-kit.js.map +1 -1
  51. package/dist/domain/instruments/gm-percussion.js +48 -48
  52. package/dist/domain/instruments/gm-percussion.js.map +1 -1
  53. package/dist/domain/instruments/gm-polyphony.js +6 -6
  54. package/dist/domain/instruments/gm-range.js +6 -6
  55. package/dist/domain/instruments/gm-transposition.js +1 -1
  56. package/dist/domain/instruments/gm.d.ts +1 -1
  57. package/dist/domain/instruments/gm.js +161 -161
  58. package/dist/domain/instruments/gm.js.map +1 -1
  59. package/dist/domain/instruments/icon-art.d.ts +75 -0
  60. package/dist/domain/instruments/icon-art.d.ts.map +1 -0
  61. package/dist/domain/instruments/icon-art.js +93 -0
  62. package/dist/domain/instruments/icon-art.js.map +1 -0
  63. package/dist/domain/instruments/instrument-fit.d.ts +31 -0
  64. package/dist/domain/instruments/instrument-fit.d.ts.map +1 -0
  65. package/dist/domain/instruments/instrument-fit.js +117 -0
  66. package/dist/domain/instruments/instrument-fit.js.map +1 -0
  67. package/dist/domain/instruments/instrument-options.d.ts +62 -0
  68. package/dist/domain/instruments/instrument-options.d.ts.map +1 -0
  69. package/dist/domain/instruments/instrument-options.js +69 -0
  70. package/dist/domain/instruments/instrument-options.js.map +1 -0
  71. package/dist/domain/instruments/track-instrument.d.ts +80 -0
  72. package/dist/domain/instruments/track-instrument.d.ts.map +1 -0
  73. package/dist/domain/instruments/track-instrument.js +102 -0
  74. package/dist/domain/instruments/track-instrument.js.map +1 -0
  75. package/dist/domain/notation/chord-symbol.d.ts +26 -0
  76. package/dist/domain/notation/chord-symbol.d.ts.map +1 -0
  77. package/dist/domain/notation/chord-symbol.js +32 -0
  78. package/dist/domain/notation/chord-symbol.js.map +1 -0
  79. package/dist/domain/notation/lyric-syllables.d.ts +15 -0
  80. package/dist/domain/notation/lyric-syllables.d.ts.map +1 -0
  81. package/dist/domain/notation/lyric-syllables.js +15 -0
  82. package/dist/domain/notation/lyric-syllables.js.map +1 -0
  83. package/dist/domain/notation/music-vocabulary.d.ts +59 -0
  84. package/dist/domain/notation/music-vocabulary.d.ts.map +1 -0
  85. package/dist/domain/notation/music-vocabulary.js +164 -0
  86. package/dist/domain/notation/music-vocabulary.js.map +1 -0
  87. package/dist/domain/pitch/pitch.d.ts +1 -1
  88. package/dist/domain/pitch/pitch.js +29 -29
  89. package/dist/domain/pitch/transpose.d.ts +1 -1
  90. package/dist/domain/pitch/transpose.js +2 -2
  91. package/dist/domain/quantization/quantize.d.ts +2 -2
  92. package/dist/domain/quantization/quantize.js +9 -9
  93. package/dist/domain/quantization/quantize.js.map +1 -1
  94. package/dist/domain/score/articulation.d.ts +56 -0
  95. package/dist/domain/score/articulation.d.ts.map +1 -0
  96. package/dist/domain/score/articulation.js +56 -0
  97. package/dist/domain/score/articulation.js.map +1 -0
  98. package/dist/domain/score/bar-numbers.d.ts +38 -0
  99. package/dist/domain/score/bar-numbers.d.ts.map +1 -0
  100. package/dist/domain/score/bar-numbers.js +49 -0
  101. package/dist/domain/score/bar-numbers.js.map +1 -0
  102. package/dist/domain/score/collapse-rests.d.ts +23 -0
  103. package/dist/domain/score/collapse-rests.d.ts.map +1 -0
  104. package/dist/domain/score/collapse-rests.js +78 -0
  105. package/dist/domain/score/collapse-rests.js.map +1 -0
  106. package/dist/domain/score/cue-notes.d.ts +24 -0
  107. package/dist/domain/score/cue-notes.d.ts.map +1 -0
  108. package/dist/domain/score/cue-notes.js +105 -0
  109. package/dist/domain/score/cue-notes.js.map +1 -0
  110. package/dist/domain/score/dynamics.d.ts +62 -0
  111. package/dist/domain/score/dynamics.d.ts.map +1 -0
  112. package/dist/domain/score/dynamics.js +142 -0
  113. package/dist/domain/score/dynamics.js.map +1 -0
  114. package/dist/domain/score/effective-clef.d.ts +42 -0
  115. package/dist/domain/score/effective-clef.d.ts.map +1 -0
  116. package/dist/domain/score/effective-clef.js +39 -0
  117. package/dist/domain/score/effective-clef.js.map +1 -0
  118. package/dist/domain/score/extract-part.d.ts +10 -0
  119. package/dist/domain/score/extract-part.d.ts.map +1 -0
  120. package/dist/domain/score/extract-part.js +44 -0
  121. package/dist/domain/score/extract-part.js.map +1 -0
  122. package/dist/domain/score/factory.d.ts +1 -1
  123. package/dist/domain/score/factory.js +12 -12
  124. package/dist/domain/score/factory.js.map +1 -1
  125. package/dist/domain/score/fermata-tempo.d.ts +40 -0
  126. package/dist/domain/score/fermata-tempo.d.ts.map +1 -0
  127. package/dist/domain/score/fermata-tempo.js +95 -0
  128. package/dist/domain/score/fermata-tempo.js.map +1 -0
  129. package/dist/domain/score/flatten.d.ts +32 -0
  130. package/dist/domain/score/flatten.d.ts.map +1 -0
  131. package/dist/domain/score/flatten.js +132 -0
  132. package/dist/domain/score/flatten.js.map +1 -0
  133. package/dist/domain/score/fragment.d.ts +4 -8
  134. package/dist/domain/score/fragment.d.ts.map +1 -1
  135. package/dist/domain/score/fragment.js +8 -8
  136. package/dist/domain/score/fragment.js.map +1 -1
  137. package/dist/domain/score/ids.d.ts +1 -1
  138. package/dist/domain/score/ids.js +6 -6
  139. package/dist/domain/score/ottava.d.ts +41 -0
  140. package/dist/domain/score/ottava.d.ts.map +1 -0
  141. package/dist/domain/score/ottava.js +98 -0
  142. package/dist/domain/score/ottava.js.map +1 -0
  143. package/dist/domain/score/performance-timeline.d.ts +42 -0
  144. package/dist/domain/score/performance-timeline.d.ts.map +1 -0
  145. package/dist/domain/score/performance-timeline.js +78 -0
  146. package/dist/domain/score/performance-timeline.js.map +1 -0
  147. package/dist/domain/score/queries.d.ts +2 -2
  148. package/dist/domain/score/queries.js +7 -7
  149. package/dist/domain/score/queries.js.map +1 -1
  150. package/dist/domain/score/rehearsal-marks.d.ts +30 -0
  151. package/dist/domain/score/rehearsal-marks.d.ts.map +1 -0
  152. package/dist/domain/score/rehearsal-marks.js +126 -0
  153. package/dist/domain/score/rehearsal-marks.js.map +1 -0
  154. package/dist/domain/score/repeat-order.d.ts +21 -0
  155. package/dist/domain/score/repeat-order.d.ts.map +1 -0
  156. package/dist/domain/score/repeat-order.js +123 -0
  157. package/dist/domain/score/repeat-order.js.map +1 -0
  158. package/dist/domain/score/snapshot-tree.d.ts +23 -0
  159. package/dist/domain/score/snapshot-tree.d.ts.map +1 -0
  160. package/dist/domain/score/snapshot-tree.js +45 -0
  161. package/dist/domain/score/snapshot-tree.js.map +1 -0
  162. package/dist/domain/score/ties.d.ts +1 -1
  163. package/dist/domain/score/ties.js +7 -7
  164. package/dist/domain/score/ties.js.map +1 -1
  165. package/dist/domain/score/written-pitch.d.ts +33 -0
  166. package/dist/domain/score/written-pitch.d.ts.map +1 -0
  167. package/dist/domain/score/written-pitch.js +110 -0
  168. package/dist/domain/score/written-pitch.js.map +1 -0
  169. package/dist/domain/selection/range-select.d.ts +13 -0
  170. package/dist/domain/selection/range-select.d.ts.map +1 -0
  171. package/dist/domain/selection/range-select.js +40 -0
  172. package/dist/domain/selection/range-select.js.map +1 -0
  173. package/dist/domain/selection/selection-editing.d.ts +30 -0
  174. package/dist/domain/selection/selection-editing.d.ts.map +1 -0
  175. package/dist/domain/selection/selection-editing.js +31 -0
  176. package/dist/domain/selection/selection-editing.js.map +1 -0
  177. package/dist/domain/selection/selection.d.ts +2 -2
  178. package/dist/domain/selection/selection.js +7 -7
  179. package/dist/domain/selection/selection.js.map +1 -1
  180. package/dist/domain/selection/types.d.ts +2 -2
  181. package/dist/domain/selection/types.d.ts.map +1 -1
  182. package/dist/domain/time/duration-modifiers.d.ts +40 -0
  183. package/dist/domain/time/duration-modifiers.d.ts.map +1 -0
  184. package/dist/domain/time/duration-modifiers.js +50 -0
  185. package/dist/domain/time/duration-modifiers.js.map +1 -0
  186. package/dist/domain/time/duration-selection.d.ts +23 -0
  187. package/dist/domain/time/duration-selection.d.ts.map +1 -0
  188. package/dist/domain/time/duration-selection.js +50 -0
  189. package/dist/domain/time/duration-selection.js.map +1 -0
  190. package/dist/domain/time/durations.js +6 -6
  191. package/dist/domain/time/durations.js.map +1 -1
  192. package/dist/domain/time/fraction.d.ts +1 -1
  193. package/dist/domain/time/fraction.js +1 -1
  194. package/dist/domain/time/tap-to-note.d.ts +21 -0
  195. package/dist/domain/time/tap-to-note.d.ts.map +1 -0
  196. package/dist/domain/time/tap-to-note.js +43 -0
  197. package/dist/domain/time/tap-to-note.js.map +1 -0
  198. package/dist/domain/time/tempo-map.d.ts +1 -1
  199. package/dist/domain/time/ticks.d.ts +1 -1
  200. package/dist/domain/time/ticks.d.ts.map +1 -1
  201. package/dist/domain/time/ticks.js +14 -14
  202. package/dist/domain/time/ticks.js.map +1 -1
  203. package/dist/domain/time/tuplets.d.ts +42 -0
  204. package/dist/domain/time/tuplets.d.ts.map +1 -0
  205. package/dist/domain/time/tuplets.js +53 -0
  206. package/dist/domain/time/tuplets.js.map +1 -0
  207. package/dist/domain/validation/issues.d.ts +1 -1
  208. package/dist/domain/validation/issues.js +20 -20
  209. package/dist/domain/validation/validator.d.ts +2 -2
  210. package/dist/domain/validation/validator.d.ts.map +1 -1
  211. package/dist/domain/validation/validator.js +31 -31
  212. package/dist/domain/validation/validator.js.map +1 -1
  213. package/dist/domain/voicing/allocate.d.ts +2 -2
  214. package/dist/domain/voicing/allocate.js +6 -6
  215. package/dist/domain/voicing/allocate.js.map +1 -1
  216. package/dist/index.d.ts +72 -5535
  217. package/dist/index.d.ts.map +1 -1
  218. package/dist/index.js +79 -462
  219. package/dist/index.js.map +1 -1
  220. package/dist/model/api.d.ts +1696 -0
  221. package/dist/model/api.d.ts.map +1 -0
  222. package/dist/model/api.js +125 -0
  223. package/dist/model/api.js.map +1 -0
  224. package/dist/model/generation.d.ts +2178 -0
  225. package/dist/model/generation.d.ts.map +1 -0
  226. package/dist/model/generation.js +142 -0
  227. package/dist/model/generation.js.map +1 -0
  228. package/dist/model/schemas.d.ts +1200 -0
  229. package/dist/model/schemas.d.ts.map +1 -0
  230. package/dist/model/schemas.js +200 -0
  231. package/dist/model/schemas.js.map +1 -0
  232. package/dist/model/score.d.ts +462 -0
  233. package/dist/model/score.d.ts.map +1 -0
  234. package/dist/model/score.js +34 -0
  235. package/dist/model/score.js.map +1 -0
  236. package/dist/platform/audio.d.ts.map +1 -1
  237. package/dist/platform/file.d.ts.map +1 -1
  238. package/dist/platform/index.d.ts +7 -7
  239. package/dist/platform/index.js +7 -7
  240. package/dist/platform/midi-input.d.ts +2 -2
  241. package/dist/platform/midi.d.ts.map +1 -1
  242. package/dist/platform/mod.d.ts +2 -2
  243. package/dist/platform/playback.d.ts +7 -7
  244. package/dist/platform/xml.js +1 -1
  245. package/dist/services/regeneration/controller.d.ts +6 -6
  246. package/dist/services/regeneration/controller.js +12 -12
  247. package/dist/services/regeneration/controller.js.map +1 -1
  248. package/dist/test/fixtures.d.ts +1 -1
  249. package/dist/test/fixtures.js +103 -103
  250. package/dist/test/fixtures.js.map +1 -1
  251. package/package.json +1 -1
@@ -0,0 +1,13 @@
1
+ import type { Score, UUID } from "../../index.js";
2
+ /**
3
+ * Every note whose `startTick` falls in `[min(fromTick,toTick),
4
+ * max(...))` on one of `trackIds`, in ascending tick order.
5
+ *
6
+ * Half-open on purpose: cmd-clicking exactly on a note selects everything up
7
+ * to it but not the note itself, so two adjacent ranges sharing a boundary
8
+ * don't both claim the note sitting on it. Rests are skipped — they aren't
9
+ * selectable material for any downstream action (transpose, regenerate,
10
+ * copy all operate on notes).
11
+ */
12
+ export declare function noteIdsInTickRange(score: Score, fromTick: number, toTick: number, trackIds: readonly UUID[]): UUID[];
13
+ //# sourceMappingURL=range-select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"range-select.d.ts","sourceRoot":"","sources":["../../../src/domain/selection/range-select.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,SAAS,IAAI,EAAE,GACxB,IAAI,EAAE,CAuBR"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Caret-to-click range selection (the cmd-click gesture). Pure over `Score`
3
+ * — no store, no DOM — so the tick math is unit-testable without rendering.
4
+ */
5
+ import { isNoteEvent } from "../../index.js";
6
+ /**
7
+ * Every note whose `startTick` falls in `[min(fromTick,toTick),
8
+ * max(...))` on one of `trackIds`, in ascending tick order.
9
+ *
10
+ * Half-open on purpose: cmd-clicking exactly on a note selects everything up
11
+ * to it but not the note itself, so two adjacent ranges sharing a boundary
12
+ * don't both claim the note sitting on it. Rests are skipped — they aren't
13
+ * selectable material for any downstream action (transpose, regenerate,
14
+ * copy all operate on notes).
15
+ */
16
+ export function noteIdsInTickRange(score, fromTick, toTick, trackIds) {
17
+ const start = Math.min(fromTick, toTick);
18
+ const end = Math.max(fromTick, toTick);
19
+ if (start === end || trackIds.length === 0)
20
+ return [];
21
+ const wanted = new Set(trackIds);
22
+ const hits = [];
23
+ for (const track of score.tracks) {
24
+ if (!wanted.has(track.id))
25
+ continue;
26
+ for (const measure of track.measures) {
27
+ for (const voice of measure.voices) {
28
+ for (const event of voice.events) {
29
+ if (!isNoteEvent(event))
30
+ continue;
31
+ if (event.startTick >= start && event.startTick < end) {
32
+ hits.push({ id: event.id, startTick: event.startTick });
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ return hits.sort((a, b) => a.startTick - b.startTick).map((h) => h.id);
39
+ }
40
+ //# sourceMappingURL=range-select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"range-select.js","sourceRoot":"","sources":["../../../src/domain/selection/range-select.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAY,EACZ,QAAgB,EAChB,MAAc,EACd,QAAyB;IAEzB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACvC,IAAI,KAAK,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IACjC,MAAM,IAAI,GAA2C,EAAE,CAAC;IAExD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAAE,SAAS;QACpC,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACrC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;gBACnC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;oBACjC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;wBAAE,SAAS;oBAClC,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;wBACtD,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;oBAC1D,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACzE,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * When the piano keyboard edits the selection instead of entering notes.
3
+ *
4
+ * Pure over a note list — no store, no DOM — so the rule that decides between
5
+ * the keyboard's two jobs is testable on its own, in the same shape as
6
+ * `tap-to-note.ts` and `pitch-drag.ts`.
7
+ */
8
+ import type { NoteEvent } from "../../index.js";
9
+ export type ChordSelection = {
10
+ startTick: number;
11
+ durationTicks: number;
12
+ trackId: string;
13
+ voiceId: string;
14
+ notes: NoteEvent[];
15
+ };
16
+ /**
17
+ * The selection as one editable chord, or `null` when it is not exactly one.
18
+ *
19
+ * A chord is notes sharing a start tick in one voice. A selection spanning
20
+ * several ticks contains more than one chord, and there is no defensible way
21
+ * to guess which one the player meant — so the keyboard stays in entry mode
22
+ * rather than editing something arbitrary. That the rule fits in a sentence is
23
+ * the point: it has to be explainable in the UI.
24
+ *
25
+ * Voice, not just track: two voices on one stave routinely sound together, and
26
+ * treating a melody note and the bass note underneath it as one chord would
27
+ * let a key press edit a line the player was not looking at.
28
+ */
29
+ export declare function chordSelection(notes: readonly NoteEvent[]): ChordSelection | null;
30
+ //# sourceMappingURL=selection-editing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-editing.d.ts","sourceRoot":"","sources":["../../../src/domain/selection/selection-editing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,cAAc,GAAG,IAAI,CAmBvB"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The selection as one editable chord, or `null` when it is not exactly one.
3
+ *
4
+ * A chord is notes sharing a start tick in one voice. A selection spanning
5
+ * several ticks contains more than one chord, and there is no defensible way
6
+ * to guess which one the player meant — so the keyboard stays in entry mode
7
+ * rather than editing something arbitrary. That the rule fits in a sentence is
8
+ * the point: it has to be explainable in the UI.
9
+ *
10
+ * Voice, not just track: two voices on one stave routinely sound together, and
11
+ * treating a melody note and the bass note underneath it as one chord would
12
+ * let a key press edit a line the player was not looking at.
13
+ */
14
+ export function chordSelection(notes) {
15
+ if (notes.length === 0)
16
+ return null;
17
+ const [first] = notes;
18
+ const sameChord = notes.every((note) => note.startTick === first.startTick &&
19
+ note.trackId === first.trackId &&
20
+ note.voiceId === first.voiceId);
21
+ if (!sameChord)
22
+ return null;
23
+ return {
24
+ startTick: first.startTick,
25
+ durationTicks: first.durationTicks,
26
+ trackId: first.trackId,
27
+ voiceId: first.voiceId,
28
+ notes: [...notes],
29
+ };
30
+ }
31
+ //# sourceMappingURL=selection-editing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-editing.js","sourceRoot":"","sources":["../../../src/domain/selection/selection-editing.ts"],"names":[],"mappings":"AAiBA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAC5B,KAA2B;IAE3B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;IACtB,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAC3B,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS;QAClC,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO;QAC9B,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,CACjC,CAAC;IACF,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,OAAO;QACL,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC;KAClB,CAAC;AACJ,CAAC"}
@@ -1,5 +1,5 @@
1
- import type { Score } from '../../index.js';
2
- import type { ScoreRange, ScoreSelection } from './types.js';
1
+ import type { Score } from "../../index.js";
2
+ import type { ScoreRange, ScoreSelection } from "./types.js";
3
3
  /**
4
4
  * The smallest tick range, aligned to full-measure boundaries on every
5
5
  * implicated track, that covers everything named by `sel` (selected
@@ -2,7 +2,7 @@
2
2
  * Selection helpers (spec §9). Pure, read-only functions over `Score` +
3
3
  * `ScoreSelection`/`ScoreRange` — no UI, store, or rendering concerns.
4
4
  */
5
- import { findEvent, findMeasure, findTrack, scoreEndTick, } from '../score/queries.js';
5
+ import { findEvent, findMeasure, findTrack, scoreEndTick, } from "../score/queries.js";
6
6
  /**
7
7
  * The smallest tick range, aligned to full-measure boundaries on every
8
8
  * implicated track, that covers everything named by `sel` (selected
@@ -36,7 +36,7 @@ export function selectionToRange(score, sel) {
36
36
  continue;
37
37
  minTick = Math.min(minTick, measure.startTick);
38
38
  maxTick = Math.max(maxTick, measure.startTick + measure.durationTicks);
39
- const owningTrack = score.tracks.find(t => t.measures.some(m => m.id === measureId));
39
+ const owningTrack = score.tracks.find((t) => t.measures.some((m) => m.id === measureId));
40
40
  if (owningTrack)
41
41
  trackIds.add(owningTrack.id);
42
42
  }
@@ -54,7 +54,7 @@ export function selectionToRange(score, sel) {
54
54
  const scopedTrackIds = [...trackIds];
55
55
  const tracksInScope = scopedTrackIds.length > 0
56
56
  ? scopedTrackIds
57
- .map(id => findTrack(score, id))
57
+ .map((id) => findTrack(score, id))
58
58
  .filter((t) => t !== null)
59
59
  : score.tracks;
60
60
  let alignedStart = minTick;
@@ -86,9 +86,9 @@ function dedupe(ids) {
86
86
  * swapped into ascending order if given reversed).
87
87
  */
88
88
  export function normalizeSelection(score, sel) {
89
- const eventIds = dedupe(sel.eventIds).filter(id => findEvent(score, id) !== null);
90
- const measureIds = dedupe(sel.measureIds).filter(id => findMeasure(score, id) !== null);
91
- const trackIds = dedupe(sel.trackIds).filter(id => findTrack(score, id) !== null);
89
+ const eventIds = dedupe(sel.eventIds).filter((id) => findEvent(score, id) !== null);
90
+ const measureIds = dedupe(sel.measureIds).filter((id) => findMeasure(score, id) !== null);
91
+ const trackIds = dedupe(sel.trackIds).filter((id) => findTrack(score, id) !== null);
92
92
  const normalized = { eventIds, measureIds, trackIds };
93
93
  if (sel.range) {
94
94
  const startTick = Math.max(0, Math.min(sel.range.startTick, sel.range.endTick));
@@ -114,7 +114,7 @@ export function selectionIsRegenerable(score, sel) {
114
114
  return false;
115
115
  if (range.endTick > scoreEndTick(score))
116
116
  return false;
117
- return range.trackIds.every(id => findTrack(score, id) !== null);
117
+ return range.trackIds.every((id) => findTrack(score, id) !== null);
118
118
  }
119
119
  export function selectionSummaryLabel(sel, copy, regenerated = false) {
120
120
  const mark = (summary) => regenerated ? copy.regenerated(summary) : summary;
@@ -1 +1 @@
1
- {"version":3,"file":"selection.js","sourceRoot":"","sources":["../../../src/domain/selection/selection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"selection.js","sourceRoot":"","sources":["../../../src/domain/selection/selection.ts"],"names":[],"mappings":"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"}
@@ -3,8 +3,8 @@
3
3
  * canonical in @sudobility/music_types and re-exported here so domain
4
4
  * modules keep one import site; `emptySelection` is the runtime helper.
5
5
  */
6
- export type { ScoreRange, ScoreSelection } from '../../index.js';
7
- import type { ScoreSelection } from '../../index.js';
6
+ export type { ScoreRange, ScoreSelection } from "../../model/score.js";
7
+ import type { ScoreSelection } from "../../model/score.js";
8
8
  /** An empty selection: no events, measures, tracks, or range selected. */
9
9
  export declare function emptySelection(): ScoreSelection;
10
10
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/domain/selection/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,0EAA0E;AAC1E,wBAAgB,cAAc,IAAI,cAAc,CAE/C"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/domain/selection/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,0EAA0E;AAC1E,wBAAgB,cAAc,IAAI,cAAc,CAE/C"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Composing and decomposing a `DurationName`.
3
+ *
4
+ * The model spells out all eighteen names — `quarter`, `dotted-quarter`,
5
+ * `triplet-quarter` and so on — but a toolbar with eighteen buttons is
6
+ * unreadable. The editor instead offers six base values and two modifiers,
7
+ * which is how notation editors have always done it: pick the note, then dot
8
+ * or tuplet it.
9
+ *
10
+ * Pure, so the composition rules are testable without a toolbar.
11
+ */
12
+ import type { DurationName } from "../../index.js";
13
+ /** The six plain note values, longest first — the toolbar's row. */
14
+ export declare const BASE_DURATIONS: readonly ["whole", "half", "quarter", "eighth", "sixteenth", "thirtysecond"];
15
+ export type BaseDuration = (typeof BASE_DURATIONS)[number];
16
+ /**
17
+ * A duration split into the parts a toolbar shows.
18
+ *
19
+ * `dotted` and `triplet` are mutually exclusive, because the model has no name
20
+ * for a dotted triplet — so the type cannot represent one either.
21
+ */
22
+ export type DurationParts = {
23
+ base: BaseDuration;
24
+ modifier: "none" | "dotted" | "triplet";
25
+ };
26
+ /** Splits a `DurationName` into its base value and modifier. */
27
+ export declare function durationParts(duration: DurationName): DurationParts;
28
+ /** The `DurationName` for a base value and modifier. */
29
+ export declare function composeDuration(parts: DurationParts): DurationName;
30
+ /**
31
+ * `duration` with `modifier` applied, or stripped if it was already active.
32
+ *
33
+ * Toggling one modifier clears the other rather than stacking: there is no
34
+ * dotted triplet in the model, and silently producing one of the two would be
35
+ * worse than plainly replacing it.
36
+ */
37
+ export declare function withModifier(duration: DurationName, modifier: "dotted" | "triplet"): DurationName;
38
+ /** `duration` rebased onto a different note value, keeping its modifier. */
39
+ export declare function withBase(duration: DurationName, base: BaseDuration): DurationName;
40
+ //# sourceMappingURL=duration-modifiers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration-modifiers.d.ts","sourceRoot":"","sources":["../../../src/domain/time/duration-modifiers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,oEAAoE;AACpE,eAAO,MAAM,cAAc,8EAOjB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;CACzC,CAAC;AAEF,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,YAAY,GAAG,aAAa,CAcnE;AAED,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CAGlE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAC7B,YAAY,CAMd;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,YAAY,GACjB,YAAY,CAEd"}
@@ -0,0 +1,50 @@
1
+ /** The six plain note values, longest first — the toolbar's row. */
2
+ export const BASE_DURATIONS = [
3
+ "whole",
4
+ "half",
5
+ "quarter",
6
+ "eighth",
7
+ "sixteenth",
8
+ "thirtysecond",
9
+ ];
10
+ /** Splits a `DurationName` into its base value and modifier. */
11
+ export function durationParts(duration) {
12
+ if (duration.startsWith("dotted-")) {
13
+ return {
14
+ base: duration.slice("dotted-".length),
15
+ modifier: "dotted",
16
+ };
17
+ }
18
+ if (duration.startsWith("triplet-")) {
19
+ return {
20
+ base: duration.slice("triplet-".length),
21
+ modifier: "triplet",
22
+ };
23
+ }
24
+ return { base: duration, modifier: "none" };
25
+ }
26
+ /** The `DurationName` for a base value and modifier. */
27
+ export function composeDuration(parts) {
28
+ if (parts.modifier === "none")
29
+ return parts.base;
30
+ return `${parts.modifier}-${parts.base}`;
31
+ }
32
+ /**
33
+ * `duration` with `modifier` applied, or stripped if it was already active.
34
+ *
35
+ * Toggling one modifier clears the other rather than stacking: there is no
36
+ * dotted triplet in the model, and silently producing one of the two would be
37
+ * worse than plainly replacing it.
38
+ */
39
+ export function withModifier(duration, modifier) {
40
+ const parts = durationParts(duration);
41
+ return composeDuration({
42
+ base: parts.base,
43
+ modifier: parts.modifier === modifier ? "none" : modifier,
44
+ });
45
+ }
46
+ /** `duration` rebased onto a different note value, keeping its modifier. */
47
+ export function withBase(duration, base) {
48
+ return composeDuration({ base, modifier: durationParts(duration).modifier });
49
+ }
50
+ //# sourceMappingURL=duration-modifiers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration-modifiers.js","sourceRoot":"","sources":["../../../src/domain/time/duration-modifiers.ts"],"names":[],"mappings":"AAaA,oEAAoE;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,OAAO;IACP,MAAM;IACN,SAAS;IACT,QAAQ;IACR,WAAW;IACX,cAAc;CACN,CAAC;AAeX,gEAAgE;AAChE,MAAM,UAAU,aAAa,CAAC,QAAsB;IAClD,IAAI,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAiB;YACtD,QAAQ,EAAE,QAAQ;SACnB,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAiB;YACvD,QAAQ,EAAE,SAAS;SACpB,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,QAAwB,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC9D,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,eAAe,CAAC,KAAoB;IAClD,IAAI,KAAK,CAAC,QAAQ,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC;IACjD,OAAO,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAkB,CAAC;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,QAAsB,EACtB,QAA8B;IAE9B,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACtC,OAAO,eAAe,CAAC;QACrB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;KAC1D,CAAC,CAAC;AACL,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,QAAQ,CACtB,QAAsB,EACtB,IAAkB;IAElB,OAAO,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { DurationName, NoteEvent } from "../../index.js";
2
+ import type { BaseDuration } from "../time/duration-modifiers.js";
3
+ export type DurationDisplay =
4
+ /** A single agreed duration: the selection's, or the armed one when nothing is selected. */
5
+ {
6
+ kind: "single";
7
+ duration: DurationName;
8
+ base: BaseDuration;
9
+ }
10
+ /** Selected notes disagree, or one of them is a length no name covers. */
11
+ | {
12
+ kind: "mixed";
13
+ };
14
+ /**
15
+ * The duration to show for `notes`, falling back to `armed` when none are
16
+ * selected.
17
+ *
18
+ * A note whose length matches no duration name (an imported or quantized note
19
+ * of an odd tick count) counts as `mixed` rather than being rounded: the
20
+ * control would otherwise offer to "keep" a value the note does not have.
21
+ */
22
+ export declare function durationDisplay(notes: readonly NoteEvent[], ppq: number, armed: DurationName): DurationDisplay;
23
+ //# sourceMappingURL=duration-selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration-selection.d.ts","sourceRoot":"","sources":["../../../src/domain/time/duration-selection.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,MAAM,MAAM,eAAe;AACzB,4FAA4F;AAC1F;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE;AAChE,0EAA0E;GACxE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,YAAY,GAClB,eAAe,CAgBjB"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * What the duration selector shows, given the selection.
3
+ *
4
+ * One control replaced six toggle buttons, and it has to answer a different
5
+ * question depending on what is selected:
6
+ *
7
+ * - **nothing selected** — the length the *next* note will be written at, which
8
+ * is the store's `snapGrid`. The control is an instruction about the future.
9
+ * - **one note selected** — that note's length. The control is a readout.
10
+ * - **several, all the same length** — that length; they agree, so there is an
11
+ * answer.
12
+ * - **several of different lengths** — no single answer, so `mixed`. Not the
13
+ * first note's length, and not the armed length: either would claim the
14
+ * selection is something it is not, and picking that same value from the menu
15
+ * would then look like a no-op while silently rewriting every other note.
16
+ *
17
+ * Picking a value always applies to every selected note *and* arms it for the
18
+ * next one, so the control never has to explain which of its two jobs it is
19
+ * doing. Kept pure and separate from the toolbar so the four cases can be
20
+ * tested without rendering one.
21
+ */
22
+ import { durationNameForTicks } from "../../index.js";
23
+ import { durationParts } from "../time/duration-modifiers.js";
24
+ /**
25
+ * The duration to show for `notes`, falling back to `armed` when none are
26
+ * selected.
27
+ *
28
+ * A note whose length matches no duration name (an imported or quantized note
29
+ * of an odd tick count) counts as `mixed` rather than being rounded: the
30
+ * control would otherwise offer to "keep" a value the note does not have.
31
+ */
32
+ export function durationDisplay(notes, ppq, armed) {
33
+ if (notes.length === 0) {
34
+ return { kind: "single", duration: armed, base: durationParts(armed).base };
35
+ }
36
+ let agreed = null;
37
+ for (const note of notes) {
38
+ const name = durationNameForTicks(note.durationTicks, ppq);
39
+ if (name === null)
40
+ return { kind: "mixed" };
41
+ if (agreed === null)
42
+ agreed = name;
43
+ else if (agreed !== name)
44
+ return { kind: "mixed" };
45
+ }
46
+ return agreed === null
47
+ ? { kind: "mixed" }
48
+ : { kind: "single", duration: agreed, base: durationParts(agreed).base };
49
+ }
50
+ //# sourceMappingURL=duration-selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration-selection.js","sourceRoot":"","sources":["../../../src/domain/time/duration-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAS9D;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,KAA2B,EAC3B,GAAW,EACX,KAAmB;IAEnB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9E,CAAC;IAED,IAAI,MAAM,GAAwB,IAAI,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC3D,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAC5C,IAAI,MAAM,KAAK,IAAI;YAAE,MAAM,GAAG,IAAI,CAAC;aAC9B,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACrD,CAAC;IAED,OAAO,MAAM,KAAK,IAAI;QACpB,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;QACnB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;AAC7E,CAAC"}
@@ -1,9 +1,9 @@
1
- import { DURATIONS, ticksFor } from './ticks.js';
2
- const NON_TRIPLET_DURATION_NAMES = Object.keys(DURATIONS).filter(name => !name.startsWith('triplet-'));
1
+ import { DURATIONS, ticksFor } from "./ticks.js";
2
+ const NON_TRIPLET_DURATION_NAMES = Object.keys(DURATIONS).filter((name) => !name.startsWith("triplet-"));
3
3
  /** Distinct, descending tick lengths for every renderable (non-triplet) duration at a given PPQ. */
4
4
  function decomposableDurationTicks(ppq) {
5
- const ticks = new Set(NON_TRIPLET_DURATION_NAMES.map(name => ticksFor(name, ppq)));
6
- return [...ticks].filter(t => t > 0).sort((a, b) => b - a);
5
+ const ticks = new Set(NON_TRIPLET_DURATION_NAMES.map((name) => ticksFor(name, ppq)));
6
+ return [...ticks].filter((t) => t > 0).sort((a, b) => b - a);
7
7
  }
8
8
  /**
9
9
  * Greedily decomposes a tick length into the largest renderable (non-triplet,
@@ -19,7 +19,7 @@ export function decomposeDuration(ticks, ppq) {
19
19
  const result = [];
20
20
  let remaining = ticks;
21
21
  while (remaining > 0) {
22
- const next = candidates.find(c => c <= remaining);
22
+ const next = candidates.find((c) => c <= remaining);
23
23
  if (next === undefined) {
24
24
  result.push(remaining);
25
25
  break;
@@ -39,7 +39,7 @@ export function decomposeDuration(ticks, ppq) {
39
39
  export function splitAtBoundaries(startTick, durationTicks, boundaries) {
40
40
  const endTick = startTick + durationTicks;
41
41
  const cutPoints = [...new Set(boundaries)]
42
- .filter(b => b > startTick && b < endTick)
42
+ .filter((b) => b > startTick && b < endTick)
43
43
  .sort((a, b) => a - b);
44
44
  const segments = [];
45
45
  let cursor = startTick;
@@ -1 +1 @@
1
- {"version":3,"file":"durations.js","sourceRoot":"","sources":["../../../src/domain/time/durations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,0BAA0B,GAC9B,MAAM,CAAC,IAAI,CAAC,SAAS,CACtB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;AAE/C,oGAAoG;AACpG,SAAS,yBAAyB,CAAC,GAAW;IAC5C,MAAM,KAAK,GAAG,IAAI,GAAG,CACnB,0BAA0B,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAC5D,CAAC;IACF,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,GAAW;IAC1D,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAE1B,MAAM,UAAU,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;IAClD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,OAAO,SAAS,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC;QAClD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACvB,MAAM;QACR,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,SAAS,IAAI,IAAI,CAAC;IACpB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAID;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAAiB,EACjB,aAAqB,EACrB,UAAoB;IAEpB,MAAM,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC;IAC1C,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;SACvC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,IAAI,CAAC,GAAG,OAAO,CAAC;SACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAEzB,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,IAAI,MAAM,GAAG,SAAS,CAAC;IACvB,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,CAAC;IACf,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,GAAG,MAAM,EAAE,CAAC,CAAC;IAEtE,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"durations.js","sourceRoot":"","sources":["../../../src/domain/time/durations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,0BAA0B,GAC9B,MAAM,CAAC,IAAI,CAAC,SAAS,CACtB,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;AAEjD,oGAAoG;AACpG,SAAS,yBAAyB,CAAC,GAAW;IAC5C,MAAM,KAAK,GAAG,IAAI,GAAG,CACnB,0BAA0B,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAC9D,CAAC;IACF,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,GAAW;IAC1D,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAE1B,MAAM,UAAU,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;IAClD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,OAAO,SAAS,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC;QACpD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACvB,MAAM;QACR,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,SAAS,IAAI,IAAI,CAAC;IACpB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAID;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAAiB,EACjB,aAAqB,EACrB,UAAoB;IAEpB,MAAM,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC;IAC1C,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;SACvC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,SAAS,IAAI,CAAC,GAAG,OAAO,CAAC;SAC3C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAEzB,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,IAAI,MAAM,GAAG,SAAS,CAAC;IACvB,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,CAAC;IACf,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,GAAG,MAAM,EAAE,CAAC,CAAC;IAEtE,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { Fraction } from '../../index.js';
1
+ import type { Fraction } from "../../index.js";
2
2
  /** Constructs a `Fraction` from a numerator and denominator, unnormalized. */
3
3
  export declare function fraction(numerator: number, denominator: number): Fraction;
4
4
  /**
@@ -17,7 +17,7 @@ export function fraction(numerator, denominator) {
17
17
  */
18
18
  export function normalizeFraction(f) {
19
19
  if (f.denominator === 0) {
20
- throw new Error('normalizeFraction: denominator cannot be zero');
20
+ throw new Error("normalizeFraction: denominator cannot be zero");
21
21
  }
22
22
  if (f.numerator === 0) {
23
23
  return { numerator: 0, denominator: 1 };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Turning a key tap into a written note.
3
+ *
4
+ * Pure — no DOM, no store, no audio — so the timing maths is unit-testable, in
5
+ * the same shape as the editor's other geometry modules.
6
+ */
7
+ import type { DurationName } from "../../index.js";
8
+ /**
9
+ * The written duration closest to a tap of `heldMs` at `bpm`.
10
+ *
11
+ * Matched on a *ratio*, not a difference: a 40ms overshoot is nothing against a
12
+ * whole note and everything against a thirty-second, and comparing absolute
13
+ * error would push almost every tap toward the longest value. Comparing
14
+ * `log(actual / candidate)` treats being 20% long the same at every duration,
15
+ * which is how a player experiences it.
16
+ *
17
+ * A tap shorter than the briefest candidate returns that candidate rather than
18
+ * nothing: someone stabbing at a key means the shortest note, not no note.
19
+ */
20
+ export declare function durationForTap(heldMs: number, bpm: number): DurationName;
21
+ //# sourceMappingURL=tap-to-note.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tap-to-note.d.ts","sourceRoot":"","sources":["../../../src/domain/time/tap-to-note.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAkBnD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAexE"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The durations a tap can land on, longest first, with their length as a
3
+ * fraction of a whole note.
4
+ *
5
+ * Stops at a thirty-second because that is what the duration toolbar offers:
6
+ * a tap cannot produce something the buttons cannot.
7
+ */
8
+ const CANDIDATES = [
9
+ { name: "whole", whole: 1 },
10
+ { name: "half", whole: 1 / 2 },
11
+ { name: "quarter", whole: 1 / 4 },
12
+ { name: "eighth", whole: 1 / 8 },
13
+ { name: "sixteenth", whole: 1 / 16 },
14
+ { name: "thirtysecond", whole: 1 / 32 },
15
+ ];
16
+ /**
17
+ * The written duration closest to a tap of `heldMs` at `bpm`.
18
+ *
19
+ * Matched on a *ratio*, not a difference: a 40ms overshoot is nothing against a
20
+ * whole note and everything against a thirty-second, and comparing absolute
21
+ * error would push almost every tap toward the longest value. Comparing
22
+ * `log(actual / candidate)` treats being 20% long the same at every duration,
23
+ * which is how a player experiences it.
24
+ *
25
+ * A tap shorter than the briefest candidate returns that candidate rather than
26
+ * nothing: someone stabbing at a key means the shortest note, not no note.
27
+ */
28
+ export function durationForTap(heldMs, bpm) {
29
+ const safeBpm = bpm > 0 ? bpm : 120;
30
+ const wholeNoteMs = (60000 / safeBpm) * 4;
31
+ const held = Math.max(1, heldMs) / wholeNoteMs;
32
+ let best = CANDIDATES[CANDIDATES.length - 1];
33
+ let bestError = Number.POSITIVE_INFINITY;
34
+ for (const candidate of CANDIDATES) {
35
+ const error = Math.abs(Math.log(held / candidate.whole));
36
+ if (error < bestError) {
37
+ bestError = error;
38
+ best = candidate;
39
+ }
40
+ }
41
+ return best.name;
42
+ }
43
+ //# sourceMappingURL=tap-to-note.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tap-to-note.js","sourceRoot":"","sources":["../../../src/domain/time/tap-to-note.ts"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,MAAM,UAAU,GAAyD;IACvE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE;IAC3B,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE;IAC9B,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE;IACjC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE;IAChC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;IACpC,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;CACxC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,GAAW;IACxD,MAAM,OAAO,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACpC,MAAM,WAAW,GAAG,CAAC,KAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,WAAW,CAAC;IAE/C,IAAI,IAAI,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC7C,IAAI,SAAS,GAAG,MAAM,CAAC,iBAAiB,CAAC;IACzC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACzD,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;YACtB,SAAS,GAAG,KAAK,CAAC;YAClB,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { TempoEvent } from '../../index.js';
1
+ import type { TempoEvent } from "../../index.js";
2
2
  /**
3
3
  * Maps between ticks and seconds given a (possibly empty) set of tempo
4
4
  * change events. Tempo is piecewise-constant (a bpm value applies from its
@@ -1,4 +1,4 @@
1
- import type { DurationName, Fraction, TimeSignature } from '../../index.js';
1
+ import type { DurationName, Fraction, TimeSignature } from "../../index.js";
2
2
  /**
3
3
  * Duration name -> fraction of a whole note. Values are consumed by
4
4
  * `fractionToTicks`, where a quarter note (1/4) equals `ppq` ticks.
@@ -1 +1 @@
1
- {"version":3,"file":"ticks.d.ts","sourceRoot":"","sources":["../../../src/domain/time/ticks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,QAAQ,EACR,aAAa,EACd,MAAM,gBAAgB,CAAC;AA0BxB;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,YAAY,EAAE,QAAQ,CAyBpD,CAAC;AAEF,gEAAgE;AAChE,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpE;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,YAAY,GAAG,IAAI,CAErB;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3E;AAUD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMxE;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAQvE"}
1
+ {"version":3,"file":"ticks.d.ts","sourceRoot":"","sources":["../../../src/domain/time/ticks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AA0B5E;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,YAAY,EAAE,QAAQ,CAyBpD,CAAC;AAEF,gEAAgE;AAChE,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpE;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,YAAY,GAAG,IAAI,CAErB;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3E;AAUD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMxE;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAQvE"}