//*****************************************************************************
//
// audio_playback_internal_wavefile_by_PWM.c - Audio demo to use PWM to
// play WAV audio.
//
// Copyright (c) 2013-2020 Texas Instruments Incorporated.  All rights reserved.
// Software License Agreement
// 
// Texas Instruments (TI) is supplying this software for use solely and
// exclusively on TI's microcontroller products. The software is owned by
// TI and/or its suppliers, and is protected under applicable copyright
// laws. You may not combine this software with "viral" open-source
// software in order to form a larger program.
// 
// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
// DAMAGES, FOR ANY REASON WHATSOEVER.
// 
//
//*****************************************************************************

#include <stdbool.h>
#include <stdint.h>
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_timer.h"
#include "inc/hw_types.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/pin_map.h"
#include "driverlib/sysctl.h"
#include "driverlib/timer.h"
#include "drivers/buttons.h"


//*****************************************************************************
//
//! \addtogroup example_list
//! <h1>Audio playback using PWM method with internally stored .wav file
//! (audio_playback_internal_wavefile_by_PWM)</h1>
//!
//! This example application demonstrates playing a .wav file which is stored
//! internally on the flash memory.  Several example .wav files can be selected
//! during compile time by using the #define DEMO directive.  Only PCM format
//! (uncompressed) .wav files may be played.
//!
//! Press the USR_SW1 (LEFT) button on the board to pause the playback.
//! Press the USR_SW2 (RIGHT) button to resume playback.
//!
//! This example creates an audio signal to an external speaker on the
//! BOOSTXL-AUDIO BoosterPack using the PWM method.  The MCU's PWM output will
//! pass through a basic R/C filter on the BoosterPack to generate a simple,
//! low-cost analog audio signal that plays through the speaker. 
//!
//! Visit https://www.ti.com/tool/BOOSTXL-AUDIO for details on the BoosterPack
//! daughter card.
//
//*****************************************************************************

//*****************************************************************************
//
// Global defines.
// TODO: Change the macro 'DEMO' to select different sample audio files.
//
//*****************************************************************************
#define DEMO 1

#if DEMO == 1
#include "demo_8bit_8kHz.h"
#define AUDIOSIZE (uint32_t )__pcm_mono_8_bit_8kHz_wav_size
#define AUDIODATA __pcm_mono_8_bit_8kHz_wav
#endif
#if DEMO == 2
#include "demo_chirp_16kHz.h"
#define AUDIOSIZE (uint32_t )__chirp_16kHz_wav_size
#define AUDIODATA __chirp_16kHz_wav
#endif
#if DEMO == 3
#include "demo_chirp_8kHz.h"
#define AUDIOSIZE (uint32_t )__chirp_8kHz_wav_size
#define AUDIODATA __chirp_8kHz_wav
#endif
#if DEMO == 4
#include "demo_16bit_8kHz.h"
#define AUDIOSIZE (uint32_t )__pcm_mono_16_bit_8kHz_wav_size
#define AUDIODATA __pcm_mono_16_bit_8kHz_wav
#endif


//*****************************************************************************
//
// The starting position of the audio data in the .wav file.  The first 44
// bytes are the header of the .wav file.
//
//*****************************************************************************
#define AUDIODATA_OFFSET 44
uint32_t g_ui32AudioPos = AUDIODATA_OFFSET;

//*****************************************************************************
//
// Global variables.
//
//*****************************************************************************

uint16_t g_ui16BitsPerSample;
uint32_t g_ui32SampleRate;

//*****************************************************************************
//
// The error routine that is called if the driver library encounters an error.
//
//*****************************************************************************
#ifdef DEBUG
void
__error__(char *pcFilename, uint32_t ui32Line)
{
}
#endif

//*****************************************************************************
//
//! Handles the TIMER3B interrupt.
//!
//! This function responds to the TIMER3B interrupt, updating the duty cycle of
//! the output waveform in order to produce sound.
//!
//! \return None.
//
//*****************************************************************************
void
SoundIntHandler(void)
{
    uint16_t ui16DutyCycle;
    uint16_t ui16NumChannel = AUDIODATA[22];

    //
    // Clear the timer interrupt.
    //
    MAP_TimerIntClear(TIMER3_BASE, TIMER_TIMB_TIMEOUT);

    if (g_ui32AudioPos < AUDIOSIZE)
    {
        //
        // 16 bit resolution.
        //
        if (g_ui16BitsPerSample == 16)
        {
            //
            // Calculate the PWM duty cycle to send to the speaker.
            // 1. Retrieve the two bytes from the .wav file and add 32768 to
            // adjust for unsigned value.
            // 2. Divide the two byte data by 2^16 to obtain a ratio for PWM
            // duty cycle.
            //
            ui16DutyCycle = ((AUDIODATA[g_ui32AudioPos+1] << 8) +
                             AUDIODATA[g_ui32AudioPos]) + 32768;
            ui16DutyCycle = MAP_TimerLoadGet(TIMER3_BASE, TIMER_A) *
                                             ui16DutyCycle / 65536;
            MAP_TimerMatchSet(TIMER3_BASE, TIMER_A, ui16DutyCycle);

            //
            // While playing the wave file, at each sample point, retrieve the
            // next two bytes depending on the number of tracks in the audio
            // file.  If two tracks, read two bytes at four bytes boundary.
            // If one track, read two bytes at two bytes boundary.
            //
            if (ui16NumChannel == 0x02)
            {
                g_ui32AudioPos += 4;
            }
            else
            {
                g_ui32AudioPos += 2;
            }
        }
        //
        // 8 bit resolution.
        //
        else
        {
            //
            // Retrieve the each audio byte from the wave file.
            //
            ui16DutyCycle = (AUDIODATA[g_ui32AudioPos]);
            MAP_TimerMatchSet(TIMER3_BASE, TIMER_A, ui16DutyCycle);

            //
            // While playing the wave file, at each sample point, retrieve the
            // next byte depending on the number of tracks in the audio file.
            // If two tracks, read one byte at two bytes boundary.  If one
            // track, read one byte at one byte boundary.
            //
            if (ui16NumChannel == 0x02)
            {
                g_ui32AudioPos+=2;
            }
            else
            {
                g_ui32AudioPos++;
            }
        }
    }
    else
    {
        //
        // Reset audio position to the beginning of audio data
        //
        g_ui32AudioPos = AUDIODATA_OFFSET;
    }
}

