Signal chain in float32_t and fixes for float signal path (#73)

* 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.

Co-authored-by: Holger Wirtz <wirtz@parasitstudio.de>
pull/84/head
probonopd 3 years ago committed by GitHub
parent 075e17407d
commit f98f5db10d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 2
      Synth_Dexed
  3. 2
      src/Synth_Dexed.mk
  4. 21
      src/common.h
  5. 5
      src/dexedadapter.h
  6. 27
      src/effect_platervbstereo.cpp
  7. 49
      src/effect_platervbstereo.h
  8. 128
      src/minidexed.cpp
  9. 853
      src/minidexed.cpp.O
  10. 10
      src/minidexed.h
  11. 87
      src/mixer.cpp
  12. 79
      src/mixer.h
  13. 4
      src/performance.ini
  14. 12
      src/performanceconfig.cpp
  15. 6
      src/performanceconfig.h
  16. 4
      src/uimenu.cpp

@ -158,7 +158,7 @@ sudo losetup -d "${DEV}"
rm -r boot rm -r boot
# Write to SD card # Write to SD card
sudo dd if="${IMG}" of=/dev/mmcblk0 bs=512k status=progress sudo dd if="${IMG}" of=/dev/mmcblk0 bs=512k status=progress && sync
``` ```
## Acknowledgements ## Acknowledgements

@ -1 +1 @@
Subproject commit 70293ae5998643c706244b090504dde8b4097851 Subproject commit e414a8718300815aefc3fe0acd8df5c12ad0b58a

@ -30,6 +30,6 @@ INCLUDE += -I $(SYNTH_DEXED_DIR)
INCLUDE += -I $(CMSIS_CORE_INCLUDE_DIR) INCLUDE += -I $(CMSIS_CORE_INCLUDE_DIR)
INCLUDE += -I $(CMSIS_DSP_INCLUDE_DIR) INCLUDE += -I $(CMSIS_DSP_INCLUDE_DIR)
INCLUDE += -I $(CMSIS_DSP_PRIVATE_INCLUDE_DIR) INCLUDE += -I $(CMSIS_DSP_PRIVATE_INCLUDE_DIR)
CXXFLAGS += -DARM_MATH_NEON CXXFLAGS += -DARM_MATH_NEON -DHAVE_NEON
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] 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]

@ -0,0 +1,21 @@
#ifndef _common_h
#define _common_h
inline long maplong(long x, long in_min, long in_max, long out_min, long out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
inline float32_t mapfloat(float32_t val, float32_t in_min, float32_t 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); \
__typeof__(high) _high = (high); \
(_amt < _low) ? _low : ((_amt > _high) ? _high : _amt); \
})
#endif

@ -17,6 +17,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#ifndef _dexedadapter_h #ifndef _dexedadapter_h
#define _dexedadapter_h #define _dexedadapter_h
@ -56,10 +57,10 @@ public:
m_SpinLock.Release (); m_SpinLock.Release ();
} }
void getSamples (uint16_t n_samples, int16_t* buffer) void getSamples (float32_t* buffer, uint16_t n_samples)
{ {
m_SpinLock.Acquire (); m_SpinLock.Acquire ();
Dexed::getSamples (n_samples, buffer); Dexed::getSamples (buffer, n_samples);
m_SpinLock.Release (); m_SpinLock.Release ();
} }

