- 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)
This commit is contained in:
Randy Heit 2008-03-16 00:54:53 +00:00
parent b8304868ec
commit ce811319a7
12 changed files with 378 additions and 179 deletions

View file

@ -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) March 14, 2008 (Changes by Graf Zahl)
- fixed: A_PainShootSkull was missing a NULL pointer check for the spawn type. - fixed: A_PainShootSkull was missing a NULL pointer check for the spawn type.
- implemented Vavoom's vertex height things (1504, 1505) that can explicitly - implemented Vavoom's vertex height things (1504, 1505) that can explicitly
define slopes for triangular sectors. The height is specified as the thing's define slopes for triangular sectors. The height is specified as the thing's
z-coordinate. 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 March 13, 2008
- Fixed: When returning to a hub map, inventory belonging to the temporary - 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 copies of the players stored in the archive would be destroyed and break

View file

@ -128,7 +128,8 @@ public:
enum { extra_size = SPC_DSP::extra_size }; enum { extra_size = SPC_DSP::extra_size };
enum { signature_size = 35 }; enum { signature_size = 35 };
static char const signature [signature_size + 1];
private: private:
SPC_DSP dsp; SPC_DSP dsp;
@ -247,8 +248,6 @@ private:
uint8_t ipl_rom [0x40]; uint8_t ipl_rom [0x40];
}; };
static char const signature [signature_size + 1];
void save_regs( uint8_t out [reg_count] ); void save_regs( uint8_t out [reg_count] );
}; };

View file

@ -155,7 +155,8 @@ size_t PropagateMark()
assert(obj->IsGray()); assert(obj->IsGray());
obj->Gray2Black(); obj->Gray2Black();
Gray = obj->GCNext; 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 // SweepList
// //
// Runs a limited sweep on a list, returning the location where to resume // 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()); assert(curr->IsDead());
*p = curr->ObjNext; *p = curr->ObjNext;
if (!(curr->ObjectFlags & OF_EuthanizeMe)) if (!(curr->ObjectFlags & OF_EuthanizeMe))
{ // The object must be destroyed before it can be finalized. { // The object must be destroyed before it can be finalized.
assert(!curr->IsKindOf(RUNTIME_CLASS(DThinker))); // 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->Destroy();
} }
curr->ObjectFlags |= OF_Cleanup; curr->ObjectFlags |= OF_Cleanup;
@ -263,6 +270,7 @@ static void MarkRoot()
Mark(screen); Mark(screen);
Mark(StatusBar); Mark(StatusBar);
DThinker::MarkRoots(); DThinker::MarkRoots();
FCanvasTextureInfo::Mark();
Mark(DACSThinker::ActiveThinker); Mark(DACSThinker::ActiveThinker);
for (i = 0; i < BODYQUESIZE; ++i) for (i = 0; i < BODYQUESIZE; ++i)
{ {

View file

@ -54,6 +54,8 @@ bool DThinker::bSerialOverride = false;
void FThinkerList::AddTail(DThinker *thinker) void FThinkerList::AddTail(DThinker *thinker)
{ {
assert(thinker->PrevThinker == NULL && thinker->NextThinker == NULL);
assert(!(thinker->ObjectFlags & OF_EuthanizeMe));
if (Sentinel == NULL) if (Sentinel == NULL)
{ {
Sentinel = new DThinker(DThinker::NO_LINK); Sentinel = new DThinker(DThinker::NO_LINK);
@ -62,7 +64,6 @@ void FThinkerList::AddTail(DThinker *thinker)
Sentinel->PrevThinker = Sentinel; Sentinel->PrevThinker = Sentinel;
GC::WriteBarrier(Sentinel); GC::WriteBarrier(Sentinel);
} }
assert(thinker->PrevThinker == NULL && thinker->NextThinker == NULL);
DThinker *tail = Sentinel->PrevThinker; DThinker *tail = Sentinel->PrevThinker;
assert(tail->NextThinker == Sentinel); assert(tail->NextThinker == Sentinel);
thinker->PrevThinker = tail; 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 // Thinkers with the OF_JustSpawned flag set go in the FreshThinkers
// list. Anything else goes in the regular Thinkers list. // 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); FreshThinkers[stat].AddTail(thinker);
} }
@ -367,16 +373,30 @@ void DThinker::DestroyMostThinkersInList (FThinkerList &list, int stat)
DestroyThinkersInList (list); DestroyThinkersInList (list);
} }
else if (list.Sentinel != NULL) else if (list.Sentinel != NULL)
{ // Move all players to an ancillary list to ensure { // If it's a voodoo doll, destroy it. Otherwise, simply remove
// that the collector won't consider them dead. // it from the list. G_FinishTravel() will find it later from
while (list.Sentinel->NextThinker != list.Sentinel) // 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; next = probe->NextThinker;
thinker->Remove(); if (!probe->IsKindOf(RUNTIME_CLASS(APlayerPawn)) || // <- should not happen
Thinkers[MAX_STATNUM+1].AddTail(thinker); static_cast<AActor *>(probe)->player == NULL ||
static_cast<AActor *>(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;
} }
} }

View file

@ -1162,20 +1162,10 @@ FArchive &FArchive::ReadObject (DObject* &obj, PClass *wanttype)
{ {
// When the temporary player's inventory items were loaded, // When the temporary player's inventory items were loaded,
// they became owned by the real player. Undo that now. // they became owned by the real player. Undo that now.
AInventory *item; for (AInventory *item = tempobj->Inventory; item != NULL; item = item->Inventory)
for (item = tempobj->Inventory; item != NULL; item = item->Inventory)
{ {
item->Owner = tempobj; 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 (); tempobj->Destroy ();
} }
else else

View file

@ -2372,6 +2372,7 @@ bool PTR_BounceTraverse (intercept_t *in)
I_Error ("PTR_BounceTraverse: not a line?"); I_Error ("PTR_BounceTraverse: not a line?");
li = in->d.line; li = in->d.line;
assert(((size_t)li - (size_t)lines) % sizeof(line_t) == 0);
if (li->flags & ML_BLOCKEVERYTHING) if (li->flags & ML_BLOCKEVERYTHING)
{ {
goto bounceblocking; goto bounceblocking;
@ -2448,6 +2449,7 @@ bool P_BounceWall (AActor *mo)
leady = mo->y-mo->radius; leady = mo->y-mo->radius;
} }
bestslidefrac = FRACUNIT+1; bestslidefrac = FRACUNIT+1;
bestslideline = NULL;
if (P_PathTraverse(leadx, leady, leadx+mo->momx, leady+mo->momy, if (P_PathTraverse(leadx, leady, leadx+mo->momx, leady+mo->momy,
PT_ADDLINES, PTR_BounceTraverse) && BlockingLine == NULL) PT_ADDLINES, PTR_BounceTraverse) && BlockingLine == NULL)
{ // Could not find a wall, so bounce off the floor/ceiling instead. { // 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); mo->FloorBounceMissile (mo->Sector->ceilingplane);
return true; return true;
} }
/*
else
{
return (mo->flags2 & MF2_BOUNCE2) != 0;
}
*/
} }
line = bestslideline ? bestslideline : BlockingLine; line = bestslideline ? bestslideline : BlockingLine;

View file

