- Since our OPL chips are emulated, we can give them a feature that real OPL2 chips lacked:

Full stereo panning for the MIDI player. (The raw OPL players are unaffected.) To get the
  mono output back, you can set opl_stereo to false.
- Changed SoftSynthMIDIDevice::OpenStream() to allocate the same number of samples for stereo
  and mono streams.

SVN r3929 (trunk)
This commit is contained in:
Randy Heit 2012-11-02 03:25:50 +00:00
parent 1b5bff9603
commit 3b0ad55285
9 changed files with 95 additions and 18 deletions

View file

@ -115,6 +115,8 @@ Revision History:
#define INLINE __inline #define INLINE __inline
#endif #endif
#define CENTER_PANNING_POWER 0.70710678118 /* [RH] volume at center for EQP */
#define HALF_PI (PI/2)
#define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ #define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */
#define EG_SH 16 /* 16.16 fixed point (EG timing) */ #define EG_SH 16 /* 16.16 fixed point (EG timing) */
@ -246,6 +248,8 @@ typedef struct{
UINT32 fc; /* Freq. Increment base */ UINT32 fc; /* Freq. Increment base */
UINT32 ksl_base; /* KeyScaleLevel Base step */ UINT32 ksl_base; /* KeyScaleLevel Base step */
UINT8 kcode; /* key code (for key scaling) */ UINT8 kcode; /* key code (for key scaling) */
float LeftVol; /* volumes for stereo panning */
float RightVol;
} OPL_CH; } OPL_CH;
/* OPL state */ /* OPL state */
@ -297,6 +301,7 @@ typedef struct fm_opl_f {
int rate; /* sampling rate (Hz) */ int rate; /* sampling rate (Hz) */
double freqbase; /* frequency base */ double freqbase; /* frequency base */
double TimerBase; /* Timer base time (==sampling time)*/ double TimerBase; /* Timer base time (==sampling time)*/
bool IsStereo; /* Write stereo output */
} FM_OPL; } FM_OPL;
@ -876,7 +881,7 @@ INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm, unsign
#define volume_calc(OP) ((OP)->TLL + ((UINT32)(OP)->volume) + (LFO_AM & (OP)->AMmask)) #define volume_calc(OP) ((OP)->TLL + ((UINT32)(OP)->volume) + (LFO_AM & (OP)->AMmask))
/* calculate output */ /* calculate output */
INLINE void OPL_CALC_CH( OPL_CH *CH, float *buffer ) INLINE float OPL_CALC_CH( OPL_CH *CH )
{ {
OPL_SLOT *SLOT; OPL_SLOT *SLOT;
unsigned int env; unsigned int env;
@ -905,8 +910,9 @@ INLINE void OPL_CALC_CH( OPL_CH *CH, float *buffer )
{ {
output += op_calc(SLOT->Cnt, env, phase_modulation, SLOT->wavetable); output += op_calc(SLOT->Cnt, env, phase_modulation, SLOT->wavetable);
/* [RH] Convert to floating point. */ /* [RH] Convert to floating point. */
*buffer += float(output) / 10240; return float(output) / 10240;
} }
return 0;
} }
/* /*
@ -1282,6 +1288,13 @@ static void OPL_initalize(FM_OPL *OPL)
OPL->eg_timer_overflow = UINT32(( 1 ) * (1<<EG_SH)); OPL->eg_timer_overflow = UINT32(( 1 ) * (1<<EG_SH));
/*logerror("OPLinit eg_timer_add=%8x eg_timer_overflow=%8x\n", OPL->eg_timer_add, OPL->eg_timer_overflow);*/ /*logerror("OPLinit eg_timer_add=%8x eg_timer_overflow=%8x\n", OPL->eg_timer_add, OPL->eg_timer_overflow);*/
// [RH] Support full MIDI panning. (But default to mono and center panning.)
OPL->IsStereo = false;
for (int i = 0; i < 9; ++i)
{
OPL->P_CH[i].LeftVol = (float)CENTER_PANNING_POWER;
OPL->P_CH[i].RightVol = (float)CENTER_PANNING_POWER;
}
} }
INLINE void FM_KEYON(OPL_SLOT *SLOT, UINT32 key_set) INLINE void FM_KEYON(OPL_SLOT *SLOT, UINT32 key_set)
@ -1897,6 +1910,28 @@ void YM3812SetUpdateHandler(void *chip,OPL_UPDATEHANDLER UpdateHandler,int param
OPLSetUpdateHandler(YM3812, UpdateHandler, param); OPLSetUpdateHandler(YM3812, UpdateHandler, param);
} }
/* [RH] Full support for MIDI panning */
void YM3812SetStereo(void *chip, bool stereo)
{
if (chip != NULL)
{
FM_OPL *YM3812 = (FM_OPL *)chip;
YM3812->IsStereo = stereo;
}
}
void YM3812SetPanning(void *chip, int c, int pan)
{
if (chip != NULL)
{
FM_OPL *YM3812 = (FM_OPL *)chip;
// This is the MIDI-recommended pan formula. 0 and 1 are
// both hard left so that 64 can be perfectly center.
double level = (pan <= 1) ? 0 : (pan - 1) / 126.0;
YM3812->P_CH[c].LeftVol = (float)cos(HALF_PI * level);
YM3812->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
@ -1969,7 +2004,16 @@ static bool CalcVoice (FM_OPL *OPL, int voice, float *buffer, int length)
advance_lfo(OPL); advance_lfo(OPL);
output = 0; output = 0;
OPL_CALC_CH(CH, buffer + i); float sample = OPL_CALC_CH(CH);
if (!OPL->IsStereo)
{
buffer[i] += sample;
}
else
{
buffer[i*2] += sample * CH->LeftVol;
buffer[i*2+1] += sample * CH->RightVol;
}
advance(OPL, voice, voice); advance(OPL, voice, voice);
} }
@ -1987,7 +2031,18 @@ static bool CalcRhythm (FM_OPL *OPL, float *buffer, int length)
output = 0; output = 0;
OPL_CALC_RH(&OPL->P_CH[0], OPL->noise_rng & 1); OPL_CALC_RH(&OPL->P_CH[0], OPL->noise_rng & 1);
/* [RH] Convert to floating point. */ /* [RH] Convert to floating point. */
buffer[i] += float(output) / 10240; float sample = float(output) / 10240;
if (!OPL->IsStereo)
{
buffer[i] += sample;
}
else
{
// [RH] Always use center panning for rhythm.
// The MIDI player doesn't use the rhythm section anyway.
buffer[i*2] += sample * CENTER_PANNING_POWER;
buffer[i*2+1] += sample * CENTER_PANNING_POWER;
}
advance(OPL, 6, 8); advance(OPL, 6, 8);
advance_noise(OPL); advance_noise(OPL);

