Beispiel #1
0
EffectNyquist::EffectNyquist(wxString fName)
{
   mAction = _("Applying Nyquist Effect...");
   mInputCmd = wxEmptyString;
   mCmd = wxEmptyString;
   SetEffectFlags(HIDDEN_EFFECT);
   mInteractive = false;
   mExternal = false;
   mCompiler = false;
   mDebug = false;
   mIsSal = false;
   mOK = false;

   mStop = false;
   mBreak = false;
   mCont = false;

   if (fName == wxT("")) {
      // Interactive Nyquist
      mOK = true;
      mInteractive = true;
      mName = _("Nyquist Prompt...");
      SetEffectFlags(PROCESS_EFFECT | BUILTIN_EFFECT | ADVANCED_EFFECT);
      return;
   }

   // wxLogNull dontLog; // Vaughan, 2010-08-27: Why turn off logging? Logging is good!

   mName = wxFileName(fName).GetName();
   mFileName = wxFileName(fName);
   mFileModified = mFileName.GetModificationTime();
   ParseFile();
}
Beispiel #2
0
void EffectNyquist::ParseFile()
{
   wxTextFile f(mFileName.GetFullPath());
   if (!f.Open())
      return;

   mCmd = wxT("");
   SetEffectFlags(PROCESS_EFFECT | PLUGIN_EFFECT);
   mOK = false;
   mIsSal = false;
   mControls.Clear();
   mDebug = false;

   int i;
   int len = f.GetLineCount();
   wxString line;
   for (i = 0; i < len; i++) {
      line = f[i];
      if (line.Length() > 1 && line[0] == wxT(';')) {
         Parse(line);
      }
      // preserve comments so that SAL effects compile with proper line numbers
      mCmd += line + wxT("\n");
   }
}
Beispiel #3
0
VSTEffect::VSTEffect(const wxString & path)
:  mPath(path)
{
   mModule = NULL;
   mAEffect = NULL;
   mInBuffer = NULL;
   mOutBuffer = NULL;
   mInputs = 0;
   mOutputs = 0;
   mChannels = 0;
   mBlockSize = 0;

   memset(&mTimeInfo, 0, sizeof(mTimeInfo));
   mTimeInfo.samplePos = 0.0;
   mTimeInfo.sampleRate = 44100.0;
   mTimeInfo.nanoSeconds = wxGetLocalTimeMillis().ToDouble();
   mTimeInfo.tempo = 120.0;
   mTimeInfo.timeSigNumerator = 4;
   mTimeInfo.timeSigDenominator = 4;
   mTimeInfo.flags = kVstTempoValid | kVstNanosValid;

   PluginManager & pm = PluginManager::Get();

   if (pm.IsRegistered(VSTPLUGINTYPE, mPath)) {
      mName = pm.Read(wxT("Name"), wxEmptyString);
      mVendor = pm.Read(wxT("Vendor"), wxEmptyString);
      mInputs = pm.Read(wxT("Inputs"), 0L);
      mOutputs = pm.Read(wxT("Outputs"), 0L);
   }
   else if (Load()) {
      pm.RegisterPlugin(VSTPLUGINTYPE, mPath);
      pm.Write(wxT("Name"), mName);
      pm.Write(wxT("Vendor"), mVendor);
      pm.Write(wxT("Inputs"), mInputs);
      pm.Write(wxT("Outputs"), mOutputs);
   }

   if (mVendor.IsEmpty()) {
      mVendor = VSTPLUGINTYPE;
   }

   if (mName.IsEmpty()) {
      wxFileName fn(mPath);
      mName = fn.GetName();
   }

   int flags = PLUGIN_EFFECT;
   if (mInputs == 0) {
      flags |= INSERT_EFFECT;
   }
   else if (mOutputs == 0) {
      flags |= ANALYZE_EFFECT;
   }
   else {
      flags |= PROCESS_EFFECT;
   }

   SetEffectFlags(flags);
}
Beispiel #4
0
EffectToneGen::EffectToneGen()
{
   SetEffectFlags(BUILTIN_EFFECT | INSERT_EFFECT);
   mbChirp = false;
   mbLogInterpolation = false;
   waveform = 0;                //sine
   frequency[0] = float(440.0);          //Hz
   frequency[1] = float(1320.0);          //Hz
   amplitude[0] = float(0.8);
   amplitude[1] = float(0.1);
   interpolation = 0;
}
Beispiel #5
0
VampEffect::VampEffect(Vamp::HostExt::PluginLoader::PluginKey key,
                       int output,
                       bool hasParameters,
                       wxString name,
                       wxString category) :
    mKey(key),
    mOutput(output),
    mHasParameters(hasParameters),
    mName(name),
    mRate(0),
    mCategory(category),
    mPlugin(NULL)
{
    SetEffectFlags(PLUGIN_EFFECT | ANALYZE_EFFECT);
}
Beispiel #6
0
VampEffect::VampEffect(Vamp::HostExt::PluginLoader::PluginKey key,
                       int output,
                       bool hasParameters,
                       wxString name,
                       wxString category) :
   mKey(key),
   mOutput(output),
   mHasParameters(hasParameters),
   mName(name),
   mRate(0),
   mCategory(category),
   mPlugin(NULL)
{
   Vamp::HostExt::PluginLoader *loader = Vamp::HostExt::PluginLoader::getInstance();
   mPlugin = loader->loadPlugin(mKey, 48000); // rate doesn't matter here

   SetEffectFlags(PLUGIN_EFFECT | ANALYZE_EFFECT);
}
EffectAutoDuck::EffectAutoDuck()
{
   SetEffectFlags(BUILTIN_EFFECT | PROCESS_EFFECT | ADVANCED_EFFECT);
   
   gPrefs->Read(wxT("/Effects/AutoDuck/DuckAmountDb"),
      &mDuckAmountDb, PARAM_DEFAULT_DUCK_AMOUNT_DB);
   gPrefs->Read(wxT("/Effects/AutoDuck/InnerFadeDownLen"),
      &mInnerFadeDownLen, PARAM_DEFAULT_INNER_FADE_DOWN_LEN);
   gPrefs->Read(wxT("/Effects/AutoDuck/InnerFadeUpLen"),
      &mInnerFadeUpLen, PARAM_DEFAULT_INNER_FADE_UP_LEN);
   gPrefs->Read(wxT("/Effects/AutoDuck/OuterFadeDownLen"),
      &mOuterFadeDownLen, PARAM_DEFAULT_OUTER_FADE_DOWN_LEN);
   gPrefs->Read(wxT("/Effects/AutoDuck/OuterFadeUpLen"),
      &mOuterFadeUpLen, PARAM_DEFAULT_OUTER_FADE_UP_LEN);
   gPrefs->Read(wxT("/Effects/AutoDuck/ThresholdDb"),
      &mThresholdDb, PARAM_DEFAULT_THRESHOLD_DB);
   gPrefs->Read(wxT("/Effects/AutoDuck/MaximumPause"),
      &mMaximumPause, PARAM_DEFAULT_MAXIMUM_PAUSE);

   mControlTrack = NULL;
}
Beispiel #8
0
void EffectNyquist::SetCommand(wxString cmd)
{
   mExternal = true;
   mInteractive = false;
   mCmd = wxT("");
   SetEffectFlags(INSERT_EFFECT | HIDDEN_EFFECT);
   mOK = false;
   mIsSal = false;
   mControls.Clear();

   wxStringTokenizer lines(cmd, wxT("\n"));
   while (lines.HasMoreTokens()) {
      wxString line = lines.GetNextToken();
      
      if (line.Length() > 1 && line[0] == wxT(';')) {
         Parse(line);
      }
      else {
         mCmd += line + wxT("\n");
      }
   }
}
Beispiel #9
0
LV2Effect::LV2Effect(const LilvPlugin *data,
                     const std::set<wxString> & categories)
