Strange behavior

Hello to all

I am quite new to this forum and Nyquist. It is rather hard to dive into the whole matter. But you already know this yourself. I think, you all make a real good job, especially Steve and Edgar (which are sometimes downsampled by Gayle…)

Now for the first of the strange behaviors I encountered while making a few steps in writing some code. In Audacity, I am always working at a samplerate of 48000 Hz. Consider the following code for the NYQ-prompt:

(Defun print-srates ()
(display "" *sound-srate*)
(display "" *default-sound-srate*)
(display "" *control-srate*)
(display "" *default-control-srate*))
(print-srates)
(nyq:environment-init)
(print-srates)
(set-sound-srate 48000)
(set-control-srate 2400)
(print-srates)

The debug-window should print:

: SOUND-SRATE = 48000
: DEFAULT-SOUND-SRATE = 44100
: CONTROL-SRATE = 2205
: DEFAULT-CONTROL-SRATE = 2205
: SOUND-SRATE = 44100
: DEFAULT-SOUND-SRATE = 44100
: CONTROL-SRATE = 2205
: DEFAULT-CONTROL-SRATE = 2205
: SOUND-SRATE = 48000
: DEFAULT-SOUND-SRATE = 48000
: CONTROL-SRATE = 2400
: DEFAULT-CONTROL-SRATE = 2400

It seems to me, that Audacity manipulates the wrong global variable, i. e. the default-sound-srate should be set to the sample rate, provided by the input-sound , not the simple sound-srate. The D-S-S makes only sense in Nyquist, running in stand-alone-mode.
Obviously, the different Nyquist-functions do not always operate on the same (sound-)rate-base I will try to find an example), therefore the output may not be correct. Furthermore, the control-srate could also be set to match an other sample rate than 44100 Hz.
What do you think about this issue?

greetings from Switzerland
RJ

It is necessary to have access to the sample rate of the track audio. This is available as sound-srate. I presume that you agree that this is required?

As you say, the default-sound-srate is a Nyquist global. Unless change in the Nyquist code it will always be a known default value. There may be situations where this is useful (Edgar may have some thoughts on this if he catches this topic).

If required default-sound-srate may easily be changed to the same as the current track [sound-srate] within a plug-in by adding:

(set-sound-srate *sound-srate*)

Try this on a track with a 48 kHz sample rate:

(print *sound-srate*)
(print *default-sound-srate*)
(set-sound-srate *sound-srate*)
(print *sound-srate*)
(print *default-sound-srate*)

returns in the debug window:

48000
44100
48000
48000

I agree that it is odd that control-srate is not tied to sound-srate. It would seem more logical for control-srate to automatically be (/ sound-srate 20). Perhaps this was overlooked when shoehorning Nyquist into Audacity.

Thank you Steve for the fast reply.

