Sound object in *SCRATCH*

[Topic split from: AGC compensation ]

Thanks Edgar, that gives a good clear picture.

I was surprised that this worked as I remember reading that sounds cannot be stored in scratch, but clearly they can if you know how.
So how does this work?

(progn (setf *scratch* (rms-envelope s)) s)

This executes the expressions (setf scratch (rms-envelope s)) and “S” and then returns “S”

The first of these expressions sets scratch to “(rms-envelope s)” which is a sound.

In a second run we can recall “(rms-envelope s)” because it persists in scratch.
So if we run:

(defun rms-envelope (snd)
  (if (arrayp s)
      (mult 0.5 (sum (rms (aref s 0) 20) (rms (aref s 1) 20)))
      (rms s 20)))

(progn (setf *scratch* (rms-envelope s)) s)

and then run

() *scratch*

[the parentheses are just to stop the Nyquist Prompt from complaining that it looks like SAL]
the rms envelope is returned.

However if we try the same thing with either of these snippets, it does not work - why not?

(defun rms-envelope (snd)
  (if (arrayp s)
      (mult 0.5 (sum (rms (aref s 0) 20) (rms (aref s 1) 20)))
      (rms s 20)))

(setf *scratch* (rms-envelope s))



(defun rms-envelope (snd)
  (if (arrayp s)
      (mult 0.5 (sum (rms (aref s 0) 20) (rms (aref s 1) 20)))
      (rms s 20)))

(progn (setf *scratch* (rms-envelope s)))

With me all three versions work, here is what happens.

Version 1 returns a sound in the Audacity track sample rate:

(progn
  (setf *scratch* (rms-envelope s))  ; 20Hz samplerate
  s)  ; *sound-srate*

This version first computes the 20Hz signal and stores it in SCRATCH. The s value is only returned to re-insert the original sound again it the Audacity track, because otherwise either “Nyquist did not return audio” or any other nonsense would happen like explained below.

Version 2 returns a sound with 20Hz samplerate:

(setf *scratch* (rms-envelope s))  ; 20Hz samplerate

Version 3 also returns a sound with 20Hz samplerate:

(progn
  (setf *scratch* (rms-envelope s)))  ; 20Hz samplerate

Version 2 and 3 return a sound with a samplerate of 20Hz, but the stupid Audacity Nyquist interface can’t recognize that the samplerate has changed, so it inserts the 20Hz sound as a sound with a samplerate of sound-srate back into the Audacity track. With me here this looks as if the complete sound has been deleted from the track, but if I zoom very hard into the beginning of the Audacity track, then I can see a sound of a few hundered samples. This is the returned 20Hz signal. Once again Audacity behaves idiotic.

  • edgar

Do not try to store huge sounds in SCRATCH, or at least SETF SCRATCH to NIL if the sound is no longer needed. Also a Nyquist “sound” is only a pointer to the first block of samples. I’m not sure what happens if the sound pointer in SCRATCH points to samples from an AU file in the Audacity project folder, that may have been modified or deleted by any other Audacity edit or effect operation.

Yes they return a sound when the code snippet is run, but only the first one appears to store the 20Hz sample rate envelope in scratch.

When the following is run in the Nyquist Prompt

() *scratch*

the envelope is returned if this code was used immediately before:

(progn
  (setf *scratch* (rms-envelope s))  ; 20Hz samplerate
  s)  ; *sound-srate*

but not if either of the other two were run immediately before.


If I run this code on a short mono sound:

(defun test (sig)
  (sum 0 sig))

