@gjsify/webaudio 0.3.21 → 0.4.3
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/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/audio-buffer-source-node.js +1 -1
- package/lib/esm/audio-buffer.js +1 -1
- package/lib/esm/audio-context.js +1 -1
- package/lib/esm/audio-destination-node.js +1 -1
- package/lib/esm/audio-node.js +1 -1
- package/lib/esm/audio-param.js +1 -1
- package/lib/esm/gain-node.js +1 -1
- package/lib/esm/gst-decoder.js +1 -1
- package/lib/esm/gst-init.js +1 -1
- package/lib/esm/gst-player.js +1 -1
- package/lib/esm/html-audio-element.js +1 -1
- package/package.json +53 -50
- package/src/audio-buffer-source-node.ts +0 -84
- package/src/audio-buffer.ts +0 -47
- package/src/audio-context.ts +0 -102
- package/src/audio-destination-node.ts +0 -12
- package/src/audio-node.ts +0 -37
- package/src/audio-param.ts +0 -103
- package/src/gain-node.ts +0 -23
- package/src/gst-decoder.ts +0 -109
- package/src/gst-init.ts +0 -15
- package/src/gst-player.ts +0 -185
- package/src/html-audio-element.ts +0 -76
- package/src/index.ts +0 -14
- package/src/register.ts +0 -17
- package/src/test.mts +0 -5
- package/src/webaudio.spec.ts +0 -351
- package/tsconfig.json +0 -36
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AudioNode as e}from"./audio-node.js";import{AudioParam as t}from"./audio-param.js";import{GstPlayer as n}from"./gst-player.js";import{GainNode as r}from"./gain-node.js";var AudioBufferSourceNode=class extends e{buffer=null;loop=!1;loopStart=0;loopEnd=0;playbackRate;onended=null;_player=null;_started=!1;constructor(){super(0,1),this.playbackRate=new t(1,.0625,16)}start(e=0,t=0,r){if(this._started)throw new DOMException(`AudioBufferSourceNode can only be started once`,`InvalidStateError`);if(this._started=!0,!this.buffer)return;let i=this._findGainNode(),a=i?i.gain.value:1;this._player=new n({audioBuffer:this.buffer,volume:a,loop:this.loop,offset:t,duration:r,playbackRate:this.playbackRate.value,onEnded:()=>{i&&i._activePlayers.delete(this._player),this._player=null,this.onended?.()}}),i&&this._player&&i._activePlayers.add(this._player)}stop(e=0){this._player&&this._player.stop()}_findGainNode(){for(let e of this._outputs){if(e instanceof r)return e;for(let t of e._outputs)if(t instanceof r)return t}return null}};export{AudioBufferSourceNode};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{AudioNode as e}from"./audio-node.js";import{AudioParam as t}from"./audio-param.js";import{GstPlayer as n}from"./gst-player.js";import{GainNode as r}from"./gain-node.js";var AudioBufferSourceNode=class extends e{buffer=null;loop=!1;loopStart=0;loopEnd=0;playbackRate;onended=null;_player=null;_started=!1;constructor(){super(0,1),this.playbackRate=new t(1,.0625,16)}start(e=0,t=0,r){if(this._started)throw new DOMException(`AudioBufferSourceNode can only be started once`,`InvalidStateError`);if(this._started=!0,!this.buffer)return;let i=this._findGainNode(),a=i?i.gain.value:1;this._player=new n({audioBuffer:this.buffer,volume:a,loop:this.loop,offset:t,duration:r,playbackRate:this.playbackRate.value,onEnded:()=>{i&&i._activePlayers.delete(this._player),this._player=null,this.onended?.()}}),i&&this._player&&i._activePlayers.add(this._player)}stop(e=0){this._player&&this._player.stop()}_findGainNode(){for(let e of this._outputs){if(e instanceof r)return e;for(let t of e._outputs)if(t instanceof r)return t}return null}};export{AudioBufferSourceNode};
|
package/lib/esm/audio-buffer.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var AudioBuffer=class{sampleRate;length;duration;numberOfChannels;_channelData;constructor(e){this.sampleRate=e.sampleRate,this.length=e.length,this.numberOfChannels=e.numberOfChannels,this.duration=this.length/this.sampleRate,this._channelData=[];for(let e=0;e<this.numberOfChannels;e++)this._channelData.push(new Float32Array(this.length))}getChannelData(e){if(e<0||e>=this.numberOfChannels)throw RangeError(`channel index ${e} out of range [0, ${this.numberOfChannels})`);return this._channelData[e]}copyFromChannel(e,t,n=0){let r=this.getChannelData(t),i=Math.min(e.length,r.length-n);e.set(r.subarray(n,n+i))}copyToChannel(e,t,n=0){let r=this.getChannelData(t),i=Math.min(e.length,r.length-n);r.set(e.subarray(0,i),n)}};export{AudioBuffer};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";var AudioBuffer=class{sampleRate;length;duration;numberOfChannels;_channelData;constructor(e){this.sampleRate=e.sampleRate,this.length=e.length,this.numberOfChannels=e.numberOfChannels,this.duration=this.length/this.sampleRate,this._channelData=[];for(let e=0;e<this.numberOfChannels;e++)this._channelData.push(new Float32Array(this.length))}getChannelData(e){if(e<0||e>=this.numberOfChannels)throw RangeError(`channel index ${e} out of range [0, ${this.numberOfChannels})`);return this._channelData[e]}copyFromChannel(e,t,n=0){let r=this.getChannelData(t),i=Math.min(e.length,r.length-n);e.set(r.subarray(n,n+i))}copyToChannel(e,t,n=0){let r=this.getChannelData(t),i=Math.min(e.length,r.length-n);r.set(e.subarray(0,i),n)}};export{AudioBuffer};
|
package/lib/esm/audio-context.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AudioNode as e}from"./audio-node.js";import{ensureGstInit as t}from"./gst-init.js";import{GainNode as n}from"./gain-node.js";import{AudioBufferSourceNode as r}from"./audio-buffer-source-node.js";import{AudioBuffer as i}from"./audio-buffer.js";import{AudioDestinationNode as a}from"./audio-destination-node.js";import{decodeAudioDataSync as o}from"./gst-decoder.js";import s from"gi://GLib?version=2.0";var AudioContext=class{state=`suspended`;sampleRate=44100;destination;listener={};_startTime;constructor(){t(),this._startTime=s.get_monotonic_time(),this.destination=new a}get currentTime(){return(s.get_monotonic_time()-this._startTime)/1e6}createGain(){return new n}createBufferSource(){return new r}createBuffer(e,t,n){return new i({numberOfChannels:e,length:t,sampleRate:n})}decodeAudioData(e,t,n){try{let n=o(e);return t?.(n),Promise.resolve(n)}catch(e){let t=e instanceof DOMException?e:new DOMException(`Unable to decode audio data`,`EncodingError`);return n?.(t),Promise.reject(t)}}async resume(){this.state=`running`}async suspend(){this.state=`suspended`}async close(){this.state=`closed`}createAnalyser(){return{connect:()=>{},disconnect:()=>{},fftSize:2048,frequencyBinCount:1024,getByteFrequencyData:()=>{},getFloatFrequencyData:()=>{}}}createDynamicsCompressor(){return new e}createBiquadFilter(){return new e}createConvolver(){return new e}createPanner(){return new e}createStereoPanner(){return new e}addEventListener(e,t){}removeEventListener(e,t){}};export{AudioContext};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{AudioNode as e}from"./audio-node.js";import{ensureGstInit as t}from"./gst-init.js";import{GainNode as n}from"./gain-node.js";import{AudioBufferSourceNode as r}from"./audio-buffer-source-node.js";import{AudioBuffer as i}from"./audio-buffer.js";import{AudioDestinationNode as a}from"./audio-destination-node.js";import{decodeAudioDataSync as o}from"./gst-decoder.js";import s from"gi://GLib?version=2.0";var AudioContext=class{state=`suspended`;sampleRate=44100;destination;listener={};_startTime;constructor(){t(),this._startTime=s.get_monotonic_time(),this.destination=new a}get currentTime(){return(s.get_monotonic_time()-this._startTime)/1e6}createGain(){return new n}createBufferSource(){return new r}createBuffer(e,t,n){return new i({numberOfChannels:e,length:t,sampleRate:n})}decodeAudioData(e,t,n){try{let n=o(e);return t?.(n),Promise.resolve(n)}catch(e){let t=e instanceof DOMException?e:new DOMException(`Unable to decode audio data`,`EncodingError`);return n?.(t),Promise.reject(t)}}async resume(){this.state=`running`}async suspend(){this.state=`suspended`}async close(){this.state=`closed`}createAnalyser(){return{connect:()=>{},disconnect:()=>{},fftSize:2048,frequencyBinCount:1024,getByteFrequencyData:()=>{},getFloatFrequencyData:()=>{}}}createDynamicsCompressor(){return new e}createBiquadFilter(){return new e}createConvolver(){return new e}createPanner(){return new e}createStereoPanner(){return new e}addEventListener(e,t){}removeEventListener(e,t){}};export{AudioContext};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AudioNode as e}from"./audio-node.js";var AudioDestinationNode=class extends e{maxChannelCount=2;constructor(){super(1,0)}};export{AudioDestinationNode};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{AudioNode as e}from"./audio-node.js";var AudioDestinationNode=class extends e{maxChannelCount=2;constructor(){super(1,0)}};export{AudioDestinationNode};
|
package/lib/esm/audio-node.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var AudioNode=class{_outputs=new Set;_inputs=new Set;numberOfInputs;numberOfOutputs;channelCount;constructor(e=1,t=1){this.numberOfInputs=e,this.numberOfOutputs=t,this.channelCount=2}connect(e){return this._outputs.add(e),e._inputs.add(this),e}disconnect(e){if(e)this._outputs.delete(e),e._inputs.delete(this);else{for(let e of this._outputs)e._inputs.delete(this);this._outputs.clear()}}};export{AudioNode};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";var AudioNode=class{_outputs=new Set;_inputs=new Set;numberOfInputs;numberOfOutputs;channelCount;constructor(e=1,t=1){this.numberOfInputs=e,this.numberOfOutputs=t,this.channelCount=2}connect(e){return this._outputs.add(e),e._inputs.add(this),e}disconnect(e){if(e)this._outputs.delete(e),e._inputs.delete(this);else{for(let e of this._outputs)e._inputs.delete(this);this._outputs.clear()}}};export{AudioNode};
|
package/lib/esm/audio-param.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"gi://GLib?version=2.0";var AudioParam=class{defaultValue;minValue;maxValue;_onChange=null;_value;_rampTimerId=null;constructor(e=0,t=-34028235e31,n=34028235e31){this.defaultValue=e,this.minValue=t,this.maxValue=n,this._value=e}get value(){return this._value}set value(e){this._cancelRamp(),this._value=Math.max(this.minValue,Math.min(this.maxValue,e)),this._onChange?.(this._value)}setValueAtTime(e,t){return this.value=e,this}linearRampToValueAtTime(e,t){return this.value=e,this}exponentialRampToValueAtTime(e,t){return this.value=e,this}setTargetAtTime(t,n,r){if(this._cancelRamp(),r<=0)return this.value=t,this;let i=Math.max(10,Math.round(r*100));return this._rampTimerId=e.timeout_add(e.PRIORITY_DEFAULT,i,()=>{let n=t-this._value;if(Math.abs(n)<.001)return this._value=t,this._onChange?.(this._value),this._rampTimerId=null,e.SOURCE_REMOVE;let a=1-Math.exp(-i/(r*1e3));return this._value+=n*a,this._onChange?.(this._value),e.SOURCE_CONTINUE}),this}setValueCurveAtTime(e,t,n){return this}cancelScheduledValues(e){return this._cancelRamp(),this}cancelAndHoldAtTime(e){return this._cancelRamp(),this}_cancelRamp(){this._rampTimerId!==null&&(e.source_remove(this._rampTimerId),this._rampTimerId=null)}};export{AudioParam};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"gi://GLib?version=2.0";var AudioParam=class{defaultValue;minValue;maxValue;_onChange=null;_value;_rampTimerId=null;constructor(e=0,t=-34028235e31,n=34028235e31){this.defaultValue=e,this.minValue=t,this.maxValue=n,this._value=e}get value(){return this._value}set value(e){this._cancelRamp(),this._value=Math.max(this.minValue,Math.min(this.maxValue,e)),this._onChange?.(this._value)}setValueAtTime(e,t){return this.value=e,this}linearRampToValueAtTime(e,t){return this.value=e,this}exponentialRampToValueAtTime(e,t){return this.value=e,this}setTargetAtTime(t,n,r){if(this._cancelRamp(),r<=0)return this.value=t,this;let i=Math.max(10,Math.round(r*100));return this._rampTimerId=e.timeout_add(e.PRIORITY_DEFAULT,i,()=>{let n=t-this._value;if(Math.abs(n)<.001)return this._value=t,this._onChange?.(this._value),this._rampTimerId=null,e.SOURCE_REMOVE;let a=1-Math.exp(-i/(r*1e3));return this._value+=n*a,this._onChange?.(this._value),e.SOURCE_CONTINUE}),this}setValueCurveAtTime(e,t,n){return this}cancelScheduledValues(e){return this._cancelRamp(),this}cancelAndHoldAtTime(e){return this._cancelRamp(),this}_cancelRamp(){this._rampTimerId!==null&&(e.source_remove(this._rampTimerId),this._rampTimerId=null)}};export{AudioParam};
|
package/lib/esm/gain-node.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AudioNode as e}from"./audio-node.js";import{AudioParam as t}from"./audio-param.js";var GainNode=class extends e{gain;_activePlayers=new Set;constructor(){super(1,1),this.gain=new t(1,0,10),this.gain._onChange=e=>{for(let t of this._activePlayers)t.setVolume(e)}}};export{GainNode};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{AudioNode as e}from"./audio-node.js";import{AudioParam as t}from"./audio-param.js";var GainNode=class extends e{gain;_activePlayers=new Set;constructor(){super(1,1),this.gain=new t(1,0,10),this.gain._onChange=e=>{for(let t of this._activePlayers)t.setVolume(e)}}};export{GainNode};
|
package/lib/esm/gst-decoder.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Gst as e,ensureGstInit as t}from"./gst-init.js";import{AudioBuffer as n}from"./audio-buffer.js";import"gi://GstApp?version=1.0";function decodeAudioDataSync(r){if(t(),!(r instanceof ArrayBuffer)||r.byteLength===0)throw new DOMException(`Unable to decode audio data`,`EncodingError`);let i=e.parse_launch(`appsrc name=src ! decodebin ! audioconvert ! audioresample ! capsfilter caps=audio/x-raw,format=F32LE,layout=interleaved ! appsink name=sink sync=false`),a=i.get_by_name(`src`),o=i.get_by_name(`sink`);i.set_state(e.State.PLAYING);let s=new Uint8Array(r);a.push_buffer(e.Buffer.new_wrapped(s)),a.end_of_stream();let c=[],l=0,u=0;for(;;){let t=o.try_pull_sample(2*Number(e.SECOND));if(!t)break;if(l===0){let e=t.get_caps();if(e){let t=e.get_structure(0);[,l]=t.get_int(`rate`),[,u]=t.get_int(`channels`)}}let n=t.get_buffer();if(!n)continue;let[r,i]=n.map(e.MapFlags.READ);r&&(c.push(new Uint8Array(i.data)),n.unmap(i))}if(i.set_state(e.State.NULL),l===0||u===0)throw new DOMException(`Unable to decode audio data`,`EncodingError`);let d=0;for(let e of c)d+=e.length;let f=d/(4*u),p=new n({numberOfChannels:u,length:f,sampleRate:l}),m=0;for(let e of c){let t=new Float32Array(e.buffer,e.byteOffset,e.length/4),n=t.length/u;for(let e=0;e<n;e++)for(let n=0;n<u;n++)p._channelData[n][m+e]=t[e*u+n];m+=n}return p}export{decodeAudioDataSync};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{Gst as e,ensureGstInit as t}from"./gst-init.js";import{AudioBuffer as n}from"./audio-buffer.js";import"gi://GstApp?version=1.0";function decodeAudioDataSync(r){if(t(),!(r instanceof ArrayBuffer)||r.byteLength===0)throw new DOMException(`Unable to decode audio data`,`EncodingError`);let i=e.parse_launch(`appsrc name=src ! decodebin ! audioconvert ! audioresample ! capsfilter caps=audio/x-raw,format=F32LE,layout=interleaved ! appsink name=sink sync=false`),a=i.get_by_name(`src`),o=i.get_by_name(`sink`);i.set_state(e.State.PLAYING);let s=new Uint8Array(r);a.push_buffer(e.Buffer.new_wrapped(s)),a.end_of_stream();let c=[],l=0,u=0;for(;;){let t=o.try_pull_sample(2*Number(e.SECOND));if(!t)break;if(l===0){let e=t.get_caps();if(e){let t=e.get_structure(0);[,l]=t.get_int(`rate`),[,u]=t.get_int(`channels`)}}let n=t.get_buffer();if(!n)continue;let[r,i]=n.map(e.MapFlags.READ);r&&(c.push(new Uint8Array(i.data)),n.unmap(i))}if(i.set_state(e.State.NULL),l===0||u===0)throw new DOMException(`Unable to decode audio data`,`EncodingError`);let d=0;for(let e of c)d+=e.length;let f=d/(4*u),p=new n({numberOfChannels:u,length:f,sampleRate:l}),m=0;for(let e of c){let t=new Float32Array(e.buffer,e.byteOffset,e.length/4),n=t.length/u;for(let e=0;e<n;e++)for(let n=0;n<u;n++)p._channelData[n][m+e]=t[e*u+n];m+=n}return p}export{decodeAudioDataSync};
|
package/lib/esm/gst-init.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"gi://Gst?version=1.0";let t=!1;function ensureGstInit(){t||=(e.init(null),!0)}export{e as Gst,ensureGstInit};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"gi://Gst?version=1.0";let t=!1;function ensureGstInit(){t||=(e.init(null),!0)}export{e as Gst,ensureGstInit};
|
package/lib/esm/gst-player.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Gst as e,ensureGstInit as t}from"./gst-init.js";import n from"gi://GLib?version=2.0";import"gi://GstApp?version=1.0";var GstPlayer=class{_pipeline=null;_volumeElement=null;_busWatchId=null;_ended=!1;_loop;_onEnded;_audioBuffer;constructor(n){t(),this._loop=n.loop,this._onEnded=n.onEnded,this._audioBuffer=n.audioBuffer;let{audioBuffer:r,volume:i,offset:a,duration:o,playbackRate:s}=n,c=r.sampleRate,l=r.numberOfChannels,u=this._interleave(r,a,o);if(u.length===0){this._fireEnded();return}let d=`appsrc name=src caps="${`audio/x-raw,format=F32LE,rate=${c},channels=${l},layout=interleaved`}" format=3 ! audioconvert ! volume name=vol ! autoaudiosink`;this._pipeline=e.parse_launch(d),this._volumeElement=this._pipeline.get_by_name(`vol`);let f=this._pipeline.get_by_name(`src`);this._volumeElement.set_property(`volume`,Math.max(0,Math.min(i,10)));let p=this._pipeline.get_bus();this._busWatchId=p.add_watch(0,(t,n)=>(n.type===e.MessageType.EOS?this._loop&&!this._ended?this._restartPlayback(f,u):this._fireEnded():n.type===e.MessageType.ERROR&&this._fireEnded(),!0));let m=e.Buffer.new_wrapped(u),h=u.length/(4*l);m.pts=0,m.duration=Math.floor(h/c*Number(e.SECOND)),f.push_buffer(m),f.end_of_stream(),s!==1&&(this._pipeline.set_state(e.State.PAUSED),this._pipeline.seek(s,e.Format.TIME,e.SeekFlags.FLUSH|e.SeekFlags.ACCURATE,e.SeekType.SET,0,e.SeekType.NONE,-1)),this._pipeline.set_state(e.State.PLAYING)}setVolume(e){this._volumeElement&&!this._ended&&this._volumeElement.set_property(`volume`,Math.max(0,Math.min(e,10)))}setLoop(e){this._loop=e}stop(){this._ended||this._fireEnded()}get ended(){return this._ended}_restartPlayback(t,n){this._pipeline&&this._pipeline.seek_simple(e.Format.TIME,e.SeekFlags.FLUSH,0)}_fireEnded(){this._ended||(this._ended=!0,this._cleanup(),this._onEnded())}_cleanup(){let t=this._pipeline;this._pipeline=null,this._volumeElement=null,this._busWatchId=null,t&&n.idle_add(n.PRIORITY_LOW,()=>(t.set_state(e.State.NULL),n.SOURCE_REMOVE))}_interleave(e,t,n){let r=e.numberOfChannels,i=Math.min(Math.floor(t*e.sampleRate),e.length),a=e.length-i,o=n===void 0?a:Math.min(Math.floor(n*e.sampleRate),a);if(o<=0)return new Uint8Array;let s=new Float32Array(o*r);for(let t=0;t<o;t++)for(let n=0;n<r;n++)s[t*r+n]=e._channelData[n][i+t];return new Uint8Array(s.buffer)}};export{GstPlayer};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{Gst as e,ensureGstInit as t}from"./gst-init.js";import n from"gi://GLib?version=2.0";import"gi://GstApp?version=1.0";var GstPlayer=class{_pipeline=null;_volumeElement=null;_busWatchId=null;_ended=!1;_loop;_onEnded;_audioBuffer;constructor(n){t(),this._loop=n.loop,this._onEnded=n.onEnded,this._audioBuffer=n.audioBuffer;let{audioBuffer:r,volume:i,offset:a,duration:o,playbackRate:s}=n,c=r.sampleRate,l=r.numberOfChannels,u=this._interleave(r,a,o);if(u.length===0){this._fireEnded();return}let d=`appsrc name=src caps="${`audio/x-raw,format=F32LE,rate=${c},channels=${l},layout=interleaved`}" format=3 ! audioconvert ! volume name=vol ! autoaudiosink`;this._pipeline=e.parse_launch(d),this._volumeElement=this._pipeline.get_by_name(`vol`);let f=this._pipeline.get_by_name(`src`);this._volumeElement.set_property(`volume`,Math.max(0,Math.min(i,10)));let p=this._pipeline.get_bus();this._busWatchId=p.add_watch(0,(t,n)=>(n.type===e.MessageType.EOS?this._loop&&!this._ended?this._restartPlayback(f,u):this._fireEnded():n.type===e.MessageType.ERROR&&this._fireEnded(),!0));let m=e.Buffer.new_wrapped(u),h=u.length/(4*l);m.pts=0,m.duration=Math.floor(h/c*Number(e.SECOND)),f.push_buffer(m),f.end_of_stream(),s!==1&&(this._pipeline.set_state(e.State.PAUSED),this._pipeline.seek(s,e.Format.TIME,e.SeekFlags.FLUSH|e.SeekFlags.ACCURATE,e.SeekType.SET,0,e.SeekType.NONE,-1)),this._pipeline.set_state(e.State.PLAYING)}setVolume(e){this._volumeElement&&!this._ended&&this._volumeElement.set_property(`volume`,Math.max(0,Math.min(e,10)))}setLoop(e){this._loop=e}stop(){this._ended||this._fireEnded()}get ended(){return this._ended}_restartPlayback(t,n){this._pipeline&&this._pipeline.seek_simple(e.Format.TIME,e.SeekFlags.FLUSH,0)}_fireEnded(){this._ended||(this._ended=!0,this._cleanup(),this._onEnded())}_cleanup(){let t=this._pipeline;this._pipeline=null,this._volumeElement=null,this._busWatchId=null,t&&n.idle_add(n.PRIORITY_LOW,()=>(t.set_state(e.State.NULL),n.SOURCE_REMOVE))}_interleave(e,t,n){let r=e.numberOfChannels,i=Math.min(Math.floor(t*e.sampleRate),e.length),a=e.length-i,o=n===void 0?a:Math.min(Math.floor(n*e.sampleRate),a);if(o<=0)return new Uint8Array;let s=new Float32Array(o*r);for(let t=0;t<o;t++)for(let n=0;n<r;n++)s[t*r+n]=e._channelData[n][i+t];return new Uint8Array(s.buffer)}};export{GstPlayer};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Gst as e,ensureGstInit as t}from"./gst-init.js";const n=new Set([`audio/mpeg`,`audio/mp3`,`audio/wav`,`audio/x-wav`,`audio/ogg`,`audio/webm`,`audio/flac`,`audio/x-flac`,`audio/aac`,`audio/mp4`]);var HTMLAudioElement=class{src=``;volume=1;loop=!1;paused=!0;currentTime=0;duration=0;readyState=0;_pipeline=null;canPlayType(e){let t=e.split(`;`)[0].trim().toLowerCase();return n.has(t)?`maybe`:``}play(){return!this.src||(t(),this._cleanup(),this._pipeline=e.ElementFactory.make(`playbin`,`player`),!this._pipeline)?Promise.resolve():(this._pipeline.set_property(`uri`,this.src),this._pipeline.set_property(`volume`,this.volume),this._pipeline.set_state(e.State.PLAYING),this.paused=!1,Promise.resolve())}pause(){this._pipeline&&(this._pipeline.set_state(e.State.PAUSED),this.paused=!0)}load(){this._cleanup()}addEventListener(e,t){}removeEventListener(e,t){}_cleanup(){this._pipeline&&=(this._pipeline.set_state(e.State.NULL),null)}};export{HTMLAudioElement};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{Gst as e,ensureGstInit as t}from"./gst-init.js";const n=new Set([`audio/mpeg`,`audio/mp3`,`audio/wav`,`audio/x-wav`,`audio/ogg`,`audio/webm`,`audio/flac`,`audio/x-flac`,`audio/aac`,`audio/mp4`]);var HTMLAudioElement=class{src=``;volume=1;loop=!1;paused=!0;currentTime=0;duration=0;readyState=0;_pipeline=null;canPlayType(e){let t=e.split(`;`)[0].trim().toLowerCase();return n.has(t)?`maybe`:``}play(){return!this.src||(t(),this._cleanup(),this._pipeline=e.ElementFactory.make(`playbin`,`player`),!this._pipeline)?Promise.resolve():(this._pipeline.set_property(`uri`,this.src),this._pipeline.set_property(`volume`,this.volume),this._pipeline.set_state(e.State.PLAYING),this.paused=!1,Promise.resolve())}pause(){this._pipeline&&(this._pipeline.set_state(e.State.PAUSED),this.paused=!0)}load(){this._cleanup()}addEventListener(e,t){}removeEventListener(e,t){}_cleanup(){this._pipeline&&=(this._pipeline.set_state(e.State.NULL),null)}};export{HTMLAudioElement};
|
package/package.json
CHANGED
|
@@ -1,53 +1,56 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
"name": "@gjsify/webaudio",
|
|
3
|
+
"version": "0.4.3",
|
|
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
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
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": "workspace:^"
|
|
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": "workspace:^",
|
|
52
|
+
"@gjsify/unit": "workspace:^",
|
|
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.3.21"
|
|
40
|
-
},
|
|
41
|
-
"devDependencies": {
|
|
42
|
-
"@girs/gjs": "4.0.0-rc.14",
|
|
43
|
-
"@girs/glib-2.0": "2.88.0-4.0.0-rc.14",
|
|
44
|
-
"@girs/gst-1.0": "1.28.1-4.0.0-rc.14",
|
|
45
|
-
"@girs/gstapp-1.0": "1.0.0-4.0.0-rc.14",
|
|
46
|
-
"@girs/gstaudio-1.0": "1.0.0-4.0.0-rc.14",
|
|
47
|
-
"@girs/gstbase-1.0": "1.0.0-4.0.0-rc.14",
|
|
48
|
-
"@gjsify/cli": "^0.3.21",
|
|
49
|
-
"@gjsify/unit": "^0.3.21",
|
|
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
|
-
}
|
package/src/audio-buffer.ts
DELETED
|
@@ -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
|
-
}
|
package/src/audio-context.ts
DELETED
|
@@ -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
|
-
}
|
package/src/audio-param.ts
DELETED
|
@@ -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
|
-
}
|