|
@@ -33,18 +33,224 @@
|
|
|
#include "Sound.h"
|
|
#include "Sound.h"
|
|
|
#include "SoundSource3D.h"
|
|
#include "SoundSource3D.h"
|
|
|
|
|
|
|
|
|
|
+#ifdef USE_OPENGL
|
|
|
#include <portaudio.h>
|
|
#include <portaudio.h>
|
|
|
|
|
+#else
|
|
|
|
|
+#include "Thread.h"
|
|
|
|
|
+#include "Timer.h"
|
|
|
|
|
+
|
|
|
|
|
+#include <windows.h>
|
|
|
|
|
+#include <mmsystem.h>
|
|
|
|
|
+#include <dsound.h>
|
|
|
|
|
+#endif
|
|
|
|
|
|
|
|
#include "DebugNew.h"
|
|
#include "DebugNew.h"
|
|
|
|
|
|
|
|
static const int MIN_BUFFERLENGTH = 20;
|
|
static const int MIN_BUFFERLENGTH = 20;
|
|
|
static const int MIN_MIXRATE = 11025;
|
|
static const int MIN_MIXRATE = 11025;
|
|
|
static const int MAX_MIXRATE = 48000;
|
|
static const int MAX_MIXRATE = 48000;
|
|
|
|
|
+static const int AUDIO_FPS = 100;
|
|
|
|
|
|
|
|
|
|
+#ifdef USE_OPENGL
|
|
|
static unsigned numInstances = 0;
|
|
static unsigned numInstances = 0;
|
|
|
|
|
|
|
|
-static int AudioCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
|
|
|
|
|
- const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData);
|
|
|
|
|
|
|
+static int AudioCallback(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer,
|
|
|
|
|
+ const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData);
|
|
|
|
|
+#else
|
|
|
|
|
+class AudioStream : public Thread
|
|
|
|
|
+{
|
|
|
|
|
+public:
|
|
|
|
|
+ /// Construct
|
|
|
|
|
+ AudioStream(Audio* owner) :
|
|
|
|
|
+ owner_(owner),
|
|
|
|
|
+ dsObject_(0),
|
|
|
|
|
+ dsBuffer_(0)
|
|
|
|
|
+ {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Destruct
|
|
|
|
|
+ ~AudioStream()
|
|
|
|
|
+ {
|
|
|
|
|
+ Close();
|
|
|
|
|
+
|
|
|
|
|
+ if (dsObject_)
|
|
|
|
|
+ {
|
|
|
|
|
+ dsObject_->Release();
|
|
|
|
|
+ dsObject_ = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Create the DirectSound buffer
|
|
|
|
|
+ bool Open(unsigned windowHandle, int bufferLengthMSec, int mixRate, bool stereo)
|
|
|
|
|
+ {
|
|
|
|
|
+ Close();
|
|
|
|
|
+
|
|
|
|
|
+ if (!dsObject_)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (DirectSoundCreate(0, &dsObject_, 0) != DS_OK)
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (dsObject_->SetCooperativeLevel((HWND)windowHandle, DSSCL_PRIORITY) != DS_OK)
|
|
|
|
|
+ return false;
|
|
|
|
|
+
|
|
|
|
|
+ WAVEFORMATEX waveFormat;
|
|
|
|
|
+ waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
|
|
|
|
+ waveFormat.nSamplesPerSec = mixRate;
|
|
|
|
|
+ waveFormat.wBitsPerSample = 16;
|
|
|
|
|
+
|
|
|
|
|
+ if (stereo)
|
|
|
|
|
+ waveFormat.nChannels = 2;
|
|
|
|
|
+ else
|
|
|
|
|
+ waveFormat.nChannels = 1;
|
|
|
|
|
+
|
|
|
|
|
+ sampleSize_ = waveFormat.nChannels * waveFormat.wBitsPerSample / 8;
|
|
|
|
|
+ unsigned numSamples = (bufferLengthMSec * mixRate) / 1000;
|
|
|
|
|
+
|
|
|
|
|
+ waveFormat.nAvgBytesPerSec = mixRate * sampleSize_;
|
|
|
|
|
+ waveFormat.nBlockAlign = sampleSize_;
|
|
|
|
|
+ waveFormat.cbSize = 0;
|
|
|
|
|
+
|
|
|
|
|
+ DSBUFFERDESC bufferDesc;
|
|
|
|
|
+ memset(&bufferDesc, 0, sizeof(bufferDesc));
|
|
|
|
|
+ bufferDesc.dwSize = sizeof(bufferDesc);
|
|
|
|
|
+ bufferDesc.dwFlags = DSBCAPS_STICKYFOCUS;
|
|
|
|
|
+ bufferDesc.dwBufferBytes = numSamples * sampleSize_;
|
|
|
|
|
+ bufferDesc.lpwfxFormat = &waveFormat;
|
|
|
|
|
+ bufferSize_ = bufferDesc.dwBufferBytes;
|
|
|
|
|
+
|
|
|
|
|
+ return dsObject_->CreateSoundBuffer(&bufferDesc, &dsBuffer_, 0) == DS_OK;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Destroy the DirectSound buffer
|
|
|
|
|
+ void Close()
|
|
|
|
|
+ {
|
|
|
|
|
+ StopPlayback();
|
|
|
|
|
+
|
|
|
|
|
+ if (dsBuffer_)
|
|
|
|
|
+ {
|
|
|
|
|
+ dsBuffer_->Release();
|
|
|
|
|
+ dsBuffer_ = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Start playback
|
|
|
|
|
+ bool StartPlayback()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (IsStarted())
|
|
|
|
|
+ return true;
|
|
|
|
|
+ if (!dsBuffer_)
|
|
|
|
|
+ return false;
|
|
|
|
|
+
|
|
|
|
|
+ // Clear the buffer before starting playback
|
|
|
|
|
+ DWORD bytes1, bytes2;
|
|
|
|
|
+ void *ptr1, *ptr2;
|
|
|
|
|
+ if (dsBuffer_->Lock(0, bufferSize_, &ptr1, &bytes1, &ptr2, &bytes2, 0) == DS_OK)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (bytes1)
|
|
|
|
|
+ memset(ptr1, 0, bytes1);
|
|
|
|
|
+ if (bytes2)
|
|
|
|
|
+ memset(ptr2, 0, bytes2);
|
|
|
|
|
+ dsBuffer_->Unlock(ptr1, bytes1, ptr2, bytes2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (Start())
|
|
|
|
|
+ {
|
|
|
|
|
+ SetPriority(THREAD_PRIORITY_ABOVE_NORMAL);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Stop playback
|
|
|
|
|
+ void StopPlayback()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (dsBuffer_ && IsStarted())
|
|
|
|
|
+ Stop();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Mixing thread function
|
|
|
|
|
+ void ThreadFunction()
|
|
|
|
|
+ {
|
|
|
|
|
+ DWORD playCursor = 0;
|
|
|
|
|
+ DWORD writeCursor = 0;
|
|
|
|
|
+
|
|
|
|
|
+ while (shouldRun_)
|
|
|
|
|
+ {
|
|
|
|
|
+ Timer audioUpdateTimer;
|
|
|
|
|
+
|
|
|
|
|
+ // Restore buffer / restart playback if necessary
|
|
|
|
|
+ DWORD status;
|
|
|
|
|
+ dsBuffer_->GetStatus(&status);
|
|
|
|
|
+ if (status == DSBSTATUS_BUFFERLOST)
|
|
|
|
|
+ {
|
|
|
|
|
+ dsBuffer_->Restore();
|
|
|
|
|
+ dsBuffer_->GetStatus(&status);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!(status & DSBSTATUS_PLAYING))
|
|
|
|
|
+ {
|
|
|
|
|
+ dsBuffer_->Play(0, 0, DSBPLAY_LOOPING);
|
|
|
|
|
+ writeCursor = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Get current buffer position
|
|
|
|
|
+ dsBuffer_->GetCurrentPosition(&playCursor, 0);
|
|
|
|
|
+ playCursor %= bufferSize_;
|
|
|
|
|
+ playCursor &= -((int)sampleSize_);
|
|
|
|
|
+
|
|
|
|
|
+ if (playCursor != writeCursor)
|
|
|
|
|
+ {
|
|
|
|
|
+ int writeBytes = playCursor - writeCursor;
|
|
|
|
|
+ if (writeBytes < 0)
|
|
|
|
|
+ writeBytes += bufferSize_;
|
|
|
|
|
+
|
|
|
|
|
+ // Try to lock buffer
|
|
|
|
|
+ DWORD bytes1, bytes2;
|
|
|
|
|
+ void *ptr1, *ptr2;
|
|
|
|
|
+ if (dsBuffer_->Lock(writeCursor, writeBytes, &ptr1, &bytes1, &ptr2, &bytes2, 0) == DS_OK)
|
|
|
|
|
+ {
|
|
|
|
|
+ // Mix sound to locked positions
|
|
|
|
|
+ {
|
|
|
|
|
+ MutexLock Lock(owner_->GetMutex());
|
|
|
|
|
+
|
|
|
|
|
+ if (bytes1)
|
|
|
|
|
+ owner_->MixOutput(ptr1, bytes1 / sampleSize_);
|
|
|
|
|
+ if (bytes2)
|
|
|
|
|
+ owner_->MixOutput(ptr2, bytes2 / sampleSize_);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Unlock buffer and update write cursor
|
|
|
|
|
+ dsBuffer_->Unlock(ptr1, bytes1, ptr2, bytes2);
|
|
|
|
|
+ writeCursor += writeBytes;
|
|
|
|
|
+ if (writeCursor >= bufferSize_)
|
|
|
|
|
+ writeCursor -= bufferSize_;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Sleep the remaining time of the audio update period
|
|
|
|
|
+ int audioSleepTime = Max(1000 / AUDIO_FPS - (int)audioUpdateTimer.GetMSec(false), 0);
|
|
|
|
|
+ Sleep(audioSleepTime);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ dsBuffer_->Stop();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+private:
|
|
|
|
|
+ /// Audio subsystem
|
|
|
|
|
+ Audio* owner_;
|
|
|
|
|
+ /// DirectSound interface
|
|
|
|
|
+ IDirectSound* dsObject_;
|
|
|
|
|
+ /// DirectSound buffer
|
|
|
|
|
+ IDirectSoundBuffer* dsBuffer_;
|
|
|
|
|
+ /// Sound buffer size in bytes
|
|
|
|
|
+ unsigned bufferSize_;
|
|
|
|
|
+ /// Sound buffer sample size
|
|
|
|
|
+ unsigned sampleSize_;
|
|
|
|
|
+ /// Playing flag
|
|
|
|
|
+ bool playing_;
|
|
|
|
|
+};
|
|
|
|
|
+#endif
|
|
|
|
|
|
|
|
OBJECTTYPESTATIC(Audio);
|
|
OBJECTTYPESTATIC(Audio);
|
|
|
|
|
|
|
@@ -62,6 +268,7 @@ Audio::Audio(Context* context) :
|
|
|
masterGain_[i] = 1.0f;
|
|
masterGain_[i] = 1.0f;
|
|
|
|
|
|
|
|
// Initialize PortAudio under static mutex in case this is the first instance
|
|
// Initialize PortAudio under static mutex in case this is the first instance
|
|
|
|
|
+ #ifdef USE_OPENGL
|
|
|
{
|
|
{
|
|
|
MutexLock lock(GetStaticMutex());
|
|
MutexLock lock(GetStaticMutex());
|
|
|
if (!numInstances)
|
|
if (!numInstances)
|
|
@@ -71,6 +278,7 @@ Audio::Audio(Context* context) :
|
|
|
}
|
|
}
|
|
|
++numInstances;
|
|
++numInstances;
|
|
|
}
|
|
}
|
|
|
|
|
+ #endif
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Audio::~Audio()
|
|
Audio::~Audio()
|
|
@@ -78,6 +286,7 @@ Audio::~Audio()
|
|
|
Release();
|
|
Release();
|
|
|
|
|
|
|
|
// Uninitialize PortAudio under static mutex in case this is the last instance
|
|
// Uninitialize PortAudio under static mutex in case this is the last instance
|
|
|
|
|
+ #ifdef USE_OPENGL
|
|
|
{
|
|
{
|
|
|
MutexLock lock(GetStaticMutex());
|
|
MutexLock lock(GetStaticMutex());
|
|
|
|
|
|
|
@@ -85,6 +294,10 @@ Audio::~Audio()
|
|
|
if (!numInstances)
|
|
if (!numInstances)
|
|
|
Pa_Terminate();
|
|
Pa_Terminate();
|
|
|
}
|
|
}
|
|
|
|
|
+ #else
|
|
|
|
|
+ delete (AudioStream*)stream_;
|
|
|
|
|
+ stream_ = 0;
|
|
|
|
|
+ #endif
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool Audio::SetMode(int bufferLengthMSec, int mixRate, bool stereo, bool interpolate)
|
|
bool Audio::SetMode(int bufferLengthMSec, int mixRate, bool stereo, bool interpolate)
|
|
@@ -95,8 +308,9 @@ bool Audio::SetMode(int bufferLengthMSec, int mixRate, bool stereo, bool interpo
|
|
|
mixRate = Clamp(mixRate, MIN_MIXRATE, MAX_MIXRATE);
|
|
mixRate = Clamp(mixRate, MIN_MIXRATE, MAX_MIXRATE);
|
|
|
|
|
|
|
|
// Guarantee a fragment size that is low enough so that Vorbis decoding buffers do not wrap
|
|
// Guarantee a fragment size that is low enough so that Vorbis decoding buffers do not wrap
|
|
|
- unsigned fragmentSize = NextPowerOfTwo(mixRate >> 6);
|
|
|
|
|
|
|
+ fragmentSize_ = NextPowerOfTwo(mixRate >> 6);
|
|
|
|
|
|
|
|
|
|
+ #ifdef USE_OPENGL
|
|
|
PaStreamParameters outputParams;
|
|
PaStreamParameters outputParams;
|
|
|
outputParams.device = Pa_GetDefaultOutputDevice();
|
|
outputParams.device = Pa_GetDefaultOutputDevice();
|
|
|
outputParams.channelCount = stereo ? 2 : 1;
|
|
outputParams.channelCount = stereo ? 2 : 1;
|
|
@@ -109,14 +323,27 @@ bool Audio::SetMode(int bufferLengthMSec, int mixRate, bool stereo, bool interpo
|
|
|
LOGERROR("Failed to open audio stream");
|
|
LOGERROR("Failed to open audio stream");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
+ #else
|
|
|
|
|
+ if (!stream_)
|
|
|
|
|
+ stream_ = new AudioStream(this);
|
|
|
|
|
+
|
|
|
|
|
+ unsigned windowHandle = 0;
|
|
|
|
|
+ Graphics* graphics = GetSubsystem<Graphics>();
|
|
|
|
|
+ if (graphics)
|
|
|
|
|
+ windowHandle = graphics->GetWindowHandle();
|
|
|
|
|
+
|
|
|
|
|
+ if (!((AudioStream*)stream_)->Open(windowHandle, bufferLengthMSec, mixRate, stereo))
|
|
|
|
|
+ {
|
|
|
|
|
+ LOGERROR("Failed to open audio stream");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ #endif
|
|
|
|
|
|
|
|
|
|
+ clipBuffer_ = new int[stereo ? fragmentSize_ << 1 : fragmentSize_];
|
|
|
sampleSize_ = sizeof(short);
|
|
sampleSize_ = sizeof(short);
|
|
|
if (stereo)
|
|
if (stereo)
|
|
|
sampleSize_ <<= 1;
|
|
sampleSize_ <<= 1;
|
|
|
|
|
|
|
|
- // Allocate the clipping buffer
|
|
|
|
|
- clipBuffer_ = new int[stereo ? fragmentSize << 1 : fragmentSize];
|
|
|
|
|
-
|
|
|
|
|
mixRate_ = mixRate;
|
|
mixRate_ = mixRate;
|
|
|
stereo_ = stereo;
|
|
stereo_ = stereo;
|
|
|
interpolate_ = interpolate;
|
|
interpolate_ = interpolate;
|
|
@@ -149,11 +376,19 @@ bool Audio::Play()
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ #ifdef USE_OPENGL
|
|
|
if (Pa_StartStream(stream_) != paNoError)
|
|
if (Pa_StartStream(stream_) != paNoError)
|
|
|
{
|
|
{
|
|
|
LOGERROR("Failed to start playback");
|
|
LOGERROR("Failed to start playback");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
+ #else
|
|
|
|
|
+ if (!((AudioStream*)stream_)->StartPlayback())
|
|
|
|
|
+ {
|
|
|
|
|
+ LOGERROR("Failed to start playback");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ #endif
|
|
|
|
|
|
|
|
playing_ = true;
|
|
playing_ = true;
|
|
|
return true;
|
|
return true;
|
|
@@ -164,7 +399,12 @@ void Audio::Stop()
|
|
|
if (!stream_ || !playing_)
|
|
if (!stream_ || !playing_)
|
|
|
return;
|
|
return;
|
|
|
|
|
|
|
|
|
|
+ #ifdef USE_OPENGL
|
|
|
Pa_StopStream(stream_);
|
|
Pa_StopStream(stream_);
|
|
|
|
|
+ else
|
|
|
|
|
+ ((AudioStream*)stream_)->StopPlayback();
|
|
|
|
|
+ #endif
|
|
|
|
|
+
|
|
|
playing_ = false;
|
|
playing_ = false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -230,6 +470,7 @@ void Audio::RemoveSoundSource(SoundSource* channel)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+#ifdef USE_OPENGL
|
|
|
int AudioCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*
|
|
int AudioCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*
|
|
|
timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
|
|
timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
|
|
|
{
|
|
{
|
|
@@ -242,26 +483,35 @@ int AudioCallback(const void *inputBuffer, void *outputBuffer, unsigned long fra
|
|
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
+#endif
|
|
|
|
|
|
|
|
-void Audio::MixOutput(void *dest, unsigned mixSamples)
|
|
|
|
|
|
|
+void Audio::MixOutput(void *dest, unsigned samples)
|
|
|
{
|
|
{
|
|
|
- unsigned clipSamples = mixSamples;
|
|
|
|
|
- if (stereo_)
|
|
|
|
|
- clipSamples <<= 1;
|
|
|
|
|
-
|
|
|
|
|
- // Clear clip buffer
|
|
|
|
|
- memset(clipBuffer_.RawPtr(), 0, clipSamples * sizeof(int));
|
|
|
|
|
- int* clipPtr = clipBuffer_.RawPtr();
|
|
|
|
|
-
|
|
|
|
|
- // Mix samples to clip buffer
|
|
|
|
|
- for (PODVector<SoundSource*>::Iterator i = soundSources_.Begin(); i != soundSources_.End(); ++i)
|
|
|
|
|
- (*i)->Mix(clipPtr, mixSamples, mixRate_, stereo_, interpolate_);
|
|
|
|
|
-
|
|
|
|
|
- // Copy output from clip buffer to destination
|
|
|
|
|
- clipPtr = clipBuffer_.RawPtr();
|
|
|
|
|
- short* destPtr = (short*)dest;
|
|
|
|
|
- while (clipSamples--)
|
|
|
|
|
- *destPtr++ = Clamp(*clipPtr++, -32768, 32767);
|
|
|
|
|
|
|
+ while (samples)
|
|
|
|
|
+ {
|
|
|
|
|
+ // If sample count exceeds the fragment (clip buffer) size, split the work
|
|
|
|
|
+ unsigned workSamples = Min((int)samples, (int)fragmentSize_);
|
|
|
|
|
+ unsigned clipSamples = workSamples;
|
|
|
|
|
+ if (stereo_)
|
|
|
|
|
+ clipSamples <<= 1;
|
|
|
|
|
+
|
|
|
|
|
+ // Clear clip buffer
|
|
|
|
|
+ memset(clipBuffer_.RawPtr(), 0, clipSamples * sizeof(int));
|
|
|
|
|
+ int* clipPtr = clipBuffer_.RawPtr();
|
|
|
|
|
+
|
|
|
|
|
+ // Mix samples to clip buffer
|
|
|
|
|
+ for (PODVector<SoundSource*>::Iterator i = soundSources_.Begin(); i != soundSources_.End(); ++i)
|
|
|
|
|
+ (*i)->Mix(clipPtr, workSamples, mixRate_, stereo_, interpolate_);
|
|
|
|
|
+
|
|
|
|
|
+ // Copy output from clip buffer to destination
|
|
|
|
|
+ clipPtr = clipBuffer_.RawPtr();
|
|
|
|
|
+ short* destPtr = (short*)dest;
|
|
|
|
|
+ while (clipSamples--)
|
|
|
|
|
+ *destPtr++ = Clamp(*clipPtr++, -32768, 32767);
|
|
|
|
|
+
|
|
|
|
|
+ samples -= workSamples;
|
|
|
|
|
+ ((unsigned char*&)destPtr) += sampleSize_ * workSamples;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void Audio::HandleRenderUpdate(StringHash eventType, VariantMap& eventData)
|
|
void Audio::HandleRenderUpdate(StringHash eventType, VariantMap& eventData)
|
|
@@ -277,9 +527,13 @@ void Audio::Release()
|
|
|
|
|
|
|
|
if (stream_)
|
|
if (stream_)
|
|
|
{
|
|
{
|
|
|
|
|
+ #ifdef USE_OPENGL
|
|
|
Pa_CloseStream(stream_);
|
|
Pa_CloseStream(stream_);
|
|
|
-
|
|
|
|
|
stream_ = 0;
|
|
stream_ = 0;
|
|
|
|
|
+ #else
|
|
|
|
|
+ ((AudioStream*)stream_)->Close();
|
|
|
|
|
+ #endif
|
|
|
|
|
+
|
|
|
clipBuffer_.Reset();
|
|
clipBuffer_.Reset();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|