/*
 * PcmOutput.cpp
 *
 *  Created on: Jan 1, 2012
 *      Author: va_beaver
 */

#include "PcmOutput.h"

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>

uint16_t _sampleRate;

uint8_t *playingBuffer;
uint16_t playingBufferSize;
uint8_t *queuedBuffer;
uint16_t queuedBufferSize;
uint16_t bufferOffset;
uint8_t underflow;

void PCM_init(uint16_t sampleRate) {
	_sampleRate = sampleRate;
	playingBufferSize = 0;
	queuedBufferSize = 0;
	bufferOffset = 0;

    asm("cli");

    PRR0 &= ~(1 << PRTIM1);      // Clear any power-down on timer0
    PRR1 &= ~(1 << PRTIM5);      // Clear any power-down on timer5

    // Setup PWM output
    // WGM5[3:0] = b0101 (Fast PWM, 8-bit)
    // COM5A[1:0] = b10 (non-inverting)
    // COM5B[1:0] = b10 (non-inverting)
    // COM5C[1:0] = b10 (non-inverting)
    // CS5[2:0] = b001 (clock/1)
    TCCR5A = (1 << WGM50) | (1 << COM5A1) | (1 << COM5B1) | (1 << COM5C1);     // Non-inverting, WGM
    TCCR5B = (1 << WGM52) | (1 << CS50);     // WGM, full clock speed
    TCCR5C = 0;
    TCNT5H = 0x00;     // Initialise timer counter to zero
    TCNT5L = 0x00;     //
    TIMSK5 = 0x00;     // No interrupts
    OCR5AH = 0x00;     // 50% duty cycle
    OCR5AL = 0x00;     //
    OCR5BH = 0x00;     // 50% duty cycle
    OCR5BL = 0x00;     //
    OCR5CH = 0x00;     // 50% duty cycle
    OCR5CL = 0x00;     //

    // Setup sample interrupt
    PRR1 = 0x00;      // Clear any power-down on the timer
    TCCR1A = 0x00;    // Don't mess with output pins, CTC operation mode
    TCCR1B = 0x09;    // CTC operation mode, start timer
    TCCR1C = 0x00;    //
    TCNT1 = 0x0000;   // Initialise timer counter to zero
    OCR1A = F_CPU / sampleRate;   // Sample rate
    TIMSK1 = 0x02;    // Enable interrupt on compare match

    DDRL |= 0x38;     // Enable PWM output for OC5A, OC5B, OC5C

    asm("sei");
}

bool PCM_needBuffer() {
    bool retVal = false;

    if (playingBufferSize == 0) retVal = true;
    if (queuedBufferSize == 0) retVal = true;

    return retVal;
}

int PCM_enqueue(uint8_t *newBufferPos, uint16_t newBufferSize) {
    asm("cli");

    int retVal = -1;

    if (playingBufferSize == 0) {
		playingBuffer = newBufferPos;
		playingBufferSize = newBufferSize;
		retVal = 1;
	} else if (queuedBufferSize == 0) {
		queuedBuffer = newBufferPos;
		queuedBufferSize = newBufferSize;
		retVal = 0;
	}

    asm("sei");

    return retVal;
}

uint8_t PCM_hasUnderflowed() {
    asm("cli");

    register uint8_t retVal;

    retVal = underflow;
    underflow = 0;

    asm("sei");

    return retVal;
}

ISR(TIMER1_COMPA_vect) {
    asm("cli");

    register uint8_t sample;

    if (playingBufferSize) {
    	sample = playingBuffer[bufferOffset] >> 1;
		OCR5AH = 0x00;
		OCR5AL = sample;
		OCR5BH = 0x00;
		OCR5BL = sample;
		OCR5CH = 0x00;
		OCR5CL = sample;
		bufferOffset++;

		if (bufferOffset == playingBufferSize) {
			bufferOffset = 0;
			playingBuffer = queuedBuffer;
			playingBufferSize = queuedBufferSize;
			queuedBufferSize = 0;
			if (!playingBufferSize) {
				underflow = 1;
			}
		}
	}

    asm("sei");
}
