- Make OPL emulation more of a black box.

SVN r3942 (trunk)
This commit is contained in:
Randy Heit 2012-11-06 05:21:00 +00:00
parent 47d9859246
commit df1e802412
9 changed files with 188 additions and 261 deletions

View file

@ -102,6 +102,17 @@ Revision History:
//#include "driver.h" /* use M.A.M.E. */ //#include "driver.h" /* use M.A.M.E. */
#include "fmopl.h" #include "fmopl.h"
/* compiler dependence */
#ifndef OSD_CPU_H
#define OSD_CPU_H
typedef unsigned char UINT8; /* unsigned 8bit */
typedef unsigned short UINT16; /* unsigned 16bit */
typedef unsigned int UINT32; /* unsigned 32bit */
typedef signed char INT8; /* signed 8bit */
typedef signed short INT16; /* signed 16bit */
typedef signed int INT32; /* signed 32bit */
#endif
#ifndef PI #ifndef PI
#define PI 3.14159265358979323846 #define PI 3.14159265358979323846
#endif #endif
@ -1343,13 +1354,11 @@ static void OPLWriteReg(FM_OPL *OPL, int r, int v)
/* timer 2 */ /* timer 2 */
if(OPL->st[1] != st2) if(OPL->st[1] != st2)
{ {
double interval = st2 ? (double)OPL->T[1]*OPL_TIMERBASE : 0.0;
OPL->st[1] = st2; OPL->st[1] = st2;
} }
/* timer 1 */ /* timer 1 */
if(OPL->st[0] != st1) if(OPL->st[0] != st1)
{ {
double interval = st1 ? (double)OPL->T[0]*OPL_TIMERBASE : 0.0;
OPL->st[0] = st1; OPL->st[0] = st1;
} }
} }
@ -1560,165 +1569,80 @@ static void OPLResetChip(FM_OPL *OPL)
} }
} }
/* Create one of virtual YM3812 */
/* 'clock' is chip clock in Hz */ class YM3812 : public OPLEmul
/* 'rate' is sampling rate */
static FM_OPL *OPLCreate()
{ {
char *ptr; private:
FM_OPL *OPL; FM_OPL Chip;
int state_size;
if (OPL_LockTable() ==-1) return NULL; public:
/* Create one of virtual YM3812 */
/* calculate OPL state size */ YM3812(bool stereo)
state_size = sizeof(FM_OPL); {
if (OPL_LockTable() == -1) return;
/* allocate memory block */
ptr = (char *)malloc(state_size);
if (ptr==NULL)
return NULL;
/* clear */ /* clear */
memset(ptr,0,state_size); memset(&Chip, 0, sizeof(Chip));
OPL = (FM_OPL *)ptr;
ptr += sizeof(FM_OPL);
/* init global tables */ /* init global tables */
OPL_initalize(OPL); OPL_initalize(&Chip);
return OPL; Chip.IsStereo = true;
}
/* Destroy one of virtual YM3812 */ Reset();
static void OPLDestroy(FM_OPL *OPL) }
{
/* Destroy one of virtual YM3812 */
~YM3812()
{
OPL_UnLockTable(); OPL_UnLockTable();
free(OPL); }
}
/* YM3812 I/O interface */ /* YM3812 I/O interface */
static int OPLWrite(FM_OPL *OPL,int a,int v) int Write(int a, int v)
{ {
if( !(a&1) ) if( !(a&1) )
{ /* address port */ { /* address port */
OPL->address = v & 0xff; Chip.address = v & 0xff;
} }
else else
{ /* data port */ { /* data port */
OPLWriteReg(OPL,OPL->address,v); OPLWriteReg(&Chip, Chip.address, v);
} }
return OPL->status>>7; return Chip.status>>7;
}
static unsigned char OPLRead(FM_OPL *OPL,int a)
{
if( !(a&1) )
{
/* status port */
/* OPL and OPL2 */
return OPL->status & (OPL->statusmask|0x80);
} }
return 0xff; void Reset()
}
/* CSM Key Controll */
INLINE void CSMKeyControll(OPL_CH *CH)
{
FM_KEYON (&CH->SLOT[SLOT1], 4);
FM_KEYON (&CH->SLOT[SLOT2], 4);
/* The key off should happen exactly one sample later - not implemented correctly yet */
FM_KEYOFF(&CH->SLOT[SLOT1], ~4);
FM_KEYOFF(&CH->SLOT[SLOT2], ~4);
}
void *YM3812Init()
{
/* emulator create */
FM_OPL *YM3812 = OPLCreate();
if (YM3812)
YM3812ResetChip(YM3812);
return YM3812;
}
void YM3812Shutdown(void *chip)
{
FM_OPL *YM3812 = (FM_OPL *)chip;
/* emulator shutdown */
OPLDestroy(YM3812);
}
void YM3812ResetChip(void *chip)
{
FM_OPL *YM3812 = (FM_OPL *)chip;
OPLResetChip(YM3812);
}
int YM3812Write(void *chip, int a, int v)
{
FM_OPL *YM3812 = (FM_OPL *)chip;
return OPLWrite(YM3812, a, v);
}
unsigned char YM3812Read(void *chip, int a)
{
FM_OPL *YM3812 = (FM_OPL *)chip;
/* YM3812 always returns bit2 and bit1 in HIGH state */
return OPLRead(YM3812, a) | 0x06 ;
}
/* [RH] Full support for MIDI panning */
void YM3812SetStereo(void *chip, bool stereo)
{
if (chip != NULL)
{ {
FM_OPL *YM3812 = (FM_OPL *)chip; OPLResetChip(&Chip);
YM3812->IsStereo = stereo;
} }
}
void YM3812SetPanning(void *chip, int c, int pan) /* [RH] Full support for MIDI panning */
{ void SetPanning(int c, int pan)
if (chip != NULL)
{ {
FM_OPL *YM3812 = (FM_OPL *)chip;
// This is the MIDI-recommended pan formula. 0 and 1 are // This is the MIDI-recommended pan formula. 0 and 1 are
// both hard left so that 64 can be perfectly center. // both hard left so that 64 can be perfectly center.
double level = (pan <= 1) ? 0 : (pan - 1) / 126.0; double level = (pan <= 1) ? 0 : (pan - 1) / 126.0;
YM3812->P_CH[c].LeftVol = (float)cos(HALF_PI * level); Chip.P_CH[c].LeftVol = (float)cos(HALF_PI * level);
YM3812->P_CH[c].RightVol = (float)sin(HALF_PI * level); Chip.P_CH[c].RightVol = (float)sin(HALF_PI * level);
} }
}
/*
** Generate samples for one of the YM3812's /*
** ** Generate samples for one of the YM3812's
** 'which' is the virtual YM3812 number **
** '*buffer' is the output buffer pointer ** '*buffer' is the output buffer pointer
** 'length' is the number of samples that should be generated ** 'length' is the number of samples that should be generated
*/ */
void YM3812UpdateOne(void *chip, float *buffer, int length) void Update(float *buffer, int length)
{ {
FM_OPL *OPL = (FM_OPL *)chip;
int i; int i;
if (OPL == NULL) UINT8 rhythm = Chip.rhythm&0x20;
{
return;
}
UINT8 rhythm = OPL->rhythm&0x20; UINT32 lfo_am_cnt_bak = Chip.lfo_am_cnt;
UINT32 eg_timer_bak = Chip.eg_timer;
UINT32 lfo_am_cnt_bak = OPL->lfo_am_cnt; UINT32 eg_cnt_bak = Chip.eg_cnt;
UINT32 eg_timer_bak = OPL->eg_timer;
UINT32 eg_cnt_bak = OPL->eg_cnt;
UINT32 lfo_am_cnt_out = lfo_am_cnt_bak; UINT32 lfo_am_cnt_out = lfo_am_cnt_bak;
UINT32 eg_timer_out = eg_timer_bak; UINT32 eg_timer_out = eg_timer_bak;
@ -1726,28 +1650,59 @@ void YM3812UpdateOne(void *chip, float *buffer, int length)
for (i = 0; i <= (rhythm ? 5 : 8); ++i) for (i = 0; i <= (rhythm ? 5 : 8); ++i)
{ {
OPL->lfo_am_cnt = lfo_am_cnt_bak; Chip.lfo_am_cnt = lfo_am_cnt_bak;
OPL->eg_timer = eg_timer_bak; Chip.eg_timer = eg_timer_bak;
OPL->eg_cnt = eg_cnt_bak; Chip.eg_cnt = eg_cnt_bak;
if (CalcVoice (OPL, i, buffer, length)) if (CalcVoice (&Chip, i, buffer, length))
{ {
lfo_am_cnt_out = OPL->lfo_am_cnt; lfo_am_cnt_out = Chip.lfo_am_cnt;
eg_timer_out = OPL->eg_timer; eg_timer_out = Chip.eg_timer;
eg_cnt_out = OPL->eg_cnt; eg_cnt_out = Chip.eg_cnt;
} }
} }
OPL->lfo_am_cnt = lfo_am_cnt_out; Chip.lfo_am_cnt = lfo_am_cnt_out;
OPL->eg_timer = eg_timer_out; Chip.eg_timer = eg_timer_out;
OPL->eg_cnt = eg_cnt_out; Chip.eg_cnt = eg_cnt_out;
if (rhythm) /* Rhythm part */ if (rhythm) /* Rhythm part */
{ {
OPL->lfo_am_cnt = lfo_am_cnt_bak; Chip.lfo_am_cnt = lfo_am_cnt_bak;
OPL->eg_timer = eg_timer_bak; Chip.eg_timer = eg_timer_bak;
OPL->eg_cnt = eg_cnt_bak; Chip.eg_cnt = eg_cnt_bak;
CalcRhythm (OPL, buffer, length); CalcRhythm (&Chip, buffer, length);
} }
}
FString GetVoiceString(void *chip)
{
FM_OPL *OPL = (FM_OPL *)chip;
char out[9*3];
for (int i = 0; i <= 8; ++i)
{
int color;
if (OPL != NULL && (OPL->P_CH[i].SLOT[0].state != EG_OFF || OPL->P_CH[i].SLOT[1].state != EG_OFF))
{
color = 'D'; // Green means in use
}
else
{
color = 'A'; // Brick means free
}
out[i*3+0] = '\x1c';
out[i*3+1] = color;
out[i*3+2] = '*';
}
return FString (out, 9*3);
}
};
OPLEmul *YM3812Init(bool stereo)
{
/* emulator create */
return new YM3812(stereo);
} }
// [RH] Render a whole voice at once. If nothing else, it lets us avoid // [RH] Render a whole voice at once. If nothing else, it lets us avoid
@ -1813,27 +1768,3 @@ static bool CalcRhythm (FM_OPL *OPL, float *buffer, int length)
} }
return true; return true;
} }
FString YM3812GetVoiceString(void *chip)
{
FM_OPL *OPL = (FM_OPL *)chip;
char out[9*3];
for (int i = 0; i <= 8; ++i)
{
int color;
if (OPL != NULL && (OPL->P_CH[i].SLOT[0].state != EG_OFF || OPL->P_CH[i].SLOT[1].state != EG_OFF))
{
color = 'D'; // Green means in use
}
else
{
color = 'A'; // Brick means free
}
out[i*3+0] = '\x1c';
out[i*3+1] = color;
out[i*3+2] = '*';
}
return FString (out, 9*3);
}

