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

#include "Squawk.h"

// Period range, used for clamping
#define PERIOD_MIN 28
#define PERIOD_MAX 3424

// Convenience macros
#define LO4(V)    ((V) & 0x0F)
#define HI4(V)    (((V) & 0xF0) >> 4)
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define FREQ(PERIOD) (tuning_long / (PERIOD))

// SquawkStream class for PROGMEM data
class StreamROM : public SquawkStream {
  private:
    uint8_t *p_start;
    uint8_t *p_cursor;
  public:
    StreamROM(const uint8_t *p_rom = NULL) { p_start = p_cursor = (uint8_t*)p_rom; }
    uint8_t read() { return pgm_read_byte(p_cursor++); }
    void seek(size_t offset) { p_cursor = p_start + offset; }
};

// Oscillator memory
typedef struct {
  uint8_t fxp;
  uint8_t offset;
  uint8_t mode;
} pto_t;

// Deconstructed cell
typedef struct {
  uint8_t fxc, fxp, ixp;
} cel_t;

// Effect memory
typedef struct {
  int8_t    volume;
  uint8_t   port_speed;
  uint16_t  port_target;
  bool      glissando;
  pto_t     vibr;
  pto_t     trem;
  uint16_t  period;
  uint8_t   param;
} fxm_t;

// Locals
static uint8_t  order_count;
static uint8_t  order[64];
static uint8_t  speed;
static uint8_t  tick;
static uint8_t  ix_row;
static uint8_t  ix_order;
static uint8_t  ix_nextrow;
static uint8_t  ix_nextorder;
static uint8_t  row_delay;
static fxm_t    fxm[4];
static cel_t    cel[4];
static uint32_t tuning_long;
static uint16_t sample_rate;
static float    tuning = 1.0;
static uint16_t tick_rate = 50;

static SquawkStream *stream;
static uint16_t stream_base;
static StreamROM rom;

// Imports
extern intptr_t squawk_register;
extern uint16_t cia;

// Exports
__attribute__((used)) osc_t osc[4];
__attribute__((used)) uint8_t pcm = 128;

// ProTracker period tables
const uint16_t period_tbl[84] PROGMEM = {
  3424, 3232, 3048, 2880, 2712, 2560, 2416, 2280, 2152, 2032, 1920, 1814,
  1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016,  960,  907,
   856,  808,  762,  720,  678,  640,  604,  570,  538,  508,  480,  453,
   428,  404,  381,  360,  339,  320,  302,  285,  269,  254,  240,  226,
   214,  202,  190,  180,  170,  160,  151,  143,  135,  127,  120,  113,
   107,  101,   95,   90,   85,   80,   75,   71,   67,   63,   60,   56,
    53,   50,   47,   45,   42,   40,   37,   35,   33,   31,   30,   28,
};

// ProTracker sine table
const int8_t sine_tbl[32] PROGMEM = {
  0x00, 0x0C, 0x18, 0x25, 0x30, 0x3C, 0x47, 0x51, 0x5A, 0x62, 0x6A, 0x70, 0x76, 0x7A, 0x7D, 0x7F,
  0x7F, 0x7F, 0x7D, 0x7A, 0x76, 0x70, 0x6A, 0x62, 0x5A, 0x51, 0x47, 0x3C, 0x30, 0x25, 0x18, 0x0C,
};

// Squawk object
SquawkSynth Squawk;

// Look up or generate waveform for ProTracker vibrato/tremolo oscillator
static int8_t do_osc(pto_t *p_osc) {
  int8_t sample = 0;
  int16_t mul;
  switch(p_osc->mode & 0x03) {
    case 0: // Sine
      sample = pgm_read_byte(&sine_tbl[(p_osc->offset) & 0x1F]);
      if(p_osc->offset & 0x20) sample = -sample;
      break;
    case 1: // Square
      sample = (p_osc->offset & 0x20) ? 127 : -128;
      break;
    case 2: // Saw
      sample = -(p_osc->offset << 2);
      break;
    case 3: // Noise (random)
      sample = rand();
      break;
  }
  mul = sample * LO4(p_osc->fxp);
  p_osc->offset = (p_osc->offset + HI4(p_osc->fxp));
  return mul >> 6;
}

// Calculates and returns arpeggio period
// Essentially finds period of current note + halftones
static inline uint16_t arpeggio(uint8_t ch, uint8_t halftones) {
  uint8_t n;
  for(n = 0; n != 47; n++) {
    if(fxm[ch].period >= pgm_read_word(&period_tbl[n])) break;
  }
  return pgm_read_word(&period_tbl[MIN(n + halftones, 47)]);
}

