## Csound random Generators

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 and Pseudo-Randomness

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.

To get a series of "random" numbers means:
You cannot predict what the next number will be.

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.

## Pseudo-Random Number Generators (PRNG's)

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`:

• Ni+1 = ((Ni * M) + A) % 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.

## Features of Common PRNG Variants

Now we turn to listing a few consequences of the just-related arrangement.

• The very first number used (N0) is called the seed. This can be set by the user, and determines the random sequence that is generated.
• There is a limited number of distinct values that can be generated, usually 232 or 231 (over 4 and 2 billion respectively), or just 216 or 215 (65536 and 32768) values. This might be called the precision of the PRNG.
• This means that as soon as the PRNG generates an earlier output value (eg the seed), it will restart from there, outputting the same sequence of numbers. The number of values generated is called the cycle length.
• The same seed to the same PRNG will lead to the same sequence of output values.
• The cycle length and the precision are distinct, and may vary separately.

All these limitations are relevant to Csound, since the noise generators use four variants of the basic PRNG. For instance,

• The shortest cycle PRNG sequences, of 32 or 65 thousand values, are not good for generating white-noise audio at higher sample rates. There the looping may be audible. The default `rand` opcode is such a case.
• The lowest precision PRNG's should perhaps not be used for some applications, eg noise dithering, since they output fewer values than supported by common sample formats.
• There are various methods of seeding, and in some cases there are problems with getting it to operate as desired. We'll return to this in the usage tips section.

### Shared and Separate Sequences

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:

• If different `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.
• When the same instance of the PRNG is used for every opcode instance (`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's Random Opcodes

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.

### Comparison of `rand`, `(bi)rnd()` and x-noise ugens

There 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`

Note 1: Dependent on C library used to compile your particular Csound version. Usually dependent on compiler. 231 length versions should be typical (or at least common) today, but do not rely on it - it could be an ancient 16-bit model!

Note 2: The precision concerns the internal generator. For standard Csound versions, which use 32-bit float data, no more than float precision (24 bits) is representable in orchestra variables. 64-bit versions of Csound will however get maximum precision from `rnd()` and `birnd()`.

## The `rand` family

• ``xr    ``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.

## Various Noise Distributions (or "x-noise") Family

````    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.

## Other Opcodes Using Random Numbers

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.

• Note: So-called "pink" noise is spectrally modified (often filtered) to have less high-frequency content (it falls off with -3 dB per octave), and thus sounds less sharp than the "white" noise generated by all other noise opcodes. See links below.

`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).

## Usage Tips

There are several common types of randomness and noise usage in computer music. This writer would recommend different Csound opcodes for different tasks:

• Plain white noise synthesis, either as component of a complex sound, or used directly for subtractive synthesis or digital processing etc:
Use `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.
• Small variations to synthesized sounds, to make them less static:
`randi` or `randh` may be useful here, else use any random opcode.
• Small variations to musical events (eg. pitch and time offset), to add a live or "human" touch:
Use an x-noise opcode to get the desired distribution, or `rand` if repeatable variation is more important.
• Generation of never-repeated series of music events (rhythms or melodies):
Use the appropriate x-noise distribution opcode with a "`seed`` 1.0`" orchestra statement.
• Generation of repeatable but arbitrary series of music events:
Use external scripting to generate score, or accept uniform noise and use `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.

### Seeding Problems in `rand` Family

Each 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:

• On some systems (eg MS Windows), the system clock ticks only every millisecond. This means that all or many instances of `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.
• The behaviour of the orchestra will be system dependent.

### Seeding Problem Workaround

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
``````

## What is Distribution?

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 distribution is the frequency count for each value in a set of random values.

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.

### Noise, Melodies and Rhythms

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.

## REFERENCES

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