TeensyVariablePlayback and Synth_Dexed libraries with fixes added.

Small fix for note refresh initialization of Dexed instances.
dev
Holger Wirtz 1 year 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. 229
      third-party/TeensyVariablePlayback/src/ResamplingReader.h
  18. 17
      third-party/TeensyVariablePlayback/src/ResamplingSdReader.h
  19. 13
      third-party/TeensyVariablePlayback/src/ResamplingSerialFlashReader.h
  20. 5
      third-party/TeensyVariablePlayback/src/loop_type.h
  21. 16
      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]->doRefreshVoice();
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
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);
#ifdef DEBUG
Serial.printf(PSTR("Note refresh: %d\n"), configuration.dexed[selected_instance_id].note_refresh);
#endif
display.setCursor(0, 1);
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
loadVoiceParameters KEYWORD2
getNumNotesPlaying KEYWORD2
uint32_t getXRun KEYWORD2
uint16_t getRenderTimeMax KEYWORD2
getXRun KEYWORD2
getRenderTimeMax KEYWORD2
resetRenderTimeMax KEYWORD2
ControllersRefresh KEYWORD2
setVelocityScale KEYWORD2
getVelocityScale KEYWORD2
setVelocityScale KEYWORD2
setEngineType KEYWORD2
getEngineType KEYWORD2
checkSystemExclusive KEYWORD2
# Sound methods KEYWORD2
keyup KEYWORD2
@ -45,7 +51,7 @@ panic KEYWORD2
notesOff KEYWORD2
resetControllers KEYWORD2
setMasterTune KEYWORD2
int8_t getMasterTune KEYWORD2
getMasterTune KEYWORD2
setPortamentoMode KEYWORD2
setPBController KEYWORD2
setMWController KEYWORD2
@ -83,11 +89,11 @@ getAftertouchRange KEYWORD2
setAftertouchTarget KEYWORD2
getAftertouchTarget KEYWORD2
setFilterCutoff KEYWORD2
float getFilterCutoff KEYWORD2
getFilterCutoff KEYWORD2
setFilterResonance KEYWORD2
float getFilterResonance KEYWORD2
getFilterResonance KEYWORD2
setGain KEYWORD2
float getGain KEYWORD2
getGain KEYWORD2
# Voice configuration methods 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
#include <stdlib.h>
#include <stdint.h>
template<typename T, size_t size, size_t alignment = 16>
class AlignedBuf {

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

@ -44,6 +44,8 @@
#include "lfo.h"
#include "PluginFx.h"
#include "compressor.h"
#include "EngineMkI.h"
#include "EngineOpl.h"
#define NUM_VOICE_PARAMETERS 156
@ -143,6 +145,18 @@ enum ON_OFF {
ON
};
enum VELOCITY_SCALES {
MIDI_VELOCITY_SCALING_OFF,
MIDI_VELOCITY_SCALING_DX7,
MIDI_VELOCITY_SCALING_DX7II
};
enum ENGINES {
MSFA,
MKI,
OPL
};
// GLOBALS
//==============================================================================
@ -178,6 +192,9 @@ class Dexed
void setVelocityScale(uint8_t offset, uint8_t max);
void getVelocityScale(uint8_t* offset, uint8_t* max);
void setVelocityScale(uint8_t setup);
void setEngineType(uint8_t engine);
uint8_t getEngineType(void);
uint32_t getEngineAddress(void);
#ifndef TEENSYDUINO
void setCompressor(bool comp);
bool getCompressor(void);
@ -352,7 +369,9 @@ class Dexed
uint8_t engineType;
VoiceStatus voiceStatus;
Lfo lfo;
FmCore* engineMsfa;
FmCore engineMsfa;
EngineMkI engineMkI;
EngineOpl engineOpl;
void getSamples(float32_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);
@ -363,12 +382,6 @@ class Dexed
#ifndef TEENSYDUINO
Compressor* compressor;
#endif
enum {
MIDI_VELOCITY_SCALING_OFF,
MIDI_VELOCITY_SCALING_DX7,
MIDI_VELOCITY_SCALING_DX7II
};
};
#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)
## 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:
* refactored code to generic classes
* improve memory leaks

