source: rtos_arduino/trunk/arduino_lib/libraries/Robot_Control/src/Squawk.cpp@ 136

Last change on this file since 136 was 136, checked in by ertl-honda, 8 years ago

ライブラリとOS及びベーシックなサンプルの追加.

File size: 17.1 KB
Line 
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
20class 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
31typedef struct {
32 uint8_t fxp;
33 uint8_t offset;
34 uint8_t mode;
35} pto_t;
36
37// Deconstructed cell
38typedef struct {
39 uint8_t fxc, fxp, ixp;
40} cel_t;
41
42// Effect memory
43typedef 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
55static uint8_t order_count;
56static uint8_t order[64];
57static uint8_t speed;
58static uint8_t tick;
59static uint8_t ix_row;
60static uint8_t ix_order;
61static uint8_t ix_nextrow;
62static uint8_t ix_nextorder;
63static uint8_t row_delay;
64static fxm_t fxm[4];
65static cel_t cel[4];
66static uint32_t tuning_long;
67static uint16_t sample_rate;
68static float tuning = 1.0;
69static uint16_t tick_rate = 50;
70
71static SquawkStream *stream;
72static uint16_t stream_base;
73static StreamROM rom;
74
75// Imports
76extern intptr_t squawk_register;
77extern uint16_t cia;
78
79// Exports
80osc_t osc[4];
81uint8_t pcm = 128;
82
83// ProTracker period tables
84const 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
95const 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
101SquawkSynth Squawk;
102
103// Look up or generate waveform for ProTracker vibrato/tremolo oscillator
104static 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
129static 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
139static 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
156void 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
163void 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
170void 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
251static 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
309static 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
322void SquawkSynth::play() {
323 TIMSK1 = 1 << OCIE1A; // Enable interrupt
324}
325
326// Load a melody stream and start grinding samples
327void 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
356void SquawkSynth::play(const uint8_t *melody) {
357 pause();
358 rom = StreamROM(melody);
359 play(&rom);
360}
361
362// Pause playback
363void SquawkSynth::pause() {
364 TIMSK1 = 0; // Disable interrupt
365}
366
367// Stop playing, unload melody
368void SquawkSynth::stop() {
369 pause();
370 order_count = 0; // Unload melody
371}
372
373// Progress module by one tick
374void 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}
Note: See TracBrowser for help on using the repository browser.