1 /* $Id: audio.cpp,v 1.37 2005/12/19 12:11:53 chfreund Exp $ */
2 
3 /******************************************************************************/
4 
5 #include <SDL.h>
6 #include <SDL_mixer.h>
7 
8 #include <dirent.h>
9 #include <cmath>
10 
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <unistd.h>
14 
15 #include "global.hpp"
16 #include "audio.hpp"
17 #include "loader.hpp"
18 #include "wopsettings.hpp"
19 #include "random.hpp"
20 #include "string.hpp"
21 
22 /******************************************************************************/
23 
24 Audio* Audio::m_instance = 0;
25 bool Audio::m_quiet = true;
26 bool Audio::m_playNextTrack = false, Audio::m_inJukeboxMode = false;
27 int Audio::m_listeningPositionX = 0;
28 int Audio::m_listeningPositionY = 0;
29 static double lowPassFilterVal = 0.0;
30 bool Audio::m_lowPassFilterEnabled = false;
31 FILE* Audio::m_outputFile = 0;
32 std::vector<Audio::TrackDesc> Audio::m_track;
33 Mix_Music* Audio::m_currentTrack = 0;
34 
35 /******************************************************************************/
36 
37 #define QUIET_RETURN if( m_quiet ) return
38 
39 /******************************************************************************/
40 
initLowPassFilterBuffer(int * buffer,const int bufferLength)41 bool initLowPassFilterBuffer( int* buffer, const int bufferLength ) {
42 	for( int i = 0; i < bufferLength; i++ ) buffer[i] = 0;
43 	return true;
44 }
45 
lowPassFilter(int channel,void * stream,int len,void * udata)46 void lowPassFilter( int channel, void* stream, int len, void* udata ) {
47 	const int bufferLength = 32;
48 	static int buffer[bufferLength];
49 	static const bool bufferInitialized = initLowPassFilterBuffer( buffer, bufferLength );
50 	static int bufferPos = 0;
51 	static int meanL = 0, meanR = 0;
52 
53 	int* sample = reinterpret_cast<int*>( stream );
54 	Sint32 origSampleL, origSampleR;
55 	len /= 4;
56 
57 	for( int samplePos = 0; samplePos < len; samplePos++ ) {
58 		origSampleL = sample[samplePos] >> 16;
59 		meanL += origSampleL - buffer[bufferPos];
60 		buffer[bufferPos] = origSampleL;
61 		//buffer[bufferPos] = ECHO * buffer[bufferPos] + (1.0 - ECHO) * newSample;
62 
63 		bufferPos = (bufferPos + 1) % bufferLength;
64 
65 		origSampleR = (sample[samplePos] << 16) >> 16;
66 		meanR += origSampleR - buffer[bufferPos];
67 		buffer[bufferPos] = origSampleR;
68 		//buffer[bufferPos] = ECHO * buffer[bufferPos] + (1.0 - ECHO) * newSample;
69 
70 		bufferPos = (bufferPos + 1) % bufferLength;
71 
72 		const int newSampleL = int(lowPassFilterVal*meanL + (1.0-lowPassFilterVal)*(origSampleL<<4));
73 		const int newSampleR = int(lowPassFilterVal*meanR + (1.0-lowPassFilterVal)*(origSampleR<<4));
74 
75 		sample[samplePos] = (((newSampleL>>4) & 0x0000ffff) << 16) +
76 		                     ((newSampleR>>4) & 0x0000ffff);
77 	}
78 }
79 
80 /******************************************************************************/
81 
Audio()82 Audio::Audio()
83 	: m_isRecording( false ) {
84 	WopSettings* settings = WopSettings::getInstance();
85 	m_quiet = settings->getQuiet();
86 	QUIET_RETURN;
87 
88 	LOG( 1 ) INFO( "Audio::Audio: Initializing audio\n" );
89 
90 	m_quiet = true;
91 
92 #ifdef WIN32
93 	m_chunksize = 1024;
94 #else
95 	m_chunksize = 1024;
96 #endif
97 
98 	if( ! CHECK( ! SDL_InitSubSystem( SDL_INIT_AUDIO ),
99 	                       "Audio::Audio: Couldn't initialize audio\n" ) ) return;
100 
101 	if( ! CHECK( ! Mix_OpenAudio( 22050, AUDIO_S16SYS, 2, m_chunksize ),
102 	                       "Audio::Audio: Couldn't open audio device\n") ) return;
103 
104 	INFO( "Audio::Audio: allocated %i channels\n", Mix_AllocateChannels( 32 ) );
105 
106 	m_quiet = false;
107 
108 	/*
109 	int frequency, channels;
110 	Uint16 format;
111 	channels = frequency = format = 0;
112 	if( Mix_QuerySpec( &frequency, &format, &channels) == 1 ) {
113 		LOG( 1 ) INFO( "Audio::Audio: Mixer opened with %i Hz and %i channels\n",
114 		              frequency, channels );
115 	}
116 	else {
117 		LOG( 1 ) INFO( "Audio::Audio: Couldn't open mixer\n" );
118 	}
119 	*/
120 
121 	// insert menu track as entry 0
122 	const string menuTrackName( Loader::getInstance()->getAbsolutePath( "sound/effects/intro.ogg" ));
123 	m_track.push_back( TrackDesc( menuTrackName ));
124 	m_track[0].playMe = false;
125 
126 	// read music dir
127 	String musicDir;
128 	if( settings->getMusicDir( musicDir ) ) {
129 		string dir( musicDir.getString() );
130 		addTracksInDir( 5, dir );
131 	}
132 }
133 
134 /******************************************************************************/
135 
~Audio()136 Audio::~Audio() {
137 	QUIET_RETURN;
138 
139 	LOG( 1 ) INFO( "Audio::~Audio: starting destructor\n");
140 
141 	dumpToFile( NULL );
142 	stopTrack();
143 	Mix_HaltChannel( -1 );
144 	Mix_CloseAudio();
145 	SDL_QuitSubSystem( SDL_INIT_AUDIO );
146 
147 	LOG( 1 ) INFO( "Audio::~Audio: done\n" );
148 }
149 
150 /******************************************************************************/
151 
update()152 void Audio::update() {
153 	QUIET_RETURN;
154 
155 	if( m_playNextTrack ) {
156 		m_playNextTrack = false;
157 		if( loadTrack() )
158 			playTrack();
159 		else
160 			m_playNextTrack = true;
161 	}
162 }
163 
164 /******************************************************************************/
165 
loadSound(const char * filename) const166 Mix_Chunk* Audio::loadSound( const char* filename ) const {
167 	QUIET_RETURN NULL;
168 
169 	Mix_Chunk* chunk = 0;
170 	CHECK( ( chunk = Loader::getInstance()->getSound(filename, Loader::noErrorAbort) ),
171 	         "Audio::loadSound: couldn't load sound in file %s\n", filename );
172 	return chunk;
173 }
174 
175 /******************************************************************************/
176 
playSound(Mix_Chunk * chunk)177 void Audio::playSound( Mix_Chunk* chunk ) {
178 	QUIET_RETURN;
179 
180 	if( ! chunk ) return; // NULL samples shouldn't be played
181 
182 	const int channel = Mix_PlayChannel( -1, chunk, 0 );
183 	DBG( 5 ) if( ! CHECK( channel != -1, "Audio::playSound: couldn't play sound\n" ) ) return;
184 
185 	if ( m_isRecording ) {
186 		if ( !m_currentRecordFrame )
187 			m_currentRecordFrame = new std::vector<Mix_Chunk*>;
188 		m_currentRecordFrame->push_back( chunk );
189 	}
190 }
191 
192 /******************************************************************************/
193 
playSound(Mix_Chunk * chunk,const int sourcePositionX,const int sourcePositionY)194 void Audio::playSound( Mix_Chunk* chunk, const int sourcePositionX,
195 		                                     const int sourcePositionY ) {
196 	QUIET_RETURN;
197 
198 	if( ! chunk ) return; // NULL samples shouldn't be played
199 
200 	const real DECAY = 0.5;    // higher value -> faster decay with distance
201 	const int MIN_VOLUME = 220; // the lowest volume for distant sound sources; has to be <= 255
202 
203 	const int channel = Mix_PlayChannel( -1, chunk, 0 );
204 	DBG( 5 ) if( ! CHECK( channel != -1, "Audio::playSound: couldn't play sound\n" ) ) return;
205 
206 	if ( m_isRecording ) {
207 		if ( !m_currentRecordFrame )
208 			m_currentRecordFrame = new std::vector<Mix_Chunk*>;
209 		m_currentRecordFrame->push_back( chunk );
210 	}
211 
212 	const int dx = sourcePositionX - m_listeningPositionX,
213 	          dy = sourcePositionY - m_listeningPositionY;
214 	const int dist = ROUND( DECAY * SQRT_REAL( static_cast<real>( dx*dx + dy*dy ) ) );
215 	Sint16 angle;
216 	if( dist <= 5 )
217 		angle = 0;
218 	else
219 		angle = ROUND( ATAN2_REAL( static_cast<real>( dy ), static_cast<real>( dx ))
220 		                  * 180 / M_PI ) + 90;
221 
222 	Mix_SetPosition( channel, angle, static_cast<Uint8>( dist > MIN_VOLUME ? MIN_VOLUME : dist ) );
223 }
224 
225 /******************************************************************************/
226 
fileDumpProcessor(void * udata,Uint8 * stream,int len)227 static void fileDumpProcessor( void* udata, Uint8* stream, int len ) {
228 	if( fwrite( stream, 1, len, reinterpret_cast<FILE*>( udata )) !=
229 		  static_cast<size_t>( len )) {
230 		CHECK( false, "fileDumpProcessor (in Audio): could not write audio data\n" );
231 		Audio::getInstance()->dumpToFile( NULL );
232 	}
233 }
234 
235 /******************************************************************************/
236 
dumpToFile(const char * filename)237 bool Audio::dumpToFile( const char* filename ) {
238 	if( filename ) {                             // open new file
239 		if( m_outputFile ) dumpToFile( NULL );     // if already open, close it before
240 		m_outputFile = fopen( filename, "w" );
241 		if( ! CHECK( m_outputFile, "Audio::dumpToFile: could not open file '%s'\n",
242 		           filename )) return false;
243 		Mix_SetPostMix( fileDumpProcessor, m_outputFile );  // register processor
244 	}
245 	else {                                       // close file
246 		if( m_outputFile ) {                       // only if open
247 			Mix_SetPostMix( NULL, NULL );
248 			fclose( m_outputFile );
249 			m_outputFile = NULL;
250 		}
251 	}
252 
253 	return true;
254 }
255 
256 /******************************************************************************/
257 
startRecording()258 void Audio::startRecording() {
259 	// clear record vector
260 	for ( std::vector<std::vector<Mix_Chunk*>*>::iterator iter = m_recordVector.begin();
261          iter != m_recordVector.end(); iter++ ) {
262 		delete *iter;
263 	}
264 	m_recordVector.clear();
265 
266 	m_currentRecordFrame = 0;
267 	m_isRecording = true;
268 }
269 
stopRecording()270 void Audio::stopRecording() {
271 	m_isRecording = false;
272 }
273 
nextRecordFrame()274 void Audio::nextRecordFrame() {
275 	m_recordVector.push_back( m_currentRecordFrame );
276 	m_currentRecordFrame = 0;
277 }
278 
writeRecordToFile(const char * filename)279 void Audio::writeRecordToFile( const char* filename ) {
280 	stopRecording();
281 
282 	dumpToFile( filename );
283 	for ( std::vector<std::vector<Mix_Chunk*>*>::iterator iter = m_recordVector.begin();
284 	      iter != m_recordVector.end(); iter++ ) {
285 		std::vector<Mix_Chunk*>* currentFrame = *iter;
286 		if ( currentFrame ) {
287 			for ( std::vector<Mix_Chunk*>::iterator frameIter = currentFrame->begin();
288 			      frameIter != currentFrame->end(); frameIter++ ) {
289 				Mix_Chunk* chunk = *frameIter;
290 				if ( chunk )
291 					Mix_PlayChannel( -1, chunk, 0 );
292 			}
293 		}
294 		SDL_Delay( 40 );
295 	}
296 	dumpToFile( 0 );
297 }
298 
299 /******************************************************************************/
300 
addTracksInDir(const int depth,const string & dir)301 void Audio::addTracksInDir( const int depth, const string& dir ) {
302 	struct stat fileStat;
303 
304 	LOG( 3 ) INFO( "Audio::Audio: looking for suitable sound files in '%s'\n", dir.c_str() );
305 	DIR* musicDir = opendir( dir.c_str() );
306 
307 	if( CHECK( musicDir, "Audio::Audio: could not open music directory '%s'\n", dir.c_str() )) {
308 		// look through directory files
309 		while( true ) {
310 			dirent* dirEntry = readdir( musicDir );
311 			if( ! dirEntry ) break; // no more files in directory
312 			const string entryName  = dir + '/' + string( dirEntry->d_name );
313 			LOG( 5 ) INFO( "Audio::Audio: found file '%s'\n", entryName.c_str() );
314 
315 			// try to stat file
316 			if( stat( entryName.c_str(), &fileStat ) != 0 ) continue;
317 
318 			// is it a file?
319 			if( S_ISREG( fileStat.st_mode )) {
320 			// it is a file, so go on
321 				LOG( 5 ) INFO( "Audio::Audio: '%s' is file or link\n", entryName.c_str() );
322 				// test suffix
323 				if( entryName.compare( entryName.size()-4, 4, string( ".mp3" )) == 0 ||
324 				    entryName.compare( entryName.size()-4, 4, string( ".ogg" )) == 0 ||
325 				    entryName.compare( entryName.size()-4, 4, string( ".mid" )) == 0 ||
326 				    entryName.compare( entryName.size()-4, 4, string( ".mod" )) == 0 ||
327 				    entryName.compare( entryName.size()-4, 4, string( ".wav" )) == 0 ) {
328 					// everything is ok, append to track files
329 					LOG( 4 ) INFO( "Audio::Audio: adding '%s' to track files\n",
330 					               entryName.c_str() );
331 					m_track.push_back( TrackDesc( entryName ));
332 				}
333 			}
334 
335 			else
336 
337 			// are we not too deep and is it a directory and is it not a hidden dir?
338 			if( depth > 0 && S_ISDIR( fileStat.st_mode ) &&
339 			    dirEntry->d_name[0] != '.' ) {
340 				addTracksInDir( depth-1, entryName );
341 			}
342 
343 		}
344 		closedir( musicDir );
345 	}
346 }
347 
348 /******************************************************************************/
349 
setJukeboxMode(const bool inJukeboxMode)350 void Audio::setJukeboxMode( const bool inJukeboxMode ) {
351 	QUIET_RETURN;
352 
353 	if( m_track.size() <= 1 ) return;
354 
355 	if( m_inJukeboxMode ) {
356 		Mix_HookMusicFinished( 0 );
357 		stopTrack();
358 	}
359 	m_inJukeboxMode = inJukeboxMode;
360 	if( m_inJukeboxMode ) {
361 		Mix_HookMusicFinished( trackEnded );
362 		m_playNextTrack = ! (Mix_PlayingMusic() ||
363 		                     Mix_FadingMusic() != MIX_NO_FADING);
364 	}
365 	else m_playNextTrack = false;
366 }
367 
368 /******************************************************************************/
369 
loadTrack(int track)370 bool Audio::loadTrack( int track ) {
371 	QUIET_RETURN false;
372 
373 	// track index out of bounds?
374 	if( ! CHECK( track < static_cast<int>( m_track.size() ), "Audio::loadTrack: "
375 	             "track index too big\n" )) return false;
376 
377 	if( m_currentTrack ) {
378 		stopTrack();
379 		Mix_FreeMusic( m_currentTrack );
380 		m_currentTrack = 0;
381 	}
382 
383 	if( track < 0 ) {
384 		track = localRnd.getUint32Between( 1, m_track.size()-1 );
385 	}
386 
387 	LOG( 2 ) INFO( "Audio::loadTrack: trying to load track %i (\"%s\")\n",
388 	              track, m_track[track].trackName.c_str() );
389 	m_currentTrack = Mix_LoadMUS( m_track[track].trackName.c_str() );
390 	LOG( 5 ) INFO( "Audio::loadTrack: track started\n" );
391 	return CHECK( m_currentTrack != 0, "Audio::loadTrack: could not load track %i "
392 	                                "(\"%s\"); SDL error: %s\n",
393 	              track, m_track[track].trackName.c_str(), Mix_GetError() );
394 }
395 
396 /******************************************************************************/
397 
playTrack()398 void Audio::playTrack() {
399 	fadeInTrack( 0 );
400 }
401 
402 /******************************************************************************/
403 
stopTrack()404 void Audio::stopTrack() {
405 	fadeOutTrack( 0 );
406 }
407 
408 /******************************************************************************/
409 
fadeInTrack(const int time)410 void Audio::fadeInTrack( const int time ) {
411 	LOG( 5 ) INFO( "Audio::fadeInTrack: fading in\n" );
412 	if( m_currentTrack ) {
413 		if( time > 0 )
414 			Mix_FadeInMusic( m_currentTrack, 1, time );
415 		else
416 			Mix_PlayMusic( m_currentTrack, 1 );
417 	}
418 	LOG( 5 ) INFO( "Audio::fadeInTrack: fading in done\n" );
419 }
420 
421 /******************************************************************************/
422 
fadeOutTrack(const int time)423 void Audio::fadeOutTrack( const int time ) {
424 	LOG( 5 ) INFO( "Audio::fadeOutTrack: fading out\n" );
425 	if( m_currentTrack ) {
426 		if( time > 0 )
427 			Mix_FadeOutMusic( time );
428 		else
429 			Mix_HaltMusic();
430 	}
431 	LOG( 5 ) INFO( "Audio::fadeOutTrack: fading out done\n" );
432 }
433 
434 /******************************************************************************/
435 
setLowPassFilter(const double val) const436 void Audio::setLowPassFilter ( const double val ) const {
437 	//Mix_SetPostMix( lowPass, NULL );
438 	if( ! m_lowPassFilterEnabled && val > 0.0 ) {
439 		m_lowPassFilterEnabled = true;
440 		Mix_RegisterEffect( MIX_CHANNEL_POST, lowPassFilter, NULL , NULL );
441 	}
442 
443 	if( m_lowPassFilterEnabled && val <= 0.0 ) {
444 		Mix_UnregisterEffect( MIX_CHANNEL_POST, lowPassFilter );
445 		m_lowPassFilterEnabled = false;
446 	}
447 
448 	lowPassFilterVal = max( 0.0, val );
449 }
450 
451 /******************************************************************************/
452 
453