From 46055cde25ad8a59b7c086e617604623194f6d47 Mon Sep 17 00:00:00 2001 From: Steve Daulton Date: Sat, 2 Jan 2016 09:00:40 +0000 Subject: [PATCH] Fix effect preview issues Fixes bug 1274 and unblocks bug 995. --- src/effects/Effect.cpp | 11 +- src/effects/Effect.h | 3 + src/effects/Paulstretch.cpp | 143 +++++++++------ src/effects/Paulstretch.h | 6 +- src/effects/TruncSilence.cpp | 314 +++++++++++++++++++++----------- src/effects/TruncSilence.h | 16 +- src/effects/nyquist/Nyquist.cpp | 4 +- 7 files changed, 327 insertions(+), 170 deletions(-) diff --git a/src/effects/Effect.cpp b/src/effects/Effect.cpp index 744ce1777..da9a86d69 100644 --- a/src/effects/Effect.cpp +++ b/src/effects/Effect.cpp @@ -2431,11 +2431,6 @@ bool Effect::IsHidden() void Effect::Preview(bool dryOnly) { - if (mIsLinearEffect) - wxLogDebug(wxT("Linear Effect")); - else - wxLogDebug(wxT("Non-linear Effect")); - if (mNumTracks == 0) { // nothing to preview return; } @@ -2553,8 +2548,9 @@ void Effect::Preview(bool dryOnly) playbackTracks.Add(src); src = (WaveTrack *) iter.Next(); } - if (isNyquist && isGenerator) - t1 = mT1; + // Some effects (Paulstretch) may need to generate more + // than previewLen, so take the min. + t1 = std::min(mT0 + previewLen, mT1); #ifdef EXPERIMENTAL_MIDI_OUT NoteTrackArray empty; @@ -2569,7 +2565,6 @@ void Effect::Preview(bool dryOnly) if (token) { int previewing = eProgressSuccess; -wxLogDebug(wxT("mT0 %.3f t1 %.3f"),mT0,t1); // The progress dialog must be deleted before stopping the stream // to allow events to flow to the app during StopStream processing. // The progress dialog blocks these events. diff --git a/src/effects/Effect.h b/src/effects/Effect.h index 0b499d9fb..1b9cc3b90 100644 --- a/src/effects/Effect.h +++ b/src/effects/Effect.h @@ -334,6 +334,9 @@ protected: // (such as fade effects) need to know the full selection length. void SetPreviewFullSelectionFlag(bool previewDurationFlag); + // Use this if the effect needs to know if it is previewing + bool IsPreviewing() { return mIsPreview; } + // Most effects only require selected tracks to be copied for Preview. // If IncludeNotSelectedPreviewTracks(true), then non-linear effects have // preview copies of all wave tracks. diff --git a/src/effects/Paulstretch.cpp b/src/effects/Paulstretch.cpp index 76e21a042..4bd37ed2f 100644 --- a/src/effects/Paulstretch.cpp +++ b/src/effects/Paulstretch.cpp @@ -26,6 +26,7 @@ #include "../ShuttleGui.h" #include "../FFT.h" #include "../widgets/valnum.h" +#include "../Prefs.h" #include "../WaveTrack.h" @@ -79,8 +80,8 @@ END_EVENT_TABLE() EffectPaulstretch::EffectPaulstretch() { - amount = DEF_Amount; - time_resolution = DEF_Time; + mAmount = DEF_Amount; + mTime_resolution = DEF_Time; SetLinearEffectFlag(true); } @@ -112,8 +113,8 @@ EffectType EffectPaulstretch::GetType() bool EffectPaulstretch::GetAutomationParameters(EffectAutomationParameters & parms) { - parms.WriteFloat(KEY_Amount, amount); - parms.WriteFloat(KEY_Time, time_resolution); + parms.WriteFloat(KEY_Amount, mAmount); + parms.WriteFloat(KEY_Time, mTime_resolution); return true; } @@ -123,14 +124,27 @@ bool EffectPaulstretch::SetAutomationParameters(EffectAutomationParameters & par ReadAndVerifyFloat(Amount); ReadAndVerifyFloat(Time); - amount = Amount; - time_resolution = Time; + mAmount = Amount; + mTime_resolution = Time; return true; } // Effect implementation +double EffectPaulstretch::CalcPreviewInputLength(double previewLength) +{ + // FIXME: Preview is currently at the project rate, but should really be + // at the track rate (bugs 1284 and 852). + int minDuration = GetBufferSize(mProjectRate) * 2 + 1; + + // Preview playback may need to be trimmed but this is the smallest selection that we can use. + double minLength = std::max(minDuration / mProjectRate, previewLength / mAmount); + + return minLength; +} + + bool EffectPaulstretch::Process() { CopyInputTracks(); @@ -159,11 +173,12 @@ bool EffectPaulstretch::Process() return true; } + void EffectPaulstretch::PopulateOrExchange(ShuttleGui & S) { S.StartMultiColumn(2, wxALIGN_CENTER); { - FloatingPointValidator vldAmount(1, &amount); + FloatingPointValidator vldAmount(1, &mAmount); vldAmount.SetMin(MIN_Amount); /* i18n-hint: This is how many times longer the sound will be, e.g. applying @@ -172,7 +187,7 @@ void EffectPaulstretch::PopulateOrExchange(ShuttleGui & S) */ S.AddTextBox(_("Stretch Factor:"), wxT(""), 10)->SetValidator(vldAmount); - FloatingPointValidator vldTime(3, &time_resolution, NUM_VAL_ONE_TRAILING_ZERO); + FloatingPointValidator vldTime(3, &mTime_resolution, NUM_VAL_ONE_TRAILING_ZERO); vldTime.SetMin(MIN_Time); S.AddTextBox(_("Time Resolution (seconds):"), wxT(""), 10)->SetValidator(vldTime); } @@ -206,33 +221,66 @@ void EffectPaulstretch::OnText(wxCommandEvent & WXUNUSED(evt)) EnableApply(mUIParent->TransferDataFromWindow()); } +int EffectPaulstretch::GetBufferSize(double rate) +{ + // Audacity's fft requires a power of 2 + float tmp = rate * mTime_resolution / 2.0; + tmp = log(tmp) / log(2.0); + tmp = pow(2.0, floor(tmp + 0.5)); + + return std::max((int)tmp, 128); +} + bool EffectPaulstretch::ProcessOne(WaveTrack *track,double t0,double t1,int count) { - int stretch_buf_size;//must be power of 2 (because Audacity's fft requires it) - if (time_resolutionGetRate()*time_resolution*0.5; - tmp=log(tmp)/log(2.0); - tmp=pow(2.0,floor(tmp+0.5)); - stretch_buf_size=(int)tmp; - }; - if (stretch_buf_size<128) stretch_buf_size=128; - double amount=this->amount; - if (amountGetRate()); + double amount = this->mAmount; sampleCount start = track->TimeToLongSamples(t0); sampleCount end = track->TimeToLongSamples(t1); sampleCount len = (sampleCount)(end - start); - m_t1=mT1; + int minDuration = stretch_buf_size * 2 + 1; + if (len < minDuration){ //error because the selection is too short + + float maxTimeRes = log(len) / log(2.0); + maxTimeRes = pow(2.0, floor(maxTimeRes) + 0.5); + maxTimeRes = maxTimeRes / track->GetRate(); + + if (this->IsPreviewing()) { + double defaultPreviewLen; + gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &defaultPreviewLen, 6.0); + + /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/ + if ((minDuration / mProjectRate) < defaultPreviewLen) { + ::wxMessageBox (wxString::Format(_("Audio selection too short to preview.\n\n" + "Try increasing the audio selection to at least %.1f seconds,\n" + "or reducing the 'Time Resolution' to less than %.1f seconds."), + (minDuration / track->GetRate()) + 0.05, // round up to 1/10 s. + floor(maxTimeRes * 10.0) / 10.0), + GetName(), wxOK | wxICON_EXCLAMATION); + } + else { + /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/ + ::wxMessageBox (wxString::Format(_("Unable to Preview.\n\n" + "For the current audio selection, the maximum\n" + "'Time Resolution' is %.1f seconds."), + floor(maxTimeRes * 10.0) / 10.0), + GetName(), wxOK | wxICON_EXCLAMATION); + } + } + else { + /* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/ + ::wxMessageBox (wxString::Format(_("The 'Time Resolution' is too long for the selection.\n\n" + "Try increasing the audio selection to at least %.1f seconds,\n" + "or reducing the 'Time Resolution' to less than %.1f seconds."), + (minDuration / track->GetRate()) + 0.05, // round up to 1/10 s. + floor(maxTimeRes * 10.0) / 10.0), + GetName(), wxOK | wxICON_EXCLAMATION); + } - if (len<=(stretch_buf_size*2+1)){//error because the selection is too short - /* i18n-hint: This is an effect error message, for the effect named Paulstretch. - * Time Resolution is a parameter of the effect, the translation should match - */ - ::wxMessageBox(_("Error in Paulstretch:\nThe selection is too short.\n It must be much longer than the Time Resolution.")); return false; - }; + } double adjust_amount=(double)len/((double)len-((double)stretch_buf_size*2.0)); @@ -296,15 +344,11 @@ bool EffectPaulstretch::ProcessOne(WaveTrack *track,double t0,double t1,int coun delete [] fade_track_smps; outputTrack->Flush(); - - track->Clear(t0,t1); - track->Paste(t0,outputTrack); - if (!cancelled){ - double flen=t1-t0; - if (s>0) m_t1=t0+flen*(double)outs/(double)(s); - }; - + bool success = track->Paste(t0,outputTrack); + if (!cancelled && success){ + m_t1 = mT0 + outputTrack->GetEndTime(); + } delete stretch; delete []buffer0; @@ -343,9 +387,8 @@ PaulStretch::PaulStretch(float rap_,int in_bufsize_,float samplerate_) fft_c[i]=0.0; fft_s[i]=0.0; fft_freq[i]=0.0; - }; - -}; + } +} PaulStretch::~PaulStretch() { @@ -357,13 +400,13 @@ PaulStretch::~PaulStretch() delete [] fft_s; delete [] fft_freq; delete [] fft_tmp; -}; +} void PaulStretch::set_rap(float newrap) { if (rap>=1.0) rap=newrap; else rap=1.0; -}; +} void PaulStretch::process(float *smps,int nsmps) { @@ -371,7 +414,7 @@ void PaulStretch::process(float *smps,int nsmps) if ((smps!=NULL)&&(nsmps!=0)){ if (nsmps>poolsize){ nsmps=poolsize; - }; + } int nleft=poolsize-nsmps; //move left the samples from the pool to make room for new samples @@ -379,7 +422,7 @@ void PaulStretch::process(float *smps,int nsmps) //add new samples to the pool for (int i=0;imax) max=a; float b=fabs(fft_smps[i]); if (b>max2) max2=b; - }; + } //make the output buffer @@ -430,12 +472,11 @@ void PaulStretch::process(float *smps,int nsmps) float a=(0.5+0.5*cos(i*tmp)); float out=fft_smps[i+out_bufsize]*(1.0-a)+old_out_smp_buf[i]*a; out_buf[i]=out*(hinv_sqrt2-(1.0-hinv_sqrt2)*cos(i*2.0*tmp))*ampfactor; - }; + } //copy the current output buffer to old buffer for (int i=0;i=1.0){ ri+=(int)floor(remained_samples); remained_samples=remained_samples-floor(remained_samples); - }; + } if (ri>poolsize){ ri=poolsize; - }; + } return ri; -}; +} int PaulStretch::get_nsamples_for_fill() { return poolsize; -}; +} diff --git a/src/effects/Paulstretch.h b/src/effects/Paulstretch.h index 0741aeb04..2f3b601b8 100644 --- a/src/effects/Paulstretch.h +++ b/src/effects/Paulstretch.h @@ -40,6 +40,7 @@ public: // Effect implementation + virtual double CalcPreviewInputLength(double previewLength); virtual bool Process(); virtual void PopulateOrExchange(ShuttleGui & S); virtual bool TransferDataToWindow(); @@ -49,12 +50,13 @@ private: // EffectPaulstretch implementation void OnText(wxCommandEvent & evt); + int GetBufferSize(double rate); bool ProcessOne(WaveTrack *track, double t0, double t1, int count); private: - float amount; - float time_resolution; //seconds + float mAmount; + float mTime_resolution; //seconds double m_t1; DECLARE_EVENT_TABLE(); diff --git a/src/effects/TruncSilence.cpp b/src/effects/TruncSilence.cpp index 3f8f27f48..e57f0c0bd 100644 --- a/src/effects/TruncSilence.cpp +++ b/src/effects/TruncSilence.cpp @@ -58,6 +58,13 @@ Param( Compress, double, XO("Compress"), 50.0, 0.0, 99.9, static const sampleCount DEF_BlendFrameCount = 100; +// Lower bound on the amount of silence to find at a time -- this avoids +// detecting silence repeatedly in low-frequency sounds. +static const double DEF_MinTruncMs = 0.001; + +// Typical fraction of total time taken by detection (better to guess low) +const double detectFrac = 0.4; + #include WX_DEFINE_LIST(RegionList); @@ -154,6 +161,41 @@ bool EffectTruncSilence::SetAutomationParameters(EffectAutomationParameters & pa // Effect implementation +double EffectTruncSilence::CalcPreviewInputLength(double previewLength) +{ + double inputLength = mT1 - mT0; + double minInputLength = inputLength; + + // Master list of silent regions + RegionList silences; + silences.DeleteContents(true); + + // Start with the whole selection silent + Region *sel = new Region; + sel->start = mT0; + sel->end = mT1; + silences.push_back(sel); + + SelectedTrackListOfKindIterator iter(Track::Wave, mTracks); + int whichTrack = 0; + + for (Track *t = iter.First(); t; t = iter.Next()) { + WaveTrack *wt = (WaveTrack *)t; + + RegionList trackSilences; + trackSilences.DeleteContents(true); + + sampleCount index = wt->TimeToLongSamples(mT0); + sampleCount silentFrame = 0; // length of the current silence + + Analyze(silences, trackSilences, wt, &silentFrame, &index, whichTrack, &inputLength, &minInputLength); + + whichTrack++; + } + return inputLength; +} + + bool EffectTruncSilence::Startup() { wxString base = wxT("/Effects/TruncateSilence/"); @@ -206,17 +248,9 @@ bool EffectTruncSilence::Startup() bool EffectTruncSilence::Process() { - // Typical fraction of total time taken by detection (better to guess low) - const double detectFrac = .4; - // Copy tracks CopyInputTracks(Track::All); - // Lower bound on the amount of silence to find at a time -- this avoids - // detecting silence repeatedly in low-frequency sounds. - const double minTruncMs = 0.001; - double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex]; - // Master list of silent regions; it is responsible for deleting them. // This list should always be kept in order. RegionList silences; @@ -237,111 +271,19 @@ bool EffectTruncSilence::Process() // Smallest silent region to detect in frames sampleCount minSilenceFrames = - sampleCount(wxMax( mInitialAllowedSilence, minTruncMs) * - wt->GetRate()); + sampleCount(wxMax( mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate()); // // Scan the track for silences // RegionList trackSilences; trackSilences.DeleteContents(true); - sampleCount blockLen = wt->GetMaxBlockSize(); - sampleCount start = wt->TimeToLongSamples(mT0); - sampleCount end = wt->TimeToLongSamples(mT1); - // Allocate buffer - float *buffer = new float[blockLen]; + sampleCount index = wt->TimeToLongSamples(mT0); + sampleCount silentFrame = 0; - sampleCount index = start; - sampleCount silentFrames = 0; - bool cancelled = false; - - // Keep position in overall silences list for optimization - RegionList::iterator rit(silences.begin()); - - while (index < end) - { - // Show progress dialog, test for cancellation - cancelled = TotalProgress( - detectFrac * (whichTrack + index / (double)end) / - (double)GetNumWaveTracks()); - if (cancelled) - { - break; - } - - // - // Optimization: if not in a silent region skip ahead to the next one - // - double curTime = wt->LongSamplesToTime(index); - for ( ; rit != silences.end(); ++rit) - { - // Find the first silent region ending after current time - if ((*rit)->end >= curTime) - { - break; - } - } - - if (rit == silences.end()) - { - // No more regions -- no need to process the rest of the track - break; - } - else if ((*rit)->start > curTime) - { - // End current silent region, skip ahead - if (silentFrames >= minSilenceFrames) - { - Region *r = new Region; - r->start = wt->LongSamplesToTime(index - silentFrames); - r->end = wt->LongSamplesToTime(index); - trackSilences.push_back(r); - } - silentFrames = 0; - - index = wt->TimeToLongSamples((*rit)->start); - } - // - // End of optimization - // - - // Limit size of current block if we've reached the end - sampleCount count = blockLen; - if ((index + count) > end) - { - count = end - index; - } - - // Fill buffer - wt->Get((samplePtr)(buffer), floatSample, index, count); - - // Look for silences in current block - for (sampleCount i = 0; i < count; ++i) - { - if (fabs(buffer[i]) < truncDbSilenceThreshold) - { - ++silentFrames; - } - else - { - if (silentFrames >= minSilenceFrames) - { - // Record the silent region - Region *r = new Region; - r->start = wt->LongSamplesToTime(index + i - silentFrames); - r->end = wt->LongSamplesToTime(index + i); - trackSilences.push_back(r); - } - silentFrames = 0; - } - } - - // Next block - index += count; - } - - delete [] buffer; + // Detect silences + bool cancelled = !(Analyze(silences, trackSilences, wt, &silentFrame, &index, whichTrack)); // Buffer has been freed, so we're OK to return if cancelled if (cancelled) @@ -350,11 +292,11 @@ bool EffectTruncSilence::Process() return false; } - if (silentFrames >= minSilenceFrames) + if (silentFrame >= minSilenceFrames) { // Track ended in silence -- record region Region *r = new Region; - r->start = wt->LongSamplesToTime(index - silentFrames); + r->start = wt->LongSamplesToTime(index - silentFrame); r->end = wt->LongSamplesToTime(index); trackSilences.push_back(r); } @@ -480,6 +422,164 @@ bool EffectTruncSilence::Process() return true; } +bool EffectTruncSilence::Analyze(RegionList& silenceList, + RegionList& trackSilences, + WaveTrack* wt, + sampleCount* silentFrame, + sampleCount* index, + int whichTrack, + double* inputLength /*= NULL*/, + double* minInputLength /*= NULL*/) +{ + // Smallest silent region to detect in frames + sampleCount minSilenceFrames = sampleCount(wxMax( mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate()); + + double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex]; + sampleCount blockLen = wt->GetMaxBlockSize(); + sampleCount start = wt->TimeToLongSamples(mT0); + sampleCount end = wt->TimeToLongSamples(mT1); + sampleCount outLength = 0; + + double previewLength; + gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &previewLength, 6.0); + // Minimum required length in samples. + const sampleCount previewLen = previewLength * wt->GetRate(); + + // Keep position in overall silences list for optimization + RegionList::iterator rit(silenceList.begin()); + + // Allocate buffer + float *buffer = new float[blockLen]; + + // Loop through current track + while (*index < end) { + if (inputLength && ((outLength >= previewLen) || (*index - start > wt->TimeToLongSamples(*minInputLength)))) { + *inputLength = std::min(*inputLength, *minInputLength); + if (outLength >= previewLen) { + *minInputLength = *inputLength; + } + return true; + } + + if (!inputLength) { + // Show progress dialog, test for cancellation + bool cancelled = TotalProgress( + detectFrac * (whichTrack + (*index - start) / (double)(end - start)) / + (double)GetNumWaveTracks()); + if (cancelled) { + delete [] buffer; + return false; + } + } + + // Optimization: if not in a silent region skip ahead to the next one + + double curTime = wt->LongSamplesToTime(*index); + for ( ; rit != silenceList.end(); ++rit) { + // Find the first silent region ending after current time + if ((*rit)->end >= curTime) { + break; + } + } + + if (rit == silenceList.end()) { + // No more regions -- no need to process the rest of the track + if (inputLength) { + // Add available samples up to previewLength. + sampleCount remainingTrackSamples = wt->TimeToLongSamples(wt->GetEndTime()) - *index; + sampleCount requiredTrackSamples = previewLen - outLength; + outLength += (remainingTrackSamples > requiredTrackSamples)? requiredTrackSamples : remainingTrackSamples; + } + + break; + } + else if ((*rit)->start > curTime) { + // End current silent region, skip ahead + if (*silentFrame >= minSilenceFrames) { + Region *r = new Region; + r->start = wt->LongSamplesToTime(*index - *silentFrame); + r->end = wt->LongSamplesToTime(*index); + trackSilences.push_back(r); + } + *silentFrame = 0; + sampleCount newIndex = wt->TimeToLongSamples((*rit)->start); + if (inputLength) { + sampleCount requiredTrackSamples = previewLen - outLength; + // Add non-silent sample to outLength + outLength += ((newIndex - *index) > requiredTrackSamples)? requiredTrackSamples : newIndex - *index; + } + + *index = newIndex; + } + // End of optimization + + // Limit size of current block if we've reached the end + sampleCount count = blockLen; + if ((*index + count) > end) { + count = end - *index; + } + + // Fill buffer + wt->Get((samplePtr)(buffer), floatSample, *index, count); + + // Look for silenceList in current block + for (sampleCount i = 0; i < count; ++i) { + if (inputLength && ((outLength >= previewLen) || (outLength > wt->TimeToLongSamples(*minInputLength)))) { + *inputLength = wt->LongSamplesToTime(*index + i) - wt->LongSamplesToTime(start); + break; + } + + if (fabs(buffer[i]) < truncDbSilenceThreshold) { + (*silentFrame)++; + } + else { + sampleCount allowed = 0; + if (*silentFrame >= minSilenceFrames) { + if (inputLength) { + switch (mActionIndex) { + case kTruncate: + outLength += wt->TimeToLongSamples(mTruncLongestAllowedSilence); + break; + case kCompress: + allowed = wt->TimeToLongSamples(mInitialAllowedSilence); + outLength += allowed + + (*silentFrame - allowed) * mSilenceCompressPercent / 100.0; + break; + // default: // Not currently used. + } + } + + // Record the silent region + Region *r = new Region; + r->start = wt->LongSamplesToTime(*index + i - *silentFrame); + r->end = wt->LongSamplesToTime(*index + i); + trackSilences.push_back(r); + } + else if (inputLength) { // included as part of non-silence + outLength += *silentFrame; + } + *silentFrame = 0; + if (inputLength) { + ++outLength; // Add non-silent sample to outLength + } + } + } + // Next block + *index += count; + } + delete [] buffer; + + if (inputLength) { + *inputLength = std::min(*inputLength, *minInputLength); + if (outLength >= previewLen) { + *minInputLength = *inputLength; + } + } + + return true; +} + + void EffectTruncSilence::PopulateOrExchange(ShuttleGui & S) { wxASSERT(kNumActions == WXSIZEOF(kActionStrings)); diff --git a/src/effects/TruncSilence.h b/src/effects/TruncSilence.h index 2c2c0d7fc..73917e263 100644 --- a/src/effects/TruncSilence.h +++ b/src/effects/TruncSilence.h @@ -57,7 +57,21 @@ public: // Effect implementation + virtual double CalcPreviewInputLength(double previewLength); virtual bool Startup(); + + // Analyze a single track to find silences + // If inputLength is not NULL we are calculating the minimum + // amount of input for previewing. + virtual bool Analyze(RegionList &silenceList, + RegionList &trackSilences, + WaveTrack* wt, + sampleCount* silentFrame, + sampleCount* index, + int whichTrack, + double* inputLength = NULL, + double* minInputLength = NULL); + virtual bool Process(); virtual void PopulateOrExchange(ShuttleGui & S); virtual bool TransferDataToWindow(); @@ -68,7 +82,7 @@ private: //ToDo ... put BlendFrames in Effects, Project, or other class void BlendFrames(float* buffer, int leftIndex, int rightIndex, int blendFrameCount); - void Intersect(RegionList &dest, const RegionList &src); + void Intersect(RegionList &dest, const RegionList & src); void OnControlChange(wxCommandEvent & evt); void UpdateUI(); diff --git a/src/effects/nyquist/Nyquist.cpp b/src/effects/nyquist/Nyquist.cpp index d7f5ffe1a..3c821d0c4 100644 --- a/src/effects/nyquist/Nyquist.cpp +++ b/src/effects/nyquist/Nyquist.cpp @@ -571,7 +571,9 @@ bool NyquistEffect::Process() mProps += wxString::Format(wxT("(putprop '*PROJECT* (float %s) 'PREVIEW-DURATION)\n"), Internat::ToString(previewLen).c_str()); - + // *PREVIEWP* is true when previewing (better than relying on track view). + wxString isPreviewing = (this->IsPreviewing())? wxT("T") : wxT("NIL"); + mProps += wxString::Format(wxT("(setf *PREVIEWP* %s)\n"), isPreviewing.c_str()); mProps += wxString::Format(wxT("(putprop '*SELECTION* (float %s) 'START)\n"), Internat::ToString(mT0).c_str());