You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
500 lines
18 KiB
500 lines
18 KiB
#ifndef TEENSYAUDIOLIBRARY_RESAMPLINGREADER_H
|
|
#define TEENSYAUDIOLIBRARY_RESAMPLINGREADER_H
|
|
|
|
#include <Arduino.h>
|
|
#include <cstdint>
|
|
#include "loop_type.h"
|
|
#include "interpolation.h"
|
|
#include "waveheaderparser.h"
|
|
|
|
namespace newdigate {
|
|
|
|
template<class TArray, class TFile>
|
|
class ResamplingReader {
|
|
public:
|
|
ResamplingReader() {
|
|
}
|
|
virtual ~ResamplingReader() {
|
|
}
|
|
|
|
virtual TFile open(char *filename) = 0;
|
|
virtual TArray* createSourceBuffer() = 0;
|
|
virtual int16_t getSourceBufferValue(long index) = 0;
|
|
virtual void close(void) = 0;
|
|
|
|
void begin(void)
|
|
{
|
|
if (_interpolationType != ResampleInterpolationType::resampleinterpolation_none) {
|
|
initializeInterpolationPoints();
|
|
}
|
|
_playing = false;
|
|
_bufferPosition = _header_offset;
|
|
_file_size = 0;
|
|
}
|
|
|
|
bool playRaw(TArray *array, uint32_t length, uint16_t numChannels)
|
|
{
|
|
_sourceBuffer = array;
|
|
stop();
|
|
|
|
_header_offset = 0;
|
|
_file_size = length * 2;
|
|
_loop_start = 0;
|
|
_loop_finish = length;
|
|
setNumChannels(numChannels);
|
|
|
|
reset();
|
|
_playing = true;
|
|
return true;
|
|
}
|
|
|
|
bool playRaw(TArray *array, uint16_t numChannels) {
|
|
return playRaw(array, false, numChannels);
|
|
}
|
|
|
|
bool playWav(TArray *array, uint32_t length) {
|
|
return playRaw(array, true);
|
|
}
|
|
|
|
bool play(const char *filename, bool isWave, uint16_t numChannelsIfRaw = 0)
|
|
{
|
|
close();
|
|
|
|
if (!isWave) // if raw file, then hardcode the numChannels as per the parameter
|
|
setNumChannels(numChannelsIfRaw);
|
|
|
|
_filename = new char[strlen(filename)+1] {0};
|
|
memcpy(_filename, filename, strlen(filename) + 1);
|
|
|
|
TFile file = open(_filename);
|
|
if (!file) {
|
|
Serial.printf("Not able to open file: %s\n", _filename);
|
|
if (_filename) delete [] _filename;
|
|
_filename = nullptr;
|
|
return false;
|
|
}
|
|
|
|
_file_size = file.size();
|
|
if (isWave) {
|
|
wav_header wav_header;
|
|
wav_data_header data_header;
|
|
|
|
WaveHeaderParser wavHeaderParser;
|
|
char buffer[36];
|
|
size_t bytesRead = file.read(buffer, 36);
|
|
|
|
wavHeaderParser.readWaveHeaderFromBuffer((const char *) buffer, wav_header);
|
|
if (wav_header.bit_depth != 16) {
|
|
Serial.printf("Needs 16 bit audio! Aborting.... (got %d)", wav_header.bit_depth);
|
|
return false;
|
|
}
|
|
setNumChannels(wav_header.num_channels);
|
|
|
|
bytesRead = file.read(buffer, 8);
|
|
unsigned infoTagsSize;
|
|
if (!wavHeaderParser.readInfoTags((unsigned char *)buffer, 0, infoTagsSize))
|
|
{
|
|
Serial.println("Not able to read header! Aborting...");
|
|
return false;
|
|
}
|
|
|
|
file.seek(36 + infoTagsSize);
|
|
bytesRead = file.read(buffer, 8);
|
|
|
|
if (!wavHeaderParser.readDataHeader((unsigned char *)buffer, 0, data_header)) {
|
|
Serial.println("Not able to read header! Aborting...");
|
|
return false;
|
|
}
|
|
|
|
_header_offset = (44 + infoTagsSize) / 2;
|
|
_loop_finish = ((data_header.data_bytes) / 2) + _header_offset;
|
|
} else
|
|
_loop_finish = _file_size / 2;
|
|
|
|
file.close();
|
|
|
|
if (_file_size <= _header_offset * sizeof(int16_t)) {
|
|
_playing = false;
|
|
if (_filename) delete [] _filename;
|
|
_filename = nullptr;
|
|
Serial.printf("Wave file contains no samples: %s\n", filename);
|
|
return false;
|
|
}
|
|
|
|
_sourceBuffer = createSourceBuffer();
|
|
_loop_start = _header_offset;
|
|
|
|
reset();
|
|
_playing = true;
|
|
return true;
|
|
}
|
|
|
|
bool playRaw(const char *filename, uint16_t numChannelsIfRaw){
|
|
return play(filename, false, numChannelsIfRaw);
|
|
}
|
|
|
|
bool playWav(const char *filename){
|
|
return play(filename, true);
|
|
}
|
|
|
|
bool play()
|
|
{
|
|
stop();
|
|
reset();
|
|
_playing = true;
|
|
return true;
|
|
}
|
|
|
|
void stop(void)
|
|
{
|
|
if (_playing) {
|
|
_playing = false;
|
|
}
|
|
}
|
|
|
|
bool isPlaying(void) { return _playing; }
|
|
|
|
unsigned int read(void **buf, uint16_t nsamples) {
|
|
if (!_playing) return 0;
|
|
|
|
int16_t *index[_numChannels];
|
|
unsigned int count = 0;
|
|
for (int channel=0; channel < _numChannels; channel++) {
|
|
index[channel] = (int16_t*)buf[channel];
|
|
}
|
|
|
|
while (count < nsamples) {
|
|
for (int channel=0; channel < _numChannels; channel++) {
|
|
if (readNextValue(index[channel], channel)) {
|
|
if (channel == _numChannels - 1)
|
|
count++;
|
|
index[channel]++;
|
|
}
|
|
else {
|
|
// we have reached the end of the file
|
|
|
|
switch (_loopType) {
|
|
case looptype_repeat:
|
|
{
|
|
if (_playbackRate >= 0.0)
|
|
_bufferPosition = _loop_start;
|
|
else
|
|
_bufferPosition = _loop_finish - _numChannels;
|
|
|
|
break;
|
|
}
|
|
|
|
case looptype_pingpong:
|
|
{
|
|
if (_playbackRate >= 0.0) {
|
|
_bufferPosition = _loop_finish - _numChannels;
|
|
//printf("switching to reverse playback...\n");
|
|
}
|
|
else {
|
|
_bufferPosition = _header_offset;
|
|
//printf("switching to forward playback...\n");
|
|
}
|
|
_playbackRate = -_playbackRate;
|
|
break;
|
|
}
|
|
|
|
case looptype_none:
|
|
default:
|
|
{
|
|
//Serial.printf("end of loop...\n");
|
|
/* no looping - return the number of (resampled) bytes returned... */
|
|
close();
|
|
return count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// read the sample value for given channel and store it at the location pointed to by the pointer 'value'
|
|
bool readNextValue(int16_t *value, uint16_t channel) {
|
|
if (_playbackRate >= 0 ) {
|
|
//forward playback
|
|
if (_bufferPosition >= _loop_finish )
|
|
return false;
|
|
|
|
} else if (_playbackRate < 0) {
|
|
// reverse playback
|
|
if (_bufferPosition < _header_offset)
|
|
return false;
|
|
}
|
|
|
|
|
|
int16_t result = getSourceBufferValue(_bufferPosition + channel);
|
|
if (_interpolationType == ResampleInterpolationType::resampleinterpolation_linear) {
|
|
|
|
double abs_remainder = abs(_remainder);
|
|
if (abs_remainder > 0.0) {
|
|
|
|
if (_playbackRate > 0) {
|
|
if (_remainder - _playbackRate < 0.0){
|
|
// we crossed over a whole number, make sure we update the samples for interpolation
|
|
|
|
if (_playbackRate > 1.0) {
|
|
// need to update last sample
|
|
_interpolationPoints[channel][1].y = getSourceBufferValue(_bufferPosition-_numChannels);
|
|
}
|
|
|
|
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
|
|
_interpolationPoints[channel][1].y = result;
|
|
if (_numInterpolationPoints < 2)
|
|
_numInterpolationPoints++;
|
|
}
|
|
}
|
|
else if (_playbackRate < 0) {
|
|
if (_remainder - _playbackRate > 0.0){
|
|
// we crossed over a whole number, make sure we update the samples for interpolation
|
|
|
|
if (_playbackRate < -1.0) {
|
|
// need to update last sample
|
|
_interpolationPoints[channel][1].y = getSourceBufferValue(_bufferPosition+_numChannels);
|
|
}
|
|
|
|
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
|
|
_interpolationPoints[channel][1].y = result;
|
|
if (_numInterpolationPoints < 2)
|
|
_numInterpolationPoints++;
|
|
}
|
|
}
|
|
|
|
if (_numInterpolationPoints > 1) {
|
|
result = abs_remainder * _interpolationPoints[channel][1].y + (1.0 - abs_remainder) * _interpolationPoints[channel][0].y;
|
|
//Serial.printf("[%f]\n", interpolation);
|
|
}
|
|
} else {
|
|
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
|
|
_interpolationPoints[channel][1].y = result;
|
|
if (_numInterpolationPoints < 2)
|
|
_numInterpolationPoints++;
|
|
|
|
result =_interpolationPoints[channel][0].y;
|
|
//Serial.printf("%f\n", result);
|
|
}
|
|
}
|
|
else if (_interpolationType == ResampleInterpolationType::resampleinterpolation_quadratic) {
|
|
double abs_remainder = abs(_remainder);
|
|
if (abs_remainder > 0.0) {
|
|
if (_playbackRate > 0) {
|
|
if (_remainder - _playbackRate < 0.0){
|
|
// we crossed over a whole number, make sure we update the samples for interpolation
|
|
int numberOfSamplesToUpdate = - floor(_remainder - _playbackRate);
|
|
if (numberOfSamplesToUpdate > 4)
|
|
numberOfSamplesToUpdate = 4; // if playbackrate > 4, only need to pop last 4 samples
|
|
for (int i=numberOfSamplesToUpdate; i > 0; i--) {
|
|
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
|
|
_interpolationPoints[channel][1].y = _interpolationPoints[channel][2].y;
|
|
_interpolationPoints[channel][2].y = _interpolationPoints[channel][3].y;
|
|
_interpolationPoints[channel][3].y = getSourceBufferValue(_bufferPosition-(i*_numChannels)+1+channel);
|
|
if (_numInterpolationPoints < 4) _numInterpolationPoints++;
|
|
}
|
|
}
|
|
}
|
|
else if (_playbackRate < 0) {
|
|
if (_remainder - _playbackRate > 0.0){
|
|
// we crossed over a whole number, make sure we update the samples for interpolation
|
|
int numberOfSamplesToUpdate = ceil(_remainder - _playbackRate);
|
|
if (numberOfSamplesToUpdate > 4)
|
|
numberOfSamplesToUpdate = 4; // if playbackrate > 4, only need to pop last 4 samples
|
|
for (int i=numberOfSamplesToUpdate; i > 0; i--) {
|
|
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
|
|
_interpolationPoints[channel][1].y = _interpolationPoints[channel][2].y;
|
|
_interpolationPoints[channel][2].y = _interpolationPoints[channel][3].y;
|
|
_interpolationPoints[channel][3].y = getSourceBufferValue(_bufferPosition+(i*_numChannels)-1+channel);
|
|
if (_numInterpolationPoints < 4) _numInterpolationPoints++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_numInterpolationPoints >= 4) {
|
|
//int16_t interpolation = interpolate(_interpolationPoints, 1.0 + abs_remainder, 4);
|
|
int16_t interpolation
|
|
= fastinterpolate(
|
|
_interpolationPoints[channel][0].y,
|
|
_interpolationPoints[channel][1].y,
|
|
_interpolationPoints[channel][2].y,
|
|
_interpolationPoints[channel][3].y,
|
|
1.0 + abs_remainder);
|
|
result = interpolation;
|
|
//Serial.printf("[%f]\n", interpolation);
|
|
} else
|
|
result = 0;
|
|
} else {
|
|
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
|
|
_interpolationPoints[channel][1].y = _interpolationPoints[channel][2].y;
|
|
_interpolationPoints[channel][2].y = _interpolationPoints[channel][3].y;
|
|
_interpolationPoints[channel][3].y = result;
|
|
if (_numInterpolationPoints < 4) {
|
|
_numInterpolationPoints++;
|
|
result = 0;
|
|
} else
|
|
result = _interpolationPoints[channel][1].y;
|
|
//Serial.printf("%f\n", result);
|
|
}
|
|
}
|
|
|
|
if (channel == _numChannels - 1) {
|
|
_remainder += _playbackRate;
|
|
|
|
auto delta = static_cast<signed int>(_remainder);
|
|
_remainder -= static_cast<double>(delta);
|
|
_bufferPosition += (delta * _numChannels);
|
|
}
|
|
|
|
*value = result;
|
|
return true;
|
|
}
|
|
|
|
void setPlaybackRate(double f) {
|
|
_playbackRate = f;
|
|
if (f < 0.0 && _bufferPosition == 0) {
|
|
//_file.seek(_file_size);
|
|
_bufferPosition = _file_size/2 - _numChannels;
|
|
}
|
|
}
|
|
|
|
float playbackRate() {
|
|
return _playbackRate;
|
|
}
|
|
|
|
void loop(uint32_t numSamples) {
|
|
_loop_start = _bufferPosition;
|
|
_loop_finish = _bufferPosition + numSamples * _numChannels;
|
|
_loopType = loop_type::looptype_repeat;
|
|
}
|
|
|
|
void setLoopType(loop_type loopType)
|
|
{
|
|
_loopType = loopType;
|
|
}
|
|
|
|
loop_type getLoopType() {
|
|
return _loopType;
|
|
}
|
|
|
|
int available(void) {
|
|
return _playing;
|
|
}
|
|
|
|
void reset(void) {
|
|
if (_interpolationType != ResampleInterpolationType::resampleinterpolation_none) {
|
|
initializeInterpolationPoints();
|
|
}
|
|
_numInterpolationPoints = 0;
|
|
if (_playbackRate > 0.0) {
|
|
// forward playabck - set _file_offset to first audio block in file
|
|
_bufferPosition = _header_offset;
|
|
} else {
|
|
// reverse playback - forward _file_offset to last audio block in file
|
|
_bufferPosition = _loop_finish - _numChannels;
|
|
}
|
|
}
|
|
|
|
void setLoopStart(uint32_t loop_start) {
|
|
_loop_start = _header_offset + (loop_start * _numChannels);
|
|
}
|
|
|
|
void setLoopFinish(uint32_t loop_finish) {
|
|
// sample number, (NOT byte number)
|
|
_loop_finish = _header_offset + (loop_finish * _numChannels);
|
|
}
|
|
|
|
void setInterpolationType(ResampleInterpolationType interpolationType) {
|
|
if (interpolationType != _interpolationType) {
|
|
_interpolationType = interpolationType;
|
|
initializeInterpolationPoints();
|
|
}
|
|
}
|
|
|
|
int16_t getNumChannels() {
|
|
return _numChannels;
|
|
}
|
|
|
|
void setNumChannels(uint16_t numChannels) {
|
|
if (numChannels != _numChannels) {
|
|
_numChannels = numChannels;
|
|
initializeInterpolationPoints();
|
|
}
|
|
}
|
|
|
|
void setHeaderSizeInBytes(uint32_t headerSizeInBytes) {
|
|
_header_offset = headerSizeInBytes / 2;
|
|
if (_bufferPosition < _header_offset) {
|
|
if (_playbackRate >= 0) {
|
|
_bufferPosition = _header_offset;
|
|
} else
|
|
_bufferPosition = _loop_finish - _numChannels;
|
|
}
|
|
}
|
|
|
|
#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592
|
|
uint32_t positionMillis()
|
|
{
|
|
return ((uint64_t)_file_size * B2M) >> 32;
|
|
}
|
|
|
|
uint32_t lengthMillis()
|
|
{
|
|
return ((uint64_t)_file_size * B2M) >> 32;
|
|
}
|
|
|
|
protected:
|
|
volatile bool _playing = false;
|
|
|
|
int32_t _file_size;
|
|
int32_t _header_offset = 0; // == (header size in bytes ) / 2
|
|
|
|
double _playbackRate = 1.0;
|
|
double _remainder = 0.0;
|
|
loop_type _loopType = looptype_none;
|
|
int _bufferPosition = 0;
|
|
int32_t _loop_start = 0;
|
|
int32_t _loop_finish = 0;
|
|
int16_t _numChannels = -1;
|
|
uint16_t _numInterpolationPointsChannels = 0;
|
|
char *_filename = nullptr;
|
|
TArray *_sourceBuffer = nullptr;
|
|
|
|
ResampleInterpolationType _interpolationType = ResampleInterpolationType::resampleinterpolation_none;
|
|
unsigned int _numInterpolationPoints = 0;
|
|
InterpolationData **_interpolationPoints = nullptr;
|
|
|
|
void initializeInterpolationPoints(void) {
|
|
if (_numChannels < 0)
|
|
return;
|
|
|
|
deleteInterpolationPoints();
|
|
_interpolationPoints = new InterpolationData*[_numChannels];
|
|
for (int channel=0; channel < _numChannels; channel++) {
|
|
InterpolationData *interpolation = new InterpolationData[4];
|
|
interpolation[0].y = 0.0;
|
|
interpolation[1].y = 0.0;
|
|
interpolation[2].y = 0.0;
|
|
interpolation[3].y = 0.0;
|
|
_interpolationPoints[channel] = interpolation ;
|
|
}
|
|
_numInterpolationPointsChannels = _numChannels;
|
|
}
|
|
|
|
void deleteInterpolationPoints(void)
|
|
{
|
|
if (!_interpolationPoints) return;
|
|
for (int i=0; i<_numInterpolationPointsChannels; i++) {
|
|
delete [] _interpolationPoints[i];
|
|
}
|
|
delete [] _interpolationPoints;
|
|
_interpolationPoints = nullptr;
|
|
_numInterpolationPointsChannels = 0;
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
#endif //TEENSYAUDIOLIBRARY_RESAMPLINGREADER_H
|
|
|