Note that bitwise processing in Nyquist is slow. It is generally a lot more efficient to work with sounds rather than samples.
There are various ways of accessing samples. You can read samples into an array and then access their values from the array (see Snd-fetch-array), or you can access samples as specified times with sref, or you can read samples from a sound object with snd-fromobject (not the easiest way, but often the quickest way for bit-wise processing)
I’m not sure what you mean by “express the sound”. If you have a number of small sounds you can simply add them together sequentially using “Sim” and “At”. If there are not too many you could use Seqrep. If you are working with individual samples you can load them into an array and then make a sound from the array with Snd-from-array.
It would be a lot easier to give relevant help if you were a bit more specific about what you are wanting to do.
Let’s make it simple… I’m having really dumb problems with just finding the right magic words for simple things.
I know I can just return this for instance to extract the latter half of the input sound. I gather the times in extract are relative to whatever length the input has.
(extract 0.5 1.0 (cue s))
So how do I concatenate that with another piece? I am falling down on this elementary thing. I can’t make seq do what I want. I am confused about sounds and behaviors. Do the wrong thing, and my parameterless experimental effect just does nothing. The following is not the right thing to transpose halves of the sound.
Yes this is complicated. I’ll explain as best I can, but this is complicated.
Yes the problem is all tied into the idea of warp, the “environment”, behaviours and sounds.
“Extract” works by changing the start and stop time of a sound, so in a simple example, if you have a sound with duration 10 seconds, and you set the (absolute) start time to 3 seconds and the stop time to 7 seconds, then the middle 4 seconds will play (as expected). The important thing here is that “extract” does not “edit” the sound, it just changes the start and stop times of the sound.
In your example, you are attempting to extract the second half of a sound and place it in front of the first half of the sound, but herein lies a problem:
We extract the second half of the sound by setting the start time half way through and the stop time at the end.
We extract the first half of the sound by setting the start time at zero and the stop time half way through.
The problem is that both are really still the same sound.
When we try to put them together, we offset the start time of the first half by half the duration, which we hope will also offset the stop time by the same amount (so that the total duration is unchanged. However, we have also set the stop time of the sound to the end of the sound - so now we have a contradiction of where the actual stop time lies.
The simplest way to resolve this is to re-set the stop time to where we want it after making the sequence - something like:
I’m not a programming virgin here… Am I supposed to think in functional or imperative programming terms? When you say “change” a sound do you really mean a destructive update to s or not?
I gathered from my impatient reading of just parts of the docs that snd-fetch is destructive but is also exceptional among the functions for manipulating sounds.
I also have this vague understanding that cue and at and the like are in some sense constructing deferred computations, which is what “behaviors” are, unlike computed arrays of samples which is what “sounds” are, yet the functions that operate on them can indifferently take sounds or behaviors, and my script can return either, but the results are not alike, in ways I haven’t yet figured out. Am I right or just muddling myself more?
Tried your code, it does what I described. Remove the outer “extract” and instead I get the latter half, followed by the entire clip. Then, replace the second “s” with (snd-copy s), and that does not fix the behavior.
I still don’t have the right mental model of what’s going on here or how to generalize to put arbitrary slices of s together. Could you spell out for me the sequence of evaluations? Is there a “behavior construction time” when extract, sim, at are evaluated, then a “call” to evaluate the behavior after it is returned?
I feel like there is some obvious intuition here that I am stupidly missing.
I’m probably not the best person to answer as I’ve had no formal training in programming.
Nyquist is based on LISP (XLISP) and I’d say that it is primarily used as a Functional programming language, though it does also have some Object orientated capability. One feature of Nyquist that can sometimes turn round and bite is that it uses lazy evaluation, though of course this also has benefits in terms of speed. Because of this it does not make a lot of sense to talk about the precise order of evaluation, because it is “on demand”.
Most of the primitives in Nyquist are written in C (highly optimised and largely computer generated). This is why working with sounds rather than samples is generally recommended, because then the inner loops run very much faster in the compiled C code than is possible in an (interpreted) LISP loop.
You are correct that most functions are non-destructive, and that snd-fetch is one of the exceptions (along with snd-fetch-array).
Just about everything in Nyquist are pointers. Even when dealing with multiple copies of a sound, in most cases they are just pointers to the same data.
A large part of the difficulty with warp and the environment is because Nyquist was developed as a standalone language which has been shoe-horned into Audacity. In the standalone version, if you set the start time of a sound for 10 minutes in the future, the sound does not start until that time. However, in Audacity the sound needs to be returned to the track, so it must be evaluated “out of time”. For the standalone version of Nyquist it is meaningless to consider time before the *start" time, because it doesn’t exist, but in Audacity we have a “frozen” timeline, so it is tempting to assume that we can refer to arbitrary points in time. Nyquist cannot back-track, time always goes forward for Nyquist. A further complication is that the precise details of how time are handled depend on the type of plug-in. For a “process” (effect) type plug-in, 1 second in “local” time is equivalent to the duration of the selection, whereas for a generate type plug-in, 1 second is equivalent to 1 second on the Audacity time line.
The silence and sound finer analyzers supplied with Audacity do use snd-fetch to scan sequentially, so if that is acceptable, perhaps it’s not that prohibitive for my use on few-second selections.
I still don’t understand why my wrong versions went wrong… but somehow I managed to write some code that does what I want! Check this out, it’s actually more general than I even need, the example permutes AND replicates pieces of the sound, but in practice I’ll just want to cut out some pieces. (Sorry I don’t know how to quote code properly)
Your code is somewhat overshooting, because it duplicates the time management, which is already integrated in the high level functions (I feel a certain C influence…).
That’s alright, I do it myself in order to keep the control over the procedure. However, I then usually work with the low level functions (but only exceptionally with ‘snd-fetch’) due to their slimness.
As stated in the documentation, when sequencing succeeds without “cue” or “sound”, it must be regarded as a bug, rather than a feature.
I seldom use cue, but it is necessary if you want to make use of things like ‘stretch’, ‘sustain’ and so on.
seq has its own evaluation routine. You can read the comments in the (lisp) source code, if you’re interested.
Yes, your [(rest (rest …))]
(or better ‘(nthcdr 2…)’ will be needed if you want to extract individual time pairs. My code just uses the start of the last call as end for the new one.
I guess this recursive method works because there will different lexical contexts be established, whereas in the first case all is handled in the same environment and causes therefore time problems (as Steve suggested).
I think there is a bug in SND-SEQ (which is used by SEQ and SEQREP).
The upshot is that SEQ and SEQREP can only see “S” if it is in the first argument. Occurrences of “S” in the second or subsequent arguments are seen as NIL.
Thus, this will work:
(seq s (osc 60))
and this works:
(setq s1 s)
(seq (osc 60) s1)
but this doesn’t work:
(seq (osc 60) s)
;; returns: "error: bad argument type - NIL"
I’ve written to Roger Dannenberg about this and will post here if I get a reply.
The “environment” affecting the evaluation of a behavior involves the “lexical environment” of the lisp code in which the behevior object was constructed? They are “closures” somehow? So you mean your code would actually NOT be equivalent to just writing the expansion (seq (extract …) (seq (extract…) (s-rest …))) out? That sounds really weird and if that is correct I am tremendously confused.