diff --git a/src/effects/VST/VSTEffect.cpp b/src/effects/VST/VSTEffect.cpp index cc6c8f918..6ef03b7e0 100644 --- a/src/effects/VST/VSTEffect.cpp +++ b/src/effects/VST/VSTEffect.cpp @@ -14,6 +14,10 @@ **********************************************************************/ +// ******************************************************************* +// WARNING: This is NOT 64-bit safe +// ******************************************************************* + #include "../../Audacity.h" #if USE_VST @@ -30,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -59,8 +64,9 @@ #include "../../Prefs.h" #include "../../xml/XMLFileReader.h" #include "../../xml/XMLWriter.h" -#include "../EffectManager.h" #include "../../Theme.h" +#include "../../widgets/valnum.h" +#include "../EffectManager.h" #include "../images/Arrow.xpm" #include "VSTEffect.h" @@ -679,6 +685,7 @@ void PluginRegistrationDialog::ToggleItem(int i) void PluginRegistrationDialog::OnApply(wxCommandEvent & WXUNUSED(event)) { mCancelClicked = false; + Disable(); size_t cnt = mFiles.GetCount(); for (size_t i = 0; i < cnt && !mCancelClicked; i++) { @@ -704,9 +711,126 @@ void PluginRegistrationDialog::OnCancel(wxCommandEvent & WXUNUSED(event)) EndModal(mCancelClicked ? wxID_CANCEL : wxID_OK); } +/////////////////////////////////////////////////////////////////////////////// +// +// VSTEffectSettingsDialog +// +/////////////////////////////////////////////////////////////////////////////// +class VSTEffectSettingsDialog:public wxDialog +{ + public: + VSTEffectSettingsDialog(wxWindow * parent); + virtual ~VSTEffectSettingsDialog(); + void PopulateOrExchange(ShuttleGui & S); + bool Validate(); + private: + int mBufferSize; +}; + +VSTEffectSettingsDialog::VSTEffectSettingsDialog(wxWindow * parent) +: wxDialog(parent, wxID_ANY, wxString(_("VST Effect Settings"))) +{ + gPrefs->Read(wxT("/VST/BufferSize"), &mBufferSize, 8192); + + ShuttleGui S(this, eIsCreatingFromPrefs); + PopulateOrExchange(S); +} + +VSTEffectSettingsDialog::~VSTEffectSettingsDialog() +{ +} + +void VSTEffectSettingsDialog::PopulateOrExchange(ShuttleGui & S) +{ + S.SetBorder(5); + S.StartHorizontalLay(wxEXPAND, 1); + { + S.StartVerticalLay(false); + { + S.StartStatic(_("Buffer Specification")); + { + wxIntegerValidator vld(&mBufferSize); + vld.SetRange(8, 1048576); + + S.AddVariableText(wxString() + + _("The buffer size controls the number of samples sent to the effect ") + + _("on each iteration. Smaller values will cause slower processing and ") + + _("some effects require 8192 samples or less to work properly. However ") + + _("most effects can accept large buffers and using them will greatly ") + + _("reduce processing time."))->Wrap(650); + + S.StartHorizontalLay(wxALIGN_LEFT); + { + wxTextCtrl *t; + t = S.TieNumericTextBox(_("Buffer Size (8 to 1048576 samples)"), + wxT("/VST/BufferSize"), + wxT(""), 12); + t->SetMinSize(wxSize(100, -1)); + t->SetValidator(vld); + } + S.EndHorizontalLay(); + } + S.EndStatic(); + + S.StartStatic(_("Buffer Delay Compensation")); + { + S.AddVariableText(wxString() + + _("As part of their processing, some VST effects must delay returning ") + + _("audio to Audacity. When not compensating for this delay, you will ") + + _("notice that bits of silence have been inserted into the audio. ") + + _("Enabling this setting will provide that compensation, but it may ") + + _("not work for all VST effects."))->Wrap(650); + + S.StartHorizontalLay(wxALIGN_LEFT); + { + S.TieCheckBox(_("Enable compensation"), wxT("/VST/UseBufferDelay"), true); + } + S.EndHorizontalLay(); + } + S.EndStatic(); + + S.StartStatic(_("Presentation Method")); + { + S.AddVariableText(wxString() + + _("Most VST effects provide a graphical interface for setting the ") + + _("parameter values. However, a basic text only method is also ") + + _("available."))->Wrap(650); + S.TieCheckBox(_("Enable graphical interface"), wxT("/VST/GUI"), true); + } + S.EndStatic(); + + S.StartStatic(_("Effect Refresh")); + { + S.AddVariableText(wxString() + + _("To improve Audacity startup, a search for VST effects is performed ") + + _("once and relevent information is recorded. When you add VST effects ") + + _("to your system, you need to tell Audacity to rescan so the new ") + + _("information can be recorded."))->Wrap(650); + S.TieCheckBox(_("Rescan effects on next launch"), wxT("/VST/Rescan"), false); + } + S.EndStatic(); + } + S.EndVerticalLay(); + } + S.EndHorizontalLay(); + + S.AddStandardButtons(); + + Layout(); + Fit(); + Center(); +} + +bool VSTEffectSettingsDialog::Validate() +{ + ShuttleGui S(this, eIsSavingToPrefs); + PopulateOrExchange(S); + + return true; +} /////////////////////////////////////////////////////////////////////////////// // @@ -731,6 +855,7 @@ class VSTEffectDialog:public wxDialog, XMLTagHandler void OnProgramText(wxCommandEvent & evt); void OnLoad(wxCommandEvent & evt); void OnSave(wxCommandEvent & evt); + void OnSettings(wxCommandEvent & evt); void OnSlider(wxCommandEvent &event); @@ -780,7 +905,8 @@ enum ID_VST_PROGRAM = 11000, ID_VST_LOAD, ID_VST_SAVE, - ID_VST_SLIDERS + ID_VST_SLIDERS, + ID_VST_SETTINGS }; BEGIN_EVENT_TABLE(VSTEffectDialog, wxDialog) @@ -793,6 +919,7 @@ BEGIN_EVENT_TABLE(VSTEffectDialog, wxDialog) EVT_TEXT(ID_VST_PROGRAM, VSTEffectDialog::OnProgramText) EVT_BUTTON(ID_VST_LOAD, VSTEffectDialog::OnLoad) EVT_BUTTON(ID_VST_SAVE, VSTEffectDialog::OnSave) + EVT_BUTTON(ID_VST_SETTINGS, VSTEffectDialog::OnSettings) EVT_SLIDER(wxID_ANY, VSTEffectDialog::OnSlider) END_EVENT_TABLE() @@ -926,7 +1053,7 @@ void VSTEffectDialog::BuildFancy() wxBoxSizer *hs = new wxBoxSizer(wxHORIZONTAL); wxSizerItem *si; - vs->Add(BuildProgramBar(), 0, wxCENTER); + vs->Add(BuildProgramBar(), 0, wxCENTER | wxEXPAND); si = hs->Add(rect->right - rect->left, rect->bottom - rect->top); vs->Add(hs, 0, wxCENTER); @@ -971,7 +1098,7 @@ void VSTEffectDialog::BuildPlain() mLabels = new wxStaticText *[mAEffect->numParams]; wxBoxSizer *vSizer = new wxBoxSizer(wxVERTICAL); - vSizer->Add(BuildProgramBar(), 0, wxALIGN_CENTER); + vSizer->Add(BuildProgramBar(), 0, wxALIGN_CENTER | wxEXPAND); wxScrolledWindow *sw = new wxScrolledWindow(this, wxID_ANY, @@ -1101,6 +1228,11 @@ wxSizer *VSTEffectDialog::BuildProgramBar() bt = new wxButton(this, ID_VST_SAVE, _("Save")); hs->Add(bt, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + hs->AddStretchSpacer(); + + bt = new wxButton(this, ID_VST_SETTINGS, _("Settings")); + hs->Add(bt, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, 5); + return hs; } @@ -1298,6 +1430,12 @@ void VSTEffectDialog::OnSave(wxCommandEvent & WXUNUSED(event)) xmlFile.Close(); } +void VSTEffectDialog::OnSettings(wxCommandEvent & WXUNUSED(event)) +{ + VSTEffectSettingsDialog dlg(this); + dlg.ShowModal(); +} + void VSTEffectDialog::OnClose(wxCloseEvent & WXUNUSED(event)) { EndModal(false); @@ -1663,10 +1801,10 @@ int VSTEffectDialog::b64decode(wxString in, void *out) typedef AEffect *(*vstPluginMain)(audioMasterCallback audioMaster); -static long int audioMaster(AEffect * effect, - long int opcode, - long int index, - long int value, +static intptr_t audioMaster(AEffect * effect, + int32_t opcode, + int32_t index, + intptr_t value, void * ptr, float opt) { @@ -1712,6 +1850,14 @@ static long int audioMaster(AEffect * effect, } break; + // Inputs, outputs, or initial delay has changed...all we care about is initial delay. + case audioMasterIOChanged: + if (vst) { + vst->SetBufferDelay(effect->initialDelay); + return 0; + } + break; + // Ignore these case audioMasterBeginEdit: case audioMasterEndEdit: @@ -1722,6 +1868,7 @@ static long int audioMaster(AEffect * effect, return 0; case audioMasterCanDo: + wxLogDebug(wxT("VST canDo: %s\n"), wxString::FromAscii((char *)ptr).c_str()); return 0; } @@ -1872,6 +2019,32 @@ bool VSTEffect::Init() return true; } +// +// Some history... +// +// Before we ran into the Antress plugin problem with buffer size limitations, +// (see below) we just had a plain old effect loop...get the input samples, pass +// them to the effect, save the output samples. +// +// But, the hack I put in to limit the buffer size to only 8k (normally 512k or so) +// severely impacted performance. So, Michael C. added some intermediate buffering +// that sped things up quite a bit and this is how things have worked for quite a +// while. It still didn't get the performance back to the pre-hack stage, but it +// was a definite benefit. +// +// History over... +// +// I've recently (May 2014) tried newer versions of the Antress effects and they +// no longer seem to have a problem with buffer size. So, I've made a bit of a +// compromise...I've made the buffer size user configurable. Should have done this +// from the beginning. I've left the default 8k, just in case, but now the user +// can set the buffering based on their specific setup and needs. +// +// And at the same time I added buffer delay compensation, which allows Audacity +// to account for latency introduced by some effects. This is based on information +// provided by the effect, so it will not work with all effects since they don't +// allow provide the information (kn0ck0ut is one). +// bool VSTEffect::PromptUser() { VSTEffectDialog dlog(mParent, mName, this, mAEffect); @@ -1889,6 +2062,21 @@ bool VSTEffect::Process() CopyInputTracks(); bool bGoodResult = true; + // Some VST effects (Antress Modern is an example), do not like + // overly large block sizes. Unfortunately, I have not found a + // way to determine if the effect has a maximum it will support, + // so just limit to small value for now. This will increase + // processing time and, it's a shame, because most plugins seem + // to be able to handle much larger sizes. + // + // NOTE: This no longer seems to apply to more recent versions + // of Antress plugins, but leaving comment and 8192 default + // just in case. + gPrefs->Read(wxT("/VST/BufferSize"), &mBufferSize, 8192); + + gPrefs->Read(wxT("/VST/UseBufferDelay"), &mUseBufferDelay, true); + mBufferDelay = 0; + mInBuffer = NULL; mOutBuffer = NULL; @@ -1918,14 +2106,10 @@ bool VSTEffect::Process() if (mBlockSize == 0) { mBlockSize = mWTBlockSize = left->GetMaxBlockSize() * 2; - // Some VST effects (Antress Modern is an example), do not like - // overly large block sizes. Unfortunately, I have not found a - // way to determine if the effect has a maximum it will support, - // so just limit to small value for now. This will increase - // processing time and, it's a shame, because most plugins seem - // to be able to handle much larger sizes. - if (mBlockSize > 8192) { // The Antress limit - mBlockSize = 8192; + // Limit the buffer size to the user specified value since they may + // have wanted a smaller value for a reason. + if (mBlockSize > mBufferSize) { + mBlockSize = mBufferSize; } mInBuffer = new float *[mInputs]; @@ -2006,7 +2190,35 @@ bool VSTEffect::ProcessStereo(int count, // Tell effect we're starting to process callDispatcher(effStartProcess, 0, 0, NULL, 0.0); - // Actually perform the effect here + // Get the initial latency + SetBufferDelay(mAEffect->initialDelay); + + // LLL: + // + // Some explanation to what this mess is all about. + // (see history above) + // + // For each input block of samples, we pass it to the VST effect along with a + // variable output location. This output location is simply a pointer into a + // much larger buffer. This reduces the number of calls required to add the + // samples to the output track which was Michael's speed up mentioned above. + // + // The buffer delay compensation adds even more complexitity... + // + // Upon return from the effect, the output samples are "moved to the left" by + // the number of samples in the current delay setting, effectively removing the + // delay introduced by the effect. + // + // At the same time the total number of delayed samples are gathered and when the + // there is no further input data to process, the loop continues to call the + // effect with an empty input buffer until the effect has had a chance to + // return all of the remaining delayed samples. + // + // Please note, that this process has next to no documetation on how it should + // work, so a lot of this was from trial and error. It appears to be correct + // though since it has worked with every plugin I've found that adds latency, + // with the exception of kn0ck0ut. I'm sure there are other effects out there + // that add latency but do not provide the delay information, so be wary. :-) sampleCount originalLen = len; sampleCount ls = lstart; sampleCount rs = rstart; @@ -2014,24 +2226,87 @@ bool VSTEffect::ProcessStereo(int count, sampleCount outrs = rstart; sampleCount outBufferCursor = 0; float **outBufSegment = new float *[mOutputs]; - while (len) { - int block = mBlockSize; - if (block > len) { - block = len; + sampleCount delay = 0; + sampleCount delayed = 0; + bool cleared = false; + + // Call the effect until we run out of input or delayed samples + while (len || delayed) { + sampleCount block = mBlockSize; + + // As long as we have input samples, use those + if (len) { + // At the end if we don't have enough left for a whole block + if (block > len) { + block = len; + } + + // Get the samples into our buffer + left->Get((samplePtr)mInBuffer[0], floatSample, ls, block); + if (right) { + right->Get((samplePtr)mInBuffer[1], floatSample, rs, block); + } + } + // We've reached the end of the input samples, so start processing + // delayed ones if there are any + else if (delayed) { + // At the end if we don't have enough left for a whole block + if (block > delayed) { + block = delayed; + } + + // Clear the input buffer so that we only pass zeros to the effect. + if (!cleared) { + for (int i = 1; i < mInputs; i++) { + for (int j = 0; j < mBlockSize; j++) { + mInBuffer[i][j] = 0.0; + } + } + cleared = true; + } } - left->Get((samplePtr)mInBuffer[0], floatSample, ls, block); - if (right) { - right->Get((samplePtr)mInBuffer[1], floatSample, rs, block); + // Set current output pointer + for (int i = 0; i < mOutputs; i++) { + outBufSegment[i] = mOutBuffer[i] + outBufferCursor; } - for (int i = 0; i < mOutputs; i++) - outBufSegment[i] = mOutBuffer[i ] + outBufferCursor; + // Go let the effect moleste the samples callProcessReplacing(mInBuffer, outBufSegment, block); - outBufferCursor += block; - //Process 2 audacity blockfiles per WaveTrack::Set independently of mBlockSize - //because it is extremely slow to do multiple Set()s per blockfile due to Undo History - //If we do more optimization we should probably align the Sets to blockfile boundries. + + // Get the current number of delayed samples and accumulate + delay += mBufferDelay; + delayed += mBufferDelay; + + // Reset...the effect will set this again if it has a further + // need to delay samples...some effects only set the value once + // at the start of processing. + mBufferDelay = 0; + + // If the effect has delayed the output by more samples than our + // current block size, then we leave the output pointers where they + // are. This will effectively remove those delayed samples from the + // output buffer. + if (delay >= block) { + delay -= block; + } + // We have some delayed samples, at the beginning of the output samples, + // so overlay them by shifting the remaining output samples. + else if (delay > 0) { + sampleCount oblock = block - delay; + memmove(outBufSegment[0], outBufSegment[0] + delay, SAMPLE_SIZE(floatSample) * oblock); + memmove(outBufSegment[1], outBufSegment[1] + delay, SAMPLE_SIZE(floatSample) * oblock); + delay = 0; + outBufferCursor += oblock; + } + // no delay, just bump to the new output location + else { + outBufferCursor += block; + } + + // Process 2 audacity blockfiles per WaveTrack::Set independently of mBlockSize + // because it is extremely slow to do multiple Set()s per blockfile due to Undo History + // If we do more optimization we should probably align the Sets to blockfile boundries. if (outBufferCursor >= mWTBlockSize) { left->Set((samplePtr)mOutBuffer[0], floatSample, outls, mWTBlockSize); if (right) { @@ -2047,7 +2322,19 @@ bool VSTEffect::ProcessStereo(int count, outrs += mWTBlockSize; } - len -= block; + // Still processing input samples + if (len) { + len -= block; + } + // Or maybe we're working on delayed samples + else if (delayed) { + delayed -= block; + } + + // "ls" and "rs" serve as the input sample index for the left and + // right channels when processing the input samples. If we flip + // over to processing delayed samples, the simply become counters + // for the progress display. ls += block; rs += block; mTimeInfo.samplePos += ((double) block / mTimeInfo.sampleRate); @@ -2066,7 +2353,7 @@ bool VSTEffect::ProcessStereo(int count, } } - //finish taking the remainder. + // Finish taking the remainder if (outBufferCursor) { left->Set((samplePtr)mOutBuffer[0], floatSample, outls, outBufferCursor); if (right) { @@ -2467,6 +2754,14 @@ VstTimeInfo *VSTEffect::GetTimeInfo() return &mTimeInfo; } +void VSTEffect::SetBufferDelay(int samples) +{ + // We do not support negative delay + if (samples >= 0 && mUseBufferDelay) { + mBufferDelay = samples; + } +} + wxString VSTEffect::GetString(int opcode, int index) { char buf[256]; diff --git a/src/effects/VST/VSTEffect.h b/src/effects/VST/VSTEffect.h index b890534ac..904070bea 100644 --- a/src/effects/VST/VSTEffect.h +++ b/src/effects/VST/VSTEffect.h @@ -72,12 +72,11 @@ class VSTEffect:public Effect static int ShowPluginListDialog( const wxArrayString & files ); static void ShowProgressDialog( const wxString & longest, const wxArrayString & files ); - - // Utility methods int GetChannels(); VstTimeInfo *GetTimeInfo(); + void SetBufferDelay(int samples); wxString GetString(int opcode, int index = 0); void SetString(int opcode, const wxString & str, int index = 0); @@ -113,6 +112,11 @@ class VSTEffect:public Effect VstTimeInfo mTimeInfo; + bool mUseBufferDelay; + int mBufferDelay; + + int mBufferSize; + sampleCount mBlockSize; sampleCount mWTBlockSize; float **mInBuffer; diff --git a/src/effects/VST/aeffectx.h b/src/effects/VST/aeffectx.h index d8e019b2a..c9fbbda3c 100644 --- a/src/effects/VST/aeffectx.h +++ b/src/effects/VST/aeffectx.h @@ -118,7 +118,7 @@ const int effGetVendorString = 47; const int effGetProductString = 48; const int effGetVendorVersion = 49; const int effCanDo = 51; // currently unused -// The next one was gleaned from http://asseca.com/vst-24-specs/efIdle.html +// The next one was gleaned from http://www.asseca.org/vst-24-specs/efIdle.html const int effIdle = 53; const int effGetVstVersion = 58; // currently unused // The next two were gleaned from http://www.kvraudio.com/forum/printview.php?t=143587&start=0 @@ -128,13 +128,22 @@ const int effStopProcess = 72; const int kEffectMagic = CCONST( 'V', 's', 't', 'P' ); const int kVstLangEnglish = 1; const int kVstMidiType = 1; -const int kVstParameterUsesFloatStep = 1 << 2; + const int kVstNanosValid = 1 << 8; +const int kVstPpqPosValid = 1 << 9; const int kVstTempoValid = 1 << 10; +const int kVstBarsValid = 1 << 11; +const int kVstCyclePosValid = 1 << 12; +const int kVstTimeSigValid = 1 << 13; +const int kVstSmpteValid = 1 << 14; // from Ardour +const int kVstClockValid = 1 << 15; // from Ardour + const int kVstTransportPlaying = 1 << 1; +const int kVstTransportCycleActive = 1 << 2; +const int kVstTransportChanged = 1; -class remoteVstPlugin; +class RemoteVstPlugin; class VstMidiEvent @@ -183,9 +192,9 @@ public: // 00 int numEvents; // 04 - int reserved; + void *reserved; // 08 - VstEvent * events; + VstEvent* events[1]; } ; @@ -196,7 +205,7 @@ public: class VstParameterProperties { public: - float stepFloat; +/* float stepFloat; char label[64]; int flags; int minInteger; @@ -205,7 +214,24 @@ public: char shortLabel[8]; int category; char categoryLabel[24]; - char empty[128]; + char empty[128];*/ + + float stepFloat; + float smallStepFloat; + float largeStepFloat; + char label[64]; + unsigned int flags; + unsigned int minInteger; + unsigned int maxInteger; + unsigned int stepInteger; + unsigned int largeStepInteger; + char shortLabel[8]; + unsigned short displayIndex; + unsigned short category; + unsigned short numParametersInCategory; + unsigned short reserved; + char categoryLabel[24]; + char future[16]; } ; @@ -219,7 +245,7 @@ public: // 00-03 int magic; // dispatcher 04-07 - int (* dispatcher)( AEffect * , int , int , int , void * , float ); + intptr_t (* dispatcher)( AEffect * , int , int , intptr_t, void * , float ); // process, quite sure 08-0b void (* process)( AEffect * , float * * , float * * , int ); // setParameter 0c-0f @@ -237,15 +263,18 @@ public: // flags 24-27 int flags; // Fill somewhere 28-2b - void * user; - // Zeroes 2c-2f 30-33 34-37 38-3b - char empty3[4 + 4 + 4 + 4]; + void * ptr1; + void * ptr2; + int initialDelay; + // Zeroes 34-37 38-3b + int empty3a; + int empty3b; // 1.0f 3c-3f float unkown_float; // An object? pointer 40-43 - char empty4[4]; + void *ptr3; // Zeroes 44-47 - char empty5[4]; + void *user; // Id 48-4b int32_t uniqueID; // Don't know 4c-4f @@ -256,6 +285,8 @@ public: } ; + + class VstTimeInfo { public: @@ -265,12 +296,16 @@ public: double sampleRate; // 10 double nanoSeconds; - // unconfirmed 18 - char empty1[8]; + // 18 + double ppqPos; // 20? double tempo; - // unconfirmed 28 30 38 - char empty2[8 + 8 + 8]; + // 28 + double barStartPos; + // 30? + double cycleStartPos; + // 38? + double cycleEndPos; // 40? int timeSigNumerator; // 44? @@ -284,13 +319,20 @@ public: - -typedef long int (* audioMasterCallback)( AEffect * , long int , long int , - long int , void * , float ); -// we don't use it, may be noise -#define VSTCALLBACK +typedef intptr_t (* audioMasterCallback)( AEffect * , int32_t, int32_t, intptr_t, void * , float ); +// from http://www.asseca.org/vst-24-specs/efGetParameterProperties.html +enum VstParameterFlags +{ + kVstParameterIsSwitch = 1 << 0, // parameter is a switch (on/off) + kVstParameterUsesIntegerMinMax = 1 << 1, // minInteger, maxInteger valid + kVstParameterUsesFloatStep = 1 << 2, // stepFloat, smallStepFloat, largeStepFloat valid + kVstParameterUsesIntStep = 1 << 3, // stepInteger, largeStepInteger valid + kVstParameterSupportsDisplayIndex = 1 << 4, // displayIndex valid + kVstParameterSupportsDisplayCategory = 1 << 5, // category, etc. valid + kVstParameterCanRamp = 1 << 6 // set if parameter value can ramp up/down +}; #endif