1 /*
2     Copyright (C) 2005-2007 Tom Beaumont
3 
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18 
19 #ifndef DISABLE_SOUND
20 #include <list>
21 #include <vector>
22 #include <ctime>
23 #include <SDL_mixer.h>
24 #include "sfx.h"
25 #include "system-directory.h"
26 
27 #define SOUND_START_DELAY -0.2
28 #define MUSIC_VOLUME 0.75
29 
30 static const char* const music_names[HHOP_MUSIC_MAX] =
31 {
32 	"music-ending",
33 	"music-game",
34 //	"music-map",
35 //	"music-title",
36 //	"music-win"
37 };
38 static const char* const sound_names[HHOP_SOUND_MAX] =
39 {
40 	"sound-builder",
41 	"sound-collapse",
42 	"sound-crack",
43 	"sound-death",
44 	"sound-disintegrate",
45 	"sound-explode-big",
46 	"sound-explode-small",
47 	"sound-floater-enter",
48 	"sound-floater-move",
49 	"sound-found-antiice",
50 	"sound-found-jump",
51 	"sound-ice",
52 	"sound-laser",
53 	"sound-lift-up",
54 	"sound-lift-down",
55 	"sound-spinner",
56 	"sound-step",
57 	"sound-trampoline",
58 	"sound-ui-fade",
59 	"sound-ui-menu",
60 	"sound-used-antiice",
61 	"sound-used-jump",
62 	"sound-win"
63 };
64 
65 /* We store delayed sound effects in a queue and play them back when the
66    time is right. It's sort of ugly but makes creating samples easier. */
67 class SoundQueue
68 {
69 public:
SoundQueue(int ty,double ti)70 	SoundQueue(int ty, double ti) : type(ty), time(ti) { }
71 public:
72 	int type;
73 	double time;
74 };
75 
76 class SoundEngine
77 {
78 public:
SoundEngine(const char * path)79 	SoundEngine(const char* path) : disable_music(0), disable_effects(0), music_curr(-1), music_next(-1)
80 	{
81 		int i;
82 		int j;
83 		char* pth;
84 		const char* name;
85 		lisysDir* dir;
86 
87 		srand (time (NULL));
88 
89 		// Open data directory.
90 		dir = lisys_dir_open (path);
91 		if (dir == NULL)
92 			return;
93 		lisys_dir_set_filter (dir, LISYS_DIR_FILTER_FILES);
94 		if (!lisys_dir_scan (dir))
95 		{
96 			lisys_dir_free (dir);
97 			return;
98 		}
99 
100 		// Scan for sound and music.
101 		for (i = 0 ; i < lisys_dir_get_count (dir) ; i++)
102 		{
103 			name = lisys_dir_get_name (dir, i);
104 			pth = lisys_dir_get_path (dir, i);
105 			for (j = 0 ; j < HHOP_MUSIC_MAX ; j++)
106 			{
107 				if (strstr (name, music_names[j]) == name)
108 				{
109 					Mix_Music* music = Mix_LoadMUS(pth);
110 					if (music)
111 						music_chunks[j].push_back(music);
112 					else
113 						fprintf(stderr, "Cannot load music `%s': %s\n", name, Mix_GetError());
114 				}
115 			}
116 			for (j = 0 ; j < HHOP_SOUND_MAX ; j++)
117 			{
118 				if (strstr (name, sound_names[j]) == name)
119 				{
120 					Mix_Chunk* sound = Mix_LoadWAV(pth);
121 					if (sound)
122 						sound_chunks[j].push_back(sound);
123 					else
124 						fprintf(stderr, "Cannot load effect `%s': %s\n", name, Mix_GetError());
125 				}
126 			}
127 			free (pth);
128 		}
129 
130 		lisys_dir_free (dir);
131 	}
~SoundEngine()132 	~SoundEngine()
133 	{
134 		int j;
135 
136 		for (j = 0 ; j < HHOP_MUSIC_MAX ; j++)
137 		{
138 			std::vector<Mix_Music*>::iterator i;
139 			for (i = music_chunks[j].begin() ; i != music_chunks[j].end() ; i++)
140 				Mix_FreeMusic (*i);
141 		}
142 		for (j = 0 ; j < HHOP_SOUND_MAX ; j++)
143 		{
144 			std::vector<Mix_Chunk*>::iterator i;
145 			for (i = sound_chunks[j].begin() ; i != sound_chunks[j].end() ; i++)
146 				Mix_FreeChunk (*i);
147 		}
148 	}
PlayMusic(int type)149 	void PlayMusic(int type)
150 	{
151 		if (disable_music)
152 			return;
153 		int size = music_chunks[type].size();
154 		if (size)
155 		{
156 			int music = rand () % size;
157 			Mix_FadeInMusic(music_chunks[type][music], 1, HHOP_FADE_MUSIC_IN);
158 		}
159 	}
PlaySound(int type)160 	void PlaySound(int type)
161 	{
162 		if (disable_effects)
163 			return;
164 		int size = sound_chunks[type].size();
165 		if (size)
166 		{
167 			int sound = rand() % size;
168 			Mix_PlayChannel(-1, sound_chunks[type][sound], 0);
169 		}
170 	}
QueueMusic(int type)171 	void QueueMusic(int type)
172 	{
173 		if (disable_music)
174 			return;
175 		if (music_curr != type || music_next != type)
176 		{
177 			music_curr = -2;
178 			music_next = type;
179 			Mix_FadeOutMusic(HHOP_FADE_MUSIC_OUT);
180 		}
181 	}
QueueSound(int type,double time)182 	void QueueSound(int type, double time)
183 	{
184 		if (disable_effects)
185 			return;
186 		sound_queue.push_back(SoundQueue(type, time));
187 	}
ToggleEffects()188 	void ToggleEffects()
189 	{
190 		disable_effects = !disable_effects;
191 		if (disable_effects)
192 		{
193 			Mix_HaltChannel(-1);
194 			sound_queue.clear();
195 		}
196 	}
ToggleMusic()197 	void ToggleMusic()
198 	{
199 		disable_music = !disable_music;
200 		if (disable_music)
201 			Mix_HaltMusic();
202 	}
UndoQueue()203 	void UndoQueue()
204 	{
205 		sound_queue.clear();
206 	}
Update(double time)207 	void Update(double time)
208 	{
209 		while (true)
210 		{
211 			std::list<SoundQueue>::iterator i;
212 
213 			// Find the first effect that needs playing.
214 			for (i = sound_queue.begin() ; i != sound_queue.end() ; i++)
215 			{
216 				if (time >= i->time)
217 				{
218 					PlaySound(i->type);
219 					break;
220 				}
221 			}
222 
223 			// Erase the effect or stop if not found.
224 			if (i != sound_queue.end())
225 				sound_queue.erase(i);
226 			else
227 				break;
228 		}
229 		if (!disable_music && !Mix_PlayingMusic())
230 		{
231 			PlayMusic(music_next);
232 			music_curr = music_next;
233 		}
234 	}
235 public:
236 	int disable_music;
237 	int disable_effects;
238 	int music_curr;
239 	int music_next;
240 	std::vector<Mix_Music*> music_chunks[HHOP_MUSIC_MAX];
241 	std::vector<Mix_Chunk*> sound_chunks[HHOP_SOUND_MAX];
242 	std::list<SoundQueue> sound_queue;
243 };
244 
245 static SoundEngine* sound_engine;
246 #endif
247 
InitSound(const char * path)248 void InitSound(const char* path)
249 {
250 #ifndef DISABLE_SOUND
251 	SDL_InitSubSystem(SDL_INIT_AUDIO);
252 	if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024) == -1)
253 	{
254 		fprintf(stderr, "Initializing audio failed: %s\n", Mix_GetError());
255 		exit(1);
256 	}
257 	Mix_AllocateChannels(HHOP_EFFECT_CHANNELS);
258 	Mix_VolumeMusic((int)(MIX_MAX_VOLUME*MUSIC_VOLUME));
259 
260 	sound_engine = new SoundEngine(path);
261 #endif
262 }
263 
FreeSound()264 void FreeSound()
265 {
266 #ifndef DISABLE_SOUND
267 	delete sound_engine;
268 	Mix_CloseAudio();
269 #endif
270 }
271 
PlayMusic(int type)272 void PlayMusic(int type)
273 {
274 #ifndef DISABLE_SOUND
275 	sound_engine->QueueMusic(type);
276 #endif
277 }
278 
PlaySound(int type)279 void PlaySound(int type)
280 {
281 #ifndef DISABLE_SOUND
282 	sound_engine->PlaySound(type);
283 #endif
284 }
285 
QueueSound(int type,double time)286 void QueueSound(int type, double time)
287 {
288 #ifndef DISABLE_SOUND
289 	sound_engine->QueueSound(type, time + SOUND_START_DELAY);
290 #endif
291 }
292 
ToggleMusic()293 void ToggleMusic()
294 {
295 #ifndef DISABLE_SOUND
296 	sound_engine->ToggleMusic();
297 #endif
298 }
299 
ToggleEffects()300 void ToggleEffects()
301 {
302 #ifndef DISABLE_SOUND
303 	sound_engine->ToggleEffects();
304 #endif
305 }
306 
UndoSound()307 void UndoSound()
308 {
309 #ifndef DISABLE_SOUND
310 	sound_engine->UndoQueue();
311 #endif
312 }
313 
UpdateSound(double time)314 void UpdateSound(double time)
315 {
316 #ifndef DISABLE_SOUND
317 	sound_engine->Update(time);
318 #endif
319 }
320