diff --git a/actor.cpp b/actor.cpp index 403ea1e8048..779890c45ad 100644 --- a/actor.cpp +++ b/actor.cpp @@ -20,7 +20,6 @@ #include "engine.h" #include "costume.h" #include "sound.h" -#include "mixer.h" #include #include #include diff --git a/bits.h b/bits.h index f74e63371c0..c23d97dcb14 100644 --- a/bits.h +++ b/bits.h @@ -36,6 +36,10 @@ #define ARRAYSIZE(x) ((int)(sizeof(x) / sizeof(x[0]))) +template inline T ABS (T x) { return (x>=0) ? x : -x; } +template inline T MIN (T a, T b) { return (a inline T MAX (T a, T b) { return (a>b) ? a : b; } + #if defined(_MSC_VER) #define scumm_stricmp stricmp diff --git a/costume.cpp b/costume.cpp index d5ebdfc4385..eaf34ad72e0 100644 --- a/costume.cpp +++ b/costume.cpp @@ -25,7 +25,6 @@ #include "model.h" #include "lua.h" #include "sound.h" -#include "mixer.h" #include #include #include diff --git a/lua.cpp b/lua.cpp index 506af047506..cf85f939a0b 100644 --- a/lua.cpp +++ b/lua.cpp @@ -25,7 +25,6 @@ #include "color.h" #include "costume.h" #include "engine.h" -#include "mixer.h" #include "sound.h" #include "smush.h" #include "textobject.h" @@ -1023,12 +1022,14 @@ static void GetTextObjectDimensions() { static void StartMovie() { Smush player; - player.play("intro.snm", ""); + char *filename = lua_getstring(lua_getparam(1)); + player.play(filename, ""); } static void StartFullscreenMovie() { Smush player; - player.play("intro.snm", ""); + char *filename = lua_getstring(lua_getparam(1)); + player.play(filename, ""); } // Stub function for builtin functions not yet implemented diff --git a/main.cpp b/main.cpp index 628b5e061cd..90be375c8ae 100644 --- a/main.cpp +++ b/main.cpp @@ -25,7 +25,8 @@ #include "lua.h" #include "registry.h" #include "engine.h" -#include "mixer.h" +#include "sound.h" +#include "mixer/mixer.h" #ifndef _MSC_VER #include #endif @@ -52,6 +53,8 @@ static void saveRegistry() { } #endif +extern SoundMixer *g_mixer; + int main(int argc, char *argv[]) { char GLDriver[1024]; int i; @@ -102,6 +105,7 @@ int main(int argc, char *argv[]) { } } + g_mixer = new SoundMixer(); Mixer::instance()->start(); lua_open(); @@ -154,5 +158,7 @@ int main(int argc, char *argv[]) { #endif Engine::instance()->mainLoop(); + delete g_mixer; + return 0; } diff --git a/mixer.cpp b/mixer.cpp deleted file mode 100644 index ffb4f7eba3c..00000000000 --- a/mixer.cpp +++ /dev/null @@ -1,440 +0,0 @@ -// Residual - Virtual machine to run LucasArts' 3D adventure games -// Copyright (C) 2003 The ScummVM-Residual Team (www.scummvm.org) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library 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 -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -#include "stdafx.h" -#include "mixer.h" -#include "sound.h" -#include "debug.h" -#include -#include - -class AudioLock { -public: - AudioLock() { SDL_LockAudio(); } - ~AudioLock() { SDL_UnlockAudio(); } -}; - -struct imuseTableEntry { - int stateNum; - const char *filename; -}; - -static const imuseTableEntry grimMusicTable[] = { - { 1001, "1001 - Manny's Office.IMC" }, - { 1002, "1002 - Mr. Frustration.IMC" }, - { 1003, "1002 - Mr. Frustration.IMC" }, - { 1004, "1002 - Mr. Frustration.IMC" }, - { 1005, "1002 - Mr. Frustration.IMC" }, - { 1006, "1002 - Mr. Frustration.IMC" }, - { 1007, "1002 - Mr. Frustration.IMC" }, - { 1008, "1008 - Domino's Office.IMC" }, - { 1009, "1009 - Copal's Office.IMC" }, - { 1010, "1010 - Ledge.IMC" }, - { 1011, "1011 - Roof.IMC" }, - { 1020, "1020 - Tube Room.IMC" }, - { 1021, "1021 - Brennis.IMC" }, - { 1022, "1022 - Lobby.IMC" }, - { 1023, "1023 - Packing Room.IMC" }, - { 1030, "1030 - Garage.IMC" }, - { 1031, "1031 - Glottis' Shop.IMC" }, - { 1032, "1030 - Garage.IMC" }, - { 1040, "1040 - Festival Wet.IMC" }, - { 1041, "1041 - Festival Dry.IMC" }, - { 1042, "1041 - Festival Dry.IMC" }, - { 1043, "1041 - Festival Dry.IMC" }, - { 1044, "1040 - Festival Wet.IMC" }, - { 1050, "1050 - Headquarters.IMC" }, - { 1060, "1060 - Real World.IMC" }, - { 1070, "1070 - Stump Room.IMC" }, - { 1071, "1071 - Signpost Room.IMC" }, - { 1072, "1072 - Navigation.IMC" }, - { 1073, "1071 - Signpost Room.IMC" }, - { 1074, "1074 - Bone Wagon.IMC" }, - { 1075, "1075 - Spider's Eye.IMC" }, - { 1076, "1076 - Spider Room.IMC" }, - { 1077, "1077 - Tree Pump Amb.IMC" }, - { 1078, "1078 - Tree Pump.IMC" }, - { 1079, "1071 - Signpost Room.IMC" }, - { 1080, "1080 - Beaver Room Lobby.IMC" }, - { 1081, "1081 - Beaver Dam.IMC" }, - { 1082, "1083 - Beaver Room.IMC" }, - { 1083, "1083 - Beaver Room.IMC" }, - { 1084, "1084 - Foggy Cactus.IMC" }, - { 1085, "1085 - Rubamat Exterior.IMC" }, - { 1086, "1086 - Blue Hector.IMC" }, - { 1100, "1109 - Cafe Exterior.IMC" }, - { 1101, "1101 - Cafe Office.IMC" }, - { 1102, "1102 - Cafe Intercom.IMC" }, - { 1103, "1103 - Coat Check.IMC" }, - { 1104, "1104 - Lupe.IMC" }, - { 1105, "1106 - Glottis Noodle.IMC" }, - { 1106, "1106 - Glottis Noodle.IMC" }, - { 1107, "1101 - Cafe Office.IMC" }, - { 1108, "1108 - Casino Interior.IMC" }, - { 1109, "1109 - Cafe Exterior.IMC" }, - { 1110, "1110 - Cafe Ledge.IMC" }, - { 1111, "1108 - Casino Interior.IMC" }, - { 1112, "1112 - Rusty Sans Vox.IMC" }, - { 1120, "1120 - Elevator Station.IMC" }, - { 1121, "1122 - Blue Exterior.IMC" }, - { 1122, "1122 - Blue Exterior.IMC" }, - { 1123, "1123 - Blue Casket Ins.IMC" }, - { 1124, "1124 - Blue Casket Amb.IMC" }, - { 1125, "1125 - Smooth Hector.IMC" }, - { 1126, "1122 - Blue Exterior.IMC" }, - { 1127, "1127 - Limbo Dock.IMC" }, - { 1128, "1128 - Limbo Talk.IMC" }, - { 1129, "1129 - Limbo Poem.IMC" }, - { 1130, "1130 - Dry Dock.IMC" }, - { 1131, "1131 - Dry Dock Strike.IMC" }, - { 1132, "1132 - Lighthouse Ext.IMC" }, - { 1133, "1133 - Lola's Last.IMC" }, - { 1140, "1140 - Police Station.IMC" }, - { 1141, "1141 - Police Interior.IMC" }, - { 1142, "1141 - Police Interior.IMC" }, - { 1143, "1143 - Morgue.IMC" }, - { 1144, "1140 - Police Station.IMC" }, - { 1145, "1145 - Bridge Blimp.IMC" }, - { 1146, "1146 - LOL Security Ext.IMC" }, - { 1147, "1147 - LOL Security Int.IMC" }, - { 1148, "1148 - Carla's Life.IMC" }, - { 1149, "1149 - Bomb.IMC" }, - { 1150, "1150 - Track Stairs.IMC" }, - { 1151, "1151 - Track Stairs.IMC" }, - { 1152, "1152 - Track Stairs.IMC" }, - { 1153, "1153 - Track Base.IMC" }, - { 1154, "1154 - Kitty Hall.IMC" }, - { 1155, "1155 - Sanspoof.IMC" }, - { 1156, "1156 - Kitty Stables.IMC" }, - { 1160, "1160 - High Roller Hall.IMC" }, - { 1161, "1161 - High Roller Lnge.IMC" }, - { 1162, "1162 - Glottis Gambling.IMC" }, - { 1163, "1163 - Max's Office.IMC" }, - { 1164, "1125 - Hector Steps Out.IMC" }, - { 1165, "1125 - Hector Steps Out.IMC" }, - { 1166, "1125 - Hector Steps Out.IMC" }, - { 1167, "1167 - Dillopede Elev.IMC" }, - { 1168, "1168 - Dillopede Elev.IMC" }, - { 1169, "1169 - Dillopede Elev.IMC" }, - { 1170, "1170 - Extendo Bridge.IMC" }, - { 1171, "1170 - Extendo Bridge.IMC" }, - { 1172, "1170 - Extendo Bridge.IMC" }, - { 1173, "1173 - Scrimshaw Int.IMC" }, - { 1174, "1174 - Scrim Sleep.IMC" }, - { 1180, "1180 - Note to Manny.IMC" }, - { 1181, "1155 - Sanspoof.IMC" }, - { 1190, "1106 - Glottis Noodle.IMC" }, - { 1191, "1106 - Glottis Noodle.IMC" }, - { 1201, "1201 - Lola Zapata.IMC" }, - { 1202, "1202 - Inside the Lola.IMC" }, - { 1203, "1203 - Engine Room.IMC" }, - { 1204, "1204 - Porthole.IMC" }, - { 1205, "1204 - Porthole.IMC" }, - { 1210, "1210 - Sunken Lola.IMC" }, - { 1211, "1211 - Pearl Crater Sub.IMC" }, - { 1220, "1220 - Miner's Room.IMC" }, - { 1221, "1221 - Miner's Room.IMC" }, - { 1222, "1222 - Exterior Airlock.IMC" }, - { 1223, "1223 - Factory Hub.IMC" }, - { 1224, "1224 - Foreman's Office.IMC" }, - { 1230, "1230 - Vault Door.IMC" }, - { 1231, "1231 - Outer Vault.IMC" }, - { 1232, "1232 - Inner Vault.IMC" }, - { 1233, "1233 - Ashtray Room.IMC" }, - { 1234, "1234 - Ashtray Scary.IMC" }, - { 1235, "1235 - Ashtray Pensive.IMC" }, - { 1236, "1236 - Domino's Room.IMC" }, - { 1240, "1240 - Conveyor Under.IMC" }, - { 1241, "1240 - Conveyor Under.IMC" }, - { 1242, "1241 - Crane Intro.IMC" }, - { 1243, "1243 - Anchor Room.IMC" }, - { 1244, "1244 - Glottis Hanging.IMC" }, - { 1245, "1245 - End of the World.IMC" }, - { 1246, "1246 - End World Later.IMC" }, - { 1247, "1241 - Crane Intro.IMC" }, - { 1250, "1250 - Upper Beach.IMC" }, - { 1251, "1250 - Upper Beach.IMC" }, - { 1252, "1252 - Lower Beach Boat.IMC" }, - { 1253, "1253 - Lamancha Sub.IMC" }, - { 1254, "1254 - Crane Later.IMC" }, - { 1301, "1301 - Temple Gate.IMC" }, - { 1302, "1301 - Temple Gate.IMC" }, - { 1303, "1303 - Truck Depot.IMC" }, - { 1304, "1304 - Mayan Train Sta.IMC" }, - { 1305, "1305 - Mayan Workshop.IMC" }, - { 1306, "1306 - Mayan Train Pad.IMC" }, - { 1307, "1307 - Mechanic's Kitch.IMC" }, - { 1310, "1310 - Jello Bomb.IMC" }, - { 1311, "1310 - Jello Bomb.IMC" }, - { 1312, "1125 - Smooth Hector.IMC" }, - { 1313, "1125 - Smooth Hector.IMC" }, - { 1314, "1125 - Smooth Hector.IMC" }, - { 1315, "1122 - Blue Exterior.IMC" }, - { 1316, "1122 - Blue Exterior.IMC" }, - { 1317, "1122 - Blue Exterior.IMC" }, - { 1318, "1332 - Hector's Foyer.IMC" }, - { 1319, "1319 - Florist Video.IMC" }, - { 1320, "1320 - New LSA HQ.IMC" }, - { 1321, "1321 - LSA Sewer.IMC" }, - { 1322, "1321 - LSA Sewer.IMC" }, - { 1323, "1323 - Sewer Maze.IMC" }, - { 1324, "1324 - Albinozod.IMC" }, - { 1325, "1325 - Florist Shop.IMC" }, - { 1326, "1326 - Florist Shop Int.IMC" }, - { 1327, "1327 - Florist OK.IMC" }, - { 1328, "1323 - Sewer Maze.IMC" }, - { 1329, "1329 - Theater Backstag.IMC" }, - { 1330, "1330 - Lemans Lobby.IMC" }, - { 1331, "1330 - Lemans Lobby.IMC" }, - { 1332, "1332 - Hector's Foyer.IMC" }, - { 1333, "1333 - Brennis Talk.IMC" }, - { 1334, "1334 - Albino Trap.IMC" }, - { 1340, "1342 - Neon Ledge.IMC" }, - { 1350, "1350 - Meadow Flowers.IMC" }, - { 1351, "1351 - Meadow.IMC" }, - { 1352, "1352 - Car Head.IMC" }, - { 1353, "1353 - Greenhouse Appr.IMC" }, - { 1354, "1354 - Game Ending.IMC" }, - { 1355, "1355 - Shootout.IMC" }, - { 1400, "1400 - Start Credits.IMC" }, - { 1401, "1401 - Smooth Hector.IMC" }, - { 2001, "2001 - Climb Rope.IMC" }, - { 2010, "2010 - Glottis OK.IMC" }, - { 2020, "2020 - Reap Bruno.IMC" }, - { 2030, "2030 - Ledgepeckers.IMC" }, - { 2050, "2050 - Glottis Heart.IMC" }, - { 2055, "2055 - Slingshot Bone.IMC" }, - { 2060, "2060 - Glott Tree Fall.IMC" }, - { 2070, "2070 - Beaver Fly.IMC" }, - { 2071, "2071 - Beaver Sink.IMC" }, - { 2080, "2080 - Meet Velasco.IMC" }, - { 2140, "2140 - Ooo Bonewagon.IMC" }, - { 2141, "2141 - Ooo Meche.IMC" }, - { 2155, "2155 - Find Detector.IMC" }, - { 2156, "2156 - Glott Drink Wine.IMC" }, - { 2157, "2157 - Glott No Wine.IMC" }, - { 2161, "2161 - Raoul Appears.IMC" }, - { 2162, "2162 - Raoul KO.IMC" }, - { 2163, "2163 - Raoul Dissed.IMC" }, - { 2165, "2165 - Fake Tix.IMC" }, - { 2180, "2180 - Befriend Commies.IMC" }, - { 2186, "2186 - Nick Punchout.IMC" }, - { 2200, "2200 - Year 3 Iris.IMC" }, - { 2210, "2210 - Hit Men.IMC" }, - { 2230, "2230 - Open Vault.IMC" }, - { 2235, "2235 - Dead Tix.IMC" }, - { 2240, "2240 - Sprinkler.IMC" }, - { 2250, "2250 - Crane Track.IMC" }, - { 2255, "2255 - Crane Fall.IMC" }, - { 2300, "2300 - Yr 4 Iris.IMC" }, - { 2301, "2301 - Pop Bruno Casket.IMC" }, - { 2310, "2310 - Rocket Idea.IMC" }, - { 2320, "2320 - Jello Suspense.IMC" }, - { 2325, "2325 - Lumbago Lemo.IMC" }, - { 2327, "2327 - Breath Mint.IMC" }, - { 2330, "2330 - Pigeon Fly.IMC" }, - { 2340, "2340 - Coffee On Boys.IMC" }, - { 2350, "2350 - Sprout Aha.IMC" }, - { 2360, "2360 - Chowchilla Bye.IMC" }, - { 2370, "2370 - Salvador Death.IMC" }, - { 2399, "2399 - End Credits.IMC" } -}; - -Mixer *Mixer::instance_ = NULL; - -Mixer *Mixer::instance() { - if (instance_ == NULL) - instance_ = new Mixer; - return instance_; -} - -void mixerCallback(void *userdata, Uint8 *stream, int len) { - Mixer *m = static_cast(userdata); - int16 *samples = reinterpret_cast(stream); - m->getAudio(samples, len / 2); -} - -Mixer::Mixer() : - musicSound_(NULL), seqSound_(NULL) -{ -} - -void Mixer::start() { - Sound::init(); - - SDL_AudioSpec desired; - memset(&desired, 0, sizeof(desired)); // for valgrind cleanness - desired.freq = 22050; - desired.format = AUDIO_S16SYS; - desired.channels = 2; - desired.samples = 2048; - desired.callback = mixerCallback; - desired.userdata = this; - SDL_OpenAudio(&desired, NULL); - SDL_PauseAudio(0); -} - -void Mixer::playVoice(Sound *s) { - AudioLock l; - - s->reset(); - voiceSounds_.push_back(s); -} - -void Mixer::playSfx(Sound *s) { - AudioLock l; - - s->reset(); - sfxSounds_.push_back(s); -} - -void Mixer::stopSfx(Sound *s) { - AudioLock l; - - for (sound_list::iterator i = sfxSounds_.begin(); - i != sfxSounds_.end(); ) { - if (*i == s) - i = sfxSounds_.erase(i); - else - i++; - } -} - -void Mixer::stopVoice(Sound *s) { - AudioLock l; - - for (sound_list::iterator i = voiceSounds_.begin(); - i != voiceSounds_.end(); ) { - if (*i == s) - i = voiceSounds_.erase(i); - else - i++; - } -} - -static int compareStates(const void *p1, const void *p2) { - const imuseTableEntry *e1 = static_cast(p1); - const imuseTableEntry *e2 = static_cast(p2); - return e1->stateNum - e2->stateNum; -} - -void Mixer::setImuseState(int state) { - Sound *newSound = NULL; - - if (state != 1000) { - imuseTableEntry key; - key.stateNum = state; - const imuseTableEntry *e = static_cast - (std::bsearch(&key, grimMusicTable, - sizeof(grimMusicTable) / sizeof(grimMusicTable[0]), - sizeof(grimMusicTable[0]), compareStates)); - if (e == NULL) { - warning("Unknown IMuse state %d\n", state); - return; - } - - newSound = ResourceLoader::instance()->loadSound(e->filename); - if (newSound == NULL) { - warning("Could not find music file %s\n", e->filename); - return; - } - } - - AudioLock l; - if (newSound != musicSound_) { - if (newSound != NULL) - newSound->reset(); - musicSound_ = newSound; - } -} - -void Mixer::setImuseSeq(int state) { - Sound *newSound = NULL; - - if (state != 2000) { - imuseTableEntry key; - key.stateNum = state; - const imuseTableEntry *e = static_cast - (std::bsearch(&key, grimMusicTable, - sizeof(grimMusicTable) / sizeof(grimMusicTable[0]), - sizeof(grimMusicTable[0]), compareStates)); - if (e == NULL) { - warning("Unknown IMuse state %d\n", state); - return; - } - - Sound *newSound = ResourceLoader::instance()->loadSound(e->filename); - if (newSound == NULL) { - warning("Could not find music file %s\n", e->filename); - return; - } - } - - AudioLock l; - if (newSound != seqSound_) { - if (newSound != NULL) - newSound->reset(); - seqSound_ = newSound; - } -} - -Sound *Mixer::findSfx(const char *filename) { - AudioLock l; - - for (sound_list::iterator i = sfxSounds_.begin(); - i != sfxSounds_.end(); i++) { - if (std::strcmp((*i)->filename(), filename) == 0) - return *i; - } - return NULL; -} - -bool Mixer::voicePlaying() const { - AudioLock l; - - return ! voiceSounds_.empty(); -} - -void Mixer::getAudio(int16 *data, int numSamples) { - memset(data, 0, numSamples * 2); - for (sound_list::iterator i = voiceSounds_.begin(); - i != voiceSounds_.end(); ) { - (*i)->mix(data, numSamples); - if ((*i)->done()) - i = voiceSounds_.erase(i); - else - i++; - } - for (sound_list::iterator i = sfxSounds_.begin(); - i != sfxSounds_.end(); ) { - (*i)->mix(data, numSamples); - if ((*i)->done()) - i = sfxSounds_.erase(i); - else - i++; - } - if (seqSound_ != NULL) { - seqSound_->mix(data, numSamples); - if (seqSound_->done()) - seqSound_ = NULL; - } - else if (musicSound_ != NULL) { - musicSound_->mix(data, numSamples); - if (musicSound_->done()) - musicSound_->reset(); - } -} diff --git a/mixer.h b/mixer.h deleted file mode 100644 index bf3f920f402..00000000000 --- a/mixer.h +++ /dev/null @@ -1,56 +0,0 @@ -// Residual - Virtual machine to run LucasArts' 3D adventure games -// Copyright (C) 2003 The ScummVM-Residual Team (www.scummvm.org) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library 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 -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -#ifndef MIXER_H -#define MIXER_H - -#include "bits.h" -#include "resource.h" -#include -#include - -class Sound; - -class Mixer { -public: - static Mixer *instance(); - - void start(); - - void playVoice(Sound *s); - void playSfx(Sound *s); - void stopSfx(Sound *s); - void stopVoice(Sound *s); - void setImuseState(int state); - void setImuseSeq(int seq); - - Sound *findSfx(const char *filename); - bool voicePlaying() const; - -private: - Mixer(); - void getAudio(int16 *data, int numSamples); - - static Mixer *instance_; - typedef std::list > sound_list; - sound_list voiceSounds_, sfxSounds_; - ResPtr musicSound_, seqSound_; - - friend void mixerCallback(void *userdata, uint8 *stream, int len); -}; - -#endif diff --git a/mixer/audiostream.cpp b/mixer/audiostream.cpp new file mode 100644 index 00000000000..5674380cf31 --- /dev/null +++ b/mixer/audiostream.cpp @@ -0,0 +1,241 @@ +// Residual - Virtual machine to run LucasArts' 3D adventure games +// Copyright (C) 2003 The ScummVM-Residual Team (www.scummvm.org) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "../stdafx.h" +#include "../debug.h" +#include "mixer.h" +#include "audiostream.h" + + +// This used to be an inline template function, but +// buggy template function handling in MSVC6 forced +// us to go with the macro approach. So far this is +// the only template function that MSVC6 seemed to +// compile incorrectly. Knock on wood. +#define READSAMPLE(is16Bit, isUnsigned, ptr) \ + ((is16Bit ? READ_BE_UINT16(ptr) : (*ptr << 8)) ^ (isUnsigned ? 0x8000 : 0)) + +template +class LinearMemoryStream : public AudioInputStream { +protected: + const byte *_ptr; + const byte *_end; + const byte *_loopPtr; + const byte *_loopEnd; + + inline int16 readIntern() { + //assert(_ptr < _end); + int16 val = READSAMPLE(is16Bit, isUnsigned, _ptr); + _ptr += (is16Bit ? 2 : 1); + if (_loopPtr && eosIntern()) { + _ptr = _loopPtr; + _end = _loopEnd; + } + return val; + } + inline bool eosIntern() const { return _ptr >= _end; }; +public: + LinearMemoryStream(const byte *ptr, uint len, uint loopOffset, uint loopLen) + : _ptr(ptr), _end(ptr+len), _loopPtr(0), _loopEnd(0) { + + // Verify the buffer sizes are sane + if (is16Bit && stereo) + assert((len & 3) == 0 && (loopLen & 3) == 0); + else if (is16Bit || stereo) + assert((len & 1) == 0 && (loopLen & 1) == 0); + + if (loopLen) { + _loopPtr = _ptr + loopOffset; + _loopEnd = _loopPtr + loopLen; + } + if (stereo) // Stereo requires even sized data + assert(len % 2 == 0); + } + int readBuffer(int16 *buffer, const int numSamples); + + int16 read() { return readIntern(); } + bool eos() const { return eosIntern(); } + bool isStereo() const { return stereo; } +}; + +template +int LinearMemoryStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + while (samples < numSamples && !eosIntern()) { + const int len = MIN(numSamples, samples + (int)(_end - _ptr) / (is16Bit ? 2 : 1)); + while (samples < len) { + *buffer++ = READSAMPLE(is16Bit, isUnsigned, _ptr); + _ptr += (is16Bit ? 2 : 1); + samples++; + } + // Loop, if looping was specified + if (_loopPtr && eosIntern()) { + _ptr = _loopPtr; + _end = _loopEnd; + } + } + return samples; +} + +// Wrapped memory stream, to be used by the ChannelStream class (and possibly others?) +template +class WrappedMemoryStream : public WrappedAudioInputStream { +protected: + byte *_bufferStart; + byte *_bufferEnd; + byte *_pos; + byte *_end; + + inline int16 readIntern(); + inline bool eosIntern() const { return _end == _pos; }; +public: + WrappedMemoryStream(uint bufferSize); + ~WrappedMemoryStream() { free(_bufferStart); } + int readBuffer(int16 *buffer, const int numSamples); + + int16 read() { return readIntern(); } + bool eos() const { return eosIntern(); } + bool isStereo() const { return stereo; } + + void append(const byte *data, uint32 len); +}; + + +template +WrappedMemoryStream::WrappedMemoryStream(uint bufferSize) { + + // Verify the buffer size is sane + if (is16Bit && stereo) + assert((bufferSize & 3) == 0); + else if (is16Bit || stereo) + assert((bufferSize & 1) == 0); + + _bufferStart = (byte *)malloc(bufferSize); + _pos = _end = _bufferStart; + _bufferEnd = _bufferStart + bufferSize; +} + +template +inline int16 WrappedMemoryStream::readIntern() { + //assert(_pos != _end); + int16 val = READSAMPLE(is16Bit, isUnsigned, _pos); + _pos += (is16Bit ? 2 : 1); + + // Wrap around? + if (_pos >= _bufferEnd) + _pos = _pos - (_bufferEnd - _bufferStart); + + return val; +} + +template +int WrappedMemoryStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + while (samples < numSamples && !eosIntern()) { + const byte *endMarker = (_pos > _end) ? _bufferEnd : _end; + const int len = MIN(numSamples, samples + (int)(endMarker - _pos) / (is16Bit ? 2 : 1)); + while (samples < len) { + *buffer++ = READSAMPLE(is16Bit, isUnsigned, _pos); + _pos += (is16Bit ? 2 : 1); + samples++; + } + // Wrap around? + if (_pos >= _bufferEnd) + _pos = _pos - (_bufferEnd - _bufferStart); + } + return samples; +} + +template +void WrappedMemoryStream::append(const byte *data, uint32 len) { + + // Verify the buffer size is sane + if (is16Bit && stereo) + assert((len & 3) == 0); + else if (is16Bit || stereo) + assert((len & 1) == 0); + + if (_end + len > _bufferEnd) { + // Wrap-around case + uint32 size_to_end_of_buffer = _bufferEnd - _end; + len -= size_to_end_of_buffer; + if ((_end < _pos) || (_bufferStart + len >= _pos)) { + warning("WrappedMemoryStream: buffer overflow (A)"); + return; + } + memcpy(_end, data, size_to_end_of_buffer); + memcpy(_bufferStart, data + size_to_end_of_buffer, len); + _end = _bufferStart + len; + } else { + if ((_end < _pos) && (_end + len >= _pos)) { + warning("WrappedMemoryStream: buffer overflow (B)"); + return; + } + memcpy(_end, data, len); + _end += len; + } +} + +template +static AudioInputStream *makeLinearInputStream(const byte *ptr, uint32 len, bool is16Bit, bool isUnsigned, uint loopOffset, uint loopLen) { + if (isUnsigned) { + if (is16Bit) + return new LinearMemoryStream(ptr, len, loopOffset, loopLen); + else + return new LinearMemoryStream(ptr, len, loopOffset, loopLen); + } else { + if (is16Bit) + return new LinearMemoryStream(ptr, len, loopOffset, loopLen); + else + return new LinearMemoryStream(ptr, len, loopOffset, loopLen); + } +} + +template +static WrappedAudioInputStream *makeWrappedInputStream(uint32 len, bool is16Bit, bool isUnsigned) { + if (isUnsigned) { + if (is16Bit) + return new WrappedMemoryStream(len); + else + return new WrappedMemoryStream(len); + } else { + if (is16Bit) + return new WrappedMemoryStream(len); + else + return new WrappedMemoryStream(len); + } +} + +AudioInputStream *makeLinearInputStream(byte _flags, const byte *ptr, uint32 len, uint loopOffset, uint loopLen) { + const bool is16Bit = (_flags & SoundMixer::FLAG_16BITS) != 0; + const bool isUnsigned = (_flags & SoundMixer::FLAG_UNSIGNED) != 0; + if (_flags & SoundMixer::FLAG_STEREO) { + return makeLinearInputStream(ptr, len, is16Bit, isUnsigned, loopOffset, loopLen); + } else { + return makeLinearInputStream(ptr, len, is16Bit, isUnsigned, loopOffset, loopLen); + } +} + +WrappedAudioInputStream *makeWrappedInputStream(byte _flags, uint32 len) { + const bool is16Bit = (_flags & SoundMixer::FLAG_16BITS) != 0; + const bool isUnsigned = (_flags & SoundMixer::FLAG_UNSIGNED) != 0; + if (_flags & SoundMixer::FLAG_STEREO) { + return makeWrappedInputStream(len, is16Bit, isUnsigned); + } else { + return makeWrappedInputStream(len, is16Bit, isUnsigned); + } +} diff --git a/mixer/audiostream.h b/mixer/audiostream.h new file mode 100644 index 00000000000..d404650521e --- /dev/null +++ b/mixer/audiostream.h @@ -0,0 +1,94 @@ +// Residual - Virtual machine to run LucasArts' 3D adventure games +// Copyright (C) 2003 The ScummVM-Residual Team (www.scummvm.org) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef AUDIOSTREAM_H +#define AUDIOSTREAM_H + +#include "../stdafx.h" +#include "../bits.h" + +/** + * Generic input stream for the resampling code. + */ +class AudioInputStream { +public: + virtual ~AudioInputStream() {} + + /** + * Fill the given buffer with up to numSamples samples. + * Returns the actual number of samples read, or -1 if + * a critical error occured (note: you *must* check if + * this value is less than what you requested, this can + * happen when the stream is fully used up). + * For stereo stream, buffer will be filled with interleaved + * left and right channel samples. + * + * For maximum efficency, subclasses should always override + * the default implementation! + */ + virtual int readBuffer(int16 *buffer, const int numSamples) { + int samples; + for (samples = 0; samples < numSamples && !eos(); samples++) { + *buffer++ = read(); + } + return samples; + } + + /** Read a singel (16 bit signed) sample from the stream. */ + virtual int16 read() = 0; + + /** Is this a stereo stream? */ + virtual bool isStereo() const = 0; + + /* End of stream reached? */ + virtual bool eos() const = 0; + + virtual int getRate() const { return -1; } +}; + +class WrappedAudioInputStream : public AudioInputStream { +public: + virtual void append(const byte *data, uint32 len) = 0; +}; + +class ZeroInputStream : public AudioInputStream { +protected: + int _len; +public: + ZeroInputStream(uint len) : _len(len) { } + int readBuffer(int16 *buffer, const int numSamples) { + int samples = MIN(_len, numSamples); + memset(buffer, 0, samples * 2); + _len -= samples; + return samples; + } + int16 read() { assert(_len > 0); _len--; return 0; } + int size() const { return _len; } + bool isStereo() const { return false; } + bool eos() const { return _len <= 0; } +}; + +class MusicStream : public AudioInputStream { +public: + virtual int getRate() const = 0; +}; + + +AudioInputStream *makeLinearInputStream(byte _flags, const byte *ptr, uint32 len, uint loopOffset, uint loopLen); +WrappedAudioInputStream *makeWrappedInputStream(byte _flags, uint32 len); + +#endif diff --git a/mixer/mixer.cpp b/mixer/mixer.cpp new file mode 100644 index 00000000000..b6f37689099 --- /dev/null +++ b/mixer/mixer.cpp @@ -0,0 +1,525 @@ +// Residual - Virtual machine to run LucasArts' 3D adventure games +// Copyright (C) 2003 The ScummVM-Residual Team (www.scummvm.org) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "../stdafx.h" +#include "../bits.h" +#include "../debug.h" + +#include "mixer.h" +#include "rate.h" +#include "audiostream.h" + +SoundMixer *g_mixer = NULL; + +StackLock::StackLock(MutexRef mutex) + : _mutex(mutex) { + lock_mutex(_mutex); +} + +StackLock::~StackLock() { + unlock_mutex(_mutex); +} + +MutexRef create_mutex() { + return (MutexRef) SDL_CreateMutex(); +} + +void lock_mutex(MutexRef mutex) { + SDL_mutexP((SDL_mutex *) mutex); +} + +void unlock_mutex(MutexRef mutex) { + SDL_mutexV((SDL_mutex *) mutex); +} + +void delete_mutex(MutexRef mutex) { + SDL_DestroyMutex((SDL_mutex *) mutex); +} + +/** + * Channels used by the sound mixer. + */ +class Channel { +protected: + SoundMixer *_mixer; + PlayingSoundHandle *_handle; + RateConverter *_converter; + AudioInputStream *_input; + byte _volume; + int8 _pan; + bool _paused; + +public: + int _id; + + Channel(SoundMixer *mixer, PlayingSoundHandle *handle) + : _mixer(mixer), _handle(handle), _converter(0), _input(0), _volume(0), _pan(0), _paused(false), _id(-1) { + assert(mixer); + } + virtual ~Channel(); + void destroy(); + virtual void mix(int16 *data, uint len); + virtual void pause(bool paused) { + _paused = paused; + } + virtual bool isPaused() { + return _paused; + } + virtual void setChannelVolume(const byte volume) { + _volume = volume; + } + virtual void setChannelPan(const int8 pan) { + _pan = pan; + } + virtual int getVolume() const { + return _mixer->getVolume(); + } +}; + +class ChannelRaw : public Channel { + byte *_ptr; +public: + ChannelRaw(SoundMixer *mixer, PlayingSoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, byte volume, int8 pan, int id, uint32 loopStart, uint32 loopEnd); + ~ChannelRaw(); +}; + +class ChannelStream : public Channel { + bool _finished; +public: + ChannelStream(SoundMixer *mixer, PlayingSoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, uint32 buffer_size, byte volume, int8 pan); + void mix(int16 *data, uint len); + void append(void *sound, uint32 size); + void finish() { _finished = true; } +}; + +SoundMixer::SoundMixer() { + _mutex = NULL; + + _premixParam = NULL; + _premixProc = NULL; + int i = 0; + + _outputRate = 0; + + _globalVolume = 0; + + _paused = false; + + for (i = 0; i != NUM_CHANNELS; i++) + _channels[i] = NULL; +} + +SoundMixer::~SoundMixer() { + SDL_CloseAudio(); + for (int i = 0; i != NUM_CHANNELS; i++) { + delete _channels[i]; + } + delete_mutex(_mutex); +} + +void set_sound_proc(SoundProc proc, void *param) { + SDL_AudioSpec desired; + + memset(&desired, 0, sizeof(desired)); + + /* only one format supported at the moment */ + desired.freq = 22050; + desired.format = AUDIO_S16SYS; + desired.channels = 2; + desired.samples = 2048; + desired.callback = proc; + desired.userdata = param; + if (SDL_OpenAudio(&desired, NULL) != 0) { + return; + } + SDL_PauseAudio(0); +} + +void SoundMixer::bindToSystem() { + _mutex = create_mutex(); + _outputRate = 22050; + + if (_outputRate == 0) + error("OSystem returned invalid sample rate"); + + set_sound_proc(mixCallback, this); +} + +void SoundMixer::setupPremix(PremixProc *proc, void *param) { + StackLock lock(_mutex); + _premixParam = param; + _premixProc = proc; +} + +int SoundMixer::newStream(PlayingSoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, uint32 buffer_size, byte volume, int8 pan) { + StackLock lock(_mutex); + return insertChannel(handle, new ChannelStream(this, handle, sound, size, rate, flags, buffer_size, volume, pan)); +} + +void SoundMixer::appendStream(PlayingSoundHandle handle, void *sound, uint32 size) { + StackLock lock(_mutex); + + if (handle == 0) + return; + + int index = handle - 1; + + if ((index < 0) || (index >= NUM_CHANNELS)) { + warning("soundMixer::appendStream has invalid index %d", index); + return; + } + + ChannelStream *chan; + chan = (ChannelStream *)_channels[index]; + if (!chan) { + error("Trying to append to nonexistant streamer : %d", index); + } else { + chan->append(sound, size); + } +} + +void SoundMixer::endStream(PlayingSoundHandle handle) { + StackLock lock(_mutex); + + // Simply ignore stop requests for handles of sounds that already terminated + if (handle == 0) + return; + + int index = handle - 1; + + if ((index < 0) || (index >= NUM_CHANNELS)) { + warning("soundMixer::endStream has invalid index %d", index); + return; + } + + ChannelStream *chan; + chan = (ChannelStream *)_channels[index]; + if (!chan) { + error("Trying to end a nonexistant streamer : %d", index); + } else { + chan->finish(); + } +} + +int SoundMixer::insertChannel(PlayingSoundHandle *handle, Channel *chan) { + int index = -1; + for (int i = 0; i != NUM_CHANNELS; i++) { + if (_channels[i] == NULL) { + index = i; + break; + } + } + if(index == -1) { + warning("SoundMixer::out of mixer slots"); + delete chan; + return -1; + } + + _channels[index] = chan; + if (handle) + *handle = index + 1; + return index; +} + +int SoundMixer::playRaw(PlayingSoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, int id, byte volume, int8 pan, uint32 loopStart, uint32 loopEnd) { + StackLock lock(_mutex); + + // Prevent duplicate sounds + if (id != -1) { + for (int i = 0; i != NUM_CHANNELS; i++) + if (_channels[i] != NULL && _channels[i]->_id == id) + return -1; + } + + return insertChannel(handle, new ChannelRaw(this, handle, sound, size, rate, flags, volume, pan, id, loopStart, loopEnd)); +} + +void SoundMixer::mix(int16 *buf, uint len) { + StackLock lock(_mutex); + + if (_premixProc && !_paused) { + _premixProc(_premixParam, buf, len); + } else { + // zero the buf out + memset(buf, 0, 2 * len * sizeof(int16)); + } + + if (!_paused) { + // now mix all channels + for (int i = 0; i != NUM_CHANNELS; i++) + if (_channels[i] && !_channels[i]->isPaused()) + _channels[i]->mix(buf, len); + } +} + +void SoundMixer::mixCallback(void *s, byte *samples, int len) { + assert(s); + assert(samples); + // Len is the number of bytes in the buffer; we divide it by + // four to get the number of samples (stereo 16 bit). + ((SoundMixer *)s)->mix((int16 *)samples, len >> 2); +} + +void SoundMixer::stopAll() { + StackLock lock(_mutex); + for (int i = 0; i != NUM_CHANNELS; i++) + if (_channels[i]) + _channels[i]->destroy(); +} + +void SoundMixer::stopChannel(int index) { + if ((index < 0) || (index >= NUM_CHANNELS)) { + warning("soundMixer::stop has invalid index %d", index); + return; + } + + StackLock lock(_mutex); + if (_channels[index]) + _channels[index]->destroy(); +} + +void SoundMixer::stopID(int id) { + StackLock lock(_mutex); + for (int i = 0; i != NUM_CHANNELS; i++) { + if (_channels[i] != NULL && _channels[i]->_id == id) { + _channels[i]->destroy(); + return; + } + } +} + +void SoundMixer::stopHandle(PlayingSoundHandle handle) { + StackLock lock(_mutex); + + // Simply ignore stop requests for handles of sounds that already terminated + if (handle == 0) + return; + + int index = handle - 1; + + if ((index < 0) || (index >= NUM_CHANNELS)) { + warning("soundMixer::stopHandle has invalid index %d", index); + return; + } + + if (_channels[index]) + _channels[index]->destroy(); +} + +void SoundMixer::setChannelVolume(PlayingSoundHandle handle, byte volume) { + StackLock lock(_mutex); + + if (handle == 0) + return; + + int index = handle - 1; + + if ((index < 0) || (index >= NUM_CHANNELS)) { + warning("soundMixer::setChannelVolume has invalid index %d", index); + return; + } + + if (_channels[index]) + _channels[index]->setChannelVolume(volume); +} + +void SoundMixer::setChannelPan(PlayingSoundHandle handle, int8 pan) { + StackLock lock(_mutex); + + if (handle == 0) + return; + + int index = handle - 1; + + if ((index < 0) || (index >= NUM_CHANNELS)) { + warning("soundMixer::setChannelVolume has invalid index %d", index); + return; + } + + if (_channels[index]) + _channels[index]->setChannelPan(pan); +} + +void SoundMixer::pauseAll(bool paused) { + _paused = paused; +} + +void SoundMixer::pauseChannel(int index, bool paused) { + if ((index < 0) || (index >= NUM_CHANNELS)) { + warning("soundMixer::pauseChannel has invalid index %d", index); + return; + } + + StackLock lock(_mutex); + if (_channels[index]) + _channels[index]->pause(paused); +} + +void SoundMixer::pauseID(int id, bool paused) { + StackLock lock(_mutex); + for (int i = 0; i != NUM_CHANNELS; i++) { + if (_channels[i] != NULL && _channels[i]->_id == id) { + _channels[i]->pause(paused); + return; + } + } +} + +void SoundMixer::pauseHandle(PlayingSoundHandle handle, bool paused) { + StackLock lock(_mutex); + + // Simply ignore pause/unpause requests for handles of sound that alreayd terminated + if (handle == 0) + return; + + int index = handle - 1; + + if ((index < 0) || (index >= NUM_CHANNELS)) { + warning("soundMixer::pauseHandle has invalid index %d", index); + return; + } + + if (_channels[index]) + _channels[index]->pause(paused); +} + +void SoundMixer::setVolume(int volume) { + // Check range + if (volume > 256) + volume = 256; + else if (volume < 0) + volume = 0; + + _globalVolume = volume; +} + +Channel::~Channel() { + delete _converter; + delete _input; + if (_handle) + *_handle = 0; +} + +void Channel::destroy() { + for (int i = 0; i != SoundMixer::NUM_CHANNELS; i++) + if (_mixer->_channels[i] == this) + _mixer->_channels[i] = 0; + delete this; +} + +/* len indicates the number of sample *pairs*. So a value of + 10 means that the buffer contains twice 10 sample, each + 16 bits, for a total of 40 bytes. + */ +void Channel::mix(int16 *data, uint len) { + assert(_input); + if (_input->eos()) { + // TODO: call drain method + destroy(); + } else { + assert(_converter); + + // The pan value ranges from -127 to +127. That's 255 different values. + // From the channel pan/volume and the global volume, we compute the + // effective volume for the left and right channel. + // Note the slightly odd divisor: the 255 reflects the fact that + // the maximal value for _volume is 255, while the 254 is there + // because the maximal left/right pan value is 2*127 = 254. + // The value getVolume() returns is in the range 0 - 256. + // Hence, the vol_l/vol_r values will be in that range, too + + int vol = getVolume() * _volume; + st_volume_t vol_l = (127 - _pan) * vol / (255 * 254); + st_volume_t vol_r = (127 + _pan) * vol / (255 * 254); + + _converter->flow(*_input, data, len, vol_l, vol_r); + } +} + +/* RAW mixer */ +ChannelRaw::ChannelRaw(SoundMixer *mixer, PlayingSoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, byte volume, int8 pan, int id, uint32 loopStart, uint32 loopEnd) + : Channel(mixer, handle) { + _id = id; + _ptr = (byte *)sound; + _volume = volume; + _pan = pan; + + // Create the input stream + if (flags & SoundMixer::FLAG_LOOP) { + if (loopEnd == 0) { + _input = makeLinearInputStream(flags, _ptr, size, 0, size); + } else { + assert(loopStart < loopEnd && loopEnd <= size); + _input = makeLinearInputStream(flags, _ptr, size, loopStart, loopEnd - loopStart); + } + } else { + _input = makeLinearInputStream(flags, _ptr, size, 0, 0); + } + + // Get a rate converter instance + _converter = makeRateConverter(rate, mixer->getOutputRate(), _input->isStereo(), (flags & SoundMixer::FLAG_REVERSE_STEREO) != 0); + + if (!(flags & SoundMixer::FLAG_AUTOFREE)) + _ptr = 0; +} + +ChannelRaw::~ChannelRaw() { + free(_ptr); +} + +ChannelStream::ChannelStream(SoundMixer *mixer, PlayingSoundHandle *handle, + void *sound, uint32 size, uint rate, + byte flags, uint32 buffer_size, byte volume, int8 pan) + : Channel(mixer, handle) { + _volume = volume; + _pan = pan; + assert(size <= buffer_size); + + // Create the input stream + _input = makeWrappedInputStream(flags, buffer_size); + + // Append the initial data + ((WrappedAudioInputStream *)_input)->append((const byte *)sound, size); + + // Get a rate converter instance + _converter = makeRateConverter(rate, mixer->getOutputRate(), _input->isStereo(), (flags & SoundMixer::FLAG_REVERSE_STEREO) != 0); + + _finished = false; +} + +void ChannelStream::append(void *data, uint32 len) { + ((WrappedAudioInputStream *)_input)->append((const byte *)data, len); +} + +void ChannelStream::mix(int16 *data, uint len) { + assert(_input); + if (_input->eos()) { + // TODO: call drain method + + // Normally, the stream stays around even if all its data is used up. + // This is in case more data is streamed into it. To make the stream + // go away, one can either stop() it (which takes effect immediately, + // ignoring any remaining sound data), or finish() it, which means + // it will finish playing before it terminates itself. + if (_finished) { + destroy(); + } + } else { + // Invoke the parent implementation. + Channel::mix(data, len); + } +} diff --git a/mixer/mixer.h b/mixer/mixer.h new file mode 100644 index 00000000000..7cf6ec57257 --- /dev/null +++ b/mixer/mixer.h @@ -0,0 +1,145 @@ +// Residual - Virtual machine to run LucasArts' 3D adventure games +// Copyright (C) 2003 The ScummVM-Residual Team (www.scummvm.org) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef SOUND_MIXER_H +#define SOUND_MIXER_H + +#include "../stdafx.h" +#include "../bits.h" +#include + +typedef uint32 PlayingSoundHandle; +typedef struct Mutex *MutexRef; +typedef void (*SoundProc)(void *param, byte *buf, int len); +typedef int (*TimerProc)(int interval); + +class StackLock { + MutexRef _mutex; +public: + StackLock(MutexRef mutex); + ~StackLock(); +}; + +MutexRef create_mutex(); +void lock_mutex(MutexRef mutex); +void unlock_mutex(MutexRef mutex); +void delete_mutex(MutexRef mutex); + +class Channel; + +class SoundMixer { + friend class Channel; +public: + typedef void PremixProc (void *param, int16 *data, uint len); + + enum { + NUM_CHANNELS = 16 + }; + + enum { + FLAG_UNSIGNED = 1 << 0, // unsigned samples (default: signed) + FLAG_STEREO = 1 << 1, // sound is in stereo (default: mono) + FLAG_16BITS = 1 << 2, // sound is 16 bits wide (default: 8bit) + FLAG_AUTOFREE = 1 << 3, // sound buffer is freed automagically at the end of playing + FLAG_REVERSE_STEREO = 1 << 4, // reverse the left and right stereo channel + FLAG_LOOP = 1 << 5 // loop the audio + }; + +private: + MutexRef _mutex; + + void *_premixParam; + PremixProc *_premixProc; + + uint _outputRate; + + int _globalVolume; + + bool _paused; + + Channel *_channels[NUM_CHANNELS]; + +public: + SoundMixer(); + ~SoundMixer(); + + void bindToSystem(); + + void setupPremix(PremixProc *proc, void *param); + + // start playing a raw sound + int playRaw(PlayingSoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, + int id = -1, byte volume = 255, int8 pan = 0, uint32 loopStart = 0, uint32 loopEnd = 0); + + /** Start a new stream. */ + int newStream(PlayingSoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, uint32 buffer_size, byte volume = 255, int8 pan = 0); + + /** Append to an existing stream. */ + void appendStream(PlayingSoundHandle handle, void *sound, uint32 size); + + /** Mark a stream as finished - it will play all its remaining data, then stop. */ + void endStream(PlayingSoundHandle handle); + + /** stop all currently playing sounds */ + void stopAll(); + + /** stop playing the given channel */ + void stopChannel(int channel); + + /** stop playing the sound with given ID */ + void stopID(int id); + + /** stop playing the channel for the given handle */ + void stopHandle(PlayingSoundHandle handle); + + /** pause/unpause all channels */ + void pauseAll(bool paused); + + /** pause/unpause the given channel */ + void pauseChannel(int index, bool paused); + + /** pause/unpause the sound with the given ID */ + void pauseID(int id, bool paused); + + /** pause/unpause the channel for the given handle */ + void pauseHandle(PlayingSoundHandle handle, bool paused); + + /** set the channel volume for the given handle (0 - 255) */ + void setChannelVolume(PlayingSoundHandle handle, byte volume); + + /** set the channel pan for the given handle (-127 ... 0 ... 127) (left ... center ... right)*/ + void setChannelPan(PlayingSoundHandle handle, int8 pan); + + /** set the global volume, 0-256 */ + void setVolume(int volume); + + /** query the global volume, 0-256 */ + int getVolume() const { return _globalVolume; } + + /** query the output rate in kHz */ + uint getOutputRate() const { return _outputRate; } + +private: + int insertChannel(PlayingSoundHandle *handle, Channel *chan); + + /** main mixer method */ + void mix(int16 *buf, uint len); + + static void mixCallback(void *s, byte *samples, int len); +}; + +#endif diff --git a/mixer/rate.cpp b/mixer/rate.cpp new file mode 100644 index 00000000000..0fd3322aede --- /dev/null +++ b/mixer/rate.cpp @@ -0,0 +1,235 @@ +// Residual - Virtual machine to run LucasArts' 3D adventure games +// Copyright (C) 2003 The ScummVM-Residual Team (www.scummvm.org) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +/* + * The code in this file is based on code with Copyright 1998 Fabrice Bellard + * Fabrice original code is part of SoX (http://sox.sourceforge.net). + * Max Horn adapted that code to the needs of ScummVM and rewrote it partial, + * in the process removing any use of floating point arithmetic. Various other + * improvments over the original code were made. + */ + +#include "../stdafx.h" +#include "rate.h" +#include "audiostream.h" +#include "../debug.h" + +/** + * The precision of the fractional computations used by the rate converter. + * Normally you should never have to modify this value. + */ +#define FRAC_BITS 16 + +/** + * The size of the intermediate input cache. Bigger values may increase + * performance, but only until some point (depends largely on cache size, + * target processor and various other factors), at which it will decrease + * again. + */ +#define INTERMEDIATE_BUFFER_SIZE 512 + + +/** + * Audio rate converter based on simple linear Interpolation. + * + * The use of fractional increment allows us to use no buffer. It + * avoid the problems at the end of the buffer we had with the old + * method which stored a possibly big buffer of size + * lcm(in_rate,out_rate). + * + * Limited to sampling frequency <= 65535 Hz. + */ + +template +class LinearRateConverter : public RateConverter { +protected: + st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE]; + const st_sample_t *inPtr; + int inLen; + + /** fractional position of the output stream in input stream unit */ + unsigned long opos, opos_frac; + + /** fractional position increment in the output stream */ + unsigned long opos_inc, opos_inc_frac; + + /** position in the input stream (integer) */ + unsigned long ipos; + + /** last sample(s) in the input stream (left/right channel) */ + st_sample_t ilast[2]; + /** current sample(s) in the input stream (left/right channel) */ + st_sample_t icur[2]; + +public: + LinearRateConverter(st_rate_t inrate, st_rate_t outrate); + int flow(AudioInputStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r); + int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { + return (ST_SUCCESS); + } +}; + + +/* + * Prepare processing. + */ +template +LinearRateConverter::LinearRateConverter(st_rate_t inrate, st_rate_t outrate) { + unsigned long incr; + + if (inrate == outrate) { + error("Input and Output rates must be different to use rate effect"); + } + + if (inrate >= 65536 || outrate >= 65536) { + error("rate effect can only handle rates < 65536"); + } + + opos_frac = 0; + opos = 1; + + /* increment */ + incr = (inrate << FRAC_BITS) / outrate; + + opos_inc_frac = incr & ((1UL << FRAC_BITS) - 1); + opos_inc = incr >> FRAC_BITS; + + ipos = 0; + + ilast[0] = ilast[1] = 0; + icur[0] = icur[1] = 0; + + inLen = 0; +} + +/* + * Processed signed long samples from ibuf to obuf. + * Return number of samples processed. + */ +template +int LinearRateConverter::flow(AudioInputStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { + st_sample_t *ostart, *oend; + st_sample_t out[2]; + + const int numChannels = stereo ? 2 : 1; + int i; + + ostart = obuf; + oend = obuf + osamp * 2; + + while (obuf < oend) { + + // read enough input samples so that ipos > opos + while (ipos <= opos) { + // Check if we have to refill the buffer + if (inLen == 0) { + inPtr = inBuf; + inLen = input.readBuffer(inBuf, ARRAYSIZE(inBuf)); + if (inLen <= 0) + goto the_end; + } + for (i = 0; i < numChannels; i++) { + ilast[i] = icur[i]; + icur[i] = *inPtr++; + inLen--; + } + ipos++; + } + + // Loop as long as the outpos trails behind, and as long as there is + // still space in the output buffer. + while (ipos > opos) { + + // interpolate + out[0] = out[1] = (st_sample_t)(ilast[0] + (((icur[0] - ilast[0]) * opos_frac + (1UL << (FRAC_BITS-1))) >> FRAC_BITS)); + + if (stereo) { + // interpolate + out[reverseStereo ? 0 : 1] = (st_sample_t)(ilast[1] + (((icur[1] - ilast[1]) * opos_frac + (1UL << (FRAC_BITS-1))) >> FRAC_BITS)); + } + + // output left channel + clampedAdd(*obuf++, (out[0] * (int)vol_l) >> 8); + + // output right channel + clampedAdd(*obuf++, (out[1] * (int)vol_r) >> 8); + + // Increment output position + unsigned long tmp = opos_frac + opos_inc_frac; + opos += opos_inc + (tmp >> FRAC_BITS); + opos_frac = tmp & ((1UL << FRAC_BITS) - 1); + + // Abort if we reached the end of the output buffer + if (obuf >= oend) + goto the_end; + } + } + +the_end: + return (ST_SUCCESS); +} + +/** + * Simple audio rate converter for the case that the inrate equals the outrate. + */ +template +class CopyRateConverter : public RateConverter { +public: + virtual int flow(AudioInputStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) { + int16 tmp[2]; + st_size_t len = osamp; + assert(input.isStereo() == stereo); + while (!input.eos() && len--) { + tmp[0] = tmp[1] = input.read(); + if (stereo) + tmp[reverseStereo ? 0 : 1] = input.read(); + + // output left channel + clampedAdd(*obuf++, (tmp[0] * (int)vol_l) >> 8); + + // output right channel + clampedAdd(*obuf++, (tmp[1] * (int)vol_r) >> 8); + } + return (ST_SUCCESS); + } + virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) { + return (ST_SUCCESS); + } +}; + +/** + * Create and return a RateConverter object for the specified input and output rates. + */ +RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo) { + if (inrate != outrate) { + if (stereo) { + if (reverseStereo) + return new LinearRateConverter(inrate, outrate); + else + return new LinearRateConverter(inrate, outrate); + } else + return new LinearRateConverter(inrate, outrate); + } else { + if (stereo) { + if (reverseStereo) + return new CopyRateConverter(); + else + return new CopyRateConverter(); + } else + return new CopyRateConverter(); + } +} diff --git a/mixer/rate.h b/mixer/rate.h new file mode 100644 index 00000000000..9b8a0c0f51c --- /dev/null +++ b/mixer/rate.h @@ -0,0 +1,73 @@ +// Residual - Virtual machine to run LucasArts' 3D adventure games +// Copyright (C) 2003 The ScummVM-Residual Team (www.scummvm.org) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef SOUND_RATE_H +#define SOUND_RATE_H + +#include "../bits.h" + +class AudioInputStream; + +typedef int16 st_sample_t; +typedef uint16 st_volume_t; +typedef uint32 st_size_t; +typedef uint32 st_rate_t; + +/* Minimum and maximum values a sample can hold. */ +#define ST_SAMPLE_MAX 0x7fffL +#define ST_SAMPLE_MIN (-ST_SAMPLE_MAX - 1L) + +#define ST_EOF (-1) +#define ST_SUCCESS (0) + +static inline void clampedAdd(int16& a, int b) { + register int val; +#ifdef OUTPUT_UNSIGNED_AUDIO + val = (a ^ 0x8000) + b; +#else + val = a + b; +#endif + + if (val > ST_SAMPLE_MAX) + val = ST_SAMPLE_MAX; + else if (val < ST_SAMPLE_MIN) + val = ST_SAMPLE_MIN; + +#ifdef OUTPUT_UNSIGNED_AUDIO + a = ((int16)val) ^ 0x8000; +#else + a = val; +#endif +} + +// Q&D hack to get this SOX stuff to work +#define st_report warning +#define st_warn warning +#define st_fail error + + +class RateConverter { +public: + RateConverter() {} + virtual ~RateConverter() {} + virtual int flow(AudioInputStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) = 0; + virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) = 0; +}; + +RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo = false); + +#endif diff --git a/residual.vcproj b/residual.vcproj index 410dafe96e0..63f42970b8c 100644 --- a/residual.vcproj +++ b/residual.vcproj @@ -191,12 +191,6 @@ - - - - @@ -282,6 +276,40 @@ RelativePath="vector3d.h"> + + + + + + + + + + + + + + + + + + + + -//#include "file.h" -//#include "util.h" #include "smush.h" -//#include "system.h" +#include "mixer/mixer.h" #include Smush::Smush() { @@ -33,18 +31,11 @@ Smush::Smush() { _speed = 0; _channels = -1; _freq = 0; -// g_system = new OSystem(); -// _mixer = new SoundMixer(); -// _mixer->bindToSystem(g_system); -// _mixer->setVolume(100); -// _mixer->setMusicVolume(100); -// _soundHandle = 0; + _soundHandle = 0; } Smush::~Smush() { deinit(); -// delete g_system; -// delete _mixer; } void Smush::init() { @@ -68,23 +59,24 @@ void Smush::handleBlocky16(byte *src) { void decompressVima(const char *src, int16 *dest, int destLen); void vimaInit(); +extern SoundMixer *g_mixer; + void Smush::handleWave(const byte *src, uint32 size) { int16 *dst = new int16[size * _channels]; - decompressVima((char *)src, dst, size * _channels * 2); delete dst; -/* // convert our LE ones to BE - for (uint32_t j = 0; j < size * _channels; j++) + decompressVima((char *)src, dst, size * _channels * 2); + // convert our LE ones to BE + for (uint32 j = 0; j < size * _channels; j++) dst[j] = SWAP_BYTES_16(dst[j]); int flags = SoundMixer::FLAG_16BITS | SoundMixer::FLAG_AUTOFREE; if (_channels == 2) flags |= SoundMixer::FLAG_STEREO; if (_soundHandle == 0) - _mixer->newStream(&_soundHandle, (byte *)dst, size * _channels * 2, _freq, + g_mixer->newStream(&_soundHandle, (byte *)dst, size * _channels * 2, _freq, flags, 300000); else - _mixer->appendStream(_soundHandle, (byte *)dst, size * _channels * 2); -*/ + g_mixer->appendStream(_soundHandle, (byte *)dst, size * _channels * 2); } void Smush::handleFrame() { @@ -196,7 +188,7 @@ void Smush::play(const char *filename, const char *directory) { // SDL_BlitSurface(image, &src, screen, NULL); // SDL_UpdateRect(screen, 0, 0, 0, 0); -// SDL_Delay(_speed / 1000); + SDL_Delay(_speed / 1000); } } diff --git a/smush.h b/smush.h index 7ff3eb5720f..17d604b3429 100644 --- a/smush.h +++ b/smush.h @@ -22,7 +22,7 @@ #include "debug.h" #include #include "blocky16.h" -//#include "mixer.h" +#include "mixer/mixer.h" class File { private: @@ -65,8 +65,7 @@ private: int32 _nbframes; Blocky16 _blocky16; File _file; -// SoundMixer *_mixer; -// PlayingSoundHandle _soundHandle; + PlayingSoundHandle _soundHandle; int32 _frame; bool _alreadyInit; diff --git a/sound.cpp b/sound.cpp index 7e12ff33710..7feb77efb45 100644 --- a/sound.cpp +++ b/sound.cpp @@ -17,11 +17,402 @@ #include "stdafx.h" #include "sound.h" +#include "mixer/mixer.h" #include "bits.h" #include "debug.h" #include #include +struct imuseTableEntry { + int stateNum; + const char *filename; +}; + +static const imuseTableEntry grimMusicTable[] = { + { 1001, "1001 - Manny's Office.IMC" }, + { 1002, "1002 - Mr. Frustration.IMC" }, + { 1003, "1002 - Mr. Frustration.IMC" }, + { 1004, "1002 - Mr. Frustration.IMC" }, + { 1005, "1002 - Mr. Frustration.IMC" }, + { 1006, "1002 - Mr. Frustration.IMC" }, + { 1007, "1002 - Mr. Frustration.IMC" }, + { 1008, "1008 - Domino's Office.IMC" }, + { 1009, "1009 - Copal's Office.IMC" }, + { 1010, "1010 - Ledge.IMC" }, + { 1011, "1011 - Roof.IMC" }, + { 1020, "1020 - Tube Room.IMC" }, + { 1021, "1021 - Brennis.IMC" }, + { 1022, "1022 - Lobby.IMC" }, + { 1023, "1023 - Packing Room.IMC" }, + { 1030, "1030 - Garage.IMC" }, + { 1031, "1031 - Glottis' Shop.IMC" }, + { 1032, "1030 - Garage.IMC" }, + { 1040, "1040 - Festival Wet.IMC" }, + { 1041, "1041 - Festival Dry.IMC" }, + { 1042, "1041 - Festival Dry.IMC" }, + { 1043, "1041 - Festival Dry.IMC" }, + { 1044, "1040 - Festival Wet.IMC" }, + { 1050, "1050 - Headquarters.IMC" }, + { 1060, "1060 - Real World.IMC" }, + { 1070, "1070 - Stump Room.IMC" }, + { 1071, "1071 - Signpost Room.IMC" }, + { 1072, "1072 - Navigation.IMC" }, + { 1073, "1071 - Signpost Room.IMC" }, + { 1074, "1074 - Bone Wagon.IMC" }, + { 1075, "1075 - Spider's Eye.IMC" }, + { 1076, "1076 - Spider Room.IMC" }, + { 1077, "1077 - Tree Pump Amb.IMC" }, + { 1078, "1078 - Tree Pump.IMC" }, + { 1079, "1071 - Signpost Room.IMC" }, + { 1080, "1080 - Beaver Room Lobby.IMC" }, + { 1081, "1081 - Beaver Dam.IMC" }, + { 1082, "1083 - Beaver Room.IMC" }, + { 1083, "1083 - Beaver Room.IMC" }, + { 1084, "1084 - Foggy Cactus.IMC" }, + { 1085, "1085 - Rubamat Exterior.IMC" }, + { 1086, "1086 - Blue Hector.IMC" }, + { 1100, "1109 - Cafe Exterior.IMC" }, + { 1101, "1101 - Cafe Office.IMC" }, + { 1102, "1102 - Cafe Intercom.IMC" }, + { 1103, "1103 - Coat Check.IMC" }, + { 1104, "1104 - Lupe.IMC" }, + { 1105, "1106 - Glottis Noodle.IMC" }, + { 1106, "1106 - Glottis Noodle.IMC" }, + { 1107, "1101 - Cafe Office.IMC" }, + { 1108, "1108 - Casino Interior.IMC" }, + { 1109, "1109 - Cafe Exterior.IMC" }, + { 1110, "1110 - Cafe Ledge.IMC" }, + { 1111, "1108 - Casino Interior.IMC" }, + { 1112, "1112 - Rusty Sans Vox.IMC" }, + { 1120, "1120 - Elevator Station.IMC" }, + { 1121, "1122 - Blue Exterior.IMC" }, + { 1122, "1122 - Blue Exterior.IMC" }, + { 1123, "1123 - Blue Casket Ins.IMC" }, + { 1124, "1124 - Blue Casket Amb.IMC" }, + { 1125, "1125 - Smooth Hector.IMC" }, + { 1126, "1122 - Blue Exterior.IMC" }, + { 1127, "1127 - Limbo Dock.IMC" }, + { 1128, "1128 - Limbo Talk.IMC" }, + { 1129, "1129 - Limbo Poem.IMC" }, + { 1130, "1130 - Dry Dock.IMC" }, + { 1131, "1131 - Dry Dock Strike.IMC" }, + { 1132, "1132 - Lighthouse Ext.IMC" }, + { 1133, "1133 - Lola's Last.IMC" }, + { 1140, "1140 - Police Station.IMC" }, + { 1141, "1141 - Police Interior.IMC" }, + { 1142, "1141 - Police Interior.IMC" }, + { 1143, "1143 - Morgue.IMC" }, + { 1144, "1140 - Police Station.IMC" }, + { 1145, "1145 - Bridge Blimp.IMC" }, + { 1146, "1146 - LOL Security Ext.IMC" }, + { 1147, "1147 - LOL Security Int.IMC" }, + { 1148, "1148 - Carla's Life.IMC" }, + { 1149, "1149 - Bomb.IMC" }, + { 1150, "1150 - Track Stairs.IMC" }, + { 1151, "1151 - Track Stairs.IMC" }, + { 1152, "1152 - Track Stairs.IMC" }, + { 1153, "1153 - Track Base.IMC" }, + { 1154, "1154 - Kitty Hall.IMC" }, + { 1155, "1155 - Sanspoof.IMC" }, + { 1156, "1156 - Kitty Stables.IMC" }, + { 1160, "1160 - High Roller Hall.IMC" }, + { 1161, "1161 - High Roller Lnge.IMC" }, + { 1162, "1162 - Glottis Gambling.IMC" }, + { 1163, "1163 - Max's Office.IMC" }, + { 1164, "1125 - Hector Steps Out.IMC" }, + { 1165, "1125 - Hector Steps Out.IMC" }, + { 1166, "1125 - Hector Steps Out.IMC" }, + { 1167, "1167 - Dillopede Elev.IMC" }, + { 1168, "1168 - Dillopede Elev.IMC" }, + { 1169, "1169 - Dillopede Elev.IMC" }, + { 1170, "1170 - Extendo Bridge.IMC" }, + { 1171, "1170 - Extendo Bridge.IMC" }, + { 1172, "1170 - Extendo Bridge.IMC" }, + { 1173, "1173 - Scrimshaw Int.IMC" }, + { 1174, "1174 - Scrim Sleep.IMC" }, + { 1180, "1180 - Note to Manny.IMC" }, + { 1181, "1155 - Sanspoof.IMC" }, + { 1190, "1106 - Glottis Noodle.IMC" }, + { 1191, "1106 - Glottis Noodle.IMC" }, + { 1201, "1201 - Lola Zapata.IMC" }, + { 1202, "1202 - Inside the Lola.IMC" }, + { 1203, "1203 - Engine Room.IMC" }, + { 1204, "1204 - Porthole.IMC" }, + { 1205, "1204 - Porthole.IMC" }, + { 1210, "1210 - Sunken Lola.IMC" }, + { 1211, "1211 - Pearl Crater Sub.IMC" }, + { 1220, "1220 - Miner's Room.IMC" }, + { 1221, "1221 - Miner's Room.IMC" }, + { 1222, "1222 - Exterior Airlock.IMC" }, + { 1223, "1223 - Factory Hub.IMC" }, + { 1224, "1224 - Foreman's Office.IMC" }, + { 1230, "1230 - Vault Door.IMC" }, + { 1231, "1231 - Outer Vault.IMC" }, + { 1232, "1232 - Inner Vault.IMC" }, + { 1233, "1233 - Ashtray Room.IMC" }, + { 1234, "1234 - Ashtray Scary.IMC" }, + { 1235, "1235 - Ashtray Pensive.IMC" }, + { 1236, "1236 - Domino's Room.IMC" }, + { 1240, "1240 - Conveyor Under.IMC" }, + { 1241, "1240 - Conveyor Under.IMC" }, + { 1242, "1241 - Crane Intro.IMC" }, + { 1243, "1243 - Anchor Room.IMC" }, + { 1244, "1244 - Glottis Hanging.IMC" }, + { 1245, "1245 - End of the World.IMC" }, + { 1246, "1246 - End World Later.IMC" }, + { 1247, "1241 - Crane Intro.IMC" }, + { 1250, "1250 - Upper Beach.IMC" }, + { 1251, "1250 - Upper Beach.IMC" }, + { 1252, "1252 - Lower Beach Boat.IMC" }, + { 1253, "1253 - Lamancha Sub.IMC" }, + { 1254, "1254 - Crane Later.IMC" }, + { 1301, "1301 - Temple Gate.IMC" }, + { 1302, "1301 - Temple Gate.IMC" }, + { 1303, "1303 - Truck Depot.IMC" }, + { 1304, "1304 - Mayan Train Sta.IMC" }, + { 1305, "1305 - Mayan Workshop.IMC" }, + { 1306, "1306 - Mayan Train Pad.IMC" }, + { 1307, "1307 - Mechanic's Kitch.IMC" }, + { 1310, "1310 - Jello Bomb.IMC" }, + { 1311, "1310 - Jello Bomb.IMC" }, + { 1312, "1125 - Smooth Hector.IMC" }, + { 1313, "1125 - Smooth Hector.IMC" }, + { 1314, "1125 - Smooth Hector.IMC" }, + { 1315, "1122 - Blue Exterior.IMC" }, + { 1316, "1122 - Blue Exterior.IMC" }, + { 1317, "1122 - Blue Exterior.IMC" }, + { 1318, "1332 - Hector's Foyer.IMC" }, + { 1319, "1319 - Florist Video.IMC" }, + { 1320, "1320 - New LSA HQ.IMC" }, + { 1321, "1321 - LSA Sewer.IMC" }, + { 1322, "1321 - LSA Sewer.IMC" }, + { 1323, "1323 - Sewer Maze.IMC" }, + { 1324, "1324 - Albinozod.IMC" }, + { 1325, "1325 - Florist Shop.IMC" }, + { 1326, "1326 - Florist Shop Int.IMC" }, + { 1327, "1327 - Florist OK.IMC" }, + { 1328, "1323 - Sewer Maze.IMC" }, + { 1329, "1329 - Theater Backstag.IMC" }, + { 1330, "1330 - Lemans Lobby.IMC" }, + { 1331, "1330 - Lemans Lobby.IMC" }, + { 1332, "1332 - Hector's Foyer.IMC" }, + { 1333, "1333 - Brennis Talk.IMC" }, + { 1334, "1334 - Albino Trap.IMC" }, + { 1340, "1342 - Neon Ledge.IMC" }, + { 1350, "1350 - Meadow Flowers.IMC" }, + { 1351, "1351 - Meadow.IMC" }, + { 1352, "1352 - Car Head.IMC" }, + { 1353, "1353 - Greenhouse Appr.IMC" }, + { 1354, "1354 - Game Ending.IMC" }, + { 1355, "1355 - Shootout.IMC" }, + { 1400, "1400 - Start Credits.IMC" }, + { 1401, "1401 - Smooth Hector.IMC" }, + { 2001, "2001 - Climb Rope.IMC" }, + { 2010, "2010 - Glottis OK.IMC" }, + { 2020, "2020 - Reap Bruno.IMC" }, + { 2030, "2030 - Ledgepeckers.IMC" }, + { 2050, "2050 - Glottis Heart.IMC" }, + { 2055, "2055 - Slingshot Bone.IMC" }, + { 2060, "2060 - Glott Tree Fall.IMC" }, + { 2070, "2070 - Beaver Fly.IMC" }, + { 2071, "2071 - Beaver Sink.IMC" }, + { 2080, "2080 - Meet Velasco.IMC" }, + { 2140, "2140 - Ooo Bonewagon.IMC" }, + { 2141, "2141 - Ooo Meche.IMC" }, + { 2155, "2155 - Find Detector.IMC" }, + { 2156, "2156 - Glott Drink Wine.IMC" }, + { 2157, "2157 - Glott No Wine.IMC" }, + { 2161, "2161 - Raoul Appears.IMC" }, + { 2162, "2162 - Raoul KO.IMC" }, + { 2163, "2163 - Raoul Dissed.IMC" }, + { 2165, "2165 - Fake Tix.IMC" }, + { 2180, "2180 - Befriend Commies.IMC" }, + { 2186, "2186 - Nick Punchout.IMC" }, + { 2200, "2200 - Year 3 Iris.IMC" }, + { 2210, "2210 - Hit Men.IMC" }, + { 2230, "2230 - Open Vault.IMC" }, + { 2235, "2235 - Dead Tix.IMC" }, + { 2240, "2240 - Sprinkler.IMC" }, + { 2250, "2250 - Crane Track.IMC" }, + { 2255, "2255 - Crane Fall.IMC" }, + { 2300, "2300 - Yr 4 Iris.IMC" }, + { 2301, "2301 - Pop Bruno Casket.IMC" }, + { 2310, "2310 - Rocket Idea.IMC" }, + { 2320, "2320 - Jello Suspense.IMC" }, + { 2325, "2325 - Lumbago Lemo.IMC" }, + { 2327, "2327 - Breath Mint.IMC" }, + { 2330, "2330 - Pigeon Fly.IMC" }, + { 2340, "2340 - Coffee On Boys.IMC" }, + { 2350, "2350 - Sprout Aha.IMC" }, + { 2360, "2360 - Chowchilla Bye.IMC" }, + { 2370, "2370 - Salvador Death.IMC" }, + { 2399, "2399 - End Credits.IMC" } +}; + +Mixer *Mixer::instance_ = NULL; + +Mixer *Mixer::instance() { + if (instance_ == NULL) + instance_ = new Mixer; + return instance_; +} + +void mixerCallback(void *userdata, int16 *stream, uint len) { + Mixer *m = static_cast(userdata); + m->getAudio(stream, len * 2); +} + +extern SoundMixer *g_mixer; + +Mixer::Mixer() : + musicSound_(NULL), seqSound_(NULL) +{ +} + +void Mixer::start() { + Sound::init(); + g_mixer->setupPremix(mixerCallback, this); + g_mixer->bindToSystem(); + g_mixer->setVolume(100); +} + +void Mixer::playVoice(Sound *s) { + s->reset(); + voiceSounds_.push_back(s); +} + +void Mixer::playSfx(Sound *s) { + s->reset(); + sfxSounds_.push_back(s); +} + +void Mixer::stopSfx(Sound *s) { + for (sound_list::iterator i = sfxSounds_.begin(); + i != sfxSounds_.end(); ) { + if (*i == s) + i = sfxSounds_.erase(i); + else + i++; + } +} + +void Mixer::stopVoice(Sound *s) { + for (sound_list::iterator i = voiceSounds_.begin(); + i != voiceSounds_.end(); ) { + if (*i == s) + i = voiceSounds_.erase(i); + else + i++; + } +} + +static int compareStates(const void *p1, const void *p2) { + const imuseTableEntry *e1 = static_cast(p1); + const imuseTableEntry *e2 = static_cast(p2); + return e1->stateNum - e2->stateNum; +} + +void Mixer::setImuseState(int state) { + Sound *newSound = NULL; + + if (state != 1000) { + imuseTableEntry key; + key.stateNum = state; + const imuseTableEntry *e = static_cast + (std::bsearch(&key, grimMusicTable, + sizeof(grimMusicTable) / sizeof(grimMusicTable[0]), + sizeof(grimMusicTable[0]), compareStates)); + if (e == NULL) { + warning("Unknown IMuse state %d\n", state); + return; + } + + newSound = ResourceLoader::instance()->loadSound(e->filename); + if (newSound == NULL) { + warning("Could not find music file %s\n", e->filename); + return; + } + } + + if (newSound != musicSound_) { + if (newSound != NULL) + newSound->reset(); + musicSound_ = newSound; + } +} + +void Mixer::setImuseSeq(int state) { + Sound *newSound = NULL; + + if (state != 2000) { + imuseTableEntry key; + key.stateNum = state; + const imuseTableEntry *e = static_cast + (std::bsearch(&key, grimMusicTable, + sizeof(grimMusicTable) / sizeof(grimMusicTable[0]), + sizeof(grimMusicTable[0]), compareStates)); + if (e == NULL) { + warning("Unknown IMuse state %d\n", state); + return; + } + + Sound *newSound = ResourceLoader::instance()->loadSound(e->filename); + if (newSound == NULL) { + warning("Could not find music file %s\n", e->filename); + return; + } + } + + if (newSound != seqSound_) { + if (newSound != NULL) + newSound->reset(); + seqSound_ = newSound; + } +} + +Sound *Mixer::findSfx(const char *filename) { + for (sound_list::iterator i = sfxSounds_.begin(); + i != sfxSounds_.end(); i++) { + if (std::strcmp((*i)->filename(), filename) == 0) + return *i; + } + return NULL; +} + +bool Mixer::voicePlaying() const { + return ! voiceSounds_.empty(); +} + +void Mixer::getAudio(int16 *data, int numSamples) { + memset(data, 0, numSamples * 2); + for (sound_list::iterator i = voiceSounds_.begin(); + i != voiceSounds_.end(); ) { + (*i)->mix(data, numSamples); + if ((*i)->done()) + i = voiceSounds_.erase(i); + else + i++; + } + for (sound_list::iterator i = sfxSounds_.begin(); + i != sfxSounds_.end(); ) { + (*i)->mix(data, numSamples); + if ((*i)->done()) + i = sfxSounds_.erase(i); + else + i++; + } + if (seqSound_ != NULL) { + seqSound_->mix(data, numSamples); + if (seqSound_->done()) + seqSound_ = NULL; + } + else if (musicSound_ != NULL) { + musicSound_->mix(data, numSamples); + if (musicSound_->done()) + musicSound_->reset(); + } +} + #define ST_SAMPLE_MAX 0x7fffL #define ST_SAMPLE_MIN (-ST_SAMPLE_MAX - 1L) diff --git a/sound.h b/sound.h index 166e1814393..3ca4e889531 100644 --- a/sound.h +++ b/sound.h @@ -20,6 +20,7 @@ #include "bits.h" #include "resource.h" +#include class Sound : public Resource { public: @@ -40,4 +41,34 @@ private: friend class Mixer; }; +class Mixer { +public: + static Mixer *instance(); + + void start(); + + void playVoice(Sound *s); + void playSfx(Sound *s); + void stopSfx(Sound *s); + void stopVoice(Sound *s); + void setImuseState(int state); + void setImuseSeq(int seq); + + Sound *findSfx(const char *filename); + bool voicePlaying() const; + + void getAudio(int16 *data, int numSamples); + +private: + Mixer(); + ~Mixer(); + + static Mixer *instance_; + typedef std::list > sound_list; + sound_list voiceSounds_, sfxSounds_; + ResPtr musicSound_, seqSound_; + + friend void mixerCallback(void *userdata, uint8 *stream, int len); +}; + #endif