- Did some restructuring of the OPL code in preparation for turning it

into a general MIDI player.
- Fixed: Passing false for a stream callback did not stop the stream.
- Removed opl_frequency, since the only time the emulation sounds good is
  when it plays at the exact frequency of a real chip.
- Music no longer plays at all when snd_musicvolume is 0.
- Bumped up snd_sfxvolume and snd_musicvolume default values.

SVN r862 (trunk)
This commit is contained in:
Randy Heit 2008-03-28 03:19:18 +00:00
parent 776d89428d
commit 938ae1767b
11 changed files with 243 additions and 163 deletions

View File

@ -1,4 +1,11 @@
March 27, 2008 March 27, 2008
- Did some restructuring of the OPL code in preparation for turning it
into a general MIDI player.
- Fixed: Passing false for a stream callback did not stop the stream.
- Removed opl_frequency, since the only time the emulation sounds good is
when it plays at the exact frequency of a real chip.
- Music no longer plays at all when snd_musicvolume is 0.
- Bumped up snd_sfxvolume and snd_musicvolume default values.
- Changed D3DFB to explicitly request double buffering instead of assuming - Changed D3DFB to explicitly request double buffering instead of assuming
that the drivers will treat a BackBufferCount of 0 as a request for that the drivers will treat a BackBufferCount of 0 as a request for
double buffering. double buffering.

View File

