aboutsummaryrefslogtreecommitdiff
path: root/libraries/Robot_Control/Squawk.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/Robot_Control/Squawk.cpp')
-rw-r--r--libraries/Robot_Control/Squawk.cpp601
1 files changed, 0 insertions, 601 deletions
diff --git a/libraries/Robot_Control/Squawk.cpp b/libraries/Robot_Control/Squawk.cpp
deleted file mode 100644
index 5b39ebe..0000000
--- a/libraries/Robot_Control/Squawk.cpp
+++ /dev/null
@@ -1,601 +0,0 @@
-// 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
-osc_t osc[4];
-uint8_t pcm = 128;
-
-// ProTracker period tables
-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
-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
-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;
-} \ No newline at end of file