/**
 ******************************************************************************
 * @file           : usbd_audio_if.c
 * @brief          : Generic media access Layer.
 ******************************************************************************
 *
 * Copyright (c) 2017 STMicroelectronics International N.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted, provided that the following conditions are met:
 *
 * 1. Redistribution of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of STMicroelectronics nor the names of other
 *    contributors to this software may be used to endorse or promote products
 *    derived from this software without specific written permission.
 * 4. This software, including modifications and/or derivative works of this
 *    software, must execute solely and exclusively on microcontroller or
 *    microprocessor devices manufactured by or for STMicroelectronics.
 * 5. Redistribution and use of this software other than as permitted under
 *    this license is void and will automatically terminate your rights under
 *    this license.
 *
 * THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY
 * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT
 * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************
 */

/* Includes ------------------------------------------------------------------*/
#include "usbd_audio_if.h"
/* USER CODE BEGIN INCLUDE */
#include "uhsdr_board.h"
#include "audio_driver.h"
/* USER CODE END INCLUDE */

/** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY
 * @{
 */

/** @defgroup USBD_AUDIO 
 * @brief usbd core module
 * @{
 */

/** @defgroup USBD_AUDIO_Private_TypesDefinitions
 * @{
 */
/* USER CODE BEGIN PRIVATE_TYPES */
/* USER CODE END PRIVATE_TYPES */ 
/**
 * @}
 */

/** @defgroup USBD_AUDIO_Private_Defines
 * @{
 */
/* USER CODE BEGIN PRIVATE_DEFINES */
/* USER CODE END PRIVATE_DEFINES */

/**
 * @}
 */

/** @defgroup USBD_AUDIO_Private_Macros
 * @{
 */
/* USER CODE BEGIN PRIVATE_MACRO */
/* USER CODE END PRIVATE_MACRO */

/**
 * @}
 */

/** @defgroup USBD_AUDIO_IF_Private_Variables
 * @{
 */
/* USER CODE BEGIN PRIVATE_VARIABLES */
/* USER CODE END PRIVATE_VARIABLES */

/**
 * @}
 */

/** @defgroup USBD_AUDIO_IF_Exported_Variables
 * @{
 */
extern USBD_HandleTypeDef hUsbDeviceFS;
/* USER CODE BEGIN EXPORTED_VARIABLES */
/* USER CODE END EXPORTED_VARIABLES */

/**
 * @}
 */

/** @defgroup USBD_AUDIO_Private_FunctionPrototypes
 * @{
 */
static int8_t  AUDIO_Init_FS         (uint32_t  AudioFreq, uint32_t Volume, uint32_t options);
static int8_t  AUDIO_DeInit_FS       (uint32_t options);
static int8_t  AUDIO_AudioCmd_FS     (uint8_t* pbuf, uint32_t size, uint8_t cmd);
static int8_t  AUDIO_VolumeCtl_FS    (uint8_t vol);
static int8_t  AUDIO_MuteCtl_FS      (uint8_t cmd);
static int8_t  AUDIO_PeriodicTC_FS   (uint8_t cmd);
static int8_t  AUDIO_GetState_FS     (void);
static int8_t  AUDIO_InVolumeCtl_FS  (uint8_t vol);


/* Audio Machine States */
typedef enum
{
    AUDIO_STATE_INACTIVE =           0x00,
    AUDIO_STATE_ACTIVE,
    AUDIO_STATE_PLAYING,
    AUDIO_STATE_PAUSED,
    AUDIO_STATE_STOPPED,
    AUDIO_STATE_ERROR
} USB_AUDIO_MACHINE_State_t;



/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */
static USB_AUDIO_MACHINE_State_t AudioState = AUDIO_STATE_INACTIVE;

typedef enum
{
    USB_DIG_AUDIO_OUT_INIT = 0,
    USB_DIG_AUDIO_OUT_IDLE,
    USB_DIG_ADUIO_OUT_TX,
} USB_AUDIO_State_t;

USB_AUDIO_State_t usb_dig_audio_state = USB_DIG_AUDIO_OUT_INIT;


/**
 * @}
 */

/** @defgroup usbd_audio_out_if_Private_Functions
 * @{
 */

#define USB_AUDIO_OUT_NUM_BUF 16
#define USB_AUDIO_OUT_PKT_SIZE   (AUDIO_OUT_PACKET/2)
#define USB_AUDIO_OUT_BUF_SIZE (USB_AUDIO_OUT_NUM_BUF * USB_AUDIO_OUT_PKT_SIZE)

static volatile int16_t out_buffer[USB_AUDIO_OUT_BUF_SIZE]; //buffer for filtered PCM data from Recv.
static volatile uint16_t out_buffer_tail;
static volatile uint16_t out_buffer_head;
static volatile uint16_t out_buffer_overflow;
static volatile uint16_t out_buffer_underflow;

