1705 lines
49 KiB
JavaScript
1705 lines
49 KiB
JavaScript
/*-------------------------------------------------------------------------
|
|
| 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()
|
|
//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()
|
|
muted = paused = false;
|
|
|
|
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;
|
|
}
|
|
}
|