@hraness/dawg 0.6.1 → 0.8.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 (277) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/DAWG.md +454 -140
  3. package/README.md +30 -26
  4. package/core/autotune.ts +1119 -0
  5. package/core/chords.ts +271 -23
  6. package/core/clips.ts +499 -0
  7. package/core/diff.ts +184 -104
  8. package/core/expression.ts +15 -0
  9. package/core/fx.ts +99 -29
  10. package/core/ids.ts +52 -0
  11. package/core/instruments.ts +19 -0
  12. package/core/keys.ts +3 -3
  13. package/core/loop.ts +8 -0
  14. package/core/lyrics.ts +297 -0
  15. package/core/master.ts +3 -3
  16. package/core/range.ts +581 -0
  17. package/core/resonators.ts +16 -2
  18. package/core/routing.ts +165 -0
  19. package/core/score.ts +955 -14
  20. package/core/sdk/eval-child.ts +7 -2
  21. package/core/sdk/eval.ts +35 -6
  22. package/core/sdk/print.ts +287 -5
  23. package/core/sdk/sync-lyrics.ts +49 -0
  24. package/core/sdk/v1.ts +2132 -46
  25. package/core/sections.ts +480 -35
  26. package/core/sing.ts +815 -0
  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/guides/agent.md +29 -0
  50. package/guides/arrange.md +31 -0
  51. package/guides/audio.md +31 -0
  52. package/guides/audition.md +20 -14
  53. package/guides/automation.md +12 -7
  54. package/guides/chords.md +15 -15
  55. package/guides/effects.md +17 -16
  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 +15 -7
  62. package/guides/music.md +23 -8
  63. package/guides/notes.md +15 -9
  64. package/guides/panes.md +32 -0
  65. package/guides/performance.md +15 -13
  66. package/guides/play.md +21 -13
  67. package/guides/project.md +26 -8
  68. package/guides/providers.md +20 -14
  69. package/guides/resample.md +15 -9
  70. package/guides/rhythm.md +18 -13
  71. package/guides/sessions.md +17 -7
  72. package/guides/show-me.md +31 -0
  73. package/guides/sound.md +25 -9
  74. package/guides/sounds.md +15 -13
  75. package/guides/styles.md +31 -0
  76. package/guides/tape.md +32 -0
  77. package/guides/tempo.md +15 -10
  78. package/guides/tracks.md +15 -10
  79. package/guides/tuning.md +32 -0
  80. package/guides/voice.md +31 -0
  81. package/guides/web-search.md +18 -8
  82. package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
  83. package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
  84. package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
  85. package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
  86. package/native/prebuilt/manifest.json +21 -0
  87. package/package.json +6 -2
  88. package/src/agent/agent.ts +130 -18
  89. package/src/agent/calibration-tools.ts +53 -0
  90. package/src/agent/clip-tools.ts +453 -0
  91. package/src/agent/command-agent.ts +378 -0
  92. package/src/agent/drum-tools.ts +2 -2
  93. package/src/agent/expression-tools.ts +1 -1
  94. package/src/agent/gateway.ts +246 -60
  95. package/src/agent/models.ts +53 -12
  96. package/src/agent/ops.ts +27 -1
  97. package/src/agent/pack-tools.ts +1 -1
  98. package/src/agent/planner.ts +25 -0
  99. package/src/agent/portable-schema.ts +80 -0
  100. package/src/agent/preview-tool.ts +4 -1
  101. package/src/agent/provider.ts +22 -8
  102. package/src/agent/range-tools.ts +216 -0
  103. package/src/agent/rhythm-tools.ts +1 -1
  104. package/src/agent/section-tools.ts +1 -1
  105. package/src/agent/show-me.ts +497 -0
  106. package/src/agent/steer.ts +15 -0
  107. package/src/agent/style-tools.ts +217 -0
  108. package/src/agent/tool-error.ts +12 -0
  109. package/src/agent/tools.ts +110 -23
  110. package/src/agent/usage.ts +2 -2
  111. package/src/agent/voice-tools.ts +925 -0
  112. package/src/agent/xcb-agent.ts +13 -10
  113. package/src/argv.ts +38 -0
  114. package/src/audio/analysis.ts +253 -0
  115. package/src/audio/arrange.ts +49 -5
  116. package/src/audio/audio-command.ts +218 -0
  117. package/src/audio/autotune-engine.ts +101 -0
  118. package/src/audio/autotune.ts +640 -0
  119. package/src/audio/clips.ts +240 -0
  120. package/src/audio/devices.ts +264 -0
  121. package/src/audio/doctor.ts +157 -0
  122. package/src/audio/dsp/bandbank.ts +138 -0
  123. package/src/audio/dsp/envelope.ts +10 -0
  124. package/src/audio/dsp/follow.ts +120 -0
  125. package/src/audio/dsp/formant.ts +427 -0
  126. package/src/audio/dsp/glottal.ts +243 -0
  127. package/src/audio/dsp/interp.ts +7 -2
  128. package/src/audio/dsp/lpc.ts +50 -0
  129. package/src/audio/dsp/periodicity.ts +59 -0
  130. package/src/audio/dsp/pitch.ts +995 -0
  131. package/src/audio/dsp/psola.ts +199 -0
  132. package/src/audio/effects/chain.ts +3 -1
  133. package/src/audio/effects/common.ts +43 -0
  134. package/src/audio/effects/convolution.ts +7 -4
  135. package/src/audio/effects/filter.ts +48 -69
  136. package/src/audio/effects/formant.ts +263 -0
  137. package/src/audio/engine.ts +360 -40
  138. package/src/audio/fit.ts +35 -3
  139. package/src/audio/instrument-check.ts +59 -43
  140. package/src/audio/instruments.ts +4 -0
  141. package/src/audio/keys/calibration.ts +56 -0
  142. package/src/audio/keys/electric.ts +8 -1
  143. package/src/audio/keys/engine.ts +13 -1
  144. package/src/audio/keys/piano.ts +22 -2
  145. package/src/audio/kits.ts +135 -6
  146. package/src/audio/live.ts +114 -20
  147. package/src/audio/native.ts +615 -0
  148. package/src/audio/preview.ts +30 -2
  149. package/src/audio/render-worker.ts +2 -0
  150. package/src/audio/renderer.ts +2 -0
  151. package/src/audio/resample.ts +2 -1
  152. package/src/audio/sampler.ts +85 -4
  153. package/src/audio/samples.ts +20 -2
  154. package/src/audio/sing/analysis.ts +193 -0
  155. package/src/audio/sing/engine.ts +949 -0
  156. package/src/audio/strings/bow.ts +48 -5
  157. package/src/audio/strings/engine.ts +8 -2
  158. package/src/audio/synth/oscillators.ts +31 -21
  159. package/src/audio/synth/voice.ts +34 -1
  160. package/src/audio/vocoder/bank.ts +314 -0
  161. package/src/audio/vocoder/carrier.ts +165 -0
  162. package/src/audio/vocoder/control.ts +68 -0
  163. package/src/audio/vocoder/detect.ts +50 -0
  164. package/src/audio/vocoder/index.ts +304 -0
  165. package/src/audio/vocoder/talkbox.ts +143 -0
  166. package/src/audio/wav.ts +500 -77
  167. package/src/audio/winds/engine.ts +5 -1
  168. package/src/audio/winds/trim.ts +28 -4
  169. package/src/audio/winds/trims1.ts +297 -0
  170. package/src/audio/winds/voice.ts +15 -2
  171. package/src/auth/cli.ts +38 -36
  172. package/src/auth/credentials.ts +30 -1
  173. package/src/auth/login.ts +15 -9
  174. package/src/auth/tui.ts +19 -10
  175. package/src/commands/arrange.ts +82 -32
  176. package/src/commands/autotune.ts +421 -0
  177. package/src/commands/calibration.ts +74 -0
  178. package/src/commands/clips.ts +887 -0
  179. package/src/commands/drums.ts +3 -2
  180. package/src/commands/edit.ts +11 -4
  181. package/src/commands/expression.ts +1 -1
  182. package/src/commands/formant.ts +221 -0
  183. package/src/commands/fx.ts +101 -32
  184. package/src/commands/grammar.ts +560 -0
  185. package/src/commands/help.ts +774 -366
  186. package/src/commands/history.ts +139 -10
  187. package/src/commands/keys.ts +25 -8
  188. package/src/commands/modal.ts +1 -1
  189. package/src/commands/music.ts +1 -1
  190. package/src/commands/nearest.ts +53 -0
  191. package/src/commands/pack.ts +9 -2
  192. package/src/commands/param-range.ts +56 -0
  193. package/src/commands/parses.ts +135 -0
  194. package/src/commands/progression.ts +170 -0
  195. package/src/commands/range.ts +763 -0
  196. package/src/commands/resample.ts +13 -10
  197. package/src/commands/rhythm.ts +3 -0
  198. package/src/commands/rig.ts +3 -24
  199. package/src/commands/sample.ts +11 -1
  200. package/src/commands/sing.ts +474 -0
  201. package/src/commands/strum.ts +13 -1
  202. package/src/commands/style.ts +415 -0
  203. package/src/commands/time.ts +6 -3
  204. package/src/commands/tuning.ts +3 -3
  205. package/src/commands/vocal-pitch.ts +617 -0
  206. package/src/commands/vocal.ts +147 -0
  207. package/src/commands/vocoder.ts +627 -0
  208. package/src/commands/wind.ts +2 -2
  209. package/src/fs/durable.ts +50 -0
  210. package/src/identity/actor.ts +127 -0
  211. package/src/lang/glossary.ts +511 -0
  212. package/src/launch-args.ts +267 -0
  213. package/src/main.ts +2508 -308
  214. package/src/media/cli.ts +20 -3
  215. package/src/media/import.ts +3 -1
  216. package/src/project/check.ts +21 -2
  217. package/src/project/clip-pins.ts +72 -0
  218. package/src/project/init.ts +23 -8
  219. package/src/project/sync.ts +418 -86
  220. package/src/render.ts +20 -1
  221. package/src/session/client.ts +107 -3
  222. package/src/session/clipboard.ts +79 -0
  223. package/src/session/daemon.ts +199 -5
  224. package/src/session/live-host.ts +232 -0
  225. package/src/session/meta.ts +14 -0
  226. package/src/session/origin.ts +174 -0
  227. package/src/session/port.ts +109 -14
  228. package/src/session/presence.ts +34 -4
  229. package/src/session/protocol.ts +365 -7
  230. package/src/session/rebase.ts +63 -12
  231. package/src/session/receipt.ts +265 -0
  232. package/src/session/shared-live.ts +131 -0
  233. package/src/session/store.ts +126 -45
  234. package/src/tui/arrange-menu.ts +225 -23
  235. package/src/tui/audition.ts +1 -1
  236. package/src/tui/euclid.ts +113 -27
  237. package/src/tui/fader.ts +282 -42
  238. package/src/tui/granular-menu.ts +2 -4
  239. package/src/tui/knob-fields.ts +107 -0
  240. package/src/tui/knob-map.ts +198 -0
  241. package/src/tui/menu-clips.ts +297 -0
  242. package/src/tui/menu-time.ts +20 -13
  243. package/src/tui/menu-voice.ts +405 -0
  244. package/src/tui/menu.ts +987 -179
  245. package/src/tui/modal-menu.ts +6 -6
  246. package/src/tui/performance-menu.ts +5 -2
  247. package/src/tui/play-chords.ts +4 -2
  248. package/src/tui/play-mode.ts +15 -1
  249. package/src/tui/play-session.ts +243 -28
  250. package/src/tui/sing-menu.ts +278 -0
  251. package/src/tui/style-menu.ts +104 -0
  252. package/src/tui/tape-mode.ts +580 -0
  253. package/src/tui/tape-view.ts +263 -0
  254. package/src/tui/vocoder-menu.ts +244 -0
  255. package/src/tui/wind-menu.ts +3 -3
  256. package/src/version.ts +8 -0
  257. package/src/web/fetch.ts +115 -29
  258. package/tui/activity.ts +180 -9
  259. package/tui/app.ts +491 -84
  260. package/tui/clip-row.ts +132 -0
  261. package/tui/delight.ts +193 -0
  262. package/tui/drawer.ts +233 -27
  263. package/tui/frame-gate.ts +76 -0
  264. package/tui/grammar.ts +143 -65
  265. package/tui/guide.ts +50 -5
  266. package/tui/highway.ts +309 -30
  267. package/tui/hints.ts +197 -0
  268. package/tui/hits.ts +7 -1
  269. package/tui/input.ts +60 -9
  270. package/tui/keys.ts +1 -1
  271. package/tui/knobs.ts +268 -0
  272. package/tui/play-strip.ts +80 -14
  273. package/tui/prompt.ts +1 -1
  274. package/tui/screen.ts +151 -18
  275. package/tui/tape.ts +439 -0
  276. package/tui/text.ts +35 -2
  277. package/tui/theme.ts +46 -1
