0

I downloaded the PCM Audio speaker_pcm (see link below) and it worked easily with the supplied Mac startup chime. I also got my own sounds parsed properly via a combination of sox and wav2c incantations as described. However, when I tried to store and play two or more (very short e.g. 150ms, 8K) sound clips in sequence, I couldn't seem to get it to work. It only sounds like it is playing the last clip in order. I only modified the code (see below) from the original to address the multiple clips. My pointer dereferencing seems to be working (it plays the last clip in sequence), but it won't play a group of them. What am I doing wrong?

(You can download the 01.h 02.h 03.h files from my website, pete dot zelchenko dot com slash 2017 slash the file name. I can't post lots of links here. You can also create your own using the incantations about sox and wav2c in the documentation below, but it takes time. The three files are the words "one" "two" "three" and that's it.)

/*
 * (PCM Audio speaker_pcm downloaded from http://playground.arduino.cc/Code/PCMAudio)
 *  
 * speaker_pcm
 *
 * Plays 8-bit PCM audio on pin 11 using pulse-width modulation (PWM).
 * For Arduino with Atmega168 at 16 MHz.
 *
 * Uses two timers. The first changes the sample value 8000 times a second.
 * The second holds pin 11 high for 0-255 ticks out of a 256-tick cycle,
 * depending on sample value. The second timer repeats 62500 times per second
 * (16000000 / 256), much faster than the playback rate (8000 Hz), so
 * it almost sounds halfway decent, just really quiet on a PC speaker.
 *
 * Takes over Timer 1 (16-bit) for the 8000 Hz timer. This breaks PWM
 * (analogWrite()) for Arduino pins 9 and 10. Takes Timer 2 (8-bit)
 * for the pulse width modulation, breaking PWM for pins 11 & 3.
 *
 * References:
 *     [links]
 *
 * Michael Smith <michael [at] hurts.ca>
 */

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>

