diff --git a/stepmania/src/arch/Sound/ALSA9Helpers.cpp b/stepmania/src/arch/Sound/ALSA9Helpers.cpp index 3baffd6f67..46b8a860e8 100644 --- a/stepmania/src/arch/Sound/ALSA9Helpers.cpp +++ b/stepmania/src/arch/Sound/ALSA9Helpers.cpp @@ -1,35 +1,31 @@ #include "global.h" #include "RageLog.h" +#include "RageUtil.h" #include "ALSA9Helpers.h" #include "SDL_utils.h" /* int err; must be defined before using this macro */ #define ALSA_ASSERT(x) \ - if (err < 0) { LOG->Trace("RageSound_ALSA9: ASSERT %s: %s", x, snd_strerror(err)); } + if (err < 0) { LOG->Trace("RageSound_ALSA9: ASSERT %s: %s (%i)", x, snd_strerror(err), err); } -Alsa9Buf::Alsa9Buf( hw hardware, int channels_, int samplerate_, int writeahead_ ) +void Alsa9Buf::SetHWParams() { - channels = channels_; - samplerate = samplerate_; - samplebits = 16; - writeahead = writeahead_; - // buffer_locked = false; - last_cursor_pos = 0; - //write_cursor = LastPosition = buffer_bytes_filled = 0; - - /* open the device */ - // if we instead use plughw: then our requested format WILL be provided int err; - err = snd_pcm_open( &pcm, "hw:0,0", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK ); - if (err < 0) - RageException::ThrowNonfatal("snd_pcm_open: %s", snd_strerror(err)); + + if( snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED ) + snd_pcm_drop( pcm ); + if( snd_pcm_state(pcm) != SND_PCM_STATE_OPEN ) + { + /* Reset the stream to SND_PCM_STATE_OPEN. */ + err = snd_pcm_hw_free( pcm ); + ALSA_ASSERT("snd_pcm_hw_free"); + } +// RAGE_ASSERT_M( snd_pcm_state(pcm) == SND_PCM_STATE_OPEN, ssprintf("(%s)", snd_pcm_state_name(snd_pcm_state(pcm))) ); /* allocate the hardware parameters structure */ snd_pcm_hw_params_t *hwparams; - err = snd_pcm_hw_params_malloc(&hwparams); - ALSA_ASSERT("snd_pcm_hw_params_malloc"); + snd_pcm_hw_params_alloca( &hwparams ); - /* not exactly sure what this does */ err = snd_pcm_hw_params_any(pcm, hwparams); ALSA_ASSERT("snd_pcm_hw_params_any"); @@ -45,17 +41,47 @@ Alsa9Buf::Alsa9Buf( hw hardware, int channels_, int samplerate_, int writeahead_ err = snd_pcm_hw_params_set_channels(pcm, hwparams, 2); ALSA_ASSERT("snd_pcm_hw_params_set_channels"); - unsigned int rate = samplerate; - err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0); - // check if we got the rate we desire + if( samplerate != Alsa9Buf::DYNAMIC_SAMPLERATE ) + { + unsigned int rate = samplerate; + err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0); + ALSA_ASSERT("snd_pcm_hw_params_set_rate_near"); + + if( (int) rate != samplerate ) + LOG->Warn("Alsa9Buf::SetHWParams: Couldn't get %ihz (got %ihz instead)", samplerate, rate); + } /* write the hardware parameters to the device */ err = snd_pcm_hw_params(pcm, hwparams); ALSA_ASSERT("snd_pcm_hw_params"); +} - snd_pcm_hw_params_free(hwparams); +Alsa9Buf::Alsa9Buf( hw hardware, int channels_, int samplerate_ ) +{ + channels = channels_; + samplerate = samplerate_; + samplebits = 16; + last_cursor_pos = 0; + + /* Open the device. */ + int err; + err = snd_pcm_open( &pcm, "dmix", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK ); +// err = snd_pcm_open( &pcm, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK ); + if (err < 0) + RageException::ThrowNonfatal("snd_pcm_open: %s", snd_strerror(err)); + + SetHWParams(); + + + snd_pcm_sw_params_t *swparams; + snd_pcm_sw_params_alloca( &swparams ); + + /* Disable SND_PCM_STATE_XRUN. */ + err = snd_pcm_sw_params_set_stop_threshold( pcm, swparams, 10000000 ); + ALSA_ASSERT("snd_pcm_sw_params_set_stop_threshold"); + err = snd_pcm_sw_params(pcm, swparams); + ALSA_ASSERT("snd_pcm_sw_params"); - /* prepare the device to reveice data */ err = snd_pcm_prepare(pcm); ALSA_ASSERT("snd_pcm_prepare"); @@ -84,7 +110,7 @@ Alsa9Buf::~Alsa9Buf() } -int Alsa9Buf::GetNumFramesToFill() +int Alsa9Buf::GetNumFramesToFill( int writeahead ) { snd_pcm_sframes_t avail_frames = snd_pcm_avail_update(pcm); if( avail_frames < 0 && Recover(avail_frames) ) @@ -97,8 +123,11 @@ int Alsa9Buf::GetNumFramesToFill() } const snd_pcm_sframes_t filled_frames = total_frames - avail_frames; + RAGE_ASSERT_M( filled_frames >= 0, ssprintf("total %i, %i avail", total_frames, avail_frames) ); + const snd_pcm_sframes_t frames_to_fill = (writeahead - filled_frames) & ~3; - ASSERT( frames_to_fill <= (int)writeahead ); + RAGE_ASSERT_M( frames_to_fill <= (int)writeahead, ssprintf("writeahead %i, %i filled, %i to fill", writeahead, filled_frames, frames_to_fill) ); +// LOG->Trace("%li/%li fr avail, %li", avail_frames, total_frames, frames_to_fill); // LOG->Trace("%li avail, %li filled, %li to", avail_frames, filled_frames, frames_to_fill); return max( 0l, frames_to_fill ); @@ -113,7 +142,7 @@ void Alsa9Buf::Write( const Sint16 *buffer, int frames ) if( wrote < 0 ) { - LOG->Trace( "RageSoundDriver_ALSA9::GetData: snd_pcm_mmap_writei: %s", snd_strerror(wrote) ); + LOG->Trace( "RageSoundDriver_ALSA9::GetData: snd_pcm_mmap_writei: %s (%i)", snd_strerror(wrote), wrote ); return; } @@ -154,21 +183,46 @@ bool Alsa9Buf::Recover( int r ) int Alsa9Buf::GetPosition() const { - snd_pcm_status_t *status; - snd_pcm_status_alloca(&status); - - int err = snd_pcm_status( pcm, status ); - - ALSA_ASSERT("snd_pcm_status"); - - snd_pcm_state_t state = snd_pcm_status_get_state( status ); - if ( state == SND_PCM_STATE_PREPARED ) + if( snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED ) + { + LOG->Trace("???"); return 0; + } snd_pcm_hwsync( pcm ); + /* delay is returned in frames */ - snd_pcm_sframes_t delay = snd_pcm_status_get_delay(status); + snd_pcm_sframes_t delay; + int err = snd_pcm_delay( pcm, &delay ); + LOG->Trace("last %i, del %i", last_cursor_pos, delay); return last_cursor_pos - delay; } +void Alsa9Buf::Reset() +{ + /* Nothing is playing. Reset the sample count; this is just to + * prevent eventual overflow. */ + last_cursor_pos = 0; +} + +void Alsa9Buf::Play() +{ + /* NOP. It'll start playing when it gets some data. */ +} + +void Alsa9Buf::Stop() +{ + snd_pcm_drop( pcm ); + snd_pcm_prepare( pcm ); +} + +void Alsa9Buf::SetSampleRate(int hz) +{ + samplerate = hz; + + SetHWParams(); + + snd_pcm_prepare( pcm ); +} + diff --git a/stepmania/src/arch/Sound/ALSA9Helpers.h b/stepmania/src/arch/Sound/ALSA9Helpers.h index a1ca49ba43..0046d6f356 100644 --- a/stepmania/src/arch/Sound/ALSA9Helpers.h +++ b/stepmania/src/arch/Sound/ALSA9Helpers.h @@ -9,7 +9,7 @@ class Alsa9Buf { private: - int channels, samplerate, samplebits, writeahead; + int channels, samplerate, samplebits; int buffersize, last_cursor_pos; snd_pcm_sframes_t total_frames; @@ -18,6 +18,7 @@ private: snd_pcm_t *pcm; bool Recover( int r ); + void SetHWParams(); public: enum hw { HW_HARDWARE, HW_SOFTWARE, HW_DONT_CARE }; @@ -25,10 +26,10 @@ public: /* If samplerate is DYNAMIC_SAMPLERATE, then call SetSampleRate before * you use the sample. */ enum { DYNAMIC_SAMPLERATE = -1 }; - Alsa9Buf( hw hardware, int channels, int samplerate, int writeahead ); + Alsa9Buf( hw hardware, int channels, int samplerate ); ~Alsa9Buf(); - int GetNumFramesToFill(); + int GetNumFramesToFill( int writeahead ); void Write( const Sint16 *buffer, int frames ); void Reset(); diff --git a/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.cpp b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.cpp index ce69c0125e..fdbb123c57 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.cpp +++ b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.cpp @@ -1,7 +1,6 @@ #include "global.h" #include "RageSoundDriver_ALSA9.h" -#include "RageTimer.h" #include "RageLog.h" #include "RageSound.h" #include "RageSoundManager.h" @@ -14,6 +13,8 @@ const int samples_per_frame = channels; const int bytes_per_frame = sizeof(Sint16) * samples_per_frame; const unsigned max_writeahead = 1024*8; +const int num_chunks = 8; +const int chunksize = max_writeahead / num_chunks; int RageSound_ALSA9::MixerThread_start(void *p) { @@ -29,107 +30,180 @@ void RageSound_ALSA9::MixerThread() while(!shutdown) { - GetData(); - const float delay_ms = 1000 * float(max_writeahead) / samplerate; - SDL_Delay( int(delay_ms) / 2); + /* Sleep for the size of one chunk. */ + const int chunksize_frames = max_writeahead / num_chunks; + float sleep_secs = (float(chunksize_frames) / samplerate); + SDL_Delay( 20 ); // int(1000 * sleep_secs)); + + LockMutex L(SOUNDMAN->lock); + for( unsigned i = 0; i < stream_pool.size(); ++i ) + { + if( stream_pool[i]->state == stream_pool[i]->INACTIVE ) + continue; /* inactive */ + + while( stream_pool[i]->GetData(false) ) + ; + } } } -/* Returns the number of frames processed */ -void RageSound_ALSA9::GetData() +RageSound_ALSA9::stream::~stream() { - const int frames_to_fill = pcm->GetNumFramesToFill(); - if( frames_to_fill <= 0 ) - return; + delete pcm; +} + +/* Returns the number of frames processed */ +bool RageSound_ALSA9::stream::GetData(bool init) +{ + int frames_to_fill = pcm->GetNumFramesToFill( max_writeahead ); + if( !init ) + frames_to_fill = min( frames_to_fill, chunksize ); + + if( frames_to_fill < chunksize ) + return false; - /* Sint16 represents a single sample - * each frame contains one sample per channel - */ static Sint16 *buf = NULL; - if (!buf) - buf = new Sint16[max_writeahead*samples_per_frame*4]; + if ( !buf ) + buf = new Sint16[max_writeahead*samples_per_frame]; - static SoundMixBuffer mix; - mix.SetVolume( SOUNDMAN->GetMixVolume() ); - - LockMutex L(SOUNDMAN->lock); - for(unsigned i = 0; i < sounds.size(); ++i) + unsigned len = frames_to_fill*bytes_per_frame; + /* It might be INACTIVE, when we're prebuffering. We just don't want to + * fill anything in STOPPING; in that case, we just clear the audio buffer. */ + if( state != STOPPING ) { - if(sounds[i]->stopping) - continue; + LOG->Trace("%p pp %i", this, pcm->GetPlayPos() ); + const unsigned got = snd->GetPCM( (char *) buf, len, pcm->GetPlayPos() ); - /* Call the callback. - * Get the units straight, - * = GetPCM(, , ) - */ - unsigned got = sounds[i]->snd->GetPCM( (char *) buf, frames_to_fill*bytes_per_frame, pcm->GetPlayPos() ) / sizeof(Sint16); - mix.write((Sint16 *) buf, got); - - if( int(got) < frames_to_fill ) + if( got < len ) { - /* This sound is finishing. */ - sounds[i]->stopping = true; - } - } - L.Unlock(); + /* Fill the remainder of the buffer with silence. */ + memset( buf+got, 0, len-got ); - memset( buf, 0, sizeof(Sint16) * frames_to_fill * samples_per_frame ); - mix.read( buf ); + /* STOPPING tells the mixer thread to release the stream once str->flush_bufs + * buffers have been flushed. */ + state = STOPPING; + + /* Flush two buffers worth of data. */ + flush_pos = pcm->GetPlayPos(); + } + } else { + /* Silence the buffer. */ + memset( buf, 0, len ); + } pcm->Write( buf, frames_to_fill ); + + return true; } void RageSound_ALSA9::StartMixing(RageSound *snd) { - sound *s = new sound; - s->snd = snd; - LockMutex L(SOUNDMAN->lock); - sounds.push_back(s); + + /* Find an unused buffer. */ + unsigned i; + for( i = 0; i < stream_pool.size(); ++i ) + { + if( stream_pool[i]->state == stream_pool[i]->INACTIVE ) + break; + } + + if( i == stream_pool.size() ) + { + /* We don't have a free sound buffer. Fake it. */ + SOUNDMAN->AddFakeSound(snd); + return; + } + + /* Give the stream to the playing sound and remove it from the pool. */ + stream_pool[i]->snd = snd; + stream_pool[i]->pcm->SetSampleRate( snd->GetSampleRate() ); + + /* Pre-buffer the stream. */ + stream_pool[i]->GetData(true); + stream_pool[i]->pcm->Play(); + + /* Normally, at this point we should still be INACTIVE, in which case, + * tell the mixer thread to start mixing this channel. However, if it's + * been changed to STOPPING, then we actually finished the whole file + * in the prebuffering GetData calls above, so leave it alone and let it + * finish on its own. */ + if( stream_pool[i]->state == stream_pool[i]->INACTIVE ) + stream_pool[i]->state = stream_pool[i]->PLAYING; } void RageSound_ALSA9::Update(float delta) { - LockMutex L(SOUNDMAN->lock); - /* SoundStopped might erase sounds out from under us, so make a copy * of the sound list. */ - vector snds = sounds; - for(unsigned i = 0; i < snds.size(); ++i) - { - if(!sounds[i]->stopping) continue; + vector str = stream_pool; + ASSERT( SOUNDMAN ); + LockMutex L( SOUNDMAN->lock ); - if(GetPosition(snds[i]->snd) < sounds[i]->flush_pos) + for(unsigned i = 0; i < str.size(); ++i) + { + if( str[i]->state != str[i]->STOPPING ) + continue; + + int ps = str[i]->pcm->GetPosition(); + if( ps < str[i]->flush_pos ) continue; /* stopping but still flushing */ - /* This sound is done. */ - snds[i]->snd->StopPlaying(); + /* The sound has stopped and flushed all of its buffers. */ + if( str[i]->snd != NULL ) + str[i]->snd->StopPlaying(); + str[i]->snd = NULL; + + str[i]->pcm->Stop(); + str[i]->state = str[i]->INACTIVE; } + } void RageSound_ALSA9::StopMixing(RageSound *snd) { + ASSERT(snd != NULL); LockMutex L(SOUNDMAN->lock); - /* Find the sound. */ unsigned i; - for(i = 0; i < sounds.size(); ++i) - if(sounds[i]->snd == snd) break; - if(i == sounds.size()) + for( i = 0; i < stream_pool.size(); ++i ) + if(stream_pool[i]->snd == snd) + break; + + if( i == stream_pool.size() ) { LOG->Trace("not stopping a sound because it's not playing"); return; } - delete sounds[i]; - sounds.erase(sounds.begin()+i, sounds.begin()+i+1); + /* STOPPING tells the mixer thread to release the stream once str->flush_bufs + * buffers have been flushed. */ + stream_pool[i]->state = stream_pool[i]->STOPPING; + + /* Flush two buffers worth of data. */ + stream_pool[i]->flush_pos = stream_pool[i]->pcm->GetPlayPos(); + + /* This function is called externally (by RageSound) to stop immediately. + * We need to prevent SoundStopped from being called; it should only be + * called when we stop implicitely at the end of a sound. Set snd to NULL. */ + stream_pool[i]->snd = NULL; } int RageSound_ALSA9::GetPosition(const RageSound *snd) const { - return pcm->GetPosition(); + LockMutex L(SOUNDMAN->lock); + + unsigned i; + for( i = 0; i < stream_pool.size(); ++i ) + if( stream_pool[i]->snd == snd ) + break; + + if( i == stream_pool.size() ) + RageException::Throw("GetPosition: Sound %s is not being played", snd->GetLoadedFilePath().c_str()); + + return stream_pool[i]->pcm->GetPosition(); } @@ -137,8 +211,37 @@ RageSound_ALSA9::RageSound_ALSA9() { shutdown = false; - pcm = new Alsa9Buf( Alsa9Buf::HW_DONT_CARE, channels, samplerate, max_writeahead ); - + /* Create a bunch of streams and put them into the stream pool. */ + for(int i = 0; i < 8; ++i) + { +// LOG->Trace("%i\n", i); + Alsa9Buf *newbuf; + try { + newbuf = new Alsa9Buf( Alsa9Buf::HW_HARDWARE, channels, Alsa9Buf::DYNAMIC_SAMPLERATE ); + } catch(const RageException &e) { + /* If we didn't get at least 8, fail. */ + if(i >= 8) break; /* OK */ + + /* Clean up; the dtor won't be called. */ + for(int n = 0; n < i; ++n) + delete stream_pool[n]; + + if(i) + { + /* We created at least one hardware buffer. */ + LOG->Trace("Could only create %i buffers; need at least 8 (failed with %s). Hardware ALSA driver can't be used.", i, e.what()); + RageException::ThrowNonfatal("Driver unusable (not enough hardware buffers)"); + } + RageException::ThrowNonfatal("Driver unusable (no hardware buffers)"); + } + + stream *s = new stream; + s->pcm = newbuf; + stream_pool.push_back(s); + } + + LOG->Trace("Got %i hardware buffers", stream_pool.size()); + MixingThread.SetName( "RageSound_ALSA9" ); MixingThread.Create( MixerThread_start, this ); } @@ -151,12 +254,8 @@ RageSound_ALSA9::~RageSound_ALSA9() MixingThread.Wait(); LOG->Trace("Mixer thread shut down."); - delete pcm; -} - -float RageSound_ALSA9::GetPlayLatency() const -{ - return float(max_writeahead)/samplerate; + for(unsigned i = 0; i < stream_pool.size(); ++i) + delete stream_pool[i]; } /* diff --git a/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.h b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.h index 05367de594..7d214afdd6 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.h +++ b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9.h @@ -1,5 +1,5 @@ -#ifndef RAGE_SOUND_ALSA9 -#define RAGE_SOUND_ALSA9 +#ifndef RAGE_SOUND_ALSA9_H +#define RAGE_SOUND_ALSA9_H #include "RageSound.h" #include "RageThreads.h" @@ -10,20 +10,35 @@ class RageSound_ALSA9: public RageSoundDriver { private: - struct sound + struct stream { - RageSound *snd; - bool stopping; - int flush_pos; /* state == STOPPING only */ - sound() { snd = NULL; stopping=false; } - }; + /* Actual audio stream: */ + Alsa9Buf *pcm; - /* List of currently playing sounds: */ - vector sounds; + /* Sound object that's playing on this stream, or NULL if this + * channel is available: */ + RageSound *snd; + + enum { + INACTIVE, + PLAYING, + STOPPING + } state; + + int flush_pos; /* state == STOPPING only */ + + bool GetData( bool init ); + + stream() { pcm = NULL; snd = NULL; state=INACTIVE; } + ~stream(); + }; + friend struct stream; + + /* Pool of available streams. */ + vector stream_pool; bool shutdown; - Alsa9Buf *pcm; static int MixerThread_start(void *p); void MixerThread(); @@ -36,7 +51,7 @@ public: void StartMixing(RageSound *snd); void StopMixing(RageSound *snd); int GetPosition(const RageSound *snd) const; - float GetPlayLatency() const; + int GetSampleRate() const { return -1; } void Update(float delta); diff --git a/stepmania/src/arch/Sound/RageSoundDriver_ALSA9_Software.cpp b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9_Software.cpp new file mode 100644 index 0000000000..46c0100823 --- /dev/null +++ b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9_Software.cpp @@ -0,0 +1,165 @@ +#include "global.h" +#include "RageSoundDriver_ALSA9_Software.h" + +#include "RageLog.h" +#include "RageSound.h" +#include "RageSoundManager.h" +#include "RageUtil.h" + +const int channels = 2; +const int samplerate = 44100; + +const int samples_per_frame = channels; +const int bytes_per_frame = sizeof(Sint16) * samples_per_frame; + +const unsigned max_writeahead = 1024*8; + +int RageSound_ALSA9_Software::MixerThread_start(void *p) +{ + ((RageSound_ALSA9_Software *) p)->MixerThread(); + return 0; +} + +void RageSound_ALSA9_Software::MixerThread() +{ + /* SOUNDMAN will be set once RageSoundManager's ctor returns and + * assigns it; we might get here before that happens, though. */ + while(!SOUNDMAN && !shutdown) SDL_Delay(10); + + while(!shutdown) + { + GetData(); + const float delay_ms = 1000 * float(max_writeahead) / samplerate; + SDL_Delay( int(delay_ms) / 2); + } +} + +/* Returns the number of frames processed */ +void RageSound_ALSA9_Software::GetData() +{ + const int frames_to_fill = pcm->GetNumFramesToFill( max_writeahead ); + if( frames_to_fill <= 0 ) + return; + + /* Sint16 represents a single sample + * each frame contains one sample per channel + */ + static Sint16 *buf = NULL; + if (!buf) + buf = new Sint16[max_writeahead*samples_per_frame*4]; + + static SoundMixBuffer mix; + mix.SetVolume( SOUNDMAN->GetMixVolume() ); + + LockMutex L(SOUNDMAN->lock); + for(unsigned i = 0; i < sounds.size(); ++i) + { + if(sounds[i]->stopping) + continue; + + /* Call the callback. + * Get the units straight, + * = GetPCM(, , ) + */ + unsigned got = sounds[i]->snd->GetPCM( (char *) buf, frames_to_fill*bytes_per_frame, pcm->GetPlayPos() ) / sizeof(Sint16); + mix.write((Sint16 *) buf, got); + + if( int(got) < frames_to_fill ) + { + /* This sound is finishing. */ + sounds[i]->stopping = true; + } + } + L.Unlock(); + + memset( buf, 0, sizeof(Sint16) * frames_to_fill * samples_per_frame ); + mix.read( buf ); + + pcm->Write( buf, frames_to_fill ); +} + + +void RageSound_ALSA9_Software::StartMixing(RageSound *snd) +{ + sound *s = new sound; + s->snd = snd; + + LockMutex L(SOUNDMAN->lock); + sounds.push_back(s); +} + +void RageSound_ALSA9_Software::Update(float delta) +{ + LockMutex L(SOUNDMAN->lock); + + /* SoundStopped might erase sounds out from under us, so make a copy + * of the sound list. */ + vector snds = sounds; + for(unsigned i = 0; i < snds.size(); ++i) + { + if(!sounds[i]->stopping) continue; + + if(GetPosition(snds[i]->snd) < sounds[i]->flush_pos) + continue; /* stopping but still flushing */ + + /* This sound is done. */ + snds[i]->snd->StopPlaying(); + } +} + +void RageSound_ALSA9_Software::StopMixing(RageSound *snd) +{ + LockMutex L(SOUNDMAN->lock); + + /* Find the sound. */ + unsigned i; + for(i = 0; i < sounds.size(); ++i) + if(sounds[i]->snd == snd) break; + if(i == sounds.size()) + { + LOG->Trace("not stopping a sound because it's not playing"); + return; + } + + delete sounds[i]; + sounds.erase(sounds.begin()+i, sounds.begin()+i+1); +} + + +int RageSound_ALSA9_Software::GetPosition(const RageSound *snd) const +{ + return pcm->GetPosition(); +} + + +RageSound_ALSA9_Software::RageSound_ALSA9_Software() +{ + shutdown = false; + + pcm = new Alsa9Buf( Alsa9Buf::HW_DONT_CARE, channels, samplerate ); + + MixingThread.SetName( "RageSound_ALSA9_Software" ); + MixingThread.Create( MixerThread_start, this ); +} + +RageSound_ALSA9_Software::~RageSound_ALSA9_Software() +{ + /* Signal the mixing thread to quit. */ + shutdown = true; + LOG->Trace("Shutting down mixer thread ..."); + MixingThread.Wait(); + LOG->Trace("Mixer thread shut down."); + + delete pcm; +} + +float RageSound_ALSA9_Software::GetPlayLatency() const +{ + return float(max_writeahead)/samplerate; +} + +/* + * Copyright (c) 2002 by the person(s) listed below. All rights reserved. + * + * Glenn Maynard (RageSoundDriver_WaveOut) + */ diff --git a/stepmania/src/arch/Sound/RageSoundDriver_ALSA9_Software.h b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9_Software.h new file mode 100644 index 0000000000..2ed642d649 --- /dev/null +++ b/stepmania/src/arch/Sound/RageSoundDriver_ALSA9_Software.h @@ -0,0 +1,56 @@ +#ifndef RAGE_SOUND_ALSA9_SOFTWARE_H +#define RAGE_SOUND_ALSA9_SOFTWARE_H + +#include "RageSound.h" +#include "RageThreads.h" +#include "RageSoundDriver.h" + +#include "ALSA9Helpers.h" + +class RageSound_ALSA9_Software: public RageSoundDriver +{ +private: + struct sound + { + RageSound *snd; + bool stopping; + int flush_pos; /* state == STOPPING only */ + sound() { snd = NULL; stopping=false; } + }; + + /* List of currently playing sounds: */ + vector sounds; + + bool shutdown; + + Alsa9Buf *pcm; + + static int MixerThread_start(void *p); + void MixerThread(); + RageThread MixingThread; + + void GetData(); + +public: + /* virtuals: */ + void StartMixing(RageSound *snd); + void StopMixing(RageSound *snd); + int GetPosition(const RageSound *snd) const; + float GetPlayLatency() const; + + void Update(float delta); + + RageSound_ALSA9_Software(); + ~RageSound_ALSA9_Software(); +}; + +#endif + +/* + * Copyright (c) 2003 by the person(s) listed below. All rights reserved. + * + * Glenn Maynard + * + * 2003-02 Modified to fake playing sound Aaron VonderHaar + * + */ diff --git a/stepmania/src/arch/arch.cpp b/stepmania/src/arch/arch.cpp index 5a7777987f..796479db89 100644 --- a/stepmania/src/arch/arch.cpp +++ b/stepmania/src/arch/arch.cpp @@ -114,6 +114,7 @@ RageSoundDriver *MakeRageSoundDriver(CString drivers) #endif #ifdef HAVE_ALSA if(!DriversToTry[i].CompareNoCase("ALSA9")) ret = new RageSound_ALSA9; + if(!DriversToTry[i].CompareNoCase("ALSA9-sw")) ret = new RageSound_ALSA9_Software; #endif #ifdef HAVE_OSS if(!DriversToTry[i].CompareNoCase("OSS")) ret = new RageSound_OSS; diff --git a/stepmania/src/arch/arch.h b/stepmania/src/arch/arch.h index d6451a82dd..dbfe7d47ef 100644 --- a/stepmania/src/arch/arch.h +++ b/stepmania/src/arch/arch.h @@ -22,7 +22,7 @@ RageSoundDriver *MakeRageSoundDriver(CString drivers); /* Define the default list of sound drivers for each arch. It's * OK to list drivers that may not be available. */ #if defined(LINUX) - #define DEFAULT_SOUND_DRIVER_LIST "ALSA9,OSS,Null" + #define DEFAULT_SOUND_DRIVER_LIST "ALSA9,ALSA9-sw,OSS,Null" #elif defined(DARWIN) #define DEFAULT_SOUND_DRIVER_LIST "CoreAudio,QT,QT1" #elif defined(_WINDOWS) diff --git a/stepmania/src/arch/arch_linux.h b/stepmania/src/arch/arch_linux.h index 7c64fd157e..63e60ecf26 100644 --- a/stepmania/src/arch/arch_linux.h +++ b/stepmania/src/arch/arch_linux.h @@ -3,6 +3,7 @@ #ifdef HAVE_ALSA #include "Sound/RageSoundDriver_ALSA9.h" +#include "Sound/RageSoundDriver_ALSA9_Software.h" #endif #ifdef HAVE_OSS