/*--------------------------------------------------------------------------
  Project:  Manual 2D Library - m2d
  File:     m2d_soundManager.cpp

  Copyright (C)2007-2009 Nintendo  All Rights Reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law. They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Revision: 1758 $
 *-------------------------------------------------------------------------*/

#include <nitro.h>
#include <nnsys.h>

// #define AUDIO_PRINT  OS_Printf
#define AUDIO_PRINT(...)    ((void) 0)
#define AUDIO_ASSERT        NNS_ASSERT

#include <ntmv/m2d/soundManager.h>


namespace ntmv
{
namespace m2d
{

/*!
  Constructor
 */
SoundManager::SoundManager() :
    mInitialized( false ) ,
    mpCallback( NULL )
{
}


/*!
  Destructor
 */
SoundManager::~SoundManager()
{
}



/*!
  Initialization process
  @param p_arg Pointer to sound archive data
 */
void SoundManager::init( u8* p_arg )
{
    // Initialize the sound system
    NNS_SndInit();

    // Initializes the Sound Archive structure.
    NNS_SndArcInitOnMemory( &mArc, p_arg );
    BOOL bb = NNS_SndArcPlayerSetup( NULL );
    AUDIO_ASSERT(bb);

    // Initialize the sound effect information structures
    for ( int i = 0; i < MAXCT_SE; i++ )
    {
        NNS_SndHandleInit( &mSeInfo[i].sndHandle );
        mSeInfo[i].seNo = -1;
        mSeInfo[i].holdSeFlag = false;
        mSeInfo[i].holdSeUpdateFlag = false;
    }

    // Initialize information structures for background music
    NNS_SndHandleInit( &mBgmInfo.sndHandle );
    mBgmInfo.bgmNo = -1;
    mBgmInfo.pitch = 0;

    // Initialize member variables and so on
    mSeBlk = 0;

    // Get the maximum number of sound effects
    mMaxSeNum = NNS_SndArcGetSeqArcSeqCount( SEQ_ARC_NO );
    AUDIO_PRINT("侓 嵟戝SE悢  : %d\n", mMaxSeNum);

    // Get the maximum background music count
    mMaxBgmNum = NNS_SndArcGetSeqCount();
    AUDIO_PRINT("侓 嵟戝BGM悢 : %d\n", mMaxBgmNum);
    
    mInitialized = true;

    AUDIO_PRINT("侓 弶婜壔姰椆\n");
}


/*!
  Frame processing
 */
void SoundManager::update()
{
    if ( !mInitialized ) return;

    // Process frames for held sound effects
    for ( int i = 0; i < MAXCT_SE; i++ )
    {
        // If a sound is being played
        if ( Assigned( mSeInfo[i].sndHandle.player ) )
        {
            // If the call was suddenly cut off
            if ( !mSeInfo[i].holdSeUpdateFlag )
            {
                if ( mSeInfo[i].holdSeFlag )
                {
//                     AUDIO_PRINT("侓 stopSe->%d(hold:%d)\n", mSeInfo[i].seNo, mSeInfo[i].holdSeFlag);
                    NNS_SndPlayerStopSeq( &mSeInfo[i].sndHandle, CTFRAME_SE_STOP );
                    mSeInfo[i].seNo = -1;
                    mSeInfo[i].holdSeFlag = false; // in any case, flags are unset every frame
                }
            }
            else
            {
                mSeInfo[i].holdSeUpdateFlag = false;
            }
        }
    }

    NNS_SndMain();
}


/*!
  Trigger sound effect playback
  @param  seNo Sound effect number
  @param  posX X-coordinate (can be omitted)
  @return  The sound effect handle that was successful in playback
 */
NNSSndHandle* SoundManager::startSe( int seNo, int posX )
{
    // Return if initialization has not finished
    if ( !mInitialized )
    {
        AUDIO_PRINT("侓 startSe() err : mInitialized = %d\n", mInitialized);
        return NULL;
    }

    // Return if the ID is illegal
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return NULL;
    }

    // Check whether the sound effect that requested playback is already being played back
    SSeInfo* se = FIsSeActive( seNo );

    // If it is not already being played back, first allocate a handle
    if ( !Assigned(se) )
    {
        se = FGetFreeSeBlock( true );
    }

    // Do nothing and return if a handle could not be allocated
    if ( !Assigned(se) )
    {
        AUDIO_PRINT("侓 僴儞僪儖妋曐偱偒偢\n");
        return NULL;
    }

