TeensyVariablePlayback and Synth_Dexed libraries with fixes added.

Small fix for note refresh initialization of Dexed instances.
dev
Holger Wirtz 2 years ago
parent eaf7736830
commit c5b3a4df5a
  1. 2
      MicroDexed.ino
  2. 3
      UI.hpp
  3. 88
      third-party/Synth_Dexed/.gitignore
  4. 68
      third-party/Synth_Dexed/examples/SimplePlayAndEngineChange/SimplePlayAndEngineChange.ino
  5. 18
      third-party/Synth_Dexed/keywords.txt
  6. 352
      third-party/Synth_Dexed/src/EngineMkI.cpp
  7. 44
      third-party/Synth_Dexed/src/EngineMkI.h
  8. 221
      third-party/Synth_Dexed/src/EngineOpl.cpp
  9. 37
      third-party/Synth_Dexed/src/EngineOpl.h
  10. 1
      third-party/Synth_Dexed/src/aligned_buf.h
  11. 47
      third-party/Synth_Dexed/src/dexed.cpp
  12. 27
      third-party/Synth_Dexed/src/dexed.h
  13. 22
      third-party/TeensyVariablePlayback/README.md
  14. 4
      third-party/TeensyVariablePlayback/library.json
  15. 2
      third-party/TeensyVariablePlayback/library.properties
  16. 11
      third-party/TeensyVariablePlayback/src/ResamplingLfsReader.h
  17. 227
      third-party/TeensyVariablePlayback/src/ResamplingReader.h
  18. 17
      third-party/TeensyVariablePlayback/src/ResamplingSdReader.h
  19. 11
      third-party/TeensyVariablePlayback/src/ResamplingSerialFlashReader.h
  20. 5
      third-party/TeensyVariablePlayback/src/loop_type.h
  21. 14
      third-party/TeensyVariablePlayback/src/playresmp.h
  22. 14
      third-party/TeensyVariablePlayback/test/audio/output_test.h

