fork from github
This commit is contained in:
51
src/AudioFileSource.h
Normal file
51
src/AudioFileSource.h
Normal file
@@ -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
|
||||
|
||||
190
src/AudioFileSourceBuffer.cpp
Normal file
190
src/AudioFileSourceBuffer.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
62
src/AudioFileSourceBuffer.h
Normal file
62
src/AudioFileSourceBuffer.h
Normal file
@@ -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
|
||||
|
||||
64
src/AudioFileSourceFATFS.h
Normal file
64
src/AudioFileSourceFATFS.h
Normal file
@@ -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
|
||||
|
||||
73
src/AudioFileSourceFS.cpp
Normal file
73
src/AudioFileSourceFS.cpp
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
51
src/AudioFileSourceFS.h
Normal file
51
src/AudioFileSourceFS.h
Normal file
@@ -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
|
||||
|
||||
148
src/AudioFileSourceFunction.cpp
Normal file
148
src/AudioFileSourceFunction.cpp
Normal file
@@ -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;
|
||||
}
|
||||
119
src/AudioFileSourceFunction.h
Normal file
119
src/AudioFileSourceFunction.h
Normal file
@@ -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
|
||||
158
src/AudioFileSourceHTTPStream.cpp
Normal file
158
src/AudioFileSourceHTTPStream.cpp
Normal file
@@ -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
|
||||
67
src/AudioFileSourceHTTPStream.h
Normal file
67
src/AudioFileSourceHTTPStream.h
Normal file
@@ -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
|
||||
|
||||
222
src/AudioFileSourceICYStream.cpp
Normal file
222
src/AudioFileSourceICYStream.cpp
Normal file
@@ -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
|
||||
48
src/AudioFileSourceICYStream.h
Normal file
48
src/AudioFileSourceICYStream.h
Normal file
@@ -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
|
||||
280
src/AudioFileSourceID3.cpp
Normal file
280
src/AudioFileSourceID3.cpp
Normal file
@@ -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();
|
||||
}
|
||||
48
src/AudioFileSourceID3.h
Normal file
48
src/AudioFileSourceID3.h
Normal file
@@ -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
|
||||
|
||||
43
src/AudioFileSourceLittleFS.h
Normal file
43
src/AudioFileSourceLittleFS.h
Normal file
@@ -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
|
||||
|
||||
99
src/AudioFileSourcePROGMEM.cpp
Normal file
99
src/AudioFileSourcePROGMEM.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
49
src/AudioFileSourcePROGMEM.h
Normal file
49
src/AudioFileSourcePROGMEM.h
Normal file
@@ -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
|
||||
|
||||
78
src/AudioFileSourceSD.cpp
Normal file
78
src/AudioFileSourceSD.cpp
Normal file
@@ -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();
|
||||
}
|
||||
49
src/AudioFileSourceSD.h
Normal file
49
src/AudioFileSourceSD.h
Normal file
@@ -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
|
||||
|
||||
40
src/AudioFileSourceSPIFFS.h
Normal file
40
src/AudioFileSourceSPIFFS.h
Normal file
@@ -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
|
||||
|
||||
171
src/AudioFileSourceSPIRAMBuffer.cpp
Normal file
171
src/AudioFileSourceSPIRAMBuffer.cpp
Normal file
@@ -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
|
||||
65
src/AudioFileSourceSPIRAMBuffer.h
Normal file
65
src/AudioFileSourceSPIRAMBuffer.h
Normal file
@@ -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
|
||||
98
src/AudioFileSourceSTDIO.cpp
Normal file
98
src/AudioFileSourceSTDIO.cpp
Normal file
@@ -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
|
||||
53
src/AudioFileSourceSTDIO.h
Normal file
53
src/AudioFileSourceSTDIO.h
Normal file
@@ -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
|
||||
|
||||
61
src/AudioFileStream.cpp
Normal file
61
src/AudioFileStream.cpp
Normal file
@@ -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? */
|
||||
}
|
||||
48
src/AudioFileStream.h
Normal file
48
src/AudioFileStream.h
Normal file
@@ -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
|
||||
54
src/AudioGenerator.h
Normal file
54
src/AudioGenerator.h
Normal file
@@ -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
|
||||
219
src/AudioGeneratorAAC.cpp
Normal file
219
src/AudioGeneratorAAC.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
64
src/AudioGeneratorAAC.h
Normal file
64
src/AudioGeneratorAAC.h
Normal file
@@ -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
|
||||
|
||||
199
src/AudioGeneratorFLAC.cpp
Normal file
199
src/AudioGeneratorFLAC.cpp
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
89
src/AudioGeneratorFLAC.h
Normal file
89
src/AudioGeneratorFLAC.h
Normal file
@@ -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
|
||||
|
||||
639
src/AudioGeneratorMIDI.cpp
Normal file
639
src/AudioGeneratorMIDI.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
181
src/AudioGeneratorMIDI.h
Normal file
181
src/AudioGeneratorMIDI.h
Normal file
@@ -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
|
||||
|
||||
876
src/AudioGeneratorMOD.cpp
Normal file
876
src/AudioGeneratorMOD.cpp
Normal file
@@ -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
src/AudioGeneratorMOD.h
Normal file
168
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
|
||||
|
||||
412
src/AudioGeneratorMP3.cpp
Normal file
412
src/AudioGeneratorMP3.cpp
Normal file
@@ -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
|
||||
}
|
||||
|
||||
84
src/AudioGeneratorMP3.h
Normal file
84
src/AudioGeneratorMP3.h
Normal file
@@ -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
|
||||
|
||||
162
src/AudioGeneratorMP3a.cpp
Normal file
162
src/AudioGeneratorMP3a.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
57
src/AudioGeneratorMP3a.h
Normal file
57
src/AudioGeneratorMP3a.h
Normal file
@@ -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
|
||||
142
src/AudioGeneratorOpus.cpp
Normal file
142
src/AudioGeneratorOpus.cpp
Normal file
@@ -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;
|
||||
}
|
||||
70
src/AudioGeneratorOpus.h
Normal file
70
src/AudioGeneratorOpus.h
Normal file
@@ -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
|
||||
|
||||
292
src/AudioGeneratorRTTTL.cpp
Normal file
292
src/AudioGeneratorRTTTL.cpp
Normal file
@@ -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;
|
||||
}
|
||||
66
src/AudioGeneratorRTTTL.h
Normal file
66
src/AudioGeneratorRTTTL.h
Normal file
@@ -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
|
||||
|
||||
302
src/AudioGeneratorTalkie.cpp
Normal file
302
src/AudioGeneratorTalkie.cpp
Normal file
@@ -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;
|
||||
}
|
||||
66
src/AudioGeneratorTalkie.h
Normal file
66
src/AudioGeneratorTalkie.h
Normal file
@@ -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
|
||||
|
||||
316
src/AudioGeneratorWAV.cpp
Normal file
316
src/AudioGeneratorWAV.cpp
Normal file
@@ -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;
|
||||
}
|
||||
61
src/AudioGeneratorWAV.h
Normal file
61
src/AudioGeneratorWAV.h
Normal file
@@ -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
|
||||
|
||||
5
src/AudioLogger.cpp
Normal file
5
src/AudioLogger.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
#include "AudioLogger.h"
|
||||
|
||||
DevNullOut silencedLogger;
|
||||
Print* audioLogger = &silencedLogger;
|
||||
19
src/AudioLogger.h
Normal file
19
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
src/AudioOutput.h
Normal file
85
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
|
||||
|
||||
89
src/AudioOutputBuffer.cpp
Normal file
89
src/AudioOutputBuffer.cpp
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
49
src/AudioOutputBuffer.h
Normal file
49
src/AudioOutputBuffer.h
Normal file
@@ -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
|
||||
|
||||
245
src/AudioOutputFilterBiquad.cpp
Normal file
245
src/AudioOutputFilterBiquad.cpp
Normal file
@@ -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();
|
||||
}
|
||||
80
src/AudioOutputFilterBiquad.h
Normal file
80
src/AudioOutputFilterBiquad.h
Normal file
@@ -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
|
||||
|
||||
112
src/AudioOutputFilterDecimate.cpp
Normal file
112
src/AudioOutputFilterDecimate.cpp
Normal file
@@ -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();
|
||||
}
|
||||
51
src/AudioOutputFilterDecimate.h
Normal file
51
src/AudioOutputFilterDecimate.h
Normal file
@@ -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
|
||||
|
||||
311
src/AudioOutputI2S.cpp
Normal file
311
src/AudioOutputI2S.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
64
src/AudioOutputI2S.h
Normal file
64
src/AudioOutputI2S.h
Normal file
@@ -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;
|
||||
};
|
||||
113
src/AudioOutputI2SNoDAC.cpp
Normal file
113
src/AudioOutputI2SNoDAC.cpp
Normal file
@@ -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;
|
||||
}
|
||||
43
src/AudioOutputI2SNoDAC.h
Normal file
43
src/AudioOutputI2SNoDAC.h
Normal file
@@ -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
|
||||
};
|
||||
244
src/AudioOutputMixer.cpp
Normal file
244
src/AudioOutputMixer.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
88
src/AudioOutputMixer.h
Normal file
88
src/AudioOutputMixer.h
Normal file
@@ -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
|
||||
|
||||
45
src/AudioOutputNull.h
Normal file
45
src/AudioOutputNull.h
Normal file
@@ -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
|
||||
|
||||
291
src/AudioOutputSPDIF.cpp
Normal file
291
src/AudioOutputSPDIF.cpp
Normal file
@@ -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
|
||||
76
src/AudioOutputSPDIF.h
Normal file
76
src/AudioOutputSPDIF.h
Normal file
@@ -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
|
||||
113
src/AudioOutputSPIFFSWAV.cpp
Normal file
113
src/AudioOutputSPIFFSWAV.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
45
src/AudioOutputSPIFFSWAV.h
Normal file
45
src/AudioOutputSPIFFSWAV.h
Normal file
@@ -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
|
||||
|
||||
115
src/AudioOutputSTDIO.cpp
Normal file
115
src/AudioOutputSTDIO.cpp
Normal file
@@ -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
|
||||
|
||||
47
src/AudioOutputSTDIO.h
Normal file
47
src/AudioOutputSTDIO.h
Normal file
@@ -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
|
||||
78
src/AudioOutputSerialWAV.cpp
Normal file
78
src/AudioOutputSerialWAV.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
39
src/AudioOutputSerialWAV.h
Normal file
39
src/AudioOutputSerialWAV.h
Normal file
@@ -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
src/AudioOutputULP.cpp
Normal file
262
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
|
||||
69
src/AudioOutputULP.h
Normal file
69
src/AudioOutputULP.h
Normal file
@@ -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
src/AudioStatus.h
Normal file
57
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
src/ESP8266Audio.h
Normal file
50
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"
|
||||
283
src/driver/SinglePinI2SDriver.cpp
Normal file
283
src/driver/SinglePinI2SDriver.cpp
Normal file
@@ -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)
|
||||
85
src/driver/SinglePinI2SDriver.h
Normal file
85
src/driver/SinglePinI2SDriver.h
Normal file
@@ -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
src/libflac/AUTHORS
Normal file
58
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
src/libflac/COPYING.FDL
Normal file
397
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
src/libflac/COPYING.GPL
Normal file
339
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.
|
||||
504
src/libflac/COPYING.LGPL
Normal file
504
src/libflac/COPYING.LGPL
Normal file
@@ -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!
|
||||
|
||||
|
||||
29
src/libflac/COPYING.Xiph
Normal file
29
src/libflac/COPYING.Xiph
Normal file
@@ -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.
|
||||
46
src/libflac/FLAC/assert.h
Normal file
46
src/libflac/FLAC/assert.h
Normal file
@@ -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
|
||||
185
src/libflac/FLAC/callback.h
Normal file
185
src/libflac/FLAC/callback.h
Normal file
@@ -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
|
||||
97
src/libflac/FLAC/export.h
Normal file
97
src/libflac/FLAC/export.h
Normal file
@@ -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
src/libflac/FLAC/format.h
Normal file
1025
src/libflac/FLAC/format.h
Normal file
File diff suppressed because it is too large
Load Diff
2182
src/libflac/FLAC/metadata.h
Normal file
2182
src/libflac/FLAC/metadata.h
Normal file
File diff suppressed because it is too large
Load Diff
85
src/libflac/FLAC/ordinals.h
Normal file
85
src/libflac/FLAC/ordinals.h
Normal file
@@ -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
|
||||
1573
src/libflac/FLAC/stream_decoder.h
Normal file
1573
src/libflac/FLAC/stream_decoder.h
Normal file
File diff suppressed because it is too large
Load Diff
254
src/libflac/README
Normal file
254
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.
|
||||
5
src/libflac/README.ESP8266
Normal file
5
src/libflac/README.ESP8266
Normal file
@@ -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.
|
||||
75
src/libflac/bitmath.c
Normal file
75
src/libflac/bitmath.c
Normal file
@@ -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
src/libflac/bitreader.c
Normal file
1100
src/libflac/bitreader.c
Normal file
File diff suppressed because it is too large
Load Diff
248
src/libflac/config.h
Normal file
248
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
src/libflac/cpu.c
Normal file
299
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
src/libflac/crc.c
Normal file
150
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
src/libflac/fixed.c
Normal file
397
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
src/libflac/float.c
Normal file
304
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 */
|
||||
594
src/libflac/format.c
Normal file
594
src/libflac/format.c
Normal file
@@ -0,0 +1,594 @@
|
||||
/* 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 <stdio.h>
|
||||
#include <stdlib.h> /* for qsort() */
|
||||
#include <string.h> /* for memset() */
|
||||
#include "FLAC/assert.h"
|
||||
#include "FLAC/format.h"
|
||||
#include "share/alloc.h"
|
||||
#include "share/compat.h"
|
||||
#include "private/format.h"
|
||||
#include "private/macros.h"
|
||||
|
||||
#pragma GCC optimize ("O3")
|
||||
|
||||
/* PACKAGE_VERSION should come from configure */
|
||||
FLAC_API const char *FLAC__VERSION_STRING = PACKAGE_VERSION;
|
||||
|
||||
FLAC_API const char *FLAC__VENDOR_STRING = "reference libFLAC " PACKAGE_VERSION " 20190804";
|
||||
|
||||
FLAC_API const FLAC__byte FLAC__STREAM_SYNC_STRING[4] = { 'f','L','a','C' };
|
||||
FLAC_API const uint32_t FLAC__STREAM_SYNC = 0x664C6143;
|
||||
FLAC_API const uint32_t FLAC__STREAM_SYNC_LEN = 32; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_STREAMINFO_MIN_BLOCK_SIZE_LEN = 16; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_STREAMINFO_MAX_BLOCK_SIZE_LEN = 16; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN = 24; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN = 24; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_STREAMINFO_SAMPLE_RATE_LEN = 20; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_STREAMINFO_CHANNELS_LEN = 3; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_STREAMINFO_BITS_PER_SAMPLE_LEN = 5; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN = 36; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_STREAMINFO_MD5SUM_LEN = 128; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_APPLICATION_ID_LEN = 32; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_SEEKPOINT_SAMPLE_NUMBER_LEN = 64; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_SEEKPOINT_STREAM_OFFSET_LEN = 64; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_SEEKPOINT_FRAME_SAMPLES_LEN = 16; /* bits */
|
||||
|
||||
FLAC_API const FLAC__uint64 FLAC__STREAM_METADATA_SEEKPOINT_PLACEHOLDER = FLAC__U64L(0xffffffffffffffff);
|
||||
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN = 32; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_VORBIS_COMMENT_NUM_COMMENTS_LEN = 32; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_INDEX_OFFSET_LEN = 64; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_INDEX_NUMBER_LEN = 8; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_INDEX_RESERVED_LEN = 3*8; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_TRACK_OFFSET_LEN = 64; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_TRACK_NUMBER_LEN = 8; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_TRACK_ISRC_LEN = 12*8; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_TRACK_TYPE_LEN = 1; /* bit */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_TRACK_PRE_EMPHASIS_LEN = 1; /* bit */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_TRACK_RESERVED_LEN = 6+13*8; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_TRACK_NUM_INDICES_LEN = 8; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_MEDIA_CATALOG_NUMBER_LEN = 128*8; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_LEAD_IN_LEN = 64; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_IS_CD_LEN = 1; /* bit */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_RESERVED_LEN = 7+258*8; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_CUESHEET_NUM_TRACKS_LEN = 8; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_PICTURE_TYPE_LEN = 32; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_PICTURE_MIME_TYPE_LENGTH_LEN = 32; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_PICTURE_DESCRIPTION_LENGTH_LEN = 32; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_PICTURE_WIDTH_LEN = 32; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_PICTURE_HEIGHT_LEN = 32; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_PICTURE_DEPTH_LEN = 32; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_PICTURE_COLORS_LEN = 32; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_PICTURE_DATA_LENGTH_LEN = 32; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_IS_LAST_LEN = 1; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_TYPE_LEN = 7; /* bits */
|
||||
FLAC_API const uint32_t FLAC__STREAM_METADATA_LENGTH_LEN = 24; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__FRAME_HEADER_SYNC = 0x3ffe;
|
||||
FLAC_API const uint32_t FLAC__FRAME_HEADER_SYNC_LEN = 14; /* bits */
|
||||
FLAC_API const uint32_t FLAC__FRAME_HEADER_RESERVED_LEN = 1; /* bits */
|
||||
FLAC_API const uint32_t FLAC__FRAME_HEADER_BLOCKING_STRATEGY_LEN = 1; /* bits */
|
||||
FLAC_API const uint32_t FLAC__FRAME_HEADER_BLOCK_SIZE_LEN = 4; /* bits */
|
||||
FLAC_API const uint32_t FLAC__FRAME_HEADER_SAMPLE_RATE_LEN = 4; /* bits */
|
||||
FLAC_API const uint32_t FLAC__FRAME_HEADER_CHANNEL_ASSIGNMENT_LEN = 4; /* bits */
|
||||
FLAC_API const uint32_t FLAC__FRAME_HEADER_BITS_PER_SAMPLE_LEN = 3; /* bits */
|
||||
FLAC_API const uint32_t FLAC__FRAME_HEADER_ZERO_PAD_LEN = 1; /* bits */
|
||||
FLAC_API const uint32_t FLAC__FRAME_HEADER_CRC_LEN = 8; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__FRAME_FOOTER_CRC_LEN = 16; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__ENTROPY_CODING_METHOD_TYPE_LEN = 2; /* bits */
|
||||
FLAC_API const uint32_t FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_ORDER_LEN = 4; /* bits */
|
||||
FLAC_API const uint32_t FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_PARAMETER_LEN = 4; /* bits */
|
||||
FLAC_API const uint32_t FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE2_PARAMETER_LEN = 5; /* bits */
|
||||
FLAC_API const uint32_t FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_RAW_LEN = 5; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_ESCAPE_PARAMETER = 15; /* == (1<<FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_PARAMETER_LEN)-1 */
|
||||
FLAC_API const uint32_t FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE2_ESCAPE_PARAMETER = 31; /* == (1<<FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE2_PARAMETER_LEN)-1 */
|
||||
|
||||
FLAC_API const char * const FLAC__EntropyCodingMethodTypeString[] = {
|
||||
"PARTITIONED_RICE",
|
||||
"PARTITIONED_RICE2"
|
||||
};
|
||||
|
||||
FLAC_API const uint32_t FLAC__SUBFRAME_LPC_QLP_COEFF_PRECISION_LEN = 4; /* bits */
|
||||
FLAC_API const uint32_t FLAC__SUBFRAME_LPC_QLP_SHIFT_LEN = 5; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__SUBFRAME_ZERO_PAD_LEN = 1; /* bits */
|
||||
FLAC_API const uint32_t FLAC__SUBFRAME_TYPE_LEN = 6; /* bits */
|
||||
FLAC_API const uint32_t FLAC__SUBFRAME_WASTED_BITS_FLAG_LEN = 1; /* bits */
|
||||
|
||||
FLAC_API const uint32_t FLAC__SUBFRAME_TYPE_CONSTANT_BYTE_ALIGNED_MASK = 0x00;
|
||||
FLAC_API const uint32_t FLAC__SUBFRAME_TYPE_VERBATIM_BYTE_ALIGNED_MASK = 0x02;
|
||||
FLAC_API const uint32_t FLAC__SUBFRAME_TYPE_FIXED_BYTE_ALIGNED_MASK = 0x10;
|
||||
FLAC_API const uint32_t FLAC__SUBFRAME_TYPE_LPC_BYTE_ALIGNED_MASK = 0x40;
|
||||
|
||||
// Not used in AudioGeneratorFLAC, possibly save some RAM
|
||||
/*
|
||||
FLAC_API const char * const FLAC__SubframeTypeString[] = {
|
||||
"CONSTANT",
|
||||
"VERBATIM",
|
||||
"FIXED",
|
||||
"LPC"
|
||||
};
|
||||
|
||||
FLAC_API const char * const FLAC__ChannelAssignmentString[] = {
|
||||
"INDEPENDENT",
|
||||
"LEFT_SIDE",
|
||||
"RIGHT_SIDE",
|
||||
"MID_SIDE"
|
||||
};
|
||||
|
||||
FLAC_API const char * const FLAC__FrameNumberTypeString[] = {
|
||||
"FRAME_NUMBER_TYPE_FRAME_NUMBER",
|
||||
"FRAME_NUMBER_TYPE_SAMPLE_NUMBER"
|
||||
};
|
||||
|
||||
FLAC_API const char * const FLAC__MetadataTypeString[] = {
|
||||
"STREAMINFO",
|
||||
"PADDING",
|
||||
"APPLICATION",
|
||||
"SEEKTABLE",
|
||||
"VORBIS_COMMENT",
|
||||
"CUESHEET",
|
||||
"PICTURE"
|
||||
};
|
||||
|
||||
FLAC_API const char * const FLAC__StreamMetadata_Picture_TypeString[] = {
|
||||
"Other",
|
||||
"32x32 pixels 'file icon' (PNG only)",
|
||||
"Other file icon",
|
||||
"Cover (front)",
|
||||
"Cover (back)",
|
||||
"Leaflet page",
|
||||
"Media (e.g. label side of CD)",
|
||||
"Lead artist/lead performer/soloist",
|
||||
"Artist/performer",
|
||||
"Conductor",
|
||||
"Band/Orchestra",
|
||||
"Composer",
|
||||
"Lyricist/text writer",
|
||||
"Recording Location",
|
||||
"During recording",
|
||||
"During performance",
|
||||
"Movie/video screen capture",
|
||||
"A bright coloured fish",
|
||||
"Illustration",
|
||||
"Band/artist logotype",
|
||||
"Publisher/Studio logotype"
|
||||
};
|
||||
*/
|
||||
|
||||
FLAC_API FLAC__bool FLAC__format_sample_rate_is_valid(uint32_t sample_rate)
|
||||
{
|
||||
if(sample_rate == 0 || sample_rate > FLAC__MAX_SAMPLE_RATE) {
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
FLAC_API FLAC__bool FLAC__format_blocksize_is_subset(uint32_t blocksize, uint32_t sample_rate)
|
||||
{
|
||||
if(blocksize > 16384)
|
||||
return false;
|
||||
else if(sample_rate <= 48000 && blocksize > 4608)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
FLAC_API FLAC__bool FLAC__format_sample_rate_is_subset(uint32_t sample_rate)
|
||||
{
|
||||
if(
|
||||
!FLAC__format_sample_rate_is_valid(sample_rate) ||
|
||||
(
|
||||
sample_rate >= (1u << 16) &&
|
||||
!(sample_rate % 1000 == 0 || sample_rate % 10 == 0)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
/* @@@@ add to unit tests; it is already indirectly tested by the metadata_object tests */
|
||||
FLAC_API FLAC__bool FLAC__format_seektable_is_legal(const FLAC__StreamMetadata_SeekTable *seek_table)
|
||||
{
|
||||
uint32_t i;
|
||||
FLAC__uint64 prev_sample_number = 0;
|
||||
FLAC__bool got_prev = false;
|
||||
|
||||
FLAC__ASSERT(0 != seek_table);
|
||||
|
||||
for(i = 0; i < seek_table->num_points; i++) {
|
||||
if(got_prev) {
|
||||
if(
|
||||
seek_table->points[i].sample_number != FLAC__STREAM_METADATA_SEEKPOINT_PLACEHOLDER &&
|
||||
seek_table->points[i].sample_number <= prev_sample_number
|
||||
)
|
||||
return false;
|
||||
}
|
||||
prev_sample_number = seek_table->points[i].sample_number;
|
||||
got_prev = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* used as the sort predicate for qsort() */
|
||||
static int seekpoint_compare_(const FLAC__StreamMetadata_SeekPoint *l, const FLAC__StreamMetadata_SeekPoint *r)
|
||||
{
|
||||
/* we don't just 'return l->sample_number - r->sample_number' since the result (FLAC__int64) might overflow an 'int' */
|
||||
if(l->sample_number == r->sample_number)
|
||||
return 0;
|
||||
else if(l->sample_number < r->sample_number)
|
||||
return -1;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* @@@@ add to unit tests; it is already indirectly tested by the metadata_object tests */
|
||||
FLAC_API uint32_t FLAC__format_seektable_sort(FLAC__StreamMetadata_SeekTable *seek_table)
|
||||
{
|
||||
uint32_t i, j;
|
||||
FLAC__bool first;
|
||||
|
||||
FLAC__ASSERT(0 != seek_table);
|
||||
|
||||
if (seek_table->num_points == 0)
|
||||
return 0;
|
||||
|
||||
/* sort the seekpoints */
|
||||
qsort(seek_table->points, seek_table->num_points, sizeof(FLAC__StreamMetadata_SeekPoint), (int (*)(const void *, const void *))seekpoint_compare_);
|
||||
|
||||
/* uniquify the seekpoints */
|
||||
first = true;
|
||||
for(i = j = 0; i < seek_table->num_points; i++) {
|
||||
if(seek_table->points[i].sample_number != FLAC__STREAM_METADATA_SEEKPOINT_PLACEHOLDER) {
|
||||
if(!first) {
|
||||
if(seek_table->points[i].sample_number == seek_table->points[j-1].sample_number)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
seek_table->points[j++] = seek_table->points[i];
|
||||
}
|
||||
|
||||
for(i = j; i < seek_table->num_points; i++) {
|
||||
seek_table->points[i].sample_number = FLAC__STREAM_METADATA_SEEKPOINT_PLACEHOLDER;
|
||||
seek_table->points[i].stream_offset = 0;
|
||||
seek_table->points[i].frame_samples = 0;
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
/*
|
||||
* also disallows non-shortest-form encodings, c.f.
|
||||
* http://www.unicode.org/versions/corrigendum1.html
|
||||
* and a more clear explanation at the end of this section:
|
||||
* http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
|
||||
*/
|
||||
static uint32_t utf8len_(const FLAC__byte *utf8)
|
||||
{
|
||||
FLAC__ASSERT(0 != utf8);
|
||||
if ((utf8[0] & 0x80) == 0) {
|
||||
return 1;
|
||||
}
|
||||
else if ((utf8[0] & 0xE0) == 0xC0 && (utf8[1] & 0xC0) == 0x80) {
|
||||
if ((utf8[0] & 0xFE) == 0xC0) /* overlong sequence check */
|
||||
return 0;
|
||||
return 2;
|
||||
}
|
||||
else if ((utf8[0] & 0xF0) == 0xE0 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80) {
|
||||
if (utf8[0] == 0xE0 && (utf8[1] & 0xE0) == 0x80) /* overlong sequence check */
|
||||
return 0;
|
||||
/* illegal surrogates check (U+D800...U+DFFF and U+FFFE...U+FFFF) */
|
||||
if (utf8[0] == 0xED && (utf8[1] & 0xE0) == 0xA0) /* D800-DFFF */
|
||||
return 0;
|
||||
if (utf8[0] == 0xEF && utf8[1] == 0xBF && (utf8[2] & 0xFE) == 0xBE) /* FFFE-FFFF */
|
||||
return 0;
|
||||
return 3;
|
||||
}
|
||||
else if ((utf8[0] & 0xF8) == 0xF0 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80) {
|
||||
if (utf8[0] == 0xF0 && (utf8[1] & 0xF0) == 0x80) /* overlong sequence check */
|
||||
return 0;
|
||||
return 4;
|
||||
}
|
||||
else if ((utf8[0] & 0xFC) == 0xF8 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80 && (utf8[4] & 0xC0) == 0x80) {
|
||||
if (utf8[0] == 0xF8 && (utf8[1] & 0xF8) == 0x80) /* overlong sequence check */
|
||||
return 0;
|
||||
return 5;
|
||||
}
|
||||
else if ((utf8[0] & 0xFE) == 0xFC && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80 && (utf8[4] & 0xC0) == 0x80 && (utf8[5] & 0xC0) == 0x80) {
|
||||
if (utf8[0] == 0xFC && (utf8[1] & 0xFC) == 0x80) /* overlong sequence check */
|
||||
return 0;
|
||||
return 6;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
FLAC_API FLAC__bool FLAC__format_vorbiscomment_entry_name_is_legal(const char *name)
|
||||
{
|
||||
char c;
|
||||
for(c = *name; c; c = *(++name))
|
||||
if(c < 0x20 || c == 0x3d || c > 0x7d)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
FLAC_API FLAC__bool FLAC__format_vorbiscomment_entry_value_is_legal(const FLAC__byte *value, uint32_t length)
|
||||
{
|
||||
if(length == (uint32_t)(-1)) {
|
||||
while(*value) {
|
||||
uint32_t n = utf8len_(value);
|
||||
if(n == 0)
|
||||
return false;
|
||||
value += n;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const FLAC__byte *end = value + length;
|
||||
while(value < end) {
|
||||
uint32_t n = utf8len_(value);
|
||||
if(n == 0)
|
||||
return false;
|
||||
value += n;
|
||||
}
|
||||
if(value != end)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FLAC_API FLAC__bool FLAC__format_vorbiscomment_entry_is_legal(const FLAC__byte *entry, uint32_t length)
|
||||
{
|
||||
const FLAC__byte *s, *end;
|
||||
|
||||
for(s = entry, end = s + length; s < end && *s != '='; s++) {
|
||||
if(*s < 0x20 || *s > 0x7D)
|
||||
return false;
|
||||
}
|
||||
if(s == end)
|
||||
return false;
|
||||
|
||||
s++; /* skip '=' */
|
||||
|
||||
while(s < end) {
|
||||
uint32_t n = utf8len_(s);
|
||||
if(n == 0)
|
||||
return false;
|
||||
s += n;
|
||||
}
|
||||
if(s != end)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* @@@@ add to unit tests; it is already indirectly tested by the metadata_object tests */
|
||||
FLAC_API FLAC__bool FLAC__format_cuesheet_is_legal(const FLAC__StreamMetadata_CueSheet *cue_sheet, FLAC__bool check_cd_da_subset, const char **violation)
|
||||
{
|
||||
uint32_t i, j;
|
||||
|
||||
if(check_cd_da_subset) {
|
||||
if(cue_sheet->lead_in < 2 * 44100) {
|
||||
if(violation) *violation = "CD-DA cue sheet must have a lead-in length of at least 2 seconds";
|
||||
return false;
|
||||
}
|
||||
if(cue_sheet->lead_in % 588 != 0) {
|
||||
if(violation) *violation = "CD-DA cue sheet lead-in length must be evenly divisible by 588 samples";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(cue_sheet->num_tracks == 0) {
|
||||
if(violation) *violation = "cue sheet must have at least one track (the lead-out)";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(check_cd_da_subset && cue_sheet->tracks[cue_sheet->num_tracks-1].number != 170) {
|
||||
if(violation) *violation = "CD-DA cue sheet must have a lead-out track number 170 (0xAA)";
|
||||
return false;
|
||||
}
|
||||
|
||||
for(i = 0; i < cue_sheet->num_tracks; i++) {
|
||||
if(cue_sheet->tracks[i].number == 0) {
|
||||
if(violation) *violation = "cue sheet may not have a track number 0";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(check_cd_da_subset) {
|
||||
if(!((cue_sheet->tracks[i].number >= 1 && cue_sheet->tracks[i].number <= 99) || cue_sheet->tracks[i].number == 170)) {
|
||||
if(violation) *violation = "CD-DA cue sheet track number must be 1-99 or 170";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(check_cd_da_subset && cue_sheet->tracks[i].offset % 588 != 0) {
|
||||
if(violation) {
|
||||
if(i == cue_sheet->num_tracks-1) /* the lead-out track... */
|
||||
*violation = "CD-DA cue sheet lead-out offset must be evenly divisible by 588 samples";
|
||||
else
|
||||
*violation = "CD-DA cue sheet track offset must be evenly divisible by 588 samples";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(i < cue_sheet->num_tracks - 1) {
|
||||
if(cue_sheet->tracks[i].num_indices == 0) {
|
||||
if(violation) *violation = "cue sheet track must have at least one index point";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(cue_sheet->tracks[i].indices[0].number > 1) {
|
||||
if(violation) *violation = "cue sheet track's first index number must be 0 or 1";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for(j = 0; j < cue_sheet->tracks[i].num_indices; j++) {
|
||||
if(check_cd_da_subset && cue_sheet->tracks[i].indices[j].offset % 588 != 0) {
|
||||
if(violation) *violation = "CD-DA cue sheet track index offset must be evenly divisible by 588 samples";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(j > 0) {
|
||||
if(cue_sheet->tracks[i].indices[j].number != cue_sheet->tracks[i].indices[j-1].number + 1) {
|
||||
if(violation) *violation = "cue sheet track index numbers must increase by 1";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* @@@@ add to unit tests; it is already indirectly tested by the metadata_object tests */
|
||||
FLAC_API FLAC__bool FLAC__format_picture_is_legal(const FLAC__StreamMetadata_Picture *picture, const char **violation)
|
||||
{
|
||||
char *p;
|
||||
FLAC__byte *b;
|
||||
|
||||
for(p = picture->mime_type; *p; p++) {
|
||||
if(*p < 0x20 || *p > 0x7e) {
|
||||
if(violation) *violation = "MIME type string must contain only printable ASCII characters (0x20-0x7e)";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for(b = picture->description; *b; ) {
|
||||
uint32_t n = utf8len_(b);
|
||||
if(n == 0) {
|
||||
if(violation) *violation = "description string must be valid UTF-8";
|
||||
return false;
|
||||
}
|
||||
b += n;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* These routines are private to libFLAC
|
||||
*/
|
||||
uint32_t FLAC__format_get_max_rice_partition_order(uint32_t blocksize, uint32_t predictor_order)
|
||||
{
|
||||
return
|
||||
FLAC__format_get_max_rice_partition_order_from_blocksize_limited_max_and_predictor_order(
|
||||
FLAC__format_get_max_rice_partition_order_from_blocksize(blocksize),
|
||||
blocksize,
|
||||
predictor_order
|
||||
);
|
||||
}
|
||||
|
||||
uint32_t FLAC__format_get_max_rice_partition_order_from_blocksize(uint32_t blocksize)
|
||||
{
|
||||
uint32_t max_rice_partition_order = 0;
|
||||
while(!(blocksize & 1)) {
|
||||
max_rice_partition_order++;
|
||||
blocksize >>= 1;
|
||||
}
|
||||
return flac_min(FLAC__MAX_RICE_PARTITION_ORDER, max_rice_partition_order);
|
||||
}
|
||||
|
||||
uint32_t FLAC__format_get_max_rice_partition_order_from_blocksize_limited_max_and_predictor_order(uint32_t limit, uint32_t blocksize, uint32_t predictor_order)
|
||||
{
|
||||
uint32_t max_rice_partition_order = limit;
|
||||
|
||||
while(max_rice_partition_order > 0 && (blocksize >> max_rice_partition_order) <= predictor_order)
|
||||
max_rice_partition_order--;
|
||||
|
||||
FLAC__ASSERT(
|
||||
(max_rice_partition_order == 0 && blocksize >= predictor_order) ||
|
||||
(max_rice_partition_order > 0 && blocksize >> max_rice_partition_order > predictor_order)
|
||||
);
|
||||
|
||||
return max_rice_partition_order;
|
||||
}
|
||||
|
||||
void FLAC__format_entropy_coding_method_partitioned_rice_contents_init(FLAC__EntropyCodingMethod_PartitionedRiceContents *object)
|
||||
{
|
||||
FLAC__ASSERT(0 != object);
|
||||
|
||||
object->parameters = 0;
|
||||
object->raw_bits = 0;
|
||||
object->capacity_by_order = 0;
|
||||
}
|
||||
|
||||
void FLAC__format_entropy_coding_method_partitioned_rice_contents_clear(FLAC__EntropyCodingMethod_PartitionedRiceContents *object)
|
||||
{
|
||||
FLAC__ASSERT(0 != object);
|
||||
|
||||
if(0 != object->parameters)
|
||||
free(object->parameters);
|
||||
if(0 != object->raw_bits)
|
||||
free(object->raw_bits);
|
||||
FLAC__format_entropy_coding_method_partitioned_rice_contents_init(object);
|
||||
}
|
||||
|
||||
FLAC__bool FLAC__format_entropy_coding_method_partitioned_rice_contents_ensure_size(FLAC__EntropyCodingMethod_PartitionedRiceContents *object, uint32_t max_partition_order)
|
||||
{
|
||||
FLAC__ASSERT(0 != object);
|
||||
|
||||
FLAC__ASSERT(object->capacity_by_order > 0 || (0 == object->parameters && 0 == object->raw_bits));
|
||||
|
||||
if(object->capacity_by_order < max_partition_order) {
|
||||
if(0 == (object->parameters = safe_realloc_(object->parameters, sizeof(uint32_t)*(1 << max_partition_order))))
|
||||
return false;
|
||||
if(0 == (object->raw_bits = safe_realloc_(object->raw_bits, sizeof(uint32_t)*(1 << max_partition_order))))
|
||||
return false;
|
||||
memset(object->raw_bits, 0, sizeof(uint32_t)*(1 << max_partition_order));
|
||||
object->capacity_by_order = max_partition_order;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
1359
src/libflac/lpc.c
Normal file
1359
src/libflac/lpc.c
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user