forked from earlephilhower/ESP8266Audio
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
AudioFileSourceFunction
which can generate sound from functions (…
- Loading branch information
1 parent
9b4f360
commit f974af9
Showing
3 changed files
with
345 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |