//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //===========================================================================// #include "snd_dev_wave.h" #define WIN32_LEAN_AND_MEAN #include #pragma warning( disable: 4201 ) #include #pragma warning( default: 4201 ) #include #include #include "soundsystem/snd_audio_source.h" #include "soundsystem.h" #include "soundsystem/snd_device.h" #include "tier1/utlvector.h" #include "filesystem.h" #include "sentence.h" //----------------------------------------------------------------------------- // Forward declarations //----------------------------------------------------------------------------- class CAudioMixer; //----------------------------------------------------------------------------- // Important constants //----------------------------------------------------------------------------- // 64K is > 1 second at 16-bit, 22050 Hz // 44k: UNDONE - need to double buffers now that we're playing back at 44100? #define OUTPUT_CHANNEL_COUNT 2 #define BYTES_PER_SAMPLE 2 #define OUTPUT_SAMPLE_RATE SOUND_DMA_SPEED #define OUTPUT_BUFFER_COUNT 64 #define OUTPUT_BUFFER_MASK 0x3F #define OUTPUT_BUFFER_SAMPLE_COUNT (OUTPUT_BUFFER_SIZE_BYTES / BYTES_PER_SAMPLE) #define OUTPUT_BUFFER_SIZE_BYTES 1024 #define PAINTBUFFER_SIZE 1024 #define MAX_CHANNELS 16 //----------------------------------------------------------------------------- // Implementation of IAudioDevice for WAV files //----------------------------------------------------------------------------- class CAudioDeviceWave : public IAudioDevice { public: // Inherited from IAudioDevice virtual bool Init( void ); virtual void Shutdown( void ); virtual const char *DeviceName( void ) const; virtual int DeviceChannels( void ) const; virtual int DeviceSampleBits( void ) const; virtual int DeviceSampleBytes( void ) const; virtual int DeviceSampleRate( void ) const; virtual int DeviceSampleCount( void ) const; virtual void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress, bool forward = true ); virtual void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress, bool forward = true ); virtual void Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress, bool forward = true ); virtual void Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress, bool forward = true ); virtual int PaintBufferSampleCount( void ) const; virtual void MixBegin( void ); // mix a buffer up to time (time is absolute) void Update( float time ); void Flush( void ); void TransferBufferStereo16( short *pOutput, int sampleCount ); int GetOutputPosition( void ); float GetAmountofTimeAhead( void ); int GetNumberofSamplesAhead( void ); void AddSource( CAudioMixer *pSource ); void StopSounds( void ); int FindSourceIndex( CAudioMixer *pSource ); CAudioMixer *GetMixerForSource( CAudioSource *source ); private: class CAudioMixerState { public: CAudioMixer *mixer; }; class CAudioBuffer { public: WAVEHDR *hdr; bool submitted; int submit_sample_count; CUtlVector< CAudioMixerState > m_Referenced; }; struct portable_samplepair_t { int left; int right; }; void OpenWaveOut( void ); void CloseWaveOut( void ); void AllocateOutputBuffers(); void FreeOutputBuffers(); void* AllocOutputMemory( int nSize, HGLOBAL &hMemory ); void FreeOutputMemory( HGLOBAL &hMemory ); bool ValidWaveOut( void ) const; CAudioBuffer *GetEmptyBuffer( void ); void SilenceBuffer( short *pSamples, int sampleCount ); void SetChannel( int channelIndex, CAudioMixer *pSource ); void FreeChannel( int channelIndex ); void RemoveMixerChannelReferences( CAudioMixer *mixer ); void AddToReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ); void RemoveFromReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ); bool IsSourceReferencedByActiveBuffer( CAudioMixer *mixer ); bool IsSoundInReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ); // Compute how many samples we've mixed since most recent buffer submission void ComputeSampleAheadAmount( void ); // This is a single allocation for all wave headers (there are OUTPUT_BUFFER_COUNT of them) HGLOBAL m_hWaveHdr; // This is a single allocation for all wave data (there are OUTPUT_BUFFER_COUNT of them) HANDLE m_hWaveData; HWAVEOUT m_waveOutHandle; float m_mixTime; float m_baseTime; int m_sampleIndex; CAudioBuffer m_buffers[ OUTPUT_BUFFER_COUNT ]; CAudioMixer *m_sourceList[MAX_CHANNELS]; int m_nEstimatedSamplesAhead; portable_samplepair_t m_paintbuffer[ PAINTBUFFER_SIZE ]; }; //----------------------------------------------------------------------------- // Singleton //----------------------------------------------------------------------------- IAudioDevice *Audio_CreateWaveDevice( void ) { return new CAudioDeviceWave; } //----------------------------------------------------------------------------- // Init, shutdown //----------------------------------------------------------------------------- bool CAudioDeviceWave::Init( void ) { m_hWaveData = NULL; m_hWaveHdr = NULL; m_waveOutHandle = NULL; for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) { CAudioBuffer *buffer = &m_buffers[ i ]; Assert( buffer ); buffer->hdr = NULL; buffer->submitted = false; buffer->submit_sample_count = false; } OpenWaveOut(); m_mixTime = m_baseTime = -1; m_sampleIndex = 0; memset( m_sourceList, 0, sizeof(m_sourceList) ); m_nEstimatedSamplesAhead = (int)( ( float ) OUTPUT_SAMPLE_RATE / 10.0f ); return true; } void CAudioDeviceWave::Shutdown( void ) { CloseWaveOut(); } //----------------------------------------------------------------------------- // WAV out device //----------------------------------------------------------------------------- inline bool CAudioDeviceWave::ValidWaveOut( void ) const { return m_waveOutHandle != 0; } //----------------------------------------------------------------------------- // Opens the windows wave out device //----------------------------------------------------------------------------- void CAudioDeviceWave::OpenWaveOut( void ) { WAVEFORMATEX waveFormat; memset( &waveFormat, 0, sizeof(waveFormat) ); // Select a PCM, 16-bit stereo playback device waveFormat.cbSize = sizeof(waveFormat); waveFormat.wFormatTag = WAVE_FORMAT_PCM; waveFormat.nChannels = DeviceChannels(); waveFormat.wBitsPerSample = DeviceSampleBits(); waveFormat.nSamplesPerSec = DeviceSampleRate(); waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; MMRESULT errorCode = waveOutOpen( &m_waveOutHandle, WAVE_MAPPER, &waveFormat, 0, 0L, CALLBACK_NULL ); while ( errorCode != MMSYSERR_NOERROR ) { if ( errorCode != MMSYSERR_ALLOCATED ) { DWarning( "soundsystem", 1, "waveOutOpen failed\n" ); m_waveOutHandle = 0; return; } int nRetVal = MessageBox( NULL, "The sound hardware is in use by another app.\n\n" "Select Retry to try to start sound again or Cancel to run with no sound.", "Sound not available", MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION); if ( nRetVal != IDRETRY ) { DWarning( "soundsystem", 1, "waveOutOpen failure--hardware already in use\n" ); m_waveOutHandle = 0; return; } errorCode = waveOutOpen( &m_waveOutHandle, WAVE_MAPPER, &waveFormat, 0, 0L, CALLBACK_NULL ); } AllocateOutputBuffers(); } //----------------------------------------------------------------------------- // Closes the windows wave out device //----------------------------------------------------------------------------- void CAudioDeviceWave::CloseWaveOut( void ) { if ( ValidWaveOut() ) { waveOutReset( m_waveOutHandle ); FreeOutputBuffers(); waveOutClose( m_waveOutHandle ); m_waveOutHandle = NULL; } } //----------------------------------------------------------------------------- // Alloc output memory //----------------------------------------------------------------------------- void* CAudioDeviceWave::AllocOutputMemory( int nSize, HGLOBAL &hMemory ) { // Output memory for waveform data+hdrs must be // globally allocated with GMEM_MOVEABLE and GMEM_SHARE flags. hMemory = GlobalAlloc( GMEM_MOVEABLE | GMEM_SHARE, nSize ); if ( !hMemory ) { DWarning( "soundsystem", 1, "Sound: Out of memory.\n"); CloseWaveOut(); return NULL; } HPSTR lpData = (char *)GlobalLock( hMemory ); if ( !lpData ) { DWarning( "soundsystem", 1, "Sound: Failed to lock.\n"); GlobalFree( hMemory ); hMemory = NULL; CloseWaveOut(); return NULL; } memset( lpData, 0, nSize ); return lpData; } //----------------------------------------------------------------------------- // Free output memory //----------------------------------------------------------------------------- void CAudioDeviceWave::FreeOutputMemory( HGLOBAL &hMemory ) { if ( hMemory ) { GlobalUnlock( hMemory ); GlobalFree( hMemory ); hMemory = NULL; } } //----------------------------------------------------------------------------- // Allocate, free output buffers //----------------------------------------------------------------------------- void CAudioDeviceWave::AllocateOutputBuffers() { // Allocate and lock memory for the waveform data. int nBufferSize = OUTPUT_BUFFER_SIZE_BYTES * OUTPUT_BUFFER_COUNT; HPSTR lpData = (char *)AllocOutputMemory( nBufferSize, m_hWaveData ); if ( !lpData ) return; // Allocate and lock memory for the waveform header int nHdrSize = sizeof( WAVEHDR ) * OUTPUT_BUFFER_COUNT; LPWAVEHDR lpWaveHdr = (LPWAVEHDR)AllocOutputMemory( nHdrSize, m_hWaveHdr ); if ( !lpWaveHdr ) return; // After allocation, set up and prepare headers. for ( int i=0 ; i < OUTPUT_BUFFER_COUNT; i++ ) { LPWAVEHDR lpHdr = lpWaveHdr + i; lpHdr->dwBufferLength = OUTPUT_BUFFER_SIZE_BYTES; lpHdr->lpData = lpData + (i * OUTPUT_BUFFER_SIZE_BYTES); MMRESULT nResult = waveOutPrepareHeader( m_waveOutHandle, lpHdr, sizeof(WAVEHDR) ); if ( nResult != MMSYSERR_NOERROR ) { DWarning( "soundsystem", 1, "Sound: failed to prepare wave headers\n" ); CloseWaveOut(); return; } m_buffers[i].hdr = lpHdr; } } void CAudioDeviceWave::FreeOutputBuffers() { // Unprepare headers. for ( int i=0 ; i < OUTPUT_BUFFER_COUNT; i++ ) { if ( m_buffers[i].hdr ) { waveOutUnprepareHeader( m_waveOutHandle, m_buffers[i].hdr, sizeof(WAVEHDR) ); m_buffers[i].hdr = NULL; } m_buffers[i].submitted = false; m_buffers[i].submit_sample_count = 0; m_buffers[i].m_Referenced.Purge(); } FreeOutputMemory( m_hWaveData ); FreeOutputMemory( m_hWaveHdr ); } //----------------------------------------------------------------------------- // Device parameters //----------------------------------------------------------------------------- const char *CAudioDeviceWave::DeviceName( void ) const { return "Windows WAVE"; } int CAudioDeviceWave::DeviceChannels( void ) const { return 2; } int CAudioDeviceWave::DeviceSampleBits( void ) const { return (BYTES_PER_SAMPLE * 8); } int CAudioDeviceWave::DeviceSampleBytes( void ) const { return BYTES_PER_SAMPLE; } int CAudioDeviceWave::DeviceSampleRate( void ) const { return OUTPUT_SAMPLE_RATE; } int CAudioDeviceWave::DeviceSampleCount( void ) const { return OUTPUT_BUFFER_SAMPLE_COUNT; } int CAudioDeviceWave::PaintBufferSampleCount( void ) const { return PAINTBUFFER_SIZE; } //----------------------------------------------------------------------------- // Mixing routines //----------------------------------------------------------------------------- void CAudioDeviceWave::Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress, bool forward ) { int sampleIndex = 0; fixedint sampleFrac = inputOffset; int fixup = 0; int fixupstep = 1; if ( !forward ) { fixup = outCount - 1; fixupstep = -1; } for ( int i = 0; i < outCount; i++, fixup += fixupstep ) { int dest = max( outputOffset + fixup, 0 ); m_paintbuffer[ dest ].left += pChannel->leftvol * pData[sampleIndex]; m_paintbuffer[ dest ].right += pChannel->rightvol * pData[sampleIndex]; sampleFrac += rateScaleFix; sampleIndex += FIX_INTPART(sampleFrac); sampleFrac = FIX_FRACPART(sampleFrac); } } void CAudioDeviceWave::Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress, bool forward ) { int sampleIndex = 0; fixedint sampleFrac = inputOffset; int fixup = 0; int fixupstep = 1; if ( !forward ) { fixup = outCount - 1; fixupstep = -1; } for ( int i = 0; i < outCount; i++, fixup += fixupstep ) { int dest = max( outputOffset + fixup, 0 ); m_paintbuffer[ dest ].left += pChannel->leftvol * pData[sampleIndex]; m_paintbuffer[ dest ].right += pChannel->rightvol * pData[sampleIndex+1]; sampleFrac += rateScaleFix; sampleIndex += FIX_INTPART(sampleFrac)<<1; sampleFrac = FIX_FRACPART(sampleFrac); } } void CAudioDeviceWave::Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress, bool forward ) { int sampleIndex = 0; fixedint sampleFrac = inputOffset; int fixup = 0; int fixupstep = 1; if ( !forward ) { fixup = outCount - 1; fixupstep = -1; } for ( int i = 0; i < outCount; i++, fixup += fixupstep ) { int dest = max( outputOffset + fixup, 0 ); m_paintbuffer[ dest ].left += (pChannel->leftvol * pData[sampleIndex])>>8; m_paintbuffer[ dest ].right += (pChannel->rightvol * pData[sampleIndex])>>8; sampleFrac += rateScaleFix; sampleIndex += FIX_INTPART(sampleFrac); sampleFrac = FIX_FRACPART(sampleFrac); } } void CAudioDeviceWave::Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress, bool forward ) { int sampleIndex = 0; fixedint sampleFrac = inputOffset; int fixup = 0; int fixupstep = 1; if ( !forward ) { fixup = outCount - 1; fixupstep = -1; } for ( int i = 0; i < outCount; i++, fixup += fixupstep ) { int dest = max( outputOffset + fixup, 0 ); m_paintbuffer[ dest ].left += (pChannel->leftvol * pData[sampleIndex])>>8; m_paintbuffer[ dest ].right += (pChannel->rightvol * pData[sampleIndex+1])>>8; sampleFrac += rateScaleFix; sampleIndex += FIX_INTPART(sampleFrac)<<1; sampleFrac = FIX_FRACPART(sampleFrac); } } void CAudioDeviceWave::MixBegin( void ) { memset( m_paintbuffer, 0, sizeof(m_paintbuffer) ); } void CAudioDeviceWave::TransferBufferStereo16( short *pOutput, int sampleCount ) { for ( int i = 0; i < sampleCount; i++ ) { if ( m_paintbuffer[i].left > 32767 ) m_paintbuffer[i].left = 32767; else if ( m_paintbuffer[i].left < -32768 ) m_paintbuffer[i].left = -32768; if ( m_paintbuffer[i].right > 32767 ) m_paintbuffer[i].right = 32767; else if ( m_paintbuffer[i].right < -32768 ) m_paintbuffer[i].right = -32768; *pOutput++ = (short)m_paintbuffer[i].left; *pOutput++ = (short)m_paintbuffer[i].right; } } void CAudioDeviceWave::RemoveMixerChannelReferences( CAudioMixer *mixer ) { for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) { RemoveFromReferencedList( mixer, &m_buffers[ i ] ); } } void CAudioDeviceWave::AddToReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ) { // Already in list for ( int i = 0; i < buffer->m_Referenced.Size(); i++ ) { if ( buffer->m_Referenced[ i ].mixer == mixer ) return; } // Just remove it int idx = buffer->m_Referenced.AddToTail(); CAudioMixerState *state = &buffer->m_Referenced[ idx ]; state->mixer = mixer; } void CAudioDeviceWave::RemoveFromReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ) { for ( int i = 0; i < buffer->m_Referenced.Size(); i++ ) { if ( buffer->m_Referenced[ i ].mixer == mixer ) { buffer->m_Referenced.Remove( i ); break; } } } bool CAudioDeviceWave::IsSoundInReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer ) { for ( int i = 0; i < buffer->m_Referenced.Size(); i++ ) { if ( buffer->m_Referenced[ i ].mixer == mixer ) { return true; } } return false; } bool CAudioDeviceWave::IsSourceReferencedByActiveBuffer( CAudioMixer *mixer ) { if ( !ValidWaveOut() ) return false; CAudioBuffer *buffer; for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) { buffer = &m_buffers[ i ]; if ( !buffer->submitted ) continue; if ( buffer->hdr->dwFlags & WHDR_DONE ) continue; // See if it's referenced if ( IsSoundInReferencedList( mixer, buffer ) ) return true; } return false; } CAudioDeviceWave::CAudioBuffer *CAudioDeviceWave::GetEmptyBuffer( void ) { CAudioBuffer *pOutput = NULL; if ( ValidWaveOut() ) { for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) { if ( !(m_buffers[ i ].submitted ) || m_buffers[i].hdr->dwFlags & WHDR_DONE ) { pOutput = &m_buffers[i]; pOutput->submitted = true; pOutput->m_Referenced.Purge(); break; } } } return pOutput; } void CAudioDeviceWave::SilenceBuffer( short *pSamples, int sampleCount ) { int i; for ( i = 0; i < sampleCount; i++ ) { // left *pSamples++ = 0; // right *pSamples++ = 0; } } void CAudioDeviceWave::Flush( void ) { waveOutReset( m_waveOutHandle ); } // mix a buffer up to time (time is absolute) void CAudioDeviceWave::Update( float time ) { if ( !ValidWaveOut() ) return; // reset the system if ( m_mixTime < 0 || time < m_baseTime ) { m_baseTime = time; m_mixTime = 0; } // put time in our coordinate frame time -= m_baseTime; if ( time > m_mixTime ) { CAudioBuffer *pBuffer = GetEmptyBuffer(); // no free buffers, mixing is ahead of the playback! if ( !pBuffer || !pBuffer->hdr ) { //Con_Printf( "out of buffers\n" ); return; } // UNDONE: These numbers are constants // calc number of samples (2 channels * 2 bytes per sample) int sampleCount = pBuffer->hdr->dwBufferLength >> 2; m_mixTime += sampleCount * (1.0f / OUTPUT_SAMPLE_RATE); short *pSamples = reinterpret_cast(pBuffer->hdr->lpData); SilenceBuffer( pSamples, sampleCount ); int tempCount = sampleCount; while ( tempCount > 0 ) { if ( tempCount > PaintBufferSampleCount() ) { sampleCount = PaintBufferSampleCount(); } else { sampleCount = tempCount; } MixBegin(); for ( int i = 0; i < MAX_CHANNELS; i++ ) { CAudioMixer *pSource = m_sourceList[i]; if ( !pSource ) continue; int currentsample = pSource->GetSamplePosition(); bool forward = pSource->GetDirection(); if ( pSource->GetActive() ) { if ( !pSource->MixDataToDevice( this, pSource->GetChannel(), currentsample, sampleCount, DeviceSampleRate(), forward ) ) { // Source becomes inactive when last submitted sample is finally // submitted. But it lingers until it's no longer referenced pSource->SetActive( false ); } else { AddToReferencedList( pSource, pBuffer ); } } else { if ( !IsSourceReferencedByActiveBuffer( pSource ) ) { if ( !pSource->GetAutoDelete() ) { FreeChannel( i ); } } else { pSource->IncrementSamples( pSource->GetChannel(), currentsample, sampleCount, DeviceSampleRate(), forward ); } } } TransferBufferStereo16( pSamples, sampleCount ); m_sampleIndex += sampleCount; tempCount -= sampleCount; pSamples += sampleCount * 2; } // if the buffers aren't aligned on sample boundaries, this will hard-lock the machine! pBuffer->submit_sample_count = GetOutputPosition(); waveOutWrite( m_waveOutHandle, pBuffer->hdr, sizeof(*(pBuffer->hdr)) ); } } /* int CAudioDeviceWave::GetNumberofSamplesAhead( void ) { ComputeSampleAheadAmount(); return m_nEstimatedSamplesAhead; } float CAudioDeviceWave::GetAmountofTimeAhead( void ) { ComputeSampleAheadAmount(); return ( (float)m_nEstimatedSamplesAhead / (float)OUTPUT_SAMPLE_RATE ); } // Find the most recent submitted sample that isn't flagged as whdr_done void CAudioDeviceWave::ComputeSampleAheadAmount( void ) { m_nEstimatedSamplesAhead = 0; int newest_sample_index = -1; int newest_sample_count = 0; CAudioBuffer *buffer; if ( ValidDevice() ) { for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ ) { buffer = &m_buffers[ i ]; if ( !buffer->submitted ) continue; if ( buffer->hdr->dwFlags & WHDR_DONE ) continue; if ( buffer->submit_sample_count > newest_sample_count ) { newest_sample_index = i; newest_sample_count = buffer->submit_sample_count; } } } if ( newest_sample_index == -1 ) return; buffer = &m_buffers[ newest_sample_index ]; int currentPos = GetOutputPosition() ; m_nEstimatedSamplesAhead = currentPos - buffer->submit_sample_count; } */ int CAudioDeviceWave::FindSourceIndex( CAudioMixer *pSource ) { for ( int i = 0; i < MAX_CHANNELS; i++ ) { if ( pSource == m_sourceList[i] ) { return i; } } return -1; } CAudioMixer *CAudioDeviceWave::GetMixerForSource( CAudioSource *source ) { for ( int i = 0; i < MAX_CHANNELS; i++ ) { if ( !m_sourceList[i] ) continue; if ( source == m_sourceList[i]->GetSource() ) { return m_sourceList[i]; } } return NULL; } void CAudioDeviceWave::AddSource( CAudioMixer *pSource ) { int slot = 0; for ( int i = 0; i < MAX_CHANNELS; i++ ) { if ( !m_sourceList[i] ) { slot = i; break; } } if ( m_sourceList[slot] ) { FreeChannel( slot ); } SetChannel( slot, pSource ); pSource->SetActive( true ); } void CAudioDeviceWave::StopSounds( void ) { for ( int i = 0; i < MAX_CHANNELS; i++ ) { if ( m_sourceList[i] ) { FreeChannel( i ); } } } void CAudioDeviceWave::SetChannel( int channelIndex, CAudioMixer *pSource ) { if ( channelIndex < 0 || channelIndex >= MAX_CHANNELS ) return; m_sourceList[channelIndex] = pSource; } void CAudioDeviceWave::FreeChannel( int channelIndex ) { if ( channelIndex < 0 || channelIndex >= MAX_CHANNELS ) return; if ( m_sourceList[channelIndex] ) { RemoveMixerChannelReferences( m_sourceList[channelIndex] ); delete m_sourceList[channelIndex]; m_sourceList[channelIndex] = NULL; } } int CAudioDeviceWave::GetOutputPosition( void ) { if ( !m_waveOutHandle ) return 0; MMTIME mmtime; mmtime.wType = TIME_SAMPLES; waveOutGetPosition( m_waveOutHandle, &mmtime, sizeof( MMTIME ) ); // Convert time to sample count return ( mmtime.u.sample ); }