Using Flash to play Audio Clips

Short audio clips can be stored in on-chip flash memory instead of using an SD card or USB drive file. There are tools available to convert audio wav files to a C source code file that initializes a large array with the sample values extracted from the wav file. An example using this approach for mbed will be examined. Keep in mind that audio samples will use a lot of flash memory (as much as 8-80K bytes/second) so this is not an option to store several long music files. It does work for applications that only need a few seconds of audio.


Demo of 8Khz 8-bit Flash Audio Player with Class D Amp and Speaker. While audio is playing with timer interrupts, code in main continues to run and flashes the LEDs.

/media/uploads/4180_1/wavtocode.jpg WAVtoCode GUI Display in Windows

The first step is to select a tool or program that will read in and convert a *.wav file to a C array source file. The freeware tool used in this example is WAVtoCode. Download from here and install it on a Windows PC. There are a few other options, but they are not easy to use or will require writing conversion code that understands the complex *.wav file formats. This tool converts OK to a file (all that is needed here), but the GUI locks up if you try to play audio files longer than a couple seconds using the tool (perhaps one of those X86 64K data memory addressing issues?) The recent version downloaded from https://colinjs.com/software.htm#t_WAVToCode may have fixed this issue.

Select an audio file with the desired sound (most audio formats can be converted). The next step is to use Audacity to open the sound file, downsample to a lower frequency (Project Rate perhaps 8-16K), convert to mono (if stereo), shorten the audio clip to the desired length (just a couple seconds), and save the new audio file in *.wav format using File->Export. A manual is available under Help.

Next, start the WAVtoCode program and File-> open the new *.wav file. When the GUI displays the audio image, select 16-bit on the bottom. Then Tools->Mix to Mono, and finally File->Save Marked Section as C code. Note the "Sample Per Sec" value in the GUI upper left, it will be needed later to set playback rates.

The tool then outputs a large C source file that declares and initializes a huge array with data values from the *.wav file. The file below contains the first 30 seconds of the Star Trek theme music and uses just about all of the available flash memory on the mbed LPC1768.

/**********************************************************************
* Written by WAVToCode
* Date:				Sat Oct 21 03:57:58 PM
* FileName:			startrek.C
* Interleaved:		Yes
* Signed:			No
* No. of channels:	1
* No. of samples:	239491
* Bits/Sample:		16
**********************************************************************/

#define NUM_ELEMENTS 239491

BYTE data[NUM_ELEMENTS] = {
32768, 32769, 32768, 32768, 32768, 32768, 32768, 32768, // 0-7
32768, 32767, 32767, 32769, 32770, 32767, 32767, 32769, // 8-15
32767, 32767, 32768, 32769, 32769, 32769, 32769, 32766, // 16-23
32768, 32769, 32765, 32765, 32767, 32766, 32768, 32771, // 24-31
32773, 32770, 32767, 32769, 32770, 32759, 32761, 32779, // 32-39
32767, 32740, 32736, 32768, 32794, 32797, 32772, 32758, // 40-47
...... many lines omitted here!
38139, 40435, 31231, 32938, 33758, 31203, 38445, 36222, // 239464-239471
37363, 42525, 34607, 34234, 31633, 27288, 31886, 28147, // 239472-239479
25763, 32197, 25778, 31732, 47807, 40669, 36453, 35499, // 239480-239487
30878, 33801, 35500};// 239488-239490

Open the new C source file (*.c )in an editor (i.e., notepad) and edit the first line on the array declaration and change it to:

const unsigned short sound_data[NUM_ELEMENTS] = { 

"const" tells the compiler to use flash to store the array (and not RAM!), and the 16-bit values are unsigned.

Select all of the text with CTL A and then CTL C to copy. It can then be pasted into an C source file edit window in the mbed cloud compiler. There is so much code that it probably makes sense to put it in its own new *.h file and then include it from main. So you probably want to create a new *.h file in the project for it, open it in the editor, paste, and save.

Next, a program is needed to play the audio values from the array to a speaker using a D/A or PWM. If you are using PWM instead of AnalogOut, be sure to set the PWM frequency to about 10X to highest audio frequency and rescale array values from 0.0 to 1.0 for mbed's PwmOut API (i.e, divide by 65535.0 for 16-bit unsigned samples). The easiest way to set the audio sample period is to use an mbed Ticker (timer interrupt). Pin assignments in code examples for the audio output (speaker) and LED1 are for the mbed LPC1768. They will need to be changed for other mbed boards.

Here is a code example for a short audio clip of the famous Cylon saying "By Your Command":

#include "mbed.h"
AnalogOut speaker(p18);
Ticker sampletick;
DigitalOut myled(LED1);

//setup const array in flash with audio values 
//from free wav file conversion tool at
//http://ccgi.cjseymour.plus.com/wavtocode/wavtocode.htm
//see https://os.mbed.com/users/4180_1/notebook/using-flash-to-play-audio-clips/
#include "cylonbyc.h"

#define sample_freq 11025.0
//get and set the frequency from wav conversion tool GUI
int i=0;

//interrupt routine to play next audio sample from array in flash
void audio_sample ()
{
    speaker.write_u16(sound_data[i]);
    i++;
    if (i>= NUM_ELEMENTS) {
        i = 0;
        sampletick.detach();
        myled = 0;
    }
}
int main()
{
    while(1) {
        myled = 1;
//use a ticker to play and send next audio sample value to D/A
        sampletick.attach(&audio_sample, 1.0 / sample_freq);
//can do other things while audio plays with timer interrupts
        wait(10.0);
    }
}

Import programflash_audio_player

Plays audio samples from an array stored in flash. Values are from an audio *.wav file that was automatically converted to a C array data source file.

The "# include cyclonbyc.h" pulls in the generated code that initializes the sound_data array. Manually change the sample_freq in the define to your "sample per sec" value noted earlier. NUM_ELEMENTS is defined in the C array sound file.

Don't forget that a speaker will need a driver circuit. See Using a speaker for audio output for suggestions.

Reducing Flash Memory Usage

In the cloud compiler, double check your flash memory usage using by clicking on “Build Options” (lower right after a compile). If less fidelity on audio playback can be tolerated, memory can be saved by using 8-bit samples, lower sample rates, and shorter audio clips. Many alarm and alert sounds repeat, so only one cycle could be saved and it could be played back in a loop.

Here is a version of the same Cylon audio clip using only 8-bit samples. In WAVtoCode, the C source file was created using the 8-bit sample option which generates an unsigned char array (instead of 16-bit unsigned short). It still sounds OK on my setup, but uses half of the memory to store audio samples.

Import programflash_audio_player_8bit

Plays audio samples from an array stored in flash. Values are from an audio *.wav file that was automatically converted to a C array data source file. 8-bit samples used in this version.

Here is the same setup, but it is using a PWM pin for audio output (instead of AnalogOut). Keep in mind that it needs to increase the PWM frequency which can cause problems for other PWM devices like servos that might be setup for slower PWM frequencies. Many chips have only one PWM clock circuit for all PWM outputs. The default is 50hz and the standard mbed servo class is setup for that rate(way too slow for audio!). Note that the sample values need to be scaled from unsigned chars to 0.0 to 1.0 for PWM (done in code with a simple /255.0). Precomputing the constant value 1.0/255.0, and multiplying may be a bit faster on some processors.

Import programflash_audio_player_8bitPWM

Plays audio samples from an array stored in flash. Values are from an audio *.wav file that was automatically converted to a C array data source file. 8-bit samples used in this version and a PWM output pin (instead of AnalogOut)

With 8-bit samples at 8Khz on the mbed LPC1768 which has 512K of flash, around a minute of audio playback is possible as in the video demo, but only if the program code needed for everything else in flash is short. The audio amp circuit low pass filter on the breakout could be modified a bit at frequencies this low to reduce some of the hum.

Using I2S Digital Audio

Here is a flash player setup using I2S digital audio output. It was tested on a MAX98357A breakout. This IC is a PCM I2S Input Class D Audio Power Amplifier. The Adafruit Stereo Bonnet has two MAX98357A ICs and two optional wired speakers for stereo output. It can be plugged into a Pi Cobbler for breadboard use. Both Adafruit and Sparkfun recently introduced some small mono I2S amp breakout boards with the same I2S chip which should also work with this code example.

#include "mbed.h"
#include "I2S.h"
Ticker sampletick;
DigitalOut myled(LED1);
//Plays Audio Clip using Array in Flash
//using I2S input Class D audio amp
//setup const array in flash with audio values
//from free wav file conversion tool at
//http://ccgi.cjseymour.plus.com/wavtocode/wavtocode.htm
//see https://os.mbed.com/users/4180_1/notebook/using-flash-to-play-audio-clips/
//tested on MAX98357A
#include "cylonbyc.h"
#define sample_freq 11025.0

I2S i2s(I2S_TRANSMIT, p5, p6, p7);
//p5(PWM value sent as serial data bit) <-> Din
//p6(WS) <-> LRC left/right channel clock
//p7(bit clock) <-> BCLK

int i = 0;
int bufflen = 1;
int buffer[1];
void isr()
{
    buffer[0] = sound_data[i]>>1;//scale down volume a bit on amp
    i2s.write(buffer, bufflen);//Send next PWM value to amp
    i++;
    if (i>= NUM_ELEMENTS) {
        i = 0;
        sampletick.detach();
        i2s.stop();
        myled = 0;
    }
}

int main()
{
    i2s.stereomono(I2S_MONO);
    i2s.masterslave(I2S_MASTER);
    i2s.frequency(sample_freq);
    while(1) {
       i2s.start();
        myled = 1;
        sampletick.attach(&isr, 1.0/sample_freq);//turn on timer interrupt
        wait(10);
    }
}

Import programflash_audio_playerI2S

Plays audio samples from an array stored in flash using I2S. Values are from an audio *.wav file that was automatically converted to a C array data source file.

Wiring

mbed LPC1768Adafruit Stereo Bonnet on Pi CobblerExt Supply
gndgndgnd
5V5V
p5 (I2S Data)21
p6 (I2S LRC)19
p7 (I2S BitClk)18

Note: A header must be soldered on the stereo bonnet to fit into the Cobbler Adapter that plugs into the breadboard. Pins are labeled on the Cobbler board silkscreen. The hardware and I2S will support stereo output instead of mono (as used in the demo), but more flash memory will be required.

This I2S demo code was simplified a bit to use a timer, similar to the earlier flash player examples. Ideally, it should use the I2S hardware's FIFO buffers, I2S TX interrupts, and perhaps even use DMA. The I2S library only has code for the LPC1768, so a lot of effort will be required to port it to another mbed board instead of the LPC1768 assuming it has I2S hardware support, since the basic mbed APIs do not support I2S hardware.

Using DMA to play Audio Clips from Flash

DMA is not supported by the official mbed APIs, but there is a DMA library for the LPC1768. Here is a short demo program using the MODDMA library for the mbed LPC1768 to play an audio file from flash using the DAC output (p18). It appears that audio samples need to be in 32-bit format for DMA directly to the DAC, so it increases memory use quite a bit for flash audio clip storage. The audio sample source code file was edited and a #define was used to reformat the flash data file source code used in the earlier examples into a 32-bit format needed for DMA. To run the demo, attach a speaker with a driver circuit to p18. DMA to PWM does not appear to be supported, but DMA to I2S is on the LPC1768.

Import programDMA_flash_audio_player

Demo using DMA to play recorded audio samples to the DAC (p18) on the mbed LPC1768 using the MODDMA library for the LPC1768.

Playing Audio in mbed version 5 OS

The AnalogOut write_u16 function has an issue with a mutex lock currently (10/19) in mbed OS 5 and generates an OS runtime error when used in an interrupt routine. It is possible to avoid this problem by writing directly to the DACs I/O register. On the LPC1768 mbed board, replace write_u16... with "LCP_DAC->DACR = sound_data[i];". The PWM output option works OK in OS 5 without changes.

Mixing or Adding Sounds

Many devices need to be able to selectively mix audio sounds. As an example, this is very common in games when the fire button is hit or something explodes. In the digital domain, it is possible to add audio sample values to mix sounds. They will need to have the same audio sample rate and each sound will need a flash array of the recorded sound data. Since two values are added, to avoid overflow and the resulting audio distortion, the values will need to be scaled down in the audio tools or in the code prior to the addition operation.

Here is a demo program with two sounds for a steam engine. When the pushbutton is hit, a whistle sound is added to the constant sound of the trains's steam engine.

Import programsteam_engine_sound_effects

Steam engine sound effects using data from flash. Hitting a pushbutton adds a whistle sound to the engine noise. A speaker with a driver plays audio.

The interrupt routine below adds the two sound sample values when the pushbutton is hit. The code scales down the values using >>1 (i.e., divides by 2 - but faster) prior to the addition operation. The new audio sample value (sum) is then sent to the DAC output. There are two .H files with the two recorded sound sample value arrays and the length (array size) of each audio clip is also different.

//interrupt routine to mix two sounds and play next audio sample from arrays in flash
void audio_sample ()
{
    unsigned short data;
    data = steam_train_data[i]>>1;
    //add whistle sound if button hit
    //since two audio signals are added, scaling down using >>1
    //(i.e., shift is faster than /2) avoids overflow (i.e., audio distortion)
    //As an alternative, data could also be scaled down
    //in the .h flash data files using the audio tools
    if (!whistle) {
    //scale down by 2 to avoid overflow, amp saturation, and audio distortion        
        data = data+(steam_whistle_data[j]>>1);
        j++;
        if (j>NUM_ELEMENTS_WHISTLE) j=0;
    } else j=0;

    speaker.write_u16(data);
    i++;
    if (i>NUM_ELEMENTS_TRAIN) i = 0;
}


1 comment on Using Flash to play Audio Clips:

17 Mar 2019

i was facing this issues since very long. now i can easily play audio clips from flash. thanks for sharing this.you can get help from Yahoo Customer support if you have lost your credentials of your yahoo account or any other issues with service offered by yahoo. you can simply mail your query and you will be responded with the best solution for your problem. You can have a look at this for further information. https://crumbles.co/yahoo-customer-service-number/

Please log in to post comments.