text: //========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose: 
  //
  //===========================================================================//
  
  #include "audio_pch.h"
  #include <OpenAL/al.h>
  #include <OpenAL/alc.h>
  #ifdef OSX
  #include <OpenAL/MacOSX_OALExtensions.h>
  #endif
  
  
  // memdbgon must be the last include file in a .cpp file!!!
  #include "tier0/memdbgon.h"
  
  #ifndef DEDICATED  // have to test this because VPC is forcing us to compile this file.
  
  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 );
  
  #define NUM_BUFFERS_SOURCES		128
  #define	BUFF_MASK				(NUM_BUFFERS_SOURCES - 1 )
  #define	BUFFER_SIZE			0x0400
  
  
  //-----------------------------------------------------------------------------
  //
  // NOTE: This only allows 16-bit, stereo wave out
  //
  //-----------------------------------------------------------------------------
  class CAudioDeviceOpenAL : 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 "OpenAL"; }
  	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 );
  	bool	ValidWaveOut( void ) const;
  
  	ALuint  m_Buffer[NUM_BUFFERS_SOURCES];
  	ALuint  m_Source[1];
  	int		m_SndBufSize;
  	
  	void *m_sndBuffers;
  	
  	int			m_deviceSampleCount;
  
  	int			m_buffersSent;
  	int			m_buffersCompleted;
  	int			m_pauseCount;
  	bool		m_bSoundsShutdown;
  };
  
  
  IAudioDevice *Audio_CreateOpenALDevice( void )
  {
  	CAudioDeviceOpenAL *wave = NULL;
  	if ( !wave )
  	{
  		wave = new CAudioDeviceOpenAL;
  	}
  	
  	if ( wave->Init() )
  		return wave;
  	
  	delete wave;
  	wave = NULL;
  	
  	return NULL;
  }
  
  
  void OnSndSurroundCvarChanged( IConVar *pVar, const char *pOldString, float flOldValue );
  void OnSndSurroundLegacyChanged( IConVar *pVar, const char *pOldString, float flOldValue );
  
  //-----------------------------------------------------------------------------
  // Init, shutdown
  //-----------------------------------------------------------------------------
  bool CAudioDeviceOpenAL::Init( void )
  {
  	m_SndBufSize = 0;
  	m_sndBuffers = NULL;
  	m_pauseCount = 0;
  
  	m_bSurround = false;
  	m_bSurroundCenter = false;
  	m_bHeadphone = false;
  	m_buffersSent = 0;
  	m_buffersCompleted = 0;
  	m_pauseCount = 0;
  	m_bSoundsShutdown = false;
  	
  	static bool first = true;
  	if ( first )
  	{
  		snd_surround.SetValue( 2 );
  		snd_surround.InstallChangeCallback( &OnSndSurroundCvarChanged );
  		snd_legacy_surround.InstallChangeCallback( &OnSndSurroundLegacyChanged );
  		first = false;
  	}
  	
  	OpenWaveOut();
  
  	if ( snd_firsttime )
  	{
  		DevMsg( "Wave sound initialized\n" );
  	}
  	return ValidWaveOut();
  }
  
  void CAudioDeviceOpenAL::Shutdown( void )
  {
  	CloseWaveOut();
  }
  
  
  //-----------------------------------------------------------------------------
  // WAV out device
  //-----------------------------------------------------------------------------
  inline bool CAudioDeviceOpenAL::ValidWaveOut( void ) const 
  { 
  	return m_sndBuffers != 0; 
  }
  
  
  //-----------------------------------------------------------------------------
  // Opens the windows wave out device
  //-----------------------------------------------------------------------------
  void CAudioDeviceOpenAL::OpenWaveOut( void )
  {
  	m_buffersSent = 0;
  	m_buffersCompleted = 0;
  	
  	ALenum      error;
  	ALCcontext    *newContext = NULL;
  	ALCdevice    *newDevice = NULL;
  	
  	// Create a new OpenAL Device
  	// Pass NULL to specify the system‚use default output device
  	const ALCchar *initStr = (const ALCchar *)"\'( (sampling-rate 44100 ))";
      
  	newDevice = alcOpenDevice(initStr);
  	if (newDevice != NULL)
  	{
  		// Create a new OpenAL Context
  		// The new context will render to the OpenAL Device just created 
  		ALCint attr[] = { ALC_FREQUENCY, DeviceDmaSpeed(), ALC_SYNC, AL_FALSE, 0 };
  		
  		newContext = alcCreateContext(newDevice, attr );
  		if (newContext != NULL)
  		{
  			// Make the new context the Current OpenAL Context
  			alcMakeContextCurrent(newContext);
  			
  			// Create some OpenAL Buffer Objects
  			alGenBuffers( NUM_BUFFERS_SOURCES, m_Buffer);
  			if((error = alGetError()) != AL_NO_ERROR) 
  			{
  				DevMsg("Error Generating Buffers: ");
  				return;
  			}
  			
  			// Create some OpenAL Source Objects
  			alGenSources(1, m_Source);
  			if(alGetError() != AL_NO_ERROR) 
  			{
  				DevMsg("Error generating sources! \n");
  				return;
  			}
  			
  			alListener3f( AL_POSITION,0.0f,0.0f,0.0f);
  			int i;
  			for ( i = 0; i < 1; i++ )
  			{
  				alSource3f( m_Source[i],AL_POSITION,0.0f,0.0f,0.0f );
  				alSourcef( m_Source[i], AL_PITCH, 1.0f );
  				alSourcef( m_Source[i], AL_GAIN, 1.0f );
  			}
  			
  		}
  	}
  	
  	m_SndBufSize = NUM_BUFFERS_SOURCES*BUFFER_SIZE;
  	m_deviceSampleCount = m_SndBufSize / DeviceSampleBytes();
  
  	if ( !m_sndBuffers )
  	{
  		m_sndBuffers = malloc( m_SndBufSize );
  		memset( m_sndBuffers, 0x0, m_SndBufSize );
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Closes the windows wave out device
  //-----------------------------------------------------------------------------
  void CAudioDeviceOpenAL::CloseWaveOut( void ) 
  { 
  	if ( ValidWaveOut() )
  	{
  		ALCcontext  *context = NULL;
  		ALCdevice  *device = NULL;
  	
  		m_bSoundsShutdown = true;
  		alSourceStop( m_Source[0] );
  		
  		// Delete the Sources
  		alDeleteSources(1, m_Source);
  		// Delete the Buffers
  		alDeleteBuffers(NUM_BUFFERS_SOURCES, m_Buffer);
  		
  		//Get active context
  		context = alcGetCurrentContext();
  		//Get device for active context
  		device = alcGetContextsDevice(context);
  		alcMakeContextCurrent( NULL );
  		alcSuspendContext(context);
  		//Release context
  		alcDestroyContext(context);
  		//Close device
  		alcCloseDevice(device);	
  	}
  	
  	if ( m_sndBuffers )
  	{
  		free( m_sndBuffers );
  		m_sndBuffers = NULL;
  	}
  }
  
  
  
  //-----------------------------------------------------------------------------
  // Mixing setup
  //-----------------------------------------------------------------------------
  int CAudioDeviceOpenAL::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;
  }
  
  
  #ifdef OSX
  ALvoid  alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq)
  {
      static alBufferDataStaticProcPtr proc = NULL;
      
      if (proc == NULL) {
          proc = (alBufferDataStaticProcPtr) alGetProcAddress((const ALCchar*) "alBufferDataStatic");
      }
      
      if (proc)
          proc(bid, format, data, size, freq);
  	
  }
  #endif
  
  
  //-----------------------------------------------------------------------------
  // Actually performs the mixing
  //-----------------------------------------------------------------------------
  void CAudioDeviceOpenAL::PaintEnd( void )
  {
  	if ( !m_sndBuffers /*|| m_bSoundsShutdown*/ )
  		return;
  	
  	ALint state;
  	ALenum      error;
  	int iloop;
  	
  	int	cblocks = 4 << 1; 
  	ALint processed = 1;
  	ALuint lastUnqueuedBuffer = 0;
  	ALuint unqueuedBuffer = -1;
  	int nProcessedLoop = 200; // spin for a max of 200 times de-queing buffers, fixes a hang on exit
  	while ( processed > 0 && --nProcessedLoop > 0 )
  	{
  		alGetSourcei( m_Source[ 0 ], AL_BUFFERS_PROCESSED, &processed);
  		error = alGetError();
  		if (error != AL_NO_ERROR)
  			break;
  		
  		if ( processed > 0 )
  		{
  			lastUnqueuedBuffer = unqueuedBuffer;
  			alSourceUnqueueBuffers( m_Source[ 0 ], 1, &unqueuedBuffer );
  			error = alGetError();
  			if ( error != AL_NO_ERROR && error != AL_INVALID_NAME ) 
  			{
  				DevMsg( "Error alSourceUnqueueBuffers %d\n", error );
  				break;
  			}
  			else
  			{
  				m_buffersCompleted++;	// this buffer has been played
  			}
  		}
  	}
  	
  	//
  	// submit a few new sound blocks
  	//
  	// 44K sound support
  	while (((m_buffersSent - m_buffersCompleted) >> SAMPLE_16BIT_SHIFT) < cblocks)
  	{	
  		int iBuf = m_buffersSent&BUFF_MASK; 
  #ifdef OSX
  		alBufferDataStaticProc( m_Buffer[iBuf], AL_FORMAT_STEREO16, (char *)m_sndBuffers + iBuf*BUFFER_SIZE, BUFFER_SIZE, DeviceDmaSpeed() );
  #else
  		alBufferData( m_Buffer[iBuf], AL_FORMAT_STEREO16, (char *)m_sndBuffers + iBuf*BUFFER_SIZE, BUFFER_SIZE, DeviceDmaSpeed() );
  #endif
  		if ( (error = alGetError()) != AL_NO_ERROR ) 
  		{
  			DevMsg( "Error alBufferData %d %d\n", iBuf, error );
  		}  
  		
  		alSourceQueueBuffers( m_Source[0], 1, &m_Buffer[iBuf] );
  		if ( (error = alGetError() ) != AL_NO_ERROR ) 
  		{
  			DevMsg( "Error alSourceQueueBuffers %d %d\n", iBuf, error );
  		}  
  		m_buffersSent++;
  	}
  	
  	// make sure the stream is playing
  	alGetSourcei( m_Source[ 0 ], AL_SOURCE_STATE, &state);
  	if ( state != AL_PLAYING )
  	{
  		DevMsg( "Restarting sound playback\n" );
  		alSourcePlay( m_Source[0] );
  		if((error = alGetError()) != AL_NO_ERROR) 
  		{
  			DevMsg( "Error alSourcePlay %d\n", error );
  		}  
  	}
  }
  
  int CAudioDeviceOpenAL::GetOutputPosition( void )
  {
  	int s = m_buffersSent * BUFFER_SIZE;
  
  	s >>= SAMPLE_16BIT_SHIFT;
  
  	s &= (DeviceSampleCount()-1);
  
  	return s / DeviceChannels();
  }
  
  
  //-----------------------------------------------------------------------------
  // Pausing
  //-----------------------------------------------------------------------------
  void CAudioDeviceOpenAL::Pause( void )
  {
  	m_pauseCount++;
  	if (m_pauseCount == 1)
  	{
  		alSourceStop( m_Source[0] );
  	}
  }
  
  
  void CAudioDeviceOpenAL::UnPause( void )
  {
  	if ( m_pauseCount > 0 )
  	{
  		m_pauseCount--;
  	}
  	
  	if ( m_pauseCount == 0 )
  	{ 
  		alSourcePlay( m_Source[0] );
  	}
  }
  
  bool CAudioDeviceOpenAL::IsActive( void )
  {
  	return ( m_pauseCount == 0 );
  }
  
  float CAudioDeviceOpenAL::MixDryVolume( void )
  {
  	return 0;
  }
  
  
  bool CAudioDeviceOpenAL::Should3DMix( void )
  {
  	return false;
  }
  
  
  void CAudioDeviceOpenAL::ClearBuffer( void )
  {
  	if ( !m_sndBuffers )
  		return;
  
  	Q_memset( m_sndBuffers, 0x0, DeviceSampleCount() * DeviceSampleBytes() );
  }
  
  void CAudioDeviceOpenAL::UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up )
  {
  }
  
  
  void CAudioDeviceOpenAL::MixBegin( int sampleCount )
  {
  	MIX_ClearAllPaintBuffers( sampleCount, false );
  }
  
  
  void CAudioDeviceOpenAL::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 CAudioDeviceOpenAL::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 CAudioDeviceOpenAL::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 CAudioDeviceOpenAL::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 CAudioDeviceOpenAL::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 CAudioDeviceOpenAL::ChannelReset( int entnum, int channelIndex, float distanceMod )
  {
  }
  
  
  void CAudioDeviceOpenAL::TransferSamples( int end )
  {
  	int		lpaintedtime = g_paintedtime;
  	int		endtime = end;
  	
  	// resumes playback...
  
  	if ( m_sndBuffers )
  	{
  		S_TransferStereo16( m_sndBuffers, PAINTBUFFER, lpaintedtime, endtime );
  	}
  }
  
  void CAudioDeviceOpenAL::SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono )
  {
  	VPROF("CAudioDeviceOpenAL::SpatializeChannel");
  	S_SpatializeChannel( volume, master_vol, &sourceDir, gain, mono );
  }
  
  void CAudioDeviceOpenAL::StopAllSounds( void )
  {
  	m_bSoundsShutdown = true;
  	alSourceStop( m_Source[0] );
  }
  
  
  
  void CAudioDeviceOpenAL::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 );
  }
  
  
  static uint32 GetOSXSpeakerConfig()
  {
  	return 2;
  }
  
  static uint32 GetSpeakerConfigForSurroundMode( int surroundMode, const char **pConfigDesc )
  {
  	uint32 newSpeakerConfig = 2;
  	*pConfigDesc = "stereo speaker";
  	return newSpeakerConfig;
  }
  
  
  
  void OnSndSurroundCvarChanged( IConVar *pVar, const char *pOldString, float flOldValue )
  {
  	// if the old value is -1, we're setting this from the detect routine for the first time
  	// no need to reset the device
  	if ( flOldValue == -1 )
  		return;
  	
  	// get the user's previous speaker config
  	uint32 speaker_config = GetOSXSpeakerConfig();
  	
  	// get the new config
  	uint32 newSpeakerConfig = 0;
  	const char *speakerConfigDesc = "";
  	
  	ConVarRef var( pVar );
  	newSpeakerConfig = GetSpeakerConfigForSurroundMode( var.GetInt(), &speakerConfigDesc );
  	// make sure the config has changed
  	if (newSpeakerConfig == speaker_config)
  		return;
  	
  	// set new configuration
  	//SetWindowsSpeakerConfig(newSpeakerConfig);
  	
  	Msg("Speaker configuration has been changed to %s.\n", speakerConfigDesc);
  	
  	// restart sound system so it takes effect
  	//g_pSoundServices->RestartSoundSystem();
  }
  
  void OnSndSurroundLegacyChanged( IConVar *pVar, const char *pOldString, float flOldValue )
  {
  }
  
  #endif // !DEDICATED
  
  