All the functions in nyquist.lsp seem to use sound-srate (with the exeption of "abs-env, where default-sound-srate is mentioned in a block). Obviously, I made an mistake during my explorations.
The following code produced my mistake:

;(set-sound-srate *sound-srate*)
(defun detail (snd-in)
(format t "Start und Stopp ~a~%" (snd-extent snd-in ny:all))
(format t "Länge ~a, bei ~a Samplerate~% ~a~%" (snd-length snd-in ny:all) (snd-srate snd-in) (snd-samples snd-in   20)))
;;;Reverses a sound. only test,
;;;calculates 3 times longer than the sound plays...
(defun snd-reverse (s-in)
(Setq s-len (snd-length s-in ny:all))
(setq smp-len (recip (snd-srate s-in))) 
(seqrep (num s-len) (abs-env (const (snd-sref s-in  (* (- s-len (1+ num)) smp-len)) (recip  *control-srate*)))))
;;
(setf rmp (ramp (* 9 (recip *control-srate*))))
;(set-logical-stop rmp (* 10 (recip *control-srate*)))
(detail rmp)
(setf rmp2 (snd-reverse rmp))
(detail rmp2)
(setf rmp3 (seq (cue rmp) (cue rmp2)))
(detail rmp3)

My goal was to obtain a simple ramp that starts with 0 and ends with 10 values all in all. Depending on the selection (<> 1 sec), the output is not the expected one. If you remove the “;” before “(set-soun-srate…)”, the result is just fine. For this reason it is, that I wrongly assumed, the Frequency would cause the problem. Now, I think it is just another case of “warp/environment in NYQ and Audacity”-missmatch. As a matter of fact, “set-sound-srate” not only sets the default sr but also calls “nyQ:environment-init”. Thus, I guess the time parameters, given by Audacity, are newly initialized.
Another thing that is worth mentioning, are the values that are returned by “ramp”. Did you see, that the values “1” are added up? By setting a new logical stop (remove the “;”), this faulty behavior is cured. This fact does not become obvious when making a sequence, where the next sample is 0. Either, the logical Stop should be defined correctly, or the last value should be zero.
It would be fine, if this was mended in nyquist.lsp or (because this would take years…) Edgar could add a note on the web page listing the nyquist functions. I am ready, if any assistance is needed.

RJ

PS. Please excuse any typos and the bad code formatting. In the first place, I speak normally german and in the second place, I am blind, thus I do not care too much for caps and intending when writing code (for myself).

Sorry for the late response, but it took quite a while until I had the time to grep through the Audacity sources.

The result is that Robert is right and the Nyquist ABS-ENV macro produces wrong results if the Audacity track sample rate is not 44100 Hertz because SOUND-SRATE is bound to DEFAULT-SOUND-SRATE in the scope of ABS-ENV, where the Nyquist DEFAULT-SOUND-SRATE in Audacity is wrongly initialized with constantly 44100 Hertz instead to the value of the Audacity track sample rate.

Also the Nyquist CONTROL-SRATE and DEFAULT-CONTROL-SRATE variables are wrongly initialized with constantly 2205 Hertz instead to the value of the Audacity track sample rate dividied by twenty.

Also Robert is theoretically right that DEFAULT-SOUND-SRATE should be set to the Audacity project sample rate, but only theoretically, because Nyquist in Audacity is doomed to work within the scope of a single Audacity track, where Nyquist does not have the capability to change the Audacity track sample rate. This has to the consequence that a Nyquist sound object, returned by Nyquist with a sample rate different than the Audacity track sample rate, will be treatened wrong by the Audacity Nyquist interface.

Examples for known side-effects of mismatching sample rates:

[1] If a Nyquist envelope of e.g. 100 samples, computed by Nyquist with a sample rate of CONTROL-SRATE, is be directly returned to Audacity, then the envelope is inserted into the Audacity audio track as a sound of 100 samples with a sample rate of SOUND-SRATE, not CONTROL-SRATE.

[2] If the sound of an Audacity selection is resampled by a Nyquist function to a different sample rate, and the resampled sound is returned to Audacity, then the sound is inserted into the Audacity audio track with s rample rate of SOUND-SRATE, not with its new sample rate.

These side-effects come from Nyquist’s incapability to change the Audacity track sample rate. But fixing this is not as easy as it sounds because:

[1] The Audacity selection often is only a part of an audio track, and it’s very questionable if it would make sense to suddenly change the sample rate in the middle of an Audacity audio track, only because the returned Nyquist sound has a different sample rate.

[2] It’s also not a really clever idea to automatically resample a returned Nyquist sound to the Audacity track sample rate, because this would make the behaviour of the Audacity Nyquist interface even more complicated than it already is.

As long as Nyquist can only work within the scope of a single audio track it makes not much sense for Nyquist to know the Audacity project sample rate, because there is nothing Nyquist could do with this information. Or does anybody know a specific use-case for this? Maybe I haven’t realized where this information could be useful.

Things that can be done with a realistic chance to become true:

I will try to find a ways how to initialize the DEFAULT-SOUND-SRATE to the Audacity track sample rate, and also initialize CONTROL-SRATE and DEFAULT-CONTROL-SRATE to the correct values directly in the Audacity Nyquist interface. This is not too hard and has a reasonably low risk to introduce other unwanted side-effects.

Preliminary workarounds:

Setting all four variables to their correct values in a Nyquist plugin can be done by writing the following lines at the beginning of the plugin code:

(setf *default-sound-srate* *sound-srate*)
(setf *default-control-srate* (/ *sound-srate* 20.0))
(setf *control-srate* *default-control-srate*)

There is no need to call NYQ:ENVIRONMENT-INIT afterwards, because all other environment values do not depend on the values of these variables.

I have found no places other than ABS-ENV where DEFAULT-SOUND-SRATE is used, but we must take into account that ABS-ENV is called by other Nyquist functions like ENV and GATE, and also used by the Nyquist XMUSIC library.

  • edgar

I’ve not had chance to look thoroughly, but I don’t think that will make much practical difference will it?
I think that it would often be useful if ABS-ENV used the track sample rate.

Yes, that’s exactly what I wrote above. Currently ABS-ENV in Audacity always uses 44100 for SOUND-SRATE, what is wrong if the track sample rate is not 44100. It would be more useful if ABS-ENV would always use the Audacity track sample rate for SOUND-SRATE, as originally intended by Nyquist.

To achieve this it’s necessary to set the DEFAULT-SOUND-SRATE to the track sample rate, too, instead of constantly 44100 Hertz, because ABS-ENV temporarilly sets SOUND-SRATE to the value of DEFAULT-SOUND-SRATE, but DEFAULT-SOUND-SRATE has a wrong value.

The problem in Nyquist code:

(defmacro abs-env (args)
  (progv '(*sound-srate*) '(*default-sound-srate*)
    ;; here *sound-srate* always has a value of 44100 Hertz what is wrong
  ))

All sample rate computations inside ABS-ENV are currently wrong if the Audacity track has a sample rate other than 44100 Hertz. This effect is clearly based on a bug in the Audacity Nyquist interface, where DEFAULT-SOUND-SRATE is not set to the Audacity track sample rate.

  • edgar

To set all four Nyquist variables to their correct values the C code in “audacity/lib-src/libnyquist/nyx.c”, line 599 ff. must be canged as follows:

Original code:

   /* Bind the sample rate to the "*sound-srate*" global */
   flo = cvflonum(rate);
   setvalue(xlenter("*SOUND-SRATE*"), flo);

New code:

   /* Bind the sample rate to "*sound-srate*" globals */
   flo = cvflonum(rate);
   setvalue(xlenter("*DEFAULT-SOUND-SRATE*"), flo);
   setvalue(xlenter("*SOUND-SRATE*"), flo);

   /* Bind the control sample rate to "*control-srate*" globals */
   flo = cvflonum((double) rate / 20.0);
   setvalue(xlenter("*DEFAULT-CONTROL-SRATE*"), flo);
   setvalue(xlenter("*CONTROL-SRATE*"), flo);

Then recompile Audacity and run the following code from the Audacity Nyquist prompt:

(defun print-srates ()
  (format t "*default-sound-srate* = ~a~%"
            *default-sound-srate*)
  (format t "*sound-srate* = ~a~%"
            *sound-srate*)
  (format t "*default-control-srate* = ~a~%"
            *default-control-srate*)
  (format t "*control-srate* = ~a~%"
            *control-srate*))

(format t "GLOBAL values:~%")
(print-srates)

(format t "~%ABS-ENV values:~%")
(abs-env (print-srates))

With an Audacity track sample rate of 44100 Hertz the Debug window displays:

GLOBAL values:
*default-sound-srate* = 44100
*sound-srate* = 44100
*default-control-srate* = 2205
*control-srate* = 2205

ABS-ENV values:
*default-sound-srate* = 44100
*sound-srate* = 44100
*default-control-srate* = 2205
*control-srate* = 2205

With an Audacity track sample rate of 48000 Hertz the Debug window displays:

GLOBAL values:
*default-sound-srate* = 48000
*sound-srate* = 48000
*default-control-srate* = 2400
*control-srate* = 2400

ABS-ENV values:
*default-sound-srate* = 48000
*sound-srate* = 48000
*default-control-srate* = 2400
*control-srate* = 2400

All values in a comparison chart:

Track sample rate         44100       44100        48000          48000
GLOBAL values              old         new          old            new
*default-sound-srate*     44100 (ok)  44100 (ok)   44100 (wrong)  48000 (ok)
*sound-srate*             44100 (ok)  44100 (ok)   48000 (ok)     48000 (ok)
*default-control-srate*    2205 (ok)   2205 (ok)    2205 (wrong)   2400 (ok)
*control-srate*            2205 (ok)   2205 (ok)    2205 (wrong)   2400 (ok)

ABS-ENV values             old         new          old            new
*default-sound-srate*     44100 (ok)  44100 (ok)   44100 (wrong)  48000 (ok)
*sound-srate*             44100 (ok)  44100 (ok)   44100 (wrong)  48000 (ok)
*default-control-srate*    2205 (ok)   2205 (ok)    2205 (wrong)   2400 (ok)
*control-srate*            2205 (ok)   2205 (ok)    2205 (wrong)   2400 (ok)

I still need to test this again with a fresh Audacity SVN checkout, but everything looks good so far…

  • edgar

Hi Edgar
Thank you very much for your diligent work. It seems to be the perfect solution. It is a relief that I do not have to insert your mentioned code lines above in each and every plug-in that comes from “out there”. Abs-env is doubtless a very strong function as it resets so much parameters, but normally very useful when dealing with real times i.e. absolute time values.
Besides, what do you think about the second issue with the ramp-function? I restate the problem: when a ramp is defined such as

(ramp 10)

the returned sound is 1 sample longer than (10 x control-srate). If I merge this with a other envelope, such as const (with value 1), so are the two sounds properly joined in time, but the last sample is added at the beginning of the second env and a +6 Db-clipping is produced. Shouldn’t ramp reach its max value of 1 at time dur x control-srate (where the logical stop is set) and the following extra-sample be set to zero? It is of course not that tragical in every-day-practice, there are all the original pwl-functions for a workaround. It is simply a little flaw in Nyquist’s reliability when dealing with clipping dangers.
After all I am happy, that there is a lot going on in the development departement. The new Audacity-Revisions include nyquist-plug-ins in chain operations and the Nyquist-prompt can now be reached with one keystroke (if defined by the user)! A lot to test, a lot to learn…
Thx
RJ

I think that behaviour is correct as per the documentation: Nyquist Functions

Two examples to avoid the 1 sample peak value:

(mult 0.5 (seq
  (pwlv 0 2 1)
  (snd-const 1 2 *control-srate* 2)))
; 8820 samples



(mult 0.5 (seq
  (ramp 2)
  (snd-const 1 (+ 2 (/ *control-srate*)) *control-srate* 2)))
; 8821 samples



Currently only available in Windows alpha version, and may not make it into the next release unless cross-platform issues can be resolved.

So would that be classed as a “bug” (rather than a “feature”)?
If so then it needs to be added to bugzilla and treated as such (fixed).
If it’s classed as a “feature” then it would be properly addressed as a “feature request” to change it.
Personally I think it looks like a bug rather than a feature but I think you (Edgar) are better qualified to say.

I’ve applied your modification to revision 11769 and it looks fine.

Attached is the code change as a patch.

I’m assuming that you would class this issue as a bug rather than a feature, so I’ve submitted this bug report along with the patch: http://bugzilla.audacityteam.org/show_bug.cgi?id=520
default-srate.patch (712 Bytes)

#§$! - once again steve was faster than me…

Here some code to print the Nyquist environment variables, only to make sure that the Audacity Nyquist interface at startup behaves exactly the same as the NYQ:ENVIRONMENT-INIT function, used by the original Nyquist:

(defmacro assert (symbol &rest reference)
  `(if (equal ,symbol ,@reference)
       (format t "~a = ~a ; ok~%" ',symbol ,symbol)
       (format t "~a = ~a ; should be ~a~%" ',symbol ,symbol ,@reference)))

(defun print-env ()
  (assert *warp* '(0.0 1.0 nil))
  (assert *loud* 0.0)
  (assert *transpose* 0.0)
  (assert *sustain* 1.0)
  (assert *start* min-start-time)
  (assert *stop* max-stop-time)
  (assert *default-control-srate* (/ *sound-srate* 20.0))
  (assert *control-srate* (/ *sound-srate* 20.0))
  (assert *default-sound-srate* *sound-srate*)
  (assert *sound-srate* *sound-srate*))

(print-env)

Note that PRINT-ENV cannot detect wrong sample rate values inside ABS-ENV, because all sample rates are tested against SOUND-SRATE, what with the original bug has a wrong value inside ABS-ENV, so PRINT-ENV in this case prints “ok”, what is wrong (what a mess).

I have tested the code several hours in all variations and can find no bad side-effects to any other Nyquist environment variables.

Here is my patch againts Audacity Revision 11769 (SVN checkout from one hour ago), steve’s version misses the “s” of “globals” (what is not really important, because it’s inside a comment):
nyquist-globals.patch (805 Bytes)
@steve: yes, this is clearly an Audacity bug and must be reported as such. I have no bugzilla account (or can’t remeber to have one), so maybe you may please submit this patch? In case you already have submitted your version, just leave it as-is.

Thanks,

  • edgar

Hi Steve
thanks for spending your time with my “Ramp-trifle”. I am going to reuse your code from above to see, wether I got the hang of it.
First PWL:

(set-control-srate 10)
(snd-display
(seq
  (pwlv 0 2 1)
  (snd-const 0.8 2 *control-srate* 2)))

Note: Mult Ileft out, because it only cuts the amplitude in half. the const-value is 0.8 in order to see, where the transition takes place.
The problem of PWL - how it is mentioned in the documentation - becomes fairly obvious: the value of 1 is never reached.

Now the code for ramp:

(set-control-srate 10)
(snd-display
(seq
  (ramp 2)
  (snd-const 0.8 (+ 2 (/ *control-srate*)) *control-srate* 2))) )

That’s great, we now have the value of 1 (but where?.
You overcame the clipping danger in a elegant way. You took a low-level-function, where t0 can be defined and moved it by 1/control-srate. Of course, const was only a example for the sequence with ramp. The other functions, where your tactics can be applied, are:
(snd-from-array
(snd-fromarraystream
(snd-fromobject
(snd-const
(snd-white
(snd-zero
(snd-pwl
and all Table-Lookup Oscillator Functions (with prefix snd)
such as (snd-amosc…
But eventually, thats but cheating…

I don’t think so. Here is the Text:

(ramp [duration]) [LISP]
Returns a linear ramp from 0 to 1 over duration (default is 1). The function actually reaches 1 at duration, (footnote 1) and therefore has one extra sample, (footnote 2) making the total duration be duration + 1/*Control-srate* (footnote 3). See Figure 6 for more detail. Ramp is unaffected by the sustain transformation. The effect of time warping is to warp the starting and ending times only. The ramp itself is unwarped (linear). The sample rate is *control-srate*.

Figure 6: Ramps generated by pwl and ramp functions. The pwl version ramps toward the breakpoint (1, 1), but in order to ramp back to zero at breakpoint (1, 0), the function never reaches an amplitude of 1. If used at the beginning of a seq construct, the next sound will begin at time 1. 
The ramp version actually reaches breakpoint (1, 1) (footnote 4); notice that it is one sample longer than the pwl version (footnote 2). If used in a sequence, the next sound after ramp would start at time 1 + P, where P is the sample period (footnote 3).

My footnotes:
(1) Nope, it reaches 1 at Duration plus one sample.
(2) true
(3) thats the time samples are apart from one another.
(4) Nope, at point 1 we have a odd amplitude, in the example above, this would be 0.95.

I presume, Roger’s intentions regarding this function were:

  1. Ramp should reach amplitude 1 at Duration.
  2. The next sound should start at Duration plus 1 Sample.
  3. This sample should return to amplitude 0.

Wasn’t it such a tiny trifle, I surely would ask Roger himself, maybe in this way…
"Hello Mr Dannenberg
I really love your trumpet playing. Besides what were your intentions regarding the RAMP-function in Nyquist?

Quite a ridiculous thought, isn’t it.
Sorry that I am so finnicky, I’ll try to better myself.
Best regards
RJ

Roger is of course the only one who knows this exactly, but as far as I can tell Roger uses the functions from the introductionary chapters of the Nyquist manual to teach Nyquist to students who partially never have worked with audio processing or programming languages before. That’s why functions like RAMP are designed for simplicity, not for accuracy.

If you need high precision envelopes then there is no other way than to compute the envelopes with the full high resolution sample rate of SOUND-SRATE instead of CONTROL-SRATE, because otherwise you will have to fight lots of linear interpolation artifacts at the start and stop points, when envelopes are concatenated or applied to high resolution sounds.

The disadvantage of computing envelopes with SOUND-SRATE is that twenty times more samples need to be computed, what takes twenty times longer per envelope, but computing ebvelopes with SOUND-SRATE and using PWL and similar functions is the only way to achieve sample accuracy with Nyquist.

If I have time later on I will look at the specific RAMP problem, but it’s probably because RAMP is defined too simple.

  • edgar

:smiley: rofl.

That part is clearly wrong in Audacity, but I suspect that it is an implementation issue.

Having a look in nyquist.lsp

;; RAMP -- linear ramp from 0 to x
;;
(defun ramp (&optional (x 1))
  (let* ((duration (get-duration x)))
    (set-logical-stop
      (warp-abs nil
        (at *rslt*
          (sustain-abs 1
                       (pwl duration 1 (+ duration (/ *control-srate*))))))
      x)))

The sound that is returned to Audacity has duration “x” (default = 1) so there is no final “zero sample” returned to Audacity because that will be at time “X” + 1 sample.

key:
s = sample number
v = value

(warp-abs nil
  (sustain-abs 1
    (ramp (/ 10 *control-srate*))))

we get:

s=0 v=0
s=1 v=0.1
s=2 v=0.2
s=3 v=0.3
s=4 v=0.4
s=5 v=0.5
s=6 v=0.6
s=7 v=0.7
s=8 v=0.8
s=9 v=0.9
s=10 v=1.0

(warp-abs nil
  (sustain-abs 1
   (pwlv 0 (/ 10 *control-srate*) 1)))

we get:
s=0 v=0
s=1 v=0.1
s=2 v=0.2
s=3 v=0.3
s=4 v=0.4
s=5 v=0.5
s=6 v=0.6
s=7 v=0.7
s=8 v=0.8
s=9 v=0.9

Test with:

(setq p-out "")
(dotimes (sample (snd-length s ny:all))
  (setq p-out (format nil "~a~%s=~a v=~a" p-out sample (snd-fetch s))))
(print p-out)

Thank yu Steve
Eventually, you brought the solution to my mind. The answer to the problem is quite simple:

(defun ramp-new (&optional (x 1))
  (let* ((duration (get-duration x)))
    (set-logical-stop
      (warp-abs nil
        (at *rslt*
          (sustain-abs 1
                       (pwl duration 1 (+ duration (recip  *control-srate*))))))
      (+ x (recip *control-srate*)))))

(snd-display (abs-env (seq (ramp-new(/ 10 *control-srate*))(ramp-new(/ 10 *control-srate*)))))

I only changed the x in the last line of the original code to

...(+ x (recip *control-srate*))

that is where Set-logical-stop gets its time-value from.
Just let the code run (debug-mode) and you will see that we now have 22 samples, 11 for each ramp. Thats perfectly okay since we wanted a duration of 10 and there is always a sample more to “enclosure” a period of time. It even runs smoothly, when I change the original code in Nyquist.Lsp. Everybody is invited to test it.

Robert

PS. The recip-function could of course be exchanged by “/”. I tend to use this function because it always yields a Floatnum-result, even if the variable is a integer and i don’t like laboriously computed zeros…

Just a thought…

(abs-env 
  (sim
    (ramp (/ 10 *control-srate*))
    (at (/ 11 *control-srate*) (ramp(/ 10 *control-srate*)))))

Yes, that surely works. It all comes to this:
The logical stop in RAMP is wrongly defined. There are many solutions to the problem but only two approaches in general, either you change the stop-time of RAMP or you correct the start-time for the next sound. That is exactly what you did above.

@Edgar
I am not looking for high precision envelopes. My absolute favorite Nyquist-plug-in is Steve’s Text-envelope, which is simply a indispensable tool for people like me, even if it only knows linear Interpolation.
The hypothetic thought was rather this: you want to simulate the sound of a Buluchakanug-bird from Borneo. The wavetable you need for this is a Ramp from -1 to 1, joined by a Cosine-curve. If you used the original ramp-function, you’d receive a amplitude of 2 at the joint,because the logical stop is wrongly set. The poor bird seems to have a crack in his beak…
In reality, of course, I would take the Pwl-function, because the value of 1 is given by the cosine and hasn’t to be doubled. High precision for wavetables should not eat up to much resource-power since their length is limited to 1 million samples anyway.