From 2de143f14eeddc400f303b8a8f7dbeb20603b358 Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Fri, 13 Dec 2002 07:44:34 +0000 Subject: [PATCH] add new sound code --- stepmania/src/RageSound.cpp | 614 +++++++++++++++++++++++++++++ stepmania/src/RageSound.h | 110 ++++++ stepmania/src/RageSoundManager.cpp | 132 +++++++ stepmania/src/RageSoundManager.h | 51 +++ 4 files changed, 907 insertions(+) create mode 100644 stepmania/src/RageSound.cpp create mode 100644 stepmania/src/RageSound.h create mode 100644 stepmania/src/RageSoundManager.cpp create mode 100644 stepmania/src/RageSoundManager.h diff --git a/stepmania/src/RageSound.cpp b/stepmania/src/RageSound.cpp new file mode 100644 index 0000000000..ad0fb6788e --- /dev/null +++ b/stepmania/src/RageSound.cpp @@ -0,0 +1,614 @@ +/* + * Handle loading and decoding of sounds through SDL_sound. This file + * is portable; actual playing is handled in RageSoundManager. + * For small files, pre-decode the entire file into a regular buffer. We + * might want to play many samples at once, and we don't want to have to decode + * 5-10 mp3s simultaneously during play. + * + * For larger files, decode them on the fly. These are usually music, and there's + * usually only one of those playing at a time. When we get updates, decode data + * at the same rate we're playing it. If we don't do this, and we're being read + * in large chunks, we're forced to decode in larger chunks as well, which can + * cause framerate problems. + * + * Known problems: + * I hear a click in one speaker at the beginning of some MP3s. This is probably + * something wrong with my SDL_sound MAD wrapper ... + * + * TODO: + * Rate (speed) + * Configurable buffer sizes (stored in SoundManager) and so on + * + * We need (yet) another layer of abstraction: RageSoundSource. It'll just + * implement the SDL_sound interface (in a class). Two implementations; + * one, used normally, that just wraps SDL_sound; and another that is given + * a list of sounds and time offsets and transparently mixes them together + * at the given times, filling the gaps with silence. This should be an easy + * way to handle autoplay tracks in keyed games. Normal background music can + * be passed to it with an offset of 0 (or the gap, however it works out). + */ + +#include "stdafx.h" +#include "RageSound.h" +#include "RageSoundManager.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "RageException.h" +#include "RageTimer.h" + +#include "SDL_sound-1.0.0/SDL_sound.h" +#ifdef _DEBUG +#pragma comment(lib, "SDL_sound-1.0.0/lib/sdl_sound_static_d.lib") +#else +#pragma comment(lib, "SDL_sound-1.0.0/lib/sdl_sound_static.lib") +#endif + +const int channels = 2; +const int samplesize = 2 * channels; /* 16-bit */ +const int samplerate = 44100; + +/* If a sound is smaller than this, we'll load it entirely into memory. */ +const int max_prebuf_size = 1024*256; + +/* The most data to buffer when streaming. This should generally be at least as large + * as the largest hardware buffer. */ +const int internal_buffer_size = 1024*16; + +/* The amount of data to read from SDL_sound at once. */ +const int read_block_size = 1024; + +RageSound::RageSound() +{ + ASSERT(SOUNDMAN); + LockMutex L(SOUNDMAN->lock); + + static bool initialized = false; + if(!initialized) + { + if(!Sound_Init()) + throw RageException( "RageSoundManager::RageSoundManager: error initializing sound loader: %s", Sound_GetError()); + initialized = true; + } + + stream.Sample = NULL; + position = 0; + playing = false; + Loop = false; + speed = 1.0f; + stream.buf.reserve(internal_buffer_size); + + /* Register ourselves, so we receive Update()s. */ + SOUNDMAN->all_sounds.insert(this); +} + +RageSound::~RageSound() +{ + Unload(); + + /* Unregister ourselves. */ + SOUNDMAN->all_sounds.erase(this); +} + +RageSound::RageSound(const RageSound &cpy) +{ + ASSERT(SOUNDMAN); + LockMutex L(SOUNDMAN->lock); + + stream.Sample = NULL; + position = 0; + playing = false; + Loop = false; + speed = 1.0f; + stream.buf.reserve(internal_buffer_size); + + /* Copy some of it; don't copy the playing state. */ + big = cpy.big; + full_buf = cpy.full_buf; + + if(big) + { + /* We can't copy the Sound_Sample, so load a new one. + * Don't bother trying to load it in a small buffer. */ + Load(cpy.GetLoadedFilePath(), false); + } + + /* Register ourselves, so we receive Update()s. */ + SOUNDMAN->all_sounds.insert(this); +} + +void RageSound::Unload() +{ + if(IsPlaying()) + Stop(); + + Sound_FreeSample(stream.Sample); + stream.Sample = NULL; + + m_sFilePath = ""; + stream.buf.clear(); + + full_buf.erase(); +} + +void RageSound::Load(CString sSoundFilePath, bool cache) +{ + LOG->Trace( "RageSound::LoadSound( '%s' )", sSoundFilePath.GetString() ); + + Unload(); + + m_sFilePath = sSoundFilePath; + + Sound_AudioInfo sound_desired; + sound_desired.channels = channels; + sound_desired.format = AUDIO_S16SYS; + sound_desired.rate = samplerate; + + Sound_Sample *NewSample = Sound_NewSampleFromFile(sSoundFilePath.GetString(), + &sound_desired, read_block_size); + + if( NewSample == NULL ) + throw RageException( "RageSound::LoadSound: error loading %s: %s", + sSoundFilePath.GetString(), Sound_GetError() ); + + /* Try to decode into full_buf. */ + big = false; + if(!cache) + big = true; /* Don't. */ + + /* Check the length, and see if we think it'll fit in the buffer. */ + { + int len = Sound_Length(NewSample); + /* This will fail with EAGAIN if it'll take a while. We only want + * to do this if it's fast. */ + if(len != -1) { + float secs = len / 1000.f; + + int pcmsize = int(secs * samplerate * samplesize); /* seconds -> bytes */ + if(pcmsize > max_prebuf_size) + big = true; /* Don't bother trying to preload it. */ + else + full_buf.reserve(pcmsize); + } + + Sound_Rewind(NewSample); + } + + while(!big) { + int cnt = Sound_Decode(NewSample); + + if(cnt < 0) + throw RageException("Read error on %s: %s", + sSoundFilePath.GetString(), Sound_GetError() ); /* XXX (see other error-handling XXX) */ + + /* Add the buffer. */ + full_buf.append((const char *)NewSample->buffer, + (const char *)NewSample->buffer+cnt); + + if(full_buf.size() > max_prebuf_size) { + full_buf.erase(); + big = true; /* too big */ + } + + if(NewSample->flags & SOUND_SAMPLEFLAG_EOF) + break; + } + + if(big) { + /* Oops; we need to stream it. */ + stream.Sample = NewSample; + Sound_Rewind(stream.Sample); + } else { + /* We're done with the stream. */ + Sound_FreeSample(NewSample); + } + + position = 0; +} + +void RageSound::Play( bool bLoop, float fStartSeconds, float fLengthSeconds ) +{ + LockMutex L(SOUNDMAN->lock); + + if(playing) Stop(); + playing = true; + + Loop = bLoop; + + if(fStartSeconds != -1000) + { + m_StartSeconds = fStartSeconds; + SetPositionSeconds(m_StartSeconds); + } else m_StartSeconds = 0; + + m_LengthSeconds = fLengthSeconds; + + // Tell the sound manager to start mixing us + SOUNDMAN->StartMixing(this); +} + +void RageSound::Update(float delta) +{ + if(playing && big) + stream.FillBuf(int(delta * samplerate * samplesize)); +} + +/* Fill the buffer by about "bytes" worth of data. (We might go a little + * over, and we won't overflow our buffer.) */ +int RageSound::stream_t::FillBuf(int bytes) +{ + ASSERT(Sample); + + if(Sample->flags & SOUND_SAMPLEFLAG_EOF) + return false; /* EOF */ + + while(bytes > 0) + { + if(buf.size()+read_block_size > buf.capacity()) + break; /* full */ + + int cnt = Sound_Decode(Sample); + if(cnt == -1) + { + /* There was a fatal error; get it with Sound_GetError(). + * XXX: How should we handle sound errors? We can't + * just return error, since we're in a separate thread. + * Most of the time we should probably just warn and move + * on (no big deal), but the gameplay screen should query + * periodically and do something more intelligent when we + * fail (so we don't play out the rest of the song in + * silence) ... */ + throw RageException("Read error: %s", + Sound_GetError() ); + } + + buf.write((const char *)Sample->buffer, cnt); + + bytes -= cnt; + } + + return true; +} + +/* Called by the mixer: return a block of sound data. + * Be careful; this is called in a separate thread. */ +int RageSound::GetPCM(char *buffer, int size, int sampleno) +{ + LockMutex L(SOUNDMAN->lock); + + /* If the sound is paused, just fill the buffer with silence. + * Hmm. Pausing is annoying, since we'll get startup latency if + * we keep our buffer playing; we should stop the stream completely + * when we pause ... */ + ASSERT(playing); + + int bytes_stored = 0; + + pos_map[0] = pos_map[1]; + pos_map[1] = pos_map[2]; + pos_map[2] = pos_map[3]; + pos_map[3].clear(); + + /* "sampleno" is the audio driver's conception of time. "position" + * is ours. Keep track of sampleno->position mappings for two GetPCM calls. + * + * This way, when we query the time later on, we can derive position + * values from the sampleno values returned from GetPosition. + * + * We need to keep two buffers worth of values, since we might loop at + * the end of a buffer. */ + + /* Now actually put data from the correct buffer into the output. */ + while(size) + { + int got; + if(position < 0) { + /* We havn't *really* started playing yet, so just feed silence. How + * many more bytes of silence do we need? */ + got = -position * samplesize; + got = min(got, size); + memset(buffer, 0, got); + } else if(big) { + /* Feed data out of our streaming buffer. */ + ASSERT(stream.Sample); + got = min(int(stream.buf.size()), size); + stream.buf.read(buffer, got); + } else { + /* Feed data out of our full buffer. */ + int byte_pos = position * samplesize; + got = min(int(full_buf.size())-byte_pos, size); + got = max(got, 0); + if(got) + memcpy(buffer, full_buf.data()+byte_pos, got); + } + + if(got) { + /* Save this sampleno/position map. */ + pos_map[3].push_back(pos_map_t(sampleno, position, got/samplesize)); + + int got_samples = got / samplesize; /* bytes -> samples */ + + /* This block goes from position to position+got_samples. */ + const float FADE_TIME = 1.5f; + + /* XXX: Loop shouldn't set fading; add a Fade_Time member? + * + * We want to fade when there's FADE_TIME seconds left, but if + * m_LengthSeconds is -1, we don't know the length we're playing. + * (m_LengthSeconds is the length to play, not the length of the + * source.) If we don't know the length, don't fade. */ + if(Loop) { + Sint16 *p = (Sint16 *) buffer; + int this_position = position; + + for(int samp = 0; samp < got_samples; ++samp) + { + float fSecsUntilSilent = (m_StartSeconds + m_LengthSeconds) - float(this_position)/samplerate; + float fVolPercent = fSecsUntilSilent / FADE_TIME; + + fVolPercent = clamp(fVolPercent, 0.f, 1.f); + for(int i = 0; i < channels; ++i) { + *p = short(*p * fVolPercent); + p++; + } + this_position++; + } + } + + + bytes_stored += got; + position += got_samples; + size -= got; + buffer += got; + sampleno += got_samples; + } + + /* If we've filled the buffer, we're done. */ + if(!size) break; + + /* We need more data. Find out if we've hit EOF. */ + bool HitEOF = true; + if(big) { + /* If we don't have any data left buffered, fill the buffer by up to + * as much as we need. */ + if(stream.buf.size() || stream.FillBuf(size)) + HitEOF = false; /* we have more */ + } else { + unsigned byte_pos = position * samplesize; /* samples -> bytes */ + if(byte_pos < full_buf.size()) + HitEOF = false; /* we have more */ + } + + /* If we've passed the stop point (m_StartSeconds+m_LengthSeconds), pretend + * we've hit EOF. */ + if(m_LengthSeconds != -1 && + float(position)/samplerate > m_StartSeconds+m_LengthSeconds) + HitEOF = true; + + if(!HitEOF) + continue; + + /* We're at EOF. If we're not looping, just stop. */ + if(!Loop) + break; + + /* Rewind and start over. */ + SetPositionSeconds(m_StartSeconds); + } + + return bytes_stored; +} + +/* After we finish via the GetPCM return value, this is called by + * RageSoundManager once the sound is actually flushed, which means + * we're *really* stopped. */ +void RageSound::SoundStopped() +{ + LOG->Trace("stop completed"); + Stop(); +} + +void RageSound::Pause() +{ + if(!playing) return; + + // Tell the sound manager to stop mixing this sound. + SOUNDMAN->StopMixing(this); + + playing = false; +} + +void RageSound::Stop() +{ + /* Tell the sound manager to stop mixing this sound. */ + SOUNDMAN->StopMixing(this); + + if(big) { + ASSERT(stream.Sample); + Sound_Rewind(stream.Sample); + stream.buf.clear(); + } + + playing = false; + position = 0; + for(int i = 0; i < 4; ++i) + pos_map[i].clear(); +} + +float RageSound::GetLengthSeconds() +{ + if(big) { + ASSERT(stream.Sample); + + int len = Sound_Length(stream.Sample); + if(len == -1 && stream.Sample->flags & SOUND_SAMPLEFLAG_EAGAIN) { + /* This indicates the length check will take a little while; call + * it again to confirm. */ + len = Sound_Length(stream.Sample); + } + + if(len < 0) + return -1; /* XXX: put a Sound_GetError() error message somewhere */ + + return len / 1000.f; /* ms -> secs */ + } else { + /* We have the whole file loaded; just return the position. */ + return full_buf.size() / (float(samplerate)*samplesize); + } +} + +float RageSound::GetPositionSeconds() +{ + LockMutex L(SOUNDMAN->lock); + + /* If we're not playing, just report the static position. */ + if( !IsPlaying() ) + return position / float(samplerate); + + /* If we don't yet have any position data, GetPCM hasn't yet been called at all, + * so report the static position. */ + { + bool HasData = false; + for(int i = 0; i < 4; ++i) { + if(!pos_map[i].empty()) HasData = true; + } + if(!HasData) { + LOG->Trace("no data yet; %i", position); + return position / float(samplerate); + } + } + + /* Get our current hardware position. */ + int cur_sample = SOUNDMAN->GetPosition(this); + + /* Concatenate the pos_maps. (Just a cheat to simplify this a little; this + * is small, so this isn't very expensive.) */ + vector posmaps; + for(int n = 0; n < 4; ++n) + posmaps.insert(posmaps.end(), pos_map[n].begin(), pos_map[n].end()); + + /* sampleno is probably in one of the pos_maps. Search through them + * to figure out what position this sampleno maps to. */ + + int closest_position = 0, closest_position_dist = INT_MAX; + for(unsigned i = 0; i < posmaps.size(); ++i) { + if(cur_sample >= posmaps[i].sampleno && + cur_sample < posmaps[i].sampleno+posmaps[i].samples) + { + /* cur_sample lies in this block; it's an exact match. Figure + * out the exact position. */ + int diff = posmaps[i].position - posmaps[i].sampleno; + return float(cur_sample + diff) / samplerate; + } + + /* See if the current position is close to the beginning of this block. */ + int dist = abs(posmaps[i].sampleno - cur_sample); + if(dist < closest_position_dist) + { + closest_position_dist = dist; + closest_position = posmaps[i].position; + } + + /* See if the current position is close to the end of this block. */ + dist = abs(posmaps[i].sampleno + posmaps[i].samples - cur_sample); + if(dist < closest_position_dist) + { + closest_position_dist = dist + posmaps[i].samples; + closest_position = posmaps[i].position + posmaps[i].samples; + } + } + + /* The sample is out of the range of data we've actually sent. + * Return the closest position. + * + * There are three cases when this happens: + * 1. After the first GetPCM call, but before it actually gets heard. + * 2. After GetPCM returns EOF and the sound has flushed, but before + * SoundStopped has been called. + * 3. Underflow; we'll be given a larger sample number than we know about. + */ + + return closest_position / float(samplerate); +} + +void RageSound::SetPositionSeconds( float fSeconds ) +{ + LockMutex L(SOUNDMAN->lock); + + position = int(fSeconds * samplerate); + if( fSeconds < 0 ) + fSeconds = 0; + + if(big) { + ASSERT(stream.Sample); + Sound_AccurateSeek(stream.Sample, int(fSeconds * 1000)); + stream.buf.clear(); + } +} + +void RageSound::SetPlaybackRate( float fScale ) +{ + LockMutex L(SOUNDMAN->lock); + + speed = fScale; +} + +void RageSound::LoadAndPlayIfNotAlready( CString sSoundFilePath ) +{ + SOUNDMAN->lock.Lock(); + if( GetLoadedFilePath() == sSoundFilePath && IsPlaying() ) + return; // do nothing + SOUNDMAN->lock.Unlock(); + + Load( sSoundFilePath ); + Play( true ); +} + +void CircBuf::reserve(unsigned n) +{ + clear(); + buf.erase(); + buf.insert(buf.end(), n, 0); +} + +void CircBuf::clear() +{ + cnt = start = 0; +} + +void CircBuf::write(const char *buffer, unsigned buffer_size) +{ + ASSERT(size() + buffer_size <= capacity()); /* overflow */ + + while(buffer_size) + { + unsigned write_pos = start + size(); + if(write_pos >= buf.size()) write_pos -= buf.size(); + + int cpy = min(buffer_size, buf.size() - write_pos); + buf.replace(write_pos, cpy, buffer, cpy); + + cnt += cpy; + + buffer += cpy; + buffer_size -= cpy; + } +} + +void CircBuf::read(char *buffer, unsigned buffer_size) +{ + ASSERT(size() >= buffer_size); /* underflow */ + + while(buffer_size) + { + unsigned total = min(buf.size() - start, size()); + unsigned cpy = min(buffer_size, total); + buf.copy(buffer, cpy, start); + + start += cpy; + if(start == buf.size()) start = 0; + cnt -= cpy; + + buffer += cpy; + buffer_size -= cpy; + } +} diff --git a/stepmania/src/RageSound.h b/stepmania/src/RageSound.h new file mode 100644 index 0000000000..489f1acfcf --- /dev/null +++ b/stepmania/src/RageSound.h @@ -0,0 +1,110 @@ +#ifndef RAGE_SOUND_OBJ_H +#define RAGE_SOUND_OBJ_H + +#include "SDL_sound-1.0.0/SDL_sound.h" + +#include "RageThreads.h" +#include "RageSoundManager.h" + +class CircBuf +{ + string buf; + unsigned cnt, start; + +public: + + CircBuf() { clear(); } + unsigned size() const { return cnt; } + unsigned capacity() const { return buf.size(); } + void reserve(unsigned n); + void clear(); + void write(const char *buf, unsigned size); + void read(char *buf, unsigned size); +}; + +class RageSound +{ + /* These are only used when big == true: */ + struct stream_t { + Sound_Sample *Sample; + int FillBuf(int bytes); + CircBuf buf; + } stream; + + /* These are only used when big == false: */ + basic_string full_buf; + + /* If true, we're streaming data through sample and buf. If false, the entire + * file is pre-loaded into full_buf. */ + bool big; + + /* Sound blocks we've sent out recently through GetPCM. We keep track + * of each block for the last four calls of GetPCM. */ + struct pos_map_t { + /* Sample number from the POV of the sound driver: */ + int sampleno; + + /* Actual sound position within the sample: */ + int position; + + /* The number of samples in this block: */ + int samples; + + pos_map_t(int samp, int pos, int cnt) { sampleno=samp, position=pos; samples=cnt; } + }; + vector pos_map[4]; + + CString m_sFilePath; +// float m_Rate; + + /* If looping, the range to loop: */ + float m_StartSeconds, m_LengthSeconds; + bool Loop; + + /* Current position of the output sound; if < 0, nothing will play until it + * becomes positive. This is recorded in samples, to avoid rounding error. */ + int position; + bool playing; + + float speed; + +public: + RageSound(); + ~RageSound(); + RageSound(const RageSound &cpy); + + /* If cache is true, we'll preload the entire file into memory if it's + * small enough. False is only generally used when we're going to do + * operations on a file but not actually play it (eg. to find out + * its length). */ + void Load(CString fn, bool cache = true); + + void LoadAndPlayIfNotAlready( CString sSoundFilePath ); + void Unload(); + + void Play( bool bLoop = false, float fStartSeconds = -1000, float fLengthSeconds = -1 ); + void Pause(); + void Stop(); + float GetLengthSeconds(); + float GetPositionSeconds(); + void SetPositionSeconds( float fSeconds ); + void SetPlaybackRate( float fScale ); + float GetPlaybackRate() const { return speed; } + bool IsPlaying() const { return playing; } + CString GetLoadedFilePath() const { return m_sFilePath; }; + + /* Called only by the sound drivers: */ + /* This function should return the number of bytes actually put into buffer. + * If less than size is returned, it signals the stream to stop; once it's + * flushed, SoundStopped will be called. Until then, SOUNDMAN->GetPosition + * can still be called (the sound is still playing). */ + int GetPCM(char *buffer, int size, int sampleno); + + /* Called by the sound driver when a sound has actually finished stopping + * normally. Not called when Stop() is called to stop the sound prematurely. */ + void SoundStopped(); + + void Update(float delta); +}; + +#endif diff --git a/stepmania/src/RageSoundManager.cpp b/stepmania/src/RageSoundManager.cpp new file mode 100644 index 0000000000..9a5fda1734 --- /dev/null +++ b/stepmania/src/RageSoundManager.cpp @@ -0,0 +1,132 @@ +#include "stdafx.h" + +#include "RageSoundManager.h" +#include "RageException.h" +#include "RageUtil.h" +#include "RageSound.h" +#include "RageLog.h" +#include "RageTimer.h" + +#include "arch/arch.h" +#include "arch/Sound/RageSoundDriver.h" + +RageSoundManager::RageSoundManager() +{ + driver = MakeRageSoundDriver(); + if(!driver) + throw RageException("Couldn't find a sound driver that works"); +} + +RageSoundManager::~RageSoundManager() +{ + /* Clear any sounds that we own and havn't freed yet. */ + set::iterator j = owned_sounds.begin(); + while(j != owned_sounds.end()) + delete *(j++); + + delete driver; +} + +void RageSoundManager::StartMixing(RageSound *snd) +{ + driver->StartMixing(snd); +} + +void RageSoundManager::StopMixing(RageSound *snd) +{ + driver->StopMixing(snd); + + /* The sound is finished, and should be deleted if it's in owned_sounds. + * However, this call might be *from* the sound itself, and it'd be + * a mess to delete it while it's on the call stack. Instead, put it + * in a queue to delete, and delete it on the next update. */ + if(owned_sounds.find(snd) != owned_sounds.end()) { + sounds_to_delete.insert(snd); + owned_sounds.erase(snd); + } +} + +int RageSoundManager::GetPosition(const RageSound *snd) const +{ + return driver->GetPosition(snd); +} + +void RageSoundManager::Update(float delta) +{ + while(!sounds_to_delete.empty()) + { + delete *sounds_to_delete.begin(); + sounds_to_delete.erase(sounds_to_delete.begin()); + } + + for(set::iterator i = all_sounds.begin(); + i != all_sounds.end(); ++i) + (*i)->Update(delta); + + driver->Update(delta); +} + +float RageSoundManager::GetPlayLatency() const +{ + return driver->GetPlayLatency(); +} + +void RageSoundManager::PlayCopy( const RageSound &snd ) +{ + /* Make a copy of the sound and play it; this detaches the sound + * from the screen. This has a few effects: + * + * 1. If this is called more than once, it actually plays a new + * copy of the sound, rather than rewinding the currently-playing + * one. (This is important for keyed games.) + * + * 2. If the screen closes, the sound plays to completion. + * + * I'm not sure if copying will become a performance problem. As a + * flag, we could play snd instead of a copy if snd isn't actually + * playing. This would keep property 1 and lose 2, which would be + * fine for keyed games. Copying a streamed sound reopens the sound; + * copying a prebuffered sound makes a new copy of the buffer (unless + * std::basic_string happens to be refcounted). That's not too bad, + * but we might do five of these in the same frame ... + */ + RageSound *newsnd = new RageSound(snd); + + /* We're responsible for freeing it. */ + owned_sounds.insert(newsnd); + + newsnd->Play(); +} + +void RageSoundManager::PlayOnce( CString sPath ) +{ + /* We want this to start quickly, so don't try to prebuffer it. */ + RageSound *snd = new RageSound; + snd->Load(sPath, false); + + /* We're responsible for freeing it. */ + owned_sounds.insert(snd); + + snd->Play(); +} + +void RageSoundManager::PlayOnceFromDir( CString sDir ) +{ + if( sDir == "" ) + return; + + // make sure there's a backslash at the end of this path + if( sDir[sDir.GetLength()-1] != '\\' ) + sDir += "\\"; + + CStringArray arraySoundFiles; + GetDirListing( sDir + "*.mp3", arraySoundFiles ); + GetDirListing( sDir + "*.wav", arraySoundFiles ); + GetDirListing( sDir + "*.ogg", arraySoundFiles ); + + if( arraySoundFiles.empty() ) + return; + + int index = rand() % arraySoundFiles.size(); + PlayOnce( sDir + arraySoundFiles[index] ); +} diff --git a/stepmania/src/RageSoundManager.h b/stepmania/src/RageSoundManager.h new file mode 100644 index 0000000000..d0a14a6d11 --- /dev/null +++ b/stepmania/src/RageSoundManager.h @@ -0,0 +1,51 @@ +#ifndef RAGE_SOUND_MANAGER_H +#define RAGE_SOUND_MANAGER_H + +#include +#include "arch/Sound/RageSoundDriver.h" + +#include "RageThreads.h" + +class RageSound; + +//#define PlayOnceStreamed PlayOnce +//#define PlayOnceStreamedFromDir PlayOnceFromDir + +class RageSoundManager +{ + /* Set of sounds that we've taken over (and are responsible for deleting + * when they're finished playing): */ + set owned_sounds; + + /* Set of sounds that are finished and should be deleted. */ + set sounds_to_delete; + + RageSoundDriver *driver; + +public: + RageMutex lock; + + RageSoundManager(); + ~RageSoundManager(); + + void Update(float delta); + void StartMixing(RageSound *snd); /* used by RageSound */ + void StopMixing(RageSound *snd); /* used by RageSound */ + int GetPosition(const RageSound *snd) const; /* used by RageSound */ + + float GetPlayLatency() const; + + void PlayOnce( CString sPath ); + void PlayOnceFromDir( CString sDir ); + void PlayCopy( const RageSound &snd ); + + + /* A list of all sounds that currently exist. RageSound adds and removes + * itself to this. */ + set all_sounds; +}; + +extern RageSoundManager *SOUNDMAN; + +#endif +