I hope others will find the tool menu equal convenient as we do.
Incidentely, there already some circumstances when Nyquist calls a plug-in only once. It is precisely then, when nothing (nil) is returned.
You can test it by creating several tracks and then enter (Nyq-prompt):
(print (random 100))
This returns as many times a number as there are tracks selected. If you change the code to
(print (random 100))
nil
and press the debug button, only one call will be made and only 1 number displayed in the debug window.
I’ve split this topic because it raises an interesting point, but unrelated to the original topic.
Consider running this code from the Nyquist Prompt or as an effect plug-in on multiple tracks:
(setq test (peak s ny:all))
(if (> test 0)
(format nil "sound")
(format t "silence"))
If all tracks have audio with an amplitude grater than zero it will run for each track.
If a track is encountered that either has no audio, or contains silence, then Audacity will pop up the message: “Nyquist did not return audio” and the effect will quit.
(setq test (peak s ny:all))
(format (= test 0) "~a~%" test)
Ok, we see that it is no problem to stop the execution by any test imagineable. The bad thing is that we always have the standard error message, which can’t be altered - not even temporaily.
However, there’s a silver streak at the horizon …
If one doesn’t mind cheating, there’s also this variant
;nyquist plug-in
;version 1
;type analyze
;name "1 Test For All"
;action "testing code"
;info "Nth Power of X"
;control x "Base X" real 7 0.001 1000
;control n "Exponent N" real 7 0.001 1000
(Format nil "~a raised by ~a gives ~a" x n
(expt x n))+(
But that’s still not where the fun begins.
Our next goal is to eliminate the error message that no audio was returned.
To eliminate the dialog box, we have to…
…return the sound. That’s also true for analyze plug-ins (which I wrongly assumed could not modify and return any sound - my fault).
Once again, the plug-in must do its job in the first call and afterwards only return the sound it just has fetched from Audacity.
That’s what this topic shall be all about (although I didn’t have the intention to start a whole thread about the subject in the first place).
What we need first is a counter, that tells us in which call we are. Normally, all variables are deleted after a call and Audacity starts the new call again from scratch, only the user inputs are restored and the sound, len and samplerate of the next track’s selected region are newly assigned.
Since Audacity 1.3.11 or so, it is possible to save values in the variable scratch which is not re-initialized between calls. Not enough, it holds the value during an entire Audacity session (which is for our purpose rather bad).
Ok, scratch would be a good candidate for the counter if we only knew when to reset it…
(to be continued)
Here is a little plug-in that mixes multiple mono tracks down to an empty (or silent) track.
Unfortunately there are difficulties in attaching a sound to SCRATCH so I’ve converted the sound to an array, which is not ideal but it works.
“Reset” returns NIL so that it only runs once.
Running the plug-in returns a null sound to each track (does nothing) until it finds an empty or silent track. On finding an empty or silent track it returns the mix:
Nice code, May come in handy at some time.
A few remarks. scratch is a pipe between different plug-in calls. But it gets narrow as a bottle neck if different plug-ins make use of it. Therefore, we would normally assign the sound to a property of scratch rather than its symbol-value. Something like:
(putprop '*scratch* s 'sd-mix-to-empty)
That’s for the first assignement of scratch, when it is not yet bound.
I would assign the sound-object directly, arrays are very memory consuming. The only point to watch carefully is that the assignement is not the last expression in the code, otherwise the samples are deleted by Audacity. Create a few tracks and try this.
(print s) s
(sum (setf *scratch* s))
The sum function does the trick, it does nothing but force a new memory allocation for the sound in scratch. When you press the debug button, the address of s is printed out. As you can see, it toggles between two values. You can now comment out the last line and press debug. Now, the addresses of s are printed again (and the original s returned). The values are no more intermittent. This indicates that the different tracks are always loaded into the same memory range.
Therefore, also the putprop code from above should have a surrounding dummy function (sum/mult etc.) if it is the last expression.
Are there any other problems with sounds in scratch I am not aware of? Please tell
(still to be continued)
Yes I know the code is not very practical as is. It was intended just as a demonstration of ways to return one result from multiple selected tracks.
Something a little more practical:
;nyquist plug-in
;version 3
;type process
;name "Cross-over split..."
;info "Select one mono audio tracks and two empty mono tracks below it."
;control xfreq "Split at (Hz)" real "" 2000 500 5500
(setq amplitude (peak s ny:all))
(when (not (get '*scratch* 'scross-over-split-count))
(putprop '*scratch* 0 'scross-over-split-count))
(cond
((> amplitude 0)
(format t "amplitude > 0~%")
(putprop '*scratch* s 'scross-over-split-sound)
(putprop '*scratch* 2 'scross-over-split-count)
(s-rest 0))
((= (get '*scratch* 'scross-over-split-count) 2)
(format t "count = 2~%")
(putprop '*scratch* 1 'scross-over-split-count)
(highpass2 (get '*scratch* 'scross-over-split-sound) xfreq))
((= (get '*scratch* 'scross-over-split-count) 1)
(format t "count = 1~%")
(putprop '*scratch* 0 'scross-over-split-count)
(let ((output (lowpass2 (get '*scratch* 'scross-over-split-sound) xfreq)))
(remprop '*scratch* 'scross-over-split-sound)
output))
(t (format t "nil~%")
nil))
An interesting (and somewhat annoying) thing here is that if applied to exactly 1 audio track with sound and 2 silent tracks below it, then the plug-in works as expected. High frequencies are returned to the first empty track, low frequencies to the second empty track and the debug window shows:
amplitude > 0
count = 2
count = 1
Now the annoyance; If more than two empty/silent tracks below the audio track are selected, then the debug window still displays:
amplitude > 0
count = 2
count = 1
which shows that the audio has indeed been processed, but because the final track returns nil, the plug-in exits and returns nothing to Audacity.
The problem can be worked around by replacing the last couple of lines with something like:
(t (format t "nil~%")
"Too many tracksn"))
but then Audacity shows “Too many tracks” for each additional empty track.
Two features that I think it would be very useful to have:
A symbol, which like scratch survives from one track to the next, but unlike scratch does not survive beyond one run of the plug-in. This would provide a simple test from which the track number could be counted.
For Nyquist to be aware of how many tracks have been selected.
Point 1:
That’s why I said above that it is bad in our case that scratch survives for the whole session (although it is fine for other cases). I recently managed to overcome this problem i.e. if the plug-in was started anew (with the GUI). The code is a little too complex and long to post here. Look for the next version of any of my plug-ins.
The features are in general:
The plug-in recognizes the first call after the GUI.
Current settings can be stored permanently (in contrast to Audacity which saves the parameters for one session only).
The “factory” defaults can also be restored.
The plug-in counts its calls for the total time since it was downloaded.
Point 2:
That’s perhaps the piece of information I miss the most, since the plug-in doesn’t work very well as a future teller…
In any input/output system, there’s a “eof” and one knows that there’s nothing more to come - time for tidying up. For a “end-of–tracks” mechanism it is to late.
However, it would be anyway preferable to know the number of selected tracks in advance (e.g. for paired operations that require an even number of tracks).
Your second program is indeed very useful. I’ve written a similar one (based on Edgar’s multitrack plug-in). I use it to copy and split the original audio into 4 (inverted) tracks with a different frequency band in each. It allows to equalize (num pad 1 and 7 for gain -/+) the audio during playback - very interactive and intuitive. Would be nice if those tracks could be grouped and collapsed into one track (only virtually, not destructive). Heavens, another feature request - will it ever end?
@Steve
For your cross-split plug-in, I suggest that you change the last two lines to
(t (format nil "nil~%")
(sum s)))
There won’t appear any annoying messages anymore. However, if the code encounters at any time a new non-silent track, it starts the process again and tries to split up the sound.
Perhaps the user wants this behaviour and selects on purpose more than 3 tracks.
If this is not what we desire, then we run again into the old problem, when and how to stop the execution, or how Gothe meant: “The spirits that I called…”
But as I said before, there’s a (crude) solution for this problem.
I’ve noticed that you have used (s-rest 0) for the first track. This doesn’t work, the first track is not silenced. To make it work, the silence must be at least one sample long (don’t ask me why). But why silence the original? You could apply the highpass filter directly on this audio. Or you can invert it and use as a complement to the following tracks. that’s fine if you want to equalize the sound or find the right mixture (wet/dry) for an effect such as chorus.
I nearly always use this technique because it is non-destructive.
Ok, I admit, it is a little bit a mess when using splitted stereo tracks and 32 tracks/channel in order to equalize them separately…
It does exactly what I intended it to do - nothing. It returns a null sound.
It’s a little trick to quickly do nothing to a track without generating an error. It’s much faster than returning “s”. The trick can be used as a workaround for the “end of tracks”, so the last couple of lines could be:
(t (format t "nil~%")
(s-rest 0)))
but it is just a workaround and we need to take care to avoid returning nil for any track as that kicks out the plug-in.
A feature/limitation of returning a null sound is that it removes the selection, or rather shrinks the selection down to just the cursor position.
Ok, it’s fine when you used it on purpose.
It’s a nice option to be able to “ignore” tracks if it is not the last one.
We could of course place our own “end-of-tracks” signal manually. Something like
snd-from-array 0 44100 (vector 1.2345e-20))
from the prompt. For your code, we would simply remove all properties and return s when the amplitude is greater than 0 and exact this value.
The predicate is a little tricky, due to rounding errors.
(setf eot 1.2345E-10)
(setf s (snd-from-array 0 44100 (vector eot )))
(setf amplitude (peak s 1) )
(format t "End of track? ~a"
(= (* 1e14 eot) (truncate (* 1e14 amplitude))))
That is of course not really practical pfor the normal user. The track should be invisible and automatically be created by audacity. And the value should be unique (-1#IND or so).
Probably the most user friendly way to mark “end of tracks”, assuming that tracks to be processed contain non-silent audio, is to use an empty track.
As long the plug-in is reset after running then it is quite easy to count the tracks, for example:
;type analyze
;info "Use Debug button to confirm reset."
;control action "Action" choice "Run test,Reset test" 0
(defun test ( sig &aux (test (get '*scratch* 'eot-test)))
(cond
;; empty/silent track to mark end
((= (peak sig ny:all) 0)
(remprop '*scratch* 'eot-test)
(print "End of Tracks.n"))
;; first track has run
(test
(putprop '*scratch* (1+ test) 'eot-test)
(format nil "Track number ~a.~%" (1+ test)))
;; this is the first track
(t (putprop '*scratch* 1 'eot-test)
(print "First track.n"))))
(defun reset ()
(remprop '*scratch* 'eot-test)
(format t "Plug-in has been reset."))
;; reset or run test
(if (= action 1)
(reset)
(if (arrayp s)
(test (aref s 0))
(test s)))
but we still have the same underlying problem and I can’t see any way round it that does not require user interaction such as setting a special “end of tracks” track or manually resetting. If Nyquist knew how many tracks were selected then that would resolve this limitation. I think that is definitely a feature that we would want in “version 4” plug-ins.
I wonder if not scratch itself could be used to hold the current number of selected tracks. Firstly, it hasn’t assigned a value yet, secondly, as we have seen, it is preferable to use properties on scratch to ensure individual use by different plug-ins (and the symbol-value is therefore still nil) and thirdly scratch survives the calls over the different tracks, the number of tracks has only once to be assigned to it after the GUI.
There is of course also the a solution ponderable with a property “number-of-selected-tracks”, but I like the separation. “symbol-value of scratch by Audacity / Properties of scratch reserved for plug-ins”.
Maybe we could shyly send a request in the developers mailing list, if it is even possible to determine the selected tracks. I have the impression that the source-code doesn’t differenciate the different track types audio, labels and midi until it sends it to the plug-in. The developers should know where and when the number of tracks can be grabed and given to scratch (or any other variable yet to be invented). .
C++ is not one of my languages, but I’ve been having a look through the source code to see what I can work out. As far as I can make out, there is only ever “one” Nyquist effect. When a Nyquist plug-in is selected, the XLISP code is loaded into the one “interactive” Nyquist effect thus creating the plug-in on demand. Audacity knows which tracks are selected before a Nyquist plug-in is created, which can be seen in the fact that Nyquist plug-ins are greyed out when no audio tracks are selected. I can’t see any existing variable for the number of tracks, but when multiple tracks are selected it looks like Audacity loops through the tracks and makes a list of the tracks to be processed before it sends the track data to the effect, so at that point I’d guess that a counter could be added.
scratch was created to provide a way for information to survive from one invocation of a plug-in to the next. I’d prefer that scratch was used only for this purpose rather than making it also responsible for passing data from Audacity to Nyquist. I prefer the idea of creating a new global variable that can pass additional project information to Nyquist.
I haven’t thrown a glance recently at the source code. In any case, a variable can be sent to Nyquist wither by taking the loop max directly or by firstly sieving out the wrong track types. The fact that label tracks are greyed out doesn’t say too much, this could be done in a total different module, but that doesn’t concern us, does it?
I know to what purpose scratch was created. But it didn’t take much time before RBD and others realized that it is better not too touch the symbol-value itself. It can too easily be modified by other plug-ins. It seems that scratch will remain bare and untouched (except when we’re trying something out and are too lazy to put a property to it).
However, it really doesn’t matter what variable carries the desired value as long as it is made available to us.
There is another property of scratch that isn’t assigned, namely the symbol-function. But I haven’t the faintest idea what function would be worse to store there…
It is a little bit confusing how code is executed. For your cross-split code above, it would have been nice to abord the execution after the 3 tracks, But forced errors seem to cancel the whole process. On the other hand, if I try some code and there’s an error, I frequently end up with some sound that I surely did not want to be placed there (sigh).
I think that only occurs if there is an error that does not cause the plug-in code to terminate “and” there was a sound returned by the last function executed.
I find this most commonly happens when I have mismatched parentheses (not an uncommon issue)
A very simple example with a missing close bracket:
(setf test (hzosc 1000))
(mult test 0.5
The first function is executed, returning a sound.
The second function is incomplete and produces an error.
The sound from the first function is returned to Audacity and is applied in each selected track.
In contrast, if the last valid statement before the error returns nil:
(setf test (hzosc 1000))
nil
(mult test 0.5
then nil is returned and no data is returned to any track (even if there was sound returned for some of the selected tracks - as in one of our previous examples).
That’s really hard to tell. It has something to do with the stacking of the different commands. You know, something is evaluated, an error detected, the breakloop entered, the next expression fetched and so on.
I tried to show the current expression and its environment with the help of eval-hook but nothing can’t be gained from it because the crucial thing seems to happen at some hidden moment. Furthermore, there are some interrupting functions inbetween, especially the garbage collection and the error routines of course. Impossible to trap them all. At least it isn’t worth the effort.
The case would be different if it worked with sounds also i.e. if the precalculated sounds wouldn’t be discarded by Audacity.
Facit: mere calculations that end up with numbers or text can be interrupted after one track, those with sounds can not.