Bochs/bochs/iodev/sb16.cc
Todd T.Fries bdb89cd364 merge in BRANCH-io-cleanup.
To see the commit logs for this use either cvsweb or
cvs update -r BRANCH-io-cleanup and then 'cvs log' the various files.

In general this provides a generic interface for logging.

logfunctions:: is a class that is inherited by some classes, and also
.   allocated as a standalone global called 'genlog'.  All logging uses
.   one of the ::info(), ::error(), ::ldebug(), ::panic() methods of this
.   class through 'BX_INFO(), BX_ERROR(), BX_DEBUG(), BX_PANIC()' macros
.   respectively.
.
.   An example usage:
.     BX_INFO(("Hello, World!\n"));

iofunctions:: is a class that is allocated once by default, and assigned
as the iofunction of each logfunctions instance.  It is this class that
maintains the file descriptor and other output related code, at this
point using vfprintf().  At some future point, someone may choose to
write a gui 'console' for bochs to which messages would be redirected
simply by assigning a different iofunction class to the various logfunctions
objects.

More cleanup is coming, but this works for now.  If you want to see alot
of debugging output, in main.cc, change onoff[LOGLEV_DEBUG]=0 to =1.

Comments, bugs, flames, to me: todd@fries.net
2001-05-15 14:49:57 +00:00

3449 lines
84 KiB
C++