@@ -0,0 +1,218 @@
1
+ /**
2
+ * `audio`, `audio out <name|default>`, `audio in <name|default>` and
3
+ * `audio test`: the typed form of Ctrl-K › Project › audio. One output and
4
+ * one input; the choice lives in `<config>/audio.json` on this machine.
5
+ */
6
+ import type { AudioBackendInfo } from "./engine.ts";
7
+ import {
8
+ audioChoicePath,
9
+ blipSamples,
10
+ deviceLabel,
11
+ deviceSupport,
12
+ levelMeter,
13
+ listDevices,
14
+ matchDevice,
15
+ measureInput,
16
+ parseAudioCommand,
17
+ playOnce,
18
+ readAudioChoice,
19
+ writeAudioChoice,
20
+ type AudioChoice,
21
+ } from "./devices.ts";
22
+ import type { MenuAudioDevices } from "../tui/menu.ts";
23
+
24
+ export const AUDIO_USAGE =
25
+ "usage · audio · audio out <name|default> · audio in <name|default> · audio test";
26
+
27
+ export type AudioCommandContext = Readonly<{
28
+ info: AudioBackendInfo;
29
+ env?: Readonly<Record<string, string | undefined>>;
30
+ /** Where the choice is saved (default `<config>/audio.json`). */
31
+ path?: string;
32
+ /** Move this window's playback (the engine's `setDevice`). */
33
+ setOutput?: (name: string | undefined) => void;
34
+ /** Injected by tests: no real waiting for the blip, tone and meter. */
35
+ wait?: (ms: number) => Promise<void>;
36
+ /** Run the blip without blocking the reply (the default awaits it). */
37
+ background?: (task: Promise<unknown>) => void;
38
+ }>;
39
+
40
+ export type AudioCommandResult = Readonly<{
41
+ ok: boolean;
42
+ message: string;
43
+ /** Extra lines for a panel (the device lists, the test result). */
44
+ lines?: readonly string[];
45
+ }>;
46
+
47
+ function names(devices: readonly { name: string }[]): string {
48
+ return devices.length > 0
49
+ ? devices.map((device) => device.name).join(" · ")
50
+ : "none found";
51
+ }
52
+
53
+ /** What Project › audio shows. */
54
+ export function audioMenuState(
55
+ info: AudioBackendInfo,
56
+ choice: AudioChoice = readAudioChoice(),
57
+ env: Readonly<Record<string, string | undefined>> = process.env,
58
+ ): MenuAudioDevices {
59
+ const support = deviceSupport(info);
60
+ if (support.kind !== "native") {
61
+ const output =
62
+ support.kind === "sox"
63
+ ? ((env.DAWG_AUDIO_DEVICE?.trim() || choice.output) ?? "default")
64
+ : "default";
65
+ return {
66
+ output,
67
+ input: "default",
68
+ outputs: [],
69
+ inputs: [],
70
+ unavailable:
71
+ support.kind === "sox"
72
+ ? "sox cannot list devices · type audio out <name> (sox AUDIODEV) · the native sink lists them"
73
+ : support.reason,
74
+ };
75
+ }
76
+ const outputs = listDevices(support.library, false);
77
+ const inputs = listDevices(support.library, true);
78
+ const override = env.DAWG_AUDIO_DEVICE?.trim();
79
+ return {
80
+ output: deviceLabel(override || choice.output, outputs),
81
+ input: deviceLabel(choice.input, inputs),
82
+ outputs: outputs.map((device) => device.name),
83
+ inputs: inputs.map((device) => device.name),
84
+ };
85
+ }
86
+
87
+ export async function runAudioCommand(
88
+ args: string,
89
+ context: AudioCommandContext,
90
+ ): Promise<AudioCommandResult> {
91
+ const parsed = parseAudioCommand(args);
92
+ if (!parsed) return { ok: false, message: AUDIO_USAGE };
93
+ const env = context.env ?? process.env;
94
+ const path = context.path ?? audioChoicePath(env);
95
+ const choice = readAudioChoice(path);
96
+ const support = deviceSupport(context.info);
97
+ const override = env.DAWG_AUDIO_DEVICE?.trim() || undefined;
98
+ const overrideNote = override
99
+ ? ` · DAWG_AUDIO_DEVICE=${override} overrides the output`
100
+ : "";
101
+
102
+ if (parsed.kind === "show") {
103
+ const state = audioMenuState(context.info, choice, env);
104
+ const lines = [
105
+ `output ${state.output}`,
106
+ `input ${state.input}`,
107
+ `player ${context.info.backend}`,
108
+ ];
109
+ if (state.unavailable) lines.push(state.unavailable);
110
+ else {
111
+ lines.push(`outputs ${state.outputs.join(" · ") || "none found"}`);
112
+ lines.push(`inputs ${state.inputs.join(" · ") || "none found"}`);
113
+ }
114
+ return {
115
+ ok: true,
116
+ message: `audio · out ${state.output} · in ${state.input}${overrideNote}`,
117
+ lines,
118
+ };
119
+ }
120
+
121
+ if (support.kind === "none") return { ok: false, message: support.reason };
122
+
123
+ if (parsed.kind === "set" && parsed.side === "output") {
124
+ if (support.kind === "sox") {
125
+ writeAudioChoice("output", parsed.name, path);
126
+ context.setOutput?.(override ?? parsed.name);
127
+ return {
128
+ ok: true,
129
+ message: `audio out ${parsed.name ?? "default"} · sox plays on AUDIODEV (it cannot list devices; the native sink can)${overrideNote}`,
130
+ };
131
+ }
132
+ const devices = listDevices(support.library, false);
133
+ const device = parsed.name ? matchDevice(devices, parsed.name) : undefined;
134
+ if (parsed.name && !device)
135
+ return {
136
+ ok: false,
137
+ message: `no output named "${parsed.name}" · ${names(devices)}`,
138
+ };
139
+ writeAudioChoice("output", device?.name, path);
140
+ const playing = override ?? device?.name;
141
+ context.setOutput?.(playing);
142
+ // The soft blip says where sound now comes out.
143
+ const blip = playOnce(
144
+ support.library,
145
+ playing,
146
+ blipSamples(48_000),
147
+ 48_000,
148
+ context.wait,
149
+ ).catch(() => false);
150
+ if (context.background) context.background(blip);
151
+ else await blip;
152
+ return {
153
+ ok: true,
154
+ message: `audio out ${deviceLabel(device?.name, devices)}${overrideNote}`,
155
+ };
156
+ }
157
+
158
+ if (parsed.kind === "set") {
159
+ if (support.kind === "sox")
160
+ return {
161
+ ok: false,
162
+ message:
163
+ "choosing an input needs the native sink (it captures by name; sox here only plays)",
164
+ };
165
+ const devices = listDevices(support.library, true);
166
+ const device = parsed.name ? matchDevice(devices, parsed.name) : undefined;
167
+ if (parsed.name && !device)
168
+ return {
169
+ ok: false,
170
+ message: `no input named "${parsed.name}" · ${names(devices)}`,
171
+ };
172
+ writeAudioChoice("input", device?.name, path);
173
+ return {
174
+ ok: true,
175
+ message: `audio in ${deviceLabel(device?.name, devices)} · audio test meters it`,
176
+ };
177
+ }
178
+
179
+ // audio test: a short tone on the output, then the input's level.
180
+ if (support.kind === "sox")
181
+ return {
182
+ ok: false,
183
+ message:
184
+ "audio test needs the native sink (it plays the tone and meters the input)",
185
+ };
186
+ const output = override ?? choice.output;
187
+ const outputs = listDevices(support.library, false);
188
+ const inputs = listDevices(support.library, true);
189
+ const played = await playOnce(
190
+ support.library,
191
+ output,
192
+ blipSamples(48_000, 0.4, 440, 0.15),
193
+ 48_000,
194
+ context.wait,
195
+ ).catch(() => false);
196
+ let meter: string;
197
+ try {
198
+ const level = await measureInput(
199
+ support.library,
200
+ choice.input,
201
+ 1,
202
+ 48_000,
203
+ context.wait,
204
+ );
205
+ meter = levelMeter(level.peakDb);
206
+ } catch (error) {
207
+ meter = `could not open (${error instanceof Error ? error.message : String(error)})`;
208
+ }
209
+ const lines = [
210
+ `output ${deviceLabel(output, outputs)} · ${played ? "tone played" : "could not open"}`,
211
+ `input ${deviceLabel(choice.input, inputs)} · peak ${meter}`,
212
+ ];
213
+ return {
214
+ ok: played,
215
+ message: `audio test · ${lines[0]!.replace(/\s+/, " ")} · ${lines[1]!.replace(/\s+/, " ")}`,
216
+ lines,
217
+ };
218
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The built-in pitch engine for autotune (0.7): the pitch lane's tracker
3
+ * (src/audio/dsp/pitch.ts) and PSOLA (src/audio/dsp/psola.ts) behind the
4
+ * `PitchEngine` seam of src/audio/autotune.ts. Curves are memoised per
5
+ * audio (`id ?? sha256`, else the buffer itself) and voice range; a file
6
+ * whose curve the analysis cache already holds is not tracked again. With
7
+ * `defer` (live play) a missing curve is tracked in the background and the
8
+ * buffer plays untuned until it is ready. Deterministic: the same audio
9
+ * always gives the same curve and the same tuned bytes.
10
+ */
11
+ import { cachedPitchCurve } from "./analysis.ts";
12
+ import {
13
+ PITCH_TRACKER_VERSION,
14
+ trackPitch,
15
+ trackPitchAsync,
16
+ type PitchCurve,
17
+ } from "./dsp/pitch.ts";
18
+ import { psola } from "./dsp/psola.ts";
19
+ import type { PitchEngine, TunableBuffer } from "./autotune.ts";
20
+
21
+ /** Curves kept in memory by key; older ones are tracked again on demand. */
22
+ const CURVE_ENTRIES = 64;
23
+
24
+ const curves = new Map<string, PitchCurve>();
25
+ const anonymous = new WeakMap<Float32Array, Map<string, PitchCurve>>();
26
+ const pending = new Set<string>();
27
+
28
+ function keyOf(buffer: TunableBuffer): string | undefined {
29
+ return buffer.id ?? buffer.sha256;
30
+ }
31
+
32
+ function lookup(buffer: TunableBuffer, voice: string): PitchCurve | undefined {
33
+ const key = keyOf(buffer);
34
+ if (key === undefined) return anonymous.get(buffer.mono)?.get(voice);
35
+ const name = `${key}|${voice}`;
36
+ const hit = curves.get(name);
37
+ if (hit) {
38
+ curves.delete(name);
39
+ curves.set(name, hit);
40
+ return hit;
41
+ }
42
+ // The decoded file itself: the pitch lane's analysis cache may know it.
43
+ if (buffer.id === undefined && buffer.sha256 !== undefined) {
44
+ const cached = cachedPitchCurve(
45
+ buffer.sha256,
46
+ voice as Parameters<typeof cachedPitchCurve>[1],
47
+ );
48
+ if (cached) store(buffer, voice, cached);
49
+ return cached;
50
+ }
51
+ return undefined;
52
+ }
53
+
54
+ function store(buffer: TunableBuffer, voice: string, curve: PitchCurve): void {
55
+ const key = keyOf(buffer);
56
+ if (key === undefined) {
57
+ const byVoice = anonymous.get(buffer.mono) ?? new Map<string, PitchCurve>();
58
+ byVoice.set(voice, curve);
59
+ anonymous.set(buffer.mono, byVoice);
60
+ return;
61
+ }
62
+ const name = `${key}|${voice}`;
63
+ curves.delete(name);
64
+ curves.set(name, curve);
65
+ while (curves.size > CURVE_ENTRIES)
66
+ curves.delete(curves.keys().next().value!);
67
+ }
68
+
69
+ /** Drops the memoised curves (tests). */
70
+ export function clearEngineCurves(): void {
71
+ curves.clear();
72
+ pending.clear();
73
+ }
74
+
75
+ /** The pitch lane's tracker and PSOLA as an autotune engine. */
76
+ export const builtinPitchEngine: PitchEngine = Object.freeze({
77
+ version: `yin${PITCH_TRACKER_VERSION}-psola1`,
78
+ curve(buffer, voice, options) {
79
+ const hit = lookup(buffer, voice);
80
+ if (hit) return hit;
81
+ const x = Float64Array.from(buffer.mono);
82
+ if (!options?.defer) {
83
+ const curve = trackPitch(x, buffer.sampleRate, { voice });
84
+ store(buffer, voice, curve);
85
+ return curve;
86
+ }
87
+ const name = `${keyOf(buffer) ?? "anon"}|${voice}`;
88
+ if (!pending.has(name)) {
89
+ pending.add(name);
90
+ void trackPitchAsync(x, buffer.sampleRate, { voice }).then(
91
+ (curve) => {
92
+ pending.delete(name);
93
+ store(buffer, voice, curve);
94
+ },
95
+ () => pending.delete(name),
96
+ );
97
+ }
98
+ return undefined;
99
+ },
100
+ psola: (x, sampleRate, curve) => psola(x, sampleRate, curve),
101
+ });