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

View file

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

View file

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

View file

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

View file

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

View file

@ -530,7 +530,7 @@ protected:
struct FCanvasTextureInfo
{
FCanvasTextureInfo *Next;
AActor *Viewpoint;
TObjPtr<AActor> 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;

View file

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

View file

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

View file

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

View file

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

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 "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, &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;
}
}
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;
}