Skip to content

Commit

Permalink
Add AudioFileSourceFunction which can generate sound from functions (
Browse files Browse the repository at this point in the history
  • Loading branch information
hideakitai authored Apr 12, 2021
1 parent 9b4f360 commit f974af9
Show file tree
Hide file tree
Showing 3 changed files with 345 additions and 0 deletions.
78 changes: 78 additions & 0 deletions examples/PlayWAVFromFunction/PlayWAVFromFunction.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#include <Arduino.h>
#include "AudioFileSourceFunction.h"
#include "AudioGeneratorWAV.h"
#include "AudioOutputI2SNoDAC.h"

float hz = 440.f;

// pre-defined function can also be used to generate the wave
float sine_wave(const float time) {
float v = sin(TWO_PI * hz * time); // C
v *= fmod(time, 1.f); // change linear
v *= 0.5; // scale
return v;
};

AudioGeneratorWAV* wav;
AudioFileSourceFunction* file;
AudioOutputI2SNoDAC* out;

void setup() {
Serial.begin(115200);
delay(1000);

// ===== create instance with length of song in [sec] =====
file = new AudioFileSourceFunction(8.);
//
// you can set (sec, channels, hz, bit/sample) but you should care about
// the trade-off between performance and the audio quality
//
// file = new AudioFileSourceFunction(sec, channels, hz, bit/sample);
// channels : default = 1
// hz : default = 8000 (8000, 11025, 22050, 44100, 48000, etc.)
// bit/sample : default = 16 (8, 16, 32)

// ===== set your sound function =====
file->addAudioGenerators([&](const float time) {
float v = sin(TWO_PI * hz * time); // generate sine wave
v *= fmod(time, 1.f); // change linear
v *= 0.5; // scale
return v;
});
//
// sound function should have one argument(float) and one return(float)
// param : float (current time [sec] of the song)
// return : float (the amplitude of sound which varies from -1.f to +1.f)
//
// sound function can be registerd only one or the same number with channels
// if the channels > 1 && the number of function == 1,
// same function are used to generate the sound in every channel
//
// file = new AudioFileSourceFunction(8., 2);
// file->addAudioGenerators(
// // L (channel 0)
// [](const float time) {
// return 0.25 * sin(TWO_PI * 440.f * time) * fmod(time, 1.f); // C
// },
// // R (channel 1)
// [](const float time) {
// return 0.25 * sin(TWO_PI * 550.f * time) * fmod(time, 1.f); // E
// }
// );
//
// you can also use the pre-defined function
// file->addAudioGenerators(sine_wave);

out = new AudioOutputI2SNoDAC();
wav = new AudioGeneratorWAV();
wav->begin(file, out);
}

void loop() {
if (wav->isRunning()) {
if (!wav->loop()) wav->stop();
} else {
Serial.println("function done!");
delay(1000);
}
}
148 changes: 148 additions & 0 deletions src/AudioFileSourceFunction.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
AudioFileSourceFunction
Audio ouptut generator which can generate WAV file data from function
Copyright (C) 2021 Hideaki Tai
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "AudioFileSourceFunction.h"

AudioFileSourceFunction::AudioFileSourceFunction(float sec, uint16_t channels, uint32_t sample_per_sec, uint16_t bits_per_sample) {
uint32_t bytes_per_sec = sample_per_sec * channels * bits_per_sample / 8;
uint32_t len = uint32_t(sec * (float)bytes_per_sec);

// RIFF chunk
strncpy(wav_header.riff.chunk_id, "RIFF", 4);
wav_header.riff.chunk_size = 4 // size of riff chunk w/o chunk_id and chunk_size
+ 8 + 16 // size of format chunk
+ 8 + len; // size of data chunk
strncpy(wav_header.riff.format, "WAVE", 4);

// format chunk
strncpy(wav_header.format.chunk_id, "fmt ", 4);
wav_header.format.chunk_size = 16;
wav_header.format.format_tag = 0x0001; // PCM
wav_header.format.channels = channels;
wav_header.format.sample_per_sec = sample_per_sec;
wav_header.format.avg_bytes_per_sec = bytes_per_sec;
wav_header.format.block_align = channels * bits_per_sample / 8;
wav_header.format.bits_per_sample = bits_per_sample;

// data chunk
strncpy(wav_header.data.chunk_id, "data", 4);
wav_header.data.chunk_size = len;

funcs.reserve(channels);
pos = 0;
size = sizeof(WavHeader) + len;
is_ready = false;
is_unique = false;
}

AudioFileSourceFunction::~AudioFileSourceFunction() {
close();
}

uint32_t AudioFileSourceFunction::read(void* data, uint32_t len) {
// callback size must be 1 or equal to channels
if (!is_ready)
return 0;

uint8_t* d = reinterpret_cast<uint8_t*>(data);
uint32_t i = 0;
while (i < len) {
uint32_t p = pos + i;
if (p < sizeof(WavHeader)) {
// header bytes
d[i] = wav_header.bytes[p];
i += 1;
} else {
// data bytes
float time = (float)p / (float)wav_header.format.avg_bytes_per_sec;
float v = funcs[0](time);
for (size_t ch = 0; ch < wav_header.format.channels; ++ch) {
if (!is_unique && ch > 0)
v = funcs[ch](time);

switch (wav_header.format.bits_per_sample) {
case 8: {
Uint8AndInt8 vs {int8_t(v * (float)0x7F)};
d[i] = vs.u;
break;
}
case 32: {
Uint8AndInt32 vs {int32_t(v * (float)0x7FFFFFFF)};
d[i + 0] = vs.u[0];
d[i + 1] = vs.u[1];
d[i + 2] = vs.u[2];
d[i + 3] = vs.u[3];
break;
}
case 16:
default: {
Uint8AndInt16 vs {int16_t(v * (float)0x7FFF)};
d[i + 0] = vs.u[0];
d[i + 1] = vs.u[1];
break;
}
}
}
i += wav_header.format.block_align;
}
}
pos += i;
return (pos >= size) ? 0 : i;
}

bool AudioFileSourceFunction::seek(int32_t pos, int dir) {
if (dir == SEEK_SET) {
if (pos < 0 || (uint32_t)pos >= size)
return false;
this->pos = pos;
} else if (dir == SEEK_CUR) {
int32_t p = (int32_t)this->pos + pos;
if (p < 0 || (uint32_t)p >= size)
return false;
this->pos = p;
} else {
int32_t p = (int32_t)this->size + pos;
if (p < 0 || (uint32_t)p >= size)
return false;
this->pos = p;
}
return true;
}

bool AudioFileSourceFunction::close() {
funcs.clear();
pos = 0;
size = 0;
is_ready = false;
is_unique = false;
return true;
}

bool AudioFileSourceFunction::isOpen() {
return is_ready;
}

uint32_t AudioFileSourceFunction::getSize() {
return size;
}

uint32_t AudioFileSourceFunction::getPos() {
return pos;
}
119 changes: 119 additions & 0 deletions src/AudioFileSourceFunction.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
AudioFileSourceFunction
Audio ouptut generator which can generate WAV file data from function
Copyright (C) 2021 Hideaki Tai
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef _AUDIOFILESOURCEFUNCTION_H
#define _AUDIOFILESOURCEFUNCTION_H

#include <Arduino.h>
#include <vector>
#include <functional>

#include "AudioFileSource.h"

class AudioFileSourceFunction : public AudioFileSource {
union WavHeader {
struct {
// RIFF chunk
struct {
char chunk_id[4]; // "RIFF"
uint32_t chunk_size; // 4 + (8 + sizeof(format_chunk)(16)) + (8 + sizeof(data_chunk))
char format[4]; // "WAVE"
} riff;
// format chunk
struct {
char chunk_id[4]; // "fmt "
uint32_t chunk_size; // 16
uint16_t format_tag; // 1: PCM
uint16_t channels; // 1: MONO, 2: STEREO
uint32_t sample_per_sec; // 8000, 11025, 22050, 44100, 48000
uint32_t avg_bytes_per_sec; // sample_per_sec * channels * bits_per_sample / 8
uint16_t block_align; // channels * bits_per_sample / 8
uint16_t bits_per_sample; // 8, 16, 32
} format;
// data chunk
struct {
char chunk_id[4]; // "data"
uint32_t chunk_size; // num_samples * channels * bytes_per_sample
// audio data follows here...
} data;
};
uint8_t bytes[44];
} wav_header;

union Uint8AndInt8 {
int8_t i;
uint8_t u;
};

union Uint8AndInt16 {
int16_t i;
uint8_t u[2];
};

union Uint8AndInt32 {
int32_t i;
uint8_t u[4];
};

using callback_t = std::function<float(float)>;
std::vector<callback_t> funcs;
uint32_t pos;
uint32_t size;
bool is_ready;
bool is_unique;

public:
AudioFileSourceFunction(float sec, uint16_t channels = 1, uint32_t sample_per_sec = 8000, uint16_t bits_per_sample = 16);
virtual ~AudioFileSourceFunction() override;

template <typename F, typename... Fs>
bool addAudioGenerators(const F& f, Fs&&... fs) {
funcs.emplace_back(f);
return addAudioGenerators(std::forward<Fs>(fs)...);
}
bool addAudioGenerators() {
funcs.shrink_to_fit();
if (funcs.size() == 1) {
is_ready = true;
is_unique = true;
return true;
} else if (funcs.size() == wav_header.format.channels) {
is_ready = true;
is_unique = false;
return true;
} else {
is_ready = false;
is_unique = false;
funcs.clear();
return false;
}
}

virtual uint32_t read(void* data, uint32_t len) override;
virtual bool seek(int32_t pos, int dir) override;

virtual bool close() override;
virtual bool isOpen() override;

virtual uint32_t getSize() override;
virtual uint32_t getPos() override;
};

#endif // _AUDIOFILESOURCEFUNCTION_H

0 comments on commit f974af9

Please sign in to comment.