- 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.


SVN r864 (trunk)
This commit is contained in:
Randy Heit 2008-03-29 05:01:38 +00:00
parent 8f49ea7f48
commit cd70087ed5
15 changed files with 707 additions and 106 deletions

View file

@ -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.

View file

@ -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,

View file

@ -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;

View file

@ -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);
}

View file

@ -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);

View file

@ -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 ();
}

View file

@ -25,6 +25,9 @@ public:
protected:
virtual int PlayTick() = 0;
void Serialize();
void Unserialize();
double NextTickIn;
double SamplesPerTick;
bool TwoChips;

View file

@ -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
}

View file

@ -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:

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}
}
//==========================================================================

View file

@ -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)
{

View file

@ -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

View file

@ -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"
>