@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,206 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
VocalAnalysis,
|
|
3
|
+
VocalApplyRequest,
|
|
4
|
+
VocalCapabilities,
|
|
5
|
+
VocalCreateRequest,
|
|
6
|
+
VocalEditResult,
|
|
7
|
+
VocalHistoryState,
|
|
8
|
+
VocalNotesResult,
|
|
9
|
+
VocalPitchEvaluation,
|
|
10
|
+
VocalRenderRequest,
|
|
11
|
+
VocalRenderResult,
|
|
12
|
+
VocalRestoreRequest,
|
|
13
|
+
VocalStateBytes,
|
|
14
|
+
VocalStateToken,
|
|
15
|
+
VocalUint64,
|
|
16
|
+
} from './public_types_vocal_edit';
|
|
17
|
+
|
|
18
|
+
export type VocalWorkerMutation =
|
|
19
|
+
| { kind: 'beginEdit'; expectedRevision?: VocalUint64 }
|
|
20
|
+
| { kind: 'apply'; request: VocalApplyRequest }
|
|
21
|
+
| { kind: 'commit'; expectedRevision: VocalUint64 }
|
|
22
|
+
| { kind: 'cancel' }
|
|
23
|
+
| { kind: 'undo'; expectedRevision?: VocalUint64 }
|
|
24
|
+
| { kind: 'redo'; expectedRevision?: VocalUint64 }
|
|
25
|
+
| { kind: 'notes' }
|
|
26
|
+
| { kind: 'analysis' }
|
|
27
|
+
| { kind: 'capabilities' }
|
|
28
|
+
| { kind: 'exportState' }
|
|
29
|
+
| { kind: 'outputLength' }
|
|
30
|
+
| { kind: 'history' }
|
|
31
|
+
| { kind: 'draftToken' }
|
|
32
|
+
| { kind: 'evaluatePitch'; noteId: number; draft?: boolean }
|
|
33
|
+
| {
|
|
34
|
+
kind: 'mapCoordinate';
|
|
35
|
+
noteId: number;
|
|
36
|
+
sample: number;
|
|
37
|
+
/** `false` maps source to destination; `true` maps destination to source. */
|
|
38
|
+
inverse: boolean;
|
|
39
|
+
draft?: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type VocalWorkerCreateRequest =
|
|
43
|
+
| { kind: 'create'; request: VocalCreateRequest }
|
|
44
|
+
| { kind: 'restore'; request: VocalRestoreRequest };
|
|
45
|
+
|
|
46
|
+
export interface VocalWorkerCreateMessage {
|
|
47
|
+
type: 'sonare:vocal-create';
|
|
48
|
+
id: number;
|
|
49
|
+
sessionId: string;
|
|
50
|
+
clientIntentSequence: VocalUint64;
|
|
51
|
+
request: VocalWorkerCreateRequest;
|
|
52
|
+
cancelBuffer?: SharedArrayBuffer;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface VocalWorkerMutateMessage {
|
|
56
|
+
type: 'sonare:vocal-mutate';
|
|
57
|
+
id: number;
|
|
58
|
+
sessionId: string;
|
|
59
|
+
clientIntentSequence: VocalUint64;
|
|
60
|
+
baseToken?: VocalStateToken;
|
|
61
|
+
mutation: VocalWorkerMutation;
|
|
62
|
+
cancelBuffer?: SharedArrayBuffer;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface VocalWorkerPreviewMessage {
|
|
66
|
+
type: 'sonare:vocal-preview';
|
|
67
|
+
id: number;
|
|
68
|
+
sessionId: string;
|
|
69
|
+
clientIntentSequence: VocalUint64;
|
|
70
|
+
baseToken?: VocalStateToken;
|
|
71
|
+
request: VocalRenderRequest;
|
|
72
|
+
cancelBuffer?: SharedArrayBuffer;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface VocalWorkerDisposeMessage {
|
|
76
|
+
type: 'sonare:vocal-dispose';
|
|
77
|
+
id: number;
|
|
78
|
+
sessionId: string;
|
|
79
|
+
clientIntentSequence: VocalUint64;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface VocalWorkerCancelMessage {
|
|
83
|
+
type: 'sonare:vocal-cancel';
|
|
84
|
+
id: number;
|
|
85
|
+
sessionId: string;
|
|
86
|
+
clientIntentSequence: VocalUint64;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type VocalWorkerRequestMessage =
|
|
90
|
+
| VocalWorkerCreateMessage
|
|
91
|
+
| VocalWorkerMutateMessage
|
|
92
|
+
| VocalWorkerPreviewMessage
|
|
93
|
+
| VocalWorkerDisposeMessage
|
|
94
|
+
| VocalWorkerCancelMessage;
|
|
95
|
+
|
|
96
|
+
export interface VocalWorkerCreateResult {
|
|
97
|
+
sessionId: string;
|
|
98
|
+
token: VocalStateToken;
|
|
99
|
+
notes: VocalNotesResult;
|
|
100
|
+
analysis: VocalAnalysis;
|
|
101
|
+
capabilities: VocalCapabilities;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface VocalWorkerResponseBase {
|
|
105
|
+
id: number;
|
|
106
|
+
sessionId: string;
|
|
107
|
+
clientIntentSequence: VocalUint64;
|
|
108
|
+
token?: VocalStateToken;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type VocalWorkerResult =
|
|
112
|
+
| VocalWorkerCreateResult
|
|
113
|
+
| VocalEditResult
|
|
114
|
+
| VocalNotesResult
|
|
115
|
+
| VocalAnalysis
|
|
116
|
+
| VocalCapabilities
|
|
117
|
+
| VocalStateBytes
|
|
118
|
+
| VocalRenderResult
|
|
119
|
+
| VocalStateToken
|
|
120
|
+
| VocalHistoryState
|
|
121
|
+
| VocalPitchEvaluation
|
|
122
|
+
| number
|
|
123
|
+
| null;
|
|
124
|
+
|
|
125
|
+
export interface VocalWorkerResultMessage extends VocalWorkerResponseBase {
|
|
126
|
+
type: 'sonare:vocal-result';
|
|
127
|
+
result: VocalWorkerResult;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface VocalWorkerProgressMessage extends VocalWorkerResponseBase {
|
|
131
|
+
type: 'sonare:vocal-progress';
|
|
132
|
+
complete: boolean;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface VocalWorkerErrorMessage extends VocalWorkerResponseBase {
|
|
136
|
+
type: 'sonare:vocal-error';
|
|
137
|
+
error: {
|
|
138
|
+
name: string;
|
|
139
|
+
message: string;
|
|
140
|
+
code?: number;
|
|
141
|
+
codeName?: string;
|
|
142
|
+
reason?: number;
|
|
143
|
+
field?: string;
|
|
144
|
+
expected?: VocalUint64;
|
|
145
|
+
actual?: VocalUint64;
|
|
146
|
+
expectedText?: string;
|
|
147
|
+
actualText?: string;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export type VocalWorkerResponseMessage =
|
|
152
|
+
| VocalWorkerResultMessage
|
|
153
|
+
| VocalWorkerProgressMessage
|
|
154
|
+
| VocalWorkerErrorMessage;
|
|
155
|
+
|
|
156
|
+
export function tokenEquals(
|
|
157
|
+
a: VocalStateToken | undefined,
|
|
158
|
+
b: VocalStateToken | undefined,
|
|
159
|
+
): boolean {
|
|
160
|
+
if (!a || !b) {
|
|
161
|
+
return a === b;
|
|
162
|
+
}
|
|
163
|
+
return (
|
|
164
|
+
a.sessionEpoch === b.sessionEpoch &&
|
|
165
|
+
a.revision === b.revision &&
|
|
166
|
+
a.draftId === b.draftId &&
|
|
167
|
+
a.generation === b.generation &&
|
|
168
|
+
a.requestId === b.requestId &&
|
|
169
|
+
a.profileId === b.profileId
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Compare the mutable session state portion of a token.
|
|
174
|
+
*
|
|
175
|
+
* Render results add request/profile metadata to the state token. That
|
|
176
|
+
* metadata identifies the render artifact, so it must not make a snapshot's
|
|
177
|
+
* state appear different from the session state used to create it.
|
|
178
|
+
*/
|
|
179
|
+
export function stateTokenEquals(
|
|
180
|
+
a: VocalStateToken | undefined,
|
|
181
|
+
b: VocalStateToken | undefined,
|
|
182
|
+
): boolean {
|
|
183
|
+
if (!a || !b) {
|
|
184
|
+
return a === b;
|
|
185
|
+
}
|
|
186
|
+
return (
|
|
187
|
+
a.sessionEpoch === b.sessionEpoch &&
|
|
188
|
+
a.revision === b.revision &&
|
|
189
|
+
a.draftId === b.draftId &&
|
|
190
|
+
a.generation === b.generation &&
|
|
191
|
+
a.profileId === b.profileId
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Decimal sequence comparison without converting a uint64 to Number. */
|
|
196
|
+
export function compareUint64(a: VocalUint64, b: VocalUint64): number {
|
|
197
|
+
const left = a.replace(/^0+(?=\d)/, '');
|
|
198
|
+
const right = b.replace(/^0+(?=\d)/, '');
|
|
199
|
+
if (left.length !== right.length) {
|
|
200
|
+
return left.length < right.length ? -1 : 1;
|
|
201
|
+
}
|
|
202
|
+
if (left === right) {
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
return left < right ? -1 : 1;
|
|
206
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import type { VocalStateToken, VocalUint64 } from './public_types_vocal_edit';
|
|
2
|
+
import type {
|
|
3
|
+
ProjectVocalEditApplyRequest,
|
|
4
|
+
ProjectVocalEditApplyResult,
|
|
5
|
+
ProjectVocalEditDependency,
|
|
6
|
+
ProjectVocalOriginalSource,
|
|
7
|
+
ProjectVocalReason,
|
|
8
|
+
ProjectVocalRehydrateItem,
|
|
9
|
+
} from './public_types_vocal_project';
|
|
10
|
+
import { assertSampleRate } from './validation';
|
|
11
|
+
|
|
12
|
+
/** The embind Project object is deliberately structural here. */
|
|
13
|
+
export interface VocalProjectNative {
|
|
14
|
+
applyVocalEdit: (request: unknown) => unknown;
|
|
15
|
+
getVocalEditDependencies: () => unknown;
|
|
16
|
+
rehydrateVocalEdits: (originals: unknown, cancel?: unknown) => unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const UINT32_MAX = 0xffff_ffff;
|
|
20
|
+
const PROJECT_ID_MAX = UINT32_MAX - 1;
|
|
21
|
+
const UINT64_MAX = '18446744073709551615';
|
|
22
|
+
|
|
23
|
+
function requireObject(value: unknown, field: string): Record<string, unknown> {
|
|
24
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
25
|
+
throw new TypeError(`${field} must be an object`);
|
|
26
|
+
}
|
|
27
|
+
return value as Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function uint32(value: unknown, field: string, allowZero = true): number {
|
|
31
|
+
if (
|
|
32
|
+
typeof value !== 'number' ||
|
|
33
|
+
!Number.isSafeInteger(value) ||
|
|
34
|
+
value < (allowZero ? 0 : 1) ||
|
|
35
|
+
value > PROJECT_ID_MAX
|
|
36
|
+
) {
|
|
37
|
+
throw new RangeError(`${field} must be a uint32 project id`);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function nativeUint32(value: unknown, field: string): number {
|
|
43
|
+
if (
|
|
44
|
+
typeof value !== 'number' ||
|
|
45
|
+
!Number.isSafeInteger(value) ||
|
|
46
|
+
value < 0 ||
|
|
47
|
+
value > UINT32_MAX
|
|
48
|
+
) {
|
|
49
|
+
throw new RangeError(`${field} must be a native uint32`);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function nativeInt64(value: unknown, field: string): number {
|
|
55
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value)) {
|
|
56
|
+
throw new RangeError(`${field} must be a safe integer`);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function positiveSampleCount(value: unknown, field: string): number {
|
|
62
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0) {
|
|
63
|
+
throw new RangeError(`${field} must be a positive safe integer`);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function finite(value: unknown, field: string): number {
|
|
69
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
70
|
+
throw new RangeError(`${field} must be finite`);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function decimalUint64(value: unknown, field: string): VocalUint64 {
|
|
76
|
+
if (typeof value !== 'string' || !/^(0|[1-9]\d*)$/.test(value)) {
|
|
77
|
+
throw new RangeError(`${field} must be a canonical decimal uint64 string`);
|
|
78
|
+
}
|
|
79
|
+
if (
|
|
80
|
+
value.length > UINT64_MAX.length ||
|
|
81
|
+
(value.length === UINT64_MAX.length && value > UINT64_MAX)
|
|
82
|
+
) {
|
|
83
|
+
throw new RangeError(`${field} must fit uint64`);
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hexSha256(value: unknown, field: string): string {
|
|
89
|
+
if (typeof value !== 'string' || !/^[0-9a-fA-F]{64}$/.test(value)) {
|
|
90
|
+
throw new TypeError(`${field} must be 64 hexadecimal characters`);
|
|
91
|
+
}
|
|
92
|
+
return value.toLowerCase();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function finiteMono(value: unknown, field: string): Float32Array {
|
|
96
|
+
if (!(value instanceof Float32Array) || value.length === 0) {
|
|
97
|
+
throw new TypeError(`${field} must be a non-empty Float32Array`);
|
|
98
|
+
}
|
|
99
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
100
|
+
if (!Number.isFinite(value[index])) {
|
|
101
|
+
throw new RangeError(`${field} must contain only finite samples`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return new Float32Array(value);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function sve1(value: unknown): Uint8Array {
|
|
108
|
+
if (!(value instanceof Uint8Array) || value.length === 0) {
|
|
109
|
+
throw new TypeError('sve1 must be a non-empty Uint8Array');
|
|
110
|
+
}
|
|
111
|
+
return new Uint8Array(value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function copyToken(value: unknown): VocalStateToken {
|
|
115
|
+
const source = requireObject(value, 'renderToken');
|
|
116
|
+
return {
|
|
117
|
+
sessionEpoch: decimalUint64(source.sessionEpoch, 'renderToken.sessionEpoch'),
|
|
118
|
+
revision: decimalUint64(source.revision, 'renderToken.revision'),
|
|
119
|
+
draftId: decimalUint64(source.draftId, 'renderToken.draftId'),
|
|
120
|
+
generation: decimalUint64(source.generation, 'renderToken.generation'),
|
|
121
|
+
requestId: decimalUint64(source.requestId, 'renderToken.requestId'),
|
|
122
|
+
profileId: uint32(source.profileId, 'renderToken.profileId'),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function copyApplyRequest(request: ProjectVocalEditApplyRequest): ProjectVocalEditApplyRequest {
|
|
127
|
+
const source = requireObject(request, 'request');
|
|
128
|
+
const renderedMono = finiteMono(source.renderedMono, 'renderedMono');
|
|
129
|
+
const expectedSourceSampleCount = positiveSampleCount(
|
|
130
|
+
source.expectedSourceSampleCount,
|
|
131
|
+
'expectedSourceSampleCount',
|
|
132
|
+
);
|
|
133
|
+
if (renderedMono.length !== expectedSourceSampleCount) {
|
|
134
|
+
throw new RangeError(
|
|
135
|
+
`renderedMono length must equal expectedSourceSampleCount (${renderedMono.length} != ${expectedSourceSampleCount})`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
const expectedSourceSampleRate = finite(
|
|
139
|
+
source.expectedSourceSampleRate,
|
|
140
|
+
'expectedSourceSampleRate',
|
|
141
|
+
);
|
|
142
|
+
assertSampleRate('Project.applyVocalEdit', expectedSourceSampleRate);
|
|
143
|
+
const renderedSampleRate = finite(source.renderedSampleRate, 'renderedSampleRate');
|
|
144
|
+
assertSampleRate('Project.applyVocalEdit', renderedSampleRate, 'renderedSampleRate');
|
|
145
|
+
if (renderedSampleRate !== expectedSourceSampleRate) {
|
|
146
|
+
throw new RangeError('renderedSampleRate must equal expectedSourceSampleRate');
|
|
147
|
+
}
|
|
148
|
+
const renderedStartSample = source.renderedStartSample ?? 0;
|
|
149
|
+
if (renderedStartSample !== 0) {
|
|
150
|
+
throw new RangeError('renderedStartSample must be zero for the v1 Project vocal API');
|
|
151
|
+
}
|
|
152
|
+
const renderToken = copyToken(source.renderToken);
|
|
153
|
+
if (renderToken.draftId !== '0' || renderToken.generation !== '0') {
|
|
154
|
+
throw new RangeError('renderToken must be a committed session token');
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
clipId: uint32(source.clipId, 'clipId', false),
|
|
158
|
+
takeId: uint32(source.takeId ?? 0, 'takeId'),
|
|
159
|
+
expectedSourceId: uint32(source.expectedSourceId, 'expectedSourceId', false),
|
|
160
|
+
expectedSourceSampleRate,
|
|
161
|
+
expectedSourceSampleCount,
|
|
162
|
+
expectedSourceSha256: hexSha256(source.expectedSourceSha256, 'expectedSourceSha256'),
|
|
163
|
+
expectedClipLengthPpq: finite(source.expectedClipLengthPpq, 'expectedClipLengthPpq'),
|
|
164
|
+
expectedSourceOffsetPpq: finite(source.expectedSourceOffsetPpq, 'expectedSourceOffsetPpq'),
|
|
165
|
+
renderedMono,
|
|
166
|
+
renderedSampleRate,
|
|
167
|
+
renderedStartSample: 0,
|
|
168
|
+
renderToken,
|
|
169
|
+
sve1: sve1(source.sve1),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function copyApplyResult(value: unknown): ProjectVocalEditApplyResult {
|
|
174
|
+
const source = requireObject(value, 'applyVocalEdit result');
|
|
175
|
+
return {
|
|
176
|
+
clipId: uint32(source.clipId, 'result.clipId', false),
|
|
177
|
+
takeId: uint32(source.takeId, 'result.takeId'),
|
|
178
|
+
originalSourceId: uint32(source.originalSourceId, 'result.originalSourceId', false),
|
|
179
|
+
derivedSourceId: uint32(source.derivedSourceId, 'result.derivedSourceId', false),
|
|
180
|
+
committedRevision: decimalUint64(source.committedRevision, 'result.committedRevision'),
|
|
181
|
+
profileId: uint32(source.profileId, 'result.profileId'),
|
|
182
|
+
derivedSourceSha256: hexSha256(source.derivedSourceSha256, 'result.derivedSourceSha256'),
|
|
183
|
+
sidecarKey:
|
|
184
|
+
typeof source.sidecarKey === 'string'
|
|
185
|
+
? source.sidecarKey
|
|
186
|
+
: (() => {
|
|
187
|
+
throw new TypeError('result.sidecarKey must be a string');
|
|
188
|
+
})(),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function copyDependency(value: unknown): ProjectVocalEditDependency {
|
|
193
|
+
const source = requireObject(value, 'vocal edit dependency');
|
|
194
|
+
const reason = nativeUint32(source.reason, 'dependency.reason');
|
|
195
|
+
return {
|
|
196
|
+
clipId: nativeUint32(source.clipId, 'dependency.clipId'),
|
|
197
|
+
takeId: nativeUint32(source.takeId, 'dependency.takeId'),
|
|
198
|
+
originalSourceId: nativeUint32(source.originalSourceId, 'dependency.originalSourceId'),
|
|
199
|
+
derivedSourceId: nativeUint32(source.derivedSourceId, 'dependency.derivedSourceId'),
|
|
200
|
+
sourceSampleRate: nativeUint32(source.sourceSampleRate, 'dependency.sourceSampleRate'),
|
|
201
|
+
profileId: nativeUint32(source.profileId, 'dependency.profileId'),
|
|
202
|
+
sourceSampleCount: nativeInt64(source.sourceSampleCount, 'dependency.sourceSampleCount'),
|
|
203
|
+
committedRevision: decimalUint64(source.committedRevision, 'dependency.committedRevision'),
|
|
204
|
+
originalSourceSha256: hexSha256(source.originalSourceSha256, 'dependency.originalSourceSha256'),
|
|
205
|
+
derivedSourceSha256: hexSha256(source.derivedSourceSha256, 'dependency.derivedSourceSha256'),
|
|
206
|
+
originalPcmAvailable: source.originalPcmAvailable === true,
|
|
207
|
+
derivedPcmAvailable: source.derivedPcmAvailable === true,
|
|
208
|
+
reason: reason as ProjectVocalReason | number,
|
|
209
|
+
sidecarKey:
|
|
210
|
+
typeof source.sidecarKey === 'string'
|
|
211
|
+
? source.sidecarKey
|
|
212
|
+
: (() => {
|
|
213
|
+
throw new TypeError('dependency.sidecarKey must be a string');
|
|
214
|
+
})(),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function copyRehydrateItem(value: unknown): ProjectVocalRehydrateItem {
|
|
219
|
+
const source = requireObject(value, 'vocal rehydrate item');
|
|
220
|
+
const status = source.status;
|
|
221
|
+
if (typeof status !== 'number' || !Number.isSafeInteger(status) || status < 0 || status > 2) {
|
|
222
|
+
throw new RangeError('rehydrate.status must be one of 0, 1, or 2');
|
|
223
|
+
}
|
|
224
|
+
const reason = nativeUint32(source.reason, 'rehydrate.reason');
|
|
225
|
+
return {
|
|
226
|
+
clipId: nativeUint32(source.clipId, 'rehydrate.clipId'),
|
|
227
|
+
takeId: nativeUint32(source.takeId, 'rehydrate.takeId'),
|
|
228
|
+
derivedSourceId: nativeUint32(source.derivedSourceId, 'rehydrate.derivedSourceId'),
|
|
229
|
+
status: status as ProjectVocalRehydrateItem['status'],
|
|
230
|
+
reason: reason as ProjectVocalReason | number,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function copyOriginalSources(
|
|
235
|
+
originals: readonly ProjectVocalOriginalSource[],
|
|
236
|
+
): ProjectVocalOriginalSource[] {
|
|
237
|
+
if (!Array.isArray(originals)) {
|
|
238
|
+
throw new TypeError('originals must be an array');
|
|
239
|
+
}
|
|
240
|
+
const seen = new Set<number>();
|
|
241
|
+
return originals.map((original, index) => {
|
|
242
|
+
const source = requireObject(original, `originals[${index}]`);
|
|
243
|
+
const sourceId = uint32(source.sourceId, `originals[${index}].sourceId`, false);
|
|
244
|
+
if (seen.has(sourceId)) {
|
|
245
|
+
throw new RangeError(`originals contains duplicate sourceId ${sourceId}`);
|
|
246
|
+
}
|
|
247
|
+
seen.add(sourceId);
|
|
248
|
+
const sampleRate = finite(source.sampleRate, `originals[${index}].sampleRate`);
|
|
249
|
+
assertSampleRate('Project.rehydrateVocalEdits', sampleRate, `originals[${index}].sampleRate`);
|
|
250
|
+
return {
|
|
251
|
+
sourceId,
|
|
252
|
+
mono: finiteMono(source.mono, `originals[${index}].mono`),
|
|
253
|
+
sampleRate,
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Apply a complete committed vocal render to a native Project object. */
|
|
259
|
+
export function projectApplyVocalEdit(
|
|
260
|
+
native: VocalProjectNative,
|
|
261
|
+
request: ProjectVocalEditApplyRequest,
|
|
262
|
+
): ProjectVocalEditApplyResult {
|
|
263
|
+
const normalized = copyApplyRequest(request);
|
|
264
|
+
return copyApplyResult(native.applyVocalEdit(normalized));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Return copied vocal sidecar dependencies in native storage order. */
|
|
268
|
+
export function projectGetVocalEditDependencies(
|
|
269
|
+
native: VocalProjectNative,
|
|
270
|
+
): ProjectVocalEditDependency[] {
|
|
271
|
+
const value = native.getVocalEditDependencies();
|
|
272
|
+
if (!Array.isArray(value)) {
|
|
273
|
+
throw new TypeError('native vocal dependencies must be an array');
|
|
274
|
+
}
|
|
275
|
+
return value.map(copyDependency);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Rehydrate unresolved vocal PCM without creating a Project history entry.
|
|
280
|
+
* Any truthy return from `cancel` cancels the call.
|
|
281
|
+
*/
|
|
282
|
+
export function projectRehydrateVocalEdits(
|
|
283
|
+
native: VocalProjectNative,
|
|
284
|
+
originals: readonly ProjectVocalOriginalSource[],
|
|
285
|
+
cancel?: () => boolean,
|
|
286
|
+
): ProjectVocalRehydrateItem[] {
|
|
287
|
+
if (cancel !== undefined && typeof cancel !== 'function') {
|
|
288
|
+
throw new TypeError('cancel must be a function');
|
|
289
|
+
}
|
|
290
|
+
const normalized = copyOriginalSources(originals);
|
|
291
|
+
let callbackFailed = false;
|
|
292
|
+
let callbackFailure: unknown;
|
|
293
|
+
const guardedCancel =
|
|
294
|
+
cancel === undefined
|
|
295
|
+
? undefined
|
|
296
|
+
: () => {
|
|
297
|
+
try {
|
|
298
|
+
return cancel();
|
|
299
|
+
} catch (error) {
|
|
300
|
+
callbackFailed = true;
|
|
301
|
+
callbackFailure = error;
|
|
302
|
+
// Stop native work without allowing a JS exception to cross the C ABI.
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
let value: unknown;
|
|
307
|
+
try {
|
|
308
|
+
value = native.rehydrateVocalEdits(normalized, guardedCancel);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
if (callbackFailed) {
|
|
311
|
+
throw callbackFailure;
|
|
312
|
+
}
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
if (callbackFailed) {
|
|
316
|
+
throw callbackFailure;
|
|
317
|
+
}
|
|
318
|
+
// The adapter reports a throwing callback as data instead of throwing
|
|
319
|
+
// through C++ frames, whose destructors a JS exception would skip.
|
|
320
|
+
if (value !== null && typeof value === 'object' && 'cancelFailure' in value) {
|
|
321
|
+
throw (value as { cancelFailure: unknown }).cancelFailure;
|
|
322
|
+
}
|
|
323
|
+
if (!Array.isArray(value)) {
|
|
324
|
+
throw new TypeError('native vocal rehydrate result must be an array');
|
|
325
|
+
}
|
|
326
|
+
return value.map(copyRehydrateItem);
|
|
327
|
+
}
|
package/src/web_midi.ts
CHANGED
|
@@ -31,6 +31,21 @@ export interface WebMidiEngine {
|
|
|
31
31
|
value: number,
|
|
32
32
|
time?: number,
|
|
33
33
|
): void;
|
|
34
|
+
pushMidiInputPitchBend?(group: number, channel: number, bend14: number, time?: number): void;
|
|
35
|
+
pushMidiInputChannelPressure?(
|
|
36
|
+
group: number,
|
|
37
|
+
channel: number,
|
|
38
|
+
pressure: number,
|
|
39
|
+
time?: number,
|
|
40
|
+
): void;
|
|
41
|
+
pushMidiInputPolyPressure?(
|
|
42
|
+
group: number,
|
|
43
|
+
channel: number,
|
|
44
|
+
note: number,
|
|
45
|
+
pressure: number,
|
|
46
|
+
time?: number,
|
|
47
|
+
): void;
|
|
48
|
+
pushMidiInputUmp?(words: Uint32Array | readonly number[], time?: number): void;
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
type MidiInputState = 'connected' | 'disconnected';
|
|
@@ -285,8 +300,8 @@ function dispatchMidiMessage(
|
|
|
285
300
|
return 0;
|
|
286
301
|
}
|
|
287
302
|
const first = data[0];
|
|
288
|
-
if (first > 0xff) {
|
|
289
|
-
|
|
303
|
+
if (first > 0xff || data instanceof Uint32Array) {
|
|
304
|
+
dispatchUmpMessages(
|
|
290
305
|
engine,
|
|
291
306
|
data,
|
|
292
307
|
timestampToSamples?.(event.receivedTime ?? event.timeStamp ?? 0) ?? 0,
|
|
@@ -312,8 +327,12 @@ function dispatchMidiMessage(
|
|
|
312
327
|
}
|
|
313
328
|
|
|
314
329
|
const a = readU7(data, offset);
|
|
315
|
-
|
|
316
|
-
|
|
330
|
+
if (a < 0) {
|
|
331
|
+
return status;
|
|
332
|
+
}
|
|
333
|
+
const oneDataByteMessage = message === 0xc0 || message === 0xd0;
|
|
334
|
+
const b = oneDataByteMessage ? -1 : readU7(data, offset + 1);
|
|
335
|
+
if (!oneDataByteMessage && b < 0) {
|
|
317
336
|
return status;
|
|
318
337
|
}
|
|
319
338
|
|
|
@@ -331,20 +350,56 @@ function dispatchMidiMessage(
|
|
|
331
350
|
}
|
|
332
351
|
} else if (message === 0xb0 && b >= 0) {
|
|
333
352
|
engine.pushMidiInputCc(group, channel, a, b, portTimeSamples);
|
|
353
|
+
} else if (message === 0xa0 && b >= 0) {
|
|
354
|
+
engine.pushMidiInputPolyPressure?.(group, channel, a, b, portTimeSamples);
|
|
355
|
+
} else if (message === 0xd0) {
|
|
356
|
+
engine.pushMidiInputChannelPressure?.(group, channel, a, portTimeSamples);
|
|
357
|
+
} else if (message === 0xe0 && b >= 0) {
|
|
358
|
+
engine.pushMidiInputPitchBend?.(group, channel, a | (b << 7), portTimeSamples);
|
|
334
359
|
}
|
|
335
360
|
|
|
336
361
|
return status;
|
|
337
362
|
}
|
|
338
363
|
|
|
339
|
-
function
|
|
364
|
+
function dispatchUmpMessages(
|
|
340
365
|
engine: WebMidiEngine,
|
|
341
366
|
words: ArrayLike<number>,
|
|
342
367
|
portTimeSamples: number,
|
|
368
|
+
): void {
|
|
369
|
+
let offset = 0;
|
|
370
|
+
while (offset < words.length) {
|
|
371
|
+
const word0 = words[offset] >>> 0;
|
|
372
|
+
const wordCount = umpWordCount(word0 >>> 28);
|
|
373
|
+
if (words.length - offset < wordCount) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const packet = new Uint32Array(wordCount);
|
|
377
|
+
for (let index = 0; index < wordCount; index++) {
|
|
378
|
+
packet[index] = words[offset + index] >>> 0;
|
|
379
|
+
}
|
|
380
|
+
dispatchUmpMessage(engine, packet, portTimeSamples);
|
|
381
|
+
offset += wordCount;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function dispatchUmpMessage(
|
|
386
|
+
engine: WebMidiEngine,
|
|
387
|
+
words: Uint32Array,
|
|
388
|
+
portTimeSamples: number,
|
|
343
389
|
): void {
|
|
344
390
|
const word0 = words[0] >>> 0;
|
|
345
391
|
const messageType = word0 >>> 28;
|
|
346
392
|
const group = (word0 >>> 24) & 0x0f;
|
|
347
393
|
|
|
394
|
+
// Data packets (MT 0x3/0x5) need the owned SysEx path; raw input refuses them.
|
|
395
|
+
if (messageType === 0x3 || messageType === 0x5) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (engine.pushMidiInputUmp) {
|
|
399
|
+
engine.pushMidiInputUmp(words, portTimeSamples);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
348
403
|
if (messageType === 0x2) {
|
|
349
404
|
const status = (word0 >>> 16) & 0xff;
|
|
350
405
|
const message = status & 0xf0;
|
|
@@ -361,6 +416,12 @@ function dispatchUmpMessage(
|
|
|
361
416
|
}
|
|
362
417
|
} else if (message === 0xb0) {
|
|
363
418
|
engine.pushMidiInputCc(group, channel, a, b, portTimeSamples);
|
|
419
|
+
} else if (message === 0xa0) {
|
|
420
|
+
engine.pushMidiInputPolyPressure?.(group, channel, a, b, portTimeSamples);
|
|
421
|
+
} else if (message === 0xd0) {
|
|
422
|
+
engine.pushMidiInputChannelPressure?.(group, channel, a, portTimeSamples);
|
|
423
|
+
} else if (message === 0xe0) {
|
|
424
|
+
engine.pushMidiInputPitchBend?.(group, channel, a | (b << 7), portTimeSamples);
|
|
364
425
|
}
|
|
365
426
|
return;
|
|
366
427
|
}
|
|
@@ -380,10 +441,44 @@ function dispatchUmpMessage(
|
|
|
380
441
|
engine.pushMidiInputNoteOn(group, channel, data1, velocity, portTimeSamples);
|
|
381
442
|
} else if (status === 0xb) {
|
|
382
443
|
engine.pushMidiInputCc(group, channel, data1, (word1 >>> 25) & 0x7f, portTimeSamples);
|
|
444
|
+
} else if (status === 0xa) {
|
|
445
|
+
engine.pushMidiInputPolyPressure?.(
|
|
446
|
+
group,
|
|
447
|
+
channel,
|
|
448
|
+
data1,
|
|
449
|
+
(word1 >>> 25) & 0x7f,
|
|
450
|
+
portTimeSamples,
|
|
451
|
+
);
|
|
452
|
+
} else if (status === 0xd) {
|
|
453
|
+
engine.pushMidiInputChannelPressure?.(group, channel, (word1 >>> 25) & 0x7f, portTimeSamples);
|
|
454
|
+
} else if (status === 0xe) {
|
|
455
|
+
engine.pushMidiInputPitchBend?.(group, channel, word1 >>> 18, portTimeSamples);
|
|
383
456
|
}
|
|
384
457
|
}
|
|
385
458
|
}
|
|
386
459
|
|
|
460
|
+
function umpWordCount(messageType: number): number {
|
|
461
|
+
switch (messageType & 0x0f) {
|
|
462
|
+
case 0x0:
|
|
463
|
+
case 0x1:
|
|
464
|
+
case 0x2:
|
|
465
|
+
case 0x6:
|
|
466
|
+
case 0x7:
|
|
467
|
+
return 1;
|
|
468
|
+
case 0x3:
|
|
469
|
+
case 0x4:
|
|
470
|
+
case 0x8:
|
|
471
|
+
case 0x9:
|
|
472
|
+
case 0xa:
|
|
473
|
+
return 2;
|
|
474
|
+
case 0xb:
|
|
475
|
+
case 0xc:
|
|
476
|
+
return 3;
|
|
477
|
+
default:
|
|
478
|
+
return 4;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
387
482
|
function readU7(data: ArrayLike<number>, index: number): number {
|
|
388
483
|
if (index >= data.length) {
|
|
389
484
|
return -1;
|