/*---------------------------------------------------------------------------*
  Project:  TwlSDK - libraries - snd - common
  File:     snd_bank.c

  Copyright 2004-2008 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.

  $Date:: 2009-06-19#$
  $Rev: 10786 $
  $Author: okajima_manabu $
 *---------------------------------------------------------------------------*/

#include <nitro/snd/common/bank.h>

#ifndef SDK_FROM_TOOL

#include <nitro/misc.h>
#include <nitro/mi.h>
#include <nitro/snd/common/main.h>

#else

#define SDK_ASSERT(exp)           ((void) 0)
#define SDK_NULL_ASSERT(exp)           ((void) 0)
#define SDK_MINMAX_ASSERT(exp, min, max)           ((void) 0)

#define SNDi_LockMutex() ((void) 0)
#define SNDi_UnlockMutex() ((void) 0)

static u8 MI_ReadByte(const void *address)
{
    return *(u8 *)address;
}

#define HW_MAIN_MEM             0x02000000

#ifdef _MSC_VER
#pragma warning( disable : 4018 )      // Warning: Signed/unsigned mismatch
#pragma warning( disable : 4311 )      // Warning: Pointer truncation from 'type' to 'type'
#pragma warning( disable : 4312 )      // Warning: Conversion from 'type' to 'type' of greater size
#endif

#endif /* SDK_FROM_TOOL */

/******************************************************************************
	Static function declarations
 ******************************************************************************/
static const SNDWaveData *GetWaveData(const SNDBankData *bank, int waveArcNo, int waveIndex);

/******************************************************************************
	Public functions
 ******************************************************************************/

/*---------------------------------------------------------------------------*
  Name:         SND_AssignWaveArc

  Description:  Assigns a waveform archive to bank (ThreadSafe).

  Arguments:    bank    - Pointer to SNDBankData structure
                index   - Wave archive index
                waveArc - Pointer to SNDWaveArc structure

  Returns:      None.
 *---------------------------------------------------------------------------*/
void SND_AssignWaveArc(SNDBankData *bank, int index, SNDWaveArc *waveArc)
{
    SNDWaveArcLink *next;
    SNDWaveArcLink *prev;

    SDK_NULL_ASSERT(bank);
    SDK_NULL_ASSERT(waveArc);
#ifdef SDK_ARM9
    SDK_TASSERTMSG(((u32)bank & 0x1f) == 0, "bank address must be aligned 32 bytes boundary.");
    SDK_TASSERTMSG(((u32)waveArc & 0x1f) == 0, "waveArc address must be aligned 32 bytes boundary.");
#endif
    SDK_MINMAX_ASSERT(index, 0, SND_BANK_TO_WAVEARC_MAX - 1);

    SNDi_LockMutex();

    if (bank->waveArcLink[index].waveArc != NULL)
    {
        // When already connected

        // If it's the same as the waveform archive to which it is trying to connect, it does nothing
        if (waveArc == bank->waveArcLink[index].waveArc)
        {
            SNDi_UnlockMutex();
            return;
        }

        // If a different waveform archive is connected, it disconnects once
        if (&bank->waveArcLink[index] == bank->waveArcLink[index].waveArc->topLink)
        {
            bank->waveArcLink[index].waveArc->topLink = bank->waveArcLink[index].next;

#ifdef SDK_ARM9
            DC_StoreRange(bank->waveArcLink[index].waveArc, sizeof(SNDWaveArc));
#endif
        }
        else
        {
            prev = bank->waveArcLink[index].waveArc->topLink;
            while (prev != NULL)
            {
                if (&bank->waveArcLink[index] == prev->next)
                    break;
                prev = prev->next;
            }
            SDK_NULL_ASSERT(prev);
            prev->next = bank->waveArcLink[index].next;

#ifdef SDK_ARM9
            DC_StoreRange(prev, sizeof(SNDWaveArcLink));
#endif
        }
    }

    next = waveArc->topLink;
    waveArc->topLink = &bank->waveArcLink[index];
    bank->waveArcLink[index].next = next;
    bank->waveArcLink[index].waveArc = waveArc;

    SNDi_UnlockMutex();

#ifdef SDK_ARM9
    DC_StoreRange(bank, sizeof(SNDBankData));
    DC_StoreRange(waveArc, sizeof(SNDWaveArc));
#endif
}

