mirror of https://github.com/probonopd/MiniDexed
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
parent
35b07f17f5
commit
77813bf90e
@ -1 +1 @@ |
|||||||
Subproject commit e414a8718300815aefc3fe0acd8df5c12ad0b58a |
Subproject commit f304ae4e1cfa6ca9bab918f8f5a55bd562756e78 |
@ -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 |
@ -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 (); |
|
||||||
} |
|
@ -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 |
|
Loading…
Reference in new issue