@libraz/libcantus 0.9.3 → 0.9.5

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 (201) hide show
  1. package/README.md +29 -20
  2. package/README_ja.md +268 -0
  3. package/dist/analyze/index.cjs +39 -1947
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +161 -91
  6. package/dist/analyze/index.d.ts +161 -91
  7. package/dist/analyze/index.js +19 -8
  8. package/dist/chunk-2A2QMA74.cjs +261 -0
  9. package/dist/chunk-2A2QMA74.cjs.map +1 -0
  10. package/dist/chunk-2PTFGVD7.js +261 -0
  11. package/dist/chunk-2PTFGVD7.js.map +1 -0
  12. package/dist/chunk-357ZDPVU.cjs +162 -0
  13. package/dist/chunk-357ZDPVU.cjs.map +1 -0
  14. package/dist/chunk-3KX76RLG.js +1548 -0
  15. package/dist/chunk-3KX76RLG.js.map +1 -0
  16. package/dist/chunk-4VZ5MZAM.cjs +889 -0
  17. package/dist/chunk-4VZ5MZAM.cjs.map +1 -0
  18. package/dist/{chunk-C5XYKUVG.js → chunk-7EONYVI5.js} +471 -171
  19. package/dist/chunk-7EONYVI5.js.map +1 -0
  20. package/dist/{chunk-IT2O4XFE.js → chunk-7JEP3MFY.js} +42 -34
  21. package/dist/chunk-7JEP3MFY.js.map +1 -0
  22. package/dist/{chunk-4MHA432G.js → chunk-AID75RN3.js} +572 -250
  23. package/dist/chunk-AID75RN3.js.map +1 -0
  24. package/dist/chunk-BSLTLGM3.cjs +497 -0
  25. package/dist/chunk-BSLTLGM3.cjs.map +1 -0
  26. package/dist/chunk-CU6VG2YM.cjs +109 -0
  27. package/dist/chunk-CU6VG2YM.cjs.map +1 -0
  28. package/dist/{chunk-EH6EM37U.js → chunk-DZMHIGI4.js} +81 -59
  29. package/dist/chunk-DZMHIGI4.js.map +1 -0
  30. package/dist/{chunk-AKLYPWIN.js → chunk-EDOTEABK.js} +45 -19
  31. package/dist/chunk-EDOTEABK.js.map +1 -0
  32. package/dist/{chunk-2ZWMSJH6.js → chunk-HN2OEUN2.js} +7 -4
  33. package/dist/chunk-HN2OEUN2.js.map +1 -0
  34. package/dist/{chunk-MIWO425M.js → chunk-KBTTEX7Q.js} +128 -43
  35. package/dist/chunk-KBTTEX7Q.js.map +1 -0
  36. package/dist/chunk-KQN2ZYND.cjs +724 -0
  37. package/dist/chunk-KQN2ZYND.cjs.map +1 -0
  38. package/dist/chunk-MPTVJIVG.cjs +3176 -0
  39. package/dist/chunk-MPTVJIVG.cjs.map +1 -0
  40. package/dist/chunk-MTMZTMWM.cjs +53 -0
  41. package/dist/chunk-MTMZTMWM.cjs.map +1 -0
  42. package/dist/{chunk-CHMXELDS.js → chunk-NNB67XA2.js} +372 -197
  43. package/dist/chunk-NNB67XA2.js.map +1 -0
  44. package/dist/chunk-OL3IHHSJ.cjs +1204 -0
  45. package/dist/chunk-OL3IHHSJ.cjs.map +1 -0
  46. package/dist/chunk-PI3UXRWK.cjs +528 -0
  47. package/dist/chunk-PI3UXRWK.cjs.map +1 -0
  48. package/dist/chunk-R2VVFAIN.js +528 -0
  49. package/dist/chunk-R2VVFAIN.js.map +1 -0
  50. package/dist/chunk-T64C7ZD7.cjs +1 -0
  51. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  52. package/dist/chunk-WFDXOLUB.cjs +337 -0
  53. package/dist/chunk-WFDXOLUB.cjs.map +1 -0
  54. package/dist/chunk-WTAN4ACG.cjs +1548 -0
  55. package/dist/chunk-WTAN4ACG.cjs.map +1 -0
  56. package/dist/chunk-YFJXS3AU.cjs +33 -0
  57. package/dist/chunk-YFJXS3AU.cjs.map +1 -0
  58. package/dist/{chunk-GOAW4VYL.js → chunk-YUE6TJ4M.js} +315 -145
  59. package/dist/chunk-YUE6TJ4M.js.map +1 -0
  60. package/dist/{chunk-62QZKCJQ.js → chunk-ZGLJM4A4.js} +10 -6
  61. package/dist/chunk-ZGLJM4A4.js.map +1 -0
  62. package/dist/core/index.cjs +140 -647
  63. package/dist/core/index.cjs.map +1 -1
  64. package/dist/core/index.d.cts +313 -41
  65. package/dist/core/index.d.ts +313 -41
  66. package/dist/core/index.js +64 -16
  67. package/dist/{function-DVH4Eusr.d.ts → function-CxixTB02.d.cts} +122 -10
  68. package/dist/{function-DYt4iKsn.d.cts → function-DTCt675z.d.ts} +122 -10
  69. package/dist/generate/index.cjs +28 -4085
  70. package/dist/generate/index.cjs.map +1 -1
  71. package/dist/generate/index.d.cts +374 -66
  72. package/dist/generate/index.d.ts +374 -66
  73. package/dist/generate/index.js +23 -9
  74. package/dist/{index-Jc0Ryi79.d.ts → index-B2Ulxxpo.d.cts} +21 -5
  75. package/dist/{index-CpOsP1y9.d.cts → index-BkYJ0xMq.d.cts} +18 -17
  76. package/dist/{index-CxqBQtH1.d.cts → index-BuaB-ypt.d.ts} +21 -5
  77. package/dist/index-CAN7v-kw.d.ts +199 -0
  78. package/dist/{index-Bt4tOUD9.d.ts → index-COw1Ky9g.d.ts} +93 -8
  79. package/dist/{index-Dikst44v.d.cts → index-Cfx8xgH0.d.ts} +14 -11
  80. package/dist/{index-DxvARIP2.d.ts → index-DRtIhlJj.d.ts} +18 -17
  81. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  82. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  83. package/dist/index-alrRninU.d.cts +199 -0
  84. package/dist/{index-BNlGP86A.d.cts → index-oTQbYYIs.d.cts} +93 -8
  85. package/dist/index-tR9jTn26.d.cts +275 -0
  86. package/dist/index-tR9jTn26.d.ts +275 -0
  87. package/dist/{index-Bg5dYMmZ.d.ts → index-x2vcsduE.d.cts} +14 -11
  88. package/dist/index.cjs +429 -7383
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.cts +15 -15
  91. package/dist/index.d.ts +15 -15
  92. package/dist/index.js +121 -27
  93. package/dist/model/index.cjs +8 -2562
  94. package/dist/model/index.cjs.map +1 -1
  95. package/dist/model/index.d.cts +471 -81
  96. package/dist/model/index.d.ts +471 -81
  97. package/dist/model/index.js +6 -7
  98. package/dist/styled-Bl0ZK28h.d.ts +543 -0
  99. package/dist/styled-Crv6SA4S.d.cts +543 -0
  100. package/dist/theory/index.cjs +172 -1822
  101. package/dist/theory/index.cjs.map +1 -1
  102. package/dist/theory/index.d.cts +151 -233
  103. package/dist/theory/index.d.ts +151 -233
  104. package/dist/theory/index.js +30 -7
  105. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  106. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  107. package/package.json +34 -13
  108. package/dist/chunk-2ZWMSJH6.js.map +0 -1
  109. package/dist/chunk-4MHA432G.js.map +0 -1
  110. package/dist/chunk-62QZKCJQ.js.map +0 -1
  111. package/dist/chunk-AKLYPWIN.js.map +0 -1
  112. package/dist/chunk-C5XYKUVG.js.map +0 -1
  113. package/dist/chunk-CHMXELDS.js.map +0 -1
  114. package/dist/chunk-DYWFNC4A.js +0 -152
  115. package/dist/chunk-DYWFNC4A.js.map +0 -1
  116. package/dist/chunk-EH6EM37U.js.map +0 -1
  117. package/dist/chunk-FBQK3ZQN.js +0 -131
  118. package/dist/chunk-FBQK3ZQN.js.map +0 -1
  119. package/dist/chunk-GOAW4VYL.js.map +0 -1
  120. package/dist/chunk-I5R7ZBJS.js +0 -202
  121. package/dist/chunk-I5R7ZBJS.js.map +0 -1
  122. package/dist/chunk-IT2O4XFE.js.map +0 -1
  123. package/dist/chunk-MIWO425M.js.map +0 -1
  124. package/dist/chunk-SGU7SPLK.js +0 -924
  125. package/dist/chunk-SGU7SPLK.js.map +0 -1
  126. package/dist/index-CGMZNCx6.d.cts +0 -140
  127. package/dist/index-CGMZNCx6.d.ts +0 -140
  128. package/dist/index-CnZCo29u.d.cts +0 -108
  129. package/dist/index-JnF4v9bx.d.ts +0 -108
  130. package/dist/styled-DZ7GjShb.d.ts +0 -282
  131. package/dist/styled-ZTbdaVFM.d.cts +0 -282
  132. package/src/analyze/arrange/index.ts +0 -21
  133. package/src/analyze/arrange/internal.ts +0 -127
  134. package/src/analyze/arrange/tension.ts +0 -199
  135. package/src/analyze/arrange/tracks.ts +0 -312
  136. package/src/analyze/detect/index.ts +0 -272
  137. package/src/analyze/functional/borrowed.ts +0 -84
  138. package/src/analyze/functional/cadence.ts +0 -66
  139. package/src/analyze/functional/function.ts +0 -184
  140. package/src/analyze/functional/index.ts +0 -23
  141. package/src/analyze/functional/internal.ts +0 -29
  142. package/src/analyze/functional/roman.ts +0 -402
  143. package/src/analyze/index.ts +0 -49
  144. package/src/analyze/timeline/index.ts +0 -451
  145. package/src/analyze/voice/index.ts +0 -339
  146. package/src/core/event-index/index.ts +0 -104
  147. package/src/core/index.ts +0 -61
  148. package/src/core/interval/index.ts +0 -81
  149. package/src/core/meter/index.ts +0 -302
  150. package/src/core/pitch/index.ts +0 -311
  151. package/src/core/random/index.ts +0 -76
  152. package/src/core/tuning/index.ts +0 -186
  153. package/src/core/types.ts +0 -29
  154. package/src/core/validation/index.ts +0 -119
  155. package/src/generate/bass/index.ts +0 -373
  156. package/src/generate/countermelody/index.ts +0 -470
  157. package/src/generate/drums/beat.ts +0 -266
  158. package/src/generate/drums/euclid.ts +0 -65
  159. package/src/generate/drums/fills.ts +0 -356
  160. package/src/generate/drums/ghost.ts +0 -119
  161. package/src/generate/drums/hihat.ts +0 -285
  162. package/src/generate/drums/hit.ts +0 -31
  163. package/src/generate/drums/index.ts +0 -361
  164. package/src/generate/drums/internal.ts +0 -257
  165. package/src/generate/drums/kick.ts +0 -160
  166. package/src/generate/drums/percussion.ts +0 -121
  167. package/src/generate/drums/rng.ts +0 -7
  168. package/src/generate/drums/swing.ts +0 -71
  169. package/src/generate/groove/index.ts +0 -351
  170. package/src/generate/harmonize/index.ts +0 -451
  171. package/src/generate/index.ts +0 -58
  172. package/src/generate/motif/index.ts +0 -422
  173. package/src/generate/progression/index.ts +0 -362
  174. package/src/generate/reharmony/index.ts +0 -323
  175. package/src/generate/rhythm/index.ts +0 -175
  176. package/src/index.ts +0 -13
  177. package/src/model/chord.ts +0 -442
  178. package/src/model/index.ts +0 -15
  179. package/src/model/interval.ts +0 -82
  180. package/src/model/key.ts +0 -266
  181. package/src/model/note.ts +0 -176
  182. package/src/model/progression.ts +0 -161
  183. package/src/model/shared.ts +0 -12
  184. package/src/theory/chord/index.ts +0 -375
  185. package/src/theory/chordscale/index.ts +0 -374
  186. package/src/theory/counterpoint/index.ts +0 -230
  187. package/src/theory/harmony/index.ts +0 -77
  188. package/src/theory/index.ts +0 -112
  189. package/src/theory/safety/index.ts +0 -488
  190. package/src/theory/scale/degrees.ts +0 -123
  191. package/src/theory/scale/index.ts +0 -28
  192. package/src/theory/scale/key.ts +0 -56
  193. package/src/theory/scale/masks.ts +0 -153
  194. package/src/theory/spelling/index.ts +0 -249
  195. package/src/theory/symbol/index.ts +0 -268
  196. package/src/theory/voicing/index.ts +0 -5
  197. package/src/theory/voicing/internal.ts +0 -168
  198. package/src/theory/voicing/leading.ts +0 -112
  199. package/src/theory/voicing/satb.ts +0 -216
  200. package/src/theory/voicing/styled.ts +0 -199
  201. package/tsconfig.json +0 -23