    // Playback
    NNS_SndPlayerStopSeq( &se -> sndHandle, CTFRAME_SE_STOP );
    (void)NNS_SndArcPlayerStartSeqArc( &se -> sndHandle, SEQ_ARC_NO, seNo );
//     AUDIO_PRINT("侓 seNo: %d\n", seNo);


    // callback test
//     if ( Assigned( mpCallback ) ) { mpCallback -> startSe( seNo ); }
    
                    
    // pan
    if ( 128 != posX ) {
        s32 pan = cutGELE_S32( posX, 0, 255 ); // Limit posX to values between 0 and 255
        pan -= 128;
        NNS_SndPlayerSetTrackPan( &se -> sndHandle, 0xffff, pan );
    }

    // Save information on the sound effect that was played back
    se -> seNo = seNo;
    se -> holdSeFlag = false;

    return &se -> sndHandle;
}


/*!
  Trigger sound effect playback (the pitch changes together with the pan)
  @param  seNo Sound effect number
  @param  posX X-coordinate (can be omitted)
  @return  The sound effect handle that was successful in playback
 */
NNSSndHandle* SoundManager::startSeWithPitch( int seNo, int posX )
{
    // Return if initialization has not finished
    if ( !mInitialized )
    {
        AUDIO_PRINT("侓 startSeWithPitch() err : mInitialized = %d\n", mInitialized);
        return NULL;
    }

    // Return if the ID is illegal
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return NULL;
    }

    // Check whether the requested sound effect is already being played back
    SSeInfo* se = FIsSeActive( seNo );

    // If it is not already being played back, first allocate a handle
    if ( !Assigned(se) )
    {
        se = FGetFreeSeBlock( true );
    }

    // Do nothing and return if a handle could not be allocated
    if ( !Assigned(se) )
    {
        AUDIO_PRINT("侓 僴儞僪儖妋曐偱偒偢\n");
        return NULL;
    }

    // Playback
    NNS_SndPlayerStopSeq( &se -> sndHandle, CTFRAME_SE_STOP );
    (void)NNS_SndArcPlayerStartSeqArc( &se -> sndHandle, SEQ_ARC_NO, seNo );
//     AUDIO_PRINT("侓 seNo: %d\n", seNo);

    // pan
    s32 pan = cutGELE_S32( posX, 0, 255 ); // Limit posX to values between 0 and 255
    pan -= 128;
    NNS_SndPlayerSetTrackPan( &se -> sndHandle, 0xffff, pan );

    // pitch
    s32 pitch = cutGELE_S32( posX, 0, 255 ); // Limit posX to values between 0 and 255
    pitch = pitch * 6 * 64 / 255;
//     pitch *= 64;
    NNS_SndPlayerSetTrackPitch( &se -> sndHandle, 0xffff, pitch );
//     AUDIO_PRINT("侓 seNo: %d(pan:%4d, pitch:%5d)\n", seNo, pan, pitch);

    // Save information on the sound effect that was played back
    se -> seNo = seNo;
    se -> holdSeFlag = false;

    return &se -> sndHandle;
}


/*!
  Trigger sound effect playback (variable audio volume)
  @param  seNo Sound effect number
  @param  vol  Audio volume, with a scrolling speed between 0 and 127 (can be omitted)
  @return  The sound effect handle that was successful in playback
 */
NNSSndHandle* SoundManager::startSeWithVol( int seNo, int vol )
{
    // Return if initialization has not finished
    if ( !mInitialized )
    {
        AUDIO_PRINT("侓 startSeWithVol() err : mInitialized = %d\n", mInitialized);
        return NULL;
    }

    // Return if the ID is illegal
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return NULL;
    }

    // Check whether the requested sound effect is already being played back
    SSeInfo* se = FIsSeActive( seNo );

    // If it is not already being played back, first allocate a handle
    if ( !Assigned(se) )
    {
        se = FGetFreeSeBlock( true );
    }

    // Do nothing and return if a handle could not be allocated
    if ( !Assigned(se) )
    {
        AUDIO_PRINT("侓 僴儞僪儖妋曐偱偒偢\n");
        return NULL;
    }

    // Playback
    NNS_SndPlayerStopSeq( &se -> sndHandle, CTFRAME_SE_STOP );
    (void)NNS_SndArcPlayerStartSeqArc( &se -> sndHandle, SEQ_ARC_NO, seNo );

    // Change vol
    s32 v = (128-vol)/2 + vol;     // Convert vol to a value between 64 and 127
    v = cutGELE_S32( v, 64, 127 ); // Limit vol to a value between 64 and 127
    NNS_SndPlayerSetTrackVolume( &se -> sndHandle, 0xffff, v );

    // Save information on the sound effect that was played back
    se -> seNo = seNo;
    se -> holdSeFlag = false;

    return &se -> sndHandle;
}


