Delay FX: Support for tempo sync

pull/764/head
jnonis 6 months ago
parent bed555fcfb
commit 9c55aa6a46
  1. 2
      src/Makefile
  2. 7
      src/effect_base.h
  3. 98
      src/effect_delay.cpp
  4. 32
      src/effect_delay.h
  5. 2
      src/effect_talreverb3.h
  6. 50
      src/minidexed.cpp
  7. 13
      src/minidexed.h
  8. 51
      src/uimenu.cpp
  9. 3
      src/uimenu.h

@ -19,3 +19,5 @@ OPTIMIZE = -O3
include ./Synth_Dexed.mk include ./Synth_Dexed.mk
include ./Rules.mk include ./Rules.mk
EXTRACLEAN += moddistortion/*.[od]

@ -35,6 +35,13 @@ public:
{ {
} }
/**
* Set the tempo in BPM.
*/
virtual void setTempo(unsigned tempo)
{
}
virtual void setParameter(unsigned param, unsigned value) virtual void setParameter(unsigned param, unsigned value)
{ {
} }

@ -21,9 +21,9 @@ AudioEffectDelay::AudioEffectDelay(float32_t samplerate) : AudioEffect(samplerat
memset(this->bufferL, 0, this->bufferSize * sizeof(float32_t)); memset(this->bufferL, 0, this->bufferSize * sizeof(float32_t));
memset(this->bufferR, 0, this->bufferSize * sizeof(float32_t)); memset(this->bufferR, 0, this->bufferSize * sizeof(float32_t));
this->timeL = 0.36f; this->setParameter(AudioEffectDelay::Param::TIME_L, 360);
this->timeR = 0.36f; this->setParameter(AudioEffectDelay::Param::TIME_R, 360);
this->feedback = 0.6f; this->setParameter(AudioEffectDelay::Param::FEEDBACK, 60);
this->pingPongMode = false; this->pingPongMode = false;
this->setMix(0.5f); this->setMix(0.5f);
} }
@ -45,6 +45,13 @@ void AudioEffectDelay::initializeSendFX()
this->setParameter(AudioEffectDelay::Param::MIX, 100); this->setParameter(AudioEffectDelay::Param::MIX, 100);
} }
void AudioEffectDelay::setTempo(unsigned tempo)
{
this->tempo = (float32_t) tempo;
this->setParameter(AudioEffectDelay::Param::TIME_L, timeLValue);
this->setParameter(AudioEffectDelay::Param::TIME_R, timeRValue);
}
void AudioEffectDelay::setParameter(unsigned param, unsigned value) void AudioEffectDelay::setParameter(unsigned param, unsigned value)
{ {
switch (param) switch (param)
@ -53,10 +60,12 @@ void AudioEffectDelay::setParameter(unsigned param, unsigned value)
this->setBypass(value == 1); this->setBypass(value == 1);
break; break;
case AudioEffectDelay::Param::TIME_L: case AudioEffectDelay::Param::TIME_L:
this->timeL = (float32_t) value / 1000.0f; this->timeLValue = value;
this->timeL = this->calculateTime(value);
break; break;
case AudioEffectDelay::Param::TIME_R: case AudioEffectDelay::Param::TIME_R:
this->timeR = (float32_t) value / 1000.0f; this->timeRValue = value;
this->timeR = this->calculateTime(value);
break; break;
case AudioEffectDelay::Param::FEEDBACK: case AudioEffectDelay::Param::FEEDBACK:
this->feedback = (float32_t) value / 100.0f; this->feedback = (float32_t) value / 100.0f;
@ -82,9 +91,9 @@ unsigned AudioEffectDelay::getParameter(unsigned param)
case AudioEffectDelay::Param::BYPASS: case AudioEffectDelay::Param::BYPASS:
return this->getBypass() ? 1 : 0; return this->getBypass() ? 1 : 0;
case AudioEffectDelay::Param::TIME_L: case AudioEffectDelay::Param::TIME_L:
return roundf(this->timeL * 1000); return this->timeLValue;
case AudioEffectDelay::Param::TIME_R: case AudioEffectDelay::Param::TIME_R:
return roundf(this->timeR * 1000); return this->timeRValue;
case AudioEffectDelay::Param::FEEDBACK: case AudioEffectDelay::Param::FEEDBACK:
return roundf(this->feedback * 100); return roundf(this->feedback * 100);
case AudioEffectDelay::Param::TONE: case AudioEffectDelay::Param::TONE:
@ -98,6 +107,81 @@ unsigned AudioEffectDelay::getParameter(unsigned param)
} }
} }
float32_t AudioEffectDelay::calculateTime(unsigned value)
{
if (value < AudioEffectDelay::MAX_DELAY_TIME * 1000)
{
return (float32_t) value / 1000.0f;
}
float32_t numerator;
float32_t denominator;
switch (value - AudioEffectDelay::MAX_DELAY_TIME * 1000)
{
case AudioEffectDelay::SyncTime::T_1_32:
numerator = 1.0f;
denominator = 32.0f;
break;
case AudioEffectDelay::SyncTime::T_1_24:
numerator = 1.0f;
denominator = 24.0f;
break;
case AudioEffectDelay::SyncTime::T_1_16:
numerator = 1.0f;
denominator = 16.0f;
break;
case AudioEffectDelay::SyncTime::T_1_12:
numerator = 1.0f;
denominator = 12.0f;
break;
case AudioEffectDelay::SyncTime::T_3_32:
numerator = 3.0f;
denominator = 32.0f;
break;
case AudioEffectDelay::SyncTime::T_1_8:
numerator = 1.0f;
denominator = 8.0f;
break;
case AudioEffectDelay::SyncTime::T_1_6:
numerator = 1.0f;
denominator = 6.0f;
break;
case AudioEffectDelay::SyncTime::T_3_16:
numerator = 3.0f;
denominator = 16.0f;
break;
case AudioEffectDelay::SyncTime::T_1_4:
numerator = 1.0f;
denominator = 4.0f;
break;
case AudioEffectDelay::SyncTime::T_1_3:
numerator = 1.0f;
denominator = 3.0f;
break;
case AudioEffectDelay::SyncTime::T_3_8:
numerator = 3.0f;
denominator = 8.0f;
break;
case AudioEffectDelay::SyncTime::T_1_2:
numerator = 1.0f;
denominator = 2.0f;
break;
case AudioEffectDelay::SyncTime::T_2_3:
numerator = 2.0f;
denominator = 3.0f;
break;
case AudioEffectDelay::SyncTime::T_3_4:
numerator = 3.0f;
denominator = 4.0f;
break;
case AudioEffectDelay::SyncTime::T_1_1:
default:
numerator = 1.0f;
denominator = 1.0f;
break;
}
return 60000.0f / this->tempo * numerator / denominator / 1000.0f;
}
void AudioEffectDelay::setMix(float32_t mix) void AudioEffectDelay::setMix(float32_t mix)
{ {
this->mix = mix; this->mix = mix;

@ -2,7 +2,8 @@
* Stereo Delay * Stereo Delay
* Features: * Features:
* - Tone control using Low Pass Filter * - Tone control using Low Pass Filter
* - Ping Pong mode. * - Ping Pong mode
* - Tempo Sync
* Javier Nonis (https://github.com/jnonis) - 2024 * Javier Nonis (https://github.com/jnonis) - 2024
*/ */
#ifndef _EFFECT_DELAY_H #ifndef _EFFECT_DELAY_H
@ -28,12 +29,33 @@ public:
UNKNOWN UNKNOWN
}; };
enum SyncTime
{
T_1_32,
T_1_24,
T_1_16,
T_1_12,
T_3_32,
T_1_8,
T_1_6,
T_3_16,
T_1_4,
T_1_3,
T_3_8,
T_1_2,
T_2_3,
T_3_4,
T_1_1,
T_UNKNOWN
};
AudioEffectDelay(float32_t samplerate); AudioEffectDelay(float32_t samplerate);
virtual ~AudioEffectDelay(); virtual ~AudioEffectDelay();
virtual unsigned getId(); virtual unsigned getId();
virtual void initializeSendFX(); virtual void initializeSendFX();
virtual void setTempo(unsigned tempo);
virtual void setParameter(unsigned param, unsigned value); virtual void setParameter(unsigned param, unsigned value);
virtual unsigned getParameter(unsigned param); virtual unsigned getParameter(unsigned param);
protected: protected:
@ -48,15 +70,19 @@ private:
float32_t* bufferR; float32_t* bufferR;
unsigned index; unsigned index;
float32_t timeL; // Left delay time in seconds (0.0 - 2.0) unsigned timeLValue; // To keep the time value for both millis or sync time
float32_t timeR; // Right delay time in seconds (0.0 - 2.0) unsigned timeRValue; // To keep the time value for both millis or sync time
float32_t timeL; // Left delay time in seconds
float32_t timeR; // Right delay time in seconds
float32_t feedback; // Feedback (0.0 - 1.0) float32_t feedback; // Feedback (0.0 - 1.0)
AudioEffectLPF* lpf; AudioEffectLPF* lpf;
bool pingPongMode; bool pingPongMode;
float32_t mix; float32_t mix;
float32_t dryMix; float32_t dryMix;
float32_t wetMix; float32_t wetMix;
float32_t tempo = 120;
float32_t calculateTime(unsigned value);
void setMix(float32_t mix); void setMix(float32_t mix);
}; };