@ -153,14 +153,13 @@ AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate)
lfo2_phase_acc = 0; lfo2_phase_acc = 0;
lfo2_adder = (UINT32_MAX + 1)/(samplerate * LFO2_FREQ_HZ); lfo2_adder = (UINT32_MAX + 1)/(samplerate * LFO2_FREQ_HZ);
send_level = 0.0; reverb_level = 0.0f;
} }
// #define sat16(n, rshift) signed_saturate_rshift((n), 16, (rshift)) // #define sat16(n, rshift) signed_saturate_rshift((n), 16, (rshift))
void AudioEffectPlateReverb::doReverb(uint16_t len, int16_t audioblock[][2]) void AudioEffectPlateReverb::doReverb(const float32_t* inblockL, const float32_t* inblockR, float32_t* rvbblockL, float32_t* rvbblockR, uint16_t len)
{ {
int i;
float32_t input, acc, temp1, temp2; float32_t input, acc, temp1, temp2;
uint16_t temp16; uint16_t temp16;
float32_t rv_time; float32_t rv_time;
@ -203,7 +202,7 @@ void AudioEffectPlateReverb::doReverb(uint16_t len, int16_t audioblock[][2])
rv_time = rv_time_k; rv_time = rv_time_k;
for (i=0; i < len; i++) for (uint16_t i=0; i < len; i++)
{ {
// do the LFOs // do the LFOs
lfo1_phase_acc += lfo1_adder; lfo1_phase_acc += lfo1_adder;
@ -236,7 +235,7 @@ void AudioEffectPlateReverb::doReverb(uint16_t len, int16_t audioblock[][2])
y += (int64_t)y1 * idx; y += (int64_t)y1 * idx;
lfo2_out_cos = (int32_t) (y >> (32-8)); // 16bit output lfo2_out_cos = (int32_t) (y >> (32-8)); // 16bit output
input = (float32_t(audioblock[i][0])/32767.0f) * input_attn; input = inblockL[i] * input_attn;
// chained input allpasses, channel L // chained input allpasses, channel L
acc = in_allp1_bufL[in_allp1_idxL] + input * in_allp_k; acc = in_allp1_bufL[in_allp1_idxL] + input * in_allp_k;
@ -259,7 +258,7 @@ void AudioEffectPlateReverb::doReverb(uint16_t len, int16_t audioblock[][2])
in_allp_out_L = acc; in_allp_out_L = acc;
if (++in_allp4_idxL >= sizeof(in_allp4_bufL)/sizeof(float32_t)) in_allp4_idxL = 0; if (++in_allp4_idxL >= sizeof(in_allp4_bufL)/sizeof(float32_t)) in_allp4_idxL = 0;
input = (float32_t(audioblock[i][1])/32767.0f) * input_attn; input = inblockR[i] * input_attn;
// chained input allpasses, channel R // chained input allpasses, channel R
acc = in_allp1_bufR[in_allp1_idxR] + input * in_allp_k; acc = in_allp1_bufR[in_allp1_idxR] + input * in_allp_k;
@ -406,13 +405,7 @@ void AudioEffectPlateReverb::doReverb(uint16_t len, int16_t audioblock[][2])
temp1 = acc - master_lowpass_l; temp1 = acc - master_lowpass_l;
master_lowpass_l += temp1 * master_lowpass_f; master_lowpass_l += temp1 * master_lowpass_f;
int32_t out = audioblock[i][0] + int16_t(master_lowpass_l * 32767.0f * send_level); rvbblockL[i] = master_lowpass_l;
if(out > INT16_MAX)
audioblock[i][0] = INT16_MAX;
else if(out < INT16_MIN)
audioblock[i][0] = INT16_MIN;
else
audioblock[i][0] = out;
// Channel R // Channel R
#ifdef TAP1_MODULATED #ifdef TAP1_MODULATED
@ -456,12 +449,6 @@ void AudioEffectPlateReverb::doReverb(uint16_t len, int16_t audioblock[][2])
temp1 = acc - master_lowpass_r; temp1 = acc - master_lowpass_r;
master_lowpass_r += temp1 * master_lowpass_f; master_lowpass_r += temp1 * master_lowpass_f;
out = audioblock[i][1] + int16_t(master_lowpass_l * 32767.0f * send_level); rvbblockR[i] = master_lowpass_r;
if(out > INT16_MAX)
audioblock[i][1] = INT16_MAX;
else if(out < INT16_MIN)
audioblock[i][1] = INT16_MIN;
else
audioblock[i][1] = out;
} }
} }

