@ensomi/runtime 0.0.0 → 0.1.1

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 (42) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +28 -0
  3. package/package.json +34 -3
  4. package/pkg/ensomi_generate_wasm.d.ts +109 -0
  5. package/pkg/ensomi_generate_wasm.js +806 -0
  6. package/pkg/ensomi_generate_wasm_bg.wasm +0 -0
  7. package/pkg/ensomi_generate_wasm_bg.wasm.d.ts +25 -0
  8. package/resampler/resample-11025-boundary.bin +0 -0
  9. package/resampler/resample-11025.bin +0 -0
  10. package/resampler/resample-11025.json +18 -0
  11. package/resampler/resample-16000-boundary.bin +0 -0
  12. package/resampler/resample-16000.bin +0 -0
  13. package/resampler/resample-16000.json +18 -0
  14. package/resampler/resample-192000-boundary.bin +0 -0
  15. package/resampler/resample-192000.bin +0 -0
  16. package/resampler/resample-192000.json +18 -0
  17. package/resampler/resample-24000-boundary.bin +0 -0
  18. package/resampler/resample-24000.bin +0 -0
  19. package/resampler/resample-24000.json +18 -0
  20. package/resampler/resample-32000-boundary.bin +0 -0
  21. package/resampler/resample-32000.bin +0 -0
  22. package/resampler/resample-32000.json +18 -0
  23. package/resampler/resample-44100-boundary.bin +0 -0
  24. package/resampler/resample-44100.bin +0 -0
  25. package/resampler/resample-44100.json +18 -0
  26. package/resampler/resample-48000-boundary.bin +0 -0
  27. package/resampler/resample-48000.bin +0 -0
  28. package/resampler/resample-48000.json +18 -0
  29. package/resampler/resample-88200-boundary.bin +0 -0
  30. package/resampler/resample-88200.bin +0 -0
  31. package/resampler/resample-88200.json +18 -0
  32. package/resampler/resample-96000-boundary.bin +0 -0
  33. package/resampler/resample-96000.bin +0 -0
  34. package/resampler/resample-96000.json +18 -0
  35. package/src/core.js +40 -0
  36. package/src/decode_audio.js +22 -0
  37. package/src/export.js +31 -0
  38. package/src/generate.js +73 -0
  39. package/src/index.d.ts +157 -0
  40. package/src/index.js +5 -0
  41. package/src/models.js +116 -0
  42. package/src/types.js +7 -0
package/src/models.js ADDED
@@ -0,0 +1,116 @@
1
+ import { callCore, coreInstance, loadCore, readFile } from './core.js';
2
+
3
+ /** Reads files relative to a base URL: with `fetch`, or from the file system for a `file:` URL in Node. */
4
+ export function urlSource(baseUrl) {
5
+ const base = baseUrl.replace(/\/$/, '') + '/';
6
+ return async (path) => {
7
+ const url = new URL(path, base);
8
+ if (url.protocol === 'file:')
9
+ return readFile(url);
10
+ const response = await fetch(url);
11
+ if (!response.ok)
12
+ throw new Error(`Model download failed: ${path} (${response.status})`);
13
+ return new Uint8Array(await response.arrayBuffer());
14
+ };
15
+ }
16
+
17
+ export function localFilesSource(files) {
18
+ const entries = new Map(Array.from(files, f => [f.name, f]));
19
+ return async (path) => {
20
+ const file = entries.get(path) ?? entries.get(path.split('/').pop());
21
+ if (!file)
22
+ throw new Error(`Missing model file: ${path}`);
23
+ return new Uint8Array(await file.arrayBuffer());
24
+ };
25
+ }
26
+
27
+ /** Graphs that use WebGPU when it is available. The other graphs run once per beat or head row on small
28
+ * inputs, where WebGPU's per-call transfers cost more than computing on the CPU. */
29
+ const GPU_GRAPHS = new Set(['beatthis']);
30
+
31
+ /** The model in the core: `runtime.json` and the files it names besides the graphs. */
32
+ async function coreModels(core, manifestJson, read) {
33
+ const paths = core.WasmModels.requiredFiles(manifestJson);
34
+ const bytes = await Promise.all(paths.map(path => read(path)));
35
+ return callCore(() => new core.WasmModels(manifestJson, Object.fromEntries(paths.map((path, i) => [path, bytes[i]]))));
36
+ }
37
+
38
+ export class Models {
39
+ constructor(core, wasm, ort, sessions, source, runtimeSource, manifestJson) {
40
+ this.core = core;
41
+ this.wasm = wasm;
42
+ this.ort = ort;
43
+ this.sessions = sessions;
44
+ this.source = source;
45
+ this.runtimeSource = runtimeSource;
46
+ this.manifestJson = manifestJson;
47
+ this.instance = coreInstance();
48
+ this.capabilities = JSON.parse(wasm.capabilities());
49
+ }
50
+ /** Loads the model into the core again if a trap replaced the core's instance since (see `callCore`). */
51
+ async reloadCore() {
52
+ if (this.instance === coreInstance())
53
+ return;
54
+ this.wasm = await coreModels(this.core, this.manifestJson, this.source);
55
+ this.instance = coreInstance();
56
+ }
57
+ async dispose() {
58
+ for (const session of Object.values(this.sessions))
59
+ await session.release();
60
+ if (this.instance === coreInstance())
61
+ this.wasm.free();
62
+ }
63
+ }
64
+
65
+ /** Where the soxr tables for raw PCM at other rates than 22050 Hz are read from: the package's `resampler/`. */
66
+ function defaultRuntimeSource(path) {
67
+ return urlSource(new URL('../resampler/', import.meta.url).href)(path);
68
+ }
69
+
70
+ /**
71
+ * Loads the six graphs into ONNX Runtime Web sessions and the rest of the model package into the core.
72
+ * `source` is a base URL or an async function `(relativePath) => Uint8Array`.
73
+ * Options: `ort` (the onnxruntime-web module), `session` (SessionOptions), `backend` ('webgpu' | 'wasm'),
74
+ * `wasm` (ORT's `env.wasm` flags), `runtimeSource` (reads the `resample-*` tables), `core` (the Wasm core's
75
+ * bytes, module or URL).
76
+ */
77
+ export async function loadModels(source, options = {}) {
78
+ const read = typeof source === 'string' ? urlSource(source) : source;
79
+ const core = await loadCore(options.core);
80
+ const manifestBytes = await read('runtime.json');
81
+ const manifestJson = new TextDecoder().decode(manifestBytes);
82
+ const manifest = JSON.parse(manifestJson);
83
+ if (manifest.format !== 'ensomi-onnx-runtime-v1')
84
+ throw new Error(`Unsupported runtime format: ${manifest.format}`);
85
+ const backend = options.backend ?? (typeof navigator !== 'undefined' && 'gpu' in navigator ? 'webgpu' : 'wasm');
86
+ const ort = options.ort ?? (backend === 'webgpu' ? await import('onnxruntime-web/webgpu') : await import('onnxruntime-web/wasm'));
87
+ if (options.wasm)
88
+ Object.assign(ort.env.wasm, options.wasm);
89
+ const sessions = {};
90
+ // All files download at once; sessions are created in order, each as soon as its file has arrived.
91
+ const graphs = Object.entries(manifest.graphs), reads = graphs.map(([, graph]) => read(graph.file));
92
+ const create = (async () => {
93
+ for (const [i, [name]] of graphs.entries())
94
+ sessions[name] = await ort.InferenceSession.create(await reads[i], options.session ?? { executionProviders: backend === 'webgpu' && GPU_GRAPHS.has(name) ? ['webgpu', 'wasm'] : ['wasm'] });
95
+ })();
96
+ const release = async () => {
97
+ await create.catch(() => undefined);
98
+ for (const session of Object.values(sessions))
99
+ await session.release();
100
+ };
101
+ try {
102
+ const wasm = await coreModels(core, manifestJson, read);
103
+ try {
104
+ await Promise.all([create, ...reads]);
105
+ }
106
+ catch (error) {
107
+ wasm.free();
108
+ throw error;
109
+ }
110
+ return new Models(core, wasm, ort, sessions, read, options.runtimeSource ?? defaultRuntimeSource, manifestJson);
111
+ }
112
+ catch (error) {
113
+ await release();
114
+ throw error;
115
+ }
116
+ }
package/src/types.js ADDED
@@ -0,0 +1,7 @@
1
+ // EnsomiProtocol 0.2 enum values, with the reverse mapping TypeScript numeric enums have.
2
+ const numeric = (names) => Object.freeze(Object.fromEntries(names.flatMap((name, value) => [[name, value], [value, name]])));
3
+
4
+ export const LongNoteAmount = numeric(['UNSPECIFIED', 'TAP_ONLY', 'HALF', 'NATURAL']);
5
+ export const PatternKind = numeric(['UNSPECIFIED', 'STREAM', 'JUMPSTREAM', 'CHORDSTREAM', 'JACK', 'CHORDJACK']);
6
+ export const ControlStatus = numeric(['UNSPECIFIED', 'WORKS', 'EXPERIMENTAL', 'NOT_WORKING']);
7
+ export const GenerationPhase = numeric(['UNSPECIFIED', 'LOADING', 'TIMING', 'ROWS', 'ARRANGEMENT', 'EXPORT']);