mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-08 05:51:09 +00:00
ce811319a7
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)
340 lines
10 KiB
C++
340 lines
10 KiB
C++
/*
|
|
** 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 "templates.h"
|
|
#include "c_cvars.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
|
|
{
|
|
BYTE ID;
|
|
BYTE Type;
|
|
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)
|
|
|
|
// 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)
|
|
{
|
|
return sys->createCodec(&SPCCodecDescription);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// SPC_Open
|
|
//
|
|
//==========================================================================
|
|
|
|
static FMOD_RESULT SPC_DoOpen(FMOD_CODEC_STATE *codec, SPCCodecData *userdata)
|
|
{
|
|
FMOD_RESULT result;
|
|
char spcfile[SNES_SPC::spc_file_size];
|
|
unsigned int bytesread;
|
|
SNES_SPC *spc;
|
|
SPC_Filter *filter;
|
|
|
|
if (codec->filesize < 66048)
|
|
{ // Anything smaller than this is definitely not a (valid) SPC.
|
|
return FMOD_ERR_FORMAT;
|
|
}
|
|
result = codec->fileseek(codec->filehandle, 0, 0);
|
|
if (result != FMOD_OK)
|
|
{
|
|
return result;
|
|
}
|
|
// 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)
|
|
{
|
|
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;
|
|
|
|
// 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;
|
|
|
|
result = codec->fileread(codec->filehandle, &id, 4, &bytesread, NULL);
|
|
if (result == FMOD_OK && bytesread == 4 && id == MAKE_ID('x','i','d','6'))
|
|
{
|
|
DWORD size;
|
|
|
|
result = codec->fileread(codec->filehandle, &size, 4, &bytesread, NULL);
|
|
if (result == FMOD_OK && bytesread == 4)
|
|
{
|
|
size = LittleLong(size);
|
|
DWORD pos = SNES_SPC::spc_file_size + 8;
|
|
|
|
while (pos < size)
|
|
{
|
|
XID6Tag tag;
|
|
|
|
result = codec->fileread(codec->filehandle, &tag, 4, &bytesread, NULL);
|
|
if (result != FMOD_OK || bytesread != 4)
|
|
{
|
|
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))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (userdata == NULL)
|
|
{
|
|
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;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// SPC_Open
|
|
//
|
|
//==========================================================================
|
|
|
|
static FMOD_RESULT F_CALLBACK SPC_Open(FMOD_CODEC_STATE *codec, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo)
|
|
{
|
|
return SPC_DoOpen(codec, NULL);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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;
|
|
}
|