From d50e9fee52132d1555b6a07f7bc2995722455896 Mon Sep 17 00:00:00 2001 From: lllucius Date: Thu, 30 Oct 2014 14:04:48 +0000 Subject: [PATCH] Realtime preview round 2 This changes the realtime preview from using 1 effect for all tracks to an effect per track (logical track). This should clear up the bad audio when more than one track (or a stereo track) is present. An unfortunate side effect is that meter effects no longer work since the one presented to the user is not the one doing the actual work. Suggestions on how to remedy this are welcome. --- include/audacity/EffectInterface.h | 5 +- src/AudioIO.cpp | 126 +++++++++++++++++------------ src/effects/Effect.cpp | 20 ++++- src/effects/Effect.h | 5 +- src/effects/EffectManager.cpp | 99 ++++++++++------------- src/effects/EffectManager.h | 10 ++- src/effects/VST/VSTEffect.cpp | 94 ++++++++++++++++++--- src/effects/VST/VSTEffect.h | 26 ++++-- 8 files changed, 249 insertions(+), 136 deletions(-) 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; };