/*!
  Scroll sound effect playback (multi-method)
  @param  seNo Sound effect number
  @param speed This is similar to the so-called "scrolling speed"
  @param objId ID of the object that is scrolling
  @return  The sound effect handle that was successful in playback
 */
NNSSndHandle* SoundManager::startScrollSe( int seNo, int speed, u32 objId )
{
    // Return if initialization has not finished
    if ( !mInitialized )
    {
        AUDIO_PRINT("侓 startSe() err : mInitialized = %d\n", mInitialized);
        return NULL;
    }

    // Return if the ID is illegal
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return NULL;
    }

    // Check whether the sound effect that requested playback is already being played back
    SSeInfo* se = FIsSeActive( seNo );

    // Allocate a handle and continue
    se = FGetFreeSeBlock( !Assigned(se) );

    // Do nothing and return if a handle could not be allocated
    if ( !Assigned(se) )
    {
        AUDIO_PRINT("侓 僴儞僪儖妋曐偱偒偢\n");
        return NULL;
    }

    // [Launcher] Go to exclusive processing
    if ( Assigned( mpCallback ) ) { mpCallback -> setScrollSpec( this, speed, objId ); }

    // Playback
    NNS_SndPlayerStopSeq( &se -> sndHandle, CTFRAME_SE_STOP );
    (void)NNS_SndArcPlayerStartSeqArc( &se -> sndHandle, SEQ_ARC_NO, seNo );

    // Save information on the sound effect that was played back
    se -> seNo = seNo;
    se -> holdSeFlag = false;

    return &se -> sndHandle;
}


/*!
  Trigger multiple sound effect playback (sound effects with the same ID will be played back without being overwritten)
  @param  seNo Sound effect number
  @param  posX X-coordinate (can be omitted)
  @return  The sound effect handle that was successful in playback
 */
NNSSndHandle* SoundManager::startMultiSe( int seNo, int posX )
{
    // Return if initialization has not finished
    if ( !mInitialized )
    {
        AUDIO_PRINT("侓 startSe() err : mInitialized = %d\n", mInitialized);
        return NULL;
    }

    // Return if the ID is illegal
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return NULL;
    }

    // Check whether the sound effect that requested playback is already being played back
    SSeInfo* se = FIsSeActive( seNo );

    // Allocate a handle and continue
    se = FGetFreeSeBlock( !Assigned(se) );

    // Do nothing and return if a handle could not be allocated
    if ( !Assigned(se) )
    {
        AUDIO_PRINT("侓 僴儞僪儖妋曐偱偒偢\n");
        return NULL;
    }

    // playback
    NNS_SndPlayerStopSeq( &se -> sndHandle, CTFRAME_SE_STOP );
    (void)NNS_SndArcPlayerStartSeqArc( &se -> sndHandle, SEQ_ARC_NO, seNo );
//     AUDIO_PRINT("侓 seNo: %d\n", seNo);

    // pan
    if ( 128 != posX ) {
        s32 pan = cutGELE_S32( posX, 0, 255 ); // Limit posX to values between 0 and 255
        pan -= 128;
        NNS_SndPlayerSetTrackPan( &se -> sndHandle, 0xffff, pan );
    }

    // Save information on the sound effect that was played back
    se -> seNo = seNo;
    se -> holdSeFlag = false;

    return &se -> sndHandle;
}


/*!
  Playback of held sound effects (apply pan, if necessary)
  @param  seNo Sound effect number
  @param  posX X-coordinate (can be omitted)
  @return  The sound effect handle that was successful in playback
 */
NNSSndHandle* SoundManager::holdSe( int seNo, int posX )
{
    // Return if initialization has not finished
    if ( !mInitialized )
    {
        AUDIO_PRINT("侓 holdSe() err : mInitialized = %d\n", mInitialized);
        return NULL;
    }

    // Return if the ID is illegal
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return NULL;
    }

    // Check whether the requested sound effect is already being played back
    SSeInfo* se = FIsSeActive( seNo );

    // Limit posX to values between 0 and 255
    s32 pos_x = cutGELE_S32( posX, 0, 255 );

    // If it is not already being played back, allocate a handle and then begin playback
    if ( !Assigned(se) )
    {
        se = FGetFreeSeBlock( true );

        // Do nothing and return if a handle could not be allocated
        if ( !Assigned(se) )
        {
            AUDIO_PRINT("侓 僴儞僪儖妋曐偱偒偢\n");
            return NULL;
        }

        // Playback
        NNS_SndPlayerStopSeq( &se -> sndHandle, CTFRAME_SE_STOP );
        (void)NNS_SndArcPlayerStartSeqArc( &se -> sndHandle, SEQ_ARC_NO, seNo );

        // Save information on the sound effect that was played back
        se -> seNo = seNo;

        AUDIO_PRINT("侓 seNo: %d...<HOLD>\n", seNo);
    }

    // pan
    s32 pan = pos_x;
    pan -= 128;
    NNS_SndPlayerSetTrackPan( &se -> sndHandle, 0xffff, pan );