// Copyright (C) 2001 MandrakeSoft S.A.
//
// MandrakeSoft S.A.
// 43, rue d'Aboukir
// 75002 Paris - France
// http://www.linux-mandrake.com/
// http://www.mandrakesoft.com/
//
// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// This file (SB16.CC) written and donated by Josef Drexler
#include "bochs.h"
#define LOG_THIS bx_sb16.
// some shortcuts to save typing
#define LOGFILE BX_SB16_THIS logfile
#define BOTHLOG(x) (x)
#define MIDILOG(x) ((bx_options.sb16.midimode>0?x:0x7f))
#define WAVELOG(x) ((bx_options.sb16.wavemode>0?x:0x7f))
#define MIDIDATA BX_SB16_THIS midifile
#define WAVEDATA BX_SB16_THIS wavefile
#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_OUTPUT BX_SB16_THIS output
bx_sb16_c bx_sb16;
#if BX_USE_SB16_SMF
#define this ((void *)&bx_sb16)
#endif
bx_sb16_c::bx_sb16_c(void)
{
setprefix("[SB16]");
settype(SB16LOG);
}
bx_sb16_c::~bx_sb16_c(void)
{
switch (bx_options.sb16.midimode)
{
case 2:
finishmidifile();
break;
case 1:
if (MPU.outputinit != 0)
BX_SB16_OUTPUT->closemidioutput();
break;
case 3:
if (MIDIDATA != NULL)
fclose(MIDIDATA);
break;
}
switch (bx_options.sb16.wavemode)
{
case 2:
finishvocfile();
break;
case 1:
if (DSP.outputinit != 0)
BX_SB16_OUTPUT->closewaveoutput();
break;
case 3:
if (WAVEDATA != NULL)
fclose(WAVEDATA);
break;
}
delete(BX_SB16_OUTPUT);
delete(DSP.dma.chunk);
if (bx_options.sb16.loglevel > 0)
fclose(LOGFILE);
}
void bx_sb16_c::init(bx_devices_c *d)
{
BX_SB16_THIS devices = d;
unsigned addr;
if ( (bx_options.sb16.logfile == NULL) ||
(strlen(bx_options.sb16.logfile) < 1) )
bx_options.sb16.loglevel = 0;
if (bx_options.sb16.loglevel > 0)
{
LOGFILE = fopen(bx_options.sb16.logfile,"w"); // logfile for errors etc.
if (LOGFILE == NULL)
{
BX_INFO(("#SB16: Error opening file %s. Logging disabled.", bx_options.sb16.logfile));
bx_options.sb16.loglevel = 0;
}
}
// let the output functions initialize
BX_SB16_OUTPUT = new BX_SOUND_OUTPUT_C(BX_SB16_THISP);
if (BX_SB16_OUTPUT == NULL)
{
writelog( MIDILOG(2), "Couldn't initialize output devices. Output disabled.");
bx_options.sb16.midimode = 0;
bx_options.sb16.wavemode = 0;
}
if ( (bx_options.sb16.midimode == 2) ||
(bx_options.sb16.midimode == 3) )
{
MIDIDATA = fopen(bx_options.sb16.midifile,"w");
if (MIDIDATA == NULL)
{
writelog (MIDILOG(2), "Error opening file %s. Midimode disabled.", bx_options.sb16.midifile);
bx_options.sb16.midimode = 0;
}
else if (bx_options.sb16.midimode == 2)
initmidifile();
}
if ( (bx_options.sb16.wavemode == 2) ||
(bx_options.sb16.wavemode == 3) )
{
WAVEDATA = fopen(bx_options.sb16.wavefile,"w");
if (WAVEDATA == NULL)
{
writelog (WAVELOG(2), "Error opening file %s. Wavemode disabled.", bx_options.sb16.wavefile);
bx_options.sb16.wavemode = 0;
}
else if (bx_options.sb16.wavemode == 2)
initvocfile();
}
DSP.dma.chunk = new Bit8u[BX_SOUND_OUTPUT_WAVEPACKETSIZE];
DSP.dma.chunkindex = 0;
DSP.outputinit = 0;
MPU.outputinit = 0;
if (DSP.dma.chunk == NULL)
{
writelog( WAVELOG(2), "Couldn't allocate wave buffer - wave output disabled.");
bx_options.sb16.wavemode = 0;
}
BX_INFO(("#SB16 options: midi=%d,%s wave=%d,%s log=%d,%s dmatimer=%d\n",
bx_options.sb16.midimode, bx_options.sb16.midifile,
bx_options.sb16.wavemode, bx_options.sb16.wavefile,
bx_options.sb16.loglevel, bx_options.sb16.logfile,
bx_options.sb16.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.resetport = 1; // so that one call to dsp_reset is sufficient
dsp_reset(0); // (reset is 1 to 0 transition)
BX_SB16_IRQ = -1; // will be initialized later by the mixer reset
// call the mixer reset
mixer_writeregister(0x00);
mixer_writedata(0x00);
// reset the FM emulation
OPL.mode = dual;
opl_entermode(single);
// Allocate the IO addresses, 2x0..2xf, 3x0..3x4 and 388..38b
for (addr=BX_SB16_IO; addr<BX_SB16_IO+BX_SB16_IOLEN; addr++) {
BX_SB16_THIS devices->register_io_read_handler(this,
&read_handler, addr, "SB16");
BX_SB16_THIS devices->register_io_write_handler(this,
&write_handler, addr, "SB16");
}
for (addr=BX_SB16_IOMPU; addr<BX_SB16_IOMPU+BX_SB16_IOMPULEN; addr++) {
BX_SB16_THIS devices->register_io_read_handler(this,
&read_handler, addr, "SB16");
BX_SB16_THIS devices->register_io_write_handler(this,
&write_handler, addr, "SB16");
}
/* Uncomment this if you know the consequences...
for (addr=BX_SB16_IOADLIB; addr<BX_SB16_IOADLIB+BX_SB16_IOADLIBLEN; addr++) {
BX_SB16_THIS devices->register_io_read_handler(this,
read_handler, addr, "SB16");
BX_SB16_THIS devices->register_io_write_handler(this,
write_handler, addr, "SB16");
}
*/
writelog(BOTHLOG(3),
"driver 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
MPU.timer_handle = bx_pc_system.register_timer
(BX_SB16_THISP, mpu_timer, 500000 / 384, 1, 1);
// midi timer: active, continuous, 500000 / 384 seconds (384 = delta time, 500000 = sec per beat at 120 bpm. Don't change this!)
DSP.timer_handle = bx_pc_system.register_timer
(BX_SB16_THISP, dsp_dmatimer, 1, 1, 0);
// dma timer: inactive, continous, frequency variable
OPL.timer_handle = bx_pc_system.register_timer
(BX_SB16_THISP, opl_timer, 80, 1, 0);
// 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;
}
// 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 dsp_getsamplebyte()
// when the next byte has been 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.
if ( (bx_options.sb16.wavemode != 1) ||
( (This->dsp.dma.chunkindex + 1 < BX_SOUND_OUTPUT_WAVEPACKETSIZE) &&
(This->dsp.dma.count > 0) ) ||
(This->output->waveready() == BX_SOUND_OUTPUT_OK) )
bx_pc_system.set_DRQ(BX_SB16_DMAL, 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)
{
BX_SB16_THIS devices->pic->untrigger_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.stereo = 0;
DSP.dma.issigned = 0;
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 mode, value8;
Bit16u length;
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:
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:
case 0xe4:
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(&value8);
DSP.datain.get(&value8);
break;
case 0x0f:
DSP.datain.get(&value8);
DSP.dataout.put(0); // 0 means no ASP present?
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.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.samplerate) );
DSP.dma.timeconstant = 65536 - (Bit32u) 256000000 / (Bit32u) DSP.dma.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);
// only handled for VOC output so far
if (bx_options.sb16.wavemode == 2)
{
Bit8u temparray[3] = { length & 0xff, length >> 8, DSP.dma.timeconstant >> 8 };
writevocblock(3, 3, temparray, 0, NULL);
}
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;
// used and expected by the CL diagnose.exe
case 0xe0:
DSP.dataout.put(0x55);
break;
// get version, out 2 bytes (major, minor)
case 0xe1:
// none, o1/2: version major.minor
DSP.dataout.put(4);
if (DSP.dataout.put(11) == 0)
{
writelog(WAVELOG(3), "DSP version couldn't be written - buffer overflow");
}
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;
// used and expected by the CL diagnose.exe
case 0xe4:
DSP.dataout.put(0xaa);
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
BX_SB16_THIS devices->pic->trigger_irq(BX_SB16_IRQ);
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)
{
// 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.bits = 16;
DSP.dma.bps = 2;
}
else // 0xc? = 8 bit DMA
{
DSP.dma.bits = 8;
DSP.dma.bps = 1;
}
// Prevent division by zero in some instances
if ( DSP.dma.samplerate == 0 )
DSP.dma.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.stereo = (mode >> 5) & 1;
if (DSP.dma.stereo != 0)
DSP.dma.bps *= 2;
DSP.dma.blocklength = length;
DSP.dma.issigned = (mode >> 4) & 1;
DSP.dma.count = (DSP.dma.blocklength + 1) * DSP.dma.bps - 1;
DSP.dma.highspeed = (comp >> 4) & 1;
DSP.dma.chunkindex = 0;
DSP.dma.chunkcount = 0;
Bit32u sampledatarate = (Bit32u) DSP.dma.samplerate * (Bit32u) DSP.dma.bps;
DSP.dma.timer = (Bit32u) bx_options.sb16.dmatimer / sampledatarate;
writelog( WAVELOG(5), "DMA is %db, %dHz, %s, %s, mode %d, %s, %s, %d bps, %d us/b",
DSP.dma.bits, DSP.dma.samplerate, (DSP.dma.stereo != 0)?"stereo":"mono",
(DSP.dma.output == 1)?"output":"input", DSP.dma.mode,
(DSP.dma.issigned == 1)?"signed":"unsigned",
(DSP.dma.highspeed == 1)?"highspeed":"normal speed",
sampledatarate, DSP.dma.timer);
DSP.dma.format = DSP.dma.issigned | ( (comp & 7) << 1) | ( (comp & 8) << 4);
// write the output to the device/file
if (DSP.dma.output == 1)
{
if (bx_options.sb16.wavemode == 1)
{
if (DSP.outputinit == 0)
{
if (BX_SB16_OUTPUT->openwaveoutput(bx_options.sb16.wavefile) != BX_SOUND_OUTPUT_OK)
{
bx_options.sb16.wavemode = 0;
writelog( WAVELOG(2), "Error: Could not open wave output device.");
}
else
DSP.outputinit = 1;
}
if (DSP.outputinit == 1)
BX_SB16_OUTPUT->startwaveplayback(DSP.dma.samplerate, DSP.dma.bits, DSP.dma.stereo, DSP.dma.format);
}
}
dsp_enabledma();
}
// 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 )
{
DSP.irqpending = 0;
MIXER.reg[0x82] &= (~0x01);
writelog( WAVELOG(4), "8-bit DMA or SBMIDI IRQ acknowledged");
if (MIXER.reg[0x82] == 0)
BX_SB16_THIS devices->pic->untrigger_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 )
{
DSP.irqpending = 0;
MIXER.reg[0x82] &= (~0x02);
if (MIXER.reg[0x82] == 0)
BX_SB16_THIS devices->pic->untrigger_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
// get a wave packet from the input device (not supported yet)
void bx_sb16_c::dsp_getwavepacket()
{
writelog( WAVELOG(3), "DMA reads not supported. Returning silence.");
// fill the buffer with silence. Watch for 16bit transfer and signed/unsigned
// these are the two different bytes in 16bit transfer (both the same for 8bit)
Bit8u byteA, byteB;
byteA = 0x00; // compatible with all signed transfers
byteB = 0x00;
int i;
if (DSP.dma.issigned == 0)
byteB = 0x80;
if (DSP.dma.bits == 8)
byteA = byteB;
for (i = 0; i < BX_SOUND_OUTPUT_WAVEPACKETSIZE; i++)
DSP.dma.chunk[i] = ( (i & 1) == 0) ? byteA : byteB;
DSP.dma.chunkcount = BX_SOUND_OUTPUT_WAVEPACKETSIZE;
DSP.dma.chunkindex = 0;
}
// write a wave packet to the output device
void bx_sb16_c::dsp_sendwavepacket()
{
switch (bx_options.sb16.wavemode)
{
case 1:
BX_SB16_OUTPUT->sendwavepacket(DSP.dma.chunkindex, DSP.dma.chunk);
break;
case 3:
fwrite(DSP.dma.chunk, 1, DSP.dma.chunkindex, WAVEDATA);
break;
case 2:
Bit8u temparray[12] =
{DSP.dma.samplerate & 0xff, DSP.dma.samplerate >> 8, 0, 0,
DSP.dma.bits, DSP.dma.stereo + 1, 0, 0, 0, 0, 0, 0 };
switch ( (DSP.dma.format >> 1) & 7)
{
case 2:
temparray[7] = 3;
break;
case 3:
temparray[7] = 2;
break;
case 4:
temparray[7] = 1;
break;
}
if (DSP.dma.bits == 16)
temparray[7] = 4;
writevocblock(9, 12, temparray, DSP.dma.chunkindex, DSP.dma.chunk);
break;
}
DSP.dma.chunkindex = 0;
}
// put a sample byte into the output buffer
void bx_sb16_c::dsp_getsamplebyte(Bit8u value)
{
if (DSP.dma.chunkindex < BX_SOUND_OUTPUT_WAVEPACKETSIZE)
DSP.dma.chunk[DSP.dma.chunkindex++] = value;
if (DSP.dma.chunkindex >= BX_SOUND_OUTPUT_WAVEPACKETSIZE)
dsp_sendwavepacket();
}
// read a sample byte from the input buffer
Bit8u bx_sb16_c::dsp_putsamplebyte()
{
if (DSP.dma.chunkindex >= DSP.dma.chunkcount)
dsp_getwavepacket();
return DSP.dma.chunk[DSP.dma.chunkindex++];
}
// 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
if (bx_options.sb16.wavemode == 1)
{
if (DSP.dma.mode != 2)
BX_SB16_OUTPUT->stopwaveplayback(); // don't stop if Auto-DMA
}
else if (bx_options.sb16.wavemode == 2)
fflush(WAVEDATA);
}
// generate the appropriate IRQ
if (DSP.dma.bits == 8)
MIXER.reg[0x82] |= 1;
else
MIXER.reg[0x82] |= 2;
BX_SB16_THIS devices->pic->trigger_irq(BX_SB16_IRQ);
DSP.irqpending = 1;
//if auto-DMA, reinitialize
if (DSP.dma.mode == 2)
{
DSP.dma.count = (DSP.dma.blocklength + 1) * DSP.dma.bps - 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)
void bx_sb16_c::dma_read8(Bit8u *data_byte)
{
bx_pc_system.set_DRQ(BX_SB16_DMAL, 0); // the timer will raise it again
if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
writelog( WAVELOG(5), "Received 8-bit DMA %2x, %d remaining ",
*data_byte, DSP.dma.count);
DSP.dma.count--;
dsp_getsamplebyte(*data_byte);
if (DSP.dma.count == 0xffff) // last byte received
dsp_dmadone();
}
void bx_sb16_c::dma_write8(Bit8u *data_byte)
{
bx_pc_system.set_DRQ(BX_SB16_DMAL, 0); // the timer will raise it again
DSP.dma.count--;
*data_byte = dsp_putsamplebyte();
if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
writelog( WAVELOG(5), "Sent 8-bit DMA %2x, %d remaining ",
*data_byte, DSP.dma.count);
if (DSP.dma.count == 0xffff) // last byte sent
dsp_dmadone();
}
void bx_sb16_c::dma_read16(Bit16u *data_word)
{
bx_pc_system.set_DRQ(BX_SB16_DMAH, 0); // the timer will raise it again
if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
writelog( WAVELOG(5), "Received 16-bit DMA %4x, %d remaining ",
*data_word, DSP.dma.count);
DSP.dma.count -= 2;
dsp_getsamplebyte(*data_word & 0xff);
dsp_getsamplebyte(*data_word >> 8);
if (DSP.dma.count == 0xffff) // last byte received
dsp_dmadone();
}
void bx_sb16_c::dma_write16(Bit16u *data_word)
{
Bit8u byte1, byte2;
bx_pc_system.set_DRQ(BX_SB16_DMAH, 0); // the timer will raise it again
DSP.dma.count -= 2;
byte1 = dsp_putsamplebyte();
byte2 = dsp_putsamplebyte();
// all input is in little endian
*data_word = byte1 | (byte2 << 8);
if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
writelog( WAVELOG(5), "Sent 16-bit DMA %4x, %d remaining ",
*data_word, DSP.dma.count);
if (DSP.dma.count == 0xffff) // last byte sent
dsp_dmadone();
}
// the mixer, supported type is CT1745 (as in an SB16)
void bx_sb16_c::mixer_writedata(Bit32u value)
{
int i;
if (MIXER.regindex >= BX_SB16_MIX_REG)
return; // index out of range
// store the value
MIXER.reg[MIXER.regindex] = value;
// do some action depending on what register was written
switch (MIXER.regindex)
{
case 0: // initialize mixer
writelog(BOTHLOG(4), "Initializing mixer...");
for (i=0; i<BX_SB16_MIX_REG; i++)
MIXER.reg[i] = 0;
MIXER.reg[0x80] = 2; // IRQ 5
MIXER.reg[0x81] = 2; // 8-bit DMA 1, no 16-bit DMA
set_irq_dma(); // set the IRQ and DMA
MIXER.regindex = 0; // next mixer register read is register 0
break;
case 0x80: // IRQ mask
case 0x81: // DMA mask
set_irq_dma(); // both 0x80 and 0x81 handled
break;
// Note: some registers are bit-mapped to others. This should be
// reflected here, in case somebody uses this to find out the
// version of the DSP
// These registers are 0x04, 0x0a, 0x22, 0x26, 0x28, 0x2e
}
}
Bit32u bx_sb16_c::mixer_readdata()
{
return(MIXER.reg[MIXER.regindex]);
}
void bx_sb16_c::mixer_writeregister(Bit32u value)
{
MIXER.regindex = value;
writelog(BOTHLOG(4), "Mixer register %02x set to %02x",
MIXER.regindex, MIXER.reg[MIXER.regindex]);
}
void bx_sb16_c::set_irq_dma()
{
int newirq;
// 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)
BX_SB16_THIS devices->unregister_irq(BX_SB16_IRQ, "SB16");
BX_SB16_IRQ = newirq;
BX_SB16_THIS devices->register_irq(BX_SB16_IRQ, "SB16");
}
// set the 8 bit DMA
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);
}
// and the 16 bit DMA
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!
}
writelog(BOTHLOG(4), "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_options.sb16.midimode == 1) &&
(BX_SB16_OUTPUT->midiready() == BX_SOUND_OUTPUT_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;
BX_SB16_THIS devices->pic->trigger_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();
/*
if (BX_SB16_IRQ != -1)
{
MIXER.reg[0x82] |= 4;
BX_SB16_THIS devices->pic->trigger_irq(BX_SB16_IRQ);
}
*/
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] == 0)
BX_SB16_THIS devices->pic->untrigger_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);
return;
}
// 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
Boolean 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;
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_PATCHTABLESIZE) 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_PATCHTABLESIZE) 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_PATCHTABLESIZE) 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;
case 5:
EMUL.datain.puts("Current OPL2/3 mode: %s",
// ok, I admit that this is a bit ugly...
(OPL.mode == single)?"single OPL2 (OPL3 disabled)\n":
(OPL.mode == adlib)?"single OPL2 (no OPL3)\n":
(OPL.mode == dual)?"double OPL2\n":
(OPL.mode == opl3)?"OPL3\n":
"unknown");
break;
default:
EMUL.datain.puts("no info. Only slots 0..5 have values.\n");
break;
}
break;
case 6: // close midi and wave files and/or output
if ( (bx_options.sb16.midimode == 2) ||
(bx_options.sb16.midimode == 3) )
{
if (bx_options.sb16.midimode == 2)
finishmidifile();
fclose(MIDIDATA);
}
else if (bx_options.sb16.midimode == 1)
BX_SB16_OUTPUT->closemidioutput();
bx_options.sb16.midimode = 0;
if ( (bx_options.sb16.wavemode == 2) ||
(bx_options.sb16.wavemode == 3) )
{
if (bx_options.sb16.wavemode == 2)
finishvocfile();
fclose(WAVEDATA);
}
else
BX_SB16_OUTPUT->closewaveoutput();
bx_options.sb16.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
EMUL.dataout.get(&value8);
writelog(4, "Entering OPL2/3 mode %d", value8);
opl_entermode( (bx_sb16_fm_mode) value8);
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
// select a new operational mode for the FM part
// this also serves as reset for the OPL chip
void bx_sb16_c::opl_entermode(bx_sb16_fm_mode newmode)
{
int i, j;
// do nothing if the mode is unchanged
if ( OPL.mode == newmode)
return;
// if the old mode was 0, and the new mode is 3, then
// no reset is necessary, just set the flag
if ( (OPL.mode == single) && (newmode == opl3) )
{
writelog( MIDILOG(4), "OPL3 mode enabled");
OPL.mode = newmode;
return;
}
writelog( MIDILOG(4), "Switching to OPL mode %d from %d", newmode, OPL.mode);
for (i=0; i<BX_SB16_FM_NCH; i++)
opl_keyonoff(i, 0);
OPL.mode = newmode;
if (OPL.timer_running != 0)
{
bx_pc_system.deactivate_timer( OPL.timer_handle );
OPL.timer_running = 0;
}
OPL.drumchannel = 10;
OPL.midichannels = 0xffff; // all channels but the drum channel available
OPL.midichannels &= ~(1 << OPL.drumchannel);
for (i=0; i<2; i++)
{
OPL.wsenable[i] = 0;
OPL.tmask[i] = 0;
OPL.tflag[i] = 0;
OPL.percmode[i] = 0;
}
for (i=0; i<4; i++)
{
OPL.timer[i] = 0;
OPL.timerinit[i] = 0;
}
// initialize the operators
for (i=0; i<BX_SB16_FM_NOP; i++)
for (j=0; j<BX_SB16_FM_OPB; j++)
OPL.oper[i][j] = 0;
// TESTING for array bounds - compiler should bark if too high
OPL.oper[BX_SB16_FM_NOP-1][BX_SB16_FM_OPB-1] = 0;
// initialize the channels
// first zero all values
for (i=0; i<BX_SB16_FM_NCH; i++)
{
OPL.chan[i].nop = 0;
for (j=0; j<4; j++)
{
OPL.chan[i].opnum[j] = 0;
OPL.chan[i].outputlevel[j] = 0;
}
OPL.chan[i].freq = 0;
OPL.chan[i].afreq = 0;
OPL.chan[i].midichan = 0xff;
OPL.chan[i].needprogch = 0;
OPL.chan[i].midinote = 0;
OPL.chan[i].midibend = 0;
OPL.chan[i].midivol = 0;
}
// assign the operators
for (i=0; i<BX_SB16_FM_NCH; i++)
{
OPL.chan[i].nop = 2;
// who invented this absolutely insane operator grouping??
// it's like this: (ch 9...17 as 0...8 but higher operators)
// ch: 0 1 2 3 4 5 6 7 8
// op1: 0 1 2 6 7 8 12 13 14
// op2: 3 4 5 9 10 11 15 16 17
OPL.chan[i].opnum[0] = i + ( (int) (i / 3) ) * 3;
OPL.chan[i].opnum[1] = OPL.chan[i].opnum[0] + 3;
}
// assign 4-op operators to the appropriate channels
// note- they are not used unless .nop == 4
for (i=0; i<6; i++)
{
j = i + (i /3) * 6;
OPL.chan[j].opnum[2] = OPL.chan[j + 3].opnum[0];
OPL.chan[j].opnum[3] = OPL.chan[j + 3].opnum[1];
}
}
// this is called whenever one of the timer elapses
void bx_sb16_c::opl_timerevent()
{
for (int i=0; i<4; i++)
if ( (OPL.tmask[i/2] & (1 << (i % 2)) ) != 0)
{ // only running timers
if ( (OPL.timer[i]--) == 0)
{ // overflow occured, set flags accordingly
OPL.timer[i] = OPL.timerinit[i]; // reset the counter
if ( (OPL.tmask[i/2] >> (5 + i % 2) ) != 0) // set flags only if unmasked
{
OPL.tflag[i/2] &= 1 << (5 + 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];
if ( (OPL.mode == single) || (OPL.mode == opl3) )
status |= 0x06; // this is for OPL3 detections
writelog( MIDILOG(5), "OPL status of chip %d is %02x", chipid, status);
return status;
}
// set the register index for one of the OPL2's or the
// base or advanced register index for the OPL3
void bx_sb16_c::opl_index(Bit32u value, int chipid)
{
OPL.index[chipid] = value;
}
// write to the data port
void bx_sb16_c::opl_data(Bit32u value, int chipid)
{
int index = OPL.index[chipid];
int opernum = -1; // OPL3 operator number; 0..35
int channum = -1; // OPL3 channel number; 0..17
int subopnum = -1; // channel operator; 0..nop-1
writelog( MIDILOG(4), "Write to OPL(%d) register %02x: %02x",
chipid, index, value);
// first find out operator and/or channel numbers
// case 0x20 ... 0x95: includes too many ports, but that is harmless
// case 0xe0 ... 0xf5:
if ( ((index>=0x20) && (index<=0x95)) ||
((index>=0xe0) && (index<=0xf5)) ) {
// operator access
// find the operator number. 0..17 on chip 1, 18..35 on chip 2
// note, the numbers are not continuous (again...), so we need
// this rather weird calculation
opernum = index & 0x07;
if (opernum > 5) // invalid register, has no operator associated
{
opernum = -1;
goto break_here;
}
opernum += (index & 0x18) * 6;
if (opernum > 17) // Operators 18+ have to be accessed on other address set
{
opernum = -1;
goto break_here;
}
if (chipid == 1)
opernum += BX_SB16_FM_NOP / 2;
// find out the channel number, and which of the channel's operators this is
channum = opernum % 3 + ( (int) (opernum / 6) ) * 3;
subopnum = 0;
if ( (opernum % 6) > 2) // second operator
subopnum = 1;
// if (channel - 3) is in a four-operator mode, that is really
// what this operator belongs to
if (channum >= 3)
if ( OPL.chan[channum - 3].nop == 4 )
{
channum -= 3;
subopnum += 2;
}
writelog( MIDILOG(5), "Is Channel %d, Oper %d, Subop %d",
channum, opernum, subopnum);
}
else if ( (index>=0xa0) && (index<=0xc8) ) {
// channel access
channum = index & 0x0f;
if (OPL.chan[channum].nop == 0)
channum = -1; // the channel is disabled
writelog( MIDILOG(5), "Is channel %d", channum);
}
break_here:
switch (index & 0xff)
{
// WSEnable and Test Register
case 0x01:
OPL.wsenable[chipid] = (value >> 5) & 1;
if ( (value & 0x1f) != 0)
writelog( MIDILOG(3), "Warning: Test Register set to %02x", value & 0x1f);
break;
// the two timer counts
case 0x02:
case 0x03:
OPL.timerinit[(index - 2) + chipid * 2] =
OPL.timer[(index - 2) + chipid * 2] = value;
break;
// if OPL2: timer masks
// if OPL3: 4-operator modes
case 0x04:
if ( (chipid == 0) || (OPL.mode == dual) )
opl_settimermask(value, chipid);
else
opl_set4opmode(value & 0x3f);
break;
// only OPL3: OPL3 enable
case 0x05:
if ( ( OPL.mode == single ) || ( OPL.mode == opl3 ) )
{
if ( (value & 1) != 0)
opl_entermode(opl3);
else
opl_entermode(single);
break;
}
// otherwise let default: catch it
// Composite Sine Wave and Note-sel (ignored)
case 0x08:
if (value != 0)
writelog( MIDILOG(3),
"Warning: write of %02x to CSW/Note-sel ignored",
value);
break;
// most importantly the percussion part
case 0xbd:
opl_setpercussion(value, chipid);
break;
// the operator registers
// case 0x20 ... 0x35:
case 0x20:
case 0x21:
case 0x22:
case 0x23:
case 0x24:
case 0x25:
case 0x26:
case 0x27:
case 0x28:
case 0x29:
case 0x2a:
case 0x2b:
case 0x2c:
case 0x2d:
case 0x2e:
case 0x2f:
case 0x30:
case 0x31:
case 0x32:
case 0x33:
case 0x34:
case 0x35:
// case 0x60 ... 0x75:
case 0x60:
case 0x61:
case 0x62:
case 0x63:
case 0x64:
case 0x65:
case 0x66:
case 0x67:
case 0x68:
case 0x69:
case 0x6a:
case 0x6b:
case 0x6c:
case 0x6d:
case 0x6e:
case 0x6f:
case 0x70:
case 0x71:
case 0x72:
case 0x73:
case 0x74:
case 0x75:
// case 0x80 ... 0x95:
case 0x80:
case 0x81:
case 0x82:
case 0x83:
case 0x84:
case 0x85:
case 0x86:
case 0x87:
case 0x88:
case 0x89:
case 0x8a:
case 0x8b:
case 0x8c:
case 0x8d:
case 0x8e:
case 0x8f:
case 0x90:
case 0x91:
case 0x92:
case 0x93:
case 0x94:
case 0x95:
if (opernum != -1)
{
opl_changeop(channum, opernum, (index / 0x20) - 1, value);
break;
}
// else let default: catch it
// case 0x40 ... 0x55:
case 0x40:
case 0x41:
case 0x42:
case 0x43:
case 0x44:
case 0x45:
case 0x46:
case 0x47:
case 0x48:
case 0x49:
case 0x4a:
case 0x4b:
case 0x4c:
case 0x4d:
case 0x4e:
case 0x4f:
case 0x50:
case 0x51:
case 0x52:
case 0x53:
case 0x54:
case 0x55:
if (opernum != -1)
{
opl_changeop(channum, opernum, 1, value & 0xc0);
if (subopnum != -1)
opl_setvolume(channum, subopnum, value & 0x3f);
break;
}
// else let default: catch it
// case 0xe0 ... 0xf5:
case 0xe0:
case 0xe1:
case 0xe2:
case 0xe3:
case 0xe4:
case 0xe5:
case 0xe6:
case 0xe7:
case 0xe8:
case 0xe9:
case 0xea:
case 0xeb:
case 0xec:
case 0xed:
case 0xee:
case 0xef:
case 0xf0:
case 0xf1:
case 0xf2:
case 0xf3:
case 0xf4:
case 0xf5:
if (opernum != -1)
{
opl_changeop(channum, opernum, 5, value & 0x07);
break;
}
// else let default: catch it
// and the channel registers
// case 0xa0 ... 0xa8:
case 0xa0:
case 0xa1:
case 0xa2:
case 0xa3:
case 0xa4:
case 0xa5:
case 0xa6:
case 0xa7:
case 0xa8:
if (channum != -1)
{
OPL.chan[channum].freq &= 0xff00;
OPL.chan[channum].freq |= value;
if ( (OPL.chan[channum].freqch |= 1) == 3)
opl_setfreq(channum);
break;
}
// else let default: catch it
// case 0xb0 ... 0xb8:
case 0xb0:
case 0xb1:
case 0xb2:
case 0xb3:
case 0xb4:
case 0xb5:
case 0xb6:
case 0xb7:
case 0xb8:
if (channum != -1)
{
OPL.chan[channum].freq &= 0x00ff;
OPL.chan[channum].freq |= (value & 0x1f) << 8;
if ( (OPL.chan[channum].freqch |= 2) == 3)
opl_setfreq(channum);
opl_keyonoff(channum, (value >> 5) & 1);
break;
}
// else let default: catch it
// this is a channel access, but it belongs to the instrument
// definition, so put it into value [4] of the channel's first operator
// case 0xc0 ... 0xc8:
case 0xc0:
case 0xc1:
case 0xc2:
case 0xc3:
case 0xc4:
case 0xc5:
case 0xc6:
case 0xc7:
case 0xc8:
if (channum != -1)
{
int needchange = 0;
if ((OPL.oper[OPL.chan[channum].opnum[0]][4] & 1) != (value & 1))
needchange = 1;
opl_changeop(channum, OPL.chan[channum].opnum[0], 4, value & 0x3f);
if (needchange == 1)
opl_setmodulation(channum);
break;
}
// else let default: catch it
default:
writelog( MIDILOG(3), "Attempt to write %02x to unknown OPL(%d) register %02x",
value, chipid, index);
break;
}
}
// change a value of an operator
void bx_sb16_c::opl_changeop(int channum, int opernum, int byte, int value)
{
if (OPL.oper[opernum][byte] != value)
{
OPL.oper[opernum][byte] = value;
OPL.chan[channum].needprogch = 1;
}
}
// called for a write to the 4-operator mode register
void bx_sb16_c::opl_set4opmode(int new4opmode)
{
int i, channel1, channel2;
writelog( MIDILOG(4), "Switching to 4-op mode %02x", new4opmode);
// every bit switches a 4-op channel-pairing on or off
// 4-op mode is two channels combined into the first one
for (i = 0; i<6; i++)
{
channel1 = i + (i / 3) * 6;
channel2 = channel1 + 3;
if ( ( (new4opmode >> i) & 1) != 0)
{ // enable 4-op mode
opl_keyonoff(channel1, 0);
opl_keyonoff(channel2, 0);
OPL.chan[channel1].nop = 4;
OPL.chan[channel2].nop = 0;
OPL.chan[channel1].needprogch = 1;
}
else
{ // disable 4-op mode
opl_keyonoff(channel1, 0);
OPL.chan[channel1].nop = 2;
OPL.chan[channel2].nop = 2;
OPL.chan[channel1].needprogch = 1;
OPL.chan[channel2].needprogch = 1;
}
}
}
// 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, 0, 1);
OPL.timer_running = 1;
}
else
{
writelog( MIDILOG(5), "Stopping timers");
bx_pc_system.deactivate_timer( OPL.timer_handle );
OPL.timer_running = 0;
}
}
// called when the modulation mode of a channel changes
void bx_sb16_c::opl_setmodulation(int channel)
{
int opernum = OPL.chan[channel].opnum[0];
if ( (OPL.chan[channel].nop == 0) &&
(channel >= 3) &&
(OPL.chan[channel].nop == 4) )
channel -= 3;
if (OPL.chan[channel].nop == 2)
{
OPL.chan[channel].ncarr = (OPL.oper[opernum][4] & 1) + 1;
OPL.chan[channel].needprogch = 1;
}
else if (OPL.chan[channel].nop == 4)
{
int opernum2 = OPL.chan[channel].opnum[2];
int modmode = (OPL.oper[opernum][4] & 1) |
( (OPL.oper[opernum2][4] & 1) >> 1);
OPL.chan[channel].ncarr = modmode + 1 - (modmode / 2);
OPL.chan[channel].needprogch = 1;
}
}
// called for a write to register 0xbd, the percussion register
void bx_sb16_c::opl_setpercussion(Bit8u value, int chipid)
{
UNUSED(value);
UNUSED(chipid);
}
// called when a channel volume changes
// opnum is which of the channel's operators had the change, not
// the actual operator number. Thus, it's from 0..3.
void bx_sb16_c::opl_setvolume(int channel, int opnum, int outlevel)
{
UNUSED(opnum);
UNUSED(outlevel);
OPL.chan[channel].midivol = 127;
}
// called when a frequency change is complete, to find out the
// corresponding midi key and pitch bender values
void bx_sb16_c::opl_setfreq(int channel)
{
int block,fnum;
OPL.chan[channel].freqch = 0;
// definition:
// low-byte of freq: 8 bit F-Number, LSB's
// high-byte of freq: [2 reserved][KEY-ON][3 block][2 F-Number MSB's]
// [KEY-ON] is ignored by this function
//
// the definition of the F-number is
// F-Number = Frequency * 2**(20-block) / (49716 Hz)
//
// Thus, the frequency can be calculated as
// Frequency = F-Number / 2**(20-block) * 49716 Hz
//
// (But remember that afreq is in 10^-3 Hz!)
//
fnum = OPL.chan[channel].freq & 0x3ff;
block = (OPL.chan[channel].freq >> 10) & 0x07;
writelog( MIDILOG(5), "F-Num is %d, block is %d", fnum, block);
Bit32u realfreq;
const Bit32u freqbase = 49716000; // const is better than #define if type is important
// this is a bit messy to preserve accuracy as much as possible,
// otherwise we might either lose precision, or the higher bits.
if (block < 16)
realfreq = ( (freqbase >> 4) * fnum) >> (16 - block);
else
realfreq = (freqbase * fnum) >> (20 - block);
OPL.chan[channel].afreq = realfreq;
// now find out what MIDI key this corresponds to, and with what
// pitch bender value... (the latter not implemented yet)
int octave=0; // 0: Octave from 523.2511 Hz; pos=higher, neg=lower
int keynum=0; // 0=C; 1=C#; 2=D; ...; 11=B
if (realfreq > 8175) // 8.175 is smallest possible frequency
{
const Bit32u freqC = 523251; // Midi note 72; "C": 523.251 Hz
Bit32u keyfreq; // Frequency scaled to the octave from freqC to 2*freqC
if (realfreq > freqC)
{
while ( (realfreq >> (++octave)) > freqC);
keyfreq = realfreq >> (--octave);
}
else
{
while ( (realfreq << (++octave)) < freqC);
keyfreq = realfreq << (--octave);
octave = -octave;
}
// this is a reasonable approximation for keyfreq /= 1.059463
// (that value is 2**(1/12), which is the difference between two keys)
while ( (keyfreq -= ( (keyfreq * 1000) / 17817) ) > freqC)
keynum++;
}
else
{
octave = -6;
keynum = 0;
}
OPL.chan[channel].midinote = (octave + 6) * 12 + keynum;
writelog( MIDILOG(5), "New frequency %.3f is key %d in octave %d; midi note %d",
(float) realfreq/1000.0, keynum, octave, OPL.chan[channel].midinote);
}
// called when a note is possibly turned on or off
void bx_sb16_c::opl_keyonoff(int channel, Boolean onoff)
{
int i;
// first check if there really is a change in the state
if (onoff == OPL.chan[channel].midion)
return;
Bit8u commandbytes[3];
// check if we have a midi channel, otherwise allocate one if possible
if (OPL.chan[channel].midichan == 0xff)
{
for (i=0; i<16; i++)
if ( ( ( OPL.midichannels >> i) & 1 ) != 0)
{
OPL.chan[channel].midichan = i;
OPL.midichannels &= ~(1 << i); // mark channel as used
OPL.chan[channel].needprogch = 1;
}
if (OPL.chan[channel].midichan == 0xff)
return;
}
if (OPL.chan[channel].needprogch != 0)
opl_midichannelinit(channel);
commandbytes[0] = OPL.chan[channel].midichan;
commandbytes[1] = OPL.chan[channel].midinote;
commandbytes[2] = 0;
if (onoff == 0)
commandbytes[0] |= 0x80; // turn it off
else
{
commandbytes[0] |= 0x90; // turn it on
commandbytes[1] = OPL.chan[channel].midivol;
}
writemidicommand(commandbytes[1], 2, & (commandbytes[1]) );
}
// setup a midi channel
void bx_sb16_c::opl_midichannelinit(int channel)
{
UNUSED(channel);
}
/* Handlers for the midi commands/midi file output */
// Write the header of the midi file. Track length is 0x7fffffff
// until we know how long it's really going to be
void bx_sb16_c::initmidifile()
{
struct {
Bit8u chunk[4];
Bit32u chunklen; // all values in BIG Endian!
Bit16u smftype;
Bit16u tracknum;
Bit16u timecode; // 0x80 + deltatimesperquarter << 8
} midiheader =
#ifdef BX_LITTLE_ENDIAN
{ "MTh", 0x06000000, 0x0100, 0x0100, 0x8001 };
#else
{ "MTh", 6, 1, 1, 0x180 };
#endif
midiheader.chunk[3] = 'k';
struct {
Bit8u chunk[4];
Bit32u chunklen;
Bit8u data[15];
} trackheader =
#ifdef BX_LITTLE_ENDIAN
{ "MTr", 0xffffff7f,
#else
{ "MTr", 0x7fffffff,
#endif
{ 0x00,0xff,0x51,3,0x07,0xa1,0x20, // set tempo 120 (0x7a120 us per quarter)
0x00,0xff,0x58,4,4,2,0x18,0x08 }}; // time sig 4/4
trackheader.chunk[3] = 'k';
fwrite(&midiheader, 1, 14, MIDIDATA );
fwrite(&trackheader, 1, 23, MIDIDATA );
return;
}
// write the midi command to the midi file
void bx_sb16_c::writemidicommand(int command, int length, Bit8u data[])
{
/* We need to determine the time elapsed since the last MIDI command */
int deltatime = currentdeltatime();
/* Initialize output device if necessary and not done yet */
if (bx_options.sb16.midimode == 1)
{
if (MPU.outputinit != 1)
{
writelog( MIDILOG(4), "Initializing Midi output.");
if (BX_SB16_OUTPUT->openmidioutput(bx_options.sb16.midifile) == BX_SOUND_OUTPUT_OK)
MPU.outputinit = 1;
else
MPU.outputinit = 0;
if (MPU.outputinit != 1)
{
writelog( MIDILOG(2), "Error: Couldn't open midi output. Midi disabled.");
bx_options.sb16.midimode = 0;
}
}
BX_SB16_OUTPUT->sendmidicommand(deltatime, command, length, data);
return;
}
else if (bx_options.sb16.midimode < 2)
return;
if (bx_options.sb16.midimode == 2)
writedeltatime(deltatime);
fputc(command, MIDIDATA);
if ( (command == 0xf0) ||
(command == 0xf7) ) // write event length for sysex/meta events
writedeltatime(length);
fwrite(data, 1, length, MIDIDATA);
}
// 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(Boolean force)
{
int i, channel;
Bit8u value;
Boolean 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;
// and trigger IRQ?
// MPU.irqpending = 1;
// BX_SB16_THIS devices->pic->trigger_irq(BX_SB16_IRQMPU);
}
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);
}
}
}
}
// convert a number into a delta time coded value
int bx_sb16_c::converttodeltatime(Bit32u deltatime, Bit8u value[4])
{
int i, count;
Bit8u outbytes[4];
count = 0;
if (deltatime <= 0)
{
count = 1;
value[0] = 0;
}
else
{
while ( (deltatime > 0) && (count < 4) ) // split into parts
{ // of seven bits
outbytes[count++] = deltatime & 0x7f;
deltatime >>= 7;
};
for (i=0; i<count; i++) // reverse order and
value[i] = outbytes[count - i - 1] | 0x80; // set eighth bit on
value[count - 1] &= 0x7f; // all but last byte
}
return count;
}
// write a delta time coded value to the midi file
void bx_sb16_c::writedeltatime(Bit32u deltatime)
{
Bit8u outbytes[4];
int count,i;
count = converttodeltatime(deltatime, outbytes);
for (i=0; i<count; i++)
fputc(outbytes[i], MIDIDATA );
}
// close the midi file, and set the track length accordingly
void bx_sb16_c::finishmidifile()
{
fpos_t tracklen;
struct {
Bit8u delta, statusbyte, metaevent, length;
} metatrackend = { 0, 0xff, 0x2f, 0 };
// Meta event track end (0xff 0x2f 0x00) plus leading delta time
fwrite(&metatrackend, 1, sizeof metatrackend, MIDIDATA );
fgetpos(MIDIDATA, &tracklen);
tracklen -= 22; // subtract the midi file and track header
fseek(MIDIDATA, 22 - 4, SEEK_SET);
// value has to be in big endian
#ifdef BX_LITTLE_ENDIAN
tracklen = (tracklen << 24) | (tracklen >> 24) |
((tracklen & 0x00ff0000) >> 8) |
((tracklen & 0x0000ff00) << 8);
#endif
fwrite(&tracklen, 4, 1, MIDIDATA);
return;
}
/* Handlers for the voc file output */
// Write the header of the voc file.
void bx_sb16_c::initvocfile()
{
struct {
char id[20];
Bit16u headerlen; // All in LITTLE Endian!
Bit16u version;
Bit16u chksum;
} vocheader =
{ "Creative Voice File",
#ifdef BX_LITTLE_ENDIAN
0x1a, 0x0114, 0x111f };
#else
0x1a00, 0x1401, 0x1f11 };
#endif
vocheader.id[19] = 26; // Replace string end with 26
fwrite(&vocheader, 1, sizeof vocheader, WAVEDATA);
}
// write one block to the voc file
void bx_sb16_c::writevocblock(int block,
Bit32u headerlen, Bit8u header[],
Bit32u datalen, Bit8u data[])
{
Bit32u i;
if (block > 9)
{
writelog( WAVELOG(3), "VOC Block %d not recognized, ignored.", block);
return;
}
fputc(block, WAVEDATA);
i = headerlen + datalen;
#ifdef BX_LITTLE_ENDIAN
fwrite(&i, 1, 3, WAVEDATA); // write the length in 24-bit little endian
#else
Bit8u lengthbytes[3];
lengthbytes[0] = i & 0xff; i >>= 8;
lengthbytes[1] = i & 0xff; i >>= 8;
lengthbytes[2] = i & 0xff;
fwrite(lengthbytes, 1, 3, WAVEDATA);
#endif
writelog( WAVELOG(5), "Voc block %d; Headerlen %d; Datalen %d",
block, headerlen, datalen );
if (headerlen > 0)
fwrite(header, 1, headerlen, WAVEDATA);
if (datalen > 0)
fwrite(data, 1, datalen, WAVEDATA);
}
// close the voc file
void bx_sb16_c::finishvocfile()
{
fputc(0, WAVEDATA); // blocktype 0: end block
}
// 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
// we support only byte access to the port
if (io_len != 1)
{
writelog(3, "Read access to %03x not byte access, len=%d!",
address, io_len);
return(0xff);
}
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 %03x for %d: unsupported port!", address, io_len);
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
// we only support byte access to the prot
if (io_len != 1)
{
writelog(3, "Write access to %03x for %d to %02x: "
"not byte access!", address, io_len, value);
return;
}
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(value, 0);
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);
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(value, 1);
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);
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 %03x for %d to %02x: unsupported port!",
address, io_len, value);
return;
}
void bx_sb16_c::writelog(int loglevel, const char *str, ...)
{
// append a line to the log file, if desired
if ( (int) bx_options.sb16.loglevel >= loglevel)
{
time_t timep = time(NULL);
tm *t = localtime(&timep);
BX_INFO(( "SB16 %02d:%02d:%02d (%i): ",
t->tm_hour, t->tm_min, t->tm_sec, loglevel ));
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(void)
{
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
Boolean 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
Boolean bx_sb16_buffer::puts(char *data, ...)
{
if (data == NULL)
return 0; // invalid string
//char string[length];
char *string;
int index = 0;
string = (char *) malloc(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!\n"));
while (string[index] != 0)
{
if (put( (Bit8u) string[index]) == 0)
return 0; // buffer full
index++;
}
return 1;
}
// This returns if the buffer is full, i.e. if a put will fail
Boolean 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
Boolean 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
Boolean 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
Boolean 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
Boolean 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
Boolean 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
Boolean bx_sb16_buffer::hascommand(void)
{
return havecommand;
}
int bx_sb16_buffer::commandbytes(void)
{
return bytesneeded;
}
// The dummy output functions. They don't do anything
bx_sound_output_c::bx_sound_output_c(bx_sb16_c *sb16)
{
UNUSED(sb16);
}
bx_sound_output_c::~bx_sound_output_c()
{
}
int bx_sound_output_c::waveready()
{
return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::midiready()
{
return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::openmidioutput(char *device)
{
UNUSED(device);
return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::sendmidicommand(int delta, int command, int length, Bit8u data[])
{
UNUSED(delta);
UNUSED(command);
UNUSED(length);
UNUSED(data);
return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::closemidioutput()
{
return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::openwaveoutput(char *device)
{
UNUSED(device);
return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::startwaveplayback(int frequency, int bits, int stereo, int format)
{
UNUSED(frequency);
UNUSED(bits);
UNUSED(stereo);
UNUSED(format);
return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::sendwavepacket(int length, Bit8u data[])
{
UNUSED(length);
UNUSED(data);
return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::stopwaveplayback()
{
return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::closewaveoutput()
{
return BX_SOUND_OUTPUT_OK;
}