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