#define SAMPLE_RATE 8000

    /*
     * The audio data needs to be unsigned, 8-bit, 8000 Hz, and small enough
     * to fit in flash. 10000-13000 samples is about the limit.
     *
     * sounddata.h should look like this:
     *     const int sounddata_length=10000;
     *     const unsigned char sounddata_data[] PROGMEM = { ..... };
     *
     * You can use wav2c from GBA CSS (OR use EncodeAudio? http://highlowtech.org/?p=1963):
     *     http://thieumsweb.free.fr/english/gbacss.html
     * Then add "PROGMEM" in the right place. I hacked it up to dump the samples
     * as unsigned rather than signed, but it shouldn't matter.
     *
     * [link deleted]
     * mplayer -ao pcm macstartup.mp3
     * sox audiodump.wav -v 1.32 -c 1 -r 8000 -u -1 macstartup-8000.wav
     * sox macstartup-8000.wav macstartup-cut.wav trim 0 10000s
     * wav2c macstartup-cut.wav sounddata.h sounddata
     *
     * (starfox) nb. under sox 12.18 (distributed in CentOS 5), i needed to run
     * the following command to convert my wav file to the appropriate format:
     * sox audiodump.wav -c 1 -r 8000 -u -b macstartup-8000.wav
     */

    //#include "sounddata.h"
    #include "toolow.h"
    #include "toohigh.h"

    #include "01.h"
    #include "02.h"
    #include "03.h"    

    int      ledPin   = 13;
    int      speakerPin = 11; // Can be either 3 or 11, two PWM outputs connected to Timer 2

    volatile uint16_t sample;
    byte lastSample;

    int sounddata_length;

    /*int sounddata_length;
    signed char *sounddata_data[10000]; */

    void stopPlayback()
    {
        // Disable playback per-sample interrupt.
        TIMSK1 &= ~_BV(OCIE1A);

        // Disable the per-sample timer completely.
        TCCR1B &= ~_BV(CS10);

        // Disable the PWM timer.
        TCCR2B &= ~_BV(CS10);

        digitalWrite(speakerPin, LOW);
    }

    unsigned int sounddata_addr;

    // This is called at 8000 Hz to load the next sample.
    ISR(TIMER1_COMPA_vect) {
        if (sample >= sounddata_length) {
            if (sample == sounddata_length + lastSample) {
                stopPlayback();
            }
            else {
                if(speakerPin==11){
                    // Ramp down to zero to reduce the click at the end of playback.
                    OCR2A = sounddata_length + lastSample - sample;
                } else {
                    OCR2B = sounddata_length + lastSample - sample;                
                }
            }
        }
        else {
            if(speakerPin==11){
                OCR2A = pgm_read_byte(sounddata_addr + sample);
            } else {
                OCR2B = pgm_read_byte(sounddata_addr + sample);            
            }
        }

        ++sample;
    }


    void startPlayback(int clip)
    {

    signed char *sounddata_data[10000];

        switch (clip) {
          case   1:  sounddata_data[0] = &s_01_data[0]; sounddata_length = s_01_length; break;
          case   2:  sounddata_data[0] = &s_02_data[0]; sounddata_length = s_02_length; break;
          case   3:  sounddata_data[0] = &s_03_data[0]; sounddata_length = s_03_length; break;

        }

    sounddata_addr = sounddata_data[0];

        pinMode(speakerPin, OUTPUT);

        // Set up Timer 2 to do pulse width modulation on the speaker
        // pin.

    *  // Use internal clock (datasheet p.160)
        ASSR &= ~(_BV(EXCLK) | _BV(AS2));

        // Set fast PWM mode  (p.157)
        TCCR2A |= _BV(WGM21) | _BV(WGM20);
        TCCR2B &= ~_BV(WGM22);

        if(speakerPin==11){
            // Do non-inverting PWM on pin OC2A (p.155)
            // On the Arduino this is pin 11.
            TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
            TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
            // No prescaler (p.158)
            TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

            // Set initial pulse width to the first sample.
            OCR2A = pgm_read_byte(&sounddata_data[0]);
        } else {
            // Do non-inverting PWM on pin OC2B (p.155)
            // On the Arduino this is pin 3.
            TCCR2A = (TCCR2A | _BV(COM2B1)) & ~_BV(COM2B0);
            TCCR2A &= ~(_BV(COM2A1) | _BV(COM2A0));
            // No prescaler (p.158)
            TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

            // Set initial pulse width to the first sample.
            OCR2B = pgm_read_byte(&sounddata_data[0]);
        }

        // Set up Timer 1 to send a sample every interrupt.
        cli();

        // Set CTC mode (Clear Timer on Compare Match) (p.133)
        // Have to set OCR1A *after*, otherwise it gets reset to 0!
        TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
        TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));

        // No prescaler (p.134)
        TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

        // Set the compare register (OCR1A).
        // OCR1A is a 16-bit register, so we have to do this with
        // interrupts disabled to be safe.
        OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 8000 = 2000

        // Enable interrupt when TCNT1 == OCR1A (p.136)
        TIMSK1 |= _BV(OCIE1A);

        lastSample = pgm_read_byte(&sounddata_data[sounddata_length-1]);
        sample = 0;
        sei();
    }

    void setup()
    {
    // audio pins
        pinMode(ledPin, OUTPUT);
        digitalWrite(ledPin, HIGH);

        startPlayback(01);
        startPlayback(02);
        startPlayback(03);

    }

    void loop()
    {    
      loop();
    }
  • Which Arduino board ? Arduino Uno ? – Jot Jun 17 '17 at 17:03
  • I know I'm answering my own question. A lot of rules on stackexchange and dog only knows if this violates one. It looks as if I need to put enough delay after each clip that the clips don't walk on each other. It's that simple. Without the delays, it makes sense that they will just overlap and you'll just hear a high-pitched shriek. I put a delay(250) in for these short clips just to test, but you can calculate the necessary delay for each clip after startPlayback() of each clip, based on the length of the given clip. – Pete Zelchenko Jun 18 '17 at 0:13
2

I think it's just a case of startPlayback() being asynchronous. It doesn't block; it doesn't wait for playback to finish. It just sets up the 8000 Hz timer interrupt and returns.

The original code calls stopPlayback() when it runs out of samples. You could change that block (under if (sample >= sounddata_length)) to have it cycle on to the next clip, rather than stopping.

(Note: I'm the original author of the PCM sample but it's been quite a while so take this with a grain of salt. Surprised it still works with newer boards. Have fun!)

0

The Mega2560 apparently has to use pin 10 instead of pin 11 for PCM output. It would be better if this were defined in the main sketch instead of the library.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.