/** * @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/ * *****************************************************************************/ #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; 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 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 } }