static void audio_out_put_buffer(int16_t sample)
{

    uint32_t next_head = (out_buffer_head + 1) %USB_AUDIO_OUT_BUF_SIZE;

    if (next_head != out_buffer_head)
    {
        out_buffer[out_buffer_head] = sample;
        out_buffer_head = next_head;
    }
    else
    {
        // ok. We loose data now, should never ever happen, but so what
        // will cause minor distortion if only a few bytes.
        out_buffer_overflow++;
    }
}

uint16_t audio_out_buffer_fill()
{
    uint16_t temp_head = out_buffer_head;
    return ((((temp_head < out_buffer_tail)?USB_AUDIO_OUT_BUF_SIZE:0) + temp_head) - out_buffer_tail);
}

volatile int16_t* audio_out_buffer_next_pkt(uint32_t len)
{
    if (audio_out_buffer_fill() >= len)
    {
        return &out_buffer[out_buffer_tail];
    }
    else
    {

        return NULL;
    }
}
static void audio_out_buffer_pop_pkt(volatile int16_t* ptr, uint32_t len)
{
    if (ptr)
    {
        // there was data and pkt has been used
        // free  the space
        out_buffer_tail = (out_buffer_tail+len)%USB_AUDIO_OUT_BUF_SIZE;
    }
}

/* len is length in  stereo  samples */
void UsbdAudio_FillTxBuffer(AudioSample_t *buffer, uint32_t len)
{
    volatile int16_t *pkt = audio_out_buffer_next_pkt(2*len);

    static uint16_t fill_buffer = 1;
    if (fill_buffer == 0 && pkt)
    {
        for (uint32_t idx = len; idx; idx--)
        {
            // the purpose is to place the USB input exactly as the I2S does
            // which is weird if on F4 and 32 Bit transfers are done. (mixed endian)
            // for all other systems we scale 16bit USB audio to 32bit
            // on 16 bit audio nothing at  all happens here.
            buffer->l = I2S_Int16_2_AudioSample(*pkt++);
            buffer->r = I2S_Int16_2_AudioSample(*pkt++);
            buffer++;
        }
        audio_out_buffer_pop_pkt(pkt,2*len);
    }
    else
    {
        if (fill_buffer == 0)
        {
            out_buffer_underflow++;
            fill_buffer = 1;
        }
        if (audio_out_buffer_next_pkt((USB_AUDIO_OUT_BUF_SIZE*2)/3) != NULL)
        {
            fill_buffer = 0;
        }
        // Deliver silence if not enough data is stored in buffer
        for (uint32_t idx = len; idx; idx--)
        {
            buffer->l = 0;
            buffer->r = 0;
            buffer++;
        }
    }
}

/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */

/**
 * @}
 */

USBD_AUDIO_ItfTypeDef USBD_AUDIO_fops_FS = 
{
        AUDIO_Init_FS,
        AUDIO_DeInit_FS,
        AUDIO_AudioCmd_FS,
        AUDIO_VolumeCtl_FS,
        AUDIO_MuteCtl_FS,
        AUDIO_PeriodicTC_FS,
        AUDIO_GetState_FS,
        AUDIO_InVolumeCtl_FS,
};

/* Private functions ---------------------------------------------------------*/
/**
 * @brief  AUDIO_Init_FS
 *         Initializes the AUDIO media low layer over USB FS IP
 * @param  AudioFreq: Audio frequency used to play the audio stream.
 * @param  Volume: Initial volume level (from 0 (Mute) to 100 (Max))
 * @param  options: Reserved for future use
 * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
 */
static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)
{ 
    /* USER CODE BEGIN 0 */
    AudioState = AUDIO_STATE_ACTIVE;
    return (USBD_OK);
    /* USER CODE END 0 */
}

/**
 * @brief  AUDIO_DeInit_FS
 *         DeInitializes the AUDIO media low layer
 * @param  options: Reserved for future use
 * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
 */
static int8_t AUDIO_DeInit_FS(uint32_t options)
{
    /* USER CODE BEGIN 1 */
    AudioState = AUDIO_STATE_INACTIVE;
    return (USBD_OK);
    /* USER CODE END 1 */
}

/**
 * @brief  AUDIO_AudioCmd_FS
 *         Handles AUDIO command.
 * @param  pbuf: Pointer to buffer of data to be sent
 * @param  size: Number of data to be sent (in bytes)
 * @param  cmd: Command opcode
 * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
 */

static uint16_t fill;

