Re: [Audacity-nyquist] Audacity and SAL
A free multi-track audio editor and recorder
Brought to you by:
aosiniao
From: Roger D. <rb...@cs...> - 2009-02-07 20:36:16
|
My apologies to the list -- when I first sent this, it had an entire audacity-nyquist digest appended to it. Maybe the moderator will see this and just delete the big message that is pending approval. -Roger This is a reply to Leland's post... The general method used to get sounds from Audacity is flawed, and now is probably the right time to think about fixing it. The solution (at least I'm fairly confident about this) is to provide access functions to retrieve sounds from Audacity, e.g. get-audio-selection(), get-audio-track(track-num [, optional parameters ...]). I think that currently Audacity limits the access (and the potential damage) to selections, but there are cases where access to other tracks, labels, etc. could make plugins much more powerful. Access functions that allow the plugin to survey the project and pull in data would provide a general, extensible interface. The main reason for access functions though is to allow a sound to be accessed multiple times. To do this now, you have to store all the samples in Nyquist as floats, and we've seen innumerable discussions about this, especially for normalization. With access functions, one could get and read a sound to compute the maximum value; then one could get another copy of the same sound and read it again to do dsp and return a result. This can be done without accumulating samples in the Nyquist heap. I suppose we can keep the current interface where the variable S is bound to the selection: If the plugin ignores S, it will just sit there with the potential to access samples and when the plugin returns, S will be set to NIL. So if backward compatibility with S is good (at the expense of being redundant with get-selection()), then this should work ok. > ;nyquist plug-in > ;version 3 > ;codetype sal > ;type generate > ;categories "http://lv2plug.in/ns/lv2core#GeneratorPlugin" > ;name "Pluck..." > ;action "Generating pluck sound..." > ;control p "Pluck MIDI pitch" int "" 60 1 127 > ;control fade "Fade-out type" choice "abrupt,gradual" 0 > ;control dur "Duration [seconds]" real "" 1 0.1 30 > > define function main() > begin > display "testing" > with final-amp = #?(fade = 1, 0.001, 0.000001), > pluck-sound = snd-pluck(*sound-srate*, step-to-hz(p), 0, dur, > final-amp), ;; alternatively: pluck-sound = pluck(p, 0, dur, final-amp) > max-peak = peak(pluck-sound, ny:all) > return (0.8 / max-peak) * pluck-sound > end > A few comments on the code: use WITH to declare local variables. Besides the general software engineering issues, in this case, pluck-sound will otherwise be a global variable that will accumulate all the pluck samples in Nyquist memory. Note commas between local variables -- you only get one WITH, but you can declare multiple locals. I used PLUCK instead of SND-PLUCK. It calls SND-PLUCK but the API is a bit simpler. I used "*" instead of SCALE -- after all that work on the SAL parser, it's a waste not to use infix notation :-) > > There's 3 things to look for there: > > 1) There's a new "codetype" comment that tells Audacity the plugin is > in SAL syntax. Anyone have a better suggestion for the keyword? > > 2) The plugin MUST define a function called "main" as that's what > Audacity will execute. This was necessary because you need to > "return" the result. > > 3) The plugin MUST "return" the desired result, be it, sound, string, > double, or whatever. These all sound like the right things to me. With "main", it occurred to me that S could be passed in as a parameter. This would eliminate the need to make S a global and to (inside nyx) set S to NIL when the plugin returns. For that matter, ALL the plugin parameters could be passed in as parameters. However, since the global S and global parameter value mechanism is likely to stay for Lisp syntax, I think we should stick with the same approach for Sal. Therefore, I agree with keeping main parameterless. > > What Audacity actually passes to Nyquist is: > > (setf p 60) > (setf fade 0) > (setf dur 0.997000) > (setf *sal-call-stack* nil) > (setf *audacity-plugin-result* nil) > (eval (sal-compile "define variable *audacity-plugin-result* > define function main() > begin > display \"testing\" > set final-amp = #?(fade = 1, 0.001, 0.000001) > set pluck-sound = snd-pluck(*sound-srate*, step-to-hz(p), 0, dur, > final-amp) > set max-peak = peak(pluck-sound, ny:all) > return scale(0.8 / max-peak, pluck-sound) > end > > set *audacity-plugin-result* = main() > " nil t nil)) > (eval *audacity-plugin-result*) > > And the SAL compiler produces: > > (PROG2 (SETF *SAL-LINE* 1) > (SETF *AUDACITY-PLUGIN-RESULT* > (IF (BOUNDP (QUOTE *AUDACITY-PLUGIN-RESULT*)) > *AUDACITY-PLUGIN-RESULT*))) > > (DEFUN MAIN > NIL > (SAL-TRACE-ENTER (QUOTE MAIN) > (LIST) > (QUOTE NIL)) > (PROG* NIL > (PROG2 (SETF *SAL-LINE* 4) > (DISPLAY "testing")) > (PROGN (SETF *SAL-LINE* 5) > (SETF FINAL-AMP > (IF (EQL FADE 1) 0.001 1e-06))) > (PROGN (SETF *SAL-LINE* 6) > (SETF PLUCK-SOUND > (SND-PLUCK *SOUND-SRATE* > (STEP-TO-HZ P) > 0 > DUR > FINAL-AMP))) > (PROGN (SETF *SAL-LINE* 7) > (SETF MAX-PEAK > (PEAK PLUCK-SOUND NY:ALL))) > (PROG2 (SETF *SAL-LINE* 8) > (SAL-RETURN-FROM MAIN > (SCALE (/ 0.8 MAX-PEAK) > PLUCK-SOUND))) > (SAL-RETURN-FROM MAIN NIL))) > > (PROGN (SETF *SAL-LINE* 11) > (SETF *AUDACITY-PLUGIN-RESULT* (MAIN))) > Here's what I think should be passed to Nyquist: (setf p 60) (setf fade 0) (setf dur 0.997000) (setf *sal-call-stack* nil) (eval (sal-compile "define function main() begin display \"testing\" set final-amp = #?(fade = 1, 0.001, 0.000001) set pluck-sound = snd-pluck(*sound-srate*, step-to-hz(p), 0, dur, final-amp) set max-peak = peak(pluck-sound, ny:all) return scale(0.8 / max-peak, pluck-sound) end " nil t nil)) (main) Notice that all of this is passed as a string to nyx_eval_expression. This function uses XLISP to read one expression at a time, including defining main and finally calling it. The value of the last expression is returned, which is what we want. Do not create *audacity-plugin-result*, a global, and assign the result of main to it, because this binds a global variable to the sound. When the sound is accessed to obtain samples for Audacity, the extra reference from the global will prevent samples from being freed; instead the samples will accumulate in Nyquist memory. > All this works fine and I can run plugins using SAL syntax directly > from the menus. But, I don't know if this is the best way to do it, > so suggestions are welcome. > > (Remember, to be gentle and use baby speak since I don't under > language too well yet. ;-)) Yes, well at least you have working code, and I'm just spewing ideas. The concepts are right, but I may have made a few typos. Leland |