Support for Nyquist effects in Chains

How feasible would it be to add one Nyquist effect to the Chain menu?

What I’m thinking of is for there to be a “dummy” Nyquist effect (possibly called “chain.ny”) that can be added as an effect in a batch processing chain. If this could be done (preferably with the facility to pass parameters to the effect, though that would be an optional bonus feature), then “chain.ny” could be manually edited to include arbitrary Nyquist code for use in a processing chain.

An alternative approach might be to add the “Nyquist Prompt” to Chains, in which the Nyquist Prompt text can be added as the “Parameters”.

Sorry Steve, this is way out of my league! I will take a quick look at the code but don’t have much time now that summer gig season is in full swing.

You mean something like this:
NyquistChain1.png
NyquistChain2.png
NyquistChain3.png
I can get this far with a few lines of code but do not really know where to go from here! Can you write me a tiny Nyquist effect which has no GUI and no params --it could just add 5 secs of silence to the start of the track; then a different effect which takes a param named “amount” which has no GUI but needs a single param (number of seconds) “amount” which adds that to the front of the track? This would give me something with which to test.

here is the patch so far…

Index: src/BatchCommands.cpp
===================================================================
--- src/BatchCommands.cpp	(revision 11173)
+++ src/BatchCommands.cpp	(working copy)
@@ -60,6 +60,10 @@
    wxT("ExportMp3"),
    wxT("ExportOgg"),
    wxT("ExportWav")
+#  if defined(USE_NYQUIST)
+      ,
+   wxT("Nyquist")//efm5
+#  endif
 };
 
 static const wxString CleanSpeech = wxT("CleanSpeech");
@@ -238,6 +242,9 @@
    AddToChain( wxT("Leveller") );
    AddToChain( wxT("Normalize") );
    AddToChain( wxT("ExportMp3") );
+#  if defined(USE_NYQUIST)
+   AddToChain( wxT("Nyquist") );//efm5
+# endif
 }
 
 void BatchCommands::SetWavToMp3Chain()
@@ -488,6 +495,11 @@
       return false;
 #endif
    } 
+#  if defined(USE_NYQUIST)
+   else if (command == wxT("Nyquist")) {//efm5 start
+      wxMessageBox(wxT("nyquist msg"));//debug msg
+   }//efm5 end
+# endif
    wxMessageBox(wxString::Format(_("Command %s not implemented yet"),command.c_str()));
    return false;
 }

Yes, that’s exactly the sort of thing.

This is the most simple full plug-in that I could think of. It scales the amplitude of the selection by 0.5 (half amplitude).
Save this as a text file with a file extension .ny

;nyquist plug-in
;version 1
;type process
;name "Half Amplitude"
;action "Processing..."

(mult s 0.5)



This might be a tricky bit.
Nyquist code does not have its own GUI. Nyquist plug-ins begin with a few header lines that are read by Audacity (see here: Audacity and Nyquist ). If the header includes one or more lines:

;control parameters

then Audacity will create a GUI with the appropriate widgets (see here: Audacity and Nyquist )

To create a global variable in Nyquist and give it a value, the Nyquist code is:

(setq variable value)

For “process” type plug-ins (“Effect” plug-ins), a special global variable “s” passes audio from the track to Nyquist.
If the track is a mono track, then the value of “s” is of data type “sound”.
If the track is a stereo track, then the value of “s” is an array with two elements. Each element is a “sound” (one for each channel).
A “sound” is a primitive data type in Nyquist. Nyquist Functions

Ways that it may be possible to pass parameters to Nyquist:

  1. To send values for the control widgets in a similar way to how parameters are sent to other effects (I don’t know how that is done, or how the effect GUI is suppressed).
  2. Pass a named variable with a value to Nyquist in a similar way to how “S” is passed to Nyquist.
  3. Pass code to the Nyquist plug-in. This may work most easily if using the Nyquist Prompt as the Nyquist Chain effect.

Example of Nyquist code that includes a variable:

(setq amount 0.5)
(mult s amount)

Example of a Nyquist plug-in that has user input:

;nyquist plug-in
;version 1
;type process
;name "Change Amplitude"
;action "Processing..."
;control amount "Amount to amplify" real "Linear scale" 0.5 0.0 10.0

(mult s amount)

OK, will play some more after doing the gardening…

OK, gardening done for the day–the joy of multi-tasking is I got to think about this feature while potting, weeding and putting in a new section of lawn!

