@aelionsdk/audio 1.2.0-rc.1 → 1.2.0-rc.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 +3 -1
- package/dist/analysis.d.ts +28 -11
- package/dist/analysis.d.ts.map +1 -1
- package/dist/analysis.js +40 -10
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Audio scheduling and deterministic PCM processing primitives for AelionSDK.
|
|
|
8
8
|
npm install @aelionsdk/audio@next
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
`next` currently resolves to `1.2.0-rc.
|
|
11
|
+
`next` currently resolves to `1.2.0-rc.2`. Product applications should prefer
|
|
12
12
|
`@aelionsdk/sdk`; use this package directly when building a custom audio host,
|
|
13
13
|
analysis pipeline or renderer integration.
|
|
14
14
|
|
|
@@ -18,6 +18,8 @@ analysis pipeline or renderer integration.
|
|
|
18
18
|
- streaming resampling and pitch-preserving time stretch;
|
|
19
19
|
- SharedArrayBuffer and transferable PCM queues;
|
|
20
20
|
- AudioWorklet clocks, device state and video scheduling.
|
|
21
|
+
- bounded beat and audio-energy change analysis. Audio analysis does not claim
|
|
22
|
+
pixel-based video scene detection.
|
|
21
23
|
|
|
22
24
|
Queue and clock instances own browser and buffer resources. Stop producers,
|
|
23
25
|
cancel pending work and dispose the owning session or primitive when playback
|
package/dist/analysis.d.ts
CHANGED
|
@@ -22,18 +22,26 @@ export interface BeatDetectionResult {
|
|
|
22
22
|
readonly totalFrames: number;
|
|
23
23
|
readonly beats: readonly BeatMarker[];
|
|
24
24
|
}
|
|
25
|
-
/** A
|
|
26
|
-
export interface
|
|
27
|
-
/**
|
|
25
|
+
/** A candidate boundary inferred from a discontinuity in audio energy. */
|
|
26
|
+
export interface AudioEnergyBoundary {
|
|
27
|
+
/** Audio frame where the energy change is detected. */
|
|
28
28
|
readonly frame: number;
|
|
29
29
|
/** Normalized change magnitude, 0..1. */
|
|
30
30
|
readonly magnitude: number;
|
|
31
31
|
}
|
|
32
|
-
/** Result of
|
|
32
|
+
/** Result of audio-energy change detection over a PCM source. */
|
|
33
|
+
export interface AudioEnergyChangeDetectionResult {
|
|
34
|
+
readonly sampleRate: number;
|
|
35
|
+
readonly totalFrames: number;
|
|
36
|
+
readonly changes: readonly AudioEnergyBoundary[];
|
|
37
|
+
}
|
|
38
|
+
/** @deprecated Use AudioEnergyBoundary; audio alone cannot detect video scenes. */
|
|
39
|
+
export type SceneBoundary = AudioEnergyBoundary;
|
|
40
|
+
/** @deprecated Use AudioEnergyChangeDetectionResult. */
|
|
33
41
|
export interface SceneDetectionResult {
|
|
34
42
|
readonly sampleRate: number;
|
|
35
43
|
readonly totalFrames: number;
|
|
36
|
-
readonly scenes: readonly
|
|
44
|
+
readonly scenes: readonly AudioEnergyBoundary[];
|
|
37
45
|
}
|
|
38
46
|
/**
|
|
39
47
|
* Detect beats from an interleaved PCM source using a bounded energy-envelope
|
|
@@ -46,13 +54,22 @@ export declare function detectBeats(options: AnalysisSource & {
|
|
|
46
54
|
readonly minimumOnsetDb?: number;
|
|
47
55
|
}): Promise<BeatDetectionResult>;
|
|
48
56
|
/**
|
|
49
|
-
* Detect
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
* Detect audio-energy discontinuities as editing candidates. This function
|
|
58
|
+
* does not inspect video pixels and therefore deliberately makes no claim to
|
|
59
|
+
* detect scene boundaries.
|
|
60
|
+
*/
|
|
61
|
+
export declare function detectAudioEnergyChanges(options: AnalysisSource & {
|
|
62
|
+
readonly minimumJumpDb?: number;
|
|
63
|
+
}): Promise<AudioEnergyChangeDetectionResult>;
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated Audio-only input cannot detect video scenes. Use
|
|
66
|
+
* `detectAudioEnergyChanges` and treat the result as editing candidates.
|
|
54
67
|
*/
|
|
55
68
|
export declare function detectScenes(options: AnalysisSource & {
|
|
56
69
|
readonly minimumJumpDb?: number;
|
|
57
|
-
}): Promise<
|
|
70
|
+
}): Promise<{
|
|
71
|
+
sampleRate: number;
|
|
72
|
+
totalFrames: number;
|
|
73
|
+
scenes: readonly AudioEnergyBoundary[];
|
|
74
|
+
}>;
|
|
58
75
|
//# sourceMappingURL=analysis.d.ts.map
|
package/dist/analysis.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../src/analysis.ts"],"names":[],"mappings":"AAEA,6FAA6F;AAC7F,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,CACnB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC,YAAY,CAAC,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAClD;AAED,8BAA8B;AAC9B,MAAM,WAAW,UAAU;IACzB,wBAAwB;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,kDAAkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;CACvC;AAED,
|
|
1
|
+
{"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../src/analysis.ts"],"names":[],"mappings":"AAEA,6FAA6F;AAC7F,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,CACnB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC,YAAY,CAAC,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAClD;AAED,8BAA8B;AAC9B,MAAM,WAAW,UAAU;IACzB,wBAAwB;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,kDAAkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;CACvC;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,iEAAiE;AACjE,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAClD;AAED,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAEhD,wDAAwD;AACxD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACjD;AAmBD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,cAAc,GAAG;IAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7D,OAAO,CAAC,mBAAmB,CAAC,CAkC9B;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,cAAc,GAAG;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5D,OAAO,CAAC,gCAAgC,CAAC,CA+B3C;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE;;;;GAO/F"}
|
package/dist/analysis.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { throwIfAborted } from '@aelionsdk/core';
|
|
2
2
|
const MIN_CHANGE_DB = 6;
|
|
3
|
+
function validateSource(options, threshold, label) {
|
|
4
|
+
if (!Number.isSafeInteger(options.sampleRate) || options.sampleRate <= 0) {
|
|
5
|
+
throw new RangeError(`${label} sampleRate must be a positive safe integer`);
|
|
6
|
+
}
|
|
7
|
+
if (!Number.isSafeInteger(options.channelCount) || options.channelCount <= 0) {
|
|
8
|
+
throw new RangeError(`${label} channelCount must be a positive safe integer`);
|
|
9
|
+
}
|
|
10
|
+
if (!Number.isSafeInteger(options.totalFrames) || options.totalFrames < 0) {
|
|
11
|
+
throw new RangeError(`${label} totalFrames must be a non-negative safe integer`);
|
|
12
|
+
}
|
|
13
|
+
if (!Number.isFinite(threshold) || threshold < 0) {
|
|
14
|
+
throw new RangeError(`${label} threshold must be a non-negative finite number`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
3
17
|
/**
|
|
4
18
|
* Detect beats from an interleaved PCM source using a bounded energy-envelope
|
|
5
19
|
* onset detector. The signal is processed in fixed windows, computing a local
|
|
@@ -12,6 +26,7 @@ export async function detectBeats(options) {
|
|
|
12
26
|
const channelCount = options.channelCount;
|
|
13
27
|
const totalFrames = options.totalFrames;
|
|
14
28
|
const minimumOnsetDb = options.minimumOnsetDb ?? MIN_CHANGE_DB;
|
|
29
|
+
validateSource(options, minimumOnsetDb, 'Beat detection');
|
|
15
30
|
const windowFrames = Math.max(1, Math.round(sampleRate / 20));
|
|
16
31
|
const beats = [];
|
|
17
32
|
let previousDb = 0;
|
|
@@ -33,28 +48,29 @@ export async function detectBeats(options) {
|
|
|
33
48
|
const onset = db - floor > minimumOnsetDb && db > previousDb;
|
|
34
49
|
if (onset && startFrame - lastBeatFrame >= windowFrames) {
|
|
35
50
|
const strength = Math.min(1, Math.max(0, (db - floor) / 24));
|
|
36
|
-
beats.push({ frame: startFrame, frameCount
|
|
51
|
+
beats.push({ frame: startFrame, frameCount, strength });
|
|
37
52
|
lastBeatFrame = startFrame;
|
|
38
53
|
}
|
|
39
54
|
previousDb = db;
|
|
40
55
|
options.onProgress?.((startFrame + frameCount) / totalFrames);
|
|
41
56
|
}
|
|
57
|
+
if (totalFrames === 0)
|
|
58
|
+
options.onProgress?.(1);
|
|
42
59
|
return { sampleRate, totalFrames, beats };
|
|
43
60
|
}
|
|
44
61
|
/**
|
|
45
|
-
* Detect
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* This is a deterministic, media-agnostic approximation driven by the same
|
|
49
|
-
* readFrames source; it does not inspect video pixels.
|
|
62
|
+
* Detect audio-energy discontinuities as editing candidates. This function
|
|
63
|
+
* does not inspect video pixels and therefore deliberately makes no claim to
|
|
64
|
+
* detect scene boundaries.
|
|
50
65
|
*/
|
|
51
|
-
export async function
|
|
66
|
+
export async function detectAudioEnergyChanges(options) {
|
|
52
67
|
const sampleRate = options.sampleRate;
|
|
53
68
|
const channelCount = options.channelCount;
|
|
54
69
|
const totalFrames = options.totalFrames;
|
|
55
70
|
const minimumJumpDb = options.minimumJumpDb ?? MIN_CHANGE_DB;
|
|
71
|
+
validateSource(options, minimumJumpDb, 'Audio energy analysis');
|
|
56
72
|
const windowFrames = Math.max(1, Math.round(sampleRate / 10));
|
|
57
|
-
const
|
|
73
|
+
const changes = [];
|
|
58
74
|
let previousDb = 0;
|
|
59
75
|
for (let startFrame = 0; startFrame < totalFrames; startFrame += windowFrames) {
|
|
60
76
|
throwIfAborted(options.signal, 'Scene detection');
|
|
@@ -71,11 +87,25 @@ export async function detectScenes(options) {
|
|
|
71
87
|
if (startFrame > 0) {
|
|
72
88
|
const jump = Math.abs(db - previousDb);
|
|
73
89
|
if (jump >= minimumJumpDb) {
|
|
74
|
-
|
|
90
|
+
changes.push({ frame: startFrame, magnitude: Math.min(1, jump / 24) });
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
93
|
previousDb = db;
|
|
78
94
|
options.onProgress?.((startFrame + frameCount) / totalFrames);
|
|
79
95
|
}
|
|
80
|
-
|
|
96
|
+
if (totalFrames === 0)
|
|
97
|
+
options.onProgress?.(1);
|
|
98
|
+
return { sampleRate, totalFrames, changes };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* @deprecated Audio-only input cannot detect video scenes. Use
|
|
102
|
+
* `detectAudioEnergyChanges` and treat the result as editing candidates.
|
|
103
|
+
*/
|
|
104
|
+
export async function detectScenes(options) {
|
|
105
|
+
const result = await detectAudioEnergyChanges(options);
|
|
106
|
+
return {
|
|
107
|
+
sampleRate: result.sampleRate,
|
|
108
|
+
totalFrames: result.totalFrames,
|
|
109
|
+
scenes: result.changes,
|
|
110
|
+
};
|
|
81
111
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aelionsdk/audio",
|
|
3
|
-
"version": "1.2.0-rc.
|
|
3
|
+
"version": "1.2.0-rc.2",
|
|
4
4
|
"description": "AudioWorklet clock, PCM buffering and audio mixing for AelionSDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"provenance": true
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@aelionsdk/core": "1.2.0-rc.
|
|
38
|
-
"@aelionsdk/render-ir": "1.2.0-rc.
|
|
37
|
+
"@aelionsdk/core": "1.2.0-rc.2",
|
|
38
|
+
"@aelionsdk/render-ir": "1.2.0-rc.2"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsc -b",
|