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) |
||||
add_subdirectory(array) |
||||
add_subdirectory(LittleFS) |
||||
add_subdirectory(sampleloader) |
||||
add_subdirectory(sd_play_all) |
||||
add_subdirectory(sd_raw) |
||||
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