View file

@ -1,39 +1,11 @@
#ifndef __FMOPL_H_ #ifndef __FMOPL_H_
#define __FMOPL_H_ #define __FMOPL_H_
#include "zstring.h" #include "opl.h"
// Multiplying OPL_SAMPLE_RATE by ADLIB_CLOCK_MUL gives the number // Multiplying OPL_SAMPLE_RATE by ADLIB_CLOCK_MUL gives the number
// Adlib clocks per second, as used by the RAWADATA file format. // Adlib clocks per second, as used by the RAWADATA file format.
/* compiler dependence */ OPLEmul *YM3812Init(bool stereo);
#ifndef OSD_CPU_H
#define OSD_CPU_H
typedef unsigned char UINT8; /* unsigned 8bit */
typedef unsigned short UINT16; /* unsigned 16bit */
typedef unsigned int UINT32; /* unsigned 32bit */
typedef signed char INT8; /* signed 8bit */
typedef signed short INT16; /* signed 16bit */
typedef signed int INT32; /* signed 32bit */
#endif
typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec);
typedef void (*OPL_IRQHANDLER)(int param,int irq);
typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us);
typedef void (*OPL_PORTHANDLER_W)(int param,unsigned char data);
typedef unsigned char (*OPL_PORTHANDLER_R)(int param);
void *YM3812Init();
void YM3812Shutdown(void *chip);
void YM3812ResetChip(void *chip);
int YM3812Write(void *chip, int a, int v);
unsigned char YM3812Read(void *chip, int a);
void YM3812UpdateOne(void *chip, float *buffer, int length);
void YM3812SetStereo(void *chip, bool stereo);
void YM3812SetPanning(void *chip, int c, int pan);
FString YM3812GetVoiceString(void *chip);
#endif #endif

