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