First thoughts:

  1. User Interface:
    A) Is it OK to interrupt the chain with a typical Nyquist dialog asking for user input?
    i) If “No” how do we pass parameters into the effect?
    ii) If “Yes” do we want to be able to access all (Nyquist??) effects?
    B) If we go with a one-off effect that the user writes/edits where do we store the file?
    i) If it is a typical xxx.ny file stored in plug-ins it will be picked up automatically for inclusion in the general menu.
    a) Do we want it in the Audacity menu?
    b) If we do not want it in the menu how do we deal with this?
    ii) We could invent a custom extension-- .nc maybe, and keep it in the plug-ins folder.

  2. Code:
    A) Can we find a member of the Development Team to mentor the project?
    i) If not, can we find someone on Mac who can compile Audacity so we may pre-alpha test on all 3 platforms?
    B) Should we expect to get this committed pre-2.0?
    C) Can this be written as a module?
    i) If so, should it be a module?
    ii) Can we find a programmer who knows how to write modules who might help?

I’ll play with a proof-of-concept based on “no user input dialog”, one-off, using .ny (thus it will appear in the menu) and not-a-module (I do not know how to write modules). I think the ultimate goal could be “allow user dialog”, any Nyquist effect including a “hidden” chainEffect.nc which will be in plug-ins, user-editable and NOT in the menu, and it would be nice if we could make it a module so it would not be dependent on a Development Team programmer/mentor.

It is whipping me! When called via a chain the nyquist code fails to return audio and I cannot figure out why. Here is the code so far:

Index: src/BatchCommands.cpp
===================================================================
--- src/BatchCommands.cpp	(revision 11173)
+++ src/BatchCommands.cpp	(working copy)
@@ -60,6 +60,10 @@
    wxT("ExportMp3"),
    wxT("ExportOgg"),
    wxT("ExportWav")
+#  if defined(USE_NYQUIST)
+      ,
+   wxT("Nyquist")//efm5
+#  endif
 };
 
 static const wxString CleanSpeech = wxT("CleanSpeech");
@@ -410,7 +414,7 @@
 // If you find yourself adding lots of existing commands from the menus here, STOP
 // and think again.  
 // ======= IMPORTANT ========
-bool BatchCommands::ApplySpecialCommand(int iCommand, const wxString command,const wxString params)
+bool BatchCommands::ApplySpecialCommand(int iCommand, const wxString command, const wxString params)
 {
    if (ReportAndSkip(command, params))
       return true;
@@ -425,12 +429,14 @@
    }
 
    wxString filename;
-   if (mFileName.IsEmpty()) {   
-      filename = project->BuildCleanFileName(project->GetFileName());
+   if (command != wxT("Nyquist")) {//efm5 start
+      if (mFileName.IsEmpty()) {   
+         filename = project->BuildCleanFileName(project->GetFileName());
+      }
+      else {
+         filename = project->BuildCleanFileName(mFileName);
+      }
    }
-   else {
-      filename = project->BuildCleanFileName(mFileName);
-   }
 
    // We have a command index, but we don't use it!
    // TODO: Make this special-batch-command code use the menu item code....
@@ -488,6 +494,14 @@
       return false;
 #endif
    } 
+#  if defined(USE_NYQUIST)//efm5 start
+   else if (command == wxT("Nyquist")) {
+      Effect * f = EffectManager::Get().GetEffectByIdentifier(wxT("Batch"));
+      if (f != NULL) {
+         return ApplyEffectCommand(f, command, params);
+      }
+   }
+# endif//efm5 end
    wxMessageBox(wxString::Format(_("Command %s not implemented yet"),command.c_str()));
    return false;
 }

I am very close but am having trouble debugging the “C” code as I may not set breakpoints in it because it does not compile into Debug code. Will play with it some more later.

Got it!

Index: src/BatchCommands.cpp
===================================================================
--- src/BatchCommands.cpp	(revision 11173)
+++ src/BatchCommands.cpp	(working copy)
@@ -60,6 +60,10 @@
    wxT("ExportMp3"),
    wxT("ExportOgg"),
    wxT("ExportWav")
+#  if defined(USE_NYQUIST)
+      ,
+   wxT("Nyquist")//efm5
+#  endif
 };
 
 static const wxString CleanSpeech = wxT("CleanSpeech");
@@ -410,7 +414,7 @@
 // If you find yourself adding lots of existing commands from the menus here, STOP
 // and think again.  
 // ======= IMPORTANT ========
