1 //       _________ __                 __
2 //      /   _____//  |_____________ _/  |______     ____  __ __  ______
3 //      \_____  \\   __\_  __ \__  \\   __\__  \   / ___\|  |  \/  ___/
4 //      /        \|  |  |  | \// __ \|  |  / __ \_/ /_/  >  |  /\___ |
5 //     /_______  /|__|  |__|  (____  /__| (____  /\___  /|____//____  >
6 //             \/                  \/          \//_____/            \/
7 //  ______________________                           ______________________
8 //                        T H E   W A R   B E G I N S
9 //         Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name sound_server.cpp - The sound server (hardware layer and so on) */
12 //
13 //      (c) Copyright 1998-2006 by Lutz Sammer, Fabrice Rossi, and
14 //                                 Jimmy Salmon
15 //
16 //      This program is free software; you can redistribute it and/or modify
17 //      it under the terms of the GNU General Public License as published by
18 //      the Free Software Foundation; only version 2 of the License.
19 //
20 //      This program is distributed in the hope that it will be useful,
21 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
22 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 //      GNU General Public License for more details.
24 //
25 //      You should have received a copy of the GNU General Public License
26 //      along with this program; if not, write to the Free Software
27 //      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
28 //      02111-1307, USA.
29 //
30 
31 
32 //@{
33 
34 /*----------------------------------------------------------------------------
35 --  Includes
36 ----------------------------------------------------------------------------*/
37 
38 #include "stratagus.h"
39 
40 #include "sound_server.h"
41 
42 #ifdef USE_FLUIDSYNTH
43 #include "fluidsynth.h"
44 #endif
45 
46 #include "iocompat.h"
47 #include "iolib.h"
48 #include "unit.h"
49 
50 #include "SDL.h"
51 #include "SDL_mixer.h"
52 
53 /*----------------------------------------------------------------------------
54 --  Variables
55 ----------------------------------------------------------------------------*/
56 
57 static bool SoundInitialized;    /// is sound initialized
58 static bool MusicEnabled = true;
59 static bool EffectsEnabled = true;
60 static double VolumeScale = 1.0;
61 static int MusicVolume = 0;
62 
63 extern volatile bool MusicFinished;
64 
65 /// Channels for sound effects and unit speech
66 struct SoundChannel {
67 	Origin *Unit;          /// pointer to unit, who plays the sound, if any
68 	void (*FinishedCallback)(int channel); /// Callback for when a sample finishes playing
69 };
70 
71 #define MaxChannels 64     /// How many channels are supported
72 
73 static SoundChannel Channels[MaxChannels];
74 
75 static void ChannelFinished(int channel);
76 
77 /**
78 **  Check if this sound is already playing
79 */
SampleIsPlaying(Mix_Chunk * sample)80 bool SampleIsPlaying(Mix_Chunk *sample)
81 {
82 	for (int i = 0; i < MaxChannels; ++i) {
83 		if (Mix_GetChunk(i) == sample && Mix_Playing(i)) {
84 			return true;
85 		}
86 	}
87 	return false;
88 }
89 
UnitSoundIsPlaying(Origin * origin)90 bool UnitSoundIsPlaying(Origin *origin)
91 {
92 	for (int i = 0; i < MaxChannels; ++i) {
93 		if (origin && Channels[i].Unit && origin->Id && Channels[i].Unit->Id
94 			&& origin->Id == Channels[i].Unit->Id && Mix_Playing(i)) {
95 			return true;
96 		}
97 	}
98 	return false;
99 }
100 
101 /**
102 **  A channel is finished playing
103 */
ChannelFinished(int channel)104 static void ChannelFinished(int channel)
105 {
106 	if (Channels[channel].FinishedCallback) {
107 		Channels[channel].FinishedCallback(channel);
108 	}
109 	delete Channels[channel].Unit;
110 	Channels[channel].Unit = NULL;
111 }
112 
113 /**
114 **  Set the channel volume
115 **
116 **  @param channel  Channel to set
117 **  @param volume   New volume 0-255, <0 will not set the volume
118 **
119 **  @return         Current volume of the channel, -1 for error
120 */
SetChannelVolume(int channel,int volume)121 int SetChannelVolume(int channel, int volume)
122 {
123 	return Mix_Volume(channel, volume * VolumeScale);
124 }
125 
126 /**
127 **  Set the channel stereo
128 **
129 **  @param channel  Channel to set
130 **  @param stereo   -128 to 127, out of range will not set the stereo
131 **
132 **  @return         Current stereo of the channel, -1 for error
133 */
SetChannelStereo(int channel,int stereo)134 void SetChannelStereo(int channel, int stereo)
135 {
136 	if (Preference.StereoSound == false) {
137 		Mix_SetPanning(channel, 255, 255);
138 	} else {
139 		int left, right;
140 		if (stereo > 0) {
141 			left = 255 - stereo;
142 			right = 255;
143 		} else {
144 			left = 255;
145 			right = 255 + stereo;
146 		}
147 		Mix_SetPanning(channel, left, right);
148 	}
149 }
150 
151 /**
152 **  Set the channel's callback for when a sound finishes playing
153 **
154 **  @param channel   Channel to set
155 **  @param callback  Callback to call when the sound finishes
156 */
SetChannelFinishedCallback(int channel,void (* callback)(int channel))157 void SetChannelFinishedCallback(int channel, void (*callback)(int channel))
158 {
159 	if (channel < 0 || channel >= MaxChannels) {
160 		return;
161 	}
162 	Channels[channel].FinishedCallback = callback;
163 }
164 
165 /**
166 **  Get the sample playing on a channel
167 */
GetChannelSample(int channel)168 Mix_Chunk *GetChannelSample(int channel)
169 {
170 	if (Mix_Playing(channel)) {
171 		return Mix_GetChunk(channel);
172 	}
173 	return NULL;
174 }
175 
176 /**
177 **  Stop a channel
178 **
179 **  @param channel  Channel to stop
180 */
StopChannel(int channel)181 void StopChannel(int channel)
182 {
183 	Mix_HaltChannel(channel);
184 }
185 
186 /**
187 **  Stop all channels
188 */
StopAllChannels()189 void StopAllChannels()
190 {
191 	Mix_HaltChannel(-1);
192 }
193 
194 static Mix_Music *currentMusic = NULL;
195 
LoadMusic(const char * name)196 static Mix_Music *LoadMusic(const char *name)
197 {
198 	if (currentMusic) {
199 		Mix_HaltMusic();
200 		Mix_FreeMusic(currentMusic);
201 	}
202 	currentMusic = Mix_LoadMUS(name);
203 	if (currentMusic) {
204 		return currentMusic;
205 	}
206 
207 	CFile *f = new CFile;
208 	if (f->open(name, CL_OPEN_READ) == -1) {
209 		printf("Can't open file '%s'\n", name);
210 		delete f;
211 		return NULL;
212 	}
213 	currentMusic = Mix_LoadMUS_RW(f->as_SDL_RWops(), 0);
214 	return currentMusic;
215 }
216 
LoadSample(const char * name)217 static Mix_Chunk *LoadSample(const char *name)
218 {
219 	Mix_Chunk *r = Mix_LoadWAV(name);
220 	if (r) {
221 		return r;
222 	}
223 	CFile *f = new CFile;
224 	if (f->open(name, CL_OPEN_READ) == -1) {
225 		printf("Can't open file '%s'\n", name);
226 		delete f;
227 		return NULL;
228 	}
229 	return Mix_LoadWAV_RW(f->as_SDL_RWops(), 0);
230 }
231 
232 /**
233 **  Load a music file
234 **
235 **  @param name  File name
236 **
237 **  @return      Mix_Music pointer
238 */
LoadMusic(const std::string & name)239 Mix_Music *LoadMusic(const std::string &name)
240 {
241 	const std::string filename = LibraryFileName(name.c_str());
242 	Mix_Music *music = LoadMusic(filename.c_str());
243 
244 	if (music == NULL) {
245 		fprintf(stderr, "Can't load the music '%s'\n", name.c_str());
246 	}
247 	return music;
248 }
249 
250 /**
251 **  Load a sample
252 **
253 **  @param name  File name of sample (short version).
254 **
255 **  @return      General sample loaded from file into memory.
256 **
257 **  @todo  Add streaming, caching support.
258 */
LoadSample(const std::string & name)259 Mix_Chunk *LoadSample(const std::string &name)
260 {
261 	const std::string filename = LibraryFileName(name.c_str());
262 	Mix_Chunk *sample = LoadSample(filename.c_str());
263 
264 	if (sample == NULL) {
265 		fprintf(stderr, "Can't load the sound '%s': %s\n", name.c_str(), Mix_GetError());
266 	}
267 	return sample;
268 }
269 
270 /**
271 **  Play a sound sample
272 **
273 **  @param sample  Sample to play
274 **
275 **  @return        Channel number, -1 for error
276 */
PlaySample(Mix_Chunk * sample,Origin * origin)277 int PlaySample(Mix_Chunk *sample, Origin *origin)
278 {
279 	int channel = -1;
280 	DebugPrint("play sample %d\n" _C_ sample->volume);
281 	if (SoundEnabled() && EffectsEnabled && sample) {
282 		channel = Mix_PlayChannel(-1, sample, 0);
283 		Channels[channel].FinishedCallback = NULL;
284 		if (origin && origin->Base) {
285 			Origin *source = new Origin;
286 			source->Base = origin->Base;
287 			source->Id = origin->Id;
288 			Channels[channel].Unit = source;
289 		}
290 	}
291 	return channel;
292 }
293 
294 /**
295 **  Set the global sound volume.
296 **
297 **  @param volume  the sound volume 0-255
298 */
SetEffectsVolume(int volume)299 void SetEffectsVolume(int volume)
300 {
301 	VolumeScale = (volume * 1.0) / 255.0;
302 }
303 
304 /**
305 **  Get effects volume
306 */
GetEffectsVolume()307 int GetEffectsVolume()
308 {
309 	return VolumeScale * 255;
310 }
311 
312 /**
313 **  Set effects enabled
314 */
SetEffectsEnabled(bool enabled)315 void SetEffectsEnabled(bool enabled)
316 {
317 	EffectsEnabled = enabled;
318 }
319 
320 /**
321 **  Check if effects are enabled
322 */
IsEffectsEnabled()323 bool IsEffectsEnabled()
324 {
325 	return EffectsEnabled;
326 }
327 
328 /*----------------------------------------------------------------------------
329 --  Music
330 ----------------------------------------------------------------------------*/
331 
332 /**
333 **  Set the music finished callback
334 */
SetMusicFinishedCallback(void (* callback)())335 void SetMusicFinishedCallback(void (*callback)())
336 {
337 	Mix_HookMusicFinished(callback);
338 }
339 
340 /**
341 **  Play a music file.
342 **
343 **  @param sample  Music sample.
344 **
345 **  @return        0 if music is playing, -1 if not.
346 */
PlayMusic(Mix_Music * sample)347 int PlayMusic(Mix_Music *sample)
348 {
349 	if (sample) {
350 		Mix_VolumeMusic(MusicVolume);
351 		MusicFinished = false;
352 		Mix_PlayMusic(sample, 0);
353 		Mix_VolumeMusic(MusicVolume / 4.0);
354 		return 0;
355 	} else {
356 		DebugPrint("Could not play sample\n");
357 		return -1;
358 	}
359 }
360 
361 /**
362 **  Play a music file.
363 **
364 **  @param file  Name of music file, format is automatically detected.
365 **
366 **  @return      0 if music is playing, -1 if not.
367 */
PlayMusic(const std::string & file)368 int PlayMusic(const std::string &file)
369 {
370 	if (!SoundEnabled() || !IsMusicEnabled()) {
371 		return -1;
372 	}
373 	DebugPrint("play music %s\n" _C_ file.c_str());
374 	Mix_Music *music = LoadMusic(file);
375 
376 	if (music) {
377 		MusicFinished = false;
378 		Mix_FadeInMusic(music, 0, 200);
379 		return 0;
380 	} else {
381 		DebugPrint("Could not play %s\n" _C_ file.c_str());
382 		return -1;
383 	}
384 }
385 
386 /**
387 **  Stop the current playing music.
388 */
StopMusic()389 void StopMusic()
390 {
391 	Mix_FadeOutMusic(200);
392 }
393 
394 /**
395 **  Set the music volume.
396 **
397 **  @param volume  the music volume 0-255
398 */
SetMusicVolume(int volume)399 void SetMusicVolume(int volume)
400 {
401 	// due to left-right separation, sound effect volume is effectively halfed,
402 	// so we adjust the music
403 	MusicVolume = volume;
404 	Mix_VolumeMusic(volume / 4.0);
405 }
406 
407 /**
408 **  Get music volume
409 */
GetMusicVolume()410 int GetMusicVolume()
411 {
412 	return MusicVolume;
413 }
414 
415 /**
416 **  Set music enabled
417 */
SetMusicEnabled(bool enabled)418 void SetMusicEnabled(bool enabled)
419 {
420 	if (enabled) {
421 		MusicEnabled = true;
422 	} else {
423 		MusicEnabled = false;
424 		StopMusic();
425 	}
426 }
427 
428 /**
429 **  Check if music is enabled
430 */
IsMusicEnabled()431 bool IsMusicEnabled()
432 {
433 	return MusicEnabled;
434 }
435 
436 /**
437 **  Check if music is playing
438 */
IsMusicPlaying()439 bool IsMusicPlaying()
440 {
441 	return Mix_PlayingMusic();
442 }
443 
444 /*----------------------------------------------------------------------------
445 --  Init
446 ----------------------------------------------------------------------------*/
447 
448 /**
449 **  Check if sound is enabled
450 */
SoundEnabled()451 bool SoundEnabled()
452 {
453 	return SoundInitialized;
454 }
455 
456 /**
457 **  Initialize sound card hardware part with SDL.
458 **
459 **  @param freq  Sample frequency (44100,22050,11025 hz).
460 **  @param size  Sample size (8bit, 16bit)
461 **
462 **  @return      True if failure, false if everything ok.
463 */
InitSdlSound()464 static int InitSdlSound()
465 {
466 	// just activate everything we can by setting all bits
467 	Mix_Init(std::numeric_limits<unsigned int>::max());
468 	if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, 1024)) {
469 		fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError());
470 		return -1;
471 	} else {
472 		printf("Supported sound decoders:");
473 		for (int i = 0; i < Mix_GetNumChunkDecoders(); i++) {
474 			printf(" %s", Mix_GetChunkDecoder(i));
475 		}
476 		printf("\nSupported music decoders:");
477 		for (int i = 0; i < Mix_GetNumMusicDecoders(); i++) {
478 			printf(" %s", Mix_GetMusicDecoder(i));
479 		}
480 		printf("\n");
481 	}
482 	return 0;
483 }
484 
485 /**
486 **  Initialize sound card.
487 **
488 **  @return  True if failure, false if everything ok.
489 */
InitSound()490 int InitSound()
491 {
492 	//
493 	// Open sound device, 8bit samples, stereo.
494 	//
495 	if (InitSdlSound()) {
496 		SoundInitialized = false;
497 		return 1;
498 	}
499 	SoundInitialized = true;
500 	Mix_AllocateChannels(MaxChannels);
501 	Mix_ChannelFinished(ChannelFinished);
502 
503 	// Now we're ready for the callback to run
504 	Mix_ResumeMusic();
505 	Mix_Resume(-1);
506 	return 0;
507 }
508 
509 /**
510 **  Cleanup sound server.
511 */
QuitSound()512 void QuitSound()
513 {
514 	Mix_CloseAudio();
515 	Mix_Quit();
516 	SoundInitialized = false;
517 }
518 
519 //@}
520