Probability Distributions

# Probability Distribution Generators

Andreas Pfenning and Roger B. Dannenberg

This page describes some uses of probability distribution functions that are defined in `lib/distributions.lsp`, so be sure to `load "distributions"` before trying these examples. See the manual for more details.

A probability distribution generator outputs a single number based the particular distribution and the input parameters. These functions were created to supplement the uniform random generator `real-random`. There are two types of distributions: continuous and discrete. For either distribution, looking at the diagrams will show you the likely range of outputs. In a continuous distribution, the likelihood of returning a value between two points on the x-axis is equal to the area under the curve between those two points. Every continuous distribution has the property that the total area under the curve = 1. In a discrete distribution, only integers >= 0 are allowed as output. Each one of these numbers has a probability of being returned which is equal to the height of the bar in the diagram. For the user's convenience, many distributions have optional bounds that come in useful for certain applications.

### Example 1 – Varying time with a continuous distribution generator

One of the most common distributions found in algorithmic composition is known as the Poisson process. This consists of a sequence of notes or events separated by an amount of time determined by an exponential distribution. The resulting sequence of events has the following property: every point in time is equally likely to be that start of a note. Another property is that, the probability of an event happening in the next unit of time is completely independent of how long it has been since the previous event. These distributions are naturally occuring when events happen randomly without any coordination.

The following is just one example how to use the exponential distribution. The first input parameter, .25, spreads the distribution out so that higher values are more likely. The second input is optional and is an upper bound on the output.

Note: all examples are in SAL syntax followed by Lisp syntax in small type.

    play seqrep(i, 20,
                pluck(c4, 0.5 * exponential-dist(.25, 2.0)))

    (play (seqrep (i 20)
            (pluck c4 (* 0.5 (exponential-dist .25 2.0)))))

### Example 2 – Varying pitch with a discrete distribution generator

Discrete distribution generators are useful for changing pitch (to integer values). Constraining the output to a whole number means that every pitch is going to be in the chromatic scale. In the case of the binomial distribution, both parameters together determine the mean. One way to get feel for how the different parameters affect the distribution is to print values as you generate them. Distribution parameters may require some tweaking to produce a desired affect. In some cases, altering the parameters can yield unexpected and interesting sounds.

    function try-binomial()
      play seqrep(i, 20,
                  pluck(binomial-dist(6, .5) + c4), 0.1))

    (defun try-binomial ()
      (play (seqrep (i 20)
              (pluck (+ (binomial-dist 6 .5) c4) 0.1))))

### Example 3 – Using granular synthesis with pitch distribution

In granular synthesis, interesting sounds can be made by having a pitch vary according to some distribution other than the simple uniform distribution. In `lib/dist-test.lsp`, there is a a granular synthesis function that can take in any distribution (within the bounds of granular synthesis) and vary the pitch of the tone based on that distribution. To do this, you pass in a parameterless function as a parameter. Since most distribution functions take parameters, it is necessary to create a closure as shown below using a `lambda` expression. Strictly speaking, SAL does not support closures, but in this case, we can fool the compiler into creating a lambda expression by pretending `lambda` is a function. The SAL expression and the (more proper) Lisp expression are as follows:

    function make-gamma(nu, high)
      return lambda(nil, gamma-dist(nu, high))

    (defun make-gamma (nu high)
      (lambda () (gamma-dist nu high)))

Here, `make-gamma` takes two parameters and returns a function with no parameters. This no-parameter function remembers the values of `nu` and `high` and will call `gamma-dist` with these. For example, try

    print apply(make-gamma(2, 5.0), nil)

    (apply (make-gamma 2 5.0) nil)

(The `apply` function applies a function to a list of arguments; in this case the list is nil, so there are no arguments.) This example just makes the function and uses it once, so there's not much point, but in the granular synthesis example, the function will be called many times. Unlike passing a number as a parameter, passing a function means that the granular synthesis function can call the function for every grain, resulting in a new value for each grain. (Be sure to load `lib/dist-test.lsp` before running this example.)

    function try-pitch-dist()
      play simrep(i, 4,
                  pitch-dist-granulate(
                    "demos/demo-snd.aiff", 0.04, 0.0, 0.02, 0.001,
                    make-gamma(2, 5.0), 0, 0))) ~ 4

    (defun try-pitch-dist ()
      (play (stretch 4
              (simrep (i 4)
                (pitch-dist-granulate "demos/demo-snd.aiff" 0.04 0.0 0.02 0.001
                                      (make-gamma 2 5.0) 0 0)))))

### Example 4 – Using granular synthesis with grain length distribution

The length of grains can also be made to vary by a distribution passed in as a continuation. Here is an example.

    function make-gauss(xmu, sigma, low, high)
      return lambda(nil, gaussian-dist(xmu, sigma, low, high)

    function try-len-dist()
      play simrep(i, 2,
    	      len-dist-granulate("demos/demo-snd.aiff",
    	                         make-gauss(0.0, 1.0, 0.1, .5),
                                     0.02, 0.001, 2.0, 0, 0)) ~ 4

    (defun make-gauss (xmu sigma low high)
      (lambda ()(gaussian-dist xmu sigma low high)))

    (defun try-len-dist ()
      (play (stretch 4
              (simrep (i 2)
    	    (len-dist-granulate "demos/demo-snd.aiff"
    	      (make-gauss 0.0 1.0 0.1 .5) 0.02 0.001 2.0 0 0)))))

### A Challenge

Can you come up with a set of examples that clearly illustrates different distributions through sound? (I would love to add more examples here. -RBD)