Nyquist crash with snd-avg

I am still using 2.0.3. This is a bug in the library, for real this time.

Audacity crashes when snd-avg is given certain inputs and the resulting sound is evaluated.

The problem depends only on the lengths of the sounds involved, not the values of the samples.

Do this in the Nyquist prompt.

(setf srate 44100.0)

(defun make-sound (length)
  (let ((arr (make-array length)))
    (dotimes (ii length) (setf (aref arr ii) 0))
    (snd-from-array 0 srate arr)))

(let* ((nn 44100)
       (mm 650)
       (snd1 (make-sound nn))
       (snd2 (make-sound mm))
       (snd3 (convolve snd1 snd2))
       (snd4 (snd-xform snd3 srate 0 (/ (/ mm 2.0) srate) MAX-STOP-TIME 1.0)))
  (snd-avg snd4 440 220 OP-PEAK))

This is an abstraction of what I am trying to do. Certain combinations of values for mm and nn are poisonous, others not. It is not obvious what the pattern is yet, but I started running into this only when I made mm much larger than before.

Here is a simpler way to demonstrate the crash. As written it does not crash. Change the definition of nn as commented and it does crash, and apparently for any larger value.

Is it significant that this least value of nn that causes a crash is one half of the step size?

If I fix nn at 110 and vary mm, I get a crash for 550 (another multiple of half the step size! – and I presume for all all larger mm) but not for 549 (and I presume all smaller).

And notice this strangeness: If use-convolve is nil, there is no crash. Yet if snd3 is defined by convolve, there is a crash, even though it has been completely evaluated first by snd-flatten.

(setf srate 44100.0)

(defun make-sound (length)
  (let ((arr (make-array length)))
    (dotimes (ii length) (setf (aref arr ii) 0))
    (snd-from-array 0 srate arr)))

(setf use-convolve t)

(let* ((mm 650)
       (nn 109) ; crashes with (nn 110)
       (snd1 (make-sound nn))
       (snd2 (make-sound mm))
       (snd3 (if use-convolve
		 (convolve snd1 snd2)
		 (make-sound (+ nn mm))))
       (_ (print (snd-flatten snd3 ny:all))))
  (snd-avg snd3 440 220 OP-PEAK))

I explored the boundaries of the problem a little more.

(setf srate 44100.0)

(defun make-sound (length)
  (let ((arr (make-array length)))
    (dotimes (ii length) (setf (aref arr ii) 0))
    (snd-from-array 0 srate arr)))

(setf use-convolve t)

(let ((multiplier 5)
      (half-step 110))
  (let* ((mm (* multiplier half-step))
	 (nn (1- half-step)) ; (nn half-step)
	 (snd1 (make-sound nn))
	 (snd2 (make-sound mm))
	 (snd3 (if use-convolve
		   (convolve snd1 snd2)
		   (make-sound (+ nn mm))))
	 (_ (print (snd-flatten snd3 ny:all))))
    (snd-avg snd3 (* 4 half-step) (* 2 half-step) OP-PEAK)))

Changed half-step to many values from 1 to 110 and got no crashes.

Changed the definition of nn as commented, and it seems any positive value of half-step causes a crash, no matter how small.

Now take this version, and just vary multiplier. 5 is the least value causing a crash, but there is no crash with 6 or 8. I did try up to 11 and got crashes for 7, 9, 10, 11.

That’s enough for tonight.

(setf srate 44100.0)

(defun make-sound (length)
  (let ((arr (make-array length)))
    (dotimes (ii length) (setf (aref arr ii) 0))
    (snd-from-array 0 srate arr)))

(setf use-convolve t)

(let ((multiplier 5)
      (half-step 110))
  (let* ((mm (* multiplier half-step))
	 (nn half-step)
	 (snd1 (make-sound nn))
	 (snd2 (make-sound mm))
	 (snd3 (if use-convolve
		   (convolve snd1 snd2)
		   (make-sound (+ nn mm))))
	 (_ (print (snd-flatten snd3 ny:all))))
    (snd-avg snd3 (* (1- multiplier) half-step) (* 2 half-step) OP-PEAK)))

Do you mean “even” value?

You refer to the third of my four messages above. I said any positive value for the HALF-step, which means an even value of the step. I just reconfirmed that there is a crash with half-step of 109.

It’s an interesting bug (and yes, I do agree that it’s a bug :wink:)

Taking your first code example, if you remove the snd-avg command from the end:

(setf srate 44100.0)

(defun make-sound (length)
  (let ((arr (make-array length)))
    (dotimes (ii length) (setf (aref arr ii) 0))
    (snd-from-array 0 srate arr)))

