@gjsify/webaudio 0.4.0 → 0.4.4

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/package.json CHANGED
@@ -1,53 +1,56 @@
1
1
  {
2
- "name": "@gjsify/webaudio",
3
- "version": "0.4.0",
4
- "description": "Web Audio API for GJS using GStreamer as audio backend",
5
- "type": "module",
6
- "module": "lib/esm/index.js",
7
- "types": "lib/types/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./lib/types/index.d.ts",
11
- "default": "./lib/esm/index.js"
2
+ "name": "@gjsify/webaudio",
3
+ "version": "0.4.4",
4
+ "description": "Web Audio API for GJS using GStreamer as audio backend",
5
+ "type": "module",
6
+ "module": "lib/esm/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/esm/index.js"
12
+ },
13
+ "./register": {
14
+ "types": "./lib/types/register.d.ts",
15
+ "default": "./lib/esm/register.js"
16
+ }
12
17
  },
13
- "./register": {
14
- "types": "./lib/types/register.d.ts",
15
- "default": "./lib/esm/register.js"
18
+ "files": [
19
+ "lib"
20
+ ],
21
+ "sideEffects": [
22
+ "./lib/esm/register.js"
23
+ ],
24
+ "scripts": {
25
+ "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs || exit 0",
26
+ "check": "tsc --noEmit",
27
+ "build": "gjsify run build:gjsify && gjsify run build:types",
28
+ "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
29
+ "build:types": "tsc",
30
+ "build:test": "gjsify run build:test:gjs",
31
+ "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
32
+ "test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:gjs",
33
+ "test:gjs": "gjsify run test.gjs.mjs"
34
+ },
35
+ "keywords": [
36
+ "gjs",
37
+ "webaudio",
38
+ "gstreamer",
39
+ "audio-api"
40
+ ],
41
+ "dependencies": {
42
+ "@gjsify/dom-events": "^0.4.4"
43
+ },
44
+ "devDependencies": {
45
+ "@girs/gjs": "4.0.0-rc.15",
46
+ "@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
47
+ "@girs/gst-1.0": "1.28.1-4.0.0-rc.15",
48
+ "@girs/gstapp-1.0": "1.0.0-4.0.0-rc.15",
49
+ "@girs/gstaudio-1.0": "1.0.0-4.0.0-rc.15",
50
+ "@girs/gstbase-1.0": "1.0.0-4.0.0-rc.15",
51
+ "@gjsify/cli": "^0.4.4",
52
+ "@gjsify/unit": "^0.4.4",
53
+ "@types/node": "^25.6.2",
54
+ "typescript": "^6.0.3"
16
55
  }
17
- },
18
- "sideEffects": [
19
- "./lib/esm/register.js"
20
- ],
21
- "scripts": {
22
- "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs || exit 0",
23
- "check": "tsc --noEmit",
24
- "build": "yarn build:gjsify && yarn build:types",
25
- "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
26
- "build:types": "tsc",
27
- "build:test": "yarn build:test:gjs",
28
- "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
29
- "test": "yarn build:gjsify && yarn build:test && yarn test:gjs",
30
- "test:gjs": "gjsify run test.gjs.mjs"
31
- },
32
- "keywords": [
33
- "gjs",
34
- "webaudio",
35
- "gstreamer",
36
- "audio-api"
37
- ],
38
- "dependencies": {
39
- "@gjsify/dom-events": "^0.4.0"
40
- },
41
- "devDependencies": {
42
- "@girs/gjs": "4.0.0-rc.15",
43
- "@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
44
- "@girs/gst-1.0": "1.28.1-4.0.0-rc.15",
45
- "@girs/gstapp-1.0": "1.0.0-4.0.0-rc.15",
46
- "@girs/gstaudio-1.0": "1.0.0-4.0.0-rc.15",
47
- "@girs/gstbase-1.0": "1.0.0-4.0.0-rc.15",
48
- "@gjsify/cli": "^0.4.0",
49
- "@gjsify/unit": "^0.4.0",
50
- "@types/node": "^25.6.2",
51
- "typescript": "^6.0.3"
52
- }
53
- }
56
+ }
@@ -1,84 +0,0 @@
1
- // AudioBufferSourceNode — single-use audio playback node backed by GStreamer.
2
- //
3
- // W3C spec: each source node can only be started once. Excalibur creates a new
4
- // AudioBufferSourceNode for every play via _createNewBufferSource().
5
- //
6
- // Audio graph: AudioBufferSourceNode → GainNode → AudioDestinationNode
7
- //
8
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode
9
-
10
- import { AudioNode } from './audio-node.js';
11
- import { AudioParam } from './audio-param.js';
12
- import { GstPlayer } from './gst-player.js';
13
- import { GainNode } from './gain-node.js';
14
- import type { AudioBuffer } from './audio-buffer.js';
15
-
16
- export class AudioBufferSourceNode extends AudioNode {
17
- buffer: AudioBuffer | null = null;
18
- loop = false;
19
- loopStart = 0;
20
- loopEnd = 0;
21
- readonly playbackRate: AudioParam;
22
- onended: (() => void) | null = null;
23
-
24
- private _player: GstPlayer | null = null;
25
- private _started = false;
26
-
27
- constructor() {
28
- super(0, 1); // 0 inputs (source node), 1 output
29
- this.playbackRate = new AudioParam(1, 0.0625, 16);
30
- }
31
-
32
- start(when = 0, offset = 0, duration?: number): void {
33
- if (this._started) {
34
- throw new DOMException('AudioBufferSourceNode can only be started once', 'InvalidStateError');
35
- }
36
- this._started = true;
37
-
38
- if (!this.buffer) return;
39
-
40
- // Walk connection chain to find GainNode and its volume
41
- const gainNode = this._findGainNode();
42
- const volume = gainNode ? gainNode.gain.value : 1;
43
-
44
- this._player = new GstPlayer({
45
- audioBuffer: this.buffer,
46
- volume,
47
- loop: this.loop,
48
- offset,
49
- duration,
50
- playbackRate: this.playbackRate.value,
51
- onEnded: () => {
52
- // Unregister from GainNode
53
- if (gainNode) {
54
- gainNode._activePlayers.delete(this._player!);
55
- }
56
- this._player = null;
57
- this.onended?.();
58
- },
59
- });
60
-
61
- // Register with GainNode for live volume updates
62
- if (gainNode && this._player) {
63
- gainNode._activePlayers.add(this._player);
64
- }
65
- }
66
-
67
- stop(_when = 0): void {
68
- if (this._player) {
69
- this._player.stop();
70
- }
71
- }
72
-
73
- /** Walk the output chain to find a GainNode */
74
- private _findGainNode(): GainNode | null {
75
- for (const node of this._outputs) {
76
- if (node instanceof GainNode) return node;
77
- // Check one level deeper (in case of intermediary nodes)
78
- for (const inner of node._outputs) {
79
- if (inner instanceof GainNode) return inner;
80
- }
81
- }
82
- return null;
83
- }
84
- }
@@ -1,47 +0,0 @@
1
- // AudioBuffer — holds decoded PCM audio data as per-channel Float32Arrays.
2
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer
3
-
4
- export interface AudioBufferOptions {
5
- numberOfChannels: number;
6
- length: number;
7
- sampleRate: number;
8
- }
9
-
10
- export class AudioBuffer {
11
- readonly sampleRate: number;
12
- readonly length: number;
13
- readonly duration: number;
14
- readonly numberOfChannels: number;
15
- /** @internal */
16
- _channelData: Float32Array[];
17
-
18
- constructor(options: AudioBufferOptions) {
19
- this.sampleRate = options.sampleRate;
20
- this.length = options.length;
21
- this.numberOfChannels = options.numberOfChannels;
22
- this.duration = this.length / this.sampleRate;
23
- this._channelData = [];
24
- for (let i = 0; i < this.numberOfChannels; i++) {
25
- this._channelData.push(new Float32Array(this.length));
26
- }
27
- }
28
-
29
- getChannelData(channel: number): Float32Array {
30
- if (channel < 0 || channel >= this.numberOfChannels) {
31
- throw new RangeError(`channel index ${channel} out of range [0, ${this.numberOfChannels})`);
32
- }
33
- return this._channelData[channel];
34
- }
35
-
36
- copyFromChannel(destination: Float32Array, channelNumber: number, bufferOffset = 0): void {
37
- const src = this.getChannelData(channelNumber);
38
- const len = Math.min(destination.length, src.length - bufferOffset);
39
- destination.set(src.subarray(bufferOffset, bufferOffset + len));
40
- }
41
-
42
- copyToChannel(source: Float32Array, channelNumber: number, bufferOffset = 0): void {
43
- const dest = this.getChannelData(channelNumber);
44
- const len = Math.min(source.length, dest.length - bufferOffset);
45
- dest.set(source.subarray(0, len), bufferOffset);
46
- }
47
- }
@@ -1,102 +0,0 @@
1
- // AudioContext — top-level Web Audio API entry point backed by GStreamer.
2
- //
3
- // Phase 1: covers Excalibur.js needs (decodeAudioData, createBufferSource,
4
- // createGain, currentTime, resume/suspend/close).
5
- //
6
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/AudioContext
7
-
8
- import GLib from 'gi://GLib?version=2.0';
9
- import { ensureGstInit } from './gst-init.js';
10
- import { AudioBuffer } from './audio-buffer.js';
11
- import { AudioNode } from './audio-node.js';
12
- import { AudioDestinationNode } from './audio-destination-node.js';
13
- import { AudioBufferSourceNode } from './audio-buffer-source-node.js';
14
- import { GainNode } from './gain-node.js';
15
- import { decodeAudioDataSync } from './gst-decoder.js';
16
-
17
- export class AudioContext {
18
- state: AudioContextState = 'suspended';
19
- readonly sampleRate = 44100;
20
- readonly destination: AudioDestinationNode;
21
- readonly listener = {};
22
-
23
- private _startTime: number;
24
-
25
- constructor() {
26
- ensureGstInit();
27
- this._startTime = GLib.get_monotonic_time();
28
- this.destination = new AudioDestinationNode();
29
- }
30
-
31
- /** Monotonically increasing time in seconds since context creation. */
32
- get currentTime(): number {
33
- return (GLib.get_monotonic_time() - this._startTime) / 1_000_000;
34
- }
35
-
36
- createGain(): GainNode {
37
- return new GainNode();
38
- }
39
-
40
- createBufferSource(): AudioBufferSourceNode {
41
- return new AudioBufferSourceNode();
42
- }
43
-
44
- createBuffer(numberOfChannels: number, length: number, sampleRate: number): AudioBuffer {
45
- return new AudioBuffer({ numberOfChannels, length, sampleRate });
46
- }
47
-
48
- /**
49
- * Decode encoded audio data (MP3, WAV, OGG, etc.) into an AudioBuffer.
50
- * Uses GStreamer's decodebin for format-agnostic decoding.
51
- */
52
- decodeAudioData(
53
- arrayBuffer: ArrayBuffer,
54
- successCallback?: (buffer: AudioBuffer) => void,
55
- errorCallback?: (error: DOMException) => void
56
- ): Promise<AudioBuffer> {
57
- try {
58
- const buffer = decodeAudioDataSync(arrayBuffer);
59
- successCallback?.(buffer);
60
- return Promise.resolve(buffer);
61
- } catch (err) {
62
- const domErr = err instanceof DOMException
63
- ? err
64
- : new DOMException('Unable to decode audio data', 'EncodingError');
65
- errorCallback?.(domErr);
66
- return Promise.reject(domErr);
67
- }
68
- }
69
-
70
- async resume(): Promise<void> {
71
- this.state = 'running';
72
- }
73
-
74
- async suspend(): Promise<void> {
75
- this.state = 'suspended';
76
- }
77
-
78
- async close(): Promise<void> {
79
- this.state = 'closed';
80
- }
81
-
82
- // Stub methods for APIs not yet backed by GStreamer (Phase 3)
83
- createAnalyser(): any {
84
- return {
85
- connect: () => {},
86
- disconnect: () => {},
87
- fftSize: 2048,
88
- frequencyBinCount: 1024,
89
- getByteFrequencyData: () => {},
90
- getFloatFrequencyData: () => {},
91
- };
92
- }
93
-
94
- createDynamicsCompressor(): AudioNode { return new AudioNode(); }
95
- createBiquadFilter(): any { return new AudioNode(); }
96
- createConvolver(): AudioNode { return new AudioNode(); }
97
- createPanner(): AudioNode { return new AudioNode(); }
98
- createStereoPanner(): AudioNode { return new AudioNode(); }
99
-
100
- addEventListener(_type: string, _listener: any): void {}
101
- removeEventListener(_type: string, _listener: any): void {}
102
- }
@@ -1,12 +0,0 @@
1
- // AudioDestinationNode — represents the final audio output (speakers).
2
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/AudioDestinationNode
3
-
4
- import { AudioNode } from './audio-node.js';
5
-
6
- export class AudioDestinationNode extends AudioNode {
7
- readonly maxChannelCount = 2;
8
-
9
- constructor() {
10
- super(1, 0); // 1 input, 0 outputs (terminal node)
11
- }
12
- }
package/src/audio-node.ts DELETED
@@ -1,37 +0,0 @@
1
- // AudioNode — base class for all audio graph nodes.
2
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/AudioNode
3
-
4
- export class AudioNode {
5
- /** @internal downstream connections */
6
- _outputs: Set<AudioNode> = new Set();
7
- /** @internal upstream connections */
8
- _inputs: Set<AudioNode> = new Set();
9
-
10
- readonly numberOfInputs: number;
11
- readonly numberOfOutputs: number;
12
- readonly channelCount: number;
13
-
14
- constructor(numberOfInputs = 1, numberOfOutputs = 1) {
15
- this.numberOfInputs = numberOfInputs;
16
- this.numberOfOutputs = numberOfOutputs;
17
- this.channelCount = 2;
18
- }
19
-
20
- connect(destination: AudioNode): AudioNode {
21
- this._outputs.add(destination);
22
- destination._inputs.add(this);
23
- return destination;
24
- }
25
-
26
- disconnect(destination?: AudioNode): void {
27
- if (destination) {
28
- this._outputs.delete(destination);
29
- destination._inputs.delete(this);
30
- } else {
31
- for (const node of this._outputs) {
32
- node._inputs.delete(this);
33
- }
34
- this._outputs.clear();
35
- }
36
- }
37
- }
@@ -1,103 +0,0 @@
1
- // AudioParam — holds a value with scheduling support.
2
- // Phase 1: direct .value + setTargetAtTime (used by Excalibur.js).
3
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/AudioParam
4
-
5
- import GLib from 'gi://GLib?version=2.0';
6
-
7
- export class AudioParam {
8
- readonly defaultValue: number;
9
- readonly minValue: number;
10
- readonly maxValue: number;
11
-
12
- /** @internal callback invoked when value changes */
13
- _onChange: ((value: number) => void) | null = null;
14
-
15
- private _value: number;
16
- private _rampTimerId: number | null = null;
17
-
18
- constructor(defaultValue = 0, minValue = -3.4028235e38, maxValue = 3.4028235e38) {
19
- this.defaultValue = defaultValue;
20
- this.minValue = minValue;
21
- this.maxValue = maxValue;
22
- this._value = defaultValue;
23
- }
24
-
25
- get value(): number {
26
- return this._value;
27
- }
28
-
29
- set value(v: number) {
30
- this._cancelRamp();
31
- this._value = Math.max(this.minValue, Math.min(this.maxValue, v));
32
- this._onChange?.(this._value);
33
- }
34
-
35
- setValueAtTime(value: number, _startTime: number): AudioParam {
36
- // Phase 1: apply immediately (ignore scheduling)
37
- this.value = value;
38
- return this;
39
- }
40
-
41
- linearRampToValueAtTime(value: number, _endTime: number): AudioParam {
42
- // Phase 1: apply immediately
43
- this.value = value;
44
- return this;
45
- }
46
-
47
- exponentialRampToValueAtTime(value: number, _endTime: number): AudioParam {
48
- // Phase 1: apply immediately
49
- this.value = value;
50
- return this;
51
- }
52
-
53
- setTargetAtTime(target: number, _startTime: number, timeConstant: number): AudioParam {
54
- // Exponential approach used by Excalibur for smooth volume transitions.
55
- // After each timeConstant interval, value moves ~63.2% closer to target.
56
- this._cancelRamp();
57
-
58
- if (timeConstant <= 0) {
59
- this.value = target;
60
- return this;
61
- }
62
-
63
- const stepMs = Math.max(10, Math.round(timeConstant * 100)); // ~10 steps per timeConstant
64
- this._rampTimerId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, stepMs, () => {
65
- const diff = target - this._value;
66
- if (Math.abs(diff) < 0.001) {
67
- this._value = target;
68
- this._onChange?.(this._value);
69
- this._rampTimerId = null;
70
- return GLib.SOURCE_REMOVE;
71
- }
72
- // Exponential approach: move 1 - e^(-dt/tc) closer per step
73
- const factor = 1 - Math.exp(-stepMs / (timeConstant * 1000));
74
- this._value += diff * factor;
75
- this._onChange?.(this._value);
76
- return GLib.SOURCE_CONTINUE;
77
- });
78
-
79
- return this;
80
- }
81
-
82
- setValueCurveAtTime(_values: Float32Array, _startTime: number, _duration: number): AudioParam {
83
- // Phase 1: no-op
84
- return this;
85
- }
86
-
87
- cancelScheduledValues(_startTime: number): AudioParam {
88
- this._cancelRamp();
89
- return this;
90
- }
91
-
92
- cancelAndHoldAtTime(_cancelTime: number): AudioParam {
93
- this._cancelRamp();
94
- return this;
95
- }
96
-
97
- private _cancelRamp(): void {
98
- if (this._rampTimerId !== null) {
99
- GLib.source_remove(this._rampTimerId);
100
- this._rampTimerId = null;
101
- }
102
- }
103
- }
package/src/gain-node.ts DELETED
@@ -1,23 +0,0 @@
1
- // GainNode — controls audio volume via an AudioParam.
2
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/GainNode
3
-
4
- import { AudioNode } from './audio-node.js';
5
- import { AudioParam } from './audio-param.js';
6
- import type { GstPlayer } from './gst-player.js';
7
-
8
- export class GainNode extends AudioNode {
9
- readonly gain: AudioParam;
10
-
11
- /** @internal active players that need volume updates */
12
- _activePlayers: Set<GstPlayer> = new Set();
13
-
14
- constructor() {
15
- super(1, 1);
16
- this.gain = new AudioParam(1, 0, 10);
17
- this.gain._onChange = (value) => {
18
- for (const player of this._activePlayers) {
19
- player.setVolume(value);
20
- }
21
- };
22
- }
23
- }
@@ -1,109 +0,0 @@
1
- // GStreamer decode pipeline: ArrayBuffer (MP3/WAV/OGG) → AudioBuffer (PCM Float32)
2
- //
3
- // Pipeline: appsrc → decodebin → audioconvert → audioresample → capsfilter(F32LE) → appsink
4
- // Uses try_pull_sample() for synchronous decoding (avoids GJS thread-safety issues).
5
- //
6
- // Reference: GStreamer 1.0 via gi://Gst, GstApp via gi://GstApp
7
-
8
- import { ensureGstInit, Gst } from './gst-init.js';
9
- import { AudioBuffer } from './audio-buffer.js';
10
-
11
- // Force GstApp typelib load so get_by_name() resolves AppSrc/AppSink types
12
- import GstApp from 'gi://GstApp?version=1.0';
13
- void GstApp;
14
-
15
- const PIPELINE_DESC =
16
- 'appsrc name=src ! decodebin ! audioconvert ! audioresample ! ' +
17
- 'capsfilter caps=audio/x-raw,format=F32LE,layout=interleaved ! ' +
18
- 'appsink name=sink sync=false';
19
-
20
- /**
21
- * Decode encoded audio data (MP3, WAV, OGG, FLAC, etc.) into an AudioBuffer
22
- * containing PCM Float32 channel data.
23
- *
24
- * This is a synchronous operation that blocks until decoding completes.
25
- * It must be called from the main thread (GJS requirement).
26
- */
27
- export function decodeAudioDataSync(arrayBuffer: ArrayBuffer): AudioBuffer {
28
- ensureGstInit();
29
-
30
- // Reject non-ArrayBuffer / empty input before touching GStreamer —
31
- // gst_memory_new_wrapped() asserts data != NULL and empty TypedArrays
32
- // marshal to a NULL pointer through GI.
33
- if (!(arrayBuffer instanceof ArrayBuffer) || arrayBuffer.byteLength === 0) {
34
- throw new DOMException('Unable to decode audio data', 'EncodingError');
35
- }
36
-
37
- const pipeline = Gst.parse_launch(PIPELINE_DESC) as Gst.Bin;
38
- const appsrc = pipeline.get_by_name('src')!;
39
- const appsink = pipeline.get_by_name('sink')!;
40
-
41
- pipeline.set_state(Gst.State.PLAYING);
42
-
43
- // Push encoded data into the pipeline
44
- const data = new Uint8Array(arrayBuffer);
45
- (appsrc as any).push_buffer(Gst.Buffer.new_wrapped(data));
46
- (appsrc as any).end_of_stream();
47
-
48
- // Pull decoded PCM samples
49
- const chunks: Uint8Array[] = [];
50
- let sampleRate = 0;
51
- let channels = 0;
52
-
53
- while (true) {
54
- const sample = (appsink as any).try_pull_sample(2 * Number(Gst.SECOND));
55
- if (!sample) break;
56
-
57
- // Read format from the first sample's negotiated caps
58
- if (sampleRate === 0) {
59
- const caps = sample.get_caps();
60
- if (caps) {
61
- const struct = caps.get_structure(0);
62
- [, sampleRate] = struct.get_int('rate');
63
- [, channels] = struct.get_int('channels');
64
- }
65
- }
66
-
67
- const buffer = sample.get_buffer();
68
- if (!buffer) continue;
69
-
70
- const [ok, mapInfo] = buffer.map(Gst.MapFlags.READ);
71
- if (ok) {
72
- // Copy data — mapInfo.data is only valid until unmap
73
- chunks.push(new Uint8Array(mapInfo.data));
74
- buffer.unmap(mapInfo);
75
- }
76
- }
77
-
78
- pipeline.set_state(Gst.State.NULL);
79
-
80
- if (sampleRate === 0 || channels === 0) {
81
- throw new DOMException('Unable to decode audio data', 'EncodingError');
82
- }
83
-
84
- // Concatenate chunks into a single interleaved Float32 buffer
85
- let totalBytes = 0;
86
- for (const c of chunks) totalBytes += c.length;
87
- const totalFrames = totalBytes / (4 * channels);
88
-
89
- const audioBuffer = new AudioBuffer({
90
- numberOfChannels: channels,
91
- length: totalFrames,
92
- sampleRate,
93
- });
94
-
95
- // De-interleave into per-channel Float32Arrays
96
- let offset = 0;
97
- for (const chunk of chunks) {
98
- const f32 = new Float32Array(chunk.buffer, chunk.byteOffset, chunk.length / 4);
99
- const framesInChunk = f32.length / channels;
100
- for (let frame = 0; frame < framesInChunk; frame++) {
101
- for (let ch = 0; ch < channels; ch++) {
102
- audioBuffer._channelData[ch][offset + frame] = f32[frame * channels + ch];
103
- }
104
- }
105
- offset += framesInChunk;
106
- }
107
-
108
- return audioBuffer;
109
- }
package/src/gst-init.ts DELETED
@@ -1,15 +0,0 @@
1
- // Lazy GStreamer initialization — call ensureGstInit() before any Gst API usage.
2
- // Reference: GStreamer 1.0 via gi://Gst
3
-
4
- import Gst from 'gi://Gst?version=1.0';
5
-
6
- let initialized = false;
7
-
8
- export function ensureGstInit(): void {
9
- if (!initialized) {
10
- Gst.init(null);
11
- initialized = true;
12
- }
13
- }
14
-
15
- export { Gst };