static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)
{
    /* USER CODE BEGIN 2 */
    /* Check the current state */
    if ((AudioState == AUDIO_STATE_INACTIVE) || (AudioState == AUDIO_STATE_ERROR))
    {
        AudioState = AUDIO_STATE_ERROR;
        return USBD_FAIL;
    }

    switch (cmd)
    {
    /* Process the PLAY command ----------------------------*/
    case AUDIO_CMD_START:
    case AUDIO_CMD_PLAY:
        /* If current state is Active or Stopped */
        if ((AudioState == AUDIO_STATE_ACTIVE) || \
                (AudioState == AUDIO_STATE_STOPPED) || \
                (AudioState == AUDIO_STATE_PAUSED) || \
                (AudioState == AUDIO_STATE_PLAYING))
        {


            if ((ts.txrx_mode == TRX_MODE_RX && (ts.rx_iq_source == RX_IQ_DIG || ts.rx_iq_source == RX_IQ_DIGIQ))
                || (ts.txrx_mode == TRX_MODE_TX && (ts.tx_audio_source == TX_AUDIO_DIG || ts.tx_audio_source == TX_AUDIO_DIGIQ)))
            {
                uint16_t* pkt = (uint16_t*)pbuf;
                uint32_t count;
                static bool too_high = false;

                fill =  audio_out_buffer_fill();
                // USB_AUDIO_OUT_NUM_BUF * USB_AUDIO_OUT_PKT_SIZE
                bool is_low_space = fill > (3*(USB_AUDIO_OUT_NUM_BUF/4) * USB_AUDIO_OUT_PKT_SIZE);
                bool is_high_space = fill < (USB_AUDIO_OUT_NUM_BUF/4 * USB_AUDIO_OUT_PKT_SIZE);
                bool is_high_ok = fill > (3*(USB_AUDIO_OUT_NUM_BUF/4) * USB_AUDIO_OUT_PKT_SIZE);
                // bool is_low_ok = fill < ((USB_AUDIO_OUT_NUM_BUF/4) * USB_AUDIO_OUT_PKT_SIZE);

                uint16_t num_samples = size/2;

                if (is_high_ok)
                {
                    too_high = false;
                } else  {
                    too_high = is_high_space;
                }


                if (is_low_space)
                {
                    num_samples-=2;
                }
                for (count =0; count < num_samples; count++)
                {
                    audio_out_put_buffer(pkt[count]);
                }
                if (too_high)
                {
                    audio_out_put_buffer(pkt[num_samples-2]);
                    audio_out_put_buffer(pkt[num_samples-1]);
                }

            }
            AudioState = AUDIO_STATE_PLAYING;
            return USBD_OK;
        }
        else /* Not allowed command */
        {
            return USBD_FAIL;
        }
        break;

    /* Process the STOP command ----------------------------*/
    case AUDIO_CMD_STOP:
        if (AudioState != AUDIO_STATE_PLAYING)
        {
            /* Unsupported command */
            return USBD_FAIL;
        }
        else
        {
            AudioState = AUDIO_STATE_STOPPED;
            return USBD_OK;
        }

    /* Process the PAUSE command ---------------------------*/
    case AUDIO_CMD_PAUSE:
        if (AudioState != AUDIO_STATE_PLAYING)
        {
            /* Unsupported command */
            return USBD_FAIL;
        }
        else
        {
            AudioState = AUDIO_STATE_PAUSED;
            return USBD_OK;
        }

    /* Unsupported command ---------------------------------*/
    default:
        return USBD_FAIL;
    }
    return USBD_OK;
    /* USER CODE END 2 */

}

/**
 * @brief  AUDIO_VolumeCtl_FS
 *         Controls AUDIO Volume.
 * @param  vol: volume level (0..100)
 * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
 */
static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)
{
    /* USER CODE BEGIN 3 */
    uint16_t source = ts.tx_audio_source == TX_AUDIO_DIGIQ?TX_AUDIO_DIGIQ:TX_AUDIO_DIG;
    ts.tx_gain[source] = vol;
    return (USBD_OK);
    /* USER CODE END 3 */
}

/**
 * @brief  AUDIO_MuteCtl_FS
 *         Controls AUDIO Mute.
 * @param  cmd: command opcode
 * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
 */
static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)
{
    /* USER CODE BEGIN 4 */
    return (USBD_OK);
    /* USER CODE END 4 */
}

/**
 * @brief  AUDIO_PeriodicT_FS
 * @param  cmd: Command opcode
 * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
 */
static int8_t AUDIO_PeriodicTC_FS (uint8_t cmd)
{
    /* USER CODE BEGIN 5 */
    return (USBD_OK);
    /* USER CODE END 5 */
}

/**
 * @brief  AUDIO_GetState_FS
 *         Gets AUDIO State.
 * @param  None
 * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
 */
static int8_t AUDIO_GetState_FS (void)
{
    /* USER CODE BEGIN 6 */
    return (USBD_OK);
    /* USER CODE END 6 */
}

#if 0
/**
 * @brief  Manages the DMA full Transfer complete event.
 * @param  None
 * @retval None
 */
void TransferComplete_CallBack_FS(void)
{
    /* USER CODE BEGIN 7 */
    USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);
    /* USER CODE END 7 */
}

/**
 * @brief  Manages the DMA Half Transfer complete event.
 * @param  None
 * @retval None
 */
void HalfTransfer_CallBack_FS(void)
{ 
    /* USER CODE BEGIN 8 */
    USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);
    /* USER CODE END 8 */
}

/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */
/* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */

/**
 * @}
 */

/**
 * @}
 */
#endif

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
static int8_t  AUDIO_InVolumeCtl_FS    (uint8_t vol)
{
    /* Call low layer volume setting function */
    ts.rx_gain[RX_AUDIO_DIG].value = vol;
    return USBD_OK;
}

