@libraz/libsonare 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -19
- package/dist/index.d.ts +5384 -1
- package/dist/index.js +867 -573
- package/dist/index.js.map +1 -1
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +1083 -5227
- package/dist/worklet.js +2677 -2452
- package/dist/worklet.js.map +1 -1
- package/package.json +4 -9
- package/src/clip_page_streamer.ts +250 -0
- package/src/effects_mastering.ts +85 -1089
- package/src/effects_transform.ts +286 -0
- package/src/effects_voice_change.ts +118 -0
- package/src/feature_spectrogram.ts +42 -2
- package/src/features.ts +1 -0
- package/src/index.ts +11 -0
- package/src/mastering_chain.ts +200 -0
- package/src/mastering_core.ts +248 -0
- package/src/mastering_dynamics.ts +105 -0
- package/src/mastering_repair.ts +161 -0
- package/src/mixing_oneshot.ts +54 -0
- package/src/module_state.ts +1 -2
- package/src/project.ts +71 -1712
- package/src/project_class.ts +861 -0
- package/src/project_internal.ts +332 -0
- package/src/project_synth.ts +43 -0
- package/src/project_types.ts +570 -0
- package/src/public_types.ts +6 -1221
- package/src/public_types_acoustic.ts +115 -0
- package/src/public_types_mastering.ts +333 -0
- package/src/public_types_mixing.ts +97 -0
- package/src/public_types_music.ts +352 -0
- package/src/public_types_realtime.ts +163 -0
- package/src/public_types_spectral.ts +194 -0
- package/src/realtime_engine.ts +94 -0
- package/src/sonare.js.d.ts +72 -0
- package/src/worklet/engine-automation.ts +73 -0
- package/src/worklet/engine-capture-facade.ts +80 -0
- package/src/worklet/engine-clips.ts +71 -0
- package/src/worklet/engine-markers.ts +93 -0
- package/src/worklet/engine-mixer-facade.ts +186 -0
- package/src/worklet/engine-node.ts +451 -0
- package/src/worklet/engine-offline.ts +162 -0
- package/src/worklet/engine-options.ts +13 -0
- package/src/worklet/engine-parameter-facade.ts +172 -0
- package/src/worklet/engine-processor.ts +764 -0
- package/src/worklet/engine-register.ts +136 -0
- package/src/worklet/engine-strips.ts +315 -0
- package/src/worklet/engine-sync.ts +94 -0
- package/src/worklet/engine-tempo-facade.ts +141 -0
- package/src/worklet/engine.ts +998 -0
- package/src/worklet/guards.ts +14 -1
- package/src/worklet/messages.ts +60 -20
- package/src/worklet/mixer-processor.ts +368 -0
- package/src/worklet/protocol.ts +3 -0
- package/src/worklet/voice-changer-processor.ts +246 -0
- package/src/worklet.ts +20 -3549
- package/dist/sonare-rt-module.js +0 -2
- package/dist/sonare-rt.js +0 -2
- package/dist/sonare-rt.wasm +0 -0
- package/src/sonare-rt.d.ts +0 -93
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { EngineCaptureStatus, EngineMarker, EngineTrackLane, RealtimeEngine } from '../index';
|
|
2
|
+
import type { SonareEngineSyncCaptureMessage, SonareEngineTransportFacade } from './messages';
|
|
3
|
+
|
|
4
|
+
/** Capture configuration options accepted by the engine's `configureCapture`. */
|
|
5
|
+
export interface CaptureOptions {
|
|
6
|
+
bufferFrames: number;
|
|
7
|
+
channels?: number;
|
|
8
|
+
source?: EngineCaptureStatus['source'];
|
|
9
|
+
recordOffsetSamples?: number;
|
|
10
|
+
inputMonitor?: { enabled: boolean; gain?: number };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Normalizes capture options into the resolved config carried by the
|
|
15
|
+
* `syncCapture` message, applying integer truncation and defaults.
|
|
16
|
+
*
|
|
17
|
+
* @param options Raw capture options.
|
|
18
|
+
* @param defaultChannels Channel count to use when `options.channels` is unset.
|
|
19
|
+
*/
|
|
20
|
+
export function buildCaptureConfig(
|
|
21
|
+
options: CaptureOptions,
|
|
22
|
+
defaultChannels: number,
|
|
23
|
+
): Omit<SonareEngineSyncCaptureMessage, 'type'> {
|
|
24
|
+
return {
|
|
25
|
+
bufferFrames: Math.trunc(options.bufferFrames),
|
|
26
|
+
channels: Math.trunc(options.channels ?? defaultChannels),
|
|
27
|
+
source: options.source ?? 'output',
|
|
28
|
+
recordOffsetSamples: Math.trunc(options.recordOffsetSamples ?? 0),
|
|
29
|
+
inputMonitor: {
|
|
30
|
+
enabled: Boolean(options.inputMonitor?.enabled),
|
|
31
|
+
gain: options.inputMonitor?.gain ?? 1,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Collaborator surface the transport facade needs from the owning engine: the
|
|
38
|
+
* realtime node (sample-accurate command transport), the offline engine it
|
|
39
|
+
* mirrors, and the tempo/loop setters plus playing-state bookkeeping.
|
|
40
|
+
*/
|
|
41
|
+
export interface EngineTransportContext {
|
|
42
|
+
readonly sampleRate: number;
|
|
43
|
+
realtimeNode: {
|
|
44
|
+
play(sampleTime?: number): boolean;
|
|
45
|
+
stop(sampleTime?: number): boolean;
|
|
46
|
+
seekPpq(ppq: number, sampleTime?: number): boolean;
|
|
47
|
+
seekSample(timelineSample: number, sampleTime?: number): boolean;
|
|
48
|
+
};
|
|
49
|
+
offlineEngine: RealtimeEngine;
|
|
50
|
+
setTransportPlaying(playing: boolean): void;
|
|
51
|
+
flushPendingInstrumentSync(): void;
|
|
52
|
+
setTempo(bpm: number): void;
|
|
53
|
+
setTempoSegments(segments: readonly { startPpq: number; bpm: number }[]): void;
|
|
54
|
+
setLoop(startPpq: number, endPpq: number, enabled?: boolean): boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Builds the public transport facade that fans control to both engines. */
|
|
58
|
+
export function buildTransportFacade(ctx: EngineTransportContext): SonareEngineTransportFacade {
|
|
59
|
+
return {
|
|
60
|
+
play: (sampleTime = -1) => {
|
|
61
|
+
const ok = ctx.realtimeNode.play(sampleTime);
|
|
62
|
+
if (ok) {
|
|
63
|
+
ctx.setTransportPlaying(true);
|
|
64
|
+
}
|
|
65
|
+
return ok;
|
|
66
|
+
},
|
|
67
|
+
stop: (sampleTime = -1) => {
|
|
68
|
+
const ok = ctx.realtimeNode.stop(sampleTime);
|
|
69
|
+
if (ok) {
|
|
70
|
+
ctx.setTransportPlaying(false);
|
|
71
|
+
ctx.flushPendingInstrumentSync();
|
|
72
|
+
}
|
|
73
|
+
return ok;
|
|
74
|
+
},
|
|
75
|
+
seekPpq: (ppq, sampleTime = -1) => {
|
|
76
|
+
ctx.offlineEngine.seekPpq(ppq, sampleTime);
|
|
77
|
+
return ctx.realtimeNode.seekPpq(ppq, sampleTime);
|
|
78
|
+
},
|
|
79
|
+
seekSeconds: (seconds, sampleTime = -1) => {
|
|
80
|
+
const timelineSample = Math.max(0, Math.round(seconds * ctx.sampleRate));
|
|
81
|
+
ctx.offlineEngine.seekSample(timelineSample, sampleTime);
|
|
82
|
+
return ctx.realtimeNode.seekSample(timelineSample, sampleTime);
|
|
83
|
+
},
|
|
84
|
+
setTempo: (bpm) => ctx.setTempo(bpm),
|
|
85
|
+
setTempoSegments: (segments) => ctx.setTempoSegments(segments),
|
|
86
|
+
setLoop: (startPpq, endPpq, enabled = true) => ctx.setLoop(startPpq, endPpq, enabled),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Validates and normalizes an append-only mixer-lane declaration.
|
|
92
|
+
*
|
|
93
|
+
* Lane indices are append-only: the new list must start with the already
|
|
94
|
+
* declared lane ids in their current order and may only append new track ids.
|
|
95
|
+
* Returns the normalized lane entries (numbers coerced to descriptors) and the
|
|
96
|
+
* resulting ordered id list.
|
|
97
|
+
*
|
|
98
|
+
* @throws if any track id is invalid, ids are duplicated, or the existing lane
|
|
99
|
+
* order is not preserved.
|
|
100
|
+
*/
|
|
101
|
+
export function normalizeTrackLanes(
|
|
102
|
+
existing: readonly number[],
|
|
103
|
+
lanes: ReadonlyArray<number | EngineTrackLane>,
|
|
104
|
+
): { entries: EngineTrackLane[]; ids: number[] } {
|
|
105
|
+
const entries = lanes.map((lane) => (typeof lane === 'number' ? { trackId: lane } : lane));
|
|
106
|
+
const ids: number[] = [];
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (!Number.isInteger(entry.trackId) || entry.trackId <= 0) {
|
|
109
|
+
throw new Error(`Invalid track id for mixer lane: ${String(entry.trackId)}`);
|
|
110
|
+
}
|
|
111
|
+
ids.push(entry.trackId);
|
|
112
|
+
}
|
|
113
|
+
if (new Set(ids).size !== ids.length) {
|
|
114
|
+
throw new Error('Duplicate track id in mixer lane list');
|
|
115
|
+
}
|
|
116
|
+
for (let index = 0; index < existing.length; index++) {
|
|
117
|
+
if (ids[index] !== existing[index]) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'Mixer lanes are append-only: keep existing lanes in order and only append new track ids',
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { entries, ids };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Resolves a marker set, assigning fresh ids to entries without one and
|
|
128
|
+
* validating explicit ids (positive, unique).
|
|
129
|
+
*
|
|
130
|
+
* @param markers The marker list to resolve.
|
|
131
|
+
* @param nextMarkerId The id counter to draw fresh ids from.
|
|
132
|
+
* @returns The resolved markers and the advanced id counter.
|
|
133
|
+
* @throws on a non-finite ppq, an invalid id, or a duplicate id.
|
|
134
|
+
*/
|
|
135
|
+
export function resolveMarkerSet(
|
|
136
|
+
markers: ReadonlyArray<{ ppq: number; name?: string; id?: number }>,
|
|
137
|
+
nextMarkerId: number,
|
|
138
|
+
): { resolved: EngineMarker[]; nextMarkerId: number } {
|
|
139
|
+
const resolved: EngineMarker[] = [];
|
|
140
|
+
const seen = new Set<number>();
|
|
141
|
+
let counter = nextMarkerId;
|
|
142
|
+
for (const marker of markers) {
|
|
143
|
+
if (!Number.isFinite(marker.ppq)) {
|
|
144
|
+
throw new Error(`Invalid marker ppq: ${String(marker.ppq)}`);
|
|
145
|
+
}
|
|
146
|
+
if (marker.id !== undefined) {
|
|
147
|
+
if (!Number.isInteger(marker.id) || marker.id <= 0) {
|
|
148
|
+
throw new Error(`Invalid marker id: ${String(marker.id)}`);
|
|
149
|
+
}
|
|
150
|
+
if (seen.has(marker.id)) {
|
|
151
|
+
throw new Error(`Duplicate marker id: ${marker.id}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const id = marker.id ?? counter++;
|
|
155
|
+
seen.add(id);
|
|
156
|
+
if (id >= counter) {
|
|
157
|
+
counter = id + 1;
|
|
158
|
+
}
|
|
159
|
+
resolved.push({ id, ppq: marker.ppq, name: marker.name ?? '' });
|
|
160
|
+
}
|
|
161
|
+
return { resolved, nextMarkerId: counter };
|
|
162
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RealtimeEngine } from '../index';
|
|
2
|
+
import type { SonareRealtimeEngineNodeOptions } from './messages';
|
|
3
|
+
|
|
4
|
+
export interface SonareEngineOptions extends SonareRealtimeEngineNodeOptions {
|
|
5
|
+
offlineEngine?: RealtimeEngine;
|
|
6
|
+
offlineBlockSize?: number;
|
|
7
|
+
offlineChannelCount?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type SuspendableAudioContext = BaseAudioContext & {
|
|
11
|
+
suspend?: () => Promise<void>;
|
|
12
|
+
resume?: () => Promise<void>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { EngineParameterInfo, RealtimeEngine } from '../index';
|
|
2
|
+
import type { SonareRealtimeEngineNode } from './engine-node';
|
|
3
|
+
import {
|
|
4
|
+
ENGINE_MIXER_PARAM_FADER_DB,
|
|
5
|
+
ENGINE_MIXER_PARAM_PAN,
|
|
6
|
+
engineMixerBusTarget,
|
|
7
|
+
engineMixerLaneTarget,
|
|
8
|
+
engineMixerMasterTarget,
|
|
9
|
+
SonareEngineCommandType,
|
|
10
|
+
} from './protocol';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Collaborator surface the parameter / automation-id resolution helpers need
|
|
14
|
+
* from the owning {@link SonareEngine}: the offline engine they mirror into and
|
|
15
|
+
* query, the realtime node they command, the lane/bus declaration helpers, and
|
|
16
|
+
* the declared track-lane id store used to resolve insert-automation ids.
|
|
17
|
+
*/
|
|
18
|
+
export interface EngineParameterContext {
|
|
19
|
+
readonly offlineEngine: RealtimeEngine;
|
|
20
|
+
readonly realtimeNode: SonareRealtimeEngineNode;
|
|
21
|
+
readonly trackLaneIds: number[];
|
|
22
|
+
resolveParamId(nodeId: string, param: string | number): number;
|
|
23
|
+
ensureTrackLane(target: string | number): number;
|
|
24
|
+
ensureBus(busId: number): number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function setParam(
|
|
28
|
+
ctx: EngineParameterContext,
|
|
29
|
+
nodeId: string,
|
|
30
|
+
param: string | number,
|
|
31
|
+
value: number,
|
|
32
|
+
): boolean {
|
|
33
|
+
const paramId = ctx.resolveParamId(nodeId, param);
|
|
34
|
+
// Mirror the change into the offline engine so a subsequent offline render
|
|
35
|
+
// reflects the live value, then push a sample-accurate command to the
|
|
36
|
+
// realtime runtime (mirrors setTempo/setLoop above).
|
|
37
|
+
ctx.offlineEngine.setParameter(paramId, value);
|
|
38
|
+
return ctx.realtimeNode.sendCommand({
|
|
39
|
+
type: SonareEngineCommandType.SetParam,
|
|
40
|
+
targetId: paramId,
|
|
41
|
+
sampleTime: -1,
|
|
42
|
+
argFloat: value,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function setSoloMute(
|
|
47
|
+
ctx: EngineParameterContext,
|
|
48
|
+
target: string | number,
|
|
49
|
+
solo: boolean,
|
|
50
|
+
mute: boolean,
|
|
51
|
+
): boolean {
|
|
52
|
+
const laneIndex = ctx.ensureTrackLane(target);
|
|
53
|
+
ctx.offlineEngine.setSoloMute(laneIndex, solo, mute);
|
|
54
|
+
return ctx.realtimeNode.sendCommand({
|
|
55
|
+
type: SonareEngineCommandType.SetSoloMute,
|
|
56
|
+
targetId: laneIndex,
|
|
57
|
+
sampleTime: -1,
|
|
58
|
+
argInt: (mute ? 0x1 : 0) | (solo ? 0x2 : 0),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns the automation target id for a mixer strip parameter.
|
|
64
|
+
*
|
|
65
|
+
* The id addresses the engine's reserved mixer namespace, so it can be fed
|
|
66
|
+
* straight to setAutomationLane to automate a fader or pan without
|
|
67
|
+
* registering a parameter.
|
|
68
|
+
*
|
|
69
|
+
* @param target Track id (declares a mixer lane on first use) or 'master'.
|
|
70
|
+
* @param kind Strip parameter to address.
|
|
71
|
+
* @returns Reserved engine parameter id for the strip parameter.
|
|
72
|
+
*/
|
|
73
|
+
export function automationParamId(
|
|
74
|
+
ctx: EngineParameterContext,
|
|
75
|
+
target: string | number,
|
|
76
|
+
kind: 'faderDb' | 'pan',
|
|
77
|
+
): number {
|
|
78
|
+
const paramKind = kind === 'pan' ? ENGINE_MIXER_PARAM_PAN : ENGINE_MIXER_PARAM_FADER_DB;
|
|
79
|
+
if (target === 'master') {
|
|
80
|
+
return engineMixerMasterTarget(paramKind);
|
|
81
|
+
}
|
|
82
|
+
return engineMixerLaneTarget(ctx.ensureTrackLane(target), paramKind);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Returns the automation target id for a bus fader.
|
|
87
|
+
*
|
|
88
|
+
* @param busId Bus id (declares the mixer bus on first use).
|
|
89
|
+
* @returns Reserved engine parameter id for the bus fader gain (dB).
|
|
90
|
+
*/
|
|
91
|
+
export function busAutomationParamId(ctx: EngineParameterContext, busId: number): number {
|
|
92
|
+
return engineMixerBusTarget(ctx.ensureBus(busId), ENGINE_MIXER_PARAM_FADER_DB);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolves a track-lane insert parameter (JSON-key name) to the reserved
|
|
97
|
+
* insert-automation id fed straight to setAutomationLane. Declares the track's
|
|
98
|
+
* mixer lane first (like automationParamId) so the offline engine resolves the
|
|
99
|
+
* same strip selector the realtime engine uses.
|
|
100
|
+
*
|
|
101
|
+
* @param target Track id (declares a mixer lane on first use).
|
|
102
|
+
* @param insertIndex Index into the strip's combined insert sequence.
|
|
103
|
+
* @param paramName Processor JSON-key parameter name.
|
|
104
|
+
* @returns Reserved insert-automation id, or -1 when strip/insert/key unknown.
|
|
105
|
+
*/
|
|
106
|
+
export function resolveTrackInsertAutomationId(
|
|
107
|
+
ctx: EngineParameterContext,
|
|
108
|
+
target: string | number,
|
|
109
|
+
insertIndex: number,
|
|
110
|
+
paramName: string,
|
|
111
|
+
): number {
|
|
112
|
+
const laneIndex = ctx.ensureTrackLane(target);
|
|
113
|
+
return ctx.offlineEngine.resolveTrackInsertAutomationId(
|
|
114
|
+
ctx.trackLaneIds[laneIndex],
|
|
115
|
+
insertIndex,
|
|
116
|
+
paramName,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolves a master-strip insert parameter to its reserved insert-automation
|
|
122
|
+
* id.
|
|
123
|
+
*
|
|
124
|
+
* @param insertIndex Index into the master strip's insert sequence.
|
|
125
|
+
* @param paramName Processor JSON-key parameter name.
|
|
126
|
+
* @returns Reserved insert-automation id, or -1 when insert/key unknown.
|
|
127
|
+
*/
|
|
128
|
+
export function resolveMasterInsertAutomationId(
|
|
129
|
+
ctx: EngineParameterContext,
|
|
130
|
+
insertIndex: number,
|
|
131
|
+
paramName: string,
|
|
132
|
+
): number {
|
|
133
|
+
return ctx.offlineEngine.resolveMasterInsertAutomationId(insertIndex, paramName);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Resolves a bus-strip insert parameter to its reserved insert-automation id.
|
|
138
|
+
* Declares the mixer bus first so the offline engine resolves the same bus
|
|
139
|
+
* selector.
|
|
140
|
+
*
|
|
141
|
+
* @param busId Bus id (declares the mixer bus on first use).
|
|
142
|
+
* @param insertIndex Index into the bus strip's insert sequence.
|
|
143
|
+
* @param paramName Processor JSON-key parameter name.
|
|
144
|
+
* @returns Reserved insert-automation id, or -1 when bus/insert/key unknown.
|
|
145
|
+
*/
|
|
146
|
+
export function resolveBusInsertAutomationId(
|
|
147
|
+
ctx: EngineParameterContext,
|
|
148
|
+
busId: number,
|
|
149
|
+
insertIndex: number,
|
|
150
|
+
paramName: string,
|
|
151
|
+
): number {
|
|
152
|
+
ctx.ensureBus(busId);
|
|
153
|
+
return ctx.offlineEngine.resolveBusInsertAutomationId(busId, insertIndex, paramName);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Returns the number of automation lanes installed on the engine, including
|
|
158
|
+
* lanes whose breakpoint list is currently empty.
|
|
159
|
+
*
|
|
160
|
+
* @returns Engine-side automation lane count.
|
|
161
|
+
*/
|
|
162
|
+
export function automationLaneCount(ctx: EngineParameterContext): number {
|
|
163
|
+
return ctx.offlineEngine.automationLaneCount();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function listParameters(ctx: EngineParameterContext): EngineParameterInfo[] {
|
|
167
|
+
const parameters: EngineParameterInfo[] = [];
|
|
168
|
+
for (let index = 0; index < ctx.offlineEngine.parameterCount(); index++) {
|
|
169
|
+
parameters.push(ctx.offlineEngine.parameterInfoByIndex(index));
|
|
170
|
+
}
|
|
171
|
+
return parameters;
|
|
172
|
+
}
|