discord-client/webpack/lib/audio-manager.js

248 lines
5.6 KiB
JavaScript

export default class AudioManager{
static get BUFFER_SIZE(){ return 16384; }
constructor(){
/* (1) Build Audio Context */
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
this.gain = this.ctx.createGain();
/* (3) Create input (typically recorder) */
this.input = null;
/* (4) Create network I/O controllers (WebSocket) */
this.network = {
out: this.ctx.createScriptProcessor(AudioManager.BUFFER_SIZE, 1, 1),
in: null // will contain NETWORK:IN source node
};
/* (5) Bind network routines */
this.network.out.onaudioprocess = this.send.bind(this);
/* (5) Specify node chains */
this.node = {
input: [ this.ctx.createAnalyser() ], // INPUT connects to it
netout: [] // INPUT chains through it until NETOUT
};
/* (6) Create output + bind gain */
this.output = this.ctx.destination;
/* (7) Initialise websocket */
this.ws = null;
}
/* (1) Binds an input stream
*
---------------------------------------------------------*/
bind(){
let current_node = null;
/* (1) Bind INPUT ------> NETWORK:OUT circuit
---------------------------------------------------------*/
current_node = this.input;
/* (1) Connect INPUT to input list */
for( let node of this.node.input )
current_node.connect(node);
/* (2) Chain INPUT to input chain */
for( let node of this.node.netout ){
current_node.connect(node);
current_node = node;
}
/* (3) Finally connect to NETWORK:OUT */
current_node.connect(this.network.out);
/* (2) Bind NETWORK:IN ------> OUTPUT circuit
---------------------------------------------------------*/
// WILL BE DONE ON receive()
/* (1) Finally connect to OUTPUT */
// current_node.connect(this.gain);
this.gain.connect(this.output);
}
/* (2) Binds an input stream
*
---------------------------------------------------------*/
bindRecorderStream(_stream){
/* (1) Bind audio stream */
this.input = this.ctx.createMediaStreamSource(_stream);
}
/* (3) Sharing process implementation
*
---------------------------------------------------------*/
send(_audioprocess){
/*DEBUG*///console.warn('time of', 16*2048/8, 'bytes in ', new Date().getTime()-window.timer);
/*DEBUG*///window.timer = new Date().getTime();
let buf32 = new Float32Array(AudioManager.BUFFER_SIZE);
// _audioprocess.inputBuffer.copyFromChannel(buf32, 0);
_audioprocess.inputBuffer.copyFromChannel(buf32, 0);
let buf16 = this.f32toi16(buf32);
if( this.ws && this.ws.readyState === 1 ) console.log(`send buffer[${buf16.length}]`);
else console.log(`websocket not connected`);
this.ws && this.ws.send(buf16);
}
receive(_buffer){
console.log(`play buffer[${_buffer.length}]`);
/* (1) Convert to Float32Array */
let buf32 = this.i16tof32(_buffer);
/* (2) Create source node */
let source = this.ctx.createBufferSource();
/* (3) Create buffer and dump data */
let input_buffer = this.ctx.createBuffer(1, AudioManager.BUFFER_SIZE, this.ctx.sampleRate);
input_buffer.getChannelData(0).set(buf32);
/* (4) Pass buffer to source node */
source.buffer = input_buffer;
/* (5) Connect and play audio */
source.connect(this.gain);
source.start(0);
}
/* (4) Convert Float32Array to Int16Array
*
* @buf32<Float32Array> Input
*
* @return buf16<Int16Array> Converted output
*
---------------------------------------------------------*/
f32toi16(buf32){
/* (1) Initialise output */
let buf16 = new Int16Array(buf32.length);
/* (2) Initialize loop */
let i = 0, l = buf32.length;
/* (3) Convert each value */
for( ; i < l ; i++ )
buf16[i] = (buf32[i] < 0) ? 0x8000 * buf32[i] : 0x7FFF * buf32[i];
return buf16;
}
/* (2) Convert Int16Array to Float32Array
*
* @buf16<Int16Array> Input
*
* @return buf32<Float32Array> Converted output
*
---------------------------------------------------------*/
i16tof32(buf16){
/* (1) Initialise output */
let buf32 = new Float32Array(buf16.length);
/* (2) Initialize loop */
let i = 0, l = buf16.length;
/* (3) Convert each value */
for( ; i < l ; i++ )
buf32[i] = (buf16[i] >= 0x8000) ? -(0x10000 * buf16[i])/0x8000 : buf16[i] / 0x7FFF;
return buf32;
}
/* (8) Connect websocket
*
* @address<String> Websocket address
*
---------------------------------------------------------*/
wsconnect(_addr){
/* (1) Create websocket connection */
this.ws = new WebSocket(_addr);
/* (2) Manage websocket responses */
this.ws.onmessage = function(_msg){
if( !(_msg.data instanceof Blob) )
return console.warn('[NaB] Not A Blob');
let fr = new FileReader();
fr.onload = function(){
let buf16 = new Int16Array(fr.result);
this.receive(buf16);
}.bind(this);
fr.readAsArrayBuffer(_msg.data);
}.bind(this);
}
/* (x) Access microphone + launch all
*
---------------------------------------------------------*/
launch(wsAddress='wss://ws.douscord.xdrm.io/audio/2'){
/* (1) Start websocket */
this.wsconnect(wsAddress);
window.recorder = null;
if( navigator.mediaDevices && navigator.mediaDevices.getUserMedia ){
navigator.mediaDevices.getUserMedia({ audio: true })
.then( stream => {
recorder = new MediaRecorder(stream);
this.bindRecorderStream(stream);
this.bind();
recorder.onstart = () => console.warn('start');
recorder.onstop = () => {
recorder.stream.getTracks().map( t => t.stop() );
};
// start recording
recorder.start();
})
.catch( e => console.warn('error getting audio stream', e) );
}else
console.warn('getUserMedia() not supported');
}
}