[136] | 1 | // Squawk Soft-Synthesizer Library for Arduino
|
---|
| 2 | //
|
---|
| 3 | // Davey Taylor 2013
|
---|
| 4 | // d.taylor@arduino.cc
|
---|
| 5 |
|
---|
| 6 | #include "Squawk.h"
|
---|
| 7 |
|
---|
| 8 | // Period range, used for clamping
|
---|
| 9 | #define PERIOD_MIN 28
|
---|
| 10 | #define PERIOD_MAX 3424
|
---|
| 11 |
|
---|
| 12 | // Convenience macros
|
---|
| 13 | #define LO4(V) ((V) & 0x0F)
|
---|
| 14 | #define HI4(V) (((V) & 0xF0) >> 4)
|
---|
| 15 | #define MIN(A, B) ((A) < (B) ? (A) : (B))
|
---|
| 16 | #define MAX(A, B) ((A) > (B) ? (A) : (B))
|
---|
| 17 | #define FREQ(PERIOD) (tuning_long / (PERIOD))
|
---|
| 18 |
|
---|
| 19 | // SquawkStream class for PROGMEM data
|
---|
| 20 | class StreamROM : public SquawkStream {
|
---|
| 21 | private:
|
---|
| 22 | uint8_t *p_start;
|
---|
| 23 | uint8_t *p_cursor;
|
---|
| 24 | public:
|
---|
| 25 | StreamROM(const uint8_t *p_rom = NULL) { p_start = p_cursor = (uint8_t*)p_rom; }
|
---|
| 26 | uint8_t read() { return pgm_read_byte(p_cursor++); }
|
---|
| 27 | void seek(size_t offset) { p_cursor = p_start + offset; }
|
---|
| 28 | };
|
---|
| 29 |
|
---|
| 30 | // Oscillator memory
|
---|
| 31 | typedef struct {
|
---|
| 32 | uint8_t fxp;
|
---|
| 33 | uint8_t offset;
|
---|
| 34 | uint8_t mode;
|
---|
| 35 | } pto_t;
|
---|
| 36 |
|
---|
| 37 | // Deconstructed cell
|
---|
| 38 | typedef struct {
|
---|
| 39 | uint8_t fxc, fxp, ixp;
|
---|
| 40 | } cel_t;
|
---|
| 41 |
|
---|
| 42 | // Effect memory
|
---|
| 43 | typedef struct {
|
---|
| 44 | int8_t volume;
|
---|
| 45 | uint8_t port_speed;
|
---|
| 46 | uint16_t port_target;
|
---|
| 47 | bool glissando;
|
---|
| 48 | pto_t vibr;
|
---|
| 49 | pto_t trem;
|
---|
| 50 | uint16_t period;
|
---|
| 51 | uint8_t param;
|
---|
| 52 | } fxm_t;
|
---|
| 53 |
|
---|
| 54 | // Locals
|
---|
| 55 | static uint8_t order_count;
|
---|
| 56 | static uint8_t order[64];
|
---|
| 57 | static uint8_t speed;
|
---|
| 58 | static uint8_t tick;
|
---|
| 59 | static uint8_t ix_row;
|
---|
| 60 | static uint8_t ix_order;
|
---|
| 61 | static uint8_t ix_nextrow;
|
---|
| 62 | static uint8_t ix_nextorder;
|
---|
| 63 | static uint8_t row_delay;
|
---|
| 64 | static fxm_t fxm[4];
|
---|
| 65 | static cel_t cel[4];
|
---|
| 66 | static uint32_t tuning_long;
|
---|
| 67 | static uint16_t sample_rate;
|
---|
| 68 | static float tuning = 1.0;
|
---|
| 69 | static uint16_t tick_rate = 50;
|
---|
| 70 |
|
---|
| 71 | static SquawkStream *stream;
|
---|
| 72 | static uint16_t stream_base;
|
---|
| 73 | static StreamROM rom;
|
---|
| 74 |
|
---|
| 75 | // Imports
|
---|
| 76 | extern intptr_t squawk_register;
|
---|
| 77 | extern uint16_t cia;
|
---|
| 78 |
|
---|
| 79 | // Exports
|
---|
| 80 | osc_t osc[4];
|
---|
| 81 | uint8_t pcm = 128;
|
---|
| 82 |
|
---|
| 83 | // ProTracker period tables
|
---|
| 84 | const uint16_t period_tbl[84] PROGMEM = {
|
---|
| 85 | 3424, 3232, 3048, 2880, 2712, 2560, 2416, 2280, 2152, 2032, 1920, 1814,
|
---|
| 86 | 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960, 907,
|
---|
| 87 | 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453,
|
---|
| 88 | 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226,
|
---|
| 89 | 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113,
|
---|
| 90 | 107, 101, 95, 90, 85, 80, 75, 71, 67, 63, 60, 56,
|
---|
| 91 | 53, 50, 47, 45, 42, 40, 37, 35, 33, 31, 30, 28,
|
---|
| 92 | };
|
---|
| 93 |
|
---|
| 94 | // ProTracker sine table
|
---|
| 95 | const int8_t sine_tbl[32] PROGMEM = {
|
---|
| 96 | 0x00, 0x0C, 0x18, 0x25, 0x30, 0x3C, 0x47, 0x51, 0x5A, 0x62, 0x6A, 0x70, 0x76, 0x7A, 0x7D, 0x7F,
|
---|
| 97 | 0x7F, 0x7F, 0x7D, 0x7A, 0x76, 0x70, 0x6A, 0x62, 0x5A, 0x51, 0x47, 0x3C, 0x30, 0x25, 0x18, 0x0C,
|
---|
| 98 | };
|
---|
| 99 |
|
---|
| 100 | // Squawk object
|
---|
| 101 | SquawkSynth Squawk;
|
---|
| 102 |
|
---|
| 103 | // Look up or generate waveform for ProTracker vibrato/tremolo oscillator
|
---|
| 104 | static int8_t do_osc(pto_t *p_osc) {
|
---|
| 105 | int8_t sample = 0;
|
---|
| 106 | int16_t mul;
|
---|
| 107 | switch(p_osc->mode & 0x03) {
|
---|
| 108 | case 0: // Sine
|
---|
| 109 | sample = pgm_read_byte(&sine_tbl[(p_osc->offset) & 0x1F]);
|
---|
| 110 | if(p_osc->offset & 0x20) sample = -sample;
|
---|
| 111 | break;
|
---|
| 112 | case 1: // Square
|
---|
| 113 | sample = (p_osc->offset & 0x20) ? 127 : -128;
|
---|
| 114 | break;
|
---|
| 115 | case 2: // Saw
|
---|
| 116 | sample = -(p_osc->offset << 2);
|
---|
| 117 | break;
|
---|
| 118 | case 3: // Noise (random)
|
---|
| 119 | sample = rand();
|
---|
| 120 | break;
|
---|
| 121 | }
|
---|
| 122 | mul = sample * LO4(p_osc->fxp);
|
---|
| 123 | p_osc->offset = (p_osc->offset + HI4(p_osc->fxp));
|
---|
| 124 | return mul >> 6;
|
---|
| 125 | }
|
---|
| 126 |
|
---|
| 127 | // Calculates and returns arpeggio period
|
---|
| 128 | // Essentially finds period of current note + halftones
|
---|
| 129 | static inline uint16_t arpeggio(uint8_t ch, uint8_t halftones) {
|
---|
| 130 | uint8_t n;
|
---|
| 131 | for(n = 0; n != 47; n++) {
|
---|
| 132 | if(fxm[ch].period >= pgm_read_word(&period_tbl[n])) break;
|
---|
| 133 | }
|
---|
| 134 | return pgm_read_word(&period_tbl[MIN(n + halftones, 47)]);
|
---|
| 135 | }
|
---|
| 136 |
|
---|
| 137 | // Calculates and returns glissando period
|
---|
| 138 | // Essentially snaps a sliding frequency to the closest note
|
---|
| 139 | static inline uint16_t glissando(uint8_t ch) {
|
---|
| 140 | uint8_t n;
|
---|
| 141 | uint16_t period_h, period_l;
|
---|
| 142 | for(n = 0; n != 47; n++) {
|
---|
| 143 | period_l = pgm_read_word(&period_tbl[n]);
|
---|
| 144 | period_h = pgm_read_word(&period_tbl[n + 1]);
|
---|
| 145 | if(fxm[ch].period < period_l && fxm[ch].period >= period_h) {
|
---|
| 146 | if(period_l - fxm[ch].period <= fxm[ch].period - period_h) {
|
---|
| 147 | period_h = period_l;
|
---|
| 148 | }
|
---|
| 149 | break;
|
---|
| 150 | }
|
---|
| 151 | }
|
---|
| 152 | return period_h;
|
---|
| 153 | }
|
---|
| 154 |
|
---|
| 155 | // Tunes Squawk to a different frequency
|
---|
| 156 | void SquawkSynth::tune(float new_tuning) {
|
---|
| 157 | tuning = new_tuning;
|
---|
| 158 | tuning_long = (long)(((double)3669213184.0 / (double)sample_rate) * (double)tuning);
|
---|
| 159 |
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | // Sets tempo
|
---|
| 163 | void SquawkSynth::tempo(uint16_t new_tempo) {
|
---|
| 164 | tick_rate = new_tempo;
|
---|
| 165 | cia = sample_rate / tick_rate; // not atomic?
|
---|
| 166 | }
|
---|
| 167 |
|
---|
| 168 | // Initializes Squawk
|
---|
| 169 | // Sets up the selected port, and the sample grinding ISR
|
---|
| 170 | void SquawkSynth::begin(uint16_t hz) {
|
---|
| 171 | word isr_rr;
|
---|
| 172 |
|
---|
| 173 | sample_rate = hz;
|
---|
| 174 | tuning_long = (long)(((double)3669213184.0 / (double)sample_rate) * (double)tuning);
|
---|
| 175 | cia = sample_rate / tick_rate;
|
---|
| 176 |
|
---|
| 177 | if(squawk_register == (intptr_t)&OCR0A) {
|
---|
| 178 | // Squawk uses PWM on OCR0A/PD5(ATMega328/168)/PB7(ATMega32U4)
|
---|
| 179 | #ifdef __AVR_ATmega32U4__
|
---|
| 180 | DDRB |= 0b10000000; // TODO: FAIL on 32U4
|
---|
| 181 | #else
|
---|
| 182 | DDRD |= 0b01000000;
|
---|
| 183 | #endif
|
---|
| 184 | TCCR0A = 0b10000011; // Fast-PWM 8-bit
|
---|
| 185 | TCCR0B = 0b00000001; // 62500Hz
|
---|
| 186 | OCR0A = 0x7F;
|
---|
| 187 | } else if(squawk_register == (intptr_t)&OCR0B) {
|
---|
| 188 | // Squawk uses PWM on OCR0B/PC5(ATMega328/168)/PD0(ATMega32U4)
|
---|
| 189 | #ifdef __AVR_ATmega32U4__
|
---|
| 190 | DDRD |= 0b00000001;
|
---|
| 191 | #else
|
---|
| 192 | DDRD |= 0b00100000;
|
---|
| 193 | #endif // Set timer mode to
|
---|
| 194 | TCCR0A = 0b00100011; // Fast-PWM 8-bit
|
---|
| 195 | TCCR0B = 0b00000001; // 62500Hz
|
---|
| 196 | OCR0B = 0x7F;
|
---|
| 197 | #ifdef OCR2A
|
---|
| 198 | } else if(squawk_register == (intptr_t)&OCR2A) {
|
---|
| 199 | // Squawk uses PWM on OCR2A/PB3
|
---|
| 200 | DDRB |= 0b00001000; // Set timer mode to
|
---|
| 201 | TCCR2A = 0b10000011; // Fast-PWM 8-bit
|
---|
| 202 | TCCR2B = 0b00000001; // 62500Hz
|
---|
| 203 | OCR2A = 0x7F;
|
---|
| 204 | #endif
|
---|
| 205 | #ifdef OCR2B
|
---|
| 206 | } else if(squawk_register == (intptr_t)&OCR2B) {
|
---|
| 207 | // Squawk uses PWM on OCR2B/PD3
|
---|
| 208 | DDRD |= 0b00001000; // Set timer mode to
|
---|
| 209 | TCCR2A = 0b00100011; // Fast-PWM 8-bit
|
---|
| 210 | TCCR2B = 0b00000001; // 62500Hz
|
---|
| 211 | OCR2B = 0x7F;
|
---|
| 212 | #endif
|
---|
| 213 | #ifdef OCR3AL
|
---|
| 214 | } else if(squawk_register == (intptr_t)&OCR3AL) {
|
---|
| 215 | // Squawk uses PWM on OCR3AL/PC6
|
---|
| 216 | DDRC |= 0b01000000; // Set timer mode to
|
---|
| 217 | TCCR3A = 0b10000001; // Fast-PWM 8-bit
|
---|
| 218 | TCCR3B = 0b00001001; // 62500Hz
|
---|
| 219 | OCR3AH = 0x00;
|
---|
| 220 | OCR3AL = 0x7F;
|
---|
| 221 | #endif
|
---|
| 222 | } else if(squawk_register == (intptr_t)&SPDR) {
|
---|
| 223 | // NOT YET SUPPORTED
|
---|
| 224 | // Squawk uses external DAC via SPI
|
---|
| 225 | // TODO: Configure SPI
|
---|
| 226 | // TODO: Needs SS toggle in sample grinder
|
---|
| 227 | } else if(squawk_register == (intptr_t)&PORTB) {
|
---|
| 228 | // NOT YET SUPPORTED
|
---|
| 229 | // Squawk uses resistor ladder on PORTB
|
---|
| 230 | // TODO: Needs shift right in sample grinder
|
---|
| 231 | DDRB = 0b11111111;
|
---|
| 232 | } else if(squawk_register == (intptr_t)&PORTC) {
|
---|
| 233 | // NOT YET SUPPORTED
|
---|
| 234 | // Squawk uses resistor ladder on PORTC
|
---|
| 235 | // TODO: Needs shift right in sample grinder
|
---|
| 236 | DDRC = 0b11111111;
|
---|
| 237 | }
|
---|
| 238 |
|
---|
| 239 | // Seed LFSR (needed for noise)
|
---|
| 240 | osc[3].freq = 0x2000;
|
---|
| 241 |
|
---|
| 242 | // Set up ISR to run at sample_rate (may not be exact)
|
---|
| 243 | isr_rr = F_CPU / sample_rate;
|
---|
| 244 | TCCR1A = 0b00000000; // Set timer mode
|
---|
| 245 | TCCR1B = 0b00001001;
|
---|
| 246 | OCR1AH = isr_rr >> 8; // Set freq
|
---|
| 247 | OCR1AL = isr_rr & 0xFF;
|
---|
| 248 | }
|
---|
| 249 |
|
---|
| 250 | // Decrunches a 9 byte row into a useful data
|
---|
| 251 | static void decrunch_row() {
|
---|
| 252 | uint8_t data;
|
---|
| 253 |
|
---|
| 254 | // Initial decrunch
|
---|
| 255 | stream->seek(stream_base + ((order[ix_order] << 6) + ix_row) * 9);
|
---|
| 256 | data = stream->read(); cel[0].fxc = data << 0x04;
|
---|
| 257 | cel[1].fxc = data & 0xF0;
|
---|
| 258 | data = stream->read(); cel[0].fxp = data;
|
---|
| 259 | data = stream->read(); cel[1].fxp = data;
|
---|
| 260 | data = stream->read(); cel[2].fxc = data << 0x04;
|
---|
| 261 | cel[3].fxc = data >> 0x04;
|
---|
| 262 | data = stream->read(); cel[2].fxp = data;
|
---|
| 263 | data = stream->read(); cel[3].fxp = data;
|
---|
| 264 | data = stream->read(); cel[0].ixp = data;
|
---|
| 265 | data = stream->read(); cel[1].ixp = data;
|
---|
| 266 | data = stream->read(); cel[2].ixp = data;
|
---|
| 267 |
|
---|
| 268 | // Decrunch extended effects
|
---|
| 269 | if(cel[0].fxc == 0xE0) { cel[0].fxc |= cel[0].fxp >> 4; cel[0].fxp &= 0x0F; }
|
---|
| 270 | if(cel[1].fxc == 0xE0) { cel[1].fxc |= cel[1].fxp >> 4; cel[1].fxp &= 0x0F; }
|
---|
| 271 | if(cel[2].fxc == 0xE0) { cel[2].fxc |= cel[2].fxp >> 4; cel[2].fxp &= 0x0F; }
|
---|
| 272 |
|
---|
| 273 | // Decrunch cell 3 ghetto-style
|
---|
| 274 | cel[3].ixp = ((cel[3].fxp & 0x80) ? 0x00 : 0x7F) | ((cel[3].fxp & 0x40) ? 0x80 : 0x00);
|
---|
| 275 | cel[3].fxp &= 0x3F;
|
---|
| 276 | switch(cel[3].fxc) {
|
---|
| 277 | case 0x02:
|
---|
| 278 | 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;
|
---|
| 279 | case 0x01: if(cel[3].fxp & 0x08) cel[3].fxp = (cel[3].fxp & 0x07) << 4; cel[3].fxc = 0xA0; break;
|
---|
| 280 | case 0x04: cel[3].fxc = 0xC0; break;
|
---|
| 281 | case 0x05: cel[3].fxc = 0xB0; break;
|
---|
| 282 | case 0x06: cel[3].fxc = 0xD0; break;
|
---|
| 283 | case 0x07: cel[3].fxc = 0xF0; break;
|
---|
| 284 | case 0x08: cel[3].fxc = 0xE7; break;
|
---|
| 285 | case 0x09: cel[3].fxc = 0xE9; break;
|
---|
| 286 | case 0x0A: cel[3].fxc = (cel[3].fxp & 0x08) ? 0xEA : 0xEB; cel[3].fxp &= 0x07; break;
|
---|
| 287 | case 0x0B: cel[3].fxc = (cel[3].fxp & 0x10) ? 0xED : 0xEC; cel[3].fxp &= 0x0F; break;
|
---|
| 288 | case 0x0C: cel[3].fxc = 0xEE; break;
|
---|
| 289 | }
|
---|
| 290 |
|
---|
| 291 | // Apply generic effect parameter memory
|
---|
| 292 | uint8_t ch;
|
---|
| 293 | cel_t *p_cel = cel;
|
---|
| 294 | fxm_t *p_fxm = fxm;
|
---|
| 295 | for(ch = 0; ch != 4; ch++) {
|
---|
| 296 | uint8_t fx = p_cel->fxc;
|
---|
| 297 | if(fx == 0x10 || fx == 0x20 || fx == 0xE1 || fx == 0xE2 || fx == 0x50 || fx == 0x60 || fx == 0xA0) {
|
---|
| 298 | if(p_cel->fxp) {
|
---|
| 299 | p_fxm->param = p_cel->fxp;
|
---|
| 300 | } else {
|
---|
| 301 | p_cel->fxp = p_fxm->param;
|
---|
| 302 | }
|
---|
| 303 | }
|
---|
| 304 | p_cel++; p_fxm++;
|
---|
| 305 | }
|
---|
| 306 | }
|
---|
| 307 |
|
---|
| 308 | // Resets playback
|
---|
| 309 | static void playroutine_reset() {
|
---|
| 310 | memset(fxm, 0, sizeof(fxm));
|
---|
| 311 | tick = 0;
|
---|
| 312 | ix_row = 0;
|
---|
| 313 | ix_order = 0;
|
---|
| 314 | ix_nextrow = 0xFF;
|
---|
| 315 | ix_nextorder = 0xFF;
|
---|
| 316 | row_delay = 0;
|
---|
| 317 | speed = 6;
|
---|
| 318 | decrunch_row();
|
---|
| 319 | }
|
---|
| 320 |
|
---|
| 321 | // Start grinding samples
|
---|
| 322 | void SquawkSynth::play() {
|
---|
| 323 | TIMSK1 = 1 << OCIE1A; // Enable interrupt
|
---|
| 324 | }
|
---|
| 325 |
|
---|
| 326 | // Load a melody stream and start grinding samples
|
---|
| 327 | void SquawkSynth::play(SquawkStream *melody) {
|
---|
| 328 | uint8_t n;
|
---|
| 329 | pause();
|
---|
| 330 | stream = melody;
|
---|
| 331 | stream->seek(0);
|
---|
| 332 | n = stream->read();
|
---|
| 333 | if(n == 'S') {
|
---|
| 334 | // Squawk SD file
|
---|
| 335 | stream->seek(4);
|
---|
| 336 | stream_base = stream->read() << 8;
|
---|
| 337 | stream_base |= stream->read();
|
---|
| 338 | stream_base += 6;
|
---|
| 339 | } else {
|
---|
| 340 | // Squawk ROM array
|
---|
| 341 | stream_base = 1;
|
---|
| 342 | }
|
---|
| 343 | stream->seek(stream_base);
|
---|
| 344 | order_count = stream->read();
|
---|
| 345 | if(order_count <= 64) {
|
---|
| 346 | stream_base += order_count + 1;
|
---|
| 347 | for(n = 0; n < order_count; n++) order[n] = stream->read();
|
---|
| 348 | playroutine_reset();
|
---|
| 349 | play();
|
---|
| 350 | } else {
|
---|
| 351 | order_count = 0;
|
---|
| 352 | }
|
---|
| 353 | }
|
---|
| 354 |
|
---|
| 355 | // Load a melody in PROGMEM and start grinding samples
|
---|
| 356 | void SquawkSynth::play(const uint8_t *melody) {
|
---|
| 357 | pause();
|
---|
| 358 | rom = StreamROM(melody);
|
---|
| 359 | play(&rom);
|
---|
| 360 | }
|
---|
| 361 |
|
---|
| 362 | // Pause playback
|
---|
| 363 | void SquawkSynth::pause() {
|
---|
| 364 | TIMSK1 = 0; // Disable interrupt
|
---|
| 365 | }
|
---|
| 366 |
|
---|
| 367 | // Stop playing, unload melody
|
---|
| 368 | void SquawkSynth::stop() {
|
---|
| 369 | pause();
|
---|
| 370 | order_count = 0; // Unload melody
|
---|
| 371 | }
|
---|
| 372 |
|
---|
| 373 | // Progress module by one tick
|
---|
| 374 | void squawk_playroutine() {
|
---|
| 375 | static bool lockout = false;
|
---|
| 376 |
|
---|
| 377 | if(!order_count) return;
|
---|
| 378 |
|
---|
| 379 | // Protect from re-entry via ISR
|
---|
| 380 | cli();
|
---|
| 381 | if(lockout) {
|
---|
| 382 | sei();
|
---|
| 383 | return;
|
---|
| 384 | }
|
---|
| 385 | lockout = true;
|
---|
| 386 | sei();
|
---|
| 387 |
|
---|
| 388 | // Handle row delay
|
---|
| 389 | if(row_delay) {
|
---|
| 390 | if(tick == 0) row_delay--;
|
---|
| 391 | // Advance tick
|
---|
| 392 | if(++tick == speed) tick = 0;
|
---|
| 393 | } else {
|
---|
| 394 |
|
---|
| 395 | // Quick pointer access
|
---|
| 396 | fxm_t *p_fxm = fxm;
|
---|
| 397 | osc_t *p_osc = osc;
|
---|
| 398 | cel_t *p_cel = cel;
|
---|
| 399 |
|
---|
| 400 | // Temps
|
---|
| 401 | uint8_t ch, fx, fxp;
|
---|
| 402 | bool pattern_jump = false;
|
---|
| 403 | uint8_t ix_period;
|
---|
| 404 |
|
---|
| 405 | for(ch = 0; ch != 4; ch++) {
|
---|
| 406 | uint8_t temp;
|
---|
| 407 |
|
---|
| 408 | // Local register copy
|
---|
| 409 | fx = p_cel->fxc;
|
---|
| 410 | fxp = p_cel->fxp;
|
---|
| 411 | ix_period = p_cel->ixp;
|
---|
| 412 |
|
---|
| 413 | // If first tick
|
---|
| 414 | if(tick == (fx == 0xED ? fxp : 0)) {
|
---|
| 415 |
|
---|
| 416 | // Reset volume
|
---|
| 417 | if(ix_period & 0x80) p_osc->vol = p_fxm->volume = 0x20;
|
---|
| 418 |
|
---|
| 419 | if((ix_period & 0x7F) != 0x7F) {
|
---|
| 420 |
|
---|
| 421 | // Reset oscillators (unless continous flag set)
|
---|
| 422 | if((p_fxm->vibr.mode & 0x4) == 0x0) p_fxm->vibr.offset = 0;
|
---|
| 423 | if((p_fxm->trem.mode & 0x4) == 0x0) p_fxm->trem.offset = 0;
|
---|
| 424 |
|
---|
| 425 | // Cell has note
|
---|
| 426 | if(fx == 0x30 || fx == 0x50) {
|
---|
| 427 |
|
---|
| 428 | // Tone-portamento effect setup
|
---|
| 429 | p_fxm->port_target = pgm_read_word(&period_tbl[ix_period & 0x7F]);
|
---|
| 430 | } else {
|
---|
| 431 |
|
---|
| 432 | // Set required effect memory parameters
|
---|
| 433 | p_fxm->period = pgm_read_word(&period_tbl[ix_period & 0x7F]);
|
---|
| 434 |
|
---|
| 435 | // Start note
|
---|
| 436 | if(ch != 3) p_osc->freq = FREQ(p_fxm->period);
|
---|
| 437 |
|
---|
| 438 | }
|
---|
| 439 | }
|
---|
| 440 |
|
---|
| 441 | // Effects processed when tick = 0
|
---|
| 442 | switch(fx) {
|
---|
| 443 | case 0x30: // Portamento
|
---|
| 444 | if(fxp) p_fxm->port_speed = fxp;
|
---|
| 445 | break;
|
---|
| 446 | case 0xB0: // Jump to pattern
|
---|
| 447 | ix_nextorder = (fxp >= order_count ? 0x00 : fxp);
|
---|
| 448 | ix_nextrow = 0;
|
---|
| 449 | pattern_jump = true;
|
---|
| 450 | break;
|
---|
| 451 | case 0xC0: // Set volume
|
---|
| 452 | p_osc->vol = p_fxm->volume = MIN(fxp, 0x20);
|
---|
| 453 | break;
|
---|
| 454 | case 0xD0: // Jump to row
|
---|
| 455 | if(!pattern_jump) ix_nextorder = ((ix_order + 1) >= order_count ? 0x00 : ix_order + 1);
|
---|
| 456 | pattern_jump = true;
|
---|
| 457 | ix_nextrow = (fxp > 63 ? 0 : fxp);
|
---|
| 458 | break;
|
---|
| 459 | case 0xF0: // Set speed, BPM(CIA) not supported
|
---|
| 460 | if(fxp <= 0x20) speed = fxp;
|
---|
| 461 | break;
|
---|
| 462 | case 0x40: // Vibrato
|
---|
| 463 | if(fxp) p_fxm->vibr.fxp = fxp;
|
---|
| 464 | break;
|
---|
| 465 | case 0x70: // Tremolo
|
---|
| 466 | if(fxp) p_fxm->trem.fxp = fxp;
|
---|
| 467 | break;
|
---|
| 468 | case 0xE1: // Fine slide up
|
---|
| 469 | if(ch != 3) {
|
---|
| 470 | p_fxm->period = MAX(p_fxm->period - fxp, PERIOD_MIN);
|
---|
| 471 | p_osc->freq = FREQ(p_fxm->period);
|
---|
| 472 | }
|
---|
| 473 | break;
|
---|
| 474 | case 0xE2: // Fine slide down
|
---|
| 475 | if(ch != 3) {
|
---|
| 476 | p_fxm->period = MIN(p_fxm->period + fxp, PERIOD_MAX);
|
---|
| 477 | p_osc->freq = FREQ(p_fxm->period);
|
---|
| 478 | }
|
---|
| 479 | break;
|
---|
| 480 | case 0xE3: // Glissando control
|
---|
| 481 | p_fxm->glissando = (fxp != 0);
|
---|
| 482 | break;
|
---|
| 483 | case 0xE4: // Set vibrato waveform
|
---|
| 484 | p_fxm->vibr.mode = fxp;
|
---|
| 485 | break;
|
---|
| 486 | case 0xE7: // Set tremolo waveform
|
---|
| 487 | p_fxm->trem.mode = fxp;
|
---|
| 488 | break;
|
---|
| 489 | case 0xEA: // Fine volume slide up
|
---|
| 490 | p_osc->vol = p_fxm->volume = MIN(p_fxm->volume + fxp, 0x20);
|
---|
| 491 | break;
|
---|
| 492 | case 0xEB: // Fine volume slide down
|
---|
| 493 | p_osc->vol = p_fxm->volume = MAX(p_fxm->volume - fxp, 0);
|
---|
| 494 | break;
|
---|
| 495 | case 0xEE: // Delay
|
---|
| 496 | row_delay = fxp;
|
---|
| 497 | break;
|
---|
| 498 | }
|
---|
| 499 | } else {
|
---|
| 500 |
|
---|
| 501 | // Effects processed when tick > 0
|
---|
| 502 | switch(fx) {
|
---|
| 503 | case 0x10: // Slide up
|
---|
| 504 | if(ch != 3) {
|
---|
| 505 | p_fxm->period = MAX(p_fxm->period - fxp, PERIOD_MIN);
|
---|
| 506 | p_osc->freq = FREQ(p_fxm->period);
|
---|
| 507 | }
|
---|
| 508 | break;
|
---|
| 509 | case 0x20: // Slide down
|
---|
| 510 | if(ch != 3) {
|
---|
| 511 | p_fxm->period = MIN(p_fxm->period + fxp, PERIOD_MAX);
|
---|
| 512 | p_osc->freq = FREQ(p_fxm->period);
|
---|
| 513 | }
|
---|
| 514 | break;
|
---|
| 515 | /*
|
---|
| 516 | // Just feels... ugly
|
---|
| 517 | case 0xE9: // Retrigger note
|
---|
| 518 | temp = tick; while(temp >= fxp) temp -= fxp;
|
---|
| 519 | if(!temp) {
|
---|
| 520 | if(ch == 3) {
|
---|
| 521 | p_osc->freq = p_osc->phase = 0x2000;
|
---|
| 522 | } else {
|
---|
| 523 | p_osc->phase = 0;
|
---|
| 524 | }
|
---|
| 525 | }
|
---|
| 526 | break;
|
---|
| 527 | */
|
---|
| 528 | case 0xEC: // Note cut
|
---|
| 529 | if(fxp == tick) p_osc->vol = 0x00;
|
---|
| 530 | break;
|
---|
| 531 | default: // Multi-effect processing
|
---|
| 532 |
|
---|
| 533 | // Portamento
|
---|
| 534 | if(ch != 3 && (fx == 0x30 || fx == 0x50)) {
|
---|
| 535 | if(p_fxm->period < p_fxm->port_target) p_fxm->period = MIN(p_fxm->period + p_fxm->port_speed, p_fxm->port_target);
|
---|
| 536 | else p_fxm->period = MAX(p_fxm->period - p_fxm->port_speed, p_fxm->port_target);
|
---|
| 537 | if(p_fxm->glissando) p_osc->freq = FREQ(glissando(ch));
|
---|
| 538 | else p_osc->freq = FREQ(p_fxm->period);
|
---|
| 539 | }
|
---|
| 540 |
|
---|
| 541 | // Volume slide
|
---|
| 542 | if(fx == 0x50 || fx == 0x60 || fx == 0xA0) {
|
---|
| 543 | if((fxp & 0xF0) == 0) p_fxm->volume -= (LO4(fxp));
|
---|
| 544 | if((fxp & 0x0F) == 0) p_fxm->volume += (HI4(fxp));
|
---|
| 545 | p_osc->vol = p_fxm->volume = MAX(MIN(p_fxm->volume, 0x20), 0);
|
---|
| 546 | }
|
---|
| 547 | }
|
---|
| 548 | }
|
---|
| 549 |
|
---|
| 550 | // Normal play and arpeggio
|
---|
| 551 | if(fx == 0x00) {
|
---|
| 552 | if(ch != 3) {
|
---|
| 553 | temp = tick; while(temp > 2) temp -= 2;
|
---|
| 554 | if(temp == 0) {
|
---|
| 555 |
|
---|
| 556 | // Reset
|
---|
| 557 | p_osc->freq = FREQ(p_fxm->period);
|
---|
| 558 | } else if(fxp) {
|
---|
| 559 |
|
---|
| 560 | // Arpeggio
|
---|
| 561 | p_osc->freq = FREQ(arpeggio(ch, (temp == 1 ? HI4(fxp) : LO4(fxp))));
|
---|
| 562 | }
|
---|
| 563 | }
|
---|
| 564 | } else if(fx == 0x40 || fx == 0x60) {
|
---|
| 565 |
|
---|
| 566 | // Vibrato
|
---|
| 567 | if(ch != 3) p_osc->freq = FREQ((p_fxm->period + do_osc(&p_fxm->vibr)));
|
---|
| 568 | } else if(fx == 0x70) {
|
---|
| 569 | int8_t trem = p_fxm->volume + do_osc(&p_fxm->trem);
|
---|
| 570 | p_osc->vol = MAX(MIN(trem, 0x20), 0);
|
---|
| 571 | }
|
---|
| 572 |
|
---|
| 573 | // Next channel
|
---|
| 574 | p_fxm++; p_cel++; p_osc++;
|
---|
| 575 | }
|
---|
| 576 |
|
---|
| 577 | // Advance tick
|
---|
| 578 | if(++tick == speed) tick = 0;
|
---|
| 579 |
|
---|
| 580 | // Advance playback
|
---|
| 581 | if(tick == 0) {
|
---|
| 582 | if(++ix_row == 64) {
|
---|
| 583 | ix_row = 0;
|
---|
| 584 | if(++ix_order >= order_count) ix_order = 0;
|
---|
| 585 | }
|
---|
| 586 | // Forced order/row
|
---|
| 587 | if( ix_nextorder != 0xFF ) {
|
---|
| 588 | ix_order = ix_nextorder;
|
---|
| 589 | ix_nextorder = 0xFF;
|
---|
| 590 | }
|
---|
| 591 | if( ix_nextrow != 0xFF ) {
|
---|
| 592 | ix_row = ix_nextrow;
|
---|
| 593 | ix_nextrow = 0xFF;
|
---|
| 594 | }
|
---|
| 595 | decrunch_row();
|
---|
| 596 | }
|
---|
| 597 |
|
---|
| 598 | }
|
---|
| 599 |
|
---|
| 600 | lockout = false;
|
---|
| 601 | }
|
---|