@ -2,10 +2,10 @@
"name": "TeensyVariablePlayback",
"frameworks": "Arduino",
"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",
"url": "https://github.com/newdigate/teensy-variable-playback",
"version": "1.0.14",
"version": "1.0.16",
"export": {
"exclude": [
".vscode",

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

@ -61,8 +61,15 @@ public:
uint32_t positionMillis(void) {
if (_file_size == 0) return 0;
return (uint32_t) (( (double)_bufferPosition * lengthMillis() ) / (double)(_file_size/2));
if (!_useDualPlaybackHead) {
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) {

@ -28,7 +28,11 @@ public:
initializeInterpolationPoints();
}
_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;
}
@ -176,10 +180,11 @@ public:
switch (_loopType) {
case looptype_repeat:
{
_crossfade = 0.0;
if (_playbackRate >= 0.0)
_bufferPosition = _loop_start;
_bufferPosition1 = _loop_start;
else
_bufferPosition = _loop_finish - _numChannels;
_bufferPosition1 = _loop_finish - _numChannels;
break;
}
@ -187,12 +192,13 @@ public:
case looptype_pingpong:
{
if (_playbackRate >= 0.0) {
_bufferPosition = _loop_finish - _numChannels;
//printf("switching to reverse playback...\n");
_bufferPosition1 = _loop_finish - _numChannels;
}
else {
_bufferPosition = _header_offset;
//printf("switching to forward playback...\n");
if (_play_start == play_start::play_start_sample)
_bufferPosition1 = _header_offset;
else
_bufferPosition1 = _loop_start;
}
_playbackRate = -_playbackRate;
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'
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;
if (!_useDualPlaybackHead) {
if (_playbackRate >= 0 ) {
//forward playback
if (_bufferPosition1 >= _loop_finish )
return false;
} else if (_playbackRate < 0) {
// reverse playback
if (_play_start == play_start::play_start_sample) {
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) {
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);
if (!_useDualPlaybackHead) {
if ( _numInterpolationPoints < 2 &&_playbackRate > 1.0 && _bufferPosition1 - _numChannels > _header_offset * 2 ) {
// need to update last sample
_interpolationPoints[channel][1].y = getSourceBufferValue(_bufferPosition1 - _numChannels);
}
}
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
_interpolationPoints[channel][1].y = result;
if (_numInterpolationPoints < 2)
@ -251,12 +317,12 @@ public:
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);
if (!_useDualPlaybackHead) {
if (_numInterpolationPoints < 2 && _playbackRate < -1.0) {
// need to update last sample
_interpolationPoints[channel][1].y = getSourceBufferValue(_bufferPosition1 + _numChannels);
}
}
_interpolationPoints[channel][0].y = _interpolationPoints[channel][1].y;
_interpolationPoints[channel][1].y = result;
if (_numInterpolationPoints < 2)
@ -266,7 +332,6 @@ public:
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;
@ -275,7 +340,6 @@ public:
_numInterpolationPoints++;
result =_interpolationPoints[channel][0].y;
//Serial.printf("%f\n", result);
}
}
else if (_interpolationType == ResampleInterpolationType::resampleinterpolation_quadratic) {
@ -291,7 +355,12 @@ public:
_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 (!_useDualPlaybackHead) {
_interpolationPoints[channel][3].y = getSourceBufferValue(_bufferPosition1-(i*_numChannels)+1+channel);
} else
{
_interpolationPoints[channel][3].y = result;
}
if (_numInterpolationPoints < 4) _numInterpolationPoints++;
}
}
@ -306,7 +375,12 @@ public:
_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 (!_useDualPlaybackHead) {
_interpolationPoints[channel][3].y = getSourceBufferValue(_bufferPosition1+(i*_numChannels)-1+channel);
} else
{
_interpolationPoints[channel][3].y = result;
}
if (_numInterpolationPoints < 4) _numInterpolationPoints++;
}
}
@ -338,13 +412,21 @@ public:
//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);
if (!_useDualPlaybackHead) {
_bufferPosition1 += (delta * _numChannels);
} else {
if (_crossfade < 1.0)
_bufferPosition1 += (delta * _numChannels);
if (_crossfade > 0.0)
_bufferPosition2 += (delta * _numChannels);
}
}
*value = result;
@ -353,9 +435,38 @@ public:
void setPlaybackRate(double f) {
_playbackRate = f;
if (f < 0.0 && _bufferPosition == 0) {
//_file.seek(_file_size);
_bufferPosition = _file_size/2 - _numChannels;
if (!_useDualPlaybackHead) {
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) {
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) {
_loop_start = _bufferPosition;
_loop_finish = _bufferPosition + numSamples * _numChannels;
int bufferPosition = (_crossfade < 1.0)? _bufferPosition1 : _bufferPosition2;
_loop_start = bufferPosition;
_loop_finish = bufferPosition + numSamples * _numChannels;
_loopType = loop_type::looptype_repeat;
}
@ -389,11 +501,18 @@ public:
_numInterpolationPoints = 0;
if (_playbackRate > 0.0) {
// 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 {
// 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) {
@ -405,6 +524,14 @@ public:
_loop_finish = _header_offset + (loop_finish * _numChannels);
}
void setUseDualPlaybackHead(bool useDualPlaybackHead) {
_useDualPlaybackHead = useDualPlaybackHead;
}
void setCrossfadeDurationInSamples(unsigned int crossfadeDurationInSamples) {
_crossfadeDurationInSamples = crossfadeDurationInSamples;
}
void setInterpolationType(ResampleInterpolationType interpolationType) {
if (interpolationType != _interpolationType) {
_interpolationType = interpolationType;
@ -425,14 +552,12 @@ public:
void setHeaderSizeInBytes(uint32_t headerSizeInBytes) {
_header_offset = headerSizeInBytes / 2;
if (_bufferPosition < _header_offset) {
if (_playbackRate >= 0) {
_bufferPosition = _header_offset;
} else
_bufferPosition = _loop_finish - _numChannels;
}
}
void setPlayStart(play_start start) {
_play_start = start;
}
#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592
uint32_t positionMillis()
{
@ -452,8 +577,14 @@ protected:
double _playbackRate = 1.0;
double _remainder = 0.0;
loop_type _loopType = looptype_none;
int _bufferPosition = 0;
loop_type _loopType = loop_type::looptype_none;
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_finish = 0;
int16_t _numChannels = -1;

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

@ -65,10 +65,17 @@ public:
uint32_t positionMillis(void) {
if (_file_size == 0) return 0;
return (uint32_t) (( (double)_bufferPosition * lengthMillis() ) / (double)(_file_size/2));
if (!_useDualPlaybackHead) {
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) {
return ((uint64_t)_file_size * B2M) >> 32;
}

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

@ -70,6 +70,18 @@ class AudioPlayResmp : public AudioStream
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) {
if (enable)
reader->setInterpolationType(ResampleInterpolationType::resampleinterpolation_quadratic);
@ -91,7 +103,7 @@ class AudioPlayResmp : public AudioStream
if (_numChannels == -1)
return;
unsigned int n;
unsigned int i, n;
audio_block_t *blocks[_numChannels];
int16_t *data[_numChannels];
// only update if we're playing
@ -136,4 +148,4 @@ class AudioPlayResmp : public AudioStream
TResamplingReader *reader;
};
#endif // TEENSY_RESAMPLING_SDREADER_PLAYRESMP_H
#endif // TEENSY_RESAMPLING_SDREADER_PLAYRESMP_H

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

Loading…
Cancel
Save