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