-bool BatchCommands::ApplySpecialCommand(int iCommand, const wxString command,const wxString params)
+bool BatchCommands::ApplySpecialCommand(int iCommand, const wxString command, const wxString params)
 {
    if (ReportAndSkip(command, params))
       return true;
@@ -425,12 +429,14 @@
    }
 
    wxString filename;
-   if (mFileName.IsEmpty()) {   
-      filename = project->BuildCleanFileName(project->GetFileName());
-   }
-   else {
-      filename = project->BuildCleanFileName(mFileName);
-   }
+   if (command != wxT("Nyquist")) {//efm5 start
+      if (mFileName.IsEmpty()) {   
+         filename = project->BuildCleanFileName(project->GetFileName());
+      }
+      else {
+         filename = project->BuildCleanFileName(mFileName);
+      }
+   }//efm5 end
 
    // We have a command index, but we don't use it!
    // TODO: Make this special-batch-command code use the menu item code....
@@ -488,6 +494,14 @@
       return false;
 #endif
    } 
+#  if defined(USE_NYQUIST)//efm5 start
+   else if (command == wxT("Nyquist")) {
+      Effect * f = EffectManager::Get().GetEffectByIdentifier(wxT("Batch"));
+      if (f != NULL) {
+         return ApplyEffectCommand(f, command, params, true);
+      }
+   }
+# endif//efm5 end
    wxMessageBox(wxString::Format(_("Command %s not implemented yet"),command.c_str()));
    return false;
 }
@@ -511,7 +525,7 @@
    return true;
 }
 
-bool BatchCommands::ApplyEffectCommand(   Effect * f, const wxString command, const wxString params)
+bool BatchCommands::ApplyEffectCommand(   Effect * f, const wxString command, const wxString params, bool pbNyquistChain)
 {
    //Possibly end processing here, if in batch-debug
    if( ReportAndSkip(command, params))
@@ -525,7 +539,11 @@
    project->SelectAllIfNone();
 
    // NOW actually apply the effect.
-   return project->OnEffect(ALL_EFFECTS | CONFIGURED_EFFECT , f, params, false);
+   //efm5 start
+   int type = ALL_EFFECTS | CONFIGURED_EFFECT;
+   if (pbNyquistChain) type = PLUGIN_EFFECT | PROCESS_EFFECT;
+   return project->OnEffect(type, f, params, false);
+   //efm5 end
 }
 
 bool BatchCommands::ApplyMenuCommand(const wxString command, const wxString params)
Index: src/BatchCommands.h
===================================================================
--- src/BatchCommands.h	(revision 11173)
+++ src/BatchCommands.h	(working copy)
@@ -28,7 +28,7 @@
    bool ApplyCommand( const wxString command, const wxString params );
    bool ApplyCommandInBatchMode(const wxString & command, const wxString &params);
    bool ApplySpecialCommand(int iCommand, const wxString command,const wxString params);
-   bool ApplyEffectCommand(Effect * f, const wxString command, const wxString params);
+   bool ApplyEffectCommand(Effect * f, const wxString command, const wxString params, bool pbNyquistChain = false);
    bool ApplyMenuCommand(const wxString command, const wxString params);
    bool ReportAndSkip( const wxString command, const wxString params );
    void AbortBatch();

chain.ny:

;nyquist plug-in
;version 1
;type process
;name "Batch"
;action "Processing custom chain effect"

(mult s 0.5)

To make debugging a bit easier I changed the text on the Nyquist code you supplied. I am going to try something a bit more challenging next…

more challenging…

I kept the first few lines of the half amp code and inserted the code from SilenceFinder:

;nyquist plug-in
;version 1
;type process
;name "Batch"
;action "Processing custom chain effect"


;info "Written by Alex S. Brown, PMP (http://www.alexsbrown.com) nReleased under terms of the GNU General Public License version 2nAdds point labels in areas of silence according to the specifiednlevel and duration of silence. If too many silences are detected,nincrease the silence level and duration; if too few are detected,nreduce the level and duration."
;control sil-lev "Treat audio below this level as silence [ -dB]" real "" 26 0 100
;control sil-dur "Minimum duration of silence [seconds]" real "" 1.0 0.1 5.0
;control labelbeforedur "Label placement [seconds before silence ends]" real "" 0.3 0.0 1.0

;Create a function to make the sum the two channels if they are stereo
(defun mono-s (s-in) (if (arrayp s-in) (snd-add (aref s-in 0) (aref s-in 1))
s-in))

