Next try for ReverbMixer and NEON extensions (RPi2/3/4) (#86)

* Signal chain is now float32_t with much more support of CMSIS5.

* Fixes for float signal path.

* Several fixes for float signal path.

* Manual merge of changes from upstream.

* Fixes for wrong panning calculation.

* Added code for master compressor.
Added code for mixer.
Added HAVE_NEON to MAkefile for enabling more optimizations on Synth_Dexed.

* Adding mixer class - but it has some linking problems...

* Fast fix.

* Next fast fix.

* Fixing effect_mixer.

* Added ReverbSend Parameter for each TG.

* Fixes for ReverbSend and Pan.

* Fixing constrain().

* Fix aarch64 build, thanks @rsta2

https://github.com/probonopd/MiniDexed/pull/85#issuecomment-1095391183

* Fix for building for RPi1.

* Add TGParameterReverbSend

https://github.com/probonopd/MiniDexed/pull/86#issuecomment-1097079538
Thanks @rsta2

* Add TGParameterReverbSend

https://github.com/probonopd/MiniDexed/pull/86#issuecomment-1097079538
Thanks @rsta2

* Implement changes suggested by @rsta2

Description:
https://github.com/probonopd/MiniDexed/pull/86#issuecomment-1097912829

Thanks @rsta2

Co-authored-by: Holger Wirtz <wirtz@parasitstudio.de>
Co-authored-by: probonopd <probonopd@users.noreply.github.com>
pull/97/head 2022-04-16
Holger 3 years ago committed by GitHub
parent 35b07f17f5
commit 77813bf90e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Synth_Dexed
  2. 2
      src/Makefile
  3. 14
      src/Synth_Dexed.mk
  4. 5
      src/common.h
  5. 325
      src/effect_compressor.cpp
  6. 84
      src/effect_compressor.h
  7. 173
      src/effect_mixer.hpp
  8. 1
      src/effect_platervbstereo.h
  9. 158
      src/minidexed.cpp
  10. 853
      src/minidexed.cpp.O
  11. 13
      src/minidexed.h
  12. 1
      src/minidexed.ini
  13. 87
      src/mixer.cpp
  14. 79
      src/mixer.h
  15. 15
      src/performance.ini
  16. 20
      src/performanceconfig.cpp
  17. 3
      src/performanceconfig.h
  18. 1
      src/uimenu.cpp

@ -1 +1 @@
Subproject commit e414a8718300815aefc3fe0acd8df5c12ad0b58a Subproject commit f304ae4e1cfa6ca9bab918f8f5a55bd562756e78

@ -9,7 +9,7 @@ CMSIS_DIR = ../CMSIS_5/CMSIS
OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \
mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \
sysexfileloader.o performanceconfig.o perftimer.o \ sysexfileloader.o performanceconfig.o perftimer.o \
effect_platervbstereo.o effect_compressor.o effect_platervbstereo.o
include ./Synth_Dexed.mk include ./Synth_Dexed.mk
include ./Rules.mk include ./Rules.mk

@ -5,7 +5,9 @@
CMSIS_CORE_INCLUDE_DIR = $(CMSIS_DIR)/Core/Include CMSIS_CORE_INCLUDE_DIR = $(CMSIS_DIR)/Core/Include
CMSIS_DSP_INCLUDE_DIR = $(CMSIS_DIR)/DSP/Include CMSIS_DSP_INCLUDE_DIR = $(CMSIS_DIR)/DSP/Include
CMSIS_DSP_PRIVATE_INCLUDE_DIR = $(CMSIS_DIR)/DSP/PrivateInclude CMSIS_DSP_PRIVATE_INCLUDE_DIR = $(CMSIS_DIR)/DSP/PrivateInclude
CMSIS_DSP_COMPUTELIB_INCLUDE_DIR = $(CMSIS_DIR)/DSP/ComputeLibrary/Include
CMSIS_DSP_SOURCE_DIR = $(CMSIS_DIR)/DSP/Source CMSIS_DSP_SOURCE_DIR = $(CMSIS_DIR)/DSP/Source
CMSIS_DSP_COMPUTELIB_SRC_DIR = $(CMSIS_DIR)/DSP/ComputeLibrary/Source
OBJS += \ OBJS += \
$(SYNTH_DEXED_DIR)/PluginFx.o \ $(SYNTH_DEXED_DIR)/PluginFx.o \
@ -24,12 +26,18 @@ OBJS += \
$(CMSIS_DSP_SOURCE_DIR)/BasicMathFunctions/BasicMathFunctions.o \ $(CMSIS_DSP_SOURCE_DIR)/BasicMathFunctions/BasicMathFunctions.o \
$(CMSIS_DSP_SOURCE_DIR)/FastMathFunctions/FastMathFunctions.o \ $(CMSIS_DSP_SOURCE_DIR)/FastMathFunctions/FastMathFunctions.o \
$(CMSIS_DSP_SOURCE_DIR)/FilteringFunctions/FilteringFunctions.o \ $(CMSIS_DSP_SOURCE_DIR)/FilteringFunctions/FilteringFunctions.o \
$(CMSIS_DSP_SOURCE_DIR)/CommonTables/CommonTables.o $(CMSIS_DSP_SOURCE_DIR)/CommonTables/CommonTables.o \
$(CMSIS_DSP_COMPUTELIB_SRC_DIR)/arm_cl_tables.o
INCLUDE += -I $(SYNTH_DEXED_DIR) INCLUDE += -I $(SYNTH_DEXED_DIR)
INCLUDE += -I $(CMSIS_CORE_INCLUDE_DIR) INCLUDE += -I $(CMSIS_CORE_INCLUDE_DIR)
INCLUDE += -I $(CMSIS_DSP_INCLUDE_DIR) INCLUDE += -I $(CMSIS_DSP_INCLUDE_DIR)
INCLUDE += -I $(CMSIS_DSP_PRIVATE_INCLUDE_DIR) INCLUDE += -I $(CMSIS_DSP_PRIVATE_INCLUDE_DIR)
CXXFLAGS += -DARM_MATH_NEON -DHAVE_NEON INCLUDE += -I $(CMSIS_DSP_COMPUTELIB_INCLUDE_DIR)
EXTRACLEAN = $(SYNTH_DEXED_DIR)/*.[od] $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/BasicMathFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/FastMathFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/FilteringFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/CommonTables/*.[od] ifeq ($(strip $(AARCH)),64)
DEFINE += -DARM_MATH_NEON
DEFINE += -DHAVE_NEON
endif
EXTRACLEAN = $(SYNTH_DEXED_DIR)/*.[od] $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/BasicMathFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/FastMathFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/FilteringFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/CommonTables/*.[od] $(CMSIS_DSP_COMPUTELIB_SRC_DIR)/*.[od]

@ -11,6 +11,11 @@ inline float32_t mapfloat(float32_t val, float32_t in_min, float32_t in_max, flo
return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
} }
inline float32_t mapfloat(int val, int in_min, int in_max, float32_t out_min, float32_t out_max)
{
return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
#define constrain(amt, low, high) ({ \ #define constrain(amt, low, high) ({ \
__typeof__(amt) _amt = (amt); \ __typeof__(amt) _amt = (amt); \
__typeof__(low) _low = (low); \ __typeof__(low) _low = (low); \

@ -0,0 +1,325 @@
/* From https://github.com/chipaudette/OpenAudio_ArduinoLibrary */
/*
AudioEffectCompressor
Created: Chip Audette, Dec 2016 - Jan 2017
Purpose; Apply dynamic range compression to the audio stream.
Assumes floating-point data.
This processes a single stream fo audio data (ie, it is mono)
MIT License. use at your own risk.
*/
#include <circle/logger.h>
#include <cstdlib>
#include "effect_compressor.h"
LOGMODULE ("compressor");
Compressor::Compressor(const float32_t sample_rate_Hz) {
//setDefaultValues(AUDIO_SAMPLE_RATE); resetStates();
setDefaultValues(sample_rate_Hz);
resetStates();
}
void Compressor::setDefaultValues(const float32_t sample_rate_Hz) {
setThresh_dBFS(-20.0f); //set the default value for the threshold for compression
setCompressionRatio(5.0f); //set the default copression ratio
setAttack_sec(0.005f, sample_rate_Hz); //default to this value
setRelease_sec(0.200f, sample_rate_Hz); //default to this value
setHPFilterCoeff(); enableHPFilter(true); //enable the HP filter to remove any DC offset from the audio
}
//Compute the instantaneous desired gain, including the compression ratio and
//threshold for where the comrpession kicks in
void Compressor::calcInstantaneousTargetGain(float32_t *audio_level_dB_block, float32_t *inst_targ_gain_dB_block, uint16_t len)
{
// how much are we above the compression threshold?
float32_t above_thresh_dB_block[len];
//arm_copy_f32(zeroblock_f32,above_thresh_dB_block,len);
arm_offset_f32(audio_level_dB_block, //CMSIS DSP for "add a constant value to all elements"
-thresh_dBFS, //this is the value to be added
above_thresh_dB_block, //this is the output
len);
// scale by the compression ratio...this is what the output level should be (this is our target level)
arm_scale_f32(above_thresh_dB_block, //CMSIS DSP for "multiply all elements by a constant value"
1.0f / comp_ratio, //this is the value to be multiplied
inst_targ_gain_dB_block, //this is the output
len);
// compute the instantaneous gain...which is the difference between the target level and the original level
arm_sub_f32(inst_targ_gain_dB_block, //CMSIS DSP for "subtract two vectors element-by-element"
above_thresh_dB_block, //this is the vector to be subtracted
inst_targ_gain_dB_block, //this is the output
len);
// limit the target gain to attenuation only (this part of the compressor should not make things louder!)
for (uint16_t i=0; i < len; i++) {
if (inst_targ_gain_dB_block[i] > 0.0f) inst_targ_gain_dB_block[i] = 0.0f;
}
return; //output is passed through inst_targ_gain_dB_block
}
//this method applies the "attack" and "release" constants to smooth the
//target gain level through time.
void Compressor::calcSmoothedGain_dB(float32_t *inst_targ_gain_dB_block, float32_t *gain_dB_block, uint16_t len)
{
float32_t gain_dB;
float32_t one_minus_attack_const = 1.0f - attack_const;
float32_t one_minus_release_const = 1.0f - release_const;
for (uint16_t i = 0; i < len; i++) {
gain_dB = inst_targ_gain_dB_block[i];
//smooth the gain using the attack or release constants
if (gain_dB < prev_gain_dB) { //are we in the attack phase?
gain_dB_block[i] = attack_const*prev_gain_dB + one_minus_attack_const*gain_dB;
} else { //or, we're in the release phase
gain_dB_block[i] = release_const*prev_gain_dB + one_minus_release_const*gain_dB;
}
//save value for the next time through this loop
prev_gain_dB = gain_dB_block[i];
}
return; //the output here is gain_block
}
// Here's the method that estimates the level of the audio (in dB)
// It squares the signal and low-pass filters to get a time-averaged
// signal power. It then
void Compressor::calcAudioLevel_dB(float32_t *wav_block, float32_t *level_dB_block, uint16_t len) {
// calculate the instantaneous signal power (square the signal)
float32_t wav_pow_block[len];
//arm_copy_f32(zeroblock_f32,wav_pow_block,len);
arm_mult_f32(wav_block, wav_block, wav_pow_block, len);
// low-pass filter and convert to dB
float32_t c1 = level_lp_const, c2 = 1.0f - c1; //prepare constants
for (uint16_t i = 0; i < len; i++) {
// first-order low-pass filter to get a running estimate of the average power
wav_pow_block[i] = c1*prev_level_lp_pow + c2*wav_pow_block[i];
// save the state of the first-order low-pass filter
prev_level_lp_pow = wav_pow_block[i];
//now convert the signal power to dB (but not yet multiplied by 10.0)
level_dB_block[i] = log10f_approx(wav_pow_block[i]);
}
//limit the amount that the state of the smoothing filter can go toward negative infinity
if (prev_level_lp_pow < (1.0E-13)) prev_level_lp_pow = 1.0E-13; //never go less than -130 dBFS
//scale the wav_pow_block by 10.0 to complete the conversion to dB
arm_scale_f32(level_dB_block, 10.0f, level_dB_block, len); //use ARM DSP for speed!
return; //output is passed through level_dB_block
}
//This method computes the desired gain from the compressor, given an estimate
//of the signal level (in dB)
void Compressor::calcGain(float32_t *audio_level_dB_block, float32_t *gain_block,uint16_t len)
{
//first, calculate the instantaneous target gain based on the compression ratio
float32_t inst_targ_gain_dB_block[len];
//arm_copy_f32(zeroblock_f32,inst_targ_gain_dB_block,len);
calcInstantaneousTargetGain(audio_level_dB_block, inst_targ_gain_dB_block,len);
//second, smooth in time (attack and release) by stepping through each sample
float32_t gain_dB_block[len];
//arm_copy_f32(zeroblock_f32,gain_dB_block,len);
calcSmoothedGain_dB(inst_targ_gain_dB_block,gain_dB_block, len);
//finally, convert from dB to linear gain: gain = 10^(gain_dB/20); (ie this takes care of the sqrt, too!)
arm_scale_f32(gain_dB_block, 1.0f/20.0f, gain_dB_block, len); //divide by 20
for (uint16_t i = 0; i < len; i++) gain_block[i] = pow10f(gain_dB_block[i]); //do the 10^(x)
return; //output is passed through gain_block
}
//here's the method that does all the work
void Compressor::doCompression(float32_t *audio_block, uint16_t len) {
//Serial.println("AudioEffectGain_F32: updating."); //for debugging.
if (!audio_block) {
LOGERR("No audio_block available for Compressor!");
return;
}
//apply a high-pass filter to get rid of the DC offset
if (use_HP_prefilter)
arm_biquad_cascade_df1_f32(&hp_filt_struct, audio_block, audio_block, len);
//apply the pre-gain...a negative gain value will disable
if (pre_gain > 0.0f)
arm_scale_f32(audio_block, pre_gain, audio_block, len); //use ARM DSP for speed!
//calculate the level of the audio (ie, calculate a smoothed version of the signal power)
float32_t audio_level_dB_block[len];
//arm_copy_f32(zeroblock_f32,audio_level_dB_block,len);
calcAudioLevel_dB(audio_block, audio_level_dB_block, len); //returns through audio_level_dB_block
//compute the desired gain based on the observed audio level
float32_t gain_block[len];
//arm_copy_f32(zeroblock_f32,gain_block,len);
calcGain(audio_level_dB_block, gain_block, len); //returns through gain_block
//apply the desired gain...store the processed audio back into audio_block
arm_mult_f32(audio_block, gain_block, audio_block, len);
}
//methods to set parameters of this module
void Compressor::resetStates(void)
{
prev_level_lp_pow = 1.0f;
prev_gain_dB = 0.0f;
//initialize the HP filter. (This also resets the filter states,)
arm_biquad_cascade_df1_init_f32(&hp_filt_struct, hp_nstages, hp_coeff, hp_state);
}
void Compressor::setPreGain(float32_t g)
{
pre_gain = g;
}
void Compressor::setPreGain_dB(float32_t gain_dB)
{
setPreGain(pow(10.0, gain_dB / 20.0));
}
void Compressor::setCompressionRatio(float32_t cr)
{
comp_ratio = max(0.001f, cr); //limit to positive values
updateThresholdAndCompRatioConstants();
}
void Compressor::setAttack_sec(float32_t a, float32_t fs_Hz)
{
attack_sec = a;
attack_const = expf(-1.0f / (attack_sec * fs_Hz)); //expf() is much faster than exp()
//also update the time constant for the envelope extraction
setLevelTimeConst_sec(min(attack_sec,release_sec) / 5.0, fs_Hz); //make the level time-constant one-fifth the gain time constants
}
void Compressor::setRelease_sec(float32_t r, float32_t fs_Hz)
{
release_sec = r;
release_const = expf(-1.0f / (release_sec * fs_Hz)); //expf() is much faster than exp()
//also update the time constant for the envelope extraction
setLevelTimeConst_sec(min(attack_sec,release_sec) / 5.0, fs_Hz); //make the level time-constant one-fifth the gain time constants
}
void Compressor::setLevelTimeConst_sec(float32_t t_sec, float32_t fs_Hz)
{
const float32_t min_t_sec = 0.002f; //this is the minimum allowed value
level_lp_sec = max(min_t_sec,t_sec);
level_lp_const = expf(-1.0f / (level_lp_sec * fs_Hz)); //expf() is much faster than exp()
}
void Compressor::setThresh_dBFS(float32_t val)
{
thresh_dBFS = val;
setThreshPow(pow(10.0, thresh_dBFS / 10.0));
}
void Compressor::enableHPFilter(boolean flag)
{
use_HP_prefilter = flag;
}
void Compressor::setHPFilterCoeff_N2IIR_Matlab(float32_t b[], float32_t a[])
{
//https://www.keil.com/pack/doc/CMSIS/DSP/html/group__BiquadCascadeDF1.html#ga8e73b69a788e681a61bccc8959d823c5
//Use matlab to compute the coeff for HP at 20Hz: [b,a]=butter(2,20/(44100/2),'high'); %assumes fs_Hz = 44100
hp_coeff[0] = b[0]; hp_coeff[1] = b[1]; hp_coeff[2] = b[2]; //here are the matlab "b" coefficients
hp_coeff[3] = -a[1]; hp_coeff[4] = -a[2]; //the DSP needs the "a" terms to have opposite sign vs Matlab
}
void Compressor::setHPFilterCoeff(void)
{
//https://www.keil.com/pack/doc/CMSIS/DSP/html/group__BiquadCascadeDF1.html#ga8e73b69a788e681a61bccc8959d823c5
//Use matlab to compute the coeff for HP at 20Hz: [b,a]=butter(2,20/(44100/2),'high'); %assumes fs_Hz = 44100
float32_t b[] = {9.979871156751189e-01, -1.995974231350238e+00, 9.979871156751189e-01}; //from Matlab
float32_t a[] = { 1.000000000000000e+00, -1.995970179642828e+00, 9.959782830576472e-01}; //from Matlab
setHPFilterCoeff_N2IIR_Matlab(b, a);
//hp_coeff[0] = b[0]; hp_coeff[1] = b[1]; hp_coeff[2] = b[2]; //here are the matlab "b" coefficients
//hp_coeff[3] = -a[1]; hp_coeff[4] = -a[2]; //the DSP needs the "a" terms to have opposite sign vs Matlab
}
void Compressor::updateThresholdAndCompRatioConstants(void)
{
comp_ratio_const = 1.0f-(1.0f / comp_ratio);
thresh_pow_FS_wCR = powf(thresh_pow_FS, comp_ratio_const);
}
void Compressor::setThreshPow(float32_t t_pow)
{
thresh_pow_FS = t_pow;
updateThresholdAndCompRatioConstants();
}
// Accelerate the powf(10.0,x) function
static float32_t pow10f(float32_t x)
{
//return powf(10.0f,x) //standard, but slower
return expf(2.302585092994f*x); //faster: exp(log(10.0f)*x)
}
// Accelerate the log10f(x) function?
static float32_t log10f_approx(float32_t x)
{
//return log10f(x); //standard, but slower
return log2f_approx(x)*0.3010299956639812f; //faster: log2(x)/log2(10)
}
/* ----------------------------------------------------------------------
** Fast approximation to the log2() function. It uses a two step
** process. First, it decomposes the floating-point number into
** a fractional component F and an exponent E. The fraction component
** is used in a polynomial approximation and then the exponent added
** to the result. A 3rd order polynomial is used and the result
** when computing db20() is accurate to 7.984884e-003 dB.
** ------------------------------------------------------------------- */
//https://community.arm.com/tools/f/discussions/4292/cmsis-dsp-new-functionality-proposal/22621#22621
//float32_t log2f_approx_coeff[4] = {1.23149591368684f, -4.11852516267426f, 6.02197014179219f, -3.13396450166353f};
static float32_t log2f_approx(float32_t X)
{
//float32_t *C = &log2f_approx_coeff[0];
float32_t Y;
float32_t F;
int E;
// This is the approximation to log2()
F = frexpf(fabsf(X), &E);
// Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E;
//Y = *C++;
Y = 1.23149591368684f;
Y *= F;
//Y += (*C++);
Y += -4.11852516267426f;
Y *= F;
//Y += (*C++);
Y += 6.02197014179219f;
Y *= F;
//Y += (*C++);
Y += -3.13396450166353f;
Y += E;
return(Y);
}

@ -0,0 +1,84 @@
/* From https://github.com/chipaudette/OpenAudio_ArduinoLibrary */
/*
AudioEffectCompressor
Created: Chip Audette, Dec 2016 - Jan 2017
Purpose; Apply dynamic range compression to the audio stream.
Assumes floating-point data.
This processes a single stream fo audio data (ie, it is mono)
MIT License. use at your own risk.
*/
#ifndef _COMPRESSOR_H
#define _COMPRESSOR_H
#include <arm_math.h> //ARM DSP extensions. https://www.keil.com/pack/doc/CMSIS/DSP/html/index.html
#include "synth.h"
class Compressor
{
public:
//constructor
Compressor(const float32_t sample_rate_Hz);
void doCompression(float32_t *audio_block, uint16_t len);
void setDefaultValues(const float32_t sample_rate_Hz);
void setPreGain(float32_t g);
void setPreGain_dB(float32_t gain_dB);
void setCompressionRatio(float32_t cr);
void setAttack_sec(float32_t a, float32_t fs_Hz);
void setRelease_sec(float32_t r, float32_t fs_Hz);
void setLevelTimeConst_sec(float32_t t_sec, float32_t fs_Hz);
void setThresh_dBFS(float32_t val);
void enableHPFilter(boolean flag);
float32_t getPreGain_dB(void);
float32_t getAttack_sec(void);
float32_t getRelease_sec(void);
float32_t getLevelTimeConst_sec(void);
float32_t getThresh_dBFS(void);
float32_t getCompressionRatio(void);
float32_t getCurrentLevel_dBFS(void);
float32_t getCurrentGain_dB(void);
protected:
void calcAudioLevel_dB(float32_t *wav_block, float32_t *level_dB_block, uint16_t len);
void calcGain(float32_t *audio_level_dB_block, float32_t *gain_block,uint16_t len);
void calcInstantaneousTargetGain(float32_t *audio_level_dB_block, float32_t *inst_targ_gain_dB_block, uint16_t len);
void calcSmoothedGain_dB(float32_t *inst_targ_gain_dB_block, float32_t *gain_dB_block, uint16_t len);
void resetStates(void);
void setHPFilterCoeff_N2IIR_Matlab(float32_t b[], float32_t a[]);
//state-related variables
float32_t *inputQueueArray_f32[1]; //memory pointer for the input to this module
float32_t prev_level_lp_pow = 1.0;
float32_t prev_gain_dB = 0.0; //last gain^2 used
//HP filter state-related variables
arm_biquad_casd_df1_inst_f32 hp_filt_struct;
static const uint8_t hp_nstages = 1;
float32_t hp_coeff[5 * hp_nstages] = {1.0, 0.0, 0.0, 0.0, 0.0}; //no filtering. actual filter coeff set later
float32_t hp_state[4 * hp_nstages];
void setHPFilterCoeff(void);
//private parameters related to gain calculation
float32_t attack_const, release_const, level_lp_const; //used in calcGain(). set by setAttack_sec() and setRelease_sec();
float32_t comp_ratio_const, thresh_pow_FS_wCR; //used in calcGain(); set in updateThresholdAndCompRatioConstants()
void updateThresholdAndCompRatioConstants(void);
//settings
float32_t attack_sec, release_sec, level_lp_sec;
float32_t thresh_dBFS = 0.0; //threshold for compression, relative to digital full scale
float32_t thresh_pow_FS = 1.0f; //same as above, but not in dB
void setThreshPow(float32_t t_pow);
float32_t comp_ratio = 1.0; //compression ratio
float32_t pre_gain = -1.0; //gain to apply before the compression. negative value disables
boolean use_HP_prefilter;
};
// Accelerate the powf(10.0,x) function
static float32_t pow10f(float32_t x);
// Accelerate the log10f(x) function?
static float32_t log10f_approx(float32_t x);
static float32_t log2f_approx(float32_t X);
#endif

@ -0,0 +1,173 @@
// Taken from https://github.com/manicken/Audio/tree/templateMixer
// Adapted for MiniDexed by Holger Wirtz <dcoredump@googlemail.com>
#ifndef effect_mixer_h_
#define effect_mixer_h_
#include <cstdint>
#include <assert.h>
#include "arm_math.h"
#define UNITY_GAIN 1.0f
#define MAX_GAIN 1.0f
#define MIN_GAIN 0.0f
#define UNITY_PANORAMA 1.0f
#define MAX_PANORAMA 1.0f
#define MIN_PANORAMA 0.0f
template <int NN> class AudioMixer
{
public:
AudioMixer(uint16_t len)
{
buffer_length=len;
for (uint8_t i=0; i<NN; i++)
multiplier[i] = UNITY_GAIN;
sumbufL=new float32_t[buffer_length];
arm_fill_f32(0.0f, sumbufL, len);
}
~AudioMixer()
{
delete [] sumbufL;
}
void doAddMix(uint8_t channel, float32_t* in)
{
float32_t tmp[buffer_length];
assert(in);
if(multiplier[channel]!=UNITY_GAIN)
arm_scale_f32(in,multiplier[channel],tmp,buffer_length);
arm_add_f32(sumbufL, tmp, sumbufL, buffer_length);
}
void gain(uint8_t channel, float32_t gain)
{
if (channel >= NN) return;
if (gain > MAX_GAIN)
gain = MAX_GAIN;
else if (gain < MIN_GAIN)
gain = MIN_GAIN;
multiplier[channel] = gain;
}
void gain(float32_t gain)
{
for (uint8_t i = 0; i < NN; i++)
{
if (gain > MAX_GAIN)
gain = MAX_GAIN;
else if (gain < MIN_GAIN)
gain = MIN_GAIN;
multiplier[i] = gain;
}
}
void getMix(float32_t* buffer)
{
assert(buffer);
assert(sumbufL);
arm_copy_f32(sumbufL, buffer, buffer_length);
if(sumbufL)
arm_fill_f32(0.0f, sumbufL, buffer_length);
}
protected:
float32_t multiplier[NN];
float32_t* sumbufL;
uint16_t buffer_length;
};
template <int NN> class AudioStereoMixer : public AudioMixer<NN>
{
public:
AudioStereoMixer(uint16_t len) : AudioMixer<NN>(len)
{
for (uint8_t i=0; i<NN; i++)
panorama[i] = UNITY_PANORAMA;
sumbufR=new float32_t[buffer_length];
arm_fill_f32(0.0f, sumbufR, buffer_length);
}
~AudioStereoMixer()
{
delete [] sumbufR;
}
void pan(uint8_t channel, float32_t pan)
{
if (channel >= NN) return;
if (pan > MAX_PANORAMA)
pan = MAX_PANORAMA;
else if (pan < MIN_PANORAMA)
pan = MIN_PANORAMA;
panorama[channel] = pan;
}
void doAddMix(uint8_t channel, float32_t* in)
{
float32_t tmp[buffer_length];
assert(in);
// left
arm_scale_f32(in, 1.0f-panorama[channel], tmp, buffer_length);
if(multiplier[channel]!=UNITY_GAIN)
arm_scale_f32(tmp,multiplier[channel],tmp,buffer_length);
arm_add_f32(sumbufL, tmp, sumbufL, buffer_length);
// right
arm_scale_f32(in, panorama[channel], tmp, buffer_length);
if(multiplier[channel]!=UNITY_GAIN)
arm_scale_f32(tmp,multiplier[channel],tmp,buffer_length);
arm_add_f32(sumbufR, tmp, sumbufR, buffer_length);
}
void doAddMix(uint8_t channel, float32_t* inL, float32_t* inR)
{
float32_t tmp[buffer_length];
assert(inL);
assert(inR);
// left
if(multiplier[channel]!=UNITY_GAIN)
arm_scale_f32(inL,multiplier[channel],tmp,buffer_length);
arm_add_f32(sumbufL, tmp, sumbufL, buffer_length);
// right
if(multiplier[channel]!=UNITY_GAIN)
arm_scale_f32(inR,multiplier[channel],tmp,buffer_length);
arm_add_f32(sumbufR, tmp, sumbufR, buffer_length);
}
void getMix(float32_t* bufferL, float32_t* bufferR)
{
assert(bufferR);
assert(bufferL);
assert(sumbufL);
assert(sumbufR);
arm_copy_f32 (sumbufL, bufferL, buffer_length);
arm_copy_f32 (sumbufR, bufferR, buffer_length);
if(sumbufL)
arm_fill_f32(0.0f, sumbufL, buffer_length);
if(sumbufR)
arm_fill_f32(0.0f, sumbufR, buffer_length);
}
protected:
using AudioMixer<NN>::sumbufL;
using AudioMixer<NN>::multiplier;
using AudioMixer<NN>::buffer_length;
float32_t panorama[NN];
float32_t* sumbufR;
};
#endif

@ -41,7 +41,6 @@
* *
*/ */
#pragma once
#ifndef _EFFECT_PLATERVBSTEREO_H #ifndef _EFFECT_PLATERVBSTEREO_H
#define _EFFECT_PLATERVBSTEREO_H #define _EFFECT_PLATERVBSTEREO_H

@ -58,7 +58,6 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
m_nProgram[i] = 0; m_nProgram[i] = 0;
m_nVolume[i] = 100; m_nVolume[i] = 100;
m_nPan[i] = 64; m_nPan[i] = 64;
m_fPan[i] = 0.5f;
m_nMasterTune[i] = 0; m_nMasterTune[i] = 0;
m_nMIDIChannel[i] = CMIDIDevice::Disabled; m_nMIDIChannel[i] = CMIDIDevice::Disabled;
@ -66,6 +65,8 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
m_nNoteLimitHigh[i] = 127; m_nNoteLimitHigh[i] = 127;
m_nNoteShift[i] = 0; m_nNoteShift[i] = 0;
m_nReverbSend[i] = 0;
m_pTG[i] = new CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()); m_pTG[i] = new CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ());
assert (m_pTG[i]); assert (m_pTG[i]);
@ -114,7 +115,12 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
} }
#endif #endif
// BEGIN setup tg_mixer
tg_mixer = new AudioStereoMixer<CConfig::ToneGenerators>(pConfig->GetChunkSize()/2);
// END setup tgmixer
// BEGIN setup reverb // BEGIN setup reverb
reverb_send_mixer = new AudioStereoMixer<CConfig::ToneGenerators>(pConfig->GetChunkSize()/2);
reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate()); reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate());
SetParameter (ParameterReverbEnable, 1); SetParameter (ParameterReverbEnable, 1);
SetParameter (ParameterReverbSize, 70); SetParameter (ParameterReverbSize, 70);
@ -122,7 +128,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
SetParameter (ParameterReverbLowDamp, 50); SetParameter (ParameterReverbLowDamp, 50);
SetParameter (ParameterReverbLowPass, 30); SetParameter (ParameterReverbLowPass, 30);
SetParameter (ParameterReverbDiffusion, 65); SetParameter (ParameterReverbDiffusion, 65);
SetParameter (ParameterReverbLevel, 80); SetParameter (ParameterReverbLevel, 99);
// END setup reverb // END setup reverb
SetParameter (ParameterCompressorEnable, 1); SetParameter (ParameterCompressorEnable, 1);
@ -158,6 +164,11 @@ bool CMiniDexed::Initialize (void)
m_pTG[i]->setPBController (12, 1); m_pTG[i]->setPBController (12, 1);
m_pTG[i]->setMWController (99, 7, 0); m_pTG[i]->setMWController (99, 7, 0);
tg_mixer->pan(i,mapfloat(m_nPan[i],0,127,0.0f,1.0f));
tg_mixer->gain(i,1.0f);
reverb_send_mixer->pan(i,mapfloat(m_nPan[i],0,127,0.0f,1.0f));
reverb_send_mixer->gain(i,mapfloat(m_nReverbSend[i],0,99,0.0f,1.0f));
} }
if (m_PerformanceConfig.Load ()) if (m_PerformanceConfig.Load ())
@ -174,6 +185,8 @@ bool CMiniDexed::Initialize (void)
m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG); m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG);
m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG); m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG);
m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG); m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG);
SetReverbSend (m_PerformanceConfig.GetReverbSend (nTG), nTG);
} }
// Effects // Effects
@ -314,10 +327,7 @@ CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void)
void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG) void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG)
{ {
if (nBankLSB > 127) nBankLSB=constrain((int)nBankLSB,0,127);
{
return;
}
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
m_nVoiceBankID[nTG] = nBankLSB; m_nVoiceBankID[nTG] = nBankLSB;
@ -327,10 +337,7 @@ void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG)
void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG)
{ {
if (nProgram > 31) nProgram=constrain((int)nProgram,0,31);
{
return;
}
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
m_nProgram[nTG] = nProgram; m_nProgram[nTG] = nProgram;
@ -346,40 +353,45 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG)
void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG)
{ {
if (nVolume > 127) nVolume=constrain((int)nVolume,0,127);
{
return;
}
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
m_nVolume[nTG] = nVolume; m_nVolume[nTG] = nVolume;
assert (m_pTG[nTG]); assert (m_pTG[nTG]);
m_pTG[nTG]->setGain (nVolume / 127.0); m_pTG[nTG]->setGain (nVolume / 127.0f);
m_UI.ParameterChanged (); m_UI.ParameterChanged ();
} }
void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) void CMiniDexed::SetPan (unsigned nPan, unsigned nTG)
{ {
if (nPan > 127) nPan=constrain((int)nPan,0,127);
{
return;
}
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
m_nPan[nTG] = nPan; m_nPan[nTG] = nPan;
m_fPan[nTG]=mapfloat(nPan,0,127,0.0,1.0);
tg_mixer->pan(nTG,mapfloat(nPan,0,127,0.0f,1.0f));
reverb_send_mixer->pan(nTG,mapfloat(nPan,0,127,0.0f,1.0f));
m_UI.ParameterChanged ();
}
void CMiniDexed::SetReverbSend (unsigned nReverbSend, unsigned nTG)
{
nReverbSend=constrain((int)nReverbSend,0,99);
assert (nTG < CConfig::ToneGenerators);
m_nReverbSend[nTG] = nReverbSend;
reverb_send_mixer->gain(nTG,mapfloat(nReverbSend,0,99,0.0f,1.0f));
m_UI.ParameterChanged (); m_UI.ParameterChanged ();
} }
void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG) void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG)
{ {
if (!(-99 <= nMasterTune && nMasterTune <= 99)) nMasterTune=constrain((int)nMasterTune,-99,99);
{
return;
}
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
m_nMasterTune[nTG] = nMasterTune; m_nMasterTune[nTG] = nMasterTune;
@ -517,44 +529,51 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue)
break; break;
case ParameterReverbEnable: case ParameterReverbEnable:
nValue=constrain((int)nValue,0,1);
m_ReverbSpinLock.Acquire (); m_ReverbSpinLock.Acquire ();
reverb->set_bypass (!nValue); reverb->set_bypass (!nValue);
m_ReverbSpinLock.Release (); m_ReverbSpinLock.Release ();
break; break;
case ParameterReverbSize: case ParameterReverbSize:
nValue=constrain((int)nValue,0,99);
m_ReverbSpinLock.Acquire (); m_ReverbSpinLock.Acquire ();
reverb->size (nValue / 99.0); reverb->size (nValue / 99.0f);
m_ReverbSpinLock.Release (); m_ReverbSpinLock.Release ();
break; break;
case ParameterReverbHighDamp: case ParameterReverbHighDamp:
nValue=constrain((int)nValue,0,99);
m_ReverbSpinLock.Acquire (); m_ReverbSpinLock.Acquire ();
reverb->hidamp (nValue / 99.0); reverb->hidamp (nValue / 99.0f);
m_ReverbSpinLock.Release (); m_ReverbSpinLock.Release ();
break; break;
case ParameterReverbLowDamp: case ParameterReverbLowDamp:
nValue=constrain((int)nValue,0,99);
m_ReverbSpinLock.Acquire (); m_ReverbSpinLock.Acquire ();
reverb->lodamp (nValue / 99.0); reverb->lodamp (nValue / 99.0f);
m_ReverbSpinLock.Release (); m_ReverbSpinLock.Release ();
break; break;
case ParameterReverbLowPass: case ParameterReverbLowPass:
nValue=constrain((int)nValue,0,99);
m_ReverbSpinLock.Acquire (); m_ReverbSpinLock.Acquire ();
reverb->lowpass (nValue / 99.0); reverb->lowpass (nValue / 99.0f);
m_ReverbSpinLock.Release (); m_ReverbSpinLock.Release ();
break; break;
case ParameterReverbDiffusion: case ParameterReverbDiffusion:
nValue=constrain((int)nValue,0,99);
m_ReverbSpinLock.Acquire (); m_ReverbSpinLock.Acquire ();
reverb->diffusion (nValue / 99.0); reverb->diffusion (nValue / 99.0f);
m_ReverbSpinLock.Release (); m_ReverbSpinLock.Release ();
break; break;
case ParameterReverbLevel: case ParameterReverbLevel:
nValue=constrain((int)nValue,0,99);
m_ReverbSpinLock.Acquire (); m_ReverbSpinLock.Acquire ();
reverb->level (nValue / 99.0); reverb->level (nValue / 99.0f);
m_ReverbSpinLock.Release (); m_ReverbSpinLock.Release ();
break; break;
@ -587,6 +606,8 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT
SetMIDIChannel ((uint8_t) nValue, nTG); SetMIDIChannel ((uint8_t) nValue, nTG);
break; break;
case TGParameterReverbSend: SetReverbSend (nValue, nTG); break;
default: default:
assert (0); assert (0);
break; break;
@ -605,6 +626,7 @@ int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG)
case TGParameterPan: return m_nPan[nTG]; case TGParameterPan: return m_nPan[nTG];
case TGParameterMasterTune: return m_nMasterTune[nTG]; case TGParameterMasterTune: return m_nMasterTune[nTG];
case TGParameterMIDIChannel: return m_nMIDIChannel[nTG]; case TGParameterMIDIChannel: return m_nMIDIChannel[nTG];
case TGParameterReverbSend: return m_nReverbSend[nTG];
default: default:
assert (0); assert (0);
@ -677,15 +699,9 @@ void CMiniDexed::ProcessSound (void)
float32_t SampleBuffer[nFrames]; float32_t SampleBuffer[nFrames];
m_pTG[0]->getSamples (SampleBuffer, nFrames); m_pTG[0]->getSamples (SampleBuffer, nFrames);
// Convert dual float array (left, right) to single int16 array (left/right) // Convert single float array (mono) to int16 array
float32_t tmp_float[nFrames*2]; int16_t tmp_int[nFrames];
int16_t tmp_int[nFrames*2]; arm_float_to_q15(SampleBuffer,tmp_int,nFrames);
for(uint16_t i=0; i<nFrames;i++)
{
tmp_float[i*2]=SampleBuffer[i];
tmp_float[(i*2)+1]=SampleBuffer[i];
}
arm_float_to_q15(tmp_float,tmp_int,nFrames*2);
if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int))
{ {
@ -743,59 +759,55 @@ void CMiniDexed::ProcessSound (void)
// Audio signal path after tone generators starts here // Audio signal path after tone generators starts here
// //
// now mix the output of all TGs assert (CConfig::ToneGenerators == 8);
float32_t SampleBuffer[2][nFrames];
uint8_t indexL=0, indexR=1;
// swap stereo channels if needed
uint8_t indexL=0, indexR=1;
if (m_bChannelsSwapped) if (m_bChannelsSwapped)
{ {
indexL=1; indexL=1;
indexR=0; indexR=0;
} }
// init left sum output
assert (SampleBuffer[0]!=NULL);
arm_fill_f32(0.0, SampleBuffer[0], nFrames);
// init right sum output
assert (SampleBuffer[1]!=NULL);
arm_fill_f32(0.0, SampleBuffer[1], nFrames);
assert (CConfig::ToneGenerators == 8); // BEGIN TG mixing
// BEGIN stereo panorama
for (uint8_t i = 0; i < CConfig::ToneGenerators; i++) for (uint8_t i = 0; i < CConfig::ToneGenerators; i++)
{ {
float32_t tmpBuffer[nFrames]; tg_mixer->doAddMix(i,m_OutputLevel[i]);
reverb_send_mixer->doAddMix(i,m_OutputLevel[i]);
m_PanoramaSpinLock.Acquire ();
// calculate left panorama of this TG
arm_scale_f32(m_OutputLevel[i], 1.0f-m_fPan[i], tmpBuffer, nFrames);
// add left panorama output of this TG to sum output
arm_add_f32(SampleBuffer[indexL], tmpBuffer, SampleBuffer[indexL], nFrames);
// calculate right panorama of this TG
arm_scale_f32(m_OutputLevel[i], m_fPan[i], tmpBuffer, nFrames);
// add right panaorama output of this TG to sum output
arm_add_f32(SampleBuffer[indexR], tmpBuffer, SampleBuffer[indexR], nFrames);
m_PanoramaSpinLock.Release ();
} }
// END stereo panorama // END TG mixing
// BEGIN create SampleBuffer for holding audio data
float32_t SampleBuffer[2][nFrames];
// END create SampleBuffer for holding audio data
// get the mix of all TGs
tg_mixer->getMix(SampleBuffer[indexL], SampleBuffer[indexR]);
// BEGIN adding reverb // BEGIN adding reverb
if (m_nParameter[ParameterReverbEnable]) if (m_nParameter[ParameterReverbEnable])
{ {
float32_t ReverbBuffer[2][nFrames]; float32_t ReverbBuffer[2][nFrames];
float32_t ReverbSendBuffer[2][nFrames];
arm_fill_f32(0.0f, ReverbBuffer[indexL], nFrames);
arm_fill_f32(0.0f, ReverbBuffer[indexR], nFrames);
arm_fill_f32(0.0f, ReverbSendBuffer[indexR], nFrames);
arm_fill_f32(0.0f, ReverbSendBuffer[indexL], nFrames);
m_ReverbSpinLock.Acquire (); m_ReverbSpinLock.Acquire ();
reverb->doReverb(SampleBuffer[indexL],SampleBuffer[indexR],ReverbBuffer[0], ReverbBuffer[1],nFrames);
m_ReverbSpinLock.Release (); reverb_send_mixer->getMix(ReverbSendBuffer[indexL], ReverbSendBuffer[indexR]);
reverb->doReverb(ReverbSendBuffer[indexL],ReverbSendBuffer[indexR],ReverbBuffer[indexL], ReverbBuffer[indexR],nFrames);
// scale down and add left reverb buffer by reverb level // scale down and add left reverb buffer by reverb level
arm_scale_f32(ReverbBuffer[0], reverb->get_level(), ReverbBuffer[0], nFrames); arm_scale_f32(ReverbBuffer[indexL], reverb->get_level(), ReverbBuffer[indexL], nFrames);
arm_add_f32(SampleBuffer[indexL], ReverbBuffer[0], SampleBuffer[indexL], nFrames); arm_add_f32(SampleBuffer[indexL], ReverbBuffer[indexL], SampleBuffer[indexL], nFrames);
// scale down and add right reverb buffer by reverb level // scale down and add right reverb buffer by reverb level
arm_scale_f32(ReverbBuffer[1], reverb->get_level(), ReverbBuffer[1], nFrames); arm_scale_f32(ReverbBuffer[indexR], reverb->get_level(), ReverbBuffer[indexR], nFrames);
arm_add_f32(SampleBuffer[indexR], ReverbBuffer[1], SampleBuffer[indexR], nFrames); arm_add_f32(SampleBuffer[indexR], ReverbBuffer[indexR], SampleBuffer[indexR], nFrames);
m_ReverbSpinLock.Release ();
} }
// END adding reverb // END adding reverb
@ -837,6 +849,8 @@ bool CMiniDexed::SavePerformance (void)
m_PerformanceConfig.SetNoteLimitLow (m_nNoteLimitLow[nTG], nTG); m_PerformanceConfig.SetNoteLimitLow (m_nNoteLimitLow[nTG], nTG);
m_PerformanceConfig.SetNoteLimitHigh (m_nNoteLimitHigh[nTG], nTG); m_PerformanceConfig.SetNoteLimitHigh (m_nNoteLimitHigh[nTG], nTG);
m_PerformanceConfig.SetNoteShift (m_nNoteShift[nTG], nTG); m_PerformanceConfig.SetNoteShift (m_nNoteShift[nTG], nTG);
m_PerformanceConfig.SetReverbSend (m_nReverbSend[nTG], nTG);
} }
m_PerformanceConfig.SetCompressorEnable (!!m_nParameter[ParameterCompressorEnable]); m_PerformanceConfig.SetCompressorEnable (!!m_nParameter[ParameterCompressorEnable]);

