From 77813bf90efede7b1a7090a7a3742ac41357f299 Mon Sep 17 00:00:00 2001 From: Holger Date: Fri, 15 Apr 2022 08:54:46 +0200 Subject: [PATCH] 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 Co-authored-by: probonopd --- Synth_Dexed | 2 +- src/Makefile | 2 +- src/Synth_Dexed.mk | 14 +- src/common.h | 5 + src/effect_compressor.cpp | 325 ++++++++++++++ src/effect_compressor.h | 84 ++++ src/effect_mixer.hpp | 173 ++++++++ src/effect_platervbstereo.h | 1 - src/minidexed.cpp | 158 ++++--- src/minidexed.cpp.O | 853 ------------------------------------ src/minidexed.h | 13 +- src/minidexed.ini | 1 + src/mixer.cpp | 87 ---- src/mixer.h | 79 ---- src/performance.ini | 15 +- src/performanceconfig.cpp | 20 +- src/performanceconfig.h | 3 + src/uimenu.cpp | 1 + 18 files changed, 731 insertions(+), 1105 deletions(-) create mode 100644 src/effect_compressor.cpp create mode 100644 src/effect_compressor.h create mode 100644 src/effect_mixer.hpp delete mode 100644 src/minidexed.cpp.O delete mode 100644 src/mixer.cpp delete mode 100644 src/mixer.h diff --git a/Synth_Dexed b/Synth_Dexed index e414a87..f304ae4 160000 --- a/Synth_Dexed +++ b/Synth_Dexed @@ -1 +1 @@ -Subproject commit e414a8718300815aefc3fe0acd8df5c12ad0b58a +Subproject commit f304ae4e1cfa6ca9bab918f8f5a55bd562756e78 diff --git a/src/Makefile b/src/Makefile index fd8987a..e967049 100644 --- a/src/Makefile +++ b/src/Makefile @@ -9,7 +9,7 @@ CMSIS_DIR = ../CMSIS_5/CMSIS OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ sysexfileloader.o performanceconfig.o perftimer.o \ - effect_platervbstereo.o + effect_compressor.o effect_platervbstereo.o include ./Synth_Dexed.mk include ./Rules.mk diff --git a/src/Synth_Dexed.mk b/src/Synth_Dexed.mk index 498038a..b68bf9c 100644 --- a/src/Synth_Dexed.mk +++ b/src/Synth_Dexed.mk @@ -5,7 +5,9 @@ CMSIS_CORE_INCLUDE_DIR = $(CMSIS_DIR)/Core/Include CMSIS_DSP_INCLUDE_DIR = $(CMSIS_DIR)/DSP/Include 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_COMPUTELIB_SRC_DIR = $(CMSIS_DIR)/DSP/ComputeLibrary/Source OBJS += \ $(SYNTH_DEXED_DIR)/PluginFx.o \ @@ -24,12 +26,18 @@ OBJS += \ $(CMSIS_DSP_SOURCE_DIR)/BasicMathFunctions/BasicMathFunctions.o \ $(CMSIS_DSP_SOURCE_DIR)/FastMathFunctions/FastMathFunctions.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 $(CMSIS_CORE_INCLUDE_DIR) INCLUDE += -I $(CMSIS_DSP_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] diff --git a/src/common.h b/src/common.h index ef6a51f..902f74b 100644 --- a/src/common.h +++ b/src/common.h @@ -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; } +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) ({ \ __typeof__(amt) _amt = (amt); \ __typeof__(low) _low = (low); \ diff --git a/src/effect_compressor.cpp b/src/effect_compressor.cpp new file mode 100644 index 0000000..d2aaec7 --- /dev/null +++ b/src/effect_compressor.cpp @@ -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 +#include +#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); +} diff --git a/src/effect_compressor.h b/src/effect_compressor.h new file mode 100644 index 0000000..df84092 --- /dev/null +++ b/src/effect_compressor.h @@ -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 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 diff --git a/src/effect_mixer.hpp b/src/effect_mixer.hpp new file mode 100644 index 0000000..f8e7865 --- /dev/null +++ b/src/effect_mixer.hpp @@ -0,0 +1,173 @@ +// Taken from https://github.com/manicken/Audio/tree/templateMixer +// Adapted for MiniDexed by Holger Wirtz + +#ifndef effect_mixer_h_ +#define effect_mixer_h_ + +#include +#include +#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 class AudioMixer +{ +public: + AudioMixer(uint16_t len) + { + buffer_length=len; + for (uint8_t i=0; i= 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 class AudioStereoMixer : public AudioMixer +{ +public: + AudioStereoMixer(uint16_t len) : AudioMixer(len) + { + for (uint8_t i=0; i= 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::sumbufL; + using AudioMixer::multiplier; + using AudioMixer::buffer_length; + float32_t panorama[NN]; + float32_t* sumbufR; +}; + +#endif diff --git a/src/effect_platervbstereo.h b/src/effect_platervbstereo.h index b7af531..23538c4 100644 --- a/src/effect_platervbstereo.h +++ b/src/effect_platervbstereo.h @@ -41,7 +41,6 @@ * */ -#pragma once #ifndef _EFFECT_PLATERVBSTEREO_H #define _EFFECT_PLATERVBSTEREO_H diff --git a/src/minidexed.cpp b/src/minidexed.cpp index f33a5ca..9a13d4c 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -58,7 +58,6 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_nProgram[i] = 0; m_nVolume[i] = 100; m_nPan[i] = 64; - m_fPan[i] = 0.5f; m_nMasterTune[i] = 0; m_nMIDIChannel[i] = CMIDIDevice::Disabled; @@ -66,6 +65,8 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_nNoteLimitHigh[i] = 127; m_nNoteShift[i] = 0; + m_nReverbSend[i] = 0; + m_pTG[i] = new CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()); assert (m_pTG[i]); @@ -114,7 +115,12 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, } #endif + // BEGIN setup tg_mixer + tg_mixer = new AudioStereoMixer(pConfig->GetChunkSize()/2); + // END setup tgmixer + // BEGIN setup reverb + reverb_send_mixer = new AudioStereoMixer(pConfig->GetChunkSize()/2); reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate()); SetParameter (ParameterReverbEnable, 1); SetParameter (ParameterReverbSize, 70); @@ -122,7 +128,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, SetParameter (ParameterReverbLowDamp, 50); SetParameter (ParameterReverbLowPass, 30); SetParameter (ParameterReverbDiffusion, 65); - SetParameter (ParameterReverbLevel, 80); + SetParameter (ParameterReverbLevel, 99); // END setup reverb SetParameter (ParameterCompressorEnable, 1); @@ -158,6 +164,11 @@ bool CMiniDexed::Initialize (void) m_pTG[i]->setPBController (12, 1); 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 ()) @@ -174,6 +185,8 @@ bool CMiniDexed::Initialize (void) m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG); m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG); m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG); + + SetReverbSend (m_PerformanceConfig.GetReverbSend (nTG), nTG); } // Effects @@ -314,10 +327,7 @@ CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void) void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG) { - if (nBankLSB > 127) - { - return; - } + nBankLSB=constrain((int)nBankLSB,0,127); assert (nTG < CConfig::ToneGenerators); m_nVoiceBankID[nTG] = nBankLSB; @@ -327,10 +337,7 @@ void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG) void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) { - if (nProgram > 31) - { - return; - } + nProgram=constrain((int)nProgram,0,31); assert (nTG < CConfig::ToneGenerators); m_nProgram[nTG] = nProgram; @@ -346,40 +353,45 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) { - if (nVolume > 127) - { - return; - } + nVolume=constrain((int)nVolume,0,127); assert (nTG < CConfig::ToneGenerators); m_nVolume[nTG] = nVolume; assert (m_pTG[nTG]); - m_pTG[nTG]->setGain (nVolume / 127.0); + m_pTG[nTG]->setGain (nVolume / 127.0f); m_UI.ParameterChanged (); } void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) { - if (nPan > 127) - { - return; - } + nPan=constrain((int)nPan,0,127); assert (nTG < CConfig::ToneGenerators); 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 (); } void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG) { - if (!(-99 <= nMasterTune && nMasterTune <= 99)) - { - return; - } + nMasterTune=constrain((int)nMasterTune,-99,99); assert (nTG < CConfig::ToneGenerators); m_nMasterTune[nTG] = nMasterTune; @@ -517,44 +529,51 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue) break; case ParameterReverbEnable: + nValue=constrain((int)nValue,0,1); m_ReverbSpinLock.Acquire (); reverb->set_bypass (!nValue); m_ReverbSpinLock.Release (); break; case ParameterReverbSize: + nValue=constrain((int)nValue,0,99); m_ReverbSpinLock.Acquire (); - reverb->size (nValue / 99.0); + reverb->size (nValue / 99.0f); m_ReverbSpinLock.Release (); break; case ParameterReverbHighDamp: + nValue=constrain((int)nValue,0,99); m_ReverbSpinLock.Acquire (); - reverb->hidamp (nValue / 99.0); + reverb->hidamp (nValue / 99.0f); m_ReverbSpinLock.Release (); break; case ParameterReverbLowDamp: + nValue=constrain((int)nValue,0,99); m_ReverbSpinLock.Acquire (); - reverb->lodamp (nValue / 99.0); + reverb->lodamp (nValue / 99.0f); m_ReverbSpinLock.Release (); break; case ParameterReverbLowPass: + nValue=constrain((int)nValue,0,99); m_ReverbSpinLock.Acquire (); - reverb->lowpass (nValue / 99.0); + reverb->lowpass (nValue / 99.0f); m_ReverbSpinLock.Release (); break; case ParameterReverbDiffusion: + nValue=constrain((int)nValue,0,99); m_ReverbSpinLock.Acquire (); - reverb->diffusion (nValue / 99.0); + reverb->diffusion (nValue / 99.0f); m_ReverbSpinLock.Release (); break; case ParameterReverbLevel: + nValue=constrain((int)nValue,0,99); m_ReverbSpinLock.Acquire (); - reverb->level (nValue / 99.0); + reverb->level (nValue / 99.0f); m_ReverbSpinLock.Release (); break; @@ -587,6 +606,8 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT SetMIDIChannel ((uint8_t) nValue, nTG); break; + case TGParameterReverbSend: SetReverbSend (nValue, nTG); break; + default: assert (0); break; @@ -605,6 +626,7 @@ int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) case TGParameterPan: return m_nPan[nTG]; case TGParameterMasterTune: return m_nMasterTune[nTG]; case TGParameterMIDIChannel: return m_nMIDIChannel[nTG]; + case TGParameterReverbSend: return m_nReverbSend[nTG]; default: assert (0); @@ -677,15 +699,9 @@ void CMiniDexed::ProcessSound (void) float32_t SampleBuffer[nFrames]; m_pTG[0]->getSamples (SampleBuffer, nFrames); - // 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; iWrite (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 // - // now mix the output of all TGs - float32_t SampleBuffer[2][nFrames]; - uint8_t indexL=0, indexR=1; + assert (CConfig::ToneGenerators == 8); + // swap stereo channels if needed + 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 + // BEGIN TG mixing 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-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 (); + tg_mixer->doAddMix(i,m_OutputLevel[i]); + reverb_send_mixer->doAddMix(i,m_OutputLevel[i]); } - // 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 if (m_nParameter[ParameterReverbEnable]) { 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 (); - 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 - arm_scale_f32(ReverbBuffer[0], reverb->get_level(), ReverbBuffer[0], nFrames); - arm_add_f32(SampleBuffer[indexL], ReverbBuffer[0], SampleBuffer[indexL], nFrames); + arm_scale_f32(ReverbBuffer[indexL], reverb->get_level(), ReverbBuffer[indexL], nFrames); + arm_add_f32(SampleBuffer[indexL], ReverbBuffer[indexL], 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); + arm_scale_f32(ReverbBuffer[indexR], reverb->get_level(), ReverbBuffer[indexR], nFrames); + arm_add_f32(SampleBuffer[indexR], ReverbBuffer[indexR], SampleBuffer[indexR], nFrames); + + m_ReverbSpinLock.Release (); } // END adding reverb @@ -837,6 +849,8 @@ bool CMiniDexed::SavePerformance (void) m_PerformanceConfig.SetNoteLimitLow (m_nNoteLimitLow[nTG], nTG); m_PerformanceConfig.SetNoteLimitHigh (m_nNoteLimitHigh[nTG], nTG); m_PerformanceConfig.SetNoteShift (m_nNoteShift[nTG], nTG); + + m_PerformanceConfig.SetReverbSend (m_nReverbSend[nTG], nTG); } m_PerformanceConfig.SetCompressorEnable (!!m_nParameter[ParameterCompressorEnable]); diff --git a/src/minidexed.cpp.O b/src/minidexed.cpp.O deleted file mode 100644 index 17a9c77..0000000 --- a/src/minidexed.cpp.O +++ /dev/null @@ -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 . -// -#include "minidexed.h" -#include -#include -#include -#include -#include -#include -#include -#include - -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; iWrite (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 (); -} diff --git a/src/minidexed.h b/src/minidexed.h index 1aed730..ee0f288 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -40,8 +40,9 @@ #include #include #include "common.h" +#include "effect_mixer.hpp" #include "effect_platervbstereo.h" -#include "mixer.h" +#include "effect_compressor.h" class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE @@ -77,6 +78,8 @@ public: void setPitchbend (int16_t value, unsigned nTG); void ControllersRefresh (unsigned nTG); + void SetReverbSend (unsigned nReverbSend, unsigned nTG); // 0 .. 127 + enum TParameter { ParameterCompressorEnable, @@ -101,6 +104,7 @@ public: TGParameterPan, TGParameterMasterTune, TGParameterMIDIChannel, + TGParameterReverbSend, TGParameterUnknown }; @@ -143,7 +147,6 @@ private: unsigned m_nProgram[CConfig::ToneGenerators]; unsigned m_nVolume[CConfig::ToneGenerators]; unsigned m_nPan[CConfig::ToneGenerators]; - float32_t m_fPan[CConfig::ToneGenerators]; int m_nMasterTune[CConfig::ToneGenerators]; unsigned m_nMIDIChannel[CConfig::ToneGenerators]; @@ -151,6 +154,8 @@ private: unsigned m_nNoteLimitHigh[CConfig::ToneGenerators]; int m_nNoteShift[CConfig::ToneGenerators]; + unsigned m_nReverbSend[CConfig::ToneGenerators]; + CUserInterface m_UI; CSysExFileLoader m_SysExFileLoader; CPerformanceConfig m_PerformanceConfig; @@ -175,9 +180,9 @@ private: bool m_bProfileEnabled; AudioEffectPlateReverb* reverb; - AudioStereoMixer<8>* tg_mixer; + AudioStereoMixer* tg_mixer; + AudioStereoMixer* reverb_send_mixer; - CSpinLock m_PanoramaSpinLock; CSpinLock m_ReverbSpinLock; }; diff --git a/src/minidexed.ini b/src/minidexed.ini index b9f33ce..4ecc003 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -3,6 +3,7 @@ # # Sound device +#SoundDevice=i2s SoundDevice=pwm #SoundDevice=hdmi SampleRate=48000 diff --git a/src/mixer.cpp b/src/mixer.cpp deleted file mode 100644 index 16f4b51..0000000 --- a/src/mixer.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// Taken from https://github.com/manicken/Audio/tree/templateMixer -// Adapted for MiniDexed by Holger Wirtz - -/* 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 -#include -#include -#include "arm_math.h" -#include "mixer.h" - -template void AudioMixer::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 void AudioMixer::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 void AudioMixer::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 void AudioStereoMixer::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 - -/* 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 - -#define UNITYGAIN 1.0f -#define MAX_GAIN 1.0f -#define MIN_GAIN 0.0f - -template class AudioMixer -{ -public: - AudioMixer(void) - { - for (uint8_t i=0; i class AudioStereoMixer : public AudioMixer -{ -public: - AudioStereoMixer(void) - { - AudioMixer(); - for (uint8_t i=0; i