/*---------------------------------------------------------------------------*
  Name:         SND_DestroyBank

  Description:  Destroys bank (ThreadSafe).

  Arguments:    bank    - Pointer to SNDBankData structure

  Returns:      None.
 *---------------------------------------------------------------------------*/
void SND_DestroyBank(SNDBankData *bank)
{
    SNDWaveArc *waveArc;
    SNDWaveArcLink *prev;
    int     i;

    SDK_NULL_ASSERT(bank);
#ifdef SDK_ARM9
    SDK_TASSERTMSG(((u32)bank & 0x1f) == 0, "bank address must be aligned 32 bytes boundary.");
#endif

    SNDi_LockMutex();

    for (i = 0; i < SND_BANK_TO_WAVEARC_MAX; i++)
    {
        waveArc = bank->waveArcLink[i].waveArc;
        if (waveArc == NULL)
            continue;

        if (&bank->waveArcLink[i] == waveArc->topLink)
        {
            waveArc->topLink = bank->waveArcLink[i].next;

#ifdef SDK_ARM9
            DC_StoreRange(waveArc, sizeof(SNDWaveArc));
#endif
        }
        else
        {
            prev = waveArc->topLink;
            while (prev != NULL)
            {
                if (&bank->waveArcLink[i] == prev->next)
                    break;
                prev = prev->next;
            }
            SDK_NULL_ASSERT(prev);
            prev->next = bank->waveArcLink[i].next;

#ifdef SDK_ARM9
            DC_StoreRange(prev, sizeof(SNDWaveArcLink));
#endif
        }
    }

    SNDi_UnlockMutex();
}

/*---------------------------------------------------------------------------*
  Name:         SND_DestroyWaveArc

  Description:  Destroys the waveform archive (ThreadSafe).

  Arguments:    waveArc - Pointer to SNDWaveArc structure

  Returns:      None.
 *---------------------------------------------------------------------------*/
void SND_DestroyWaveArc(SNDWaveArc *waveArc)
{
    SNDWaveArcLink *link;
    SNDWaveArcLink *next;

    SDK_NULL_ASSERT(waveArc);
#ifdef SDK_ARM9
    SDK_TASSERTMSG(((u32)waveArc & 0x1f) == 0, "waveArc address must be aligned 32 bytes boundary.");
#endif

    SNDi_LockMutex();

    link = waveArc->topLink;
    while (link != NULL)
    {
        next = link->next;

        link->waveArc = NULL;
        link->next = NULL;

#ifdef SDK_ARM9
        DC_StoreRange(link, sizeof(SNDWaveArcLink));
#endif
        link = next;
    }

    SNDi_UnlockMutex();
}


/*---------------------------------------------------------------------------*
  Name:         SND_ReadInstData

  Description:  Gets instrument parameters from bank (ThreadSafe).

  Arguments:    bank    - Pointer to SNDBankData structure
                prgNo - Program number
                key - Note key
                inst - Pointer to SNDInstData structure

  Returns:      Whether it was successful.
 *---------------------------------------------------------------------------*/
