2001-10-03 17:10:38 +04:00
|
|
|
/////////////////////////////////////////////////////////////////////////
|
2002-10-06 23:04:47 +04:00
|
|
|
// $Id: sb16.cc,v 1.24 2002-10-06 19:04:47 bdenney Exp $
|
2001-10-03 17:10:38 +04:00
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
2002-01-13 20:07:14 +03:00
|
|
|
// Copyright (C) 2002 MandrakeSoft S.A.
|
2001-04-10 05:04:59 +04:00
|
|
|
//
|
|
|
|
// 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"
|
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 18:49:57 +04:00
|
|
|
#define LOG_THIS bx_sb16.
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
// some shortcuts to save typing
|
|
|
|
#define LOGFILE BX_SB16_THIS logfile
|
|
|
|
#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
|
|
|
|
|
2001-05-18 00:58:31 +04:00
|
|
|
// here's a safe way to print out null pointeres
|
|
|
|
#define MIGHT_BE_NULL(x) ((x==NULL)? "(null)" : x)
|
|
|
|
|
2001-04-10 05:04:59 +04:00
|
|
|
bx_sb16_c bx_sb16;
|
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 18:49:57 +04:00
|
|
|
#if BX_USE_SB16_SMF
|
|
|
|
#define this ((void *)&bx_sb16)
|
2001-04-10 05:04:59 +04:00
|
|
|
#endif
|
|
|
|
|
|
|
|
bx_sb16_c::bx_sb16_c(void)
|
|
|
|
{
|
2001-06-27 23:16:01 +04:00
|
|
|
put("SB16");
|
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 18:49:57 +04:00
|
|
|
settype(SB16LOG);
|
2002-10-06 23:04:47 +04:00
|
|
|
MPU.timer_handle = BX_NULL_TIMER_HANDLE;
|
|
|
|
DSP.timer_handle = BX_NULL_TIMER_HANDLE;
|
|
|
|
OPL.timer_handle = BX_NULL_TIMER_HANDLE;
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
bx_sb16_c::~bx_sb16_c(void)
|
|
|
|
{
|
2002-08-23 22:12:02 +04:00
|
|
|
if (!bx_options.sb16.Opresent->get ())
|
|
|
|
return;
|
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
switch (bx_options.sb16.Omidimode->get ())
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
|
|
|
case 2:
|
2002-08-23 22:12:02 +04:00
|
|
|
if (MIDIDATA != NULL)
|
|
|
|
finishmidifile();
|
2001-04-10 05:04:59 +04:00
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
if (MPU.outputinit != 0)
|
2002-08-23 22:12:02 +04:00
|
|
|
BX_SB16_OUTPUT->closemidioutput();
|
2001-04-10 05:04:59 +04:00
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
if (MIDIDATA != NULL)
|
2002-08-23 22:12:02 +04:00
|
|
|
fclose(MIDIDATA);
|
2001-04-10 05:04:59 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
switch (bx_options.sb16.Owavemode->get ())
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
|
|
|
case 2:
|
2002-08-23 22:12:02 +04:00
|
|
|
if (WAVEDATA != NULL)
|
|
|
|
finishvocfile();
|
2001-04-10 05:04:59 +04:00
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
if (DSP.outputinit != 0)
|
2002-08-23 22:12:02 +04:00
|
|
|
BX_SB16_OUTPUT->closewaveoutput();
|
2001-04-10 05:04:59 +04:00
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
if (WAVEDATA != NULL)
|
2002-08-23 22:12:02 +04:00
|
|
|
fclose(WAVEDATA);
|
2001-04-10 05:04:59 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(BX_SB16_OUTPUT);
|
|
|
|
|
2002-04-10 00:12:39 +04:00
|
|
|
delete [] DSP.dma.chunk;
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
if ((bx_options.sb16.Ologlevel->get () > 0) && LOGFILE)
|
2001-04-10 05:04:59 +04:00
|
|
|
fclose(LOGFILE);
|
|
|
|
}
|
|
|
|
|
|
|
|
void bx_sb16_c::init(bx_devices_c *d)
|
|
|
|
{
|
|
|
|
BX_SB16_THIS devices = d;
|
|
|
|
unsigned addr;
|
|
|
|
|
2002-08-23 22:12:02 +04:00
|
|
|
if (!bx_options.sb16.Opresent->get ())
|
|
|
|
return;
|
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
if ( (strlen(bx_options.sb16.Ologfile->getptr ()) < 1) )
|
|
|
|
bx_options.sb16.Ologlevel->set (0);
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
if (bx_options.sb16.Ologlevel->get () > 0)
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
2001-06-21 22:34:50 +04:00
|
|
|
LOGFILE = fopen(bx_options.sb16.Ologfile->getptr (),"w"); // logfile for errors etc.
|
2001-04-10 05:04:59 +04:00
|
|
|
if (LOGFILE == NULL)
|
|
|
|
{
|
2001-06-21 22:34:50 +04:00
|
|
|
BX_ERROR(("Error opening file %s. Logging disabled.", bx_options.sb16.Ologfile->getptr ()));
|
|
|
|
bx_options.sb16.Ologlevel->set (0);
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.");
|
2001-06-21 22:34:50 +04:00
|
|
|
bx_options.sb16.Omidimode->set (0);
|
|
|
|
bx_options.sb16.Owavemode->set (0);
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
if ( (bx_options.sb16.Omidimode->get () == 2) ||
|
|
|
|
(bx_options.sb16.Omidimode->get () == 3) )
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
2001-12-22 16:30:10 +03:00
|
|
|
MIDIDATA = fopen(bx_options.sb16.Omidifile->getptr (),"wb");
|
2001-04-10 05:04:59 +04:00
|
|
|
if (MIDIDATA == NULL)
|
|
|
|
{
|
2001-06-21 22:34:50 +04:00
|
|
|
writelog (MIDILOG(2), "Error opening file %s. Midimode disabled.", bx_options.sb16.Omidifile->getptr ());
|
|
|
|
bx_options.sb16.Omidimode->set (0);
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
2001-06-21 22:34:50 +04:00
|
|
|
else if (bx_options.sb16.Omidimode->get () == 2)
|
2001-04-10 05:04:59 +04:00
|
|
|
initmidifile();
|
|
|
|
}
|
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
if ( (bx_options.sb16.Owavemode->get () == 2) ||
|
|
|
|
(bx_options.sb16.Owavemode->get () == 3) )
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
2001-12-22 16:30:10 +03:00
|
|
|
WAVEDATA = fopen(bx_options.sb16.Owavefile->getptr (),"wb");
|
2001-04-10 05:04:59 +04:00
|
|
|
if (WAVEDATA == NULL)
|
|
|
|
{
|
2001-06-21 22:34:50 +04:00
|
|
|
writelog (WAVELOG(2), "Error opening file %s. Wavemode disabled.", bx_options.sb16.Owavefile);
|
|
|
|
bx_options.sb16.Owavemode->set (0);
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
2001-06-21 22:34:50 +04:00
|
|
|
else if (bx_options.sb16.Owavemode->get () == 2)
|
2001-04-10 05:04:59 +04:00
|
|
|
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.");
|
2001-06-21 22:34:50 +04:00
|
|
|
bx_options.sb16.Owavemode->set (0);
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_INFO(("midi=%d,%s wave=%d,%s log=%d,%s dmatimer=%d",
|
2001-06-21 22:34:50 +04:00
|
|
|
bx_options.sb16.Omidimode->get (), MIGHT_BE_NULL(bx_options.sb16.Omidifile->getptr ()),
|
|
|
|
bx_options.sb16.Owavemode->get (), MIGHT_BE_NULL(bx_options.sb16.Owavefile->getptr ()),
|
|
|
|
bx_options.sb16.Ologlevel->get (), MIGHT_BE_NULL(bx_options.sb16.Ologfile->getptr ()),
|
|
|
|
bx_options.sb16.Odmatimer->get ()));
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2002-01-13 20:07:14 +03:00
|
|
|
MIXER.reg[0x80] = 2; // IRQ 5
|
|
|
|
MIXER.reg[0x81] = 2; // 8-bit DMA 1, no 16-bit DMA
|
|
|
|
MIXER.reg[0x82] = 0; // no IRQ pending
|
|
|
|
set_irq_dma(); // set the IRQ and DMA
|
|
|
|
|
2001-04-10 05:04:59 +04:00
|
|
|
// 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,
|
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 18:49:57 +04:00
|
|
|
&read_handler, addr, "SB16");
|
2001-04-10 05:04:59 +04:00
|
|
|
BX_SB16_THIS devices->register_io_write_handler(this,
|
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 18:49:57 +04:00
|
|
|
&write_handler, addr, "SB16");
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
for (addr=BX_SB16_IOMPU; addr<BX_SB16_IOMPU+BX_SB16_IOMPULEN; addr++) {
|
|
|
|
BX_SB16_THIS devices->register_io_read_handler(this,
|
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 18:49:57 +04:00
|
|
|
&read_handler, addr, "SB16");
|
2001-04-10 05:04:59 +04:00
|
|
|
BX_SB16_THIS devices->register_io_write_handler(this,
|
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 18:49:57 +04:00
|
|
|
&write_handler, addr, "SB16");
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
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
|
2002-10-06 23:04:47 +04:00
|
|
|
if (MPU.timer_handle == BX_NULL_TIMER_HANDLE) {
|
|
|
|
MPU.timer_handle = bx_pc_system.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 = bx_pc_system.register_timer
|
|
|
|
(BX_SB16_THISP, dsp_dmatimer, 1, 1, 0, "sb16.dsp");
|
|
|
|
// dma timer: inactive, continous, frequency variable
|
|
|
|
}
|
|
|
|
|
|
|
|
if (OPL.timer_handle == BX_NULL_TIMER_HANDLE) {
|
|
|
|
OPL.timer_handle = bx_pc_system.register_timer
|
|
|
|
(BX_SB16_THISP, opl_timer, 80, 1, 0, "sb16.opl");
|
|
|
|
// opl timer: inactive, continuous, frequency 80us
|
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
writelog(MIDILOG(4), "Timers initialized, midi %d, dma %d, opl %d",
|
|
|
|
MPU.timer_handle, DSP.timer_handle, OPL.timer_handle );
|
|
|
|
MPU.current_timer = 0;
|
|
|
|
}
|
|
|
|
|
2002-08-27 23:54:46 +04:00
|
|
|
void bx_sb16_c::reset(unsigned type)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2001-04-10 05:04:59 +04:00
|
|
|
// 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.
|
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
if ( (bx_options.sb16.Owavemode->get () != 1) ||
|
2001-04-10 05:04:59 +04:00
|
|
|
( (This->dsp.dma.chunkindex + 1 < BX_SOUND_OUTPUT_WAVEPACKETSIZE) &&
|
|
|
|
(This->dsp.dma.count > 0) ) ||
|
2002-01-05 13:30:24 +03:00
|
|
|
(This->output->waveready() == BX_SOUND_OUTPUT_OK) ) {
|
|
|
|
if (DSP.dma.bits == 8)
|
2002-06-16 19:02:28 +04:00
|
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAL, 1);
|
2002-01-05 13:30:24 +03:00
|
|
|
else
|
2002-06-16 19:02:28 +04:00
|
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAH, 1);
|
2002-01-05 13:30:24 +03:00
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2002-01-29 20:20:12 +03:00
|
|
|
BX_SB16_THIS devices->pic->lower_irq(BX_SB16_IRQ);
|
2001-04-10 05:04:59 +04:00
|
|
|
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
|
2001-06-21 22:34:50 +04:00
|
|
|
if (bx_options.sb16.Owavemode->get () == 2)
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
|
|
|
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
|
2002-01-29 20:20:12 +03:00
|
|
|
BX_SB16_THIS devices->pic->raise_irq(BX_SB16_IRQ);
|
2001-04-10 05:04:59 +04:00
|
|
|
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;
|
2001-06-21 22:34:50 +04:00
|
|
|
DSP.dma.timer = (Bit32u) bx_options.sb16.Odmatimer->get () / sampledatarate;
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2001-06-21 22:34:50 +04:00
|
|
|
if (bx_options.sb16.Owavemode->get () == 1)
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
|
|
|
if (DSP.outputinit == 0)
|
|
|
|
{
|
2001-06-21 22:34:50 +04:00
|
|
|
if (BX_SB16_OUTPUT->openwaveoutput(bx_options.sb16.Owavefile->getptr ()) != BX_SOUND_OUTPUT_OK)
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
2001-06-21 22:34:50 +04:00
|
|
|
bx_options.sb16.Owavemode->set (0);
|
2001-04-10 05:04:59 +04:00
|
|
|
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 )
|
|
|
|
{
|
|
|
|
MIXER.reg[0x82] &= (~0x01);
|
|
|
|
writelog( WAVELOG(4), "8-bit DMA or SBMIDI IRQ acknowledged");
|
2002-01-25 23:31:42 +03:00
|
|
|
if (MIXER.reg[0x82] == 0) {
|
|
|
|
DSP.irqpending = 0;
|
2002-01-29 20:20:12 +03:00
|
|
|
BX_SB16_THIS devices->pic->lower_irq(BX_SB16_IRQ);
|
2002-01-25 23:31:42 +03:00
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
2002-01-25 23:31:42 +03:00
|
|
|
if (MIXER.reg[0x82] == 0) {
|
|
|
|
DSP.irqpending = 0;
|
2002-01-29 20:20:12 +03:00
|
|
|
BX_SB16_THIS devices->pic->lower_irq(BX_SB16_IRQ);
|
2002-01-25 23:31:42 +03:00
|
|
|
}
|
2001-04-10 05:04:59 +04:00
|
|
|
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()
|
|
|
|
{
|
2001-06-21 22:34:50 +04:00
|
|
|
switch (bx_options.sb16.Owavemode->get ())
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
|
|
|
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
|
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
if (bx_options.sb16.Owavemode->get () == 1)
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
|
|
|
if (DSP.dma.mode != 2)
|
|
|
|
BX_SB16_OUTPUT->stopwaveplayback(); // don't stop if Auto-DMA
|
|
|
|
}
|
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
else if (bx_options.sb16.Owavemode->get () == 2)
|
2001-04-10 05:04:59 +04:00
|
|
|
fflush(WAVEDATA);
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate the appropriate IRQ
|
|
|
|
if (DSP.dma.bits == 8)
|
|
|
|
MIXER.reg[0x82] |= 1;
|
|
|
|
else
|
|
|
|
MIXER.reg[0x82] |= 2;
|
|
|
|
|
2002-01-29 20:20:12 +03:00
|
|
|
BX_SB16_THIS devices->pic->raise_irq(BX_SB16_IRQ);
|
2001-04-10 05:04:59 +04:00
|
|
|
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)
|
|
|
|
{
|
2002-06-16 19:02:28 +04:00
|
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAL, 0); // the timer will raise it again
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2002-06-16 19:02:28 +04:00
|
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAL, 0); // the timer will raise it again
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2002-06-16 19:02:28 +04:00
|
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAH, 0); // the timer will raise it again
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2002-01-05 13:30:24 +03:00
|
|
|
DSP.dma.count--;
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2002-06-16 19:02:28 +04:00
|
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAH, 0); // the timer will raise it again
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2002-01-05 13:30:24 +03:00
|
|
|
DSP.dma.count--;
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
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...");
|
2002-01-13 20:07:14 +03:00
|
|
|
for (i=0; i<0x80; i++)
|
2001-04-10 05:04:59 +04:00
|
|
|
MIXER.reg[i] = 0;
|
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
2002-06-16 19:02:28 +04:00
|
|
|
static Boolean isInitialized=0;
|
2001-04-10 05:04:59 +04:00
|
|
|
int newirq;
|
2002-06-16 19:02:28 +04:00
|
|
|
int oldDMA8, oldDMA16;
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
// 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
|
2002-06-16 19:02:28 +04:00
|
|
|
oldDMA8=BX_SB16_DMAL;
|
2001-04-10 05:04:59 +04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2002-06-16 19:02:28 +04:00
|
|
|
// Unregister the previous DMA if initialized
|
|
|
|
if ( (isInitialized) && (oldDMA8 != BX_SB16_DMAL) ) {
|
|
|
|
BX_UNREGISTER_DMA_CHANNEL(oldDMA8);
|
|
|
|
}
|
|
|
|
|
|
|
|
// And register the new 8bits DMA Channel
|
|
|
|
if ( (!isInitialized) || (oldDMA8 != BX_SB16_DMAL) ) {
|
|
|
|
BX_REGISTER_DMA8_CHANNEL(BX_SB16_DMAL, bx_sb16.dma_read8, bx_sb16.dma_write8, "SB16");
|
|
|
|
}
|
|
|
|
|
2001-04-10 05:04:59 +04:00
|
|
|
// and the 16 bit DMA
|
2002-06-16 19:02:28 +04:00
|
|
|
oldDMA16=BX_SB16_DMAH;
|
2001-04-10 05:04:59 +04:00
|
|
|
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!
|
|
|
|
}
|
|
|
|
|
2002-06-16 19:02:28 +04:00
|
|
|
// Unregister the previous DMA if initialized
|
|
|
|
if ( (isInitialized) && (oldDMA16 != 0) && (oldDMA16 != BX_SB16_DMAH) ) {
|
|
|
|
BX_UNREGISTER_DMA_CHANNEL(oldDMA16);
|
|
|
|
}
|
|
|
|
|
|
|
|
// And register the new 16bits DMA Channel
|
|
|
|
if ( (BX_SB16_DMAH != 0) && (oldDMA16 != BX_SB16_DMAH) ) {
|
|
|
|
BX_REGISTER_DMA16_CHANNEL(BX_SB16_DMAH, bx_sb16.dma_read16, bx_sb16.dma_write16, "SB16");
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not already initialized
|
|
|
|
if(!isInitialized) {
|
|
|
|
isInitialized=1;
|
|
|
|
}
|
|
|
|
|
2001-04-10 05:04:59 +04:00
|
|
|
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) ||
|
2001-06-21 22:34:50 +04:00
|
|
|
( (bx_options.sb16.Omidimode->get () == 1) &&
|
2001-04-10 05:04:59 +04:00
|
|
|
(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;
|
2002-01-29 20:20:12 +03:00
|
|
|
BX_SB16_THIS devices->pic->raise_irq(BX_SB16_IRQMPU);
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
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)
|
2002-01-29 20:20:12 +03:00
|
|
|
BX_SB16_THIS devices->pic->lower_irq(BX_SB16_IRQMPU);
|
2001-04-10 05:04:59 +04:00
|
|
|
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
|
2001-06-21 22:34:50 +04:00
|
|
|
if ( (bx_options.sb16.Omidimode->get () == 2) ||
|
|
|
|
(bx_options.sb16.Omidimode->get () == 3) )
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
2001-06-21 22:34:50 +04:00
|
|
|
if (bx_options.sb16.Omidimode->get () == 2)
|
2001-04-10 05:04:59 +04:00
|
|
|
finishmidifile();
|
|
|
|
fclose(MIDIDATA);
|
|
|
|
}
|
2001-06-21 22:34:50 +04:00
|
|
|
else if (bx_options.sb16.Omidimode->get () == 1)
|
2001-04-10 05:04:59 +04:00
|
|
|
BX_SB16_OUTPUT->closemidioutput();
|
2001-06-21 22:34:50 +04:00
|
|
|
bx_options.sb16.Omidimode->set (0);
|
2001-04-10 05:04:59 +04:00
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
if ( (bx_options.sb16.Owavemode->get () == 2) ||
|
|
|
|
(bx_options.sb16.Owavemode->get () == 3) )
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
2001-06-21 22:34:50 +04:00
|
|
|
if (bx_options.sb16.Owavemode->get () == 2)
|
2001-04-10 05:04:59 +04:00
|
|
|
finishvocfile();
|
|
|
|
fclose(WAVEDATA);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
BX_SB16_OUTPUT->closewaveoutput();
|
2001-06-21 22:34:50 +04:00
|
|
|
bx_options.sb16.Owavemode->set (0);
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
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;
|
2001-12-22 16:30:10 +03:00
|
|
|
if ((OPL.oper[OPL.chan[channum].opnum[0]][4] & 1) != (int)(value & 1))
|
2001-04-10 05:04:59 +04:00
|
|
|
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
|
2001-09-15 17:33:16 +04:00
|
|
|
{ "MTh", 0x06000000, 0, 0x0100, 0x8001 };
|
2001-04-10 05:04:59 +04:00
|
|
|
#else
|
2001-09-15 17:33:16 +04:00
|
|
|
{ "MTh", 6, 0, 1, 0x180 };
|
2001-04-10 05:04:59 +04:00
|
|
|
#endif
|
2001-09-15 17:33:16 +04:00
|
|
|
midiheader.chunk[3] = 'd';
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
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 */
|
2001-06-21 22:34:50 +04:00
|
|
|
if (bx_options.sb16.Omidimode->get () == 1)
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
|
|
|
if (MPU.outputinit != 1)
|
|
|
|
{
|
|
|
|
writelog( MIDILOG(4), "Initializing Midi output.");
|
2001-06-21 22:34:50 +04:00
|
|
|
if (BX_SB16_OUTPUT->openmidioutput(bx_options.sb16.Omidifile->getptr ()) == BX_SOUND_OUTPUT_OK)
|
2001-04-10 05:04:59 +04:00
|
|
|
MPU.outputinit = 1;
|
|
|
|
else
|
|
|
|
MPU.outputinit = 0;
|
|
|
|
if (MPU.outputinit != 1)
|
|
|
|
{
|
|
|
|
writelog( MIDILOG(2), "Error: Couldn't open midi output. Midi disabled.");
|
2001-06-21 22:34:50 +04:00
|
|
|
bx_options.sb16.Omidimode->set (0);
|
2001-04-10 05:04:59 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
BX_SB16_OUTPUT->sendmidicommand(deltatime, command, length, data);
|
|
|
|
return;
|
|
|
|
}
|
2001-06-21 22:34:50 +04:00
|
|
|
else if (bx_options.sb16.Omidimode->get () < 2)
|
2001-04-10 05:04:59 +04:00
|
|
|
return;
|
|
|
|
|
2001-06-21 22:34:50 +04:00
|
|
|
if (bx_options.sb16.Omidimode->get () == 2)
|
2001-04-10 05:04:59 +04:00
|
|
|
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()
|
|
|
|
{
|
|
|
|
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 );
|
|
|
|
|
2001-06-09 05:30:20 +04:00
|
|
|
Bit32u tracklen = ftell(MIDIDATA);
|
|
|
|
if (tracklen < 0)
|
|
|
|
BX_PANIC (("ftell failed in finishmidifile"));
|
|
|
|
if (tracklen < 22)
|
|
|
|
BX_PANIC (("finishmidifile with track length too short"));
|
2001-04-10 05:04:59 +04:00
|
|
|
tracklen -= 22; // subtract the midi file and track header
|
|
|
|
fseek(MIDIDATA, 22 - 4, SEEK_SET);
|
2001-06-09 05:30:20 +04:00
|
|
|
// value has to be in big endian
|
2001-04-10 05:04:59 +04:00
|
|
|
#ifdef BX_LITTLE_ENDIAN
|
|
|
|
tracklen = (tracklen << 24) | (tracklen >> 24) |
|
2001-06-09 05:30:20 +04:00
|
|
|
((tracklen & 0x00ff0000) >> 8) |
|
|
|
|
((tracklen & 0x0000ff00) << 8);
|
2001-04-10 05:04:59 +04:00
|
|
|
#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
|
2001-06-21 22:34:50 +04:00
|
|
|
if ( (int) bx_options.sb16.Ologlevel->get () >= loglevel)
|
2001-04-10 05:04:59 +04:00
|
|
|
{
|
2002-01-25 23:31:42 +03:00
|
|
|
fprintf(LOGFILE, "%011lld (%d) ", bx_pc_system.time_ticks(), loglevel);
|
2001-04-10 05:04:59 +04:00
|
|
|
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)
|
2002-04-10 00:12:39 +04:00
|
|
|
delete [] buffer;
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
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)
|
2001-05-30 22:56:02 +04:00
|
|
|
BX_PANIC(("bx_sb16_buffer: puts() too long!"));
|
2001-04-10 05:04:59 +04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|