ScriptedAmigaEmulator/sae/audio.js
Rupert Hausberger 0c5b018866 update 0.7.0
2012-12-22 19:39:33 +01:00

1126 lines
32 KiB
JavaScript

/**************************************************************************
* SAE - Scripted Amiga Emulator
*
* https://github.com/naTmeg/ScriptedAmigaEmulator
*
* ©2012 Rupert Hausberger
* Commercial use is prohibited.
*
**************************************************************************/
/*const DEBUG_CHANNEL_MASK = 15
function debugchannel(ch) {
return ((1 << ch) & DEBUG_CHANNEL_MASK) != 0;
}*/
function Filter() {
const DENORMAL_OFFSET = 1E-10;
this.on = false;
this.led_filter_on = false;
var filter_state = [
{ rc1:0,rc2:0,rc3:0,rc4:0,rc5:0 },
{ rc1:0,rc2:0,rc3:0,rc4:0,rc5:0 },
{ rc1:0,rc2:0,rc3:0,rc4:0,rc5:0 },
{ rc1:0,rc2:0,rc3:0,rc4:0,rc5:0 }
];
var filter1_a0 = 0;
var filter2_a0 = 0;
var filter_a0 = 0;
function getRate(v) {
switch (v) {
case SAEV_Config_Audio_Rate_11025: return 11025;
case SAEV_Config_Audio_Rate_22050: return 22050;
case SAEV_Config_Audio_Rate_44100: return 44100;
case SAEV_Config_Audio_Rate_48000: return 48000;
default: return 44100;
}
}
function calc(sample_rate, cutoff_freq) {
if (cutoff_freq >= sample_rate / 2)
return 1.0;
var omega = 2 * Math.PI * cutoff_freq / sample_rate;
omega = Math.tan(omega / 2) * 2;
return 1 / (1 + 1 / omega);
}
this.setup = function(on) {
this.on = on;
var rate = getRate(AMIGA.config.audio.rate);
filter1_a0 = calc(rate, 6200);
filter2_a0 = calc(rate, 20000);
filter_a0 = calc(rate, 7000);
/*console.log(rate);
console.log(filter1_a0);
console.log(filter2_a0);
console.log(filter_a0);*/
}
this.reset = function() {
filter_state = [
{ rc1:0,rc2:0,rc3:0,rc4:0,rc5:0 },
{ rc1:0,rc2:0,rc3:0,rc4:0,rc5:0 },
{ rc1:0,rc2:0,rc3:0,rc4:0,rc5:0 },
{ rc1:0,rc2:0,rc3:0,rc4:0,rc5:0 }
];
}
this.filter = function(input, state) {
if (!this.on)
return input;
var o, fs = filter_state[state];
fs.rc1 = filter1_a0 * input + (1 - filter1_a0) * fs.rc1 + DENORMAL_OFFSET;
fs.rc2 = filter2_a0 * fs.rc1 + (1 - filter2_a0) * fs.rc2;
var no = fs.rc2;
if (this.led_filter_on) {
fs.rc3 = filter_a0 * no + (1 - filter_a0) * fs.rc3;
fs.rc4 = filter_a0 * fs.rc3 + (1 - filter_a0) * fs.rc4;
fs.rc5 = filter_a0 * fs.rc4 + (1 - filter_a0) * fs.rc5;
o = fs.rc5;
} else
o = no;
if (o > 32767) o = 32767; else if (o < -32768) o = -32768;
return o;
}
}
function Channel(num) {
this.num = num;
this.adk_mask = 0;
this.enabled = false;
this.evtime = 0;
this.dmaenstore = false;
this.intreq = false;
this.dr = false;
this.dsr = false;
this.pbufldl = false;
this.dat_written = false;
this.state = 0;
this.lc = 0;
this.pt = 0;
this.per = 0;
this.vol = 0;
this.len = 0;
this.wlen = 0;
this.dat = 0;
this.dat2 = 0;
this.current_sample = 0;
this.last_sample = 0;
this.ptx = 0;
this.ptx_written = false;
this.ptx_tofetch = false;
this.reset = function() {
this.adk_mask = 0;
this.enabled = false;
this.evtime = CYCLE_MAX;
this.dmaenstore = false;
this.intreq = false;
this.dr = false;
this.dsr = false;
this.pbufldl = false;
this.dat_written = false;
this.state = 0;
this.lc = 0;
this.pt = 0;
this.per = PERIOD_MAX - 1;
this.vol = 0;
this.len = 0;
this.wlen = 0;
this.dat = 0;
this.dat2 = 0;
this.current_sample = 0;
this.last_sample = 0;
this.ptx = 0;
this.ptx_written = false;
this.ptx_tofetch = false;
}
//const audio_channel_mask = 15;
this.newsample = function(sample) {
//if (debugchannel(this.num)) BUG.info('Audio.channel[%d].newsample() %02x', nr, sample);
//if (!(audio_channel_mask & (1 << this.num))) sample = 0;
if (sample & 0x80) sample -= 0x100;
this.last_sample = this.current_sample;
this.current_sample = sample;
}
this.isirq = function() {
return (AMIGA.INTREQR() & (0x80 << this.num)) != 0;
}
this.setirq = function(which) {
//if (debugchannel(this.num) && this.wlen > 1) BUG.info('Audio.channel[%d].setirq() %d, %d', this.num, which, this.isirq() ? 1 : 0);
AMIGA.INTREQ_0(INTF_SETCLR | (0x80 << this.num));
}
this.zerostate = function() {
//if (debugchannel(this.num)) BUG.info('Audio.channel[%d].zerostate()', this.num);
this.state = 0;
this.evtime = CYCLE_MAX;
this.intreq = false;
this.dmaenstore = false;
}
this.setdr = function() {
//if (debugchannel(this.num) && this.dr) BUG.info('Audio.channel[%d].setdr() DR already active (STATE %d)', this.num, this.state);
this.dr = true;
if (this.wlen == 1) {
this.dsr = true;
//if (debugchannel(this.num) && this.wlen > 1) BUG.info('Audio.channel[%d].setdr() DSR on, pt %08x', this.num, this.pt);
}
}
this.loaddat = function(modper) {
var audav = (AMIGA.adkcon & (0x01 << this.num)) != 0;
var audap = (AMIGA.adkcon & (0x10 << this.num)) != 0;
if (audav || (modper && audap)) {
if (this.num >= 3)
return;
if (modper && audap) {
if (this.dat == 0)
AMIGA.audio.channel[this.num + 1].per = PERIOD_MAX;
else if (this.dat > PERIOD_MIN)
AMIGA.audio.channel[this.num + 1].per = this.dat * CYCLE_UNIT;
else
AMIGA.audio.channel[this.num + 1].per = PERIOD_MIN * CYCLE_UNIT;
} else if (audav) {
AMIGA.audio.channel[this.num + 1].vol = this.dat;
AMIGA.audio.channel[this.num + 1].vol &= 127;
if (AMIGA.audio.channel[this.num + 1].vol > 64)
AMIGA.audio.channel[this.num + 1].vol = 64;
}
} else {
//if (debugchannel(this.num)) BUG.info('Audio.channel[%d].loaddat() new %04x, old %04x', this.num, this.dat, this.dat2);
this.dat2 = this.dat;
}
}
this.loadper = function() {
this.evtime = this.per;
if (this.evtime < CYCLE_UNIT)
BUG.info('Audio.channel[%d].loadper() bug %d', this.num, this.evtime);
}
this.state_channel = function(perfin) {
this.state_channel2(perfin);
this.dat_written = false;
}
this.state_channel2 = function(perfin) {
var chan_ena = ((AMIGA.dmacon & DMAF_DMAEN) && (AMIGA.dmacon & (1 << this.num))) ? true : false;
var old_dma = this.dmaenstore;
var audav = (AMIGA.adkcon & (0x01 << this.num)) != 0;
var audap = (AMIGA.adkcon & (0x10 << this.num)) != 0;
var napnav = (!audav && !audap) || audav;
this.dmaenstore = chan_ena;
if (!AMIGA.config.audio.enabled) {
this.zerostate();
return;
}
AMIGA.audio.activate();
if ((this.state == 2 || this.state == 3) && AMIGA.config.cpu.speed == SAEV_Config_CPU_Speed_Maximum && !chan_ena && old_dma) {
//if (debugchannel(this.num)) BUG.info('Audio.channel[%d].state_channel2() INSTADMAOFF', this.num);
this.newsample(this.dat2 & 0xff);
if (napnav)
this.setirq(91);
this.zerostate();
return;
}
//if (debugchannel(this.num) && old_dma != chan_ena) BUG.info('Audio.channel[%d].state_channel2() DMA %d, IRQ %d', this.num, chan_ena ? 1 : 0, this.isirq() ? 1 : 0);
switch (this.state) {
case 0: {
if (chan_ena) {
this.evtime = CYCLE_MAX;
this.state = 1;
this.dr = true;
this.wlen = this.len;
this.ptx_written = false;
if (this.wlen > 2)
this.ptx_tofetch = true;
this.dsr = true;
//if (debugchannel(this.num)) BUG.info('Audio.channel[%d].state_channel2() 0>1, LEN %d', this.num, this.wlen);
} else if (this.dat_written && !this.isirq()) {
this.state = 2;
this.setirq(0);
this.loaddat(false);
if (AMIGA.config.cpu.speed == SAEV_Config_CPU_Speed_Maximum && this.per < 10 * CYCLE_UNIT) {
this.newsample(this.dat2 & 0xff);
this.zerostate();
} else {
this.pbufldl = true;
this.state_channel2(false);
}
} else {
this.zerostate();
}
break;
}
case 1: {
this.evtime = CYCLE_MAX;
if (!chan_ena) {
this.zerostate();
return;
}
if (!this.dat_written)
return;
this.setirq(10);
this.setdr();
if (this.wlen != 1) {
//this.wlen = (this.wlen - 1) & 0xffff;
if ((--this.wlen) < 0) this.wlen = 0xffff;
}
this.state = 5;
break;
}
case 5: {
this.evtime = CYCLE_MAX;
if (!chan_ena) {
this.zerostate();
return;
}
if (!this.dat_written)
return;
//if (debugchannel(this.num)) BUG.info('Audio.channel[%d].state_channel2() >5, LEN %d', this.num, this.wlen);
if (this.ptx_written) {
this.ptx_written = false;
this.lc = this.ptx;
}
this.loaddat(false);
if (napnav)
this.setdr();
this.state = 2;
this.loadper();
this.pbufldl = true;
this.intreq = false;
this.state_channel2(false);
break;
}
case 2: {
if (this.pbufldl) {
this.newsample((this.dat2 >> 8) & 0xff);
this.loadper();
this.pbufldl = false;
}
if (!perfin)
return;
if (audap)
this.loaddat(true);
if (chan_ena) {
if (audap)
this.setdr();
if (this.intreq && audap)
this.setirq(21);
} else {
if (audap)
this.setirq(22);
}
this.pbufldl = true;
this.state = 3;
this.state_channel2(false);
break;
}
case 3: {
if (this.pbufldl) {
this.newsample((this.dat2 >> 0) & 0xff);
this.loadper();
this.pbufldl = false;
}
if (!perfin)
return;
if (chan_ena) {
this.loaddat(false);
if (this.intreq && napnav)
this.setirq(31);
if (napnav)
this.setdr();
} else {
if (this.isirq()) {
//if (debugchannel(this.num)) BUG.info('Audio.channel[%d].state_channel2() IDLE', this.num);
this.zerostate();
return;
}
this.loaddat(false);
if (napnav)
this.setirq(32);
}
this.intreq = false;
this.pbufldl = true;
this.state = 2;
this.state_channel2(false);
break;
}
}
}
}
function Audi0() {
var channel = null;
var last_cycles = 0;
var next_sample_evtime = 0;
var scaled_sample_evtime_orig = 0;
var scaled_sample_evtime = 0;
var work_to_do = 0;
var prevcon = -1;
var driver = {
ctx: null,
moz: {
/*currentWritePosition: 0,
prebufferSize: 0,
tail: null,
tailPosition: null,*/
interval: null
},
def: {
node: null
},
paused:false
};
var sampleBuffer = {
size: 0,
realSize: 0,
data:0,
pos: 0,
rate: new mavg(10)
};
var ringBuffer = {
size: 0,
data:0,
usage:0,
readPos: 0,
writePos: 0
};
this.filter = new Filter();
/*---------------------------------*/
function getRate(v) {
switch (v) {
case SAEV_Config_Audio_Rate_11025: return 11025;
case SAEV_Config_Audio_Rate_22050: return 22050;
case SAEV_Config_Audio_Rate_44100: return 44100;
case SAEV_Config_Audio_Rate_48000: return 48000;
default: return 44100;
}
}
this.recalc_sample_evtime = function() {
var hpos, lines = 0;
if (AMIGA.config.video.ntsc) {
hpos = 227.5;
lines += 0.5;
} else {
var longframe = (AMIGA.playfield.bplcon0 & 4) ? -1 : AMIGA.events.lofStore;
hpos = 227;
if (longframe < 0)
lines += 0.5;
else if (longframe > 0)
lines += 1.0;
}
lines += AMIGA.events.maxvpos;
scaled_sample_evtime_orig = Math.floor(hpos * lines * AMIGA.events.hz * CYCLE_UNIT / getRate(AMIGA.config.audio.rate));
scaled_sample_evtime = scaled_sample_evtime_orig;
//sampler_evtime = hpos * lines * AMIGA.events.hz * CYCLE_UNIT;
//scaled_sample_evtime = Math.floor((AMIGA.events.maxhpos * AMIGA.events.maxvpos * AMIGA.events.hz + getRate(AMIGA.config.audio.rate) - 1) / getRate(AMIGA.config.audio.rate)) * CYCLE_UNIT;
console.log('Audio.recalc_sample_evtime() scaled_sample_evtime %f (%f) | hpos %d, lines %d', scaled_sample_evtime, scaled_sample_evtime * CYCLE_UNIT_INV, hpos, lines);
}
this.setup = function() {
if (channel === null) {
channel = [];
for (var i = 0; i < 4; i++)
channel[i] = new Channel(i);
}
if (!AMIGA.config.audio.enabled || AMIGA.config.audio.mode == SAEV_Config_Audio_Mode_Emul)
return;
if (driver.ctx === null) {
if (BrowserDetect.browser == 'Firefox') {
try {
driver.ctx = new Audio();
BUG.info('Audio.setup() using "Audio()"');
} catch (e) {}
} else {
try {
driver.ctx = new AudioContext();
BUG.info('Audio.setup() using "AudioContext()"');
} catch (e) {
try {
driver.ctx = new webkitAudioContext();
BUG.info('Audio.setup() using "webkitAudioContext()"');
} catch (e) {}
}
}
}
if (driver.ctx === null) {
if (confirm('Can\'t initialise WebAudio. Continue without audio-playback?')) {
AMIGA.config.audio.mode = SAEV_Config_Audio_Mode_Emul;
return;
} else
Fatal(SAEE_Audio_WebAudio_Not_Avail, 'Can\'t initialise WebAudio.');
}
this.filter.setup(0);
this.recalc_sample_evtime();
sampleBuffer.size = 8192 >> 0;
sampleBuffer.realSize = sampleBuffer.size << 1;
sampleBuffer.data = new Float32Array(sampleBuffer.realSize << (AMIGA.config.audio.channels - 1));
ringBuffer.size = sampleBuffer.size << 1;
ringBuffer.data = new Float32Array(ringBuffer.size << (AMIGA.config.audio.channels - 1));
if (BrowserDetect.browser == 'Firefox') {
driver.ctx.mozSetup(AMIGA.config.audio.channels, getRate(AMIGA.config.audio.rate));
/*driver.moz.currentWritePosition = 0;
driver.moz.prebufferSize = getRate(AMIGA.config.audio.rate) >> 1; //prebuffer 500ms
driver.moz.tail = null;*/
//driver.moz.interval = setInterval(mozHandler, Math.floor(1000 / (getRate(AMIGA.config.audio.rate) / sampleBuffer.size)));
driver.moz.interval = setInterval(mozHandler, Math.floor(1000 / AMIGA.events.hz * (sampleBuffer.size / (getRate(AMIGA.config.audio.rate) / AMIGA.events.hz))));
BUG.info('Audio.setup() enabled mozilla audio, channels %d, rate %d', AMIGA.config.audio.channels, getRate(AMIGA.config.audio.rate));
} else {
var inputs = BrowserDetect.browser == 'Safari' ? 1 : 0;
//if (inputs == 1) BUG.info('Audio.setup() Safari fix enabled.');
driver.def.node = driver.ctx.createJavaScriptNode(sampleBuffer.size, inputs, AMIGA.config.audio.channels);
driver.def.node.onaudioprocess = defFill;
driver.def.node.connect(driver.ctx.destination);
BUG.info('Audio.setup() enabled webkit audio, channels %d, rate %d', AMIGA.config.audio.channels, getRate(AMIGA.config.audio.rate));
}
}
this.cleanup = function() {
if (driver.ctx !== null) {
if (BrowserDetect.browser == 'Firefox') {
clearInterval(driver.moz.interval);
} else {
driver.def.node.disconnect(0);
driver.def.node = null;
}
}
}
this.pauseResume = function(pause) {
if (!AMIGA.config.audio.enabled || AMIGA.config.audio.mode == SAEV_Config_Audio_Mode_Emul) return;
if (driver.ctx !== null) {
if (BrowserDetect.browser == 'Firefox') {
if (pause && !driver.paused) {
clearInterval(driver.moz.interval);
driver.paused = true;
} else if (!pause && driver.paused) {
driver.moz.interval = setInterval(mozHandler, 100);
driver.paused = false;
}
} else {
if (driver.def.node) {
if (pause && !driver.paused) {
driver.def.node.disconnect(0);
driver.paused = true;
} else if (!pause && driver.paused) {
driver.def.node.connect(driver.ctx.destination);
driver.paused = false;
}
}
}
}
}
this.reset = function() {
for (var i = 0; i < 4; i++)
channel[i].reset();
last_cycles = AMIGA.events.currcycle;
next_sample_evtime = scaled_sample_evtime;
this.schedule();
AMIGA.events.schedule();
work_to_do = 0;
prevcon = -1;
sampleBuffer.pos = 0;
sampleBuffer.rate.clr();
ringBuffer.usage = 0;
ringBuffer.readPos = 0;
ringBuffer.writePos = 0;
this.filter.reset();
}
/*---------------------------------*/
this.event_reset = function() {
for (var i = 0; i < 4; i++)
channel[i].zerostate();
last_cycles = AMIGA.events.currcycle;
next_sample_evtime = scaled_sample_evtime;
this.schedule();
AMIGA.events.schedule();
}
this.activate = function() {
//BUG.info('Audio.activate()');
var ret = 0;
if (!work_to_do) {
this.pauseResume(0);
ret = 1;
this.event_reset();
}
work_to_do = 4 * AMIGA.events.maxvpos * 50;
return ret;
}
this.deactivate = function() {
//BUG.info('Audio.deactivate()');
this.pauseResume(1);
sampleBuffer.pos = 0;
sampleBuffer.rate.clr();
this.event_reset();
}
this.state_machine = function() {
this.update();
for (var i = 0; i < 4; i++)
channel[i].state_channel(false);
this.schedule();
AMIGA.events.schedule();
}
this.schedule = function() {
var best = CYCLE_MAX;
AMIGA.events.eventtab[EV_AUDIO].active = false;
AMIGA.events.eventtab[EV_AUDIO].oldcycles = AMIGA.events.currcycle;
for (var i = 0; i < 4; i++) {
if (channel[i].evtime != CYCLE_MAX) {
if (best > channel[i].evtime) {
best = channel[i].evtime;
AMIGA.events.eventtab[EV_AUDIO].active = true;
}
}
}
AMIGA.events.eventtab[EV_AUDIO].evtime = AMIGA.events.currcycle + best;
}
this.update = function() {
if (!AMIGA.config.audio.enabled || !work_to_do) {
last_cycles = AMIGA.events.currcycle;
return;
}
var n_cycles = AMIGA.events.currcycle - last_cycles;
while (n_cycles > 0) {
var best_evtime = n_cycles + 1;
var i, rounded;
for (i = 0; i < 4; i++) {
if (channel[i].evtime != CYCLE_MAX && best_evtime > channel[i].evtime)
best_evtime = channel[i].evtime;
}
rounded = Math.floor(next_sample_evtime);
if ((next_sample_evtime - rounded) >= 0.5)
rounded++;
if (AMIGA.config.audio.mode != SAEV_Config_Audio_Mode_Emul && best_evtime > rounded)
best_evtime = rounded;
if (best_evtime > n_cycles)
best_evtime = n_cycles;
next_sample_evtime -= best_evtime;
/*if (AMIGA.config.audio.mode != SAEV_Config_Audio_Mode_Emul) {
if (sample_prehandler)
sample_prehandler (best_evtime / CYCLE_UNIT);
}*/
for (i = 0; i < 4; i++) {
if (channel[i].evtime != CYCLE_MAX)
channel[i].evtime -= best_evtime;
}
n_cycles -= best_evtime;
if (AMIGA.config.audio.mode != SAEV_Config_Audio_Mode_Emul) {
if (rounded == best_evtime) {
next_sample_evtime += scaled_sample_evtime;
this.sample_handler_def();
//this.sample_handler_crux();
//this.sample_handler_rh();
}
}
for (i = 0; i < 4; i++) {
if (channel[i].evtime == 0) {
channel[i].state_channel(true);
if (channel[i].evtime == 0) {
BUG.info('Audio.update() sound bug in channel %d (evtime == 0)', i);
channel[i].evtime = CYCLE_MAX;
}
}
}
}
last_cycles = AMIGA.events.currcycle - n_cycles;
}
this.update_adkmasks = function() {
var t = AMIGA.adkcon | (AMIGA.adkcon >> 4);
channel[0].adk_mask = ((t >> 0) & 1) == 0 ? 0xffffffff : 0;
channel[1].adk_mask = ((t >> 1) & 1) == 0 ? 0xffffffff : 0;
channel[2].adk_mask = ((t >> 2) & 1) == 0 ? 0xffffffff : 0;
channel[3].adk_mask = ((t >> 3) & 1) == 0 ? 0xffffffff : 0;
channel[0].enabled = ((t >> 0) & 1) == 0;
channel[1].enabled = ((t >> 1) & 1) == 0;
channel[2].enabled = ((t >> 2) & 1) == 0;
channel[3].enabled = ((t >> 3) & 1) == 0;
if ((prevcon & 0xff) != (AMIGA.adkcon & 0xff)) {
this.activate();
prevcon = AMIGA.adkcon;
}
}
this.handler = function() {
this.update();
this.schedule();
}
this.hsync = function() {
//if (!AMIGA.config.audio.enabled) return; /* done in events.hSyncPre() */
if (work_to_do > 0) {
work_to_do--;
if (work_to_do == 0)
this.deactivate();
}
this.update();
}
/*---------------------------------*/
this.AUDxDAT = function(nr, v) {
//BUG.info('AUD%dDAT %x', nr, v);
channel[nr].dat = v;
channel[nr].dat_written = true;
if (channel[nr].state == 2 || channel[nr].state == 3) {
var chan_ena = ((AMIGA.dmacon & DMAF_DMAEN) && (AMIGA.dmacon & (1 << nr))) ? true : false;
if (chan_ena) {
if (channel[nr].wlen == 1) {
channel[nr].wlen = channel[nr].len;
channel[nr].intreq = true;
} else {
//channel[nr].wlen = (channel[nr].wlen - 1) & 0xffff;
if ((--channel[nr].wlen) < 0) channel[nr].wlen = 0xffff;
}
}
} else {
this.activate();
this.update();
channel[nr].state_channel(false);
this.schedule();
AMIGA.events.schedule();
}
channel[nr].dat_written = false;
}
this.AUDxPER = function(nr, v) {
this.activate();
this.update();
var per = v * CYCLE_UNIT;
if (per == 0)
per = PERIOD_MAX - 1;
if (per < PERIOD_MIN * CYCLE_UNIT)
per = PERIOD_MIN * CYCLE_UNIT;
if (per < PERIOD_MIN_NONCE * CYCLE_UNIT && !AMIGA.config.cpu.exact && channel[nr].dmaenstore)
per = PERIOD_MIN_NONCE * CYCLE_UNIT;
if (channel[nr].per == PERIOD_MAX - 1 && per != PERIOD_MAX - 1) {
channel[nr].evtime = CYCLE_UNIT;
if (AMIGA.config.audio.enabled) {
this.schedule();
AMIGA.events.schedule();
}
}
channel[nr].per = per;
//if (debugchannel(nr)) BUG.info('AUD%dPER() %x', nr, v);
}
this.AUDxLEN = function(nr, v) {
this.activate();
this.update();
channel[nr].len = v;
//if (debugchannel(nr)) BUG.info('AUD%dLEN() %x', nr, v);
}
this.AUDxVOL = function(nr, v) {
v &= 127;
if (v > 64) v = 64;
this.activate();
this.update();
channel[nr].vol = v;
//if (debugchannel(nr)) BUG.info('AUD%dVOL() %x', nr, v);
}
this.AUDxLCH = function(nr, v) {
this.activate();
this.update();
if (AMIGA.config.cpu.speed == SAEV_Config_CPU_Speed_Maximum && ((channel[nr].ptx_tofetch && channel[nr].state == 1) || channel[nr].ptx_written)) {
channel[nr].ptx = channel[nr].lc;
channel[nr].ptx_written = true;
} else
channel[nr].lc = ((channel[nr].lc & 0xffff) | (v << 16)) >>> 0;
}
this.AUDxLCL = function(nr, v) {
this.activate();
this.update();
if (AMIGA.config.cpu.speed == SAEV_Config_CPU_Speed_Maximum && ((channel[nr].ptx_tofetch && channel[nr].state == 1) || channel[nr].ptx_written)) {
channel[nr].ptx = channel[nr].lc;
channel[nr].ptx_written = true;
} else
channel[nr].lc = ((channel[nr].lc & ~0xffff) | (v & 0xfffe)) >>> 0;
}
/*---------------------------------*/
this.getpt = function(nr, reset) {
var p = channel[nr].pt;
channel[nr].pt += 2;
if (reset)
channel[nr].pt = channel[nr].lc;
channel[nr].ptx_tofetch = false;
return p;
}
this.dmal = function() {
var dmal = 0;
for (var nr = 0; nr < 4; nr++) {
if (channel[nr].dr)
dmal |= (1 << (nr * 2));
if (channel[nr].dsr)
dmal |= (1 << (nr * 2 + 1));
channel[nr].dr = channel[nr].dsr = false;
}
//if (dmal) BUG.info('Audio.dmal() %d', dmal);
return dmal;
}
/*---------------------------------*/
const inv32768 = 1.0 / 32768;
this.sample_handler_def = function() {
var data0 = channel[0].enabled ? (channel[0].current_sample * channel[0].vol) : 0;
var data1 = channel[1].enabled ? (channel[1].current_sample * channel[1].vol) : 0;
var data2 = channel[2].enabled ? (channel[2].current_sample * channel[2].vol) : 0;
var data3 = channel[3].enabled ? (channel[3].current_sample * channel[3].vol) : 0;
data0 += data3;
data1 += data2;
data2 = data0 << 1;
data3 = data1 << 1;
data2 = this.filter.filter(data2, 0);
data3 = this.filter.filter(data3, 1);
if (sampleBuffer.pos < sampleBuffer.realSize) {
if (AMIGA.config.audio.channels == SAEV_Config_Audio_Channels_Stereo) {
sampleBuffer.data[(sampleBuffer.pos << 1) ] = inv32768 * data2;
sampleBuffer.data[(sampleBuffer.pos << 1) + 1] = inv32768 * data3;
sampleBuffer.pos++;
} else
sampleBuffer.data[sampleBuffer.pos++] = inv32768 * ((data2 + data3) * 0.5);
} else
BUG.info('Audio.sample_handler() audio buffer over-run!');
}
this.sample_handler_crux = function() {
var data0 = channel[0].enabled ? (channel[0].current_sample * channel[0].vol) : 0;
var data1 = channel[1].enabled ? (channel[1].current_sample * channel[1].vol) : 0;
var data2 = channel[2].enabled ? (channel[2].current_sample * channel[2].vol) : 0;
var data3 = channel[3].enabled ? (channel[3].current_sample * channel[3].vol) : 0;
var data0p = channel[0].enabled ? (channel[0].last_sample * channel[0].vol) : 0;
var data1p = channel[1].enabled ? (channel[1].last_sample * channel[1].vol) : 0;
var data2p = channel[2].enabled ? (channel[2].last_sample * channel[2].vol) : 0;
var data3p = channel[3].enabled ? (channel[3].last_sample * channel[3].vol) : 0;
{
const INTERVAL = scaled_sample_evtime * 3;
var ratio, ratio1;
ratio1 = channel[0].per - channel[0].evtime;
ratio = Math.floor((ratio1 << 12) / INTERVAL);
if (channel[0].evtime < scaled_sample_evtime || ratio1 >= INTERVAL)
ratio = 4096;
data0 = (data0 * ratio + data0p * (4096 - ratio)) >> 12;
ratio1 = channel[1].per - channel[1].evtime;
ratio = Math.floor((ratio1 << 12) / INTERVAL);
if (channel[1].evtime < scaled_sample_evtime || ratio1 >= INTERVAL)
ratio = 4096;
data1 = (data1 * ratio + data1p * (4096 - ratio)) >> 12;
ratio1 = channel[2].per - channel[2].evtime;
ratio = Math.floor((ratio1 << 12) / INTERVAL);
if (channel[2].evtime < scaled_sample_evtime || ratio1 >= INTERVAL)
ratio = 4096;
data2 = (data2 * ratio + data2p * (4096 - ratio)) >> 12;
ratio1 = channel[3].per - channel[3].evtime;
ratio = Math.floor((ratio1 << 12) / INTERVAL);
if (channel[3].evtime < scaled_sample_evtime || ratio1 >= INTERVAL)
ratio = 4096;
data3 = (data3 * ratio + data3p * (4096 - ratio)) >> 12;
}
data0 += data3;
data1 += data2;
data2 = data0 << 1;
data3 = data1 << 1;
data2 = this.filter.filter(data2, 0);
data3 = this.filter.filter(data3, 1);
if (sampleBuffer.pos < sampleBuffer.realSize) {
if (AMIGA.config.audio.channels == SAEV_Config_Audio_Channels_Stereo) {
sampleBuffer.data[(sampleBuffer.pos << 1) ] = inv32768 * data2;
sampleBuffer.data[(sampleBuffer.pos << 1) + 1] = inv32768 * data3;
sampleBuffer.pos++;
} else
sampleBuffer.data[sampleBuffer.pos++] = inv32768 * ((data2 + data3) * 0.5);
} else
BUG.info('Audio.sample_handler() audio buffer over-run!');
}
this.sample_handler_rh = function() {
var data0 = channel[0].enabled ? (channel[0].current_sample * channel[0].vol) : 0;
var data1 = channel[1].enabled ? (channel[1].current_sample * channel[1].vol) : 0;
var data2 = channel[2].enabled ? (channel[2].current_sample * channel[2].vol) : 0;
var data3 = channel[3].enabled ? (channel[3].current_sample * channel[3].vol) : 0;
var data0p = channel[0].enabled ? (channel[0].last_sample * channel[0].vol) : 0;
var data1p = channel[1].enabled ? (channel[1].last_sample * channel[1].vol) : 0;
var data2p = channel[2].enabled ? (channel[2].last_sample * channel[2].vol) : 0;
var data3p = channel[3].enabled ? (channel[3].last_sample * channel[3].vol) : 0;
{
var delta, ratio;
delta = channel[0].per;
ratio = ((channel[0].evtime % delta) << 8) / delta;
data0 = (data0 * (256 - ratio) + data0p * ratio) >> 8;
delta = channel[1].per;
ratio = ((channel[1].evtime % delta) << 8) / delta;
data1 = (data1 * (256 - ratio) + data1p * ratio) >> 8;
delta = channel[2].per;
ratio = ((channel[2].evtime % delta) << 8) / delta;
data1 += (data2 * (256 - ratio) + data2p * ratio) >> 8;
delta = channel[3].per;
ratio = ((channel[3].evtime % delta) << 8) / delta;
data0 += (data3 * (256 - ratio) + data3p * ratio) >> 8;
}
data2 = data0 << 1;
data3 = data1 << 1;
data2 = this.filter.filter(data2, 0);
data3 = this.filter.filter(data3, 1);
if (sampleBuffer.pos < sampleBuffer.realSize) {
if (AMIGA.config.audio.channels == SAEV_Config_Audio_Channels_Stereo) {
sampleBuffer.data[(sampleBuffer.pos << 1) ] = inv32768 * data2;
sampleBuffer.data[(sampleBuffer.pos << 1) + 1] = inv32768 * data3;
sampleBuffer.pos++;
} else
sampleBuffer.data[sampleBuffer.pos++] = inv32768 * ((data2 + data3) * 0.5);
} else
BUG.info('Audio.sample_handler() audio buffer over-run!');
}
/*---------------------------------*/
//var avg = 0, cnt = 0;
function fillRingBuffer() {
if (sampleBuffer.pos == 0) return false;
/*avg += sampleBuffer.pos;
if ((++cnt) == 7) {
var newrate = Math.floor(avg / 7);
sampleBuffer.rate = Math.floor(sampleBuffer.rate + newrate >> 1);
avg = cnt = 0;
}
if (sampleBuffer.rate == 0) {
sampleBuffer.pos = 0;
return false;
}
var rate = sampleBuffer.rate;*/
var rate = sampleBuffer.rate.set(sampleBuffer.pos);
var drop = false;
if (ringBuffer.usage + sampleBuffer.pos < ringBuffer.size) {
for (var i = 0; i < sampleBuffer.pos; i++) {
var pos = ringBuffer.writePos + i < ringBuffer.size ? ringBuffer.writePos + i : ringBuffer.writePos + i - ringBuffer.size;
if (AMIGA.config.audio.channels == SAEV_Config_Audio_Channels_Stereo) {
ringBuffer.data[(pos << 1) ] = sampleBuffer.data[(i << 1) ];
ringBuffer.data[(pos << 1) + 1] = sampleBuffer.data[(i << 1) + 1];
} else
ringBuffer.data[pos] = sampleBuffer.data[i];
}
ringBuffer.writePos += sampleBuffer.pos; if (ringBuffer.writePos >= ringBuffer.size) ringBuffer.writePos -= ringBuffer.size;
ringBuffer.usage += sampleBuffer.pos;
} else {
//console.log('ringBuffer overflow', ringBuffer.usage, rate, ringBuffer.size - ringBuffer.usage);
/* fill rest until full */
var rest = ringBuffer.size - ringBuffer.usage;
for (var i = 0; i < rest; i++) {
var pos = ringBuffer.writePos + i < ringBuffer.size ? ringBuffer.writePos + i : ringBuffer.writePos + i - ringBuffer.size;
if (AMIGA.config.audio.channels == SAEV_Config_Audio_Channels_Stereo) {
ringBuffer.data[(pos << 1) ] = sampleBuffer.data[(i << 1) ];
ringBuffer.data[(pos << 1) + 1] = sampleBuffer.data[(i << 1) + 1];
} else
ringBuffer.data[pos] = sampleBuffer.data[i];
}
ringBuffer.writePos += rest; if (ringBuffer.writePos >= ringBuffer.size) ringBuffer.writePos -= ringBuffer.size;
ringBuffer.usage += rest;
rate = sampleBuffer.size;// + (sampleBuffer.size >> 1);
drop = true;
}
if (ringBuffer.usage - rate < 0) {
//console.log('ringBuffer underflow', ringBuffer.usage, rate);
rate += ringBuffer.usage - rate;
drop = false;
}
var playBuffer = new Float32Array(rate << (AMIGA.config.audio.channels - 1));
if (ringBuffer.usage - rate >= 0) {
for (var i = 0; i < rate; i++) {
var pos = ringBuffer.readPos + i < ringBuffer.size ? ringBuffer.readPos + i : ringBuffer.readPos + i - ringBuffer.size;
if (AMIGA.config.audio.channels == SAEV_Config_Audio_Channels_Stereo) {
playBuffer[(i << 1) ] = ringBuffer.data[(pos << 1) ];
playBuffer[(i << 1) + 1] = ringBuffer.data[(pos << 1) + 1];
} else
playBuffer[i] = ringBuffer.data[pos];
}
ringBuffer.readPos += rate; if (ringBuffer.readPos >= ringBuffer.size) ringBuffer.readPos -= ringBuffer.size;
ringBuffer.usage -= rate;
if (drop) {
ringBuffer.readPos += ringBuffer.usage; if (ringBuffer.readPos >= ringBuffer.size) ringBuffer.readPos -= ringBuffer.size;
ringBuffer.usage = 0;
}
}
//console.log(sampleBuffer.pos, ringBuffer.usage, rate);
sampleBuffer.pos = 0;
return [playBuffer, rate];
}
function defFill(e) {
var rb = fillRingBuffer();
if (rb === false) return;
var playBuffer = rb[0];
var rate = rb[1];
var step = rate / sampleBuffer.size;
if (AMIGA.config.audio.channels == SAEV_Config_Audio_Channels_Stereo) {
var data1 = e.outputBuffer.getChannelData(0);
var data2 = e.outputBuffer.getChannelData(1);
for (var i = 0, j = 0.0; i < sampleBuffer.size; i++, j += step) {
data1[i] = playBuffer[(j >> 0) << 1];
data2[i] = playBuffer[((j >> 0) << 1) + 1];
}
} else {
var data = e.outputBuffer.getChannelData(0);
for (var i = 0, j = 0.0; i < sampleBuffer.size; i++, j += step)
data[i] = playBuffer[j >> 0];
}
}
function mozFill(data) {
var rb = fillRingBuffer();
if (rb === false) return;
var playBuffer = rb[0];
var rate = rb[1];
var step = rate / sampleBuffer.size;
if (AMIGA.config.audio.channels == SAEV_Config_Audio_Channels_Stereo) {
for (var i = 0, j = 0.0; i < sampleBuffer.size; i++, j += step) {
data[i*2] = playBuffer[(j >> 0)*2];
data[i*2+1] = playBuffer[(j >> 0)*2+1];
}
} else {
for (var i = 0, j = 0.0; i < data.length; i++, j += step)
data[i] = playBuffer[j >> 0];
}
}
function mozHandler() {
var soundData = new Float32Array(sampleBuffer.realSize);
mozFill(soundData);
driver.ctx.mozWriteAudio(soundData);
}
function mozHandler_full() {
var written;
if (driver.moz.tail) {
written = driver.ctx.mozWriteAudio(driver.moz.tail.subarray(driver.moz.tailPosition));
driver.moz.currentWritePosition += written;
driver.moz.tailPosition += written;
if (driver.moz.tailPosition < driver.moz.tail.length) return;
driver.moz.tail = null;
}
var currentPosition = driver.ctx.mozCurrentSampleOffset();
var available = currentPosition + driver.moz.prebufferSize - driver.moz.currentWritePosition;
if (available > 0) {
var soundData = new Float32Array(available);
mozFill(soundData);
written = driver.ctx.mozWriteAudio(soundData);
if (written < soundData.length) {
driver.moz.tail = soundData;
driver.moz.tailPosition = written;
}
driver.moz.currentWritePosition += written;
}
}
}