parent
858cc9e92e
commit
892b4ea545
@ -0,0 +1,69 @@ |
|||||||
|
#include <Audio.h> |
||||||
|
#include "synth_dexed.h" |
||||||
|
|
||||||
|
uint8_t fmpiano_sysex[156] = { |
||||||
|
95, 29, 20, 50, 99, 95, 00, 00, 41, 00, 19, 00, 00, 03, 00, 06, 79, 00, 01, 00, 14, // OP6 eg_rate_1-4, level_1-4, kbd_lev_scl_brk_pt, kbd_lev_scl_lft_depth, kbd_lev_scl_rht_depth, kbd_lev_scl_lft_curve, kbd_lev_scl_rht_curve, kbd_rate_scaling, amp_mod_sensitivity, key_vel_sensitivity, operator_output_level, osc_mode, osc_freq_coarse, osc_freq_fine, osc_detune
|
||||||
|
95, 20, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 00, 99, 00, 01, 00, 00, // OP5
|
||||||
|
95, 29, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 06, 89, 00, 01, 00, 07, // OP4
|
||||||
|
95, 20, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 02, 99, 00, 01, 00, 07, // OP3
|
||||||
|
95, 50, 35, 78, 99, 75, 00, 00, 00, 00, 00, 00, 00, 03, 00, 07, 58, 00, 14, 00, 07, // OP2
|
||||||
|
96, 25, 25, 67, 99, 75, 00, 00, 00, 00, 00, 00, 00, 03, 00, 02, 99, 00, 01, 00, 10, // OP1
|
||||||
|
94, 67, 95, 60, 50, 50, 50, 50, // 4 * pitch EG rates, 4 * pitch EG level
|
||||||
|
04, 06, 00, // algorithm, feedback, osc sync
|
||||||
|
34, 33, 00, 00, 00, 04, // lfo speed, lfo delay, lfo pitch_mod_depth, lfo_amp_mod_depth, lfo_sync, lfo_waveform
|
||||||
|
03, 24, // pitch_mod_sensitivity, transpose
|
||||||
|
70, 77, 45, 80, 73, 65, 78, 79, 00, 00 // 10 * char for name ("DEFAULT ")
|
||||||
|
}; // FM-Piano
|
||||||
|
|
||||||
|
AudioSynthDexed dexed(4,SAMPLE_RATE); // 4 voices max
|
||||||
|
AudioOutputI2S i2s1; |
||||||
|
AudioControlSGTL5000 sgtl5000_1; |
||||||
|
AudioConnection patchCord1(dexed, 0, i2s1, 0); |
||||||
|
AudioConnection patchCord2(dexed, 0, i2s1, 1); |
||||||
|
|
||||||
|
void setup() |
||||||
|
{ |
||||||
|
AudioMemory(32); |
||||||
|
|
||||||
|
sgtl5000_1.enable(); |
||||||
|
sgtl5000_1.lineOutLevel(29); |
||||||
|
sgtl5000_1.dacVolumeRamp(); |
||||||
|
sgtl5000_1.dacVolume(1.0); |
||||||
|
sgtl5000_1.unmuteHeadphone(); |
||||||
|
sgtl5000_1.unmuteLineout(); |
||||||
|
sgtl5000_1.volume(0.8, 0.8); // Headphone volume
|
||||||
|
} |
||||||
|
|
||||||
|
void loop() |
||||||
|
{ |
||||||
|
static uint8_t count; |
||||||
|
|
||||||
|
if (count % 2 == 0) |
||||||
|
{ |
||||||
|
dexed.loadInitVoice(); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
dexed.loadVoiceParameters(fmpiano_sysex); |
||||||
|
dexed.setTranspose(36); |
||||||
|
} |
||||||
|
|
||||||
|
Serial.println("Key-Down"); |
||||||
|
dexed.keydown(48, 100); |
||||||
|
delay(100); |
||||||
|
dexed.keydown(52, 100); |
||||||
|
delay(100); |
||||||
|
dexed.keydown(55, 100); |
||||||
|
delay(100); |
||||||
|
dexed.keydown(60, 100); |
||||||
|
delay(2000); |
||||||
|
|
||||||
|
Serial.println("Key-Up"); |
||||||
|
dexed.keyup(48); |
||||||
|
dexed.keyup(52); |
||||||
|
dexed.keyup(55); |
||||||
|
dexed.keyup(60); |
||||||
|
delay(2000); |
||||||
|
|
||||||
|
count++; |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
/*
|
||||||
|
MicroDexed |
||||||
|
|
||||||
|
MicroDexed is a port of the Dexed sound engine |
||||||
|
(https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6/4.x with audio shield.
|
||||||
|
Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android
|
||||||
|
|
||||||
|
(c)2018-2021 H. Wirtz <wirtz@parasitstudio.de> |
||||||
|
|
||||||
|
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, write to the Free Software Foundation, |
||||||
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||||
|
|
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef TEENSY_BOARD_DETECTION_H_INCLUDED |
||||||
|
#define TEENSY_BOARD_DETECTION_H_INCLUDED |
||||||
|
|
||||||
|
|
||||||
|
// Teensy-4.x
|
||||||
|
#if defined(__IMXRT1062__) || defined (ARDUINO_TEENSY40) || defined (ARDUINO_TEENSY41) |
||||||
|
#define TEENSY4 |
||||||
|
#if defined (ARDUINO_TEENSY40) |
||||||
|
#define TEENSY4_0 |
||||||
|
#elif defined (ARDUINO_TEENSY41) |
||||||
|
#define TEENSY4_1 |
||||||
|
#endif |
||||||
|
#endif |
||||||
|
|
||||||
|
// Teensy-3.6
|
||||||
|
#if defined(__MK66FX1M0__) |
||||||
|
# define TEENSY3_6 |
||||||
|
#endif |
||||||
|
|
||||||
|
// Teensy-3.5
|
||||||
|
#if defined (__MK64FX512__) |
||||||
|
#define TEENSY3_5 |
||||||
|
#endif |
||||||
|
|
||||||
|
#endif |
@ -1,6 +1,8 @@ |
|||||||
cmake_minimum_required(VERSION 3.5) |
cmake_minimum_required(VERSION 3.5) |
||||||
add_subdirectory(array) |
add_subdirectory(array) |
||||||
|
add_subdirectory(LittleFS) |
||||||
add_subdirectory(sampleloader) |
add_subdirectory(sampleloader) |
||||||
add_subdirectory(sd_play_all) |
add_subdirectory(sd_play_all) |
||||||
add_subdirectory(sd_raw) |
add_subdirectory(sd_raw) |
||||||
add_subdirectory(sd_wav) |
add_subdirectory(sd_wav) |
||||||
|
add_subdirectory(SerialFlash) |
||||||
|
@ -0,0 +1,7 @@ |
|||||||
|
cmake_minimum_required(VERSION 3.10) |
||||||
|
project(sd_raw) |
||||||
|
set(CMAKE_CXX_STANDARD 14) |
||||||
|
add_definitions(-DPROG_FLASH_SIZE=10000) |
||||||
|
teensy_include_directories(../../src) |
||||||
|
teensy_add_executable(littlefs_raw littlefs_raw.ino) |
||||||
|
teensy_target_link_libraries(littlefs_raw teensy_variable_playback SD SdFat Audio LittleFS SPI SerialFlash cores Wire arm_math) |
@ -0,0 +1,95 @@ |
|||||||
|
// Plays a RAW (16-bit signed) PCM audio file at slower or faster rate
|
||||||
|
// this example requires an uSD-card inserted to teensy 3.6 with a file called DEMO.RAW
|
||||||
|
#include <Arduino.h> |
||||||
|
#include <Audio.h> |
||||||
|
#include <LittleFS.h> |
||||||
|
#include <TeensyVariablePlayback.h> |
||||||
|
|
||||||
|
LittleFS_Program myfs; |
||||||
|
#define PROG_FLASH_SIZE 1024 * 1024 * 1 // Specify size to use of onboard Teensy Program Flash chip
|
||||||
|
|
||||||
|
// GUItool: begin automatically generated code
|
||||||
|
AudioPlayLfsResmp playLfsRaw1(myfs); //xy=324,457
|
||||||
|
AudioOutputI2S i2s2; //xy=840.8571472167969,445.5714416503906
|
||||||
|
AudioConnection patchCord1(playLfsRaw1, 0, i2s2, 0); |
||||||
|
AudioConnection patchCord2(playLfsRaw1, 0, i2s2, 1); |
||||||
|
AudioControlSGTL5000 audioShield; |
||||||
|
// GUItool: end automatically generated code
|
||||||
|
|
||||||
|
#define A14 10 |
||||||
|
|
||||||
|
const char* _filename = "DEMO.RAW"; |
||||||
|
const int analogInPin = A14; |
||||||
|
unsigned long lastSamplePlayed = 0; |
||||||
|
uint32_t diskSize; |
||||||
|
|
||||||
|
double getPlaybackRate(int16_t analog) { //analog: 0..1023
|
||||||
|
return (analog - 512.0) / 512.0; |
||||||
|
} |
||||||
|
|
||||||
|
void setup() { |
||||||
|
analogReference(0); |
||||||
|
pinMode(analogInPin, INPUT_DISABLE); // i.e. Analog
|
||||||
|
|
||||||
|
// see if the Flash is present and can be initialized:
|
||||||
|
// lets check to see if the T4 is setup for security first
|
||||||
|
#if ARDUINO_TEENSY40 |
||||||
|
if ((IOMUXC_GPR_GPR11 & 0x100) == 0x100) { |
||||||
|
//if security is active max disk size is 960x1024
|
||||||
|
if (PROG_FLASH_SIZE > 960 * 1024) { |
||||||
|
diskSize = 960 * 1024; |
||||||
|
Serial.printf("Security Enables defaulted to %u bytes\n", diskSize); |
||||||
|
} else { |
||||||
|
diskSize = PROG_FLASH_SIZE; |
||||||
|
Serial.printf("Security Not Enabled using %u bytes\n", diskSize); |
||||||
|
} |
||||||
|
} |
||||||
|
#else |
||||||
|
diskSize = PROG_FLASH_SIZE; |
||||||
|
#endif |
||||||
|
|
||||||
|
// checks that the LittFS program has started with the disk size specified
|
||||||
|
if (!myfs.begin(diskSize)) { |
||||||
|
Serial.printf("Error starting %s\n", "PROGRAM FLASH DISK"); |
||||||
|
while (1) { |
||||||
|
// Error, so don't do anything more - stay stuck here
|
||||||
|
} |
||||||
|
} |
||||||
|
Serial.println("LittleFS initialized."); |
||||||
|
|
||||||
|
audioShield.enable(); |
||||||
|
audioShield.volume(0.5); |
||||||
|
|
||||||
|
playLfsRaw1.enableInterpolation(true); |
||||||
|
int newsensorValue = analogRead(analogInPin); |
||||||
|
playLfsRaw1.setPlaybackRate(getPlaybackRate(newsensorValue)); |
||||||
|
|
||||||
|
AudioMemory(24); |
||||||
|
} |
||||||
|
|
||||||
|
void loop() { |
||||||
|
|
||||||
|
int newsensorValue = analogRead(analogInPin); |
||||||
|
playLfsRaw1.setPlaybackRate(getPlaybackRate(newsensorValue)); |
||||||
|
|
||||||
|
unsigned currentMillis = millis(); |
||||||
|
if (currentMillis > lastSamplePlayed + 500) { |
||||||
|
if (!playLfsRaw1.isPlaying()) { |
||||||
|
playLfsRaw1.playRaw(_filename, 1); |
||||||
|
lastSamplePlayed = currentMillis; |
||||||
|
|
||||||
|
Serial.print("Memory: "); |
||||||
|
Serial.print(AudioMemoryUsage()); |
||||||
|
Serial.print(","); |
||||||
|
Serial.print(AudioMemoryUsageMax()); |
||||||
|
Serial.println(); |
||||||
|
} |
||||||
|
} |
||||||
|
delay(10); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
namespace std { |
||||||
|
void __throw_bad_function_call() {} |
||||||
|
void __throw_length_error(char const*) {} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
cmake_minimum_required(VERSION 3.10) |
||||||
|
project(serialflash_raw) |
||||||
|
set(CMAKE_CXX_STANDARD 14) |
||||||
|
add_definitions(-DPROG_FLASH_SIZE=10000) |
||||||
|
teensy_include_directories(../../src) |
||||||
|
teensy_add_executable(serialflash serialflash.ino) |
||||||
|
teensy_target_link_libraries(serialflash teensy_variable_playback Audio SerialFlash SPI cores Wire arm_math) |
@ -0,0 +1,79 @@ |
|||||||
|
// Plays a RAW (16-bit signed) PCM audio file at slower or faster rate
|
||||||
|
// this example requires an uSD-card inserted to teensy 3.6 with a file called DEMO.RAW
|
||||||
|
#include <Arduino.h> |
||||||
|
#include <Audio.h> |
||||||
|
#include <SerialFlash.h> |
||||||
|
#include <TeensyVariablePlayback.h> |
||||||
|
|
||||||
|
#define CSPIN 6 |
||||||
|
|
||||||
|
SerialFlashChip myfs; |
||||||
|
|
||||||
|
// GUItool: begin automatically generated code
|
||||||
|
AudioPlaySerialFlashResmp playSerialFlash1(myfs); //xy=324,457
|
||||||
|
AudioOutputI2S i2s2; //xy=840.8571472167969,445.5714416503906
|
||||||
|
AudioConnection patchCord1(playSerialFlash1, 0, i2s2, 0); |
||||||
|
AudioConnection patchCord2(playSerialFlash1, 0, i2s2, 1); |
||||||
|
AudioControlSGTL5000 audioShield; |
||||||
|
// GUItool: end automatically generated code
|
||||||
|
|
||||||
|
#define A14 10 |
||||||
|
|
||||||
|
const char* _filename = "DEMO.RAW"; |
||||||
|
const int analogInPin = A14; |
||||||
|
unsigned long lastSamplePlayed = 0; |
||||||
|
uint32_t diskSize; |
||||||
|
|
||||||
|
double getPlaybackRate(int16_t analog) { //analog: 0..1023
|
||||||
|
return (analog - 512.0) / 512.0; |
||||||
|
} |
||||||
|
|
||||||
|
void setup() { |
||||||
|
analogReference(0); |
||||||
|
pinMode(analogInPin, INPUT_DISABLE); // i.e. Analog
|
||||||
|
|
||||||
|
|
||||||
|
if (!SerialFlash.begin(CSPIN)) { |
||||||
|
while (1) { |
||||||
|
Serial.println(F("Unable to access SPI Flash chip")); |
||||||
|
delay(1000); |
||||||
|
} |
||||||
|
} |
||||||
|
Serial.println("SerialFlash initialized."); |
||||||
|
|
||||||
|
audioShield.enable(); |
||||||
|
audioShield.volume(0.5); |
||||||
|
|
||||||
|
playSerialFlash1.enableInterpolation(true); |
||||||
|
int newsensorValue = analogRead(analogInPin); |
||||||
|
playSerialFlash1.setPlaybackRate(getPlaybackRate(newsensorValue)); |
||||||
|
|
||||||
|
AudioMemory(24); |
||||||
|
} |
||||||
|
|
||||||
|
void loop() { |
||||||
|
|
||||||
|
int newsensorValue = analogRead(analogInPin); |
||||||
|
playSerialFlash1.setPlaybackRate(getPlaybackRate(newsensorValue)); |
||||||
|
|
||||||
|
unsigned currentMillis = millis(); |
||||||
|
if (currentMillis > lastSamplePlayed + 500) { |
||||||
|
if (!playSerialFlash1.isPlaying()) { |
||||||
|
playSerialFlash1.playRaw(_filename, 1); |
||||||
|
lastSamplePlayed = currentMillis; |
||||||
|
|
||||||
|
Serial.print("Memory: "); |
||||||
|
Serial.print(AudioMemoryUsage()); |
||||||
|
Serial.print(","); |
||||||
|
Serial.print(AudioMemoryUsageMax()); |
||||||
|
Serial.println(); |
||||||
|
} |
||||||
|
} |
||||||
|
delay(10); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
namespace std { |
||||||
|
void __throw_bad_function_call() {} |
||||||
|
void __throw_length_error(char const*) {} |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
#ifndef TEENSY_RESAMPLING_INDEXABLELITTLEFS_FILE_H |
||||||
|
#define TEENSY_RESAMPLING_INDEXABLELITTLEFS_FILE_H |
||||||
|
|
||||||
|
#include <Arduino.h> |
||||||
|
#include "IndexableFile.h" |
||||||
|
#include <LittleFS.h> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
namespace newdigate { |
||||||
|
|
||||||
|
template<size_t BUFFER_SIZE, size_t MAX_NUM_BUFFERS> // BUFFER_SIZE needs to be a power of two
|
||||||
|
class IndexableLittleFSFile : public IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS, File> { |
||||||
|
public: |
||||||
|
static_assert(isPowerOf2(BUFFER_SIZE), "BUFFER_SIZE must be a power of 2"); |
||||||
|
|
||||||
|
IndexableLittleFSFile(LittleFS &fs, const char *filename) :
|
||||||
|
IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS, File>(filename), |
||||||
|
_myFS(fs)
|
||||||
|
{ |
||||||
|
IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,File>::_file = _myFS.open(filename); |
||||||
|
} |
||||||
|
|
||||||
|
File open(const char *filename) override { |
||||||
|
return _myFS.open(filename); |
||||||
|
} |
||||||
|
|
||||||
|
virtual ~IndexableLittleFSFile() { |
||||||
|
IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,File>::close(); |
||||||
|
} |
||||||
|
|
||||||
|
int16_t &operator[](int i) { |
||||||
|
return IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,File>::operator[](i); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
LittleFS &_myFS; |
||||||
|
}; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif //TEENSY_RESAMPLING_INDEXABLELITTLEFS_FILE_H
|
@ -0,0 +1,38 @@ |
|||||||
|
#ifndef TEENSY_RESAMPLING_INDEXABLESD_FILE_H |
||||||
|
#define TEENSY_RESAMPLING_INDEXABLESD_FILE_H |
||||||
|
|
||||||
|
#include <Arduino.h> |
||||||
|
#include "IndexableFile.h" |
||||||
|
#include <SD.h> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
namespace newdigate { |
||||||
|
|
||||||
|
template<size_t BUFFER_SIZE, size_t MAX_NUM_BUFFERS> // BUFFER_SIZE needs to be a power of two
|
||||||
|
class IndexableSDFile : public IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,File> { |
||||||
|
public: |
||||||
|
static_assert(isPowerOf2(BUFFER_SIZE), "BUFFER_SIZE must be a power of 2"); |
||||||
|
|
||||||
|
IndexableSDFile(const char *filename) :
|
||||||
|
IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,File>(filename) { |
||||||
|
IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,File>::_file = open(filename); |
||||||
|
} |
||||||
|
|
||||||
|
File open(const char *filename) override { |
||||||
|
return SD.open(filename); |
||||||
|
} |
||||||
|
|
||||||
|
virtual ~IndexableSDFile() { |
||||||
|
IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,File>::close(); |
||||||
|
} |
||||||
|
|
||||||
|
int16_t &operator[](int i) { |
||||||
|
return IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,File>::operator[](i); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,43 @@ |
|||||||
|
#ifndef TEENSY_RESAMPLING_INDEXABLESERIALFLASH_FILE_H |
||||||
|
#define TEENSY_RESAMPLING_INDEXABLESERIALFLASH_FILE_H |
||||||
|
|
||||||
|
#include <Arduino.h> |
||||||
|
#include "IndexableFile.h" |
||||||
|
#include <SerialFlash.h> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
namespace newdigate { |
||||||
|
|
||||||
|
template<size_t BUFFER_SIZE, size_t MAX_NUM_BUFFERS> // BUFFER_SIZE needs to be a power of two
|
||||||
|
class IndexableSerialFlashFile : public IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,SerialFlashFile> { |
||||||
|
public: |
||||||
|
static_assert(isPowerOf2(BUFFER_SIZE), "BUFFER_SIZE must be a power of 2"); |
||||||
|
|
||||||
|
IndexableSerialFlashFile(SerialFlashChip &fs, const char *filename) :
|
||||||
|
IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,SerialFlashFile>(filename), |
||||||
|
_myFS(fs)
|
||||||
|
{ |
||||||
|
IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,SerialFlashFile>::_file = _myFS.open(filename); |
||||||
|
} |
||||||
|
|
||||||
|
SerialFlashFile open(const char *filename) override { |
||||||
|
return _myFS.open(filename); |
||||||
|
} |
||||||
|
|
||||||
|
virtual ~IndexableSerialFlashFile() { |
||||||
|
IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,SerialFlashFile>::close(); |
||||||
|
} |
||||||
|
|
||||||
|
int16_t &operator[](int i) { |
||||||
|
return IndexableFile<BUFFER_SIZE, MAX_NUM_BUFFERS,SerialFlashFile>::operator[](i); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
SerialFlashChip &_myFS; |
||||||
|
}; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif //TEENSY_RESAMPLING_INDEXABLESERIALFLASH_FILE_H
|
@ -0,0 +1,78 @@ |
|||||||
|
//
|
||||||
|
// Created by Nicholas Newdigate on 10/02/2019.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TEENSYAUDIOLIBRARY_RESAMPLINGLFSREADER_H |
||||||
|
#define TEENSYAUDIOLIBRARY_RESAMPLINGLFSREADER_H |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
#include "IndexableLittleFSFile.h" |
||||||
|
#include "ResamplingReader.h" |
||||||
|
#include "LittleFS.h" |
||||||
|
|
||||||
|
#define RESAMPLE_BUFFER_SAMPLE_SIZE 128 |
||||||
|
|
||||||
|
#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592
|
||||||
|
|
||||||
|
namespace newdigate { |
||||||
|
|
||||||
|
class ResamplingLfsReader : public ResamplingReader< IndexableLittleFSFile<128, 2>, File > { |
||||||
|
public: |
||||||
|
ResamplingLfsReader(LittleFS &fs) :
|
||||||
|
ResamplingReader(), |
||||||
|
_myFS(fs)
|
||||||
|
{ |
||||||
|
} |
||||||
|
virtual ~ResamplingLfsReader() { |
||||||
|
} |
||||||
|
|
||||||
|
int16_t getSourceBufferValue(long index) override { |
||||||
|
return (*_sourceBuffer)[index]; |
||||||
|
} |
||||||
|
|
||||||
|
int available(void) |
||||||
|
{ |
||||||
|
return _playing; |
||||||
|
} |
||||||
|
|
||||||
|
File open(char *filename) override { |
||||||
|
return _myFS.open(filename); |
||||||
|
} |
||||||
|
|
||||||
|
void close(void) override |
||||||
|
{ |
||||||
|
if (_playing) |
||||||
|
stop(); |
||||||
|
if (_sourceBuffer != nullptr) { |
||||||
|
_sourceBuffer->close(); |
||||||
|
delete _sourceBuffer; |
||||||
|
_sourceBuffer = nullptr; |
||||||
|
} |
||||||
|
if (_filename != nullptr) { |
||||||
|
delete [] _filename; |
||||||
|
_filename = nullptr; |
||||||
|
} |
||||||
|
deleteInterpolationPoints(); |
||||||
|
} |
||||||
|
|
||||||
|
IndexableLittleFSFile<128, 2>* createSourceBuffer() override { |
||||||
|
return new IndexableLittleFSFile<128, 2>(_myFS, _filename); |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t positionMillis(void) { |
||||||
|
if (_file_size == 0) return 0; |
||||||
|
|
||||||
|
return (uint32_t) (( (double)_bufferPosition * lengthMillis() ) / (double)(_file_size/2)); |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t lengthMillis(void) { |
||||||
|
return ((uint64_t)_file_size * B2M) >> 32; |
||||||
|
} |
||||||
|
|
||||||
|
protected:
|
||||||
|
LittleFS &_myFS; |
||||||
|
}; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
#endif //TEENSYAUDIOLIBRARY_RESAMPLINGLFSREADER_H
|
@ -0,0 +1,500 @@ |
|||||||
|
#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
|
@ -0,0 +1,82 @@ |
|||||||
|
//
|
||||||
|
// Created by Nicholas Newdigate on 10/02/2019.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TEENSYAUDIOLIBRARY_RESAMPLINGSERIALFLASHREADER_H |
||||||
|
#define TEENSYAUDIOLIBRARY_RESAMPLINGSERIALFLASHREADER_H |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
#include "spi_interrupt.h" |
||||||
|
#include "loop_type.h" |
||||||
|
#include "interpolation.h" |
||||||
|
#include "IndexableSerialFlashFile.h" |
||||||
|
#include "ResamplingReader.h" |
||||||
|
#include "SerialFlash.h" |
||||||
|
|
||||||
|
#define RESAMPLE_BUFFER_SAMPLE_SIZE 128 |
||||||
|
|
||||||
|
#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592
|
||||||
|
|
||||||
|
namespace newdigate { |
||||||
|
|
||||||
|
class ResamplingSerialFlashReader : public ResamplingReader< IndexableSerialFlashFile<128, 2>, SerialFlashFile > { |
||||||
|
public: |
||||||
|
ResamplingSerialFlashReader(SerialFlashChip &fs) :
|
||||||
|
ResamplingReader(), |
||||||
|
_myFS(fs)
|
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
virtual ~ResamplingSerialFlashReader() { |
||||||
|
} |
||||||
|
|
||||||
|
int16_t getSourceBufferValue(long index) override { |
||||||
|
return (*_sourceBuffer)[index]; |
||||||
|
} |
||||||
|
|
||||||
|
int available(void) |
||||||
|
{ |
||||||
|
return _playing; |
||||||
|
} |
||||||
|
|
||||||
|
SerialFlashFile open(char *filename) override { |
||||||
|
return _myFS.open(filename); |
||||||
|
} |
||||||
|
|
||||||
|
void close(void) override |
||||||
|
{ |
||||||
|
if (_playing) |
||||||
|
stop(); |
||||||
|
if (_sourceBuffer != nullptr) { |
||||||
|
_sourceBuffer->close(); |
||||||
|
delete _sourceBuffer; |
||||||
|
_sourceBuffer = nullptr; |
||||||
|
} |
||||||
|
if (_filename != nullptr) { |
||||||
|
delete [] _filename; |
||||||
|
_filename = nullptr; |
||||||
|
} |
||||||
|
deleteInterpolationPoints(); |
||||||
|
} |
||||||
|
|
||||||
|
IndexableSerialFlashFile<128, 2>* createSourceBuffer() override { |
||||||
|
return new IndexableSerialFlashFile<128, 2>(_myFS, _filename); |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t positionMillis(void) { |
||||||
|
if (_file_size == 0) return 0; |
||||||
|
|
||||||
|
return (uint32_t) (( (double)_bufferPosition * lengthMillis() ) / (double)(_file_size/2)); |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t lengthMillis(void) { |
||||||
|
return ((uint64_t)_file_size * B2M) >> 32; |
||||||
|
} |
||||||
|
|
||||||
|
protected:
|
||||||
|
SerialFlashChip &_myFS; |
||||||
|
}; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
#endif //TEENSYAUDIOLIBRARY_RESAMPLINGSERIALFLASHREADER_H
|
@ -0,0 +1,26 @@ |
|||||||
|
//
|
||||||
|
// Created by Nicholas Newdigate on 18/07/2020.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TEENSY_RESAMPLING_SDREADER_PLAYLFSRAWRESMP_H |
||||||
|
#define TEENSY_RESAMPLING_SDREADER_PLAYLFSRAWRESMP_H |
||||||
|
|
||||||
|
#include "ResamplingLfsReader.h" |
||||||
|
|
||||||
|
class AudioPlayLfsResmp : public AudioPlayResmp<newdigate::ResamplingLfsReader> |
||||||
|
{ |
||||||
|
public: |
||||||
|
AudioPlayLfsResmp(LittleFS &fs) : |
||||||
|
AudioPlayResmp<newdigate::ResamplingLfsReader>() |
||||||
|
{ |
||||||
|
reader = new newdigate::ResamplingLfsReader(fs); |
||||||
|
begin(); |
||||||
|
} |
||||||
|
|
||||||
|
virtual ~AudioPlayLfsResmp() { |
||||||
|
delete reader; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
#endif //TEENSY_RESAMPLING_SDREADER_PLAYLFSRAWRESMP_H
|
@ -0,0 +1,31 @@ |
|||||||
|
//
|
||||||
|
// Created by Nicholas Newdigate on 18/07/2020.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TEENSY_RESAMPLING_SDREADER_PLAYSERIALFLASHRAWRESMP_H |
||||||
|
#define TEENSY_RESAMPLING_SDREADER_PLAYSERIALFLASHRAWRESMP_H |
||||||
|
|
||||||
|
#include "Arduino.h" |
||||||
|
#include "AudioStream.h" |
||||||
|
#include "SerialFlash.h" |
||||||
|
#include "stdint.h" |
||||||
|
#include "ResamplingSerialFlashReader.h" |
||||||
|
#include "playresmp.h" |
||||||
|
|
||||||
|
class AudioPlaySerialFlashResmp : public AudioPlayResmp<newdigate::ResamplingSerialFlashReader> |
||||||
|
{ |
||||||
|
public: |
||||||
|
AudioPlaySerialFlashResmp(SerialFlashChip &fs) : |
||||||
|
AudioPlayResmp<newdigate::ResamplingSerialFlashReader>() |
||||||
|
{ |
||||||
|
reader = new newdigate::ResamplingSerialFlashReader(fs); |
||||||
|
begin(); |
||||||
|
} |
||||||
|
|
||||||
|
virtual ~AudioPlaySerialFlashResmp() { |
||||||
|
delete reader; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
#endif //TEENSY_RESAMPLING_SDREADER_PLAYSERIALFLASHRAWRESMP_H
|
Loading…
Reference in new issue