@ -2106,6 +2106,8 @@ void set_voiceconfig_params(uint8_t instance_id) {
MicroDexed[instance_id]->setOPAll(configuration.dexed[instance_id].op_enabled); MicroDexed[instance_id]->setOPAll(configuration.dexed[instance_id].op_enabled);
MicroDexed[instance_id]->doRefreshVoice(); MicroDexed[instance_id]->doRefreshVoice();
MicroDexed[instance_id]->setMonoMode(configuration.dexed[instance_id].monopoly); MicroDexed[instance_id]->setMonoMode(configuration.dexed[instance_id].monopoly);
MicroDexed[instance_id]->setNoteRefreshMode(configuration.dexed[instance_id].note_refresh);
MicroDexed[instance_id]->setEngineType(MSFA);
// Dexed output level // Dexed output level
MicroDexed[instance_id]->setGain(midi_volume_transform(map(configuration.dexed[instance_id].sound_intensity, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX, 0, 127))); MicroDexed[instance_id]->setGain(midi_volume_transform(map(configuration.dexed[instance_id].sound_intensity, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX, 0, 127)));

@ -2908,6 +2908,9 @@ void UI_func_note_refresh(uint8_t param) {
} }
MicroDexed[selected_instance_id]->setNoteRefreshMode(configuration.dexed[selected_instance_id].note_refresh); MicroDexed[selected_instance_id]->setNoteRefreshMode(configuration.dexed[selected_instance_id].note_refresh);
#ifdef DEBUG
Serial.printf(PSTR("Note refresh: %d\n"), configuration.dexed[selected_instance_id].note_refresh);
#endif
display.setCursor(0, 1); display.setCursor(0, 1);
switch (configuration.dexed[selected_instance_id].note_refresh) { switch (configuration.dexed[selected_instance_id].note_refresh) {

@ -1,88 +0,0 @@
# ---> C++
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# ---> C
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf

@ -0,0 +1,68 @@
#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;
static uint8_t engine_change;
if (count % 2 == 0) {
dexed.loadInitVoice();
} else {
dexed.loadVoiceParameters(fmpiano_sysex);
dexed.setTranspose(36);
}
dexed.setEngineType(engine_change++ % 3);
Serial.printf("Engine changed to: %d at address 0x%8d\n", dexed.getEngineType(), dexed.getEngineAddress());
Serial.printf("Key-Down\n");
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.printf("Key-Up\n");
dexed.keyup(48);
dexed.keyup(52);
dexed.keyup(55);
dexed.keyup(60);
delay(2000);
count++;
}

@ -31,10 +31,16 @@ getVoiceDataElement KEYWORD2
loadInitVoice KEYWORD2 loadInitVoice KEYWORD2
loadVoiceParameters KEYWORD2 loadVoiceParameters KEYWORD2
getNumNotesPlaying KEYWORD2 getNumNotesPlaying KEYWORD2
uint32_t getXRun KEYWORD2 getXRun KEYWORD2
uint16_t getRenderTimeMax KEYWORD2 getRenderTimeMax KEYWORD2
resetRenderTimeMax KEYWORD2 resetRenderTimeMax KEYWORD2
ControllersRefresh KEYWORD2 ControllersRefresh KEYWORD2
setVelocityScale KEYWORD2
getVelocityScale KEYWORD2
setVelocityScale KEYWORD2
setEngineType KEYWORD2
getEngineType KEYWORD2
checkSystemExclusive KEYWORD2
# Sound methods KEYWORD2 # Sound methods KEYWORD2
keyup KEYWORD2 keyup KEYWORD2
@ -45,7 +51,7 @@ panic KEYWORD2
notesOff KEYWORD2 notesOff KEYWORD2
resetControllers KEYWORD2 resetControllers KEYWORD2
setMasterTune KEYWORD2 setMasterTune KEYWORD2
int8_t getMasterTune KEYWORD2 getMasterTune KEYWORD2
setPortamentoMode KEYWORD2 setPortamentoMode KEYWORD2
setPBController KEYWORD2 setPBController KEYWORD2
setMWController KEYWORD2 setMWController KEYWORD2
@ -83,11 +89,11 @@ getAftertouchRange KEYWORD2
setAftertouchTarget KEYWORD2 setAftertouchTarget KEYWORD2
getAftertouchTarget KEYWORD2 getAftertouchTarget KEYWORD2
setFilterCutoff KEYWORD2 setFilterCutoff KEYWORD2
float getFilterCutoff KEYWORD2 getFilterCutoff KEYWORD2
setFilterResonance KEYWORD2 setFilterResonance KEYWORD2
float getFilterResonance KEYWORD2 getFilterResonance KEYWORD2
setGain KEYWORD2 setGain KEYWORD2
float getGain KEYWORD2 getGain KEYWORD2
# Voice configuration methods KEYWORD2 # Voice configuration methods KEYWORD2
setOPRateAll KEYWORD2 setOPRateAll KEYWORD2

@ -0,0 +1,352 @@
/*
* Copyright (C) 2015-2017 Pascal Gauthier.
*
* 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
*
* The code is based on ppplay https://github.com/stohrendorf/ppplay and opl3
* math documentation :
* https://github.com/gtaylormb/opl3_fpga/blob/master/docs/opl3math/opl3math.pdf
*
*/
#include "EngineMkI.h"
#define _USE_MATH_DEFINES
#include <cmath>
#include <cstdlib>
#include "sin.h"
#include "exp2.h"
#ifdef DEBUG
#include "time.h"
//#define MKIDEBUG
#endif
const int32_t __attribute__ ((aligned(16))) zeros[_N_] = {0};
static const uint16_t NEGATIVE_BIT = 0x8000;
static const uint16_t ENV_BITDEPTH = 14;
static const uint16_t SINLOG_BITDEPTH = 10;
static const uint16_t SINLOG_TABLESIZE = 1<<SINLOG_BITDEPTH;
static uint16_t sinLogTable[SINLOG_TABLESIZE];
static const uint16_t SINEXP_BITDEPTH = 10;
static const uint16_t SINEXP_TABLESIZE = 1<<SINEXP_BITDEPTH;
static uint16_t sinExpTable[SINEXP_TABLESIZE];
const uint16_t ENV_MAX = 1<<ENV_BITDEPTH;
static inline uint16_t sinLog(uint16_t phi) {
const uint16_t SINLOG_TABLEFILTER = SINLOG_TABLESIZE-1;
const uint16_t index = (phi & SINLOG_TABLEFILTER);
switch( ( phi & (SINLOG_TABLESIZE * 3) ) ) {
case 0:
return sinLogTable[index];
case SINLOG_TABLESIZE:
return sinLogTable[index ^ SINLOG_TABLEFILTER];
case SINLOG_TABLESIZE * 2 :
return sinLogTable[index] | NEGATIVE_BIT;
default:
return sinLogTable[index ^ SINLOG_TABLEFILTER] | NEGATIVE_BIT;
}
}
EngineMkI::EngineMkI() {
float bitReso = SINLOG_TABLESIZE;
for(int i=0;i<SINLOG_TABLESIZE;i++) {
float x1 = sin(((0.5+i)/bitReso) * M_PI/2.0);
sinLogTable[i] = round(-1024 * log2(x1));
}
bitReso = SINEXP_TABLESIZE;
for(int i=0;i<SINEXP_TABLESIZE;i++) {
float x1 = (pow(2, float(i)/bitReso)-1) * 4096;
sinExpTable[i] = round(x1);
}
#ifdef MKIDEBUG
char buffer[4096];
int pos = 0;
TRACE("****************************************");
for(int i=0;i<SINLOG_TABLESIZE;i++) {
pos += sprintf(buffer+pos, "%d ", sinLogTable[i]);
if ( pos > 90 ) {
TRACE("SINLOGTABLE: %s" ,buffer);
buffer[0] = 0;
pos = 0;
}
}
TRACE("SINLOGTABLE: %s", buffer);
buffer[0] = 0;
pos = 0;
TRACE("----------------------------------------");
for(int i=0;i<SINEXP_TABLESIZE;i++) {
pos += sprintf(buffer+pos, "%d ", sinExpTable[i]);
if ( pos > 90 ) {
TRACE("SINEXTTABLE: %s" ,buffer);
buffer[0] = 0;
pos = 0;
}
}
TRACE("SINEXTTABLE: %s", buffer);
TRACE("****************************************");
#endif
}
inline int32_t mkiSin(int32_t phase, uint16_t env) {
uint16_t expVal = sinLog(phase >> (22 - SINLOG_BITDEPTH)) + (env);
//int16_t expValShow = expVal;
const bool isSigned = expVal & NEGATIVE_BIT;
expVal &= ~NEGATIVE_BIT;
const uint16_t SINEXP_FILTER = 0x3FF;
uint16_t result = 4096 + sinExpTable[( expVal & SINEXP_FILTER ) ^ SINEXP_FILTER];
//uint16_t resultB4 = result;
result >>= ( expVal >> 10 ); // exp
#ifdef MKIDEBUG
if ( ( time(NULL) % 5 ) == 0 ) {
if ( expValShow < 0 ) {
expValShow = (expValShow + 0x7FFF) * -1;
}
//TRACE(",%d,%d,%d,%d,%d,%d", phase >> (22 - SINLOG_BITDEPTH), env, expValShow, ( expVal & SINEXP_FILTER ) ^ SINEXP_FILTER, resultB4, result);
}
#endif
if( isSigned )
return (-result - 1) << 13;
else
return result << 13;
}
void EngineMkI::compute(int32_t *output, const int32_t *input,
int32_t phase0, int32_t freq,
int32_t gain1, int32_t gain2, bool add) {
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
int32_t gain = gain1;
int32_t phase = phase0;
const int32_t *adder = add ? output : zeros;
for (int i = 0; i < _N_; i++) {
gain += dgain;
int32_t y = mkiSin((phase+input[i]), gain);
output[i] = y + adder[i];
phase += freq;
}
}
void EngineMkI::compute_pure(int32_t *output, int32_t phase0, int32_t freq,
int32_t gain1, int32_t gain2, bool add) {
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
int32_t gain = gain1;
int32_t phase = phase0;
const int32_t *adder = add ? output : zeros;
for (int i = 0; i < _N_; i++) {
gain += dgain;
int32_t y = mkiSin(phase , gain);
output[i] = y + adder[i];
phase += freq;
}
}
void EngineMkI::compute_fb(int32_t *output, int32_t phase0, int32_t freq,
int32_t gain1, int32_t gain2,
int32_t *fb_buf, int fb_shift, bool add) {
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
int32_t gain = gain1;
int32_t phase = phase0;
const int32_t *adder = add ? output : zeros;
int32_t y0 = fb_buf[0];
int32_t y = fb_buf[1];
for (int i = 0; i < _N_; i++) {
gain += dgain;
int32_t scaled_fb = (y0 + y) >> (fb_shift + 1);
y0 = y;
y = mkiSin((phase+scaled_fb), gain);
output[i] = y + adder[i];
phase += freq;
}
fb_buf[0] = y0;
fb_buf[1] = y;
}
// exclusively used for ALGO 6 with feedback
void EngineMkI::compute_fb2(int32_t *output, FmOpParams *parms, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift) {
int32_t dgain[2];
int32_t gain[2];
int32_t phase[2];
int32_t y0 = fb_buf[0];
int32_t y = fb_buf[1];
phase[0] = parms[0].phase;
phase[1] = parms[1].phase;
parms[1].gain_out = (ENV_MAX-(parms[1].level_in >> (28-ENV_BITDEPTH)));
gain[0] = gain01;
gain[1] = parms[1].gain_out == 0 ? (ENV_MAX-1) : parms[1].gain_out;
dgain[0] = (gain02 - gain01 + (_N_ >> 1)) >> LG_N;
dgain[1] = (parms[1].gain_out - (parms[1].gain_out == 0 ? (ENV_MAX-1) : parms[1].gain_out));
for (int i = 0; i < _N_; i++) {
int32_t scaled_fb = (y0 + y) >> (fb_shift + 1);
// op 0
gain[0] += dgain[0];
y0 = y;
y = mkiSin(phase[0]+scaled_fb, gain[0]);
phase[0] += parms[0].freq;
// op 1
gain[1] += dgain[1];
y = mkiSin(phase[1]+y, gain[1]);
phase[1] += parms[1].freq;
output[i] = y;
}
fb_buf[0] = y0;
fb_buf[1] = y;
}
// exclusively used for ALGO 4 with feedback
void EngineMkI::compute_fb3(int32_t *output, FmOpParams *parms, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift) {
int32_t dgain[3];
int32_t gain[3];
int32_t phase[3];
int32_t y0 = fb_buf[0];
int32_t y = fb_buf[1];
phase[0] = parms[0].phase;
phase[1] = parms[1].phase;
phase[2] = parms[2].phase;
parms[1].gain_out = (ENV_MAX-(parms[1].level_in >> (28-ENV_BITDEPTH)));
parms[2].gain_out = (ENV_MAX-(parms[2].level_in >> (28-ENV_BITDEPTH)));
gain[0] = gain01;
gain[1] = parms[1].gain_out == 0 ? (ENV_MAX-1) : parms[1].gain_out;
gain[2] = parms[2].gain_out == 0 ? (ENV_MAX-1) : parms[2].gain_out;
dgain[0] = (gain02 - gain01 + (_N_ >> 1)) >> LG_N;
dgain[1] = (parms[1].gain_out - (parms[1].gain_out == 0 ? (ENV_MAX-1) : parms[1].gain_out));
dgain[2] = (parms[2].gain_out - (parms[2].gain_out == 0 ? (ENV_MAX-1) : parms[2].gain_out));
for (int i = 0; i < _N_; i++) {
int32_t scaled_fb = (y0 + y) >> (fb_shift + 1);
// op 0
gain[0] += dgain[0];
y0 = y;
y = mkiSin(phase[0]+scaled_fb, gain[0]);
phase[0] += parms[0].freq;
// op 1
gain[1] += dgain[1];
y = mkiSin(phase[1]+y, gain[1]);
phase[1] += parms[1].freq;
// op 2
gain[2] += dgain[2];
y = mkiSin(phase[2]+y, gain[2]);
phase[2] += parms[2].freq;
output[i] = y;
}
fb_buf[0] = y0;
fb_buf[1] = y;
}
void EngineMkI::render(int32_t *output, FmOpParams *params, int32_t algorithm, int32_t *fb_buf, int32_t feedback_shift) {
const int kLevelThresh = ENV_MAX-100;
FmAlgorithm alg = algorithms[algorithm];
bool has_contents[3] = { true, false, false };
bool fb_on = feedback_shift < 16;
switch(algorithm) {
case 3 : case 5 :
if ( fb_on )
alg.ops[0] = 0xc4;
}
for (int op = 0; op < 6; op++) {
int flags = alg.ops[op];
bool add = (flags & OUT_BUS_ADD) != 0;
FmOpParams &param = params[op];
int inbus = (flags >> 4) & 3;
int outbus = flags & 3;
int32_t *outptr = (outbus == 0) ? output : buf_[outbus - 1].get();
int32_t gain1 = param.gain_out == 0 ? (ENV_MAX-1) : param.gain_out;
int32_t gain2 = ENV_MAX-(param.level_in >> (28-ENV_BITDEPTH));
param.gain_out = gain2;
if (gain1 <= kLevelThresh || gain2 <= kLevelThresh) {
if (!has_contents[outbus]) {
add = false;
}
if (inbus == 0 || !has_contents[inbus]) {
// PG: this is my 'dirty' implementation of FB for 2 and 3 operators...
if ((flags & 0xc0) == 0xc0 && fb_on) {
switch ( algorithm ) {
// three operator feedback, process exception for ALGO 4
case 3 :
compute_fb3(outptr, params, gain1, gain2, fb_buf, min((feedback_shift+2), (int32_t)16));
params[1].phase += params[1].freq << LG_N; // hack, we already processed op-5 - op-4
params[2].phase += params[2].freq << LG_N; // yuk yuk
op += 2; // ignore the 2 other operators
break;
// two operator feedback, process exception for ALGO 6
case 5 :
compute_fb2(outptr, params, gain1, gain2, fb_buf, min((feedback_shift+2), (int32_t)16));
params[1].phase += params[1].freq << LG_N; // yuk, hack, we already processed op-5
op++; // ignore next operator;
break;
case 31 :
// one operator feedback, process exception for ALGO 32
compute_fb(outptr, param.phase, param.freq, gain1, gain2, fb_buf, min((feedback_shift+2), (int32_t)16), add);
break;
default:
// one operator feedback, normal process
compute_fb(outptr, param.phase, param.freq, gain1, gain2, fb_buf, feedback_shift, add);
break;
}
} else {
compute_pure(outptr, param.phase, param.freq, gain1, gain2, add);
}
} else {
compute(outptr, buf_[inbus - 1].get(), param.phase, param.freq, gain1, gain2, add);
}
has_contents[outbus] = true;
} else if (!add) {
has_contents[outbus] = false;
}
param.phase += param.freq << LG_N;
}
}

@ -0,0 +1,44 @@
/*
* Copyright 2014 Pascal Gauthier.
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ENGINEMKI_H_INCLUDED
#define ENGINEMKI_H_INCLUDED
#include "aligned_buf.h"
#include "fm_op_kernel.h"
#include "controllers.h"
#include "fm_core.h"
class EngineMkI : public FmCore {
public:
EngineMkI();
void render(int32_t *output, FmOpParams *params, int32_t algorithm, int32_t *fb_buf, int32_t feedback_shift);
void compute(int32_t *output, const int32_t *input, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, bool add);
void compute_pure(int32_t *output, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, bool add);
void compute_fb(int32_t *output, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, int32_t *fb_buf, int fb_gain, bool add);
void compute_fb2(int32_t *output, FmOpParams *params, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift);
void compute_fb3(int32_t *output, FmOpParams *params, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift);
};
#endif // ENGINEMKI_H_INCLUDED

@ -0,0 +1,221 @@
/*
* Copyright (C) 2014 Pascal Gauthier.
* Copyright (C) 2012 Steffen Ohrendorf <steffen.ohrendorf@gmx.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
*
* Original Java Code: Copyright (C) 2008 Robson Cozendey <robson@cozendey.com>
*
* Some code based on forum posts in: http://forums.submarine.org.uk/phpBB/viewforum.php?f=9,
* Copyright (C) 2010-2013 by carbon14 and opl3
*
*/
#include "EngineOpl.h"
const int32_t __attribute__ ((aligned(16))) zeros[_N_] = {0};
uint16_t SignBit = 0x8000;
uint16_t sinLogTable[256] = {
2137, 1731, 1543, 1419, 1326, 1252, 1190, 1137, 1091, 1050, 1013, 979, 949, 920, 894, 869,
846, 825, 804, 785, 767, 749, 732, 717, 701, 687, 672, 659, 646, 633, 621, 609,
598, 587, 576, 566, 556, 546, 536, 527, 518, 509, 501, 492, 484, 476, 468, 461,
453, 446, 439, 432, 425, 418, 411, 405, 399, 392, 386, 380, 375, 369, 363, 358,
352, 347, 341, 336, 331, 326, 321, 316, 311, 307, 302, 297, 293, 289, 284, 280,
276, 271, 267, 263, 259, 255, 251, 248, 244, 240, 236, 233, 229, 226, 222, 219,
215, 212, 209, 205, 202, 199, 196, 193, 190, 187, 184, 181, 178, 175, 172, 169,
167, 164, 161, 159, 156, 153, 151, 148, 146, 143, 141, 138, 136, 134, 131, 129,
127, 125, 122, 120, 118, 116, 114, 112, 110, 108, 106, 104, 102, 100, 98, 96,
94, 92, 91, 89, 87, 85, 83, 82, 80, 78, 77, 75, 74, 72, 70, 69,
67, 66, 64, 63, 62, 60, 59, 57, 56, 55, 53, 52, 51, 49, 48, 47,
46, 45, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30,
29, 28, 27, 26, 25, 24, 23, 23, 22, 21, 20, 20, 19, 18, 17, 17,
16, 15, 15, 14, 13, 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7,
7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2,
2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0
};
uint16_t sinExpTable[256] = {
0, 3, 6, 8, 11, 14, 17, 20, 22, 25, 28, 31, 34, 37, 40, 42,
45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90,
93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 123, 126, 130, 133, 136, 139,
142, 145, 148, 152, 155, 158, 161, 164, 168, 171, 174, 177, 181, 184, 187, 190,
194, 197, 200, 204, 207, 210, 214, 217, 220, 224, 227, 231, 234, 237, 241, 244,
248, 251, 255, 258, 262, 265, 268, 272, 276, 279, 283, 286, 290, 293, 297, 300,
304, 308, 311, 315, 318, 322, 326, 329, 333, 337, 340, 344, 348, 352, 355, 359,
363, 367, 370, 374, 378, 382, 385, 389, 393, 397, 401, 405, 409, 412, 416, 420,
424, 428, 432, 436, 440, 444, 448, 452, 456, 460, 464, 468, 472, 476, 480, 484,
488, 492, 496, 501, 505, 509, 513, 517, 521, 526, 530, 534, 538, 542, 547, 551,
555, 560, 564, 568, 572, 577, 581, 585, 590, 594, 599, 603, 607, 612, 616, 621,
625, 630, 634, 639, 643, 648, 652, 657, 661, 666, 670, 675, 680, 684, 689, 693,
698, 703, 708, 712, 717, 722, 726, 731, 736, 741, 745, 750, 755, 760, 765, 770,
774, 779, 784, 789, 794, 799, 804, 809, 814, 819, 824, 829, 834, 839, 844, 849,
854, 859, 864, 869, 874, 880, 885, 890, 895, 900, 906, 911, 916, 921, 927, 932,
937, 942, 948, 953, 959, 964, 969, 975, 980, 986, 991, 996, 1002, 1007, 1013, 1018
};
inline uint16_t sinLog(uint16_t phi) {
const uint8_t index = (phi & 0xff);
switch( ( phi & 0x0300 ) ) {
case 0x0000:
// rising quarter wave Shape A
return sinLogTable[index];
case 0x0100:
// falling quarter wave Shape B
return sinLogTable[index ^ 0xFF];
case 0x0200:
// rising quarter wave -ve Shape C
return sinLogTable[index] | SignBit;
default:
// falling quarter wave -ve Shape D
return sinLogTable[index ^ 0xFF] | SignBit;
}
}
// 16 env units are ~3dB and halve the output
/**
* @brief OPL Sine Wave calculation
* @param[in] phase Wave phase (0..1023)
* @param[in] env Envelope value (0..511)
* @warning @a env will not be checked for correct values.
*/
inline int16_t oplSin( uint16_t phase, uint16_t env ) {
uint16_t expVal = sinLog(phase) + (env << 3);
const bool isSigned = expVal & SignBit;
expVal &= ~SignBit;
// expVal: 0..2137+511*8 = 0..6225
// result: 0..1018+1024
uint32_t result = 0x0400 + sinExpTable[( expVal & 0xff ) ^ 0xFF];
result <<= 1;
result >>= ( expVal >> 8 ); // exp
if( isSigned ) {
// -1 for one's complement
return -result - 1;
} else {
return result;
}
}
void EngineOpl::compute(int32_t *output, const int32_t *input, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, bool add) {
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
int32_t gain = gain1;
int32_t phase = phase0;
const int32_t *adder = add ? output : zeros;
for (int i = 0; i < _N_; i++) {
gain += dgain;
int32_t y = oplSin((phase+input[i]) >> 14, gain);
output[i] = (y << 14) + adder[i];
phase += freq;
}
}
void EngineOpl::compute_pure(int32_t *output, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, bool add) {
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
int32_t gain = gain1;
int32_t phase = phase0;
const int32_t *adder = add ? output : zeros;
for (int i = 0; i < _N_; i++) {
gain += dgain;
int32_t y = oplSin(phase >> 14, gain);
output[i] = (y << 14) + adder[i];
phase += freq;
}
}
void EngineOpl::compute_fb(int32_t *output, int32_t phase0, int32_t freq,
int32_t gain1, int32_t gain2,
int32_t *fb_buf, int fb_shift, bool add) {
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
int32_t gain = gain1;
int32_t phase = phase0;
const int32_t *adder = add ? output : zeros;
int32_t y0 = fb_buf[0];
int32_t y = fb_buf[1];
for (int i = 0; i < _N_; i++) {
gain += dgain;
int32_t scaled_fb = (y0 + y) >> (fb_shift + 1);
y0 = y;
y = oplSin((phase+scaled_fb) >> 14, gain) << 14;
output[i] = y + adder[i];
phase += freq;
}
fb_buf[0] = y0;
fb_buf[1] = y;
}
void EngineOpl::render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int32_t feedback_shift) {
const int kLevelThresh = 507; // really ????
const FmAlgorithm alg = algorithms[algorithm];
bool has_contents[3] = { true, false, false };
for (int op = 0; op < 6; op++) {
int flags = alg.ops[op];
bool add = (flags & OUT_BUS_ADD) != 0;
FmOpParams &param = params[op];
int inbus = (flags >> 4) & 3;
int outbus = flags & 3;
int32_t *outptr = (outbus == 0) ? output : buf_[outbus - 1].get();
int32_t gain1 = param.gain_out == 0 ? 511 : param.gain_out;
int32_t gain2 = 512-(param.level_in >> 19);
param.gain_out = gain2;
if (gain1 <= kLevelThresh || gain2 <= kLevelThresh) {
if (!has_contents[outbus]) {
add = false;
}
if (inbus == 0 || !has_contents[inbus]) {
// todo: more than one op in a feedback loop
if ((flags & 0xc0) == 0xc0 && feedback_shift < 16) {
// cout << op << " fb " << inbus << outbus << add << endl;
compute_fb(outptr, param.phase, param.freq,
gain1, gain2,
fb_buf, feedback_shift, add);
} else {
// cout << op << " pure " << inbus << outbus << add << endl;
compute_pure(outptr, param.phase, param.freq,
gain1, gain2, add);
}
} else {
// cout << op << " normal " << inbus << outbus << " " << param.freq << add << endl;
compute(outptr, buf_[inbus - 1].get(),
param.phase, param.freq, gain1, gain2, add);
}
has_contents[outbus] = true;
} else if (!add) {
has_contents[outbus] = false;
}
param.phase += param.freq << LG_N;
}
}