@ -1,853 +0,0 @@
//
// minidexed.cpp
//
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
// Copyright (C) 2022 The MiniDexed Team
//
// 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, see <http://www.gnu.org/licenses/>.
//
#include "minidexed.h"
#include <circle/logger.h>
#include <circle/memory.h>
#include <circle/pwmsoundbasedevice.h>
#include <circle/i2ssoundbasedevice.h>
#include <circle/hdmisoundbasedevice.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
LOGMODULE ("minidexed");
CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, FATFS *pFileSystem)
:
#ifdef ARM_ALLOW_MULTI_CORE
CMultiCoreSupport (CMemorySystem::Get ()),
#endif
m_pConfig (pConfig),
m_UI (this, pGPIOManager, pConfig),
m_PerformanceConfig (pFileSystem),
m_PCKeyboard (this, pConfig),
m_SerialMIDI (this, pInterrupt, pConfig),
m_bUseSerial (false),
m_pSoundDevice (0),
m_bChannelsSwapped (pConfig->GetChannelsSwapped ()),
#ifdef ARM_ALLOW_MULTI_CORE
m_nActiveTGsLog2 (0),
#endif
m_GetChunkTimer ("GetChunk",
1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()),
m_bProfileEnabled (m_pConfig->GetProfileEnabled ())
{
assert (m_pConfig);
for (unsigned i = 0; i < CConfig::ToneGenerators; i++)
{
m_nVoiceBankID[i] = 0;
m_nProgram[i] = 0;
m_nVolume[i] = 100;
m_nPan[i] = 64;
pan_float[i]=0.0f;
m_nMasterTune[i] = 0;
m_nMIDIChannel[i] = CMIDIDevice::Disabled;
m_nNoteLimitLow[i] = 0;
m_nNoteLimitHigh[i] = 127;
m_nNoteShift[i] = 0;
m_pTG[i] = new CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ());
assert (m_pTG[i]);
m_pTG[i]->activate ();
}
for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++)
{
m_pMIDIKeyboard[i] = new CMIDIKeyboard (this, pConfig, i);
assert (m_pMIDIKeyboard[i]);
}
// select the sound device
const char *pDeviceName = pConfig->GetSoundDevice ();
if (strcmp (pDeviceName, "i2s") == 0)
{
LOGNOTE ("I2S mode");
m_pSoundDevice = new CI2SSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (),
pConfig->GetChunkSize (), false,
pI2CMaster, pConfig->GetDACI2CAddress ());
}
else if (strcmp (pDeviceName, "hdmi") == 0)
{
LOGNOTE ("HDMI mode");
m_pSoundDevice = new CHDMISoundBaseDevice (pInterrupt, pConfig->GetSampleRate (),
pConfig->GetChunkSize ());
// The channels are swapped by default in the HDMI sound driver.
// TODO: Remove this line, when this has been fixed in the driver.
m_bChannelsSwapped = !m_bChannelsSwapped;
}
else
{
LOGNOTE ("PWM mode");
m_pSoundDevice = new CPWMSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (),
pConfig->GetChunkSize ());
}
#ifdef ARM_ALLOW_MULTI_CORE
for (unsigned nCore = 0; nCore < CORES; nCore++)
{
m_CoreStatus[nCore] = CoreStatusInit;
}
#endif
// BEGIN setup tg_mixer
//tg_mixer = new AudioStereoMixer<8>();
// END setup tg_mixer
SetParameter (ParameterCompressorEnable, 1);
// BEGIN setup reverb
reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate());
SetParameter (ParameterReverbEnable, 1);
SetParameter (ParameterReverbSize, 70);
SetParameter (ParameterReverbHighDamp, 50);
SetParameter (ParameterReverbLowDamp, 50);
SetParameter (ParameterReverbLowPass, 30);
SetParameter (ParameterReverbDiffusion, 20);
SetParameter (ParameterReverbLevel, 80);
// END setup reverb
};
bool CMiniDexed::Initialize (void)
{
assert (m_pConfig);
assert (m_pSoundDevice);
if (!m_UI.Initialize ())
{
return false;
}
m_SysExFileLoader.Load ();
if (m_SerialMIDI.Initialize ())
{
LOGNOTE ("Serial MIDI interface enabled");
m_bUseSerial = true;
}
for (unsigned i = 0; i < CConfig::ToneGenerators; i++)
{
assert (m_pTG[i]);
SetVolume (100, i);
ProgramChange (0, i);
m_pTG[i]->setTranspose (24);
m_pTG[i]->setPBController (12, 1);
m_pTG[i]->setMWController (99, 7, 0);
}
if (m_PerformanceConfig.Load ())
{
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
{
BankSelectLSB (m_PerformanceConfig.GetBankNumber (nTG), nTG);
ProgramChange (m_PerformanceConfig.GetVoiceNumber (nTG), nTG);
SetMIDIChannel (m_PerformanceConfig.GetMIDIChannel (nTG), nTG);
SetVolume (m_PerformanceConfig.GetVolume (nTG), nTG);
SetPan (m_PerformanceConfig.GetPan (nTG), nTG);
SetMasterTune (m_PerformanceConfig.GetDetune (nTG), nTG);
m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG);
m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG);
m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG);
}
// Effects
SetParameter (ParameterCompressorEnable, m_PerformanceConfig.GetCompressorEnable () ? 1 : 0);
SetParameter (ParameterReverbEnable, m_PerformanceConfig.GetReverbEnable () ? 1 : 0);
SetParameter (ParameterReverbSize, m_PerformanceConfig.GetReverbSize ());
SetParameter (ParameterReverbHighDamp, m_PerformanceConfig.GetReverbHighDamp ());
SetParameter (ParameterReverbLowDamp, m_PerformanceConfig.GetReverbLowDamp ());
SetParameter (ParameterReverbLowPass, m_PerformanceConfig.GetReverbLowPass ());
SetParameter (ParameterReverbDiffusion, m_PerformanceConfig.GetReverbDiffusion ());
SetParameter (ParameterReverbLevel, m_PerformanceConfig.GetReverbLevel ());
}
else
{
SetMIDIChannel (CMIDIDevice::OmniMode, 0);
}
// setup and start the sound device
if (!m_pSoundDevice->AllocateQueueFrames (m_pConfig->GetChunkSize ()))
{
LOGERR ("Cannot allocate sound queue");
return false;
}
#ifndef ARM_ALLOW_MULTI_CORE
m_pSoundDevice->SetWriteFormat (SoundFormatSigned16, 1); // 16-bit Mono
#else
m_pSoundDevice->SetWriteFormat (SoundFormatSigned16, 2); // 16-bit Stereo
#endif
m_nQueueSizeFrames = m_pSoundDevice->GetQueueSizeFrames ();
m_pSoundDevice->Start ();
#ifdef ARM_ALLOW_MULTI_CORE
// start secondary cores
if (!CMultiCoreSupport::Initialize ())
{
return false;
}
#endif
return true;
}
void CMiniDexed::Process (bool bPlugAndPlayUpdated)
{
#ifndef ARM_ALLOW_MULTI_CORE
ProcessSound ();
#endif
for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++)
{
assert (m_pMIDIKeyboard[i]);
m_pMIDIKeyboard[i]->Process (bPlugAndPlayUpdated);
}
m_PCKeyboard.Process (bPlugAndPlayUpdated);
if (m_bUseSerial)
{
m_SerialMIDI.Process ();
}
m_UI.Process ();
if (m_bProfileEnabled)
{
m_GetChunkTimer.Dump ();
}
}
#ifdef ARM_ALLOW_MULTI_CORE
void CMiniDexed::Run (unsigned nCore)
{
assert (1 <= nCore && nCore < CORES);
if (nCore == 1)
{
m_CoreStatus[nCore] = CoreStatusIdle; // core 1 ready
// wait for cores 2 and 3 to be ready
for (unsigned nCore = 2; nCore < CORES; nCore++)
{
while (m_CoreStatus[nCore] != CoreStatusIdle)
{
// just wait
}
}
while (m_CoreStatus[nCore] != CoreStatusExit)
{
ProcessSound ();
}
}
else // core 2 and 3
{
while (1)
{
m_CoreStatus[nCore] = CoreStatusIdle; // ready to be kicked
while (m_CoreStatus[nCore] == CoreStatusIdle)
{
// just wait
}
// now kicked from core 1
if (m_CoreStatus[nCore] == CoreStatusExit)
{
m_CoreStatus[nCore] = CoreStatusUnknown;
break;
}
assert (m_CoreStatus[nCore] == CoreStatusBusy);
// process the TGs, assigned to this core (2 or 3)
assert (m_nFramesToProcess <= CConfig::MaxChunkSize);
unsigned nTG = CConfig::TGsCore1 + (nCore-2)*CConfig::TGsCore23;
for (unsigned i = 0; i < CConfig::TGsCore23; i++, nTG++)
{
assert (m_pTG[nTG]);
m_pTG[nTG]->getSamples (m_OutputLevel[nTG], m_nFramesToProcess);
}
}
}
}
#endif
CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void)
{
return &m_SysExFileLoader;
}
void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG)
{
if (nBankLSB > 127)
{
return;
}
assert (nTG < CConfig::ToneGenerators);
m_nVoiceBankID[nTG] = nBankLSB;
m_UI.ParameterChanged ();
}
void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG)
{
if (nProgram > 31)
{
return;
}
assert (nTG < CConfig::ToneGenerators);
m_nProgram[nTG] = nProgram;
uint8_t Buffer[156];
m_SysExFileLoader.GetVoice (m_nVoiceBankID[nTG], nProgram, Buffer);
assert (m_pTG[nTG]);
m_pTG[nTG]->loadVoiceParameters (Buffer);
m_UI.ParameterChanged ();
}
void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG)
{
if (nVolume > 127)
{
return;
}
assert (nTG < CConfig::ToneGenerators);
m_nVolume[nTG] = nVolume;
assert (m_pTG[nTG]);
m_pTG[nTG]->setGain (nVolume / 127.0);
m_UI.ParameterChanged ();
}
void CMiniDexed::SetPan (unsigned nPan, unsigned nTG)
{
constrain(nPan,-1.0f,1.0f);
assert (nTG < CConfig::ToneGenerators);
m_nPan[nTG] = nPan;
pan_float[nTG]=mapfloat(nPan,0,127,-1.0,1.0);
m_UI.ParameterChanged ();
}
void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG)
{
if (!(-99 <= nMasterTune && nMasterTune <= 99))
{
return;
}
assert (nTG < CConfig::ToneGenerators);
m_nMasterTune[nTG] = nMasterTune;
assert (m_pTG[nTG]);
m_pTG[nTG]->setMasterTune ((int8_t) nMasterTune);
m_UI.ParameterChanged ();
}
void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
m_nMIDIChannel[nTG] = uchChannel;
for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++)
{
assert (m_pMIDIKeyboard[i]);
m_pMIDIKeyboard[i]->SetChannel (uchChannel, nTG);
}
m_PCKeyboard.SetChannel (uchChannel, nTG);
if (m_bUseSerial)
{
m_SerialMIDI.SetChannel (uchChannel, nTG);
}
#ifdef ARM_ALLOW_MULTI_CORE
unsigned nActiveTGs = 0;
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
{
if (m_nMIDIChannel[nTG] != CMIDIDevice::Disabled)
{
nActiveTGs++;
}
}
assert (nActiveTGs <= 8);
static const unsigned Log2[] = {0, 0, 1, 2, 2, 3, 3, 3, 3};
m_nActiveTGsLog2 = Log2[nActiveTGs];
#endif
m_UI.ParameterChanged ();
}
void CMiniDexed::keyup (int16_t pitch, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
assert (m_pTG[nTG]);
pitch = ApplyNoteLimits (pitch, nTG);
if (pitch >= 0)
{
m_pTG[nTG]->keyup (pitch);
}
}
void CMiniDexed::keydown (int16_t pitch, uint8_t velocity, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
assert (m_pTG[nTG]);
pitch = ApplyNoteLimits (pitch, nTG);
if (pitch >= 0)
{
m_pTG[nTG]->keydown (pitch, velocity);
}
}
int16_t CMiniDexed::ApplyNoteLimits (int16_t pitch, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
if ( pitch < (int16_t) m_nNoteLimitLow[nTG]
|| pitch > (int16_t) m_nNoteLimitHigh[nTG])
{
return -1;
}
pitch += m_nNoteShift[nTG];
if ( pitch < 0
|| pitch > 127)
{
return -1;
}
return pitch;
}
void CMiniDexed::setSustain(bool sustain, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
assert (m_pTG[nTG]);
m_pTG[nTG]->setSustain (sustain);
}
void CMiniDexed::setModWheel (uint8_t value, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
assert (m_pTG[nTG]);
m_pTG[nTG]->setModWheel (value);
}
void CMiniDexed::setPitchbend (int16_t value, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
assert (m_pTG[nTG]);
m_pTG[nTG]->setPitchbend (value);
}
void CMiniDexed::ControllersRefresh (unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
assert (m_pTG[nTG]);
m_pTG[nTG]->ControllersRefresh ();
}
void CMiniDexed::SetParameter (TParameter Parameter, int nValue)
{
assert (reverb);
assert (Parameter < ParameterUnknown);
m_nParameter[Parameter] = nValue;
switch (Parameter)
{
<<<<<<< HEAD
case ParameterReverbSize: reverb->size (fValue); break;
case ParameterReverbHighDamp: reverb->hidamp (fValue); break;
case ParameterReverbLowDamp: reverb->lodamp (fValue); break;
case ParameterReverbLowPass: reverb->lowpass (fValue); break;
case ParameterReverbDiffusion: reverb->diffusion (fValue); break;
case ParameterReverbLevel: reverb->level (fValue); break;
=======
case ParameterCompressorEnable:
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
{
assert (m_pTG[nTG]);
m_pTG[nTG]->setCompressor (!!nValue);
}
break;
case ParameterReverbEnable:
m_ReverbSpinLock.Acquire ();
reverb->set_bypass (!nValue);
m_ReverbSpinLock.Release ();
break;
case ParameterReverbSize:
m_ReverbSpinLock.Acquire ();
reverb->size (nValue / 99.0);
m_ReverbSpinLock.Release ();
break;
case ParameterReverbHighDamp:
m_ReverbSpinLock.Acquire ();
reverb->hidamp (nValue / 99.0);
m_ReverbSpinLock.Release ();
break;
case ParameterReverbLowDamp:
m_ReverbSpinLock.Acquire ();
reverb->lodamp (nValue / 99.0);
m_ReverbSpinLock.Release ();
break;
case ParameterReverbLowPass:
m_ReverbSpinLock.Acquire ();
reverb->lowpass (nValue / 99.0);
m_ReverbSpinLock.Release ();
break;
case ParameterReverbDiffusion:
m_ReverbSpinLock.Acquire ();
reverb->diffusion (nValue / 99.0);
m_ReverbSpinLock.Release ();
break;
case ParameterReverbLevel:
m_ReverbSpinLock.Acquire ();
reverb->level (nValue / 99.0);
m_ReverbSpinLock.Release ();
break;
default:
assert (0);
break;
}
}
int CMiniDexed::GetParameter (TParameter Parameter)
{
assert (Parameter < ParameterUnknown);
return m_nParameter[Parameter];
}
void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
switch (Parameter)
{
case TGParameterVoiceBank: BankSelectLSB (nValue, nTG); break;
case TGParameterProgram: ProgramChange (nValue, nTG); break;
case TGParameterVolume: SetVolume (nValue, nTG); break;
case TGParameterPan: SetPan (nValue, nTG); break;
case TGParameterMasterTune: SetMasterTune (nValue, nTG); break;
case TGParameterMIDIChannel:
assert (0 <= nValue && nValue <= 255);
SetMIDIChannel ((uint8_t) nValue, nTG);
break;
default:
assert (0);
break;
}
}
int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
switch (Parameter)
{
case TGParameterVoiceBank: return m_nVoiceBankID[nTG];
case TGParameterProgram: return m_nProgram[nTG];
case TGParameterVolume: return m_nVolume[nTG];
case TGParameterPan: return m_nPan[nTG];
case TGParameterMasterTune: return m_nMasterTune[nTG];
case TGParameterMIDIChannel: return m_nMIDIChannel[nTG];
default:
assert (0);
return 0;
}
}
void CMiniDexed::SetVoiceParameter (uint8_t uchOffset, uint8_t uchValue, unsigned nOP, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
assert (m_pTG[nTG]);
assert (nOP <= 6);
if (nOP < 6)
{
nOP = 5 - nOP; // OPs are in reverse order
}
uchOffset += nOP * 21;
assert (uchOffset < 156);
m_pTG[nTG]->setVoiceDataElement (uchOffset, uchValue);
}
uint8_t CMiniDexed::GetVoiceParameter (uint8_t uchOffset, unsigned nOP, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
assert (m_pTG[nTG]);
assert (nOP <= 6);
if (nOP < 6)
{
nOP = 5 - nOP; // OPs are in reverse order
}
uchOffset += nOP * 21;
assert (uchOffset < 156);
return m_pTG[nTG]->getVoiceDataElement (uchOffset);
}
std::string CMiniDexed::GetVoiceName (unsigned nTG)
{
char VoiceName[11];
memset (VoiceName, 0, sizeof VoiceName);
assert (nTG < CConfig::ToneGenerators);
assert (m_pTG[nTG]);
m_pTG[nTG]->setName (VoiceName);
std::string Result (VoiceName);
return Result;
}
#ifndef ARM_ALLOW_MULTI_CORE
void CMiniDexed::ProcessSound (void)
{
assert (m_pSoundDevice);
unsigned nFrames = m_nQueueSizeFrames - m_pSoundDevice->GetQueueFramesAvail ();
if (nFrames >= m_nQueueSizeFrames/2)
{
if (m_bProfileEnabled)
{
m_GetChunkTimer.Start ();
}
//int16_t SampleBuffer[nFrames]; // TODO float->int
m_pTG[0]->getSamples (SampleBuffer, nFrames);
if ( m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer)
!= (int) sizeof SampleBuffer)
{
LOGERR ("Sound data dropped");
}
if (m_bProfileEnabled)
{
m_GetChunkTimer.Stop ();
}
}
}
#else // #ifdef ARM_ALLOW_MULTI_CORE
void CMiniDexed::ProcessSound (void)
{
assert (m_pSoundDevice);
unsigned nFrames = m_nQueueSizeFrames - m_pSoundDevice->GetQueueFramesAvail ();
if (nFrames >= m_nQueueSizeFrames/2)
{
if (m_bProfileEnabled)
{
m_GetChunkTimer.Start ();
}
m_nFramesToProcess = nFrames;
// kick secondary cores
for (unsigned nCore = 2; nCore < CORES; nCore++)
{
assert (m_CoreStatus[nCore] == CoreStatusIdle);
m_CoreStatus[nCore] = CoreStatusBusy;
}
// process the TGs assigned to core 1
assert (nFrames <= CConfig::MaxChunkSize);
for (unsigned i = 0; i < CConfig::TGsCore1; i++)
{
assert (m_pTG[i]);
m_pTG[i]->getSamples (m_OutputLevel[i], nFrames);
}
// wait for cores 2 and 3 to complete their work
for (unsigned nCore = 2; nCore < CORES; nCore++)
{
while (m_CoreStatus[nCore] != CoreStatusIdle)
{
// just wait
}
}
//
// Audio signal path after tone generators starts here
//
// now mix the output of all TGs
float32_t SampleBuffer[2][nFrames];
uint8_t indexL=0, indexR=1;
if (m_bChannelsSwapped)
{
indexL=1;
indexR=0;
}
// init left sum output
assert (SampleBuffer[0]!=NULL);
arm_fill_f32(0.0, SampleBuffer[0], nFrames);
// init right sum output
assert (SampleBuffer[1]!=NULL);
arm_fill_f32(0.0, SampleBuffer[1], nFrames);
assert (CConfig::ToneGenerators == 8);
// BEGIN stereo panorama
for (uint8_t i = 0; i < CConfig::ToneGenerators; i++)
{
float32_t tmpBuffer[nFrames];
m_PanoramaSpinLock.Acquire ();
// calculate left panorama of this TG
arm_scale_f32(m_OutputLevel[i], 1.0f-pan_float[i], tmpBuffer, nFrames);
// add left panorama output of this TG to sum output
arm_add_f32(SampleBuffer[indexL], tmpBuffer, SampleBuffer[indexL], nFrames);
// calculate right panorama of this TG
arm_scale_f32(m_OutputLevel[i], pan_float[i], tmpBuffer, nFrames);
// add right panaorama output of this TG to sum output
arm_add_f32(SampleBuffer[indexR], tmpBuffer, SampleBuffer[indexR], nFrames);
m_PanoramaSpinLock.Release ();
}
// END stereo panorama
// BEGIN adding reverb
if (m_nParameter[ParameterReverbEnable])
{
float32_t ReverbBuffer[2][nFrames];
m_ReverbSpinLock.Acquire ();
reverb->doReverb(SampleBuffer[indexL],SampleBuffer[indexR],ReverbBuffer[0], ReverbBuffer[1],nFrames);
m_ReverbSpinLock.Release ();
// scale down and add left reverb buffer by reverb level
arm_scale_f32(ReverbBuffer[0], reverb->get_level(), ReverbBuffer[0], nFrames);
arm_add_f32(SampleBuffer[indexL], ReverbBuffer[0], SampleBuffer[indexL], nFrames);
// scale down and add right reverb buffer by reverb level
arm_scale_f32(ReverbBuffer[1], reverb->get_level(), ReverbBuffer[1], nFrames);
arm_add_f32(SampleBuffer[indexR], ReverbBuffer[1], SampleBuffer[indexR], nFrames);
}
// END adding reverb
// Convert dual float array (left, right) to single int16 array (left/right)
float32_t tmp_float[nFrames*2];
int16_t tmp_int[nFrames*2];
for(uint16_t i=0; i<nFrames;i++)
{
tmp_float[i*2]=SampleBuffer[indexL][i];
tmp_float[(i*2)+1]=SampleBuffer[indexR][i];
}
arm_float_to_q15(tmp_float,tmp_int,nFrames*2);
if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int))
{
LOGERR ("Sound data dropped");
}
if (m_bProfileEnabled)
{
m_GetChunkTimer.Stop ();
}
}
}
#endif
bool CMiniDexed::SavePerformance (void)
{
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
{
m_PerformanceConfig.SetBankNumber (m_nVoiceBankID[nTG], nTG);
m_PerformanceConfig.SetVoiceNumber (m_nProgram[nTG], nTG);
m_PerformanceConfig.SetMIDIChannel (m_nMIDIChannel[nTG], nTG);
m_PerformanceConfig.SetVolume (m_nVolume[nTG], nTG);
m_PerformanceConfig.SetPan (m_nPan[nTG], nTG);
m_PerformanceConfig.SetDetune (m_nMasterTune[nTG], nTG);
m_PerformanceConfig.SetNoteLimitLow (m_nNoteLimitLow[nTG], nTG);
m_PerformanceConfig.SetNoteLimitHigh (m_nNoteLimitHigh[nTG], nTG);
m_PerformanceConfig.SetNoteShift (m_nNoteShift[nTG], nTG);
}
m_PerformanceConfig.SetCompressorEnable (!!m_nParameter[ParameterCompressorEnable]);
m_PerformanceConfig.SetReverbEnable (!!m_nParameter[ParameterReverbEnable]);
m_PerformanceConfig.SetReverbSize (m_nParameter[ParameterReverbSize]);
m_PerformanceConfig.SetReverbHighDamp (m_nParameter[ParameterReverbHighDamp]);
m_PerformanceConfig.SetReverbLowDamp (m_nParameter[ParameterReverbLowDamp]);
m_PerformanceConfig.SetReverbLowPass (m_nParameter[ParameterReverbLowPass]);
m_PerformanceConfig.SetReverbDiffusion (m_nParameter[ParameterReverbDiffusion]);
m_PerformanceConfig.SetReverbLevel (m_nParameter[ParameterReverbLevel]);
return m_PerformanceConfig.Save ();
}

