#include "Arduboy.h" #include "audio.h" const byte PROGMEM tune_pin_to_timer_PGM[] = { 3, 1 }; volatile byte *_tunes_timer1_pin_port; volatile byte _tunes_timer1_pin_mask; volatile int32_t timer1_toggle_count; volatile byte *_tunes_timer3_pin_port; volatile byte _tunes_timer3_pin_mask; byte _tune_pins[AVAILABLE_TIMERS]; byte _tune_num_chans = 0; volatile boolean tune_playing; // is the score still playing? volatile unsigned wait_timer_frequency2; /* its current frequency */ volatile boolean wait_timer_playing = false; /* is it currently playing a note? */ volatile boolean tonePlaying = false; volatile unsigned long wait_toggle_count; /* countdown score waits */ // pointers to your musical score and your position in said score volatile const byte *score_start = 0; volatile const byte *score_cursor = 0; // Table of midi note frequencies * 2 // They are times 2 for greater accuracy, yet still fits in a word. // Generated from Excel by =ROUND(2*440/32*(2^((x-9)/12)),0) for 0= _tune_num_chans) return; // we only have frequencies for 128 notes if (note > 127) { return; } timer_num = pgm_read_byte(tune_pin_to_timer_PGM + chan); if (note < 48) { frequency2 = pgm_read_byte(_midi_byte_note_frequencies + note); } else { frequency2 = pgm_read_word(_midi_word_note_frequencies + note - 48); } //****** 16-bit timer ********* // two choices for the 16 bit timers: ck/1 or ck/64 ocr = F_CPU / frequency2 - 1; prescalar_bits = 0b001; if (ocr > 0xffff) { ocr = F_CPU / frequency2 / 64 - 1; prescalar_bits = 0b011; } // Set the OCR for the given timer, then turn on the interrupts switch (timer_num) { case 1: TCCR1B = (TCCR1B & 0b11111000) | prescalar_bits; OCR1A = ocr; bitWrite(TIMSK1, OCIE1A, 1); break; case 3: TCCR3B = (TCCR3B & 0b11111000) | prescalar_bits; OCR3A = ocr; wait_timer_frequency2 = frequency2; // for "tune_delay" function wait_timer_playing = true; bitWrite(TIMSK3, OCIE3A, 1); break; } } void ArduboyTunes::stopNote(byte chan) { byte timer_num; timer_num = pgm_read_byte(tune_pin_to_timer_PGM + chan); switch (timer_num) { case 1: TIMSK1 &= ~(1 << OCIE1A); // disable the interrupt *_tunes_timer1_pin_port &= ~(_tunes_timer1_pin_mask); // keep pin low after stop break; case 3: wait_timer_playing = false; *_tunes_timer3_pin_port &= ~(_tunes_timer3_pin_mask); // keep pin low after stop break; } } void ArduboyTunes::playScore(const byte *score) { score_start = score; score_cursor = score_start; step(); /* execute initial commands */ tune_playing = true; /* release the interrupt routine */ } void ArduboyTunes::stopScore (void) { for (uint8_t i = 0; i < _tune_num_chans; i++) stopNote(i); tune_playing = false; } bool ArduboyTunes::playing() { return tune_playing; } /* Do score commands until a "wait" is found, or the score is stopped. This is called initially from tune_playcore, but then is called from the interrupt routine when waits expire. */ /* if CMD < 0x80, then the other 7 bits and the next byte are a 15-bit big-endian number of msec to wait */ void ArduboyTunes::step() { byte command, opcode, chan; unsigned duration; while (1) { command = pgm_read_byte(score_cursor++); opcode = command & 0xf0; chan = command & 0x0f; if (opcode == TUNE_OP_STOPNOTE) { /* stop note */ stopNote(chan); } else if (opcode == TUNE_OP_PLAYNOTE) { /* play note */ playNote(chan, pgm_read_byte(score_cursor++)); } else if (opcode == TUNE_OP_RESTART) { /* restart score */ score_cursor = score_start; } else if (opcode == TUNE_OP_STOP) { /* stop score */ tune_playing = false; break; } else if (opcode < 0x80) { /* wait count in msec. */ duration = ((unsigned)command << 8) | (pgm_read_byte(score_cursor++)); wait_toggle_count = ((unsigned long) wait_timer_frequency2 * duration + 500) / 1000; if (wait_toggle_count == 0) wait_toggle_count = 1; break; } } } void ArduboyTunes::closeChannels(void) { byte timer_num; for (uint8_t chan=0; chan < _tune_num_chans; chan++) { timer_num = pgm_read_byte(tune_pin_to_timer_PGM + chan); switch (timer_num) { case 1: TIMSK1 &= ~(1 << OCIE1A); break; case 3: TIMSK3 &= ~(1 << OCIE3A); break; } digitalWrite(_tune_pins[chan], 0); } _tune_num_chans = 0; tune_playing = false; } void ArduboyTunes::soundOutput() { if (wait_timer_playing) { // toggle the pin if we're sounding a note *_tunes_timer3_pin_port ^= _tunes_timer3_pin_mask; } if (tune_playing && wait_toggle_count && --wait_toggle_count == 0) { // end of a score wait, so execute more score commands ArduboyTunes::step(); // execute commands } } void ArduboyTunes::tone(unsigned int frequency, unsigned long duration) { tonePlaying = true; uint8_t prescalarbits = 0b001; int32_t toggle_count = 0; uint32_t ocr = 0; // two choices for the 16 bit timers: ck/1 or ck/64 ocr = F_CPU / frequency / 2 - 1; prescalarbits = 0b001; if (ocr > 0xffff) { ocr = F_CPU / frequency / 2 / 64 - 1; prescalarbits = 0b011; } TCCR1B = (TCCR1B & 0b11111000) | prescalarbits; // Calculate the toggle count if (duration > 0) { toggle_count = 2 * frequency * duration / 1000; } else { toggle_count = -1; } // Set the OCR for the given timer, // set the toggle count, // then turn on the interrupts OCR1A = ocr; timer1_toggle_count = toggle_count; bitWrite(TIMSK1, OCIE1A, 1); } // TIMER 1 ISR(TIMER1_COMPA_vect) { if (tonePlaying) { if (timer1_toggle_count != 0) { // toggle the pin *_tunes_timer1_pin_port ^= _tunes_timer1_pin_mask; if (timer1_toggle_count > 0) timer1_toggle_count--; } else { tonePlaying = false; TIMSK1 &= ~(1 << OCIE1A); // disable the interrupt *_tunes_timer1_pin_port &= ~(_tunes_timer1_pin_mask); // keep pin low after stop } } else { *_tunes_timer1_pin_port ^= _tunes_timer1_pin_mask; // toggle the pin } } // TIMER 3 ISR(TIMER3_COMPA_vect) { // Timer 3 is the one assigned first, so we keep it running always // and use it to time score waits, whether or not it is playing a note. ArduboyTunes::soundOutput(); }