In-Place editing

I have a pretty strong programming background (though LISP is a bit foreign to me), but I can’t figure out how to edit sound in place with my Nyquist plugin.

  1. I’ve recorded audio
  2. I copy it using snd-copy, and step through that using snd-fetch (to analyze the track for editing, basically read-ahead)
  3. I use snd-fetch to loop through ‘s’ next, modifying it

This alone seems to give a number as a return popup (value differs depending on how I structure my code, it appears to just be a return value from the last executed command)

I’ve also tried putting ‘s’ at the end of my code, as I saw that in one example - this results in no returns messages but also doesn’t alter the audio at all.

Basically I want to modify the sound in place, and silence certain sections after my plugin analyzes it. The analyzing works fine, I can actually mark it up with labels but I can’t get it to return the sound. I can’t find any good tutorials or examples so I’d appreciate any advice.

The manual says:

Note> : > snd-fetch > modifies > sound> ; it is strongly recommended to copy sound using > snd-copy > and access only the copy with > snd-fetch> .

What the manual means by “modifies” is “destroys / messes up / corrupts / makes unusable”.

If you need “s” (the sound from Audacity), then don’t use snd-fetch on it. If you need to use snd-fetch, only use it on copies of the sound.

See also: “Return Values”

Well that’s a shame. I was hoping by modifies meant I was changing the referenced data. I have no problem copying it, but then how do I concatenate all the little bits I snd-fetch so I can return it? Is snd-from-array what I should be using, with ‘snd-t0 s’ for the t0 argument?

I’m assuming I was on the right track initially from what you said, and I’ll just be able to
(return s-modified)
once I have it all concatenated.

I’m not sure what exactly you are trying to do.

  1. Read a track or selection piece by piece
  2. Analyze that as I read it (basically doing a read-ahead)
  3. Silence certain parts of the track

I can do 1 & 2 currently, and can easily place labels using my script marking where the modifications need to happen, so I know the analysis/positions are right. I just can’t seem to find the right functions to modify the track (or build a replacement copy and return that, as I suggested in my previous post).

snd-fetch works perfectly for the size of data I need to be analyzing, but I don’t see any other similar put functions. I tried reassembling the fetched parts into an array and using snd-from-array, but i’m clearly doing something wrong as that returns a number.

Here is a code example/test. For simplicity this example modifies every bit of the sound, but that isn’t the case in the full script.
Currently it’s choking on snd-length, and I’m not sure why. Any help making this example work would be appreciated.

;nyquist plug-in
;version 1
;type process
;name "Remove Blips..."
;action "Removing Blips..."
;info "Modified version of other plugins like Silence Finder"
;control sil-lev "Silence threshold [ -dB]" real "" 26 0 100
;control noise-dur "Maximum blip duration [seconds]" real "" 0.1 0.1 5.0
;control sil-dur "Minimum duration of silence gap [seconds]" real "" 0.1 0.1 5.0
;control noise-lev "Real sounds must exceed this level [ -dB]" real "" 26 0 100

;Set the silence threshold level (convert it to a linear form)
(setq thres (db-to-linear (* -1 sil-lev)))

(setq n-placed 0)
(setq s-placed 0)

(setq noise-c 0)
(setq sil-c 0)
(setq lastmarkwassilence 0)
(setq lastmark 0)
(setq clearstarted 0)

(setq s2-count 1)
(setq s1-length 0)

(setq s1 (snd-copy s))
(setq s2 (snd-copy s))

;Store the sample rate of the sound
(setq s1-srate (snd-srate s1))

;Initialize the silence counter and the labels variable
(setq sil-c 0)

;Convert the silence duration in seconds to a length in samples
(setq sil-length (* sil-dur s1-srate))

(setq final-array (make-array snd-length(s1 999999)))

;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)

	(setf (aref final-array s1-length) (scale-db -20 v))
	(setq s1-length (+ s1-length 1))


(setq finalsound (snd-from-array (snd-t0 s1) s1-srate final-array))

Hopefully this will help.
I’ve put my initials [SD] after stuff that I’ve changed.
If you enable (uncomment) the “print” lines, only apply to very short selections (around 10 samples will be enough to show what is going on).

;nyquist plug-in
;version 1
;type process
;name "Remove Blips..."
;action "Removing Blips..."
;info "Modified version of other plugins like Silence Finder"
;control sil-lev "Silence threshold [ -dB]" real "" 26 0 100
;control noise-dur "Maximum blip duration [seconds]" real "" 0.1 0.1 5.0
;control sil-dur "Minimum duration of silence gap [seconds]" real "" 0.1 0.1 5.0
;control noise-lev "Real sounds must exceed this level [ -dB]" real "" 26 0 100