@ -40,8 +40,9 @@
#include <circle/soundbasedevice.h> #include <circle/soundbasedevice.h>
#include <circle/spinlock.h> #include <circle/spinlock.h>
#include "common.h" #include "common.h"
#include "effect_mixer.hpp"
#include "effect_platervbstereo.h" #include "effect_platervbstereo.h"
#include "mixer.h" #include "effect_compressor.h"
class CMiniDexed class CMiniDexed
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
@ -77,6 +78,8 @@ public:
void setPitchbend (int16_t value, unsigned nTG); void setPitchbend (int16_t value, unsigned nTG);
void ControllersRefresh (unsigned nTG); void ControllersRefresh (unsigned nTG);
void SetReverbSend (unsigned nReverbSend, unsigned nTG); // 0 .. 127
enum TParameter enum TParameter
{ {
ParameterCompressorEnable, ParameterCompressorEnable,
@ -101,6 +104,7 @@ public:
TGParameterPan, TGParameterPan,
TGParameterMasterTune, TGParameterMasterTune,
TGParameterMIDIChannel, TGParameterMIDIChannel,
TGParameterReverbSend,
TGParameterUnknown TGParameterUnknown
}; };
@ -143,7 +147,6 @@ private:
unsigned m_nProgram[CConfig::ToneGenerators]; unsigned m_nProgram[CConfig::ToneGenerators];
unsigned m_nVolume[CConfig::ToneGenerators]; unsigned m_nVolume[CConfig::ToneGenerators];
unsigned m_nPan[CConfig::ToneGenerators]; unsigned m_nPan[CConfig::ToneGenerators];
float32_t m_fPan[CConfig::ToneGenerators];
int m_nMasterTune[CConfig::ToneGenerators]; int m_nMasterTune[CConfig::ToneGenerators];
unsigned m_nMIDIChannel[CConfig::ToneGenerators]; unsigned m_nMIDIChannel[CConfig::ToneGenerators];
@ -151,6 +154,8 @@ private:
unsigned m_nNoteLimitHigh[CConfig::ToneGenerators]; unsigned m_nNoteLimitHigh[CConfig::ToneGenerators];
int m_nNoteShift[CConfig::ToneGenerators]; int m_nNoteShift[CConfig::ToneGenerators];
unsigned m_nReverbSend[CConfig::ToneGenerators];
CUserInterface m_UI; CUserInterface m_UI;
CSysExFileLoader m_SysExFileLoader; CSysExFileLoader m_SysExFileLoader;
CPerformanceConfig m_PerformanceConfig; CPerformanceConfig m_PerformanceConfig;
@ -175,9 +180,9 @@ private:
bool m_bProfileEnabled; bool m_bProfileEnabled;
AudioEffectPlateReverb* reverb; AudioEffectPlateReverb* reverb;
AudioStereoMixer<8>* tg_mixer; AudioStereoMixer<CConfig::ToneGenerators>* tg_mixer;
AudioStereoMixer<CConfig::ToneGenerators>* reverb_send_mixer;
CSpinLock m_PanoramaSpinLock;
CSpinLock m_ReverbSpinLock; CSpinLock m_ReverbSpinLock;
}; };