;Create a function to reduce the sample rate and prepare the signal for
;analysis. RMS is good to monitor volume the way humans hear it, but is not
;available in Audacity. Used a peak-calculating function instead.
;NOTE: this is the place to add any processing to improve the quality of the
;signal. Noise filters could improve the quality of matches for noisy signals.
;PERFORMANCE vs. ACCURACY
;Reducing the samples per second should improve the performance and decrease
;the accuracy of the labels. Increasing the samples per second will do the
;opposite. The more samples checked, the longer it takes. The more samples
;checked, the more precisely the program can place the silence labels.
;my-srate-ratio determines the number of samples in my-s. Set the number after (snd-srate s)
;higher to increase the number of samples.

(defun my-s (s-in)
 (setq my-srate-ratio (truncate (/ (snd-srate (mono-s s-in)) 100)))
 (snd-avg (mono-s s-in) my-srate-ratio my-srate-ratio OP-PEAK)
)

;Set the silence threshold level (convert it to a linear form)
(setq thres (db-to-linear (* -1 sil-lev)))
;Store the sample rate of the sound
(setq s1-srate (snd-srate (my-s s)))
;Initialize the variable that will hold the length of the sound.
;Do not calculate it now with snd-length, because it would waste memory.
;We will calculate it later.
(setq s1-length 0)
;Initialize the silence counter and the labels variable
(setq sil-c 0)
(setq l NIL)
;Convert the silence duration in seconds to a length in samples
(setq sil-length (* sil-dur s1-srate))

;Define a function to add new items to the list of labels
(defun add-label (l-time l-text)
 (setq l (cons (list l-time l-text) l))
)

;The main working part of the program, it counts
;the number of sequential samples with volume under
;the threshold. It adds to a list of markers ever time
;there is a longer period of silence than the silence
;duration amount.

;It runs through a loop, adding to the list of markers (l)
;each time it finds silence.
(let (s1) ;Define s1 as a local variable to allow efficient memory use
 ; Get the sample into s1, then free s to save memory
 (setq s1 (my-s s))
 (setq s nil)
 ;Capture the result of this "do" loop, because we need the sountd's legnth
 ;in samples.
 (setq s1-length
  ;Keep repeating, incrementing the counter and getting another sample
  ;each time through the loop.
  (do ((n 1 (+ n 1)) (v (snd-fetch s1) (setq v (snd-fetch s1))))
   ;Exit when we run out of samples (v is nil) and return the number of
   ;samples processed (n)
   ((not v) n)
   ;Start the execution part of the do loop
   ;if found silence, increment the silence counter
   (if (< v thres) (setq sil-c (+ sil-c 1)))

   ;If this sample is NOT silent and the previous samples were silent
   ;then mark the passage.
   (if (and (> v thres) (> sil-c sil-length))
 	  ;Mark the user-set number of seconds BEFORE this point to avoid clipping the start
 	  ;of the material.
    (add-label (- (/ n s1-srate) labelbeforedur) "S")
   )
   ;If this sample is NOT silent, then reset the silence counter
   (if (> v thres)
    (setq sil-c 0)
   )
  )
 )
)

;Check for a long period of silence at the end
;of the sample. If so, then mark it.
(if (> sil-c sil-length)
 ;If found, add a label
 ;Label time is the time the silence began plus the silence duration target
 ;amount. We calculate the time the silence began as the end-time minus the
 ;final value of the silence counter
 (add-label (+ (/ (- s1-length sil-c) s1-srate) sil-dur) "S")
)

;If no silence markers were found, return a message
(if (null l)
 (setq l "No silences found. Try reducing the silencenlevel and minimum silence duration.")
)
l

worked a charm! Can I figure out how to pass a parameter?

I see now that without major surgery to the Nyquist concept I would not be able to pass generic parameters.

I see how to hide the Batch menu item:

Index: src/BatchCommands.cpp
===================================================================
--- src/BatchCommands.cpp	(revision 11173)
+++ src/BatchCommands.cpp	(working copy)
@@ -60,6 +60,10 @@
    wxT("ExportMp3"),
    wxT("ExportOgg"),
    wxT("ExportWav")
+#  if defined(USE_NYQUIST)
+      ,
+   wxT("Nyquist")//efm5
+#  endif
 };
 
 static const wxString CleanSpeech = wxT("CleanSpeech");
@@ -410,7 +414,7 @@
 // If you find yourself adding lots of existing commands from the menus here, STOP
 // and think again.  
 // ======= IMPORTANT ========
