blob: a175987a0c03cd7e76b5fa6f729b4645a86a11eb [file] [log] [blame] [raw]
"use strict";
/**
* Note: Uses AudioContext.createScriptProcessor, which is deprecated,
* but which no satisfactory substitute is availble.
* @constructor
* @param {BusConnector} bus
* @suppress {deprecated}
*/
function SpeakerAdapter(bus)
{
if(typeof window === "undefined")
{
return;
}
if(!window.AudioContext && !window.webkitAudioContext)
{
console.warn("Web browser doesn't support Web Audio API");
return;
}
/** @const @type {BusConnector} */
this.bus = bus;
this.audio_context = new (window.AudioContext || window.webkitAudioContext)();
// TODO: Find / calibrate / verify the filter frequencies
this.beep_oscillator = this.audio_context.createOscillator();
this.beep_oscillator.type = "square";
this.beep_oscillator.frequency.setValueAtTime(440, this.audio_context.currentTime);
this.beep_gain = this.audio_context.createGain();
this.beep_gain.gain.setValueAtTime(0, this.audio_context.currentTime);
this.dac_processor = this.audio_context.createScriptProcessor(2048, 0, 2);
this.dac_processor.onaudioprocess = this.dac_process.bind(this);
this.dac_gain_left = this.audio_context.createGain();
this.dac_gain_left.gain.setValueAtTime(1, this.audio_context.currentTime);
this.dac_gain_right = this.audio_context.createGain();
this.dac_gain_right.gain.setValueAtTime(1, this.audio_context.currentTime);
this.dac_splitter = this.audio_context.createChannelSplitter(2);
this.dac_buffer0 = new Float32Array(this.dac_processor.bufferSize);
this.dac_buffer1 = new Float32Array(this.dac_processor.bufferSize);
this.master_splitter = this.audio_context.createChannelSplitter(2);
this.master_volume_left = this.audio_context.createGain();
this.master_volume_right = this.audio_context.createGain();
this.master_treble_left = this.audio_context.createBiquadFilter();
this.master_treble_right = this.audio_context.createBiquadFilter();
this.master_treble_left.type = "highshelf";
this.master_treble_right.type = "highshelf";
this.master_treble_left.frequency.setValueAtTime(2000, this.audio_context.currentTime);
this.master_treble_right.frequency.setValueAtTime(2000, this.audio_context.currentTime);
this.master_bass_left = this.audio_context.createBiquadFilter();
this.master_bass_right = this.audio_context.createBiquadFilter();
this.master_bass_left.type = "lowshelf";
this.master_bass_right.type = "lowshelf";
this.master_bass_left.frequency.setValueAtTime(200, this.audio_context.currentTime);
this.master_bass_right.frequency.setValueAtTime(200, this.audio_context.currentTime);
this.master_gain_left = this.audio_context.createGain();
this.master_gain_right = this.audio_context.createGain();
this.master_merger = this.audio_context.createChannelMerger(2);
// Mixer Graph
// Don't initially connect beep oscillator
this.beep_gain
.connect(this.master_splitter);
this.dac_processor
.connect(this.dac_splitter);
this.dac_splitter
.connect(this.dac_gain_left, 0)
.connect(this.master_volume_left);
this.dac_splitter
.connect(this.dac_gain_right, 1)
.connect(this.master_volume_right);
this.master_splitter
.connect(this.master_volume_left, 0)
/* Treble and bass disabled: leads to lag and noise
.connect(this.master_treble_left)
.connect(this.master_bass_left)
.connect(this.master_gain_left)
*/
.connect(this.master_merger, 0, 0);
this.master_splitter
.connect(this.master_volume_right, 1)
/* Treble and bass disabled: leads to lag and noise
.connect(this.master_treble_right)
.connect(this.master_bass_right)
.connect(this.master_gain_right)
*/
.connect(this.master_merger, 0, 1);
this.master_merger
.connect(this.audio_context.destination);
// Mixer Switches
bus.register("mixer-pcspeaker-connect", function()
{
this.beep_gain.connect(this.master_splitter);
}, this);
bus.register("mixer-pcspeaker-disconnect", function()
{
this.beep_gain.disconnect();
}, this);
bus.register("mixer-dac-connect", function()
{
this.dac_gain_left.connect(this.master_volume_right);
this.dac_gain_right.connect(this.master_volume_right);
}, this);
bus.register("mixer-dac-disconnect", function()
{
this.dac_gain_left.disconnect();
this.dac_gain_right.disconnect();
}, this);
// Mixer Levels
function create_volume_handler(audio_node, scaling, in_decibels)
{
if(in_decibels)
{
return function(decibels)
{
audio_node.gain.setValueAtTime(decibels, this.audio_context.currentTime);
};
}
else
{
return function(decibels)
{
var gain = Math.pow(10, decibels / 20) * scaling;
audio_node.gain.setValueAtTime(gain, this.audio_context.currentTime);
};
}
};
bus.register("mixer-pcspeaker-volume",
create_volume_handler(this.beep_gain, 1, false), this);
bus.register("mixer-dac-volume-left",
create_volume_handler(this.dac_gain_left, 3, false), this);
bus.register("mixer-dac-volume-right",
create_volume_handler(this.dac_gain_right, 1, false), this);
bus.register("mixer-master-volume-left",
create_volume_handler(this.master_volume_left, 1, false), this);
bus.register("mixer-master-volume-right",
create_volume_handler(this.master_volume_right, 1, false), this);
bus.register("mixer-master-gain-left",
create_volume_handler(this.master_gain_left, 1, false), this);
bus.register("mixer-master-gain-right",
create_volume_handler(this.master_gain_right, 1, false), this);
bus.register("mixer-master-treble-left",
create_volume_handler(this.master_treble_left, 1, true), this);
bus.register("mixer-master-treble-right",
create_volume_handler(this.master_treble_right, 1, true), this);
bus.register("mixer-master-bass-left",
create_volume_handler(this.master_bass_left, 1, true), this);
bus.register("mixer-master-bass-right",
create_volume_handler(this.master_bass_right, 1, true), this);
// Emulator Events
bus.register("emulator-stopped", function()
{
this.audio_context.suspend();
}, this);
bus.register("emulator-started", function()
{
this.audio_context.resume();
}, this);
// PC Speaker
bus.register("pcspeaker-enable", function()
{
this.beep_oscillator.connect(this.beep_gain);
}, this);
bus.register("pcspeaker-disable", function()
{
this.beep_oscillator.disconnect();
}, this);
bus.register("pcspeaker-update", function(data)
{
var counter_mode = data[0];
var counter_reload = data[1];
var frequency = 0;
var beep_enabled = counter_mode === 3;
if(beep_enabled)
{
frequency = OSCILLATOR_FREQ * 1000 / counter_reload;
frequency = Math.min(frequency, this.beep_oscillator.frequency.maxValue);
frequency = Math.max(frequency, 0);
}
this.beep_oscillator.frequency.setValueAtTime(frequency, this.audio_context.currentTime);
}, this);
// DAC
bus.register("dac-update-data", function(data)
{
this.dac_buffer0 = data[0];
this.dac_buffer1 = data[1];
}, this);
bus.register("dac-request-samplerate", function()
{
bus.send("dac-tell-samplerate", this.audio_context.sampleRate);
}, this);
bus.send("dac-tell-samplerate", this.audio_context.sampleRate);
bus.register("dac-enable", function(enabled)
{
this.dac_processor.connect(this.dac_splitter);
}, this);
bus.register("dac-disable", function()
{
this.dac_processor.disconnect();
}, this);
if(DEBUG)
{
this.debug_dac = false;
this.debug_dac_out = [];
window["speaker_debug_dac_out"] = this.debug_dac_out;
window["speaker_debug_start"] = () =>
{
this.debug_dac = true;
setTimeout(() =>
{
this.debug_dac = false;
},250);
}
}
// Start Nodes
this.beep_oscillator.start();
}
SpeakerAdapter.prototype.dac_process = function(event)
{
var out = event.outputBuffer;
out.copyToChannel(this.dac_buffer0, 0);
out.copyToChannel(this.dac_buffer1, 1);
this.bus.send("dac-request-data", out.length);
if(DEBUG)
{
if(this.debug_dac)
{
this.debug_dac_out.push(event.outputBuffer.getChannelData(0).slice());
}
}
};