View file

@ -32,6 +32,8 @@ int YM3812Write(void *chip, int a, int v);
unsigned char YM3812Read(void *chip, int a); unsigned char YM3812Read(void *chip, int a);
int YM3812TimerOver(void *chip, int c); int YM3812TimerOver(void *chip, int c);
void YM3812UpdateOne(void *chip, float *buffer, int length); void YM3812UpdateOne(void *chip, float *buffer, int length);
void YM3812SetStereo(void *chip, bool stereo);
void YM3812SetPanning(void *chip, int c, int pan);
void YM3812SetTimerHandler(void *chip, OPL_TIMERHANDLER TimerHandler, int channelOffset); void YM3812SetTimerHandler(void *chip, OPL_TIMERHANDLER TimerHandler, int channelOffset);
void YM3812SetIRQHandler(void *chip, OPL_IRQHANDLER IRQHandler, int param); void YM3812SetIRQHandler(void *chip, OPL_IRQHANDLER IRQHandler, int param);

View file

@ -252,6 +252,12 @@ void OPLio::OPLwritePan(uint channel, struct OPL2instrument *instr, int pan)
else bits = 0x30; // both else bits = 0x30; // both
OPLwriteValue(0xC0, channel, instr->feedback | bits); OPLwriteValue(0xC0, channel, instr->feedback | bits);
// Set real panning if we're using emulated chips.
if (chips[0] != NULL)
{
YM3812SetPanning(chips[channel/9], channel%9, pan+64);
}
} }
} }
@ -298,13 +304,14 @@ void OPLio::OPLshutup(void)
/* /*
* Initialize hardware upon startup * Initialize hardware upon startup
*/ */
int OPLio::OPLinit(uint numchips) int OPLio::OPLinit(uint numchips, bool stereo)
{ {
assert(numchips >= 1 && numchips <= 2); assert(numchips >= 1 && numchips <= 2);
chips[0] = YM3812Init (3579545, int(OPL_SAMPLE_RATE)); chips[0] = YM3812Init (3579545, int(OPL_SAMPLE_RATE));
chips[1] = NULL; chips[1] = NULL;
if (chips[0] != NULL) if (chips[0] != NULL)
{ {
YM3812SetStereo(chips[0], stereo);
if (numchips > 1) if (numchips > 1)
{ {
chips[1] = YM3812Init (3579545, int(OPL_SAMPLE_RATE)); chips[1] = YM3812Init (3579545, int(OPL_SAMPLE_RATE));
@ -314,6 +321,10 @@ int OPLio::OPLinit(uint numchips)
chips[0] = NULL; chips[0] = NULL;
return -1; return -1;
} }
else
{
YM3812SetStereo(chips[1], stereo);
}
} }
} }
else else

