Audacity 3.2.0
NyquistBase.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 NyquistBase.cpp
6
7 Dominic Mazzoni
8
9**********************************************************************/
10#include "NyquistBase.h"
11#include "BasicUI.h"
12#include "EffectManager.h"
13#include "EffectOutputTracks.h"
14#include "FileNames.h"
15#include "LabelTrack.h"
16#include "Languages.h"
17#include "NoteTrack.h"
19#include "PluginManager.h"
20#include "Prefs.h"
21#include "Project.h"
22#include "ProjectRate.h"
23#include "ShuttleAutomation.h"
24#include "SpectrogramSettings.h"
25#include "SyncLock.h"
26#include "TempDirectory.h"
27#include "TimeTrack.h"
28#include "TimeWarper.h"
29#include "ViewInfo.h"
31#include "WaveClip.h"
32#include "WaveTrack.h"
33#include "wxFileNameWrapper.h"
34
35#include "nyquist/xlisp/xlisp.h"
36#include "nyx.h"
37
38#include <cfloat>
39#include <cstring>
40#include <iostream>
41#include <wx/log.h>
42#include <wx/numformatter.h>
43#include <wx/sstream.h>
44#include <wx/tokenzr.h>
45#include <wx/txtstrm.h>
46#include <wx/wfstream.h>
47
49
50// Protect Nyquist from selections greater than 2^31 samples (bug 439)
51#define NYQ_MAX_LEN (std::numeric_limits<int64_t>::max())
52
53static const wxChar* KEY_Command = wxT("Command");
54static const wxChar* KEY_Parameters = wxT("Parameters");
55
56NyquistBase::NyquistBase(const wxString& fName)
57 : mIsPrompt { fName == NYQUIST_PROMPT_ID }
58{
59 mAction = XO("Applying Nyquist Effect...");
60 mExternal = false;
61 mCompiler = false;
62 mTrace = false;
63 mRedirectOutput = false;
64 mDebug = false;
65 mIsSal = false;
66 mOK = false;
67 mAuthor = XO("n/a");
68 mReleaseVersion = XO("n/a");
69 mCopyright = XO("n/a");
70
71 // set clip/split handling when applying over clip boundary.
72 mRestoreSplits = true; // Default: Restore split lines.
73 mMergeClips = -1; // Default (auto): Merge if length remains unchanged.
74
75 mVersion = 4;
76
77 mStop = false;
78 mBreak = false;
79 mCont = false;
80 mIsTool = false;
81
83
84 // Interactive Nyquist
85 if (mIsPrompt)
86 {
89 mIsTool = true;
92 mOK = true;
93 return;
94 }
95
96 if (fName == NYQUIST_WORKER_ID)
97 {
98 // Effect spawned from Nyquist Prompt
99 /* i18n-hint: It is acceptable to translate this the same as for "Nyquist
100 * Prompt" */
101 mName = XO("Nyquist Worker");
102 return;
103 }
104
105 mFileName = fName;
106 // Use the file name verbatim as effect name.
107 // This is only a default name, overridden if we find a $name line:
108 mName = Verbatim(mFileName.GetName());
109 mFileModified = mFileName.GetModificationTime();
110 ParseFile();
111
113 mInitError = XO("Ill-formed Nyquist plug-in header");
114}
115
117{
118}
119
120// ComponentInterface implementation
121
123{
124 if (mIsPrompt)
125 return NYQUIST_PROMPT_ID;
126
127 return mFileName.GetFullPath();
128}
129
131{
132 if (mIsPrompt)
134
135 return mName;
136}
137
139{
140 if (mIsPrompt)
141 {
142 return XO("Audacity");
143 }
144
145 return mAuthor;
146}
147
149{
150 // Are Nyquist version strings really supposed to be translatable?
151 // See commit a06e561 which used XO for at least one of them
153}
154
156{
157 return mCopyright;
158}
159
161{
162 return mIsPrompt ? wxString("Nyquist_Prompt") : mManPage;
163}
164
165std::pair<bool, FilePath> NyquistBase::CheckHelpPage() const
166{
168 wxString fileName;
169
170 for (size_t i = 0, cnt = paths.size(); i < cnt; i++)
171 {
172 fileName = wxFileName(paths[i] + wxT("/") + mHelpFile).GetFullPath();
173 if (wxFileExists(fileName))
174 {
175 return { true, fileName };
176 }
177 }
178 return { false, wxEmptyString };
179}
180
182{
183 return mHelpPage;
184}
185
186// EffectDefinitionInterface implementation
187
189{
190 return mType;
191}
192
194{
195 if (mIsTool)
196 return EffectTypeTool;
197 return mType;
198}
199
201{
203}
204
206{
207 if (mIsPrompt)
208 {
209 return true;
210 }
211
212 return mControls.size() != 0;
213}
214
216{
217 return mIsPrompt;
218}
219
221{
222 return mDebugButton;
223}
224
227{
228 if (auto pSa = dynamic_cast<ShuttleSetAutomation*>(&visitor))
229 LoadSettings(*pSa->mpEap, settings);
230 return true;
231}
232
234 ConstSettingsVisitor& visitor, const EffectSettings& settings) const
235{
236 // For now we assume Nyquist can do get and set better than VisitSettings
237 // can, And so we ONLY use it for getting the signature.
238 if (auto pGa = dynamic_cast<ShuttleGetAutomation*>(&visitor))
239 {
240 SaveSettings(settings, *pGa->mpEap);
241 return true;
242 }
243
244 // Get the "definition," only for the help or info commands
245 if (mExternal)
246 return true;
247
248 if (mIsPrompt)
249 {
250 visitor.Define(mInputCmd, KEY_Command, wxString {});
251 visitor.Define(mParameters, KEY_Parameters, wxString {});
252 return true;
253 }
254
255 for (const auto& ctrl : mControls)
256 {
257 double d = ctrl.val;
258
259 if (d == UNINITIALIZED_CONTROL && ctrl.type != NYQ_CTRL_STRING)
260 d = GetCtrlValue(ctrl.valStr);
261
262 if (
263 ctrl.type == NYQ_CTRL_FLOAT || ctrl.type == NYQ_CTRL_FLOAT_TEXT ||
264 ctrl.type == NYQ_CTRL_TIME)
265 visitor.Define(
266 d, static_cast<const wxChar*>(ctrl.var.c_str()), (double)0.0,
267 ctrl.low, ctrl.high, 1.0);
268 else if (ctrl.type == NYQ_CTRL_INT || ctrl.type == NYQ_CTRL_INT_TEXT)
269 {
270 int x = d;
271 visitor.Define(
272 x, static_cast<const wxChar*>(ctrl.var.c_str()), 0,
273 static_cast<int>(ctrl.low), static_cast<int>(ctrl.high), 1);
274 // parms.Write(ctrl.var, (int) d);
275 }
276 else if (ctrl.type == NYQ_CTRL_CHOICE)
277 {
278 // untranslated
279 int x = d;
280 // parms.WriteEnum(ctrl.var, (int) d, choices);
281 visitor.DefineEnum(
282 x, static_cast<const wxChar*>(ctrl.var.c_str()), 0,
283 ctrl.choices.data(), ctrl.choices.size());
284 }
285 else if (ctrl.type == NYQ_CTRL_STRING || ctrl.type == NYQ_CTRL_FILE)
286 {
287 visitor.Define(
288 ctrl.valStr, ctrl.var, wxString {}, ctrl.lowStr, ctrl.highStr);
289 // parms.Write(ctrl.var, ctrl.valStr);
290 }
291 }
292 return true;
293}
294
296 const EffectSettings&, CommandParameters& parms) const
297{
298 if (mIsPrompt)
299 {
300 parms.Write(KEY_Command, mInputCmd);
301 parms.Write(KEY_Parameters, mParameters);
302
303 return true;
304 }
305
306 for (size_t c = 0, cnt = mControls.size(); c < cnt; c++)
307 {
308 const NyqControl& ctrl = mControls[c];
309 double d = ctrl.val;
310
311 if (d == UNINITIALIZED_CONTROL && ctrl.type != NYQ_CTRL_STRING)
312 {
313 d = GetCtrlValue(ctrl.valStr);
314 }
315
316 if (
317 ctrl.type == NYQ_CTRL_FLOAT || ctrl.type == NYQ_CTRL_FLOAT_TEXT ||
318 ctrl.type == NYQ_CTRL_TIME)
319 {
320 parms.Write(ctrl.var, d);
321 }
322 else if (ctrl.type == NYQ_CTRL_INT || ctrl.type == NYQ_CTRL_INT_TEXT)
323 {
324 parms.Write(ctrl.var, (int)d);
325 }
326 else if (ctrl.type == NYQ_CTRL_CHOICE)
327 {
328 // untranslated
329 parms.WriteEnum(
330 ctrl.var, (int)d, ctrl.choices.data(), ctrl.choices.size());
331 }
332 else if (ctrl.type == NYQ_CTRL_STRING)
333 {
334 parms.Write(ctrl.var, ctrl.valStr);
335 }
336 else if (ctrl.type == NYQ_CTRL_FILE)
337 {
338 // Convert the given path string to platform-dependent equivalent
339 resolveFilePath(const_cast<wxString&>(ctrl.valStr));
340 parms.Write(ctrl.var, ctrl.valStr);
341 }
342 }
343
344 return true;
345}
346
348 const CommandParameters& parms, EffectSettings& settings) const
349{
350 // To do: externalize state so const_cast isn't needed
351 return const_cast<NyquistBase*>(this)->DoLoadSettings(parms, settings);
352}
353
356{
357 // Due to a constness problem that happens when using the prompt, we need
358 // to be ready to switch the params to a local instance.
359 const CommandParameters* pParms = &parms;
360 CommandParameters localParms;
361
362 if (mIsPrompt)
363 {
364 parms.Read(KEY_Command, &mInputCmd, wxEmptyString);
365 parms.Read(KEY_Parameters, &mParameters, wxEmptyString);
366
367 if (!mInputCmd.empty())
368 {
370 }
371
372 if (!mParameters.empty())
373 {
374 pParms = &localParms;
375 localParms.SetParameters(mParameters);
376 }
377
378 if (!IsBatchProcessing())
379 {
381 }
382
385 mExternal = true;
386
387 if (!IsBatchProcessing())
388 {
389 return true;
390 }
391 }
392
393 // Constants to document what the true/false values mean.
394 const auto kTestOnly = true;
395 const auto kTestAndSet = false;
396
397 // badCount will encompass both actual bad values and missing values.
398 // We probably never actually have bad values when using the dialogs
399 // since the dialog validation will catch them.
400 int badCount;
401 // When batch processing, we just ignore missing/bad parameters.
402 // We'll end up using defaults in those cases.
403 if (!IsBatchProcessing())
404 {
405 badCount = SetLispVarsFromParameters(*pParms, kTestOnly);
406 if (badCount > 0)
407 return false;
408 }
409
410 badCount = SetLispVarsFromParameters(*pParms, kTestAndSet);
411 // We never do anything with badCount here.
412 // It might be non zero, for missing parameters, and we allow that,
413 // and don't distinguish that from an out-of-range value.
414 return true;
415}
416
417// Sets the lisp variables form the parameters.
418// returns the number of bad settings.
419// We can run this just testing for bad values, or actually setting when
420// the values are good.
422 const CommandParameters& parms, bool bTestOnly)
423{
424 int badCount = 0;
425 // First pass verifies values
426 for (size_t c = 0, cnt = mControls.size(); c < cnt; c++)
427 {
428 NyqControl& ctrl = mControls[c];
429 bool good = false;
430
431 // This GetCtrlValue code is preserved from former code,
432 // but probably is pointless. The value d isn't used later,
433 // and GetCtrlValue does not appear to have important needed
434 // side effects.
435 if (!bTestOnly)
436 {
437 double d = ctrl.val;
438 if (d == UNINITIALIZED_CONTROL && ctrl.type != NYQ_CTRL_STRING)
439 {
440 d = GetCtrlValue(ctrl.valStr);
441 }
442 }
443
444 if (
445 ctrl.type == NYQ_CTRL_FLOAT || ctrl.type == NYQ_CTRL_FLOAT_TEXT ||
446 ctrl.type == NYQ_CTRL_TIME)
447 {
448 double val;
449 good =
450 parms.Read(ctrl.var, &val) && val >= ctrl.low && val <= ctrl.high;
451 if (good && !bTestOnly)
452 ctrl.val = val;
453 }
454 else if (ctrl.type == NYQ_CTRL_INT || ctrl.type == NYQ_CTRL_INT_TEXT)
455 {
456 int val;
457 good =
458 parms.Read(ctrl.var, &val) && val >= ctrl.low && val <= ctrl.high;
459 if (good && !bTestOnly)
460 ctrl.val = (double)val;
461 }
462 else if (ctrl.type == NYQ_CTRL_CHOICE)
463 {
464 int val;
465 // untranslated
466 good = parms.ReadEnum(
467 ctrl.var, &val, ctrl.choices.data(), ctrl.choices.size()) &&
468 val != wxNOT_FOUND;
469 if (good && !bTestOnly)
470 ctrl.val = (double)val;
471 }
472 else if (ctrl.type == NYQ_CTRL_STRING || ctrl.type == NYQ_CTRL_FILE)
473 {
474 wxString val;
475 good = parms.Read(ctrl.var, &val);
476 if (good && !bTestOnly)
477 ctrl.valStr = val;
478 }
479 else if (ctrl.type == NYQ_CTRL_TEXT)
480 {
481 // This "control" is just fixed text (nothing to save or restore),
482 // Does not count for good/bad counting.
483 good = true;
484 }
485 badCount += !good ? 1 : 0;
486 }
487 return badCount;
488}
489
490// Effect Implementation
492{
493 // When Nyquist Prompt spawns an effect GUI, Init() is called for Nyquist
494 // Prompt, and then again for the spawned (mExternal) effect.
495
496 // EffectType may not be defined in script, so
497 // reset each time we call the Nyquist Prompt.
498 if (mIsPrompt)
499 {
501 // Reset effect type each time we call the Nyquist Prompt.
503 mIsSpectral = false;
504 mDebugButton = true; // Debug button always enabled for Nyquist Prompt.
506 true; // Preview button always enabled for Nyquist Prompt.
507 mVersion = 4;
508 }
509
510 // As of Audacity 2.1.2 rc1, 'spectral' effects are allowed only if
511 // the selected track(s) are in a spectrogram view, and there is at
512 // least one frequency bound and Spectral Selection is enabled for the
513 // selected track(s) - (but don't apply to Nyquist Prompt).
514
515 if (!mIsPrompt && mIsSpectral)
516 {
517 // Completely skip the spectral editing limitations if there is no
518 // project because that is editing of macro parameters
519 if (const auto project = FindProject())
520 {
521 bool bAllowSpectralEditing = false;
522 bool hasSpectral = false;
523 for (auto t : TrackList::Get(*project).Selected<const WaveTrack>())
524 {
525 // Find() not Get() to avoid creation-on-demand of views in case we
526 // are only previewing
527 const auto displays = GetDisplaysHook::Call(t);
528 hasSpectral |= displays.end() !=
529 std::find(
530 displays.begin(), displays.end(),
532 WaveChannelViewConstants::Spectrum, {} });
533
534 if (
535 hasSpectral &&
537 {
538 bAllowSpectralEditing = true;
539 break;
540 }
541 }
542
543 if (!bAllowSpectralEditing || ((mF0 < 0.0) && (mF1 < 0.0)))
544 {
545 using namespace BasicUI;
546 if (!hasSpectral)
548 XO("Enable track spectrogram view before\n"
549 "applying 'Spectral' effects."),
550 MessageBoxOptions {}.IconStyle(Icon::Error));
551 else
553 XO("To use 'Spectral effects', enable 'Spectral Selection'\n"
554 "in the track Spectrogram settings and select the\n"
555 "frequency range for the effect to act von."),
556 MessageBoxOptions {}.IconStyle(Icon::Error));
557 return false;
558 }
559 }
560 }
561
562 if (!mIsPrompt && !mExternal)
563 {
564 // TODO: (bugs):
565 // 1) If there is more than one plug-in with the same name,
566 // GetModificationTime may pick the wrong one. 2) If the ;type is changed
567 // after the effect has been registered, the plug-in will appear in the
568 // wrong menu.
569
570 // TODO: If we want to auto-add parameters from spectral selection,
571 // we will need to modify this test.
572 // Note that removing it stops the caching of parameter values,
573 //(during this session).
574 if (mFileName.GetModificationTime().IsLaterThan(mFileModified))
575 {
576 // If the effect has internal state, save and restore it.
577 // If the effect is stateless, saving and restoring don't matter.
578 auto dummySettings = MakeSettings();
579 constexpr auto key = L"TemporarySettings";
580 SaveUserPreset(key, dummySettings);
581
582 mMaxLen = NYQ_MAX_LEN;
583 ParseFile();
584 mFileModified = mFileName.GetModificationTime();
585
586 // Ignore failure
587 (void)LoadUserPreset(key, dummySettings);
588 }
589 }
590
591 return true;
592}
593
594static void RegisterFunctions();
595
599{
600 using ProgressReport = std::function<bool(double)>;
601
602 NyxContext(ProgressReport progressReport, double scale, double progressTot)
603 : mProgressReport { move(progressReport) }
604 , mScale { scale }
605 , mProgressTot { progressTot }
606 {
607 }
608
609 int GetCallback(
610 float* buffer, int channel, int64_t start, int64_t len, int64_t totlen);
611 int PutCallback(
612 float* buffer, int channel, int64_t start, int64_t len, int64_t totlen);
613 static int StaticGetCallback(
614 float* buffer, int channel, int64_t start, int64_t len, int64_t totlen,
615 void* userdata);
616 static int StaticPutCallback(
617 float* buffer, int channel, int64_t start, int64_t len, int64_t totlen,
618 void* userdata);
619
623
624 unsigned mCurNumChannels {};
625
626 using Buffer = std::unique_ptr<float[]>;
629 size_t mCurBufferLen[2] {};
631
633
634 double mProgressIn {};
635 double mProgressOut {};
636
638 const double mScale;
639 const double mProgressTot;
640
641 std::exception_ptr mpException {};
642};
643
645{
646 if (mIsPrompt && mControls.size() > 0 && !IsBatchProcessing())
647 {
648 auto& nyquistSettings = GetSettings(settings);
649 auto cleanup = finally([&] {
650 // Free up memory
651 nyquistSettings.proxySettings = {};
652 });
654 proxy.SetCommand(mInputCmd);
655 proxy.mDebug = nyquistSettings.proxyDebug;
656 proxy.mControls = move(nyquistSettings.controls);
657 auto result = Delegate(proxy, nyquistSettings.proxySettings);
658 if (result)
659 {
660 mT0 = proxy.mT0;
661 mT1 = proxy.mT1;
662 }
663 return result;
664 }
665
666 // Check for reentrant Nyquist commands.
667 // I'm choosing to mark skipped Nyquist commands as successful even though
668 // they are skipped. The reason is that when Nyquist calls out to a chain,
669 // and that chain contains Nyquist, it will be clearer if the chain
670 // completes skipping Nyquist, rather than doing nothing at all.
671 if (mReentryCount > 0)
672 return true;
673
674 // Restore the reentry counter (to zero) when we exit.
675 auto countRestorer = valueRestorer(mReentryCount);
678
679 bool success = true;
680 int nEffectsSoFar = EffectOutputTracks::nEffectsDone;
681 mProjectChanged = false;
683 em.SetSkipStateFlag(false);
684
685 // This code was added in a fix for bug 2392 (no preview for Nyquist)
686 // It was commented out in a fix for bug 2428 (no progress dialog from a
687 // macro)
688 // if (mExternal) {
689 // mProgress->Hide();
690 //}
691
692 mOutputTime = 0;
693 mCount = 0;
694 const auto scale =
695 (GetType() == EffectTypeProcess ? 0.5 : 1.0) / GetNumWaveGroups();
696
697 mStop = false;
698 mBreak = false;
699 mCont = false;
700
701 mTrackIndex = 0;
702
703 // If in tool mode, then we don't do anything with the track and selection.
704 const bool bOnePassTool = (GetType() == EffectTypeTool);
705
706 // We must copy all the tracks, because Paste needs label tracks to ensure
707 // correct sync-lock group behavior when the timeline is affected; then we
708 // just want to operate on the selected wave tracks
709 std::optional<EffectOutputTracks> oOutputs;
710 if (!bOnePassTool)
711 oOutputs.emplace(
713 true, false);
714
716 bOnePassTool ?
717 0 :
718 oOutputs->Get().Selected<const WaveTrack>().sum(&WaveTrack::NChannels);
719
720 mDebugOutput = {};
721 if (!mHelpFile.empty() && !mHelpFileExists)
722 {
724 XO("error: File \"%s\" specified in header but not found in plug-in path.\n")
725 .Format(mHelpFile);
726 }
727
728 if (mVersion >= 4)
729 {
730 auto project = FindProject();
731
732 mProps = wxEmptyString;
733
734 mProps += wxString::Format(
735 wxT("(putprop '*AUDACITY* (list %d %d %d) 'VERSION)\n"),
736 AUDACITY_VERSION, AUDACITY_RELEASE, AUDACITY_REVISION);
737 wxString lang = gPrefs->Read(wxT("/Locale/Language"), wxT(""));
738 lang =
739 (lang.empty()) ?
741 lang;
742 mProps += wxString::Format(
743 wxT("(putprop '*AUDACITY* \"%s\" 'LANGUAGE)\n"), lang);
744
745 mProps += wxString::Format(
746 wxT("(setf *DECIMAL-SEPARATOR* #\\%c)\n"),
747 wxNumberFormatter::GetDecimalSeparator());
748
749 mProps += wxString::Format(
750 wxT("(putprop '*SYSTEM-DIR* \"%s\" 'BASE)\n"),
752 mProps += wxString::Format(
753 wxT("(putprop '*SYSTEM-DIR* \"%s\" 'DATA)\n"),
755 mProps += wxString::Format(
756 wxT("(putprop '*SYSTEM-DIR* \"%s\" 'HELP)\n"),
757 EscapeString(FileNames::HtmlHelpDir().RemoveLast()));
758 mProps += wxString::Format(
759 wxT("(putprop '*SYSTEM-DIR* \"%s\" 'TEMP)\n"),
761 mProps += wxString::Format(
762 wxT("(putprop '*SYSTEM-DIR* \"%s\" 'SYS-TEMP)\n"),
764 mProps += wxString::Format(
765 wxT("(putprop '*SYSTEM-DIR* \"%s\" 'DOCUMENTS)\n"),
767 mProps += wxString::Format(
768 wxT("(putprop '*SYSTEM-DIR* \"%s\" 'HOME)\n"),
770
772 wxString list;
773 for (size_t i = 0, cnt = paths.size(); i < cnt; i++)
774 {
775 list += wxT("\"") + EscapeString(paths[i]) + wxT("\" ");
776 }
777 list = list.RemoveLast();
778
779 mProps += wxString::Format(
780 wxT("(putprop '*SYSTEM-DIR* (list %s) 'PLUGIN)\n"), list);
781 mProps += wxString::Format(
782 wxT("(putprop '*SYSTEM-DIR* (list %s) 'PLUG-IN)\n"), list);
783 mProps += wxString::Format(
784 wxT("(putprop '*SYSTEM-DIR* \"%s\" 'USER-PLUG-IN)\n"),
786
787 // Date and time:
788 wxDateTime now = wxDateTime::Now();
789 int year = now.GetYear();
790 int doy = now.GetDayOfYear();
791 int dom = now.GetDay();
792 // enumerated constants
793 wxDateTime::Month month = now.GetMonth();
794 wxDateTime::WeekDay day = now.GetWeekDay();
795
796 // Date/time as a list: year, day of year, hour, minute, seconds
797 mProps += wxString::Format(
798 wxT("(setf *SYSTEM-TIME* (list %d %d %d %d %d))\n"), year, doy,
799 now.GetHour(), now.GetMinute(), now.GetSecond());
800
801 mProps += wxString::Format(
802 wxT("(putprop '*SYSTEM-TIME* \"%s\" 'DATE)\n"), now.FormatDate());
803 mProps += wxString::Format(
804 wxT("(putprop '*SYSTEM-TIME* \"%s\" 'TIME)\n"), now.FormatTime());
805 mProps += wxString::Format(
806 wxT("(putprop '*SYSTEM-TIME* \"%s\" 'ISO-DATE)\n"),
807 now.FormatISODate());
808 mProps += wxString::Format(
809 wxT("(putprop '*SYSTEM-TIME* \"%s\" 'ISO-TIME)\n"),
810 now.FormatISOTime());
811 mProps +=
812 wxString::Format(wxT("(putprop '*SYSTEM-TIME* %d 'YEAR)\n"), year);
813 mProps += wxString::Format(
814 wxT("(putprop '*SYSTEM-TIME* %d 'DAY)\n"), dom); // day of month
815 mProps +=
816 wxString::Format(wxT("(putprop '*SYSTEM-TIME* %d 'MONTH)\n"), month);
817 mProps += wxString::Format(
818 wxT("(putprop '*SYSTEM-TIME* \"%s\" 'MONTH-NAME)\n"),
819 now.GetMonthName(month));
820 mProps += wxString::Format(
821 wxT("(putprop '*SYSTEM-TIME* \"%s\" 'DAY-NAME)\n"),
822 now.GetWeekDayName(day));
823
824 mProps += wxString::Format(
825 wxT("(putprop '*PROJECT* %d 'PROJECTS)\n"),
826 (int)AllProjects {}.size());
827 mProps += wxString::Format(
828 wxT("(putprop '*PROJECT* \"%s\" 'NAME)\n"),
829 EscapeString(project->GetProjectName()));
830
831 int numTracks = 0;
832 int numWave = 0;
833 int numLabel = 0;
834 int numMidi = 0;
835 int numTime = 0;
836 wxString waveTrackList; // track positions of selected audio tracks.
837
838 {
839 auto countRange = TrackList::Get(*project).Any();
840 for (auto t : countRange)
841 {
842 t->TypeSwitch([&](const WaveTrack&) {
843 numWave++;
844 if (t->GetSelected())
845 waveTrackList += wxString::Format(wxT("%d "), 1 + numTracks);
846 });
847 numTracks++;
848 }
849 numLabel = countRange.Filter<const LabelTrack>().size();
850#if defined(USE_MIDI)
851 numMidi = countRange.Filter<const NoteTrack>().size();
852#endif
853 numTime = countRange.Filter<const TimeTrack>().size();
854 }
855
856 // We use Internat::ToString() rather than "%g" here because we
857 // always have to use the dot as decimal separator when giving
858 // numbers to Nyquist, whereas using "%g" will use the user's
859 // decimal separator which may be a comma in some countries.
860 mProps += wxString::Format(
861 wxT("(putprop '*PROJECT* (float %s) 'RATE)\n"),
863 mProps +=
864 wxString::Format(wxT("(putprop '*PROJECT* %d 'TRACKS)\n"), numTracks);
865 mProps += wxString::Format(
866 wxT("(putprop '*PROJECT* %d 'WAVETRACKS)\n"), numWave);
867 mProps += wxString::Format(
868 wxT("(putprop '*PROJECT* %d 'LABELTRACKS)\n"), numLabel);
869 mProps += wxString::Format(
870 wxT("(putprop '*PROJECT* %d 'MIDITRACKS)\n"), numMidi);
871 mProps += wxString::Format(
872 wxT("(putprop '*PROJECT* %d 'TIMETRACKS)\n"), numTime);
873
874 double previewLen = 6.0;
875 gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &previewLen);
876 mProps += wxString::Format(
877 wxT("(putprop '*PROJECT* (float %s) 'PREVIEW-DURATION)\n"),
878 Internat::ToString(previewLen));
879
880 // *PREVIEWP* is true when previewing (better than relying on track view).
881 wxString isPreviewing = (this->IsPreviewing()) ? wxT("T") : wxT("NIL");
882 mProps += wxString::Format(wxT("(setf *PREVIEWP* %s)\n"), isPreviewing);
883
884 mProps += wxString::Format(
885 wxT("(putprop '*SELECTION* (list %s) 'TRACKS)\n"), waveTrackList);
886 mProps += wxString::Format(
887 wxT("(putprop '*SELECTION* %d 'CHANNELS)\n"), mNumSelectedChannels);
888 }
889
890 // Nyquist Prompt does not require a selection, but effects do.
891 if (!bOnePassTool && (mNumSelectedChannels == 0))
892 {
893 auto message = XO("Audio selection required.");
894 using namespace BasicUI;
896 message, MessageBoxOptions {}.IconStyle(Icon::Error));
897 }
898
899 std::optional<TrackIterRange<WaveTrack>> pRange;
900 if (!bOnePassTool)
901 pRange.emplace(oOutputs->Get().Selected<WaveTrack>());
902
903 // Keep track of whether the current track is first selected in its sync-lock
904 // group (we have no idea what the length of the returned audio will be, so
905 // we have to handle sync-lock group behavior the "old" way).
906 mFirstInGroup = true;
907 Track* gtLast = NULL;
908 double progressTot {};
909
910 for (; bOnePassTool || pRange->first != pRange->second;
911 (void)(!pRange || (++pRange->first, true)))
912 {
913 // Prepare to accumulate more debug output in OutputCallback
915 mDebugOutput = Verbatim("%s").Format(std::cref(mDebugOutputStr));
916
917 // New context for each channel group of input
918 NyxContext nyxContext {
919 [this](double frac) { return TotalProgress(frac); }, scale, progressTot
920 };
921 auto& mCurNumChannels = nyxContext.mCurNumChannels;
922 auto& mCurChannelGroup = nyxContext.mCurChannelGroup;
923 auto& mCurTrack = nyxContext.mCurTrack;
924 auto& mCurStart = nyxContext.mCurStart;
925 auto& mCurLen = nyxContext.mCurLen;
926
927 mCurChannelGroup = pRange ? *pRange->first : nullptr;
928 mCurTrack[0] = mCurChannelGroup ?
929 (*mCurChannelGroup->Channels().begin()).get() :
930 nullptr;
931 mCurNumChannels = 1;
932 assert(mCurChannelGroup != nullptr || bOnePassTool);
933 if ((mT1 >= mT0) || bOnePassTool)
934 {
935 if (bOnePassTool)
936 {
937 }
938 else
939 {
940 if (auto channels = mCurChannelGroup->Channels();
941 channels.size() > 1)
942 {
943 // TODO: more-than-two-channels
944 // Pay attention to consistency of mNumSelectedChannels
945 // with the running tally made by this loop!
946 mCurNumChannels = 2;
947
948 mCurTrack[1] = (*++channels.first).get();
949 }
950
951 // Check whether we're in the same group as the last selected track
952 Track* gt = *SyncLock::Group(*mCurChannelGroup).first;
953 mFirstInGroup = !gtLast || (gtLast != gt);
954 gtLast = gt;
955
956 mCurStart = mCurChannelGroup->TimeToLongSamples(mT0);
957 auto end = mCurChannelGroup->TimeToLongSamples(mT1);
958 mCurLen = end - mCurStart;
959
960 wxASSERT(mCurLen <= NYQ_MAX_LEN);
961
962 mCurLen = std::min(mCurLen, mMaxLen);
963 }
964
965 // libnyquist breaks except in LC_NUMERIC=="C".
966 //
967 // Note that we must set the locale to "C" even before calling
968 // nyx_init() because otherwise some effects will not work!
969 //
970 // MB: setlocale is not thread-safe. Should use uselocale()
971 // if available, or fix libnyquist to be locale-independent.
972 // See also http://bugzilla.audacityteam.org/show_bug.cgi?id=642#c9
973 // for further info about this thread safety question.
974 wxString prevlocale = wxSetlocale(LC_NUMERIC, NULL);
975 wxSetlocale(LC_NUMERIC, wxString(wxT("C")));
976
977 nyx_init();
978 nyx_set_os_callback(StaticOSCallback, (void*)this);
979 nyx_capture_output(StaticOutputCallback, (void*)this);
980
981 auto cleanup = finally([&] {
982 nyx_capture_output(NULL, (void*)NULL);
983 nyx_set_os_callback(NULL, (void*)NULL);
984 nyx_cleanup();
985 });
986
987 if (mVersion >= 4)
988 {
989 mPerTrackProps = wxEmptyString;
990 wxString lowHz = wxT("nil");
991 wxString highHz = wxT("nil");
992 wxString centerHz = wxT("nil");
993 wxString bandwidth = wxT("nil");
994
995 if (mF0 >= 0.0)
996 {
997 lowHz.Printf(wxT("(float %s)"), Internat::ToString(mF0));
998 }
999
1000 if (mF1 >= 0.0)
1001 {
1002 highHz.Printf(wxT("(float %s)"), Internat::ToString(mF1));
1003 }
1004
1005 if ((mF0 >= 0.0) && (mF1 >= 0.0))
1006 {
1007 centerHz.Printf(
1008 wxT("(float %s)"), Internat::ToString(sqrt(mF0 * mF1)));
1009 }
1010
1011 if ((mF0 > 0.0) && (mF1 >= mF0))
1012 {
1013 // with very small values, bandwidth calculation may be inf.
1014 // (Observed on Linux)
1015 double bw = log(mF1 / mF0) / log(2.0);
1016 if (!std::isinf(bw))
1017 {
1018 bandwidth.Printf(wxT("(float %s)"), Internat::ToString(bw));
1019 }
1020 }
1021
1022 mPerTrackProps += wxString::Format(
1023 wxT("(putprop '*SELECTION* %s 'LOW-HZ)\n"), lowHz);
1024 mPerTrackProps += wxString::Format(
1025 wxT("(putprop '*SELECTION* %s 'CENTER-HZ)\n"), centerHz);
1026 mPerTrackProps += wxString::Format(
1027 wxT("(putprop '*SELECTION* %s 'HIGH-HZ)\n"), highHz);
1028 mPerTrackProps += wxString::Format(
1029 wxT("(putprop '*SELECTION* %s 'BANDWIDTH)\n"), bandwidth);
1030
1031 const auto t0 =
1032 mCurChannelGroup ? mCurChannelGroup->SnapToSample(mT0) : mT0;
1033 const auto t1 =
1034 mCurChannelGroup ? mCurChannelGroup->SnapToSample(mT1) : mT1;
1035 mPerTrackProps += wxString::Format(
1036 wxT("(putprop '*SELECTION* (float %s) 'START)\n"),
1037 Internat::ToString(t0));
1038 mPerTrackProps += wxString::Format(
1039 wxT("(putprop '*SELECTION* (float %s) 'END)\n"),
1040 Internat::ToString(t1));
1041 }
1042
1043 success = ProcessOne(nyxContext, oOutputs ? &*oOutputs : nullptr);
1044
1045 // Reset previous locale
1046 wxSetlocale(LC_NUMERIC, prevlocale);
1047
1048 if (!success || bOnePassTool)
1049 {
1050 goto finish;
1051 }
1052 progressTot += nyxContext.mProgressIn + nyxContext.mProgressOut;
1053 }
1054
1055 mCount += mCurNumChannels;
1056 }
1057
1058 if (mOutputTime > 0.0)
1059 {
1060 mT1 = mT0 + mOutputTime;
1061 }
1062
1063finish:
1064
1065 // Show debug window if trace set in plug-in header and something to show.
1066 mDebug = (mTrace && !mDebugOutput.Translation().empty()) ? true : mDebug;
1067
1068 if (mDebug && !mRedirectOutput)
1070
1071 // Has rug been pulled from under us by some effect done within Nyquist??
1072 if (!bOnePassTool && (nEffectsSoFar == EffectOutputTracks::nEffectsDone))
1073 {
1074 if (success)
1075 oOutputs->Commit();
1076 }
1077 else
1078 {
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 {
1084 auto& selectedRegion = ViewInfo::Get(*project).selectedRegion;
1085 mT0 = selectedRegion.t0();
1086 mT1 = selectedRegion.t1();
1087 }
1088 else
1089 {
1090 mT0 = 0;
1091 mT1 = -1;
1092 }
1093 }
1094
1095 if (!mProjectChanged)
1096 em.SetSkipStateFlag(true);
1097
1098 return success;
1099}
1100
1101namespace
1102{
1103wxString GetClipBoundaries(const Track* t)
1104{
1105 wxString clips;
1106 const auto wt = dynamic_cast<const WaveTrack*>(t);
1107 if (!wt)
1108 return clips;
1109 auto ca = wt->SortedIntervalArray();
1110 // Each clip is a list (start-time, end-time)
1111 // Limit number of clips added to avoid argument stack overflow error (bug
1112 // 2300).
1113 for (size_t i = 0, n = ca.size(); i < n; ++i)
1114 {
1115 if (i < 1000)
1116 {
1117 clips += wxString::Format(
1118 wxT("(list (float %s) (float %s))"),
1119 Internat::ToString(ca[i]->GetPlayStartTime()),
1120 Internat::ToString(ca[i]->GetPlayEndTime()));
1121 }
1122 else if (i == 1000)
1123 {
1124 // If final clip is NIL, plug-in developer knows there are more than
1125 // 1000 clips in channel.
1126 clips += "NIL";
1127 }
1128 else if (i > 1000)
1129 {
1130 break;
1131 }
1132 }
1133 return clips;
1134};
1135} // namespace
1136
1137// NyquistBase implementation
1138
1140 NyxContext& nyxContext, EffectOutputTracks* pOutputs)
1141{
1142 const auto mCurNumChannels = nyxContext.mCurNumChannels;
1143 nyx_rval rval;
1144
1145 wxString cmd;
1146 cmd += wxT("(snd-set-latency 0.1)");
1147
1148 // A tool may be using AUD-DO which will potentially invalidate *TRACK*
1149 // so tools do not get *TRACK*.
1150 if (GetType() == EffectTypeTool)
1151 cmd += wxT("(setf S 0.25)\n"); // No Track.
1152 else if (mVersion >= 4)
1153 {
1154 nyx_set_audio_name("*TRACK*");
1155 cmd += wxT("(setf S 0.25)\n");
1156 }
1157 else
1158 {
1159 nyx_set_audio_name("S");
1160 cmd += wxT("(setf *TRACK* '*unbound*)\n");
1161 }
1162
1163 if (mVersion >= 4)
1164 {
1165 cmd += mProps;
1166 cmd += mPerTrackProps;
1167 }
1168
1169 const auto& mCurChannelGroup = nyxContext.mCurChannelGroup;
1170
1171 if ((mVersion >= 4) && (GetType() != EffectTypeTool))
1172 {
1173 // Set the track TYPE and VIEW properties
1174 wxString type;
1175 wxString view;
1176 wxString bitFormat;
1177 wxString spectralEditp;
1178
1179 mCurChannelGroup->TypeSwitch(
1180 [&](const WaveTrack& wt) {
1181 type = wxT("wave");
1182 spectralEditp = SpectrogramSettings::Get(*mCurChannelGroup)
1184 wxT("T") :
1185 wxT("NIL");
1186 view = wxT("NIL");
1187 // Find() not Get() to avoid creation-on-demand of views in case we
1188 // are only previewing
1189 const auto displays = GetDisplaysHook::Call(&wt);
1190 const auto format = [&](decltype(displays[0]) display) {
1191 // Get the English name of the view type, without menu codes,
1192 // as a string that Lisp can examine
1193 return wxString::Format(
1194 wxT("\"%s\""), display.name.Stripped().Debug());
1195 };
1196 if (displays.empty())
1197 ;
1198 else if (displays.size() == 1)
1199 view = format(displays[0]);
1200 else
1201 {
1202 view = wxT("(list");
1203 for (auto display : displays)
1204 view += wxString(wxT(" ")) + format(display);
1205 view += wxT(")");
1206 }
1207 },
1208#if defined(USE_MIDI)
1209 [&](const NoteTrack&) {
1210 type = wxT("midi");
1211 view = wxT("\"Midi\"");
1212 },
1213#endif
1214 [&](const LabelTrack&) {
1215 type = wxT("label");
1216 view = wxT("\"Label\"");
1217 },
1218 [&](const TimeTrack&) {
1219 type = wxT("time");
1220 view = wxT("\"Time\"");
1221 });
1222
1223 cmd +=
1224 wxString::Format(wxT("(putprop '*TRACK* %d 'INDEX)\n"), ++mTrackIndex);
1225 cmd += wxString::Format(
1226 wxT("(putprop '*TRACK* \"%s\" 'NAME)\n"),
1227 EscapeString(mCurChannelGroup->GetName()));
1228 cmd += wxString::Format(wxT("(putprop '*TRACK* \"%s\" 'TYPE)\n"), type);
1229 // Note: "View" property may change when Audacity's choice of track views
1230 // has stabilized.
1231 cmd += wxString::Format(wxT("(putprop '*TRACK* %s 'VIEW)\n"), view);
1232 cmd += wxString::Format(
1233 wxT("(putprop '*TRACK* %d 'CHANNELS)\n"), mCurNumChannels);
1234
1235 // NOTE: Audacity 2.1.3 True if spectral selection is enabled regardless
1236 // of track view.
1237 cmd += wxString::Format(
1238 wxT("(putprop '*TRACK* %s 'SPECTRAL-EDIT-ENABLED)\n"), spectralEditp);
1239
1240 const double startTime = mCurChannelGroup->GetStartTime();
1241 const double endTime = mCurChannelGroup->GetEndTime();
1242
1243 cmd += wxString::Format(
1244 wxT("(putprop '*TRACK* (float %s) 'START-TIME)\n"),
1245 Internat::ToString(startTime));
1246 cmd += wxString::Format(
1247 wxT("(putprop '*TRACK* (float %s) 'END-TIME)\n"),
1248 Internat::ToString(endTime));
1249 cmd += wxString::Format(
1250 wxT(
1251 "(putprop '*TRACK* (float %s) 'GAIN)\n"), // https://github.com/audacity/audacity/issues/7097:
1252 // not to break all
1253 // nyquist scripts out
1254 // there, we keep the old
1255 // name.
1256 Internat::ToString(mCurChannelGroup->GetVolume()));
1257 cmd += wxString::Format(
1258 wxT("(putprop '*TRACK* (float %s) 'PAN)\n"),
1259 Internat::ToString(mCurChannelGroup->GetPan()));
1260 cmd += wxString::Format(
1261 wxT("(putprop '*TRACK* (float %s) 'RATE)\n"),
1262 Internat::ToString(mCurChannelGroup->GetRate()));
1263
1264 switch (mCurChannelGroup->GetSampleFormat())
1265 {
1266 case int16Sample:
1267 bitFormat = wxT("16");
1268 break;
1269 case int24Sample:
1270 bitFormat = wxT("24");
1271 break;
1272 case floatSample:
1273 bitFormat = wxT("32.0");
1274 break;
1275 }
1276 cmd +=
1277 wxString::Format(wxT("(putprop '*TRACK* %s 'FORMAT)\n"), bitFormat);
1278
1279 float maxPeakLevel = 0.0; // Deprecated as of 2.1.3
1280 const auto inClipBoundaries = GetClipBoundaries(
1281 pOutputs ? pOutputs->GetMatchingInput(*mCurChannelGroup) : nullptr);
1282 const auto outClipBoundaries = GetClipBoundaries(mCurChannelGroup);
1283 wxString inClips, outClips, peakString, rmsString;
1284 auto& mCurTrack = nyxContext.mCurTrack;
1285 for (size_t i = 0; i < mCurNumChannels; i++)
1286 {
1287 float maxPeak = 0.0;
1288 if (mCurNumChannels > 1)
1289 {
1290 inClips += wxT("(list ");
1291 outClips += wxT("(list ");
1292 }
1293 inClips += inClipBoundaries;
1294 outClips += outClipBoundaries;
1295 if (mCurNumChannels > 1)
1296 {
1297 inClips += wxT(" )");
1298 outClips += wxT(" )");
1299 }
1300 float min, max;
1302 *mCurTrack[i], mT0, mT1); // may throw
1303 min = pair.first, max = pair.second;
1304 maxPeak = std::max(std::max(fabs(min), fabs(max)), maxPeak);
1305 maxPeakLevel = std::max(maxPeakLevel, maxPeak);
1306
1307 // On Debian, NaN samples give maxPeak = 3.40282e+38 (FLT_MAX)
1308 if (
1309 !std::isinf(maxPeak) && !std::isnan(maxPeak) && (maxPeak < FLT_MAX))
1310 {
1311 peakString += wxString::Format(
1312 wxT("(float %s) "), Internat::ToString(maxPeak));
1313 }
1314 else
1315 {
1316 peakString += wxT("nil ");
1317 }
1318
1319 float rms =
1320 WaveChannelUtilities::GetRMS(*mCurTrack[i], mT0, mT1); // may throw
1321 if (!std::isinf(rms) && !std::isnan(rms))
1322 {
1323 rmsString +=
1324 wxString::Format(wxT("(float %s) "), Internat::ToString(rms));
1325 }
1326 else
1327 {
1328 rmsString += wxT("NIL ");
1329 }
1330 }
1331 // A list of clips for mono, or an array of lists for multi-channel.
1332 cmd += wxString::Format(
1333 wxT("(putprop '*TRACK* %s%s ) 'INCLIPS)\n"),
1334 (mCurNumChannels == 1) ? wxT("(list ") : wxT("(vector "), inClips);
1335 cmd += wxString::Format(
1336 wxT("(putprop '*TRACK* %s%s ) 'CLIPS)\n"),
1337 (mCurNumChannels == 1) ? wxT("(list ") : wxT("(vector "), outClips);
1338
1339 (mCurNumChannels > 1) ?
1340 cmd += wxString::Format(
1341 wxT("(putprop '*SELECTION* (vector %s) 'PEAK)\n"), peakString) :
1342 cmd +=
1343 wxString::Format(wxT("(putprop '*SELECTION* %s 'PEAK)\n"), peakString);
1344
1345 if (
1346 !std::isinf(maxPeakLevel) && !std::isnan(maxPeakLevel) &&
1347 (maxPeakLevel < FLT_MAX))
1348 {
1349 cmd += wxString::Format(
1350 wxT("(putprop '*SELECTION* (float %s) 'PEAK-LEVEL)\n"),
1351 Internat::ToString(maxPeakLevel));
1352 }
1353
1354 (mCurNumChannels > 1) ?
1355 cmd += wxString::Format(
1356 wxT("(putprop '*SELECTION* (vector %s) 'RMS)\n"), rmsString) :
1357 cmd +=
1358 wxString::Format(wxT("(putprop '*SELECTION* %s 'RMS)\n"), rmsString);
1359 }
1360
1361 // If in tool mode, then we don't do anything with the track and selection.
1362 if (GetType() == EffectTypeTool)
1363 nyx_set_audio_params(44100, 0);
1364 else if (GetType() == EffectTypeGenerate)
1365 nyx_set_audio_params(mCurChannelGroup->GetRate(), 0);
1366 else
1367 {
1368 auto curLen = nyxContext.mCurLen.as_long_long();
1369 nyx_set_audio_params(mCurChannelGroup->GetRate(), curLen);
1370 nyx_set_input_audio(
1371 NyxContext::StaticGetCallback, &nyxContext, (int)mCurNumChannels,
1372 curLen, mCurChannelGroup->GetRate());
1373 }
1374
1375 // Restore the Nyquist sixteenth note symbol for Generate plug-ins.
1376 // See http://bugzilla.audacityteam.org/show_bug.cgi?id=490.
1377 if (GetType() == EffectTypeGenerate)
1378 {
1379 cmd += wxT("(setf s 0.25)\n");
1380 }
1381
1382 if (mDebug || mTrace)
1383 {
1384 cmd += wxT("(setf *tracenable* T)\n");
1385 if (mExternal)
1386 {
1387 cmd += wxT("(setf *breakenable* T)\n");
1388 }
1389 }
1390 else
1391 {
1392 // Explicitly disable backtrace and prevent values
1393 // from being carried through to the output.
1394 // This should be the final command before evaluating the Nyquist script.
1395 cmd += wxT("(setf *tracenable* NIL)\n");
1396 }
1397
1398 for (unsigned int j = 0; j < mControls.size(); j++)
1399 {
1400 if (
1401 mControls[j].type == NYQ_CTRL_FLOAT ||
1402 mControls[j].type == NYQ_CTRL_FLOAT_TEXT ||
1403 mControls[j].type == NYQ_CTRL_TIME)
1404 {
1405 // We use Internat::ToString() rather than "%f" here because we
1406 // always have to use the dot as decimal separator when giving
1407 // numbers to Nyquist, whereas using "%f" will use the user's
1408 // decimal separator which may be a comma in some countries.
1409 cmd += wxString::Format(
1410 wxT("(setf %s %s)\n"), mControls[j].var,
1411 Internat::ToString(mControls[j].val, 14));
1412 }
1413 else if (
1414 mControls[j].type == NYQ_CTRL_INT ||
1415 mControls[j].type == NYQ_CTRL_INT_TEXT ||
1416 mControls[j].type == NYQ_CTRL_CHOICE)
1417 {
1418 cmd += wxString::Format(
1419 wxT("(setf %s %d)\n"), mControls[j].var, (int)(mControls[j].val));
1420 }
1421 else if (
1422 mControls[j].type == NYQ_CTRL_STRING ||
1423 mControls[j].type == NYQ_CTRL_FILE)
1424 {
1425 cmd += wxT("(setf ");
1426 // restrict variable names to 7-bit ASCII:
1427 cmd += mControls[j].var;
1428 cmd += wxT(" \"");
1429 cmd += EscapeString(
1430 mControls[j].valStr); // unrestricted value will become quoted UTF-8
1431 cmd += wxT("\")\n");
1432 }
1433 }
1434
1435 if (mIsSal)
1436 {
1437 wxString str = EscapeString(mCmd);
1438 // this is tricky: we need SAL to call main so that we can get a
1439 // SAL traceback in the event of an error (sal-compile catches the
1440 // error and calls sal-error-output), but SAL does not return values.
1441 // We will catch the value in a special global aud:result and if no
1442 // error occurs, we will grab the value with a LISP expression
1443 str += wxT("\nset aud:result = main()\n");
1444
1445 if (mDebug || mTrace)
1446 {
1447 // since we're about to evaluate SAL, remove LISP trace enable and
1448 // break enable (which stops SAL processing) and turn on SAL stack
1449 // trace
1450 cmd += wxT("(setf *tracenable* nil)\n");
1451 cmd += wxT("(setf *breakenable* nil)\n");
1452 cmd += wxT("(setf *sal-traceback* t)\n");
1453 }
1454
1455 if (mCompiler)
1456 {
1457 cmd += wxT("(setf *sal-compiler-debug* t)\n");
1458 }
1459
1460 cmd += wxT("(setf *sal-call-stack* nil)\n");
1461 // if we do not set this here and an error occurs in main, another
1462 // error will be raised when we try to return the value of aud:result
1463 // which is unbound
1464 cmd += wxT("(setf aud:result nil)\n");
1465 cmd += wxT("(sal-compile-audacity \"") + str + wxT("\" t t nil)\n");
1466 // Capture the value returned by main (saved in aud:result), but
1467 // set aud:result to nil so sound results can be evaluated without
1468 // retaining audio in memory
1469 cmd += wxT("(prog1 aud:result (setf aud:result nil))\n");
1470 }
1471 else
1472 {
1473 cmd += mCmd;
1474 }
1475
1476 // Evaluate the expression, which may invoke the get callback, but often does
1477 // not, leaving that to delayed evaluation of the output sound
1478 rval = nyx_eval_expression(cmd.mb_str(wxConvUTF8));
1479
1480 // If we're not showing debug window, log errors and warnings:
1481 const auto output = mDebugOutput.Translation();
1482 if (!output.empty() && !mDebug && !mTrace)
1483 {
1484 /* i18n-hint: An effect "returned" a message.*/
1485 wxLogMessage(wxT("\'%s\' returned:\n%s"), mName.Translation(), output);
1486 }
1487
1488 // Audacity has no idea how long Nyquist processing will take, but
1489 // can monitor audio being returned.
1490 // Anything other than audio should be returned almost instantly
1491 // so notify the user that process has completed (bug 558)
1492 if (
1493 (rval != nyx_audio) &&
1494 ((mCount + mCurNumChannels) == mNumSelectedChannels))
1495 {
1496 if (mCurNumChannels == 1)
1497 {
1498 TrackProgress(mCount, 1.0, XO("Processing complete."));
1499 }
1500 else
1501 {
1502 TrackGroupProgress(mCount, 1.0, XO("Processing complete."));
1503 }
1504 }
1505
1506 if ((rval == nyx_audio) && (GetType() == EffectTypeTool))
1507 {
1508 // Catch this first so that we can also handle other errors.
1509 mDebugOutput =
1510 /* i18n-hint: Don't translate ';type tool'. */
1511 XO("';type tool' effects cannot return audio from Nyquist.\n") +
1513 rval = nyx_error;
1514 }
1515
1516 if ((rval == nyx_labels) && (GetType() == EffectTypeTool))
1517 {
1518 // Catch this first so that we can also handle other errors.
1519 mDebugOutput =
1520 /* i18n-hint: Don't translate ';type tool'. */
1521 XO("';type tool' effects cannot return labels from Nyquist.\n") +
1523 rval = nyx_error;
1524 }
1525
1526 if (rval == nyx_error)
1527 {
1528 // Return value is not valid type.
1529 // Show error in debug window if trace enabled, otherwise log.
1530 if (mTrace)
1531 {
1532 /* i18n-hint: "%s" is replaced by name of plug-in.*/
1533 mDebugOutput = XO("nyx_error returned from %s.\n")
1534 .Format(mName.empty() ? XO("plug-in") : mName) +
1536 mDebug = true;
1537 }
1538 else
1539 {
1540 wxLogMessage(
1541 "Nyquist returned nyx_error:\n%s", mDebugOutput.Translation());
1542 }
1543 return false;
1544 }
1545
1546 if (rval == nyx_list)
1547 {
1548 wxLogMessage("Nyquist returned nyx_list");
1549 if (GetType() == EffectTypeTool)
1550 {
1551 mProjectChanged = true;
1552 }
1553 else
1554 {
1555 BasicUI::ShowMessageBox(XO("Nyquist returned a list."));
1556 }
1557 return true;
1558 }
1559
1560 if (rval == nyx_string)
1561 {
1562 // Assume the string has already been translated within the Lisp runtime
1563 // if necessary, by one of the gettext functions defined below, before it
1564 // is communicated back to C++
1565 auto msg = Verbatim(NyquistToWxString(nyx_get_string()));
1566 if (!msg.empty())
1567 { // Empty string may be used as a No-Op return value.
1569 }
1570 else if (GetType() == EffectTypeTool)
1571 {
1572 // ;tools may change the project with aud-do commands so
1573 // it is essential that the state is added to history.
1574 mProjectChanged = true;
1575 return true;
1576 }
1577 else
1578 {
1579 // A true no-op.
1580 return true;
1581 }
1582
1583 // True if not process type.
1584 // If not returning audio from process effect,
1585 // return first result then stop (disables preview)
1586 // but allow all output from Nyquist Prompt.
1587 return (GetType() != EffectTypeProcess || mIsPrompt);
1588 }
1589
1590 if (rval == nyx_double)
1591 {
1592 auto str = XO("Nyquist returned the value: %f").Format(nyx_get_double());
1594 return (GetType() != EffectTypeProcess || mIsPrompt);
1595 }
1596
1597 if (rval == nyx_int)
1598 {
1599 auto str = XO("Nyquist returned the value: %d").Format(nyx_get_int());
1601 return (GetType() != EffectTypeProcess || mIsPrompt);
1602 }
1603
1604 if (rval == nyx_labels)
1605 {
1606 assert(GetType() != EffectTypeTool); // Guaranteed above
1607 // Therefore bOnePassTool was false in Process()
1608 // Therefore output tracks were allocated
1609 assert(pOutputs);
1610
1611 mProjectChanged = true;
1612 unsigned int numLabels = nyx_get_num_labels();
1613 unsigned int l;
1614 auto ltrack = *pOutputs->Get().Any<LabelTrack>().begin();
1615 if (!ltrack)
1616 {
1617 auto newTrack = std::make_shared<LabelTrack>();
1618 // new track name should be unique among the names in the list of input
1619 // tracks, not output
1620 newTrack->SetName(
1621 inputTracks()->MakeUniqueTrackName(LabelTrack::GetDefaultName()));
1622 ltrack =
1623 static_cast<LabelTrack*>(pOutputs->AddToOutputTracks(newTrack));
1624 }
1625
1626 for (l = 0; l < numLabels; l++)
1627 {
1628 double t0, t1;
1629 const char* str;
1630
1631 // PRL: to do:
1632 // let Nyquist analyzers define more complicated selections
1633 nyx_get_label(l, &t0, &t1, &str);
1634
1635 ltrack->AddLabel(SelectedRegion(t0 + mT0, t1 + mT0), UTF8CTOWX(str));
1636 }
1637 return (GetType() != EffectTypeProcess || mIsPrompt);
1638 }
1639
1640 wxASSERT(rval == nyx_audio);
1641
1642 int outChannels = nyx_get_audio_num_channels();
1643 if (outChannels > (int)mCurNumChannels)
1644 {
1646 XO("Nyquist returned too many audio channels.\n"));
1647 return false;
1648 }
1649
1650 if (outChannels == -1)
1651 {
1653 XO("Nyquist returned one audio channel as an array.\n"));
1654 return false;
1655 }
1656
1657 if (outChannels == 0)
1658 {
1659 BasicUI::ShowMessageBox(XO("Nyquist returned an empty array.\n"));
1660 return false;
1661 }
1662
1663 nyxContext.mOutputTrack = mCurChannelGroup->EmptyCopy();
1664 auto out = nyxContext.mOutputTrack;
1665
1666 // Now fully evaluate the sound
1667 int success = nyx_get_audio(NyxContext::StaticPutCallback, &nyxContext);
1668
1669 // See if GetCallback found read errors
1670 if (auto pException = nyxContext.mpException)
1671 std::rethrow_exception(pException);
1672
1673 if (!success)
1674 return false;
1675
1676 mOutputTime = out->GetEndTime();
1677 if (mOutputTime <= 0)
1678 {
1679 BasicUI::ShowMessageBox(XO("Nyquist returned nil audio.\n"));
1680 return false;
1681 }
1682
1683 WaveTrack::Holder tempTrack;
1684 if (outChannels < static_cast<int>(mCurNumChannels))
1685 {
1686 // Be careful to do this before duplication
1687 out->Flush();
1688 // Must destroy one temporary list before repopulating another with
1689 // correct channel grouping
1690 nyxContext.mOutputTrack.reset();
1691 tempTrack = out->MonoToStereo();
1692 }
1693 else
1694 {
1695 tempTrack = move(nyxContext.mOutputTrack);
1696 out->Flush();
1697 }
1698
1699 {
1700 const bool bMergeClips = (mMergeClips < 0)
1701 // Use sample counts to determine default
1702 // behaviour - times will rarely be equal.
1703 ?
1704 (out->TimeToLongSamples(mT0) +
1705 out->TimeToLongSamples(mOutputTime) ==
1706 out->TimeToLongSamples(mT1)) :
1707 mMergeClips != 0;
1708 PasteTimeWarper warper { mT1, mT0 + tempTrack->GetEndTime() };
1709 mCurChannelGroup->ClearAndPaste(
1710 mT0, mT1, *tempTrack, mRestoreSplits, bMergeClips, &warper);
1711 }
1712
1713 // If we were first in the group adjust non-selected group tracks
1714 if (mFirstInGroup)
1715 {
1716 for (auto t : SyncLock::Group(*mCurChannelGroup))
1717 if (!t->GetSelected() && SyncLock::IsSyncLockSelected(*t))
1718 t->SyncLockAdjust(mT1, mT0 + out->GetEndTime());
1719
1720 // Only the first channel can be first in its group
1721 mFirstInGroup = false;
1722 }
1723
1724 mProjectChanged = true;
1725 return true;
1726}
1727
1728// ============================================================================
1729// NyquistBase Implementation
1730// ============================================================================
1731
1732wxString NyquistBase::NyquistToWxString(const char* nyqString)
1733{
1734 wxString str(nyqString, wxConvUTF8);
1735 if (nyqString != NULL && nyqString[0] && str.empty())
1736 {
1737 // invalid UTF-8 string, convert as Latin-1
1738 str = _(
1739 "[Warning: Nyquist returned invalid UTF-8 string, converted here as Latin-1]");
1740 // TODO: internationalization of strings from Nyquist effects, at least
1741 // from those shipped with Audacity
1742 str += LAT1CTOWX(nyqString);
1743 }
1744 return str;
1745}
1746
1747wxString NyquistBase::EscapeString(const wxString& inStr)
1748{
1749 wxString str = inStr;
1750
1751 str.Replace(wxT("\\"), wxT("\\\\"));
1752 str.Replace(wxT("\""), wxT("\\\""));
1753
1754 return str;
1755}
1756
1757std::vector<EnumValueSymbol> NyquistBase::ParseChoice(const wxString& text)
1758{
1759 std::vector<EnumValueSymbol> results;
1760 if (text[0] == wxT('('))
1761 {
1762 // New style: expecting a Lisp-like list of strings
1763 Tokenizer tzer;
1764 tzer.Tokenize(text, true, 1, 1);
1765 auto& choices = tzer.tokens;
1766 wxString extra;
1767 for (auto& choice : choices)
1768 {
1769 auto label = UnQuote(choice, true, &extra);
1770 if (extra.empty())
1771 results.push_back(TranslatableString { label, {} });
1772 else
1773 results.push_back({ extra, TranslatableString { label, {} } });
1774 }
1775 }
1776 else
1777 {
1778 // Old style: expecting a comma-separated list of
1779 // un-internationalized names, ignoring leading and trailing spaces
1780 // on each; and the whole may be quoted
1781 auto choices = wxStringTokenize(
1782 text[0] == wxT('"') ? text.Mid(1, text.length() - 2) : text, wxT(","));
1783 for (auto& choice : choices)
1784 results.push_back({ choice.Trim(true).Trim(false) });
1785 }
1786 return results;
1787}
1788
1790{
1791 // todo: error handling
1792 FileExtensions results;
1793 if (text[0] == wxT('('))
1794 {
1795 Tokenizer tzer;
1796 tzer.Tokenize(text, true, 1, 1);
1797 for (const auto& token : tzer.tokens)
1798 results.push_back(UnQuote(token));
1799 }
1800 return results;
1801}
1802
1804{
1805 // todo: error handling
1806 FileNames::FileType result;
1807 if (text[0] == wxT('('))
1808 {
1809 Tokenizer tzer;
1810 tzer.Tokenize(text, true, 1, 1);
1811 auto& tokens = tzer.tokens;
1812 if (tokens.size() == 2)
1813 result = { UnQuoteMsgid(tokens[0]), ParseFileExtensions(tokens[1]) };
1814 }
1815 return result;
1816}
1817
1819{
1820 // todo: error handling
1821 FileNames::FileTypes results;
1822 if (text[0] == wxT('('))
1823 {
1824 Tokenizer tzer;
1825 tzer.Tokenize(text, true, 1, 1);
1826 auto& types = tzer.tokens;
1827 if (!types.empty() && types[0][0] == wxT('('))
1828 for (auto& type : types)
1829 results.push_back(ParseFileType(type));
1830 }
1831 if (results.empty())
1832 {
1833 // Old-style is a specially formatted string, maybe translated
1834 // Parse it for compatibility
1835 auto str = UnQuote(text);
1836 auto pieces = wxSplit(str, '|');
1837 // Should have an even number
1838 auto size = pieces.size();
1839 if (size % 2 == 1)
1840 --size, pieces.pop_back();
1841 for (size_t ii = 0; ii < size; ii += 2)
1842 {
1843 FileExtensions extensions;
1844 auto extensionStrings = wxSplit(pieces[ii + 1], ';');
1845 for (const auto& extensionString : extensionStrings)
1846 if (extensionString.StartsWith(wxT("*.")))
1847 {
1848 auto ext = extensionString.substr(2);
1849 if (ext == wxT("*"))
1850 // "*.*" to match all
1851 ext.clear();
1852 extensions.push_back(ext);
1853 }
1854 results.push_back({ Verbatim(pieces[ii]), extensions });
1855 }
1856 }
1857 return results;
1858}
1859
1861{
1862 mRedirectOutput = true;
1863}
1864
1865void NyquistBase::SetCommand(const wxString& cmd)
1866{
1867 mExternal = true;
1868
1869 if (cmd.size())
1870 {
1871 ParseCommand(cmd);
1872 }
1873}
1874
1876{
1877 mBreak = true;
1878}
1879
1881{
1882 mCont = true;
1883}
1884
1886{
1887 mStop = true;
1888}
1889
1891 const wxString& s, bool allowParens, wxString* pExtraString)
1892{
1893 if (pExtraString)
1894 *pExtraString = wxString {};
1895
1896 int len = s.length();
1897 if (len >= 2 && s[0] == wxT('\"') && s[len - 1] == wxT('\"'))
1898 {
1899 auto unquoted = s.Mid(1, len - 2);
1900 // Sorry, no context strings, yet
1901 // (See also comments in NyquistEffectsModule::AutoRegisterPlugins)
1902 return TranslatableString { unquoted, {} };
1903 }
1904 else if (
1905 allowParens && len >= 2 && s[0] == wxT('(') && s[len - 1] == wxT(')'))
1906 {
1907 Tokenizer tzer;
1908 tzer.Tokenize(s, true, 1, 1);
1909 auto& tokens = tzer.tokens;
1910 if (tokens.size() > 1)
1911 {
1912 if (pExtraString && tokens[1][0] == '(')
1913 {
1914 // A choice with a distinct internal string form like
1915 // ("InternalString" (_ "Visible string"))
1916 // Recur to find the two strings
1917 *pExtraString = UnQuote(tokens[0], false);
1918 return UnQuoteMsgid(tokens[1]);
1919 }
1920 else
1921 {
1922 // Assume the first token was _ -- we don't check that
1923 // And the second is the string, which is internationalized
1924 // Sorry, no context strings, yet
1925 return UnQuoteMsgid(tokens[1], false);
1926 }
1927 }
1928 else
1929 return {};
1930 }
1931 else
1932 // If string was not quoted, assume no translation exists
1933 return Verbatim(s);
1934}
1935
1937 const wxString& s, bool allowParens, wxString* pExtraString)
1938{
1939 return UnQuoteMsgid(s, allowParens, pExtraString).Translation();
1940}
1941
1942double NyquistBase::GetCtrlValue(const wxString& s)
1943{
1944 /* For this to work correctly requires that the plug-in header is
1945 * parsed on each run so that the correct value for "half-srate" may
1946 * be determined.
1947 *
1948 auto project = FindProject();
1949 if (project && s.IsSameAs(wxT("half-srate"), false)) {
1950 auto rate =
1951 TrackList::Get( *project ).Selected< const WaveTrack >()
1952 .min( &WaveTrack::GetRate );
1953 return (rate / 2.0);
1954 }
1955 */
1956
1958}
1959
1961 const wxString& line, bool eof, size_t trimStart, size_t trimEnd)
1962{
1963 auto endToken = [&] {
1964 if (!tok.empty())
1965 {
1966 tokens.push_back(tok);
1967 tok = wxT("");
1968 }
1969 };
1970
1971 for (auto c :
1972 make_iterator_range(line.begin() + trimStart, line.end() - trimEnd))
1973 {
1974 if (q && !sl && c == wxT('\\'))
1975 {
1976 // begin escaped character, only within quotes
1977 sl = true;
1978 continue;
1979 }
1980
1981 if (!sl && c == wxT('"'))
1982 {
1983 // Unescaped quote
1984 if (!q)
1985 {
1986 // start of string
1987 if (!paren)
1988 // finish previous token
1989 endToken();
1990 // Include the delimiter in the token
1991 tok += c;
1992 q = true;
1993 }
1994 else
1995 {
1996 // end of string
1997 // Include the delimiter in the token
1998 tok += c;
1999 if (!paren)
2000 endToken();
2001 q = false;
2002 }
2003 }
2004 else if (!q && !paren && (c == wxT(' ') || c == wxT('\t')))
2005 // Unenclosed whitespace
2006 // Separate tokens; don't accumulate this character
2007 endToken();
2008 else if (!q && c == wxT(';'))
2009 // semicolon not in quotes, but maybe in parentheses
2010 // Lisp style comments with ; (but not with #| ... |#) are allowed
2011 // within a wrapped header multi-line, so that i18n hint comments may
2012 // be placed before strings and found by xgettext
2013 break;
2014 else if (!q && c == wxT('('))
2015 {
2016 // Start of list or sublist
2017 if (++paren == 1)
2018 // finish previous token; begin list, including the delimiter
2019 endToken(), tok += c;
2020 else
2021 // defer tokenizing of nested list to a later pass over the token
2022 tok += c;
2023 }
2024 else if (!q && c == wxT(')'))
2025 {
2026 // End of list or sublist
2027 if (--paren == 0)
2028 // finish list, including the delimiter
2029 tok += c, endToken();
2030 else if (paren < 0)
2031 // forgive unbalanced right paren
2032 paren = 0, endToken();
2033 else
2034 // nested list; deferred tokenizing
2035 tok += c;
2036 }
2037 else
2038 {
2039 if (sl && paren)
2040 // Escaped character in string inside list, to be parsed again
2041 // Put the escape back for the next pass
2042 tok += wxT('\\');
2043 if (sl && !paren && c == 'n')
2044 // Convert \n to newline, the only special escape besides \\ or \"
2045 // But this should not be used if a string needs to localize.
2046 // Instead, simply put a line break in the string.
2047 c = '\n';
2048 tok += c;
2049 }
2050
2051 sl = false;
2052 }
2053
2054 if (eof || (!q && !paren))
2055 {
2056 endToken();
2057 return true;
2058 }
2059 else
2060 {
2061 // End of line but not of file, and a string or list is yet unclosed
2062 // If a string, accumulate a newline character
2063 if (q)
2064 tok += wxT('\n');
2065 return false;
2066 }
2067}
2068
2070 Tokenizer& tzer, const wxString& line, bool eof, bool first)
2071{
2072 if (!tzer.Tokenize(line, eof, first ? 1 : 0, 0))
2073 return false;
2074
2075 const auto& tokens = tzer.tokens;
2076 int len = tokens.size();
2077 if (len < 1)
2078 {
2079 return true;
2080 }
2081
2082 // Consistency decision is for "plug-in" as the correct spelling
2083 // "plugin" (deprecated) is allowed as an undocumented convenience.
2084 if (
2085 len == 2 && tokens[0] == wxT("nyquist") &&
2086 (tokens[1] == wxT("plug-in") || tokens[1] == wxT("plugin")))
2087 {
2088 mOK = true;
2089 return true;
2090 }
2091
2092 if (len >= 2 && tokens[0] == wxT("type"))
2093 {
2094 wxString tok = tokens[1];
2095 mIsTool = false;
2096 if (tok == wxT("tool"))
2097 {
2098 mIsTool = true;
2100 // we allow
2101 // ;type tool
2102 // ;type tool process
2103 // ;type tool generate
2104 // ;type tool analyze
2105 // The last three are placed in the tool menu, but are processed as
2106 // process, generate or analyze.
2107 if (len >= 3)
2108 tok = tokens[2];
2109 }
2110
2111 if (tok == wxT("process"))
2112 {
2114 }
2115 else if (tok == wxT("generate"))
2116 {
2118 }
2119 else if (tok == wxT("analyze"))
2120 {
2122 }
2123
2124 if (len >= 3 && tokens[2] == wxT("spectral"))
2125 {
2126 ;
2127 mIsSpectral = true;
2128 }
2129 return true;
2130 }
2131
2132 if (len == 2 && tokens[0] == wxT("codetype"))
2133 {
2134 // This will stop ParseProgram() from doing a best guess as program type.
2135 if (tokens[1] == wxT("lisp"))
2136 {
2137 mIsSal = false;
2138 mFoundType = true;
2139 }
2140 else if (tokens[1] == wxT("sal"))
2141 {
2142 mIsSal = true;
2143 mFoundType = true;
2144 }
2145 return true;
2146 }
2147
2148 if (len >= 2 && tokens[0] == wxT("debugflags"))
2149 {
2150 for (int i = 1; i < len; i++)
2151 {
2152 // "trace" sets *tracenable* (LISP) or *sal-traceback* (SAL)
2153 // and displays debug window IF there is anything to show.
2154 if (tokens[i] == wxT("trace"))
2155 {
2156 mTrace = true;
2157 }
2158 else if (tokens[i] == wxT("notrace"))
2159 {
2160 mTrace = false;
2161 }
2162 else if (tokens[i] == wxT("compiler"))
2163 {
2164 mCompiler = true;
2165 }
2166 else if (tokens[i] == wxT("nocompiler"))
2167 {
2168 mCompiler = false;
2169 }
2170 }
2171 return true;
2172 }
2173
2174 // We support versions 1, 2 and 3
2175 // (Version 2 added support for string parameters.)
2176 // (Version 3 added support for choice parameters.)
2177 // (Version 4 added support for project/track/selection information.)
2178 if (len >= 2 && tokens[0] == wxT("version"))
2179 {
2180 long v;
2181 tokens[1].ToLong(&v);
2182 if (v < 1 || v > 4)
2183 {
2184 // This is an unsupported plug-in version
2185 mOK = false;
2186 mInitError =
2187 XO("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 {
2196 // Names do not yet support context strings for translations, or
2197 // internal names distinct from visible English names.
2198 // (See also comments in NyquistEffectsModule::AutoRegisterPlugins)
2199 auto name = UnQuote(tokens[1]);
2200 // Strip ... from name if it's present, perhaps in third party plug-ins
2201 // Menu system puts ... back if there are any controls
2202 // This redundant naming convention must NOT be followed for
2203 // shipped Nyquist effects with internationalization. Else the msgid
2204 // later looked up will lack the ... and will not be found.
2205 if (name.EndsWith(wxT("...")))
2206 name = name.RemoveLast(3);
2207 mName = TranslatableString { name, {} };
2208 return true;
2209 }
2210
2211 if (len >= 2 && tokens[0] == wxT("action"))
2212 {
2213 mAction = TranslatableString { UnQuote(tokens[1]), {} };
2214 return true;
2215 }
2216
2217 if (len >= 2 && tokens[0] == wxT("info"))
2218 {
2219 mInfo = TranslatableString { UnQuote(tokens[1]), {} };
2220 return true;
2221 }
2222
2223 if (len >= 2 && tokens[0] == wxT("preview"))
2224 {
2225 if (tokens[1] == wxT("enabled") || tokens[1] == wxT("true"))
2226 {
2227 mEnablePreview = true;
2228 SetLinearEffectFlag(false);
2229 }
2230 else if (tokens[1] == wxT("linear"))
2231 {
2232 mEnablePreview = true;
2233 SetLinearEffectFlag(true);
2234 }
2235 else if (tokens[1] == wxT("selection"))
2236 {
2237 mEnablePreview = true;
2239 }
2240 else if (tokens[1] == wxT("disabled") || tokens[1] == wxT("false"))
2241 {
2242 mEnablePreview = false;
2243 }
2244 return true;
2245 }
2246
2247 // Maximum number of samples to be processed. This can help the
2248 // progress bar if effect does not process all of selection.
2249 if (len >= 2 && tokens[0] == wxT("maxlen"))
2250 {
2251 long long v; // Note that Nyquist may overflow at > 2^31 samples (bug 439)
2252 tokens[1].ToLongLong(&v);
2253 mMaxLen = (sampleCount)v;
2254 }
2255
2256 if (len >= 2 && tokens[0] == wxT("mergeclips"))
2257 {
2258 long v;
2259 // -1 = auto (default), 0 = don't merge clips, 1 = do merge clips
2260 tokens[1].ToLong(&v);
2261 mMergeClips = v;
2262 return true;
2263 }
2264
2265 if (len >= 2 && tokens[0] == wxT("restoresplits"))
2266 {
2267 long v;
2268 // Splits are restored by default. Set to 0 to prevent.
2269 tokens[1].ToLong(&v);
2270 mRestoreSplits = !!v;
2271 return true;
2272 }
2273
2274 if (len >= 2 && tokens[0] == wxT("author"))
2275 {
2276 mAuthor = TranslatableString { UnQuote(tokens[1]), {} };
2277 return true;
2278 }
2279
2280 if (len >= 2 && tokens[0] == wxT("release"))
2281 {
2282 // Value must be quoted if the release version string contains spaces.
2283 mReleaseVersion = TranslatableString { UnQuote(tokens[1]), {} };
2284 return true;
2285 }
2286
2287 if (len >= 2 && tokens[0] == wxT("copyright"))
2288 {
2289 mCopyright = TranslatableString { UnQuote(tokens[1]), {} };
2290 return true;
2291 }
2292
2293 // Page name in Audacity development manual
2294 if (len >= 2 && tokens[0] == wxT("manpage"))
2295 {
2296 // do not translate
2297 mManPage = UnQuote(tokens[1], false);
2298 return true;
2299 }
2300
2301 // Local Help file
2302 if (len >= 2 && tokens[0] == wxT("helpfile"))
2303 {
2304 // do not translate
2305 mHelpFile = UnQuote(tokens[1], false);
2306 return true;
2307 }
2308
2309 // Debug button may be disabled for release plug-ins.
2310 if (len >= 2 && tokens[0] == wxT("debugbutton"))
2311 {
2312 if (tokens[1] == wxT("disabled") || tokens[1] == wxT("false"))
2313 {
2314 mDebugButton = false;
2315 }
2316 return true;
2317 }
2318
2319 if (len >= 3 && tokens[0] == wxT("control"))
2320 {
2321 NyqControl ctrl;
2322
2323 if (len == 3 && tokens[1] == wxT("text"))
2324 {
2325 ctrl.var = tokens[1];
2326 ctrl.label = UnQuote(tokens[2]);
2327 ctrl.type = NYQ_CTRL_TEXT;
2328 }
2329 else if (len >= 5)
2330 {
2331 ctrl.var = tokens[1];
2332 ctrl.name = UnQuote(tokens[2]);
2333 // 3 is type, below
2334 ctrl.label = tokens[4];
2335
2336 // valStr may or may not be a quoted string
2337 ctrl.valStr = len > 5 ? tokens[5] : wxString {};
2338 ctrl.val = GetCtrlValue(ctrl.valStr);
2339 if (
2340 ctrl.valStr.length() > 0 &&
2341 (ctrl.valStr[0] == wxT('(') || ctrl.valStr[0] == wxT('"')))
2342 ctrl.valStr = UnQuote(ctrl.valStr);
2343
2344 // 6 is minimum, below
2345 // 7 is maximum, below
2346
2347 if (tokens[3] == wxT("string"))
2348 {
2349 ctrl.type = NYQ_CTRL_STRING;
2350 ctrl.label = UnQuote(ctrl.label);
2351 }
2352 else if (tokens[3] == wxT("choice"))
2353 {
2354 ctrl.type = NYQ_CTRL_CHOICE;
2355 ctrl.choices = ParseChoice(ctrl.label);
2356 ctrl.label = wxT("");
2357 }
2358 else if (tokens[3] == wxT("file"))
2359 {
2360 ctrl.type = NYQ_CTRL_FILE;
2361 ctrl.fileTypes = ParseFileTypes(tokens[6]);
2362 // will determine file dialog styles:
2363 ctrl.highStr = UnQuote(tokens[7]);
2364 ctrl.label = UnQuote(ctrl.label);
2365 }
2366 else
2367 {
2368 ctrl.label = UnQuote(ctrl.label);
2369
2370 if (len < 8)
2371 {
2372 return true;
2373 }
2374
2375 if (
2376 (tokens[3] == wxT("float")) ||
2377 (tokens[3] == wxT("real"))) // Deprecated
2378 ctrl.type = NYQ_CTRL_FLOAT;
2379 else if (tokens[3] == wxT("int"))
2380 ctrl.type = NYQ_CTRL_INT;
2381 else if (tokens[3] == wxT("float-text"))
2383 else if (tokens[3] == wxT("int-text"))
2384 ctrl.type = NYQ_CTRL_INT_TEXT;
2385 else if (tokens[3] == wxT("time"))
2386 ctrl.type = NYQ_CTRL_TIME;
2387 else
2388 {
2389 wxString str;
2390 str.Printf(
2391 wxT(
2392 "Bad Nyquist 'control' type specification: '%s' in plug-in file '%s'.\nControl not created."),
2393 tokens[3], mFileName.GetFullPath());
2394
2395 // Too disturbing to show alert before Audacity frame is up.
2396 // EffectUIServices::DoMessageBox(*this,
2397 // str,
2398 // wxOK | wxICON_EXCLAMATION,
2399 // XO("Nyquist Warning") );
2400
2401 // Note that the AudacityApp's mLogger has not yet been created,
2402 // so this brings up an alert box, but after the Audacity frame
2403 // is up.
2404 wxLogWarning(str);
2405 return true;
2406 }
2407
2408 ctrl.lowStr = UnQuote(tokens[6]);
2409 if (
2410 ctrl.type == NYQ_CTRL_INT_TEXT &&
2411 ctrl.lowStr.IsSameAs(wxT("nil"), false))
2412 {
2413 ctrl.low = INT_MIN;
2414 }
2415 else if (
2416 ctrl.type == NYQ_CTRL_FLOAT_TEXT &&
2417 ctrl.lowStr.IsSameAs(wxT("nil"), false))
2418 {
2419 ctrl.low = -(FLT_MAX);
2420 }
2421 else if (
2422 ctrl.type == NYQ_CTRL_TIME &&
2423 ctrl.lowStr.IsSameAs(wxT("nil"), false))
2424 {
2425 ctrl.low = 0.0;
2426 }
2427 else
2428 {
2429 ctrl.low = GetCtrlValue(ctrl.lowStr);
2430 }
2431
2432 ctrl.highStr = UnQuote(tokens[7]);
2433 if (
2434 ctrl.type == NYQ_CTRL_INT_TEXT &&
2435 ctrl.highStr.IsSameAs(wxT("nil"), false))
2436 {
2437 ctrl.high = INT_MAX;
2438 }
2439 else if (
2440 (ctrl.type == NYQ_CTRL_FLOAT_TEXT ||
2441 ctrl.type == NYQ_CTRL_TIME) &&
2442 ctrl.highStr.IsSameAs(wxT("nil"), false))
2443 {
2444 ctrl.high = FLT_MAX;
2445 }
2446 else
2447 {
2448 ctrl.high = GetCtrlValue(ctrl.highStr);
2449 }
2450
2451 if (ctrl.high < ctrl.low)
2452 {
2453 ctrl.high = ctrl.low;
2454 }
2455
2456 if (ctrl.val < ctrl.low)
2457 {
2458 ctrl.val = ctrl.low;
2459 }
2460
2461 if (ctrl.val > ctrl.high)
2462 {
2463 ctrl.val = ctrl.high;
2464 }
2465
2466 ctrl.ticks = 1000;
2467 if (
2468 ctrl.type == NYQ_CTRL_INT && (ctrl.high - ctrl.low < ctrl.ticks))
2469 {
2470 ctrl.ticks = (int)(ctrl.high - ctrl.low);
2471 }
2472 }
2473 }
2474
2475 if (!make_iterator_range(mPresetNames).contains(ctrl.var))
2476 {
2477 mControls.push_back(ctrl);
2478 }
2479 }
2480
2481 // Deprecated
2482 if (len >= 2 && tokens[0] == wxT("categories"))
2483 {
2484 for (size_t i = 1; i < tokens.size(); ++i)
2485 {
2486 mCategories.push_back(tokens[i]);
2487 }
2488 }
2489 return true;
2490}
2491
2492bool NyquistBase::ParseProgram(wxInputStream& stream)
2493{
2494 if (!stream.IsOk())
2495 {
2496 mInitError = XO("Could not open file");
2497 return false;
2498 }
2499
2500 wxTextInputStream pgm(stream, wxT(" \t"), wxConvAuto());
2501
2502 mCmd = wxT("");
2503 mCmd.Alloc(10000);
2504 mIsSal = false;
2505 mControls.clear();
2506 mCategories.clear();
2507 mIsSpectral = false;
2508 mManPage = wxEmptyString; // If not wxEmptyString, must be a page in the
2509 // Audacity manual.
2510 mHelpFile =
2511 wxEmptyString; // If not wxEmptyString, must be a valid HTML help file.
2512 mHelpFileExists = false;
2513 mDebug = false;
2514 mTrace = false;
2515 mDebugButton = true; // Debug button enabled by default.
2516 mEnablePreview = true; // Preview button enabled by default.
2517
2518 // Bug 1934.
2519 // All Nyquist plug-ins should have a ';type' field, but if they don't we
2520 // default to being an Effect.
2522
2523 mFoundType = false;
2524 while (!stream.Eof() && stream.IsOk())
2525 {
2526 wxString line = pgm.ReadLine();
2527 if (
2528 line.length() > 1 &&
2529 // New in 2.3.0: allow magic comment lines to start with $
2530 // The trick is that xgettext will not consider such lines comments
2531 // and will extract the strings they contain
2532 (line[0] == wxT(';') || line[0] == wxT('$')))
2533 {
2534 Tokenizer tzer;
2535 unsigned nLines = 1;
2536 bool done;
2537 // Allow continuations within control lines.
2538 bool control = line[0] == wxT('$') || line.StartsWith(wxT(";control"));
2539 do
2540 done = Parse(tzer, line, !control || stream.Eof(), nLines == 1);
2541 while (!done && (line = pgm.ReadLine(), ++nLines, true));
2542
2543 // Don't pass these lines to the interpreter, so it doesn't get
2544 // confused by $, but pass blanks, so that SAL effects compile with
2545 // proper line numbers
2546 while (nLines--)
2547 mCmd += wxT('\n');
2548 }
2549 else
2550 {
2551 if (!mFoundType && line.length() > 0)
2552 {
2553 if (
2554 line[0] == wxT('(') ||
2555 (line[0] == wxT('#') && line.length() > 1 &&
2556 line[1] == wxT('|')))
2557 {
2558 mIsSal = false;
2559 mFoundType = true;
2560 }
2561 else if (line.Upper().Find(wxT("RETURN")) != wxNOT_FOUND)
2562 {
2563 mIsSal = true;
2564 mFoundType = true;
2565 }
2566 }
2567 mCmd += line + wxT("\n");
2568 }
2569 }
2570 if (!mFoundType && mIsPrompt)
2571 {
2572 using namespace BasicUI;
2573 /* i1n-hint: SAL and LISP are names for variant syntaxes for the
2574 Nyquist programming language. Leave them, and 'return', untranslated. */
2576 XO("Your code looks like SAL syntax, but there is no \'return\' statement.\n\
2577For SAL, use a return statement such as:\n\treturn *track* * 0.1\n\
2578or for LISP, begin with an open parenthesis such as:\n\t(mult *track* 0.1)\n ."),
2579 MessageBoxOptions {}.IconStyle(Icon::Error));
2580 /* i18n-hint: refers to programming "languages" */
2581 mInitError = XO("Could not determine language");
2582 return false;
2583 // Else just throw it at Nyquist to see what happens
2584 }
2585
2586 const auto helpStuff = CheckHelpPage();
2587 mHelpFileExists = helpStuff.first;
2588 mHelpPage = helpStuff.second;
2589
2590 return true;
2591}
2592
2594{
2595 wxFileInputStream rawStream(mFileName.GetFullPath());
2596 wxBufferedInputStream stream(rawStream, 10000);
2597
2598 ParseProgram(stream);
2599}
2600
2601bool NyquistBase::ParseCommand(const wxString& cmd)
2602{
2603 wxStringInputStream stream(cmd + wxT(" "));
2604
2605 return ParseProgram(stream);
2606}
2607
2609 float* buffer, int channel, int64_t start, int64_t len, int64_t totlen,
2610 void* userdata)
2611{
2612 auto This = static_cast<NyxContext*>(userdata);
2613 return This->GetCallback(buffer, channel, start, len, totlen);
2614}
2615
2617 float* buffer, int ch, int64_t start, int64_t len, int64_t)
2618{
2619 if (mCurBuffer[ch])
2620 {
2621 if (
2622 (mCurStart + start) < mCurBufferStart[ch] ||
2623 (mCurStart + start) + len > mCurBufferStart[ch] + mCurBufferLen[ch])
2624 {
2625 mCurBuffer[ch].reset();
2626 }
2627 }
2628
2629 if (!mCurBuffer[ch])
2630 {
2631 mCurBufferStart[ch] = (mCurStart + start);
2632 mCurBufferLen[ch] = mCurTrack[ch]->GetBestBlockSize(mCurBufferStart[ch]);
2633
2634 if (mCurBufferLen[ch] < (size_t)len)
2635 mCurBufferLen[ch] = mCurTrack[ch]->GetIdealBlockSize();
2636
2637 mCurBufferLen[ch] = limitSampleBufferSize(
2638 mCurBufferLen[ch], mCurStart + mCurLen - mCurBufferStart[ch]);
2639
2640 // C++20
2641 // mCurBuffer[ch] = std::make_unique_for_overwrite(mCurBufferLen[ch]);
2642 mCurBuffer[ch] = Buffer { safenew float[mCurBufferLen[ch]] };
2643 try
2644 {
2645 mCurTrack[ch]->GetFloats(
2646 mCurBuffer[ch].get(), mCurBufferStart[ch], mCurBufferLen[ch]);
2647 }
2648 catch (...)
2649 {
2650 // Save the exception object for re-throw when out of the library
2651 mpException = std::current_exception();
2652 return -1;
2653 }
2654 }
2655
2656 // We have guaranteed above that this is nonnegative and bounded by
2657 // mCurBufferLen[ch]:
2658 auto offset = (mCurStart + start - mCurBufferStart[ch]).as_size_t();
2659 const void* src = &mCurBuffer[ch][offset];
2660 std::memcpy(buffer, src, len * sizeof(float));
2661
2662 if (ch == 0)
2663 {
2664 double progress = mScale * ((start + len) / mCurLen.as_double());
2665 if (progress > mProgressIn)
2666 mProgressIn = progress;
2667 if (mProgressReport(mProgressIn + mProgressOut + mProgressTot))
2668 return -1;
2669 }
2670
2671 return 0;
2672}
2673
2675 float* buffer, int channel, int64_t start, int64_t len, int64_t totlen,
2676 void* userdata)
2677{
2678 auto This = static_cast<NyxContext*>(userdata);
2679 return This->PutCallback(buffer, channel, start, len, totlen);
2680}
2681
2683 float* buffer, int channel, int64_t start, int64_t len, int64_t totlen)
2684{
2685 // Don't let C++ exceptions propagate through the Nyquist library
2686 return GuardedCall<int>(
2687 [&] {
2688 if (channel == 0)
2689 {
2690 double progress = mScale * ((float)(start + len) / totlen);
2691 if (progress > mProgressOut)
2692 mProgressOut = progress;
2693 if (mProgressReport(mProgressIn + mProgressOut + mProgressTot))
2694 return -1;
2695 }
2696
2697 auto iChannel = mOutputTrack->Channels().begin();
2698 std::advance(iChannel, channel);
2699 const auto pChannel = *iChannel;
2700 pChannel->Append((samplePtr)buffer, floatSample, len);
2701
2702 return 0; // success
2703 },
2704 MakeSimpleGuard(-1)); // translate all exceptions into failure
2705}
2706
2708{
2709 ((NyquistBase*)This)->OutputCallback(c);
2710}
2711
2713{
2714 // Always collect Nyquist error messages for normal plug-ins
2715 if (!mRedirectOutput)
2716 {
2717 mDebugOutputStr += (wxChar)c;
2718 return;
2719 }
2720
2721 std::cout << (char)c;
2722}
2723
2725{
2726 ((NyquistBase*)This)->OSCallback();
2727}
2728
2730{
2731 if (mStop)
2732 {
2733 mStop = false;
2734 nyx_stop();
2735 }
2736 else if (mBreak)
2737 {
2738 mBreak = false;
2739 nyx_break();
2740 }
2741 else if (mCont)
2742 {
2743 mCont = false;
2744 nyx_continue();
2745 }
2746
2748}
2749
2751{
2752 const auto& audacityPathList = FileNames::AudacityPathList();
2753 FilePaths pathList;
2754
2755 for (size_t i = 0; i < audacityPathList.size(); i++)
2756 {
2757 wxString prefix = audacityPathList[i] + wxFILE_SEP_PATH;
2758 FileNames::AddUniquePathToPathList(prefix + wxT("nyquist"), pathList);
2759 FileNames::AddUniquePathToPathList(prefix + wxT("plugins"), pathList);
2760 FileNames::AddUniquePathToPathList(prefix + wxT("plug-ins"), pathList);
2761 }
2762 pathList.push_back(FileNames::PlugInDir());
2763
2764 return pathList;
2765}
2766
2768{
2769 return mOK;
2770}
2771
2780 wxString& path, FileExtension extension /* empty string */)
2781{
2782#if defined(__WXMSW__)
2783 path.Replace("/", wxFileName::GetPathSeparator());
2784#endif
2785
2786 path.Trim(true).Trim(false);
2787
2788 typedef std::unordered_map<wxString, FilePath> map;
2789 map pathKeys = {
2790 { "*home*", PlatformCompatibility::GetHomeDir() },
2792 { "*default*", FileNames::DefaultToDocumentsFolder("").GetPath() },
2793 { "*export*", FileNames::FindDefaultPath(FileNames::Operation::Export) },
2794 { "*save*", FileNames::FindDefaultPath(FileNames::Operation::Save) },
2795 { "*config*", FileNames::DataDir() }
2796 };
2797
2798 int characters = path.Find(wxFileName::GetPathSeparator());
2799 if (characters == wxNOT_FOUND) // Just a path or just a file name
2800 {
2801 if (path.empty())
2802 path = "*default*";
2803
2804 if (pathKeys.find(path) != pathKeys.end())
2805 {
2806 // Keyword found, so assume this is the intended directory.
2807 path = pathKeys[path] + wxFileName::GetPathSeparator();
2808 }
2809 else // Just a file name
2810 {
2811 path = pathKeys["*default*"] + wxFileName::GetPathSeparator() + path;
2812 }
2813 }
2814 else // path + file name
2815 {
2816 wxString firstDir = path.Left(characters);
2817 wxString rest = path.Mid(characters);
2818
2819 if (pathKeys.find(firstDir) != pathKeys.end())
2820 {
2821 path = pathKeys[firstDir] + rest;
2822 }
2823 }
2824
2825 wxFileName fname = path;
2826
2827 // If the directory is invalid, better to leave it as is (invalid) so that
2828 // the user sees the error rather than an unexpected file path.
2829 if (fname.wxFileName::IsOk() && fname.GetFullName().empty())
2830 {
2831 path = fname.GetPathWithSep() + _("untitled");
2832 if (!extension.empty())
2833 path = path + '.' + extension;
2834 }
2835}
2836
2837bool NyquistBase::validatePath(wxString path)
2838{
2839 wxFileName fname = path;
2840 wxString dir = fname.GetPath();
2841
2842 return (
2843 fname.wxFileName::IsOk() && wxFileName::DirExists(dir) &&
2844 !fname.GetFullName().empty());
2845}
2846
2848{
2849 int seconds = static_cast<int>(t);
2850 int hh = seconds / 3600;
2851 int mm = seconds % 3600;
2852 mm = mm / 60;
2853 return wxString::Format("%d:%d:%.3f", hh, mm, t - (hh * 3600 + mm * 60));
2854}
2855
2856static LVAL gettext()
2857{
2858 auto string = UTF8CTOWX(getstring(xlgastring()));
2859#if !HAS_I18N_CONTEXTS
2860 // allow ignored context argument
2861 if (moreargs())
2862 nextarg();
2863#endif
2864 xllastarg();
2865 return cvstring(GetCustomTranslation(string).mb_str(wxConvUTF8));
2866}
2867
2868static LVAL gettextc()
2869{
2870#if HAS_I18N_CONTEXTS
2871 auto string = UTF8CTOWX(getstring(xlgastring()));
2872 auto context = UTF8CTOWX(getstring(xlgastring()));
2873 xllastarg();
2874 return cvstring(
2875 wxGetTranslation(string, "", 0, "", context).mb_str(wxConvUTF8));
2876#else
2877 return gettext();
2878#endif
2879}
2880
2881static LVAL ngettext()
2882{
2883 auto string1 = UTF8CTOWX(getstring(xlgastring()));
2884 auto string2 = UTF8CTOWX(getstring(xlgastring()));
2885 auto number = getfixnum(xlgafixnum());
2886#if !HAS_I18N_CONTEXTS
2887 // allow ignored context argument
2888 if (moreargs())
2889 nextarg();
2890#endif
2891 xllastarg();
2892 return cvstring(
2893 wxGetTranslation(string1, string2, number).mb_str(wxConvUTF8));
2894}
2895
2896static LVAL ngettextc()
2897{
2898#if HAS_I18N_CONTEXTS
2899 auto string1 = UTF8CTOWX(getstring(xlgastring()));
2900 auto string2 = UTF8CTOWX(getstring(xlgastring()));
2901 auto number = getfixnum(xlgafixnum());
2902 auto context = UTF8CTOWX(getstring(xlgastring()));
2903 xllastarg();
2904 return cvstring(wxGetTranslation(string1, string2, number, "", context)
2905 .mb_str(wxConvUTF8));
2906#else
2907 return ngettext();
2908#endif
2909}
2910
2911void* nyq_make_opaque_string(int size, unsigned char* src)
2912{
2913 LVAL dst;
2914 unsigned char* dstp;
2915 dst = new_string((int)(size + 2));
2916 dstp = getstring(dst);
2917
2918 /* copy the source to the destination */
2919 while (size-- > 0)
2920 *dstp++ = *src++;
2921 *dstp = '\0';
2922
2923 return (void*)dst;
2924}
2925
2926void* nyq_reformat_aud_do_response(const wxString& Str)
2927{
2928 LVAL dst;
2929 LVAL message;
2930 LVAL success;
2931 wxString Left = Str.BeforeLast('\n').BeforeLast('\n').ToAscii();
2932 wxString Right = Str.BeforeLast('\n').AfterLast('\n').ToAscii();
2933 message = cvstring(Left);
2934 success = Right.EndsWith("OK") ? s_true : nullptr;
2935 dst = cons(message, success);
2936 return (void*)dst;
2937}
2938
2939void* ExecForLisp(char* pIn)
2940{
2941 wxString Str1(pIn);
2942 wxString Str2;
2943
2945
2947}
2948
2949/* xlc_aud_do -- interface to C routine aud_do */
2950
2951LVAL xlc_aud_do(void)
2952{
2953 // Based on string-trim...
2954 unsigned char* leftp;
2955 LVAL src, dst;
2956
2957 /* get the string */
2958 src = xlgastring();
2959 xllastarg();
2960
2961 /* setup the string pointer */
2962 leftp = getstring(src);
2963
2964 // Go call my real function here...
2965 dst = (LVAL)ExecForLisp((char*)leftp);
2966
2967 // dst = cons(dst, (LVAL)1);
2968 /* return the new string */
2969 return (dst);
2970}
2971
2973{
2974 // Add functions to XLisp. Do this only once,
2975 // before the first call to nyx_init.
2976 static bool firstTime = true;
2977 if (firstTime)
2978 {
2979 firstTime = false;
2980
2981 // All function names must be UP-CASED
2982 static const FUNDEF functions[] = {
2983 { "_", SUBR, gettext }, { "_C", SUBR, gettextc },
2984 { "NGETTEXT", SUBR, ngettext }, { "NGETTEXTC", SUBR, ngettextc },
2985 { "AUD-DO", SUBR, xlc_aud_do },
2986 };
2987
2988 xlbindfunctions(functions, WXSIZEOF(functions));
2989 }
2990}
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.
int min(int a, int b)
#define str(a)
#define NYQUISTEFFECTS_FAMILY
Definition: EffectBase.h:141
EffectType
@ EffectTypeAnalyze
@ EffectTypeGenerate
@ EffectTypeTool
@ EffectTypeProcess
XO("Cut/Copy/Paste")
wxString FileExtension
File extension, not including any leading dot.
Definition: Identifier.h:224
wxString PluginPath
type alias for identifying a Plugin supplied by a module, each module defining its own interpretation...
Definition: Identifier.h:214
STRINGS_API const wxString & GetCustomTranslation(const wxString &str1)
Definition: Internat.cpp:46
#define LAT1CTOWX(X)
Definition: Internat.h:158
#define UTF8CTOWX(X)
Definition: Internat.h:157
#define _(s)
Definition: Internat.h:73
IteratorRange< Iterator > make_iterator_range(const Iterator &i1, const Iterator &i2)
Definition: IteratorX.h:210
ValueRestorer< T > valueRestorer(T &var)
inline functions provide convenient parameter type deduction
Definition: MemoryX.h:253
#define safenew
Definition: MemoryX.h:10
static const AudacityProject::AttachedObjects::RegisteredFactory key
static LVAL gettextc()
void * ExecForLisp(char *pIn)
void * nyq_make_opaque_string(int size, unsigned char *src)
void * nyq_reformat_aud_do_response(const wxString &Str)
LVAL xlc_aud_do(void)
static LVAL ngettextc()
static const wxChar * KEY_Command
Definition: NyquistBase.cpp:53
static LVAL ngettext()
static const wxChar * KEY_Parameters
Definition: NyquistBase.cpp:54
static void RegisterFunctions()
#define NYQ_MAX_LEN
Definition: NyquistBase.cpp:51
static LVAL gettext()
#define UNINITIALIZED_CONTROL
Definition: NyquistBase.h:26
@ NYQ_CTRL_STRING
Definition: NyquistBase.h:32
@ NYQ_CTRL_TEXT
Definition: NyquistBase.h:36
@ NYQ_CTRL_TIME
Definition: NyquistBase.h:37
@ NYQ_CTRL_INT_TEXT
Definition: NyquistBase.h:34
@ NYQ_CTRL_INT
Definition: NyquistBase.h:30
@ NYQ_CTRL_CHOICE
Definition: NyquistBase.h:33
@ NYQ_CTRL_FLOAT_TEXT
Definition: NyquistBase.h:35
@ NYQ_CTRL_FILE
Definition: NyquistBase.h:38
@ NYQ_CTRL_FLOAT
Definition: NyquistBase.h:31
#define NYQUIST_WORKER_ID
Definition: NyquistBase.h:25
#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
wxString Str2
wxString name
Definition: TagsEditor.cpp:166
TranslatableString label
Definition: TagsEditor.cpp:165
const auto project
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
static Settings & settings()
Definition: TrackInfo.cpp:51
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
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 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,...
double mT1
Definition: EffectBase.h:123
void SetPreviewFullSelectionFlag(bool previewDurationFlag)
Definition: EffectBase.cpp:215
bool IsPreviewing() const
Definition: EffectBase.h:100
void SetLinearEffectFlag(bool linearEffectFlag)
Definition: EffectBase.cpp:210
const TrackList * inputTracks() const
Definition: EffectBase.h:102
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:116
wxArrayString mPresetNames
Definition: EffectBase.h:108
double mF0
Definition: EffectBase.h:105
double mF1
Definition: EffectBase.h:106
double mT0
Definition: EffectBase.h:122
const AudacityProject * FindProject() const
Definition: EffectBase.cpp:220
bool TrackGroupProgress(int whichGroup, double frac, const TranslatableString &={}) const
Definition: Effect.cpp:353
bool Delegate(Effect &delegate, EffectSettings &settings, InstanceFinder finder={})
Re-invoke DoEffect on another Effect object that implements the work.
Definition: Effect.cpp:322
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
int GetNumWaveGroups() const
Definition: Effect.h:140
Performs effect computation.
EffectManager is the class that handles effects and effect categories.
Definition: EffectManager.h:52
void SetSkipStateFlag(bool flag)
static EffectManager & Get()
Use this object to copy the input tracks to tentative outputTracks.
const Track * GetMatchingInput(const Track &outTrack) const
Gets the matching input track for the given output track if it finds its match, else nullptr.
Track * AddToOutputTracks(const std::shared_ptr< Track > &t)
Use this to add an output track, not corresponding to an input.
std::pair< double, double > TimeInterval
TrackList & Get()
Expose the output track list for iterations or even erasures.
static NyquistSettings & GetSettings(EffectSettings &settings)
Assume settings originated from MakeSettings() and copies thereof.
Definition: Effect.h:166
std::vector< FileType > FileTypes
Definition: FileNames.h:75
static result_type Call(Arguments &&...arguments)
Null check of the installed function is done for you.
static wxString ToString(double numberToConvert, int digitsAfterDecimalPoint=-1)
Convert a number to a string, always uses the dot as decimal separator.
Definition: Internat.cpp:126
static bool CompatibleToDouble(const wxString &stringToConvert, double *result)
Convert a string to a number.
Definition: Internat.cpp:109
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:98
static wxString GetDefaultName()
Definition: LabelTrack.cpp:96
A Track that is used for Midi notes. (Somewhat old code).
Definition: NoteTrack.h:78
A control on a NyquistDialog.
Definition: NyquistBase.h:42
wxString highStr
Definition: NyquistBase.h:58
double high
Definition: NyquistBase.h:61
wxString label
Definition: NyquistBase.h:53
wxString name
Definition: NyquistBase.h:52
wxString var
Definition: NyquistBase.h:51
std::vector< EnumValueSymbol > choices
Definition: NyquistBase.h:54
wxString valStr
Definition: NyquistBase.h:56
double low
Definition: NyquistBase.h:60
double val
Definition: NyquistBase.h:59
FileNames::FileTypes fileTypes
Definition: NyquistBase.h:55
wxString lowStr
Definition: NyquistBase.h:57
An Effect that calls up a Nyquist (XLISP) plug-in, i.e. many possible effects from this one class.
Definition: NyquistBase.h:77
bool VisitSettings(SettingsVisitor &visitor, EffectSettings &settings) override
wxString mHelpFile
Definition: NyquistBase.h:269
EffectFamilySymbol GetFamily() const override
Report identifier and user-visible name of the effect protocol.
const bool mIsPrompt
Definition: NyquistBase.h:251
void SetCommand(const wxString &cmd)
bool Parse(Tokenizer &tokenizer, const wxString &line, bool eof, bool first)
bool Process(EffectInstance &instance, EffectSettings &settings) override
bool mHelpFileExists
Definition: NyquistBase.h:270
wxString EscapeString(const wxString &inStr)
bool mCompiler
Definition: NyquistBase.h:241
static void resolveFilePath(wxString &path, FileExtension extension={})
bool validatePath(wxString path)
VendorSymbol GetVendor() const override
bool LoadSettings(const CommandParameters &parms, EffectSettings &settings) const override
Restore settings from keys and values.
FileNames::FileType ParseFileType(const wxString &text)
wxString mInputCmd
Definition: NyquistBase.h:254
wxString mParameters
Definition: NyquistBase.h:255
void ParseFile()
bool ProcessOne(NyxContext &nyxContext, EffectOutputTracks *pOutputs)
EffectType GetType() const override
Type determines how it behaves.
bool mFoundType
Definition: NyquistBase.h:240
TranslatableString mCopyright
Definition: NyquistBase.h:267
bool SaveSettings(const EffectSettings &settings, CommandParameters &parms) const override
Store settings as keys and values.
bool IsInteractive() const override
Whether the effect needs a dialog for entry of settings.
wxString mProps
Definition: NyquistBase.h:299
TranslatableString mInitError
Definition: NyquistBase.h:253
void RedirectOutput()
TranslatableString mPromptName
Definition: NyquistBase.h:259
static int mReentryCount
Definition: NyquistBase.h:166
wxArrayString mCategories
Definition: NyquistBase.h:297
static void StaticOSCallback(void *userdata)
unsigned mNumSelectedChannels
Definition: NyquistBase.h:295
static std::vector< EnumValueSymbol > ParseChoice(const wxString &text)
EffectType mType
Definition: NyquistBase.h:272
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
wxString GetVersion() const override
wxString mCmd
Definition: NyquistBase.h:256
wxFileName mFileName
Name of the Nyquist script file this effect is loaded from.
Definition: NyquistBase.h:233
static wxString UnQuote(const wxString &s, bool allowParens=true, wxString *pExtraString=nullptr)
static void StaticOutputCallback(int c, void *userdata)
wxDateTime mFileModified
When the script was last modified on disk.
Definition: NyquistBase.h:234
bool mRedirectOutput
Definition: NyquistBase.h:279
wxString mManPage
Definition: NyquistBase.h:268
FileExtensions ParseFileExtensions(const wxString &text)
TranslatableString mAction
Definition: NyquistBase.h:260
unsigned mCount
Definition: NyquistBase.h:294
TranslatableString mDebugOutput
Definition: NyquistBase.h:282
FileNames::FileTypes ParseFileTypes(const wxString &text)
PluginPath GetPath() const override
static double GetCtrlValue(const wxString &s)
virtual ~NyquistBase()
bool ParseProgram(wxInputStream &stream)
EffectType GetClassification() const override
Determines which menu it appears in; default same as GetType().
FilePath HelpPage() const override
Fully qualified local help file name, default is empty.
TranslatableString mReleaseVersion
Definition: NyquistBase.h:266
bool mRestoreSplits
Definition: NyquistBase.h:302
bool EnablesDebug() const override
Whether the effect dialog should have a Debug button; default, always false.
wxString mDebugOutputStr
Definition: NyquistBase.h:281
static TranslatableString UnQuoteMsgid(const wxString &s, bool allowParens=true, wxString *pExtraString=nullptr)
bool mProjectChanged
Definition: NyquistBase.h:280
std::pair< bool, FilePath > CheckHelpPage() const
bool mExternal
Definition: NyquistBase.h:244
static wxString NyquistToWxString(const char *nyqString)
wxString ToTimeFormat(double t)
EffectType mPromptType
Definition: NyquistBase.h:273
TranslatableString mAuthor
Definition: NyquistBase.h:262
bool Init() override
NyquistBase(const wxString &fName)
Definition: NyquistBase.cpp:56
TranslatableString mInfo
Definition: NyquistBase.h:261
TranslatableString GetDescription() const override
bool mIsSpectral
Definition: NyquistBase.h:245
bool IsDefault() const override
Whether the effect sorts "above the line" in the menus.
bool mDebugButton
Definition: NyquistBase.h:276
bool mEnablePreview
Definition: NyquistBase.h:275
bool ParseCommand(const wxString &cmd)
std::vector< NyqControl > mControls
Definition: NyquistBase.h:287
bool mFirstInGroup
Definition: NyquistBase.h:292
int SetLispVarsFromParameters(const CommandParameters &parms, bool bTestOnly)
wxString mPerTrackProps
Definition: NyquistBase.h:300
double mOutputTime
Definition: NyquistBase.h:293
void OutputCallback(int c)
TranslatableString mName
Name of the Effect (untranslated)
Definition: NyquistBase.h:257
sampleCount mMaxLen
Definition: NyquistBase.h:290
bool DoLoadSettings(const CommandParameters &parms, EffectSettings &settings)
void OSCallback()
ComponentInterfaceSymbol GetSymbol() const override
FilePath mHelpPage
Definition: NyquistBase.h:271
static FilePaths GetNyquistSearchPath()
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 sets parameters to a value (from a string)
bool SpectralSelectionEnabled() const
static SpectrogramSettings & Get(const WaveTrack &track)
static TrackIterRange< Track > Group(Track &track)
Definition: SyncLock.cpp:150
static bool IsSyncLockSelected(const Track &track)
Definition: SyncLock.cpp:80
A kind of Track used to 'warp time'.
Definition: TimeTrack.h:24
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
R TypeSwitch(const Functions &...functions)
Definition: Track.h:381
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:950
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
auto Selected() -> TrackIterRange< TrackType >
Definition: Track.h:967
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
TranslatableString & Format(Args &&...args) &
Capture variadic format arguments (by copy) when there is no plural.
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:216
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
IntervalHolders SortedIntervalArray()
Return all WaveClips sorted by clip play start time.
Definition: WaveTrack.cpp:3297
size_t NChannels() const override
A constant property.
Definition: WaveTrack.cpp:530
std::shared_ptr< WaveTrack > Holder
Definition: WaveTrack.h:247
virtual bool Read(const wxString &key, bool *value) const =0
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
long long as_long_long() const
Definition: SampleCount.h:48
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:287
FILES_API FilePath PlugInDir()
The user plug-in directory (not a system one)
FILES_API FilePath HtmlHelpDir()
FILES_API wxFileNameWrapper DefaultToDocumentsFolder(const wxString &preference)
FILES_API void AddUniquePathToPathList(const FilePath &path, FilePaths &pathList)
FILES_API FilePath BaseDir()
FILES_API FilePath DataDir()
Audacity user data directory.
FILES_API FilePath FindDefaultPath(Operation op)
FILES_API const FilePaths & AudacityPathList()
A list of directories that should be searched for Audacity files (plug-ins, help files,...
wxString GetSystemLanguageCode(const FilePaths &pathList)
Definition: Languages.cpp:83
std::string FILES_API GetDocumentsDir()
std::string FILES_API GetTempDir()
std::string FILES_API GetHomeDir()
FILES_API wxString TempDir()
WAVE_TRACK_API std::pair< float, float > GetMinMax(const WaveChannel &channel, double t0, double t1, bool mayThrow=true)
WAVE_TRACK_API float GetRMS(const WaveChannel &channel, double t0, double t1, bool mayThrow=true)
wxString GetClipBoundaries(const Track *t)
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101
__finl float_x4 __vecc sqrt(const float_x4 &a)
MessageBoxOptions && IconStyle(Icon style) &&
Definition: BasicUI.h:104
Externalized state of a plug-in.
Buffer mCurBuffer[2]
used only in GetCallback
WaveTrack::Holder mOutputTrack
int PutCallback(float *buffer, int channel, int64_t start, int64_t len, int64_t totlen)
NyxContext(ProgressReport progressReport, double scale, double progressTot)
sampleCount mCurBufferStart[2]
std::unique_ptr< float[]> Buffer
WaveChannel * mCurTrack[2]
static int StaticPutCallback(float *buffer, int channel, int64_t start, int64_t len, int64_t totlen, void *userdata)
unsigned mCurNumChannels
Not used in the callbacks.
const ProgressReport mProgressReport
std::function< bool(double)> ProgressReport
std::exception_ptr mpException
WaveTrack * mCurChannelGroup
int GetCallback(float *buffer, int channel, int64_t start, int64_t len, int64_t totlen)
static int StaticGetCallback(float *buffer, int channel, int64_t start, int64_t len, int64_t totlen, void *userdata)
bool Tokenize(const wxString &line, bool eof, size_t trimStart, size_t trimEnd)
wxArrayStringEx tokens
Definition: NyquistBase.h:206