BOOL SND_ReadInstData(const SNDBankData *bank, int prgNo, int key, SNDInstData *inst)
{
    u32     instOffset;

    SDK_NULL_ASSERT(bank);
    SDK_NULL_ASSERT(inst);

    if (prgNo < 0)
        return FALSE;

#ifdef SDK_FROM_TOOL
    if (bank->fileHeader.signature[0] == 'S' &&
        bank->fileHeader.signature[1] == 'B' &&
        bank->fileHeader.signature[2] == 'C' && bank->fileHeader.signature[3] == 'B')
    {
        const SNDBankDataCallback *bankcb = (const SNDBankDataCallback *)bank;
        return bankcb->readInstDataFunc(bankcb, prgNo, key, inst);
    }
#endif

    SNDi_LockMutex();

    if (prgNo >= bank->instCount)
    {                                  // Note: Read from MainMemory
        SNDi_UnlockMutex();
        return FALSE;
    }

    instOffset = bank->instOffset[prgNo];       // Note: Read from MainMemory
    inst->type = (u8)(instOffset & 0xff);
    instOffset >>= 8;

    // Process for each instrument type
    switch (inst->type)
    {
    case SND_INST_PCM:
    case SND_INST_PSG:
    case SND_INST_NOISE:
    case SND_INST_DIRECTPCM:
    case SND_INST_NULL:
        {
            const SNDInstParam *param = (const SNDInstParam *)((u8 *)bank + instOffset);

            inst->param = *param;      // Structure copy
            break;
        }

    case SND_INST_DRUM_SET:
        {
            const SNDDrumSet *drumSet = (const SNDDrumSet *)((u8 *)bank + instOffset);
            u8      min = MI_ReadByte(&drumSet->min);
            u8      max = MI_ReadByte(&drumSet->max);

            if (key < min || key > max)
            {
                SNDi_UnlockMutex();
                return FALSE;
            }

            *inst = drumSet->instOffset[key - min];     // Structure copy
            break;
        }

    case SND_INST_KEY_SPLIT:
        {
            int     index = 0;
            const SNDKeySplit *keySplit = (const SNDKeySplit *)((u8 *)bank + instOffset);

            while (key > MI_ReadByte(&keySplit->key[index]))
            {
                index++;
                if (index >= SND_INST_KEYSPLIT_MAX)
                {
                    SNDi_UnlockMutex();
                    return FALSE;
                }
            }

            *inst = keySplit->instOffset[index];        // Structure copy
            break;
        }

    case SND_INST_INVALID:
    default:
        SNDi_UnlockMutex();
        return FALSE;
    }

    SNDi_UnlockMutex();

    return TRUE;
}

/*---------------------------------------------------------------------------*
  Name:         SND_WriteInstData

  Description:  Writes instrument data (ThreadSafe).

  Arguments:    bank    - Pointer to SNDBankData structure
                prgNo - Program number
                key - Note key
                inst - Pointer to SNDInstData structure

  Returns:      Whether it was successful.
 *---------------------------------------------------------------------------*/
BOOL SND_WriteInstData(SNDBankData *bank, int prgNo, int key, const struct SNDInstData *inst)
{
    u32     instOffset;
    u8      type;

    SDK_NULL_ASSERT(bank);
    SDK_NULL_ASSERT(inst);

    if (prgNo < 0)
        return FALSE;

    SNDi_LockMutex();
    if (prgNo >= bank->instCount)
    {                                  // Note: Read from MainMemory
        SNDi_UnlockMutex();
        return FALSE;
    }

    instOffset = bank->instOffset[prgNo];       // Note: Read from MainMemory
    type = (u8)(instOffset & 0xff);
    instOffset >>= 8;

    // Process for each instrument type
    switch (type)
    {
    case SND_INST_PCM:
    case SND_INST_PSG:
    case SND_INST_NOISE:
    case SND_INST_DIRECTPCM:
    case SND_INST_NULL:
        {
            SNDInstParam *param = (SNDInstParam *)((u8 *)bank + instOffset);

            bank->instOffset[prgNo] = (instOffset << 8) | inst->type;
            *param = inst->param;      // Structure copy
            break;
        }

    case SND_INST_DRUM_SET:
        {
            SNDDrumSet *drumSet = (SNDDrumSet *)((u8 *)bank + instOffset);
            u8      min = MI_ReadByte(&drumSet->min);
            u8      max = MI_ReadByte(&drumSet->max);

            if (key < min || key > max)
            {
                SNDi_UnlockMutex();
                return FALSE;
            }

            drumSet->instOffset[key - min] = *inst;     // Structure copy
            break;
        }

    case SND_INST_KEY_SPLIT:
        {
            int     index = 0;
            SNDKeySplit *keySplit = (SNDKeySplit *)((u8 *)bank + instOffset);

            while (key > MI_ReadByte(&keySplit->key[index]))
            {
                index++;
                if (index >= SND_INST_KEYSPLIT_MAX)
                {
                    SNDi_UnlockMutex();
                    return FALSE;
                }
            }

            keySplit->instOffset[index] = *inst;        // Structure copy
            break;
        }

    case SND_INST_INVALID:
    default:
        SNDi_UnlockMutex();
        return FALSE;
    }

    SNDi_UnlockMutex();

#ifdef SDK_ARM9
    DC_StoreRange(bank, bank->fileHeader.fileSize);
#endif

    return TRUE;
}