//     AUDIO_PRINT("侓 pan : %d\n", pan);


    // Save information on the sound effect that was played back
    se -> holdSeFlag = true;
    se -> holdSeUpdateFlag = true;
    return &se -> sndHandle;

}


/*!
  Playback of held sound effects. x- and y-coordinates are converted into distance (calculated here) and pan information, then used.
  @param  seNo Sound effect number
  @param  posX X-coordinate
  @param  posY Y-coordinate
  @return  The sound effect handle that was successful in playback
 */
NNSSndHandle* SoundManager::holdSeWithPos( int seNo, int posX, int posY )
{
    // Return if initialization has not finished
    if ( !mInitialized )
    {
        AUDIO_PRINT("侓 holdSeWithPos() err : mInitialized = %d\n", mInitialized);
        return NULL;
    }

    // Return if the ID is illegal
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return NULL;
    }

    // Check whether the requested sound effect is already being played back
    SSeInfo* se = FIsSeActive( seNo );

    s32 pos_x = cutGELE_S32( posX, 0, 255 );
    s32 pos_y = cutGELE_S32( posY, 0, 191 );

    // If it is not already being played back, allocate a handle and then begin playback
    if ( !Assigned(se) )
    {
        se = FGetFreeSeBlock( true );

        // Do nothing and return if a handle could not be allocated
        if ( !Assigned(se) )
        {
            AUDIO_PRINT("侓 僴儞僪儖妋曐偱偒偢\n");
            return NULL;
        }

        // Playback
        NNS_SndPlayerStopSeq( &se -> sndHandle, CTFRAME_SE_STOP );
        (void)NNS_SndArcPlayerStartSeqArc( &se -> sndHandle, SEQ_ARC_NO, seNo );

        // Save information on the sound effect that was played back
        se -> seNo = seNo;
        se -> posX = pos_x;
        se -> posY = pos_y;
    }

    //---- Update vol and pan from pos
    // pan
    s32 pan = pos_x;
    pan -= 128;
    NNS_SndPlayerSetTrackPan( &se -> sndHandle, 0xffff, pan );
//     AUDIO_PRINT("侓 pan : %d\n", pan);

    // vol
    fx64 t;
    s32 vol;
    s32 x = se->posX - pos_x; // amount to move the x-coordinate
    s32 y = se->posY - pos_y; // amount to move the y-coordinate

    SDK_ASSERT( !CP_IsSqrtBusy() );
		
	t = (fx64)x * x;
	t += (fx64)y * y;
	
	t <<= 2;                           // for better precision
		
	CP_SetSqrt64( (u64)t );
	vol = ( (fx32)CP_GetSqrtResult32() + 1 ) >> 1;
    vol = cutGELE_S32( vol << 1, 0, 127 ); // Simply scale by a factor of 2
    
    // Currently, we don't want track1 to change (see below)
    NNS_SndPlayerSetTrackVolume( &se -> sndHandle, 0xffff^Tr(1), vol );
//     AUDIO_PRINT("侓 pan:%3d vol:%3d\n", pan, vol);

    // Save information on the sound effect that was played back
    se -> posX = pos_x;
    se -> posY = pos_y;
    se -> holdSeFlag = true;
    se -> holdSeUpdateFlag = true;
    return &se -> sndHandle;

}


/*!
  Playback of held sound effects (using the precalculated distance between two points)
  @param  seNo Sound effect number
  @param distance Distance
  @param  posX X-coordinate (can be omitted)
  @return  The sound effect handle that was successful in playback
 */
