984905c73a
decided to make bx_param_num_c::get() return 32bit integers again instead of trying to find every single case that was broken when I changed bx_param_num_c::get() to return a 64bit integer. - as soon as get() returns 32 bit values again, the changes in the previous rev is are unnecessary.
3504 lines
86 KiB
C++
3504 lines
86 KiB
C++
/////////////////////////////////////////////////////////////////////////
|
|
// $Id: sb16.cc,v 1.26 2002-10-21 00:59:17 bdenney Exp $
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2002 MandrakeSoft S.A.
|
|
//
|
|
// MandrakeSoft S.A.
|
|
// 43, rue d'Aboukir
|
|
// 75002 Paris - France
|
|
// http://www.linux-mandrake.com/
|
|
// http://www.mandrakesoft.com/
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
// This file (SB16.CC) written and donated by Josef Drexler
|
|
|
|
|
|
#include "bochs.h"
|
|
#define LOG_THIS bx_sb16.
|
|
|
|
// some shortcuts to save typing
|
|
#define LOGFILE BX_SB16_THIS logfile
|
|
#define 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
|
|
|
|
// here's a safe way to print out null pointeres
|
|
#define MIGHT_BE_NULL(x) ((x==NULL)? "(null)" : x)
|
|
|
|
bx_sb16_c bx_sb16;
|
|
#if BX_USE_SB16_SMF
|
|
#define this ((void *)&bx_sb16)
|
|
#endif
|
|
|
|
bx_sb16_c::bx_sb16_c(void)
|
|
{
|
|
put("SB16");
|
|
settype(SB16LOG);
|
|
MPU.timer_handle = BX_NULL_TIMER_HANDLE;
|
|
DSP.timer_handle = BX_NULL_TIMER_HANDLE;
|
|
OPL.timer_handle = BX_NULL_TIMER_HANDLE;
|
|
}
|
|
|
|
bx_sb16_c::~bx_sb16_c(void)
|
|
{
|
|
if (!bx_options.sb16.Opresent->get ())
|
|
return;
|
|
|
|
switch (bx_options.sb16.Omidimode->get ())
|
|
{
|
|
case 2:
|
|
if (MIDIDATA != NULL)
|
|
finishmidifile();
|
|
break;
|
|
case 1:
|
|
if (MPU.outputinit != 0)
|
|
BX_SB16_OUTPUT->closemidioutput();
|
|
break;
|
|
case 3:
|
|
if (MIDIDATA != NULL)
|
|
fclose(MIDIDATA);
|
|
break;
|
|
}
|
|
|
|
switch (bx_options.sb16.Owavemode->get ())
|
|
{
|
|
case 2:
|
|
if (WAVEDATA != NULL)
|
|
finishvocfile();
|
|
break;
|
|
case 1:
|
|
if (DSP.outputinit != 0)
|
|
BX_SB16_OUTPUT->closewaveoutput();
|
|
break;
|
|
case 3:
|
|
if (WAVEDATA != NULL)
|
|
fclose(WAVEDATA);
|
|
break;
|
|
}
|
|
|
|
delete(BX_SB16_OUTPUT);
|
|
|
|
delete [] DSP.dma.chunk;
|
|
|
|
if ((bx_options.sb16.Ologlevel->get () > 0) && LOGFILE)
|
|
fclose(LOGFILE);
|
|
}
|
|
|
|
void bx_sb16_c::init(bx_devices_c *d)
|
|
{
|
|
BX_SB16_THIS devices = d;
|
|
unsigned addr;
|
|
|
|
if (!bx_options.sb16.Opresent->get ())
|
|
return;
|
|
|
|
if ( (strlen(bx_options.sb16.Ologfile->getptr ()) < 1) )
|
|
bx_options.sb16.Ologlevel->set (0);
|
|
|
|
if (bx_options.sb16.Ologlevel->get () > 0)
|
|
{
|
|
LOGFILE = fopen(bx_options.sb16.Ologfile->getptr (),"w"); // logfile for errors etc.
|
|
if (LOGFILE == NULL)
|
|
{
|
|
BX_ERROR(("Error opening file %s. Logging disabled.", bx_options.sb16.Ologfile->getptr ()));
|
|
bx_options.sb16.Ologlevel->set (0);
|
|
}
|
|
}
|
|
|
|
// let the output functions initialize
|
|
BX_SB16_OUTPUT = new BX_SOUND_OUTPUT_C(BX_SB16_THISP);
|
|
|
|
if (BX_SB16_OUTPUT == NULL)
|
|
{
|
|
writelog( MIDILOG(2), "Couldn't initialize output devices. Output disabled.");
|
|
bx_options.sb16.Omidimode->set (0);
|
|
bx_options.sb16.Owavemode->set (0);
|
|
}
|
|
|
|
if ( (bx_options.sb16.Omidimode->get () == 2) ||
|
|
(bx_options.sb16.Omidimode->get () == 3) )
|
|
{
|
|
MIDIDATA = fopen(bx_options.sb16.Omidifile->getptr (),"wb");
|
|
if (MIDIDATA == NULL)
|
|
{
|
|
writelog (MIDILOG(2), "Error opening file %s. Midimode disabled.", bx_options.sb16.Omidifile->getptr ());
|
|
bx_options.sb16.Omidimode->set (0);
|
|
}
|
|
else if (bx_options.sb16.Omidimode->get () == 2)
|
|
initmidifile();
|
|
}
|
|
|
|
if ( (bx_options.sb16.Owavemode->get () == 2) ||
|
|
(bx_options.sb16.Owavemode->get () == 3) )
|
|
{
|
|
WAVEDATA = fopen(bx_options.sb16.Owavefile->getptr (),"wb");
|
|
if (WAVEDATA == NULL)
|
|
{
|
|
writelog (WAVELOG(2), "Error opening file %s. Wavemode disabled.", bx_options.sb16.Owavefile);
|
|
bx_options.sb16.Owavemode->set (0);
|
|
}
|
|
else if (bx_options.sb16.Owavemode->get () == 2)
|
|
initvocfile();
|
|
}
|
|
|
|
DSP.dma.chunk = new Bit8u[BX_SOUND_OUTPUT_WAVEPACKETSIZE];
|
|
DSP.dma.chunkindex = 0;
|
|
DSP.outputinit = 0;
|
|
MPU.outputinit = 0;
|
|
|
|
if (DSP.dma.chunk == NULL)
|
|
{
|
|
writelog( WAVELOG(2), "Couldn't allocate wave buffer - wave output disabled.");
|
|
bx_options.sb16.Owavemode->set (0);
|
|
}
|
|
|
|
BX_INFO(("midi=%d,%s wave=%d,%s log=%d,%s dmatimer=%d",
|
|
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 ()));
|
|
|
|
// 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
|
|
|
|
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
|
|
|
|
// call the mixer reset
|
|
mixer_writeregister(0x00);
|
|
mixer_writedata(0x00);
|
|
|
|
// reset the FM emulation
|
|
OPL.mode = dual;
|
|
opl_entermode(single);
|
|
|
|
// Allocate the IO addresses, 2x0..2xf, 3x0..3x4 and 388..38b
|
|
for (addr=BX_SB16_IO; addr<BX_SB16_IO+BX_SB16_IOLEN; addr++) {
|
|
BX_SB16_THIS devices->register_io_read_handler(this,
|
|
&read_handler, addr, "SB16");
|
|
BX_SB16_THIS devices->register_io_write_handler(this,
|
|
&write_handler, addr, "SB16");
|
|
}
|
|
for (addr=BX_SB16_IOMPU; addr<BX_SB16_IOMPU+BX_SB16_IOMPULEN; addr++) {
|
|
BX_SB16_THIS devices->register_io_read_handler(this,
|
|
&read_handler, addr, "SB16");
|
|
BX_SB16_THIS devices->register_io_write_handler(this,
|
|
&write_handler, addr, "SB16");
|
|
}
|
|
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
|
|
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
|
|
}
|
|
|
|
writelog(MIDILOG(4), "Timers initialized, midi %d, dma %d, opl %d",
|
|
MPU.timer_handle, DSP.timer_handle, OPL.timer_handle );
|
|
MPU.current_timer = 0;
|
|
}
|
|
|
|
void bx_sb16_c::reset(unsigned type)
|
|
{
|
|
}
|
|
|
|
// the timer functions
|
|
void bx_sb16_c::mpu_timer (void *this_ptr)
|
|
{
|
|
((bx_sb16_c *) this_ptr)->mpu401.current_timer++;
|
|
}
|
|
|
|
void bx_sb16_c::dsp_dmatimer (void *this_ptr)
|
|
{
|
|
bx_sb16_c *This = (bx_sb16_c *) this_ptr;
|
|
|
|
// raise the DRQ line. It is then lowered by dsp_getsamplebyte()
|
|
// when the next byte has been received.
|
|
// However, don't do this if the next byte/word will fill up the
|
|
// output buffer and the output functions are not ready yet.
|
|
|
|
if ( (bx_options.sb16.Owavemode->get () != 1) ||
|
|
( (This->dsp.dma.chunkindex + 1 < BX_SOUND_OUTPUT_WAVEPACKETSIZE) &&
|
|
(This->dsp.dma.count > 0) ) ||
|
|
(This->output->waveready() == BX_SOUND_OUTPUT_OK) ) {
|
|
if (DSP.dma.bits == 8)
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAL, 1);
|
|
else
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAH, 1);
|
|
}
|
|
}
|
|
|
|
void bx_sb16_c::opl_timer (void *this_ptr)
|
|
{
|
|
((bx_sb16_c *) this_ptr)->opl_timerevent();
|
|
}
|
|
|
|
// the various IO handlers
|
|
|
|
// The DSP/FM music part
|
|
|
|
// dsp_reset() resets the DSP after the sequence 1/0. Returns
|
|
// 0xaa on the data port
|
|
void bx_sb16_c::dsp_reset(Bit32u value)
|
|
{
|
|
|
|
writelog(WAVELOG(4), "DSP Reset port write value %x", value);
|
|
|
|
// just abort high speed mode if it is set
|
|
if (DSP.dma.highspeed != 0)
|
|
{
|
|
DSP.dma.highspeed = 0;
|
|
writelog(WAVELOG(4), "High speed mode aborted");
|
|
return;
|
|
}
|
|
|
|
if ( (DSP.resetport == 1) &&
|
|
(value == 0) )
|
|
{
|
|
|
|
// 1-0 sequences to reset port, do one of the following:
|
|
// if in UART MIDI mode, abort it, don't reset
|
|
// if in Highspeed mode (not SB16!), abort it, don't reset
|
|
// otherwise reset
|
|
|
|
if (DSP.midiuartmode != 0)
|
|
{ // abort UART MIDI mode
|
|
DSP.midiuartmode = 0;
|
|
writelog(MIDILOG(4), "DSP UART MIDI mode aborted");
|
|
return;
|
|
}
|
|
|
|
// do the reset
|
|
writelog(WAVELOG(4), "DSP resetting...");
|
|
|
|
if (DSP.irqpending != 0)
|
|
{
|
|
BX_SB16_THIS devices->pic->lower_irq(BX_SB16_IRQ);
|
|
writelog(WAVELOG(4), "DSP reset: IRQ untriggered");
|
|
}
|
|
if (DSP.dma.mode != 0)
|
|
{
|
|
writelog(WAVELOG(4), "DSP reset: DMA aborted");
|
|
DSP.dma.mode = 1; // no auto init anymore
|
|
dsp_dmadone();
|
|
}
|
|
|
|
DSP.resetport = 0;
|
|
DSP.speaker = 0;
|
|
DSP.irqpending = 0;
|
|
DSP.midiuartmode = 0;
|
|
DSP.prostereo = 0;
|
|
|
|
DSP.dma.mode = 0;
|
|
DSP.dma.fifo = 0;
|
|
DSP.dma.output = 0;
|
|
DSP.dma.stereo = 0;
|
|
DSP.dma.issigned = 0;
|
|
DSP.dma.count = 0;
|
|
DSP.dma.highspeed = 0;
|
|
DSP.dma.chunkindex = 0;
|
|
|
|
DSP.dataout.reset(); // clear the buffers
|
|
DSP.datain.reset();
|
|
|
|
DSP.dataout.put(0xaa); // acknowledge the reset
|
|
}
|
|
else
|
|
DSP.resetport = value;
|
|
}
|
|
|
|
|
|
// dsp_dataread() reads the data port of the DSP
|
|
|
|
Bit32u bx_sb16_c::dsp_dataread()
|
|
{
|
|
Bit8u value = 0xff;
|
|
|
|
// if we are in MIDI UART mode, call the mpu401 part instead
|
|
if (DSP.midiuartmode != 0)
|
|
value = mpu_dataread();
|
|
else
|
|
{
|
|
// default behaviour: if none available, return last byte again
|
|
// if (DSP.dataout.empty() == 0)
|
|
DSP.dataout.get(&value);
|
|
}
|
|
|
|
writelog(WAVELOG(4), "DSP Data port read, result = %x", value);
|
|
|
|
return(value);
|
|
}
|
|
|
|
|
|
// dsp_datawrite() writes a command or data byte to the data port
|
|
|
|
void bx_sb16_c::dsp_datawrite(Bit32u value)
|
|
{
|
|
int bytesneeded;
|
|
Bit8u mode, value8;
|
|
Bit16u length;
|
|
|
|
writelog(WAVELOG(4), "DSP Data port write, value %x", value);
|
|
|
|
// in high speed mode, any data passed to DSP is a sample
|
|
if (DSP.dma.highspeed != 0)
|
|
{
|
|
dsp_getsamplebyte(value);
|
|
return;
|
|
}
|
|
|
|
// route information to mpu401 part if in MIDI UART mode
|
|
if (DSP.midiuartmode != 0)
|
|
{
|
|
mpu_datawrite(value);
|
|
return;
|
|
}
|
|
|
|
if (DSP.datain.hascommand() == 1) // already a command pending, add to argument list
|
|
{
|
|
if (DSP.datain.put(value) == 0)
|
|
{
|
|
writelog(WAVELOG(3), "DSP command buffer overflow for command %02x",
|
|
DSP.datain.currentcommand());
|
|
}
|
|
}
|
|
else
|
|
// no command pending, set one up
|
|
{
|
|
bytesneeded = 0; // find out how many arguments the command takes
|
|
switch (value)
|
|
{ // all fallbacks intended!
|
|
case 0x04:
|
|
case 0x0f:
|
|
case 0x10:
|
|
case 0x40:
|
|
case 0x38:
|
|
case 0xe0:
|
|
bytesneeded = 1;
|
|
break;
|
|
case 0x05:
|
|
case 0x0e:
|
|
case 0x14:
|
|
case 0x16:
|
|
case 0x17:
|
|
case 0x41:
|
|
case 0x42:
|
|
case 0x48:
|
|
case 0x74:
|
|
case 0x75:
|
|
case 0x76:
|
|
case 0x77:
|
|
case 0x80:
|
|
case 0xe4:
|
|
bytesneeded = 2;
|
|
break;
|
|
|
|
// 0xb0 ... 0xbf:
|
|
case 0xb0:
|
|
case 0xb1:
|
|
case 0xb2:
|
|
case 0xb3:
|
|
case 0xb4:
|
|
case 0xb5:
|
|
case 0xb6:
|
|
case 0xb7:
|
|
case 0xb8:
|
|
case 0xb9:
|
|
case 0xba:
|
|
case 0xbb:
|
|
case 0xbc:
|
|
case 0xbd:
|
|
case 0xbe:
|
|
case 0xbf:
|
|
|
|
// 0xc0 ... 0xcf:
|
|
case 0xc0:
|
|
case 0xc1:
|
|
case 0xc2:
|
|
case 0xc3:
|
|
case 0xc4:
|
|
case 0xc5:
|
|
case 0xc6:
|
|
case 0xc7:
|
|
case 0xc8:
|
|
case 0xc9:
|
|
case 0xca:
|
|
case 0xcb:
|
|
case 0xcc:
|
|
case 0xcd:
|
|
case 0xce:
|
|
case 0xcf:
|
|
bytesneeded = 3;
|
|
break;
|
|
}
|
|
DSP.datain.newcommand(value, bytesneeded);
|
|
}
|
|
|
|
if (DSP.datain.commanddone() == 1) // command is complete, process it
|
|
{
|
|
writelog(WAVELOG(4), "DSP command %x with %d arg bytes",
|
|
DSP.datain.currentcommand(), DSP.datain.bytes());
|
|
|
|
switch (DSP.datain.currentcommand())
|
|
{
|
|
// DSP commands - comments are the parameters for
|
|
// this command, and/or the output
|
|
|
|
// ASP commands (Advanced Signal Processor)
|
|
// undocumented (?), just from looking what an SB16 does
|
|
case 0x04:
|
|
DSP.datain.get(&value8);
|
|
break;
|
|
|
|
case 0x05:
|
|
DSP.datain.get(&value8);
|
|
DSP.datain.get(&value8);
|
|
break;
|
|
|
|
case 0x0e:
|
|
DSP.datain.get(&value8);
|
|
DSP.datain.get(&value8);
|
|
break;
|
|
|
|
case 0x0f:
|
|
DSP.datain.get(&value8);
|
|
DSP.dataout.put(0); // 0 means no ASP present?
|
|
break;
|
|
|
|
// direct mode DAC
|
|
case 0x10:
|
|
// 1: 8bit sample
|
|
DSP.datain.get(&value8); // sample is ignored
|
|
break;
|
|
|
|
// uncomp'd, normal DAC DMA
|
|
case 0x14:
|
|
// 1,2: lo(length) hi(length)
|
|
DSP.datain.getw(&length);
|
|
dsp_dma(0xc0, 0x00, length, 0);
|
|
break;
|
|
|
|
// 2-bit comp'd, normal DAC DMA, no ref byte
|
|
case 0x16:
|
|
// 1,2: lo(length) hi(length)
|
|
DSP.datain.getw(&length);
|
|
dsp_dma(0xc0, 0x00, length, 2);
|
|
break;
|
|
|
|
// 2-bit comp'd, normal DAC DMA, 1 ref byte
|
|
case 0x17:
|
|
// 1,2: lo(length) hi(length)
|
|
DSP.datain.getw(&length);
|
|
dsp_dma(0xc0, 0x00, length, 2|8);
|
|
break;
|
|
|
|
// uncomp'd, auto DAC DMA
|
|
case 0x1c:
|
|
// none
|
|
dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 0);
|
|
break;
|
|
|
|
// 2-bit comp'd, auto DAC DMA, 1 ref byte
|
|
case 0x1f:
|
|
// none
|
|
dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 2|8);
|
|
break;
|
|
|
|
// direct mode ADC
|
|
case 0x20:
|
|
// o1: 8bit sample
|
|
DSP.dataout.put(0x80); // put a silence, for now.
|
|
break;
|
|
|
|
// uncomp'd, normal ADC DMA
|
|
case 0x24:
|
|
// 1,2: lo(length) hi(length)
|
|
DSP.datain.getw(&length);
|
|
dsp_dma(0xc8, 0x00, length, 0);
|
|
break;
|
|
|
|
// uncomp'd, auto ADC DMA
|
|
case 0x2c:
|
|
// none
|
|
dsp_dma(0xcc, 0x00, DSP.dma.blocklength, 0);
|
|
break;
|
|
|
|
// ? polling mode MIDI input
|
|
case 0x30:
|
|
break;
|
|
|
|
// ? interrupt mode MIDI input
|
|
case 0x31:
|
|
break;
|
|
|
|
// 0x34..0x37: UART mode MIDI output
|
|
case 0x34:
|
|
|
|
// UART mode MIDI input/output
|
|
case 0x35:
|
|
|
|
// UART polling mode MIDI IO with time stamp
|
|
case 0x36:
|
|
|
|
// UART interrupt mode MIDI IO with time stamp
|
|
case 0x37:
|
|
// Fallbacks intended - all set the midi uart mode
|
|
DSP.midiuartmode = 1;
|
|
break;
|
|
|
|
// MIDI output
|
|
case 0x38:
|
|
DSP.datain.get(&value8);
|
|
// route to mpu401 part
|
|
mpu_datawrite(value8);
|
|
break;
|
|
|
|
// set time constant
|
|
case 0x40:
|
|
// 1: timeconstant
|
|
DSP.datain.get(&value8);
|
|
DSP.dma.timeconstant = value8 << 8;
|
|
DSP.dma.samplerate = (Bit32u) 256000000L / ((Bit32u) 65536L - (Bit32u) DSP.dma.timeconstant);
|
|
break;
|
|
|
|
// set samplerate for input
|
|
case 0x41:
|
|
// (fallback intended)
|
|
|
|
// set samplerate for output
|
|
case 0x42:
|
|
// 1,2: hi(frq) lo(frq)
|
|
DSP.datain.getw1( &(DSP.dma.samplerate) );
|
|
DSP.dma.timeconstant = 65536 - (Bit32u) 256000000 / (Bit32u) DSP.dma.samplerate ;
|
|
break;
|
|
|
|
// set block length
|
|
case 0x48:
|
|
// 1,2: lo(blk len) hi(blk len)
|
|
DSP.datain.getw( &(DSP.dma.blocklength) );
|
|
break;
|
|
|
|
// 4-bit comp'd, normal DAC DMA, no ref byte
|
|
case 0x74:
|
|
// 1,2: lo(length) hi(length)
|
|
DSP.datain.getw(&length);
|
|
dsp_dma(0xc0, 0x00, length, 4);
|
|
break;
|
|
|
|
// 4-bit comp'd, normal DAC DMA, 1 ref byte
|
|
case 0x75:
|
|
// 1,2: lo(length) hi(length)
|
|
DSP.datain.getw(&length);
|
|
dsp_dma(0xc0, 0x00, length, 4|8);
|
|
break;
|
|
|
|
// 3-bit comp'd, normal DAC DMA, no ref byte
|
|
case 0x76:
|
|
// 1,2: lo(length) hi(length)
|
|
DSP.datain.getw(&length);
|
|
dsp_dma(0xc0, 0x00, length, 3);
|
|
break;
|
|
|
|
// 3-bit comp'd, normal DAC DMA, 1 ref byte
|
|
case 0x77:
|
|
// 1,2: lo(length) hi(length)
|
|
DSP.datain.getw(&length);
|
|
dsp_dma(0xc0, 0x00, length, 3|8);
|
|
break;
|
|
|
|
// 4-bit comp'd, auto DAC DMA, 1 ref byte
|
|
case 0x7d:
|
|
// none
|
|
dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 4|8);
|
|
break;
|
|
|
|
// 3-bit comp'd, auto DAC DMA, 1 ref byte
|
|
case 0x7f:
|
|
// none
|
|
dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 3|8);
|
|
break;
|
|
|
|
// silence period
|
|
case 0x80:
|
|
// 1,2: lo(silence) hi(silence) (len in samples)
|
|
DSP.datain.getw(&length);
|
|
// only handled for VOC output so far
|
|
if (bx_options.sb16.Owavemode->get () == 2)
|
|
{
|
|
Bit8u temparray[3] = { length & 0xff, length >> 8, DSP.dma.timeconstant >> 8 };
|
|
writevocblock(3, 3, temparray, 0, NULL);
|
|
}
|
|
break;
|
|
|
|
// 8-bit auto DAC DMA, highspeed
|
|
case 0x90:
|
|
//none
|
|
dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 16);
|
|
break;
|
|
|
|
// 8-bit normal DAC DMA, highspeed
|
|
case 0x91:
|
|
//none
|
|
dsp_dma(0xc0, 0x00, DSP.dma.blocklength, 16);
|
|
break;
|
|
|
|
// 8-bit auto ADC DMA, highspeed
|
|
case 0x98:
|
|
//none
|
|
dsp_dma(0xcc, 0x00, DSP.dma.blocklength, 16);
|
|
break;
|
|
|
|
case 0x99: // 8-bit normal DMA
|
|
//none
|
|
dsp_dma(0xc8, 0x00, DSP.dma.blocklength, 16);
|
|
break;
|
|
|
|
// switch to mono for SBPro DAC/ADC
|
|
case 0xa0:
|
|
// none
|
|
DSP.prostereo = 1;
|
|
break;
|
|
|
|
// switch to stereo for SBPro DAC/ADC
|
|
case 0xa8:
|
|
//// none
|
|
DSP.prostereo = 2;
|
|
break;
|
|
|
|
// 0xb0 ... 0xbf:
|
|
// 16 bit DAC/ADC DMA, general commands
|
|
// fallback intended
|
|
case 0xb0:
|
|
case 0xb1:
|
|
case 0xb2:
|
|
case 0xb3:
|
|
case 0xb4:
|
|
case 0xb5:
|
|
case 0xb6:
|
|
case 0xb7:
|
|
case 0xb8:
|
|
case 0xb9:
|
|
case 0xba:
|
|
case 0xbb:
|
|
case 0xbc:
|
|
case 0xbd:
|
|
case 0xbe:
|
|
case 0xbf:
|
|
|
|
// 0xc0 ... 0xcf:
|
|
// 8 bit DAC/ADC DMA, general commands
|
|
case 0xc0:
|
|
case 0xc1:
|
|
case 0xc2:
|
|
case 0xc3:
|
|
case 0xc4:
|
|
case 0xc5:
|
|
case 0xc6:
|
|
case 0xc7:
|
|
case 0xc8:
|
|
case 0xc9:
|
|
case 0xca:
|
|
case 0xcb:
|
|
case 0xcc:
|
|
case 0xcd:
|
|
case 0xce:
|
|
case 0xcf:
|
|
DSP.datain.get(&mode);
|
|
DSP.datain.getw(&length);
|
|
dsp_dma(DSP.datain.currentcommand(), mode, length, 0);
|
|
break;
|
|
|
|
// pause 8 bit DMA transfer
|
|
case 0xd0:
|
|
// none
|
|
if (DSP.dma.mode != 0)
|
|
dsp_disabledma();
|
|
break;
|
|
|
|
// speaker on
|
|
case 0xd1:
|
|
// none
|
|
DSP.speaker = 1;
|
|
break;
|
|
|
|
// speaker off
|
|
case 0xd3:
|
|
// none
|
|
DSP.speaker = 0;
|
|
break;
|
|
|
|
// continue 8 bit DMA, see 0xd0
|
|
case 0xd4:
|
|
// none
|
|
if (DSP.dma.mode != 0)
|
|
dsp_enabledma();
|
|
break;
|
|
|
|
// pause 16 bit DMA
|
|
case 0xd5:
|
|
// none
|
|
if (DSP.dma.mode != 0)
|
|
dsp_disabledma();
|
|
break;
|
|
|
|
// continue 16 bit DMA, see 0xd5
|
|
case 0xd6:
|
|
// none
|
|
if (DSP.dma.mode != 0)
|
|
dsp_enabledma();
|
|
break;
|
|
|
|
// read speaker on/off (out ff=on, 00=off)
|
|
case 0xd8:
|
|
// none, o1: speaker; ff/00
|
|
DSP.dataout.put( (DSP.speaker == 1)?0xff:0x00);
|
|
break;
|
|
|
|
// stop 16 bit auto DMA
|
|
case 0xd9:
|
|
// none
|
|
if (DSP.dma.mode != 0)
|
|
{
|
|
DSP.dma.mode = 1; // no auto init anymore
|
|
dsp_dmadone();
|
|
}
|
|
break;
|
|
|
|
// stop 8 bit auto DMA
|
|
case 0xda:
|
|
// none
|
|
if (DSP.dma.mode != 0)
|
|
{
|
|
DSP.dma.mode = 1; // no auto init anymore
|
|
dsp_dmadone();
|
|
}
|
|
break;
|
|
|
|
// used and expected by the CL diagnose.exe
|
|
case 0xe0:
|
|
DSP.dataout.put(0x55);
|
|
break;
|
|
|
|
// get version, out 2 bytes (major, minor)
|
|
case 0xe1:
|
|
// none, o1/2: version major.minor
|
|
DSP.dataout.put(4);
|
|
if (DSP.dataout.put(11) == 0)
|
|
{
|
|
writelog(WAVELOG(3), "DSP version couldn't be written - buffer overflow");
|
|
}
|
|
break;
|
|
|
|
case 0xe3:
|
|
// none, output: Copyright string
|
|
// the Windows driver needs the exact text, otherwise it
|
|
// won't load. Same for diagnose.exe
|
|
DSP.dataout.puts("COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.");
|
|
DSP.dataout.put(0); // need extra string end
|
|
break;
|
|
|
|
// used and expected by the CL diagnose.exe
|
|
case 0xe4:
|
|
DSP.dataout.put(0xaa);
|
|
break;
|
|
|
|
// Trigger 8-bit IRQ
|
|
case 0xf2:
|
|
DSP.dataout.put(0xaa);
|
|
DSP.irqpending = 1;
|
|
MIXER.reg[0x82] |= 1; // reg 82 shows the kind of IRQ
|
|
BX_SB16_THIS devices->pic->raise_irq(BX_SB16_IRQ);
|
|
break;
|
|
|
|
// unknown command
|
|
default:
|
|
writelog(WAVELOG(3), "unknown DSP command %x, ignored",
|
|
DSP.datain.currentcommand());
|
|
break;
|
|
}
|
|
DSP.datain.clearcommand();
|
|
DSP.datain.flush();
|
|
}
|
|
}
|
|
|
|
// dsp_dma() initiates all kinds of dma transfers
|
|
void bx_sb16_c::dsp_dma(Bit8u command, Bit8u mode, Bit16u length, Bit8u comp)
|
|
{
|
|
// command: 8bit, 16bit, in/out, single/auto, fifo
|
|
// mode: mono/stereo, signed/unsigned
|
|
// (for info on command and mode see sound blaster programmer's manual,
|
|
// cmds bx and cx)
|
|
// length: number of samples - not number of bytes
|
|
// comp: bit-coded are: type of compression; ref-byte; highspeed
|
|
// D0..D2: 0=none, 2,3,4 bits ADPCM
|
|
// D3: ref-byte
|
|
// D6: highspeed
|
|
|
|
writelog( WAVELOG(4), "DMA initialized. Cmd %02x, mode %02x, length %d, comp %d",
|
|
command, mode, length, comp);
|
|
|
|
if ( (command >> 4) == 0xb ) // 0xb? = 16 bit DMA
|
|
{
|
|
DSP.dma.bits = 16;
|
|
DSP.dma.bps = 2;
|
|
}
|
|
else // 0xc? = 8 bit DMA
|
|
{
|
|
DSP.dma.bits = 8;
|
|
DSP.dma.bps = 1;
|
|
}
|
|
|
|
// Prevent division by zero in some instances
|
|
if ( DSP.dma.samplerate == 0 )
|
|
DSP.dma.samplerate= 10752;
|
|
command &= 0x0f;
|
|
DSP.dma.output = 1 - (command >> 3); // 1=output, 0=input
|
|
DSP.dma.mode = 1 + ( (command >> 2) & 1); // 0=none, 1=normal, 2=auto
|
|
DSP.dma.fifo = (command >> 1) & 1; // ? not sure what this is
|
|
|
|
DSP.dma.stereo = (mode >> 5) & 1;
|
|
|
|
if (DSP.dma.stereo != 0)
|
|
DSP.dma.bps *= 2;
|
|
|
|
DSP.dma.blocklength = length;
|
|
DSP.dma.issigned = (mode >> 4) & 1;
|
|
DSP.dma.count = (DSP.dma.blocklength + 1) * DSP.dma.bps - 1;
|
|
DSP.dma.highspeed = (comp >> 4) & 1;
|
|
|
|
DSP.dma.chunkindex = 0;
|
|
DSP.dma.chunkcount = 0;
|
|
|
|
Bit32u sampledatarate = (Bit32u) DSP.dma.samplerate * (Bit32u) DSP.dma.bps;
|
|
DSP.dma.timer = (Bit32u) bx_options.sb16.Odmatimer->get () / sampledatarate;
|
|
|
|
writelog( WAVELOG(5), "DMA is %db, %dHz, %s, %s, mode %d, %s, %s, %d bps, %d us/b",
|
|
DSP.dma.bits, DSP.dma.samplerate, (DSP.dma.stereo != 0)?"stereo":"mono",
|
|
(DSP.dma.output == 1)?"output":"input", DSP.dma.mode,
|
|
(DSP.dma.issigned == 1)?"signed":"unsigned",
|
|
(DSP.dma.highspeed == 1)?"highspeed":"normal speed",
|
|
sampledatarate, DSP.dma.timer);
|
|
|
|
DSP.dma.format = DSP.dma.issigned | ( (comp & 7) << 1) | ( (comp & 8) << 4);
|
|
|
|
// write the output to the device/file
|
|
if (DSP.dma.output == 1)
|
|
{
|
|
if (bx_options.sb16.Owavemode->get () == 1)
|
|
{
|
|
if (DSP.outputinit == 0)
|
|
{
|
|
if (BX_SB16_OUTPUT->openwaveoutput(bx_options.sb16.Owavefile->getptr ()) != BX_SOUND_OUTPUT_OK)
|
|
{
|
|
bx_options.sb16.Owavemode->set (0);
|
|
writelog( WAVELOG(2), "Error: Could not open wave output device.");
|
|
}
|
|
else
|
|
DSP.outputinit = 1;
|
|
}
|
|
|
|
if (DSP.outputinit == 1)
|
|
BX_SB16_OUTPUT->startwaveplayback(DSP.dma.samplerate, DSP.dma.bits, DSP.dma.stereo, DSP.dma.format);
|
|
}
|
|
}
|
|
|
|
dsp_enabledma();
|
|
}
|
|
|
|
|
|
// dsp_enabledma(): Start the DMA timer and thus the transfer
|
|
|
|
void bx_sb16_c::dsp_enabledma()
|
|
{
|
|
bx_pc_system.activate_timer( DSP.timer_handle, DSP.dma.timer, 1 );
|
|
}
|
|
|
|
// dsp_disabledma(): Stop the DMA timer and thus the transfer, but don't abort it
|
|
|
|
void bx_sb16_c::dsp_disabledma()
|
|
{
|
|
bx_pc_system.deactivate_timer( DSP.timer_handle );
|
|
}
|
|
|
|
|
|
// dsp_bufferstatus() checks if the DSP is ready for data/commands
|
|
|
|
Bit32u bx_sb16_c::dsp_bufferstatus()
|
|
{
|
|
Bit32u result = 0x7f;
|
|
|
|
// MSB set -> not ready for commands
|
|
if (DSP.datain.full() == 1) result |= 0x80;
|
|
|
|
writelog(WAVELOG(4), "DSP Buffer status read, result %x", result);
|
|
|
|
return(result);
|
|
}
|
|
|
|
|
|
// dsp_status() checks if the DSP is ready to send data
|
|
|
|
Bit32u bx_sb16_c::dsp_status()
|
|
{
|
|
Bit32u result = 0x7f;
|
|
|
|
// read might be to acknowledge IRQ
|
|
if ( DSP.irqpending != 0 )
|
|
{
|
|
MIXER.reg[0x82] &= (~0x01);
|
|
writelog( WAVELOG(4), "8-bit DMA or SBMIDI IRQ acknowledged");
|
|
if (MIXER.reg[0x82] == 0) {
|
|
DSP.irqpending = 0;
|
|
BX_SB16_THIS devices->pic->lower_irq(BX_SB16_IRQ);
|
|
}
|
|
}
|
|
|
|
// if buffer is not empty, there is data to be read
|
|
if (DSP.dataout.empty() == 0) result |= 0x80;
|
|
|
|
writelog(WAVELOG(4), "DSP output status read, result %x", result);
|
|
|
|
return(result);
|
|
|
|
}
|
|
|
|
// dsp_irq16ack() notifies that the 16bit DMA IRQ has been acknowledged
|
|
Bit32u bx_sb16_c::dsp_irq16ack()
|
|
{
|
|
Bit32u result = 0xff;
|
|
|
|
if ( DSP.irqpending != 0 )
|
|
{
|
|
MIXER.reg[0x82] &= (~0x02);
|
|
if (MIXER.reg[0x82] == 0) {
|
|
DSP.irqpending = 0;
|
|
BX_SB16_THIS devices->pic->lower_irq(BX_SB16_IRQ);
|
|
}
|
|
writelog( WAVELOG(4), "16-bit DMA IRQ acknowledged");
|
|
}
|
|
else
|
|
writelog( WAVELOG(3), "16-bit DMA IRQ acknowledged but not active!");
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
// the DMA handlers
|
|
|
|
|
|
// highlevel input and output handlers - rerouting to/from file,device
|
|
|
|
// get a wave packet from the input device (not supported yet)
|
|
void bx_sb16_c::dsp_getwavepacket()
|
|
{
|
|
writelog( WAVELOG(3), "DMA reads not supported. Returning silence.");
|
|
|
|
|
|
// fill the buffer with silence. Watch for 16bit transfer and signed/unsigned
|
|
|
|
// these are the two different bytes in 16bit transfer (both the same for 8bit)
|
|
Bit8u byteA, byteB;
|
|
|
|
byteA = 0x00; // compatible with all signed transfers
|
|
byteB = 0x00;
|
|
int i;
|
|
|
|
if (DSP.dma.issigned == 0)
|
|
byteB = 0x80;
|
|
|
|
if (DSP.dma.bits == 8)
|
|
byteA = byteB;
|
|
|
|
for (i = 0; i < BX_SOUND_OUTPUT_WAVEPACKETSIZE; i++)
|
|
DSP.dma.chunk[i] = ( (i & 1) == 0) ? byteA : byteB;
|
|
|
|
DSP.dma.chunkcount = BX_SOUND_OUTPUT_WAVEPACKETSIZE;
|
|
DSP.dma.chunkindex = 0;
|
|
}
|
|
|
|
// write a wave packet to the output device
|
|
void bx_sb16_c::dsp_sendwavepacket()
|
|
{
|
|
switch (bx_options.sb16.Owavemode->get ())
|
|
{
|
|
case 1:
|
|
BX_SB16_OUTPUT->sendwavepacket(DSP.dma.chunkindex, DSP.dma.chunk);
|
|
break;
|
|
case 3:
|
|
fwrite(DSP.dma.chunk, 1, DSP.dma.chunkindex, WAVEDATA);
|
|
break;
|
|
case 2:
|
|
Bit8u temparray[12] =
|
|
{DSP.dma.samplerate & 0xff, DSP.dma.samplerate >> 8, 0, 0,
|
|
DSP.dma.bits, DSP.dma.stereo + 1, 0, 0, 0, 0, 0, 0 };
|
|
switch ( (DSP.dma.format >> 1) & 7)
|
|
{
|
|
case 2:
|
|
temparray[7] = 3;
|
|
break;
|
|
case 3:
|
|
temparray[7] = 2;
|
|
break;
|
|
case 4:
|
|
temparray[7] = 1;
|
|
break;
|
|
}
|
|
if (DSP.dma.bits == 16)
|
|
temparray[7] = 4;
|
|
|
|
writevocblock(9, 12, temparray, DSP.dma.chunkindex, DSP.dma.chunk);
|
|
break;
|
|
}
|
|
|
|
DSP.dma.chunkindex = 0;
|
|
}
|
|
|
|
// put a sample byte into the output buffer
|
|
void bx_sb16_c::dsp_getsamplebyte(Bit8u value)
|
|
{
|
|
if (DSP.dma.chunkindex < BX_SOUND_OUTPUT_WAVEPACKETSIZE)
|
|
DSP.dma.chunk[DSP.dma.chunkindex++] = value;
|
|
|
|
if (DSP.dma.chunkindex >= BX_SOUND_OUTPUT_WAVEPACKETSIZE)
|
|
dsp_sendwavepacket();
|
|
}
|
|
|
|
// read a sample byte from the input buffer
|
|
Bit8u bx_sb16_c::dsp_putsamplebyte()
|
|
{
|
|
if (DSP.dma.chunkindex >= DSP.dma.chunkcount)
|
|
dsp_getwavepacket();
|
|
|
|
return DSP.dma.chunk[DSP.dma.chunkindex++];
|
|
}
|
|
|
|
// called when the last byte of a DMA transfer has been received/sent
|
|
void bx_sb16_c::dsp_dmadone()
|
|
{
|
|
writelog( WAVELOG(4), "DMA transfer done, triggering IRQ");
|
|
|
|
if ( (DSP.dma.output == 1) && (DSP.dma.mode != 2) )
|
|
{
|
|
dsp_sendwavepacket(); // flush the output
|
|
|
|
if (bx_options.sb16.Owavemode->get () == 1)
|
|
{
|
|
if (DSP.dma.mode != 2)
|
|
BX_SB16_OUTPUT->stopwaveplayback(); // don't stop if Auto-DMA
|
|
}
|
|
|
|
else if (bx_options.sb16.Owavemode->get () == 2)
|
|
fflush(WAVEDATA);
|
|
}
|
|
|
|
// generate the appropriate IRQ
|
|
if (DSP.dma.bits == 8)
|
|
MIXER.reg[0x82] |= 1;
|
|
else
|
|
MIXER.reg[0x82] |= 2;
|
|
|
|
BX_SB16_THIS devices->pic->raise_irq(BX_SB16_IRQ);
|
|
DSP.irqpending = 1;
|
|
|
|
//if auto-DMA, reinitialize
|
|
if (DSP.dma.mode == 2)
|
|
{
|
|
DSP.dma.count = (DSP.dma.blocklength + 1) * DSP.dma.bps - 1;
|
|
writelog( WAVELOG(4), "auto-DMA reinitializing to length %d", DSP.dma.count);
|
|
}
|
|
else
|
|
{
|
|
DSP.dma.mode = 0;
|
|
dsp_disabledma();
|
|
}
|
|
}
|
|
|
|
// now the actual transfer routines, called by the DMA controller
|
|
// note that read = from application to soundcard (output),
|
|
// and write = from soundcard to application (input)
|
|
void bx_sb16_c::dma_read8(Bit8u *data_byte)
|
|
{
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAL, 0); // the timer will raise it again
|
|
|
|
if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
|
|
writelog( WAVELOG(5), "Received 8-bit DMA %2x, %d remaining ",
|
|
*data_byte, DSP.dma.count);
|
|
DSP.dma.count--;
|
|
|
|
dsp_getsamplebyte(*data_byte);
|
|
|
|
if (DSP.dma.count == 0xffff) // last byte received
|
|
dsp_dmadone();
|
|
}
|
|
|
|
void bx_sb16_c::dma_write8(Bit8u *data_byte)
|
|
{
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAL, 0); // the timer will raise it again
|
|
|
|
DSP.dma.count--;
|
|
|
|
*data_byte = dsp_putsamplebyte();
|
|
|
|
if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
|
|
writelog( WAVELOG(5), "Sent 8-bit DMA %2x, %d remaining ",
|
|
*data_byte, DSP.dma.count);
|
|
|
|
if (DSP.dma.count == 0xffff) // last byte sent
|
|
dsp_dmadone();
|
|
}
|
|
|
|
void bx_sb16_c::dma_read16(Bit16u *data_word)
|
|
{
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAH, 0); // the timer will raise it again
|
|
|
|
if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
|
|
writelog( WAVELOG(5), "Received 16-bit DMA %4x, %d remaining ",
|
|
*data_word, DSP.dma.count);
|
|
|
|
DSP.dma.count--;
|
|
|
|
dsp_getsamplebyte(*data_word & 0xff);
|
|
dsp_getsamplebyte(*data_word >> 8);
|
|
|
|
if (DSP.dma.count == 0xffff) // last byte received
|
|
dsp_dmadone();
|
|
}
|
|
|
|
void bx_sb16_c::dma_write16(Bit16u *data_word)
|
|
{
|
|
Bit8u byte1, byte2;
|
|
|
|
BX_DMA_SET_DRQ(BX_SB16_DMAH, 0); // the timer will raise it again
|
|
|
|
DSP.dma.count--;
|
|
|
|
byte1 = dsp_putsamplebyte();
|
|
byte2 = dsp_putsamplebyte();
|
|
|
|
// all input is in little endian
|
|
*data_word = byte1 | (byte2 << 8);
|
|
|
|
if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
|
|
writelog( WAVELOG(5), "Sent 16-bit DMA %4x, %d remaining ",
|
|
*data_word, DSP.dma.count);
|
|
|
|
if (DSP.dma.count == 0xffff) // last byte sent
|
|
dsp_dmadone();
|
|
}
|
|
|
|
|
|
// the mixer, supported type is CT1745 (as in an SB16)
|
|
void bx_sb16_c::mixer_writedata(Bit32u value)
|
|
{
|
|
int i;
|
|
|
|
if (MIXER.regindex >= BX_SB16_MIX_REG)
|
|
return; // index out of range
|
|
|
|
// store the value
|
|
MIXER.reg[MIXER.regindex] = value;
|
|
|
|
// do some action depending on what register was written
|
|
switch (MIXER.regindex)
|
|
{
|
|
case 0: // initialize mixer
|
|
|
|
writelog(BOTHLOG(4), "Initializing mixer...");
|
|
for (i=0; i<0x80; i++)
|
|
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()
|
|
{
|
|
static Boolean isInitialized=0;
|
|
int newirq;
|
|
int oldDMA8, oldDMA16;
|
|
|
|
// set the IRQ according to the value in mixer register 0x80
|
|
switch (MIXER.reg[0x80])
|
|
{
|
|
case 1:
|
|
newirq = 2;
|
|
break;
|
|
case 2:
|
|
newirq = 5;
|
|
break;
|
|
case 4:
|
|
newirq = 7;
|
|
break;
|
|
case 8:
|
|
newirq = 10;
|
|
break;
|
|
default:
|
|
newirq = 5;
|
|
writelog(BOTHLOG(3), "Bad value %02x in mixer register 0x80. IRQ set to %d",
|
|
MIXER.reg[0x80], newirq);
|
|
MIXER.reg[0x80] = 2;
|
|
}
|
|
if (newirq != BX_SB16_IRQ) // a different IRQ was set
|
|
{
|
|
if (BX_SB16_IRQ > 0)
|
|
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
|
|
oldDMA8=BX_SB16_DMAL;
|
|
switch (MIXER.reg[0x81] & 0x0f)
|
|
{
|
|
case 1:
|
|
BX_SB16_DMAL = 0;
|
|
break;
|
|
case 2:
|
|
BX_SB16_DMAL = 1;
|
|
break;
|
|
case 8:
|
|
BX_SB16_DMAL = 3;
|
|
break;
|
|
default:
|
|
BX_SB16_DMAL = 1;
|
|
writelog(BOTHLOG(3), "Bad value %02x in mixer register 0x81. DMA8 set to %d",
|
|
MIXER.reg[0x81], BX_SB16_DMAL);
|
|
MIXER.reg[0x81] &= (~0x0f);
|
|
MIXER.reg[0x81] |= (1 << BX_SB16_DMAL);
|
|
}
|
|
|
|
// Unregister the previous DMA if initialized
|
|
if ( (isInitialized) && (oldDMA8 != BX_SB16_DMAL) ) {
|
|
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");
|
|
}
|
|
|
|
// and the 16 bit DMA
|
|
oldDMA16=BX_SB16_DMAH;
|
|
switch (MIXER.reg[0x81] >> 4)
|
|
{
|
|
case 0:
|
|
BX_SB16_DMAH = 0; // no 16-bit DMA
|
|
break;
|
|
case 2:
|
|
BX_SB16_DMAH = 5;
|
|
break;
|
|
case 4:
|
|
BX_SB16_DMAH = 6;
|
|
break;
|
|
case 8:
|
|
BX_SB16_DMAH = 7;
|
|
break;
|
|
default:
|
|
BX_SB16_DMAH = 0;
|
|
writelog(BOTHLOG(3), "Bad value %02x in mixer register 0x81. DMA16 set to %d",
|
|
MIXER.reg[0x81], BX_SB16_DMAH);
|
|
MIXER.reg[0x81] &= (~0xf0);
|
|
// MIXER.reg[0x81] |= (1 << BX_SB16_DMAH);
|
|
// no default 16 bit channel!
|
|
}
|
|
|
|
// Unregister the previous DMA if initialized
|
|
if ( (isInitialized) && (oldDMA16 != 0) && (oldDMA16 != BX_SB16_DMAH) ) {
|
|
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;
|
|
}
|
|
|
|
writelog(BOTHLOG(4), "Resources set to I%d D%d H%d",
|
|
BX_SB16_IRQ, BX_SB16_DMAL, BX_SB16_DMAH);
|
|
|
|
}
|
|
|
|
|
|
// now the MPU 401 part
|
|
|
|
|
|
// the MPU 401 status port shows if input or output are ready
|
|
// Note that the bits are inverse to their meaning
|
|
|
|
Bit32u bx_sb16_c::mpu_status()
|
|
{
|
|
Bit32u result = 0;
|
|
|
|
if ( (MPU.datain.full() == 1) ||
|
|
( (bx_options.sb16.Omidimode->get () == 1) &&
|
|
(BX_SB16_OUTPUT->midiready() == BX_SOUND_OUTPUT_ERR) ) )
|
|
result |= 0x40; // output not ready
|
|
if (MPU.dataout.empty() == 1)
|
|
result |= 0x80; // no input available
|
|
|
|
writelog(MIDILOG(4), "MPU status port, result %02x", result);
|
|
|
|
return(result);
|
|
}
|
|
|
|
|
|
// the MPU 401 command port
|
|
|
|
void bx_sb16_c::mpu_command(Bit32u value)
|
|
{
|
|
int i;
|
|
int bytesneeded;
|
|
|
|
if (MPU.cmd.hascommand() == 1) // already a command pending, abort that one
|
|
{
|
|
if ( (MPU.cmd.currentcommand() != value) ||
|
|
(MPU.cmd.commanddone() == 0) )
|
|
// it's a different command, or the old one isn't done yet, abort it
|
|
{
|
|
MPU.cmd.clearcommand();
|
|
MPU.cmd.flush();
|
|
}
|
|
// if it's the same one, and we just completed the argument list,
|
|
// we leave it as it is and process it here
|
|
}
|
|
|
|
if (MPU.cmd.hascommand() == 0) // no command pending, set one up
|
|
{
|
|
bytesneeded = 0;
|
|
if ( (value >> 4) == 14) bytesneeded = 1;
|
|
MPU.cmd.newcommand(value, bytesneeded);
|
|
}
|
|
|
|
if (MPU.cmd.commanddone() == 1) // command is complete, process it
|
|
{
|
|
switch (MPU.cmd.currentcommand())
|
|
{
|
|
case 0x3f:
|
|
writelog(MIDILOG(5), "MPU cmd: UART mode on");
|
|
MPU.uartmode=1;
|
|
MPU.irqpending=1;
|
|
MPU.singlecommand=0;
|
|
if (BX_SB16_IRQMPU != -1)
|
|
{
|
|
MIXER.reg[0x82] |= 4;
|
|
BX_SB16_THIS devices->pic->raise_irq(BX_SB16_IRQMPU);
|
|
}
|
|
break;
|
|
|
|
case 0xff:
|
|
writelog(MIDILOG(4), "MPU cmd: Master reset of device");
|
|
MPU.uartmode=MPU.forceuartmode;
|
|
MPU.singlecommand=0;
|
|
for (i=0; i<16; i++)
|
|
{
|
|
MPU.banklsb[i] = 0;
|
|
MPU.bankmsb[i] = 0;
|
|
MPU.program[i] = 0;
|
|
}
|
|
MPU.cmd.reset();
|
|
MPU.dataout.reset();
|
|
MPU.datain.reset();
|
|
MPU.midicmd.reset();
|
|
|
|
/*
|
|
if (BX_SB16_IRQ != -1)
|
|
{
|
|
MIXER.reg[0x82] |= 4;
|
|
BX_SB16_THIS devices->pic->trigger_irq(BX_SB16_IRQ);
|
|
}
|
|
*/
|
|
break;
|
|
case 0xd0: // d0 and df: prefix for midi command
|
|
case 0xdf: // like uart mode, but only a single command
|
|
MPU.singlecommand = 1;
|
|
writelog(MIDILOG(4), "MPU: prefix %02x received",
|
|
MPU.cmd.currentcommand());
|
|
break;
|
|
default:
|
|
writelog(MIDILOG(3), "MPU cmd: unknown command %02x ignored",
|
|
MPU.cmd.currentcommand());
|
|
break;
|
|
}
|
|
|
|
// Need to put an MPU_ACK into the data port if command successful
|
|
// we'll fake it even if we didn't process the command, so as to
|
|
// allow detection of the MPU 401.
|
|
if (MPU.dataout.put(0xfe) == 0)
|
|
writelog(MIDILOG(3), "MPU_ACK error - output buffer full");
|
|
MPU.cmd.clearcommand(); // clear the command from the buffer
|
|
}
|
|
}
|
|
|
|
|
|
// MPU 401 data port/read: contains an MPU_ACK after receiving a command
|
|
// Will contain other data as well when other than UART mode is supported
|
|
|
|
Bit32u bx_sb16_c::mpu_dataread()
|
|
{
|
|
Bit8u res8bit;
|
|
Bit32u result;
|
|
|
|
// also acknowledge IRQ?
|
|
if ( MPU.irqpending != 0 )
|
|
{
|
|
MPU.irqpending = 0;
|
|
MIXER.reg[0x82] &= (~4);
|
|
if (MIXER.reg[0x82] == 0)
|
|
BX_SB16_THIS devices->pic->lower_irq(BX_SB16_IRQMPU);
|
|
writelog(MIDILOG(4), "MPU IRQ acknowledged");
|
|
}
|
|
|
|
if ( MPU.dataout.get(&res8bit) == 0)
|
|
{
|
|
writelog(MIDILOG(3), "MPU data port not ready - no data in buffer");
|
|
result = 0xff;
|
|
}
|
|
else
|
|
result = (Bit32u) res8bit;
|
|
|
|
writelog(MIDILOG(4), "MPU data port, result %02x", result);
|
|
|
|
return(result);
|
|
}
|
|
|
|
|
|
// MPU 401 data port/write: This is where the midi stream comes from,
|
|
// as well as arguments to any pending command
|
|
|
|
void bx_sb16_c::mpu_datawrite(Bit32u value)
|
|
{
|
|
writelog(MIDILOG(4), "write to MPU data port, value %02x", value);
|
|
|
|
if (MPU.cmd.hascommand() == 1)
|
|
{ // there is a command pending, add arguments to it
|
|
if (MPU.cmd.put(value) == 0)
|
|
writelog(MIDILOG(3), "MPU Command arguments too long - buffer full");
|
|
if (MPU.cmd.commanddone() == 1)
|
|
BX_SB16_THIS mpu_command(MPU.cmd.currentcommand());
|
|
}
|
|
else if ( (MPU.uartmode == 0) && (MPU.singlecommand == 0) )
|
|
{
|
|
// Hm? No UART mode, but still data? Maybe should send it
|
|
// to the command port... Only SBMPU401.EXE does this...
|
|
writelog(MIDILOG(4), "MPU Data %02x received but no UART mode. Assuming it's a command.", value);
|
|
mpu_command(value);
|
|
return;
|
|
}
|
|
else // no MPU command pending, in UART mode, this has to be midi data
|
|
mpu_mididata(value);
|
|
return;
|
|
}
|
|
|
|
// A byte of midi data has been received
|
|
void bx_sb16_c::mpu_mididata(Bit32u value)
|
|
{
|
|
// first, find out if it is a midi command or midi data
|
|
Boolean ismidicommand = 0;
|
|
if (value >= 0x80)
|
|
{ // bit 8 usually denotes a midi command...
|
|
ismidicommand = 1;
|
|
if ( (value == 0xf7) && (MPU.midicmd.currentcommand() == 0xf0) )
|
|
// ...except if it is a continuing SYSEX message, then it just
|
|
// denotes the end of a SYSEX chunk, not the start of a message
|
|
{
|
|
ismidicommand = 0; // first, it's not a command
|
|
MPU.midicmd.newcommand(MPU.midicmd.currentcommand(),
|
|
MPU.midicmd.bytes());
|
|
// Then, set needed bytes to current buffer
|
|
// because we didn't know the length before
|
|
}
|
|
}
|
|
|
|
if (ismidicommand == 1)
|
|
{ // this is a command, check if an old one is pending
|
|
if (MPU.midicmd.hascommand() == 1)
|
|
{
|
|
writelog(MIDILOG(3), "Midi command %02x incomplete, has %d of %d bytes.",
|
|
MPU.midicmd.currentcommand(), MPU.midicmd.bytes(),
|
|
MPU.midicmd.commandbytes() );
|
|
// write as much as we can. Should we do this?
|
|
processmidicommand(0);
|
|
// clear the pending command
|
|
MPU.midicmd.clearcommand();
|
|
MPU.midicmd.flush();
|
|
}
|
|
|
|
// find the number of arguments to the command
|
|
static const signed eventlength[] = { 2, 2, 2, 2, 1, 1, 2, 255};
|
|
// note - length 255 commands have unknown length
|
|
MPU.midicmd.newcommand(value, eventlength[ (value & 0x70) >> 4 ]);
|
|
}
|
|
else // no command, just arguments to the old command
|
|
{
|
|
if (MPU.midicmd.hascommand() == 0)
|
|
{ // no command pending, ignore the data
|
|
writelog(MIDILOG(3), "Midi data %02x received, but no command pending?", value);
|
|
return;
|
|
}
|
|
|
|
// just some data to the command
|
|
if (MPU.midicmd.put(value) == 0)
|
|
writelog(MIDILOG(3), "Midi buffer overflow!");
|
|
if (MPU.midicmd.commanddone() == 1)
|
|
{
|
|
// the command is complete, process it
|
|
writelog(MIDILOG(5), "Midi command %02x complete, has %d bytes.",
|
|
MPU.midicmd.currentcommand(), MPU.midicmd.bytes() );
|
|
processmidicommand(0);
|
|
// and remove the command from the buffer
|
|
MPU.midicmd.clearcommand();
|
|
MPU.midicmd.flush();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// The emulator port/read: See if commands were successful
|
|
|
|
Bit32u bx_sb16_c::emul_read()
|
|
{
|
|
Bit8u res8bit;
|
|
Bit32u result;
|
|
|
|
if ( EMUL.datain.get(&res8bit) == 0)
|
|
{
|
|
writelog(3, "emulator port not ready - no data in buffer");
|
|
result = 0x00;
|
|
}
|
|
else
|
|
result = (Bit32u) res8bit;
|
|
|
|
writelog(4, "emulator port, result %02x", result);
|
|
|
|
return(result);
|
|
}
|
|
|
|
// Emulator port/write: Changing instrument mapping etc.
|
|
|
|
void bx_sb16_c::emul_write(Bit32u value)
|
|
{
|
|
Bit8u value8;
|
|
|
|
writelog(4, "write to emulator port, value %02x", value);
|
|
|
|
if (EMUL.dataout.hascommand() == 0) // no command pending, set it up
|
|
{
|
|
static signed char cmdlength[] =
|
|
{ 0, 0, 4, 2, 6, 1, 0, 0, 1, 1, 0, 1};
|
|
if (value > 11)
|
|
{
|
|
writelog(3, "emulator command %02x unknown, ignored.", value);
|
|
return;
|
|
}
|
|
writelog(5, "emulator command %02x, needs %d arguments",
|
|
value, cmdlength[value]);
|
|
EMUL.dataout.newcommand(value, cmdlength[value]);
|
|
EMUL.datain.reset();
|
|
EMUL.datain.put(0xfe);
|
|
}
|
|
else
|
|
EMUL.dataout.put(value); // otherwise just add data
|
|
|
|
if (EMUL.dataout.commanddone() == 1)
|
|
{ // process the command
|
|
writelog(4, "executing emulator command %02x with %d arguments",
|
|
EMUL.dataout.currentcommand(), EMUL.dataout.bytes());
|
|
switch (EMUL.dataout.currentcommand())
|
|
{
|
|
case 0: // reinit of emulator
|
|
writelog(4, "Emulator reinitialized");
|
|
|
|
EMUL.remaps = 0;
|
|
|
|
EMUL.dataout.reset();
|
|
EMUL.datain.reset();
|
|
EMUL.datain.put(0xfe);
|
|
break;
|
|
case 1: // dummy command to reset state of emulator port
|
|
// just give a few times to end any commands
|
|
break;
|
|
case 2: // map bank
|
|
if (EMUL.remaps >= BX_SB16_PATCHTABLESIZE) break;
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbankmsb));
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbanklsb));
|
|
EMUL.remaplist[EMUL.remaps].oldprogch = 0xff;
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbankmsb));
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbanklsb));
|
|
EMUL.remaplist[EMUL.remaps].newprogch = 0xff;
|
|
EMUL.datain.put(4);
|
|
writelog(4, "Map bank command received, from %d %d to %d %d",
|
|
EMUL.remaplist[EMUL.remaps].oldbankmsb,
|
|
EMUL.remaplist[EMUL.remaps].oldbanklsb,
|
|
EMUL.remaplist[EMUL.remaps].newbankmsb,
|
|
EMUL.remaplist[EMUL.remaps].newbanklsb);
|
|
EMUL.remaps++;
|
|
break;
|
|
case 3: // map program change
|
|
if (EMUL.remaps >= BX_SB16_PATCHTABLESIZE) break;
|
|
EMUL.remaplist[EMUL.remaps].oldbankmsb = 0xff;
|
|
EMUL.remaplist[EMUL.remaps].oldbanklsb = 0xff;
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldprogch));
|
|
EMUL.remaplist[EMUL.remaps].newbankmsb = 0xff;
|
|
EMUL.remaplist[EMUL.remaps].newbanklsb = 0xff;
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newprogch));
|
|
EMUL.datain.put(2);
|
|
writelog(4, "Map program change received, from %d to %d",
|
|
EMUL.remaplist[EMUL.remaps].oldprogch,
|
|
EMUL.remaplist[EMUL.remaps].newprogch);
|
|
EMUL.remaps++;
|
|
break;
|
|
case 4: // map bank and program change
|
|
if (EMUL.remaps >= BX_SB16_PATCHTABLESIZE) break;
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbankmsb));
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbanklsb));
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldprogch));
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbankmsb));
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbanklsb));
|
|
EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newprogch));
|
|
EMUL.datain.put(6);
|
|
writelog(4, "Complete remap received, from %d %d %d to %d %d %d",
|
|
EMUL.remaplist[EMUL.remaps].oldbankmsb,
|
|
EMUL.remaplist[EMUL.remaps].oldbanklsb,
|
|
EMUL.remaplist[EMUL.remaps].oldprogch,
|
|
EMUL.remaplist[EMUL.remaps].newbankmsb,
|
|
EMUL.remaplist[EMUL.remaps].newbanklsb,
|
|
EMUL.remaplist[EMUL.remaps].newprogch);
|
|
|
|
EMUL.remaps++;
|
|
break;
|
|
case 5: EMUL.dataout.get(&value8); // dump emulator state
|
|
switch (value8)
|
|
{
|
|
case 0:
|
|
EMUL.datain.puts("SB16 Emulator for Bochs\n");
|
|
break;
|
|
case 1:
|
|
EMUL.datain.puts("UART mode=%d (force=%d)\n",
|
|
MPU.uartmode, MPU.forceuartmode);
|
|
break;
|
|
case 2:
|
|
EMUL.datain.puts("timer=%d\n", MPU.current_timer);
|
|
break;
|
|
case 3:
|
|
EMUL.datain.puts("%d remappings active\n", EMUL.remaps);
|
|
break;
|
|
case 4:
|
|
EMUL.datain.puts("Resources are A%3x I%d D%d H%d T%d P%3x; Adlib at %3x\n",
|
|
BX_SB16_IO, BX_SB16_IRQ, BX_SB16_DMAL,
|
|
BX_SB16_DMAH, 6, BX_SB16_IOMPU, BX_SB16_IOADLIB);
|
|
break;
|
|
case 5:
|
|
EMUL.datain.puts("Current OPL2/3 mode: %s",
|
|
// ok, I admit that this is a bit ugly...
|
|
(OPL.mode == single)?"single OPL2 (OPL3 disabled)\n":
|
|
(OPL.mode == adlib)?"single OPL2 (no OPL3)\n":
|
|
(OPL.mode == dual)?"double OPL2\n":
|
|
(OPL.mode == opl3)?"OPL3\n":
|
|
"unknown");
|
|
break;
|
|
default:
|
|
EMUL.datain.puts("no info. Only slots 0..5 have values.\n");
|
|
break;
|
|
}
|
|
break;
|
|
case 6: // close midi and wave files and/or output
|
|
if ( (bx_options.sb16.Omidimode->get () == 2) ||
|
|
(bx_options.sb16.Omidimode->get () == 3) )
|
|
{
|
|
if (bx_options.sb16.Omidimode->get () == 2)
|
|
finishmidifile();
|
|
fclose(MIDIDATA);
|
|
}
|
|
else if (bx_options.sb16.Omidimode->get () == 1)
|
|
BX_SB16_OUTPUT->closemidioutput();
|
|
bx_options.sb16.Omidimode->set (0);
|
|
|
|
if ( (bx_options.sb16.Owavemode->get () == 2) ||
|
|
(bx_options.sb16.Owavemode->get () == 3) )
|
|
{
|
|
if (bx_options.sb16.Owavemode->get () == 2)
|
|
finishvocfile();
|
|
fclose(WAVEDATA);
|
|
}
|
|
else
|
|
BX_SB16_OUTPUT->closewaveoutput();
|
|
bx_options.sb16.Owavemode->set (0);
|
|
|
|
break;
|
|
case 7: // clear bank/program mappings
|
|
EMUL.remaps = 0;
|
|
writelog(4, "Bank/program mappings cleared.");
|
|
break;
|
|
case 8: // set force uart mode on/off
|
|
EMUL.dataout.get(&value8);
|
|
MPU.forceuartmode = value8;
|
|
if (value8 != 0)
|
|
MPU.uartmode = MPU.forceuartmode;
|
|
writelog(4, "Force UART mode = %d", MPU.forceuartmode);
|
|
break;
|
|
case 9: // enter specific OPL2/3 mode
|
|
EMUL.dataout.get(&value8);
|
|
writelog(4, "Entering OPL2/3 mode %d", value8);
|
|
opl_entermode( (bx_sb16_fm_mode) value8);
|
|
break;
|
|
case 10: // check emulator present
|
|
EMUL.datain.put(0x55);
|
|
break;
|
|
case 11: // send data to midi device
|
|
EMUL.dataout.get(&value8);
|
|
mpu_mididata(value8);
|
|
}
|
|
EMUL.dataout.clearcommand();
|
|
EMUL.dataout.flush();
|
|
}
|
|
}
|
|
|
|
// and finally the OPL (FM emulation) part
|
|
|
|
// select a new operational mode for the FM part
|
|
// this also serves as reset for the OPL chip
|
|
void bx_sb16_c::opl_entermode(bx_sb16_fm_mode newmode)
|
|
{
|
|
int i, j;
|
|
|
|
// do nothing if the mode is unchanged
|
|
if ( OPL.mode == newmode)
|
|
return;
|
|
|
|
// if the old mode was 0, and the new mode is 3, then
|
|
// no reset is necessary, just set the flag
|
|
if ( (OPL.mode == single) && (newmode == opl3) )
|
|
{
|
|
writelog( MIDILOG(4), "OPL3 mode enabled");
|
|
OPL.mode = newmode;
|
|
return;
|
|
}
|
|
|
|
writelog( MIDILOG(4), "Switching to OPL mode %d from %d", newmode, OPL.mode);
|
|
|
|
for (i=0; i<BX_SB16_FM_NCH; i++)
|
|
opl_keyonoff(i, 0);
|
|
|
|
OPL.mode = newmode;
|
|
|
|
if (OPL.timer_running != 0)
|
|
{
|
|
bx_pc_system.deactivate_timer( OPL.timer_handle );
|
|
OPL.timer_running = 0;
|
|
}
|
|
|
|
OPL.drumchannel = 10;
|
|
|
|
OPL.midichannels = 0xffff; // all channels but the drum channel available
|
|
OPL.midichannels &= ~(1 << OPL.drumchannel);
|
|
|
|
for (i=0; i<2; i++)
|
|
{
|
|
OPL.wsenable[i] = 0;
|
|
OPL.tmask[i] = 0;
|
|
OPL.tflag[i] = 0;
|
|
OPL.percmode[i] = 0;
|
|
}
|
|
|
|
for (i=0; i<4; i++)
|
|
{
|
|
OPL.timer[i] = 0;
|
|
OPL.timerinit[i] = 0;
|
|
}
|
|
|
|
// initialize the operators
|
|
for (i=0; i<BX_SB16_FM_NOP; i++)
|
|
for (j=0; j<BX_SB16_FM_OPB; j++)
|
|
OPL.oper[i][j] = 0;
|
|
|
|
// TESTING for array bounds - compiler should bark if too high
|
|
OPL.oper[BX_SB16_FM_NOP-1][BX_SB16_FM_OPB-1] = 0;
|
|
|
|
// initialize the channels
|
|
|
|
// first zero all values
|
|
for (i=0; i<BX_SB16_FM_NCH; i++)
|
|
{
|
|
OPL.chan[i].nop = 0;
|
|
for (j=0; j<4; j++)
|
|
{
|
|
OPL.chan[i].opnum[j] = 0;
|
|
OPL.chan[i].outputlevel[j] = 0;
|
|
}
|
|
OPL.chan[i].freq = 0;
|
|
OPL.chan[i].afreq = 0;
|
|
OPL.chan[i].midichan = 0xff;
|
|
OPL.chan[i].needprogch = 0;
|
|
OPL.chan[i].midinote = 0;
|
|
OPL.chan[i].midibend = 0;
|
|
OPL.chan[i].midivol = 0;
|
|
}
|
|
|
|
// assign the operators
|
|
for (i=0; i<BX_SB16_FM_NCH; i++)
|
|
{
|
|
OPL.chan[i].nop = 2;
|
|
// who invented this absolutely insane operator grouping??
|
|
// it's like this: (ch 9...17 as 0...8 but higher operators)
|
|
// ch: 0 1 2 3 4 5 6 7 8
|
|
// op1: 0 1 2 6 7 8 12 13 14
|
|
// op2: 3 4 5 9 10 11 15 16 17
|
|
OPL.chan[i].opnum[0] = i + ( (int) (i / 3) ) * 3;
|
|
OPL.chan[i].opnum[1] = OPL.chan[i].opnum[0] + 3;
|
|
}
|
|
|
|
// assign 4-op operators to the appropriate channels
|
|
// note- they are not used unless .nop == 4
|
|
for (i=0; i<6; i++)
|
|
{
|
|
j = i + (i /3) * 6;
|
|
OPL.chan[j].opnum[2] = OPL.chan[j + 3].opnum[0];
|
|
OPL.chan[j].opnum[3] = OPL.chan[j + 3].opnum[1];
|
|
}
|
|
}
|
|
|
|
// this is called whenever one of the timer elapses
|
|
void bx_sb16_c::opl_timerevent()
|
|
{
|
|
for (int i=0; i<4; i++)
|
|
if ( (OPL.tmask[i/2] & (1 << (i % 2)) ) != 0)
|
|
{ // only running timers
|
|
if ( (OPL.timer[i]--) == 0)
|
|
{ // overflow occured, set flags accordingly
|
|
OPL.timer[i] = OPL.timerinit[i]; // reset the counter
|
|
if ( (OPL.tmask[i/2] >> (5 + i % 2) ) != 0) // set flags only if unmasked
|
|
{
|
|
OPL.tflag[i/2] &= 1 << (5 + i % 2); // set the overflow flag
|
|
OPL.tflag[i/2] &= 1 << 7; // set the IRQ flag
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// return the status of one of the OPL2's, or the
|
|
// base status of the OPL3
|
|
Bit32u bx_sb16_c::opl_status(int chipid)
|
|
{
|
|
Bit32u status = OPL.tflag[chipid];
|
|
|
|
if ( (OPL.mode == single) || (OPL.mode == opl3) )
|
|
status |= 0x06; // this is for OPL3 detections
|
|
|
|
writelog( MIDILOG(5), "OPL status of chip %d is %02x", chipid, status);
|
|
|
|
return status;
|
|
}
|
|
|
|
// set the register index for one of the OPL2's or the
|
|
// base or advanced register index for the OPL3
|
|
void bx_sb16_c::opl_index(Bit32u value, int chipid)
|
|
{
|
|
OPL.index[chipid] = value;
|
|
}
|
|
|
|
// write to the data port
|
|
void bx_sb16_c::opl_data(Bit32u value, int chipid)
|
|
{
|
|
int index = OPL.index[chipid];
|
|
int opernum = -1; // OPL3 operator number; 0..35
|
|
int channum = -1; // OPL3 channel number; 0..17
|
|
int subopnum = -1; // channel operator; 0..nop-1
|
|
|
|
writelog( MIDILOG(4), "Write to OPL(%d) register %02x: %02x",
|
|
chipid, index, value);
|
|
|
|
// first find out operator and/or channel numbers
|
|
// case 0x20 ... 0x95: includes too many ports, but that is harmless
|
|
// case 0xe0 ... 0xf5:
|
|
if ( ((index>=0x20) && (index<=0x95)) ||
|
|
((index>=0xe0) && (index<=0xf5)) ) {
|
|
// operator access
|
|
// find the operator number. 0..17 on chip 1, 18..35 on chip 2
|
|
|
|
// note, the numbers are not continuous (again...), so we need
|
|
// this rather weird calculation
|
|
opernum = index & 0x07;
|
|
if (opernum > 5) // invalid register, has no operator associated
|
|
{
|
|
opernum = -1;
|
|
goto break_here;
|
|
}
|
|
|
|
opernum += (index & 0x18) * 6;
|
|
if (opernum > 17) // Operators 18+ have to be accessed on other address set
|
|
{
|
|
opernum = -1;
|
|
goto break_here;
|
|
}
|
|
|
|
if (chipid == 1)
|
|
opernum += BX_SB16_FM_NOP / 2;
|
|
|
|
// find out the channel number, and which of the channel's operators this is
|
|
channum = opernum % 3 + ( (int) (opernum / 6) ) * 3;
|
|
subopnum = 0;
|
|
|
|
if ( (opernum % 6) > 2) // second operator
|
|
subopnum = 1;
|
|
|
|
// if (channel - 3) is in a four-operator mode, that is really
|
|
// what this operator belongs to
|
|
if (channum >= 3)
|
|
if ( OPL.chan[channum - 3].nop == 4 )
|
|
{
|
|
channum -= 3;
|
|
subopnum += 2;
|
|
}
|
|
writelog( MIDILOG(5), "Is Channel %d, Oper %d, Subop %d",
|
|
channum, opernum, subopnum);
|
|
}
|
|
else if ( (index>=0xa0) && (index<=0xc8) ) {
|
|
// channel access
|
|
channum = index & 0x0f;
|
|
if (OPL.chan[channum].nop == 0)
|
|
channum = -1; // the channel is disabled
|
|
writelog( MIDILOG(5), "Is channel %d", channum);
|
|
}
|
|
|
|
break_here:
|
|
|
|
switch (index & 0xff)
|
|
{
|
|
|
|
// WSEnable and Test Register
|
|
case 0x01:
|
|
OPL.wsenable[chipid] = (value >> 5) & 1;
|
|
if ( (value & 0x1f) != 0)
|
|
writelog( MIDILOG(3), "Warning: Test Register set to %02x", value & 0x1f);
|
|
break;
|
|
|
|
// the two timer counts
|
|
case 0x02:
|
|
case 0x03:
|
|
OPL.timerinit[(index - 2) + chipid * 2] =
|
|
OPL.timer[(index - 2) + chipid * 2] = value;
|
|
break;
|
|
|
|
// if OPL2: timer masks
|
|
// if OPL3: 4-operator modes
|
|
case 0x04:
|
|
if ( (chipid == 0) || (OPL.mode == dual) )
|
|
opl_settimermask(value, chipid);
|
|
else
|
|
opl_set4opmode(value & 0x3f);
|
|
break;
|
|
|
|
// only OPL3: OPL3 enable
|
|
case 0x05:
|
|
if ( ( OPL.mode == single ) || ( OPL.mode == opl3 ) )
|
|
{
|
|
if ( (value & 1) != 0)
|
|
opl_entermode(opl3);
|
|
else
|
|
opl_entermode(single);
|
|
break;
|
|
}
|
|
// otherwise let default: catch it
|
|
|
|
// Composite Sine Wave and Note-sel (ignored)
|
|
case 0x08:
|
|
if (value != 0)
|
|
writelog( MIDILOG(3),
|
|
"Warning: write of %02x to CSW/Note-sel ignored",
|
|
value);
|
|
break;
|
|
|
|
// most importantly the percussion part
|
|
case 0xbd:
|
|
opl_setpercussion(value, chipid);
|
|
break;
|
|
|
|
// the operator registers
|
|
// case 0x20 ... 0x35:
|
|
case 0x20:
|
|
case 0x21:
|
|
case 0x22:
|
|
case 0x23:
|
|
case 0x24:
|
|
case 0x25:
|
|
case 0x26:
|
|
case 0x27:
|
|
case 0x28:
|
|
case 0x29:
|
|
case 0x2a:
|
|
case 0x2b:
|
|
case 0x2c:
|
|
case 0x2d:
|
|
case 0x2e:
|
|
case 0x2f:
|
|
case 0x30:
|
|
case 0x31:
|
|
case 0x32:
|
|
case 0x33:
|
|
case 0x34:
|
|
case 0x35:
|
|
// case 0x60 ... 0x75:
|
|
case 0x60:
|
|
case 0x61:
|
|
case 0x62:
|
|
case 0x63:
|
|
case 0x64:
|
|
case 0x65:
|
|
case 0x66:
|
|
case 0x67:
|
|
case 0x68:
|
|
case 0x69:
|
|
case 0x6a:
|
|
case 0x6b:
|
|
case 0x6c:
|
|
case 0x6d:
|
|
case 0x6e:
|
|
case 0x6f:
|
|
case 0x70:
|
|
case 0x71:
|
|
case 0x72:
|
|
case 0x73:
|
|
case 0x74:
|
|
case 0x75:
|
|
// case 0x80 ... 0x95:
|
|
case 0x80:
|
|
case 0x81:
|
|
case 0x82:
|
|
case 0x83:
|
|
case 0x84:
|
|
case 0x85:
|
|
case 0x86:
|
|
case 0x87:
|
|
case 0x88:
|
|
case 0x89:
|
|
case 0x8a:
|
|
case 0x8b:
|
|
case 0x8c:
|
|
case 0x8d:
|
|
case 0x8e:
|
|
case 0x8f:
|
|
case 0x90:
|
|
case 0x91:
|
|
case 0x92:
|
|
case 0x93:
|
|
case 0x94:
|
|
case 0x95:
|
|
if (opernum != -1)
|
|
{
|
|
opl_changeop(channum, opernum, (index / 0x20) - 1, value);
|
|
break;
|
|
}
|
|
// else let default: catch it
|
|
|
|
// case 0x40 ... 0x55:
|
|
case 0x40:
|
|
case 0x41:
|
|
case 0x42:
|
|
case 0x43:
|
|
case 0x44:
|
|
case 0x45:
|
|
case 0x46:
|
|
case 0x47:
|
|
case 0x48:
|
|
case 0x49:
|
|
case 0x4a:
|
|
case 0x4b:
|
|
case 0x4c:
|
|
case 0x4d:
|
|
case 0x4e:
|
|
case 0x4f:
|
|
case 0x50:
|
|
case 0x51:
|
|
case 0x52:
|
|
case 0x53:
|
|
case 0x54:
|
|
case 0x55:
|
|
if (opernum != -1)
|
|
{
|
|
opl_changeop(channum, opernum, 1, value & 0xc0);
|
|
if (subopnum != -1)
|
|
opl_setvolume(channum, subopnum, value & 0x3f);
|
|
break;
|
|
}
|
|
// else let default: catch it
|
|
|
|
// case 0xe0 ... 0xf5:
|
|
case 0xe0:
|
|
case 0xe1:
|
|
case 0xe2:
|
|
case 0xe3:
|
|
case 0xe4:
|
|
case 0xe5:
|
|
case 0xe6:
|
|
case 0xe7:
|
|
case 0xe8:
|
|
case 0xe9:
|
|
case 0xea:
|
|
case 0xeb:
|
|
case 0xec:
|
|
case 0xed:
|
|
case 0xee:
|
|
case 0xef:
|
|
case 0xf0:
|
|
case 0xf1:
|
|
case 0xf2:
|
|
case 0xf3:
|
|
case 0xf4:
|
|
case 0xf5:
|
|
if (opernum != -1)
|
|
{
|
|
opl_changeop(channum, opernum, 5, value & 0x07);
|
|
break;
|
|
}
|
|
// else let default: catch it
|
|
|
|
|
|
// and the channel registers
|
|
// case 0xa0 ... 0xa8:
|
|
case 0xa0:
|
|
case 0xa1:
|
|
case 0xa2:
|
|
case 0xa3:
|
|
case 0xa4:
|
|
case 0xa5:
|
|
case 0xa6:
|
|
case 0xa7:
|
|
case 0xa8:
|
|
if (channum != -1)
|
|
{
|
|
OPL.chan[channum].freq &= 0xff00;
|
|
OPL.chan[channum].freq |= value;
|
|
if ( (OPL.chan[channum].freqch |= 1) == 3)
|
|
opl_setfreq(channum);
|
|
break;
|
|
}
|
|
// else let default: catch it
|
|
|
|
// case 0xb0 ... 0xb8:
|
|
case 0xb0:
|
|
case 0xb1:
|
|
case 0xb2:
|
|
case 0xb3:
|
|
case 0xb4:
|
|
case 0xb5:
|
|
case 0xb6:
|
|
case 0xb7:
|
|
case 0xb8:
|
|
if (channum != -1)
|
|
{
|
|
OPL.chan[channum].freq &= 0x00ff;
|
|
OPL.chan[channum].freq |= (value & 0x1f) << 8;
|
|
if ( (OPL.chan[channum].freqch |= 2) == 3)
|
|
opl_setfreq(channum);
|
|
opl_keyonoff(channum, (value >> 5) & 1);
|
|
break;
|
|
}
|
|
// else let default: catch it
|
|
|
|
|
|
// this is a channel access, but it belongs to the instrument
|
|
// definition, so put it into value [4] of the channel's first operator
|
|
// case 0xc0 ... 0xc8:
|
|
case 0xc0:
|
|
case 0xc1:
|
|
case 0xc2:
|
|
case 0xc3:
|
|
case 0xc4:
|
|
case 0xc5:
|
|
case 0xc6:
|
|
case 0xc7:
|
|
case 0xc8:
|
|
if (channum != -1)
|
|
{
|
|
int needchange = 0;
|
|
if ((OPL.oper[OPL.chan[channum].opnum[0]][4] & 1) != (int)(value & 1))
|
|
needchange = 1;
|
|
|
|
opl_changeop(channum, OPL.chan[channum].opnum[0], 4, value & 0x3f);
|
|
|
|
if (needchange == 1)
|
|
opl_setmodulation(channum);
|
|
break;
|
|
}
|
|
// else let default: catch it
|
|
|
|
default:
|
|
writelog( MIDILOG(3), "Attempt to write %02x to unknown OPL(%d) register %02x",
|
|
value, chipid, index);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// change a value of an operator
|
|
void bx_sb16_c::opl_changeop(int channum, int opernum, int byte, int value)
|
|
{
|
|
if (OPL.oper[opernum][byte] != value)
|
|
{
|
|
OPL.oper[opernum][byte] = value;
|
|
OPL.chan[channum].needprogch = 1;
|
|
}
|
|
}
|
|
|
|
// called for a write to the 4-operator mode register
|
|
void bx_sb16_c::opl_set4opmode(int new4opmode)
|
|
{
|
|
int i, channel1, channel2;
|
|
|
|
writelog( MIDILOG(4), "Switching to 4-op mode %02x", new4opmode);
|
|
|
|
// every bit switches a 4-op channel-pairing on or off
|
|
// 4-op mode is two channels combined into the first one
|
|
for (i = 0; i<6; i++)
|
|
{
|
|
channel1 = i + (i / 3) * 6;
|
|
channel2 = channel1 + 3;
|
|
|
|
if ( ( (new4opmode >> i) & 1) != 0)
|
|
{ // enable 4-op mode
|
|
opl_keyonoff(channel1, 0);
|
|
opl_keyonoff(channel2, 0);
|
|
|
|
OPL.chan[channel1].nop = 4;
|
|
OPL.chan[channel2].nop = 0;
|
|
|
|
OPL.chan[channel1].needprogch = 1;
|
|
}
|
|
else
|
|
{ // disable 4-op mode
|
|
opl_keyonoff(channel1, 0);
|
|
|
|
OPL.chan[channel1].nop = 2;
|
|
OPL.chan[channel2].nop = 2;
|
|
|
|
OPL.chan[channel1].needprogch = 1;
|
|
OPL.chan[channel2].needprogch = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// called for a write to port 4 of either chip
|
|
void bx_sb16_c::opl_settimermask(int value, int chipid)
|
|
{
|
|
if ( (value & 0x80) != 0) // reset IRQ and timer flags
|
|
{ // all other bits ignored!
|
|
writelog( MIDILOG(5), "IRQ Reset called");
|
|
OPL.tflag[chipid] = 0;
|
|
return;
|
|
}
|
|
|
|
OPL.tmask[chipid] = value & 0x63;
|
|
writelog( MIDILOG(5), "New timer mask for chip %d is %02x",
|
|
chipid, OPL.tmask[chipid]);
|
|
|
|
// do we have to activate or deactivate the timer?
|
|
if ( ( (value & 0x03) != 0) ^ (OPL.timer_running != 0) )
|
|
if ( (value & 0x03) != 0) // yes, it's different. Start or stop?
|
|
{
|
|
writelog( MIDILOG(5), "Starting timers");
|
|
bx_pc_system.activate_timer( OPL.timer_handle, 0, 1);
|
|
OPL.timer_running = 1;
|
|
}
|
|
else
|
|
{
|
|
writelog( MIDILOG(5), "Stopping timers");
|
|
bx_pc_system.deactivate_timer( OPL.timer_handle );
|
|
OPL.timer_running = 0;
|
|
}
|
|
}
|
|
|
|
// called when the modulation mode of a channel changes
|
|
void bx_sb16_c::opl_setmodulation(int channel)
|
|
{
|
|
int opernum = OPL.chan[channel].opnum[0];
|
|
|
|
if ( (OPL.chan[channel].nop == 0) &&
|
|
(channel >= 3) &&
|
|
(OPL.chan[channel].nop == 4) )
|
|
channel -= 3;
|
|
|
|
if (OPL.chan[channel].nop == 2)
|
|
{
|
|
OPL.chan[channel].ncarr = (OPL.oper[opernum][4] & 1) + 1;
|
|
OPL.chan[channel].needprogch = 1;
|
|
}
|
|
else if (OPL.chan[channel].nop == 4)
|
|
{
|
|
int opernum2 = OPL.chan[channel].opnum[2];
|
|
int modmode = (OPL.oper[opernum][4] & 1) |
|
|
( (OPL.oper[opernum2][4] & 1) >> 1);
|
|
OPL.chan[channel].ncarr = modmode + 1 - (modmode / 2);
|
|
OPL.chan[channel].needprogch = 1;
|
|
}
|
|
}
|
|
|
|
// called for a write to register 0xbd, the percussion register
|
|
void bx_sb16_c::opl_setpercussion(Bit8u value, int chipid)
|
|
{
|
|
UNUSED(value);
|
|
UNUSED(chipid);
|
|
}
|
|
|
|
// called when a channel volume changes
|
|
// opnum is which of the channel's operators had the change, not
|
|
// the actual operator number. Thus, it's from 0..3.
|
|
void bx_sb16_c::opl_setvolume(int channel, int opnum, int outlevel)
|
|
{
|
|
UNUSED(opnum);
|
|
UNUSED(outlevel);
|
|
|
|
OPL.chan[channel].midivol = 127;
|
|
}
|
|
|
|
|
|
// called when a frequency change is complete, to find out the
|
|
// corresponding midi key and pitch bender values
|
|
void bx_sb16_c::opl_setfreq(int channel)
|
|
{
|
|
int block,fnum;
|
|
|
|
OPL.chan[channel].freqch = 0;
|
|
|
|
// definition:
|
|
// low-byte of freq: 8 bit F-Number, LSB's
|
|
// high-byte of freq: [2 reserved][KEY-ON][3 block][2 F-Number MSB's]
|
|
// [KEY-ON] is ignored by this function
|
|
//
|
|
// the definition of the F-number is
|
|
// F-Number = Frequency * 2**(20-block) / (49716 Hz)
|
|
//
|
|
// Thus, the frequency can be calculated as
|
|
// Frequency = F-Number / 2**(20-block) * 49716 Hz
|
|
//
|
|
// (But remember that afreq is in 10^-3 Hz!)
|
|
//
|
|
|
|
fnum = OPL.chan[channel].freq & 0x3ff;
|
|
block = (OPL.chan[channel].freq >> 10) & 0x07;
|
|
|
|
writelog( MIDILOG(5), "F-Num is %d, block is %d", fnum, block);
|
|
|
|
Bit32u realfreq;
|
|
const Bit32u freqbase = 49716000; // const is better than #define if type is important
|
|
|
|
// this is a bit messy to preserve accuracy as much as possible,
|
|
// otherwise we might either lose precision, or the higher bits.
|
|
if (block < 16)
|
|
realfreq = ( (freqbase >> 4) * fnum) >> (16 - block);
|
|
else
|
|
realfreq = (freqbase * fnum) >> (20 - block);
|
|
|
|
OPL.chan[channel].afreq = realfreq;
|
|
|
|
// now find out what MIDI key this corresponds to, and with what
|
|
// pitch bender value... (the latter not implemented yet)
|
|
int octave=0; // 0: Octave from 523.2511 Hz; pos=higher, neg=lower
|
|
int keynum=0; // 0=C; 1=C#; 2=D; ...; 11=B
|
|
|
|
if (realfreq > 8175) // 8.175 is smallest possible frequency
|
|
{
|
|
const Bit32u freqC = 523251; // Midi note 72; "C": 523.251 Hz
|
|
Bit32u keyfreq; // Frequency scaled to the octave from freqC to 2*freqC
|
|
|
|
if (realfreq > freqC)
|
|
{
|
|
while ( (realfreq >> (++octave)) > freqC);
|
|
|
|
keyfreq = realfreq >> (--octave);
|
|
}
|
|
else
|
|
{
|
|
while ( (realfreq << (++octave)) < freqC);
|
|
|
|
keyfreq = realfreq << (--octave);
|
|
|
|
octave = -octave;
|
|
}
|
|
|
|
// this is a reasonable approximation for keyfreq /= 1.059463
|
|
// (that value is 2**(1/12), which is the difference between two keys)
|
|
while ( (keyfreq -= ( (keyfreq * 1000) / 17817) ) > freqC)
|
|
keynum++;
|
|
}
|
|
else
|
|
{
|
|
octave = -6;
|
|
keynum = 0;
|
|
}
|
|
|
|
OPL.chan[channel].midinote = (octave + 6) * 12 + keynum;
|
|
|
|
writelog( MIDILOG(5), "New frequency %.3f is key %d in octave %d; midi note %d",
|
|
(float) realfreq/1000.0, keynum, octave, OPL.chan[channel].midinote);
|
|
}
|
|
|
|
// called when a note is possibly turned on or off
|
|
void bx_sb16_c::opl_keyonoff(int channel, Boolean onoff)
|
|
{
|
|
int i;
|
|
|
|
// first check if there really is a change in the state
|
|
if (onoff == OPL.chan[channel].midion)
|
|
return;
|
|
|
|
Bit8u commandbytes[3];
|
|
|
|
// check if we have a midi channel, otherwise allocate one if possible
|
|
if (OPL.chan[channel].midichan == 0xff)
|
|
{
|
|
for (i=0; i<16; i++)
|
|
if ( ( ( OPL.midichannels >> i) & 1 ) != 0)
|
|
{
|
|
OPL.chan[channel].midichan = i;
|
|
OPL.midichannels &= ~(1 << i); // mark channel as used
|
|
OPL.chan[channel].needprogch = 1;
|
|
}
|
|
if (OPL.chan[channel].midichan == 0xff)
|
|
return;
|
|
}
|
|
|
|
if (OPL.chan[channel].needprogch != 0)
|
|
opl_midichannelinit(channel);
|
|
|
|
commandbytes[0] = OPL.chan[channel].midichan;
|
|
commandbytes[1] = OPL.chan[channel].midinote;
|
|
commandbytes[2] = 0;
|
|
|
|
if (onoff == 0)
|
|
commandbytes[0] |= 0x80; // turn it off
|
|
else
|
|
{
|
|
commandbytes[0] |= 0x90; // turn it on
|
|
commandbytes[1] = OPL.chan[channel].midivol;
|
|
}
|
|
|
|
writemidicommand(commandbytes[1], 2, & (commandbytes[1]) );
|
|
}
|
|
|
|
// setup a midi channel
|
|
void bx_sb16_c::opl_midichannelinit(int channel)
|
|
{
|
|
UNUSED(channel);
|
|
}
|
|
|
|
|
|
/* Handlers for the midi commands/midi file output */
|
|
|
|
// Write the header of the midi file. Track length is 0x7fffffff
|
|
// until we know how long it's really going to be
|
|
|
|
void bx_sb16_c::initmidifile()
|
|
{
|
|
struct {
|
|
Bit8u chunk[4];
|
|
Bit32u chunklen; // all values in BIG Endian!
|
|
Bit16u smftype;
|
|
Bit16u tracknum;
|
|
Bit16u timecode; // 0x80 + deltatimesperquarter << 8
|
|
} midiheader =
|
|
#ifdef BX_LITTLE_ENDIAN
|
|
{ "MTh", 0x06000000, 0, 0x0100, 0x8001 };
|
|
#else
|
|
{ "MTh", 6, 0, 1, 0x180 };
|
|
#endif
|
|
midiheader.chunk[3] = 'd';
|
|
|
|
struct {
|
|
Bit8u chunk[4];
|
|
Bit32u chunklen;
|
|
Bit8u data[15];
|
|
} trackheader =
|
|
#ifdef BX_LITTLE_ENDIAN
|
|
{ "MTr", 0xffffff7f,
|
|
#else
|
|
{ "MTr", 0x7fffffff,
|
|
#endif
|
|
{ 0x00,0xff,0x51,3,0x07,0xa1,0x20, // set tempo 120 (0x7a120 us per quarter)
|
|
0x00,0xff,0x58,4,4,2,0x18,0x08 }}; // time sig 4/4
|
|
trackheader.chunk[3] = 'k';
|
|
|
|
fwrite(&midiheader, 1, 14, MIDIDATA );
|
|
fwrite(&trackheader, 1, 23, MIDIDATA );
|
|
return;
|
|
}
|
|
|
|
// write the midi command to the midi file
|
|
|
|
void bx_sb16_c::writemidicommand(int command, int length, Bit8u data[])
|
|
{
|
|
/* We need to determine the time elapsed since the last MIDI command */
|
|
int deltatime = currentdeltatime();
|
|
|
|
/* Initialize output device if necessary and not done yet */
|
|
if (bx_options.sb16.Omidimode->get () == 1)
|
|
{
|
|
if (MPU.outputinit != 1)
|
|
{
|
|
writelog( MIDILOG(4), "Initializing Midi output.");
|
|
if (BX_SB16_OUTPUT->openmidioutput(bx_options.sb16.Omidifile->getptr ()) == BX_SOUND_OUTPUT_OK)
|
|
MPU.outputinit = 1;
|
|
else
|
|
MPU.outputinit = 0;
|
|
if (MPU.outputinit != 1)
|
|
{
|
|
writelog( MIDILOG(2), "Error: Couldn't open midi output. Midi disabled.");
|
|
bx_options.sb16.Omidimode->set (0);
|
|
}
|
|
}
|
|
BX_SB16_OUTPUT->sendmidicommand(deltatime, command, length, data);
|
|
return;
|
|
}
|
|
else if (bx_options.sb16.Omidimode->get () < 2)
|
|
return;
|
|
|
|
if (bx_options.sb16.Omidimode->get () == 2)
|
|
writedeltatime(deltatime);
|
|
|
|
fputc(command, MIDIDATA);
|
|
if ( (command == 0xf0) ||
|
|
(command == 0xf7) ) // write event length for sysex/meta events
|
|
writedeltatime(length);
|
|
|
|
fwrite(data, 1, length, MIDIDATA);
|
|
}
|
|
|
|
|
|
// determine how many delta times have passed since
|
|
// this function was called last
|
|
|
|
int bx_sb16_c::currentdeltatime()
|
|
{
|
|
int deltatime;
|
|
|
|
// counting starts at first access
|
|
if (MPU.last_delta_time == 0xffffffff)
|
|
MPU.last_delta_time = MPU.current_timer;
|
|
|
|
deltatime = MPU.current_timer - MPU.last_delta_time;
|
|
MPU.last_delta_time = MPU.current_timer;
|
|
|
|
return deltatime;
|
|
}
|
|
|
|
|
|
// process the midi command stored in MPU.midicmd.to the midi driver
|
|
|
|
void bx_sb16_c::processmidicommand(Boolean force)
|
|
{
|
|
int i, channel;
|
|
Bit8u value;
|
|
Boolean needremap = 0;
|
|
|
|
channel = MPU.midicmd.currentcommand() & 0xf;
|
|
|
|
// we need to log bank changes and program changes
|
|
if ( (MPU.midicmd.currentcommand() >> 4) == 0xc)
|
|
{ // a program change
|
|
value = MPU.midicmd.peek(0);
|
|
writelog(MIDILOG(1), "* ProgramChange channel %d to %d",
|
|
channel, value);
|
|
MPU.program[channel] = value;
|
|
needremap = 1;
|
|
}
|
|
else if ( (MPU.midicmd.currentcommand() >> 4) == 0xb)
|
|
{ // a control change, could be a bank change
|
|
if (MPU.midicmd.peek(0) == 0)
|
|
{ // bank select MSB
|
|
value = MPU.midicmd.peek(1);
|
|
writelog(MIDILOG(1), "* BankSelectMSB (%x %x %x) channel %d to %d",
|
|
MPU.midicmd.peek(0), MPU.midicmd.peek(1), MPU.midicmd.peek(2),
|
|
channel, value);
|
|
MPU.bankmsb[channel] = value;
|
|
needremap = 1;
|
|
}
|
|
else if (MPU.midicmd.peek(0) == 32)
|
|
{ // bank select LSB
|
|
value = MPU.midicmd.peek(1);
|
|
writelog(MIDILOG(1), "* BankSelectLSB channel %d to %d",
|
|
channel, value);
|
|
MPU.banklsb[channel] = value;
|
|
needremap = 1;
|
|
}
|
|
}
|
|
|
|
Bit8u temparray[256];
|
|
i = 0;
|
|
while (MPU.midicmd.empty() == 0)
|
|
MPU.midicmd.get( &(temparray[i++]) );
|
|
|
|
writemidicommand(MPU.midicmd.currentcommand(), i, temparray);
|
|
|
|
// if single command, revert to command mode
|
|
if (MPU.singlecommand != 0)
|
|
{
|
|
MPU.singlecommand = 0;
|
|
// and trigger IRQ?
|
|
// MPU.irqpending = 1;
|
|
// BX_SB16_THIS devices->pic->trigger_irq(BX_SB16_IRQMPU);
|
|
}
|
|
|
|
if ( (force == 0) && (needremap == 1) )
|
|
// have to check the remap lists, and remap program change if necessary
|
|
midiremapprogram(channel);
|
|
}
|
|
|
|
|
|
// check if a program change has to be remapped, and do it if necessary
|
|
|
|
void bx_sb16_c::midiremapprogram(int channel)
|
|
{
|
|
int bankmsb,banklsb,program;
|
|
Bit8u commandbytes[2];
|
|
|
|
bankmsb = MPU.bankmsb[channel];
|
|
banklsb = MPU.banklsb[channel];
|
|
program = MPU.program[channel];
|
|
|
|
for (int i = 0; i < EMUL.remaps; i++)
|
|
{
|
|
if ( ( (EMUL.remaplist[i].oldbankmsb == bankmsb) ||
|
|
(EMUL.remaplist[i].oldbankmsb == 0xff) ) &&
|
|
( (EMUL.remaplist[i].oldbanklsb == banklsb) ||
|
|
(EMUL.remaplist[i].oldbanklsb == 0xff) ) &&
|
|
( (EMUL.remaplist[i].oldprogch == program) ||
|
|
(EMUL.remaplist[i].oldprogch == 0xff) ) )
|
|
{
|
|
writelog(5, "Remapping instrument for channel %d", channel);
|
|
if ( (EMUL.remaplist[i].newbankmsb != bankmsb) &&
|
|
(EMUL.remaplist[i].newbankmsb != 0xff) )
|
|
{ // write control change bank msb
|
|
MPU.bankmsb[channel] = EMUL.remaplist[i].newbankmsb;
|
|
commandbytes[0] = 0;
|
|
commandbytes[1] = EMUL.remaplist[i].newbankmsb;
|
|
writemidicommand(0xb0 | channel, 2, commandbytes);
|
|
}
|
|
if ( (EMUL.remaplist[i].newbanklsb != banklsb) &&
|
|
(EMUL.remaplist[i].newbanklsb != 0xff) )
|
|
{ // write control change bank lsb
|
|
MPU.banklsb[channel] = EMUL.remaplist[i].newbanklsb;
|
|
commandbytes[0] = 32;
|
|
commandbytes[1] = EMUL.remaplist[i].newbanklsb;
|
|
writemidicommand(0xb0 | channel, 2, commandbytes);
|
|
}
|
|
if ( (EMUL.remaplist[i].newprogch != program) &&
|
|
(EMUL.remaplist[i].newprogch != 0xff) )
|
|
{ // write program change
|
|
MPU.program[channel] = EMUL.remaplist[i].newprogch;
|
|
commandbytes[0] = EMUL.remaplist[i].newprogch;
|
|
writemidicommand(0xc0 | channel, 1, commandbytes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// convert a number into a delta time coded value
|
|
int bx_sb16_c::converttodeltatime(Bit32u deltatime, Bit8u value[4])
|
|
{
|
|
int i, count;
|
|
Bit8u outbytes[4];
|
|
|
|
count = 0;
|
|
|
|
if (deltatime <= 0)
|
|
{
|
|
count = 1;
|
|
value[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
while ( (deltatime > 0) && (count < 4) ) // split into parts
|
|
{ // of seven bits
|
|
outbytes[count++] = deltatime & 0x7f;
|
|
deltatime >>= 7;
|
|
};
|
|
for (i=0; i<count; i++) // reverse order and
|
|
value[i] = outbytes[count - i - 1] | 0x80; // set eighth bit on
|
|
value[count - 1] &= 0x7f; // all but last byte
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
// write a delta time coded value to the midi file
|
|
void bx_sb16_c::writedeltatime(Bit32u deltatime)
|
|
{
|
|
Bit8u outbytes[4];
|
|
int count,i;
|
|
|
|
count = converttodeltatime(deltatime, outbytes);
|
|
|
|
for (i=0; i<count; i++)
|
|
fputc(outbytes[i], MIDIDATA );
|
|
}
|
|
|
|
|
|
// close the midi file, and set the track length accordingly
|
|
|
|
void bx_sb16_c::finishmidifile()
|
|
{
|
|
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 );
|
|
|
|
Bit32u tracklen = ftell(MIDIDATA);
|
|
if (tracklen < 0)
|
|
BX_PANIC (("ftell failed in finishmidifile"));
|
|
if (tracklen < 22)
|
|
BX_PANIC (("finishmidifile with track length too short"));
|
|
tracklen -= 22; // subtract the midi file and track header
|
|
fseek(MIDIDATA, 22 - 4, SEEK_SET);
|
|
// value has to be in big endian
|
|
#ifdef BX_LITTLE_ENDIAN
|
|
tracklen = (tracklen << 24) | (tracklen >> 24) |
|
|
((tracklen & 0x00ff0000) >> 8) |
|
|
((tracklen & 0x0000ff00) << 8);
|
|
#endif
|
|
fwrite(&tracklen, 4, 1, MIDIDATA);
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/* Handlers for the voc file output */
|
|
|
|
// Write the header of the voc file.
|
|
|
|
void bx_sb16_c::initvocfile()
|
|
{
|
|
struct {
|
|
char id[20];
|
|
Bit16u headerlen; // All in LITTLE Endian!
|
|
Bit16u version;
|
|
Bit16u chksum;
|
|
} vocheader =
|
|
{ "Creative Voice File",
|
|
#ifdef BX_LITTLE_ENDIAN
|
|
0x1a, 0x0114, 0x111f };
|
|
#else
|
|
0x1a00, 0x1401, 0x1f11 };
|
|
#endif
|
|
|
|
vocheader.id[19] = 26; // Replace string end with 26
|
|
|
|
fwrite(&vocheader, 1, sizeof vocheader, WAVEDATA);
|
|
}
|
|
|
|
// write one block to the voc file
|
|
void bx_sb16_c::writevocblock(int block,
|
|
Bit32u headerlen, Bit8u header[],
|
|
Bit32u datalen, Bit8u data[])
|
|
{
|
|
Bit32u i;
|
|
|
|
if (block > 9)
|
|
{
|
|
writelog( WAVELOG(3), "VOC Block %d not recognized, ignored.", block);
|
|
return;
|
|
}
|
|
|
|
fputc(block, WAVEDATA);
|
|
|
|
i = headerlen + datalen;
|
|
#ifdef BX_LITTLE_ENDIAN
|
|
fwrite(&i, 1, 3, WAVEDATA); // write the length in 24-bit little endian
|
|
#else
|
|
Bit8u lengthbytes[3];
|
|
lengthbytes[0] = i & 0xff; i >>= 8;
|
|
lengthbytes[1] = i & 0xff; i >>= 8;
|
|
lengthbytes[2] = i & 0xff;
|
|
fwrite(lengthbytes, 1, 3, WAVEDATA);
|
|
#endif
|
|
writelog( WAVELOG(5), "Voc block %d; Headerlen %d; Datalen %d",
|
|
block, headerlen, datalen );
|
|
if (headerlen > 0)
|
|
fwrite(header, 1, headerlen, WAVEDATA);
|
|
if (datalen > 0)
|
|
fwrite(data, 1, datalen, WAVEDATA);
|
|
}
|
|
|
|
|
|
// close the voc file
|
|
void bx_sb16_c::finishvocfile()
|
|
{
|
|
fputc(0, WAVEDATA); // blocktype 0: end block
|
|
}
|
|
|
|
|
|
// static IO port read callback handler
|
|
// redirects to non-static class handler to avoid virtual functions
|
|
|
|
Bit32u bx_sb16_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len)
|
|
{
|
|
#if !BX_USE_SB16_SMF
|
|
bx_sb16_c *class_ptr = (bx_sb16_c *) this_ptr;
|
|
|
|
return( class_ptr->read(address, io_len) );
|
|
}
|
|
|
|
|
|
Bit32u bx_sb16_c::read(Bit32u address, unsigned io_len)
|
|
{
|
|
#else
|
|
UNUSED(this_ptr);
|
|
#endif // !BX_USE_SB16_SMF
|
|
|
|
// we support only byte access to the port
|
|
if (io_len != 1)
|
|
{
|
|
writelog(3, "Read access to %03x not byte access, len=%d!",
|
|
address, io_len);
|
|
return(0xff);
|
|
}
|
|
|
|
switch (address)
|
|
{
|
|
// 2x0: FM Music Status Port
|
|
// 2x8 and 388 are aliases
|
|
case BX_SB16_IO + 0x00:
|
|
case BX_SB16_IO + 0x08:
|
|
case BX_SB16_IOADLIB + 0x00:
|
|
return opl_status(0);
|
|
|
|
// 2x1: reserved (w: FM Music Data Port)
|
|
// 2x9 and 389 are aliases
|
|
case BX_SB16_IO + 0x01:
|
|
case BX_SB16_IO + 0x09:
|
|
case BX_SB16_IOADLIB + 0x01:
|
|
break;
|
|
|
|
// 2x2: Advanced Music Status Port
|
|
// or (for SBPro1) FM Music Status Port 2
|
|
// 38a is an alias
|
|
case BX_SB16_IO + 0x02:
|
|
case BX_SB16_IOADLIB + 0x02:
|
|
return opl_status(1);
|
|
|
|
// 2x3: reserved (w: Adv. FM Music Data Port)
|
|
// or (for SBPro1) FM Music Data Port 2
|
|
// 38b is an alias
|
|
case BX_SB16_IO + 0x03:
|
|
case BX_SB16_IOADLIB + 0x03:
|
|
break;
|
|
|
|
// 2x4: reserved (w: Mixer Register Port)
|
|
case BX_SB16_IO + 0x04:
|
|
break;
|
|
|
|
// 2x5: Mixer Data Port
|
|
case BX_SB16_IO + 0x05:
|
|
return mixer_readdata();
|
|
|
|
// 2x6: reserved (w: DSP Reset)
|
|
case BX_SB16_IO + 0x06:
|
|
break;
|
|
|
|
// 2x7: reserved
|
|
case BX_SB16_IO + 0x07:
|
|
break;
|
|
|
|
// 2x8: FM Music Status Port (OPL-2)
|
|
// handled above
|
|
|
|
// 2x9: reserved (w: FM Music Data Port)
|
|
// handled above
|
|
|
|
// 2xa: DSP Read Data Port
|
|
case BX_SB16_IO + 0x0a:
|
|
return dsp_dataread();
|
|
|
|
// 2xb: reserved
|
|
case BX_SB16_IO + 0x0b:
|
|
break;
|
|
|
|
// 2xc: DSP Buffer Status Port
|
|
case BX_SB16_IO + 0x0c:
|
|
return dsp_bufferstatus();
|
|
|
|
// 2xd: reserved
|
|
case BX_SB16_IO + 0x0d:
|
|
break;
|
|
|
|
// 2xe: DSP Data Status Port
|
|
case BX_SB16_IO + 0x0e:
|
|
return dsp_status();
|
|
|
|
// 2xf: DSP Acknowledge 16bit DMA IRQ
|
|
case BX_SB16_IO + 0x0f:
|
|
return dsp_irq16ack();
|
|
|
|
// 3x0: MPU Data Port Read
|
|
case BX_SB16_IOMPU + 0x00:
|
|
return mpu_dataread();
|
|
|
|
// 3x1: MPU Status Port
|
|
case BX_SB16_IOMPU + 0x01:
|
|
return mpu_status();
|
|
|
|
// 3x2: reserved
|
|
case BX_SB16_IOMPU + 0x02:
|
|
break;
|
|
|
|
// 3x3: *Emulator* Port
|
|
case BX_SB16_IOMPU + 0x03:
|
|
return emul_read();
|
|
}
|
|
|
|
// If we get here, the port wasn't valid
|
|
writelog(3, "Read access to %03x for %d: unsupported port!", address, io_len);
|
|
|
|
return(0xff);
|
|
}
|
|
|
|
// static IO port write callback handler
|
|
// redirects to non-static class handler to avoid virtual functions
|
|
|
|
void bx_sb16_c::write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len)
|
|
{
|
|
#if !BX_USE_SB16_SMF
|
|
bx_sb16_c *class_ptr = (bx_sb16_c *) this_ptr;
|
|
|
|
class_ptr->write(address, value, io_len);
|
|
}
|
|
|
|
void bx_sb16_c::write(Bit32u address, Bit32u value, unsigned io_len)
|
|
{
|
|
#else
|
|
UNUSED(this_ptr);
|
|
#endif // !BX_USE_SB16_SMF
|
|
// we only support byte access to the prot
|
|
if (io_len != 1)
|
|
{
|
|
writelog(3, "Write access to %03x for %d to %02x: "
|
|
"not byte access!", address, io_len, value);
|
|
return;
|
|
}
|
|
|
|
switch (address)
|
|
{
|
|
// 2x0: FM Music Register Port
|
|
// 2x8 and 388 are aliases
|
|
case BX_SB16_IO + 0x00:
|
|
case BX_SB16_IO + 0x08:
|
|
case BX_SB16_IOADLIB + 0x00:
|
|
opl_index(value, 0);
|
|
return;
|
|
|
|
// 2x1: FM Music Data Port
|
|
// 2x9 and 389 are aliases
|
|
case BX_SB16_IO + 0x01:
|
|
case BX_SB16_IO + 0x09:
|
|
case BX_SB16_IOADLIB + 0x01:
|
|
opl_data(value, 0);
|
|
return;
|
|
|
|
// 2x2: Advanced FM Music Register Port
|
|
// or (for SBPro1) FM Music Register Port 2
|
|
// 38a is an alias
|
|
case BX_SB16_IO + 0x02:
|
|
case BX_SB16_IOADLIB + 0x02:
|
|
opl_index(value, 1);
|
|
return;
|
|
|
|
// 2x3: Advanced FM Music Data Port
|
|
// or (for SBPro1) FM Music Data Port 2
|
|
// 38b is an alias
|
|
case BX_SB16_IO + 0x03:
|
|
case BX_SB16_IOADLIB + 0x03:
|
|
opl_data(value, 1);
|
|
return;
|
|
|
|
// 2x4: Mixer Register Port
|
|
case BX_SB16_IO + 0x04:
|
|
mixer_writeregister(value);
|
|
return;
|
|
|
|
// 2x5: Mixer Data Portr,
|
|
case BX_SB16_IO + 0x05:
|
|
mixer_writedata(value);
|
|
return;
|
|
|
|
// 2x6: DSP Reset
|
|
case BX_SB16_IO + 0x06:
|
|
dsp_reset(value);
|
|
return;
|
|
|
|
// 2x7: reserved
|
|
case BX_SB16_IO + 0x07:
|
|
break;
|
|
|
|
// 2x8: FM Music Register Port (OPL-2)
|
|
// handled above
|
|
|
|
// 2x9: FM Music Data Port
|
|
// handled above
|
|
|
|
// 2xa: reserved (r: DSP Data Port)
|
|
case BX_SB16_IO + 0x0a:
|
|
break;
|
|
|
|
// 2xb: reserved
|
|
case BX_SB16_IO + 0x0b:
|
|
break;
|
|
|
|
// 2xc: DSP Write Command/Data
|
|
case BX_SB16_IO + 0x0c:
|
|
dsp_datawrite(value);
|
|
return;
|
|
|
|
// 2xd: reserved
|
|
case BX_SB16_IO + 0x0d:
|
|
break;
|
|
|
|
// 2xe: reserved (r: DSP Buffer Status)
|
|
case BX_SB16_IO + 0x0e:
|
|
break;
|
|
|
|
// 2xf: reserved
|
|
case BX_SB16_IO + 0x0f:
|
|
break;
|
|
|
|
// 3x0: MPU Command Port
|
|
case BX_SB16_IOMPU + 0x00:
|
|
mpu_datawrite(value);
|
|
return;
|
|
|
|
// 3x1: MPU Data Port
|
|
case BX_SB16_IOMPU + 0x01:
|
|
mpu_command(value);
|
|
return;
|
|
|
|
// 3x2: reserved
|
|
case BX_SB16_IOMPU + 0x02:
|
|
break;
|
|
|
|
// 3x3: *Emulator* Port
|
|
case BX_SB16_IOMPU + 0x03:
|
|
emul_write(value);
|
|
return;
|
|
}
|
|
|
|
// if we arrive here, the port is unsupported
|
|
writelog(3, "Write access to %03x for %d to %02x: unsupported port!",
|
|
address, io_len, value);
|
|
|
|
return;
|
|
}
|
|
|
|
void bx_sb16_c::writelog(int loglevel, const char *str, ...)
|
|
{
|
|
// append a line to the log file, if desired
|
|
if ( (int) bx_options.sb16.Ologlevel->get () >= loglevel)
|
|
{
|
|
fprintf(LOGFILE, "%011lld (%d) ", bx_pc_system.time_ticks(), loglevel);
|
|
va_list ap;
|
|
va_start(ap, str);
|
|
vfprintf(LOGFILE, str, ap);
|
|
va_end(ap);
|
|
fprintf(LOGFILE, "\n");
|
|
fflush(LOGFILE);
|
|
}
|
|
}
|
|
|
|
// the round-robin FIFO buffers of the SB16
|
|
bx_sb16_buffer::bx_sb16_buffer()
|
|
{
|
|
length = 0; // total bytes in buffer
|
|
head = 0; // pointer to next slot available for new data
|
|
tail = 0; // pointer to next slot to be read from
|
|
buffer = NULL; // pointer to the actual data
|
|
}
|
|
|
|
void bx_sb16_buffer::init(int bufferlen)
|
|
{
|
|
if (buffer != NULL) // Was it initialized before?
|
|
delete buffer;
|
|
|
|
length = bufferlen;
|
|
buffer = new Bit8u[length];
|
|
if (buffer == NULL)
|
|
length = 0; // This will be checked later
|
|
|
|
reset();
|
|
}
|
|
|
|
void bx_sb16_buffer::reset()
|
|
{
|
|
head = 0; // Reset the pointers
|
|
tail = 0;
|
|
|
|
clearcommand(); // no current command set
|
|
}
|
|
|
|
bx_sb16_buffer::~bx_sb16_buffer(void)
|
|
{
|
|
if (buffer != NULL)
|
|
delete [] buffer;
|
|
|
|
buffer = NULL;
|
|
length = 0;
|
|
}
|
|
|
|
|
|
// Report how many bytes are available
|
|
int bx_sb16_buffer::bytes(void)
|
|
{
|
|
if (empty() != 0)
|
|
return 0; // empty / not initialized
|
|
|
|
int bytes = head - tail;
|
|
if (bytes < 0) bytes += length;
|
|
return (bytes);
|
|
}
|
|
|
|
// This puts one byte into the buffer
|
|
Boolean bx_sb16_buffer::put(Bit8u data)
|
|
{
|
|
if (full() != 0)
|
|
return 0; // buffer full
|
|
|
|
buffer[head++] = data; // Write data, and increase write pointer
|
|
head %= length; // wrap it around so it stays inside the data
|
|
|
|
return 1; // put was successful
|
|
}
|
|
|
|
// This writes a formatted string to the buffer
|
|
Boolean bx_sb16_buffer::puts(char *data, ...)
|
|
{
|
|
|
|
if (data == NULL)
|
|
return 0; // invalid string
|
|
|
|
//char string[length];
|
|
char *string;
|
|
int index = 0;
|
|
|
|
string = (char *) malloc(length);
|
|
|
|
va_list ap;
|
|
va_start(ap, data);
|
|
vsprintf(string, data, ap);
|
|
va_end(ap);
|
|
|
|
if ( (int) strlen(string) >= length)
|
|
BX_PANIC(("bx_sb16_buffer: puts() too long!"));
|
|
|
|
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;
|
|
}
|