(let* ((nn 44100)
       (mm 650)
       (snd1 (make-sound nn))
       (snd2 (make-sound mm))
       (snd3 (convolve snd1 snd2))
       (snd4 (snd-xform snd3 srate 0 (/ (/ mm 2.0) srate) MAX-STOP-TIME 1.0)))
  snd4)

then snd4 is returned to the track.
Then if you run snd-avg:

(snd-avg s 440 220 OP-PEAK)

it completes correctly with no error.

There should really be no difference between splitting the process in two like this and your original code, yet the original code reliably crashes.
My current guess is that convolve is doing something bad and that snd-avg is then stepping in it.

I think that to sort this one out will require running the code in a proper debugging environment (one for Rodger).

Something about the intermediate result is foiling snd-avg, for sure. Strangely it remains so even though snd-flatten is first done to the result of convolve.
This bug makes me unhappy enough that I will trywriting my own snd-avg using snd-fromobject

That workaround works!

(defmacro update-op (op x val)
  (let ((temp1 (gensym))
	(temp2 (gensym)))
    `(let ((,temp1 ,val)
	   (,temp2 ,x))
       (setf ,x (if ,temp2 (,op ,temp2 ,temp1) ,temp1)))))
(defmacro update-max (x val) `(update-op max ,x ,val))
(defmacro update-min (x val) `(update-op min ,x ,val))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; define a class that wraps a nullary closure as an object with :next method
(setf object-from-closure (send class :new '(closure)))
(send object-from-closure :answer :next '()
      '((funcall closure)))
(send object-from-closure :answer :isnew '(cl)
      '((setf closure cl)))

(defun make-object-from-closure (closure)
  (send object-from-closure :new closure))

;;; do what snd-avg does, only assuming the block size is a multiple of
;;; the step size, and operation is OP-PEAK.
(defun my-snd-avg (snd steps-per-block step-size)
  (let* ((t0 (snd-t0 snd))
	 (reduced-srate (/ (snd-srate snd) step-size))
	 (my-snd (snd-copy snd))
	 (arr (make-array steps-per-block))
	 (index (1- steps-per-block))
	 (count steps-per-block)
	 (f
	  #'(lambda ()
	      (prog1
		  (dotimes
		      (_ count (let (temp) 
				 (dotimes (jj steps-per-block temp)
				   (let ((val (aref arr jj)))
				     (when val (update-max temp val))))))
		    (let ((samples
			   (snd-fetch-array my-snd step-size step-size)))
		      (setf (aref arr index)
			    (and samples (let (temp)
					   (dotimes (jj step-size temp)
					     (update-max
					      temp (abs (aref samples jj))))))
			    index (if (zerop index)
				      (1- steps-per-block)
				      (1- index)))))
		(setf count 1)))))
    (snd-fromobject t0 reduced-srate (make-object-from-closure f))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(setf srate 44100.0)

(defun make-sound (length)
  (let ((arr (make-array length)))
    (dotimes (ii length) (setf (aref arr ii) 0))
    (snd-from-array 0 srate arr)))

(setf use-convolve t)
(setf crash nil)


(let ((multiplier 5)
      (half-step 110))
  (let* ((mm (* multiplier half-step))
	 (nn half-step)
	 (snd1 (make-sound nn))
	 (snd2 (make-sound mm))
	 (snd3 (if use-convolve
		   (convolve snd1 snd2)
		   (make-sound (+ nn mm))))
	 (_ (print (snd-flatten snd3 ny:all))))
    (if crash
	(snd-avg snd3 (* (1- multiplier) half-step) (* 2 half-step) OP-PEAK)
	(my-snd-avg snd3 (/ (1- multiplier) 2) (* 2 half-step)))))

Another detail worth mentioning. It appears, from the time it takes to crash, that something is wrong at the end of the sound returned from snd-avg. The crash happens later if the bad sound is longer. The progress indicator for my plug-in advances almost to the end before crashing.

What’s faster as a workaround is to call snd-xform with block size equal to step size and again for snd-xforms of the sound that remove a multiple of step-size samples. Then use snd-fromobject to interleave the samples of the several snd-avgs.

That fixes the trouble… sometimes.

Trying another workaround. Compute a convolution with a big window as a sum of convolutions with pieces of the window, shifting the summands appropriately. Should give identical results, but avoiding large windows if I use sufficient pieces.

It seems after all that large convolution windows are the source of the trouble, though only when snd-avg is applied, but then even if the convolution has been the input to sum, snd-xform, or snd-flatten.