NNSSndHandle* SoundManager::holdSeWithDis( int seNo, int distance, int posX )
{
    // Return if initialization has not finished
    if ( !mInitialized )
    {
        AUDIO_PRINT("侓 holdSeWithDis() err : mInitialized = %d\n", mInitialized);
        return NULL;
    }

    // Return if the ID is illegal
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return NULL;
    }

    // Check whether the requested sound effect is already being played back
    SSeInfo* se = FIsSeActive( seNo );

    // Limit posX to values between 0 and 255
    s32 pos_x = cutGELE_S32( posX, 0, 255 );

    // If it is not already being played back, allocate a handle and then begin playback
    if ( !Assigned(se) )
    {
        se = FGetFreeSeBlock( true );

        // Do nothing and return if a handle could not be allocated
        if ( !Assigned(se) )
        {
            AUDIO_PRINT("侓 僴儞僪儖妋曐偱偒偢\n");
            return NULL;
        }

        // Playback
        NNS_SndPlayerStopSeq( &se -> sndHandle, CTFRAME_SE_STOP );
        (void)NNS_SndArcPlayerStartSeqArc( &se -> sndHandle, SEQ_ARC_NO, seNo );

        // Save information on the sound effect that was played back
        se -> seNo = seNo;
    }

//     AUDIO_PRINT("侓 distance : %d\n", distance);
    //---- Update pan, vol, and pitch from distance
    // pan
    s32 pan = pos_x;
    pan -= 128;
    NNS_SndPlayerSetTrackPan( &se -> sndHandle, 0xffff, pan );

    // vol
    s32 vol;
    if ( 0 == distance )
    {
        vol = 0;
    }
    else
    {
        vol = 63 + distance;    // Set vol to 64 when distance is 1
    }
    vol = cutGELE_S32( vol, 0, 127 );
    NNS_SndPlayerSetTrackVolume( &se -> sndHandle, 0xffff, vol );

    // pitch
    // Standard pitch when distance is 1
    // Each time it increases by one, raise notes by a half step (64)
    s32 pitch = ( distance - 1 ) * 64; 
    pitch = cutGELE_S32( pitch, -32768, 32767 );
    NNS_SndPlayerSetTrackPitch( &se -> sndHandle, 0xffff, pitch );
    
//     AUDIO_PRINT("侓 pan:%3d vol:%3d pitch:%6d\n", pan, vol, pitch);

    // Save information on the sound effect that was played back
    se -> holdSeFlag = true;
    se -> holdSeUpdateFlag = true;
    return &se -> sndHandle;

}


/*!
  Playback of held sound effects (use changes measured with pixels)
  @param  seNo Sound effect number
  @param dot Pixel count
  @param  posX X-coordinate (can be omitted)
  @return  The sound effect handle that was successful in playback
 */
NNSSndHandle* SoundManager::holdSeWithDot( int seNo, int dot, int posX )
{
    // Return if initialization has not finished
    if ( !mInitialized )
    {
        AUDIO_PRINT("侓 holdSeWithDot() err : mInitialized = %d\n", mInitialized);
        return NULL;
    }

    // Return if the ID is illegal
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return NULL;
    }

    // Check whether the requested sound effect is already being played back
    SSeInfo* se = FIsSeActive( seNo );

    // Limit posX to values between 0 and 255
    s32 pos_x = cutGELE_S32( posX, 0, 255 );

    // If it is not already being played back, allocate a handle and then begin playback
    if ( !Assigned(se) )
    {
        se = FGetFreeSeBlock( true );

        // Do nothing and return if a handle could not be allocated
        if ( !Assigned(se) )
        {
            AUDIO_PRINT("侓 僴儞僪儖妋曐偱偒偢\n");
            return NULL;
        }

        // Playback
        NNS_SndPlayerStopSeq( &se -> sndHandle, CTFRAME_SE_STOP );
        (void)NNS_SndArcPlayerStartSeqArc( &se -> sndHandle, SEQ_ARC_NO, seNo );

        // Save information on the sound effect that was played back
        se -> seNo = seNo;
    }

    // pan
    s32 pan = pos_x;
    pan -= 128;
    NNS_SndPlayerSetTrackPan( &se -> sndHandle, 0xffff, pan );

    // Open and close tracks according to the number of pixels
    if ( 1 > dot ) {
        NNS_SndPlayerSetTrackMuteEx( &se -> sndHandle, 0xffff, NNS_SND_SEQ_MUTE_RELEASE ); // Mute all tracks
    }
    else if ( 1 == dot ) {
        NNS_SndPlayerSetTrackMuteEx( &se -> sndHandle, Tr(0), NNS_SND_SEQ_MUTE_OFF ); // Unmute track 0
        NNS_SndPlayerSetTrackMuteEx( &se -> sndHandle, 0xffff^Tr(0), NNS_SND_SEQ_MUTE_RELEASE ); // Mute all tracks except for track 0
    }
    else if ( ( 1 < dot ) && ( 5 > dot ) ) {
        NNS_SndPlayerSetTrackMuteEx( &se -> sndHandle, Tr(0)|Tr(1), NNS_SND_SEQ_MUTE_OFF ); // Unmute track 0,1
        NNS_SndPlayerSetTrackMuteEx( &se -> sndHandle, 0xffff^(Tr(0)|Tr(1)), NNS_SND_SEQ_MUTE_RELEASE ); // Mute all tracks except for track 0,1
    }
    else if ( ( 5 <= dot ) && ( 16 > dot ) ) {
        NNS_SndPlayerSetTrackMuteEx( &se -> sndHandle, Tr(0)|Tr(1)|Tr(2), NNS_SND_SEQ_MUTE_OFF ); // Unmute tracks 0, 1, and 2
        NNS_SndPlayerSetTrackMuteEx( &se -> sndHandle, 0xffff^(Tr(0)|Tr(1)|Tr(2)), NNS_SND_SEQ_MUTE_RELEASE ); // Mute all tracks except for tracks 0, 1, and 2
    }
    else {
        NNS_SndPlayerSetTrackMuteEx( &se -> sndHandle, 0xffff, NNS_SND_SEQ_MUTE_OFF ); // Unmute all tracks
    }
    AUDIO_PRINT("侓 pan:%3d dot:%6d\n", pan, dot);


    // Save information on the sound effect that was played back
    se -> holdSeFlag = true;
    se -> holdSeUpdateFlag = true;
    return &se -> sndHandle;

}