@ -45,37 +45,9 @@
#ifndef _EFFECT_PLATERVBSTEREO_H #ifndef _EFFECT_PLATERVBSTEREO_H
#define _EFFECT_PLATERVBSTEREO_H #define _EFFECT_PLATERVBSTEREO_H
#include "arm_math.h"
#include <stdint.h> #include <stdint.h>
#include <arm_math.h>
#define constrain(amt, low, high) ({ \ #include "common.h"
__typeof__(amt) _amt = (amt); \
__typeof__(low) _low = (low); \
__typeof__(high) _high = (high); \
(_amt < _low) ? _low : ((_amt > _high) ? _high : _amt); \
})
/*
template<typename T>
inline static T min(const T& a, const T& b) {
return a < b ? a : b;
}
template<typename T>
inline static T max(const T& a, const T& b) {
return a > b ? a : b;
}
inline long maplong(long x, long in_min, long in_max, long out_min, long out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
*/
inline float32_t mapfloat(float32_t val, float32_t in_min, float32_t in_max, float32_t out_min, float32_t out_max)
{
return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
/*** /***
* Loop delay modulation: comment/uncomment to switch sin/cos * Loop delay modulation: comment/uncomment to switch sin/cos
@ -89,34 +61,28 @@ class AudioEffectPlateReverb
{ {
public: public:
AudioEffectPlateReverb(float32_t samplerate); AudioEffectPlateReverb(float32_t samplerate);
void doReverb(uint16_t len, int16_t audioblock[][2]); void doReverb(const float32_t* inblockL, const float32_t* inblockR, float32_t* rvbblockL, float32_t* rvbblockR,uint16_t len);
void size(float n) void size(float n)
{ {
n = constrain(n, 0.0f, 1.0f); n = constrain(n, 0.0f, 1.0f);
n = mapfloat(n, 0.0f, 1.0f, 0.2f, rv_time_k_max); n = mapfloat(n, 0.0f, 1.0f, 0.2f, rv_time_k_max);
float32_t attn = mapfloat(n, 0.0f, rv_time_k_max, 0.5f, 0.25f); float32_t attn = mapfloat(n, 0.0f, rv_time_k_max, 0.5f, 0.25f);
//__disable_irq();
rv_time_k = n; rv_time_k = n;
input_attn = attn; input_attn = attn;
//__enable_irq();
} }
void hidamp(float n) void hidamp(float n)
{ {
n = constrain(n, 0.0f, 1.0f); n = constrain(n, 0.0f, 1.0f);
//__disable_irq();
lp_hidamp_k = 1.0f - n; lp_hidamp_k = 1.0f - n;
//__enable_irq();
} }
void lodamp(float n) void lodamp(float n)
{ {
n = constrain(n, 0.0f, 1.0f); n = constrain(n, 0.0f, 1.0f);
//__disable_irq();
lp_lodamp_k = -n; lp_lodamp_k = -n;
rv_time_scaler = 1.0f - n * 0.12f; // limit the max reverb time, otherwise it will clip rv_time_scaler = 1.0f - n * 0.12f; // limit the max reverb time, otherwise it will clip
//__enable_irq();
} }
void lowpass(float n) void lowpass(float n)
@ -130,24 +96,23 @@ public:
{ {
n = constrain(n, 0.0f, 1.0f); n = constrain(n, 0.0f, 1.0f);
n = mapfloat(n, 0.0f, 1.0f, 0.005f, 0.65f); n = mapfloat(n, 0.0f, 1.0f, 0.005f, 0.65f);
//__disable_irq();
in_allp_k = n; in_allp_k = n;
loop_allp_k = n; loop_allp_k = n;
//__enable_irq();
} }
void send(float n) void level(float n)
{ {
send_level = constrain(n, 0.0f, 1.0f); reverb_level = constrain(n, 0.0f, 1.0f);
} }
float32_t get_size(void) {return rv_time_k;} float32_t get_size(void) {return rv_time_k;}
bool get_bypass(void) {return bypass;} bool get_bypass(void) {return bypass;}
void set_bypass(bool state) {bypass = state;}; void set_bypass(bool state) {bypass = state;};
void tgl_bypass(void) {bypass ^=1;} void tgl_bypass(void) {bypass ^=1;}
float32_t get_level(void) {return reverb_level;}
private: private:
bool bypass = false; bool bypass = false;
float32_t send_level; float32_t reverb_level;
float32_t input_attn; float32_t input_attn;
float32_t in_allp_k; // input allpass coeff float32_t in_allp_k; // input allpass coeff

@ -58,6 +58,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
m_nProgram[i] = 0; m_nProgram[i] = 0;
m_nVolume[i] = 100; m_nVolume[i] = 100;
m_nPan[i] = 64; m_nPan[i] = 64;
m_fPan[i] = 0.5f;
m_nMasterTune[i] = 0; m_nMasterTune[i] = 0;
m_nMIDIChannel[i] = CMIDIDevice::Disabled; m_nMIDIChannel[i] = CMIDIDevice::Disabled;
@ -113,8 +114,6 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
} }
#endif #endif
SetParameter (ParameterCompressorEnable, 1);
// BEGIN setup reverb // BEGIN setup reverb
reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate()); reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate());
SetParameter (ParameterReverbEnable, 1); SetParameter (ParameterReverbEnable, 1);
@ -123,8 +122,10 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
SetParameter (ParameterReverbLowDamp, 50); SetParameter (ParameterReverbLowDamp, 50);
SetParameter (ParameterReverbLowPass, 30); SetParameter (ParameterReverbLowPass, 30);
SetParameter (ParameterReverbDiffusion, 65); SetParameter (ParameterReverbDiffusion, 65);
SetParameter (ParameterReverbSend, 80); SetParameter (ParameterReverbLevel, 80);
// END setup reverb // END setup reverb
SetParameter (ParameterCompressorEnable, 1);
}; };
bool CMiniDexed::Initialize (void) bool CMiniDexed::Initialize (void)
@ -183,7 +184,7 @@ bool CMiniDexed::Initialize (void)
SetParameter (ParameterReverbLowDamp, m_PerformanceConfig.GetReverbLowDamp ()); SetParameter (ParameterReverbLowDamp, m_PerformanceConfig.GetReverbLowDamp ());
SetParameter (ParameterReverbLowPass, m_PerformanceConfig.GetReverbLowPass ()); SetParameter (ParameterReverbLowPass, m_PerformanceConfig.GetReverbLowPass ());
SetParameter (ParameterReverbDiffusion, m_PerformanceConfig.GetReverbDiffusion ()); SetParameter (ParameterReverbDiffusion, m_PerformanceConfig.GetReverbDiffusion ());
SetParameter (ParameterReverbSend, m_PerformanceConfig.GetReverbSend ()); SetParameter (ParameterReverbLevel, m_PerformanceConfig.GetReverbLevel ());
} }
else else
{ {
@ -298,7 +299,7 @@ void CMiniDexed::Run (unsigned nCore)
for (unsigned i = 0; i < CConfig::TGsCore23; i++, nTG++) for (unsigned i = 0; i < CConfig::TGsCore23; i++, nTG++)
{ {
assert (m_pTG[nTG]); assert (m_pTG[nTG]);
m_pTG[nTG]->getSamples (m_nFramesToProcess, m_OutputLevel[nTG]); m_pTG[nTG]->getSamples (m_OutputLevel[nTG],m_nFramesToProcess);
} }
} }
} }
@ -368,6 +369,7 @@ void CMiniDexed::SetPan (unsigned nPan, unsigned nTG)
assert (nTG < CConfig::ToneGenerators); assert (nTG < CConfig::ToneGenerators);
m_nPan[nTG] = nPan; m_nPan[nTG] = nPan;
m_fPan[nTG]=mapfloat(nPan,0,127,0.0,1.0);
m_UI.ParameterChanged (); m_UI.ParameterChanged ();
} }
@ -550,9 +552,9 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue)
m_ReverbSpinLock.Release (); m_ReverbSpinLock.Release ();
break; break;
case ParameterReverbSend: case ParameterReverbLevel:
m_ReverbSpinLock.Acquire (); m_ReverbSpinLock.Acquire ();
reverb->send (nValue / 99.0); reverb->level (nValue / 99.0);
m_ReverbSpinLock.Release (); m_ReverbSpinLock.Release ();
break; break;
@ -672,11 +674,20 @@ void CMiniDexed::ProcessSound (void)
m_GetChunkTimer.Start (); m_GetChunkTimer.Start ();
} }
int16_t SampleBuffer[nFrames]; float32_t SampleBuffer[nFrames];
m_pTG[0]->getSamples (nFrames, SampleBuffer); 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; i<nFrames;i++)
{
tmp_float[i*2]=SampleBuffer[i];
tmp_float[(i*2)+1]=SampleBuffer[i];
}
arm_float_to_q15(tmp_float,tmp_int,nFrames*2);
if ( m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int))
!= (int) sizeof SampleBuffer)
{ {
LOGERR ("Sound data dropped"); LOGERR ("Sound data dropped");
} }
@ -716,7 +727,7 @@ void CMiniDexed::ProcessSound (void)
for (unsigned i = 0; i < CConfig::TGsCore1; i++) for (unsigned i = 0; i < CConfig::TGsCore1; i++)
{ {
assert (m_pTG[i]); assert (m_pTG[i]);
m_pTG[i]->getSamples (nFrames, m_OutputLevel[i]); m_pTG[i]->getSamples (m_OutputLevel[i], nFrames);
} }
// wait for cores 2 and 3 to complete their work // wait for cores 2 and 3 to complete their work
@ -728,54 +739,77 @@ void CMiniDexed::ProcessSound (void)
} }
} }
//
// Audio signal path after tone generators starts here
//
// now mix the output of all TGs // now mix the output of all TGs
int16_t SampleBuffer[nFrames][2]; float32_t SampleBuffer[2][nFrames];
uint8_t indexL=0, indexR=1;
assert (CConfig::ToneGenerators == 8); if (m_bChannelsSwapped)
for (unsigned i = 0; i < nFrames; i++)
{
int32_t nLeft = m_OutputLevel[0][i] * (127-m_nPan[0])
+ m_OutputLevel[1][i] * (127-m_nPan[1])
+ m_OutputLevel[2][i] * (127-m_nPan[2])
+ m_OutputLevel[3][i] * (127-m_nPan[3])
+ m_OutputLevel[4][i] * (127-m_nPan[4])
+ m_OutputLevel[5][i] * (127-m_nPan[5])
+ m_OutputLevel[6][i] * (127-m_nPan[6])
+ m_OutputLevel[7][i] * (127-m_nPan[7]);
nLeft >>= m_nActiveTGsLog2 + 7;
int32_t nRight = m_OutputLevel[0][i] * m_nPan[0]
+ m_OutputLevel[1][i] * m_nPan[1]
+ m_OutputLevel[2][i] * m_nPan[2]
+ m_OutputLevel[3][i] * m_nPan[3]
+ m_OutputLevel[4][i] * m_nPan[4]
+ m_OutputLevel[5][i] * m_nPan[5]
+ m_OutputLevel[6][i] * m_nPan[6]
+ m_OutputLevel[7][i] * m_nPan[7];
nRight >>= m_nActiveTGsLog2 + 7;
if (!m_bChannelsSwapped)
{
SampleBuffer[i][0] = (int16_t) nLeft;
SampleBuffer[i][1] = (int16_t) nRight;
}
else
{ {
SampleBuffer[i][0] = (int16_t) nRight; indexL=1;
SampleBuffer[i][1] = (int16_t) nLeft; 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-m_fPan[i], tmpBuffer, nFrames);
// add left panorama output of this TG to sum output
arm_add_f32(SampleBuffer[indexL], tmpBuffer, SampleBuffer[indexL], nFrames);
// calculate right panorama of this TG
arm_scale_f32(m_OutputLevel[i], m_fPan[i], tmpBuffer, nFrames);
// add right panaorama output of this TG to sum output
arm_add_f32(SampleBuffer[indexR], tmpBuffer, SampleBuffer[indexR], nFrames);
m_PanoramaSpinLock.Release ();
} }
// END stereo panorama
// BEGIN adding reverb // BEGIN adding reverb
if (m_nParameter[ParameterReverbEnable]) if (m_nParameter[ParameterReverbEnable])
{ {
float32_t ReverbBuffer[2][nFrames];
m_ReverbSpinLock.Acquire (); m_ReverbSpinLock.Acquire ();
reverb->doReverb(nFrames,SampleBuffer); reverb->doReverb(SampleBuffer[indexL],SampleBuffer[indexR],ReverbBuffer[0], ReverbBuffer[1],nFrames);
m_ReverbSpinLock.Release (); 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 // END adding reverb
if (m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) != (int) sizeof SampleBuffer) // 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"); LOGERR ("Sound data dropped");
} }
@ -812,7 +846,7 @@ bool CMiniDexed::SavePerformance (void)
m_PerformanceConfig.SetReverbLowDamp (m_nParameter[ParameterReverbLowDamp]); m_PerformanceConfig.SetReverbLowDamp (m_nParameter[ParameterReverbLowDamp]);
m_PerformanceConfig.SetReverbLowPass (m_nParameter[ParameterReverbLowPass]); m_PerformanceConfig.SetReverbLowPass (m_nParameter[ParameterReverbLowPass]);
m_PerformanceConfig.SetReverbDiffusion (m_nParameter[ParameterReverbDiffusion]); m_PerformanceConfig.SetReverbDiffusion (m_nParameter[ParameterReverbDiffusion]);
m_PerformanceConfig.SetReverbSend (m_nParameter[ParameterReverbSend]); m_PerformanceConfig.SetReverbLevel (m_nParameter[ParameterReverbLevel]);
return m_PerformanceConfig.Save (); return m_PerformanceConfig.Save ();
} }

@ -0,0 +1,853 @@
//
// 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 ();
}

@ -39,7 +39,9 @@
#include <circle/multicore.h> #include <circle/multicore.h>
#include <circle/soundbasedevice.h> #include <circle/soundbasedevice.h>
#include <circle/spinlock.h> #include <circle/spinlock.h>
#include "common.h"
#include "effect_platervbstereo.h" #include "effect_platervbstereo.h"
#include "mixer.h"
class CMiniDexed class CMiniDexed
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
@ -84,7 +86,7 @@ public:
ParameterReverbLowDamp, ParameterReverbLowDamp,
ParameterReverbLowPass, ParameterReverbLowPass,
ParameterReverbDiffusion, ParameterReverbDiffusion,
ParameterReverbSend, ParameterReverbLevel,
ParameterUnknown ParameterUnknown
}; };
@ -141,6 +143,7 @@ private:
unsigned m_nProgram[CConfig::ToneGenerators]; unsigned m_nProgram[CConfig::ToneGenerators];
unsigned m_nVolume[CConfig::ToneGenerators]; unsigned m_nVolume[CConfig::ToneGenerators];
unsigned m_nPan[CConfig::ToneGenerators]; unsigned m_nPan[CConfig::ToneGenerators];
float32_t m_fPan[CConfig::ToneGenerators];
int m_nMasterTune[CConfig::ToneGenerators]; int m_nMasterTune[CConfig::ToneGenerators];
unsigned m_nMIDIChannel[CConfig::ToneGenerators]; unsigned m_nMIDIChannel[CConfig::ToneGenerators];
@ -165,13 +168,16 @@ private:
unsigned m_nActiveTGsLog2; unsigned m_nActiveTGsLog2;
volatile TCoreStatus m_CoreStatus[CORES]; volatile TCoreStatus m_CoreStatus[CORES];
volatile unsigned m_nFramesToProcess; volatile unsigned m_nFramesToProcess;
int16_t m_OutputLevel[CConfig::ToneGenerators][CConfig::MaxChunkSize]; float32_t m_OutputLevel[CConfig::ToneGenerators][CConfig::MaxChunkSize];
#endif #endif
CPerformanceTimer m_GetChunkTimer; CPerformanceTimer m_GetChunkTimer;
bool m_bProfileEnabled; bool m_bProfileEnabled;
AudioEffectPlateReverb* reverb; AudioEffectPlateReverb* reverb;
AudioStereoMixer<8>* tg_mixer;
CSpinLock m_PanoramaSpinLock;
CSpinLock m_ReverbSpinLock; CSpinLock m_ReverbSpinLock;
}; };

@ -0,0 +1,87 @@
// 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);
}

@ -0,0 +1,79 @@
// 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

@ -109,7 +109,7 @@ NoteShift8=0
#ReverbLowDamp=50 # 0 .. 99 #ReverbLowDamp=50 # 0 .. 99
#ReverbLowPass=30 # 0 .. 99 #ReverbLowPass=30 # 0 .. 99
#ReverbDiffusion=65 # 0 .. 99 #ReverbDiffusion=65 # 0 .. 99
#ReverbSend=80 # 0 .. 99 #ReverbLevel=80 # 0 .. 99
# Effects # Effects
CompressorEnable=1 CompressorEnable=1
@ -119,4 +119,4 @@ ReverbHighDamp=50
ReverbLowDamp=50 ReverbLowDamp=50
ReverbLowPass=30 ReverbLowPass=30
ReverbDiffusion=65 ReverbDiffusion=65
ReverbSend=80 ReverbLevel=80

@ -99,7 +99,7 @@ bool CPerformanceConfig::Load (void)
m_nReverbLowDamp = m_Properties.GetNumber ("ReverbLowDamp", 50); m_nReverbLowDamp = m_Properties.GetNumber ("ReverbLowDamp", 50);
m_nReverbLowPass = m_Properties.GetNumber ("ReverbLowPass", 30); m_nReverbLowPass = m_Properties.GetNumber ("ReverbLowPass", 30);
m_nReverbDiffusion = m_Properties.GetNumber ("ReverbDiffusion", 65); m_nReverbDiffusion = m_Properties.GetNumber ("ReverbDiffusion", 65);
m_nReverbSend = m_Properties.GetNumber ("ReverbSend", 80); m_nReverbLevel = m_Properties.GetNumber ("ReverbLevel", 80);
return bResult; return bResult;
} }
@ -161,7 +161,7 @@ bool CPerformanceConfig::Save (void)
m_Properties.SetNumber ("ReverbLowDamp", m_nReverbLowDamp); m_Properties.SetNumber ("ReverbLowDamp", m_nReverbLowDamp);
m_Properties.SetNumber ("ReverbLowPass", m_nReverbLowPass); m_Properties.SetNumber ("ReverbLowPass", m_nReverbLowPass);
m_Properties.SetNumber ("ReverbDiffusion", m_nReverbDiffusion); m_Properties.SetNumber ("ReverbDiffusion", m_nReverbDiffusion);
m_Properties.SetNumber ("ReverbSend", m_nReverbSend); m_Properties.SetNumber ("ReverbLevel", m_nReverbLevel);
return m_Properties.Save (); return m_Properties.Save ();
} }
@ -309,9 +309,9 @@ unsigned CPerformanceConfig::GetReverbDiffusion (void) const
return m_nReverbDiffusion; return m_nReverbDiffusion;
} }
unsigned CPerformanceConfig::GetReverbSend (void) const unsigned CPerformanceConfig::GetReverbLevel (void) const
{ {
return m_nReverbSend; return m_nReverbLevel;
} }
void CPerformanceConfig::SetCompressorEnable (bool bValue) void CPerformanceConfig::SetCompressorEnable (bool bValue)
@ -349,7 +349,7 @@ void CPerformanceConfig::SetReverbDiffusion (unsigned nValue)
m_nReverbDiffusion = nValue; m_nReverbDiffusion = nValue;
} }
void CPerformanceConfig::SetReverbSend (unsigned nValue) void CPerformanceConfig::SetReverbLevel (unsigned nValue)
{ {
m_nReverbSend = nValue; m_nReverbLevel = nValue;
} }

@ -66,7 +66,7 @@ public:
unsigned GetReverbLowDamp (void) const; // 0 .. 99 unsigned GetReverbLowDamp (void) const; // 0 .. 99
unsigned GetReverbLowPass (void) const; // 0 .. 99 unsigned GetReverbLowPass (void) const; // 0 .. 99
unsigned GetReverbDiffusion (void) const; // 0 .. 99 unsigned GetReverbDiffusion (void) const; // 0 .. 99
unsigned GetReverbSend (void) const; // 0 .. 99 unsigned GetReverbLevel (void) const; // 0 .. 99
void SetCompressorEnable (bool bValue); void SetCompressorEnable (bool bValue);
void SetReverbEnable (bool bValue); void SetReverbEnable (bool bValue);
@ -75,7 +75,7 @@ public:
void SetReverbLowDamp (unsigned nValue); void SetReverbLowDamp (unsigned nValue);
void SetReverbLowPass (unsigned nValue); void SetReverbLowPass (unsigned nValue);
void SetReverbDiffusion (unsigned nValue); void SetReverbDiffusion (unsigned nValue);
void SetReverbSend (unsigned nValue); void SetReverbLevel (unsigned nValue);
private: private:
CPropertiesFatFsFile m_Properties; CPropertiesFatFsFile m_Properties;
@ -97,7 +97,7 @@ private:
unsigned m_nReverbLowDamp; unsigned m_nReverbLowDamp;
unsigned m_nReverbLowPass; unsigned m_nReverbLowPass;
unsigned m_nReverbDiffusion; unsigned m_nReverbDiffusion;
unsigned m_nReverbSend; unsigned m_nReverbLevel;
}; };
#endif #endif

@ -88,7 +88,7 @@ const CUIMenu::TMenuItem CUIMenu::s_ReverbMenu[] =
{"Low damp", EditGlobalParameter, 0, CMiniDexed::ParameterReverbLowDamp}, {"Low damp", EditGlobalParameter, 0, CMiniDexed::ParameterReverbLowDamp},
{"Low pass", EditGlobalParameter, 0, CMiniDexed::ParameterReverbLowPass}, {"Low pass", EditGlobalParameter, 0, CMiniDexed::ParameterReverbLowPass},
{"Diffusion", EditGlobalParameter, 0, CMiniDexed::ParameterReverbDiffusion}, {"Diffusion", EditGlobalParameter, 0, CMiniDexed::ParameterReverbDiffusion},
{"Send", EditGlobalParameter, 0, CMiniDexed::ParameterReverbSend}, {"Level", EditGlobalParameter, 0, CMiniDexed::ParameterReverbLevel},
{0} {0}
}; };
@ -166,7 +166,7 @@ const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknow
{0, 99, 1}, // ParameterReverbLowDamp {0, 99, 1}, // ParameterReverbLowDamp
{0, 99, 1}, // ParameterReverbLowPass {0, 99, 1}, // ParameterReverbLowPass
{0, 99, 1}, // ParameterReverbDiffusion {0, 99, 1}, // ParameterReverbDiffusion
{0, 99, 1} // ParameterReverbSend {0, 99, 1} // ParameterReverbLevel
}; };
// must match CMiniDexed::TTGParameter // must match CMiniDexed::TTGParameter

Loading…
Cancel
Save