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