-bool BatchCommands::ApplySpecialCommand(int iCommand, const wxString command,const wxString params)
+bool BatchCommands::ApplySpecialCommand(int iCommand, const wxString command, const wxString params)
 {
    if (ReportAndSkip(command, params))
       return true;
@@ -425,12 +429,14 @@
    }
 
    wxString filename;
-   if (mFileName.IsEmpty()) {   
-      filename = project->BuildCleanFileName(project->GetFileName());
-   }
-   else {
-      filename = project->BuildCleanFileName(mFileName);
-   }
+   if (command != wxT("Nyquist")) {//efm5 start
+      if (mFileName.IsEmpty()) {   
+         filename = project->BuildCleanFileName(project->GetFileName());
+      }
+      else {
+         filename = project->BuildCleanFileName(mFileName);
+      }
+   }//efm5 end
 
    // We have a command index, but we don't use it!
    // TODO: Make this special-batch-command code use the menu item code....
@@ -488,6 +494,14 @@
       return false;
 #endif
    } 
+#  if defined(USE_NYQUIST)//efm5 start
+   else if (command == wxT("Nyquist")) {
+      Effect * f = EffectManager::Get().GetEffectByIdentifier(wxT("Batch"));
+      if (f != NULL) {
+         return ApplyEffectCommand(f, command, params, true);
+      }
+   }
+# endif//efm5 end
    wxMessageBox(wxString::Format(_("Command %s not implemented yet"),command.c_str()));
    return false;
 }
@@ -511,7 +525,7 @@
    return true;
 }
 
-bool BatchCommands::ApplyEffectCommand(   Effect * f, const wxString command, const wxString params)
+bool BatchCommands::ApplyEffectCommand(   Effect * f, const wxString command, const wxString params, bool pbNyquistChain)
 {
    //Possibly end processing here, if in batch-debug
    if( ReportAndSkip(command, params))
@@ -525,7 +539,11 @@
    project->SelectAllIfNone();
 
    // NOW actually apply the effect.
-   return project->OnEffect(ALL_EFFECTS | CONFIGURED_EFFECT , f, params, false);
+   //efm5 start
+   int type = ALL_EFFECTS | CONFIGURED_EFFECT;
+   if (pbNyquistChain) type = PLUGIN_EFFECT | PROCESS_EFFECT;
+   return project->OnEffect(type, f, params, false);
+   //efm5 end
 }
 
 bool BatchCommands::ApplyMenuCommand(const wxString command, const wxString params)
Index: src/BatchCommands.h
===================================================================
--- src/BatchCommands.h	(revision 11173)
+++ src/BatchCommands.h	(working copy)
@@ -28,7 +28,7 @@
    bool ApplyCommand( const wxString command, const wxString params );
    bool ApplyCommandInBatchMode(const wxString & command, const wxString &params);
    bool ApplySpecialCommand(int iCommand, const wxString command,const wxString params);
-   bool ApplyEffectCommand(Effect * f, const wxString command, const wxString params);
+   bool ApplyEffectCommand(Effect * f, const wxString command, const wxString params, bool pbNyquistChain = false);
    bool ApplyMenuCommand(const wxString command, const wxString params);
    bool ReportAndSkip( const wxString command, const wxString params );
    void AbortBatch();
Index: src/Menus.cpp
===================================================================
--- src/Menus.cpp	(revision 11173)
+++ src/Menus.cpp	(working copy)
@@ -890,7 +890,8 @@
          c->AddSeparator();
          names.Clear();
          for (size_t i = 0; i < effects->GetCount(); i++) {
-            names.Add((*effects)[i]->GetEffectName());
+            if ((*effects)[i]->GetEffectName() !=  wxT("Batch")) 
+               names.Add((*effects)[i]->GetEffectName());
          }
          c->AddItemList(wxT("EffectPlugin"), names, FN(OnProcessPlugin), true);
       }

I think this is as close as I can come to your request! You have a “hidden” (not in the menu) Nyquist .ny source code file called “chain.ny”, it’s personal name is “Batch” and any Nyquist plug-in with a name of “Batch” will be ignored when creating the visual Effects menu. You have a new Batch command called “Nyquist”, it takes NO parameters. Stick it in a batch command chain and the chain.ny Batch plug-in will be executed.

