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 return true;
203 }
204
AssignWorkBuffer(uptr buffer,size_t size)205 bool FxReverb::AssignWorkBuffer( uptr buffer, size_t size )
206 {
207 NW_NULL_ASSERT( buffer );
208 if ( buffer == NULL )
209 {
210 return false;
211 }
212
213 m_pBuffer = buffer;
214 m_BufferSize = size;
215 return true;
216 }
217
ReleaseWorkBuffer()218 void FxReverb::ReleaseWorkBuffer()
219 {
220 NW_NULL_ASSERT( m_pBuffer );
221 m_pBuffer = NULL;
222 }
223
GetRequiredMemSize()224 size_t FxReverb::GetRequiredMemSize()
225 {
226 const size_t bufSizeForEarlyReflection = sizeof(s32)
227 * ConvertMsecToSamples( RoundUpToMsecPerFrame( m_Param.m_EarlyReflectionTime ) );
228 const size_t bufSizeForPreDelay = sizeof(s32)
229 * ConvertMsecToSamples( RoundUpToMsecPerFrame( m_Param.m_PreDelayTime ) );
230 const size_t bufSizeForFilterComp0 = sizeof(s32) * m_FilterSize.m_Comb0;
231 const size_t bufSizeForFilterComp1 = sizeof(s32) * m_FilterSize.m_Comb1;
232 const size_t bufSizeForFilterAllPass = sizeof(s32) * m_FilterSize.m_AllPass;
233
234 size_t result = (
235 bufSizeForEarlyReflection +
236 bufSizeForPreDelay +
237 bufSizeForFilterComp0 +
238 bufSizeForFilterComp1 +
239 bufSizeForFilterAllPass ) * m_ProcessChannelCount;
240
241 result += 32;
242 // 特に制約は無いはずだが、32 バイト境界に置くため、+32 しておく。
243 return result;
244 }
245
AllocBuffer()246 void FxReverb::AllocBuffer()
247 {
248 NW_NULL_ASSERT( m_pBuffer );
249
250 const size_t bufSizeForEarlyReflection = sizeof(s32)
251 * ConvertMsecToSamples( RoundUpToMsecPerFrame( m_Param.m_EarlyReflectionTime ) );
252 const size_t bufSizeForPreDelay = sizeof(s32)
253 * ConvertMsecToSamples( RoundUpToMsecPerFrame( m_Param.m_PreDelayTime ) );
254 const size_t bufSizeForFilterComp0 = sizeof(s32) * m_FilterSize.m_Comb0;
255 const size_t bufSizeForFilterComp1 = sizeof(s32) * m_FilterSize.m_Comb1;
256 const size_t bufSizeForFilterAllPass = sizeof(s32) * m_FilterSize.m_AllPass;
257
258 uptr ptr = ut::RoundUp( m_pBuffer, 32 );
259 // NN_LOG("[%s] ptr(%p) m_pBuffer(%p) +bufSize(%p)\n",
260 // __FUNCTION__, ptr, m_pBuffer, m_pBuffer+m_BufferSize);
261 for ( int ch = 0; ch < m_ProcessChannelCount; ch++ )
262 {
263 m_WorkBuffer.m_EarlyReflection[ch] = reinterpret_cast<s32*>(ptr);
264 ptr += bufSizeForEarlyReflection;
265
266 m_WorkBuffer.m_PreDelay[ch] = reinterpret_cast<s32*>(ptr);
267 ptr += bufSizeForPreDelay;
268
269 m_WorkBuffer.m_CombFilter[ch][0] = reinterpret_cast<s32*>(ptr);
270 ptr += bufSizeForFilterComp0;
271
272 m_WorkBuffer.m_CombFilter[ch][1] = reinterpret_cast<s32*>(ptr);
273 ptr += bufSizeForFilterComp1;
274
275 m_WorkBuffer.m_AllPassFilter[ch] = reinterpret_cast<s32*>(ptr);
276 ptr += bufSizeForFilterAllPass;
277 }
278 // NN_LOG(" => ptr(%p)\n", ptr );
279 NW_ASSERT( ptr <= m_pBuffer + m_BufferSize );
280 }
281
FreeBuffer()282 void FxReverb::FreeBuffer()
283 {
284 for ( int ch = 0; ch < m_ProcessChannelCount; ch++ )
285 {
286 m_WorkBuffer.m_EarlyReflection[ch] = NULL;
287 m_WorkBuffer.m_PreDelay[ch] = NULL;
288 m_WorkBuffer.m_CombFilter[ch][0] = NULL;
289 m_WorkBuffer.m_CombFilter[ch][1] = NULL;
290 m_WorkBuffer.m_AllPassFilter[ch] = NULL;
291 }
292 }
293
InitializeParam()294 void FxReverb::InitializeParam()
295 {
296 // 初期反射音(One-Shotディレイ)
297 f32 early_time = RoundUpToMsecPerFrame( m_Param.m_EarlyReflectionTime );
298 m_EarlyLength = ConvertMsecToSamples( early_time );
299 m_EarlyPos = 0;
300
301 // 後置残響音
302
303 // プリディレイ
304 f32 pre_delay_time = RoundUpToMsecPerFrame( m_Param.m_PreDelayTime );
305 m_PreDelayLength = ConvertMsecToSamples( pre_delay_time );
306 m_PreDelayPos = 0;
307
308 // くし型フィルタ
309 f32 fused_time_sec = static_cast<f32>( m_Param.m_FusedTime ) / 1000.f;
310 NW_ASSERT( fused_time_sec != 0.f );
311
312 m_CombFilterLength[0] = static_cast<s32>( m_FilterSize.m_Comb0 );
313 m_CombFilterLength[1] = static_cast<s32>( m_FilterSize.m_Comb1 );
314
315 for ( s32 i = 0; i < 2; i++ )
316 {
317 m_CombFilterPos[i] = 0;
318
319 // 減衰時間から係数(フィードバックゲインへ変換)
320 f32 comb_coef = std::powf( 10.f,
321 (-3.f * static_cast<f32>(m_CombFilterLength[i]) /
322 (fused_time_sec * internal::driver::HardwareManager::FX_SAMPLE_RATE) ) );
323 m_CombFilterCoef[i] = static_cast<s32>( static_cast<f32>(0x80L) * comb_coef );
324 }
325
326 // 全域通過フィルタ
327 m_AllPassFilterLength = static_cast<s32>( m_FilterSize.m_AllPass );
328 m_AllPassFilterPos = 0;
329 f32 all_pass_coef = m_Param.m_Coloration;
330 m_AllPassFilterCoef = static_cast<s32>( static_cast<f32>(0x80L) * all_pass_coef );
331
332 // 各係数
333 m_EarlyGain = static_cast<s32>( static_cast<f32>(0x80L) * m_Param.m_EarlyGain );
334 m_FusedGain = static_cast<s32>( static_cast<f32>(0x80L) * m_Param.m_FusedGain );
335
336 f32 lpf_coef = m_Param.m_Damping;
337 if ( lpf_coef > 0.95f ) lpf_coef = 0.95f;
338 m_LpfCoef1 = static_cast<s32>( static_cast<s32>(0x80L) * ( 1.f - lpf_coef ) );
339 m_LpfCoef2 = static_cast<s32>( static_cast<s32>(0x80L) * lpf_coef );
340
341
342 // バッファを0クリア
343 std::memset( reinterpret_cast<void*>(m_pBuffer), 0, m_BufferSize );
344 }
345
UpdateBuffer(int numChannels,nn::snd::AuxBusData * data,s32 sampleLength,SampleFormat format,f32 sampleRate,OutputMode mode)346 void FxReverb::UpdateBuffer(
347 int numChannels,
348 nn::snd::AuxBusData* data,
349 s32 sampleLength,
350 SampleFormat format,
351 f32 sampleRate,
352 OutputMode mode )
353 {
354 NW_UNUSED_VARIABLE( sampleRate );
355 NW_UNUSED_VARIABLE( format );
356 NW_UNUSED_VARIABLE( mode );
357 NW_UNUSED_VARIABLE( numChannels );
358
359 if ( m_IsActive == false )
360 {
361 return;
362 }
363 NW_NULL_ASSERT( data );
364
365 s32* input[nn::snd::CHANNEL_INDEX_NUM];
366 input[nn::snd::CHANNEL_INDEX_FRONT_LEFT] = data->frontLeft;
367 input[nn::snd::CHANNEL_INDEX_FRONT_RIGHT] = data->frontRight;
368 input[nn::snd::CHANNEL_INDEX_REAR_LEFT] = data->rearLeft;
369 input[nn::snd::CHANNEL_INDEX_REAR_RIGHT] = data->rearRight;
370
371
372 //-----------------------------------------------------------------
373 // NOTE : 高速化のポイント
374 //
375 // ・固定小数計算に変更
376 // (浮動小数計算はレイテンシがあるので)
377 // ・メモリに置かれているデータを数回参照する箇所は、一度ローカル変数に
378 // 入れて使う
379 // (バスアクセスが遅いので、レジスタに移してから使った方が速い。
380 // 配列とメンバがこれに相当する)
381 // ・その他、処理の調整(式の変形など)
382 //-----------------------------------------------------------------
383 u32 early_pos = 0;
384 u32 pre_delay_pos = 0;
385 u32 comb_filter_pos0 = 0;
386 u32 comb_filter_pos1 = 0;
387 u32 allpass_filter_pos = 0;
388
389 for ( int ch = 0; ch < m_ProcessChannelCount; ch++ )
390 {
391 early_pos = m_EarlyPos;
392 pre_delay_pos = m_PreDelayPos;
393 comb_filter_pos0 = m_CombFilterPos[0];
394 comb_filter_pos1 = m_CombFilterPos[1];
395 allpass_filter_pos = m_AllPassFilterPos;
396
397 s32* early_reflection = m_WorkBuffer.m_EarlyReflection[ch];
398 s32* pre_delay = m_WorkBuffer.m_PreDelay[ch];
399
400 for ( s32 samp = 0; samp < sampleLength; samp++ )
401 {
402 s32 indata = input[ch][samp];
403
404 // 初期反射音
405 s32 early_out = early_reflection[early_pos] * m_EarlyGain;
406 // early_out >>= 7; // NOTE : 後で fused_out と纏めてシフトする
407 early_reflection[early_pos] = indata;
408
409
410 //
411 // 以下、後置残響音
412 //
413
414 // プリディレイ
415 s32 pre_delay_out = pre_delay[pre_delay_pos];
416 pre_delay[pre_delay_pos] = indata;
417
418 s32 filter_out = 0;
419
420
421 // くし型フィルタ (1段目)
422 s32* comb_line = m_WorkBuffer.m_CombFilter[ch][0];
423 s32 out_tmp = comb_line[comb_filter_pos0];
424
425 s32 comb_fb_0 = out_tmp;
426 if( comb_fb_0 < 0 )
427 {
428 s32 tmp = -comb_fb_0;
429 tmp = ( tmp * m_CombFilterCoef[0] ) >> 7;
430 comb_fb_0 = -tmp;
431 }
432 else
433 {
434 comb_fb_0 = ( comb_fb_0 * m_CombFilterCoef[0] ) >> 7;
435 }
436
437 comb_line[comb_filter_pos0] = pre_delay_out + comb_fb_0;
438 filter_out += out_tmp;
439
440
441 // くし型フィルタ (2段目)
442 comb_line = m_WorkBuffer.m_CombFilter[ch][1];
443 out_tmp = comb_line[comb_filter_pos1];
444
445 s32 comb_fb_1 = out_tmp;
446 if( comb_fb_1 < 0 )
447 {
448 s32 tmp = -comb_fb_1;
449 tmp = ( tmp * m_CombFilterCoef[1] ) >> 7;
450 comb_fb_1 = -tmp;
451 }
452 else
453 {
454 comb_fb_1 = ( comb_fb_1 * m_CombFilterCoef[1] ) >> 7;
455 }
456
457 comb_line[comb_filter_pos1] = pre_delay_out + comb_fb_1;
458 filter_out -= out_tmp; // 逆相にして、出力のリプルを抑える
459
460
461 // 全域通過フィルタ (1段)
462 s32* allpass_line = m_WorkBuffer.m_AllPassFilter[ch];
463 out_tmp = allpass_line[allpass_filter_pos];
464 s32 allpass_coef = m_AllPassFilterCoef;
465
466 s32 allpass_in = out_tmp;
467 if( allpass_in < 0 ){
468 s32 tmp = -allpass_in;
469 tmp = ( tmp * allpass_coef ) >> 7;
470 allpass_in = -tmp;
471 }
472 else
473 {
474 allpass_in = ( allpass_in * allpass_coef ) >> 7;
475 }
476 allpass_in += filter_out;
477
478 allpass_line[allpass_filter_pos] = allpass_in;
479
480
481 s32 fo_2 = allpass_in;
482 if( fo_2 < 0 )
483 {
484 s32 tmp = -fo_2;
485 tmp = ( tmp * allpass_coef ) >> 7;
486 fo_2 = -tmp;
487 }
488 else
489 {
490 fo_2 = ( fo_2 * allpass_coef ) >> 7;
491 }
492 filter_out = out_tmp - fo_2;
493
494
495 // 単極 LPF
496
497 // NOTE : 以下の変形で乗算を1つ減らせます
498 // また、m_LpfCoef1 が不要になります
499 // (m_LpfCoef1 は残してありますので、以下の方が良ければ
500 // 消して下さい)
501 //
502 // OUT = ( 1 - Coef ) * In + Coef * Histry
503 // = In - Coef * In + Coef * Histry
504 // = In - Coef * ( In + History )
505
506 s32 tmp = m_LpfCoef2 * ( filter_out + m_LastLpfOut[ch] );
507 tmp >>= 7;
508 s32 fused_out = filter_out - tmp;
509 m_LastLpfOut[ch] = fused_out;
510
511
512 // 出力
513 fused_out *= m_FusedGain;
514 fused_out += early_out;
515 fused_out >>= 7;
516 input[ch][samp] = fused_out;
517
518 ++early_pos;
519 ++pre_delay_pos;
520 ++comb_filter_pos0;
521 ++comb_filter_pos1;
522 ++allpass_filter_pos;
523 }
524 }
525
526 // バッファ位置をメモリに書き戻す
527 if ( early_pos >= m_EarlyLength )
528 {
529 m_EarlyPos = 0;
530 }
531 else
532 {
533 m_EarlyPos = early_pos;
534 }
535
536 if ( pre_delay_pos >= m_PreDelayLength )
537 {
538 m_PreDelayPos = 0;
539 }
540 else
541 {
542 m_PreDelayPos = pre_delay_pos;
543 }
544
545 if ( comb_filter_pos0 >= m_CombFilterLength[0] )
546 {
547 m_CombFilterPos[0] = 0;
548 }
549 else
550 {
551 m_CombFilterPos[0] = comb_filter_pos0;
552 }
553
554 if ( comb_filter_pos1 >= m_CombFilterLength[1] )
555 {
556 m_CombFilterPos[1] = 0;
557 }
558 else
559 {
560 m_CombFilterPos[1] = comb_filter_pos1;
561 }
562
563 if ( allpass_filter_pos >= m_AllPassFilterLength )
564 {
565 m_AllPassFilterPos = 0;
566 }
567 else
568 {
569 m_AllPassFilterPos = allpass_filter_pos;
570 }
571 }
572
573 } // namespace nw::snd
574 } // namespace nw
575
576