@ -13,8 +13,6 @@
class AudioEffectTalReverb3 : public AudioEffect class AudioEffectTalReverb3 : public AudioEffect
{ {
public: public:
static const unsigned MAX_DELAY_TIME = 1;
enum Param enum Param
{ {
BYPASS, BYPASS,

@ -64,7 +64,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
m_nClockCounter = 0; m_nClockCounter = 0;
m_mClockTime = 0; m_mClockTime = 0;
m_nBPM = 120; m_nTempo = 120;
for (unsigned i = 0; i < CConfig::ToneGenerators; i++) for (unsigned i = 0; i < CConfig::ToneGenerators; i++)
{ {
@ -96,8 +96,10 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
m_nAftertouchRange[i]=99; m_nAftertouchRange[i]=99;
m_nAftertouchTarget[i]=0; m_nAftertouchTarget[i]=0;
#ifdef ARM_ALLOW_MULTI_CORE
memset(m_OutputLevel[i][0], 0, CConfig::MaxChunkSize * sizeof(float32_t)); memset(m_OutputLevel[i][0], 0, CConfig::MaxChunkSize * sizeof(float32_t));
memset(m_OutputLevel[i][1], 0, CConfig::MaxChunkSize * sizeof(float32_t)); memset(m_OutputLevel[i][1], 0, CConfig::MaxChunkSize * sizeof(float32_t));
#endif
m_InsertFXSpinLock[i] = new CSpinLock(); m_InsertFXSpinLock[i] = new CSpinLock();
m_InsertFX[i] = new AudioEffectNone(pConfig->GetSampleRate ()); m_InsertFX[i] = new AudioEffectNone(pConfig->GetSampleRate ());
@ -610,6 +612,7 @@ void CMiniDexed::setInsertFXType (unsigned nType, unsigned nTG)
m_InsertFXSpinLock[nTG]->Acquire(); m_InsertFXSpinLock[nTG]->Acquire();
delete m_InsertFX[nTG]; delete m_InsertFX[nTG];
m_InsertFX[nTG] = newAudioEffect(nType, m_pConfig->GetSampleRate()); m_InsertFX[nTG] = newAudioEffect(nType, m_pConfig->GetSampleRate());
m_InsertFX[nTG]->setTempo(m_nTempo);
m_InsertFXSpinLock[nTG]->Release(); m_InsertFXSpinLock[nTG]->Release();
m_UI.ParameterChanged (); m_UI.ParameterChanged ();
@ -623,6 +626,7 @@ void CMiniDexed::setSendFXType (unsigned nType)
delete m_SendFX; delete m_SendFX;
} }
m_SendFX = newAudioEffect(nType, m_pConfig->GetSampleRate()); m_SendFX = newAudioEffect(nType, m_pConfig->GetSampleRate());
m_SendFX->setTempo(m_nTempo);
m_SendFX->initializeSendFX(); m_SendFX->initializeSendFX();
m_SendFXSpinLock.Release(); m_SendFXSpinLock.Release();
@ -728,9 +732,25 @@ void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG)
unsigned CMiniDexed::getTempo (void) unsigned CMiniDexed::getTempo (void)
{ {
return this->m_nBPM; return this->m_nTempo;
} }
void CMiniDexed::setTempo(unsigned nValue)
{
m_nTempo = nValue;
// Set Tempo to FXs
m_SendFX->setTempo(m_nTempo);
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
{
m_InsertFX[nTG]->setTempo(m_nTempo);
}
// Update UI
m_UI.ParameterChanged();
}
void CMiniDexed::handleClock (void) void CMiniDexed::handleClock (void)
{ {
if (m_nClockCounter == 0) if (m_nClockCounter == 0)
@ -747,11 +767,11 @@ void CMiniDexed::handleClock (void)
// Calculate BPM // Calculate BPM
auto now = std::chrono::high_resolution_clock::now(); auto now = std::chrono::high_resolution_clock::now();
unsigned timeDelta = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count() - m_mClockTime; unsigned timeDelta = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count() - m_mClockTime;
m_nBPM = roundf(60000 / timeDelta); unsigned newTempo = roundf(60000 / timeDelta);
if (m_nTempo != newTempo)
// Set BPM to FXs {
this->setTempo(newTempo);
m_UI.ParameterChanged(); }
} }
} }
@ -950,6 +970,10 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue)
BankSelectPerformance(nValue); BankSelectPerformance(nValue);
break; break;
case ParameterTempo:
this->setTempo(nValue);
break;
default: default:
assert (0); assert (0);
break; break;
@ -966,6 +990,8 @@ int CMiniDexed::GetParameter (TParameter Parameter)
return m_SendFX->getId(); return m_SendFX->getId();
case ParameterSendFXLevel: case ParameterSendFXLevel:
return roundf(m_SendFXLevel * 100); return roundf(m_SendFXLevel * 100);
case ParameterTempo:
return this->getTempo();
default: default:
return m_nParameter[Parameter]; return m_nParameter[Parameter];
} }
@ -1193,9 +1219,17 @@ void CMiniDexed::ProcessSound (void)
m_InsertFX[0]->process(SampleBuffer[0], SampleBuffer[0], SampleBuffer[0], SampleBuffer[1], nFrames); m_InsertFX[0]->process(SampleBuffer[0], SampleBuffer[0], SampleBuffer[0], SampleBuffer[1], nFrames);
m_InsertFXSpinLock[0]->Release(); m_InsertFXSpinLock[0]->Release();
// Convert dual float array (left, right) to single int16 array (left/right)
float32_t tmp_float[nFrames*2];
for(uint16_t i=0; i<nFrames;i++)
{
tmp_float[i*2]=SampleBuffer[0][i];
tmp_float[(i*2)+1]=SampleBuffer[1][i];
}
// Convert single float array (mono) to int16 array // Convert single float array (mono) to int16 array
int16_t tmp_int[nFrames]; int16_t tmp_int[nFrames];
arm_float_to_q15(SampleBuffer,tmp_int,nFrames); arm_float_to_q15(tmp_float,tmp_int,nFrames*2);
if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int))
{ {

@ -82,6 +82,7 @@ public:
void SetMIDIChannel (uint8_t uchChannel, unsigned nTG); void SetMIDIChannel (uint8_t uchChannel, unsigned nTG);
unsigned getTempo(void); unsigned getTempo(void);
void setTempo(unsigned nValue);
void handleClock(void); void handleClock(void);
void keyup (int16_t pitch, unsigned nTG); void keyup (int16_t pitch, unsigned nTG);
@ -167,6 +168,7 @@ public:
ParameterReverbLevel, ParameterReverbLevel,
ParameterPerformanceSelectChannel, ParameterPerformanceSelectChannel,
ParameterPerformanceBank, ParameterPerformanceBank,
ParameterTempo,
ParameterUnknown ParameterUnknown
}; };
@ -251,15 +253,6 @@ private:
void LoadPerformanceParameters(void); void LoadPerformanceParameters(void);
void ProcessSound (void); void ProcessSound (void);
unsigned getChorusIEnable(uint8_t nTG);
void setChorusIEnable(uint8_t nTG, unsigned enable);
unsigned getChorusIIEnable(uint8_t nTG);
void setChorusIIEnable(uint8_t nTG, unsigned enable);
unsigned getChorusIRate(uint8_t nTG);
void setChorusIRate(uint8_t nTG, unsigned int rate);
unsigned getChorusIIRate(uint8_t nTG);
void setChorusIIRate(uint8_t nTG, unsigned int rate);
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
enum TCoreStatus enum TCoreStatus
{ {
@ -365,7 +358,7 @@ private:
unsigned m_nClockCounter; unsigned m_nClockCounter;
unsigned long m_mClockTime; unsigned long m_mClockTime;
unsigned m_nBPM; unsigned m_nTempo; // Tempo in BPM
}; };
#endif #endif

