|
|
|
//
|
|
|
|
// 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/sound/pwmsoundbasedevice.h>
|
|
|
|
#include <circle/sound/i2ssoundbasedevice.h>
|
|
|
|
#include <circle/sound/hdmisoundbasedevice.h>
|
|
|
|
#include <circle/gpiopin.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
LOGMODULE ("minidexed");
|
|
|
|
|
|
|
|
CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
|
|
|
|
CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, CSPIMaster *pSPIMaster, FATFS *pFileSystem)
|
|
|
|
:
|
|
|
|
#ifdef ARM_ALLOW_MULTI_CORE
|
|
|
|
CMultiCoreSupport (CMemorySystem::Get ()),
|
|
|
|
#endif
|
|
|
|
m_pConfig (pConfig),
|
|
|
|
m_UI (this, pGPIOManager, pI2CMaster, pSPIMaster, pConfig),
|
|
|
|
m_PerformanceConfig (pFileSystem),
|
|
|
|
m_PCKeyboard (this, pConfig, &m_UI),
|
|
|
|
m_SerialMIDI (this, pInterrupt, pConfig, &m_UI),
|
|
|
|
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 ()),
|
|
|
|
m_bSavePerformance (false),
|
|
|
|
m_bSavePerformanceNewFile (false),
|
|
|
|
m_bSetNewPerformance (false),
|
|
|
|
m_bSetNewPerformanceBank (false),
|
|
|
|
m_bSetFirstPerformance (false),
|
|
|
|
m_bDeletePerformance (false),
|
|
|
|
m_bLoadPerformanceBusy(false),
|
|
|
|
m_bLoadPerformanceBankBusy(false)
|
|
|
|
{
|
|
|
|
assert (m_pConfig);
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < CConfig::ToneGenerators; i++)
|
|
|
|
{
|
|
|
|
m_nVoiceBankID[i] = 0;
|
|
|
|
m_nVoiceBankIDMSB[i] = 0;
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
m_nProgram[i] = 0;
|
|
|
|
m_nVolume[i] = 100;
|
|
|
|
m_nPan[i] = 64;
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
m_nMasterTune[i] = 0;
|
|
|
|
m_nCutoff[i] = 99;
|
|
|
|
m_nResonance[i] = 0;
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
m_nMIDIChannel[i] = CMIDIDevice::Disabled;
|
|
|
|
m_nPitchBendRange[i] = 2;
|
|
|
|
m_nPitchBendStep[i] = 0;
|
|
|
|
m_nPortamentoMode[i] = 0;
|
|
|
|
m_nPortamentoGlissando[i] = 0;
|
|
|
|
m_nPortamentoTime[i] = 0;
|
|
|
|
m_bMonoMode[i]=0;
|
|
|
|
m_nNoteLimitLow[i] = 0;
|
|
|
|
m_nNoteLimitHigh[i] = 127;
|
|
|
|
m_nNoteShift[i] = 0;
|
|
|
|
|
|
|
|
m_nModulationWheelRange[i]=99;
|
|
|
|
m_nModulationWheelTarget[i]=7;
|
|
|
|
m_nFootControlRange[i]=99;
|
|
|
|
m_nFootControlTarget[i]=0;
|
|
|
|
m_nBreathControlRange[i]=99;
|
|
|
|
m_nBreathControlTarget[i]=0;
|
|
|
|
m_nAftertouchRange[i]=99;
|
|
|
|
m_nAftertouchTarget[i]=0;
|
|
|
|
|
|
|
|
m_nReverbSend[i] = 0;
|
|
|
|
m_uchOPMask[i] = 0b111111; // All operators on
|
|
|
|
|
|
|
|
m_pTG[i] = new CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ());
|
|
|
|
assert (m_pTG[i]);
|
|
|
|
|
|
|
|
m_pTG[i]->setEngineType(pConfig->GetEngineType ());
|
|
|
|
m_pTG[i]->activate ();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pConfig->GetUSBGadgetMode())
|
|
|
|
{
|
|
|
|
#if RASPPI==5
|
|
|
|
LOGNOTE ("USB Gadget (Device) Mode NOT supported on RPI 5");
|
|
|
|
#else
|
|
|
|
LOGNOTE ("USB In Gadget (Device) Mode");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LOGNOTE ("USB In Host Mode");
|
|
|
|
}
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++)
|
|
|
|
{
|
|
|
|
m_pMIDIKeyboard[i] = new CMIDIKeyboard (this, pConfig, &m_UI, 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)
|
|
|
|
{
|
|
|
|
#if RASPPI==5
|
|
|
|
LOGNOTE ("HDMI mode NOT supported on RPI 5.");
|
|
|
|
#else
|
|
|
|
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;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
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
|
|
|
|
|
|
|
|
setMasterVolume(1.0);
|
|
|
|
|
|
|
|
// BEGIN setup tg_mixer
|
|
|
|
tg_mixer = new AudioStereoMixer<CConfig::ToneGenerators>(pConfig->GetChunkSize()/2);
|
|
|
|
// END setup tgmixer
|
|
|
|
|
|
|
|
// BEGIN setup reverb
|
|
|
|
reverb_send_mixer = new AudioStereoMixer<CConfig::ToneGenerators>(pConfig->GetChunkSize()/2);
|
|
|
|
reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate());
|
|
|
|
SetParameter (ParameterReverbEnable, 1);
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
SetParameter (ParameterReverbSize, 70);
|
|
|
|
SetParameter (ParameterReverbHighDamp, 50);
|
|
|
|
SetParameter (ParameterReverbLowDamp, 50);
|
|
|
|
SetParameter (ParameterReverbLowPass, 30);
|
|
|
|
SetParameter (ParameterReverbDiffusion, 65);
|
|
|
|
SetParameter (ParameterReverbLevel, 99);
|
|
|
|
// END setup reverb
|
|
|
|
|
|
|
|
SetParameter (ParameterCompressorEnable, 1);
|
|
|
|
|
|
|
|
SetPerformanceSelectChannel(m_pConfig->GetPerformanceSelectChannel());
|
|
|
|
|
|
|
|
SetParameter (ParameterPerformanceBank, 0);
|
|
|
|
};
|
|
|
|
|
|
|
|
bool CMiniDexed::Initialize (void)
|
|
|
|
{
|
|
|
|
assert (m_pConfig);
|
|
|
|
assert (m_pSoundDevice);
|
|
|
|
|
|
|
|
if (!m_UI.Initialize ())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_SysExFileLoader.Load (m_pConfig->GetHeaderlessSysExVoices ());
|
|
|
|
|
|
|
|
if (m_SerialMIDI.Initialize ())
|
|
|
|
{
|
|
|
|
LOGNOTE ("Serial MIDI interface enabled");
|
|
|
|
|
|
|
|
m_bUseSerial = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_pConfig->GetMIDIRXProgramChange())
|
|
|
|
{
|
|
|
|
int nPerfCh = GetParameter(ParameterPerformanceSelectChannel);
|
|
|
|
if (nPerfCh == CMIDIDevice::Disabled) {
|
|
|
|
LOGNOTE("Program Change: Enabled for Voices");
|
|
|
|
} else if (nPerfCh == CMIDIDevice::OmniMode) {
|
|
|
|
LOGNOTE("Program Change: Enabled for Performances (Omni)");
|
|
|
|
} else {
|
|
|
|
LOGNOTE("Program Change: Enabled for Performances (CH %d)", nPerfCh+1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
LOGNOTE("Program Change: Disabled");
|
|
|
|
}
|
|
|
|
|
|
|
|
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 (2, 0);
|
|
|
|
m_pTG[i]->setMWController (99, 1, 0);
|
|
|
|
|
|
|
|
m_pTG[i]->setFCController (99, 1, 0);
|
|
|
|
m_pTG[i]->setBCController (99, 1, 0);
|
|
|
|
m_pTG[i]->setATController (99, 1, 0);
|
|
|
|
|
|
|
|
tg_mixer->pan(i,mapfloat(m_nPan[i],0,127,0.0f,1.0f));
|
|
|
|
tg_mixer->gain(i,1.0f);
|
|
|
|
reverb_send_mixer->pan(i,mapfloat(m_nPan[i],0,127,0.0f,1.0f));
|
|
|
|
reverb_send_mixer->gain(i,mapfloat(m_nReverbSend[i],0,99,0.0f,1.0f));
|
|
|
|
}
|
|
|
|
|
|
|
|
m_PerformanceConfig.Init();
|
|
|
|
if (m_PerformanceConfig.Load ())
|
|
|
|
{
|
|
|
|
LoadPerformanceParameters();
|
|
|
|
}
|
|
|
|
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_bSavePerformance)
|
|
|
|
{
|
|
|
|
DoSavePerformance ();
|
|
|
|
|
|
|
|
m_bSavePerformance = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_bSavePerformanceNewFile)
|
|
|
|
{
|
|
|
|
DoSavePerformanceNewFile ();
|
|
|
|
m_bSavePerformanceNewFile = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_bSetNewPerformanceBank && !m_bLoadPerformanceBusy && !m_bLoadPerformanceBankBusy)
|
|
|
|
{
|
|
|
|
DoSetNewPerformanceBank ();
|
|
|
|
if (m_nSetNewPerformanceBankID == GetActualPerformanceBankID())
|
|
|
|
{
|
|
|
|
m_bSetNewPerformanceBank = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is no pending SetNewPerformance already, then see if we need to find the first performance to load
|
|
|
|
// NB: If called from the UI, then there will not be a SetNewPerformance, so load the first existing one.
|
|
|
|
// If called from MIDI, there will probably be a SetNewPerformance alongside the Bank select.
|
|
|
|
if (!m_bSetNewPerformance && m_bSetFirstPerformance)
|
|
|
|
{
|
|
|
|
DoSetFirstPerformance();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_bSetNewPerformance && !m_bSetNewPerformanceBank && !m_bLoadPerformanceBusy && !m_bLoadPerformanceBankBusy)
|
|
|
|
{
|
|
|
|
DoSetNewPerformance ();
|
|
|
|
if (m_nSetNewPerformanceID == GetActualPerformanceID())
|
|
|
|
{
|
|
|
|
m_bSetNewPerformance = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(m_bDeletePerformance)
|
|
|
|
{
|
|
|
|
DoDeletePerformance ();
|
|
|
|
m_bDeletePerformance = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
CPerformanceConfig *CMiniDexed::GetPerformanceConfig (void)
|
|
|
|
{
|
|
|
|
return &m_PerformanceConfig;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::BankSelect (unsigned nBank, unsigned nTG)
|
|
|
|
{
|
|
|
|
nBank=constrain((int)nBank,0,16383);
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
|
|
|
|
if (GetSysExFileLoader ()->IsValidBank(nBank))
|
|
|
|
{
|
|
|
|
// Only change if we have the bank loaded
|
|
|
|
m_nVoiceBankID[nTG] = nBank;
|
|
|
|
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::BankSelectPerformance (unsigned nBank)
|
|
|
|
{
|
|
|
|
nBank=constrain((int)nBank,0,16383);
|
|
|
|
|
|
|
|
if (GetPerformanceConfig ()->IsValidPerformanceBank(nBank))
|
|
|
|
{
|
|
|
|
// Only change if we have the bank loaded
|
|
|
|
m_nVoiceBankIDPerformance = nBank;
|
|
|
|
SetNewPerformanceBank (nBank);
|
|
|
|
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::BankSelectMSB (unsigned nBankMSB, unsigned nTG)
|
|
|
|
{
|
|
|
|
nBankMSB=constrain((int)nBankMSB,0,127);
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
// MIDI Spec 1.0 "BANK SELECT" states:
|
|
|
|
// "The transmitter must transmit the MSB and LSB as a pair,
|
|
|
|
// and the Program Change must be sent immediately after
|
|
|
|
// the Bank Select pair."
|
|
|
|
//
|
|
|
|
// So it isn't possible to validate the selected bank ID until
|
|
|
|
// we receive both MSB and LSB so just store the MSB for now.
|
|
|
|
m_nVoiceBankIDMSB[nTG] = nBankMSB;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::BankSelectMSBPerformance (unsigned nBankMSB)
|
|
|
|
{
|
|
|
|
nBankMSB=constrain((int)nBankMSB,0,127);
|
|
|
|
m_nVoiceBankIDMSBPerformance = nBankMSB;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG)
|
|
|
|
{
|
|
|
|
nBankLSB=constrain((int)nBankLSB,0,127);
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
unsigned nBank = m_nVoiceBankID[nTG];
|
|
|
|
unsigned nBankMSB = m_nVoiceBankIDMSB[nTG];
|
|
|
|
nBank = (nBankMSB << 7) + nBankLSB;
|
|
|
|
|
|
|
|
// Now should have both MSB and LSB so enable the BankSelect
|
|
|
|
BankSelect(nBank, nTG);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::BankSelectLSBPerformance (unsigned nBankLSB)
|
|
|
|
{
|
|
|
|
nBankLSB=constrain((int)nBankLSB,0,127);
|
|
|
|
|
|
|
|
unsigned nBank = m_nVoiceBankIDPerformance;
|
|
|
|
unsigned nBankMSB = m_nVoiceBankIDMSBPerformance;
|
|
|
|
nBank = (nBankMSB << 7) + nBankLSB;
|
|
|
|
|
|
|
|
// Now should have both MSB and LSB so enable the BankSelect
|
|
|
|
BankSelectPerformance(nBank);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG)
|
|
|
|
{
|
|
|
|
assert (m_pConfig);
|
|
|
|
|
|
|
|
unsigned nBankOffset;
|
|
|
|
bool bPCAcrossBanks = m_pConfig->GetExpandPCAcrossBanks();
|
|
|
|
if (bPCAcrossBanks)
|
|
|
|
{
|
|
|
|
// Note: This doesn't actually change the bank in use
|
|
|
|
// but will allow PC messages of 0..127
|
|
|
|
// to select across four consecutive banks of voices.
|
|
|
|
//
|
|
|
|
// So if the current bank = 5 then:
|
|
|
|
// PC 0-31 = Bank 5, Program 0-31
|
|
|
|
// PC 32-63 = Bank 6, Program 0-31
|
|
|
|
// PC 64-95 = Bank 7, Program 0-31
|
|
|
|
// PC 96-127 = Bank 8, Program 0-31
|
|
|
|
nProgram=constrain((int)nProgram,0,127);
|
|
|
|
nBankOffset = nProgram >> 5;
|
|
|
|
nProgram = nProgram % 32;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
nBankOffset = 0;
|
|
|
|
nProgram=constrain((int)nProgram,0,31);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
m_nProgram[nTG] = nProgram;
|
|
|
|
|
|
|
|
uint8_t Buffer[156];
|
|
|
|
m_SysExFileLoader.GetVoice (m_nVoiceBankID[nTG]+nBankOffset, nProgram, Buffer);
|
|
|
|
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_pTG[nTG]->loadVoiceParameters (Buffer);
|
|
|
|
|
|
|
|
if (m_pConfig->GetMIDIAutoVoiceDumpOnPC())
|
|
|
|
{
|
|
|
|
// Only do the voice dump back out over MIDI if we have a specific
|
|
|
|
// MIDI channel configured for this TG
|
|
|
|
if (m_nMIDIChannel[nTG] < CMIDIDevice::Channels)
|
|
|
|
{
|
|
|
|
m_SerialMIDI.SendSystemExclusiveVoice(nProgram,0,nTG);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::ProgramChangePerformance (unsigned nProgram)
|
|
|
|
{
|
|
|
|
if (m_nParameter[ParameterPerformanceSelectChannel] != CMIDIDevice::Disabled)
|
|
|
|
{
|
|
|
|
// Program Change messages change Performances.
|
|
|
|
if (m_PerformanceConfig.IsValidPerformance(nProgram))
|
|
|
|
{
|
|
|
|
SetNewPerformance(nProgram);
|
|
|
|
}
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG)
|
|
|
|
{
|
|
|
|
nVolume=constrain((int)nVolume,0,127);
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
m_nVolume[nTG] = nVolume;
|
|
|
|
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_pTG[nTG]->setGain (nVolume / 127.0f);
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetPan (unsigned nPan, unsigned nTG)
|
|
|
|
{
|
|
|
|
nPan=constrain((int)nPan,0,127);
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
m_nPan[nTG] = nPan;
|
|
|
|
|
|
|
|
tg_mixer->pan(nTG,mapfloat(nPan,0,127,0.0f,1.0f));
|
|
|
|
reverb_send_mixer->pan(nTG,mapfloat(nPan,0,127,0.0f,1.0f));
|
|
|
|
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetReverbSend (unsigned nReverbSend, unsigned nTG)
|
|
|
|
{
|
|
|
|
nReverbSend=constrain((int)nReverbSend,0,99);
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
m_nReverbSend[nTG] = nReverbSend;
|
|
|
|
|
|
|
|
reverb_send_mixer->gain(nTG,mapfloat(nReverbSend,0,99,0.0f,1.0f));
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG)
|
|
|
|
{
|
|
|
|
nMasterTune=constrain((int)nMasterTune,-99,99);
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
m_nMasterTune[nTG] = nMasterTune;
|
|
|
|
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_pTG[nTG]->setMasterTune ((int8_t) nMasterTune);
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetCutoff (int nCutoff, unsigned nTG)
|
|
|
|
{
|
|
|
|
nCutoff = constrain (nCutoff, 0, 99);
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
m_nCutoff[nTG] = nCutoff;
|
|
|
|
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_pTG[nTG]->setFilterCutoff (mapfloat (nCutoff, 0, 99, 0.0f, 1.0f));
|
|
|
|
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetResonance (int nResonance, unsigned nTG)
|
|
|
|
{
|
|
|
|
nResonance = constrain (nResonance, 0, 99);
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
m_nResonance[nTG] = nResonance;
|
|
|
|
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_pTG[nTG]->setFilterResonance (mapfloat (nResonance, 0, 99, 0.0f, 1.0f));
|
|
|
|
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (uchChannel < CMIDIDevice::ChannelUnknown);
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
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++)
|
|
|
|
{
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
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
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
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::panic(uint8_t value, unsigned nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
if (value == 0) {
|
|
|
|
m_pTG[nTG]->panic ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::notesOff(uint8_t value, unsigned nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
if (value == 0) {
|
|
|
|
m_pTG[nTG]->notesOff ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setModWheel (uint8_t value, unsigned nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_pTG[nTG]->setModWheel (value);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CMiniDexed::setFootController (uint8_t value, unsigned nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_pTG[nTG]->setFootController (value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setBreathController (uint8_t value, unsigned nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_pTG[nTG]->setBreathController (value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setAftertouch (uint8_t value, unsigned nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_pTG[nTG]->setAftertouch (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 ();
|
|
|
|
}
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
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:
|
|
|
|
nValue=constrain((int)nValue,0,1);
|
|
|
|
m_ReverbSpinLock.Acquire ();
|
|
|
|
reverb->set_bypass (!nValue);
|
|
|
|
m_ReverbSpinLock.Release ();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ParameterReverbSize:
|
|
|
|
nValue=constrain((int)nValue,0,99);
|
|
|
|
m_ReverbSpinLock.Acquire ();
|
|
|
|
reverb->size (nValue / 99.0f);
|
|
|
|
m_ReverbSpinLock.Release ();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ParameterReverbHighDamp:
|
|
|
|
nValue=constrain((int)nValue,0,99);
|
|
|
|
m_ReverbSpinLock.Acquire ();
|
|
|
|
reverb->hidamp (nValue / 99.0f);
|
|
|
|
m_ReverbSpinLock.Release ();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ParameterReverbLowDamp:
|
|
|
|
nValue=constrain((int)nValue,0,99);
|
|
|
|
m_ReverbSpinLock.Acquire ();
|
|
|
|
reverb->lodamp (nValue / 99.0f);
|
|
|
|
m_ReverbSpinLock.Release ();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ParameterReverbLowPass:
|
|
|
|
nValue=constrain((int)nValue,0,99);
|
|
|
|
m_ReverbSpinLock.Acquire ();
|
|
|
|
reverb->lowpass (nValue / 99.0f);
|
|
|
|
m_ReverbSpinLock.Release ();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ParameterReverbDiffusion:
|
|
|
|
nValue=constrain((int)nValue,0,99);
|
|
|
|
m_ReverbSpinLock.Acquire ();
|
|
|
|
reverb->diffusion (nValue / 99.0f);
|
|
|
|
m_ReverbSpinLock.Release ();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ParameterReverbLevel:
|
|
|
|
nValue=constrain((int)nValue,0,99);
|
|
|
|
m_ReverbSpinLock.Acquire ();
|
|
|
|
reverb->level (nValue / 99.0f);
|
|
|
|
m_ReverbSpinLock.Release ();
|
|
|
|
break;
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
|
|
|
|
case ParameterPerformanceSelectChannel:
|
|
|
|
// Nothing more to do
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ParameterPerformanceBank:
|
|
|
|
BankSelectPerformance(nValue);
|
|
|
|
break;
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
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: BankSelect (nValue, nTG); break;
|
|
|
|
case TGParameterVoiceBankMSB: BankSelectMSB (nValue, nTG); break;
|
|
|
|
case TGParameterVoiceBankLSB: BankSelectLSB (nValue, nTG); break;
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
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 TGParameterCutoff: SetCutoff (nValue, nTG); break;
|
|
|
|
case TGParameterResonance: SetResonance (nValue, nTG); break;
|
|
|
|
case TGParameterPitchBendRange: setPitchbendRange (nValue, nTG); break;
|
|
|
|
case TGParameterPitchBendStep: setPitchbendStep (nValue, nTG); break;
|
|
|
|
case TGParameterPortamentoMode: setPortamentoMode (nValue, nTG); break;
|
|
|
|
case TGParameterPortamentoGlissando: setPortamentoGlissando (nValue, nTG); break;
|
|
|
|
case TGParameterPortamentoTime: setPortamentoTime (nValue, nTG); break;
|
|
|
|
case TGParameterMonoMode: setMonoMode (nValue , nTG); break;
|
|
|
|
|
|
|
|
case TGParameterMWRange: setModController(0, 0, nValue, nTG); break;
|
|
|
|
case TGParameterMWPitch: setModController(0, 1, nValue, nTG); break;
|
|
|
|
case TGParameterMWAmplitude: setModController(0, 2, nValue, nTG); break;
|
|
|
|
case TGParameterMWEGBias: setModController(0, 3, nValue, nTG); break;
|
|
|
|
|
|
|
|
case TGParameterFCRange: setModController(1, 0, nValue, nTG); break;
|
|
|
|
case TGParameterFCPitch: setModController(1, 1, nValue, nTG); break;
|
|
|
|
case TGParameterFCAmplitude: setModController(1, 2, nValue, nTG); break;
|
|
|
|
case TGParameterFCEGBias: setModController(1, 3, nValue, nTG); break;
|
|
|
|
|
|
|
|
case TGParameterBCRange: setModController(2, 0, nValue, nTG); break;
|
|
|
|
case TGParameterBCPitch: setModController(2, 1, nValue, nTG); break;
|
|
|
|
case TGParameterBCAmplitude: setModController(2, 2, nValue, nTG); break;
|
|
|
|
case TGParameterBCEGBias: setModController(2, 3, nValue, nTG); break;
|
|
|
|
|
|
|
|
case TGParameterATRange: setModController(3, 0, nValue, nTG); break;
|
|
|
|
case TGParameterATPitch: setModController(3, 1, nValue, nTG); break;
|
|
|
|
case TGParameterATAmplitude: setModController(3, 2, nValue, nTG); break;
|
|
|
|
case TGParameterATEGBias: setModController(3, 3, nValue, nTG); break;
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
case TGParameterMIDIChannel:
|
|
|
|
assert (0 <= nValue && nValue <= 255);
|
|
|
|
SetMIDIChannel ((uint8_t) nValue, nTG);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TGParameterReverbSend: SetReverbSend (nValue, nTG); break;
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
default:
|
|
|
|
assert (0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
|
|
|
|
switch (Parameter)
|
|
|
|
{
|
|
|
|
case TGParameterVoiceBank: return m_nVoiceBankID[nTG];
|
|
|
|
case TGParameterVoiceBankMSB: return m_nVoiceBankID[nTG] >> 7;
|
|
|
|
case TGParameterVoiceBankLSB: return m_nVoiceBankID[nTG] & 0x7F;
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
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 TGParameterCutoff: return m_nCutoff[nTG];
|
|
|
|
case TGParameterResonance: return m_nResonance[nTG];
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
case TGParameterMIDIChannel: return m_nMIDIChannel[nTG];
|
|
|
|
case TGParameterReverbSend: return m_nReverbSend[nTG];
|
|
|
|
case TGParameterPitchBendRange: return m_nPitchBendRange[nTG];
|
|
|
|
case TGParameterPitchBendStep: return m_nPitchBendStep[nTG];
|
|
|
|
case TGParameterPortamentoMode: return m_nPortamentoMode[nTG];
|
|
|
|
case TGParameterPortamentoGlissando: return m_nPortamentoGlissando[nTG];
|
|
|
|
case TGParameterPortamentoTime: return m_nPortamentoTime[nTG];
|
|
|
|
case TGParameterMonoMode: return m_bMonoMode[nTG] ? 1 : 0;
|
|
|
|
|
|
|
|
case TGParameterMWRange: return getModController(0, 0, nTG);
|
|
|
|
case TGParameterMWPitch: return getModController(0, 1, nTG);
|
|
|
|
case TGParameterMWAmplitude: return getModController(0, 2, nTG);
|
|
|
|
case TGParameterMWEGBias: return getModController(0, 3, nTG);
|
|
|
|
|
|
|
|
case TGParameterFCRange: return getModController(1, 0, nTG);
|
|
|
|
case TGParameterFCPitch: return getModController(1, 1, nTG);
|
|
|
|
case TGParameterFCAmplitude: return getModController(1, 2, nTG);
|
|
|
|
case TGParameterFCEGBias: return getModController(1, 3, nTG);
|
|
|
|
|
|
|
|
case TGParameterBCRange: return getModController(2, 0, nTG);
|
|
|
|
case TGParameterBCPitch: return getModController(2, 1, nTG);
|
|
|
|
case TGParameterBCAmplitude: return getModController(2, 2, nTG);
|
|
|
|
case TGParameterBCEGBias: return getModController(2, 3, nTG);
|
|
|
|
|
|
|
|
case TGParameterATRange: return getModController(3, 0, nTG);
|
|
|
|
case TGParameterATPitch: return getModController(3, 1, nTG);
|
|
|
|
case TGParameterATAmplitude: return getModController(3, 2, nTG);
|
|
|
|
case TGParameterATEGBias: return getModController(3, 3, nTG);
|
|
|
|
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
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)
|
|
|
|
{
|
|
|
|
if (uchOffset == DEXED_OP_ENABLE)
|
|
|
|
{
|
|
|
|
if (uchValue)
|
|
|
|
{
|
|
|
|
m_uchOPMask[nTG] |= 1 << nOP;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_uchOPMask[nTG] &= ~(1 << nOP);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_pTG[nTG]->setOPAll (m_uchOPMask[nTG]);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
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)
|
|
|
|
{
|
|
|
|
if (uchOffset == DEXED_OP_ENABLE)
|
|
|
|
{
|
|
|
|
return !!(m_uchOPMask[nTG] & (1 << nOP));
|
|
|
|
}
|
|
|
|
|
Support hierarchic menus (#61)
* Support hierarchic menus
This introduces a new menu engine implemented in the class CUIMenu,
which can be configured with C-tables. This should make it easier to
extend the menus, without modifying the code too much. The UI provides
a main menu, which selects the TG to be edited and a TG sub-menu, which
presents the already known TG parameters (voice bank, voice, volume,
pan, detune and channel). A sub-menu is entered with single click and
left with double click. There are arrows displayed on the LCD, which
show the direction(s), to which the knob can be moved. All TG related
parameters are maintained in the class CMiniDexed now.
* uimenu: Make the tables const
* uimenu: Add sub-menu "Edit Voice"
Menu items can be re-sorted, if necessary.
* uimenu: Add "Reverb" sub-menu
* Map reverb float parameters to range 0 .. 99
* minidexed: Add global (non-TG) parameters
* minidexed: Protect reverb module with spin lock
3 years ago
|
|
|
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 single float array (mono) to int16 array
|
|
|
|
int16_t tmp_int[nFrames];
|
|
|
|
arm_float_to_q15(SampleBuffer,tmp_int,nFrames);
|
|
|
|
|
|
|
|
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
|
|
|
|
//
|
|
|
|
|
|
|
|
assert (CConfig::ToneGenerators == 8);
|
|
|
|
|
|
|
|
uint8_t indexL=0, indexR=1;
|
|
|
|
|
|
|
|
// BEGIN TG mixing
|
|
|
|
float32_t tmp_float[nFrames*2];
|
|
|
|
int16_t tmp_int[nFrames*2];
|
|
|
|
|
|
|
|
if(nMasterVolume > 0.0)
|
|
|
|
{
|
|
|
|
for (uint8_t i = 0; i < CConfig::ToneGenerators; i++)
|
|
|
|
{
|
|
|
|
tg_mixer->doAddMix(i,m_OutputLevel[i]);
|
|
|
|
reverb_send_mixer->doAddMix(i,m_OutputLevel[i]);
|
|
|
|
}
|
|
|
|
// END TG mixing
|
|
|
|
|
|
|
|
// BEGIN create SampleBuffer for holding audio data
|
|
|
|
float32_t SampleBuffer[2][nFrames];
|
|
|
|
// END create SampleBuffer for holding audio data
|
|
|
|
|
|
|
|
// get the mix of all TGs
|
|
|
|
tg_mixer->getMix(SampleBuffer[indexL], SampleBuffer[indexR]);
|
|
|
|
|
|
|
|
// BEGIN adding reverb
|
|
|
|
if (m_nParameter[ParameterReverbEnable])
|
|
|
|
{
|
|
|
|
float32_t ReverbBuffer[2][nFrames];
|
|
|
|
float32_t ReverbSendBuffer[2][nFrames];
|
|
|
|
|
|
|
|
arm_fill_f32(0.0f, ReverbBuffer[indexL], nFrames);
|
|
|
|
arm_fill_f32(0.0f, ReverbBuffer[indexR], nFrames);
|
|
|
|
arm_fill_f32(0.0f, ReverbSendBuffer[indexR], nFrames);
|
|
|
|
arm_fill_f32(0.0f, ReverbSendBuffer[indexL], nFrames);
|
|
|
|
|
|
|
|
m_ReverbSpinLock.Acquire ();
|
|
|
|
|
|
|
|
reverb_send_mixer->getMix(ReverbSendBuffer[indexL], ReverbSendBuffer[indexR]);
|
|
|
|
reverb->doReverb(ReverbSendBuffer[indexL],ReverbSendBuffer[indexR],ReverbBuffer[indexL], ReverbBuffer[indexR],nFrames);
|
|
|
|
|
|
|
|
// scale down and add left reverb buffer by reverb level
|
|
|
|
arm_scale_f32(ReverbBuffer[indexL], reverb->get_level(), ReverbBuffer[indexL], nFrames);
|
|
|
|
arm_add_f32(SampleBuffer[indexL], ReverbBuffer[indexL], SampleBuffer[indexL], nFrames);
|
|
|
|
// scale down and add right reverb buffer by reverb level
|
|
|
|
arm_scale_f32(ReverbBuffer[indexR], reverb->get_level(), ReverbBuffer[indexR], nFrames);
|
|
|
|
arm_add_f32(SampleBuffer[indexR], ReverbBuffer[indexR], SampleBuffer[indexR], nFrames);
|
|
|
|
|
|
|
|
m_ReverbSpinLock.Release ();
|
|
|
|
}
|
|
|
|
// END adding reverb
|
|
|
|
|
|
|
|
// swap stereo channels if needed prior to writing back out
|
|
|
|
if (m_bChannelsSwapped)
|
|
|
|
{
|
|
|
|
indexL=1;
|
|
|
|
indexR=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert dual float array (left, right) to single int16 array (left/right)
|
|
|
|
for(uint16_t i=0; i<nFrames;i++)
|
|
|
|
{
|
|
|
|
if(nMasterVolume >0.0 && nMasterVolume <1.0)
|
|
|
|
{
|
|
|
|
tmp_float[i*2]=SampleBuffer[indexL][i] * nMasterVolume;
|
|
|
|
tmp_float[(i*2)+1]=SampleBuffer[indexR][i] * nMasterVolume;
|
|
|
|
}
|
|
|
|
else if(nMasterVolume == 1.0)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
arm_fill_q15(0, 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
|
|
|
|
|
|
|
|
unsigned CMiniDexed::GetPerformanceSelectChannel (void)
|
|
|
|
{
|
|
|
|
// Stores and returns Select Channel using MIDI Device Channel definitions
|
|
|
|
return (unsigned) GetParameter (ParameterPerformanceSelectChannel);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetPerformanceSelectChannel (unsigned uCh)
|
|
|
|
{
|
|
|
|
// Turns a configuration setting to MIDI Device Channel definitions
|
|
|
|
// Mirrors the logic in Performance Config for handling MIDI channel configuration
|
|
|
|
if (uCh == 0)
|
|
|
|
{
|
|
|
|
SetParameter (ParameterPerformanceSelectChannel, CMIDIDevice::Disabled);
|
|
|
|
}
|
|
|
|
else if (uCh < CMIDIDevice::Channels)
|
|
|
|
{
|
|
|
|
SetParameter (ParameterPerformanceSelectChannel, uCh - 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SetParameter (ParameterPerformanceSelectChannel, CMIDIDevice::OmniMode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::SavePerformance (bool bSaveAsDeault)
|
|
|
|
{
|
|
|
|
if (m_PerformanceConfig.GetInternalFolderOk())
|
|
|
|
{
|
|
|
|
m_bSavePerformance = true;
|
|
|
|
m_bSaveAsDeault=bSaveAsDeault;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::DoSavePerformance (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.SetCutoff (m_nCutoff[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetResonance (m_nResonance[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetPitchBendRange (m_nPitchBendRange[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetPitchBendStep (m_nPitchBendStep[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetPortamentoMode (m_nPortamentoMode[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetPortamentoGlissando (m_nPortamentoGlissando[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetPortamentoTime (m_nPortamentoTime[nTG], nTG);
|
|
|
|
|
|
|
|
m_PerformanceConfig.SetNoteLimitLow (m_nNoteLimitLow[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetNoteLimitHigh (m_nNoteLimitHigh[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetNoteShift (m_nNoteShift[nTG], nTG);
|
|
|
|
m_pTG[nTG]->getVoiceData(m_nRawVoiceData);
|
|
|
|
m_PerformanceConfig.SetVoiceDataToTxt (m_nRawVoiceData, nTG);
|
|
|
|
m_PerformanceConfig.SetMonoMode (m_bMonoMode[nTG], nTG);
|
|
|
|
|
|
|
|
m_PerformanceConfig.SetModulationWheelRange (m_nModulationWheelRange[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetModulationWheelTarget (m_nModulationWheelTarget[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetFootControlRange (m_nFootControlRange[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetFootControlTarget (m_nFootControlTarget[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetBreathControlRange (m_nBreathControlRange[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetBreathControlTarget (m_nBreathControlTarget[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetAftertouchRange (m_nAftertouchRange[nTG], nTG);
|
|
|
|
m_PerformanceConfig.SetAftertouchTarget (m_nAftertouchTarget[nTG], nTG);
|
|
|
|
|
|
|
|
m_PerformanceConfig.SetReverbSend (m_nReverbSend[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]);
|
|
|
|
|
|
|
|
if(m_bSaveAsDeault)
|
|
|
|
{
|
|
|
|
m_PerformanceConfig.SetNewPerformance(0);
|
|
|
|
|
|
|
|
}
|
|
|
|
return m_PerformanceConfig.Save ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setMonoMode(uint8_t mono, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_bMonoMode[nTG]= mono != 0;
|
|
|
|
m_pTG[nTG]->setMonoMode(constrain(mono, 0, 1));
|
|
|
|
m_pTG[nTG]->doRefreshVoice();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setPitchbendRange(uint8_t range, uint8_t nTG)
|
|
|
|
{
|
|
|
|
range = constrain (range, 0, 12);
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_nPitchBendRange[nTG] = range;
|
|
|
|
|
|
|
|
m_pTG[nTG]->setPitchbendRange(range);
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setPitchbendStep(uint8_t step, uint8_t nTG)
|
|
|
|
{
|
|
|
|
step= constrain (step, 0, 12);
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_nPitchBendStep[nTG] = step;
|
|
|
|
|
|
|
|
m_pTG[nTG]->setPitchbendStep(step);
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setPortamentoMode(uint8_t mode, uint8_t nTG)
|
|
|
|
{
|
|
|
|
mode= constrain (mode, 0, 1);
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_nPortamentoMode[nTG] = mode;
|
|
|
|
|
|
|
|
m_pTG[nTG]->setPortamentoMode(mode);
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setPortamentoGlissando(uint8_t glissando, uint8_t nTG)
|
|
|
|
{
|
|
|
|
glissando = constrain (glissando, 0, 1);
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_nPortamentoGlissando[nTG] = glissando;
|
|
|
|
|
|
|
|
m_pTG[nTG]->setPortamentoGlissando(glissando);
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setPortamentoTime(uint8_t time, uint8_t nTG)
|
|
|
|
{
|
|
|
|
time = constrain (time, 0, 99);
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
m_nPortamentoTime[nTG] = time;
|
|
|
|
|
|
|
|
m_pTG[nTG]->setPortamentoTime(time);
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setModWheelRange(uint8_t range, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
m_nModulationWheelRange[nTG] = range;
|
|
|
|
m_pTG[nTG]->setMWController(range, m_pTG[nTG]->getModWheelTarget(), 0);
|
|
|
|
// m_pTG[nTG]->setModWheelRange(constrain(range, 0, 99)); replaces with the above due to wrong constrain on dexed_synth module.
|
|
|
|
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setModWheelTarget(uint8_t target, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
m_nModulationWheelTarget[nTG] = target;
|
|
|
|
|
|
|
|
m_pTG[nTG]->setModWheelTarget(constrain(target, 0, 7));
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setFootControllerRange(uint8_t range, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
m_nFootControlRange[nTG]=range;
|
|
|
|
m_pTG[nTG]->setFCController(range, m_pTG[nTG]->getFootControllerTarget(), 0);
|
|
|
|
// m_pTG[nTG]->setFootControllerRange(constrain(range, 0, 99)); replaces with the above due to wrong constrain on dexed_synth module.
|
|
|
|
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setFootControllerTarget(uint8_t target, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
m_nFootControlTarget[nTG] = target;
|
|
|
|
|
|
|
|
m_pTG[nTG]->setFootControllerTarget(constrain(target, 0, 7));
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setBreathControllerRange(uint8_t range, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
m_nBreathControlRange[nTG]=range;
|
|
|
|
m_pTG[nTG]->setBCController(range, m_pTG[nTG]->getBreathControllerTarget(), 0);
|
|
|
|
//m_pTG[nTG]->setBreathControllerRange(constrain(range, 0, 99));
|
|
|
|
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setBreathControllerTarget(uint8_t target, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
m_nBreathControlTarget[nTG]=target;
|
|
|
|
|
|
|
|
m_pTG[nTG]->setBreathControllerTarget(constrain(target, 0, 7));
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setAftertouchRange(uint8_t range, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
m_nAftertouchRange[nTG]=range;
|
|
|
|
m_pTG[nTG]->setATController(range, m_pTG[nTG]->getAftertouchTarget(), 0);
|
|
|
|
// m_pTG[nTG]->setAftertouchRange(constrain(range, 0, 99));
|
|
|
|
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setAftertouchTarget(uint8_t target, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
m_nAftertouchTarget[nTG]=target;
|
|
|
|
|
|
|
|
m_pTG[nTG]->setAftertouchTarget(constrain(target, 0, 7));
|
|
|
|
m_pTG[nTG]->ControllersRefresh();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::loadVoiceParameters(const uint8_t* data, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
uint8_t voice[161];
|
|
|
|
|
|
|
|
memcpy(voice, data, sizeof(uint8_t)*161);
|
|
|
|
|
|
|
|
// fix voice name
|
|
|
|
for (uint8_t i = 0; i < 10; i++)
|
|
|
|
{
|
|
|
|
if (voice[151 + i] > 126) // filter characters
|
|
|
|
voice[151 + i] = 32;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_pTG[nTG]->loadVoiceParameters(&voice[6]);
|
|
|
|
m_pTG[nTG]->doRefreshVoice();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setVoiceDataElement(uint8_t data, uint8_t number, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
m_pTG[nTG]->setVoiceDataElement(constrain(data, 0, 155),constrain(number, 0, 99));
|
|
|
|
//m_pTG[nTG]->doRefreshVoice();
|
|
|
|
m_UI.ParameterChanged ();
|
|
|
|
}
|
|
|
|
|
|
|
|
int16_t CMiniDexed::checkSystemExclusive(const uint8_t* pMessage,const uint16_t nLength, uint8_t nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
return(m_pTG[nTG]->checkSystemExclusive(pMessage, nLength));
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::getSysExVoiceDump(uint8_t* dest, uint8_t nTG)
|
|
|
|
{
|
|
|
|
uint8_t checksum = 0;
|
|
|
|
uint8_t data[155];
|
|
|
|
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
|
|
|
|
m_pTG[nTG]->getVoiceData(data);
|
|
|
|
|
|
|
|
dest[0] = 0xF0; // SysEx start
|
|
|
|
dest[1] = 0x43; // ID=Yamaha
|
|
|
|
dest[2] = 0x00 | m_nMIDIChannel[nTG]; // 0x0c Sub-status 0 and MIDI channel
|
|
|
|
dest[3] = 0x00; // Format number (0=1 voice)
|
|
|
|
dest[4] = 0x01; // Byte count MSB
|
|
|
|
dest[5] = 0x1B; // Byte count LSB
|
|
|
|
for (uint8_t n = 0; n < 155; n++)
|
|
|
|
{
|
|
|
|
checksum -= data[n];
|
|
|
|
dest[6 + n] = data[n];
|
|
|
|
}
|
|
|
|
dest[161] = checksum & 0x7f; // Checksum
|
|
|
|
dest[162] = 0xF7; // SysEx end
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setMasterVolume (float32_t vol)
|
|
|
|
{
|
|
|
|
if(vol < 0.0)
|
|
|
|
vol = 0.0;
|
|
|
|
else if(vol > 1.0)
|
|
|
|
vol = 1.0;
|
|
|
|
|
|
|
|
nMasterVolume=vol;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string CMiniDexed::GetPerformanceFileName(unsigned nID)
|
|
|
|
{
|
|
|
|
return m_PerformanceConfig.GetPerformanceFileName(nID);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string CMiniDexed::GetPerformanceName(unsigned nID)
|
|
|
|
{
|
|
|
|
return m_PerformanceConfig.GetPerformanceName(nID);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned CMiniDexed::GetLastPerformance()
|
|
|
|
{
|
|
|
|
return m_PerformanceConfig.GetLastPerformance();
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned CMiniDexed::GetPerformanceBank()
|
|
|
|
{
|
|
|
|
return m_PerformanceConfig.GetPerformanceBank();
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned CMiniDexed::GetLastPerformanceBank()
|
|
|
|
{
|
|
|
|
return m_PerformanceConfig.GetLastPerformanceBank();
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned CMiniDexed::GetActualPerformanceID()
|
|
|
|
{
|
|
|
|
return m_PerformanceConfig.GetActualPerformanceID();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetActualPerformanceID(unsigned nID)
|
|
|
|
{
|
|
|
|
m_PerformanceConfig.SetActualPerformanceID(nID);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned CMiniDexed::GetActualPerformanceBankID()
|
|
|
|
{
|
|
|
|
return m_PerformanceConfig.GetActualPerformanceBankID();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetActualPerformanceBankID(unsigned nBankID)
|
|
|
|
{
|
|
|
|
m_PerformanceConfig.SetActualPerformanceBankID(nBankID);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::SetNewPerformance(unsigned nID)
|
|
|
|
{
|
|
|
|
m_bSetNewPerformance = true;
|
|
|
|
m_nSetNewPerformanceID = nID;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::SetNewPerformanceBank(unsigned nBankID)
|
|
|
|
{
|
|
|
|
m_bSetNewPerformanceBank = true;
|
|
|
|
m_nSetNewPerformanceBankID = nBankID;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetFirstPerformance(void)
|
|
|
|
{
|
|
|
|
m_bSetFirstPerformance = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::DoSetNewPerformance (void)
|
|
|
|
{
|
|
|
|
m_bLoadPerformanceBusy = true;
|
|
|
|
|
|
|
|
unsigned nID = m_nSetNewPerformanceID;
|
|
|
|
m_PerformanceConfig.SetNewPerformance(nID);
|
|
|
|
|
|
|
|
if (m_PerformanceConfig.Load ())
|
|
|
|
{
|
|
|
|
LoadPerformanceParameters();
|
|
|
|
m_bLoadPerformanceBusy = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SetMIDIChannel (CMIDIDevice::OmniMode, 0);
|
|
|
|
m_bLoadPerformanceBusy = false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::DoSetNewPerformanceBank (void)
|
|
|
|
{
|
|
|
|
m_bLoadPerformanceBankBusy = true;
|
|
|
|
|
|
|
|
unsigned nBankID = m_nSetNewPerformanceBankID;
|
|
|
|
m_PerformanceConfig.SetNewPerformanceBank(nBankID);
|
|
|
|
|
|
|
|
m_bLoadPerformanceBankBusy = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::DoSetFirstPerformance(void)
|
|
|
|
{
|
|
|
|
unsigned nID = m_PerformanceConfig.FindFirstPerformance();
|
|
|
|
SetNewPerformance(nID);
|
|
|
|
m_bSetFirstPerformance = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::SavePerformanceNewFile ()
|
|
|
|
{
|
|
|
|
m_bSavePerformanceNewFile = m_PerformanceConfig.GetInternalFolderOk() && m_PerformanceConfig.CheckFreePerformanceSlot();
|
|
|
|
return m_bSavePerformanceNewFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::DoSavePerformanceNewFile (void)
|
|
|
|
{
|
|
|
|
if (m_PerformanceConfig.CreateNewPerformanceFile())
|
|
|
|
{
|
|
|
|
if(SavePerformance(false))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CMiniDexed::LoadPerformanceParameters(void)
|
|
|
|
{
|
|
|
|
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
|
|
|
|
{
|
|
|
|
|
|
|
|
BankSelect (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);
|
|
|
|
SetCutoff (m_PerformanceConfig.GetCutoff (nTG), nTG);
|
|
|
|
SetResonance (m_PerformanceConfig.GetResonance (nTG), nTG);
|
|
|
|
setPitchbendRange (m_PerformanceConfig.GetPitchBendRange (nTG), nTG);
|
|
|
|
setPitchbendStep (m_PerformanceConfig.GetPitchBendStep (nTG), nTG);
|
|
|
|
setPortamentoMode (m_PerformanceConfig.GetPortamentoMode (nTG), nTG);
|
|
|
|
setPortamentoGlissando (m_PerformanceConfig.GetPortamentoGlissando (nTG), nTG);
|
|
|
|
setPortamentoTime (m_PerformanceConfig.GetPortamentoTime (nTG), nTG);
|
|
|
|
|
|
|
|
m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG);
|
|
|
|
m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG);
|
|
|
|
m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG);
|
|
|
|
|
|
|
|
if(m_PerformanceConfig.VoiceDataFilled(nTG))
|
|
|
|
{
|
|
|
|
uint8_t* tVoiceData = m_PerformanceConfig.GetVoiceDataFromTxt(nTG);
|
|
|
|
m_pTG[nTG]->loadVoiceParameters(tVoiceData);
|
|
|
|
}
|
|
|
|
setMonoMode(m_PerformanceConfig.GetMonoMode(nTG) ? 1 : 0, nTG);
|
|
|
|
SetReverbSend (m_PerformanceConfig.GetReverbSend (nTG), nTG);
|
|
|
|
|
|
|
|
setModWheelRange (m_PerformanceConfig.GetModulationWheelRange (nTG), nTG);
|
|
|
|
setModWheelTarget (m_PerformanceConfig.GetModulationWheelTarget (nTG), nTG);
|
|
|
|
setFootControllerRange (m_PerformanceConfig.GetFootControlRange (nTG), nTG);
|
|
|
|
setFootControllerTarget (m_PerformanceConfig.GetFootControlTarget (nTG), nTG);
|
|
|
|
setBreathControllerRange (m_PerformanceConfig.GetBreathControlRange (nTG), nTG);
|
|
|
|
setBreathControllerTarget (m_PerformanceConfig.GetBreathControlTarget (nTG), nTG);
|
|
|
|
setAftertouchRange (m_PerformanceConfig.GetAftertouchRange (nTG), nTG);
|
|
|
|
setAftertouchTarget (m_PerformanceConfig.GetAftertouchTarget (nTG), 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 ());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string CMiniDexed::GetNewPerformanceDefaultName(void)
|
|
|
|
{
|
|
|
|
return m_PerformanceConfig.GetNewPerformanceDefaultName();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetNewPerformanceName(std::string nName)
|
|
|
|
{
|
|
|
|
m_PerformanceConfig.SetNewPerformanceName(nName);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::IsValidPerformance(unsigned nID)
|
|
|
|
{
|
|
|
|
return m_PerformanceConfig.IsValidPerformance(nID);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::IsValidPerformanceBank(unsigned nBankID)
|
|
|
|
{
|
|
|
|
return m_PerformanceConfig.IsValidPerformanceBank(nBankID);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::SetVoiceName (std::string VoiceName, unsigned nTG)
|
|
|
|
{
|
|
|
|
assert (nTG < CConfig::ToneGenerators);
|
|
|
|
assert (m_pTG[nTG]);
|
|
|
|
char Name[10];
|
|
|
|
strncpy(Name, VoiceName.c_str(),10);
|
|
|
|
m_pTG[nTG]->getName (Name);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::DeletePerformance(unsigned nID)
|
|
|
|
{
|
|
|
|
if (m_PerformanceConfig.IsValidPerformance(nID) && m_PerformanceConfig.GetInternalFolderOk())
|
|
|
|
{
|
|
|
|
m_bDeletePerformance = true;
|
|
|
|
m_nDeletePerformanceID = nID;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::DoDeletePerformance(void)
|
|
|
|
{
|
|
|
|
unsigned nID = m_nDeletePerformanceID;
|
|
|
|
if(m_PerformanceConfig.DeletePerformance(nID))
|
|
|
|
{
|
|
|
|
if (m_PerformanceConfig.Load ())
|
|
|
|
{
|
|
|
|
LoadPerformanceParameters();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SetMIDIChannel (CMIDIDevice::OmniMode, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMiniDexed::GetPerformanceSelectToLoad(void)
|
|
|
|
{
|
|
|
|
return m_pConfig->GetPerformanceSelectToLoad();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMiniDexed::setModController (unsigned controller, unsigned parameter, uint8_t value, uint8_t nTG)
|
|
|
|
{
|
|
|
|
uint8_t nBits;
|
|
|
|
|
|
|
|
switch (controller)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
if (parameter == 0)
|
|
|
|
{
|
|
|
|
setModWheelRange(value, nTG);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
value=constrain(value, 0, 1);
|
|
|
|
nBits=m_nModulationWheelTarget[nTG];
|
|
|
|
value == 1 ? nBits |= 1 << (parameter-1) : nBits &= ~(1 << (parameter-1));
|
|
|
|
setModWheelTarget(nBits , nTG);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
if (parameter == 0)
|
|
|
|
{
|
|
|
|
setFootControllerRange(value, nTG);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
value=constrain(value, 0, 1);
|
|
|
|
nBits=m_nFootControlTarget[nTG];
|
|
|
|
value == 1 ? nBits |= 1 << (parameter-1) : nBits &= ~(1 << (parameter-1));
|
|
|
|
setFootControllerTarget(nBits , nTG);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
if (parameter == 0)
|
|
|
|
{
|
|
|
|
setBreathControllerRange(value, nTG);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
value=constrain(value, 0, 1);
|
|
|
|
nBits=m_nBreathControlTarget[nTG];
|
|
|
|
value == 1 ? nBits |= 1 << (parameter-1) : nBits &= ~(1 << (parameter-1));
|
|
|
|
setBreathControllerTarget(nBits , nTG);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
if (parameter == 0)
|
|
|
|
{
|
|
|
|
setAftertouchRange(value, nTG);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
value=constrain(value, 0, 1);
|
|
|
|
nBits=m_nAftertouchTarget[nTG];
|
|
|
|
value == 1 ? nBits |= 1 << (parameter-1) : nBits &= ~(1 << (parameter-1));
|
|
|
|
setAftertouchTarget(nBits , nTG);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned CMiniDexed::getModController (unsigned controller, unsigned parameter, uint8_t nTG)
|
|
|
|
{
|
|
|
|
unsigned nBits;
|
|
|
|
switch (controller)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
if (parameter == 0)
|
|
|
|
{
|
|
|
|
return m_nModulationWheelRange[nTG];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
|
|
|
nBits=m_nModulationWheelTarget[nTG];
|
|
|
|
nBits &= 1 << (parameter-1);
|
|
|
|
return (nBits != 0 ? 1 : 0) ;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
if (parameter == 0)
|
|
|
|
{
|
|
|
|
return m_nFootControlRange[nTG];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
nBits=m_nFootControlTarget[nTG];
|
|
|
|
nBits &= 1 << (parameter-1) ;
|
|
|
|
return (nBits != 0 ? 1 : 0) ;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
if (parameter == 0)
|
|
|
|
{
|
|
|
|
return m_nBreathControlRange[nTG];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
nBits=m_nBreathControlTarget[nTG];
|
|
|
|
nBits &= 1 << (parameter-1) ;
|
|
|
|
return (nBits != 0 ? 1 : 0) ;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
if (parameter == 0)
|
|
|
|
{
|
|
|
|
return m_nAftertouchRange[nTG];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
nBits=m_nAftertouchTarget[nTG];
|
|
|
|
nBits &= 1 << (parameter-1) ;
|
|
|
|
return (nBits != 0 ? 1 : 0) ;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|