@hraness/dawg 0.6.0 → 0.7.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 (275) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/DAWG.md +655 -141
  3. package/README.md +27 -25
  4. package/core/autotune.ts +1119 -0
  5. package/core/chords.ts +759 -23
  6. package/core/clips.ts +499 -0
  7. package/core/diff.ts +182 -99
  8. package/core/expression.ts +158 -6
  9. package/core/fx.ts +354 -32
  10. package/core/granular.ts +91 -0
  11. package/core/instruments.ts +180 -0
  12. package/core/keys.ts +573 -4
  13. package/core/loop.ts +5 -0
  14. package/core/lyrics.ts +297 -0
  15. package/core/master.ts +3 -3
  16. package/core/resonators.ts +306 -4
  17. package/core/routing.ts +165 -0
  18. package/core/score.ts +1181 -16
  19. package/core/sdk/eval-child.ts +7 -2
  20. package/core/sdk/eval.ts +35 -6
  21. package/core/sdk/print.ts +354 -6
  22. package/core/sdk/sync-lyrics.ts +49 -0
  23. package/core/sdk/v1.ts +3160 -69
  24. package/core/sections.ts +298 -24
  25. package/core/sing.ts +815 -0
  26. package/core/strings.ts +237 -2
  27. package/core/style-provenance.ts +80 -0
  28. package/core/styles/africa-mena-southasia.ts +2893 -0
  29. package/core/styles/americas.ts +3810 -0
  30. package/core/styles/art.ts +4993 -0
  31. package/core/styles/base.ts +123 -0
  32. package/core/styles/cycles.ts +106 -0
  33. package/core/styles/electronic.ts +2723 -0
  34. package/core/styles/europe-asia-pacific.ts +2838 -0
  35. package/core/styles/excerpt.ts +29 -0
  36. package/core/styles/gamelan.ts +283 -0
  37. package/core/styles/generate.ts +2199 -0
  38. package/core/styles/index.ts +515 -0
  39. package/core/styles/parts.ts +106 -0
  40. package/core/styles/pop.ts +3189 -0
  41. package/core/styles/rock.ts +2993 -0
  42. package/core/styles/roots.ts +4175 -0
  43. package/core/styles/schema.ts +429 -0
  44. package/core/styles/taxonomy.ts +940 -0
  45. package/core/styles/validate.ts +528 -0
  46. package/core/tempo.ts +32 -2
  47. package/core/tuning.ts +19 -3
  48. package/core/vocoder.ts +524 -0
  49. package/core/winds.ts +652 -0
  50. package/guides/agent.md +29 -0
  51. package/guides/arrange.md +30 -0
  52. package/guides/audition.md +20 -14
  53. package/guides/automation.md +12 -6
  54. package/guides/chords.md +16 -14
  55. package/guides/effects.md +17 -15
  56. package/guides/faders.md +20 -16
  57. package/guides/files.md +13 -9
  58. package/guides/getting-started.md +15 -11
  59. package/guides/keys.md +19 -15
  60. package/guides/media.md +17 -12
  61. package/guides/mix.md +12 -6
  62. package/guides/music.md +23 -8
  63. package/guides/notes.md +15 -9
  64. package/guides/performance.md +15 -10
  65. package/guides/play.md +21 -13
  66. package/guides/project.md +25 -8
  67. package/guides/providers.md +20 -14
  68. package/guides/resample.md +32 -0
  69. package/guides/rhythm.md +17 -13
  70. package/guides/sessions.md +17 -7
  71. package/guides/show-me.md +31 -0
  72. package/guides/sound.md +25 -9
  73. package/guides/sounds.md +15 -12
  74. package/guides/styles.md +31 -0
  75. package/guides/tempo.md +15 -10
  76. package/guides/tracks.md +15 -10
  77. package/guides/tuning.md +32 -0
  78. package/guides/voice.md +31 -0
  79. package/guides/web-search.md +18 -8
  80. package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
  81. package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
  82. package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
  83. package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
  84. package/native/prebuilt/manifest.json +21 -0
  85. package/package.json +5 -2
  86. package/src/agent/agent.ts +131 -13
  87. package/src/agent/calibration-tools.ts +53 -0
  88. package/src/agent/chord-tools.ts +153 -1
  89. package/src/agent/clip-tools.ts +453 -0
  90. package/src/agent/command-agent.ts +369 -0
  91. package/src/agent/drum-tools.ts +2 -2
  92. package/src/agent/expression-tools.ts +91 -0
  93. package/src/agent/gateway.ts +246 -60
  94. package/src/agent/models.ts +53 -12
  95. package/src/agent/ops.ts +15 -1
  96. package/src/agent/pack-tools.ts +1 -1
  97. package/src/agent/planner.ts +13 -0
  98. package/src/agent/portable-schema.ts +80 -0
  99. package/src/agent/preview-tool.ts +5 -1
  100. package/src/agent/provider.ts +22 -8
  101. package/src/agent/resample-tool.ts +131 -0
  102. package/src/agent/rhythm-tools.ts +1 -1
  103. package/src/agent/section-tools.ts +1 -1
  104. package/src/agent/show-me.ts +497 -0
  105. package/src/agent/steer.ts +15 -0
  106. package/src/agent/style-tools.ts +217 -0
  107. package/src/agent/tool-error.ts +12 -0
  108. package/src/agent/tools.ts +307 -37
  109. package/src/agent/usage.ts +2 -2
  110. package/src/agent/voice-tools.ts +925 -0
  111. package/src/agent/xcb-agent.ts +16 -6
  112. package/src/argv.ts +38 -0
  113. package/src/audio/analysis.ts +253 -0
  114. package/src/audio/arrange.ts +58 -3
  115. package/src/audio/autotune-engine.ts +101 -0
  116. package/src/audio/autotune.ts +640 -0
  117. package/src/audio/clips.ts +240 -0
  118. package/src/audio/doctor.ts +86 -0
  119. package/src/audio/dsp/bandbank.ts +138 -0
  120. package/src/audio/dsp/envelope.ts +171 -0
  121. package/src/audio/dsp/follow.ts +120 -0
  122. package/src/audio/dsp/formant.ts +427 -0
  123. package/src/audio/dsp/glottal.ts +243 -0
  124. package/src/audio/dsp/interp.ts +7 -2
  125. package/src/audio/dsp/lpc.ts +50 -0
  126. package/src/audio/dsp/periodicity.ts +59 -0
  127. package/src/audio/dsp/pitch.ts +995 -0
  128. package/src/audio/dsp/psola.ts +199 -0
  129. package/src/audio/dsp/shift.ts +256 -0
  130. package/src/audio/effects/chain.ts +21 -4
  131. package/src/audio/effects/common.ts +57 -0
  132. package/src/audio/effects/convolution.ts +113 -5
  133. package/src/audio/effects/filter.ts +48 -69
  134. package/src/audio/effects/formant.ts +263 -0
  135. package/src/audio/effects/gaze.ts +354 -0
  136. package/src/audio/engine.ts +206 -33
  137. package/src/audio/fit.ts +80 -3
  138. package/src/audio/granular.ts +352 -21
  139. package/src/audio/instrument-check.ts +67 -47
  140. package/src/audio/instruments.ts +36 -3
  141. package/src/audio/keys/calibration.ts +56 -0
  142. package/src/audio/keys/electric.ts +427 -0
  143. package/src/audio/keys/engine.ts +156 -20
  144. package/src/audio/keys/organ.ts +1335 -0
  145. package/src/audio/keys/piano.ts +72 -8
  146. package/src/audio/keys/sympathetic.ts +127 -0
  147. package/src/audio/kits.ts +135 -6
  148. package/src/audio/live-worker.ts +3 -1
  149. package/src/audio/live.ts +247 -26
  150. package/src/audio/native.ts +615 -0
  151. package/src/audio/preview.ts +45 -3
  152. package/src/audio/render-worker.ts +2 -0
  153. package/src/audio/renderer.ts +2 -0
  154. package/src/audio/resample.ts +286 -0
  155. package/src/audio/resonators.ts +10 -2
  156. package/src/audio/sampler.ts +252 -24
  157. package/src/audio/samples.ts +46 -3
  158. package/src/audio/sing/analysis.ts +193 -0
  159. package/src/audio/sing/engine.ts +949 -0
  160. package/src/audio/strings/body.ts +29 -16
  161. package/src/audio/strings/bow.ts +699 -0
  162. package/src/audio/strings/engine.ts +274 -22
  163. package/src/audio/strings/measure.test-helpers.ts +2 -0
  164. package/src/audio/synth/oscillators.ts +31 -21
  165. package/src/audio/synth/voice.ts +34 -1
  166. package/src/audio/vocoder/bank.ts +314 -0
  167. package/src/audio/vocoder/carrier.ts +165 -0
  168. package/src/audio/vocoder/control.ts +68 -0
  169. package/src/audio/vocoder/detect.ts +50 -0
  170. package/src/audio/vocoder/index.ts +304 -0
  171. package/src/audio/vocoder/talkbox.ts +143 -0
  172. package/src/audio/wav.ts +608 -85
  173. package/src/audio/winds/engine.ts +306 -0
  174. package/src/audio/winds/filters.ts +153 -0
  175. package/src/audio/winds/pitch.ts +104 -0
  176. package/src/audio/winds/trim.ts +104 -0
  177. package/src/audio/winds/trims.ts +917 -0
  178. package/src/audio/winds/trims1.ts +297 -0
  179. package/src/audio/winds/voice.ts +492 -0
  180. package/src/auth/cli.ts +38 -36
  181. package/src/auth/credentials.ts +30 -1
  182. package/src/auth/login.ts +15 -9
  183. package/src/auth/tui.ts +19 -10
  184. package/src/commands/arrange.ts +44 -29
  185. package/src/commands/autotune.ts +421 -0
  186. package/src/commands/calibration.ts +74 -0
  187. package/src/commands/clips.ts +887 -0
  188. package/src/commands/drums.ts +3 -2
  189. package/src/commands/edit.ts +11 -4
  190. package/src/commands/expression.ts +104 -23
  191. package/src/commands/formant.ts +221 -0
  192. package/src/commands/fx.ts +105 -33
  193. package/src/commands/grammar.ts +558 -0
  194. package/src/commands/granular.ts +7 -2
  195. package/src/commands/help.ts +652 -339
  196. package/src/commands/history.ts +18 -0
  197. package/src/commands/keys.ts +293 -17
  198. package/src/commands/modal.ts +67 -4
  199. package/src/commands/music.ts +1 -1
  200. package/src/commands/nearest.ts +53 -0
  201. package/src/commands/pack.ts +9 -2
  202. package/src/commands/param-range.ts +56 -0
  203. package/src/commands/parses.ts +130 -0
  204. package/src/commands/progression.ts +170 -0
  205. package/src/commands/resample.ts +281 -0
  206. package/src/commands/rhythm.ts +3 -0
  207. package/src/commands/rig.ts +8 -25
  208. package/src/commands/sample.ts +49 -2
  209. package/src/commands/shift.ts +119 -0
  210. package/src/commands/sing.ts +478 -0
  211. package/src/commands/string.ts +28 -2
  212. package/src/commands/strum.ts +485 -0
  213. package/src/commands/style.ts +415 -0
  214. package/src/commands/time.ts +6 -3
  215. package/src/commands/tuning.ts +3 -3
  216. package/src/commands/vocal-pitch.ts +616 -0
  217. package/src/commands/vocal.ts +147 -0
  218. package/src/commands/vocoder.ts +627 -0
  219. package/src/commands/wind.ts +244 -0
  220. package/src/fs/durable.ts +50 -0
  221. package/src/lang/glossary.ts +493 -0
  222. package/src/launch-args.ts +163 -0
  223. package/src/main.ts +1521 -255
  224. package/src/media/cli.ts +20 -3
  225. package/src/media/import.ts +3 -1
  226. package/src/project/check.ts +26 -3
  227. package/src/project/clip-pins.ts +72 -0
  228. package/src/project/init.ts +23 -8
  229. package/src/project/sync.ts +418 -86
  230. package/src/render.ts +36 -1
  231. package/src/session/daemon.ts +3 -0
  232. package/src/session/meta.ts +14 -0
  233. package/src/session/origin.ts +154 -0
  234. package/src/session/port.ts +22 -4
  235. package/src/session/presence.ts +50 -7
  236. package/src/session/protocol.ts +5 -1
  237. package/src/session/rebase.ts +18 -5
  238. package/src/session/receipt.ts +258 -0
  239. package/src/session/store.ts +65 -43
  240. package/src/tui/arrange-menu.ts +65 -31
  241. package/src/tui/audition.ts +1 -1
  242. package/src/tui/euclid.ts +18 -13
  243. package/src/tui/fader.ts +228 -41
  244. package/src/tui/granular-menu.ts +2 -4
  245. package/src/tui/menu-clips.ts +297 -0
  246. package/src/tui/menu-time.ts +20 -13
  247. package/src/tui/menu-voice.ts +405 -0
  248. package/src/tui/menu.ts +1377 -193
  249. package/src/tui/modal-menu.ts +38 -11
  250. package/src/tui/performance-menu.ts +46 -1
  251. package/src/tui/play-chords.ts +108 -5
  252. package/src/tui/play-mode.ts +16 -1
  253. package/src/tui/play-session.ts +81 -16
  254. package/src/tui/sing-menu.ts +278 -0
  255. package/src/tui/style-menu.ts +104 -0
  256. package/src/tui/vocoder-menu.ts +244 -0
  257. package/src/tui/wind-menu.ts +144 -0
  258. package/src/version.ts +8 -0
  259. package/src/web/fetch.ts +115 -29
  260. package/tui/activity.ts +180 -9
  261. package/tui/app.ts +274 -40
  262. package/tui/clip-row.ts +132 -0
  263. package/tui/delight.ts +144 -0
  264. package/tui/drawer.ts +70 -22
  265. package/tui/frame-gate.ts +76 -0
  266. package/tui/grammar.ts +112 -63
  267. package/tui/guide.ts +42 -4
  268. package/tui/highway.ts +269 -25
  269. package/tui/hints.ts +192 -0
  270. package/tui/input.ts +60 -9
  271. package/tui/keys.ts +1 -1
  272. package/tui/play-strip.ts +68 -14
  273. package/tui/prompt.ts +1 -1
  274. package/tui/screen.ts +144 -14
  275. package/tui/theme.ts +27 -0
