scummvm/sword2/driver/d_sound.cpp
Torbjörn Andersson 9729256b2b Added locking to the music code. I'm not really the right person to do this
but at least it doesn't seem to do any harm.

Disabled the sound FX "garbage collection" in FxServer(). I'm not really
convinced it's necessary at all, and even if it is, doing it from a
separate thread it just begging for trouble. I've modified OpenFx()
slightly to deal with this, but I may still have introduced regressions.

Temporarily disabled the "goto label1" hack, since it seems to be the main
reason for ScummVM crashing if I allow a piece of music to finish on its
own (i.e. when not terminating it prematurely by triggering another piece
of music).

svn-id: r9990
2003-09-04 10:58:55 +00:00

1499 lines
38 KiB
C++

/* Copyright (C) 1994-2003 Revolution Software Ltd
*
* 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.
*
* $Header$
*/
//=============================================================================
//
// Filename : d_sound.c
// Created : 3rd December 1996
// By : P.R.Porter
//
// Summary : This module holds the driver interface to direct sound.
//
// Functions
// ---------
//
// --------------------------------------------------------------------------
//
// int32 PlayCompSpeech(const char *filename, uint32 id, uint8 vol, int8 pan)
//
// This function loads, decompresses and plays the wav 'id' from the cluster
// 'filename'. An error occurs if speech is already playing, or directSound
// comes accross problems. 'volume' can be from 0 to 16. 'pan' can be from
// -16 (full left) to 16 (full right).
// id is the text line id used to reference the speech within the speech
// cluster.
//
// --------------------------------------------------------------------------
//
// int32 StopSpeechSword2(void)
//
// Stops the speech from playing.
//
// --------------------------------------------------------------------------
//
// int32 GetSpeechStatus(void)
//
// Returns either RDSE_SAMPLEPLAYING or RDSE_SAMPLEFINISHED
//
// --------------------------------------------------------------------------
//
// int32 AmISpeaking(void)
//
// Returns either RDSE_QUIET or RDSE_SPEAKING
//
// --------------------------------------------------------------------------
//
// int32 PauseSpeech(void)
//
// Stops the speech dead in it's tracks.
//
// --------------------------------------------------------------------------
//
// int32 UnpauseSpeech(void)
//
// Re-starts the speech from where it was stopped.
//
// --------------------------------------------------------------------------
//
// int32 OpenFx(int32 id, uint8 *data)
//
// This function opens a sound effect ready for playing. A unique id should
// be passed in so that each effect can be referenced individually.
//
// WARNING: Zero is not a valid ID.
//
// --------------------------------------------------------------------------
//
// int32 PlayFx(int32 id, uint8 *data, uint8 vol, int8 pan, uint8 type)
//
// This function plays a sound effect. If the effect has already been opened
// then *data should be NULL, and the sound effect will simply be obtained
// from the id passed in. If the effect has not been opened, then the wav
// data should be passed in data. The sound effect will be closed when it
// has finished playing.
//
// The volume can be between 0 (minimum) and 16 (maximum). The pan defines
// the left/right balance of the sample. -16 is full left, and 16 is full
// right with 0 in the middle. The sample type can be either RDSE_FXSPOT, or
// RDSE_FXLOOP.
//
// WARNING: Zero is not a valid ID
//
// --------------------------------------------------------------------------
//
// int32 CloseFx(int32 id)
//
// This function closes a sound effect which has been previously opened for
// playing. Sound effects must be closed when they are finished with,
// otherwise you will run out of sound effect buffers.
//
// --------------------------------------------------------------------------
//
// int32 ClearAllFx(void)
//
// This function clears all of the sound effects which are currently open or
// playing, irrespective of type.
//
// --------------------------------------------------------------------------
//
// int32 StreamCompMusic(uint8 *filename, uint32 id, int32 loopFlag)
//
// Streams music 'id' from the cluster file 'filename'. The loopFlag should
// be set to RDSE_FXLOOP if the music is to loop back to the start.
// Otherwise, it should be RDSE_FXSPOT.
// The return value must be checked for any problems.
//
// --------------------------------------------------------------------------
//
// void StopMusic(void)
//
// Fades out and stops the music.
//
// --------------------------------------------------------------------------
//
// int32 PauseMusic(void)
//
// Stops the music dead in it's tracks.
//
// --------------------------------------------------------------------------
//
// int32 UnpauseMusic(void)
//
// Re-starts the music from where it was stopped.
//
// ---------------------------------------------------------------------------
//
// int32 MusicTimeRemaining(void)
//
// Returns the time left for the current tune.
//
// ----------------------------------------------------------------------------
//
// int32 ReverseStereo(void)
//
// This function reverse the pan table, thus reversing the stereo.
//
//=============================================================================
#include "stdafx.h"
#include "driver96.h"
#include "rdwin.h" // for hwnd.
#include "d_sound.h"
#include "../sword2.h"
#include "common/timer.h"
#include "sound/mixer.h"
// Decompression macros
#define MakeCompressedByte(shift, sign, amplitude) (((shift) << 4) + ((sign) << 3) + (amplitude))
#define GetCompressedShift(byte) ((byte) >> 4)
#define GetCompressedSign(byte) (((byte) >> 3) & 1)
#define GetCompressedAmplitude(byte) ((byte) & 7)
#define GetdAPower(dA, power) for (power = 15; power > 0 && !((dA) & (1 << power)); power--)
int32 panTable[33] = {
-127, -119, -111, -103, -95, -87, -79, -71, -63, -55, -47, -39, -31, -23, -15, -7,
0,
7, 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, 95, 103, 111, 119, 127
};
/*
int32 panTable[33] = {
-10000,
-1500, -1400, -1300, -1200,
-1100, -1000, -900, -800,
-700, -600, -500, -400,
-300, -200, -100, 0,
100, 200, 300, 400,
500, 600, 700, 800,
900, 1000, 1100, 1200,
1300, 1400, 1500, 10000
};
int32 volTable[241] = {
-10000, -3925, -3852, -3781, -3710, -3642, -3574, -3508, -3443, -3379, -3316, -3255, -3194, -3135, -3077, -3020, -2964, -2909, -2855, -2802, -2750, -2699, -2649, -2600, -2551, -2504, -2458, -2412, -2367, -2323, -2280, -2238, -2197, -2156, -2116, -2077, -2038, -2000, -1963, -1927, -1891, -1856, -1821, -1788, -1755, -1722, -1690, -1659, -1628, -1598, -1568, -1539, -1510, -1482, -1455, -1428, -1401, -1375, -1350, -1325,
-1300, -1290, -1279, -1269, -1259, -1249, -1239, -1229, -1219, -1209, -1199, -1190, -1180, -1171, -1161, -1152, -1142, -1133, -1124, -1115, -1106, -1097, -1088, -1080, -1071, -1062, -1054, -1045, -1037, -1028, -1020, -1012, -1004, -996, -988, -980, -972, -964, -956, -949, -941, -933, -926, -918, -911, -904, -896, -889, -882, -875, -868, -861, -854, -847, -840, -833, -827, -820, -813, -807,
-800, -791, -782, -773, -764, -755, -747, -738, -730, -721, -713, -705, -697, -689, -681, -673, -665, -658, -650, -643, -635, -628, -621, -613, -606, -599, -593, -586, -579, -572, -566, -559, -553, -546, -540, -534, -528, -522, -516, -510, -504, -498, -492, -487, -481, -476, -470, -465, -459, -454, -449, -444, -439, -434, -429, -424, -419, -414, -409, -404,
-400, -362, -328, -297, -269, -244, -221, -200, -181, -164, -148, -134, -122, -110, -100, -90, -82, -74, -67, -61, -55, -50, -45, -41, -37, -33, -30, -27, -25, -22, -20, -18, -16, -15, -13, -12, -11, -10, -9, -8, -7, -6, -6, -5, -5, -4, -4, -3, -3, -3, -2, -2, -2, -2, -1, -1, -1, -1, -1, 0
};
*/
int32 musicVolTable[17] = {
0, 15, 31, 47, 63, 79, 95, 111, 127, 143, 159, 175, 191, 207, 223, 239, 255
};
/*
int32 musicVolTable[17] = {
-10000,
-5000, -3000, -2500, -2250,
-2000, -1750, -1500, -1250,
-1000, -750, -500, -350,
-200, -100, -50, 0
};
*/
void sword2_sound_handler (void *engine) {
g_sword2->_sound->FxServer();
}
Sword2Sound::Sword2Sound(SoundMixer *mixer) {
_mutex = g_system->create_mutex();
soundOn = 0;
speechStatus = 0;
fxPaused = 0;
speechPaused = 0;
speechVol = 14;
fxVol = 14;
speechMuted = 0;
fxMuted = 0;
compressedMusic = 0;
volMusic[0] = 16;
volMusic[1] = 16;
musicMuted = 0;
bufferSizeMusic = 4410;
_mixer = mixer;
memset(fxId, 0, sizeof(fxId));
memset(fxiPaused, 0, sizeof(fxiPaused));
memset(fxRate, 0, sizeof(fxRate));
memset(musStreaming, 0, sizeof(musStreaming));
memset(musicPaused, 0, sizeof(musicPaused));
memset(musFading, 0, sizeof(musFading));
memset(musLooping, 0, sizeof(musLooping));
memset(musFilePos, 0, sizeof(musFilePos));
memset(musEnd, 0, sizeof(musEnd));
memset(musLastSample, 0, sizeof(musLastSample));
memset(musId, 0, sizeof(musId));
memset(soundHandleMusic, 0, sizeof(soundHandleMusic));
memset(soundHandleFx, 0, sizeof(soundHandleFx));
soundHandleSpeech = 0;
memset(bufferFx, 0, sizeof(bufferFx));
memset(flagsFx, 0, sizeof(flagsFx));
memset(bufferSizeFx, 0, sizeof(bufferSizeFx));
soundOn = 1;
g_engine->_timer->installProcedure(sword2_sound_handler, 100000);
}
Sword2Sound::~Sword2Sound() {
g_engine->_timer->releaseProcedure(sword2_sound_handler);
if (_mutex)
g_system->delete_mutex(_mutex);
}
// --------------------------------------------------------------------------
// This function reverse the pan table, thus reversing the stereo.
// --------------------------------------------------------------------------
int32 Sword2Sound::ReverseStereo(void) {
int i,j;
for (i = 0; i < 16; i++) {
j = panTable[i];
panTable[i] = panTable[32 - i];
panTable[32 - i] = j;
}
return (RD_OK);
}
// --------------------------------------------------------------------------
// This function returns the index of the sound effect with the ID passed in.
// --------------------------------------------------------------------------
int32 Sword2Sound::GetFxIndex(int32 id) {
int32 i = 0;
while (i < MAXFX) {
if (fxId[i] == id)
break;
i++;
}
return(i);
}
int32 Sword2Sound::IsFxOpen(int32 id) {
int32 i = 0;
while (i < MAXFX) {
if (fxId[i] == id)
break;
i++;
}
if (i == MAXFX)
return 1;
else
return 0;
}
// --------------------------------------------------------------------------
// This function checks the status of all current sound effects, and clears
// out the ones which are no longer required in a buffer. It is called on
// a slow timer from rdwin.c
// --------------------------------------------------------------------------
void Sword2Sound::FxServer(void) {
// FIXME: This function is called from a separate thread, and
// manipulates data structures that are used by several other
// functions throughout the file.
//
// I guess that means we need to add locking and stuff.
//
// Maybe that explains why BS2 still crashes every now and then.
if (!soundOn)
return;
if (musicPaused[0] + musicPaused[1] == 0) {
if (compressedMusic == 1)
UpdateCompSampleStreaming();
}
// FIXME: Doing this sort of things from a separate thread seems like
// just asking for trouble. But removing it outright will cause
// regressions which need to be investigated.
#if 0
int i;
if (fxPaused) {
for (i = 0; i < MAXFX; i++) {
if ((fxId[i] == (int32) 0xfffffffe) || (fxId[i] == (int32) 0xffffffff)) {
if (!soundHandleFx[i]) {
fxId[i] = 0;
if (bufferFx[i] != NULL) {
free(bufferFx[i]);
bufferFx[i] = NULL;
}
bufferSizeFx[i] = 0;
flagsFx[i] = 0;
}
}
}
return;
}
for (i = 0; i < MAXFX; i++) {
if (fxId[i]) {
if (!soundHandleFx[i]) {
fxId[i] = 0;
if (bufferFx[i] != NULL) {
free(bufferFx[i]);
bufferFx[i] = NULL;
}
bufferSizeFx[i] = 0;
flagsFx[i] = 0;
}
}
}
#endif
}
int32 Sword2Sound::AmISpeaking() {
if ((!speechMuted) && (!speechPaused) && (soundHandleSpeech != 0)) {
return (RDSE_SPEAKING);
}
return (RDSE_QUIET);
}
int32 Sword2Sound::GetCompSpeechSize(const char *filename, uint32 speechid) {
int32 i;
uint32 speechIndex[2];
File fp;
// Open the speech cluster and find the data offset & size
fp.open(filename, g_engine->getGameDataPath());
if (fp.isOpen() == false)
return(0);
fp.seek((++speechid) * 8, SEEK_SET);
if (fp.read(speechIndex, sizeof(uint32) * 2) != (sizeof(uint32) * 2)) {
fp.close();
return (0);
}
if (!speechIndex[0] || !speechIndex[1]) {
fp.close();
return (0);
}
fp.close();
i = (speechIndex[1] - 1) * 2 + sizeof(_wavHeader) + 8;
return(i);
}
int32 Sword2Sound::PreFetchCompSpeech(const char *filename, uint32 speechid, uint8 *waveMem) {
uint32 i;
uint16 *data16;
uint8 *data8;
uint32 speechIndex[2];
_wavHeader *pwf = (_wavHeader *) waveMem;
File fp;
// Open the speech cluster and find the data offset & size
fp.open(filename, g_engine->getGameDataPath());
if (fp.isOpen() == false)
return(RDERR_INVALIDFILENAME);
fp.seek((++speechid) * 8, SEEK_SET);
if (fp.read(speechIndex, sizeof(uint32) * 2) != (sizeof(uint32) * 2)) {
fp.close();
return (RDERR_READERROR);
}
if (!speechIndex[0] || !speechIndex[1]) {
fp.close();
return (RDERR_INVALIDID);
}
data16 = (uint16*)(waveMem + sizeof(_wavHeader));
memset(pwf, 0, sizeof(_wavHeader));
*((uint32*)pwf->riff) = 'FFIR';
*((uint32*)pwf->wavID) = 'EVAW';
*((uint32*)pwf->format) = ' tmf';
pwf->formatLen = 0x00000010;
pwf->formatTag = 0x0001;
pwf->channels = 0x0001;
pwf->samplesPerSec = 0x5622;
pwf->avgBytesPerSec = 0x0000;
pwf->blockAlign = 0xAC44;
pwf->unknown1 = 0x0000;
pwf->unknown2 = 0x0002;
pwf->bitsPerSample = 0x0010;
*((uint32*)data16) = 'atad';
data16 += 2;
*((uint32*)data16) = (speechIndex[1] - 1) * 2;
data16 += 2;
pwf->fileLength = (speechIndex[1] - 1) * 2 + sizeof(_wavHeader) + 8;
// Calculate position in buffer to load compressed sound into
data8 = (uint8*)data16 + (speechIndex[1]-1);
fp.seek(speechIndex[0], SEEK_SET);
if (fp.read(data8, speechIndex[1]) != speechIndex[1]) {
fp.close();
return (RDERR_INVALIDID);
}
fp.close();
data16[0] = *((int16*)data8); // Starting Value
i = 1;
while (i < (speechIndex[1] - 1)) {
if (GetCompressedSign(data8[i + 1]))
data16[i] = data16[i - 1] - (GetCompressedAmplitude(data8[i + 1]) << GetCompressedShift(data8[i + 1]));
else
data16[i] = data16[i - 1] + (GetCompressedAmplitude(data8[i + 1]) << GetCompressedShift(data8[i + 1]));
i++;
}
return(RD_OK);
}
int32 Sword2Sound::PlayCompSpeech(const char *filename, uint32 speechid, uint8 vol, int8 pan) {
uint32 i;
uint16 *data16;
uint8 *data8;
uint32 speechIndex[2];
void *lpv1;
File fp;
uint32 bufferSize;
if (!speechMuted) {
if (GetSpeechStatus() == RDERR_SPEECHPLAYING)
return RDERR_SPEECHPLAYING;
// Open the speech cluster and find the data offset & size
fp.open(filename, g_engine->getGameDataPath());
if (fp.isOpen() == false)
return(RDERR_INVALIDFILENAME);
fp.seek((++speechid) * 8, SEEK_SET);
if (fp.read(speechIndex, sizeof(uint32) * 2) != (sizeof(uint32) * 2)) {
fp.close();
return (RDERR_READERROR);
}
if (speechIndex[0] == 0 || speechIndex[1] == 0) {
fp.close();
return (RDERR_INVALIDID);
}
bufferSize = (speechIndex[1] - 1) * 2;
// Create tempory buffer for compressed speech
if ((data8 = (uint8 *)malloc(speechIndex[1])) == NULL) {
fp.close();
return(RDERR_OUTOFMEMORY);
}
fp.seek(speechIndex[0], SEEK_SET);
if (fp.read(data8, speechIndex[1]) != speechIndex[1]) {
fp.close();
free(data8);
return (RDERR_INVALIDID);
}
fp.close();
lpv1 = malloc(bufferSize);
// decompress data into speech buffer.
data16 = (uint16*)lpv1;
data16[0] = *((int16*)data8); // Starting Value
i = 1;
while (i < bufferSize / 2) {
if (GetCompressedSign(data8[i + 1]))
data16[i] = data16[i - 1] - (GetCompressedAmplitude(data8[i + 1]) << GetCompressedShift(data8[i + 1]));
else
data16[i] = data16[i - 1] + (GetCompressedAmplitude(data8[i + 1]) << GetCompressedShift(data8[i + 1]));
i++;
}
free(data8);
// Modify the volume according to the master volume
byte volume;
int8 p;
if (speechMuted) {
volume = 0;
} else {
volume = vol * speechVol;
}
p = panTable[pan + 16];
// Start the speech playing
speechPaused = 1;
uint32 flags = SoundMixer::FLAG_16BITS;
flags |= SoundMixer::FLAG_AUTOFREE;
//Until the mixer supports LE samples natively, we need to convert our LE ones to BE
for (uint j = 0; j < (bufferSize / 2); j++)
data16[j] = TO_BE_16(data16[j]);
_mixer->playRaw(&soundHandleSpeech, data16, bufferSize, 22050, flags, volume, pan);
speechStatus = 1;
}
DipMusic();
return (RD_OK);
}
int32 Sword2Sound::StopSpeechSword2(void) {
if (!soundOn)
return(RD_OK);
if (speechStatus) {
g_engine->_mixer->stopHandle(soundHandleSpeech);
speechStatus = 0;
return(RD_OK);
}
return(RDERR_SPEECHNOTPLAYING);
}
int32 Sword2Sound::GetSpeechStatus(void) {
if ((!soundOn) || (!speechStatus))
return(RDSE_SAMPLEFINISHED);
if (speechPaused)
return(RDSE_SAMPLEPLAYING);
if (!soundHandleSpeech) {
speechStatus = 0;
return(RDSE_SAMPLEFINISHED);
}
return(RDSE_SAMPLEPLAYING);
}
void Sword2Sound::SetSpeechVolume(uint8 volume) {
speechVol = volume;
if ((soundHandleSpeech != 0) && !speechMuted && GetSpeechStatus() == RDSE_SAMPLEPLAYING) {
g_engine->_mixer->setChannelVolume(soundHandleSpeech, 16 * speechVol);
}
}
uint8 Sword2Sound::GetSpeechVolume() {
return speechVol;
}
void Sword2Sound::MuteSpeech(uint8 mute) {
speechMuted = mute;
if (GetSpeechStatus() == RDSE_SAMPLEPLAYING) {
if (mute) {
g_engine->_mixer->setChannelVolume(soundHandleSpeech, 0);
} else {
g_engine->_mixer->setChannelVolume(soundHandleSpeech, 16 * speechVol);
}
}
}
uint8 Sword2Sound::IsSpeechMute(void) {
return speechMuted;
}
int32 Sword2Sound::PauseSpeech(void) {
if (GetSpeechStatus() == RDSE_SAMPLEPLAYING) {
speechPaused = 1;
g_engine->_mixer->pauseHandle(soundHandleSpeech, true);
}
return(RD_OK);
}
int32 Sword2Sound::UnpauseSpeech(void) {
if (speechPaused) {
speechPaused = 0;
g_engine->_mixer->pauseHandle(soundHandleSpeech, false);
}
return(RD_OK);
}
int32 Sword2Sound::OpenFx(int32 id, uint8 *data) {
int32 i, fxi;
uint32 *data32 = NULL;
_wavHeader *wav;
wav = (_wavHeader *) data;
if (soundOn) {
// Check for a valid id.
if (id == 0)
return(RDERR_INVALIDID);
// Check that the fx is not already open
for (i = 0; i < MAXFX; i++) {
if (fxId[i] == id)
return(RDERR_FXALREADYOPEN);
}
// Now choose a free slot for the fx
fxi = 0;
while (fxi < MAXFX) {
if (fxId[fxi] == 0)
break;
fxi++;
}
if (fxi == MAXFX) {
// Expire the first sound effect that isn't currently
// playing.
// FIXME. This may need a bit of work. I still haven't
// grasped all the intricacies of the sound effects
// handling.
//
// Anyway, it'd be nicer - in theory - to expire the
// least recently used slot.
fxi = 0;
while (fxi < MAXFX) {
if (!soundHandleFx[fxi])
break;
fxi++;
}
// Still no dice? I give up!
if (fxi == MAXFX)
return(RDERR_NOFREEBUFFERS);
}
// Set the sample size - search for the size of the data.
i = 0;
while (i < 100) {
if (*data == 'd') {
data32 = (uint32*)data;
if (*data32 == 'atad')
break;
}
i += 1;
data++;
}
if (!data32)
return(RDERR_INVALIDWAV);
bufferSizeFx[fxi] = READ_LE_UINT32(data32 + 1);
// Fill the speech buffer with data
if (bufferFx[fxi] != NULL)
free(bufferFx[fxi]);
bufferFx[fxi] = (uint16*)malloc(bufferSizeFx[fxi]);
memcpy(bufferFx[fxi], (uint8 *)(data32 + 2), bufferSizeFx[fxi]);
flagsFx[fxi] = SoundMixer::FLAG_16BITS;
if (wav->channels == 2)
flagsFx[fxi] |= SoundMixer::FLAG_STEREO;
fxRate[fxi] = wav->samplesPerSec;
//Until the mixer supports LE samples natively, we need to convert our LE ones to BE
for (int32 j = 0; j < (bufferSizeFx[fxi] / 2); j++)
bufferFx[fxi][j] = TO_BE_16(bufferFx[fxi][j]);
fxId[fxi] = id;
}
return(RD_OK);
}
int32 Sword2Sound::PlayFx(int32 id, uint8 *data, uint8 vol, int8 pan, uint8 type) {
int32 i, loop;
uint32 hr;
if (type == RDSE_FXLOOP)
loop = 1;
else
loop = 0;
if (soundOn) {
if (data == NULL) {
if (type == RDSE_FXLEADOUT) {
id = (int32) 0xffffffff;
i = GetFxIndex(id);
if (i == MAXFX) {
warning("PlayFx(%d, %d, %d, %d) - Not open", id, vol, pan, type);
return(RDERR_FXNOTOPEN);
}
flagsFx[i] &= ~SoundMixer::FLAG_LOOP;
byte volume;
// Start the sound effect playing
if (musicMuted) {
volume = 0;
} else {
volume = musicVolTable[volMusic[0]];
}
g_engine->_mixer->playRaw(&soundHandleFx[i], bufferFx[i], bufferSizeFx[i], fxRate[i], flagsFx[i], volume, 0);
} else {
i = GetFxIndex(id);
if (i == MAXFX) {
warning("PlayFx(%d, %d, %d, %d) - Not open", id, vol, pan, type);
return(RDERR_FXNOTOPEN);
}
if (loop == 1)
flagsFx[i] |= SoundMixer::FLAG_LOOP;
else
flagsFx[i] &= ~SoundMixer::FLAG_LOOP;
fxVolume[i] = vol;
byte volume;
int8 p;
// Start the sound effect playing
if (fxMuted) {
volume = 0;
} else {
volume = vol * fxVol;
}
p = panTable[pan + 16];
g_engine->_mixer->playRaw(&soundHandleFx[i], bufferFx[i], bufferSizeFx[i], fxRate[i], flagsFx[i], volume, p);
}
} else {
if (type == RDSE_FXLEADIN) {
id = (int32) 0xfffffffe;
hr = OpenFx(id, data);
if (hr != RD_OK) {
return hr;
}
i = GetFxIndex(id);
if (i == MAXFX) {
warning("PlayFx(%d, %d, %d, %d) - Not found", id, vol, pan, type);
return RDERR_FXFUCKED;
}
flagsFx[i] &= ~SoundMixer::FLAG_LOOP;
byte volume;
if (musicMuted) {
volume = 0;
} else {
volume = musicVolTable[volMusic[0]];
}
g_engine->_mixer->playRaw(&soundHandleFx[i], bufferFx[i], bufferSizeFx[i], fxRate[i], flagsFx[i], volume, 0);
} else {
hr = OpenFx(id, data);
if (hr != RD_OK) {
return(hr);
}
i = GetFxIndex(id);
if (i == MAXFX) {
warning("PlayFx(%d, %d, %d, %d) - Not found", id, vol, pan, type);
return(RDERR_FXFUCKED);
}
if (loop == 1)
flagsFx[i] |= SoundMixer::FLAG_LOOP;
else
flagsFx[i] &= ~SoundMixer::FLAG_LOOP;
fxVolume[i] = vol;
byte volume;
int8 p;
// Start the sound effect playing
if (fxMuted) {
volume = 0;
} else {
volume = vol * fxVol;
}
p = panTable[pan + 16];
g_engine->_mixer->playRaw(&soundHandleFx[i], bufferFx[i], bufferSizeFx[i], fxRate[i], flagsFx[i], volume, p);
}
}
}
return(RD_OK);
}
int32 Sword2Sound::SetFxVolumePan(int32 id, uint8 vol, int8 pan) {
int32 i = GetFxIndex(id);
if (i == MAXFX)
return RDERR_FXNOTOPEN;
fxVolume[i] = vol;
if (!fxMuted) {
g_engine->_mixer->setChannelVolume(soundHandleFx[i], vol * fxVol);
g_engine->_mixer->setChannelPan(soundHandleFx[i], panTable[pan + 16]);
}
return RD_OK;
}
int32 Sword2Sound::SetFxIdVolume(int32 id, uint8 vol) {
int32 i = GetFxIndex(id);
if (i == MAXFX)
return RDERR_FXNOTOPEN;
fxVolume[i] = vol;
if (!fxMuted) {
g_engine->_mixer->setChannelVolume(soundHandleFx[i], vol * fxVol);
}
return RD_OK;
}
int32 Sword2Sound::ClearAllFx(void) {
int i;
if (!soundOn)
return(RD_OK);
i = 0;
while (i < MAXFX) {
if ((fxId[i]) && (fxId[i] != (int32) 0xfffffffe) && (fxId[i] != (int32) 0xffffffff)) {
g_engine->_mixer->stopHandle(soundHandleFx[i]);
fxId[i] = 0;
fxiPaused[i] = 0;
if (bufferFx[i] != NULL) {
free(bufferFx[i]);
bufferFx[i] = NULL;
}
bufferSizeFx[i] = 0;
flagsFx[i] = 0;
}
i++;
}
return(RD_OK);
}
int32 Sword2Sound::CloseFx(int32 id) {
int i;
if (!soundOn)
return(RD_OK);
i = GetFxIndex(id);
if (i < MAXFX) {
g_engine->_mixer->stopHandle(soundHandleFx[i]);
fxId[i] = 0;
fxiPaused[i] = 0;
if (bufferFx[i] != NULL) {
free(bufferFx[i]);
bufferFx[i] = NULL;
}
bufferSizeFx[i] = 0;
flagsFx[i] = 0;
}
return(RD_OK);
}
int32 Sword2Sound::PauseFx(void) {
int i;
if (!fxPaused) {
for (i = 0; i < MAXFX; i++) {
if (fxId[i]) {
g_engine->_mixer->pauseHandle(soundHandleFx[i], true);
fxiPaused[i] = 1;
} else {
fxiPaused[i] = 0;
}
}
fxPaused = 1;
}
return (RD_OK);
}
int32 Sword2Sound::PauseFxForSequence(void) {
int i;
if (!fxPaused) {
for (i = 0; i<MAXFX; i++) {
if ((fxId[i]) && (fxId[i] != (int32) 0xfffffffe)) {
g_engine->_mixer->pauseHandle(soundHandleFx[i], true);
fxiPaused[i] = 1;
} else {
fxiPaused[i] = 0;
}
}
fxPaused = 1;
}
return (RD_OK);
}
int32 Sword2Sound::UnpauseFx(void) {
int i;
if (fxPaused) {
for (i = 0; i < MAXFX; i++) {
if (fxiPaused[i] && fxId[i]) {
g_engine->_mixer->pauseHandle(soundHandleFx[i], false);
}
}
fxPaused = 0;
}
return (RD_OK);
}
uint8 Sword2Sound::GetFxVolume() {
return fxVol;
}
void Sword2Sound::SetFxVolume(uint8 volume) {
int32 fxi;
fxVol = volume;
// Now update the volume of any fxs playing
for (fxi = 0; fxi < MAXFX; fxi++) {
if (fxId[fxi] && !fxMuted) {
g_engine->_mixer->setChannelVolume(soundHandleFx[fxi], fxVolume[fxi] * fxVol);
}
}
}
void Sword2Sound::MuteFx(uint8 mute) {
int32 fxi;
fxMuted = mute;
// Now update the volume of any fxs playing
for (fxi = 0; fxi < MAXFX; fxi++) {
if (fxId[fxi]) {
if (mute) {
g_engine->_mixer->setChannelVolume(soundHandleFx[fxi], 0);
} else {
g_engine->_mixer->setChannelVolume(soundHandleFx[fxi], fxVolume[fxi] * fxVol);
}
}
}
}
uint8 Sword2Sound::IsFxMute(void) {
return (fxMuted);
}
void Sword2Sound::StartMusicFadeDown(int i) {
StackLock lock(_mutex);
g_engine->_mixer->stopHandle(soundHandleMusic[i]);
musFading[i] = -16;
musStreaming[i] = 0;
if (fpMus.isOpen())
fpMus.close();
}
int32 Sword2Sound::StreamCompMusic(const char *filename, uint32 musicId, int32 looping) {
StackLock lock(_mutex);
return StreamCompMusicFromLock(filename, musicId, looping);
}
int32 Sword2Sound::StreamCompMusicFromLock(const char *filename, uint32 musicId, int32 looping) {
int32 primaryStream = -1;
int32 secondaryStream = -1;
int32 i;
int32 v0, v1;
uint16 *data16;
uint8 *data8;
compressedMusic = 1;
// If both music streams are playing, that should mean one of them is
// fading out. Pick that one.
if (musStreaming[0] && musStreaming[1]) {
if (musFading[0])
primaryStream = 0;
else
primaryStream = 1;
musFading[primaryStream] = 0;
g_engine->_mixer->stopHandle(soundHandleMusic[primaryStream]);
musStreaming[primaryStream] = 0;
}
// Pick the available music stream. If no music is playing it doesn't
// matter which we use, so pick the first one.
if (musStreaming[0] || musStreaming[1]) {
if (musStreaming[0]) {
primaryStream = 1;
secondaryStream = 0;
} else {
primaryStream = 0;
secondaryStream = 1;
}
} else
primaryStream = 0;
strcpy(musFilename[primaryStream], filename);
// Save looping info and tune id
musLooping[primaryStream] = looping;
musId[primaryStream] = musicId;
// Don't start streaming if the volume is off.
if (IsMusicMute())
return RD_OK;
// Always use fpMus[0] (all music in one cluster)
// musFilePos[primaryStream] for different pieces of music.
if (!fpMus.isOpen())
fpMus.open(filename, g_engine->getGameDataPath());
if (!fpMus.isOpen())
return RDERR_INVALIDFILENAME;
// Start other music stream fading out
if (secondaryStream != -1 && !musFading[secondaryStream])
musFading[secondaryStream] = -16;
fpMus.seek((musicId + 1) * 8, SEEK_SET);
musFilePos[primaryStream] = fpMus.readUint32LE();
musEnd[primaryStream] = fpMus.readUint32LE();
if (!musEnd[primaryStream] || !musFilePos[primaryStream]) {
fpMus.close();
return RDERR_INVALIDID;
}
// Calculate the file position of the end of the music
musEnd[primaryStream] += musFilePos[primaryStream];
// Create a temporary buffer
data8 = (uint8*) malloc(bufferSizeMusic / 2);
if (!data8) {
fpMus.close();
return RDERR_OUTOFMEMORY;
}
// Seek to start of the compressed music
fpMus.seek(musFilePos[primaryStream], SEEK_SET);
// Read the compressed data in to the buffer
if ((int32) fpMus.read(data8, bufferSizeMusic / 2) != bufferSizeMusic / 2) {
fpMus.close();
free(data8);
return RDERR_INVALIDID;
}
// Store the current position in the file for future streaming
musFilePos[primaryStream] = fpMus.pos();
// FIXME: We used this decompression function in several places, so
// it really should be a separate function.
// decompress the music into the music buffer.
data16 = (uint16 *) malloc(bufferSizeMusic);
if (!data16)
return RDERR_OUTOFMEMORY;
data16[0] = READ_LE_UINT16(data8); // First sample value
i = 1;
while (i < (bufferSizeMusic / 2) - 1) {
if (GetCompressedSign(data8[i + 1]))
data16[i] = data16[i - 1] - (GetCompressedAmplitude(data8[i + 1]) << GetCompressedShift(data8[i + 1]));
else
data16[i] = data16[i - 1] + (GetCompressedAmplitude(data8[i + 1]) << GetCompressedShift(data8[i + 1]));
i++;
}
// Store the value of the last sample ready for next batch of
// decompression
musLastSample[primaryStream] = data16[i - 1];
// Free the compressed sound buffer
free(data8);
// Modify the volume according to the master volume and music mute
// state
if (musicMuted)
v0 = v1 = 0;
else {
v0 = volMusic[0];
v1 = volMusic[1];
}
byte volume;
int8 pan;
if (v0 > v1) {
volume = musicVolTable[v0];
pan = (musicVolTable[v1 * 16 / v0] / 2) - 127;
} else if (v1 > v0) {
volume = musicVolTable[v1];
pan = (musicVolTable[v0 * 16 / v1] / 2) + 127;
} else {
volume = musicVolTable[v1];
pan = 0;
}
// FIXME: Until the mixer supports LE samples natively, we need to
// convert our LE ones to BE
for (i = 0; i < (bufferSizeMusic / 2); i++) {
data16[i] = TO_BE_16(data16[i]);
}
g_engine->_mixer->newStream(&soundHandleMusic[primaryStream], data16,
bufferSizeMusic, 22050, SoundMixer::FLAG_16BITS, 100000, volume, pan);
free(data16);
// Recorder some last variables
musStreaming[primaryStream] = 1;
return RD_OK;
}
void Sword2Sound::UpdateCompSampleStreaming(void) {
StackLock lock(_mutex);
uint32 i,j;
int32 v0, v1;
int32 len;
uint16 *data16;
uint8 *data8;
int fade;
for (i = 0; i < MAXMUS; i++) {
if (musStreaming[i]) {
if (musFading[i] < 0) {
if (++musFading[i] == 0) {
g_engine->_mixer->stopHandle(soundHandleMusic[i]);
musStreaming[i] = 0;
musLooping[i] = 0;
} else {
// Modify the volume according to the master volume and music mute state
if (musicMuted)
v0 = v1 = 0;
else {
v0 = (volMusic[0] * (0 - musFading[i]) / 16);
v1 = (volMusic[1] * (0 - musFading[i]) / 16);
}
byte volume;
int8 pan;
if (v0 > v1) {
volume = musicVolTable[v0];
pan = (musicVolTable[v1 * 16 / v0] / 2) - 127;
}
if (v1 > v0) {
volume = musicVolTable[v1];
pan = (musicVolTable[v0 * 16 / v1] / 2) + 127;
} else {
volume = musicVolTable[v1];
pan = 0;
}
g_engine->_mixer->setChannelVolume(soundHandleMusic[i], volume);
g_engine->_mixer->setChannelPan(soundHandleMusic[i], pan);
// FIXME: hack. this need cleanup.
// originaly it has 3 second buffer ahead enought for fading
// that why it's need play music for time while fading is going
// Temporary disabled this because it
// causes a crash whenever music is
// allowed to finish on its own (i.e.
// not by being replaced with another
// piece of music.)
// goto label1;
}
} else {
label1:
len = bufferSizeMusic;
// Reduce length if it requires reading past the end of the music
if (musFilePos[i] + len >= musEnd[i]) {
len = musEnd[i] - musFilePos[i];
fade = 1; // End of music reaced so we'll need to fade and repeat
} else
fade = 0;
if (len > 0) {
data8 = (uint8*)malloc(len / 2);
// Allocate a compressed data buffer
if (data8 == NULL) {
fpMus.close();
musFading[i] = -16;
return;
}
// Seek to update position of compressed music when neccassary (probably never occurs)
if ((int32) fpMus.pos() != musFilePos[i]) {
fpMus.seek(musFilePos[i], SEEK_SET);
}
// Read the compressed data in to the buffer
if ((int32)fpMus.read(data8, len / 2) != (len / 2)) {
fpMus.close();
free(data8);
musFading[i] = -16;
return;
}
// Update the current position in the file for future streaming
musFilePos[i] = fpMus.pos();
// decompress the music into the music buffer.
data16 = (uint16*)malloc(len);
// Decompress the first byte using the last decompressed sample
if (GetCompressedSign(data8[0]))
data16[0] = musLastSample[i] - (GetCompressedAmplitude(data8[0]) << GetCompressedShift(data8[0]));
else
data16[0] = musLastSample[i] + (GetCompressedAmplitude(data8[0]) << GetCompressedShift(data8[0]));
j = 1;
while (j < (uint32)len / 2) {
if (GetCompressedSign(data8[j]))
data16[j] = data16[j - 1] - (GetCompressedAmplitude(data8[j]) << GetCompressedShift(data8[j]));
else
data16[j] = data16[j - 1] + (GetCompressedAmplitude(data8[j]) << GetCompressedShift(data8[j]));
j++;
}
musLastSample[i] = data16[j - 1];
//Until the mixer supports LE samples natively, we need to convert our LE ones to BE
for (int32 y = 0; y < (len / 2); y++) {
data16[y] = TO_BE_16(data16[y]);
}
// Paranoid check that seems to
// be necessary.
if (len & 1)
len--;
g_engine->_mixer->appendStream(soundHandleMusic[i], data16, len);
free(data16);
// Free the compressed data buffer and unlock the sound buffer.
free(data8);
// End of the music so we need to start fading and start the music again
if (fade) {
g_engine->_mixer->stopHandle(soundHandleMusic[i]);
musFading[i] = -16; // Fade the old music
// Close the music cluster if it's open
if (fpMus.isOpen()) {
fpMus.close();
}
// Loop if neccassary
if (musLooping[i]) {
StreamCompMusicFromLock(musFilename[i], musId[i], musLooping[i]);
}
}
}
}
}
}
DipMusic();
}
int32 Sword2Sound::DipMusic() {
// TODO: implement this func
// disable this func for now
return RD_OK;
StackLock lock(_mutex);
/*
int32 len;
int32 readCursor, writeCursor;
int32 dwBytes1, dwBytes2;
int16 *sample;
int32 total = 0;
int32 i;
int32 status;
LPVOID lpv1, lpv2;
HRESULT hr = DS_OK;
LPDIRECTSOUNDBUFFER dsbMusic = NULL;
int32 currentMusicVol = musicVolTable[volMusic[0]];
int32 minMusicVol;
// Find which music buffer is currently playing
for (i = 0; i<MAXMUS && !dsbMusic; i++)
{
if (musStreaming[i] && musFading[i] == 0)
dsbMusic = lpDsbMus[i];
}
if ((!musicMuted) && dsbMusic && (!speechMuted) && (volMusic[0]>2))
{
minMusicVol = musicVolTable[volMusic[0] - 3];
if (speechStatus)
{
IDirectSoundBuffer_GetStatus(dsbSpeech, &status);
if ((hr = IDirectSoundBuffer_GetCurrentPosition(dsbMusic, &readCursor, &writeCursor)) != DS_OK)
return hr;
len = 44100 / 12 ;// 12th of a second
if ((hr = IDirectSoundBuffer_Lock(dsbMusic, readCursor, len, &lpv1, &dwBytes1, &lpv2, &dwBytes2, 0)) != DS_OK)
return hr;
for (i = 0, sample = (int16*)lpv1; sample<(int16*)((int8*)lpv1+dwBytes1); sample+= 30, i++) // 60 samples
{
if (*sample>0)
total += *sample;
else
total -= *sample;
}
total /= i;
total = minMusicVol + ( ( (currentMusicVol - minMusicVol) * total ) / 8000);
if (total > currentMusicVol)
total = currentMusicVol;
IDirectSoundBuffer_SetVolume(dsbMusic, total);
IDirectSoundBuffer_Unlock(dsbMusic,lpv1,dwBytes1,lpv2,dwBytes2);
}
else
{
IDirectSoundBuffer_GetVolume(dsbMusic, &total);
total += 50;
if (total > currentMusicVol)
total = currentMusicVol;
IDirectSoundBuffer_SetVolume(dsbMusic, total);
}
}
return (hr);
*/
}
int32 Sword2Sound::MusicTimeRemaining() {
StackLock lock(_mutex);
int i;
for (i = 0; i < MAXMUS && !musStreaming[i]; i++) {
// this is meant to be empty! (James19aug97)
}
if (i == MAXMUS)
return 0;
return (musEnd[i] - musFilePos[i]) / 22050;
}
void Sword2Sound::StopMusic(void) {
StackLock lock(_mutex);
int i;
for (i = 0; i < MAXMUS; i++) {
if (musStreaming[i])
musFading[i] = -16;
else
musLooping[i] = 0;
}
if (fpMus.isOpen()) {
fpMus.close();
}
}
int32 Sword2Sound::PauseMusic(void) {
StackLock lock(_mutex);
int i;
if (soundOn) {
for (i = 0; i < 2; i++) {
if (musStreaming[i]) {
musicPaused[i] = TRUE;
g_engine->_mixer->pauseHandle(soundHandleMusic[i], true);
} else {
musicPaused[i] = FALSE;
}
}
}
return(RD_OK);
}
int32 Sword2Sound::UnpauseMusic(void) {
StackLock lock(_mutex);
int i;
if (soundOn) {
for (i = 0; i < 2; i++) {
if (musicPaused[i]) {
g_engine->_mixer->pauseHandle(soundHandleMusic[i], false);
musicPaused[i] = FALSE;
}
}
}
return(RD_OK);
}
void Sword2Sound::SetMusicVolume(uint8 volume) {
StackLock lock(_mutex);
int i;
for (i = 0; i < MAXMUS; i++) {
volMusic[i] = volume;
if (musStreaming[i] && !musFading[i] && !musicMuted) {
g_engine->_mixer->setChannelVolume(soundHandleMusic[i], musicVolTable[volume]);
}
}
}
uint8 Sword2Sound::GetMusicVolume() {
return (uint8) volMusic[0];
}
void Sword2Sound::MuteMusic(uint8 mute) {
StackLock lock(_mutex);
int i;
musicMuted = mute;
for (i = 0; i < MAXMUS; i++) {
if (!mute) {
if (!musStreaming[i] && musLooping[i])
StreamCompMusicFromLock(musFilename[i], musId[i], musLooping[i]);
}
if (musStreaming[i] && !musFading[i]) {
if (mute) {
g_engine->_mixer->setChannelVolume(soundHandleMusic[i], musicVolTable[0]);
} else {
g_engine->_mixer->setChannelVolume(soundHandleMusic[i], musicVolTable[volMusic[i]]);
}
}
}
}
uint8 Sword2Sound::IsMusicMute(void) {
return (musicMuted);
}