mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-15 00:42:06 +00:00
4b8f303ca9
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5686 fc73d0e0-1445-4013-8a0c-d673dee63da5
1263 lines
30 KiB
C
1263 lines
30 KiB
C
/*
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
// snd_mem.c: sound caching
|
|
|
|
#include "quakedef.h"
|
|
|
|
#include "winquake.h"
|
|
#include "fs.h"
|
|
|
|
typedef struct
|
|
{
|
|
int format;
|
|
int rate;
|
|
int bitwidth;
|
|
int numchannels;
|
|
int loopstart;
|
|
int samples;
|
|
int dataofs; // chunk starts this many bytes from file start
|
|
} wavinfo_t;
|
|
|
|
static wavinfo_t GetWavinfo (char *name, qbyte *wav, int wavlength);
|
|
|
|
int cache_full_cycle;
|
|
|
|
qbyte *S_Alloc (int size);
|
|
|
|
#define LINEARUPSCALE(in, inrate, insamps, out, outrate, outlshift, outrshift) \
|
|
{ \
|
|
scale = inrate / (double)outrate; \
|
|
infrac = floor(scale * 65536); \
|
|
outsamps = insamps / scale; \
|
|
inaccum = 0; \
|
|
outnlsamps = floor(1.0 / scale); \
|
|
outsamps -= outnlsamps; \
|
|
\
|
|
while (outsamps) \
|
|
{ \
|
|
*out = ((0xFFFF - inaccum)*in[0] + inaccum*in[1]) >> (16 - outlshift + outrshift); \
|
|
inaccum += infrac; \
|
|
in += (inaccum >> 16); \
|
|
inaccum &= 0xFFFF; \
|
|
out++; \
|
|
outsamps--; \
|
|
} \
|
|
while (outnlsamps) \
|
|
{ \
|
|
*out = (*in >> outrshift) << outlshift; \
|
|
out++; \
|
|
outnlsamps--; \
|
|
} \
|
|
}
|
|
|
|
#define LINEARUPSCALESTEREO(in, inrate, insamps, out, outrate, outlshift, outrshift) \
|
|
{ \
|
|
scale = inrate / (double)outrate; \
|
|
infrac = floor(scale * 65536); \
|
|
outsamps = insamps / scale; \
|
|
inaccum = 0; \
|
|
outnlsamps = floor(1.0 / scale); \
|
|
outsamps -= outnlsamps; \
|
|
\
|
|
while (outsamps) \
|
|
{ \
|
|
out[0] = ((0xFFFF - inaccum)*in[0] + inaccum*in[2]) >> (16 - outlshift + outrshift); \
|
|
out[1] = ((0xFFFF - inaccum)*in[1] + inaccum*in[3]) >> (16 - outlshift + outrshift); \
|
|
inaccum += infrac; \
|
|
in += (inaccum >> 16) * 2; \
|
|
inaccum &= 0xFFFF; \
|
|
out += 2; \
|
|
outsamps--; \
|
|
} \
|
|
while (outnlsamps) \
|
|
{ \
|
|
out[0] = (in[0] >> outrshift) << outlshift; \
|
|
out[1] = (in[1] >> outrshift) << outlshift; \
|
|
out += 2; \
|
|
outnlsamps--; \
|
|
} \
|
|
}
|
|
|
|
#define LINEARUPSCALESTEREOTOMONO(in, inrate, insamps, out, outrate, outlshift, outrshift) \
|
|
{ \
|
|
scale = inrate / (double)outrate; \
|
|
infrac = floor(scale * 65536); \
|
|
outsamps = insamps / scale; \
|
|
inaccum = 0; \
|
|
outnlsamps = floor(1.0 / scale); \
|
|
outsamps -= outnlsamps; \
|
|
\
|
|
while (outsamps) \
|
|
{ \
|
|
*out = ((((0xFFFF - inaccum)*in[0] + inaccum*in[2]) >> (16 - outlshift + outrshift)) + \
|
|
(((0xFFFF - inaccum)*in[1] + inaccum*in[3]) >> (16 - outlshift + outrshift))) >> 1; \
|
|
inaccum += infrac; \
|
|
in += (inaccum >> 16) * 2; \
|
|
inaccum &= 0xFFFF; \
|
|
out++; \
|
|
outsamps--; \
|
|
} \
|
|
while (outnlsamps) \
|
|
{ \
|
|
out[0] = (((in[0] >> outrshift) << outlshift) + ((in[1] >> outrshift) << outlshift)) >> 1; \
|
|
out++; \
|
|
outnlsamps--; \
|
|
} \
|
|
}
|
|
|
|
#define LINEARDOWNSCALE(in, inrate, insamps, out, outrate, outlshift, outrshift) \
|
|
{ \
|
|
scale = outrate / (double)inrate; \
|
|
infrac = floor(scale * 65536); \
|
|
inaccum = 0; \
|
|
insamps--; \
|
|
outsampleft = 0; \
|
|
\
|
|
while (insamps) \
|
|
{ \
|
|
inaccum += infrac; \
|
|
if (inaccum >> 16) \
|
|
{ \
|
|
inaccum &= 0xFFFF; \
|
|
outsampleft += (infrac - inaccum) * (*in); \
|
|
*out = outsampleft >> (16 - outlshift + outrshift); \
|
|
out++; \
|
|
outsampleft = inaccum * (*in); \
|
|
} \
|
|
else \
|
|
outsampleft += infrac * (*in); \
|
|
in++; \
|
|
insamps--; \
|
|
} \
|
|
outsampleft += (0xFFFF - inaccum) * (*in);\
|
|
*out = outsampleft >> (16 - outlshift + outrshift); \
|
|
}
|
|
|
|
#define LINEARDOWNSCALESTEREO(in, inrate, insamps, out, outrate, outlshift, outrshift) \
|
|
{ \
|
|
scale = outrate / (double)inrate; \
|
|
infrac = floor(scale * 65536); \
|
|
inaccum = 0; \
|
|
insamps--; \
|
|
outsampleft = 0; \
|
|
outsampright = 0; \
|
|
\
|
|
while (insamps) \
|
|
{ \
|
|
inaccum += infrac; \
|
|
if (inaccum >> 16) \
|
|
{ \
|
|
inaccum &= 0xFFFF; \
|
|
outsampleft += (infrac - inaccum) * in[0]; \
|
|
outsampright += (infrac - inaccum) * in[1]; \
|
|
out[0] = outsampleft >> (16 - outlshift + outrshift); \
|
|
out[1] = outsampright >> (16 - outlshift + outrshift); \
|
|
out += 2; \
|
|
outsampleft = inaccum * in[0]; \
|
|
outsampright = inaccum * in[1]; \
|
|
} \
|
|
else \
|
|
{ \
|
|
outsampleft += infrac * in[0]; \
|
|
outsampright += infrac * in[1]; \
|
|
} \
|
|
in += 2; \
|
|
insamps--; \
|
|
} \
|
|
outsampleft += (0xFFFF - inaccum) * in[0];\
|
|
outsampright += (0xFFFF - inaccum) * in[1];\
|
|
out[0] = outsampleft >> (16 - outlshift + outrshift); \
|
|
out[1] = outsampright >> (16 - outlshift + outrshift); \
|
|
}
|
|
|
|
#define LINEARDOWNSCALESTEREOTOMONO(in, inrate, insamps, out, outrate, outlshift, outrshift) \
|
|
{ \
|
|
scale = outrate / (double)inrate; \
|
|
infrac = floor(scale * 65536); \
|
|
inaccum = 0; \
|
|
insamps--; \
|
|
outsampleft = 0; \
|
|
\
|
|
while (insamps) \
|
|
{ \
|
|
inaccum += infrac; \
|
|
if (inaccum >> 16) \
|
|
{ \
|
|
inaccum &= 0xFFFF; \
|
|
outsampleft += (infrac - inaccum) * ((in[0] + in[1]) >> 1); \
|
|
*out = outsampleft >> (16 - outlshift + outrshift); \
|
|
out++; \
|
|
outsampleft = inaccum * ((in[0] + in[1]) >> 1); \
|
|
} \
|
|
else \
|
|
outsampleft += infrac * ((in[0] + in[1]) >> 1); \
|
|
in += 2; \
|
|
insamps--; \
|
|
} \
|
|
outsampleft += (0xFFFF - inaccum) * ((in[0] + in[1]) >> 1);\
|
|
*out = outsampleft >> (16 - outlshift + outrshift); \
|
|
}
|
|
|
|
#define STANDARDRESCALE(in, inrate, insamps, out, outrate, outlshift, outrshift) \
|
|
{ \
|
|
scale = inrate / (double)outrate; \
|
|
infrac = floor(scale * 65536); \
|
|
outsamps = insamps / scale; \
|
|
inaccum = 0; \
|
|
\
|
|
while (outsamps) \
|
|
{ \
|
|
*out = (*in >> outrshift) << outlshift; \
|
|
inaccum += infrac; \
|
|
in += (inaccum >> 16); \
|
|
inaccum &= 0xFFFF; \
|
|
out++; \
|
|
outsamps--; \
|
|
} \
|
|
}
|
|
|
|
#define STANDARDRESCALESTEREO(in, inrate, insamps, out, outrate, outlshift, outrshift) \
|
|
{ \
|
|
scale = inrate / (double)outrate; \
|
|
infrac = floor(scale * 65536); \
|
|
outsamps = insamps / scale; \
|
|
inaccum = 0; \
|
|
\
|
|
while (outsamps) \
|
|
{ \
|
|
out[0] = (in[0] >> outrshift) << outlshift; \
|
|
out[1] = (in[1] >> outrshift) << outlshift; \
|
|
inaccum += infrac; \
|
|
in += (inaccum >> 16) * 2; \
|
|
inaccum &= 0xFFFF; \
|
|
out += 2; \
|
|
outsamps--; \
|
|
} \
|
|
}
|
|
|
|
#define STANDARDRESCALESTEREOTOMONO(in, inrate, insamps, out, outrate, outlshift, outrshift) \
|
|
{ \
|
|
scale = inrate / (double)outrate; \
|
|
infrac = floor(scale * 65536); \
|
|
outsamps = insamps / scale; \
|
|
inaccum = 0; \
|
|
\
|
|
while (outsamps) \
|
|
{ \
|
|
out[0] = (((in[0] >> outrshift) << outlshift) + ((in[1] >> outrshift) << outlshift)) >> 1; \
|
|
inaccum += infrac; \
|
|
in += (inaccum >> 16) * 2; \
|
|
inaccum &= 0xFFFF; \
|
|
out++; \
|
|
outsamps--; \
|
|
} \
|
|
}
|
|
|
|
#define QUICKCONVERT(in, insamps, out, outlshift, outrshift) \
|
|
{ \
|
|
while (insamps) \
|
|
{ \
|
|
*out = (*in >> outrshift) << outlshift; \
|
|
out++; \
|
|
in++; \
|
|
insamps--; \
|
|
} \
|
|
}
|
|
|
|
#define QUICKCONVERTSTEREOTOMONO(in, insamps, out, outlshift, outrshift) \
|
|
{ \
|
|
while (insamps) \
|
|
{ \
|
|
*out = (((in[0] >> outrshift) << outlshift) + ((in[1] >> outrshift) << outlshift)) >> 1; \
|
|
out++; \
|
|
in += 2; \
|
|
insamps--; \
|
|
} \
|
|
}
|
|
|
|
// SND_ResampleStream: takes a sound stream and converts with given parameters. Limited to
|
|
// 8-16-bit signed conversions and mono-to-mono/stereo-to-stereo conversions.
|
|
// Not an in-place algorithm.
|
|
void SND_ResampleStream (void *in, int inrate, qaudiofmt_t informat, int inchannels, int insamps, void *out, int outrate, qaudiofmt_t outformat, int outchannels, int resampstyle)
|
|
{
|
|
double scale;
|
|
signed char *in8 = (signed char *)in;
|
|
short *in16 = (short *)in;
|
|
signed char *out8 = (signed char *)out;
|
|
short *out16 = (short *)out;
|
|
int outsamps, outnlsamps, outsampleft, outsampright;
|
|
int infrac, inaccum;
|
|
|
|
if (insamps <= 0)
|
|
return;
|
|
|
|
if (inchannels == outchannels && informat == outformat && inrate == outrate)
|
|
{
|
|
memcpy(out, in, informat*insamps*inchannels);
|
|
return;
|
|
}
|
|
|
|
if (inchannels == 1 && outchannels == 1)
|
|
{
|
|
if (informat == QAF_S8)
|
|
{
|
|
if (outformat == QAF_S8)
|
|
{
|
|
if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALE(in8, inrate, insamps, out8, outrate, 0, 0)
|
|
else
|
|
STANDARDRESCALE(in8, inrate, insamps, out8, outrate, 0, 0)
|
|
}
|
|
else // downsample
|
|
{
|
|
if (resampstyle > 1)
|
|
LINEARDOWNSCALE(in8, inrate, insamps, out8, outrate, 0, 0)
|
|
else
|
|
STANDARDRESCALE(in8, inrate, insamps, out8, outrate, 0, 0)
|
|
}
|
|
return;
|
|
}
|
|
else if (outformat == QAF_S16)
|
|
{
|
|
if (inrate == outrate) // quick convert
|
|
QUICKCONVERT(in8, insamps, out16, 8, 0)
|
|
else if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALE(in8, inrate, insamps, out16, outrate, 8, 0)
|
|
else
|
|
STANDARDRESCALE(in8, inrate, insamps, out16, outrate, 8, 0)
|
|
}
|
|
else // downsample
|
|
{
|
|
if (resampstyle > 1)
|
|
LINEARDOWNSCALE(in8, inrate, insamps, out16, outrate, 8, 0)
|
|
else
|
|
STANDARDRESCALE(in8, inrate, insamps, out16, outrate, 8, 0)
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
else if (informat == QAF_S16) // 16-bit
|
|
{
|
|
if (outformat == QAF_S16)
|
|
{
|
|
if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALE(in16, inrate, insamps, out16, outrate, 0, 0)
|
|
else
|
|
STANDARDRESCALE(in16, inrate, insamps, out16, outrate, 0, 0)
|
|
}
|
|
else // downsample
|
|
{
|
|
if (resampstyle > 1)
|
|
LINEARDOWNSCALE(in16, inrate, insamps, out16, outrate, 0, 0)
|
|
else
|
|
STANDARDRESCALE(in16, inrate, insamps, out16, outrate, 0, 0)
|
|
}
|
|
return;
|
|
}
|
|
else if (outformat == QAF_S8)
|
|
{
|
|
if (inrate == outrate) // quick convert
|
|
QUICKCONVERT(in16, insamps, out8, 0, 8)
|
|
else if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALE(in16, inrate, insamps, out8, outrate, 0, 8)
|
|
else
|
|
STANDARDRESCALE(in16, inrate, insamps, out8, outrate, 0, 8)
|
|
}
|
|
else // downsample
|
|
{
|
|
if (resampstyle > 1)
|
|
LINEARDOWNSCALE(in16, inrate, insamps, out8, outrate, 0, 8)
|
|
else
|
|
STANDARDRESCALE(in16, inrate, insamps, out8, outrate, 0, 8)
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else if (outchannels == 2 && inchannels == 2)
|
|
{
|
|
if (informat == QAF_S8)
|
|
{
|
|
if (outformat == QAF_S8)
|
|
{
|
|
if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0)
|
|
else
|
|
STANDARDRESCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0)
|
|
}
|
|
else // downsample
|
|
{
|
|
if (resampstyle > 1)
|
|
LINEARDOWNSCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0)
|
|
else
|
|
STANDARDRESCALESTEREO(in8, inrate, insamps, out8, outrate, 0, 0)
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (inrate == outrate) // quick convert
|
|
{
|
|
insamps *= 2;
|
|
QUICKCONVERT(in8, insamps, out16, 8, 0)
|
|
}
|
|
else if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0)
|
|
else
|
|
STANDARDRESCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0)
|
|
}
|
|
else // downsample
|
|
{
|
|
if (resampstyle > 1)
|
|
LINEARDOWNSCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0)
|
|
else
|
|
STANDARDRESCALESTEREO(in8, inrate, insamps, out16, outrate, 8, 0)
|
|
}
|
|
}
|
|
}
|
|
else if (informat == QAF_S16) // 16-bit
|
|
{
|
|
if (outformat == QAF_S16)
|
|
{
|
|
if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0)
|
|
else
|
|
STANDARDRESCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0)
|
|
}
|
|
else // downsample
|
|
{
|
|
if (resampstyle > 1)
|
|
LINEARDOWNSCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0)
|
|
else
|
|
STANDARDRESCALESTEREO(in16, inrate, insamps, out16, outrate, 0, 0)
|
|
}
|
|
}
|
|
else if (outformat == QAF_S8)
|
|
{
|
|
if (inrate == outrate) // quick convert
|
|
{
|
|
insamps *= 2;
|
|
QUICKCONVERT(in16, insamps, out8, 0, 8)
|
|
}
|
|
else if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8)
|
|
else
|
|
STANDARDRESCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8)
|
|
}
|
|
else // downsample
|
|
{
|
|
if (resampstyle > 1)
|
|
LINEARDOWNSCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8)
|
|
else
|
|
STANDARDRESCALESTEREO(in16, inrate, insamps, out8, outrate, 0, 8)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#if 0
|
|
else if (outchannels == 1 && inchannels == 2)
|
|
{
|
|
if (informat == QAF_S8)
|
|
{
|
|
if (outformat == QAF_S8)
|
|
{
|
|
if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALESTEREOTOMONO(in8, inrate, insamps, out8, outrate, 0, 0)
|
|
else
|
|
STANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out8, outrate, 0, 0)
|
|
}
|
|
else // downsample
|
|
STANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out8, outrate, 0, 0)
|
|
}
|
|
else if (outformat == QAF_S16)
|
|
{
|
|
if (inrate == outrate) // quick convert
|
|
QUICKCONVERTSTEREOTOMONO(in8, insamps, out16, 8, 0)
|
|
else if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALESTEREOTOMONO(in8, inrate, insamps, out16, outrate, 8, 0)
|
|
else
|
|
STANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out16, outrate, 8, 0)
|
|
}
|
|
else // downsample
|
|
STANDARDRESCALESTEREOTOMONO(in8, inrate, insamps, out16, outrate, 8, 0)
|
|
}
|
|
}
|
|
else if (informat == QAF_S16) // 16-bit
|
|
{
|
|
if (outformat == QAF_S16)
|
|
{
|
|
if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALESTEREOTOMONO(in16, inrate, insamps, out16, outrate, 0, 0)
|
|
else
|
|
STANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out16, outrate, 0, 0)
|
|
}
|
|
else // downsample
|
|
STANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out16, outrate, 0, 0)
|
|
}
|
|
else if (outformat == QAF_S8)
|
|
{
|
|
if (inrate == outrate) // quick convert
|
|
QUICKCONVERTSTEREOTOMONO(in16, insamps, out8, 0, 8)
|
|
else if (inrate < outrate) // upsample
|
|
{
|
|
if (resampstyle)
|
|
LINEARUPSCALESTEREOTOMONO(in16, inrate, insamps, out8, outrate, 0, 8)
|
|
else
|
|
STANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out8, outrate, 0, 8)
|
|
}
|
|
else // downsample
|
|
STANDARDRESCALESTEREOTOMONO(in16, inrate, insamps, out8, outrate, 0, 8)
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
ResampleSfx
|
|
================
|
|
*/
|
|
static qboolean ResampleSfx (sfx_t *sfx, int inrate, int inchannels, qaudiofmt_t informat, int insamps, int inloopstart, qbyte *data)
|
|
{
|
|
extern cvar_t snd_linearresample;
|
|
extern cvar_t snd_loadasstereo;
|
|
double scale;
|
|
sfxcache_t *sc;
|
|
int outsamps;
|
|
int len;
|
|
qaudiofmt_t outformat;
|
|
|
|
scale = snd_speed / (double)inrate;
|
|
outsamps = insamps * scale;
|
|
if (loadas8bit.ival < 0)
|
|
outformat = QAF_S16;
|
|
else if (loadas8bit.ival)
|
|
outformat = QAF_S8;
|
|
else
|
|
outformat = informat;
|
|
len = outsamps * QAF_BYTES(outformat) * inchannels;
|
|
|
|
sfx->decoder.buf = sc = BZ_Malloc(sizeof(sfxcache_t) + len);
|
|
if (!sc)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
sc->numchannels = inchannels;
|
|
sc->format = outformat;
|
|
sc->speed = snd_speed;
|
|
sc->length = outsamps;
|
|
sc->soundoffset = 0;
|
|
sc->data = (qbyte*)(sc+1);
|
|
if (inloopstart == -1)
|
|
sfx->loopstart = inloopstart;
|
|
else
|
|
sfx->loopstart = inloopstart * scale;
|
|
|
|
SND_ResampleStream (data,
|
|
inrate,
|
|
informat,
|
|
inchannels,
|
|
insamps,
|
|
sc->data,
|
|
sc->speed,
|
|
sc->format,
|
|
sc->numchannels,
|
|
snd_linearresample.ival);
|
|
|
|
if (inchannels == 1 && snd_loadasstereo.ival)
|
|
{ //I'm implementing this to work around what looks like a firefox bug, where mono buffers don't get played (but stereo works just fine despite all the spacialisation issues associated with that).
|
|
sfxcache_t *nc = sfx->decoder.buf = BZ_Malloc(sizeof(sfxcache_t) + len*2);
|
|
*nc = *sc;
|
|
nc->data = (qbyte*)(nc+1);
|
|
SND_ResampleStream (sc->data,
|
|
sc->speed,
|
|
sc->format,
|
|
sc->numchannels,
|
|
outsamps,
|
|
nc->data,
|
|
nc->speed*2,
|
|
nc->format,
|
|
nc->numchannels,
|
|
false);
|
|
nc->numchannels *= 2;
|
|
BZ_Free(sc);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//=============================================================================
|
|
#ifdef PACKAGE_DOOMWAD
|
|
#define DSPK_RATE 140
|
|
#define DSPK_BASE 170.0
|
|
#define DSPK_EXP 0.0433
|
|
|
|
/*
|
|
qboolean QDECL S_LoadDoomSpeakerSound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed, qboolean forcedecode)
|
|
{
|
|
sfxcache_t *sc;
|
|
|
|
// format data from Unofficial Doom Specs v1.6
|
|
unsigned short *dataus;
|
|
int samples, len, inrate, inaccum;
|
|
qbyte *outdata;
|
|
qbyte towrite;
|
|
double timeraccum, timerfreq;
|
|
|
|
if (datalen < 4)
|
|
return NULL;
|
|
|
|
dataus = (unsigned short*)data;
|
|
|
|
if (LittleShort(dataus[0]) != 0)
|
|
return NULL;
|
|
|
|
samples = LittleShort(dataus[1]);
|
|
|
|
data += 4;
|
|
datalen -= 4;
|
|
|
|
if (datalen != samples)
|
|
return NULL;
|
|
|
|
len = (int)((double)samples * (double)snd_speed / DSPK_RATE);
|
|
|
|
sc = Cache_Alloc (&s->cache, len + sizeof(sfxcache_t), s->name);
|
|
if (!sc)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
sc->length = len;
|
|
s->loopstart = -1;
|
|
sc->numchannels = 1;
|
|
sc->width = 1;
|
|
sc->speed = snd_speed;
|
|
|
|
timeraccum = 0;
|
|
outdata = sc->data;
|
|
towrite = 0x40;
|
|
inrate = (int)((double)snd_speed / DSPK_RATE);
|
|
inaccum = inrate;
|
|
if (*data)
|
|
timerfreq = DSPK_BASE * pow((double)2.0, DSPK_EXP * (*data));
|
|
else
|
|
timerfreq = 0;
|
|
|
|
while (len > 0)
|
|
{
|
|
timeraccum += timerfreq;
|
|
if (timeraccum > (float)snd_speed)
|
|
{
|
|
towrite ^= 0xFF; // swap speaker component
|
|
timeraccum -= (float)snd_speed;
|
|
}
|
|
|
|
inaccum--;
|
|
if (!inaccum)
|
|
{
|
|
data++;
|
|
if (*data)
|
|
timerfreq = DSPK_BASE * pow((double)2.0, DSPK_EXP * (*data));
|
|
inaccum = inrate;
|
|
}
|
|
*outdata = towrite;
|
|
outdata++;
|
|
len--;
|
|
}
|
|
|
|
return sc;
|
|
}
|
|
*/
|
|
static qboolean QDECL S_LoadDoomSound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed, qboolean forcedecode)
|
|
{
|
|
// format data from Unofficial Doom Specs v1.6
|
|
unsigned short *dataus;
|
|
int samples, rate;
|
|
|
|
if (datalen < 8)
|
|
return false;
|
|
|
|
dataus = (unsigned short*)data;
|
|
|
|
if (LittleShort(dataus[0]) != 3)
|
|
return false;
|
|
|
|
rate = LittleShort(dataus[1]);
|
|
samples = LittleShort(dataus[2]);
|
|
|
|
data += 8;
|
|
datalen -= 8;
|
|
|
|
if (datalen != samples)
|
|
return false;
|
|
|
|
COM_CharBias(data, datalen);
|
|
|
|
return ResampleSfx (s, rate, 1, 1, samples, -1, data);
|
|
}
|
|
#endif
|
|
|
|
static qboolean QDECL S_LoadWavSound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed, qboolean forcedecode)
|
|
{
|
|
wavinfo_t info;
|
|
qaudiofmt_t format;
|
|
|
|
if (datalen < 4 || strncmp(data, "RIFF", 4))
|
|
return false;
|
|
|
|
info = GetWavinfo (s->name, data, datalen);
|
|
if (info.numchannels < 1 || info.numchannels > 2)
|
|
{
|
|
s->loadstate = SLS_FAILED;
|
|
Con_Printf ("%s has an unsupported quantity of channels.\n",s->name);
|
|
return false;
|
|
}
|
|
|
|
if (info.format == 1 && info.bitwidth == 8) //unsigned bytes
|
|
{
|
|
COM_CharBias(data + info.dataofs, info.samples*info.numchannels);
|
|
format = QAF_S8;
|
|
}
|
|
else if (info.format == 1 && info.bitwidth == 16) //signed shorts
|
|
{
|
|
COM_SwapLittleShortBlock((short *)(data + info.dataofs), info.samples*info.numchannels);
|
|
format = QAF_S16;
|
|
}
|
|
else if (info.format == 1 && info.bitwidth == 32) //24 or 32bit int audio
|
|
{
|
|
short *out = (short *)(data + info.dataofs);
|
|
int *in = (int *)(data + info.dataofs);
|
|
size_t samples = info.samples*info.numchannels;
|
|
while(samples --> 0)
|
|
{ //in place size conversion, so we need to do it forwards.
|
|
*out++ = LittleLong(*in++)>>16; //just drop the least significant bits.
|
|
}
|
|
format = QAF_S16;
|
|
}
|
|
#ifdef MIXER_F32
|
|
else if (info.format == 3 && info.bitwidth == 32) //signed floats
|
|
{
|
|
if (bigendian)
|
|
{
|
|
size_t i = info.samples*info.numchannels;
|
|
float *ptr = (float*)(data + info.dataofs);
|
|
while(i --> 0)
|
|
ptr[i] = LittleFloat(ptr[i]);
|
|
}
|
|
format = QAF_F32;
|
|
}
|
|
#else
|
|
else if (info.format == 3 && info.bitwidth == 4) //signed floats
|
|
{
|
|
short *out = (short *)(data + info.dataofs);
|
|
float *in = (float *)(data + info.dataofs);
|
|
size_t samples = info.samples*info.numchannels;
|
|
int t;
|
|
while(samples --> 0)
|
|
{ //in place size conversion, so we need to do it forwards.
|
|
t = LittleFloat(*in++) * 32767;
|
|
t = bound(-32768, t, 32767);
|
|
*out++ = t;
|
|
}
|
|
format = QAF_S16;
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
s->loadstate = SLS_FAILED;
|
|
switch(info.format)
|
|
{
|
|
case 1/*WAVE_FORMAT_PCM*/:
|
|
case 3/*WAVE_FORMAT_IEEE_FLOAT*/: Con_Printf ("%s has an unsupported width (%i bits).\n", s->name, info.bitwidth); break;
|
|
case 6/*WAVE_FORMAT_ALAW*/: Con_Printf ("%s uses unsupported a-law format.\n", s->name); break;
|
|
case 7/*WAVE_FORMAT_MULAW*/: Con_Printf ("%s uses unsupported mu-law format.\n", s->name); break;
|
|
case 0xfffe/*WAVE_FORMAT_EXTENSIBLE*/:
|
|
default: Con_Printf ("%s has an unsupported format (%#x).\n", s->name, info.format); break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return ResampleSfx (s, info.rate, info.numchannels, format, info.samples, info.loopstart, data + info.dataofs);
|
|
}
|
|
|
|
qboolean QDECL S_LoadOVSound (sfx_t *s, qbyte *data, size_t datalen, int sndspeed, qboolean forcedecode);
|
|
|
|
#ifdef FTE_TARGET_WEB
|
|
//web browsers contain their own decoding libraries that our openal stuff can use.
|
|
static qboolean QDECL S_LoadBrowserFile (sfx_t *s, qbyte *data, size_t datalen, int sndspeed, qboolean forcedecode)
|
|
{
|
|
sfxcache_t *sc;
|
|
s->decoder.buf = sc = BZ_Malloc(sizeof(sfxcache_t) + datalen);
|
|
s->loopstart = -1;
|
|
sc->data = (qbyte*)(sc+1);
|
|
sc->length = datalen;
|
|
sc->format = QAF_BLOB; //ie: not pcm
|
|
sc->speed = sndspeed;
|
|
sc->numchannels = 2;
|
|
sc->soundoffset = 0;
|
|
memcpy(sc->data, data, datalen);
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
//highest priority is last.
|
|
static struct
|
|
{
|
|
S_LoadSound_t loadfunc;
|
|
void *module;
|
|
} AudioInputPlugins[10] =
|
|
{
|
|
#ifdef FTE_TARGET_WEB
|
|
{S_LoadBrowserFile},
|
|
#endif
|
|
#ifdef AVAIL_OGGVORBIS
|
|
{S_LoadOVSound},
|
|
#endif
|
|
{S_LoadWavSound},
|
|
#ifdef PACKAGE_DOOMWAD
|
|
{S_LoadDoomSound},
|
|
// {S_LoadDoomSpeakerSound},
|
|
#endif
|
|
};
|
|
|
|
qboolean S_RegisterSoundInputPlugin(void *module, S_LoadSound_t loadfnc)
|
|
{
|
|
int i;
|
|
for (i = 0; i < sizeof(AudioInputPlugins)/sizeof(AudioInputPlugins[0]); i++)
|
|
{
|
|
if (!AudioInputPlugins[i].loadfunc)
|
|
{
|
|
AudioInputPlugins[i].module = module;
|
|
AudioInputPlugins[i].loadfunc = loadfnc;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
void S_UnregisterSoundInputModule(void *module)
|
|
{ //unregister all sound handlers for the given module.
|
|
int i;
|
|
for (i = 0; i < sizeof(AudioInputPlugins)/sizeof(AudioInputPlugins[0]); i++)
|
|
{
|
|
if (AudioInputPlugins[i].module == module)
|
|
{
|
|
AudioInputPlugins[i].module = NULL;
|
|
AudioInputPlugins[i].loadfunc = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void S_LoadedOrFailed (void *ctx, void *ctxdata, size_t a, size_t b)
|
|
{
|
|
sfx_t *s = ctx;
|
|
s->loadstate = a;
|
|
}
|
|
/*
|
|
==============
|
|
S_LoadSound
|
|
==============
|
|
*/
|
|
|
|
static void S_LoadSoundWorker (void *ctx, void *ctxdata, size_t forcedecode, size_t b)
|
|
{
|
|
sfx_t *s = ctx;
|
|
char namebuffer[256];
|
|
qbyte *data;
|
|
int i;
|
|
size_t result;
|
|
char *name = s->name;
|
|
size_t filesize;
|
|
|
|
s->loopstart = -1;
|
|
|
|
if (s->syspath)
|
|
{
|
|
vfsfile_t *f;
|
|
|
|
if ((f = VFSOS_Open(name, "rb")))
|
|
{
|
|
filesize = VFS_GETLEN(f);
|
|
data = BZ_Malloc (filesize);
|
|
result = VFS_READ(f, data, filesize);
|
|
|
|
if (result != filesize)
|
|
Con_SafePrintf("S_LoadSound() fread: Filename: %s, expected %"PRIuSIZE", result was %"PRIuSIZE"\n", name, filesize, result);
|
|
|
|
VFS_CLOSE(f);
|
|
}
|
|
else
|
|
{
|
|
Con_SafePrintf ("Couldn't load %s\n", namebuffer);
|
|
COM_AddWork(WG_MAIN, S_LoadedOrFailed, s, NULL, SLS_FAILED, 0);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Con_Printf ("S_LoadSound: %x\n", (int)stackbuf);
|
|
// load it in
|
|
const char *prefixes[] = {"sound/", ""};
|
|
const char *extensions[] = {
|
|
".wav",
|
|
#ifdef AVAIL_OGGOPUS
|
|
".opus",
|
|
#endif
|
|
#ifdef AVAIL_OGGVORBIS
|
|
".ogg",
|
|
#endif
|
|
};
|
|
char altname[sizeof(namebuffer)];
|
|
char orig[16];
|
|
size_t pre, ex;
|
|
|
|
data = NULL;
|
|
filesize = 0;
|
|
if (*name == '*') //q2 sexed sounds
|
|
{
|
|
//clq2_parsestartsound detects this also, and should not try playing these sounds.
|
|
s->loadstate = SLS_FAILED;
|
|
return;
|
|
}
|
|
|
|
for (pre = 0; !data && pre < countof(prefixes); pre++)
|
|
{
|
|
if (name[0] == '.' && name[1] == '.' && name[2] == '/')
|
|
{ //someone's being specific. disable prefixes entirely.
|
|
if (pre)
|
|
break;
|
|
//not relative to sound/
|
|
Q_snprintfz(namebuffer, sizeof(namebuffer), "%s", name+3);
|
|
}
|
|
else
|
|
Q_snprintfz(namebuffer, sizeof(namebuffer), "%s%s", prefixes[pre], name);
|
|
|
|
data = FS_LoadMallocFile(namebuffer, &filesize);
|
|
if (data)
|
|
break;
|
|
COM_FileExtension(namebuffer, orig, sizeof(orig));
|
|
COM_StripExtension(namebuffer, altname, sizeof(altname));
|
|
for (ex = 0; ex < countof(extensions); ex++)
|
|
{
|
|
if (!strcmp(orig, extensions[ex]+1))
|
|
continue;
|
|
Q_snprintfz(namebuffer, sizeof(namebuffer), "%s%s", altname, extensions[ex]);
|
|
data = FS_LoadMallocFile(namebuffer, &filesize);
|
|
if (data)
|
|
{
|
|
static float throttletimer;
|
|
Con_ThrottlePrintf(&throttletimer, 1, "S_LoadSound: %s%s requested, but could only find %s\n", prefixes[pre], name, namebuffer);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data)
|
|
Validation_FileLoaded(name, data, filesize);
|
|
}
|
|
|
|
if (!data)
|
|
{
|
|
//FIXME: check to see if queued for download.
|
|
if (name[0] == '.' && name[1] == '.' && name[2] == '/')
|
|
Con_DPrintf ("Couldn't load %s\n", name+3);
|
|
else
|
|
Con_DPrintf ("Couldn't load sound/%s\n", name);
|
|
COM_AddWork(WG_MAIN, S_LoadedOrFailed, s, NULL, SLS_FAILED, 0);
|
|
return;
|
|
}
|
|
|
|
for (i = sizeof(AudioInputPlugins)/sizeof(AudioInputPlugins[0])-1; i >= 0; i--)
|
|
{
|
|
if (AudioInputPlugins[i].loadfunc)
|
|
{
|
|
if (AudioInputPlugins[i].loadfunc(s, data, filesize, snd_speed, forcedecode))
|
|
{
|
|
//wake up the main thread in case it decided to wait for us.
|
|
COM_AddWork(WG_MAIN, S_LoadedOrFailed, s, NULL, SLS_LOADED, 0);
|
|
BZ_Free(data);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (s->loadstate != SLS_FAILED)
|
|
Con_Printf ("Format not recognised: %s\n", namebuffer);
|
|
|
|
COM_AddWork(WG_MAIN, S_LoadedOrFailed, s, NULL, SLS_FAILED, 0);
|
|
BZ_Free(data);
|
|
return;
|
|
}
|
|
|
|
qboolean S_LoadSound (sfx_t *s, qboolean force)
|
|
{
|
|
if (s->loadstate == SLS_NOTLOADED && sndcardinfo)
|
|
{
|
|
s->loadstate = SLS_LOADING;
|
|
COM_AddWork(WG_LOADER, S_LoadSoundWorker, s, NULL, force, 0);
|
|
}
|
|
if (s->loadstate == SLS_FAILED)
|
|
return false; //it failed to load once before, don't bother trying again.
|
|
|
|
return true; //loaded okay, or still loading
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
WAV loading
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
typedef struct
|
|
{
|
|
char *wavname;
|
|
qbyte *data_p;
|
|
qbyte *iff_end;
|
|
qbyte *last_chunk;
|
|
qbyte *iff_data;
|
|
int iff_chunk_len;
|
|
} wavctx_t;
|
|
|
|
static short GetLittleShort(wavctx_t *ctx)
|
|
{
|
|
short val = 0;
|
|
val = *ctx->data_p;
|
|
val = val + (*(ctx->data_p+1)<<8);
|
|
ctx->data_p += 2;
|
|
return val;
|
|
}
|
|
|
|
static int GetLittleLong(wavctx_t *ctx)
|
|
{
|
|
int val = 0;
|
|
val = *ctx->data_p;
|
|
val = val + (*(ctx->data_p+1)<<8);
|
|
val = val + (*(ctx->data_p+2)<<16);
|
|
val = val + (*(ctx->data_p+3)<<24);
|
|
ctx->data_p += 4;
|
|
return val;
|
|
}
|
|
|
|
static unsigned int FindNextChunk(wavctx_t *ctx, char *name)
|
|
{
|
|
unsigned int dataleft;
|
|
|
|
while (1)
|
|
{
|
|
dataleft = ctx->iff_end - ctx->last_chunk;
|
|
if (dataleft < 8)
|
|
{ // didn't find the chunk
|
|
ctx->data_p = NULL;
|
|
return 0;
|
|
}
|
|
|
|
ctx->data_p=ctx->last_chunk;
|
|
ctx->data_p += 4;
|
|
dataleft-= 8;
|
|
ctx->iff_chunk_len = GetLittleLong(ctx);
|
|
if (ctx->iff_chunk_len < 0)
|
|
{
|
|
ctx->data_p = NULL;
|
|
return 0;
|
|
}
|
|
if (ctx->iff_chunk_len > dataleft)
|
|
{
|
|
Con_DPrintf ("\"%s\" seems truncated by %i bytes\n", ctx->wavname, ctx->iff_chunk_len-dataleft);
|
|
#if 1
|
|
ctx->iff_chunk_len = dataleft;
|
|
#else
|
|
ctx->data_p = NULL;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
dataleft-= ctx->iff_chunk_len;
|
|
// if (iff_chunk_len > 1024*1024)
|
|
// Sys_Error ("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len);
|
|
ctx->data_p -= 8;
|
|
ctx->last_chunk = ctx->data_p + 8 + ctx->iff_chunk_len;
|
|
if ((ctx->iff_chunk_len&1) && dataleft)
|
|
ctx->last_chunk++;
|
|
if (!Q_strncmp(ctx->data_p, name, 4))
|
|
return ctx->iff_chunk_len;
|
|
}
|
|
}
|
|
|
|
static unsigned int FindChunk(wavctx_t *ctx, char *name)
|
|
{
|
|
ctx->last_chunk = ctx->iff_data;
|
|
return FindNextChunk (ctx, name);
|
|
}
|
|
|
|
|
|
#if 0
|
|
static void DumpChunks(void)
|
|
{
|
|
char str[5];
|
|
|
|
str[4] = 0;
|
|
data_p=iff_data;
|
|
do
|
|
{
|
|
memcpy (str, data_p, 4);
|
|
data_p += 4;
|
|
iff_chunk_len = GetLittleLong();
|
|
Con_Printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len);
|
|
data_p += (iff_chunk_len + 1) & ~1;
|
|
} while (data_p < iff_end);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
============
|
|
GetWavinfo
|
|
============
|
|
*/
|
|
static wavinfo_t GetWavinfo (char *name, qbyte *wav, int wavlength)
|
|
{
|
|
extern cvar_t snd_ignorecueloops;
|
|
wavinfo_t info;
|
|
int i;
|
|
int samples;
|
|
int chunklen;
|
|
wavctx_t ctx;
|
|
|
|
memset (&info, 0, sizeof(info));
|
|
|
|
if (!wav)
|
|
return info;
|
|
|
|
ctx.data_p = NULL;
|
|
ctx.last_chunk = NULL;
|
|
ctx.iff_chunk_len = 0;
|
|
|
|
ctx.iff_data = wav;
|
|
ctx.iff_end = wav + wavlength;
|
|
ctx.wavname = name;
|
|
|
|
// find "RIFF" chunk
|
|
chunklen = FindChunk(&ctx, "RIFF");
|
|
if (chunklen < 4 || Q_strncmp(ctx.data_p+8, "WAVE", 4))
|
|
{
|
|
Con_Printf("Missing RIFF/WAVE chunks in %s\n", name);
|
|
return info;
|
|
}
|
|
|
|
// get "fmt " chunk
|
|
ctx.iff_data = ctx.data_p + 12;
|
|
// DumpChunks ();
|
|
|
|
chunklen = FindChunk(&ctx, "fmt ");
|
|
if (chunklen < 24-8)
|
|
{
|
|
Con_Printf("Missing/truncated fmt chunk\n");
|
|
return info;
|
|
}
|
|
ctx.data_p += 8;
|
|
info.format = GetLittleShort(&ctx);
|
|
|
|
info.numchannels = GetLittleShort(&ctx);
|
|
info.rate = GetLittleLong(&ctx);
|
|
ctx.data_p += 4+2;
|
|
info.bitwidth = GetLittleShort(&ctx);
|
|
|
|
// get cue chunk
|
|
chunklen = FindChunk(&ctx, "cue ");
|
|
if (chunklen >= 36-8 && !snd_ignorecueloops.ival)
|
|
{
|
|
ctx.data_p += 32;
|
|
info.loopstart = GetLittleLong(&ctx);
|
|
// Con_Printf("loopstart=%d\n", sfx->loopstart);
|
|
|
|
// if the next chunk is a LIST chunk, look for a cue length marker
|
|
chunklen = FindNextChunk (&ctx, "LIST");
|
|
if (chunklen >= 32-8)
|
|
{
|
|
if (!strncmp (ctx.data_p + 28, "mark", 4))
|
|
{ // this is not a proper parse, but it works with cooledit...
|
|
ctx.data_p += 24;
|
|
i = GetLittleLong (&ctx); // samples in loop
|
|
info.samples = info.loopstart + i;
|
|
// Con_Printf("looped length: %i\n", i);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
info.loopstart = -1;
|
|
|
|
// find data chunk
|
|
chunklen = FindChunk(&ctx, "data");
|
|
if (!ctx.data_p)
|
|
{
|
|
Con_Printf("Missing data chunk in %s\n", name);
|
|
return info;
|
|
}
|
|
|
|
ctx.data_p += 8;
|
|
samples = (chunklen<<3) / info.bitwidth / info.numchannels;
|
|
|
|
if (info.samples)
|
|
{
|
|
if (samples < info.samples)
|
|
{
|
|
info.samples = samples;
|
|
Con_Printf ("Sound %s has a bad loop length\n", name);
|
|
}
|
|
}
|
|
else
|
|
info.samples = samples;
|
|
|
|
if (info.loopstart > info.samples)
|
|
{
|
|
Con_Printf ("Sound %s has a bad loop start\n", name);
|
|
info.loopstart = info.samples;
|
|
}
|
|
|
|
info.dataofs = ctx.data_p - wav;
|
|
|
|
return info;
|
|
}
|