View file

@ -58,8 +58,8 @@ void OPLio::WriteDelay(int ticks)
void OPLio::OPLwriteReg(int which, uint reg, uchar data) void OPLio::OPLwriteReg(int which, uint reg, uchar data)
{ {
YM3812Write (chips[which], 0, reg); chips[which]->Write(0, reg);
YM3812Write (chips[which], 1, data); chips[which]->Write(1, data);
} }
/* /*
@ -256,7 +256,7 @@ void OPLio::OPLwritePan(uint channel, struct OPL2instrument *instr, int pan)
// Set real panning if we're using emulated chips. // Set real panning if we're using emulated chips.
if (chips[0] != NULL) if (chips[0] != NULL)
{ {
YM3812SetPanning(chips[channel/9], channel%9, pan+64); chips[channel/9]->SetPanning(channel%9, pan+64);
} }
} }
} }
@ -307,33 +307,22 @@ void OPLio::OPLshutup(void)
int OPLio::OPLinit(uint numchips, bool stereo) int OPLio::OPLinit(uint numchips, bool stereo)
{ {
assert(numchips >= 1 && numchips <= 2); assert(numchips >= 1 && numchips <= 2);
chips[0] = YM3812Init(); uint i;
chips[1] = NULL; memset(chips, 0, sizeof(chips));
if (chips[0] != NULL) for (i = 0; i < numchips; ++i)
{ {
YM3812SetStereo(chips[0], stereo); OPLEmul *chip = YM3812Init(stereo);
if (numchips > 1)
if (chip == NULL)
{ {
chips[1] = YM3812Init(); break;
if (chips[1] == NULL)
{
YM3812Shutdown(chips[0]);
chips[0] = NULL;
return -1;
} }
else chips[i] = chip;
{
YM3812SetStereo(chips[1], stereo);
} }
} NumChips = i;
} OPLchannels = OPL2CHANNELS * i;
else
{
return -1;
}
OPLchannels = OPL2CHANNELS * numchips;
OPLwriteInitState(); OPLwriteInitState();
return 0; return i;
} }
void OPLio::OPLwriteInitState() void OPLio::OPLwriteInitState()
@ -352,8 +341,12 @@ void OPLio::OPLwriteInitState()
*/ */
void OPLio::OPLdeinit(void) void OPLio::OPLdeinit(void)
{ {
YM3812Shutdown (chips[0]); for (size_t i = 0; i < countof(chips); ++i)
chips[0] = NULL; {
YM3812Shutdown (chips[1]); if (chips[i] != NULL)
chips[1] = NULL; {
delete chips[i];
chips[i] = NULL;
}
}
} }

View file

@ -92,7 +92,7 @@ OPLMIDIDevice::OPLMIDIDevice()
int OPLMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) int OPLMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata)
{ {
if (io == NULL || io->OPLinit(TwoChips + 1, IsStereo)) if (io == NULL || 0 == io->OPLinit(TwoChips + 1, IsStereo))
{ {
return 1; return 1;
} }

View file

@ -155,7 +155,7 @@ int DiskWriterIO::OPLinit(uint numchips, bool dontcare)
if (File == NULL) if (File == NULL)
{ {
Printf("Could not open %s for writing.\n", Filename.GetChars()); Printf("Could not open %s for writing.\n", Filename.GetChars());
return -1; return 0;
} }
if (Format == FMT_RDOS) if (Format == FMT_RDOS)
@ -189,7 +189,7 @@ int DiskWriterIO::OPLinit(uint numchips, bool dontcare)
CurChip = 0; CurChip = 0;
OPLchannels = OPL2CHANNELS * numchips; OPLchannels = OPL2CHANNELS * numchips;
OPLwriteInitState(); OPLwriteInitState();
return 0; return 1;
} }
//========================================================================== //==========================================================================

View file

@ -183,8 +183,9 @@ struct OPLio {
virtual void SetClockRate(double samples_per_tick); virtual void SetClockRate(double samples_per_tick);
virtual void WriteDelay(int ticks); virtual void WriteDelay(int ticks);
class OPLEmul *chips[2];
uint OPLchannels; uint OPLchannels;
void *chips[2]; uint NumChips;
}; };
struct DiskWriterIO : public OPLio struct DiskWriterIO : public OPLio

