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