@ -3,6 +3,7 @@
# #
# Sound device # Sound device
#SoundDevice=i2s
SoundDevice=pwm SoundDevice=pwm
#SoundDevice=hdmi #SoundDevice=hdmi
SampleRate=48000 SampleRate=48000

@ -1,87 +0,0 @@
// Taken from https://github.com/manicken/Audio/tree/templateMixer
// Adapted for MiniDexed by Holger Wirtz <dcoredump@googlemail.com>
/* Audio Library for Teensy 3.X
* Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
*
* Development of this audio library was funded by PJRC.COM, LLC by sales of
* Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop
* open source software by purchasing Teensy or other PJRC products.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice, development funding notice, and this permission
* notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <cstdlib>
#include <stdint.h>
#include <assert.h>
#include "arm_math.h"
#include "mixer.h"
template <int NN> void AudioMixer<NN>::gain(uint8_t channel, float32_t gain)
{
if (channel >= NN) return;
if (gain > MAX_GAIN)
gain = MAX_GAIN;
else if (gain < MIN_GAIN)
gain = MIN_GAIN;
multiplier[channel] = gain;
}
template <int NN> void AudioMixer<NN>::gain(float32_t gain)
{
for (uint8_t i = 0; i < NN; i++)
{
if (gain > MAX_GAIN)
gain = MAX_GAIN;
else if (gain < MIN_GAIN)
gain = MIN_GAIN;
multiplier[i] = gain;
}
}
template <int NN> void AudioMixer<NN>::doAddMix(uint8_t channel, float32_t* in, float32_t* out, uint16_t len)
{
float32_t* tmp=malloc(sizeof(float32_t)*len);
assert(tmp!=NULL);
arm_scale_f32(in,multiplier[channel],tmp,len);
arm_add_f32(out, tmp, out, len);
free(tmp);
}
template <int NN> void AudioStereoMixer<NN>::doAddMix(uint8_t channel, float32_t* in[], float32_t* out[], uint16_t len)
{
float32_t* tmp=malloc(sizeof(float32_t)*len);
assert(tmp!=NULL);
// panorama
for(uint16_t i=0;i<len;i++)
{
// left
arm_scale_f32(in+(i*2),multiplier[channel],tmp+(i*2),len);
arm_add_f32(out(i*2), tmp(i*2), out(i*2), len*2);
// right
}
free(tmp);
}

@ -1,79 +0,0 @@
// Taken from https://github.com/manicken/Audio/tree/templateMixer
// Adapted for MiniDexed by Holger Wirtz <dcoredump@googlemail.com>
/* Audio Library for Teensy 3.X
* Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
*
* Development of this audio library was funded by PJRC.COM, LLC by sales of
* Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop
* open source software by purchasing Teensy or other PJRC products.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice, development funding notice, and this permission
* notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef template_mixer_h_
#define template_mixer_h_
#include "arm_math.h"
#include <stdint.h>
#define UNITYGAIN 1.0f
#define MAX_GAIN 1.0f
#define MIN_GAIN 0.0f
template <int NN> class AudioMixer
{
public:
AudioMixer(void)
{
for (uint8_t i=0; i<NN; i++)
multiplier[i] = UNITYGAIN;
}
void doAddMix(uint8_t channel, float32_t* in, float32_t* out, uint16_t len);
/**
* this sets the individual gains
* @param channel
* @param gain
*/
void gain(uint8_t channel, float32_t gain);
/**
* set all channels to specified gain
* @param gain
*/
void gain(float32_t gain);
protected:
float32_t multiplier[NN];
};
template <int NN> class AudioStereoMixer : public AudioMixer<NN>
{
public:
AudioStereoMixer(void)
{
AudioMixer<NN>();
for (uint8_t i=0; i<NN; i++)
panorama[i] = 0.0;
}
void doAddMix(uint8_t channel, float32_t* in[], float32_t* out[], uint16_t len);
protected:
float32_t panorama[NN];
};
#endif

