mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-23 04:22:34 +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)
|
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
|
||||||
|
|
|
@ -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] );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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, &, 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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue