diff --git a/docs/rh-log.txt b/docs/rh-log.txt index b114e8be3..ba1b33b72 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,3 +1,17 @@ +March 28, 2008 +- Moved sound sample rate, buffer size, and buffer count to the + advanced sound options menu. Removed opl_enable from the menu. +- Added OPL synth as MIDI device -3. Since this is based on the MUS player + code, it only supports those events and controllers supported by MUS. + Some of Duke's MIDIs sound awful, but I think that may be more because + it's using different instruments... There's a thread in the MIDI streamer + class that could be taken out for Linux, since it doesn't need to deal + with the Windows Multimedia API, but for now, this is still Windows-only. +- Changed the output of the OPL emulator from 32-bit integers to 32-bit + floats, so I can write its output directly to the stream buffer. In + addition, this lets me bring the OPL volume level much closer to the + standard MIDI volume. + March 27, 2008 - Did some restructuring of the OPL code in preparation for turning it into a general MIDI player. diff --git a/src/m_options.cpp b/src/m_options.cpp index 02b515c95..6b0ae1348 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -1244,9 +1244,6 @@ static menuitem_t SoundItems[] = { ediscrete,"Speaker mode", {&snd_speakermode}, {8.0}, {0.0}, {0.0}, {(value_t *)SpeakerModes} }, { ediscrete,"Resampler", {&snd_resampler}, {4.0}, {0.0}, {0.0}, {(value_t *)Resamplers} }, { discrete, "HRTF filter", {&snd_hrtf}, {2.0}, {0.0}, {0.0}, {(value_t *)OnOff} }, - { discrete, "Sample rate", {&snd_samplerate}, {8.0}, {0.0}, {0.0}, {SampleRates} }, - { discrete, "Buffer size", {&snd_buffersize}, {8.0}, {0.0}, {0.0}, {BufferSizes} }, - { discrete, "Buffer count", {&snd_buffercount}, {12.0}, {0.0}, {0.0}, {BufferCounts} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { more, "Advanced options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)AdvSoundOptions} }, @@ -1274,15 +1271,18 @@ EXTERN_CVAR (Bool, opl_onechip) static menuitem_t AdvSoundItems[] = { - { whitetext,"OPL Synthesis", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Use FM Synth for MUS music",{&opl_enable}, {2.0}, {0.0}, {0.0}, {OnOff} }, + { discrete, "Sample rate", {&snd_samplerate}, {8.0}, {0.0}, {0.0}, {SampleRates} }, + { discrete, "Buffer size", {&snd_buffersize}, {8.0}, {0.0}, {0.0}, {BufferSizes} }, + { discrete, "Buffer count", {&snd_buffercount}, {12.0}, {0.0}, {0.0}, {BufferCounts} }, + { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, + { whitetext,"OPL Synthesis", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { discrete, "Only emulate one OPL chip", {&opl_onechip}, {2.0}, {0.0}, {0.0}, {OnOff} }, }; static menu_t AdvSoundMenu = { "ADVANCED SOUND OPTIONS", - 1, + 0, countof(AdvSoundItems), 0, AdvSoundItems, diff --git a/src/oplsynth/mlopl.cpp b/src/oplsynth/mlopl.cpp index 6cd589c60..0622962e9 100644 --- a/src/oplsynth/mlopl.cpp +++ b/src/oplsynth/mlopl.cpp @@ -208,7 +208,8 @@ struct OP2instrEntry *musicBlock::getInstrument(uint channel, uchar note) if (note < 35 || note > 81) return NULL; /* wrong percussion number */ instrnumber = note + (128-35); - } else + } + else { instrnumber = driverdata.channelInstr[channel]; } @@ -295,15 +296,14 @@ void musicBlock::OPLchangeControl(uint channel, uchar controller, int value) { uint i; uint id = channel; - struct OPLdata *data = &driverdata; switch (controller) { case ctrlPatch: /* change instrument */ - data->channelInstr[channel] = value; + OPLprogramChange(channel, value); break; case ctrlModulation: - data->channelModulation[channel] = value; + driverdata.channelModulation[channel] = value; for(i = 0; i < io->OPLchannels; i++) { struct channelEntry *ch = &channels[i]; @@ -325,7 +325,7 @@ void musicBlock::OPLchangeControl(uint channel, uchar controller, int value) } break; case ctrlVolume: /* change volume */ - data->channelVolume[channel] = value; + driverdata.channelVolume[channel] = value; for(i = 0; i < io->OPLchannels; i++) { struct channelEntry *ch = &channels[i]; @@ -338,7 +338,7 @@ void musicBlock::OPLchangeControl(uint channel, uchar controller, int value) } break; case ctrlPan: /* change pan (balance) */ - data->channelPan[channel] = value -= 64; + driverdata.channelPan[channel] = value -= 64; for(i = 0; i < io->OPLchannels; i++) { struct channelEntry *ch = &channels[i]; @@ -350,22 +350,26 @@ void musicBlock::OPLchangeControl(uint channel, uchar controller, int value) } break; case ctrlSustainPedal: /* change sustain pedal (hold) */ - data->channelSustain[channel] = value; + driverdata.channelSustain[channel] = value; if (value < 0x40) releaseSustain(channel); break; } } +void musicBlock::OPLprogramChange(uint channel, int value) +{ + driverdata.channelInstr[channel] = value; +} -void musicBlock::OPLplayMusic() +void musicBlock::OPLplayMusic(int vol) { uint i; struct OPLdata *data = &driverdata; for (i = 0; i < CHANNELS; i++) { - data->channelVolume[i] = 127; /* default volume 127 (full volume) */ + data->channelVolume[i] = vol; /* default volume 127 for MUS (full volume) */ data->channelSustain[i] = 0; data->channelLastVolume[i] = 64; data->channelPitch[i] = 64; diff --git a/src/oplsynth/music_opl_mididevice.cpp b/src/oplsynth/music_opl_mididevice.cpp new file mode 100644 index 000000000..e8383c371 --- /dev/null +++ b/src/oplsynth/music_opl_mididevice.cpp @@ -0,0 +1,481 @@ +/* +** music_opl_mididevice.cpp +** Provides an emulated OPL implementation of a MIDI output device. +** +**--------------------------------------------------------------------------- +** Copyright 2008 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** Uses Vladimir Arnost's MUS player library. +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include "i_musicinterns.h" +#include "templates.h" +#include "doomdef.h" +#include "m_swap.h" +#include "w_wad.h" + +// MACROS ------------------------------------------------------------------ + +#if defined(_DEBUG) && defined(_WIN32) +#define UNIMPL(m,c,s,t) \ + { char foo[128]; sprintf(foo, m, c, s, t); OutputDebugString(foo); } +#else +#define UNIMPL(m,c,s,t) +#endif + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +extern OPLmusicBlock *BlockForStats; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// OPLMIDIDevice Contructor +// +//========================================================================== + +OPLMIDIDevice::OPLMIDIDevice() +{ + Stream = NULL; + Tempo = 0; + Division = 0; + Events = NULL; + Started = false; + + FWadLump data = Wads.OpenLumpName("GENMIDI"); + OPLloadBank(data); +} + +//========================================================================== +// +// OPLMIDIDevice Destructor +// +//========================================================================== + +OPLMIDIDevice::~OPLMIDIDevice() +{ + Close(); +} + +//========================================================================== +// +// OPLMIDIDevice :: Open +// +// Returns 0 on success. +// +//========================================================================== + +int OPLMIDIDevice::Open(void (*callback)(UINT, void *, DWORD, DWORD), void *userdata) +{ + if (io == NULL || io->OPLinit(TwoChips + 1, uint(OPL_SAMPLE_RATE))) + { + return 1; + } + + Stream = GSnd->CreateStream(FillStream, int(OPL_SAMPLE_RATE / 14) * 4, + SoundStream::Mono | SoundStream::Float, int(OPL_SAMPLE_RATE), this); + if (Stream == NULL) + { + return 2; + } + + Callback = callback; + CallbackData = userdata; + Tempo = 500000; + Division = 100; + CalcTickRate(); + + OPLstopMusic(); + OPLplayMusic(100); + + return 0; +} + +//========================================================================== +// +// OPLMIDIDevice :: Close +// +//========================================================================== + +void OPLMIDIDevice::Close() +{ + if (Stream != NULL) + { + delete Stream; + Stream = NULL; + } + io->OPLdeinit(); + Started = false; +} + +//========================================================================== +// +// OPLMIDIDevice :: IsOpen +// +//========================================================================== + +bool OPLMIDIDevice::IsOpen() const +{ + return Stream != NULL; +} + +//========================================================================== +// +// OPLMIDIDevice :: GetTechnology +// +//========================================================================== + +int OPLMIDIDevice::GetTechnology() const +{ + return MOD_FMSYNTH; +} + +//========================================================================== +// +// OPLMIDIDevice :: SetTempo +// +//========================================================================== + +int OPLMIDIDevice::SetTempo(int tempo) +{ + Tempo = tempo; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// OPLMIDIDevice :: SetTimeDiv +// +//========================================================================== + +int OPLMIDIDevice::SetTimeDiv(int timediv) +{ + Division = timediv; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// OPLMIDIDevice :: CalcTickRate +// +// Tempo is the number of microseconds per quarter note. +// Division is the number of ticks per quarter note. +// +//========================================================================== + +void OPLMIDIDevice::CalcTickRate() +{ + SamplesPerTick = OPL_SAMPLE_RATE / (1000000.0 / Tempo) / Division; +} + +//========================================================================== +// +// OPLMIDIDevice :: Resume +// +//========================================================================== + +int OPLMIDIDevice::Resume() +{ + if (!Started) + { + if (Stream->Play(true, 1, false)) + { + Started = true; + BlockForStats = this; + return 0; + } + return 1; + } + return 0; +} + +//========================================================================== +// +// OPLMIDIDevice :: Stop +// +//========================================================================== + +void OPLMIDIDevice::Stop() +{ + if (Started) + { + Stream->Stop(); + Started = false; + BlockForStats = NULL; + } +} + +//========================================================================== +// +// OPLMIDIDevice :: StreamOut +// +//========================================================================== + +int OPLMIDIDevice::StreamOut(MIDIHDR *header) +{ + Serialize(); + header->lpNext = NULL; + if (Events == NULL) + { + Events = header; + NextTickIn = SamplesPerTick * *(DWORD *)header->lpData; + Position = 0; + } + else + { + MIDIHDR **p; + + for (p = &Events; *p != NULL; p = &(*p)->lpNext) + { } + *p = header; + } + Unserialize(); + return 0; +} + +//========================================================================== +// +// OPLMIDIDevice :: PrepareHeader +// +//========================================================================== + +int OPLMIDIDevice::PrepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// OPLMIDIDevice :: UnprepareHeader +// +//========================================================================== + +int OPLMIDIDevice::UnprepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// OPLMIDIDevice :: FakeVolume +// +// Since the OPL output is rendered as a normal stream, its volume is +// controlled through the GSnd interface, not here. +// +//========================================================================== + +bool OPLMIDIDevice::FakeVolume() +{ + return false; +} + +//========================================================================== +// +// OPLMIDIDevice :: Pause +// +//========================================================================== + +bool OPLMIDIDevice::Pause(bool paused) +{ + if (Stream != NULL) + { + return Stream->SetPaused(paused); + } + return true; +} + +//========================================================================== +// +// OPLMIDIDevice :: PlayTick +// +// event[0] = delta time +// event[1] = unused +// event[2] = event +// +//========================================================================== + +int OPLMIDIDevice::PlayTick() +{ + DWORD delay = 0; + + while (delay == 0 && Events != NULL) + { + DWORD *event = (DWORD *)(Events->lpData + Position); + if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) + { + Tempo = MEVT_EVENTPARM(event[2]); + CalcTickRate(); + } + else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) + { // Should I handle master volume changes? + } + else if (MEVT_EVENTTYPE(event[2]) == 0) + { // Short MIDI event + int status = event[2] & 0xff; + int parm1 = (event[2] >> 8) & 0x7f; + int parm2 = (event[2] >> 16) & 0x7f; + HandleEvent(status, parm1, parm2); + } + + // Advance to next event. + if (event[2] < 0x80000000) + { // Short message + Position += 12; + } + else + { // Long message + Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3); + } + + // Did we use up this buffer? + if (Position >= Events->dwBytesRecorded) + { + Events = Events->lpNext; + Position = 0; + + if (Callback != NULL) + { + Callback(MOM_DONE, CallbackData, 0, 0); + } + } + + if (Events == NULL) + { // No more events. Just return something to keep the song playing + // while we wait for more to be submitted. + return int(Division); + } + + delay = *(DWORD *)(Events->lpData + Position); + } + return delay; +} + +//========================================================================== +// +// OPLMIDIDevice :: HandleEvent +// +// Processes a normal MIDI event. +// +//========================================================================== + +void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2) +{ + int command = status & 0xF0; + int channel = status & 0x0F; + + // Swap channels 9 and 15, because their roles are reversed + // in MUS and MIDI formats. + if (channel == 9) + { + channel = 15; + } + else if (channel == 15) + { + channel = 9; + } + + switch (command) + { + case MIDI_NOTEOFF: + playingcount--; + OPLreleaseNote(channel, parm1); + break; + + case MIDI_NOTEON: + playingcount++; + OPLplayNote(channel, parm1, parm2); + break; + + case MIDI_POLYPRESS: + UNIMPL("Unhandled note aftertouch: Channel %d, note %d, value %d\n", channel, parm1, parm2); + break; + + case MIDI_CTRLCHANGE: + switch (parm1) + { + case 0: OPLchangeControl(channel, ctrlBank, parm2); break; + case 1: OPLchangeControl(channel, ctrlModulation, parm2); break; + case 7: OPLchangeControl(channel, ctrlVolume, parm2); break; + case 10: OPLchangeControl(channel, ctrlPan, parm2); break; + case 11: OPLchangeControl(channel, ctrlExpression, parm2); break; + case 64: OPLchangeControl(channel, ctrlSustainPedal, parm2); break; + case 67: OPLchangeControl(channel, ctrlSoftPedal, parm2); break; + case 91: OPLchangeControl(channel, ctrlReverb, parm2); break; + case 93: OPLchangeControl(channel, ctrlChorus, parm2); break; + case 120: OPLchangeControl(channel, ctrlSoundsOff, parm2); break; + case 121: OPLchangeControl(channel, ctrlResetCtrls, parm2); break; + case 123: OPLchangeControl(channel, ctrlNotesOff, parm2); break; + case 126: OPLchangeControl(channel, ctrlMono, parm2); break; + case 127: OPLchangeControl(channel, ctrlPoly, parm2); break; + default: + UNIMPL("Unhandled controller: Channel %d, controller %d, value %d\n", channel, parm1, parm2); + break; + } + break; + + case MIDI_PRGMCHANGE: + OPLprogramChange(channel, parm1); + break; + + case MIDI_CHANPRESS: + UNIMPL("Unhandled channel aftertouch: Channel %d, value %d\n", channel, parm1, 0); + break; + + case MIDI_PITCHBEND: + // MUS pitch is 8 bit, but MIDI pitch is 14-bit + OPLpitchWheel(channel, (parm1 | (parm2 >> 1)) >> (14 - 8)); + break; + } +} + +//========================================================================== +// +// OPLMIDIDevice :: FillStream static +// +//========================================================================== + +bool OPLMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +{ + OPLMIDIDevice *device = (OPLMIDIDevice *)userdata; + return device->ServiceStream (buff, len); +} diff --git a/src/oplsynth/muslib.h b/src/oplsynth/muslib.h index 4a6d34e2f..7128ccdc8 100644 --- a/src/oplsynth/muslib.h +++ b/src/oplsynth/muslib.h @@ -199,7 +199,8 @@ struct musicBlock { void OPLreleaseNote(uint channel, uchar note); void OPLpitchWheel(uint channel, int pitch); void OPLchangeControl(uint channel, uchar controller, int value); - void OPLplayMusic(); + void OPLprogramChange(uint channel, int value); + void OPLplayMusic(int vol); void OPLstopMusic(); void OPLchangeVolume(uint volume); diff --git a/src/oplsynth/opl_mus_player.cpp b/src/oplsynth/opl_mus_player.cpp index 3fde91a2d..22ad6cc9a 100644 --- a/src/oplsynth/opl_mus_player.cpp +++ b/src/oplsynth/opl_mus_player.cpp @@ -19,7 +19,7 @@ EXTERN_CVAR (Bool, opl_onechip) -static OPLmusicBlock *BlockForStats; +OPLmusicBlock *BlockForStats; OPLmusicBlock::OPLmusicBlock() { @@ -55,17 +55,18 @@ OPLmusicBlock::~OPLmusicBlock() delete io; } -void OPLmusicBlock::ResetChips () +void OPLmusicBlock::Serialize() { - TwoChips = !opl_onechip; #ifdef _WIN32 EnterCriticalSection (&ChipAccess); #else if (SDL_mutexP (ChipAccess) != 0) return; #endif - io->OPLdeinit (); - io->OPLinit (TwoChips + 1, uint(OPL_SAMPLE_RATE)); +} + +void OPLmusicBlock::Unserialize() +{ #ifdef _WIN32 LeaveCriticalSection (&ChipAccess); #else @@ -73,10 +74,19 @@ void OPLmusicBlock::ResetChips () #endif } +void OPLmusicBlock::ResetChips () +{ + TwoChips = !opl_onechip; + Serialize(); + io->OPLdeinit (); + io->OPLinit (TwoChips + 1, uint(OPL_SAMPLE_RATE)); + Unserialize(); +} + void OPLmusicBlock::Restart() { OPLstopMusic (); - OPLplayMusic (); + OPLplayMusic (127); MLtime = 0; playingcount = 0; } @@ -229,15 +239,12 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) samples1 = samples; memset(buff, 0, numbytes); -#ifdef _WIN32 - EnterCriticalSection (&ChipAccess); -#else - if (SDL_mutexP (ChipAccess) != 0) - return true; -#endif + Serialize(); while (numsamples > 0) { - int samplesleft = MIN (numsamples, int(NextTickIn)); + double ticky = NextTickIn; + int tick_in = int(NextTickIn); + int samplesleft = MIN(numsamples, tick_in); if (samplesleft > 0) { @@ -246,6 +253,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) { YM3812UpdateOne (1, samples1, samplesleft); } + assert(NextTickIn == ticky); NextTickIn -= samplesleft; assert (NextTickIn >= 0); numsamples -= samplesleft; @@ -254,7 +262,8 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) if (NextTickIn < 1) { - int next = PlayTick (); + int next = PlayTick(); + assert(next >= 0); if (next == 0) { // end of song if (!Looping || prevEnded) @@ -286,11 +295,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) } } } -#ifdef _WIN32 - LeaveCriticalSection (&ChipAccess); -#else - SDL_mutexV (ChipAccess); -#endif + Unserialize(); return res; } @@ -447,7 +452,7 @@ OPLmusicWriter::OPLmusicWriter (const char *songname, const char *filename) io = new DiskWriterIO (); if (((DiskWriterIO *)io)->OPLinit (filename) == 0) { - OPLplayMusic (); + OPLplayMusic (127); score = scoredata + ((MUSheader *)scoredata)->scoreStart; Go (); } diff --git a/src/oplsynth/opl_mus_player.h b/src/oplsynth/opl_mus_player.h index 30cc6f1ba..c82b6e952 100644 --- a/src/oplsynth/opl_mus_player.h +++ b/src/oplsynth/opl_mus_player.h @@ -25,6 +25,9 @@ public: protected: virtual int PlayTick() = 0; + void Serialize(); + void Unserialize(); + double NextTickIn; double SamplesPerTick; bool TwoChips; diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index ef597fa74..4fcdc57f0 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -305,6 +305,7 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le - OPL: - if explicitly selected by $mididevice - when opl_enable is true and no midi device is set for the song + - when snd_mididevice is -3 and no midi device is set for the song Timidity: - if explicitly selected by $mididevice @@ -320,7 +321,7 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le - when snd_mididevice is >= 0 and no midi device is set for the song - as fallback when both OPL and Timidity failed and snd_mididevice is >= 0 */ - if ((opl_enable && device == MDEV_DEFAULT) || device == MDEV_OPL) + if (((opl_enable || snd_mididevice == -3) && device == MDEV_DEFAULT) || device == MDEV_OPL) { info = new OPLMUSSong (file, musiccache, len); } @@ -379,10 +380,13 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le if (id == MAKE_ID('M','T','h','d')) { // This is a midi file - // MIDI can't be played with OPL so use default. - if (device == MDEV_OPL) device = MDEV_DEFAULT; /* MIDI are played as: + OPL: + - if explicitly selected by $mididevice + - when opl_enable is true and no midi device is set for the song + - when snd_mididevice is -3 and no midi device is set for the song + Timidity: - if explicitly selected by $mididevice - when snd_mididevice is -2 and no midi device is set for the song @@ -397,21 +401,24 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le - when snd_mididevice is >= 0 and no midi device is set for the song - as fallback when Timidity failed and snd_mididevice is >= 0 */ - - if ((device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT)) && GSnd != NULL) + if ((device == MDEV_OPL || (snd_mididevice == -3 && device == MDEV_DEFAULT)) && GSnd != NULL) + { + info = new MIDISong2 (file, musiccache, len, true); + } + else if ((device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT)) && GSnd != NULL) { info = new TimiditySong (file, musiccache, len); - if (!info->IsValid()) - { - delete info; - info = NULL; - device = MDEV_DEFAULT; - } + } + if (info != NULL && !info->IsValid()) + { + delete info; + info = NULL; + device = MDEV_DEFAULT; } #ifdef _WIN32 if (info == NULL && device != MDEV_FMOD && (snd_mididevice >= 0 || device == MDEV_MMAPI)) { - info = new MIDISong2 (file, musiccache, len); + info = new MIDISong2 (file, musiccache, len, false); } #endif // _WIN32 } diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 883cc8573..a26992747 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -66,8 +66,28 @@ struct MIDIHDR BYTE *lpData; DWORD dwBufferLength; DWORD dwBytesRecorded; - MIDIHDR *Next; + MIDIHDR *lpNext; }; + +enum +{ + MOD_MIDIPORT = 1, + MOD_SYNTH, + MOD_SQSYNTH, + MOD_FMSYNTH, + MOD_MAPPER, + MOD_WAVETABLE, + MOD_SWSYNTH +}; + +#define MEVT_TEMPO ((BYTE)1) +#define MEVT_NOP ((BYTE)2) +#define MEVT_LONGMSG ((BYTE)128) + +#define MEVT_EVENTTYPE(x) ((BYTE)((x) >> 24)) +#define MEVT_EVENTPARM(x) ((x) & 0xffffff) + +#define MOM_DONE 969 #endif class MIDIDevice @@ -87,6 +107,8 @@ public: virtual void Stop() = 0; virtual int PrepareHeader(MIDIHDR *data) = 0; virtual int UnprepareHeader(MIDIHDR *data) = 0; + virtual bool FakeVolume() = 0; + virtual bool Pause(bool paused) = 0; }; // WinMM implementation of a MIDI output device ----------------------------- @@ -107,6 +129,8 @@ public: void Stop(); int PrepareHeader(MIDIHDR *data); int UnprepareHeader(MIDIHDR *data); + bool FakeVolume(); + bool Pause(bool paused); protected: static void CALLBACK CallbackFunc(HMIDIOUT, UINT, DWORD_PTR, DWORD, DWORD); @@ -138,10 +162,25 @@ public: void Stop(); int PrepareHeader(MIDIHDR *data); int UnprepareHeader(MIDIHDR *data); + bool FakeVolume(); + bool Pause(bool paused); protected: + static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata); + void (*Callback)(unsigned int, void *, DWORD, DWORD); void *CallbackData; + + void CalcTickRate(); + void HandleEvent(int status, int parm1, int parm2); + int PlayTick(); + + SoundStream *Stream; + double Tempo; + double Division; + MIDIHDR *Events; + bool Started; + DWORD Position; }; // Base class for streaming MUS and MIDI files ------------------------------ @@ -149,7 +188,7 @@ protected: class MIDIStreamer : public MusInfo { public: - MIDIStreamer(); + MIDIStreamer(bool opl); ~MIDIStreamer(); void MusicVolumeChanged(); @@ -208,6 +247,7 @@ protected: int InitialTempo; BYTE ChannelVolumes[16]; DWORD Volume; + bool UseOPLDevice; }; // MUS file played with a MIDI stream --------------------------------------- @@ -235,7 +275,7 @@ protected: class MIDISong2 : public MIDIStreamer { public: - MIDISong2 (FILE *file, char *musiccache, int length); + MIDISong2 (FILE *file, char *musiccache, int length, bool opl); ~MIDISong2 (); protected: diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp index f168e53cf..e85919a79 100644 --- a/src/sound/music_midi_base.cpp +++ b/src/sound/music_midi_base.cpp @@ -21,7 +21,7 @@ CUSTOM_CVAR (Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) if (!nummididevicesset) return; - if ((self >= (signed)nummididevices) || (self < -2)) + if ((self >= (signed)nummididevices) || (self < -3)) { Printf ("ID out of range. Using default device.\n"); self = 0; @@ -76,43 +76,38 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) { if (*outValues == NULL) { - int count = 1 + nummididevices + (nummididevices > 0); + int count = 3 + nummididevices; value_t *values; + UINT id; + int p; *outValues = values = new value_t[count]; - values[0].name = "TiMidity++"; - values[0].value = -2.0; - values[1].name = "FMOD"; - values[1].value = -1.0; - if (nummididevices > 0) + values[0].name = "OPL Synth Emulation"; + values[0].value = -3.0; + values[1].name = "TiMidity++"; + values[1].value = -2.0; + values[2].name = "FMOD"; + values[2].value = -1.0; + for (id = 0, p = 3; id < nummididevices; ++id) { - UINT id; - int p; + MIDIOUTCAPS caps; + MMRESULT res; - for (id = 0, p = 2; id < nummididevices; ++id) + res = midiOutGetDevCaps (id, &caps, sizeof(caps)); + if (res == MMSYSERR_NOERROR) { - MIDIOUTCAPS caps; - MMRESULT res; + size_t len = strlen (caps.szPname) + 1; + char *name = new char[len]; - res = midiOutGetDevCaps (id, &caps, sizeof(caps)); - if (res == MMSYSERR_NOERROR) - { - size_t len = strlen (caps.szPname) + 1; - char *name = new char[len]; - - memcpy (name, caps.szPname, len); - values[p].name = name; - values[p].value = (float)id; - ++p; - } + memcpy (name, caps.szPname, len); + values[p].name = name; + values[p].value = (float)id; + ++p; } - *numValues = (float)p; - } - else - { - *numValues = 2.f; } + assert(p == count); + *numValues = float(count); } } @@ -185,8 +180,8 @@ CCMD (snd_listmididevices) CUSTOM_CVAR(Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { - if (self < -2) - self = -2; + if (self < -3) + self = -3; else if (self > -1) self = -1; } @@ -195,16 +190,17 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) { if (*outValues == NULL) { - int count = 1 + nummididevices + (nummididevices > 0); value_t *values; - *outValues = values = new value_t[count]; + *outValues = values = new value_t[3]; - values[0].name = "TiMidity++"; - values[0].value = -2.0; - values[1].name = "FMOD"; - values[1].value = -1.0; - *numValues = 2.f; + values[0].name = "OPL Synth Emulation"; + values[0].value = -3.0; + values[1].name = "TiMidity++"; + values[1].value = -2.0; + values[2].name = "FMOD"; + values[2].value = -1.0; + *numValues = 3.f; } } diff --git a/src/sound/music_midi_midiout.cpp b/src/sound/music_midi_midiout.cpp index 1993a2c4b..333c15740 100644 --- a/src/sound/music_midi_midiout.cpp +++ b/src/sound/music_midi_midiout.cpp @@ -87,8 +87,6 @@ struct MIDISong2::TrackInfo // EXTERNAL DATA DECLARATIONS ---------------------------------------------- -extern UINT mididevice; - // PRIVATE DATA DEFINITIONS ------------------------------------------------ static BYTE EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 }; @@ -106,8 +104,8 @@ static BYTE CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // //========================================================================== -MIDISong2::MIDISong2 (FILE *file, char *musiccache, int len) -: MusHeader(0), Tracks(0) +MIDISong2::MIDISong2 (FILE *file, char *musiccache, int len, bool opl) +: MIDIStreamer(opl), MusHeader(0), Tracks(0) { int p; int i; diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index 087ce2c8f..fa959c2ab 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -69,9 +69,9 @@ extern UINT mididevice; // //========================================================================== -MIDIStreamer::MIDIStreamer() +MIDIStreamer::MIDIStreamer(bool opl) : MIDI(0), PlayerThread(0), ExitEvent(0), BufferDoneEvent(0), - Division(0), InitialTempo(500000) + Division(0), InitialTempo(500000), UseOPLDevice(opl) { BufferDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (BufferDoneEvent == NULL) @@ -164,7 +164,14 @@ void MIDIStreamer::Play (bool looping) InitialPlayback = true; assert(MIDI == NULL); - MIDI = new WinMIDIDevice(mididevice); + if (UseOPLDevice) + { + MIDI = new OPLMIDIDevice; + } + else + { + MIDI = new WinMIDIDevice(mididevice); + } if (0 != MIDI->Open(Callback, this)) { @@ -251,7 +258,10 @@ void MIDIStreamer::Pause () if (m_Status == STATE_Playing) { m_Status = STATE_Paused; - OutputVolume(0); + if (!MIDI->Pause(true)) + { + OutputVolume(0); + } } } @@ -268,7 +278,10 @@ void MIDIStreamer::Resume () { if (m_Status == STATE_Paused) { - OutputVolume(Volume); + if (!MIDI->Pause(false)) + { + OutputVolume(Volume); + } m_Status = STATE_Playing; } } @@ -328,12 +341,18 @@ bool MIDIStreamer::IsPlaying () void MIDIStreamer::MusicVolumeChanged () { - float realvolume = clamp<float>(snd_musicvolume * relative_volume, 0.f, 1.f); - DWORD onechanvol = clamp<DWORD>((DWORD)(realvolume * 65535.f), 0, 65535); - Volume = onechanvol; + if (MIDI->FakeVolume()) + { + float realvolume = clamp<float>(snd_musicvolume * relative_volume, 0.f, 1.f); + Volume = clamp<DWORD>((DWORD)(realvolume * 65535.f), 0, 65535); + } + else + { + Volume = 0xFFFF; + } if (m_Status == STATE_Playing) { - OutputVolume(onechanvol); + OutputVolume(Volume); } } @@ -347,8 +366,11 @@ void MIDIStreamer::MusicVolumeChanged () void MIDIStreamer::OutputVolume (DWORD volume) { - NewVolume = volume; - VolumeChanged = true; + if (MIDI->FakeVolume()) + { + NewVolume = volume; + VolumeChanged = true; + } } //========================================================================== diff --git a/src/sound/music_mus_midiout.cpp b/src/sound/music_mus_midiout.cpp index 3fb8b9d9c..d2ee0dd81 100644 --- a/src/sound/music_mus_midiout.cpp +++ b/src/sound/music_mus_midiout.cpp @@ -42,8 +42,6 @@ // MACROS ------------------------------------------------------------------ -#define MAX_TIME (140/20) // Each stream buffer lasts only 1/20 of a second - // TYPES ------------------------------------------------------------------- // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- @@ -91,7 +89,7 @@ static const BYTE CtrlTranslate[15] = //========================================================================== MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len) -: MusHeader(0), MusBuffer(0) +: MIDIStreamer(false), MusHeader(0), MusBuffer(0) { if (ExitEvent == NULL) { diff --git a/src/sound/music_win_mididevice.cpp b/src/sound/music_win_mididevice.cpp index fb4f7378f..f164f6a2e 100644 --- a/src/sound/music_win_mididevice.cpp +++ b/src/sound/music_win_mididevice.cpp @@ -203,6 +203,20 @@ void WinMIDIDevice::Stop() } } +//========================================================================== +// +// WinMIDIDevice :: Pause +// +// Some docs claim pause is unreliable and can cause the stream to stop +// functioning entirely. Truth or fiction? +// +//========================================================================== + +bool WinMIDIDevice::Pause(bool paused) +{ + return false; +} + //========================================================================== // // WinMIDIDevice :: StreamOut @@ -236,6 +250,20 @@ int WinMIDIDevice::UnprepareHeader(MIDIHDR *header) return midiOutUnprepareHeader((HMIDIOUT)MidiOut, header, sizeof(MIDIHDR)); } +//========================================================================== +// +// WinMIDIDevice :: FakeVolume +// +// Because there are too many MIDI devices out there that don't support +// global volume changes, fake the volume for all of them. +// +//========================================================================== + +bool WinMIDIDevice::FakeVolume() +{ + return true; +} + //========================================================================== // // WinMIDIDevice :: CallbackFunc static diff --git a/zdoom.vcproj b/zdoom.vcproj index b470fc18e..0337faee4 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -2840,6 +2840,10 @@ RelativePath="src\sound\music_mus_opl.cpp" > </File> + <File + RelativePath=".\src\oplsynth\music_opl_mididevice.cpp" + > + </File> <File RelativePath="src\sound\music_spc.cpp" >