@ -12,28 +12,31 @@
#NoteLimitLow#=0 # 0 .. 127, C-2 .. G8 #NoteLimitLow#=0 # 0 .. 127, C-2 .. G8
#NoteLimitHigh#=127 # 0 .. 127, C-2 .. G8 #NoteLimitHigh#=127 # 0 .. 127, C-2 .. G8
#NoteShift#=0 # -24 .. 24 #NoteShift#=0 # -24 .. 24
#ReverbSend#=0 # 0 .. 99
# TG1 # TG1
BankNumber1=0 BankNumber1=0
VoiceNumber1=1 VoiceNumber1=1
MIDIChannel1=255 MIDIChannel1=255
Volume1=100 Volume1=100
Pan1=32 Pan1=0
Detune1=-11 Detune1=-11
NoteLimitLow1=0 NoteLimitLow1=0
NoteLimitHigh1=127 NoteLimitHigh1=127
NoteShift1=0 NoteShift1=0
ReverbSend1=99
# TG2 # TG2
BankNumber2=0 BankNumber2=0
VoiceNumber2=1 VoiceNumber2=1
MIDIChannel2=255 MIDIChannel2=255
Volume2=100 Volume2=100
Pan2=96 Pan2=127
Detune2=11 Detune2=11
NoteLimitLow2=0 NoteLimitLow2=0
NoteLimitHigh2=127 NoteLimitHigh2=127
NoteShift2=0 NoteShift2=0
ReverbSend2=70
# TG3 # TG3
BankNumber3=0 BankNumber3=0
@ -45,6 +48,7 @@ Detune3=-7
NoteLimitLow3=0 NoteLimitLow3=0
NoteLimitHigh3=127 NoteLimitHigh3=127
NoteShift3=0 NoteShift3=0
ReverbSend3=0
# TG4 # TG4
BankNumber4=0 BankNumber4=0
@ -56,6 +60,7 @@ Detune4=7
NoteLimitLow4=0 NoteLimitLow4=0
NoteLimitHigh4=127 NoteLimitHigh4=127
NoteShift4=0 NoteShift4=0
ReverbSend4=0
# TG5 # TG5
BankNumber5=0 BankNumber5=0
@ -67,6 +72,7 @@ Detune5=0
NoteLimitLow5=0 NoteLimitLow5=0
NoteLimitHigh5=127 NoteLimitHigh5=127
NoteShift5=0 NoteShift5=0
ReverbSend5=0
# TG6 # TG6
BankNumber6=0 BankNumber6=0
@ -78,6 +84,7 @@ Detune6=0
NoteLimitLow6=0 NoteLimitLow6=0
NoteLimitHigh6=127 NoteLimitHigh6=127
NoteShift6=0 NoteShift6=0
ReverbSend6=0
# TG7 # TG7
BankNumber7=0 BankNumber7=0
@ -89,6 +96,7 @@ Detune7=0
NoteLimitLow7=0 NoteLimitLow7=0
NoteLimitHigh7=127 NoteLimitHigh7=127
NoteShift7=0 NoteShift7=0
ReverbSend7=0
# TG8 # TG8
BankNumber8=0 BankNumber8=0
@ -100,6 +108,7 @@ Detune8=0
NoteLimitLow8=0 NoteLimitLow8=0
NoteLimitHigh8=127 NoteLimitHigh8=127
NoteShift8=0 NoteShift8=0
ReverbSend8=0
# Effects # Effects
#CompressorEnable=1 # 0: off, 1: on #CompressorEnable=1 # 0: off, 1: on
@ -119,4 +128,4 @@ ReverbHighDamp=50
ReverbLowDamp=50 ReverbLowDamp=50
ReverbLowPass=30 ReverbLowPass=30
ReverbDiffusion=65 ReverbDiffusion=65
ReverbLevel=80 ReverbLevel=99

