@libraz/libsonare 1.3.0 → 1.3.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.
- package/dist/index.js +33 -18
- package/dist/index.js.map +1 -1
- package/dist/sonare-rt.wasm +0 -0
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +1 -1
- package/package.json +1 -1
- package/src/live_audio.ts +6 -4
- package/src/opfs_clip_pages.ts +19 -4
- package/src/web_midi.ts +10 -11
package/dist/sonare-rt.wasm
CHANGED
|
Binary file
|
package/dist/sonare.wasm
CHANGED
|
Binary file
|
package/dist/worklet.d.ts
CHANGED
|
@@ -3740,7 +3740,7 @@ interface OpfsClipPageProviderBinding {
|
|
|
3740
3740
|
supplyRequest(request: ClipPageRequest): Promise<boolean>;
|
|
3741
3741
|
close(): void;
|
|
3742
3742
|
}
|
|
3743
|
-
declare const opfsClipPageWorkerSource = "\nself.onmessage = async (event) => {\n const message = event.data;\n if (!message || message.type !== 'sonare:read-clip-page') return;\n const { requestId, path, pageIndex, numChannels, numSamples, pageFrames, dataOffsetBytes = 0 } = message;\n try {\n if (pageIndex < 0) {\n self.postMessage({ type: 'sonare:clip-page', requestId, pageIndex, ok: false });\n return;\n }\n const startFrame = pageIndex * pageFrames;\n if (startFrame >= numSamples) {\n self.postMessage({ type: 'sonare:clip-page', requestId, pageIndex, ok: false });\n return;\n }\n const root = await self.navigator.storage.getDirectory();\n let dir = root;\n const parts = String(path).split('/').filter(Boolean);\n for (let i = 0; i < parts.length - 1; ++i) {\n dir = await dir.getDirectoryHandle(parts[i]);\n }\n const fileHandle = await dir.getFileHandle(parts[parts.length - 1]);\n const access = await fileHandle.createSyncAccessHandle();\n try {\n const frames = Math.min(pageFrames, numSamples - startFrame);\n const frameBytes = numChannels * 4;\n const bytes = new Uint8Array(frames * frameBytes);\n
|
|
3743
|
+
declare const opfsClipPageWorkerSource = "\nself.onmessage = async (event) => {\n const message = event.data;\n if (!message || message.type !== 'sonare:read-clip-page') return;\n const { requestId, path, pageIndex, numChannels, numSamples, pageFrames, dataOffsetBytes = 0 } = message;\n try {\n if (pageIndex < 0) {\n self.postMessage({ type: 'sonare:clip-page', requestId, pageIndex, ok: false });\n return;\n }\n const startFrame = pageIndex * pageFrames;\n if (startFrame >= numSamples) {\n self.postMessage({ type: 'sonare:clip-page', requestId, pageIndex, ok: false });\n return;\n }\n const root = await self.navigator.storage.getDirectory();\n let dir = root;\n const parts = String(path).split('/').filter(Boolean);\n for (let i = 0; i < parts.length - 1; ++i) {\n dir = await dir.getDirectoryHandle(parts[i]);\n }\n const fileHandle = await dir.getFileHandle(parts[parts.length - 1]);\n const access = await fileHandle.createSyncAccessHandle();\n try {\n const frames = Math.min(pageFrames, numSamples - startFrame);\n const frameBytes = numChannels * 4;\n const bytes = new Uint8Array(frames * frameBytes);\n let bytesReadTotal = 0;\n const readOffset = dataOffsetBytes + startFrame * frameBytes;\n while (bytesReadTotal < bytes.byteLength) {\n const bytesRead = access.read(bytes.subarray(bytesReadTotal), {\n at: readOffset + bytesReadTotal,\n });\n if (bytesRead <= 0) {\n break;\n }\n bytesReadTotal += bytesRead;\n }\n if (bytesReadTotal !== bytes.byteLength || bytesReadTotal % frameBytes !== 0) {\n self.postMessage({ type: 'sonare:clip-page', requestId, pageIndex, ok: false });\n return;\n }\n const framesRead = bytesReadTotal / frameBytes;\n const view = new DataView(bytes.buffer, 0, framesRead * frameBytes);\n const channelBuffers = Array.from({ length: numChannels }, () => new ArrayBuffer(framesRead * 4));\n for (let ch = 0; ch < numChannels; ++ch) {\n const channel = new Float32Array(channelBuffers[ch]);\n for (let frame = 0; frame < framesRead; ++frame) {\n channel[frame] = view.getFloat32((frame * numChannels + ch) * 4, true);\n }\n }\n self.postMessage(\n { type: 'sonare:clip-page', requestId, pageIndex, ok: true, frames: framesRead, channelBuffers },\n channelBuffers,\n );\n } finally {\n access.close();\n }\n } catch (error) {\n self.postMessage({\n type: 'sonare:clip-page',\n requestId,\n pageIndex,\n ok: false,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n};\n";
|
|
3744
3744
|
declare function createOpfsClipPageWorker(): Worker;
|
|
3745
3745
|
declare function createOpfsClipPageProvider(engine: RealtimeEngine, options: OpfsClipPageProviderOptions): OpfsClipPageProviderBinding;
|
|
3746
3746
|
|
package/package.json
CHANGED
package/src/live_audio.ts
CHANGED
|
@@ -16,11 +16,13 @@ export async function bindMicrophoneInput(
|
|
|
16
16
|
engine: SonareRealtimeEngineNode | AudioWorkletNode,
|
|
17
17
|
options: BindMicrophoneInputOptions = {},
|
|
18
18
|
): Promise<MicrophoneInputBinding> {
|
|
19
|
+
const { stream: providedStream, stopTracksOnClose = true, ...constraints } = options;
|
|
19
20
|
const stream =
|
|
20
|
-
|
|
21
|
+
providedStream ??
|
|
21
22
|
(await navigator.mediaDevices.getUserMedia({
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
...constraints,
|
|
24
|
+
audio: constraints.audio ?? true,
|
|
25
|
+
video: constraints.video ?? false,
|
|
24
26
|
}));
|
|
25
27
|
const source = context.createMediaStreamSource(stream);
|
|
26
28
|
const node = 'node' in engine ? engine.node : engine;
|
|
@@ -35,7 +37,7 @@ export async function bindMicrophoneInput(
|
|
|
35
37
|
}
|
|
36
38
|
closed = true;
|
|
37
39
|
source.disconnect();
|
|
38
|
-
if (
|
|
40
|
+
if (stopTracksOnClose) {
|
|
39
41
|
for (const track of stream.getAudioTracks()) {
|
|
40
42
|
track.stop();
|
|
41
43
|
}
|
package/src/opfs_clip_pages.ts
CHANGED
|
@@ -55,12 +55,22 @@ self.onmessage = async (event) => {
|
|
|
55
55
|
const frames = Math.min(pageFrames, numSamples - startFrame);
|
|
56
56
|
const frameBytes = numChannels * 4;
|
|
57
57
|
const bytes = new Uint8Array(frames * frameBytes);
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
58
|
+
let bytesReadTotal = 0;
|
|
59
|
+
const readOffset = dataOffsetBytes + startFrame * frameBytes;
|
|
60
|
+
while (bytesReadTotal < bytes.byteLength) {
|
|
61
|
+
const bytesRead = access.read(bytes.subarray(bytesReadTotal), {
|
|
62
|
+
at: readOffset + bytesReadTotal,
|
|
63
|
+
});
|
|
64
|
+
if (bytesRead <= 0) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
bytesReadTotal += bytesRead;
|
|
68
|
+
}
|
|
69
|
+
if (bytesReadTotal !== bytes.byteLength || bytesReadTotal % frameBytes !== 0) {
|
|
61
70
|
self.postMessage({ type: 'sonare:clip-page', requestId, pageIndex, ok: false });
|
|
62
71
|
return;
|
|
63
72
|
}
|
|
73
|
+
const framesRead = bytesReadTotal / frameBytes;
|
|
64
74
|
const view = new DataView(bytes.buffer, 0, framesRead * frameBytes);
|
|
65
75
|
const channelBuffers = Array.from({ length: numChannels }, () => new ArrayBuffer(framesRead * 4));
|
|
66
76
|
for (let ch = 0; ch < numChannels; ++ch) {
|
|
@@ -137,7 +147,12 @@ export function createOpfsClipPageProvider(
|
|
|
137
147
|
entry.resolve(false);
|
|
138
148
|
return;
|
|
139
149
|
}
|
|
140
|
-
|
|
150
|
+
try {
|
|
151
|
+
provider.supply(response.pageIndex, channels);
|
|
152
|
+
} catch {
|
|
153
|
+
entry.resolve(false);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
141
156
|
entry.resolve(true);
|
|
142
157
|
};
|
|
143
158
|
worker.addEventListener('message', onMessage as EventListener);
|
package/src/web_midi.ts
CHANGED
|
@@ -105,9 +105,8 @@ export async function bindWebMidi(
|
|
|
105
105
|
engine: RealtimeEngine,
|
|
106
106
|
options: BindWebMidiOptions = {},
|
|
107
107
|
): Promise<WebMidiBinding> {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
if (typeof requestMIDIAccess !== 'function') {
|
|
108
|
+
const navigatorWithMidi = globalThis.navigator as NavigatorWithMidi | undefined;
|
|
109
|
+
if (typeof navigatorWithMidi?.requestMIDIAccess !== 'function') {
|
|
111
110
|
throw new Error('Web MIDI is not available in this environment');
|
|
112
111
|
}
|
|
113
112
|
|
|
@@ -115,7 +114,9 @@ export async function bindWebMidi(
|
|
|
115
114
|
assertNibble('bindWebMidi', group, 'group');
|
|
116
115
|
const destinationId = options.destinationId ?? 0;
|
|
117
116
|
const selectedIds = new Set(options.inputIds ?? []);
|
|
118
|
-
|
|
117
|
+
// Invoke through the navigator so the browser's native method keeps its
|
|
118
|
+
// required `this` binding (detached calls throw "Illegal invocation").
|
|
119
|
+
const access = await navigatorWithMidi.requestMIDIAccess({
|
|
119
120
|
sysex: options.sysex ?? false,
|
|
120
121
|
software: options.software ?? true,
|
|
121
122
|
});
|
|
@@ -154,9 +155,7 @@ export async function bindWebMidi(
|
|
|
154
155
|
runningStatus,
|
|
155
156
|
options.timestampToSamples,
|
|
156
157
|
);
|
|
157
|
-
|
|
158
|
-
runningStatus = status;
|
|
159
|
-
}
|
|
158
|
+
runningStatus = status;
|
|
160
159
|
};
|
|
161
160
|
if (input.addEventListener) {
|
|
162
161
|
input.addEventListener('midimessage', listener);
|
|
@@ -268,12 +267,12 @@ function dispatchMidiMessage(
|
|
|
268
267
|
const message = status & 0xf0;
|
|
269
268
|
const channel = status & 0x0f;
|
|
270
269
|
if (message < 0x80 || message > 0xe0) {
|
|
271
|
-
return status;
|
|
270
|
+
return status >= 0xf8 ? runningStatus : 0;
|
|
272
271
|
}
|
|
273
272
|
|
|
274
273
|
const a = readU7(data, offset);
|
|
275
274
|
const b = readU7(data, offset + 1);
|
|
276
|
-
if (a < 0) {
|
|
275
|
+
if (a < 0 || b < 0) {
|
|
277
276
|
return status;
|
|
278
277
|
}
|
|
279
278
|
|
|
@@ -282,9 +281,9 @@ function dispatchMidiMessage(
|
|
|
282
281
|
: 0;
|
|
283
282
|
|
|
284
283
|
if (message === 0x80) {
|
|
285
|
-
engine.pushMidiInputNoteOff(group, channel, a, b
|
|
284
|
+
engine.pushMidiInputNoteOff(group, channel, a, b, portTimeSamples);
|
|
286
285
|
} else if (message === 0x90) {
|
|
287
|
-
if (
|
|
286
|
+
if (b === 0) {
|
|
288
287
|
engine.pushMidiInputNoteOff(group, channel, a, 0, portTimeSamples);
|
|
289
288
|
} else {
|
|
290
289
|
engine.pushMidiInputNoteOn(group, channel, a, b, portTimeSamples);
|