diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 77e325a6c..804d301d3 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,6 +1,12 @@ +March 6, 2008 +- The full master volume SysEx is now always sent to the MIDI device, even if + it seems to have a working volume control. +- Renamed music_midi_stream.cpp to music_midi_base.cpp. +- Moved the WinMM MIDI code into a new container class. + March 4, 2008 - Moved the identical code between the MUS and MIDI streamers into a new base - class so they all the low-level details of MIDI streaming are kept in + class so that all the low-level details of MIDI streaming are kept in one place. - Converted the SMF MIDI playback to use the same MIDI streams as MUS playback. @@ -9,7 +15,7 @@ March 4, 2008 manner. - Fixed: The MEVT_* values are not defined shifted into their spot for a MIDIEVENT, so I need to do it myself. -- Fixed: Pausing a MUS and the changing snd_midivolume caused the paused +- Fixed: Pausing a MUS and then changing snd_midivolume caused the paused notes to become audible. March 3, 2008 diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 17f506e92..928a6c265 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -55,8 +55,61 @@ public: #ifdef _WIN32 +// A device that provides a WinMM-like MIDI streaming interface ------------- + +class MIDIDevice +{ +public: + MIDIDevice(); + virtual ~MIDIDevice(); + + virtual int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) = 0; + virtual void Close() = 0; + virtual bool IsOpen() const = 0; + virtual int GetTechnology() const = 0; + virtual int SetTempo(int tempo) = 0; + virtual int SetTimeDiv(int timediv) = 0; + virtual int StreamOut(MIDIHDR *data) = 0; + virtual int Resume() = 0; + virtual void Stop() = 0; + virtual int PrepareHeader(MIDIHDR *data) = 0; + virtual int UnprepareHeader(MIDIHDR *data) = 0; +}; + +// WinMM implementation of a MIDI output device ----------------------------- + +class WinMIDIDevice : public MIDIDevice +{ +public: + WinMIDIDevice(int dev_id); + ~WinMIDIDevice(); + int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); + void Close(); + bool IsOpen() const; + int GetTechnology() const; + int SetTempo(int tempo); + int SetTimeDiv(int timediv); + int StreamOut(MIDIHDR *data); + int Resume(); + void Stop(); + int PrepareHeader(MIDIHDR *data); + int UnprepareHeader(MIDIHDR *data); + +protected: + static void CALLBACK CallbackFunc(HMIDIOUT, UINT, DWORD_PTR, DWORD, DWORD); + + HMIDISTRM MidiOut; + UINT DeviceID; + DWORD SavedVolume; + bool VolumeWorks; + + void (*Callback)(unsigned int, void *, DWORD, DWORD); + void *CallbackData; +}; + // Base class for streaming MUS and MIDI files ------------------------------ + class MIDIStreamer : public MusInfo { public: @@ -75,7 +128,7 @@ public: protected: static DWORD WINAPI PlayerProc (LPVOID lpParameter); - static void CALLBACK Callback(HMIDIOUT handle, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2); + static void Callback(UINT uMsg, void *userdata, DWORD dwParam1, DWORD dwParam2); DWORD PlayerLoop(); void OutputVolume (DWORD volume); int FillBuffer(int buffer_num, int max_events, DWORD max_time); @@ -83,7 +136,7 @@ protected: int VolumeControllerChange(int channel, int volume); // Virtuals for subclasses to override - virtual void CheckCaps(DWORD dev_id); + virtual void CheckCaps(); virtual void DoInitialSetup() = 0; virtual void DoRestart() = 0; virtual bool CheckDone() = 0; @@ -101,12 +154,11 @@ protected: SONG_ERROR }; - HMIDISTRM MidiOut; + MIDIDevice *MIDI; HANDLE PlayerThread; HANDLE ExitEvent; HANDLE BufferDoneEvent; - DWORD SavedVolume; - bool VolumeWorks; + DWORD Events[2][MAX_EVENTS*3]; MIDIHDR Buffer[2]; int BufferNum; @@ -150,7 +202,7 @@ public: ~MIDISong2 (); protected: - void CheckCaps(DWORD dev_id); + void CheckCaps(); void DoInitialSetup(); void DoRestart(); bool CheckDone(); diff --git a/src/sound/music_midi_stream.cpp b/src/sound/music_midi_base.cpp similarity index 100% rename from src/sound/music_midi_stream.cpp rename to src/sound/music_midi_base.cpp diff --git a/src/sound/music_midi_midiout.cpp b/src/sound/music_midi_midiout.cpp index 9f73685fe..b089b7707 100644 --- a/src/sound/music_midi_midiout.cpp +++ b/src/sound/music_midi_midiout.cpp @@ -217,24 +217,22 @@ MIDISong2::~MIDISong2 () // MIDISong2 :: CheckCaps // // Find out if this is an FM synth or not for EMIDI's benefit. +// (Do any released EMIDIs use track designations?) // //========================================================================== -void MIDISong2::CheckCaps(DWORD dev_id) +void MIDISong2::CheckCaps() { - MIDIOUTCAPS caps; + int tech = MIDI->GetTechnology(); DesignationMask = 0xFF0F; - if (MMSYSERR_NOERROR == midiOutGetDevCaps (dev_id, &caps, sizeof(caps))) + if (tech == MOD_FMSYNTH) { - if (caps.wTechnology == MOD_FMSYNTH) - { - DesignationMask = 0x00F0; - } - else if (caps.wTechnology == MOD_MIDIPORT) - { - DesignationMask = 0x0001; - } + DesignationMask = 0x00F0; + } + else if (tech == MOD_MIDIPORT) + { + DesignationMask = 0x0001; } } @@ -438,7 +436,7 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) } } track->Designated = true; - event = 0xFF; + event = MIDI_META; break; case 111: // EMIDI Track Exclusion @@ -446,7 +444,7 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) { track->Designation &= ~(1 << data2); } - event = 0xFF; + event = MIDI_META; break; case 112: // EMIDI Program Change @@ -729,9 +727,7 @@ MIDISong2::TrackInfo *MIDISong2::FindNextDue () void MIDISong2::SetTempo(int new_tempo) { - MIDIPROPTEMPO tempo = { sizeof(MIDIPROPTEMPO), new_tempo }; - - if (MMSYSERR_NOERROR == midiStreamProperty(MidiOut, (LPBYTE)&tempo, MIDIPROP_SET | MIDIPROP_TEMPO)) + if (MMSYSERR_NOERROR == MIDI->SetTempo(new_tempo)) { Tempo = new_tempo; } diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index 171a100f6..264bde4a4 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -71,7 +71,7 @@ extern UINT mididevice; //========================================================================== MIDIStreamer::MIDIStreamer() -: MidiOut(0), PlayerThread(0), ExitEvent(0), BufferDoneEvent(0), +: MIDI(0), PlayerThread(0), ExitEvent(0), BufferDoneEvent(0), Division(0), InitialTempo(500000) { BufferDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL); @@ -104,6 +104,10 @@ MIDIStreamer::~MIDIStreamer() { CloseHandle(BufferDoneEvent); } + if (MIDI != NULL) + { + delete MIDI; + } } //========================================================================== @@ -139,7 +143,7 @@ bool MIDIStreamer::IsValid() const // //========================================================================== -void MIDIStreamer::CheckCaps(DWORD dev_id) +void MIDIStreamer::CheckCaps() { } @@ -152,7 +156,6 @@ void MIDIStreamer::CheckCaps(DWORD dev_id) void MIDIStreamer::Play (bool looping) { DWORD tid; - UINT dev_id; m_Status = STATE_Stopped; m_Looping = looping; @@ -160,41 +163,27 @@ void MIDIStreamer::Play (bool looping) VolumeChanged = false; Restarting = true; InitialPlayback = true; - dev_id = MAX(mididevice, 0u); - if (MMSYSERR_NOERROR != midiStreamOpen(&MidiOut, &dev_id, 1, (DWORD_PTR)Callback, (DWORD_PTR)this, CALLBACK_FUNCTION)) + assert(MIDI == NULL); + MIDI = new WinMIDIDevice(mididevice); + + if (0 != MIDI->Open(Callback, this)) { Printf(PRINT_BOLD, "Could not open MIDI out device\n"); return; } - CheckCaps(dev_id); + CheckCaps(); // Set time division and tempo. - MIDIPROPTIMEDIV timediv = { sizeof(MIDIPROPTIMEDIV), Division }; - MIDIPROPTEMPO tempo = { sizeof(MIDIPROPTEMPO), Tempo = InitialTempo }; - - if (MMSYSERR_NOERROR != midiStreamProperty(MidiOut, (LPBYTE)&timediv, MIDIPROP_SET | MIDIPROP_TIMEDIV) || - MMSYSERR_NOERROR != midiStreamProperty(MidiOut, (LPBYTE)&tempo, MIDIPROP_SET | MIDIPROP_TEMPO)) + if (0 != MIDI->SetTimeDiv(Division) || + 0 != MIDI->SetTempo(Tempo = InitialTempo)) { Printf(PRINT_BOLD, "Setting MIDI stream speed failed\n"); - midiStreamClose(MidiOut); - MidiOut = NULL; + MIDI->Close(); return; } - // Try two different methods for setting the stream to full volume. - // Unfortunately, this isn't as reliable as it once was, which is a pity. - // The real volume selection is done by setting the volume controller for - // each channel. Because every General MIDI-compliant device must support - // this controller, it is the most reliable means of setting the volume. - - VolumeWorks = (MMSYSERR_NOERROR == midiOutGetVolume((HMIDIOUT)MidiOut, &SavedVolume)); - if (VolumeWorks) - { - VolumeWorks &= (MMSYSERR_NOERROR == midiOutSetVolume((HMIDIOUT)MidiOut, 0xffffffff)); - } - snd_midivolume.Callback(); // set volume to current music's properties OutputVolume (midivolume & 0xffff); @@ -208,7 +197,7 @@ void MIDIStreamer::Play (bool looping) int res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME); if (res == SONG_MORE) { - if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR))) + if (0 != MIDI->StreamOut(&Buffer[BufferNum])) { Printf ("Initial midiStreamOut failed\n"); Stop(); @@ -223,7 +212,7 @@ void MIDIStreamer::Play (bool looping) Restarting = true; if (SONG_MORE == FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME)) { - if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR))) + if (0 != MIDI->StreamOut(&Buffer[BufferNum])) { Printf ("Initial midiStreamOut failed\n"); Stop(); @@ -250,9 +239,9 @@ void MIDIStreamer::Play (bool looping) } while (BufferNum != 0); - if (MMSYSERR_NOERROR != midiStreamRestart(MidiOut)) + if (0 != MIDI->Resume()) { - Printf ("midiStreamRestart failed\n"); + Printf ("Starting MIDI playback failed\n"); Stop(); } else @@ -260,7 +249,7 @@ void MIDIStreamer::Play (bool looping) PlayerThread = CreateThread(NULL, 0, PlayerProc, this, 0, &tid); if (PlayerThread == NULL) { - Printf ("MUS CreateThread failed\n"); + Printf ("Creating MIDI thread failed\n"); Stop(); } else @@ -324,18 +313,17 @@ void MIDIStreamer::Stop () CloseHandle(PlayerThread); PlayerThread = NULL; } - if (MidiOut) + if (MIDI != NULL && MIDI->IsOpen()) { - midiStreamStop(MidiOut); - midiOutReset((HMIDIOUT)MidiOut); - if (VolumeWorks) - { - midiOutSetVolume((HMIDIOUT)MidiOut, SavedVolume); - } - midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[0], sizeof(MIDIHDR)); - midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[1], sizeof(MIDIHDR)); - midiStreamClose(MidiOut); - MidiOut = NULL; + MIDI->Stop(); + MIDI->UnprepareHeader(&Buffer[0]); + MIDI->UnprepareHeader(&Buffer[1]); + MIDI->Close(); + } + if (MIDI != NULL) + { + delete MIDI; + MIDI = NULL; } m_Status = STATE_Stopped; } @@ -402,9 +390,9 @@ int MIDIStreamer::VolumeControllerChange(int channel, int volume) // //========================================================================== -void CALLBACK MIDIStreamer::Callback(HMIDIOUT hOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2) +void MIDIStreamer::Callback(UINT uMsg, void *userdata, DWORD dwParam1, DWORD dwParam2) { - MIDIStreamer *self = (MIDIStreamer *)dwInstance; + MIDIStreamer *self = (MIDIStreamer *)userdata; if (self->EndQueued > 1) { @@ -502,7 +490,7 @@ bool MIDIStreamer::ServiceEvent() { return false; } - if (MMSYSERR_NOERROR != midiOutUnprepareHeader((HMIDIOUT)MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR))) + if (0 != MIDI->UnprepareHeader(&Buffer[BufferNum])) { return true; } @@ -510,7 +498,7 @@ fill: switch (FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME)) { case SONG_MORE: - if (MMSYSERR_NOERROR != midiStreamOut(MidiOut, &Buffer[BufferNum], sizeof(MIDIHDR))) + if (0 != MIDI->StreamOut(&Buffer[BufferNum])) { return true; } @@ -569,16 +557,13 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time) if (InitialPlayback) { InitialPlayback = false; - if (!VolumeWorks) - { - // Send the full master volume SysEx message. - events[0] = 0; // dwDeltaTime - events[1] = 0; // dwStreamID - events[2] = (MEVT_LONGMSG << 24) | 8; // dwEvent - events[3] = 0x047f7ff0; // dwParms[0] - events[4] = 0xf77f7f01; // dwParms[1] - events += 5; - } + // Send the full master volume SysEx message. + events[0] = 0; // dwDeltaTime + events[1] = 0; // dwStreamID + events[2] = (MEVT_LONGMSG << 24) | 8; // dwEvent + events[3] = 0x047f7ff0; // dwParms[0] + events[4] = 0xf77f7f01; // dwParms[1] + events += 5; DoInitialSetup(); } @@ -627,10 +612,25 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time) Buffer[buffer_num].lpData = (LPSTR)Events[buffer_num]; Buffer[buffer_num].dwBufferLength = DWORD((LPSTR)events - Buffer[buffer_num].lpData); Buffer[buffer_num].dwBytesRecorded = Buffer[buffer_num].dwBufferLength; - if (MMSYSERR_NOERROR != midiOutPrepareHeader((HMIDIOUT)MidiOut, &Buffer[buffer_num], sizeof(MIDIHDR))) + if (0 != MIDI->PrepareHeader(&Buffer[buffer_num])) { return SONG_ERROR; } return SONG_MORE; } + +//========================================================================== +// +// MIDIDevice constructor and desctructor stubs. +// +//========================================================================== + +MIDIDevice::MIDIDevice() +{ +} + +MIDIDevice::~MIDIDevice() +{ +} + #endif diff --git a/src/sound/music_win_mididevice.cpp b/src/sound/music_win_mididevice.cpp new file mode 100644 index 000000000..69f3cca97 --- /dev/null +++ b/src/sound/music_win_mididevice.cpp @@ -0,0 +1,254 @@ +/* +** music_mididevice.cpp +** Provides a WinMM implementation of a generic 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. +**--------------------------------------------------------------------------- +** +*/ + +#ifdef _WIN32 + +// HEADER FILES ------------------------------------------------------------ + +#include "i_musicinterns.h" +#include "templates.h" +#include "doomdef.h" +#include "m_swap.h" + +// MACROS ------------------------------------------------------------------ + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// WinMIDIDevice Contructor +// +//========================================================================== + +WinMIDIDevice::WinMIDIDevice(int dev_id) +{ + DeviceID = MAX(dev_id, 0); + MidiOut = 0; +} + +//========================================================================== +// +// WinMIDIDevice Destructor +// +//========================================================================== + +WinMIDIDevice::~WinMIDIDevice() +{ + Close(); +} + +//========================================================================== +// +// WinMIDIDevice :: Open +// +//========================================================================== + +int WinMIDIDevice::Open(void (*callback)(UINT, void *, DWORD, DWORD), void *userdata) +{ + MMRESULT err; + + Callback = callback; + CallbackData = userdata; + if (MidiOut == NULL) + { + err = midiStreamOpen(&MidiOut, &DeviceID, 1, (DWORD_PTR)CallbackFunc, (DWORD_PTR)this, CALLBACK_FUNCTION); + + if (err == MMSYSERR_NOERROR) + { + // Set master volume to full, if the device allows it on this interface. + VolumeWorks = (MMSYSERR_NOERROR == midiOutGetVolume((HMIDIOUT)MidiOut, &SavedVolume)); + if (VolumeWorks) + { + VolumeWorks &= (MMSYSERR_NOERROR == midiOutSetVolume((HMIDIOUT)MidiOut, 0xffffffff)); + } + } + } + return 0; +} + +//========================================================================== +// +// WinMIDIDevice :: Close +// +//========================================================================== + +void WinMIDIDevice::Close() +{ + if (MidiOut != NULL) + { + midiStreamClose(MidiOut); + MidiOut = NULL; + } +} + +//========================================================================== +// +// WinMIDIDevice :: IsOpen +// +//========================================================================== + +bool WinMIDIDevice::IsOpen() const +{ + return MidiOut != NULL; +} + +//========================================================================== +// +// WinMIDIDevice :: GetTechnology +// +//========================================================================== + +int WinMIDIDevice::GetTechnology() const +{ + MIDIOUTCAPS caps; + + if (MMSYSERR_NOERROR == midiOutGetDevCaps(DeviceID, &caps, sizeof(caps))) + { + return caps.wTechnology; + } + return -1; +} + +//========================================================================== +// +// WinMIDIDevice :: SetTempo +// +//========================================================================== + +int WinMIDIDevice::SetTempo(int tempo) +{ + MIDIPROPTEMPO data = { sizeof(MIDIPROPTEMPO), tempo }; + return midiStreamProperty(MidiOut, (LPBYTE)&data, MIDIPROP_SET | MIDIPROP_TEMPO); +} + +//========================================================================== +// +// WinMIDIDevice :: SetTimeDiv +// +//========================================================================== + +int WinMIDIDevice::SetTimeDiv(int timediv) +{ + MIDIPROPTIMEDIV data = { sizeof(MIDIPROPTIMEDIV), timediv }; + return midiStreamProperty(MidiOut, (LPBYTE)&data, MIDIPROP_SET | MIDIPROP_TIMEDIV); +} + +//========================================================================== +// +// WinMIDIDevice :: Resume +// +//========================================================================== + +int WinMIDIDevice::Resume() +{ + return midiStreamRestart(MidiOut); +} + +//========================================================================== +// +// WinMIDIDevice :: Stop +// +//========================================================================== + +void WinMIDIDevice::Stop() +{ + midiStreamStop(MidiOut); + midiOutReset((HMIDIOUT)MidiOut); + if (VolumeWorks) + { + midiOutSetVolume((HMIDIOUT)MidiOut, SavedVolume); + } +} + +//========================================================================== +// +// WinMIDIDevice :: StreamOut +// +//========================================================================== + +int WinMIDIDevice::StreamOut(MIDIHDR *header) +{ + return midiStreamOut(MidiOut, header, sizeof(MIDIHDR)); +} + +//========================================================================== +// +// WinMIDIDevice :: PrepareHeader +// +//========================================================================== + +int WinMIDIDevice::PrepareHeader(MIDIHDR *header) +{ + return midiOutPrepareHeader((HMIDIOUT)MidiOut, header, sizeof(MIDIHDR)); +} + +//========================================================================== +// +// WinMIDIDevice :: UnprepareHeader +// +//========================================================================== + +int WinMIDIDevice::UnprepareHeader(MIDIHDR *header) +{ + return midiOutUnprepareHeader((HMIDIOUT)MidiOut, header, sizeof(MIDIHDR)); +} + +//========================================================================== +// +// WinMIDIDevice :: CallbackFunc static +// +//========================================================================== + +void CALLBACK WinMIDIDevice::CallbackFunc(HMIDIOUT hOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + WinMIDIDevice *self = (WinMIDIDevice *)dwInstance; + if (self->Callback != NULL) + { + self->Callback(uMsg, self->CallbackData, dwParam1, dwParam2); + } +} + +#endif diff --git a/zdoom.vcproj b/zdoom.vcproj index 0e0029f73..c63377ff6 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -9255,11 +9255,11 @@ > + +