/*!
  Playback of held sound effects with sweep (this can be done from the specified pitch)
  @param  seNo Sound effect number
  @param percent Note to start at relative to the final pitch (currently limited to 10 notes)
  @param  posX X-coordinate (can be omitted)
  @return  The sound effect handle that was successful in playback
 */
NNSSndHandle* SoundManager::holdSeWithSweep( int seNo, int percent, int posX )
{
    // Return if initialization has not finished
    if ( !mInitialized )
    {
        AUDIO_PRINT("侓 holdSeWithSweep() err : mInitialized = %d\n", mInitialized);
        return NULL;
    }

    // Return if the ID is illegal
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return NULL;
    }

    // Check whether the requested sound effect is already being played back
    SSeInfo* se = FIsSeActive( seNo );

    // Limit posX to values between 0 and 255
    s32 pos_x = cutGELE_S32( posX, 0, 255 );

    // If it is not already being played back, allocate a handle and then begin playback
    if ( !Assigned(se) )
    {
        se = FGetFreeSeBlock( true );

        // Do nothing and return if a handle could not be allocated
        if ( !Assigned(se) )
        {
            AUDIO_PRINT("侓 僴儞僪儖妋曐偱偒偢\n");
            return NULL;
        }

        // Write the starting point of the sweep to a global variable
        s32 per = cutGELE_S32( percent, 0, 100 );
        s32 sweep_pitch = (64*per)/10 - 640; // Divide a half step into 10 equal parts and calculate the sweep starting position
        BOOL b1 = NNS_SndPlayerWriteGlobalVariable( 0, (s16)sweep_pitch );

        // Write the sweep note length to a global variable
        s32 sweep_len = MATH_IAbs( 100 - per );
        BOOL b2 = NNS_SndPlayerWriteGlobalVariable( 1, (s16)sweep_len );

        // Write decay to a global variable
        s32 decay = ((127-72)*per)/100 + 72; // (127-72): decay is 72 when an upper-limit sweep as been performed, so make decay larger if the sweep has become short
        BOOL b3 = NNS_SndPlayerWriteGlobalVariable( 2, (s16)decay );

        if ( !b1 || !b2 || !b3 ) {
            AUDIO_PRINT("侓 僔乕働儞僗僌儘乕僶儖曄悢傊偺彂偒崬傒偵幐攕偟偰偄傑偡\n");
        }
        else {
//             AUDIO_PRINT("侓 Morphing sound effect playback < start_pitch=fn7%d, len=%d, decay=%d\n", sweep_pitch, sweep_len, decay);
        }

        // Playback
        NNS_SndPlayerStopSeq( &se -> sndHandle, CTFRAME_SE_STOP );
        (void)NNS_SndArcPlayerStartSeqArc( &se -> sndHandle, SEQ_ARC_NO, seNo );

        // Save information on the sound effect that was played back
        se -> seNo = seNo;
    }

    // pan
    s32 pan = pos_x;
    pan -= 128;
    NNS_SndPlayerSetTrackPan( &se -> sndHandle, 0xffff, pan );
//     AUDIO_PRINT("侓 pan : %d\n", pan);


    // Save information on the sound effect that was played back
    se -> holdSeFlag = true;
    se -> holdSeUpdateFlag = true;
    return &se -> sndHandle;

}


