diff --git a/src/Experimental.h b/src/Experimental.h index f2589b621..e77aa9b72 100644 --- a/src/Experimental.h +++ b/src/Experimental.h @@ -150,6 +150,9 @@ // to enable. #define EXPERIMENTAL_MODULE_PREFS +// Define to allow realtime processing in Audacity effects that have been converted. +#define EXPERIMENTAL_REALTIME_AUDACITY_EFFECTS + // Define to include the effects rack (such as it is). //#define EXPERIMENTAL_EFFECTS_RACK diff --git a/src/effects/Effect.cpp b/src/effects/Effect.cpp index d5ecdfcc0..b7b0f910d 100644 --- a/src/effects/Effect.cpp +++ b/src/effects/Effect.cpp @@ -421,6 +421,8 @@ bool Effect::RealtimeInitialize() return mClient->RealtimeInitialize(); } + mBlockSize = 512; + return false; } @@ -455,9 +457,15 @@ bool Effect::RealtimeSuspend() mRealtimeSuspendLock.Leave(); return true; } + + return false; } - return false; + mRealtimeSuspendLock.Enter(); + mRealtimeSuspendCount++; + mRealtimeSuspendLock.Leave(); + + return true; } bool Effect::RealtimeResume() @@ -471,9 +479,15 @@ bool Effect::RealtimeResume() mRealtimeSuspendLock.Leave(); return true; } + + return false; } - return false; + mRealtimeSuspendLock.Enter(); + mRealtimeSuspendCount--; + mRealtimeSuspendLock.Leave(); + + return true; } bool Effect::RealtimeProcessStart() @@ -677,6 +691,7 @@ bool Effect::CloseUI() mUIParent->RemoveEventHandler(this); mUIParent = NULL; + mUIDialog = NULL; return true; } @@ -2270,7 +2285,7 @@ bool Effect::RealtimeAddProcessor(int group, int chans, float rate) } // Add a new processor - mClient->RealtimeAddProcessor(gchans, rate); + RealtimeAddProcessor(gchans, rate); // Bump to next processor mCurrentProcessor++; @@ -2377,7 +2392,7 @@ sampleCount Effect::RealtimeProcess(int group, for (sampleCount block = 0; block < numSamples; block += mBlockSize) { sampleCount cnt = (block + mBlockSize > numSamples ? numSamples - block : mBlockSize); - len += mClient->RealtimeProcess(processor, clientIn, clientOut, cnt); + len += RealtimeProcess(processor, clientIn, clientOut, cnt); for (int i = 0 ; i < mNumAudioIn; i++) { diff --git a/src/effects/Phaser.cpp b/src/effects/Phaser.cpp index 529353a82..24efb0feb 100644 --- a/src/effects/Phaser.cpp +++ b/src/effects/Phaser.cpp @@ -56,6 +56,9 @@ Param( Feedback, int, XO("Feedback"), 0, -100, 100, 1 ); // How many samples are processed before recomputing the lfo value again #define lfoskipsamples 20 +#include +WX_DEFINE_OBJARRAY(EffectPhaserStateArray); + // // EffectPhaser // @@ -110,6 +113,15 @@ EffectType EffectPhaser::GetType() return EffectTypeProcess; } +bool EffectPhaser::SupportsRealtime() +{ +#if defined(EXPERIMENTAL_REALTIME_AUDACITY_EFFECTS) + return true; +#else + return false; +#endif +} + // EffectClientInterface implementation int EffectPhaser::GetAudioInCount() @@ -124,20 +136,10 @@ int EffectPhaser::GetAudioOutCount() bool EffectPhaser::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames chanMap) { - for (int j = 0; j < mStages; j++) - { - old[j] = 0; - } - - skipcount = 0; - gain = 0; - fbout = 0; - lfoskip = mFreq * 2 * M_PI / mSampleRate; - - phase = mPhase * M_PI / 180; + InstanceInit(mMaster, mSampleRate); if (chanMap[0] == ChannelNameFrontRight) { - phase += M_PI; + mMaster.phase += M_PI; } return true; @@ -145,40 +147,43 @@ bool EffectPhaser::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelName sampleCount EffectPhaser::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) { - float *ibuf = inBlock[0]; - float *obuf = outBlock[0]; + return InstanceProcess(mMaster, inBlock, outBlock, blockLen); +} - for (sampleCount i = 0; i < blockLen; i++) - { - double in = ibuf[i]; +bool EffectPhaser::RealtimeInitialize() +{ + SetBlockSize(512); - double m = in + fbout * mFeedback / 101; // Feedback must be less than 100% to avoid infinite gain. + mSlaves.Clear(); - if (((skipcount++) % lfoskipsamples) == 0) - { - //compute sine between 0 and 1 - gain = (1.0 + cos(skipcount * lfoskip + phase)) / 2.0; + return true; +} - // change lfo shape - gain = expm1(gain * phaserlfoshape) / expm1(phaserlfoshape); +bool EffectPhaser::RealtimeAddProcessor(int WXUNUSED(numChannels), float sampleRate) +{ + EffectPhaserState slave; - // attenuate the lfo - gain = 1.0 - gain / 255.0 * mDepth; - } + InstanceInit(slave, sampleRate); - // phasing routine - for (int j = 0; j < mStages; j++) - { - double tmp = old[j]; - old[j] = gain * tmp + m; - m = tmp - gain * old[j]; - } - fbout = m; + mSlaves.Add(slave); - obuf[i] = (float) ((m * mDryWet + in * (255 - mDryWet)) / 255); - } + return true; +} - return blockLen; +bool EffectPhaser::RealtimeFinalize() +{ + mSlaves.Clear(); + + return true; +} + +sampleCount EffectPhaser::RealtimeProcess(int group, + float **inbuf, + float **outbuf, + sampleCount numSamples) +{ + + return InstanceProcess(mSlaves[group], inbuf, outbuf, numSamples); } bool EffectPhaser::GetAutomationParameters(EffectAutomationParameters & parms) @@ -329,6 +334,70 @@ bool EffectPhaser::TransferDataFromWindow() // EffectPhaser implementation +void EffectPhaser::InstanceInit(EffectPhaserState & data, float sampleRate) +{ + data.samplerate = sampleRate; + + for (int j = 0; j < mStages; j++) + { + data.old[j] = 0; + } + + data.skipcount = 0; + data.gain = 0; + data.fbout = 0; + data.laststages = 0; + + return; +} + +sampleCount EffectPhaser::InstanceProcess(EffectPhaserState & data, float **inBlock, float **outBlock, sampleCount blockLen) +{ + float *ibuf = inBlock[0]; + float *obuf = outBlock[0]; + + for (int j = data.laststages; j < mStages; j++) + { + data.old[j] = 0; + } + data.laststages = mStages; + + data.lfoskip = mFreq * 2 * M_PI / data.samplerate; + data.phase = mPhase * M_PI / 180; + + for (sampleCount i = 0; i < blockLen; i++) + { + double in = ibuf[i]; + + double m = in + data.fbout * mFeedback / 101; // Feedback must be less than 100% to avoid infinite gain. + + if (((data.skipcount++) % lfoskipsamples) == 0) + { + //compute sine between 0 and 1 + data.gain = (1.0 + cos(data.skipcount * data.lfoskip + data.phase)) / 2.0; + + // change lfo shape + data.gain = expm1(data.gain * phaserlfoshape) / expm1(phaserlfoshape); + + // attenuate the lfo + data.gain = 1.0 - data.gain / 255.0 * mDepth; + } + + // phasing routine + for (int j = 0; j < mStages; j++) + { + double tmp = data.old[j]; + data.old[j] = data.gain * tmp + m; + m = tmp - data.gain * data.old[j]; + } + data.fbout = m; + + obuf[i] = (float) ((m * mDryWet + in * (255 - mDryWet)) / 255); + } + + return blockLen; +} + void EffectPhaser::OnStagesSlider(wxCommandEvent & evt) { mStages = (evt.GetInt() / SCL_Stages) & ~1; // must be even; diff --git a/src/effects/Phaser.h b/src/effects/Phaser.h index 8619b36d8..d02126fa4 100644 --- a/src/effects/Phaser.h +++ b/src/effects/Phaser.h @@ -29,6 +29,22 @@ class ShuttleGui; #define PHASER_PLUGIN_SYMBOL XO("Phaser") +class EffectPhaserState +{ +public: + // state variables + float samplerate; + sampleCount skipcount; + double old[NUM_STAGES]; // must be as large as MAX_STAGES + double gain; + double fbout; + double lfoskip; + double phase; + int laststages; +}; + +WX_DECLARE_OBJARRAY(EffectPhaserState, EffectPhaserStateArray); + class EffectPhaser : public Effect { public: @@ -43,6 +59,7 @@ public: // EffectIdentInterface implementation virtual EffectType GetType(); + virtual bool SupportsRealtime(); // EffectClientInterface implementation @@ -50,6 +67,13 @@ public: virtual int GetAudioOutCount(); virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool RealtimeInitialize(); + virtual bool RealtimeAddProcessor(int numChannels, float sampleRate); + virtual bool RealtimeFinalize(); + virtual sampleCount RealtimeProcess(int group, + float **inbuf, + float **outbuf, + sampleCount numSamples); virtual bool GetAutomationParameters(EffectAutomationParameters & parms); virtual bool SetAutomationParameters(EffectAutomationParameters & parms); @@ -62,6 +86,9 @@ public: protected: // EffectPhaser implementation + void InstanceInit(EffectPhaserState & data, float sampleRate); + sampleCount InstanceProcess(EffectPhaserState & data, float **inBlock, float **outBlock, sampleCount blockLen); + void OnStagesSlider(wxCommandEvent & evt); void OnDryWetSlider(wxCommandEvent & evt); void OnFeedbackSlider(wxCommandEvent & evt); @@ -87,13 +114,8 @@ protected: */ private: - // state variables - sampleCount skipcount; - double old[NUM_STAGES]; // must be as large as MAX_STAGES - double gain; - double fbout; - double lfoskip; - double phase; + EffectPhaserState mMaster; + EffectPhaserStateArray mSlaves; // parameters int mStages; diff --git a/src/effects/Wahwah.cpp b/src/effects/Wahwah.cpp index 7cf551093..0b0e448f0 100644 --- a/src/effects/Wahwah.cpp +++ b/src/effects/Wahwah.cpp @@ -49,6 +49,9 @@ Param( FreqOfs, int, XO("Offset"), 30, 0, 100, 1 ); / // How many samples are processed before recomputing the lfo value again #define lfoskipsamples 30 +#include +WX_DEFINE_OBJARRAY(EffectWahwahStateArray); + // // EffectWahwah // @@ -100,6 +103,15 @@ EffectType EffectWahwah::GetType() return EffectTypeProcess; } +bool EffectWahwah::SupportsRealtime() +{ +#if defined(EXPERIMENTAL_REALTIME_AUDACITY_EFFECTS) + return true; +#else + return false; +#endif +} + // EffectClientInterface implementation int EffectWahwah::GetAudioInCount() @@ -114,26 +126,11 @@ int EffectWahwah::GetAudioOutCount() bool EffectWahwah::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames chanMap) { - lfoskip = mFreq * 2 * M_PI / mSampleRate; - skipcount = 0; - xn1 = 0; - xn2 = 0; - yn1 = 0; - yn2 = 0; - b0 = 0; - b1 = 0; - b2 = 0; - a0 = 0; - a1 = 0; - a2 = 0; + InstanceInit(mMaster, mSampleRate); - depth = mDepth / 100.0; - freqofs = mFreqOfs / 100.0; - - phase = mPhase * M_PI / 180.0; if (chanMap[0] == ChannelNameFrontRight) { - phase += M_PI; + mMaster.phase += M_PI; } return true; @@ -141,41 +138,43 @@ bool EffectWahwah::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelName sampleCount EffectWahwah::ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen) { - float *ibuf = inBlock[0]; - float *obuf = outBlock[0]; - double frequency, omega, sn, cs, alpha; - double in, out; + return InstanceProcess(mMaster, inBlock, outBlock, blockLen); +} - for (int i = 0; i < blockLen; i++) - { - in = (double) ibuf[i]; +bool EffectWahwah::RealtimeInitialize() +{ + SetBlockSize(512); - if ((skipcount++) % lfoskipsamples == 0) - { - frequency = (1 + cos(skipcount * lfoskip + phase)) / 2; - frequency = frequency * depth * (1 - freqofs) + freqofs; - frequency = exp((frequency - 1) * 6); - omega = M_PI * frequency; - sn = sin(omega); - cs = cos(omega); - alpha = sn / (2 * mRes); - b0 = (1 - cs) / 2; - b1 = 1 - cs; - b2 = (1 - cs) / 2; - a0 = 1 + alpha; - a1 = -2 * cs; - a2 = 1 - alpha; - }; - out = (b0 * in + b1 * xn1 + b2 * xn2 - a1 * yn1 - a2 * yn2) / a0; - xn2 = xn1; - xn1 = in; - yn2 = yn1; - yn1 = out; + mSlaves.Clear(); - obuf[i] = (float) out; - } + return true; +} - return blockLen; +bool EffectWahwah::RealtimeAddProcessor(int WXUNUSED(numChannels), float sampleRate) +{ + EffectWahwahState slave; + + InstanceInit(slave, sampleRate); + + mSlaves.Add(slave); + + return true; +} + +bool EffectWahwah::RealtimeFinalize() +{ + mSlaves.Clear(); + + return true; +} + +sampleCount EffectWahwah::RealtimeProcess(int group, + float **inbuf, + float **outbuf, + sampleCount numSamples) +{ + + return InstanceProcess(mSlaves[group], inbuf, outbuf, numSamples); } bool EffectWahwah::GetAutomationParameters(EffectAutomationParameters & parms) @@ -299,6 +298,73 @@ bool EffectWahwah::TransferDataFromWindow() // EffectWahwah implementation +void EffectWahwah::InstanceInit(EffectWahwahState & data, float sampleRate) +{ + data.samplerate = sampleRate; + data.lfoskip = mFreq * 2 * M_PI / sampleRate; + data.skipcount = 0; + data.xn1 = 0; + data.xn2 = 0; + data.yn1 = 0; + data.yn2 = 0; + data.b0 = 0; + data.b1 = 0; + data.b2 = 0; + data.a0 = 0; + data.a1 = 0; + data.a2 = 0; + + data.depth = mDepth / 100.0; + data.freqofs = mFreqOfs / 100.0; + + data.phase = mPhase * M_PI / 180.0; +} + +sampleCount EffectWahwah::InstanceProcess(EffectWahwahState & data, float **inBlock, float **outBlock, sampleCount blockLen) +{ + float *ibuf = inBlock[0]; + float *obuf = outBlock[0]; + double frequency, omega, sn, cs, alpha; + double in, out; + + data.lfoskip = mFreq * 2 * M_PI / data.samplerate; + data.depth = mDepth / 100.0; + data.freqofs = mFreqOfs / 100.0; + + data.phase = mPhase * M_PI / 180.0; + + for (int i = 0; i < blockLen; i++) + { + in = (double) ibuf[i]; + + if ((data.skipcount++) % lfoskipsamples == 0) + { + frequency = (1 + cos(data.skipcount * data.lfoskip + data.phase)) / 2; + frequency = frequency * data.depth * (1 - data.freqofs) + data.freqofs; + frequency = exp((frequency - 1) * 6); + omega = M_PI * frequency; + sn = sin(omega); + cs = cos(omega); + alpha = sn / (2 * mRes); + data.b0 = (1 - cs) / 2; + data.b1 = 1 - cs; + data.b2 = (1 - cs) / 2; + data.a0 = 1 + alpha; + data.a1 = -2 * cs; + data.a2 = 1 - alpha; + }; + out = (data.b0 * in + data.b1 * data.xn1 + data.b2 * data.xn2 - data.a1 * data.yn1 - data.a2 * data.yn2) / data.a0; + data.xn2 = data.xn1; + data.xn1 = in; + data.yn2 = data.yn1; + data.yn1 = out; + + obuf[i] = (float) out; + } + + return blockLen; +} + void EffectWahwah::OnFreqSlider(wxCommandEvent & evt) { mFreq = (double) evt.GetInt() / SCL_Freq; diff --git a/src/effects/Wahwah.h b/src/effects/Wahwah.h index 0e39cd501..4cb9d1565 100644 --- a/src/effects/Wahwah.h +++ b/src/effects/Wahwah.h @@ -27,6 +27,21 @@ class ShuttleGui; #define WAHWAH_PLUGIN_SYMBOL XO("Wahwah") +class EffectWahwahState +{ +public: + float samplerate; + double depth; + double freqofs; + double phase; + double lfoskip; + unsigned long skipcount; + double xn1, xn2, yn1, yn2; + double b0, b1, b2, a0, a1, a2; +}; + +WX_DECLARE_OBJARRAY(EffectWahwahState, EffectWahwahStateArray); + class EffectWahwah : public Effect { public: @@ -41,6 +56,7 @@ public: // EffectIdentInterface implementation virtual EffectType GetType(); + virtual bool SupportsRealtime(); // EffectClientInterface implementation @@ -48,6 +64,13 @@ public: virtual int GetAudioOutCount(); virtual bool ProcessInitialize(sampleCount totalLen, ChannelNames chanMap = NULL); virtual sampleCount ProcessBlock(float **inBlock, float **outBlock, sampleCount blockLen); + virtual bool RealtimeInitialize(); + virtual bool RealtimeAddProcessor(int numChannels, float sampleRate); + virtual bool RealtimeFinalize(); + virtual sampleCount RealtimeProcess(int group, + float **inbuf, + float **outbuf, + sampleCount numSamples); virtual bool GetAutomationParameters(EffectAutomationParameters & parms); virtual bool SetAutomationParameters(EffectAutomationParameters & parms); @@ -60,6 +83,9 @@ public: private: // EffectWahwah implementation + void InstanceInit(EffectWahwahState & data, float sampleRate); + sampleCount InstanceProcess(EffectWahwahState & data, float **inBlock, float **outBlock, sampleCount blockLen); + void OnFreqSlider(wxCommandEvent & evt); void OnPhaseSlider(wxCommandEvent & evt); void OnDepthSlider(wxCommandEvent & evt); @@ -73,15 +99,10 @@ private: void OnFreqOffText(wxCommandEvent & evt); private: - double depth; - double freqofs; - double phase; - double lfoskip; - unsigned long skipcount; - double xn1, xn2, yn1, yn2; - double b0, b1, b2, a0, a1, a2; + EffectWahwahState mMaster; + EffectWahwahStateArray mSlaves; -/* Parameters: + /* Parameters: mFreq - LFO frequency mPhase - LFO startphase in RADIANS - useful for stereo WahWah mDepth - Wah depth