/*------------------------------------------------------------------------- | SAE - Scripted Amiga Emulator | https://github.com/naTmeg/ScriptedAmigaEmulator | | Copyright (C) 2012-2016 Rupert Hausberger | | This program is free software; you can redistribute it and/or | modify it under the terms of the GNU General Public License | as published by the Free Software Foundation; either version 2 | of the License, or (at your option) any later version. | | This program is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | Note: ported from WinUAE 3.2.x -------------------------------------------------------------------------*/ /* global variables */ var SAEV_Audio_vsynctimebase_orig = 0; /*---------------------------------*/ /* global references */ var SAER_Audio_deactivate = null; /*---------------------------------*/ function SAEO_Audio() { const PAULA_FREQ_PAL = SAEC_Playfield_CLOCK_PAL / 123; const PAULA_FREQ_NTSC = SAEC_Playfield_CLOCK_NTSC / 124; var driver = { context:null, processor:null, connected:false }; const CACHE_FRAMES_MULT = 16; var cache = { frames:0, buffer:null, readoffset:0, writeoffset:0, wait:false }; const SCALE_FRAMES_MULT = 4; var scale = { frames:0, buffer:null }; const SOUND_SYNC_MULTIPLIER = 1.0; var scaled_sample_evtime_orig = 0.0; var muted = false; var paused = false; var have_sound = false; var sound_available = false; //var avg_correct = 0.0; //var cnt_correct = 0.0; var used_freq = 0; //OWN /*-----------------------------------------------------------------------*/ this.update_sound = function(clk) { if (have_sound) { scaled_sample_evtime_orig = clk * SAEC_Events_CYCLE_UNIT * SOUND_SYNC_MULTIPLIER / used_freq; scaled_sample_evtime = scaled_sample_evtime_orig; SAEF_log("audio.update_sound() freq %f Hz, scaled sample eventtime %f cycles", used_freq, scaled_sample_evtime * SAEC_Events_CYCLE_UNIT_INV); } } /*-----------------------------------------------------------------------*/ /*const ADJUST_LIMIT = 6; const ADJUST_LIMIT2 = 1; //->SAEV_Audio_vsynctimebase_orig var vsynctimebase_orig = 0; //int function sound_setadjust(v) { if (v < -ADJUST_LIMIT) v = -ADJUST_LIMIT; else if (v > ADJUST_LIMIT) v = ADJUST_LIMIT; vsynctimebase = (SAEV_Audio_vsynctimebase_orig * (1000.0 + v) / 1000.0) >>> 0; scaled_sample_evtime = scaled_sample_evtime_orig; } var tfprev = 0; //fix reset function docorrection(s, sndbuf, sync, granulaty) { //static int tfprev; avg_correct += sync; cnt_correct++; if (granulaty < 10) granulaty = 10; if (tfprev != SAEV_Events_timeframes) { var avg = avg_correct / cnt_correct; var skipmode = sync / 100.0; var avgskipmode = avg / (10000.0 / granulaty); if ((tfprev % 10) == 0) SAEF_log("%+05d S=%.1f AVG=%.1f (IMM=%.1f + AVG=%.1f = %.1f)", sndbuf, sync, avg, skipmode, avgskipmode, skipmode + avgskipmode); SAER.gui.data.sndbuf = sndbuf; if (skipmode > ADJUST_LIMIT2) skipmode = ADJUST_LIMIT2; if (skipmode < -ADJUST_LIMIT2) skipmode = -ADJUST_LIMIT2; sound_setadjust(skipmode + avgskipmode); tfprev = SAEV_Events_timeframes; } }*/ /*---------------------------------*/ function cachediff(write, read) { var diff = write - read; if (diff > cache.frames >> 1) diff = cache.frames - write + read; else if (diff < -cache.frames >> 1) diff = cache.frames - read + write; return diff; } function cachewrite(buffer, frames) { var diff = cachediff(cache.writeoffset, cache.readoffset); if (diff > cache.frames >> 2) { SAEF_warn("audio.cachewrite() full %d", diff + frames); return false; } if (cache.writeoffset + frames > cache.frames) { var partsize = cache.frames - cache.writeoffset; if (partsize) { //SAEF_log("audio.cachewrite() write0 %d %d", cache.writeoffset, partsize); for (var j = 0; j < SAEV_config.audio.channels; j++) { //for (var i = 0; i < partsize; i++) cache.buffer[j][cache.writeoffset + i] = buffer[j][i]; cache.buffer[j].set(buffer[j].subarray(0, partsize), cache.writeoffset); } } if (frames - partsize) { //SAEF_log("audio.cachewrite() write1 %d %d", 0, frames - partsize); for (var j = 0; j < SAEV_config.audio.channels; j++) { //for (var i = 0; i < frames - partsize; i++) cache.buffer[j][i] = buffer[j][partsize + i]; cache.buffer[j].set(buffer[j].subarray(partsize, partsize + (frames - partsize))); } } cache.writeoffset = frames - partsize; } else { //SAEF_log("audio.cachewrite() write2 %d %d", cache.writeoffset, frames); for (var j = 0; j < SAEV_config.audio.channels; j++) { //for (var i = 0; i < frames; i++) cache.buffer[j][cache.writeoffset + i] = buffer[j][i]; cache.buffer[j].set(buffer[j].subarray(0, frames), cache.writeoffset); } cache.writeoffset += frames; } return true; } function cacheread(buffer, frames) { var diff = cachediff(cache.writeoffset, cache.readoffset + frames); if (diff < 0) { //SAEF_log("audio.cacheread() clr %d", frames); for (var j = 0; j < SAEV_config.audio.channels; j++) { //SAEF_memset(buffer[j],0, 0, frames); for (var i = 0; i < frames; i++) buffer[j][i] = 0; } frames -= -diff; } if (frames > 0) { if (cache.readoffset + frames > cache.frames) { var partsize = cache.frames - cache.readoffset; if (partsize) { //SAEF_log("audio.cacheread() read0 %d %d", cache.readoffset, partsize); for (var j = 0; j < SAEV_config.audio.channels; j++) { //for (var i = 0; i < partsize; i++) buffer[j][i] = cache.buffer[j][cache.readoffset + i]; buffer[j].set(cache.buffer[j].subarray(cache.readoffset, cache.readoffset + partsize)); } } if (frames - partsize) { //SAEF_log("audio.cacheread() read1 %d %d", 0, frames - partsize); for (var j = 0; j < SAEV_config.audio.channels; j++) { //for (var i = 0; i < frames - partsize; i++) buffer[j][partsize + i] = cache.buffer[j][i]; buffer[j].set(cache.buffer[j].subarray(0, frames - partsize), partsize); } } cache.readoffset = frames - partsize; } else { //SAEF_log("audio.cacheread() read2 %d %d", cache.readoffset, frames); for (var j = 0; j < SAEV_config.audio.channels; j++) { //for (var i = 0; i < frames; i++) buffer[j][i] = cache.buffer[j][cache.readoffset + i]; buffer[j].set(cache.buffer[j].subarray(cache.readoffset, cache.readoffset + frames)); } cache.readoffset += frames; } return true; } else if (frames < 0) SAEF_warn("audio.cacheread() empty %d", frames); return false; } /*---------------------------------*/ const INV32768 = 1.0 / 32768; /* mul is always fasten than div */ function scaleplay(e, buffer, frames) { if (muted) { for (var ch = 0; ch < SAEV_config.audio.channels; ch++) { var data = e.outputBuffer.getChannelData(ch); for (var i = 0; i < e.outputBuffer.length; i++) data[i] = 0.0; } return; } /*if (driver.context.sampleRate != used_freq) { } else*/ { var step = frames / e.outputBuffer.length; for (var ch = 0; ch < SAEV_config.audio.channels; ch++) { var data = e.outputBuffer.getChannelData(ch); for (var i = 0, j = 0.0; i < e.outputBuffer.length; i++, j += step) data[i] = buffer[ch][j >>> 0] * INV32768; } } } function process_sound_buffer_webaudio(e) { if (paused || cache.wait || paula.currrent == 0) var scale_frames = paula.frames; else var scale_frames = paula.average.set(paula.currrent); //SAEF_log("audio.process_sound_buffer_webaudio() %d %d", paula.currrent, scale_frames); paula.currrent = 0; cacheread(scale.buffer, scale_frames); scaleplay(e, scale.buffer, scale_frames); } function finish_sound_buffer_webaudio(buffer, frames) { if (!paused && have_sound) { cachewrite(buffer, frames); cache.wait = false; } } /*-----------------------------------------------------------------------*/ function pause_sound() { //SAER.gui.data.sndbuf_status = 0; //SAER.gui.data.sndbuf = 0; if (!paused && have_sound) { SAEF_log("audio.pause_sound()"); paused = true; //disconnect_sound(); //driver.context.suspend().then(function() { SAEF_log("audio.pause_sound() ...done"); }); } } function resume_sound() { if (paused && have_sound) { SAEF_log("audio.resume_sound()"); //connect_sound(); //driver.context.resume().then(function() { SAEF_log("audio.resume_sound() ...done"); }); paused = false; cache.wait = true; } } /*-----------------------------------------------------------------------*/ function connect_sound() { if (!driver.connected) { driver.processor.onaudioprocess = process_sound_buffer_webaudio; driver.processor.connect(driver.context.destination); driver.connected = true; } } function disconnect_sound() { if (driver.connected) { driver.processor.disconnect(driver.context.destination); driver.processor.onaudioprocess = function(e) {}; driver.connected = false; } } function open_sound() { driver.context = null; try { var AudioContextDriver = window.AudioContext || window.webkitAudioContext; driver.context = new AudioContextDriver(); driver.processor = driver.context.createScriptProcessor(paula.frames, SAEV_config.audio.channels, SAEV_config.audio.channels); } catch (e) { if (driver.context) driver.context.close().then(function() {}); return false; } if (SAEV_config.audio.freq == SAEC_Config_Audio_Freq_Auto) used_freq = driver.context.sampleRate; else used_freq = SAEV_config.audio.freq; if (cache.buffer === null) { cache.frames = paula.frames * CACHE_FRAMES_MULT; cache.buffer = new Array(2); for (var j = 0; j < cache.buffer.length; j++) cache.buffer[j] = new Int16Array(cache.frames); } if (scale.buffer === null) { scale.frames = paula.frames * SCALE_FRAMES_MULT; scale.buffer = new Array(2); for (var j = 0; j < scale.buffer.length; j++) scale.buffer[j] = new Int16Array(scale.frames); } connect_sound(); have_sound = true; SAEF_info("sae.audio() %d channels, frequency %d/%d Hz, %d frames", SAEV_config.audio.channels, used_freq, driver.context.sampleRate, paula.frames); return true; } function close_sound() { //SAER.gui.data.sndbuf_status = 3; //SAER.gui.data.sndbuf = 0; if (have_sound) { SAEF_log("audio.close_sound() initialised..."); disconnect_sound(); if (driver.context.close) { driver.context.close().then(function() { driver.context = null; SAEF_log("audio.close_sound() ...done"); }); } paused = false; have_sound = false; } } /*-----------------------------------------------------------------------*/ function obtain_sound() { //setup_sound() if (SAEV_config.audio.mode >= SAEC_Config_Audio_Mode_On) { if (SAEC_info.audio.webAudio) sound_available = true; else { /*if (confirm("'WebAudio' is not supported by this browser.\n\nContinue without audio-playback?")) SAEV_config.audio.mode = SAEC_Config_Audio_Mode_Off_Emul; else*/ return SAEE_Audio_RequiresWebAudio; } } return SAEE_None; } function setup_sound() { //init_sound() muted = paused = false; //SAER.gui.data.sndbuf_status = 3; //SAER.gui.data.sndbuf = 0; if (!have_sound) return open_sound(); return true; } function cleanup_sound() { //OWN close_sound(); } function reset_sound() { //reset_sound() cache.readoffset = 0; cache.writeoffset = 0; cache.wait = true; paula.average.clr(); } function mute_sound(mute) { //OWN muted = mute; } /*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/ /*-----------------------------------------------------------------------*/ const MAX_EV = SAEC_Events_CYCLE_MAX; const PERIOD_MIN = 4; const PERIOD_MIN_NONCE = 60; const PERIOD_MAX = SAEC_Events_CYCLE_MAX; const AUDIO_CHANNELS_PAULA = 4; const AUDIO_CHANNELS_MAX = 4; //8 const SOUND_MAX_DELAY_BUFFER = 1024; const MIXED_STEREO_MAX = 16; const MIXED_STEREO_SCALE = 32; function audio_channel_data() { this.enabled = false; //this.adk_mask = 0; //uint this.evtime = 0; //uint this.dmaenstore = false; this.intreq2 = false; this.dr = false; this.dsr = false; this.pbufldl = false; this.drhpos = 0; this.dat_written = false; this.lc = 0; this.pt = 0; //uaecptr this.current_sample = 0; this.last_sample = 0; this.state = 0; this.per = 0; this.vol = 0; this.len = 0; this.wlen = 0; this.dat = 0; this.dat2 = 0; //u16 //Anti this.sample_accum = 0; this.sample_accum_time = 0; //too fast cpu fixes this.ptx = 0; //uaecptr this.ptx_written = false; this.ptx_tofetch = false; this.dmaofftime_active = false; this.clr = function() { this.enabled = false; //this.adk_mask = 0 this.evtime = 0; this.dmaenstore = false; this.intreq2 = false; this.dr = false; this.dsr = false; this.pbufldl = false; this.drhpos = 0; this.dat_written = false; this.lc = this.pt = 0; this.current_sample = 0; this.last_sample = 0; this.state = 0; this.per = 0; this.vol = 0; this.len = 0; this.wlen = 0; this.dat = this.dat2 = 0; //Anti this.sample_accum = 0; this.sample_accum_time = 0; //too fast cpu fixes this.ptx = 0; this.ptx_written = false; this.ptx_tofetch = false; this.dmaofftime_active = false; }; } var audio_channel = new Array(AUDIO_CHANNELS_MAX); for (var vi = 0; vi < AUDIO_CHANNELS_MAX; vi++) audio_channel[vi] = new audio_channel_data(); var audio_channel_mask = 15; //global var audio_channel_count = AUDIO_CHANNELS_PAULA; var audio_work_to_do = 0; var sample_handler = function() {}; var sample_prehandler = null; var sample_evtime = 0.0; //global var scaled_sample_evtime = 0.0; var last_cycles = 0; var next_sample_evtime = 0.0; //var paula_buffer = null; //u16 * //var paula_pointer = null; //u16 * //var paula_size = 0; var paula = { //OWN frames: 0, buffer: null, pointer: 0, currrent: 0, average: new SAEO_MAvg(10) }; var datas = new Int16Array(AUDIO_CHANNELS_PAULA); var right_word_saved = new Int16Array(SOUND_MAX_DELAY_BUFFER); //u32 [SOUND_MAX_DELAY_BUFFER] var left_word_saved = new Int16Array(SOUND_MAX_DELAY_BUFFER); var saved_ptr = 0; var mixed_on = 0, mixed_stereo_size = 0, mixed_mul1 = 0, mixed_mul2 = 0; var usehacks = false; /*-----------------------------------------------------------------------*/ var led_filter_forced = 0, sound_use_filter = 0, led_filter_on = 0; /* denormals are very small floating point numbers that force FPUs into slow mode. All lowpass filters using floats are suspectible to denormals unless a small offset is added to avoid very small floating point numbers. */ const DENORMAL_OFFSET = 1E-10; function filter_state() { this.rc1 = 0.0; this.rc2 = 0.0; this.rc3 = 0.0; this.rc4 = 0.0; this.rc5 = 0.0; this.clr = function() { this.rc1 = 0.0; this.rc2 = 0.0; this.rc3 = 0.0; this.rc4 = 0.0; this.rc5 = 0.0; } } var sound_filter_state = new Array(AUDIO_CHANNELS_PAULA); for (var vi = 0; vi < AUDIO_CHANNELS_PAULA; vi++) sound_filter_state[vi] = new filter_state(); var a500e_filter1_a0 = 0.0; var a500e_filter2_a0 = 0.0; var filter_a0 = 0.0; /* a500 and a1200 use the same */ const FILTER_NONE = 0; const FILTER_MODEL_A500 = 1; const FILTER_MODEL_A1200 = 2; /* Amiga has two separate filtering circuits per channel, a static RC filter * on A500 and the LED filter. This code emulates both. * * The Amiga filtering circuitry depends on Amiga model. Older Amigas seem * to have a 6 dB/oct RC filter with cutoff frequency such that the -6 dB * point for filter is reached at 6 kHz, while newer Amigas have no filtering. * * The LED filter is complicated, and we are modelling it with a pair of * RC filters, the other providing a highboost. The LED starts to cut * into signal somewhere around 5-6 kHz, and there"s some kind of highboost * in effect above 12 kHz. Better measurements are required. * * The current filtering should be accurate to 2 dB with the filter on, * and to 1 dB with the filter off. */ function filter(input, fs) { var normal_output, led_output, output; //input = (uae_s16)input; //ORG //if (input & 0x8000) input -= 0x10000; //OWN switch (sound_use_filter) { case FILTER_MODEL_A500: { fs.rc1 = a500e_filter1_a0 * input + (1 - a500e_filter1_a0) * fs.rc1 + DENORMAL_OFFSET; fs.rc2 = a500e_filter2_a0 * fs.rc1 + (1 - a500e_filter2_a0) * fs.rc2; normal_output = fs.rc2; fs.rc3 = filter_a0 * normal_output + (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; led_output = fs.rc5; break; } case FILTER_MODEL_A1200: { normal_output = input; fs.rc2 = filter_a0 * normal_output + (1 - filter_a0) * fs.rc2 + DENORMAL_OFFSET; fs.rc3 = filter_a0 * fs.rc2 + (1 - filter_a0) * fs.rc3; fs.rc4 = filter_a0 * fs.rc3 + (1 - filter_a0) * fs.rc4; led_output = fs.rc4; break; } case FILTER_NONE: default: return input; } if (led_filter_on) output = ~~led_output; else output = ~~normal_output; if (output > 32767) output = 32767; else if (output < -32768) output = -32768; return output; //return output < 0 ? output + 0x10000 : output; //OWN } this.led_filter_audio = function() { led_filter_on = 0; if (led_filter_forced > 0 || (SAER.gui.data.powerled && led_filter_forced >= 0)) led_filter_on = 1; } /*-----------------------------------------------------------------------*/ function clear_sound_buffers() { //if (!have_sound) return; //memset(paula_sndbuffer, 0, paula_size); for (var i = 0; i < paula.buffer.length; i++) SAEF_memset(paula.buffer[i],0, 0, paula.frames); //paula_pointer = paula_buffer; paula.pointer = 0; paula.currrent = 0; } function finish_sound_buffer() { //paula_pointer = paula_buffer; paula.pointer = 0; //if (currprefs.turbo_emulation) return; //if (!have_sound) return; //if (SAER.gui.data.sndbuf_status == 3) // SAER.gui.data.sndbuf_status = 0; //if (!paused) finish_sound_buffer_webaudio(paula.buffer, paula.frames); } function check_sound_buffers() { //if ((uae_u8*)paula_pointer - (uae_u8*)paula_buffer >= paula_size) if (paula.pointer >= paula.frames) finish_sound_buffer(); } /*-----------------------------------------------------------------------*/ //#define PUT_SOUND_WORD(b) do { *(uae_u16 *)paula_pointer = b; paula_pointer = (uae_u16 *)(((uae_u8 *)paula_pointer) + 2); } while (0) //#define PUT_SOUND_WORD_MONO(b) PUT_SOUND_WORD(b) /* Always put the right word before the left word. */ function put_sound_word_right(w) { //u32 if (mixed_on) right_word_saved[saved_ptr] = w; else //PUT_SOUND_WORD(w); paula.buffer[1][paula.pointer] = w; } function put_sound_word_left(w) { if (mixed_on) { left_word_saved[saved_ptr] = w; var lnew = w; var rnew = right_word_saved[saved_ptr]; saved_ptr = (saved_ptr + 1) & mixed_stereo_size; var lold = left_word_saved[saved_ptr]; var tmp = ~~((rnew * mixed_mul2 + lold * mixed_mul1) / MIXED_STEREO_SCALE); var rold = right_word_saved[saved_ptr]; w = ~~((lnew * mixed_mul2 + rold * mixed_mul1) / MIXED_STEREO_SCALE); //PUT_SOUND_WORD(w); //PUT_SOUND_WORD(tmp); paula.buffer[1][paula.pointer] = w; paula.buffer[0][paula.pointer] = tmp; } else //PUT_SOUND_WORD(w); paula.buffer[0][paula.pointer] = w; } /*---------------------------------*/ function anti_prehandler(best_evtime) { for (var i = 0; i < audio_channel_count; i++) { var acd = audio_channel[i]; //var output = (acd.current_sample * acd.vol) & acd.adk_mask; var output = acd.enabled ? acd.current_sample * acd.vol: 0; acd.sample_accum += output * best_evtime; acd.sample_accum_time += best_evtime; } } function samplexx_anti_handler(datasp, ch_start, ch_num) { for (var i = ch_start, j = 0; j < ch_num; i++, j++) { datasp[j] = audio_channel[i].sample_accum_time ? Math.floor(audio_channel[i].sample_accum / audio_channel[i].sample_accum_time) : 0; audio_channel[i].sample_accum = 0; audio_channel[i].sample_accum_time = 0; } } /*---------------------------------*/ /* Mono */ function sample16_mono_handler() { var data0 = audio_channel[0].enabled ? audio_channel[0].current_sample * audio_channel[0].vol : 0; var data1 = audio_channel[1].enabled ? audio_channel[1].current_sample * audio_channel[1].vol : 0; var data2 = audio_channel[2].enabled ? audio_channel[2].current_sample * audio_channel[2].vol : 0; var data3 = audio_channel[3].enabled ? audio_channel[3].current_sample * audio_channel[3].vol : 0; data0 += data1; data0 += data2; data0 += data3; var data = data0; if (SAEV_config.audio.filter) data = filter(data, sound_filter_state[0]); //PUT_SOUND_WORD_MONO(data); paula.buffer[0][paula.pointer++] = data; paula.currrent++; check_sound_buffers(); } function sample16i_anti_mono_handler() { samplexx_anti_handler(datas, 0, AUDIO_CHANNELS_PAULA); var data1 = datas[0] + datas[3] + datas[1] + datas[2]; if (SAEV_config.audio.filter) data1 = filter(data1, sound_filter_state[0]); //PUT_SOUND_WORD_MONO(data1); paula.buffer[0][paula.pointer++] = data1; paula.currrent++; check_sound_buffers(); } function sample16i_rh_mono_handler() { var data0, data1, data2, data3, data0p, data1p, data2p, data3p; var delta, ratio; //ulong if (audio_channel[0].enabled) { data0 = audio_channel[0].current_sample * audio_channel[0].vol; data0p = audio_channel[0].last_sample * audio_channel[0].vol; } else data0 = data0p = 0; if (audio_channel[1].enabled) { data1 = audio_channel[1].current_sample * audio_channel[1].vol; data1p = audio_channel[1].last_sample * audio_channel[1].vol; } else data1 = data1p = 0; if (audio_channel[2].enabled) { data2 = audio_channel[2].current_sample * audio_channel[2].vol; data2p = audio_channel[2].last_sample * audio_channel[2].vol; } else data2 = data2p = 0; if (audio_channel[3].enabled) { data3 = audio_channel[3].current_sample * audio_channel[3].vol; data3p = audio_channel[3].last_sample * audio_channel[3].vol; } else data3 = data3p = 0; delta = audio_channel[0].per; ratio = ~~(((audio_channel[0].evtime % delta) << 8) / delta); data0 = (data0 * (256 - ratio) + data0p * ratio) >> 8; delta = audio_channel[1].per; ratio = ~~(((audio_channel[1].evtime % delta) << 8) / delta); data0 += (data1 * (256 - ratio) + data1p * ratio) >> 8; delta = audio_channel[2].per; ratio = ~~(((audio_channel[2].evtime % delta) << 8) / delta); data0 += (data2 * (256 - ratio) + data2p * ratio) >> 8; delta = audio_channel[3].per; ratio = ~~(((audio_channel[3].evtime % delta) << 8) / delta); data0 += (data3 * (256 - ratio) + data3p * ratio) >> 8; var data = data0; if (SAEV_config.audio.filter) data = filter(data, sound_filter_state[0]); //PUT_SOUND_WORD_MONO(data); paula.buffer[0][paula.pointer++] = data; paula.currrent++; check_sound_buffers(); } function sample16i_crux_mono_handler() { var data0, data1, data2, data3, data0p, data1p, data2p, data3p; if (audio_channel[0].enabled) { data0 = audio_channel[0].current_sample * audio_channel[0].vol; data0p = audio_channel[0].last_sample * audio_channel[0].vol; } else data0 = data0p = 0; if (audio_channel[1].enabled) { data1 = audio_channel[1].current_sample * audio_channel[1].vol; data1p = audio_channel[1].last_sample * audio_channel[1].vol; } else data1 = data1p = 0; if (audio_channel[2].enabled) { data2 = audio_channel[2].current_sample * audio_channel[2].vol; data2p = audio_channel[2].last_sample * audio_channel[2].vol; } else data2 = data2p = 0; if (audio_channel[3].enabled) { data3 = audio_channel[3].current_sample * audio_channel[3].vol; data3p = audio_channel[3].last_sample * audio_channel[3].vol; } else data3 = data3p = 0; { var cdp, ratio, ratio1; var INTERVAL = scaled_sample_evtime * 3; cdp = audio_channel[0]; ratio1 = cdp.per - cdp.evtime; ratio = ~~((ratio1 << 12) / INTERVAL); if (cdp.evtime < scaled_sample_evtime || ratio1 >= INTERVAL) ratio = 4096; data0 = (data0 * ratio + data0p * (4096 - ratio)) >> 12; cdp = audio_channel[1]; ratio1 = cdp.per - cdp.evtime; ratio = ~~((ratio1 << 12) / INTERVAL); if (cdp.evtime < scaled_sample_evtime || ratio1 >= INTERVAL) ratio = 4096; data1 = (data1 * ratio + data1p * (4096 - ratio)) >> 12; cdp = audio_channel[2]; ratio1 = cdp.per - cdp.evtime; ratio = ~~((ratio1 << 12) / INTERVAL); if (cdp.evtime < scaled_sample_evtime || ratio1 >= INTERVAL) ratio = 4096; data2 = (data2 * ratio + data2p * (4096 - ratio)) >> 12; cdp = audio_channel[3]; ratio1 = cdp.per - cdp.evtime; ratio = ~~((ratio1 << 12) / INTERVAL); if (cdp.evtime < scaled_sample_evtime || ratio1 >= INTERVAL) ratio = 4096; data3 = (data3 * ratio + data3p * (4096 - ratio)) >> 12; } data1 += data2; data0 += data3; data0 += data1; var data = data0; if (SAEV_config.audio.filter) data = filter(data, sound_filter_state[0]); //PUT_SOUND_WORD_MONO (data); paula.buffer[0][paula.pointer++] = data; paula.currrent++; check_sound_buffers(); } /*---------------------------------*/ /* Stereo */ function sample16s_handler() { var data0 = audio_channel[0].enabled ? audio_channel[0].current_sample * audio_channel[0].vol : 0; var data1 = audio_channel[1].enabled ? audio_channel[1].current_sample * audio_channel[1].vol : 0; var data2 = audio_channel[2].enabled ? audio_channel[2].current_sample * audio_channel[2].vol : 0; var data3 = audio_channel[3].enabled ? audio_channel[3].current_sample * audio_channel[3].vol : 0; data0 += data3; data1 += data2; data2 = data0 << 1; data3 = data1 << 1; if (SAEV_config.audio.filter) { data2 = filter(data2, sound_filter_state[0]); data3 = filter(data3, sound_filter_state[1]); } put_sound_word_right(data2); put_sound_word_left(data3); paula.pointer++; paula.currrent++; check_sound_buffers(); } function sample16si_anti_handler() { samplexx_anti_handler(datas, 0, AUDIO_CHANNELS_PAULA); var data1 = datas[0] + datas[3]; var data2 = datas[1] + datas[2]; data1 = data1 << 1; data2 = data2 << 1; if (SAEV_config.audio.filter) { data1 = filter(data1, sound_filter_state[0]); data2 = filter(data2, sound_filter_state[1]); } put_sound_word_right(data1); put_sound_word_left(data2); paula.pointer++; paula.currrent++; check_sound_buffers(); } function sample16si_rh_handler() { var data0, data1, data2, data3, data0p, data1p, data2p, data3p; var delta, ratio; //ulong if (audio_channel[0].enabled) { data0 = audio_channel[0].current_sample * audio_channel[0].vol; data0p = audio_channel[0].last_sample * audio_channel[0].vol; } else data0 = data0p = 0; if (audio_channel[1].enabled) { data1 = audio_channel[1].current_sample * audio_channel[1].vol; data1p = audio_channel[1].last_sample * audio_channel[1].vol; } else data1 = data1p = 0; if (audio_channel[2].enabled) { data2 = audio_channel[2].current_sample * audio_channel[2].vol; data2p = audio_channel[2].last_sample * audio_channel[2].vol; } else data2 = data2p = 0; if (audio_channel[3].enabled) { data3 = audio_channel[3].current_sample * audio_channel[3].vol; data3p = audio_channel[3].last_sample * audio_channel[3].vol; } else data3 = data3p = 0; delta = audio_channel[0].per; ratio = ~~(((audio_channel[0].evtime % delta) << 8) / delta); data0 = (data0 * (256 - ratio) + data0p * ratio) >> 8; delta = audio_channel[1].per; ratio = ~~(((audio_channel[1].evtime % delta) << 8) / delta); data1 = (data1 * (256 - ratio) + data1p * ratio) >> 8; delta = audio_channel[2].per; ratio = ~~(((audio_channel[2].evtime % delta) << 8) / delta); data1 += (data2 * (256 - ratio) + data2p * ratio) >> 8; delta = audio_channel[3].per; ratio = ~~(((audio_channel[3].evtime % delta) << 8) / delta); data0 += (data3 * (256 - ratio) + data3p * ratio) >> 8; data2 = data0; data2 = data2 << 1; data3 = data1; data3 = data3 << 1; if (SAEV_config.audio.filter) { data2 = filter(data2, sound_filter_state[0]); data3 = filter(data3, sound_filter_state[1]); } put_sound_word_right(data2); put_sound_word_left(data3); paula.pointer++; paula.currrent++; check_sound_buffers(); } function sample16si_crux_handler() { var data0, data1, data2, data3, data0p, data1p, data2p, data3p; if (audio_channel[0].enabled) { data0 = audio_channel[0].current_sample * audio_channel[0].vol; data0p = audio_channel[0].last_sample * audio_channel[0].vol; } else data0 = data0p = 0; if (audio_channel[1].enabled) { data1 = audio_channel[1].current_sample * audio_channel[1].vol; data1p = audio_channel[1].last_sample * audio_channel[1].vol; } else data1 = data1p = 0; if (audio_channel[2].enabled) { data2 = audio_channel[2].current_sample * audio_channel[2].vol; data2p = audio_channel[2].last_sample * audio_channel[2].vol; } else data2 = data2p = 0; if (audio_channel[3].enabled) { data3 = audio_channel[3].current_sample * audio_channel[3].vol; data3p = audio_channel[3].last_sample * audio_channel[3].vol; } else data3 = data3p = 0; { var cdp, ratio, ratio1; var INTERVAL = scaled_sample_evtime * 3; cdp = audio_channel[0]; ratio1 = cdp.per - cdp.evtime; ratio = ~~((ratio1 << 12) / INTERVAL); if (cdp.evtime < scaled_sample_evtime || ratio1 >= INTERVAL) ratio = 4096; data0 = (data0 * ratio + data0p * (4096 - ratio)) >> 12; cdp = audio_channel[1]; ratio1 = cdp.per - cdp.evtime; ratio = ~~((ratio1 << 12) / INTERVAL); if (cdp.evtime < scaled_sample_evtime || ratio1 >= INTERVAL) ratio = 4096; data1 = (data1 * ratio + data1p * (4096 - ratio)) >> 12; cdp = audio_channel[2]; ratio1 = cdp.per - cdp.evtime; ratio = ~~((ratio1 << 12) / INTERVAL); if (cdp.evtime < scaled_sample_evtime || ratio1 >= INTERVAL) ratio = 4096; data2 = (data2 * ratio + data2p * (4096 - ratio)) >> 12; cdp = audio_channel[3]; ratio1 = cdp.per - cdp.evtime; ratio = ~~((ratio1 << 12) / INTERVAL); if (cdp.evtime < scaled_sample_evtime || ratio1 >= INTERVAL) ratio = 4096; data3 = (data3 * ratio + data3p * (4096 - ratio)) >> 12; } data1 += data2; data0 += data3; data2 = data0; data2 = data2 << 1; data3 = data1; data3 = data3 << 1; if (SAEV_config.audio.filter) { data2 = filter(data2, sound_filter_state[0]); data3 = filter(data3, sound_filter_state[1]); } put_sound_word_right(data2); put_sound_word_left(data3); paula.pointer++; paula.currrent++; check_sound_buffers(); } /*-----------------------------------------------------------------------*/ function zerostate(nr) { var cdp = audio_channel[nr]; cdp.state = 0; cdp.evtime = MAX_EV; cdp.intreq2 = 0; cdp.dmaenstore = false; cdp.dmaofftime_active = false; } function schedule_audio() { var best = MAX_EV; SAER_Events_eventtab[SAEC_Events_EV_AUDIO].active = false; SAER_Events_eventtab[SAEC_Events_EV_AUDIO].oldcycles = SAEV_Events_currcycle; for (var i = 0; i < audio_channel_count; i++) { var cdp = audio_channel[i]; if (cdp.evtime != MAX_EV) { if (best > cdp.evtime) { best = cdp.evtime; SAER_Events_eventtab[SAEC_Events_EV_AUDIO].active = true; } } } SAER_Events_eventtab[SAEC_Events_EV_AUDIO].evtime = SAEV_Events_currcycle + best; } function audio_event_reset() { last_cycles = SAEV_Events_currcycle; next_sample_evtime = scaled_sample_evtime; for (var i = 0; i < AUDIO_CHANNELS_PAULA; i++) zerostate(i); schedule_audio(); SAER.events.schedule(); } function audio_deactivate() { //SAER.gui.data.sndbuf_status = 3; //SAER.gui.data.sndbuf = 0; audio_work_to_do = 0; //pause_sound_buffer(); clear_sound_buffers(); audio_event_reset(); } SAER_Audio_deactivate = audio_deactivate; /* used by cpu.cpu_halt() */ function audio_activate() { var ret = false; if (audio_work_to_do == 0) { //restart_sound_buffer(); audio_event_reset(); cache.wait = true; ret = true; } audio_work_to_do = 4 * SAER.playfield.get_maxvpos_nom() * 50; return ret; } /*-----------------------------------------------------------------------*/ /* DMAL */ this.getpt = function(nr, reset) { //audio_getpt() var cdp = audio_channel[nr]; var p = cdp.pt; cdp.pt += 2; if (reset) cdp.pt = cdp.lc; cdp.ptx_tofetch = false; return p; } this.dmal = function() { //audio_dmal() var dmal = 0; for (var nr = 0; nr < AUDIO_CHANNELS_PAULA; nr++) { var cdp = audio_channel[nr]; if (cdp.dr) dmal |= 1 << (nr * 2); if (cdp.dsr) dmal |= 1 << (nr * 2 + 1); cdp.dr = cdp.dsr = false; } return dmal; } /*-----------------------------------------------------------------------*/ function isirq(nr) { //return (SAER.custom.INTREQR() & (0x80 << nr)) != 0; return (SAEV_Custom_intreq & (0x80 << nr)) != 0; } function setirq(nr, which) { SAER.custom.INTREQ_0(SAEC_Custom_INTF_SETCLR | (0x80 << nr)); } function newsample(nr, sample) { var cdp = audio_channel[nr]; //if (!(audio_channel_mask & (1 << nr))) sample = 0; if (sample & 0x80) sample -= 0x100; //OWN cdp.last_sample = cdp.current_sample; cdp.current_sample = sample; } function setdr(nr) { var cdp = audio_channel[nr]; cdp.drhpos = SAER.events.current_hpos(); cdp.dr = true; if (cdp.wlen == 1) cdp.dsr = true; } function loaddat(nr, modper) { var cdp = audio_channel[nr]; var audav = SAEV_Custom_adkcon & (0x01 << nr); var audap = SAEV_Custom_adkcon & (0x10 << nr); if (audav || (modper && audap)) { if (nr >= 3) return; var cdp1 = audio_channel[nr + 1]; //OWN if (modper && audap) { if (cdp.dat == 0) cdp1.per = 65536 * SAEC_Events_CYCLE_UNIT; else if (cdp.dat > PERIOD_MIN) cdp1.per = cdp.dat * SAEC_Events_CYCLE_UNIT; else cdp1.per = PERIOD_MIN * SAEC_Events_CYCLE_UNIT; } else if (audav) { cdp1.vol = cdp.dat; cdp1.vol &= 127; if (cdp1.vol > 64) cdp1.vol = 64; } } else cdp.dat2 = cdp.dat; } function loadper(nr) { var cdp = audio_channel[nr]; cdp.evtime = cdp.per; if (cdp.evtime < SAEC_Events_CYCLE_UNIT) SAEF_error("audio.LOADPER%d bug %d", nr, cdp.evtime); } function audio_state_channel2(nr, perfin) { var cdp = audio_channel[nr]; var chan_ena = (SAEV_Custom_dmacon & SAEC_Custom_DMAF_DMAEN) != 0 && (SAEV_Custom_dmacon & (1 << nr)) != 0; var old_dma = cdp.dmaenstore; var audav = SAEV_Custom_adkcon & (0x01 << nr); var audap = SAEV_Custom_adkcon & (0x10 << nr); var napnav = (!audav && !audap) || audav; var hpos = SAER.events.current_hpos(); cdp.dmaenstore = chan_ena; if (SAEV_config.audio.mode == SAEC_Config_Audio_Mode_Off) { zerostate(nr); return; } audio_activate(); if ((cdp.state == 2 || cdp.state == 3) && usehacks) { if (!chan_ena && old_dma) { // DMA switched off, state=2/3 and "too fast CPU": set flag cdp.dmaofftime_active = true; } if (cdp.dmaofftime_active && !old_dma && chan_ena) { // We are still in state=2/3 and program is going to re-enable // DMA. Force state to zero to prevent CPU timed DMA wait // routines in common tracker players to lose notes. newsample(nr, cdp.dat2 & 0xff); /*#if 0 if (napnav) setirq(nr, 91); #endif*/ zerostate (nr); } } switch (cdp.state) { case 0: { if (chan_ena) { cdp.evtime = MAX_EV; cdp.state = 1; cdp.dr = true; cdp.drhpos = hpos; cdp.wlen = cdp.len; cdp.ptx_written = false; /* Some programs first start short empty sample and then later switch to * real sample, we must not enable the hack in this case */ if (cdp.wlen > 2) cdp.ptx_tofetch = true; cdp.dsr = true; } else if (cdp.dat_written && !isirq(nr)) { cdp.state = 2; setirq(nr, 0); loaddat(nr, false); if (usehacks && cdp.per < 10 * SAEC_Events_CYCLE_UNIT) { // make sure audio.device AUDxDAT startup returns to idle state before DMA is enabled newsample(nr, cdp.dat2 & 0xff); zerostate(nr); } else { cdp.pbufldl = true; audio_state_channel2(nr, false); } } else { zerostate(nr); } break; } case 1: { cdp.evtime = MAX_EV; if (!chan_ena) { zerostate(nr); return; } if (!cdp.dat_written) return; setirq(nr, 10); setdr(nr); if (cdp.wlen != 1) cdp.wlen = ((cdp.wlen - 1) >>> 0) & 0xffff; cdp.state = 5; break; } case 5: { cdp.evtime = MAX_EV; if (!chan_ena) { zerostate(nr); return; } if (!cdp.dat_written) return; if (cdp.ptx_written) { cdp.ptx_written = 0; cdp.lc = cdp.ptx; } loaddat(nr, false); if (napnav) setdr(nr); cdp.state = 2; loadper(nr); cdp.pbufldl = true; cdp.intreq2 = 0; audio_state_channel2(nr, false); break; } case 2: { if (cdp.pbufldl) { newsample(nr, (cdp.dat2 >> 8) & 0xff); loadper(nr); cdp.pbufldl = false; } if (!perfin) return; if (audap) loaddat(nr, true); if (chan_ena) { if (audap) setdr(nr); if (cdp.intreq2 && audap) setirq(nr, 21); } else { if (audap) setirq(nr, 22); } cdp.pbufldl = true; cdp.state = 3; audio_state_channel2(nr, false); break; } case 3: { if (cdp.pbufldl) { newsample(nr, cdp.dat2 & 0xff); loadper(nr); cdp.pbufldl = false; } if (!perfin) return; if (chan_ena) { loaddat(nr, false); if (cdp.intreq2 && napnav) setirq(nr, 31); if (napnav) setdr(nr); } else { if (isirq(nr)) { zerostate(nr); return; } loaddat(nr, false); if (napnav) setirq(nr, 32); } cdp.intreq2 = 0; cdp.pbufldl = true; cdp.state = 2; audio_state_channel2(nr, false); break; } } } function audio_state_channel(nr, perfin) { var cdp = audio_channel[nr]; if (nr < AUDIO_CHANNELS_PAULA) { audio_state_channel2(nr, perfin); cdp.dat_written = false; } } this.state_machine = function() { //audio_state_machine() called in SAER.custom.DMACON() this.update(); for (var nr = 0; nr < AUDIO_CHANNELS_PAULA; nr++) { var cdp = audio_channel[nr]; audio_state_channel2(nr, false); cdp.dat_written = false; } schedule_audio(); SAER.events.schedule(); } /*-----------------------------------------------------------------------*/ this.obtain = function() { return obtain_sound(); } /* This computes the 1st order low-pass filter term b0. The a1 term is 1.0 - b0. The center frequency marks the -3 dB point. */ function rc_calculate_a0(sample_rate, cutoff_freq) { var omega; /* The BLT correction formula below blows up if the cutoff is above nyquist. */ if (cutoff_freq >= sample_rate >> 1) return 1.0; omega = 2 * Math.PI * cutoff_freq / sample_rate; /* Compensate for the bilinear transformation. This allows us to specify the stop frequency more exactly, but the filter becomes less steep further from stopband. */ omega = Math.tan(omega / 2) * 2; return 1.0 / (1.0 + 1.0 / omega); } /*this.check_prefs_changed_audio = function() { if (sound_available) { var ch = 1; //if (ch > 0) clear_sound_buffers(); if (ch) { this.set_audio(); audio_activate(); } } }*/ this.setup = function() { //set_audio() usehacks = SAEV_config.cpu.model >= SAEC_Config_CPU_Model_68020 || SAEV_config.cpu.speed != SAEC_Config_CPU_Speed_Original;// || (currprefs.cs_hacks & 4); //used_freq = SAEV_config.chipset.ntsc ? PAULA_FREQ_NTSC : PAULA_FREQ_PAL; used_freq = SAEC_Config_Audio_Freq_44100; paula.frames = SAEV_config.audio.bufferFrames; paula.buffer = new Array(2); //max channels for (var j = 0; j < paula.buffer.length; j++) paula.buffer[j] = new Int16Array(paula.frames); //paula_size = SAEV_config.audio.bufferFrames * SAEV_config.audio.channels * 2; //paula_pointer = paula_buffer; paula.pointer = 0; paula.currrent = 0; if (SAEV_config.audio.mode >= SAEC_Config_Audio_Mode_On) { if (!setup_sound()) return SAEE_Audio_RequiresWebAudio; //can't happen cos we fail on audio.obtain() } next_sample_evtime = scaled_sample_evtime; last_cycles = SAEV_Events_currcycle; //SAER.playfield.compute_vsynctime_ext(); //OWN unused SAEV_Audio_vsynctimebase_orig var sep = SAEV_config.audio.stereoSeparation * 3 >> 1; if (sep >= 15) sep = 16; mixed_mul1 = (MIXED_STEREO_SCALE >> 1) - sep; mixed_mul2 = (MIXED_STEREO_SCALE >> 1) + sep; var delay = SAEV_config.audio.stereoDelay; mixed_stereo_size = delay > 0 ? (1 << delay) - 1 : 0; mixed_on = sep < MIXED_STEREO_MAX || mixed_stereo_size > 0; if (mixed_on) { SAEF_log("audio.setup() mixing enabled"); saved_ptr = 0; SAEF_memset(right_word_saved,0, 0, SOUND_MAX_DELAY_BUFFER); } led_filter_forced = -1; // always off sound_use_filter = 0; if (SAEV_config.audio.filter) { if (SAEV_config.audio.filter == SAEC_Config_Audio_Filter_On) led_filter_forced = 1; if (SAEV_config.audio.filter == SAEC_Config_Audio_Filter_Emul) led_filter_forced = 0; if (SAEV_config.audio.filterType == SAEC_Config_Audio_FilterType_A500) sound_use_filter = FILTER_MODEL_A500; else if (SAEV_config.audio.filterType == SAEC_Config_Audio_FilterType_A1200) sound_use_filter = FILTER_MODEL_A1200; } a500e_filter1_a0 = rc_calculate_a0(used_freq, 6200); a500e_filter2_a0 = rc_calculate_a0(used_freq, 20000); filter_a0 = rc_calculate_a0(used_freq, 7000); this.led_filter_audio(); switch (SAEV_config.audio.interpol) { case SAEC_Config_Audio_Interpol_None: { switch (SAEV_config.audio.channels) { case SAEC_Config_Audio_Channels_Mono: sample_handler = sample16_mono_handler; break; case SAEC_Config_Audio_Channels_Stereo: sample_handler = sample16s_handler; break; } break; } case SAEC_Config_Audio_Interpol_Anti: { switch (SAEV_config.audio.channels) { case SAEC_Config_Audio_Channels_Mono: sample_handler = sample16i_anti_mono_handler; break; case SAEC_Config_Audio_Channels_Stereo: sample_handler = sample16si_anti_handler; break; } break; } case SAEC_Config_Audio_Interpol_RH: { switch (SAEV_config.audio.channels) { case SAEC_Config_Audio_Channels_Mono: sample_handler = sample16i_rh_mono_handler; break; case SAEC_Config_Audio_Channels_Stereo: sample_handler = sample16si_rh_handler; break; } break; } case SAEC_Config_Audio_Interpol_Crux: { switch (SAEV_config.audio.channels) { case SAEC_Config_Audio_Channels_Mono: sample_handler = sample16i_crux_mono_handler; break; case SAEC_Config_Audio_Channels_Stereo: sample_handler = sample16si_crux_handler; break; } break; } } sample_prehandler = null; if (SAEV_config.audio.interpol == SAEC_Config_Audio_Interpol_Anti) sample_prehandler = anti_prehandler; if (SAEV_config.audio.mode == SAEC_Config_Audio_Mode_Off) { SAER_Events_eventtab[SAEC_Events_EV_AUDIO].active = false; SAER.events.schedule(); } else { audio_activate(); //schedule_audio(); //OWN makes no sense //SAER.events.schedule(); } return SAEE_None; } this.cleanup = function() { cleanup_sound(); } this.reset = function() { reset_sound(); for (var i = 0; i < sound_filter_state.length; i++) sound_filter_state[i].clr(); for (i = 0; i < AUDIO_CHANNELS_MAX; i++) { var cdp = audio_channel[i]; cdp.clr(); cdp.per = PERIOD_MAX - 1; cdp.vol = 0; cdp.evtime = MAX_EV; } last_cycles = SAEV_Events_currcycle; next_sample_evtime = scaled_sample_evtime; schedule_audio(); SAER.events.schedule(); prevcon = -1; //OWN } this.pauseResume = function(pause) { if (pause) pause_sound(); else resume_sound(); } this.mute = function(mute) { mute_sound(mute) } /*-----------------------------------------------------------------------*/ var prevcon = -1; this.update_adkmasks = function() { //static int prevcon = -1; var t = SAEV_Custom_adkcon | (SAEV_Custom_adkcon >> 4); /*audio_channel[0].adk_mask = (((t >> 0) & 1) - 1) >>> 0; audio_channel[1].adk_mask = (((t >> 1) & 1) - 1) >>> 0; audio_channel[2].adk_mask = (((t >> 2) & 1) - 1) >>> 0; audio_channel[3].adk_mask = (((t >> 3) & 1) - 1) >>> 0;*/ audio_channel[0].enabled = (t & 1) == 0; audio_channel[1].enabled = (t & 3) == 0; audio_channel[2].enabled = (t & 7) == 0; audio_channel[3].enabled = (t & 15) == 0; if ((prevcon & 0xff) != (SAEV_Custom_adkcon & 0xff)) { audio_activate(); prevcon = SAEV_Custom_adkcon; } } this.update = function() { var n_cycles = 0; if (SAEV_config.audio.mode == SAEC_Config_Audio_Mode_Off || audio_work_to_do == 0) { last_cycles = SAEV_Events_currcycle; return; } n_cycles = SAEV_Events_currcycle - last_cycles; while (n_cycles > 0) { var best_evtime = n_cycles + 1; var rounded; var i; for (i = 0; i < audio_channel_count; i++) { if (audio_channel[i].evtime != MAX_EV && best_evtime > audio_channel[i].evtime) best_evtime = audio_channel[i].evtime; } /* next_sample_evtime >= 0 so floor() behaves as expected rounded = floor(next_sample_evtime); if ((next_sample_evtime - rounded) >= 0.5) rounded++; */ rounded = Math.round(next_sample_evtime); if (SAEV_config.audio.mode > SAEC_Config_Audio_Mode_Off_Emul && best_evtime > rounded) best_evtime = rounded; if (best_evtime > n_cycles) best_evtime = n_cycles; /* Decrease time-to-wait counters */ next_sample_evtime -= best_evtime; if (SAEV_config.audio.mode > SAEC_Config_Audio_Mode_Off_Emul) { if (sample_prehandler !== null) sample_prehandler(Math.floor(best_evtime * SAEC_Events_CYCLE_UNIT_INV)); } for (i = 0; i < audio_channel_count; i++) { if (audio_channel[i].evtime != MAX_EV) audio_channel[i].evtime -= best_evtime; } n_cycles -= best_evtime; if (SAEV_config.audio.mode > SAEC_Config_Audio_Mode_Off_Emul) { /* Test if new sample needs to be outputted */ if (rounded == best_evtime) { /* Before the following addition, next_sample_evtime is in range [-0.5, 0.5) */ next_sample_evtime += scaled_sample_evtime; sample_handler(); } } for (i = 0; i < audio_channel_count; i++) { if (audio_channel[i].evtime == 0) { audio_state_channel(i, true); if (audio_channel[i].evtime == 0) { SAEF_error("audio.update() evtime == 0 (channel %d)", i); audio_channel[i].evtime = MAX_EV; } } } } last_cycles = SAEV_Events_currcycle - n_cycles; } this.handler = function() { //audio_evhandler() this.update(); schedule_audio(); } this.hsync = function() { //audio_hsync() //if (SAEV_config.audio.mode == SAEC_Config_Audio_Mode_Off) return; //OWN done in custom/devices_hsync() if (audio_work_to_do > 0) { audio_work_to_do--; if (audio_work_to_do == 0) audio_deactivate(); } this.update(); } this.vsync = function() { //audio_vsync() } this.AUDxDAT = function(nr, v) { var cdp = audio_channel[nr]; var chan_ena = (SAEV_Custom_dmacon & SAEC_Custom_DMAF_DMAEN) != 0 && (SAEV_Custom_dmacon & (1 << nr)) != 0; cdp.dat = v; cdp.dat_written = true; if (cdp.state == 2 || cdp.state == 3) { if (chan_ena) { if (cdp.wlen == 1) { cdp.wlen = cdp.len; cdp.intreq2 = true; } else cdp.wlen = ((cdp.wlen - 1) >>> 0) & 0xffff; } } else { audio_activate(); this.update(); audio_state_channel(nr, false); schedule_audio(); SAER.events.schedule(); } cdp.dat_written = false; } this.AUDxLCH = function(nr, v) { var cdp = audio_channel[nr]; audio_activate(); this.update(); v = v & (SAEV_config.chipset.mask == SAEC_Config_Chipset_Mask_OCS ? 7 : 31); //OWN /* Someone wants to update PT but DSR has not yet been processed. Too fast CPU and some tracker players: enable DMA, CPU delay, update AUDxPT with loop position*/ if (usehacks && ((cdp.ptx_tofetch && cdp.state == 1) || cdp.ptx_written)) { cdp.ptx = cdp.lc; cdp.ptx_written = true; } else cdp.lc = ((v << 16) | (cdp.lc & 0x0000ffff)) >>> 0; } this.AUDxLCL = function(nr, v) { var cdp = audio_channel[nr]; audio_activate(); this.update(); if (usehacks && ((cdp.ptx_tofetch && cdp.state == 1) || cdp.ptx_written)) { cdp.ptx = cdp.lc; cdp.ptx_written = true; } else cdp.lc = ((cdp.lc & 0xffff0000) | (v & 0xFFFE)) >>> 0; } this.AUDxPER = function(nr, v) { var cdp = audio_channel[nr]; audio_activate(); this.update(); var per = v * SAEC_Events_CYCLE_UNIT; if (per == 0) per = PERIOD_MAX - 1; if (per < PERIOD_MIN * SAEC_Events_CYCLE_UNIT) per = PERIOD_MIN * SAEC_Events_CYCLE_UNIT; if (per < PERIOD_MIN_NONCE * SAEC_Events_CYCLE_UNIT && cdp.dmaenstore) per = PERIOD_MIN_NONCE * SAEC_Events_CYCLE_UNIT; if (cdp.per == PERIOD_MAX - 1 && per != PERIOD_MAX - 1) { cdp.evtime = SAEC_Events_CYCLE_UNIT; if (SAEV_config.audio.mode != SAEC_Config_Audio_Mode_Off) { schedule_audio(); SAER.events.schedule(); } } cdp.per = per; } this.AUDxLEN = function(nr, v) { audio_activate(); this.update(); audio_channel[nr].len = v; } this.AUDxVOL = function(nr, v) { v &= 127; if (v > 64) v = 64; audio_activate(); this.update(); audio_channel[nr].vol = v; } }