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