@libraz/libsonare 1.8.0 → 1.8.2
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/README.md +5 -5
- package/dist/_effects_common.d.ts +2 -1
- package/dist/_effects_common.d.ts.map +1 -1
- package/dist/analysis.js +3 -0
- package/dist/analysis.js.map +1 -1
- package/dist/effects_mastering.d.ts +2 -2
- package/dist/effects_mastering.d.ts.map +1 -1
- package/dist/effects_note_ops.d.ts +55 -30
- package/dist/effects_note_ops.d.ts.map +1 -1
- package/dist/effects_timepitch.d.ts +17 -12
- package/dist/effects_timepitch.d.ts.map +1 -1
- package/dist/feature_decompose.d.ts +1 -1
- package/dist/feature_decompose.d.ts.map +1 -1
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2098 -155
- package/dist/index.js.map +1 -1
- package/dist/instrument_types.d.ts +27 -3
- package/dist/instrument_types.d.ts.map +1 -1
- package/dist/mastering_core.d.ts +30 -3
- package/dist/mastering_core.d.ts.map +1 -1
- package/dist/mixer.d.ts +2 -2
- package/dist/mixer.d.ts.map +1 -1
- package/dist/project.d.ts +5 -4
- package/dist/project.d.ts.map +1 -1
- package/dist/project_class.d.ts +57 -3
- package/dist/project_class.d.ts.map +1 -1
- package/dist/project_internal.d.ts +35 -1
- package/dist/project_internal.d.ts.map +1 -1
- package/dist/project_types.d.ts +17 -6
- package/dist/project_types.d.ts.map +1 -1
- package/dist/public_types.d.ts +1 -0
- package/dist/public_types.d.ts.map +1 -1
- package/dist/public_types_mastering.d.ts +19 -1
- package/dist/public_types_mastering.d.ts.map +1 -1
- package/dist/public_types_playback.d.ts +1 -0
- package/dist/public_types_playback.d.ts.map +1 -1
- package/dist/public_types_spectral.d.ts +5 -5
- package/dist/public_types_spectral.d.ts.map +1 -1
- package/dist/public_types_vocal_edit.d.ts +250 -0
- package/dist/public_types_vocal_edit.d.ts.map +1 -0
- package/dist/public_types_vocal_project.d.ts +63 -0
- package/dist/public_types_vocal_project.d.ts.map +1 -0
- package/dist/realtime_engine.d.ts +106 -16
- package/dist/realtime_engine.d.ts.map +1 -1
- package/dist/repair_dereverb.d.ts +1 -0
- package/dist/repair_dereverb.d.ts.map +1 -1
- package/dist/repair_noise.d.ts +1 -0
- package/dist/repair_noise.d.ts.map +1 -1
- package/dist/sonare-analysis.wasm +0 -0
- package/dist/sonare.d.ts +89 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/streaming_processors.d.ts +5 -0
- package/dist/streaming_processors.d.ts.map +1 -1
- package/dist/vocal_edit.d.ts +131 -0
- package/dist/vocal_edit.d.ts.map +1 -0
- package/dist/vocal_edit_worker.d.ts +51 -0
- package/dist/vocal_edit_worker.d.ts.map +1 -0
- package/dist/vocal_edit_worker.js +1462 -0
- package/dist/vocal_edit_worker.js.map +1 -0
- package/dist/vocal_edit_worker_client.d.ts +118 -0
- package/dist/vocal_edit_worker_client.d.ts.map +1 -0
- package/dist/vocal_edit_worker_protocol.d.ts +139 -0
- package/dist/vocal_edit_worker_protocol.d.ts.map +1 -0
- package/dist/vocal_project.d.ts +17 -0
- package/dist/vocal_project.d.ts.map +1 -0
- package/dist/web_midi.d.ts +4 -0
- package/dist/web_midi.d.ts.map +1 -1
- package/dist/worker.js.map +1 -1
- package/dist/worklet/engine-mixer-facade.d.ts +42 -5
- package/dist/worklet/engine-mixer-facade.d.ts.map +1 -1
- package/dist/worklet/engine-node.d.ts +10 -0
- package/dist/worklet/engine-node.d.ts.map +1 -1
- package/dist/worklet/engine-processor.d.ts +6 -1
- package/dist/worklet/engine-processor.d.ts.map +1 -1
- package/dist/worklet/engine-register.d.ts.map +1 -1
- package/dist/worklet/engine-strips.d.ts +8 -4
- package/dist/worklet/engine-strips.d.ts.map +1 -1
- package/dist/worklet/engine-sync.d.ts +4 -2
- package/dist/worklet/engine-sync.d.ts.map +1 -1
- package/dist/worklet/engine.d.ts +42 -9
- package/dist/worklet/engine.d.ts.map +1 -1
- package/dist/worklet/guards.d.ts +10 -2
- package/dist/worklet/guards.d.ts.map +1 -1
- package/dist/worklet/messages.d.ts +54 -9
- package/dist/worklet/messages.d.ts.map +1 -1
- package/dist/worklet/mixer-processor.d.ts +2 -2
- package/dist/worklet/mixer-processor.d.ts.map +1 -1
- package/dist/worklet/protocol.d.ts +20 -2
- package/dist/worklet/protocol.d.ts.map +1 -1
- package/dist/worklet.d.ts +1 -1
- package/dist/worklet.d.ts.map +1 -1
- package/dist/worklet.js +808 -204
- package/dist/worklet.js.map +1 -1
- package/package.json +6 -1
- package/src/_effects_common.ts +36 -6
- package/src/effects_mastering.ts +4 -0
- package/src/effects_note_ops.ts +92 -44
- package/src/effects_timepitch.ts +42 -31
- package/src/feature_decompose.ts +2 -2
- package/src/index.ts +39 -0
- package/src/instrument_types.ts +29 -3
- package/src/mastering_core.ts +98 -2
- package/src/mixer.ts +41 -21
- package/src/project.ts +8 -1
- package/src/project_class.ts +136 -1
- package/src/project_internal.ts +90 -0
- package/src/project_types.ts +18 -6
- package/src/public_types.ts +1 -0
- package/src/public_types_mastering.ts +23 -0
- package/src/public_types_playback.ts +1 -0
- package/src/public_types_spectral.ts +5 -5
- package/src/public_types_vocal_edit.ts +289 -0
- package/src/public_types_vocal_project.ts +69 -0
- package/src/realtime_engine.ts +157 -24
- package/src/repair_dereverb.ts +1 -0
- package/src/repair_noise.ts +1 -0
- package/src/sonare.js.d.ts +89 -0
- package/src/streaming_processors.ts +8 -0
- package/src/vocal_edit.ts +885 -0
- package/src/vocal_edit_worker.ts +832 -0
- package/src/vocal_edit_worker_client.ts +812 -0
- package/src/vocal_edit_worker_protocol.ts +206 -0
- package/src/vocal_project.ts +327 -0
- package/src/web_midi.ts +100 -5
- package/src/worklet/engine-mixer-facade.ts +422 -87
- package/src/worklet/engine-node.ts +69 -4
- package/src/worklet/engine-processor.ts +129 -34
- package/src/worklet/engine-register.ts +3 -0
- package/src/worklet/engine-strips.ts +36 -14
- package/src/worklet/engine-sync.ts +6 -1
- package/src/worklet/engine.ts +76 -40
- package/src/worklet/guards.ts +81 -0
- package/src/worklet/messages.ts +63 -4
- package/src/worklet/mixer-processor.ts +10 -3
- package/src/worklet/protocol.ts +39 -8
- package/src/worklet.ts +2 -0
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
import { getSonareModule } from './module_state';
|
|
2
|
+
import type {
|
|
3
|
+
VocalAnalysis,
|
|
4
|
+
VocalApplyRequest,
|
|
5
|
+
VocalCapabilities,
|
|
6
|
+
VocalCreateRequest,
|
|
7
|
+
VocalEditOperation,
|
|
8
|
+
VocalEditResult,
|
|
9
|
+
VocalErrorShape,
|
|
10
|
+
VocalHistoryState,
|
|
11
|
+
VocalJobProgress,
|
|
12
|
+
VocalNote,
|
|
13
|
+
VocalNotesResult,
|
|
14
|
+
VocalPitchEvaluation,
|
|
15
|
+
VocalRange,
|
|
16
|
+
VocalRenderRequest,
|
|
17
|
+
VocalRenderResult,
|
|
18
|
+
VocalRestoreRequest,
|
|
19
|
+
VocalSessionLimits,
|
|
20
|
+
VocalStateToken,
|
|
21
|
+
VocalUint64,
|
|
22
|
+
} from './public_types_vocal_edit';
|
|
23
|
+
import type { SonareModule } from './sonare.js';
|
|
24
|
+
|
|
25
|
+
/** Native symbols registered by the full vocal-edit WASM binding. */
|
|
26
|
+
export interface VocalWasmExports {
|
|
27
|
+
vocalEditApiVersion: () => number;
|
|
28
|
+
vocalEditAvailable: () => number;
|
|
29
|
+
vocalEditSessionCreate: (samples: Float32Array, sampleRate: number, options: unknown) => number;
|
|
30
|
+
vocalEditSessionRestore: (
|
|
31
|
+
samples: Float32Array,
|
|
32
|
+
sampleRate: number,
|
|
33
|
+
state: Uint8Array,
|
|
34
|
+
options: unknown,
|
|
35
|
+
) => number;
|
|
36
|
+
vocalEditSessionDestroy: (handle: number) => void;
|
|
37
|
+
vocalEditSessionNotes: (handle: number) => VocalNotesResult;
|
|
38
|
+
vocalEditDraftNotes: (handle: number) => VocalNotesResult;
|
|
39
|
+
vocalEditSessionToken: (handle: number) => VocalStateToken;
|
|
40
|
+
vocalEditDraftToken: (handle: number) => VocalStateToken;
|
|
41
|
+
vocalEditSessionAnalysis: (handle: number) => VocalAnalysis;
|
|
42
|
+
vocalEditSessionCapabilities: (handle: number) => VocalCapabilities;
|
|
43
|
+
vocalEditSessionOutputLength: (handle: number) => number;
|
|
44
|
+
vocalEditSessionHistory: (handle: number) => VocalHistoryState;
|
|
45
|
+
vocalEditSessionBeginEdit: (handle: number, revision: VocalUint64) => number;
|
|
46
|
+
vocalEditDraftApply: (
|
|
47
|
+
handle: number,
|
|
48
|
+
generation: VocalUint64,
|
|
49
|
+
operations: readonly VocalEditOperation[],
|
|
50
|
+
) => VocalEditResult;
|
|
51
|
+
vocalEditDraftCommit: (handle: number, revision: VocalUint64) => VocalEditResult;
|
|
52
|
+
vocalEditDraftCancel: (handle: number) => void;
|
|
53
|
+
vocalEditDraftDestroy: (handle: number) => void;
|
|
54
|
+
vocalEditSessionApplyHistory: (
|
|
55
|
+
handle: number,
|
|
56
|
+
revision: VocalUint64,
|
|
57
|
+
redo: boolean,
|
|
58
|
+
) => VocalEditResult;
|
|
59
|
+
vocalEditEvaluatePitch: (handle: number, noteId: number, draft: boolean) => VocalPitchEvaluation;
|
|
60
|
+
vocalEditMapCoordinate: (
|
|
61
|
+
handle: number,
|
|
62
|
+
noteId: number,
|
|
63
|
+
sample: number,
|
|
64
|
+
draft: boolean,
|
|
65
|
+
inverse: boolean,
|
|
66
|
+
) => number;
|
|
67
|
+
vocalEditCaptureSnapshot: (handle: number, draft: boolean) => number;
|
|
68
|
+
vocalEditSnapshotDestroy: (handle: number) => void;
|
|
69
|
+
vocalEditSnapshotOutputLength: (handle: number) => number;
|
|
70
|
+
vocalEditSnapshotRender: (
|
|
71
|
+
handle: number,
|
|
72
|
+
startSample: number,
|
|
73
|
+
endSample: number,
|
|
74
|
+
requestId: VocalUint64,
|
|
75
|
+
) => VocalRenderResult;
|
|
76
|
+
vocalEditRenderJobBegin: (
|
|
77
|
+
handle: number,
|
|
78
|
+
startSample: number,
|
|
79
|
+
endSample: number,
|
|
80
|
+
requestId: VocalUint64,
|
|
81
|
+
) => number;
|
|
82
|
+
vocalEditRenderJobNext: (handle: number) => boolean;
|
|
83
|
+
vocalEditRenderJobFinalize: (handle: number) => VocalRenderResult;
|
|
84
|
+
vocalEditRenderJobAbort: (handle: number) => void;
|
|
85
|
+
vocalEditRenderJobDestroy: (handle: number) => void;
|
|
86
|
+
vocalEditSessionExportState: (handle: number) => Uint8Array;
|
|
87
|
+
vocalEditLastErrorDetail: () => VocalErrorShape & { readonly sequence: VocalUint64 };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface VocalWasmModule extends SonareModule, VocalWasmExports {}
|
|
91
|
+
|
|
92
|
+
type VocalErrorDetail = VocalErrorShape & { readonly sequence: VocalUint64 };
|
|
93
|
+
|
|
94
|
+
const vocalErrorFields = [
|
|
95
|
+
'reason',
|
|
96
|
+
'field',
|
|
97
|
+
'expected',
|
|
98
|
+
'actual',
|
|
99
|
+
'expectedText',
|
|
100
|
+
'actualText',
|
|
101
|
+
] as const satisfies readonly (keyof VocalErrorShape)[];
|
|
102
|
+
|
|
103
|
+
let cachedModuleSource: VocalWasmModule | null = null;
|
|
104
|
+
let cachedVocalModule: VocalWasmModule | null = null;
|
|
105
|
+
|
|
106
|
+
function addVocalErrorDetail(error: unknown, detail: VocalErrorDetail): void {
|
|
107
|
+
if (error === null || typeof error !== 'object') {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const candidate = error as { name?: unknown; code?: unknown } & Record<string, unknown>;
|
|
111
|
+
if (candidate.name !== 'SonareError' || typeof candidate.code !== 'number') {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
for (const field of vocalErrorFields) {
|
|
115
|
+
Object.defineProperty(candidate, field, {
|
|
116
|
+
configurable: true,
|
|
117
|
+
enumerable: true,
|
|
118
|
+
value: detail[field],
|
|
119
|
+
writable: false,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function module(): VocalWasmModule {
|
|
125
|
+
const source = getSonareModule() as VocalWasmModule;
|
|
126
|
+
if (source === cachedModuleSource && cachedVocalModule !== null) {
|
|
127
|
+
return cachedVocalModule;
|
|
128
|
+
}
|
|
129
|
+
const detailReader = (source as Partial<VocalWasmModule>).vocalEditLastErrorDetail;
|
|
130
|
+
if (typeof detailReader !== 'function') {
|
|
131
|
+
cachedModuleSource = source;
|
|
132
|
+
cachedVocalModule = source;
|
|
133
|
+
return source;
|
|
134
|
+
}
|
|
135
|
+
const functionCache = new Map<PropertyKey, (...args: unknown[]) => unknown>();
|
|
136
|
+
const wrapped = new Proxy(source, {
|
|
137
|
+
get(target, property, receiver) {
|
|
138
|
+
const member = Reflect.get(target, property, receiver);
|
|
139
|
+
if (
|
|
140
|
+
property === 'vocalEditLastErrorDetail' ||
|
|
141
|
+
typeof property !== 'string' ||
|
|
142
|
+
!property.startsWith('vocalEdit') ||
|
|
143
|
+
typeof member !== 'function'
|
|
144
|
+
) {
|
|
145
|
+
return member;
|
|
146
|
+
}
|
|
147
|
+
const cached = functionCache.get(property);
|
|
148
|
+
if (cached !== undefined) {
|
|
149
|
+
return cached;
|
|
150
|
+
}
|
|
151
|
+
const wrappedMember = (...args: unknown[]): unknown => {
|
|
152
|
+
let beforeSequence: VocalUint64 | undefined;
|
|
153
|
+
try {
|
|
154
|
+
beforeSequence = detailReader.call(target).sequence;
|
|
155
|
+
} catch {
|
|
156
|
+
// An older module may expose the function but not its complete detail
|
|
157
|
+
// shape. Native errors still follow the generic module conversion.
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
return Reflect.apply(member, target, args);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (beforeSequence !== undefined) {
|
|
163
|
+
try {
|
|
164
|
+
const detail = detailReader.call(target);
|
|
165
|
+
if (detail.sequence !== beforeSequence) {
|
|
166
|
+
addVocalErrorDetail(error, detail);
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
// Keep the original native error if detail decoding is unavailable.
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
functionCache.set(property, wrappedMember);
|
|
176
|
+
return wrappedMember;
|
|
177
|
+
},
|
|
178
|
+
}) as VocalWasmModule;
|
|
179
|
+
cachedModuleSource = source;
|
|
180
|
+
cachedVocalModule = wrapped;
|
|
181
|
+
return wrapped;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function requireHandle(handle: number, subject: string): void {
|
|
185
|
+
if (!Number.isSafeInteger(handle) || handle <= 0) {
|
|
186
|
+
throw new TypeError(`${subject} has already been disposed`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function requireFinite(value: number, field: string): number {
|
|
191
|
+
if (!Number.isFinite(value)) {
|
|
192
|
+
throw new RangeError(`${field} must be finite`);
|
|
193
|
+
}
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function requireSample(value: number, field: string): number {
|
|
198
|
+
requireFinite(value, field);
|
|
199
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
200
|
+
throw new RangeError(`${field} must be a non-negative safe integer`);
|
|
201
|
+
}
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function requireUint32(value: number, field: string): number {
|
|
206
|
+
requireSample(value, field);
|
|
207
|
+
if (value > 0xffffffff) {
|
|
208
|
+
throw new RangeError(`${field} must fit uint32`);
|
|
209
|
+
}
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function requireSampleRate(value: number): number {
|
|
214
|
+
requireSample(value, 'sampleRate');
|
|
215
|
+
if (value < 8000 || value > 384000) {
|
|
216
|
+
throw new RangeError('sampleRate must be an integer in [8000, 384000]');
|
|
217
|
+
}
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function requireToken(value: string | undefined, field: string, fallback = '0'): VocalUint64 {
|
|
222
|
+
const token = value ?? fallback;
|
|
223
|
+
if (typeof token !== 'string' || !/^(0|[1-9]\d*)$/.test(token)) {
|
|
224
|
+
throw new RangeError(`${field} must be a canonical decimal uint64 string`);
|
|
225
|
+
}
|
|
226
|
+
if (token.length > 20 || (token.length === 20 && token > '18446744073709551615')) {
|
|
227
|
+
throw new RangeError(`${field} must fit uint64`);
|
|
228
|
+
}
|
|
229
|
+
return token;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
type RevisionArgument = VocalUint64 | { expectedRevision?: VocalUint64 };
|
|
233
|
+
|
|
234
|
+
function resolveRevision(
|
|
235
|
+
argument: RevisionArgument | undefined,
|
|
236
|
+
fallback: VocalUint64,
|
|
237
|
+
): VocalUint64 {
|
|
238
|
+
const value = typeof argument === 'string' ? argument : argument?.expectedRevision;
|
|
239
|
+
return requireToken(value, 'expectedRevision', fallback);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function requireRange(range: VocalRange): VocalRange {
|
|
243
|
+
if (!range || typeof range !== 'object') {
|
|
244
|
+
throw new TypeError('range must be an object');
|
|
245
|
+
}
|
|
246
|
+
const startSample = requireSample(range.startSample, 'range.startSample');
|
|
247
|
+
const endSample = requireSample(range.endSample, 'range.endSample');
|
|
248
|
+
if (endSample < startSample) {
|
|
249
|
+
throw new RangeError('range.endSample must be >= startSample');
|
|
250
|
+
}
|
|
251
|
+
return { startSample, endSample };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function copyToken(token: VocalStateToken): VocalStateToken {
|
|
255
|
+
return {
|
|
256
|
+
sessionEpoch: requireToken(token.sessionEpoch, 'sessionEpoch'),
|
|
257
|
+
revision: requireToken(token.revision, 'revision'),
|
|
258
|
+
draftId: requireToken(token.draftId, 'draftId'),
|
|
259
|
+
generation: requireToken(token.generation, 'generation'),
|
|
260
|
+
requestId: requireToken(token.requestId, 'requestId'),
|
|
261
|
+
profileId: token.profileId,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function copyRange(range: VocalRange): VocalRange {
|
|
266
|
+
return { startSample: range.startSample, endSample: range.endSample };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function copyNotes(value: VocalNotesResult): VocalNotesResult {
|
|
270
|
+
const notes: VocalNote[] = Array.from(value.notes, (note) => ({
|
|
271
|
+
...note,
|
|
272
|
+
amplitude: new Float32Array(note.amplitude),
|
|
273
|
+
edit: {
|
|
274
|
+
...note.edit,
|
|
275
|
+
pitch: {
|
|
276
|
+
...note.edit.pitch,
|
|
277
|
+
target:
|
|
278
|
+
note.edit.pitch.target.mode === 'curve'
|
|
279
|
+
? {
|
|
280
|
+
mode: 'curve' as const,
|
|
281
|
+
points: note.edit.pitch.target.points.map((point) => ({ ...point })),
|
|
282
|
+
}
|
|
283
|
+
: { ...note.edit.pitch.target },
|
|
284
|
+
},
|
|
285
|
+
amplitudeEnvelope: Array.from(note.edit.amplitudeEnvelope),
|
|
286
|
+
formant: { ...note.edit.formant },
|
|
287
|
+
},
|
|
288
|
+
}));
|
|
289
|
+
return {
|
|
290
|
+
notes,
|
|
291
|
+
transitions: Array.from(value.transitions, (transition) => ({ ...transition })),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function copyAnalysis(value: VocalAnalysis): VocalAnalysis {
|
|
296
|
+
return {
|
|
297
|
+
frameOriginSample: value.frameOriginSample,
|
|
298
|
+
samplesPerFrame: value.samplesPerFrame,
|
|
299
|
+
frameLengthSamples: value.frameLengthSamples,
|
|
300
|
+
f0Hz: new Float32Array(value.f0Hz),
|
|
301
|
+
voiced: new Uint8Array(value.voiced),
|
|
302
|
+
algorithmId: value.algorithmId,
|
|
303
|
+
algorithmVersion: value.algorithmVersion,
|
|
304
|
+
sourceLengthSamples: value.sourceLengthSamples,
|
|
305
|
+
sampleRate: value.sampleRate,
|
|
306
|
+
sourceSha256: value.sourceSha256,
|
|
307
|
+
analysisSha256: value.analysisSha256,
|
|
308
|
+
fminHz: value.fminHz,
|
|
309
|
+
fmaxHz: value.fmaxHz,
|
|
310
|
+
yinThreshold: value.yinThreshold,
|
|
311
|
+
voicedThreshold: value.voicedThreshold,
|
|
312
|
+
centered: value.centered,
|
|
313
|
+
segmentationThresholdCents: value.segmentationThresholdCents,
|
|
314
|
+
minNoteMs: value.minNoteMs,
|
|
315
|
+
referenceHz: value.referenceHz,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function copyPitch(value: VocalPitchEvaluation): VocalPitchEvaluation {
|
|
320
|
+
return {
|
|
321
|
+
sourceSamples: new Float64Array(value.sourceSamples),
|
|
322
|
+
measuredMidi: new Float64Array(value.measuredMidi),
|
|
323
|
+
targetMidi: new Float64Array(value.targetMidi),
|
|
324
|
+
effectiveMidi: new Float64Array(value.effectiveMidi),
|
|
325
|
+
voiced: new Uint8Array(value.voiced),
|
|
326
|
+
hasTarget: new Uint8Array(value.hasTarget),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function copyRender(value: VocalRenderResult): VocalRenderResult {
|
|
331
|
+
return {
|
|
332
|
+
samples: new Float32Array(value.samples),
|
|
333
|
+
startSample: value.startSample,
|
|
334
|
+
token: copyToken(value.token),
|
|
335
|
+
processedRanges: Array.from(value.processedRanges, copyRange),
|
|
336
|
+
cacheHitUnits: requireToken(value.cacheHitUnits, 'cacheHitUnits'),
|
|
337
|
+
dryPassedFrames: requireToken(value.dryPassedFrames, 'dryPassedFrames'),
|
|
338
|
+
limitedCorrectionFrames: requireToken(value.limitedCorrectionFrames, 'limitedCorrectionFrames'),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function validateSessionLimits(limits: VocalSessionLimits | undefined): void {
|
|
343
|
+
if (limits === undefined) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (limits.maxHistoryBytes !== undefined) {
|
|
347
|
+
requireToken(limits.maxHistoryBytes, 'limits.maxHistoryBytes');
|
|
348
|
+
}
|
|
349
|
+
if (limits.maxCacheBytes !== undefined) {
|
|
350
|
+
requireToken(limits.maxCacheBytes, 'limits.maxCacheBytes');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function copyEditResult(value: VocalEditResult): VocalEditResult {
|
|
355
|
+
return {
|
|
356
|
+
token: copyToken(value.token),
|
|
357
|
+
dirtyRanges: Array.from(value.dirtyRanges, copyRange),
|
|
358
|
+
idChanges: Array.from(value.idChanges, (change) => ({
|
|
359
|
+
operationIndex: change.operationIndex,
|
|
360
|
+
retiredId: change.retiredId,
|
|
361
|
+
newIds: Array.from(change.newIds),
|
|
362
|
+
})),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function validateCreate(request: VocalCreateRequest): void {
|
|
367
|
+
if (!request || typeof request !== 'object') {
|
|
368
|
+
throw new TypeError('request must be an object');
|
|
369
|
+
}
|
|
370
|
+
if (!(request.samples instanceof Float32Array) || request.samples.length === 0) {
|
|
371
|
+
throw new TypeError('samples must be a non-empty Float32Array');
|
|
372
|
+
}
|
|
373
|
+
for (const sample of request.samples) {
|
|
374
|
+
requireFinite(sample, 'samples');
|
|
375
|
+
}
|
|
376
|
+
requireSampleRate(request.sampleRate);
|
|
377
|
+
if (request.outputLengthSamples !== undefined) {
|
|
378
|
+
requireSample(request.outputLengthSamples, 'outputLengthSamples');
|
|
379
|
+
}
|
|
380
|
+
validateSessionLimits(request.limits);
|
|
381
|
+
if (request.analysis) {
|
|
382
|
+
const analysis = request.analysis;
|
|
383
|
+
if (analysis.algorithmId !== undefined) {
|
|
384
|
+
if (typeof analysis.algorithmId !== 'string') {
|
|
385
|
+
throw new TypeError('analysis.algorithmId must be a string');
|
|
386
|
+
}
|
|
387
|
+
if (analysis.algorithmId.includes('\0')) {
|
|
388
|
+
throw new RangeError('analysis.algorithmId must not contain NUL');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
requireFinite(analysis.frameOriginSample, 'analysis.frameOriginSample');
|
|
392
|
+
requireFinite(analysis.samplesPerFrame, 'analysis.samplesPerFrame');
|
|
393
|
+
if (analysis.samplesPerFrame <= 0) {
|
|
394
|
+
throw new RangeError('analysis.samplesPerFrame must be positive');
|
|
395
|
+
}
|
|
396
|
+
requireSample(analysis.frameLengthSamples, 'analysis.frameLengthSamples');
|
|
397
|
+
if (analysis.frameLengthSamples === 0) {
|
|
398
|
+
throw new RangeError('analysis.frameLengthSamples must be positive');
|
|
399
|
+
}
|
|
400
|
+
if (!(analysis.f0Hz instanceof Float32Array) || !(analysis.voiced instanceof Uint8Array)) {
|
|
401
|
+
throw new TypeError('analysis arrays must be Float32Array and Uint8Array');
|
|
402
|
+
}
|
|
403
|
+
if (analysis.f0Hz.length !== analysis.voiced.length) {
|
|
404
|
+
throw new RangeError('analysis arrays must have equal lengths');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function normalizeRequest(
|
|
410
|
+
request: VocalRenderRequest,
|
|
411
|
+
outputLengthSamples: number,
|
|
412
|
+
): {
|
|
413
|
+
range: VocalRange;
|
|
414
|
+
requestId: VocalUint64;
|
|
415
|
+
} {
|
|
416
|
+
const range =
|
|
417
|
+
request.range === undefined
|
|
418
|
+
? { startSample: 0, endSample: requireSample(outputLengthSamples, 'outputLengthSamples') }
|
|
419
|
+
: requireRange(request.range);
|
|
420
|
+
const requestId = requireToken(request.requestId, 'requestId');
|
|
421
|
+
return { range, requestId };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Whether this build has the C vocal-edit surface linked. */
|
|
425
|
+
export function vocalEditAvailable(): boolean {
|
|
426
|
+
const raw = module();
|
|
427
|
+
return typeof raw.vocalEditAvailable === 'function' && raw.vocalEditAvailable() !== 0;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Version of the C vocal-edit ABI exposed by the loaded WASM module. */
|
|
431
|
+
export function vocalEditApiVersion(): number {
|
|
432
|
+
const raw = module();
|
|
433
|
+
return typeof raw.vocalEditApiVersion === 'function' ? raw.vocalEditApiVersion() : 0;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Analyse mono PCM, or adopt a host analysis, and open an editing session. */
|
|
437
|
+
export function createVocalEditSession(request: VocalCreateRequest): VocalEditSession {
|
|
438
|
+
validateCreate(request);
|
|
439
|
+
if (!vocalEditAvailable()) {
|
|
440
|
+
throw new Error('vocal edit is unavailable in this WASM build');
|
|
441
|
+
}
|
|
442
|
+
return new VocalEditSession(
|
|
443
|
+
module().vocalEditSessionCreate(request.samples, request.sampleRate, request),
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Open a new session from an exported state blob and the same source PCM. */
|
|
448
|
+
export function restoreVocalEditSession(request: VocalRestoreRequest): VocalEditSession {
|
|
449
|
+
if (!(request.samples instanceof Float32Array) || request.samples.length === 0) {
|
|
450
|
+
throw new TypeError('samples must be a non-empty Float32Array');
|
|
451
|
+
}
|
|
452
|
+
if (!(request.state instanceof Uint8Array)) {
|
|
453
|
+
throw new TypeError('state must be a Uint8Array');
|
|
454
|
+
}
|
|
455
|
+
for (const sample of request.samples) {
|
|
456
|
+
requireFinite(sample, 'samples');
|
|
457
|
+
}
|
|
458
|
+
requireSampleRate(request.sampleRate);
|
|
459
|
+
validateSessionLimits(request.limits);
|
|
460
|
+
if (!vocalEditAvailable()) {
|
|
461
|
+
throw new Error('vocal edit is unavailable in this WASM build');
|
|
462
|
+
}
|
|
463
|
+
return new VocalEditSession(
|
|
464
|
+
module().vocalEditSessionRestore(request.samples, request.sampleRate, request.state, request),
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/** A vocal editing session. Disposing it also disposes every live draft it began. */
|
|
469
|
+
export class VocalEditSession {
|
|
470
|
+
private handle: number;
|
|
471
|
+
private readonly drafts = new Set<VocalEditDraft>();
|
|
472
|
+
private disposed = false;
|
|
473
|
+
|
|
474
|
+
/** @internal Instances are created by {@link createVocalEditSession} and {@link restoreVocalEditSession}. */
|
|
475
|
+
constructor(handle: number) {
|
|
476
|
+
requireHandle(handle, 'session');
|
|
477
|
+
this.handle = handle;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
dispose(): void {
|
|
481
|
+
if (this.disposed) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
for (const draft of this.drafts) {
|
|
485
|
+
draft.dispose();
|
|
486
|
+
}
|
|
487
|
+
this.drafts.clear();
|
|
488
|
+
module().vocalEditSessionDestroy(this.handle);
|
|
489
|
+
this.handle = 0;
|
|
490
|
+
this.disposed = true;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
delete(): void {
|
|
494
|
+
this.dispose();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
destroy(): void {
|
|
498
|
+
this.dispose();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private native(): number {
|
|
502
|
+
if (this.disposed) {
|
|
503
|
+
throw new TypeError('session has already been disposed');
|
|
504
|
+
}
|
|
505
|
+
requireHandle(this.handle, 'session');
|
|
506
|
+
return this.handle;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
token(): VocalStateToken {
|
|
510
|
+
return copyToken(module().vocalEditSessionToken(this.native()));
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
revision(): VocalUint64 {
|
|
514
|
+
return this.token().revision;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
notes(): VocalNotesResult {
|
|
518
|
+
return copyNotes(module().vocalEditSessionNotes(this.native()));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
analysis(): VocalAnalysis {
|
|
522
|
+
return copyAnalysis(module().vocalEditSessionAnalysis(this.native()));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
capabilities(): VocalCapabilities {
|
|
526
|
+
return { ...module().vocalEditSessionCapabilities(this.native()) };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
outputLengthSamples(): number {
|
|
530
|
+
return requireSample(
|
|
531
|
+
module().vocalEditSessionOutputLength(this.native()),
|
|
532
|
+
'outputLengthSamples',
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
history(): VocalHistoryState {
|
|
537
|
+
const value = module().vocalEditSessionHistory(this.native());
|
|
538
|
+
return { canUndo: value.canUndo === true, canRedo: value.canRedo === true };
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
beginEdit(options: RevisionArgument = {}): VocalEditDraft {
|
|
542
|
+
const draft = new VocalEditDraft(
|
|
543
|
+
module().vocalEditSessionBeginEdit(this.native(), resolveRevision(options, this.revision())),
|
|
544
|
+
this,
|
|
545
|
+
);
|
|
546
|
+
this.drafts.add(draft);
|
|
547
|
+
return draft;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
undo(options: RevisionArgument = {}): VocalEditResult {
|
|
551
|
+
return copyEditResult(
|
|
552
|
+
module().vocalEditSessionApplyHistory(
|
|
553
|
+
this.native(),
|
|
554
|
+
resolveRevision(options, this.revision()),
|
|
555
|
+
false,
|
|
556
|
+
),
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
redo(options: RevisionArgument = {}): VocalEditResult {
|
|
561
|
+
return copyEditResult(
|
|
562
|
+
module().vocalEditSessionApplyHistory(
|
|
563
|
+
this.native(),
|
|
564
|
+
resolveRevision(options, this.revision()),
|
|
565
|
+
true,
|
|
566
|
+
),
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
evaluatePitch(noteId: number): VocalPitchEvaluation {
|
|
571
|
+
return copyPitch(
|
|
572
|
+
module().vocalEditEvaluatePitch(this.native(), requireUint32(noteId, 'noteId'), false),
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
sourceSampleToDestinationSample(noteId: number, sourceSample: number): number {
|
|
577
|
+
requireFinite(sourceSample, 'sourceSample');
|
|
578
|
+
return module().vocalEditMapCoordinate(
|
|
579
|
+
this.native(),
|
|
580
|
+
requireUint32(noteId, 'noteId'),
|
|
581
|
+
sourceSample,
|
|
582
|
+
false,
|
|
583
|
+
false,
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
destinationSampleToSourceSample(noteId: number, destinationSample: number): number {
|
|
588
|
+
requireFinite(destinationSample, 'destinationSample');
|
|
589
|
+
return module().vocalEditMapCoordinate(
|
|
590
|
+
this.native(),
|
|
591
|
+
requireUint32(noteId, 'noteId'),
|
|
592
|
+
destinationSample,
|
|
593
|
+
false,
|
|
594
|
+
true,
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
captureRenderSnapshot(): VocalRenderSnapshot {
|
|
599
|
+
return new VocalRenderSnapshot(module().vocalEditCaptureSnapshot(this.native(), false));
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
exportState(): Uint8Array {
|
|
603
|
+
return new Uint8Array(module().vocalEditSessionExportState(this.native()));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** @internal Used by the draft to remove itself from the owner set. */
|
|
607
|
+
_forgetDraft(draft: VocalEditDraft): void {
|
|
608
|
+
this.drafts.delete(draft);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export class VocalEditDraft {
|
|
613
|
+
private handle: number;
|
|
614
|
+
private disposed = false;
|
|
615
|
+
private readonly owner: VocalEditSession;
|
|
616
|
+
|
|
617
|
+
/** @internal Instances are created by {@link VocalEditSession.beginEdit}. */
|
|
618
|
+
constructor(handle: number, owner: VocalEditSession) {
|
|
619
|
+
requireHandle(handle, 'draft');
|
|
620
|
+
this.handle = handle;
|
|
621
|
+
this.owner = owner;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private native(): number {
|
|
625
|
+
if (this.disposed) {
|
|
626
|
+
throw new TypeError('draft has already been disposed');
|
|
627
|
+
}
|
|
628
|
+
requireHandle(this.handle, 'draft');
|
|
629
|
+
return this.handle;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
token(): VocalStateToken {
|
|
633
|
+
return copyToken(module().vocalEditDraftToken(this.native()));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
notes(): VocalNotesResult {
|
|
637
|
+
return copyNotes(module().vocalEditDraftNotes(this.native()));
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
apply(request: VocalApplyRequest): VocalEditResult {
|
|
641
|
+
if (request.expectedGeneration === undefined) {
|
|
642
|
+
throw new TypeError('expectedGeneration is required');
|
|
643
|
+
}
|
|
644
|
+
const generation = requireToken(request.expectedGeneration, 'expectedGeneration');
|
|
645
|
+
if (!Array.isArray(request.operations)) {
|
|
646
|
+
throw new TypeError('operations must be an array');
|
|
647
|
+
}
|
|
648
|
+
return copyEditResult(
|
|
649
|
+
module().vocalEditDraftApply(this.native(), generation, request.operations),
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
commit(options: RevisionArgument = {}): VocalEditResult {
|
|
654
|
+
const result = copyEditResult(
|
|
655
|
+
module().vocalEditDraftCommit(this.native(), resolveRevision(options, this.owner.revision())),
|
|
656
|
+
);
|
|
657
|
+
this.dispose();
|
|
658
|
+
return result;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
cancel(): void {
|
|
662
|
+
if (this.disposed) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
module().vocalEditDraftCancel(this.native());
|
|
666
|
+
this.dispose();
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
evaluatePitch(noteId: number): VocalPitchEvaluation {
|
|
670
|
+
return copyPitch(
|
|
671
|
+
module().vocalEditEvaluatePitch(this.native(), requireUint32(noteId, 'noteId'), true),
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
sourceSampleToDestinationSample(noteId: number, sourceSample: number): number {
|
|
676
|
+
requireFinite(sourceSample, 'sourceSample');
|
|
677
|
+
return module().vocalEditMapCoordinate(
|
|
678
|
+
this.native(),
|
|
679
|
+
requireUint32(noteId, 'noteId'),
|
|
680
|
+
sourceSample,
|
|
681
|
+
true,
|
|
682
|
+
false,
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
destinationSampleToSourceSample(noteId: number, destinationSample: number): number {
|
|
687
|
+
requireFinite(destinationSample, 'destinationSample');
|
|
688
|
+
return module().vocalEditMapCoordinate(
|
|
689
|
+
this.native(),
|
|
690
|
+
requireUint32(noteId, 'noteId'),
|
|
691
|
+
destinationSample,
|
|
692
|
+
true,
|
|
693
|
+
true,
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
captureRenderSnapshot(): VocalRenderSnapshot {
|
|
698
|
+
return new VocalRenderSnapshot(module().vocalEditCaptureSnapshot(this.native(), true));
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
dispose(): void {
|
|
702
|
+
if (this.disposed) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
module().vocalEditDraftDestroy(this.handle);
|
|
706
|
+
this.handle = 0;
|
|
707
|
+
this.disposed = true;
|
|
708
|
+
this.owner._forgetDraft(this);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
delete(): void {
|
|
712
|
+
this.dispose();
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
destroy(): void {
|
|
716
|
+
this.dispose();
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export class VocalRenderSnapshot {
|
|
721
|
+
private handle: number;
|
|
722
|
+
private disposed = false;
|
|
723
|
+
|
|
724
|
+
/** @internal Instances are created by a session or draft. */
|
|
725
|
+
constructor(handle: number) {
|
|
726
|
+
requireHandle(handle, 'snapshot');
|
|
727
|
+
this.handle = handle;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
private native(): number {
|
|
731
|
+
if (this.disposed) {
|
|
732
|
+
throw new TypeError('snapshot has already been disposed');
|
|
733
|
+
}
|
|
734
|
+
requireHandle(this.handle, 'snapshot');
|
|
735
|
+
return this.handle;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
outputLengthSamples(): number {
|
|
739
|
+
return requireSample(
|
|
740
|
+
module().vocalEditSnapshotOutputLength(this.native()),
|
|
741
|
+
'outputLengthSamples',
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
render(request: VocalRenderRequest): VocalRenderResult {
|
|
746
|
+
const handle = this.native();
|
|
747
|
+
if (request.signal?.aborted) {
|
|
748
|
+
throw new DOMException('The render was aborted', 'AbortError');
|
|
749
|
+
}
|
|
750
|
+
const normalized = normalizeRequest(request, this.outputLengthSamples());
|
|
751
|
+
return copyRender(
|
|
752
|
+
module().vocalEditSnapshotRender(
|
|
753
|
+
handle,
|
|
754
|
+
normalized.range.startSample,
|
|
755
|
+
normalized.range.endSample,
|
|
756
|
+
normalized.requestId,
|
|
757
|
+
),
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
beginRenderJob(request: VocalRenderRequest): VocalRenderJob {
|
|
762
|
+
const handle = this.native();
|
|
763
|
+
if (request.signal?.aborted) {
|
|
764
|
+
throw new DOMException('The render was aborted', 'AbortError');
|
|
765
|
+
}
|
|
766
|
+
const normalized = normalizeRequest(request, this.outputLengthSamples());
|
|
767
|
+
return new VocalRenderJob(
|
|
768
|
+
module().vocalEditRenderJobBegin(
|
|
769
|
+
handle,
|
|
770
|
+
normalized.range.startSample,
|
|
771
|
+
normalized.range.endSample,
|
|
772
|
+
normalized.requestId,
|
|
773
|
+
),
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Render one native work unit per macrotask so `request.signal` can stop the
|
|
779
|
+
* job between units. A cancelled job publishes no partial PCM.
|
|
780
|
+
*/
|
|
781
|
+
renderAsync(request: VocalRenderRequest): Promise<VocalRenderResult> {
|
|
782
|
+
this.native();
|
|
783
|
+
const signal = request.signal;
|
|
784
|
+
if (signal?.aborted) {
|
|
785
|
+
return Promise.reject(new DOMException('The render was aborted', 'AbortError'));
|
|
786
|
+
}
|
|
787
|
+
const job = this.beginRenderJob(request);
|
|
788
|
+
return (async () => {
|
|
789
|
+
try {
|
|
790
|
+
while (true) {
|
|
791
|
+
if (signal?.aborted) {
|
|
792
|
+
job.abort();
|
|
793
|
+
throw new DOMException('The render was aborted', 'AbortError');
|
|
794
|
+
}
|
|
795
|
+
if (job.next().complete) {
|
|
796
|
+
return job.finalize();
|
|
797
|
+
}
|
|
798
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
799
|
+
}
|
|
800
|
+
} catch (error) {
|
|
801
|
+
job.abort();
|
|
802
|
+
throw error;
|
|
803
|
+
} finally {
|
|
804
|
+
job.dispose();
|
|
805
|
+
}
|
|
806
|
+
})();
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
dispose(): void {
|
|
810
|
+
if (this.disposed) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
module().vocalEditSnapshotDestroy(this.handle);
|
|
814
|
+
this.handle = 0;
|
|
815
|
+
this.disposed = true;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
delete(): void {
|
|
819
|
+
this.dispose();
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
destroy(): void {
|
|
823
|
+
this.dispose();
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
export class VocalRenderJob {
|
|
828
|
+
private handle: number;
|
|
829
|
+
private disposed = false;
|
|
830
|
+
|
|
831
|
+
/** @internal Instances are created by {@link VocalRenderSnapshot.beginRenderJob}. */
|
|
832
|
+
constructor(handle: number) {
|
|
833
|
+
requireHandle(handle, 'render job');
|
|
834
|
+
this.handle = handle;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
private native(): number {
|
|
838
|
+
if (this.disposed) {
|
|
839
|
+
throw new TypeError('render job has already been disposed');
|
|
840
|
+
}
|
|
841
|
+
requireHandle(this.handle, 'render job');
|
|
842
|
+
return this.handle;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
next(): VocalJobProgress {
|
|
846
|
+
return { complete: module().vocalEditRenderJobNext(this.native()) };
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
finalize(): VocalRenderResult {
|
|
850
|
+
const result = copyRender(module().vocalEditRenderJobFinalize(this.native()));
|
|
851
|
+
this.dispose();
|
|
852
|
+
return result;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
abort(): void {
|
|
856
|
+
if (!this.disposed) {
|
|
857
|
+
module().vocalEditRenderJobAbort(this.handle);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
dispose(): void {
|
|
862
|
+
if (this.disposed) {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
module().vocalEditRenderJobDestroy(this.handle);
|
|
866
|
+
this.handle = 0;
|
|
867
|
+
this.disposed = true;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
delete(): void {
|
|
871
|
+
this.dispose();
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
destroy(): void {
|
|
875
|
+
this.dispose();
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
export type {
|
|
880
|
+
VocalAnalysis,
|
|
881
|
+
VocalCapabilities,
|
|
882
|
+
VocalCreateRequest,
|
|
883
|
+
VocalEditResult,
|
|
884
|
+
VocalHistory,
|
|
885
|
+
} from './public_types_vocal_edit';
|