@ -53,6 +53,7 @@ const CUIMenu::TMenuItem CUIMenu::s_MainMenu[] =
{"TG8", MenuHandler, s_TGMenu, 7}, {"TG8", MenuHandler, s_TGMenu, 7},
#endif #endif
{"Effects", MenuHandler, s_EffectsMenu}, {"Effects", MenuHandler, s_EffectsMenu},
{"Tempo", EditGlobalParameter, 0, CMiniDexed::ParameterTempo},
{"Performance", MenuHandler, s_PerformanceMenu}, {"Performance", MenuHandler, s_PerformanceMenu},
{0} {0}
}; };
@ -305,7 +306,7 @@ const CUIMenu::TMenuItem CUIMenu::s_SaveMenu[] =
// must match CMiniDexed::TParameter // must match CMiniDexed::TParameter
const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknown] = const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknown] =
{ {
{0, 1, 1, ToOnOff}, // ParameterCompessorEnable {0, 1, 1, ToOnOff}, // ParameterCompressorEnable
{0, 7, 1, ToFXType}, // ParameterSendFXType {0, 7, 1, ToFXType}, // ParameterSendFXType
{0, 100, 1}, // ParameterSendFXLevel {0, 100, 1}, // ParameterSendFXLevel
{0, 1, 1, ToOnOff}, // ParameterReverbEnable {0, 1, 1, ToOnOff}, // ParameterReverbEnable
@ -316,7 +317,8 @@ const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknow
{0, 99, 1}, // ParameterReverbDiffusion {0, 99, 1}, // ParameterReverbDiffusion
{0, 99, 1}, // ParameterReverbLevel {0, 99, 1}, // ParameterReverbLevel
{0, CMIDIDevice::ChannelUnknown-1, 1, ToMIDIChannel}, // ParameterPerformanceSelectChannel {0, CMIDIDevice::ChannelUnknown-1, 1, ToMIDIChannel}, // ParameterPerformanceSelectChannel
{0, NUM_PERFORMANCE_BANKS, 1} // ParameterPerformanceBank {0, NUM_PERFORMANCE_BANKS, 1}, // ParameterPerformanceBank
{30, 250, 1} // ParameterTempo
}; };
// must match CMiniDexed::TTGParameter // must match CMiniDexed::TTGParameter
@ -372,8 +374,8 @@ const CUIMenu::TParameter CUIMenu::s_TGFXChorusParam[AudioEffectChorus::Param::U
const CUIMenu::TParameter CUIMenu::s_TGFXDelayParam[AudioEffectDelay::Param::UNKNOWN] = const CUIMenu::TParameter CUIMenu::s_TGFXDelayParam[AudioEffectDelay::Param::UNKNOWN] =
{ {
{0, 1, 1, ToOnOff}, // BYPASS {0, 1, 1, ToOnOff}, // BYPASS
{0, AudioEffectDelay::MAX_DELAY_TIME * 1000, 1}, // TIME_L {0, AudioEffectDelay::MAX_DELAY_TIME * 1000 + AudioEffectDelay::SyncTime::T_UNKNOWN, 1, ToDelayTime}, // TIME_L
{0, AudioEffectDelay::MAX_DELAY_TIME * 1000, 1}, // TIME_R {0, AudioEffectDelay::MAX_DELAY_TIME * 1000 + AudioEffectDelay::SyncTime::T_UNKNOWN, 1, ToDelayTime}, // TIME_R
{0, 100, 1}, // FEEDBACK, {0, 100, 1}, // FEEDBACK,
{0, 100, 1}, // TONE {0, 100, 1}, // TONE
{0, 1, 1, ToOnOff}, // PING_PONG {0, 1, 1, ToOnOff}, // PING_PONG
@ -1786,6 +1788,47 @@ string CUIMenu::ToMix (int nValue)
} }
} }
string CUIMenu::ToDelayTime (int nValue)
{
if (nValue < (int) (AudioEffectDelay::MAX_DELAY_TIME * 1000)) {
return to_string (nValue);
}
switch (nValue - AudioEffectDelay::MAX_DELAY_TIME * 1000)
{
case AudioEffectDelay::SyncTime::T_1_32:
return "1/32";
case AudioEffectDelay::SyncTime::T_1_24:
return "1/24";
case AudioEffectDelay::SyncTime::T_1_16:
return "1/16";
case AudioEffectDelay::SyncTime::T_1_12:
return "1/12";
case AudioEffectDelay::SyncTime::T_3_32:
return "3/32";
case AudioEffectDelay::SyncTime::T_1_8:
return "1/8";
case AudioEffectDelay::SyncTime::T_1_6:
return "1/6";
case AudioEffectDelay::SyncTime::T_3_16:
return "3/16";
case AudioEffectDelay::SyncTime::T_1_4:
return "1/4";
case AudioEffectDelay::SyncTime::T_1_3:
return "1/3";
case AudioEffectDelay::SyncTime::T_3_8:
return "3/8";
case AudioEffectDelay::SyncTime::T_1_2:
return "1/2";
case AudioEffectDelay::SyncTime::T_2_3:
return "2/3";
case AudioEffectDelay::SyncTime::T_3_4:
return "3/4";
case AudioEffectDelay::SyncTime::T_1_1:
default:
return "1/1";
}
}
void CUIMenu::TGShortcutHandler (TMenuEvent Event) void CUIMenu::TGShortcutHandler (TMenuEvent Event)
{ {
assert (m_nCurrentMenuDepth >= 2); assert (m_nCurrentMenuDepth >= 2);

@ -128,7 +128,8 @@ private:
static std::string ToPolyMono (int nValue); static std::string ToPolyMono (int nValue);
static std::string ToFXType (int nValue); static std::string ToFXType (int nValue);
static std::string ToMix (int nValue); static std::string ToMix (int nValue);
static std::string ToDelayTime (int nValue);
void TGShortcutHandler (TMenuEvent Event); void TGShortcutHandler (TMenuEvent Event);
void OPShortcutHandler (TMenuEvent Event); void OPShortcutHandler (TMenuEvent Event);

Loading…
Cancel
Save