From 3b0ad55285284b80d794b0ec01c1de4839d54b9c Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Fri, 2 Nov 2012 03:25:50 +0000 Subject: [PATCH] - 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) --- src/oplsynth/fmopl.cpp | 63 ++++++++++++++++++++++-- src/oplsynth/fmopl.h | 2 + src/oplsynth/mlopl_io.cpp | 13 ++++- src/oplsynth/music_opl_mididevice.cpp | 7 ++- src/oplsynth/muslib.h | 4 +- src/oplsynth/opl_mus_player.cpp | 15 +++--- src/oplsynth/opl_mus_player.h | 1 + src/sound/music_softsynth_mididevice.cpp | 7 ++- wadsrc/static/menudef.txt | 1 + 9 files changed, 95 insertions(+), 18 deletions(-) diff --git a/src/oplsynth/fmopl.cpp b/src/oplsynth/fmopl.cpp index 40efe3164..9fdc36ccb 100644 --- a/src/oplsynth/fmopl.cpp +++ b/src/oplsynth/fmopl.cpp @@ -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_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); diff --git a/src/oplsynth/fmopl.h b/src/oplsynth/fmopl.h index 2b3eefb6e..0064f642e 100644 --- a/src/oplsynth/fmopl.h +++ b/src/oplsynth/fmopl.h @@ -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); diff --git a/src/oplsynth/mlopl_io.cpp b/src/oplsynth/mlopl_io.cpp index 614c11f59..2c5bbc010 100644 --- a/src/oplsynth/mlopl_io.cpp +++ b/src/oplsynth/mlopl_io.cpp @@ -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 diff --git a/src/oplsynth/music_opl_mididevice.cpp b/src/oplsynth/music_opl_mididevice.cpp index ade5b5c12..d36bacb54 100644 --- a/src/oplsynth/music_opl_mididevice.cpp +++ b/src/oplsynth/music_opl_mididevice.cpp @@ -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(); diff --git a/src/oplsynth/muslib.h b/src/oplsynth/muslib.h index 7cc533ea5..189649551 100644 --- a/src/oplsynth/muslib.h +++ b/src/oplsynth/muslib.h @@ -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); diff --git a/src/oplsynth/opl_mus_player.cpp b/src/oplsynth/opl_mus_player.cpp index 31eef55fd..6df36e707 100644 --- a/src/oplsynth/opl_mus_player.cpp +++ b/src/oplsynth/opl_mus_player.cpp @@ -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; diff --git a/src/oplsynth/opl_mus_player.h b/src/oplsynth/opl_mus_player.h index d1357e0e0..57744ebfc 100644 --- a/src/oplsynth/opl_mus_player.h +++ b/src/oplsynth/opl_mus_player.h @@ -21,6 +21,7 @@ protected: bool TwoChips; bool Looping; double LastOffset; + bool IsStereo; FCriticalSection ChipAccess; }; diff --git a/src/sound/music_softsynth_mididevice.cpp b/src/sound/music_softsynth_mididevice.cpp index 5cb21687a..56d77e735 100644 --- a/src/sound/music_softsynth_mididevice.cpp +++ b/src/sound/music_softsynth_mididevice.cpp @@ -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; diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 38ac1bd74..c7dd62233 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -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