//*****************************************************************************
//
// This application performs simple audio synthesis and playback using PWM
// Method.
//
//*****************************************************************************
int
main(void)
{
    uint32_t ui32Period;

    //
    // Run from the PLL at 120 MHz.
    // Note: SYSCTL_CFG_VCO_240 is a new setting provided in TivaWare 2.2.x and
    // later to better reflect the actual VCO speed due to SYSCTL#22.
    //
    MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN |
                       SYSCTL_XTAL_16MHZ);

    //
    // Retrieve the audio sample rate and resolution.
    //
    g_ui16BitsPerSample = (AUDIODATA[35] << 8) + AUDIODATA[34];
    g_ui32SampleRate = (AUDIODATA[27] << 24) + (AUDIODATA[26] << 16) +
                       (AUDIODATA[25] << 8 ) + AUDIODATA[24];

    //
    // Configure the device pins.
    //
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);

    //
    // PA4/PB2 are used for the speaker output.
    //
    MAP_GPIOPinConfigure(GPIO_PB2_T3CCP0);
    MAP_GPIOPinTypeTimer(GPIO_PORTB_BASE, GPIO_PIN_2);
    MAP_GPIOPinTypeGPIOOutput(GPIO_PORTA_BASE, GPIO_PIN_4);

    //
    // Enable the speaker amp.
    //
    MAP_GPIOPinWrite(GPIO_PORTA_BASE, GPIO_PIN_4, 0);
    
    //
    // Enable the peripherals used by the this application.
    //
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER3);

    //
    // Compute the PWM period based on the system clock.
    //
    ui32Period = MAP_SysCtlClockGet() / g_ui32SampleRate;
    //
    // Configure the timer to run in PWM mode.
    //
    if((HWREG(TIMER3_BASE + TIMER_O_CTL) & TIMER_CTL_TBEN) == 0)
    {
        MAP_TimerConfigure(TIMER3_BASE, (TIMER_CFG_SPLIT_PAIR |
                                        TIMER_CFG_B_PERIODIC |
                                        TIMER_CFG_A_PWM));
    }

    //
    // Load the PMW period depending on the resolution.
    //
    if (g_ui16BitsPerSample == 16)
    {
        MAP_TimerLoadSet(TIMER3_BASE, TIMER_A, ui32Period - 1);
    }
    else
    {
        MAP_TimerLoadSet(TIMER3_BASE, TIMER_A, 0xFF);
    }

    //
    // Load the timeout period for the sample rate.
    //
    MAP_TimerLoadSet(TIMER3_BASE, TIMER_B, ui32Period - 1);

    //
    // Make the PWM's output active low.
    //
    MAP_TimerControlLevel(TIMER3_BASE, TIMER_A, true);

    //
    // Update the timer values on timeouts and not immediately.
    //
    MAP_TimerUpdateMode(TIMER3_BASE, TIMER_A, TIMER_UP_LOAD_TIMEOUT |
                                              TIMER_UP_MATCH_TIMEOUT);
    MAP_TimerUpdateMode(TIMER3_BASE, TIMER_B, TIMER_UP_LOAD_TIMEOUT |
                                              TIMER_UP_MATCH_TIMEOUT);
                                          
    //
    // Configure the timer to generate an interrupt at every time-out event.
    //
    MAP_TimerIntEnable(TIMER3_BASE, TIMER_TIMB_TIMEOUT);

    //
    // Enable the timer.  At this point, the timer generates an interrupt
    // after TIMER_B timeout.
    //
    MAP_TimerEnable(TIMER3_BASE, TIMER_BOTH);
    MAP_IntEnable(INT_TIMER3B);

    //
    // Enable NVIC interrupts.
    //
    IntMasterEnable();

    //
    // Initialize the buttons driver.
    //
    ButtonsInit();

    //
    // Loop forever.
    //
    while(1)
    {
        //
        // Press the USR_SW1 (LEFT) button on the board to pause the playback.
        // Press the USR_SW2 (RIGTHT) button to resume playback.
        //
        if (MAP_GPIOPinRead(BUTTONS_GPIO_BASE, LEFT_BUTTON) == 0)
        {
            //
            // Disable PWM generation and timeout generation.
            //
            MAP_TimerDisable(TIMER3_BASE, TIMER_BOTH);
        }
        if (MAP_GPIOPinRead(BUTTONS_GPIO_BASE, RIGHT_BUTTON) == 0)
        {
            //
            // Enable PWM generation and timeout generation.
            //
            MAP_TimerEnable(TIMER3_BASE, TIMER_BOTH);
        }

    }
}
