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