// Squawk Soft-Synthesizer Library for Arduino
//
// Davey Taylor 2013
// d.taylor@arduino.cc

#ifndef _SQUAWK_H_
#define _SQUAWK_H_
#include <stddef.h>
#include <inttypes.h>
#include "Arduino.h"

#define Melody const uint8_t PROGMEM

class SquawkStream {
	public:
	  virtual ~SquawkStream() = 0;
    virtual uint8_t read() = 0;
    virtual void seek(size_t offset) = 0;
};
inline SquawkStream::~SquawkStream() { }

class SquawkSynth {

protected:
  // Load and play specified melody
  void play(SquawkStream *melody);

public:
  SquawkSynth() {};

  // Initialize Squawk to generate samples at sample_rate Hz
  void begin(uint16_t sample_rate);

  // Load and play specified melody
  // melody needs to point to PROGMEM data
  void play(const uint8_t *melody);
  
  // Resume currently loaded melody (or enable direct osc manipulation by sketch)
  void play();
    
  // Pause playback
  void pause();
  
  // Stop playback (unloads song)
  void stop();
  
  // Tune Squawk to a different frequency - default is 1.0
  void tune(float tuning);

  // Change the tempo - default is 50
	void tempo(uint16_t tempo);
};

extern SquawkSynth Squawk;

// oscillator structure
typedef struct {
  uint8_t  vol;
  uint16_t freq;
  uint16_t phase;
} osc_t;

typedef osc_t Oscillator;

// oscillator memory
extern osc_t osc[4];
extern uint8_t pcm;
// channel 0 is pulse wave @ 25% duty
// channel 1 is square wave
// channel 2 is triangle wave
// channel 3 is noise

// For channel 3, freq is used as part of its LFSR and should not be changed.
// LFSR: Linear feedback shift register, a method of producing a
// pseudo-random bit sequence, used to generate nasty noise.

#ifdef __AVR_ATmega32U4__
// Supported configurations for ATmega32U4
#define SQUAWK_PWM_PIN5  OCR3AL
#define SQUAWK_PWM_PIN11 OCR0A
#define SQUAWK_PWM_PIN3  OCR0B
/*
// NOT SUPPORTED YET
#define SQUAWK_PWM_PIN6  OCR4D
#define SQUAWK_PWM_PIN9  OCR4B
#define SQUAWK_PWM_PIN10 OCR4B
*/
#endif

#ifdef __AVR_ATmega168__
// Supported configurations for ATmega168
#define SQUAWK_PWM_PIN6  OCR0A
#define SQUAWK_PWM_PIN5  OCR0B
#define SQUAWK_PWM_PIN11 OCR2A
#define SQUAWK_PWM_PIN3  OCR2B
#endif

#ifdef __AVR_ATmega328P__
// Supported configurations for ATmega328P
#define SQUAWK_PWM_PIN6  OCR0A
#define SQUAWK_PWM_PIN5  OCR0B
#define SQUAWK_PWM_PIN11 OCR2A
#define SQUAWK_PWM_PIN3  OCR2B
#endif

/*
// NOT SUPPORTED YET
#define SQUAWK_SPI SPDR
#define SQUAWK_RLD_PORTB PORTB
#define SQUAWK_RLD_PORTC PORTC
*/

extern void squawk_playroutine() asm("squawk_playroutine");

// SAMPLE GRINDER
// generates samples and updates oscillators
// uses 132 cycles (not counting playroutine)
//     ~1/3 CPU @ 44kHz on 16MHz
#define SQUAWK_CONSTRUCT_ISR(TARGET_REGISTER) \
__attribute__((used)) uint16_t cia, cia_count; \
intptr_t squawk_register = (intptr_t)&TARGET_REGISTER; \
ISR(TIMER1_COMPA_vect, ISR_NAKED) { \
  asm volatile( \
    "push r2                                          " "\n\t" \
    "in   r2,                    __SREG__             " "\n\t" \
    "push r18                                         " "\n\t" \
    "push r27                                         " "\n\t" \
    "push r26                                         " "\n\t" \
    "push r0                                          " "\n\t" \
    "push r1                                          " "\n\t" \
\
    "lds  r18,                   osc+2*%[mul]+%[fre]  " "\n\t" \
    "lds  r0,                    osc+2*%[mul]+%[pha]  " "\n\t" \
    "add  r0,                    r18                  " "\n\t" \
    "sts  osc+2*%[mul]+%[pha],   r0                   " "\n\t" \
    "lds  r18,                   osc+2*%[mul]+%[fre]+1" "\n\t" \
    "lds  r1,                    osc+2*%[mul]+%[pha]+1" "\n\t" \
    "adc  r1,                    r18                  " "\n\t" \
    "sts  osc+2*%[mul]+%[pha]+1, r1                   " "\n\t" \
\
    "mov  r27,                   r1                   " "\n\t" \
    "sbrc r27,                   7                    " "\n\t" \
    "com  r27                                         " "\n\t" \
    "lsl  r27                                         " "\n\t" \
    "lds  r26,                   osc+2*%[mul]+%[vol]  " "\n\t" \
    "subi r27,                   128                  " "\n\t" \
    "muls r27,                   r26                  " "\n\t" \
    "lsl  r1                                          " "\n\t" \
    "mov  r26,                   r1                   " "\n\t" \
\
    "lds  r18,                   osc+0*%[mul]+%[fre]  " "\n\t" \
    "lds  r0,                    osc+0*%[mul]+%[pha]  " "\n\t" \
    "add  r0,                    r18                  " "\n\t" \
    "sts  osc+0*%[mul]+%[pha],   r0                   " "\n\t" \
    "lds  r18,                   osc+0*%[mul]+%[fre]+1" "\n\t" \
    "lds  r1,                    osc+0*%[mul]+%[pha]+1" "\n\t" \
    "adc  r1,                    r18                  " "\n\t" \
    "sts  osc+0*%[mul]+%[pha]+1, r1                   " "\n\t" \
\
    "mov  r18,                   r1                   " "\n\t" \
    "lsl  r18                                         " "\n\t" \
    "and  r18,                   r1                   " "\n\t" \
    "lds  r27,                   osc+0*%[mul]+%[vol]  " "\n\t" \
    "sbrc r18,                   7                    " "\n\t" \
    "neg  r27                                         " "\n\t" \
    "add  r26,                   r27                  " "\n\t" \
\
    "lds  r18,                   osc+1*%[mul]+%[fre]  " "\n\t" \
    "lds  r0,                    osc+1*%[mul]+%[pha]  " "\n\t" \
    "add  r0,                    r18                  " "\n\t" \
    "sts  osc+1*%[mul]+%[pha],   r0                   " "\n\t" \
    "lds  r18,                   osc+1*%[mul]+%[fre]+1" "\n\t" \
    "lds  r1,                    osc+1*%[mul]+%[pha]+1" "\n\t" \
    "adc  r1,                    r18                  " "\n\t" \
    "sts  osc+1*%[mul]+%[pha]+1, r1                   " "\n\t" \
\
    "lds  r27,                   osc+1*%[mul]+%[vol]  " "\n\t" \
    "sbrc r1,                    7                    " "\n\t" \
    "neg  r27                                         " "\n\t" \
    "add  r26,                   r27                  " "\n\t" \
\
    "ldi  r27,                   1                    " "\n\t" \
    "lds  r0,                    osc+3*%[mul]+%[fre]  " "\n\t" \
    "lds  r1,                    osc+3*%[mul]+%[fre]+1" "\n\t" \
    "add  r0,                    r0                   " "\n\t" \
    "adc  r1,                    r1                   " "\n\t" \
    "sbrc r1,                    7                    " "\n\t" \
    "eor  r0,                    r27                  " "\n\t" \
    "sbrc r1,                    6                    " "\n\t" \
    "eor  r0,                    r27                  " "\n\t" \
    "sts  osc+3*%[mul]+%[fre],   r0                   " "\n\t" \
    "sts  osc+3*%[mul]+%[fre]+1, r1                   " "\n\t" \
\
    "lds  r27,                   osc+3*%[mul]+%[vol]  " "\n\t" \
    "sbrc r1,                    7                    " "\n\t" \
    "neg  r27                                         " "\n\t" \
    "add  r26,                   r27                  " "\n\t" \
\
    "lds  r27,                   pcm                  " "\n\t" \
    "add  r26,                   r27                  " "\n\t" \
    "sts  %[reg],                r26                  " "\n\t" \
\
	  "lds  r27,                   cia_count+1          " "\n\t" \
	  "lds  r26,                   cia_count            " "\n\t" \
	  "sbiw r26,                   1                    " "\n\t" \
	  "breq call_playroutine                            " "\n\t" \
	  "sts  cia_count+1,           r27                  " "\n\t" \
	  "sts  cia_count,             r26                  " "\n\t" \
    "pop  r1                                          " "\n\t" \
    "pop  r0                                          " "\n\t" \
    "pop  r26                                         " "\n\t" \
    "pop  r27                                         " "\n\t" \
    "pop  r18                                         " "\n\t" \
    "out  __SREG__,              r2                   " "\n\t" \
    "pop  r2                                          " "\n\t" \
	  "reti                                             " "\n\t" \
    "call_playroutine:                                " "\n\t" \
\
	  "lds  r27, cia+1                                  " "\n\t" \
	  "lds  r26, cia                                    " "\n\t" \
	  "sts  cia_count+1,           r27                  " "\n\t" \
	  "sts  cia_count,             r26                  " "\n\t" \
\
    "sei                                              " "\n\t" \
	  "push r19                                         " "\n\t" \
	  "push r20                                         " "\n\t" \
	  "push r21                                         " "\n\t" \
	  "push r22                                         " "\n\t" \
	  "push r23                                         " "\n\t" \
	  "push r24                                         " "\n\t" \
	  "push r25                                         " "\n\t" \
	  "push r30                                         " "\n\t" \
	  "push r31                                         " "\n\t" \
\
    "clr  r1                                          " "\n\t" \
    "call squawk_playroutine                          " "\n\t" \
\
	  "pop  r31                                         " "\n\t" \
	  "pop  r30                                         " "\n\t" \
	  "pop  r25                                         " "\n\t" \
	  "pop  r24                                         " "\n\t" \
	  "pop  r23                                         " "\n\t" \
	  "pop  r22                                         " "\n\t" \
	  "pop  r21                                         " "\n\t" \
	  "pop  r20                                         " "\n\t" \
	  "pop  r19                                         " "\n\t" \
\
    "pop  r1                                          " "\n\t" \
    "pop  r0                                          " "\n\t" \
    "pop  r26                                         " "\n\t" \
    "pop  r27                                         " "\n\t" \
    "pop  r18                                         " "\n\t" \
    "out  __SREG__,              r2                   " "\n\t" \
    "pop  r2                                          " "\n\t" \
	  "reti                                             " "\n\t" \
    : \
    : [reg] "M" _SFR_MEM_ADDR(TARGET_REGISTER), \
      [mul] "M" (sizeof(Oscillator)), \
      [pha] "M" (offsetof(Oscillator, phase)), \
      [fre] "M" (offsetof(Oscillator, freq)), \
      [vol] "M" (offsetof(Oscillator, vol)) \
  ); \
}

#endif