(if (and (boundp '*scratch*)(soundp *scratch*))
  *scratch*
  (progn
    (setf *scratch* (test s))
    s))

Then it copies the sound to scratch and returns the original sound.
If I then run it on a selection in an empty audio track, it returns the sound from scratch
If I then run it again on a selection in an empty audio track it seems to return a null sound.

Does this have something to do with: “a Nyquist “sound” is only a pointer to the first block of samples”?
I don’t understand what is happening.

I still have no full and perfect explanation, but some partial things that really make sense.

Paul Graham: The reason why Lisp has no “pointer” data type ist that internally all Lisp objects are nothing but pointers.

Audacity Source Code Investigations

The last Nyquist Lisp value that is returned by the last evaluated Lisp expression in the Nyquist Prompt text field or in the plugin code is stored in a C variable. The C variable is then tested if the returned value points to a string, list (of labels), a number, or a sound object, and the appropriate action is taken by C code. After that the C variable is set to NULL (= Lisp NIL). Because the C variable contains a pointer to a Lisp object, as a side-effect the last value returned by Nyquist is set to NIL, too. In normal Nyquist plugins this has no further consequences, because the plugin code is already finished at this point.

If the last value returned by Nyquist is a a sound object, the Lisp type of the object is still a sound afterwards (SOUNDP returns true), but the pointer inside the sound object now points to NIL, and not to a block of samples anymore. Nyquist recognizes such a sound object as an “empty” sound. The standard use for a NIL pointer in a sound object is that if two sounds of different length are computed in parallel, then the sound, that is finished first, sets its sample-pointer to NIL, so the calling function knows that the “end of samples” of the sound object is reached and no unecessary samples will be produced.

All this in combination has some very strange side-effects with the SCRATCH variable:

If the last value returned by Nyquist is a pointer to the SCRATCH symbol, and the SYMBOL-VALUE of the SCRATCH symbol points to a sound object, then the SYMBOL-VALUE of the SCRATCH symbol wil become an empty sound object, because the pointer inside the sound object is set to NIL by the “nyx.c” code in the Audacity Nyquist interface.

Some examples what this means in practice:

Generate a one-second audio track with “Generate > Tone”, then from the Nyquist prompt:

(progn
  (setf *scratch* (snd-copy s))
  s)

This stores a copy of the pointer to S in SCRATCH, then it returns a pointer to S to the C code in the Audacity Nyquist interface… This has to the consequence that the pointer to S is set to NIL in the Audacity Nyquist interface, but the copy of the original pointer is still stored in SCRATCH.

Test if the sound in SCRATCH is a non-empty sound with the following code from the Nyquist prompt:

(print (snd-length *scratch* 100000))

This should return 44100 = 1 second of samples with a samplerate of 44.1kHz.

Create a one-second silent track with “Generate > Silence”, then from the Nyquist prompt, insert a copy of the sound in SCRATCH:

(snd-copy *scratch*)

This returns a copy of the pointer to SCRATCH, not a direct pointer to SCRATCH, so only the value of the copy of the pointer is set to NIL, the SYMBOL-VALUE of SCRATCH still contains the full sound:

(print (snd-length *scratch* 100000))  => 44100

Now do the same by returning a direct pointer to SCRATCH:

() *scratch*

Now the SYMBOL-VALUE of SCRATCH has become an empty sound object:

(print (snd-length *scratch* 100000))  => 0

In the case of the RMS-ENVELOPE examples from the text boxes above, the situation is one step more complicated:

(setf *scratch* (rms-envelope s))

Here the RMS-ENVELOPE signal is stored in SCRATCH, but the SETF macro returns a pointer to the SYMBOL-VALUE of its first argument, what in this case is a direct pointer to SCRATCH, so after this code has finished, the sound in SCRATCH will be set to an empty sound by the C code in the Audacity Nyquist interface.

(progn
  (setf *scratch* (rms-envelope s))
  s)

Here the RMS-ENVELOPE signal is stored in SCRATCH, and then a pointer to the S variable is returned, so the sound in S will be set to an empty sound by the C code in the Audacity Nyquist interface, but the sound stored in SCRATCH will survive.

All this is IMO not really good behaviour, but useful to know. Only the last value returned by Nyquist is set to NIL by the Audacity Nyquist interface. You can store sounds in SCRATCH and they will survive, as long as no pointer to SCRATCH is returned as the last value by Nyquist.

  • edgar

Thanks Edgar. I had to read that twice :wink: but it makes perfect sense. A very clear explanation of a rather muddy Audacity behaviour :stuck_out_tongue:

I notice that even an empty sound object still has a sample rate, start time and stop time just like any other sound.
(snd-srate scratch) => 44100
(snd-extent scratch) => (0 0)
(snd-fetch scratch*) => NIL
(snd-length scratch 100000) => 0

I also notice that if we
(setf scratch (snd-copy s))
but then return some other sound,
the scratch still has a sound of the correct duration, but all of the sample values are zero.

(I’ve also managed to crash Audacity a few times while playing with this).


I think you’ll like this snippet:

  • Take a short mono audio track (music or just noise)
  • Create an empty mono track below it.
  • Select both track and apply the following code


(if (and (boundp '*scratch*)
         (soundp *scratch*)
         (= (snd-length *scratch* 1000000) len))
  (lp (snd-copy *scratch*) 1000)
  (progn
    (setf *scratch* (snd-copy s))
    (hp s 1000)))

I have tested the SCRATCH variable now with all possible return values (integers, floats, strings, label-lists, and sounds) and the effect described above seems only to happen with sounds, but with no other return value.

For example, first I run from the Nyquist prompt:

(setf *scratch* "Hello!")

This stores the string “Hello!” in the SCRATCH variable, and a text window with “Hello!” appears.

Now from the Nyquist prompt I return a pointer to the SYMBOL-VALUE of the SCRATCH symbol:

() *scratch*

Again a text window with “Hello!” appears, but this time the SYMBOL-VALUE of SCRATCH is not set to NIL by the C code in the Audacity Nyquist interface. I can repeat this as often as I want, until SCRATCH is set to something different by some Nyquist code.

Apparently only if a sound object is stored in the SCRATCH variable, then the sound is “emptied” by the C code in the Audacity Nyquist interface if a pointer to SCRATCH is returned by the Nyquist code.

  • edgar