1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-09-19 17:40:51 +02:00

Merge branch 'scripting-with-nyquist'

This commit is contained in:
James Crook 2018-08-01 20:35:50 +01:00
commit 703bf2b647
13 changed files with 267 additions and 63 deletions

View File

@ -103,7 +103,10 @@ class AUDACITY_DLL_API EffectDefinitionInterface /* not final */ : public Ident
public: public:
virtual ~EffectDefinitionInterface() {}; virtual ~EffectDefinitionInterface() {};
// Type determines how it behaves.
virtual EffectType GetType() = 0; virtual EffectType GetType() = 0;
// Classification determines which menu it appears in.
virtual EffectType GetClassification() { return GetType();};
virtual IdentInterfaceSymbol GetFamilyId() = 0; virtual IdentInterfaceSymbol GetFamilyId() = 0;

View File

@ -0,0 +1 @@
extern LVAL xlc_aud_do(void);

View File

@ -0,0 +1 @@
{ "AUD-DO", S, xlc_aud_do},

View File

@ -3,6 +3,7 @@
/* CHANGE LOG /* CHANGE LOG
* -------------------------------------------------------------------- * --------------------------------------------------------------------
* 28Apr03 dm changes for portability and fix compiler warnings * 28Apr03 dm changes for portability and fix compiler warnings
* 12Feb18 jkc added aud-do function, that is only available in Audacity.
*/ */
#include "xlisp.h" #include "xlisp.h"
@ -53,3 +54,50 @@ void print_local_gc_info(void)
max_sample_block_len) / 1024); max_sample_block_len) / 1024);
stdputstr(buf); stdputstr(buf);
} }
/*--------------------Audacity Automation -------------------------*/
/* These functions may later move to their own source file. */
extern void * ExecForLisp( char * pIn );
extern void * nyq_make_opaque_string( int size, unsigned char *src );
void * nyq_make_opaque_string( int size, unsigned char *src ){
LVAL dst;
unsigned char * dstp;
dst = new_string((int)(size+2));
dstp = getstring(dst);
/* copy the source to the destination */
while (size-- > 0)
*dstp++ = *src++;
*dstp = '\0';
return (void*)dst;
}
/* xlc_aud_do -- interface to C routine aud_do */
/**/
LVAL xlc_aud_do(void)
{
// Based on string-trim...
unsigned char *leftp,*rightp;
LVAL src,dst;
/* get the string */
src = xlgastring();
xllastarg();
/* setup the string pointers */
leftp = getstring(src);
rightp = leftp + getslength(src) - 2;
// Go call my real function here...
dst = (LVAL)ExecForLisp( leftp );
/* return the new string */
return (dst);
}

View File

@ -1,3 +1,4 @@
/* include actual local file headers: */ /* include actual local file headers: */
#include "sndfnintdefs.h" #include "sndfnintdefs.h"
#include "seqfnintdefs.h" #include "seqfnintdefs.h"
#include "audacityfnintdefs.h"

View File

@ -2,8 +2,11 @@
* *
* CHANGE LOG * CHANGE LOG
* 28-Apr-03 rbd Removed "include switches.h" -- already included * 28-Apr-03 rbd Removed "include switches.h" -- already included
* 12-Feb-18 jkc Added "include audacityfnintptrs.h" -- for internal Audacity commands
*/ */
/* extension to xlisp */ /* extension to xlisp */
#include "sndfnintptrs.h" #include "sndfnintptrs.h"
#include "seqfnintptrs.h" #include "seqfnintptrs.h"
#include "audacityfnintptrs.h"

View File

@ -1435,7 +1435,7 @@ const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, Effect
plug.SetProviderID(PluginManager::GetID(provider)); plug.SetProviderID(PluginManager::GetID(provider));
plug.SetEffectType(effect->GetType()); plug.SetEffectType(effect->GetClassification());
plug.SetEffectFamilyId(effect->GetFamilyId().Internal()); plug.SetEffectFamilyId(effect->GetFamilyId().Internal());
plug.SetEffectInteractive(effect->IsInteractive()); plug.SetEffectInteractive(effect->IsInteractive());
plug.SetEffectDefault(effect->IsDefault()); plug.SetEffectDefault(effect->IsDefault());

