diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 9b58ca0ce..b114e8be3 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,4 +1,11 @@ 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 that the drivers will treat a BackBufferCount of 0 as a request for double buffering. diff --git a/src/m_options.cpp b/src/m_options.cpp index f3759db8b..02b515c95 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -1270,26 +1270,13 @@ static menu_t SoundMenu = *=======================================*/ EXTERN_CVAR (Bool, opl_enable) -EXTERN_CVAR (Int, opl_frequency) 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[] = { { 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, "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 = diff --git a/src/oplsynth/opl_mus_player.cpp b/src/oplsynth/opl_mus_player.cpp index cf4d1bb3a..cd5cdd09e 100644 --- a/src/oplsynth/opl_mus_player.cpp +++ b/src/oplsynth/opl_mus_player.cpp @@ -15,21 +15,21 @@ #include "v_text.h" #include "c_dispatch.h" -#define IMF_RATE 700 +#define IMF_RATE 700.0 EXTERN_CVAR (Bool, opl_onechip) static OPLmusicBlock *BlockForStats; -OPLmusicBlock::OPLmusicBlock (FILE *file, char * musiccache, int len, int rate, int maxSamples) - : SampleRate (rate), NextTickIn (0), Looping (false), ScoreLen (len) +OPLmusicBlock::OPLmusicBlock() { scoredata = NULL; SampleBuff = NULL; SampleBuffSize = 0; + NextTickIn = 0; TwoChips = !opl_onechip; - io = new OPLio; - + Looping = false; + io = NULL; #ifdef _WIN32 InitializeCriticalSection (&ChipAccess); #else @@ -39,96 +39,12 @@ OPLmusicBlock::OPLmusicBlock (FILE *file, char * musiccache, int len, int rate, return; } #endif - - 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 (); + io = new OPLio; } -OPLmusicBlock::~OPLmusicBlock () +OPLmusicBlock::~OPLmusicBlock() { BlockForStats = NULL; - if (scoredata != NULL) - { - io->OPLdeinit (); - delete[] scoredata; - scoredata = NULL; - } if (SampleBuff != NULL) { delete[] SampleBuff; @@ -156,7 +72,7 @@ void OPLmusicBlock::ResetChips () return; #endif io->OPLdeinit (); - io->OPLinit (TwoChips + 1, SampleRate); + io->OPLinit (TwoChips + 1, uint(OPL_SAMPLE_RATE)); #ifdef _WIN32 LeaveCriticalSection (&ChipAccess); #else @@ -164,22 +80,125 @@ void OPLmusicBlock::ResetChips () #endif } -bool OPLmusicBlock::IsValid () const -{ - return scoredata != NULL; -} - -void OPLmusicBlock::SetLooping (bool loop) -{ - Looping = loop; -} - -void OPLmusicBlock::Restart () +void OPLmusicBlock::Restart() { OPLstopMusic (); OPLplayMusic (); MLtime = 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) { score = scoredata + ((MUSheader *)scoredata)->scoreStart; @@ -187,7 +206,7 @@ void OPLmusicBlock::Restart () else if (RawPlayer == RDosPlay) { 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) { @@ -234,7 +253,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) #endif while (numsamples > 0) { - int samplesleft = MIN (numsamples, NextTickIn); + int samplesleft = MIN (numsamples, int(NextTickIn)); if (samplesleft > 0) { @@ -249,7 +268,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) samples1 += samplesleft; } - if (NextTickIn == 0) + if (NextTickIn < 1) { int next = PlayTick (); if (next == 0) @@ -277,7 +296,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes) else { prevEnded = false; - NextTickIn = SamplesPerTick * next; + NextTickIn += SamplesPerTick * next; assert (NextTickIn >= 0); MLtime += next; } @@ -371,7 +390,7 @@ done: return res; } -int OPLmusicBlock::PlayTick () +int OPLmusicFile::PlayTick () { BYTE reg, data; @@ -397,7 +416,7 @@ int OPLmusicBlock::PlayTick () case 2: // Speed change or OPL3 switch if (data == 0) { - SamplesPerTick = Scale (SampleRate, LittleShort(*(WORD *)(score)), 1193180); + SamplesPerTick = OPL_SAMPLE_RATE * LittleShort(*(WORD *)(score)) / 1193180.0; score += 2; } break; diff --git a/src/oplsynth/opl_mus_player.h b/src/oplsynth/opl_mus_player.h index f7daee46e..090345673 100644 --- a/src/oplsynth/opl_mus_player.h +++ b/src/oplsynth/opl_mus_player.h @@ -9,27 +9,26 @@ #include "muslib.h" #include "files.h" +#define OPL_SAMPLE_RATE 49716.0 + class OPLmusicBlock : public musicBlock { public: - OPLmusicBlock (FILE *file, char * musiccache, int len, int rate, int maxSamples); - ~OPLmusicBlock (); - bool IsValid () const; + OPLmusicBlock(); + ~OPLmusicBlock(); - bool ServiceStream (void *buff, int numbytes); - void Restart (); - void SetLooping (bool loop); - void ResetChips (); - int PlayTick (); + bool ServiceStream(void *buff, int numbytes); + void ResetChips(); + + virtual void Restart(); protected: - int SampleRate; - int NextTickIn; - int SamplesPerTick; + virtual int PlayTick() = 0; + + double NextTickIn; + double SamplesPerTick; bool TwoChips; bool Looping; - enum { NotRaw, RDosPlay, IMF } RawPlayer; - int ScoreLen; int *SampleBuff; int SampleBuffSize; @@ -40,3 +39,20 @@ protected: SDL_mutex *ChipAccess; #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; +}; diff --git a/src/s_sound.cpp b/src/s_sound.cpp index b54f589e2..c1616139c 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -1359,8 +1359,18 @@ bool S_ChangeMusic (const char *musicname, int order, bool looping, bool force) // shutdown old music 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 - if (offset!=-1) + if (offset != -1) { mus_playing.handle = I_RegisterSong (lumpnum != -1 ? 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.name = musicname; + LastSong = ""; if (mus_playing.handle != 0) { // play it - mus_playing.name = musicname; I_PlaySong (mus_playing.handle, looping, S_GetMusicVolume (musicname)); mus_playing.baseorder = (I_SetSongPosition (mus_playing.handle, order) ? order : 0); @@ -1396,8 +1407,9 @@ void S_RestartMusic () { if (!LastSong.IsEmpty()) { - S_ChangeMusic (LastSong, mus_playing.baseorder, mus_playing.loop, true); + FString song = LastSong; LastSong = ""; + S_ChangeMusic (song, mus_playing.baseorder, mus_playing.loop, true); } } diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index d06253401..c918cff33 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -427,7 +427,9 @@ public: } else { - return FMOD_ERR_FILE_EOF; + self->Channel->stop(); + // Contrary to the docs, this return value is completely ignored. + return FMOD_ERR_INVALID_PARAM; } } diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index b2ad10cf8..ef597fa74 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -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. //========================================================================== -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) self = 0.f; else if (self > 1.f) self = 1.f; - else if (GSnd != NULL) + else { - // Set general music volume. - GSnd->SetMusicVolume(clamp(self * relative_volume, 0, 1)); - + if (GSnd != NULL) + { + // Set general music volume. + GSnd->SetMusicVolume(clamp(self * relative_volume, 0, 1)); + } // For music not implemented through the digital sound system, - // let them know about the changed. + // let them know about the change. if (currSong != NULL) { 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 () { } @@ -187,6 +198,7 @@ void I_PlaySong (void *handle, int _looping, float rel_vol) saved_relative_volume = relative_volume = rel_vol; info->Stop (); info->Play (_looping ? true : false); + info->m_NotStartedYet = false; if (info->m_Status == MusInfo::STATE_Playing) currSong = info; diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 42f896ab4..883cc8573 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -31,7 +31,7 @@ extern float relative_volume; class MusInfo { public: - MusInfo () : m_Status(STATE_Stopped) {} + MusInfo (); virtual ~MusInfo (); virtual void MusicVolumeChanged(); // snd_musicvolume changed virtual void TimidityVolumeChanged(); // timidity_mastervolume changed @@ -53,12 +53,23 @@ public: STATE_Paused } m_Status; bool m_Looping; + bool m_NotStartedYet; // Song has been created but not yet played }; #ifdef _WIN32 // 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 { public: @@ -109,8 +120,31 @@ protected: 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 { @@ -305,7 +339,7 @@ public: protected: static bool FillStream (SoundStream *stream, void *buff, int len, void *userdata); - OPLmusicBlock *Music; + OPLmusicFile *Music; }; // CD track/disk played through the multimedia system ----------------------- diff --git a/src/sound/i_sound.cpp b/src/sound/i_sound.cpp index b98337ac4..84a8ab5c0 100644 --- a/src/sound/i_sound.cpp +++ b/src/sound/i_sound.cpp @@ -95,7 +95,7 @@ SoundRenderer *GSnd; // 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) self = 0.f; diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index f2bf7ce66..087ce2c8f 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -43,7 +43,7 @@ // 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 -------------------------------------------- diff --git a/src/sound/music_mus_opl.cpp b/src/sound/music_mus_opl.cpp index ae0000da0..176abe800 100644 --- a/src/sound/music_mus_opl.cpp +++ b/src/sound/music_mus_opl.cpp @@ -1,13 +1,5 @@ #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) 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 = rate/14; + int samples = int(OPL_SAMPLE_RATE / 14); - Music = new OPLmusicBlock (file, musiccache, len, rate, samples); + Music = new OPLmusicFile (file, musiccache, len, samples); m_Stream = GSnd->CreateStream (FillStream, samples*2, - SoundStream::Mono, rate, this); + SoundStream::Mono, int(OPL_SAMPLE_RATE), this); if (m_Stream == NULL) { Printf (PRINT_BOLD, "Could not create music stream.\n");