(setq thres 
  (db-to-linear (- sil-lev)))     ; silence threshold [SD]

(setq sil-length
  (* sil-dur *sound-srate*))      ; silence duration in samples [SD]

(setq s1 (snd-copy s))

(setq final-array
  (make-array (snd-length s1 999999)))  ; correct parentheses [SD]

;; Loop through samples with counter.
(do ((n 0 (1+ n))                      ; first array index is 0. Increment with '1+' [SD]
     (v (snd-fetch s1)(setq v (snd-fetch s1))))
    ; The number of samples in the selection is 'LEN'  [SD]
    ((= n (truncate len)))
  ; (print v) ; uncomment this line to print each value of v to debug window [SD]
  ; (print n) ; uncomment this line to print each value of n to debug window [SD]
  ; Set array element 'n' to scaled 'v' [SD]
  (setf (aref final-array n)
    (mult (db-to-linear -20) v))) ; 'scale' and 'scale-db' only work on sounds [SD]

; Evaluation of the last command is returned to Audacity [SD]
; (print final-array)  ;uncomment this line to print the array to debug window [SD]
(snd-from-array 0 *sound-srate* final-array)

Note that looping bit-wise in Nyquist is very slow.
Nyquist does not see absolute track times, so it cannot represent “white space” in an Audacity track. The closest equivalent is silence (zero amplitude samples. So, for example, if you have a sound “s” that you want to split in half and separate by a one second “gap” you would need to insert one second of silence into the middle, for example:

; duration of gap in seconds
(setq gap 2.3)

;duration of gap in local time
(setq gap (/ gap (get-duration 1)))

  (extract 0 0.5 s)
  (at (+ 0.5 gap) (cue (extract 0.5 1 s))))

If you just want to silence a section of a track you can multiply that section by zero, while multiplying the rest by 1. For example

(mult s (pwlv 1 0.5 1 0.5 0 0.7 0 0.7 1 1 1))

That does help, thanks for all the code comments.

For some reason the example still doesn’t actually work though. I was on 1.3 and tried upgrading to the most recent version but that didn’t fix it. (and the debug window is empty). Claims it didn’t return any audio, when running it on a selection, or on a full track. Tried both as a .ny file, and through the Nyquist Prompt.

Takes a while to run though, so it’s clearly doing something

Use Audacity 2.0.2
Some 1.3.x versions have bugs that are now fixed.

Which example? I’ve just checked the three code samples that I posted and they all work.

The first code sample must be run as a plug-in because the Nyquist Prompt does not support the “;control” settings.
If you are copy and pasting to a .NY file, ensure that extra spaces are not being added at the beginning of lines. All of the lines at the top of the file that start with semicolons should begin at the beginning of the line.

I’ve attached the exact ny script, and I ran some more tests - apparently it does occasionally work, but for the most part it doesn’t.

Here’s the sound file (was 1.1mb so it wouldn’t let me attach it): (hovering over the middle section gives a download link)
Additionally, here’s a video of my test so you can see the exact points it worked/didn’t work:

It works twice in the video, once at 1:30, again at 2:15 - I’m really not familiar enough to figure out the reason behind it working occasionally.
totallynew.ny (1.5 KB)

See this bit:
The selection length is 21 seconds - that’s a little less than 999999 samples at 44.1 kHz.
There’s a clue there…

Try selecting more than 999999 samples and it will fail.
If you select, say 23 seconds (1,014,300 samples), and click the debug button on the effect instead of the OK button, do you get an error message in the debug window?

That solves it, thanks. I didn’t realize 999999 samples translated to such a short audio period. Adding a couple more 9’s removes any errors from that particular test. I should be able to adapt my script properly now that I know what works and what errors to watch for - I appreciate all the help! :slight_smile:

You’re welcome.
Rather than just adding a couple of extra 9’s it would be better to handle the possibility of the sound having more samples than the plug-in can cope with. The easiest way to do that would be to have an “IF” statement such as:

(if (> len 10000000)
  (print "Error. Selection too large")
    ...program code ...
    ...program code ...))

One final issue I’ve run into. I now have a fully functional script thanks to your help - this is the one attached as “noprefill”. Obviously it’s a bit slow since it loops through every sample using snd-fetch (on a 44k file it takes about as long to process the file as it would to play the sound).

