diff --git a/README.md b/README.md index 66dbb150..aa277e40 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ sudo losetup -d "${DEV}" rm -r boot # 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 diff --git a/Synth_Dexed b/Synth_Dexed index 70293ae5..e414a871 160000 --- a/Synth_Dexed +++ b/Synth_Dexed @@ -1 +1 @@ -Subproject commit 70293ae5998643c706244b090504dde8b4097851 +Subproject commit e414a8718300815aefc3fe0acd8df5c12ad0b58a diff --git a/src/Synth_Dexed.mk b/src/Synth_Dexed.mk index b411b0b0..498038a7 100644 --- a/src/Synth_Dexed.mk +++ b/src/Synth_Dexed.mk @@ -30,6 +30,6 @@ INCLUDE += -I $(SYNTH_DEXED_DIR) INCLUDE += -I $(CMSIS_CORE_INCLUDE_DIR) INCLUDE += -I $(CMSIS_DSP_INCLUDE_DIR) INCLUDE += -I $(CMSIS_DSP_PRIVATE_INCLUDE_DIR) -CXXFLAGS += -DARM_MATH_NEON +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] diff --git a/src/common.h b/src/common.h new file mode 100644 index 00000000..ef6a51f3 --- /dev/null +++ b/src/common.h @@ -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 diff --git a/src/dexedadapter.h b/src/dexedadapter.h index 7f2badc5..be04f42d 100644 --- a/src/dexedadapter.h +++ b/src/dexedadapter.h @@ -17,6 +17,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // + #ifndef _dexedadapter_h #define _dexedadapter_h @@ -56,10 +57,10 @@ class CDexedAdapter : public Dexed m_SpinLock.Release (); } - void getSamples (uint16_t n_samples, int16_t* buffer) + void getSamples (float32_t* buffer, uint16_t n_samples) { m_SpinLock.Acquire (); - Dexed::getSamples (n_samples, buffer); + Dexed::getSamples (buffer, n_samples); m_SpinLock.Release (); } diff --git a/src/effect_platervbstereo.cpp b/src/effect_platervbstereo.cpp index 4d987e90..ce2af1e4 100644 --- a/src/effect_platervbstereo.cpp +++ b/src/effect_platervbstereo.cpp @@ -153,14 +153,13 @@ AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate) lfo2_phase_acc = 0; 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)) -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; uint16_t temp16; float32_t rv_time; @@ -203,7 +202,7 @@ void AudioEffectPlateReverb::doReverb(uint16_t len, int16_t audioblock[][2]) rv_time = rv_time_k; - for (i=0; i < len; i++) + for (uint16_t i=0; i < len; i++) { // do the LFOs lfo1_phase_acc += lfo1_adder; @@ -236,7 +235,7 @@ void AudioEffectPlateReverb::doReverb(uint16_t len, int16_t audioblock[][2]) y += (int64_t)y1 * idx; 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 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; 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 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; master_lowpass_l += temp1 * master_lowpass_f; - int32_t out = audioblock[i][0] + int16_t(master_lowpass_l * 32767.0f * send_level); - if(out > INT16_MAX) - audioblock[i][0] = INT16_MAX; - else if(out < INT16_MIN) - audioblock[i][0] = INT16_MIN; - else - audioblock[i][0] = out; + rvbblockL[i] = master_lowpass_l; // Channel R #ifdef TAP1_MODULATED @@ -456,12 +449,6 @@ void AudioEffectPlateReverb::doReverb(uint16_t len, int16_t audioblock[][2]) temp1 = acc - master_lowpass_r; master_lowpass_r += temp1 * master_lowpass_f; - out = audioblock[i][1] + int16_t(master_lowpass_l * 32767.0f * send_level); - if(out > INT16_MAX) - audioblock[i][1] = INT16_MAX; - else if(out < INT16_MIN) - audioblock[i][1] = INT16_MIN; - else - audioblock[i][1] = out; + rvbblockR[i] = master_lowpass_r; } } diff --git a/src/effect_platervbstereo.h b/src/effect_platervbstereo.h index 3abc62df..b7af5313 100644 --- a/src/effect_platervbstereo.h +++ b/src/effect_platervbstereo.h @@ -45,37 +45,9 @@ #ifndef _EFFECT_PLATERVBSTEREO_H #define _EFFECT_PLATERVBSTEREO_H -#include "arm_math.h" #include - -#define constrain(amt, low, high) ({ \ - __typeof__(amt) _amt = (amt); \ - __typeof__(low) _low = (low); \ - __typeof__(high) _high = (high); \ - (_amt < _low) ? _low : ((_amt > _high) ? _high : _amt); \ -}) - -/* -template -inline static T min(const T& a, const T& b) { - return a < b ? a : b; -} - -template -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; -} - +#include +#include "common.h" /*** * Loop delay modulation: comment/uncomment to switch sin/cos @@ -89,34 +61,28 @@ class AudioEffectPlateReverb { public: 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) { n = constrain(n, 0.0f, 1.0f); 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); - //__disable_irq(); rv_time_k = n; input_attn = attn; - //__enable_irq(); } void hidamp(float n) { n = constrain(n, 0.0f, 1.0f); - //__disable_irq(); lp_hidamp_k = 1.0f - n; - //__enable_irq(); } void lodamp(float n) { n = constrain(n, 0.0f, 1.0f); - //__disable_irq(); lp_lodamp_k = -n; rv_time_scaler = 1.0f - n * 0.12f; // limit the max reverb time, otherwise it will clip - //__enable_irq(); } void lowpass(float n) @@ -130,24 +96,23 @@ class AudioEffectPlateReverb { n = constrain(n, 0.0f, 1.0f); n = mapfloat(n, 0.0f, 1.0f, 0.005f, 0.65f); - //__disable_irq(); in_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;} bool get_bypass(void) {return bypass;} void set_bypass(bool state) {bypass = state;}; void tgl_bypass(void) {bypass ^=1;} + float32_t get_level(void) {return reverb_level;} private: bool bypass = false; - float32_t send_level; + float32_t reverb_level; float32_t input_attn; float32_t in_allp_k; // input allpass coeff diff --git a/src/minidexed.cpp b/src/minidexed.cpp index eb4ba129..f33a5cad 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -58,6 +58,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_nProgram[i] = 0; m_nVolume[i] = 100; m_nPan[i] = 64; + m_fPan[i] = 0.5f; m_nMasterTune[i] = 0; m_nMIDIChannel[i] = CMIDIDevice::Disabled; @@ -113,8 +114,6 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, } #endif - SetParameter (ParameterCompressorEnable, 1); - // BEGIN setup reverb reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate()); SetParameter (ParameterReverbEnable, 1); @@ -123,8 +122,10 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, SetParameter (ParameterReverbLowDamp, 50); SetParameter (ParameterReverbLowPass, 30); SetParameter (ParameterReverbDiffusion, 65); - SetParameter (ParameterReverbSend, 80); + SetParameter (ParameterReverbLevel, 80); // END setup reverb + + SetParameter (ParameterCompressorEnable, 1); }; bool CMiniDexed::Initialize (void) @@ -183,7 +184,7 @@ bool CMiniDexed::Initialize (void) SetParameter (ParameterReverbLowDamp, m_PerformanceConfig.GetReverbLowDamp ()); SetParameter (ParameterReverbLowPass, m_PerformanceConfig.GetReverbLowPass ()); SetParameter (ParameterReverbDiffusion, m_PerformanceConfig.GetReverbDiffusion ()); - SetParameter (ParameterReverbSend, m_PerformanceConfig.GetReverbSend ()); + SetParameter (ParameterReverbLevel, m_PerformanceConfig.GetReverbLevel ()); } else { @@ -298,7 +299,7 @@ void CMiniDexed::Run (unsigned nCore) for (unsigned i = 0; i < CConfig::TGsCore23; i++, nTG++) { assert (m_pTG[nTG]); - m_pTG[nTG]->getSamples (m_nFramesToProcess, m_OutputLevel[nTG]); + m_pTG[nTG]->getSamples (m_OutputLevel[nTG],m_nFramesToProcess); } } } @@ -368,7 +369,8 @@ void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) assert (nTG < CConfig::ToneGenerators); m_nPan[nTG] = nPan; - + m_fPan[nTG]=mapfloat(nPan,0,127,0.0,1.0); + m_UI.ParameterChanged (); } @@ -550,9 +552,9 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue) m_ReverbSpinLock.Release (); break; - case ParameterReverbSend: + case ParameterReverbLevel: m_ReverbSpinLock.Acquire (); - reverb->send (nValue / 99.0); + reverb->level (nValue / 99.0); m_ReverbSpinLock.Release (); break; @@ -672,11 +674,20 @@ void CMiniDexed::ProcessSound (void) m_GetChunkTimer.Start (); } - int16_t SampleBuffer[nFrames]; - m_pTG[0]->getSamples (nFrames, SampleBuffer); + float32_t SampleBuffer[nFrames]; + m_pTG[0]->getSamples (SampleBuffer, nFrames); - 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; iWrite (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) { LOGERR ("Sound data dropped"); } @@ -716,7 +727,7 @@ void CMiniDexed::ProcessSound (void) for (unsigned i = 0; i < CConfig::TGsCore1; 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 @@ -728,54 +739,77 @@ void CMiniDexed::ProcessSound (void) } } + // + // Audio signal path after tone generators starts here + // + // now mix the output of all TGs - int16_t SampleBuffer[nFrames][2]; + 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); - for (unsigned i = 0; i < nFrames; i++) + + // BEGIN stereo panorama + for (uint8_t i = 0; i < CConfig::ToneGenerators; 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; - SampleBuffer[i][1] = (int16_t) nLeft; - } + 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 if (m_nParameter[ParameterReverbEnable]) { + float32_t ReverbBuffer[2][nFrames]; + m_ReverbSpinLock.Acquire (); - reverb->doReverb(nFrames,SampleBuffer); + 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 - 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; iWrite (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) { LOGERR ("Sound data dropped"); } @@ -812,7 +846,7 @@ bool CMiniDexed::SavePerformance (void) m_PerformanceConfig.SetReverbLowDamp (m_nParameter[ParameterReverbLowDamp]); m_PerformanceConfig.SetReverbLowPass (m_nParameter[ParameterReverbLowPass]); m_PerformanceConfig.SetReverbDiffusion (m_nParameter[ParameterReverbDiffusion]); - m_PerformanceConfig.SetReverbSend (m_nParameter[ParameterReverbSend]); + m_PerformanceConfig.SetReverbLevel (m_nParameter[ParameterReverbLevel]); return m_PerformanceConfig.Save (); } diff --git a/src/minidexed.cpp.O b/src/minidexed.cpp.O new file mode 100644 index 00000000..17a9c774 --- /dev/null +++ b/src/minidexed.cpp.O @@ -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 . +// +#include "minidexed.h" +#include +#include +#include +#include +#include +#include +#include +#include + +LOGMODULE ("minidexed"); + +CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, + CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, FATFS *pFileSystem) +: +#ifdef ARM_ALLOW_MULTI_CORE + CMultiCoreSupport (CMemorySystem::Get ()), +#endif + m_pConfig (pConfig), + m_UI (this, pGPIOManager, pConfig), + m_PerformanceConfig (pFileSystem), + m_PCKeyboard (this, pConfig), + m_SerialMIDI (this, pInterrupt, pConfig), + m_bUseSerial (false), + m_pSoundDevice (0), + m_bChannelsSwapped (pConfig->GetChannelsSwapped ()), +#ifdef ARM_ALLOW_MULTI_CORE + m_nActiveTGsLog2 (0), +#endif + m_GetChunkTimer ("GetChunk", + 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), + m_bProfileEnabled (m_pConfig->GetProfileEnabled ()) +{ + assert (m_pConfig); + + for (unsigned i = 0; i < CConfig::ToneGenerators; i++) + { + m_nVoiceBankID[i] = 0; + m_nProgram[i] = 0; + m_nVolume[i] = 100; + m_nPan[i] = 64; + pan_float[i]=0.0f; + m_nMasterTune[i] = 0; + m_nMIDIChannel[i] = CMIDIDevice::Disabled; + + m_nNoteLimitLow[i] = 0; + m_nNoteLimitHigh[i] = 127; + m_nNoteShift[i] = 0; + + m_pTG[i] = new CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()); + assert (m_pTG[i]); + + m_pTG[i]->activate (); + } + + for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) + { + m_pMIDIKeyboard[i] = new CMIDIKeyboard (this, pConfig, i); + assert (m_pMIDIKeyboard[i]); + } + + // select the sound device + const char *pDeviceName = pConfig->GetSoundDevice (); + if (strcmp (pDeviceName, "i2s") == 0) + { + LOGNOTE ("I2S mode"); + + m_pSoundDevice = new CI2SSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), + pConfig->GetChunkSize (), false, + pI2CMaster, pConfig->GetDACI2CAddress ()); + } + else if (strcmp (pDeviceName, "hdmi") == 0) + { + LOGNOTE ("HDMI mode"); + + m_pSoundDevice = new CHDMISoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), + pConfig->GetChunkSize ()); + + // The channels are swapped by default in the HDMI sound driver. + // TODO: Remove this line, when this has been fixed in the driver. + m_bChannelsSwapped = !m_bChannelsSwapped; + } + else + { + LOGNOTE ("PWM mode"); + + m_pSoundDevice = new CPWMSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), + pConfig->GetChunkSize ()); + } + +#ifdef ARM_ALLOW_MULTI_CORE + for (unsigned nCore = 0; nCore < CORES; nCore++) + { + m_CoreStatus[nCore] = CoreStatusInit; + } +#endif + + // BEGIN setup tg_mixer + //tg_mixer = new AudioStereoMixer<8>(); + // END setup tg_mixer + + SetParameter (ParameterCompressorEnable, 1); + + // BEGIN setup reverb + reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate()); + SetParameter (ParameterReverbEnable, 1); + SetParameter (ParameterReverbSize, 70); + SetParameter (ParameterReverbHighDamp, 50); + SetParameter (ParameterReverbLowDamp, 50); + SetParameter (ParameterReverbLowPass, 30); + SetParameter (ParameterReverbDiffusion, 20); + SetParameter (ParameterReverbLevel, 80); + // END setup reverb +}; + +bool CMiniDexed::Initialize (void) +{ + assert (m_pConfig); + assert (m_pSoundDevice); + + if (!m_UI.Initialize ()) + { + return false; + } + + m_SysExFileLoader.Load (); + + if (m_SerialMIDI.Initialize ()) + { + LOGNOTE ("Serial MIDI interface enabled"); + + m_bUseSerial = true; + } + + for (unsigned i = 0; i < CConfig::ToneGenerators; i++) + { + assert (m_pTG[i]); + + SetVolume (100, i); + ProgramChange (0, i); + + m_pTG[i]->setTranspose (24); + + m_pTG[i]->setPBController (12, 1); + m_pTG[i]->setMWController (99, 7, 0); + } + + if (m_PerformanceConfig.Load ()) + { + for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + { + BankSelectLSB (m_PerformanceConfig.GetBankNumber (nTG), nTG); + ProgramChange (m_PerformanceConfig.GetVoiceNumber (nTG), nTG); + SetMIDIChannel (m_PerformanceConfig.GetMIDIChannel (nTG), nTG); + SetVolume (m_PerformanceConfig.GetVolume (nTG), nTG); + SetPan (m_PerformanceConfig.GetPan (nTG), nTG); + SetMasterTune (m_PerformanceConfig.GetDetune (nTG), nTG); + + m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG); + m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG); + m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG); + } + + // Effects + SetParameter (ParameterCompressorEnable, m_PerformanceConfig.GetCompressorEnable () ? 1 : 0); + SetParameter (ParameterReverbEnable, m_PerformanceConfig.GetReverbEnable () ? 1 : 0); + SetParameter (ParameterReverbSize, m_PerformanceConfig.GetReverbSize ()); + SetParameter (ParameterReverbHighDamp, m_PerformanceConfig.GetReverbHighDamp ()); + SetParameter (ParameterReverbLowDamp, m_PerformanceConfig.GetReverbLowDamp ()); + SetParameter (ParameterReverbLowPass, m_PerformanceConfig.GetReverbLowPass ()); + SetParameter (ParameterReverbDiffusion, m_PerformanceConfig.GetReverbDiffusion ()); + SetParameter (ParameterReverbLevel, m_PerformanceConfig.GetReverbLevel ()); + } + else + { + SetMIDIChannel (CMIDIDevice::OmniMode, 0); + } + + // setup and start the sound device + if (!m_pSoundDevice->AllocateQueueFrames (m_pConfig->GetChunkSize ())) + { + LOGERR ("Cannot allocate sound queue"); + + return false; + } + +#ifndef ARM_ALLOW_MULTI_CORE + m_pSoundDevice->SetWriteFormat (SoundFormatSigned16, 1); // 16-bit Mono +#else + m_pSoundDevice->SetWriteFormat (SoundFormatSigned16, 2); // 16-bit Stereo +#endif + + m_nQueueSizeFrames = m_pSoundDevice->GetQueueSizeFrames (); + + m_pSoundDevice->Start (); + +#ifdef ARM_ALLOW_MULTI_CORE + // start secondary cores + if (!CMultiCoreSupport::Initialize ()) + { + return false; + } +#endif + + return true; +} + +void CMiniDexed::Process (bool bPlugAndPlayUpdated) +{ +#ifndef ARM_ALLOW_MULTI_CORE + ProcessSound (); +#endif + + for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) + { + assert (m_pMIDIKeyboard[i]); + m_pMIDIKeyboard[i]->Process (bPlugAndPlayUpdated); + } + + m_PCKeyboard.Process (bPlugAndPlayUpdated); + + if (m_bUseSerial) + { + m_SerialMIDI.Process (); + } + + m_UI.Process (); + + if (m_bProfileEnabled) + { + m_GetChunkTimer.Dump (); + } +} + +#ifdef ARM_ALLOW_MULTI_CORE + +void CMiniDexed::Run (unsigned nCore) +{ + assert (1 <= nCore && nCore < CORES); + + if (nCore == 1) + { + m_CoreStatus[nCore] = CoreStatusIdle; // core 1 ready + + // wait for cores 2 and 3 to be ready + for (unsigned nCore = 2; nCore < CORES; nCore++) + { + while (m_CoreStatus[nCore] != CoreStatusIdle) + { + // just wait + } + } + + while (m_CoreStatus[nCore] != CoreStatusExit) + { + ProcessSound (); + } + } + else // core 2 and 3 + { + while (1) + { + m_CoreStatus[nCore] = CoreStatusIdle; // ready to be kicked + while (m_CoreStatus[nCore] == CoreStatusIdle) + { + // just wait + } + + // now kicked from core 1 + + if (m_CoreStatus[nCore] == CoreStatusExit) + { + m_CoreStatus[nCore] = CoreStatusUnknown; + + break; + } + + assert (m_CoreStatus[nCore] == CoreStatusBusy); + + // process the TGs, assigned to this core (2 or 3) + + assert (m_nFramesToProcess <= CConfig::MaxChunkSize); + unsigned nTG = CConfig::TGsCore1 + (nCore-2)*CConfig::TGsCore23; + for (unsigned i = 0; i < CConfig::TGsCore23; i++, nTG++) + { + assert (m_pTG[nTG]); + m_pTG[nTG]->getSamples (m_OutputLevel[nTG], m_nFramesToProcess); + } + } + } +} + +#endif + +CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void) +{ + return &m_SysExFileLoader; +} + +void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG) +{ + if (nBankLSB > 127) + { + return; + } + + assert (nTG < CConfig::ToneGenerators); + m_nVoiceBankID[nTG] = nBankLSB; + + m_UI.ParameterChanged (); +} + +void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) +{ + if (nProgram > 31) + { + return; + } + + assert (nTG < CConfig::ToneGenerators); + m_nProgram[nTG] = nProgram; + + uint8_t Buffer[156]; + m_SysExFileLoader.GetVoice (m_nVoiceBankID[nTG], nProgram, Buffer); + + assert (m_pTG[nTG]); + m_pTG[nTG]->loadVoiceParameters (Buffer); + + m_UI.ParameterChanged (); +} + +void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) +{ + if (nVolume > 127) + { + return; + } + + assert (nTG < CConfig::ToneGenerators); + m_nVolume[nTG] = nVolume; + + assert (m_pTG[nTG]); + m_pTG[nTG]->setGain (nVolume / 127.0); + + m_UI.ParameterChanged (); +} + +void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) +{ + constrain(nPan,-1.0f,1.0f); + + assert (nTG < CConfig::ToneGenerators); + m_nPan[nTG] = nPan; + pan_float[nTG]=mapfloat(nPan,0,127,-1.0,1.0); + + m_UI.ParameterChanged (); +} + +void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG) +{ + if (!(-99 <= nMasterTune && nMasterTune <= 99)) + { + return; + } + + assert (nTG < CConfig::ToneGenerators); + m_nMasterTune[nTG] = nMasterTune; + + assert (m_pTG[nTG]); + m_pTG[nTG]->setMasterTune ((int8_t) nMasterTune); + + m_UI.ParameterChanged (); +} + +void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + m_nMIDIChannel[nTG] = uchChannel; + + for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) + { + assert (m_pMIDIKeyboard[i]); + m_pMIDIKeyboard[i]->SetChannel (uchChannel, nTG); + } + + m_PCKeyboard.SetChannel (uchChannel, nTG); + + if (m_bUseSerial) + { + m_SerialMIDI.SetChannel (uchChannel, nTG); + } + +#ifdef ARM_ALLOW_MULTI_CORE + unsigned nActiveTGs = 0; + for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + { + if (m_nMIDIChannel[nTG] != CMIDIDevice::Disabled) + { + nActiveTGs++; + } + } + + assert (nActiveTGs <= 8); + static const unsigned Log2[] = {0, 0, 1, 2, 2, 3, 3, 3, 3}; + m_nActiveTGsLog2 = Log2[nActiveTGs]; +#endif + + m_UI.ParameterChanged (); +} + +void CMiniDexed::keyup (int16_t pitch, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + + pitch = ApplyNoteLimits (pitch, nTG); + if (pitch >= 0) + { + m_pTG[nTG]->keyup (pitch); + } +} + +void CMiniDexed::keydown (int16_t pitch, uint8_t velocity, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + + pitch = ApplyNoteLimits (pitch, nTG); + if (pitch >= 0) + { + m_pTG[nTG]->keydown (pitch, velocity); + } +} + +int16_t CMiniDexed::ApplyNoteLimits (int16_t pitch, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + + if ( pitch < (int16_t) m_nNoteLimitLow[nTG] + || pitch > (int16_t) m_nNoteLimitHigh[nTG]) + { + return -1; + } + + pitch += m_nNoteShift[nTG]; + + if ( pitch < 0 + || pitch > 127) + { + return -1; + } + + return pitch; +} + +void CMiniDexed::setSustain(bool sustain, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->setSustain (sustain); +} + +void CMiniDexed::setModWheel (uint8_t value, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->setModWheel (value); +} + +void CMiniDexed::setPitchbend (int16_t value, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->setPitchbend (value); +} + +void CMiniDexed::ControllersRefresh (unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->ControllersRefresh (); +} + +void CMiniDexed::SetParameter (TParameter Parameter, int nValue) +{ + assert (reverb); + + assert (Parameter < ParameterUnknown); + m_nParameter[Parameter] = nValue; + + switch (Parameter) + { +<<<<<<< HEAD + case ParameterReverbSize: reverb->size (fValue); break; + case ParameterReverbHighDamp: reverb->hidamp (fValue); break; + case ParameterReverbLowDamp: reverb->lodamp (fValue); break; + case ParameterReverbLowPass: reverb->lowpass (fValue); break; + case ParameterReverbDiffusion: reverb->diffusion (fValue); break; + case ParameterReverbLevel: reverb->level (fValue); break; +======= + case ParameterCompressorEnable: + for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + { + assert (m_pTG[nTG]); + m_pTG[nTG]->setCompressor (!!nValue); + } + break; + + case ParameterReverbEnable: + m_ReverbSpinLock.Acquire (); + reverb->set_bypass (!nValue); + m_ReverbSpinLock.Release (); + break; + + case ParameterReverbSize: + m_ReverbSpinLock.Acquire (); + reverb->size (nValue / 99.0); + m_ReverbSpinLock.Release (); + break; + + case ParameterReverbHighDamp: + m_ReverbSpinLock.Acquire (); + reverb->hidamp (nValue / 99.0); + m_ReverbSpinLock.Release (); + break; + + case ParameterReverbLowDamp: + m_ReverbSpinLock.Acquire (); + reverb->lodamp (nValue / 99.0); + m_ReverbSpinLock.Release (); + break; + + case ParameterReverbLowPass: + m_ReverbSpinLock.Acquire (); + reverb->lowpass (nValue / 99.0); + m_ReverbSpinLock.Release (); + break; + + case ParameterReverbDiffusion: + m_ReverbSpinLock.Acquire (); + reverb->diffusion (nValue / 99.0); + m_ReverbSpinLock.Release (); + break; + + case ParameterReverbLevel: + m_ReverbSpinLock.Acquire (); + reverb->level (nValue / 99.0); + m_ReverbSpinLock.Release (); + break; + + default: + assert (0); + break; + } +} + +int CMiniDexed::GetParameter (TParameter Parameter) +{ + assert (Parameter < ParameterUnknown); + return m_nParameter[Parameter]; +} + +void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + + switch (Parameter) + { + case TGParameterVoiceBank: BankSelectLSB (nValue, nTG); break; + case TGParameterProgram: ProgramChange (nValue, nTG); break; + case TGParameterVolume: SetVolume (nValue, nTG); break; + case TGParameterPan: SetPan (nValue, nTG); break; + case TGParameterMasterTune: SetMasterTune (nValue, nTG); break; + + case TGParameterMIDIChannel: + assert (0 <= nValue && nValue <= 255); + SetMIDIChannel ((uint8_t) nValue, nTG); + break; + + default: + assert (0); + break; + } +} + +int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + + switch (Parameter) + { + case TGParameterVoiceBank: return m_nVoiceBankID[nTG]; + case TGParameterProgram: return m_nProgram[nTG]; + case TGParameterVolume: return m_nVolume[nTG]; + case TGParameterPan: return m_nPan[nTG]; + case TGParameterMasterTune: return m_nMasterTune[nTG]; + case TGParameterMIDIChannel: return m_nMIDIChannel[nTG]; + + default: + assert (0); + return 0; + } +} + +void CMiniDexed::SetVoiceParameter (uint8_t uchOffset, uint8_t uchValue, unsigned nOP, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + assert (nOP <= 6); + + if (nOP < 6) + { + nOP = 5 - nOP; // OPs are in reverse order + } + + uchOffset += nOP * 21; + assert (uchOffset < 156); + + m_pTG[nTG]->setVoiceDataElement (uchOffset, uchValue); +} + +uint8_t CMiniDexed::GetVoiceParameter (uint8_t uchOffset, unsigned nOP, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + assert (nOP <= 6); + + if (nOP < 6) + { + nOP = 5 - nOP; // OPs are in reverse order + } + + uchOffset += nOP * 21; + assert (uchOffset < 156); + + return m_pTG[nTG]->getVoiceDataElement (uchOffset); +} + +std::string CMiniDexed::GetVoiceName (unsigned nTG) +{ + char VoiceName[11]; + memset (VoiceName, 0, sizeof VoiceName); + + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + m_pTG[nTG]->setName (VoiceName); + + std::string Result (VoiceName); + + return Result; +} + +#ifndef ARM_ALLOW_MULTI_CORE + +void CMiniDexed::ProcessSound (void) +{ + assert (m_pSoundDevice); + + unsigned nFrames = m_nQueueSizeFrames - m_pSoundDevice->GetQueueFramesAvail (); + if (nFrames >= m_nQueueSizeFrames/2) + { + if (m_bProfileEnabled) + { + m_GetChunkTimer.Start (); + } + + //int16_t SampleBuffer[nFrames]; // TODO float->int + m_pTG[0]->getSamples (SampleBuffer, nFrames); + + if ( m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) + != (int) sizeof SampleBuffer) + { + LOGERR ("Sound data dropped"); + } + + if (m_bProfileEnabled) + { + m_GetChunkTimer.Stop (); + } + } +} + +#else // #ifdef ARM_ALLOW_MULTI_CORE + +void CMiniDexed::ProcessSound (void) +{ + assert (m_pSoundDevice); + + unsigned nFrames = m_nQueueSizeFrames - m_pSoundDevice->GetQueueFramesAvail (); + if (nFrames >= m_nQueueSizeFrames/2) + { + if (m_bProfileEnabled) + { + m_GetChunkTimer.Start (); + } + + m_nFramesToProcess = nFrames; + + // kick secondary cores + for (unsigned nCore = 2; nCore < CORES; nCore++) + { + assert (m_CoreStatus[nCore] == CoreStatusIdle); + m_CoreStatus[nCore] = CoreStatusBusy; + } + + // process the TGs assigned to core 1 + assert (nFrames <= CConfig::MaxChunkSize); + for (unsigned i = 0; i < CConfig::TGsCore1; i++) + { + assert (m_pTG[i]); + m_pTG[i]->getSamples (m_OutputLevel[i], nFrames); + } + + // wait for cores 2 and 3 to complete their work + for (unsigned nCore = 2; nCore < CORES; nCore++) + { + while (m_CoreStatus[nCore] != CoreStatusIdle) + { + // just wait + } + } + + // + // Audio signal path after tone generators starts here + // + + // now mix the output of all TGs + float32_t SampleBuffer[2][nFrames]; + uint8_t indexL=0, indexR=1; + + if (m_bChannelsSwapped) + { + indexL=1; + indexR=0; + } + + // init left sum output + assert (SampleBuffer[0]!=NULL); + arm_fill_f32(0.0, SampleBuffer[0], nFrames); + // init right sum output + assert (SampleBuffer[1]!=NULL); + arm_fill_f32(0.0, SampleBuffer[1], nFrames); + + assert (CConfig::ToneGenerators == 8); + + // BEGIN stereo panorama + for (uint8_t i = 0; i < CConfig::ToneGenerators; i++) + { + float32_t tmpBuffer[nFrames]; + + m_PanoramaSpinLock.Acquire (); + // calculate left panorama of this TG + arm_scale_f32(m_OutputLevel[i], 1.0f-pan_float[i], tmpBuffer, nFrames); + // add left panorama output of this TG to sum output + arm_add_f32(SampleBuffer[indexL], tmpBuffer, SampleBuffer[indexL], nFrames); + + // calculate right panorama of this TG + arm_scale_f32(m_OutputLevel[i], pan_float[i], tmpBuffer, nFrames); + // add right panaorama output of this TG to sum output + arm_add_f32(SampleBuffer[indexR], tmpBuffer, SampleBuffer[indexR], nFrames); + + m_PanoramaSpinLock.Release (); + } + // END stereo panorama + + // BEGIN adding reverb + if (m_nParameter[ParameterReverbEnable]) + { + float32_t ReverbBuffer[2][nFrames]; + + m_ReverbSpinLock.Acquire (); + reverb->doReverb(SampleBuffer[indexL],SampleBuffer[indexR],ReverbBuffer[0], ReverbBuffer[1],nFrames); + m_ReverbSpinLock.Release (); + + // scale down and add left reverb buffer by reverb level + arm_scale_f32(ReverbBuffer[0], reverb->get_level(), ReverbBuffer[0], nFrames); + arm_add_f32(SampleBuffer[indexL], ReverbBuffer[0], SampleBuffer[indexL], nFrames); + // scale down and add right reverb buffer by reverb level + arm_scale_f32(ReverbBuffer[1], reverb->get_level(), ReverbBuffer[1], nFrames); + arm_add_f32(SampleBuffer[indexR], ReverbBuffer[1], SampleBuffer[indexR], nFrames); + } + // END adding reverb + + // Convert dual float array (left, right) to single int16 array (left/right) + float32_t tmp_float[nFrames*2]; + int16_t tmp_int[nFrames*2]; + for(uint16_t i=0; iWrite (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) + { + LOGERR ("Sound data dropped"); + } + + if (m_bProfileEnabled) + { + m_GetChunkTimer.Stop (); + } + } +} + +#endif + +bool CMiniDexed::SavePerformance (void) +{ + for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + { + m_PerformanceConfig.SetBankNumber (m_nVoiceBankID[nTG], nTG); + m_PerformanceConfig.SetVoiceNumber (m_nProgram[nTG], nTG); + m_PerformanceConfig.SetMIDIChannel (m_nMIDIChannel[nTG], nTG); + m_PerformanceConfig.SetVolume (m_nVolume[nTG], nTG); + m_PerformanceConfig.SetPan (m_nPan[nTG], nTG); + m_PerformanceConfig.SetDetune (m_nMasterTune[nTG], nTG); + + m_PerformanceConfig.SetNoteLimitLow (m_nNoteLimitLow[nTG], nTG); + m_PerformanceConfig.SetNoteLimitHigh (m_nNoteLimitHigh[nTG], nTG); + m_PerformanceConfig.SetNoteShift (m_nNoteShift[nTG], nTG); + } + + m_PerformanceConfig.SetCompressorEnable (!!m_nParameter[ParameterCompressorEnable]); + m_PerformanceConfig.SetReverbEnable (!!m_nParameter[ParameterReverbEnable]); + m_PerformanceConfig.SetReverbSize (m_nParameter[ParameterReverbSize]); + m_PerformanceConfig.SetReverbHighDamp (m_nParameter[ParameterReverbHighDamp]); + m_PerformanceConfig.SetReverbLowDamp (m_nParameter[ParameterReverbLowDamp]); + m_PerformanceConfig.SetReverbLowPass (m_nParameter[ParameterReverbLowPass]); + m_PerformanceConfig.SetReverbDiffusion (m_nParameter[ParameterReverbDiffusion]); + m_PerformanceConfig.SetReverbLevel (m_nParameter[ParameterReverbLevel]); + + return m_PerformanceConfig.Save (); +} diff --git a/src/minidexed.h b/src/minidexed.h index 2104091f..1aed730c 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -39,7 +39,9 @@ #include #include #include +#include "common.h" #include "effect_platervbstereo.h" +#include "mixer.h" class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE @@ -84,7 +86,7 @@ class CMiniDexed ParameterReverbLowDamp, ParameterReverbLowPass, ParameterReverbDiffusion, - ParameterReverbSend, + ParameterReverbLevel, ParameterUnknown }; @@ -141,6 +143,7 @@ class CMiniDexed unsigned m_nProgram[CConfig::ToneGenerators]; unsigned m_nVolume[CConfig::ToneGenerators]; unsigned m_nPan[CConfig::ToneGenerators]; + float32_t m_fPan[CConfig::ToneGenerators]; int m_nMasterTune[CConfig::ToneGenerators]; unsigned m_nMIDIChannel[CConfig::ToneGenerators]; @@ -165,13 +168,16 @@ class CMiniDexed unsigned m_nActiveTGsLog2; volatile TCoreStatus m_CoreStatus[CORES]; volatile unsigned m_nFramesToProcess; - int16_t m_OutputLevel[CConfig::ToneGenerators][CConfig::MaxChunkSize]; + float32_t m_OutputLevel[CConfig::ToneGenerators][CConfig::MaxChunkSize]; #endif CPerformanceTimer m_GetChunkTimer; bool m_bProfileEnabled; AudioEffectPlateReverb* reverb; + AudioStereoMixer<8>* tg_mixer; + + CSpinLock m_PanoramaSpinLock; CSpinLock m_ReverbSpinLock; }; diff --git a/src/mixer.cpp b/src/mixer.cpp new file mode 100644 index 00000000..16f4b511 --- /dev/null +++ b/src/mixer.cpp @@ -0,0 +1,87 @@ +// Taken from https://github.com/manicken/Audio/tree/templateMixer +// Adapted for MiniDexed by Holger Wirtz + +/* Audio Library for Teensy 3.X + * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include "arm_math.h" +#include "mixer.h" + +template void AudioMixer::gain(uint8_t channel, float32_t gain) +{ + if (channel >= NN) return; + + if (gain > MAX_GAIN) + gain = MAX_GAIN; + else if (gain < MIN_GAIN) + gain = MIN_GAIN; + multiplier[channel] = gain; +} + +template void AudioMixer::gain(float32_t gain) +{ + for (uint8_t i = 0; i < NN; i++) + { + if (gain > MAX_GAIN) + gain = MAX_GAIN; + else if (gain < MIN_GAIN) + gain = MIN_GAIN; + multiplier[i] = gain; + } +} + +template void AudioMixer::doAddMix(uint8_t channel, float32_t* in, float32_t* out, uint16_t len) +{ + float32_t* tmp=malloc(sizeof(float32_t)*len); + + assert(tmp!=NULL); + + arm_scale_f32(in,multiplier[channel],tmp,len); + arm_add_f32(out, tmp, out, len); + + free(tmp); +} + +template void AudioStereoMixer::doAddMix(uint8_t channel, float32_t* in[], float32_t* out[], uint16_t len) +{ + float32_t* tmp=malloc(sizeof(float32_t)*len); + + assert(tmp!=NULL); + + // panorama + for(uint16_t i=0;i + +/* Audio Library for Teensy 3.X + * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef template_mixer_h_ +#define template_mixer_h_ + +#include "arm_math.h" +#include + +#define UNITYGAIN 1.0f +#define MAX_GAIN 1.0f +#define MIN_GAIN 0.0f + +template class AudioMixer +{ +public: + AudioMixer(void) + { + for (uint8_t i=0; i class AudioStereoMixer : public AudioMixer +{ +public: + AudioStereoMixer(void) + { + AudioMixer(); + for (uint8_t i=0; i