You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
MiniDexed/src/minidexed.cpp

852 lines
20 KiB

//
// 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;
m_fPan[i] = 0.5f;
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 reverb
reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate());
SetParameter (ParameterReverbEnable, 1);
SetParameter (ParameterReverbSize, 70);
SetParameter (ParameterReverbHighDamp, 50);
SetParameter (ParameterReverbLowDamp, 50);
SetParameter (ParameterReverbLowPass, 30);
SetParameter (ParameterReverbDiffusion, 65);
SetParameter (ParameterReverbLevel, 80);
// END setup reverb
SetParameter (ParameterCompressorEnable, 1);
};
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)
{
if (nPan > 127)
{
return;
}
assert (nTG < CConfig::ToneGenerators);
m_nPan[nTG] = nPan;
m_fPan[nTG]=mapfloat(nPan,0,127,0.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)
{
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 ();
}
float32_t SampleBuffer[nFrames];
m_pTG[0]->getSamples (SampleBuffer, nFrames);
// Convert dual float array (left, right) to single int16 array (left/right)
float32_t tmp_float[nFrames*2];
int16_t tmp_int[nFrames*2];
for(uint16_t i=0; i<nFrames;i++)
{
tmp_float[i*2]=SampleBuffer[i];
tmp_float[(i*2)+1]=SampleBuffer[i];
}
arm_float_to_q15(tmp_float,tmp_int,nFrames*2);
if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int))
{
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-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(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 ();
}