Bochs/bochs/iodev/sound/sb16.cc
2018-05-10 15:50:25 +00:00

3155 lines
89 KiB
C++

/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2001-2018 The Bochs Project
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
//
/////////////////////////////////////////////////////////////////////////
// The original version of the SB16 support written and donated by Josef Drexler
// Define BX_PLUGGABLE in files that can be compiled into plugins. For
// platforms that require a special tag on exported symbols, BX_PLUGGABLE
// is used to know when we are exporting symbols and when we are importing.
#define BX_PLUGGABLE
#include "iodev.h"
#if BX_SUPPORT_SB16
#include "soundlow.h"
#include "soundmod.h"
#include "sb16.h"
#include "opl.h"
#include <math.h>
#define LOG_THIS theSB16Device->
bx_sb16_c *theSB16Device = NULL;
Bit32u fmopl_callback(void *dev, Bit16u rate, Bit8u *buffer, Bit32u len);
// builtin configuration handling functions
void sb16_init_options(void)
{
static const char *sb16_mode_list[] = {
"0",
"1",
"2",
"3",
NULL
};
bx_param_c *sound = SIM->get_param("sound");
bx_list_c *menu = new bx_list_c(sound, "sb16", "SB16 Configuration");
menu->set_options(menu->SHOW_PARENT);
bx_param_bool_c *enabled = new bx_param_bool_c(menu,
"enabled",
"Enable SB16 emulation",
"Enables the SB16 emulation",
1);
bx_param_enum_c *midimode = new bx_param_enum_c(menu,
"midimode",
"Midi mode",
"Controls the MIDI output switches.",
sb16_mode_list,
0, 0);
bx_param_filename_c *midifile = new bx_param_filename_c(menu,
"midifile",
"MIDI file",
"The filename is where the MIDI data is sent to in mode 2 or 3.",
"", BX_PATHNAME_LEN);
bx_param_enum_c *wavemode = new bx_param_enum_c(menu,
"wavemode",
"Wave mode",
"Controls the wave output switches.",
sb16_mode_list,
0, 0);
bx_param_filename_c *wavefile = new bx_param_filename_c(menu,
"wavefile",
"Wave file",
"This is the file where the wave output is stored",
"", BX_PATHNAME_LEN);
bx_param_num_c *loglevel = new bx_param_num_c(menu,
"loglevel",
"Log level",
"Controls how verbose the SB16 emulation is (0 = no log, 5 = all errors and infos).",
0, 5,
0);
bx_param_filename_c *logfile = new bx_param_filename_c(menu,
"log",
"Log file",
"The file to write the SB16 emulator messages to.",
"", BX_PATHNAME_LEN);
logfile->set_extension("log");
bx_param_num_c *dmatimer = new bx_param_num_c(menu,
"dmatimer",
"DMA timer",
"Microseconds per second for a DMA cycle.",
0, BX_MAX_BIT32U,
0);
bx_list_c *deplist = new bx_list_c(NULL);
deplist->add(midimode);
deplist->add(wavemode);
deplist->add(loglevel);
deplist->add(dmatimer);
enabled->set_dependent_list(deplist);
deplist = new bx_list_c(NULL);
deplist->add(midifile);
midimode->set_dependent_list(deplist, 0);
midimode->set_dependent_bitmap(2, 0x1);
midimode->set_dependent_bitmap(3, 0x1);
deplist = new bx_list_c(NULL);
deplist->add(wavefile);
wavemode->set_dependent_list(deplist, 0);
wavemode->set_dependent_bitmap(2, 0x1);
wavemode->set_dependent_bitmap(3, 0x1);
deplist = new bx_list_c(NULL);
deplist->add(logfile);
loglevel->set_dependent_list(deplist);
loglevel->set_options(loglevel->USE_SPIN_CONTROL);
}
Bit32s sb16_options_parser(const char *context, int num_params, char *params[])
{
if (!strcmp(params[0], "sb16")) {
bx_list_c *base = (bx_list_c*) SIM->get_param(BXPN_SOUND_SB16);
int enable = 1;
SIM->get_param_bool("enabled", base)->set(1);
for (int i = 1; i < num_params; i++) {
if (!strncmp(params[i], "enabled=", 8)) {
SIM->get_param_bool("enabled", base)->parse_param(&params[i][8]);
enable = SIM->get_param_bool("enabled", base)->get();
} else if (!strncmp(params[i], "midi=", 5)) {
SIM->get_param_string("midifile", base)->set(&params[i][5]);
} else if (!strncmp(params[i], "wave=", 5)) {
SIM->get_param_string("wavefile", base)->set(&params[i][5]);
} else if (SIM->parse_param_from_list(context, params[i], base) < 0) {
BX_ERROR(("%s: unknown parameter for sb16 ignored.", context));
}
}
if ((enable != 0) && (SIM->get_param_num("dmatimer", base)->get() == 0)) {
SIM->get_param_bool("enabled", base)->set(0);
}
} else {
BX_PANIC(("%s: unknown directive '%s'", context, params[0]));
}
return 0;
}
Bit32s sb16_options_save(FILE *fp)
{
return SIM->write_param_list(fp, (bx_list_c*) SIM->get_param(BXPN_SOUND_SB16), NULL, 0);
}
// device plugin entry points
int CDECL libsb16_LTX_plugin_init(plugin_t *plugin, plugintype_t type)
{
theSB16Device = new bx_sb16_c();
BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theSB16Device, BX_PLUGIN_SB16);
// add new configuration parameter for the config interface
sb16_init_options();
// register add-on option for bochsrc and command line
SIM->register_addon_option("sb16", sb16_options_parser, sb16_options_save);
return(0); // Success
}
void CDECL libsb16_LTX_plugin_fini(void)
{
delete theSB16Device;
SIM->unregister_addon_option("sb16");
((bx_list_c*)SIM->get_param("sound"))->remove("sb16");
}
// some shortcuts to save typing
#define LOGFILE BX_SB16_THIS logfile
#define MPU BX_SB16_THIS mpu401
#define DSP BX_SB16_THIS dsp
#define MIXER BX_SB16_THIS mixer
#define EMUL BX_SB16_THIS emuldata
#define OPL BX_SB16_THIS opl
#define BX_SB16_WAVEOUT1 BX_SB16_THIS waveout[0]
#define BX_SB16_WAVEOUT2 BX_SB16_THIS waveout[1]
#define BX_SB16_WAVEIN BX_SB16_THIS wavein
#define BX_SB16_MIDIOUT1 BX_SB16_THIS midiout[0]
#define BX_SB16_MIDIOUT2 BX_SB16_THIS midiout[1]
// here's a safe way to print out null pointeres
#define MIGHT_BE_NULL(x) ((x==NULL)? "(null)" : x)
// the device object
bx_sb16_c::bx_sb16_c(void)
{
put("SB16");
memset(&mpu401, 0, sizeof(mpu401));
memset(&dsp, 0, sizeof(dsp));
memset(&opl, 0, sizeof(opl));
currentdma8 = 0;
currentdma16 = 0;
mpu401.timer_handle = BX_NULL_TIMER_HANDLE;
dsp.timer_handle = BX_NULL_TIMER_HANDLE;
opl.timer_handle = BX_NULL_TIMER_HANDLE;
waveout[0] = NULL;
waveout[1] = NULL;
wavein = NULL;
midiout[0] = NULL;
midiout[1] = NULL;
wavemode = 0;
midimode = 0;
loglevel = 0;
logfile = NULL;
rt_conf_id = -1;
}
bx_sb16_c::~bx_sb16_c(void)
{
SIM->unregister_runtime_config_handler(rt_conf_id);
closemidioutput();
if (BX_SB16_WAVEOUT1 != NULL) {
BX_SB16_WAVEOUT1->unregister_wave_callback(fmopl_callback_id);
}
closewaveoutput();
delete [] DSP.dma.chunk;
if (LOGFILE != NULL)
fclose(LOGFILE);
SIM->get_bochs_root()->remove("sb16");
bx_list_c *misc_rt = (bx_list_c*)SIM->get_param(BXPN_MENU_RUNTIME_MISC);
misc_rt->remove("sb16");
BX_DEBUG(("Exit"));
}
void bx_sb16_c::init(void)
{
unsigned addr, i;
// Read in values from config interface
bx_list_c *base = (bx_list_c*) SIM->get_param(BXPN_SOUND_SB16);
// Check if the device is disabled or not configured
if (!SIM->get_param_bool("enabled", base)->get()) {
BX_INFO(("SB16 disabled"));
// mark unused plugin for removal
((bx_param_bool_c*)((bx_list_c*)SIM->get_param(BXPN_PLUGIN_CTRL))->get_by_name("sb16"))->set(0);
return;
}
create_logfile();
BX_SB16_THIS midimode = SIM->get_param_enum("midimode", base)->get();
BX_SB16_THIS wavemode = SIM->get_param_enum("wavemode", base)->get();
BX_SB16_THIS dmatimer = SIM->get_param_num("dmatimer", base)->get();
BX_SB16_THIS loglevel = SIM->get_param_num("loglevel", base)->get();
// always initialize lowlevel driver
BX_SB16_WAVEOUT1 = DEV_sound_get_waveout(0);
if (BX_SB16_WAVEOUT1 == NULL) {
BX_PANIC(("Couldn't initialize waveout driver"));
BX_SB16_THIS wavemode &= ~1;
} else {
BX_SB16_THIS fmopl_callback_id = BX_SB16_WAVEOUT1->register_wave_callback(BX_SB16_THISP, fmopl_callback);
}
if (BX_SB16_THIS wavemode & 2) {
BX_SB16_WAVEOUT2 = DEV_sound_get_waveout(1);
if (BX_SB16_WAVEOUT2 == NULL) {
BX_PANIC(("Couldn't initialize wave file driver"));
}
}
BX_SB16_WAVEIN = DEV_sound_get_wavein();
if (BX_SB16_WAVEIN == NULL) {
BX_PANIC(("Couldn't initialize wavein driver"));
}
BX_SB16_MIDIOUT1 = DEV_sound_get_midiout(0);
if (BX_SB16_MIDIOUT1 == NULL) {
BX_PANIC(("Couldn't initialize midiout driver"));
}
if (BX_SB16_THIS midimode & 2) {
BX_SB16_MIDIOUT2 = DEV_sound_get_midiout(1);
if (BX_SB16_MIDIOUT2 == NULL) {
BX_PANIC(("Couldn't initialize midi file driver"));
}
}
DSP.dma.chunk = new Bit8u[BX_SOUNDLOW_WAVEPACKETSIZE];
DSP.dma.chunkindex = 0;
DSP.outputinit = (BX_SB16_THIS wavemode & 1);
DSP.inputinit = 0;
MPU.outputinit = 0;
if (DSP.dma.chunk == NULL)
{
writelog(WAVELOG(2), "Couldn't allocate wave buffer - wave output disabled.");
BX_SB16_THIS wavemode = 0;
}
BX_INFO(("midi=%d,'%s' wave=%d,'%s' log=%d,'%s' dmatimer=%d",
BX_SB16_THIS midimode, MIGHT_BE_NULL(SIM->get_param_string("midifile", base)->getptr()),
BX_SB16_THIS wavemode, MIGHT_BE_NULL(SIM->get_param_string("wavefile", base)->getptr()),
BX_SB16_THIS loglevel, MIGHT_BE_NULL(SIM->get_param_string("log", base)->getptr()),
BX_SB16_THIS dmatimer));
// allocate the FIFO buffers - except for the MPUMIDICMD buffer
// these sizes are generous, 16 or 8 would probably be sufficient
MPU.datain.init((int) 64); // the input
MPU.dataout.init((int) 64); // and output
MPU.cmd.init((int) 64); // and command buffers
MPU.midicmd.init((int) 256); // and the midi command buffer (note- large SYSEX'es have to fit!)
DSP.datain.init((int) 64); // the DSP input
DSP.dataout.init((int) 64); // and output buffers
EMUL.datain.init((int) 64); // the emulator ports
EMUL.dataout.init((int) 64); // for changing emulator settings
// reset all parts of the hardware by
// triggering their reset functions
// reset the Emulator port
emul_write(0x00);
// reset the MPU401
mpu_command(0xff);
MPU.last_delta_time = 0xffffffff;
// reset the DSP
DSP.dma.highspeed = 0;
DSP.dma.mode = 0;
DSP.irqpending = 0;
DSP.midiuartmode = 0;
DSP.resetport = 1; // so that one call to dsp_reset is sufficient
dsp_reset(0); // (reset is 1 to 0 transition)
DSP.testreg = 0;
BX_SB16_IRQ = -1; // will be initialized later by the mixer reset
for (i=0; i<BX_SB16_MIX_REG; i++)
MIXER.reg[i] = 0xff;
MIXER.reg[0x00] = 0; // reset register
MIXER.reg[0x80] = 2; // IRQ 5
MIXER.reg[0x81] = 2; // 8-bit DMA 1, no 16-bit DMA
MIXER.reg[0x82] = 2 << 5; // no IRQ pending
MIXER.reg[0xfd] = 16; // ???
MIXER.reg[0xfe] = 6; // ???
set_irq_dma(); // set the IRQ and DMA
// call the mixer reset
mixer_writeregister(0x00);
mixer_writedata(0x00);
// reset the FM emulation
OPL.timer_running = 0;
for (i=0; i<2; i++) {
OPL.tmask[i] = 0;
OPL.tflag[i] = 0;
}
for (i=0; i<4; i++) {
OPL.timer[i] = 0;
OPL.timerinit[i] = 0;
}
adlib_init(44100);
// csp
memset(&BX_SB16_THIS csp_reg[0], 0, sizeof(BX_SB16_THIS csp_reg));
BX_SB16_THIS csp_reg[5] = 0x01;
BX_SB16_THIS csp_reg[9] = 0xf8;
// Allocate the IO addresses, 2x0..2xf, 3x0..3x4 and 388..38b
for (addr=BX_SB16_IO; addr<BX_SB16_IO+BX_SB16_IOLEN; addr++) {
DEV_register_ioread_handler(this, &read_handler, addr, "SB16", 1);
DEV_register_iowrite_handler(this, &write_handler, addr, "SB16", 1);
}
for (addr=BX_SB16_IOMPU; addr<BX_SB16_IOMPU+BX_SB16_IOMPULEN; addr++) {
DEV_register_ioread_handler(this, &read_handler, addr, "SB16", 1);
DEV_register_iowrite_handler(this, &write_handler, addr, "SB16", 1);
}
for (addr=BX_SB16_IOADLIB; addr<BX_SB16_IOADLIB+BX_SB16_IOADLIBLEN; addr++) {
DEV_register_ioread_handler(this, read_handler, addr, "SB16", 1);
DEV_register_iowrite_handler(this, write_handler, addr, "SB16", 1);
}
writelog(BOTHLOG(1),
"SB16 emulation initialised, IRQ %d, IO %03x/%03x/%03x, DMA %d/%d",
BX_SB16_IRQ, BX_SB16_IO, BX_SB16_IOMPU, BX_SB16_IOADLIB,
BX_SB16_DMAL, BX_SB16_DMAH);
// initialize the timers
if (MPU.timer_handle == BX_NULL_TIMER_HANDLE) {
MPU.timer_handle = DEV_register_timer
(BX_SB16_THISP, mpu_timer, 500000 / 384, 1, 1, "sb16.mpu");
// midi timer: active, continuous, 500000 / 384 seconds (384 = delta time, 500000 = sec per beat at 120 bpm. Don't change this!)
}
if (DSP.timer_handle == BX_NULL_TIMER_HANDLE) {
DSP.timer_handle = DEV_register_timer
(BX_SB16_THISP, dsp_dmatimer, 1, 1, 0, "sb16.dsp");
// dma timer: inactive, continuous, frequency variable
}
if (OPL.timer_handle == BX_NULL_TIMER_HANDLE) {
OPL.timer_handle = DEV_register_timer
(BX_SB16_THISP, opl_timer, 80, 1, 0, "sb16.opl");
// opl timer: inactive, continuous, frequency 80us
}
writelog(MIDILOG(4), "Timers initialized, midi %d, dma %d, opl %d",
MPU.timer_handle, DSP.timer_handle, OPL.timer_handle);
MPU.current_timer = 0;
// init runtime parameters
bx_list_c *misc_rt = (bx_list_c*)SIM->get_param(BXPN_MENU_RUNTIME_MISC);
bx_list_c *menu = new bx_list_c(misc_rt, "sb16", "SB16 Runtime Options");
menu->set_options(menu->SHOW_PARENT | menu->USE_BOX_TITLE);
menu->add(SIM->get_param("midimode", base));
menu->add(SIM->get_param("midifile", base));
menu->add(SIM->get_param("wavemode", base));
menu->add(SIM->get_param("wavefile", base));
menu->add(SIM->get_param("loglevel", base));
menu->add(SIM->get_param("log", base));
menu->add(SIM->get_param("dmatimer", base));
SIM->get_param_enum("wavemode", base)->set_handler(sb16_param_handler);
SIM->get_param_string("wavefile", base)->set_handler(sb16_param_string_handler);
SIM->get_param_num("midimode", base)->set_handler(sb16_param_handler);
SIM->get_param_string("midifile", base)->set_handler(sb16_param_string_handler);
SIM->get_param_num("dmatimer", base)->set_handler(sb16_param_handler);
SIM->get_param_num("loglevel", base)->set_handler(sb16_param_handler);
SIM->get_param_string("log", base)->set_handler(sb16_param_string_handler);
// register handler for correct sb16 parameter handling after runtime config
BX_SB16_THIS rt_conf_id = SIM->register_runtime_config_handler(this, runtime_config_handler);
BX_SB16_THIS midi_changed = 0;
BX_SB16_THIS wave_changed = 0;
}
void bx_sb16_c::reset(unsigned type)
{
}
void bx_sb16_c::register_state(void)
{
unsigned i;
char name[8];
bx_list_c *chip, *ins_map, *patch;
bx_list_c *list = new bx_list_c(SIM->get_bochs_root(), "sb16", "SB16 State");
bx_list_c *mpu = new bx_list_c(list, "mpu");
new bx_shadow_bool_c(mpu, "uartmode", &MPU.uartmode);
new bx_shadow_bool_c(mpu, "irqpending", &MPU.irqpending);
new bx_shadow_bool_c(mpu, "forceuartmode", &MPU.forceuartmode);
new bx_shadow_bool_c(mpu, "singlecommand", &MPU.singlecommand);
new bx_shadow_num_c(mpu, "current_timer", &MPU.current_timer);
new bx_shadow_num_c(mpu, "last_delta_time", &MPU.last_delta_time);
bx_list_c *patchtbl = new bx_list_c(mpu, "patchtable");
for (i=0; i<16; i++) {
sprintf(name, "0x%02x", i);
patch = new bx_list_c(patchtbl, name);
new bx_shadow_num_c(patch, "banklsb", &MPU.banklsb[i]);
new bx_shadow_num_c(patch, "bankmsb", &MPU.bankmsb[i]);
new bx_shadow_num_c(patch, "program", &MPU.program[i]);
}
bx_list_c *dsp = new bx_list_c(list, "dsp");
new bx_shadow_num_c(dsp, "resetport", &DSP.resetport, BASE_HEX);
new bx_shadow_num_c(dsp, "speaker", &DSP.speaker, BASE_HEX);
new bx_shadow_num_c(dsp, "prostereo", &DSP.prostereo, BASE_HEX);
new bx_shadow_bool_c(dsp, "irqpending", &DSP.irqpending);
new bx_shadow_bool_c(dsp, "midiuartmode", &DSP.midiuartmode);
new bx_shadow_num_c(dsp, "testreg", &DSP.testreg, BASE_HEX);
bx_list_c *dma = new bx_list_c(dsp, "dma");
new bx_shadow_num_c(dma, "mode", &DSP.dma.mode);
new bx_shadow_num_c(dma, "bps", &DSP.dma.bps);
new bx_shadow_num_c(dma, "timer", &DSP.dma.timer);
new bx_shadow_bool_c(dma, "fifo", &DSP.dma.fifo);
new bx_shadow_bool_c(dma, "output", &DSP.dma.output);
new bx_shadow_bool_c(dma, "highspeed", &DSP.dma.highspeed);
new bx_shadow_num_c(dma, "count", &DSP.dma.count);
new bx_shadow_num_c(dma, "chunkindex", &DSP.dma.chunkindex);
new bx_shadow_num_c(dma, "chunkcount", &DSP.dma.chunkcount);
new bx_shadow_num_c(dma, "timeconstant", &DSP.dma.timeconstant);
new bx_shadow_num_c(dma, "blocklength", &DSP.dma.blocklength);
new bx_shadow_num_c(dma, "samplerate", &DSP.dma.param.samplerate);
new bx_shadow_num_c(dma, "bits", &DSP.dma.param.bits);
new bx_shadow_num_c(dma, "channels", &DSP.dma.param.channels);
new bx_shadow_num_c(dma, "format", &DSP.dma.param.format);
new bx_shadow_num_c(dma, "volume", &DSP.dma.param.volume);
new bx_shadow_num_c(list, "fm_volume", &fm_volume);
new bx_shadow_data_c(list, "chunk", DSP.dma.chunk, BX_SOUNDLOW_WAVEPACKETSIZE);
new bx_shadow_data_c(list, "csp_reg", BX_SB16_THIS csp_reg, 256, 1);
bx_list_c *opl = new bx_list_c(list, "opl");
new bx_shadow_num_c(opl, "timer_running", &OPL.timer_running);
for (i=0; i<2; i++) {
sprintf(name, "chip%d", i+1);
chip = new bx_list_c(opl, name);
new bx_shadow_num_c(chip, "index", &OPL.index[i]);
new bx_shadow_num_c(chip, "timer1", &OPL.timer[i*2]);
new bx_shadow_num_c(chip, "timer2", &OPL.timer[i*2+1]);
new bx_shadow_num_c(chip, "timerinit1", &OPL.timerinit[i*2]);
new bx_shadow_num_c(chip, "timerinit2", &OPL.timerinit[i*2+1]);
new bx_shadow_num_c(chip, "tmask", &OPL.tmask[i]);
new bx_shadow_num_c(chip, "tflag", &OPL.tflag[i]);
}
new bx_shadow_num_c(list, "mixer_regindex", &MIXER.regindex, BASE_HEX);
new bx_shadow_data_c(list, "mixer_reg", MIXER.reg, BX_SB16_MIX_REG, 1);
bx_list_c *emul = new bx_list_c(list, "emul");
new bx_shadow_num_c(emul, "remaps", &EMUL.remaps);
bx_list_c *remap = new bx_list_c(emul, "remaplist");
for (i=0; i<BX_SB16_MAX_REMAPS; i++) {
sprintf(name, "0x%02x", i);
ins_map = new bx_list_c(remap, name);
new bx_shadow_num_c(ins_map, "oldbankmsb", &EMUL.remaplist[i].oldbankmsb);
new bx_shadow_num_c(ins_map, "oldbanklsb", &EMUL.remaplist[i].oldbanklsb);
new bx_shadow_num_c(ins_map, "oldprogch", &EMUL.remaplist[i].oldprogch);
new bx_shadow_num_c(ins_map, "newbankmsb", &EMUL.remaplist[i].newbankmsb);
new bx_shadow_num_c(ins_map, "newbanklsb", &EMUL.remaplist[i].newbanklsb);
new bx_shadow_num_c(ins_map, "newprogch", &EMUL.remaplist[i].newprogch);
}
adlib_register_state(list);
}
void bx_sb16_c::after_restore_state(void)
{
set_irq_dma();
adlib_after_restore_state();
}
void bx_sb16_c::runtime_config_handler(void *this_ptr)
{
bx_sb16_c *class_ptr = (bx_sb16_c *) this_ptr;
class_ptr->runtime_config();
}
void bx_sb16_c::runtime_config(void)
{
bx_list_c *base = (bx_list_c*) SIM->get_param(BXPN_SOUND_SB16);
if (BX_SB16_THIS midi_changed != 0) {
BX_SB16_THIS closemidioutput();
if (BX_SB16_THIS midi_changed & 1) {
BX_SB16_THIS midimode = SIM->get_param_num("midimode", base)->get();
if (BX_SB16_THIS midimode & 2) {
BX_SB16_MIDIOUT2 = DEV_sound_get_midiout(1);
if (BX_SB16_MIDIOUT2 == NULL) {
BX_PANIC(("Couldn't initialize midi file driver"));
}
}
}
// writemidicommand() re-opens the output device / file on demand
BX_SB16_THIS midi_changed = 0;
}
if (BX_SB16_THIS wave_changed != 0) {
if (BX_SB16_THIS wavemode & 2) {
BX_SB16_THIS closewaveoutput();
}
if (BX_SB16_THIS wave_changed & 1) {
BX_SB16_THIS wavemode = SIM->get_param_enum("wavemode", base)->get();
DSP.outputinit = (BX_SB16_THIS wavemode & 1);
if (BX_SB16_THIS wavemode & 2) {
BX_SB16_WAVEOUT2 = DEV_sound_get_waveout(1);
if (BX_SB16_WAVEOUT2 == NULL) {
BX_PANIC(("Couldn't initialize wave file driver"));
}
}
}
// dsp_dma() re-opens the output file on demand
BX_SB16_THIS wave_changed = 0;
}
}
// the timer functions
void bx_sb16_c::mpu_timer (void *this_ptr)
{
((bx_sb16_c *) this_ptr)->mpu401.current_timer++;
}
void bx_sb16_c::dsp_dmatimer(void *this_ptr)
{
bx_sb16_c *This = (bx_sb16_c *) this_ptr;
// raise the DRQ line. It is then lowered by the dma read / write functions
// when the next byte has been sent / received.
// However, don't do this if the next byte/word will fill up the
// output buffer and the output functions are not ready yet
// or if buffer is empty in input mode.
if ((This->dsp.dma.chunkindex + 1 < BX_SOUNDLOW_WAVEPACKETSIZE) &&
(This->dsp.dma.count > 0)) {
if (((This->dsp.dma.output == 0) && (This->dsp.dma.chunkcount > 0)) ||
(This->dsp.dma.output == 1)) {
if ((DSP.dma.param.bits == 8) || (BX_SB16_DMAH == 0)) {
DEV_dma_set_drq(BX_SB16_DMAL, 1);
} else {
DEV_dma_set_drq(BX_SB16_DMAH, 1);
}
}
}
}
void bx_sb16_c::opl_timer (void *this_ptr)
{
((bx_sb16_c *) this_ptr)->opl_timerevent();
}
// the various IO handlers
// The DSP/FM music part
// dsp_reset() resets the DSP after the sequence 1/0. Returns
// 0xaa on the data port
void bx_sb16_c::dsp_reset(Bit32u value)
{
writelog(WAVELOG(4), "DSP Reset port write value %x", value);
// just abort high speed mode if it is set
if (DSP.dma.highspeed != 0)
{
DSP.dma.highspeed = 0;
writelog(WAVELOG(4), "High speed mode aborted");
return;
}
if ((DSP.resetport == 1) && (value == 0))
{
// 1-0 sequences to reset port, do one of the following:
// if in UART MIDI mode, abort it, don't reset
// if in Highspeed mode (not SB16!), abort it, don't reset
// otherwise reset
if (DSP.midiuartmode != 0)
{ // abort UART MIDI mode
DSP.midiuartmode = 0;
writelog(MIDILOG(4), "DSP UART MIDI mode aborted");
return;
}
// do the reset
writelog(WAVELOG(4), "DSP resetting...");
if (DSP.irqpending != 0)
{
DEV_pic_lower_irq(BX_SB16_IRQ);
writelog(WAVELOG(4), "DSP reset: IRQ untriggered");
}
if (DSP.dma.mode != 0)
{
writelog(WAVELOG(4), "DSP reset: DMA aborted");
DSP.dma.mode = 1; // no auto init anymore
dsp_dmadone();
}
DSP.resetport = 0;
DSP.speaker = 0;
DSP.irqpending = 0;
DSP.midiuartmode = 0;
DSP.prostereo = 0;
DSP.dma.mode = 0;
DSP.dma.fifo = 0;
DSP.dma.output = 0;
DSP.dma.param.channels = 1;
DSP.dma.count = 0;
DSP.dma.highspeed = 0;
DSP.dma.chunkindex = 0;
DSP.dataout.reset(); // clear the buffers
DSP.datain.reset();
DSP.dataout.put(0xaa); // acknowledge the reset
}
else
DSP.resetport = value;
}
// dsp_dataread() reads the data port of the DSP
Bit32u bx_sb16_c::dsp_dataread()
{
Bit8u value = 0xff;
// if we are in MIDI UART mode, call the mpu401 part instead
if (DSP.midiuartmode != 0)
value = mpu_dataread();
else
{
// default behaviour: if none available, return last byte again
// if (DSP.dataout.empty() == 0)
DSP.dataout.get(&value);
}
writelog(WAVELOG(4), "DSP Data port read, result = %x", value);
return(value);
}
// dsp_datawrite() writes a command or data byte to the data port
void bx_sb16_c::dsp_datawrite(Bit32u value)
{
int bytesneeded;
Bit8u index = 0, mode = 0, value8 = 0;
Bit16u length = 0;
writelog(WAVELOG(4), "DSP Data port write, value %x", value);
// in high speed mode, any data passed to DSP is a sample
if (DSP.dma.highspeed != 0)
{
dsp_getsamplebyte(value);
return;
}
// route information to mpu401 part if in MIDI UART mode
if (DSP.midiuartmode != 0)
{
mpu_datawrite(value);
return;
}
if (DSP.datain.hascommand() == 1) // already a command pending, add to argument list
{
if (DSP.datain.put(value) == 0)
{
writelog(WAVELOG(3), "DSP command buffer overflow for command %02x",
DSP.datain.currentcommand());
}
}
else // no command pending, set one up
{
bytesneeded = 0; // find out how many arguments the command takes
switch (value)
{ // all fallbacks intended!
case 0x04:
case 0x0f:
case 0x10:
case 0x40:
case 0x38:
case 0xe0:
case 0xe2:
case 0xe4:
case 0xf9:
bytesneeded = 1;
break;
case 0x05:
case 0x0e:
case 0x14:
case 0x16:
case 0x17:
case 0x41:
case 0x42:
case 0x48:
case 0x74:
case 0x75:
case 0x76:
case 0x77:
case 0x80:
bytesneeded = 2;
break;
// 0xb0 ... 0xbf:
case 0xb0:
case 0xb1:
case 0xb2:
case 0xb3:
case 0xb4:
case 0xb5:
case 0xb6:
case 0xb7:
case 0xb8:
case 0xb9:
case 0xba:
case 0xbb:
case 0xbc:
case 0xbd:
case 0xbe:
case 0xbf:
// 0xc0 ... 0xcf:
case 0xc0:
case 0xc1:
case 0xc2:
case 0xc3:
case 0xc4:
case 0xc5:
case 0xc6:
case 0xc7:
case 0xc8:
case 0xc9:
case 0xca:
case 0xcb:
case 0xcc:
case 0xcd:
case 0xce:
case 0xcf:
bytesneeded = 3;
break;
}
DSP.datain.newcommand(value, bytesneeded);
}
if (DSP.datain.commanddone() == 1) // command is complete, process it
{
writelog(WAVELOG(4), "DSP command %x with %d arg bytes",
DSP.datain.currentcommand(), DSP.datain.bytes());
switch (DSP.datain.currentcommand())
{
// DSP commands - comments are the parameters for
// this command, and/or the output
// ASP commands (Advanced Signal Processor)
// undocumented (?), just from looking what an SB16 does
case 0x04:
DSP.datain.get(&value8);
break;
case 0x05:
DSP.datain.get(&value8);
DSP.datain.get(&value8);
break;
case 0x0e:
DSP.datain.get(&index);
DSP.datain.get(&value8);
BX_SB16_THIS csp_reg[index] = value;
break;
case 0x0f:
DSP.datain.get(&index);
DSP.dataout.put(BX_SB16_THIS csp_reg[index]);
break;
// direct mode DAC
case 0x10:
// 1: 8bit sample
DSP.datain.get(&value8); // sample is ignored
break;
// uncomp'd, normal DAC DMA
case 0x14:
// 1,2: lo(length) hi(length)
DSP.datain.getw(&length);
dsp_dma(0xc0, 0x00, length, 0);
break;
// 2-bit comp'd, normal DAC DMA, no ref byte
case 0x16:
// 1,2: lo(length) hi(length)
DSP.datain.getw(&length);
dsp_dma(0xc0, 0x00, length, 2);
break;
// 2-bit comp'd, normal DAC DMA, 1 ref byte
case 0x17:
// 1,2: lo(length) hi(length)
DSP.datain.getw(&length);
dsp_dma(0xc0, 0x00, length, 2|8);
break;
// uncomp'd, auto DAC DMA
case 0x1c:
// none
dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 0);
break;
// 2-bit comp'd, auto DAC DMA, 1 ref byte
case 0x1f:
// none
dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 2|8);
break;
// direct mode ADC
case 0x20:
// o1: 8bit sample
DSP.dataout.put(0x80); // put a silence, for now.
break;
// uncomp'd, normal ADC DMA
case 0x24:
// 1,2: lo(length) hi(length)
DSP.datain.getw(&length);
dsp_dma(0xc8, 0x00, length, 0);
break;
// uncomp'd, auto ADC DMA
case 0x2c:
// none
dsp_dma(0xcc, 0x00, DSP.dma.blocklength, 0);
break;
// ? polling mode MIDI input
case 0x30:
break;
// ? interrupt mode MIDI input
case 0x31:
break;
// 0x34..0x37: UART mode MIDI output
case 0x34:
// UART mode MIDI input/output
case 0x35:
// UART polling mode MIDI IO with time stamp
case 0x36:
// UART interrupt mode MIDI IO with time stamp
case 0x37:
// Fallbacks intended - all set the midi uart mode
DSP.midiuartmode = 1;
break;
// MIDI output
case 0x38:
DSP.datain.get(&value8);
// route to mpu401 part
mpu_datawrite(value8);
break;
// set time constant
case 0x40:
// 1: timeconstant
DSP.datain.get(&value8);
DSP.dma.timeconstant = value8 << 8;
DSP.dma.param.samplerate = (Bit32u) 256000000L / ((Bit32u) 65536L - (Bit32u) DSP.dma.timeconstant);
break;
// set samplerate for input
case 0x41:
// (fallback intended)
// set samplerate for output
case 0x42:
// 1,2: hi(frq) lo(frq)
DSP.datain.getw1(&(DSP.dma.param.samplerate));
DSP.dma.timeconstant = 65536 - (Bit32u) 256000000 / (Bit32u) DSP.dma.param.samplerate;
break;
// set block length
case 0x48:
// 1,2: lo(blk len) hi(blk len)
DSP.datain.getw(&(DSP.dma.blocklength));
break;
// 4-bit comp'd, normal DAC DMA, no ref byte
case 0x74:
// 1,2: lo(length) hi(length)
DSP.datain.getw(&length);
dsp_dma(0xc0, 0x00, length, 4);
break;
// 4-bit comp'd, normal DAC DMA, 1 ref byte
case 0x75:
// 1,2: lo(length) hi(length)
DSP.datain.getw(&length);
dsp_dma(0xc0, 0x00, length, 4|8);
break;
// 3-bit comp'd, normal DAC DMA, no ref byte
case 0x76:
// 1,2: lo(length) hi(length)
DSP.datain.getw(&length);
dsp_dma(0xc0, 0x00, length, 3);
break;
// 3-bit comp'd, normal DAC DMA, 1 ref byte
case 0x77:
// 1,2: lo(length) hi(length)
DSP.datain.getw(&length);
dsp_dma(0xc0, 0x00, length, 3|8);
break;
// 4-bit comp'd, auto DAC DMA, 1 ref byte
case 0x7d:
// none
dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 4|8);
break;
// 3-bit comp'd, auto DAC DMA, 1 ref byte
case 0x7f:
// none
dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 3|8);
break;
// silence period
case 0x80:
// 1,2: lo(silence) hi(silence) (len in samples)
DSP.datain.getw(&length);
// TODO
break;
// 8-bit auto DAC DMA, highspeed
case 0x90:
//none
dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 16);
break;
// 8-bit normal DAC DMA, highspeed
case 0x91:
//none
dsp_dma(0xc0, 0x00, DSP.dma.blocklength, 16);
break;
// 8-bit auto ADC DMA, highspeed
case 0x98:
//none
dsp_dma(0xcc, 0x00, DSP.dma.blocklength, 16);
break;
case 0x99: // 8-bit normal DMA
//none
dsp_dma(0xc8, 0x00, DSP.dma.blocklength, 16);
break;
// switch to mono for SBPro DAC/ADC
case 0xa0:
// none
DSP.prostereo = 1;
break;
// switch to stereo for SBPro DAC/ADC
case 0xa8:
//// none
DSP.prostereo = 2;
break;
// 0xb0 ... 0xbf:
// 16 bit DAC/ADC DMA, general commands
// fallback intended
case 0xb0:
case 0xb1:
case 0xb2:
case 0xb3:
case 0xb4:
case 0xb5:
case 0xb6:
case 0xb7:
case 0xb8:
case 0xb9:
case 0xba:
case 0xbb:
case 0xbc:
case 0xbd:
case 0xbe:
case 0xbf:
// 0xc0 ... 0xcf:
// 8 bit DAC/ADC DMA, general commands
case 0xc0:
case 0xc1:
case 0xc2:
case 0xc3:
case 0xc4:
case 0xc5:
case 0xc6:
case 0xc7:
case 0xc8:
case 0xc9:
case 0xca:
case 0xcb:
case 0xcc:
case 0xcd:
case 0xce:
case 0xcf:
DSP.datain.get(&mode);
DSP.datain.getw(&length);
dsp_dma(DSP.datain.currentcommand(), mode, length, 0);
break;
// pause 8 bit DMA transfer
case 0xd0:
// none
if (DSP.dma.mode != 0)
dsp_disabledma();
break;
// speaker on
case 0xd1:
// none
DSP.speaker = 1;
break;
// speaker off
case 0xd3:
// none
DSP.speaker = 0;
break;
// continue 8 bit DMA, see 0xd0
case 0xd4:
// none
if (DSP.dma.mode != 0)
dsp_enabledma();
break;
// pause 16 bit DMA
case 0xd5:
// none
if (DSP.dma.mode != 0)
dsp_disabledma();
break;
// continue 16 bit DMA, see 0xd5
case 0xd6:
// none
if (DSP.dma.mode != 0)
dsp_enabledma();
break;
// read speaker on/off (out ff=on, 00=off)
case 0xd8:
// none, o1: speaker; ff/00
DSP.dataout.put((DSP.speaker == 1)?0xff:0x00);
break;
// stop 16 bit auto DMA
case 0xd9:
// none
if (DSP.dma.mode != 0)
{
DSP.dma.mode = 1; // no auto init anymore
dsp_dmadone();
}
break;
// stop 8 bit auto DMA
case 0xda:
// none
if (DSP.dma.mode != 0)
{
DSP.dma.mode = 1; // no auto init anymore
dsp_dmadone();
}
break;
// DSP identification
case 0xe0:
DSP.datain.get(&value8);
DSP.dataout.put(~value8);
break;
// get version, out 2 bytes (major, minor)
case 0xe1:
// none, o1/2: version major.minor
DSP.dataout.put(4);
if (DSP.dataout.put(5) == 0)
{
writelog(WAVELOG(3), "DSP version couldn't be written - buffer overflow");
}
break;
case 0xe2:
DSP.datain.get(&value8);
// TODO
writelog(WAVELOG(3), "undocumented DSP command %x ignored (value = 0x%02x)",
DSP.datain.currentcommand(), value8);
break;
case 0xe3:
// none, output: Copyright string
// the Windows driver needs the exact text, otherwise it
// won't load. Same for diagnose.exe
DSP.dataout.puts("COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.");
DSP.dataout.put(0); // need extra string end
break;
// write test register
case 0xe4:
DSP.datain.get(&DSP.testreg);
break;
// read test register
case 0xe8:
DSP.dataout.put(DSP.testreg);
break;
// Trigger 8-bit IRQ
case 0xf2:
DSP.dataout.put(0xaa);
DSP.irqpending = 1;
MIXER.reg[0x82] |= 1; // reg 82 shows the kind of IRQ
DEV_pic_raise_irq(BX_SB16_IRQ);
break;
// ??? - Win98 needs this
case 0xf9:
DSP.datain.get(&value8);
switch (value8) {
case 0x0e:
DSP.dataout.put(0xff);
break;
case 0x0f:
DSP.dataout.put(0x07);
break;
case 0x37:
DSP.dataout.put(0x38);
break;
default:
DSP.dataout.put(0x00);
}
break;
// unknown command
default:
writelog(WAVELOG(3), "unknown DSP command %x, ignored",
DSP.datain.currentcommand());
break;
}
DSP.datain.clearcommand();
DSP.datain.flush();
}
}
// dsp_dma() initiates all kinds of dma transfers
void bx_sb16_c::dsp_dma(Bit8u command, Bit8u mode, Bit16u length, Bit8u comp)
{
int ret;
bx_list_c *base;
bx_bool issigned;
// command: 8bit, 16bit, in/out, single/auto, fifo
// mode: mono/stereo, signed/unsigned
// (for info on command and mode see sound blaster programmer's manual,
// cmds bx and cx)
// length: number of samples - not number of bytes
// comp: bit-coded are: type of compression; ref-byte; highspeed
// D0..D2: 0=none, 2,3,4 bits ADPCM
// D3: ref-byte
// D6: highspeed
writelog(WAVELOG(4), "DMA initialized. Cmd %02x, mode %02x, length %d, comp %d",
command, mode, length, comp);
if ((command >> 4) == 0xb) // 0xb? = 16 bit DMA
{
DSP.dma.param.bits = 16;
DSP.dma.bps = 2;
}
else // 0xc? = 8 bit DMA
{
DSP.dma.param.bits = 8;
DSP.dma.bps = 1;
}
// Prevent division by zero in some instances
if (DSP.dma.param.samplerate == 0)
DSP.dma.param.samplerate = 10752;
command &= 0x0f;
DSP.dma.output = 1 - (command >> 3); // 1=output, 0=input
DSP.dma.mode = 1 + ((command >> 2) & 1); // 0=none, 1=normal, 2=auto
DSP.dma.fifo = (command >> 1) & 1; // ? not sure what this is
DSP.dma.param.channels = ((mode >> 5) & 1) + 1;
if (DSP.dma.param.channels == 2)
DSP.dma.bps *= 2;
DSP.dma.blocklength = length;
issigned = (mode >> 4) & 1;
DSP.dma.highspeed = (comp >> 4) & 1;
DSP.dma.chunkindex = 0;
DSP.dma.chunkcount = 0;
Bit32u sampledatarate = (Bit32u) DSP.dma.param.samplerate * (Bit32u) DSP.dma.bps;
if (DSP.dma.param.bits == 8 || (DSP.dma.param.bits == 16 && BX_SB16_DMAH != 0)) {
DSP.dma.count = DSP.dma.blocklength;
} else {
DSP.dma.count = ((DSP.dma.blocklength + 1) << 1) - 1;
}
DSP.dma.timer = BX_SB16_THIS dmatimer * BX_DMA_BUFFER_SIZE / sampledatarate;
writelog(WAVELOG(5), "DMA is %db, %dHz, %s, %s, mode %d, %s, %s, %d bps, %d usec/DMA",
DSP.dma.param.bits, DSP.dma.param.samplerate,
(DSP.dma.param.channels == 2)?"stereo":"mono",
(DSP.dma.output == 1)?"output":"input", DSP.dma.mode,
(issigned == 1)?"signed":"unsigned",
(DSP.dma.highspeed == 1)?"highspeed":"normal speed",
sampledatarate, DSP.dma.timer);
DSP.dma.param.format = issigned | ((comp & 7) << 1) | ((comp & 8) << 4);
// write the output to the device/file
if (DSP.dma.output == 1) {
if (BX_SB16_THIS wavemode & 2) {
if ((DSP.outputinit & 2) == 0) {
base = (bx_list_c*) SIM->get_param(BXPN_SOUND_SB16);
bx_param_string_c *waveparam = SIM->get_param_string("wavefile", base);
if (BX_SB16_WAVEOUT2->openwaveoutput(waveparam->getptr()) == BX_SOUNDLOW_OK)
DSP.outputinit |= 2;
else
DSP.outputinit &= ~2;
if (((DSP.outputinit & BX_SB16_THIS wavemode) & 2) == 0) {
writelog(WAVELOG(2), "Error opening file %s. Wave file output disabled.",
waveparam->getptr());
BX_SB16_THIS wavemode = DSP.outputinit;
}
}
}
DSP.dma.chunkcount = sampledatarate / 10; // 0.1 sec
if (DSP.dma.chunkcount > BX_SOUNDLOW_WAVEPACKETSIZE) {
DSP.dma.chunkcount = BX_SOUNDLOW_WAVEPACKETSIZE;
}
} else {
if (DSP.inputinit == 0) {
ret = BX_SB16_WAVEIN->openwaveinput(SIM->get_param_string(BXPN_SOUND_WAVEIN)->getptr(), sb16_adc_handler);
if (ret != BX_SOUNDLOW_OK) {
writelog(WAVELOG(2), "Error: Could not open wave input device.");
} else {
DSP.inputinit = 1;
}
}
if (DSP.inputinit == 1) {
ret = BX_SB16_WAVEIN->startwaverecord(&DSP.dma.param);
if (ret != BX_SOUNDLOW_OK) {
writelog(WAVELOG(2), "Error: Could not start wave record.");
}
}
DSP.dma.chunkcount = 0;
}
dsp_enabledma();
}
Bit32u bx_sb16_c::sb16_adc_handler(void *this_ptr, Bit32u buflen)
{
bx_sb16_c *class_ptr = (bx_sb16_c*)this_ptr;
class_ptr->dsp_adc_handler(buflen);
return 0;
}
Bit32u bx_sb16_c::dsp_adc_handler(Bit32u buflen)
{
Bit32u len;
len = DSP.dma.chunkcount - DSP.dma.chunkindex;
if (len > 0) {
memmove(DSP.dma.chunk, DSP.dma.chunk+DSP.dma.chunkindex, len);
DSP.dma.chunkcount = len;
}
DSP.dma.chunkindex = 0;
if ((DSP.dma.chunkcount + buflen) > BX_SOUNDLOW_WAVEPACKETSIZE) {
DSP.dma.chunkcount = BX_SOUNDLOW_WAVEPACKETSIZE;
len = DSP.dma.chunkcount + buflen - BX_SOUNDLOW_WAVEPACKETSIZE;
BX_DEBUG(("dsp_adc_handler(): unhandled len=%d", len));
} else {
DSP.dma.chunkcount += buflen;
len = 0;
}
BX_SB16_WAVEIN->getwavepacket(DSP.dma.chunkcount, DSP.dma.chunk);
return len;
}
// dsp_enabledma(): Start the DMA timer and thus the transfer
void bx_sb16_c::dsp_enabledma()
{
bx_pc_system.activate_timer(DSP.timer_handle, DSP.dma.timer, 1);
}
// dsp_disabledma(): Stop the DMA timer and thus the transfer, but don't abort it
void bx_sb16_c::dsp_disabledma()
{
bx_pc_system.deactivate_timer(DSP.timer_handle);
}
// dsp_bufferstatus() checks if the DSP is ready for data/commands
Bit32u bx_sb16_c::dsp_bufferstatus()
{
Bit32u result = 0x7f;
// MSB set -> not ready for commands
if (DSP.datain.full() == 1) result |= 0x80;
writelog(WAVELOG(4), "DSP Buffer status read, result %x", result);
return(result);
}
// dsp_status() checks if the DSP is ready to send data
Bit32u bx_sb16_c::dsp_status()
{
Bit32u result = 0x7f;
// read might be to acknowledge IRQ
if (DSP.irqpending != 0)
{
MIXER.reg[0x82] &= (~0x01);
writelog(WAVELOG(4), "8-bit DMA or SBMIDI IRQ acknowledged");
if ((MIXER.reg[0x82] & 0x07) == 0) {
DSP.irqpending = 0;
DEV_pic_lower_irq(BX_SB16_IRQ);
}
}
// if buffer is not empty, there is data to be read
if (DSP.dataout.empty() == 0) result |= 0x80;
writelog(WAVELOG(4), "DSP output status read, result %x", result);
return(result);
}
// dsp_irq16ack() notifies that the 16bit DMA IRQ has been acknowledged
Bit32u bx_sb16_c::dsp_irq16ack()
{
Bit32u result = 0xff;
if (DSP.irqpending != 0)
{
MIXER.reg[0x82] &= (~0x02);
if ((MIXER.reg[0x82] & 0x07) == 0) {
DSP.irqpending = 0;
DEV_pic_lower_irq(BX_SB16_IRQ);
}
writelog(WAVELOG(4), "16-bit DMA IRQ acknowledged");
}
else
writelog(WAVELOG(3), "16-bit DMA IRQ acknowledged but not active!");
return result;
}
// the DMA handlers
// highlevel input and output handlers - rerouting to/from file,device
// write a wave packet to the output device
void bx_sb16_c::dsp_sendwavepacket()
{
if (DSP.dma.chunkindex == 0)
return;
if (BX_SB16_THIS wavemode & 1) {
BX_SB16_WAVEOUT1->sendwavepacket(DSP.dma.chunkindex, DSP.dma.chunk, &DSP.dma.param);
}
if (BX_SB16_THIS wavemode & 2) {
BX_SB16_WAVEOUT2->sendwavepacket(DSP.dma.chunkindex, DSP.dma.chunk, &DSP.dma.param);
}
DSP.dma.chunkindex = 0;
}
// put a sample byte into the output buffer
void bx_sb16_c::dsp_getsamplebyte(Bit8u value)
{
if (DSP.dma.chunkindex < DSP.dma.chunkcount)
DSP.dma.chunk[DSP.dma.chunkindex++] = value;
if (DSP.dma.chunkindex >= DSP.dma.chunkcount)
dsp_sendwavepacket();
}
// read a sample byte from the input buffer
Bit8u bx_sb16_c::dsp_putsamplebyte()
{
Bit8u value = DSP.dma.chunk[DSP.dma.chunkindex++];
if (DSP.dma.chunkindex >= DSP.dma.chunkcount) {
DSP.dma.chunkcount = 0;
DSP.dma.chunkindex = 0;
}
return value;
}
// called when the last byte of a DMA transfer has been received/sent
void bx_sb16_c::dsp_dmadone()
{
writelog(WAVELOG(4), "DMA transfer done, triggering IRQ");
if ((DSP.dma.output == 1) && (DSP.dma.mode != 2)) {
dsp_sendwavepacket(); // flush the output
} else if ((DSP.dma.output == 0) && (DSP.dma.mode != 2)) {
BX_SB16_WAVEIN->stopwaverecord();
}
// generate the appropriate IRQ
if (DSP.dma.param.bits == 8)
MIXER.reg[0x82] |= 1;
else
MIXER.reg[0x82] |= 2;
DEV_pic_raise_irq(BX_SB16_IRQ);
DSP.irqpending = 1;
// if auto-DMA, reinitialize
if (DSP.dma.mode == 2)
{
if (DSP.dma.param.bits == 8 || (DSP.dma.param.bits == 16 && BX_SB16_DMAH != 0)) {
DSP.dma.count = DSP.dma.blocklength;
} else {
DSP.dma.count = ((DSP.dma.blocklength + 1) << 1) - 1;
}
writelog(WAVELOG(4), "auto-DMA reinitializing to length %d", DSP.dma.count);
}
else
{
DSP.dma.mode = 0;
dsp_disabledma();
}
}
// now the actual transfer routines, called by the DMA controller
// note that read = from application to soundcard (output),
// and write = from soundcard to application (input)
Bit16u bx_sb16_c::dma_read8(Bit8u *buffer, Bit16u maxlen)
{
Bit16u len = 0;
DEV_dma_set_drq(BX_SB16_DMAL, 0); // the timer will raise it again
writelog(WAVELOG(5), "Received 8-bit DMA: 0x%02x, %d remaining ",
buffer[0], DSP.dma.count);
do {
dsp_getsamplebyte(buffer[len++]);
DSP.dma.count--;
} while ((len < maxlen) && (DSP.dma.count != 0xffff));
if (DSP.dma.count == 0xffff) // last byte received
dsp_dmadone();
return len;
}
Bit16u bx_sb16_c::dma_write8(Bit8u *buffer, Bit16u maxlen)
{
Bit16u len = 0;
DEV_dma_set_drq(BX_SB16_DMAL, 0); // the timer will raise it again
do {
buffer[len++] = dsp_putsamplebyte();
DSP.dma.count--;
} while ((len < maxlen) && (DSP.dma.count != 0xffff));
writelog(WAVELOG(5), "Sent 8-bit DMA: 0x%02x, %d remaining ",
buffer[0], DSP.dma.count);
if (DSP.dma.count == 0xffff) // last byte sent
dsp_dmadone();
return len;
}
Bit16u bx_sb16_c::dma_read16(Bit16u *buffer, Bit16u maxlen)
{
Bit16u len = 0;
Bit8u *buf8;
DEV_dma_set_drq(BX_SB16_DMAH, 0); // the timer will raise it again
writelog(WAVELOG(5), "Received 16-bit DMA: 0x%04x, %d remaining ",
buffer[0], DSP.dma.count);
do {
buf8 = (Bit8u*)(buffer+len);
dsp_getsamplebyte(buf8[0]);
dsp_getsamplebyte(buf8[1]);
len++;
DSP.dma.count--;
} while ((len < maxlen) && (DSP.dma.count != 0xffff));
if (DSP.dma.count == 0xffff) // last word received
dsp_dmadone();
return len;
}
Bit16u bx_sb16_c::dma_write16(Bit16u *buffer, Bit16u maxlen)
{
Bit16u len = 0;
Bit8u *buf8;
DEV_dma_set_drq(BX_SB16_DMAH, 0); // the timer will raise it again
do {
buf8 = (Bit8u*)(buffer+len);
buf8[0] = dsp_putsamplebyte();
buf8[1] = dsp_putsamplebyte();
len++;
DSP.dma.count--;
} while ((len < maxlen) && (DSP.dma.count != 0xffff));
writelog(WAVELOG(5), "Sent 16-bit DMA: 0x%4x, %d remaining ",
buffer[0], DSP.dma.count);
if (DSP.dma.count == 0xffff) // last word sent
dsp_dmadone();
return len;
}
Bit16u bx_sb16_c::calc_output_volume(Bit8u reg1, Bit8u reg2, bx_bool shift)
{
Bit8u vol1, vol2;
float fvol1, fvol2;
Bit16u result;
vol1 = (MIXER.reg[reg1] >> 3);
vol2 = (MIXER.reg[reg2] >> 3);
fvol1 = pow(10.0f, (float)(31-vol1)*-0.065f);
fvol2 = pow(10.0f, (float)(31-vol2)*-0.065f);
result = (Bit8u)(255 * fvol1 * fvol2);
if (shift) result <<= 8;
return result;
}
// the mixer, supported type is CT1745 (as in an SB16)
void bx_sb16_c::mixer_writedata(Bit32u value)
{
int i;
Bit8u set_output_vol = 0;
// do some action depending on what register was written
switch (MIXER.regindex)
{
case 0: // initialize mixer
writelog(BOTHLOG(4), "Initializing mixer...");
MIXER.reg[0x04] = 0xcc;
MIXER.reg[0x0a] = 0x00;
MIXER.reg[0x22] = 0xcc;
MIXER.reg[0x26] = 0xcc;
MIXER.reg[0x28] = 0x00;
MIXER.reg[0x2e] = 0x00;
MIXER.reg[0x3c] = 0x1f;
MIXER.reg[0x3d] = 0x15;
MIXER.reg[0x3e] = 0x0b;
for (i=0x30; i<=0x35; i++)
MIXER.reg[i] = 0xc0;
for (i=0x36; i<=0x3b; i++)
MIXER.reg[i] = 0x00;
for (i=0x3f; i<=0x43; i++)
MIXER.reg[i] = 0x00;
for (i=0x44; i<=0x47; i++)
MIXER.reg[i] = 0x80;
MIXER.regindex = 0; // next mixer register read is register 0
set_output_vol = 3;
break;
case 0x04: // DAC level
MIXER.reg[0x32] = (value & 0xf0) | 0x08;
MIXER.reg[0x33] = ((value & 0x0f) << 4) | 0x08;
set_output_vol = 1;
break;
case 0x0a: // microphone level
MIXER.reg[0x3a] = (value << 5) | 0x18;
break;
case 0x22: // master volume
MIXER.reg[0x30] = (value & 0xf0) | 0x08;
MIXER.reg[0x31] = ((value & 0x0f) << 4) | 0x08;
set_output_vol = 3;
break;
case 0x26: // FM level
MIXER.reg[0x34] = (value & 0xf0) | 0x08;
MIXER.reg[0x35] = ((value & 0x0f) << 4) | 0x08;
set_output_vol = 2;
break;
case 0x28: // CD audio level
MIXER.reg[0x36] = (value & 0xf0) | 0x08;
MIXER.reg[0x37] = ((value & 0x0f) << 4) | 0x08;
break;
case 0x2e: // line in level
MIXER.reg[0x38] = (value & 0xf0) | 0x08;
MIXER.reg[0x39] = ((value & 0x0f) << 4) | 0x08;
break;
case 0x30: // master volume left
MIXER.reg[0x22] &= 0x0f;
MIXER.reg[0x22] |= (value & 0xf0);
set_output_vol = 3;
break;
case 0x31: // master volume right
MIXER.reg[0x22] &= 0xf0;
MIXER.reg[0x22] |= (value >> 4);
set_output_vol = 3;
break;
case 0x32: // DAC level left
MIXER.reg[0x04] &= 0x0f;
MIXER.reg[0x04] |= (value & 0xf0);
set_output_vol = 1;
break;
case 0x33: // DAC level right
MIXER.reg[0x04] &= 0xf0;
MIXER.reg[0x04] |= (value >> 4);
set_output_vol = 1;
break;
case 0x34: // FM level left
MIXER.reg[0x26] &= 0x0f;
MIXER.reg[0x26] |= (value & 0xf0);
set_output_vol = 2;
break;
case 0x35: // FM level right
MIXER.reg[0x26] &= 0xf0;
MIXER.reg[0x26] |= (value >> 4);
set_output_vol = 2;
break;
case 0x36: // CD audio level left
MIXER.reg[0x28] &= 0x0f;
MIXER.reg[0x28] |= (value & 0xf0);
break;
case 0x37: // CD audio level right
MIXER.reg[0x28] &= 0xf0;
MIXER.reg[0x28] |= (value >> 4);
break;
case 0x38: // line in level left
MIXER.reg[0x2e] &= 0x0f;
MIXER.reg[0x2e] |= (value & 0xf0);
break;
case 0x39: // line in level right
MIXER.reg[0x2e] &= 0xf0;
MIXER.reg[0x2e] |= (value >> 4);
break;
case 0x3a: // microphone level
MIXER.reg[0x0a] = (value >> 5);
break;
case 0x3b:
case 0x3c:
case 0x3d:
case 0x3e:
case 0x3f:
case 0x40:
case 0x41:
case 0x42:
case 0x43:
case 0x44:
case 0x45:
case 0x46:
case 0x47:
break;
case 0x80: // IRQ mask
case 0x81: // DMA mask
MIXER.reg[MIXER.regindex] = value;
set_irq_dma(); // both 0x80 and 0x81 handled
return;
default: // ignore read-only registers
return;
}
// store the value
if (MIXER.regindex != 0) {
MIXER.reg[MIXER.regindex] = value;
}
if (set_output_vol & 1) {
DSP.dma.param.volume = calc_output_volume(0x30, 0x32, 0);
DSP.dma.param.volume |= calc_output_volume(0x31, 0x33, 1);
}
if (set_output_vol & 2) {
BX_SB16_THIS fm_volume = calc_output_volume(0x30, 0x34, 0);
BX_SB16_THIS fm_volume |= calc_output_volume(0x31, 0x35, 1);
}
writelog(BOTHLOG(4), "mixer register %02x set to %02x",
MIXER.regindex, MIXER.reg[MIXER.regindex]);
}
Bit32u bx_sb16_c::mixer_readdata()
{
writelog(BOTHLOG(4), "read from mixer register %02x returns %02x",
MIXER.regindex, MIXER.reg[MIXER.regindex]);
return(MIXER.reg[MIXER.regindex]);
}
void bx_sb16_c::mixer_writeregister(Bit32u value)
{
MIXER.regindex = value;
}
void bx_sb16_c::set_irq_dma()
{
static bx_bool isInitialized=0;
int newirq;
int oldDMA8, oldDMA16;
// set the IRQ according to the value in mixer register 0x80
switch (MIXER.reg[0x80])
{
case 1:
newirq = 2;
break;
case 2:
newirq = 5;
break;
case 4:
newirq = 7;
break;
case 8:
newirq = 10;
break;
default:
newirq = 5;
writelog(BOTHLOG(3), "Bad value %02x in mixer register 0x80. IRQ set to %d",
MIXER.reg[0x80], newirq);
MIXER.reg[0x80] = 2;
}
if (newirq != BX_SB16_IRQ) // a different IRQ was set
{
if (BX_SB16_IRQ > 0)
DEV_unregister_irq(BX_SB16_IRQ, "SB16");
BX_SB16_IRQ = newirq;
DEV_register_irq(BX_SB16_IRQ, "SB16");
}
// set the 8 bit DMA
oldDMA8=BX_SB16_DMAL;
switch (MIXER.reg[0x81] & 0x0f)
{
case 1:
BX_SB16_DMAL = 0;
break;
case 2:
BX_SB16_DMAL = 1;
break;
case 8:
BX_SB16_DMAL = 3;
break;
default:
BX_SB16_DMAL = 1;
writelog(BOTHLOG(3), "Bad value %02x in mixer register 0x81. DMA8 set to %d",
MIXER.reg[0x81], BX_SB16_DMAL);
MIXER.reg[0x81] &= (~0x0f);
MIXER.reg[0x81] |= (1 << BX_SB16_DMAL);
}
// Unregister the previous DMA if initialized
if ((isInitialized) && (oldDMA8 != BX_SB16_DMAL))
DEV_dma_unregister_channel(oldDMA8);
// And register the new 8bits DMA Channel
if ((!isInitialized) || (oldDMA8 != BX_SB16_DMAL))
DEV_dma_register_8bit_channel(BX_SB16_DMAL, dma_read8, dma_write8, "SB16");
// and the 16 bit DMA
oldDMA16=BX_SB16_DMAH;
switch (MIXER.reg[0x81] >> 4)
{
case 0:
BX_SB16_DMAH = 0; // no 16-bit DMA
break;
case 2:
BX_SB16_DMAH = 5;
break;
case 4:
BX_SB16_DMAH = 6;
break;
case 8:
BX_SB16_DMAH = 7;
break;
default:
BX_SB16_DMAH = 0;
writelog(BOTHLOG(3), "Bad value %02x in mixer register 0x81. DMA16 set to %d",
MIXER.reg[0x81], BX_SB16_DMAH);
MIXER.reg[0x81] &= (~0xf0);
// MIXER.reg[0x81] |= (1 << BX_SB16_DMAH);
// no default 16 bit channel!
}
// Unregister the previous DMA if initialized
if ((isInitialized) && (oldDMA16 != 0) && (oldDMA16 != BX_SB16_DMAH))
DEV_dma_unregister_channel(oldDMA16);
// And register the new 16bits DMA Channel
if ((BX_SB16_DMAH != 0) && (oldDMA16 != BX_SB16_DMAH))
DEV_dma_register_16bit_channel(BX_SB16_DMAH, dma_read16, dma_write16, "SB16");
// If not already initialized
if(!isInitialized) {
isInitialized=1;
} else {
writelog(BOTHLOG(1), "Resources set to I%d D%d H%d",
BX_SB16_IRQ, BX_SB16_DMAL, BX_SB16_DMAH);
}
}
// now the MPU 401 part
// the MPU 401 status port shows if input or output are ready
// Note that the bits are inverse to their meaning
Bit32u bx_sb16_c::mpu_status()
{
Bit32u result = 0;
if ((MPU.datain.full() == 1) ||
((BX_SB16_THIS midimode & 1) &&
(BX_SB16_MIDIOUT1->midiready() == BX_SOUNDLOW_ERR)))
result |= 0x40; // output not ready
if (MPU.dataout.empty() == 1)
result |= 0x80; // no input available
writelog(MIDILOG(4), "MPU status port, result %02x", result);
return(result);
}
// the MPU 401 command port
void bx_sb16_c::mpu_command(Bit32u value)
{
int i;
int bytesneeded;
if (MPU.cmd.hascommand() == 1) // already a command pending, abort that one
{
if ((MPU.cmd.currentcommand() != value) ||
(MPU.cmd.commanddone() == 0))
// it's a different command, or the old one isn't done yet, abort it
{
MPU.cmd.clearcommand();
MPU.cmd.flush();
}
// if it's the same one, and we just completed the argument list,
// we leave it as it is and process it here
}
if (MPU.cmd.hascommand() == 0) // no command pending, set one up
{
bytesneeded = 0;
if ((value >> 4) == 14) bytesneeded = 1;
MPU.cmd.newcommand(value, bytesneeded);
}
if (MPU.cmd.commanddone() == 1) // command is complete, process it
{
switch (MPU.cmd.currentcommand())
{
case 0x3f:
writelog(MIDILOG(5), "MPU cmd: UART mode on");
MPU.uartmode=1;
MPU.irqpending=1;
MPU.singlecommand=0;
if (BX_SB16_IRQMPU != -1) {
MIXER.reg[0x82] |= 4;
DEV_pic_raise_irq(BX_SB16_IRQMPU);
}
break;
case 0xff:
writelog(MIDILOG(4), "MPU cmd: Master reset of device");
MPU.uartmode=MPU.forceuartmode;
MPU.singlecommand=0;
for (i=0; i<16; i++)
{
MPU.banklsb[i] = 0;
MPU.bankmsb[i] = 0;
MPU.program[i] = 0;
}
MPU.cmd.reset();
MPU.dataout.reset();
MPU.datain.reset();
MPU.midicmd.reset();
break;
case 0xd0: // d0 and df: prefix for midi command
case 0xdf: // like uart mode, but only a single command
MPU.singlecommand = 1;
writelog(MIDILOG(4), "MPU: prefix %02x received",
MPU.cmd.currentcommand());
break;
default:
writelog(MIDILOG(3), "MPU cmd: unknown command %02x ignored",
MPU.cmd.currentcommand());
break;
}
// Need to put an MPU_ACK into the data port if command successful
// we'll fake it even if we didn't process the command, so as to
// allow detection of the MPU 401.
if (MPU.dataout.put(0xfe) == 0)
writelog(MIDILOG(3), "MPU_ACK error - output buffer full");
MPU.cmd.clearcommand(); // clear the command from the buffer
}
}
// MPU 401 data port/read: contains an MPU_ACK after receiving a command
// Will contain other data as well when other than UART mode is supported
Bit32u bx_sb16_c::mpu_dataread()
{
Bit8u res8bit;
Bit32u result;
// also acknowledge IRQ?
if (MPU.irqpending != 0)
{
MPU.irqpending = 0;
MIXER.reg[0x82] &= (~4);
if ((MIXER.reg[0x82] & 0x07) == 0)
DEV_pic_lower_irq(BX_SB16_IRQMPU);
writelog(MIDILOG(4), "MPU IRQ acknowledged");
}
if (MPU.dataout.get(&res8bit) == 0) {
writelog(MIDILOG(3), "MPU data port not ready - no data in buffer");
result = 0xff;
}
else
result = (Bit32u) res8bit;
writelog(MIDILOG(4), "MPU data port, result %02x", result);
return(result);
}
// MPU 401 data port/write: This is where the midi stream comes from,
// as well as arguments to any pending command
void bx_sb16_c::mpu_datawrite(Bit32u value)
{
writelog(MIDILOG(4), "write to MPU data port, value %02x", value);
if (MPU.cmd.hascommand() == 1)
{ // there is a command pending, add arguments to it
if (MPU.cmd.put(value) == 0)
writelog(MIDILOG(3), "MPU Command arguments too long - buffer full");
if (MPU.cmd.commanddone() == 1)
BX_SB16_THIS mpu_command(MPU.cmd.currentcommand());
}
else if ((MPU.uartmode == 0) && (MPU.singlecommand == 0))
{
// Hm? No UART mode, but still data? Maybe should send it
// to the command port... Only SBMPU401.EXE does this...
writelog(MIDILOG(4), "MPU Data %02x received but no UART mode. Assuming it's a command.", value);
mpu_command(value);
return;
}
else // no MPU command pending, in UART mode, this has to be midi data
mpu_mididata(value);
}
// A byte of midi data has been received
void bx_sb16_c::mpu_mididata(Bit32u value)
{
// first, find out if it is a midi command or midi data
bx_bool ismidicommand = 0;
if (value >= 0x80)
{ // bit 8 usually denotes a midi command...
ismidicommand = 1;
if ((value == 0xf7) && (MPU.midicmd.currentcommand() == 0xf0))
// ...except if it is a continuing SYSEX message, then it just
// denotes the end of a SYSEX chunk, not the start of a message
{
ismidicommand = 0; // first, it's not a command
MPU.midicmd.newcommand(MPU.midicmd.currentcommand(),
MPU.midicmd.bytes());
// Then, set needed bytes to current buffer
// because we didn't know the length before
}
}
if (ismidicommand == 1)
{ // this is a command, check if an old one is pending
if (MPU.midicmd.hascommand() == 1)
{
writelog(MIDILOG(3), "Midi command %02x incomplete, has %d of %d bytes.",
MPU.midicmd.currentcommand(), MPU.midicmd.bytes(),
MPU.midicmd.commandbytes());
// write as much as we can. Should we do this?
processmidicommand(0);
// clear the pending command
MPU.midicmd.clearcommand();
MPU.midicmd.flush();
}
// find the number of arguments to the command
static const signed eventlength[] = { 2, 2, 2, 2, 1, 1, 2, 255};
// note - length 255 commands have unknown length
MPU.midicmd.newcommand(value, eventlength[(value & 0x70) >> 4]);
}
else // no command, just arguments to the old command
{
if (MPU.midicmd.hascommand() == 0)
{ // no command pending, ignore the data
writelog(MIDILOG(3), "Midi data %02x received, but no command pending?", value);
return;
}
// just some data to the command
if (MPU.midicmd.put(value) == 0)
writelog(MIDILOG(3), "Midi buffer overflow!");
if (MPU.midicmd.commanddone() == 1)
{
// the command is complete, process it
writelog(MIDILOG(5), "Midi command %02x complete, has %d bytes.",
MPU.midicmd.currentcommand(), MPU.midicmd.bytes());
processmidicommand(0);
// and remove the command from the buffer
MPU.midicmd.clearcommand();
MPU.midicmd.flush();
}
}
}
// The emulator port/read: See if commands were successful
Bit32u bx_sb16_c::emul_read()
{
Bit8u res8bit;
Bit32u result;
if (EMUL.datain.get(&res8bit) == 0)
{
writelog(3, "emulator port not ready - no data in buffer");
result = 0x00;
}
else result = (Bit32u) res8bit;
writelog(4, "emulator port, result %02x", result);
return(result);
}
// Emulator port/write: Changing instrument mapping etc.
void bx_sb16_c::emul_write(Bit32u value)
{
Bit8u value8 = 0;
writelog(4, "write to emulator port, value %02x", value);
if (EMUL.dataout.hascommand() == 0) // no command pending, set it up
{
static signed char cmdlength[] = { 0, 0, 4, 2, 6, 1, 0, 0, 1, 1, 0, 1};
if (value > 11)
{
writelog(3, "emulator command %02x unknown, ignored.", value);
return;
}
writelog(5, "emulator command %02x, needs %d arguments",
value, cmdlength[value]);
EMUL.dataout.newcommand(value, cmdlength[value]);
EMUL.datain.reset();
EMUL.datain.put(0xfe);
}
else
EMUL.dataout.put(value); // otherwise just add data
if (EMUL.dataout.commanddone() == 1)
{ // process the command
writelog(4, "executing emulator command %02x with %d arguments",
EMUL.dataout.currentcommand(), EMUL.dataout.bytes());
switch (EMUL.dataout.currentcommand())
{
case 0: // reinit of emulator
writelog(4, "Emulator reinitialized");
EMUL.remaps = 0;
EMUL.dataout.reset();
EMUL.datain.reset();
EMUL.datain.put(0xfe);
break;
case 1: // dummy command to reset state of emulator port
// just give a few times to end any commands
break;
case 2: // map bank
if (EMUL.remaps >= BX_SB16_MAX_REMAPS) break;
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbankmsb));
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbanklsb));
EMUL.remaplist[EMUL.remaps].oldprogch = 0xff;
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbankmsb));
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbanklsb));
EMUL.remaplist[EMUL.remaps].newprogch = 0xff;
EMUL.datain.put(4);
writelog(4, "Map bank command received, from %d %d to %d %d",
EMUL.remaplist[EMUL.remaps].oldbankmsb,
EMUL.remaplist[EMUL.remaps].oldbanklsb,
EMUL.remaplist[EMUL.remaps].newbankmsb,
EMUL.remaplist[EMUL.remaps].newbanklsb);
EMUL.remaps++;
break;
case 3: // map program change
if (EMUL.remaps >= BX_SB16_MAX_REMAPS) break;
EMUL.remaplist[EMUL.remaps].oldbankmsb = 0xff;
EMUL.remaplist[EMUL.remaps].oldbanklsb = 0xff;
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldprogch));
EMUL.remaplist[EMUL.remaps].newbankmsb = 0xff;
EMUL.remaplist[EMUL.remaps].newbanklsb = 0xff;
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newprogch));
EMUL.datain.put(2);
writelog(4, "Map program change received, from %d to %d",
EMUL.remaplist[EMUL.remaps].oldprogch,
EMUL.remaplist[EMUL.remaps].newprogch);
EMUL.remaps++;
break;
case 4: // map bank and program change
if (EMUL.remaps >= BX_SB16_MAX_REMAPS) break;
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbankmsb));
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbanklsb));
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldprogch));
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbankmsb));
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbanklsb));
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newprogch));
EMUL.datain.put(6);
writelog(4, "Complete remap received, from %d %d %d to %d %d %d",
EMUL.remaplist[EMUL.remaps].oldbankmsb,
EMUL.remaplist[EMUL.remaps].oldbanklsb,
EMUL.remaplist[EMUL.remaps].oldprogch,
EMUL.remaplist[EMUL.remaps].newbankmsb,
EMUL.remaplist[EMUL.remaps].newbanklsb,
EMUL.remaplist[EMUL.remaps].newprogch);
EMUL.remaps++;
break;
case 5: EMUL.dataout.get(&value8); // dump emulator state
switch (value8)
{
case 0:
EMUL.datain.puts("SB16 Emulator for Bochs\n");
break;
case 1:
EMUL.datain.puts("UART mode=%d (force=%d)\n",
MPU.uartmode, MPU.forceuartmode);
break;
case 2:
EMUL.datain.puts("timer=%d\n", MPU.current_timer);
break;
case 3:
EMUL.datain.puts("%d remappings active\n", EMUL.remaps);
break;
case 4:
EMUL.datain.puts("Resources are A%3x I%d D%d H%d T%d P%3x; Adlib at %3x\n",
BX_SB16_IO, BX_SB16_IRQ, BX_SB16_DMAL,
BX_SB16_DMAH, 6, BX_SB16_IOMPU, BX_SB16_IOADLIB);
break;
default:
EMUL.datain.puts("no info. Only slots 0..4 have values.\n");
break;
}
break;
case 6: // close midi and wave files and/or output
BX_SB16_THIS closemidioutput();
BX_SB16_THIS midimode = 0;
BX_SB16_THIS closewaveoutput();
BX_SB16_THIS wavemode = 0;
break;
case 7: // clear bank/program mappings
EMUL.remaps = 0;
writelog(4, "Bank/program mappings cleared.");
break;
case 8: // set force uart mode on/off
EMUL.dataout.get(&value8);
MPU.forceuartmode = value8;
if (value8 != 0)
MPU.uartmode = MPU.forceuartmode;
writelog(4, "Force UART mode = %d", MPU.forceuartmode);
break;
case 9: // enter specific OPL2/3 mode
// this feature has been removed
break;
case 10: // check emulator present
EMUL.datain.put(0x55);
break;
case 11: // send data to midi device
EMUL.dataout.get(&value8);
mpu_mididata(value8);
}
EMUL.dataout.clearcommand();
EMUL.dataout.flush();
}
}
// and finally the OPL (FM emulation) part
// this is called whenever one of the timer elapses
void bx_sb16_c::opl_timerevent()
{
Bit16u mask;
for (int i=0; i<4; i++) {
if ((OPL.tmask[i/2] & (1 << (i % 2))) != 0) { // only running timers
if ((i % 2) == 0) {
mask = 0xff;
} else {
mask = 0x3ff;
}
if (((++OPL.timer[i]) & mask) == 0) { // overflow occured, set flags accordingly
OPL.timer[i] = OPL.timerinit[i]; // reset the counter
if ((OPL.tmask[i/2] >> (6 - (i % 2))) == 0) { // set flags only if unmasked
writelog(MIDILOG(5), "OPL Timer Interrupt: Chip %d, Timer %d", i/2, 1 << (i % 2));
OPL.tflag[i/2] |= 1 << (6 - (i % 2)); // set the overflow flag
OPL.tflag[i/2] |= 1 << 7; // set the IRQ flag
}
}
}
}
}
// return the status of one of the OPL2's, or the
// base status of the OPL3
Bit32u bx_sb16_c::opl_status(int chipid)
{
Bit32u status = OPL.tflag[chipid];
writelog(MIDILOG(5), "OPL status of chip %d is %02x", chipid, status);
return status;
}
// write to the data port
void bx_sb16_c::opl_data(Bit32u value, int chipid)
{
int index = OPL.index[chipid];
writelog(MIDILOG(4), "Write to OPL(%d) register %02x: %02x",
chipid, index, value);
switch (index & 0xff) {
// the two timer counts
case 0x02:
OPL.timerinit[chipid * 2] = OPL.timer[chipid * 2] = value;
break;
case 0x03:
OPL.timerinit[chipid * 2 + 1] = OPL.timer[chipid * 2 + 1] = (value << 2);
break;
// the timer masks
case 0x04:
if (chipid == 0) {
opl_settimermask(value, chipid);
}
break;
}
}
// called for a write to port 4 of either chip
void bx_sb16_c::opl_settimermask(int value, int chipid)
{
if ((value & 0x80) != 0) { // reset IRQ and timer flags
// all other bits ignored!
writelog(MIDILOG(5), "IRQ Reset called");
OPL.tflag[chipid] = 0;
return;
}
OPL.tmask[chipid] = value & 0x63;
writelog(MIDILOG(5), "New timer mask for chip %d is %02x",
chipid, OPL.tmask[chipid]);
// do we have to activate or deactivate the timer?
if (((value & 0x03) != 0) ^ (OPL.timer_running != 0)) {
if ((value & 0x03) != 0) { // yes, it's different. Start or stop?
writelog(MIDILOG(5), "Starting timers");
bx_pc_system.activate_timer(OPL.timer_handle, 80, 1);
OPL.timer_running = 1;
} else {
writelog(MIDILOG(5), "Stopping timers");
bx_pc_system.deactivate_timer(OPL.timer_handle);
OPL.timer_running = 0;
}
}
}
Bit32u bx_sb16_c::fmopl_generator(Bit16u rate, Bit8u *buffer, Bit32u len)
{
bx_bool ret = adlib_getsample(rate, (Bit16s*)buffer, len / 4, BX_SB16_THIS fm_volume);
return ret ? len : 0;
}
Bit32u fmopl_callback(void *dev, Bit16u rate, Bit8u *buffer, Bit32u len)
{
return ((bx_sb16_c*)dev)->fmopl_generator(rate, buffer, len);
}
/* Handlers for the midi commands/midi file output */
// write the midi command to the midi file
void bx_sb16_c::writemidicommand(int command, int length, Bit8u data[])
{
bx_param_string_c *midiparam;
/* We need to determine the time elapsed since the last MIDI command */
int deltatime = currentdeltatime();
/* Initialize output device/file if necessary and not done yet */
if (BX_SB16_THIS midimode > 0) {
if ((MPU.outputinit & BX_SB16_THIS midimode) != BX_SB16_THIS midimode) {
writelog(MIDILOG(4), "Initializing Midi output.");
if (BX_SB16_THIS midimode & 1) {
midiparam = SIM->get_param_string(BXPN_SOUND_MIDIOUT);
if (BX_SB16_MIDIOUT1->openmidioutput(midiparam->getptr()) == BX_SOUNDLOW_OK)
MPU.outputinit |= 1;
else
MPU.outputinit &= ~1;
}
if (BX_SB16_THIS midimode & 2) {
bx_list_c *base = (bx_list_c*) SIM->get_param(BXPN_SOUND_SB16);
midiparam = SIM->get_param_string("midifile", base);
if (BX_SB16_MIDIOUT2->openmidioutput(midiparam->getptr()) == BX_SOUNDLOW_OK)
MPU.outputinit |= 2;
else
MPU.outputinit &= ~2;
}
if ((MPU.outputinit & BX_SB16_THIS midimode) != BX_SB16_THIS midimode) {
writelog(MIDILOG(2), "Error: Couldn't open midi output. Midi disabled.");
BX_SB16_THIS midimode = MPU.outputinit;
return;
}
}
if (BX_SB16_THIS midimode & 1) {
BX_SB16_MIDIOUT1->sendmidicommand(deltatime, command, length, data);
}
if (BX_SB16_THIS midimode & 2) {
BX_SB16_MIDIOUT2->sendmidicommand(deltatime, command, length, data);
}
}
}
// determine how many delta times have passed since
// this function was called last
int bx_sb16_c::currentdeltatime()
{
int deltatime;
// counting starts at first access
if (MPU.last_delta_time == 0xffffffff)
MPU.last_delta_time = MPU.current_timer;
deltatime = MPU.current_timer - MPU.last_delta_time;
MPU.last_delta_time = MPU.current_timer;
return deltatime;
}
// process the midi command stored in MPU.midicmd.to the midi driver
void bx_sb16_c::processmidicommand(bx_bool force)
{
int i, channel;
Bit8u value;
bx_bool needremap = 0;
channel = MPU.midicmd.currentcommand() & 0xf;
// we need to log bank changes and program changes
if ((MPU.midicmd.currentcommand() >> 4) == 0xc)
{ // a program change
value = MPU.midicmd.peek(0);
writelog(MIDILOG(1), "* ProgramChange channel %d to %d",
channel, value);
MPU.program[channel] = value;
needremap = 1;
}
else if ((MPU.midicmd.currentcommand() >> 4) == 0xb)
{ // a control change, could be a bank change
if (MPU.midicmd.peek(0) == 0)
{ // bank select MSB
value = MPU.midicmd.peek(1);
writelog(MIDILOG(1), "* BankSelectMSB (%x %x %x) channel %d to %d",
MPU.midicmd.peek(0), MPU.midicmd.peek(1), MPU.midicmd.peek(2),
channel, value);
MPU.bankmsb[channel] = value;
needremap = 1;
}
else if (MPU.midicmd.peek(0) == 32)
{ // bank select LSB
value = MPU.midicmd.peek(1);
writelog(MIDILOG(1), "* BankSelectLSB channel %d to %d",
channel, value);
MPU.banklsb[channel] = value;
needremap = 1;
}
}
Bit8u temparray[256];
i = 0;
while (MPU.midicmd.empty() == 0)
MPU.midicmd.get(&(temparray[i++]));
writemidicommand(MPU.midicmd.currentcommand(), i, temparray);
// if single command, revert to command mode
if (MPU.singlecommand != 0)
{
MPU.singlecommand = 0;
}
if ((force == 0) && (needremap == 1))
// have to check the remap lists, and remap program change if necessary
midiremapprogram(channel);
}
// check if a program change has to be remapped, and do it if necessary
void bx_sb16_c::midiremapprogram(int channel)
{
int bankmsb,banklsb,program;
Bit8u commandbytes[2];
bankmsb = MPU.bankmsb[channel];
banklsb = MPU.banklsb[channel];
program = MPU.program[channel];
for(int i = 0; i < EMUL.remaps; i++)
{
if (((EMUL.remaplist[i].oldbankmsb == bankmsb) ||
(EMUL.remaplist[i].oldbankmsb == 0xff)) &&
((EMUL.remaplist[i].oldbanklsb == banklsb) ||
(EMUL.remaplist[i].oldbanklsb == 0xff)) &&
((EMUL.remaplist[i].oldprogch == program) ||
(EMUL.remaplist[i].oldprogch == 0xff)))
{
writelog(5, "Remapping instrument for channel %d", channel);
if ((EMUL.remaplist[i].newbankmsb != bankmsb) &&
(EMUL.remaplist[i].newbankmsb != 0xff))
{ // write control change bank msb
MPU.bankmsb[channel] = EMUL.remaplist[i].newbankmsb;
commandbytes[0] = 0;
commandbytes[1] = EMUL.remaplist[i].newbankmsb;
writemidicommand(0xb0 | channel, 2, commandbytes);
}
if ((EMUL.remaplist[i].newbanklsb != banklsb) &&
(EMUL.remaplist[i].newbanklsb != 0xff))
{ // write control change bank lsb
MPU.banklsb[channel] = EMUL.remaplist[i].newbanklsb;
commandbytes[0] = 32;
commandbytes[1] = EMUL.remaplist[i].newbanklsb;
writemidicommand(0xb0 | channel, 2, commandbytes);
}
if ((EMUL.remaplist[i].newprogch != program) &&
(EMUL.remaplist[i].newprogch != 0xff))
{ // write program change
MPU.program[channel] = EMUL.remaplist[i].newprogch;
commandbytes[0] = EMUL.remaplist[i].newprogch;
writemidicommand(0xc0 | channel, 1, commandbytes);
}
}
}
}
void bx_sb16_c::closemidioutput()
{
if (BX_SB16_THIS midimode > 0) {
if (MPU.outputinit & 1) {
BX_SB16_MIDIOUT1->closemidioutput();
MPU.outputinit &= ~1;
}
if (MPU.outputinit & 2) {
BX_SB16_MIDIOUT2->closemidioutput();
MPU.outputinit &= ~2;
}
}
}
void bx_sb16_c::closewaveoutput()
{
if (BX_SB16_THIS wavemode > 0) {
if (DSP.outputinit & 2) {
BX_SB16_WAVEOUT2->closewaveoutput();
DSP.outputinit &= ~2;
}
}
}
// static IO port read callback handler
// redirects to non-static class handler to avoid virtual functions
Bit32u bx_sb16_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len)
{
#if !BX_USE_SB16_SMF
bx_sb16_c *class_ptr = (bx_sb16_c *) this_ptr;
return class_ptr->read(address, io_len);
}
Bit32u bx_sb16_c::read(Bit32u address, unsigned io_len)
{
#else
UNUSED(this_ptr);
#endif // !BX_USE_SB16_SMF
bx_pc_system.isa_bus_delay();
switch (address) {
// 2x0: FM Music Status Port
// 2x8 and 388 are aliases
case BX_SB16_IO + 0x00:
case BX_SB16_IO + 0x08:
case BX_SB16_IOADLIB + 0x00:
return opl_status(0);
// 2x1: reserved (w: FM Music Data Port)
// 2x9 and 389 are aliases
case BX_SB16_IO + 0x01:
case BX_SB16_IO + 0x09:
case BX_SB16_IOADLIB + 0x01:
break;
// 2x2: Advanced Music Status Port
// or (for SBPro1) FM Music Status Port 2
// 38a is an alias
case BX_SB16_IO + 0x02:
case BX_SB16_IOADLIB + 0x02:
return opl_status(1);
// 2x3: reserved (w: Adv. FM Music Data Port)
// or (for SBPro1) FM Music Data Port 2
// 38b is an alias
case BX_SB16_IO + 0x03:
case BX_SB16_IOADLIB + 0x03:
break;
// 2x4: reserved (w: Mixer Register Port)
case BX_SB16_IO + 0x04:
break;
// 2x5: Mixer Data Port
case BX_SB16_IO + 0x05:
return mixer_readdata();
// 2x6: reserved (w: DSP Reset)
case BX_SB16_IO + 0x06:
break;
// 2x7: reserved
case BX_SB16_IO + 0x07:
break;
// 2x8: FM Music Status Port (OPL-2)
// handled above
// 2x9: reserved (w: FM Music Data Port)
// handled above
// 2xa: DSP Read Data Port
case BX_SB16_IO + 0x0a:
return dsp_dataread();
// 2xb: reserved
case BX_SB16_IO + 0x0b:
break;
// 2xc: DSP Buffer Status Port
case BX_SB16_IO + 0x0c:
return dsp_bufferstatus();
// 2xd: reserved
case BX_SB16_IO + 0x0d:
break;
// 2xe: DSP Data Status Port
case BX_SB16_IO + 0x0e:
return dsp_status();
// 2xf: DSP Acknowledge 16bit DMA IRQ
case BX_SB16_IO + 0x0f:
return dsp_irq16ack();
// 3x0: MPU Data Port Read
case BX_SB16_IOMPU + 0x00:
return mpu_dataread();
// 3x1: MPU Status Port
case BX_SB16_IOMPU + 0x01:
return mpu_status();
// 3x2: reserved
case BX_SB16_IOMPU + 0x02:
break;
// 3x3: *Emulator* Port
case BX_SB16_IOMPU + 0x03:
return emul_read();
}
// If we get here, the port wasn't valid
writelog(3, "Read access to 0x%04x: unsupported port!", address);
return(0xff);
}
// static IO port write callback handler
// redirects to non-static class handler to avoid virtual functions
void bx_sb16_c::write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len)
{
#if !BX_USE_SB16_SMF
bx_sb16_c *class_ptr = (bx_sb16_c *) this_ptr;
class_ptr->write(address, value, io_len);
}
void bx_sb16_c::write(Bit32u address, Bit32u value, unsigned io_len)
{
#else
UNUSED(this_ptr);
#endif // !BX_USE_SB16_SMF
bx_pc_system.isa_bus_delay();
switch (address) {
// 2x0: FM Music Register Port
// 2x8 and 388 are aliases
case BX_SB16_IO + 0x00:
case BX_SB16_IO + 0x08:
case BX_SB16_IOADLIB + 0x00:
OPL.index[0] = value;
adlib_write_index(address, value);
return;
// 2x1: FM Music Data Port
// 2x9 and 389 are aliases
case BX_SB16_IO + 0x01:
case BX_SB16_IO + 0x09:
case BX_SB16_IOADLIB + 0x01:
opl_data(value, 0);
adlib_write(opl_index, value);
return;
// 2x2: Advanced FM Music Register Port
// or (for SBPro1) FM Music Register Port 2
// 38a is an alias
case BX_SB16_IO + 0x02:
case BX_SB16_IOADLIB + 0x02:
OPL.index[1] = value;
adlib_write_index(address, value);
return;
// 2x3: Advanced FM Music Data Port
// or (for SBPro1) FM Music Data Port 2
// 38b is an alias
case BX_SB16_IO + 0x03:
case BX_SB16_IOADLIB + 0x03:
opl_data(value, 1);
adlib_write(opl_index, value);
return;
// 2x4: Mixer Register Port
case BX_SB16_IO + 0x04:
mixer_writeregister(value);
return;
// 2x5: Mixer Data Portr,
case BX_SB16_IO + 0x05:
mixer_writedata(value);
return;
// 2x6: DSP Reset
case BX_SB16_IO + 0x06:
dsp_reset(value);
return;
// 2x7: reserved
case BX_SB16_IO + 0x07:
break;
// 2x8: FM Music Register Port (OPL-2)
// handled above
// 2x9: FM Music Data Port
// handled above
// 2xa: reserved (r: DSP Data Port)
case BX_SB16_IO + 0x0a:
break;
// 2xb: reserved
case BX_SB16_IO + 0x0b:
break;
// 2xc: DSP Write Command/Data
case BX_SB16_IO + 0x0c:
dsp_datawrite(value);
return;
// 2xd: reserved
case BX_SB16_IO + 0x0d:
break;
// 2xe: reserved (r: DSP Buffer Status)
case BX_SB16_IO + 0x0e:
break;
// 2xf: reserved
case BX_SB16_IO + 0x0f:
break;
// 3x0: MPU Command Port
case BX_SB16_IOMPU + 0x00:
mpu_datawrite(value);
return;
// 3x1: MPU Data Port
case BX_SB16_IOMPU + 0x01:
mpu_command(value);
return;
// 3x2: reserved
case BX_SB16_IOMPU + 0x02:
break;
// 3x3: *Emulator* Port
case BX_SB16_IOMPU + 0x03:
emul_write(value);
return;
}
// if we arrive here, the port is unsupported
writelog(3, "Write access to 0x%04x (value = 0x%02x): unsupported port!",
address, value);
}
void bx_sb16_c::create_logfile(void)
{
bx_list_c *base = (bx_list_c*) SIM->get_param(BXPN_SOUND_SB16);
bx_param_string_c *logfile = SIM->get_param_string("log", base);
if (logfile->isempty()) {
SIM->get_param_num("loglevel", base)->set(0);
return;
}
if (SIM->get_param_num("loglevel", base)->get() > 0) {
LOGFILE = fopen(logfile->getptr(),"w"); // logfile for errors etc.
if (LOGFILE == NULL) {
BX_ERROR(("Error opening file %s. Logging disabled.", logfile->getptr()));
SIM->get_param_num("loglevel", base)->set(0);
}
}
}
void bx_sb16_c::writelog(int loglev, const char *str, ...)
{
if ((LOGFILE == NULL) && (BX_SB16_THIS loglevel != 0)) {
create_logfile();
}
// append a line to the log file, if desired
if (BX_SB16_THIS loglevel >= loglev) {
fprintf(LOGFILE, FMT_TICK, bx_pc_system.time_ticks());
fprintf(LOGFILE, " (%d) ", loglev);
va_list ap;
va_start(ap, str);
vfprintf(LOGFILE, str, ap);
va_end(ap);
fprintf(LOGFILE, "\n");
fflush(LOGFILE);
}
}
// the round-robin FIFO buffers of the SB16
bx_sb16_buffer::bx_sb16_buffer()
{
length = 0; // total bytes in buffer
head = 0; // pointer to next slot available for new data
tail = 0; // pointer to next slot to be read from
buffer = NULL; // pointer to the actual data
}
void bx_sb16_buffer::init(int bufferlen)
{
if (buffer != NULL) // Was it initialized before?
delete buffer;
length = bufferlen;
buffer = new Bit8u[length];
if (buffer == NULL)
length = 0; // This will be checked later
reset();
}
void bx_sb16_buffer::reset()
{
head = 0; // Reset the pointers
tail = 0;
clearcommand(); // no current command set
}
bx_sb16_buffer::~bx_sb16_buffer()
{
if (buffer != NULL)
delete [] buffer;
buffer = NULL;
length = 0;
}
// Report how many bytes are available
int bx_sb16_buffer::bytes(void)
{
if (empty() != 0)
return 0; // empty / not initialized
int bytes = head - tail;
if (bytes < 0) bytes += length;
return (bytes);
}
// This puts one byte into the buffer
bx_bool bx_sb16_buffer::put(Bit8u data)
{
if (full() != 0)
return 0; // buffer full
buffer[head++] = data; // Write data, and increase write pointer
head %= length; // wrap it around so it stays inside the data
return 1; // put was successful
}
// This writes a formatted string to the buffer
bx_bool bx_sb16_buffer::puts(const char *data, ...)
{
if (data == NULL)
return 0; // invalid string
//char string[length];
char *string;
int index = 0;
string = new char[length];
va_list ap;
va_start(ap, data);
vsprintf(string, data, ap);
va_end(ap);
if ((int) strlen(string) >= length)
BX_PANIC(("bx_sb16_buffer: puts() too long!"));
while (string[index] != 0)
{
if (put((Bit8u) string[index]) == 0)
{
delete [] string;
return 0; // buffer full
}
index++;
}
delete [] string;
return 1;
}
// This returns if the buffer is full, i.e. if a put will fail
bx_bool bx_sb16_buffer::full(void)
{
if (length == 0)
return 1; // not initialized
if (((head + 1) % length) == tail)
return 1; // buffer full
return 0; // buffer has some space left
}
// This reads the next available byte from the buffer
bx_bool bx_sb16_buffer::get(Bit8u *data)
{
if (empty() != 0)
{
// Buffer is empty. Still, if it was initialized, return
// the last byte again.
if (length > 0)
(*data) = buffer[ (tail - 1) % length ];
return 0; // buffer empty
}
(*data) = buffer[tail++]; // read data and increase read pointer
tail %= length; // and wrap it around to stay inside the data
return 1; // get was successful
}
// Read a word in lo/hi order
bx_bool bx_sb16_buffer::getw(Bit16u *data)
{
Bit8u dummy;
if (bytes() < 2)
{
if (bytes() == 1)
{
get(&dummy);
*data = (Bit16u) dummy;
}
else
dummy = 0;
return 0;
}
get(&dummy);
*data = (Bit16u) dummy;
get(&dummy);
*data |= ((Bit16u) dummy) << 8;
return 1;
}
// Read a word in hi/lo order
bx_bool bx_sb16_buffer::getw1(Bit16u *data)
{
Bit8u dummy;
if (bytes() < 2)
{
if (bytes() == 1)
{
get(&dummy);
*data = ((Bit16u) dummy) << 8;
}
else
dummy = 0;
return 0;
}
get(&dummy);
*data = ((Bit16u) dummy) << 8;
get(&dummy);
*data |= (Bit16u) dummy;
return 1;
}
// This returns if the buffer is empty, i.e. if a get will fail
bx_bool bx_sb16_buffer::empty(void)
{
if (length == 0)
return 1; // not inialized
if (head == tail)
return 1; // buffer empty
return 0; // buffer contains data
}
// Flushes the buffer
void bx_sb16_buffer::flush(void)
{
tail = head;
return;
}
// Peeks ahead in the buffer
// Warning: No checking if result is valid. Must call bytes() to check that!
Bit8u bx_sb16_buffer::peek(int offset)
{
return buffer[(tail + offset) % length];
}
// Set a new active command
void bx_sb16_buffer::newcommand(Bit8u newcmd, int bytes)
{
command = newcmd;
havecommand = 1;
bytesneeded = bytes;
}
// Return the currently active command
Bit8u bx_sb16_buffer::currentcommand(void)
{
return command;
}
// Clear the active command
void bx_sb16_buffer::clearcommand(void)
{
command = 0;
havecommand = 0;
bytesneeded = 0;
}
// return if the command has received all necessary bytes
bx_bool bx_sb16_buffer::commanddone(void)
{
if (hascommand() == 0)
return 0; // no command pending - not done then
if (bytes() >= bytesneeded)
return 1; // yes, it's done
return 0; // no, it's not
}
// return if there is a command pending
bx_bool bx_sb16_buffer::hascommand(void)
{
return havecommand;
}
int bx_sb16_buffer::commandbytes(void)
{
return bytesneeded;
}
// runtime parameter handlers
Bit64s bx_sb16_c::sb16_param_handler(bx_param_c *param, int set, Bit64s val)
{
if (set) {
const char *pname = param->get_name();
if (!strcmp(pname, "dmatimer")) {
BX_SB16_THIS dmatimer = (Bit32u)val;
} else if (!strcmp(pname, "loglevel")) {
BX_SB16_THIS loglevel = (int)val;
} else if (!strcmp(pname, "midimode")) {
if (val != BX_SB16_THIS midimode) {
BX_SB16_THIS midi_changed |= 1;
}
} else if (!strcmp(pname, "wavemode")) {
if (val != BX_SB16_THIS wavemode) {
BX_SB16_THIS wave_changed |= 1;
}
} else {
BX_PANIC(("sb16_param_handler called with unexpected parameter '%s'", pname));
}
}
return val;
}
const char* bx_sb16_c::sb16_param_string_handler(bx_param_string_c *param, int set,
const char *oldval, const char *val,
int maxlen)
{
if ((set) && (strcmp(val, oldval))) {
const char *pname = param->get_name();
if (!strcmp(pname, "wavefile")) {
BX_SB16_THIS wave_changed |= 2;
} else if (!strcmp(pname, "midifile")) {
BX_SB16_THIS midi_changed |= 2;
} else if (!strcmp(pname, "log")) {
if (LOGFILE != NULL) {
fclose(LOGFILE);
LOGFILE = NULL;
}
// writelog() re-opens the log file on demand
} else {
BX_PANIC(("sb16_param_string_handler called with unexpected parameter '%s'", pname));
}
}
return val;
}
#endif /* if BX_SUPPORT_SB16 */