@@ -1,160 +0,0 @@
1
- import { hasHit } from './euclid.js';
2
- import type { DrumStyle, SectionType } from './internal.js';
3
- import type { DrumRng } from './rng.js';
4
-
5
- /** Kick presence flags across the eight 8th-note slots of a bar. */
6
- export type KickPattern = {
7
- beat1: boolean;
8
- beat1and: boolean;
9
- beat2: boolean;
10
- beat2and: boolean;
11
- beat3: boolean;
12
- beat3and: boolean;
13
- beat4: boolean;
14
- beat4and: boolean;
15
- };
16
-
17
- const EMPTY: KickPattern = {
18
- beat1: false,
19
- beat1and: false,
20
- beat2: false,
21
- beat2and: false,
22
- beat3: false,
23
- beat3and: false,
24
- beat4: false,
25
- beat4and: false,
26
- };
27
-
28
- /**
29
- * Whether a pre-chorus bar sits in the two-bar lift into the chorus.
30
- *
31
- * @param section Current section.
32
- * @param bar Bar index within the section.
33
- * @param sectionBars Total bars in the section.
34
- * @param nextIsChorus Whether the following section is a chorus.
35
- */
36
- export function isInPreChorusLift(
37
- section: SectionType,
38
- bar: number,
39
- sectionBars: number,
40
- nextIsChorus: boolean,
41
- ): boolean {
42
- if (section !== 'b' || !nextIsChorus || sectionBars < 3) {
43
- return false;
44
- }
45
- return bar >= sectionBars - 2;
46
- }
47
-
48
- /** Convert a 16-step euclidean bitmask into an 8th-slot kick pattern. */
49
- export function euclideanToKickPattern(mask: number): KickPattern {
50
- const slot = (step: number) => hasHit(mask, step) || hasHit(mask, (step + 15) % 16);
51
- return {
52
- beat1: slot(0),
53
- beat1and: slot(2),
54
- beat2: slot(4),
55
- beat2and: slot(6),
56
- beat3: slot(8),
57
- beat3and: slot(10),
58
- beat4: slot(12),
59
- beat4and: slot(14),
60
- };
61
- }
62
-
63
- /**
64
- * Build a section- and style-aware kick pattern.
65
- *
66
- * @param section Current section.
67
- * @param style Internal drum style.
68
- * @param bar Bar index (drives variation for a few styles).
69
- * @param rng Deterministic PRNG for probabilistic accents.
70
- */
71
- export function getKickPattern(
72
- section: SectionType,
73
- style: DrumStyle,
74
- bar: number,
75
- rng: DrumRng,
76
- ): KickPattern {
77
- const p: KickPattern = { ...EMPTY };
78
-
79
- if (section === 'outro') {
80
- p.beat1 = true;
81
- p.beat3 = true;
82
- return p;
83
- }
84
-
85
- switch (style) {
86
- case 'sparse':
87
- p.beat1 = true;
88
- if (section === 'chorus' && bar % 2 === 1) {
89
- p.beat3 = true;
90
- }
91
- break;
92
-
93
- case 'fourOnFloor':
94
- p.beat1 = p.beat2 = p.beat3 = p.beat4 = true;
95
- if (section === 'chorus' && rng.prob(0.2)) {
96
- p.beat2and = true;
97
- }
98
- break;
99
-
100
- case 'upbeat':
101
- p.beat1 = true;
102
- p.beat3 = true;
103
- if (section === 'b' || section === 'chorus') {
104
- p.beat2and = rng.prob(0.7);
105
- }
106
- if (section === 'chorus') {
107
- p.beat4and = rng.prob(0.6);
108
- }
109
- break;
110
-
111
- case 'rock':
112
- p.beat1 = true;
113
- p.beat3 = true;
114
- if (section === 'chorus') {
115
- p.beat2and = rng.prob(0.65);
116
- p.beat4and = rng.prob(0.4);
117
- } else if (section === 'b') {
118
- p.beat2and = rng.prob(0.3);
119
- }
120
- break;
121
-
122
- case 'synth':
123
- p.beat1 = true;
124
- p.beat3 = true;
125
- if (section === 'b' || section === 'chorus') {
126
- p.beat2and = rng.prob(0.75);
127
- }
128
- if (section === 'chorus') {
129
- p.beat4and = rng.prob(0.65);
130
- }
131
- break;
132
-
133
- case 'trap':
134
- p.beat1 = true;
135
- p.beat2and = rng.prob(0.8);
136
- p.beat3 = rng.prob(0.3);
137
- p.beat4and = rng.prob(0.7);
138
- break;
139
-
140
- case 'latin':
141
- p.beat1 = true;
142
- p.beat2and = true;
143
- p.beat3 = true;
144
- p.beat4and = rng.prob(0.5);
145
- break;
146
-
147
- default:
148
- p.beat1 = true;
149
- p.beat3 = true;
150
- if (section === 'b') {
151
- p.beat2and = rng.prob(0.5);
152
- } else if (section === 'chorus') {
153
- p.beat2and = rng.prob(0.55);
154
- p.beat4and = rng.prob(0.35);
155
- }
156
- break;
157
- }
158
-
159
- return p;
160
- }
@@ -1,121 +0,0 @@
1
- import { HH_16TH_BPM_THRESHOLD } from './hihat.js';
2
- import type { HitList } from './hit.js';
3
- import type { DrumRole, SectionType } from './internal.js';
4
- import { EIGHTH, GM, PercMoodCategory, SIXTEENTH, sectionIndex } from './internal.js';
5
- import type { DrumRng } from './rng.js';
6
-
7
- /** Enabled auxiliary percussion voices for a section. */
8
- export type PercussionConfig = {
9
- tambourine: boolean;
10
- shaker: boolean;
11
- handclap: boolean;
12
- shaker16th: boolean;
13
- };
14
-
15
- type PercActivation = [tambourine: boolean, shaker: boolean, handclap: boolean];
16
-
17
- // Percussion activation per [mood category][section index]. Columns:
18
- // intro, a, b, chorus, bridge, interlude, outro, chant, mixbreak.
19
- const F: PercActivation = [false, false, false];
20
- const PERC_TABLE: PercActivation[][] = [
21
- /* calm */ [F, F, F, F, F, F, F, F, F],
22
- /* std */ [F, F, [false, true, false], [true, false, true], F, F, F, F, [true, false, true]],
23
- /* ener */ [
24
- F,
25
- [false, true, false],
26
- [false, true, false],
27
- [true, true, true],
28
- F,
29
- F,
30
- F,
31
- F,
32
- [true, true, true],
33
- ],
34
- /* idol */ [
35
- F,
36
- [false, true, false],
37
- [false, true, false],
38
- [true, true, true],
39
- F,
40
- F,
41
- F,
42
- F,
43
- [true, true, true],
44
- ],
45
- /* rock */ [F, F, F, [false, false, true], F, F, F, F, [false, false, true]],
46
- ];
47
-
48
- /** Resolve which auxiliary percussion voices play for a section. */
49
- export function getPercussionConfig(
50
- mood: PercMoodCategory,
51
- section: SectionType,
52
- ): PercussionConfig {
53
- const row = PERC_TABLE[mood] ?? PERC_TABLE[PercMoodCategory.Standard];
54
- const act = row?.[sectionIndex(section)] ?? F;
55
- return {
56
- tambourine: act[0],
57
- shaker: act[1],
58
- handclap: act[2],
59
- shaker16th: act[1],
60
- };
61
- }
62
-
63
- /** Emit one bar of auxiliary percussion (tambourine/shaker/handclap). */
64
- export function generateAuxPercussionForBar(
65
- track: HitList,
66
- barStart: number,
67
- config: PercussionConfig,
68
- role: DrumRole,
69
- densityMult: number,
70
- rng: DrumRng,
71
- bpm: number,
72
- ): void {
73
- if (role === 'minimal') {
74
- return;
75
- }
76
-
77
- if (config.tambourine) {
78
- for (let beat = 1; beat <= 3; beat += 2) {
79
- const raw = 70 * densityMult * rng.float(0.9, 1.1);
80
- track.add(GM.TAMBOURINE, barStart + beat, EIGHTH, Math.max(40, Math.min(90, raw)));
81
- }
82
- }
83
-
84
- if (config.shaker) {
85
- const use16th = config.shaker16th && bpm < HH_16TH_BPM_THRESHOLD;
86
- if (use16th) {
87
- const velCurve = [0.75, 0.45, 0.6, 0.45];
88
- for (let beat = 0; beat < 4; beat += 1) {
89
- for (let sub = 0; sub < 4; sub += 1) {
90
- const raw = 80 * (velCurve[sub] ?? 0.5) * densityMult * rng.float(0.9, 1.1);
91
- track.add(
92
- GM.SHAKER,
93
- barStart + beat + sub * SIXTEENTH,
94
- SIXTEENTH,
95
- Math.max(25, Math.min(85, raw)),
96
- );
97
- }
98
- }
99
- } else {
100
- const velCurve = [0.75, 0.55];
101
- for (let beat = 0; beat < 4; beat += 1) {
102
- for (let sub = 0; sub < 2; sub += 1) {
103
- const raw = 80 * (velCurve[sub] ?? 0.6) * densityMult * rng.float(0.9, 1.1);
104
- track.add(
105
- GM.SHAKER,
106
- barStart + beat + sub * EIGHTH,
107
- EIGHTH,
108
- Math.max(25, Math.min(85, raw)),
109
- );
110
- }
111
- }
112
- }
113
- }
114
-
115
- if (config.handclap) {
116
- for (let beat = 1; beat <= 3; beat += 2) {
117
- const raw = 85 * densityMult * rng.float(0.9, 1.1);
118
- track.add(GM.HANDCLAP, barStart + beat, EIGHTH, Math.max(50, Math.min(100, raw)));
119
- }
120
- }
121
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * The drum engine's PRNG, re-exported from the shared {@link ../random} module.
3
- * `DrumRng` is retained as the drum-facing name of the shared {@link Rng} type.
4
- */
5
-
6
- export type { Rng as DrumRng } from '../../core/random/index.js';
7
- export { createRng } from '../../core/random/index.js';
@@ -1,71 +0,0 @@
1
- import type { Feel } from './internal.js';
2
-
3
- const SIXTEENTH = 0.25;
4
- const EIGHTH = 0.5;
5
- const HALF_16TH = 0.125;
6
- /** Delay of an off-beat 16th at full swing (triplet minus straight), in beats. */
7
- const DELTA_16 = 1 / 3 - 1 / 4; // 0.08333...
8
- /** Delay of an off-beat 8th at full swing, in beats. */
9
- const DELTA_8 = (2 / 3 - 1 / 2) / 2 + (2 / 3 - 1 / 2) / 2; // 0.16667...
10
-
11
- /** Grid resolution for swing quantization. */
12
- export type SwingResolution = 'eighth' | 'sixteenth';
13
-
14
- /** Effective swing after applying the groove feel to a base swing amount. */
15
- export function effectiveSwing(feel: Feel, swingAmount: number): number {
16
- if (feel === 'straight') {
17
- return 0;
18
- }
19
- if (feel === 'shuffle') {
20
- return Math.min(1, swingAmount * 1.5);
21
- }
22
- return swingAmount;
23
- }
24
-
25
- /**
26
- * Push an off-beat position toward its triplet placement.
27
- *
28
- * On-beat positions are returned unchanged; off-beat 8ths and 16ths are delayed
29
- * proportionally to the swing amount.
30
- *
31
- * @param startBeat Absolute position in beats.
32
- * @param swing Swing amount in [0, 1] (already feel-adjusted).
33
- * @param resolution Whether to swing the 16th or only the 8th grid.
34
- * @returns The swung position in beats.
35
- */
36
- export function quantizeSwing(
37
- startBeat: number,
38
- swing: number,
39
- resolution: SwingResolution,
40
- ): number {
41
- const clamped = Math.max(0, Math.min(1, swing));
42
- if (clamped <= 0) {
43
- return startBeat;
44
- }
45
- const base = Math.floor(startBeat);
46
- const offset = startBeat - base;
47
-
48
- if (resolution === 'sixteenth') {
49
- if (offset >= HALF_16TH && offset < SIXTEENTH + HALF_16TH) {
50
- return base + SIXTEENTH + DELTA_16 * clamped;
51
- }
52
- if (offset >= SIXTEENTH + HALF_16TH && offset < EIGHTH + HALF_16TH) {
53
- return base + EIGHTH + DELTA_8 * clamped;
54
- }
55
- if (offset >= EIGHTH + HALF_16TH && offset < 3 * SIXTEENTH + HALF_16TH) {
56
- // The "a" 16th is the midpoint of the compressed second half of the beat
57
- // (between the swung "and" and the next downbeat), mirroring the "e" 16th
58
- // in the first half. Because DELTA_8 = 2 * DELTA_16, that midpoint reduces
59
- // to a single DELTA_16 delay off the straight position, so the "a" never
60
- // overshoots the downbeat and needs no clamp. At the default shuffle feel
61
- // (swing 0.75) this lands near 0.8125 rather than the doubly-swung 0.9375.
62
- return base + 3 * SIXTEENTH + DELTA_16 * clamped;
63
- }
64
- return startBeat;
65
- }
66
-
67
- if (offset >= SIXTEENTH && offset < 3 * SIXTEENTH) {
68
- return base + EIGHTH + DELTA_8 * clamped;
69
- }
70
- return startBeat;
71
- }