View file

@ -64,6 +64,8 @@
// PUBLIC DATA DEFINITIONS ------------------------------------------------- // PUBLIC DATA DEFINITIONS -------------------------------------------------
CVAR(Bool, opl_stereo, true, CVAR_ARCHIVE);
// CODE -------------------------------------------------------------------- // CODE --------------------------------------------------------------------
//========================================================================== //==========================================================================
@ -74,6 +76,7 @@
OPLMIDIDevice::OPLMIDIDevice() OPLMIDIDevice::OPLMIDIDevice()
{ {
IsStereo = opl_stereo;
FWadLump data = Wads.OpenLumpName("GENMIDI"); FWadLump data = Wads.OpenLumpName("GENMIDI");
OPLloadBank(data); OPLloadBank(data);
SampleRate = (int)OPL_SAMPLE_RATE; SampleRate = (int)OPL_SAMPLE_RATE;
@ -89,11 +92,11 @@ 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)) if (io == NULL || io->OPLinit(TwoChips + 1, IsStereo))
{ {
return 1; return 1;
} }
int ret = OpenStream(14, SoundStream::Mono, callback, userdata); int ret = OpenStream(14, IsStereo ? 0 : SoundStream::Mono, callback, userdata);
if (ret == 0) if (ret == 0)
{ {
OPLstopMusic(); OPLstopMusic();

View file

@ -177,7 +177,7 @@ struct OPLio {
void OPLshutup(void); void OPLshutup(void);
void OPLwriteInitState(); void OPLwriteInitState();
virtual int OPLinit(uint numchips); virtual int OPLinit(uint numchips, bool stereo=false);
virtual void OPLdeinit(void); virtual void OPLdeinit(void);
virtual void OPLwriteReg(int which, uint reg, uchar data); virtual void OPLwriteReg(int which, uint reg, uchar data);
virtual void SetClockRate(double samples_per_tick); virtual void SetClockRate(double samples_per_tick);
@ -192,7 +192,7 @@ struct DiskWriterIO : public OPLio
DiskWriterIO(const char *filename); DiskWriterIO(const char *filename);
~DiskWriterIO(); ~DiskWriterIO();
int OPLinit(uint numchips); int OPLinit(uint numchips, bool notused=false);
void OPLdeinit(); void OPLdeinit();
void OPLwriteReg(int which, uint reg, uchar data); void OPLwriteReg(int which, uint reg, uchar data);
void SetClockRate(double samples_per_tick); void SetClockRate(double samples_per_tick);

View file

@ -25,6 +25,7 @@ OPLmusicBlock::OPLmusicBlock()
LastOffset = 0; LastOffset = 0;
TwoChips = !opl_onechip; TwoChips = !opl_onechip;
Looping = false; Looping = false;
IsStereo = false;
io = NULL; io = NULL;
io = new OPLio; io = new OPLio;
} }
@ -39,7 +40,7 @@ void OPLmusicBlock::ResetChips ()
TwoChips = !opl_onechip; TwoChips = !opl_onechip;
ChipAccess.Enter(); ChipAccess.Enter();
io->OPLdeinit (); io->OPLdeinit ();
io->OPLinit (TwoChips + 1); io->OPLinit (TwoChips + 1, IsStereo);
ChipAccess.Leave(); ChipAccess.Leave();
} }
@ -222,13 +223,11 @@ void OPLmusicFile::Restart ()
bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
{ {
float *samples = (float *)buff; float *samples1 = (float *)buff;
float *samples1; int numsamples = numbytes / (sizeof(float) << int(IsStereo));
int numsamples = numbytes / sizeof(float);
bool prevEnded = false; bool prevEnded = false;
bool res = true; bool res = true;
samples1 = samples;
memset(buff, 0, numbytes); memset(buff, 0, numbytes);
ChipAccess.Enter(); ChipAccess.Enter();
@ -242,12 +241,12 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
{ {
YM3812UpdateOne (io->chips[0], samples1, samplesleft); YM3812UpdateOne (io->chips[0], samples1, samplesleft);
YM3812UpdateOne (io->chips[1], samples1, samplesleft); YM3812UpdateOne (io->chips[1], samples1, samplesleft);
OffsetSamples(samples1, samplesleft); OffsetSamples(samples1, samplesleft << int(IsStereo));
assert(NextTickIn == ticky); assert(NextTickIn == ticky);
NextTickIn -= samplesleft; NextTickIn -= samplesleft;
assert (NextTickIn >= 0); assert (NextTickIn >= 0);
numsamples -= samplesleft; numsamples -= samplesleft;
samples1 += samplesleft; samples1 += samplesleft << int(IsStereo);
} }
if (NextTickIn < 1) if (NextTickIn < 1)
@ -262,7 +261,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
{ {
YM3812UpdateOne (io->chips[0], samples1, numsamples); YM3812UpdateOne (io->chips[0], samples1, numsamples);
YM3812UpdateOne (io->chips[1], samples1, numsamples); YM3812UpdateOne (io->chips[1], samples1, numsamples);
OffsetSamples(samples1, numsamples); OffsetSamples(samples1, numsamples << int(IsStereo));
} }
res = false; res = false;
break; break;

View file

@ -21,6 +21,7 @@ protected:
bool TwoChips; bool TwoChips;
bool Looping; bool Looping;
double LastOffset; double LastOffset;
bool IsStereo;
FCriticalSection ChipAccess; FCriticalSection ChipAccess;
}; };

