@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libraz/libsonare",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"packageManager": "yarn@4.18.0",
|
|
6
6
|
"description": "Audio analysis, mastering, mixing, and MIDI synthesis in WebAssembly",
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"types": "./dist/worker.d.ts",
|
|
24
24
|
"import": "./dist/worker.js"
|
|
25
25
|
},
|
|
26
|
+
"./vocal-edit-worker": {
|
|
27
|
+
"types": "./dist/vocal_edit_worker.d.ts",
|
|
28
|
+
"import": "./dist/vocal_edit_worker.js"
|
|
29
|
+
},
|
|
26
30
|
"./wasm": "./dist/sonare.wasm",
|
|
27
31
|
"./schemas/realtime-voice-changer-preset.schema.json": "./dist/schemas/realtime-voice-changer-preset.schema.json",
|
|
28
32
|
"./schemas/realtime-voice-changer-preset-pack.schema.json": "./dist/schemas/realtime-voice-changer-preset-pack.schema.json",
|
|
@@ -60,6 +64,7 @@
|
|
|
60
64
|
"test:browser": "node scripts/audio-worklet-smoke.mjs",
|
|
61
65
|
"test:browser:worker": "node scripts/offline-worker-smoke.mjs",
|
|
62
66
|
"test:browser:boundary": "node scripts/boundary-smoke.mjs",
|
|
67
|
+
"test:browser:vocal-worker": "node scripts/vocal-edit-worker-smoke.mjs",
|
|
63
68
|
"test:watch": "vitest",
|
|
64
69
|
"prepublishOnly": "yarn clean && cp ../../LICENSE LICENSE && cp ../../NOTICE NOTICE && yarn build && yarn lint && yarn test && yarn test:types"
|
|
65
70
|
},
|
package/src/_effects_common.ts
CHANGED
|
@@ -4,14 +4,44 @@
|
|
|
4
4
|
|
|
5
5
|
import type { VoicedFlags } from './public_types';
|
|
6
6
|
|
|
7
|
-
// The embind layer reads the companion voicing array as Float32Array.
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
export function toVoicedFloat32(voiced: VoicedFlags): Float32Array {
|
|
7
|
+
// The embind layer reads the companion voicing array as Float32Array. Keep the
|
|
8
|
+
// public union a runtime contract before conversion: strings, Float64Arrays and
|
|
9
|
+
// arbitrary array-like objects must not become flags through truthiness.
|
|
10
|
+
// Same 1/0 reduction as the Node facade's toVoicedInt32.
|
|
11
|
+
export function toVoicedFloat32(fnName: string, voiced: VoicedFlags): Float32Array {
|
|
12
|
+
if (
|
|
13
|
+
!(
|
|
14
|
+
voiced instanceof Int32Array ||
|
|
15
|
+
voiced instanceof Uint8Array ||
|
|
16
|
+
voiced instanceof Float32Array ||
|
|
17
|
+
Array.isArray(voiced)
|
|
18
|
+
)
|
|
19
|
+
) {
|
|
20
|
+
throw new TypeError(
|
|
21
|
+
`${fnName}: voiced must be an Int32Array, Uint8Array, Float32Array, number[], or boolean[]`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
12
24
|
const out = new Float32Array(voiced.length);
|
|
13
25
|
for (let index = 0; index < voiced.length; index += 1) {
|
|
14
|
-
|
|
26
|
+
const value = voiced[index];
|
|
27
|
+
if (typeof value !== 'number' && typeof value !== 'boolean') {
|
|
28
|
+
throw new TypeError(`${fnName}: voiced array entries must be numbers or booleans`);
|
|
29
|
+
}
|
|
30
|
+
out[index] = value ? 1 : 0;
|
|
15
31
|
}
|
|
16
32
|
return out;
|
|
17
33
|
}
|
|
34
|
+
|
|
35
|
+
export function assertPitchTrackLengths(
|
|
36
|
+
fnName: string,
|
|
37
|
+
f0Hz: Float32Array,
|
|
38
|
+
voiced?: VoicedFlags | null,
|
|
39
|
+
voicedProb?: Float32Array | null,
|
|
40
|
+
): void {
|
|
41
|
+
if (voiced != null && voiced.length !== f0Hz.length) {
|
|
42
|
+
throw new RangeError(`${fnName}: voiced must have the same length as f0Hz`);
|
|
43
|
+
}
|
|
44
|
+
if (voiced == null && voicedProb != null && voicedProb.length !== f0Hz.length) {
|
|
45
|
+
throw new RangeError(`${fnName}: voicedProb must have the same length as f0Hz`);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/effects_mastering.ts
CHANGED
|
@@ -43,6 +43,7 @@ export {
|
|
|
43
43
|
} from './mastering_chain';
|
|
44
44
|
export type {
|
|
45
45
|
MasteringAbMatchLoudnessRequest,
|
|
46
|
+
MasteringAbMatchLoudnessStereoRequest,
|
|
46
47
|
MasteringAmpPresetCatalogEntry,
|
|
47
48
|
MasteringAssistantParamsRequest,
|
|
48
49
|
MasteringAssistantStereoParamsRequest,
|
|
@@ -53,6 +54,7 @@ export type {
|
|
|
53
54
|
MasteringInsertTiming,
|
|
54
55
|
MasteringPairAnalyzeRequest,
|
|
55
56
|
MasteringPairProcessRequest,
|
|
57
|
+
MasteringPairProcessStereoRequest,
|
|
56
58
|
MasteringProcessorCatalogEntry,
|
|
57
59
|
MasteringProcessorCategory,
|
|
58
60
|
MasteringProcessRequest,
|
|
@@ -67,6 +69,7 @@ export type {
|
|
|
67
69
|
export {
|
|
68
70
|
mastering,
|
|
69
71
|
masteringAbMatchLoudness,
|
|
72
|
+
masteringAbMatchLoudnessStereo,
|
|
70
73
|
masteringAmpPresetCatalog,
|
|
71
74
|
masteringAssistantSuggest,
|
|
72
75
|
masteringAssistantSuggestChain,
|
|
@@ -82,6 +85,7 @@ export {
|
|
|
82
85
|
masteringPairAnalyze,
|
|
83
86
|
masteringPairProcess,
|
|
84
87
|
masteringPairProcessorNames,
|
|
88
|
+
masteringPairProcessStereo,
|
|
85
89
|
masteringProcess,
|
|
86
90
|
masteringProcessorCatalog,
|
|
87
91
|
masteringProcessorNames,
|
package/src/effects_note_ops.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* rendering an edited set back, and the split/merge operations over it.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { toVoicedFloat32 } from './_effects_common';
|
|
6
|
+
import { assertPitchTrackLengths, toVoicedFloat32 } from './_effects_common';
|
|
7
7
|
import { getSonareModule } from './module_state';
|
|
8
8
|
import type {
|
|
9
9
|
NoteExtractorOptions,
|
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
VoicedFlags,
|
|
20
20
|
} from './public_types';
|
|
21
21
|
import type { ValidateOptions } from './validation';
|
|
22
|
-
import { assertSampleRate, assertSamples } from './validation';
|
|
22
|
+
import { assertFiniteScalar, assertSampleRate, assertSamples } from './validation';
|
|
23
23
|
|
|
24
24
|
function requireModule() {
|
|
25
25
|
return getSonareModule();
|
|
@@ -35,13 +35,9 @@ function assertNoteTrack(
|
|
|
35
35
|
): Float32Array | undefined {
|
|
36
36
|
assertSamples(fnName, request.samples, request.validate !== false);
|
|
37
37
|
assertSampleRate(fnName, request.sampleRate);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
|
|
42
|
-
throw new RangeError(`${fnName}: voicedProb length must match f0Hz length`);
|
|
43
|
-
}
|
|
44
|
-
return request.voiced ? toVoicedFloat32(request.voiced) : undefined;
|
|
38
|
+
const voicedF32 = request.voiced == null ? undefined : toVoicedFloat32(fnName, request.voiced);
|
|
39
|
+
assertPitchTrackLengths(fnName, request.f0Hz, request.voiced, request.voicedProb);
|
|
40
|
+
return voicedF32;
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
export interface NoteStretchRequest extends NoteStretchOptions, ValidateOptions {
|
|
@@ -73,10 +69,10 @@ export interface ExtractNotesRequest extends NoteExtractorOptions, ValidateOptio
|
|
|
73
69
|
f0Hz: Float32Array;
|
|
74
70
|
/** F0 frames per second. */
|
|
75
71
|
frameRate: number;
|
|
76
|
-
/** Per-frame voiced flags (truthy = voiced)
|
|
77
|
-
voiced?: VoicedFlags;
|
|
78
|
-
/** Per-frame voicing probability in `[0, 1]`; read only when `voiced` is omitted
|
|
79
|
-
voicedProb?: Float32Array;
|
|
72
|
+
/** Per-frame voiced flags (truthy = voiced); takes precedence over `voicedProb` when supplied. Omit or pass `null` to use the probability array. */
|
|
73
|
+
voiced?: VoicedFlags | null;
|
|
74
|
+
/** Per-frame voicing probability in `[0, 1]`; read only when `voiced` is omitted or `null`. */
|
|
75
|
+
voicedProb?: Float32Array | null;
|
|
80
76
|
}
|
|
81
77
|
|
|
82
78
|
/** Canonical request form for {@link renderNotes}. */
|
|
@@ -97,12 +93,16 @@ export interface RenderNotesRequest extends ValidateOptions {
|
|
|
97
93
|
/**
|
|
98
94
|
* The F0 track the notes were extracted from. Required only by
|
|
99
95
|
* `vibratoDepthChange` and `driftChange`, which act on the note's own pitch
|
|
100
|
-
* curve; every other edit ignores it. The curve is not carried on a
|
|
96
|
+
* curve, or when `voiced` is supplied; every other edit ignores it. The curve is not carried on a
|
|
101
97
|
* {@link NoteObject} for the same reason it is not returned by
|
|
102
98
|
* {@link extractNotes} — it is this array sliced by
|
|
103
99
|
* `[frameStart, frameEnd)`, which the caller already holds.
|
|
100
|
+
* Positive finite values are candidates for pitch. Zero, negative and
|
|
101
|
+
* non-finite values carry no measurement.
|
|
104
102
|
*/
|
|
105
103
|
f0Hz?: Float32Array;
|
|
104
|
+
/** Per-frame voiced flags for `f0Hz`; false suppresses a candidate and true cannot make an unusable F0 valid. */
|
|
105
|
+
voiced?: VoicedFlags | null;
|
|
106
106
|
/** F0 frames per second. Required when `f0Hz` is given. */
|
|
107
107
|
frameRate?: number;
|
|
108
108
|
/**
|
|
@@ -120,9 +120,11 @@ export interface RenderNotesRequest extends ValidateOptions {
|
|
|
120
120
|
export interface DecomposeNotePitchRequest {
|
|
121
121
|
/**
|
|
122
122
|
* The note's slice of the F0 track — `f0Hz.subarray(frameStart, frameEnd)`.
|
|
123
|
-
*
|
|
123
|
+
* Positive finite values carry pitch; other values carry no measurement.
|
|
124
124
|
*/
|
|
125
125
|
f0Hz: Float32Array;
|
|
126
|
+
/** Per-frame voiced flags matching `f0Hz`; false suppresses a candidate and true cannot make an unusable F0 valid. */
|
|
127
|
+
voiced?: VoicedFlags | null;
|
|
126
128
|
/** F0 frames per second. */
|
|
127
129
|
frameRate: number;
|
|
128
130
|
/**
|
|
@@ -152,10 +154,10 @@ export interface NoteSetRequest extends NoteExtractorOptions, ValidateOptions {
|
|
|
152
154
|
f0Hz: Float32Array;
|
|
153
155
|
/** F0 frames per second. */
|
|
154
156
|
frameRate: number;
|
|
155
|
-
/** Per-frame voiced flags (truthy = voiced)
|
|
156
|
-
voiced?: VoicedFlags;
|
|
157
|
-
/** Per-frame voicing probability in `[0, 1]`; read only when `voiced` is omitted
|
|
158
|
-
voicedProb?: Float32Array;
|
|
157
|
+
/** Per-frame voiced flags (truthy = voiced); takes precedence over `voicedProb` when supplied. Omit or pass `null` to use the probability array. */
|
|
158
|
+
voiced?: VoicedFlags | null;
|
|
159
|
+
/** Per-frame voicing probability in `[0, 1]`; read only when `voiced` is omitted or `null`. */
|
|
160
|
+
voicedProb?: Float32Array | null;
|
|
159
161
|
/** The current note set. Each note's `[frameStart, frameEnd)` must be non-empty and inside the track. */
|
|
160
162
|
notes: readonly NoteSetEntry[];
|
|
161
163
|
}
|
|
@@ -285,8 +287,8 @@ export function noteMove(
|
|
|
285
287
|
* `f0Hz` sliced by `[frameStart, frameEnd)`. The amplitude curve is measured
|
|
286
288
|
* here, so it is, one entry per F0 frame over that note's span.
|
|
287
289
|
*
|
|
288
|
-
* Voicing comes from `voiced` (truthy = voiced). `voicedProb`
|
|
289
|
-
* `voiced` is absent
|
|
290
|
+
* Voicing comes from `voiced` (truthy = voiced) when it is supplied. `voicedProb`
|
|
291
|
+
* is read only when `voiced` is absent or `null`, and then a frame counts as voiced at or above
|
|
290
292
|
* `voicedThreshold` (default 0.5). At least one of the two is required. Because
|
|
291
293
|
* `voicedProb` from pYIN rises with F0 for a fixed frame length, prefer passing
|
|
292
294
|
* a {@link PitchResult}'s `voicedFlag` through `voiced`.
|
|
@@ -294,10 +296,11 @@ export function noteMove(
|
|
|
294
296
|
* @param request - Audio, F0 track, frame cadence and segmenter options
|
|
295
297
|
* @returns One {@link NoteObject} per segmented note, in time order; an empty
|
|
296
298
|
* array when the track segments to nothing
|
|
297
|
-
* @throws RangeError when
|
|
298
|
-
* or
|
|
299
|
+
* @throws RangeError when the selected voicing array differs from `f0Hz` in
|
|
300
|
+
* length (`voiced`, or `voicedProb` when `voiced` is omitted or `null`), or the
|
|
301
|
+
* samples/sample rate fail the shared input checks
|
|
299
302
|
* @throws SonareError (`InvalidParameter`) on an empty `f0Hz`, a non-positive
|
|
300
|
-
* `frameRate`, a
|
|
303
|
+
* `frameRate`, a `voicedProb` outside
|
|
301
304
|
* `[0, 1]`, or a negative option value
|
|
302
305
|
*
|
|
303
306
|
* @example
|
|
@@ -325,7 +328,7 @@ export function extractNotes(request: ExtractNotesRequest): NoteObject[] {
|
|
|
325
328
|
request.samples,
|
|
326
329
|
request.sampleRate,
|
|
327
330
|
request.f0Hz,
|
|
328
|
-
request.voicedProb,
|
|
331
|
+
request.voiced == null ? (request.voicedProb ?? undefined) : undefined,
|
|
329
332
|
voicedF32,
|
|
330
333
|
request.frameRate,
|
|
331
334
|
request,
|
|
@@ -354,7 +357,11 @@ export function extractNotes(request: ExtractNotesRequest): NoteObject[] {
|
|
|
354
357
|
* @param request - Source audio, the notes to render, the cross-fade length, and
|
|
355
358
|
* the F0 track a vibrato or drift edit reads
|
|
356
359
|
* @returns The rendered audio, the same length and sample rate as the input
|
|
357
|
-
* @throws
|
|
360
|
+
* @throws TypeError when `notes` is not an array, `f0Hz` is not a
|
|
361
|
+
* `Float32Array`, or `voiced` is not a supported flag array
|
|
362
|
+
* @throws RangeError when `frameRate` is non-finite, `voiced` is supplied
|
|
363
|
+
* without `f0Hz`, when its length differs from `f0Hz`, or when the samples or
|
|
364
|
+
* sample rate fail shared checks
|
|
358
365
|
* @throws SonareError (`InvalidParameter`) on a note whose span is empty,
|
|
359
366
|
* reversed or missing, overlapping source spans, a non-finite or non-positive
|
|
360
367
|
* edit field, a negative or non-finite envelope value, a negative `fadeMs` or
|
|
@@ -385,9 +392,28 @@ export function extractNotes(request: ExtractNotesRequest): NoteObject[] {
|
|
|
385
392
|
* ```
|
|
386
393
|
*/
|
|
387
394
|
export function renderNotes(request: RenderNotesRequest): Float32Array {
|
|
388
|
-
assertSamples('renderNotes', request.samples, request.validate !== false);
|
|
389
395
|
assertSampleRate('renderNotes', request.sampleRate);
|
|
390
|
-
|
|
396
|
+
if (!Array.isArray(request.notes)) {
|
|
397
|
+
throw new TypeError('renderNotes: notes must be an array');
|
|
398
|
+
}
|
|
399
|
+
const f0Hz = request.f0Hz;
|
|
400
|
+
if (f0Hz !== undefined && !(f0Hz instanceof Float32Array)) {
|
|
401
|
+
throw new TypeError('renderNotes: f0Hz must be a Float32Array');
|
|
402
|
+
}
|
|
403
|
+
if (f0Hz !== undefined) {
|
|
404
|
+
assertFiniteScalar('renderNotes', request.frameRate as number, 'frameRate');
|
|
405
|
+
}
|
|
406
|
+
const { voiced: publicVoiced, ...withoutVoiced } = request;
|
|
407
|
+
const voiced = publicVoiced == null ? undefined : toVoicedFloat32('renderNotes', publicVoiced);
|
|
408
|
+
if (publicVoiced != null && f0Hz === undefined) {
|
|
409
|
+
throw new RangeError('renderNotes: voiced requires f0Hz');
|
|
410
|
+
}
|
|
411
|
+
if (f0Hz !== undefined) {
|
|
412
|
+
assertPitchTrackLengths('renderNotes', f0Hz, publicVoiced);
|
|
413
|
+
}
|
|
414
|
+
assertSamples('renderNotes', request.samples, request.validate !== false);
|
|
415
|
+
const options = { ...withoutVoiced, voiced };
|
|
416
|
+
return requireModule().renderNotes(request.samples, request.sampleRate, request.notes, options);
|
|
391
417
|
}
|
|
392
418
|
|
|
393
419
|
/**
|
|
@@ -401,7 +427,10 @@ export function renderNotes(request: RenderNotesRequest): Float32Array {
|
|
|
401
427
|
*
|
|
402
428
|
* Frames whose F0 is unusable carry no measurement, so the curve is held at the
|
|
403
429
|
* nearest usable neighbour across them. Both curves therefore have an entry
|
|
404
|
-
* everywhere; a host marking
|
|
430
|
+
* everywhere; when `voiced` is supplied, a host marking held frames should
|
|
431
|
+
* retain both the `f0Hz` and `voiced` arrays because a positive F0 can be
|
|
432
|
+
* explicitly suppressed. A false flag suppresses that frame and a true flag
|
|
433
|
+
* cannot make an unusable F0 valid.
|
|
405
434
|
*
|
|
406
435
|
* A note with no usable pitch is reported as a zero `centreHz` and two empty
|
|
407
436
|
* curves rather than as an error — that is a measurement which came up empty,
|
|
@@ -410,9 +439,12 @@ export function renderNotes(request: RenderNotesRequest): Float32Array {
|
|
|
410
439
|
* @param request - The note's slice of the F0 track, its cadence, its centre, and
|
|
411
440
|
* the cutoff
|
|
412
441
|
* @returns The centre and the two curves, each one entry per frame of `f0Hz`
|
|
413
|
-
* @throws
|
|
414
|
-
*
|
|
415
|
-
*
|
|
442
|
+
* @throws TypeError when `f0Hz` is not a `Float32Array` or `voiced` is not a
|
|
443
|
+
* supported flag array
|
|
444
|
+
* @throws RangeError when `frameRate` is not finite or `voiced` differs in
|
|
445
|
+
* length from `f0Hz`
|
|
446
|
+
* @throws SonareError (`InvalidParameter`) on an empty `f0Hz`, a non-positive
|
|
447
|
+
* `frameRate`, a negative `medianHz`, or a negative `vibratoCutoffHz`
|
|
416
448
|
*
|
|
417
449
|
* @example
|
|
418
450
|
* ```ts
|
|
@@ -425,8 +457,16 @@ export function renderNotes(request: RenderNotesRequest): Float32Array {
|
|
|
425
457
|
* ```
|
|
426
458
|
*/
|
|
427
459
|
export function decomposeNotePitch(request: DecomposeNotePitchRequest): PitchDecompositionResult {
|
|
460
|
+
if (!(request.f0Hz instanceof Float32Array)) {
|
|
461
|
+
throw new TypeError('decomposeNotePitch: f0Hz must be a Float32Array');
|
|
462
|
+
}
|
|
463
|
+
assertFiniteScalar('decomposeNotePitch', request.frameRate, 'frameRate');
|
|
464
|
+
const voiced =
|
|
465
|
+
request.voiced == null ? undefined : toVoicedFloat32('decomposeNotePitch', request.voiced);
|
|
466
|
+
assertPitchTrackLengths('decomposeNotePitch', request.f0Hz, request.voiced);
|
|
428
467
|
return requireModule().decomposeNotePitch(
|
|
429
468
|
request.f0Hz,
|
|
469
|
+
voiced,
|
|
430
470
|
request.frameRate,
|
|
431
471
|
request.medianHz,
|
|
432
472
|
request.vibratoCutoffHz ?? 0,
|
|
@@ -438,22 +478,29 @@ export function decomposeNotePitch(request: DecomposeNotePitchRequest): PitchDec
|
|
|
438
478
|
*
|
|
439
479
|
* Both halves are re-derived from the audio and the track the way
|
|
440
480
|
* {@link extractNotes} derives its own, rather than by patching the fields of
|
|
441
|
-
* the note they replace. Both inherit the source note's edit, and
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
* being split.
|
|
481
|
+
* the note they replace. Both inherit the source note's edit, and the tail's
|
|
482
|
+
* `timeOffsetSamples` absorbs the duration change of the stretched head, so the
|
|
483
|
+
* halves occupy the destination timeline the unsplit note did; a note whose edit
|
|
484
|
+
* is the identity still renders bit for bit after being split. A half with no
|
|
485
|
+
* sample span at that boundary is omitted and the other keeps the edit
|
|
486
|
+
* unchanged. A one-entry envelope is a constant over the span, so both halves
|
|
487
|
+
* get that same entry; a longer one is resampled onto a grid that preserves the
|
|
488
|
+
* rendered gain at every integer source sample.
|
|
445
489
|
*
|
|
446
490
|
* Every note in the set, not just the two halves, has its spans, curves, medians
|
|
447
491
|
* and stability re-derived from `samples` and the track, because a
|
|
448
492
|
* {@link NoteSetEntry} carries no curves for this call to copy through. The
|
|
449
493
|
* frame bounds are therefore what a note is identified by here, and the audio
|
|
450
494
|
* and track must be the ones the set was extracted from or the whole set is
|
|
451
|
-
* re-measured against something else.
|
|
495
|
+
* re-measured against something else. A set whose re-derived notes would
|
|
496
|
+
* include one with no sample span is rejected rather than shortened.
|
|
452
497
|
*
|
|
453
498
|
* @param request - The source, the current note set, and where to cut
|
|
454
|
-
* @returns The whole new note set, one note longer than the one handed in
|
|
455
|
-
*
|
|
456
|
-
*
|
|
499
|
+
* @returns The whole new note set, one note longer than the one handed in, or
|
|
500
|
+
* the same length when a half with no sample span is omitted
|
|
501
|
+
* @throws RangeError when the selected voicing array differs from `f0Hz` in
|
|
502
|
+
* length (`voiced`, or `voicedProb` when `voiced` is omitted or `null`), or the
|
|
503
|
+
* samples/sample rate fail the shared input checks
|
|
457
504
|
* @throws SonareError (`InvalidParameter`) on an out-of-range `index`, a `frame`
|
|
458
505
|
* that is not strictly inside that note's own span, a note whose frame span is
|
|
459
506
|
* empty or runs past the track, or the track arguments {@link extractNotes}
|
|
@@ -480,7 +527,7 @@ export function splitNote(request: SplitNoteRequest): NoteObject[] {
|
|
|
480
527
|
request.samples,
|
|
481
528
|
request.sampleRate,
|
|
482
529
|
request.f0Hz,
|
|
483
|
-
request.voicedProb,
|
|
530
|
+
request.voiced == null ? (request.voicedProb ?? undefined) : undefined,
|
|
484
531
|
voicedF32,
|
|
485
532
|
request.frameRate,
|
|
486
533
|
request.notes,
|
|
@@ -507,8 +554,9 @@ export function splitNote(request: SplitNoteRequest): NoteObject[] {
|
|
|
507
554
|
* @param request - The source, the current note set, and the run to join
|
|
508
555
|
* @returns The whole new note set, `last - first` notes shorter than the one
|
|
509
556
|
* handed in
|
|
510
|
-
* @throws RangeError when
|
|
511
|
-
* or
|
|
557
|
+
* @throws RangeError when the selected voicing array differs from `f0Hz` in
|
|
558
|
+
* length (`voiced`, or `voicedProb` when `voiced` is omitted or `null`), or the
|
|
559
|
+
* samples/sample rate fail the shared input checks
|
|
512
560
|
* @throws SonareError (`InvalidParameter`) unless `first < last < notes.length`,
|
|
513
561
|
* on a note whose frame span is empty or runs past the track, or on the track
|
|
514
562
|
* arguments {@link extractNotes} itself rejects
|
|
@@ -534,7 +582,7 @@ export function mergeNotes(request: MergeNotesRequest): NoteObject[] {
|
|
|
534
582
|
request.samples,
|
|
535
583
|
request.sampleRate,
|
|
536
584
|
request.f0Hz,
|
|
537
|
-
request.voicedProb,
|
|
585
|
+
request.voiced == null ? (request.voicedProb ?? undefined) : undefined,
|
|
538
586
|
voicedF32,
|
|
539
587
|
request.frameRate,
|
|
540
588
|
request.notes,
|
package/src/effects_timepitch.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* correction onto a target pitch.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { toVoicedFloat32 } from './_effects_common';
|
|
6
|
+
import { assertPitchTrackLengths, toVoicedFloat32 } from './_effects_common';
|
|
7
7
|
import { resolveFftOptions } from './_fft_options';
|
|
8
8
|
import { getSonareModule } from './module_state';
|
|
9
9
|
import type { PitchCorrectOptions, VoicedFlags } from './public_types';
|
|
@@ -43,8 +43,8 @@ export interface PitchCorrectToMidiTimevaryingRequest extends ValidateOptions {
|
|
|
43
43
|
targetMidi: number;
|
|
44
44
|
sampleRate?: number;
|
|
45
45
|
hopLength?: number;
|
|
46
|
-
voiced?: VoicedFlags;
|
|
47
|
-
voicedProb?: Float32Array;
|
|
46
|
+
voiced?: VoicedFlags | null;
|
|
47
|
+
voicedProb?: Float32Array | null;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export interface PitchCorrectTimevaryingRequest extends PitchCorrectOptions {
|
|
@@ -250,19 +250,23 @@ export function pitchCorrectToMidi(
|
|
|
250
250
|
* Unlike {@link pitchCorrectToMidi} (a single constant transpose), this follows
|
|
251
251
|
* the caller-supplied per-frame `f0Hz` contour and retunes every voiced frame
|
|
252
252
|
* toward `targetMidi`, so vibrato/drift in the source is tracked rather than
|
|
253
|
-
* flattened. `voiced` (truthy = voiced)
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
253
|
+
* flattened. When `voiced` is supplied (truthy = voiced), it takes precedence
|
|
254
|
+
* over `voicedProb`; omitting it or passing `null` uses the probability array.
|
|
255
|
+
* When both are omitted, every frame is treated as voiced. An `f0Hz` NaN is
|
|
256
|
+
* accepted only for a frame marked unvoiced, matching pYIN output. The
|
|
257
|
+
* `voicedFlag` / `voicedProb` arrays of a {@link PitchResult} can
|
|
258
|
+
* be passed through directly.
|
|
258
259
|
*
|
|
259
260
|
* @param samples - Audio samples (mono, float32)
|
|
260
261
|
* @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
|
|
261
262
|
* @param targetMidi - Desired MIDI note number
|
|
262
263
|
* @param sampleRate - Sample rate in Hz
|
|
263
|
-
* @param hopLength - F0
|
|
264
|
-
*
|
|
265
|
-
* @param
|
|
264
|
+
* @param hopLength - F0 frame-center spacing in samples. Frame i is centered at
|
|
265
|
+
* sample i*hopLength; nearest-frame voicing switches halfway between centers.
|
|
266
|
+
* @param voiced - Optional per-frame voiced flags (truthy = voiced); takes
|
|
267
|
+
* precedence over `voicedProb`.
|
|
268
|
+
* @param voicedProb - Optional per-frame voicing probability in [0, 1]; used
|
|
269
|
+
* when `voiced` is omitted or `null`.
|
|
266
270
|
* @returns Pitch-corrected audio
|
|
267
271
|
*/
|
|
268
272
|
export function pitchCorrectToMidiTimevarying(
|
|
@@ -274,8 +278,8 @@ export function pitchCorrectToMidiTimevarying(
|
|
|
274
278
|
targetMidi: number,
|
|
275
279
|
sampleRate?: number,
|
|
276
280
|
hopLength?: number,
|
|
277
|
-
voiced?: VoicedFlags,
|
|
278
|
-
voicedProb?: Float32Array,
|
|
281
|
+
voiced?: VoicedFlags | null,
|
|
282
|
+
voicedProb?: Float32Array | null,
|
|
279
283
|
options?: ValidateOptions,
|
|
280
284
|
): Float32Array;
|
|
281
285
|
export function pitchCorrectToMidiTimevarying(
|
|
@@ -284,8 +288,8 @@ export function pitchCorrectToMidiTimevarying(
|
|
|
284
288
|
targetMidi?: number,
|
|
285
289
|
sampleRate = 22050,
|
|
286
290
|
hopLength = 512,
|
|
287
|
-
voiced?: VoicedFlags,
|
|
288
|
-
voicedProb?: Float32Array,
|
|
291
|
+
voiced?: VoicedFlags | null,
|
|
292
|
+
voicedProb?: Float32Array | null,
|
|
289
293
|
options: ValidateOptions = {},
|
|
290
294
|
): Float32Array {
|
|
291
295
|
const request: PitchCorrectToMidiTimevaryingRequest =
|
|
@@ -302,13 +306,16 @@ export function pitchCorrectToMidiTimevarying(
|
|
|
302
306
|
}
|
|
303
307
|
: samples;
|
|
304
308
|
assertSamples('pitchCorrectToMidiTimevarying', request.samples, request.validate !== false);
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
309
|
+
const voicedF32 =
|
|
310
|
+
request.voiced == null
|
|
311
|
+
? undefined
|
|
312
|
+
: toVoicedFloat32('pitchCorrectToMidiTimevarying', request.voiced);
|
|
313
|
+
assertPitchTrackLengths(
|
|
314
|
+
'pitchCorrectToMidiTimevarying',
|
|
315
|
+
request.f0Hz,
|
|
316
|
+
request.voiced,
|
|
317
|
+
request.voicedProb,
|
|
318
|
+
);
|
|
312
319
|
return requireModule().pitchCorrectToMidiTimevarying(
|
|
313
320
|
request.samples,
|
|
314
321
|
request.sampleRate ?? 22050,
|
|
@@ -316,7 +323,7 @@ export function pitchCorrectToMidiTimevarying(
|
|
|
316
323
|
request.targetMidi,
|
|
317
324
|
request.hopLength ?? 512,
|
|
318
325
|
voicedF32,
|
|
319
|
-
request.voicedProb,
|
|
326
|
+
request.voiced == null ? (request.voicedProb ?? undefined) : undefined,
|
|
320
327
|
);
|
|
321
328
|
}
|
|
322
329
|
|
|
@@ -333,7 +340,8 @@ export function pitchCorrectToMidiTimevarying(
|
|
|
333
340
|
* @param samples - Audio samples (mono, float32)
|
|
334
341
|
* @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
|
|
335
342
|
* @param sampleRate - Sample rate in Hz
|
|
336
|
-
* @param hopLength - F0
|
|
343
|
+
* @param hopLength - F0 frame-center spacing in samples. Frame i is centered at
|
|
344
|
+
* sample i*hopLength; nearest-frame voicing switches halfway between centers.
|
|
337
345
|
* @param options - Target mode + retune knobs + optional voiced/voicedProb arrays
|
|
338
346
|
* @returns Pitch-corrected audio
|
|
339
347
|
*/
|
|
@@ -357,15 +365,18 @@ export function pitchCorrectTimevarying(
|
|
|
357
365
|
? { samples, f0Hz: f0Hz as Float32Array, sampleRate, hopLength, ...options }
|
|
358
366
|
: samples;
|
|
359
367
|
assertSamples('pitchCorrectTimevarying', request.samples, request.validate !== false);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
368
|
+
const voicedF32 =
|
|
369
|
+
request.voiced == null ? undefined : toVoicedFloat32('pitchCorrectTimevarying', request.voiced);
|
|
370
|
+
assertPitchTrackLengths(
|
|
371
|
+
'pitchCorrectTimevarying',
|
|
372
|
+
request.f0Hz,
|
|
373
|
+
request.voiced,
|
|
374
|
+
request.voicedProb,
|
|
375
|
+
);
|
|
366
376
|
const nativeOptions = {
|
|
367
377
|
...request,
|
|
368
|
-
voiced:
|
|
378
|
+
voiced: voicedF32,
|
|
379
|
+
voicedProb: request.voiced == null ? (request.voicedProb ?? undefined) : undefined,
|
|
369
380
|
};
|
|
370
381
|
return requireModule().pitchCorrectTimevarying(
|
|
371
382
|
request.samples,
|
package/src/feature_decompose.ts
CHANGED
|
@@ -233,7 +233,7 @@ export function decomposeWithInit(
|
|
|
233
233
|
/** Options for {@link decomposeStems}. */
|
|
234
234
|
export interface DecomposeStemsRequest {
|
|
235
235
|
samples: Float32Array;
|
|
236
|
-
sampleRate
|
|
236
|
+
sampleRate?: number;
|
|
237
237
|
/** Number of NMF components (default 4). */
|
|
238
238
|
nComponents?: number;
|
|
239
239
|
/** STFT size (default 2048). */
|
|
@@ -278,7 +278,7 @@ export interface DecomposeStemsResult {
|
|
|
278
278
|
* to the input.
|
|
279
279
|
*/
|
|
280
280
|
export function decomposeStems(request: DecomposeStemsRequest): DecomposeStemsResult {
|
|
281
|
-
return requireModule().decomposeStems(request.samples, request.sampleRate, {
|
|
281
|
+
return requireModule().decomposeStems(request.samples, request.sampleRate ?? 22050, {
|
|
282
282
|
nComponents: request.nComponents,
|
|
283
283
|
nFft: request.nFft,
|
|
284
284
|
hopLength: request.hopLength,
|