You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
432 lines
12 KiB
432 lines
12 KiB
//===== Copyright © Valve Corporation, All rights reserved. =================//
|
|
//
|
|
// Purpose: audio mix data structures
|
|
//
|
|
//===========================================================================//
|
|
|
|
#ifndef AUDIO_MIX_H
|
|
#define AUDIO_MIX_H
|
|
|
|
#ifdef _WIN32
|
|
#pragma once
|
|
#endif
|
|
|
|
#include "strtools.h" // V_memset
|
|
#include "utlstring.h"
|
|
#include "utlstringtoken.h"
|
|
#include "soundsystem/lowlevel.h"
|
|
|
|
struct dspglobalvars_t
|
|
{
|
|
float m_flMixMin; // min dsp mix at close range
|
|
float m_flMixMax; // max dsp mix at long range
|
|
float m_fldbMixDrop; // reduce mix_min/max by n% if sndlvl of new sound less than dbMin
|
|
float m_flDistanceMin; // range at which sounds are mixed at flMixMin
|
|
float m_flDistanceMax; // range at which sounds are mixed at flMixMax
|
|
uint16 m_ndbMin; // if sndlvl of a new sound is < dbMin, reduce mix_min/max by m_fldbMixDrop
|
|
bool m_bIsOff;
|
|
};
|
|
|
|
class CAudioProcessor
|
|
{
|
|
public:
|
|
virtual ~CAudioProcessor() {}
|
|
CAudioProcessor( const char *pDebugName, int nChannelCount );
|
|
void SetDebugName( const char *pName );
|
|
|
|
virtual void Process( CAudioMixBuffer *pInput, CAudioMixBuffer *pOutput, int nChannelCount, dspglobalvars_t *pGlobals );
|
|
virtual void ProcessSingleChannel( const CAudioMixBuffer &input, CAudioMixBuffer *pOutput, int nChannelIndex ) = 0;
|
|
|
|
// Parameter set can modify internal or global state (or both)
|
|
virtual bool SetControlParameter( CUtlStringToken name, float flValue );
|
|
virtual float GetControlParameter( CUtlStringToken name, float flDefaultValue = 0.0f ) = 0;
|
|
virtual bool SetNameParameter( CUtlStringToken name, uint32 nNameValue ) = 0;
|
|
virtual uint32 GetNameParameter( CUtlStringToken name, uint32 nDefaultValue ) = 0;
|
|
|
|
virtual bool ShouldProcess();
|
|
float GetPrevMix( float flMix );
|
|
void ApplyMonoProcessor( CAudioMixBuffer *pInput, CAudioMixBuffer *pOutput, int nOutputChannelCount, float flMix );
|
|
void ApplyStereoProcessor( CAudioMixBuffer *pInput, CAudioMixBuffer *pOutput, int nOutputChannelCount, float flMix );
|
|
void ApplyNChannelProcessor( CAudioMixBuffer *pInput, CAudioMixBuffer *pOutput, int nChannelCount, float flMix );
|
|
|
|
CUtlString m_debugName;
|
|
uint32 m_nNameHashCode;
|
|
float m_flXFade;
|
|
float m_flXFadePrev;
|
|
float m_flMix;
|
|
int m_nChannels;
|
|
|
|
bool m_bEnabled;
|
|
};
|
|
|
|
struct audio_buffer_input_t
|
|
{
|
|
const short *m_pSamples; // pointer to the samples themselves (in 8, 16, or 32-bit format)
|
|
uint m_nSampleCount; // number of whole samples *not bytes* *not multiplied by channel count* (e.g. a one-second 16-bit 44.1KHz file would be 44100 samples)
|
|
};
|
|
|
|
// UNDONE: deprecate this and move to a 8-int, 16-int, 32-float, N channel with extract specific channel design
|
|
enum vaudio_sampleformats_t
|
|
{
|
|
SAMPLE_INT16_MONO = 0, // default
|
|
SAMPLE_INT8_MONO, // increase to 16-bit and convert to float
|
|
SAMPLE_INT16_STEREO_L, // stereo wave, extract left channel
|
|
SAMPLE_INT16_STEREO_R, // stereo wave, extract right channel
|
|
SAMPLE_INT8_STEREO_L, // stereo wave, extract left channel
|
|
SAMPLE_INT8_STEREO_R, // stereo wave, extract right channel
|
|
SAMPLE_FLOAT32_MONO, // no reformat needed
|
|
};
|
|
|
|
struct audio_source_input_t
|
|
{
|
|
const audio_buffer_input_t *m_pPackets;
|
|
uint m_nSamplingRate;
|
|
uint16 m_nPacketCount;
|
|
uint16 m_nSampleFormat;
|
|
|
|
void InitPackets( const audio_buffer_input_t *pPacketsIn, int nPacketCountIn, int nSamplingRate, int nBitsPerSample, int nChannelsPerSample )
|
|
{
|
|
V_memset( this, 0, sizeof(*this) );
|
|
Assert( nPacketCountIn >= 0 && nPacketCountIn <= UINT16_MAX );
|
|
m_nPacketCount = (uint16)nPacketCountIn;
|
|
m_pPackets = pPacketsIn;
|
|
m_nSamplingRate = nSamplingRate;
|
|
switch( nBitsPerSample )
|
|
{
|
|
case 16:
|
|
m_nSampleFormat = (uint16)( (nChannelsPerSample == 1) ? SAMPLE_INT16_MONO : SAMPLE_INT16_STEREO_L );
|
|
break;
|
|
case 8:
|
|
m_nSampleFormat = (uint16)( (nChannelsPerSample == 1) ? SAMPLE_INT8_MONO : SAMPLE_INT8_STEREO_L );
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct audio_source_indexstate_t
|
|
{
|
|
uint m_nPacketIndex;
|
|
uint m_nBufferSampleOffset;
|
|
uint m_nSampleFracOffset;
|
|
inline void Clear()
|
|
{
|
|
m_nPacketIndex = 0;
|
|
m_nBufferSampleOffset = 0;
|
|
m_nSampleFracOffset = 0;
|
|
}
|
|
};
|
|
|
|
class CAudioMixState
|
|
{
|
|
audio_source_input_t *m_pChannelsIn;
|
|
audio_source_indexstate_t *m_pChannelsOut;
|
|
uint32 m_nInputStride;
|
|
uint32 m_nPlaybackStride;
|
|
uint32 m_nChannelCount;
|
|
|
|
dspglobalvars_t *m_pGlobals;
|
|
|
|
public:
|
|
inline void Init( audio_source_input_t *pInput, uint32 nInputStride, audio_source_indexstate_t *pPlayback, uint32 nPlaybackStride, uint32 nCount )
|
|
{
|
|
m_pChannelsIn = pInput;
|
|
m_pChannelsOut = pPlayback;
|
|
m_nInputStride = nInputStride;
|
|
m_nPlaybackStride = nPlaybackStride;
|
|
m_nChannelCount = nCount;
|
|
}
|
|
void SetDSPGlobals( dspglobalvars_t *pGlobals )
|
|
{
|
|
m_pGlobals = pGlobals;
|
|
}
|
|
|
|
CAudioMixState( audio_source_input_t *pInput, uint32 nInputStride, audio_source_indexstate_t *pPlayback, uint32 nPlaybackStride, uint32 nCount )
|
|
: m_pChannelsIn(pInput), m_pChannelsOut(pPlayback), m_nInputStride(nInputStride), m_nPlaybackStride(nPlaybackStride), m_nChannelCount(nCount)
|
|
{
|
|
}
|
|
CAudioMixState()
|
|
{
|
|
m_pChannelsIn = 0;
|
|
m_pChannelsOut = 0;
|
|
m_nChannelCount = 0;
|
|
m_pGlobals = nullptr;
|
|
}
|
|
|
|
inline void Clear()
|
|
{
|
|
m_pChannelsIn = NULL;
|
|
m_pChannelsOut = NULL;
|
|
m_nChannelCount = 0;
|
|
}
|
|
|
|
inline audio_source_input_t *GetInput( int nIndex ) const
|
|
{
|
|
return (audio_source_input_t *)( (byte *)m_pChannelsIn + nIndex * m_nInputStride );
|
|
}
|
|
|
|
inline audio_source_indexstate_t *GetOutput( int nIndex ) const
|
|
{
|
|
return (audio_source_indexstate_t *)( (byte *)m_pChannelsOut + nIndex * m_nPlaybackStride );
|
|
}
|
|
bool IsChannelFinished( int nChannel ) const
|
|
{
|
|
return ( GetOutput( nChannel )->m_nPacketIndex >= GetInput( nChannel )->m_nPacketCount ) ? true : false;
|
|
}
|
|
|
|
dspglobalvars_t *DSPGlobals() const { return m_pGlobals; }
|
|
|
|
};
|
|
|
|
enum mix_command_id_t
|
|
{
|
|
AUDIO_MIX_CLEAR = 0,
|
|
AUDIO_MIX_EXTRACT_SOURCE,
|
|
AUDIO_MIX_ADVANCE_SOURCE,
|
|
AUDIO_MIX_ACCUMULATE,
|
|
AUDIO_MIX_ACCUMULATE_RAMP,
|
|
AUDIO_MIX_MULTIPLY,
|
|
AUDIO_MIX_PROCESS,
|
|
AUDIO_MIX_SUM,
|
|
AUDIO_MIX_SWAP, // swap two buffers
|
|
AUDIO_MIX_MEASURE_DEBUG_LEVEL,
|
|
AUDIO_MIX_OUTPUT_LEVEL,
|
|
};
|
|
|
|
struct audio_mix_command_t
|
|
{
|
|
uint16 m_nCommandId;
|
|
uint16 m_nOutput;
|
|
uint16 m_nInput0;
|
|
uint16 m_nInput1;
|
|
float m_flParam0;
|
|
float m_flParam1;
|
|
void Init( mix_command_id_t cmd, uint16 nOut )
|
|
{
|
|
m_nCommandId = (uint16)cmd;
|
|
m_nOutput = nOut;
|
|
m_nInput0 = 0;
|
|
m_nInput1 = 0;
|
|
m_flParam0 = 0.0f;
|
|
m_flParam1 = 0.0f;
|
|
}
|
|
|
|
void Init( mix_command_id_t cmd, uint16 nOut, uint16 nIn0, float flScale )
|
|
{
|
|
m_nCommandId = (uint16)cmd;
|
|
m_nOutput = nOut;
|
|
m_nInput0 = nIn0;
|
|
m_nInput1 = 0;
|
|
m_flParam0 = flScale;
|
|
m_flParam1 = 0.0f;
|
|
}
|
|
|
|
void Init( mix_command_id_t cmd, uint16 nOut, uint16 nIn0, uint16 nIn1, float flScale0, float flScale1 )
|
|
{
|
|
m_nCommandId = (uint16)cmd;
|
|
m_nOutput = nOut;
|
|
m_nInput0 = nIn0;
|
|
m_nInput1 = nIn1;
|
|
m_flParam0 = flScale0;
|
|
m_flParam1 = flScale1;
|
|
}
|
|
};
|
|
|
|
class CAudioMixCommandList
|
|
{
|
|
public:
|
|
inline void ClearBuffer( uint16 nTarget )
|
|
{
|
|
audio_mix_command_t cmd;
|
|
cmd.Init( AUDIO_MIX_CLEAR, nTarget );
|
|
m_commands.AddToTail( cmd );
|
|
}
|
|
|
|
void ClearMultichannel( uint16 nTarget, int nCount );
|
|
void ScaleMultichannel( uint16 nTarget, uint16 nInput, int nCount, float flVolume );
|
|
|
|
inline void ExtractSourceToBuffer( uint16 nTarget, uint16 nChannel, float flVolume, float flPitch )
|
|
{
|
|
audio_mix_command_t cmd;
|
|
cmd.Init( AUDIO_MIX_EXTRACT_SOURCE, nTarget, nChannel, 0, flVolume, flPitch );
|
|
m_commands.AddToTail( cmd );
|
|
}
|
|
inline void AdvanceSource( uint16 nChannel, float flPitch )
|
|
{
|
|
audio_mix_command_t cmd;
|
|
cmd.Init( AUDIO_MIX_ADVANCE_SOURCE, 0, nChannel, 0, 0, flPitch );
|
|
m_commands.AddToTail( cmd );
|
|
}
|
|
|
|
inline void ProcessBuffer( uint16 nOutput, uint16 nInput, uint16 nProcessor, int nChannelCount )
|
|
{
|
|
audio_mix_command_t cmd;
|
|
cmd.Init( AUDIO_MIX_PROCESS, nOutput, nInput, nProcessor, float(nChannelCount), 0.0f );
|
|
m_commands.AddToTail( cmd );
|
|
}
|
|
|
|
inline uint16 ProcessBuffer( uint16 nOutput, uint16 nInput, int nChannelCount, CAudioProcessor *pProc )
|
|
{
|
|
uint16 nProcessor = (uint16)m_processors.AddToTail( pProc );
|
|
ProcessBuffer( nOutput, nInput, nProcessor, nChannelCount );
|
|
return nProcessor;
|
|
}
|
|
|
|
inline void ScaleBuffer( uint16 nOutput, uint16 nInput, float flVolume )
|
|
{
|
|
audio_mix_command_t cmd;
|
|
cmd.Init( AUDIO_MIX_MULTIPLY, nOutput, nInput, flVolume );
|
|
m_commands.AddToTail( cmd );
|
|
}
|
|
|
|
inline void AccumulateToBuffer( uint16 nOutput, uint16 nInput, float flVolume )
|
|
{
|
|
// if the volume is zero this will have no effect, so it is safe to skip it
|
|
if ( flVolume == 0.0f )
|
|
return;
|
|
|
|
audio_mix_command_t cmd;
|
|
cmd.Init( AUDIO_MIX_ACCUMULATE, nOutput, nInput, flVolume );
|
|
m_commands.AddToTail( cmd );
|
|
}
|
|
|
|
inline void AccumulateToBufferVolumeRamp( uint16 nOutput, uint16 nInput, float flVolumeStart, float flVolumeEnd )
|
|
{
|
|
// Too small of a volume change to ramp? Just output without the ramp
|
|
// 1e-3f is small enough that we might do it during a normal ramp
|
|
// (2e-3f is the slope of a full scale fade at 512 samples per batch)
|
|
if ( fabs( flVolumeEnd-flVolumeStart) < 1e-3f )
|
|
{
|
|
AccumulateToBuffer( nOutput, nInput, flVolumeEnd );
|
|
return;
|
|
}
|
|
|
|
audio_mix_command_t cmd;
|
|
cmd.Init( AUDIO_MIX_ACCUMULATE_RAMP, nOutput, nInput, 0, flVolumeStart, flVolumeEnd );
|
|
m_commands.AddToTail( cmd );
|
|
}
|
|
|
|
inline void Mix2x1( uint16 nOutput, uint16 nInput0, uint16 nInput1, float flVolume0, float flVolume1 )
|
|
{
|
|
audio_mix_command_t cmd;
|
|
cmd.Init( AUDIO_MIX_SUM, nOutput, nInput0, nInput1, flVolume0, flVolume1 );
|
|
m_commands.AddToTail( cmd );
|
|
}
|
|
|
|
inline void SwapBuffers( uint16 nInput0, uint16 nInput1 )
|
|
{
|
|
audio_mix_command_t cmd;
|
|
cmd.Init( AUDIO_MIX_SWAP, nInput0, nInput1, 1.0f );
|
|
m_commands.AddToTail( cmd );
|
|
}
|
|
|
|
inline void ReadOutputLevel( uint16 nLevelOutput, uint16 nInput0, uint16 nInputChannelCount )
|
|
{
|
|
audio_mix_command_t cmd;
|
|
cmd.Init( AUDIO_MIX_OUTPUT_LEVEL, nLevelOutput, nInput0, nInputChannelCount, 1.0f, 1.0f );
|
|
m_commands.AddToTail( cmd );
|
|
}
|
|
|
|
inline void DebugReadLevel( uint16 nDebugOutput0, uint16 nInput0, uint16 nInputChannelCount )
|
|
{
|
|
audio_mix_command_t cmd;
|
|
cmd.Init( AUDIO_MIX_MEASURE_DEBUG_LEVEL, nDebugOutput0, nInput0, nInputChannelCount, 1.0f, 1.0f );
|
|
m_commands.AddToTail( cmd );
|
|
}
|
|
|
|
inline uint16 AddProcessor( CAudioProcessor *pProc )
|
|
{
|
|
return (uint16)m_processors.AddToTail( pProc );
|
|
}
|
|
|
|
inline void Clear()
|
|
{
|
|
m_commands.RemoveAll();
|
|
m_processors.RemoveAll();
|
|
}
|
|
void AccumulateMultichannel( uint16 nOutput, int nOutputChannels, uint16 nInput, int nInputChannels, float flInputVolume );
|
|
|
|
CUtlVectorFixedGrowable<audio_mix_command_t, 256> m_commands;
|
|
CUtlVectorFixedGrowable<CAudioProcessor *, 8> m_processors;
|
|
};
|
|
|
|
// This describes the state for each iteration of mixing
|
|
// it is the list of all low-level audio operations that need to take place
|
|
// in order to produce one buffer of mixed output
|
|
class CAudioMixDescription : public CAudioMixCommandList
|
|
{
|
|
public:
|
|
inline void Init( int nChannels )
|
|
{
|
|
m_nMixBuffersInUse = 0;
|
|
m_nMixBufferMax = 0;
|
|
m_nDebugOutputCount = 0;
|
|
m_nOutputLevelCount = 0;
|
|
Clear();
|
|
#if USE_VOICE_LAYERS
|
|
for ( int i = 0; i < NUM_VOICE_LAYERS; i++ )
|
|
{
|
|
m_flLayerVolume[i] = 1.0f;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Add a new mix buffer
|
|
inline uint16 AllocMixBuffer( uint nCount = 1 )
|
|
{
|
|
int nOut = m_nMixBuffersInUse;
|
|
m_nMixBuffersInUse += nCount;
|
|
m_nMixBufferMax = MAX( m_nMixBufferMax, m_nMixBuffersInUse );
|
|
return (uint16)nOut;
|
|
}
|
|
inline void FreeMixBuffer( uint16 nStart, uint nCount = 1 )
|
|
{
|
|
// we only support freeing from the end of the stack
|
|
Assert( nStart + nCount == m_nMixBuffersInUse );
|
|
|
|
if ( nStart + nCount == m_nMixBuffersInUse )
|
|
{
|
|
m_nMixBuffersInUse -= nCount;
|
|
}
|
|
}
|
|
|
|
inline int AllocDebugOutputs( int nOutputs )
|
|
{
|
|
int nRet = m_nDebugOutputCount;
|
|
m_nDebugOutputCount += nOutputs;
|
|
return nRet;
|
|
}
|
|
|
|
inline int AllocOutputLevels( int nOutputs )
|
|
{
|
|
int nRet = m_nOutputLevelCount;
|
|
m_nOutputLevelCount += nOutputs;
|
|
return nRet;
|
|
}
|
|
|
|
uint m_nMixBufferMax;
|
|
uint m_nMixBuffersInUse;
|
|
uint m_nDebugOutputCount;
|
|
uint m_nOutputLevelCount;
|
|
#if USE_VOICE_LAYERS
|
|
float m_flLayerVolume[NUM_VOICE_LAYERS];
|
|
#endif
|
|
};
|
|
|
|
struct mix_debug_outputs_t
|
|
{
|
|
uint32 m_nChannelCount;
|
|
float m_flLevel;
|
|
float m_flChannelLevels[8];
|
|
};
|
|
|
|
// NOTE: This object is large (>64KB) declaring one on the stack may crash some platforms
|
|
class CAudioMixResults
|
|
{
|
|
public:
|
|
CUtlVectorFixedGrowable<mix_debug_outputs_t,8> m_debugOutputs;
|
|
CUtlVectorFixedGrowable<float, 16> m_flOutputLevels;
|
|
CUtlVectorFixedGrowable<CAudioMixBuffer, 32> m_pOutput;
|
|
};
|
|
|
|
extern void ProcessAudioMix( CAudioMixResults *pResults, const CAudioMixState &mixState, CAudioMixDescription &mixSetup );
|
|
#endif // AUDIO_MIX_H
|