diff --git a/include/audacity/EffectInterface.h b/include/audacity/EffectInterface.h index b2d147465..0118b5f27 100644 --- a/include/audacity/EffectInterface.h +++ b/include/audacity/EffectInterface.h @@ -122,11 +122,12 @@ public: virtual bool ProcessFinalize() = 0; virtual sampleCount ProcessBlock(float **inbuf, float **outbuf, sampleCount size) = 0; - virtual bool RealtimeInitialize(int numChannels, float sampleRate) = 0; + virtual bool RealtimeInitialize() = 0; + virtual bool RealtimeAddProcessor(int numChannels, float sampleRate) = 0; virtual bool RealtimeFinalize() = 0; virtual bool RealtimeSuspend() = 0; virtual bool RealtimeResume() = 0; - virtual sampleCount RealtimeProcess(float **inbuf, float **outbuf, sampleCount size) = 0; + virtual sampleCount RealtimeProcess(int index, float **inbuf, float **outbuf, sampleCount size) = 0; virtual bool ShowInterface(void *parent) = 0; }; diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 1723887d9..100b7c6ba 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -1358,6 +1358,13 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, } } while(!bDone); +#if defined(EXPERIMENTAL_REALTIME_EFFECTS) + if (mNumPlaybackChannels > 0) + { + EffectManager::Get().RealtimeInitialize(&mPlaybackTracks); + } +#endif + #ifdef AUTOMATED_INPUT_LEVEL_ADJUSTMENT AILASetStartTime(); #endif @@ -1371,13 +1378,6 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks, while( mAudioThreadShouldCallFillBuffersOnce == true ) wxMilliSleep( 50 ); -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) - if (mNumPlaybackChannels > 0) - { - EffectManager::Get().RealtimeInitialize(1, sampleRate); - } -#endif - #ifdef EXPERIMENTAL_MIDI_OUT // if no playback, reset the midi time to zero to roughly sync // with recording (or if recording is not going to happen, just @@ -3497,10 +3497,22 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, if( gAudioIO->mMidiPlaybackTracks[t]->GetSolo() ) numSolo++; #endif - for( t = 0; t < numPlaybackTracks; t++) + + int logicalCnt = 0; + int chanCnt = 0; + WaveTrack **chans = (WaveTrack **) alloca(numPlaybackChannels * sizeof(WaveTrack *)); + float **tempBufs = (float **) alloca(numPlaybackChannels * sizeof(float *)); + for (int c = 0; c < numPlaybackChannels; c++) + { + tempBufs[c] = (float *) alloca(framesPerBuffer * sizeof(float)); + } + + for (t = 0; t < numPlaybackTracks; t++) { WaveTrack *vt = gAudioIO->mPlaybackTracks[t]; + chans[chanCnt] = vt; + if (linkFlag) linkFlag = false; else { @@ -3521,15 +3533,21 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, #ifdef ORIGINAL_DO_NOT_PLAY_ALL_MUTED_TRACKS_TO_END // this is original code prior to r10680 -RBD if (cut) - { - gAudioIO->mPlaybackBuffers[t]->Discard(framesPerBuffer); - continue; - } + { + gAudioIO->mPlaybackBuffers[t]->Discard(framesPerBuffer); + continue; + } - unsigned int len = (unsigned int) - gAudioIO->mPlaybackBuffers[t]->Get((samplePtr)tempFloats, - floatSample, - (int)framesPerBuffer); + int len = gAudioIO->mPlaybackBuffers[t]->Get((samplePtr)tempBufs[chanCnt], + floatSample, + (int)framesPerBuffer); + + chanCnt++; + + if (linkFlag) + { + continue; + } #else // This code was reorganized so that if all audio tracks // are muted, we still return paComplete when the end of @@ -3553,6 +3571,10 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, (int)framesPerBuffer); } #endif + +#if defined(EXPERIMENTAL_REALTIME_EFFECTS) + len = EffectManager::Get().RealtimeProcess(logicalCnt++, tempBufs, len); +#endif // If our buffer is empty and the time indicator is past // the end, then we've actually finished playing the entire // selection. @@ -3566,47 +3588,49 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer, if (cut) // no samples to process, they've been discarded continue; #endif - -#if defined(EXPERIMENTAL_REALTIME_EFFECTS) - EffectManager::Get().RealtimeProcessMono(tempFloats, len); -#endif - - if (vt->GetChannel() == Track::LeftChannel || - vt->GetChannel() == Track::MonoChannel) + for (int c = 0; c < chanCnt; c++) { - float gain = vt->GetChannelGain(0); + vt = chans[c]; - // Output volume emulation: possibly copy meter samples, then - // apply volume, then copy to the output buffer - if (outputMeterFloats != outputFloats) - for (i = 0; i < len; ++i) - outputMeterFloats[numPlaybackChannels*i] += - gain*tempFloats[i]; + if (vt->GetChannel() == Track::LeftChannel || + vt->GetChannel() == Track::MonoChannel) + { + float gain = vt->GetChannelGain(0); - if (gAudioIO->mEmulateMixerOutputVol) - gain *= gAudioIO->mMixerOutputVol; + // Output volume emulation: possibly copy meter samples, then + // apply volume, then copy to the output buffer + if (outputMeterFloats != outputFloats) + for (int i = 0; i < len; ++i) + outputMeterFloats[numPlaybackChannels*i] += + gain*tempFloats[i]; - for(i=0; imEmulateMixerOutputVol) + gain *= gAudioIO->mMixerOutputVol; + + for(int i=0; iGetChannel() == Track::RightChannel || + vt->GetChannel() == Track::MonoChannel) + { + float gain = vt->GetChannelGain(1); + + // Output volume emulation (as above) + if (outputMeterFloats != outputFloats) + for (int i = 0; i < len; ++i) + outputMeterFloats[numPlaybackChannels*i+1] += + gain*tempFloats[i]; + + if (gAudioIO->mEmulateMixerOutputVol) + gain *= gAudioIO->mMixerOutputVol; + + for(int i=0; iGetChannel() == Track::RightChannel || - vt->GetChannel() == Track::MonoChannel) - { - float gain = vt->GetChannelGain(1); - - // Output volume emulation (as above) - if (outputMeterFloats != outputFloats) - for (i = 0; i < len; ++i) - outputMeterFloats[numPlaybackChannels*i+1] += - gain*tempFloats[i]; - - if (gAudioIO->mEmulateMixerOutputVol) - gain *= gAudioIO->mMixerOutputVol; - - for(i=0; iRealtimeInitialize(); + } +#endif + + return false; +} +bool Effect::RealtimeAddProcessor(int numChannels, float sampleRate) { #if defined(EXPERIMENTAL_REALTIME_EFFECTS) if (mClient) { mNumChannels = numChannels; - return mClient->RealtimeInitialize(numChannels, sampleRate); + return mClient->RealtimeAddProcessor(numChannels, sampleRate); } #endif @@ -1335,7 +1347,7 @@ bool Effect::RealtimeResume() return false; } -sampleCount Effect::RealtimeProcess(float **inbuf, float **outbuf, sampleCount size) +sampleCount Effect::RealtimeProcess(int index, float **inbuf, float **outbuf, sampleCount size) { #if defined(EXPERIMENTAL_REALTIME_EFFECTS) float **tin = inbuf; @@ -1381,7 +1393,7 @@ sampleCount Effect::RealtimeProcess(float **inbuf, float **outbuf, sampleCount s // If the number of channels is greater than the number of in/out the effect // can handle, then call RealtimeProcess for each "set" of tracks over its // capabilities. - return mClient->RealtimeProcess(tin, tout, size); + return mClient->RealtimeProcess(index, tin, tout, size); #else return 0; #endif diff --git a/src/effects/Effect.h b/src/effects/Effect.h index f2f934623..8edf0b066 100644 --- a/src/effects/Effect.h +++ b/src/effects/Effect.h @@ -240,11 +240,12 @@ class AUDACITY_DLL_API Effect : public EffectHostInterface static wxString StripAmpersand(const wxString& str); // Realtime Effect Processing - bool RealtimeInitialize(int numChannels, float sampleRate); + bool RealtimeInitialize(); + bool RealtimeAddProcessor(int numChannels, float sampleRate); bool RealtimeFinalize(); bool RealtimeSuspend(); bool RealtimeResume(); - sampleCount RealtimeProcess(float **inbuf, float **outbuf, sampleCount size); + sampleCount RealtimeProcess(int index, float **inbuf, float **outbuf, sampleCount size); // // protected virtual methods diff --git a/src/effects/EffectManager.cpp b/src/effects/EffectManager.cpp index ff6eb6a59..12b188bbc 100644 --- a/src/effects/EffectManager.cpp +++ b/src/effects/EffectManager.cpp @@ -392,17 +392,33 @@ void EffectManager::ShowRack() #endif #if defined(EXPERIMENTAL_REALTIME_EFFECTS) -void EffectManager::RealtimeInitialize(int numChannels, float sampleRate) +void EffectManager::RealtimeInitialize(const WaveTrackArray *tracks) { mRealtimeMutex.Lock(); - for (int i = 0; i < mRealtimeCount; i++) + mRealtimeTracks = tracks; + for (int e = 0; e < mRealtimeCount; e++) { - mRealtimeEffects[i]->RealtimeInitialize(numChannels, sampleRate); + mRealtimeEffects[e]->RealtimeInitialize(); + + for (size_t i = 0, cnt = tracks->GetCount(); i < cnt; i++) + { + WaveTrack *t = (*tracks)[i]; + if (t->GetLinked()) + { + mRealtimeEffects[e]->RealtimeAddProcessor(2, t->GetRate()); + i++; + } + else + { + mRealtimeEffects[e]->RealtimeAddProcessor(1, t->GetRate()); + } + } } - mRealtimeMutex.Unlock(); mRealtimeActive = true; + mRealtimeMutex.Unlock(); + RealtimeResume(); } @@ -412,6 +428,7 @@ void EffectManager::RealtimeFinalize() mRealtimeActive = false; mRealtimeLatency = 0; + mRealtimeTracks = NULL; mRealtimeMutex.Lock(); for (int i = 0; i < mRealtimeCount; i++) @@ -445,72 +462,27 @@ void EffectManager::RealtimeResume() mRealtimeSuspended = false; } -void EffectManager::RealtimeProcessMono(float *buffer, sampleCount numSamples) +sampleCount EffectManager::RealtimeProcess(int index, float **buffers, sampleCount numSamples) { // Can be suspended because of the audio stream being paused or because effects // have been suspended. if (mRealtimeSuspended) { - return; - } - - // We only ever have a single input channel - - wxMilliClock_t start = wxGetLocalTimeMillis(); - - float *ib = (float *) alloca(sizeof(float) * numSamples); - float *ob = (float *) alloca(sizeof(float) * numSamples); - - memcpy(ib, buffer, sizeof(float) * numSamples); - - float *ibuf = ib; - float *obuf = ob; - - mRealtimeMutex.Lock(); - for (int i = 0; i < mRealtimeCount; i++) - { - mRealtimeEffects[i]->RealtimeProcess(&ibuf, &obuf, numSamples); - - float *tbuf = ibuf; - ibuf = obuf; - obuf = tbuf; - } - mRealtimeMutex.Unlock(); - - memcpy(buffer, ibuf, sizeof(float) * numSamples); - - mRealtimeLatency = (int) (wxGetLocalTimeMillis() - start).GetValue(); -} - -void EffectManager::RealtimeProcessStereo(float *buffer, sampleCount numSamples) -{ - // Can be suspended because of the audio stream being paused or because effects - // have been suspended. - if (mRealtimeSuspended) - { - return; + return 0; } wxMilliClock_t start = wxGetLocalTimeMillis(); - float *ilc = (float *) alloca(sizeof(float) * numSamples); - float *irc = (float *) alloca(sizeof(float) * numSamples); float *olc = (float *) alloca(sizeof(float) * numSamples); float *orc = (float *) alloca(sizeof(float) * numSamples); - for (int opos = 0, ipos = 0; opos < numSamples; opos++, ipos += 2) - { - ilc[opos] = buffer[ipos]; - irc[opos] = buffer[ipos+1]; - } - - float *ibuf[2] = {ilc, irc}; + float *ibuf[2] = {buffers[0], buffers[1]}; float *obuf[2] = {olc, orc}; mRealtimeMutex.Lock(); for (int i = 0; i < mRealtimeCount; i++) { - mRealtimeEffects[i]->RealtimeProcess(ibuf, obuf, numSamples); + mRealtimeEffects[i]->RealtimeProcess(index, ibuf, obuf, numSamples); float *tbuf[2] = {ibuf[0], ibuf[1]}; ibuf[0] = obuf[0]; @@ -520,13 +492,18 @@ void EffectManager::RealtimeProcessStereo(float *buffer, sampleCount numSamples) } mRealtimeMutex.Unlock(); - for (int opos = 0, ipos = 0; ipos < numSamples; ipos++, opos += 2) + if (obuf[0] == buffers[0]) { - buffer[opos] = ibuf[0][ipos] > 1.0 ? 1.0 : ibuf[0][ipos]; - buffer[opos+1] = ibuf[1][ipos] > 1.0 ? 1.0 : ibuf[1][ipos]; + memcpy(buffers[1], ibuf[1], sizeof(float) * numSamples); + memcpy(buffers[0], ibuf[0], sizeof(float) * numSamples); } mRealtimeLatency = (int) (wxGetLocalTimeMillis() - start).GetValue(); + + // + // This is wrong...needs to handle tails + // + return numSamples; } int EffectManager::GetRealtimeLatency() @@ -548,11 +525,21 @@ void EffectManager::SetRealtime(const EffectArray & effects) Effect **rtold = mRealtimeEffects; mRealtimeEffects = rteffects; mRealtimeCount = effects.GetCount(); + + if (mRealtimeActive) + { + const WaveTrackArray *tracks = mRealtimeTracks; + RealtimeFinalize(); + RealtimeInitialize(tracks); + } + mRealtimeMutex.Unlock(); + if (rtold) { delete [] rtold; } + } } #endif diff --git a/src/effects/EffectManager.h b/src/effects/EffectManager.h index da7cf0b88..dd43e19fc 100644 --- a/src/effects/EffectManager.h +++ b/src/effects/EffectManager.h @@ -94,12 +94,11 @@ class AUDACITY_DLL_API EffectManager #if defined(EXPERIMENTAL_REALTIME_EFFECTS) // Realtime effect processing - void RealtimeInitialize(int numChannels, float sampleRate); + void RealtimeInitialize(const WaveTrackArray *tracks); void RealtimeFinalize(); void RealtimeSuspend(); void RealtimeResume(); - void RealtimeProcessMono(float *buffer, sampleCount numSamples); - void RealtimeProcessStereo(float *buffer, sampleCount numSamples); + sampleCount RealtimeProcess(int index, float **buffers, sampleCount numSamples); void SetRealtime(const EffectArray & mActive); int GetRealtimeLatency(); #endif @@ -164,6 +163,11 @@ private: int mRealtimeLatency; bool mRealtimeActive; bool mRealtimeSuspended; + const WaveTrackArray *mRealtimeTracks; + + float **inbuffers; + float **outbuffers; + #endif #ifdef EFFECT_CATEGORIES diff --git a/src/effects/VST/VSTEffect.cpp b/src/effects/VST/VSTEffect.cpp index 158a603f7..98d1edbfb 100644 --- a/src/effects/VST/VSTEffect.cpp +++ b/src/effects/VST/VSTEffect.cpp @@ -3068,12 +3068,19 @@ intptr_t VSTEffect::AudioMaster(AEffect * effect, return 0; } +#if defined(EXPERIMENTAL_REALTIME_EFFECTS) + case audioMasterAutomate: + if (vst) + vst->Automate(index, opt); + return 0; + +#else // These are not needed since we don't need the parameter values until after the editor // has already been closed. If we did realtime effects, then we'd need these. case audioMasterBeginEdit: case audioMasterEndEdit: case audioMasterAutomate: - +#endif // We're always connected (sort of) case audioMasterPinConnected: @@ -3098,10 +3105,11 @@ intptr_t VSTEffect::AudioMaster(AEffect * effect, return 0; } -VSTEffect::VSTEffect(const wxString & path) -: mHost(NULL), - mPath(path) +VSTEffect::VSTEffect(const wxString & path, VSTEffect *master) +: mPath(path), + mMaster(master) { + mHost = NULL; mModule = NULL; mAEffect = NULL; mDlg = NULL; @@ -3132,6 +3140,12 @@ VSTEffect::VSTEffect(const wxString & path) mTimeInfo.timeSigNumerator = 4; mTimeInfo.timeSigDenominator = 4; mTimeInfo.flags = kVstTempoValid | kVstNanosValid; + + // If we're a slave then go ahead a load immediately + if (mMaster) + { + Load(); + } } VSTEffect::~VSTEffect() @@ -3389,15 +3403,33 @@ sampleCount VSTEffect::ProcessBlock(float **inbuf, float **outbuf, sampleCount s return size; } -bool VSTEffect::RealtimeInitialize(int numChannels, float sampleRate) +bool VSTEffect::RealtimeInitialize() { - SetSampleRate(sampleRate); + // This is really just a dummy value and one to make the dialog happy since + // all processing is handled by slaves. + SetSampleRate(44100); + + return ProcessInitialize(); +} + +bool VSTEffect::RealtimeAddProcessor(int numChannels, float sampleRate) +{ + VSTEffect *slave = new VSTEffect(mPath, this); + mSlaves.Add(slave); + + slave->SetSampleRate(sampleRate); return ProcessInitialize(); } bool VSTEffect::RealtimeFinalize() { + for (size_t i = 0, cnt = mSlaves.GetCount(); i < cnt; i++) + { + delete mSlaves[i]; + } + mSlaves.Clear(); + return ProcessFinalize(); } @@ -3415,9 +3447,14 @@ bool VSTEffect::RealtimeResume() return true; } -sampleCount VSTEffect::RealtimeProcess(float **inbuf, float **outbuf, sampleCount size) +sampleCount VSTEffect::RealtimeProcess(int index, float **inbuf, float **outbuf, sampleCount size) { - return ProcessBlock(inbuf, outbuf, size); + if (index < 0 || index >= mSlaves.GetCount()) + { + return 0; + } + + return mSlaves[index]->ProcessBlock(inbuf, outbuf, size); } // @@ -3971,6 +4008,22 @@ void VSTEffect::UpdateDisplay() return; } +void VSTEffect::Automate(int index, float value) +{ + // Just ignore it if we're a slave + if (mMaster) + { + return; + } + + for (size_t i = 0, cnt = mSlaves.GetCount(); i < cnt; i++) + { + mSlaves[i]->callSetParameter(index, value); + } + + return; +} + void VSTEffect::SetBufferDelay(int samples) { // We do not support negative delay @@ -4026,16 +4079,31 @@ void VSTEffect::callProcessReplacing(float **inputs, mAEffect->processReplacing(mAEffect, inputs, outputs, sampleframes); } -void VSTEffect::callSetParameter(int index, float parameter) -{ - mAEffect->setParameter(mAEffect, index, parameter); -} - float VSTEffect::callGetParameter(int index) { return mAEffect->getParameter(mAEffect, index); } +void VSTEffect::callSetParameter(int index, float value) +{ + mAEffect->setParameter(mAEffect, index, value); + + for (size_t i = 0, cnt = mSlaves.GetCount(); i < cnt; i++) + { + mSlaves[i]->callSetParameter(index, value); + } +} + +void VSTEffect::callSetProgram(int index) +{ + callDispatcher(effSetProgram, 0, index, NULL, 0.0); + + for (size_t i = 0, cnt = mSlaves.GetCount(); i < cnt; i++) + { + mSlaves[i]->callSetProgram(index); + } +} + //////////////////////////////////////////////////////////////////////////////// // Base64 en/decoding // diff --git a/src/effects/VST/VSTEffect.h b/src/effects/VST/VSTEffect.h index 4657deb19..56dafd572 100644 --- a/src/effects/VST/VSTEffect.h +++ b/src/effects/VST/VSTEffect.h @@ -14,6 +14,8 @@ #include "audacity/ModuleInterface.h" #include "audacity/PluginInterface.h" +#include + #include "aeffectx.h" #define VSTCMDKEY L"-checkvst" @@ -44,11 +46,14 @@ typedef AEffect *(*vstPluginMain)(audioMasterCallback audioMaster); class VSTEffectTimer; class VSTEffectDialog; +class VSTEffect; + +WX_DEFINE_ARRAY_PTR(VSTEffect *, VSTEffectArray); class VSTEffect : public EffectClientInterface { public: - VSTEffect(const wxString & path); + VSTEffect(const wxString & path, VSTEffect *master = NULL); virtual ~VSTEffect(); // IdentInterface implementation @@ -89,11 +94,12 @@ class VSTEffect : public EffectClientInterface virtual bool ProcessFinalize(); virtual sampleCount ProcessBlock(float **inbuf, float **outbuf, sampleCount size); - virtual bool RealtimeInitialize(int numChannels, float sampleRate); + virtual bool RealtimeInitialize(); + virtual bool RealtimeAddProcessor(int numChannels, float sampleRate); virtual bool RealtimeFinalize(); virtual bool RealtimeSuspend(); virtual bool RealtimeResume(); - virtual sampleCount RealtimeProcess(float **inbuf, float **outbuf, sampleCount size); + virtual sampleCount RealtimeProcess(int index, float **inbuf, float **outbuf, sampleCount size); virtual bool ShowInterface(void *parent); @@ -122,6 +128,10 @@ private: static wxString b64encode(const void *in, int len); static int b64decode(wxString in, void *out); + // Realtime + bool IsSlave(); + + // Utility methods VstTimeInfo *GetTimeInfo(); @@ -130,8 +140,9 @@ private: void SetBufferDelay(int samples); void NeedIdle(); void NeedEditIdle(bool state); - void UpdateDisplay(); void SizeWindow(int w, int h); + void UpdateDisplay(); + void Automate(int index, float value); void PowerOn(); void PowerOff(); void InterfaceClosed(); @@ -144,8 +155,9 @@ private: intptr_t callDispatcher(int opcode, int index, intptr_t value, void *ptr, float opt); void callProcessReplacing(float **inputs, float **outputs, int sampleframes); - void callSetParameter(int index, float parameter); + void callSetParameter(int index, float value); float callGetParameter(int index); + void callSetProgram(int index); private: EffectHostInterface *mHost; @@ -191,6 +203,10 @@ private: VSTEffectTimer *mTimer; int mTimerGuard; + // Realtime processing + VSTEffect *mMaster; // non-NULL if a slave + VSTEffectArray mSlaves; + friend class VSTEffectDialog; friend class VSTEffectsModule; };