/*!
  Stop a sound effect
  @param sndHandle  Handle for the sound effect to stop
  @param fadeFrame  Fade-out frame
 */
void SoundManager::stopSe( NNSSndHandle* sndHandle, int fadeFrame )
{
    if ( !Assigned(sndHandle) ) return; // invalid handle

    if ( Assigned(sndHandle->player) )
    {
        for ( int i = 0; i < MAXCT_SE; i++ )
        {
            if ( sndHandle == &mSeInfo[i].sndHandle )
            {
                NNS_SndPlayerStopSeq( &mSeInfo[i].sndHandle, fadeFrame );
                mSeInfo[i].seNo = -1;
                mSeInfo[i].holdSeFlag = false;
                mSeInfo[i].holdSeUpdateFlag = false;
            }
        }
    }
}


/*!
  Stop a sound effect
  @param seNo  Number of the sound effect to stop
  @param fadeFrame  Fade-out frame
 */
void SoundManager::stopSe( int seNo, int fadeFrame )
{
    // Return if initialization has not finished
    if ( !mInitialized )
    {
        AUDIO_PRINT("侓 stopSe() err : mInitialized = %d\n", mInitialized);
        return;
    }

    // Return if the ID is illegal
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return;
    }

    for ( int i = 0; i < MAXCT_SE; i++ ) {
        if ( Assigned( mSeInfo[i].sndHandle.player ) ) {
            if ( seNo == mSeInfo[i].seNo ) {
                NNS_SndPlayerStopSeq( &mSeInfo[i].sndHandle, fadeFrame );
                mSeInfo[i].seNo = -1;
                mSeInfo[i].holdSeFlag = false;
                mSeInfo[i].holdSeUpdateFlag = false;
            }
        }
    }
}


/*!
  Is the sound effect being played back?
  @param  seNo  ID of the sound effect to check
  @return TRUE if it is being played back and FALSE otherwise
 */
BOOL SoundManager::isSeActive( int seNo )
{
    return Assigned( FIsSeActive( seNo ) );
}


/*!
  Is the sound effect being played back?
  @param  seNo  ID of the sound effect to check
  @return  Sound effect information structure
 */
SSeInfo* SoundManager::FIsSeActive( int seNo )
{
    // Check the value of seNo
    if ( !checkRangeGELT_S32( seNo, 0, (s32)mMaxSeNum ) )
    {
        AUDIO_PRINT("侓 WARNING : invalid seNo = %d\n", seNo);
        return NULL;
    }

    for ( int i = 0; i < MAXCT_SE; i++ )
    {
        if ( seNo == mSeInfo[i].seNo )
        {
            if ( Assigned( mSeInfo[i].sndHandle.player ) )
            {
                return &mSeInfo[i];
            }
        }
    }
    
    return NULL;
}


/*!
  Gets a free sound effect handle
  @param  isForce  Is this a forced action?
  @return  Sound effect information structure
 */
SSeInfo* SoundManager::FGetFreeSeBlock( BOOL isForce )
{
    mSeBlk = clipGELT_S32( mSeBlk + 1, 0, MAXCT_SE );

    for ( int i = 0; i < MAXCT_SE; i++ )
    {
        s32 id = clipGELT_S32( mSeBlk + i, 0, MAXCT_SE );
        if ( !Assigned( mSeInfo[id].sndHandle.player ) )
        {
            mSeBlk = id;
            return &mSeInfo[ mSeBlk ];
        }
    }

    // TODO: Even though a handle is taken forcibly here, it would probably be best to look at priorities.
    // 
    return isForce ? &mSeInfo[ mSeBlk ] : NULL;
}


/*!
  Is the background music being played back?
  @return TRUE if it is being played back and FALSE otherwise
 */
BOOL SoundManager::isBgmActive()
{
    return Assigned( mBgmInfo.sndHandle.player );
}


/*!
  Background music playback
  @param  bgmNo  Background music number
  @return  TRUE if playback was successful and FALSE on failure
 */
BOOL SoundManager::startBgm( int bgmNo )
{
    // Return if initialization has not finished
    if ( !mInitialized ) {
        AUDIO_PRINT("侓 startBgm() err : mInitialized = %d\n", mInitialized);
        return false;
    }

    // Return if the background music number is illegal
    if ( !checkRangeGELT_S32( bgmNo, 0, (s32)mMaxBgmNum ) ) {
        AUDIO_PRINT("侓 WARNING : invalid bgmNo = %d\n", bgmNo);
        return false;
    }

    // When nothing is being played back, or when the background music that is being played back has a different number than the requested background music
    if ( mBgmInfo.bgmNo != bgmNo || !isBgmActive() ) {

        // Playback
        NNS_SndPlayerStopSeq( &mBgmInfo.sndHandle, 0 );
        BOOL bb = NNS_SndArcPlayerStartSeq( &mBgmInfo.sndHandle , bgmNo );
        if (!bb) return false;
        AUDIO_PRINT("侓 bgmNo: %d -> play...\n", bgmNo);
    }
    else {
        AUDIO_PRINT("侓 bgmNo: %d -> re-requested<WARNING>\n", bgmNo);
        return false;
    }

    // Store information on the background music that was played back
    mBgmInfo.bgmNo = bgmNo;

    return true;

}
    
    
/*!
  Stops the background music
  @param fadeFrame  Fade-out frame
 */
