mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-11-15 16:51:31 +00:00
- 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:
parent
b8304868ec
commit
ce811319a7
12 changed files with 378 additions and 179 deletions
|
@ -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
|
||||
|
|
|
@ -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] );
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue