/*******************************************************************************
* DISCLAIMER
* This software is supplied by Renesas Electronics Corporation and is only
* intended for use with Renesas products. No other uses are authorized. This
* software is owned by Renesas Electronics Corporation and is protected under
* all applicable laws, including copyright laws.
* THIS SOFTWARE IS PROVIDED "AS IS" AND RENESAS MAKES NO WARRANTIES REGARDING
* THIS SOFTWARE, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING BUT NOT
* LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NON-INFRINGEMENT. ALL SUCH WARRANTIES ARE EXPRESSLY DISCLAIMED.
* TO THE MAXIMUM EXTENT PERMITTED NOT PROHIBITED BY LAW, NEITHER RENESAS
* ELECTRONICS CORPORATION NOR ANY OF ITS AFFILIATED COMPANIES SHALL BE LIABLE
* FOR ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES FOR
* ANY REASON RELATED TO THIS SOFTWARE, EVEN IF RENESAS OR ITS AFFILIATES HAVE
* BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
* Renesas reserves the right, without notice, to make changes to this software
* and to discontinue the availability of this software. By using this software,
* you agree to the additional terms and conditions found by accessing the
* following link:
* http://www.renesas.com/disclaimer
* Copyright (C) 2014 Renesas Electronics Corporation. All rights reserved.
*******************************************************************************/
/*******************************************************************************
* File Name   : usb_audio_api.c
* Description : usb audio thread
*******************************************************************************/

/*******************************************************************************
Includes   <System Includes> , "Project Includes"
*******************************************************************************/
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "cmsis_os.h"
#include "r_typedefs.h"
#include "usb_hdrv_rza1.h"
#include "usb_haudio_rza1.h"
#include "ff.h"
#include "drv_scux.h"
#include "dec.h"
#include "usb_audio.h"

/*******************************************************************************
Typedef definitions
*******************************************************************************/


/*******************************************************************************
Macro definitions
*******************************************************************************/
#define MAX_MAILQ                   8u
#define INIT_DEV_ADDR               0xffffu
#define AOUT_1SEC                   1000u
#define USB_AUDIO_10MS_SAMPLES      (STRM_OUT_SAMPLE_RATE / (AOUT_1SEC / USB_HAUDIO_LINK_COUNT))
#define USB_AUDIO_BLOCK_SIZE        (USB_AUDIO_10MS_SAMPLES * DEC_CHANNEL_STEREO * DEC_4BYTE_PER_SAMPLE)
#define USB_HOST_DATA_LINKBUF_SIZE  (USB_AUDIO_BLOCK_SIZE * USB_HAUDIO_LINK_CHANGE)
#define MUTE_BUFF_SIZE              (USB_AUDIO_BLOCK_SIZE)

#define BUFF_SIZE_16BPS             (USB_AUDIO_10MS_SAMPLES * DEC_CHANNEL_STEREO * DEC_2BYTE_PER_SAMPLE)
#define BUFF_SIZE_24BPS             (USB_AUDIO_10MS_SAMPLES * DEC_CHANNEL_STEREO * DEC_3BYTE_PER_SAMPLE)
#define BUFF_SIZE_24BPS_WITH_PAD    (USB_AUDIO_10MS_SAMPLES * DEC_CHANNEL_STEREO * DEC_4BYTE_PER_SAMPLE)

#define SRC_OUT_BLOCK_SIZE          (USB_AUDIO_10MS_SAMPLES * DEC_CHANNEL_STEREO * DEC_4BYTE_PER_SAMPLE)
#define SRC_OUT_BUFF_SIZE           (SRC_OUT_BLOCK_SIZE * USB_HAUDIO_LINK_CHANGE)

/* Volume Down */
#define SHIFT_VOL                   (0)    /* Right-shifts the value worth PCM data to the specified */
#define SHIFT_BYTE                  (8)    /* Right-shifts 8 bits */
#define SHIFT_WORD                  (16)   /* Right-shifts 16 bits */

/* Audio out status */
#define AOUT_STS_WAIT_PLAY          ((uint32_t)0UL)
#define AOUT_STS_PLAYING            ((uint32_t)1UL)
#define AOUT_STS_WAIT_END           ((uint32_t)2UL)


/*#define DEBUG_PRINT*/
/*******************************************************************************
Imported global variables and functions (from other files)
*******************************************************************************/
extern int32_t SCUX_DmaScuxToOutBufContinue (void);
/*******************************************************************************
Exported global variables and functions (to be accessed by other files)
*******************************************************************************/
void USB_Audio_thread(void const *arg);

osThreadId usb_audio_thread_id;
osMailQId usb_audio_mailq_id;
osThreadDef(USB_Audio_thread, osPriorityRealtime, 1, 0);
osMailQDef(usb_audio_mailq, MAX_MAILQ, msg_audio_out_t);

uint8_t * AOUT_Get_SrcOutReadBuffInfo(uint32_t *buff_size);
uint8_t * AOUT_Get_SrcOutWriteBuffInfo(uint32_t *buff_size);
void AOUT_Set_SrcOutRp(void);

/*******************************************************************************
Private global variables and functions
*******************************************************************************/

static uint8_t  aout_audio_buff[USB_HOST_DATA_LINKBUF_SIZE]  __attribute__ ((section ("NC_DATA")));
static uint32_t aout_src_out_buff[SRC_OUT_BUFF_SIZE / sizeof(uint32_t)]  __attribute__ ((section ("NC_DATA")));
static uint8_t  aout_mute_buff[MUTE_BUFF_SIZE]  __attribute__ ((section ("NC_DATA")));

static bool_t aout_scux_to_mem_flg;
static bool_t aout_play_status;
static uint32_t aout_srcout_wp;
static uint32_t aout_srcout_rp;
static uint16_t aout_devadr;
static uint32_t aout_output_format;

static void aout_data_copy_from32_to32bit (int32_t * p_pcm_out, int32_t * p_pcm_in, uint32_t pcm_size);
static void aout_data_copy_from32_to24bit (int8_t * p_pcm_out, int8_t * p_pcm_in, uint32_t pcm_size);
static void aout_data_copy_from32_to16bit (int16_t * p_pcm_out, int32_t * p_pcm_in, uint32_t pcm_size);

/*******************************************************************************
* Function Name: USB_Audio_thread
* Description  : 
* Argument     : 
* Return Value : 
*******************************************************************************/
void USB_Audio_thread(void const *arg)
{
    int32_t  buff_index = 0;
    uint8_t *rp;
    uint32_t buf_size = BUFF_SIZE_24BPS;
    int32_t  ret;

    msg_audio_out_t *rptr;
    msg_audio_out_t msg;
    osEvent evt;
    uint32_t timeout = osWaitForever;
    uint32_t mute = MUTE_ON;

    aout_devadr = INIT_DEV_ADDR;
    aout_output_format = (uint32_t)USB_HAUDIO_24BPS;

    (void)memset(aout_mute_buff, 0x00, sizeof(aout_mute_buff));
    (void)memset(aout_audio_buff, 0x00, sizeof(aout_audio_buff));
    (void)memset(aout_src_out_buff, 0x00, sizeof(aout_src_out_buff));

    while (1)
    {
        /* ==== get event ==== */
        evt = osMailGet(usb_audio_mailq_id, timeout);
        if (evt.status == osEventMail)
        {
            rptr = evt.value.p;
            msg.event = rptr->event;
            msg.detail = rptr->detail;
            msg.param1 = rptr->param1;
            msg.param2 = rptr->param2;
            (void)osMailFree(usb_audio_mailq_id, rptr);

            switch (msg.event)
            {
            case AUDIO_START:
                timeout = 0;
                buff_index = 0;
                break;
            case AUDIO_STOP:
                timeout = osWaitForever;
                (void)usb0_api_haudio_stream_out_stop_link(aout_devadr);
                aout_devadr = INIT_DEV_ADDR;
                break;
            case AUDIO_MUTE:
                mute = msg.param1;
                break;
            case AUDIO_BUFF_FULL:
                aout_scux_to_mem_flg = true;
                if (aout_play_status == AOUT_STS_WAIT_PLAY)
                {
                    aout_play_status = AOUT_STS_PLAYING;
                    mute = MUTE_OFF;
                }
                break;
            case AUDIO_END_MUSIC:
                aout_play_status = AOUT_STS_WAIT_END;
                break;
            default:
                /* DO NOTHING */
                break;
            }
        }
        else if (evt.status == osOK)
        {
            if (aout_scux_to_mem_flg == true)
            {
                if(aout_srcout_rp != aout_srcout_wp)
                {
                    aout_scux_to_mem_flg = false;
                    (void)SCUX_DmaScuxToOutBufContinue ();
                }
            }
            else if ((aout_play_status == AOUT_STS_WAIT_END) && (aout_srcout_rp == aout_srcout_wp))
            {
                /* end music data */
                mute = MUTE_ON;
                aout_play_status = AOUT_STS_WAIT_PLAY;
                (void)DEC_End ();
            }
            else
            {
                /* DO NOTHING */
            }
            /* ==== timeout process ==== */
            /* wait trans music data */
            ret = usb0_api_haudio_stream_out_wait_link(aout_devadr, buff_index);
            if (ret == USB_HAUDIO_SUCCESS)
            {
                if (mute == MUTE_ON)
                {
                    /* set mute data */
                    if(aout_output_format == (uint32_t)USB_HAUDIO_24BPS)
                    {
                        buf_size = BUFF_SIZE_24BPS;
                    }
                    else if (aout_output_format == (uint32_t)USB_HAUDIO_24BPS_WITH_PAD)
                    {
                        buf_size = BUFF_SIZE_24BPS_WITH_PAD;
                    }
                    else if (aout_output_format == (uint32_t)USB_HAUDIO_16BPS)
                    {
                        buf_size = BUFF_SIZE_16BPS;
                    }
                    else
                    {
                        /* DO NOTHING */
                    }
                    (void)memcpy(&aout_audio_buff[buf_size * buff_index], aout_mute_buff, buf_size);
                }
                else
                {
                    /* set audio data */
                    rp = AOUT_Get_SrcOutReadBuffInfo((uint32_t *)NULL);

                    /* volume down audio data */
                    if (aout_output_format == (uint32_t)USB_HAUDIO_24BPS_WITH_PAD)
                    {
                        aout_data_copy_from32_to32bit (
                                (int32_t *)&aout_audio_buff[BUFF_SIZE_24BPS_WITH_PAD * buff_index],
                                (int32_t *)rp,
                                ((STRM_OUT_SAMPLE_RATE * USB_HAUDIO_LINK_COUNT) / (AOUT_1SEC)));
                    }
                    else if(aout_output_format == (uint32_t)USB_HAUDIO_24BPS)
                    {
                        aout_data_copy_from32_to24bit (
                                (int8_t *)&aout_audio_buff[BUFF_SIZE_24BPS * buff_index],
                                (int8_t *)rp,
                                ((STRM_OUT_SAMPLE_RATE * USB_HAUDIO_LINK_COUNT) / (AOUT_1SEC)));
                    }
                    else if (aout_output_format == (uint32_t)USB_HAUDIO_16BPS)
                    {
                        aout_data_copy_from32_to16bit (
                                (int16_t *)&aout_audio_buff[BUFF_SIZE_16BPS * buff_index],
                                (int32_t *)rp,
                                ((STRM_OUT_SAMPLE_RATE * USB_HAUDIO_LINK_COUNT) / (AOUT_1SEC)));
                    }
                    else
                    {
                        /* DO NOTHING */
                    }

                    (void)AOUT_Set_SrcOutRp();
                }
                buff_index++;
                if (buff_index == (uint32_t)USB_HAUDIO_LINK_CHANGE)
                {
                    buff_index = 0;
                }
                if((mute == MUTE_OFF) && (aout_srcout_rp == aout_srcout_wp) && (aout_play_status == AOUT_STS_PLAYING))
                {
                    (void)printf("AUDIO-OUT no sound data!\n");
                }
            }
            else
            {
                (void)printf("AUDIO-OUT stream-out wait Error.\n");
                (void)DEC_ErrStrm();
                (void)usb0_api_haudio_stream_out_stop_link(aout_devadr);
                timeout = osWaitForever;
                aout_devadr = INIT_DEV_ADDR;
            }
        }
        else
        {
            /* DO NOTHING */
        }
    }
}

/*******************************************************************************
* Function Name: AOUT_Set_Mute_Buff
* Description  : 
* Argument     : 
* Return Value : 
*******************************************************************************/
void AOUT_Set_Mute_Buff(void)
{
   (void)SCUX_DmaFixedToScuxSetQueue (aout_mute_buff, sizeof(aout_mute_buff));
   (void)SCUX_DmaFixedToScuxContinue ();
}

/*******************************************************************************
* Function Name: AOUT_Init_AudioOut
* Description  : 
* Argument     : 
* Return Value : 
*******************************************************************************/
void AOUT_Init_AudioOut(void)
{
    aout_scux_to_mem_flg = false;
    aout_play_status = AOUT_STS_WAIT_PLAY;
    aout_srcout_wp = 0;
    aout_srcout_rp = 0;

    (void)memset(&aout_src_out_buff[0], 0x00, sizeof(aout_src_out_buff));
}

/*******************************************************************************
* Function Name: AOUT_Create_usb_audio_thread
* Description  : 
* Argument     : 
* Return Value : 
*******************************************************************************/
int32_t AOUT_Create_usb_audio_thread(void)
{
    msg_audio_out_t *rptr;
    osEvent evt;
    int32_t ret;

    if (usb_audio_mailq_id == 0)
    {
        usb_audio_mailq_id = osMailCreate(osMailQ(usb_audio_mailq), NULL);
    }
    else
    {
        /* dummy read mail queue */
        do
        {
            evt = osMailGet(usb_audio_mailq_id, 0);
            if (evt.status == osEventMail)
            {
                rptr = evt.value.p;
                (void)osMailFree(usb_audio_mailq_id, rptr);
            }
        }
        while (evt.status == osEventMail);
    }
    usb_audio_thread_id = osThreadCreate(osThread(USB_Audio_thread),NULL);

    if ((usb_audio_thread_id != 0) && (usb_audio_mailq_id != 0))
    {
        ret = 0;
    }
    else
    {
        ret = -1;
    }
    return ret;
}

/*******************************************************************************
* Function Name: AOUT_Terminate_usb_audio_thread
* Description  : 
* Argument     : 
* Return Value : 
*******************************************************************************/
void AOUT_Terminate_usb_audio_thread(void)
{
    (void)usb0_api_haudio_stream_out_stop_link(aout_devadr);
    if (osThreadTerminate (usb_audio_thread_id) != osOK)
    {
        (void)printf("osThreadTerminate error\n");
    }
    else
    {
        usb_audio_thread_id = 0;
    }
}

/*******************************************************************************
* Function Name: AOUT_Set_SrcOutWp
* Description  : 
* Argument     : 
* Return Value : 
*******************************************************************************/
void AOUT_Set_SrcOutWp(void)
{
    aout_srcout_wp++;
    if (aout_srcout_wp == (uint32_t)USB_HAUDIO_LINK_CHANGE)
    {
        aout_srcout_wp = 0;
    }
}

/*******************************************************************************
* Function Name: AOUT_Set_SrcOutRp
* Description  : 
* Argument     : 
* Return Value : 
*******************************************************************************/
void AOUT_Set_SrcOutRp(void)
{
    aout_srcout_rp++;
    if (aout_srcout_rp == (uint32_t)USB_HAUDIO_LINK_CHANGE)
    {
        aout_srcout_rp = 0;
    }
}

/*******************************************************************************
* Function Name: AOUT_Get_SrcOutWriteBuffInfo
* Description  : 
* Argument     : 
* Return Value : 
*******************************************************************************/
uint8_t *AOUT_Get_SrcOutWriteBuffInfo(uint32_t *buff_size)
{
    uint32_t size = (uint32_t)SRC_OUT_BLOCK_SIZE;

    if (buff_size != NULL)
    {
        *buff_size = size;
    }
    return (uint8_t *)&aout_src_out_buff[(size / sizeof(uint32_t)) * aout_srcout_wp];
}


/*******************************************************************************
* Function Name: AOUT_Get_SrcOutReadBuffInfo
* Description  : 
* Argument     : 
* Return Value : 
*******************************************************************************/
uint8_t *AOUT_Get_SrcOutReadBuffInfo(uint32_t *buff_size)
{
    uint32_t size = (uint32_t)SRC_OUT_BLOCK_SIZE;

    if (buff_size != NULL)
    {
        *buff_size = size;
    }
    return (uint8_t *)&aout_src_out_buff[(size / sizeof(uint32_t)) * aout_srcout_rp];
}

/******************************************************************************
* Function Name: aout_data_copy_from32_to32bit
* Description  : 
* Arguments    : 
* Return Value : 
******************************************************************************/
static void aout_data_copy_from32_to32bit (int32_t * p_pcm_out, int32_t * p_pcm_in, uint32_t pcm_size)
{
    uint32_t pcm_loop;

    for (pcm_loop = pcm_size/2; pcm_loop > 0; pcm_loop--)
    {
        *p_pcm_out = *p_pcm_in >> SHIFT_VOL;    /* ch1 volume down */
        p_pcm_out++;
        p_pcm_in++;
        *p_pcm_out = *p_pcm_in >> SHIFT_VOL;    /* ch2 volume down */
        p_pcm_out++;
        p_pcm_in++;

        *p_pcm_out = *p_pcm_in >> SHIFT_VOL;    /* ch1 volume down */
        p_pcm_out++;
        p_pcm_in++;
        *p_pcm_out = *p_pcm_in >> SHIFT_VOL;    /* ch2 volume down */
        p_pcm_out++;
        p_pcm_in++;

    }
}

