Audacity 3.2.0
Nyquist.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Nyquist.cpp
6
7 Dominic Mazzoni
8
9******************************************************************//****************************************************************//****************************************************************//*******************************************************************/
26
27
28#include "Nyquist.h"
29#include "EffectOutputTracks.h"
30
31#include <algorithm>
32#include <cmath>
33#include <cstring>
34
35#include <locale.h>
36
37#include <wx/checkbox.h>
38#include <wx/choice.h>
39#include <wx/datetime.h>
40#include <wx/log.h>
41#include <wx/scrolwin.h>
42#include <wx/sizer.h>
43#include <wx/slider.h>
44#include <wx/sstream.h>
45#include <wx/stattext.h>
46#include <wx/textdlg.h>
47#include <wx/tokenzr.h>
48#include <wx/txtstrm.h>
49#include <wx/valgen.h>
50#include <wx/wfstream.h>
51#include <wx/numformatter.h>
52#include <wx/stdpaths.h>
53
54#include "LabelTrack.h"
55#include "NoteTrack.h"
57#include "../../prefs/GUIPrefs.h"
58#include "../../prefs/SpectrogramSettings.h"
59#include "../../tracks/playabletrack/wavetrack/ui/WaveChannelView.h"
60#include "../../tracks/playabletrack/wavetrack/ui/WaveChannelViewConstants.h"
61#include "../../widgets/NumericTextCtrl.h"
62#include "../../widgets/valnum.h"
63#include "../EffectEditor.h"
64#include "AudacityMessageBox.h"
65#include "BasicUI.h"
66#include "EffectManager.h"
67#include "FileNames.h"
68#include "Languages.h"
69#include "PluginManager.h"
70#include "Prefs.h"
71#include "ProgressDialog.h"
72#include "Project.h"
73#include "ProjectRate.h"
74#include "ShuttleAutomation.h"
75#include "ShuttleGui.h"
76#include "SyncLock.h"
77#include "TempDirectory.h"
78#include "TimeTrack.h"
79#include "TimeWarper.h"
80#include "ViewInfo.h"
82#include "WaveClip.h"
83#include "WaveTrack.h"
84#include "wxFileNameWrapper.h"
85
87
88#ifndef nyx_returns_start_and_end_time
89#error You need to update lib-src/libnyquist
90#endif
91
92#include <locale.h>
93#include <iostream>
94#include <ostream>
95#include <sstream>
96#include <float.h>
97
98#define NYQUIST_WORKER_ID wxT("Nyquist Worker")
99
101
102enum
103{
104 ID_Editor = 10000,
107
108 ID_Slider = 11000,
109 ID_Text = 12000,
110 ID_Choice = 13000,
111 ID_Time = 14000,
112 ID_FILE = 15000
114
115// Protect Nyquist from selections greater than 2^31 samples (bug 439)
116#define NYQ_MAX_LEN (std::numeric_limits<int64_t>::max())
117
118#define UNINITIALIZED_CONTROL ((double)99999999.99)
119
120static const wxChar *KEY_Command = wxT("Command");
121static const wxChar *KEY_Parameters = wxT("Parameters");
122
124//
125// NyquistEffect
126//
128
129BEGIN_EVENT_TABLE(NyquistEffect, wxEvtHandler)
132
134 wxEVT_COMMAND_SLIDER_UPDATED, NyquistEffect::OnSlider)
138 wxEVT_COMMAND_CHOICE_SELECTED, NyquistEffect::OnChoice)
144
145NyquistEffect::NyquistEffect(const wxString &fName)
146 : mIsPrompt{ fName == NYQUIST_PROMPT_ID }
147{
148 mAction = XO("Applying Nyquist Effect...");
149 mExternal = false;
150 mCompiler = false;
151 mTrace = false;
152 mRedirectOutput = false;
153 mDebug = false;
154 mIsSal = false;
155 mOK = false;
156 mAuthor = XO("n/a");
157 mReleaseVersion = XO("n/a");
158 mCopyright = XO("n/a");
159
160 // set clip/split handling when applying over clip boundary.
161 mRestoreSplits = true; // Default: Restore split lines.
162 mMergeClips = -1; // Default (auto): Merge if length remains unchanged.
163
164 mVersion = 4;
165
166 mStop = false;
167 mBreak = false;
168 mCont = false;
169 mIsTool = false;
170
171 mMaxLen = NYQ_MAX_LEN;
172
173 // Interactive Nyquist
174 if (mIsPrompt) {
175 mName = NYQUIST_PROMPT_NAME;
176 mType = EffectTypeTool;
177 mIsTool = true;
178 mPromptName = mName;
179 mPromptType = mType;
180 mOK = true;
181 return;
182 }
183
184 if (fName == NYQUIST_WORKER_ID) {
185 // Effect spawned from Nyquist Prompt
186/* i18n-hint: It is acceptable to translate this the same as for "Nyquist Prompt" */
187 mName = XO("Nyquist Worker");
188 return;
189 }
190
191 mFileName = fName;
192 // Use the file name verbatim as effect name.
193 // This is only a default name, overridden if we find a $name line:
194 mName = Verbatim( mFileName.GetName() );
195 mFileModified = mFileName.GetModificationTime();
196 ParseFile();
197
198 if (!mOK && mInitError.empty())
199 mInitError = XO("Ill-formed Nyquist plug-in header");
200}
201
203{
204}
205
206// ComponentInterface implementation
207
209{
210 if (mIsPrompt)
211 return NYQUIST_PROMPT_ID;
212
213 return mFileName.GetFullPath();
214}
215
217{
218 if (mIsPrompt)
220
221 return mName;
222}
223
225{
226 if (mIsPrompt)
227 {
228 return XO("Audacity");
229 }
230
231 return mAuthor;
232}
233
235{
236 // Are Nyquist version strings really supposed to be translatable?
237 // See commit a06e561 which used XO for at least one of them
239}
240
242{
243 return mCopyright;
244}
245
247{
248 return mIsPrompt
249 ? wxString("Nyquist_Prompt")
250 : mManPage;
251}
252
253
254std::pair<bool, FilePath> NyquistEffect::CheckHelpPage() const
255{
257 wxString fileName;
258
259 for (size_t i = 0, cnt = paths.size(); i < cnt; i++) {
260 fileName = wxFileName(paths[i] + wxT("/") + mHelpFile).GetFullPath();
261 if (wxFileExists(fileName))
262 {
263 return { true, fileName };
264 }
265 }
266 return { false, wxEmptyString };
267}
268
269
271{
272 return mHelpPage;
273}
274
275// EffectDefinitionInterface implementation
276
278{
279 return mType;
280}
281
283{
284 if (mIsTool)
285 return EffectTypeTool;
286 return mType;
287}
288
290{
292}
293
295{
296 if (mIsPrompt)
297 {
298 return true;
299 }
300
301 return mControls.size() != 0;
302}
303
305{
306 return mIsPrompt;
307}
308
311{
312 if (auto pSa = dynamic_cast<ShuttleSetAutomation*>(&visitor))
313 LoadSettings(*pSa->mpEap, settings);
314 return true;
315}
316
318 ConstSettingsVisitor &visitor, const EffectSettings &settings) const
319{
320 // For now we assume Nyquist can do get and set better than VisitSettings can,
321 // And so we ONLY use it for getting the signature.
322 if (auto pGa = dynamic_cast<ShuttleGetAutomation*>(&visitor)) {
323 SaveSettings(settings, *pGa->mpEap);
324 return true;
325 }
326 else if (auto pSd = dynamic_cast<ShuttleGetDefinition*>(&visitor);
327 !pSd)
328 // must be the NullShuttle
329 return true;
330
331 // Get the "definition," only for the help or info commands
332 if (mExternal)
333 return true;
334
335 if (mIsPrompt) {
336 visitor.Define( mInputCmd, KEY_Command, wxString{} );
337 visitor.Define( mParameters, KEY_Parameters, wxString{} );
338 return true;
339 }
340
341 for (const auto &ctrl : mControls) {
342 double d = ctrl.val;
343
344 if (d == UNINITIALIZED_CONTROL && ctrl.type != NYQ_CTRL_STRING)
345 d = GetCtrlValue(ctrl.valStr);
346
347 if (ctrl.type == NYQ_CTRL_FLOAT || ctrl.type == NYQ_CTRL_FLOAT_TEXT ||
348 ctrl.type == NYQ_CTRL_TIME)
349 visitor.Define( d, static_cast<const wxChar*>( ctrl.var.c_str() ),
350 (double)0.0, ctrl.low, ctrl.high, 1.0);
351 else if (ctrl.type == NYQ_CTRL_INT || ctrl.type == NYQ_CTRL_INT_TEXT) {
352 int x = d;
353 visitor.Define( x, static_cast<const wxChar*>( ctrl.var.c_str() ), 0,
354 static_cast<int>(ctrl.low), static_cast<int>(ctrl.high), 1);
355 //parms.Write(ctrl.var, (int) d);
356 }
357 else if (ctrl.type == NYQ_CTRL_CHOICE) {
358 // untranslated
359 int x = d;
360 //parms.WriteEnum(ctrl.var, (int) d, choices);
361 visitor.DefineEnum( x, static_cast<const wxChar*>( ctrl.var.c_str() ),
362 0, ctrl.choices.data(), ctrl.choices.size() );
363 }
364 else if (ctrl.type == NYQ_CTRL_STRING || ctrl.type == NYQ_CTRL_FILE) {
365 visitor.Define( ctrl.valStr, ctrl.var,
366 wxString{}, ctrl.lowStr, ctrl.highStr );
367 //parms.Write(ctrl.var, ctrl.valStr);
368 }
369 }
370 return true;
371}
372
374 const EffectSettings &, CommandParameters & parms) const
375{
376 if (mIsPrompt)
377 {
378 parms.Write(KEY_Command, mInputCmd);
379 parms.Write(KEY_Parameters, mParameters);
380
381 return true;
382 }
383
384 for (size_t c = 0, cnt = mControls.size(); c < cnt; c++)
385 {
386 const NyqControl & ctrl = mControls[c];
387 double d = ctrl.val;
388
389 if (d == UNINITIALIZED_CONTROL && ctrl.type != NYQ_CTRL_STRING)
390 {
391 d = GetCtrlValue(ctrl.valStr);
392 }
393
394 if (ctrl.type == NYQ_CTRL_FLOAT || ctrl.type == NYQ_CTRL_FLOAT_TEXT ||
395 ctrl.type == NYQ_CTRL_TIME)
396 {
397 parms.Write(ctrl.var, d);
398 }
399 else if (ctrl.type == NYQ_CTRL_INT || ctrl.type == NYQ_CTRL_INT_TEXT)
400 {
401 parms.Write(ctrl.var, (int) d);
402 }
403 else if (ctrl.type == NYQ_CTRL_CHOICE)
404 {
405 // untranslated
406 parms.WriteEnum(ctrl.var, (int) d,
407 ctrl.choices.data(), ctrl.choices.size());
408 }
409 else if (ctrl.type == NYQ_CTRL_STRING)
410 {
411 parms.Write(ctrl.var, ctrl.valStr);
412 }
413 else if (ctrl.type == NYQ_CTRL_FILE)
414 {
415 // Convert the given path string to platform-dependent equivalent
416 resolveFilePath(const_cast<wxString&>(ctrl.valStr));
417 parms.Write(ctrl.var, ctrl.valStr);
418 }
419 }
420
421 return true;
422}
423
425 const CommandParameters & parms, EffectSettings &settings) const
426{
427 // To do: externalize state so const_cast isn't needed
428 return const_cast<NyquistEffect*>(this)->DoLoadSettings(parms, settings);
429}
430
433{
434 // Due to a constness problem that happens when using the prompt, we need
435 // to be ready to switch the params to a local instance.
436 const CommandParameters* pParms = &parms;
437 CommandParameters localParms;
438
439 if (mIsPrompt)
440 {
441 parms.Read(KEY_Command, &mInputCmd, wxEmptyString);
442 parms.Read(KEY_Parameters, &mParameters, wxEmptyString);
443
444 if (!mInputCmd.empty())
445 {
447 }
448
449 if (!mParameters.empty())
450 {
451 pParms = &localParms;
452 localParms.SetParameters(mParameters);
453 }
454
455 if (!IsBatchProcessing())
456 {
458 }
459
462 mExternal = true;
463
464 if (!IsBatchProcessing())
465 {
466 return true;
467 }
468 }
469
470 // Constants to document what the true/false values mean.
471 const auto kTestOnly = true;
472 const auto kTestAndSet = false;
473
474 // badCount will encompass both actual bad values and missing values.
475 // We probably never actually have bad values when using the dialogs
476 // since the dialog validation will catch them.
477 int badCount;
478 // When batch processing, we just ignore missing/bad parameters.
479 // We'll end up using defaults in those cases.
480 if (!IsBatchProcessing()) {
481 badCount = SetLispVarsFromParameters(*pParms, kTestOnly);
482 if (badCount > 0)
483 return false;
484 }
485
486 badCount = SetLispVarsFromParameters(*pParms, kTestAndSet);
487 // We never do anything with badCount here.
488 // It might be non zero, for missing parameters, and we allow that,
489 // and don't distinguish that from an out-of-range value.
490 return true;
491}
492
493// Sets the lisp variables form the parameters.
494// returns the number of bad settings.
495// We can run this just testing for bad values, or actually setting when
496// the values are good.
498{
499 int badCount = 0;
500 // First pass verifies values
501 for (size_t c = 0, cnt = mControls.size(); c < cnt; c++)
502 {
503 NyqControl & ctrl = mControls[c];
504 bool good = false;
505
506 // This GetCtrlValue code is preserved from former code,
507 // but probably is pointless. The value d isn't used later,
508 // and GetCtrlValue does not appear to have important needed
509 // side effects.
510 if (!bTestOnly) {
511 double d = ctrl.val;
512 if (d == UNINITIALIZED_CONTROL && ctrl.type != NYQ_CTRL_STRING)
513 {
514 d = GetCtrlValue(ctrl.valStr);
515 }
516 }
517
518 if (ctrl.type == NYQ_CTRL_FLOAT || ctrl.type == NYQ_CTRL_FLOAT_TEXT ||
519 ctrl.type == NYQ_CTRL_TIME)
520 {
521 double val;
522 good = parms.Read(ctrl.var, &val) &&
523 val >= ctrl.low &&
524 val <= ctrl.high;
525 if (good && !bTestOnly)
526 ctrl.val = val;
527 }
528 else if (ctrl.type == NYQ_CTRL_INT || ctrl.type == NYQ_CTRL_INT_TEXT)
529 {
530 int val;
531 good = parms.Read(ctrl.var, &val) &&
532 val >= ctrl.low &&
533 val <= ctrl.high;
534 if (good && !bTestOnly)
535 ctrl.val = (double)val;
536 }
537 else if (ctrl.type == NYQ_CTRL_CHOICE)
538 {
539 int val;
540 // untranslated
541 good = parms.ReadEnum(ctrl.var, &val,
542 ctrl.choices.data(), ctrl.choices.size()) &&
543 val != wxNOT_FOUND;
544 if (good && !bTestOnly)
545 ctrl.val = (double)val;
546 }
547 else if (ctrl.type == NYQ_CTRL_STRING || ctrl.type == NYQ_CTRL_FILE)
548 {
549 wxString val;
550 good = parms.Read(ctrl.var, &val);
551 if (good && !bTestOnly)
552 ctrl.valStr = val;
553 }
554 else if (ctrl.type == NYQ_CTRL_TEXT)
555 {
556 // This "control" is just fixed text (nothing to save or restore),
557 // Does not count for good/bad counting.
558 good = true;
559 }
560 badCount += !good ? 1 : 0;
561 }
562 return badCount;
563}
564
565// Effect Implementation
567{
568 // When Nyquist Prompt spawns an effect GUI, Init() is called for Nyquist Prompt,
569 // and then again for the spawned (mExternal) effect.
570
571 // EffectType may not be defined in script, so
572 // reset each time we call the Nyquist Prompt.
573 if (mIsPrompt) {
575 // Reset effect type each time we call the Nyquist Prompt.
577 mIsSpectral = false;
578 mDebugButton = true; // Debug button always enabled for Nyquist Prompt.
579 mEnablePreview = true; // Preview button always enabled for Nyquist Prompt.
580 mVersion = 4;
581 }
582
583 // As of Audacity 2.1.2 rc1, 'spectral' effects are allowed only if
584 // the selected track(s) are in a spectrogram view, and there is at
585 // least one frequency bound and Spectral Selection is enabled for the
586 // selected track(s) - (but don't apply to Nyquist Prompt).
587
588 if (!mIsPrompt && mIsSpectral) {
589 // Completely skip the spectral editing limitations if there is no
590 // project because that is editing of macro parameters
591 if (const auto project = FindProject()) {
592 bool bAllowSpectralEditing = false;
593 bool hasSpectral = false;
594 for (auto t :
595 TrackList::Get( *project ).Selected<const WaveTrack>()) {
596 // Find() not Get() to avoid creation-on-demand of views in case we are
597 // only previewing
598 auto pView = WaveChannelView::FindFirst(t);
599 if ( pView ) {
600 const auto displays = pView->GetDisplays();
601 if (displays.end() != std::find(
602 displays.begin(), displays.end(),
604 WaveChannelViewConstants::Spectrum, {} }))
605 hasSpectral = true;
606 }
607 if ( hasSpectral &&
609 bAllowSpectralEditing = true;
610 break;
611 }
612 }
613
614 if (!bAllowSpectralEditing || ((mF0 < 0.0) && (mF1 < 0.0))) {
615 if (!hasSpectral) {
617 XO("Enable track spectrogram view before\n"
618 "applying 'Spectral' effects."),
619 wxOK | wxICON_EXCLAMATION | wxCENTRE,
620 XO("Error") );
621 } else {
623 XO("To use 'Spectral effects', enable 'Spectral Selection'\n"
624 "in the track Spectrogram settings and select the\n"
625 "frequency range for the effect to act on."),
626 wxOK | wxICON_EXCLAMATION | wxCENTRE,
627 XO("Error") );
628 }
629 return false;
630 }
631 }
632 }
633
634 if (!mIsPrompt && !mExternal)
635 {
636 //TODO: (bugs):
637 // 1) If there is more than one plug-in with the same name, GetModificationTime may pick the wrong one.
638 // 2) If the ;type is changed after the effect has been registered, the plug-in will appear in the wrong menu.
639
640 //TODO: If we want to auto-add parameters from spectral selection,
641 //we will need to modify this test.
642 //Note that removing it stops the caching of parameter values,
643 //(during this session).
644 if (mFileName.GetModificationTime().IsLaterThan(mFileModified))
645 {
646 // If the effect has internal state, save and restore it.
647 // If the effect is stateless, saving and restoring don't matter.
648 auto dummySettings = MakeSettings();
649 constexpr auto key = L"TemporarySettings";
650 SaveUserPreset(key, dummySettings);
651
652 mMaxLen = NYQ_MAX_LEN;
653 ParseFile();
654 mFileModified = mFileName.GetModificationTime();
655
656 // Ignore failure
657 (void) LoadUserPreset(key, dummySettings);
658 }
659 }
660
661 return true;
662}
663
664static void RegisterFunctions();
665
669 using ProgressReport = std::function<bool(double)>;
670
671 NyxContext(ProgressReport progressReport, double scale, double progressTot)
672 : mProgressReport{ move(progressReport) }
673 , mScale{ scale }
674 , mProgressTot{ progressTot }
675 {}
676
677 int GetCallback(float *buffer, int channel,
678 int64_t start, int64_t len, int64_t totlen);
679 int PutCallback(float *buffer, int channel,
680 int64_t start, int64_t len, int64_t totlen);
681 static int StaticGetCallback(float *buffer, int channel,
682 int64_t start, int64_t len, int64_t totlen, void *userdata);
683 static int StaticPutCallback(float *buffer, int channel,
684 int64_t start, int64_t len, int64_t totlen, void *userdata);
685
689
690 unsigned mCurNumChannels{};
691
692 using Buffer = std::unique_ptr<float[]>;
695 size_t mCurBufferLen[2]{};
697
699
700 double mProgressIn{};
701 double mProgressOut{};
702
704 const double mScale;
705 const double mProgressTot;
706
707 std::exception_ptr mpException{};
708};
709
711{
712 if (mIsPrompt && mControls.size() > 0 && !IsBatchProcessing()) {
713 auto &nyquistSettings = GetSettings(settings);
714 auto cleanup = finally([&]{
715 // Free up memory
716 nyquistSettings.proxySettings = {};
717 });
719 proxy.SetCommand(mInputCmd);
720 proxy.mDebug = nyquistSettings.proxyDebug;
721 proxy.mControls = move(nyquistSettings.controls);
722 auto result = Delegate(proxy, nyquistSettings.proxySettings);
723 if (result) {
724 mT0 = proxy.mT0;
725 mT1 = proxy.mT1;
726 }
727 return result;
728 }
729
730 // Check for reentrant Nyquist commands.
731 // I'm choosing to mark skipped Nyquist commands as successful even though
732 // they are skipped. The reason is that when Nyquist calls out to a chain,
733 // and that chain contains Nyquist, it will be clearer if the chain completes
734 // skipping Nyquist, rather than doing nothing at all.
735 if( mReentryCount > 0 )
736 return true;
737
738 // Restore the reentry counter (to zero) when we exit.
739 auto countRestorer = valueRestorer( mReentryCount);
742
743 bool success = true;
744 int nEffectsSoFar = EffectOutputTracks::nEffectsDone;
745 mProjectChanged = false;
747 em.SetSkipStateFlag(false);
748
749 // This code was added in a fix for bug 2392 (no preview for Nyquist)
750 // It was commented out in a fix for bug 2428 (no progress dialog from a macro)
751 //if (mExternal) {
752 // mProgress->Hide();
753 //}
754
755 mOutputTime = 0;
756 mCount = 0;
757 const auto scale =
758 (GetType() == EffectTypeProcess ? 0.5 : 1.0) / GetNumWaveGroups();
759
760 mStop = false;
761 mBreak = false;
762 mCont = false;
763
764 mTrackIndex = 0;
765
766 // If in tool mode, then we don't do anything with the track and selection.
767 const bool bOnePassTool = (GetType() == EffectTypeTool);
768
769 // We must copy all the tracks, because Paste needs label tracks to ensure
770 // correct sync-lock group behavior when the timeline is affected; then we just want
771 // to operate on the selected wave tracks
772 std::optional<EffectOutputTracks> oOutputs;
773 if (!bOnePassTool)
774 oOutputs.emplace(
776 true, false);
777
778 mNumSelectedChannels = bOnePassTool
779 ? 0
780 : oOutputs->Get().Selected<const WaveTrack>()
782
783 mDebugOutput = {};
784 if (!mHelpFile.empty() && !mHelpFileExists) {
786"error: File \"%s\" specified in header but not found in plug-in path.\n")
787 .Format( mHelpFile );
788 }
789
790 if (mVersion >= 4)
791 {
792 auto project = FindProject();
793
794 mProps = wxEmptyString;
795
796 mProps += wxString::Format(wxT("(putprop '*AUDACITY* (list %d %d %d) 'VERSION)\n"), AUDACITY_VERSION, AUDACITY_RELEASE, AUDACITY_REVISION);
797 wxString lang = gPrefs->Read(wxT("/Locale/Language"), wxT(""));
798 lang = (lang.empty())
800 : lang;
801 mProps += wxString::Format(wxT("(putprop '*AUDACITY* \"%s\" 'LANGUAGE)\n"), lang);
802
803 mProps += wxString::Format(wxT("(setf *DECIMAL-SEPARATOR* #\\%c)\n"), wxNumberFormatter::GetDecimalSeparator());
804
805 mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'BASE)\n"), EscapeString(FileNames::BaseDir()));
806 mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'DATA)\n"), EscapeString(FileNames::DataDir()));
807 mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'HELP)\n"), EscapeString(FileNames::HtmlHelpDir().RemoveLast()));
808 mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'TEMP)\n"), EscapeString(TempDirectory::TempDir()));
809 mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'SYS-TEMP)\n"), EscapeString(wxStandardPaths::Get().GetTempDir()));
810 mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'DOCUMENTS)\n"), EscapeString(wxStandardPaths::Get().GetDocumentsDir()));
811 mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'HOME)\n"), EscapeString(wxGetHomeDir()));
812
814 wxString list;
815 for (size_t i = 0, cnt = paths.size(); i < cnt; i++)
816 {
817 list += wxT("\"") + EscapeString(paths[i]) + wxT("\" ");
818 }
819 list = list.RemoveLast();
820
821 mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* (list %s) 'PLUGIN)\n"), list);
822 mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* (list %s) 'PLUG-IN)\n"), list);
823 mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'USER-PLUG-IN)\n"),
825
826 // Date and time:
827 wxDateTime now = wxDateTime::Now();
828 int year = now.GetYear();
829 int doy = now.GetDayOfYear();
830 int dom = now.GetDay();
831 // enumerated constants
832 wxDateTime::Month month = now.GetMonth();
833 wxDateTime::WeekDay day = now.GetWeekDay();
834
835 // Date/time as a list: year, day of year, hour, minute, seconds
836 mProps += wxString::Format(wxT("(setf *SYSTEM-TIME* (list %d %d %d %d %d))\n"),
837 year, doy, now.GetHour(), now.GetMinute(), now.GetSecond());
838
839 mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'DATE)\n"), now.FormatDate());
840 mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'TIME)\n"), now.FormatTime());
841 mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'ISO-DATE)\n"), now.FormatISODate());
842 mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'ISO-TIME)\n"), now.FormatISOTime());
843 mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* %d 'YEAR)\n"), year);
844 mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* %d 'DAY)\n"), dom); // day of month
845 mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* %d 'MONTH)\n"), month);
846 mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'MONTH-NAME)\n"), now.GetMonthName(month));
847 mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'DAY-NAME)\n"), now.GetWeekDayName(day));
848
849 mProps += wxString::Format(wxT("(putprop '*PROJECT* %d 'PROJECTS)\n"),
850 (int) AllProjects{}.size());
851 mProps += wxString::Format(wxT("(putprop '*PROJECT* \"%s\" 'NAME)\n"), EscapeString(project->GetProjectName()));
852
853 int numTracks = 0;
854 int numWave = 0;
855 int numLabel = 0;
856 int numMidi = 0;
857 int numTime = 0;
858 wxString waveTrackList; // track positions of selected audio tracks.
859
860 {
861 auto countRange = TrackList::Get( *project ).Any();
862 for (auto t : countRange) {
863 t->TypeSwitch( [&](const WaveTrack &) {
864 numWave++;
865 if (t->GetSelected())
866 waveTrackList += wxString::Format(wxT("%d "), 1 + numTracks);
867 });
868 numTracks++;
869 }
870 numLabel = countRange.Filter<const LabelTrack>().size();
871 #if defined(USE_MIDI)
872 numMidi = countRange.Filter<const NoteTrack>().size();
873 #endif
874 numTime = countRange.Filter<const TimeTrack>().size();
875 }
876
877 // We use Internat::ToString() rather than "%g" here because we
878 // always have to use the dot as decimal separator when giving
879 // numbers to Nyquist, whereas using "%g" will use the user's
880 // decimal separator which may be a comma in some countries.
881 mProps += wxString::Format(wxT("(putprop '*PROJECT* (float %s) 'RATE)\n"),
883 mProps += wxString::Format(wxT("(putprop '*PROJECT* %d 'TRACKS)\n"), numTracks);
884 mProps += wxString::Format(wxT("(putprop '*PROJECT* %d 'WAVETRACKS)\n"), numWave);
885 mProps += wxString::Format(wxT("(putprop '*PROJECT* %d 'LABELTRACKS)\n"), numLabel);
886 mProps += wxString::Format(wxT("(putprop '*PROJECT* %d 'MIDITRACKS)\n"), numMidi);
887 mProps += wxString::Format(wxT("(putprop '*PROJECT* %d 'TIMETRACKS)\n"), numTime);
888
889 double previewLen = 6.0;
890 gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &previewLen);
891 mProps += wxString::Format(wxT("(putprop '*PROJECT* (float %s) 'PREVIEW-DURATION)\n"),
892 Internat::ToString(previewLen));
893
894 // *PREVIEWP* is true when previewing (better than relying on track view).
895 wxString isPreviewing = (this->IsPreviewing())? wxT("T") : wxT("NIL");
896 mProps += wxString::Format(wxT("(setf *PREVIEWP* %s)\n"), isPreviewing);
897
898 mProps += wxString::Format(wxT("(putprop '*SELECTION* (list %s) 'TRACKS)\n"), waveTrackList);
899 mProps += wxString::Format(wxT("(putprop '*SELECTION* %d 'CHANNELS)\n"), mNumSelectedChannels);
900 }
901
902 // Nyquist Prompt does not require a selection, but effects do.
903 if (!bOnePassTool && (mNumSelectedChannels == 0)) {
904 auto message = XO("Audio selection required.");
906 message,
907 wxOK | wxCENTRE | wxICON_EXCLAMATION,
908 XO("Nyquist Error") );
909 }
910
911 std::optional<TrackIterRange<WaveTrack>> pRange;
912 if (!bOnePassTool)
913 pRange.emplace(oOutputs->Get().Selected<WaveTrack>());
914
915 // Keep track of whether the current track is first selected in its sync-lock group
916 // (we have no idea what the length of the returned audio will be, so we have
917 // to handle sync-lock group behavior the "old" way).
918 mFirstInGroup = true;
919 Track *gtLast = NULL;
920 double progressTot{};
921
922 for (;
923 bOnePassTool || pRange->first != pRange->second;
924 (void) (!pRange || (++pRange->first, true))
925 ) {
926 // Prepare to accumulate more debug output in OutputCallback
928 mDebugOutput = Verbatim( "%s" ).Format( std::cref( mDebugOutputStr ) );
929
930 // New context for each channel group of input
931 NyxContext nyxContext{ [this](double frac){ return TotalProgress(frac); },
932 scale, progressTot };
933 auto &mCurNumChannels = nyxContext.mCurNumChannels;
934 auto &mCurChannelGroup = nyxContext.mCurChannelGroup;
935 auto &mCurTrack = nyxContext.mCurTrack;
936 auto &mCurStart = nyxContext.mCurStart;
937 auto &mCurLen = nyxContext.mCurLen;
938
939 mCurChannelGroup = pRange ? *pRange->first : nullptr;
940 mCurTrack[0] = mCurChannelGroup
941 ? (*mCurChannelGroup->Channels().begin()).get()
942 : nullptr;
943 mCurNumChannels = 1;
944 assert(mCurChannelGroup != nullptr || bOnePassTool);
945 if ( (mT1 >= mT0) || bOnePassTool ) {
946 if (bOnePassTool) {
947 }
948 else {
949 if (auto channels = mCurChannelGroup->Channels()
950 ; channels.size() > 1
951 ) {
952 // TODO: more-than-two-channels
953 // Pay attention to consistency of mNumSelectedChannels
954 // with the running tally made by this loop!
955 mCurNumChannels = 2;
956
957 mCurTrack[1] = (* ++ channels.first).get();
958 }
959
960 // Check whether we're in the same group as the last selected track
961 Track *gt = *SyncLock::Group(*mCurChannelGroup).first;
962 mFirstInGroup = !gtLast || (gtLast != gt);
963 gtLast = gt;
964
965 mCurStart = mCurChannelGroup->TimeToLongSamples(mT0);
966 auto end = mCurChannelGroup->TimeToLongSamples(mT1);
967 mCurLen = end - mCurStart;
968
969 wxASSERT(mCurLen <= NYQ_MAX_LEN);
970
971 mCurLen = std::min(mCurLen, mMaxLen);
972 }
973
974 // libnyquist breaks except in LC_NUMERIC=="C".
975 //
976 // Note that we must set the locale to "C" even before calling
977 // nyx_init() because otherwise some effects will not work!
978 //
979 // MB: setlocale is not thread-safe. Should use uselocale()
980 // if available, or fix libnyquist to be locale-independent.
981 // See also http://bugzilla.audacityteam.org/show_bug.cgi?id=642#c9
982 // for further info about this thread safety question.
983 wxString prevlocale = wxSetlocale(LC_NUMERIC, NULL);
984 wxSetlocale(LC_NUMERIC, wxString(wxT("C")));
985
986 nyx_init();
987 nyx_set_os_callback(StaticOSCallback, (void *)this);
988 nyx_capture_output(StaticOutputCallback, (void *)this);
989
990 auto cleanup = finally( [&] {
991 nyx_capture_output(NULL, (void *)NULL);
992 nyx_set_os_callback(NULL, (void *)NULL);
993 nyx_cleanup();
994 } );
995
996
997 if (mVersion >= 4)
998 {
999 mPerTrackProps = wxEmptyString;
1000 wxString lowHz = wxT("nil");
1001 wxString highHz = wxT("nil");
1002 wxString centerHz = wxT("nil");
1003 wxString bandwidth = wxT("nil");
1004
1005 if (mF0 >= 0.0) {
1006 lowHz.Printf(wxT("(float %s)"), Internat::ToString(mF0));
1007 }
1008
1009 if (mF1 >= 0.0) {
1010 highHz.Printf(wxT("(float %s)"), Internat::ToString(mF1));
1011 }
1012
1013 if ((mF0 >= 0.0) && (mF1 >= 0.0)) {
1014 centerHz.Printf(wxT("(float %s)"), Internat::ToString(sqrt(mF0 * mF1)));
1015 }
1016
1017 if ((mF0 > 0.0) && (mF1 >= mF0)) {
1018 // with very small values, bandwidth calculation may be inf.
1019 // (Observed on Linux)
1020 double bw = log(mF1 / mF0) / log(2.0);
1021 if (!std::isinf(bw)) {
1022 bandwidth.Printf(wxT("(float %s)"), Internat::ToString(bw));
1023 }
1024 }
1025
1026 mPerTrackProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'LOW-HZ)\n"), lowHz);
1027 mPerTrackProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'CENTER-HZ)\n"), centerHz);
1028 mPerTrackProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'HIGH-HZ)\n"), highHz);
1029 mPerTrackProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'BANDWIDTH)\n"), bandwidth);
1030
1031 const auto t0 =
1032 mCurChannelGroup ? mCurChannelGroup->SnapToSample(mT0) : mT0;
1033 const auto t1 =
1034 mCurChannelGroup ? mCurChannelGroup->SnapToSample(mT1) : mT1;
1035 mPerTrackProps += wxString::Format(
1036 wxT("(putprop '*SELECTION* (float %s) 'START)\n"),
1037 Internat::ToString(t0));
1038 mPerTrackProps += wxString::Format(
1039 wxT("(putprop '*SELECTION* (float %s) 'END)\n"),
1040 Internat::ToString(t1));
1041 }
1042
1043 success = ProcessOne(nyxContext, oOutputs ? &*oOutputs : nullptr);
1044
1045 // Reset previous locale
1046 wxSetlocale(LC_NUMERIC, prevlocale);
1047
1048 if (!success || bOnePassTool) {
1049 goto finish;
1050 }
1051 progressTot += nyxContext.mProgressIn + nyxContext.mProgressOut;
1052 }
1053
1054 mCount += mCurNumChannels;
1055 }
1056
1057 if (mOutputTime > 0.0) {
1058 mT1 = mT0 + mOutputTime;
1059 }
1060
1061finish:
1062
1063 // Show debug window if trace set in plug-in header and something to show.
1064 mDebug = (mTrace && !mDebugOutput.Translation().empty())? true : mDebug;
1065
1066 if (mDebug && !mRedirectOutput) {
1067 NyquistOutputDialog dlog(nullptr, -1,
1068 mName,
1069 XO("Debug Output: "),
1070 mDebugOutput);
1071 dlog.CentreOnParent();
1072 dlog.ShowModal();
1073 }
1074
1075 // Has rug been pulled from under us by some effect done within Nyquist??
1076 if (!bOnePassTool && (nEffectsSoFar == EffectOutputTracks::nEffectsDone)) {
1077 if (success)
1078 oOutputs->Commit();
1079 }
1080 else {
1081 // Do not use the results.
1082 // Selection is to be set to whatever it is in the project.
1083 auto project = FindProject();
1084 if (project) {
1085 auto &selectedRegion = ViewInfo::Get( *project ).selectedRegion;
1086 mT0 = selectedRegion.t0();
1087 mT1 = selectedRegion.t1();
1088 }
1089 else {
1090 mT0 = 0;
1091 mT1 = -1;
1092 }
1093
1094 }
1095
1096 if (!mProjectChanged)
1097 em.SetSkipStateFlag(true);
1098
1099 return success;
1100}
1101
1103 wxWindow &parent, const EffectDialogFactory &factory,
1104 std::shared_ptr<EffectInstance> &pInstance, EffectSettingsAccess &access,
1105 bool forceModal)
1106{
1107 int res = wxID_APPLY;
1109 // Show the normal (prompt or effect) interface
1111 parent, factory, pInstance, access, forceModal);
1112 }
1113
1114
1115 // Remember if the user clicked debug
1116 mDebug = (res == eDebugID);
1117
1118 // We're done if the user clicked "Close", we are not the Nyquist Prompt,
1119 // or the program currently loaded into the prompt doesn't have a UI.
1120 if (!res || !mIsPrompt || mControls.size() == 0 || !pInstance)
1121 return res;
1122
1123 // Nyquist prompt was OK, but gave us some magic ;control comments to
1124 // reinterpret into a second dialog
1125
1127 effect.SetCommand(mInputCmd);
1128 Finally Do{[&]{
1129 // A second dialog will use effect as a pushed event handler.
1130 // wxWidgets delays window destruction until idle time.
1131 // Yield to destroy the dialog while effect is still in scope.
1133 }};
1134
1135 // Must give effect its own settings to interpret, not those in access
1136 // Let's also give it its own instance
1137 auto newSettings = effect.MakeSettings();
1138 auto pNewInstance = effect.MakeInstance();
1139 auto newAccess = std::make_shared<SimpleEffectSettingsAccess>(newSettings);
1140
1141 if (IsBatchProcessing()) {
1142 effect.SetBatchProcessing();
1143
1146 effect.LoadSettings(cp, newSettings);
1147
1148 // Show the normal (prompt or effect) interface
1149 // Don't pass this as first argument, pass the worker to itself
1150 res = effect.ShowHostInterface(effect,
1151 parent, factory, pNewInstance, *newAccess, forceModal);
1152 if (res) {
1154 effect.SaveSettings(newSettings, cp);
1156 }
1157 }
1158 else {
1159 if (!factory)
1160 return 0;
1161 // Don't pass this as first argument, pass the worker to itself
1162 res = effect.ShowHostInterface(effect,
1163 parent, factory, pNewInstance, *newAccess, false );
1164 if (!res)
1165 return 0;
1166
1167 // Wrap the new settings in the old settings
1169 auto &nyquistSettings = GetSettings(settings);
1170 nyquistSettings.proxySettings = std::move(newSettings);
1171 nyquistSettings.proxyDebug = this->mDebug;
1172 nyquistSettings.controls = move(effect.mControls);
1173 return nullptr;
1174 });
1175 }
1176 if (!pNewInstance)
1177 // Propagate the failure from nested ShowHostInterface
1178 pInstance.reset();
1179 return res;
1180}
1181
1182std::unique_ptr<EffectEditor> NyquistEffect::PopulateOrExchange(
1184 const EffectOutputs *)
1185{
1186 mUIParent = S.GetParent();
1187 if (mIsPrompt)
1189 else
1191 return nullptr;
1192}
1193
1195{
1196 return mDebugButton;
1197}
1198
1200{
1201 mUIParent->TransferDataToWindow();
1202
1203 bool success;
1204 if (mIsPrompt)
1205 {
1206 success = TransferDataToPromptWindow();
1207 }
1208 else
1209 {
1210 success = TransferDataToEffectWindow();
1211 }
1212
1213 if (success)
1214 {
1216 }
1217
1218 return success;
1219}
1220
1222{
1223 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
1224 {
1225 return false;
1226 }
1227
1228 if (mIsPrompt)
1229 {
1231 }
1233}
1234
1235namespace
1236{
1237wxString GetClipBoundaries(const Track* t)
1238{
1239 wxString clips;
1240 const auto wt = dynamic_cast<const WaveTrack*>(t);
1241 if (!wt)
1242 return clips;
1243 auto ca = wt->SortedIntervalArray();
1244 // Each clip is a list (start-time, end-time)
1245 // Limit number of clips added to avoid argument stack overflow error (bug
1246 // 2300).
1247 for (size_t i = 0, n = ca.size(); i < n; ++i)
1248 {
1249 if (i < 1000)
1250 {
1251 clips += wxString::Format(
1252 wxT("(list (float %s) (float %s))"),
1253 Internat::ToString(ca[i]->GetPlayStartTime()),
1254 Internat::ToString(ca[i]->GetPlayEndTime()));
1255 }
1256 else if (i == 1000)
1257 {
1258 // If final clip is NIL, plug-in developer knows there are more than
1259 // 1000 clips in channel.
1260 clips += "NIL";
1261 }
1262 else if (i > 1000)
1263 {
1264 break;
1265 }
1266 }
1267 return clips;
1268};
1269} // namespace
1270
1271// NyquistEffect implementation
1272
1274 NyxContext &nyxContext, EffectOutputTracks *pOutputs)
1275{
1276 const auto mCurNumChannels = nyxContext.mCurNumChannels;
1277 nyx_rval rval;
1278
1279 wxString cmd;
1280 cmd += wxT("(snd-set-latency 0.1)");
1281
1282 // A tool may be using AUD-DO which will potentially invalidate *TRACK*
1283 // so tools do not get *TRACK*.
1284 if (GetType() == EffectTypeTool)
1285 cmd += wxT("(setf S 0.25)\n"); // No Track.
1286 else if (mVersion >= 4) {
1287 nyx_set_audio_name("*TRACK*");
1288 cmd += wxT("(setf S 0.25)\n");
1289 }
1290 else {
1291 nyx_set_audio_name("S");
1292 cmd += wxT("(setf *TRACK* '*unbound*)\n");
1293 }
1294
1295 if(mVersion >= 4) {
1296 cmd += mProps;
1297 cmd += mPerTrackProps;
1298 }
1299
1300 const auto& mCurChannelGroup = nyxContext.mCurChannelGroup;
1301
1302 if( (mVersion >= 4) && (GetType() != EffectTypeTool) ) {
1303 // Set the track TYPE and VIEW properties
1304 wxString type;
1305 wxString view;
1306 wxString bitFormat;
1307 wxString spectralEditp;
1308
1309 mCurChannelGroup->TypeSwitch(
1310 [&](const WaveTrack &wt) {
1311 type = wxT("wave");
1312 spectralEditp = SpectrogramSettings::Get(*mCurChannelGroup)
1313 .SpectralSelectionEnabled()? wxT("T") : wxT("NIL");
1314 view = wxT("NIL");
1315 // Find() not Get() to avoid creation-on-demand of views in case we are
1316 // only previewing
1317 if (const auto pView = WaveChannelView::FindFirst(&wt)) {
1318 auto displays = pView->GetDisplays();
1319 auto format = [&]( decltype(displays[0]) display ) {
1320 // Get the English name of the view type, without menu codes,
1321 // as a string that Lisp can examine
1322 return wxString::Format( wxT("\"%s\""),
1323 display.name.Stripped().Debug() );
1324 };
1325 if (displays.empty())
1326 ;
1327 else if (displays.size() == 1)
1328 view = format( displays[0] );
1329 else {
1330 view = wxT("(list");
1331 for ( auto display : displays )
1332 view += wxString(wxT(" ")) + format( display );
1333 view += wxT(")");
1334 }
1335 }
1336 },
1337#if defined(USE_MIDI)
1338 [&](const NoteTrack &) {
1339 type = wxT("midi");
1340 view = wxT("\"Midi\"");
1341 },
1342#endif
1343 [&](const LabelTrack &) {
1344 type = wxT("label");
1345 view = wxT("\"Label\"");
1346 },
1347 [&](const TimeTrack &) {
1348 type = wxT("time");
1349 view = wxT("\"Time\"");
1350 }
1351 );
1352
1353 cmd += wxString::Format(wxT("(putprop '*TRACK* %d 'INDEX)\n"), ++mTrackIndex);
1354 cmd += wxString::Format(wxT("(putprop '*TRACK* \"%s\" 'NAME)\n"), EscapeString(mCurChannelGroup->GetName()));
1355 cmd += wxString::Format(wxT("(putprop '*TRACK* \"%s\" 'TYPE)\n"), type);
1356 // Note: "View" property may change when Audacity's choice of track views has stabilized.
1357 cmd += wxString::Format(wxT("(putprop '*TRACK* %s 'VIEW)\n"), view);
1358 cmd += wxString::Format(wxT("(putprop '*TRACK* %d 'CHANNELS)\n"), mCurNumChannels);
1359
1360 //NOTE: Audacity 2.1.3 True if spectral selection is enabled regardless of track view.
1361 cmd += wxString::Format(wxT("(putprop '*TRACK* %s 'SPECTRAL-EDIT-ENABLED)\n"), spectralEditp);
1362
1363 const double startTime = mCurChannelGroup->GetStartTime();
1364 const double endTime = mCurChannelGroup->GetEndTime();
1365
1366 cmd += wxString::Format(wxT("(putprop '*TRACK* (float %s) 'START-TIME)\n"),
1367 Internat::ToString(startTime));
1368 cmd += wxString::Format(wxT("(putprop '*TRACK* (float %s) 'END-TIME)\n"),
1369 Internat::ToString(endTime));
1370 cmd += wxString::Format(
1371 wxT("(putprop '*TRACK* (float %s) 'GAIN)\n"), // https://github.com/audacity/audacity/issues/7097:
1372 // not to break all
1373 // nyquist scripts out
1374 // there, we keep the old
1375 // name.
1376 Internat::ToString(mCurChannelGroup->GetVolume()));
1377 cmd += wxString::Format(wxT("(putprop '*TRACK* (float %s) 'PAN)\n"),
1378 Internat::ToString(mCurChannelGroup->GetPan()));
1379 cmd += wxString::Format(wxT("(putprop '*TRACK* (float %s) 'RATE)\n"),
1380 Internat::ToString(mCurChannelGroup->GetRate()));
1381
1382 switch (mCurChannelGroup->GetSampleFormat())
1383 {
1384 case int16Sample:
1385 bitFormat = wxT("16");
1386 break;
1387 case int24Sample:
1388 bitFormat = wxT("24");
1389 break;
1390 case floatSample:
1391 bitFormat = wxT("32.0");
1392 break;
1393 }
1394 cmd += wxString::Format(wxT("(putprop '*TRACK* %s 'FORMAT)\n"), bitFormat);
1395
1396 float maxPeakLevel = 0.0; // Deprecated as of 2.1.3
1397 const auto inClipBoundaries = GetClipBoundaries(
1398 pOutputs ? pOutputs->GetMatchingInput(*mCurChannelGroup) : nullptr);
1399 const auto outClipBoundaries = GetClipBoundaries(mCurChannelGroup);
1400 wxString inClips, outClips, peakString, rmsString;
1401 auto &mCurTrack = nyxContext.mCurTrack;
1402 for (size_t i = 0; i < mCurNumChannels; i++) {
1403 float maxPeak = 0.0;
1404 if (mCurNumChannels > 1)
1405 {
1406 inClips += wxT("(list ");
1407 outClips += wxT("(list ");
1408 }
1409 inClips += inClipBoundaries;
1410 outClips += outClipBoundaries;
1411 if (mCurNumChannels > 1)
1412 {
1413 inClips += wxT(" )");
1414 outClips += wxT(" )");
1415 }
1416 float min, max;
1417 auto pair =
1418 WaveChannelUtilities::GetMinMax(*mCurTrack[i], mT0, mT1); // may throw
1419 min = pair.first, max = pair.second;
1420 maxPeak = wxMax(wxMax(fabs(min), fabs(max)), maxPeak);
1421 maxPeakLevel = wxMax(maxPeakLevel, maxPeak);
1422
1423 // On Debian, NaN samples give maxPeak = 3.40282e+38 (FLT_MAX)
1424 if (!std::isinf(maxPeak) && !std::isnan(maxPeak) && (maxPeak < FLT_MAX)) {
1425 peakString += wxString::Format(wxT("(float %s) "), Internat::ToString(maxPeak));
1426 } else {
1427 peakString += wxT("nil ");
1428 }
1429
1430 float rms =
1431 WaveChannelUtilities::GetRMS(*mCurTrack[i], mT0, mT1); // may throw
1432 if (!std::isinf(rms) && !std::isnan(rms)) {
1433 rmsString += wxString::Format(wxT("(float %s) "), Internat::ToString(rms));
1434 } else {
1435 rmsString += wxT("NIL ");
1436 }
1437 }
1438 // A list of clips for mono, or an array of lists for multi-channel.
1439 cmd += wxString::Format(
1440 wxT("(putprop '*TRACK* %s%s ) 'INCLIPS)\n"),
1441 (mCurNumChannels == 1) ? wxT("(list ") : wxT("(vector "), inClips);
1442 cmd += wxString::Format(
1443 wxT("(putprop '*TRACK* %s%s ) 'CLIPS)\n"),
1444 (mCurNumChannels == 1) ? wxT("(list ") : wxT("(vector "), outClips);
1445
1446 (mCurNumChannels > 1)?
1447 cmd += wxString::Format(wxT("(putprop '*SELECTION* (vector %s) 'PEAK)\n"), peakString) :
1448 cmd += wxString::Format(wxT("(putprop '*SELECTION* %s 'PEAK)\n"), peakString);
1449
1450 if (!std::isinf(maxPeakLevel) && !std::isnan(maxPeakLevel) && (maxPeakLevel < FLT_MAX)) {
1451 cmd += wxString::Format(wxT("(putprop '*SELECTION* (float %s) 'PEAK-LEVEL)\n"),
1452 Internat::ToString(maxPeakLevel));
1453 }
1454
1455 (mCurNumChannels > 1)?
1456 cmd += wxString::Format(wxT("(putprop '*SELECTION* (vector %s) 'RMS)\n"), rmsString) :
1457 cmd += wxString::Format(wxT("(putprop '*SELECTION* %s 'RMS)\n"), rmsString);
1458 }
1459
1460 // If in tool mode, then we don't do anything with the track and selection.
1461 if (GetType() == EffectTypeTool)
1462 nyx_set_audio_params(44100, 0);
1463 else if (GetType() == EffectTypeGenerate)
1464 nyx_set_audio_params(mCurChannelGroup->GetRate(), 0);
1465 else {
1466 auto curLen = nyxContext.mCurLen.as_long_long();
1467 nyx_set_audio_params(mCurChannelGroup->GetRate(), curLen);
1468 nyx_set_input_audio(NyxContext::StaticGetCallback, &nyxContext,
1469 (int)mCurNumChannels, curLen, mCurChannelGroup->GetRate());
1470 }
1471
1472 // Restore the Nyquist sixteenth note symbol for Generate plug-ins.
1473 // See http://bugzilla.audacityteam.org/show_bug.cgi?id=490.
1474 if (GetType() == EffectTypeGenerate) {
1475 cmd += wxT("(setf s 0.25)\n");
1476 }
1477
1478 if (mDebug || mTrace) {
1479 cmd += wxT("(setf *tracenable* T)\n");
1480 if (mExternal) {
1481 cmd += wxT("(setf *breakenable* T)\n");
1482 }
1483 }
1484 else {
1485 // Explicitly disable backtrace and prevent values
1486 // from being carried through to the output.
1487 // This should be the final command before evaluating the Nyquist script.
1488 cmd += wxT("(setf *tracenable* NIL)\n");
1489 }
1490
1491 for (unsigned int j = 0; j < mControls.size(); j++) {
1492 if (mControls[j].type == NYQ_CTRL_FLOAT || mControls[j].type == NYQ_CTRL_FLOAT_TEXT ||
1493 mControls[j].type == NYQ_CTRL_TIME) {
1494 // We use Internat::ToString() rather than "%f" here because we
1495 // always have to use the dot as decimal separator when giving
1496 // numbers to Nyquist, whereas using "%f" will use the user's
1497 // decimal separator which may be a comma in some countries.
1498 cmd += wxString::Format(wxT("(setf %s %s)\n"),
1499 mControls[j].var,
1500 Internat::ToString(mControls[j].val, 14));
1501 }
1502 else if (mControls[j].type == NYQ_CTRL_INT ||
1503 mControls[j].type == NYQ_CTRL_INT_TEXT ||
1504 mControls[j].type == NYQ_CTRL_CHOICE) {
1505 cmd += wxString::Format(wxT("(setf %s %d)\n"),
1506 mControls[j].var,
1507 (int)(mControls[j].val));
1508 }
1509 else if (mControls[j].type == NYQ_CTRL_STRING || mControls[j].type == NYQ_CTRL_FILE) {
1510 cmd += wxT("(setf ");
1511 // restrict variable names to 7-bit ASCII:
1512 cmd += mControls[j].var;
1513 cmd += wxT(" \"");
1514 cmd += EscapeString(mControls[j].valStr); // unrestricted value will become quoted UTF-8
1515 cmd += wxT("\")\n");
1516 }
1517 }
1518
1519 if (mIsSal) {
1520 wxString str = EscapeString(mCmd);
1521 // this is tricky: we need SAL to call main so that we can get a
1522 // SAL traceback in the event of an error (sal-compile catches the
1523 // error and calls sal-error-output), but SAL does not return values.
1524 // We will catch the value in a special global aud:result and if no
1525 // error occurs, we will grab the value with a LISP expression
1526 str += wxT("\nset aud:result = main()\n");
1527
1528 if (mDebug || mTrace) {
1529 // since we're about to evaluate SAL, remove LISP trace enable and
1530 // break enable (which stops SAL processing) and turn on SAL stack
1531 // trace
1532 cmd += wxT("(setf *tracenable* nil)\n");
1533 cmd += wxT("(setf *breakenable* nil)\n");
1534 cmd += wxT("(setf *sal-traceback* t)\n");
1535 }
1536
1537 if (mCompiler) {
1538 cmd += wxT("(setf *sal-compiler-debug* t)\n");
1539 }
1540
1541 cmd += wxT("(setf *sal-call-stack* nil)\n");
1542 // if we do not set this here and an error occurs in main, another
1543 // error will be raised when we try to return the value of aud:result
1544 // which is unbound
1545 cmd += wxT("(setf aud:result nil)\n");
1546 cmd += wxT("(sal-compile-audacity \"") + str + wxT("\" t t nil)\n");
1547 // Capture the value returned by main (saved in aud:result), but
1548 // set aud:result to nil so sound results can be evaluated without
1549 // retaining audio in memory
1550 cmd += wxT("(prog1 aud:result (setf aud:result nil))\n");
1551 }
1552 else {
1553 cmd += mCmd;
1554 }
1555
1556 // Evaluate the expression, which may invoke the get callback, but often does
1557 // not, leaving that to delayed evaluation of the output sound
1558 rval = nyx_eval_expression(cmd.mb_str(wxConvUTF8));
1559
1560 // If we're not showing debug window, log errors and warnings:
1561 const auto output = mDebugOutput.Translation();
1562 if (!output.empty() && !mDebug && !mTrace) {
1563 /* i18n-hint: An effect "returned" a message.*/
1564 wxLogMessage(wxT("\'%s\' returned:\n%s"),
1565 mName.Translation(), output);
1566 }
1567
1568 // Audacity has no idea how long Nyquist processing will take, but
1569 // can monitor audio being returned.
1570 // Anything other than audio should be returned almost instantly
1571 // so notify the user that process has completed (bug 558)
1572 if ((rval != nyx_audio) && ((mCount + mCurNumChannels) == mNumSelectedChannels)) {
1573 if (mCurNumChannels == 1) {
1574 TrackProgress(mCount, 1.0, XO("Processing complete."));
1575 }
1576 else {
1577 TrackGroupProgress(mCount, 1.0, XO("Processing complete."));
1578 }
1579 }
1580
1581 if ((rval == nyx_audio) && (GetType() == EffectTypeTool)) {
1582 // Catch this first so that we can also handle other errors.
1583 mDebugOutput =
1584 /* i18n-hint: Don't translate ';type tool'. */
1585 XO("';type tool' effects cannot return audio from Nyquist.\n")
1586 + mDebugOutput;
1587 rval = nyx_error;
1588 }
1589
1590 if ((rval == nyx_labels) && (GetType() == EffectTypeTool)) {
1591 // Catch this first so that we can also handle other errors.
1592 mDebugOutput =
1593 /* i18n-hint: Don't translate ';type tool'. */
1594 XO("';type tool' effects cannot return labels from Nyquist.\n")
1595 + mDebugOutput;
1596 rval = nyx_error;
1597 }
1598
1599 if (rval == nyx_error) {
1600 // Return value is not valid type.
1601 // Show error in debug window if trace enabled, otherwise log.
1602 if (mTrace) {
1603 /* i18n-hint: "%s" is replaced by name of plug-in.*/
1604 mDebugOutput = XO("nyx_error returned from %s.\n")
1605 .Format( mName.empty() ? XO("plug-in") : mName )
1606 + mDebugOutput;
1607 mDebug = true;
1608 }
1609 else {
1610 wxLogMessage(
1611 "Nyquist returned nyx_error:\n%s", mDebugOutput.Translation());
1612 }
1613 return false;
1614 }
1615
1616 if (rval == nyx_list) {
1617 wxLogMessage("Nyquist returned nyx_list");
1618 if (GetType() == EffectTypeTool) {
1619 mProjectChanged = true;
1620 } else {
1622 XO("Nyquist returned a list.") );
1623 }
1624 return true;
1625 }
1626
1627 if (rval == nyx_string) {
1628 // Assume the string has already been translated within the Lisp runtime
1629 // if necessary, by one of the gettext functions defined below, before it
1630 // is communicated back to C++
1631 auto msg = Verbatim( NyquistToWxString(nyx_get_string()) );
1632 if (!msg.empty()) { // Empty string may be used as a No-Op return value.
1634 }
1635 else if (GetType() == EffectTypeTool) {
1636 // ;tools may change the project with aud-do commands so
1637 // it is essential that the state is added to history.
1638 mProjectChanged = true;
1639 return true;
1640 }
1641 else {
1642 // A true no-op.
1643 return true;
1644 }
1645
1646 // True if not process type.
1647 // If not returning audio from process effect,
1648 // return first result then stop (disables preview)
1649 // but allow all output from Nyquist Prompt.
1650 return (GetType() != EffectTypeProcess || mIsPrompt);
1651 }
1652
1653 if (rval == nyx_double) {
1654 auto str = XO("Nyquist returned the value: %f")
1655 .Format(nyx_get_double());
1657 return (GetType() != EffectTypeProcess || mIsPrompt);
1658 }
1659
1660 if (rval == nyx_int) {
1661 auto str = XO("Nyquist returned the value: %d")
1662 .Format(nyx_get_int());
1664 return (GetType() != EffectTypeProcess || mIsPrompt);
1665 }
1666
1667 if (rval == nyx_labels) {
1668 assert(GetType() != EffectTypeTool); // Guaranteed above
1669 // Therefore bOnePassTool was false in Process()
1670 // Therefore output tracks were allocated
1671 assert(pOutputs);
1672
1673 mProjectChanged = true;
1674 unsigned int numLabels = nyx_get_num_labels();
1675 unsigned int l;
1676 auto ltrack = *pOutputs->Get().Any<LabelTrack>().begin();
1677 if (!ltrack) {
1678 auto newTrack = std::make_shared<LabelTrack>();
1679 //new track name should be unique among the names in the list of input tracks, not output
1680 newTrack->SetName(inputTracks()->MakeUniqueTrackName(LabelTrack::GetDefaultName()));
1681 ltrack = static_cast<LabelTrack*>(
1682 pOutputs->AddToOutputTracks(newTrack));
1683 }
1684
1685 for (l = 0; l < numLabels; l++) {
1686 double t0, t1;
1687 const char *str;
1688
1689 // PRL: to do:
1690 // let Nyquist analyzers define more complicated selections
1691 nyx_get_label(l, &t0, &t1, &str);
1692
1693 ltrack->AddLabel(SelectedRegion(t0 + mT0, t1 + mT0), UTF8CTOWX(str));
1694 }
1695 return (GetType() != EffectTypeProcess || mIsPrompt);
1696 }
1697
1698 wxASSERT(rval == nyx_audio);
1699
1700 int outChannels = nyx_get_audio_num_channels();
1701 if (outChannels > (int)mCurNumChannels) {
1703 XO("Nyquist returned too many audio channels.\n"));
1704 return false;
1705 }
1706
1707 if (outChannels == -1) {
1709 XO("Nyquist returned one audio channel as an array.\n"));
1710 return false;
1711 }
1712
1713 if (outChannels == 0) {
1715 XO("Nyquist returned an empty array.\n"));
1716 return false;
1717 }
1718
1719 nyxContext.mOutputTrack = mCurChannelGroup->EmptyCopy();
1720 auto out = nyxContext.mOutputTrack;
1721
1722 // Now fully evaluate the sound
1723 int success = nyx_get_audio(NyxContext::StaticPutCallback, &nyxContext);
1724
1725 // See if GetCallback found read errors
1726 if (auto pException = nyxContext.mpException)
1727 std::rethrow_exception(pException);
1728
1729 if (!success)
1730 return false;
1731
1732 mOutputTime = out->GetEndTime();
1733 if (mOutputTime <= 0) {
1735 *this, XO("Nyquist returned nil audio.\n"));
1736 return false;
1737 }
1738
1739 WaveTrack::Holder tempTrack;
1740 if (outChannels < static_cast<int>(mCurNumChannels)) {
1741 // Be careful to do this before duplication
1742 out->Flush();
1743 // Must destroy one temporary list before repopulating another with
1744 // correct channel grouping
1745 nyxContext.mOutputTrack.reset();
1746 tempTrack = out->MonoToStereo();
1747 }
1748 else {
1749 tempTrack = move(nyxContext.mOutputTrack);
1750 out->Flush();
1751 }
1752
1753 {
1754 const bool bMergeClips = (mMergeClips < 0)
1755 // Use sample counts to determine default behaviour - times will rarely
1756 // be equal.
1757 ? (out->TimeToLongSamples(mT0) + out->TimeToLongSamples(mOutputTime)
1758 == out->TimeToLongSamples(mT1))
1759 : mMergeClips != 0;
1760 PasteTimeWarper warper { mT1, mT0 + tempTrack->GetEndTime() };
1761 mCurChannelGroup->ClearAndPaste(
1762 mT0, mT1, *tempTrack, mRestoreSplits, bMergeClips, &warper);
1763 }
1764
1765 // If we were first in the group adjust non-selected group tracks
1766 if (mFirstInGroup) {
1767 for (auto t : SyncLock::Group(*mCurChannelGroup))
1768 if (!t->GetSelected() && SyncLock::IsSyncLockSelected(*t))
1769 t->SyncLockAdjust(mT1, mT0 + out->GetEndTime());
1770
1771 // Only the first channel can be first in its group
1772 mFirstInGroup = false;
1773 }
1774
1775 mProjectChanged = true;
1776 return true;
1777}
1778
1779// ============================================================================
1780// NyquistEffect Implementation
1781// ============================================================================
1782
1783wxString NyquistEffect::NyquistToWxString(const char *nyqString)
1784{
1785 wxString str(nyqString, wxConvUTF8);
1786 if (nyqString != NULL && nyqString[0] && str.empty()) {
1787 // invalid UTF-8 string, convert as Latin-1
1788 str = _("[Warning: Nyquist returned invalid UTF-8 string, converted here as Latin-1]");
1789 // TODO: internationalization of strings from Nyquist effects, at least
1790 // from those shipped with Audacity
1791 str += LAT1CTOWX(nyqString);
1792 }
1793 return str;
1794}
1795
1796wxString NyquistEffect::EscapeString(const wxString & inStr)
1797{
1798 wxString str = inStr;
1799
1800 str.Replace(wxT("\\"), wxT("\\\\"));
1801 str.Replace(wxT("\""), wxT("\\\""));
1802
1803 return str;
1804}
1805
1806std::vector<EnumValueSymbol> NyquistEffect::ParseChoice(const wxString & text)
1807{
1808 std::vector<EnumValueSymbol> results;
1809 if (text[0] == wxT('(')) {
1810 // New style: expecting a Lisp-like list of strings
1811 Tokenizer tzer;
1812 tzer.Tokenize(text, true, 1, 1);
1813 auto &choices = tzer.tokens;
1814 wxString extra;
1815 for (auto &choice : choices) {
1816 auto label = UnQuote(choice, true, &extra);
1817 if (extra.empty())
1818 results.push_back( TranslatableString{ label, {} } );
1819 else
1820 results.push_back(
1821 { extra, TranslatableString{ label, {} } } );
1822 }
1823 }
1824 else {
1825 // Old style: expecting a comma-separated list of
1826 // un-internationalized names, ignoring leading and trailing spaces
1827 // on each; and the whole may be quoted
1828 auto choices = wxStringTokenize(
1829 text[0] == wxT('"') ? text.Mid(1, text.length() - 2) : text,
1830 wxT(",")
1831 );
1832 for (auto &choice : choices)
1833 results.push_back( { choice.Trim(true).Trim(false) } );
1834 }
1835 return results;
1836}
1837
1839{
1840 // todo: error handling
1841 FileExtensions results;
1842 if (text[0] == wxT('(')) {
1843 Tokenizer tzer;
1844 tzer.Tokenize(text, true, 1, 1);
1845 for (const auto &token : tzer.tokens)
1846 results.push_back( UnQuote( token ) );
1847 }
1848 return results;
1849}
1850
1852{
1853 // todo: error handling
1854 FileNames::FileType result;
1855 if (text[0] == wxT('(')) {
1856 Tokenizer tzer;
1857 tzer.Tokenize(text, true, 1, 1);
1858 auto &tokens = tzer.tokens;
1859 if ( tokens.size() == 2 )
1860 result =
1861 { UnQuoteMsgid( tokens[0] ), ParseFileExtensions( tokens[1] ) };
1862 }
1863 return result;
1864}
1865
1867{
1868 // todo: error handling
1869 FileNames::FileTypes results;
1870 if (text[0] == wxT('(')) {
1871 Tokenizer tzer;
1872 tzer.Tokenize(text, true, 1, 1);
1873 auto &types = tzer.tokens;
1874 if ( !types.empty() && types[0][0] == wxT('(') )
1875 for (auto &type : types)
1876 results.push_back( ParseFileType( type ) );
1877 }
1878 if ( results.empty() ) {
1879 // Old-style is a specially formatted string, maybe translated
1880 // Parse it for compatibility
1881 auto str = UnQuote( text );
1882 auto pieces = wxSplit( str, '|' );
1883 // Should have an even number
1884 auto size = pieces.size();
1885 if ( size % 2 == 1 )
1886 --size, pieces.pop_back();
1887 for ( size_t ii = 0; ii < size; ii += 2 ) {
1888 FileExtensions extensions;
1889 auto extensionStrings = wxSplit( pieces[ii + 1], ';' );
1890 for ( const auto &extensionString : extensionStrings )
1891 if ( extensionString.StartsWith( wxT("*.") ) ) {
1892 auto ext = extensionString.substr( 2 );
1893 if (ext == wxT("*"))
1894 // "*.*" to match all
1895 ext.clear();
1896 extensions.push_back( ext );
1897 }
1898 results.push_back( { Verbatim( pieces[ii] ), extensions } );
1899 }
1900 }
1901 return results;
1902}
1903
1905{
1906 mRedirectOutput = true;
1907}
1908
1909void NyquistEffect::SetCommand(const wxString &cmd)
1910{
1911 mExternal = true;
1912
1913 if (cmd.size()) {
1914 ParseCommand(cmd);
1915 }
1916}
1917
1919{
1920 mBreak = true;
1921}
1922
1924{
1925 mCont = true;
1926}
1927
1929{
1930 mStop = true;
1931}
1932
1933TranslatableString NyquistEffect::UnQuoteMsgid(const wxString &s, bool allowParens,
1934 wxString *pExtraString)
1935{
1936 if (pExtraString)
1937 *pExtraString = wxString{};
1938
1939 int len = s.length();
1940 if (len >= 2 && s[0] == wxT('\"') && s[len - 1] == wxT('\"')) {
1941 auto unquoted = s.Mid(1, len - 2);
1942 // Sorry, no context strings, yet
1943 // (See also comments in NyquistEffectsModule::AutoRegisterPlugins)
1944 return TranslatableString{ unquoted, {} };
1945 }
1946 else if (allowParens &&
1947 len >= 2 && s[0] == wxT('(') && s[len - 1] == wxT(')')) {
1948 Tokenizer tzer;
1949 tzer.Tokenize(s, true, 1, 1);
1950 auto &tokens = tzer.tokens;
1951 if (tokens.size() > 1) {
1952 if (pExtraString && tokens[1][0] == '(') {
1953 // A choice with a distinct internal string form like
1954 // ("InternalString" (_ "Visible string"))
1955 // Recur to find the two strings
1956 *pExtraString = UnQuote(tokens[0], false);
1957 return UnQuoteMsgid(tokens[1]);
1958 }
1959 else {
1960 // Assume the first token was _ -- we don't check that
1961 // And the second is the string, which is internationalized
1962 // Sorry, no context strings, yet
1963 return UnQuoteMsgid( tokens[1], false );
1964 }
1965 }
1966 else
1967 return {};
1968 }
1969 else
1970 // If string was not quoted, assume no translation exists
1971 return Verbatim( s );
1972}
1973
1974wxString NyquistEffect::UnQuote(const wxString &s, bool allowParens,
1975 wxString *pExtraString)
1976{
1977 return UnQuoteMsgid( s, allowParens, pExtraString ).Translation();
1978}
1979
1980double NyquistEffect::GetCtrlValue(const wxString &s)
1981{
1982 /* For this to work correctly requires that the plug-in header is
1983 * parsed on each run so that the correct value for "half-srate" may
1984 * be determined.
1985 *
1986 auto project = FindProject();
1987 if (project && s.IsSameAs(wxT("half-srate"), false)) {
1988 auto rate =
1989 TrackList::Get( *project ).Selected< const WaveTrack >()
1990 .min( &WaveTrack::GetRate );
1991 return (rate / 2.0);
1992 }
1993 */
1994
1996}
1997
1999 const wxString &line, bool eof,
2000 size_t trimStart, size_t trimEnd)
2001{
2002 auto endToken = [&]{
2003 if (!tok.empty()) {
2004 tokens.push_back(tok);
2005 tok = wxT("");
2006 }
2007 };
2008
2009 for (auto c :
2010 make_iterator_range(line.begin() + trimStart, line.end() - trimEnd)) {
2011 if (q && !sl && c == wxT('\\')) {
2012 // begin escaped character, only within quotes
2013 sl = true;
2014 continue;
2015 }
2016
2017 if (!sl && c == wxT('"')) {
2018 // Unescaped quote
2019 if (!q) {
2020 // start of string
2021 if (!paren)
2022 // finish previous token
2023 endToken();
2024 // Include the delimiter in the token
2025 tok += c;
2026 q = true;
2027 }
2028 else {
2029 // end of string
2030 // Include the delimiter in the token
2031 tok += c;
2032 if (!paren)
2033 endToken();
2034 q = false;
2035 }
2036 }
2037 else if (!q && !paren && (c == wxT(' ') || c == wxT('\t')))
2038 // Unenclosed whitespace
2039 // Separate tokens; don't accumulate this character
2040 endToken();
2041 else if (!q && c == wxT(';'))
2042 // semicolon not in quotes, but maybe in parentheses
2043 // Lisp style comments with ; (but not with #| ... |#) are allowed
2044 // within a wrapped header multi-line, so that i18n hint comments may
2045 // be placed before strings and found by xgettext
2046 break;
2047 else if (!q && c == wxT('(')) {
2048 // Start of list or sublist
2049 if (++paren == 1)
2050 // finish previous token; begin list, including the delimiter
2051 endToken(), tok += c;
2052 else
2053 // defer tokenizing of nested list to a later pass over the token
2054 tok += c;
2055 }
2056 else if (!q && c == wxT(')')) {
2057 // End of list or sublist
2058 if (--paren == 0)
2059 // finish list, including the delimiter
2060 tok += c, endToken();
2061 else if (paren < 0)
2062 // forgive unbalanced right paren
2063 paren = 0, endToken();
2064 else
2065 // nested list; deferred tokenizing
2066 tok += c;
2067 }
2068 else {
2069 if (sl && paren)
2070 // Escaped character in string inside list, to be parsed again
2071 // Put the escape back for the next pass
2072 tok += wxT('\\');
2073 if (sl && !paren && c == 'n')
2074 // Convert \n to newline, the only special escape besides \\ or \"
2075 // But this should not be used if a string needs to localize.
2076 // Instead, simply put a line break in the string.
2077 c = '\n';
2078 tok += c;
2079 }
2080
2081 sl = false;
2082 }
2083
2084 if (eof || (!q && !paren)) {
2085 endToken();
2086 return true;
2087 }
2088 else {
2089 // End of line but not of file, and a string or list is yet unclosed
2090 // If a string, accumulate a newline character
2091 if (q)
2092 tok += wxT('\n');
2093 return false;
2094 }
2095}
2096
2098 Tokenizer &tzer, const wxString &line, bool eof, bool first)
2099{
2100 if ( !tzer.Tokenize(line, eof, first ? 1 : 0, 0) )
2101 return false;
2102
2103 const auto &tokens = tzer.tokens;
2104 int len = tokens.size();
2105 if (len < 1) {
2106 return true;
2107 }
2108
2109 // Consistency decision is for "plug-in" as the correct spelling
2110 // "plugin" (deprecated) is allowed as an undocumented convenience.
2111 if (len == 2 && tokens[0] == wxT("nyquist") &&
2112 (tokens[1] == wxT("plug-in") || tokens[1] == wxT("plugin"))) {
2113 mOK = true;
2114 return true;
2115 }
2116
2117 if (len >= 2 && tokens[0] == wxT("type")) {
2118 wxString tok = tokens[1];
2119 mIsTool = false;
2120 if (tok == wxT("tool")) {
2121 mIsTool = true;
2123 // we allow
2124 // ;type tool
2125 // ;type tool process
2126 // ;type tool generate
2127 // ;type tool analyze
2128 // The last three are placed in the tool menu, but are processed as
2129 // process, generate or analyze.
2130 if (len >= 3)
2131 tok = tokens[2];
2132 }
2133
2134 if (tok == wxT("process")) {
2136 }
2137 else if (tok == wxT("generate")) {
2139 }
2140 else if (tok == wxT("analyze")) {
2142 }
2143
2144 if (len >= 3 && tokens[2] == wxT("spectral")) {;
2145 mIsSpectral = true;
2146 }
2147 return true;
2148 }
2149
2150 if (len == 2 && tokens[0] == wxT("codetype")) {
2151 // This will stop ParseProgram() from doing a best guess as program type.
2152 if (tokens[1] == wxT("lisp")) {
2153 mIsSal = false;
2154 mFoundType = true;
2155 }
2156 else if (tokens[1] == wxT("sal")) {
2157 mIsSal = true;
2158 mFoundType = true;
2159 }
2160 return true;
2161 }
2162
2163 if (len >= 2 && tokens[0] == wxT("debugflags")) {
2164 for (int i = 1; i < len; i++) {
2165 // "trace" sets *tracenable* (LISP) or *sal-traceback* (SAL)
2166 // and displays debug window IF there is anything to show.
2167 if (tokens[i] == wxT("trace")) {
2168 mTrace = true;
2169 }
2170 else if (tokens[i] == wxT("notrace")) {
2171 mTrace = false;
2172 }
2173 else if (tokens[i] == wxT("compiler")) {
2174 mCompiler = true;
2175 }
2176 else if (tokens[i] == wxT("nocompiler")) {
2177 mCompiler = false;
2178 }
2179 }
2180 return true;
2181 }
2182
2183 // We support versions 1, 2 and 3
2184 // (Version 2 added support for string parameters.)
2185 // (Version 3 added support for choice parameters.)
2186 // (Version 4 added support for project/track/selection information.)
2187 if (len >= 2 && tokens[0] == wxT("version")) {
2188 long v;
2189 tokens[1].ToLong(&v);
2190 if (v < 1 || v > 4) {
2191 // This is an unsupported plug-in version
2192 mOK = false;
2193 mInitError = XO(
2194"This version of Audacity does not support Nyquist plug-in version %ld")
2195 .Format( v );
2196 return true;
2197 }
2198 mVersion = (int) v;
2199 }
2200
2201 if (len >= 2 && tokens[0] == wxT("name")) {
2202 // Names do not yet support context strings for translations, or
2203 // internal names distinct from visible English names.
2204 // (See also comments in NyquistEffectsModule::AutoRegisterPlugins)
2205 auto name = UnQuote(tokens[1]);
2206 // Strip ... from name if it's present, perhaps in third party plug-ins
2207 // Menu system puts ... back if there are any controls
2208 // This redundant naming convention must NOT be followed for
2209 // shipped Nyquist effects with internationalization. Else the msgid
2210 // later looked up will lack the ... and will not be found.
2211 if (name.EndsWith(wxT("...")))
2212 name = name.RemoveLast(3);
2213 mName = TranslatableString{ name, {} };
2214 return true;
2215 }
2216
2217 if (len >= 2 && tokens[0] == wxT("action")) {
2218 mAction = TranslatableString{ UnQuote(tokens[1]), {} };
2219 return true;
2220 }
2221
2222 if (len >= 2 && tokens[0] == wxT("info")) {
2223 mInfo = TranslatableString{ UnQuote(tokens[1]), {} };
2224 return true;
2225 }
2226
2227 if (len >= 2 && tokens[0] == wxT("preview")) {
2228 if (tokens[1] == wxT("enabled") || tokens[1] == wxT("true")) {
2229 mEnablePreview = true;
2230 SetLinearEffectFlag(false);
2231 }
2232 else if (tokens[1] == wxT("linear")) {
2233 mEnablePreview = true;
2234 SetLinearEffectFlag(true);
2235 }
2236 else if (tokens[1] == wxT("selection")) {
2237 mEnablePreview = true;
2239 }
2240 else if (tokens[1] == wxT("disabled") || tokens[1] == wxT("false")) {
2241 mEnablePreview = false;
2242 }
2243 return true;
2244 }
2245
2246 // Maximum number of samples to be processed. This can help the
2247 // progress bar if effect does not process all of selection.
2248 if (len >= 2 && tokens[0] == wxT("maxlen")) {
2249 long long v; // Note that Nyquist may overflow at > 2^31 samples (bug 439)
2250 tokens[1].ToLongLong(&v);
2251 mMaxLen = (sampleCount) v;
2252 }
2253
2254 if (len >= 2 && tokens[0] == wxT("mergeclips")) {
2255 long v;
2256 // -1 = auto (default), 0 = don't merge clips, 1 = do merge clips
2257 tokens[1].ToLong(&v);
2258 mMergeClips = v;
2259 return true;
2260 }
2261
2262 if (len >= 2 && tokens[0] == wxT("restoresplits")) {
2263 long v;
2264 // Splits are restored by default. Set to 0 to prevent.
2265 tokens[1].ToLong(&v);
2266 mRestoreSplits = !!v;
2267 return true;
2268 }
2269
2270 if (len >= 2 && tokens[0] == wxT("author")) {
2271 mAuthor = TranslatableString{ UnQuote(tokens[1]), {} };
2272 return true;
2273 }
2274
2275 if (len >= 2 && tokens[0] == wxT("release")) {
2276 // Value must be quoted if the release version string contains spaces.
2278 TranslatableString{ UnQuote(tokens[1]), {} };
2279 return true;
2280 }
2281
2282 if (len >= 2 && tokens[0] == wxT("copyright")) {
2283 mCopyright = TranslatableString{ UnQuote(tokens[1]), {} };
2284 return true;
2285 }
2286
2287 // Page name in Audacity development manual
2288 if (len >= 2 && tokens[0] == wxT("manpage")) {
2289 // do not translate
2290 mManPage = UnQuote(tokens[1], false);
2291 return true;
2292 }
2293
2294 // Local Help file
2295 if (len >= 2 && tokens[0] == wxT("helpfile")) {
2296 // do not translate
2297 mHelpFile = UnQuote(tokens[1], false);
2298 return true;
2299 }
2300
2301 // Debug button may be disabled for release plug-ins.
2302 if (len >= 2 && tokens[0] == wxT("debugbutton")) {
2303 if (tokens[1] == wxT("disabled") || tokens[1] == wxT("false")) {
2304 mDebugButton = false;
2305 }
2306 return true;
2307 }
2308
2309
2310 if (len >= 3 && tokens[0] == wxT("control")) {
2311 NyqControl ctrl;
2312
2313 if (len == 3 && tokens[1] == wxT("text")) {
2314 ctrl.var = tokens[1];
2315 ctrl.label = UnQuote( tokens[2] );
2316 ctrl.type = NYQ_CTRL_TEXT;
2317 }
2318 else if (len >= 5)
2319 {
2320 ctrl.var = tokens[1];
2321 ctrl.name = UnQuote( tokens[2] );
2322 // 3 is type, below
2323 ctrl.label = tokens[4];
2324
2325 // valStr may or may not be a quoted string
2326 ctrl.valStr = len > 5 ? tokens[5] : wxString{};
2327 ctrl.val = GetCtrlValue(ctrl.valStr);
2328 if (ctrl.valStr.length() > 0 &&
2329 (ctrl.valStr[0] == wxT('(') ||
2330 ctrl.valStr[0] == wxT('"')))
2331 ctrl.valStr = UnQuote( ctrl.valStr );
2332
2333 // 6 is minimum, below
2334 // 7 is maximum, below
2335
2336 if (tokens[3] == wxT("string")) {
2337 ctrl.type = NYQ_CTRL_STRING;
2338 ctrl.label = UnQuote( ctrl.label );
2339 }
2340 else if (tokens[3] == wxT("choice")) {
2341 ctrl.type = NYQ_CTRL_CHOICE;
2342 ctrl.choices = ParseChoice(ctrl.label);
2343 ctrl.label = wxT("");
2344 }
2345 else if (tokens[3] == wxT("file")) {
2346 ctrl.type = NYQ_CTRL_FILE;
2347 ctrl.fileTypes = ParseFileTypes(tokens[6]);
2348 // will determine file dialog styles:
2349 ctrl.highStr = UnQuote( tokens[7] );
2350 ctrl.label = UnQuote(ctrl.label);
2351 }
2352 else {
2353 ctrl.label = UnQuote( ctrl.label );
2354
2355 if (len < 8) {
2356 return true;
2357 }
2358
2359 if ((tokens[3] == wxT("float")) ||
2360 (tokens[3] == wxT("real"))) // Deprecated
2361 ctrl.type = NYQ_CTRL_FLOAT;
2362 else if (tokens[3] == wxT("int"))
2363 ctrl.type = NYQ_CTRL_INT;
2364 else if (tokens[3] == wxT("float-text"))
2366 else if (tokens[3] == wxT("int-text"))
2367 ctrl.type = NYQ_CTRL_INT_TEXT;
2368 else if (tokens[3] == wxT("time"))
2369 ctrl.type = NYQ_CTRL_TIME;
2370 else
2371 {
2372 wxString str;
2373 str.Printf(wxT("Bad Nyquist 'control' type specification: '%s' in plug-in file '%s'.\nControl not created."),
2374 tokens[3], mFileName.GetFullPath());
2375
2376 // Too disturbing to show alert before Audacity frame is up.
2377 // EffectUIServices::DoMessageBox(*this,
2378 // str,
2379 // wxOK | wxICON_EXCLAMATION,
2380 // XO("Nyquist Warning") );
2381
2382 // Note that the AudacityApp's mLogger has not yet been created,
2383 // so this brings up an alert box, but after the Audacity frame is up.
2384 wxLogWarning(str);
2385 return true;
2386 }
2387
2388 ctrl.lowStr = UnQuote( tokens[6] );
2389 if (ctrl.type == NYQ_CTRL_INT_TEXT && ctrl.lowStr.IsSameAs(wxT("nil"), false)) {
2390 ctrl.low = INT_MIN;
2391 }
2392 else if (ctrl.type == NYQ_CTRL_FLOAT_TEXT && ctrl.lowStr.IsSameAs(wxT("nil"), false)) {
2393 ctrl.low = -(FLT_MAX);
2394 }
2395 else if (ctrl.type == NYQ_CTRL_TIME && ctrl.lowStr.IsSameAs(wxT("nil"), false)) {
2396 ctrl.low = 0.0;
2397 }
2398 else {
2399 ctrl.low = GetCtrlValue(ctrl.lowStr);
2400 }
2401
2402 ctrl.highStr = UnQuote( tokens[7] );
2403 if (ctrl.type == NYQ_CTRL_INT_TEXT && ctrl.highStr.IsSameAs(wxT("nil"), false)) {
2404 ctrl.high = INT_MAX;
2405 }
2406 else if ((ctrl.type == NYQ_CTRL_FLOAT_TEXT || ctrl.type == NYQ_CTRL_TIME) &&
2407 ctrl.highStr.IsSameAs(wxT("nil"), false))
2408 {
2409 ctrl.high = FLT_MAX;
2410 }
2411 else {
2412 ctrl.high = GetCtrlValue(ctrl.highStr);
2413 }
2414
2415 if (ctrl.high < ctrl.low) {
2416 ctrl.high = ctrl.low;
2417 }
2418
2419 if (ctrl.val < ctrl.low) {
2420 ctrl.val = ctrl.low;
2421 }
2422
2423 if (ctrl.val > ctrl.high) {
2424 ctrl.val = ctrl.high;
2425 }
2426
2427 ctrl.ticks = 1000;
2428 if (ctrl.type == NYQ_CTRL_INT &&
2429 (ctrl.high - ctrl.low < ctrl.ticks)) {
2430 ctrl.ticks = (int)(ctrl.high - ctrl.low);
2431 }
2432 }
2433 }
2434
2435 if( ! make_iterator_range( mPresetNames ).contains( ctrl.var ) )
2436 {
2437 mControls.push_back(ctrl);
2438 }
2439 }
2440
2441 // Deprecated
2442 if (len >= 2 && tokens[0] == wxT("categories")) {
2443 for (size_t i = 1; i < tokens.size(); ++i) {
2444 mCategories.push_back(tokens[i]);
2445 }
2446 }
2447 return true;
2448}
2449
2450bool NyquistEffect::ParseProgram(wxInputStream & stream)
2451{
2452 if (!stream.IsOk())
2453 {
2454 mInitError = XO("Could not open file");
2455 return false;
2456 }
2457
2458 wxTextInputStream pgm(stream, wxT(" \t"), wxConvAuto());
2459
2460 mCmd = wxT("");
2461 mCmd.Alloc(10000);
2462 mIsSal = false;
2463 mControls.clear();
2464 mCategories.clear();
2465 mIsSpectral = false;
2466 mManPage = wxEmptyString; // If not wxEmptyString, must be a page in the Audacity manual.
2467 mHelpFile = wxEmptyString; // If not wxEmptyString, must be a valid HTML help file.
2468 mHelpFileExists = false;
2469 mDebug = false;
2470 mTrace = false;
2471 mDebugButton = true; // Debug button enabled by default.
2472 mEnablePreview = true; // Preview button enabled by default.
2473
2474 // Bug 1934.
2475 // All Nyquist plug-ins should have a ';type' field, but if they don't we default to
2476 // being an Effect.
2478
2479 mFoundType = false;
2480 while (!stream.Eof() && stream.IsOk())
2481 {
2482 wxString line = pgm.ReadLine();
2483 if (line.length() > 1 &&
2484 // New in 2.3.0: allow magic comment lines to start with $
2485 // The trick is that xgettext will not consider such lines comments
2486 // and will extract the strings they contain
2487 (line[0] == wxT(';') || line[0] == wxT('$')) )
2488 {
2489 Tokenizer tzer;
2490 unsigned nLines = 1;
2491 bool done;
2492 // Allow continuations within control lines.
2493 bool control =
2494 line[0] == wxT('$') || line.StartsWith( wxT(";control") );
2495 do
2496 done = Parse(tzer, line, !control || stream.Eof(), nLines == 1);
2497 while(!done &&
2498 (line = pgm.ReadLine(), ++nLines, true));
2499
2500 // Don't pass these lines to the interpreter, so it doesn't get confused
2501 // by $, but pass blanks,
2502 // so that SAL effects compile with proper line numbers
2503 while (nLines --)
2504 mCmd += wxT('\n');
2505 }
2506 else
2507 {
2508 if(!mFoundType && line.length() > 0) {
2509 if (line[0] == wxT('(') ||
2510 (line[0] == wxT('#') && line.length() > 1 && line[1] == wxT('|')))
2511 {
2512 mIsSal = false;
2513 mFoundType = true;
2514 }
2515 else if (line.Upper().Find(wxT("RETURN")) != wxNOT_FOUND)
2516 {
2517 mIsSal = true;
2518 mFoundType = true;
2519 }
2520 }
2521 mCmd += line + wxT("\n");
2522 }
2523 }
2524 if (!mFoundType && mIsPrompt)
2525 {
2526 /* i1n-hint: SAL and LISP are names for variant syntaxes for the
2527 Nyquist programming language. Leave them, and 'return', untranslated. */
2529 XO(
2530"Your code looks like SAL syntax, but there is no \'return\' statement.\n\
2531For SAL, use a return statement such as:\n\treturn *track* * 0.1\n\
2532or for LISP, begin with an open parenthesis such as:\n\t(mult *track* 0.1)\n ."),
2534 XO("Error in Nyquist code") );
2535 /* i18n-hint: refers to programming "languages" */
2536 mInitError = XO("Could not determine language");
2537 return false;
2538 // Else just throw it at Nyquist to see what happens
2539 }
2540
2541 const auto helpStuff = CheckHelpPage();
2542 mHelpFileExists = helpStuff.first;
2543 mHelpPage = helpStuff.second;
2544
2545 return true;
2546}
2547
2549{
2550 wxFileInputStream rawStream(mFileName.GetFullPath());
2551 wxBufferedInputStream stream(rawStream, 10000);
2552
2553 ParseProgram(stream);
2554}
2555
2556bool NyquistEffect::ParseCommand(const wxString & cmd)
2557{
2558 wxStringInputStream stream(cmd + wxT(" "));
2559
2560 return ParseProgram(stream);
2561}
2562
2564 int64_t start, int64_t len, int64_t totlen, void *userdata)
2565{
2566 auto This = static_cast<NyxContext*>(userdata);
2567 return This->GetCallback(buffer, channel, start, len, totlen);
2568}
2569
2571 int64_t start, int64_t len, int64_t)
2572{
2573 if (mCurBuffer[ch]) {
2574 if ((mCurStart + start) < mCurBufferStart[ch] ||
2575 (mCurStart + start) + len >
2576 mCurBufferStart[ch] + mCurBufferLen[ch]) {
2577 mCurBuffer[ch].reset();
2578 }
2579 }
2580
2581 if (!mCurBuffer[ch]) {
2582 mCurBufferStart[ch] = (mCurStart + start);
2583 mCurBufferLen[ch] = mCurTrack[ch]->GetBestBlockSize(mCurBufferStart[ch]);
2584
2585 if (mCurBufferLen[ch] < (size_t) len)
2586 mCurBufferLen[ch] = mCurTrack[ch]->GetIdealBlockSize();
2587
2588 mCurBufferLen[ch] = limitSampleBufferSize(mCurBufferLen[ch],
2589 mCurStart + mCurLen - mCurBufferStart[ch]);
2590
2591 // C++20
2592 // mCurBuffer[ch] = std::make_unique_for_overwrite(mCurBufferLen[ch]);
2593 mCurBuffer[ch] = Buffer{ safenew float[ mCurBufferLen[ch] ] };
2594 try {
2595 mCurTrack[ch]->GetFloats( mCurBuffer[ch].get(),
2596 mCurBufferStart[ch], mCurBufferLen[ch]);
2597 }
2598 catch ( ... ) {
2599 // Save the exception object for re-throw when out of the library
2600 mpException = std::current_exception();
2601 return -1;
2602 }
2603 }
2604
2605 // We have guaranteed above that this is nonnegative and bounded by
2606 // mCurBufferLen[ch]:
2607 auto offset = (mCurStart + start - mCurBufferStart[ch]).as_size_t();
2608 const void *src = &mCurBuffer[ch][offset];
2609 std::memcpy(buffer, src, len * sizeof(float));
2610
2611 if (ch == 0) {
2612 double progress = mScale * ((start + len) / mCurLen.as_double());
2613 if (progress > mProgressIn)
2614 mProgressIn = progress;
2615 if (mProgressReport(mProgressIn + mProgressOut + mProgressTot))
2616 return -1;
2617 }
2618
2619 return 0;
2620}
2621
2623 int64_t start, int64_t len, int64_t totlen, void *userdata)
2624{
2625 auto This = static_cast<NyxContext*>(userdata);
2626 return This->PutCallback(buffer, channel, start, len, totlen);
2627}
2628
2629int NyquistEffect::NyxContext::PutCallback(float *buffer, int channel,
2630 int64_t start, int64_t len, int64_t totlen)
2631{
2632 // Don't let C++ exceptions propagate through the Nyquist library
2633 return GuardedCall<int>( [&] {
2634 if (channel == 0) {
2635 double progress = mScale * ((float)(start + len) / totlen);
2636 if (progress > mProgressOut)
2637 mProgressOut = progress;
2638 if (mProgressReport(mProgressIn + mProgressOut + mProgressTot))
2639 return -1;
2640 }
2641
2642 auto iChannel = mOutputTrack->Channels().begin();
2643 std::advance(iChannel, channel);
2644 const auto pChannel = *iChannel;
2645 pChannel->Append((samplePtr)buffer, floatSample, len);
2646
2647 return 0; // success
2648 }, MakeSimpleGuard(-1)); // translate all exceptions into failure
2649}
2650
2652{
2653 ((NyquistEffect *)This)->OutputCallback(c);
2654}
2655
2657{
2658 // Always collect Nyquist error messages for normal plug-ins
2659 if (!mRedirectOutput) {
2660 mDebugOutputStr += (wxChar)c;
2661 return;
2662 }
2663
2664 std::cout << (char)c;
2665}
2666
2668{
2669 ((NyquistEffect *)This)->OSCallback();
2670}
2671
2673{
2674 if (mStop) {
2675 mStop = false;
2676 nyx_stop();
2677 }
2678 else if (mBreak) {
2679 mBreak = false;
2680 nyx_break();
2681 }
2682 else if (mCont) {
2683 mCont = false;
2684 nyx_continue();
2685 }
2686
2687 // LLL: STF figured out that yielding while the effect is being applied
2688 // produces an EXTREME slowdown. It appears that yielding is not
2689 // really necessary on Linux and Windows.
2690 //
2691 // However, on the Mac, the spinning cursor appears during longer
2692 // Nyquist processing and that may cause the user to think Audacity
2693 // has crashed or hung. In addition, yielding or not on the Mac
2694 // doesn't seem to make much of a difference in execution time.
2695 //
2696 // So, yielding on the Mac only...
2697#if defined(__WXMAC__)
2698 wxYieldIfNeeded();
2699#endif
2700}
2701
2703{
2704 const auto &audacityPathList = FileNames::AudacityPathList();
2705 FilePaths pathList;
2706
2707 for (size_t i = 0; i < audacityPathList.size(); i++)
2708 {
2709 wxString prefix = audacityPathList[i] + wxFILE_SEP_PATH;
2710 FileNames::AddUniquePathToPathList(prefix + wxT("nyquist"), pathList);
2711 FileNames::AddUniquePathToPathList(prefix + wxT("plugins"), pathList);
2712 FileNames::AddUniquePathToPathList(prefix + wxT("plug-ins"), pathList);
2713 }
2714 pathList.push_back(FileNames::PlugInDir());
2715
2716 return pathList;
2717}
2718
2720{
2721 mCommandText->ChangeValue(mInputCmd);
2722
2723 return true;
2724}
2725
2727{
2728 for (size_t i = 0, cnt = mControls.size(); i < cnt; i++)
2729 {
2730 NyqControl & ctrl = mControls[i];
2731
2732 if (ctrl.type == NYQ_CTRL_CHOICE)
2733 {
2734 const auto count = ctrl.choices.size();
2735
2736 int val = (int)ctrl.val;
2737 if (val < 0 || val >= (int)count)
2738 {
2739 val = 0;
2740 }
2741
2742 wxChoice *c = (wxChoice *) mUIParent->FindWindow(ID_Choice + i);
2743 c->SetSelection(val);
2744 }
2745 else if (ctrl.type == NYQ_CTRL_INT || ctrl.type == NYQ_CTRL_FLOAT)
2746 {
2747 // wxTextCtrls are handled by the validators
2748 double range = ctrl.high - ctrl.low;
2749 int val = (int)(0.5 + ctrl.ticks * (ctrl.val - ctrl.low) / range);
2750 wxSlider *s = (wxSlider *) mUIParent->FindWindow(ID_Slider + i);
2751 s->SetValue(val);
2752 }
2753 else if (ctrl.type == NYQ_CTRL_TIME)
2754 {
2755 NumericTextCtrl *n = (NumericTextCtrl *) mUIParent->FindWindow(ID_Time + i);
2756 n->SetValue(ctrl.val);
2757 }
2758 }
2759
2760 return true;
2761}
2762
2764{
2765 mInputCmd = mCommandText->GetValue();
2766
2767 // Un-correct smart quoting, bothersomely applied in wxTextCtrl by
2768 // the native widget of MacOS 10.9 SDK
2769 const wxString left = wxT("\u201c"), right = wxT("\u201d"), dumb = '"';
2770 mInputCmd.Replace(left, dumb, true);
2771 mInputCmd.Replace(right, dumb, true);
2772
2773 const wxString leftSingle = wxT("\u2018"), rightSingle = wxT("\u2019"),
2774 dumbSingle = '\'';
2775 mInputCmd.Replace(leftSingle, dumbSingle, true);
2776 mInputCmd.Replace(rightSingle, dumbSingle, true);
2777
2778 return ParseCommand(mInputCmd);
2779}
2780
2782{
2783 if (mControls.size() == 0)
2784 {
2785 return true;
2786 }
2787
2788 for (unsigned int i = 0; i < mControls.size(); i++)
2789 {
2790 NyqControl *ctrl = &mControls[i];
2791
2792 if (ctrl->type == NYQ_CTRL_STRING || ctrl->type == NYQ_CTRL_TEXT)
2793 {
2794 continue;
2795 }
2796
2797 if (ctrl->val == UNINITIALIZED_CONTROL)
2798 {
2799 ctrl->val = GetCtrlValue(ctrl->valStr);
2800 }
2801
2802 if (ctrl->type == NYQ_CTRL_CHOICE)
2803 {
2804 continue;
2805 }
2806
2807 if (ctrl->type == NYQ_CTRL_FILE)
2808 {
2809 resolveFilePath(ctrl->valStr);
2810
2811 wxString path;
2812 if (ctrl->valStr.StartsWith("\"", &path))
2813 {
2814 // Validate if a list of quoted paths.
2815 if (path.EndsWith("\"", &path))
2816 {
2817 path.Replace("\"\"", "\"");
2818 wxStringTokenizer tokenizer(path, "\"");
2819 while (tokenizer.HasMoreTokens())
2820 {
2821 wxString token = tokenizer.GetNextToken();
2822 if(!validatePath(token))
2823 {
2824 const auto message =
2825 XO("\"%s\" is not a valid file path.").Format( token );
2827 message,
2828 wxOK | wxICON_EXCLAMATION | wxCENTRE,
2829 XO("Error") );
2830 return false;
2831 }
2832 }
2833 continue;
2834 }
2835 else
2836 {
2837 const auto message =
2838 /* i18n-hint: Warning that there is one quotation mark rather than a pair.*/
2839 XO("Mismatched quotes in\n%s").Format( ctrl->valStr );
2841 message,
2842 wxOK | wxICON_EXCLAMATION | wxCENTRE,
2843 XO("Error") );
2844 return false;
2845 }
2846 }
2847 // Validate a single path.
2848 else if (validatePath(ctrl->valStr))
2849 {
2850 continue;
2851 }
2852
2853 // Validation failed
2854 const auto message =
2855 XO("\"%s\" is not a valid file path.").Format( ctrl->valStr );
2857 message,
2858 wxOK | wxICON_EXCLAMATION | wxCENTRE,
2859 XO("Error") );
2860 return false;
2861 }
2862
2863 if (ctrl->type == NYQ_CTRL_TIME)
2864 {
2865 NumericTextCtrl *n = (NumericTextCtrl *) mUIParent->FindWindow(ID_Time + i);
2866 ctrl->val = n->GetValue();
2867 }
2868
2869 if (ctrl->type == NYQ_CTRL_INT_TEXT && ctrl->lowStr.IsSameAs(wxT("nil"), false)) {
2870 ctrl->low = INT_MIN;
2871 }
2872 else if ((ctrl->type == NYQ_CTRL_FLOAT_TEXT || ctrl->type == NYQ_CTRL_TIME) &&
2873 ctrl->lowStr.IsSameAs(wxT("nil"), false))
2874 {
2875 ctrl->low = -(FLT_MAX);
2876 }
2877 else
2878 {
2879 ctrl->low = GetCtrlValue(ctrl->lowStr);
2880 }
2881
2882 if (ctrl->type == NYQ_CTRL_INT_TEXT && ctrl->highStr.IsSameAs(wxT("nil"), false)) {
2883 ctrl->high = INT_MAX;
2884 }
2885 else if ((ctrl->type == NYQ_CTRL_FLOAT_TEXT || ctrl->type == NYQ_CTRL_TIME) &&
2886 ctrl->highStr.IsSameAs(wxT("nil"), false))
2887 {
2888 ctrl->high = FLT_MAX;
2889 }
2890 else
2891 {
2892 ctrl->high = GetCtrlValue(ctrl->highStr);
2893 }
2894
2895 if (ctrl->high < ctrl->low)
2896 {
2897 ctrl->high = ctrl->low + 1;
2898 }
2899
2900 if (ctrl->val < ctrl->low)
2901 {
2902 ctrl->val = ctrl->low;
2903 }
2904
2905 if (ctrl->val > ctrl->high)
2906 {
2907 ctrl->val = ctrl->high;
2908 }
2909
2910 ctrl->ticks = 1000;
2911 if (ctrl->type == NYQ_CTRL_INT &&
2912 (ctrl->high - ctrl->low < ctrl->ticks))
2913 {
2914 ctrl->ticks = (int)(ctrl->high - ctrl->low);
2915 }
2916 }
2917
2918 return true;
2919}
2920
2922{
2923 S.StartVerticalLay();
2924 {
2925 S.StartMultiColumn(3, wxEXPAND);
2926 {
2927 S.SetStretchyCol(1);
2928
2929 S.AddVariableText(XO("Enter Nyquist Command: "));
2930
2931 S.AddSpace(1, 1);
2932 }
2933 S.EndMultiColumn();
2934
2935 S.StartHorizontalLay(wxEXPAND, 1);
2936 {
2937 mCommandText = S.Focus()
2938 .MinSize( { 500, 200 } )
2939 .AddTextWindow(wxT(""));
2940 }
2941 S.EndHorizontalLay();
2942
2943 S.StartHorizontalLay(wxALIGN_CENTER, 0);
2944 {
2945 S.Id(ID_Load).AddButton(XXO("&Load"));
2946 S.Id(ID_Save).AddButton(XXO("&Save"));
2947 }
2948 S.EndHorizontalLay();
2949 }
2950 S.EndVerticalLay();
2951}
2952
2954{
2955 wxScrolledWindow *scroller = S.Style(wxVSCROLL | wxTAB_TRAVERSAL)
2956 .StartScroller(2);
2957 {
2958 S.StartMultiColumn(4);
2959 {
2960 for (size_t i = 0; i < mControls.size(); i++)
2961 {
2962 NyqControl & ctrl = mControls[i];
2963
2964 if (ctrl.type == NYQ_CTRL_TEXT)
2965 {
2966 S.EndMultiColumn();
2967 S.StartHorizontalLay(wxALIGN_LEFT, 0);
2968 {
2969 S.AddSpace(0, 10);
2970 S.AddFixedText( Verbatim( ctrl.label ), false );
2971 }
2972 S.EndHorizontalLay();
2973 S.StartMultiColumn(4);
2974 }
2975 else
2976 {
2977 auto prompt = XXO("%s:").Format( ctrl.name );
2978 S.AddPrompt( prompt );
2979
2980 if (ctrl.type == NYQ_CTRL_STRING)
2981 {
2982 S.AddSpace(10, 10);
2983
2984 auto item = S.Id(ID_Text + i)
2985 .Validator<wxGenericValidator>(&ctrl.valStr)
2986 .Name( prompt )
2987 .AddTextBox( {}, wxT(""), 50);
2988 }
2989 else if (ctrl.type == NYQ_CTRL_CHOICE)
2990 {
2991 S.AddSpace(10, 10);
2992
2993 S.Id(ID_Choice + i).AddChoice( {},
2994 Msgids( ctrl.choices.data(), ctrl.choices.size() ) );
2995 }
2996 else if (ctrl.type == NYQ_CTRL_TIME)
2997 {
2998 S.AddSpace(10, 10);
2999
3000 const auto options = NumericTextCtrl::Options{}
3001 .AutoPos(true)
3002 .MenuEnabled(true)
3003 .ReadOnly(false);
3004
3005 NumericTextCtrl *time = safenew
3007 S.GetParent(), (ID_Time + i),
3010 ctrl.val,
3011 options);
3012 S
3013 .Name( prompt )
3014 .Position(wxALIGN_LEFT | wxALL)
3015 .AddWindow(time);
3016 }
3017 else if (ctrl.type == NYQ_CTRL_FILE)
3018 {
3019 S.AddSpace(10, 10);
3020
3021 // Get default file extension if specified in wildcards
3022 FileExtension defaultExtension;
3023 if (!ctrl.fileTypes.empty()) {
3024 const auto &type = ctrl.fileTypes[0];
3025 if ( !type.extensions.empty() )
3026 defaultExtension = type.extensions[0];
3027 }
3028 resolveFilePath(ctrl.valStr, defaultExtension);
3029
3030 wxTextCtrl *item = S.Id(ID_Text+i)
3031 .Name( prompt )
3032 .AddTextBox( {}, wxT(""), 40);
3033 item->SetValidator(wxGenericValidator(&ctrl.valStr));
3034
3035 if (ctrl.label.empty())
3036 // We'd expect wxFileSelectorPromptStr to already be translated, but apparently not.
3037 ctrl.label = wxGetTranslation( wxFileSelectorPromptStr );
3038 S.Id(ID_FILE + i).AddButton(
3039 Verbatim(ctrl.label), wxALIGN_LEFT);
3040 }
3041 else
3042 {
3043 // Integer or Real
3044 if (ctrl.type == NYQ_CTRL_INT_TEXT || ctrl.type == NYQ_CTRL_FLOAT_TEXT)
3045 {
3046 S.AddSpace(10, 10);
3047 }
3048
3049 S.Id(ID_Text+i);
3050 if (ctrl.type == NYQ_CTRL_FLOAT || ctrl.type == NYQ_CTRL_FLOAT_TEXT)
3051 {
3052 double range = ctrl.high - ctrl.low;
3053 S.Validator<FloatingPointValidator<double>>(
3054 // > 12 decimal places can cause rounding errors in display.
3055 12, &ctrl.val,
3056 // Set number of decimal places
3057 (range < 10
3058 ? NumValidatorStyle::THREE_TRAILING_ZEROES
3059 : range < 100
3060 ? NumValidatorStyle::TWO_TRAILING_ZEROES
3061 : NumValidatorStyle::ONE_TRAILING_ZERO),
3062 ctrl.low, ctrl.high
3063 );
3064 }
3065 else
3066 {
3067 S.Validator<IntegerValidator<double>>(
3068 &ctrl.val, NumValidatorStyle::DEFAULT,
3069 (int) ctrl.low, (int) ctrl.high);
3070 }
3071 wxTextCtrl *item = S
3072 .Name( prompt )
3073 .AddTextBox( {}, wxT(""),
3074 (ctrl.type == NYQ_CTRL_INT_TEXT ||
3075 ctrl.type == NYQ_CTRL_FLOAT_TEXT) ? 25 : 12);
3076
3077 if (ctrl.type == NYQ_CTRL_INT || ctrl.type == NYQ_CTRL_FLOAT)
3078 {
3079 S.Id(ID_Slider + i)
3080 .Style(wxSL_HORIZONTAL)
3081 .MinSize( { 150, -1 } )
3082 .AddSlider( {}, 0, ctrl.ticks, 0);
3083 }
3084 }
3085
3086 if (ctrl.type != NYQ_CTRL_FILE)
3087 {
3088 if (ctrl.type == NYQ_CTRL_CHOICE || ctrl.label.empty())
3089 {
3090 S.AddSpace(10, 10);
3091 }
3092 else
3093 {
3094 S.AddUnits( Verbatim( ctrl.label ) );
3095 }
3096 }
3097 }
3098 }
3099 }
3100 S.EndMultiColumn();
3101 }
3102 S.EndScroller();
3103
3104 scroller->SetScrollRate(0, 20);
3105
3106 // This fools NVDA into not saying "Panel" when the dialog gets focus
3107 scroller->SetName(wxT("\a"));
3108 scroller->SetLabel(wxT("\a"));
3109}
3110
3111// NyquistEffect implementation
3112
3114{
3115 return mOK;
3116}
3117
3118static const FileNames::FileType
3119 /* i18n-hint: Nyquist is the name of a programming language */
3120 NyquistScripts = { XO("Nyquist scripts"), { wxT("ny") }, true }
3121 /* i18n-hint: Lisp is the name of a programming language */
3122 , LispScripts = { XO("Lisp scripts"), { wxT("lsp") }, true }
3123;
3124
3125void NyquistEffect::OnLoad(wxCommandEvent & WXUNUSED(evt))
3126{
3127 if (mCommandText->IsModified())
3128 {
3129 if (wxNO == EffectUIServices::DoMessageBox(*this,
3130 XO("Current program has been modified.\nDiscard changes?"),
3131 wxYES_NO ) )
3132 {
3133 return;
3134 }
3135 }
3136
3137 FileDialogWrapper dlog(
3138 mUIParent,
3139 XO("Load Nyquist script"),
3140 mFileName.GetPath(),
3141 wxEmptyString,
3142 {
3143 NyquistScripts,
3144 LispScripts,
3145 FileNames::TextFiles,
3146 FileNames::AllFiles
3147 },
3148 wxFD_OPEN | wxRESIZE_BORDER);
3149
3150 if (dlog.ShowModal() != wxID_OK)
3151 {
3152 return;
3153 }
3154
3155 mFileName = dlog.GetPath();
3156
3157 if (!mCommandText->LoadFile(mFileName.GetFullPath()))
3158 {
3159 EffectUIServices::DoMessageBox(*this, XO("File could not be loaded"));
3160 }
3161}
3162
3163void NyquistEffect::OnSave(wxCommandEvent & WXUNUSED(evt))
3164{
3165 FileDialogWrapper dlog(
3166 mUIParent,
3167 XO("Save Nyquist script"),
3168 mFileName.GetPath(),
3169 mFileName.GetFullName(),
3170 {
3171 NyquistScripts,
3172 LispScripts,
3173 FileNames::AllFiles
3174 },
3175 wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER);
3176
3177 if (dlog.ShowModal() != wxID_OK)
3178 {
3179 return;
3180 }
3181
3182 mFileName = dlog.GetPath();
3183
3184 if (!mCommandText->SaveFile(mFileName.GetFullPath()))
3185 {
3186 EffectUIServices::DoMessageBox(*this, XO("File could not be saved"));
3187 }
3188}
3189
3190void NyquistEffect::OnSlider(wxCommandEvent & evt)
3191{
3192 int i = evt.GetId() - ID_Slider;
3193 NyqControl & ctrl = mControls[i];
3194
3195 int val = evt.GetInt();
3196 double range = ctrl.high - ctrl.low;
3197 double newVal = (val / (double)ctrl.ticks) * range + ctrl.low;
3198
3199 // Determine precision for displayed number
3200 int precision = range < 1.0 ? 3 :
3201 range < 10.0 ? 2 :
3202 range < 100.0 ? 1 :
3203 0;
3204
3205 // If the value is at least one tick different from the current value
3206 // change it (this prevents changes from manually entered values unless
3207 // the slider actually moved)
3208 if (fabs(newVal - ctrl.val) >= (1 / (double)ctrl.ticks) * range &&
3209 fabs(newVal - ctrl.val) >= pow(0.1, precision) / 2)
3210 {
3211 // First round to the appropriate precision
3212 newVal *= pow(10.0, precision);
3213 newVal = floor(newVal + 0.5);
3214 newVal /= pow(10.0, precision);
3215
3216 ctrl.val = newVal;
3217
3218 mUIParent->FindWindow(ID_Text + i)->GetValidator()->TransferToWindow();
3219 }
3220}
3221
3222void NyquistEffect::OnChoice(wxCommandEvent & evt)
3223{
3224 mControls[evt.GetId() - ID_Choice].val = (double) evt.GetInt();
3225}
3226
3227void NyquistEffect::OnTime(wxCommandEvent& evt)
3228{
3229 int i = evt.GetId() - ID_Time;
3230 static double value = 0.0;
3231 NyqControl & ctrl = mControls[i];
3232
3233 NumericTextCtrl *n = (NumericTextCtrl *) mUIParent->FindWindow(ID_Time + i);
3234 double val = n->GetValue();
3235
3236 // Observed that two events transmitted on each control change (Linux)
3237 // so skip if value has not changed.
3238 if (val != value) {
3239 if (val < ctrl.low || val > ctrl.high) {
3240 const auto message = XO("Value range:\n%s to %s")
3241 .Format( ToTimeFormat(ctrl.low), ToTimeFormat(ctrl.high) );
3243 message,
3244 wxOK | wxCENTRE,
3245 XO("Value Error") );
3246 }
3247
3248 if (val < ctrl.low)
3249 val = ctrl.low;
3250 else if (val > ctrl.high)
3251 val = ctrl.high;
3252
3253 n->SetValue(val);
3254 value = val;
3255 }
3256}
3257
3258void NyquistEffect::OnFileButton(wxCommandEvent& evt)
3259{
3260 int i = evt.GetId() - ID_FILE;
3261 NyqControl & ctrl = mControls[i];
3262
3263 // Get style flags:
3264 // Ensure legal combinations so that wxWidgets does not throw an assert error.
3265 unsigned int flags = 0;
3266 if (!ctrl.highStr.empty())
3267 {
3268 wxStringTokenizer tokenizer(ctrl.highStr, ",");
3269 while ( tokenizer.HasMoreTokens() )
3270 {
3271 wxString token = tokenizer.GetNextToken().Trim(true).Trim(false);
3272 if (token.IsSameAs("open", false))
3273 {
3274 flags |= wxFD_OPEN;
3275 flags &= ~wxFD_SAVE;
3276 flags &= ~wxFD_OVERWRITE_PROMPT;
3277 }
3278 else if (token.IsSameAs("save", false))
3279 {
3280 flags |= wxFD_SAVE;
3281 flags &= ~wxFD_OPEN;
3282 flags &= ~wxFD_MULTIPLE;
3283 flags &= ~wxFD_FILE_MUST_EXIST;
3284 }
3285 else if (token.IsSameAs("overwrite", false) && !(flags & wxFD_OPEN))
3286 {
3287 flags |= wxFD_OVERWRITE_PROMPT;
3288 }
3289 else if (token.IsSameAs("exists", false) && !(flags & wxFD_SAVE))
3290 {
3291 flags |= wxFD_FILE_MUST_EXIST;
3292 }
3293 else if (token.IsSameAs("multiple", false) && !(flags & wxFD_SAVE))
3294 {
3295 flags |= wxFD_MULTIPLE;
3296 }
3297 }
3298 }
3299
3300 resolveFilePath(ctrl.valStr);
3301
3302 wxFileName fname = ctrl.valStr;
3303 wxString defaultDir = fname.GetPath();
3304 wxString defaultFile = fname.GetName();
3305 auto message = XO("Select a file");
3306
3307 if (flags & wxFD_MULTIPLE)
3308 message = XO("Select one or more files");
3309 else if (flags & wxFD_SAVE)
3310 message = XO("Save file as");
3311
3312 FileDialogWrapper openFileDialog(mUIParent->FindWindow(ID_FILE + i),
3313 message,
3314 defaultDir,
3315 defaultFile,
3316 ctrl.fileTypes,
3317 flags); // styles
3318
3319 if (openFileDialog.ShowModal() == wxID_CANCEL)
3320 {
3321 return;
3322 }
3323
3324 wxString path;
3325 // When multiple files selected, return file paths as a list of quoted strings.
3326 if (flags & wxFD_MULTIPLE)
3327 {
3328 wxArrayString selectedFiles;
3329 openFileDialog.GetPaths(selectedFiles);
3330
3331 for (size_t sf = 0; sf < selectedFiles.size(); sf++) {
3332 path += "\"";
3333 path += selectedFiles[sf];
3334 path += "\"";
3335 }
3336 ctrl.valStr = path;
3337 }
3338 else
3339 {
3340 ctrl.valStr = openFileDialog.GetPath();
3341 }
3342
3343 mUIParent->FindWindow(ID_Text + i)->GetValidator()->TransferToWindow();
3344}
3345
3354 wxString& path, FileExtension extension /* empty string */)
3355{
3356#if defined(__WXMSW__)
3357 path.Replace("/", wxFileName::GetPathSeparator());
3358#endif
3359
3360 path.Trim(true).Trim(false);
3361
3362 typedef std::unordered_map<wxString, FilePath> map;
3363 map pathKeys = {
3364 {"*home*", wxGetHomeDir()},
3365 {"~", wxGetHomeDir()},
3366 {"*default*", FileNames::DefaultToDocumentsFolder("").GetPath()},
3367 {"*export*", FileNames::FindDefaultPath(FileNames::Operation::Export)},
3368 {"*save*", FileNames::FindDefaultPath(FileNames::Operation::Save)},
3369 {"*config*", FileNames::DataDir()}
3370 };
3371
3372 int characters = path.Find(wxFileName::GetPathSeparator());
3373 if(characters == wxNOT_FOUND) // Just a path or just a file name
3374 {
3375 if (path.empty())
3376 path = "*default*";
3377
3378 if (pathKeys.find(path) != pathKeys.end())
3379 {
3380 // Keyword found, so assume this is the intended directory.
3381 path = pathKeys[path] + wxFileName::GetPathSeparator();
3382 }
3383 else // Just a file name
3384 {
3385 path = pathKeys["*default*"] + wxFileName::GetPathSeparator() + path;
3386 }
3387 }
3388 else // path + file name
3389 {
3390 wxString firstDir = path.Left(characters);
3391 wxString rest = path.Mid(characters);
3392
3393 if (pathKeys.find(firstDir) != pathKeys.end())
3394 {
3395 path = pathKeys[firstDir] + rest;
3396 }
3397 }
3398
3399 wxFileName fname = path;
3400
3401 // If the directory is invalid, better to leave it as is (invalid) so that
3402 // the user sees the error rather than an unexpected file path.
3403 if (fname.wxFileName::IsOk() && fname.GetFullName().empty())
3404 {
3405 path = fname.GetPathWithSep() + _("untitled");
3406 if (!extension.empty())
3407 path = path + '.' + extension;
3408 }
3409}
3410
3411
3413{
3414 wxFileName fname = path;
3415 wxString dir = fname.GetPath();
3416
3417 return (fname.wxFileName::IsOk() &&
3418 wxFileName::DirExists(dir) &&
3419 !fname.GetFullName().empty());
3420}
3421
3422
3424{
3425 int seconds = static_cast<int>(t);
3426 int hh = seconds / 3600;
3427 int mm = seconds % 3600;
3428 mm = mm / 60;
3429 return wxString::Format("%d:%d:%.3f", hh, mm, t - (hh * 3600 + mm * 60));
3430}
3431
3432
3433void NyquistEffect::OnText(wxCommandEvent & evt)
3434{
3435 int i = evt.GetId() - ID_Text;
3436
3437 NyqControl & ctrl = mControls[i];
3438
3439 if (wxDynamicCast(evt.GetEventObject(), wxWindow)->GetValidator()->TransferFromWindow())
3440 {
3441 if (ctrl.type == NYQ_CTRL_FLOAT || ctrl.type == NYQ_CTRL_INT)
3442 {
3443 int pos = (int)floor((ctrl.val - ctrl.low) /
3444 (ctrl.high - ctrl.low) * ctrl.ticks + 0.5);
3445
3446 wxSlider *slider = (wxSlider *)mUIParent->FindWindow(ID_Slider + i);
3447 slider->SetValue(pos);
3448 }
3449 }
3450}
3451
3453//
3454// NyquistOutputDialog
3455//
3457
3458
3459BEGIN_EVENT_TABLE(NyquistOutputDialog, wxDialogWrapper)
3462
3463NyquistOutputDialog::NyquistOutputDialog(wxWindow * parent, wxWindowID id,
3464 const TranslatableString & title,
3465 const TranslatableString & prompt,
3466 const TranslatableString &message)
3467: wxDialogWrapper{ parent, id, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER }
3468{
3469 SetName();
3470
3471 ShuttleGui S{ this, eIsCreating };
3472 {
3473 S.SetBorder(10);
3474
3475 S.AddVariableText( prompt, false, wxALIGN_LEFT | wxLEFT | wxTOP | wxRIGHT );
3476
3477 // TODO: use ShowInfoDialog() instead.
3478 // Beware this dialog MUST work with screen readers.
3479 S.Prop( 1 )
3480 .Position(wxEXPAND | wxALL)
3481 .MinSize( { 480, 250 } )
3482 .Style(wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH)
3483 .AddTextWindow( message.Translation() );
3484
3485 S.SetBorder( 5 );
3486
3487 S.StartHorizontalLay(wxALIGN_CENTRE | wxLEFT | wxBOTTOM | wxRIGHT, 0 );
3488 {
3489 /* i18n-hint: In most languages OK is to be translated as OK. It appears on a button.*/
3490 S.Id(wxID_OK).AddButton( XXO("OK"), wxALIGN_CENTRE, true );
3491 }
3492 S.EndHorizontalLay();
3493
3494 }
3495
3496 SetAutoLayout(true);
3497 GetSizer()->Fit(this);
3498 GetSizer()->SetSizeHints(this);
3499}
3500
3501// ============================================================================
3502// NyquistOutputDialog implementation
3503// ============================================================================
3504
3505void NyquistOutputDialog::OnOk(wxCommandEvent & /* event */)
3506{
3507 EndModal(wxID_OK);
3508}
3509
3510// Registration of extra functions in XLisp.
3511#include "../../../lib-src/libnyquist/nyquist/xlisp/xlisp.h"
3512
3513static LVAL gettext()
3514{
3515 auto string = UTF8CTOWX(getstring(xlgastring()));
3516#if !HAS_I18N_CONTEXTS
3517 // allow ignored context argument
3518 if ( moreargs() )
3519 nextarg();
3520#endif
3521 xllastarg();
3522 return cvstring(GetCustomTranslation(string).mb_str(wxConvUTF8));
3523}
3524
3525static LVAL gettextc()
3526{
3527#if HAS_I18N_CONTEXTS
3528 auto string = UTF8CTOWX(getstring(xlgastring()));
3529 auto context = UTF8CTOWX(getstring(xlgastring()));
3530 xllastarg();
3531 return cvstring(wxGetTranslation( string, "", 0, "", context )
3532 .mb_str(wxConvUTF8));
3533#else
3534 return gettext();
3535#endif
3536}
3537
3538static LVAL ngettext()
3539{
3540 auto string1 = UTF8CTOWX(getstring(xlgastring()));
3541 auto string2 = UTF8CTOWX(getstring(xlgastring()));
3542 auto number = getfixnum(xlgafixnum());
3543#if !HAS_I18N_CONTEXTS
3544 // allow ignored context argument
3545 if ( moreargs() )
3546 nextarg();
3547#endif
3548 xllastarg();
3549 return cvstring(
3550 wxGetTranslation(string1, string2, number).mb_str(wxConvUTF8));
3551}
3552
3553static LVAL ngettextc()
3554{
3555#if HAS_I18N_CONTEXTS
3556 auto string1 = UTF8CTOWX(getstring(xlgastring()));
3557 auto string2 = UTF8CTOWX(getstring(xlgastring()));
3558 auto number = getfixnum(xlgafixnum());
3559 auto context = UTF8CTOWX(getstring(xlgastring()));
3560 xllastarg();
3561 return cvstring(wxGetTranslation( string1, string2, number, "", context )
3562 .mb_str(wxConvUTF8));
3563#else
3564 return ngettext();
3565#endif
3566}
3567
3568void * nyq_make_opaque_string( int size, unsigned char *src ){
3569 LVAL dst;
3570 unsigned char * dstp;
3571 dst = new_string((int)(size+2));
3572 dstp = getstring(dst);
3573
3574 /* copy the source to the destination */
3575 while (size-- > 0)
3576 *dstp++ = *src++;
3577 *dstp = '\0';
3578
3579 return (void*)dst;
3580}
3581
3582void * nyq_reformat_aud_do_response(const wxString & Str) {
3583 LVAL dst;
3584 LVAL message;
3585 LVAL success;
3586 wxString Left = Str.BeforeLast('\n').BeforeLast('\n').ToAscii();
3587 wxString Right = Str.BeforeLast('\n').AfterLast('\n').ToAscii();
3588 message = cvstring(Left);
3589 success = Right.EndsWith("OK") ? s_true : nullptr;
3590 dst = cons(message, success);
3591 return (void *)dst;
3592}
3593
3594#include "../../commands/ScriptCommandRelay.h"
3595
3596
3597/* xlc_aud_do -- interface to C routine aud_do */
3598
3599LVAL xlc_aud_do(void)
3600{
3601// Based on string-trim...
3602 unsigned char *leftp;
3603 LVAL src,dst;
3604
3605 /* get the string */
3606 src = xlgastring();
3607 xllastarg();
3608
3609 /* setup the string pointer */
3610 leftp = getstring(src);
3611
3612 // Go call my real function here...
3613 dst = (LVAL)ExecForLisp( (char *)leftp );
3614
3615 //dst = cons(dst, (LVAL)1);
3616 /* return the new string */
3617 return (dst);
3618}
3619
3621{
3622 // Add functions to XLisp. Do this only once,
3623 // before the first call to nyx_init.
3624 static bool firstTime = true;
3625 if (firstTime) {
3626 firstTime = false;
3627
3628 // All function names must be UP-CASED
3629 static const FUNDEF functions[] = {
3630 { "_", SUBR, gettext },
3631 { "_C", SUBR, gettextc },
3632 { "NGETTEXT", SUBR, ngettext },
3633 { "NGETTEXTC", SUBR, ngettextc },
3634 { "AUD-DO", SUBR, xlc_aud_do },
3635 };
3636
3637 xlbindfunctions( functions, WXSIZEOF( functions ) );
3638 }
3639}
wxEVT_COMMAND_BUTTON_CLICKED
wxT("CloseDown"))
SimpleGuard< R > MakeSimpleGuard(R value) noexcept(noexcept(SimpleGuard< R >{ value }))
Convert a value to a handler function returning that value, suitable for GuardedCall<R>
static RegisteredToolbarFactory factory
Toolkit-neutral facade for basic user interface services.
END_EVENT_TABLE()
int min(int a, int b)
#define str(a)
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
const TranslatableString name
Definition: Distortion.cpp:76
#define NYQUISTEFFECTS_FAMILY
Definition: EffectBase.h:141
EffectType
@ EffectTypeAnalyze
@ EffectTypeGenerate
@ EffectTypeTool
@ EffectTypeProcess
std::function< DialogFactoryResults(wxWindow &parent, EffectBase &, EffectUIServices &, EffectSettingsAccess &) > EffectDialogFactory
Type of function that creates a dialog for an effect.
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
wxString FileExtension
File extension, not including any leading dot.
Definition: Identifier.h:224
wxString PluginPath
type alias for identifying a Plugin supplied by a module, each module defining its own interpretation...
Definition: Identifier.h:214
STRINGS_API const wxString & GetCustomTranslation(const wxString &str1)
Definition: Internat.cpp:46
#define LAT1CTOWX(X)
Definition: Internat.h:158
#define UTF8CTOWX(X)
Definition: Internat.h:157
#define _(s)
Definition: Internat.h:73
IteratorRange< Iterator > make_iterator_range(const Iterator &i1, const Iterator &i2)
Definition: IteratorX.h:210
ValueRestorer< T > valueRestorer(T &var)
inline functions provide convenient parameter type deduction
Definition: MemoryX.h:253
#define safenew
Definition: MemoryX.h:10
static const AudacityProject::AttachedObjects::RegisteredFactory key
static const auto title
const NumericConverterType & NumericConverterType_TIME()
static LVAL gettextc()
Definition: Nyquist.cpp:3525
#define UNINITIALIZED_CONTROL
Definition: Nyquist.cpp:118
void * nyq_make_opaque_string(int size, unsigned char *src)
Definition: Nyquist.cpp:3568
void * nyq_reformat_aud_do_response(const wxString &Str)
Definition: Nyquist.cpp:3582
LVAL xlc_aud_do(void)
Definition: Nyquist.cpp:3599
static const FileNames::FileType LispScripts
Definition: Nyquist.cpp:3122
wxEVT_COMMAND_TEXT_UPDATED
Definition: Nyquist.cpp:136
static LVAL ngettextc()
Definition: Nyquist.cpp:3553
static const wxChar * KEY_Command
Definition: Nyquist.cpp:120
static const FileNames::FileType NyquistScripts
Definition: Nyquist.cpp:3120
EVT_COMMAND_RANGE(ID_Slider, ID_Slider+99, wxEVT_COMMAND_SLIDER_UPDATED, NyquistEffect::OnSlider) EVT_COMMAND_RANGE(ID_Text
#define NYQUIST_WORKER_ID
Definition: Nyquist.cpp:98
@ ID_Editor
Definition: Nyquist.cpp:104
@ ID_Load
Definition: Nyquist.cpp:105
@ ID_FILE
Definition: Nyquist.cpp:112
@ ID_Choice
Definition: Nyquist.cpp:110
@ ID_Slider
Definition: Nyquist.cpp:108
@ ID_Save
Definition: Nyquist.cpp:106
static LVAL ngettext()
Definition: Nyquist.cpp:3538
ID_Text
Definition: Nyquist.cpp:135
static const wxChar * KEY_Parameters
Definition: Nyquist.cpp:121
NyquistEffect::OnText ID_Time
Definition: Nyquist.cpp:139
static void RegisterFunctions()
Definition: Nyquist.cpp:3620
#define NYQ_MAX_LEN
Definition: Nyquist.cpp:116
static LVAL gettext()
Definition: Nyquist.cpp:3513
@ NYQ_CTRL_STRING
Definition: Nyquist.h:35
@ NYQ_CTRL_TEXT
Definition: Nyquist.h:39
@ NYQ_CTRL_TIME
Definition: Nyquist.h:40
@ NYQ_CTRL_INT_TEXT
Definition: Nyquist.h:37
@ NYQ_CTRL_INT
Definition: Nyquist.h:33
@ NYQ_CTRL_CHOICE
Definition: Nyquist.h:36
@ NYQ_CTRL_FLOAT_TEXT
Definition: Nyquist.h:38
@ NYQ_CTRL_FILE
Definition: Nyquist.h:41
@ NYQ_CTRL_FLOAT
Definition: Nyquist.h:34
#define NYQUIST_PROMPT_ID
#define NYQUIST_PROMPT_NAME
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
wxString FilePath
Definition: Project.h:21
an object holding per-project preferred sample rate
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
char * samplePtr
Definition: SampleFormat.h:57
void * ExecForLisp(char *pIn)
TranslatableStrings Msgids(const EnumValueSymbol strings[], size_t nStrings)
Convenience function often useful when adding choice controls.
@ eIsCreating
Definition: ShuttleGui.h:37
@ eDebugID
Definition: ShuttleGui.h:628
TranslatableString label
Definition: TagsEditor.cpp:165
const auto project
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
#define S(N)
Definition: ToChars.cpp:64
static Settings & settings()
Definition: TrackInfo.cpp:51
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
int id
size_t size() const
Definition: Project.cpp:17
CommandParameters, derived from wxFileConfig, is essentially doing the same things as the SettingsVis...
bool WriteEnum(const wxString &key, int value, const EnumValueSymbol choices[], size_t nChoices)
bool GetParameters(wxString &parms)
bool ReadEnum(const wxString &key, int *pi, const EnumValueSymbol choices[], size_t nChoices, const ObsoleteMap obsoletes[]=nullptr, size_t nObsoletes=0) const
bool SetParameters(const wxString &parms)
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
Base class for many of the effects in Audacity.
Definition: EffectBase.h:33
double mT1
Definition: EffectBase.h:123
void SetPreviewFullSelectionFlag(bool previewDurationFlag)
Definition: EffectBase.cpp:215
bool IsPreviewing() const
Definition: EffectBase.h:100
void SetLinearEffectFlag(bool linearEffectFlag)
Definition: EffectBase.cpp:210
const TrackList * inputTracks() const
Definition: EffectBase.h:102
double mProjectRate
Definition: EffectBase.h:119
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:116
wxArrayString mPresetNames
Definition: EffectBase.h:108
double mF0
Definition: EffectBase.h:105
double mF1
Definition: EffectBase.h:106
double mT0
Definition: EffectBase.h:122
const AudacityProject * FindProject() const
Definition: EffectBase.cpp:220
static bool EnablePreview(wxWindow *parent, bool enable=true)
bool TrackGroupProgress(int whichGroup, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:353
void SetBatchProcessing() override
Definition: Effect.cpp:300
bool Delegate(Effect &delegate, EffectSettings &settings, InstanceFinder finder={})
Re-invoke DoEffect on another Effect object that implements the work.
Definition: Effect.cpp:322
virtual NumericFormatID GetSelectionFormat()
Definition: Effect.cpp:187
bool TotalProgress(double frac, const TranslatableString &={}) const
Definition: Effect.cpp:335
bool TrackProgress(int whichTrack, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:343
bool IsBatchProcessing() const override
Definition: Effect.cpp:295
unsigned TestUIFlags(unsigned mask)
Definition: Effect.cpp:291
int GetNumWaveGroups() const
Definition: Effect.h:140
Performs effect computation.
EffectManager is the class that handles effects and effect categories.
Definition: EffectManager.h:52
void SetSkipStateFlag(bool flag)
static EffectManager & Get()
Use this object to copy the input tracks to tentative outputTracks.
const Track * GetMatchingInput(const Track &outTrack) const
Gets the matching input track for the given output track if it finds its match, else nullptr.
Track * AddToOutputTracks(const std::shared_ptr< Track > &t)
Use this to add an output track, not corresponding to an input.
std::pair< double, double > TimeInterval
TrackList & Get()
Expose the output track list for iterations or even erasures.
Hold values to send to effect output meters.
void ModifySettings(Function &&function)
Do a correct read-modify-write of settings.
static int DoMessageBox(const EffectPlugin &plugin, const TranslatableString &message, long style=DefaultMessageBoxStyle, const TranslatableString &titleStr={})
virtual int ShowHostInterface(EffectBase &plugin, wxWindow &parent, const EffectDialogFactory &factory, std::shared_ptr< EffectInstance > &pInstance, EffectSettingsAccess &access, bool forceModal=false)
static NyquistSettings & GetSettings(EffectSettings &settings)
Assume settings originated from MakeSettings() and copies thereof.
Definition: Effect.h:166
EffectSettings MakeSettings() const override
Definition: Effect.h:156
virtual wxString GetPath() const
virtual void GetPaths(wxArrayString &paths) const
virtual int ShowModal()
std::vector< FileType > FileTypes
Definition: FileNames.h:75
static FormatterContext SampleRateContext(double sampleRate)
static wxString ToString(double numberToConvert, int digitsAfterDecimalPoint=-1)
Convert a number to a string, always uses the dot as decimal separator.
Definition: Internat.cpp:126
static bool CompatibleToDouble(const wxString &stringToConvert, double *result)
Convert a string to a number.
Definition: Internat.cpp:109
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:98
static wxString GetDefaultName()
Definition: LabelTrack.cpp:96
A Track that is used for Midi notes. (Somewhat old code).
Definition: NoteTrack.h:78
void SetValue(double newValue)
A control on a NyquistDialog.
Definition: Nyquist.h:45
wxString highStr
Definition: Nyquist.h:61
double high
Definition: Nyquist.h:64
wxString label
Definition: Nyquist.h:56
wxString name
Definition: Nyquist.h:55
wxString var
Definition: Nyquist.h:54
int ticks
Definition: Nyquist.h:65
std::vector< EnumValueSymbol > choices
Definition: Nyquist.h:57
int type
Definition: Nyquist.h:53
wxString valStr
Definition: Nyquist.h:59
double low
Definition: Nyquist.h:63
double val
Definition: Nyquist.h:62
FileNames::FileTypes fileTypes
Definition: Nyquist.h:58
wxString lowStr
Definition: Nyquist.h:60
An Effect that calls up a Nyquist (XLISP) plug-in, i.e. many possible effects from this one class.
Definition: Nyquist.h:80
bool Process(EffectInstance &instance, EffectSettings &settings) override
Definition: Nyquist.cpp:710
static TranslatableString UnQuoteMsgid(const wxString &s, bool allowParens=true, wxString *pExtraString=nullptr)
Definition: Nyquist.cpp:1933
TranslatableString GetDescription() const override
Definition: Nyquist.cpp:241
bool mDebugButton
Definition: Nyquist.h:266
wxWeakRef< wxWindow > mUIParent
Definition: Nyquist.h:146
bool mIsSpectral
Definition: Nyquist.h:236
EffectType mType
Definition: Nyquist.h:262
static double GetCtrlValue(const wxString &s)
Definition: Nyquist.cpp:1980
void Continue()
Definition: Nyquist.cpp:1923
wxTextCtrl * mCommandText
Definition: Nyquist.h:292
bool mBreak
Definition: Nyquist.h:228
bool ParseCommand(const wxString &cmd)
Definition: Nyquist.cpp:2556
TranslatableString mDebugOutput
Definition: Nyquist.h:272
bool mEnablePreview
Definition: Nyquist.h:265
bool mRestoreSplits
Definition: Nyquist.h:289
std::pair< bool, FilePath > CheckHelpPage() const
Definition: Nyquist.cpp:254
bool TransferDataFromPromptWindow()
Definition: Nyquist.cpp:2763
TranslatableString mCopyright
Definition: Nyquist.h:257
int mVersion
Definition: Nyquist.h:274
void RedirectOutput()
Definition: Nyquist.cpp:1904
bool TransferDataFromWindow(EffectSettings &settings) override
Definition: Nyquist.cpp:1221
TranslatableString mPromptName
Definition: Nyquist.h:249
bool mFoundType
Definition: Nyquist.h:231
bool SaveSettings(const EffectSettings &settings, CommandParameters &parms) const override
Store settings as keys and values.
Definition: Nyquist.cpp:373
bool ProcessOne(NyxContext &nyxContext, EffectOutputTracks *pOutputs)
Definition: Nyquist.cpp:1273
void OutputCallback(int c)
Definition: Nyquist.cpp:2656
bool Parse(Tokenizer &tokenizer, const wxString &line, bool eof, bool first)
Definition: Nyquist.cpp:2097
FileExtensions ParseFileExtensions(const wxString &text)
Definition: Nyquist.cpp:1838
void BuildEffectWindow(ShuttleGui &S)
Definition: Nyquist.cpp:2953
bool IsDefault() const override
Whether the effect sorts "above the line" in the menus.
Definition: Nyquist.cpp:304
bool mHelpFileExists
Definition: Nyquist.h:260
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
Definition: Nyquist.cpp:246
void Break()
Definition: Nyquist.cpp:1918
wxString mManPage
Definition: Nyquist.h:258
EffectFamilySymbol GetFamily() const override
Report identifier and user-visible name of the effect protocol.
Definition: Nyquist.cpp:289
bool mDebug
Definition: Nyquist.h:268
virtual ~NyquistEffect()
Definition: Nyquist.cpp:202
bool ParseProgram(wxInputStream &stream)
Definition: Nyquist.cpp:2450
static void StaticOSCallback(void *userdata)
Definition: Nyquist.cpp:2667
bool DoLoadSettings(const CommandParameters &parms, EffectSettings &settings)
Definition: Nyquist.cpp:431
int SetLispVarsFromParameters(const CommandParameters &parms, bool bTestOnly)
Definition: Nyquist.cpp:497
bool mCont
Definition: Nyquist.h:229
void OnLoad(wxCommandEvent &evt)
Definition: Nyquist.cpp:3125
bool EnablesDebug() const override
Whether the effect dialog should have a Debug button; default, always false.
Definition: Nyquist.cpp:1194
static void StaticOutputCallback(int c, void *userdata)
Definition: Nyquist.cpp:2651
bool TransferDataFromEffectWindow()
Definition: Nyquist.cpp:2781
void OnFileButton(wxCommandEvent &evt)
Definition: Nyquist.cpp:3258
bool TransferDataToPromptWindow()
Definition: Nyquist.cpp:2719
wxString ToTimeFormat(double t)
Definition: Nyquist.cpp:3423
int ShowHostInterface(EffectBase &plugin, wxWindow &parent, const EffectDialogFactory &factory, std::shared_ptr< EffectInstance > &pInstance, EffectSettingsAccess &access, bool forceModal=false) override
Definition: Nyquist.cpp:1102
EffectType GetType() const override
Type determines how it behaves.
Definition: Nyquist.cpp:277
wxString mPerTrackProps
Definition: Nyquist.h:287
bool validatePath(wxString path)
Definition: Nyquist.cpp:3412
bool TransferDataToEffectWindow()
Definition: Nyquist.cpp:2726
static int mReentryCount
Definition: Nyquist.h:148
bool mStop
Definition: Nyquist.h:227
TranslatableString mAuthor
Definition: Nyquist.h:252
FileNames::FileTypes ParseFileTypes(const wxString &text)
Definition: Nyquist.cpp:1866
FilePath mHelpPage
Definition: Nyquist.h:261
unsigned mNumSelectedChannels
Definition: Nyquist.h:282
EffectType GetClassification() const override
Determines which menu it appears in; default same as GetType().
Definition: Nyquist.cpp:282
wxString mHelpFile
Definition: Nyquist.h:259
int mMergeClips
Definition: Nyquist.h:290
static FilePaths GetNyquistSearchPath()
Definition: Nyquist.cpp:2702
bool mRedirectOutput
Definition: Nyquist.h:269
std::unique_ptr< EffectEditor > PopulateOrExchange(ShuttleGui &S, EffectInstance &instance, EffectSettingsAccess &access, const EffectOutputs *pOutputs) override
Add controls to effect panel; always succeeds.
Definition: Nyquist.cpp:1182
const bool mIsPrompt
Definition: Nyquist.h:242
sampleCount mMaxLen
Definition: Nyquist.h:277
wxFileName mFileName
Name of the Nyquist script file this effect is loaded from.
Definition: Nyquist.h:224
wxString mDebugOutputStr
Definition: Nyquist.h:271
wxString mInputCmd
Definition: Nyquist.h:245
bool TransferDataToWindow(const EffectSettings &settings) override
Definition: Nyquist.cpp:1199
void OnSlider(wxCommandEvent &evt)
Definition: Nyquist.cpp:3190
bool mProjectChanged
Definition: Nyquist.h:270
bool mExternal
Definition: Nyquist.h:235
void OnSave(wxCommandEvent &evt)
Definition: Nyquist.cpp:3163
bool mFirstInGroup
Definition: Nyquist.h:279
TranslatableString mName
Name of the Effect (untranslated)
Definition: Nyquist.h:248
wxString EscapeString(const wxString &inStr)
Definition: Nyquist.cpp:1796
TranslatableString mAction
Definition: Nyquist.h:250
wxString mProps
Definition: Nyquist.h:286
void OSCallback()
Definition: Nyquist.cpp:2672
FilePath HelpPage() const override
Fully qualified local help file name, default is empty.
Definition: Nyquist.cpp:270
std::vector< NyqControl > mControls
Definition: Nyquist.h:275
static wxString NyquistToWxString(const char *nyqString)
Definition: Nyquist.cpp:1783
int mTrackIndex
Definition: Nyquist.h:278
bool mCompiler
Definition: Nyquist.h:232
void SetCommand(const wxString &cmd)
Definition: Nyquist.cpp:1909
ComponentInterfaceSymbol GetSymbol() const override
Definition: Nyquist.cpp:216
FileNames::FileType ParseFileType(const wxString &text)
Definition: Nyquist.cpp:1851
bool IsInteractive() const override
Whether the effect needs a dialog for entry of settings.
Definition: Nyquist.cpp:294
void BuildPromptWindow(ShuttleGui &S)
Definition: Nyquist.cpp:2921
void OnTime(wxCommandEvent &evt)
Definition: Nyquist.cpp:3227
PluginPath GetPath() const override
Definition: Nyquist.cpp:208
wxArrayString mCategories
Definition: Nyquist.h:284
static std::vector< EnumValueSymbol > ParseChoice(const wxString &text)
Definition: Nyquist.cpp:1806
TranslatableString mReleaseVersion
Definition: Nyquist.h:256
bool VisitSettings(SettingsVisitor &visitor, EffectSettings &settings) override
Definition: Nyquist.cpp:309
bool LoadSettings(const CommandParameters &parms, EffectSettings &settings) const override
Restore settings from keys and values.
Definition: Nyquist.cpp:424
double mOutputTime
Definition: Nyquist.h:280
static wxString UnQuote(const wxString &s, bool allowParens=true, wxString *pExtraString=nullptr)
Definition: Nyquist.cpp:1974
bool Init() override
Definition: Nyquist.cpp:566
bool mIsTool
Definition: Nyquist.h:237
wxString mCmd
Definition: Nyquist.h:247
TranslatableString mInitError
Definition: Nyquist.h:244
wxString mParameters
Definition: Nyquist.h:246
static void resolveFilePath(wxString &path, FileExtension extension={})
Definition: Nyquist.cpp:3353
bool mTrace
Definition: Nyquist.h:233
void OnChoice(wxCommandEvent &evt)
Definition: Nyquist.cpp:3222
void ParseFile()
Definition: Nyquist.cpp:2548
bool mIsSal
Definition: Nyquist.h:234
TranslatableString mInfo
Definition: Nyquist.h:251
EffectType mPromptType
Definition: Nyquist.h:263
unsigned mCount
Definition: Nyquist.h:281
void OnText(wxCommandEvent &evt)
Definition: Nyquist.cpp:3433
VendorSymbol GetVendor() const override
Definition: Nyquist.cpp:224
wxString GetVersion() const override
Definition: Nyquist.cpp:234
Dialog used with NyquistEffect.
Definition: Nyquist.h:300
void OnOk(wxCommandEvent &event)
Definition: Nyquist.cpp:3505
Unit slope but with either a jump (pasting more) or a flat interval (pasting less)
Definition: TimeWarper.h:181
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
double GetRate() const
Definition: ProjectRate.cpp:53
Defines a selected portion of a project.
Visitor of effect or command parameters. This is a base class with lots of virtual functions that do ...
virtual void Define(Arg< bool > var, const wxChar *key, bool vdefault, bool vmin=false, bool vmax=false, bool vscl=false)
virtual void DefineEnum(Arg< int > var, const wxChar *key, int vdefault, const EnumValueSymbol strings[], size_t nStrings)
SettingsVisitor that gets parameter values into a string.
SettingsVisitor that retrieves a JSON format definition of a command's parameters.
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
SettingsVisitor that sets parameters to a value (from a string)
bool SpectralSelectionEnabled() const
static SpectrogramSettings & Get(const WaveTrack &track)
std::shared_ptr< EffectInstance > MakeInstance() const override
Make an object maintaining short-term state of an Effect.
static TrackIterRange< Track > Group(Track &track)
Definition: SyncLock.cpp:150
static bool IsSyncLockSelected(const Track &track)
Definition: SyncLock.cpp:80
A kind of Track used to 'warp time'.
Definition: TimeTrack.h:24
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
R TypeSwitch(const Functions &...functions)
Definition: Track.h:381
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:950
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
auto Selected() -> TrackIterRange< TrackType >
Definition: Track.h:967
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
TranslatableString & Format(Args &&...args) &
Capture variadic format arguments (by copy) when there is no plural.
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:216
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
static WaveChannelView * FindFirst(WaveTrack *pWt)
If pWt is not null, return a pointer to the view of the first channel.
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
IntervalHolders SortedIntervalArray()
Return all WaveClips sorted by clip play start time.
Definition: WaveTrack.cpp:3297
size_t NChannels() const override
A constant property.
Definition: WaveTrack.cpp:530
std::shared_ptr< WaveTrack > Holder
Definition: WaveTrack.h:247
virtual bool Read(const wxString &key, bool *value) const =0
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
long long as_long_long() const
Definition: SampleCount.h:48
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:202
void Yield()
Dispatch waiting events, including actions enqueued by CallAfter.
Definition: BasicUI.cpp:225
FILES_API FilePath PlugInDir()
The user plug-in directory (not a system one)
FILES_API FilePath HtmlHelpDir()
FILES_API wxFileNameWrapper DefaultToDocumentsFolder(const wxString &preference)
FILES_API void AddUniquePathToPathList(const FilePath &path, FilePaths &pathList)
FILES_API FilePath BaseDir()
FILES_API FilePath DataDir()
Audacity user data directory.
FILES_API FilePath FindDefaultPath(Operation op)
FILES_API const FilePaths & AudacityPathList()
A list of directories that should be searched for Audacity files (plug-ins, help files,...
wxString GetSystemLanguageCode(const FilePaths &pathList)
Definition: Languages.cpp:83
std::string FILES_API GetDocumentsDir()
FILES_API wxString TempDir()
WAVE_TRACK_API std::pair< float, float > GetMinMax(const WaveChannel &channel, double t0, double t1, bool mayThrow=true)
WAVE_TRACK_API float GetRMS(const WaveChannel &channel, double t0, double t1, bool mayThrow=true)
wxString GetClipBoundaries(const Track *t)
Definition: Nyquist.cpp:1237
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101
__finl float_x4 __vecc sqrt(const float_x4 &a)
Externalized state of a plug-in.
"finally" as in The C++ Programming Language, 4th ed., p. 358 Useful for defining ad-hoc RAII actions...
Definition: MemoryX.h:175
Options & MenuEnabled(bool enable)
Options & AutoPos(bool enable)
Options & ReadOnly(bool enable)
WaveTrack * mCurChannelGroup
Definition: Nyquist.cpp:686
static int StaticPutCallback(float *buffer, int channel, int64_t start, int64_t len, int64_t totlen, void *userdata)
Definition: Nyquist.cpp:2622
std::exception_ptr mpException
Definition: Nyquist.cpp:707
unsigned mCurNumChannels
Not used in the callbacks.
Definition: Nyquist.cpp:690
int GetCallback(float *buffer, int channel, int64_t start, int64_t len, int64_t totlen)
Definition: Nyquist.cpp:2570
WaveChannel * mCurTrack[2]
Definition: Nyquist.cpp:687
std::unique_ptr< float[]> Buffer
Definition: Nyquist.cpp:692
Buffer mCurBuffer[2]
used only in GetCallback
Definition: Nyquist.cpp:693
const double mProgressTot
Definition: Nyquist.cpp:705
WaveTrack::Holder mOutputTrack
Definition: Nyquist.cpp:698
std::function< bool(double)> ProgressReport
Definition: Nyquist.cpp:669
const ProgressReport mProgressReport
Definition: Nyquist.cpp:703
static int StaticGetCallback(float *buffer, int channel, int64_t start, int64_t len, int64_t totlen, void *userdata)
Definition: Nyquist.cpp:2563
sampleCount mCurBufferStart[2]
Definition: Nyquist.cpp:694
NyxContext(ProgressReport progressReport, double scale, double progressTot)
Definition: Nyquist.cpp:671
int PutCallback(float *buffer, int channel, int64_t start, int64_t len, int64_t totlen)
Definition: Nyquist.cpp:2629
bool Tokenize(const wxString &line, bool eof, size_t trimStart, size_t trimEnd)
Definition: Nyquist.cpp:1998
wxArrayStringEx tokens
Definition: Nyquist.h:190