mirror of
https://github.com/XProger/OpenLara.git
synced 2025-04-21 11:31:56 +02:00
1241 lines
42 KiB
C++
1241 lines
42 KiB
C++
#ifndef H_SOUND
|
|
#define H_SOUND
|
|
|
|
#define DECODE_ADPCM
|
|
#define DECODE_IMA
|
|
#define DECODE_VAG
|
|
#define DECODE_XA
|
|
|
|
#define DECODE_OGG
|
|
|
|
#if !defined(_OS_PSP) && !defined(_OS_WEB) && !defined(_OS_PSV) && !defined(_OS_3DS)
|
|
#define DECODE_MP3
|
|
#endif
|
|
|
|
#include "utils.h"
|
|
|
|
#ifdef DECODE_MP3
|
|
#include "libs/minimp3/minimp3.h"
|
|
#endif
|
|
|
|
#ifdef DECODE_OGG
|
|
#ifdef USE_LIBVORBIS
|
|
#include <tremor/ivorbisfile.h>
|
|
#else
|
|
#define STB_VORBIS_HEADER_ONLY
|
|
#include "libs/stb_vorbis/stb_vorbis.c"
|
|
#endif
|
|
#endif
|
|
|
|
#define SND_CHANNELS_MAX 128
|
|
#define SND_FADEOFF_DIST (1024.0f * 8.0f)
|
|
#define SND_LOWPASS_FREQ 0.2f
|
|
#define SND_MAX_VOLUME 20
|
|
#define SND_PAN_FACTOR 0.7f
|
|
#define SND_FACING_FACTOR 0.3f
|
|
|
|
namespace Sound {
|
|
|
|
static const int8 SPU_POS[] = { 0, 60, 115, 98, 122 };
|
|
static const int8 SPU_NEG[] = { 0, 0, -52, -55, -60 };
|
|
|
|
static const int16 SPU_ZIG_ZAG[7][30] = {
|
|
{ 0, 0, 0, 0, 0, 0, -0x0002, +0x000A, -0x0022, +0x0041, -0x0054, +0x0034, +0x0009, -0x010A, +0x0400, -0x0A78, +0x234C, +0x6794, -0x1780, +0x0BCD, -0x0623, +0x0350, -0x016D, +0x006B, +0x000A, -0x0010, +0x0011, -0x0008, +0x0003, -0x0001 },
|
|
{ 0, 0, 0, 0, -0x0002, 0, +0x0003, -0x0013, +0x003C, -0x004B, +0x00A2, -0x00E3, +0x0132, -0x0043, -0x0267, +0x0C9D, +0x74BB, -0x11B4, +0x09B8, -0x05BF, +0x0372, -0x01A8, +0x00A6, -0x001B, +0x0005, +0x0006, -0x0008, +0x0003, -0x0001, 0 },
|
|
{ 0, 0, 0, -0x0001, +0x0003, -0x0002, -0x0005, +0x001F, -0x004A, +0x00B3, -0x0192, +0x02B1, -0x039E, +0x04F8, -0x05A6, +0x7939, -0x05A6, +0x04F8, -0x039E, +0x02B1, -0x0192, +0x00B3, -0x004A, +0x001F, -0x0005, -0x0002, +0x0003, -0x0001, 0, 0 },
|
|
{ 0, 0, -0x0001, +0x0003, -0x0008, +0x0006, +0x0005, -0x001B, +0x00A6, -0x01A8, +0x0372, -0x05BF, +0x09B8, -0x11B4, +0x74BB, +0x0C9D, -0x0267, -0x0043, +0x0132, -0x00E3, +0x00A2, -0x004B, +0x003C, -0x0013, +0x0003, 0, -0x0002, 0, 0, 0 },
|
|
{ 0, -0x0001, +0x0003, -0x0008, +0x0011, -0x0010, +0x000A, +0x006B, -0x016D, +0x0350, -0x0623, +0x0BCD, -0x1780, +0x6794, +0x234C, -0x0A78, +0x0400, -0x010A, +0x0009, +0x0034, -0x0054, +0x0041, -0x0022, +0x000A, -0x0001, 0, +0x0001, 0, 0, 0 },
|
|
{ 0, +0x0002, -0x0008, +0x0010, -0x0023, +0x002B, +0x001A, -0x00EB, +0x027B, -0x0548, +0x0AFA, -0x16FA, +0x53E0, +0x3C07, -0x1249, +0x080E, -0x0347, +0x015B, -0x0044, -0x0017, +0x0046, -0x0023, +0x0011, -0x0005, 0, 0, 0, 0, 0, 0 },
|
|
{ 0, -0x0005, +0x0011, -0x0023, +0x0046, -0x0017, -0x0044, +0x015B, -0x0347, +0x080E, -0x1249, +0x3C07, +0x53E0, -0x16FA, +0x0AFA, -0x0548, +0x027B, -0x00EB, +0x001A, +0x002B, -0x0023, +0x0010, -0x0008, +0x0002, 0, 0, 0, 0, 0, 0 },
|
|
};
|
|
|
|
struct Frame {
|
|
int16 L, R;
|
|
};
|
|
|
|
struct FrameHI {
|
|
int32 L, R;
|
|
};
|
|
|
|
struct Stats {
|
|
int mixer;
|
|
int reverb;
|
|
int render[2];
|
|
int ogg;
|
|
} stats;
|
|
|
|
namespace Filter {
|
|
#define MAX_FDN 16
|
|
#define MAX_DELAY 954
|
|
|
|
#define DSP_SCALE_BIT 8
|
|
#define DSP_SCALE (1 << DSP_SCALE_BIT)
|
|
|
|
static const int16 FDN[MAX_FDN] = { 281, 331, 373, 419, 461, 503, 547, 593, 641, 683, 727, 769, 811, 853, 907, 953 };
|
|
|
|
struct Delay {
|
|
int index;
|
|
int16 out[MAX_DELAY];
|
|
|
|
void process(int16 &x, int16 delay) {
|
|
index = (index + 1) % delay;
|
|
int16 y = out[index];
|
|
out[index] = x;
|
|
x = y;
|
|
}
|
|
};
|
|
|
|
struct Absorption {
|
|
int16 out;
|
|
|
|
void process(int16 &x, int32 gain, int32 damping) {
|
|
x = out = (out * damping + ((x * gain * (DSP_SCALE - damping)) >> DSP_SCALE_BIT)) >> DSP_SCALE_BIT;
|
|
}
|
|
};
|
|
|
|
struct LowPass {
|
|
float buffer[2][4];
|
|
|
|
LowPass() {
|
|
memset(buffer, 0, sizeof(buffer));
|
|
}
|
|
|
|
inline void process(int32 &x, float *out, float freq) {
|
|
out[0] += freq * (float(x) - out[0]);
|
|
out[1] += freq * (out[0] - out[1]);
|
|
out[2] += freq * (out[1] - out[2]);
|
|
out[3] += freq * (out[2] - out[3]);
|
|
x = int32(out[3]);
|
|
}
|
|
|
|
void process(FrameHI *frames, int count, float freq) {
|
|
for (int i = 0; i < count; i++) {
|
|
process(frames[i].L, buffer[0], freq);
|
|
process(frames[i].R, buffer[1], freq);
|
|
}
|
|
}
|
|
};
|
|
|
|
struct Reverberation {
|
|
Delay df[MAX_FDN];
|
|
Absorption af[MAX_FDN];
|
|
|
|
int32 output[MAX_FDN];
|
|
int16 buffer[MAX_FDN];
|
|
int16 panCoeff[MAX_FDN][2];
|
|
int32 absCoeff[MAX_FDN][2]; // absorption gain & damping
|
|
|
|
Reverberation() {
|
|
for (int i = 0; i < MAX_FDN; i++) {
|
|
panCoeff[i][0] = (i % 2) ? -1 : 1;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_FDN; i += 2) {
|
|
if ((i / 2) % 2)
|
|
panCoeff[i][1] = panCoeff[i + 1][1] = -1;
|
|
else
|
|
panCoeff[i][1] = panCoeff[i + 1][1] = 1;
|
|
}
|
|
|
|
clear();
|
|
}
|
|
|
|
void clear() {
|
|
memset(output, 0, sizeof(output));
|
|
memset(df, 0, sizeof(df));
|
|
memset(af, 0, sizeof(af));
|
|
|
|
setRoomSize(vec3(1.0f));
|
|
}
|
|
|
|
void setRoomSize(const vec3 &size) {
|
|
float S = (size.x * size.z + size.x * size.y + size.z * size.y) * 2;
|
|
float V = size.x * size.y * size.z;
|
|
float f = 0.1f; // absorption factor
|
|
float rt60 = 0.161f * (V / (S * f));
|
|
float k = -10.0f / (44100.0f * rt60);
|
|
|
|
for (int i = 0; i < MAX_FDN; i++) {
|
|
float v = powf(10.0f, FDN[i] * k);
|
|
absCoeff[i][0] = int32(v * DSP_SCALE);
|
|
absCoeff[i][1] = int32((1.0f - (2.0f / (1.0f + powf(v, 1.0f - 1.0f / 0.15f)))) * DSP_SCALE);
|
|
}
|
|
};
|
|
|
|
void process(FrameHI *frames, int count) {
|
|
PROFILE_CPU_TIMING(stats.reverb);
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
FrameHI &frame = frames[i];
|
|
int32 in = (frame.L + frame.R) / 2;
|
|
int32 out = 0;
|
|
int32 L = 0;
|
|
int32 R = 0;
|
|
|
|
// apply delay & absorption filters
|
|
for (int j = 0; j < MAX_FDN; j++) {
|
|
int16 k = clamp(in + output[j], -0x7FFF, 0x7FFF);
|
|
df[j].process(k, FDN[j]);
|
|
af[j].process(k, absCoeff[j][0], absCoeff[j][1]);
|
|
buffer[j] = k;
|
|
out += k;
|
|
}
|
|
out = out * 2 / MAX_FDN;
|
|
|
|
// apply pan
|
|
int16 buf = buffer[MAX_FDN - 1];
|
|
for (int j = 0; j < MAX_FDN; j++) {
|
|
output[j] = max(0, out - buf);
|
|
buf = buffer[j];
|
|
L += buf ^ panCoeff[j][0];
|
|
R += buf ^ panCoeff[j][1];
|
|
}
|
|
|
|
frame.L += L / MAX_FDN;
|
|
frame.R += R / MAX_FDN;
|
|
}
|
|
}
|
|
};
|
|
|
|
#undef MAX_FDN
|
|
#undef MAX_DELAY
|
|
};
|
|
|
|
struct Decoder {
|
|
Stream *stream;
|
|
int channels, freq, offset;
|
|
Frame prevFrame;
|
|
|
|
Decoder(Stream *stream, int channels, int freq) : stream(stream), channels(channels), freq(freq), offset(stream ? stream->pos : 0) {
|
|
memset(&prevFrame, 0, sizeof(prevFrame));
|
|
}
|
|
|
|
virtual ~Decoder() { delete stream; }
|
|
virtual int decode(Frame *frames, int count) { return 0; }
|
|
virtual void replay() { stream->seek(offset - stream->pos); }
|
|
|
|
int resample(Sound::Frame *frames, Sound::Frame &frame) {
|
|
if (freq == 44100) {
|
|
frames[0] = frame;
|
|
return 1;
|
|
}
|
|
|
|
int dL = int(frame.L) - int(prevFrame.L);
|
|
int dR = int(frame.R) - int(prevFrame.R);
|
|
switch (freq) {
|
|
case 11025 :
|
|
if (channels == 2) {
|
|
frames[0].L = prevFrame.L + dL / 4; // 0.25 L
|
|
frames[0].R = prevFrame.R + dR / 4; // 0.25 R
|
|
frames[1].L = prevFrame.L + dL / 2; // 0.50 L
|
|
frames[1].R = prevFrame.R + dR / 2; // 0.50 R
|
|
frames[2].L = prevFrame.L + dL * 3 / 4; // 0.75 L
|
|
frames[2].R = prevFrame.R + dR * 3 / 4; // 0.75 R
|
|
} else {
|
|
frames[0].L = frames[0].R = prevFrame.L + dL / 4; // 0.25 LR
|
|
frames[1].L = frames[1].R = prevFrame.L + dL / 2; // 0.50 LR
|
|
frames[2].L = frames[2].R = prevFrame.L + dL * 3 / 4; // 0.75 LR
|
|
}
|
|
frames[3] = prevFrame = frame; // 1.00 LR
|
|
return 4;
|
|
case 22050 :
|
|
if (channels == 2) {
|
|
frames[0].L = prevFrame.L + dL / 2; // 0.50 L
|
|
frames[0].R = prevFrame.R + dR / 2; // 0.50 R
|
|
} else
|
|
frames[0].L = frames[0].R = prevFrame.L + dL / 2; // 0.50 LR
|
|
frames[1] = prevFrame = frame; // 1.00 LR
|
|
return 2;
|
|
default : // impossible
|
|
ASSERT(false);
|
|
int k = 44100 / freq;
|
|
for (int i = 0; i < k; i++) frames[i] = frame; // no lerp
|
|
return k;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct PCM : Decoder {
|
|
int size, bits;
|
|
|
|
PCM(Stream *stream, int channels, int freq, int size, int bits) : Decoder(stream, channels, freq), size(size), bits(bits) {}
|
|
|
|
virtual int decode(Frame *frames, int count) {
|
|
if (stream->pos - offset >= size) return 0;
|
|
|
|
// ! in the original game series only 11025 and 22050 Hz single channel samples were used ! //
|
|
|
|
Frame frame;
|
|
if (bits == 16) {
|
|
int16 value;
|
|
if (channels == 2) {
|
|
frame.L = stream->read(value);
|
|
frame.R = stream->read(value);
|
|
} else
|
|
frame.L = frame.R = stream->read(value);
|
|
} else if (bits == 8 || bits == -8) {
|
|
|
|
if (bits > 0) {
|
|
uint8 value;
|
|
if (channels == 2) {
|
|
frame.L = stream->read(value) * 257 - 32768;
|
|
frame.R = stream->read(value) * 257 - 32768;
|
|
} else
|
|
frame.L = frame.R = stream->read(value) * 257 - 32768;
|
|
} else {
|
|
int8 value;
|
|
if (channels == 2) {
|
|
frame.L = (stream->read(value) + 128) * 257 - 32768;
|
|
frame.R = (stream->read(value) + 128) * 257 - 32768;
|
|
} else
|
|
frame.L = frame.R = (stream->read(value) + 128) * 257 - 32768;
|
|
}
|
|
|
|
} else {
|
|
ASSERT(false);
|
|
return 0;
|
|
}
|
|
|
|
return resample(frames, frame);
|
|
}
|
|
};
|
|
|
|
#ifdef DECODE_ADPCM
|
|
struct ADPCM : Decoder { // https://wiki.multimedia.cx/?title=Microsoft_ADPCM
|
|
int size, block;
|
|
|
|
ADPCM(Stream *stream, int channels, int freq, int size, int block) : Decoder(stream, channels, freq), size(size), block(block) {}
|
|
|
|
struct Channel {
|
|
int16 c1, c2;
|
|
int16 delta;
|
|
int16 sample1;
|
|
int16 sample2;
|
|
|
|
int predicate(uint8 nibble) {
|
|
static const int table[] = { 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 };
|
|
|
|
int8 ns = nibble;
|
|
if (ns & 8) ns -= 16;
|
|
|
|
int sample = (sample1 * c1 + sample2 * c2) / 256 + ns * delta;
|
|
sample = clamp(sample, -32768, 32767);
|
|
sample2 = sample1;
|
|
sample1 = sample;
|
|
delta = max(table[nibble] * delta / 256, 16);
|
|
return sample;
|
|
}
|
|
} channel[2];
|
|
|
|
virtual int decode(Frame *frames, int count) {
|
|
static const int coeff1[] = { 256, 512, 0, 192, 240, 460, 392 };
|
|
static const int coeff2[] = { 0, -256, 0, 64, 0, -208, -232 };
|
|
|
|
int seek = stream->pos - offset;
|
|
if (seek >= size) return 0;
|
|
|
|
if (seek % block == 0) {
|
|
for (int i = 0; i < channels; i++) {
|
|
uint8 index;
|
|
stream->read(index);
|
|
channel[i].c1 = coeff1[index];
|
|
channel[i].c2 = coeff2[index];
|
|
}
|
|
for (int i = 0; i < channels; i++) stream->read(channel[i].delta);
|
|
for (int i = 0; i < channels; i++) stream->read(channel[i].sample1);
|
|
for (int i = 0; i < channels; i++) stream->read(channel[i].sample2);
|
|
|
|
if (channels == 1) {
|
|
if (freq == 22050) {
|
|
ASSERT(count >= 4);
|
|
frames[0].L = frames[0].R =
|
|
frames[1].L = frames[1].R = channel[0].sample2;
|
|
frames[2].L = frames[2].R =
|
|
frames[3].L = frames[3].R = channel[0].sample1;
|
|
return 4;
|
|
} else {
|
|
ASSERT(count >= 2);
|
|
frames[0].L = frames[0].R = channel[0].sample2;
|
|
frames[1].L = frames[1].R = channel[0].sample1;
|
|
return 2;
|
|
}
|
|
} else {
|
|
ASSERT(freq == 44100);
|
|
ASSERT(count >= 2);
|
|
frames[0].L = channel[0].sample2;
|
|
frames[0].R = channel[1].sample2;
|
|
frames[1].L = channel[0].sample1;
|
|
frames[1].R = channel[1].sample1;
|
|
return 2;
|
|
}
|
|
} else {
|
|
uint8 value;
|
|
stream->read(value);
|
|
uint8 n1 = value >> 4, n2 = value & 0xF;
|
|
|
|
if (channels == 1) {
|
|
if (freq == 22050) {
|
|
ASSERT(count >= 4);
|
|
frames[0].L = frames[0].R =
|
|
frames[1].L = frames[1].R = channel[0].predicate(n1);
|
|
frames[2].L = frames[2].R =
|
|
frames[3].L = frames[3].R = channel[0].predicate(n2);
|
|
return 4;
|
|
} else {
|
|
ASSERT(count >= 2);
|
|
frames[0].L = frames[0].R = channel[0].predicate(n1);
|
|
frames[1].L = frames[1].R = channel[0].predicate(n2);
|
|
return 2;
|
|
}
|
|
} else {
|
|
ASSERT(freq == 44100);
|
|
frames[0].L = channel[0].predicate(n1);
|
|
frames[0].R = channel[1].predicate(n2);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#ifdef DECODE_IMA
|
|
struct IMA : Decoder { // https://wiki.multimedia.cx/?title=Microsoft_ADPCM
|
|
struct State {
|
|
int amp, idx;
|
|
} state[2];
|
|
|
|
int freq;
|
|
|
|
IMA(Stream *stream, int channels, int freq) : Decoder(stream, channels, freq) {
|
|
memset(state, 0, sizeof(state));
|
|
}
|
|
|
|
int16 getSample(uint8 n, State &state) {
|
|
static int indexLUT[] = {
|
|
-1, -1, -1, -1, 2, 4, 6, 8,
|
|
};
|
|
|
|
static int stepLUT[] = {
|
|
7, 8, 9, 10, 11, 12, 13, 14,
|
|
16, 17, 19, 21, 23, 25, 28, 31,
|
|
34, 37, 41, 45, 50, 55, 60, 66,
|
|
73, 80, 88, 97, 107, 118, 130, 143,
|
|
157, 173, 190, 209, 230, 253, 279, 307,
|
|
337, 371, 408, 449, 494, 544, 598, 658,
|
|
724, 796, 876, 963, 1060, 1166, 1282, 1411,
|
|
1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
|
|
3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
|
|
7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
|
|
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
|
|
32767
|
|
};
|
|
|
|
int step = stepLUT[state.idx];
|
|
int idx = n & 7;
|
|
|
|
state.idx = clamp(state.idx + indexLUT[idx], 0, 88);
|
|
|
|
int diff = (2 * idx + 1) * step >> 3;
|
|
|
|
if (n & 8) {
|
|
state.amp -= diff;
|
|
if (state.amp < -32768)
|
|
state.amp = -32768;
|
|
} else {
|
|
state.amp += diff;
|
|
if (state.amp > 32767)
|
|
state.amp = 32767;
|
|
}
|
|
|
|
return state.amp;
|
|
}
|
|
|
|
virtual int decode(Frame *frames, int count) {
|
|
uint8 n;
|
|
stream->read(n);
|
|
|
|
int a = getSample(n >> 4, state[0]);
|
|
int b = getSample(n & 0x0F, state[1 % channels]);
|
|
|
|
Frame frame;
|
|
if (channels == 2) {
|
|
frame.L = a;
|
|
frame.R = b;
|
|
return resample(frames, frame);
|
|
} else {
|
|
frame.L = frame.R = a;
|
|
int i = resample(frames, frame);
|
|
frame.L = frame.R = b;
|
|
return i + resample(frames + i, frame);
|
|
}
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#ifdef DECODE_VAG
|
|
struct VAG : Decoder {
|
|
uint8 pred, shift, flags;
|
|
int s1, s2;
|
|
Frame buffer[28 * 4];
|
|
int bufferSize;
|
|
|
|
VAG(Stream *stream) : Decoder(stream, 1, 11025), s1(0), s2(0), bufferSize(0) {}
|
|
|
|
void predicate(short value) {
|
|
int s = (s1 * SPU_POS[pred] + s2 * SPU_NEG[pred]) >> 6;
|
|
s = clamp((value >> shift) + s, -32768, 32767);
|
|
s2 = s1;
|
|
s1 = s;
|
|
}
|
|
|
|
void resample(Frame *frames, short value) {
|
|
predicate(value);
|
|
frames[0].L = frames[0].R = s2 + (s1 - s2) / 4; // 0.25
|
|
frames[1].L = frames[1].R = s2 + (s1 - s2) / 2; // 0.50
|
|
frames[2].L = frames[2].R = s2 + (s1 - s2) * 3 / 4; // 0.75
|
|
frames[3].L = frames[3].R = s1; // 1.00
|
|
}
|
|
|
|
int processBlock() {
|
|
if (stream->pos >= stream->size)
|
|
return 0;
|
|
stream->read(pred);
|
|
stream->read(flags);
|
|
shift = pred & 0x0F;
|
|
pred >>= 4;
|
|
|
|
int i = 0;
|
|
while (i < 14) {
|
|
uint8 d;
|
|
stream->read(d);
|
|
resample(&buffer[i * 8 + 0], (d & 0x0F) << 12);
|
|
resample(&buffer[i * 8 + 4], (d & 0xF0) << 8);
|
|
i++;
|
|
}
|
|
return i * 8;
|
|
}
|
|
|
|
virtual int decode(Frame *frames, int count) {
|
|
int res = 0;
|
|
|
|
while (res < count) {
|
|
if (!bufferSize && !(bufferSize = processBlock())) // if no data in buffer - process next block
|
|
break;
|
|
|
|
int length = min(bufferSize, count - res);
|
|
memcpy(&frames[res], buffer, length * sizeof(Frame));
|
|
res += length;
|
|
|
|
if (bufferSize -= length) { // if data remained in buffer, move it to the beginning
|
|
memcpy(buffer, &buffer[sizeof(buffer) / sizeof(Frame) - bufferSize], bufferSize * sizeof(Frame));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
virtual void replay() {
|
|
stream->setPos(0);
|
|
s1 = s2 = 0;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#ifdef DECODE_XA
|
|
// http://problemkaputt.de/psx-spx.htm#cdromxaaudioadpcmcompression
|
|
struct XA : Decoder {
|
|
uint8 pred, shift, flags;
|
|
int s1, s2;
|
|
|
|
Frame buffer[18 * 112];
|
|
int pos;
|
|
|
|
struct Group {
|
|
uint8 params[16];
|
|
uint8 data[112];
|
|
} groups[18];
|
|
|
|
Frame prevFrames[2];
|
|
|
|
Frame lerpFrames[32];
|
|
uint32 lerpPos;
|
|
|
|
XA(Stream *stream) : Decoder(stream, 1, 11025), s1(0), s2(0), pos(COUNT(buffer)), lerpPos(0) {
|
|
memset(prevFrames, 0, sizeof(prevFrames));
|
|
memset(lerpFrames, 0, sizeof(lerpFrames));
|
|
}
|
|
|
|
void decode28(Group &group, int block, int channel) {
|
|
int16 *dst = channel ? &buffer[pos].R : &buffer[pos].L;
|
|
int16 &old = channel ? prevFrames[0].R : prevFrames[0].L;
|
|
int16 &older = channel ? prevFrames[1].R : prevFrames[1].L;
|
|
|
|
int shift = 12 - (group.params[4 + block * 2 + channel] & 0x0F);
|
|
int filter = (group.params[4 + block * 2 + channel] & 0x30) >> 4;
|
|
|
|
int f0 = SPU_POS[filter];
|
|
int f1 = SPU_NEG[filter];
|
|
|
|
for (int i = 0; i < 28; i++) {
|
|
int t = (group.data[block + i * 4] >> (channel * 4)) & 0x0F;
|
|
if (t & 8)
|
|
t -= 16;
|
|
int s = (t << shift) + ((old * f0 + older * f1 + 32) / 64);
|
|
s = clamp(s, -32768, 32767);
|
|
older = old;
|
|
old = s;
|
|
dst[0] = s;
|
|
dst += 2; // skip second channel
|
|
}
|
|
}
|
|
|
|
void processBlock() {
|
|
if (stream->pos >= stream->size)
|
|
return;
|
|
|
|
stream->raw(groups, sizeof(groups));
|
|
|
|
pos = 0;
|
|
|
|
for (int i = 0; i < COUNT(groups); i++)
|
|
for (int j = 0; j < 4; j++) {
|
|
decode28(groups[i], j, 0);
|
|
decode28(groups[i], j, 1);
|
|
pos += 28;
|
|
}
|
|
|
|
pos = 0;
|
|
}
|
|
|
|
void ZigZagOut(Frame &frame, uint8 p, const int16 *LUT) {
|
|
FrameHI sum;
|
|
sum.L = sum.R = 0;
|
|
|
|
for (uint8 i = 1; i < 30; i++) {
|
|
Frame &f = lerpFrames[uint8(p - i) & 0x1F];
|
|
sum.L += f.L * LUT[i];
|
|
sum.R += f.R * LUT[i];
|
|
}
|
|
|
|
frame.L = clamp(sum.L >> 15, -32767, 32767);
|
|
frame.R = clamp(sum.R >> 15, -32767, 32767);
|
|
}
|
|
|
|
virtual int decode(Frame *frames, int count) {
|
|
if (pos >= COUNT(buffer))
|
|
processBlock();
|
|
|
|
ASSERT((int(COUNT(buffer)) - pos) % 6 == 0)
|
|
ASSERT(count % 7 == 0)
|
|
|
|
count = min(count, (int(COUNT(buffer)) - pos) / 6 * 7);
|
|
|
|
int i = 0;
|
|
while (i < count) {
|
|
ASSERT(pos < COUNT(buffer));
|
|
lerpFrames[lerpPos++ & 0x1F] = buffer[pos++];
|
|
lerpFrames[lerpPos++ & 0x1F] = buffer[pos++];
|
|
lerpFrames[lerpPos++ & 0x1F] = buffer[pos++];
|
|
lerpFrames[lerpPos++ & 0x1F] = buffer[pos++];
|
|
lerpFrames[lerpPos++ & 0x1F] = buffer[pos++];
|
|
lerpFrames[lerpPos++ & 0x1F] = buffer[pos++];
|
|
ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[0]);
|
|
ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[1]);
|
|
ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[2]);
|
|
ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[3]);
|
|
ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[4]);
|
|
ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[5]);
|
|
ZigZagOut(frames[i++], lerpPos, SPU_ZIG_ZAG[6]);
|
|
}
|
|
|
|
ASSERT(i == count);
|
|
|
|
return count;
|
|
}
|
|
|
|
virtual void replay() {
|
|
stream->setPos(0);
|
|
s1 = s2 = 0;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#ifdef DECODE_MP3
|
|
struct MP3 : Decoder {
|
|
mp3_decoder_t mp3;
|
|
char *buffer;
|
|
int size, pos;
|
|
|
|
MP3(Stream *stream, int channels) : Decoder(stream, channels, 0), size(stream->size), pos(0) {
|
|
mp3 = mp3_create();
|
|
buffer = new char[size]; // TODO: file streaming
|
|
stream->raw(buffer, size);
|
|
}
|
|
|
|
virtual ~MP3() {
|
|
delete[] buffer;
|
|
mp3_done(mp3);
|
|
}
|
|
|
|
virtual int decode(Frame *frames, int count) {
|
|
mp3_info_t info;
|
|
int i = 0;
|
|
char *ptr = (char*)frames;
|
|
while (ptr < (char*)&frames[count]) {
|
|
int res = mp3_decode(mp3, buffer + pos, size - pos, (short*)ptr, &info);
|
|
if (res) {
|
|
pos += res;
|
|
ptr += info.audio_bytes;
|
|
i += info.audio_bytes;
|
|
} else
|
|
break;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
virtual void replay() {
|
|
mp3_done(mp3);
|
|
mp3 = mp3_create();
|
|
pos = 0;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#ifdef DECODE_OGG
|
|
|
|
#ifdef USE_LIBVORBIS
|
|
struct OGG : Decoder {
|
|
OggVorbis_File vf;
|
|
FILE *memFile;
|
|
uint8 *data;
|
|
|
|
OGG(Stream *stream, int channels) : Decoder(stream, channels, 0) {
|
|
char buf[255];
|
|
strcpy(buf, contentDir);
|
|
strcat(buf, stream->name);
|
|
|
|
data = new uint8[stream->size];
|
|
stream->raw(data, stream->size);
|
|
|
|
memFile = fmemopen(data, stream->size, "rb");
|
|
int err = ov_open(memFile, &vf, NULL, 0);
|
|
ASSERT(err >= 0);
|
|
vorbis_info *info = ov_info(&vf, -1);
|
|
this->channels = info->channels;
|
|
this->freq = info->rate;
|
|
}
|
|
|
|
virtual ~OGG() {
|
|
ov_clear(&vf);
|
|
fclose(memFile);
|
|
delete[] data;
|
|
}
|
|
|
|
virtual int decode(Frame *frames, int count) {
|
|
PROFILE_CPU_TIMING(stats.ogg);
|
|
int i = 0;
|
|
int bytes = count * sizeof(Frame);
|
|
while (i < bytes) {
|
|
int bitstream;
|
|
int res = ov_read(&vf, (char*)frames + i, bytes - i, &bitstream);
|
|
if (!res) break;
|
|
i += res;
|
|
}
|
|
return i / sizeof(Frame);
|
|
}
|
|
|
|
virtual void replay() {
|
|
fseek(memFile, 0, SEEK_SET);
|
|
ov_open(memFile, &vf, NULL, 0);
|
|
}
|
|
};
|
|
#else // stb_vorbis
|
|
struct OGG : Decoder {
|
|
stb_vorbis *ogg;
|
|
stb_vorbis_alloc alloc;
|
|
uint8 *data;
|
|
|
|
OGG(Stream *stream, int channels) : Decoder(stream, channels, 0) {
|
|
char buf[255];
|
|
strcpy(buf, contentDir);
|
|
strcat(buf, stream->name);
|
|
|
|
data = new uint8[stream->size];
|
|
stream->raw(data, stream->size);
|
|
|
|
alloc.alloc_buffer_length_in_bytes = 256 * 1024;
|
|
alloc.alloc_buffer = new char[alloc.alloc_buffer_length_in_bytes];
|
|
ogg = stb_vorbis_open_memory(data, stream->size, NULL, &alloc);
|
|
ASSERT(ogg);
|
|
stb_vorbis_info info = stb_vorbis_get_info(ogg);
|
|
this->channels = info.channels;
|
|
this->freq = info.sample_rate;
|
|
}
|
|
|
|
virtual ~OGG() {
|
|
stb_vorbis_close(ogg);
|
|
delete[] alloc.alloc_buffer;
|
|
delete[] data;
|
|
}
|
|
|
|
virtual int decode(Frame *frames, int count) {
|
|
PROFILE_CPU_TIMING(stats.ogg);
|
|
int i = 0;
|
|
while (i < count) {
|
|
int res = stb_vorbis_get_samples_short_interleaved(ogg, channels, (short*)frames + i, (count - i) * 2);
|
|
if (!res) break;
|
|
i += res;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
virtual void replay() {
|
|
stb_vorbis_seek_start(ogg);
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#endif // DECODE_OGG
|
|
|
|
Core::Mutex lock;
|
|
|
|
struct Listener {
|
|
mat4 matrix;
|
|
bool underwater;
|
|
} listener[2];
|
|
|
|
int listenersCount;
|
|
|
|
Listener& getListener(const vec3 &pos) {
|
|
if (listenersCount == 1 || (listener[0].matrix.getPos() - pos).length2() < (listener[1].matrix.getPos() - pos).length2())
|
|
return listener[0];
|
|
return listener[1];
|
|
}
|
|
|
|
enum Flags {
|
|
LOOP = 1,
|
|
PAN = 2,
|
|
UNIQUE = 4,
|
|
REPLAY = 8,
|
|
MUSIC = 16,
|
|
FLIPPED = 32,
|
|
UNFLIPPED = 64,
|
|
};
|
|
|
|
bool flipped;
|
|
|
|
struct Sample {
|
|
const vec3 *uniquePtr;
|
|
Decoder *decoder;
|
|
vec3 pos;
|
|
float volume;
|
|
float volumeTarget;
|
|
float volumeDelta;
|
|
float pitch;
|
|
int flags;
|
|
int id;
|
|
bool isPlaying;
|
|
bool isPaused;
|
|
bool stopAfterFade;
|
|
|
|
Sample(Decoder *decoder, float volume, float pitch, int flags, int id) : uniquePtr(NULL), decoder(decoder), volume(volume), volumeTarget(volume), volumeDelta(0.0f), pitch(pitch), flags(flags), id(id) {
|
|
isPlaying = decoder != NULL;
|
|
isPaused = false;
|
|
}
|
|
|
|
Sample(Stream *stream, const vec3 *pos, float volume, float pitch, int flags, int id) : uniquePtr(pos), decoder(NULL), volume(volume), volumeTarget(volume), volumeDelta(0.0f), pitch(pitch), flags(flags), id(id) {
|
|
this->pos = pos ? *pos : vec3(0.0f);
|
|
|
|
uint32 fourcc;
|
|
stream->read(fourcc);
|
|
if (fourcc == FOURCC("RIFF")) { // wav
|
|
|
|
struct {
|
|
uint16 format;
|
|
uint16 channels;
|
|
uint32 samplesPerSec;
|
|
uint32 bytesPerSec;
|
|
uint16 block;
|
|
uint16 sampleBits;
|
|
} waveFmt;
|
|
|
|
stream->seek(8);
|
|
while (stream->pos < stream->size) {
|
|
uint32 type, size;
|
|
stream->read(type);
|
|
stream->read(size);
|
|
if (type == FOURCC("fmt ")) {
|
|
stream->raw(&waveFmt, sizeof(waveFmt));
|
|
stream->seek(size - sizeof(waveFmt));
|
|
} else if (type == FOURCC("data")) {
|
|
if (waveFmt.format == 1) decoder = new PCM(stream, waveFmt.channels, waveFmt.samplesPerSec, size, waveFmt.sampleBits);
|
|
#ifdef DECODE_ADPCM
|
|
if (waveFmt.format == 2) decoder = new ADPCM(stream, waveFmt.channels, waveFmt.samplesPerSec, size, waveFmt.block);
|
|
#endif
|
|
break;
|
|
} else
|
|
stream->seek(size);
|
|
}
|
|
}
|
|
else if (fourcc == FOURCC("OggS")) { // ogg
|
|
stream->seek(-4);
|
|
#ifdef DECODE_OGG
|
|
decoder = new OGG(stream, 2);
|
|
#endif
|
|
}
|
|
else if (fourcc == FOURCC("ID3\3")) { // mp3
|
|
#ifdef DECODE_MP3
|
|
decoder = new MP3(stream, 2);
|
|
#endif
|
|
}
|
|
else if (fourcc == FOURCC("SEGA")) { // Sega Saturn PCM mono signed 8-bit 11025 Hz
|
|
decoder = new PCM(stream, 1, 11025, stream->size, -8);
|
|
}
|
|
else { // vag
|
|
stream->setPos(0);
|
|
#ifdef DECODE_VAG
|
|
decoder = new VAG(stream);
|
|
#endif
|
|
}
|
|
|
|
if (!decoder)
|
|
delete stream;
|
|
|
|
isPlaying = decoder != NULL;
|
|
isPaused = false;
|
|
}
|
|
|
|
~Sample() {
|
|
delete decoder;
|
|
}
|
|
|
|
void setVolume(float value, float time) {
|
|
if (value < 0.0f) {
|
|
stopAfterFade = true;
|
|
value = 0.0f;
|
|
} else
|
|
stopAfterFade = false;
|
|
|
|
volumeTarget = value;
|
|
volumeDelta = volumeTarget - volume;
|
|
if (time > 0.0f)
|
|
volumeDelta /= 44100.0f * time;
|
|
}
|
|
|
|
vec2 getPan() {
|
|
if (!(flags & PAN))
|
|
return vec2(1.0f);
|
|
mat4 m = Sound::getListener(pos).matrix;
|
|
vec3 v = pos - m.offset().xyz();
|
|
vec3 n = v.normal();
|
|
|
|
float dist = max(0.0f, 1.0f - (v.length() / SND_FADEOFF_DIST));
|
|
float pan = m.right().xyz().dot(n);
|
|
float facing = (0.5f - m.dir().xyz().dot(n) * 0.5f) * SND_FACING_FACTOR + (1.0f - SND_FACING_FACTOR);
|
|
|
|
vec2 value(min(1.0f, 1.0f - pan),
|
|
min(1.0f, 1.0f + pan));
|
|
|
|
return (value * SND_PAN_FACTOR + (1.0f - SND_PAN_FACTOR)) * facing * dist;
|
|
}
|
|
|
|
bool render(Frame *frames, int count) {
|
|
if (!isPlaying) return false;
|
|
|
|
if (isPaused) {
|
|
memset(frames, 0, sizeof(Frame) * count);
|
|
return true;
|
|
}
|
|
|
|
// decode
|
|
int i = 0;
|
|
while (i < count) {
|
|
int res = decoder->decode(&frames[i], count - i);
|
|
if (res == 0) {
|
|
if (!(flags & LOOP)) {
|
|
isPlaying = false;
|
|
break;
|
|
} else
|
|
decoder->replay();
|
|
}
|
|
i += res;
|
|
}
|
|
// apply volume
|
|
#define VOL_CONV(x) (1.0f - sqrtf(1.0f - x * x));
|
|
|
|
float m = ((flags & MUSIC) ? Core::settings.audio.music : Core::settings.audio.sound) / float(SND_MAX_VOLUME);
|
|
float v = volume * m;
|
|
vec2 pan = getPan();
|
|
vec2 vol = pan * VOL_CONV(v);
|
|
for (int j = 0; j < i; j++) {
|
|
if (volumeDelta != 0.0f) { // increase / decrease channel volume
|
|
volume += volumeDelta;
|
|
if ((volumeDelta < 0.0f && volume < volumeTarget) ||
|
|
(volumeDelta > 0.0f && volume > volumeTarget)) {
|
|
volume = volumeTarget;
|
|
volumeDelta = 0.0f;
|
|
if (stopAfterFade)
|
|
isPlaying = false;
|
|
}
|
|
v = volume * m;
|
|
vol = pan * VOL_CONV(v);
|
|
}
|
|
frames[j].L = int(frames[j].L * vol.x);
|
|
frames[j].R = int(frames[j].R * vol.y);
|
|
}
|
|
#undef VOL_CONV
|
|
|
|
return true;
|
|
}
|
|
|
|
void stop() {
|
|
isPlaying = false;
|
|
}
|
|
|
|
void replay() {
|
|
decoder->replay();
|
|
}
|
|
|
|
void pause() {
|
|
isPaused = true;
|
|
}
|
|
|
|
void resume() {
|
|
isPaused = false;
|
|
}
|
|
} *channels[SND_CHANNELS_MAX];
|
|
int channelsCount;
|
|
|
|
typedef void (Callback)(Sample *channel);
|
|
Callback *callback;
|
|
|
|
FrameHI *result;
|
|
Frame *buffer;
|
|
|
|
// TODO: per listener
|
|
Filter::Reverberation reverb;
|
|
Filter::LowPass lowPass;
|
|
|
|
void init() {
|
|
flipped = false;
|
|
channelsCount = 0;
|
|
callback = NULL;
|
|
buffer = NULL;
|
|
result = NULL;
|
|
#ifdef DECODE_MP3
|
|
mp3_decode_init();
|
|
#endif
|
|
}
|
|
|
|
void deinit() {
|
|
for (int i = 0; i < channelsCount; i++)
|
|
delete channels[i];
|
|
#ifdef DECODE_MP3
|
|
mp3_decode_free();
|
|
#endif
|
|
delete[] buffer;
|
|
delete[] result;
|
|
}
|
|
|
|
void renderChannels(FrameHI *result, int count, bool music) {
|
|
PROFILE_CPU_TIMING(stats.render[music]);
|
|
|
|
int bufSize = count + count / 2 + 4;
|
|
if (!buffer) buffer = new Frame[bufSize]; // + 50% for pitch
|
|
|
|
for (int i = 0; i < channelsCount; i++) {
|
|
if (music != ((channels[i]->flags & MUSIC) != 0))
|
|
continue;
|
|
|
|
if (channels[i]->flags & (FLIPPED | UNFLIPPED)) {
|
|
if (!(channels[i]->flags & (flipped ? FLIPPED : UNFLIPPED)))
|
|
continue;
|
|
|
|
vec3 d = channels[i]->pos - getListener(channels[i]->pos).matrix.getPos();
|
|
if (fabsf(d.x) > SND_FADEOFF_DIST || fabsf(d.y) > SND_FADEOFF_DIST || fabsf(d.z) > SND_FADEOFF_DIST)
|
|
continue;
|
|
}
|
|
|
|
if ((channels[i]->flags & LOOP) && channels[i]->volume < EPS && channels[i]->volumeTarget < EPS)
|
|
continue;
|
|
|
|
memset(buffer, 0, sizeof(Frame) * bufSize);
|
|
channels[i]->render(buffer, (int(count * channels[i]->pitch) + 3) / 4 * 4);
|
|
|
|
if (channels[i]->pitch == 1.0f) { // no pitch
|
|
for (int j = 0; j < count; j++) {
|
|
result[j].L += buffer[j].L;
|
|
result[j].R += buffer[j].R;
|
|
}
|
|
} else { // has pitch (interpolate values for smooth wave)
|
|
float t = 0.0f;
|
|
for (int j = 0; j < count; j++, t += channels[i]->pitch) {
|
|
int idxA = int(t);
|
|
int idxB = (j == (count - 1)) ? idxA : (idxA + 1);
|
|
int st = int((t - idxA) * DSP_SCALE);
|
|
Frame &a = buffer[idxA];
|
|
Frame &b = buffer[idxB];
|
|
|
|
result[j].L += a.L + ((b.L - a.L) * st >> DSP_SCALE_BIT);
|
|
result[j].R += a.R + ((b.R - a.R) * st >> DSP_SCALE_BIT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void convFrames(FrameHI *from, Frame *to, int count) {
|
|
for (int i = 0; i < count; i++) {
|
|
to[i].L = clamp(from[i].L, -32767, 32767);
|
|
to[i].R = clamp(from[i].R, -32767, 32767);
|
|
}
|
|
}
|
|
|
|
void fill(Frame *frames, int count) {
|
|
OS_LOCK(lock);
|
|
PROFILE_CPU_TIMING(stats.mixer);
|
|
|
|
if (!channelsCount) {
|
|
if (result && (Core::settings.audio.music != 0 || Core::settings.audio.sound != 0)) {
|
|
memset(result, 0, sizeof(FrameHI) * count);
|
|
if (Core::settings.audio.reverb)
|
|
reverb.process(result, count);
|
|
convFrames(result, frames, count);
|
|
} else
|
|
memset(frames, 0, sizeof(frames[0]) * count);
|
|
return;
|
|
}
|
|
|
|
if (!result) result = new FrameHI[count];
|
|
memset(result, 0, sizeof(FrameHI) * count);
|
|
|
|
if (Core::settings.audio.sound != 0) {
|
|
renderChannels(result, count, false);
|
|
|
|
if (Core::settings.audio.reverb) {
|
|
if (listener[0].underwater) {
|
|
lowPass.process(result, count, SND_LOWPASS_FREQ);
|
|
}
|
|
reverb.process(result, count);
|
|
}
|
|
}
|
|
|
|
if (Core::settings.audio.music != 0) {
|
|
renderChannels(result, count, true);
|
|
}
|
|
|
|
convFrames(result, frames, count);
|
|
|
|
for (int i = 0; i < channelsCount; i++)
|
|
if (!channels[i]->isPlaying) {
|
|
if (callback) callback(channels[i]);
|
|
delete channels[i];
|
|
channels[i] = channels[--channelsCount];
|
|
i--;
|
|
}
|
|
}
|
|
|
|
Stream *openCDAudioWAD(const char *name, int index = -1) {
|
|
if (!Stream::existsContent(name))
|
|
return NULL;
|
|
|
|
Stream *stream = new Stream(name);
|
|
if (stream->size) {
|
|
struct Item {
|
|
char name[260];
|
|
int size;
|
|
int offset;
|
|
} entity;
|
|
|
|
stream->seek(sizeof(entity) * index);
|
|
stream->raw(&entity, sizeof(entity));
|
|
stream->setPos(entity.offset);
|
|
return stream;
|
|
}
|
|
delete stream;
|
|
return NULL;
|
|
}
|
|
|
|
Stream *openCDAudioMP3(const char *dat, const char *name, int index = -1) {
|
|
if (!Stream::existsContent(dat) || !Stream::existsContent(name))
|
|
return NULL;
|
|
Stream *stream = new Stream(name);
|
|
return stream;
|
|
}
|
|
|
|
Sample* getChannel(int id, const vec3 *pos) {
|
|
for (int i = 0; i < channelsCount; i++)
|
|
if (channels[i]->id == id && channels[i]->uniquePtr == pos)
|
|
return channels[i];
|
|
return NULL;
|
|
}
|
|
|
|
Sample* play(Stream *stream, const vec3 *pos = NULL, float volume = 1.0f, float pitch = 0.0f, int flags = 0, int id = - 1) {
|
|
OS_LOCK(lock);
|
|
|
|
ASSERT(pitch >= 0.0f);
|
|
if (!stream) return NULL;
|
|
if (volume > 0.001f) {
|
|
if (pos && !(flags & (FLIPPED | UNFLIPPED | MUSIC)) && (flags & PAN)) {
|
|
vec3 listenerPos = getListener(*pos).matrix.getPos();
|
|
vec3 d = *pos - listenerPos;
|
|
|
|
if (fabsf(d.x) > SND_FADEOFF_DIST || fabsf(d.y) > SND_FADEOFF_DIST || fabsf(d.z) > SND_FADEOFF_DIST) {
|
|
delete stream;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (flags & (UNIQUE | REPLAY)) {
|
|
Sample *ch = getChannel(id, pos);
|
|
|
|
if (ch) {
|
|
if (pos)
|
|
ch->pos = *pos;
|
|
ch->pitch = pitch;
|
|
|
|
if (flags & REPLAY)
|
|
ch->replay();
|
|
|
|
delete stream;
|
|
return ch;
|
|
}
|
|
}
|
|
|
|
if (channelsCount < SND_CHANNELS_MAX)
|
|
return channels[channelsCount++] = new Sample(stream, pos, volume, pitch, flags, id);
|
|
|
|
LOG("! no free channels\n");
|
|
}
|
|
delete stream;
|
|
return NULL;
|
|
}
|
|
|
|
Sample* play(Decoder *decoder) {
|
|
OS_LOCK(lock);
|
|
|
|
if (channelsCount < SND_CHANNELS_MAX)
|
|
return channels[channelsCount++] = new Sample(decoder, 1.0f, 1.0f, MUSIC, -1);
|
|
return NULL;
|
|
}
|
|
|
|
void stop(int id = -1) {
|
|
OS_LOCK(lock);
|
|
|
|
for (int i = 0; i < channelsCount; i++)
|
|
if (id == -1 || channels[i]->id == id)
|
|
channels[i]->stop();
|
|
}
|
|
|
|
void stopAll() {
|
|
OS_LOCK(lock);
|
|
reverb.clear();
|
|
|
|
for (int i = 0; i < channelsCount; i++)
|
|
delete channels[i];
|
|
channelsCount = 0;
|
|
}
|
|
}
|
|
|
|
#endif
|