/** * @file ArduboyPlaytune.cpp * \brief An Arduino library that plays a one or two part musical score and * generates tones. Intended for the Arduboy game system. */ /***************************************************************************** * ArduboyPlaytune * * Plays a one or two part musical score and generates tones. * * Derived from: * Playtune: An Arduino tune player library * https://github.com/LenShustek/arduino-playtune * * Modified to work well with the Arduboy game system * https://www.arduboy.com/ * * The MIT License (MIT) * * (C) Copyright 2016, Chris J. Martinez, Kevin Bates, Josh Goebel, Scott Allen * Based on work (C) Copyright 2011, 2015, Len Shustek * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * This was inspired by and adapted from "A Tone Generator Library", * written by Brett Hagman, http://www.roguerobotics.com/ * *****************************************************************************/ /* 27 March 2018, L. Shustek This was adapted from my Playtune library, but it was missing this fix that prevents a "stuttering" playback because of timing errors: 15 August 2016, L. Shustek, - Fixed a timing error: T Wasiluk's change to using a 16-bit timer instead of an 8-bit timer for score waits exposed a old bug that was in the original Brett Hagman code: when writing the timer OCR value, we need to clear the timer counter, or else (the manual says) "the counter [might] miss the compare match...and will have to count to its maximum value (0xFFFF) and wrap around starting at 0 before the compare match can occur". This caused an error that was small and not noticeable for the 8-bit timer, but could be hundreds of milliseconds for the 16-bit counter. Thanks go to Joey Babcock for pushing me to figure out why his music sounded weird, and for discovering that it worked ok with the 2013 version that used the 8-bit timer for score waits. */ #include "ArduboyPlaytune.h" #include static const byte tune_pin_to_timer[] = { 3, 1 }; static volatile byte *_tunes_timer1_pin_port; static volatile byte _tunes_timer1_pin_mask; static volatile int32_t timer1_toggle_count; static volatile byte *_tunes_timer3_pin_port; static volatile byte _tunes_timer3_pin_mask; static byte _tune_pins[AVAILABLE_TIMERS]; static byte _tune_num_chans = 0; static volatile boolean tune_playing = false; // is the score still playing? static volatile unsigned wait_timer_frequency2; /* its current frequency */ static volatile boolean wait_timer_playing = false; /* is it currently playing a note? */ static volatile unsigned long wait_toggle_count; /* countdown score waits */ static volatile boolean all_muted = false; // indicates all sound is muted static volatile boolean tone_playing = false; static volatile boolean tone_mutes_score = false; static volatile boolean tone_only = false; // indicates don't play score on tone channel static volatile boolean mute_score = false; // indicates tone playing so mute other channels // pointer to a function that indicates if sound is enabled static boolean (*outputEnabled)(); // pointers to your musical score and your position in said score static volatile const byte *score_start = 0; static 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; } // if channel 1 is for tones only if ((chan == 1) && tone_only) { return; } // we only have frequencies for 128 notes if (note > 127) { return; } timer_num = tune_pin_to_timer[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: if (!tone_playing) { TCCR1B = (TCCR1B & 0b11111000) | prescalar_bits; OCR1A = ocr; TCNT1 = 0; //LJS bitWrite(TIMSK1, OCIE1A, 1); } break; case 3: TCCR3B = (TCCR3B & 0b11111000) | prescalar_bits; OCR3A = ocr; TCNT3 = 0; //LJS wait_timer_frequency2 = frequency2; // for "tune_delay" function wait_timer_playing = true; bitWrite(TIMSK3, OCIE3A, 1); break; } } void ArduboyPlaytune::stopNote(byte chan) { byte timer_num; timer_num = tune_pin_to_timer[chan]; switch (timer_num) { case 1: if (!tone_playing) { 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; if (!mute_score) { *_tunes_timer3_pin_port &= ~(_tunes_timer3_pin_mask); // keep pin low after stop } break; } } void ArduboyPlaytune::playScore(const byte *score) { score_start = score; score_cursor = score_start; step(); /* execute initial commands */ tune_playing = true; /* release the interrupt routine */ } void ArduboyPlaytune::stopScore() { for (uint8_t i = 0; i < _tune_num_chans; i++) stopNote(i); tune_playing = false; } boolean ArduboyPlaytune::playing() { return tune_playing; } /* Do score commands until a "wait" is found, or the score is stopped. This is called initially from playScore(), 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 ArduboyPlaytune::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 */ all_muted = !outputEnabled(); playNote(chan, pgm_read_byte(score_cursor++)); } 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; } else if (opcode == TUNE_OP_RESTART) { /* restart score */ score_cursor = score_start; } else if (opcode == TUNE_OP_STOP) { /* stop score */ tune_playing = false; break; } } } void ArduboyPlaytune::closeChannels() { byte timer_num; for (uint8_t chan=0; chan < _tune_num_chans; chan++) { timer_num = tune_pin_to_timer[chan]; switch (timer_num) { case 1: TIMSK1 &= ~(1 << OCIE1A); *_tunes_timer1_pin_port &= ~(_tunes_timer1_pin_mask); // set pin low break; case 3: TIMSK3 &= ~(1 << OCIE3A); *_tunes_timer3_pin_port &= ~(_tunes_timer3_pin_mask); // set pin low break; } } _tune_num_chans = 0; tune_playing = tone_playing = tone_only = mute_score = false; } void ArduboyPlaytune::tone(unsigned int frequency, unsigned long duration) { // don't output the tone if sound is muted or // the tone channel isn't initialised if (!outputEnabled() || _tune_num_chans < 2) { return; } tone_playing = true; mute_score = tone_mutes_score; 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); } void ArduboyPlaytune::toneMutesScore(boolean mute) { tone_mutes_score = mute; } // ===== Interrupt service routines ===== // TIMER 1 ISR(TIMER1_COMPA_vect) { if (tone_playing) { 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 { tone_playing = mute_score = false; TIMSK1 &= ~(1 << OCIE1A); // disable the interrupt *_tunes_timer1_pin_port &= ~(_tunes_timer1_pin_mask); // keep pin low after stop } } else { if (!all_muted) { *_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. // toggle the pin if we're sounding a note if (wait_timer_playing && !mute_score && !all_muted) { *_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 ArduboyPlaytune::step(); // execute commands } }