1 /*---------------------------------------------------------------------------*
2   Project:  NintendoWare
3   File:     snd_Sound3DCalculator.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_Sound3DCalculator.h>
21 
22 #include <nw/snd/snd_Sound3DManager.h>      // nw::snd::Sound3DParam
23 #include <nw/snd/snd_Sound3DListener.h>
24 
25 namespace nw {
26 namespace snd {
27 
28 /* ------------------------------------------------------------------------
29         inline function
30    ------------------------------------------------------------------------ */
31 namespace {
32 
33 // (x1,y1) (x2,y2) の2点を通る線形関数において、xに対応するyの値を求める
SolveLinerFunction(f32 x,f32 x1,f32 x2,f32 y1,f32 y2)34 inline f32 SolveLinerFunction( f32 x, f32 x1, f32 x2, f32 y1, f32 y2 )
35 {
36     if ( x1 == x2 ) return ( y1 + y2 ) / 2.0f;
37     f32 divider = x1 - x2;
38     return x * ( y1 - y2 ) / divider + ( x1 * y2 - x2 * y1 ) / divider;
39 }
40 
41 }
42 
43 /* ------------------------------------------------------------------------
44         member function
45    ------------------------------------------------------------------------ */
46 
47 /*---------------------------------------------------------------------------*
48   Name:         CalcVolumeAndPriority
49 
50   Description:  3Dサウンドの音量と優先度を計算する
51 
52   Arguments:    manager - 3Dサウンドマネージャー
53                 listener - 3Dサウンドリスナー
54                 actorParam - 3Dサウンドアクターのパラメータ構造体
55                 volumePtr - 計算した音量を格納するポインタ
56                 priorityPtr - 計算した優先度を格納するポインタ
57 
58   Returns:      なし
59  *---------------------------------------------------------------------------*/
CalcVolumeAndPriority(const Sound3DManager & manager,const Sound3DListener & listener,const Sound3DParam & actorParam,f32 * volumePtr,int * priorityPtr)60 void Sound3DCalculator::CalcVolumeAndPriority(
61     const Sound3DManager& manager,
62     const Sound3DListener& listener,
63     const Sound3DParam& actorParam,
64     f32* volumePtr,
65     int* priorityPtr
66 )
67 {
68     //------------------------------------------------------------------
69     // リスナーから見たアクターの位置を取得
70     nw::math::VEC3 pos;
71     nw::math::VEC3Sub( &pos, &actorParam.position, &listener.GetPosition() );
72     const f32 actorDistance = nw::math::VEC3Len( &pos );
73 
74     CalcVolumeAndPriorityImpl(
75         actorDistance,
76         static_cast<SoundArchive::Sound3DInfo::DecayCurve>(actorParam.decayCurve),
77         actorParam.decayRatio,
78         manager.GetMaxPriorityReduction(),
79         listener.GetMaxVolumeDistance(),
80         listener.GetUnitDistance(),
81         volumePtr,
82         priorityPtr
83     );
84 }
85 
86 /*---------------------------------------------------------------------------*
87   Name:         CalcPan
88 
89   Description:  3Dサウンドのパンを計算する
90 
91   Arguments:    manager - 3Dサウンドマネージャー
92                 listener - 3Dサウンドリスナー
93                 actorParam - 3Dサウンドアクターのパラメータ構造体
94                 calcPanParam - パン計算に使用する構造体
95                 panPtr - 計算したパンを格納するポインタ
96                 spanPtr - 計算したサラウンドパンを格納するポインタ
97 
98   Returns:      なし
99  *---------------------------------------------------------------------------*/
CalcPan(const Sound3DManager & manager,const Sound3DListener & listener,const Sound3DParam & actorParam,const CalcPanParam & calcPanParam,f32 * panPtr,f32 * spanPtr)100 void Sound3DCalculator::CalcPan(
101     const Sound3DManager& manager,
102     const Sound3DListener& listener,
103     const Sound3DParam& actorParam,
104     const CalcPanParam& calcPanParam,
105     f32* panPtr,
106     f32* spanPtr
107 )
108 {
109     //------------------------------------------------------------------
110     // リスナーから見たアクターの位置を取得
111     const math::MTX34& listenerMtx = listener.GetMatrix();
112     nw::math::VEC3 pos;
113     nw::math::VEC3Transform( &pos, &listenerMtx, &actorParam.position );
114     const f32 actorDistance = nw::math::VEC3Len( &pos );
115 
116     CalcPanImpl(
117         pos,
118         listener.GetInteriorSize(),
119         actorDistance,
120         manager.GetPanRange(),
121         calcPanParam.stereoSpeakerAngle,
122         calcPanParam.surroundSpeakerFrontAngle,
123         calcPanParam.surroundSpeakerRearAngle,
124         calcPanParam.surroundPanOffset,
125         panPtr,
126         spanPtr
127     );
128 }
129 
130 /*---------------------------------------------------------------------------*
131   Name:         CalcPitch
132 
133   Description:  3Dサウンドの音程を計算する
134 
135   Arguments:    manager - 3Dサウンドマネージャー
136                 listener - 3Dサウンドリスナー
137                 actorParam - 3Dサウンドアクターのパラメータ構造体
138                 pitchPtr - 計算した音程を格納するポインタ
139 
140   Returns:      なし
141  *---------------------------------------------------------------------------*/
CalcPitch(const Sound3DManager & manager,const Sound3DListener & listener,const Sound3DParam & actorParam,f32 * pitchPtr)142 void Sound3DCalculator::CalcPitch(
143     const Sound3DManager& manager,
144     const Sound3DListener& listener,
145     const Sound3DParam& actorParam,
146     f32* pitchPtr
147 )
148 {
149     NW_NULL_ASSERT( pitchPtr );
150 
151     const f32 sonicVelocity = manager.GetSonicVelocity();
152     if ( sonicVelocity == 0.0f ) {
153         *pitchPtr = 1.0f;
154         return;
155     }
156 
157     //------------------------------------------------------------------
158     // リスナーから見たアクターの位置を取得
159     nw::math::VEC3 relPos;
160     nw::math::VEC3Sub( &relPos, &actorParam.position, &listener.GetPosition() );
161     const f32 distance = nw::math::VEC3Len( &relPos );
162     if ( distance > 0.0f ) {
163         relPos /= distance;
164     }
165 
166     const f32 dopplerFactor = actorParam.dopplerFactor / 32.0f;
167 
168     f32 actorVelocity;
169     f32 listenerVelocity;
170     if ( distance > 0.0f ) {
171         actorVelocity = - nw::math::VEC3Dot( &relPos, &actorParam.velocity );
172         listenerVelocity = - nw::math::VEC3Dot( &relPos, &listener.GetVelocity() );
173     }
174     else {
175         actorVelocity = - nw::math::VEC3Len( &actorParam.velocity );
176         listenerVelocity = nw::math::VEC3Len( &listener.GetVelocity() );
177     }
178     actorVelocity *= dopplerFactor;
179     listenerVelocity *= dopplerFactor;
180 
181     f32 pitch;
182     if ( listenerVelocity > sonicVelocity ) {
183         pitch = 0.0f;
184     }
185     else if ( actorVelocity >= sonicVelocity ) {
186         pitch = 65535.0f;
187     }
188     else {
189         pitch = ( sonicVelocity - listenerVelocity ) / ( sonicVelocity - actorVelocity );
190     }
191 
192     *pitchPtr = pitch;
193 }
194 
195 /*---------------------------------------------------------------------------*
196   Name:         CalcBiquadValue
197 
198   Description:  Biquadフィルタの値を計算する
199 
200   Arguments:    manager - 3Dサウンドマネージャー
201                 listener - 3Dサウンドリスナー
202                 actorParam - 3Dサウンドアクターのパラメータ構造体
203                 pitchPtr - 計算したBiquadフィルタ値を格納するポインタ
204 
205   Returns:      なし
206  *---------------------------------------------------------------------------*/
CalcBiquadFilterValue(const Sound3DManager & manager,const Sound3DListener & listener,const Sound3DParam & actorParam,f32 * biquadFilterValuePtr)207 void Sound3DCalculator::CalcBiquadFilterValue(
208     const Sound3DManager& manager,
209     const Sound3DListener& listener,
210     const Sound3DParam& actorParam,
211     f32* biquadFilterValuePtr
212 )
213 {
214     NW_NULL_ASSERT( biquadFilterValuePtr );
215 
216     NW_UNUSED_VARIABLE( manager );
217 
218     //------------------------------------------------------------------
219     // リスナーから見たアクターの位置を取得
220     nw::math::VEC3 pos;
221     nw::math::VEC3Sub( &pos, &actorParam.position, &listener.GetPosition() );
222     const f32 actorDistance = nw::math::VEC3Len( &pos );
223 
224     // Biquadフィルタの値はボリューム距離に追従
225     f32 biquadFilterValue = 0.0f;
226     f32 maxVolumeDistance = listener.GetMaxVolumeDistance();
227     f32 maxBiquadFilterValue = listener.GetMaxBiquadFilterValue();
228 
229     if( actorDistance > maxVolumeDistance )
230     {
231         biquadFilterValue =
232             ( actorDistance - maxVolumeDistance ) /
233             listener.GetUnitDistance() *
234             listener.GetUnitBiquadFilterValue();
235 
236         if ( biquadFilterValue > maxBiquadFilterValue )
237         {
238             biquadFilterValue = maxBiquadFilterValue;
239         }
240     }
241 
242     *biquadFilterValuePtr = biquadFilterValue;
243 }
244 
CalcVolumeAndPriorityImpl(f32 actorDistance,SoundArchive::Sound3DInfo::DecayCurve decayCurve,f32 decayRatio,int maxPriorityReduction,f32 maxVolumeDistance,f32 unitDistance,f32 * volumePtr,int * priorityPtr)245 void Sound3DCalculator::CalcVolumeAndPriorityImpl(
246     f32 actorDistance,
247     SoundArchive::Sound3DInfo::DecayCurve decayCurve,
248     f32 decayRatio,
249     int maxPriorityReduction,
250     f32 maxVolumeDistance,
251     f32 unitDistance,
252     f32* volumePtr,
253     int* priorityPtr
254 )
255 {
256     NW_NULL_ASSERT( volumePtr );
257     NW_NULL_ASSERT( priorityPtr );
258 
259     static const f32 MAX_VOLUME = 1.0f;
260     f32 volume = MAX_VOLUME;
261 
262     if ( actorDistance > maxVolumeDistance )
263     {
264         switch ( decayCurve )
265         {
266         case DECAY_CURVE_LOG:
267             volume = std::powf( decayRatio, ( actorDistance - maxVolumeDistance ) / unitDistance );
268             break;
269         case DECAY_CURVE_LINEAR:
270             volume = 1.0f - ( actorDistance - maxVolumeDistance ) / unitDistance * ( 1.0f - decayRatio );
271             if ( volume < 0.0f ) volume = 0.0f;
272             break;
273         }
274     }
275 
276     *volumePtr = volume;
277     *priorityPtr = - static_cast<int>( ( 1.0f - volume ) * maxPriorityReduction );
278 }
279 
CalcPanImpl(const nw::math::VEC3 & pos,f32 interiorSize,f32 actorDistance,f32 panRange,f32 stereoSpeakerAngle,f32 surroundSpeakerFrontAngle,f32 surroundSpeakerRearAngle,f32 surroundPanOffset,f32 * panPtr,f32 * spanPtr)280 void Sound3DCalculator::CalcPanImpl(
281     const nw::math::VEC3& pos,
282     f32 interiorSize,
283     f32 actorDistance,
284     f32 panRange,
285     f32 stereoSpeakerAngle,
286     f32 surroundSpeakerFrontAngle,
287     f32 surroundSpeakerRearAngle,
288     f32 surroundPanOffset,
289     f32* panPtr,
290     f32* spanPtr
291 )
292 {
293 #if 0
294     switch ( SoundSystem::GetOutputMode() )
295     {
296 #if 0 // TODO: 3D Surround
297     case OUTPUT_MODE_DPL2:
298     case OUTPUT_MODE_SURROUND:
299         CalcPanSurround(
300             pos,
301             interiorSize,
302             actorDistance,
303             panRange,
304             surroundSpeakerFrontAngle,
305             surroundSpeakerRearAngle,
306             surroundPanOffset,
307             panPtr,
308             spanPtr
309         );
310         break;
311 #endif
312     case OUTPUT_MODE_STEREO:
313         CalcPanStereo(
314             pos,
315             interiorSize,
316             actorDistance,
317             panRange,
318             speakerAngleStereo,
319             panPtr,
320             spanPtr
321         );
322         break;
323     case OUTPUT_MODE_MONO:
324     default:
325         *panPtr = 0.0f;
326         *spanPtr = 0.0f;
327         break;
328     }
329 #else
330     // TORIAEZU 必ずサラウンドで計算する
331     NW_UNUSED_VARIABLE(stereoSpeakerAngle);
332     CalcPanSurround( pos, interiorSize, actorDistance, panRange,
333             surroundSpeakerFrontAngle, surroundSpeakerRearAngle,
334             surroundPanOffset, panPtr, spanPtr );
335 #endif
336 }
337 
CalcPanSurround(const nw::math::VEC3 & pos,f32 interiorSize,f32 actorDistance,f32 panRange,f32 surroundSpeakerFrontAngle,f32 surroundSpeakerRearAngle,f32 surroundPanOffset,f32 * panPtr,f32 * surroundPanPtr)338 void Sound3DCalculator::CalcPanSurround(
339     const nw::math::VEC3& pos,
340     f32 interiorSize,
341     f32 actorDistance,
342     f32 panRange,
343     f32 surroundSpeakerFrontAngle,
344     f32 surroundSpeakerRearAngle,
345     f32 surroundPanOffset,
346     f32* panPtr,
347     f32* surroundPanPtr
348 )
349 {
350     NW_NULL_ASSERT( panPtr );
351     NW_NULL_ASSERT( surroundPanPtr );
352 
353     // アクターの角度と距離を求める
354     f32 angle;
355     f32 distance;
356     CalcAngleAndDistance(
357         pos,
358         actorDistance,
359         interiorSize,
360         &angle,
361         &distance
362     );
363 
364     NW_FMINMAX_ASSERT( surroundSpeakerFrontAngle, 0.0f,              nw::math::F_PI / 2.0f );
365     NW_FMINMAX_ASSERT( surroundSpeakerRearAngle,  nw::math::F_PI / 2.0f, nw::math::F_PI        );
366 
367     f32 x = 0.0f;
368     f32 z = 0.0f;
369 
370     // スピーカーの角度をパン座標の角度に変換する
371     const f32 angleRearLeft   = -surroundSpeakerRearAngle;
372     const f32 angleFrontLeft  = -surroundSpeakerFrontAngle;
373     const f32 angleFrontRight = surroundSpeakerFrontAngle;
374     const f32 angleRearRight  = surroundSpeakerRearAngle;
375 
376     if ( angle < angleRearLeft )
377     {
378         x = SolveLinerFunction( angle, -nw::math::F_PI, angleRearLeft, 0.0f, -1.0f );
379         z = 1.0f;
380     }
381     else if ( angle < -nw::math::F_PI / 2.0f )
382     {
383         x = -1.0f;
384         z = SolveLinerFunction( angle, angleRearLeft, -nw::math::F_PI / 2.0f, 1.0f, 0.0f );
385     }
386     else if ( angle < angleFrontLeft )
387     {
388         x = -1.0f;
389         z = SolveLinerFunction( angle, -nw::math::F_PI / 2.0f, angleFrontLeft, 0.0f, -1.0f );
390     }
391     else if ( angle < angleFrontRight )
392     {
393         x = SolveLinerFunction( angle, angleFrontLeft, angleFrontRight, -1.0f, 1.0f );
394         z = -1.0f;
395     }
396     else if ( angle < nw::math::F_PI / 2.0f )
397     {
398         x = 1.0f;
399         z = SolveLinerFunction( angle, angleFrontRight, nw::math::F_PI / 2.0f, -1.0f, 0.0f );
400     }
401     else if ( angle < angleRearRight )
402     {
403         x = 1.0f;
404         z = SolveLinerFunction( angle, nw::math::F_PI / 2.0f, angleRearRight, 0.0f, 1.0f );
405     }
406     else
407     {
408         x = SolveLinerFunction( angle, angleRearRight, nw::math::F_PI, 1.0f, 0.0f );
409         z = 1.0f;
410     }
411 
412     // 原点の補正
413     f32 a = ( std::cosf( surroundSpeakerFrontAngle ) + std::cosf( surroundSpeakerRearAngle ) ) / 2.0f;
414     f32 speakerOffset = a / ( a + ( -std::cosf( surroundSpeakerRearAngle ) ) );
415 
416     const f32 center_x = 0.0f;
417     const f32 center_z = speakerOffset;
418 
419     // パンを設定する
420     x *= panRange;
421     z *= panRange;
422     *panPtr = x * distance + center_x * ( 1.0f - distance );
423     *surroundPanPtr = z * distance + center_z * ( 1.0f - distance ) + 1.0f + surroundPanOffset;
424 }
425 
426 #if 0 // 不要かも
427 void Sound3DCalculator::CalcPanStereo(
428     const nw::math::VEC3& pos,
429     f32 interiorSize,
430     f32 actorDistance,
431     f32 panRange,
432     f32 speakerAngleStereo,
433     f32* panPtr,
434     f32* spanPtr
435 )
436 {
437     NW_NULL_ASSERT( panPtr );
438     // NW_NULL_ASSERT( spanPtr );    // TODO:
439     NW_UNUSED_VARIABLE( spanPtr );
440 
441     // アクターの角度と距離を求める
442     f32 angle;
443     f32 distance;
444     CalcAngleAndDistance(
445         pos,
446         actorDistance,
447         interiorSize,
448         &angle,
449         &distance
450     );
451 
452     NW_FMINMAX_ASSERT( speakerAngleStereo, 0.0f, nw::math::F_PI / 2.0f );
453 
454     f32 x = 0.0f;
455 
456     const f32 angleRearLeft   = - nw::math::F_PI + speakerAngleStereo;
457     const f32 angleFrontLeft  = -speakerAngleStereo;
458     const f32 angleFrontRight = speakerAngleStereo;
459     const f32 angleRearRight  = nw::math::F_PI - speakerAngleStereo;
460 
461     if ( angle < angleRearLeft )
462     {
463         x = SolveLinerFunction( angle, -nw::math::F_PI, angleRearLeft, 0.0f, -1.0f );
464     }
465     else if ( angle < angleFrontLeft )
466     {
467         x = -1.0f;
468     }
469     else if ( angle < angleFrontRight )
470     {
471         x = SolveLinerFunction( angle, angleFrontLeft, angleFrontRight, -1.0f, 1.0f );
472     }
473     else if ( angle < angleRearRight )
474     {
475         x = 1.0f;
476     }
477     else
478     {
479         x = SolveLinerFunction( angle, angleRearRight, nw::math::F_PI, 1.0f, 0.0f );
480     }
481 
482     // パンを設定する
483     x *= panRange;
484     *panPtr = x * distance;
485     // *spanPtr = 0.0f;     // TODO:
486 }
487 #endif
488 
CalcAngleAndDistance(const nw::math::VEC3 & pos,f32 actorDistance,f32 interiorSize,f32 * anglePtr,f32 * distancePtr)489 void Sound3DCalculator::CalcAngleAndDistance(
490     const nw::math::VEC3& pos,
491     f32 actorDistance,
492     f32 interiorSize,
493     f32* anglePtr,
494     f32* distancePtr
495 )
496 {
497     NW_NULL_ASSERT( anglePtr );
498     NW_NULL_ASSERT( distancePtr );
499 
500     nw::math::VEC3 interiorPos;
501 
502     if ( actorDistance == 0.0f )
503     {
504         interiorPos.x = interiorPos.y = interiorPos.z = 0.0f;
505     }
506     else
507     {
508         nw::math::VEC3 yPlanePoint( pos.x, 0, pos.z );
509         f32 d = nw::math::VEC3Len( &yPlanePoint );
510         if ( d > interiorSize )
511         {
512             // 音源がインテリアサイズを超えた場合はインテリアサイズ内に音源を置く
513             yPlanePoint.x *= interiorSize / d;
514             yPlanePoint.z *= interiorSize / d;
515         }
516 
517         f32 yPlaneDistance = nw::math::VEC3Len( &yPlanePoint );
518         interiorPos.x = pos.x * yPlaneDistance / actorDistance;
519         interiorPos.y = 0;
520         interiorPos.z = pos.z * yPlaneDistance / actorDistance;
521     }
522 
523     *anglePtr = std::atan2f( interiorPos.x, -interiorPos.z );
524     *distancePtr = nw::math::VEC3Len( &interiorPos ) / interiorSize;
525 }
526 
527 } // namespace nw::snd
528 } // namespace nw
529 
530