This does not solve the situation of wanting to apply two or more different Nyquist plug-ins during a batch command chain (one could add Nyquist2, Nyquist3 etc. batch commands–each using a different user-edited chain2.ny & chain3.ny etc. plug-in; this would require re-compiling Audacity). There are LOTS of TODOs & FIX-MEs in the batch command code especially in relation to effects. It may not make much sense to go further than this until these are resolved (and that will require major changes to the Effects code!)

Thanks for your work on this Edgar. This sounds very encouraging.
I’ll need a bit of time to play with this.

That’s fantastic :smiley:

I had a bit of trouble applying the patch thanks to phpBB messing up the formatting of the code, but have now got it successfully installed.

This feature could I’m sure be developed further (passing parameters would be the number 1 major improvement) but as it is now it is an extremely useful new feature.
I suspect that your right about further development requiring “major surgery”, however, what you have already achieved goes way beyond what I was hoping for. I was only asking for a feasibility assessment, but you’ve provided a functional new feature with enormous potential. Thank you, I’m delighted :smiley:

A) No, not really as that largely defeats the point of batch processing.
i) Technically, I’ve no idea. In terms of the interface, what would be most useful would be to enter “variable value” pairs to replace the ;control settings (more later)
ii) Ability to access all Nyquist plug-ins would be great, but even one is a massive boon as it allows custom effects to be created by the user and applied in a chain. There must have been dozens of requests on the forum where we’ve had to send users off to explore SoX and other command line tools which can now be handled by Audacity Chains. A very recent case was a user asking how to copy the right channel of a stereo track to the left channel for a large collection of audio recordings. Another recent case was how to batch Normalize without altering the stereo balance. This feature has a tremendous range of application - I’m excited!

B) As now, a typical xxx.ny file works perfectly, though I think it would be beneficial to not hide it but to include it in the list of effects.
The advantage of displaying the “Batch” effect in the normal Effect menu is that it makes testing the Nyquist code very much easier.
Example:
Task: to make a custom multi-band notch filter for removing noise.
Nyquist Code: Something like (notch2 (notch2 (notch2 s 60 10) 120 20) 180 25)
To tweak the parameters, the “chain.ny” file can be opened in a text editor, the initial parameters set, and the file saved but kept open in the text editor. The effect can then be applied to a test sample, using the “Batch” effect from the “Edit” menu. If the results are not quite right, the parameters can be changed in the text editor, saved again, and tested again. When the parameters are just right, the effect can then be used in a Chain command.

A) So far no response, but with the encouraging results of your patch I’ll see if I can entice anyone.
i) I’m not sure how far along Bruno is with building on Mac - I’ll PM him.
B) Unfortunately my guess is no, unless we get the backing of a senior developer.
C) No idea, but with revision 11179 I can’t get the Nyquist WorkBench module to show up. Do you know if modules have been disabled in some way?

About passing parameters and using existing plug-ins:

Example: Batch processing with “Vocal Remover”.

  1. Copy and paste from vocalremove.ny to chains.ny
  2. Delete or comment out (with an additional preceding semicolon) the ;categories line.
  3. (optional) Delete the ;info header line
  4. Change the name to “Batch” in the ;name header line
  5. Delete or comment out (with an additional preceding semicolons) the ;control header lines
  6. Set the variables for the user input parameters with a new line, for example:
(setq action 0 bc 0 range "500 2000")

The new chains.ny file will now look like this:

;nyquist plug-in
;version 3
;type process
;name "Vocal Remover (for center-panned vocals)..."
;action "Removing vocals or other center-panned audio..."

;;control action "Remove vocals or view Help" choice "Remove vocals,View Help" 0
;;control bc "Removal choice" choice "Simple (entire spectrum),Remove frequency band,Retain frequency band" 0
;;control range "Frequency band lower and upper limit [Hz]n [Enter two values between 0 and 20000]" string " " "500 2000"
(setq action 0 bc 0 range "500 2000")
.....
.....

This will now allow the Vocal Remover effect to be used for batch processing in Chain commands. The “parameters” are set by the line (setq action 0 bc 0 range “500 2000”) which in this case are the same as the defaults.

The crucial parts are that

  1. The ;control header lines are disabled
  2. The variables “action”, “bc” and “range” are set to valid values. In this case, integer 0, integer 0 and string “500 2000”.

Removing the ;control header lines is not a problem since the “chains.ny” plug-in will invariably be customised by the user, so they can just be deleted.
What would be nice, and this is likely to be the problematic part, is how to send the variable/value pairs from the “Parameters” field in the Chains command to the plug-in.