View File

@ -97,6 +97,72 @@ int ExecCommand(wxString *pIn, wxString *pOut)
return 0; return 0;
} }
/// This is the function which actually obeys one command. Rather than applying
/// the command directly, an event containing a reference to the command is sent
/// to the main (GUI) thread. This is because having more than one thread access
/// the GUI at a time causes problems with wxwidgets.
int ExecCommand2(wxString *pIn, wxString *pOut)
{
{
CommandBuilder builder(*pIn);
if (builder.WasValid())
{
AudacityProject *project = GetActiveProject();
OldStyleCommandPointer cmd = builder.GetCommand();
AppCommandEvent ev;
ev.SetCommand(cmd);
AudacityApp & App = wxGetApp();
App.OnReceiveCommand(ev);
*pOut = wxEmptyString;
}
else
{
*pOut = wxT("Syntax error!\n");
*pOut += builder.GetErrorMessage() + wxT("\n");
}
}
// Wait until all responses from the command have been received.
// The last response is signalled by an empty line.
wxString msg = ScriptCommandRelay::ReceiveResponse().GetMessage();
while (msg != wxT("\n"))
{
//wxLogDebug( "Msg: %s", msg );
*pOut += msg + wxT("\n");
msg = ScriptCommandRelay::ReceiveResponse().GetMessage();
}
return 0;
}
#ifdef __cplusplus
extern "C" {
// The void * return is actually a Lisp LVAL and will be cast to such as needed.
extern void * ExecForLisp( char * pIn );
extern void * nyq_make_opaque_string( int size, unsigned char *src );
};
#endif
void * ExecForLisp( char * pIn ){
wxString Str1( pIn );
wxString Str2;
ExecCommand2( &Str1, &Str2 );
// wxString provides a const char *
const char * pStr = static_cast<const char*>(Str2);
// We'll be passing it as a non-const unsigned char *
// That 'unsafe' cast is actually safe. nyq_make_opaque_string is just copying the string.
void * pResult = nyq_make_opaque_string( Str2.Length(), (unsigned char *)pStr );
return pResult;
};
/// Adds a response to the queue to be sent back to the script /// Adds a response to the queue to be sent back to the script
void ScriptCommandRelay::SendResponse(const wxString &response) void ScriptCommandRelay::SendResponse(const wxString &response)
{ {

View File

@ -207,6 +207,10 @@ void SelectTracksCommand::PopulateOrExchange(ShuttleGui & S)
bool SelectTracksCommand::Apply(const CommandContext &context) bool SelectTracksCommand::Apply(const CommandContext &context)
{ {
// Count selection as a do-nothing effect.
// Used to invalidate cached selection and tracks.
Effect::IncEffectCounter();
int index = 0; int index = 0;
TrackList *tracks = context.GetProject()->GetTracks(); TrackList *tracks = context.GetProject()->GetTracks();

View File

@ -70,6 +70,9 @@ greater use in future.
#include <unordered_map> #include <unordered_map>
// Effect application counter
int Effect::nEffectsDone=0;
static const int kDummyID = 20000; static const int kDummyID = 20000;
static const int kSaveAsID = 20001; static const int kSaveAsID = 20001;
static const int kImportID = 20002; static const int kImportID = 20002;
@ -1074,8 +1077,9 @@ bool Effect::SetAutomationParameters(const wxString & parms)
preset preset
) )
); );
// We are using defualt settings and we still wish to continue.
return false; return true;
//return false;
} }
if (!mUIDialog) if (!mUIDialog)
@ -1246,7 +1250,7 @@ bool Effect::DoEffect(wxWindow *parent,
returnVal = Process(); returnVal = Process();
} }
if (returnVal) if (returnVal && (mT1 >= mT0 ))
{ {
selectedRegion->setTimes(mT0, mT1); selectedRegion->setTimes(mT0, mT1);
} }
@ -2246,6 +2250,7 @@ void Effect::ReplaceProcessedTracks(const bool bGoodResult)
// The output list is no longer needed // The output list is no longer needed
mOutputTracks.reset(); mOutputTracks.reset();
mOutputTracksType = Track::None; mOutputTracksType = Track::None;
nEffectsDone++;
} }
void Effect::CountWaveTracks() void Effect::CountWaveTracks()
@ -3245,7 +3250,11 @@ void EffectUIHost::OnApply(wxCommandEvent & evt)
} }
// Honor the "select all if none" preference...a little hackish, but whatcha gonna do... // Honor the "select all if none" preference...a little hackish, but whatcha gonna do...
if (!mIsBatch && mEffect && mEffect->GetType() != EffectTypeGenerate && mProject->mViewInfo.selectedRegion.isPoint()) if (!mIsBatch &&
mEffect &&
mEffect->GetType() != EffectTypeGenerate &&
mEffect->GetType() != EffectTypeTool &&
mProject->mViewInfo.selectedRegion.isPoint())
{ {
auto flags = AlwaysEnabledFlag; auto flags = AlwaysEnabledFlag;
bool allowed = mProject->ReportIfActionNotAllowed( bool allowed = mProject->ReportIfActionNotAllowed(

View File

@ -270,6 +270,8 @@ class AUDACITY_DLL_API Effect /* not final */ : public wxEvtHandler,
long style = DefaultMessageBoxStyle, long style = DefaultMessageBoxStyle,
const wxString& titleStr = wxString{}); const wxString& titleStr = wxString{});
static void IncEffectCounter(){ nEffectsDone++;};
// //
// protected virtual methods // protected virtual methods
// //
@ -341,7 +343,6 @@ protected:
bool TrackGroupProgress(int whichGroup, double frac, const wxString & = wxEmptyString); bool TrackGroupProgress(int whichGroup, double frac, const wxString & = wxEmptyString);
int GetNumWaveTracks() { return mNumTracks; } int GetNumWaveTracks() { return mNumTracks; }
int GetNumWaveGroups() { return mNumGroups; } int GetNumWaveGroups() { return mNumGroups; }
// Calculates the start time and selection length in samples // Calculates the start time and selection length in samples
@ -370,6 +371,9 @@ protected:
void CopyInputTracks(); // trackType = Track::Wave void CopyInputTracks(); // trackType = Track::Wave
void CopyInputTracks(int trackType); void CopyInputTracks(int trackType);
// A global counter of all the successful Effect invocations.
static int nEffectsDone;
// For the use of analyzers, which don't need to make output wave tracks, // For the use of analyzers, which don't need to make output wave tracks,
// but may need to add label tracks. // but may need to add label tracks.
class AddedAnalysisTrack { class AddedAnalysisTrack {

View File

@ -75,6 +75,8 @@ effects from this one class.
#include "../../Experimental.h" #include "../../Experimental.h"
int NyquistEffect::mReentryCount = 0;
enum enum
{ {
ID_Editor = 10000, ID_Editor = 10000,
@ -147,6 +149,7 @@ NyquistEffect::NyquistEffect(const wxString &fName)
mStop = false; mStop = false;
mBreak = false; mBreak = false;
mCont = false; mCont = false;
mIsTool = false;
mMaxLen = NYQ_MAX_LEN; mMaxLen = NYQ_MAX_LEN;
@ -165,6 +168,7 @@ NyquistEffect::NyquistEffect(const wxString &fName)
if (fName == NYQUIST_TOOLS_PROMPT_ID) { if (fName == NYQUIST_TOOLS_PROMPT_ID) {
mName = XO("Nyquist Tools Prompt"); mName = XO("Nyquist Tools Prompt");
mType = EffectTypeTool; mType = EffectTypeTool;
mIsTool = true;
mPromptName = mName; mPromptName = mName;
mPromptType = mType; mPromptType = mType;
mOK = true; mOK = true;
@ -263,6 +267,13 @@ EffectType NyquistEffect::GetType()
return mType; return mType;
} }
EffectType NyquistEffect::GetClassification()
{
if (mIsTool)
return EffectTypeTool;
return mType;
}
IdentInterfaceSymbol NyquistEffect::GetFamilyId() IdentInterfaceSymbol NyquistEffect::GetFamilyId()
{ {
return NYQUISTEFFECTS_FAMILY; return NYQUISTEFFECTS_FAMILY;
@ -582,12 +593,25 @@ bool NyquistEffect::CheckWhetherSkipEffect()
static void RegisterFunctions(); static void RegisterFunctions();
bool NyquistEffect::Process() bool NyquistEffect::Process()
{ {
// Check for reentrant Nyquist commands.
// I'm choosing to mark skipped Nyquist commands as successful even though
// they are skipped. The reason is that when Nyquist calls out to a chain,
// and that chain contains Nyquist, it will be clearer if the chain completes
// skipping Nyquist, rather than doing nothing at all.
if( mReentryCount > 0 )
return true;
// Restore the reentry counter (to zero) when we exit.
auto cleanup = valueRestorer( mReentryCount);
mReentryCount++;
RegisterFunctions(); RegisterFunctions();
bool success = true; bool success = true;
int nEffectsSoFar = nEffectsDone;
mProjectChanged = false; mProjectChanged = false;
EffectManager & em = EffectManager::Get(); EffectManager & em = EffectManager::Get();
em.SetSkipStateFlag(false); em.SetSkipStateFlag(false);
@ -596,12 +620,7 @@ bool NyquistEffect::Process()
mProgress->Hide(); 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.get());
mCurTrack[0] = (WaveTrack *) iter.First();
mOutputTime = 0; mOutputTime = 0;
mCount = 0; mCount = 0;
mProgressIn = 0; mProgressIn = 0;
@ -752,52 +771,67 @@ bool NyquistEffect::Process()
mProps += wxString::Format(wxT("(putprop '*SELECTION* %d 'CHANNELS)\n"), mNumSelectedChannels); mProps += wxString::Format(wxT("(putprop '*SELECTION* %d 'CHANNELS)\n"), mNumSelectedChannels);
} }
// If in tool mode, then we don't do anything with the track and selection.
bool bOnePassTool = (GetType() == EffectTypeTool);
// 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
if( !bOnePassTool )
CopyInputTracks(Track::All);
SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks.get());
mCurTrack[0] = (WaveTrack *)iter.First();
// Keep track of whether the current track is first selected in its sync-lock group // 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 // (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). // to handle sync-lock group behavior the "old" way).
mFirstInGroup = true; mFirstInGroup = true;
Track *gtLast = NULL; Track *gtLast = NULL;
while (mCurTrack[0]) { while (mCurTrack[0] || bOnePassTool) {
mCurNumChannels = 1; mCurNumChannels = 1;
if (mT1 >= mT0) { if ((mT1 >= mT0)||bOnePassTool) {
if (mCurTrack[0]->GetLinked()) { if (bOnePassTool) {
mCurNumChannels = 2;
mCurTrack[1] = (WaveTrack *)iter.Next(); } else {
if (mCurTrack[1]->GetRate() != mCurTrack[0]->GetRate()) { if (mCurTrack[0]->GetLinked()) {
Effect::MessageBox(_("Sorry, cannot apply effect on stereo tracks where the tracks don't match."), mCurNumChannels = 2;
wxOK | wxCENTRE);
success = false; mCurTrack[1] = (WaveTrack *)iter.Next();
goto finish; if (mCurTrack[1]->GetRate() != mCurTrack[0]->GetRate()) {
Effect::MessageBox(_("Sorry, cannot apply effect on stereo tracks where the tracks don't match."),
wxOK | wxCENTRE);
success = false;
goto finish;
}
mCurStart[1] = mCurTrack[1]->TimeToLongSamples(mT0);
} }
mCurStart[1] = mCurTrack[1]->TimeToLongSamples(mT0);
// Check whether we're in the same group as the last selected track
SyncLockedTracksIterator gIter(mOutputTracks.get());
Track *gt = gIter.StartWith(mCurTrack[0]);
mFirstInGroup = !gtLast || (gtLast != gt);
gtLast = gt;
mCurStart[0] = mCurTrack[0]->TimeToLongSamples(mT0);
auto end = mCurTrack[0]->TimeToLongSamples(mT1);
mCurLen = end - mCurStart[0];
if (mCurLen > NYQ_MAX_LEN) {
float hours = (float)NYQ_MAX_LEN / (44100 * 60 * 60);
const auto message = wxString::Format(
_("Selection too long for Nyquist code.\nMaximum allowed selection is %ld samples\n(about %.1f hours at 44100 Hz sample rate)."),
(long)NYQ_MAX_LEN, hours
);
Effect::MessageBox(message, wxOK | wxCENTRE, _("Nyquist Error"));
if (!mProjectChanged)
em.SetSkipStateFlag(true);
return false;
}
mCurLen = std::min(mCurLen, mMaxLen);
} }
// Check whether we're in the same group as the last selected track
SyncLockedTracksIterator gIter(mOutputTracks.get());
Track *gt = gIter.StartWith(mCurTrack[0]);
mFirstInGroup = !gtLast || (gtLast != gt);
gtLast = gt;
mCurStart[0] = mCurTrack[0]->TimeToLongSamples(mT0);
auto end = mCurTrack[0]->TimeToLongSamples(mT1);
mCurLen = end - mCurStart[0];
if (mCurLen > NYQ_MAX_LEN) {
float hours = (float)NYQ_MAX_LEN / (44100 * 60 * 60);
const auto message = wxString::Format(
_("Selection too long for Nyquist code.\nMaximum allowed selection is %ld samples\n(about %.1f hours at 44100 Hz sample rate)."),
(long)NYQ_MAX_LEN, hours
);
Effect::MessageBox(message, wxOK | wxCENTRE, _("Nyquist Error"));
if (!mProjectChanged)
em.SetSkipStateFlag(true);
return false;
}
mCurLen = std::min(mCurLen, mMaxLen);
mProgressIn = 0.0; mProgressIn = 0.0;
mProgressOut = 0.0; mProgressOut = 0.0;
@ -866,7 +900,7 @@ _("Selection too long for Nyquist code.\nMaximum allowed selection is %ld sample
// Reset previous locale // Reset previous locale
wxSetlocale(LC_NUMERIC, prevlocale); wxSetlocale(LC_NUMERIC, prevlocale);
if (!success) { if (!success || bOnePassTool) {
goto finish; goto finish;
} }
mProgressTot += mProgressIn + mProgressOut; mProgressTot += mProgressIn + mProgressOut;
@ -894,7 +928,13 @@ finish:
dlog.ShowModal(); dlog.ShowModal();
} }
ReplaceProcessedTracks(success); // Has rug been pulled from under us by some effect done within Nyquist??
if( !bOnePassTool && ( nEffectsSoFar == nEffectsDone ))
ReplaceProcessedTracks(success);
else{
ReplaceProcessedTracks(false); // Do not use the results.
mT1 = mT0 - 1.0;// And don't use the times either, in resetting the selection (make them bogus).
}
if (!mProjectChanged) if (!mProjectChanged)
em.SetSkipStateFlag(true); em.SetSkipStateFlag(true);
@ -987,7 +1027,11 @@ bool NyquistEffect::ProcessOne()
wxString cmd; wxString cmd;
cmd += wxT("(snd-set-latency 0.1)"); cmd += wxT("(snd-set-latency 0.1)");
if (mVersion >= 4) { // A tool may be using AUD-DO which will potentially invalidate *TRACK*
// so tools do not get *TRACK*.
if (GetType() == EffectTypeTool)
; // No Track.
else if (mVersion >= 4) {
nyx_set_audio_name("*TRACK*"); nyx_set_audio_name("*TRACK*");
cmd += wxT("(setf S 0.25)\n"); cmd += wxT("(setf S 0.25)\n");
} }
@ -996,7 +1040,7 @@ bool NyquistEffect::ProcessOne()
cmd += wxT("(setf *TRACK* '*unbound*)\n"); cmd += wxT("(setf *TRACK* '*unbound*)\n");
} }
if (mVersion >= 4) { if( (mVersion >= 4) && (GetType() != EffectTypeTool) ) {
cmd += mProps; cmd += mProps;
cmd += mPerTrackProps; cmd += mPerTrackProps;
@ -1147,7 +1191,11 @@ bool NyquistEffect::ProcessOne()
cmd += wxString::Format(wxT("(putprop '*SELECTION* %s 'RMS)\n"), rmsString); cmd += wxString::Format(wxT("(putprop '*SELECTION* %s 'RMS)\n"), rmsString);
} }
if (GetType() == EffectTypeGenerate) { // If in tool mode, then we don't do anything with the track and selection.
if (GetType() == EffectTypeTool) {
nyx_set_audio_params(44100, 0);
}
else if (GetType() == EffectTypeGenerate) {
nyx_set_audio_params(mCurTrack[0]->GetRate(), 0); nyx_set_audio_params(mCurTrack[0]->GetRate(), 0);
} }
else { else {
@ -1308,7 +1356,7 @@ bool NyquistEffect::ProcessOne()
// True if not process type. // True if not process type.
// If not returning audio from process effect, // If not returning audio from process effect,
// return first reult then stop (disables preview) // return first result then stop (disables preview)
// but allow all output from Nyquist Prompt. // but allow all output from Nyquist Prompt.
return (GetType() != EffectTypeProcess || mIsPrompt); return (GetType() != EffectTypeProcess || mIsPrompt);
} }
@ -1728,18 +1776,32 @@ bool NyquistEffect::Parse(
} }
if (len >= 2 && tokens[0] == wxT("type")) { if (len >= 2 && tokens[0] == wxT("type")) {
if (tokens[1] == wxT("process")) { wxString tok = tokens[1];
mIsTool = false;
if (tok == wxT("tool")) {
mIsTool = true;
mType = EffectTypeTool;
// we allow
// ;type tool
// ;type tool process
// ;type tool generate
// ;type tool analyze
// The last three are placed in the tool menu, but are processed as
// process, generate or analyze.
if (len >= 3)
tok = tokens[2];
}
if (tok == wxT("process")) {
mType = EffectTypeProcess; mType = EffectTypeProcess;
} }
else if (tokens[1] == wxT("generate")) { else if (tok == wxT("generate")) {
mType = EffectTypeGenerate; mType = EffectTypeGenerate;
} }
else if (tokens[1] == wxT("analyze")) { else if (tok == wxT("analyze")) {
mType = EffectTypeAnalyze; mType = EffectTypeAnalyze;
} }
else if (tokens[1] == wxT("tool")) {
mType = EffectTypeTool;
}
if (len >= 3 && tokens[2] == wxT("spectral")) {; if (len >= 3 && tokens[2] == wxT("spectral")) {;
mIsSpectral = true; mIsSpectral = true;
} }

View File

@ -99,6 +99,7 @@ public:
// EffectDefinitionInterface implementation // EffectDefinitionInterface implementation
EffectType GetType() override; EffectType GetType() override;
EffectType GetClassification() override;
IdentInterfaceSymbol GetFamilyId() override; IdentInterfaceSymbol GetFamilyId() override;
bool IsInteractive() override; bool IsInteractive() override;
bool IsDefault() override; bool IsDefault() override;
@ -120,7 +121,6 @@ public:
bool TransferDataFromWindow() override; bool TransferDataFromWindow() override;
// NyquistEffect implementation // NyquistEffect implementation
// For Nyquist Workbench support // For Nyquist Workbench support
void RedirectOutput(); void RedirectOutput();
void SetCommand(const wxString &cmd); void SetCommand(const wxString &cmd);
@ -129,6 +129,7 @@ public:
void Stop(); void Stop();
private: private:
static int mReentryCount;
// NyquistEffect implementation // NyquistEffect implementation
bool ProcessOne(); bool ProcessOne();
@ -218,6 +219,7 @@ private:
bool mIsSal; bool mIsSal;
bool mExternal; bool mExternal;
bool mIsSpectral; bool mIsSpectral;
bool mIsTool;
/** True if the code to execute is obtained interactively from the user via /** True if the code to execute is obtained interactively from the user via
* the "Nyquist Effect Prompt", or "Nyquist Tools Prompt", false for all other effects (lisp code read from * the "Nyquist Effect Prompt", or "Nyquist Tools Prompt", false for all other effects (lisp code read from
* files) * files)
@ -241,7 +243,7 @@ private:
wxString mHelpFile; wxString mHelpFile;
bool mHelpFileExists; bool mHelpFileExists;
EffectType mType; EffectType mType;
EffectType mPromptType; // If a prompt, need ot remember original type. EffectType mPromptType; // If a prompt, need to remember original type.
bool mEnablePreview; bool mEnablePreview;
bool mDebugButton; // Set to false to disable Debug button. bool mDebugButton; // Set to false to disable Debug button.