mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-19 17:40:15 +02:00
This brings the builtin, LV2, and VAMP effects inline with the Audio Units, LADSPA, and VST effects. All effects now share a common UI. This gives all effects (though not implemented for all): User and factory preset capability Preset import/export capability Shared or private configuration options Builtin effects can now be migrated to RTP, depending on algorithm. LV2 effects now support graphical interfaces if the plugin supplies one. Nyquist prompt enhanced to provide some features of the Nyquist Workbench. It may not look like it, but this was a LOT of work, so trust me, there WILL be problems and everything effect related should be suspect. Keep a sharp eye (or two) open.
2094 lines
59 KiB
C++
2094 lines
59 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
Nyquist.cpp
|
|
|
|
Dominic Mazzoni
|
|
|
|
******************************************************************//**
|
|
|
|
\class NyquistEffect
|
|
\brief An Effect that calls up a Nyquist (XLISP) plug in, i.e. many possible
|
|
effects from this one class.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class NyquistOutputDialog
|
|
\brief Dialog used with NyquistEffect
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class NyqControl
|
|
\brief A control on a NyquistDialog.
|
|
|
|
*//*******************************************************************/
|
|
|
|
#include "../../Audacity.h"
|
|
|
|
#include <math.h>
|
|
#include <locale.h>
|
|
|
|
#include <wx/checkbox.h>
|
|
#include <wx/choice.h>
|
|
#include <wx/intl.h>
|
|
#include <wx/log.h>
|
|
#include <wx/msgdlg.h>
|
|
#include <wx/sstream.h>
|
|
#include <wx/textdlg.h>
|
|
#include <wx/txtstrm.h>
|
|
#include <wx/wfstream.h>
|
|
#include <wx/datetime.h>
|
|
|
|
#include "../../AudacityApp.h"
|
|
#include "../../FileNames.h"
|
|
#include "../../Internat.h"
|
|
#include "../../LabelTrack.h"
|
|
#include "../../Project.h"
|
|
#include "../../ShuttleGui.h"
|
|
#include "../../WaveClip.h"
|
|
#include "../../WaveTrack.h"
|
|
#include "../../widgets/valnum.h"
|
|
#include "../../Prefs.h"
|
|
|
|
#include "FileDialog.h"
|
|
|
|
#include "Nyquist.h"
|
|
|
|
#ifndef nyx_returns_start_and_end_time
|
|
#error You need to update lib-src/libnyquist
|
|
#endif
|
|
|
|
#include <locale.h>
|
|
#include <iostream>
|
|
#include <ostream>
|
|
#include <sstream>
|
|
#include <float.h>
|
|
|
|
enum
|
|
{
|
|
ID_Editor = 10000,
|
|
ID_Version,
|
|
ID_Load,
|
|
ID_Save,
|
|
ID_Clear,
|
|
ID_Debug,
|
|
|
|
ID_Slider = 11000,
|
|
ID_Text = 12000,
|
|
ID_Choice = 13000
|
|
};
|
|
|
|
#define UNINITIALIZED_CONTROL ((double)99999999.99)
|
|
|
|
static const wxChar *KEY_Version = wxTRANSLATE("Version");
|
|
static const wxChar *KEY_Command = wxTRANSLATE("Command");
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// NyquistEffect
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <wx/arrimpl.cpp>
|
|
WX_DEFINE_OBJARRAY(NyqControlArray);
|
|
|
|
BEGIN_EVENT_TABLE(NyquistEffect, wxEvtHandler)
|
|
EVT_BUTTON(ID_Load, NyquistEffect::OnLoad)
|
|
EVT_BUTTON(ID_Save, NyquistEffect::OnSave)
|
|
EVT_BUTTON(ID_Clear, NyquistEffect::OnClear)
|
|
EVT_BUTTON(ID_Debug, NyquistEffect::OnDebug)
|
|
|
|
EVT_COMMAND_RANGE(ID_Slider, ID_Slider+99,
|
|
wxEVT_COMMAND_SLIDER_UPDATED, NyquistEffect::OnSlider)
|
|
EVT_COMMAND_RANGE(ID_Text, ID_Text+99,
|
|
wxEVT_COMMAND_TEXT_UPDATED, NyquistEffect::OnText)
|
|
EVT_COMMAND_RANGE(ID_Choice, ID_Choice + 99,
|
|
wxEVT_COMMAND_CHOICE_SELECTED, NyquistEffect::OnChoice)
|
|
END_EVENT_TABLE()
|
|
|
|
NyquistEffect::NyquistEffect(wxString fName)
|
|
{
|
|
mAction = _("Applying Nyquist Effect...");
|
|
mInputCmd = wxEmptyString;
|
|
mCmd = wxEmptyString;
|
|
mIsPrompt = false;
|
|
mExternal = false;
|
|
mCompiler = false;
|
|
mDebug = false;
|
|
mIsSal = false;
|
|
mOK = false;
|
|
mAuthor = wxT("N/A");
|
|
mCopyright = wxT("N/A");
|
|
|
|
// set clip/split handling when applying over clip boundary.
|
|
mRestoreSplits = true; // Default: Restore split lines.
|
|
mMergeClips = -1; // Default (auto): Merge if length remains unchanged.
|
|
|
|
mVersion = 4;
|
|
|
|
mStop = false;
|
|
mBreak = false;
|
|
mCont = false;
|
|
|
|
// Interactive Nyquist
|
|
if (fName == NYQUIST_PROMPT_ID)
|
|
{
|
|
mName = wxTRANSLATE("Nyquist Prompt");
|
|
mType = EffectTypeProcess;
|
|
mOK = true;
|
|
mIsPrompt = true;
|
|
|
|
return;
|
|
}
|
|
|
|
mName = wxFileName(fName).GetName();
|
|
mFileName = wxFileName(fName);
|
|
mFileModified = mFileName.GetModificationTime();
|
|
ParseFile();
|
|
}
|
|
|
|
NyquistEffect::~NyquistEffect()
|
|
{
|
|
}
|
|
|
|
// IdentInterface implementation
|
|
|
|
wxString NyquistEffect::GetPath()
|
|
{
|
|
if (mIsPrompt)
|
|
{
|
|
return NYQUIST_PROMPT_ID;
|
|
}
|
|
|
|
return mFileName.GetFullPath();
|
|
}
|
|
|
|
wxString NyquistEffect::GetSymbol()
|
|
{
|
|
if (mIsPrompt)
|
|
{
|
|
return wxTRANSLATE("Nyquist Prompt");
|
|
}
|
|
|
|
return mName;
|
|
}
|
|
|
|
wxString NyquistEffect::GetName()
|
|
{
|
|
return GetSymbol();
|
|
}
|
|
|
|
wxString NyquistEffect::GetVendor()
|
|
{
|
|
if (mIsPrompt)
|
|
{
|
|
return _("Audacity");
|
|
}
|
|
|
|
return mAuthor;
|
|
}
|
|
|
|
wxString NyquistEffect::GetVersion()
|
|
{
|
|
return wxT("N/A");
|
|
}
|
|
|
|
wxString NyquistEffect::GetDescription()
|
|
{
|
|
return mCopyright;
|
|
}
|
|
|
|
// EffectIdentInterface implementation
|
|
|
|
EffectType NyquistEffect::GetType()
|
|
{
|
|
return mType;
|
|
}
|
|
|
|
wxString NyquistEffect::GetFamily()
|
|
{
|
|
return NYQUISTEFFECTS_FAMILY;
|
|
}
|
|
|
|
bool NyquistEffect::IsInteractive()
|
|
{
|
|
if (mIsPrompt)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return mControls.GetCount() != 0;
|
|
}
|
|
|
|
bool NyquistEffect::IsDefault()
|
|
{
|
|
return mIsPrompt;
|
|
}
|
|
|
|
// EffectClientInterface implementation
|
|
|
|
bool NyquistEffect::GetAutomationParameters(EffectAutomationParameters & parms)
|
|
{
|
|
if (mExternal)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (mIsPrompt)
|
|
{
|
|
parms.Write(KEY_Command, mInputCmd);
|
|
parms.Write(KEY_Version, mVersion);
|
|
|
|
return true;
|
|
}
|
|
|
|
for (size_t c = 0, cnt = mControls.GetCount(); c < cnt; c++)
|
|
{
|
|
NyqControl & ctrl = mControls[c];
|
|
double d = ctrl.val;
|
|
|
|
if (d == UNINITIALIZED_CONTROL && ctrl.type != NYQ_CTRL_STRING)
|
|
{
|
|
d = GetCtrlValue(ctrl.valStr);
|
|
}
|
|
|
|
if (ctrl.type == NYQ_CTRL_REAL)
|
|
{
|
|
parms.Write(ctrl.var, d);
|
|
}
|
|
else if (ctrl.type == NYQ_CTRL_INT)
|
|
{
|
|
parms.Write(ctrl.var, (int) d);
|
|
}
|
|
else if (ctrl.type == NYQ_CTRL_CHOICE)
|
|
{
|
|
parms.WriteEnum(ctrl.var, (int) d, wxStringTokenize(ctrl.label, wxT(",")));
|
|
}
|
|
else if (ctrl.type == NYQ_CTRL_STRING)
|
|
{
|
|
parms.Write(ctrl.var, ctrl.valStr);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool NyquistEffect::SetAutomationParameters(EffectAutomationParameters & parms)
|
|
{
|
|
if (mExternal)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (mIsPrompt)
|
|
{
|
|
parms.Read(KEY_Command, &mInputCmd, wxEmptyString);
|
|
parms.Read(KEY_Version, &mVersion, mVersion);
|
|
|
|
return true;
|
|
}
|
|
|
|
// First pass verifies values
|
|
for (size_t c = 0, cnt = mControls.GetCount(); c < cnt; c++)
|
|
{
|
|
NyqControl & ctrl = mControls[c];
|
|
bool good = false;
|
|
|
|
if (ctrl.type == NYQ_CTRL_REAL)
|
|
{
|
|
double val;
|
|
good = parms.Read(ctrl.var, &val) &&
|
|
val >= ctrl.low &&
|
|
val <= ctrl.high;
|
|
}
|
|
else if (ctrl.type == NYQ_CTRL_INT)
|
|
{
|
|
int val;
|
|
good = parms.Read(ctrl.var, &val) &&
|
|
val >= ctrl.low &&
|
|
val <= ctrl.high;
|
|
}
|
|
else if (ctrl.type == NYQ_CTRL_CHOICE)
|
|
{
|
|
int val;
|
|
good = parms.ReadEnum(ctrl.var, &val, wxStringTokenize(ctrl.label, wxT(","))) &&
|
|
val != wxNOT_FOUND;
|
|
}
|
|
else if (ctrl.type == NYQ_CTRL_STRING)
|
|
{
|
|
wxString val;
|
|
good = parms.Read(ctrl.var, &val);
|
|
}
|
|
|
|
if (!good)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Second pass sets the variables
|
|
for (size_t c = 0, cnt = mControls.GetCount(); c < cnt; c++)
|
|
{
|
|
NyqControl & ctrl = mControls[c];
|
|
|
|
double d = ctrl.val;
|
|
if (d == UNINITIALIZED_CONTROL && ctrl.type != NYQ_CTRL_STRING)
|
|
{
|
|
d = GetCtrlValue(ctrl.valStr);
|
|
}
|
|
|
|
if (ctrl.type == NYQ_CTRL_REAL)
|
|
{
|
|
parms.Read(ctrl.var, &ctrl.val);
|
|
}
|
|
else if (ctrl.type == NYQ_CTRL_INT)
|
|
{
|
|
int val;
|
|
parms.Read(ctrl.var, &val);
|
|
ctrl.val = (double) val;
|
|
}
|
|
else if (ctrl.type == NYQ_CTRL_CHOICE)
|
|
{
|
|
int val;
|
|
parms.ReadEnum(ctrl.var, &val, wxStringTokenize(ctrl.label, wxT(",")));
|
|
ctrl.val = (double) val;
|
|
}
|
|
else if (ctrl.type == NYQ_CTRL_STRING)
|
|
{
|
|
wxString val;
|
|
parms.Read(ctrl.var, &ctrl.valStr);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Effect Implementation
|
|
|
|
bool NyquistEffect::Process()
|
|
{
|
|
bool success = true;
|
|
|
|
if (mExternal) {
|
|
mProgress->Hide();
|
|
}
|
|
|
|
// We must copy all the tracks, because Paste needs label tracks to ensure
|
|
// correct sync-lock group behavior when the timeline is affected; then we just want
|
|
// to operate on the selected wave tracks
|
|
CopyInputTracks(Track::All);
|
|
SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks);
|
|
mCurTrack[0] = (WaveTrack *) iter.First();
|
|
mOutputTime = 0;
|
|
mCount = 0;
|
|
mProgressIn = 0;
|
|
mProgressOut = 0;
|
|
mProgressTot = 0;
|
|
mScale = (GetType() == EffectTypeProcess ? 0.5 : 1.0) / GetNumWaveGroups();
|
|
|
|
mStop = false;
|
|
mBreak = false;
|
|
mCont = false;
|
|
|
|
mTrackIndex = 0;
|
|
|
|
mDebugOutput = new std::string();
|
|
mOutput.Clear();
|
|
|
|
if (mVersion >= 4)
|
|
{
|
|
AudacityProject *project = GetActiveProject();
|
|
|
|
mProps = wxEmptyString;
|
|
|
|
mProps += wxString::Format(wxT("(putprop '*AUDACITY* (list %d %d %d) 'VERSION)\n"), AUDACITY_VERSION, AUDACITY_RELEASE, AUDACITY_REVISION);
|
|
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'BASE)\n"), EscapeString(FileNames::BaseDir()).c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'DATA)\n"), EscapeString(FileNames::DataDir()).c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'HELP)\n"), EscapeString(FileNames::HtmlHelpDir().RemoveLast()).c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* \"%s\" 'TEMP)\n"), EscapeString(FileNames::TempDir()).c_str());
|
|
|
|
wxArrayString paths = NyquistEffect::GetNyquistSearchPath();
|
|
wxString list;
|
|
for (size_t i = 0, cnt = paths.GetCount(); i < cnt; i++)
|
|
{
|
|
list += wxT("\"") + EscapeString(paths[i]) + wxT("\" ");
|
|
}
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-DIR* (list %s) 'PLUGIN)\n"), list.RemoveLast().c_str());
|
|
|
|
|
|
// Date and time:
|
|
wxDateTime now = wxDateTime::Now();
|
|
int year = now.GetYear();
|
|
int doy = now.GetDayOfYear();
|
|
int dom = now.GetDay();
|
|
// enumerated constants
|
|
wxDateTime::Month month = now.GetMonth();
|
|
wxDateTime::WeekDay day = now.GetWeekDay();
|
|
|
|
// Date/time as a list: year, day of year, hour, minute, seconds
|
|
mProps += wxString::Format(wxT("(setf *SYSTEM-TIME* (list %d %d %d %d %d))\n"),
|
|
year, doy, now.GetHour(), now.GetMinute(), now.GetSecond());
|
|
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'DATE)\n"), now.FormatDate().c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'TIME)\n"), now.FormatTime().c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'ISO-DATE)\n"), now.FormatISODate().c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'ISO-TIME)\n"), now.FormatISOTime().c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* %d 'YEAR)\n"), year);
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* %d 'DAY)\n"), dom); // day of month
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* %d 'MONTH)\n"), month);
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'MONTH-NAME)\n"), now.GetMonthName(month).c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SYSTEM-TIME* \"%s\" 'DAY-NAME)\n"), now.GetWeekDayName(day).c_str());
|
|
|
|
|
|
TrackListIterator all(project->GetTracks());
|
|
Track *t;
|
|
int numTracks = 0;
|
|
int numWave = 0;
|
|
int numLabel = 0;
|
|
int numMidi = 0;
|
|
int numTime = 0;
|
|
wxString waveTrackList = wxT(""); // track positions of selected audio tracks.
|
|
|
|
for (t = all.First(); t; t = all.Next())
|
|
{
|
|
switch (t->GetKind())
|
|
{
|
|
case Track::Wave:
|
|
numWave++;
|
|
if (t->GetSelected()) {
|
|
waveTrackList += wxString::Format(wxT("%d "), 1 + numTracks);
|
|
}
|
|
break;
|
|
case Track::Label: numLabel++; break;
|
|
#if defined(USE_MIDI)
|
|
case Track::Note: numMidi++; break;
|
|
#endif
|
|
case Track::Time: numTime++; break;
|
|
default: break;
|
|
}
|
|
|
|
numTracks++;
|
|
if (t->GetLinked())
|
|
{
|
|
all.Next();
|
|
}
|
|
}
|
|
|
|
// We use Internat::ToString() rather than "%g" here because we
|
|
// always have to use the dot as decimal separator when giving
|
|
// numbers to Nyquist, whereas using "%g" will use the user's
|
|
// decimal separator which may be a comma in some countries.
|
|
mProps += wxString::Format(wxT("(putprop '*PROJECT* (float %s) 'RATE)\n"),
|
|
Internat::ToString(project->GetRate()).c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*PROJECT* %d 'TRACKS)\n"), numTracks);
|
|
mProps += wxString::Format(wxT("(putprop '*PROJECT* %d 'WAVETRACKS)\n"), numWave);
|
|
mProps += wxString::Format(wxT("(putprop '*PROJECT* %d 'LABELTRACKS)\n"), numLabel);
|
|
mProps += wxString::Format(wxT("(putprop '*PROJECT* %d 'MIDITRACKS)\n"), numMidi);
|
|
mProps += wxString::Format(wxT("(putprop '*PROJECT* %d 'TIMETRACKS)\n"), numTime);
|
|
|
|
SelectedTrackListOfKindIterator sel(Track::Wave, mOutputTracks);
|
|
int numChannels = 0;
|
|
for (WaveTrack *t = (WaveTrack *) sel.First(); t; t = (WaveTrack *) sel.Next()) {
|
|
numChannels++;
|
|
if (mT1 >= mT0) {
|
|
if (t->GetLinked()) {
|
|
numChannels++;
|
|
sel.Next();
|
|
}
|
|
}
|
|
}
|
|
|
|
mProps += wxString::Format(wxT("(putprop '*SELECTION* (float %s) 'START)\n"),
|
|
Internat::ToString(mT0).c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SELECTION* (float %s) 'END)\n"),
|
|
Internat::ToString(mT1).c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SELECTION* (list %s) 'TRACKS)\n"), waveTrackList.c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SELECTION* %d 'CHANNELS)\n"), numChannels);
|
|
|
|
wxString lowHz = wxT("nil");
|
|
wxString highHz = wxT("nil");
|
|
wxString centerHz = wxT("nil");
|
|
wxString bandwidth = wxT("nil");
|
|
|
|
#if defined(EXPERIMENTAL_SPECTRAL_EDITING)
|
|
if (mF0 >= 0.0) {
|
|
lowHz.Printf(wxT("(float %s)"), Internat::ToString(mF0).c_str());
|
|
}
|
|
|
|
if (mF1 >= 0.0) {
|
|
highHz.Printf(wxT("(float %s)"), Internat::ToString(mF1).c_str());
|
|
}
|
|
|
|
if ((mF0 >= 0.0) && (mF1 >= 0.0)) {
|
|
centerHz.Printf(wxT("(float %s)"), Internat::ToString(sqrt(mF0 * mF1)).c_str());
|
|
}
|
|
|
|
if ((mF0 > 0.0) && (mF1 >= mF0)) {
|
|
bandwidth.Printf(wxT("(float %s)"), Internat::ToString(log(mF1 / mF0)/log(2.0)).c_str());
|
|
}
|
|
|
|
#endif
|
|
mProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'LOW-HZ)\n"), lowHz.c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'CENTER-HZ)\n"), centerHz.c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'HIGH-HZ)\n"), highHz.c_str());
|
|
mProps += wxString::Format(wxT("(putprop '*SELECTION* %s 'BANDWIDTH)\n"), bandwidth.c_str());
|
|
}
|
|
|
|
// Keep track of whether the current track is first selected in its sync-lock group
|
|
// (we have no idea what the length of the returned audio will be, so we have
|
|
// to handle sync-lock group behavior the "old" way).
|
|
mFirstInGroup = true;
|
|
Track *gtLast = NULL;
|
|
|
|
while (mCurTrack[0]) {
|
|
mCurNumChannels = 1;
|
|
if (mT1 >= mT0) {
|
|
if (mCurTrack[0]->GetLinked()) {
|
|
mCurNumChannels = 2;
|
|
|
|
mCurTrack[1] = (WaveTrack *)iter.Next();
|
|
if (mCurTrack[1]->GetRate() != mCurTrack[0]->GetRate()) {
|
|
wxMessageBox(_("Sorry, cannot apply effect on stereo tracks where the tracks don't match."),
|
|
wxT("Nyquist"),
|
|
wxOK | wxCENTRE, mUIParent);
|
|
success = false;
|
|
goto finish;
|
|
}
|
|
mCurStart[1] = mCurTrack[1]->TimeToLongSamples(mT0);
|
|
}
|
|
|
|
// Check whether we're in the same group as the last selected track
|
|
SyncLockedTracksIterator gIter(mOutputTracks);
|
|
Track *gt = gIter.First(mCurTrack[0]);
|
|
mFirstInGroup = !gtLast || (gtLast != gt);
|
|
gtLast = gt;
|
|
|
|
mCurStart[0] = mCurTrack[0]->TimeToLongSamples(mT0);
|
|
sampleCount end = mCurTrack[0]->TimeToLongSamples(mT1);
|
|
mCurLen = (sampleCount)(end - mCurStart[0]);
|
|
|
|
mProgressIn = 0.0;
|
|
mProgressOut = 0.0;
|
|
|
|
// libnyquist breaks except in LC_NUMERIC=="C".
|
|
//
|
|
// Note that we must set the locale to "C" even before calling
|
|
// nyx_init() because otherwise some effects will not work!
|
|
//
|
|
// MB: setlocale is not thread-safe. Should use uselocale()
|
|
// if available, or fix libnyquist to be locale-independent.
|
|
// See also http://bugzilla.audacityteam.org/show_bug.cgi?id=642#c9
|
|
// for further info about this thread safety question.
|
|
wxString prevlocale = wxSetlocale(LC_NUMERIC, NULL);
|
|
wxSetlocale(LC_NUMERIC, wxString(wxT("C")));
|
|
|
|
nyx_init();
|
|
nyx_set_os_callback(StaticOSCallback, (void *)this);
|
|
nyx_capture_output(StaticOutputCallback, (void *)this);
|
|
|
|
success = ProcessOne();
|
|
|
|
nyx_capture_output(NULL, (void *)NULL);
|
|
nyx_set_os_callback(NULL, (void *)NULL);
|
|
nyx_cleanup();
|
|
|
|
// Reset previous locale
|
|
wxSetlocale(LC_NUMERIC, prevlocale);
|
|
|
|
if (!success) {
|
|
goto finish;
|
|
}
|
|
mProgressTot += mProgressIn + mProgressOut;
|
|
}
|
|
|
|
mCurTrack[0] = (WaveTrack *) iter.Next();
|
|
mCount += mCurNumChannels;
|
|
}
|
|
|
|
if (mOutputTime > 0.0) {
|
|
mT1 = mT0 + mOutputTime;
|
|
}
|
|
|
|
finish:
|
|
|
|
ReplaceProcessedTracks(success);
|
|
|
|
mDebug = false;
|
|
|
|
delete mDebugOutput;
|
|
|
|
return success;
|
|
}
|
|
|
|
bool NyquistEffect::ShowInterface(wxWindow *parent, bool forceModal)
|
|
{
|
|
// Show the normal (prompt or effect) interface
|
|
bool res = Effect::ShowInterface(parent, forceModal);
|
|
printf("res = %d %d %d\n", res, mIsPrompt, (int)mControls.GetCount());
|
|
// We're done if the user clicked "Close", we are not the Nyquist Prompt,
|
|
// or the program currently loaded into the prompt doesn't have a UI.
|
|
if (!res || !mIsPrompt || mControls.GetCount() == 0)
|
|
{
|
|
return res;
|
|
}
|
|
|
|
NyquistEffect effect(NYQUIST_WORKER_ID);
|
|
|
|
effect.SetCommand(mInputCmd);
|
|
effect.mDebug = false;
|
|
|
|
SelectedRegion region(mT0, mT1);
|
|
effect.DoEffect(parent,
|
|
mProjectRate,
|
|
mTracks,
|
|
mFactory,
|
|
®ion,
|
|
true);
|
|
|
|
return false;
|
|
}
|
|
|
|
void NyquistEffect::PopulateOrExchange(ShuttleGui & S)
|
|
{
|
|
if (mIsPrompt)
|
|
{
|
|
BuildPromptWindow(S);
|
|
}
|
|
else
|
|
{
|
|
BuildEffectWindow(S);
|
|
}
|
|
}
|
|
|
|
bool NyquistEffect::TransferDataToWindow()
|
|
{
|
|
mUIParent->TransferDataToWindow();
|
|
bool success;
|
|
|
|
if (mIsPrompt)
|
|
{
|
|
success = TransferDataToPromptWindow();
|
|
}
|
|
else
|
|
{
|
|
|
|
success = TransferDataToEffectWindow();
|
|
}
|
|
|
|
if (success)
|
|
{
|
|
EnablePreview(mEnablePreview);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool NyquistEffect::TransferDataFromWindow()
|
|
{
|
|
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (mIsPrompt)
|
|
{
|
|
return TransferDataFromPromptWindow();
|
|
}
|
|
|
|
return TransferDataFromEffectWindow();
|
|
}
|
|
|
|
// NyquistEffect implementation
|
|
|
|
bool NyquistEffect::ProcessOne()
|
|
{
|
|
nyx_rval rval;
|
|
|
|
wxString cmd;
|
|
|
|
if (mVersion >= 4) {
|
|
nyx_set_audio_name("*TRACK*");
|
|
cmd += wxT("(setf S 0.25)\n");
|
|
}
|
|
else {
|
|
nyx_set_audio_name("S");
|
|
cmd += wxT("(setf *TRACK* '*unbound*)\n");
|
|
}
|
|
|
|
if (mVersion >= 4) {
|
|
cmd += mProps;
|
|
|
|
// Set the track TYPE and VIEW properties
|
|
wxString type;
|
|
wxString view;
|
|
wxString bitFormat;
|
|
switch (mCurTrack[0]->GetKind())
|
|
{
|
|
case Track::Wave:
|
|
type = wxT("wave");
|
|
switch (((WaveTrack *) mCurTrack[0])->GetDisplay())
|
|
{
|
|
case WaveTrack::WaveformDisplay: view = wxT("\"Waveform\""); break;
|
|
case WaveTrack::WaveformDBDisplay: view = wxT("\"Waveform (dB)\""); break;
|
|
case WaveTrack::SpectrumDisplay: view = wxT("\"Spectrogram\""); break;
|
|
case WaveTrack::SpectrumLogDisplay: view = wxT("\"Spectrogram log(f)\""); break;
|
|
case WaveTrack::PitchDisplay: view = wxT("\"Pitch (EAC)\""); break;
|
|
default: view = wxT("NIL"); break;
|
|
}
|
|
break;
|
|
#if defined(USE_MIDI)
|
|
case Track::Note:
|
|
type = wxT("midi");
|
|
view = wxT("\"Midi\"");
|
|
break;
|
|
#endif
|
|
case Track::Label:
|
|
type = wxT("label");
|
|
view = wxT("\"Label\"");
|
|
break;
|
|
case Track::Time:
|
|
type = wxT("time");
|
|
view = wxT("\"Time\"");
|
|
break;
|
|
}
|
|
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* %d 'INDEX)\n"), ++mTrackIndex);
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* \"%s\" 'NAME)\n"), mCurTrack[0]->GetName().c_str());
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* \"%s\" 'TYPE)\n"), type.c_str());
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* %s 'VIEW)\n"), view.c_str());
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* %d 'CHANNELS)\n"), mCurNumChannels);
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* (float %s) 'START-TIME)\n"),
|
|
Internat::ToString(mCurTrack[0]->GetStartTime()).c_str());
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* (float %s) 'END-TIME)\n"),
|
|
Internat::ToString(mCurTrack[0]->GetEndTime()).c_str());
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* (float %s) 'GAIN)\n"),
|
|
Internat::ToString(mCurTrack[0]->GetGain()).c_str());
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* (float %s) 'PAN)\n"),
|
|
Internat::ToString(mCurTrack[0]->GetPan()).c_str());
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* (float %s) 'RATE)\n"),
|
|
Internat::ToString(mCurTrack[0]->GetRate()).c_str());
|
|
|
|
switch (mCurTrack[0]->GetSampleFormat())
|
|
{
|
|
case int16Sample:
|
|
bitFormat = wxT("16");
|
|
break;
|
|
case int24Sample:
|
|
bitFormat = wxT("24");
|
|
break;
|
|
case floatSample:
|
|
bitFormat = wxT("32.0");
|
|
break;
|
|
}
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* %s 'FORMAT)\n"), bitFormat.c_str());
|
|
|
|
float maxPeak = 0.0;
|
|
wxString clips;
|
|
for (int i = 0; i < mCurNumChannels; i++) {
|
|
WaveClipArray ca;
|
|
mCurTrack[i]->FillSortedClipArray(ca);
|
|
// A list of clips for mono, or an array of lists for multi-channel.
|
|
if (mCurNumChannels > 1) clips += wxT("(list ");
|
|
// Each clip is a list (start-time, end-time)
|
|
for (size_t j = 0; j < ca.GetCount(); j++) {
|
|
clips += wxString::Format(wxT("(list (float %s) (float %s))"),
|
|
Internat::ToString(ca[j]->GetStartTime()).c_str(),
|
|
Internat::ToString(ca[j]->GetEndTime()).c_str());
|
|
}
|
|
if (mCurNumChannels > 1) clips += wxT(" )");
|
|
|
|
float min, max;
|
|
mCurTrack[i]->GetMinMax(&min, &max, mT0, mT1);
|
|
maxPeak = wxMax(wxMax(fabs(min), fabs(max)), maxPeak);
|
|
}
|
|
// A list of clips for mono, or an array of lists for multi-channel.
|
|
cmd += wxString::Format(wxT("(putprop '*TRACK* %s%s ) 'CLIPS)\n"),
|
|
(mCurNumChannels == 1) ? wxT("(list ") : wxT("(vector "),
|
|
clips.c_str());
|
|
cmd += wxString::Format(wxT("(putprop '*SELECTION* (float %s) 'PEAK-LEVEL)\n"),
|
|
Internat::ToString(maxPeak).c_str());
|
|
}
|
|
|
|
if (GetType() == EffectTypeGenerate) {
|
|
nyx_set_audio_params(mCurTrack[0]->GetRate(), 0);
|
|
}
|
|
else {
|
|
nyx_set_audio_params(mCurTrack[0]->GetRate(), mCurLen);
|
|
|
|
nyx_set_input_audio(StaticGetCallback, (void *)this,
|
|
mCurNumChannels,
|
|
mCurLen, mCurTrack[0]->GetRate());
|
|
}
|
|
|
|
// Restore the Nyquist sixteenth note symbol for Generate plugins.
|
|
// See http://bugzilla.audacityteam.org/show_bug.cgi?id=490.
|
|
if (GetType() == EffectTypeGenerate) {
|
|
cmd += wxT("(setf s 0.25)\n");
|
|
}
|
|
|
|
if (mDebug) {
|
|
cmd += wxT("(setf *tracenable* T)\n");
|
|
if (mExternal) {
|
|
cmd += wxT("(setf *breakenable* T)\n");
|
|
}
|
|
}
|
|
else {
|
|
// Explicitly disable backtrace and prevent values
|
|
// from being carried through to the output.
|
|
// This should be the final command before evaluating the Nyquist script.
|
|
cmd += wxT("(setf *tracenable* NIL)\n");
|
|
}
|
|
|
|
for (unsigned int j = 0; j < mControls.GetCount(); j++) {
|
|
if (mControls[j].type == NYQ_CTRL_REAL) {
|
|
// We use Internat::ToString() rather than "%f" here because we
|
|
// always have to use the dot as decimal separator when giving
|
|
// numbers to Nyquist, whereas using "%f" will use the user's
|
|
// decimal separator which may be a comma in some countries.
|
|
cmd += wxString::Format(wxT("(setf %s %s)\n"),
|
|
mControls[j].var.c_str(),
|
|
Internat::ToString(mControls[j].val, 14).c_str());
|
|
}
|
|
else if (mControls[j].type == NYQ_CTRL_INT ||
|
|
mControls[j].type == NYQ_CTRL_CHOICE) {
|
|
cmd += wxString::Format(wxT("(setf %s %d)\n"),
|
|
mControls[j].var.c_str(),
|
|
(int)(mControls[j].val));
|
|
}
|
|
else if (mControls[j].type == NYQ_CTRL_STRING) {
|
|
cmd += wxT("(setf ");
|
|
// restrict variable names to 7-bit ASCII:
|
|
cmd += mControls[j].var.c_str();
|
|
cmd += wxT(" \"");
|
|
cmd += EscapeString(mControls[j].valStr); // unrestricted value will become quoted UTF-8
|
|
cmd += wxT("\")\n");
|
|
}
|
|
}
|
|
|
|
if (mIsSal) {
|
|
wxString str = mCmd;
|
|
EscapeString(str);
|
|
// this is tricky: we need SAL to call main so that we can get a
|
|
// SAL traceback in the event of an error (sal-compile catches the
|
|
// error and calls sal-error-output), but SAL does not return values.
|
|
// We will catch the value in a special global aud:result and if no
|
|
// error occurs, we will grab the value with a LISP expression
|
|
str += wxT("\nset aud:result = main()\n");
|
|
|
|
if (mDebug) {
|
|
// since we're about to evaluate SAL, remove LISP trace enable and
|
|
// break enable (which stops SAL processing) and turn on SAL stack
|
|
// trace
|
|
cmd += wxT("(setf *tracenable* nil)\n");
|
|
cmd += wxT("(setf *breakenable* nil)\n");
|
|
cmd += wxT("(setf *sal-traceback* t)\n");
|
|
}
|
|
|
|
if (mCompiler) {
|
|
cmd += wxT("(setf *sal-compiler-debug* t)\n");
|
|
}
|
|
|
|
cmd += wxT("(setf *sal-call-stack* nil)\n");
|
|
// if we do not set this here and an error occurs in main, another
|
|
// error will be raised when we try to return the value of aud:result
|
|
// which is unbound
|
|
cmd += wxT("(setf aud:result nil)\n");
|
|
cmd += wxT("(sal-compile-audacity \"") + str + wxT("\" t t nil)\n");
|
|
// Capture the value returned by main (saved in aud:result), but
|
|
// set aud:result to nil so sound results can be evaluated without
|
|
// retaining audio in memory
|
|
cmd += wxT("(prog1 aud:result (setf aud:result nil))\n");
|
|
}
|
|
else {
|
|
cmd += mCmd;
|
|
}
|
|
|
|
int i;
|
|
for (i = 0; i < mCurNumChannels; i++) {
|
|
mCurBuffer[i] = NULL;
|
|
}
|
|
|
|
wxLogMessage(wxT("%s"), cmd.c_str());
|
|
rval = nyx_eval_expression(cmd.mb_str(wxConvUTF8));
|
|
|
|
if (!rval) {
|
|
wxLogWarning(wxT("Nyquist returned NIL"));
|
|
return true;
|
|
}
|
|
|
|
if (rval == nyx_string) {
|
|
wxMessageBox(NyquistToWxString(nyx_get_string()),
|
|
wxT("Nyquist"),
|
|
wxOK | wxCENTRE, mUIParent);
|
|
|
|
// True if not process type.
|
|
// If not returning audio from process effect,
|
|
// return first reult then stop (disables preview)
|
|
// but allow all output from Nyquist Prompt.
|
|
return (GetType() != EffectTypeProcess || mIsPrompt);
|
|
}
|
|
|
|
if (rval == nyx_double) {
|
|
wxString str;
|
|
str.Printf(_("Nyquist returned the value:") + wxString(wxT(" %f")),
|
|
nyx_get_double());
|
|
wxMessageBox(str, wxT("Nyquist"),
|
|
wxOK | wxCENTRE, mUIParent);
|
|
return (GetType() != EffectTypeProcess || mIsPrompt);
|
|
}
|
|
|
|
if (rval == nyx_int) {
|
|
wxString str;
|
|
str.Printf(_("Nyquist returned the value:") + wxString(wxT(" %d")),
|
|
nyx_get_int());
|
|
wxMessageBox(str, wxT("Nyquist"),
|
|
wxOK | wxCENTRE, mUIParent);
|
|
return (GetType() != EffectTypeProcess || mIsPrompt);
|
|
}
|
|
|
|
if (rval == nyx_labels) {
|
|
unsigned int numLabels = nyx_get_num_labels();
|
|
unsigned int l;
|
|
LabelTrack *ltrack = NULL;
|
|
|
|
TrackListIterator iter(mOutputTracks);
|
|
for (Track *t = iter.First(); t; t = iter.Next()) {
|
|
if (t->GetKind() == Track::Label) {
|
|
ltrack = (LabelTrack *)t;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ltrack) {
|
|
ltrack = mFactory->NewLabelTrack();
|
|
AddToOutputTracks((Track *)ltrack);
|
|
}
|
|
|
|
for (l = 0; l < numLabels; l++) {
|
|
double t0, t1;
|
|
const char *str;
|
|
|
|
// PRL: to do:
|
|
// let Nyquist analyzers define more complicated selections
|
|
nyx_get_label(l, &t0, &t1, &str);
|
|
|
|
ltrack->AddLabel(SelectedRegion(t0 + mT0, t1 + mT0), UTF8CTOWX(str));
|
|
}
|
|
return (GetType() != EffectTypeProcess || mIsPrompt);
|
|
}
|
|
|
|
if (rval != nyx_audio) {
|
|
// This should not happen, but leaving in for now just in case (Dec 2014)
|
|
wxMessageBox(_("Undefined return value.\n"), wxT("Nyquist"),
|
|
wxOK | wxCENTRE, mUIParent);
|
|
return false;
|
|
}
|
|
|
|
int outChannels;
|
|
|
|
outChannels = nyx_get_audio_num_channels();
|
|
if (outChannels > mCurNumChannels) {
|
|
wxMessageBox(_("Nyquist returned too many audio channels.\n"),
|
|
wxT("Nyquist"),
|
|
wxOK | wxCENTRE, mUIParent);
|
|
return false;
|
|
}
|
|
|
|
if (outChannels == -1) {
|
|
wxMessageBox(_("Nyquist returned one audio channel as an array.\n"),
|
|
wxT("Nyquist"),
|
|
wxOK | wxCENTRE, mUIParent);
|
|
return false;
|
|
}
|
|
|
|
if (outChannels == 0) {
|
|
wxMessageBox(_("Nyquist returned an empty array.\n"),
|
|
wxT("Nyquist"),
|
|
wxOK | wxCENTRE, mUIParent);
|
|
return false;
|
|
}
|
|
|
|
double rate = mCurTrack[0]->GetRate();
|
|
for (i = 0; i < outChannels; i++) {
|
|
sampleFormat format = mCurTrack[i]->GetSampleFormat();
|
|
|
|
if (outChannels == mCurNumChannels) {
|
|
rate = mCurTrack[i]->GetRate();
|
|
}
|
|
|
|
mOutputTrack[i] = mFactory->NewWaveTrack(format, rate);
|
|
mCurBuffer[i] = NULL;
|
|
}
|
|
|
|
int success = nyx_get_audio(StaticPutCallback, (void *)this);
|
|
|
|
if (!success) {
|
|
for(i = 0; i < outChannels; i++) {
|
|
delete mOutputTrack[i];
|
|
mOutputTrack[i] = NULL;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < outChannels; i++) {
|
|
mOutputTrack[i]->Flush();
|
|
if (mCurBuffer[i]) {
|
|
DeleteSamples(mCurBuffer[i]);
|
|
}
|
|
mOutputTime = mOutputTrack[i]->GetEndTime();
|
|
|
|
if (mOutputTime <= 0) {
|
|
wxMessageBox(_("Nyquist did not return audio.\n"),
|
|
wxT("Nyquist"),
|
|
wxOK | wxCENTRE, mUIParent);
|
|
for (i = 0; i < outChannels; i++) {
|
|
delete mOutputTrack[i];
|
|
mOutputTrack[i] = NULL;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < mCurNumChannels; i++) {
|
|
WaveTrack *out;
|
|
|
|
if (outChannels == mCurNumChannels) {
|
|
out = mOutputTrack[i];
|
|
}
|
|
else {
|
|
out = mOutputTrack[0];
|
|
}
|
|
|
|
if (mMergeClips < 0) {
|
|
// Use sample counts to determine default behaviour - times will rarely be equal.
|
|
bool bMergeClips = (out->TimeToLongSamples(mT0) + out->TimeToLongSamples(mOutputTime) ==
|
|
out->TimeToLongSamples(mT1));
|
|
mCurTrack[i]->ClearAndPaste(mT0, mT1, out, mRestoreSplits, bMergeClips);
|
|
}
|
|
else {
|
|
mCurTrack[i]->ClearAndPaste(mT0, mT1, out, mRestoreSplits, mMergeClips != 0);
|
|
}
|
|
|
|
// If we were first in the group adjust non-selected group tracks
|
|
if (mFirstInGroup) {
|
|
SyncLockedTracksIterator git(mOutputTracks);
|
|
Track *t;
|
|
for (t = git.First(mCurTrack[i]); t; t = git.Next())
|
|
{
|
|
if (!t->GetSelected() && t->IsSyncLockSelected()) {
|
|
t->SyncLockAdjust(mT1, mT0 + out->GetEndTime());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only the first channel can be first in its group
|
|
mFirstInGroup = false;
|
|
}
|
|
|
|
for (i = 0; i < outChannels; i++) {
|
|
delete mOutputTrack[i];
|
|
mOutputTrack[i] = NULL;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// NyquistEffect Implementation
|
|
// ============================================================================
|
|
|
|
wxString NyquistEffect::NyquistToWxString(const char *nyqString)
|
|
{
|
|
wxString str(nyqString, wxConvUTF8);
|
|
if (nyqString != NULL && nyqString[0] && str.IsEmpty()) {
|
|
// invalid UTF-8 string, convert as Latin-1
|
|
str = _("[Warning: Nyquist returned invalid UTF-8 string, converted here as Latin-1]");
|
|
str += LAT1CTOWX(nyqString);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
wxString NyquistEffect::EscapeString(const wxString & inStr)
|
|
{
|
|
wxString str = inStr;
|
|
|
|
str.Replace(wxT("\\"), wxT("\\\\"));
|
|
str.Replace(wxT("\""), wxT("\\\""));
|
|
|
|
return str;
|
|
}
|
|
|
|
void NyquistEffect::SetCommand(wxString cmd)
|
|
{
|
|
mExternal = true;
|
|
|
|
ParseCommand(cmd);
|
|
}
|
|
|
|
void NyquistEffect::Break()
|
|
{
|
|
mBreak = true;
|
|
}
|
|
|
|
void NyquistEffect::Continue()
|
|
{
|
|
mCont = true;
|
|
}
|
|
|
|
void NyquistEffect::Stop()
|
|
{
|
|
mStop = true;
|
|
}
|
|
|
|
wxString NyquistEffect::UnQuote(wxString s)
|
|
{
|
|
wxString out;
|
|
int len = s.Length();
|
|
|
|
if (len >= 2 && s[0] == wxT('\"') && s[len - 1] == wxT('\"')) {
|
|
return s.Mid(1, len - 2);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
double NyquistEffect::GetCtrlValue(wxString s)
|
|
{
|
|
if (s == wxT("rate")) {
|
|
TrackListOfKindIterator iter(Track::Wave, mTracks);
|
|
return ((WaveTrack *)iter.First())->GetRate();
|
|
}
|
|
|
|
return Internat::CompatibleToDouble(s);
|
|
}
|
|
|
|
void NyquistEffect::Parse(wxString line)
|
|
{
|
|
wxArrayString tokens;
|
|
|
|
int i;
|
|
int len = line.Length();
|
|
bool sl = false;
|
|
bool q = false;
|
|
wxString tok = wxT("");
|
|
|
|
for (i = 1; i < len; i++) {
|
|
wxChar c = line[i];
|
|
|
|
if (c == wxT('\\')) {
|
|
sl = true;
|
|
}
|
|
else if (c == wxT('"')) {
|
|
q = !q;
|
|
}
|
|
else {
|
|
if ((!q && !sl && c == wxT(' ')) || c == wxT('\t')) {
|
|
tokens.Add(tok);
|
|
tok = wxT("");
|
|
}
|
|
else if (sl && c == wxT('n')) {
|
|
tok += wxT('\n');
|
|
}
|
|
else {
|
|
tok += c;
|
|
}
|
|
|
|
sl = false;
|
|
}
|
|
}
|
|
|
|
if (tok != wxT("")) {
|
|
tokens.Add(tok);
|
|
}
|
|
|
|
len = tokens.GetCount();
|
|
if (len < 1) {
|
|
return;
|
|
}
|
|
|
|
// As of version 4 plugins ";nyquist plug-in" is depricated in favour of ";nyquist plugin".
|
|
// The hyphenated version must be maintained while we support plugin versions < 4.
|
|
if (len == 2 && tokens[0] == wxT("nyquist") &&
|
|
(tokens[1] == wxT("plugin") || tokens[1] == wxT("plug-in"))) {
|
|
mOK = true;
|
|
return;
|
|
}
|
|
|
|
if (len >= 2 && tokens[0] == wxT("type")) {
|
|
if (tokens[1] == wxT("process")) {
|
|
mType = EffectTypeProcess;
|
|
}
|
|
else if (tokens[1] == wxT("generate")) {
|
|
mType = EffectTypeGenerate;
|
|
}
|
|
else if (tokens[1] == wxT("analyze")) {
|
|
mType = EffectTypeAnalyze;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (len == 2 && tokens[0] == wxT("codetype")) {
|
|
// This will stop ParseProgram() from doing a best guess as program type.
|
|
if (tokens[1] == wxT("lisp")) {
|
|
mIsSal = false;
|
|
mFoundType = true;
|
|
}
|
|
else if (tokens[1] == wxT("sal")) {
|
|
mIsSal = true;
|
|
mFoundType = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (len >= 2 && tokens[0] == wxT("debugflags")) {
|
|
for (int i = 1; i < len; i++) {
|
|
// Note: "trace" and "notrace" are overridden by "Debug" and "OK"
|
|
// buttons if the plug-in generates a dialog box by using controls
|
|
if (tokens[i] == wxT("trace")) {
|
|
mDebug = true;
|
|
}
|
|
else if (tokens[i] == wxT("notrace")) {
|
|
mDebug = false;
|
|
}
|
|
else if (tokens[i] == wxT("compiler")) {
|
|
mCompiler = true;
|
|
}
|
|
else if (tokens[i] == wxT("nocompiler")) {
|
|
mCompiler = false;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// We support versions 1, 2 and 3
|
|
// (Version 2 added support for string parameters.)
|
|
// (Version 3 added support for choice parameters.)
|
|
// (Version 4 added support for project/track/selection information.)
|
|
if (len >= 2 && tokens[0] == wxT("version")) {
|
|
long v;
|
|
tokens[1].ToLong(&v);
|
|
if (v < 1 && v > 4) {
|
|
// This is an unsupported plug-in version
|
|
mOK = false;
|
|
return;
|
|
}
|
|
mVersion = (int) v;
|
|
}
|
|
|
|
if (len >= 2 && tokens[0] == wxT("name")) {
|
|
mName = UnQuote(tokens[1]);
|
|
if (mName.EndsWith(wxT("...")))
|
|
{
|
|
mName = mName.RemoveLast(3);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (len >= 2 && tokens[0] == wxT("action")) {
|
|
mAction = UnQuote(tokens[1]);
|
|
return;
|
|
}
|
|
|
|
if (len >= 2 && tokens[0] == wxT("info")) {
|
|
mInfo = UnQuote(tokens[1]);
|
|
return;
|
|
}
|
|
|
|
if (len >= 2 && tokens[0] == wxT("preview")) {
|
|
if (tokens[1] == wxT("enabled") || tokens[1] == wxT("true")) {
|
|
mEnablePreview = true;
|
|
}
|
|
else if (tokens[1] == wxT("disabled") || tokens[1] == wxT("false")) {
|
|
mEnablePreview = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
#if defined(EXPERIMENTAL_NYQUIST_SPLIT_CONTROL)
|
|
if (len >= 2 && tokens[0] == wxT("mergeclips")) {
|
|
long v;
|
|
// -1 = auto (default), 0 = don't merge clips, 1 = do merge clips
|
|
tokens[1].ToLong(&v);
|
|
mMergeClips = v;
|
|
return;
|
|
}
|
|
|
|
if (len >= 2 && tokens[0] == wxT("restoresplits")) {
|
|
long v;
|
|
// Splits are restored by default. Set to 0 to prevent.
|
|
tokens[1].ToLong(&v);
|
|
mRestoreSplits = v;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (len >= 2 && tokens[0] == wxT("author")) {
|
|
mAuthor = UnQuote(tokens[1]);
|
|
return;
|
|
}
|
|
|
|
if (len >= 2 && tokens[0] == wxT("copyright")) {
|
|
mCopyright = UnQuote(tokens[1]);
|
|
return;
|
|
}
|
|
|
|
if (len >= 6 && tokens[0] == wxT("control")) {
|
|
NyqControl ctrl;
|
|
|
|
ctrl.var = tokens[1];
|
|
ctrl.name = tokens[2];
|
|
ctrl.label = tokens[4];
|
|
ctrl.valStr = tokens[5];
|
|
ctrl.val = GetCtrlValue(ctrl.valStr);
|
|
|
|
if (tokens[3] == wxT("string")) {
|
|
ctrl.type = NYQ_CTRL_STRING;
|
|
}
|
|
else if (tokens[3] == wxT("choice")) {
|
|
ctrl.type = NYQ_CTRL_CHOICE;
|
|
}
|
|
else {
|
|
if (len < 8) {
|
|
return;
|
|
}
|
|
|
|
if ((tokens[3] == wxT("real")) ||
|
|
(tokens[3] == wxT("float"))) // undocumented, but useful, alternative
|
|
ctrl.type = NYQ_CTRL_REAL;
|
|
else if (tokens[3] == wxT("int"))
|
|
ctrl.type = NYQ_CTRL_INT;
|
|
else
|
|
{
|
|
wxString str;
|
|
str.Printf(_("Bad Nyquist 'control' type specification: '%s' in plugin file '%s'.\nControl not created."),
|
|
tokens[3].c_str(), mFileName.GetFullPath().c_str());
|
|
|
|
// Too disturbing to show alert before Audacity frame is up.
|
|
// wxMessageBox(str, wxT("Nyquist Warning"), wxOK | wxICON_EXCLAMATION);
|
|
|
|
// Note that the AudacityApp's mLogger has not yet been created,
|
|
// so this brings up an alert box, but after the Audacity frame is up.
|
|
wxLogWarning(str);
|
|
return;
|
|
}
|
|
|
|
ctrl.lowStr = tokens[6];
|
|
ctrl.low = GetCtrlValue(ctrl.lowStr);
|
|
ctrl.highStr = tokens[7];
|
|
ctrl.high = GetCtrlValue(ctrl.highStr);
|
|
|
|
if (ctrl.high < ctrl.low) {
|
|
ctrl.high = ctrl.low + 1;
|
|
}
|
|
|
|
if (ctrl.val < ctrl.low) {
|
|
ctrl.val = ctrl.low;
|
|
}
|
|
|
|
if (ctrl.val > ctrl.high) {
|
|
ctrl.val = ctrl.high;
|
|
}
|
|
|
|
ctrl.ticks = 1000;
|
|
if (ctrl.type == NYQ_CTRL_INT &&
|
|
(ctrl.high - ctrl.low < ctrl.ticks)) {
|
|
ctrl.ticks = (int)(ctrl.high - ctrl.low);
|
|
}
|
|
}
|
|
|
|
if( mPresetNames.Index( ctrl.var ) == wxNOT_FOUND )
|
|
{
|
|
mControls.Add(ctrl);
|
|
}
|
|
}
|
|
|
|
if (len >= 2 && tokens[0] == wxT("categories")) {
|
|
for (size_t i = 1; i < tokens.GetCount(); ++i) {
|
|
mCategories.Add(tokens[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool NyquistEffect::ParseProgram(wxInputStream & stream)
|
|
{
|
|
if (!stream.IsOk())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
wxTextInputStream pgm(stream);
|
|
|
|
mCmd = wxT("");
|
|
mIsSal = false;
|
|
mControls.Clear();
|
|
mCategories.Clear();
|
|
|
|
mFoundType = false;
|
|
while (!stream.Eof() && stream.IsOk())
|
|
{
|
|
wxString line = pgm.ReadLine().Trim(false);
|
|
if (line.Length() > 1 && line[0] == wxT(';'))
|
|
{
|
|
Parse(line);
|
|
}
|
|
else if (!mFoundType && line.Length() > 0)
|
|
{
|
|
mFoundType = true;
|
|
|
|
if (line[0] == wxT('(') ||
|
|
(line[0] == wxT('#') && line.Length() > 1 && line[1] == wxT('|')))
|
|
{
|
|
mIsSal = false;
|
|
}
|
|
else if (line.MakeUpper().Find(wxT("RETURN")) != wxNOT_FOUND)
|
|
{
|
|
mIsSal = true;
|
|
}
|
|
else if (mIsPrompt)
|
|
{
|
|
wxMessageBox(_("Your code looks like SAL syntax, but there is no return statement. Either use a return statement such as\n\treturn s * 0.1\nfor SAL, or begin with an open parenthesis such as\n\t(mult s 0.1)\n for LISP."), _("Error in Nyquist code"), wxOK | wxCENTRE);
|
|
return false;
|
|
}
|
|
// Just throw it at Nyquist to see what happens
|
|
}
|
|
|
|
// preserve comments so that SAL effects compile with proper line numbers
|
|
mCmd += line + wxT("\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void NyquistEffect::ParseFile()
|
|
{
|
|
mEnablePreview = true;
|
|
|
|
wxFileInputStream stream(mFileName.GetFullPath());
|
|
|
|
ParseProgram(stream);
|
|
}
|
|
|
|
bool NyquistEffect::ParseCommand(const wxString & cmd)
|
|
{
|
|
mEnablePreview = true;
|
|
|
|
wxStringInputStream stream(cmd + wxT(" "));
|
|
|
|
return ParseProgram(stream);
|
|
}
|
|
|
|
int NyquistEffect::StaticGetCallback(float *buffer, int channel,
|
|
long start, long len, long totlen,
|
|
void *userdata)
|
|
{
|
|
NyquistEffect *This = (NyquistEffect *)userdata;
|
|
return This->GetCallback(buffer, channel, start, len, totlen);
|
|
}
|
|
|
|
int NyquistEffect::GetCallback(float *buffer, int ch,
|
|
long start, long len, long WXUNUSED(totlen))
|
|
{
|
|
if (mCurBuffer[ch]) {
|
|
if ((mCurStart[ch] + start) < mCurBufferStart[ch] ||
|
|
(mCurStart[ch] + start)+len >
|
|
mCurBufferStart[ch]+mCurBufferLen[ch]) {
|
|
delete[] mCurBuffer[ch];
|
|
mCurBuffer[ch] = NULL;
|
|
}
|
|
}
|
|
|
|
if (!mCurBuffer[ch]) {
|
|
mCurBufferStart[ch] = (mCurStart[ch] + start);
|
|
mCurBufferLen[ch] = mCurTrack[ch]->GetBestBlockSize(mCurBufferStart[ch]);
|
|
|
|
if (mCurBufferLen[ch] < len) {
|
|
mCurBufferLen[ch] = mCurTrack[ch]->GetIdealBlockSize();
|
|
}
|
|
|
|
if (mCurBufferStart[ch] + mCurBufferLen[ch] > mCurStart[ch] + mCurLen) {
|
|
mCurBufferLen[ch] = mCurStart[ch] + mCurLen - mCurBufferStart[ch];
|
|
}
|
|
|
|
mCurBuffer[ch] = NewSamples(mCurBufferLen[ch], floatSample);
|
|
if (!mCurTrack[ch]->Get(mCurBuffer[ch], floatSample,
|
|
mCurBufferStart[ch], mCurBufferLen[ch])) {
|
|
|
|
wxPrintf(wxT("GET error\n"));
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
long offset = (mCurStart[ch] + start) - mCurBufferStart[ch];
|
|
CopySamples(mCurBuffer[ch] + offset*SAMPLE_SIZE(floatSample), floatSample,
|
|
(samplePtr)buffer, floatSample,
|
|
len);
|
|
|
|
if (ch == 0) {
|
|
double progress = mScale*(((float)start+len)/mCurLen);
|
|
|
|
if (progress > mProgressIn) {
|
|
mProgressIn = progress;
|
|
}
|
|
|
|
if (TotalProgress(mProgressIn+mProgressOut+mProgressTot)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int NyquistEffect::StaticPutCallback(float *buffer, int channel,
|
|
long start, long len, long totlen,
|
|
void *userdata)
|
|
{
|
|
NyquistEffect *This = (NyquistEffect *)userdata;
|
|
return This->PutCallback(buffer, channel, start, len, totlen);
|
|
}
|
|
|
|
int NyquistEffect::PutCallback(float *buffer, int channel,
|
|
long start, long len, long totlen)
|
|
{
|
|
if (channel == 0) {
|
|
double progress = mScale*((float)(start+len)/totlen);
|
|
|
|
if (progress > mProgressOut) {
|
|
mProgressOut = progress;
|
|
}
|
|
|
|
if (TotalProgress(mProgressIn+mProgressOut+mProgressTot)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (mOutputTrack[channel]->Append((samplePtr)buffer, floatSample, len)) {
|
|
return 0; // success
|
|
}
|
|
|
|
return -1; // failure
|
|
}
|
|
|
|
void NyquistEffect::StaticOutputCallback(int c, void *This)
|
|
{
|
|
((NyquistEffect *)This)->OutputCallback(c);
|
|
}
|
|
|
|
void NyquistEffect::OutputCallback(int c)
|
|
{
|
|
if (mDebug && !mExternal) {
|
|
mOutput += c;
|
|
return;
|
|
}
|
|
// mOutput += wxString::FromUTF8((char *) &c, 1);
|
|
mOutput += c;
|
|
std::cout << (char)c;
|
|
}
|
|
|
|
void NyquistEffect::StaticOSCallback(void *This)
|
|
{
|
|
((NyquistEffect *)This)->OSCallback();
|
|
}
|
|
|
|
void NyquistEffect::OSCallback()
|
|
{
|
|
if (mStop) {
|
|
mStop = false;
|
|
nyx_stop();
|
|
}
|
|
else if (mBreak) {
|
|
mBreak = false;
|
|
nyx_break();
|
|
}
|
|
else if (mCont) {
|
|
mCont = false;
|
|
nyx_continue();
|
|
}
|
|
|
|
// LLL: STF figured out that yielding while the effect is being applied
|
|
// produces an EXTREME slowdown. It appears that yielding is not
|
|
// really necessary on Linux and Windows.
|
|
//
|
|
// However, on the Mac, the spinning cursor appears during longer
|
|
// Nyquist processing and that may cause the user to think Audacity
|
|
// has crashed or hung. In addition, yielding or not on the Mac
|
|
// doesn't seem to make much of a difference in execution time.
|
|
//
|
|
// So, yielding on the Mac only...
|
|
#if defined(__WXMAC__)
|
|
wxYieldIfNeeded();
|
|
#endif
|
|
}
|
|
|
|
wxArrayString NyquistEffect::GetNyquistSearchPath()
|
|
{
|
|
wxArrayString audacityPathList = wxGetApp().audacityPathList;
|
|
wxArrayString pathList;
|
|
|
|
for (size_t i = 0; i < audacityPathList.GetCount(); i++)
|
|
{
|
|
wxString prefix = audacityPathList[i] + wxFILE_SEP_PATH;
|
|
wxGetApp().AddUniquePathToPathList(prefix + wxT("nyquist"), pathList);
|
|
wxGetApp().AddUniquePathToPathList(prefix + wxT("plugins"), pathList);
|
|
wxGetApp().AddUniquePathToPathList(prefix + wxT("plug-ins"), pathList);
|
|
}
|
|
|
|
return pathList;
|
|
}
|
|
|
|
bool NyquistEffect::TransferDataToPromptWindow()
|
|
{
|
|
mCommandText->ChangeValue(mInputCmd);
|
|
mVersionCheckBox->SetValue(mVersion <= 3);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool NyquistEffect::TransferDataToEffectWindow()
|
|
{
|
|
for (size_t i = 0, cnt = mControls.GetCount(); i < cnt; i++)
|
|
{
|
|
NyqControl & ctrl = mControls[i];
|
|
|
|
if (ctrl.type == NYQ_CTRL_STRING)
|
|
{
|
|
wxTextCtrl *t = (wxTextCtrl *) mUIParent->FindWindow(ID_Text + i);
|
|
t->ChangeValue(ctrl.valStr);
|
|
}
|
|
else if (ctrl.type == NYQ_CTRL_CHOICE)
|
|
{
|
|
wxArrayString choices = wxStringTokenize(ctrl.label, wxT(","));
|
|
|
|
int val = (int)ctrl.val;
|
|
if (val < 0 || val >= (int)choices.GetCount())
|
|
{
|
|
val = 0;
|
|
}
|
|
|
|
wxChoice *c = (wxChoice *) mUIParent->FindWindow(ID_Choice + i);
|
|
c->SetSelection(val);
|
|
}
|
|
else
|
|
{
|
|
// wxTextCtrls are handled by the validators
|
|
double range = ctrl.high - ctrl.low;
|
|
int val = (int)(0.5 + ctrl.ticks * (ctrl.val - ctrl.low) / range);
|
|
wxSlider *s = (wxSlider *) mUIParent->FindWindow(ID_Slider + i);
|
|
s->SetValue(val);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool NyquistEffect::TransferDataFromPromptWindow()
|
|
{
|
|
mInputCmd = mCommandText->GetValue();
|
|
mVersion = mVersionCheckBox->GetValue() ? 3 : 4;
|
|
|
|
return ParseCommand(mInputCmd);
|
|
}
|
|
|
|
bool NyquistEffect::TransferDataFromEffectWindow()
|
|
{
|
|
if (!mExternal)
|
|
{
|
|
//TODO: If we want to auto-add parameters from spectral selection,
|
|
//we will need to modify this test.
|
|
//Note that removing it stops the caching of parameter values,
|
|
//(during this session).
|
|
if (mFileName.GetModificationTime().IsLaterThan(mFileModified))
|
|
{
|
|
ParseFile();
|
|
mFileModified = mFileName.GetModificationTime();
|
|
}
|
|
}
|
|
|
|
if (mControls.GetCount() == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < mControls.GetCount(); i++)
|
|
{
|
|
NyqControl *ctrl = &mControls[i];
|
|
|
|
if (ctrl->type == NYQ_CTRL_STRING)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ctrl->val == UNINITIALIZED_CONTROL)
|
|
{
|
|
ctrl->val = GetCtrlValue(ctrl->valStr);
|
|
}
|
|
|
|
if (ctrl->type == NYQ_CTRL_CHOICE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ctrl->low = GetCtrlValue(ctrl->lowStr);
|
|
ctrl->high = GetCtrlValue(ctrl->highStr);
|
|
|
|
if (ctrl->high < ctrl->low)
|
|
{
|
|
ctrl->high = ctrl->low + 1;
|
|
}
|
|
|
|
if (ctrl->val < ctrl->low)
|
|
{
|
|
ctrl->val = ctrl->low;
|
|
}
|
|
|
|
if (ctrl->val > ctrl->high)
|
|
{
|
|
ctrl->val = ctrl->high;
|
|
}
|
|
|
|
ctrl->ticks = 1000;
|
|
if (ctrl->type == NYQ_CTRL_INT &&
|
|
(ctrl->high - ctrl->low < ctrl->ticks))
|
|
{
|
|
ctrl->ticks = (int)(ctrl->high - ctrl->low);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void NyquistEffect::BuildPromptWindow(ShuttleGui & S)
|
|
{
|
|
S.StartVerticalLay();
|
|
{
|
|
S.StartMultiColumn(3, wxEXPAND);
|
|
{
|
|
S.SetStretchyCol(1);
|
|
|
|
S.AddVariableText(_("Enter Nyquist Command: "));
|
|
|
|
S.AddSpace(1, 1);
|
|
|
|
mVersionCheckBox = S.AddCheckBox(_("&Use legacy (version 3) syntax."),
|
|
(mVersion == 3) ? wxT("true") : wxT("false"));
|
|
}
|
|
S.EndMultiColumn();
|
|
|
|
S.StartHorizontalLay(wxEXPAND, 1);
|
|
{
|
|
mCommandText = S.AddTextWindow(wxT(""));
|
|
mCommandText->SetMinSize(wxSize(500, 200));
|
|
}
|
|
S.EndHorizontalLay();
|
|
|
|
S.StartHorizontalLay(wxALIGN_CENTER, 0);
|
|
{
|
|
S.Id(ID_Load).AddButton(_("&Load"));
|
|
S.Id(ID_Save).AddButton(_("&Save"));
|
|
S.Id(ID_Clear).AddButton(_("&Clear"));
|
|
S.AddSpace(10, 1);
|
|
S.Id(ID_Debug).AddButton(_("&Debug"));
|
|
}
|
|
S.EndHorizontalLay();
|
|
}
|
|
S.EndVerticalLay();
|
|
|
|
mCommandText->SetFocus();
|
|
}
|
|
|
|
void NyquistEffect::BuildEffectWindow(ShuttleGui & S)
|
|
{
|
|
S.SetStyle(wxVSCROLL | wxTAB_TRAVERSAL);
|
|
wxScrolledWindow *scroller = S.StartScroller(2);
|
|
{
|
|
S.StartMultiColumn(4);
|
|
{
|
|
for (size_t i = 0; i < mControls.GetCount(); i++)
|
|
{
|
|
NyqControl & ctrl = mControls[i];
|
|
|
|
S.AddPrompt(ctrl.name + wxT(":"));
|
|
|
|
if (ctrl.type == NYQ_CTRL_STRING)
|
|
{
|
|
S.AddSpace(10, 10);
|
|
|
|
S.Id(ID_Text + i).AddTextBox(wxT(""), ctrl.valStr, 12);
|
|
}
|
|
else if (ctrl.type == NYQ_CTRL_CHOICE)
|
|
{
|
|
S.AddSpace(10, 10);
|
|
|
|
wxArrayString choices = wxStringTokenize(ctrl.label, wxT(","));
|
|
|
|
int val = (int)ctrl.val;
|
|
if (val < 0 || val >= (int)choices.GetCount())
|
|
{
|
|
val = 0;
|
|
}
|
|
|
|
S.Id(ID_Choice + i).AddChoice(wxT(""), choices[val], &choices);
|
|
}
|
|
else
|
|
{
|
|
// Integer or Real
|
|
wxTextCtrl *item = S.Id(ID_Text+i).AddTextBox(wxT(""), wxT(""), 12);
|
|
|
|
double range = ctrl.high - ctrl.low;
|
|
|
|
if (ctrl.type == NYQ_CTRL_REAL)
|
|
{
|
|
// > 12 decimal places can cause rounding errors in display.
|
|
FloatingPointValidator<double> vld(12, &ctrl.val);
|
|
vld.SetRange(ctrl.low, ctrl.high);
|
|
|
|
// Set number of decimal places
|
|
int style = range < 10 ? NUM_VAL_THREE_TRAILING_ZEROES :
|
|
range < 100 ? NUM_VAL_TWO_TRAILING_ZEROES :
|
|
NUM_VAL_ONE_TRAILING_ZERO;
|
|
vld.SetStyle(style);
|
|
|
|
item->SetValidator(vld);
|
|
}
|
|
else
|
|
{
|
|
IntegerValidator<double> vld(&ctrl.val);
|
|
vld.SetRange((int) ctrl.low, (int) ctrl.high);
|
|
item->SetValidator(vld);
|
|
}
|
|
|
|
int val = (int)(0.5 + ctrl.ticks * (ctrl.val - ctrl.low) / range);
|
|
S.SetStyle(wxSL_HORIZONTAL);
|
|
S.Id(ID_Slider + i).AddSlider(wxT(""), val, ctrl.ticks, 0);
|
|
S.SetSizeHints(150, -1);
|
|
}
|
|
|
|
if (ctrl.type == NYQ_CTRL_CHOICE || ctrl.label.IsEmpty())
|
|
{
|
|
S.AddSpace(10, 10);
|
|
}
|
|
else
|
|
{
|
|
S.AddUnits(ctrl.label);
|
|
}
|
|
}
|
|
}
|
|
S.EndMultiColumn();
|
|
}
|
|
S.EndScroller();
|
|
|
|
scroller->SetScrollRate(0, 20);
|
|
}
|
|
|
|
// NyquistEffect implementation
|
|
|
|
bool NyquistEffect::IsOk()
|
|
{
|
|
return mOK;
|
|
}
|
|
|
|
void NyquistEffect::OnLoad(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
if (mCommandText->IsModified())
|
|
{
|
|
if (wxMessageBox(_("Current program has been modified.\nDiscard changes?"),
|
|
GetName(),
|
|
wxYES_NO) == wxNO)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
FileDialog dlog(mUIParent,
|
|
_("Load Nyquist script"),
|
|
mFileName.GetPath(),
|
|
wxEmptyString,
|
|
_("Nyquist scripts (*.ny)|*.ny|Lisp scripts (*.lsp)|*.lsp|All files|*"),
|
|
wxFD_OPEN | wxRESIZE_BORDER);
|
|
|
|
if (dlog.ShowModal() != wxID_OK)
|
|
{
|
|
return;
|
|
}
|
|
|
|
mFileName = dlog.GetPath();
|
|
|
|
if (!mCommandText->LoadFile(mFileName.GetFullPath()))
|
|
{
|
|
wxMessageBox(_("File could not be loaded"), GetName());
|
|
}
|
|
}
|
|
|
|
void NyquistEffect::OnSave(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
FileDialog dlog(mUIParent,
|
|
_("Save Nyquist script"),
|
|
mFileName.GetPath(),
|
|
mFileName.GetFullName(),
|
|
_("Nyquist scripts (*.ny)|*.ny|Lisp scripts (*.lsp)|*.lsp|All files|*"),
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER);
|
|
|
|
if (dlog.ShowModal() != wxID_OK)
|
|
{
|
|
return;
|
|
}
|
|
|
|
mFileName = dlog.GetPath();
|
|
|
|
if (!mCommandText->SaveFile(mFileName.GetFullPath()))
|
|
{
|
|
wxMessageBox(_("File could not be saved"), GetName());
|
|
}
|
|
}
|
|
|
|
void NyquistEffect::OnClear(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
mCommandText->Clear();
|
|
}
|
|
|
|
void NyquistEffect::OnDebug(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
NyquistEffect effect(NYQUIST_WORKER_ID);
|
|
|
|
effect.SetCommand(mCommandText->GetValue());
|
|
effect.mDebug = true;
|
|
|
|
SelectedRegion region(mT0, mT1);
|
|
effect.DoEffect(mUIParent,
|
|
mProjectRate,
|
|
mTracks,
|
|
mFactory,
|
|
®ion,
|
|
true);
|
|
|
|
NyquistOutputDialog dlog(mUIParent,
|
|
wxID_ANY,
|
|
_("Nyquist"),
|
|
_("Nyquist Output: "),
|
|
effect.mOutput);
|
|
dlog.CentreOnParent();
|
|
dlog.ShowModal();
|
|
|
|
return;
|
|
}
|
|
|
|
void NyquistEffect::OnSlider(wxCommandEvent & evt)
|
|
{
|
|
int i = evt.GetId() - ID_Slider;
|
|
NyqControl & ctrl = mControls[i];
|
|
|
|
int val = evt.GetInt();
|
|
double range = ctrl.high - ctrl.low;
|
|
double newVal = (val / (double)ctrl.ticks) * range + ctrl.low;
|
|
|
|
// Determine precision for displayed number
|
|
int precision = range < 1.0 ? 3 :
|
|
range < 10.0 ? 2 :
|
|
range < 100.0 ? 1 :
|
|
0;
|
|
|
|
// If the value is at least one tick different from the current value
|
|
// change it (this prevents changes from manually entered values unless
|
|
// the slider actually moved)
|
|
if (fabs(newVal - ctrl.val) >= (1 / (double)ctrl.ticks) * range &&
|
|
fabs(newVal - ctrl.val) >= pow(0.1, precision) / 2)
|
|
{
|
|
// First round to the appropriate precision
|
|
newVal *= pow(10.0, precision);
|
|
newVal = floor(newVal + 0.5);
|
|
newVal /= pow(10.0, precision);
|
|
|
|
ctrl.val = newVal;
|
|
|
|
mUIParent->FindWindow(ID_Text + i)->GetValidator()->TransferToWindow();
|
|
}
|
|
}
|
|
|
|
void NyquistEffect::OnChoice(wxCommandEvent & evt)
|
|
{
|
|
mControls[evt.GetId() - ID_Choice].val = (double) evt.GetInt();
|
|
}
|
|
|
|
void NyquistEffect::OnText(wxCommandEvent & evt)
|
|
{
|
|
int i = evt.GetId() - ID_Text;
|
|
|
|
NyqControl & ctrl = mControls[i];
|
|
|
|
if (ctrl.type == NYQ_CTRL_STRING)
|
|
{
|
|
ctrl.valStr = evt.GetString();
|
|
}
|
|
else
|
|
{
|
|
if (wxDynamicCast(evt.GetEventObject(), wxWindow)->GetValidator()->TransferFromWindow())
|
|
{
|
|
int pos = (int)floor((ctrl.val - ctrl.low) /
|
|
(ctrl.high - ctrl.low) * ctrl.ticks + 0.5);
|
|
|
|
wxSlider *slider = (wxSlider *)mUIParent->FindWindow(ID_Slider + i);
|
|
slider->SetValue(pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// NyquistOutputDialog
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
BEGIN_EVENT_TABLE(NyquistOutputDialog, wxDialog)
|
|
EVT_BUTTON(wxID_OK, NyquistOutputDialog::OnOk)
|
|
END_EVENT_TABLE()
|
|
|
|
NyquistOutputDialog::NyquistOutputDialog(wxWindow * parent, wxWindowID id,
|
|
const wxString & title,
|
|
const wxString & prompt,
|
|
wxString message)
|
|
: wxDialog(parent, id, title)
|
|
{
|
|
wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
|
|
wxBoxSizer *hSizer;
|
|
wxButton *button;
|
|
wxControl *item;
|
|
|
|
item = new wxStaticText(this, -1, prompt);
|
|
item->SetName(prompt); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs)
|
|
mainSizer->Add(item, 0, wxALIGN_LEFT | wxLEFT | wxTOP | wxRIGHT, 10);
|
|
|
|
// TODO use ShowInfoDialog() instead.
|
|
// Beware this dialog MUST work with screen readers.
|
|
item = new wxTextCtrl(this, -1, message,
|
|
wxDefaultPosition, wxSize(400, 200),
|
|
wxTE_MULTILINE | wxTE_READONLY);
|
|
mainSizer->Add(item, 0, wxALIGN_LEFT | wxALL, 10);
|
|
|
|
hSizer = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
/* i18n-hint: In most languages OK is to be translated as OK. It appears on a button.*/
|
|
button = new wxButton(this, wxID_OK, _("OK"));
|
|
button->SetDefault();
|
|
hSizer->Add(button, 0, wxALIGN_CENTRE | wxALL, 5);
|
|
|
|
mainSizer->Add(hSizer, 0, wxALIGN_CENTRE | wxLEFT | wxBOTTOM | wxRIGHT, 5);
|
|
|
|
SetAutoLayout(true);
|
|
SetSizer(mainSizer);
|
|
mainSizer->Fit(this);
|
|
mainSizer->SetSizeHints(this);
|
|
}
|
|
|
|
// ============================================================================
|
|
// NyquistOutputDialog implementation
|
|
// ============================================================================
|
|
|
|
void NyquistOutputDialog::OnOk(wxCommandEvent & /* event */)
|
|
{
|
|
EndModal(wxID_OK);
|
|
}
|
|
|