// Calculates and returns glissando period
// Essentially snaps a sliding frequency to the closest note
static inline uint16_t glissando(uint8_t ch) {
  uint8_t n;
  uint16_t period_h, period_l;
  for(n = 0; n != 47; n++) {
    period_l = pgm_read_word(&period_tbl[n]);
    period_h = pgm_read_word(&period_tbl[n + 1]);
    if(fxm[ch].period < period_l && fxm[ch].period >= period_h) {
      if(period_l - fxm[ch].period <= fxm[ch].period - period_h) {
        period_h = period_l;
      }
      break;
    }
  }
  return period_h;
}

// Tunes Squawk to a different frequency
void SquawkSynth::tune(float new_tuning) {
  tuning = new_tuning;
  tuning_long = (long)(((double)3669213184.0 / (double)sample_rate) * (double)tuning);

}

// Sets tempo
void SquawkSynth::tempo(uint16_t new_tempo) {
  tick_rate = new_tempo;
  cia = sample_rate / tick_rate; // not atomic?
}

// Initializes Squawk
// Sets up the selected port, and the sample grinding ISR
void SquawkSynth::begin(uint16_t hz) {
  word isr_rr;

  sample_rate = hz;
  tuning_long = (long)(((double)3669213184.0 / (double)sample_rate) * (double)tuning);
  cia = sample_rate / tick_rate;

  if(squawk_register == (intptr_t)&OCR0A) {
    // Squawk uses PWM on OCR0A/PD5(ATMega328/168)/PB7(ATMega32U4)
#ifdef  __AVR_ATmega32U4__
    DDRB  |= 0b10000000; // TODO: FAIL on 32U4
#else
    DDRD  |= 0b01000000;
#endif
    TCCR0A = 0b10000011; // Fast-PWM 8-bit
    TCCR0B = 0b00000001; // 62500Hz
    OCR0A  = 0x7F;
  } else if(squawk_register == (intptr_t)&OCR0B) {
    // Squawk uses PWM on OCR0B/PC5(ATMega328/168)/PD0(ATMega32U4)
#ifdef  __AVR_ATmega32U4__
    DDRD  |= 0b00000001;
#else
    DDRD  |= 0b00100000;
#endif                   // Set timer mode to
    TCCR0A = 0b00100011; // Fast-PWM 8-bit
    TCCR0B = 0b00000001; // 62500Hz
    OCR0B  = 0x7F;
#ifdef OCR2A
  } else if(squawk_register == (intptr_t)&OCR2A) {
    // Squawk uses PWM on OCR2A/PB3
    DDRB  |= 0b00001000; // Set timer mode to
    TCCR2A = 0b10000011; // Fast-PWM 8-bit
    TCCR2B = 0b00000001; // 62500Hz
    OCR2A  = 0x7F;
#endif
#ifdef OCR2B
  } else if(squawk_register == (intptr_t)&OCR2B) {
    // Squawk uses PWM on OCR2B/PD3
    DDRD  |= 0b00001000; // Set timer mode to
    TCCR2A = 0b00100011; // Fast-PWM 8-bit
    TCCR2B = 0b00000001; // 62500Hz
    OCR2B  = 0x7F;
#endif
#ifdef OCR3AL
  } else if(squawk_register == (intptr_t)&OCR3AL) {
    // Squawk uses PWM on OCR3AL/PC6
    DDRC  |= 0b01000000; // Set timer mode to
    TCCR3A = 0b10000001; // Fast-PWM 8-bit
    TCCR3B = 0b00001001; // 62500Hz
    OCR3AH = 0x00;
    OCR3AL = 0x7F;
#endif
  } else if(squawk_register == (intptr_t)&SPDR) {
    // NOT YET SUPPORTED
    // Squawk uses external DAC via SPI
    // TODO: Configure SPI
    // TODO: Needs SS toggle in sample grinder
  } else if(squawk_register == (intptr_t)&PORTB) {
    // NOT YET SUPPORTED
    // Squawk uses resistor ladder on PORTB
    // TODO: Needs shift right in sample grinder
    DDRB   = 0b11111111;
  } else if(squawk_register == (intptr_t)&PORTC) {
    // NOT YET SUPPORTED
    // Squawk uses resistor ladder on PORTC
    // TODO: Needs shift right in sample grinder
    DDRC   = 0b11111111;
  }

  // Seed LFSR (needed for noise)
  osc[3].freq = 0x2000;

  // Set up ISR to run at sample_rate (may not be exact)
  isr_rr = F_CPU / sample_rate;
  TCCR1A = 0b00000000;     // Set timer mode
  TCCR1B = 0b00001001;
  OCR1AH = isr_rr >> 8;    // Set freq
  OCR1AL = isr_rr & 0xFF;
}

// Decrunches a 9 byte row into a useful data
static void decrunch_row() {
  uint8_t data;

  // Initial decrunch
  stream->seek(stream_base + ((order[ix_order] << 6) + ix_row) * 9);
  data = stream->read(); cel[0].fxc  =  data << 0x04;
                         cel[1].fxc  =  data &  0xF0;
  data = stream->read(); cel[0].fxp  =  data;
  data = stream->read(); cel[1].fxp  =  data;
  data = stream->read(); cel[2].fxc  =  data << 0x04;
                         cel[3].fxc  =  data >> 0x04;
  data = stream->read(); cel[2].fxp  =  data;
  data = stream->read(); cel[3].fxp  =  data;
  data = stream->read(); cel[0].ixp  =  data;
  data = stream->read(); cel[1].ixp  =  data;
  data = stream->read(); cel[2].ixp  =  data;

  // Decrunch extended effects
  if(cel[0].fxc == 0xE0) { cel[0].fxc |= cel[0].fxp >> 4; cel[0].fxp &= 0x0F; }
  if(cel[1].fxc == 0xE0) { cel[1].fxc |= cel[1].fxp >> 4; cel[1].fxp &= 0x0F; }
  if(cel[2].fxc == 0xE0) { cel[2].fxc |= cel[2].fxp >> 4; cel[2].fxp &= 0x0F; }

  // Decrunch cell 3 ghetto-style
  cel[3].ixp = ((cel[3].fxp & 0x80) ? 0x00 : 0x7F) | ((cel[3].fxp & 0x40) ? 0x80 : 0x00);
  cel[3].fxp &= 0x3F;
  switch(cel[3].fxc) {
    case 0x02:
    case 0x03: if(cel[3].fxc & 0x01) cel[3].fxp |= 0x40; cel[3].fxp = (cel[3].fxp >> 4) | (cel[3].fxp << 4); cel[3].fxc = 0x70; break;
    case 0x01: if(cel[3].fxp & 0x08) cel[3].fxp = (cel[3].fxp & 0x07) << 4; cel[3].fxc = 0xA0; break;
    case 0x04: cel[3].fxc = 0xC0; break;
    case 0x05: cel[3].fxc = 0xB0; break;
    case 0x06: cel[3].fxc = 0xD0; break;
    case 0x07: cel[3].fxc = 0xF0; break;
    case 0x08: cel[3].fxc = 0xE7; break;
    case 0x09: cel[3].fxc = 0xE9; break;
    case 0x0A: cel[3].fxc = (cel[3].fxp & 0x08) ? 0xEA : 0xEB; cel[3].fxp &= 0x07; break;
    case 0x0B: cel[3].fxc = (cel[3].fxp & 0x10) ? 0xED : 0xEC; cel[3].fxp &= 0x0F; break;
    case 0x0C: cel[3].fxc = 0xEE; break;
  }

  // Apply generic effect parameter memory
  uint8_t ch;
  cel_t *p_cel = cel;
  fxm_t *p_fxm = fxm;
  for(ch = 0; ch != 4; ch++) {
    uint8_t fx = p_cel->fxc;
    if(fx == 0x10 || fx == 0x20 || fx == 0xE1 || fx == 0xE2 || fx == 0x50 || fx == 0x60 || fx == 0xA0) {
      if(p_cel->fxp) {
        p_fxm->param = p_cel->fxp;
      } else {
        p_cel->fxp = p_fxm->param;
      }
    }
    p_cel++; p_fxm++;
  }
}

// Resets playback
static void playroutine_reset() {
  memset(fxm, 0, sizeof(fxm));
  tick         = 0;
  ix_row       = 0;
  ix_order     = 0;
  ix_nextrow   = 0xFF;
  ix_nextorder = 0xFF;
  row_delay    = 0;
  speed        = 6;
  decrunch_row();
}

// Start grinding samples
void SquawkSynth::play() {
  TIMSK1 = 1 << OCIE1A; // Enable interrupt
}

// Load a melody stream and start grinding samples
void SquawkSynth::play(SquawkStream *melody) {
  uint8_t n;
  pause();
  stream = melody;
  stream->seek(0);
  n = stream->read();
  if(n == 'S') {
    // Squawk SD file
    stream->seek(4);
    stream_base = stream->read() << 8;
    stream_base |= stream->read();
    stream_base += 6;
  } else {
    // Squawk ROM array
    stream_base = 1;
  }
  stream->seek(stream_base);
  order_count = stream->read();
  if(order_count <= 64) {
    stream_base += order_count + 1;
    for(n = 0; n < order_count; n++) order[n] = stream->read();
    playroutine_reset();
    play();
  } else {
    order_count = 0;
  }
}

// Load a melody in PROGMEM and start grinding samples
void SquawkSynth::play(const uint8_t *melody) {
  pause();
  rom = StreamROM(melody);
  play(&rom);
}

// Pause playback
void SquawkSynth::pause() {
  TIMSK1 = 0; // Disable interrupt
}

// Stop playing, unload melody
void SquawkSynth::stop() {
  pause();
  order_count = 0; // Unload melody
}

// Progress module by one tick
__attribute__((used)) void squawk_playroutine() {
  static bool lockout = false;

  if(!order_count) return;

  // Protect from re-entry via ISR
  cli();
  if(lockout) {
    sei();
    return;
  }
  lockout = true;
  sei();

  // Handle row delay
  if(row_delay) {
    if(tick == 0) row_delay--;
    // Advance tick
    if(++tick == speed) tick = 0;
  } else {

    // Quick pointer access
    fxm_t *p_fxm = fxm;
    osc_t *p_osc = osc;
    cel_t *p_cel = cel;

    // Temps
    uint8_t ch, fx, fxp;
    bool    pattern_jump = false;
    uint8_t ix_period;

    for(ch = 0; ch != 4; ch++) {
      uint8_t       temp;

      // Local register copy
      fx        = p_cel->fxc;
      fxp       = p_cel->fxp;
      ix_period = p_cel->ixp;

      // If first tick
      if(tick == (fx == 0xED ? fxp : 0)) {

        // Reset volume
        if(ix_period & 0x80) p_osc->vol = p_fxm->volume = 0x20;

        if((ix_period & 0x7F) != 0x7F) {

          // Reset oscillators (unless continous flag set)
          if((p_fxm->vibr.mode & 0x4) == 0x0) p_fxm->vibr.offset = 0;
          if((p_fxm->trem.mode & 0x4) == 0x0) p_fxm->trem.offset = 0;

          // Cell has note
          if(fx == 0x30 || fx == 0x50) {

            // Tone-portamento effect setup
            p_fxm->port_target = pgm_read_word(&period_tbl[ix_period & 0x7F]);
          } else {

            // Set required effect memory parameters
            p_fxm->period = pgm_read_word(&period_tbl[ix_period & 0x7F]);

            // Start note
            if(ch != 3) p_osc->freq = FREQ(p_fxm->period);

          }
        }

        // Effects processed when tick = 0
        switch(fx) {
          case 0x30: // Portamento
            if(fxp) p_fxm->port_speed = fxp;
            break;
          case 0xB0: // Jump to pattern
            ix_nextorder = (fxp >= order_count ? 0x00 : fxp);
            ix_nextrow = 0;
            pattern_jump = true;
            break;
          case 0xC0: // Set volume
            p_osc->vol = p_fxm->volume = MIN(fxp, 0x20);
            break;
          case 0xD0: // Jump to row
            if(!pattern_jump) ix_nextorder = ((ix_order + 1) >= order_count ? 0x00 : ix_order + 1);
            pattern_jump = true;
            ix_nextrow = (fxp > 63 ? 0 : fxp);
            break;
          case 0xF0: // Set speed, BPM(CIA) not supported
            if(fxp <= 0x20) speed = fxp;
            break;
          case 0x40: // Vibrato
            if(fxp) p_fxm->vibr.fxp = fxp;
            break;
          case 0x70: // Tremolo
            if(fxp) p_fxm->trem.fxp = fxp;
            break;
          case 0xE1: // Fine slide up
            if(ch != 3) {
              p_fxm->period = MAX(p_fxm->period - fxp, PERIOD_MIN);
              p_osc->freq = FREQ(p_fxm->period);
            }
            break;
          case 0xE2: // Fine slide down
            if(ch != 3) {
              p_fxm->period = MIN(p_fxm->period + fxp, PERIOD_MAX);
              p_osc->freq = FREQ(p_fxm->period);
            }
            break;
          case 0xE3: // Glissando control
            p_fxm->glissando = (fxp != 0);
            break;
          case 0xE4: // Set vibrato waveform
            p_fxm->vibr.mode = fxp;
            break;
          case 0xE7: // Set tremolo waveform
            p_fxm->trem.mode = fxp;
            break;
          case 0xEA: // Fine volume slide up
            p_osc->vol = p_fxm->volume = MIN(p_fxm->volume + fxp, 0x20);
            break;
          case 0xEB: // Fine volume slide down
            p_osc->vol = p_fxm->volume = MAX(p_fxm->volume - fxp, 0);
            break;
          case 0xEE: // Delay
            row_delay = fxp;
            break;
        }
      } else {

        // Effects processed when tick > 0
        switch(fx) {
          case 0x10: // Slide up
            if(ch != 3) {
              p_fxm->period = MAX(p_fxm->period - fxp, PERIOD_MIN);
              p_osc->freq = FREQ(p_fxm->period);
            }
            break;
          case 0x20: // Slide down
            if(ch != 3) {
              p_fxm->period = MIN(p_fxm->period + fxp, PERIOD_MAX);
              p_osc->freq = FREQ(p_fxm->period);
            }
            break;
/*
          // Just feels... ugly
          case 0xE9: // Retrigger note
            temp = tick; while(temp >= fxp) temp -= fxp;
            if(!temp) {
              if(ch == 3) {
                p_osc->freq = p_osc->phase = 0x2000;
              } else {
                p_osc->phase = 0;
              }
            }
            break;
*/            
          case 0xEC: // Note cut
            if(fxp == tick) p_osc->vol = 0x00;
            break;
          default:   // Multi-effect processing

            // Portamento
            if(ch != 3 && (fx == 0x30 || fx == 0x50)) {
              if(p_fxm->period < p_fxm->port_target) p_fxm->period = MIN(p_fxm->period + p_fxm->port_speed,  p_fxm->port_target);
              else                                   p_fxm->period = MAX(p_fxm->period - p_fxm->port_speed,  p_fxm->port_target);
              if(p_fxm->glissando) p_osc->freq = FREQ(glissando(ch));
              else                 p_osc->freq = FREQ(p_fxm->period);
            }

            // Volume slide
            if(fx == 0x50 || fx == 0x60 || fx == 0xA0) {
              if((fxp & 0xF0) == 0) p_fxm->volume -= (LO4(fxp));
              if((fxp & 0x0F) == 0) p_fxm->volume += (HI4(fxp));
              p_osc->vol = p_fxm->volume = MAX(MIN(p_fxm->volume, 0x20), 0);
            }
        }
      }

      // Normal play and arpeggio
      if(fx == 0x00) {
        if(ch != 3) {
          temp = tick; while(temp > 2) temp -= 2;
          if(temp == 0) {

            // Reset
            p_osc->freq = FREQ(p_fxm->period);
          } else if(fxp) {

            // Arpeggio
            p_osc->freq = FREQ(arpeggio(ch, (temp == 1 ? HI4(fxp) : LO4(fxp))));
          }
        }
      } else if(fx == 0x40 || fx == 0x60) {

        // Vibrato
        if(ch != 3) p_osc->freq = FREQ((p_fxm->period + do_osc(&p_fxm->vibr)));
      } else if(fx == 0x70) {
        int8_t trem = p_fxm->volume + do_osc(&p_fxm->trem);
        p_osc->vol = MAX(MIN(trem, 0x20), 0);
      }

      // Next channel
      p_fxm++; p_cel++; p_osc++;
    }

    // Advance tick
    if(++tick == speed) tick = 0;

    // Advance playback
    if(tick == 0) {
      if(++ix_row == 64) {
        ix_row = 0;
        if(++ix_order >= order_count) ix_order = 0;
      }
	    // Forced order/row
	    if( ix_nextorder != 0xFF ) {
	      ix_order = ix_nextorder;
	      ix_nextorder = 0xFF;
	    }
	    if( ix_nextrow != 0xFF ) {
	      ix_row = ix_nextrow;
	      ix_nextrow = 0xFF;
	    }
			decrunch_row();
    }

  }

  lockout = false;
}
