New third-party libraries.

dev
Holger Wirtz 2 years ago
parent 858cc9e92e
commit 892b4ea545
  1. 2
      third-party/LCDMenuLib2/src/LCDMenuLib2.h
  2. 9
      third-party/LCDMenuLib2/src/LCDMenuLib2_macros.h
  3. 0
      third-party/MD_REncoder/ISSUE_TEMPLATE.md
  4. 0
      third-party/MD_REncoder/LICENSE
  5. 0
      third-party/MD_REncoder/PULL_REQUEST.md
  6. 0
      third-party/MD_REncoder/README.md
  7. 0
      third-party/MD_REncoder/examples/Polling/Polling.ino
  8. 0
      third-party/MD_REncoder/keywords.txt
  9. 0
      third-party/MD_REncoder/library.properties
  10. 0
      third-party/MD_REncoder/src/MD_REncoder.cpp
  11. 0
      third-party/MD_REncoder/src/MD_REncoder.h
  12. 69
      third-party/Synth_Dexed/examples/synth_dexed.ino
  13. 50
      third-party/Synth_Dexed/src/teensy_board_detection.h
  14. 0
      third-party/Synth_MDA_EPiano/.gitignore
  15. 0
      third-party/Synth_MDA_EPiano/LICENSE-GPL3.txt
  16. 0
      third-party/Synth_MDA_EPiano/README.md
  17. 0
      third-party/Synth_MDA_EPiano/examples/MDA_EP_SimplePlay/MDA_EP_SimplePlay.ino
  18. 0
      third-party/Synth_MDA_EPiano/library.properties
  19. 16
      third-party/Synth_MDA_EPiano/src/mdaEPiano.cpp
  20. 0
      third-party/Synth_MDA_EPiano/src/mdaEPiano.h
  21. 0
      third-party/Synth_MDA_EPiano/src/mdaEPianoData.h
  22. 0
      third-party/Synth_MDA_EPiano/src/mdaEPianoDataXfade.h
  23. 0
      third-party/Synth_MDA_EPiano/src/synth_epiano.h
  24. 0
      third-party/Synth_MDA_EPiano/src/synth_mda_epiano.h
  25. 0
      third-party/Synth_MDA_EPiano/utility/xfade_generator.c
  26. 2
      third-party/TeensyTimerTool/library.json
  27. 2
      third-party/TeensyTimerTool/library.properties
  28. 10
      third-party/TeensyTimerTool/src/API/baseTimer.h
  29. 2
      third-party/TeensyTimerTool/src/ITimerChannel.h
  30. 1
      third-party/TeensyTimerTool/src/TimerModules/TCK/TckChannel.h
  31. 7
      third-party/TeensyTimerTool/src/TimerModules/TCK/tickCounters.cpp
  32. 7
      third-party/TeensyTimerTool/src/types.h
  33. 2
      third-party/TeensyVariablePlayback/CMakeLists.txt
  34. 8
      third-party/TeensyVariablePlayback/README.md
  35. 2
      third-party/TeensyVariablePlayback/examples/CMakeLists.txt
  36. 7
      third-party/TeensyVariablePlayback/examples/LittleFS/CMakeLists.txt
  37. 95
      third-party/TeensyVariablePlayback/examples/LittleFS/littlefs_raw.ino
  38. 7
      third-party/TeensyVariablePlayback/examples/SerialFlash/CMakeLists.txt
  39. 79
      third-party/TeensyVariablePlayback/examples/SerialFlash/serialflash.ino
  40. 2
      third-party/TeensyVariablePlayback/library.json
  41. 2
      third-party/TeensyVariablePlayback/library.properties
  42. 17
      third-party/TeensyVariablePlayback/src/CMakeLists.txt
  43. 35
      third-party/TeensyVariablePlayback/src/IndexableFile.h
  44. 43
      third-party/TeensyVariablePlayback/src/IndexableLittleFSFile.h
  45. 38
      third-party/TeensyVariablePlayback/src/IndexableSDFile.h
  46. 43
      third-party/TeensyVariablePlayback/src/IndexableSerialFlashFile.h
  47. 113
      third-party/TeensyVariablePlayback/src/ResamplingArrayReader.h
  48. 78
      third-party/TeensyVariablePlayback/src/ResamplingLfsReader.h
  49. 500
      third-party/TeensyVariablePlayback/src/ResamplingReader.h
  50. 136
      third-party/TeensyVariablePlayback/src/ResamplingSdReader.h
  51. 82
      third-party/TeensyVariablePlayback/src/ResamplingSerialFlashReader.h
  52. 5
      third-party/TeensyVariablePlayback/src/TeensyVariablePlayback.h
  53. 51
      third-party/TeensyVariablePlayback/src/playarrayresmp.h
  54. 26
      third-party/TeensyVariablePlayback/src/playlfsresmp.h
  55. 137
      third-party/TeensyVariablePlayback/src/playresmp.h
  56. 43
      third-party/TeensyVariablePlayback/src/playsdresmp.h
  57. 31
      third-party/TeensyVariablePlayback/src/playserialflashresmp.h
  58. 30
      third-party/TeensyVariablePlayback/src/waveheaderparser.h
  59. 4
      third-party/TeensyVariablePlayback/test/low_level/array/ResamplingArrayFixture.h
  60. 4
      third-party/TeensyVariablePlayback/test/low_level/arraywav/ResamplingArrayWavFixture.h
  61. 4
      third-party/TeensyVariablePlayback/test/low_level/indexedfile/test_indexablefile.cpp
  62. 4
      third-party/TeensyVariablePlayback/test/low_level/sd/ResamplingReaderFixture.h
  63. 2
      third-party/TeensyVariablePlayback/test/low_level/sd/test_raw_mono_noloop_forward_double_rate_playback.cpp
  64. 2
      third-party/TeensyVariablePlayback/test/low_level/sd/test_raw_mono_noloop_forward_playback.cpp

@ -44,7 +44,7 @@
// ####################### // // ####################### //
// you can change this parameters // you can change this parameters
//#define _LCDML_cfg_use_ram // enable this line when you want to use the ram mode #define _LCDML_cfg_use_ram // enable this line when you want to use the ram mode
// set the number of custom events (this could be a button ore something else) // set the number of custom events (this could be a button ore something else)
#define _LCDML_CE_cb_function_cnt 4 // this is the number of custom event callback functions which are supported #define _LCDML_CE_cb_function_cnt 4 // this is the number of custom event callback functions which are supported

