/*---------------------------------------------------------------------------* Project: NintendoWare File: snd_FxDelay.cpp Copyright (C)2009-2010 Nintendo Co., Ltd./HAL Laboratory, Inc. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Revision: $ *---------------------------------------------------------------------------*/ #include "precompiled.h" #include namespace { const u32 SAMPLE_PER_FRAME = NN_SND_SAMPLES_PER_FRAME; // samples } // namespace namespace nw { namespace snd { #ifdef NW_SND_FX_DELAY_CALC_LOAD s64 FxDelay::s_FxLoad = 0; s32 FxDelay::s_SampleLength = 0; f32 FxDelay::s_SampleRate = 32000.f; #endif //------------------------------------------------------------------------------ FxDelay::FxDelay() : m_pBuffer( NULL ), m_BufferSize( 0 ), m_CurFrame( 0 ), m_FeedbackGain( 0x0 ), m_LpfCoef1( 0x10000 ), m_LpfCoef2( 0x0 ), m_ProcessChannelCount( 4 ), m_IsActive( false ) { FreeBuffer(); for ( int ch = 0; ch < m_ProcessChannelCount; ch++ ) { m_WorkBuffer.m_Lpf[ch] = 0; } } //------------------------------------------------------------------------------ FxDelay::~FxDelay() { if ( m_IsActive ) { Finalize(); } if ( m_pBuffer != NULL ) { ReleaseWorkBuffer(); } } //------------------------------------------------------------------------------ bool FxDelay::SetParam( const FxDelay::Param& param ) { // 範囲チェック { NW_MINMAX_ASSERT( param.m_Damping, 0.0f, 1.0f ); if ( param.m_Damping < 0.0f || param.m_Damping > 1.0f ) { return false; } NW_MINMAX_ASSERT( param.m_FeedbackGain, 0.0f, 1.0f ); if ( param.m_FeedbackGain < 0.0f || param.m_FeedbackGain > 1.0f ) { return false; } } // バッファ長に影響するパラメータ { if ( m_IsActive == true ) { if ( param.m_DelayTime > m_DelayTimeAtInitialize ) { return false; } if ( m_IsEnableSurroundAtInitialize == false && param.m_IsEnableSurround == true ) { return false; } } // ディレイ長 m_DelayFrames = ( param.m_DelayTime * 1000 ) / NN_SND_USECS_PER_FRAME; if ( m_DelayFrames == 0 ) { m_DelayFrames = 1; } // 計算チャンネル数 if ( param.m_IsEnableSurround == false ) { m_ProcessChannelCount = 2; } else { m_ProcessChannelCount = 4; } } // バッファ長に影響しないパラメータ { // フィードバックゲイン m_FeedbackGain = static_cast( static_cast(0x80L) * param.m_FeedbackGain ); // ダンピング f32 lpf_coef = param.m_Damping; if ( lpf_coef > 0.95f ) { lpf_coef = 0.95f; } f32 lpf_coef_1 = 1.f - lpf_coef; f32 lpf_coef_2 = lpf_coef; m_LpfCoef1 = static_cast( static_cast(0x80L) * lpf_coef_1 ); m_LpfCoef2 = static_cast( static_cast(0x80L) * lpf_coef_2 ); } m_Param = param; // 構造体コピー return true; } //------------------------------------------------------------------------------ size_t FxDelay::GetRequiredMemSize() { NW_ASSERT( m_DelayFrames != 0 ); size_t result = ( sizeof(s32) * SAMPLE_PER_FRAME * m_DelayFrames ) * m_ProcessChannelCount; result += 32; // 念のためバッファを 32 バイト境界におくため +32 する return result; } //------------------------------------------------------------------------------ bool FxDelay::AssignWorkBuffer( uptr buffer, size_t size ) { NW_NULL_ASSERT( buffer ); if ( buffer == NULL ) { return false; } m_pBuffer = buffer; m_BufferSize = size; return true; } //------------------------------------------------------------------------------ void FxDelay::ReleaseWorkBuffer() { NW_NULL_ASSERT( m_pBuffer ); m_pBuffer = NULL; } //------------------------------------------------------------------------------ bool FxDelay::Initialize() { NW_ASSERT( ! m_IsActive ); if ( m_IsActive ) { return false; } m_DelayTimeAtInitialize = m_Param.m_DelayTime; m_IsEnableSurroundAtInitialize = m_Param.m_IsEnableSurround; AllocBuffer(); InitializeParam(); m_IsActive = true; return true; } //------------------------------------------------------------------------------ void FxDelay::Finalize() { if ( ! m_IsActive ) { return; } m_IsActive = false; std::memset( reinterpret_cast( &m_Param ), 0, sizeof(Param) ); FreeBuffer(); } //------------------------------------------------------------------------------ void FxDelay::UpdateBuffer( int numChannels, nn::snd::AuxBusData* data, s32 sampleLength, nw::snd::SampleFormat format, f32 sampleRate, nw::snd::OutputMode mode ) { NW_UNUSED_VARIABLE( format ); NW_UNUSED_VARIABLE( mode ); NW_UNUSED_VARIABLE( numChannels ); if( ! m_IsActive ) { return; } #ifdef NW_SND_FX_DELAY_CALC_LOAD s_SampleLength = sampleLength; s_SampleRate = sampleRate; nn::os::Tick st,end; st = nn::os::Tick::GetSystemCurrent(); #else NW_UNUSED_VARIABLE( sampleRate ); #endif NW_NULL_ASSERT( data ); NW_ASSERT( m_DelayFrames != 0 ); s32* input[ nn::snd::CHANNEL_INDEX_NUM ]; input[ nn::snd::CHANNEL_INDEX_FRONT_LEFT ] = data->frontLeft; input[ nn::snd::CHANNEL_INDEX_FRONT_RIGHT ] = data->frontRight; input[ nn::snd::CHANNEL_INDEX_REAR_LEFT ] = data->rearLeft; input[ nn::snd::CHANNEL_INDEX_REAR_RIGHT ] = data->rearRight; const u32 start_pos = SAMPLE_PER_FRAME * m_CurFrame; for ( u32 ch = 0; ch < m_ProcessChannelCount; ch++ ) { u32 cur_pos = start_pos; s32* in_ptr = &input[ch][0]; s32* delay_ptr = &m_WorkBuffer.m_Delay[ch][0]; s32* lpf_ptr = &m_WorkBuffer.m_Lpf[ch]; for ( u32 samp = 0; samp < sampleLength; samp++ ) { //--------------------------------------------------------- // Delay //--------------------------------------------------------- s32 delay_out = delay_ptr[cur_pos]; s32 feedback = delay_out; if ( feedback < 0 ) { s32 tmp = -feedback; tmp = ( tmp * m_FeedbackGain ) >> 7; feedback = -tmp; // NOTE: 算術右シフト時の負値の扱いが面倒だったので、こんな感じにした。 // 誤差が出ないように楽に実装できる方法があれば、そちらで…。 } else { feedback = ( feedback * m_FeedbackGain ) >> 7; } feedback = in_ptr[samp] - feedback; // フィードバックの位相を反転 //--------------------------------------------------------- // LPF //--------------------------------------------------------- s32 lpf_out = m_LpfCoef1 * feedback + m_LpfCoef2 * *lpf_ptr; lpf_out >>= 7; // NOTE: 算術右シフトの場合、負値では 0 になるべきところが // -1 で残るケースが出てくるが、誤差範囲として許容 *lpf_ptr = lpf_out; delay_ptr[cur_pos] = lpf_out; //--------------------------------------------------------- // 出力 //--------------------------------------------------------- in_ptr[samp] = delay_out; //--------------------------------------------------------- // end //--------------------------------------------------------- ++cur_pos; } } if ( ++m_CurFrame >= m_DelayFrames ) { m_CurFrame = 0; } #ifdef NW_SND_FX_DELAY_CALC_LOAD end = nn::os::Tick::GetSystemCurrent(); s_FxLoad = (s64)end-(s64)st; #endif } //------------------------------------------------------------------------------ void FxDelay::AllocBuffer() { NW_NULL_ASSERT( m_pBuffer ); NW_ASSERT( m_DelayFrames != 0 ); const size_t ch_buffer_size = sizeof(s32) * SAMPLE_PER_FRAME * m_DelayFrames; uptr ptr = nw::ut::RoundUp( m_pBuffer, 32 ); for( int ch = 0; ch < m_ProcessChannelCount; ch++ ) { m_WorkBuffer.m_Delay[ch] = reinterpret_cast(ptr); ptr += ch_buffer_size; } NW_ASSERT( ptr <= m_pBuffer + m_BufferSize ); } //------------------------------------------------------------------------------ void FxDelay::FreeBuffer() { for ( int ch = 0; ch < m_ProcessChannelCount; ch++ ) { m_WorkBuffer.m_Delay[ch] = NULL; } } //------------------------------------------------------------------------------ void FxDelay::InitializeParam() { m_CurFrame = 0; std::memset( reinterpret_cast( m_pBuffer ), 0, m_BufferSize ); for ( int ch = 0; ch < m_ProcessChannelCount; ch++ ) { m_WorkBuffer.m_Lpf[ch] = 0; } } //------------------------------------------------------------------------------ #ifdef NW_SND_FX_DELAY_CALC_LOAD f32 FxDelay::GetLoad() { if ( s_SampleLength == 0 ) { return 0.0f; } NW_ASSERT( s_SampleRate != 0 ); int maxLoad = ( s_SampleLength * nn::os::Tick::TICKS_PER_SECOND) / s_SampleRate; return ( (f32)s_FxLoad / (f32)maxLoad ) * 100.0f; } #endif } // namespace nw::snd } // namespace nw