//*****************************************************************************
//
//! @file opus_recorder.c
//!
//! @brief Example to stream raw and/or opus encoded data to PC .
//!
//! AmU2S or RTT can be used.
//!
//*****************************************************************************

//*****************************************************************************
//
// Copyright (c) 2019, Ambiq Micro
// 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. Redistributions 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 the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// Third party software included in this distribution is subject to the
// additional license terms as defined in the /docs/licenses directory.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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.
//
// This is part of revision v2.2.0-7-g63f7c2ba1 of the AmbiqSuite Development Package.
//
//*****************************************************************************
#include <string.h>
#include "am_mcu_apollo.h"
#include "am_bsp.h"
#include "am_util.h"
#include "SEGGER_RTT.h"
#include "amu2s.h"
#include "ae_api.h"

//*****************************************************************************
//
// Options
//
//*****************************************************************************
#define PRINT_UART          0
#define USE_OPUS            1

//*****************************************************************************
//
// Cache configuration
//
//*****************************************************************************
const am_hal_cachectrl_config_t am_hal_cachectrl_benchmark =
{
    .bLRU                       = 0,
    .eDescript                  = AM_HAL_CACHECTRL_DESCR_1WAY_128B_512E,
    .eMode                      = AM_HAL_CACHECTRL_CONFIG_MODE_INSTR,
};

#define RTT_LOGGER_BUFFER_LENGTH_BYTE   (128*1024)
uint8_t ui8RttLoggerBuffer[RTT_LOGGER_BUFFER_LENGTH_BYTE];

#define AUDIO_FRAME_MS                  20

#define AUDIO_FRAME_SIZE_SAMPLES        ((16000)/(1000/AUDIO_FRAME_MS))
#define AUDIO_FRAME_SIZE_MONO_BYTES     (AUDIO_FRAME_SIZE_SAMPLES*2)
#define AUDIO_FRAME_SIZE_STEREO_BYTES   (AUDIO_FRAME_SIZE_SAMPLES*4)

#define ENCODED_HEADER_SIZE_BYITES      8
#define ENCODED_COMPRESS_RATE           8
#define ENCODED_FRAME_SIZE_BYTES        ((AUDIO_FRAME_SIZE_SAMPLES*2/ENCODED_COMPRESS_RATE) + ENCODED_HEADER_SIZE_BYITES)


uint8_t g_opusAudioInputBuffer[AUDIO_FRAME_SIZE_MONO_BYTES]; // 20ms mono raw audio frame
bool g_opusInputReadyFlag = false;

#if USE_OPUS
uint8_t g_opusAudioOutputBuffer[ENCODED_FRAME_SIZE_BYTES]; // encoded audio frame, with header
uint16_t g_opusInputIndex = 0;
#endif // #if USE_OPUS

//*****************************************************************************
//
// PDM configuration information.
//
//*****************************************************************************
void *PDMHandle = NULL;

#define PDM_CLK                     12
#define PDM_CLK_PIN_CFG             AM_HAL_PIN_12_PDMCLK
#define PDM_DATA                    11
#define PDM_DATA_PIN_CFG            AM_HAL_PIN_11_PDMDATA

am_hal_pdm_config_t g_sPdmConfig =
{
    .eClkDivider = AM_HAL_PDM_MCLKDIV_1,
    .eLeftGain = AM_HAL_PDM_GAIN_P105DB,
    .eRightGain = AM_HAL_PDM_GAIN_P105DB,
    .ui32DecimationRate = 48,     // CLK 1.5 Mhz
    .bHighPassEnable = 0, //
    .ui32HighPassCutoff = 0xB,
    .ePDMClkSpeed = AM_HAL_PDM_CLK_1_5MHZ,
    .bInvertI2SBCLK = 0,
    .ePDMClkSource = AM_HAL_PDM_INTERNAL_CLK,
    .bPDMSampleDelay = 0,
    .bDataPacking = 1,
    .ePCMChannels = AM_HAL_PDM_CHANNEL_LEFT,
    .bLRSwap = 0,
    .bSoftMute = 0,
};