@ -89,6 +89,9 @@ bool CPerformanceConfig::Load (void)
PropertyName.Format ("NoteShift%u", nTG+1); PropertyName.Format ("NoteShift%u", nTG+1);
m_nNoteShift[nTG] = m_Properties.GetSignedNumber (PropertyName, 0); m_nNoteShift[nTG] = m_Properties.GetSignedNumber (PropertyName, 0);
PropertyName.Format ("ReverbSend%u", nTG+1);
m_nReverbSend[nTG] = m_Properties.GetNumber (PropertyName, 50);
} }
m_bCompressorEnable = m_Properties.GetNumber ("CompressorEnable", 1) != 0; m_bCompressorEnable = m_Properties.GetNumber ("CompressorEnable", 1) != 0;
@ -99,7 +102,7 @@ bool CPerformanceConfig::Load (void)
m_nReverbLowDamp = m_Properties.GetNumber ("ReverbLowDamp", 50); m_nReverbLowDamp = m_Properties.GetNumber ("ReverbLowDamp", 50);
m_nReverbLowPass = m_Properties.GetNumber ("ReverbLowPass", 30); m_nReverbLowPass = m_Properties.GetNumber ("ReverbLowPass", 30);
m_nReverbDiffusion = m_Properties.GetNumber ("ReverbDiffusion", 65); m_nReverbDiffusion = m_Properties.GetNumber ("ReverbDiffusion", 65);
m_nReverbLevel = m_Properties.GetNumber ("ReverbLevel", 80); m_nReverbLevel = m_Properties.GetNumber ("ReverbLevel", 99);
return bResult; return bResult;
} }
@ -151,6 +154,9 @@ bool CPerformanceConfig::Save (void)
PropertyName.Format ("NoteShift%u", nTG+1); PropertyName.Format ("NoteShift%u", nTG+1);
m_Properties.SetSignedNumber (PropertyName, m_nNoteShift[nTG]); m_Properties.SetSignedNumber (PropertyName, m_nNoteShift[nTG]);
PropertyName.Format ("ReverbSend%u", nTG+1);
m_Properties.SetNumber (PropertyName, m_nReverbSend[nTG]);
} }
m_Properties.SetNumber ("CompressorEnable", m_bCompressorEnable ? 1 : 0); m_Properties.SetNumber ("CompressorEnable", m_bCompressorEnable ? 1 : 0);
@ -220,6 +226,12 @@ int CPerformanceConfig::GetNoteShift (unsigned nTG) const
return m_nNoteShift[nTG]; return m_nNoteShift[nTG];
} }
unsigned CPerformanceConfig::GetReverbSend (unsigned nTG) const
{
assert (nTG < CConfig::ToneGenerators);
return m_nReverbSend[nTG];
}
void CPerformanceConfig::SetBankNumber (unsigned nValue, unsigned nTG) void CPerformanceConfig::SetBankNumber (unsigned nValue, unsigned nTG)
{ {
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
@ -274,6 +286,12 @@ void CPerformanceConfig::SetNoteShift (int nValue, unsigned nTG)
m_nNoteShift[nTG] = nValue; m_nNoteShift[nTG] = nValue;
} }
void CPerformanceConfig::SetReverbSend (unsigned nValue, unsigned nTG)
{
assert (nTG < CConfig::ToneGenerators);
m_nReverbSend[nTG] = nValue;
}
bool CPerformanceConfig::GetCompressorEnable (void) const bool CPerformanceConfig::GetCompressorEnable (void) const
{ {
return m_bCompressorEnable; return m_bCompressorEnable;

@ -47,6 +47,7 @@ public:
unsigned GetNoteLimitLow (unsigned nTG) const; // 0 .. 127 unsigned GetNoteLimitLow (unsigned nTG) const; // 0 .. 127
unsigned GetNoteLimitHigh (unsigned nTG) const; // 0 .. 127 unsigned GetNoteLimitHigh (unsigned nTG) const; // 0 .. 127
int GetNoteShift (unsigned nTG) const; // -24 .. 24 int GetNoteShift (unsigned nTG) const; // -24 .. 24
unsigned GetReverbSend (unsigned nTG) const; // 0 .. 127
void SetBankNumber (unsigned nValue, unsigned nTG); void SetBankNumber (unsigned nValue, unsigned nTG);
void SetVoiceNumber (unsigned nValue, unsigned nTG); void SetVoiceNumber (unsigned nValue, unsigned nTG);
@ -57,6 +58,7 @@ public:
void SetNoteLimitLow (unsigned nValue, unsigned nTG); void SetNoteLimitLow (unsigned nValue, unsigned nTG);
void SetNoteLimitHigh (unsigned nValue, unsigned nTG); void SetNoteLimitHigh (unsigned nValue, unsigned nTG);
void SetNoteShift (int nValue, unsigned nTG); void SetNoteShift (int nValue, unsigned nTG);
void SetReverbSend (unsigned nValue, unsigned nTG);
// Effects // Effects
bool GetCompressorEnable (void) const; bool GetCompressorEnable (void) const;
@ -89,6 +91,7 @@ private:
unsigned m_nNoteLimitLow[CConfig::ToneGenerators]; unsigned m_nNoteLimitLow[CConfig::ToneGenerators];
unsigned m_nNoteLimitHigh[CConfig::ToneGenerators]; unsigned m_nNoteLimitHigh[CConfig::ToneGenerators];
int m_nNoteShift[CConfig::ToneGenerators]; int m_nNoteShift[CConfig::ToneGenerators];
int m_nReverbSend[CConfig::ToneGenerators];
bool m_bCompressorEnable; bool m_bCompressorEnable;
bool m_bReverbEnable; bool m_bReverbEnable;

@ -63,6 +63,7 @@ const CUIMenu::TMenuItem CUIMenu::s_TGMenu[] =
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
{"Pan", EditTGParameter, 0, CMiniDexed::TGParameterPan}, {"Pan", EditTGParameter, 0, CMiniDexed::TGParameterPan},
#endif #endif
{"Reverb-Send", EditTGParameter, 0, CMiniDexed::TGParameterReverbSend},
{"Detune", EditTGParameter, 0, CMiniDexed::TGParameterMasterTune}, {"Detune", EditTGParameter, 0, CMiniDexed::TGParameterMasterTune},
{"Channel", EditTGParameter, 0, CMiniDexed::TGParameterMIDIChannel}, {"Channel", EditTGParameter, 0, CMiniDexed::TGParameterMIDIChannel},
{"Edit Voice", MenuHandler, s_EditVoiceMenu}, {"Edit Voice", MenuHandler, s_EditVoiceMenu},

Loading…
Cancel
Save