/******************************************************************************
* Function Name: aout_data_copy_from32_to24bit
* Description  : 
* Arguments    : 
* Return Value : 
******************************************************************************/
static void aout_data_copy_from32_to24bit (int8_t * p_pcm_out, int8_t * p_pcm_in, uint32_t pcm_size)
{
    uint32_t pcm_loop;
    int32_t *ptr;

    ptr = (int32_t *)p_pcm_in;

    for (pcm_loop = pcm_size/2; pcm_loop > 0; pcm_loop--)
    {
        /* ch1 volume down */
        *ptr++ >>= SHIFT_VOL;
        p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;

        /* ch2 volume down */
        *ptr++ >>= SHIFT_VOL;
        p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;

        /* ch1 volume down */
        *ptr++ >>= SHIFT_VOL;
        p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;

        /* ch2 volume down */
        *ptr++ >>= SHIFT_VOL;
        p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;
        *p_pcm_out++ = *p_pcm_in++;
    }
}

/******************************************************************************
* Function Name: aout_data_copy_from32_to16bit
* Description  : 
* Arguments    : 
* Return Value : 
******************************************************************************/
static void aout_data_copy_from32_to16bit (int16_t * p_pcm_out, int32_t * p_pcm_in, uint32_t pcm_size)
{
    uint32_t pcm_loop;

    for (pcm_loop = pcm_size/2; pcm_loop > 0; pcm_loop--)
    {
        *p_pcm_out = (int16_t)(*p_pcm_in >> (SHIFT_WORD + SHIFT_VOL));    /* ch1 volume down */
        p_pcm_out++;
        p_pcm_in++;
        *p_pcm_out = (int16_t)(*p_pcm_in >> (SHIFT_WORD + SHIFT_VOL));    /* ch2 volume down */
        p_pcm_out++;
        p_pcm_in++;

        *p_pcm_out = (int16_t)(*p_pcm_in >> (SHIFT_WORD + SHIFT_VOL));    /* ch1 volume down */
        p_pcm_out++;
        p_pcm_in++;
        *p_pcm_out = (int16_t)(*p_pcm_in >> (SHIFT_WORD + SHIFT_VOL));    /* ch2 volume down */
        p_pcm_out++;
        p_pcm_in++;
    }
}

/*******************************************************************************
* Function Name: AOUT_Init_stream
* Description  : 
* Argument     : 
* Return Value : 
*******************************************************************************/
int32_t AOUT_Init_stream (uint16_t devadr)
{
    int32_t ret = 0;
    int32_t usb_result;
    uint32_t output_format;

    (void)memset(aout_audio_buff, 0x00, sizeof(aout_audio_buff));
    aout_devadr = devadr;

    /* start usb stream out */
    usb_result = usb0_api_haudio_stream_out_start_link(aout_devadr,
                                                 (uint32_t)STRM_OUT_SAMPLE_RATE,
                                                 &aout_audio_buff[0],
                                                 sizeof(aout_audio_buff),
                                                 &output_format);
    if (usb_result == (int32_t)USB_HAUDIO_SUCCESS)
    {
        aout_output_format = output_format;
        usb_result = usb0_api_haudio_stream_out_link(aout_devadr);
    }
    if (usb_result != (int32_t)USB_HAUDIO_SUCCESS)
    {
        (void)usb0_api_haudio_stream_out_stop_link(aout_devadr);
        (void)printf("AUDIO-OUT stream-out Error.\n");
        ret = -1;
    }

    return ret;
}

/*******************************************************************************
* Function Name: AOUT_SndMsg
* Description  : 
* Argument     : 
* Return Value : 
*******************************************************************************/
int32_t AOUT_SndMsg (uint32_t event, int32_t detail, uint32_t param1, uint32_t param2, uint32_t timeout)
{
    int32_t    ret = -1;
    msg_audio_out_t *mptr;
    osStatus   os_status;

    if (usb_audio_mailq_id != 0)
    {
        mptr = osMailAlloc(usb_audio_mailq_id, timeout);
        if (mptr != NULL)
        {
            mptr->event = event;
            mptr->detail = detail;
            mptr->param1 = param1;
            mptr->param2 = param2;
            os_status = osMailPut(usb_audio_mailq_id, mptr);
            if (os_status == osOK)
            {
                ret = 0;
            }
            else
            {
                (void)printf("AOUT SndMsg error: osMailPut error %d\n",ret);
            }
        }
        else
        {
            /* DO NOTHING */
        }
    }
    else
    {
        /* DO NOTHING */
    }

    return ret;
}
/* End of File */
