import esp audio lib
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
AudioFileSource
|
||||
Base class of an input "file" to be used by AudioGenerator
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOFILESOURCE_H
|
||||
#define _AUDIOFILESOURCE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "AudioStatus.h"
|
||||
|
||||
class AudioFileSource
|
||||
{
|
||||
public:
|
||||
AudioFileSource() {};
|
||||
virtual ~AudioFileSource() {};
|
||||
virtual bool open(const char *filename) { (void)filename; return false; };
|
||||
virtual uint32_t read(void *data, uint32_t len) { (void)data; (void)len; return 0; };
|
||||
virtual uint32_t readNonBlock(void *data, uint32_t len) { return read(data, len); };
|
||||
virtual bool seek(int32_t pos, int dir) { (void)pos; (void)dir; return false; };
|
||||
virtual bool close() { return false; };
|
||||
virtual bool isOpen() { return false; };
|
||||
virtual uint32_t getSize() { return 0; };
|
||||
virtual uint32_t getPos() { return 0; };
|
||||
virtual bool loop() { return true; };
|
||||
|
||||
public:
|
||||
virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); }
|
||||
virtual bool RegisterStatusCB(AudioStatus::statusCBFn fn, void *data) { return cb.RegisterStatusCB(fn, data); }
|
||||
|
||||
protected:
|
||||
AudioStatus cb;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
AudioFileSourceBuffer
|
||||
Double-buffered file source using system RAM
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 <Arduino.h>
|
||||
#include "AudioFileSourceBuffer.h"
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
AudioFileSourceBuffer::AudioFileSourceBuffer(AudioFileSource *source, uint32_t buffSizeBytes)
|
||||
{
|
||||
buffSize = buffSizeBytes;
|
||||
buffer = (uint8_t*)malloc(sizeof(uint8_t) * buffSize);
|
||||
if (!buffer) audioLogger->printf_P(PSTR("Unable to allocate AudioFileSourceBuffer::buffer[]\n"));
|
||||
deallocateBuffer = true;
|
||||
writePtr = 0;
|
||||
readPtr = 0;
|
||||
src = source;
|
||||
length = 0;
|
||||
filled = false;
|
||||
}
|
||||
|
||||
AudioFileSourceBuffer::AudioFileSourceBuffer(AudioFileSource *source, void *inBuff, uint32_t buffSizeBytes)
|
||||
{
|
||||
buffSize = buffSizeBytes;
|
||||
buffer = (uint8_t*)inBuff;
|
||||
deallocateBuffer = false;
|
||||
writePtr = 0;
|
||||
readPtr = 0;
|
||||
src = source;
|
||||
length = 0;
|
||||
filled = false;
|
||||
}
|
||||
|
||||
AudioFileSourceBuffer::~AudioFileSourceBuffer()
|
||||
{
|
||||
if (deallocateBuffer) free(buffer);
|
||||
buffer = NULL;
|
||||
}
|
||||
|
||||
bool AudioFileSourceBuffer::seek(int32_t pos, int dir)
|
||||
{
|
||||
if(dir == SEEK_CUR && (readPtr+pos) < length) {
|
||||
readPtr += pos;
|
||||
return true;
|
||||
} else {
|
||||
// Invalidate
|
||||
readPtr = 0;
|
||||
writePtr = 0;
|
||||
length = 0;
|
||||
return src->seek(pos, dir);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioFileSourceBuffer::close()
|
||||
{
|
||||
if (deallocateBuffer) free(buffer);
|
||||
buffer = NULL;
|
||||
return src->close();
|
||||
}
|
||||
|
||||
bool AudioFileSourceBuffer::isOpen()
|
||||
{
|
||||
return src->isOpen();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceBuffer::getSize()
|
||||
{
|
||||
return src->getSize();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceBuffer::getPos()
|
||||
{
|
||||
return src->getPos();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceBuffer::getFillLevel()
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceBuffer::read(void *data, uint32_t len)
|
||||
{
|
||||
if (!buffer) return src->read(data, len);
|
||||
|
||||
uint32_t bytes = 0;
|
||||
if (!filled) {
|
||||
// Fill up completely before returning any data at all
|
||||
cb.st(STATUS_FILLING, PSTR("Refilling buffer"));
|
||||
length = src->read(buffer, buffSize);
|
||||
writePtr = length % buffSize;
|
||||
filled = true;
|
||||
}
|
||||
|
||||
// Pull from buffer until we've got none left or we've satisfied the request
|
||||
uint8_t *ptr = reinterpret_cast<uint8_t*>(data);
|
||||
uint32_t toReadFromBuffer = (len < length) ? len : length;
|
||||
if ( (toReadFromBuffer > 0) && (readPtr >= writePtr) ) {
|
||||
uint32_t toReadToEnd = (toReadFromBuffer < (uint32_t)(buffSize - readPtr)) ? toReadFromBuffer : (buffSize - readPtr);
|
||||
memcpy(ptr, &buffer[readPtr], toReadToEnd);
|
||||
readPtr = (readPtr + toReadToEnd) % buffSize;
|
||||
len -= toReadToEnd;
|
||||
length -= toReadToEnd;
|
||||
ptr += toReadToEnd;
|
||||
bytes += toReadToEnd;
|
||||
toReadFromBuffer -= toReadToEnd;
|
||||
}
|
||||
if (toReadFromBuffer > 0) { // We know RP < WP at this point
|
||||
memcpy(ptr, &buffer[readPtr], toReadFromBuffer);
|
||||
readPtr = (readPtr + toReadFromBuffer) % buffSize;
|
||||
len -= toReadFromBuffer;
|
||||
length -= toReadFromBuffer;
|
||||
ptr += toReadFromBuffer;
|
||||
bytes += toReadFromBuffer;
|
||||
toReadFromBuffer -= toReadFromBuffer;
|
||||
}
|
||||
|
||||
if (len) {
|
||||
// Still need more, try direct read from src
|
||||
bytes += src->read(ptr, len);
|
||||
// We're out of buffered data, need to force a complete refill. Thanks, @armSeb
|
||||
readPtr = 0;
|
||||
writePtr = 0;
|
||||
length = 0;
|
||||
filled = false;
|
||||
cb.st(STATUS_UNDERFLOW, PSTR("Buffer underflow"));
|
||||
}
|
||||
|
||||
fill();
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void AudioFileSourceBuffer::fill()
|
||||
{
|
||||
if (!buffer) return;
|
||||
|
||||
if (length < buffSize) {
|
||||
// Now try and opportunistically fill the buffer
|
||||
if (readPtr > writePtr) {
|
||||
if (readPtr == writePtr+1) return;
|
||||
uint32_t bytesAvailMid = readPtr - writePtr - 1;
|
||||
int cnt = src->readNonBlock(&buffer[writePtr], bytesAvailMid);
|
||||
length += cnt;
|
||||
writePtr = (writePtr + cnt) % buffSize;
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffSize > writePtr) {
|
||||
uint32_t bytesAvailEnd = buffSize - writePtr;
|
||||
int cnt = src->readNonBlock(&buffer[writePtr], bytesAvailEnd);
|
||||
length += cnt;
|
||||
writePtr = (writePtr + cnt) % buffSize;
|
||||
if (cnt != (int)bytesAvailEnd) return;
|
||||
}
|
||||
|
||||
if (readPtr > 1) {
|
||||
uint32_t bytesAvailStart = readPtr - 1;
|
||||
int cnt = src->readNonBlock(&buffer[writePtr], bytesAvailStart);
|
||||
length += cnt;
|
||||
writePtr = (writePtr + cnt) % buffSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool AudioFileSourceBuffer::loop()
|
||||
{
|
||||
if (!src->loop()) return false;
|
||||
fill();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
AudioFileSourceBuffer
|
||||
Double-buffered input file using system RAM
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOFILESOURCEBUFFER_H
|
||||
#define _AUDIOFILESOURCEBUFFER_H
|
||||
|
||||
#include "AudioFileSource.h"
|
||||
|
||||
|
||||
class AudioFileSourceBuffer : public AudioFileSource
|
||||
{
|
||||
public:
|
||||
AudioFileSourceBuffer(AudioFileSource *in, uint32_t bufferBytes);
|
||||
AudioFileSourceBuffer(AudioFileSource *in, void *buffer, uint32_t bufferBytes); // Pre-allocated buffer by app
|
||||
virtual ~AudioFileSourceBuffer() override;
|
||||
|
||||
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;
|
||||
virtual bool loop() override;
|
||||
|
||||
virtual uint32_t getFillLevel();
|
||||
|
||||
enum { STATUS_FILLING=2, STATUS_UNDERFLOW };
|
||||
|
||||
private:
|
||||
virtual void fill();
|
||||
|
||||
private:
|
||||
AudioFileSource *src;
|
||||
uint32_t buffSize;
|
||||
uint8_t *buffer;
|
||||
bool deallocateBuffer;
|
||||
uint32_t writePtr;
|
||||
uint32_t readPtr;
|
||||
uint32_t length;
|
||||
bool filled;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
AudioFileSourceFS
|
||||
Input Arduion "file" to be used by AudioGenerator
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOFILESOURCEFATFS_H
|
||||
#define _AUDIOFILESOURCEFATFS_H
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
#include <FFat.h>
|
||||
|
||||
#include "AudioFileSource.h"
|
||||
#include "AudioFileSourceFS.h"
|
||||
|
||||
/*
|
||||
AudioFileSource for FAT filesystem.
|
||||
*/
|
||||
class AudioFileSourceFATFS : public AudioFileSourceFS
|
||||
{
|
||||
public:
|
||||
AudioFileSourceFATFS() : AudioFileSourceFS(FFat) {};
|
||||
AudioFileSourceFATFS(const char *filename) : AudioFileSourceFS(FFat) {
|
||||
// We call open() ourselves because calling AudioFileSourceFS(FFat, filename)
|
||||
// would call the parent open() and we do not want that
|
||||
open(filename);
|
||||
};
|
||||
|
||||
virtual bool open(const char *filename) override {
|
||||
// make sure that the FATFS filesystem has been mounted
|
||||
if (!FFat.begin()) {
|
||||
audioLogger->printf_P(PSTR("Unable to initialize FATFS filesystem\n"));
|
||||
return false;
|
||||
} else {
|
||||
// now that the fielsystem has been mounted, we can call the regular parent open() function
|
||||
return AudioFileSourceFS::open(filename);
|
||||
}
|
||||
};
|
||||
|
||||
// Others are inherited from base
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
AudioFileSourceFS
|
||||
Input "file" to be used by AudioGenerator
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 "AudioFileSourceFS.h"
|
||||
#ifdef ESP32
|
||||
#include "SPIFFS.h"
|
||||
#endif
|
||||
|
||||
AudioFileSourceFS::AudioFileSourceFS(FS &fs, const char *filename)
|
||||
{
|
||||
filesystem = &fs;
|
||||
open(filename);
|
||||
}
|
||||
|
||||
bool AudioFileSourceFS::open(const char *filename)
|
||||
{
|
||||
#ifndef ESP32
|
||||
filesystem->begin();
|
||||
#endif
|
||||
f = filesystem->open(filename, "r");
|
||||
return f;
|
||||
}
|
||||
|
||||
AudioFileSourceFS::~AudioFileSourceFS()
|
||||
{
|
||||
if (f) f.close();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceFS::read(void *data, uint32_t len)
|
||||
{
|
||||
return f.read(reinterpret_cast<uint8_t*>(data), len);
|
||||
}
|
||||
|
||||
bool AudioFileSourceFS::seek(int32_t pos, int dir)
|
||||
{
|
||||
return f.seek(pos, (dir==SEEK_SET)?SeekSet:(dir==SEEK_CUR)?SeekCur:SeekEnd);
|
||||
}
|
||||
|
||||
bool AudioFileSourceFS::close()
|
||||
{
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioFileSourceFS::isOpen()
|
||||
{
|
||||
return f?true:false;
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceFS::getSize()
|
||||
{
|
||||
if (!f) return 0;
|
||||
return f.size();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
AudioFileSourceFS
|
||||
Input Arduion "file" to be used by AudioGenerator
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOFILESOURCEFS_H
|
||||
#define _AUDIOFILESOURCEFS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
|
||||
#include "AudioFileSource.h"
|
||||
|
||||
class AudioFileSourceFS : public AudioFileSource
|
||||
{
|
||||
public:
|
||||
AudioFileSourceFS(fs::FS &fs) { filesystem = &fs; }
|
||||
AudioFileSourceFS(fs::FS &fs, const char *filename);
|
||||
virtual ~AudioFileSourceFS() override;
|
||||
|
||||
virtual bool open(const char *filename) override;
|
||||
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 { if (!f) return 0; else return f.position(); };
|
||||
|
||||
private:
|
||||
fs::FS *filesystem;
|
||||
fs::File f;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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 - sizeof(WavHeader)) / (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;
|
||||
}
|
||||
@@ -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,0 +1,158 @@
|
||||
/*
|
||||
AudioFileSourceHTTPStream
|
||||
Streaming HTTP source
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
|
||||
#include "AudioFileSourceHTTPStream.h"
|
||||
|
||||
AudioFileSourceHTTPStream::AudioFileSourceHTTPStream()
|
||||
{
|
||||
pos = 0;
|
||||
reconnectTries = 0;
|
||||
saveURL[0] = 0;
|
||||
}
|
||||
|
||||
AudioFileSourceHTTPStream::AudioFileSourceHTTPStream(const char *url)
|
||||
{
|
||||
saveURL[0] = 0;
|
||||
reconnectTries = 0;
|
||||
open(url);
|
||||
}
|
||||
|
||||
bool AudioFileSourceHTTPStream::open(const char *url)
|
||||
{
|
||||
pos = 0;
|
||||
http.begin(client, url);
|
||||
http.setReuse(true);
|
||||
#ifndef ESP32
|
||||
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
|
||||
#endif
|
||||
int code = http.GET();
|
||||
if (code != HTTP_CODE_OK) {
|
||||
http.end();
|
||||
cb.st(STATUS_HTTPFAIL, PSTR("Can't open HTTP request"));
|
||||
return false;
|
||||
}
|
||||
size = http.getSize();
|
||||
strncpy(saveURL, url, sizeof(saveURL));
|
||||
saveURL[sizeof(saveURL)-1] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioFileSourceHTTPStream::~AudioFileSourceHTTPStream()
|
||||
{
|
||||
http.end();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceHTTPStream::read(void *data, uint32_t len)
|
||||
{
|
||||
if (data==NULL) {
|
||||
audioLogger->printf_P(PSTR("ERROR! AudioFileSourceHTTPStream::read passed NULL data\n"));
|
||||
return 0;
|
||||
}
|
||||
return readInternal(data, len, false);
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceHTTPStream::readNonBlock(void *data, uint32_t len)
|
||||
{
|
||||
if (data==NULL) {
|
||||
audioLogger->printf_P(PSTR("ERROR! AudioFileSourceHTTPStream::readNonBlock passed NULL data\n"));
|
||||
return 0;
|
||||
}
|
||||
return readInternal(data, len, true);
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceHTTPStream::readInternal(void *data, uint32_t len, bool nonBlock)
|
||||
{
|
||||
retry:
|
||||
if (!http.connected()) {
|
||||
cb.st(STATUS_DISCONNECTED, PSTR("Stream disconnected"));
|
||||
http.end();
|
||||
for (int i = 0; i < reconnectTries; i++) {
|
||||
char buff[64];
|
||||
sprintf_P(buff, PSTR("Attempting to reconnect, try %d"), i);
|
||||
cb.st(STATUS_RECONNECTING, buff);
|
||||
delay(reconnectDelayMs);
|
||||
if (open(saveURL)) {
|
||||
cb.st(STATUS_RECONNECTED, PSTR("Stream reconnected"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!http.connected()) {
|
||||
cb.st(STATUS_DISCONNECTED, PSTR("Unable to reconnect"));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ((size > 0) && (pos >= size)) return 0;
|
||||
|
||||
WiFiClient *stream = http.getStreamPtr();
|
||||
|
||||
// Can't read past EOF...
|
||||
if ( (size > 0) && (len > (uint32_t)(pos - size)) ) len = pos - size;
|
||||
|
||||
if (!nonBlock) {
|
||||
int start = millis();
|
||||
while ((stream->available() < (int)len) && (millis() - start < 500)) yield();
|
||||
}
|
||||
|
||||
size_t avail = stream->available();
|
||||
if (!nonBlock && !avail) {
|
||||
cb.st(STATUS_NODATA, PSTR("No stream data available"));
|
||||
http.end();
|
||||
goto retry;
|
||||
}
|
||||
if (avail == 0) return 0;
|
||||
if (avail < len) len = avail;
|
||||
|
||||
int read = stream->read(reinterpret_cast<uint8_t*>(data), len);
|
||||
pos += read;
|
||||
return read;
|
||||
}
|
||||
|
||||
bool AudioFileSourceHTTPStream::seek(int32_t pos, int dir)
|
||||
{
|
||||
audioLogger->printf_P(PSTR("ERROR! AudioFileSourceHTTPStream::seek not implemented!"));
|
||||
(void) pos;
|
||||
(void) dir;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioFileSourceHTTPStream::close()
|
||||
{
|
||||
http.end();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioFileSourceHTTPStream::isOpen()
|
||||
{
|
||||
return http.connected();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceHTTPStream::getSize()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceHTTPStream::getPos()
|
||||
{
|
||||
return pos;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
AudioFileSourceHTTPStream
|
||||
Connect to a HTTP based streaming service
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#ifdef ESP32
|
||||
#include <HTTPClient.h>
|
||||
#else
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#endif
|
||||
#include "AudioFileSource.h"
|
||||
|
||||
class AudioFileSourceHTTPStream : public AudioFileSource
|
||||
{
|
||||
friend class AudioFileSourceICYStream;
|
||||
|
||||
public:
|
||||
AudioFileSourceHTTPStream();
|
||||
AudioFileSourceHTTPStream(const char *url);
|
||||
virtual ~AudioFileSourceHTTPStream() override;
|
||||
|
||||
virtual bool open(const char *url) override;
|
||||
virtual uint32_t read(void *data, uint32_t len) override;
|
||||
virtual uint32_t readNonBlock(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;
|
||||
bool SetReconnect(int tries, int delayms) { reconnectTries = tries; reconnectDelayMs = delayms; return true; }
|
||||
void useHTTP10 () { http.useHTTP10(true); }
|
||||
|
||||
enum { STATUS_HTTPFAIL=2, STATUS_DISCONNECTED, STATUS_RECONNECTING, STATUS_RECONNECTED, STATUS_NODATA };
|
||||
|
||||
private:
|
||||
virtual uint32_t readInternal(void *data, uint32_t len, bool nonBlock);
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
int pos;
|
||||
int size;
|
||||
int reconnectTries;
|
||||
int reconnectDelayMs;
|
||||
char saveURL[128];
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
AudioFileSourceICYStream
|
||||
Streaming Shoutcast ICY source
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "AudioFileSourceICYStream.h"
|
||||
#include <string.h>
|
||||
|
||||
AudioFileSourceICYStream::AudioFileSourceICYStream()
|
||||
{
|
||||
pos = 0;
|
||||
reconnectTries = 0;
|
||||
saveURL[0] = 0;
|
||||
}
|
||||
|
||||
AudioFileSourceICYStream::AudioFileSourceICYStream(const char *url)
|
||||
{
|
||||
saveURL[0] = 0;
|
||||
reconnectTries = 0;
|
||||
open(url);
|
||||
}
|
||||
|
||||
bool AudioFileSourceICYStream::open(const char *url)
|
||||
{
|
||||
static const char *hdr[] = { "icy-metaint", "icy-name", "icy-genre", "icy-br" };
|
||||
pos = 0;
|
||||
http.begin(client, url);
|
||||
http.addHeader("Icy-MetaData", "1");
|
||||
http.collectHeaders( hdr, 4 );
|
||||
http.setReuse(true);
|
||||
int code = http.GET();
|
||||
if (code != HTTP_CODE_OK) {
|
||||
http.end();
|
||||
cb.st(STATUS_HTTPFAIL, PSTR("Can't open HTTP request"));
|
||||
return false;
|
||||
}
|
||||
if (http.hasHeader(hdr[0])) {
|
||||
String ret = http.header(hdr[0]);
|
||||
icyMetaInt = ret.toInt();
|
||||
} else {
|
||||
icyMetaInt = 0;
|
||||
}
|
||||
if (http.hasHeader(hdr[1])) {
|
||||
String ret = http.header(hdr[1]);
|
||||
// cb.md("SiteName", false, ret.c_str());
|
||||
}
|
||||
if (http.hasHeader(hdr[2])) {
|
||||
String ret = http.header(hdr[2]);
|
||||
// cb.md("Genre", false, ret.c_str());
|
||||
}
|
||||
if (http.hasHeader(hdr[3])) {
|
||||
String ret = http.header(hdr[3]);
|
||||
// cb.md("Bitrate", false, ret.c_str());
|
||||
}
|
||||
|
||||
icyByteCount = 0;
|
||||
size = http.getSize();
|
||||
strncpy(saveURL, url, sizeof(saveURL));
|
||||
saveURL[sizeof(saveURL)-1] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioFileSourceICYStream::~AudioFileSourceICYStream()
|
||||
{
|
||||
http.end();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceICYStream::readInternal(void *data, uint32_t len, bool nonBlock)
|
||||
{
|
||||
// Ensure we can't possibly read 2 ICY headers in a single go #355
|
||||
if (icyMetaInt > 1) {
|
||||
len = std::min((int)(icyMetaInt >> 1), (int)len);
|
||||
}
|
||||
retry:
|
||||
if (!http.connected()) {
|
||||
cb.st(STATUS_DISCONNECTED, PSTR("Stream disconnected"));
|
||||
http.end();
|
||||
for (int i = 0; i < reconnectTries; i++) {
|
||||
char buff[64];
|
||||
sprintf_P(buff, PSTR("Attempting to reconnect, try %d"), i);
|
||||
cb.st(STATUS_RECONNECTING, buff);
|
||||
delay(reconnectDelayMs);
|
||||
if (open(saveURL)) {
|
||||
cb.st(STATUS_RECONNECTED, PSTR("Stream reconnected"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!http.connected()) {
|
||||
cb.st(STATUS_DISCONNECTED, PSTR("Unable to reconnect"));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ((size > 0) && (pos >= size)) return 0;
|
||||
|
||||
WiFiClient *stream = http.getStreamPtr();
|
||||
|
||||
// Can't read past EOF...
|
||||
if ( (size > 0) && (len > (uint32_t)(pos - size)) ) len = pos - size;
|
||||
|
||||
if (!nonBlock) {
|
||||
int start = millis();
|
||||
while ((stream->available() < (int)len) && (millis() - start < 500)) yield();
|
||||
}
|
||||
|
||||
size_t avail = stream->available();
|
||||
if (!nonBlock && !avail) {
|
||||
cb.st(STATUS_NODATA, PSTR("No stream data available"));
|
||||
http.end();
|
||||
goto retry;
|
||||
}
|
||||
if (avail == 0) return 0;
|
||||
if (avail < len) len = avail;
|
||||
|
||||
int read = 0;
|
||||
int ret = 0;
|
||||
// If the read would hit an ICY block, split it up...
|
||||
if (((int)(icyByteCount + len) > (int)icyMetaInt) && (icyMetaInt > 0)) {
|
||||
int beforeIcy = icyMetaInt - icyByteCount;
|
||||
if (beforeIcy > 0) {
|
||||
ret = stream->read(reinterpret_cast<uint8_t*>(data), beforeIcy);
|
||||
if (ret < 0) ret = 0;
|
||||
read += ret;
|
||||
pos += ret;
|
||||
len -= ret;
|
||||
data = (void *)(reinterpret_cast<char*>(data) + ret);
|
||||
icyByteCount += ret;
|
||||
if (ret != beforeIcy) return read; // Partial read
|
||||
}
|
||||
|
||||
// ICY MD handling
|
||||
int mdSize;
|
||||
uint8_t c;
|
||||
int mdret = stream->read(&c, 1);
|
||||
if (mdret==0) return read;
|
||||
mdSize = c * 16;
|
||||
if ((mdret == 1) && (mdSize > 0)) {
|
||||
// This is going to get ugly fast.
|
||||
char icyBuff[256 + 16 + 1];
|
||||
char *readInto = icyBuff + 16;
|
||||
memset(icyBuff, 0, 16); // Ensure no residual matches occur
|
||||
while (mdSize) {
|
||||
int toRead = mdSize > 256 ? 256 : mdSize;
|
||||
int ret = stream->read((uint8_t*)readInto, toRead);
|
||||
if (ret < 0) return read;
|
||||
if (ret == 0) { delay(1); continue; }
|
||||
mdSize -= ret;
|
||||
// At this point we have 0...15 = last 15 chars read from prior read plus new data
|
||||
int end = 16 + ret; // The last byte of valid data
|
||||
char *header = (char *)memmem((void*)icyBuff, end, (void*)"StreamTitle=", 12);
|
||||
if (!header) {
|
||||
// No match, so move the last 16 bytes back to the start and continue
|
||||
memmove(icyBuff, icyBuff+end-16, 16);
|
||||
delay(1);
|
||||
continue;
|
||||
}
|
||||
// Found header, now move it to the front
|
||||
int lastValidByte = end - (header -icyBuff) + 1;
|
||||
memmove(icyBuff, header, lastValidByte);
|
||||
// Now fill the buffer to the end with read data
|
||||
while (mdSize && lastValidByte < 255) {
|
||||
int toRead = mdSize > (256 - lastValidByte) ? (256 - lastValidByte) : mdSize;
|
||||
ret = stream->read((uint8_t*)icyBuff + lastValidByte, toRead);
|
||||
if (ret==-1) return read; // error
|
||||
if (ret == 0) { delay(1); continue; }
|
||||
mdSize -= ret;
|
||||
lastValidByte += ret;
|
||||
}
|
||||
// Buffer now contains StreamTitle=....., parse it
|
||||
char *p = icyBuff+12;
|
||||
if (*p=='\'' || *p== '"' ) {
|
||||
char closing[] = { *p, ';', '\0' };
|
||||
char *psz = strstr( p+1, closing );
|
||||
if( !psz ) psz = strchr( &icyBuff[13], ';' );
|
||||
if( psz ) *psz = '\0';
|
||||
p++;
|
||||
} else {
|
||||
char *psz = strchr( p, ';' );
|
||||
if( psz ) *psz = '\0';
|
||||
}
|
||||
cb.md("StreamTitle", false, p);
|
||||
|
||||
// Now skip rest of MD block
|
||||
while (mdSize) {
|
||||
int toRead = mdSize > 256 ? 256 : mdSize;
|
||||
ret = stream->read((uint8_t*)icyBuff, toRead);
|
||||
if (ret < 0) return read;
|
||||
if (ret == 0) { delay(1); continue; }
|
||||
mdSize -= ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
icyByteCount = 0;
|
||||
}
|
||||
|
||||
ret = stream->read(reinterpret_cast<uint8_t*>(data), len);
|
||||
if (ret < 0) ret = 0;
|
||||
read += ret;
|
||||
pos += ret;
|
||||
icyByteCount += ret;
|
||||
return read;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
AudioFileSourceHTTPStream
|
||||
Connect to a HTTP based streaming service
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#ifdef ESP32
|
||||
#include <HTTPClient.h>
|
||||
#else
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#endif
|
||||
|
||||
#include "AudioFileSourceHTTPStream.h"
|
||||
|
||||
class AudioFileSourceICYStream : public AudioFileSourceHTTPStream
|
||||
{
|
||||
public:
|
||||
AudioFileSourceICYStream();
|
||||
AudioFileSourceICYStream(const char *url);
|
||||
virtual ~AudioFileSourceICYStream() override;
|
||||
|
||||
virtual bool open(const char *url) override;
|
||||
|
||||
private:
|
||||
virtual uint32_t readInternal(void *data, uint32_t len, bool nonBlock) override;
|
||||
int icyMetaInt;
|
||||
int icyByteCount;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
AudioFileSourceID3
|
||||
ID3 filter that extracts any ID3 fields and sends to CB function
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 "AudioFileSourceID3.h"
|
||||
|
||||
// Handle unsync operation in ID3 with custom class
|
||||
class AudioFileSourceUnsync : public AudioFileSource
|
||||
{
|
||||
public:
|
||||
AudioFileSourceUnsync(AudioFileSource *src, int len, bool unsync);
|
||||
virtual ~AudioFileSourceUnsync() override;
|
||||
virtual uint32_t read(void *data, uint32_t len) override;
|
||||
|
||||
int getByte();
|
||||
bool eof();
|
||||
|
||||
private:
|
||||
AudioFileSource *src;
|
||||
int remaining;
|
||||
bool unsync;
|
||||
int savedByte;
|
||||
};
|
||||
|
||||
AudioFileSourceUnsync::AudioFileSourceUnsync(AudioFileSource *src, int len, bool unsync)
|
||||
{
|
||||
this->src = src;
|
||||
this->remaining = len;
|
||||
this->unsync = unsync;
|
||||
this->savedByte = -1;
|
||||
}
|
||||
|
||||
AudioFileSourceUnsync::~AudioFileSourceUnsync()
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceUnsync::read(void *data, uint32_t len)
|
||||
{
|
||||
uint32_t bytes = 0;
|
||||
uint8_t *ptr = reinterpret_cast<uint8_t*>(data);
|
||||
|
||||
// This is only used during ID3 parsing, so no need to optimize here...
|
||||
while (len--) {
|
||||
int b = getByte();
|
||||
if (b >= 0) {
|
||||
*(ptr++) = (uint8_t)b;
|
||||
bytes++;
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
int AudioFileSourceUnsync::getByte()
|
||||
{
|
||||
// If we're not unsync, just read.
|
||||
if (!unsync) {
|
||||
uint8_t c;
|
||||
if (!remaining) return -1;
|
||||
remaining--;
|
||||
if (1 != src->read(&c, 1)) return -1;
|
||||
return c;
|
||||
}
|
||||
|
||||
// If we've saved a pre-read character, return it immediately
|
||||
if (savedByte >= 0) {
|
||||
int s = savedByte;
|
||||
savedByte = -1;
|
||||
return s;
|
||||
}
|
||||
|
||||
if (remaining <= 0) {
|
||||
return -1;
|
||||
} else if (remaining == 1) {
|
||||
remaining--;
|
||||
uint8_t c;
|
||||
if (1 != src->read(&c, 1)) return -1;
|
||||
else return c;
|
||||
} else {
|
||||
uint8_t c;
|
||||
remaining--;
|
||||
if (1 != src->read(&c, 1)) return -1;
|
||||
if (c != 0xff) {
|
||||
return c;
|
||||
}
|
||||
// Saw 0xff, check next byte. If 0 then eat it, OTW return the 0xff
|
||||
uint8_t d;
|
||||
remaining--;
|
||||
if (1 != src->read(&d, 1)) return c;
|
||||
if (d != 0x00) {
|
||||
savedByte = d;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioFileSourceUnsync::eof()
|
||||
{
|
||||
if (remaining<=0) return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
AudioFileSourceID3::AudioFileSourceID3(AudioFileSource *src)
|
||||
{
|
||||
this->src = src;
|
||||
this->checked = false;
|
||||
}
|
||||
|
||||
AudioFileSourceID3::~AudioFileSourceID3()
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceID3::read(void *data, uint32_t len)
|
||||
{
|
||||
int rev = 0;
|
||||
|
||||
if (checked) {
|
||||
return src->read(data, len);
|
||||
}
|
||||
checked = true;
|
||||
// <10 bytes initial read, not enough space to check header
|
||||
if (len<10) return src->read(data, len);
|
||||
|
||||
uint8_t *buff = reinterpret_cast<uint8_t*>(data);
|
||||
int ret = src->read(data, 10);
|
||||
if (ret<10) return ret;
|
||||
|
||||
if ((buff[0]!='I') || (buff[1]!='D') || (buff[2]!='3') || (buff[3]>0x04) || (buff[3]<0x02) || (buff[4]!=0)) {
|
||||
cb.md("eof", false, "id3");
|
||||
return 10 + src->read(buff+10, len-10);
|
||||
}
|
||||
|
||||
rev = buff[3];
|
||||
bool unsync = false;
|
||||
bool exthdr = false;
|
||||
|
||||
switch(rev) {
|
||||
case 2:
|
||||
unsync = (buff[5] & 0x80);
|
||||
exthdr = false;
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
unsync = (buff[5] & 0x80);
|
||||
exthdr = (buff[5] & 0x40);
|
||||
break;
|
||||
};
|
||||
|
||||
int id3Size = buff[6];
|
||||
id3Size = id3Size << 7;
|
||||
id3Size |= buff[7];
|
||||
id3Size = id3Size << 7;
|
||||
id3Size |= buff[8];
|
||||
id3Size = id3Size << 7;
|
||||
id3Size |= buff[9];
|
||||
// Every read from now may be unsync'd
|
||||
AudioFileSourceUnsync id3(src, id3Size, unsync);
|
||||
|
||||
if (exthdr) {
|
||||
int ehsz = (id3.getByte()<<24) | (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte());
|
||||
for (int j=0; j<ehsz-4; j++) id3.getByte(); // Throw it away
|
||||
}
|
||||
|
||||
do {
|
||||
unsigned char frameid[4];
|
||||
int framesize;
|
||||
bool compressed;
|
||||
|
||||
frameid[0] = id3.getByte();
|
||||
frameid[1] = id3.getByte();
|
||||
frameid[2] = id3.getByte();
|
||||
if (rev==2) frameid[3] = 0;
|
||||
else frameid[3] = id3.getByte();
|
||||
|
||||
if (frameid[0]==0 && frameid[1]==0 && frameid[2]==0 && frameid[3]==0) {
|
||||
// We're in padding
|
||||
while (!id3.eof()) {
|
||||
id3.getByte();
|
||||
}
|
||||
} else {
|
||||
if (rev==2) {
|
||||
framesize = (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte());
|
||||
compressed = false;
|
||||
} else {
|
||||
framesize = (id3.getByte()<<24) | (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte());
|
||||
id3.getByte(); // skip 1st flag
|
||||
compressed = id3.getByte()&0x80;
|
||||
}
|
||||
if (compressed) {
|
||||
int decompsize = (id3.getByte()<<24) | (id3.getByte()<<16) | (id3.getByte()<<8) | (id3.getByte());
|
||||
// TODO - add libz decompression, for now ignore this one...
|
||||
(void)decompsize;
|
||||
for (int j=0; j<framesize; j++)
|
||||
id3.getByte();
|
||||
}
|
||||
|
||||
// Read the value and send to callback
|
||||
char value[64];
|
||||
uint32_t i;
|
||||
bool isUnicode = (id3.getByte()==1) ? true : false;
|
||||
for (i=0; i<(uint32_t)framesize-1; i++) {
|
||||
if (i<sizeof(value)-1) value[i] = id3.getByte();
|
||||
else (void)id3.getByte();
|
||||
}
|
||||
value[i<sizeof(value)-1?i:sizeof(value)-1] = 0; // Terminate the string...
|
||||
if ( (frameid[0]=='T' && frameid[1]=='A' && frameid[2]=='L' && frameid[3] == 'B' ) ||
|
||||
(frameid[0]=='T' && frameid[1]=='A' && frameid[2]=='L' && rev==2) ) {
|
||||
cb.md("Album", isUnicode, value);
|
||||
} else if ( (frameid[0]=='T' && frameid[1]=='I' && frameid[2]=='T' && frameid[3] == '2') ||
|
||||
(frameid[0]=='T' && frameid[1]=='T' && frameid[2]=='2' && rev==2) ) {
|
||||
cb.md("Title", isUnicode, value);
|
||||
} else if ( (frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='E' && frameid[3] == '1') ||
|
||||
(frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='1' && rev==2) ) {
|
||||
cb.md("Performer", isUnicode, value);
|
||||
} else if ( (frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && frameid[3] == 'R') ||
|
||||
(frameid[0]=='T' && frameid[1]=='Y' && frameid[2]=='E' && rev==2) ) {
|
||||
cb.md("Year", isUnicode, value);
|
||||
} else if ( (frameid[0]=='T' && frameid[1]=='R' && frameid[2]=='C' && frameid[3] == 'K') ||
|
||||
(frameid[0]=='T' && frameid[1]=='R' && frameid[2]=='K' && rev==2) ) {
|
||||
cb.md("track", isUnicode, value);
|
||||
} else if ( (frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='O' && frameid[3] == 'S') ||
|
||||
(frameid[0]=='T' && frameid[1]=='P' && frameid[2]=='A' && rev==2) ) {
|
||||
cb.md("Set", isUnicode, value);
|
||||
} else if ( (frameid[0]=='P' && frameid[1]=='O' && frameid[2]=='P' && frameid[3] == 'M') ||
|
||||
(frameid[0]=='P' && frameid[1]=='O' && frameid[2]=='P' && rev==2) ) {
|
||||
cb.md("Popularimeter", isUnicode, value);
|
||||
} else if ( (frameid[0]=='T' && frameid[1]=='C' && frameid[2]=='M' && frameid[3] == 'P') ) {
|
||||
cb.md("Compilation", isUnicode, value);
|
||||
}
|
||||
}
|
||||
} while (!id3.eof());
|
||||
|
||||
// use callback function to signal end of tags and beginning of content.
|
||||
cb.md("eof", false, "id3");
|
||||
|
||||
// All ID3 processing done, return to main caller
|
||||
return src->read(data, len);
|
||||
}
|
||||
|
||||
bool AudioFileSourceID3::seek(int32_t pos, int dir)
|
||||
{
|
||||
return src->seek(pos, dir);
|
||||
}
|
||||
|
||||
bool AudioFileSourceID3::close()
|
||||
{
|
||||
return src->close();
|
||||
}
|
||||
|
||||
bool AudioFileSourceID3::isOpen()
|
||||
{
|
||||
return src->isOpen();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceID3::getSize()
|
||||
{
|
||||
return src->getSize();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceID3::getPos()
|
||||
{
|
||||
return src->getPos();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
AudioFileSourceID3
|
||||
ID3 filter that extracts any ID3 fields and sends to CB function
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOFILESOURCEID3_H
|
||||
#define _AUDIOFILESOURCEID3_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "AudioFileSource.h"
|
||||
|
||||
class AudioFileSourceID3 : public AudioFileSource
|
||||
{
|
||||
public:
|
||||
AudioFileSourceID3(AudioFileSource *src);
|
||||
virtual ~AudioFileSourceID3() override;
|
||||
|
||||
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;
|
||||
|
||||
private:
|
||||
AudioFileSource *src;
|
||||
bool checked;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
AudioFileSourceFS
|
||||
Input Arduion "file" to be used by AudioGenerator
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOFILESOURCESPIFFS_H
|
||||
#define _AUDIOFILESOURCESPIFFS_H
|
||||
|
||||
#ifndef ESP32 // No LittleFS there, yet
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
#include "AudioFileSource.h"
|
||||
#include "AudioFileSourceFS.h"
|
||||
|
||||
class AudioFileSourceLittleFS : public AudioFileSourceFS
|
||||
{
|
||||
public:
|
||||
AudioFileSourceLittleFS() : AudioFileSourceFS(LittleFS) { };
|
||||
AudioFileSourceLittleFS(const char *filename) : AudioFileSourceFS(LittleFS, filename) {};
|
||||
// Others are inherited from base
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
AudioFileSourcePROGMEM
|
||||
Store a "file" as a PROGMEM array and use it as audio source data
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 "AudioFileSourcePROGMEM.h"
|
||||
|
||||
AudioFileSourcePROGMEM::AudioFileSourcePROGMEM()
|
||||
{
|
||||
opened = false;
|
||||
progmemData = NULL;
|
||||
progmemLen = 0;
|
||||
filePointer = 0;
|
||||
}
|
||||
|
||||
AudioFileSourcePROGMEM::AudioFileSourcePROGMEM(const void *data, uint32_t len)
|
||||
{
|
||||
open(data, len);
|
||||
}
|
||||
|
||||
AudioFileSourcePROGMEM::~AudioFileSourcePROGMEM()
|
||||
{
|
||||
}
|
||||
|
||||
bool AudioFileSourcePROGMEM::open(const void *data, uint32_t len)
|
||||
{
|
||||
if (!data || !len) return false;
|
||||
|
||||
opened = true;
|
||||
progmemData = data;
|
||||
progmemLen = len;
|
||||
filePointer = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourcePROGMEM::getSize()
|
||||
{
|
||||
if (!opened) return 0;
|
||||
return progmemLen;
|
||||
}
|
||||
|
||||
bool AudioFileSourcePROGMEM::isOpen()
|
||||
{
|
||||
return opened;
|
||||
}
|
||||
|
||||
bool AudioFileSourcePROGMEM::close()
|
||||
{
|
||||
opened = false;
|
||||
progmemData = NULL;
|
||||
progmemLen = 0;
|
||||
filePointer = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioFileSourcePROGMEM::seek(int32_t pos, int dir)
|
||||
{
|
||||
if (!opened) return false;
|
||||
uint32_t newPtr;
|
||||
switch (dir) {
|
||||
case SEEK_SET: newPtr = pos; break;
|
||||
case SEEK_CUR: newPtr = filePointer + pos; break;
|
||||
case SEEK_END: newPtr = progmemLen - pos; break;
|
||||
default: return false;
|
||||
}
|
||||
if (newPtr > progmemLen) return false;
|
||||
filePointer = newPtr;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourcePROGMEM::read(void *data, uint32_t len)
|
||||
{
|
||||
if (!opened) return 0;
|
||||
if (filePointer >= progmemLen) return 0;
|
||||
|
||||
uint32_t toRead = progmemLen - filePointer;
|
||||
if (toRead > len) toRead = len;
|
||||
|
||||
memcpy_P(data, reinterpret_cast<const uint8_t*>(progmemData)+filePointer, toRead);
|
||||
filePointer += toRead;
|
||||
return toRead;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
AudioFileSourcePROGMEM
|
||||
Store a "file" as a PROGMEM array and use it as audio source data
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOFILESOURCEPROGMEM_H
|
||||
#define _AUDIOFILESOURCEPROGMEM_H
|
||||
|
||||
#include "AudioFileSource.h"
|
||||
|
||||
class AudioFileSourcePROGMEM : public AudioFileSource
|
||||
{
|
||||
public:
|
||||
AudioFileSourcePROGMEM();
|
||||
AudioFileSourcePROGMEM(const void *data, uint32_t len);
|
||||
virtual ~AudioFileSourcePROGMEM() override;
|
||||
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 { if (!opened) return 0; else return filePointer; };
|
||||
|
||||
bool open(const void *data, uint32_t len);
|
||||
|
||||
private:
|
||||
bool opened;
|
||||
const void *progmemData;
|
||||
uint32_t progmemLen;
|
||||
uint32_t filePointer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
AudioFileSourceSPIFFS
|
||||
Input SD card "file" to be used by AudioGenerator
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 "AudioFileSourceSD.h"
|
||||
|
||||
AudioFileSourceSD::AudioFileSourceSD()
|
||||
{
|
||||
}
|
||||
|
||||
AudioFileSourceSD::AudioFileSourceSD(const char *filename)
|
||||
{
|
||||
open(filename);
|
||||
}
|
||||
|
||||
bool AudioFileSourceSD::open(const char *filename)
|
||||
{
|
||||
f = SD.open(filename, FILE_READ);
|
||||
return f;
|
||||
}
|
||||
|
||||
AudioFileSourceSD::~AudioFileSourceSD()
|
||||
{
|
||||
if (f) f.close();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceSD::read(void *data, uint32_t len)
|
||||
{
|
||||
return f.read(reinterpret_cast<uint8_t*>(data), len);
|
||||
}
|
||||
|
||||
bool AudioFileSourceSD::seek(int32_t pos, int dir)
|
||||
{
|
||||
if (!f) return false;
|
||||
if (dir==SEEK_SET) return f.seek(pos);
|
||||
else if (dir==SEEK_CUR) return f.seek(f.position() + pos);
|
||||
else if (dir==SEEK_END) return f.seek(f.size() + pos);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioFileSourceSD::close()
|
||||
{
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioFileSourceSD::isOpen()
|
||||
{
|
||||
return f?true:false;
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceSD::getSize()
|
||||
{
|
||||
if (!f) return 0;
|
||||
return f.size();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceSD::getPos()
|
||||
{
|
||||
if (!f) return 0;
|
||||
return f.position();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
AudioFileSourceSPIFFS
|
||||
Input SD card "file" to be used by AudioGenerator
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOFILESOURCESD_H
|
||||
#define _AUDIOFILESOURCESD_H
|
||||
|
||||
#include "AudioFileSource.h"
|
||||
#include <SD.h>
|
||||
|
||||
|
||||
class AudioFileSourceSD : public AudioFileSource
|
||||
{
|
||||
public:
|
||||
AudioFileSourceSD();
|
||||
AudioFileSourceSD(const char *filename);
|
||||
virtual ~AudioFileSourceSD() override;
|
||||
|
||||
virtual bool open(const char *filename) override;
|
||||
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;
|
||||
|
||||
private:
|
||||
File f;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
AudioFileSourceFS
|
||||
Input Arduion "file" to be used by AudioGenerator
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOFILESOURCESPIFFS_H
|
||||
#define _AUDIOFILESOURCESPIFFS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
|
||||
#include "AudioFileSource.h"
|
||||
#include "AudioFileSourceFS.h"
|
||||
|
||||
class AudioFileSourceSPIFFS : public AudioFileSourceFS
|
||||
{
|
||||
public:
|
||||
AudioFileSourceSPIFFS() : AudioFileSourceFS(SPIFFS) { };
|
||||
AudioFileSourceSPIFFS(const char *filename) : AudioFileSourceFS(SPIFFS, filename) {};
|
||||
// Others are inherited from base
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
AudioFileSourceSPIRAMBuffer
|
||||
Buffered file source in external SPI RAM
|
||||
|
||||
Copyright (C) 2017 Sebastien Decourriere
|
||||
Based on AudioFileSourceBuffer class from Earle F. Philhower, III
|
||||
|
||||
Copyright (C) 2020 Earle F. Philhower, III
|
||||
Rewritten for speed and functionality
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "AudioFileSourceSPIRAMBuffer.h"
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
AudioFileSourceSPIRAMBuffer::AudioFileSourceSPIRAMBuffer(AudioFileSource *source, uint8_t csPin, uint32_t buffSizeBytes)
|
||||
{
|
||||
ram.begin(40, csPin);
|
||||
ramSize = buffSizeBytes;
|
||||
writePtr = 0;
|
||||
readPtr = 0;
|
||||
filled = false;
|
||||
src = source;
|
||||
audioLogger->printf_P(PSTR("SPI RAM buffer size: %u Bytes\n"), ramSize);
|
||||
}
|
||||
|
||||
AudioFileSourceSPIRAMBuffer::~AudioFileSourceSPIRAMBuffer()
|
||||
{
|
||||
ram.end();
|
||||
}
|
||||
|
||||
bool AudioFileSourceSPIRAMBuffer::seek(int32_t pos, int dir)
|
||||
{
|
||||
// Invalidate
|
||||
readPtr = 0;
|
||||
writePtr = 0;
|
||||
filled = false;
|
||||
return src->seek(pos, dir);
|
||||
}
|
||||
|
||||
bool AudioFileSourceSPIRAMBuffer::close()
|
||||
{
|
||||
return src->close();
|
||||
}
|
||||
|
||||
bool AudioFileSourceSPIRAMBuffer::isOpen()
|
||||
{
|
||||
return src->isOpen();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceSPIRAMBuffer::getSize()
|
||||
{
|
||||
return src->getSize();
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceSPIRAMBuffer::getPos()
|
||||
{
|
||||
return src->getPos() - (writePtr - readPtr);
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceSPIRAMBuffer::read(void *data, uint32_t len)
|
||||
{
|
||||
uint32_t bytes = 0;
|
||||
|
||||
// Check if the buffer isn't empty, otherwise we try to fill completely
|
||||
if (!filled) {
|
||||
cb.st(999, PSTR("Filling buffer..."));
|
||||
uint8_t buffer[256];
|
||||
writePtr = 0;
|
||||
readPtr = 0;
|
||||
// Fill up completely before returning any data at all
|
||||
do {
|
||||
int toRead = std::min(ramSize - (writePtr - readPtr), sizeof(buffer));
|
||||
int length = src->read(buffer, toRead);
|
||||
if (length > 0) {
|
||||
#ifdef FAKERAM
|
||||
for (size_t i=0; i<length; i++) fakeRAM[(i+writePtr)%ramSize] = buffer[i];
|
||||
#else
|
||||
ram.writeBytes(writePtr % ramSize, buffer, length);
|
||||
#endif
|
||||
writePtr += length;
|
||||
} else {
|
||||
// EOF, break out of read loop
|
||||
break;
|
||||
}
|
||||
} while ((writePtr - readPtr) < ramSize);
|
||||
filled = true;
|
||||
cb.st(999, PSTR("Buffer filled..."));
|
||||
}
|
||||
|
||||
// Read up to the entire buffer from RAM
|
||||
uint32_t toReadFromBuffer = std::min(len, writePtr - readPtr);
|
||||
uint8_t *ptr = reinterpret_cast<uint8_t*>(data);
|
||||
if (toReadFromBuffer > 0) {
|
||||
#ifdef FAKERAM
|
||||
for (size_t i=0; i<toReadFromBuffer; i++) ptr[i] = fakeRAM[(i+readPtr)%ramSize];
|
||||
#else
|
||||
ram.readBytes(readPtr % ramSize, ptr, toReadFromBuffer);
|
||||
#endif
|
||||
readPtr += toReadFromBuffer;
|
||||
ptr += toReadFromBuffer;
|
||||
bytes += toReadFromBuffer;
|
||||
len -= toReadFromBuffer;
|
||||
}
|
||||
|
||||
// If len>0 there is no data left in buffer and we try to read more directly from source.
|
||||
// Then, we trigger a complete buffer refill
|
||||
if (len) {
|
||||
bytes += src->read(data, len);
|
||||
filled = false;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void AudioFileSourceSPIRAMBuffer::fill()
|
||||
{
|
||||
// Make sure the buffer is pre-filled before make partial fill.
|
||||
if (!filled) return;
|
||||
|
||||
for (auto i=0; i<5; i++) {
|
||||
// Make sure there is at least buffer size free in RAM
|
||||
uint8_t buffer[128];
|
||||
if ((ramSize - (writePtr - readPtr)) < sizeof(buffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cnt = src->readNonBlock(buffer, sizeof(buffer));
|
||||
if (cnt) {
|
||||
#ifdef FAKERAM
|
||||
for (size_t i=0; i<cnt; i++) fakeRAM[(i+writePtr)%ramSize] = buffer[i];
|
||||
#else
|
||||
ram.writeBytes(writePtr % ramSize, buffer, cnt);
|
||||
#endif
|
||||
writePtr += cnt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioFileSourceSPIRAMBuffer::loop()
|
||||
{
|
||||
static uint32_t last = 0;
|
||||
if (!src->loop()) return false;
|
||||
fill();
|
||||
if ((ESP.getCycleCount() - last) > microsecondsToClockCycles(1000000)) {
|
||||
last = ESP.getCycleCount();
|
||||
char str[65];
|
||||
memset(str, '#', 64);
|
||||
str[64] = 0;
|
||||
str[((writePtr - readPtr) * 64)/ramSize] = 0;
|
||||
cb.st(((writePtr - readPtr) * 100)/ramSize, str);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
AudioFileSourceSPIRAMBuffer
|
||||
Buffered file source in external SPI RAM
|
||||
|
||||
Copyright (C) 2017 Sebastien Decourriere
|
||||
Based on AudioFileSourceBuffer class from Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
#pragma once
|
||||
|
||||
#include "AudioFileSource.h"
|
||||
#include <SPI.h>
|
||||
#include "spiram-fast.h"
|
||||
//#define FAKERAM
|
||||
// #define SPIBUF_DEBUG
|
||||
|
||||
class AudioFileSourceSPIRAMBuffer : public AudioFileSource
|
||||
{
|
||||
public:
|
||||
#ifdef FAKERAM
|
||||
AudioFileSourceSPIRAMBuffer(AudioFileSource *in, uint8_t csPin = 15, uint32_t bufferBytes = 2048);
|
||||
#else
|
||||
AudioFileSourceSPIRAMBuffer(AudioFileSource *in, uint8_t csPin = 15, uint32_t bufferBytes = 128*1024);
|
||||
#endif
|
||||
virtual ~AudioFileSourceSPIRAMBuffer() override;
|
||||
|
||||
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;
|
||||
virtual bool loop() override;
|
||||
|
||||
private:
|
||||
virtual void fill();
|
||||
|
||||
private:
|
||||
AudioFileSource *src;
|
||||
ESP8266SPIRAM ram;
|
||||
size_t ramSize;
|
||||
size_t writePtr;
|
||||
size_t readPtr;
|
||||
bool filled;
|
||||
|
||||
#ifdef FAKERAM
|
||||
char fakeRAM[2048];
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
AudioFileSourceSTDIO
|
||||
Input STDIO "file" to be used by AudioGenerator
|
||||
Only for hot-based testing
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 <Arduino.h>
|
||||
#ifndef ARDUINO
|
||||
#include <time.h>
|
||||
|
||||
#include "AudioFileSourceSTDIO.h"
|
||||
|
||||
AudioFileSourceSTDIO::AudioFileSourceSTDIO()
|
||||
{
|
||||
f = NULL;
|
||||
srand(time(NULL));
|
||||
}
|
||||
|
||||
AudioFileSourceSTDIO::AudioFileSourceSTDIO(const char *filename)
|
||||
{
|
||||
open(filename);
|
||||
}
|
||||
|
||||
bool AudioFileSourceSTDIO::open(const char *filename)
|
||||
{
|
||||
f = fopen(filename, "rb");
|
||||
return f;
|
||||
}
|
||||
|
||||
AudioFileSourceSTDIO::~AudioFileSourceSTDIO()
|
||||
{
|
||||
if (f) fclose(f);
|
||||
f = NULL;
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceSTDIO::read(void *data, uint32_t len)
|
||||
{
|
||||
// if (rand() % 100 == 69) { // Give 0 data 1%
|
||||
// printf("0 read\n");
|
||||
// len = 0;
|
||||
// } else if (rand() % 100 == 1) { // Give short reads 1%
|
||||
// printf("0 read\n");
|
||||
// len = 0;
|
||||
// }
|
||||
int ret = fread(reinterpret_cast<uint8_t*>(data), 1, len, f);
|
||||
// if (ret && rand() % 100 < 5 ) {
|
||||
// // We're really mean...throw bad data in the mix
|
||||
// printf("bad data\n");
|
||||
// for (int i=0; i<100; i++)
|
||||
// *(reinterpret_cast<uint8_t*>(data) + (rand() % ret)) = rand();
|
||||
// }
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AudioFileSourceSTDIO::seek(int32_t pos, int dir)
|
||||
{
|
||||
return fseek(f, pos, dir) == 0;
|
||||
}
|
||||
|
||||
bool AudioFileSourceSTDIO::close()
|
||||
{
|
||||
fclose(f);
|
||||
f = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioFileSourceSTDIO::isOpen()
|
||||
{
|
||||
return f?true:false;
|
||||
}
|
||||
|
||||
uint32_t AudioFileSourceSTDIO::getSize()
|
||||
{
|
||||
if (!f) return 0;
|
||||
uint32_t p = ftell(f);
|
||||
fseek(f, 0, SEEK_END);
|
||||
uint32_t len = ftell(f);
|
||||
fseek(f, p, SEEK_SET);
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
AudioFileSourceSTDIO
|
||||
Input SPIFFS "file" to be used by AudioGenerator
|
||||
Only for host-based testing, not Arduino
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOFILESOURCESTDIO_H
|
||||
#define _AUDIOFILESOURCESTDIO_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifndef ARDUINO
|
||||
|
||||
#include "AudioFileSource.h"
|
||||
|
||||
class AudioFileSourceSTDIO : public AudioFileSource
|
||||
{
|
||||
public:
|
||||
AudioFileSourceSTDIO();
|
||||
AudioFileSourceSTDIO(const char *filename);
|
||||
virtual ~AudioFileSourceSTDIO() override;
|
||||
|
||||
virtual bool open(const char *filename) override;
|
||||
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 { if (!f) return 0; else return (uint32_t)ftell(f); };
|
||||
|
||||
private:
|
||||
FILE *f;
|
||||
};
|
||||
|
||||
#endif // !ARDUINO
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <Arduino.h>
|
||||
#include "AudioFileStream.h"
|
||||
|
||||
|
||||
AudioFileStream::AudioFileStream(AudioFileSource *source, int definedLen)
|
||||
{
|
||||
src = source;
|
||||
len = definedLen;
|
||||
ptr = 0;
|
||||
saved = -1;
|
||||
}
|
||||
|
||||
AudioFileStream::~AudioFileStream()
|
||||
{
|
||||
// If there's a defined len, read until we're empty
|
||||
if (len) {
|
||||
while (ptr++ < len) (void)read();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int AudioFileStream::available()
|
||||
{
|
||||
if (saved >= 0) return 1;
|
||||
else if (len) return ptr - len;
|
||||
else if (src->getSize()) return (src->getPos() - src->getSize());
|
||||
else return 1;
|
||||
}
|
||||
|
||||
int AudioFileStream::read()
|
||||
{
|
||||
uint8_t c;
|
||||
int r;
|
||||
if (ptr >= len) return -1;
|
||||
ptr++;
|
||||
if (saved >= 0) {
|
||||
c = (uint8_t)saved;
|
||||
saved = -1;
|
||||
r = 1;
|
||||
} else {
|
||||
r = src->read(&c, 1);
|
||||
}
|
||||
if (r != 1) return -1;
|
||||
return (int)c;
|
||||
}
|
||||
|
||||
int AudioFileStream::peek()
|
||||
{
|
||||
uint8_t c;
|
||||
if ((ptr+1) >= len) return -1;
|
||||
if (saved >= 0) return saved;
|
||||
int r = src->read(&c, 1);
|
||||
if (r<1) return -1;
|
||||
saved = c;
|
||||
return saved;
|
||||
}
|
||||
|
||||
void AudioFileStream::flush()
|
||||
{
|
||||
/* noop? */
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
AudioFileStream
|
||||
Convert an AudioFileSource* to a Stream*
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 AUDIOFILESTREAM_H
|
||||
#define AUDIOFILESTREAM_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "AudioFileSource.h"
|
||||
|
||||
class AudioFileStream : public Stream
|
||||
{
|
||||
public:
|
||||
AudioFileStream(AudioFileSource *source, int definedLen);
|
||||
virtual ~AudioFileStream();
|
||||
|
||||
public:
|
||||
// Stream interface - see the Arduino library documentation.
|
||||
virtual int available() override;
|
||||
virtual int read() override;
|
||||
virtual int peek() override;
|
||||
virtual void flush() override;
|
||||
virtual size_t write(uint8_t x) override { (void)x; return 0; };
|
||||
|
||||
private:
|
||||
AudioFileSource *src;
|
||||
int saved;
|
||||
int len;
|
||||
int ptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
AudioGenerator
|
||||
Base class of an audio output generator
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOGENERATOR_H
|
||||
#define _AUDIOGENERATOR_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "AudioStatus.h"
|
||||
#include "AudioFileSource.h"
|
||||
#include "AudioOutput.h"
|
||||
|
||||
class AudioGenerator
|
||||
{
|
||||
public:
|
||||
AudioGenerator() { lastSample[0] = 0; lastSample[1] = 0; };
|
||||
virtual ~AudioGenerator() {};
|
||||
virtual bool begin(AudioFileSource *source, AudioOutput *output) { (void)source; (void)output; return false; };
|
||||
virtual bool loop() { return false; };
|
||||
virtual bool stop() { return false; };
|
||||
virtual bool isRunning() { return false;};
|
||||
virtual void desync () { };
|
||||
|
||||
public:
|
||||
virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); }
|
||||
virtual bool RegisterStatusCB(AudioStatus::statusCBFn fn, void *data) { return cb.RegisterStatusCB(fn, data); }
|
||||
|
||||
protected:
|
||||
bool running;
|
||||
AudioFileSource *file;
|
||||
AudioOutput *output;
|
||||
int16_t lastSample[2];
|
||||
|
||||
protected:
|
||||
AudioStatus cb;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
AudioGeneratorAAC
|
||||
Audio output generator using the Helix AAC decoder
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
#include "AudioGeneratorAAC.h"
|
||||
|
||||
AudioGeneratorAAC::AudioGeneratorAAC()
|
||||
{
|
||||
preallocateSpace = NULL;
|
||||
preallocateSize = 0;
|
||||
|
||||
running = false;
|
||||
file = NULL;
|
||||
output = NULL;
|
||||
|
||||
buff = (uint8_t*)malloc(buffLen);
|
||||
outSample = (int16_t*)malloc(1024 * 2 * sizeof(uint16_t));
|
||||
if (!buff || !outSample) {
|
||||
audioLogger->printf_P(PSTR("ERROR: Out of memory in AAC\n"));
|
||||
Serial.flush();
|
||||
}
|
||||
|
||||
hAACDecoder = AACInitDecoder();
|
||||
if (!hAACDecoder) {
|
||||
audioLogger->printf_P(PSTR("Out of memory error! hAACDecoder==NULL\n"));
|
||||
Serial.flush();
|
||||
}
|
||||
|
||||
buffValid = 0;
|
||||
lastFrameEnd = 0;
|
||||
validSamples = 0;
|
||||
curSample = 0;
|
||||
lastRate = 0;
|
||||
lastChannels = 0;
|
||||
}
|
||||
|
||||
AudioGeneratorAAC::AudioGeneratorAAC(void *preallocateData, int preallocateSz)
|
||||
{
|
||||
preallocateSpace = preallocateData;
|
||||
preallocateSize = preallocateSz;
|
||||
|
||||
running = false;
|
||||
file = NULL;
|
||||
output = NULL;
|
||||
|
||||
uint8_t *p = (uint8_t*)preallocateSpace;
|
||||
buff = (uint8_t*) p;
|
||||
p += (buffLen + 7) & ~7;
|
||||
outSample = (int16_t*) p;
|
||||
p += (1024 * 2 * sizeof(int16_t) + 7) & ~7;
|
||||
int used = p - (uint8_t*)preallocateSpace;
|
||||
int availSpace = preallocateSize - used;
|
||||
if (availSpace < 0 ) {
|
||||
audioLogger->printf_P(PSTR("ERROR: Out of memory in AAC\n"));
|
||||
}
|
||||
|
||||
hAACDecoder = AACInitDecoderPre(p, availSpace);
|
||||
if (!hAACDecoder) {
|
||||
audioLogger->printf_P(PSTR("Out of memory error! hAACDecoder==NULL\n"));
|
||||
Serial.flush();
|
||||
}
|
||||
buffValid = 0;
|
||||
lastFrameEnd = 0;
|
||||
validSamples = 0;
|
||||
curSample = 0;
|
||||
lastRate = 0;
|
||||
lastChannels = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
AudioGeneratorAAC::~AudioGeneratorAAC()
|
||||
{
|
||||
if (!preallocateSpace) {
|
||||
AACFreeDecoder(hAACDecoder);
|
||||
free(buff);
|
||||
free(outSample);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioGeneratorAAC::stop()
|
||||
{
|
||||
running = false;
|
||||
output->stop();
|
||||
return file->close();
|
||||
}
|
||||
|
||||
bool AudioGeneratorAAC::isRunning()
|
||||
{
|
||||
return running;
|
||||
}
|
||||
|
||||
bool AudioGeneratorAAC::FillBufferWithValidFrame()
|
||||
{
|
||||
buff[0] = 0; // Destroy any existing sync word @ 0
|
||||
int nextSync;
|
||||
do {
|
||||
nextSync = AACFindSyncWord(buff + lastFrameEnd, buffValid - lastFrameEnd);
|
||||
if (nextSync >= 0) nextSync += lastFrameEnd;
|
||||
lastFrameEnd = 0;
|
||||
if (nextSync == -1) {
|
||||
if (buffValid && buff[buffValid-1]==0xff) { // Could be 1st half of syncword, preserve it...
|
||||
buff[0] = 0xff;
|
||||
buffValid = file->read(buff+1, buffLen-1);
|
||||
if (buffValid==0) return false; // No data available, EOF
|
||||
} else { // Try a whole new buffer
|
||||
buffValid = file->read(buff, buffLen-1);
|
||||
if (buffValid==0) return false; // No data available, EOF
|
||||
}
|
||||
}
|
||||
} while (nextSync == -1);
|
||||
|
||||
// Move the frame to start at offset 0 in the buffer
|
||||
buffValid -= nextSync; // Throw out prior to nextSync
|
||||
memmove(buff, buff+nextSync, buffValid);
|
||||
|
||||
// We have a sync word at 0 now, try and fill remainder of buffer
|
||||
buffValid += file->read(buff + buffValid, buffLen - buffValid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorAAC::loop()
|
||||
{
|
||||
if (!running) goto done; // Nothing to do here!
|
||||
|
||||
// If we've got data, try and pump it out...
|
||||
while (validSamples) {
|
||||
if (lastChannels == 1) {
|
||||
lastSample[0] = outSample[curSample];
|
||||
lastSample[1] = outSample[curSample];
|
||||
} else {
|
||||
lastSample[0] = outSample[curSample*2];
|
||||
lastSample[1] = outSample[curSample*2 + 1];
|
||||
}
|
||||
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
|
||||
validSamples--;
|
||||
curSample++;
|
||||
}
|
||||
|
||||
// No samples available, need to decode a new frame
|
||||
if (FillBufferWithValidFrame()) {
|
||||
// buff[0] start of frame, decode it...
|
||||
unsigned char *inBuff = reinterpret_cast<unsigned char *>(buff);
|
||||
int bytesLeft = buffValid;
|
||||
int ret = AACDecode(hAACDecoder, &inBuff, &bytesLeft, outSample);
|
||||
if (ret) {
|
||||
// Error, skip the frame...
|
||||
char buff[48];
|
||||
sprintf_P(buff, PSTR("AAC decode error %d"), ret);
|
||||
cb.st(ret, buff);
|
||||
} else {
|
||||
lastFrameEnd = buffValid - bytesLeft;
|
||||
AACFrameInfo fi;
|
||||
AACGetLastFrameInfo(hAACDecoder, &fi);
|
||||
if ((int)fi.sampRateOut != (int)lastRate) {
|
||||
output->SetRate(fi.sampRateOut);
|
||||
lastRate = fi.sampRateOut;
|
||||
}
|
||||
if (fi.nChans != lastChannels) {
|
||||
output->SetChannels(fi.nChans);
|
||||
lastChannels = fi.nChans;
|
||||
}
|
||||
curSample = 0;
|
||||
validSamples = fi.outputSamps / lastChannels;
|
||||
}
|
||||
} else {
|
||||
running = false; // No more data, we're done here...
|
||||
}
|
||||
|
||||
done:
|
||||
file->loop();
|
||||
output->loop();
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
bool AudioGeneratorAAC::begin(AudioFileSource *source, AudioOutput *output)
|
||||
{
|
||||
if (!source) return false;
|
||||
file = source;
|
||||
if (!output) return false;
|
||||
this->output = output;
|
||||
if (!file->isOpen()) return false; // Error
|
||||
|
||||
output->begin();
|
||||
|
||||
// AAC always comes out at 16 bits
|
||||
output->SetBitsPerSample(16);
|
||||
|
||||
|
||||
memset(buff, 0, buffLen);
|
||||
memset(outSample, 0, 1024*2*sizeof(int16_t));
|
||||
|
||||
|
||||
running = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
AudioGeneratorAAC
|
||||
Audio output generator using the Helix AAC decoder
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOGENERATORAAC_H
|
||||
#define _AUDIOGENERATORAAC_H
|
||||
|
||||
#include "AudioGenerator.h"
|
||||
#include "libhelix-aac/aacdec.h"
|
||||
|
||||
class AudioGeneratorAAC : public AudioGenerator
|
||||
{
|
||||
public:
|
||||
AudioGeneratorAAC();
|
||||
AudioGeneratorAAC(void *preallocateData, int preallocateSize);
|
||||
virtual ~AudioGeneratorAAC() override;
|
||||
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
|
||||
virtual bool loop() override;
|
||||
virtual bool stop() override;
|
||||
virtual bool isRunning() override;
|
||||
|
||||
protected:
|
||||
void *preallocateSpace;
|
||||
int preallocateSize;
|
||||
|
||||
// Helix AAC decoder
|
||||
HAACDecoder hAACDecoder;
|
||||
|
||||
// Input buffering
|
||||
const int buffLen = 1600;
|
||||
uint8_t *buff; //[1600]; // File buffer required to store at least a whole compressed frame
|
||||
int16_t buffValid;
|
||||
int16_t lastFrameEnd;
|
||||
bool FillBufferWithValidFrame(); // Read until we get a valid syncword and min(feof, 2048) butes in the buffer
|
||||
|
||||
// Output buffering
|
||||
int16_t *outSample; //[1024 * 2]; // Interleaved L/R
|
||||
int16_t validSamples;
|
||||
int16_t curSample;
|
||||
|
||||
// Each frame may change this if they're very strange, I guess
|
||||
unsigned int lastRate;
|
||||
int lastChannels;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
AudioGeneratorFLAC
|
||||
Audio output generator that plays FLAC audio files
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 <AudioGeneratorFLAC.h>
|
||||
|
||||
AudioGeneratorFLAC::AudioGeneratorFLAC()
|
||||
{
|
||||
flac = NULL;
|
||||
channels = 0;
|
||||
sampleRate = 0;
|
||||
bitsPerSample = 0;
|
||||
buff[0] = NULL;
|
||||
buff[1] = NULL;
|
||||
buffPtr = 0;
|
||||
buffLen = 0;
|
||||
running = false;
|
||||
}
|
||||
|
||||
AudioGeneratorFLAC::~AudioGeneratorFLAC()
|
||||
{
|
||||
if (flac)
|
||||
FLAC__stream_decoder_delete(flac);
|
||||
flac = NULL;
|
||||
}
|
||||
|
||||
bool AudioGeneratorFLAC::begin(AudioFileSource *source, AudioOutput *output)
|
||||
{
|
||||
if (!source) return false;
|
||||
file = source;
|
||||
if (!output) return false;
|
||||
this->output = output;
|
||||
if (!file->isOpen()) return false; // Error
|
||||
|
||||
flac = FLAC__stream_decoder_new();
|
||||
if (!flac) return false;
|
||||
|
||||
(void)FLAC__stream_decoder_set_md5_checking(flac, false);
|
||||
|
||||
FLAC__StreamDecoderInitStatus ret = FLAC__stream_decoder_init_stream(flac, _read_cb, _seek_cb, _tell_cb, _length_cb, _eof_cb, _write_cb, _metadata_cb, _error_cb, reinterpret_cast<void*>(this) );
|
||||
if (ret != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
FLAC__stream_decoder_delete(flac);
|
||||
flac = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
output->begin();
|
||||
running = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorFLAC::loop()
|
||||
{
|
||||
FLAC__bool ret;
|
||||
|
||||
if (!running) goto done;
|
||||
|
||||
if (!output->ConsumeSample(lastSample)) goto done; // Try and send last buffered sample
|
||||
|
||||
do {
|
||||
if (buffPtr == buffLen) {
|
||||
ret = FLAC__stream_decoder_process_single(flac);
|
||||
if (!ret) {
|
||||
running = false;
|
||||
goto done;
|
||||
} else {
|
||||
// We might be done...
|
||||
if (FLAC__stream_decoder_get_state(flac)==FLAC__STREAM_DECODER_END_OF_STREAM) {
|
||||
running = false;
|
||||
goto done;
|
||||
}
|
||||
unsigned newsr = FLAC__stream_decoder_get_sample_rate(flac);
|
||||
unsigned newch = FLAC__stream_decoder_get_channels(flac);
|
||||
unsigned newbps = FLAC__stream_decoder_get_bits_per_sample(flac);
|
||||
if (newsr != sampleRate) output->SetRate(sampleRate = newsr);
|
||||
if (newch != channels) output->SetChannels(channels = newch);
|
||||
if (newbps != bitsPerSample) output->SetBitsPerSample( bitsPerSample = newbps);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for some weird case where above didn't give any data
|
||||
if (buffPtr == buffLen) {
|
||||
goto done; // At some point the flac better error and we'll return
|
||||
}
|
||||
if (bitsPerSample <= 16) {
|
||||
lastSample[AudioOutput::LEFTCHANNEL] = buff[0][buffPtr] & 0xffff;
|
||||
if (channels==2) lastSample[AudioOutput::RIGHTCHANNEL] = buff[1][buffPtr] & 0xffff;
|
||||
else lastSample[AudioOutput::RIGHTCHANNEL] = lastSample[AudioOutput::LEFTCHANNEL];
|
||||
} else if (bitsPerSample <= 24) {
|
||||
lastSample[AudioOutput::LEFTCHANNEL] = (buff[0][buffPtr]>>8) & 0xffff;
|
||||
if (channels==2) lastSample[AudioOutput::RIGHTCHANNEL] = (buff[1][buffPtr]>>8) & 0xffff;
|
||||
else lastSample[AudioOutput::RIGHTCHANNEL] = lastSample[AudioOutput::LEFTCHANNEL];
|
||||
} else {
|
||||
lastSample[AudioOutput::LEFTCHANNEL] = (buff[0][buffPtr]>>16) & 0xffff;
|
||||
if (channels==2) lastSample[AudioOutput::RIGHTCHANNEL] = (buff[1][buffPtr]>>16) & 0xffff;
|
||||
else lastSample[AudioOutput::RIGHTCHANNEL] = lastSample[AudioOutput::LEFTCHANNEL];
|
||||
}
|
||||
buffPtr++;
|
||||
} while (running && output->ConsumeSample(lastSample));
|
||||
|
||||
done:
|
||||
file->loop();
|
||||
output->loop();
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
bool AudioGeneratorFLAC::stop()
|
||||
{
|
||||
if (flac)
|
||||
FLAC__stream_decoder_delete(flac);
|
||||
flac = NULL;
|
||||
running = false;
|
||||
output->stop();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorFLAC::isRunning()
|
||||
{
|
||||
return running;
|
||||
}
|
||||
|
||||
|
||||
|
||||
FLAC__StreamDecoderReadStatus AudioGeneratorFLAC::read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes)
|
||||
{
|
||||
(void) decoder;
|
||||
if (*bytes==0) return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
||||
*bytes = file->read(buffer, sizeof(FLAC__byte) * (*bytes));
|
||||
if (*bytes==0) return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
||||
}
|
||||
FLAC__StreamDecoderSeekStatus AudioGeneratorFLAC::seek_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset)
|
||||
{
|
||||
(void) decoder;
|
||||
if (!file->seek((int32_t)absolute_byte_offset, 0)) return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
|
||||
}
|
||||
FLAC__StreamDecoderTellStatus AudioGeneratorFLAC::tell_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset)
|
||||
{
|
||||
(void) decoder;
|
||||
*absolute_byte_offset = file->getPos();
|
||||
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderLengthStatus AudioGeneratorFLAC::length_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length)
|
||||
{
|
||||
(void) decoder;
|
||||
*stream_length = file->getSize();
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
|
||||
}
|
||||
FLAC__bool AudioGeneratorFLAC::eof_cb(const FLAC__StreamDecoder *decoder)
|
||||
{
|
||||
(void) decoder;
|
||||
if (file->getPos() >= file->getSize()) return true;
|
||||
return false;
|
||||
}
|
||||
FLAC__StreamDecoderWriteStatus AudioGeneratorFLAC::write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[])
|
||||
{
|
||||
(void) decoder;
|
||||
// Hackish warning here. FLAC sends the buffer but doesn't free it until the next call to decode_frame, so we stash
|
||||
// the pointers here and use it in our loop() instead of memcpy()'ing into yet another buffer.
|
||||
buffLen = frame->header.blocksize;
|
||||
buff[0] = (const int *)buffer[0];
|
||||
if (frame->header.channels>1) buff[1] = (const int *)buffer[1];
|
||||
else buff[1] = (const int *)buffer[0];
|
||||
buffPtr = 0;
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
void AudioGeneratorFLAC::metadata_cb(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata)
|
||||
{
|
||||
(void) decoder;
|
||||
(void) metadata;
|
||||
audioLogger->printf_P(PSTR("Metadata\n"));
|
||||
}
|
||||
char AudioGeneratorFLAC::error_cb_str[64];
|
||||
void AudioGeneratorFLAC::error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status)
|
||||
{
|
||||
(void) decoder;
|
||||
strncpy_P(error_cb_str, FLAC__StreamDecoderErrorStatusString[status], 64);
|
||||
cb.st((int)status, error_cb_str);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
AudioGeneratorFLAC
|
||||
Audio output generator that plays FLAC audio files
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOGENERATORFLAC_H
|
||||
#define _AUDIOGENERATORFLAC_H
|
||||
|
||||
#include <AudioGenerator.h>
|
||||
extern "C" {
|
||||
#include "libflac/FLAC/stream_decoder.h"
|
||||
};
|
||||
|
||||
class AudioGeneratorFLAC : public AudioGenerator
|
||||
{
|
||||
public:
|
||||
AudioGeneratorFLAC();
|
||||
virtual ~AudioGeneratorFLAC() override;
|
||||
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
|
||||
virtual bool loop() override;
|
||||
virtual bool stop() override;
|
||||
virtual bool isRunning() override;
|
||||
|
||||
protected:
|
||||
// FLAC info
|
||||
uint16_t channels;
|
||||
uint32_t sampleRate;
|
||||
uint16_t bitsPerSample;
|
||||
|
||||
// We need to buffer some data in-RAM to avoid doing 1000s of small reads
|
||||
const int *buff[2];
|
||||
uint16_t buffPtr;
|
||||
uint16_t buffLen;
|
||||
FLAC__StreamDecoder *flac;
|
||||
|
||||
// FLAC callbacks, need static functions to bounce into c++ from c
|
||||
static FLAC__StreamDecoderReadStatus _read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) {
|
||||
return static_cast<AudioGeneratorFLAC*>(client_data)->read_cb(decoder, buffer, bytes);
|
||||
};
|
||||
static FLAC__StreamDecoderSeekStatus _seek_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) {
|
||||
return static_cast<AudioGeneratorFLAC*>(client_data)->seek_cb(decoder, absolute_byte_offset);
|
||||
};
|
||||
static FLAC__StreamDecoderTellStatus _tell_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) {
|
||||
return static_cast<AudioGeneratorFLAC*>(client_data)->tell_cb(decoder, absolute_byte_offset);
|
||||
};
|
||||
static FLAC__StreamDecoderLengthStatus _length_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) {
|
||||
return static_cast<AudioGeneratorFLAC*>(client_data)->length_cb(decoder, stream_length);
|
||||
};
|
||||
static FLAC__bool _eof_cb(const FLAC__StreamDecoder *decoder, void *client_data) {
|
||||
return static_cast<AudioGeneratorFLAC*>(client_data)->eof_cb(decoder);
|
||||
};
|
||||
static FLAC__StreamDecoderWriteStatus _write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) {
|
||||
return static_cast<AudioGeneratorFLAC*>(client_data)->write_cb(decoder, frame, buffer);
|
||||
};
|
||||
static void _metadata_cb(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
|
||||
static_cast<AudioGeneratorFLAC*>(client_data)->metadata_cb(decoder, metadata);
|
||||
};
|
||||
static void _error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) {
|
||||
static_cast<AudioGeneratorFLAC*>(client_data)->error_cb(decoder, status);
|
||||
};
|
||||
// Actual FLAC callbacks
|
||||
FLAC__StreamDecoderReadStatus read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes);
|
||||
FLAC__StreamDecoderSeekStatus seek_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset);
|
||||
FLAC__StreamDecoderTellStatus tell_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset);
|
||||
FLAC__StreamDecoderLengthStatus length_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length);
|
||||
FLAC__bool eof_cb(const FLAC__StreamDecoder *decoder);
|
||||
FLAC__StreamDecoderWriteStatus write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[]);
|
||||
void metadata_cb(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata);
|
||||
static char error_cb_str[64];
|
||||
void error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,639 @@
|
||||
/*
|
||||
AudioGeneratorMIDI
|
||||
Audio output generator that plays MIDI files using a SF2 SoundFont
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
The MIDI processing engine is a heavily modified version of MIDITONES,
|
||||
by Len Shustek, https://github.com/LenShustek/miditones .
|
||||
Whereas MIDITONES original simply parsed a file beforehand to a byte
|
||||
stream to be played by another program, this does the parsing and
|
||||
playback in real-time.
|
||||
|
||||
Here's his original header/readme w/MIT license, which is subsumed by the
|
||||
GPL license of the ESP8266Audio project.
|
||||
*/
|
||||
|
||||
/***************************************************************************
|
||||
|
||||
MIDITONES: Convert a MIDI file into a simple bytestream of notes
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2011,2013,2015,2016, Len Shustek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
**************************************************************************/
|
||||
|
||||
|
||||
#include "AudioGeneratorMIDI.h"
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
#define TSF_NO_STDIO
|
||||
#define TSF_IMPLEMENTATION
|
||||
#include "libtinysoundfont/tsf.h"
|
||||
|
||||
/**************** utility routines **********************/
|
||||
|
||||
/* announce a fatal MIDI file format error */
|
||||
|
||||
void AudioGeneratorMIDI::midi_error(const char *msg, int curpos)
|
||||
{
|
||||
cb.st(curpos, msg);
|
||||
#if 0
|
||||
int ptr;
|
||||
audioLogger->printf("---> MIDI file error at position %04X (%d): %s\n", (uint16_t) curpos, (uint16_t) curpos, msg);
|
||||
/* print some bytes surrounding the error */
|
||||
ptr = curpos - 16;
|
||||
if (ptr < 0) ptr = 0;
|
||||
buffer.seek( buffer.data, ptr );
|
||||
for (int i = 0; i < 32; i++) {
|
||||
char c;
|
||||
buffer.read (buffer.data, &c, 1);
|
||||
audioLogger->printf((ptr + i) == curpos ? " [%02X] " : "%02X ", (int) c & 0xff);
|
||||
}
|
||||
audioLogger->printf("\n");
|
||||
#endif
|
||||
running = false;
|
||||
}
|
||||
|
||||
/* check that we have a specified number of bytes left in the buffer */
|
||||
|
||||
void AudioGeneratorMIDI::chk_bufdata (int ptr, unsigned long int len) {
|
||||
if ((unsigned) (ptr + len) > buflen)
|
||||
midi_error ("data missing", ptr);
|
||||
}
|
||||
|
||||
/* fetch big-endian numbers */
|
||||
|
||||
uint16_t AudioGeneratorMIDI::rev_short (uint16_t val) {
|
||||
return ((val & 0xff) << 8) | ((val >> 8) & 0xff);
|
||||
}
|
||||
|
||||
uint32_t AudioGeneratorMIDI::rev_long (uint32_t val) {
|
||||
return (((rev_short ((uint16_t) val) & 0xffff) << 16) |
|
||||
(rev_short ((uint16_t) (val >> 16)) & 0xffff));
|
||||
}
|
||||
|
||||
/************** process the MIDI file header *****************/
|
||||
|
||||
void AudioGeneratorMIDI::process_header (void) {
|
||||
struct midi_header hdr;
|
||||
unsigned int time_division;
|
||||
|
||||
chk_bufdata (hdrptr, sizeof (struct midi_header));
|
||||
buffer.seek (buffer.data, hdrptr);
|
||||
buffer.read (buffer.data, &hdr, sizeof (hdr));
|
||||
if (!charcmp ((char *) hdr.MThd, "MThd"))
|
||||
midi_error ("Missing 'MThd'", hdrptr);
|
||||
num_tracks = rev_short (hdr.number_of_tracks);
|
||||
time_division = rev_short (hdr.time_division);
|
||||
if (time_division < 0x8000)
|
||||
ticks_per_beat = time_division;
|
||||
else
|
||||
ticks_per_beat = ((time_division >> 8) & 0x7f) /* SMTE frames/sec */ *(time_division & 0xff); /* ticks/SMTE frame */
|
||||
hdrptr += rev_long (hdr.header_size) + 8; /* point past header to track header, presumably. */
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**************** Process a MIDI track header *******************/
|
||||
|
||||
void AudioGeneratorMIDI::start_track (int tracknum) {
|
||||
struct track_header hdr;
|
||||
unsigned long tracklen;
|
||||
|
||||
chk_bufdata (hdrptr, sizeof (struct track_header));
|
||||
buffer.seek (buffer.data, hdrptr);
|
||||
buffer.read (buffer.data, &hdr, sizeof (hdr));
|
||||
if (!charcmp ((char *) (hdr.MTrk), "MTrk"))
|
||||
midi_error ("Missing 'MTrk'", hdrptr);
|
||||
tracklen = rev_long (hdr.track_size);
|
||||
hdrptr += sizeof (struct track_header); /* point past header */
|
||||
chk_bufdata (hdrptr, tracklen);
|
||||
track[tracknum].trkptr = hdrptr;
|
||||
hdrptr += tracklen; /* point to the start of the next track */
|
||||
track[tracknum].trkend = hdrptr; /* the point past the end of the track */
|
||||
}
|
||||
|
||||
unsigned char AudioGeneratorMIDI::buffer_byte (int offset) {
|
||||
unsigned char c;
|
||||
buffer.seek (buffer.data, offset);
|
||||
buffer.read (buffer.data, &c, 1);
|
||||
return c;
|
||||
}
|
||||
|
||||
unsigned short AudioGeneratorMIDI::buffer_short (int offset) {
|
||||
unsigned short s;
|
||||
buffer.seek (buffer.data, offset);
|
||||
buffer.read (buffer.data, &s, sizeof (short));
|
||||
return s;
|
||||
}
|
||||
|
||||
unsigned int AudioGeneratorMIDI::buffer_int32 (int offset) {
|
||||
uint32_t i;
|
||||
buffer.seek (buffer.data, offset);
|
||||
buffer.read (buffer.data, &i, sizeof (i));
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Get a MIDI-style variable-length integer */
|
||||
|
||||
unsigned long AudioGeneratorMIDI::get_varlen (int *ptr) {
|
||||
/* Get a 1-4 byte variable-length value and adjust the pointer past it.
|
||||
These are a succession of 7-bit values with a MSB bit of zero marking the end */
|
||||
|
||||
unsigned long val;
|
||||
int i, byte;
|
||||
|
||||
val = 0;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
byte = buffer_byte ((*ptr)++);
|
||||
val = (val << 7) | (byte & 0x7f);
|
||||
if (!(byte & 0x80))
|
||||
return val;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
/*************** Process the MIDI track data ***************************/
|
||||
|
||||
/* Skip in the track for the next "note on", "note off" or "set tempo" command,
|
||||
then record that information in the track status block and return. */
|
||||
|
||||
void AudioGeneratorMIDI::find_note (int tracknum) {
|
||||
unsigned long int delta_time;
|
||||
int event, chan;
|
||||
int note, velocity, controller, pressure, pitchbend, instrument;
|
||||
int meta_cmd, meta_length;
|
||||
unsigned long int sysex_length;
|
||||
struct track_status *t;
|
||||
const char *tag;
|
||||
|
||||
/* process events */
|
||||
|
||||
t = &track[tracknum]; /* our track status structure */
|
||||
while (t->trkptr < t->trkend) {
|
||||
|
||||
delta_time = get_varlen (&t->trkptr);
|
||||
t->time += delta_time;
|
||||
if (buffer_byte (t->trkptr) < 0x80)
|
||||
event = t->last_event; /* using "running status": same event as before */
|
||||
else { /* otherwise get new "status" (event type) */
|
||||
event = buffer_byte (t->trkptr++);
|
||||
}
|
||||
if (event == 0xff) { /* meta-event */
|
||||
meta_cmd = buffer_byte (t->trkptr++);
|
||||
meta_length = get_varlen(&t->trkptr);
|
||||
switch (meta_cmd) {
|
||||
case 0x00:
|
||||
break;
|
||||
case 0x01:
|
||||
tag = "description";
|
||||
goto show_text;
|
||||
case 0x02:
|
||||
tag = "copyright";
|
||||
goto show_text;
|
||||
case 0x03:
|
||||
tag = "track name";
|
||||
goto show_text;
|
||||
case 0x04:
|
||||
tag = "instrument name";
|
||||
goto show_text;
|
||||
case 0x05:
|
||||
tag = "lyric";
|
||||
goto show_text;
|
||||
case 0x06:
|
||||
tag = "marked point";
|
||||
goto show_text;
|
||||
case 0x07:
|
||||
tag = "cue point";
|
||||
show_text:
|
||||
break;
|
||||
case 0x20:
|
||||
break;
|
||||
case 0x2f:
|
||||
break;
|
||||
case 0x51: /* tempo: 3 byte big-endian integer! */
|
||||
t->cmd = CMD_TEMPO;
|
||||
t->tempo = rev_long (buffer_int32 (t->trkptr - 1)) & 0xffffffL;
|
||||
t->trkptr += meta_length;
|
||||
return;
|
||||
case 0x54:
|
||||
break;
|
||||
case 0x58:
|
||||
break;
|
||||
case 0x59:
|
||||
break;
|
||||
case 0x7f:
|
||||
tag = "sequencer data";
|
||||
goto show_hex;
|
||||
default: /* unknown meta command */
|
||||
tag = "???";
|
||||
show_hex:
|
||||
break;
|
||||
}
|
||||
t->trkptr += meta_length;
|
||||
}
|
||||
|
||||
else if (event < 0x80)
|
||||
midi_error ("Unknown MIDI event type", t->trkptr);
|
||||
|
||||
else {
|
||||
if (event < 0xf0)
|
||||
t->last_event = event; // remember "running status" if not meta or sysex event
|
||||
chan = event & 0xf;
|
||||
t->chan = chan;
|
||||
switch (event >> 4) {
|
||||
case 0x8:
|
||||
t->note = buffer_byte (t->trkptr++);
|
||||
velocity = buffer_byte (t->trkptr++);
|
||||
note_off:
|
||||
t->cmd = CMD_STOPNOTE;
|
||||
return; /* stop processing and return */
|
||||
case 0x9:
|
||||
t->note = buffer_byte (t->trkptr++);
|
||||
velocity = buffer_byte (t->trkptr++);
|
||||
if (velocity == 0) /* some scores use note-on with zero velocity for off! */
|
||||
goto note_off;
|
||||
t->velocity = velocity;
|
||||
t->cmd = CMD_PLAYNOTE;
|
||||
return; /* stop processing and return */
|
||||
case 0xa:
|
||||
note = buffer_byte (t->trkptr++);
|
||||
velocity = buffer_byte (t->trkptr++);
|
||||
break;
|
||||
case 0xb:
|
||||
controller = buffer_byte (t->trkptr++);
|
||||
velocity = buffer_byte (t->trkptr++);
|
||||
break;
|
||||
case 0xc:
|
||||
instrument = buffer_byte (t->trkptr++);
|
||||
midi_chan_instrument[chan] = instrument; // record new instrument for this channel
|
||||
break;
|
||||
case 0xd:
|
||||
pressure = buffer_byte (t->trkptr++);
|
||||
break;
|
||||
case 0xe:
|
||||
pitchbend = buffer_byte (t->trkptr) | (buffer_byte (t->trkptr + 1) << 7);
|
||||
t->trkptr += 2;
|
||||
break;
|
||||
case 0xf:
|
||||
sysex_length = get_varlen (&t->trkptr);
|
||||
t->trkptr += sysex_length;
|
||||
break;
|
||||
default:
|
||||
midi_error ("Unknown MIDI command", t->trkptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
t->cmd = CMD_TRACKDONE; /* no more notes to process */
|
||||
++tracks_done;
|
||||
|
||||
// Remove unused warnings..maybe some day we'll look at these
|
||||
(void)note;
|
||||
(void)controller;
|
||||
(void)pressure;
|
||||
(void)pitchbend;
|
||||
(void)tag;
|
||||
}
|
||||
|
||||
|
||||
// Open file, parse headers, get ready tio process MIDI
|
||||
void AudioGeneratorMIDI::PrepareMIDI(AudioFileSource *src)
|
||||
{
|
||||
MakeStreamFromAFS(src, &afsMIDI);
|
||||
tsf_stream_wrap_cached(&afsMIDI, 32, 64, &buffer);
|
||||
buflen = buffer.size (buffer.data);
|
||||
|
||||
/* process the MIDI file header */
|
||||
|
||||
hdrptr = buffer.tell (buffer.data); /* pointer to file and track headers */
|
||||
process_header ();
|
||||
printf (" Processing %d tracks.\n", num_tracks);
|
||||
if (num_tracks > MAX_TRACKS)
|
||||
midi_error ("Too many tracks", buffer.tell (buffer.data));
|
||||
|
||||
/* initialize processing of all the tracks */
|
||||
|
||||
for (tracknum = 0; tracknum < num_tracks; ++tracknum) {
|
||||
start_track (tracknum); /* process the track header */
|
||||
find_note (tracknum); /* position to the first note on/off */
|
||||
}
|
||||
|
||||
notes_skipped = 0;
|
||||
tracknum = 0;
|
||||
earliest_tracknum = 0;
|
||||
earliest_time = 0;
|
||||
}
|
||||
|
||||
// Parses the note on/offs until we are ready to render some more samples. Then return the
|
||||
// total number of samples to render before we need to be called again
|
||||
int AudioGeneratorMIDI::PlayMIDI()
|
||||
{
|
||||
/* Continue processing all tracks, in an order based on the simulated time.
|
||||
This is not unlike multiway merging used for tape sorting algoritms in the 50's! */
|
||||
|
||||
do { /* while there are still track notes to process */
|
||||
static struct track_status *trk;
|
||||
static struct tonegen_status *tg;
|
||||
static int tgnum;
|
||||
static int count_tracks;
|
||||
static unsigned long delta_time, delta_msec;
|
||||
|
||||
/* Find the track with the earliest event time,
|
||||
and output a delay command if time has advanced.
|
||||
|
||||
A potential improvement: If there are multiple tracks with the same time,
|
||||
first do the ones with STOPNOTE as the next command, if any. That would
|
||||
help avoid running out of tone generators. In practice, though, most MIDI
|
||||
files do all the STOPNOTEs first anyway, so it won't have much effect.
|
||||
*/
|
||||
|
||||
earliest_time = 0x7fffffff;
|
||||
|
||||
/* Usually we start with the track after the one we did last time (tracknum),
|
||||
so that if we run out of tone generators, we have been fair to all the tracks.
|
||||
The alternate "strategy1" says we always start with track 0, which means
|
||||
that we favor early tracks over later ones when there aren't enough tone generators.
|
||||
*/
|
||||
|
||||
count_tracks = num_tracks;
|
||||
do {
|
||||
if (++tracknum >= num_tracks)
|
||||
tracknum = 0;
|
||||
trk = &track[tracknum];
|
||||
if (trk->cmd != CMD_TRACKDONE && trk->time < earliest_time) {
|
||||
earliest_time = trk->time;
|
||||
earliest_tracknum = tracknum;
|
||||
}
|
||||
} while (--count_tracks);
|
||||
|
||||
tracknum = earliest_tracknum; /* the track we picked */
|
||||
trk = &track[tracknum];
|
||||
if (earliest_time < timenow)
|
||||
midi_error ("INTERNAL: time went backwards", trk->trkptr);
|
||||
|
||||
/* If time has advanced, output a "delay" command */
|
||||
|
||||
delta_time = earliest_time - timenow;
|
||||
if (delta_time) {
|
||||
/* Convert ticks to milliseconds based on the current tempo */
|
||||
unsigned long long temp;
|
||||
temp = ((unsigned long long) delta_time * tempo) / ticks_per_beat;
|
||||
delta_msec = temp / 1000; // get around LCC compiler bug
|
||||
if (delta_msec > 0x7fff)
|
||||
midi_error ("INTERNAL: time delta too big", trk->trkptr);
|
||||
int samples = (((int) delta_msec) * freq) / 1000;
|
||||
timenow = earliest_time;
|
||||
return samples;
|
||||
}
|
||||
timenow = earliest_time;
|
||||
|
||||
/* If this track event is "set tempo", just change the global tempo.
|
||||
That affects how we generate "delay" commands. */
|
||||
|
||||
if (trk->cmd == CMD_TEMPO) {
|
||||
tempo = trk->tempo;
|
||||
find_note (tracknum);
|
||||
}
|
||||
|
||||
/* If this track event is "stop note", process it and all subsequent "stop notes" for this track
|
||||
that are happening at the same time. Doing so frees up as many tone generators as possible. */
|
||||
|
||||
else if (trk->cmd == CMD_STOPNOTE)
|
||||
do {
|
||||
// stop a note
|
||||
for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { /* find which generator is playing it */
|
||||
tg = &tonegen[tgnum];
|
||||
if (tg->playing && tg->track == tracknum && tg->note == trk->note) {
|
||||
tsf_note_off (g_tsf, tg->instrument, tg->note);
|
||||
tg->playing = false;
|
||||
trk->tonegens[tgnum] = false;
|
||||
}
|
||||
}
|
||||
find_note (tracknum); // use up the note
|
||||
} while (trk->cmd == CMD_STOPNOTE && trk->time == timenow);
|
||||
|
||||
/* If this track event is "start note", process only it.
|
||||
Don't do more than one, so we allow other tracks their chance at grabbing tone generators. */
|
||||
|
||||
else if (trk->cmd == CMD_PLAYNOTE) {
|
||||
bool foundgen = false;
|
||||
/* if not, then try for any free tone generator */
|
||||
if (!foundgen)
|
||||
for (tgnum = 0; tgnum < num_tonegens; ++tgnum) {
|
||||
tg = &tonegen[tgnum];
|
||||
if (!tg->playing) {
|
||||
foundgen = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundgen) {
|
||||
if (tgnum + 1 > num_tonegens_used)
|
||||
num_tonegens_used = tgnum + 1;
|
||||
tg->playing = true;
|
||||
tg->track = tracknum;
|
||||
tg->note = trk->note;
|
||||
trk->tonegens[tgnum] = true;
|
||||
trk->preferred_tonegen = tgnum;
|
||||
if (tg->instrument != midi_chan_instrument[trk->chan]) { /* new instrument for this generator */
|
||||
tg->instrument = midi_chan_instrument[trk->chan];
|
||||
}
|
||||
tsf_note_on (g_tsf, tg->instrument, tg->note, trk->velocity / 127.0); // velocity = 0...127
|
||||
} else {
|
||||
++notes_skipped;
|
||||
}
|
||||
find_note (tracknum); // use up the note
|
||||
}
|
||||
}
|
||||
while (tracks_done < num_tracks);
|
||||
return -1; // EOF
|
||||
}
|
||||
|
||||
|
||||
void AudioGeneratorMIDI::StopMIDI()
|
||||
{
|
||||
|
||||
buffer.close(buffer.data);
|
||||
tsf_close(g_tsf);
|
||||
printf (" %s %d tone generators were used.\n",
|
||||
num_tonegens_used < num_tonegens ? "Only" : "All", num_tonegens_used);
|
||||
if (notes_skipped)
|
||||
printf
|
||||
(" %d notes were skipped because there weren't enough tone generators.\n", notes_skipped);
|
||||
|
||||
printf (" Done.\n");
|
||||
}
|
||||
|
||||
|
||||
bool AudioGeneratorMIDI::begin(AudioFileSource *src, AudioOutput *out)
|
||||
{
|
||||
// Clear out status variables
|
||||
for (int i=0; i<MAX_TONEGENS; i++) memset(&tonegen[i], 0, sizeof(struct tonegen_status));
|
||||
for (int i=0; i<MAX_TRACKS; i++) memset(&track[i], 0, sizeof(struct track_status));
|
||||
memset(midi_chan_instrument, 0, sizeof(midi_chan_instrument));
|
||||
|
||||
g_tsf = tsf_load(&afsSF2);
|
||||
if (!g_tsf) return false;
|
||||
tsf_set_output (g_tsf, TSF_MONO, freq, -10 /* dB gain -10 */ );
|
||||
|
||||
if (!out->SetRate( freq )) return false;
|
||||
if (!out->SetBitsPerSample( 16 )) return false;
|
||||
if (!out->SetChannels( 1 )) return false;
|
||||
if (!out->begin()) return false;
|
||||
|
||||
output = out;
|
||||
file = src;
|
||||
|
||||
running = true;
|
||||
|
||||
PrepareMIDI(src);
|
||||
|
||||
samplesToPlay = 0;
|
||||
numSamplesRendered = 0;
|
||||
sentSamplesRendered = 0;
|
||||
|
||||
sawEOF = false;
|
||||
return running;
|
||||
}
|
||||
|
||||
|
||||
bool AudioGeneratorMIDI::loop()
|
||||
{
|
||||
static int c = 0;
|
||||
|
||||
if (!running) goto done; // Nothing to do here!
|
||||
|
||||
// First, try and push in the stored sample. If we can't, then punt and try later
|
||||
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
|
||||
|
||||
// Try and stuff the buffer one sample at a time
|
||||
do {
|
||||
c++;
|
||||
if (c%44100 == 0) yield();
|
||||
|
||||
play:
|
||||
|
||||
if (sentSamplesRendered < numSamplesRendered) {
|
||||
lastSample[AudioOutput::LEFTCHANNEL] = samplesRendered[sentSamplesRendered];
|
||||
lastSample[AudioOutput::RIGHTCHANNEL] = samplesRendered[sentSamplesRendered];
|
||||
sentSamplesRendered++;
|
||||
} else if (samplesToPlay) {
|
||||
numSamplesRendered = sizeof(samplesRendered)/sizeof(samplesRendered[0]);
|
||||
if ((int)samplesToPlay < (int)(sizeof(samplesRendered)/sizeof(samplesRendered[0]))) numSamplesRendered = samplesToPlay;
|
||||
tsf_render_short_fast(g_tsf, samplesRendered, numSamplesRendered, 0);
|
||||
lastSample[AudioOutput::LEFTCHANNEL] = samplesRendered[0];
|
||||
lastSample[AudioOutput::RIGHTCHANNEL] = samplesRendered[0];
|
||||
sentSamplesRendered = 1;
|
||||
samplesToPlay -= numSamplesRendered;
|
||||
} else {
|
||||
numSamplesRendered = 0;
|
||||
sentSamplesRendered = 0;
|
||||
if (sawEOF) {
|
||||
running = false;
|
||||
} else {
|
||||
samplesToPlay = PlayMIDI();
|
||||
if (samplesToPlay == -1) {
|
||||
sawEOF = true;
|
||||
samplesToPlay = freq / 2;
|
||||
}
|
||||
goto play;
|
||||
}
|
||||
}
|
||||
} while (running && output->ConsumeSample(lastSample));
|
||||
|
||||
done:
|
||||
file->loop();
|
||||
output->loop();
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
bool AudioGeneratorMIDI::stop()
|
||||
{
|
||||
StopMIDI();
|
||||
output->stop();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int AudioGeneratorMIDI::afs_read(void *data, void *ptr, unsigned int size)
|
||||
{
|
||||
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
|
||||
return s->read(ptr, size);
|
||||
}
|
||||
|
||||
int AudioGeneratorMIDI::afs_tell(void *data)
|
||||
{
|
||||
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
|
||||
return s->getPos();
|
||||
}
|
||||
|
||||
int AudioGeneratorMIDI::afs_skip(void *data, unsigned int count)
|
||||
{
|
||||
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
|
||||
return s->seek(count, SEEK_CUR);
|
||||
}
|
||||
|
||||
int AudioGeneratorMIDI::afs_seek(void *data, unsigned int pos)
|
||||
{
|
||||
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
|
||||
return s->seek(pos, SEEK_SET);
|
||||
}
|
||||
|
||||
int AudioGeneratorMIDI::afs_close(void *data)
|
||||
{
|
||||
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
|
||||
return s->close();
|
||||
}
|
||||
|
||||
int AudioGeneratorMIDI::afs_size(void *data)
|
||||
{
|
||||
AudioFileSource *s = reinterpret_cast<AudioFileSource *>(data);
|
||||
return s->getSize();
|
||||
}
|
||||
|
||||
void AudioGeneratorMIDI::MakeStreamFromAFS(AudioFileSource *src, tsf_stream *afs)
|
||||
{
|
||||
afs->data = reinterpret_cast<void*>(src);
|
||||
afs->read = &afs_read;
|
||||
afs->tell = &afs_tell;
|
||||
afs->skip = &afs_skip;
|
||||
afs->seek = &afs_seek;
|
||||
afs->close = &afs_close;
|
||||
afs->size = &afs_size;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
AudioGeneratorMIDI
|
||||
Audio output generator that plays MIDI files using a SF2 SoundFont
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOGENERATORMIDI_H
|
||||
#define _AUDIOGENERATORMIDI_H
|
||||
|
||||
#include "AudioGenerator.h"
|
||||
|
||||
#define TSF_NO_STDIO
|
||||
#include "libtinysoundfont/tsf.h"
|
||||
|
||||
class AudioGeneratorMIDI : public AudioGenerator
|
||||
{
|
||||
public:
|
||||
AudioGeneratorMIDI() { freq=44100; running = false; };
|
||||
virtual ~AudioGeneratorMIDI() override {};
|
||||
bool SetSoundfont(AudioFileSource *newsf2) {
|
||||
if (isRunning()) return false;
|
||||
sf2 = newsf2;
|
||||
MakeStreamFromAFS(sf2, &afsSF2);
|
||||
return true;
|
||||
}
|
||||
bool SetSampleRate(int newfreq) {
|
||||
if (isRunning()) return false;
|
||||
freq = newfreq;
|
||||
return true;
|
||||
}
|
||||
virtual bool begin(AudioFileSource *mid, AudioOutput *output) override;
|
||||
virtual bool loop() override;
|
||||
virtual bool stop() override;
|
||||
virtual bool isRunning() override { return running; };
|
||||
|
||||
private:
|
||||
int freq;
|
||||
tsf *g_tsf;
|
||||
struct tsf_stream buffer;
|
||||
struct tsf_stream afsMIDI;
|
||||
struct tsf_stream afsSF2;
|
||||
AudioFileSource *sf2;
|
||||
AudioFileSource *midi;
|
||||
|
||||
protected:
|
||||
struct midi_header {
|
||||
int8_t MThd[4];
|
||||
uint32_t header_size;
|
||||
uint16_t format_type;
|
||||
uint16_t number_of_tracks;
|
||||
uint16_t time_division;
|
||||
};
|
||||
|
||||
struct track_header {
|
||||
int8_t MTrk[4];
|
||||
uint32_t track_size;
|
||||
};
|
||||
|
||||
enum { MAX_TONEGENS = 32, /* max tone generators: tones we can play simultaneously */
|
||||
MAX_TRACKS = 24
|
||||
}; /* max number of MIDI tracks we will process */
|
||||
|
||||
int hdrptr;
|
||||
unsigned long buflen;
|
||||
int num_tracks;
|
||||
int tracks_done = 0;
|
||||
int num_tonegens = MAX_TONEGENS;
|
||||
int num_tonegens_used = 0;
|
||||
unsigned int ticks_per_beat = 240;
|
||||
unsigned long timenow = 0;
|
||||
unsigned long tempo; /* current tempo in usec/qnote */
|
||||
// State needed for PlayMID()
|
||||
int notes_skipped = 0;
|
||||
int tracknum = 0;
|
||||
int earliest_tracknum = 0;
|
||||
unsigned long earliest_time = 0;
|
||||
|
||||
struct tonegen_status { /* current status of a tone generator */
|
||||
bool playing; /* is it playing? */
|
||||
char track; /* if so, which track is the note from? */
|
||||
char note; /* what note is playing? */
|
||||
char instrument; /* what instrument? */
|
||||
} tonegen[MAX_TONEGENS];
|
||||
|
||||
struct track_status { /* current processing point of a MIDI track */
|
||||
int trkptr; /* ptr to the next note change */
|
||||
int trkend; /* ptr past the end of the track */
|
||||
unsigned long time; /* what time we're at in the score */
|
||||
unsigned long tempo; /* the tempo last set, in usec per qnote */
|
||||
unsigned int preferred_tonegen; /* for strategy2, try to use this generator */
|
||||
unsigned char cmd; /* CMD_xxxx next to do */
|
||||
unsigned char note; /* for which note */
|
||||
unsigned char chan; /* from which channel it was */
|
||||
unsigned char velocity; /* the current volume */
|
||||
unsigned char last_event; /* the last event, for MIDI's "running status" */
|
||||
bool tonegens[MAX_TONEGENS]; /* which tone generators our notes are playing on */
|
||||
} track[MAX_TRACKS];
|
||||
|
||||
int midi_chan_instrument[16]; /* which instrument is currently being played on each channel */
|
||||
|
||||
/* output bytestream commands, which are also stored in track_status.cmd */
|
||||
enum { CMD_PLAYNOTE = 0x90, /* play a note: low nibble is generator #, note is next byte */
|
||||
CMD_STOPNOTE = 0x80, /* stop a note: low nibble is generator # */
|
||||
CMD_INSTRUMENT = 0xc0, /* change instrument; low nibble is generator #, instrument is next byte */
|
||||
CMD_RESTART = 0xe0, /* restart the score from the beginning */
|
||||
CMD_STOP = 0xf0, /* stop playing */
|
||||
CMD_TEMPO = 0xFE, /* tempo in usec per quarter note ("beat") */
|
||||
CMD_TRACKDONE = 0xFF
|
||||
}; /* no more data left in this track */
|
||||
|
||||
|
||||
|
||||
/* portable string length */
|
||||
int strlength (const char *str) {
|
||||
int i;
|
||||
for (i = 0; str[i] != '\0'; ++i);
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
/* match a constant character sequence */
|
||||
|
||||
int charcmp (const char *buf, const char *match) {
|
||||
int len, i;
|
||||
len = strlength (match);
|
||||
for (i = 0; i < len; ++i)
|
||||
if (buf[i] != match[i])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned char buffer_byte (int offset);
|
||||
unsigned short buffer_short (int offset);
|
||||
unsigned int buffer_int32 (int offset);
|
||||
|
||||
void midi_error (const char *msg, int curpos);
|
||||
void chk_bufdata (int ptr, unsigned long int len);
|
||||
uint16_t rev_short (uint16_t val);
|
||||
uint32_t rev_long (uint32_t val);
|
||||
void process_header (void);
|
||||
void start_track (int tracknum);
|
||||
|
||||
unsigned long get_varlen (int *ptr);
|
||||
void find_note (int tracknum);
|
||||
void PrepareMIDI(AudioFileSource *src);
|
||||
int PlayMIDI();
|
||||
void StopMIDI();
|
||||
|
||||
// tsf_stream <-> AudioFileSource
|
||||
static int afs_read(void *data, void *ptr, unsigned int size);
|
||||
static int afs_tell(void *data);
|
||||
static int afs_skip(void *data, unsigned int count);
|
||||
static int afs_seek(void *data, unsigned int pos);
|
||||
static int afs_close(void *data);
|
||||
static int afs_size(void *data);
|
||||
void MakeStreamFromAFS(AudioFileSource *src, tsf_stream *afs);
|
||||
|
||||
int samplesToPlay;
|
||||
bool sawEOF;
|
||||
int numSamplesRendered;
|
||||
int sentSamplesRendered ;
|
||||
short samplesRendered[256];
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,876 @@
|
||||
/*
|
||||
AudioGeneratorMOD
|
||||
Audio output generator that plays Amiga MOD tracker files
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
#define PGM_READ_UNALIGNED 0
|
||||
|
||||
#include "AudioGeneratorMOD.h"
|
||||
|
||||
/*
|
||||
Ported/hacked out from STELLARPLAYER by Ronen K.
|
||||
http://mobile4dev.blogspot.com/2012/11/stellaris-launchpad-mod-player.html
|
||||
A version exists in GitHub at https://github.com/steveway/stellarplayer
|
||||
and also at https://github.com/MikesModz/StellarPlayer
|
||||
Both which were themselves a port of the PIC32 MOD player
|
||||
https://www.youtube.com/watch?v=i3Yl0TISQBE (seems to no longer be available.)
|
||||
|
||||
Most changes involved reducing memory usage by changing data structures,
|
||||
moving constants to PROGMEM and minor tweaks to allow non pow2 buffer sizes.
|
||||
*/
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
#define NOTE(r, c) (Player.currentPattern.note8[r][c]==NONOTE8?NONOTE:8*Player.currentPattern.note8[r][c])
|
||||
|
||||
#ifndef min
|
||||
#define min(X,Y) ((X) < (Y) ? (X) : (Y))
|
||||
#endif
|
||||
|
||||
AudioGeneratorMOD::AudioGeneratorMOD()
|
||||
{
|
||||
sampleRate = 44100;
|
||||
fatBufferSize = 6 * 1024;
|
||||
stereoSeparation = 32;
|
||||
mixerTick = 0;
|
||||
usePAL = false;
|
||||
UpdateAmiga();
|
||||
running = false;
|
||||
file = NULL;
|
||||
output = NULL;
|
||||
}
|
||||
|
||||
AudioGeneratorMOD::~AudioGeneratorMOD()
|
||||
{
|
||||
// Free any remaining buffers
|
||||
for (int i = 0; i < CHANNELS; i++) {
|
||||
FatBuffer.channels[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioGeneratorMOD::stop()
|
||||
{
|
||||
// We may be stopping because of allocation failures, so always deallocate
|
||||
for (int i = 0; i < CHANNELS; i++) {
|
||||
free(FatBuffer.channels[i]);
|
||||
FatBuffer.channels[i] = NULL;
|
||||
}
|
||||
if (file) file->close();
|
||||
running = false;
|
||||
output->stop();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorMOD::loop()
|
||||
{
|
||||
if (!running) goto done; // Easy-peasy
|
||||
|
||||
// First, try and push in the stored sample. If we can't, then punt and try later
|
||||
if (!output->ConsumeSample(lastSample)) goto done; // FIFO full, wait...
|
||||
|
||||
// Now advance enough times to fill the i2s buffer
|
||||
do {
|
||||
if (mixerTick == 0) {
|
||||
running = RunPlayer();
|
||||
if (!running) {
|
||||
stop();
|
||||
goto done;
|
||||
}
|
||||
mixerTick = Player.samplesPerTick;
|
||||
}
|
||||
GetSample( lastSample );
|
||||
mixerTick--;
|
||||
} while (output->ConsumeSample(lastSample));
|
||||
|
||||
done:
|
||||
file->loop();
|
||||
output->loop();
|
||||
|
||||
// We'll be left with one sample still in our buffer because it couldn't fit in the FIFO
|
||||
return running;
|
||||
}
|
||||
|
||||
bool AudioGeneratorMOD::begin(AudioFileSource *source, AudioOutput *out)
|
||||
{
|
||||
if (running) stop();
|
||||
|
||||
if (!source) return false;
|
||||
file = source;
|
||||
if (!out) return false;
|
||||
output = out;
|
||||
|
||||
if (!file->isOpen()) return false; // Can't read the file!
|
||||
|
||||
// Set the output values properly
|
||||
if (!output->SetRate(sampleRate)) return false;
|
||||
if (!output->SetBitsPerSample(16)) return false;
|
||||
if (!output->SetChannels(2)) return false;
|
||||
if (!output->begin()) return false;
|
||||
|
||||
UpdateAmiga();
|
||||
|
||||
for (int i = 0; i < CHANNELS; i++) {
|
||||
FatBuffer.channels[i] = reinterpret_cast<uint8_t*>(malloc(fatBufferSize));
|
||||
if (!FatBuffer.channels[i]) {
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!LoadMOD()) {
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
running = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sorted Amiga periods
|
||||
static const uint16_t amigaPeriods[296] PROGMEM = {
|
||||
907, 900, 894, 887, 881, 875, 868, 862, // -8 to -1
|
||||
856, 850, 844, 838, 832, 826, 820, 814, // C-1 to +7
|
||||
808, 802, 796, 791, 785, 779, 774, 768, // C#1 to +7
|
||||
762, 757, 752, 746, 741, 736, 730, 725, // D-1 to +7
|
||||
720, 715, 709, 704, 699, 694, 689, 684, // D#1 to +7
|
||||
678, 675, 670, 665, 660, 655, 651, 646, // E-1 to +7
|
||||
640, 636, 632, 628, 623, 619, 614, 610, // F-1 to +7
|
||||
604, 601, 597, 592, 588, 584, 580, 575, // F#1 to +7
|
||||
570, 567, 563, 559, 555, 551, 547, 543, // G-1 to +7
|
||||
538, 535, 532, 528, 524, 520, 516, 513, // G#1 to +7
|
||||
508, 505, 502, 498, 494, 491, 487, 484, // A-1 to +7
|
||||
480, 477, 474, 470, 467, 463, 460, 457, // A#1 to +7
|
||||
453, 450, 447, 444, 441, 437, 434, 431, // B-1 to +7
|
||||
428, 425, 422, 419, 416, 413, 410, 407, // C-2 to +7
|
||||
404, 401, 398, 395, 392, 390, 387, 384, // C#2 to +7
|
||||
381, 379, 376, 373, 370, 368, 365, 363, // D-2 to +7
|
||||
360, 357, 355, 352, 350, 347, 345, 342, // D#2 to +7
|
||||
339, 337, 335, 332, 330, 328, 325, 323, // E-2 to +7
|
||||
320, 318, 316, 314, 312, 309, 307, 305, // F-2 to +7
|
||||
302, 300, 298, 296, 294, 292, 290, 288, // F#2 to +7
|
||||
285, 284, 282, 280, 278, 276, 274, 272, // G-2 to +7
|
||||
269, 268, 266, 264, 262, 260, 258, 256, // G#2 to +7
|
||||
254, 253, 251, 249, 247, 245, 244, 242, // A-2 to +7
|
||||
240, 238, 237, 235, 233, 232, 230, 228, // A#2 to +7
|
||||
226, 225, 223, 222, 220, 219, 217, 216, // B-2 to +7
|
||||
214, 212, 211, 209, 208, 206, 205, 203, // C-3 to +7
|
||||
202, 200, 199, 198, 196, 195, 193, 192, // C#3 to +7
|
||||
190, 189, 188, 187, 185, 184, 183, 181, // D-3 to +7
|
||||
180, 179, 177, 176, 175, 174, 172, 171, // D#3 to +7
|
||||
170, 169, 167, 166, 165, 164, 163, 161, // E-3 to +7
|
||||
160, 159, 158, 157, 156, 155, 154, 152, // F-3 to +7
|
||||
151, 150, 149, 148, 147, 146, 145, 144, // F#3 to +7
|
||||
143, 142, 141, 140, 139, 138, 137, 136, // G-3 to +7
|
||||
135, 134, 133, 132, 131, 130, 129, 128, // G#3 to +7
|
||||
127, 126, 125, 125, 123, 123, 122, 121, // A-3 to +7
|
||||
120, 119, 118, 118, 117, 116, 115, 114, // A#3 to +7
|
||||
113, 113, 112, 111, 110, 109, 109, 108 // B-3 to +7
|
||||
};
|
||||
#define ReadAmigaPeriods(a) (uint16_t)pgm_read_word(amigaPeriods + (a))
|
||||
|
||||
static const uint8_t sine[64] PROGMEM = {
|
||||
0, 24, 49, 74, 97, 120, 141, 161,
|
||||
180, 197, 212, 224, 235, 244, 250, 253,
|
||||
255, 253, 250, 244, 235, 224, 212, 197,
|
||||
180, 161, 141, 120, 97, 74, 49, 24
|
||||
};
|
||||
#define ReadSine(a) pgm_read_byte(sine + (a))
|
||||
|
||||
|
||||
static inline uint16_t MakeWord(uint8_t h, uint8_t l) { return h << 8 | l; }
|
||||
|
||||
bool AudioGeneratorMOD::LoadHeader()
|
||||
{
|
||||
uint8_t i;
|
||||
uint8_t temp[4];
|
||||
uint8_t junk[22];
|
||||
|
||||
if (20 != file->read(/*Mod.name*/junk, 20)) return false; // Skip MOD name
|
||||
for (i = 0; i < SAMPLES; i++) {
|
||||
if (22 != file->read(junk /*Mod.samples[i].name*/, 22)) return false; // Skip sample name
|
||||
if (2 != file->read(temp, 2)) return false;
|
||||
Mod.samples[i].length = MakeWord(temp[0], temp[1]) * 2;
|
||||
if (1 != file->read(reinterpret_cast<uint8_t*>(&Mod.samples[i].fineTune), 1)) return false;
|
||||
if (Mod.samples[i].fineTune > 7) Mod.samples[i].fineTune -= 16;
|
||||
if (1 != file->read(&Mod.samples[i].volume, 1)) return false;
|
||||
if (2 != file->read(temp, 2)) return false;
|
||||
Mod.samples[i].loopBegin = MakeWord(temp[0], temp[1]) * 2;
|
||||
if (2 != file->read(temp, 2)) return false;
|
||||
Mod.samples[i].loopLength = MakeWord(temp[0], temp[1]) * 2;
|
||||
if (Mod.samples[i].loopBegin + Mod.samples[i].loopLength > Mod.samples[i].length)
|
||||
Mod.samples[i].loopLength = Mod.samples[i].length - Mod.samples[i].loopBegin;
|
||||
}
|
||||
|
||||
if (1 != file->read(&Mod.songLength, 1)) return false;
|
||||
if (1 != file->read(temp, 1)) return false; // Discard this byte
|
||||
|
||||
Mod.numberOfPatterns = 0;
|
||||
for (i = 0; i < 128; i++) {
|
||||
if (1 != file->read(&Mod.order[i], 1)) return false;
|
||||
if (Mod.order[i] > Mod.numberOfPatterns)
|
||||
Mod.numberOfPatterns = Mod.order[i];
|
||||
}
|
||||
Mod.numberOfPatterns++;
|
||||
|
||||
// Offset 1080
|
||||
if (4 != file->read(temp, 4)) return false;;
|
||||
if (!strncmp(reinterpret_cast<const char*>(temp + 1), "CHN", 3))
|
||||
Mod.numberOfChannels = temp[0] - '0';
|
||||
else if (!strncmp(reinterpret_cast<const char*>(temp + 2), "CH", 2))
|
||||
Mod.numberOfChannels = (temp[0] - '0') * 10 + temp[1] - '0';
|
||||
else
|
||||
Mod.numberOfChannels = 4;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioGeneratorMOD::LoadSamples()
|
||||
{
|
||||
uint8_t i;
|
||||
uint32_t fileOffset = 1084 + Mod.numberOfPatterns * ROWS * Mod.numberOfChannels * 4 - 1;
|
||||
|
||||
for (i = 0; i < SAMPLES; i++) {
|
||||
|
||||
if (Mod.samples[i].length) {
|
||||
Mixer.sampleBegin[i] = fileOffset;
|
||||
Mixer.sampleEnd[i] = fileOffset + Mod.samples[i].length;
|
||||
if (Mod.samples[i].loopLength > 2) {
|
||||
Mixer.sampleloopBegin[i] = fileOffset + Mod.samples[i].loopBegin;
|
||||
Mixer.sampleLoopLength[i] = Mod.samples[i].loopLength;
|
||||
Mixer.sampleLoopEnd[i] = Mixer.sampleloopBegin[i] + Mixer.sampleLoopLength[i];
|
||||
} else {
|
||||
Mixer.sampleloopBegin[i] = 0;
|
||||
Mixer.sampleLoopLength[i] = 0;
|
||||
Mixer.sampleLoopEnd[i] = 0;
|
||||
}
|
||||
fileOffset += Mod.samples[i].length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool AudioGeneratorMOD::LoadPattern(uint8_t pattern)
|
||||
{
|
||||
uint8_t row;
|
||||
uint8_t channel;
|
||||
uint8_t i;
|
||||
uint8_t temp[4];
|
||||
uint16_t amigaPeriod;
|
||||
|
||||
if (!file->seek(1084 + pattern * ROWS * Mod.numberOfChannels * 4, SEEK_SET)) return false;
|
||||
|
||||
for (row = 0; row < ROWS; row++) {
|
||||
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
|
||||
|
||||
if (4 != file->read(temp, 4)) return false;
|
||||
|
||||
Player.currentPattern.sampleNumber[row][channel] = (temp[0] & 0xF0) + (temp[2] >> 4);
|
||||
|
||||
amigaPeriod = ((temp[0] & 0xF) << 8) + temp[1];
|
||||
// Player.currentPattern.note[row][channel] = NONOTE;
|
||||
Player.currentPattern.note8[row][channel] = NONOTE8;
|
||||
for (i = 1; i < 37; i++)
|
||||
if (amigaPeriod > ReadAmigaPeriods(i * 8) - 3 &&
|
||||
amigaPeriod < ReadAmigaPeriods(i * 8) + 3)
|
||||
Player.currentPattern.note8[row][channel] = i;
|
||||
|
||||
Player.currentPattern.effectNumber[row][channel] = temp[2] & 0xF;
|
||||
Player.currentPattern.effectParameter[row][channel] = temp[3];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioGeneratorMOD::Portamento(uint8_t channel)
|
||||
{
|
||||
if (Player.lastAmigaPeriod[channel] < Player.portamentoNote[channel]) {
|
||||
Player.lastAmigaPeriod[channel] += Player.portamentoSpeed[channel];
|
||||
if (Player.lastAmigaPeriod[channel] > Player.portamentoNote[channel])
|
||||
Player.lastAmigaPeriod[channel] = Player.portamentoNote[channel];
|
||||
}
|
||||
if (Player.lastAmigaPeriod[channel] > Player.portamentoNote[channel]) {
|
||||
Player.lastAmigaPeriod[channel] -= Player.portamentoSpeed[channel];
|
||||
if (Player.lastAmigaPeriod[channel] < Player.portamentoNote[channel])
|
||||
Player.lastAmigaPeriod[channel] = Player.portamentoNote[channel];
|
||||
}
|
||||
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
|
||||
}
|
||||
|
||||
void AudioGeneratorMOD::Vibrato(uint8_t channel)
|
||||
{
|
||||
uint16_t delta;
|
||||
uint16_t temp;
|
||||
|
||||
temp = Player.vibratoPos[channel] & 31;
|
||||
|
||||
switch (Player.waveControl[channel] & 3) {
|
||||
case 0:
|
||||
delta = ReadSine(temp);
|
||||
break;
|
||||
case 1:
|
||||
temp <<= 3;
|
||||
if (Player.vibratoPos[channel] < 0)
|
||||
temp = 255 - temp;
|
||||
delta = temp;
|
||||
break;
|
||||
case 2:
|
||||
delta = 255;
|
||||
break;
|
||||
case 3:
|
||||
delta = rand() & 255;
|
||||
break;
|
||||
}
|
||||
|
||||
delta *= Player.vibratoDepth[channel];
|
||||
delta >>= 7;
|
||||
|
||||
if (Player.vibratoPos[channel] >= 0)
|
||||
Mixer.channelFrequency[channel] = Player.amiga / (Player.lastAmigaPeriod[channel] + delta);
|
||||
else
|
||||
Mixer.channelFrequency[channel] = Player.amiga / (Player.lastAmigaPeriod[channel] - delta);
|
||||
|
||||
Player.vibratoPos[channel] += Player.vibratoSpeed[channel];
|
||||
if (Player.vibratoPos[channel] > 31) Player.vibratoPos[channel] -= 64;
|
||||
}
|
||||
|
||||
void AudioGeneratorMOD::Tremolo(uint8_t channel)
|
||||
{
|
||||
uint16_t delta;
|
||||
uint16_t temp;
|
||||
|
||||
temp = Player.tremoloPos[channel] & 31;
|
||||
|
||||
switch (Player.waveControl[channel] & 3) {
|
||||
case 0:
|
||||
delta = ReadSine(temp);
|
||||
break;
|
||||
case 1:
|
||||
temp <<= 3;
|
||||
if (Player.tremoloPos[channel] < 0)
|
||||
temp = 255 - temp;
|
||||
delta = temp;
|
||||
break;
|
||||
case 2:
|
||||
delta = 255;
|
||||
break;
|
||||
case 3:
|
||||
delta = rand() & 255;
|
||||
break;
|
||||
}
|
||||
|
||||
delta *= Player.tremoloDepth[channel];
|
||||
delta >>= 6;
|
||||
|
||||
if (Player.tremoloPos[channel] >= 0) {
|
||||
if (Player.volume[channel] + delta > 64) delta = 64 - Player.volume[channel];
|
||||
Mixer.channelVolume[channel] = Player.volume[channel] + delta;
|
||||
} else {
|
||||
if (Player.volume[channel] - delta < 0) delta = Player.volume[channel];
|
||||
Mixer.channelVolume[channel] = Player.volume[channel] - delta;
|
||||
}
|
||||
|
||||
Player.tremoloPos[channel] += Player.tremoloSpeed[channel];
|
||||
if (Player.tremoloPos[channel] > 31) Player.tremoloPos[channel] -= 64;
|
||||
}
|
||||
|
||||
bool AudioGeneratorMOD::ProcessRow()
|
||||
{
|
||||
bool jumpFlag;
|
||||
bool breakFlag;
|
||||
uint8_t channel;
|
||||
uint8_t sampleNumber;
|
||||
uint16_t note;
|
||||
uint8_t effectNumber;
|
||||
uint8_t effectParameter;
|
||||
uint8_t effectParameterX;
|
||||
uint8_t effectParameterY;
|
||||
uint16_t sampleOffset;
|
||||
|
||||
if (!running) return false;
|
||||
|
||||
Player.lastRow = Player.row++;
|
||||
jumpFlag = false;
|
||||
breakFlag = false;
|
||||
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
|
||||
|
||||
sampleNumber = Player.currentPattern.sampleNumber[Player.lastRow][channel];
|
||||
note = NOTE(Player.lastRow, channel);
|
||||
effectNumber = Player.currentPattern.effectNumber[Player.lastRow][channel];
|
||||
effectParameter = Player.currentPattern.effectParameter[Player.lastRow][channel];
|
||||
effectParameterX = effectParameter >> 4;
|
||||
effectParameterY = effectParameter & 0xF;
|
||||
sampleOffset = 0;
|
||||
|
||||
if (sampleNumber) {
|
||||
Player.lastSampleNumber[channel] = sampleNumber - 1;
|
||||
if (!(effectParameter == 0xE && effectParameterX == NOTEDELAY))
|
||||
Player.volume[channel] = Mod.samples[Player.lastSampleNumber[channel]].volume;
|
||||
}
|
||||
|
||||
if (note != NONOTE) {
|
||||
Player.lastNote[channel] = note;
|
||||
Player.amigaPeriod[channel] = ReadAmigaPeriods(note + Mod.samples[Player.lastSampleNumber[channel]].fineTune);
|
||||
|
||||
if (effectNumber != TONEPORTAMENTO && effectNumber != PORTAMENTOVOLUMESLIDE)
|
||||
Player.lastAmigaPeriod[channel] = Player.amigaPeriod[channel];
|
||||
|
||||
if (!(Player.waveControl[channel] & 0x80)) Player.vibratoPos[channel] = 0;
|
||||
if (!(Player.waveControl[channel] & 0x08)) Player.tremoloPos[channel] = 0;
|
||||
}
|
||||
|
||||
switch (effectNumber) {
|
||||
case TONEPORTAMENTO:
|
||||
if (effectParameter) Player.portamentoSpeed[channel] = effectParameter;
|
||||
Player.portamentoNote[channel] = Player.amigaPeriod[channel];
|
||||
note = NONOTE;
|
||||
break;
|
||||
|
||||
case VIBRATO:
|
||||
if (effectParameterX) Player.vibratoSpeed[channel] = effectParameterX;
|
||||
if (effectParameterY) Player.vibratoDepth[channel] = effectParameterY;
|
||||
break;
|
||||
|
||||
case PORTAMENTOVOLUMESLIDE:
|
||||
Player.portamentoNote[channel] = Player.amigaPeriod[channel];
|
||||
note = NONOTE;
|
||||
break;
|
||||
|
||||
case TREMOLO:
|
||||
if (effectParameterX) Player.tremoloSpeed[channel] = effectParameterX;
|
||||
if (effectParameterY) Player.tremoloDepth[channel] = effectParameterY;
|
||||
break;
|
||||
|
||||
case SETCHANNELPANNING:
|
||||
Mixer.channelPanning[channel] = effectParameter >> 1;
|
||||
break;
|
||||
|
||||
case SETSAMPLEOFFSET:
|
||||
sampleOffset = effectParameter << 8;
|
||||
if (sampleOffset > Mod.samples[Player.lastSampleNumber[channel]].length)
|
||||
sampleOffset = Mod.samples[Player.lastSampleNumber[channel]].length;
|
||||
break;
|
||||
|
||||
case JUMPTOORDER:
|
||||
Player.orderIndex = effectParameter;
|
||||
if (Player.orderIndex >= Mod.songLength)
|
||||
Player.orderIndex = 0;
|
||||
Player.row = 0;
|
||||
jumpFlag = true;
|
||||
break;
|
||||
|
||||
case SETVOLUME:
|
||||
if (effectParameter > 64) Player.volume[channel] = 64;
|
||||
else Player.volume[channel] = effectParameter;
|
||||
break;
|
||||
|
||||
case BREAKPATTERNTOROW:
|
||||
Player.row = effectParameterX * 10 + effectParameterY;
|
||||
if (Player.row >= ROWS)
|
||||
Player.row = 0;
|
||||
if (!jumpFlag && !breakFlag) {
|
||||
Player.orderIndex++;
|
||||
if (Player.orderIndex >= Mod.songLength)
|
||||
Player.orderIndex = 0;
|
||||
}
|
||||
breakFlag = true;
|
||||
break;
|
||||
|
||||
case 0xE:
|
||||
switch (effectParameterX) {
|
||||
case FINEPORTAMENTOUP:
|
||||
Player.lastAmigaPeriod[channel] -= effectParameterY;
|
||||
break;
|
||||
|
||||
case FINEPORTAMENTODOWN:
|
||||
Player.lastAmigaPeriod[channel] += effectParameterY;
|
||||
break;
|
||||
|
||||
case SETVIBRATOWAVEFORM:
|
||||
Player.waveControl[channel] &= 0xF0;
|
||||
Player.waveControl[channel] |= effectParameterY;
|
||||
break;
|
||||
|
||||
case SETFINETUNE:
|
||||
Mod.samples[Player.lastSampleNumber[channel]].fineTune = effectParameterY;
|
||||
if (Mod.samples[Player.lastSampleNumber[channel]].fineTune > 7)
|
||||
Mod.samples[Player.lastSampleNumber[channel]].fineTune -= 16;
|
||||
break;
|
||||
|
||||
case PATTERNLOOP:
|
||||
if (effectParameterY) {
|
||||
if (Player.patternLoopCount[channel])
|
||||
Player.patternLoopCount[channel]--;
|
||||
else
|
||||
Player.patternLoopCount[channel] = effectParameterY;
|
||||
if (Player.patternLoopCount[channel])
|
||||
Player.row = Player.patternLoopRow[channel] - 1;
|
||||
} else
|
||||
Player.patternLoopRow[channel] = Player.row;
|
||||
break;
|
||||
|
||||
case SETTREMOLOWAVEFORM:
|
||||
Player.waveControl[channel] &= 0xF;
|
||||
Player.waveControl[channel] |= effectParameterY << 4;
|
||||
break;
|
||||
|
||||
case FINEVOLUMESLIDEUP:
|
||||
Player.volume[channel] += effectParameterY;
|
||||
if (Player.volume[channel] > 64) Player.volume[channel] = 64;
|
||||
break;
|
||||
|
||||
case FINEVOLUMESLIDEDOWN:
|
||||
Player.volume[channel] -= effectParameterY;
|
||||
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
|
||||
break;
|
||||
|
||||
case NOTECUT:
|
||||
note = NONOTE;
|
||||
break;
|
||||
|
||||
case PATTERNDELAY:
|
||||
Player.patternDelay = effectParameterY;
|
||||
break;
|
||||
|
||||
case INVERTLOOP:
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SETSPEED:
|
||||
if (effectParameter < 0x20)
|
||||
Player.speed = effectParameter;
|
||||
else
|
||||
Player.samplesPerTick = sampleRate / (2 * effectParameter / 5);
|
||||
break;
|
||||
}
|
||||
|
||||
if (note != NONOTE || (Player.lastAmigaPeriod[channel] &&
|
||||
effectNumber != VIBRATO && effectNumber != VIBRATOVOLUMESLIDE &&
|
||||
!(effectNumber == 0xE && effectParameterX == NOTEDELAY)))
|
||||
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
|
||||
|
||||
if (note != NONOTE)
|
||||
Mixer.channelSampleOffset[channel] = sampleOffset << DIVIDER;
|
||||
|
||||
if (sampleNumber)
|
||||
Mixer.channelSampleNumber[channel] = Player.lastSampleNumber[channel];
|
||||
|
||||
if (effectNumber != TREMOLO)
|
||||
Mixer.channelVolume[channel] = Player.volume[channel];
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorMOD::ProcessTick()
|
||||
{
|
||||
uint8_t channel;
|
||||
uint8_t sampleNumber;
|
||||
uint16_t note;
|
||||
uint8_t effectNumber;
|
||||
uint8_t effectParameter;
|
||||
uint8_t effectParameterX;
|
||||
uint8_t effectParameterY;
|
||||
uint16_t tempNote;
|
||||
|
||||
if (!running) return false;
|
||||
|
||||
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
|
||||
|
||||
if (Player.lastAmigaPeriod[channel]) {
|
||||
|
||||
sampleNumber = Player.currentPattern.sampleNumber[Player.lastRow][channel];
|
||||
// note = Player.currentPattern.note[Player.lastRow][channel];
|
||||
note = NOTE(Player.lastRow, channel);
|
||||
effectNumber = Player.currentPattern.effectNumber[Player.lastRow][channel];
|
||||
effectParameter = Player.currentPattern.effectParameter[Player.lastRow][channel];
|
||||
effectParameterX = effectParameter >> 4;
|
||||
effectParameterY = effectParameter & 0xF;
|
||||
|
||||
switch (effectNumber) {
|
||||
case ARPEGGIO:
|
||||
if (effectParameter)
|
||||
switch (Player.tick % 3) {
|
||||
case 0:
|
||||
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
|
||||
break;
|
||||
case 1:
|
||||
tempNote = Player.lastNote[channel] + effectParameterX * 8 + Mod.samples[Player.lastSampleNumber[channel]].fineTune;
|
||||
if (tempNote < 296) Mixer.channelFrequency[channel] = Player.amiga / ReadAmigaPeriods(tempNote);
|
||||
break;
|
||||
case 2:
|
||||
tempNote = Player.lastNote[channel] + effectParameterY * 8 + Mod.samples[Player.lastSampleNumber[channel]].fineTune;
|
||||
if (tempNote < 296) Mixer.channelFrequency[channel] = Player.amiga / ReadAmigaPeriods(tempNote);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case PORTAMENTOUP:
|
||||
Player.lastAmigaPeriod[channel] -= effectParameter;
|
||||
if (Player.lastAmigaPeriod[channel] < 113) Player.lastAmigaPeriod[channel] = 113;
|
||||
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
|
||||
break;
|
||||
|
||||
case PORTAMENTODOWN:
|
||||
Player.lastAmigaPeriod[channel] += effectParameter;
|
||||
if (Player.lastAmigaPeriod[channel] > 856) Player.lastAmigaPeriod[channel] = 856;
|
||||
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
|
||||
break;
|
||||
|
||||
case TONEPORTAMENTO:
|
||||
Portamento(channel);
|
||||
break;
|
||||
|
||||
case VIBRATO:
|
||||
Vibrato(channel);
|
||||
break;
|
||||
|
||||
case PORTAMENTOVOLUMESLIDE:
|
||||
Portamento(channel);
|
||||
Player.volume[channel] += effectParameterX - effectParameterY;
|
||||
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
|
||||
else if (Player.volume[channel] > 64) Player.volume[channel] = 64;
|
||||
Mixer.channelVolume[channel] = Player.volume[channel];
|
||||
break;
|
||||
|
||||
case VIBRATOVOLUMESLIDE:
|
||||
Vibrato(channel);
|
||||
Player.volume[channel] += effectParameterX - effectParameterY;
|
||||
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
|
||||
else if (Player.volume[channel] > 64) Player.volume[channel] = 64;
|
||||
Mixer.channelVolume[channel] = Player.volume[channel];
|
||||
break;
|
||||
|
||||
case TREMOLO:
|
||||
Tremolo(channel);
|
||||
break;
|
||||
|
||||
case VOLUMESLIDE:
|
||||
Player.volume[channel] += effectParameterX - effectParameterY;
|
||||
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
|
||||
else if (Player.volume[channel] > 64) Player.volume[channel] = 64;
|
||||
Mixer.channelVolume[channel] = Player.volume[channel];
|
||||
break;
|
||||
|
||||
case 0xE:
|
||||
switch (effectParameterX) {
|
||||
case RETRIGGERNOTE:
|
||||
if (!effectParameterY) break;
|
||||
if (!(Player.tick % effectParameterY)) {
|
||||
Mixer.channelSampleOffset[channel] = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case NOTECUT:
|
||||
if (Player.tick == effectParameterY)
|
||||
Mixer.channelVolume[channel] = Player.volume[channel] = 0;
|
||||
break;
|
||||
|
||||
case NOTEDELAY:
|
||||
if (Player.tick == effectParameterY) {
|
||||
if (sampleNumber) Player.volume[channel] = Mod.samples[Player.lastSampleNumber[channel]].volume;
|
||||
if (note != NONOTE) Mixer.channelSampleOffset[channel] = 0;
|
||||
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
|
||||
Mixer.channelVolume[channel] = Player.volume[channel];
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorMOD::RunPlayer()
|
||||
{
|
||||
if (!running) return false;
|
||||
|
||||
if (Player.tick == Player.speed) {
|
||||
Player.tick = 0;
|
||||
|
||||
if (Player.row == ROWS) {
|
||||
Player.orderIndex++;
|
||||
if (Player.orderIndex == Mod.songLength)
|
||||
{
|
||||
//Player.orderIndex = 0;
|
||||
// No loop, just say we're done!
|
||||
return false;
|
||||
}
|
||||
Player.row = 0;
|
||||
}
|
||||
|
||||
if (Player.patternDelay) {
|
||||
Player.patternDelay--;
|
||||
} else {
|
||||
if (Player.orderIndex != Player.oldOrderIndex)
|
||||
if (!LoadPattern(Mod.order[Player.orderIndex])) return false;
|
||||
Player.oldOrderIndex = Player.orderIndex;
|
||||
if (!ProcessRow()) return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!ProcessTick()) return false;
|
||||
}
|
||||
Player.tick++;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioGeneratorMOD::GetSample(int16_t sample[2])
|
||||
{
|
||||
int16_t sumL;
|
||||
int16_t sumR;
|
||||
uint8_t channel;
|
||||
uint32_t samplePointer;
|
||||
int8_t current;
|
||||
int8_t next;
|
||||
int16_t out;
|
||||
|
||||
if (!running) return;
|
||||
|
||||
sumL = 0;
|
||||
sumR = 0;
|
||||
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
|
||||
|
||||
if (!Mixer.channelFrequency[channel] ||
|
||||
!Mod.samples[Mixer.channelSampleNumber[channel]].length) continue;
|
||||
|
||||
Mixer.channelSampleOffset[channel] += Mixer.channelFrequency[channel];
|
||||
|
||||
if (!Mixer.channelVolume[channel]) continue;
|
||||
|
||||
samplePointer = Mixer.sampleBegin[Mixer.channelSampleNumber[channel]] +
|
||||
(Mixer.channelSampleOffset[channel] >> DIVIDER);
|
||||
|
||||
if (Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]]) {
|
||||
|
||||
if (samplePointer >= Mixer.sampleLoopEnd[Mixer.channelSampleNumber[channel]]) {
|
||||
Mixer.channelSampleOffset[channel] -= Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]] << DIVIDER;
|
||||
samplePointer -= Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]];
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (samplePointer >= Mixer.sampleEnd[Mixer.channelSampleNumber[channel]]) {
|
||||
Mixer.channelFrequency[channel] = 0;
|
||||
samplePointer = Mixer.sampleEnd[Mixer.channelSampleNumber[channel]];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (samplePointer < FatBuffer.samplePointer[channel] ||
|
||||
samplePointer >= FatBuffer.samplePointer[channel] + fatBufferSize - 1 ||
|
||||
Mixer.channelSampleNumber[channel] != FatBuffer.channelSampleNumber[channel]) {
|
||||
|
||||
uint16_t toRead = Mixer.sampleEnd[Mixer.channelSampleNumber[channel]] - samplePointer + 1;
|
||||
if (toRead > fatBufferSize) toRead = fatBufferSize;
|
||||
|
||||
if (!file->seek(samplePointer, SEEK_SET)) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
if (toRead != file->read(FatBuffer.channels[channel], toRead)) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
FatBuffer.samplePointer[channel] = samplePointer;
|
||||
FatBuffer.channelSampleNumber[channel] = Mixer.channelSampleNumber[channel];
|
||||
}
|
||||
|
||||
current = FatBuffer.channels[channel][(samplePointer - FatBuffer.samplePointer[channel]) /*& (FATBUFFERSIZE - 1)*/];
|
||||
next = FatBuffer.channels[channel][(samplePointer + 1 - FatBuffer.samplePointer[channel]) /*& (FATBUFFERSIZE - 1)*/];
|
||||
|
||||
out = current;
|
||||
|
||||
// Integer linear interpolation
|
||||
out += (next - current) * (Mixer.channelSampleOffset[channel] & ((1 << DIVIDER) - 1)) >> DIVIDER;
|
||||
|
||||
// Upscale to BITDEPTH
|
||||
out <<= BITDEPTH - 8;
|
||||
|
||||
// Channel volume
|
||||
out = out * Mixer.channelVolume[channel] >> 6;
|
||||
|
||||
// Channel panning
|
||||
sumL += out * min(128 - Mixer.channelPanning[channel], 64) >> 6;
|
||||
sumR += out * min(Mixer.channelPanning[channel], 64) >> 6;
|
||||
}
|
||||
|
||||
// Downscale to BITDEPTH
|
||||
sumL /= Mod.numberOfChannels;
|
||||
sumR /= Mod.numberOfChannels;
|
||||
|
||||
// Fill the sound buffer with unsigned values
|
||||
sample[AudioOutput::LEFTCHANNEL] = sumL + (1 << (BITDEPTH - 1));
|
||||
sample[AudioOutput::RIGHTCHANNEL] = sumR + (1 << (BITDEPTH - 1));
|
||||
}
|
||||
|
||||
bool AudioGeneratorMOD::LoadMOD()
|
||||
{
|
||||
uint8_t channel;
|
||||
|
||||
if (!LoadHeader()) return false;
|
||||
LoadSamples();
|
||||
|
||||
Player.amiga = AMIGA;
|
||||
Player.samplesPerTick = sampleRate / (2 * 125 / 5); // Hz = 2 * BPM / 5
|
||||
Player.speed = 6;
|
||||
Player.tick = Player.speed;
|
||||
Player.row = 0;
|
||||
|
||||
Player.orderIndex = 0;
|
||||
Player.oldOrderIndex = 0xFF;
|
||||
Player.patternDelay = 0;
|
||||
|
||||
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
|
||||
Player.patternLoopCount[channel] = 0;
|
||||
Player.patternLoopRow[channel] = 0;
|
||||
|
||||
Player.lastAmigaPeriod[channel] = 0;
|
||||
|
||||
Player.waveControl[channel] = 0;
|
||||
|
||||
Player.vibratoSpeed[channel] = 0;
|
||||
Player.vibratoDepth[channel] = 0;
|
||||
Player.vibratoPos[channel] = 0;
|
||||
|
||||
Player.tremoloSpeed[channel] = 0;
|
||||
Player.tremoloDepth[channel] = 0;
|
||||
Player.tremoloPos[channel] = 0;
|
||||
|
||||
FatBuffer.samplePointer[channel] = 0;
|
||||
FatBuffer.channelSampleNumber[channel] = 0xFF;
|
||||
|
||||
Mixer.channelSampleOffset[channel] = 0;
|
||||
Mixer.channelFrequency[channel] = 0;
|
||||
Mixer.channelVolume[channel] = 0;
|
||||
switch (channel % 4) {
|
||||
case 0:
|
||||
case 3:
|
||||
Mixer.channelPanning[channel] = stereoSeparation;
|
||||
break;
|
||||
default:
|
||||
Mixer.channelPanning[channel] = 128 - stereoSeparation;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
168
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioGeneratorMOD.h
Normal file
168
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioGeneratorMOD.h
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
AudioGeneratorMOD
|
||||
Audio output generator that plays Amiga MOD tracker files
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOGENERATORMOD_H
|
||||
#define _AUDIOGENERATORMOD_H
|
||||
|
||||
#include "AudioGenerator.h"
|
||||
|
||||
class AudioGeneratorMOD : public AudioGenerator
|
||||
{
|
||||
public:
|
||||
AudioGeneratorMOD();
|
||||
virtual ~AudioGeneratorMOD() override;
|
||||
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
|
||||
virtual bool loop() override;
|
||||
virtual bool stop() override;
|
||||
virtual bool isRunning() override { return running; }
|
||||
bool SetSampleRate(int hz) { if (running || (hz < 1) || (hz > 96000) ) return false; sampleRate = hz; return true; }
|
||||
bool SetBufferSize(int sz) { if (running || (sz < 1) ) return false; fatBufferSize = sz; return true; }
|
||||
bool SetStereoSeparation(int sep) { if (running || (sep<0) || (sep>64)) return false; stereoSeparation = sep; return true; }
|
||||
bool SetPAL(bool use) { if (running) return false; usePAL = use; return true; }
|
||||
|
||||
protected:
|
||||
bool LoadMOD();
|
||||
bool LoadHeader();
|
||||
void GetSample(int16_t sample[2]);
|
||||
bool RunPlayer();
|
||||
void LoadSamples();
|
||||
bool LoadPattern(uint8_t pattern);
|
||||
bool ProcessTick();
|
||||
bool ProcessRow();
|
||||
void Tremolo(uint8_t channel);
|
||||
void Portamento(uint8_t channel);
|
||||
void Vibrato(uint8_t channel);
|
||||
|
||||
|
||||
protected:
|
||||
int mixerTick;
|
||||
enum {BITDEPTH = 15};
|
||||
int sampleRate;
|
||||
int fatBufferSize; //(6*1024) // File system buffers per-CHANNEL (i.e. total mem required is 4 * FATBUFFERSIZE)
|
||||
enum {DIVIDER = 10}; // Fixed-point mantissa used for integer arithmetic
|
||||
int stereoSeparation; //STEREOSEPARATION = 32; // 0 (max) to 64 (mono)
|
||||
bool usePAL;
|
||||
|
||||
// Hz = 7093789 / (amigaPeriod * 2) for PAL
|
||||
// Hz = 7159091 / (amigaPeriod * 2) for NTSC
|
||||
int AMIGA;
|
||||
void UpdateAmiga() { AMIGA = ((usePAL?7159091:7093789) / 2 / sampleRate << DIVIDER); }
|
||||
|
||||
enum {ROWS = 64, SAMPLES = 31, CHANNELS = 4, NONOTE = 0xFFFF, NONOTE8 = 0xff };
|
||||
|
||||
typedef struct Sample {
|
||||
uint16_t length;
|
||||
int8_t fineTune;
|
||||
uint8_t volume;
|
||||
uint16_t loopBegin;
|
||||
uint16_t loopLength;
|
||||
} Sample;
|
||||
|
||||
typedef struct mod {
|
||||
Sample samples[SAMPLES];
|
||||
uint8_t songLength;
|
||||
uint8_t numberOfPatterns;
|
||||
uint8_t order[128];
|
||||
uint8_t numberOfChannels;
|
||||
} mod;
|
||||
|
||||
// Save 256 bytes by storing raw note values, unpack with macro NOTE
|
||||
typedef struct Pattern {
|
||||
uint8_t sampleNumber[ROWS][CHANNELS];
|
||||
uint8_t note8[ROWS][CHANNELS];
|
||||
uint8_t effectNumber[ROWS][CHANNELS];
|
||||
uint8_t effectParameter[ROWS][CHANNELS];
|
||||
} Pattern;
|
||||
|
||||
typedef struct player {
|
||||
Pattern currentPattern;
|
||||
|
||||
uint32_t amiga;
|
||||
uint16_t samplesPerTick;
|
||||
uint8_t speed;
|
||||
uint8_t tick;
|
||||
uint8_t row;
|
||||
uint8_t lastRow;
|
||||
|
||||
uint8_t orderIndex;
|
||||
uint8_t oldOrderIndex;
|
||||
uint8_t patternDelay;
|
||||
uint8_t patternLoopCount[CHANNELS];
|
||||
uint8_t patternLoopRow[CHANNELS];
|
||||
|
||||
uint8_t lastSampleNumber[CHANNELS];
|
||||
int8_t volume[CHANNELS];
|
||||
uint16_t lastNote[CHANNELS];
|
||||
uint16_t amigaPeriod[CHANNELS];
|
||||
int16_t lastAmigaPeriod[CHANNELS];
|
||||
|
||||
uint16_t portamentoNote[CHANNELS];
|
||||
uint8_t portamentoSpeed[CHANNELS];
|
||||
|
||||
uint8_t waveControl[CHANNELS];
|
||||
|
||||
uint8_t vibratoSpeed[CHANNELS];
|
||||
uint8_t vibratoDepth[CHANNELS];
|
||||
int8_t vibratoPos[CHANNELS];
|
||||
|
||||
uint8_t tremoloSpeed[CHANNELS];
|
||||
uint8_t tremoloDepth[CHANNELS];
|
||||
int8_t tremoloPos[CHANNELS];
|
||||
} player;
|
||||
|
||||
typedef struct mixer {
|
||||
uint32_t sampleBegin[SAMPLES];
|
||||
uint32_t sampleEnd[SAMPLES];
|
||||
uint32_t sampleloopBegin[SAMPLES];
|
||||
uint16_t sampleLoopLength[SAMPLES];
|
||||
uint32_t sampleLoopEnd[SAMPLES];
|
||||
|
||||
uint8_t channelSampleNumber[CHANNELS];
|
||||
uint32_t channelSampleOffset[CHANNELS];
|
||||
uint16_t channelFrequency[CHANNELS];
|
||||
uint8_t channelVolume[CHANNELS];
|
||||
uint8_t channelPanning[CHANNELS];
|
||||
} mixer;
|
||||
|
||||
typedef struct fatBuffer {
|
||||
uint8_t *channels[CHANNELS]; // Make dynamically allocated [FATBUFFERSIZE];
|
||||
uint32_t samplePointer[CHANNELS];
|
||||
uint8_t channelSampleNumber[CHANNELS];
|
||||
} fatBuffer;
|
||||
|
||||
// Effects
|
||||
typedef enum { ARPEGGIO = 0, PORTAMENTOUP, PORTAMENTODOWN, TONEPORTAMENTO, VIBRATO, PORTAMENTOVOLUMESLIDE,
|
||||
VIBRATOVOLUMESLIDE, TREMOLO, SETCHANNELPANNING, SETSAMPLEOFFSET, VOLUMESLIDE, JUMPTOORDER,
|
||||
SETVOLUME, BREAKPATTERNTOROW, ESUBSET, SETSPEED } EffectsValues;
|
||||
|
||||
// 0xE subset
|
||||
typedef enum { SETFILTER = 0, FINEPORTAMENTOUP, FINEPORTAMENTODOWN, GLISSANDOCONTROL, SETVIBRATOWAVEFORM,
|
||||
SETFINETUNE, PATTERNLOOP, SETTREMOLOWAVEFORM, SUBEFFECT8, RETRIGGERNOTE, FINEVOLUMESLIDEUP,
|
||||
FINEVOLUMESLIDEDOWN, NOTECUT, NOTEDELAY, PATTERNDELAY, INVERTLOOP } Effect08Subvalues;
|
||||
|
||||
// Our state lives here...
|
||||
player Player;
|
||||
mod Mod;
|
||||
mixer Mixer;
|
||||
fatBuffer FatBuffer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
AudioGeneratorMP3
|
||||
Wrap libmad MP3 library to play audio
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 "AudioGeneratorMP3.h"
|
||||
|
||||
AudioGeneratorMP3::AudioGeneratorMP3()
|
||||
{
|
||||
running = false;
|
||||
file = NULL;
|
||||
output = NULL;
|
||||
buff = NULL;
|
||||
nsCountMax = 1152/32;
|
||||
madInitted = false;
|
||||
}
|
||||
|
||||
AudioGeneratorMP3::AudioGeneratorMP3(void *space, int size): preallocateSpace(space), preallocateSize(size)
|
||||
{
|
||||
running = false;
|
||||
file = NULL;
|
||||
output = NULL;
|
||||
buff = NULL;
|
||||
nsCountMax = 1152/32;
|
||||
madInitted = false;
|
||||
}
|
||||
|
||||
AudioGeneratorMP3::AudioGeneratorMP3(void *buff, int buffSize, void *stream, int streamSize, void *frame, int frameSize, void *synth, int synthSize):
|
||||
preallocateSpace(buff), preallocateSize(buffSize),
|
||||
preallocateStreamSpace(stream), preallocateStreamSize(streamSize),
|
||||
preallocateFrameSpace(frame), preallocateFrameSize(frameSize),
|
||||
preallocateSynthSpace(synth), preallocateSynthSize(synthSize)
|
||||
{
|
||||
running = false;
|
||||
file = NULL;
|
||||
output = NULL;
|
||||
buff = NULL;
|
||||
nsCountMax = 1152/32;
|
||||
madInitted = false;
|
||||
}
|
||||
|
||||
AudioGeneratorMP3::~AudioGeneratorMP3()
|
||||
{
|
||||
if (!preallocateSpace) {
|
||||
free(buff);
|
||||
free(synth);
|
||||
free(frame);
|
||||
free(stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool AudioGeneratorMP3::stop()
|
||||
{
|
||||
if (madInitted) {
|
||||
mad_synth_finish(synth);
|
||||
mad_frame_finish(frame);
|
||||
mad_stream_finish(stream);
|
||||
madInitted = false;
|
||||
}
|
||||
|
||||
if (!preallocateSpace) {
|
||||
free(buff);
|
||||
free(synth);
|
||||
free(frame);
|
||||
free(stream);
|
||||
}
|
||||
|
||||
buff = NULL;
|
||||
synth = NULL;
|
||||
frame = NULL;
|
||||
stream = NULL;
|
||||
|
||||
running = false;
|
||||
output->stop();
|
||||
return file->close();
|
||||
}
|
||||
|
||||
bool AudioGeneratorMP3::isRunning()
|
||||
{
|
||||
return running;
|
||||
}
|
||||
|
||||
enum mad_flow AudioGeneratorMP3::ErrorToFlow()
|
||||
{
|
||||
char err[64];
|
||||
char errLine[128];
|
||||
|
||||
// Special case - eat "lost sync @ byte 0" as it always occurs and is not really correct....it never had sync!
|
||||
if ((lastReadPos==0) && (stream->error==MAD_ERROR_LOSTSYNC)) return MAD_FLOW_CONTINUE;
|
||||
|
||||
strcpy_P(err, mad_stream_errorstr(stream));
|
||||
snprintf_P(errLine, sizeof(errLine), PSTR("Decoding error '%s' at byte offset %d"),
|
||||
err, (stream->this_frame - buff) + lastReadPos);
|
||||
yield(); // Something bad happened anyway, ensure WiFi gets some time, too
|
||||
cb.st(stream->error, errLine);
|
||||
return MAD_FLOW_CONTINUE;
|
||||
}
|
||||
|
||||
enum mad_flow AudioGeneratorMP3::Input()
|
||||
{
|
||||
int unused = 0;
|
||||
|
||||
if (stream->next_frame) {
|
||||
unused = lastBuffLen - (stream->next_frame - buff);
|
||||
if (unused < 0) {
|
||||
desync();
|
||||
unused = 0;
|
||||
} else {
|
||||
memmove(buff, stream->next_frame, unused);
|
||||
}
|
||||
stream->next_frame = NULL;
|
||||
}
|
||||
|
||||
if (unused == lastBuffLen) {
|
||||
// Something wicked this way came, throw it all out and try again
|
||||
unused = 0;
|
||||
}
|
||||
|
||||
lastReadPos = file->getPos() - unused;
|
||||
int len = buffLen - unused;
|
||||
len = file->read(buff + unused, len);
|
||||
if ((len == 0) && (unused == 0)) {
|
||||
// Can't read any from the file, and we don't have anything left. It's done....
|
||||
return MAD_FLOW_STOP;
|
||||
}
|
||||
if (len < 0) {
|
||||
desync();
|
||||
unused = 0;
|
||||
}
|
||||
|
||||
lastBuffLen = len + unused;
|
||||
mad_stream_buffer(stream, buff, lastBuffLen);
|
||||
|
||||
return MAD_FLOW_CONTINUE;
|
||||
}
|
||||
|
||||
void AudioGeneratorMP3::desync ()
|
||||
{
|
||||
audioLogger->printf_P(PSTR("MP3:desync\n"));
|
||||
if (stream) {
|
||||
stream->next_frame = nullptr;
|
||||
stream->this_frame = nullptr;
|
||||
stream->sync = 0;
|
||||
}
|
||||
lastBuffLen = 0;
|
||||
}
|
||||
|
||||
bool AudioGeneratorMP3::DecodeNextFrame()
|
||||
{
|
||||
if (mad_frame_decode(frame, stream) == -1) {
|
||||
ErrorToFlow(); // Always returns CONTINUE
|
||||
return false;
|
||||
}
|
||||
nsCountMax = MAD_NSBSAMPLES(&frame->header);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorMP3::GetOneSample(int16_t sample[2])
|
||||
{
|
||||
if (synth->pcm.samplerate != lastRate) {
|
||||
output->SetRate(synth->pcm.samplerate);
|
||||
lastRate = synth->pcm.samplerate;
|
||||
}
|
||||
if (synth->pcm.channels != lastChannels) {
|
||||
output->SetChannels(synth->pcm.channels);
|
||||
lastChannels = synth->pcm.channels;
|
||||
}
|
||||
|
||||
// If we're here, we have one decoded frame and sent 0 or more samples out
|
||||
if (samplePtr < synth->pcm.length) {
|
||||
sample[AudioOutput::LEFTCHANNEL ] = synth->pcm.samples[0][samplePtr];
|
||||
sample[AudioOutput::RIGHTCHANNEL] = synth->pcm.samples[1][samplePtr];
|
||||
samplePtr++;
|
||||
} else {
|
||||
samplePtr = 0;
|
||||
|
||||
switch ( mad_synth_frame_onens(synth, frame, nsCount++) ) {
|
||||
case MAD_FLOW_STOP:
|
||||
case MAD_FLOW_BREAK: audioLogger->printf_P(PSTR("msf1ns failed\n"));
|
||||
return false; // Either way we're done
|
||||
default:
|
||||
break; // Do nothing
|
||||
}
|
||||
// for IGNORE and CONTINUE, just play what we have now
|
||||
sample[AudioOutput::LEFTCHANNEL ] = synth->pcm.samples[0][samplePtr];
|
||||
sample[AudioOutput::RIGHTCHANNEL] = synth->pcm.samples[1][samplePtr];
|
||||
samplePtr++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool AudioGeneratorMP3::loop()
|
||||
{
|
||||
if (!running) goto done; // Nothing to do here!
|
||||
|
||||
// First, try and push in the stored sample. If we can't, then punt and try later
|
||||
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
|
||||
|
||||
// Try and stuff the buffer one sample at a time
|
||||
do
|
||||
{
|
||||
// Decode next frame if we're beyond the existing generated data
|
||||
if ( (samplePtr >= synth->pcm.length) && (nsCount >= nsCountMax) ) {
|
||||
retry:
|
||||
if (Input() == MAD_FLOW_STOP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DecodeNextFrame()) {
|
||||
if (stream->error == MAD_ERROR_BUFLEN) {
|
||||
// randomly seeking can lead to endless
|
||||
// and unrecoverable "MAD_ERROR_BUFLEN" loop
|
||||
audioLogger->printf_P(PSTR("MP3:ERROR_BUFLEN %d\n"), unrecoverable);
|
||||
if (++unrecoverable >= 3) {
|
||||
unrecoverable = 0;
|
||||
stop();
|
||||
return running;
|
||||
}
|
||||
} else {
|
||||
unrecoverable = 0;
|
||||
}
|
||||
goto retry;
|
||||
}
|
||||
samplePtr = 9999;
|
||||
nsCount = 0;
|
||||
}
|
||||
|
||||
if (!GetOneSample(lastSample)) {
|
||||
audioLogger->printf_P(PSTR("G1S failed\n"));
|
||||
running = false;
|
||||
goto done;
|
||||
}
|
||||
} while (running && output->ConsumeSample(lastSample));
|
||||
|
||||
done:
|
||||
file->loop();
|
||||
output->loop();
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool AudioGeneratorMP3::begin(AudioFileSource *source, AudioOutput *output)
|
||||
{
|
||||
if (!source) return false;
|
||||
file = source;
|
||||
if (!output) return false;
|
||||
this->output = output;
|
||||
if (!file->isOpen()) {
|
||||
audioLogger->printf_P(PSTR("MP3 source file not open\n"));
|
||||
return false; // Error
|
||||
}
|
||||
|
||||
// Reset error count from previous file
|
||||
unrecoverable = 0;
|
||||
|
||||
output->SetBitsPerSample(16); // Constant for MP3 decoder
|
||||
output->SetChannels(2);
|
||||
|
||||
if (!output->begin()) return false;
|
||||
|
||||
// Where we are in generating one frame's data, set to invalid so we will run loop on first getsample()
|
||||
samplePtr = 9999;
|
||||
nsCount = 9999;
|
||||
lastRate = 0;
|
||||
lastChannels = 0;
|
||||
lastReadPos = 0;
|
||||
lastBuffLen = 0;
|
||||
|
||||
// Allocate all large memory chunks
|
||||
if (preallocateStreamSize + preallocateFrameSize + preallocateSynthSize) {
|
||||
if (preallocateSize >= preAllocBuffSize() &&
|
||||
preallocateStreamSize >= preAllocStreamSize() &&
|
||||
preallocateFrameSize >= preAllocFrameSize() &&
|
||||
preallocateSynthSize >= preAllocSynthSize()) {
|
||||
buff = reinterpret_cast<unsigned char *>(preallocateSpace);
|
||||
stream = reinterpret_cast<struct mad_stream *>(preallocateStreamSpace);
|
||||
frame = reinterpret_cast<struct mad_frame *>(preallocateFrameSpace);
|
||||
synth = reinterpret_cast<struct mad_synth *>(preallocateSynthSpace);
|
||||
}
|
||||
else {
|
||||
audioLogger->printf_P("OOM error in MP3: Want %d/%d/%d/%d bytes, have %d/%d/%d/%d bytes preallocated.\n",
|
||||
preAllocBuffSize(), preAllocStreamSize(), preAllocFrameSize(), preAllocSynthSize(),
|
||||
preallocateSize, preallocateStreamSize, preallocateFrameSize, preallocateSynthSize);
|
||||
return false;
|
||||
}
|
||||
} else if (preallocateSpace) {
|
||||
uint8_t *p = reinterpret_cast<uint8_t *>(preallocateSpace);
|
||||
buff = reinterpret_cast<unsigned char *>(p);
|
||||
p += preAllocBuffSize();
|
||||
stream = reinterpret_cast<struct mad_stream *>(p);
|
||||
p += preAllocStreamSize();
|
||||
frame = reinterpret_cast<struct mad_frame *>(p);
|
||||
p += preAllocFrameSize();
|
||||
synth = reinterpret_cast<struct mad_synth *>(p);
|
||||
p += preAllocSynthSize();
|
||||
int neededBytes = p - reinterpret_cast<uint8_t *>(preallocateSpace);
|
||||
if (neededBytes > preallocateSize) {
|
||||
audioLogger->printf_P("OOM error in MP3: Want %d bytes, have %d bytes preallocated.\n", neededBytes, preallocateSize);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
buff = reinterpret_cast<unsigned char *>(malloc(buffLen));
|
||||
stream = reinterpret_cast<struct mad_stream *>(malloc(sizeof(struct mad_stream)));
|
||||
frame = reinterpret_cast<struct mad_frame *>(malloc(sizeof(struct mad_frame)));
|
||||
synth = reinterpret_cast<struct mad_synth *>(malloc(sizeof(struct mad_synth)));
|
||||
if (!buff || !stream || !frame || !synth) {
|
||||
free(buff);
|
||||
free(stream);
|
||||
free(frame);
|
||||
free(synth);
|
||||
buff = NULL;
|
||||
stream = NULL;
|
||||
frame = NULL;
|
||||
synth = NULL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mad_stream_init(stream);
|
||||
mad_frame_init(frame);
|
||||
mad_synth_init(synth);
|
||||
synth->pcm.length = 0;
|
||||
mad_stream_options(stream, 0); // TODO - add options support
|
||||
madInitted = true;
|
||||
|
||||
running = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The following are helper routines for use in libmad to check stack/heap free
|
||||
// and to determine if there's enough stack space to allocate some blocks there
|
||||
// instead of precious heap.
|
||||
|
||||
#undef stack
|
||||
extern "C" {
|
||||
#ifdef ESP32
|
||||
//TODO - add ESP32 checks
|
||||
void stack(const char *s, const char *t, int i)
|
||||
{
|
||||
}
|
||||
int stackfree()
|
||||
{
|
||||
return 8192;
|
||||
}
|
||||
#elif defined(ESP8266) && !defined(CORE_MOCK)
|
||||
#include <cont.h>
|
||||
extern cont_t g_cont;
|
||||
|
||||
void stack(const char *s, const char *t, int i)
|
||||
{
|
||||
(void) t;
|
||||
(void) i;
|
||||
register uint32_t *sp asm("a1");
|
||||
int freestack = 4 * (sp - g_cont.stack);
|
||||
int freeheap = ESP.getFreeHeap();
|
||||
if ((freestack < 512) || (freeheap < 5120)) {
|
||||
static int laststack, lastheap;
|
||||
if (laststack!=freestack|| lastheap !=freeheap) {
|
||||
audioLogger->printf_P(PSTR("%s: FREESTACK=%d, FREEHEAP=%d\n"), s, /*t, i,*/ freestack, /*cont_get_free_stack(&g_cont),*/ freeheap);
|
||||
}
|
||||
if (freestack < 256) {
|
||||
audioLogger->printf_P(PSTR("out of stack!\n"));
|
||||
}
|
||||
if (freeheap < 1024) {
|
||||
audioLogger->printf_P(PSTR("out of heap!\n"));
|
||||
}
|
||||
Serial.flush();
|
||||
laststack = freestack;
|
||||
lastheap = freeheap;
|
||||
}
|
||||
}
|
||||
|
||||
int stackfree()
|
||||
{
|
||||
register uint32_t *sp asm("a1");
|
||||
int freestack = 4 * (sp - g_cont.stack);
|
||||
return freestack;
|
||||
}
|
||||
#else
|
||||
void stack(const char *s, const char *t, int i)
|
||||
{
|
||||
(void) s;
|
||||
(void) t;
|
||||
(void) i;
|
||||
}
|
||||
int stackfree()
|
||||
{
|
||||
return 8192;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
AudioGeneratorMP3
|
||||
Wrap libmad MP3 library to play audio
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOGENERATORMP3_H
|
||||
#define _AUDIOGENERATORMP3_H
|
||||
|
||||
#include "AudioGenerator.h"
|
||||
#include "libmad/config.h"
|
||||
#include "libmad/mad.h"
|
||||
|
||||
class AudioGeneratorMP3 : public AudioGenerator
|
||||
{
|
||||
public:
|
||||
AudioGeneratorMP3();
|
||||
AudioGeneratorMP3(void *preallocateSpace, int preallocateSize);
|
||||
AudioGeneratorMP3(void *buff, int buffSize, void *stream, int streamSize, void *frame, int frameSize, void *synth, int synthSize);
|
||||
virtual ~AudioGeneratorMP3() override;
|
||||
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
|
||||
virtual bool loop() override;
|
||||
virtual bool stop() override;
|
||||
virtual bool isRunning() override;
|
||||
virtual void desync () override;
|
||||
|
||||
static constexpr int preAllocSize () { return preAllocBuffSize() + preAllocStreamSize() + preAllocFrameSize() + preAllocSynthSize(); }
|
||||
static constexpr int preAllocBuffSize () { return ((buffLen + 7) & ~7); }
|
||||
static constexpr int preAllocStreamSize () { return ((sizeof(struct mad_stream) + 7) & ~7); }
|
||||
static constexpr int preAllocFrameSize () { return (sizeof(struct mad_frame) + 7) & ~7; }
|
||||
static constexpr int preAllocSynthSize () { return (sizeof(struct mad_synth) + 7) & ~7; }
|
||||
|
||||
protected:
|
||||
void *preallocateSpace = nullptr;
|
||||
int preallocateSize = 0;
|
||||
void *preallocateStreamSpace = nullptr;
|
||||
int preallocateStreamSize = 0;
|
||||
void *preallocateFrameSpace = nullptr;
|
||||
int preallocateFrameSize = 0;
|
||||
void *preallocateSynthSpace = nullptr;
|
||||
int preallocateSynthSize = 0;
|
||||
|
||||
static constexpr int buffLen = 0x600; // Slightly larger than largest MP3 frame
|
||||
unsigned char *buff;
|
||||
int lastReadPos;
|
||||
int lastBuffLen;
|
||||
unsigned int lastRate;
|
||||
int lastChannels;
|
||||
|
||||
// Decoding bits
|
||||
bool madInitted;
|
||||
struct mad_stream *stream;
|
||||
struct mad_frame *frame;
|
||||
struct mad_synth *synth;
|
||||
int samplePtr;
|
||||
int nsCount;
|
||||
int nsCountMax;
|
||||
|
||||
// The internal helpers
|
||||
enum mad_flow ErrorToFlow();
|
||||
enum mad_flow Input();
|
||||
bool DecodeNextFrame();
|
||||
bool GetOneSample(int16_t sample[2]);
|
||||
|
||||
private:
|
||||
int unrecoverable = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
AudioGeneratorMP3
|
||||
Audio output generator using the Helix MP3 decoder
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
#include "AudioGeneratorMP3a.h"
|
||||
|
||||
|
||||
AudioGeneratorMP3a::AudioGeneratorMP3a()
|
||||
{
|
||||
running = false;
|
||||
file = NULL;
|
||||
output = NULL;
|
||||
hMP3Decoder = MP3InitDecoder();
|
||||
if (!hMP3Decoder) {
|
||||
audioLogger->printf_P(PSTR("Out of memory error! hMP3Decoder==NULL\n"));
|
||||
Serial.flush();
|
||||
}
|
||||
// For sanity's sake...
|
||||
memset(buff, 0, sizeof(buff));
|
||||
memset(outSample, 0, sizeof(outSample));
|
||||
buffValid = 0;
|
||||
lastFrameEnd = 0;
|
||||
validSamples = 0;
|
||||
curSample = 0;
|
||||
lastRate = 0;
|
||||
lastChannels = 0;
|
||||
}
|
||||
|
||||
AudioGeneratorMP3a::~AudioGeneratorMP3a()
|
||||
{
|
||||
MP3FreeDecoder(hMP3Decoder);
|
||||
}
|
||||
|
||||
bool AudioGeneratorMP3a::stop()
|
||||
{
|
||||
if (!running) return true;
|
||||
running = false;
|
||||
output->stop();
|
||||
return file->close();
|
||||
}
|
||||
|
||||
bool AudioGeneratorMP3a::isRunning()
|
||||
{
|
||||
return running;
|
||||
}
|
||||
|
||||
bool AudioGeneratorMP3a::FillBufferWithValidFrame()
|
||||
{
|
||||
buff[0] = 0; // Destroy any existing sync word @ 0
|
||||
int nextSync;
|
||||
do {
|
||||
nextSync = MP3FindSyncWord(buff + lastFrameEnd, buffValid - lastFrameEnd);
|
||||
if (nextSync >= 0) nextSync += lastFrameEnd;
|
||||
lastFrameEnd = 0;
|
||||
if (nextSync == -1) {
|
||||
if (buff[buffValid-1]==0xff) { // Could be 1st half of syncword, preserve it...
|
||||
buff[0] = 0xff;
|
||||
buffValid = file->read(buff+1, sizeof(buff)-1);
|
||||
if (buffValid==0) return false; // No data available, EOF
|
||||
} else { // Try a whole new buffer
|
||||
buffValid = file->read(buff, sizeof(buff));
|
||||
if (buffValid==0) return false; // No data available, EOF
|
||||
}
|
||||
}
|
||||
} while (nextSync == -1);
|
||||
|
||||
// Move the frame to start at offset 0 in the buffer
|
||||
buffValid -= nextSync; // Throw out prior to nextSync
|
||||
memmove(buff, buff+nextSync, buffValid);
|
||||
|
||||
// We have a sync word at 0 now, try and fill remainder of buffer
|
||||
buffValid += file->read(buff + buffValid, sizeof(buff) - buffValid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorMP3a::loop()
|
||||
{
|
||||
if (!running) goto done; // Nothing to do here!
|
||||
|
||||
// If we've got data, try and pump it out...
|
||||
while (validSamples) {
|
||||
lastSample[0] = outSample[curSample*2];
|
||||
lastSample[1] = outSample[curSample*2 + 1];
|
||||
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
|
||||
validSamples--;
|
||||
curSample++;
|
||||
}
|
||||
|
||||
// No samples available, need to decode a new frame
|
||||
if (FillBufferWithValidFrame()) {
|
||||
// buff[0] start of frame, decode it...
|
||||
unsigned char *inBuff = reinterpret_cast<unsigned char *>(buff);
|
||||
int bytesLeft = buffValid;
|
||||
int ret = MP3Decode(hMP3Decoder, &inBuff, &bytesLeft, outSample, 0);
|
||||
if (ret) {
|
||||
// Error, skip the frame...
|
||||
char buff[48];
|
||||
sprintf(buff, "MP3 decode error %d", ret);
|
||||
cb.st(ret, buff);
|
||||
} else {
|
||||
lastFrameEnd = buffValid - bytesLeft;
|
||||
MP3FrameInfo fi;
|
||||
MP3GetLastFrameInfo(hMP3Decoder, &fi);
|
||||
if ((int)fi.samprate!= (int)lastRate) {
|
||||
output->SetRate(fi.samprate);
|
||||
lastRate = fi.samprate;
|
||||
}
|
||||
if (fi.nChans != lastChannels) {
|
||||
output->SetChannels(fi.nChans);
|
||||
lastChannels = fi.nChans;
|
||||
}
|
||||
curSample = 0;
|
||||
validSamples = fi.outputSamps / lastChannels;
|
||||
}
|
||||
} else {
|
||||
running = false; // No more data, we're done here...
|
||||
}
|
||||
|
||||
done:
|
||||
file->loop();
|
||||
output->loop();
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
bool AudioGeneratorMP3a::begin(AudioFileSource *source, AudioOutput *output)
|
||||
{
|
||||
if (!source) return false;
|
||||
file = source;
|
||||
if (!output) return false;
|
||||
this->output = output;
|
||||
if (!file->isOpen()) return false; // Error
|
||||
|
||||
output->begin();
|
||||
|
||||
// AAC always comes out at 16 bits
|
||||
output->SetBitsPerSample(16);
|
||||
|
||||
running = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
AudioGeneratorMP3
|
||||
Audio output generator using the Helix MP3 decoder
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOGENERATORMP3A_H
|
||||
#define _AUDIOGENERATORMP3A_H
|
||||
|
||||
#include "AudioGenerator.h"
|
||||
#include "libhelix-mp3/mp3dec.h"
|
||||
|
||||
class AudioGeneratorMP3a : public AudioGenerator
|
||||
{
|
||||
public:
|
||||
AudioGeneratorMP3a();
|
||||
virtual ~AudioGeneratorMP3a() override;
|
||||
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
|
||||
virtual bool loop() override;
|
||||
virtual bool stop() override;
|
||||
virtual bool isRunning() override;
|
||||
|
||||
protected:
|
||||
// Helix MP3 decoder
|
||||
HMP3Decoder hMP3Decoder;
|
||||
|
||||
// Input buffering
|
||||
uint8_t buff[1600]; // File buffer required to store at least a whole compressed frame
|
||||
int16_t buffValid;
|
||||
int16_t lastFrameEnd;
|
||||
bool FillBufferWithValidFrame(); // Read until we get a valid syncword and min(feof, 2048) butes in the buffer
|
||||
|
||||
// Output buffering
|
||||
int16_t outSample[1152 * 2]; // Interleaved L/R
|
||||
int16_t validSamples;
|
||||
int16_t curSample;
|
||||
|
||||
// Each frame may change this if they're very strange, I guess
|
||||
unsigned int lastRate;
|
||||
int lastChannels;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
AudioGeneratorOpus
|
||||
Audio output generator that plays Opus audio files
|
||||
|
||||
Copyright (C) 2020 Earle F. Philhower, III
|
||||
|
||||
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 <AudioGeneratorOpus.h>
|
||||
|
||||
AudioGeneratorOpus::AudioGeneratorOpus()
|
||||
{
|
||||
of = nullptr;
|
||||
buff = nullptr;
|
||||
buffPtr = 0;
|
||||
buffLen = 0;
|
||||
running = false;
|
||||
}
|
||||
|
||||
AudioGeneratorOpus::~AudioGeneratorOpus()
|
||||
{
|
||||
if (of) op_free(of);
|
||||
of = nullptr;
|
||||
free(buff);
|
||||
buff = nullptr;
|
||||
}
|
||||
|
||||
#define OPUS_BUFF 1024
|
||||
|
||||
bool AudioGeneratorOpus::begin(AudioFileSource *source, AudioOutput *output)
|
||||
{
|
||||
buff = (int16_t*)malloc(OPUS_BUFF * sizeof(int16_t));
|
||||
if (!buff) return false;
|
||||
|
||||
if (!source) return false;
|
||||
file = source;
|
||||
if (!output) return false;
|
||||
this->output = output;
|
||||
if (!file->isOpen()) return false; // Error
|
||||
|
||||
of = op_open_callbacks((void*)this, &cb, nullptr, 0, nullptr);
|
||||
if (!of) return false;
|
||||
|
||||
prev_li = -1;
|
||||
lastSample[0] = 0;
|
||||
lastSample[1] = 0;
|
||||
|
||||
buffPtr = 0;
|
||||
buffLen = 0;
|
||||
|
||||
output->begin();
|
||||
|
||||
// These are fixed by Opus
|
||||
output->SetRate(48000);
|
||||
output->SetBitsPerSample(16);
|
||||
output->SetChannels(2);
|
||||
|
||||
running = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorOpus::loop()
|
||||
{
|
||||
|
||||
if (!running) goto done;
|
||||
|
||||
if (!output->ConsumeSample(lastSample)) goto done; // Try and send last buffered sample
|
||||
|
||||
do {
|
||||
if (buffPtr == buffLen) {
|
||||
int ret = op_read_stereo(of, (opus_int16 *)buff, OPUS_BUFF);
|
||||
if (ret == OP_HOLE) {
|
||||
// fprintf(stderr,"\nHole detected! Corrupt file segment?\n");
|
||||
continue;
|
||||
} else if (ret <= 0) {
|
||||
running = false;
|
||||
goto done;
|
||||
}
|
||||
buffPtr = 0;
|
||||
buffLen = ret * 2;
|
||||
}
|
||||
|
||||
lastSample[AudioOutput::LEFTCHANNEL] = buff[buffPtr] & 0xffff;
|
||||
lastSample[AudioOutput::RIGHTCHANNEL] = buff[buffPtr+1] & 0xffff;
|
||||
buffPtr += 2;
|
||||
} while (running && output->ConsumeSample(lastSample));
|
||||
|
||||
done:
|
||||
file->loop();
|
||||
output->loop();
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
bool AudioGeneratorOpus::stop()
|
||||
{
|
||||
if (of) op_free(of);
|
||||
of = nullptr;
|
||||
free(buff);
|
||||
buff = nullptr;
|
||||
running = false;
|
||||
output->stop();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorOpus::isRunning()
|
||||
{
|
||||
return running;
|
||||
}
|
||||
|
||||
int AudioGeneratorOpus::read_cb(unsigned char *_ptr, int _nbytes) {
|
||||
if (_nbytes == 0) return 0;
|
||||
_nbytes = file->read(_ptr, _nbytes);
|
||||
if (_nbytes == 0) return -1;
|
||||
return _nbytes;
|
||||
}
|
||||
|
||||
int AudioGeneratorOpus::seek_cb(opus_int64 _offset, int _whence) {
|
||||
if (!file->seek((int32_t)_offset, _whence)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
opus_int64 AudioGeneratorOpus::tell_cb() {
|
||||
return file->getPos();
|
||||
}
|
||||
|
||||
int AudioGeneratorOpus::close_cb() {
|
||||
// NO OP, we close in main loop
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
AudioGeneratorOpus
|
||||
Audio output generator that plays Opus audio files
|
||||
|
||||
Copyright (C) 2020 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOGENERATOROPUS_H
|
||||
#define _AUDIOGENERATOROPUS_H
|
||||
|
||||
#include <AudioGenerator.h>
|
||||
//#include "libopus/opus.h"
|
||||
#include "opusfile/opusfile.h"
|
||||
|
||||
class AudioGeneratorOpus : public AudioGenerator
|
||||
{
|
||||
public:
|
||||
AudioGeneratorOpus();
|
||||
virtual ~AudioGeneratorOpus() override;
|
||||
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
|
||||
virtual bool loop() override;
|
||||
virtual bool stop() override;
|
||||
virtual bool isRunning() override;
|
||||
|
||||
protected:
|
||||
// Opus callbacks, need static functions to bounce into C++ from C
|
||||
static int OPUS_read(void *_stream, unsigned char *_ptr, int _nbytes) {
|
||||
return static_cast<AudioGeneratorOpus*>(_stream)->read_cb(_ptr, _nbytes);
|
||||
}
|
||||
static int OPUS_seek(void *_stream, opus_int64 _offset, int _whence) {
|
||||
return static_cast<AudioGeneratorOpus*>(_stream)->seek_cb(_offset, _whence);
|
||||
}
|
||||
static opus_int64 OPUS_tell(void *_stream) {
|
||||
return static_cast<AudioGeneratorOpus*>(_stream)->tell_cb();
|
||||
}
|
||||
static int OPUS_close(void *_stream) {
|
||||
return static_cast<AudioGeneratorOpus*>(_stream)->close_cb();
|
||||
}
|
||||
|
||||
// Actual Opus callbacks
|
||||
int read_cb(unsigned char *_ptr, int _nbytes);
|
||||
int seek_cb(opus_int64 _offset, int _whence);
|
||||
opus_int64 tell_cb();
|
||||
int close_cb();
|
||||
|
||||
private:
|
||||
OpusFileCallbacks cb = {OPUS_read, OPUS_seek, OPUS_tell, OPUS_close};
|
||||
OggOpusFile *of;
|
||||
int prev_li; // To detect changes in streams
|
||||
|
||||
int16_t *buff;
|
||||
uint32_t buffPtr;
|
||||
uint32_t buffLen;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
AudioGeneratorRTTTL
|
||||
Audio output generator that plays RTTTL (Nokia ringtone)
|
||||
|
||||
Based on the Rtttl Arduino library by James BM, https://github.com/spicajames/Rtttl
|
||||
Based on the gist from Daniel Hall https://gist.github.com/smarthall/1618800
|
||||
|
||||
Copyright (C) 2018 Earle F. Philhower, III
|
||||
|
||||
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 "AudioGeneratorRTTTL.h"
|
||||
|
||||
AudioGeneratorRTTTL::AudioGeneratorRTTTL()
|
||||
{
|
||||
running = false;
|
||||
file = NULL;
|
||||
output = NULL;
|
||||
rate = 22050;
|
||||
buff = nullptr;
|
||||
ptr = 0;
|
||||
}
|
||||
|
||||
AudioGeneratorRTTTL::~AudioGeneratorRTTTL()
|
||||
{
|
||||
free(buff);
|
||||
}
|
||||
|
||||
bool AudioGeneratorRTTTL::stop()
|
||||
{
|
||||
if (!running) return true;
|
||||
running = false;
|
||||
output->stop();
|
||||
return file->close();
|
||||
}
|
||||
|
||||
bool AudioGeneratorRTTTL::isRunning()
|
||||
{
|
||||
return running;
|
||||
}
|
||||
|
||||
bool AudioGeneratorRTTTL::loop()
|
||||
{
|
||||
if (!running) goto done; // Nothing to do here!
|
||||
|
||||
// Load the next note, if we've hit the end of the last one
|
||||
if (samplesSent == ttlSamples) {
|
||||
if (!GetNextNote()) {
|
||||
running = false;
|
||||
goto done;
|
||||
}
|
||||
samplesSent = 0;
|
||||
}
|
||||
|
||||
// Try and send out the remainder of the existing note, one per loop()
|
||||
if (ttlSamplesPerWaveFP10 == 0) { // Mute
|
||||
int16_t mute[2] = {0, 0};
|
||||
while ((samplesSent < ttlSamples) && output->ConsumeSample(mute)) {
|
||||
samplesSent++;
|
||||
}
|
||||
} else {
|
||||
while (samplesSent < ttlSamples) {
|
||||
int samplesSentFP10 = samplesSent << 10;
|
||||
int rem = samplesSentFP10 % ttlSamplesPerWaveFP10;
|
||||
int16_t val = (rem > ttlSamplesPerWaveFP10/2) ? 8192:-8192;
|
||||
int16_t s[2] = { val, val };
|
||||
if (!output->ConsumeSample(s)) goto done;
|
||||
samplesSent++;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
file->loop();
|
||||
output->loop();
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
bool AudioGeneratorRTTTL::SkipWhitespace()
|
||||
{
|
||||
while ((ptr < len) && (buff[ptr] == ' ')) ptr++;
|
||||
return ptr < len;
|
||||
}
|
||||
|
||||
bool AudioGeneratorRTTTL::ReadInt(int *dest)
|
||||
{
|
||||
if (ptr >= len) return false;
|
||||
|
||||
SkipWhitespace();
|
||||
if (ptr >= len) return false;
|
||||
if ((buff[ptr] <'0') || (buff[ptr] > '9')) return false;
|
||||
|
||||
int t = 0;
|
||||
while ((buff[ptr] >= '0') && (buff[ptr] <='9')) {
|
||||
t = (t * 10) + (buff[ptr] - '0');
|
||||
ptr++;
|
||||
}
|
||||
*dest = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool AudioGeneratorRTTTL::ParseHeader()
|
||||
{
|
||||
// Skip the title
|
||||
while ((ptr < len) && (buff[ptr] != ':')) ptr++;
|
||||
if (ptr >= len) return false;
|
||||
if (buff[ptr++] != ':') return false;
|
||||
if (!SkipWhitespace()) return false;
|
||||
if ((buff[ptr] != 'd') && (buff[ptr] != 'D')) return false;
|
||||
ptr++;
|
||||
if (!SkipWhitespace()) return false;
|
||||
if (buff[ptr++] != '=') return false;
|
||||
if (!ReadInt(&defaultDuration)) return false;
|
||||
if (!SkipWhitespace()) return false;
|
||||
if (buff[ptr++] != ',') return false;
|
||||
|
||||
if (!SkipWhitespace()) return false;
|
||||
if ((buff[ptr] != 'o') && (buff[ptr] != 'O')) return false;
|
||||
ptr++;
|
||||
if (!SkipWhitespace()) return false;
|
||||
if (buff[ptr++] != '=') return false;
|
||||
if (!ReadInt(&defaultOctave)) return false;
|
||||
if (!SkipWhitespace()) return false;
|
||||
if (buff[ptr++] != ',') return false;
|
||||
|
||||
int bpm;
|
||||
if (!SkipWhitespace()) return false;
|
||||
if ((buff[ptr] != 'b') && (buff[ptr] != 'B')) return false;
|
||||
ptr++;
|
||||
if (!SkipWhitespace()) return false;
|
||||
if (buff[ptr++] != '=') return false;
|
||||
if (!ReadInt(&bpm)) return false;
|
||||
if (!SkipWhitespace()) return false;
|
||||
if (buff[ptr++] != ':') return false;
|
||||
|
||||
wholeNoteMS = (60 * 1000 * 4) / bpm;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define NOTE_C4 262
|
||||
#define NOTE_CS4 277
|
||||
#define NOTE_D4 294
|
||||
#define NOTE_DS4 311
|
||||
#define NOTE_E4 330
|
||||
#define NOTE_F4 349
|
||||
#define NOTE_FS4 370
|
||||
#define NOTE_G4 392
|
||||
#define NOTE_GS4 415
|
||||
#define NOTE_A4 440
|
||||
#define NOTE_AS4 466
|
||||
#define NOTE_B4 494
|
||||
#define NOTE_C5 523
|
||||
#define NOTE_CS5 554
|
||||
#define NOTE_D5 587
|
||||
#define NOTE_DS5 622
|
||||
#define NOTE_E5 659
|
||||
#define NOTE_F5 698
|
||||
#define NOTE_FS5 740
|
||||
#define NOTE_G5 784
|
||||
#define NOTE_GS5 831
|
||||
#define NOTE_A5 880
|
||||
#define NOTE_AS5 932
|
||||
#define NOTE_B5 988
|
||||
#define NOTE_C6 1047
|
||||
#define NOTE_CS6 1109
|
||||
#define NOTE_D6 1175
|
||||
#define NOTE_DS6 1245
|
||||
#define NOTE_E6 1319
|
||||
#define NOTE_F6 1397
|
||||
#define NOTE_FS6 1480
|
||||
#define NOTE_G6 1568
|
||||
#define NOTE_GS6 1661
|
||||
#define NOTE_A6 1760
|
||||
#define NOTE_AS6 1865
|
||||
#define NOTE_B6 1976
|
||||
#define NOTE_C7 2093
|
||||
#define NOTE_CS7 2217
|
||||
#define NOTE_D7 2349
|
||||
#define NOTE_DS7 2489
|
||||
#define NOTE_E7 2637
|
||||
#define NOTE_F7 2794
|
||||
#define NOTE_FS7 2960
|
||||
#define NOTE_G7 3136
|
||||
#define NOTE_GS7 3322
|
||||
#define NOTE_A7 3520
|
||||
#define NOTE_AS7 3729
|
||||
#define NOTE_B7 3951
|
||||
static int notes[49] = { 0,
|
||||
NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,
|
||||
NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,
|
||||
NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,
|
||||
NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7 };
|
||||
|
||||
bool AudioGeneratorRTTTL::GetNextNote()
|
||||
{
|
||||
int dur, note, scale;
|
||||
if (ptr >= len) return false;
|
||||
|
||||
if (!ReadInt(&dur)) {
|
||||
dur = defaultDuration;
|
||||
}
|
||||
dur = wholeNoteMS / dur;
|
||||
|
||||
if (ptr >= len) return false;
|
||||
note = 0;
|
||||
switch (buff[ptr++]) {
|
||||
case 'c': case 'C': note = 1; break;
|
||||
case 'd': case 'D': note = 3; break;
|
||||
case 'e': case 'E': note = 5; break;
|
||||
case 'f': case 'F': note = 6; break;
|
||||
case 'g': case 'G': note = 8; break;
|
||||
case 'a': case 'A': note = 10; break;
|
||||
case 'b': case 'B': note = 12; break;
|
||||
case 'p': case 'P': note = 0; break;
|
||||
default: return false;
|
||||
}
|
||||
if ((ptr < len) && (buff[ptr] == '#')) {
|
||||
ptr++;
|
||||
note++;
|
||||
}
|
||||
if ((ptr < len) && (buff[ptr] == '.')) {
|
||||
ptr++;
|
||||
dur += dur / 2;
|
||||
}
|
||||
if (!ReadInt(&scale)) {
|
||||
scale = defaultOctave;
|
||||
}
|
||||
// Eat any trailing whitespace and comma
|
||||
SkipWhitespace();
|
||||
if ((ptr < len) && (buff[ptr]==',')) {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
if (scale < 4) scale = 4;
|
||||
if (scale > 7) scale = 7;
|
||||
if (note) {
|
||||
int freq = notes[(scale - 4) * 12 + note];
|
||||
// Convert from frequency in Hz to high and low samples in fixed point
|
||||
ttlSamplesPerWaveFP10 = (rate << 10) / freq;
|
||||
} else {
|
||||
ttlSamplesPerWaveFP10 = 0;
|
||||
}
|
||||
ttlSamples = (rate * dur ) / 1000;
|
||||
|
||||
//audioLogger->printf("%d %d %d %d %d\n", dur, note, scale, ttlSamplesPerWaveFP10, ttlSamples );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorRTTTL::begin(AudioFileSource *source, AudioOutput *output)
|
||||
{
|
||||
if (!source) return false;
|
||||
file = source;
|
||||
if (!output) return false;
|
||||
this->output = output;
|
||||
if (!file->isOpen()) return false; // Error
|
||||
|
||||
len = file->getSize();
|
||||
buff = (char *)malloc(len);
|
||||
if (!buff) return false;
|
||||
if (file->read(buff, len) != (uint32_t)len) return false;
|
||||
|
||||
ptr = 0;
|
||||
samplesSent = 0;
|
||||
ttlSamples = 0;
|
||||
|
||||
if (!ParseHeader()) return false;
|
||||
|
||||
if (!output->SetRate( rate )) return false;
|
||||
if (!output->SetBitsPerSample( 16 )) return false;
|
||||
if (!output->SetChannels( 2 )) return false;
|
||||
if (!output->begin()) return false;
|
||||
|
||||
running = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
AudioGeneratorRTTTL
|
||||
Audio output generator that plays RTTTL (Nokia ringtones)
|
||||
|
||||
Based on the Rtttl Arduino library by James BM, https://github.com/spicajames/Rtttl
|
||||
Based on the gist from Daniel Hall https://gist.github.com/smarthall/1618800
|
||||
|
||||
Copyright (C) 2018 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOGENERATORRTTTL_H
|
||||
#define _AUDIOGENERATORRTTTL_H
|
||||
|
||||
#include "AudioGenerator.h"
|
||||
|
||||
class AudioGeneratorRTTTL : public AudioGenerator
|
||||
{
|
||||
public:
|
||||
AudioGeneratorRTTTL();
|
||||
virtual ~AudioGeneratorRTTTL() override;
|
||||
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
|
||||
virtual bool loop() override;
|
||||
virtual bool stop() override;
|
||||
virtual bool isRunning() override;
|
||||
void SetRate(uint16_t hz) { rate = hz; }
|
||||
|
||||
private:
|
||||
bool SkipWhitespace();
|
||||
bool ReadInt(int *dest);
|
||||
bool ParseHeader();
|
||||
bool GetNextNote();
|
||||
|
||||
protected:
|
||||
uint16_t rate;
|
||||
|
||||
// We copy the entire tiny song to a buffer for easier access
|
||||
char *buff;
|
||||
int len;
|
||||
int ptr;
|
||||
|
||||
// Song-global settings
|
||||
int defaultDuration;
|
||||
int defaultOctave;
|
||||
int wholeNoteMS;
|
||||
|
||||
// The note we're currently playing
|
||||
int ttlSamplesPerWaveFP10;
|
||||
int ttlSamples;
|
||||
int samplesSent;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
AudioGeneratorTalkie
|
||||
Audio output generator that speaks using the LPC code in old TI speech chips
|
||||
Output is locked at 8khz as that's that the hardcoded LPC coefficients are built around
|
||||
|
||||
Based on the Talkie Arduino library by Peter Knight, https://github.com/going-digital/Talkie
|
||||
|
||||
Copyright (C) 2020 Earle F. Philhower, III
|
||||
|
||||
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 "AudioGeneratorTalkie.h"
|
||||
|
||||
AudioGeneratorTalkie::AudioGeneratorTalkie()
|
||||
{
|
||||
running = false;
|
||||
lastFrame = false;
|
||||
file = nullptr;
|
||||
output = nullptr;
|
||||
buff = nullptr;
|
||||
}
|
||||
|
||||
AudioGeneratorTalkie::~AudioGeneratorTalkie()
|
||||
{
|
||||
free(buff);
|
||||
}
|
||||
|
||||
bool AudioGeneratorTalkie::say(const uint8_t *data, size_t len, bool async) {
|
||||
// Finish saying anything in the pipe
|
||||
while (running) {
|
||||
loop();
|
||||
delay(0);
|
||||
}
|
||||
buff = (uint8_t*)realloc(buff, len);
|
||||
if (!buff) return false;
|
||||
memcpy_P(buff, data, len);
|
||||
|
||||
// Reset the interpreter to the start of the stream
|
||||
ptrAddr = buff;
|
||||
ptrBit = 0;
|
||||
frameLeft = 0;
|
||||
lastFrame = false;
|
||||
|
||||
running = true;
|
||||
|
||||
if (!async) {
|
||||
// Finish saying anything in the pipe
|
||||
while (running) {
|
||||
loop();
|
||||
delay(0);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorTalkie::begin(AudioFileSource *source, AudioOutput *output)
|
||||
{
|
||||
if (!output) return false;
|
||||
this->output = output;
|
||||
if (source) {
|
||||
file = source;
|
||||
if (!file->isOpen()) return false; // Error
|
||||
auto len = file->getSize();
|
||||
uint8_t *temp = (uint8_t *)malloc(len);
|
||||
if (!temp) return false;
|
||||
if (file->read(temp, len) != (uint32_t)len) return false;
|
||||
say(temp, len);
|
||||
free(temp);
|
||||
} else {
|
||||
// Reset the interpreter to the start of the stream
|
||||
ptrAddr = buff;
|
||||
ptrBit = 0;
|
||||
frameLeft = 0;
|
||||
running = false;
|
||||
}
|
||||
|
||||
if (!output->SetRate( 8000 )) return false;
|
||||
if (!output->SetBitsPerSample( 16 )) return false;
|
||||
if (!output->SetChannels( 2 )) return false;
|
||||
if (!output->begin()) return false;
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool AudioGeneratorTalkie::stop()
|
||||
{
|
||||
if (!running) return true;
|
||||
running = false;
|
||||
output->stop();
|
||||
return file ? file->close() : true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorTalkie::isRunning()
|
||||
{
|
||||
return running;
|
||||
}
|
||||
|
||||
bool AudioGeneratorTalkie::loop()
|
||||
{
|
||||
if (!running) goto done; // Nothing to do here!
|
||||
|
||||
if (!frameLeft) {
|
||||
if (lastFrame) {
|
||||
running = false;
|
||||
goto done;
|
||||
}
|
||||
lastFrame = genOneFrame();
|
||||
}
|
||||
|
||||
if (frameLeft) {
|
||||
for ( ; frameLeft; frameLeft--) {
|
||||
auto res = genOneSample();
|
||||
int16_t r[2] = {res, res};
|
||||
if (!output->ConsumeSample(r)) break;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (file) file->loop();
|
||||
output->loop();
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
// The ROMs used with the TI speech were serial, not byte wide.
|
||||
// Here's a handy routine to flip ROM data which is usually reversed.
|
||||
uint8_t AudioGeneratorTalkie::rev(uint8_t a)
|
||||
{
|
||||
// 76543210
|
||||
a = (a>>4) | (a<<4); // Swap in groups of 4
|
||||
// 32107654
|
||||
a = ((a & 0xcc)>>2) | ((a & 0x33)<<2); // Swap in groups of 2
|
||||
// 10325476
|
||||
a = ((a & 0xaa)>>1) | ((a & 0x55)<<1); // Swap bit pairs
|
||||
// 01234567
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
uint8_t AudioGeneratorTalkie::getBits(uint8_t bits) {
|
||||
uint8_t value;
|
||||
uint16_t data;
|
||||
data = rev(ptrAddr[0])<<8;
|
||||
if (ptrBit+bits > 8) {
|
||||
data |= rev(ptrAddr[1]);
|
||||
}
|
||||
data <<= ptrBit;
|
||||
value = data >> (16-bits);
|
||||
ptrBit += bits;
|
||||
if (ptrBit >= 8) {
|
||||
ptrBit -= 8;
|
||||
ptrAddr++;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wnarrowing"
|
||||
// Constant LPC coefficient tables
|
||||
static const uint8_t tmsEnergy[0x10] = {0x00,0x02,0x03,0x04,0x05,0x07,0x0a,0x0f,0x14,0x20,0x29,0x39,0x51,0x72,0xa1,0xff};
|
||||
static const uint8_t tmsPeriod[0x40] = {0x00,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2D,0x2F,0x31,0x33,0x35,0x36,0x39,0x3B,0x3D,0x3F,0x42,0x45,0x47,0x49,0x4D,0x4F,0x51,0x55,0x57,0x5C,0x5F,0x63,0x66,0x6A,0x6E,0x73,0x77,0x7B,0x80,0x85,0x8A,0x8F,0x95,0x9A,0xA0};
|
||||
static const int16_t tmsK1[0x20] = {0x82C0,0x8380,0x83C0,0x8440,0x84C0,0x8540,0x8600,0x8780,0x8880,0x8980,0x8AC0,0x8C00,0x8D40,0x8F00,0x90C0,0x92C0,0x9900,0xA140,0xAB80,0xB840,0xC740,0xD8C0,0xEBC0,0x0000,0x1440,0x2740,0x38C0,0x47C0,0x5480,0x5EC0,0x6700,0x6D40};
|
||||
static const int16_t tmsK2[0x20] = {0xAE00,0xB480,0xBB80,0xC340,0xCB80,0xD440,0xDDC0,0xE780,0xF180,0xFBC0,0x0600,0x1040,0x1A40,0x2400,0x2D40,0x3600,0x3E40,0x45C0,0x4CC0,0x5300,0x5880,0x5DC0,0x6240,0x6640,0x69C0,0x6CC0,0x6F80,0x71C0,0x73C0,0x7580,0x7700,0x7E80};
|
||||
static const int8_t tmsK3[0x10] = {0x92,0x9F,0xAD,0xBA,0xC8,0xD5,0xE3,0xF0,0xFE,0x0B,0x19,0x26,0x34,0x41,0x4F,0x5C};
|
||||
static const int8_t tmsK4[0x10] = {0xAE,0xBC,0xCA,0xD8,0xE6,0xF4,0x01,0x0F,0x1D,0x2B,0x39,0x47,0x55,0x63,0x71,0x7E};
|
||||
static const int8_t tmsK5[0x10] = {0xAE,0xBA,0xC5,0xD1,0xDD,0xE8,0xF4,0xFF,0x0B,0x17,0x22,0x2E,0x39,0x45,0x51,0x5C};
|
||||
static const int8_t tmsK6[0x10] = {0xC0,0xCB,0xD6,0xE1,0xEC,0xF7,0x03,0x0E,0x19,0x24,0x2F,0x3A,0x45,0x50,0x5B,0x66};
|
||||
static const int8_t tmsK7[0x10] = {0xB3,0xBF,0xCB,0xD7,0xE3,0xEF,0xFB,0x07,0x13,0x1F,0x2B,0x37,0x43,0x4F,0x5A,0x66};
|
||||
static const int8_t tmsK8[0x08] = {0xC0,0xD8,0xF0,0x07,0x1F,0x37,0x4F,0x66};
|
||||
static const int8_t tmsK9[0x08] = {0xC0,0xD4,0xE8,0xFC,0x10,0x25,0x39,0x4D};
|
||||
static const int8_t tmsK10[0x08] = {0xCD,0xDF,0xF1,0x04,0x16,0x20,0x3B,0x4D};
|
||||
|
||||
// The chirp we active the filter using
|
||||
static const int8_t chirp[] = {0x00,0x2a,0xd4,0x32,0xb2,0x12,0x25,0x14,0x02,0xe1,0xc5,0x02,0x5f,0x5a,0x05,0x0f,0x26,0xfc,0xa5,0xa5,0xd6,0xdd,0xdc,0xfc,0x25,0x2b,0x22,0x21,0x0f,0xff,0xf8,0xee,0xed,0xef,0xf7,0xf6,0xfa,0x00,0x03,0x02,0x01};
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
|
||||
bool AudioGeneratorTalkie::genOneFrame() {
|
||||
uint8_t energy;
|
||||
uint8_t repeat;
|
||||
|
||||
// Read speech data, processing the variable size frames.
|
||||
|
||||
energy = getBits(4);
|
||||
if (energy == 0) {
|
||||
// Energy = 0: rest frame
|
||||
synthEnergy = 0;
|
||||
} else if (energy == 0xf) {
|
||||
// Energy = 15: stop frame. Silence the synthesiser.
|
||||
synthEnergy = 0;
|
||||
synthK1 = 0;
|
||||
synthK2 = 0;
|
||||
synthK3 = 0;
|
||||
synthK4 = 0;
|
||||
synthK5 = 0;
|
||||
synthK6 = 0;
|
||||
synthK7 = 0;
|
||||
synthK8 = 0;
|
||||
synthK9 = 0;
|
||||
synthK10 = 0;
|
||||
} else {
|
||||
synthEnergy = tmsEnergy[energy];
|
||||
repeat = getBits(1);
|
||||
synthPeriod = tmsPeriod[getBits(6)];
|
||||
// A repeat frame uses the last coefficients
|
||||
if (!repeat) {
|
||||
// All frames use the first 4 coefficients
|
||||
synthK1 = tmsK1[getBits(5)];
|
||||
synthK2 = tmsK2[getBits(5)];
|
||||
synthK3 = tmsK3[getBits(4)];
|
||||
synthK4 = tmsK4[getBits(4)];
|
||||
if (synthPeriod) {
|
||||
// Voiced frames use 6 extra coefficients.
|
||||
synthK5 = tmsK5[getBits(4)];
|
||||
synthK6 = tmsK6[getBits(4)];
|
||||
synthK7 = tmsK7[getBits(4)];
|
||||
synthK8 = tmsK8[getBits(3)];
|
||||
synthK9 = tmsK9[getBits(3)];
|
||||
synthK10 = tmsK10[getBits(3)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frameLeft = 8000 / 40;
|
||||
|
||||
return (energy == 0xf); // Last frame will return true
|
||||
}
|
||||
|
||||
int16_t AudioGeneratorTalkie::genOneSample()
|
||||
{
|
||||
static uint8_t periodCounter;
|
||||
static int16_t x0,x1,x2,x3,x4,x5,x6,x7,x8,x9;
|
||||
int16_t u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10;
|
||||
|
||||
if (synthPeriod) {
|
||||
// Voiced source
|
||||
if (periodCounter < synthPeriod) {
|
||||
periodCounter++;
|
||||
} else {
|
||||
periodCounter = 0;
|
||||
}
|
||||
if (periodCounter < sizeof(chirp)) {
|
||||
u10 = ((chirp[periodCounter]) * (uint32_t) synthEnergy) >> 8;
|
||||
} else {
|
||||
u10 = 0;
|
||||
}
|
||||
} else {
|
||||
// Unvoiced source
|
||||
static uint16_t synthRand = 1;
|
||||
synthRand = (synthRand >> 1) ^ ((synthRand & 1) ? 0xB800 : 0);
|
||||
u10 = (synthRand & 1) ? synthEnergy : -synthEnergy;
|
||||
}
|
||||
// Lattice filter forward path
|
||||
u9 = u10 - (((int16_t)synthK10*x9) >> 7);
|
||||
u8 = u9 - (((int16_t)synthK9*x8) >> 7);
|
||||
u7 = u8 - (((int16_t)synthK8*x7) >> 7);
|
||||
u6 = u7 - (((int16_t)synthK7*x6) >> 7);
|
||||
u5 = u6 - (((int16_t)synthK6*x5) >> 7);
|
||||
u4 = u5 - (((int16_t)synthK5*x4) >> 7);
|
||||
u3 = u4 - (((int16_t)synthK4*x3) >> 7);
|
||||
u2 = u3 - (((int16_t)synthK3*x2) >> 7);
|
||||
u1 = u2 - (((int32_t)synthK2*x1) >> 15);
|
||||
u0 = u1 - (((int32_t)synthK1*x0) >> 15);
|
||||
|
||||
// Output clamp
|
||||
if (u0 > 511) u0 = 511;
|
||||
if (u0 < -512) u0 = -512;
|
||||
|
||||
// Lattice filter reverse path
|
||||
x9 = x8 + (((int16_t)synthK9*u8) >> 7);
|
||||
x8 = x7 + (((int16_t)synthK8*u7) >> 7);
|
||||
x7 = x6 + (((int16_t)synthK7*u6) >> 7);
|
||||
x6 = x5 + (((int16_t)synthK6*u5) >> 7);
|
||||
x5 = x4 + (((int16_t)synthK5*u4) >> 7);
|
||||
x4 = x3 + (((int16_t)synthK4*u3) >> 7);
|
||||
x3 = x2 + (((int16_t)synthK3*u2) >> 7);
|
||||
x2 = x1 + (((int32_t)synthK2*u1) >> 15);
|
||||
x1 = x0 + (((int32_t)synthK1*u0) >> 15);
|
||||
x0 = u0;
|
||||
|
||||
uint16_t v = u0; // 10 bits
|
||||
v <<= 6; // Now full 16
|
||||
return v;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
AudioGeneratorTalkie
|
||||
Audio output generator that speaks using the LPC code in old TI speech chips
|
||||
Output is locked at 8khz as that's that the hardcoded LPC coefficients are built around
|
||||
|
||||
Based on the Talkie Arduino library by Peter Knight, https://github.com/going-digital/Talkie
|
||||
|
||||
Copyright (C) 2020 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOGENERATORTALKIE_H
|
||||
#define _AUDIOGENERATORTALKIE_H
|
||||
|
||||
#include "AudioGenerator.h"
|
||||
|
||||
class AudioGeneratorTalkie : public AudioGenerator
|
||||
{
|
||||
public:
|
||||
AudioGeneratorTalkie();
|
||||
virtual ~AudioGeneratorTalkie() override;
|
||||
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
|
||||
virtual bool loop() override;
|
||||
virtual bool stop() override;
|
||||
virtual bool isRunning() override;
|
||||
bool say(const uint8_t *data, size_t len, bool async = false);
|
||||
|
||||
protected:
|
||||
// The data stream we're playing
|
||||
uint8_t *buff;
|
||||
|
||||
// Codeword stream handlers
|
||||
uint8_t *ptrAddr;
|
||||
uint8_t ptrBit;
|
||||
|
||||
bool lastFrame;
|
||||
bool genOneFrame(); // Fill up one frame's worth of data, returns if this is the last frame
|
||||
int16_t genOneSample(); // Generate one sample of a frame
|
||||
|
||||
// Utilities
|
||||
uint8_t rev(uint8_t a);
|
||||
uint8_t getBits(uint8_t bits);
|
||||
|
||||
// Synthesizer state
|
||||
uint8_t synthPeriod;
|
||||
uint16_t synthEnergy;
|
||||
int16_t synthK1, synthK2;
|
||||
int8_t synthK3, synthK4, synthK5, synthK6, synthK7, synthK8, synthK9, synthK10;
|
||||
|
||||
int frameLeft;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
AudioGeneratorWAV
|
||||
Audio output generator that reads 8 and 16-bit WAV files
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 "AudioGeneratorWAV.h"
|
||||
|
||||
AudioGeneratorWAV::AudioGeneratorWAV()
|
||||
{
|
||||
running = false;
|
||||
file = NULL;
|
||||
output = NULL;
|
||||
buffSize = 128;
|
||||
buff = NULL;
|
||||
buffPtr = 0;
|
||||
buffLen = 0;
|
||||
}
|
||||
|
||||
AudioGeneratorWAV::~AudioGeneratorWAV()
|
||||
{
|
||||
free(buff);
|
||||
buff = NULL;
|
||||
}
|
||||
|
||||
bool AudioGeneratorWAV::stop()
|
||||
{
|
||||
if (!running) return true;
|
||||
running = false;
|
||||
free(buff);
|
||||
buff = NULL;
|
||||
output->stop();
|
||||
return file->close();
|
||||
}
|
||||
|
||||
bool AudioGeneratorWAV::isRunning()
|
||||
{
|
||||
return running;
|
||||
}
|
||||
|
||||
|
||||
// Handle buffered reading, reload each time we run out of data
|
||||
bool AudioGeneratorWAV::GetBufferedData(int bytes, void *dest)
|
||||
{
|
||||
if (!running) return false; // Nothing to do here!
|
||||
uint8_t *p = reinterpret_cast<uint8_t*>(dest);
|
||||
while (bytes--) {
|
||||
// Potentially load next batch of data...
|
||||
if (buffPtr >= buffLen) {
|
||||
buffPtr = 0;
|
||||
uint32_t toRead = availBytes > buffSize ? buffSize : availBytes;
|
||||
buffLen = file->read( buff, toRead );
|
||||
availBytes -= buffLen;
|
||||
}
|
||||
if (buffPtr >= buffLen)
|
||||
return false; // No data left!
|
||||
*(p++) = buff[buffPtr++];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorWAV::loop()
|
||||
{
|
||||
if (!running) goto done; // Nothing to do here!
|
||||
|
||||
// First, try and push in the stored sample. If we can't, then punt and try later
|
||||
if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected
|
||||
|
||||
// Try and stuff the buffer one sample at a time
|
||||
do
|
||||
{
|
||||
if (bitsPerSample == 8) {
|
||||
uint8_t l, r;
|
||||
if (!GetBufferedData(1, &l)) stop();
|
||||
if (channels == 2) {
|
||||
if (!GetBufferedData(1, &r)) stop();
|
||||
} else {
|
||||
r = 0;
|
||||
}
|
||||
lastSample[AudioOutput::LEFTCHANNEL] = l;
|
||||
lastSample[AudioOutput::RIGHTCHANNEL] = r;
|
||||
} else if (bitsPerSample == 16) {
|
||||
if (!GetBufferedData(2, &lastSample[AudioOutput::LEFTCHANNEL])) stop();
|
||||
if (channels == 2) {
|
||||
if (!GetBufferedData(2, &lastSample[AudioOutput::RIGHTCHANNEL])) stop();
|
||||
} else {
|
||||
lastSample[AudioOutput::RIGHTCHANNEL] = 0;
|
||||
}
|
||||
}
|
||||
} while (running && output->ConsumeSample(lastSample));
|
||||
|
||||
done:
|
||||
file->loop();
|
||||
output->loop();
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
|
||||
bool AudioGeneratorWAV::ReadWAVInfo()
|
||||
{
|
||||
uint32_t u32;
|
||||
uint16_t u16;
|
||||
int toSkip;
|
||||
|
||||
// WAV specification document:
|
||||
// https://www.aelius.com/njh/wavemetatools/doc/riffmci.pdf
|
||||
|
||||
// Header == "RIFF"
|
||||
if (!ReadU32(&u32)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
if (u32 != 0x46464952) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid RIFF header, got: %08X \n"), (uint32_t) u32);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip ChunkSize
|
||||
if (!ReadU32(&u32)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
|
||||
// Format == "WAVE"
|
||||
if (!ReadU32(&u32)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
if (u32 != 0x45564157) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, invalid WAVE header, got: %08X \n"), (uint32_t) u32);
|
||||
return false;
|
||||
}
|
||||
|
||||
// there might be JUNK or PAD - ignore it by continuing reading until we get to "fmt "
|
||||
while (1) {
|
||||
if (!ReadU32(&u32)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
if (u32 == 0x20746d66) break; // 'fmt '
|
||||
};
|
||||
|
||||
// subchunk size
|
||||
if (!ReadU32(&u32)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
if (u32 == 16) { toSkip = 0; }
|
||||
else if (u32 == 18) { toSkip = 18 - 16; }
|
||||
else if (u32 == 40) { toSkip = 40 - 16; }
|
||||
else {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, appears not to be standard PCM \n"));
|
||||
return false;
|
||||
} // we only do standard PCM
|
||||
|
||||
// AudioFormat
|
||||
if (!ReadU16(&u16)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
if (u16 != 1) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, AudioFormat appears not to be standard PCM \n"));
|
||||
return false;
|
||||
} // we only do standard PCM
|
||||
|
||||
// NumChannels
|
||||
if (!ReadU16(&channels)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
if ((channels<1) || (channels>2)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, only mono and stereo are supported \n"));
|
||||
return false;
|
||||
} // Mono or stereo support only
|
||||
|
||||
// SampleRate
|
||||
if (!ReadU32(&sampleRate)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
if (sampleRate < 1) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, unknown sample rate \n"));
|
||||
return false;
|
||||
} // Weird rate, punt. Will need to check w/DAC to see if supported
|
||||
|
||||
// Ignore byterate and blockalign
|
||||
if (!ReadU32(&u32)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
if (!ReadU16(&u16)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
|
||||
// Bits per sample
|
||||
if (!ReadU16(&bitsPerSample)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
if ((bitsPerSample!=8) && (bitsPerSample != 16)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, only 8 or 16 bits is supported \n"));
|
||||
return false;
|
||||
} // Only 8 or 16 bits
|
||||
|
||||
// Skip any extra header
|
||||
while (toSkip) {
|
||||
uint8_t ign;
|
||||
if (!ReadU8(&ign)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
toSkip--;
|
||||
}
|
||||
|
||||
// look for data subchunk
|
||||
do {
|
||||
// id == "data"
|
||||
if (!ReadU32(&u32)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
if (u32 == 0x61746164) break; // "data"
|
||||
// Skip size, read until end of chunk
|
||||
if (!ReadU32(&u32)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
if(!file->seek(u32, SEEK_CUR)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data, seek failed\n"));
|
||||
return false;
|
||||
}
|
||||
} while (1);
|
||||
if (!file->isOpen()) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, file is not open\n"));
|
||||
return false;
|
||||
};
|
||||
|
||||
// Skip size, read until end of file...
|
||||
if (!ReadU32(&u32)) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: failed to read WAV data\n"));
|
||||
return false;
|
||||
};
|
||||
availBytes = u32;
|
||||
|
||||
// Now set up the buffer or fail
|
||||
buff = reinterpret_cast<uint8_t *>(malloc(buffSize));
|
||||
if (!buff) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::ReadWAVInfo: cannot read WAV, failed to set up buffer \n"));
|
||||
return false;
|
||||
};
|
||||
buffPtr = 0;
|
||||
buffLen = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioGeneratorWAV::begin(AudioFileSource *source, AudioOutput *output)
|
||||
{
|
||||
if (!source) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed: invalid source\n"));
|
||||
return false;
|
||||
}
|
||||
file = source;
|
||||
if (!output) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: invalid output\n"));
|
||||
return false;
|
||||
}
|
||||
this->output = output;
|
||||
if (!file->isOpen()) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: file not open\n"));
|
||||
return false;
|
||||
} // Error
|
||||
|
||||
if (!ReadWAVInfo()) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed during ReadWAVInfo\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!output->SetRate( sampleRate )) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed to SetRate in output\n"));
|
||||
return false;
|
||||
}
|
||||
if (!output->SetBitsPerSample( bitsPerSample )) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed to SetBitsPerSample in output\n"));
|
||||
return false;
|
||||
}
|
||||
if (!output->SetChannels( channels )) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: failed to SetChannels in output\n"));
|
||||
return false;
|
||||
}
|
||||
if (!output->begin()) {
|
||||
Serial.printf_P(PSTR("AudioGeneratorWAV::begin: output's begin did not return true\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
running = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
AudioGeneratorWAV
|
||||
Audio output generator that reads 8 and 16-bit WAV files
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOGENERATORWAV_H
|
||||
#define _AUDIOGENERATORWAV_H
|
||||
|
||||
#include "AudioGenerator.h"
|
||||
|
||||
class AudioGeneratorWAV : public AudioGenerator
|
||||
{
|
||||
public:
|
||||
AudioGeneratorWAV();
|
||||
virtual ~AudioGeneratorWAV() override;
|
||||
virtual bool begin(AudioFileSource *source, AudioOutput *output) override;
|
||||
virtual bool loop() override;
|
||||
virtual bool stop() override;
|
||||
virtual bool isRunning() override;
|
||||
void SetBufferSize(int sz) { buffSize = sz; }
|
||||
|
||||
private:
|
||||
bool ReadU32(uint32_t *dest) { return file->read(reinterpret_cast<uint8_t*>(dest), 4); }
|
||||
bool ReadU16(uint16_t *dest) { return file->read(reinterpret_cast<uint8_t*>(dest), 2); }
|
||||
bool ReadU8(uint8_t *dest) { return file->read(reinterpret_cast<uint8_t*>(dest), 1); }
|
||||
bool GetBufferedData(int bytes, void *dest);
|
||||
bool ReadWAVInfo();
|
||||
|
||||
|
||||
protected:
|
||||
// WAV info
|
||||
uint16_t channels;
|
||||
uint32_t sampleRate;
|
||||
uint16_t bitsPerSample;
|
||||
|
||||
uint32_t availBytes;
|
||||
|
||||
// We need to buffer some data in-RAM to avoid doing 1000s of small reads
|
||||
uint32_t buffSize;
|
||||
uint8_t *buff;
|
||||
uint16_t buffPtr;
|
||||
uint16_t buffLen;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
#include "AudioLogger.h"
|
||||
|
||||
DevNullOut silencedLogger;
|
||||
Print* audioLogger = &silencedLogger;
|
||||
19
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioLogger.h
Normal file
19
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioLogger.h
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifndef _AUDIOLOGGER_H
|
||||
#define _AUDIOLOGGER_H
|
||||
|
||||
class DevNullOut: public Print
|
||||
{
|
||||
public:
|
||||
virtual size_t write(uint8_t) { return 1; }
|
||||
};
|
||||
|
||||
extern DevNullOut silencedLogger;
|
||||
|
||||
// Global `audioLogger` is initialized to &silencedLogger
|
||||
// It can be initialized anytime to &Serial or any other Print:: derivative instance.
|
||||
extern Print* audioLogger;
|
||||
|
||||
#endif
|
||||
85
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioOutput.h
Normal file
85
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioOutput.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
AudioOutput
|
||||
Base class of an audio output player
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOOUTPUT_H
|
||||
#define _AUDIOOUTPUT_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "AudioStatus.h"
|
||||
|
||||
class AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutput() { };
|
||||
virtual ~AudioOutput() {};
|
||||
virtual bool SetRate(int hz) { hertz = hz; return true; }
|
||||
virtual bool SetBitsPerSample(int bits) { bps = bits; return true; }
|
||||
virtual bool SetChannels(int chan) { channels = chan; return true; }
|
||||
virtual bool SetGain(float f) { if (f>4.0) f = 4.0; if (f<0.0) f=0.0; gainF2P6 = (uint8_t)(f*(1<<6)); return true; }
|
||||
virtual bool begin() { return false; };
|
||||
typedef enum { LEFTCHANNEL=0, RIGHTCHANNEL=1 } SampleIndex;
|
||||
virtual bool ConsumeSample(int16_t sample[2]) { (void)sample; return false; }
|
||||
virtual uint16_t ConsumeSamples(int16_t *samples, uint16_t count)
|
||||
{
|
||||
for (uint16_t i=0; i<count; i++) {
|
||||
if (!ConsumeSample(samples)) return i;
|
||||
samples += 2;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
virtual bool stop() { return false; }
|
||||
virtual void flush() { return; }
|
||||
virtual bool loop() { return true; }
|
||||
|
||||
public:
|
||||
virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); }
|
||||
virtual bool RegisterStatusCB(AudioStatus::statusCBFn fn, void *data) { return cb.RegisterStatusCB(fn, data); }
|
||||
|
||||
protected:
|
||||
void MakeSampleStereo16(int16_t sample[2]) {
|
||||
// Mono to "stereo" conversion
|
||||
if (channels == 1)
|
||||
sample[RIGHTCHANNEL] = sample[LEFTCHANNEL];
|
||||
if (bps == 8) {
|
||||
// Upsample from unsigned 8 bits to signed 16 bits
|
||||
sample[LEFTCHANNEL] = (((int16_t)(sample[LEFTCHANNEL]&0xff)) - 128) << 8;
|
||||
sample[RIGHTCHANNEL] = (((int16_t)(sample[RIGHTCHANNEL]&0xff)) - 128) << 8;
|
||||
}
|
||||
};
|
||||
|
||||
inline int16_t Amplify(int16_t s) {
|
||||
int32_t v = (s * gainF2P6)>>6;
|
||||
if (v < -32767) return -32767;
|
||||
else if (v > 32767) return 32767;
|
||||
else return (int16_t)(v&0xffff);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint16_t hertz;
|
||||
uint8_t bps;
|
||||
uint8_t channels;
|
||||
uint8_t gainF2P6; // Fixed point 2.6
|
||||
|
||||
protected:
|
||||
AudioStatus cb;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
AudioOutputBuffer
|
||||
Adds additional bufferspace to the output chain
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 <Arduino.h>
|
||||
#include "AudioOutputBuffer.h"
|
||||
|
||||
AudioOutputBuffer::AudioOutputBuffer(int buffSizeSamples, AudioOutput *dest)
|
||||
{
|
||||
buffSize = buffSizeSamples;
|
||||
leftSample = (int16_t*)malloc(sizeof(int16_t) * buffSize);
|
||||
rightSample = (int16_t*)malloc(sizeof(int16_t) * buffSize);
|
||||
writePtr = 0;
|
||||
readPtr = 0;
|
||||
sink = dest;
|
||||
}
|
||||
|
||||
AudioOutputBuffer::~AudioOutputBuffer()
|
||||
{
|
||||
free(leftSample);
|
||||
free(rightSample);
|
||||
}
|
||||
|
||||
bool AudioOutputBuffer::SetRate(int hz)
|
||||
{
|
||||
return sink->SetRate(hz);
|
||||
}
|
||||
|
||||
bool AudioOutputBuffer::SetBitsPerSample(int bits)
|
||||
{
|
||||
return sink->SetBitsPerSample(bits);
|
||||
}
|
||||
|
||||
bool AudioOutputBuffer::SetChannels(int channels)
|
||||
{
|
||||
return sink->SetChannels(channels);
|
||||
}
|
||||
|
||||
bool AudioOutputBuffer::begin()
|
||||
{
|
||||
filled = false;
|
||||
return sink->begin();
|
||||
}
|
||||
|
||||
bool AudioOutputBuffer::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
// First, try and fill I2S...
|
||||
if (filled) {
|
||||
while (readPtr != writePtr) {
|
||||
int16_t s[2] = {leftSample[readPtr], rightSample[readPtr]};
|
||||
if (!sink->ConsumeSample(s)) break; // Can't stuff any more in I2S...
|
||||
readPtr = (readPtr + 1) % buffSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Now, do we have space for a new sample?
|
||||
int nextWritePtr = (writePtr + 1) % buffSize;
|
||||
if (nextWritePtr == readPtr) {
|
||||
filled = true;
|
||||
return false;
|
||||
}
|
||||
leftSample[writePtr] = sample[LEFTCHANNEL];
|
||||
rightSample[writePtr] = sample[RIGHTCHANNEL];
|
||||
writePtr = nextWritePtr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputBuffer::stop()
|
||||
{
|
||||
return sink->stop();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
AudioOutputBuffer
|
||||
Adds additional bufferspace to the output chain
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOOUTPUTBUFFER_H
|
||||
#define _AUDIOOUTPUTBUFFER_H
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
class AudioOutputBuffer : public AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutputBuffer(int bufferSizeSamples, AudioOutput *dest);
|
||||
virtual ~AudioOutputBuffer() override;
|
||||
virtual bool SetRate(int hz) override;
|
||||
virtual bool SetBitsPerSample(int bits) override;
|
||||
virtual bool SetChannels(int channels) override;
|
||||
virtual bool begin() override;
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual bool stop() override;
|
||||
|
||||
protected:
|
||||
AudioOutput *sink;
|
||||
int buffSize;
|
||||
int16_t *leftSample;
|
||||
int16_t *rightSample;
|
||||
int writePtr;
|
||||
int readPtr;
|
||||
bool filled;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
AudioOutputFilterBiquad
|
||||
Implements a Biquad filter
|
||||
|
||||
Copyright (C) 2012 Nigel Redmon
|
||||
Copyright (C) 2021 William Bérubé
|
||||
|
||||
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 <Arduino.h>
|
||||
#include "AudioOutputFilterBiquad.h"
|
||||
|
||||
AudioOutputFilterBiquad::AudioOutputFilterBiquad(AudioOutput *sink)
|
||||
{
|
||||
this->sink = sink;
|
||||
|
||||
type = bq_type_lowpass;
|
||||
a0 = 1.0;
|
||||
a1 = a2 = b1 = b2 = 0.0;
|
||||
Fc = 0.50;
|
||||
Q = 0.707;
|
||||
peakGain = 0.0;
|
||||
z1 = z2 = 0.0;
|
||||
}
|
||||
|
||||
AudioOutputFilterBiquad::AudioOutputFilterBiquad(int type, float Fc, float Q, float peakGain, AudioOutput *sink)
|
||||
{
|
||||
this->sink = sink;
|
||||
|
||||
SetBiquad(type, Fc, Q, peakGain);
|
||||
z1 = z2 = 0.0;
|
||||
}
|
||||
|
||||
AudioOutputFilterBiquad::~AudioOutputFilterBiquad() {}
|
||||
|
||||
bool AudioOutputFilterBiquad::SetRate(int hz)
|
||||
{
|
||||
return sink->SetRate(hz);
|
||||
}
|
||||
|
||||
bool AudioOutputFilterBiquad::SetBitsPerSample(int bits)
|
||||
{
|
||||
return sink->SetBitsPerSample(bits);
|
||||
}
|
||||
|
||||
bool AudioOutputFilterBiquad::SetChannels(int channels)
|
||||
{
|
||||
return sink->SetChannels(channels);
|
||||
}
|
||||
|
||||
bool AudioOutputFilterBiquad::SetGain(float gain)
|
||||
{
|
||||
return sink->SetGain(gain);
|
||||
}
|
||||
|
||||
void AudioOutputFilterBiquad::SetType(int type)
|
||||
{
|
||||
this->type = type;
|
||||
CalcBiquad();
|
||||
}
|
||||
|
||||
void AudioOutputFilterBiquad::SetFc(float Fc)
|
||||
{
|
||||
this->Fc = Fc;
|
||||
CalcBiquad();
|
||||
}
|
||||
|
||||
void AudioOutputFilterBiquad::SetQ(float Q)
|
||||
{
|
||||
this->Q = Q;
|
||||
CalcBiquad();
|
||||
}
|
||||
|
||||
void AudioOutputFilterBiquad::SetPeakGain(float peakGain)
|
||||
{
|
||||
this->peakGain = peakGain;
|
||||
CalcBiquad();
|
||||
}
|
||||
|
||||
void AudioOutputFilterBiquad::SetBiquad(int type, float Fc, float Q, float peakGain)
|
||||
{
|
||||
this->type = type;
|
||||
this->Fc = Fc;
|
||||
this->Q = Q;
|
||||
this->peakGain = peakGain;
|
||||
CalcBiquad();
|
||||
}
|
||||
|
||||
void AudioOutputFilterBiquad::CalcBiquad()
|
||||
{
|
||||
float norm;
|
||||
float V = pow(10, fabs(peakGain) / 20.0);
|
||||
float K = tan(M_PI * Fc);
|
||||
|
||||
switch (this->type) {
|
||||
case bq_type_lowpass:
|
||||
norm = 1 / (1 + K / Q + K * K);
|
||||
a0 = K * K * norm;
|
||||
a1 = 2 * a0;
|
||||
a2 = a0;
|
||||
b1 = 2 * (K * K - 1) * norm;
|
||||
b2 = (1 - K / Q + K * K) * norm;
|
||||
break;
|
||||
|
||||
case bq_type_highpass:
|
||||
norm = 1 / (1 + K / Q + K * K);
|
||||
a0 = 1 * norm;
|
||||
a1 = -2 * a0;
|
||||
a2 = a0;
|
||||
b1 = 2 * (K * K - 1) * norm;
|
||||
b2 = (1 - K / Q + K * K) * norm;
|
||||
break;
|
||||
|
||||
case bq_type_bandpass:
|
||||
norm = 1 / (1 + K / Q + K * K);
|
||||
a0 = K / Q * norm;
|
||||
a1 = 0;
|
||||
a2 = -a0;
|
||||
b1 = 2 * (K * K - 1) * norm;
|
||||
b2 = (1 - K / Q + K * K) * norm;
|
||||
break;
|
||||
|
||||
case bq_type_notch:
|
||||
norm = 1 / (1 + K / Q + K * K);
|
||||
a0 = (1 + K * K) * norm;
|
||||
a1 = 2 * (K * K - 1) * norm;
|
||||
a2 = a0;
|
||||
b1 = a1;
|
||||
b2 = (1 - K / Q + K * K) * norm;
|
||||
break;
|
||||
|
||||
case bq_type_peak:
|
||||
if (peakGain >= 0) { // boost
|
||||
norm = 1 / (1 + 1/Q * K + K * K);
|
||||
a0 = (1 + V/Q * K + K * K) * norm;
|
||||
a1 = 2 * (K * K - 1) * norm;
|
||||
a2 = (1 - V/Q * K + K * K) * norm;
|
||||
b1 = a1;
|
||||
b2 = (1 - 1/Q * K + K * K) * norm;
|
||||
} else { // cut
|
||||
norm = 1 / (1 + V/Q * K + K * K);
|
||||
a0 = (1 + 1/Q * K + K * K) * norm;
|
||||
a1 = 2 * (K * K - 1) * norm;
|
||||
a2 = (1 - 1/Q * K + K * K) * norm;
|
||||
b1 = a1;
|
||||
b2 = (1 - V/Q * K + K * K) * norm;
|
||||
}
|
||||
break;
|
||||
|
||||
case bq_type_lowshelf:
|
||||
if (peakGain >= 0) { // boost
|
||||
norm = 1 / (1 + sqrt(2) * K + K * K);
|
||||
a0 = (1 + sqrt(2*V) * K + V * K * K) * norm;
|
||||
a1 = 2 * (V * K * K - 1) * norm;
|
||||
a2 = (1 - sqrt(2*V) * K + V * K * K) * norm;
|
||||
b1 = 2 * (K * K - 1) * norm;
|
||||
b2 = (1 - sqrt(2) * K + K * K) * norm;
|
||||
}
|
||||
else { // cut
|
||||
norm = 1 / (1 + sqrt(2*V) * K + V * K * K);
|
||||
a0 = (1 + sqrt(2) * K + K * K) * norm;
|
||||
a1 = 2 * (K * K - 1) * norm;
|
||||
a2 = (1 - sqrt(2) * K + K * K) * norm;
|
||||
b1 = 2 * (V * K * K - 1) * norm;
|
||||
b2 = (1 - sqrt(2*V) * K + V * K * K) * norm;
|
||||
}
|
||||
break;
|
||||
|
||||
case bq_type_highshelf:
|
||||
if (peakGain >= 0) { // boost
|
||||
norm = 1 / (1 + sqrt(2) * K + K * K);
|
||||
a0 = (V + sqrt(2*V) * K + K * K) * norm;
|
||||
a1 = 2 * (K * K - V) * norm;
|
||||
a2 = (V - sqrt(2*V) * K + K * K) * norm;
|
||||
b1 = 2 * (K * K - 1) * norm;
|
||||
b2 = (1 - sqrt(2) * K + K * K) * norm;
|
||||
}
|
||||
else { // cut
|
||||
norm = 1 / (V + sqrt(2*V) * K + K * K);
|
||||
a0 = (1 + sqrt(2) * K + K * K) * norm;
|
||||
a1 = 2 * (K * K - 1) * norm;
|
||||
a2 = (1 - sqrt(2) * K + K * K) * norm;
|
||||
b1 = 2 * (K * K - V) * norm;
|
||||
b2 = (V - sqrt(2*V) * K + K * K) * norm;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
i_a0 = a0 * BQ_DECAL;
|
||||
i_a1 = a1 * BQ_DECAL;
|
||||
i_a2 = a2 * BQ_DECAL;
|
||||
|
||||
i_b1 = b1 * BQ_DECAL;
|
||||
i_b2 = b2 * BQ_DECAL;
|
||||
|
||||
i_lz1 = i_rz1 = z1 * BQ_DECAL;
|
||||
i_lz2 = i_rz2 = z2 * BQ_DECAL;
|
||||
|
||||
i_Fc = Fc * BQ_DECAL;
|
||||
i_Q = Q * BQ_DECAL;
|
||||
i_peakGain = peakGain * BQ_DECAL;
|
||||
}
|
||||
|
||||
bool AudioOutputFilterBiquad::begin()
|
||||
{
|
||||
return sink->begin();
|
||||
}
|
||||
|
||||
bool AudioOutputFilterBiquad::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
|
||||
int32_t leftSample = (sample[LEFTCHANNEL] << BQ_SHIFT) / 2;
|
||||
int32_t rightSample = (sample[RIGHTCHANNEL] << BQ_SHIFT) / 2;
|
||||
|
||||
int64_t leftOutput = ((leftSample * i_a0) >> BQ_SHIFT) + i_lz1;
|
||||
i_lz1 = ((leftSample * i_a1) >> BQ_SHIFT) + i_lz2 - ((i_b1 * leftOutput) >> BQ_SHIFT);
|
||||
i_lz2 = ((leftSample * i_a2) >> BQ_SHIFT) - ((i_b2 * leftOutput) >> BQ_SHIFT);
|
||||
|
||||
int64_t rightOutput = ((rightSample * i_a0) >> BQ_SHIFT) + i_rz1;
|
||||
i_rz1 = ((rightSample * i_a1) >> BQ_SHIFT) + i_rz2 - ((i_b1 * rightOutput) >> BQ_SHIFT);
|
||||
i_rz2 = ((rightSample * i_a2) >> BQ_SHIFT) - ((i_b2 * rightOutput) >> BQ_SHIFT);
|
||||
|
||||
int16_t out[2];
|
||||
out[LEFTCHANNEL] = (int16_t)(leftOutput >> BQ_SHIFT);
|
||||
out[RIGHTCHANNEL] = (int16_t)(rightOutput >> BQ_SHIFT);
|
||||
|
||||
return sink->ConsumeSample(out);
|
||||
}
|
||||
|
||||
bool AudioOutputFilterBiquad::stop()
|
||||
{
|
||||
return sink->stop();
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
AudioOutputFilterBiquad
|
||||
Implements a Biquad filter
|
||||
|
||||
Copyright (C) 2012 Nigel Redmon
|
||||
Copyright (C) 2021 William Bérubé
|
||||
|
||||
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 _AudioOutputFilterBiquad_H
|
||||
#define _AudioOutputFilterBiquad_H
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
#define BQ_SHIFT 16
|
||||
#define BQ_DECAL 65536
|
||||
|
||||
enum {
|
||||
bq_type_lowpass = 0,
|
||||
bq_type_highpass,
|
||||
bq_type_bandpass,
|
||||
bq_type_notch,
|
||||
bq_type_peak,
|
||||
bq_type_lowshelf,
|
||||
bq_type_highshelf
|
||||
};
|
||||
|
||||
class AudioOutputFilterBiquad : public AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutputFilterBiquad(AudioOutput *sink);
|
||||
AudioOutputFilterBiquad(int type, float Fc, float Q, float peakGain, AudioOutput *sink);
|
||||
virtual ~AudioOutputFilterBiquad() override;
|
||||
virtual bool SetRate(int hz) override;
|
||||
virtual bool SetBitsPerSample(int bits) override;
|
||||
virtual bool SetChannels(int chan) override;
|
||||
virtual bool SetGain(float f) override;
|
||||
virtual bool begin() override;
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual bool stop() override;
|
||||
|
||||
private:
|
||||
void SetType(int type);
|
||||
void SetFc(float Fc);
|
||||
void SetQ(float Q);
|
||||
void SetPeakGain(float peakGain);
|
||||
void SetBiquad(int type, float Fc, float Q, float peakGain);
|
||||
|
||||
protected:
|
||||
AudioOutput *sink;
|
||||
int buffSize;
|
||||
int16_t *leftSample;
|
||||
int16_t *rightSample;
|
||||
int writePtr;
|
||||
int readPtr;
|
||||
bool filled;
|
||||
int type;
|
||||
void CalcBiquad();
|
||||
int64_t i_a0, i_a1, i_a2, i_b1, i_b2;
|
||||
int64_t i_Fc, i_Q, i_peakGain;
|
||||
int64_t i_lz1, i_lz2, i_rz1, i_rz2;
|
||||
float a0, a1, a2, b1, b2;
|
||||
float Fc, Q, peakGain;
|
||||
float z1, z2;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
AudioOutputFilter
|
||||
Implements a user-defined FIR on a passthrough
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 <Arduino.h>
|
||||
#include "AudioOutputFilterDecimate.h"
|
||||
|
||||
AudioOutputFilterDecimate::AudioOutputFilterDecimate(uint8_t taps, const int16_t *tap, int num, int den, AudioOutput *sink)
|
||||
{
|
||||
this->sink = sink;
|
||||
|
||||
// The filter state. Passed in TAPS must be available throughout object lifetime
|
||||
this->taps = taps;
|
||||
this->tap = (int16_t*)malloc(sizeof(int16_t) * taps);
|
||||
memcpy_P(this->tap, tap, sizeof(int16_t) * taps);
|
||||
this->hist[0] = (int16_t*)malloc(sizeof(int16_t) * taps);
|
||||
memset(this->hist[0], 0, sizeof(int16_t) * taps);
|
||||
this->hist[1] = (int16_t*)malloc(sizeof(int16_t) * taps);
|
||||
memset(this->hist[1], 0, sizeof(int16_t) * taps);
|
||||
this->idx = 0;
|
||||
|
||||
// Decimator numerator and denominator with an error signal. Not great, but fast and simple
|
||||
this->num = num;
|
||||
this->den = den;
|
||||
this->err = 0;
|
||||
}
|
||||
|
||||
AudioOutputFilterDecimate::~AudioOutputFilterDecimate()
|
||||
{
|
||||
free(hist[1]);
|
||||
free(hist[0]);
|
||||
free(tap);
|
||||
}
|
||||
|
||||
bool AudioOutputFilterDecimate::SetRate(int hz)
|
||||
{
|
||||
// Modify input frequency to account for decimation
|
||||
hz *= den;
|
||||
hz /= num;
|
||||
return sink->SetRate(hz);
|
||||
}
|
||||
|
||||
bool AudioOutputFilterDecimate::SetBitsPerSample(int bits)
|
||||
{
|
||||
return sink->SetBitsPerSample(bits);
|
||||
}
|
||||
|
||||
bool AudioOutputFilterDecimate::SetChannels(int channels)
|
||||
{
|
||||
return sink->SetChannels(channels);
|
||||
}
|
||||
|
||||
bool AudioOutputFilterDecimate::SetGain(float gain)
|
||||
{
|
||||
return sink->SetGain(gain);
|
||||
}
|
||||
|
||||
bool AudioOutputFilterDecimate::begin()
|
||||
{
|
||||
return sink->begin();
|
||||
}
|
||||
|
||||
bool AudioOutputFilterDecimate::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
// Store the data samples in history always
|
||||
hist[LEFTCHANNEL][idx] = sample[LEFTCHANNEL];
|
||||
hist[RIGHTCHANNEL][idx] = sample[RIGHTCHANNEL];
|
||||
idx++;
|
||||
if (idx == taps) idx = 0;
|
||||
|
||||
// Only output if the error signal says we're ready to decimate. This simplistic way might give some aliasing noise
|
||||
err += num;
|
||||
if (err >= den) {
|
||||
err -= den;
|
||||
// Need to output a sample, so actually calculate the filter at this point in time
|
||||
// Smarter might actually shift the history by the fractional remainder or take two filters and interpolate
|
||||
int32_t accL = 0;
|
||||
int32_t accR = 0;
|
||||
int index = idx;
|
||||
for (size_t i=0; i < taps; i++) {
|
||||
index = index != 0 ? index-1 : taps-1;
|
||||
accL += (int32_t)hist[LEFTCHANNEL][index] * tap[i];
|
||||
accR += (int32_t)hist[RIGHTCHANNEL][index] * tap[i];
|
||||
};
|
||||
int16_t out[2];
|
||||
out[LEFTCHANNEL] = accL >> 16;
|
||||
out[RIGHTCHANNEL] = accR >> 16;
|
||||
return sink->ConsumeSample(out);
|
||||
}
|
||||
return true; // Nothing to do here...
|
||||
}
|
||||
|
||||
bool AudioOutputFilterDecimate::stop()
|
||||
{
|
||||
return sink->stop();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
AudioOutputFilterDecimate
|
||||
Implements a user-defined FIR on a passthrough w/rational decimation
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOOUTPUTFILTERDECIMATE_H
|
||||
#define _AUDIOOUTPUTFILTERDECIMATE_H
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
class AudioOutputFilterDecimate : public AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutputFilterDecimate(uint8_t taps, const int16_t *tap, int num, int den, AudioOutput *sink);
|
||||
virtual ~AudioOutputFilterDecimate() override;
|
||||
virtual bool SetRate(int hz) override;
|
||||
virtual bool SetBitsPerSample(int bits) override;
|
||||
virtual bool SetChannels(int chan) override;
|
||||
virtual bool SetGain(float f) override;
|
||||
virtual bool begin() override;
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual bool stop() override;
|
||||
|
||||
protected:
|
||||
AudioOutput *sink;
|
||||
uint8_t taps;
|
||||
int16_t *tap;
|
||||
int16_t *hist[2];
|
||||
int idx;
|
||||
int num;
|
||||
int den;
|
||||
int err;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
313
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioOutputI2S.cpp
Normal file
313
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioOutputI2S.cpp
Normal file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
AudioOutputI2S
|
||||
Base class for I2S interface port
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 <Arduino.h>
|
||||
#ifdef ESP32
|
||||
#include "driver/i2s.h"
|
||||
#elif defined(ARDUINO_ARCH_RP2040) || defined(ESP8266)
|
||||
#include <I2S.h>
|
||||
#endif
|
||||
#include "AudioOutputI2S.h"
|
||||
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int use_apll)
|
||||
{
|
||||
this->portNo = port;
|
||||
this->i2sOn = false;
|
||||
this->dma_buf_count = dma_buf_count;
|
||||
if (output_mode != EXTERNAL_I2S && output_mode != INTERNAL_DAC && output_mode != INTERNAL_PDM) {
|
||||
output_mode = EXTERNAL_I2S;
|
||||
}
|
||||
this->output_mode = output_mode;
|
||||
this->use_apll = use_apll;
|
||||
|
||||
//set defaults
|
||||
mono = false;
|
||||
bps = 16;
|
||||
channels = 2;
|
||||
hertz = 44100;
|
||||
bclkPin = 26;
|
||||
wclkPin = 25;
|
||||
doutPin = 22;
|
||||
SetGain(1.0);
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetPinout()
|
||||
{
|
||||
#ifdef ESP32
|
||||
if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM)
|
||||
return false; // Not allowed
|
||||
|
||||
i2s_pin_config_t pins = {
|
||||
.bck_io_num = bclkPin,
|
||||
.ws_io_num = wclkPin,
|
||||
.data_out_num = doutPin,
|
||||
.data_in_num = I2S_PIN_NO_CHANGE};
|
||||
i2s_set_pin((i2s_port_t)portNo, &pins);
|
||||
return true;
|
||||
#else
|
||||
(void)bclkPin;
|
||||
(void)wclkPin;
|
||||
(void)doutPin;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetPinout(int bclk, int wclk, int dout)
|
||||
{
|
||||
bclkPin = bclk;
|
||||
wclkPin = wclk;
|
||||
doutPin = dout;
|
||||
if (i2sOn)
|
||||
return SetPinout();
|
||||
|
||||
return true;
|
||||
}
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
AudioOutputI2S::AudioOutputI2S(long sampleRate, pin_size_t sck, pin_size_t data) {
|
||||
i2sOn = false;
|
||||
mono = false;
|
||||
bps = 16;
|
||||
channels = 2;
|
||||
hertz = sampleRate;
|
||||
bclkPin = sck;
|
||||
doutPin = data;
|
||||
SetGain(1.0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
AudioOutputI2S::~AudioOutputI2S()
|
||||
{
|
||||
#ifdef ESP32
|
||||
if (i2sOn) {
|
||||
audioLogger->printf("UNINSTALL I2S\n");
|
||||
i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
if (i2sOn)
|
||||
i2s_end();
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
stop();
|
||||
#endif
|
||||
i2sOn = false;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetRate(int hz)
|
||||
{
|
||||
// TODO - have a list of allowable rates from constructor, check them
|
||||
this->hertz = hz;
|
||||
if (i2sOn)
|
||||
{
|
||||
#ifdef ESP32
|
||||
i2s_set_sample_rates((i2s_port_t)portNo, AdjustI2SRate(hz));
|
||||
#elif defined(ESP8266)
|
||||
i2s_set_rate(AdjustI2SRate(hz));
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
I2S.setFrequency(hz);
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetBitsPerSample(int bits)
|
||||
{
|
||||
if ( (bits != 16) && (bits != 8) ) return false;
|
||||
this->bps = bits;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetChannels(int channels)
|
||||
{
|
||||
if ( (channels < 1) || (channels > 2) ) return false;
|
||||
this->channels = channels;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetOutputModeMono(bool mono)
|
||||
{
|
||||
this->mono = mono;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::begin(bool txDAC)
|
||||
{
|
||||
#ifdef ESP32
|
||||
if (!i2sOn)
|
||||
{
|
||||
if (use_apll == APLL_AUTO)
|
||||
{
|
||||
// don't use audio pll on buggy rev0 chips
|
||||
use_apll = APLL_DISABLE;
|
||||
esp_chip_info_t out_info;
|
||||
esp_chip_info(&out_info);
|
||||
if (out_info.revision > 0)
|
||||
{
|
||||
use_apll = APLL_ENABLE;
|
||||
}
|
||||
}
|
||||
|
||||
i2s_mode_t mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
|
||||
if (output_mode == INTERNAL_DAC)
|
||||
{
|
||||
mode = (i2s_mode_t)(mode | I2S_MODE_DAC_BUILT_IN);
|
||||
}
|
||||
else if (output_mode == INTERNAL_PDM)
|
||||
{
|
||||
mode = (i2s_mode_t)(mode | I2S_MODE_PDM);
|
||||
}
|
||||
|
||||
i2s_comm_format_t comm_fmt = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB);
|
||||
if (output_mode == INTERNAL_DAC)
|
||||
{
|
||||
comm_fmt = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB;
|
||||
}
|
||||
|
||||
i2s_config_t i2s_config_dac = {
|
||||
.mode = mode,
|
||||
.sample_rate = 44100,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
.communication_format = comm_fmt,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority
|
||||
.dma_buf_count = dma_buf_count,
|
||||
.dma_buf_len = 64,
|
||||
.use_apll = use_apll // Use audio PLL
|
||||
};
|
||||
audioLogger->printf("+%d %p\n", portNo, &i2s_config_dac);
|
||||
if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_dac, 0, NULL) != ESP_OK)
|
||||
{
|
||||
audioLogger->println("ERROR: Unable to install I2S drives\n");
|
||||
}
|
||||
if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM)
|
||||
{
|
||||
i2s_set_pin((i2s_port_t)portNo, NULL);
|
||||
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPinout();
|
||||
}
|
||||
i2s_zero_dma_buffer((i2s_port_t)portNo);
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
(void)dma_buf_count;
|
||||
(void)use_apll;
|
||||
if (!i2sOn)
|
||||
{
|
||||
orig_bck = READ_PERI_REG(PERIPHS_IO_MUX_MTDO_U);
|
||||
orig_ws = READ_PERI_REG(PERIPHS_IO_MUX_GPIO2_U);
|
||||
#ifdef I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS
|
||||
if (!i2s_rxtxdrive_begin(false, true, false, txDAC)) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!i2s_rxtx_begin(false, true)) {
|
||||
return false;
|
||||
}
|
||||
if (!txDAC) {
|
||||
audioLogger->printf_P(PSTR("I2SNoDAC: esp8266 arduino core should be upgraded to avoid conflicts with SPI\n"));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
(void)txDAC;
|
||||
if (!i2sOn) {
|
||||
I2S.setBCLK(bclkPin);
|
||||
I2S.setDOUT(doutPin);
|
||||
I2S.begin(hertz);
|
||||
}
|
||||
|
||||
#endif
|
||||
i2sOn = true;
|
||||
SetRate(hertz); // Default
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
|
||||
//return if we haven't called ::begin yet
|
||||
if (!i2sOn)
|
||||
return false;
|
||||
|
||||
int16_t ms[2];
|
||||
|
||||
ms[0] = sample[0];
|
||||
ms[1] = sample[1];
|
||||
MakeSampleStereo16( ms );
|
||||
|
||||
if (this->mono) {
|
||||
// Average the two samples and overwrite
|
||||
int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL];
|
||||
ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff;
|
||||
}
|
||||
#ifdef ESP32
|
||||
uint32_t s32;
|
||||
if (output_mode == INTERNAL_DAC)
|
||||
{
|
||||
int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000;
|
||||
int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000;
|
||||
s32 = (r << 16) | (l & 0xffff);
|
||||
}
|
||||
else
|
||||
{
|
||||
s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
|
||||
}
|
||||
return i2s_write_bytes((i2s_port_t)portNo, (const char *)&s32, sizeof(uint32_t), 0);
|
||||
#elif defined(ESP8266)
|
||||
uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
|
||||
return i2s_write_sample_nb(s32); // If we can't store it, return false. OTW true
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
return !!I2S.write((void*)ms, 4);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioOutputI2S::flush()
|
||||
{
|
||||
#ifdef ESP32
|
||||
// makes sure that all stored DMA samples are consumed / played
|
||||
int buffersize = 64 * this->dma_buf_count;
|
||||
int16_t samples[2] = {0x0, 0x0};
|
||||
for (int i = 0; i < buffersize; i++)
|
||||
{
|
||||
while (!ConsumeSample(samples))
|
||||
{
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
I2S.flush();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::stop()
|
||||
{
|
||||
if (!i2sOn)
|
||||
return false;
|
||||
|
||||
#ifdef ESP32
|
||||
i2s_zero_dma_buffer((i2s_port_t)portNo);
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
I2S.end();
|
||||
#endif
|
||||
i2sOn = false;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
AudioOutputI2S
|
||||
Base class for an I2S output port
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
#if defined(__SAMD51__)
|
||||
#include "I2C.h"
|
||||
|
||||
#endif
|
||||
|
||||
class AudioOutputI2S : public AudioOutput
|
||||
{
|
||||
public:
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE);
|
||||
bool SetPinout(int bclkPin, int wclkPin, int doutPin);
|
||||
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
|
||||
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
AudioOutputI2S(long sampleRate = 44100, pin_size_t sck = 26, pin_size_t data = 28);
|
||||
#endif
|
||||
virtual ~AudioOutputI2S() override;
|
||||
virtual bool SetRate(int hz) override;
|
||||
virtual bool SetBitsPerSample(int bits) override;
|
||||
virtual bool SetChannels(int channels) override;
|
||||
virtual bool begin() override { return begin(true); }
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual void flush() override;
|
||||
virtual bool stop() override;
|
||||
|
||||
bool begin(bool txDAC);
|
||||
bool SetOutputModeMono(bool mono); // Force mono output no matter the input
|
||||
|
||||
protected:
|
||||
bool SetPinout();
|
||||
virtual int AdjustI2SRate(int hz) { return hz; }
|
||||
uint8_t portNo;
|
||||
int output_mode;
|
||||
bool mono;
|
||||
bool i2sOn;
|
||||
int dma_buf_count;
|
||||
int use_apll;
|
||||
// We can restore the old values and free up these pins when in NoDAC mode
|
||||
uint32_t orig_bck;
|
||||
uint32_t orig_ws;
|
||||
|
||||
uint8_t bclkPin;
|
||||
uint8_t wclkPin;
|
||||
uint8_t doutPin;
|
||||
};
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
AudioOutputI2SNoDAC
|
||||
Audio player using SW delta-sigma to generate "analog" on I2S data
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 <Arduino.h>
|
||||
#ifdef ESP32
|
||||
#include "driver/i2s.h"
|
||||
#elif defined(ARDUINO_ARCH_RP2040) || defined(ESP8266)
|
||||
#include <I2S.h>
|
||||
#endif
|
||||
#include "AudioOutputI2SNoDAC.h"
|
||||
|
||||
|
||||
AudioOutputI2SNoDAC::AudioOutputI2SNoDAC(int port) : AudioOutputI2S(port, false)
|
||||
{
|
||||
SetOversampling(32);
|
||||
lastSamp = 0;
|
||||
cumErr = 0;
|
||||
#ifdef ESP8266
|
||||
WRITE_PERI_REG(PERIPHS_IO_MUX_MTDO_U, orig_bck);
|
||||
WRITE_PERI_REG(PERIPHS_IO_MUX_GPIO2_U, orig_ws);
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioOutputI2SNoDAC::~AudioOutputI2SNoDAC()
|
||||
{
|
||||
}
|
||||
|
||||
bool AudioOutputI2SNoDAC::SetOversampling(int os) {
|
||||
if (os % 32) return false; // Only Nx32 oversampling supported
|
||||
if (os > 256) return false; // Don't be silly now!
|
||||
if (os < 32) return false; // Nothing under 32 allowed
|
||||
|
||||
oversample = os;
|
||||
return SetRate(hertz);
|
||||
}
|
||||
|
||||
void AudioOutputI2SNoDAC::DeltaSigma(int16_t sample[2], uint32_t dsBuff[8])
|
||||
{
|
||||
// Not shift 8 because addition takes care of one mult x 2
|
||||
int32_t sum = (((int32_t)sample[0]) + ((int32_t)sample[1])) >> 1;
|
||||
fixed24p8_t newSamp = ( (int32_t)Amplify(sum) ) << 8;
|
||||
|
||||
int oversample32 = oversample / 32;
|
||||
// How much the comparison signal changes each oversample step
|
||||
fixed24p8_t diffPerStep = (newSamp - lastSamp) >> (4 + oversample32);
|
||||
|
||||
// Don't need lastSamp anymore, store this one for next round
|
||||
lastSamp = newSamp;
|
||||
|
||||
for (int j = 0; j < oversample32; j++) {
|
||||
uint32_t bits = 0; // The bits we convert the sample into, MSB to go on the wire first
|
||||
|
||||
for (int i = 32; i > 0; i--) {
|
||||
bits = bits << 1;
|
||||
if (cumErr < 0) {
|
||||
bits |= 1;
|
||||
cumErr += fixedPosValue - newSamp;
|
||||
} else {
|
||||
// Bits[0] = 0 handled already by left shift
|
||||
cumErr -= fixedPosValue + newSamp;
|
||||
}
|
||||
newSamp += diffPerStep; // Move the reference signal towards destination
|
||||
}
|
||||
dsBuff[j] = bits;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioOutputI2SNoDAC::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
int16_t ms[2];
|
||||
ms[0] = sample[0];
|
||||
ms[1] = sample[1];
|
||||
MakeSampleStereo16( ms );
|
||||
|
||||
// Make delta-sigma filled buffer
|
||||
uint32_t dsBuff[8];
|
||||
DeltaSigma(ms, dsBuff);
|
||||
|
||||
// Either send complete pulse stream or nothing
|
||||
#ifdef ESP32
|
||||
if (!i2s_write_bytes((i2s_port_t)portNo, (const char *)dsBuff, sizeof(uint32_t) * (oversample/32), 0))
|
||||
return false;
|
||||
#elif defined(ESP8266)
|
||||
if (!i2s_write_sample_nb(dsBuff[0])) return false; // No room at the inn
|
||||
// At this point we've sent in first of possibly 8 32-bits, need to send
|
||||
// remaining ones even if they block.
|
||||
for (int i = 32; i < oversample; i+=32)
|
||||
i2s_write_sample( dsBuff[i / 32]);
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
int16_t *p = (int16_t *) dsBuff;
|
||||
for (int i = 0; i < oversample / 16; i++) {
|
||||
I2S.write(*(p++));
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
AudioOutputI2SNoDAC
|
||||
Audio player using SW delta-sigma to generate "analog" on I2S data
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AudioOutputI2S.h"
|
||||
|
||||
class AudioOutputI2SNoDAC : public AudioOutputI2S
|
||||
{
|
||||
public:
|
||||
AudioOutputI2SNoDAC(int port = 0);
|
||||
virtual ~AudioOutputI2SNoDAC() override;
|
||||
virtual bool begin() override { return AudioOutputI2S::begin(false); }
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
|
||||
bool SetOversampling(int os);
|
||||
|
||||
protected:
|
||||
virtual int AdjustI2SRate(int hz) override { return hz * oversample/32; }
|
||||
uint8_t oversample;
|
||||
void DeltaSigma(int16_t sample[2], uint32_t dsBuff[4]);
|
||||
typedef int32_t fixed24p8_t;
|
||||
enum {fixedPosValue=0x007fff00}; /* 24.8 of max-signed-int */
|
||||
fixed24p8_t lastSamp; // Last sample value
|
||||
fixed24p8_t cumErr; // Running cumulative error since time began
|
||||
};
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
AudioOutputI2S
|
||||
Base class for I2S interface port
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 <Arduino.h>
|
||||
#ifdef ESP32
|
||||
#include "driver/i2s.h"
|
||||
#elif defined(ARDUINO_ARCH_RP2040) || defined(ESP8266)
|
||||
#include <I2S.h>
|
||||
#endif
|
||||
#include "AudioOutputI2S.h"
|
||||
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
AudioOutputI2S::AudioOutputI2S(int port, int output_mode, int dma_buf_count, int use_apll)
|
||||
{
|
||||
this->portNo = port;
|
||||
this->i2sOn = false;
|
||||
this->dma_buf_count = dma_buf_count;
|
||||
if (output_mode != EXTERNAL_I2S && output_mode != INTERNAL_DAC && output_mode != INTERNAL_PDM) {
|
||||
output_mode = EXTERNAL_I2S;
|
||||
}
|
||||
this->output_mode = output_mode;
|
||||
this->use_apll = use_apll;
|
||||
|
||||
//set defaults
|
||||
mono = false;
|
||||
bps = 16;
|
||||
channels = 2;
|
||||
hertz = 44100;
|
||||
bclkPin = 26;
|
||||
wclkPin = 25;
|
||||
doutPin = 22;
|
||||
SetGain(1.0);
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetPinout()
|
||||
{
|
||||
#ifdef ESP32
|
||||
if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM)
|
||||
return false; // Not allowed
|
||||
|
||||
i2s_pin_config_t pins = {
|
||||
.bck_io_num = bclkPin,
|
||||
.ws_io_num = wclkPin,
|
||||
.data_out_num = doutPin,
|
||||
.data_in_num = I2S_PIN_NO_CHANGE};
|
||||
i2s_set_pin((i2s_port_t)portNo, &pins);
|
||||
return true;
|
||||
#else
|
||||
(void)bclkPin;
|
||||
(void)wclkPin;
|
||||
(void)doutPin;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetPinout(int bclk, int wclk, int dout)
|
||||
{
|
||||
bclkPin = bclk;
|
||||
wclkPin = wclk;
|
||||
doutPin = dout;
|
||||
if (i2sOn)
|
||||
return SetPinout();
|
||||
|
||||
return true;
|
||||
}
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
AudioOutputI2S::AudioOutputI2S(long sampleRate, pin_size_t sck, pin_size_t data) {
|
||||
i2sOn = false;
|
||||
mono = false;
|
||||
bps = 16;
|
||||
channels = 2;
|
||||
hertz = sampleRate;
|
||||
bclkPin = sck;
|
||||
doutPin = data;
|
||||
SetGain(1.0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
AudioOutputI2S::~AudioOutputI2S()
|
||||
{
|
||||
#ifdef ESP32
|
||||
if (i2sOn) {
|
||||
audioLogger->printf("UNINSTALL I2S\n");
|
||||
i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
if (i2sOn)
|
||||
i2s_end();
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
stop();
|
||||
#endif
|
||||
i2sOn = false;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetRate(int hz)
|
||||
{
|
||||
// TODO - have a list of allowable rates from constructor, check them
|
||||
this->hertz = hz;
|
||||
if (i2sOn)
|
||||
{
|
||||
#ifdef ESP32
|
||||
i2s_set_sample_rates((i2s_port_t)portNo, AdjustI2SRate(hz));
|
||||
#elif defined(ESP8266)
|
||||
i2s_set_rate(AdjustI2SRate(hz));
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
I2S.setFrequency(hz);
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetBitsPerSample(int bits)
|
||||
{
|
||||
if ( (bits != 16) && (bits != 8) ) return false;
|
||||
this->bps = bits;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetChannels(int channels)
|
||||
{
|
||||
if ( (channels < 1) || (channels > 2) ) return false;
|
||||
this->channels = channels;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::SetOutputModeMono(bool mono)
|
||||
{
|
||||
this->mono = mono;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::begin(bool txDAC)
|
||||
{
|
||||
#ifdef ESP32
|
||||
if (!i2sOn)
|
||||
{
|
||||
if (use_apll == APLL_AUTO)
|
||||
{
|
||||
// don't use audio pll on buggy rev0 chips
|
||||
use_apll = APLL_DISABLE;
|
||||
esp_chip_info_t out_info;
|
||||
esp_chip_info(&out_info);
|
||||
if (out_info.revision > 0)
|
||||
{
|
||||
use_apll = APLL_ENABLE;
|
||||
}
|
||||
}
|
||||
|
||||
i2s_mode_t mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
|
||||
if (output_mode == INTERNAL_DAC)
|
||||
{
|
||||
mode = (i2s_mode_t)(mode | I2S_MODE_DAC_BUILT_IN);
|
||||
}
|
||||
else if (output_mode == INTERNAL_PDM)
|
||||
{
|
||||
mode = (i2s_mode_t)(mode | I2S_MODE_PDM);
|
||||
}
|
||||
|
||||
i2s_comm_format_t comm_fmt = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB);
|
||||
if (output_mode == INTERNAL_DAC)
|
||||
{
|
||||
comm_fmt = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB;
|
||||
}
|
||||
|
||||
i2s_config_t i2s_config_dac = {
|
||||
.mode = mode,
|
||||
.sample_rate = 44100,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
.communication_format = comm_fmt,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority
|
||||
.dma_buf_count = dma_buf_count,
|
||||
.dma_buf_len = 64,
|
||||
.use_apll = use_apll // Use audio PLL
|
||||
};
|
||||
audioLogger->printf("+%d %p\n", portNo, &i2s_config_dac);
|
||||
if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_dac, 0, NULL) != ESP_OK)
|
||||
{
|
||||
audioLogger->println("ERROR: Unable to install I2S drives\n");
|
||||
}
|
||||
if (output_mode == INTERNAL_DAC || output_mode == INTERNAL_PDM)
|
||||
{
|
||||
i2s_set_pin((i2s_port_t)portNo, NULL);
|
||||
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPinout();
|
||||
}
|
||||
i2s_zero_dma_buffer((i2s_port_t)portNo);
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
(void)dma_buf_count;
|
||||
(void)use_apll;
|
||||
if (!i2sOn)
|
||||
{
|
||||
orig_bck = READ_PERI_REG(PERIPHS_IO_MUX_MTDO_U);
|
||||
orig_ws = READ_PERI_REG(PERIPHS_IO_MUX_GPIO2_U);
|
||||
#ifdef I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS
|
||||
if (!i2s_rxtxdrive_begin(false, true, false, txDAC)) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!i2s_rxtx_begin(false, true)) {
|
||||
return false;
|
||||
}
|
||||
if (!txDAC) {
|
||||
audioLogger->printf_P(PSTR("I2SNoDAC: esp8266 arduino core should be upgraded to avoid conflicts with SPI\n"));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
(void)txDAC;
|
||||
if (!i2sOn) {
|
||||
I2S.setBCLK(bclkPin);
|
||||
I2S.setDOUT(doutPin);
|
||||
I2S.begin(hertz);
|
||||
}
|
||||
|
||||
#endif
|
||||
i2sOn = true;
|
||||
SetRate(hertz); // Default
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
|
||||
//return if we haven't called ::begin yet
|
||||
if (!i2sOn)
|
||||
return false;
|
||||
|
||||
int16_t ms[2];
|
||||
|
||||
ms[0] = sample[0];
|
||||
ms[1] = sample[1];
|
||||
MakeSampleStereo16( ms );
|
||||
|
||||
if (this->mono) {
|
||||
// Average the two samples and overwrite
|
||||
int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL];
|
||||
ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff;
|
||||
}
|
||||
#ifdef ESP32
|
||||
uint32_t s32;
|
||||
if (output_mode == INTERNAL_DAC)
|
||||
{
|
||||
int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000;
|
||||
int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000;
|
||||
s32 = (r << 16) | (l & 0xffff);
|
||||
}
|
||||
else
|
||||
{
|
||||
s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
|
||||
}
|
||||
return i2s_write_bytes((i2s_port_t)portNo, (const char *)&s32, sizeof(uint32_t), 0);
|
||||
#elif defined(ESP8266)
|
||||
uint32_t s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
|
||||
return i2s_write_sample_nb(s32); // If we can't store it, return false. OTW true
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
return !!I2S.write((void*)ms, 4);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioOutputI2S::flush()
|
||||
{
|
||||
#ifdef ESP32
|
||||
// makes sure that all stored DMA samples are consumed / played
|
||||
int buffersize = 64 * this->dma_buf_count;
|
||||
int16_t samples[2] = {0x0, 0x0};
|
||||
for (int i = 0; i < buffersize; i++)
|
||||
{
|
||||
while (!ConsumeSample(samples))
|
||||
{
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
I2S.flush();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AudioOutputI2S::stop()
|
||||
{
|
||||
if (!i2sOn)
|
||||
return false;
|
||||
|
||||
#ifdef ESP32
|
||||
i2s_zero_dma_buffer((i2s_port_t)portNo);
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
I2S.end();
|
||||
#endif
|
||||
i2sOn = false;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
AudioOutputI2S
|
||||
Base class for an I2S output port
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
class AudioOutputI2S : public AudioOutput
|
||||
{
|
||||
public:
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE);
|
||||
bool SetPinout(int bclkPin, int wclkPin, int doutPin);
|
||||
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
|
||||
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
AudioOutputI2S(long sampleRate = 44100, pin_size_t sck = 26, pin_size_t data = 28);
|
||||
#endif
|
||||
virtual ~AudioOutputI2S() override;
|
||||
virtual bool SetRate(int hz) override;
|
||||
virtual bool SetBitsPerSample(int bits) override;
|
||||
virtual bool SetChannels(int channels) override;
|
||||
virtual bool begin() override { return begin(true); }
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual void flush() override;
|
||||
virtual bool stop() override;
|
||||
|
||||
bool begin(bool txDAC);
|
||||
bool SetOutputModeMono(bool mono); // Force mono output no matter the input
|
||||
|
||||
protected:
|
||||
bool SetPinout();
|
||||
virtual int AdjustI2SRate(int hz) { return hz; }
|
||||
uint8_t portNo;
|
||||
int output_mode;
|
||||
bool mono;
|
||||
bool i2sOn;
|
||||
int dma_buf_count;
|
||||
int use_apll;
|
||||
// We can restore the old values and free up these pins when in NoDAC mode
|
||||
uint32_t orig_bck;
|
||||
uint32_t orig_ws;
|
||||
|
||||
uint8_t bclkPin;
|
||||
uint8_t wclkPin;
|
||||
uint8_t doutPin;
|
||||
};
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
AudioOutputMixer
|
||||
Simple mixer which can combine multiple inputs to a single output stream
|
||||
|
||||
Copyright (C) 2018 Earle F. Philhower, III
|
||||
|
||||
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 <Arduino.h>
|
||||
#include "AudioOutputMixer.h"
|
||||
|
||||
AudioOutputMixerStub::AudioOutputMixerStub(AudioOutputMixer *sink, int id) : AudioOutput()
|
||||
{
|
||||
this->id = id;
|
||||
this->parent = sink;
|
||||
SetGain(1.0);
|
||||
}
|
||||
|
||||
AudioOutputMixerStub::~AudioOutputMixerStub()
|
||||
{
|
||||
parent->RemoveInput(id);
|
||||
}
|
||||
|
||||
bool AudioOutputMixerStub::SetRate(int hz)
|
||||
{
|
||||
return parent->SetRate(hz, id);
|
||||
}
|
||||
|
||||
bool AudioOutputMixerStub::SetBitsPerSample(int bits)
|
||||
{
|
||||
return parent->SetBitsPerSample(bits, id);
|
||||
}
|
||||
|
||||
bool AudioOutputMixerStub::SetChannels(int channels)
|
||||
{
|
||||
return parent->SetChannels(channels, id);
|
||||
}
|
||||
|
||||
bool AudioOutputMixerStub::begin()
|
||||
{
|
||||
return parent->begin(id);
|
||||
}
|
||||
|
||||
bool AudioOutputMixerStub::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
int16_t amp[2];
|
||||
amp[LEFTCHANNEL] = Amplify(sample[LEFTCHANNEL]);
|
||||
amp[RIGHTCHANNEL] = Amplify(sample[RIGHTCHANNEL]);
|
||||
return parent->ConsumeSample(amp, id);
|
||||
}
|
||||
|
||||
bool AudioOutputMixerStub::stop()
|
||||
{
|
||||
return parent->stop(id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
AudioOutputMixer::AudioOutputMixer(int buffSizeSamples, AudioOutput *dest) : AudioOutput()
|
||||
{
|
||||
buffSize = buffSizeSamples;
|
||||
leftAccum = (int32_t*)calloc(sizeof(int32_t), buffSize);
|
||||
rightAccum = (int32_t*)calloc(sizeof(int32_t), buffSize);
|
||||
for (int i=0; i<maxStubs; i++) {
|
||||
stubAllocated[i] = false;
|
||||
stubRunning[i] = false;
|
||||
writePtr[i] = 0;
|
||||
}
|
||||
readPtr = 0;
|
||||
sink = dest;
|
||||
sinkStarted = false;
|
||||
}
|
||||
|
||||
AudioOutputMixer::~AudioOutputMixer()
|
||||
{
|
||||
free(leftAccum);
|
||||
free(rightAccum);
|
||||
}
|
||||
|
||||
|
||||
// Most "standard" interfaces should fail, only MixerStub should be able to talk to us
|
||||
bool AudioOutputMixer::SetRate(int hz)
|
||||
{
|
||||
(void) hz;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutputMixer::SetBitsPerSample(int bits)
|
||||
{
|
||||
(void) bits;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutputMixer::SetChannels(int channels)
|
||||
{
|
||||
(void) channels;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutputMixer::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
(void) sample;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutputMixer::begin()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioOutputMixer::stop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// TODO - actually ensure all samples are same speed, size, channels, rate
|
||||
bool AudioOutputMixer::SetRate(int hz, int id)
|
||||
{
|
||||
(void) id;
|
||||
return sink->SetRate(hz);
|
||||
}
|
||||
|
||||
bool AudioOutputMixer::SetBitsPerSample(int bits, int id)
|
||||
{
|
||||
(void) id;
|
||||
return sink->SetBitsPerSample(bits);
|
||||
}
|
||||
|
||||
bool AudioOutputMixer::SetChannels(int channels, int id)
|
||||
{
|
||||
(void) id;
|
||||
return sink->SetChannels(channels);
|
||||
}
|
||||
|
||||
bool AudioOutputMixer::begin(int id)
|
||||
{
|
||||
stubRunning[id] = true;
|
||||
|
||||
if (!sinkStarted) {
|
||||
sinkStarted = true;
|
||||
return sink->begin();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
AudioOutputMixerStub *AudioOutputMixer::NewInput()
|
||||
{
|
||||
for (int i=0; i<maxStubs; i++) {
|
||||
if (!stubAllocated[i]) {
|
||||
stubAllocated[i] = true;
|
||||
stubRunning[i] = false;
|
||||
writePtr[i] = readPtr; // TODO - should it be 1 before readPtr?
|
||||
AudioOutputMixerStub *stub = new AudioOutputMixerStub(this, i);
|
||||
return stub;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AudioOutputMixer::RemoveInput(int id)
|
||||
{
|
||||
stubAllocated[id] = false;
|
||||
stubRunning[id] = false;
|
||||
}
|
||||
|
||||
bool AudioOutputMixer::loop()
|
||||
{
|
||||
// First, try and fill I2S...
|
||||
// This is not optimal, but algorithmically should work fine
|
||||
bool avail;
|
||||
do {
|
||||
avail = true;
|
||||
for (int i=0; i<maxStubs && avail; i++) {
|
||||
if (stubRunning[i] && writePtr[i] == readPtr) {
|
||||
avail = false; // The read pointer is touching an active writer, can't advance
|
||||
}
|
||||
}
|
||||
if (avail) {
|
||||
int16_t s[2];
|
||||
if (leftAccum[readPtr] > 32767) {
|
||||
s[LEFTCHANNEL] = 32767;
|
||||
} else if (leftAccum[readPtr] < -32767) {
|
||||
s[LEFTCHANNEL] = -32767;
|
||||
} else {
|
||||
s[LEFTCHANNEL] = leftAccum[readPtr];
|
||||
}
|
||||
if (rightAccum[readPtr] > 32767) {
|
||||
s[RIGHTCHANNEL] = 32767;
|
||||
} else if (rightAccum[readPtr] < -32767) {
|
||||
s[RIGHTCHANNEL] = -32767;
|
||||
} else {
|
||||
s[RIGHTCHANNEL] = rightAccum[readPtr];
|
||||
}
|
||||
// s[LEFTCHANNEL] = Amplify(s[LEFTCHANNEL]);
|
||||
// s[RIGHTCHANNEL] = Amplify(s[RIGHTCHANNEL]);
|
||||
if (!sink->ConsumeSample(s)) {
|
||||
break; // Can't stuff any more in I2S...
|
||||
}
|
||||
// Clear the accums and advance the pointer to next potential sample
|
||||
leftAccum[readPtr] = 0;
|
||||
rightAccum[readPtr] = 0;
|
||||
readPtr = (readPtr + 1) % buffSize;
|
||||
}
|
||||
} while (avail);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputMixer::ConsumeSample(int16_t sample[2], int id)
|
||||
{
|
||||
loop(); // Send any pre-existing, completed I2S data we can fit
|
||||
|
||||
// Now, do we have space for a new sample?
|
||||
int nextWritePtr = (writePtr[id] + 1) % buffSize;
|
||||
if (nextWritePtr == readPtr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
leftAccum[writePtr[id]] += sample[LEFTCHANNEL];
|
||||
rightAccum[writePtr[id]] += sample[RIGHTCHANNEL];
|
||||
writePtr[id] = nextWritePtr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputMixer::stop(int id)
|
||||
{
|
||||
stubRunning[id] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
AudioOutputMixer
|
||||
Simple mixer which can combine multiple inputs to a single output stream
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOOUTPUTMIXER_H
|
||||
#define _AUDIOOUTPUTMIXER_H
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
class AudioOutputMixer;
|
||||
|
||||
|
||||
// The output stub exported by the mixer for use by the generator
|
||||
class AudioOutputMixerStub : public AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutputMixerStub(AudioOutputMixer *sink, int id);
|
||||
virtual ~AudioOutputMixerStub() override;
|
||||
virtual bool SetRate(int hz) override;
|
||||
virtual bool SetBitsPerSample(int bits) override;
|
||||
virtual bool SetChannels(int channels) override;
|
||||
virtual bool begin() override;
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual bool stop() override;
|
||||
|
||||
protected:
|
||||
AudioOutputMixer *parent;
|
||||
int id;
|
||||
};
|
||||
|
||||
// Single mixer object per output
|
||||
class AudioOutputMixer : public AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutputMixer(int samples, AudioOutput *sink);
|
||||
virtual ~AudioOutputMixer() override;
|
||||
virtual bool SetRate(int hz) override;
|
||||
virtual bool SetBitsPerSample(int bits) override;
|
||||
virtual bool SetChannels(int channels) override;
|
||||
virtual bool begin() override;
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual bool stop() override;
|
||||
virtual bool loop() override; // Send all existing samples we can to I2S
|
||||
|
||||
AudioOutputMixerStub *NewInput(); // Get a new stub to pass to a generator
|
||||
|
||||
// Stub called functions
|
||||
friend class AudioOutputMixerStub;
|
||||
private:
|
||||
void RemoveInput(int id);
|
||||
bool SetRate(int hz, int id);
|
||||
bool SetBitsPerSample(int bits, int id);
|
||||
bool SetChannels(int channels, int id);
|
||||
bool begin(int id);
|
||||
bool ConsumeSample(int16_t sample[2], int id);
|
||||
bool stop(int id);
|
||||
|
||||
protected:
|
||||
enum { maxStubs = 8 };
|
||||
AudioOutput *sink;
|
||||
bool sinkStarted;
|
||||
int16_t buffSize;
|
||||
int32_t *leftAccum;
|
||||
int32_t *rightAccum;
|
||||
bool stubAllocated[maxStubs];
|
||||
bool stubRunning[maxStubs];
|
||||
int16_t writePtr[maxStubs]; // Array of pointers for allocated stubs
|
||||
int16_t readPtr;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
AudioOutput
|
||||
Base class of an audio output player
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOOUTPUTNULL_H
|
||||
#define _AUDIOOUTPUTNULL_H
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
class AudioOutputNull : public AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutputNull() {};
|
||||
~AudioOutputNull() {};
|
||||
virtual bool begin() { samples = 0; startms = millis(); return true; }
|
||||
virtual bool ConsumeSample(int16_t sample[2]) { (void)sample; samples++; return true; }
|
||||
virtual bool stop() { endms = millis(); return true; };
|
||||
unsigned long GetMilliseconds() { return endms - startms; }
|
||||
int GetSamples() { return samples; }
|
||||
int GetFrequency() { return hertz; }
|
||||
|
||||
protected:
|
||||
unsigned long startms;
|
||||
unsigned long endms;
|
||||
int samples;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
AudioOutputSPDIF
|
||||
|
||||
S/PDIF output via I2S
|
||||
|
||||
Needs transciever from CMOS level to either optical or coaxial interface
|
||||
See: https://www.epanorama.net/documents/audio/spdif.html
|
||||
|
||||
Original idea and sources:
|
||||
Forum thread dicussing implementation
|
||||
https://forum.pjrc.com/threads/28639-S-pdif
|
||||
Teensy Audio Library
|
||||
https://github.com/PaulStoffregen/Audio/blob/master/output_spdif2.cpp
|
||||
|
||||
Adapted for ESP8266Audio
|
||||
|
||||
NOTE: This module operates I2S at 4x sampling rate, as it needs to
|
||||
send out each bit as two output symbols, packed into
|
||||
32-bit words. Even for mono sound, S/PDIF is specified minimum
|
||||
for 2 channels, each as 32-bits sub-frame. This drains I2S
|
||||
buffers 4x more quickly so you may need 4x bigger output
|
||||
buffers than usual, configurable with 'dma_buf_count'
|
||||
constructor parameter.
|
||||
|
||||
Copyright (C) 2020 Ivan Kostoski
|
||||
|
||||
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/>.
|
||||
*/
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
|
||||
#include <Arduino.h>
|
||||
#if defined(ESP32)
|
||||
#include "driver/i2s.h"
|
||||
#include "soc/rtc.h"
|
||||
#elif defined(ESP8266)
|
||||
#include "driver/SinglePinI2SDriver.h"
|
||||
#endif
|
||||
#include "AudioOutputSPDIF.h"
|
||||
|
||||
// BMC (Biphase Mark Coded) values (bit order reversed, i.e. LSB first)
|
||||
static const uint16_t spdif_bmclookup[256] PROGMEM = {
|
||||
0xcccc, 0x4ccc, 0x2ccc, 0xaccc, 0x34cc, 0xb4cc, 0xd4cc, 0x54cc,
|
||||
0x32cc, 0xb2cc, 0xd2cc, 0x52cc, 0xcacc, 0x4acc, 0x2acc, 0xaacc,
|
||||
0x334c, 0xb34c, 0xd34c, 0x534c, 0xcb4c, 0x4b4c, 0x2b4c, 0xab4c,
|
||||
0xcd4c, 0x4d4c, 0x2d4c, 0xad4c, 0x354c, 0xb54c, 0xd54c, 0x554c,
|
||||
0x332c, 0xb32c, 0xd32c, 0x532c, 0xcb2c, 0x4b2c, 0x2b2c, 0xab2c,
|
||||
0xcd2c, 0x4d2c, 0x2d2c, 0xad2c, 0x352c, 0xb52c, 0xd52c, 0x552c,
|
||||
0xccac, 0x4cac, 0x2cac, 0xacac, 0x34ac, 0xb4ac, 0xd4ac, 0x54ac,
|
||||
0x32ac, 0xb2ac, 0xd2ac, 0x52ac, 0xcaac, 0x4aac, 0x2aac, 0xaaac,
|
||||
0x3334, 0xb334, 0xd334, 0x5334, 0xcb34, 0x4b34, 0x2b34, 0xab34,
|
||||
0xcd34, 0x4d34, 0x2d34, 0xad34, 0x3534, 0xb534, 0xd534, 0x5534,
|
||||
0xccb4, 0x4cb4, 0x2cb4, 0xacb4, 0x34b4, 0xb4b4, 0xd4b4, 0x54b4,
|
||||
0x32b4, 0xb2b4, 0xd2b4, 0x52b4, 0xcab4, 0x4ab4, 0x2ab4, 0xaab4,
|
||||
0xccd4, 0x4cd4, 0x2cd4, 0xacd4, 0x34d4, 0xb4d4, 0xd4d4, 0x54d4,
|
||||
0x32d4, 0xb2d4, 0xd2d4, 0x52d4, 0xcad4, 0x4ad4, 0x2ad4, 0xaad4,
|
||||
0x3354, 0xb354, 0xd354, 0x5354, 0xcb54, 0x4b54, 0x2b54, 0xab54,
|
||||
0xcd54, 0x4d54, 0x2d54, 0xad54, 0x3554, 0xb554, 0xd554, 0x5554,
|
||||
0x3332, 0xb332, 0xd332, 0x5332, 0xcb32, 0x4b32, 0x2b32, 0xab32,
|
||||
0xcd32, 0x4d32, 0x2d32, 0xad32, 0x3532, 0xb532, 0xd532, 0x5532,
|
||||
0xccb2, 0x4cb2, 0x2cb2, 0xacb2, 0x34b2, 0xb4b2, 0xd4b2, 0x54b2,
|
||||
0x32b2, 0xb2b2, 0xd2b2, 0x52b2, 0xcab2, 0x4ab2, 0x2ab2, 0xaab2,
|
||||
0xccd2, 0x4cd2, 0x2cd2, 0xacd2, 0x34d2, 0xb4d2, 0xd4d2, 0x54d2,
|
||||
0x32d2, 0xb2d2, 0xd2d2, 0x52d2, 0xcad2, 0x4ad2, 0x2ad2, 0xaad2,
|
||||
0x3352, 0xb352, 0xd352, 0x5352, 0xcb52, 0x4b52, 0x2b52, 0xab52,
|
||||
0xcd52, 0x4d52, 0x2d52, 0xad52, 0x3552, 0xb552, 0xd552, 0x5552,
|
||||
0xccca, 0x4cca, 0x2cca, 0xacca, 0x34ca, 0xb4ca, 0xd4ca, 0x54ca,
|
||||
0x32ca, 0xb2ca, 0xd2ca, 0x52ca, 0xcaca, 0x4aca, 0x2aca, 0xaaca,
|
||||
0x334a, 0xb34a, 0xd34a, 0x534a, 0xcb4a, 0x4b4a, 0x2b4a, 0xab4a,
|
||||
0xcd4a, 0x4d4a, 0x2d4a, 0xad4a, 0x354a, 0xb54a, 0xd54a, 0x554a,
|
||||
0x332a, 0xb32a, 0xd32a, 0x532a, 0xcb2a, 0x4b2a, 0x2b2a, 0xab2a,
|
||||
0xcd2a, 0x4d2a, 0x2d2a, 0xad2a, 0x352a, 0xb52a, 0xd52a, 0x552a,
|
||||
0xccaa, 0x4caa, 0x2caa, 0xacaa, 0x34aa, 0xb4aa, 0xd4aa, 0x54aa,
|
||||
0x32aa, 0xb2aa, 0xd2aa, 0x52aa, 0xcaaa, 0x4aaa, 0x2aaa, 0xaaaa
|
||||
};
|
||||
|
||||
AudioOutputSPDIF::AudioOutputSPDIF(int dout_pin, int port, int dma_buf_count)
|
||||
{
|
||||
this->portNo = port;
|
||||
#if defined(ESP32)
|
||||
// Configure ESP32 I2S to roughly compatible to ESP8266 peripheral
|
||||
i2s_config_t i2s_config_spdif = {
|
||||
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
|
||||
.sample_rate = 88200, // 2 x sampling_rate
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // 32bit words
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // Right than left
|
||||
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // lowest interrupt priority
|
||||
.dma_buf_count = dma_buf_count,
|
||||
.dma_buf_len = DMA_BUF_SIZE_DEFAULT, // bigger buffers, reduces interrupts
|
||||
.use_apll = true // Audio PLL is needed for low clock jitter
|
||||
};
|
||||
if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_spdif, 0, NULL) != ESP_OK) {
|
||||
audioLogger->println(F("ERROR: Unable to install I2S drivers"));
|
||||
return;
|
||||
}
|
||||
i2s_zero_dma_buffer((i2s_port_t)portNo);
|
||||
SetPinout(I2S_PIN_NO_CHANGE, I2S_PIN_NO_CHANGE, dout_pin);
|
||||
rate_multiplier = 2; // 2x32bit words
|
||||
#elif defined(ESP8266)
|
||||
(void) dout_pin;
|
||||
if (!I2SDriver.begin(dma_buf_count, DMA_BUF_SIZE_DEFAULT)) {
|
||||
audioLogger->println(F("ERROR: Unable to start I2S driver"));
|
||||
return;
|
||||
}
|
||||
rate_multiplier = 4; // 4x16 bit words
|
||||
#endif
|
||||
i2sOn = true;
|
||||
mono = false;
|
||||
bps = 16;
|
||||
channels = 2;
|
||||
frame_num = 0;
|
||||
SetGain(1.0);
|
||||
hertz = 0;
|
||||
SetRate(44100);
|
||||
}
|
||||
|
||||
AudioOutputSPDIF::~AudioOutputSPDIF()
|
||||
{
|
||||
#if defined(ESP32)
|
||||
if (i2sOn) {
|
||||
i2s_stop((i2s_port_t)this->portNo);
|
||||
audioLogger->printf("UNINSTALL I2S\n");
|
||||
i2s_driver_uninstall((i2s_port_t)this->portNo); //stop & destroy i2s driver
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
if (i2sOn) I2SDriver.stop();
|
||||
#endif
|
||||
i2sOn = false;
|
||||
}
|
||||
|
||||
bool AudioOutputSPDIF::SetPinout(int bclk, int wclk, int dout)
|
||||
{
|
||||
#if defined(ESP32)
|
||||
i2s_pin_config_t pins = {
|
||||
.bck_io_num = bclk,
|
||||
.ws_io_num = wclk,
|
||||
.data_out_num = dout,
|
||||
.data_in_num = I2S_PIN_NO_CHANGE
|
||||
};
|
||||
if (i2s_set_pin((i2s_port_t)portNo, &pins) != ESP_OK) {
|
||||
audioLogger->println("ERROR setting up S/PDIF I2S pins\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
(void) bclk;
|
||||
(void) wclk;
|
||||
(void) dout;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AudioOutputSPDIF::SetRate(int hz)
|
||||
{
|
||||
if (!i2sOn) return false;
|
||||
if (hz < 32000) return false;
|
||||
if (hz == this->hertz) return true;
|
||||
this->hertz = hz;
|
||||
int adjustedHz = AdjustI2SRate(hz);
|
||||
#if defined(ESP32)
|
||||
if (i2s_set_sample_rates((i2s_port_t)portNo, adjustedHz) == ESP_OK) {
|
||||
if (adjustedHz == 88200) {
|
||||
// Manually fix the APLL rate for 44100.
|
||||
// See: https://github.com/espressif/esp-idf/issues/2634
|
||||
// sdm0 = 28, sdm1 = 8, sdm2 = 5, odir = 0 -> 88199.977
|
||||
rtc_clk_apll_enable(1, 28, 8, 5, 0);
|
||||
}
|
||||
} else {
|
||||
audioLogger->println("ERROR changing S/PDIF sample rate");
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
I2SDriver.setRate(adjustedHz);
|
||||
audioLogger->printf_P(PSTR("S/PDIF rate set: %.3f\n"), I2SDriver.getActualRate()/4);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputSPDIF::SetBitsPerSample(int bits)
|
||||
{
|
||||
if ( (bits != 16) && (bits != 8) ) return false;
|
||||
this->bps = bits;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputSPDIF::SetChannels(int channels)
|
||||
{
|
||||
if ( (channels < 1) || (channels > 2) ) return false;
|
||||
this->channels = channels;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputSPDIF::SetOutputModeMono(bool mono)
|
||||
{
|
||||
this->mono = mono;
|
||||
// Just use the left channel for mono
|
||||
if (mono) SetChannels(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputSPDIF::begin()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputSPDIF::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
if (!i2sOn) return true; // Sink the data
|
||||
int16_t ms[2];
|
||||
uint16_t hi, lo, aux;
|
||||
uint32_t buf[4];
|
||||
|
||||
ms[0] = sample[0];
|
||||
ms[1] = sample[1];
|
||||
MakeSampleStereo16(ms);
|
||||
|
||||
// S/PDIF encoding:
|
||||
// http://www.hardwarebook.info/S/PDIF
|
||||
// Original sources: Teensy Audio Library
|
||||
// https://github.com/PaulStoffregen/Audio/blob/master/output_spdif2.cpp
|
||||
//
|
||||
// Order of bits, before BMC encoding, from the definition of SPDIF format
|
||||
// PPPP AAAA SSSS SSSS SSSS SSSS SSSS VUCP
|
||||
// are sent rearanged as
|
||||
// VUCP PPPP AAAA 0000 SSSS SSSS SSSS SSSS
|
||||
// This requires a bit less shifting as 16 sample bits align and can be
|
||||
// BMC encoded with two table lookups (and at the same time flipped to LSB first).
|
||||
// There is no separate word-clock, so hopefully the receiver won't notice.
|
||||
|
||||
uint16_t sample_left = Amplify(ms[LEFTCHANNEL]);
|
||||
// BMC encode and flip left channel bits
|
||||
hi = pgm_read_word(&spdif_bmclookup[(uint8_t)(sample_left >> 8)]);
|
||||
lo = pgm_read_word(&spdif_bmclookup[(uint8_t)sample_left]);
|
||||
// Low word is inverted depending on first bit of high word
|
||||
lo ^= (~((int16_t)hi) >> 16);
|
||||
buf[0] = ((uint32_t)lo << 16) | hi;
|
||||
// Fixed 4 bits auxillary-audio-databits, the first used as parity
|
||||
// Depending on first bit of low word, invert the bits
|
||||
aux = 0xb333 ^ (((uint32_t)((int16_t)lo)) >> 17);
|
||||
// Send 'B' preamble only for the first frame of data-block
|
||||
if (frame_num == 0) {
|
||||
buf[1] = VUCP_PREAMBLE_B | aux;
|
||||
} else {
|
||||
buf[1] = VUCP_PREAMBLE_M | aux;
|
||||
}
|
||||
|
||||
uint16_t sample_right = Amplify(ms[RIGHTCHANNEL]);
|
||||
// BMC encode right channel, similar as above
|
||||
hi = pgm_read_word(&spdif_bmclookup[(uint8_t)(sample_right >> 8)]);
|
||||
lo = pgm_read_word(&spdif_bmclookup[(uint8_t)sample_right]);
|
||||
lo ^= (~((int16_t)hi) >> 16);
|
||||
buf[2] = ((uint32_t)lo << 16) | hi;
|
||||
aux = 0xb333 ^ (((uint32_t)((int16_t)lo)) >> 17);
|
||||
buf[3] = VUCP_PREAMBLE_W | aux;
|
||||
|
||||
#if defined(ESP32)
|
||||
// Assume DMA buffers are multiples of 16 bytes. Either we write all bytes or none.
|
||||
uint32_t bytes_written;
|
||||
esp_err_t ret = i2s_write((i2s_port_t)portNo, (const char*)&buf, 8 * channels, &bytes_written, 0);
|
||||
// If we didn't write all bytes, return false early and do not increment frame_num
|
||||
if ((ret != ESP_OK) || (bytes_written != (8 * channels))) return false;
|
||||
#elif defined(ESP8266)
|
||||
if (!I2SDriver.writeInterleaved(buf)) return false;
|
||||
#endif
|
||||
// Increment and rotate frame number
|
||||
if (++frame_num > 191) frame_num = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputSPDIF::stop()
|
||||
{
|
||||
#if defined(ESP32)
|
||||
i2s_zero_dma_buffer((i2s_port_t)portNo);
|
||||
#elif defined(ESP8266)
|
||||
I2SDriver.stop();
|
||||
#endif
|
||||
frame_num = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
AudioOutputSPDIF
|
||||
|
||||
S/PDIF output via I2S
|
||||
|
||||
Needs transciever from CMOS level to either optical or coaxial interface
|
||||
See: https://www.epanorama.net/documents/audio/spdif.html
|
||||
|
||||
Original idea and sources:
|
||||
Forum thread dicussing implementation
|
||||
https://forum.pjrc.com/threads/28639-S-pdif
|
||||
Teensy Audio Library
|
||||
https://github.com/PaulStoffregen/Audio/blob/master/output_spdif2.cpp
|
||||
|
||||
Adapted for ESP8266Audio
|
||||
|
||||
Copyright (C) 2020 Ivan Kostoski
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
#pragma once
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#define SPDIF_OUT_PIN_DEFAULT 27
|
||||
#define DMA_BUF_COUNT_DEFAULT 8
|
||||
#define DMA_BUF_SIZE_DEFAULT 256
|
||||
#elif defined(ESP8266)
|
||||
#define SPDIF_OUT_PIN_DEFAULT 3
|
||||
#define DMA_BUF_COUNT_DEFAULT 32
|
||||
#define DMA_BUF_SIZE_DEFAULT 64
|
||||
#endif
|
||||
|
||||
class AudioOutputSPDIF : public AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutputSPDIF(int dout_pin=SPDIF_OUT_PIN_DEFAULT, int port=0, int dma_buf_count = DMA_BUF_COUNT_DEFAULT);
|
||||
virtual ~AudioOutputSPDIF() override;
|
||||
bool SetPinout(int bclkPin, int wclkPin, int doutPin);
|
||||
virtual bool SetRate(int hz) override;
|
||||
virtual bool SetBitsPerSample(int bits) override;
|
||||
virtual bool SetChannels(int channels) override;
|
||||
virtual bool begin() override;
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual bool stop() override;
|
||||
|
||||
bool SetOutputModeMono(bool mono); // Force mono output no matter the input
|
||||
|
||||
const uint32_t VUCP_PREAMBLE_B = 0xCCE80000; // 11001100 11101000
|
||||
const uint32_t VUCP_PREAMBLE_M = 0xCCE20000; // 11001100 11100010
|
||||
const uint32_t VUCP_PREAMBLE_W = 0xCCE40000; // 11001100 11100100
|
||||
|
||||
protected:
|
||||
virtual inline int AdjustI2SRate(int hz) { return rate_multiplier * hz; }
|
||||
uint8_t portNo;
|
||||
bool mono;
|
||||
bool i2sOn;
|
||||
uint8_t frame_num;
|
||||
uint8_t rate_multiplier;
|
||||
};
|
||||
|
||||
#endif // _AUDIOOUTPUTSPDIF_H
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
AudioOutputSPIFFSWAV
|
||||
Writes a WAV file to the SPIFFS filesystem
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 <Arduino.h>
|
||||
#include <FS.h>
|
||||
#ifdef ESP32
|
||||
#include "SPIFFS.h"
|
||||
#endif
|
||||
|
||||
#include "AudioOutputSPIFFSWAV.h"
|
||||
|
||||
static const uint8_t wavHeaderTemplate[] PROGMEM = { // Hardcoded simple WAV header with 0xffffffff lengths all around
|
||||
0x52, 0x49, 0x46, 0x46, 0xff, 0xff, 0xff, 0xff, 0x57, 0x41, 0x56, 0x45,
|
||||
0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x22, 0x56, 0x00, 0x00, 0x88, 0x58, 0x01, 0x00, 0x04, 0x00, 0x10, 0x00,
|
||||
0x64, 0x61, 0x74, 0x61, 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
void AudioOutputSPIFFSWAV::SetFilename(const char *name)
|
||||
{
|
||||
if (filename) free(filename);
|
||||
filename = strdup(name);
|
||||
}
|
||||
|
||||
bool AudioOutputSPIFFSWAV::begin()
|
||||
{
|
||||
uint8_t wavHeader[sizeof(wavHeaderTemplate)];
|
||||
memset(wavHeader, 0, sizeof(wavHeader));
|
||||
|
||||
if (f) return false; // Already open!
|
||||
SPIFFS.remove(filename);
|
||||
f = SPIFFS.open(filename, "w+");
|
||||
if (!f) return false;
|
||||
|
||||
// We'll fix the header up when we close the file
|
||||
f.write(wavHeader, sizeof(wavHeader));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputSPIFFSWAV::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
for (int i=0; i<channels; i++) {
|
||||
if (bps == 8) {
|
||||
uint8_t l = sample[i] & 0xff;
|
||||
f.write(&l, sizeof(l));
|
||||
} else {
|
||||
uint8_t l = sample[i] & 0xff;
|
||||
uint8_t h = (sample[i] >> 8) & 0xff;
|
||||
f.write(&l, sizeof(l));
|
||||
f.write(&h, sizeof(h));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool AudioOutputSPIFFSWAV::stop()
|
||||
{
|
||||
uint8_t wavHeader[sizeof(wavHeaderTemplate)];
|
||||
|
||||
memcpy_P(wavHeader, wavHeaderTemplate, sizeof(wavHeaderTemplate));
|
||||
|
||||
int chunksize = f.size() - 8;
|
||||
wavHeader[4] = chunksize & 0xff;
|
||||
wavHeader[5] = (chunksize>>8)&0xff;
|
||||
wavHeader[6] = (chunksize>>16)&0xff;
|
||||
wavHeader[7] = (chunksize>>24)&0xff;
|
||||
|
||||
wavHeader[22] = channels & 0xff;
|
||||
wavHeader[23] = 0;
|
||||
|
||||
wavHeader[24] = hertz & 0xff;
|
||||
wavHeader[25] = (hertz >> 8) & 0xff;
|
||||
wavHeader[26] = (hertz >> 16) & 0xff;
|
||||
wavHeader[27] = (hertz >> 24) & 0xff;
|
||||
int byteRate = hertz * bps * channels / 8;
|
||||
wavHeader[28] = byteRate & 0xff;
|
||||
wavHeader[29] = (byteRate >> 8) & 0xff;
|
||||
wavHeader[30] = (byteRate >> 16) & 0xff;
|
||||
wavHeader[31] = (byteRate >> 24) & 0xff;
|
||||
wavHeader[32] = channels * bps / 8;
|
||||
wavHeader[33] = 0;
|
||||
wavHeader[34] = bps;
|
||||
wavHeader[35] = 0;
|
||||
|
||||
int datasize = f.size() - sizeof(wavHeader);
|
||||
wavHeader[40] = datasize & 0xff;
|
||||
wavHeader[41] = (datasize>>8)&0xff;
|
||||
wavHeader[42] = (datasize>>16)&0xff;
|
||||
wavHeader[43] = (datasize>>24)&0xff;
|
||||
|
||||
// Write real header out
|
||||
f.seek(0, SeekSet);
|
||||
f.write(wavHeader, sizeof(wavHeader));
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
AudioOutputSPIFFSWAV
|
||||
Writes a WAV file to the SPIFFS filesystem
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOOUTPUTSPIFFSWAV_H
|
||||
#define _AUDIOOUTPUTSPIFFSWAV_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
class AudioOutputSPIFFSWAV : public AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutputSPIFFSWAV() { filename = NULL; };
|
||||
~AudioOutputSPIFFSWAV() { free(filename); };
|
||||
virtual bool begin() override;
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual bool stop() override;
|
||||
void SetFilename(const char *name);
|
||||
|
||||
private:
|
||||
File f;
|
||||
char *filename;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
AudioOutputSTDIO
|
||||
Writes a WAV file to the STDIO filesystem
|
||||
Only for host-based testing
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 <Arduino.h>
|
||||
|
||||
#ifndef ARDUINO
|
||||
|
||||
#include "AudioOutputSTDIO.h"
|
||||
#include <unistd.h>
|
||||
|
||||
static const uint8_t wavHeaderTemplate[] PROGMEM = { // Hardcoded simple WAV header with 0xffffffff lengths all around
|
||||
0x52, 0x49, 0x46, 0x46, 0xff, 0xff, 0xff, 0xff, 0x57, 0x41, 0x56, 0x45,
|
||||
0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x22, 0x56, 0x00, 0x00, 0x88, 0x58, 0x01, 0x00, 0x04, 0x00, 0x10, 0x00,
|
||||
0x64, 0x61, 0x74, 0x61, 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
void AudioOutputSTDIO::SetFilename(const char *name)
|
||||
{
|
||||
free(filename);
|
||||
filename = strdup(name);
|
||||
}
|
||||
|
||||
bool AudioOutputSTDIO::begin()
|
||||
{
|
||||
uint8_t wavHeader[sizeof(wavHeaderTemplate)];
|
||||
memset(wavHeader, 0, sizeof(wavHeader));
|
||||
|
||||
if (f) return false; // Already open!
|
||||
unlink(filename);
|
||||
f = fopen(filename, "wb+");
|
||||
if (!f) return false;
|
||||
|
||||
// We'll fix the header up when we close the file
|
||||
fwrite(wavHeader, sizeof(wavHeader), 1, f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputSTDIO::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
for (int i=0; i<channels; i++) {
|
||||
if (bps == 8) {
|
||||
uint8_t l = sample[i] & 0xff;
|
||||
fwrite(&l, sizeof(l), 1, f);
|
||||
} else {
|
||||
uint8_t l = sample[i] & 0xff;
|
||||
uint8_t h = (sample[i] >> 8) & 0xff;
|
||||
fwrite(&l, sizeof(l), 1, f);
|
||||
fwrite(&h, sizeof(h), 1, f);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool AudioOutputSTDIO::stop()
|
||||
{
|
||||
uint8_t wavHeader[sizeof(wavHeaderTemplate)];
|
||||
|
||||
memcpy_P(wavHeader, wavHeaderTemplate, sizeof(wavHeaderTemplate));
|
||||
|
||||
int chunksize = ftell(f) - 8;
|
||||
wavHeader[4] = chunksize & 0xff;
|
||||
wavHeader[5] = (chunksize>>8)&0xff;
|
||||
wavHeader[6] = (chunksize>>16)&0xff;
|
||||
wavHeader[7] = (chunksize>>24)&0xff;
|
||||
|
||||
wavHeader[22] = channels & 0xff;
|
||||
wavHeader[23] = 0;
|
||||
|
||||
wavHeader[24] = hertz & 0xff;
|
||||
wavHeader[25] = (hertz >> 8) & 0xff;
|
||||
wavHeader[26] = (hertz >> 16) & 0xff;
|
||||
wavHeader[27] = (hertz >> 24) & 0xff;
|
||||
int byteRate = hertz * bps * channels / 8;
|
||||
wavHeader[28] = byteRate & 0xff;
|
||||
wavHeader[29] = (byteRate >> 8) & 0xff;
|
||||
wavHeader[30] = (byteRate >> 16) & 0xff;
|
||||
wavHeader[31] = (byteRate >> 24) & 0xff;
|
||||
wavHeader[32] = channels * bps / 8;
|
||||
wavHeader[33] = 0;
|
||||
wavHeader[34] = bps;
|
||||
wavHeader[35] = 0;
|
||||
|
||||
int datasize = ftell(f) - sizeof(wavHeader);
|
||||
wavHeader[40] = datasize & 0xff;
|
||||
wavHeader[41] = (datasize>>8)&0xff;
|
||||
wavHeader[42] = (datasize>>16)&0xff;
|
||||
wavHeader[43] = (datasize>>24)&0xff;
|
||||
|
||||
// Write real header out
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fwrite(wavHeader, sizeof(wavHeader), 1, f);
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
AudioOutputSTDIO
|
||||
Writes a WAV file to the STDIO filesystem
|
||||
Only for host-based testing
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOOUTPUTSTDIO_H
|
||||
#define _AUDIOOUTPUTSTDIO_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#ifndef ARDUINO
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
class AudioOutputSTDIO : public AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutputSTDIO() { filename = NULL; f = NULL; };
|
||||
~AudioOutputSTDIO() { free(filename); };
|
||||
virtual bool begin() override;
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual bool stop() override;
|
||||
void SetFilename(const char *name);
|
||||
|
||||
private:
|
||||
FILE *f;
|
||||
char *filename;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
AudioOutputSerialWAV
|
||||
Writes a mostly correct WAV file to the serial port
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 "AudioOutputSerialWAV.h"
|
||||
|
||||
static const uint8_t wavHeaderTemplate[] PROGMEM = { // Hardcoded simple WAV header with 0xffffffff lengths all around
|
||||
0x52, 0x49, 0x46, 0x46, 0xff, 0xff, 0xff, 0xff, 0x57, 0x41, 0x56, 0x45,
|
||||
0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x22, 0x56, 0x00, 0x00, 0x88, 0x58, 0x01, 0x00, 0x04, 0x00, 0x10, 0x00,
|
||||
0x64, 0x61, 0x74, 0x61, 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
bool AudioOutputSerialWAV::begin()
|
||||
{
|
||||
uint8_t wavHeader[sizeof(wavHeaderTemplate)];
|
||||
memcpy_P(wavHeader, wavHeaderTemplate, sizeof(wavHeaderTemplate));
|
||||
wavHeader[22] = channels & 0xff;
|
||||
wavHeader[23] = 0;
|
||||
wavHeader[24] = hertz & 0xff;
|
||||
wavHeader[25] = (hertz >> 8) & 0xff;
|
||||
wavHeader[26] = (hertz >> 16) & 0xff;
|
||||
wavHeader[27] = (hertz >> 24) & 0xff;
|
||||
int byteRate = hertz * bps * channels / 8;
|
||||
wavHeader[28] = byteRate & 0xff;
|
||||
wavHeader[29] = (byteRate >> 8) & 0xff;
|
||||
wavHeader[30] = (byteRate >> 16) & 0xff;
|
||||
wavHeader[31] = (byteRate >> 24) & 0xff;
|
||||
wavHeader[32] = channels * bps / 8;
|
||||
wavHeader[33] = 0;
|
||||
wavHeader[34] = bps;
|
||||
wavHeader[35] = 0;
|
||||
Serial.write(wavHeader, sizeof(wavHeader));
|
||||
count = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputSerialWAV::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
if (++count == 200) {
|
||||
count = 0;
|
||||
return false;
|
||||
}
|
||||
for (int i=0; i<channels; i++) {
|
||||
if (bps == 8) {
|
||||
uint8_t l = sample[i] & 0xff;
|
||||
Serial.write(l);
|
||||
} else {
|
||||
uint8_t l = sample[i] & 0xff;
|
||||
uint8_t h = (sample[i] >> 8) & 0xff;
|
||||
Serial.write(l);
|
||||
Serial.write(h);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool AudioOutputSerialWAV::stop()
|
||||
{
|
||||
audioLogger->printf_P(PSTR("\n\n\nEOF\n\n\n"));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
AudioOutputSerialWAV
|
||||
Writes a mostly correct WAV file to the serial port
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOOUTPUTSERIALWAV_H
|
||||
#define _AUDIOOUTPUTSERIALWAV_H
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
class AudioOutputSerialWAV : public AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutputSerialWAV() {};
|
||||
~AudioOutputSerialWAV() {};
|
||||
virtual bool begin() override;
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual bool stop() override;
|
||||
private:
|
||||
int count;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
262
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioOutputULP.cpp
Normal file
262
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioOutputULP.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
AudioOutputULP
|
||||
Outputs to ESP32 DAC through the ULP, freeing I2S for other uses
|
||||
|
||||
Copyright (C) 2020 Martin Laclaustra, based on bitluni's code
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
#include "AudioOutputULP.h"
|
||||
#include <esp32/ulp.h>
|
||||
#include <driver/rtc_io.h>
|
||||
#include <driver/dac.h>
|
||||
#include <soc/rtc.h>
|
||||
#include <math.h>
|
||||
|
||||
uint32_t create_I_WR_REG(uint32_t reg, uint32_t low_bit, uint32_t high_bit, uint32_t val){
|
||||
typedef union {ulp_insn_t ulp_ins; uint32_t ulp_bin;} ulp_union;
|
||||
const ulp_insn_t singleinstruction[] = {I_WR_REG(reg, low_bit, high_bit, val)};
|
||||
ulp_union recover_ins;
|
||||
recover_ins.ulp_ins=singleinstruction[0];
|
||||
return (uint32_t)(recover_ins.ulp_bin);
|
||||
}
|
||||
|
||||
uint32_t create_I_BXI(uint32_t imm_pc){
|
||||
typedef union {ulp_insn_t ulp_ins; uint32_t ulp_bin;} ulp_union;
|
||||
const ulp_insn_t singleinstruction[] = {I_BXI(imm_pc)};
|
||||
ulp_union recover_ins;
|
||||
recover_ins.ulp_ins=singleinstruction[0];
|
||||
return (uint32_t)(recover_ins.ulp_bin);
|
||||
}
|
||||
|
||||
bool AudioOutputULP::begin()
|
||||
{
|
||||
if(!stereoOutput){
|
||||
waitingOddSample = false;
|
||||
//totalSampleWords += 512;
|
||||
//dacTableStart2 = dacTableStart1;
|
||||
}
|
||||
|
||||
//calculate the actual ULP clock
|
||||
unsigned long rtc_8md256_period = rtc_clk_cal(RTC_CAL_8MD256, 1000);
|
||||
unsigned long rtc_fast_freq_hz = 1000000ULL * (1 << RTC_CLK_CAL_FRACT) * 256 / rtc_8md256_period;
|
||||
|
||||
//initialize DACs
|
||||
if(activeDACs & 1){
|
||||
dac_output_enable(DAC_CHANNEL_1);
|
||||
dac_output_voltage(DAC_CHANNEL_1, 128);
|
||||
}
|
||||
if(activeDACs & 2){
|
||||
dac_output_enable(DAC_CHANNEL_2);
|
||||
dac_output_voltage(DAC_CHANNEL_2, 128);
|
||||
}
|
||||
|
||||
int retAddress1 = 9;
|
||||
int retAddress2 = 14;
|
||||
|
||||
int loopCycles = 134;
|
||||
int loopHalfCycles1 = 90;
|
||||
int loopHalfCycles2 = 44;
|
||||
|
||||
Serial.print("Real RTC clock: ");
|
||||
Serial.println(rtc_fast_freq_hz);
|
||||
|
||||
uint32_t dt = (rtc_fast_freq_hz / hertz) - loopCycles;
|
||||
uint32_t dt2 = 0;
|
||||
if(!stereoOutput){
|
||||
dt = (rtc_fast_freq_hz / hertz) - loopHalfCycles1;
|
||||
dt2 = (rtc_fast_freq_hz / hertz) - loopHalfCycles2;
|
||||
}
|
||||
|
||||
Serial.print("dt: ");
|
||||
Serial.println(dt);
|
||||
|
||||
Serial.print("dt2: ");
|
||||
Serial.println(dt2);
|
||||
|
||||
const ulp_insn_t stereo[] = {
|
||||
//reset offset register
|
||||
I_MOVI(R3, 0),
|
||||
//delay to get the right sampling rate
|
||||
I_DELAY(dt), // 6 + dt
|
||||
//reset sample index
|
||||
I_MOVI(R0, 0), // 6
|
||||
//write the index back to memory for the main cpu
|
||||
I_ST(R0, R3, indexAddress), // 8
|
||||
//load the samples
|
||||
I_LD(R1, R0, bufferStart), // 8
|
||||
//mask the lower 8 bits
|
||||
I_ANDI(R2, R1, 0x00ff), // 6
|
||||
//multiply by 2
|
||||
I_LSHI(R2, R2, 1), // 6
|
||||
//add start position
|
||||
I_ADDI(R2, R2, dacTableStart1),// 6
|
||||
//jump to the dac opcode
|
||||
I_BXR(R2), // 4
|
||||
//back from first dac
|
||||
//delay between the two samples in mono rendering
|
||||
I_DELAY(dt2), // 6 + dt2
|
||||
//mask the upper 8 bits
|
||||
I_ANDI(R2, R1, 0xff00), // 6
|
||||
//shift the upper bits to right and multiply by 2
|
||||
I_RSHI(R2, R2, 8 - 1), // 6
|
||||
//add start position of second dac table
|
||||
I_ADDI(R2, R2, dacTableStart2),// 6
|
||||
//jump to the dac opcode
|
||||
I_BXR(R2), // 4
|
||||
//here we get back from writing the second sample
|
||||
//load 0x8080 as sample
|
||||
I_MOVI(R1, 0x8080), // 6
|
||||
//write 0x8080 in the sample buffer
|
||||
I_ST(R1, R0, indexAddress), // 8
|
||||
//increment the sample index
|
||||
I_ADDI(R0, R0, 1), // 6
|
||||
//if reached end of the buffer, jump relative to index reset
|
||||
I_BGE(-16, totalSampleWords), // 4
|
||||
//wait to get the right sample rate (2 cycles more to compensate the index reset)
|
||||
I_DELAY((unsigned int)dt + 2), // 8 + dt
|
||||
//if not, jump absolute to where index is written to memory
|
||||
I_BXI(3) // 4
|
||||
};
|
||||
// write io and jump back another 12 + 4 + 12 + 4
|
||||
|
||||
size_t load_addr = 0;
|
||||
size_t size = sizeof(stereo)/sizeof(ulp_insn_t);
|
||||
ulp_process_macros_and_load(load_addr, stereo, &size);
|
||||
// this is how to get the opcodes
|
||||
// for(int i = 0; i < size; i++)
|
||||
// Serial.println(RTC_SLOW_MEM[i], HEX);
|
||||
|
||||
//create DAC opcode tables
|
||||
switch(activeDACs){
|
||||
case 1:
|
||||
for(int i = 0; i < 256; i++)
|
||||
{
|
||||
RTC_SLOW_MEM[dacTableStart1 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC1_REG,19,26,i); //dac1: 0x1D4C0121 | (i << 10)
|
||||
RTC_SLOW_MEM[dacTableStart1 + 1 + i * 2] = create_I_BXI(retAddress1); // 0x80000000 + retAddress1 * 4
|
||||
RTC_SLOW_MEM[dacTableStart2 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC1_REG,19,26,i); //dac2: 0x1D4C0122 | (i << 10)
|
||||
RTC_SLOW_MEM[dacTableStart2 + 1 + i * 2] = create_I_BXI(retAddress2); // 0x80000000 + retAddress2 * 4
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
for(int i = 0; i < 256; i++)
|
||||
{
|
||||
RTC_SLOW_MEM[dacTableStart1 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC2_REG,19,26,i); //dac1: 0x1D4C0121 | (i << 10)
|
||||
RTC_SLOW_MEM[dacTableStart1 + 1 + i * 2] = create_I_BXI(retAddress1); // 0x80000000 + retAddress1 * 4
|
||||
RTC_SLOW_MEM[dacTableStart2 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC2_REG,19,26,i); //dac2: 0x1D4C0122 | (i << 10)
|
||||
RTC_SLOW_MEM[dacTableStart2 + 1 + i * 2] = create_I_BXI(retAddress2); // 0x80000000 + retAddress2 * 4
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
for(int i = 0; i < 256; i++)
|
||||
{
|
||||
RTC_SLOW_MEM[dacTableStart1 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC1_REG,19,26,i); //dac1: 0x1D4C0121 | (i << 10)
|
||||
RTC_SLOW_MEM[dacTableStart1 + 1 + i * 2] = create_I_BXI(retAddress1); // 0x80000000 + retAddress1 * 4
|
||||
RTC_SLOW_MEM[dacTableStart2 + i * 2] = create_I_WR_REG(RTC_IO_PAD_DAC1_REG,19,26,i); //dac2: 0x1D4C0122 | (i << 10)
|
||||
RTC_SLOW_MEM[dacTableStart2 + 1 + i * 2] = create_I_BXI(retAddress2); // 0x80000000 + retAddress2 * 4
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//set all samples to 128 (silence)
|
||||
for(int i = 0; i < totalSampleWords; i++)
|
||||
RTC_SLOW_MEM[bufferStart + i] = 0x8080;
|
||||
|
||||
//start
|
||||
RTC_SLOW_MEM[indexAddress] = 0;
|
||||
ulp_run(0);
|
||||
|
||||
//wait until ULP starts using samples and the index of output sample advances
|
||||
while(RTC_SLOW_MEM[indexAddress] == 0)
|
||||
delay(1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioOutputULP::ConsumeSample(int16_t sample[2])
|
||||
{
|
||||
int16_t ms[2];
|
||||
ms[0] = sample[0];
|
||||
ms[1] = sample[1];
|
||||
MakeSampleStereo16( ms );
|
||||
|
||||
// TODO: needs improvement (counting is different here with respect to ULP code)
|
||||
int currentSample = RTC_SLOW_MEM[indexAddress] & 0xffff;
|
||||
int currentWord = currentSample >> 1;
|
||||
|
||||
for (int i=0; i<2; i++) {
|
||||
ms[i] = ((ms[i] >> 8) + 128) & 0xff;
|
||||
}
|
||||
if(!stereoOutput) // mix both channels
|
||||
ms[0] = (uint16_t)(( (uint32_t)((int32_t)(ms[0]) + (int32_t)(ms[1])) >> 1 ) & 0xff);
|
||||
|
||||
if(waitingOddSample){ // always true for stereo because samples are consumed in pairs
|
||||
if(lastFilledWord != currentWord) // accept sample if writing index lastFilledWord has not reached index of output sample
|
||||
{
|
||||
unsigned int w;
|
||||
if(stereoOutput){
|
||||
w = ms[0];
|
||||
w |= ms[1] << 8;
|
||||
} else {
|
||||
w = bufferedOddSample;
|
||||
w |= ms[0] << 8;
|
||||
bufferedOddSample = 128;
|
||||
waitingOddSample = false;
|
||||
}
|
||||
RTC_SLOW_MEM[bufferStart + lastFilledWord] = w;
|
||||
lastFilledWord++;
|
||||
if(lastFilledWord == totalSampleWords)
|
||||
lastFilledWord = 0;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
bufferedOddSample = ms[0];
|
||||
waitingOddSample = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool AudioOutputULP::stop()
|
||||
{
|
||||
audioLogger->printf_P(PSTR("\n\n\nstop\n\n\n"));
|
||||
const ulp_insn_t stopulp[] = {
|
||||
//stop the timer
|
||||
I_END(),
|
||||
//end the program
|
||||
I_HALT()};
|
||||
|
||||
size_t load_addr = 0;
|
||||
size_t size = sizeof(stopulp)/sizeof(ulp_insn_t);
|
||||
ulp_process_macros_and_load(load_addr, stopulp, &size);
|
||||
|
||||
//start
|
||||
ulp_run(0);
|
||||
|
||||
if(activeDACs & 1){
|
||||
dac_output_voltage(DAC_CHANNEL_1, 128);
|
||||
}
|
||||
if(activeDACs & 2){
|
||||
dac_output_voltage(DAC_CHANNEL_2, 128);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
AudioOutputULP
|
||||
Outputs to ESP32 DAC through the ULP, freeing I2S for other uses
|
||||
|
||||
Copyright (C) 2020 Martin Laclaustra, based on bitluni's code
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
// Instructions:
|
||||
// AudioOutputULP out = new AudioOutputULP(); // stereo
|
||||
// Connect left channel on pin 25
|
||||
// Connect right channel on pin 26
|
||||
// OR
|
||||
// Connect mono channel on either of them (stereo samples are downmixed)
|
||||
// AudioOutputULP out = new AudioOutputULP(1); // mono, only DAC 1
|
||||
// OR
|
||||
// AudioOutputULP out = new AudioOutputULP(2); // mono, only DAC 2
|
||||
|
||||
|
||||
#ifndef _AUDIOOUTPUTULP_H
|
||||
#define _AUDIOOUTPUTULP_H
|
||||
|
||||
#include "AudioOutput.h"
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
class AudioOutputULP : public AudioOutput
|
||||
{
|
||||
public:
|
||||
AudioOutputULP(int argActiveDACs=3) {if(argActiveDACs<1||argActiveDACs>2)argActiveDACs=3;activeDACs=argActiveDACs;stereoOutput=activeDACs==3;};
|
||||
~AudioOutputULP() {};
|
||||
virtual bool begin() override;
|
||||
virtual bool ConsumeSample(int16_t sample[2]) override;
|
||||
virtual bool stop() override;
|
||||
enum : int { DAC1 = 1, DAC2 = 2 };
|
||||
private:
|
||||
int lastFilledWord = 0;
|
||||
uint8_t bufferedOddSample = 128;
|
||||
bool waitingOddSample = true; // must be set to false for mono output
|
||||
int activeDACs = 3; // 1:DAC1; 2:DAC2; 3:both;
|
||||
bool stereoOutput = true;
|
||||
const int opcodeCount = 20;
|
||||
const uint32_t dacTableStart1 = 2048 - 512;
|
||||
const uint32_t dacTableStart2 = dacTableStart1 - 512;
|
||||
uint32_t totalSampleWords = 2048 - 512 - 512 - (opcodeCount + 1); // add 512 for mono
|
||||
const int totalSamples = totalSampleWords * 2;
|
||||
const uint32_t indexAddress = opcodeCount;
|
||||
const uint32_t bufferStart = indexAddress + 1;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
#error Only the ESP32 supports ULP audio output
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
57
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioStatus.h
Normal file
57
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/AudioStatus.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
AudioStatus
|
||||
Base class for Audio* status/metadata reporting
|
||||
|
||||
Copyright (C) 2017 Earle F. Philhower, III
|
||||
|
||||
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 _AUDIOSTATUS_H
|
||||
#define _AUDIOSTATUS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "AudioLogger.h"
|
||||
|
||||
class AudioStatus
|
||||
{
|
||||
public:
|
||||
AudioStatus() { ClearCBs(); };
|
||||
virtual ~AudioStatus() {};
|
||||
|
||||
void ClearCBs() { mdFn = NULL; stFn = NULL; };
|
||||
|
||||
typedef void (*metadataCBFn)(void *cbData, const char *type, bool isUnicode, const char *str);
|
||||
bool RegisterMetadataCB(metadataCBFn f, void *cbData) { mdFn = f; mdData = cbData; return true; }
|
||||
|
||||
// Returns a unique warning/error code, varying by the object. The string may be a PSTR, use _P functions!
|
||||
typedef void (*statusCBFn)(void *cbData, int code, const char *string);
|
||||
bool RegisterStatusCB(statusCBFn f, void *cbData) { stFn = f; stData = cbData; return true; }
|
||||
|
||||
// Safely call the md function, if defined
|
||||
inline void md(const char *type, bool isUnicode, const char *string) { if (mdFn) mdFn(mdData, type, isUnicode, string); }
|
||||
|
||||
// Safely call the st function, if defined
|
||||
inline void st(int code, const char *string) { if (stFn) stFn(stData, code, string); }
|
||||
|
||||
private:
|
||||
metadataCBFn mdFn;
|
||||
void *mdData;
|
||||
statusCBFn stFn;
|
||||
void *stData;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
50
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/ESP8266Audio.h
Normal file
50
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/ESP8266Audio.h
Normal file
@@ -0,0 +1,50 @@
|
||||
// Lazy "include all the things" header for simplicity.
|
||||
// In general a user should only include the specific headers they need
|
||||
// to miniimize build times.
|
||||
|
||||
// Input stage
|
||||
#include "AudioFileSourceBuffer.h"
|
||||
#include "AudioFileSourceFATFS.h"
|
||||
#include "AudioFileSourceFS.h"
|
||||
#include "AudioFileSource.h"
|
||||
#include "AudioFileSourceHTTPStream.h"
|
||||
#include "AudioFileSourceICYStream.h"
|
||||
#include "AudioFileSourceID3.h"
|
||||
#include "AudioFileSourceLittleFS.h"
|
||||
#include "AudioFileSourcePROGMEM.h"
|
||||
#include "AudioFileSourceSD.h"
|
||||
#include "AudioFileSourceSPIFFS.h"
|
||||
#include "AudioFileSourceSPIRAMBuffer.h"
|
||||
#include "AudioFileSourceSTDIO.h"
|
||||
|
||||
// Misc. plumbing
|
||||
#include "AudioFileStream.h"
|
||||
#include "AudioLogger.h"
|
||||
#include "AudioStatus.h"
|
||||
|
||||
// Actual decode/audio generation logic
|
||||
#include "AudioGeneratorAAC.h"
|
||||
#include "AudioGeneratorFLAC.h"
|
||||
#include "AudioGenerator.h"
|
||||
#include "AudioGeneratorMIDI.h"
|
||||
#include "AudioGeneratorMOD.h"
|
||||
#include "AudioGeneratorMP3a.h"
|
||||
#include "AudioGeneratorMP3.h"
|
||||
#include "AudioGeneratorOpus.h"
|
||||
#include "AudioGeneratorRTTTL.h"
|
||||
#include "AudioGeneratorTalkie.h"
|
||||
#include "AudioGeneratorWAV.h"
|
||||
|
||||
// Render(output) sounds
|
||||
#include "AudioOutputBuffer.h"
|
||||
#include "AudioOutputFilterDecimate.h"
|
||||
#include "AudioOutput.h"
|
||||
#include "AudioOutputI2S.h"
|
||||
#include "AudioOutputI2SNoDAC.h"
|
||||
#include "AudioOutputMixer.h"
|
||||
#include "AudioOutputNull.h"
|
||||
#include "AudioOutputSerialWAV.h"
|
||||
#include "AudioOutputSPDIF.h"
|
||||
#include "AudioOutputSPIFFSWAV.h"
|
||||
#include "AudioOutputSTDIO.h"
|
||||
#include "AudioOutputULP.h"
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
SinglePinI2SDriver
|
||||
ESP8266Audio I2S Minimal driver
|
||||
|
||||
Most of this code is taken and reworked from ESP8266 Arduino core,
|
||||
which itsef is reworked from Espessif's I2S examples.
|
||||
Original code is licensed under LGPL 2.1 or above
|
||||
|
||||
Reasons for rewrite:
|
||||
- Non-configurable size of DMA buffers
|
||||
- We only need GPIO3 connected to I2SO_DATA
|
||||
- No API to queue data from ISR. Callbacks are unusable
|
||||
as i2s_write_* functions are not in IRAM and may re-enable
|
||||
SLC interrupt before returning
|
||||
- ISR overhead is not needed
|
||||
|
||||
Copyright (C) 2020 Ivan Kostoski
|
||||
|
||||
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 St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#if defined(ESP8266)
|
||||
|
||||
#include "SinglePinI2SDriver.h"
|
||||
|
||||
SinglePinI2SDriver::~SinglePinI2SDriver()
|
||||
{
|
||||
stopI2S();
|
||||
freeBuffers(bufCount);
|
||||
}
|
||||
|
||||
bool SinglePinI2SDriver::begin(uint8_t bufCount, uint16_t bufSize)
|
||||
{
|
||||
if (bufCount < 4) return false;
|
||||
if ((bufSize < 32) || (bufSize > 1020) || ((bufSize % 4) != 0)) return false;
|
||||
|
||||
stopSLC();
|
||||
|
||||
if ((bufCount != this->bufCount) || (bufSize != this->bufSize)) freeBuffers(this->bufCount);
|
||||
this->bufCount = bufCount;
|
||||
this->bufSize = bufSize;
|
||||
if (!allocateBuffers()) return false;
|
||||
|
||||
zeroBuffers();
|
||||
configureSLC();
|
||||
startI2S();
|
||||
|
||||
// Send out at least one buffer, so we have valid address of
|
||||
// finished descriptor in SLCRXEDA
|
||||
head = &descriptors[0];
|
||||
head->owner = 1;
|
||||
startSLC();
|
||||
while (!lastSentDescriptor()) delayMicroseconds(1);
|
||||
// By here, SLC should be stalled (next descriptor owner is 0)
|
||||
head->owner = 0;
|
||||
head = head->next_link_ptr;
|
||||
headPos = 0;
|
||||
underflowCount = -1;
|
||||
return true;
|
||||
};
|
||||
|
||||
void SinglePinI2SDriver::stop()
|
||||
{
|
||||
stopI2S();
|
||||
}
|
||||
|
||||
bool SinglePinI2SDriver::allocateBuffers()
|
||||
{
|
||||
// Allocate output (RXLINK) descriptors and bufferss
|
||||
if (descriptors) return true;
|
||||
descriptors = (SLCDecriptor*) calloc(bufCount, sizeof(SLCDecriptor));
|
||||
if (!descriptors) return false;
|
||||
int allocated;
|
||||
for (allocated = 0; allocated < bufCount; allocated++) {
|
||||
uint32_t* buffer = (uint32_t*)malloc(bufSize * sizeof(uint32_t));
|
||||
if (!buffer) break;
|
||||
auto descriptor = &descriptors[allocated];
|
||||
descriptor->eof = 1; // Needed for SLC to update SLCRXEDA
|
||||
descriptor->datalen = bufSize * sizeof(uint32_t);
|
||||
descriptor->blocksize = bufSize * sizeof(uint32_t);
|
||||
descriptor->buf_ptr = buffer;
|
||||
descriptor->next_link_ptr = &descriptors[(allocated+1) % bufCount];
|
||||
}
|
||||
// Release memory if not all buffers were allocated
|
||||
if (allocated < bufCount) {
|
||||
freeBuffers(allocated);
|
||||
return false;
|
||||
}
|
||||
zeroBuffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SinglePinI2SDriver::freeBuffers(int allocated)
|
||||
{
|
||||
if (!descriptors) return;
|
||||
while (--allocated >= 0) {
|
||||
if (descriptors[allocated].buf_ptr) {
|
||||
free(descriptors[allocated].buf_ptr);
|
||||
descriptors[allocated].buf_ptr = NULL;
|
||||
}
|
||||
}
|
||||
free(descriptors);
|
||||
descriptors = NULL;
|
||||
}
|
||||
|
||||
void SinglePinI2SDriver::zeroBuffers()
|
||||
{
|
||||
for(int i = 0; i < bufCount; i++) {
|
||||
auto descriptor = &descriptors[i];
|
||||
ets_memset((void *)descriptor->buf_ptr, 0, bufSize * sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
inline void SinglePinI2SDriver::advanceHead(const SLCDecriptor *lastSent)
|
||||
{
|
||||
if (headPos >= bufSize) {
|
||||
head->owner = 1; // Owned by SLC
|
||||
head = head->next_link_ptr;
|
||||
head->owner = 0; // Owned by CPU
|
||||
headPos = 0;
|
||||
// If SLC stopped, fill-up the buffers before (re)starting
|
||||
if (isSLCStopped() && (head->next_link_ptr == lastSent)) {
|
||||
underflowCount++;
|
||||
startSLC();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SinglePinI2SDriver::write(uint32_t sample)
|
||||
{
|
||||
auto lastSent = lastSentDescriptor();
|
||||
if (isSLCRunning() && (lastSent->next_link_ptr == head)) return false;
|
||||
head->buf_ptr[headPos++] = sample;
|
||||
advanceHead(lastSent);
|
||||
return true;
|
||||
};
|
||||
|
||||
bool SinglePinI2SDriver::writeInterleaved(uint32_t samples[4])
|
||||
{
|
||||
auto lastSent = lastSentDescriptor();
|
||||
if (isSLCRunning() && (lastSent->next_link_ptr == head)) return false;
|
||||
uint32_t* buf = head->buf_ptr;
|
||||
buf[headPos++] = samples[1];
|
||||
buf[headPos++] = samples[0];
|
||||
buf[headPos++] = samples[3];
|
||||
buf[headPos++] = samples[2];
|
||||
advanceHead(lastSent);
|
||||
return true;
|
||||
};
|
||||
|
||||
void SinglePinI2SDriver::configureSLC()
|
||||
{
|
||||
ETS_SLC_INTR_DISABLE();
|
||||
// Reset SLC, clear interrupts
|
||||
SLCC0 |= SLCRXLR | SLCTXLR;
|
||||
SLCC0 &= ~(SLCRXLR | SLCTXLR);
|
||||
SLCIC = 0xFFFFFFFF;
|
||||
// Configure SLC DMA in mode 1
|
||||
SLCC0 &= ~(SLCMM << SLCM);
|
||||
SLCC0 |= (1 << SLCM);
|
||||
SLCRXDC |= SLCBINR | SLCBTNR;
|
||||
SLCRXDC &= ~(SLCBRXFE | SLCBRXEM | SLCBRXFM);
|
||||
// RXLINK is output DMA, but TXLINK must be configured with valid descriptr
|
||||
SLCTXL &= ~(SLCTXLAM << SLCTXLA);
|
||||
SLCRXL &= ~(SLCRXLAM << SLCRXLA);
|
||||
SLCTXL |= (uint32)&descriptors[0] << SLCTXLA;
|
||||
SLCRXL |= (uint32)&descriptors[0] << SLCRXLA;
|
||||
// Not setting up interrupts. Not starting DMA here
|
||||
}
|
||||
|
||||
void SinglePinI2SDriver::stopSLC()
|
||||
{
|
||||
// Stop SLC, reset descriptors and clear interrupts
|
||||
ETS_SLC_INTR_DISABLE();
|
||||
SLCTXL |= SLCTXLE;
|
||||
SLCRXL |= SLCRXLE;
|
||||
SLCTXL &= ~(SLCTXLAM << SLCTXLA);
|
||||
SLCRXL &= ~(SLCRXLAM << SLCRXLA);
|
||||
SLCIC = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
void SinglePinI2SDriver::setDividers(uint8_t div1, uint8_t div2)
|
||||
{
|
||||
// Ensure dividers fit in bit fields
|
||||
div1 &= I2SBDM;
|
||||
div2 &= I2SCDM;
|
||||
// trans master(active low), recv master(active_low), !bits mod(==16 bits/chanel), clear clock dividers
|
||||
I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
|
||||
// I2SRF = Send/recv right channel first
|
||||
// I2SMR = MSB recv/xmit first
|
||||
// I2SRMS, I2STMS = We don't really care about WS delay here
|
||||
// div1, div2 = Set I2S WS clock frequency. BCLK seems to be generated from 32x this
|
||||
I2SC |= I2SRF | I2SMR | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD);
|
||||
}
|
||||
|
||||
void SinglePinI2SDriver::setRate(const uint32_t rateHz)
|
||||
{
|
||||
if (rateHz == currentRate) return;
|
||||
currentRate = rateHz;
|
||||
|
||||
uint32_t scaledBaseFreq = I2SBASEFREQ / 32;
|
||||
float bestDelta = scaledBaseFreq;
|
||||
uint8_t sbdDivBest=1;
|
||||
uint8_t scdDivBest=1;
|
||||
for (uint8_t i = 1; i < 64; i++) {
|
||||
for (uint8_t j = i; j < 64; j++) {
|
||||
float delta = fabs(((float)scaledBaseFreq/i/j) - rateHz);
|
||||
if (delta < bestDelta){
|
||||
bestDelta = delta;
|
||||
sbdDivBest = i;
|
||||
scdDivBest = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDividers(sbdDivBest, scdDivBest);
|
||||
}
|
||||
|
||||
float SinglePinI2SDriver::getActualRate()
|
||||
{
|
||||
return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM);
|
||||
}
|
||||
|
||||
void SinglePinI2SDriver::startI2S()
|
||||
{
|
||||
// Setup only I2S SD pin for output
|
||||
pinMode(I2SO_DATA, FUNCTION_1);
|
||||
I2S_CLK_ENABLE();
|
||||
// Reset I2S
|
||||
I2SIC = 0x3F;
|
||||
I2SIE = 0;
|
||||
I2SC &= ~(I2SRST);
|
||||
I2SC |= I2SRST;
|
||||
I2SC &= ~(I2SRST);
|
||||
// I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data
|
||||
I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM));
|
||||
I2SFC |= I2SDE; // Enable DMA
|
||||
// I2STXCMM, I2SRXCMM=0 => Dual channel mode, RX/TX CHAN_MOD=0
|
||||
I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM));
|
||||
// Set dividers to something resonable
|
||||
currentRate = 0;
|
||||
setRate(44100);
|
||||
// Start I2S peripheral
|
||||
I2SC |= I2STXS;
|
||||
}
|
||||
|
||||
void SinglePinI2SDriver::stopI2S()
|
||||
{
|
||||
// Disable any I2S send or receive
|
||||
I2SC &= ~(I2STXS | I2SRXS);
|
||||
// Reset I2S
|
||||
I2SC &= ~(I2SRST);
|
||||
I2SC |= I2SRST;
|
||||
I2SC &= ~(I2SRST);
|
||||
|
||||
stopSLC();
|
||||
// Reconnect RX0 ?
|
||||
//pinMode(I2SO_DATA, SPECIAL);
|
||||
}
|
||||
|
||||
int SinglePinI2SDriver::getUnderflowCount()
|
||||
{
|
||||
int count = underflowCount;
|
||||
underflowCount = 0;
|
||||
return count;
|
||||
}
|
||||
|
||||
// Global instance
|
||||
SinglePinI2SDriver I2SDriver;
|
||||
|
||||
#endif // defined(ESP8266)
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
SinglePinI2SDriver
|
||||
ESP8266Audio I2S Minimal driver
|
||||
|
||||
Copyright (C) 2020 Ivan Kostoski
|
||||
|
||||
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 St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#if defined(ESP8266)
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "osapi.h"
|
||||
#include "ets_sys.h"
|
||||
#include "i2s_reg.h"
|
||||
|
||||
struct SLCDecriptor {
|
||||
uint32_t blocksize : 12;
|
||||
uint32_t datalen : 12;
|
||||
uint32_t unused : 5;
|
||||
uint32_t sub_sof : 1;
|
||||
uint32_t eof : 1;
|
||||
volatile uint32_t owner : 1;
|
||||
uint32_t* buf_ptr;
|
||||
SLCDecriptor* next_link_ptr;
|
||||
};
|
||||
|
||||
class SinglePinI2SDriver
|
||||
{
|
||||
public:
|
||||
enum : uint8_t { I2SO_DATA = 3 }; // GPIO3/RX0
|
||||
|
||||
SinglePinI2SDriver(): descriptors(NULL), bufCount(0), bufSize(0) {};
|
||||
~SinglePinI2SDriver();
|
||||
|
||||
bool begin(uint8_t bufCount, uint16_t bufSize);
|
||||
void stop();
|
||||
void setRate(const uint32_t rateHz);
|
||||
float getActualRate();
|
||||
bool write(uint32_t sample);
|
||||
bool writeInterleaved(uint32_t samples[4]);
|
||||
int getUnderflowCount();
|
||||
|
||||
protected:
|
||||
bool allocateBuffers();
|
||||
void freeBuffers(int allocated);
|
||||
void zeroBuffers();
|
||||
void advanceHead(const SLCDecriptor *finished);
|
||||
void configureSLC();
|
||||
void stopSLC();
|
||||
void setDividers(uint8_t div1, uint8_t div2);
|
||||
void startI2S();
|
||||
void stopI2S();
|
||||
SLCDecriptor *descriptors;
|
||||
uint16_t bufCount;
|
||||
uint16_t bufSize;
|
||||
SLCDecriptor *head;
|
||||
uint32_t headPos;
|
||||
uint32_t currentRate;
|
||||
int underflowCount;
|
||||
|
||||
// (Re)Start transmission ("TX" DMA always needed to be enabled)
|
||||
static inline void startSLC() { SLCTXL |= SLCTXLS; SLCRXL |= SLCRXLS; }
|
||||
static inline uint32_t isSLCRunning() { return (SLCRXS & SLCRXF); };
|
||||
static inline uint32_t isSLCStopped() { return (SLCRXS & SLCRXE); };
|
||||
static inline SLCDecriptor *lastSentDescriptor() { return (SLCDecriptor*)SLCRXEDA; }
|
||||
};
|
||||
|
||||
// Global instance
|
||||
extern SinglePinI2SDriver I2SDriver;
|
||||
|
||||
#endif // defined(ESP8266)
|
||||
58
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/AUTHORS
Normal file
58
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/AUTHORS
Normal file
@@ -0,0 +1,58 @@
|
||||
/* FLAC - Free Lossless Audio Codec
|
||||
* Copyright (C) 2001-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
*
|
||||
* This file is part the FLAC project. FLAC is comprised of several
|
||||
* components distributed under different licenses. The codec libraries
|
||||
* are distributed under Xiph.Org's BSD-like license (see the file
|
||||
* COPYING.Xiph in this distribution). All other programs, libraries, and
|
||||
* plugins are distributed under the GPL (see COPYING.GPL). The documentation
|
||||
* is distributed under the Gnu FDL (see COPYING.FDL). Each file in the
|
||||
* FLAC distribution contains at the top the terms under which it may be
|
||||
* distributed.
|
||||
*
|
||||
* Since this particular file is relevant to all components of FLAC,
|
||||
* it may be distributed under the Xiph.Org license, which is the least
|
||||
* restrictive of those mentioned above. See the file COPYING.Xiph in this
|
||||
* distribution.
|
||||
*/
|
||||
|
||||
Current FLAC maintainer: Erik de Castro Lopo <erikd@mega-nerd.com>
|
||||
|
||||
Original author: Josh Coalson <jcoalson@users.sourceforge.net>
|
||||
|
||||
Website : https://www.xiph.org/flac/
|
||||
|
||||
FLAC is an Open Source lossless audio codec originally developed by Josh Coalson
|
||||
between 2001 and 2009. From 2009 to 2012 FLAC was basically unmaintained. In
|
||||
2012 the Erik de Castro Lopo became the chief maintainer as part of the
|
||||
Xiph.Org Foundation.
|
||||
|
||||
Other major contributors and their contributions:
|
||||
|
||||
"lvqcl" <lvqcl@users.sourceforge.net>
|
||||
* Visual Studio build system.
|
||||
* Optimisations in the encoder and decoder.
|
||||
|
||||
"Janne Hyvärinen" <cse@sci.fi>
|
||||
* Visual Studio build system.
|
||||
* Unicode handling on Windows.
|
||||
|
||||
"Andrey Astafiev" <andrei@tvcell.ru>
|
||||
* Russian translation of the HTML documentation
|
||||
|
||||
"Miroslav Lichvar" <lichvarm@phoenix.inf.upol.cz>
|
||||
* IA-32 assembly versions of several libFLAC routines
|
||||
|
||||
"Brady Patterson" <bpat@users.sourceforge.net>
|
||||
* AIFF file support, PPC assembly versions of libFLAC routines
|
||||
|
||||
"Daisuke Shimamura" <Daisuke_Shimamura@nifty.com>
|
||||
* i18n support in the XMMS plugin
|
||||
|
||||
"X-Fixer" <x-fixer@narod.ru>
|
||||
* Configuration system, tag editing, and file info in the Winamp2 plugin
|
||||
|
||||
"Matt Zimmerman" <mdz@debian.org>
|
||||
* Libtool/autoconf/automake make system, flac man page
|
||||
|
||||
397
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/COPYING.FDL
Normal file
397
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/COPYING.FDL
Normal file
@@ -0,0 +1,397 @@
|
||||
GNU Free Documentation License
|
||||
Version 1.2, November 2002
|
||||
|
||||
|
||||
Copyright (C) 2000,2001,2002 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
0. PREAMBLE
|
||||
|
||||
The purpose of this License is to make a manual, textbook, or other
|
||||
functional and useful document "free" in the sense of freedom: to
|
||||
assure everyone the effective freedom to copy and redistribute it,
|
||||
with or without modifying it, either commercially or noncommercially.
|
||||
Secondarily, this License preserves for the author and publisher a way
|
||||
to get credit for their work, while not being considered responsible
|
||||
for modifications made by others.
|
||||
|
||||
This License is a kind of "copyleft", which means that derivative
|
||||
works of the document must themselves be free in the same sense. It
|
||||
complements the GNU General Public License, which is a copyleft
|
||||
license designed for free software.
|
||||
|
||||
We have designed this License in order to use it for manuals for free
|
||||
software, because free software needs free documentation: a free
|
||||
program should come with manuals providing the same freedoms that the
|
||||
software does. But this License is not limited to software manuals;
|
||||
it can be used for any textual work, regardless of subject matter or
|
||||
whether it is published as a printed book. We recommend this License
|
||||
principally for works whose purpose is instruction or reference.
|
||||
|
||||
|
||||
1. APPLICABILITY AND DEFINITIONS
|
||||
|
||||
This License applies to any manual or other work, in any medium, that
|
||||
contains a notice placed by the copyright holder saying it can be
|
||||
distributed under the terms of this License. Such a notice grants a
|
||||
world-wide, royalty-free license, unlimited in duration, to use that
|
||||
work under the conditions stated herein. The "Document", below,
|
||||
refers to any such manual or work. Any member of the public is a
|
||||
licensee, and is addressed as "you". You accept the license if you
|
||||
copy, modify or distribute the work in a way requiring permission
|
||||
under copyright law.
|
||||
|
||||
A "Modified Version" of the Document means any work containing the
|
||||
Document or a portion of it, either copied verbatim, or with
|
||||
modifications and/or translated into another language.
|
||||
|
||||
A "Secondary Section" is a named appendix or a front-matter section of
|
||||
the Document that deals exclusively with the relationship of the
|
||||
publishers or authors of the Document to the Document's overall subject
|
||||
(or to related matters) and contains nothing that could fall directly
|
||||
within that overall subject. (Thus, if the Document is in part a
|
||||
textbook of mathematics, a Secondary Section may not explain any
|
||||
mathematics.) The relationship could be a matter of historical
|
||||
connection with the subject or with related matters, or of legal,
|
||||
commercial, philosophical, ethical or political position regarding
|
||||
them.
|
||||
|
||||
The "Invariant Sections" are certain Secondary Sections whose titles
|
||||
are designated, as being those of Invariant Sections, in the notice
|
||||
that says that the Document is released under this License. If a
|
||||
section does not fit the above definition of Secondary then it is not
|
||||
allowed to be designated as Invariant. The Document may contain zero
|
||||
Invariant Sections. If the Document does not identify any Invariant
|
||||
Sections then there are none.
|
||||
|
||||
The "Cover Texts" are certain short passages of text that are listed,
|
||||
as Front-Cover Texts or Back-Cover Texts, in the notice that says that
|
||||
the Document is released under this License. A Front-Cover Text may
|
||||
be at most 5 words, and a Back-Cover Text may be at most 25 words.
|
||||
|
||||
A "Transparent" copy of the Document means a machine-readable copy,
|
||||
represented in a format whose specification is available to the
|
||||
general public, that is suitable for revising the document
|
||||
straightforwardly with generic text editors or (for images composed of
|
||||
pixels) generic paint programs or (for drawings) some widely available
|
||||
drawing editor, and that is suitable for input to text formatters or
|
||||
for automatic translation to a variety of formats suitable for input
|
||||
to text formatters. A copy made in an otherwise Transparent file
|
||||
format whose markup, or absence of markup, has been arranged to thwart
|
||||
or discourage subsequent modification by readers is not Transparent.
|
||||
An image format is not Transparent if used for any substantial amount
|
||||
of text. A copy that is not "Transparent" is called "Opaque".
|
||||
|
||||
Examples of suitable formats for Transparent copies include plain
|
||||
ASCII without markup, Texinfo input format, LaTeX input format, SGML
|
||||
or XML using a publicly available DTD, and standard-conforming simple
|
||||
HTML, PostScript or PDF designed for human modification. Examples of
|
||||
transparent image formats include PNG, XCF and JPG. Opaque formats
|
||||
include proprietary formats that can be read and edited only by
|
||||
proprietary word processors, SGML or XML for which the DTD and/or
|
||||
processing tools are not generally available, and the
|
||||
machine-generated HTML, PostScript or PDF produced by some word
|
||||
processors for output purposes only.
|
||||
|
||||
The "Title Page" means, for a printed book, the title page itself,
|
||||
plus such following pages as are needed to hold, legibly, the material
|
||||
this License requires to appear in the title page. For works in
|
||||
formats which do not have any title page as such, "Title Page" means
|
||||
the text near the most prominent appearance of the work's title,
|
||||
preceding the beginning of the body of the text.
|
||||
|
||||
A section "Entitled XYZ" means a named subunit of the Document whose
|
||||
title either is precisely XYZ or contains XYZ in parentheses following
|
||||
text that translates XYZ in another language. (Here XYZ stands for a
|
||||
specific section name mentioned below, such as "Acknowledgements",
|
||||
"Dedications", "Endorsements", or "History".) To "Preserve the Title"
|
||||
of such a section when you modify the Document means that it remains a
|
||||
section "Entitled XYZ" according to this definition.
|
||||
|
||||
The Document may include Warranty Disclaimers next to the notice which
|
||||
states that this License applies to the Document. These Warranty
|
||||
Disclaimers are considered to be included by reference in this
|
||||
License, but only as regards disclaiming warranties: any other
|
||||
implication that these Warranty Disclaimers may have is void and has
|
||||
no effect on the meaning of this License.
|
||||
|
||||
|
||||
2. VERBATIM COPYING
|
||||
|
||||
You may copy and distribute the Document in any medium, either
|
||||
commercially or noncommercially, provided that this License, the
|
||||
copyright notices, and the license notice saying this License applies
|
||||
to the Document are reproduced in all copies, and that you add no other
|
||||
conditions whatsoever to those of this License. You may not use
|
||||
technical measures to obstruct or control the reading or further
|
||||
copying of the copies you make or distribute. However, you may accept
|
||||
compensation in exchange for copies. If you distribute a large enough
|
||||
number of copies you must also follow the conditions in section 3.
|
||||
|
||||
You may also lend copies, under the same conditions stated above, and
|
||||
you may publicly display copies.
|
||||
|
||||
|
||||
3. COPYING IN QUANTITY
|
||||
|
||||
If you publish printed copies (or copies in media that commonly have
|
||||
printed covers) of the Document, numbering more than 100, and the
|
||||
Document's license notice requires Cover Texts, you must enclose the
|
||||
copies in covers that carry, clearly and legibly, all these Cover
|
||||
Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
|
||||
the back cover. Both covers must also clearly and legibly identify
|
||||
you as the publisher of these copies. The front cover must present
|
||||
the full title with all words of the title equally prominent and
|
||||
visible. You may add other material on the covers in addition.
|
||||
Copying with changes limited to the covers, as long as they preserve
|
||||
the title of the Document and satisfy these conditions, can be treated
|
||||
as verbatim copying in other respects.
|
||||
|
||||
If the required texts for either cover are too voluminous to fit
|
||||
legibly, you should put the first ones listed (as many as fit
|
||||
reasonably) on the actual cover, and continue the rest onto adjacent
|
||||
pages.
|
||||
|
||||
If you publish or distribute Opaque copies of the Document numbering
|
||||
more than 100, you must either include a machine-readable Transparent
|
||||
copy along with each Opaque copy, or state in or with each Opaque copy
|
||||
a computer-network location from which the general network-using
|
||||
public has access to download using public-standard network protocols
|
||||
a complete Transparent copy of the Document, free of added material.
|
||||
If you use the latter option, you must take reasonably prudent steps,
|
||||
when you begin distribution of Opaque copies in quantity, to ensure
|
||||
that this Transparent copy will remain thus accessible at the stated
|
||||
location until at least one year after the last time you distribute an
|
||||
Opaque copy (directly or through your agents or retailers) of that
|
||||
edition to the public.
|
||||
|
||||
It is requested, but not required, that you contact the authors of the
|
||||
Document well before redistributing any large number of copies, to give
|
||||
them a chance to provide you with an updated version of the Document.
|
||||
|
||||
|
||||
4. MODIFICATIONS
|
||||
|
||||
You may copy and distribute a Modified Version of the Document under
|
||||
the conditions of sections 2 and 3 above, provided that you release
|
||||
the Modified Version under precisely this License, with the Modified
|
||||
Version filling the role of the Document, thus licensing distribution
|
||||
and modification of the Modified Version to whoever possesses a copy
|
||||
of it. In addition, you must do these things in the Modified Version:
|
||||
|
||||
A. Use in the Title Page (and on the covers, if any) a title distinct
|
||||
from that of the Document, and from those of previous versions
|
||||
(which should, if there were any, be listed in the History section
|
||||
of the Document). You may use the same title as a previous version
|
||||
if the original publisher of that version gives permission.
|
||||
B. List on the Title Page, as authors, one or more persons or entities
|
||||
responsible for authorship of the modifications in the Modified
|
||||
Version, together with at least five of the principal authors of the
|
||||
Document (all of its principal authors, if it has fewer than five),
|
||||
unless they release you from this requirement.
|
||||
C. State on the Title page the name of the publisher of the
|
||||
Modified Version, as the publisher.
|
||||
D. Preserve all the copyright notices of the Document.
|
||||
E. Add an appropriate copyright notice for your modifications
|
||||
adjacent to the other copyright notices.
|
||||
F. Include, immediately after the copyright notices, a license notice
|
||||
giving the public permission to use the Modified Version under the
|
||||
terms of this License, in the form shown in the Addendum below.
|
||||
G. Preserve in that license notice the full lists of Invariant Sections
|
||||
and required Cover Texts given in the Document's license notice.
|
||||
H. Include an unaltered copy of this License.
|
||||
I. Preserve the section Entitled "History", Preserve its Title, and add
|
||||
to it an item stating at least the title, year, new authors, and
|
||||
publisher of the Modified Version as given on the Title Page. If
|
||||
there is no section Entitled "History" in the Document, create one
|
||||
stating the title, year, authors, and publisher of the Document as
|
||||
given on its Title Page, then add an item describing the Modified
|
||||
Version as stated in the previous sentence.
|
||||
J. Preserve the network location, if any, given in the Document for
|
||||
public access to a Transparent copy of the Document, and likewise
|
||||
the network locations given in the Document for previous versions
|
||||
it was based on. These may be placed in the "History" section.
|
||||
You may omit a network location for a work that was published at
|
||||
least four years before the Document itself, or if the original
|
||||
publisher of the version it refers to gives permission.
|
||||
K. For any section Entitled "Acknowledgements" or "Dedications",
|
||||
Preserve the Title of the section, and preserve in the section all
|
||||
the substance and tone of each of the contributor acknowledgements
|
||||
and/or dedications given therein.
|
||||
L. Preserve all the Invariant Sections of the Document,
|
||||
unaltered in their text and in their titles. Section numbers
|
||||
or the equivalent are not considered part of the section titles.
|
||||
M. Delete any section Entitled "Endorsements". Such a section
|
||||
may not be included in the Modified Version.
|
||||
N. Do not retitle any existing section to be Entitled "Endorsements"
|
||||
or to conflict in title with any Invariant Section.
|
||||
O. Preserve any Warranty Disclaimers.
|
||||
|
||||
If the Modified Version includes new front-matter sections or
|
||||
appendices that qualify as Secondary Sections and contain no material
|
||||
copied from the Document, you may at your option designate some or all
|
||||
of these sections as invariant. To do this, add their titles to the
|
||||
list of Invariant Sections in the Modified Version's license notice.
|
||||
These titles must be distinct from any other section titles.
|
||||
|
||||
You may add a section Entitled "Endorsements", provided it contains
|
||||
nothing but endorsements of your Modified Version by various
|
||||
parties--for example, statements of peer review or that the text has
|
||||
been approved by an organization as the authoritative definition of a
|
||||
standard.
|
||||
|
||||
You may add a passage of up to five words as a Front-Cover Text, and a
|
||||
passage of up to 25 words as a Back-Cover Text, to the end of the list
|
||||
of Cover Texts in the Modified Version. Only one passage of
|
||||
Front-Cover Text and one of Back-Cover Text may be added by (or
|
||||
through arrangements made by) any one entity. If the Document already
|
||||
includes a cover text for the same cover, previously added by you or
|
||||
by arrangement made by the same entity you are acting on behalf of,
|
||||
you may not add another; but you may replace the old one, on explicit
|
||||
permission from the previous publisher that added the old one.
|
||||
|
||||
The author(s) and publisher(s) of the Document do not by this License
|
||||
give permission to use their names for publicity for or to assert or
|
||||
imply endorsement of any Modified Version.
|
||||
|
||||
|
||||
5. COMBINING DOCUMENTS
|
||||
|
||||
You may combine the Document with other documents released under this
|
||||
License, under the terms defined in section 4 above for modified
|
||||
versions, provided that you include in the combination all of the
|
||||
Invariant Sections of all of the original documents, unmodified, and
|
||||
list them all as Invariant Sections of your combined work in its
|
||||
license notice, and that you preserve all their Warranty Disclaimers.
|
||||
|
||||
The combined work need only contain one copy of this License, and
|
||||
multiple identical Invariant Sections may be replaced with a single
|
||||
copy. If there are multiple Invariant Sections with the same name but
|
||||
different contents, make the title of each such section unique by
|
||||
adding at the end of it, in parentheses, the name of the original
|
||||
author or publisher of that section if known, or else a unique number.
|
||||
Make the same adjustment to the section titles in the list of
|
||||
Invariant Sections in the license notice of the combined work.
|
||||
|
||||
In the combination, you must combine any sections Entitled "History"
|
||||
in the various original documents, forming one section Entitled
|
||||
"History"; likewise combine any sections Entitled "Acknowledgements",
|
||||
and any sections Entitled "Dedications". You must delete all sections
|
||||
Entitled "Endorsements".
|
||||
|
||||
|
||||
6. COLLECTIONS OF DOCUMENTS
|
||||
|
||||
You may make a collection consisting of the Document and other documents
|
||||
released under this License, and replace the individual copies of this
|
||||
License in the various documents with a single copy that is included in
|
||||
the collection, provided that you follow the rules of this License for
|
||||
verbatim copying of each of the documents in all other respects.
|
||||
|
||||
You may extract a single document from such a collection, and distribute
|
||||
it individually under this License, provided you insert a copy of this
|
||||
License into the extracted document, and follow this License in all
|
||||
other respects regarding verbatim copying of that document.
|
||||
|
||||
|
||||
7. AGGREGATION WITH INDEPENDENT WORKS
|
||||
|
||||
A compilation of the Document or its derivatives with other separate
|
||||
and independent documents or works, in or on a volume of a storage or
|
||||
distribution medium, is called an "aggregate" if the copyright
|
||||
resulting from the compilation is not used to limit the legal rights
|
||||
of the compilation's users beyond what the individual works permit.
|
||||
When the Document is included in an aggregate, this License does not
|
||||
apply to the other works in the aggregate which are not themselves
|
||||
derivative works of the Document.
|
||||
|
||||
If the Cover Text requirement of section 3 is applicable to these
|
||||
copies of the Document, then if the Document is less than one half of
|
||||
the entire aggregate, the Document's Cover Texts may be placed on
|
||||
covers that bracket the Document within the aggregate, or the
|
||||
electronic equivalent of covers if the Document is in electronic form.
|
||||
Otherwise they must appear on printed covers that bracket the whole
|
||||
aggregate.
|
||||
|
||||
|
||||
8. TRANSLATION
|
||||
|
||||
Translation is considered a kind of modification, so you may
|
||||
distribute translations of the Document under the terms of section 4.
|
||||
Replacing Invariant Sections with translations requires special
|
||||
permission from their copyright holders, but you may include
|
||||
translations of some or all Invariant Sections in addition to the
|
||||
original versions of these Invariant Sections. You may include a
|
||||
translation of this License, and all the license notices in the
|
||||
Document, and any Warranty Disclaimers, provided that you also include
|
||||
the original English version of this License and the original versions
|
||||
of those notices and disclaimers. In case of a disagreement between
|
||||
the translation and the original version of this License or a notice
|
||||
or disclaimer, the original version will prevail.
|
||||
|
||||
If a section in the Document is Entitled "Acknowledgements",
|
||||
"Dedications", or "History", the requirement (section 4) to Preserve
|
||||
its Title (section 1) will typically require changing the actual
|
||||
title.
|
||||
|
||||
|
||||
9. TERMINATION
|
||||
|
||||
You may not copy, modify, sublicense, or distribute the Document except
|
||||
as expressly provided for under this License. Any other attempt to
|
||||
copy, modify, sublicense or distribute the Document is void, and will
|
||||
automatically terminate your rights under this License. However,
|
||||
parties who have received copies, or rights, from you under this
|
||||
License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
|
||||
10. FUTURE REVISIONS OF THIS LICENSE
|
||||
|
||||
The Free Software Foundation may publish new, revised versions
|
||||
of the GNU Free Documentation License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns. See
|
||||
http://www.gnu.org/copyleft/.
|
||||
|
||||
Each version of the License is given a distinguishing version number.
|
||||
If the Document specifies that a particular numbered version of this
|
||||
License "or any later version" applies to it, you have the option of
|
||||
following the terms and conditions either of that specified version or
|
||||
of any later version that has been published (not as a draft) by the
|
||||
Free Software Foundation. If the Document does not specify a version
|
||||
number of this License, you may choose any version ever published (not
|
||||
as a draft) by the Free Software Foundation.
|
||||
|
||||
|
||||
ADDENDUM: How to use this License for your documents
|
||||
|
||||
To use this License in a document you have written, include a copy of
|
||||
the License in the document and put the following copyright and
|
||||
license notices just after the title page:
|
||||
|
||||
Copyright (c) YEAR YOUR NAME.
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.2
|
||||
or any later version published by the Free Software Foundation;
|
||||
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
|
||||
A copy of the license is included in the section entitled "GNU
|
||||
Free Documentation License".
|
||||
|
||||
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
|
||||
replace the "with...Texts." line with this:
|
||||
|
||||
with the Invariant Sections being LIST THEIR TITLES, with the
|
||||
Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
|
||||
|
||||
If you have Invariant Sections without Cover Texts, or some other
|
||||
combination of the three, merge those two alternatives to suit the
|
||||
situation.
|
||||
|
||||
If your document contains nontrivial examples of program code, we
|
||||
recommend releasing these examples in parallel under your choice of
|
||||
free software license, such as the GNU General Public License,
|
||||
to permit their use in free software.
|
||||
339
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/COPYING.GPL
Normal file
339
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/COPYING.GPL
Normal file
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 2 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, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,504 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
Copyright (C) 2000-2009 Josh Coalson
|
||||
Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
- Neither the name of the Xiph.org Foundation nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,46 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2001-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FLAC__ASSERT_H
|
||||
#define FLAC__ASSERT_H
|
||||
|
||||
/* we need this since some compilers (like MSVC) leave assert()s on release code (and we don't want to use their ASSERT) */
|
||||
#ifndef NDEBUG
|
||||
#include <assert.h>
|
||||
#define FLAC__ASSERT(x) assert(x)
|
||||
#define FLAC__ASSERT_DECLARATION(x) x
|
||||
#else
|
||||
#define FLAC__ASSERT(x)
|
||||
#define FLAC__ASSERT_DECLARATION(x)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,185 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2004-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FLAC__CALLBACK_H
|
||||
#define FLAC__CALLBACK_H
|
||||
|
||||
#include "ordinals.h"
|
||||
#include <stdlib.h> /* for size_t */
|
||||
|
||||
/** \file include/FLAC/callback.h
|
||||
*
|
||||
* \brief
|
||||
* This module defines the structures for describing I/O callbacks
|
||||
* to the other FLAC interfaces.
|
||||
*
|
||||
* See the detailed documentation for callbacks in the
|
||||
* \link flac_callbacks callbacks \endlink module.
|
||||
*/
|
||||
|
||||
/** \defgroup flac_callbacks FLAC/callback.h: I/O callback structures
|
||||
* \ingroup flac
|
||||
*
|
||||
* \brief
|
||||
* This module defines the structures for describing I/O callbacks
|
||||
* to the other FLAC interfaces.
|
||||
*
|
||||
* The purpose of the I/O callback functions is to create a common way
|
||||
* for the metadata interfaces to handle I/O.
|
||||
*
|
||||
* Originally the metadata interfaces required filenames as the way of
|
||||
* specifying FLAC files to operate on. This is problematic in some
|
||||
* environments so there is an additional option to specify a set of
|
||||
* callbacks for doing I/O on the FLAC file, instead of the filename.
|
||||
*
|
||||
* In addition to the callbacks, a FLAC__IOHandle type is defined as an
|
||||
* opaque structure for a data source.
|
||||
*
|
||||
* The callback function prototypes are similar (but not identical) to the
|
||||
* stdio functions fread, fwrite, fseek, ftell, feof, and fclose. If you use
|
||||
* stdio streams to implement the callbacks, you can pass fread, fwrite, and
|
||||
* fclose anywhere a FLAC__IOCallback_Read, FLAC__IOCallback_Write, or
|
||||
* FLAC__IOCallback_Close is required, and a FILE* anywhere a FLAC__IOHandle
|
||||
* is required. \warning You generally CANNOT directly use fseek or ftell
|
||||
* for FLAC__IOCallback_Seek or FLAC__IOCallback_Tell since on most systems
|
||||
* these use 32-bit offsets and FLAC requires 64-bit offsets to deal with
|
||||
* large files. You will have to find an equivalent function (e.g. ftello),
|
||||
* or write a wrapper. The same is true for feof() since this is usually
|
||||
* implemented as a macro, not as a function whose address can be taken.
|
||||
*
|
||||
* \{
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** This is the opaque handle type used by the callbacks. Typically
|
||||
* this is a \c FILE* or address of a file descriptor.
|
||||
*/
|
||||
typedef void* FLAC__IOHandle;
|
||||
|
||||
/** Signature for the read callback.
|
||||
* The signature and semantics match POSIX fread() implementations
|
||||
* and can generally be used interchangeably.
|
||||
*
|
||||
* \param ptr The address of the read buffer.
|
||||
* \param size The size of the records to be read.
|
||||
* \param nmemb The number of records to be read.
|
||||
* \param handle The handle to the data source.
|
||||
* \retval size_t
|
||||
* The number of records read.
|
||||
*/
|
||||
typedef size_t (*FLAC__IOCallback_Read) (void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle);
|
||||
|
||||
/** Signature for the write callback.
|
||||
* The signature and semantics match POSIX fwrite() implementations
|
||||
* and can generally be used interchangeably.
|
||||
*
|
||||
* \param ptr The address of the write buffer.
|
||||
* \param size The size of the records to be written.
|
||||
* \param nmemb The number of records to be written.
|
||||
* \param handle The handle to the data source.
|
||||
* \retval size_t
|
||||
* The number of records written.
|
||||
*/
|
||||
typedef size_t (*FLAC__IOCallback_Write) (const void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle);
|
||||
|
||||
/** Signature for the seek callback.
|
||||
* The signature and semantics mostly match POSIX fseek() WITH ONE IMPORTANT
|
||||
* EXCEPTION: the offset is a 64-bit type whereas fseek() is generally 'long'
|
||||
* and 32-bits wide.
|
||||
*
|
||||
* \param handle The handle to the data source.
|
||||
* \param offset The new position, relative to \a whence
|
||||
* \param whence \c SEEK_SET, \c SEEK_CUR, or \c SEEK_END
|
||||
* \retval int
|
||||
* \c 0 on success, \c -1 on error.
|
||||
*/
|
||||
typedef int (*FLAC__IOCallback_Seek) (FLAC__IOHandle handle, FLAC__int64 offset, int whence);
|
||||
|
||||
/** Signature for the tell callback.
|
||||
* The signature and semantics mostly match POSIX ftell() WITH ONE IMPORTANT
|
||||
* EXCEPTION: the offset is a 64-bit type whereas ftell() is generally 'long'
|
||||
* and 32-bits wide.
|
||||
*
|
||||
* \param handle The handle to the data source.
|
||||
* \retval FLAC__int64
|
||||
* The current position on success, \c -1 on error.
|
||||
*/
|
||||
typedef FLAC__int64 (*FLAC__IOCallback_Tell) (FLAC__IOHandle handle);
|
||||
|
||||
/** Signature for the EOF callback.
|
||||
* The signature and semantics mostly match POSIX feof() but WATCHOUT:
|
||||
* on many systems, feof() is a macro, so in this case a wrapper function
|
||||
* must be provided instead.
|
||||
*
|
||||
* \param handle The handle to the data source.
|
||||
* \retval int
|
||||
* \c 0 if not at end of file, nonzero if at end of file.
|
||||
*/
|
||||
typedef int (*FLAC__IOCallback_Eof) (FLAC__IOHandle handle);
|
||||
|
||||
/** Signature for the close callback.
|
||||
* The signature and semantics match POSIX fclose() implementations
|
||||
* and can generally be used interchangeably.
|
||||
*
|
||||
* \param handle The handle to the data source.
|
||||
* \retval int
|
||||
* \c 0 on success, \c EOF on error.
|
||||
*/
|
||||
typedef int (*FLAC__IOCallback_Close) (FLAC__IOHandle handle);
|
||||
|
||||
/** A structure for holding a set of callbacks.
|
||||
* Each FLAC interface that requires a FLAC__IOCallbacks structure will
|
||||
* describe which of the callbacks are required. The ones that are not
|
||||
* required may be set to NULL.
|
||||
*
|
||||
* If the seek requirement for an interface is optional, you can signify that
|
||||
* a data source is not seekable by setting the \a seek field to \c NULL.
|
||||
*/
|
||||
typedef struct {
|
||||
FLAC__IOCallback_Read read;
|
||||
FLAC__IOCallback_Write write;
|
||||
FLAC__IOCallback_Seek seek;
|
||||
FLAC__IOCallback_Tell tell;
|
||||
FLAC__IOCallback_Eof eof;
|
||||
FLAC__IOCallback_Close close;
|
||||
} FLAC__IOCallbacks;
|
||||
|
||||
/* \} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,97 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2000-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FLAC__EXPORT_H
|
||||
#define FLAC__EXPORT_H
|
||||
|
||||
/** \file include/FLAC/export.h
|
||||
*
|
||||
* \brief
|
||||
* This module contains \#defines and symbols for exporting function
|
||||
* calls, and providing version information and compiled-in features.
|
||||
*
|
||||
* See the \link flac_export export \endlink module.
|
||||
*/
|
||||
|
||||
/** \defgroup flac_export FLAC/export.h: export symbols
|
||||
* \ingroup flac
|
||||
*
|
||||
* \brief
|
||||
* This module contains \#defines and symbols for exporting function
|
||||
* calls, and providing version information and compiled-in features.
|
||||
*
|
||||
* If you are compiling with MSVC and will link to the static library
|
||||
* (libFLAC.lib) you should define FLAC__NO_DLL in your project to
|
||||
* make sure the symbols are exported properly.
|
||||
*
|
||||
* \{
|
||||
*/
|
||||
|
||||
#if defined(FLAC__NO_DLL)
|
||||
#define FLAC_API
|
||||
|
||||
#elif defined(_WIN32)
|
||||
#ifdef FLAC_API_EXPORTS
|
||||
#define FLAC_API __declspec(dllexport)
|
||||
#else
|
||||
#define FLAC_API __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
#elif defined(FLAC__USE_VISIBILITY_ATTR)
|
||||
#define FLAC_API __attribute__ ((visibility ("default")))
|
||||
|
||||
#else
|
||||
#define FLAC_API
|
||||
|
||||
#endif
|
||||
|
||||
/** These \#defines will mirror the libtool-based library version number, see
|
||||
* http://www.gnu.org/software/libtool/manual/libtool.html#Libtool-versioning
|
||||
*/
|
||||
#define FLAC_API_VERSION_CURRENT 11
|
||||
#define FLAC_API_VERSION_REVISION 0 /**< see above */
|
||||
#define FLAC_API_VERSION_AGE 3 /**< see above */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \c 1 if the library has been compiled with support for Ogg FLAC, else \c 0. */
|
||||
extern FLAC_API int FLAC_API_SUPPORTS_OGG_FLAC;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/* \} */
|
||||
|
||||
#endif
|
||||
1025
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/FLAC/format.h
Normal file
1025
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/FLAC/format.h
Normal file
File diff suppressed because it is too large
Load Diff
2182
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/FLAC/metadata.h
Normal file
2182
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/FLAC/metadata.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2000-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FLAC__ORDINALS_H
|
||||
#define FLAC__ORDINALS_H
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1600
|
||||
|
||||
/* Microsoft Visual Studio earlier than the 2010 version did not provide
|
||||
* the 1999 ISO C Standard header file <stdint.h>.
|
||||
*/
|
||||
|
||||
typedef signed __int8 FLAC__int8;
|
||||
typedef signed __int16 FLAC__int16;
|
||||
typedef signed __int32 FLAC__int32;
|
||||
typedef signed __int64 FLAC__int64;
|
||||
typedef unsigned __int8 FLAC__uint8;
|
||||
typedef unsigned __int16 FLAC__uint16;
|
||||
typedef unsigned __int32 FLAC__uint32;
|
||||
typedef unsigned __int64 FLAC__uint64;
|
||||
|
||||
#else
|
||||
|
||||
/* For MSVC 2010 and everything else which provides <stdint.h>. */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef int8_t FLAC__int8;
|
||||
typedef uint8_t FLAC__uint8;
|
||||
|
||||
typedef int16_t FLAC__int16;
|
||||
typedef int32_t FLAC__int32;
|
||||
typedef int64_t FLAC__int64;
|
||||
typedef uint16_t FLAC__uint16;
|
||||
typedef uint32_t FLAC__uint32;
|
||||
typedef uint64_t FLAC__uint64;
|
||||
|
||||
#endif
|
||||
|
||||
typedef int FLAC__bool;
|
||||
|
||||
typedef FLAC__uint8 FLAC__byte;
|
||||
|
||||
|
||||
#ifdef true
|
||||
#undef true
|
||||
#endif
|
||||
#ifdef false
|
||||
#undef false
|
||||
#endif
|
||||
#ifndef __cplusplus
|
||||
#define true 1
|
||||
#define false 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
254
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/README
Normal file
254
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/README
Normal file
@@ -0,0 +1,254 @@
|
||||
/* FLAC - Free Lossless Audio Codec
|
||||
* Copyright (C) 2001-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
*
|
||||
* This file is part the FLAC project. FLAC is comprised of several
|
||||
* components distributed under different licenses. The codec libraries
|
||||
* are distributed under Xiph.Org's BSD-like license (see the file
|
||||
* COPYING.Xiph in this distribution). All other programs, libraries, and
|
||||
* plugins are distributed under the LGPL or GPL (see COPYING.LGPL and
|
||||
* COPYING.GPL). The documentation is distributed under the Gnu FDL (see
|
||||
* COPYING.FDL). Each file in the FLAC distribution contains at the top the
|
||||
* terms under which it may be distributed.
|
||||
*
|
||||
* Since this particular file is relevant to all components of FLAC,
|
||||
* it may be distributed under the Xiph.Org license, which is the least
|
||||
* restrictive of those mentioned above. See the file COPYING.Xiph in this
|
||||
* distribution.
|
||||
*/
|
||||
|
||||
|
||||
FLAC is an Open Source lossless audio codec developed by Josh Coalson from 2001
|
||||
to 2009.
|
||||
|
||||
From January 2012 FLAC is being maintained by Erik de Castro Lopo under the
|
||||
auspices of the Xiph.org Foundation.
|
||||
|
||||
FLAC is comprised of
|
||||
* `libFLAC', a library which implements reference encoders and
|
||||
decoders for native FLAC and Ogg FLAC, and a metadata interface
|
||||
* `libFLAC++', a C++ object wrapper library around libFLAC
|
||||
* `flac', a command-line program for encoding and decoding files
|
||||
* `metaflac', a command-line program for viewing and editing FLAC
|
||||
metadata
|
||||
* player plugin for XMMS
|
||||
* user and API documentation
|
||||
|
||||
The libraries (libFLAC, libFLAC++) are
|
||||
licensed under Xiph.org's BSD-like license (see COPYING.Xiph). All other
|
||||
programs and plugins are licensed under the GNU General Public License
|
||||
(see COPYING.GPL). The documentation is licensed under the GNU Free
|
||||
Documentation License (see COPYING.FDL).
|
||||
|
||||
|
||||
===============================================================================
|
||||
FLAC - 1.3.2 - Contents
|
||||
===============================================================================
|
||||
|
||||
- Introduction
|
||||
- Prerequisites
|
||||
- Note to embedded developers
|
||||
- Building in a GNU environment
|
||||
- Building with Makefile.lite
|
||||
- Building with MSVC
|
||||
- Building on Mac OS X
|
||||
|
||||
|
||||
===============================================================================
|
||||
Introduction
|
||||
===============================================================================
|
||||
|
||||
This is the source release for the FLAC project. See
|
||||
|
||||
doc/html/index.html
|
||||
|
||||
for full documentation.
|
||||
|
||||
A brief description of the directory tree:
|
||||
|
||||
doc/ the HTML documentation
|
||||
examples/ example programs demonstrating the use of libFLAC and libFLAC++
|
||||
include/ public include files for libFLAC and libFLAC++
|
||||
man/ the man pages for `flac' and `metaflac'
|
||||
src/ the source code and private headers
|
||||
test/ the test scripts
|
||||
|
||||
If you have questions about building FLAC that this document does not answer,
|
||||
please submit them at the following tracker so this document can be improved:
|
||||
|
||||
https://sourceforge.net/p/flac/support-requests/
|
||||
|
||||
|
||||
===============================================================================
|
||||
Prerequisites
|
||||
===============================================================================
|
||||
|
||||
To build FLAC with support for Ogg FLAC you must have built and installed
|
||||
libogg according to the specific instructions below. You must have
|
||||
libogg 1.1.2 or greater, or there will be seeking problems with Ogg FLAC.
|
||||
|
||||
If you are building on x86 and want the assembly optimizations, you will
|
||||
need to have NASM >= 0.98.30 installed according to the specific instructions
|
||||
below.
|
||||
|
||||
|
||||
===============================================================================
|
||||
Note to embedded developers
|
||||
===============================================================================
|
||||
|
||||
libFLAC has grown larger over time as more functionality has been
|
||||
included, but much of it may be unnecessary for a particular embedded
|
||||
implementation. Unused parts may be pruned by some simple editing of
|
||||
configure.ac and src/libFLAC/Makefile.am; the following dependency
|
||||
graph shows which modules may be pruned without breaking things
|
||||
further down:
|
||||
|
||||
metadata.h
|
||||
stream_decoder.h
|
||||
format.h
|
||||
|
||||
stream_encoder.h
|
||||
stream_decoder.h
|
||||
format.h
|
||||
|
||||
stream_decoder.h
|
||||
format.h
|
||||
|
||||
In other words, for pure decoding applications, both the stream encoder
|
||||
and metadata editing interfaces can be safely removed.
|
||||
|
||||
There is a section dedicated to embedded use in the libFLAC API
|
||||
HTML documentation (see doc/html/api/index.html).
|
||||
|
||||
Also, there are several places in the libFLAC code with comments marked
|
||||
with "OPT:" where a #define can be changed to enable code that might be
|
||||
faster on a specific platform. Experimenting with these can yield faster
|
||||
binaries.
|
||||
|
||||
|
||||
===============================================================================
|
||||
Building in a GNU environment
|
||||
===============================================================================
|
||||
|
||||
FLAC uses autoconf and libtool for configuring and building.
|
||||
Better documentation for these will be forthcoming, but in
|
||||
general, this should work:
|
||||
|
||||
./configure && make && make check && make install
|
||||
|
||||
The 'make check' step is optional; omit it to skip all the tests,
|
||||
which can take several hours and use around 70-80 megs of disk space.
|
||||
Even though it will stop with an explicit message on any failure, it
|
||||
does print out a lot of stuff so you might want to capture the output
|
||||
to a file if you're having a problem. Also, don't run 'make check'
|
||||
as root because it confuses some of the tests.
|
||||
|
||||
NOTE: Despite our best efforts it's entirely possible to have
|
||||
problems when using older versions of autoconf, automake, or
|
||||
libtool. If you have the latest versions and still can't get it
|
||||
to work, see the next section on Makefile.lite.
|
||||
|
||||
There are a few FLAC-specific arguments you can give to
|
||||
`configure':
|
||||
|
||||
--enable-debug : Builds everything with debug symbols and some
|
||||
extra (and more verbose) error checking.
|
||||
|
||||
--disable-asm-optimizations : Disables the compilation of the
|
||||
assembly routines. Many routines have assembly versions for
|
||||
speed and `configure' is pretty good about knowing what is
|
||||
supported, but you can use this option to build only from the
|
||||
C sources. May be necessary for building on OS X (Intel).
|
||||
|
||||
--enable-sse : If you are building for an x86 CPU that supports
|
||||
SSE instructions, you can enable some of the faster routines
|
||||
if your operating system also supports SSE instructions. flac
|
||||
can tell if the CPU supports the instructions but currently has
|
||||
no way to test if the OS does, so if it does, you must pass
|
||||
this argument to configure to use the SSE routines. If flac
|
||||
crashes when built with this option you will have to go back and
|
||||
configure without --enable-sse. Note that
|
||||
--disable-asm-optimizations implies --disable-sse.
|
||||
|
||||
--enable-local-xmms-plugin : Installs the FLAC XMMS plugin in
|
||||
$HOME/.xmms/Plugins, instead of the global XMMS plugin area
|
||||
(usually /usr/lib/xmms/Input).
|
||||
|
||||
--with-ogg=
|
||||
--with-xmms-prefix=
|
||||
--with-libiconv-prefix=
|
||||
Use these if you have these packages but configure can't find them.
|
||||
|
||||
If you want to build completely from scratch (i.e. starting with just
|
||||
configure.ac and Makefile.am) you should be able to just run 'autogen.sh'
|
||||
but make sure and read the comments in that file first.
|
||||
|
||||
|
||||
===============================================================================
|
||||
Building with Makefile.lite
|
||||
===============================================================================
|
||||
|
||||
There is a more lightweight build system for do-it-yourself-ers.
|
||||
It is also useful if configure isn't working, which may be the
|
||||
case since lately we've had some problems with different versions
|
||||
of automake and libtool. The Makefile.lite system should work
|
||||
on GNU systems with few or no adjustments.
|
||||
|
||||
From the top level just 'make -f Makefile.lite'. You can
|
||||
specify zero or one optional target from 'release', 'debug',
|
||||
'test', or 'clean'. The default is 'release'. There is no
|
||||
'install' target but everything you need will end up in the
|
||||
obj/ directory.
|
||||
|
||||
If you are not on an x86 system or you don't have nasm, you
|
||||
may have to change the DEFINES in src/libFLAC/Makefile.lite. If
|
||||
you don't have nasm, remove -DFLAC__HAS_NASM. If your target is
|
||||
not an x86, change -DFLAC__CPU_IA32 to -DFLAC__CPU_UNKNOWN.
|
||||
|
||||
|
||||
===============================================================================
|
||||
Building with MSVC
|
||||
===============================================================================
|
||||
|
||||
There are .vcproj projects and a master FLAC.sln solution to build all
|
||||
the libraries and executables with MSVC 2005 or newer.
|
||||
|
||||
Prerequisite: you must have the Ogg libraries installed as described
|
||||
later.
|
||||
|
||||
Prerequisite: you must have nasm installed, and nasm.exe must be in
|
||||
your PATH, or the path to nasm.exe must be added to the list of
|
||||
directories for executable files in the MSVC global options.
|
||||
|
||||
To build everything, run Visual Studio, do File|Open and open FLAC.sln.
|
||||
From the dropdown in the toolbar, select "Release" instead of "Debug",
|
||||
then do Build|Build Solution.
|
||||
|
||||
This will build all libraries both statically (e.g.
|
||||
objs\release\lib\libFLAC_static.lib) and as DLLs (e.g.
|
||||
objs\release\lib\libFLAC.dll), and it will build all binaries, statically
|
||||
linked (e.g. objs\release\bin\flac.exe).
|
||||
|
||||
Everything will end up in the "objs" directory. DLLs and .exe files
|
||||
are all that are needed and can be copied to an installation area and
|
||||
added to the PATH.
|
||||
|
||||
By default the code is configured with Ogg support. Before building FLAC
|
||||
you will need to get the Ogg source distribution
|
||||
(see http://xiph.org/downloads/), build libogg_static.lib (load
|
||||
win32\libogg_static.sln, change solution configuration to "Release" and
|
||||
code generation to "Multi-threaded (/MT)", then build), copy libogg_static.lib
|
||||
into FLAC's 'objs\release\lib' directory, and copy the entire include\ogg tree
|
||||
into FLAC's 'include' directory (so that there is an 'ogg' directory in FLAC's
|
||||
'include' directory with the files ogg.h, os_types.h and config_types.h).
|
||||
|
||||
If you want to build without Ogg support, instead edit all .vcproj files
|
||||
and remove any "FLAC__HAS_OGG" definitions.
|
||||
|
||||
|
||||
===============================================================================
|
||||
Building on Mac OS X
|
||||
===============================================================================
|
||||
|
||||
If you have Fink or a recent version of OS X with the proper autotools,
|
||||
the GNU flow above should work.
|
||||
@@ -0,0 +1,5 @@
|
||||
This is LIBFLAC 1.3.2 ported to the ESP8266 by Earle F. Philhower, III
|
||||
<earlephilhower@yahoo.com>. It's pretty much unchanged from base sourcew,
|
||||
with some CRC and string constants moved into PROGMEM. Headers have been
|
||||
purged of references to STDIO/FILE*. Files not needed for playback have
|
||||
been removed.
|
||||
@@ -0,0 +1,75 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2001-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
//#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
//#endif
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
#include "private/bitmath.h"
|
||||
|
||||
/* An example of what FLAC__bitmath_silog2() computes:
|
||||
*
|
||||
* silog2(-10) = 5
|
||||
* silog2(- 9) = 5
|
||||
* silog2(- 8) = 4
|
||||
* silog2(- 7) = 4
|
||||
* silog2(- 6) = 4
|
||||
* silog2(- 5) = 4
|
||||
* silog2(- 4) = 3
|
||||
* silog2(- 3) = 3
|
||||
* silog2(- 2) = 2
|
||||
* silog2(- 1) = 2
|
||||
* silog2( 0) = 0
|
||||
* silog2( 1) = 2
|
||||
* silog2( 2) = 3
|
||||
* silog2( 3) = 3
|
||||
* silog2( 4) = 4
|
||||
* silog2( 5) = 4
|
||||
* silog2( 6) = 4
|
||||
* silog2( 7) = 4
|
||||
* silog2( 8) = 5
|
||||
* silog2( 9) = 5
|
||||
* silog2( 10) = 5
|
||||
*/
|
||||
uint32_t FLAC__bitmath_silog2(FLAC__int64 v)
|
||||
{
|
||||
if(v == 0)
|
||||
return 0;
|
||||
|
||||
if(v == -1)
|
||||
return 2;
|
||||
|
||||
v = (v < 0) ? (-(v+1)) : v;
|
||||
return FLAC__bitmath_ilog2_wide(v)+2;
|
||||
}
|
||||
1100
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/bitreader.c
Normal file
1100
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/bitreader.c
Normal file
File diff suppressed because it is too large
Load Diff
248
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/config.h
Normal file
248
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/config.h
Normal file
@@ -0,0 +1,248 @@
|
||||
#define PGM_READ_UNALIGNED 0
|
||||
|
||||
#ifdef DEBUG
|
||||
#undef NDEBUG
|
||||
#else
|
||||
#define NDEBUG
|
||||
#endif
|
||||
|
||||
/* config.h. Generated from config.h.in by configure. */
|
||||
/* config.h.in. Generated from configure.ac by autoheader. */
|
||||
|
||||
/* Define if building universal (internal helper macro) */
|
||||
/* #undef AC_APPLE_UNIVERSAL_BUILD */
|
||||
|
||||
/* Target processor is big endian. */
|
||||
#define CPU_IS_BIG_ENDIAN 0
|
||||
|
||||
/* Target processor is little endian. */
|
||||
#define CPU_IS_LITTLE_ENDIAN 1
|
||||
|
||||
/* Set FLAC__BYTES_PER_WORD to 8 (4 is the default) */
|
||||
#define ENABLE_64_BIT_WORDS 0
|
||||
|
||||
/* define to align allocated memory on 32-byte boundaries */
|
||||
#define FLAC__ALIGN_MALLOC_DATA 1
|
||||
|
||||
/* define if building for ia32/i386 */
|
||||
/* #undef FLAC__CPU_IA32 */
|
||||
|
||||
/* define if building for PowerPC */
|
||||
/* #undef FLAC__CPU_PPC */
|
||||
|
||||
/* define if building for PowerPC with SPE ABI */
|
||||
/* #undef FLAC__CPU_PPC_SPE */
|
||||
|
||||
/* define if building for SPARC */
|
||||
/* #undef FLAC__CPU_SPARC */
|
||||
|
||||
/* define if building for x86_64 */
|
||||
#undef FLAC__CPU_X86_64
|
||||
|
||||
/* define if you have docbook-to-man or docbook2man */
|
||||
#undef FLAC__HAS_DOCBOOK_TO_MAN
|
||||
|
||||
/* define if you are compiling for x86 and have the NASM assembler */
|
||||
#undef FLAC__HAS_NASM
|
||||
|
||||
/* define if you have the ogg library */
|
||||
#define FLAC__HAS_OGG 0
|
||||
|
||||
/* Set to 1 if <x86intrin.h> is available. */
|
||||
#undef FLAC__HAS_X86INTRIN
|
||||
|
||||
/* define to disable use of assembly code */
|
||||
#define FLAC__NO_ASM 1
|
||||
|
||||
/* define if building for Darwin / MacOS X */
|
||||
/* #undef FLAC__SYS_DARWIN */
|
||||
|
||||
/* define if building for Linux */
|
||||
#undef FLAC__SYS_LINUX
|
||||
|
||||
/* define to enable use of Altivec instructions */
|
||||
#undef FLAC__USE_ALTIVEC
|
||||
|
||||
/* define to enable use of AVX instructions */
|
||||
#undef FLAC__USE_AVX
|
||||
|
||||
/* Compiler has the __builtin_bswap16 intrinsic */
|
||||
#undef HAVE_BSWAP16
|
||||
|
||||
/* Compiler has the __builtin_bswap32 intrinsic */
|
||||
#undef HAVE_BSWAP32
|
||||
|
||||
/* Define to 1 if you have the <byteswap.h> header file. */
|
||||
#undef HAVE_BYTESWAP_H
|
||||
|
||||
/* define if you have clock_gettime */
|
||||
#undef HAVE_CLOCK_GETTIME
|
||||
|
||||
/* Define to 1 if you have the <cpuid.h> header file. */
|
||||
#undef HAVE_CPUID_H
|
||||
|
||||
/* Define to 1 if C++ supports variable-length arrays. */
|
||||
#define HAVE_CXX_VARARRAYS 1
|
||||
|
||||
/* Define to 1 if C supports variable-length arrays. */
|
||||
#define HAVE_C_VARARRAYS 1
|
||||
|
||||
/* Define to 1 if you have the <dlfcn.h> header file. */
|
||||
#undef HAVE_DLFCN_H
|
||||
|
||||
/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */
|
||||
#undef HAVE_FSEEKO
|
||||
|
||||
/* Define to 1 if you have the `getopt_long' function. */
|
||||
#undef HAVE_GETOPT_LONG
|
||||
|
||||
/* Define if you have the iconv() function and it works. */
|
||||
#undef HAVE_ICONV
|
||||
|
||||
/* Define to 1 if you have the <inttypes.h> header file. */
|
||||
#undef HAVE_INTTYPES_H
|
||||
|
||||
/* Define if you have <langinfo.h> and nl_langinfo(CODESET). */
|
||||
#define HAVE_LANGINFO_CODESET 1
|
||||
|
||||
/* lround support */
|
||||
#define HAVE_LROUND 1
|
||||
|
||||
/* Define to 1 if you have the <memory.h> header file. */
|
||||
#undef HAVE_MEMORY_H
|
||||
|
||||
/* Define to 1 if the system has the type `socklen_t'. */
|
||||
#undef HAVE_SOCKLEN_T
|
||||
|
||||
/* Define to 1 if you have the <stdint.h> header file. */
|
||||
#define HAVE_STDINT_H 1
|
||||
|
||||
/* Define to 1 if you have the <stdlib.h> header file. */
|
||||
#undef HAVE_STDLIB_H
|
||||
|
||||
/* Define to 1 if you have the <strings.h> header file. */
|
||||
#define HAVE_STRINGS_H 1
|
||||
|
||||
/* Define to 1 if you have the <string.h> header file. */
|
||||
#define HAVE_STRING_H 1
|
||||
|
||||
/* Define to 1 if you have the <sys/ioctl.h> header file. */
|
||||
#undef HAVE_SYS_IOCTL_H
|
||||
|
||||
/* Define to 1 if you have the <sys/param.h> header file. */
|
||||
#undef HAVE_SYS_PARAM_H
|
||||
|
||||
/* Define to 1 if you have the <sys/stat.h> header file. */
|
||||
#undef HAVE_SYS_STAT_H
|
||||
|
||||
/* Define to 1 if you have the <sys/types.h> header file. */
|
||||
#undef HAVE_SYS_TYPES_H
|
||||
|
||||
/* Define to 1 if you have the <termios.h> header file. */
|
||||
#undef HAVE_TERMIOS_H
|
||||
|
||||
/* Define to 1 if typeof works with your compiler. */
|
||||
#define HAVE_TYPEOF 1
|
||||
|
||||
/* Define to 1 if you have the <unistd.h> header file. */
|
||||
#undef HAVE_UNISTD_H
|
||||
|
||||
/* Define to 1 if you have the <x86intrin.h> header file. */
|
||||
#undef HAVE_X86INTRIN_H
|
||||
|
||||
/* Define as const if the declaration of iconv() needs const. */
|
||||
#define ICONV_CONST
|
||||
|
||||
/* Define to the sub-directory where libtool stores uninstalled libraries. */
|
||||
#define LT_OBJDIR ".libs/"
|
||||
|
||||
/* Name of package */
|
||||
#define PACKAGE "flac"
|
||||
|
||||
/* Define to the address where bug reports for this package should be sent. */
|
||||
#define PACKAGE_BUGREPORT "flac-dev@xiph.org"
|
||||
|
||||
/* Define to the full name of this package. */
|
||||
#define PACKAGE_NAME "flac"
|
||||
|
||||
/* Define to the full name and version of this package. */
|
||||
#define PACKAGE_STRING "flac 1.3.2"
|
||||
|
||||
/* Define to the one symbol short name of this package. */
|
||||
#define PACKAGE_TARNAME "flac"
|
||||
|
||||
/* Define to the home page for this package. */
|
||||
#define PACKAGE_URL "https://www.xiph.org/flac/"
|
||||
|
||||
/* Define to the version of this package. */
|
||||
#define PACKAGE_VERSION "1.3.2"
|
||||
|
||||
/* The size of `off_t', as computed by sizeof. */
|
||||
#define SIZEOF_OFF_T 4
|
||||
|
||||
/* The size of `void*', as computed by sizeof. */
|
||||
#define SIZEOF_VOIDP 4
|
||||
|
||||
/* Define to 1 if you have the ANSI C header files. */
|
||||
#define STDC_HEADERS 1
|
||||
|
||||
/* Enable extensions on AIX 3, Interix. */
|
||||
#ifndef _ALL_SOURCE
|
||||
# define _ALL_SOURCE 1
|
||||
#endif
|
||||
/* Enable GNU extensions on systems that have them. */
|
||||
#ifndef _GNU_SOURCE
|
||||
# define _GNU_SOURCE 1
|
||||
#endif
|
||||
/* Enable threading extensions on Solaris. */
|
||||
#ifndef _POSIX_PTHREAD_SEMANTICS
|
||||
# define _POSIX_PTHREAD_SEMANTICS 1
|
||||
#endif
|
||||
/* Enable extensions on HP NonStop. */
|
||||
#ifndef _TANDEM_SOURCE
|
||||
# define _TANDEM_SOURCE 1
|
||||
#endif
|
||||
/* Enable general extensions on Solaris. */
|
||||
#ifndef __EXTENSIONS__
|
||||
# define __EXTENSIONS__ 1
|
||||
#endif
|
||||
|
||||
|
||||
/* Version number of package */
|
||||
#define VERSION "1.3.2"
|
||||
|
||||
/* Target processor is big endian. */
|
||||
#define WORDS_BIGENDIAN 0
|
||||
|
||||
/* Enable large inode numbers on Mac OS X 10.5. */
|
||||
#ifndef _DARWIN_USE_64_BIT_INODE
|
||||
# define _DARWIN_USE_64_BIT_INODE 1
|
||||
#endif
|
||||
|
||||
/* Number of bits in a file offset, on hosts where this is settable. */
|
||||
/* #undef _FILE_OFFSET_BITS */
|
||||
|
||||
/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
|
||||
/* #undef _LARGEFILE_SOURCE */
|
||||
|
||||
/* Define for large files, on AIX-style hosts. */
|
||||
/* #undef _LARGE_FILES */
|
||||
|
||||
/* Define to 1 if on MINIX. */
|
||||
/* #undef _MINIX */
|
||||
|
||||
/* Define to 2 if the system does not provide POSIX.1 features except with
|
||||
this defined. */
|
||||
/* #undef _POSIX_1_SOURCE */
|
||||
|
||||
/* Define to 1 if you need to in order for `stat' and other things to work. */
|
||||
/* #undef _POSIX_SOURCE */
|
||||
|
||||
/* Define to `__inline__' or `__inline' if that's what the C compiler
|
||||
calls it, or to nothing if 'inline' is not supported under any name. */
|
||||
#ifndef __cplusplus
|
||||
/* #undef inline */
|
||||
#endif
|
||||
|
||||
/* Define to __typeof__ if your compiler spells it that way. */
|
||||
/* #undef typeof */
|
||||
299
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/cpu.c
Normal file
299
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/cpu.c
Normal file
@@ -0,0 +1,299 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2001-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
//#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
//#endif
|
||||
|
||||
#include "private/cpu.h"
|
||||
#include "share/compat.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined _MSC_VER
|
||||
#include <intrin.h> /* for __cpuid() and _xgetbv() */
|
||||
#elif defined __GNUC__ && defined HAVE_CPUID_H
|
||||
#include <cpuid.h> /* for __get_cpuid() and __get_cpuid_max() */
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
//#include <stdio.h>
|
||||
#define dfprintf fprintf
|
||||
#else
|
||||
/* This is bad practice, it should be a static void empty function */
|
||||
#define dfprintf(file, format, ...)
|
||||
#endif
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
#if defined FLAC__CPU_PPC
|
||||
#include <sys/auxv.h>
|
||||
#endif
|
||||
|
||||
#if (defined FLAC__CPU_IA32 || defined FLAC__CPU_X86_64) && (defined FLAC__HAS_NASM || FLAC__HAS_X86INTRIN) && !defined FLAC__NO_ASM
|
||||
|
||||
/* these are flags in EDX of CPUID AX=00000001 */
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_CMOV = 0x00008000;
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_MMX = 0x00800000;
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_SSE = 0x02000000;
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_SSE2 = 0x04000000;
|
||||
|
||||
/* these are flags in ECX of CPUID AX=00000001 */
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_SSE3 = 0x00000001;
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_SSSE3 = 0x00000200;
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_SSE41 = 0x00080000;
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_SSE42 = 0x00100000;
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_OSXSAVE = 0x08000000;
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_AVX = 0x10000000;
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_FMA = 0x00001000;
|
||||
|
||||
/* these are flags in EBX of CPUID AX=00000007 */
|
||||
static const uint32_t FLAC__CPUINFO_X86_CPUID_AVX2 = 0x00000020;
|
||||
|
||||
static uint32_t
|
||||
cpu_xgetbv_x86(void)
|
||||
{
|
||||
#if (defined _MSC_VER || defined __INTEL_COMPILER) && FLAC__AVX_SUPPORTED
|
||||
return (uint32_t)_xgetbv(0);
|
||||
#elif defined __GNUC__
|
||||
uint32_t lo, hi;
|
||||
__asm__ volatile (".byte 0x0f, 0x01, 0xd0" : "=a"(lo), "=d"(hi) : "c" (0));
|
||||
return lo;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
cpu_have_cpuid(void)
|
||||
{
|
||||
#if defined FLAC__CPU_X86_64 || defined __i686__ || defined __SSE__ || (defined _M_IX86_FP && _M_IX86_FP > 0)
|
||||
/* target CPU does have CPUID instruction */
|
||||
return 1;
|
||||
#elif defined FLAC__HAS_NASM
|
||||
return FLAC__cpu_have_cpuid_asm_ia32();
|
||||
#elif defined __GNUC__ && defined HAVE_CPUID_H
|
||||
if (__get_cpuid_max(0, 0) != 0)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
#elif defined _MSC_VER
|
||||
FLAC__uint32 flags1, flags2;
|
||||
__asm {
|
||||
pushfd
|
||||
pushfd
|
||||
pop eax
|
||||
mov flags1, eax
|
||||
xor eax, 0x200000
|
||||
push eax
|
||||
popfd
|
||||
pushfd
|
||||
pop eax
|
||||
mov flags2, eax
|
||||
popfd
|
||||
}
|
||||
if (((flags1^flags2) & 0x200000) != 0)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
cpuinfo_x86(FLAC__uint32 level, FLAC__uint32 *eax, FLAC__uint32 *ebx, FLAC__uint32 *ecx, FLAC__uint32 *edx)
|
||||
{
|
||||
#if defined _MSC_VER
|
||||
int cpuinfo[4];
|
||||
int ext = level & 0x80000000;
|
||||
__cpuid(cpuinfo, ext);
|
||||
if ((uint32_t)cpuinfo[0] >= level) {
|
||||
#if FLAC__AVX_SUPPORTED
|
||||
__cpuidex(cpuinfo, level, 0); /* for AVX2 detection */
|
||||
#else
|
||||
__cpuid(cpuinfo, level); /* some old compilers don't support __cpuidex */
|
||||
#endif
|
||||
*eax = cpuinfo[0]; *ebx = cpuinfo[1]; *ecx = cpuinfo[2]; *edx = cpuinfo[3];
|
||||
return;
|
||||
}
|
||||
#elif defined __GNUC__ && defined HAVE_CPUID_H
|
||||
FLAC__uint32 ext = level & 0x80000000;
|
||||
__cpuid(ext, *eax, *ebx, *ecx, *edx);
|
||||
if (*eax >= level) {
|
||||
__cpuid_count(level, 0, *eax, *ebx, *ecx, *edx);
|
||||
return;
|
||||
}
|
||||
#elif defined FLAC__HAS_NASM && defined FLAC__CPU_IA32
|
||||
FLAC__cpu_info_asm_ia32(level, eax, ebx, ecx, edx);
|
||||
return;
|
||||
#endif
|
||||
*eax = *ebx = *ecx = *edx = 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
x86_cpu_info (FLAC__CPUInfo *info)
|
||||
{
|
||||
#if (defined FLAC__CPU_IA32 || defined FLAC__CPU_X86_64) && (defined FLAC__HAS_NASM || FLAC__HAS_X86INTRIN) && !defined FLAC__NO_ASM
|
||||
FLAC__bool x86_osxsave = false;
|
||||
FLAC__bool os_avx = false;
|
||||
FLAC__uint32 flags_eax, flags_ebx, flags_ecx, flags_edx;
|
||||
|
||||
info->use_asm = true; /* we assume a minimum of 80386 */
|
||||
if (!cpu_have_cpuid())
|
||||
return;
|
||||
|
||||
cpuinfo_x86(0, &flags_eax, &flags_ebx, &flags_ecx, &flags_edx);
|
||||
info->x86.intel = (flags_ebx == 0x756E6547 && flags_edx == 0x49656E69 && flags_ecx == 0x6C65746E) ? true : false; /* GenuineIntel */
|
||||
cpuinfo_x86(1, &flags_eax, &flags_ebx, &flags_ecx, &flags_edx);
|
||||
|
||||
info->x86.cmov = (flags_edx & FLAC__CPUINFO_X86_CPUID_CMOV ) ? true : false;
|
||||
info->x86.mmx = (flags_edx & FLAC__CPUINFO_X86_CPUID_MMX ) ? true : false;
|
||||
info->x86.sse = (flags_edx & FLAC__CPUINFO_X86_CPUID_SSE ) ? true : false;
|
||||
info->x86.sse2 = (flags_edx & FLAC__CPUINFO_X86_CPUID_SSE2 ) ? true : false;
|
||||
info->x86.sse3 = (flags_ecx & FLAC__CPUINFO_X86_CPUID_SSE3 ) ? true : false;
|
||||
info->x86.ssse3 = (flags_ecx & FLAC__CPUINFO_X86_CPUID_SSSE3) ? true : false;
|
||||
info->x86.sse41 = (flags_ecx & FLAC__CPUINFO_X86_CPUID_SSE41) ? true : false;
|
||||
info->x86.sse42 = (flags_ecx & FLAC__CPUINFO_X86_CPUID_SSE42) ? true : false;
|
||||
|
||||
if (FLAC__AVX_SUPPORTED) {
|
||||
x86_osxsave = (flags_ecx & FLAC__CPUINFO_X86_CPUID_OSXSAVE) ? true : false;
|
||||
info->x86.avx = (flags_ecx & FLAC__CPUINFO_X86_CPUID_AVX ) ? true : false;
|
||||
info->x86.fma = (flags_ecx & FLAC__CPUINFO_X86_CPUID_FMA ) ? true : false;
|
||||
cpuinfo_x86(7, &flags_eax, &flags_ebx, &flags_ecx, &flags_edx);
|
||||
info->x86.avx2 = (flags_ebx & FLAC__CPUINFO_X86_CPUID_AVX2 ) ? true : false;
|
||||
}
|
||||
|
||||
#if defined FLAC__CPU_IA32
|
||||
dfprintf(stderr, "CPU info (IA-32):\n");
|
||||
#else
|
||||
dfprintf(stderr, "CPU info (x86-64):\n");
|
||||
#endif
|
||||
dfprintf(stderr, " CMOV ....... %c\n", info->x86.cmov ? 'Y' : 'n');
|
||||
dfprintf(stderr, " MMX ........ %c\n", info->x86.mmx ? 'Y' : 'n');
|
||||
dfprintf(stderr, " SSE ........ %c\n", info->x86.sse ? 'Y' : 'n');
|
||||
dfprintf(stderr, " SSE2 ....... %c\n", info->x86.sse2 ? 'Y' : 'n');
|
||||
dfprintf(stderr, " SSE3 ....... %c\n", info->x86.sse3 ? 'Y' : 'n');
|
||||
dfprintf(stderr, " SSSE3 ...... %c\n", info->x86.ssse3 ? 'Y' : 'n');
|
||||
dfprintf(stderr, " SSE41 ...... %c\n", info->x86.sse41 ? 'Y' : 'n');
|
||||
dfprintf(stderr, " SSE42 ...... %c\n", info->x86.sse42 ? 'Y' : 'n');
|
||||
|
||||
if (FLAC__AVX_SUPPORTED) {
|
||||
dfprintf(stderr, " AVX ........ %c\n", info->x86.avx ? 'Y' : 'n');
|
||||
dfprintf(stderr, " FMA ........ %c\n", info->x86.fma ? 'Y' : 'n');
|
||||
dfprintf(stderr, " AVX2 ....... %c\n", info->x86.avx2 ? 'Y' : 'n');
|
||||
}
|
||||
|
||||
/*
|
||||
* now have to check for OS support of AVX instructions
|
||||
*/
|
||||
if (FLAC__AVX_SUPPORTED && info->x86.avx && x86_osxsave && (cpu_xgetbv_x86() & 0x6) == 0x6) {
|
||||
os_avx = true;
|
||||
}
|
||||
if (os_avx) {
|
||||
dfprintf(stderr, " AVX OS sup . %c\n", info->x86.avx ? 'Y' : 'n');
|
||||
}
|
||||
if (!os_avx) {
|
||||
/* no OS AVX support */
|
||||
info->x86.avx = false;
|
||||
info->x86.avx2 = false;
|
||||
info->x86.fma = false;
|
||||
}
|
||||
#else
|
||||
info->use_asm = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
ppc_cpu_info (FLAC__CPUInfo *info)
|
||||
{
|
||||
#if defined FLAC__CPU_PPC
|
||||
#ifndef PPC_FEATURE2_ARCH_3_00
|
||||
#define PPC_FEATURE2_ARCH_3_00 0x00800000
|
||||
#endif
|
||||
|
||||
#ifndef PPC_FEATURE2_ARCH_2_07
|
||||
#define PPC_FEATURE2_ARCH_2_07 0x80000000
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (getauxval(AT_HWCAP2) & PPC_FEATURE2_ARCH_3_00) {
|
||||
info->ppc.arch_3_00 = true;
|
||||
} else if (getauxval(AT_HWCAP2) & PPC_FEATURE2_ARCH_2_07) {
|
||||
info->ppc.arch_2_07 = true;
|
||||
}
|
||||
#elif defined(__FreeBSD__)
|
||||
long hwcaps;
|
||||
elf_aux_info(AT_HWCAP2, &hwcaps, sizeof(hwcaps));
|
||||
#else
|
||||
#error Unsupported platform! Please add support for reading ppc hwcaps.
|
||||
#endif
|
||||
|
||||
if (hwcaps & PPC_FEATURE2_ARCH_3_00) {
|
||||
info->ppc.arch_3_00 = true;
|
||||
} else if (hwcaps & PPC_FEATURE2_ARCH_2_07) {
|
||||
info->ppc.arch_2_07 = true;
|
||||
}
|
||||
#else
|
||||
info->ppc.arch_2_07 = false;
|
||||
info->ppc.arch_3_00 = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void FLAC__cpu_info (FLAC__CPUInfo *info)
|
||||
{
|
||||
memset(info, 0, sizeof(*info));
|
||||
|
||||
#ifdef FLAC__CPU_IA32
|
||||
info->type = FLAC__CPUINFO_TYPE_IA32;
|
||||
#elif defined FLAC__CPU_X86_64
|
||||
info->type = FLAC__CPUINFO_TYPE_X86_64;
|
||||
#elif defined FLAC__CPU_PPC
|
||||
info->type = FLAC__CPUINFO_TYPE_PPC;
|
||||
#else
|
||||
info->type = FLAC__CPUINFO_TYPE_UNKNOWN;
|
||||
#endif
|
||||
|
||||
switch (info->type) {
|
||||
case FLAC__CPUINFO_TYPE_IA32: /* fallthrough */
|
||||
case FLAC__CPUINFO_TYPE_X86_64:
|
||||
x86_cpu_info (info);
|
||||
break;
|
||||
case FLAC__CPUINFO_TYPE_PPC:
|
||||
ppc_cpu_info (info);
|
||||
break;
|
||||
default:
|
||||
info->use_asm = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
150
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/crc.c
Normal file
150
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/crc.c
Normal file
@@ -0,0 +1,150 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2000-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
//#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
//#endif
|
||||
#define PGM_READ_UNALIGNED 0
|
||||
#include <Arduino.h>
|
||||
#include <pgmspace.h>
|
||||
|
||||
#include "private/crc.h"
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
/* CRC-8, poly = x^8 + x^2 + x^1 + x^0, init = 0 */
|
||||
|
||||
FLAC__byte const FLAC__crc8_table[256] PROGMEM = {
|
||||
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
|
||||
0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
|
||||
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
|
||||
0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
|
||||
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
|
||||
0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
|
||||
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
|
||||
0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
|
||||
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
|
||||
0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
|
||||
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
|
||||
0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
|
||||
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
|
||||
0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
|
||||
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
|
||||
0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
|
||||
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
|
||||
0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
|
||||
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
|
||||
0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
|
||||
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
|
||||
0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
|
||||
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
|
||||
0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
|
||||
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
|
||||
0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
|
||||
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
|
||||
0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
|
||||
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
|
||||
0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
|
||||
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
|
||||
0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
|
||||
};
|
||||
|
||||
/* CRC-16, poly = x^16 + x^15 + x^2 + x^0, init = 0 */
|
||||
|
||||
unsigned const FLAC__crc16_table[256] PROGMEM = {
|
||||
0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011,
|
||||
0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022,
|
||||
0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072,
|
||||
0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041,
|
||||
0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2,
|
||||
0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1,
|
||||
0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1,
|
||||
0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082,
|
||||
0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192,
|
||||
0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1,
|
||||
0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1,
|
||||
0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2,
|
||||
0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151,
|
||||
0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162,
|
||||
0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132,
|
||||
0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101,
|
||||
0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312,
|
||||
0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321,
|
||||
0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371,
|
||||
0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342,
|
||||
0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1,
|
||||
0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2,
|
||||
0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2,
|
||||
0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381,
|
||||
0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291,
|
||||
0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2,
|
||||
0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2,
|
||||
0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1,
|
||||
0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252,
|
||||
0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261,
|
||||
0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231,
|
||||
0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202
|
||||
};
|
||||
|
||||
|
||||
void FLAC__crc8_update(const FLAC__byte data, FLAC__uint8 *crc)
|
||||
{
|
||||
*crc = pgm_read_byte(&FLAC__crc8_table[*crc ^ data]);
|
||||
}
|
||||
|
||||
void FLAC__crc8_update_block(const FLAC__byte *data, unsigned len, FLAC__uint8 *crc)
|
||||
{
|
||||
while(len--)
|
||||
*crc = pgm_read_byte(&FLAC__crc8_table[*crc ^ *data++]);
|
||||
}
|
||||
|
||||
FLAC__uint8 FLAC__crc8(const FLAC__byte *data, unsigned len)
|
||||
{
|
||||
FLAC__uint8 crc = 0;
|
||||
|
||||
while(len--)
|
||||
crc = pgm_read_byte(&FLAC__crc8_table[crc ^ *data++]);
|
||||
|
||||
return crc;
|
||||
}
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
|
||||
unsigned FLAC__crc16(const FLAC__byte *data, unsigned len)
|
||||
{
|
||||
unsigned crc = 0;
|
||||
|
||||
while(len--)
|
||||
crc = ((crc<<8) ^ pgm_read_word(&FLAC__crc16_table[(crc>>8) ^ *data++])) & 0xffff;
|
||||
|
||||
return crc;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
397
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/fixed.c
Normal file
397
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/fixed.c
Normal file
@@ -0,0 +1,397 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2000-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
//#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
//#endif
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include "share/compat.h"
|
||||
#include "private/bitmath.h"
|
||||
#include "private/fixed.h"
|
||||
#include "private/macros.h"
|
||||
#include "FLAC/assert.h"
|
||||
|
||||
#ifdef local_abs
|
||||
#undef local_abs
|
||||
#endif
|
||||
#define local_abs(x) ((uint32_t)((x)<0? -(x) : (x)))
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
#ifdef FLAC__INTEGER_ONLY_LIBRARY
|
||||
/* rbps stands for residual bits per sample
|
||||
*
|
||||
* (ln(2) * err)
|
||||
* rbps = log (-----------)
|
||||
* 2 ( n )
|
||||
*/
|
||||
static FLAC__fixedpoint local__compute_rbps_integerized(FLAC__uint32 err, FLAC__uint32 n)
|
||||
{
|
||||
FLAC__uint32 rbps;
|
||||
uint32_t bits; /* the number of bits required to represent a number */
|
||||
int fracbits; /* the number of bits of rbps that comprise the fractional part */
|
||||
|
||||
FLAC__ASSERT(sizeof(rbps) == sizeof(FLAC__fixedpoint));
|
||||
FLAC__ASSERT(err > 0);
|
||||
FLAC__ASSERT(n > 0);
|
||||
|
||||
FLAC__ASSERT(n <= FLAC__MAX_BLOCK_SIZE);
|
||||
if(err <= n)
|
||||
return 0;
|
||||
/*
|
||||
* The above two things tell us 1) n fits in 16 bits; 2) err/n > 1.
|
||||
* These allow us later to know we won't lose too much precision in the
|
||||
* fixed-point division (err<<fracbits)/n.
|
||||
*/
|
||||
|
||||
fracbits = (8*sizeof(err)) - (FLAC__bitmath_ilog2(err)+1);
|
||||
|
||||
err <<= fracbits;
|
||||
err /= n;
|
||||
/* err now holds err/n with fracbits fractional bits */
|
||||
|
||||
/*
|
||||
* Whittle err down to 16 bits max. 16 significant bits is enough for
|
||||
* our purposes.
|
||||
*/
|
||||
FLAC__ASSERT(err > 0);
|
||||
bits = FLAC__bitmath_ilog2(err)+1;
|
||||
if(bits > 16) {
|
||||
err >>= (bits-16);
|
||||
fracbits -= (bits-16);
|
||||
}
|
||||
rbps = (FLAC__uint32)err;
|
||||
|
||||
/* Multiply by fixed-point version of ln(2), with 16 fractional bits */
|
||||
rbps *= FLAC__FP_LN2;
|
||||
fracbits += 16;
|
||||
FLAC__ASSERT(fracbits >= 0);
|
||||
|
||||
/* FLAC__fixedpoint_log2 requires fracbits%4 to be 0 */
|
||||
{
|
||||
const int f = fracbits & 3;
|
||||
if(f) {
|
||||
rbps >>= f;
|
||||
fracbits -= f;
|
||||
}
|
||||
}
|
||||
|
||||
rbps = FLAC__fixedpoint_log2(rbps, fracbits, (uint32_t)(-1));
|
||||
|
||||
if(rbps == 0)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* The return value must have 16 fractional bits. Since the whole part
|
||||
* of the base-2 log of a 32 bit number must fit in 5 bits, and fracbits
|
||||
* must be >= -3, these assertion allows us to be able to shift rbps
|
||||
* left if necessary to get 16 fracbits without losing any bits of the
|
||||
* whole part of rbps.
|
||||
*
|
||||
* There is a slight chance due to accumulated error that the whole part
|
||||
* will require 6 bits, so we use 6 in the assertion. Really though as
|
||||
* long as it fits in 13 bits (32 - (16 - (-3))) we are fine.
|
||||
*/
|
||||
FLAC__ASSERT((int)FLAC__bitmath_ilog2(rbps)+1 <= fracbits + 6);
|
||||
FLAC__ASSERT(fracbits >= -3);
|
||||
|
||||
/* now shift the decimal point into place */
|
||||
if(fracbits < 16)
|
||||
return rbps << (16-fracbits);
|
||||
else if(fracbits > 16)
|
||||
return rbps >> (fracbits-16);
|
||||
else
|
||||
return rbps;
|
||||
}
|
||||
|
||||
static FLAC__fixedpoint local__compute_rbps_wide_integerized(FLAC__uint64 err, FLAC__uint32 n)
|
||||
{
|
||||
FLAC__uint32 rbps;
|
||||
uint32_t bits; /* the number of bits required to represent a number */
|
||||
int fracbits; /* the number of bits of rbps that comprise the fractional part */
|
||||
|
||||
FLAC__ASSERT(sizeof(rbps) == sizeof(FLAC__fixedpoint));
|
||||
FLAC__ASSERT(err > 0);
|
||||
FLAC__ASSERT(n > 0);
|
||||
|
||||
FLAC__ASSERT(n <= FLAC__MAX_BLOCK_SIZE);
|
||||
if(err <= n)
|
||||
return 0;
|
||||
/*
|
||||
* The above two things tell us 1) n fits in 16 bits; 2) err/n > 1.
|
||||
* These allow us later to know we won't lose too much precision in the
|
||||
* fixed-point division (err<<fracbits)/n.
|
||||
*/
|
||||
|
||||
fracbits = (8*sizeof(err)) - (FLAC__bitmath_ilog2_wide(err)+1);
|
||||
|
||||
err <<= fracbits;
|
||||
err /= n;
|
||||
/* err now holds err/n with fracbits fractional bits */
|
||||
|
||||
/*
|
||||
* Whittle err down to 16 bits max. 16 significant bits is enough for
|
||||
* our purposes.
|
||||
*/
|
||||
FLAC__ASSERT(err > 0);
|
||||
bits = FLAC__bitmath_ilog2_wide(err)+1;
|
||||
if(bits > 16) {
|
||||
err >>= (bits-16);
|
||||
fracbits -= (bits-16);
|
||||
}
|
||||
rbps = (FLAC__uint32)err;
|
||||
|
||||
/* Multiply by fixed-point version of ln(2), with 16 fractional bits */
|
||||
rbps *= FLAC__FP_LN2;
|
||||
fracbits += 16;
|
||||
FLAC__ASSERT(fracbits >= 0);
|
||||
|
||||
/* FLAC__fixedpoint_log2 requires fracbits%4 to be 0 */
|
||||
{
|
||||
const int f = fracbits & 3;
|
||||
if(f) {
|
||||
rbps >>= f;
|
||||
fracbits -= f;
|
||||
}
|
||||
}
|
||||
|
||||
rbps = FLAC__fixedpoint_log2(rbps, fracbits, (uint32_t)(-1));
|
||||
|
||||
if(rbps == 0)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* The return value must have 16 fractional bits. Since the whole part
|
||||
* of the base-2 log of a 32 bit number must fit in 5 bits, and fracbits
|
||||
* must be >= -3, these assertion allows us to be able to shift rbps
|
||||
* left if necessary to get 16 fracbits without losing any bits of the
|
||||
* whole part of rbps.
|
||||
*
|
||||
* There is a slight chance due to accumulated error that the whole part
|
||||
* will require 6 bits, so we use 6 in the assertion. Really though as
|
||||
* long as it fits in 13 bits (32 - (16 - (-3))) we are fine.
|
||||
*/
|
||||
FLAC__ASSERT((int)FLAC__bitmath_ilog2(rbps)+1 <= fracbits + 6);
|
||||
FLAC__ASSERT(fracbits >= -3);
|
||||
|
||||
/* now shift the decimal point into place */
|
||||
if(fracbits < 16)
|
||||
return rbps << (16-fracbits);
|
||||
else if(fracbits > 16)
|
||||
return rbps >> (fracbits-16);
|
||||
else
|
||||
return rbps;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef FLAC__INTEGER_ONLY_LIBRARY
|
||||
uint32_t FLAC__fixed_compute_best_predictor(const FLAC__int32 data[], uint32_t data_len, float residual_bits_per_sample[FLAC__MAX_FIXED_ORDER+1])
|
||||
#else
|
||||
uint32_t FLAC__fixed_compute_best_predictor(const FLAC__int32 data[], uint32_t data_len, FLAC__fixedpoint residual_bits_per_sample[FLAC__MAX_FIXED_ORDER+1])
|
||||
#endif
|
||||
{
|
||||
FLAC__int32 last_error_0 = data[-1];
|
||||
FLAC__int32 last_error_1 = data[-1] - data[-2];
|
||||
FLAC__int32 last_error_2 = last_error_1 - (data[-2] - data[-3]);
|
||||
FLAC__int32 last_error_3 = last_error_2 - (data[-2] - 2*data[-3] + data[-4]);
|
||||
FLAC__int32 error, save;
|
||||
FLAC__uint32 total_error_0 = 0, total_error_1 = 0, total_error_2 = 0, total_error_3 = 0, total_error_4 = 0;
|
||||
uint32_t i, order;
|
||||
|
||||
for(i = 0; i < data_len; i++) {
|
||||
error = data[i] ; total_error_0 += local_abs(error); save = error;
|
||||
error -= last_error_0; total_error_1 += local_abs(error); last_error_0 = save; save = error;
|
||||
error -= last_error_1; total_error_2 += local_abs(error); last_error_1 = save; save = error;
|
||||
error -= last_error_2; total_error_3 += local_abs(error); last_error_2 = save; save = error;
|
||||
error -= last_error_3; total_error_4 += local_abs(error); last_error_3 = save;
|
||||
}
|
||||
|
||||
if(total_error_0 < flac_min(flac_min(flac_min(total_error_1, total_error_2), total_error_3), total_error_4))
|
||||
order = 0;
|
||||
else if(total_error_1 < flac_min(flac_min(total_error_2, total_error_3), total_error_4))
|
||||
order = 1;
|
||||
else if(total_error_2 < flac_min(total_error_3, total_error_4))
|
||||
order = 2;
|
||||
else if(total_error_3 < total_error_4)
|
||||
order = 3;
|
||||
else
|
||||
order = 4;
|
||||
|
||||
/* Estimate the expected number of bits per residual signal sample. */
|
||||
/* 'total_error*' is linearly related to the variance of the residual */
|
||||
/* signal, so we use it directly to compute E(|x|) */
|
||||
FLAC__ASSERT(data_len > 0 || total_error_0 == 0);
|
||||
FLAC__ASSERT(data_len > 0 || total_error_1 == 0);
|
||||
FLAC__ASSERT(data_len > 0 || total_error_2 == 0);
|
||||
FLAC__ASSERT(data_len > 0 || total_error_3 == 0);
|
||||
FLAC__ASSERT(data_len > 0 || total_error_4 == 0);
|
||||
#ifndef FLAC__INTEGER_ONLY_LIBRARY
|
||||
residual_bits_per_sample[0] = (float)((total_error_0 > 0) ? log(M_LN2 * (double)total_error_0 / (double)data_len) / M_LN2 : 0.0);
|
||||
residual_bits_per_sample[1] = (float)((total_error_1 > 0) ? log(M_LN2 * (double)total_error_1 / (double)data_len) / M_LN2 : 0.0);
|
||||
residual_bits_per_sample[2] = (float)((total_error_2 > 0) ? log(M_LN2 * (double)total_error_2 / (double)data_len) / M_LN2 : 0.0);
|
||||
residual_bits_per_sample[3] = (float)((total_error_3 > 0) ? log(M_LN2 * (double)total_error_3 / (double)data_len) / M_LN2 : 0.0);
|
||||
residual_bits_per_sample[4] = (float)((total_error_4 > 0) ? log(M_LN2 * (double)total_error_4 / (double)data_len) / M_LN2 : 0.0);
|
||||
#else
|
||||
residual_bits_per_sample[0] = (total_error_0 > 0) ? local__compute_rbps_integerized(total_error_0, data_len) : 0;
|
||||
residual_bits_per_sample[1] = (total_error_1 > 0) ? local__compute_rbps_integerized(total_error_1, data_len) : 0;
|
||||
residual_bits_per_sample[2] = (total_error_2 > 0) ? local__compute_rbps_integerized(total_error_2, data_len) : 0;
|
||||
residual_bits_per_sample[3] = (total_error_3 > 0) ? local__compute_rbps_integerized(total_error_3, data_len) : 0;
|
||||
residual_bits_per_sample[4] = (total_error_4 > 0) ? local__compute_rbps_integerized(total_error_4, data_len) : 0;
|
||||
#endif
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
#ifndef FLAC__INTEGER_ONLY_LIBRARY
|
||||
uint32_t FLAC__fixed_compute_best_predictor_wide(const FLAC__int32 data[], uint32_t data_len, float residual_bits_per_sample[FLAC__MAX_FIXED_ORDER+1])
|
||||
#else
|
||||
uint32_t FLAC__fixed_compute_best_predictor_wide(const FLAC__int32 data[], uint32_t data_len, FLAC__fixedpoint residual_bits_per_sample[FLAC__MAX_FIXED_ORDER+1])
|
||||
#endif
|
||||
{
|
||||
FLAC__int32 last_error_0 = data[-1];
|
||||
FLAC__int32 last_error_1 = data[-1] - data[-2];
|
||||
FLAC__int32 last_error_2 = last_error_1 - (data[-2] - data[-3]);
|
||||
FLAC__int32 last_error_3 = last_error_2 - (data[-2] - 2*data[-3] + data[-4]);
|
||||
FLAC__int32 error, save;
|
||||
/* total_error_* are 64-bits to avoid overflow when encoding
|
||||
* erratic signals when the bits-per-sample and blocksize are
|
||||
* large.
|
||||
*/
|
||||
FLAC__uint64 total_error_0 = 0, total_error_1 = 0, total_error_2 = 0, total_error_3 = 0, total_error_4 = 0;
|
||||
uint32_t i, order;
|
||||
|
||||
for(i = 0; i < data_len; i++) {
|
||||
error = data[i] ; total_error_0 += local_abs(error); save = error;
|
||||
error -= last_error_0; total_error_1 += local_abs(error); last_error_0 = save; save = error;
|
||||
error -= last_error_1; total_error_2 += local_abs(error); last_error_1 = save; save = error;
|
||||
error -= last_error_2; total_error_3 += local_abs(error); last_error_2 = save; save = error;
|
||||
error -= last_error_3; total_error_4 += local_abs(error); last_error_3 = save;
|
||||
}
|
||||
|
||||
if(total_error_0 < flac_min(flac_min(flac_min(total_error_1, total_error_2), total_error_3), total_error_4))
|
||||
order = 0;
|
||||
else if(total_error_1 < flac_min(flac_min(total_error_2, total_error_3), total_error_4))
|
||||
order = 1;
|
||||
else if(total_error_2 < flac_min(total_error_3, total_error_4))
|
||||
order = 2;
|
||||
else if(total_error_3 < total_error_4)
|
||||
order = 3;
|
||||
else
|
||||
order = 4;
|
||||
|
||||
/* Estimate the expected number of bits per residual signal sample. */
|
||||
/* 'total_error*' is linearly related to the variance of the residual */
|
||||
/* signal, so we use it directly to compute E(|x|) */
|
||||
FLAC__ASSERT(data_len > 0 || total_error_0 == 0);
|
||||
FLAC__ASSERT(data_len > 0 || total_error_1 == 0);
|
||||
FLAC__ASSERT(data_len > 0 || total_error_2 == 0);
|
||||
FLAC__ASSERT(data_len > 0 || total_error_3 == 0);
|
||||
FLAC__ASSERT(data_len > 0 || total_error_4 == 0);
|
||||
#ifndef FLAC__INTEGER_ONLY_LIBRARY
|
||||
residual_bits_per_sample[0] = (float)((total_error_0 > 0) ? log(M_LN2 * (double)total_error_0 / (double)data_len) / M_LN2 : 0.0);
|
||||
residual_bits_per_sample[1] = (float)((total_error_1 > 0) ? log(M_LN2 * (double)total_error_1 / (double)data_len) / M_LN2 : 0.0);
|
||||
residual_bits_per_sample[2] = (float)((total_error_2 > 0) ? log(M_LN2 * (double)total_error_2 / (double)data_len) / M_LN2 : 0.0);
|
||||
residual_bits_per_sample[3] = (float)((total_error_3 > 0) ? log(M_LN2 * (double)total_error_3 / (double)data_len) / M_LN2 : 0.0);
|
||||
residual_bits_per_sample[4] = (float)((total_error_4 > 0) ? log(M_LN2 * (double)total_error_4 / (double)data_len) / M_LN2 : 0.0);
|
||||
#else
|
||||
residual_bits_per_sample[0] = (total_error_0 > 0) ? local__compute_rbps_wide_integerized(total_error_0, data_len) : 0;
|
||||
residual_bits_per_sample[1] = (total_error_1 > 0) ? local__compute_rbps_wide_integerized(total_error_1, data_len) : 0;
|
||||
residual_bits_per_sample[2] = (total_error_2 > 0) ? local__compute_rbps_wide_integerized(total_error_2, data_len) : 0;
|
||||
residual_bits_per_sample[3] = (total_error_3 > 0) ? local__compute_rbps_wide_integerized(total_error_3, data_len) : 0;
|
||||
residual_bits_per_sample[4] = (total_error_4 > 0) ? local__compute_rbps_wide_integerized(total_error_4, data_len) : 0;
|
||||
#endif
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
void FLAC__fixed_compute_residual(const FLAC__int32 data[], uint32_t data_len, uint32_t order, FLAC__int32 residual[])
|
||||
{
|
||||
const int idata_len = (int)data_len;
|
||||
int i;
|
||||
|
||||
switch(order) {
|
||||
case 0:
|
||||
FLAC__ASSERT(sizeof(residual[0]) == sizeof(data[0]));
|
||||
memcpy(residual, data, sizeof(residual[0])*data_len);
|
||||
break;
|
||||
case 1:
|
||||
for(i = 0; i < idata_len; i++)
|
||||
residual[i] = data[i] - data[i-1];
|
||||
break;
|
||||
case 2:
|
||||
for(i = 0; i < idata_len; i++)
|
||||
residual[i] = data[i] - 2*data[i-1] + data[i-2];
|
||||
break;
|
||||
case 3:
|
||||
for(i = 0; i < idata_len; i++)
|
||||
residual[i] = data[i] - 3*data[i-1] + 3*data[i-2] - data[i-3];
|
||||
break;
|
||||
case 4:
|
||||
for(i = 0; i < idata_len; i++)
|
||||
residual[i] = data[i] - 4*data[i-1] + 6*data[i-2] - 4*data[i-3] + data[i-4];
|
||||
break;
|
||||
default:
|
||||
FLAC__ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
void FLAC__fixed_restore_signal(const FLAC__int32 residual[], uint32_t data_len, uint32_t order, FLAC__int32 data[])
|
||||
{
|
||||
int i, idata_len = (int)data_len;
|
||||
|
||||
switch(order) {
|
||||
case 0:
|
||||
FLAC__ASSERT(sizeof(residual[0]) == sizeof(data[0]));
|
||||
memcpy(data, residual, sizeof(residual[0])*data_len);
|
||||
break;
|
||||
case 1:
|
||||
for(i = 0; i < idata_len; i++)
|
||||
data[i] = residual[i] + data[i-1];
|
||||
break;
|
||||
case 2:
|
||||
for(i = 0; i < idata_len; i++)
|
||||
data[i] = residual[i] + 2*data[i-1] - data[i-2];
|
||||
break;
|
||||
case 3:
|
||||
for(i = 0; i < idata_len; i++)
|
||||
data[i] = residual[i] + 3*data[i-1] - 3*data[i-2] + data[i-3];
|
||||
break;
|
||||
case 4:
|
||||
for(i = 0; i < idata_len; i++)
|
||||
data[i] = residual[i] + 4*data[i-1] - 6*data[i-2] + 4*data[i-3] - data[i-4];
|
||||
break;
|
||||
default:
|
||||
FLAC__ASSERT(0);
|
||||
}
|
||||
}
|
||||
304
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/float.c
Normal file
304
FW/leo_muziekdoos_sam51/lib/ESP8266Audio/src/libflac/float.c
Normal file
@@ -0,0 +1,304 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2004-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2016 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
//#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
//#endif
|
||||
|
||||
#include "FLAC/assert.h"
|
||||
#include "share/compat.h"
|
||||
#include "private/float.h"
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
#ifdef FLAC__INTEGER_ONLY_LIBRARY
|
||||
|
||||
const FLAC__fixedpoint FLAC__FP_ZERO = 0;
|
||||
const FLAC__fixedpoint FLAC__FP_ONE_HALF = 0x00008000;
|
||||
const FLAC__fixedpoint FLAC__FP_ONE = 0x00010000;
|
||||
const FLAC__fixedpoint FLAC__FP_LN2 = 45426;
|
||||
const FLAC__fixedpoint FLAC__FP_E = 178145;
|
||||
|
||||
/* Lookup tables for Knuth's logarithm algorithm */
|
||||
#define LOG2_LOOKUP_PRECISION 16
|
||||
static const FLAC__uint32 log2_lookup[][LOG2_LOOKUP_PRECISION] PROGMEM = {
|
||||
{
|
||||
/*
|
||||
* 0 fraction bits
|
||||
*/
|
||||
/* undefined */ 0x00000000,
|
||||
/* lg(2/1) = */ 0x00000001,
|
||||
/* lg(4/3) = */ 0x00000000,
|
||||
/* lg(8/7) = */ 0x00000000,
|
||||
/* lg(16/15) = */ 0x00000000,
|
||||
/* lg(32/31) = */ 0x00000000,
|
||||
/* lg(64/63) = */ 0x00000000,
|
||||
/* lg(128/127) = */ 0x00000000,
|
||||
/* lg(256/255) = */ 0x00000000,
|
||||
/* lg(512/511) = */ 0x00000000,
|
||||
/* lg(1024/1023) = */ 0x00000000,
|
||||
/* lg(2048/2047) = */ 0x00000000,
|
||||
/* lg(4096/4095) = */ 0x00000000,
|
||||
/* lg(8192/8191) = */ 0x00000000,
|
||||
/* lg(16384/16383) = */ 0x00000000,
|
||||
/* lg(32768/32767) = */ 0x00000000
|
||||
},
|
||||
{
|
||||
/*
|
||||
* 4 fraction bits
|
||||
*/
|
||||
/* undefined */ 0x00000000,
|
||||
/* lg(2/1) = */ 0x00000010,
|
||||
/* lg(4/3) = */ 0x00000007,
|
||||
/* lg(8/7) = */ 0x00000003,
|
||||
/* lg(16/15) = */ 0x00000001,
|
||||
/* lg(32/31) = */ 0x00000001,
|
||||
/* lg(64/63) = */ 0x00000000,
|
||||
/* lg(128/127) = */ 0x00000000,
|
||||
/* lg(256/255) = */ 0x00000000,
|
||||
/* lg(512/511) = */ 0x00000000,
|
||||
/* lg(1024/1023) = */ 0x00000000,
|
||||
/* lg(2048/2047) = */ 0x00000000,
|
||||
/* lg(4096/4095) = */ 0x00000000,
|
||||
/* lg(8192/8191) = */ 0x00000000,
|
||||
/* lg(16384/16383) = */ 0x00000000,
|
||||
/* lg(32768/32767) = */ 0x00000000
|
||||
},
|
||||
{
|
||||
/*
|
||||
* 8 fraction bits
|
||||
*/
|
||||
/* undefined */ 0x00000000,
|
||||
/* lg(2/1) = */ 0x00000100,
|
||||
/* lg(4/3) = */ 0x0000006a,
|
||||
/* lg(8/7) = */ 0x00000031,
|
||||
/* lg(16/15) = */ 0x00000018,
|
||||
/* lg(32/31) = */ 0x0000000c,
|
||||
/* lg(64/63) = */ 0x00000006,
|
||||
/* lg(128/127) = */ 0x00000003,
|
||||
/* lg(256/255) = */ 0x00000001,
|
||||
/* lg(512/511) = */ 0x00000001,
|
||||
/* lg(1024/1023) = */ 0x00000000,
|
||||
/* lg(2048/2047) = */ 0x00000000,
|
||||
/* lg(4096/4095) = */ 0x00000000,
|
||||
/* lg(8192/8191) = */ 0x00000000,
|
||||
/* lg(16384/16383) = */ 0x00000000,
|
||||
/* lg(32768/32767) = */ 0x00000000
|
||||
},
|
||||
{
|
||||
/*
|
||||
* 12 fraction bits
|
||||
*/
|
||||
/* undefined */ 0x00000000,
|
||||
/* lg(2/1) = */ 0x00001000,
|
||||
/* lg(4/3) = */ 0x000006a4,
|
||||
/* lg(8/7) = */ 0x00000315,
|
||||
/* lg(16/15) = */ 0x0000017d,
|
||||
/* lg(32/31) = */ 0x000000bc,
|
||||
/* lg(64/63) = */ 0x0000005d,
|
||||
/* lg(128/127) = */ 0x0000002e,
|
||||
/* lg(256/255) = */ 0x00000017,
|
||||
/* lg(512/511) = */ 0x0000000c,
|
||||
/* lg(1024/1023) = */ 0x00000006,
|
||||
/* lg(2048/2047) = */ 0x00000003,
|
||||
/* lg(4096/4095) = */ 0x00000001,
|
||||
/* lg(8192/8191) = */ 0x00000001,
|
||||
/* lg(16384/16383) = */ 0x00000000,
|
||||
/* lg(32768/32767) = */ 0x00000000
|
||||
},
|
||||
{
|
||||
/*
|
||||
* 16 fraction bits
|
||||
*/
|
||||
/* undefined */ 0x00000000,
|
||||
/* lg(2/1) = */ 0x00010000,
|
||||
/* lg(4/3) = */ 0x00006a40,
|
||||
/* lg(8/7) = */ 0x00003151,
|
||||
/* lg(16/15) = */ 0x000017d6,
|
||||
/* lg(32/31) = */ 0x00000bba,
|
||||
/* lg(64/63) = */ 0x000005d1,
|
||||
/* lg(128/127) = */ 0x000002e6,
|
||||
/* lg(256/255) = */ 0x00000172,
|
||||
/* lg(512/511) = */ 0x000000b9,
|
||||
/* lg(1024/1023) = */ 0x0000005c,
|
||||
/* lg(2048/2047) = */ 0x0000002e,
|
||||
/* lg(4096/4095) = */ 0x00000017,
|
||||
/* lg(8192/8191) = */ 0x0000000c,
|
||||
/* lg(16384/16383) = */ 0x00000006,
|
||||
/* lg(32768/32767) = */ 0x00000003
|
||||
},
|
||||
{
|
||||
/*
|
||||
* 20 fraction bits
|
||||
*/
|
||||
/* undefined */ 0x00000000,
|
||||
/* lg(2/1) = */ 0x00100000,
|
||||
/* lg(4/3) = */ 0x0006a3fe,
|
||||
/* lg(8/7) = */ 0x00031513,
|
||||
/* lg(16/15) = */ 0x00017d60,
|
||||
/* lg(32/31) = */ 0x0000bb9d,
|
||||
/* lg(64/63) = */ 0x00005d10,
|
||||
/* lg(128/127) = */ 0x00002e59,
|
||||
/* lg(256/255) = */ 0x00001721,
|
||||
/* lg(512/511) = */ 0x00000b8e,
|
||||
/* lg(1024/1023) = */ 0x000005c6,
|
||||
/* lg(2048/2047) = */ 0x000002e3,
|
||||
/* lg(4096/4095) = */ 0x00000171,
|
||||
/* lg(8192/8191) = */ 0x000000b9,
|
||||
/* lg(16384/16383) = */ 0x0000005c,
|
||||
/* lg(32768/32767) = */ 0x0000002e
|
||||
},
|
||||
{
|
||||
/*
|
||||
* 24 fraction bits
|
||||
*/
|
||||
/* undefined */ 0x00000000,
|
||||
/* lg(2/1) = */ 0x01000000,
|
||||
/* lg(4/3) = */ 0x006a3fe6,
|
||||
/* lg(8/7) = */ 0x00315130,
|
||||
/* lg(16/15) = */ 0x0017d605,
|
||||
/* lg(32/31) = */ 0x000bb9ca,
|
||||
/* lg(64/63) = */ 0x0005d0fc,
|
||||
/* lg(128/127) = */ 0x0002e58f,
|
||||
/* lg(256/255) = */ 0x0001720e,
|
||||
/* lg(512/511) = */ 0x0000b8d8,
|
||||
/* lg(1024/1023) = */ 0x00005c61,
|
||||
/* lg(2048/2047) = */ 0x00002e2d,
|
||||
/* lg(4096/4095) = */ 0x00001716,
|
||||
/* lg(8192/8191) = */ 0x00000b8b,
|
||||
/* lg(16384/16383) = */ 0x000005c5,
|
||||
/* lg(32768/32767) = */ 0x000002e3
|
||||
},
|
||||
{
|
||||
/*
|
||||
* 28 fraction bits
|
||||
*/
|
||||
/* undefined */ 0x00000000,
|
||||
/* lg(2/1) = */ 0x10000000,
|
||||
/* lg(4/3) = */ 0x06a3fe5c,
|
||||
/* lg(8/7) = */ 0x03151301,
|
||||
/* lg(16/15) = */ 0x017d6049,
|
||||
/* lg(32/31) = */ 0x00bb9ca6,
|
||||
/* lg(64/63) = */ 0x005d0fba,
|
||||
/* lg(128/127) = */ 0x002e58f7,
|
||||
/* lg(256/255) = */ 0x001720da,
|
||||
/* lg(512/511) = */ 0x000b8d87,
|
||||
/* lg(1024/1023) = */ 0x0005c60b,
|
||||
/* lg(2048/2047) = */ 0x0002e2d7,
|
||||
/* lg(4096/4095) = */ 0x00017160,
|
||||
/* lg(8192/8191) = */ 0x0000b8ad,
|
||||
/* lg(16384/16383) = */ 0x00005c56,
|
||||
/* lg(32768/32767) = */ 0x00002e2b
|
||||
}
|
||||
};
|
||||
|
||||
#if 0
|
||||
static const FLAC__uint64 log2_lookup_wide[] = {
|
||||
{
|
||||
/*
|
||||
* 32 fraction bits
|
||||
*/
|
||||
/* undefined */ 0x00000000,
|
||||
/* lg(2/1) = */ FLAC__U64L(0x100000000),
|
||||
/* lg(4/3) = */ FLAC__U64L(0x6a3fe5c6),
|
||||
/* lg(8/7) = */ FLAC__U64L(0x31513015),
|
||||
/* lg(16/15) = */ FLAC__U64L(0x17d60497),
|
||||
/* lg(32/31) = */ FLAC__U64L(0x0bb9ca65),
|
||||
/* lg(64/63) = */ FLAC__U64L(0x05d0fba2),
|
||||
/* lg(128/127) = */ FLAC__U64L(0x02e58f74),
|
||||
/* lg(256/255) = */ FLAC__U64L(0x01720d9c),
|
||||
/* lg(512/511) = */ FLAC__U64L(0x00b8d875),
|
||||
/* lg(1024/1023) = */ FLAC__U64L(0x005c60aa),
|
||||
/* lg(2048/2047) = */ FLAC__U64L(0x002e2d72),
|
||||
/* lg(4096/4095) = */ FLAC__U64L(0x00171600),
|
||||
/* lg(8192/8191) = */ FLAC__U64L(0x000b8ad2),
|
||||
/* lg(16384/16383) = */ FLAC__U64L(0x0005c55d),
|
||||
/* lg(32768/32767) = */ FLAC__U64L(0x0002e2ac)
|
||||
},
|
||||
{
|
||||
/*
|
||||
* 48 fraction bits
|
||||
*/
|
||||
/* undefined */ 0x00000000,
|
||||
/* lg(2/1) = */ FLAC__U64L(0x1000000000000),
|
||||
/* lg(4/3) = */ FLAC__U64L(0x6a3fe5c60429),
|
||||
/* lg(8/7) = */ FLAC__U64L(0x315130157f7a),
|
||||
/* lg(16/15) = */ FLAC__U64L(0x17d60496cfbb),
|
||||
/* lg(32/31) = */ FLAC__U64L(0xbb9ca64ecac),
|
||||
/* lg(64/63) = */ FLAC__U64L(0x5d0fba187cd),
|
||||
/* lg(128/127) = */ FLAC__U64L(0x2e58f7441ee),
|
||||
/* lg(256/255) = */ FLAC__U64L(0x1720d9c06a8),
|
||||
/* lg(512/511) = */ FLAC__U64L(0xb8d8752173),
|
||||
/* lg(1024/1023) = */ FLAC__U64L(0x5c60aa252e),
|
||||
/* lg(2048/2047) = */ FLAC__U64L(0x2e2d71b0d8),
|
||||
/* lg(4096/4095) = */ FLAC__U64L(0x1716001719),
|
||||
/* lg(8192/8191) = */ FLAC__U64L(0xb8ad1de1b),
|
||||
/* lg(16384/16383) = */ FLAC__U64L(0x5c55d640d),
|
||||
/* lg(32768/32767) = */ FLAC__U64L(0x2e2abcf52)
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
FLAC__uint32 FLAC__fixedpoint_log2(FLAC__uint32 x, uint32_t fracbits, uint32_t precision)
|
||||
{
|
||||
const FLAC__uint32 ONE = (1u << fracbits);
|
||||
const FLAC__uint32 *table = log2_lookup[fracbits >> 2];
|
||||
|
||||
FLAC__ASSERT(fracbits < 32);
|
||||
FLAC__ASSERT((fracbits & 0x3) == 0);
|
||||
|
||||
if(x < ONE)
|
||||
return 0;
|
||||
|
||||
if(precision > LOG2_LOOKUP_PRECISION)
|
||||
precision = LOG2_LOOKUP_PRECISION;
|
||||
|
||||
/* Knuth's algorithm for computing logarithms, optimized for base-2 with lookup tables */
|
||||
{
|
||||
FLAC__uint32 y = 0;
|
||||
FLAC__uint32 z = x >> 1, k = 1;
|
||||
while (x > ONE && k < precision) {
|
||||
if (x - z >= ONE) {
|
||||
x -= z;
|
||||
z = x >> k;
|
||||
y += table[k];
|
||||
}
|
||||
else {
|
||||
z >>= 1;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* defined FLAC__INTEGER_ONLY_LIBRARY */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user