@ -95,15 +95,12 @@
// stored in flash (Teensy-3.5/Teensy-3.6/Teensy-4.x) // stored in flash (Teensy-3.5/Teensy-3.6/Teensy-4.x)
#define LCDML_langDef(name, lang, content) \ #define LCDML_langDef(name, lang, content) \
const char g_LCDML_DISP_lang_ ## lang ## _ ## name ##_var[] = {content} const char g_LCDML_DISP_lang_ ## lang ## _ ## name ##_var[] = {content}
#define LCDML_getCustomContent(lang, var, id) \ #define LCDML_getCustomContent(lang, var, id) \
if(id < _LCDML_NO_FUNC) {\ if(id < _LCDML_NO_FUNC) {\
strcpy(var, g_LCDML_DISP_lang_ ## lang ## _table[id]); \ strcpy(var, g_LCDML_DISP_lang_ ## lang ## _table[id]); \
} }
#define LCDML_createCustomLang(N, lang) \ #define LCDML_createCustomLang(N, lang) \
const char * g_LCDML_DISP_lang_ ## lang ## _table[] = { LCDML_DISP_lang_repeat(N, lang) } const char * g_LCDML_DISP_lang_ ## lang ## _table[] = { LCDML_DISP_lang_repeat(N, lang) }
#define LCDML_getCustomElementName(lang, var, element_id) \ #define LCDML_getCustomElementName(lang, var, element_id) \
if(element_id < _LCDML_NO_FUNC && (sizeof(g_LCDML_DISP_lang_ ## lang ## _table)-1) >= element_id) {\ if(element_id < _LCDML_NO_FUNC && (sizeof(g_LCDML_DISP_lang_ ## lang ## _table)-1) >= element_id) {\
strcpy(var, g_LCDML_DISP_lang_ ## lang ## _table[element_id]);\ strcpy(var, g_LCDML_DISP_lang_ ## lang ## _table[element_id]);\
@ -112,15 +109,12 @@
// stored in flash (Arduino) // stored in flash (Arduino)
#define LCDML_langDef(name, lang, content) \ #define LCDML_langDef(name, lang, content) \
const char g_LCDML_DISP_lang_ ## lang ## _ ## name ##_var[] PROGMEM = {content} const char g_LCDML_DISP_lang_ ## lang ## _ ## name ##_var[] PROGMEM = {content}
#define LCDML_getCustomContent(lang, var, id) \ #define LCDML_getCustomContent(lang, var, id) \
if(id < _LCDML_NO_FUNC) {\ if(id < _LCDML_NO_FUNC) {\
strcpy_P(var, (char*)pgm_read_word(&(g_LCDML_DISP_lang_ ## lang ## _table[id]))); \ strcpy_P(var, (char*)pgm_read_word(&(g_LCDML_DISP_lang_ ## lang ## _table[id]))); \
} }
#define LCDML_createCustomLang(N, lang) \ #define LCDML_createCustomLang(N, lang) \
const char * const g_LCDML_DISP_lang_ ## lang ## _table[] PROGMEM = { LCDML_DISP_lang_repeat(N, lang) } const char * const g_LCDML_DISP_lang_ ## lang ## _table[] PROGMEM = { LCDML_DISP_lang_repeat(N, lang) }
#define LCDML_getCustomElementName(lang, var, element_id) \ #define LCDML_getCustomElementName(lang, var, element_id) \
if(element_id < _LCDML_NO_FUNC && (sizeof(g_LCDML_DISP_lang_ ## lang ## _table)-1) >= element_id) {\ if(element_id < _LCDML_NO_FUNC && (sizeof(g_LCDML_DISP_lang_ ## lang ## _table)-1) >= element_id) {\
strcpy_P(var, (char*)pgm_read_word(&(g_LCDML_DISP_lang_ ## lang ## _table[element_id])));\ strcpy_P(var, (char*)pgm_read_word(&(g_LCDML_DISP_lang_ ## lang ## _table[element_id])));\
@ -130,15 +124,12 @@
// stored in ram (esp, stm, other controllers) // stored in ram (esp, stm, other controllers)
#define LCDML_langDef(name, lang, content) \ #define LCDML_langDef(name, lang, content) \
char g_LCDML_DISP_lang_ ## lang ## _ ## name ##_var[] = {content} char g_LCDML_DISP_lang_ ## lang ## _ ## name ##_var[] = {content}
#define LCDML_getCustomContent(lang, var, id) \ #define LCDML_getCustomContent(lang, var, id) \
if(id < _LCDML_NO_FUNC) {\ if(id < _LCDML_NO_FUNC) {\
strcpy(var, g_LCDML_DISP_lang_ ## lang ## _table[id]); \ strcpy(var, g_LCDML_DISP_lang_ ## lang ## _table[id]); \
} }
#define LCDML_createCustomLang(N, lang) \ #define LCDML_createCustomLang(N, lang) \
char * g_LCDML_DISP_lang_ ## lang ## _table[] = { LCDML_DISP_lang_repeat(N, lang) } char * g_LCDML_DISP_lang_ ## lang ## _table[] = { LCDML_DISP_lang_repeat(N, lang) }
#define LCDML_getCustomElementName(lang, var, element_id) \ #define LCDML_getCustomElementName(lang, var, element_id) \
if(element_id < _LCDML_NO_FUNC && (sizeof(g_LCDML_DISP_lang_ ## lang ## _table)-1) >= element_id) {\ if(element_id < _LCDML_NO_FUNC && (sizeof(g_LCDML_DISP_lang_ ## lang ## _table)-1) >= element_id) {\
strcpy(var, g_LCDML_DISP_lang_ ## lang ## _table[element_id]);\ strcpy(var, g_LCDML_DISP_lang_ ## lang ## _table[element_id]);\

@ -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

@ -38,14 +38,14 @@ mdaEPiano::mdaEPiano(uint8_t nvoices) // mdaEPiano::mdaEPiano(audioMasterCallbac
uint8_t i=0; uint8_t i=0;
programs = new mdaEPianoProgram[NPROGS]; programs = new mdaEPianoProgram[NPROGS];
fillpatch(i++, "Default", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f); fillpatch(i++, (char *) "Default", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f);
fillpatch(i++, "Bright", 0.500f, 0.500f, 1.000f, 0.800f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.500f); fillpatch(i++, (char *) "Bright", 0.500f, 0.500f, 1.000f, 0.800f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.500f);
fillpatch(i++, "Mellow", 0.500f, 0.500f, 0.000f, 0.000f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.246f, 0.000f); fillpatch(i++, (char *) "Mellow", 0.500f, 0.500f, 0.000f, 0.000f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.246f, 0.000f);
fillpatch(i++, "Autopan", 0.500f, 0.500f, 0.500f, 0.500f, 0.250f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.246f, 0.000f); fillpatch(i++, (char *) "Autopan", 0.500f, 0.500f, 0.500f, 0.500f, 0.250f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.246f, 0.000f);
fillpatch(i++, "Tremolo", 0.500f, 0.500f, 0.500f, 0.500f, 0.750f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.246f, 0.000f); fillpatch(i++, (char *) "Tremolo", 0.500f, 0.500f, 0.500f, 0.500f, 0.750f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.246f, 0.000f);
fillpatch(i++, "(default)", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f); fillpatch(i++, (char *) "(default)", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f);
fillpatch(i++, "(default)", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f); fillpatch(i++, (char *) "(default)", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f);
fillpatch(i++, "(default)", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f); fillpatch(i++, (char *) "(default)", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f);
setDecay(0.500f); setDecay(0.500f);
setRelease(0.500f); setRelease(0.500f);

@ -14,7 +14,7 @@
"maintainer": true "maintainer": true
}, },
"homepage": "https://github.com/luni64/TeensyTimerTool", "homepage": "https://github.com/luni64/TeensyTimerTool",
"version": "0.4.3", "version": "0.4.4",
"frameworks": "arduino", "frameworks": "arduino",
"platforms": "Teensy" "platforms": "Teensy"
} }

@ -1,5 +1,5 @@
name=TeensyTimerTool name=TeensyTimerTool
version=0.4.3 version=0.4.4
author=luni64 author=luni64
maintainer=luni64 maintainer=luni64
sentence=Generic Interface to Teensy Timers sentence=Generic Interface to Teensy Timers

@ -27,6 +27,7 @@ namespace TeensyTimerTool
inline errorCode stop(); inline errorCode stop();
inline float getMaxPeriod() const; inline float getMaxPeriod() const;
inline float getRemainingTime() const;
protected: protected:
BaseTimer(TimerGenerator *generator, bool periodic); BaseTimer(TimerGenerator *generator, bool periodic);
@ -102,4 +103,13 @@ namespace TeensyTimerTool
postError(errorCode::notInitialized); postError(errorCode::notInitialized);
return NAN; return NAN;
} }
float BaseTimer::getRemainingTime() const
{
if (timerChannel != nullptr)
return timerChannel->getRemainingTime();
postError(errorCode::notInitialized);
return NAN;
}
} // namespace TeensyTimerTool } // namespace TeensyTimerTool

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "types.h" #include "types.h"
#include <cmath>
namespace TeensyTimerTool namespace TeensyTimerTool
{ {
@ -20,6 +21,7 @@ namespace TeensyTimerTool
virtual errorCode setPrescaler(int psc) { return postError(errorCode::notImplemented); } virtual errorCode setPrescaler(int psc) { return postError(errorCode::notImplemented); }
virtual float getMaxPeriod() const = 0; virtual float getMaxPeriod() const = 0;
virtual float getRemainingTime() const { postError(errorCode::notImplemented); return NAN; }
virtual errorCode setPeriod(float microSeconds) { return postError(errorCode::notImplemented); }; virtual errorCode setPeriod(float microSeconds) { return postError(errorCode::notImplemented); };
virtual errorCode setNextPeriod(float microSeconds) { return postError(errorCode::notImplemented); }; virtual errorCode setNextPeriod(float microSeconds) { return postError(errorCode::notImplemented); };
virtual uint32_t getPeriod() { return 0; } virtual uint32_t getPeriod() { return 0; }

@ -26,6 +26,7 @@ namespace TeensyTimerTool
inline errorCode setPeriod(float us) override; inline errorCode setPeriod(float us) override;
float getMaxPeriod() const override { return tckCounter::getMaxMicros() / 1E6f; } // seconds float getMaxPeriod() const override { return tckCounter::getMaxMicros() / 1E6f; } // seconds
float getRemainingTime() const override { return (currentPeriod - (tckCounter::getCount() - startCnt)) / (float)TTT_F_CPU; }
protected: protected:
inline bool tick(); inline bool tick();

@ -1,14 +1,9 @@
#include "tickCounters.h" #include "tickCounters.h"
#include "Arduino.h" #include "Arduino.h"
#include "types.h"
#include "boardDef.h" #include "boardDef.h"
#if defined(TTT_TEENSY4X)
#define TTT_F_CPU F_CPU_ACTUAL
#else
#define TTT_F_CPU F_CPU
#endif
namespace TeensyTimerTool namespace TeensyTimerTool
{ {
//--------------------------------------------------------------------- //---------------------------------------------------------------------

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "ErrorHandling/error_codes.h" #include "ErrorHandling/error_codes.h"
#include "config.h" #include "config.h"
#include "Arduino.h"
#if not defined(PLAIN_VANILLA_CALLBACKS) #if not defined(PLAIN_VANILLA_CALLBACKS)
@ -27,3 +28,9 @@ namespace TeensyTimerTool
extern errorCode postError(errorCode); extern errorCode postError(errorCode);
} // namespace TeensyTimerTool } // namespace TeensyTimerTool
#endif #endif
#if defined(TTT_TEENSY4X)
#define TTT_F_CPU F_CPU_ACTUAL
#else
#define TTT_F_CPU F_CPU
#endif

@ -15,11 +15,13 @@ if (NOT DEFINED BUILD_FOR_LINUX)
import_arduino_library(Wire ${DEPSPATH}/Wire utility) import_arduino_library(Wire ${DEPSPATH}/Wire utility)
import_arduino_library(arm_math ${DEPSPATH}/arm_math/src) import_arduino_library(arm_math ${DEPSPATH}/arm_math/src)
import_arduino_library(Audio ${DEPSPATH}/Audio utility) import_arduino_library(Audio ${DEPSPATH}/Audio utility)
import_arduino_library(LittleFS ${DEPSPATH}/LittleFS/src littlefs)
add_subdirectory(src) add_subdirectory(src)
add_subdirectory(examples) add_subdirectory(examples)
else() else()
add_subdirectory(src) add_subdirectory(src)
add_subdirectory(test) add_subdirectory(test)
#add_subdirectory(extras/soundio/playqueue)
add_subdirectory(extras/soundio/save_raw) add_subdirectory(extras/soundio/save_raw)
add_subdirectory(extras/soundio/save_raw_sd) add_subdirectory(extras/soundio/save_raw_sd)
add_subdirectory(extras/soundio/save_wav) add_subdirectory(extras/soundio/save_wav)

@ -16,6 +16,11 @@ play 16-bit PCM raw or wav audio samples at variable playback rates on teensy
* [sd classes on wikipedia](https://en.wikipedia.org/wiki/SD_card#cite_ref-93) * [sd classes on wikipedia](https://en.wikipedia.org/wiki/SD_card#cite_ref-93)
## updates ## updates
* 16/06/2022: v1.0.14:
* refactored code to generic classes
* improve memory leaks
* remove calls to StartUsingSPI(), StopUsingSPI(), __disable_irq(), __enable_irq()
* intergated with SerialFlash and LittleFS
* 25/09/2021: v1.0.13: positionMillis() implemented for AudioPlaySdResmp * 25/09/2021: v1.0.13: positionMillis() implemented for AudioPlaySdResmp
* 25/08/2021: v1.0.12: Skip over RIFF tags in .wav header * 25/08/2021: v1.0.12: Skip over RIFF tags in .wav header
* 12/08/2021: v1.0.11: When playing a mono sample, transmit on both channels (credit to @atoktoto) * 12/08/2021: v1.0.11: When playing a mono sample, transmit on both channels (credit to @atoktoto)
@ -103,10 +108,11 @@ graph G {
<details> <details>
<summary>linux</summary> <summary>linux</summary>
You can run and test this code on your linux computer. You can write a teensy sketch, and with a few modifications, you can redirect the audio input and output to and from your soundcard. [Soundio](https://github.com/newdigate/teensy-audio-x86-stubs/tree/main/extras/soundio) bindings are optional, you can also run sketches and tests with no audio input or output.
You will need to install the following libraries.
```cmake``` ```gcc or llvm``` ```teensy-x86-stubs```[^](https://github.com/newdigate/teensy-x86-stubs) ```teensy-audio-x86-stubs```[^](https://github.com/newdigate/teensy-audio-x86-stubs) ```teensy-x86-sd-stubs```[^](https://github.com/newdigate/teensy-x86-sd-stubs) ```boost-test``` ```cmake``` ```gcc or llvm``` ```teensy-x86-stubs```[^](https://github.com/newdigate/teensy-x86-stubs) ```teensy-audio-x86-stubs```[^](https://github.com/newdigate/teensy-audio-x86-stubs) ```teensy-x86-sd-stubs```[^](https://github.com/newdigate/teensy-x86-sd-stubs) ```boost-test```
By using stub libraries, we can compile teensy code to native device architecture. To a certain extent, this allows sketches and libraries to be developed, emulated, debugged and unit-tested using linux, on your local device or a build server. In this case I have a few basic tests for the ResamplingSdReader class.
* install boost unit-test library: * install boost unit-test library:
* linux: ```sudo apt-get install -yq libboost-test-dev``` * linux: ```sudo apt-get install -yq libboost-test-dev```
* macos: ```brew install boost``` * macos: ```brew install boost```

@ -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*) {}
}

@ -5,7 +5,7 @@
"keywords": "sound, audio, sample, resample, pitch, interpolation, legrange, sampler, playback, speed", "keywords": "sound, audio, sample, resample, pitch, interpolation, legrange, sampler, playback, speed",
"description": "Teensy Variable Playback", "description": "Teensy Variable Playback",
"url": "https://github.com/newdigate/teensy-variable-playback", "url": "https://github.com/newdigate/teensy-variable-playback",
"version": "1.0.13", "version": "1.0.14",
"export": { "export": {
"exclude": [ "exclude": [
".vscode", ".vscode",

@ -1,5 +1,5 @@
name=TeensyVariablePlayback name=TeensyVariablePlayback
version=1.0.13 version=1.0.14
author=Nic Newdigate author=Nic Newdigate
maintainer=Nic Newdigate maintainer=Nic Newdigate
sentence=Play samples at variable pitch using Teensy Audio Library sentence=Play samples at variable pitch using Teensy Audio Library

@ -3,12 +3,8 @@ project(teensy_variable_playback C CXX)
set(teensy_variable_playback_VERSION 1.0.0) set(teensy_variable_playback_VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
set(SOURCE_FILES set(SOURCE_FILES
playsdresmp.cpp
ResamplingSdReader.cpp
ResamplingArrayReader.cpp
playarrayresmp.cpp
interpolation.cpp interpolation.cpp
IndexableFile.cpp) )
set(HEADER_FILES set(HEADER_FILES
loop_type.h loop_type.h
@ -17,10 +13,19 @@ set(HEADER_FILES
ResamplingSdReader.h ResamplingSdReader.h
waveheaderparser.h waveheaderparser.h
ResamplingArrayReader.h ResamplingArrayReader.h
ResamplingReader.h
playarrayresmp.h playarrayresmp.h
interpolation.h interpolation.h
TeensyVariablePlayback.h TeensyVariablePlayback.h
IndexableFile.h) IndexableFile.h
IndexableSerialFlashFile.h
IndexableLittleFSFile.h
IndexableSDFile.h
ResamplingLfsReader.h
ResamplingSerialFlashReader.h
playlfsresmp.h
playserialflashresmp.h
)
#set(CMAKE_VERBOSE_MAKEFILE 1) #set(CMAKE_VERBOSE_MAKEFILE 1)
if (NOT DEFINED TEENSY_VERSION) if (NOT DEFINED TEENSY_VERSION)

@ -18,19 +18,25 @@ constexpr bool isPowerOf2(size_t value){
return !(value == 0) && !(value & (value - 1)); return !(value == 0) && !(value & (value - 1));
} }
template<size_t BUFFER_SIZE, size_t MAX_NUM_BUFFERS> // BUFFER_SIZE needs to be a power of two template<size_t BUFFER_SIZE, size_t MAX_NUM_BUFFERS, class TFile> // BUFFER_SIZE needs to be a power of two
class IndexableFile { class IndexableFile {
public: public:
static_assert(isPowerOf2(BUFFER_SIZE), "BUFFER_SIZE must be a power of 2"); static_assert(isPowerOf2(BUFFER_SIZE), "BUFFER_SIZE must be a power of 2");
virtual TFile open(const char *filename) = 0;
static constexpr size_t element_size = sizeof(int16_t); static constexpr size_t element_size = sizeof(int16_t);
size_t buffer_to_index_shift; size_t buffer_to_index_shift;
IndexableFile(const char *filename) : IndexableFile(const char *filename) :
_buffers(), _buffers(),
buffer_to_index_shift(log2(BUFFER_SIZE)) { buffer_to_index_shift(log2(BUFFER_SIZE))
{
_filename = new char[strlen(filename)+1] {0}; _filename = new char[strlen(filename)+1] {0};
memcpy(_filename, filename, strlen(filename)); memcpy(_filename, filename, strlen(filename));
_file = SD.open(_filename); }
virtual ~IndexableFile() {
close();
} }
int16_t &operator[](int i) { int16_t &operator[](int i) {
@ -53,9 +59,7 @@ public:
#ifndef TEENSYDUINO #ifndef TEENSYDUINO
if (!_file.available()){ if (!_file.available()){
_file.close(); _file.close();
__disable_irq(); _file = open(_filename);
_file = SD.open(_filename);
__enable_irq();
} }
#endif #endif
next->buffer_size = bytesRead; next->buffer_size = bytesRead;
@ -66,15 +70,24 @@ public:
} }
void close() { void close() {
if (_file) if (_file.available()) {
_file.close(); _file.close();
}
if (_filename) for (auto && x : _buffers){
delete [] x->buffer;
delete x;
}
_buffers.clear();
if (_filename != nullptr) {
delete [] _filename; delete [] _filename;
_filename = nullptr; _filename = nullptr;
} }
private: }
File _file;
protected:
TFile _file;
char *_filename; char *_filename;
std::vector<indexedbuffer*> _buffers; std::vector<indexedbuffer*> _buffers;
@ -90,4 +103,6 @@ private:
} }
#endif #endif

@ -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

@ -6,115 +6,44 @@
#include <cstdint> #include <cstdint>
#include "loop_type.h" #include "loop_type.h"
#include "interpolation.h" #include "interpolation.h"
#include "ResamplingReader.h"
class ResamplingArrayReader { namespace newdigate {
public:
ResamplingArrayReader() {
}
void begin(void);
bool playRaw(int16_t *array, uint32_t length, uint16_t numChannels);
bool playWav(int16_t *array, uint32_t length);
bool play();
void stop(void);
bool isPlaying(void) { return _playing; }
unsigned int read(void **buf, uint16_t nbyte);
bool readNextValue(int16_t *value, uint16_t channelNumber);
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) {
__disable_irq();
_loop_start = _bufferPosition;
_loop_finish = _bufferPosition + numSamples * _numChannels;
_loopType = loop_type::looptype_repeat;
__enable_irq();
}
void setLoopType(loop_type loopType) class ResamplingArrayReader : public ResamplingReader<int16_t, File> {
{ public:
_loopType = loopType; ResamplingArrayReader() :
} ResamplingReader() {
loop_type getLoopType(){
return _loopType;
} }
int available(void); virtual ~ResamplingArrayReader() {
void reset(void);
void close(void);
void setLoopStart(uint32_t loop_start) {
_loop_start = _header_offset + (loop_start * _numChannels);
} }
void setLoopFinish(uint32_t loop_finish) { int16_t getSourceBufferValue(long index) override {
// sample number, (NOT byte number) return _sourceBuffer[index];
_loop_finish = _header_offset + (loop_finish * _numChannels);
} }
void setInterpolationType(ResampleInterpolationType interpolationType) { int available(void) {
if (interpolationType != _interpolationType) { return _playing;
_interpolationType = interpolationType;
initializeInterpolationPoints();
}
} }
int16_t getNumChannels() { int16_t* createSourceBuffer() override {
return _numChannels; return _sourceBuffer;
} }
void setNumChannels(uint16_t numChannels) { void close(void) override {
if (numChannels != _numChannels) { if (_playing) {
_numChannels = numChannels; stop();
initializeInterpolationPoints(); deleteInterpolationPoints();
} }
} }
void setHeaderSizeInBytes(uint32_t headerSizeInBytes) { File open(char *filename) override {
_header_offset = headerSizeInBytes / 2; return File();
if (_bufferPosition < _header_offset) {
if (_playbackRate >= 0) {
_bufferPosition = _header_offset;
} else
_bufferPosition = _loop_finish - _numChannels;
}
} }
protected:
private:
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;
int16_t *_sourceBuffer = nullptr;
ResampleInterpolationType _interpolationType = ResampleInterpolationType::resampleinterpolation_none;
unsigned int _numInterpolationPoints = 0;
InterpolationData **_interpolationPoints = nullptr;
void initializeInterpolationPoints(void);
void deleteInterpolationPoints(void);
}; };
}
#endif //TEENSYAUDIOLIBRARY_RESAMPLINGARRAYREADER_H #endif //TEENSYAUDIOLIBRARY_RESAMPLINGARRAYREADER_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

@ -10,87 +10,56 @@
#include "spi_interrupt.h" #include "spi_interrupt.h"
#include "loop_type.h" #include "loop_type.h"
#include "interpolation.h" #include "interpolation.h"
#include "IndexableFile.h" #include "IndexableSDFile.h"
#include "ResamplingReader.h"
#define RESAMPLE_BUFFER_SAMPLE_SIZE 128 #define RESAMPLE_BUFFER_SAMPLE_SIZE 128
#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592 #define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592
class ResamplingSdReader { namespace newdigate {
public:
ResamplingSdReader() {
}
void begin(void);
bool playRaw(const char *filename, uint16_t numChannels);
bool playWav(const char *filename);
bool play();
void stop(void);
bool isPlaying(void) { return _playing; }
unsigned int read(void **buf, uint16_t nbyte);
bool readNextValue(int16_t *value, uint16_t channelNumber);
void setPlaybackRate(double f) {
_playbackRate = f;
if (f < 0.0 && _bufferPosition == 0) {
//_file.seek(_file_size);
_bufferPosition = _file_size / 2 - _numChannels;
}
}
double playbackRate() {
return _playbackRate;
}
void setLoopType(loop_type loopType) class ResamplingSdReader : public ResamplingReader< IndexableSDFile<128, 2>, File > {
public:
ResamplingSdReader() :
ResamplingReader()
{ {
_loopType = loopType;
} }
loop_type getLoopType(){ virtual ~ResamplingSdReader() {
return _loopType;
} }
int available(void); int16_t getSourceBufferValue(long index) override {
void reset(void); return (*_sourceBuffer)[index];
void close(void);
void setLoopStart(uint32_t loop_start) {
_loop_start = _header_offset + (loop_start * _numChannels);
} }
void setLoopFinish(uint32_t loop_finish) { int available(void)
// sample number, (NOT byte number) {
_loop_finish = _header_offset + (loop_finish * _numChannels); return _playing;
} }
void setInterpolationType(ResampleInterpolationType interpolationType) { File open(char *filename) override {
if (interpolationType != _interpolationType) { return SD.open(filename);
_interpolationType = interpolationType;
initializeInterpolationPoints();
}
} }
int16_t getNumChannels() { void close(void) override
return _numChannels; {
if (_playing)
stop();
if (_sourceBuffer != nullptr) {
_sourceBuffer->close();
delete _sourceBuffer;
_sourceBuffer = nullptr;
} }
if (_filename != nullptr) {
void setNumChannels(uint16_t numChannels) { delete [] _filename;
if (numChannels != _numChannels) { _filename = nullptr;
_numChannels = numChannels;
initializeInterpolationPoints();
} }
deleteInterpolationPoints();
} }
void setHeaderSize(uint32_t headerSizeInBytes) { IndexableSDFile<128, 2>* createSourceBuffer() override {
_header_offset = headerSizeInBytes / 2; return new IndexableSDFile<128, 2>(_filename);
if (_bufferPosition < _header_offset) {
if (_playbackRate >= 0) {
_bufferPosition = _header_offset;
}
}
} }
uint32_t positionMillis(void) { uint32_t positionMillis(void) {
@ -102,54 +71,11 @@ public:
uint32_t lengthMillis(void) { uint32_t lengthMillis(void) {
return ((uint64_t)_file_size * B2M) >> 32; return ((uint64_t)_file_size * B2M) >> 32;
} }
private:
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;
newdigate::IndexableFile<128, 2> *_sourceBuffer = nullptr;
ResampleInterpolationType _interpolationType = ResampleInterpolationType::resampleinterpolation_none;
unsigned int _numInterpolationPoints = 0;
InterpolationData **_interpolationPoints = nullptr;
static bool isUsingSPI;
void StartUsingSPI(){
if (!isUsingSPI) {
isUsingSPI = true;
#if defined(HAS_KINETIS_SDHC)
if (!(SIM_SCGC3 & SIM_SCGC3_SDHC)) AudioStartUsingSPI();
#else
AudioStartUsingSPI();
#endif
}
}
void StopUsingSPI() { protected:
if (isUsingSPI) {
isUsingSPI = false;
#if defined(HAS_KINETIS_SDHC)
if (!(SIM_SCGC3 & SIM_SCGC3_SDHC)) AudioStopUsingSPI();
#else
AudioStopUsingSPI();
#endif
}
}
bool play(const char *filename, bool isWave, uint16_t numChannelsIfRaw = 0);
void initializeInterpolationPoints(void);
void deleteInterpolationPoints(void);
}; };
}
#endif //TEENSYAUDIOLIBRARY_RESAMPLINGSDREADER_H #endif //TEENSYAUDIOLIBRARY_RESAMPLINGSDREADER_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

@ -8,7 +8,10 @@
#include "waveheaderparser.h" #include "waveheaderparser.h"
#include "ResamplingSdReader.h" #include "ResamplingSdReader.h"
#include "ResamplingArrayReader.h" #include "ResamplingArrayReader.h"
#include "ResamplingLfsReader.h"
#include "ResamplingSerialFlashReader.h"
#include "playsdresmp.h" #include "playsdresmp.h"
#include "playarrayresmp.h" #include "playarrayresmp.h"
#include "playlfsresmp.h"
#include "playserialflashresmp.h"
#endif //TEENSY_RESAMPLING_ARDUINO_SAMPLER_H #endif //TEENSY_RESAMPLING_ARDUINO_SAMPLER_H

@ -11,60 +11,19 @@
#include "ResamplingArrayReader.h" #include "ResamplingArrayReader.h"
#include "playresmp.h" #include "playresmp.h"
class AudioPlayArrayResmp : public AudioPlayResmp class AudioPlayArrayResmp : public AudioPlayResmp<newdigate::ResamplingArrayReader>
{ {
public: public:
AudioPlayArrayResmp(void) : AudioPlayArrayResmp(void) :
AudioPlayResmp(), AudioPlayResmp<newdigate::ResamplingArrayReader>()
arrayReader()
{ {
reader = new newdigate::ResamplingArrayReader();
begin(); begin();
} }
void begin(void); virtual ~AudioPlayArrayResmp() {
bool playRaw(int16_t *data, uint32_t numSamples, uint16_t numChannels); delete reader;
bool playRaw(const unsigned int *data, uint32_t numSamples, uint16_t numChannels);
bool playWav(int16_t *data, uint32_t fileSize);
bool playWav(const unsigned int *data, uint32_t fileSize);
void stop(void);
bool isPlaying(void) { return arrayReader.isPlaying(); }
uint32_t positionMillis(void);
uint32_t lengthMillis(void);
virtual void update(void);
void setPlaybackRate(float f) {
arrayReader.setPlaybackRate(f);
}
void setLoopType(loop_type t) {
arrayReader.setLoopType(t);
}
void startLoop(uint32_t samples) {
arrayReader.loop(samples);
} }
void setLoopStart(uint32_t loop_start) {
arrayReader.setLoopStart(loop_start);
}
void setLoopFinish(uint32_t loop_finish) {
arrayReader.setLoopFinish(loop_finish);
}
void enableInterpolation(bool enable) {
if (enable)
arrayReader.setInterpolationType(ResampleInterpolationType::resampleinterpolation_quadratic);
else
arrayReader.setInterpolationType(ResampleInterpolationType::resampleinterpolation_none);
}
private:
uint32_t file_size;
ResamplingArrayReader arrayReader;
}; };

@ -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

@ -5,20 +5,135 @@
#include "Audio.h" #include "Audio.h"
#include "loop_type.h" #include "loop_type.h"
template <class TResamplingReader>
class AudioPlayResmp : public AudioStream class AudioPlayResmp : public AudioStream
{ {
public: public:
AudioPlayResmp(void): AudioStream(0, NULL) {} AudioPlayResmp(): AudioStream(0, NULL), reader(nullptr)
virtual ~AudioPlayResmp() {} {
}
virtual void setPlaybackRate(float f) = 0;
virtual void setLoopType(loop_type t) = 0; virtual ~AudioPlayResmp() {
virtual void setLoopStart(uint32_t loop_start) = 0; }
virtual void setLoopFinish(uint32_t loop_finish) = 0;
virtual void begin() = 0; void begin(void)
virtual void enableInterpolation(bool enable) = 0; {
virtual bool isPlaying(void) = 0; reader->begin();
virtual void stop() = 0; }
bool playRaw(const char *filename, uint16_t numChannels)
{
stop();
return reader->play(filename, false, numChannels);
}
bool playWav(const char *filename)
{
stop();
return reader->play(filename, true, 0);
}
bool playRaw(int16_t *data, uint32_t numSamples, uint16_t numChannels)
{
stop();
return reader->playRaw(data, numSamples, numChannels);
}
bool playRaw(const unsigned int *data, uint32_t numSamples, uint16_t numChannels)
{
return playRaw((int16_t *) data, numSamples, numChannels);
}
bool playWav(int16_t *data, uint32_t fileSize)
{
stop();
return reader->playWav(data, fileSize);
}
bool playWav(const unsigned int *data, uint32_t fileSize) {
return playWav((int16_t *) data, fileSize);
}
void setPlaybackRate(float f) {
reader->setPlaybackRate(f);
}
void setLoopType(loop_type t) {
reader->setLoopType(t);
}
void setLoopStart(uint32_t loop_start) {
reader->setLoopStart(loop_start);
}
void setLoopFinish(uint32_t loop_finish) {
reader->setLoopFinish(loop_finish);
}
void enableInterpolation(bool enable) {
if (enable)
reader->setInterpolationType(ResampleInterpolationType::resampleinterpolation_quadratic);
else
reader->setInterpolationType(ResampleInterpolationType::resampleinterpolation_none);
}
bool isPlaying(void) {
return reader->isPlaying();
};
void stop() {
reader->stop();
}
void update()
{
int _numChannels = reader->getNumChannels();
if (_numChannels == -1)
return;
unsigned int i, n;
audio_block_t *blocks[_numChannels];
int16_t *data[_numChannels];
// only update if we're playing
if (!reader->isPlaying()) return;
// allocate the audio blocks to transmit
for (int i=0; i < _numChannels; i++) {
blocks[i] = allocate();
if (blocks[i] == nullptr) return;
data[i] = blocks[i]->data;
}
if (reader->available()) {
// we can read more data from the file...
n = reader->read((void**)data, AUDIO_BLOCK_SAMPLES);
for (int channel=0; channel < _numChannels; channel++) {
memset( &blocks[channel]->data[n], 0, (AUDIO_BLOCK_SAMPLES - n) * 2);
transmit(blocks[channel], channel);
}
if(_numChannels == 1) {
transmit(blocks[0], 1);
}
} else {
reader->close();
}
for (int channel=0; channel < _numChannels; channel++) {
release(blocks[channel]);
}
}
uint32_t positionMillis()
{
return reader->positionMillis();
}
uint32_t lengthMillis()
{
return reader->lengthMillis();
}
protected:
TResamplingReader *reader;
}; };
#endif // TEENSY_RESAMPLING_SDREADER_PLAYRESMP_H #endif // TEENSY_RESAMPLING_SDREADER_PLAYRESMP_H

@ -13,52 +13,19 @@
#include "ResamplingSdReader.h" #include "ResamplingSdReader.h"
#include "playresmp.h" #include "playresmp.h"
class AudioPlaySdResmp : public AudioPlayResmp class AudioPlaySdResmp : public AudioPlayResmp<newdigate::ResamplingSdReader>
{ {
public: public:
AudioPlaySdResmp(void) : AudioPlaySdResmp(void) :
AudioPlayResmp(), AudioPlayResmp<newdigate::ResamplingSdReader>()
sdReader()
{ {
reader = new newdigate::ResamplingSdReader();
begin(); begin();
} }
void begin(void); virtual ~AudioPlaySdResmp() {
bool playRaw(const char *filename, uint16_t numChannels); delete reader;
bool playWav(const char *filename);
void stop(void);
bool isPlaying(void) { return sdReader.isPlaying(); }
uint32_t positionMillis(void);
uint32_t lengthMillis(void);
virtual void update(void);
void setPlaybackRate(float f) {
sdReader.setPlaybackRate(f);
}
void setLoopType(loop_type t) {
sdReader.setLoopType(t);
} }
void setLoopStart(uint32_t loop_start) {
sdReader.setLoopStart(loop_start);
}
void setLoopFinish(uint32_t loop_finish) {
sdReader.setLoopFinish(loop_finish);
}
void enableInterpolation(bool enable) {
if (enable)
sdReader.setInterpolationType(ResampleInterpolationType::resampleinterpolation_quadratic);
else
sdReader.setInterpolationType(ResampleInterpolationType::resampleinterpolation_none);
}
private:
uint32_t file_size;
ResamplingSdReader sdReader;
}; };

@ -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

@ -14,33 +14,31 @@ using namespace std;
// from https://gist.github.com/Jon-Schneider/8b7c53d27a7a13346a643dac9c19d34f // from https://gist.github.com/Jon-Schneider/8b7c53d27a7a13346a643dac9c19d34f
struct wav_header { struct wav_header {
// RIFF Header // RIFF Header
char riff_header[4]; // 00 - 03 - Contains "RIFF" char riff_header[4] = {0,0,0,0}; // 00 - 03 - Contains "RIFF"
int header_chunk_size; // 04 - 07 - Size of the wav portion of the file, which follows the first 8 bytes. File size - 8 int header_chunk_size = 0; // 04 - 07 - Size of the wav portion of the file, which follows the first 8 bytes. File size - 8
char wave_header[4]; // 08 - 11 - Contains "WAVE" char wave_header[4] = {0,0,0,0}; // 08 - 11 - Contains "WAVE"
// Format Header // Format Header
char fmt_header[4]; // 12 - 15 - Contains "fmt " (includes trailing space) char fmt_header[4] = {0,0,0,0}; // 12 - 15 - Contains "fmt " (includes trailing space)
int fmt_chunk_size; // 16 - 19 - Should be 16 for PCM int fmt_chunk_size = 0; // 16 - 19 - Should be 16 for PCM
short audio_format; // 20 - 21 - Should be 1 for PCM. 3 for IEEE Float short audio_format = 0; // 20 - 21 - Should be 1 for PCM. 3 for IEEE Float
short num_channels; // 22 - 23 short num_channels = 0; // 22 - 23
int sample_rate; // 24 - 27 int sample_rate = 0; // 24 - 27
int byte_rate; // 28 - 31 int byte_rate = 0; // 28 - 31
short sample_alignment; // 32 - 33 short sample_alignment = 0; // 32 - 33
short bit_depth; // 34 - 35 short bit_depth = 0; // 34 - 35
}; };
struct wav_data_header { struct wav_data_header {
// Data // Data
char data_header[4]; // 36 - 39 char data_header[4] = {0,0,0,0}; // 36 - 39
unsigned int data_bytes;// 40 - 43 unsigned int data_bytes = 0;// 40 - 43
}; };
class WaveHeaderParser { class WaveHeaderParser {
public: public:
bool readWaveHeader(const char *filename, wav_header &header, wav_data_header &wav_data_header) { bool readWaveHeader(const char *filename, wav_header &header, wav_data_header &wav_data_header) {
__disable_irq();
File wavFile = SD.open(filename); File wavFile = SD.open(filename);
__enable_irq();
if (!wavFile) { if (!wavFile) {
Serial.printf("Not able to open wave file... %s\n", filename); Serial.printf("Not able to open wave file... %s\n", filename);
return false; return false;
@ -77,9 +75,7 @@ public:
bool readWaveHeader(const char *filename, wav_header &header, File &wavFile) { bool readWaveHeader(const char *filename, wav_header &header, File &wavFile) {
char buffer[36]; char buffer[36];
__disable_irq();
int bytesRead = wavFile.read(buffer, 36); int bytesRead = wavFile.read(buffer, 36);
__enable_irq();
if (bytesRead != 36) { if (bytesRead != 36) {
Serial.printf("expected 36 bytes (was %d)\n", bytesRead); Serial.printf("expected 36 bytes (was %d)\n", bytesRead);
return false; return false;

@ -10,14 +10,14 @@
struct ResamplingArrayFixture { struct ResamplingArrayFixture {
ResamplingArrayFixture() { ResamplingArrayFixture() {
resamplingArrayReader = new ResamplingArrayReader(); resamplingArrayReader = new newdigate::ResamplingArrayReader();
} }
~ResamplingArrayFixture() { ~ResamplingArrayFixture() {
delete resamplingArrayReader; delete resamplingArrayReader;
} }
ResamplingArrayReader * resamplingArrayReader; newdigate::ResamplingArrayReader * resamplingArrayReader;
}; };
#endif //TEENSY_RESAMPLING_SDREADER_RESAMPLINGARRAYFIXTURE_H #endif //TEENSY_RESAMPLING_SDREADER_RESAMPLINGARRAYFIXTURE_H

@ -10,14 +10,14 @@
struct ResamplingArrayWavFixture { struct ResamplingArrayWavFixture {
ResamplingArrayWavFixture() { ResamplingArrayWavFixture() {
resamplingArrayReader = new ResamplingArrayReader(); resamplingArrayReader = new newdigate::ResamplingArrayReader();
} }
~ResamplingArrayWavFixture() { ~ResamplingArrayWavFixture() {
delete resamplingArrayReader; delete resamplingArrayReader;
} }
ResamplingArrayReader * resamplingArrayReader; newdigate::ResamplingArrayReader * resamplingArrayReader;
}; };
#endif //TEENSY_RESAMPLING_SDREADER_RESAMPLINGARRAYWAVFIXTURE_H #endif //TEENSY_RESAMPLING_SDREADER_RESAMPLINGARRAYWAVFIXTURE_H

@ -2,7 +2,7 @@
#include "IndexedFileFixture.h" #include "IndexedFileFixture.h"
#include <SD.h> #include <SD.h>
#include "IndexableFile.h" #include "IndexableSDFile.h"
BOOST_AUTO_TEST_SUITE(test_indexablefile) BOOST_AUTO_TEST_SUITE(test_indexablefile)
@ -15,7 +15,7 @@ BOOST_AUTO_TEST_SUITE(test_indexablefile)
} }
SD.setSDCardFileData((char*)file_contents, sample_size * 2); SD.setSDCardFileData((char*)file_contents, sample_size * 2);
newdigate::IndexableFile<16, 2> indexable("blah.h"); // use max 2 buffers, with 16 elements each.... newdigate::IndexableSDFile<16, 2> indexable("blah.h"); // use max 2 buffers, with 16 elements each....
/* /*
for (int i=0; i<sample_size; i++) { for (int i=0; i<sample_size; i++) {

@ -10,14 +10,14 @@
struct ResamplingReaderFixture { struct ResamplingReaderFixture {
ResamplingReaderFixture() { ResamplingReaderFixture() {
resamplingSdReader = new ResamplingSdReader(); resamplingSdReader = new newdigate::ResamplingSdReader();
} }
~ResamplingReaderFixture() { ~ResamplingReaderFixture() {
delete resamplingSdReader; delete resamplingSdReader;
} }
ResamplingSdReader * resamplingSdReader; newdigate::ResamplingSdReader * resamplingSdReader;
}; };
#endif //TEENSY_RESAMPLING_SDREADER_RESAMPLINGREADERFIXTURE_H #endif //TEENSY_RESAMPLING_SDREADER_RESAMPLINGREADERFIXTURE_H

@ -18,7 +18,7 @@ BOOST_AUTO_TEST_SUITE(test_raw_mono_noloop_forward_double_rate_playback)
SD.setSDCardFileData((char *) dataSource, size_of_datasource * 2); SD.setSDCardFileData((char *) dataSource, size_of_datasource * 2);
} }
void testReadForwardAtDoublePlaybackRate(const uint32_t size_of_datasource, ResamplingSdReader *resamplingSdReader) { void testReadForwardAtDoublePlaybackRate(const uint32_t size_of_datasource, newdigate::ResamplingSdReader *resamplingSdReader) {
printf("test_raw_mono_noloop_forward_double_rate_playback::testReadForwardAtDoublePlaybackRate(rate:%.2f\tsamples:%d)\n", playBackRate, size_of_datasource); printf("test_raw_mono_noloop_forward_double_rate_playback::testReadForwardAtDoublePlaybackRate(rate:%.2f\tsamples:%d)\n", playBackRate, size_of_datasource);
int16_t dataSource[size_of_datasource]; int16_t dataSource[size_of_datasource];

@ -24,7 +24,7 @@ BOOST_AUTO_TEST_SUITE(test_raw_mono_noloop_forward_playback)
resamplingSdReader->begin(); resamplingSdReader->begin();
resamplingSdReader->setPlaybackRate(1.0); resamplingSdReader->setPlaybackRate(1.0);
resamplingSdReader->playRaw("test2.bin", 1); resamplingSdReader->play("test2.bin", false, 1);
resamplingSdReader->setLoopType(looptype_none); resamplingSdReader->setLoopType(looptype_none);
resamplingSdReader->setInterpolationType(ResampleInterpolationType::resampleinterpolation_quadratic); resamplingSdReader->setInterpolationType(ResampleInterpolationType::resampleinterpolation_quadratic);
int16_t actual[1024]; int16_t actual[1024];

Loading…
Cancel
Save