/*---------------------------------------------------------------------------*
  Name:         SND_GetFirstInstDataPos

  Description:  Gets SNDInstPos for instrument data scanning.

  Arguments:    bank - Pointer to bank data

  Returns:      SNDInstPos structure.
 *---------------------------------------------------------------------------*/
SNDInstPos SND_GetFirstInstDataPos(const SNDBankData *bank)
{
    SNDInstPos pos;

    SDK_NULL_ASSERT(bank);

    (void)bank;

    pos.prgNo = 0;
    pos.index = 0;

    return pos;
}

/*---------------------------------------------------------------------------*
  Name:         SND_GetNextInstData

  Description:  Gets the next instrument data.

  Arguments:    bank - Pointer to bank data
                inst - Pointer that stores the obtained instrument data
                pos - Pointer to the SNDInstPos structure

  Returns:      Whether successfully obtained.
 *---------------------------------------------------------------------------*/
BOOL SND_GetNextInstData(const SNDBankData *bank, SNDInstData *inst, SNDInstPos *pos)
{
    SDK_NULL_ASSERT(bank);
    SDK_NULL_ASSERT(inst);
    SDK_NULL_ASSERT(pos);

    for (; pos->prgNo < bank->instCount; pos->prgNo++, pos->index = 0)
    {
        u32     instOffset = bank->instOffset[pos->prgNo];
        inst->type = (u8)(instOffset & 0xff);
        instOffset >>= 8;

        switch (inst->type)
        {
        case SND_INST_PCM:
        case SND_INST_PSG:
        case SND_INST_NOISE:
        case SND_INST_DIRECTPCM:
        case SND_INST_NULL:
            {
                const SNDInstParam *param = (const SNDInstParam *)((u8 *)bank + instOffset);
                inst->param = *param;
                pos->prgNo++;
                return TRUE;
            }

        case SND_INST_DRUM_SET:
            {
                const SNDDrumSet *drumSet = (const SNDDrumSet *)((u8 *)bank + instOffset);
                for (; pos->index < drumSet->max - drumSet->min + 1; pos->index++)
                {
                    *inst = drumSet->instOffset[pos->index];
                    pos->index++;
                    return TRUE;
                }
                break;
            }

        case SND_INST_KEY_SPLIT:
            {
                const SNDKeySplit *keySplit = (const SNDKeySplit *)((u8 *)bank + instOffset);
                for (; pos->index < SND_INST_KEYSPLIT_MAX; pos->index++)
                {
                    if (keySplit->key[pos->index] == 0)
                        break;
                    *inst = keySplit->instOffset[pos->index];
                    pos->index++;
                    return TRUE;
                }
                break;
            }

        default:
            break;
        }
    }

    return FALSE;
}

/*---------------------------------------------------------------------------*
  Name:         SND_GetWaveDataCount

  Description:  Gets the number of waveform data in the waveform archive.

  Arguments:    waveArc - Pointer to the waveform archive

  Returns:      Number of waveform data.
 *---------------------------------------------------------------------------*/
u32 SND_GetWaveDataCount(const struct SNDWaveArc *waveArc)
{
    SDK_NULL_ASSERT(waveArc);

    return waveArc->waveCount;
}

/*---------------------------------------------------------------------------*
  Name:         SND_SetWaveDataAddress

  Description:  Registers waveform data to the waveform archive.

  Arguments:    waveArc - Pointer to the waveform archive
                index - Index number of waveform data
                address - Address of waveform data

  Returns:      None.
 *---------------------------------------------------------------------------*/
void SND_SetWaveDataAddress(SNDWaveArc *waveArc, int index, const SNDWaveData *address)
{
    SDK_NULL_ASSERT(waveArc);
    SDK_MINMAX_ASSERT(index, 0, waveArc->waveCount);
    SDK_ASSERT(address == NULL || address >= (void *)HW_MAIN_MEM);

    SNDi_LockMutex();

    waveArc->waveOffset[index] = (u32)address;

#ifdef SDK_ARM9
    DC_StoreRange(&waveArc->waveOffset[index], sizeof(u32));
#endif

    SNDi_UnlockMutex();
}

/*---------------------------------------------------------------------------*
  Name:         SND_GetWaveDataAddress

  Description:  Gets the waveform data registered in the waveform archive.

  Arguments:    waveArc - Pointer to the waveform archive
                index - Index number of waveform data

  Returns:      Pointer to the obtained waveform data.
 *---------------------------------------------------------------------------*/
const SNDWaveData *SND_GetWaveDataAddress(const SNDWaveArc *waveArc, int index)
{
    const SNDWaveData *wave;
    u32     offset;

    SDK_NULL_ASSERT(waveArc);
    SDK_MINMAX_ASSERT(index, 0, waveArc->waveCount);

    SNDi_LockMutex();

    offset = waveArc->waveOffset[index];

    if (offset != 0)
    {
        if (offset < HW_MAIN_MEM)
            wave = (const SNDWaveData *)((u8 *)waveArc + offset);
        else
            wave = (const SNDWaveData *)offset;
    }
    else
    {
        wave = NULL;
    }

    SNDi_UnlockMutex();

    return wave;
}

//-----------------------------------------------------------------------------
// ARM7 only

#ifdef SDK_ARM7

/*---------------------------------------------------------------------------*
  Name:         SND_NoteOn

  Description:  Performs note-on using the bank.

  Arguments:    

  Returns:      Pointer to SNDChannel structure.
 *---------------------------------------------------------------------------*/
BOOL SND_NoteOn(SNDExChannel *ch_p,
                int key, int velocity, s32 length, const SNDBankData *bank, const SNDInstData *inst)
{
    const SNDWaveData *wave_data;
    int     release;
    BOOL    result;

    SDK_NULL_ASSERT(ch_p);
    SDK_MINMAX_ASSERT(key, 0, 127);
    SDK_MINMAX_ASSERT(velocity, 0, 127);
    SDK_NULL_ASSERT(inst);

    release = inst->param.release;
    if (inst->param.release == SND_BANK_DISABLE_RELEASE)
    {
        length = -1;
        release = 0;
    }

    switch (inst->type)
    {
    case SND_INST_PCM:
    case SND_INST_DIRECTPCM:
        if (inst->type == SND_INST_PCM)
        {
            wave_data = GetWaveData(bank, inst->param.wave[1], inst->param.wave[0]);
        }
        else
        {
            wave_data = (const SNDWaveData *)(inst->param.wave[1] << 16 | inst->param.wave[0]);
        }

        if (wave_data != NULL)
        {
            result = SND_StartExChannelPcm(ch_p, &wave_data->param, wave_data->samples, length);
        }
        else
        {
            result = FALSE;
        }
        break;

    case SND_INST_PSG:
        result = SND_StartExChannelPsg(ch_p, (SNDDuty)inst->param.wave[0], length);
        break;

    case SND_INST_NOISE:
        result = SND_StartExChannelNoise(ch_p, length);
        break;

    default:
        result = FALSE;
        break;
    }

    if (!result)
        return FALSE;

    ch_p->key = (u8)key;
    ch_p->original_key = inst->param.original_key;
    ch_p->velocity = (u8)velocity;

    SND_SetExChannelAttack(ch_p, inst->param.attack);
    SND_SetExChannelDecay(ch_p, inst->param.decay);
    SND_SetExChannelSustain(ch_p, inst->param.sustain);
    SND_SetExChannelRelease(ch_p, release);

    ch_p->init_pan = (s8)(inst->param.pan - 64);

    return TRUE;
}

#endif

/******************************************************************************
	Static Functions
 ******************************************************************************/

//-----------------------------------------------------------------------------
// ARM7 only

#ifdef SDK_ARM7

/*---------------------------------------------------------------------------*
  Name:         GetWaveData

  Description:  Gets waveform data from the waveform archive.

  Arguments:    bank - Pointer to SNDBankData
                waveArcNo - Wave archive number
                waveIndex - Wave index

  Returns:      Pointer to SNDWaveData structure.
 *---------------------------------------------------------------------------*/
static const SNDWaveData *GetWaveData(const SNDBankData *bank, int waveArcNo, int waveIndex)
{
    const SNDWaveArc *arc;

    SDK_MINMAX_ASSERT(waveArcNo, 0, SND_BANK_TO_WAVEARC_MAX - 1);

    arc = bank->waveArcLink[waveArcNo].waveArc;

    if (arc == NULL)
        return NULL;
    if (waveIndex >= arc->waveCount)
        return NULL;

    return SND_GetWaveDataAddress(arc, waveIndex);
}

#endif

/*====== End of snd_bank.c ======*/
