package org.jpc.emulator.peripheral;
import org.jpc.emulator.AbstractHardwareComponent;
import org.jpc.emulator.HardwareComponent;
import org.jpc.emulator.motherboard.IODevice;
import org.jpc.emulator.motherboard.IOPortHandler;
import org.jpc.j2se.Option;
import org.jpc.support.Clock;
public class Adlib extends AbstractHardwareComponent implements IODevice
{
static private final int HW_OPL2 = 0;
static private final int HW_DUALOPL2 = 1;
static private final int HW_OPL3 = 2;
private boolean ioportRegistered;
private boolean single;
private Clock timeSource;
static private class RawHeader {
/*Bit8u*/byte[] id=new byte[8]; /* 0x00, "DBRAWOPL" */
/*Bit16u*/int versionHigh; /* 0x08, size of the data following the m */
/*Bit16u*/int versionLow; /* 0x0a, size of the data following the m */
/*Bit32u*/long commands; /* 0x0c, Bit32u amount of command/data pairs */
/*Bit32u*/long milliseconds; /* 0x10, Bit32u Total milliseconds of data in this chunk */
/*Bit8u*/short hardware; /* 0x14, Bit8u Hardware Type 0=opl2,1=dual-opl2,2=opl3 */
/*Bit8u*/short format; /* 0x15, Bit8u Format 0=cmd/data interleaved, 1 maybe all cdms, followed by all data */
/*Bit8u*/short compression; /* 0x16, Bit8u Compression Type, 0 = No Compression */
/*Bit8u*/short delay256; /* 0x17, Bit8u Delay 1-256 msec command */
/*Bit8u*/short delayShift8; /* 0x18, Bit8u (delay + 1)*256 */
/*Bit8u*/short conversionTableSize; /* 0x191, Bit8u Raw Conversion Table size */
}
static final private class Timer {
double start;
double delay;
boolean enabled, overflow, masked;
/*Bit8u*/short counter;
Timer() {
masked = false;
overflow = false;
enabled = false;
counter = 0;
delay = 0;
}
//Call update before making any further changes
void Update( double time ) {
if ( !enabled || delay==0 )
return;
double deltaStart = time - start;
//Only set the overflow flag when not masked
if ( deltaStart >= 0 && !masked ) {
overflow = true;
}
}
//On a reset make sure the start is in sync with the next cycle
void Reset(double time ) {
overflow = false;
if ( delay==0 || !enabled )
return;
double delta = (time - start);
double rem = delta % delay;
double next = delay - rem;
start = time + next;
}
void Stop( ) {
enabled = false;
}
void Start(double time, /*Bits*/int scale ) {
//Don't enable again
if ( enabled ) {
return;
}
enabled = true;
delay = 0.001 * (256 - counter ) * scale;
start = time + delay;
}
}
// static private final class Capture {
// //127 entries to go from raw data to registers
// /*Bit8u*/short ToReg[127];
// //How many entries in the ToPort are used
// /*Bit8u*/short RawUsed;
// //256 entries to go from port index to raw data
// /*Bit8u*/short ToRaw[256];
// /*Bit8u*/short delay256;
// /*Bit8u*/short delayShift8;
// RawHeader header;
//
// FILE* handle; //File used for writing
// /*Bit32u*/long startTicks; //Start used to check total raw length on end
// /*Bit32u*/long lastTicks; //Last ticks when last last cmd was added
// /*Bit8u*/short buf[1024]; //16 added for delay commands and what not
// /*Bit32u*/long bufUsed;
// /*Bit8u*/short cmd[2]; //Last cmd's sent to either ports
// boolean doneOpl3;
// boolean doneDualOpl2;
//
// RegisterCache* cache;
//
// void MakeEntry( /*Bit8u*/short reg, /*Bit8u*/short& raw ) {
// ToReg[ raw ] = reg;
// ToRaw[ reg ] = raw;
// raw++;
// }
// void MakeTables( void ) {
// /*Bit8u*/short index = 0;
// memset( ToReg, 0xff, sizeof ( ToReg ) );
// memset( ToRaw, 0xff, sizeof ( ToRaw ) );
// //Select the entries that are valid and the index is the mapping to the index entry
// MakeEntry( 0x01, index ); //0x01: Waveform select
// MakeEntry( 0x04, index ); //104: Four-Operator Enable
// MakeEntry( 0x05, index ); //105: OPL3 Mode Enable
// MakeEntry( 0x08, index ); //08: CSW / NOTE-SEL
// MakeEntry( 0xbd, index ); //BD: Tremolo Depth / Vibrato Depth / Percussion Mode / BD/SD/TT/CY/HH On
// //Add the 32 byte range that hold the 18 operators
// for ( int i = 0 ; i < 24; i++ ) {
// if ( (i & 7) < 6 ) {
// MakeEntry(0x20 + i, index ); //20-35: Tremolo / Vibrato / Sustain / KSR / Frequency Multiplication Facto
// MakeEntry(0x40 + i, index ); //40-55: Key Scale Level / Output Level
// MakeEntry(0x60 + i, index ); //60-75: Attack Rate / Decay Rate
// MakeEntry(0x80 + i, index ); //80-95: Sustain Level / Release Rate
// MakeEntry(0xe0 + i, index ); //E0-F5: Waveform Select
// }
// }
// //Add the 9 byte range that hold the 9 channels
// for ( int i = 0 ; i < 9; i++ ) {
// MakeEntry(0xa0 + i, index ); //A0-A8: Frequency Number
// MakeEntry(0xb0 + i, index ); //B0-B8: Key On / Block Number / F-Number(hi /*Bits*/int)
// MakeEntry(0xc0 + i, index ); //C0-C8: FeedBack Modulation Factor / Synthesis Type
// }
// //Store the amount of bytes the table contains
// RawUsed = index;
// // assert( RawUsed <= 127 );
// delay256 = RawUsed;
// delayShift8 = RawUsed+1;
// }
//
// void ClearBuf( void ) {
// fwrite( buf, 1, bufUsed, handle );
// header.commands += bufUsed / 2;
// bufUsed = 0;
// }
// void AddBuf( /*Bit8u*/short raw, /*Bit8u*/short val ) {
// buf[bufUsed++] = raw;
// buf[bufUsed++] = val;
// if ( bufUsed >= sizeof( buf ) ) {
// ClearBuf();
// }
// }
// void AddWrite( /*Bit32u*/long regFull, /*Bit8u*/short val ) {
// /*Bit8u*/short regMask = regFull & 0xff;
// /*
// Do some special checks if we're doing opl3 or dualopl2 commands
// Although you could pretty much just stick to always doing opl3 on the player side
// */
// //Enabling opl3 4op modes will make us go into opl3 mode
// if ( header.hardware != HW_OPL3 && regFull == 0x104 && val && (*cache)[0x105] ) {
// header.hardware = HW_OPL3;
// }
// //Writing a keyon to a 2nd address enables dual opl2 otherwise
// //Maybe also check for rhythm
// if ( header.hardware == HW_OPL2 && regFull >= 0x1b0 && regFull <=0x1b8 && val ) {
// header.hardware = HW_DUALOPL2;
// }
// /*Bit8u*/short raw = ToRaw[ regMask ];
// if ( raw == 0xff )
// return;
// if ( regFull & 0x100 )
// raw |= 128;
// AddBuf( raw, val );
// }
// void WriteCache( void ) {
// /*Bitu*/int i, val;
// /* Check the registers to add */
// for (i=0;i<256;i++) {
// //Skip the note on entries
// if (i>=0xb0 && i<=0xb8)
// continue;
// val = (*cache)[ i ];
// if (val) {
// AddWrite( i, val );
// }
// val = (*cache)[ 0x100 + i ];
// if (val) {
// AddWrite( 0x100 + i, val );
// }
// }
// }
// void InitHeader( void ) {
// memset( &header, 0, sizeof( header ) );
// memcpy( header.id, "DBRAWOPL", 8 );
// header.versionLow = 0;
// header.versionHigh = 2;
// header.delay256 = delay256;
// header.delayShift8 = delayShift8;
// header.conversionTableSize = RawUsed;
// }
// void CloseFile( void ) {
// if ( handle ) {
// ClearBuf();
// /* Endianize the header and write it to beginning of the file */
// var_write( &header.versionHigh, header.versionHigh );
// var_write( &header.versionLow, header.versionLow );
// var_write( &header.commands, header.commands );
// var_write( &header.milliseconds, header.milliseconds );
// fseek( handle, 0, SEEK_SET );
// fwrite( &header, 1, sizeof( header ), handle );
// fclose( handle );
// handle = 0;
// }
// }
// public:
// boolean DoWrite( /*Bit32u*/long regFull, /*Bit8u*/short val ) {
// /*Bit8u*/short regMask = regFull & 0xff;
// //Check the raw index for this register if we actually have to save it
// if ( handle ) {
// /*
// Check if we actually care for this to be logged, else just ignore it
// */
// /*Bit8u*/short raw = ToRaw[ regMask ];
// if ( raw == 0xff ) {
// return true;
// }
// /* Check if this command will not just replace the same value
// in a reg that doesn't do anything with it
// */
// if ( (*cache)[ regFull ] == val )
// return true;
// /* Check how much time has passed */
// /*Bitu*/int passed = PIC_Ticks - lastTicks;
// lastTicks = PIC_Ticks;
// header.milliseconds += passed;
//
// //if ( passed > 0 ) LOG_MSG( "Delay %d", passed ) ;
//
// // If we passed more than 30 seconds since the last command, we'll restart the the capture
// if ( passed > 30000 ) {
// CloseFile();
// goto skipWrite;
// }
// while (passed > 0) {
// if (passed < 257) { //1-256 millisecond delay
// AddBuf( delay256, passed - 1 );
// passed = 0;
// } else {
// /*Bitu*/int shift = (passed >> 8);
// passed -= shift << 8;
// AddBuf( delayShift8, shift - 1 );
// }
// }
// AddWrite( regFull, val );
// return true;
// }
// skipWrite:
// //Not yet capturing to a file here
// //Check for commands that would start capturing, if it's not one of them return
// if ( !(
// //note on in any channel
// ( regMask>=0xb0 && regMask<=0xb8 && (val&0x020) ) ||
// //Percussion mode enabled and a note on in any percussion instrument
// ( regMask == 0xbd && ( (val&0x3f) > 0x20 ) )
// )) {
// return true;
// }
// handle = OpenCaptureFile("Raw Opl",".dro");
// if (!handle)
// return false;
// InitHeader();
// //Prepare space at start of the file for the header
// fwrite( &header, 1, sizeof(header), handle );
// /* write the Raw To Reg table */
// fwrite( &ToReg, 1, RawUsed, handle );
// /* Write the cache of last commands */
// WriteCache( );
// /* Write the command that triggered this */
// AddWrite( regFull, val );
// //Init the timing information for the next commands
// lastTicks = PIC_Ticks;
// startTicks = PIC_Ticks;
// return true;
// }
// Capture( RegisterCache* _cache ) {
// cache = _cache;
// handle = 0;
// bufUsed = 0;
// MakeTables();
// }
// ~Capture() {
// CloseFile();
// }
//
// };
private final class Chip {
//Last selected register
Timer[] timer = new Timer[2];
public Chip() {
for (int i=0;i<timer.length;i++) {
timer[i] = new Timer();
}
}
//Check for it being a write to the timer
boolean Write(/*Bit32u*/int addr, /*Bit8u*/short val) {
switch ( addr ) {
case 0x02:
timer[0].counter = val;
return true;
case 0x03:
timer[1].counter = val;
return true;
case 0x04:
double time = timeSource.getEmulatedNanos();//Pic.PIC_FullIndex();
if ((val & 0x80)!=0) {
timer[0].Reset( time );
timer[1].Reset( time );
} else {
timer[0].Update( time );
timer[1].Update( time );
if ((val & 0x1)!=0) {
timer[0].Start( time, 80 );
} else {
timer[0].Stop( );
}
timer[0].masked = (val & 0x40) > 0;
if ( timer[0].masked )
timer[0].overflow = false;
if ((val & 0x2)!=0) {
timer[1].Start( time, 320 );
} else {
timer[1].Stop( );
}
timer[1].masked = (val & 0x20) > 0;
if ( timer[1].masked )
timer[1].overflow = false;
}
return true;
}
return false;
}
//Read the current timer state, will use current double
/*Bit8u*/short Read() {
double time = timeSource.getEmulatedNanos();//Pic.PIC_FullIndex();
timer[0].Update( time );
timer[1].Update( time );
/*Bit8u*/short ret = 0;
//Overflow won't be set if a channel is masked
if ( timer[0].overflow ) {
ret |= 0x40;
ret |= 0x80;
}
if ( timer[1].overflow ) {
ret |= 0x20;
ret |= 0x80;
}
return ret;
}
}
//The type of handler this is
static final private int MODE_OPL2 = 0;
static final private int MODE_DUALOPL2 = 1;
static final private int MODE_OPL3 = 2;
static public interface Handler {
//Write an address to a chip, returns the address the chip sets
public /*Bit32u*/long WriteAddr( /*Bit32u*/int port, /*Bit8u*/short val );
//Write to a specific register in the chip
public void WriteReg( /*Bit32u*/int addr, /*Bit8u*/short val );
//Generate a certain amount of samples
public void Generate( Mixer.MixerChannel chan, /*Bitu*/int samples );
//Initialize at a specific sample rate and mode
public void Init( /*Bitu*/long rate );
}
//The cache for 2 chips or an opl3
// typedef /*Bit8u*/short RegisterCache[512];
private class Module
{
// private IoHandler.IO_ReadHandleObject[] ReadHandler = new IoHandler.IO_ReadHandleObject[3];
// private IoHandler.IO_WriteHandleObject[] WriteHandler = new IoHandler.IO_WriteHandleObject[3];
private Mixer.MixerObject mixerObject = new Mixer.MixerObject();
public Module()
{
// for (int i=0;i<ReadHandler.length;i++)
// ReadHandler[i] = new IoHandler.IO_ReadHandleObject();
// for (int i=0;i<WriteHandler.length;i++)
// WriteHandler[i] = new IoHandler.IO_WriteHandleObject();
for (int i=0;i<chip.length;i++) {
chip[i] = new Chip();
}
reg.normal = 0;
// capture = null;
/*Bitu*/int base = Option.sbbase.intValue(SBlaster.BASE, 16);
/*Bitu*/int rate = Option.oplrate.intValue(SBlaster.OPL_RATE);
//Make sure we can't select lower than 8000 to prevent fixed point issues
if ( rate < 8000 )
rate = 8000;
String oplemu = Option.oplemu.value(SBlaster.OPLEMU);
mixerChan = mixerObject.Install(OPL_CallBack,rate,"FM");
mixerChan.SetScale(2.0f);
if (oplemu.equals("fast")) {
handler = new DbOPL.Handler();
} else if (oplemu.equals("compat")) {
System.out.println("OPLEMU compat not implemented");
// if ( oplmode == OPL_opl2 ) {
// handler = new OPL2::Handler();
// } else {
// handler = new OPL3::Handler();
// }
handler = new DbOPL.Handler();
} else {
handler = new DbOPL.Handler();
}
handler.Init( rate );
switch (SBlaster.oplmode) {
case 2:
single = true;
Init(MODE_OPL2);
break;
case 3:
Init(MODE_DUALOPL2);
single = false;
break;
case 4:
Init(MODE_OPL3);
single = false;
break;
default:
single = false;
}
//0x388 range
// WriteHandler[0].Install(0x388,OPL_Write,IoHandler.IO_MB, 4 );
// ReadHandler[0].Install(0x388,OPL_Read,IoHandler.IO_MB, 4 );
// //0x220 range
// if ( !single ) {
// WriteHandler[1].Install(base,OPL_Write,IoHandler.IO_MB, 4 );
// ReadHandler[1].Install(base,OPL_Read,IoHandler.IO_MB, 4 );
// }
// //0x228 range
// WriteHandler[2].Install(base+8,OPL_Write,IoHandler.IO_MB, 2);
// ReadHandler[2].Install(base+8,OPL_Read,IoHandler.IO_MB, 1);
// MAPPER_AddHandler(OPL_SaveRawEvent,MK_f7,MMOD1|MMOD2,"caprawopl","Cap OPL");
}
//Mode we're running in
private int mode;
//Last selected address in the chip for the different modes
private class Reg {
/*Bit32u*/int normal;
/*Bit8u*/short dual(int index) {
if (index == 0)
return (short)(normal & 0xFF);
else
return (short)((normal >> 8) & 0xFF);
}
/*Bit8u*/void dual(int index, int value) {
if (index == 0) {
normal &= 0xFFFFFF00;
normal |= value & 0xFF;
} else {
normal &= 0xFFFF00FF;
normal |= (value << 8) & 0xFF;
}
}
}
private Reg reg = new Reg();
private void CacheWrite( /*Bit32u*/int reg, /*Bit8u*/short val )
{
//Store it into the cache
cache[ reg ] = val;
}
private void DualWrite( /*Bit8u*/short index, /*Bit8u*/short reg, /*Bit8u*/short val )
{
//Make sure you don't use opl3 features
//Don't allow write to disable opl3
if ( reg == 5 ) {
return;
}
//Only allow 4 waveforms
if ( reg >= 0xE0 ) {
val &= 3;
}
//Write to the timer?
if ( chip[index].Write( reg, val ) )
return;
//Enabling panning
if ( reg >= 0xc0 && reg <=0xc8 ) {
val &= 0x0f;
val |= index!=0 ? 0xA0 : 0x50;
}
/*Bit32u*/int fullReg = reg + (index!=0 ? 0x100 : 0);
handler.WriteReg( fullReg, val );
CacheWrite( fullReg, val );
}
public Mixer.MixerChannel mixerChan;
public /*Bit32u*/long lastUsed; //Ticks when adlib was last used to turn of mixing after a few second
public Handler handler; //Handler that will generate the sound
// public RegisterCache cache;
public short[] cache = new short[512];
// public Capture capture;
public Chip[] chip = new Chip[2];
//Handle port writes
public void PortWrite(/*Bitu*/int port, /*Bitu*/short val) {
//Keep track of last write time
lastUsed = timeSource.getEmulatedMicros();//Pic.PIC_Ticks;
//Maybe only enable with a keyon?
if ( !mixerChan.enabled ) {
mixerChan.Enable(true);
}
if ((port & 1)!=0) {
switch ( mode ) {
case MODE_OPL2:
case MODE_OPL3:
if ( !chip[0].Write( reg.normal, val ) ) {
handler.WriteReg( reg.normal, val );
CacheWrite( reg.normal, val );
}
break;
case MODE_DUALOPL2:
//Not a 0x??8 port, then write to a specific port
if ((port & 0x8)==0) {
/*Bit8u*/short index = (short)(( port & 2 ) >> 1);
DualWrite( index, reg.dual(index), val );
} else {
//Write to both ports
DualWrite( (short)0, reg.dual(0), val );
DualWrite( (short)1, reg.dual(1), val );
}
break;
}
} else {
//Ask the handler to write the address
//Make sure to clip them in the right range
switch ( mode ) {
case MODE_OPL2:
reg.normal = (int)handler.WriteAddr( port, val ) & 0xff;
break;
case MODE_OPL3:
reg.normal = (int)handler.WriteAddr( port, val ) & 0x1ff;
break;
case MODE_DUALOPL2:
//Not a 0x?88 port, when write to a specific side
if ((port & 0x8)==0) {
/*Bit8u*/int index = ( port & 2 ) >> 1;
reg.dual(index, val & 0xff);
} else {
reg.dual(0, val & 0xff);
reg.dual(1, val & 0xff);
}
break;
}
}
}
public /*Bitu*/int PortRead(/*Bitu*/int port) {
switch ( mode ) {
case MODE_OPL2:
//We allocated 4 ports, so just return -1 for the higher ones
if ((port & 3)==0) {
//Make sure the low /*Bits*/int are 6 on opl2
return chip[0].Read() | 0x6;
} else {
return 0xff;
}
case MODE_OPL3:
//We allocated 4 ports, so just return -1 for the higher ones
if ((port & 3)==0) {
return chip[0].Read();
} else {
return 0xff;
}
case MODE_DUALOPL2:
//Only return for the lower ports
if ((port & 1)!=0) {
return 0xff;
}
//Make sure the low /*Bits*/int are 6 on opl2
return chip[ (port >> 1) & 1].Read() | 0x6;
}
return 0;
}
public void Init(int m) {
mode = m;
switch ( mode ) {
case MODE_OPL3:
case MODE_OPL2:
break;
case MODE_DUALOPL2:
//Setup opl3 mode in the hander
handler.WriteReg(0x105, (short)1);
//Also set it up in the cache so the capturing will start opl3
CacheWrite(0x105, (short)1);
break;
}
}
}
private static Module module = null;
private final Mixer.MIXER_Handler OPL_CallBack = new Mixer.MIXER_Handler() {
public void call(/*Bitu*/int len) {
module.handler.Generate( module.mixerChan, len );
//Disable the sound generation after 30 seconds of silence
if ((timeSource.getEmulatedMicros() - module.lastUsed) > 30000000) {
/*Bitu*/int i;
for (i=0xb0;i<0xb9;i++) if ((module.cache[i] &0x20)!=0 || (module.cache[i+0x100] & 0x20)!=0) break;
if (i==0xb9) module.mixerChan.Enable(false);
else module.lastUsed = timeSource.getEmulatedMicros();//Pic.PIC_Ticks;
}
}
};
public Adlib()
{
// soundblaster must be instantiated first!
module = new Module();
}
public int[] ioPortsRequested()
{
int[] ports = new int[6 + (single ? 0:4)];
int base = Option.sbbase.intValue(SBlaster.BASE, 16);
int i=0;
for (;i <4 ; i++)
ports[i] = 0x388 + i;
if (!single)
for (int j=0;j <4 ; j++)
ports[i++] = base + j;
ports[i++] = base+8;
ports[i++] = base+9;
return ports;
}
public void acceptComponent(HardwareComponent component)
{
if ((component instanceof Clock) && component.initialised())
timeSource = (Clock) component;
if ((component instanceof IOPortHandler) && component.initialised()) {
((IOPortHandler) component).registerIOPortCapable(this);
ioportRegistered = true;
}
}
public boolean initialised()
{
return (timeSource != null) && ioportRegistered;
}
public int ioPortRead8(int address)
{
return ioPortRead32(address);
}
public int ioPortRead16(int address)
{
return ioPortRead32(address);
}
public int ioPortRead32(int port)
{
return module.PortRead(port);
}
public void ioPortWrite8(int address, int data)
{
ioPortWrite32(address, data);
}
public void ioPortWrite16(int address, int data)
{
ioPortWrite32(address, data);
}
public void ioPortWrite32(int port, int data)
{
module.PortWrite( port, (short)data);
}
}