//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //===========================================================================// #include "audio_pch.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern bool snd_firsttime; extern bool MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans ); extern void S_SpatializeChannel( int volume[6], int master_vol, const Vector *psourceDir, float gain, float mono ); // 64K is > 1 second at 16-bit, 22050 Hz // 44k: UNDONE - need to double buffers now that we're playing back at 44100? #define WAV_BUFFERS 64 #define WAV_MASK 0x3F #define WAV_BUFFER_SIZE 0x0400 //----------------------------------------------------------------------------- // // NOTE: This only allows 16-bit, stereo wave out // //----------------------------------------------------------------------------- class CAudioDeviceWave : public CAudioDeviceBase { public: bool IsActive( void ); bool Init( void ); void Shutdown( void ); void PaintEnd( void ); int GetOutputPosition( void ); void ChannelReset( int entnum, int channelIndex, float distanceMod ); void Pause( void ); void UnPause( void ); float MixDryVolume( void ); bool Should3DMix( void ); void StopAllSounds( void ); int PaintBegin( float mixAheadTime, int soundtime, int paintedtime ); void ClearBuffer( void ); void UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up ); void MixBegin( int sampleCount ); void MixUpsample( int sampleCount, int filtertype ); void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ); void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ); void Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ); void Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ); void TransferSamples( int end ); void SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono); void ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount ); const char *DeviceName( void ) { return "Windows WAVE"; } int DeviceChannels( void ) { return 2; } int DeviceSampleBits( void ) { return 16; } int DeviceSampleBytes( void ) { return 2; } int DeviceDmaSpeed( void ) { return SOUND_DMA_SPEED; } int DeviceSampleCount( void ) { return m_deviceSampleCount; } private: void OpenWaveOut( void ); void CloseWaveOut( void ); void AllocateOutputBuffers(); void FreeOutputBuffers(); void* AllocOutputMemory( int nSize, HGLOBAL &hMemory ); void FreeOutputMemory( HGLOBAL &hMemory ); bool ValidWaveOut( void ) const; int m_deviceSampleCount; int m_buffersSent; int m_buffersCompleted; int m_pauseCount; // 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; // Memory for the wave data + wave headers void *m_pBuffer; LPWAVEHDR m_pWaveHdr; }; //----------------------------------------------------------------------------- // Class factory //----------------------------------------------------------------------------- IAudioDevice *Audio_CreateWaveDevice( void ) { CAudioDeviceWave *wave = NULL; if ( !wave ) { wave = new CAudioDeviceWave; } if ( wave->Init() ) return wave; delete wave; wave = NULL; return NULL; } //----------------------------------------------------------------------------- // Init, shutdown //----------------------------------------------------------------------------- bool CAudioDeviceWave::Init( void ) { m_bSurround = false; m_bSurroundCenter = false; m_bHeadphone = false; m_buffersSent = 0; m_buffersCompleted = 0; m_pauseCount = 0; m_waveOutHandle = 0; m_pBuffer = NULL; m_pWaveHdr = NULL; m_hWaveHdr = NULL; m_hWaveData = NULL; OpenWaveOut(); if ( snd_firsttime ) { DevMsg( "Wave sound initialized\n" ); } return ValidWaveOut(); } 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 = DeviceDmaSpeed(); // 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 ) { DevWarning( "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 ) { DevWarning( "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 ) { DevWarning( "Sound: Out of memory.\n"); CloseWaveOut(); return NULL; } HPSTR lpData = (char *)GlobalLock( hMemory ); if ( !lpData ) { DevWarning( "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 output buffers //----------------------------------------------------------------------------- void CAudioDeviceWave::AllocateOutputBuffers() { // Allocate and lock memory for the waveform data. int nBufferSize = WAV_BUFFER_SIZE * WAV_BUFFERS; HPSTR lpData = (char *)AllocOutputMemory( nBufferSize, m_hWaveData ); if ( !lpData ) return; // Allocate and lock memory for the waveform header int nHdrSize = sizeof( WAVEHDR ) * WAV_BUFFERS; LPWAVEHDR lpWaveHdr = (LPWAVEHDR)AllocOutputMemory( nHdrSize, m_hWaveHdr ); if ( !lpWaveHdr ) return; // After allocation, set up and prepare headers. for ( int i=0 ; i < WAV_BUFFERS; i++ ) { LPWAVEHDR lpHdr = lpWaveHdr + i; lpHdr->dwBufferLength = WAV_BUFFER_SIZE; lpHdr->lpData = lpData + (i * WAV_BUFFER_SIZE); MMRESULT nResult = waveOutPrepareHeader( m_waveOutHandle, lpHdr, sizeof(WAVEHDR) ); if ( nResult != MMSYSERR_NOERROR ) { DevWarning( "Sound: failed to prepare wave headers\n" ); CloseWaveOut(); return; } } m_deviceSampleCount = nBufferSize / DeviceSampleBytes(); m_pBuffer = (void *)lpData; m_pWaveHdr = lpWaveHdr; } //----------------------------------------------------------------------------- // Free output buffers //----------------------------------------------------------------------------- void CAudioDeviceWave::FreeOutputBuffers() { // Unprepare headers. if ( m_pWaveHdr ) { for ( int i=0 ; i < WAV_BUFFERS; i++ ) { waveOutUnprepareHeader( m_waveOutHandle, m_pWaveHdr+i, sizeof(WAVEHDR) ); } } m_pWaveHdr = NULL; m_pBuffer = NULL; FreeOutputMemory( m_hWaveData ); FreeOutputMemory( m_hWaveHdr ); } //----------------------------------------------------------------------------- // Mixing setup //----------------------------------------------------------------------------- int CAudioDeviceWave::PaintBegin( float mixAheadTime, int soundtime, int paintedtime ) { // soundtime - total samples that have been played out to hardware at dmaspeed // paintedtime - total samples that have been mixed at speed // endtime - target for samples in mixahead buffer at speed unsigned int endtime = soundtime + mixAheadTime * DeviceDmaSpeed(); int samps = DeviceSampleCount() >> (DeviceChannels()-1); if ((int)(endtime - soundtime) > samps) endtime = soundtime + samps; if ((endtime - paintedtime) & 0x3) { // The difference between endtime and painted time should align on // boundaries of 4 samples. This is important when upsampling from 11khz -> 44khz. endtime -= (endtime - paintedtime) & 0x3; } return endtime; } //----------------------------------------------------------------------------- // Actually performs the mixing //----------------------------------------------------------------------------- void CAudioDeviceWave::PaintEnd( void ) { LPWAVEHDR h; int wResult; int cblocks; // // find which sound blocks have completed // while (1) { if ( m_buffersCompleted == m_buffersSent ) { //DevMsg ("Sound overrun\n"); break; } if ( ! (m_pWaveHdr[ m_buffersCompleted & WAV_MASK].dwFlags & WHDR_DONE) ) { break; } m_buffersCompleted++; // this buffer has been played } // // submit a few new sound blocks // // 22K sound support // 44k: UNDONE - double blocks out now that we're at 44k playback? cblocks = 4 << 1; while (((m_buffersSent - m_buffersCompleted) >> SAMPLE_16BIT_SHIFT) < cblocks) { h = m_pWaveHdr + ( m_buffersSent&WAV_MASK ); m_buffersSent++; /* * Now the data block can be sent to the output device. The * waveOutWrite function returns immediately and waveform * data is sent to the output device in the background. */ wResult = waveOutWrite( m_waveOutHandle, h, sizeof(WAVEHDR) ); if (wResult != MMSYSERR_NOERROR) { Warning( "Failed to write block to device\n"); Shutdown(); return; } } } int CAudioDeviceWave::GetOutputPosition( void ) { int s = m_buffersSent * WAV_BUFFER_SIZE; s >>= SAMPLE_16BIT_SHIFT; s &= (DeviceSampleCount()-1); return s / DeviceChannels(); } //----------------------------------------------------------------------------- // Pausing //----------------------------------------------------------------------------- void CAudioDeviceWave::Pause( void ) { m_pauseCount++; if (m_pauseCount == 1) { waveOutReset( m_waveOutHandle ); } } void CAudioDeviceWave::UnPause( void ) { if ( m_pauseCount > 0 ) { m_pauseCount--; } } bool CAudioDeviceWave::IsActive( void ) { return ( m_pauseCount == 0 ); } float CAudioDeviceWave::MixDryVolume( void ) { return 0; } bool CAudioDeviceWave::Should3DMix( void ) { return false; } void CAudioDeviceWave::ClearBuffer( void ) { int clear; if ( !m_pBuffer ) return; clear = 0; Q_memset(m_pBuffer, clear, DeviceSampleCount() * DeviceSampleBytes() ); } void CAudioDeviceWave::UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up ) { } void CAudioDeviceWave::MixBegin( int sampleCount ) { MIX_ClearAllPaintBuffers( sampleCount, false ); } void CAudioDeviceWave::MixUpsample( int sampleCount, int filtertype ) { paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr(); int ifilter = ppaint->ifilter; Assert (ifilter < CPAINTFILTERS); S_MixBufferUpsample2x( sampleCount, ppaint->pbuf, &(ppaint->fltmem[ifilter][0]), CPAINTFILTERMEM, filtertype ); ppaint->ifilter++; } void CAudioDeviceWave::Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) { int volume[CCHANVOLUMES]; paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr(); if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1)) return; Mix8MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount ); } void CAudioDeviceWave::Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) { int volume[CCHANVOLUMES]; paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr(); if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 )) return; Mix8StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount ); } void CAudioDeviceWave::Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) { int volume[CCHANVOLUMES]; paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr(); if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1 )) return; Mix16MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount ); } void CAudioDeviceWave::Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) { int volume[CCHANVOLUMES]; paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr(); if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 )) return; Mix16StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount ); } void CAudioDeviceWave::ChannelReset( int entnum, int channelIndex, float distanceMod ) { } void CAudioDeviceWave::TransferSamples( int end ) { int lpaintedtime = g_paintedtime; int endtime = end; // resumes playback... if ( m_pBuffer ) { S_TransferStereo16( m_pBuffer, PAINTBUFFER, lpaintedtime, endtime ); } } void CAudioDeviceWave::SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono ) { VPROF("CAudioDeviceWave::SpatializeChannel"); S_SpatializeChannel( volume, master_vol, &sourceDir, gain, mono ); } void CAudioDeviceWave::StopAllSounds( void ) { } void CAudioDeviceWave::ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount ) { //SX_RoomFX( endtime, filter, timefx ); DSP_Process( idsp, pbuffront, pbufrear, pbufcenter, samplecount ); }