View file

@ -96,7 +96,12 @@ SoftSynthMIDIDevice::~SoftSynthMIDIDevice()
int SoftSynthMIDIDevice::OpenStream(int chunks, int flags, void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) int SoftSynthMIDIDevice::OpenStream(int chunks, int flags, void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata)
{ {
Stream = GSnd->CreateStream(FillStream, (SampleRate / chunks) * 4, SoundStream::Float | flags, SampleRate, this); int chunksize = (SampleRate / chunks) * 4;
if (!(flags & SoundStream::Mono))
{
chunksize *= 2;
}
Stream = GSnd->CreateStream(FillStream, chunksize, SoundStream::Float | flags, SampleRate, this);
if (Stream == NULL) if (Stream == NULL)
{ {
return 2; return 2;

View file

@ -1409,6 +1409,7 @@ OptionMenu AdvSoundOptions
StaticText " " StaticText " "
StaticText "OPL Synthesis", 1 StaticText "OPL Synthesis", 1
Option "Only emulate one OPL chip", "opl_onechip", "OnOff" Option "Only emulate one OPL chip", "opl_onechip", "OnOff"
Option "Support MIDI stero panning", "opl_stereo", "OnOff"
StaticText " " StaticText " "
StaticText "GUS Emulation", 1 StaticText "GUS Emulation", 1
Slider "MIDI voices", "midi_voices", 16, 256, 4, 0 Slider "MIDI voices", "midi_voices", 16, 256, 4, 0