//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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 ); }
/** * Constructor. * * @param InitialSampleRate The sample rate to process the audio. */ FXAudio2RadioEffect( float InitialSampleRate ) : CXAPOParametersBase( &Registration, ( uint8* )Parameters, sizeof( FAudioRadioEffect ), false ) , SampleRate( InitialSampleRate ) { // Initialize the global audio processing helper classes GFinalBandPassFilter.Initialize( 2000.0f, 400.0f, SampleRate ); // Setup default values for the parameters to initialize the rest of the global // audio processing helper classes. See FXAudio2RadioEffect::OnSetParameters(). FAudioRadioEffect DefaultParameters; SetParameters( &DefaultParameters, sizeof( DefaultParameters ) ); }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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; } }
/** * Adds a radio distortion to the input buffer if the radio effect is enabled. * * @param InputProcessParameterCount Number of elements in pInputProcessParameters. * @param pInputProcessParameters Input buffer containing audio samples. * @param OutputProcessParameterCount Number of elements in pOutputProcessParameters. * @param pOutputProcessParameters Output buffer, which is not touched by this xAPO. * @param IsEnabled true if this effect is enabled; false, otherwise. */ STDMETHOD_( void, Process )( UINT32 InputProcessParameterCount, const XAPO_PROCESS_BUFFER_PARAMETERS *pInputProcessParameters, UINT32 OutputProcessParameterCount, XAPO_PROCESS_BUFFER_PARAMETERS *pOutputProcessParameters, BOOL IsEnabled ) { // Verify several condition based on the registration // properties we used to create the class. check( IsLocked() ); check( InputProcessParameterCount == 1 ); check( OutputProcessParameterCount == 1 ); check( pInputProcessParameters[0].pBuffer == pOutputProcessParameters[0].pBuffer ); // Check the global volume multiplier because this effect // will continue to play if the editor loses focus. if (IsEnabled && FApp::GetVolumeMultiplier() != 0.0f) { FAudioRadioEffect* RadioParameters = ( FAudioRadioEffect* )BeginProcess(); check( RadioParameters ); // The total sample size must account for multiple channels. const uint32 SampleSize = pInputProcessParameters[0].ValidFrameCount * WaveFormat.nChannels; const float ChebyshevPowerMultiplier = Radio_ChebyshevPowerMultiplier->GetValueOnAnyThread(); const float ChebyshevPower = Radio_ChebyshevPower->GetValueOnAnyThread(); const float ChebyshevCubedMultiplier = Radio_ChebyshevCubedMultiplier->GetValueOnAnyThread(); const float ChebyshevMultiplier = Radio_ChebyshevMultiplier->GetValueOnAnyThread(); // If we have a silent buffer, then allocate the samples. Then, set the sample values // to zero to avoid getting NANs. Otherwise, the user may hear an annoying popping noise. if( pInputProcessParameters[0].BufferFlags == XAPO_BUFFER_VALID ) { float* SampleData = ( float* )pInputProcessParameters[0].pBuffer; // Process each sample one at a time for( uint32 SampleIndex = 0; SampleIndex < SampleSize; ++SampleIndex ) { float Sample = SampleData[SampleIndex]; // 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( Sample == 0.0f ) { continue; } // Waveshape it const float SampleCubed = Sample * Sample * Sample; Sample = ( ChebyshevPowerMultiplier * FMath::Pow( Sample, ChebyshevPower ) ) - ( ChebyshevCubedMultiplier * SampleCubed ) + ( ChebyshevMultiplier * Sample ); // Again with the bandpass Sample = GFinalBandPassFilter.Process( Sample ); // Store the value after done processing SampleData[SampleIndex] = Sample; } EndProcess(); } } }