/*
 * Author: Pawel Jablonski
 * E-mail: pj@xirx.net
 * WWW: xirx.net
 * GIT: git.xirx.net
 *
 * License: You can use this code however you like
 * but leave information about the original author.
 * Code is free for non-commercial and commercial use.
 */

#include <stm32f0xx.h>
#include <stm32f0xx_hal.h>
#include <stm32f0xx_hal_gpio.h>
#include <stm32f0xx_hal_dac.h>
#include <stm32f0xx_hal_tim.h>
#include <stm32f0xx_hal_dma.h>
#include <stm32f0xx_hal_rcc.h>
#include <system_stm32f0xx.h>

#include "type.h"

#include "speaker.h"

#define SPEAKER_BUFFER_SIZE 32 //!< Note buffer size
#define SPEAKER_BUFFER_OUT_SIZE 16 //!< Output buffer size and square wave fill divider

#define SPEAKER_NOTE_QUANTITY 89 //!< Note quantity available to play

#define SPEAKER_TIME_MIN 1 //!< Minimum time to play for a note in ms multiplied by TIME_MUL
#define SPEAKER_TIME_MAX 250 //!< Maximum time to play for a note in ms multiplied by TIME_MUL
#define SPEAKER_TIME_MUL 10 //!< Time multiplicator for a note

#define SPEAKER_FILL_MIN 1 //!< Minimum fill of the square wave in percents divided by SPEAKER_BUFFER_OUT_SIZE
#define SPEAKER_FILL_MAX 15 //!< Maximum fill of the square wave in percents divided by SPEAKER_BUFFER_OUT_SIZE

#define SPEAKER_VOLUME_MAX 15.0 //!< Maximum volume level

#define SPEAKER_VALUE_ZERO 2048 //!< Output zero level for silence
#define SPEAKER_VALUE_MUL 2047.0 //!< Output level multiplicator used to conversion
#define SPEAKER_VALUE_VOLUME 0.1 //!< Output volume level multiplicator

//! List of frequencies for notes
static const unsigned short noteFrequency[SPEAKER_NOTE_QUANTITY] = {
	20, // Silent
	27, 29, 31, //  A0 - B0
	33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, // C1 - B1
	65, 69, 73, 78, 82, 87, 93, 98, 104, 110, 117, 123, // C2 - B2
	131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, // C3 - B3
	262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, // C4 - B4
	523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, // C5 - B5
	1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, // C6 - B6
	2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, // C7 - B7
	4186 // C8
};

static volatile bool isPlaying = FALSE; //!< Playing status

static volatile unsigned int bufferStart = 0; //!< Data start position
static volatile unsigned int bufferEnd = 0; //!< Data end position

static DAC_HandleTypeDef dacHandle; //!< DAC HAL handle
static TIM_HandleTypeDef timHandle; //!< Timer HAL handle for playing square wave
static DAC_ChannelConfTypeDef channelConf; //!< Configuration of DAC HAL
static DMA_HandleTypeDef dmaHandle; //!< DMA HAL handle
static TIM_HandleTypeDef timNoteHandle; //!< Timer HAL handle for making a delay between notes

static struct SpeakerNote buffer[SPEAKER_BUFFER_SIZE]; //!< Circular note buffer

static uint16_t bufferOut[SPEAKER_BUFFER_OUT_SIZE]; //!< Output buffer

static void speakerPlay(unsigned int offset);

//! Init speaker and set up DMA, DAC, Timers
void speakerInit(void)
{
	for(int i = 0; i < SPEAKER_BUFFER_OUT_SIZE; i++)
	{
		bufferOut[i] = SPEAKER_VALUE_ZERO;
	}
	
	__HAL_RCC_GPIOA_CLK_ENABLE();
	
	GPIO_InitTypeDef gpioInit = {0};
	
	gpioInit.Pin = GPIO_PIN_4;
	gpioInit.Mode = GPIO_MODE_ANALOG;
	gpioInit.Pull = GPIO_NOPULL;
	gpioInit.Speed = GPIO_SPEED_FREQ_LOW;
	
	HAL_GPIO_Init(GPIOA, &gpioInit);
	
	__HAL_RCC_TIM2_CLK_ENABLE();
	
	timHandle.Instance = TIM2;
	timHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
	timHandle.Init.Prescaler = 0;
	timHandle.Init.Period = ((HAL_RCC_GetHCLKFreq() / 100000) - 1);
	timHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
	timHandle.Init.RepetitionCounter = 0;
	
	HAL_TIM_Base_Init(&timHandle);
	HAL_TIM_Base_Start_IT(&timHandle);
	
	TIM_MasterConfigTypeDef sMasterConfig = {0};

	sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
	sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
	HAL_TIMEx_MasterConfigSynchronization(&timHandle, &sMasterConfig);
	
	__HAL_RCC_DAC1_CLK_ENABLE();
	
	dacHandle.Instance = DAC;
	
	HAL_DAC_Init(&dacHandle);
	
	channelConf.DAC_Trigger = DAC_TRIGGER_T2_TRGO;
    channelConf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
	
	HAL_DAC_ConfigChannel(&dacHandle, &channelConf, DAC_CHANNEL_1);
	HAL_DAC_Start(&dacHandle, DAC_CHANNEL_1);
	
	__HAL_RCC_DMA1_CLK_ENABLE();
	
	dmaHandle.Instance = DMA1_Channel3;
	dmaHandle.Init.Direction = DMA_MEMORY_TO_PERIPH;
	dmaHandle.Init.PeriphInc = DMA_PINC_DISABLE;
	dmaHandle.Init.MemInc = DMA_MINC_ENABLE;
	dmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
	dmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
	dmaHandle.Init.Mode = DMA_CIRCULAR;
	dmaHandle.Init.Priority = DMA_PRIORITY_HIGH;
	
	HAL_DMA_Init(&dmaHandle);
	
	__HAL_LINKDMA(&dacHandle, DMA_Handle1, dmaHandle);
	
	HAL_DAC_Start_DMA(&dacHandle, DAC_CHANNEL_1, (uint32_t *)bufferOut, SPEAKER_BUFFER_OUT_SIZE, DAC_ALIGN_12B_R);
	
	__HAL_RCC_TIM6_CLK_ENABLE();
	
	timNoteHandle.Instance = TIM6;
	timNoteHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
	timNoteHandle.Init.Prescaler = ((HAL_RCC_GetHCLKFreq() / 1000) - 1);
	timNoteHandle.Init.Period = 0;
	timNoteHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
	timNoteHandle.Init.RepetitionCounter = 0;
	
	HAL_TIM_Base_Init(&timNoteHandle);
	
	HAL_NVIC_SetPriority(TIM6_IRQn, 3, 0);
	HAL_NVIC_EnableIRQ(TIM6_IRQn);
}

