- 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
#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 EG_SH 16 /* 16.16 fixed point (EG timing) */
@ -246,6 +248,8 @@ typedef struct{
UINT32 fc; /* Freq. Increment base */
UINT32 ksl_base; /* KeyScaleLevel Base step */
UINT8 kcode; /* key code (for key scaling) */
float LeftVol; /* volumes for stereo panning */
float RightVol;
} OPL_CH;
/* OPL state */
@ -297,6 +301,7 @@ typedef struct fm_opl_f {
int rate; /* sampling rate (Hz) */
double freqbase; /* frequency base */
double TimerBase; /* Timer base time (==sampling time)*/
bool IsStereo; /* Write stereo output */
} 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))
/* calculate output */
INLINE void OPL_CALC_CH( OPL_CH *CH, float *buffer )
INLINE float OPL_CALC_CH( OPL_CH *CH )
{
OPL_SLOT *SLOT;
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);
/* [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));
/*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)
@ -1897,6 +1910,28 @@ void YM3812SetUpdateHandler(void *chip,OPL_UPDATEHANDLER UpdateHandler,int 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
@ -1969,7 +2004,16 @@ static bool CalcVoice (FM_OPL *OPL, int voice, float *buffer, int length)
advance_lfo(OPL);
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);
}
@ -1987,7 +2031,18 @@ static bool CalcRhythm (FM_OPL *OPL, float *buffer, int length)
output = 0;
OPL_CALC_RH(&OPL->P_CH[0], OPL->noise_rng & 1);
/* [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_noise(OPL);

View File

@ -32,6 +32,8 @@ int YM3812Write(void *chip, int a, int v);
unsigned char YM3812Read(void *chip, int a);
int YM3812TimerOver(void *chip, int c);
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 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
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
*/
int OPLio::OPLinit(uint numchips)
int OPLio::OPLinit(uint numchips, bool stereo)
{
assert(numchips >= 1 && numchips <= 2);
chips[0] = YM3812Init (3579545, int(OPL_SAMPLE_RATE));
chips[1] = NULL;
if (chips[0] != NULL)
{
YM3812SetStereo(chips[0], stereo);
if (numchips > 1)
{
chips[1] = YM3812Init (3579545, int(OPL_SAMPLE_RATE));
@ -314,6 +321,10 @@ int OPLio::OPLinit(uint numchips)
chips[0] = NULL;
return -1;
}
else
{
YM3812SetStereo(chips[1], stereo);
}
}
}
else

View File

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

View File

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

View File

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

View File

@ -21,6 +21,7 @@ protected:
bool TwoChips;
bool Looping;
double LastOffset;
bool IsStereo;
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)
{
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)
{
return 2;

View File

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