Compiled from various sources 1998-2001, by rasmus ekman.
This article describes the workings of Csound's three random generator opcode families: rand
, (bi)rnd()
and the various-distributions (x-noise) set.
Please be warned that the author is not well versed in mathematics, nor a native English speaker.
Randomness in nature, errors in the performance of a classical musician, arbitrariness of an improviser, and similarly most types of "noise" in physical events (such as thunder, background car traffic, an anthill or wheeze in a flute tone) cannot be represented directly in mathematics. It can at most be modelled by something more or less similar, to the best of our understanding of the phenomenon. The mathematical models will usually appear to us as microscopic components, or very wide generalizations of noise in the common sense. And since computer sound-making mostly relies on such mathematical models, this is what we use for building blocks.
For practical applications of mathematical models of systems with unpredictable parts, a limited notion of randomness is often used. This goes as follows.
This is enough to model measurements taken of a real world system that you can't predict, whether it is just not well understood, or it "really is truly random" (like eg decay times of radioactive material). The randomness functions used by Csound and most non-specialized programs can fulfill this limited randomness requirement, but as we'll see, they can not earn the title random in any other sense, so they are therefore often called "Pseudo-random number generators", or PRNGs for short.
We'll briefly explain the inner workings of what could be called "the basic PRNG".
This operates by taking any number N
, multiplying it by a (large) number M
, adding another (small or large) number A
to it, and finally taking this modulus a (large) number X
:
To generate the next number, the output of the previous number is used (indicated above by the subscripting i for the input number, and i+1 for the result). Provided that the numbers M
and A
are selected with extreme care, this will fulfil the limited pseudo-randomness requirement. There may be only one combination in many billion that actually pass various tests on randomness sported by careful scientists. X
is given implicitly by the bit-size of the digital numbers involved, but there are several variations using another modulus operation on the output.
The basic PRNG is primarily designed to be very simple and fast to compute. Quality has been completely subordinated to speed here. All randomness generators in Csound use variants of the basic PRNG, so this is not irrelevant to Csounders. We'll see how the limitations of this algorithm pops up in several disguises.
Now we turn to listing a few consequences of the just-related arrangement.
All these limitations are relevant to Csound, since the noise generators use four variants of the basic PRNG. For instance,
rand
opcode is such a case.
As explained above, a PRNG will step through a fixed sequence of numbers. The sequence can actually be considered as a fixed list of values, and the seeding can be viewed as selecting an entry point. This may be seem not very random at all, but it is usually of no consequence to mathematical systems.
Now, every PRNG needs to store its state: the last value it generated. This is used to generate the next number. The seed sets the initial state. A certain PRNG together with its state is called an instance of that PRNG. There can be several instances of the same PRNG: This is like having several people (or robots) reading from different places in the same list of numbers. Or there can be a single PRNG instance serving every opcode instance in the orchestra; each opcode in the orch takes turns getting numbers from the central generator.
Each instance of the rand
opcodes has its own private PRNG (we speak here similarly of opcode instances). The opcode pair rnd()/birnd()
uses a single central PRNG instance, while the whole x-noise family share a single instance of another PRNG.
This can lead to surprises:
rand
opcodes in the same orchestra are initied with the same number, they will output the same sequence. This of course holds when there are two separate rand
units in the same instrument, but also if there is only one unit in an instrument and this instrument is started several times from the score, playing simultaneous notes as in piano chords. To avoid this, there is aworkaround mentioned below.
rnd()/birnd()
and x-noise), seeding cannot be practically controlled by the user. Some people want to select a certain sequence of random numbers, but this only works with rand
. The rnd()
or x-noise sequences will be changed for every opcode instance whenever the number of opcodes from the same family is changed anywhere in the orchestra, or often if any change is made in the score.
We turn now to describing the behaviour of Csound's random generators.
Csound has three families of random opcodes; the ancient opcodes rand
/ randi
/randh
, the inline operators rnd()
and birnd()
, and the so-called x-noise or variable noise family. Each family of opcodes uses a variant of the basic PRNG, each variant with slightly different characteristics.
rand
, (bi)rnd()
and x-noise ugensThere are three sets of ugens for generating pseudo-random numbers in Csound. In the course of time they have been changed, and they all work slightly differently internally, so we will try to list the differences here. First a tabulated overview, details follow.
Family | rand/i/h |
bi/rnd() | x-distribution |
---|---|---|---|
Cycle length | 216 or 231 | ??? (very long) | sys dep [1] |
Precision [2] | 216 or 231 | 253 | 215 |
Share sequence | no | yes | yes |
Seedable | yes | no | globally |
Distribution | uniform | uniform | various |
Source code file | Ugens4.c/h |
Aops.c/h |
Cmath.c/h |
rand
familyxr
rand
xamp [, iseed] [, iuse31]
The original rand
opcodes use a not-very-good 16-bit PRNG. Some have worried that this has too short cycle length for audio-rate noise, so a newer 31-bit PRNG has been inserted, and is selectable by setting the iseed
flag. The newer version should be adequate for most uses, but there are issues with the seeding.
See Usage section for more about this.
rnd()
and birnd()
rnd(
xrange
)
birnd(
xrange
)
The inline operators rnd()
and birnd()
use a less common PRNG about which I haven't found any online references.
The basic PRNGs discussed up to now use integer numbers of 2 or 4 byte size internally. This was a good decision for speed until very recently. This PRNG variant uses so-called "double" data types. This uses 64 bits (or sometimes 80), ie twice as many bits as common 32-bit integers. Since bit size increase the maximum representable values exponentially, cycle length and precision of these operators are by far the best among Csound's random generators.
From inspecting the source code, barring any misunderstandings, they seem to have much higher precision, and much longer cycle than the basic PRNGs discussed above. In a very primitive raw cycle length test the code was run for 235 iterations without getting back to the seed number (that is only 16 times the length of the 31-bit versions of rand
, but it seems reasonable that the cycle is much longer).
Note that these operators are NOT seedable in any way.
Note also that the quality of the pseudo-random series output is unknown (to me...). It is probably (hopefully) not worse than many C-library versions around.
xr
uniform
krange xr
gauss
krange xr
linrand
krange xr
poisson
klambda xr
weibull
ksigma, ktau xr
betarand
krange, kalpha, kbeta
(See docs for full list)
The x-noise opcodes use the PRNG that happens to be supplied by the vendor of the compiler used to create a certain platform version of Csound. Thus different ones are used for the standard Linux, Mac, Windows versions, etc. If somebody builds Csound from source code at home, it may be different from the public version too.
The definition of this PRNG for ANSI C (the programming language Csound is written in) doesn't say what cycle length it should have, it is only is specified that the output should have 15-bit precision. The cycle length and other qualities of the PRNG varies between vendors, but historically many versions have apparently been rather bad. This perhaps needn't cause a lot of worry today: Modern compilers would be likely to use reasonable cycle length (eg 231).
The x-noise opcodes are intended for uses which would most often (in the imagination of this writer) not involve a-rate data, rather for high-level musical events like melodies and rhythms. Perhaps there are synthesis models where noise distribution is significant.Distribution is explained in the usage section.
Apart from the opcodes designed to generate random values, there are a couple of recent ones for pink noise, and some opcodes that use random values internally.
noise
This uses the same internal generator as the x-noise opcodes (the one supplied by compiler vendor). The noise is variably filtered to something like pink noise, so the resulting output does not necessarily cycle.
pinkish
The generator version of this opcode uses a 32-bit integer PRNG with cycle length and precision 232 internally. It is configurable by the user, and the random values are shifted to use 24 bits, then added up, so output precision normally goes up to 30 bits. Since random values are accumulated and added to the output the cycle length variation is not clear to this author, but it can be much longer than that of the internal PRNG. pinkish
is optionally seedable from system clock, or by a user value. Each opcode instance has its own PRNG sequence.
Some opcodes, including grain
and granule
use random values from the same compiler vendor PRNG as the x-noise ugens (and thus consume values from its internal cycle).
There are several common types of randomness and noise usage in computer music. This writer would recommend different Csound opcodes for different tasks:
rnd()/birnd()
with an a-rate xrange
argument (else it will not generate a-rate noise), or the 31-bit version of rand
. Also try out the white-pink variable noise
or the high-quality pinkish
opcode since pink is generally considered more pleasing to the ear than white noise.
randi
or randh
may be useful here, else use any random opcode.
rand
if repeatable variation is more important.
seed
1.0
" orchestra statement.
rand
(one can massage the values to the appropriate distribution, but you need the formulae, and it adds some orch code).
When an event is "repeatable" above, this means that the exact same sound (random sequence) is output every time the instrument performs a sound. As explained above, this is only practically possible when using the rand
opcode.
rand
FamilyEach instance of the rand
opcode (and siblings) use its own instance of the PRNG. The PRNG is seeded just before the instrument starts performing in the instrument initialization pass. This awakens a problem mentioned above:
If the rand
is intended to create some unexpected variation in the instrument, which should be different each time the instrument is run (or you use several instances of rand
in the same instrument), a direct number cannot be used for seed - this starts off the same pseudo-random sequence every time.
For this reason, the seed was specialized so that numbers >= 1.0 use the current system clock for seeding. This is a simple micro- or milli-second tick count, completely unrelated to Greenwich clock time. Let's call this a clock seed anyway.
This has two potentially problematic consequences:
rand
in the same instrument will get the same seed, and thus output the same sequence. This may not be the desired behaviour. A simple workaround is given below.
For those cases when you want repeatable performances of several rand
units, the seeding gets slightly complicated. To avoid getting the same sequence you would have to type all seeds by hand, which is tedious. This can be simplified by using a single seed value and adding a small amount to it:
iRandSeed init [provide seed value <0.9] ; init up to 100 rand opcodes if iRandSeed = 0.9 kval0 rand 0.5, iRandSeed kval1 rand 0.5, iRandSeed+.01 kval2 rand 0.5, iRandSeed+.02 ...etc
If you need repeatable different sequences in several instances of the same instrument, the seed has to be made global:
giRandSeed
init [provide seed value <0.9] instr 1
; init up to 1000 Rand opcodes if iRandSeed <= 0.9
kval0
rand
0.5,
giRandSeed
, 31 kval1
rand
0.5,
giRandSeed + .001
, 31 kval2
rand
0.5,
giRandSeed + .002
, 31
; Change giRandSeed every time the instrument inits so we get fresh seeds giRandSeed = giRandSeed + 0.1
if
giRandSeed <0.9 goto instrcode ; The seed is shifted slightly to avoid returning to first seed immediately giRandSeed = giRandSeed - 0.89991 instrcode: ; instrument performance code... endin
As stated earlier, with random numbers you cannot say what the next number will be, or the next thousandth, even if you record each number and try to find some pattern. But there are lots of things that can be said with statistics about (large) collections of random values.
Imagine some natural phenomenon, being measured to a heap of seemingly random values.
One of the simplest things is to add up all measured values, and divide this value by the number of values recorded. This yields the average value. Another trivial operation is to sort all the random values in the collection, then examine the middlemost one and so say what the median value is.
We get a more detailed view of the data by checking small ranges of values to see how often they come up: Whether there are more small values than large, or vice versa. This is exactly what distribution is about:
The x-noise ugens are designed to model different kinds of distributions.
Now uniform noise means that every value within the range is equally common. If the range is 0.0 up to 1.0, and you generate a huge number of values, you will get roughly as many values in the most extreme range above 0.995 as the number of values near 0.5, or below 0.005 .
With linearly distributed randoms, the frequency of generated values fall off linearly towards the extreme end. linrand
will thus output half as many values near 0.5 as values around 0.0, and even fewer values around 0.7 and still fewer at 0.995 and up. But still any value can pop up at any time; there is no way of guessing what comes next.
So, if you generate a soundfile with samples taken from rand
or any of the x-noise opcodes, the spectrum of the sound (which is the time-varying histogram of pitches in the sound, not the distribution of sample values), and equally the percieved output will be the same old white noise in every case.
Distribution of random values can be relevant when trying to create "random"-sounding rhythms and melodies. The various distributions are used to model different phenomena. For example, poisson
is specifically designed to model random variations to an average waiting time (eg of decaying nuclear material). This could perhaps be explored for rhythms.
For melodies, a different approach could be tried. Placing tones by plain uniform noise does not create any kind of melodic cohesion except accidentally. Brown noise (the term is based on molecular Brownian motion) might be interesting. This uses a random value to pick the distance to the next value, rather than the next value itself. This leads to it gradually and tenatively exploring the entire range, without jumping about too violently There is no Csound opcode to simulate this, one simply adds the next random value (not necessarily uniform noise) to the current value.
There are also two units with variable distribution; weibull
and betarand
. These might be the most interesting since they allow dynamic choice of distribution. weibull
can be used to generate exponential, gaussian and poisson-like distributions, while betarand
can do gaussian, uniform, or distribution favouring extreme values.
Numerical Recipes, chapter 12
- this gives a mathematical overview of PRNG's and distributions.
The Csound Book, ch 16: A Look at random Numbers, Noise and Chaos with Csound by John ffitch
- this article explains the mathematical concepts of noise, and discusses random distributions. It also gives graphs for the various distributions.
Colors of Noise Pseudo FAQ
- discusses pink and other noises.
Robin Whittle's page following a particular discussion about implementing pink noise.
The pinkish
opcode was designed based on material from this page.
Document date: Juli 3, 2001