Now trying to optimize the design, I was looking at stripping out snd-fetch and replacing it with snd-fetch-array, only replacing the sounds I don’t want. This cuts down on the processing time quite a bit. This script is attached as blipfilter.ny

The problem I am having is that this optimized design seems to freeze audacity indefinitely if it’s given a chunk of audio longer than 30 seconds. Under 30 seconds it works speedily and correctly. Is there something about snd-fetch-array that would cause this, or some other design flaw I’ve created that I’m not seeing?

Thanks again
blipfilter.ny (7.2 KB)
blipfilter_fullyworkign_noprefill.ny (7.92 KB)

The array is too big.
There is a finite limit to how big an array can be before something in Nyquist overflows. I don’t know the internal details of this, but the limit is somewhere around 1 million elements.

The following code snippet creates an array “myarray” and sets each element to its index number.

(setq test 100000)
(setf myarray (make-array test))

(dotimes (count test)
  (setf (aref myarray count) count))

If you set “test” to much over 1 million Nyquist will freeze.

At 44.1 kHz, 30 seconds is around 1.3 million samples, and that is the size that you are making the array - too big.
The idea of snd-fetch-array is that you can collect a reasonable number of samples in one go, say a few thousand, process them, then grab the next block of samples and continue in like fashion until you get to the end of the sound.

BTW, you might like to have a look at this brief guide to LISP code formatting - it is a big help to other people that are trying to read your code (me :smiley:).
Also it is better to use spaces rather than tabs so that the layout will be the same regardless of the text editor.
I don’t know what text editor you are using, but if you’re on Windows, Notepad++ is pretty good (and free).

Odd. I can process about 30 minutes worth of array size using my slow script - so 30 seconds is a strange array restriction on snd-fetch-array (and also weird that it overflows rather than throwing an error).

Is there any way to quickly concatenate a bunch of 30 second arrays into one 30 minute array? (Faster than looping through and copying each array element, anyway)

Regarding the formatting:
Yeah it probably looks weird to lisp programmers, since I’m formatting as I would C++ or any other normally structured language. (open parentheses are lined up with the close, vertically) I’m actually not sure why people use spaces rather than tabs - most full featured editors let you set the width of tabs, so if you use them then everyone can read in whatever indentation is most legible for them.

Lisp has an excessive number of parentheses and without lining them up properly it’s pretty easy for me to forget one and spend 10 minutes trying to find it. (Actually most things about lisp don’t follow normal conventions it seems - I spent an hour chasing a problem yesterday because lisp didn’t increment my variable as it looped, it silently declared a new local one for that loop!)

I think that it is the looping within the snd-fetch-array that bombs out rather than the array size itself (in much the same way as my previous example). It’s not that you “can’t” have very large arrays, you can, but you need to be careful how you use them.
An alternative way of writing the previous array loop example could be:

(setq test 1000000)
(setf myarray (make-array test))

(do ((count 0 (1+ count)))
    ((>= count test) "done")
  (setf (aref myarray count) count))

Unlike the “dotimes” version, this does not freeze.

How about converting them to sounds, then concatenate the sounds?

If you are familiar with C++ you may get on better with SAL rather than Lisp. I’d not be able to help you with that though as I’ve never used the SAL syntax.

:smiley: As the second-oldest high-level programming language it could be argued that Lisp’s fully parenthesized Polish prefix notation is the “normal” one.

For Lisp programming, parentheses matching is a “must have” feature.
With “correct” indentation, seeing the structure of the code is pretty easy (though may take some practice). Some text editors have “indentation guides” that can help to see the vertical alignment. For example, in this screenshot from Scite text editor, it is immediately obvious that the “cond” block ends at line 12 and that it is within the “let” block (lines 6 to 13) within the function “nyq:delaycv” (lines 4 to 13). If you’re counting parentheses you’re not doing it right.

Well I could use snd-copy to duplicate the entire chunk in one hit, but the question is then how do i perform a replace-with-silence on certain segments of that sound? Is there a function that would let me replace/mute the volume on a chunk of sound from time X to time Y, without touching the rest of it?

The analysis part of my script obviously works pretty fast since it’s only using 100 samples per second, so it’s just the replacing/muting part that I’m trying to improve the speed on.

(setq silence-from 1.1) ; time in seconds
(setq silence-to 2.2)

;;; silence sound 'sig' from x to y
(defun silence (x y sig)
  (let ((end (snd-length sig ny:all)))
      (mult sig
        (control-srate-abs *sound-srate*
          (pwlv 1 x 1 x 0 y 0 y 1 end 1))))))

(multichan-expand #'silence silence-from silence-to s)