diff options
Diffstat (limited to 'libraries/Robot_Control/Squawk.cpp')
-rw-r--r-- | libraries/Robot_Control/Squawk.cpp | 601 |
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 |