//*****************************************************************************
//
// Start a transaction to get some number of bytes from the PDM interface.
//
//*****************************************************************************
uint32_t g_ui32PDMDataBuffer[AUDIO_FRAME_SIZE_MONO_BYTES/4];
void
pdm_trigger_dma(void)
{
    //
    // Configure DMA and target address.
    //
    am_hal_pdm_transfer_t sTransfer;
    sTransfer.ui32TargetAddr = (uint32_t ) g_ui32PDMDataBuffer;
    sTransfer.ui32TotalCount = (AUDIO_FRAME_SIZE_MONO_BYTES);

    //
    // Start the data transfer.
    //
    am_hal_pdm_dma_start(PDMHandle, &sTransfer);
}

//*****************************************************************************
//
// Structure for handling PDM register state information for power up/down
//
//*****************************************************************************
typedef struct
{
    bool bValid;
}
am_hal_pdm_register_state_t;

//*****************************************************************************
//
// Structure for handling PDM HAL state information.
//
//*****************************************************************************
typedef struct
{
    am_hal_handle_prefix_t prefix;
    am_hal_pdm_register_state_t sRegState;
    uint32_t ui32Module;
}
am_hal_pdm_state_t;

void am_app_KWD_pdm_dma_disable(void *pHandle)
{
    am_hal_pdm_state_t *pState = (am_hal_pdm_state_t *) pHandle;
    uint32_t ui32Module = pState->ui32Module;

    //
    // Disable DMA
    //
    PDMn(ui32Module)->DMACFG_b.DMAEN = PDM_DMACFG_DMAEN_DIS;

}

//-----------------------------------------------------------------------------
// METHOD:  PDM_Init
// PURPOSE: PDM module configuration
//-----------------------------------------------------------------------------
void PDMInit(void)
{

    if (PDMHandle != NULL)
        return;
    //
    // Initialize, power-up, and configure the PDM.
    //
    am_hal_pdm_initialize(0, &PDMHandle);
    //
    // Configure the necessary pins.
    //
    am_hal_gpio_pincfg_t sPinCfg = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    sPinCfg.uFuncSel = PDM_CLK_PIN_CFG;
    am_hal_gpio_pinconfig(PDM_CLK, sPinCfg);

    sPinCfg.uFuncSel = PDM_DATA_PIN_CFG;
    am_hal_gpio_pinconfig(PDM_DATA, sPinCfg);

    am_hal_pdm_power_control(PDMHandle, AM_HAL_PDM_POWER_ON, false);
    am_hal_pdm_configure(PDMHandle, &g_sPdmConfig);
    am_hal_pdm_fifo_flush(PDMHandle);

    //
    // Configure and enable PDM interrupts (set up to trigger on DMA
    // completion).
    //
    am_hal_pdm_interrupt_enable(PDMHandle, (AM_HAL_PDM_INT_DERR
                                            | AM_HAL_PDM_INT_DCMP
                                            | AM_HAL_PDM_INT_UNDFL
                                            | AM_HAL_PDM_INT_OVF));
    NVIC_EnableIRQ(PDM_IRQn);
}

void PDMDeInit()
{
    if(PDMHandle == NULL)
      return;

    am_hal_pdm_interrupt_clear(PDMHandle, (AM_HAL_PDM_INT_DERR
                                            | AM_HAL_PDM_INT_DCMP
                                            | AM_HAL_PDM_INT_UNDFL
                                            | AM_HAL_PDM_INT_OVF));

    am_hal_pdm_interrupt_disable(PDMHandle, (AM_HAL_PDM_INT_DERR
                                            | AM_HAL_PDM_INT_DCMP
                                            | AM_HAL_PDM_INT_UNDFL
                                            | AM_HAL_PDM_INT_OVF));

    NVIC_DisableIRQ(PDM_IRQn);

    am_hal_gpio_pinconfig(PDM_CLK, g_AM_HAL_GPIO_DISABLE);
    am_hal_gpio_pinconfig(PDM_DATA, g_AM_HAL_GPIO_DISABLE);

    // This was a workaround code to measure power consumption. Need to review!!
    am_app_KWD_pdm_dma_disable(PDMHandle);

    am_hal_pdm_disable(PDMHandle);
    am_hal_pdm_power_control(PDMHandle, AM_HAL_PDM_POWER_OFF, false);

    am_hal_pdm_deinitialize(PDMHandle);
    PDMHandle = NULL;

}

