I am tring to build an audio recorder with arduino. Right now the setup has a microphone recording an input an outputing to an analog input of an Arduino uno.
How can I convert a (timestamp,data) reading into .WAV? I know there is more libraries that are able to do this but I am tring to undertand the structure of .WAV and would like to be able to script this my self in C\C++.
Thanks for the Attention!
/// The first 4 byte of a wav file should be the characters "RIFF" */
char chunkID[4] = {'R', 'I', 'F', 'F'};
/// 36 + SubChunk2Size
uint32_t chunkSize = 36; // You Don't know this until you write your data but at a minimum it is 36 for an empty file
/// "should be characters "WAVE"
char format[4] = {'W', 'A', 'V', 'E'};
/// " This should be the letters "fmt ", note the space character
char subChunk1ID[4] = {'f', 'm', 't', ' '};
///: For PCM == 16, since audioFormat == uint16_t
uint32_t subChunk1Size = 16;
///: For PCM this is 1, other values indicate compression
uint16_t audioFormat = 1;
///: Mono = 1, Stereo = 2, etc.
uint16_t numChannels = 1;
///: Sample Rate of file
uint32_t sampleRate = 44100;
///: SampleRate * NumChannels * BitsPerSample/8
uint32_t byteRate = 44100 * 2;
///: The number of byte for one frame NumChannels * BitsPerSample/8
uint16_t blockAlign = 2;
///: 8 bits = 8, 16 bits = 16
uint16_t bitsPerSample = 16;
///: Contains the letters "data"
char subChunk2ID[4] = {'d', 'a', 't', 'a'};
///: == NumSamples * NumChannels * BitsPerSample/8 i.e. number of byte in the data.
uint32_t subChunk2Size = 0; // You Don't know this until you write your data
There are some assumptions made here like quality of audio. The above the assumes CD quality audio 16-bit, 44.1kHz. If you need something else you will need to amend the relevant values.
You will also need to be aware if your Arduino is actually regularly recording samples or if this is essentially a sonification of data.
#include <SPI.h>
#include <SD.h>
File wavFile;
const char* filename = "data.wav";
void setup()
{
Serial.begin(9600);
while (!Serial);
if (!SD.begin(4))
while (1);
wavFile = SD.open(filename, FILE_WRITE);
if (!wavFile)
while (1);
}
Given the way that the SD Library is organised, you have to cast the elements of your header to byte
if they are not already char
. Have to is perhaps a little strong, but it certainly makes it easier to maintain the correct byte width for each element of the header without fudging things too much.
It makes sense to wrap this in its own function.
void writeWavHeader()
{
wavFile.write(chunkID,4);
wavFile.write((byte*)&chunkSize,4);
wavFile.write(format,4);
wavFile.write(subChunk1ID,4);
wavFile.write((byte*)&subChunk1Size,4);
wavFile.write((byte*)&audioFormat,2);
wavFile.write((byte*)&numChannels,2);
wavFile.write((byte*)&sampleRate,4);
wavFile.write((byte*)&byteRate,4);
wavFile.write((byte*)&blockAlign,2);
wavFile.write((byte*)&bitsPerSample,2);
wavFile.write(subChunk2ID,4);
wavFile.write((byte*)&subChunk2Size,4);
}
The wav header tells us that chunkSize and subChunk2Size are dependant on the amount of data in the file. As such, these will grow and will have to altered if you append data to the file.
There are a fe ways you can got about adding data
What method you choose will depend on your implementation which we don't have much info on. For the sake of argument, let's write one sample at a time. This approach should demonstrate what is required and the cons (regularly jumping around the file, lots of repetition) should be immediately apparent.
void writeDataToWavFile(int data)
{
int16_t sampleValue = map(data, MIN_DATA_VALUE, MAX_DATA_VALUE,-32767,32767);
subChunk2Size += numChannels * bitsPerSample/8;
wavFile.seek(40);
wavFile.write((byte*)&subChunk2Size,4);
wavFile.seek(4);
chunkSize = 36 + subChunk2Size;
wavFile.write((byte*)&chunkSize,4);
wavFile.seek(wavFile.size()-1);
wavFile.write((byte*)&sampleValue,2);
}
MIN_DATA_VALUE
and MAX_DATA_VALUE
are something you will have to define. This all assumes you're working with integer data. If it is floating point then some adjustments will need to be made.
#include <SPI.h>
#include <SD.h>
int MIN_DATA_VALUE;
int MAX_DATA_VALUE;
File wavFile;
const char* filename = "data.wav";
/// The first 4 byte of a wav file should be the characters "RIFF" */
char chunkID[4] = {'R', 'I', 'F', 'F'};
/// 36 + SubChunk2Size
uint32_t chunkSize = 36; // You Don't know this until you write your data but at a minimum it is 36 for an empty file
/// "should be characters "WAVE"
char format[4] = {'W', 'A', 'V', 'E'};
/// " This should be the letters "fmt ", note the space character
char subChunk1ID[4] = {'f', 'm', 't', ' '};
///: For PCM == 16, since audioFormat == uint16_t
uint32_t subChunk1Size = 16;
///: For PCM this is 1, other values indicate compression
uint16_t audioFormat = 1;
///: Mono = 1, Stereo = 2, etc.
uint16_t numChannels = 1;
///: Sample Rate of file
uint32_t sampleRate = 44100;
///: SampleRate * NumChannels * BitsPerSample/8
uint32_t byteRate = 44100 * 2;
///: The number of byte for one frame NumChannels * BitsPerSample/8
uint16_t blockAlign = 2;
///: 8 bits = 8, 16 bits = 16
uint16_t bitsPerSample = 16;
///: Contains the letters "data"
char subChunk2ID[4] = {'d', 'a', 't', 'a'};
///: == NumSamples * NumChannels * BitsPerSample/8 i.e. number of byte in the data.
uint32_t subChunk2Size = 0; // You Don't know this until you write your data
void setup()
{
Serial.begin(9600);
while (!Serial);
if (!SD.begin(4))
while (1);
wavFile = SD.open(filename, FILE_WRITE);
if (!wavFile)
while (1);
writeWavHeader();
}
void loop()
{
int data = getSomeData();
writeDataToWavFile(data);
}
void writeWavHeader()
{
wavFile.seek(0);
wavFile.write(chunkID,4);
wavFile.write((byte*)&chunkSize,4);
wavFile.write(format,4);
wavFile.write(subChunk1ID,4);
wavFile.write((byte*)&subChunk1Size,4);
wavFile.write((byte*)&audioFormat,2);
wavFile.write((byte*)&numChannels,2);
wavFile.write((byte*)&sampleRate,4);
wavFile.write((byte*)&byteRate,4);
wavFile.write((byte*)&blockAlign,2);
wavFile.write((byte*)&bitsPerSample,2);
wavFile.write(subChunk2ID,4);
wavFile.write((byte*)&subChunk2Size,4);
}
void writeDataToWavFile(int data)
{
int16_t sampleValue = map(data, MIN_DATA_VALUE, MAX_DATA_VALUE,-32767,32767);
subChunk2Size += numChannels * bitsPerSample/8;
wavFile.seek(40);
wavFile.write((byte*)&subChunk2Size,4);
wavFile.seek(4);
chunkSize = 36 + subChunk2Size;
wavFile.write((byte*)&chunkSize,4);
wavFile.seek(wavFile.size()-1);
wavFile.write((byte*)&sampleValue,2);
}
In sum