@ -1270,26 +1270,13 @@ static menu_t SoundMenu =
*=======================================*/ *=======================================*/
EXTERN_CVAR (Bool, opl_enable) EXTERN_CVAR (Bool, opl_enable)
EXTERN_CVAR (Int, opl_frequency)
EXTERN_CVAR (Bool, opl_onechip) EXTERN_CVAR (Bool, opl_onechip)
static value_t OPLSampleRates[] =
{
{ 4000.f, "4000 Hz" },
{ 6215.f, "6215 Hz" },
{ 12429.f, "12429 Hz" },
{ 24858.f, "24858 Hz" },
{ 49716.f, "49716 Hz" },
};
static menuitem_t AdvSoundItems[] = static menuitem_t AdvSoundItems[] =
{ {
{ whitetext,"OPL Synthesis", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { 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, "Use FM Synth for MUS music",{&opl_enable}, {2.0}, {0.0}, {0.0}, {OnOff} },
{ discrete, "Only emulate one OPL chip", {&opl_onechip}, {2.0}, {0.0}, {0.0}, {OnOff} }, { discrete, "Only emulate one OPL chip", {&opl_onechip}, {2.0}, {0.0}, {0.0}, {OnOff} },
{ discrete, "OPL synth sample rate", {&opl_frequency}, {5.0}, {0.0}, {0.0}, {OPLSampleRates} },
}; };
static menu_t AdvSoundMenu = static menu_t AdvSoundMenu =

View File

@ -15,21 +15,21 @@
#include "v_text.h" #include "v_text.h"
#include "c_dispatch.h" #include "c_dispatch.h"
#define IMF_RATE 700 #define IMF_RATE 700.0
EXTERN_CVAR (Bool, opl_onechip) EXTERN_CVAR (Bool, opl_onechip)
static OPLmusicBlock *BlockForStats; static OPLmusicBlock *BlockForStats;
OPLmusicBlock::OPLmusicBlock (FILE *file, char * musiccache, int len, int rate, int maxSamples) OPLmusicBlock::OPLmusicBlock()
: SampleRate (rate), NextTickIn (0), Looping (false), ScoreLen (len)
{ {
scoredata = NULL; scoredata = NULL;
SampleBuff = NULL; SampleBuff = NULL;
SampleBuffSize = 0; SampleBuffSize = 0;
NextTickIn = 0;
TwoChips = !opl_onechip; TwoChips = !opl_onechip;
io = new OPLio; Looping = false;
io = NULL;
#ifdef _WIN32 #ifdef _WIN32
InitializeCriticalSection (&ChipAccess); InitializeCriticalSection (&ChipAccess);
#else #else
@ -39,96 +39,12 @@ OPLmusicBlock::OPLmusicBlock (FILE *file, char * musiccache, int len, int rate,
return; return;
} }
#endif #endif
io = new OPLio;
scoredata = new BYTE[len];
if (file)
{
if (fread (scoredata, 1, len, file) != (size_t)len)
{
delete[] scoredata;
scoredata = NULL;
return;
}
}
else
{
memcpy(scoredata, &musiccache[0], len);
}
if (io->OPLinit (TwoChips + 1, rate))
{
delete[] scoredata;
scoredata = NULL;
return;
}
// Check for MUS format
if (*(DWORD *)scoredata == MAKE_ID('M','U','S',0x1a))
{
FWadLump data = Wads.OpenLumpName ("GENMIDI");
if (0 != OPLloadBank (data))
{
delete[] scoredata;
scoredata = NULL;
return;
}
BlockForStats = this;
SamplesPerTick = ((rate/14)+5)/10; // round to nearest, not lowest
RawPlayer = NotRaw;
}
// Check for RDosPlay raw OPL format
else if (((DWORD *)scoredata)[0] == MAKE_ID('R','A','W','A') &&
((DWORD *)scoredata)[1] == MAKE_ID('D','A','T','A'))
{
RawPlayer = RDosPlay;
if (*(WORD *)(scoredata + 8) == 0)
{ // A clock speed of 0 is bad
*(WORD *)(scoredata + 8) = 0xFFFF;
}
SamplesPerTick = Scale (rate, LittleShort(*(WORD *)(scoredata + 8)), 1193180);
}
// Check for modified IMF format (includes a header)
else if (((DWORD *)scoredata)[0] == MAKE_ID('A','D','L','I') &&
scoredata[4] == 'B' && scoredata[5] == 1)
{
int songlen;
BYTE *max = scoredata + ScoreLen;
RawPlayer = IMF;
SamplesPerTick = rate / IMF_RATE;
score = scoredata + 6;
// Skip track and game name
for (int i = 2; i != 0; --i)
{
while (score < max && *score++ != '\0') {}
}
if (score < max) score++; // Skip unknown byte
if (score + 8 > max)
{ // Not enough room left for song data
delete[] scoredata;
scoredata = NULL;
return;
}
songlen = LittleLong(*(DWORD *)score);
if (songlen != 0 && (songlen +=4) < ScoreLen - (score - scoredata))
{
ScoreLen = songlen + int(score - scoredata);
}
}
Restart ();
} }
OPLmusicBlock::~OPLmusicBlock () OPLmusicBlock::~OPLmusicBlock()
{ {
BlockForStats = NULL; BlockForStats = NULL;
if (scoredata != NULL)
{
io->OPLdeinit ();
delete[] scoredata;
scoredata = NULL;
}
if (SampleBuff != NULL) if (SampleBuff != NULL)
{ {
delete[] SampleBuff; delete[] SampleBuff;
@ -156,7 +72,7 @@ void OPLmusicBlock::ResetChips ()
return; return;
#endif #endif
io->OPLdeinit (); io->OPLdeinit ();
io->OPLinit (TwoChips + 1, SampleRate); io->OPLinit (TwoChips + 1, uint(OPL_SAMPLE_RATE));
#ifdef _WIN32 #ifdef _WIN32
LeaveCriticalSection (&ChipAccess); LeaveCriticalSection (&ChipAccess);
#else #else
@ -164,22 +80,125 @@ void OPLmusicBlock::ResetChips ()
#endif #endif
} }
bool OPLmusicBlock::IsValid () const void OPLmusicBlock::Restart()
{
return scoredata != NULL;
}
void OPLmusicBlock::SetLooping (bool loop)
{
Looping = loop;
}
void OPLmusicBlock::Restart ()
{ {
OPLstopMusic (); OPLstopMusic ();
OPLplayMusic (); OPLplayMusic ();
MLtime = 0; MLtime = 0;
playingcount = 0; playingcount = 0;
}
OPLmusicFile::OPLmusicFile (FILE *file, char * musiccache, int len, int maxSamples)
: ScoreLen (len)
{
if (io == NULL)
{
return;
}
scoredata = new BYTE[len];
if (file)
{
if (fread (scoredata, 1, len, file) != (size_t)len)
{
delete[] scoredata;
scoredata = NULL;
return;
}
}
else
{
memcpy(scoredata, &musiccache[0], len);
}
if (io->OPLinit (TwoChips + 1, uint(OPL_SAMPLE_RATE)))
{
delete[] scoredata;
scoredata = NULL;
return;
}
// Check for MUS format
if (*(DWORD *)scoredata == MAKE_ID('M','U','S',0x1a))
{
FWadLump data = Wads.OpenLumpName ("GENMIDI");
if (0 != OPLloadBank (data))
{
delete[] scoredata;
scoredata = NULL;
return;
}
BlockForStats = this;
SamplesPerTick = OPL_SAMPLE_RATE / 140.0;
RawPlayer = NotRaw;
}
// Check for RDosPlay raw OPL format
else if (((DWORD *)scoredata)[0] == MAKE_ID('R','A','W','A') &&
((DWORD *)scoredata)[1] == MAKE_ID('D','A','T','A'))
{
RawPlayer = RDosPlay;
if (*(WORD *)(scoredata + 8) == 0)
{ // A clock speed of 0 is bad
*(WORD *)(scoredata + 8) = 0xFFFF;
}
SamplesPerTick = OPL_SAMPLE_RATE * LittleShort(*(WORD *)(scoredata + 8)) / 1193180.0;
}
// Check for modified IMF format (includes a header)
else if (((DWORD *)scoredata)[0] == MAKE_ID('A','D','L','I') &&
scoredata[4] == 'B' && scoredata[5] == 1)
{
int songlen;
BYTE *max = scoredata + ScoreLen;
RawPlayer = IMF;
SamplesPerTick = OPL_SAMPLE_RATE / IMF_RATE;
score = scoredata + 6;
// Skip track and game name
for (int i = 2; i != 0; --i)
{
while (score < max && *score++ != '\0') {}
}
if (score < max) score++; // Skip unknown byte
if (score + 8 > max)
{ // Not enough room left for song data
delete[] scoredata;
scoredata = NULL;
return;
}
songlen = LittleLong(*(DWORD *)score);
if (songlen != 0 && (songlen +=4) < ScoreLen - (score - scoredata))
{
ScoreLen = songlen + int(score - scoredata);
}
}
Restart ();
}
OPLmusicFile::~OPLmusicFile ()
{
if (scoredata != NULL)
{
io->OPLdeinit ();
delete[] scoredata;
scoredata = NULL;
}
}
bool OPLmusicFile::IsValid () const
{
return scoredata != NULL;
}
void OPLmusicFile::SetLooping (bool loop)
{
Looping = loop;
}
void OPLmusicFile::Restart ()
{
OPLmusicBlock::Restart();
if (RawPlayer == NotRaw) if (RawPlayer == NotRaw)
{ {
score = scoredata + ((MUSheader *)scoredata)->scoreStart; score = scoredata + ((MUSheader *)scoredata)->scoreStart;
@ -187,7 +206,7 @@ void OPLmusicBlock::Restart ()
else if (RawPlayer == RDosPlay) else if (RawPlayer == RDosPlay)
{ {
score = scoredata + 10; score = scoredata + 10;
SamplesPerTick = Scale (SampleRate, LittleShort(*(WORD *)(scoredata + 8)), 1193180); SamplesPerTick = OPL_SAMPLE_RATE * LittleShort(*(WORD *)(scoredata + 8)) / 1193180.0;
} }
else if (RawPlayer == IMF) else if (RawPlayer == IMF)
{ {
@ -234,7 +253,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
#endif #endif
while (numsamples > 0) while (numsamples > 0)
{ {
int samplesleft = MIN (numsamples, NextTickIn); int samplesleft = MIN (numsamples, int(NextTickIn));
if (samplesleft > 0) if (samplesleft > 0)
{ {
@ -249,7 +268,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
samples1 += samplesleft; samples1 += samplesleft;
} }
if (NextTickIn == 0) if (NextTickIn < 1)
{ {
int next = PlayTick (); int next = PlayTick ();
if (next == 0) if (next == 0)
@ -277,7 +296,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
else else
{ {
prevEnded = false; prevEnded = false;
NextTickIn = SamplesPerTick * next; NextTickIn += SamplesPerTick * next;
assert (NextTickIn >= 0); assert (NextTickIn >= 0);
MLtime += next; MLtime += next;
} }
@ -371,7 +390,7 @@ done:
return res; return res;
} }
int OPLmusicBlock::PlayTick () int OPLmusicFile::PlayTick ()
{ {
BYTE reg, data; BYTE reg, data;
@ -397,7 +416,7 @@ int OPLmusicBlock::PlayTick ()
case 2: // Speed change or OPL3 switch case 2: // Speed change or OPL3 switch
if (data == 0) if (data == 0)
{ {
SamplesPerTick = Scale (SampleRate, LittleShort(*(WORD *)(score)), 1193180); SamplesPerTick = OPL_SAMPLE_RATE * LittleShort(*(WORD *)(score)) / 1193180.0;
score += 2; score += 2;
} }
break; break;

View File

@ -9,27 +9,26 @@
#include "muslib.h" #include "muslib.h"
#include "files.h" #include "files.h"
#define OPL_SAMPLE_RATE 49716.0
class OPLmusicBlock : public musicBlock class OPLmusicBlock : public musicBlock
{ {
public: public:
OPLmusicBlock (FILE *file, char * musiccache, int len, int rate, int maxSamples); OPLmusicBlock();
~OPLmusicBlock (); ~OPLmusicBlock();
bool IsValid () const;
bool ServiceStream (void *buff, int numbytes); bool ServiceStream(void *buff, int numbytes);
void Restart (); void ResetChips();
void SetLooping (bool loop);
void ResetChips (); virtual void Restart();
int PlayTick ();
protected: protected:
int SampleRate; virtual int PlayTick() = 0;
int NextTickIn;
int SamplesPerTick; double NextTickIn;
double SamplesPerTick;
bool TwoChips; bool TwoChips;
bool Looping; bool Looping;
enum { NotRaw, RDosPlay, IMF } RawPlayer;
int ScoreLen;
int *SampleBuff; int *SampleBuff;
int SampleBuffSize; int SampleBuffSize;
@ -40,3 +39,20 @@ protected:
SDL_mutex *ChipAccess; SDL_mutex *ChipAccess;
#endif #endif
}; };
class OPLmusicFile : public OPLmusicBlock
{
public:
OPLmusicFile(FILE *file, char *musiccache, int len, int maxSamples);
~OPLmusicFile();
bool IsValid() const;
void SetLooping(bool loop);
void Restart();
protected:
int PlayTick();
enum { NotRaw, RDosPlay, IMF } RawPlayer;
int ScoreLen;
};

View File

@ -1359,8 +1359,18 @@ bool S_ChangeMusic (const char *musicname, int order, bool looping, bool force)
// shutdown old music // shutdown old music
S_StopMusic (true); S_StopMusic (true);
// Just record it if volume is 0
if (snd_musicvolume <= 0)
{
mus_playing.loop = looping;
mus_playing.name = "";
mus_playing.baseorder = 0;
LastSong = musicname;
return true;
}
// load & register it // load & register it
if (offset!=-1) if (offset != -1)
{ {
mus_playing.handle = I_RegisterSong (lumpnum != -1 ? mus_playing.handle = I_RegisterSong (lumpnum != -1 ?
Wads.GetWadFullName (Wads.GetLumpFile (lumpnum)) : Wads.GetWadFullName (Wads.GetLumpFile (lumpnum)) :
@ -1373,10 +1383,11 @@ bool S_ChangeMusic (const char *musicname, int order, bool looping, bool force)
} }
mus_playing.loop = looping; mus_playing.loop = looping;
mus_playing.name = musicname;
LastSong = "";
if (mus_playing.handle != 0) if (mus_playing.handle != 0)
{ // play it { // play it
mus_playing.name = musicname;
I_PlaySong (mus_playing.handle, looping, S_GetMusicVolume (musicname)); I_PlaySong (mus_playing.handle, looping, S_GetMusicVolume (musicname));
mus_playing.baseorder = mus_playing.baseorder =
(I_SetSongPosition (mus_playing.handle, order) ? order : 0); (I_SetSongPosition (mus_playing.handle, order) ? order : 0);
@ -1396,8 +1407,9 @@ void S_RestartMusic ()
{ {
if (!LastSong.IsEmpty()) if (!LastSong.IsEmpty())
{ {
S_ChangeMusic (LastSong, mus_playing.baseorder, mus_playing.loop, true); FString song = LastSong;
LastSong = ""; LastSong = "";
S_ChangeMusic (song, mus_playing.baseorder, mus_playing.loop, true);
} }
} }

View File

@ -427,7 +427,9 @@ public:
} }
else else
{ {
return FMOD_ERR_FILE_EOF; self->Channel->stop();
// Contrary to the docs, this return value is completely ignored.
return FMOD_ERR_INVALID_PARAM;
} }
} }

View File

@ -90,26 +90,37 @@ float saved_relative_volume = 1.0f; // this could be used to implement an ACS Fa
// Maximum volume of MOD/stream music. // Maximum volume of MOD/stream music.
//========================================================================== //==========================================================================
CUSTOM_CVAR (Float, snd_musicvolume, 0.3f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CUSTOM_CVAR (Float, snd_musicvolume, 0.5f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
{ {
if (self < 0.f) if (self < 0.f)
self = 0.f; self = 0.f;
else if (self > 1.f) else if (self > 1.f)
self = 1.f; self = 1.f;
else if (GSnd != NULL) else
{ {
// Set general music volume. if (GSnd != NULL)
GSnd->SetMusicVolume(clamp<float>(self * relative_volume, 0, 1)); {
// Set general music volume.
GSnd->SetMusicVolume(clamp<float>(self * relative_volume, 0, 1));
}
// For music not implemented through the digital sound system, // For music not implemented through the digital sound system,
// let them know about the changed. // let them know about the change.
if (currSong != NULL) if (currSong != NULL)
{ {
currSong->MusicVolumeChanged(); currSong->MusicVolumeChanged();
} }
else
{ // If the music was stopped because volume was 0, start it now.
S_RestartMusic();
}
} }
} }
MusInfo::MusInfo()
: m_Status(STATE_Stopped), m_Looping(false), m_NotStartedYet(true)
{
}
MusInfo::~MusInfo () MusInfo::~MusInfo ()
{ {
} }
@ -187,6 +198,7 @@ void I_PlaySong (void *handle, int _looping, float rel_vol)
saved_relative_volume = relative_volume = rel_vol; saved_relative_volume = relative_volume = rel_vol;
info->Stop (); info->Stop ();
info->Play (_looping ? true : false); info->Play (_looping ? true : false);
info->m_NotStartedYet = false;
if (info->m_Status == MusInfo::STATE_Playing) if (info->m_Status == MusInfo::STATE_Playing)
currSong = info; currSong = info;

View File

@ -31,7 +31,7 @@ extern float relative_volume;
class MusInfo class MusInfo
{ {
public: public:
MusInfo () : m_Status(STATE_Stopped) {} MusInfo ();
virtual ~MusInfo (); virtual ~MusInfo ();
virtual void MusicVolumeChanged(); // snd_musicvolume changed virtual void MusicVolumeChanged(); // snd_musicvolume changed
virtual void TimidityVolumeChanged(); // timidity_mastervolume changed virtual void TimidityVolumeChanged(); // timidity_mastervolume changed
@ -53,12 +53,23 @@ public:
STATE_Paused STATE_Paused
} m_Status; } m_Status;
bool m_Looping; bool m_Looping;
bool m_NotStartedYet; // Song has been created but not yet played
}; };
#ifdef _WIN32 #ifdef _WIN32
// A device that provides a WinMM-like MIDI streaming interface ------------- // A device that provides a WinMM-like MIDI streaming interface -------------
#ifndef _WIN32
struct MIDIHDR
{
BYTE *lpData;
DWORD dwBufferLength;
DWORD dwBytesRecorded;
MIDIHDR *Next;
};
#endif
class MIDIDevice class MIDIDevice
{ {
public: public:
@ -109,8 +120,31 @@ protected:
void *CallbackData; void *CallbackData;
}; };
// Base class for streaming MUS and MIDI files ------------------------------ // OPL implementation of a MIDI output device -------------------------------
class OPLMIDIDevice : public MIDIDevice, OPLmusicBlock
{
public:
OPLMIDIDevice();
~OPLMIDIDevice();
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:
void (*Callback)(unsigned int, void *, DWORD, DWORD);
void *CallbackData;
};
// Base class for streaming MUS and MIDI files ------------------------------
class MIDIStreamer : public MusInfo class MIDIStreamer : public MusInfo
{ {
@ -305,7 +339,7 @@ public:
protected: protected:
static bool FillStream (SoundStream *stream, void *buff, int len, void *userdata); static bool FillStream (SoundStream *stream, void *buff, int len, void *userdata);
OPLmusicBlock *Music; OPLmusicFile *Music;
}; };
// CD track/disk played through the multimedia system ----------------------- // CD track/disk played through the multimedia system -----------------------

View File

@ -95,7 +95,7 @@ SoundRenderer *GSnd;
// Maximum volume of a sound effect. // Maximum volume of a sound effect.
//========================================================================== //==========================================================================
CUSTOM_CVAR (Float, snd_sfxvolume, 0.5f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL) CUSTOM_CVAR (Float, snd_sfxvolume, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL)
{ {
if (self < 0.f) if (self < 0.f)
self = 0.f; self = 0.f;

View File

@ -43,7 +43,7 @@
// MACROS ------------------------------------------------------------------ // MACROS ------------------------------------------------------------------
#define MAX_TIME (1000000/20) // Send out 1/20 of a sec of events at a time. #define MAX_TIME (1000000/10) // Send out 1/10 of a sec of events at a time.
// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

View File

@ -1,13 +1,5 @@
#include "i_musicinterns.h" #include "i_musicinterns.h"
CUSTOM_CVAR (Int, opl_frequency, 49716, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
{ // Clamp frequency to FMOD's limits
if (self < 4000)
self = 4000;
else if (self > 49716) // No need to go higher than this
self = 49716;
}
CVAR (Bool, opl_enable, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, opl_enable, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
static bool OPL_Active; static bool OPL_Active;
@ -21,15 +13,14 @@ CUSTOM_CVAR (Bool, opl_onechip, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
} }
OPLMUSSong::OPLMUSSong (FILE *file, char * musiccache, int len) OPLMUSSong::OPLMUSSong (FILE *file, char *musiccache, int len)
{ {
int rate = *opl_frequency; int samples = int(OPL_SAMPLE_RATE / 14);
int samples = rate/14;
Music = new OPLmusicBlock (file, musiccache, len, rate, samples); Music = new OPLmusicFile (file, musiccache, len, samples);
m_Stream = GSnd->CreateStream (FillStream, samples*2, m_Stream = GSnd->CreateStream (FillStream, samples*2,
SoundStream::Mono, rate, this); SoundStream::Mono, int(OPL_SAMPLE_RATE), this);
if (m_Stream == NULL) if (m_Stream == NULL)
{ {
Printf (PRINT_BOLD, "Could not create music stream.\n"); Printf (PRINT_BOLD, "Could not create music stream.\n");