:  mValid(true),
   mCategories(categories),
   mMidiInput(0),
   mLatencyPortIndex(-1)
{
   
   // We don't support any features at all, so if the plugin requires
   // any we skip it.
   LilvNodes *req = lilv_plugin_get_required_features(data);
   size_t nFeatures = lilv_nodes_size(req);
   lilv_nodes_free(req);
   if (nFeatures > 0)
   {
      mValid = false;
      return;
   }

   mData = data;
   pluginName = GetString(lilv_plugin_get_name(mData), true);

   fInBuffer = NULL;
   fOutBuffer = NULL;
   
   mLength = 0;

   // Allocate buffers for the port indices and the default control values
   int numPorts = lilv_plugin_get_num_ports(mData);
   float *minimumValues = new float [numPorts];
   float *maximumValues = new float [numPorts];
   float *defaultValues = new float [numPorts];

   // Retrieve the port ranges for all ports (some values may be NaN)
   lilv_plugin_get_port_ranges_float(mData, minimumValues, 
                                     maximumValues, defaultValues);

   // Get info about all ports
   for (int i = 0; i < numPorts; i++)
   {
      const LilvPort *port = lilv_plugin_get_port_by_index(mData, i);
      LV2Port internalPort;
      internalPort.mIndex = lilv_port_get_index(mData, port);

      // Get the port name
      LilvNode *tmpName = lilv_port_get_name(mData, port);
      internalPort.mName = GetString(tmpName);
      lilv_node_free(tmpName);

      // Get the scale points
      LilvScalePoints* points = lilv_port_get_scale_points(mData, port);
      LILV_FOREACH(scale_points, j, points)
      {
         const LilvScalePoint *point = lilv_scale_points_get(points, j);

         internalPort.mScaleValues.Add(lilv_node_as_float(lilv_scale_point_get_value(point)));
         internalPort.mScaleLabels.Add(GetString(lilv_scale_point_get_label(point)));
      }
      lilv_scale_points_free(points);

      // Get the groups
      LilvNodes *groups = lilv_port_get_value(mData, port, gPortGroup);
      if (groups)
      {
         LilvNode *group = lilv_nodes_get_first(groups);
         wxString uri = GetString(group);

         wxString label;
         const LilvNode *name = lilv_world_get(gWorld, group, gName, NULL);
         if (name)
         {
            label = GetString(name);
         }
         else
         {
            // Shouldn't happen, but provide something
            label = uri;
         }
         lilv_nodes_free(groups);

         // Check for new group
         if (mPortGroups.find(uri) == mPortGroups.end())
         {
            mPortGroups[uri] = LV2PortGroup(label);
         }
#if 0
         // Get subgroup
         //
         // LLL:  This isn't right...must find or construct a plugin with
         //       subgroups.
         LilvNodes *subgroup = lilv_node_get_value(mData, port, gSubGroupOf);
         if (subgroups)
         {
            LilvNode *subgroup = lilv_nodes_get_first(subgroups);
            wxString uri = GetString(subgroup);
            const LilvNode *subgroup = lilv_world_get(gWorld, group, gSubGroupOf, NULL);
            wxString label = GetString(name);
            lilv_nodes_free(subgroup);
         }
         else
#endif
         {
            mRootGroup.AddSubGroup(mPortGroups[uri]);
         }
         mPortGroups[uri].AddParameter(i);

      }
      else
      {
         mRootGroup.AddParameter(i);
      }

      // Get the port type
      if (lilv_port_is_a(mData, port, gAudioPortClass))
      {
         if (lilv_port_is_a(mData, port, gInputPortClass))
         {
            mAudioInputs.Add(internalPort);
         }
         else if (lilv_port_is_a(mData, port, gOutputPortClass))
         {
            mAudioOutputs.Add(internalPort);
         }
      }
      else if (lilv_port_is_a(mData, port, gControlPortClass) &&
               lilv_port_is_a(mData, port, gInputPortClass))
      {
         internalPort.mControlBuffer = float(1.0);
         internalPort.mMin = minimumValues[i];
         internalPort.mMax = maximumValues[i];
         internalPort.mDefault = defaultValues[i];
         if (isfinite(defaultValues[i]))
         {
            internalPort.mControlBuffer = defaultValues[i];
         }
         else if (isfinite(minimumValues[i]))
         {
            internalPort.mControlBuffer = minimumValues[i];
         }
         else if (isfinite(maximumValues[i]))
         {
            internalPort.mControlBuffer = maximumValues[i];
         }

         if (lilv_port_has_property(mData, port, gPortToggled))
         {
            internalPort.mToggle = true;
         }
         else if (lilv_port_has_property(mData, port, gPortIsInteger))
         {
            internalPort.mInteger = true;
         }
         else if (lilv_port_has_property(mData, port, gPortIsSampleRate))
         {
            internalPort.mSampleRate = true;
         }
         else if (lilv_port_has_property(mData, port, gPortIsEnumeration))
         {
            internalPort.mEnumeration = true;
         }

         mControlInputs.Add(internalPort);
      }
      else if (lilv_port_is_a(mData, port, gControlPortClass) &&
               lilv_port_is_a(mData, port, gOutputPortClass))
      {
         // If there is more than one latency port, the plugin is invalid
         if (lilv_port_has_property(mData, port, gPortIsLatency))
         {
            if (mLatencyPortIndex >= 0)
            {
               mValid = false;
               continue;
            }
            mLatencyPortIndex = i;
         }
         else if (!lilv_port_has_property(mData, port, gPortIsOptional))
         {
            mControlOutputs.Add(internalPort);
         }
      }
      else if (lilv_port_is_a(mData, port, gMidiPortClass) &&
               lilv_port_is_a(mData, port, gInputPortClass))
      {
         // If there is more than one MIDI input port, the plugin is invalid
         if (mMidiInput)
         {
            mValid = false;
            continue;
         }
         mMidiInput = new LV2Port(internalPort);
      }
      else
      {
         // Unknown port type, we set the invalid flag
 //        mValid = false;
      }
   }
   
   delete [] minimumValues;
   delete [] maximumValues;
   delete [] defaultValues;
   
   // MIDI synths may not have any audio inputs.
   if (mMidiInput && mAudioInputs.GetCount() > 0)
   {
      mValid = false;
   }
   
   // Determine whether the plugin is a generator, effect or analyser 
   // depending on the number of ports of each type (not completely accurate,
   // but works most of the time)
   int flags = PLUGIN_EFFECT;
   if (mAudioInputs.GetCount() == 0)
   {
      flags |= INSERT_EFFECT;
   }
   else if (mAudioOutputs.GetCount() == 0)
   {
      flags |= ANALYZE_EFFECT;
   }
   else
   {
      flags |= PROCESS_EFFECT;
   }

   SetEffectFlags(flags);
}
Beispiel #10
0
LadspaEffect::LadspaEffect(const LADSPA_Descriptor *data,
                           const std::set<wxString>& categories)
   : mCategories(categories) {
   
   mData = data;
   pluginName = LAT1CTOWX(mData->Name);

   fInBuffer = NULL;
   fOutBuffer = NULL;

   inputs = 0;
   outputs = 0;
   numInputControls = 0;
   mLength = 0;

   unsigned long p;

   inputPorts = new unsigned long [mData->PortCount];
   outputPorts = new unsigned long [mData->PortCount];
   inputControls = new float [mData->PortCount];
   outputControls = new float [mData->PortCount];

   for(p=0; p<mData->PortCount; p++) {
      LADSPA_PortDescriptor d = mData->PortDescriptors[p];
      if (LADSPA_IS_PORT_AUDIO(d)) {
         if (LADSPA_IS_PORT_INPUT(d)) {
            inputPorts[inputs] = p;
            inputs++;
         }
         else if (LADSPA_IS_PORT_OUTPUT(d)) {
            outputPorts[outputs] = p;
            outputs++;
         }
      }
      if (LADSPA_IS_PORT_CONTROL(d) &&
          LADSPA_IS_PORT_INPUT(d)) {
         numInputControls++;

         float val = float(1.0);
         LADSPA_PortRangeHint hint = mData->PortRangeHints[p];

         if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor) &&
             val < hint.LowerBound)
            val = hint.LowerBound;

         if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor) &&
             val > hint.UpperBound)
            val = hint.UpperBound;

         if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hint.HintDescriptor))
            val = hint.LowerBound;

         if (LADSPA_IS_HINT_DEFAULT_LOW(hint.HintDescriptor))
            val = hint.LowerBound * 0.75f + hint.UpperBound * 0.25f;

         if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hint.HintDescriptor))
            val = hint.LowerBound * 0.5f + hint.UpperBound * 0.5f;

         if (LADSPA_IS_HINT_DEFAULT_HIGH(hint.HintDescriptor))
            val = hint.LowerBound * 0.25f + hint.UpperBound * 0.75f;

         if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hint.HintDescriptor))
            val = hint.UpperBound;

         if (LADSPA_IS_HINT_SAMPLE_RATE(hint.HintDescriptor))
            val *= mProjectRate;

         if (LADSPA_IS_HINT_DEFAULT_0(hint.HintDescriptor))
            val = 0.0f;

         if (LADSPA_IS_HINT_DEFAULT_1(hint.HintDescriptor))
            val = 1.0f;

         if (LADSPA_IS_HINT_DEFAULT_100(hint.HintDescriptor))
            val = 100.0f;

         if (LADSPA_IS_HINT_DEFAULT_440(hint.HintDescriptor))
            val = 440.0f;

         inputControls[p] = val;
      }
   }

   int flags = PLUGIN_EFFECT;
   if (inputs == 0)
      flags |= INSERT_EFFECT;
   else if (outputs == 0)
      flags |= ANALYZE_EFFECT;
   else
      flags |= PROCESS_EFFECT;   

   SetEffectFlags(flags);
}
Beispiel #11
0
EffectFindClipping::EffectFindClipping()
{
   SetEffectFlags(BUILTIN_EFFECT | ANALYZE_EFFECT);
   mStart = 3;
   mStop = 3;
}
Beispiel #12
0
LV2Effect::LV2Effect(SLV2Plugin data,
                     const std::set<wxString>& categories)
   : mValid(true),
     mCategories(categories),
     mMidiInput(0),
     mScalePointsRetrieved(false),
     mPortGroupsRetrieved(false) {
   
   // We don't support any features at all, so if the plugin requires
   // any we skip it.
   SLV2Values req = slv2_plugin_get_required_features(data);
   size_t nFeatures = slv2_values_size(req);
   slv2_values_free(req);
   if (nFeatures > 0) {
      mValid = false;
      return;
   }

   mData = data;
   pluginName = 
      wxString::FromUTF8(slv2_value_as_string(slv2_plugin_get_name(mData)));
   
   fInBuffer = NULL;
   fOutBuffer = NULL;
   
   mLength = 0;

   uint32_t p;
   
   // Allocate buffers for the port indices and the default control values
   uint32_t numPorts = slv2_plugin_get_num_ports(mData);
   float* minimumValues = new float [numPorts];
   float* maximumValues = new float [numPorts];
   float* defaultValues = new float [numPorts];
   
   // Retrieve the port ranges for all ports (some values may be NaN)
   slv2_plugin_get_port_ranges_float(mData, minimumValues, 
                                     maximumValues, defaultValues);
   
   // Get info about all ports
   for(p = 0; p < numPorts; p++) {
      SLV2Port port = slv2_plugin_get_port_by_index(mData, p);
      LV2Port internalPort;
      internalPort.mIndex = p;

      // Get the port name
      SLV2Value tmpName = slv2_port_get_name(data, port);
      internalPort.mName = LAT1CTOWX(slv2_value_as_string(tmpName));
      slv2_value_free(tmpName);
      
      // Get the port type
      if (slv2_port_is_a(mData, port, gAudioPortClass)) {
         if (slv2_port_is_a(mData, port, gInputPortClass))
            mAudioInputs.push_back(internalPort);
         else if (slv2_port_is_a(mData, port, gOutputPortClass))
            mAudioOutputs.push_back(internalPort);
      }

      else if (slv2_port_is_a(mData, port, gControlPortClass) &&
          slv2_port_is_a(mData, port, gInputPortClass)) {
         internalPort.mControlBuffer = float(1.0);
         internalPort.mMin = minimumValues[p];
         internalPort.mMax = maximumValues[p];
         internalPort.mDefault = defaultValues[p];
         if (std::isfinite(defaultValues[p]))
            internalPort.mControlBuffer = defaultValues[p];
         else if (std::isfinite(minimumValues[p]))
            internalPort.mControlBuffer = minimumValues[p];
         else if (std::isfinite(maximumValues[p]))
            internalPort.mControlBuffer = maximumValues[p];
         if (slv2_port_has_property(data, port, gPortToggled))
            internalPort.mToggle = true;
         if (slv2_port_has_property(data, port, gPortIsInteger))
            internalPort.mInteger = true;
         if (slv2_port_has_property(data, port, gPortIsSampleRate))
            internalPort.mSampleRate = true;

         mControlInputs.push_back(internalPort);
      }
      
      else if (slv2_port_is_a(mData, port, gMidiPortClass) &&
               slv2_port_is_a(mData, port, gInputPortClass)) {
         // If there are more than one MIDI input ports, the plugin is invalid
         if (mMidiInput) {
            mValid = false;
            continue;
         }
         mMidiInput = new LV2Port(internalPort);
      }
      
      else {
         // Unknown port type, we set the invalid flag
         mValid = false;
      }
   }
   
   delete [] minimumValues;
   delete [] maximumValues;
   delete [] defaultValues;
   
   // MIDI synths may not have any audio inputs.
   if (mMidiInput && mAudioInputs.size() > 0)
      mValid = false;
   
   // Determine whether the plugin is a generator, effect or analyser 
   // depending on the number of ports of each type (not completely accurate,
   // but works most of the time)
   int flags = PLUGIN_EFFECT;
   if (mAudioInputs.size() == 0)
      flags |= INSERT_EFFECT;
   else if (mAudioOutputs.size() == 0)
      flags |= ANALYZE_EFFECT;
   else
      flags |= PROCESS_EFFECT;

   SetEffectFlags(flags);
}
Beispiel #13
0
void EffectNyquist::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;
   }

   if (len == 2 && tokens[0] == wxT("nyquist") && tokens[1] == wxT("plug-in")) {
      mOK = true;
      return;
   }

   if (len >= 2 && tokens[0] == wxT("type")) {
      if (tokens[1] == wxT("process")) {
         SetEffectFlags(PROCESS_EFFECT | PLUGIN_EFFECT);
      }
      else if (tokens[1] == wxT("generate")) {
         SetEffectFlags(INSERT_EFFECT | PLUGIN_EFFECT);
      }
      else if (tokens[1] == wxT("analyze")) {
         SetEffectFlags(ANALYZE_EFFECT | PLUGIN_EFFECT);
      }
      return;
   }

   if (len == 2 && tokens[0] == wxT("codetype")) {
      if (tokens[1] == wxT("lisp")) {
         mIsSal = false;
      }
      else if (tokens[1] == wxT("sal")) {
         mIsSal = 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.)
   if (len >= 2 && tokens[0] == wxT("version")) {
      if (tokens[1] != wxT("1") && tokens[1] != wxT("2") && tokens[1] != wxT("3")) {
         // This is an unsupported plug-in version
         mOK = false;
         return;
      }
   }

   if (len >= 2 && tokens[0] == wxT("name")) {
      mName = UnQuote(tokens[1]);
      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 >= 6 && tokens[0] == wxT("control")) {
      NyqControl ctrl;
  
      ctrl.var = tokens[1];
      ctrl.name = tokens[2];
      ctrl.label = tokens[4];
      ctrl.valStr = tokens[5];
 
      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.highStr = tokens[7];
      }

      ctrl.val = UNINITIALIZED_CONTROL;

      mControls.Add(ctrl);
   }
   
   if (len >= 2 && tokens[0] == wxT("categories")) {
      for (size_t i = 1; i < tokens.GetCount(); ++i) {
         mCategories.Add(tokens[i]);
      }
   }
}