mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-10 23:02:08 +00:00
- 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:
parent
1b5bff9603
commit
3b0ad55285
9 changed files with 95 additions and 18 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -21,6 +21,7 @@ protected:
|
|||
bool TwoChips;
|
||||
bool Looping;
|
||||
double LastOffset;
|
||||
bool IsStereo;
|
||||
|
||||
FCriticalSection ChipAccess;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue