From ce811319a7bfa45bd8f1ed52d94e7f6ed0181691 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sun, 16 Mar 2008 00:54:53 +0000 Subject: [PATCH] - Fixed: If an object is flagged for euthanization while it's in the gray list, it should just be ignored during the propagation stage. - After sleeping on it and realizing what was really going in, I generalized the inventory fix from the 13th: The actor is flagged by Destroy(), then it is later inserted into the thinker list by DThinker::SerializeAll(). So rather than unlinking the skipped player from their inventory, just make sure any flagged thinkers aren't inserted into a list. - Fixed: FCanvasTextureInfo::Viewpoint needed a read barrier, and the whole list should serve as a root. - Reimplemented SPC playback as a custom codec for FMOD. - Removed spc_frequency, because snes_spc only supports the SPC's native frequency of 32000 Hz. SVN r806 (trunk) --- docs/rh-log.txt | 16 ++ snes_spc/snes_spc/SNES_SPC.h | 5 +- src/dobjgc.cpp | 16 +- src/dthinker.cpp | 40 +++- src/farchive.cpp | 12 +- src/p_map.cpp | 8 +- src/r_data.h | 3 +- src/r_main.cpp | 21 +- src/sound/fmodsound.cpp | 4 + src/sound/i_music.cpp | 28 +-- src/sound/i_musicinterns.h | 21 -- src/sound/music_spc.cpp | 383 ++++++++++++++++++++++++++--------- 12 files changed, 378 insertions(+), 179 deletions(-) diff --git a/docs/rh-log.txt b/docs/rh-log.txt index cf32396cd2..44cdd46e69 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,9 +1,25 @@ +March 15, 2008 +- Fixed: If an object is flagged for euthanization while it's in the gray + list, it should just be ignored during the propagation stage. +- After sleeping on it and realizing what was really going in, I generalized + the inventory fix from the 13th: The actor is flagged by Destroy(), then it + is later inserted into the thinker list by DThinker::SerializeAll(). So + rather than unlinking the skipped player from their inventory, just make + sure any flagged thinkers aren't inserted into a list. +- Fixed: FCanvasTextureInfo::Viewpoint needed a read barrier, and the whole + list should serve as a root. + March 14, 2008 (Changes by Graf Zahl) - fixed: A_PainShootSkull was missing a NULL pointer check for the spawn type. - implemented Vavoom's vertex height things (1504, 1505) that can explicitly define slopes for triangular sectors. The height is specified as the thing's z-coordinate. +March 14, 2008 +- Reimplemented SPC playback as a custom codec for FMOD. +- Removed spc_frequency, because snes_spc only supports the SPC's native + frequency of 32000 Hz. + March 13, 2008 - Fixed: When returning to a hub map, inventory belonging to the temporary copies of the players stored in the archive would be destroyed and break diff --git a/snes_spc/snes_spc/SNES_SPC.h b/snes_spc/snes_spc/SNES_SPC.h index 8ea0392fd2..2bacfa8a81 100644 --- a/snes_spc/snes_spc/SNES_SPC.h +++ b/snes_spc/snes_spc/SNES_SPC.h @@ -128,7 +128,8 @@ public: enum { extra_size = SPC_DSP::extra_size }; enum { signature_size = 35 }; - + static char const signature [signature_size + 1]; + private: SPC_DSP dsp; @@ -247,8 +248,6 @@ private: uint8_t ipl_rom [0x40]; }; - static char const signature [signature_size + 1]; - void save_regs( uint8_t out [reg_count] ); }; diff --git a/src/dobjgc.cpp b/src/dobjgc.cpp index 2a009618b0..9a687b7e27 100644 --- a/src/dobjgc.cpp +++ b/src/dobjgc.cpp @@ -155,7 +155,8 @@ size_t PropagateMark() assert(obj->IsGray()); obj->Gray2Black(); Gray = obj->GCNext; - return obj->PropagateMark(); + return !(obj->ObjectFlags & OF_EuthanizeMe) ? obj->PropagateMark() : + obj->GetClass()->Size; } //========================================================================== @@ -181,7 +182,7 @@ static size_t PropagateAll() // SweepList // // Runs a limited sweep on a list, returning the location where to resume -// the sweep at next time. +// the sweep at next time. (FIXME: Horrible Engrish in this description.) // //========================================================================== @@ -204,8 +205,14 @@ static DObject **SweepList(DObject **p, size_t count, size_t *finalize_count) assert(curr->IsDead()); *p = curr->ObjNext; if (!(curr->ObjectFlags & OF_EuthanizeMe)) - { // The object must be destroyed before it can be finalized. - assert(!curr->IsKindOf(RUNTIME_CLASS(DThinker))); + { // The object must be destroyed before it can be finalized. + // Note that thinkers must already have been destroyed. If they get here without + // having been destroyed first, it means they somehow became unattached from the + // thinker lists. If I don't maintain the invariant that all live thinkers must + // be in a thinker list, then I need to add write barriers for every time a + // thinker pointer is changed. This seems easier and perfectly reasonable, since + // a live thinker that isn't on a thinker list isn't much of a thinker. + assert(!curr->IsKindOf(RUNTIME_CLASS(DThinker)) || (curr->ObjectFlags & OF_Sentinel)); curr->Destroy(); } curr->ObjectFlags |= OF_Cleanup; @@ -263,6 +270,7 @@ static void MarkRoot() Mark(screen); Mark(StatusBar); DThinker::MarkRoots(); + FCanvasTextureInfo::Mark(); Mark(DACSThinker::ActiveThinker); for (i = 0; i < BODYQUESIZE; ++i) { diff --git a/src/dthinker.cpp b/src/dthinker.cpp index 94fc9646f4..f48cc6ae1d 100644 --- a/src/dthinker.cpp +++ b/src/dthinker.cpp @@ -54,6 +54,8 @@ bool DThinker::bSerialOverride = false; void FThinkerList::AddTail(DThinker *thinker) { + assert(thinker->PrevThinker == NULL && thinker->NextThinker == NULL); + assert(!(thinker->ObjectFlags & OF_EuthanizeMe)); if (Sentinel == NULL) { Sentinel = new DThinker(DThinker::NO_LINK); @@ -62,7 +64,6 @@ void FThinkerList::AddTail(DThinker *thinker) Sentinel->PrevThinker = Sentinel; GC::WriteBarrier(Sentinel); } - assert(thinker->PrevThinker == NULL && thinker->NextThinker == NULL); DThinker *tail = Sentinel->PrevThinker; assert(tail->NextThinker == Sentinel); thinker->PrevThinker = tail; @@ -172,7 +173,12 @@ void DThinker::SerializeAll(FArchive &arc, bool hubLoad) } // Thinkers with the OF_JustSpawned flag set go in the FreshThinkers // list. Anything else goes in the regular Thinkers list. - if (thinker->ObjectFlags & OF_JustSpawned) + if (thinker->ObjectFlags & OF_EuthanizeMe) + { + // This thinker was destroyed during the loading process. Do + // not link it in to any list. + } + else if (thinker->ObjectFlags & OF_JustSpawned) { FreshThinkers[stat].AddTail(thinker); } @@ -367,16 +373,30 @@ void DThinker::DestroyMostThinkersInList (FThinkerList &list, int stat) DestroyThinkersInList (list); } else if (list.Sentinel != NULL) - { // Move all players to an ancillary list to ensure - // that the collector won't consider them dead. - while (list.Sentinel->NextThinker != list.Sentinel) + { // If it's a voodoo doll, destroy it. Otherwise, simply remove + // it from the list. G_FinishTravel() will find it later from + // a players[].mo link and destroy it then, after copying various + // information to a new player. + for (DThinker *probe = list.Sentinel->NextThinker, *next; probe != list.Sentinel; probe = next) { - DThinker *thinker = list.Sentinel->NextThinker; - thinker->Remove(); - Thinkers[MAX_STATNUM+1].AddTail(thinker); + next = probe->NextThinker; + if (!probe->IsKindOf(RUNTIME_CLASS(APlayerPawn)) || // <- should not happen + static_cast(probe)->player == NULL || + static_cast(probe)->player->mo != probe) + { + probe->Destroy(); + } + else + { + probe->Remove(); + // Technically, this doesn't need to be in any list now, since + // it's only going to be found later and destroyed before ever + // needing to tick again, but by moving it to a separate list, + // I can keep my debug assertions that all thinkers are either + // euthanizing or in a list. + Thinkers[MAX_STATNUM+1].AddTail(probe); + } } - list.Sentinel->Destroy(); - list.Sentinel = NULL; } } diff --git a/src/farchive.cpp b/src/farchive.cpp index a5fd352ed4..629f703fa2 100644 --- a/src/farchive.cpp +++ b/src/farchive.cpp @@ -1162,20 +1162,10 @@ FArchive &FArchive::ReadObject (DObject* &obj, PClass *wanttype) { // When the temporary player's inventory items were loaded, // they became owned by the real player. Undo that now. - AInventory *item; - - for (item = tempobj->Inventory; item != NULL; item = item->Inventory) + for (AInventory *item = tempobj->Inventory; item != NULL; item = item->Inventory) { item->Owner = tempobj; } - item = tempobj->Inventory; - tempobj->Inventory = NULL; -#ifdef _DEBUG - // The only references to this inventory list should be from the - // temporary player, so they will be freed when a collection occurs. - size_t a = 0; - assert(item == NULL || (a = DObject::StaticPointerSubstitution(item, NULL)) == 0); -#endif tempobj->Destroy (); } else diff --git a/src/p_map.cpp b/src/p_map.cpp index f132a9c1ef..e35fe0740c 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -2372,6 +2372,7 @@ bool PTR_BounceTraverse (intercept_t *in) I_Error ("PTR_BounceTraverse: not a line?"); li = in->d.line; + assert(((size_t)li - (size_t)lines) % sizeof(line_t) == 0); if (li->flags & ML_BLOCKEVERYTHING) { goto bounceblocking; @@ -2448,6 +2449,7 @@ bool P_BounceWall (AActor *mo) leady = mo->y-mo->radius; } bestslidefrac = FRACUNIT+1; + bestslideline = NULL; if (P_PathTraverse(leadx, leady, leadx+mo->momx, leady+mo->momy, PT_ADDLINES, PTR_BounceTraverse) && BlockingLine == NULL) { // Could not find a wall, so bounce off the floor/ceiling instead. @@ -2463,12 +2465,6 @@ bool P_BounceWall (AActor *mo) mo->FloorBounceMissile (mo->Sector->ceilingplane); return true; } - /* - else - { - return (mo->flags2 & MF2_BOUNCE2) != 0; - } - */ } line = bestslideline ? bestslideline : BlockingLine; diff --git a/src/r_data.h b/src/r_data.h index a3b6995ef3..ba0a61d2ad 100644 --- a/src/r_data.h +++ b/src/r_data.h @@ -530,7 +530,7 @@ protected: struct FCanvasTextureInfo { FCanvasTextureInfo *Next; - AActor *Viewpoint; + TObjPtr Viewpoint; FCanvasTexture *Texture; int PicNum; int FOV; @@ -539,6 +539,7 @@ struct FCanvasTextureInfo static void UpdateAll (); static void EmptyList (); static void Serialize (FArchive &arc); + static void Mark(); private: static FCanvasTextureInfo *List; diff --git a/src/r_main.cpp b/src/r_main.cpp index 9ca9c42313..d1debc2635 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -1653,9 +1653,12 @@ void FCanvasTextureInfo::Add (AActor *viewpoint, int picnum, int fov) if (probe->Texture == texture) { // Yes, change its assignment to this new camera + if (probe->Viewpoint != viewpoint || probe->FOV != fov) + { + texture->bFirstUpdate = true; + } probe->Viewpoint = viewpoint; probe->FOV = fov; - texture->bFirstUpdate = true; return; } } @@ -1753,6 +1756,22 @@ void FCanvasTextureInfo::Serialize (FArchive &arc) } } +//========================================================================== +// +// FCanvasTextureInfo :: Mark +// +// Marks all viewpoints in the list for the collector. +// +//========================================================================== + +void FCanvasTextureInfo::Mark() +{ + for (FCanvasTextureInfo *probe = List; probe != NULL; probe = probe->Next) + { + GC::Mark(probe->Viewpoint); + } +} + //========================================================================== // // R_MultiresInit diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index a239eca204..31e51617a7 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -79,6 +79,8 @@ struct FEnumList // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- +FMOD_RESULT SPC_CreateCodec(FMOD::System *sys); + // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- @@ -560,6 +562,8 @@ bool FMODSoundRenderer::Init() result = SfxGroup->addGroup(PausableSfx); ERRCHECK(result); + result = SPC_CreateCodec(Sys); + if (snd_3d) { float rolloff_factor; diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index ea13abca84..fb1908fa80 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -323,32 +323,6 @@ void *I_RegisterSong (const char *filename, char * musiccache, int offset, int l info = new TimiditySong (file, musiccache, len); } } - // Check for SPC format -#ifdef _WIN32 - else if (id == MAKE_ID('S','N','E','S') && len >= 66048) - { - char header[0x23]; - - if (file != NULL) - { - if (fread (header, 1, 0x23, file) != 0x23) - { - fclose (file); - return 0; - } - fseek (file, -0x23, SEEK_CUR); - } - else - { - memcpy(header, musiccache, 0x23); - } - - if (strncmp (header+4, "-SPC700 Sound File Data", 23) == 0) - { - info = new SPCSong (file, musiccache, len); - } - } -#endif // Check for RDosPlay raw OPL format else if (id == MAKE_ID('R','A','W','A') && len >= 12) { @@ -428,7 +402,7 @@ void *I_RegisterSong (const char *filename, char * musiccache, int offset, int l // been identified already, so don't even bother trying to load it. if (info == NULL && GSnd != NULL && len >= 1024) { - // First try loading it as MOD, then as a stream + // Let FMOD figure out what it is. if (file != NULL) { fclose (file); diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 58afb4e225..6f227ff99b 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -250,27 +250,6 @@ protected: int m_LastPos; }; -// SPC file, rendered with SNESAPU.DLL and streamed through FMOD ------------ - -struct SNES_SPC; -struct SPC_Filter; - -class SPCSong : public StreamSong -{ -public: - SPCSong (FILE *file, char * musiccache, int length); - ~SPCSong (); - void Play (bool looping); - bool IsPlaying (); - bool IsValid () const; - -protected: - static bool FillStream (SoundStream *stream, void *buff, int len, void *userdata); - - SNES_SPC *SPC; - SPC_Filter *Filter; -}; - // MIDI file played with Timidity and possibly streamed through FMOD -------- class TimiditySong : public StreamSong diff --git a/src/sound/music_spc.cpp b/src/sound/music_spc.cpp index 00614213b3..fc5d2dc307 100644 --- a/src/sound/music_spc.cpp +++ b/src/sound/music_spc.cpp @@ -1,11 +1,51 @@ -#ifdef _WIN32 +/* +** music_spc.cpp +** SPC codec for FMOD Ex, using snes_spc for decoding. +** +**--------------------------------------------------------------------------- +** 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. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ -#include "i_musicinterns.h" #include "templates.h" #include "c_cvars.h" -#include "doomdef.h" #include "SNES_SPC.h" #include "SPC_Filter.h" +#include "fmod_wrap.h" +#include "doomdef.h" +#include "m_swap.h" +#include "m_fixed.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- struct XID6Tag { @@ -14,134 +54,287 @@ struct XID6Tag WORD Value; }; +struct SPCCodecData +{ + SNES_SPC *SPC; + SPC_Filter *Filter; + FMOD_CODEC_WAVEFORMAT WaveFormat; + bool bPlayed; +}; + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +FMOD_RESULT SPC_CreateCodec(FMOD::System *sys); + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +static FMOD_RESULT SPC_DoOpen(FMOD_CODEC_STATE *codec, SPCCodecData *userdata); +static FMOD_RESULT F_CALLBACK SPC_Open(FMOD_CODEC_STATE *codec, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo); +static FMOD_RESULT F_CALLBACK SPC_Close(FMOD_CODEC_STATE *codec); +static FMOD_RESULT F_CALLBACK SPC_Read(FMOD_CODEC_STATE *codec, void *buffer, unsigned int size, unsigned int *read); +static FMOD_RESULT F_CALLBACK SPC_SetPosition(FMOD_CODEC_STATE *codec_state, int subsound, unsigned int position, FMOD_TIMEUNIT postype); + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + EXTERN_CVAR (Int, snd_samplerate) +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + CVAR (Float, spc_amp, 1.875f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CUSTOM_CVAR (Int, spc_frequency, 32000, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +static FMOD_CODEC_DESCRIPTION SPCCodecDescription = { + "Super Nintendo SPC File", + 0x00000001, + 1, + FMOD_TIMEUNIT_MS | FMOD_TIMEUNIT_PCM, + SPC_Open, + SPC_Close, + SPC_Read, + NULL, + SPC_SetPosition, + NULL, + NULL, + NULL +}; + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// SPC_CreateCodec +// +//========================================================================== + +FMOD_RESULT SPC_CreateCodec(FMOD::System *sys) { - if (spc_frequency < 8000) - { - spc_frequency = 8000; - } - else if (spc_frequency > 32000) - { - spc_frequency = 32000; - } + return sys->createCodec(&SPCCodecDescription); } -SPCSong::SPCSong (FILE *iofile, char *musiccache, int len) +//========================================================================== +// +// SPC_Open +// +//========================================================================== + +static FMOD_RESULT SPC_DoOpen(FMOD_CODEC_STATE *codec, SPCCodecData *userdata) { - FileReader *file; + FMOD_RESULT result; + char spcfile[SNES_SPC::spc_file_size]; + unsigned int bytesread; + SNES_SPC *spc; + SPC_Filter *filter; - if (iofile != NULL) - { - file = new FileReader(iofile, len); + if (codec->filesize < 66048) + { // Anything smaller than this is definitely not a (valid) SPC. + return FMOD_ERR_FORMAT; } - else + result = codec->fileseek(codec->filehandle, 0, 0); + if (result != FMOD_OK) { - file = new MemoryReader(musiccache, len); + return result; } - - // No sense in using a higher frequency than the final output - int freq = MIN (*spc_frequency, *snd_samplerate); - - SPC = new SNES_SPC; - SPC->init(); - Filter = new SPC_Filter; - - BYTE spcfile[66048]; - - file->Read (spcfile, 66048); - - SPC->load_spc(spcfile, 66048); - SPC->clear_echo(); - Filter->set_gain(int(SPC_Filter::gain_unit * spc_amp)); - - m_Stream = GSnd->CreateStream (FillStream, 16384, 0, freq, this); - if (m_Stream == NULL) + // Check the signature. + result = codec->fileread(codec->filehandle, spcfile, SNES_SPC::signature_size, &bytesread, NULL); + if (result != FMOD_OK || bytesread != SNES_SPC::signature_size || + memcmp(spcfile, SNES_SPC::signature, SNES_SPC::signature_size) != 0) { - Printf (PRINT_BOLD, "Could not create music stream.\n"); - delete file; - return; + result = FMOD_ERR_FORMAT; + // If the length is right and this is just a different version, try + // to pretend it's the current one. + if (bytesread == SNES_SPC::signature_size) + { + memcpy(spcfile + 28, SNES_SPC::signature + 28, 7); + if (memcmp(spcfile, SNES_SPC::signature, SNES_SPC::signature_size) == 0) + { + result = FMOD_OK; + } + } } + if (result != FMOD_OK) + { + return result; + } + // Load the rest of the file. + result = codec->fileread(codec->filehandle, spcfile + SNES_SPC::signature_size, + SNES_SPC::spc_file_size - SNES_SPC::signature_size, &bytesread, NULL); + if (result != FMOD_OK || bytesread < SNES_SPC::spc_min_file_size - SNES_SPC::signature_size) + { + return FMOD_ERR_FILE_BAD; + } + bytesread += SNES_SPC::signature_size; - // Search for amplification tag in extended ID666 info - if (len > 66056) + // Create the SPC. + spc = (userdata != NULL) ? userdata->SPC : new SNES_SPC; + spc->init(); + if (spc->load_spc(spcfile, bytesread) != NULL) + { + if (userdata != NULL) + { + delete spc; + } + return FMOD_ERR_FILE_BAD; + } + spc->clear_echo(); + + // Create the filter. + filter = (userdata != NULL) ? userdata->Filter : new SPC_Filter; + filter->set_gain(int(SPC_Filter::gain_unit * spc_amp)); + + // Search for amplification tag in extended ID666 info. + if (codec->filesize > SNES_SPC::spc_file_size + 8 && bytesread == SNES_SPC::spc_file_size) { DWORD id; - file->Read (&id, 4); - if (id == MAKE_ID('x','i','d','6')) + result = codec->fileread(codec->filehandle, &id, 4, &bytesread, NULL); + if (result == FMOD_OK && bytesread == 4 && id == MAKE_ID('x','i','d','6')) { DWORD size; - (*file) >> size; - DWORD pos = 66056; - - while (pos < size) + result = codec->fileread(codec->filehandle, &size, 4, &bytesread, NULL); + if (result == FMOD_OK && bytesread == 4) { - XID6Tag tag; - - file->Read (&tag, 4); - if (tag.Type == 0) + size = LittleLong(size); + DWORD pos = SNES_SPC::spc_file_size + 8; + + while (pos < size) { - // Don't care about these - } - else - { - if (pos + LittleShort(tag.Value) <= size) + XID6Tag tag; + + result = codec->fileread(codec->filehandle, &tag, 4, &bytesread, NULL); + if (result != FMOD_OK || bytesread != 4) { - if (tag.Type == 4 && tag.ID == 0x36) + break; + } + if (tag.Type == 0) + { + // Don't care about these + } + else + { + if ((pos += LittleShort(tag.Value)) <= size) + { + if (tag.Type == 4 && tag.ID == 0x36) + { + DWORD amp; + result = codec->fileread(codec->filehandle, &, 4, &bytesread, NULL); + if (result == FMOD_OK && bytesread == 4) + { + filter->set_gain(LittleLong(amp) >> 8); + } + break; + } + } + if (FMOD_OK != codec->fileseek(codec->filehandle, pos, NULL)) { - DWORD amp; - (*file) >> amp; - Filter->set_gain(amp >> 8); break; } } - file->Seek (LittleShort(tag.Value), SEEK_CUR); } } } } - delete file; -} - -SPCSong::~SPCSong () -{ - Stop(); - delete Filter; - delete SPC; -} - -bool SPCSong::IsValid () const -{ - return SPC != NULL; -} - -bool SPCSong::IsPlaying () -{ - return m_Status == STATE_Playing; -} - -void SPCSong::Play (bool looping) -{ - m_Status = STATE_Stopped; - m_Looping = true; - - if (m_Stream->Play (true, snd_musicvolume, false)) + if (userdata == NULL) { - m_Status = STATE_Playing; + userdata = new SPCCodecData; + userdata->SPC = spc; + userdata->Filter = filter; + memset(&userdata->WaveFormat, 0, sizeof(userdata->WaveFormat)); + userdata->WaveFormat.format = FMOD_SOUND_FORMAT_PCM16; + userdata->WaveFormat.channels = 2; + userdata->WaveFormat.frequency = SNES_SPC::sample_rate; + userdata->WaveFormat.lengthbytes = SNES_SPC::spc_file_size; + userdata->WaveFormat.lengthpcm = ~0u; + userdata->WaveFormat.blockalign = 4; + codec->numsubsounds = 0; + codec->waveformat = &userdata->WaveFormat; + codec->plugindata = userdata; } + userdata->bPlayed = false; + return FMOD_OK; } -bool SPCSong::FillStream (SoundStream *stream, void *buff, int len, void *userdata) +//========================================================================== +// +// SPC_Open +// +//========================================================================== + +static FMOD_RESULT F_CALLBACK SPC_Open(FMOD_CODEC_STATE *codec, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo) { - SPCSong *song = (SPCSong *)userdata; - song->SPC->play(len >> 1, (short *)buff); - song->Filter->run((short *)buff, len >> 1); - return true; + return SPC_DoOpen(codec, NULL); } -#endif +//========================================================================== +// +// SPC_Close +// +//========================================================================== + +static FMOD_RESULT F_CALLBACK SPC_Close(FMOD_CODEC_STATE *codec) +{ + SPCCodecData *data = (SPCCodecData *)codec->plugindata; + if (data != NULL) + { + delete data->Filter; + delete data->SPC; + delete data; + } + return FMOD_OK; +} + +//========================================================================== +// +// SPC_Read +// +//========================================================================== + +static FMOD_RESULT F_CALLBACK SPC_Read(FMOD_CODEC_STATE *codec, void *buffer, unsigned int size, unsigned int *read) +{ + SPCCodecData *data = (SPCCodecData *)codec->plugindata; + if (read != NULL) + { + *read = size; + } + data->bPlayed = true; + if (data->SPC->play(size >> 1, (short *)buffer) != NULL) + { + return FMOD_ERR_PLUGIN; + } + data->Filter->run((short *)buffer, size >> 1); + return FMOD_OK; +} + +static FMOD_RESULT F_CALLBACK SPC_SetPosition(FMOD_CODEC_STATE *codec, int subsound, unsigned int position, FMOD_TIMEUNIT postype) +{ + SPCCodecData *data = (SPCCodecData *)codec->plugindata; + FMOD_RESULT result; + + if (data->bPlayed) + { // Must reload the file after it's already played something. + result = SPC_DoOpen(codec, data); + if (result != FMOD_OK) + { + return result; + } + } + if (position != 0) + { + if (postype == FMOD_TIMEUNIT_PCM) + { + data->SPC->skip(position * 4); + } + else if (postype == FMOD_TIMEUNIT_MS) + { + data->SPC->skip(Scale(position, SNES_SPC::sample_rate, 1000) * 4); + } + else + { + return FMOD_ERR_UNSUPPORTED; + } + } + return FMOD_OK; +}