Plug-in types:
There are three types of Nyquist plug-in - “process”, “generate” and “analyze” which appear in the “Effect”, “Generate” and “Analyze” menus respectively.
If the “chain.ny” effect is not hidden from the normal menus, then it might be a good idea to have 3 Nyquist-chain plug-ins, one of each type, so that the plug-in is not jumping from one menu to another. (this could probably also be helpfully abused to allow using multiple Nyquist effects in the same chain - I’ll do some experiments).

Thanks again Edgar.
Steve



-   return project->OnEffect(ALL_EFFECTS | CONFIGURED_EFFECT , f, params, false);
+   //efm5 start
+   int type = ALL_EFFECTS | CONFIGURED_EFFECT;
+   if (pbNyquistChain) type = PLUGIN_EFFECT | PROCESS_EFFECT;
+   return project->OnEffect(type, f, params, false);
+   //efm5 end

I had to enforce the type as under batch processing “ALL_EFFECTS | CONFIGURED_EFFECT” was being defaulted and if the Nyquist code does not see a type including one of INSERT_EFFECT, PROCESS_EFFECT or ANALYZE_EFFECT it fails gracefully. If you use a “non-PROCESS_EFFECT” in the chain my code may fail someday unless we determine a way to set type dynamically to suit (with the current patched Audacity code & testing EqualLevel–an ANALYZE_EFFECT–it works just fine masquarading as a PROCESS_EFFECT).

// HIDDEN_EFFECT allows an item to be excluded from the effects
// menu in both CleanSpeech and in normal builds.
#define HIDDEN_EFFECT   0x0008

#define INSERT_EFFECT   0x0010
#define PROCESS_EFFECT  0x0020
#define ANALYZE_EFFECT  0x0040
#define ALL_EFFECTS     0x00FF



True, but only for the non-naive user. If the the naive user saw “Batch” as a menu item it might be terribly confusing–I guess the default chain.ny could be a NOOP dialog explaining the feature’s usage.

That’s exactly what I had in mind.
I also thought that it might be clearer and less alluring to the naive user if the name of the effect was “Chains” rather than “Batch”.

;nyquist plug-in
;version 1
;type process
;name "Chain"
;action "Processing custom chain effect"
(format nil 
"This is a 'dummy' effect that may be edited and used in Chain commands.~%
See 'Nyquist Chain' in the manual for more details.")



I’ve just tried it with this and it seems to accept all Nyquist plug-in types (though I doubt there’s much demand for Generate plug-ins in Chains).

   int type = ALL_EFFECTS | CONFIGURED_EFFECT;
   if 
      (pbNyquistChain) type = PLUGIN_EFFECT | ANALYZE_EFFECT | PROCESS_EFFECT | INSERT_EFFECT;
   return project->OnEffect(type, f, params, false);

Code question:
I noticed that in your first patch you had:

 static const wxString CleanSpeech = wxT("CleanSpeech");
@@ -238,6 +242,9 @@
    AddToChain( wxT("Leveller") );
    AddToChain( wxT("Normalize") );
    AddToChain( wxT("ExportMp3") );
+#  if defined(USE_NYQUIST)
+   AddToChain( wxT("Nyquist") );//efm5
+# endif
 }

but this has been left out of your most recent patch. Is it not needed?


Regarding using multiple Nyquist plug-ins, it’s probably possible to code the Nyquist plug-in so that it runs different code on sequential runs (if that’s ever needed).

This is the current patch that I’m using - does it look OK? Does it work on Windows?
NyquistChain.patch (2.97 KB)

Cleanspeech is going away for 2.0 (or so I have been told when crafting bug fixes which needed to take it into account) so the excised code is not needed and was never tested with Cleanspeech.

The whole API and UI stuff for this feature is very tentative and if/when the menu/effects/commands stuff gets rewritten it will all have to be revisited and hopefully become moot. Once Audacity can treat built-in & plug-in effect with equality the batch processing should come automatically.

I am leaving for my daily “feed the animals” adventure. Will grab a new SVN and test the patch in a couple hours or so. BTW, since I seem to be persona non grata these days it might be best if you leave my name out of any discussions with the Team .

The patch does not include adding chain.ny to the repository–I cannot seem to create a patch which does so.

Your patch is OK but I prefer this tiny change in formatting:

Index: src/BatchCommands.cpp
===================================================================
--- src/BatchCommands.cpp	(revision 11179)
+++ src/BatchCommands.cpp	(working copy)
@@ -59,7 +59,8 @@
    wxT("ExportFlac"),
    wxT("ExportMp3"),
    wxT("ExportOgg"),
