1 /*---------------------------------------------------------------------------*
2   Project:  NintendoWare
3   File:     snd_FxReverb.cpp
4 
5   Copyright (C)2009-2010 Nintendo Co., Ltd./HAL Laboratory, Inc.  All rights reserved.
6 
7   These coded instructions, statements, and computer programs contain
8   proprietary information of Nintendo of America Inc. and/or Nintendo
9   Company Ltd., and are protected by Federal copyright law.  They may
10   not be disclosed to third parties or copied or duplicated in any form,
11   in whole or in part, without the prior written consent of Nintendo.
12 
13   $Revision: $
14  *---------------------------------------------------------------------------*/
15 
16 #include "precompiled.h"
17 
18 #include <nw/snd/snd_FxReverb.h>
19 #include <nw/snd/snd_HardwareManager.h>
20 #include <nw/ut/ut_Inlines.h>
21 
22 namespace
23 {
24 const f32 MSEC_PER_FRAME = NN_SND_USECS_PER_FRAME / 1000.f;
25 
RoundUpToMsecPerFrame(u32 msec)26 inline f32 RoundUpToMsecPerFrame( u32 msec )
27 {
28     return ( msec > MSEC_PER_FRAME ) ? msec : MSEC_PER_FRAME;
29 }
30 
ConvertMsecToSamples(f32 msec)31 inline u32 ConvertMsecToSamples( f32 msec )
32 {
33     return static_cast<u32>( msec / MSEC_PER_FRAME ) * NN_SND_SAMPLES_PER_FRAME;
34 }
35 } // anonymous namespace
36 
37 namespace nw {
38 namespace snd {
39 
40 FxReverb::FilterSize FxReverb::s_DefaultFilterSize;
41 
FxReverb()42 FxReverb::FxReverb()
43 : m_pBuffer( NULL ),
44   m_FilterSize( s_DefaultFilterSize ),
45   m_EarlyGain( 0 ),
46   m_FusedGain( 0 ),
47   m_LpfCoef1( 0 ),
48   m_LpfCoef2( 0 ),
49   m_ProcessChannelCount( 4 ),
50   m_IsActive( false )
51 {
52     m_EarlyLength = NN_SND_SAMPLES_PER_FRAME;
53     m_EarlyPos = 0;
54 
55     m_PreDelayLength = NN_SND_SAMPLES_PER_FRAME;
56     m_PreDelayPos = 0;
57 
58     for ( int i = 0; i < 2; i++ )
59     {
60         m_CombFilterLength[i] = NN_SND_SAMPLES_PER_FRAME;
61         m_CombFilterPos[i] = 0;
62         m_CombFilterCoef[i] = 0;
63     }
64 
65     m_AllPassFilterLength = NN_SND_SAMPLES_PER_FRAME;
66     m_AllPassFilterPos = 0;
67     m_AllPassFilterCoef = 0;
68 
69     for ( int ch = 0; ch < 4; ch++ )
70     {
71         m_WorkBuffer.m_EarlyReflection[ch] = NULL;
72         m_WorkBuffer.m_PreDelay[ch] = NULL;
73 
74         for ( int i = 0; i < 2; i++ )
75         {
76             m_WorkBuffer.m_CombFilter[ch][i] = NULL;
77         }
78         m_WorkBuffer.m_AllPassFilter[ch] = NULL;
79         m_WorkBuffer.m_Lpf[ch] = 0;
80         m_LastLpfOut[ch] = 0;
81     }
82 }
83 
~FxReverb()84 FxReverb::~FxReverb()
85 {
86     if ( m_IsActive )
87     {
88         Finalize();
89     }
90     if ( m_pBuffer != NULL )
91     {
92         ReleaseWorkBuffer();
93     }
94 }
95 
Initialize()96 bool FxReverb::Initialize()
97 {
98     NW_ASSERT( ! m_IsActive );
99 
100     m_EarlyReflectionTimeAtInitialize = m_Param.m_EarlyReflectionTime;
101     m_PreDelayTimeAtInitialize = m_Param.m_PreDelayTime;
102     m_FilterSizeAtInitialize = m_FilterSize;
103     m_IsEnableSurroundAtInitialize = m_Param.m_IsEnableSurround;
104 
105     AllocBuffer();
106     InitializeParam();
107     m_IsActive = true;
108 
109     return true;
110 }
111 
Finalize()112 void FxReverb::Finalize()
113 {
114     if ( ! m_IsActive )
115     {
116         return;
117     }
118     m_IsActive = false;
119     FreeBuffer();
120 }
121 
SetParam(const FxReverb::Param & param)122 bool FxReverb::SetParam( const FxReverb::Param& param )
123 {
124     // 範囲チェック
125     {
126         NW_MINMAX_ASSERT( param.m_Coloration, 0.0f, 1.0f );
127         if ( param.m_Coloration < 0.0f || param.m_Coloration > 1.f )
128         {
129             return false;
130         }
131 
132         NW_MINMAX_ASSERT( param.m_Damping, 0.0f, 1.0f );
133         if ( param.m_Damping < 0.0f || param.m_Damping > 1.f )
134         {
135             return false;
136         }
137 
138         NW_MINMAX_ASSERT( param.m_EarlyGain, 0.0f, 1.0f );
139         if ( param.m_EarlyGain < 0.0f || param.m_EarlyGain > 1.f )
140         {
141             return false;
142         }
143 
144         NW_MINMAX_ASSERT( param.m_FusedGain, 0.0f, 1.0f );
145         if ( param.m_FusedGain < 0.0f || param.m_FusedGain > 1.f )
146         {
147             return false;
148         }
149     }
150 
151     // バッファ長に影響するパラメータ
152     {
153         if ( m_IsActive == true )
154         {
155             if ( param.m_EarlyReflectionTime > m_EarlyReflectionTimeAtInitialize )
156             {
157                 return false;
158             }
159             if ( param.m_PreDelayTime > m_PreDelayTimeAtInitialize )
160             {
161                 return false;
162             }
163             if ( param.m_pFilterSize != NULL )
164             {
165                 if ( param.m_pFilterSize->m_Comb0 > m_FilterSizeAtInitialize.m_Comb0 )
166                 {
167                     return false;
168                 }
169                 if ( param.m_pFilterSize->m_Comb1 > m_FilterSizeAtInitialize.m_Comb1 )
170                 {
171                     return false;
172                 }
173                 if ( param.m_pFilterSize->m_AllPass > m_FilterSizeAtInitialize.m_AllPass )
174                 {
175                     return false;
176                 }
177             }
178             if ( m_IsEnableSurroundAtInitialize == false && param.m_IsEnableSurround == true )
179             {
180                 return false;
181             }
182         }
183 
184         if ( param.m_IsEnableSurround == false )
185         {
186             m_ProcessChannelCount = 2;
187         }
188         else
189         {
190             m_ProcessChannelCount = 4;
191         }
192 
193     }
194 
195     m_Param = param;   // 構造体コピー
196 
197     if ( m_Param.m_pFilterSize != NULL )
198     {
199         m_FilterSize = *m_Param.m_pFilterSize;  // 構造体コピー
200         m_Param.m_pFilterSize = &m_FilterSize;
201     }
202 
203     if ( m_IsActive == true )
204     {
205         InitializeParam();
206     }
207     return true;
208 }
209 
AssignWorkBuffer(uptr buffer,size_t size)210 bool FxReverb::AssignWorkBuffer( uptr buffer, size_t size )
211 {
212     NW_NULL_ASSERT( buffer );
213     if ( buffer == NULL )
214     {
215         return false;
216     }
217 
218     m_pBuffer = buffer;
219     m_BufferSize = size;
220     return true;
221 }
222 
ReleaseWorkBuffer()223 void FxReverb::ReleaseWorkBuffer()
224 {
225     NW_NULL_ASSERT( m_pBuffer );
226     m_pBuffer = NULL;
227 }
228 
GetRequiredMemSize()229 size_t FxReverb::GetRequiredMemSize()
230 {
231     const size_t bufSizeForEarlyReflection = sizeof(s32)
232         * ConvertMsecToSamples( RoundUpToMsecPerFrame( m_Param.m_EarlyReflectionTime ) );
233     const size_t bufSizeForPreDelay = sizeof(s32)
234         * ConvertMsecToSamples( RoundUpToMsecPerFrame( m_Param.m_PreDelayTime ) );
235     const size_t bufSizeForFilterComp0 = sizeof(s32) * m_FilterSize.m_Comb0;
236     const size_t bufSizeForFilterComp1 = sizeof(s32) * m_FilterSize.m_Comb1;
237     const size_t bufSizeForFilterAllPass = sizeof(s32) * m_FilterSize.m_AllPass;
238 
239     size_t result = (
240         bufSizeForEarlyReflection +
241         bufSizeForPreDelay +
242         bufSizeForFilterComp0 +
243         bufSizeForFilterComp1 +
244         bufSizeForFilterAllPass ) * m_ProcessChannelCount;
245 
246     result += 32;
247         // 特に制約は無いはずだが、32 バイト境界に置くため、+32 しておく。
248     return result;
249 }
250 
AllocBuffer()251 void FxReverb::AllocBuffer()
252 {
253     NW_NULL_ASSERT( m_pBuffer );
254 
255     const size_t bufSizeForEarlyReflection = sizeof(s32)
256         * ConvertMsecToSamples( RoundUpToMsecPerFrame( m_Param.m_EarlyReflectionTime ) );
257     const size_t bufSizeForPreDelay = sizeof(s32)
258         * ConvertMsecToSamples( RoundUpToMsecPerFrame( m_Param.m_PreDelayTime ) );
259     const size_t bufSizeForFilterComp0 = sizeof(s32) * m_FilterSize.m_Comb0;
260     const size_t bufSizeForFilterComp1 = sizeof(s32) * m_FilterSize.m_Comb1;
261     const size_t bufSizeForFilterAllPass = sizeof(s32) * m_FilterSize.m_AllPass;
262 
263     uptr ptr = ut::RoundUp( m_pBuffer, 32 );
264     // NN_LOG("[%s] ptr(%p) m_pBuffer(%p) +bufSize(%p)\n",
265     //         __FUNCTION__, ptr, m_pBuffer, m_pBuffer+m_BufferSize);
266     for ( int ch = 0; ch < m_ProcessChannelCount; ch++ )
267     {
268         m_WorkBuffer.m_EarlyReflection[ch] = reinterpret_cast<s32*>(ptr);
269         ptr += bufSizeForEarlyReflection;
270 
271         m_WorkBuffer.m_PreDelay[ch] = reinterpret_cast<s32*>(ptr);
272         ptr += bufSizeForPreDelay;
273 
274         m_WorkBuffer.m_CombFilter[ch][0] = reinterpret_cast<s32*>(ptr);
275         ptr += bufSizeForFilterComp0;
276 
277         m_WorkBuffer.m_CombFilter[ch][1] = reinterpret_cast<s32*>(ptr);
278         ptr += bufSizeForFilterComp1;
279 
280         m_WorkBuffer.m_AllPassFilter[ch] = reinterpret_cast<s32*>(ptr);
281         ptr += bufSizeForFilterAllPass;
282     }
283     // NN_LOG(" => ptr(%p)\n", ptr );
284     NW_ASSERT( ptr <= m_pBuffer + m_BufferSize );
285 }
286 
FreeBuffer()287 void FxReverb::FreeBuffer()
288 {
289     for ( int ch = 0; ch < m_ProcessChannelCount; ch++ )
290     {
291         m_WorkBuffer.m_EarlyReflection[ch] = NULL;
292         m_WorkBuffer.m_PreDelay[ch]        = NULL;
293         m_WorkBuffer.m_CombFilter[ch][0]   = NULL;
294         m_WorkBuffer.m_CombFilter[ch][1]   = NULL;
295         m_WorkBuffer.m_AllPassFilter[ch]   = NULL;
296     }
297 }
298 
InitializeParam()299 void FxReverb::InitializeParam()
300 {
301     // 初期反射音(One-Shotディレイ)
302     f32 early_time = RoundUpToMsecPerFrame( m_Param.m_EarlyReflectionTime );
303     m_EarlyLength = ConvertMsecToSamples( early_time );
304     m_EarlyPos = 0;
305 
306     // 後置残響音
307 
308     // プリディレイ
309     f32 pre_delay_time = RoundUpToMsecPerFrame( m_Param.m_PreDelayTime );
310     m_PreDelayLength = ConvertMsecToSamples( pre_delay_time );
311     m_PreDelayPos = 0;
312 
313     // くし型フィルタ
314     f32 fused_time_sec = static_cast<f32>( m_Param.m_FusedTime ) / 1000.f;
315     NW_ASSERT( fused_time_sec != 0.f );
316 
317     m_CombFilterLength[0] = static_cast<s32>( m_FilterSize.m_Comb0 );
318     m_CombFilterLength[1] = static_cast<s32>( m_FilterSize.m_Comb1 );
319 
320     for ( s32 i = 0; i < 2; i++ )
321     {
322         m_CombFilterPos[i] = 0;
323 
324         // 減衰時間から係数(フィードバックゲインへ変換)
325         f32 comb_coef = std::powf( 10.f,
326                 (-3.f * static_cast<f32>(m_CombFilterLength[i]) /
327                  (fused_time_sec * internal::driver::HardwareManager::FX_SAMPLE_RATE) ) );
328         m_CombFilterCoef[i] = static_cast<s32>( static_cast<f32>(0x80L) * comb_coef );
329     }
330 
331     // 全域通過フィルタ
332     m_AllPassFilterLength = static_cast<s32>( m_FilterSize.m_AllPass );
333     m_AllPassFilterPos = 0;
334     f32 all_pass_coef = m_Param.m_Coloration;
335     m_AllPassFilterCoef = static_cast<s32>( static_cast<f32>(0x80L) * all_pass_coef );
336 
337     // 各係数
338     m_EarlyGain = static_cast<s32>( static_cast<f32>(0x80L) * m_Param.m_EarlyGain );
339     m_FusedGain = static_cast<s32>( static_cast<f32>(0x80L) * m_Param.m_FusedGain );
340 
341     f32 lpf_coef = m_Param.m_Damping;
342     if ( lpf_coef > 0.95f ) lpf_coef = 0.95f;
343     m_LpfCoef1 = static_cast<s32>( static_cast<s32>(0x80L) * ( 1.f - lpf_coef ) );
344     m_LpfCoef2 = static_cast<s32>( static_cast<s32>(0x80L) * lpf_coef );
345 
346 
347     // バッファを0クリア
348     std::memset( reinterpret_cast<void*>(m_pBuffer), 0, m_BufferSize );
349 }
350 
UpdateBuffer(int numChannels,nn::snd::AuxBusData * data,s32 sampleLength,SampleFormat format,f32 sampleRate,OutputMode mode)351 void FxReverb::UpdateBuffer(
352         int numChannels,
353         nn::snd::AuxBusData* data,
354         s32 sampleLength,
355         SampleFormat format,
356         f32 sampleRate,
357         OutputMode mode )
358 {
359     NW_UNUSED_VARIABLE( sampleRate );
360     NW_UNUSED_VARIABLE( format );
361     NW_UNUSED_VARIABLE( mode );
362     NW_UNUSED_VARIABLE( numChannels );
363 
364     if ( m_IsActive == false )
365     {
366         return;
367     }
368     NW_NULL_ASSERT( data );
369 
370     s32* input[nn::snd::CHANNEL_INDEX_NUM];
371     input[nn::snd::CHANNEL_INDEX_FRONT_LEFT]  = data->frontLeft;
372     input[nn::snd::CHANNEL_INDEX_FRONT_RIGHT] = data->frontRight;
373     input[nn::snd::CHANNEL_INDEX_REAR_LEFT]   = data->rearLeft;
374     input[nn::snd::CHANNEL_INDEX_REAR_RIGHT]  = data->rearRight;
375 
376 
377     //-----------------------------------------------------------------
378     // NOTE : 高速化のポイント
379     //
380     //   ・固定小数計算に変更
381     //     (浮動小数計算はレイテンシがあるので)
382     //   ・メモリに置かれているデータを数回参照する箇所は、一度ローカル変数に
383     //     入れて使う
384     //     (バスアクセスが遅いので、レジスタに移してから使った方が速い。
385     //       配列とメンバがこれに相当する)
386     //   ・その他、処理の調整(式の変形など)
387     //-----------------------------------------------------------------
388     u32 early_pos           = 0;
389     u32 pre_delay_pos       = 0;
390     u32 comb_filter_pos0    = 0;
391     u32 comb_filter_pos1    = 0;
392     u32 allpass_filter_pos  = 0;
393 
394     for ( int ch = 0; ch < m_ProcessChannelCount; ch++ )
395     {
396         early_pos           = m_EarlyPos;
397         pre_delay_pos       = m_PreDelayPos;
398         comb_filter_pos0    = m_CombFilterPos[0];
399         comb_filter_pos1    = m_CombFilterPos[1];
400         allpass_filter_pos  = m_AllPassFilterPos;
401 
402         s32* early_reflection = m_WorkBuffer.m_EarlyReflection[ch];
403         s32* pre_delay      = m_WorkBuffer.m_PreDelay[ch];
404 
405         for ( s32 samp = 0; samp < sampleLength; samp++ )
406         {
407             s32 indata = input[ch][samp];
408 
409             // 初期反射音
410             s32 early_out = early_reflection[early_pos] * m_EarlyGain;
411             // early_out >>= 7; // NOTE : 後で fused_out と纏めてシフトする
412             early_reflection[early_pos] = indata;
413 
414 
415             //
416             // 以下、後置残響音
417             //
418 
419             // プリディレイ
420             s32 pre_delay_out = pre_delay[pre_delay_pos];
421             pre_delay[pre_delay_pos] = indata;
422 
423             s32 filter_out = 0;
424 
425 
426             // くし型フィルタ (1段目)
427             s32* comb_line = m_WorkBuffer.m_CombFilter[ch][0];
428             s32 out_tmp = comb_line[comb_filter_pos0];
429 
430             s32 comb_fb_0 = out_tmp;
431             if( comb_fb_0 < 0 )
432             {
433                 s32 tmp = -comb_fb_0;
434                 tmp = ( tmp * m_CombFilterCoef[0] ) >> 7;
435                 comb_fb_0 = -tmp;
436             }
437             else
438             {
439                 comb_fb_0 = ( comb_fb_0 * m_CombFilterCoef[0] ) >> 7;
440             }
441 
442             comb_line[comb_filter_pos0] = pre_delay_out + comb_fb_0;
443             filter_out += out_tmp;
444 
445 
446             // くし型フィルタ (2段目)
447             comb_line = m_WorkBuffer.m_CombFilter[ch][1];
448             out_tmp = comb_line[comb_filter_pos1];
449 
450             s32 comb_fb_1 = out_tmp;
451             if( comb_fb_1 < 0 )
452             {
453                 s32 tmp = -comb_fb_1;
454                 tmp = ( tmp * m_CombFilterCoef[1] ) >> 7;
455                 comb_fb_1 = -tmp;
456             }
457             else
458             {
459                 comb_fb_1 = ( comb_fb_1 * m_CombFilterCoef[1] ) >> 7;
460             }
461 
462             comb_line[comb_filter_pos1] = pre_delay_out + comb_fb_1;
463             filter_out -= out_tmp;  // 逆相にして、出力のリプルを抑える
464 
465 
466             // 全域通過フィルタ (1段)
467             s32* allpass_line = m_WorkBuffer.m_AllPassFilter[ch];
468             out_tmp = allpass_line[allpass_filter_pos];
469             s32 allpass_coef = m_AllPassFilterCoef;
470 
471             s32 allpass_in = out_tmp;
472             if( allpass_in < 0 ){
473                 s32 tmp = -allpass_in;
474                 tmp = ( tmp * allpass_coef ) >> 7;
475                 allpass_in = -tmp;
476             }
477             else
478             {
479                 allpass_in = ( allpass_in * allpass_coef ) >> 7;
480             }
481             allpass_in += filter_out;
482 
483             allpass_line[allpass_filter_pos] = allpass_in;
484 
485 
486             s32 fo_2 = allpass_in;
487             if( fo_2 < 0 )
488             {
489                 s32 tmp = -fo_2;
490                 tmp = ( tmp * allpass_coef ) >> 7;
491                 fo_2 = -tmp;
492             }
493             else
494             {
495                 fo_2 = ( fo_2 * allpass_coef ) >> 7;
496             }
497             filter_out = out_tmp - fo_2;
498 
499 
500             // 単極 LPF
501 
502             // NOTE : 以下の変形で乗算を1つ減らせます
503             //         また、m_LpfCoef1 が不要になります
504             //         (m_LpfCoef1 は残してありますので、以下の方が良ければ
505             //           消して下さい)
506             //
507             // OUT = ( 1 - Coef ) * In + Coef * Histry
508             //     = In - Coef * In + Coef * Histry
509             //     = In - Coef * ( In + History )
510 
511             s32 tmp = m_LpfCoef2 * ( filter_out + m_LastLpfOut[ch] );
512             tmp >>= 7;
513             s32 fused_out = filter_out - tmp;
514             m_LastLpfOut[ch] = fused_out;
515 
516 
517             // 出力
518             fused_out *= m_FusedGain;
519             fused_out += early_out;
520             fused_out >>= 7;
521             input[ch][samp] = fused_out;
522 
523             ++early_pos;
524             ++pre_delay_pos;
525             ++comb_filter_pos0;
526             ++comb_filter_pos1;
527             ++allpass_filter_pos;
528         }
529     }
530 
531     // バッファ位置をメモリに書き戻す
532     if ( early_pos >= m_EarlyLength )
533     {
534         m_EarlyPos = 0;
535     }
536     else
537     {
538         m_EarlyPos = early_pos;
539     }
540 
541     if ( pre_delay_pos >= m_PreDelayLength )
542     {
543         m_PreDelayPos = 0;
544     }
545     else
546     {
547         m_PreDelayPos = pre_delay_pos;
548     }
549 
550     if ( comb_filter_pos0 >= m_CombFilterLength[0] )
551     {
552         m_CombFilterPos[0] = 0;
553     }
554     else
555     {
556         m_CombFilterPos[0] = comb_filter_pos0;
557     }
558 
559     if ( comb_filter_pos1 >= m_CombFilterLength[1] )
560     {
561         m_CombFilterPos[1] = 0;
562     }
563     else
564     {
565         m_CombFilterPos[1] = comb_filter_pos1;
566     }
567 
568     if ( allpass_filter_pos >= m_AllPassFilterLength )
569     {
570         m_AllPassFilterPos = 0;
571     }
572     else
573     {
574         m_AllPassFilterPos = allpass_filter_pos;
575     }
576 }
577 
578 } // namespace nw::snd
579 } // namespace nw
580 
581