@@ -16,14 +16,21 @@
16
16
  * - Post: the family's body EQ per channel (the minimal per-track post
17
17
  * hook), then the track volume, before the existing effects chain.
18
18
  */
19
- import type { PerformedNote } from "../../../core/expression.ts";
20
- import { isPianoFamily, resolvedKeys } from "../../../core/keys.ts";
19
+ import { pedalStateAt, type PerformedNote } from "../../../core/expression.ts";
20
+ import {
21
+ isElectricFamily,
22
+ isPianoFamily,
23
+ resolvedKeys,
24
+ type ElectricFamily,
25
+ } from "../../../core/keys.ts";
21
26
  import type { Track } from "../../../core/score.ts";
22
27
  import { noteHz } from "../../../core/tuning.ts";
23
28
  import type { FxLane } from "../../../core/fx.ts";
24
29
  import { interpolateAutomation } from "../effects/common.ts";
25
30
  import type { EngineContext, InstrumentEngine } from "../instruments.ts";
26
31
  import { Biquad2, clamp, CONTROL, LN1000 } from "./dsp.ts";
32
+ import { ORGAN_ENGINES } from "./organ.ts";
33
+ import { keysTrim } from "./calibration.ts";
27
34
  import {
28
35
  damperT60,
29
36
  physicalKey,
@@ -34,6 +41,15 @@ import {
34
41
  trackSeedOf,
35
42
  type PianoParams,
36
43
  } from "./piano.ts";
44
+ import {
45
+ clavReleaseT60,
46
+ electricPost,
47
+ electricVoice,
48
+ tineReleaseT60,
49
+ type ElectricParams,
50
+ type KeysVoice,
51
+ } from "./electric.ts";
52
+ import { Sympathetic } from "./sympathetic.ts";
37
53
 
38
54
  export const KEYS_LIMITS = Object.freeze({
39
55
  /** Per-track polyphony cap (voice stealing beyond it). */
@@ -98,6 +114,39 @@ export function pianoParamsAt(
98
114
  };
99
115
  }
100
116
 
117
+ /** Electric voice parameters for a note: the resolved keys plus lanes. */
118
+ export function electricParamsAt(
119
+ track: Track,
120
+ values: Values,
121
+ tick: number,
122
+ kind: ElectricFamily,
123
+ ): ElectricParams {
124
+ const lane = (name: string) => {
125
+ const points = track.fxAutomation?.[`keys-${name}` as FxLane];
126
+ const base = num(values, name);
127
+ return points && points.length > 0
128
+ ? interpolateAutomation(points, tick, base)
129
+ : base;
130
+ };
131
+ const pickup = values.pickup;
132
+ return {
133
+ kind,
134
+ hardness: clamp(lane("hardness"), 0, 1),
135
+ touch: clamp(lane("touch"), 0, 1),
136
+ decay: clamp(lane("decay"), 0.1, 4),
137
+ release: clamp(lane("release"), 0.1, 4),
138
+ width: num(values, "width"),
139
+ bark: num(values, "bark"),
140
+ bell: num(values, "bell"),
141
+ tone: clamp(lane("tone"), 0, 12000),
142
+ pickup:
143
+ pickup === "neck" || pickup === "bridge" || pickup === "out"
144
+ ? pickup
145
+ : "both",
146
+ mute: num(values, "mute"),
147
+ };
148
+ }
149
+
101
150
  /** Body voicing name for a track (the stored `body`, else the family's). */
102
151
  export function bodyOf(values: Values): string {
103
152
  const body = values.body;
@@ -105,7 +154,9 @@ export function bodyOf(values: Values): string {
105
154
  }
106
155
 
107
156
  type Live = {
108
- voice: PianoVoice;
157
+ voice: KeysVoice;
158
+ /** Calibration 1 level trim (1 for legacy songs). */
159
+ trim: number;
109
160
  /** Sample index the voice started at. */
110
161
  start: number;
111
162
  /** Sample index of key-up. */
@@ -161,10 +212,14 @@ export function renderKeysTrack(
161
212
  const sr = context.sampleRate;
162
213
  const total = left.length;
163
214
  const values = resolvedKeys(track.instrument ?? "grand", track.keys);
215
+ const electric = isElectricFamily(track.instrument)
216
+ ? track.instrument
217
+ : undefined;
164
218
  const trackSeed = trackSeedOf(track.id);
165
219
  const seedTick = context.seedTick ?? 0;
166
220
  const window = Math.round(KEYS_LIMITS.tailSeconds * sr);
167
221
  const taper = Math.round(KEYS_LIMITS.taperSeconds * sr);
222
+ const calibrated = (context.score.calibration ?? 0) >= 1;
168
223
  const onsets: Onset[] = [];
169
224
  notes.forEach((note) => {
170
225
  const hz = noteHz(note.pitch, note.cents, context.tuning);
@@ -204,18 +259,37 @@ export function renderKeysTrack(
204
259
  victim.stolen = true;
205
260
  }
206
261
  const note = onset.note;
207
- const params = pianoParamsAt(track, values, note.startTick);
208
- const voice = new PianoVoice(
209
- {
210
- pitch: note.pitch,
211
- hz: onset.hz,
212
- velocity: clamp(note.velocity, 0, 1),
213
- trackSeed,
214
- noteSeed: `${track.id}:${note.id}:${note.startTick + seedTick}`,
215
- },
216
- params,
217
- sr,
218
- );
262
+ // Una corda (0.6.1) at the onset: down is the full shift, half half.
263
+ const softState = track.softPedal
264
+ ? pedalStateAt(track.softPedal, note.startTick)
265
+ : "up";
266
+ const soft = softState === "down" ? 1 : softState === "half" ? 0.5 : 0;
267
+ const noteOn = {
268
+ pitch: note.pitch,
269
+ hz: onset.hz,
270
+ velocity: clamp(note.velocity, 0, 1),
271
+ trackSeed,
272
+ noteSeed: `${track.id}:${note.id}:${note.startTick + seedTick}`,
273
+ ...(soft > 0 ? { soft } : {}),
274
+ ...(calibrated ? { calibration: 1 } : {}),
275
+ };
276
+ const trim = calibrated
277
+ ? keysTrim(
278
+ track.keys?.preset ?? track.instrument ?? "grand",
279
+ note.pitch,
280
+ )
281
+ : 1;
282
+ const voice: KeysVoice = electric
283
+ ? electricVoice(
284
+ noteOn,
285
+ electricParamsAt(track, values, note.startTick, electric),
286
+ sr,
287
+ )
288
+ : new PianoVoice(
289
+ noteOn,
290
+ pianoParamsAt(track, values, note.startTick),
291
+ sr,
292
+ );
219
293
  const performance = note.performance;
220
294
  // The `vib`/`vibmod` wow (lofi) unless the note brings its own vibrato.
221
295
  const vib = num(values, "vib");
@@ -229,6 +303,7 @@ export function renderKeysTrack(
229
303
  bend && wow ? (t: number) => bend(t) + wow(t) : (bend ?? wow);
230
304
  live.push({
231
305
  voice,
306
+ trim,
232
307
  start: onset.start,
233
308
  off: onset.off,
234
309
  end: onset.off + window,
@@ -268,7 +343,7 @@ export function renderKeysTrack(
268
343
  gain *= Math.max(0, x.fade) / x.fadeLength;
269
344
  x.fade -= 1;
270
345
  }
271
- const y = block[s]! * gain;
346
+ const y = block[s]! * gain * x.trim;
272
347
  left[index]! += y * gl;
273
348
  right[index]! += y * gr;
274
349
  }
@@ -277,6 +352,50 @@ export function renderKeysTrack(
277
352
  if (!alive) live.splice(k, 1);
278
353
  }
279
354
  }
355
+ if (electric) {
356
+ // The electric post: suitcase vibrato or reed tremolo, then volume.
357
+ const param = electric === "wurli" ? "trem" : "vibe";
358
+ const points = track.fxAutomation?.[`keys-${param}` as FxLane];
359
+ const base = num(values, param);
360
+ const tickOf = (sample: number) =>
361
+ context.warp
362
+ ? context.warp.tick(sample)
363
+ : sample / context.samplesPerTick;
364
+ const depthAt =
365
+ points && points.length > 0
366
+ ? (sample: number) =>
367
+ interpolateAutomation(points, tickOf(sample), base)
368
+ : () => base;
369
+ const rate = electric === "wurli" ? 5.6 : num(values, "vibehz") || 4;
370
+ const startSample = Math.round(seedTick * context.samplesPerTick);
371
+ electricPost(left, right, electric, rate, depthAt, sr, startSample);
372
+ applyVolume(left, right, track, context);
373
+ return;
374
+ }
375
+ // Sympathetic resonance (0.6.1): only with `sym` and sustain pedal events.
376
+ const sym = num(values, "sym");
377
+ const pedal = track.pedal;
378
+ if (sym > 0 && pedal?.some((event) => event.state !== "up")) {
379
+ const bank = new Sympathetic(
380
+ (pitch) => noteHz(pitch, undefined, context.tuning),
381
+ sym,
382
+ sr,
383
+ );
384
+ // Pedal changes as samples; the bank reads them in order.
385
+ const changes = pedal.map((event) => ({
386
+ at: context.warp
387
+ ? context.warp.sample(event.tick)
388
+ : event.tick * context.samplesPerTick,
389
+ down: event.state !== "up",
390
+ }));
391
+ let cursor = 0;
392
+ let down = false;
393
+ bank.process(left, right, (sample) => {
394
+ while (cursor < changes.length && changes[cursor]!.at <= sample)
395
+ down = changes[cursor++]!.down;
396
+ return down;
397
+ });
398
+ }
280
399
  // The per-track post hook: body EQ, then the track volume.
281
400
  const body = bodyOf(values);
282
401
  const eqL = pianoBody(body, sr);
@@ -329,6 +448,14 @@ function applyVolume(
329
448
  export function keysReleaseSeconds(track: Track, hz: number): number {
330
449
  if (!(hz > 0) || !Number.isFinite(hz)) return 0.01;
331
450
  const values = resolvedKeys(track.instrument ?? "grand", track.keys);
451
+ if (isElectricFamily(track.instrument)) {
452
+ const release = clamp(num(values, "release"), 0.1, 4);
453
+ const t60 =
454
+ track.instrument === "clav"
455
+ ? clavReleaseT60(release)
456
+ : tineReleaseT60(physicalKey(hz), release);
457
+ return Math.max(0.01, (3 / (2 * LN1000)) * t60);
458
+ }
332
459
  const p = pianoParamsAt(track, values, 0);
333
460
  const key = physicalKey(hz);
334
461
  if (undamped(key)) return KEYS_LIMITS.tailSeconds;
@@ -353,9 +480,18 @@ function engineFor(id: string): InstrumentEngine {
353
480
  };
354
481
  }
355
482
 
356
- /** One engine per piano family (the registry dispatches by instrument id). */
483
+ /**
484
+ * One engine per keys family (the registry dispatches by instrument id):
485
+ * the pianos, then the electric keys and the organs (0.6.1).
486
+ */
357
487
  export const KEYS_ENGINES: readonly InstrumentEngine[] = Object.freeze(
358
- ["grand", "upright", "felt", "honkytonk", "prepared"]
359
- .filter((id) => isPianoFamily(id))
360
- .map(engineFor),
488
+ [
489
+ ...["grand", "upright", "felt", "honkytonk", "prepared"].filter((id) =>
490
+ isPianoFamily(id),
491
+ ),
492
+ ...["epiano", "wurli", "clav"].filter((id) => isElectricFamily(id)),
493
+ ]
494
+ .map(engineFor)
495
+ // f061-organ: tonewheel, combo and pipe (src/audio/keys/organ.ts).
496
+ .concat(ORGAN_ENGINES),
361
497
  );