21
src/oplsynth/opl.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef OPL_H
#define OPL_H
#include "zstring.h"
// Abstract base class for OPL emulators
class OPLEmul
{
public:
OPLEmul() {}
virtual ~OPLEmul() {}
virtual void Reset() = 0;
virtual int Write(int a, int v) = 0;
virtual void Update(float *buffer, int length) = 0;
virtual void SetPanning(int c, int pan) = 0;
virtual FString GetVoiceString() { return FString(); }
};
#endif

View file

@ -40,7 +40,7 @@ void OPLmusicBlock::ResetChips ()
TwoChips = !opl_onechip; TwoChips = !opl_onechip;
ChipAccess.Enter(); ChipAccess.Enter();
io->OPLdeinit (); io->OPLdeinit ();
io->OPLinit (TwoChips + 1, IsStereo); TwoChips = io->OPLinit(TwoChips + 1, IsStereo) == 2;
ChipAccess.Leave(); ChipAccess.Leave();
} }
@ -77,7 +77,7 @@ fail: delete[] scoredata;
memcpy(scoredata, &musiccache[0], len); memcpy(scoredata, &musiccache[0], len);
} }
if (io->OPLinit (TwoChips + 1)) if (0 == io->OPLinit (TwoChips + 1))
{ {
goto fail; goto fail;
} }
@ -236,11 +236,14 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
double ticky = NextTickIn; double ticky = NextTickIn;
int tick_in = int(NextTickIn); int tick_in = int(NextTickIn);
int samplesleft = MIN(numsamples, tick_in); int samplesleft = MIN(numsamples, tick_in);
size_t i;
if (samplesleft > 0) if (samplesleft > 0)
{ {
YM3812UpdateOne (io->chips[0], samples1, samplesleft); for (i = 0; i < io->NumChips; ++i)
YM3812UpdateOne (io->chips[1], samples1, samplesleft); {
io->chips[i]->Update(samples1, samplesleft);
}
OffsetSamples(samples1, samplesleft << int(IsStereo)); OffsetSamples(samples1, samplesleft << int(IsStereo));
assert(NextTickIn == ticky); assert(NextTickIn == ticky);
NextTickIn -= samplesleft; NextTickIn -= samplesleft;
@ -259,8 +262,10 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
{ {
if (numsamples > 0) if (numsamples > 0)
{ {
YM3812UpdateOne (io->chips[0], samples1, numsamples); for (i = 0; i < io->NumChips; ++i)
YM3812UpdateOne (io->chips[1], samples1, numsamples); {
io->chips[i]->Update(samples1, samplesleft);
}
OffsetSamples(samples1, numsamples << int(IsStereo)); OffsetSamples(samples1, numsamples << int(IsStereo));
} }
res = false; res = false;

View file

@ -2618,6 +2618,10 @@
RelativePath=".\src\oplsynth\muslib.h" RelativePath=".\src\oplsynth\muslib.h"
> >
</File> </File>
<File
RelativePath=".\src\oplsynth\opl.h"
>
</File>
<File <File
RelativePath=".\src\oplsynth\opl_mus_player.cpp" RelativePath=".\src\oplsynth\opl_mus_player.cpp"
> >