//! Clear the speaker buffers and stop playing sound
void speakerClear(void)
{
	__disable_irq();
	{
		isPlaying = FALSE;
		
		bufferStart = 0;
		bufferEnd = 0;
		
		HAL_TIM_Base_Stop_IT(&timNoteHandle);
	
		for(int i = 0; i < SPEAKER_BUFFER_OUT_SIZE; i++)
		{
			bufferOut[i] = SPEAKER_VALUE_ZERO;
		}
	}
	__enable_irq();
}

/**
 * Add note to the buffer
 *
 * @param note Note to add to the buffer
 */
void speakerAddNote(struct SpeakerNote note)
{
	if(note.note >= SPEAKER_NOTE_QUANTITY)
	{
		return;
	}
	
	if((note.time < SPEAKER_TIME_MIN) || (note.time > SPEAKER_TIME_MAX))
	{
		return;
	}
	
	if(note.note != NOTE_SILENT)
	{
		if((note.fill < SPEAKER_FILL_MIN) || (note.fill > SPEAKER_FILL_MAX))
		{
			return;
		}
		
		if(note.volume > SPEAKER_VOLUME_MAX)
		{
			return;
		}
	}
	
	__disable_irq();
	{
		unsigned int last = bufferEnd;
		
		if(bufferStart == ((last + 1) % SPEAKER_BUFFER_SIZE))
		{
			__enable_irq();
			return;
		}
		
		buffer[last].note = note.note;
		buffer[last].time = note.time;
		
		if(note.note == NOTE_SILENT)
		{
			buffer[last].fill = SPEAKER_FILL_MIN;
			buffer[last].volume = 0;
		}
		else
		{
			buffer[last].fill = note.fill;
			buffer[last].volume = note.volume;
		}
		
		bufferEnd = ((last + 1) % SPEAKER_BUFFER_SIZE);

		if(!isPlaying)
		{
			__enable_irq();
			
			speakerPlay(bufferStart);
			
			return;
		}
	}
	__enable_irq();
	
	return;
}

/**
 * Get free space size in the buffer
 *
 * @return Free space size in the buffer
 */
unsigned char speakerGetBufferFree(void)
{
	unsigned char bufferFree = (SPEAKER_BUFFER_SIZE - 1);
	
	__disable_irq();
	{
		if(bufferStart < bufferEnd)
		{
			bufferFree -= (bufferEnd - bufferStart);
		}
		else if(bufferStart > bufferEnd)
		{
			bufferStart = ((bufferStart - bufferEnd) - 1);
		}	
	}
	__enable_irq();
	
	return(bufferFree);
}

/**
 * Play a note
 *
 * @param offset Position of the note in the note buffer
 */
static void speakerPlay(unsigned int offset)
{
	__disable_irq();
	{
		uint16_t signalLevel = (uint16_t)(SPEAKER_VALUE_MUL * SPEAKER_VALUE_VOLUME * ((float)(buffer[offset].volume) / SPEAKER_VOLUME_MAX));
		
		uint16_t high = (SPEAKER_VALUE_ZERO + signalLevel);
		uint16_t low = (SPEAKER_VALUE_ZERO - signalLevel);
		
		for(int i = 0; i < SPEAKER_BUFFER_OUT_SIZE; i++)
		{
			bufferOut[i] = ((i < buffer[offset].fill) ? high : low);
		}
		
		__HAL_TIM_SET_AUTORELOAD(&timHandle, ((HAL_RCC_GetHCLKFreq() / (noteFrequency[buffer[offset].note] * SPEAKER_BUFFER_OUT_SIZE)) - 1));
		__HAL_TIM_SET_COUNTER(&timHandle, 0);
		
		__HAL_TIM_SET_AUTORELOAD(&timNoteHandle, (((uint32_t)(buffer[offset].time) * SPEAKER_TIME_MUL) - 1));
		__HAL_TIM_SET_COUNTER(&timNoteHandle, 0);
		
		if(!isPlaying)
		{
			isPlaying = TRUE;
			
			HAL_TIM_Base_Start_IT(&timNoteHandle);
		}
	}
	__enable_irq();
}

//! Load and play a next note from the note buffer. It is an IRQ handler connected to the timer HAL handle for making a delay between notes.
void TIM6_IRQHandler(void)
{
	if(bufferStart == bufferEnd)
	{
		speakerClear();
	}
	else
	{
		unsigned int first = bufferStart;

		speakerPlay(first);
		
		bufferStart = ((first + 1) % SPEAKER_BUFFER_SIZE);
	}
	
	HAL_TIM_IRQHandler(&timNoteHandle);	
}