void SoundManager::stopBgm( int fadeFrame )
{
    if ( !isBgmActive() ) return;
    
    NNS_SndPlayerStopSeq( &mBgmInfo.sndHandle, fadeFrame );
    mBgmInfo.bgmNo = -1;
    mBgmInfo.pitch = 0;

    AUDIO_PRINT("侓 stop BGM\n");
}


/*!
  Changes the pitch of background music tracks
  @param trackBit  Bit mask of the tracks to change
  @param pitch  Amount to change (increasing or decreasing by 64 raises or lowers by a half step, respectively)
 */
void SoundManager::FChangeBgmTrackPitch( u16 trackBit, int pitch )
{
    if ( !isBgmActive() ) return;

    NNS_SndPlayerSetTrackPitch( &mBgmInfo.sndHandle, trackBit, pitch );
}


/*!
  Waveform playback
 */
void SoundManager::playWave( const void* waveBuf, int sampleNum )
{
    // Allocate a channel for playing waveforms
    mWaveOutHandle = NNS_SndWaveOutAllocChannel( WAVE_OUT_CH );
    if ( NNS_SND_WAVEOUT_INVALID_HANDLE == mWaveOutHandle ) {
        AUDIO_PRINT("侓 攇宍嵞惗梡僴儞僪儖偺妋曐偵幐攕偟傑偟偨\n");
    }

    // Waveform playback
    BOOL bb = NNS_SndWaveOutStart(
        mWaveOutHandle,            // Handle for waveform playback
        NNS_SND_WAVE_FORMAT_PCM16, // Waveform data format
        &waveBuf,                  // Waveform data
        FALSE,                     // Loop flag
        0,                         // Sample position to start looping
        sampleNum,                 // Number of samples
        16360,                     // Sampling rate
        127,                       // Volume
        32768,                     // Playback speed (speed/32768)
        64                         // Pan
        );
        

    // Free the waveform channel
    NNS_SndWaveOutFreeChannel( mWaveOutHandle );
}


/*!
  Rotates numbers between "from" and "to"
  @param val
  @param from  Lower range
  @param to   Upper range
  @return
 */
s32 SoundManager::clipGELT_S32( s32 val, s32 from, s32 to )
{
    s32 width = to - from;
    s32 ret = val;
    if( width < 0 )
    {
        s32 aTmp = from; from = to; to = aTmp; // swap
        width = to - from;
    }
        
    if( val < from ) ret += width;
    else if( val >= to ) ret -= width;
    return ret;
}


/*!
  Extracts values between "from" and "to"
  @param val
  @param from  Lower range
  @param to   Upper range
  @return  Values within the range are left unchanged. Values that spill out will be set to the lower and upper limits.
 */
s32 SoundManager::cutGELE_S32( s32 val, s32 from, s32 to )
{
    s32 ret;
    ret = val;
    if( val < from ) ret = from;
    else if( val > to ) ret = to;
    return ret;
}


/*!
  Returns an absolute value
  @param aa
  @return aa
 */
s32 SoundManager::abs_S32( s32 aa )
{
    return ( aa < 0 ) ? -aa : aa;
}


/*!
  Checks whether a value is within a range
  @param val  The value to check
  @param min  Lower range
  @param max  Upper range
  @return  TRUE if it fits and FALSE otherwise
 */
BOOL SoundManager::checkRangeGELT_S32( s32 val, s32 min, s32 max )
{
    return ( (val >= min) && (val < max) );
}




// Empty function for the callback base class
CallbackBase::~CallbackBase()
{
}

void CallbackBase::update()
{
}

void CallbackBase::startSe( int seNo )
{
#pragma unused( seNo )
}

void CallbackBase::setScrollSpec( SoundManager* sndmgr_p, int speed, u32 objId )
{
#pragma unused( sndmgr_p )    
#pragma unused( speed )    
#pragma unused( objId )    
}

}   // namespace m2d
}   // namespace ntmv