@ -0,0 +1,37 @@
/**
*
* Copyright (c) 2014 Pascal Gauthier.
*
* 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 ENGINEOPL_H_INCLUDED
#define ENGINEOPL_H_INCLUDED
#include "aligned_buf.h"
#include "fm_op_kernel.h"
#include "controllers.h"
#include "fm_core.h"
class EngineOpl : public FmCore {
public:
virtual void render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int32_t feedback_shift);
void compute(int32_t *output, const int32_t *input, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, bool add);
void compute_pure(int32_t *output, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, bool add);
void compute_fb(int32_t *output, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, int32_t *fb_buf, int fb_gain, bool add);
};
#endif // ENGINEOPL_H_INCLUDED

@ -22,6 +22,7 @@
#define __ALIGNED_BUF_H #define __ALIGNED_BUF_H
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h>
template<typename T, size_t size, size_t alignment = 16> template<typename T, size_t size, size_t alignment = 16>
class AlignedBuf { class AlignedBuf {

@ -22,7 +22,6 @@
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#include <arm_math.h> #include <arm_math.h>
#include <limits.h> #include <limits.h>
#include <cstdlib> #include <cstdlib>
@ -54,7 +53,6 @@ Dexed::Dexed(uint8_t maxnotes, int rate)
Porta::init_sr(rate); Porta::init_sr(rate);
fx.init(rate); fx.init(rate);
engineMsfa = new FmCore;
max_notes = maxnotes; max_notes = maxnotes;
currentNote = 0; currentNote = 0;
resetControllers(); resetControllers();
@ -62,7 +60,6 @@ Dexed::Dexed(uint8_t maxnotes, int rate)
controllers.opSwitch = 0x3f; // enable all operators controllers.opSwitch = 0x3f; // enable all operators
lastKeyDown = -1; lastKeyDown = -1;
vuSignal = 0.0; vuSignal = 0.0;
controllers.core = engineMsfa;
lfo.reset(data + 137); lfo.reset(data + 137);
sustain = false; sustain = false;
voices = NULL; voices = NULL;
@ -75,6 +72,8 @@ Dexed::Dexed(uint8_t maxnotes, int rate)
render_time_max = 0; render_time_max = 0;
setVelocityScale(MIDI_VELOCITY_SCALING_OFF); setVelocityScale(MIDI_VELOCITY_SCALING_OFF);
setNoteRefreshMode(false);
setEngineType(MSFA);
#ifndef TEENSYDUINO #ifndef TEENSYDUINO
compressor = new Compressor(samplerate); compressor = new Compressor(samplerate);
@ -91,8 +90,42 @@ Dexed::~Dexed()
for (uint8_t note = 0; note < max_notes; note++) for (uint8_t note = 0; note < max_notes; note++)
delete &voices[note]; delete &voices[note];
}
void Dexed::setEngineType(uint8_t engine)
{
switch(engine)
{
case MSFA:
controllers.core = &engineMsfa;
engineType=MSFA;
break;
case MKI:
controllers.core = &engineMkI;
engineType=MKI;
break;
case OPL:
controllers.core = &engineOpl;
engineType=OPL;
break;
default:
controllers.core = &engineMsfa;
engineType=MSFA;
break;
}
delete(engineMsfa); panic();
controllers.refresh();
}
uint8_t Dexed::getEngineType(void)
{
return(engineType);
}
uint32_t Dexed::getEngineAddress(void)
{
return((uint32_t)controllers.core);
} }
void Dexed::setMaxNotes(uint8_t new_max_notes) void Dexed::setMaxNotes(uint8_t new_max_notes)
@ -1686,6 +1719,10 @@ void Dexed::setVelocityScale(uint8_t setup = MIDI_VELOCITY_SCALING_OFF)
{ {
switch(setup) switch(setup)
{ {
case MIDI_VELOCITY_SCALING_OFF:
velocity_offset=0;
velocity_max=127;
break;
case MIDI_VELOCITY_SCALING_DX7: case MIDI_VELOCITY_SCALING_DX7:
velocity_offset=16; velocity_offset=16;
velocity_max=109; velocity_max=109;
@ -1694,7 +1731,7 @@ void Dexed::setVelocityScale(uint8_t setup = MIDI_VELOCITY_SCALING_OFF)
velocity_offset=6; velocity_offset=6;
velocity_max=119; velocity_max=119;
break; break;
default: // default setup default:
velocity_offset=0; velocity_offset=0;
velocity_max=127; velocity_max=127;
break; break;

@ -44,6 +44,8 @@
#include "lfo.h" #include "lfo.h"
#include "PluginFx.h" #include "PluginFx.h"
#include "compressor.h" #include "compressor.h"
#include "EngineMkI.h"
#include "EngineOpl.h"
#define NUM_VOICE_PARAMETERS 156 #define NUM_VOICE_PARAMETERS 156
@ -143,6 +145,18 @@ enum ON_OFF {
ON ON
}; };
enum VELOCITY_SCALES {
MIDI_VELOCITY_SCALING_OFF,
MIDI_VELOCITY_SCALING_DX7,
MIDI_VELOCITY_SCALING_DX7II
};
enum ENGINES {
MSFA,
MKI,
OPL
};
// GLOBALS // GLOBALS
//============================================================================== //==============================================================================
@ -178,6 +192,9 @@ class Dexed
void setVelocityScale(uint8_t offset, uint8_t max); void setVelocityScale(uint8_t offset, uint8_t max);
void getVelocityScale(uint8_t* offset, uint8_t* max); void getVelocityScale(uint8_t* offset, uint8_t* max);
void setVelocityScale(uint8_t setup); void setVelocityScale(uint8_t setup);
void setEngineType(uint8_t engine);
uint8_t getEngineType(void);
uint32_t getEngineAddress(void);
#ifndef TEENSYDUINO #ifndef TEENSYDUINO
void setCompressor(bool comp); void setCompressor(bool comp);
bool getCompressor(void); bool getCompressor(void);
@ -352,7 +369,9 @@ class Dexed
uint8_t engineType; uint8_t engineType;
VoiceStatus voiceStatus; VoiceStatus voiceStatus;
Lfo lfo; Lfo lfo;
FmCore* engineMsfa; FmCore engineMsfa;
EngineMkI engineMkI;
EngineOpl engineOpl;
void getSamples(float32_t* buffer, uint16_t n_samples); void getSamples(float32_t* buffer, uint16_t n_samples);
void getSamples(int16_t* buffer, uint16_t n_samples); void getSamples(int16_t* buffer, uint16_t n_samples);
void compress(float32_t* wav_in, float32_t* wav_out, uint16_t n, float32_t threshold, float32_t slope, uint16_t sr, float32_t tla, float32_t twnd, float32_t tatt, float32_t trel); void compress(float32_t* wav_in, float32_t* wav_out, uint16_t n, float32_t threshold, float32_t slope, uint16_t sr, float32_t tla, float32_t twnd, float32_t tatt, float32_t trel);
@ -363,12 +382,6 @@ class Dexed
#ifndef TEENSYDUINO #ifndef TEENSYDUINO
Compressor* compressor; Compressor* compressor;
#endif #endif
enum {
MIDI_VELOCITY_SCALING_OFF,
MIDI_VELOCITY_SCALING_DX7,
MIDI_VELOCITY_SCALING_DX7II
};
}; };
#endif #endif

@ -16,6 +16,28 @@ 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
* 26/02/2022: v1.0.16:
* add option for starting sample at begining or at loop start
``` c
typedef enum play_start {
play_start_sample,
play_start_loop,
};
wave.setPlayStart(play_start::play_start_loop);
```
* 26/02/2022: v1.0.15:
* added support for dual playback head for seemless looping
* enable dual playback using linear crossfading
* set crossfade duration in number of samples
``` c
AudioPlaySdResmp wave;
wave.setUseDualPlaybackHead(true);
wave.setCrossfadeDurationInSamples(1000);
wave.playRaw((int16_t*)kick_raw, kick_raw_len / 2, numberOfChannels);
wave.setLoopStart(0);
wave.setLoopFinish(3000);
```
* 16/06/2022: v1.0.14: * 16/06/2022: v1.0.14:
* refactored code to generic classes * refactored code to generic classes
* improve memory leaks * improve memory leaks

@ -2,10 +2,10 @@
"name": "TeensyVariablePlayback", "name": "TeensyVariablePlayback",
"frameworks": "Arduino", "frameworks": "Arduino",
"platforms": "Teensy", "platforms": "Teensy",
"keywords": "sound, audio, sample, resample, pitch, interpolation, legrange, sampler, playback, speed", "keywords": "sound, audio, sample, resample, pitch, interpolation, legrange, sampler, playback, speed, loop",
"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.14", "version": "1.0.16",
"export": { "export": {
"exclude": [ "exclude": [
".vscode", ".vscode",

@ -1,5 +1,5 @@
name=TeensyVariablePlayback name=TeensyVariablePlayback
version=1.0.14 version=1.0.16
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

@ -61,8 +61,15 @@ public:
uint32_t positionMillis(void) { uint32_t positionMillis(void) {
if (_file_size == 0) return 0; if (_file_size == 0) return 0;
if (!_useDualPlaybackHead) {
return (uint32_t) (( (double)_bufferPosition * lengthMillis() ) / (double)(_file_size/2)); return (uint32_t) (( (double)_bufferPosition1 * lengthMillis() ) / (double)(_file_size/2));
} else
{
if (_crossfade < 0.5)
return (uint32_t) (( (double)_bufferPosition1 * lengthMillis() ) / (double)(_file_size/2));
else
return (uint32_t) (( (double)_bufferPosition2 * lengthMillis() ) / (double)(_file_size/2));
}
} }
uint32_t lengthMillis(void) { uint32_t lengthMillis(void) {

@ -28,7 +28,11 @@ public:
initializeInterpolationPoints(); initializeInterpolationPoints();
} }
_playing = false; _playing = false;
_bufferPosition = _header_offset; _crossfade = 0.0;
if (_play_start == play_start::play_start_sample)
_bufferPosition1 = _header_offset;
else
_bufferPosition1 = _loop_start;
_file_size = 0; _file_size = 0;
} }
@ -176,10 +180,11 @@ public:
switch (_loopType) { switch (_loopType) {
case looptype_repeat: case looptype_repeat:
{ {
_crossfade = 0.0;
if (_playbackRate >= 0.0) if (_playbackRate >= 0.0)
_bufferPosition = _loop_start; _bufferPosition1 = _loop_start;
else else
_bufferPosition = _loop_finish - _numChannels; _bufferPosition1 = _loop_finish - _numChannels;
break; break;
} }
@ -187,12 +192,13 @@ public:
case looptype_pingpong: case looptype_pingpong:
{ {
if (_playbackRate >= 0.0) { if (_playbackRate >= 0.0) {
_bufferPosition = _loop_finish - _numChannels; _bufferPosition1 = _loop_finish - _numChannels;
//printf("switching to reverse playback...\n");
} }
else { else {
_bufferPosition = _header_offset; if (_play_start == play_start::play_start_sample)
//printf("switching to forward playback...\n"); _bufferPosition1 = _header_offset;
else
_bufferPosition1 = _loop_start;
} }
_playbackRate = -_playbackRate; _playbackRate = -_playbackRate;
break; break;
@ -215,33 +221,93 @@ public:
// read the sample value for given channel and store it at the location pointed to by the pointer 'value' // 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) { bool readNextValue(int16_t *value, uint16_t channel) {
if (_playbackRate >= 0 ) { if (!_useDualPlaybackHead) {
//forward playback if (_playbackRate >= 0 ) {
if (_bufferPosition >= _loop_finish ) //forward playback
return false; if (_bufferPosition1 >= _loop_finish )
return false;
} else if (_playbackRate < 0) { } else if (_playbackRate < 0) {
// reverse playback // reverse playback
if (_bufferPosition < _header_offset) if (_play_start == play_start::play_start_sample) {
return false; if (_bufferPosition1 < _header_offset)
return false;
} else {
if (_bufferPosition1 < _loop_start)
return false;
}
}
} else {
if (_playbackRate >= 0.0) {
if (_crossfade == 0.0 && _bufferPosition1 > (_loop_finish - _numChannels) - _crossfadeDurationInSamples) {
_bufferPosition2 = _loop_start;
_crossfade = 1.0 - (( (_loop_finish-_numChannels) - _bufferPosition1 ) / static_cast<double>(_crossfadeDurationInSamples));
_crossfadeState = 1;
} else if (_crossfade == 1.0 && _bufferPosition2 > (_loop_finish - _numChannels)- _crossfadeDurationInSamples) {
_bufferPosition1 = _loop_start;
_crossfade = ((_loop_finish - _numChannels) - _bufferPosition2) / static_cast<double>(_crossfadeDurationInSamples);
_crossfadeState = 2;
} else if (_crossfadeState == 1) {
_crossfade = 1.0 - (((_loop_finish - _numChannels) - _bufferPosition1) / static_cast<double>(_crossfadeDurationInSamples));
if (_crossfade >= 1.0) {
_crossfadeState = 0;
_crossfade = 1.0;
}
} else if (_crossfadeState == 2) {
_crossfade = ( (_loop_finish - _numChannels) - _bufferPosition2 ) / static_cast<double>(_crossfadeDurationInSamples);
if (_crossfade <= 0.0) {
_crossfadeState = 0;
_crossfade = 0.0;
}
}
} else {
if (_crossfade == 0.0 && _bufferPosition1 < _crossfadeDurationInSamples + _header_offset) {
_bufferPosition2 = _loop_finish - _numChannels;
_crossfade = 1.0 - (_bufferPosition1 - _header_offset) / static_cast<double>(_crossfadeDurationInSamples);
_crossfadeState = 1;
} else if (_crossfade == 1.0 && _bufferPosition2 < _crossfadeDurationInSamples + _header_offset) {
_bufferPosition1 = _loop_finish - _numChannels;
_crossfade = (_bufferPosition2 - _header_offset) / static_cast<double>(_crossfadeDurationInSamples);
_crossfadeState = 2;
} else if (_crossfadeState == 1) {
_crossfade = 1.0 - (_bufferPosition1 - _header_offset) / static_cast<double>(_crossfadeDurationInSamples);
if (_crossfade >= 1.0) {
_crossfadeState = 0;
_crossfade = 1.0;
}
} else if (_crossfadeState == 2) {
_crossfade = (_bufferPosition2 - _header_offset) / static_cast<double>(_crossfadeDurationInSamples);
if (_crossfade <= 0.0) {
_crossfadeState = 0;
_crossfade = 0.0;
}
}
}
} }
int16_t result = 0;
if (!_useDualPlaybackHead || _crossfade == 0.0) {
result = getSourceBufferValue(_bufferPosition1 + channel);
} else if (_crossfade == 1.0){
result = getSourceBufferValue(_bufferPosition2 + channel);
} else{
int result1 = getSourceBufferValue(_bufferPosition1 + channel);
int result2 = getSourceBufferValue(_bufferPosition2 + channel);
result = ((1.0 - _crossfade ) * result1) + ((_crossfade) * result2);
}
int16_t result = getSourceBufferValue(_bufferPosition + channel);
if (_interpolationType == ResampleInterpolationType::resampleinterpolation_linear) { if (_interpolationType == ResampleInterpolationType::resampleinterpolation_linear) {
double abs_remainder = abs(_remainder); double abs_remainder = abs(_remainder);
if (abs_remainder > 0.0) { if (abs_remainder > 0.0) {
if (_playbackRate > 0) { if (_playbackRate > 0) {
if (_remainder - _playbackRate < 0.0){ if (_remainder - _playbackRate < 0.0){
// we crossed over a whole number, make sure we update the samples for interpolation // we crossed over a whole number, make sure we update the samples for interpolation
if (!_useDualPlaybackHead) {
if (_playbackRate > 1.0) { if ( _numInterpolationPoints < 2 &&_playbackRate > 1.0 && _bufferPosition1 - _numChannels > _header_offset * 2 ) {
// need to update last sample // need to update last sample
_interpolationPoints[channel][1].y = getSourceBufferValue(_bufferPosition-_numChannels); _interpolationPoints[channel][1].y = getSourceBufferValue(_bufferPosition1 - _numChannels);
}
} }
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y; _interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
_interpolationPoints[channel][1].y = result; _interpolationPoints[channel][1].y = result;
if (_numInterpolationPoints < 2) if (_numInterpolationPoints < 2)
@ -251,12 +317,12 @@ public:
else if (_playbackRate < 0) { else if (_playbackRate < 0) {
if (_remainder - _playbackRate > 0.0){ if (_remainder - _playbackRate > 0.0){
// we crossed over a whole number, make sure we update the samples for interpolation // we crossed over a whole number, make sure we update the samples for interpolation
if (!_useDualPlaybackHead) {
if (_playbackRate < -1.0) { if (_numInterpolationPoints < 2 && _playbackRate < -1.0) {
// need to update last sample // need to update last sample
_interpolationPoints[channel][1].y = getSourceBufferValue(_bufferPosition+_numChannels); _interpolationPoints[channel][1].y = getSourceBufferValue(_bufferPosition1 + _numChannels);
}
} }
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y; _interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
_interpolationPoints[channel][1].y = result; _interpolationPoints[channel][1].y = result;
if (_numInterpolationPoints < 2) if (_numInterpolationPoints < 2)
@ -266,7 +332,6 @@ public:
if (_numInterpolationPoints > 1) { if (_numInterpolationPoints > 1) {
result = abs_remainder * _interpolationPoints[channel][1].y + (1.0 - abs_remainder) * _interpolationPoints[channel][0].y; result = abs_remainder * _interpolationPoints[channel][1].y + (1.0 - abs_remainder) * _interpolationPoints[channel][0].y;
//Serial.printf("[%f]\n", interpolation);
} }
} else { } else {
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y; _interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
@ -275,7 +340,6 @@ public:
_numInterpolationPoints++; _numInterpolationPoints++;
result =_interpolationPoints[channel][0].y; result =_interpolationPoints[channel][0].y;
//Serial.printf("%f\n", result);
} }
} }
else if (_interpolationType == ResampleInterpolationType::resampleinterpolation_quadratic) { else if (_interpolationType == ResampleInterpolationType::resampleinterpolation_quadratic) {
@ -291,7 +355,12 @@ public:
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y; _interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
_interpolationPoints[channel][1].y = _interpolationPoints[channel][2].y; _interpolationPoints[channel][1].y = _interpolationPoints[channel][2].y;
_interpolationPoints[channel][2].y = _interpolationPoints[channel][3].y; _interpolationPoints[channel][2].y = _interpolationPoints[channel][3].y;
_interpolationPoints[channel][3].y = getSourceBufferValue(_bufferPosition-(i*_numChannels)+1+channel); if (!_useDualPlaybackHead) {
_interpolationPoints[channel][3].y = getSourceBufferValue(_bufferPosition1-(i*_numChannels)+1+channel);
} else
{
_interpolationPoints[channel][3].y = result;
}
if (_numInterpolationPoints < 4) _numInterpolationPoints++; if (_numInterpolationPoints < 4) _numInterpolationPoints++;
} }
} }
@ -306,7 +375,12 @@ public:
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y; _interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
_interpolationPoints[channel][1].y = _interpolationPoints[channel][2].y; _interpolationPoints[channel][1].y = _interpolationPoints[channel][2].y;
_interpolationPoints[channel][2].y = _interpolationPoints[channel][3].y; _interpolationPoints[channel][2].y = _interpolationPoints[channel][3].y;
_interpolationPoints[channel][3].y = getSourceBufferValue(_bufferPosition+(i*_numChannels)-1+channel); if (!_useDualPlaybackHead) {
_interpolationPoints[channel][3].y = getSourceBufferValue(_bufferPosition1+(i*_numChannels)-1+channel);
} else
{
_interpolationPoints[channel][3].y = result;
}
if (_numInterpolationPoints < 4) _numInterpolationPoints++; if (_numInterpolationPoints < 4) _numInterpolationPoints++;
} }
} }
@ -344,7 +418,15 @@ public:
auto delta = static_cast<signed int>(_remainder); auto delta = static_cast<signed int>(_remainder);
_remainder -= static_cast<double>(delta); _remainder -= static_cast<double>(delta);
_bufferPosition += (delta * _numChannels); if (!_useDualPlaybackHead) {
_bufferPosition1 += (delta * _numChannels);
} else {
if (_crossfade < 1.0)
_bufferPosition1 += (delta * _numChannels);
if (_crossfade > 0.0)
_bufferPosition2 += (delta * _numChannels);
}
} }
*value = result; *value = result;
@ -353,9 +435,38 @@ public:
void setPlaybackRate(double f) { void setPlaybackRate(double f) {
_playbackRate = f; _playbackRate = f;
if (f < 0.0 && _bufferPosition == 0) { if (!_useDualPlaybackHead) {
//_file.seek(_file_size); if (f < 0.0) {
_bufferPosition = _file_size/2 - _numChannels; if (_bufferPosition1 <= _header_offset) {
if (_play_start == play_start::play_start_sample)
_bufferPosition1 = _file_size/2 - _numChannels;
else
_bufferPosition1 = _loop_finish - _numChannels;
}
} else {
if (f >= 0.0 && _bufferPosition1 < _header_offset) {
if (_play_start == play_start::play_start_sample)
_bufferPosition1 = _header_offset;
else
_bufferPosition1 = _loop_start;
}
}
} else {
// _useDualPlaybackHead == true
if (_crossfade == 0.0) {
if (f < 0.0) {
if( _bufferPosition1 <= _header_offset) {
if (_play_start == play_start::play_start_sample)
_bufferPosition1 = _file_size/2 - _numChannels;
else
_bufferPosition1 = _loop_finish - _numChannels;
}
} else {
if (f >= 0.0 && _bufferPosition1 < _header_offset) {
_bufferPosition1 = _header_offset;
}
}
}
} }
} }
@ -364,8 +475,9 @@ public:
} }
void loop(uint32_t numSamples) { void loop(uint32_t numSamples) {
_loop_start = _bufferPosition; int bufferPosition = (_crossfade < 1.0)? _bufferPosition1 : _bufferPosition2;
_loop_finish = _bufferPosition + numSamples * _numChannels; _loop_start = bufferPosition;
_loop_finish = bufferPosition + numSamples * _numChannels;
_loopType = loop_type::looptype_repeat; _loopType = loop_type::looptype_repeat;
} }
@ -389,11 +501,18 @@ public:
_numInterpolationPoints = 0; _numInterpolationPoints = 0;
if (_playbackRate > 0.0) { if (_playbackRate > 0.0) {
// forward playabck - set _file_offset to first audio block in file // forward playabck - set _file_offset to first audio block in file
_bufferPosition = _header_offset; if (_play_start == play_start::play_start_sample)
_bufferPosition1 = _header_offset;
else
_bufferPosition1 = _loop_start;
} else { } else {
// reverse playback - forward _file_offset to last audio block in file // reverse playback - forward _file_offset to last audio block in file
_bufferPosition = _loop_finish - _numChannels; if (_play_start == play_start::play_start_sample)
_bufferPosition1 = _file_size/2 - _numChannels;
else
_bufferPosition1 = _loop_finish - _numChannels;
} }
_crossfade = 0.0;
} }
void setLoopStart(uint32_t loop_start) { void setLoopStart(uint32_t loop_start) {
@ -405,6 +524,14 @@ public:
_loop_finish = _header_offset + (loop_finish * _numChannels); _loop_finish = _header_offset + (loop_finish * _numChannels);
} }
void setUseDualPlaybackHead(bool useDualPlaybackHead) {
_useDualPlaybackHead = useDualPlaybackHead;
}
void setCrossfadeDurationInSamples(unsigned int crossfadeDurationInSamples) {
_crossfadeDurationInSamples = crossfadeDurationInSamples;
}
void setInterpolationType(ResampleInterpolationType interpolationType) { void setInterpolationType(ResampleInterpolationType interpolationType) {
if (interpolationType != _interpolationType) { if (interpolationType != _interpolationType) {
_interpolationType = interpolationType; _interpolationType = interpolationType;
@ -425,12 +552,10 @@ public:
void setHeaderSizeInBytes(uint32_t headerSizeInBytes) { void setHeaderSizeInBytes(uint32_t headerSizeInBytes) {
_header_offset = headerSizeInBytes / 2; _header_offset = headerSizeInBytes / 2;
if (_bufferPosition < _header_offset) { }
if (_playbackRate >= 0) {
_bufferPosition = _header_offset; void setPlayStart(play_start start) {
} else _play_start = start;
_bufferPosition = _loop_finish - _numChannels;
}
} }
#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
@ -452,8 +577,14 @@ protected:
double _playbackRate = 1.0; double _playbackRate = 1.0;
double _remainder = 0.0; double _remainder = 0.0;
loop_type _loopType = looptype_none; loop_type _loopType = loop_type::looptype_none;
int _bufferPosition = 0; play_start _play_start = play_start::play_start_sample;
int _bufferPosition1 = 0;
int _bufferPosition2 = 0;
double _crossfade = 0.0;
bool _useDualPlaybackHead = false;
unsigned int _crossfadeDurationInSamples = 256;
int _crossfadeState = 0;
int32_t _loop_start = 0; int32_t _loop_start = 0;
int32_t _loop_finish = 0; int32_t _loop_finish = 0;
int16_t _numChannels = -1; int16_t _numChannels = -1;

@ -19,7 +19,7 @@
namespace newdigate { namespace newdigate {
class ResamplingSdReader : public ResamplingReader< IndexableSDFile<128, 2>, File > { class ResamplingSdReader : public ResamplingReader< IndexableSDFile<128, 4>, File > {
public: public:
ResamplingSdReader() : ResamplingSdReader() :
ResamplingReader() ResamplingReader()
@ -58,14 +58,21 @@ public:
deleteInterpolationPoints(); deleteInterpolationPoints();
} }
IndexableSDFile<128, 2>* createSourceBuffer() override { IndexableSDFile<128, 4>* createSourceBuffer() override {
return new IndexableSDFile<128, 2>(_filename); return new IndexableSDFile<128, 4>(_filename);
} }
uint32_t positionMillis(void) { uint32_t positionMillis(void) {
if (_file_size == 0) return 0; if (_file_size == 0) return 0;
if (!_useDualPlaybackHead) {
return (uint32_t) (( (double)_bufferPosition * lengthMillis() ) / (double)(_file_size/2)); return (uint32_t) (( (double)_bufferPosition1 * lengthMillis() ) / (double)(_file_size/2));
} else
{
if (_crossfade < 0.5)
return (uint32_t) (( (double)_bufferPosition1 * lengthMillis() ) / (double)(_file_size/2));
else
return (uint32_t) (( (double)_bufferPosition2 * lengthMillis() ) / (double)(_file_size/2));
}
} }
uint32_t lengthMillis(void) { uint32_t lengthMillis(void) {

@ -65,8 +65,15 @@ public:
uint32_t positionMillis(void) { uint32_t positionMillis(void) {
if (_file_size == 0) return 0; if (_file_size == 0) return 0;
if (!_useDualPlaybackHead) {
return (uint32_t) (( (double)_bufferPosition * lengthMillis() ) / (double)(_file_size/2)); return (uint32_t) (( (double)_bufferPosition1 * lengthMillis() ) / (double)(_file_size/2));
} else
{
if (_crossfade < 0.5)
return (uint32_t) (( (double)_bufferPosition1 * lengthMillis() ) / (double)(_file_size/2));
else
return (uint32_t) (( (double)_bufferPosition2 * lengthMillis() ) / (double)(_file_size/2));
}
} }
uint32_t lengthMillis(void) { uint32_t lengthMillis(void) {

@ -11,4 +11,9 @@ typedef enum loop_type {
looptype_pingpong looptype_pingpong
} loop_type; } loop_type;
typedef enum play_start {
play_start_sample,
play_start_loop,
} play_start;
#endif //TEENSY_RESAMPLING_LOOP_TYPE_H #endif //TEENSY_RESAMPLING_LOOP_TYPE_H

@ -70,6 +70,18 @@ class AudioPlayResmp : public AudioStream
reader->setLoopFinish(loop_finish); reader->setLoopFinish(loop_finish);
} }
void setUseDualPlaybackHead(bool useDualPlaybackHead) {
reader->setUseDualPlaybackHead(useDualPlaybackHead);
}
void setCrossfadeDurationInSamples(unsigned int crossfadeDurationInSamples) {
reader->setCrossfadeDurationInSamples(crossfadeDurationInSamples);
}
void setPlayStart(play_start start) {
reader->setPlayStart(start);
}
void enableInterpolation(bool enable) { void enableInterpolation(bool enable) {
if (enable) if (enable)
reader->setInterpolationType(ResampleInterpolationType::resampleinterpolation_quadratic); reader->setInterpolationType(ResampleInterpolationType::resampleinterpolation_quadratic);
@ -91,7 +103,7 @@ class AudioPlayResmp : public AudioStream
if (_numChannels == -1) if (_numChannels == -1)
return; return;
unsigned int n; unsigned int i, n;
audio_block_t *blocks[_numChannels]; audio_block_t *blocks[_numChannels];
int16_t *data[_numChannels]; int16_t *data[_numChannels];
// only update if we're playing // only update if we're playing

@ -29,14 +29,14 @@ public:
} else { } else {
perror("getcwd() error"); perror("getcwd() error");
} }
string outputPath = string(cwd) + "/" + string(path); std::string outputPath = std::string(cwd) + "/" + std::string(path);
__filesystem::path p(outputPath); __filesystem::path p(outputPath);
if (! __filesystem::exists(p) ) if (! __filesystem::exists(p) )
__filesystem::create_directories(outputPath); __filesystem::create_directories(outputPath);
_filePath = outputPath + string(filename); _filePath = outputPath + std::string(filename);
std::cout << "saving output audio .wav file to " << _filePath << std::endl; std::cout << "saving output audio .wav file to " << _filePath << std::endl;
_outputFile.open(_filePath, ios_base::trunc | ios_base::out); _outputFile.open(_filePath, std::ios_base::trunc | std::ios_base::out);
if (!_outputFile.is_open()) { if (!_outputFile.is_open()) {
Serial.printf("couldn't open file for recording...%s\n", _filePath.c_str()); Serial.printf("couldn't open file for recording...%s\n", _filePath.c_str());
return false; return false;
@ -54,7 +54,7 @@ public:
char buf[4]; char buf[4];
buf[1] = numChannels >> 8; buf[1] = numChannels >> 8;
buf[0] = numChannels; buf[0] = numChannels;
_outputFile.seekp(22, ios_base::beg); _outputFile.seekp(22, std::ios_base::beg);
_outputFile.write(buf, 2); _outputFile.write(buf, 2);
long bytespersecond = numChannels * 2 * 44100; long bytespersecond = numChannels * 2 * 44100;
@ -62,20 +62,20 @@ public:
buf[2] = bytespersecond >> 16; buf[2] = bytespersecond >> 16;
buf[1] = bytespersecond >> 8; buf[1] = bytespersecond >> 8;
buf[0] = bytespersecond; buf[0] = bytespersecond;
_outputFile.seekp(28, ios_base::beg); _outputFile.seekp(28, std::ios_base::beg);
_outputFile.write(buf, 4); _outputFile.write(buf, 4);
short bytespersampleframe = numChannels * 2; short bytespersampleframe = numChannels * 2;
buf[1] = bytespersampleframe >> 8; buf[1] = bytespersampleframe >> 8;
buf[0] = bytespersampleframe; buf[0] = bytespersampleframe;
_outputFile.seekp(32, ios_base::beg); _outputFile.seekp(32, std::ios_base::beg);
_outputFile.write(buf, 2); _outputFile.write(buf, 2);
buf[3] = _dataSize >> 24; buf[3] = _dataSize >> 24;
buf[2] = _dataSize >> 16; buf[2] = _dataSize >> 16;
buf[1] = _dataSize >> 8; buf[1] = _dataSize >> 8;
buf[0] = _dataSize; buf[0] = _dataSize;
_outputFile.seekp(40, ios_base::beg); _outputFile.seekp(40, std::ios_base::beg);
_outputFile.write(buf, 4); _outputFile.write(buf, 4);
_outputFile.close(); _outputFile.close();
_filename = nullptr; _filename = nullptr;

Loading…
Cancel
Save