Skip to content

Add WAV file reader. #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 43 additions & 118 deletions examples/Beginner/Audio_Playback/Audio_Playback.ino
Original file line number Diff line number Diff line change
Expand Up @@ -6,154 +6,79 @@
*/

#include <Arduino_AdvancedAnalog.h>

#include <Arduino_USBHostMbed5.h>

#include <DigitalOut.h>
#include <FATFileSystem.h>

AdvancedDAC dac1(A12);

USBHostMSD msd;
mbed::FATFileSystem usb("USB_DRIVE");

FILE * file = nullptr;
int sample_size = 0;
int samples_count = 0;

WavReader wavreader;
AdvancedDAC dac1(A12);
#define N_SAMPLES (512)

void setup()
{
void setup() {
Serial.begin(115200);
while (!Serial);
while (!Serial) {
}

/* Enable power for HOST USB connector. */
// Enable power for HOST USB connector.
pinMode(PA_15, OUTPUT);
digitalWrite(PA_15, HIGH);

Serial.println("Please connect a USB stick to the GIGA's USB port ...");
while (!msd.connect()) delay(100);
Serial.println("Please connect a USB stick to the USB host port ...");
while (!msd.connect()) {
delay(100);
}

Serial.println("Mounting USB device ...");
int const rc_mount = usb.mount(&msd);
if (rc_mount)
{
if (rc_mount) {
Serial.print("Error mounting USB device ");
Serial.println(rc_mount);
return;
while (1);
}

Serial.println("Opening audio file ...");

/* 16-bit PCM Mono 16kHz realigned noise reduction */
file = fopen("/USB_DRIVE/AUDIO_SAMPLE.wav", "rb");
if (file == nullptr)
{
Serial.print("Error opening audio file: ");
Serial.println(strerror(errno));
return;
if (!wavreader.begin("/USB_DRIVE/AUDIO_SAMPLE.wav", N_SAMPLES, 1, false)) {
Serial.print("Error opening audio file: ");
while (1);
}

Serial.println("Reading audio header ...");
struct wav_header_t
{
char chunkID[4]; //"RIFF" = 0x46464952
unsigned long chunkSize; //28 [+ sizeof(wExtraFormatBytes) + wExtraFormatBytes] + sum(sizeof(chunk.id) + sizeof(chunk.size) + chunk.size)
char format[4]; //"WAVE" = 0x45564157
char subchunk1ID[4]; //"fmt " = 0x20746D66
unsigned long subchunk1Size; //16 [+ sizeof(wExtraFormatBytes) + wExtraFormatBytes]
unsigned short audioFormat;
unsigned short numChannels;
unsigned long sampleRate;
unsigned long byteRate;
unsigned short blockAlign;
unsigned short bitsPerSample;
};

wav_header_t header;
fread(&header, sizeof(header), 1, file);

Serial.println("WAV File Header read:");
char msg[64] = {0};
snprintf(msg, sizeof(msg), "File Type: %s", header.chunkID);
Serial.println(msg);
snprintf(msg, sizeof(msg), "File Size: %ld", header.chunkSize);
Serial.println(msg);
snprintf(msg, sizeof(msg), "WAV Marker: %s", header.format);
Serial.println(msg);
snprintf(msg, sizeof(msg), "Format Name: %s", header.subchunk1ID);
Serial.println(msg);
snprintf(msg, sizeof(msg), "Format Length: %ld", header.subchunk1Size);
Serial.println(msg);
snprintf(msg, sizeof(msg), "Format Type: %hd", header.audioFormat);
Serial.println(msg);
snprintf(msg, sizeof(msg), "Number of Channels: %hd", header.numChannels);
Serial.println(msg);
snprintf(msg, sizeof(msg), "Sample Rate: %ld", header.sampleRate);
Serial.println(msg);
snprintf(msg, sizeof(msg), "Sample Rate * Bits/Sample * Channels / 8: %ld", header.byteRate);
Serial.println(msg);
snprintf(msg, sizeof(msg), "Bits per Sample * Channels / 8: %hd", header.blockAlign);
Serial.println(msg);
snprintf(msg, sizeof(msg), "Bits per Sample: %hd", header.bitsPerSample);
Serial.println(msg);

/* Find the data section of the WAV file. */
struct chunk_t
{
char ID[4];
unsigned long size;
};

chunk_t chunk;
snprintf(msg, sizeof(msg), "id\t" "size");
Serial.println(msg);
/* Find data chunk. */
while (true)
{
fread(&chunk, sizeof(chunk), 1, file);
snprintf(msg, sizeof(msg), "%c%c%c%c\t" "%li", chunk.ID[0], chunk.ID[1], chunk.ID[2], chunk.ID[3], chunk.size);
Serial.println(msg);
if (*(unsigned int *) &chunk.ID == 0x61746164)
break;
/* Skip chunk data bytes. */
fseek(file, chunk.size, SEEK_CUR);
}

/* Determine number of samples. */
sample_size = header.bitsPerSample / 8;
samples_count = chunk.size * 8 / header.bitsPerSample;
snprintf(msg, sizeof(msg), "Sample size = %i", sample_size); Serial.println(msg);
snprintf(msg, sizeof(msg), "Samples count = %i", samples_count); Serial.println(msg);
snprintf(msg, sizeof(msg), "Number of Channels: %hd", wavreader.channels()); Serial.println(msg);
snprintf(msg, sizeof(msg), "Sample Rate: %ld", wavreader.sample_rate()); Serial.println(msg);
snprintf(msg, sizeof(msg), "Bits per Sample: %hd", wavreader.resolution()); Serial.println(msg);
snprintf(msg, sizeof(msg), "Number of Samples = %i", wavreader.sample_count()); Serial.println(msg);

/* Configure the advanced DAC. */
if (!dac1.begin(AN_RESOLUTION_12, header.sampleRate, 256, 16))
{
// Configure and start the DAC.
if (!dac1.begin(AN_RESOLUTION_12, wavreader.sample_rate(), N_SAMPLES, 32)) {
Serial.println("Failed to start DAC1 !");
return;
while (1);
}
}

void loop()
{
if (dac1.available() && !feof(file))
{
/* Read data from file. */
uint16_t sample_data[256] = {0};
fread(sample_data, sample_size, 256, file);

/* Get a free buffer for writing. */
SampleBuffer buf = dac1.dequeue();

/* Write data to buffer. */
for (size_t i = 0; i < buf.size(); i++)
{
/* Scale down to 12 bit. */
uint16_t const dac_val = ((static_cast<unsigned int>(sample_data[i])+32768)>>4) & 0x0fff;
buf[i] = dac_val;
void loop() {
if (dac1.available() && wavreader.available()) {
// Get a free buffer for writing.
SampleBuffer dacbuf = dac1.dequeue();

// Read a samples buffer from the wav file.
SampleBuffer pcmbuf = wavreader.read();

// Process and write samples to the DAC buffer.
for (size_t i=0; i<N_SAMPLES; i++) {
// Map the samples to positive range and down scale to 12-bit.
if (wavreader.channels() == 1) {
dacbuf[i] = ((unsigned int) ((int16_t) pcmbuf[i] + 32768)) >> 4;
} else {
// If the file has two channels set the average.
dacbuf[i] = ((unsigned int) ((((int16_t) pcmbuf[(i * 2)] + (int16_t) pcmbuf[(i * 2) + 1]) / 2) + 32768)) >> 4;
}
}

/* Write the buffer to DAC. */
dac1.write(buf);
// Write the buffer to DAC.
dac1.write(dacbuf);
pcmbuf.release();
}
}
1 change: 1 addition & 0 deletions src/Arduino_AdvancedAnalog.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@

#include "AdvancedADC.h"
#include "AdvancedDAC.h"
#include "WavReader.h"

#endif /* ADVANCEDANALOGREDUX_ARDUINO_ADVANCEDANALOG_H */
109 changes: 109 additions & 0 deletions src/WavReader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
This file is part of the Arduino_AdvancedAnalog library.
Copyright (c) 2024 Arduino SA. All rights reserved.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "Arduino.h"
#include "WavReader.h"

WavReader::~WavReader() {
stop();
}

int WavReader::begin(const char *path, size_t n_samples, size_t n_buffers, bool loop) {
this->loop = loop;

if ((file = fopen(path, "rb")) == nullptr) {
return 0;
}

// Read file header
fread(&header, sizeof(header), 1, file);

// Add more sanity checks if needed.
if (memcmp(header.chunk_id, "RIFF", 4) != 0 ||
memcmp(header.format, "WAVEfmt", 7) != 0 ||
((sizeof(Sample) * 8) < header.bits_per_sample) ||
(n_samples * header.num_channels) > sample_count()) {
stop();
return 0;
}

// Allocate the DMA buffer pool.
pool = new DMABufferPool<Sample>(n_samples, header.num_channels, n_buffers);
if (pool == nullptr) {
stop();
return 0;
}
return 1;
}

void WavReader::stop() {
if (file) {
fclose(file);
}
if (pool) {
delete pool;
}
pool = nullptr;
file = nullptr;
}

bool WavReader::available() {
if (file != nullptr && pool != nullptr) {
return pool->writable();
}
return false;
}

DMABuffer<Sample> &WavReader::read() {
while (!available()) {
__WFI();
}

DMABuffer<Sample> *buf = pool->allocate();
size_t offset = 0;
Sample *rawbuf = buf->data();
size_t n_samples = buf->size();

while (offset < n_samples) {
offset += fread(&rawbuf[offset], sizeof(Sample), n_samples - offset, file);
if (offset < n_samples) {
if (loop) {
rewind();
} else {
for (size_t i=offset; i<n_samples; i++) {
rawbuf[i] = 0;
}
fclose(file);
file = nullptr;
break;
}
}
}
return *buf;
}

int WavReader::rewind() {
if (file == nullptr) {
return 0;
}
if (fseek(file, sizeof(WavHeader), SEEK_SET) == -1) {
return 0;
}
return 1;
}
74 changes: 74 additions & 0 deletions src/WavReader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
This file is part of the Arduino_AdvancedAnalog library.
Copyright (c) 2024 Arduino SA. All rights reserved.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "AdvancedAnalog.h"

#ifndef ARDUINO_WAV_READER_H_
#define ARDUINO_WAV_READER_H_

class WavReader {
typedef struct {
char chunk_id[4];
unsigned int chunk_size;
char format[4];
char subchunk1_id[4];
unsigned int subchunk1_size;
unsigned short audio_format;
unsigned short num_channels;
unsigned int sample_rate;
unsigned int byte_rate;
unsigned short block_align;
unsigned short bits_per_sample;
char subchunk2_id[4];
unsigned int subchunk2_size;
} WavHeader;

private:
FILE *file;
bool loop;
WavHeader header;
DMABufferPool<Sample> *pool;

public:
WavReader(): file(nullptr), loop(false), pool(nullptr) {
}
~WavReader();
size_t channels() {
return header.num_channels;
}

size_t resolution() {
return header.bits_per_sample;
}

size_t sample_rate() {
return header.sample_rate;
}

size_t sample_count() {
return (header.subchunk2_size * 8) / header.bits_per_sample;
}

int begin(const char *path, size_t n_samples, size_t n_buffers, bool loop=false);
void stop();
bool available();
SampleBuffer read();
int rewind();
};
#endif /* ARDUINO_WAV_READER_H_ */