-   wxT("ExportWav")
+   wxT("ExportWav"),
+   wxT("Nyquist")
 };
 
 static const wxString CleanSpeech = wxT("CleanSpeech");
@@ -425,12 +426,14 @@
    }
 
    wxString filename;
-   if (mFileName.IsEmpty()) {   
-      filename = project->BuildCleanFileName(project->GetFileName());
+   if (command != wxT("Nyquist")) {
+      if (mFileName.IsEmpty()) {   
+         filename = project->BuildCleanFileName(project->GetFileName());
+      }
+      else {
+         filename = project->BuildCleanFileName(mFileName);
+      }
    }
-   else {
-      filename = project->BuildCleanFileName(mFileName);
-   }
 
    // We have a command index, but we don't use it!
    // TODO: Make this special-batch-command code use the menu item code....
@@ -487,7 +490,12 @@
       wxMessageBox(_("FLAC support is not included in this build of Audacity"));
       return false;
 #endif
-   } 
+   } else if (command == wxT("Nyquist")) {
+      Effect * f = EffectManager::Get().GetEffectByIdentifier(wxT("Chain"));
+      if (f != NULL) {
+         return ApplyEffectCommand(f, command, params, true);
+      }
+   }
    wxMessageBox(wxString::Format(_("Command %s not implemented yet"),command.c_str()));
    return false;
 }
@@ -511,7 +519,7 @@
    return true;
 }
 
-bool BatchCommands::ApplyEffectCommand(   Effect * f, const wxString command, const wxString params)
+bool BatchCommands::ApplyEffectCommand(   Effect * f, const wxString command, const wxString params, bool pbNyquistChain)
 {
    //Possibly end processing here, if in batch-debug
    if( ReportAndSkip(command, params))
@@ -525,7 +533,10 @@
    project->SelectAllIfNone();
 
    // NOW actually apply the effect.
-   return project->OnEffect(ALL_EFFECTS | CONFIGURED_EFFECT , f, params, false);
+   int type = ALL_EFFECTS | CONFIGURED_EFFECT;
+   if (pbNyquistChain)
+      type = PLUGIN_EFFECT | ANALYZE_EFFECT | PROCESS_EFFECT | INSERT_EFFECT;
+   return project->OnEffect(type, f, params, false);
 }
 
 bool BatchCommands::ApplyMenuCommand(const wxString command, const wxString params)
Index: src/BatchCommands.h
===================================================================
--- src/BatchCommands.h	(revision 11179)
+++ src/BatchCommands.h	(working copy)
@@ -28,7 +28,7 @@
    bool ApplyCommand( const wxString command, const wxString params );
    bool ApplyCommandInBatchMode(const wxString & command, const wxString &params);
    bool ApplySpecialCommand(int iCommand, const wxString command,const wxString params);
-   bool ApplyEffectCommand(Effect * f, const wxString command, const wxString params);
+   bool ApplyEffectCommand(Effect * f, const wxString command, const wxString params, bool pbNyquistChain = false);
    bool ApplyMenuCommand(const wxString command, const wxString params);
    bool ReportAndSkip( const wxString command, const wxString params );
    void AbortBatch();

chain.ny (246 Bytes)
NyquistChain1.patch (3.06 KB)

@Steve: has Ed “fixed this for you” ? Or does it still need transferring to the Wiki?

Peter.

Thanks to Ed we have a fully working proof of concept.
The feature request is to implement this concept into Audacity.

I’ve just been sent a new patch from one of the developers - more later (I’m on my way out).

Here’s the patch.
It adds support for all currently installed Nyquist Plug-ins, not just “chain.ny”

Note that this is an unofficial patch.

A couple of minor issues that may occur with this patch:

The developer reported that there may be ellipsis at the end of the name in the “Select Command” dialog (I’m not seeing this on Ubuntu 10.10 with svn Head)

There is one peculiarity that I am seeing, which is that the default value for a slider widget is not being read. If a Nyquist effect that has not been previously used in the current Audacity session is added to a Chain, then the value of any slider widget that takes a float value defaults to 99999999.990000 and slider widgets that take integer
values default to 99999999. Once a Nyquist effect has been run in the Audacity session, or if the effect is opened by clicking on the Parameters button, then the slider widget value is read correctly.
nyqchains.patch (4.26 KB)