Files
UnrealEngine/Engine/Build/Mac/RadioEffectUnit/RadioEffectUnit.cpp
2025-05-18 13:04:45 +08:00

420 lines
15 KiB
C++

/**
* Copyright Epic Games, Inc. All Rights Reserved.
*/
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RadioEffectUnit.cpp
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include "AUEffectBase.h"
#include "RadioEffectUnitVersion.h"
#include <AudioUnit/AudioUnitProperties.h>
typedef unsigned int UBOOL;
typedef float FLOAT;
#define PI (3.1415926535897932)
inline FLOAT appCos( FLOAT Value ) { return cosf(Value); }
inline FLOAT appTan( FLOAT Value ) { return tanf(Value); }
inline FLOAT appPow( FLOAT A, FLOAT B ) { return powf(A,B); }
/*-----------------------------------------------------------------------------
FBandPassFilter
-----------------------------------------------------------------------------*/
class FBandPassFilter
{
private:
FLOAT Coefficient0;
FLOAT Coefficient1;
FLOAT Coefficient2;
FLOAT Coefficient3;
FLOAT Coefficient4;
FLOAT Z0;
FLOAT Z1;
FLOAT Y0;
FLOAT Y1;
inline static FLOAT CalculateC( FLOAT BandwidthHz, FLOAT SampleRate )
{
const FLOAT Angle = PI * ( ( BandwidthHz * 0.5f ) / SampleRate );
return appTan( Angle - 1.0f ) / appTan( 2.0f * Angle + 1.0f );
}
inline static FLOAT CalculateD( FLOAT CenterFrequencyHz, FLOAT SampleRate )
{
const FLOAT Angle = 2.0f * PI * CenterFrequencyHz / SampleRate;
return -appCos( Angle );
}
public:
/**
* Constructor (default).
*/
FBandPassFilter( void )
: Coefficient0( 0.0f )
, Coefficient1( 0.0f )
, Coefficient2( 0.0f )
, Coefficient3( 0.0f )
, Coefficient4( 0.0f )
, Z0( 0.0f )
, Z1( 0.0f )
, Y0( 0.0f )
, Y1( 0.0f )
{
}
inline void Initialize( FLOAT FrequencyHz, FLOAT BandwidthHz, FLOAT SampleRate )
{
const FLOAT C = CalculateC( BandwidthHz, SampleRate );
const FLOAT D = CalculateD( FrequencyHz, SampleRate );
const FLOAT A0 = 1.0f;
const FLOAT A1 = D * ( 1.0f - C );
const FLOAT A2 = -C;
const FLOAT B0 = 1.0f + C;
const FLOAT B1 = 0.0f;
const FLOAT B2 = -B0;
Coefficient0 = B0 / A0;
Coefficient1 = +B1 / A0;
Coefficient2 = +B2 / A0;
Coefficient3 = -A1 / A0;
Coefficient4 = -A2 / A0;
Z0 = Z1 = Y0 = Y1 = 0.0f;
}
inline FLOAT Process( FLOAT Sample )
{
const FLOAT Y = Coefficient0 * Sample
+ Coefficient1 * Z0
+ Coefficient2 * Z1
+ Coefficient3 * Y0
+ Coefficient4 * Y1;
Z1 = Z0;
Z0 = Sample;
Y1 = Y0;
Y0 = Y;
return Y;
}
};
/*-----------------------------------------------------------------------------
Global utility classes for generating a radio distortion effect.
-----------------------------------------------------------------------------*/
static FBandPassFilter GFinalBandPassFilter;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#pragma mark ____RadioEffectUnit
static UBOOL bGLocalized = FALSE;
class RadioEffectUnit : public AUEffectBase
{
public:
RadioEffectUnit(AudioUnit Component);
virtual AUKernelBase * NewKernel() { return new RadioEffectKernel(this); }
virtual OSStatus GetParameterValueStrings( AudioUnitScope InScope,
AudioUnitParameterID InParameterID,
CFArrayRef * OutStrings );
virtual OSStatus GetParameterInfo( AudioUnitScope InScope,
AudioUnitParameterID InParameterID,
AudioUnitParameterInfo &OutParameterInfo );
virtual OSStatus GetPropertyInfo( AudioUnitPropertyID InID,
AudioUnitScope InScope,
AudioUnitElement InElement,
UInt32 & OutDataSize,
Boolean & OutWritable );
virtual OSStatus GetProperty( AudioUnitPropertyID InID,
AudioUnitScope InScope,
AudioUnitElement InElement,
void * OutData );
// Some hosting apps will REQUIRE that you support this property (and others won't), but
// it is advisable for maximal compatibility that you do support this (and report a conservative
// but reasonable value.)
virtual bool SupportsTail () { return true; }
/*! @method Version */
virtual OSStatus Version() { return kRadioEffectUnitVersion; }
protected:
class RadioEffectKernel : public AUKernelBase // most real work happens here
{
public:
RadioEffectKernel(AUEffectBase *InAudioUnit )
: AUKernelBase(InAudioUnit)
{
// Initialize per-channel state of this effect processor
}
// Required overides for the process method for this effect
// processes one channel of interleaved samples
virtual void Process( const AudioUnitSampleType * InSourceP,
AudioUnitSampleType * InDestP,
UInt32 InFramesToProcess,
UInt32 InNumChannels,
bool & IOSilence);
virtual void Reset();
//private: //state variables...
};
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AUDIOCOMPONENT_ENTRY(AUBaseFactory, RadioEffectUnit)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Unique constants for this effect
// parameters
static const FLOAT kDefaultValue_ChebyshevPowerMultiplier = 2.0f;
static const FLOAT kDefaultValue_ChebyshevPower = 5.0f;
static const FLOAT kDefaultValue_ChebyshevMultiplier = 3.0f;
static const FLOAT kDefaultValue_ChebyshevCubedMultiplier = 5.0f;
static CFStringRef kChebyshevPowerMultiplierName = CFSTR("Chebyshev Power Multiplier");
static CFStringRef kChebyshevPowerName = CFSTR("Chebyshev Power");
static CFStringRef kChebyshevMultiplierName = CFSTR("Chebyshev Multiplier");
static CFStringRef kChebyshevCubedMultiplierName = CFSTR("Chebyshev Cubed Multiplier");
enum {
RadioParam_ChebyshevPowerMultiplier,
RadioParam_ChebyshevPower,
RadioParam_ChebyshevMultiplier,
RadioParam_ChebyshevCubedMultiplier,
RadioNumberOfParameters
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RadioEffectUnit::RadioEffectUnit
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
RadioEffectUnit::RadioEffectUnit(AudioUnit Component)
: AUEffectBase(Component)
{
CreateElements();
if (!bGLocalized) {
// Because we are in a component, we need to load our bundle by identifier so we can access our localized strings
// It is important that the string passed here exactly matches that in the Info.plist Identifier string
CFBundleRef bundle = CFBundleGetBundleWithIdentifier( CFSTR("com.epicgames.audiounit.radio") );
if (bundle != NULL) {
kChebyshevPowerMultiplierName = CFCopyLocalizedStringFromTableInBundle(kChebyshevPowerMultiplierName, CFSTR("Localizable"), bundle, CFSTR(""));
kChebyshevPowerName = CFCopyLocalizedStringFromTableInBundle(kChebyshevPowerName, CFSTR("Localizable"), bundle, CFSTR(""));
kChebyshevMultiplierName = CFCopyLocalizedStringFromTableInBundle(kChebyshevMultiplierName, CFSTR("Localizable"), bundle, CFSTR(""));
kChebyshevCubedMultiplierName = CFCopyLocalizedStringFromTableInBundle(kChebyshevCubedMultiplierName, CFSTR("Localizable"), bundle, CFSTR(""));
}
bGLocalized = TRUE; //so never pass the test again...
}
GFinalBandPassFilter.Initialize( 2000.0f, 400.0f, GetSampleRate() );
SetParameter(RadioParam_ChebyshevPowerMultiplier, kDefaultValue_ChebyshevPowerMultiplier );
SetParameter(RadioParam_ChebyshevPower, kDefaultValue_ChebyshevPower );
SetParameter(RadioParam_ChebyshevMultiplier, kDefaultValue_ChebyshevMultiplier );
SetParameter(RadioParam_ChebyshevCubedMultiplier, kDefaultValue_ChebyshevCubedMultiplier );
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RadioEffectUnit::GetParameterValueStrings
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
OSStatus RadioEffectUnit::GetParameterValueStrings( AudioUnitScope InScope,
AudioUnitParameterID InParameterID,
CFArrayRef * OutStrings)
{
return kAudioUnitErr_InvalidProperty;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RadioEffectUnit::GetParameterInfo
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
OSStatus RadioEffectUnit::GetParameterInfo( AudioUnitScope InScope,
AudioUnitParameterID InParameterID,
AudioUnitParameterInfo &OutParameterInfo )
{
OSStatus Result = noErr;
OutParameterInfo.flags = kAudioUnitParameterFlag_IsWritable
| kAudioUnitParameterFlag_IsReadable;
if (InScope == kAudioUnitScope_Global) {
switch(InParameterID)
{
case RadioParam_ChebyshevPowerMultiplier:
AUBase::FillInParameterName (OutParameterInfo, kChebyshevPowerMultiplierName, false);
OutParameterInfo.unit = kAudioUnitParameterUnit_Ratio;
OutParameterInfo.minValue = 0.0;
OutParameterInfo.maxValue = 100.0;
OutParameterInfo.defaultValue = kDefaultValue_ChebyshevPowerMultiplier;
break;
case RadioParam_ChebyshevPower:
AUBase::FillInParameterName (OutParameterInfo, kChebyshevPowerName, false);
OutParameterInfo.unit = kAudioUnitParameterUnit_Ratio;
OutParameterInfo.minValue = 0.0;
OutParameterInfo.maxValue = 100.0;
OutParameterInfo.defaultValue = kDefaultValue_ChebyshevPower;
break;
case RadioParam_ChebyshevMultiplier:
AUBase::FillInParameterName (OutParameterInfo, kChebyshevMultiplierName, false);
OutParameterInfo.unit = kAudioUnitParameterUnit_Ratio;
OutParameterInfo.minValue = 0.0;
OutParameterInfo.maxValue = 100.0;
OutParameterInfo.defaultValue = kDefaultValue_ChebyshevMultiplier;
break;
case RadioParam_ChebyshevCubedMultiplier:
AUBase::FillInParameterName (OutParameterInfo, kChebyshevCubedMultiplierName, false);
OutParameterInfo.unit = kAudioUnitParameterUnit_Ratio;
OutParameterInfo.minValue = 0.0;
OutParameterInfo.maxValue = 100.0;
OutParameterInfo.defaultValue = kDefaultValue_ChebyshevCubedMultiplier;
break;
default:
Result = kAudioUnitErr_InvalidParameter;
break;
}
} else {
Result = kAudioUnitErr_InvalidParameter;
}
return Result;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RadioEffectUnit::GetPropertyInfo
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
OSStatus RadioEffectUnit::GetPropertyInfo (AudioUnitPropertyID InID,
AudioUnitScope InScope,
AudioUnitElement InElement,
UInt32 & OutDataSize,
Boolean & OutWritable)
{
if (InScope == kAudioUnitScope_Global) {
switch (InID) {
case kAudioUnitProperty_ParameterStringFromValue:
OutWritable = false;
OutDataSize = sizeof (AudioUnitParameterStringFromValue);
return noErr;
case kAudioUnitProperty_ParameterValueFromString:
OutWritable = false;
OutDataSize = sizeof (AudioUnitParameterValueFromString);
return noErr;
}
}
return AUEffectBase::GetPropertyInfo (InID, InScope, InElement, OutDataSize, OutWritable);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RadioEffectUnit::GetProperty
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
OSStatus RadioEffectUnit::GetProperty (AudioUnitPropertyID InID,
AudioUnitScope InScope,
AudioUnitElement InElement,
void * OutData)
{
if (InScope == kAudioUnitScope_Global) {
switch (InID) {
case kAudioUnitProperty_ParameterValueFromString:
return kAudioUnitErr_InvalidParameter;
case kAudioUnitProperty_ParameterStringFromValue:
return kAudioUnitErr_InvalidParameter;
}
}
return AUEffectBase::GetProperty (InID, InScope, InElement, OutData);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#pragma mark ____RadioEffectKernel
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RadioEffectUnit::RadioEffectKernel::Reset()
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void RadioEffectUnit::RadioEffectKernel::Reset()
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RadioEffectUnit::RadioEffectKernel::Process
//
// pass-through unit
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void RadioEffectUnit::RadioEffectKernel::Process( const AudioUnitSampleType * InSourceP,
AudioUnitSampleType * InDestP,
UInt32 InFramesToProcess,
UInt32 InNumChannels,
bool & IOSilence )
{
// we should be doing something with the silence flag if it is true
// like not doing any work because:
// (1) we would only be processing silence and
// (2) we don't have any latency or tail times to worry about here
//
// So, we don't reset this flag, because it is true on input and we're not doing anything
// to it so we want it to be true on output.
// BUT: your code probably will need to take into account tail processing (or latency) that
// it has once its input becomes silent... then at some point in the future, your output
// will also be silent.
UInt32 NumSampleFrames = InFramesToProcess;
const AudioUnitSampleType *SourceP = InSourceP;
AudioUnitSampleType *DestP = InDestP;
const FLOAT ChebyshevPowerMultiplier = GetParameter(RadioParam_ChebyshevPowerMultiplier);
const FLOAT ChebyshevPower = GetParameter(RadioParam_ChebyshevPower);
const FLOAT ChebyshevCubedMultiplier = GetParameter(RadioParam_ChebyshevCubedMultiplier);
const FLOAT ChebyshevMultiplier = GetParameter(RadioParam_ChebyshevMultiplier);
while (NumSampleFrames-- > 0) {
AudioUnitSampleType InputSample = *SourceP;
SourceP += InNumChannels; // advance to next frame (e.g. if stereo, we're advancing 2 samples);
// we're only processing one of an arbitrary number of interleaved channels
// here's where you do your DSP work
// Early-out of processing if the sample is zero because a zero sample
// will still create some static even if no audio is playing.
if( InputSample != 0.0f ) {
// Waveshape it
const FLOAT SampleCubed = InputSample * InputSample * InputSample;
InputSample = ( ChebyshevPowerMultiplier * appPow( InputSample, ChebyshevPower ) ) - ( ChebyshevCubedMultiplier * SampleCubed ) + ( ChebyshevMultiplier * InputSample );
// Again with the bandpass
InputSample = GFinalBandPassFilter.Process( InputSample );
}
*DestP = InputSample;
DestP += InNumChannels;
}
}