@ -530,7 +530,7 @@ protected:
struct FCanvasTextureInfo struct FCanvasTextureInfo
{ {
FCanvasTextureInfo *Next; FCanvasTextureInfo *Next;
AActor *Viewpoint; TObjPtr<AActor> Viewpoint;
FCanvasTexture *Texture; FCanvasTexture *Texture;
int PicNum; int PicNum;
int FOV; int FOV;
@ -539,6 +539,7 @@ struct FCanvasTextureInfo
static void UpdateAll (); static void UpdateAll ();
static void EmptyList (); static void EmptyList ();
static void Serialize (FArchive &arc); static void Serialize (FArchive &arc);
static void Mark();
private: private:
static FCanvasTextureInfo *List; static FCanvasTextureInfo *List;

View file

@ -1653,9 +1653,12 @@ void FCanvasTextureInfo::Add (AActor *viewpoint, int picnum, int fov)
if (probe->Texture == texture) if (probe->Texture == texture)
{ {
// Yes, change its assignment to this new camera // Yes, change its assignment to this new camera
if (probe->Viewpoint != viewpoint || probe->FOV != fov)
{
texture->bFirstUpdate = true;
}
probe->Viewpoint = viewpoint; probe->Viewpoint = viewpoint;
probe->FOV = fov; probe->FOV = fov;
texture->bFirstUpdate = true;
return; 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 // R_MultiresInit

View file

@ -79,6 +79,8 @@ struct FEnumList
// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
FMOD_RESULT SPC_CreateCodec(FMOD::System *sys);
// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
@ -560,6 +562,8 @@ bool FMODSoundRenderer::Init()
result = SfxGroup->addGroup(PausableSfx); result = SfxGroup->addGroup(PausableSfx);
ERRCHECK(result); ERRCHECK(result);
result = SPC_CreateCodec(Sys);
if (snd_3d) if (snd_3d)
{ {
float rolloff_factor; float rolloff_factor;

View file

@ -323,32 +323,6 @@ void *I_RegisterSong (const char *filename, char * musiccache, int offset, int l
info = new TimiditySong (file, musiccache, len); 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 // Check for RDosPlay raw OPL format
else if (id == MAKE_ID('R','A','W','A') && len >= 12) 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. // been identified already, so don't even bother trying to load it.
if (info == NULL && GSnd != NULL && len >= 1024) 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) if (file != NULL)
{ {
fclose (file); fclose (file);

View file

@ -250,27 +250,6 @@ protected:
int m_LastPos; 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 -------- // MIDI file played with Timidity and possibly streamed through FMOD --------
class TimiditySong : public StreamSong class TimiditySong : public StreamSong

View file

@ -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 "templates.h"
#include "c_cvars.h" #include "c_cvars.h"
#include "doomdef.h"
#include "SNES_SPC.h" #include "SNES_SPC.h"
#include "SPC_Filter.h" #include "SPC_Filter.h"
#include "fmod_wrap.h"
#include "doomdef.h"
#include "m_swap.h"
#include "m_fixed.h"
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
struct XID6Tag struct XID6Tag
{ {
@ -14,134 +54,287 @@ struct XID6Tag
WORD Value; 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) EXTERN_CVAR (Int, snd_samplerate)
// PUBLIC DATA DEFINITIONS -------------------------------------------------
CVAR (Float, spc_amp, 1.875f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) 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) return sys->createCodec(&SPCCodecDescription);
{
spc_frequency = 8000;
}
else if (spc_frequency > 32000)
{
spc_frequency = 32000;
}
} }
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) if (codec->filesize < 66048)
{ { // Anything smaller than this is definitely not a (valid) SPC.
file = new FileReader(iofile, len); return FMOD_ERR_FORMAT;
} }
else result = codec->fileseek(codec->filehandle, 0, 0);
if (result != FMOD_OK)
{ {
file = new MemoryReader(musiccache, len); return result;
} }
// Check the signature.
// No sense in using a higher frequency than the final output result = codec->fileread(codec->filehandle, spcfile, SNES_SPC::signature_size, &bytesread, NULL);
int freq = MIN (*spc_frequency, *snd_samplerate); if (result != FMOD_OK || bytesread != SNES_SPC::signature_size ||
memcmp(spcfile, SNES_SPC::signature, SNES_SPC::signature_size) != 0)
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)
{ {
Printf (PRINT_BOLD, "Could not create music stream.\n"); result = FMOD_ERR_FORMAT;
delete file; // If the length is right and this is just a different version, try
return; // 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 // Create the SPC.
if (len > 66056) 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; DWORD id;
file->Read (&id, 4); result = codec->fileread(codec->filehandle, &id, 4, &bytesread, NULL);
if (id == MAKE_ID('x','i','d','6')) if (result == FMOD_OK && bytesread == 4 && id == MAKE_ID('x','i','d','6'))
{ {
DWORD size; DWORD size;
(*file) >> size; result = codec->fileread(codec->filehandle, &size, 4, &bytesread, NULL);
DWORD pos = 66056; if (result == FMOD_OK && bytesread == 4)
while (pos < size)
{ {
XID6Tag tag; size = LittleLong(size);
DWORD pos = SNES_SPC::spc_file_size + 8;
file->Read (&tag, 4);
if (tag.Type == 0) while (pos < size)
{ {
// Don't care about these XID6Tag tag;
}
else result = codec->fileread(codec->filehandle, &tag, 4, &bytesread, NULL);
{ if (result != FMOD_OK || bytesread != 4)
if (pos + LittleShort(tag.Value) <= size)
{ {
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, &amp, 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; break;
} }
} }
file->Seek (LittleShort(tag.Value), SEEK_CUR);
} }
} }
} }
} }
delete file; if (userdata == NULL)
}
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))
{ {
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; return SPC_DoOpen(codec, NULL);
song->SPC->play(len >> 1, (short *)buff);
song->Filter->run((short *)buff, len >> 1);
return true;
} }
#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;
}