void am_pdm0_isr(void)
{
    uint32_t ui32Status;

    //
    // Read the interrupt status.
    //
    am_hal_pdm_interrupt_status_get(PDMHandle, &ui32Status, true);
    am_hal_pdm_interrupt_clear(PDMHandle, ui32Status);

    //
    // Once our DMA transaction completes, we will disable the PDM and send a
    // flag back down to the main routine. Disabling the PDM is only necessary
    // because this example only implemented a single buffer for storing FFT
    // data. More complex programs could use a system of multiple buffers to
    // allow the CPU to run the FFT in one buffer while the DMA pulls PCM data
    // into another buffer.
    //
    if (ui32Status & AM_HAL_PDM_INT_DCMP)
    {
        // trigger next traction
        PDMn(0)->DMATOTCOUNT = AUDIO_FRAME_SIZE_MONO_BYTES;  // FIFO unit in bytes

        amu2s_send(Amu2s_pcm, g_ui32PDMDataBuffer, AMU2S_PCM_BYTES);

#if USE_OPUS
        //
        // move data to opus buffer
        //
        memmove(g_opusAudioInputBuffer, g_ui32PDMDataBuffer,AUDIO_FRAME_SIZE_MONO_BYTES);
        g_opusInputReadyFlag = true;
#endif // #if USE_OPUS
    }
    else if(ui32Status & (AM_HAL_PDM_INT_UNDFL | AM_HAL_PDM_INT_OVF))
    {
        am_hal_pdm_fifo_flush(PDMHandle);
    }

}

#define AM_CRITICAL_BEGIN_VOS                                               \
    if ( 1 )                                                                \
    {                                                                       \
        volatile uint32_t ui32Primask_04172010;                             \
        ui32Primask_04172010 = am_hal_interrupt_master_disable();

#define AM_CRITICAL_END_VOS                                                 \
        am_hal_interrupt_master_set(ui32Primask_04172010);                  \
    }

//*****************************************************************************
//
// Main Function.
//
//*****************************************************************************
int
main(void)
{
    //
    // Set the clock frequency.
    //
    am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_SYSCLK_MAX, 0);

    //
    // Set the default cache configuration
    //
    am_hal_cachectrl_config(&am_hal_cachectrl_benchmark);
    am_hal_cachectrl_enable();

    //
    // Configure the board for low power operation.
    //
    am_bsp_low_power_init();

#if (PRINT_UART == 1)
    am_bsp_uart_printf_enable();

    //
    // Clear the terminal and print the banner.
    //
    am_util_stdio_terminal_clear();
    am_util_stdio_printf("Ambiq Micro 'while' example.\n\n");

    //
    // Brief description
    //
    am_util_stdio_printf("Used for measuring power in an infinite while loop.\n");

    //
    // Print the compiler version.
    //
    am_util_stdio_printf("App Compiler:    %s\n", COMPILER_VERSION);
    am_util_stdio_printf("HAL Compiler:    %s\n", g_ui8HALcompiler);
    am_util_stdio_printf("HAL SDK version: %d.%d.%d\n",
                         g_ui32HALversion.s.Major,
                         g_ui32HALversion.s.Minor,
                         g_ui32HALversion.s.Revision);
    am_util_stdio_printf("HAL compiled with %s-style registers\n",
                         g_ui32HALversion.s.bAMREGS ? "AM_REG" : "CMSIS");


#endif // PRINT_UART

    amu2s_init();
    PDMInit();
    am_hal_pdm_enable(PDMHandle);
    pdm_trigger_dma();

    SEGGER_RTT_ConfigUpBuffer(1, "TestDataLogger", ui8RttLoggerBuffer, RTT_LOGGER_BUFFER_LENGTH_BYTE, SEGGER_RTT_MODE_NO_BLOCK_SKIP);

#if USE_OPUS
    /* initialize the audio encoder */
    audio_enc_init(true);       // true for with header
#endif // #if USE_OPUS

    // master interrupt enable
    am_hal_interrupt_master_enable();

    while(1)
    {

#if USE_OPUS
        if(g_opusInputReadyFlag == true)
        {
            g_opusInputReadyFlag = false;
            // data ready to encode, run encoder

            uint32_t encoded_bytes = audio_enc_encode_frame((short*)g_opusAudioInputBuffer, AUDIO_FRAME_SIZE_SAMPLES, (unsigned char*)g_opusAudioOutputBuffer);

            // send to RTT
            SEGGER_RTT_Write(1, (uint8_t*)g_opusAudioOutputBuffer, encoded_bytes);

            // send to AmU2S
            amu2s_send(Amu2s_opus, g_opusAudioOutputBuffer, AMU2S_OPUS_BYTES);

#endif // #if USE_OPUS
        }
    }
}
