myTestlll-1.0.0: HSoM/RandomMusic.lhs
%-*- mode: Latex; abbrev-mode: true; auto-fill-function: do-auto-fill -*-
%include lhs2TeX.fmt
%include myFormat.fmt
\out{
\begin{code}
-- This code was automatically generated by lhs2tex --code, from the file
-- HSoM/RandomMusic.lhs. (See HSoM/MakeCode.bat.)
\end{code}
}
\chapter[Random Numbers ... and Markov Chains]
{Random Numbers, Probability Distributions, and Markov Chains}
\label{ch:random}
\begin{code}
module Euterpea.Examples.RandomMusic where
import Euterpea
import System.Random
import System.Random.Distributions
import qualified Data.MarkovChain as M
\end{code}
The use of randomness in composition can be justified by the somewhat
random, exploratory nature of the creative mind, and indeed it has
been used in computer music composition for many years. In this
chapter we will explore several sources of random numbers and how to
use them in generating simple melodies. With this foundation you will
hopefully be able to use randomness in more sophisticated ways in your
compositions. Music relying at least to some degree on randomness is
said to be \emph{stochastic}, or \emph{aleatoric}.
\section{Random Numbers}
\label{sec:random}
This section describes the basic functionality of Haskell's
|System.Random| module, which is a library for random numbers. The
library presents a fairly abstract interface that is structured in two
layers of type classes: one that captures the notion of a \emph{random
generator}, and one for using a random generator to create
\emph{random sequences}.
We can create a random number generator using the built-in |mkStdGen|
function:
\begin{spec}
mkStdGen :: Int -> StdGen
\end{spec}
which takes an |Int| seed as argument, and returns a ``standard
generator'' of type |StdGen|. For example, we can define:
\begin{code}
sGen :: StdGen
sGen = mkStdGen 42
\end{code}
We will use this single random generator quite extensively in the
remainder of this chapter.
|StdGen| is an instance of |Show|, and thus its values can be
printed---but they appear in a rather strange way, basically as two
integers. Try typing |sGen| to the GHCi prompt.
More importantly, |StdGen| is an instance of the |RandomGen| class:
\begin{spec}
class RandomGen g where
genRange :: g -> (Int, Int)
next :: g -> (Int, g)
split :: g -> (g, g)
\end{spec}
The reason that |Int|s are used here is that essentially all
pseudo-random number generator algorithms are based on a
fixed-precision binary number, such as |Int|. We will see later how
this can be coerced into other number types.
For now, try applying the operators in the above class to the |sGen|
value above. The |next| function is particularly important, as it
generates the next random number in a sequence as well as a new random
number generator, which in turn can be used to generate the next
number, and so on. It should be clear that we can then create an
infinite list of random |Int|s like this:
\begin{code}
randInts :: StdGen -> [Int]
randInts g = let (x,g') = next g
in x : randInts g'
\end{code}
Look at the value |take 10 (randInts sGen)| to see a sample output.
To support other number types, the |Random| library defines this type
class:
\begin{spec}
class Random a where
randomR :: RandomGen g => (a, a) -> g -> (a, g)
random :: RandomGen g => g -> (a, g)
randomRs :: RandomGen g => (a, a) -> g -> [a]
randoms :: RandomGen g => g -> [a]
randomRIO :: (a,a) -> IO a
randomIO :: IO a
\end{spec}
Built-in instances of |Random| are provided for |Int|, |Integer|,
|Float|, |Double|, |Bool|, and |Char|.
The set of operators in the |Random| class is rather daunting, so
let's focus on just one of them for now, namely the third one,
|RandomRs|, which is also perhaps the most useful one. This function
takes a random number generator (such as |sGen|), along with a range
of values, and generates an infinite list of random numbers within the
given range (the pair representing the range is treated as a closed
interval). Here are several examples of this idea:
\begin{code}
randFloats :: [Float]
randFloats = randomRs (-1,1) sGen
randIntegers :: [Integer]
randIntegers = randomRs (0,100) sGen
randString :: String
randString = randomRs ('a','z') sGen
\end{code}
Recall that a string is a list of characters, so we choose here to use
the name |randString| for our infinite list of characters. If you
believe the story about a monkey typing a novel, then you might
believe that |randString| contains something interesting to read.
So far we have used a seed to initialize our random number generators,
and this is good in the sense that it allows us to generate
repeatable, and therefore more easily testable, results. If instead
you prefer a non-repeatable result, in which you can think of the seed
as being the time of day when the program is executed, then you need
to use a function that is in the IO monad. The last two operators in
the |Random| class serve this purpose. For example, consider:
\begin{code}
randIO :: IO Float
randIO = randomRIO (0,1)
\end{code}
If you repeatedly type |randIO| at the GHCi prompt, it will return a
different random number every time. This is clearly not purely
``functional,'' and is why it is in the IO monad. As another example:
\begin{code}
randIO' :: IO ()
randIO' = do r1 <- randomRIO (0,1) :: IO Float
r2 <- randomRIO (0,1) :: IO Float
print (r1 == r2)
\end{code}
will almost always return |False|, because the chance of two randomly
generated floating point numbers being the same is exceedingly small.
(The type signature is needed
to ensure that the value generated has an unambigous type.)
\syn{|print :: Show a => a -> IO ()| converts any showable value into
a string, and displays the result in the standard output area.}
\section{Probability Distributions}
The random number generators described in the previous section are
assumed to be \emph{uniform}, meaning that the probability of
generating a number within a given interval is the same everywhere in
the range of the generator. For example, in the case of |Float| (that
purportedly represents \emph{continuous} real numbers), suppose we are
generating numbers in the range $0$ to $10$. Then we would expect the
probability of a number appearing in the range $2.3$-$2.4$ to be the
same as the probability of a number appearing in the range
$7.6$-$7.7$, namely $0.01$, or $1\%$ (i.e.\ $0.1/10$). In the case of
|Int| (a \emph{discrete} or \emph{integral} number type), we would
expect the probability of generating a 5 to be the same as generating
an 8. In both cases, we say that we have a \emph{uniform
distribution}.
But we don't always want a uniform distribution. In generating music,
in fact, it's often the case that we want some kind of a non-uniform
distribution. Mathematically, the best way to describe a distribution
is by plotting how the probability changes over the range of values
that it produces. In the case of continuous numbers, this is called
the \emph{probability density function}, which has the property that
its integral over the full range of values is equal to $1$.
The |System.Random.Distributions| library provides a number of
different probability distributions, which are described below.
Figure \ref{fig:distributions} shows the probability density functions
for each of othem.
\begin{figure}
\centering
\subfigure[Linear]{
\includegraphics[scale=0.9]{Pics/linear.eps}
}
\subfigure[Exponential]{
\includegraphics[scale=0.9]{Pics/exponential.eps}
}
\subfigure[Bilateral exponential]{
\includegraphics[scale=0.9]{Pics/bilexp.eps}
}
\subfigure[Gaussian]{
\includegraphics[scale=0.9]{Pics/gaussian.eps}
}
\subfigure[Cauchy]{
\includegraphics[scale=0.9]{Pics/cauchy.eps}
}
\subfigure[Poisson]{
\includegraphics[scale=0.9]{Pics/poisson.eps}
}
\caption{Various Probability Density Functions}
\label{fig:distributions}
\end{figure}
Here is a list and brief description of each random number generator:
\begin{description}
\item[linear] Generates a \emph{linearly} distributed random variable
between 0 and 1. The probability density function is given by:
\[ f(x) = \left\{ \begin{array}{ll}
2(1-x) & \mbox{if $0 \leq x \leq 1$} \\
0 & \mbox{otherwise}
\end{array}
\right.
\]
The type signature is:
\begin{spec}
linear :: (RandomGen g, Floating a, Random a, Ord a) =>
g -> (a,g)
\end{spec}
The mean value of the linear distribution is $1/3$.
\item[exponential] Generates an \emph{exponentially} distributed
random variable given a spread parameter $\lambda$. A larger spread
increases the probability of generating a small number. The mean of
the distribution is $1/\lambda$. The range of the generated
number is conceptually $0$ to $\infty$, although the chance of
getting a very large number is very small. The probability density
function is given by:
\[ f(x) = \lambda e^{-\lambda x} \]
The type signature is:
\begin{spec}
exponential :: (RandomGen g, Floating a, Random a) =>
a -> g -> (a,g)
\end{spec}
The first argument is the parameter $\lambda$.
\item[bilateral exponential] Generates a random number with a
\emph{bilateral exponential} distribution. It is similar to
exponential,
but the mean of the distribution is 0 and 50\% of the results fall
between $-1/\lambda$ and $1/\lambda$. The probability density
function is given by:
\[ f(x) = \frac{1}{2}\lambda e^{-\lambda ||x||} \]
The type signature is:
\begin{spec}
bilExp :: (Floating a, Ord a, Random a, RandomGen g) =>
a -> g -> (a,g)
\end{spec}
\item[Gaussian] Generates a random number with a \emph{Gaussian}, also
called \emph{normal}, distribution, given mathematically by:
\[ f(x) = \frac{1}{\sigma\sqrt{2\pi}}
e^{-\frac{(x-\mu)^2}{2\sigma^2}}
\]
where $\sigma$ is the \emph{standard deviation}, and $\mu$ is the
\emph{mean}. The type signature is:
\begin{spec}
gaussian :: (Floating a, Random a, RandomGen g) =>
a -> a -> g -> (a,g)
\end{spec}
The first argument is the standard deviation $\sigma$ and the second
is the mean $\mu$. Probabilistically, about 68.27\% of the numbers in
a Gaussian distribution fall within $\pm\sigma$ of the mean; about
$95.45\%$ are within $\pm 2\sigma$, and $99.73\%$ are within
$\pm 3\sigma$.
\item[Cauchy] Generates a \emph{Cauchy}-distributed random variable.
The distribution is symmetric with a mean of 0. The density
function is given by:
\[ f(x) = \frac{\alpha}{\pi(\alpha^2 + x^2)} \]
As with the Gaussian distribution, it is unbounded both above and
below the mean, but at its extremes it approaches 0 more slowly than
the Gaussian. The type signature is:
\begin{spec}
cauchy :: (Floating a, Random a, RandomGen g) =>
a -> g -> (a,g)
\end{spec}
The first argument corresponds to $\alpha$ above, and is called the
\emph{density}.
\item[Poisson] Generates a \emph{Poisson}-distributed random variable.
The Poisson distribution is discrete, and generates only
non-negative numbers. $\lambda$ is the mean of the distribution.
If $\lambda$ is an integer, the probability that the result is
$j = \lambda-1$ is the same as that of $j = \lambda$. The
probability of generating the number $j$ is given by:
\[ P\{X=j\} = \frac{\lambda^j}{j!} e^{-\lambda} \]
The type signature is:
\begin{spec}
poisson :: ( Num t, Ord a, Floating a, Random a
RandomGen g ) =>
a -> g -> (t, g)
\end{spec}
\item[Custom] Sometimes it is useful to define one's own discrete
probability distribution function, and to generate random numbers
based on it. The function |frequency| does this---given a list of
weight-value pairs, it generates a value randomly picked from the
list, weighting the probability of choosing each value by the given
weight.
\begin{spec}
frequency :: (Floating w, Ord w, Random w, RandomGen g) =>
[(w, a)] -> g -> (a,g)
\end{spec}
\end{description}
\subsection{Random Melodies and Random Walks}
Note that each of the non-uniform distribution random number
generators described in the last section takes zero or more parameters
as arguments, along with a uniform random number generator, and
returns a pair consisting of the next random number and a new
generator. In other words, the tail end of each type signature has
the form:
\begin{spec}
... -> g -> (a,g)
\end{spec}
where |g| is the type of the random number generator, and |a| is the
type of the next value generated.
Given such a function, we can generate an infinite sequence of random
numbers with the given distribution in a way similar to what we did
earlier for |randInts|. In fact the following function is defined in
the |Distributions| library to make this easy:
\begin{spec}
rands :: (RandomGen g, Random a) =>
(g -> (a,g)) -> g -> [a]
rands f g = x : rands f g' where (x,g') = f g
\end{spec}
Let's work through a few musical examples. One thing we will need to
do is convert a floating point number to an absolute pitch:
\begin{code}
toAbsP1 :: Float -> AbsPitch
toAbsP1 x = round (40*x + 30)
\end{code}
This function converts a number in the range $0$ to $1$ into an
absolute pitch in the range $30$ to $70$.
And as we have often done, we will also need to convert an absolute
pitch into a note, and a sequence of absolute pitches into a melody:
\begin{code}
mkNote1 :: AbsPitch -> Music Pitch
mkNote1 = note tn . pitch
mkLine1 :: [AbsPitch] -> Music Pitch
mkLine1 rands = line (take 32 (map mkNote1 rands))
\end{code}
With these functions in hand, we can now generate sequences of random
numbers with a variety of distributions, and convert each of them into
a melody. For example:
\pagebreak
\begin{code}
-- uniform distribution
m1 :: Music Pitch
m1 = mkLine1 (randomRs (30,70) sGen)
-- linear distribution
m2 :: Music Pitch
m2 = let rs1 = rands linear sGen
in mkLine1 (map toAbsP1 rs1)
-- exponential distribution
m3 :: Float -> Music Pitch
m3 lam = let rs1 = rands (exponential lam) sGen
in mkLine1 (map toAbsP1 rs1)
-- Gaussian distribution
m4 :: Float -> Float -> Music Pitch
m4 sig mu = let rs1 = rands (gaussian sig mu) sGen
in mkLine1 (map toAbsP1 rs1)
\end{code}
\vspace{.1in}\hrule
\begin{exercise}{\em
Try playing each of the above melodies, and listen to the musical
differences. For |lam|, try values of $0.1$, $1$, $5$, and $10$. For
|mu|, a value of $0.5$ will put the melody in the central part of the
scale range---then try values of $0.01$, $0.05$, and $0.1$ for |sig|.}
\end{exercise}
\begin{exercise}{\em
Do the following:
\begin{itemize}
\item
Try using some of the other probability distributions to generate a
melody.
\item
Instead of using a chromatic scale, try using a diatonic or pentatonic
scale.
\item
Try using randomness to control parameters other than pitch---in
particular, duration and/or volume.
\end{itemize}
}
\end{exercise}
\vspace{.1in}\hrule
\vspace{.1in}
Another approach to generating a melody is sometimes called a
\emph{random walk}. The idea is to start on a particular note, and
treat the sequence of random numbers as \emph{intervals}, rather than
as pitches. To prevent the melody from wandering too far from the
starting pitch, one should use a probability distribution whose mean
is zero. This comes for free with something like the bilateral
exponential, and is easily obtained with a distribution that takes the
mean as a parameter (such as the Gaussian), but is also easily
achieved for other distributions by simply subtracting the mean. To
see these two situations, here are random melodic walks using first
a Gaussian and then an exponential distribution:
\begin{code}
-- Gaussian distribution with mean set to 0
m5 :: Float -> Music Pitch
m5 sig = let rs1 = rands (gaussian sig 0) sGen
in mkLine2 50 (map toAbsP2 rs1)
-- exponential distribution with mean adjusted to 0
m6 :: Float -> Music Pitch
m6 lam = let rs1 = rands (exponential lam) sGen
in mkLine2 50 (map (toAbsP2 . subtract (1/lam)) rs1)
toAbsP2 :: Float -> AbsPitch
toAbsP2 x = round (5*x)
mkLine2 :: AbsPitch -> [AbsPitch] -> Music Pitch
mkLine2 start rands =
line (take 64 (map mkNote1 (scanl (+) start rands)))
\end{code}
Note that |toAbsP2| does something reasonable to interpret a
floating-point number as an interval, and |mkLine2| uses |scanl| to
generate a ``running sum'' that represents the melody line.
\out{
-- Test code to see how accurate the mean is:
\begin{code}
m2' = let rs1 = rands linear sGen
in sum (take 1000 rs1) / 1000 :: Float
m5' sig = let rs1 = rands (gaussian sig 0) sGen
in sum (take 1000 rs1)
m6' lam = let rs1 = rands (exponential lam) sGen
rs2 = map (subtract (1/lam)) rs1
in sum (take 1000 rs2)
\end{code}
}
%% \begin{exercise}{\em
%% Instead of ...}
%% \end{exercise}
\section{Markov Chains}
Each number in the random number sequences that we have described thus
far is \emph{independent} of any previous values in the sequence.
This is like flipping a coin---each flip has a 50\% chance of being
heads or tails, i.e.\ it is independent of any previous flips, even if
the last ten flips were all heads.
Sometimes, however, we would like the probability of a new choice to
depend upon some number of previous choices. This is called a
\emph{conditional probability}. In a discrete system, if we look only
at the previous value to help determine the next value, then these
conditional probabilities can be conveniently represented in a matrix.
For example, if we are choosing between the pitches $C$, $D$, $E$, and
$F$, then Table \ref{fig:markov-table} might represent the conditional
probabilities of each possible outcome. The previous pitch is found
in the left column---thus note that the sum of each row is $1.0$. So,
for example, the probability of choosing a $D$ given that the previous
pitch was an $E$ is $0.6$, and the probability of an $F$ occurring
twice in succession is $0.2$. The resulting stochastic system is
called a \emph{Markov Chain}.
\begin{table}
\begin{center}
\begin{tabular}{||l||l||l||l||l||} \hline
& |C| & |D| & |E| & |F| \\ \hline
|C| & 0.4 & 0.2 & 0.2 & 0.2 \\ \hline
|D| & 0.3 & 0.2 & 0.0 & 0.5 \\ \hline
|E| & 0.1 & 0.6 & 0.1 & 0.2 \\ \hline
|F| & 0.2 & 0.3 & 0.3 & 0.2 \\ \hline
\end{tabular}
\end{center}
\caption{Second-Order Markov Chain}
\label{fig:markov-table}
\end{table}
This idea can of course be generalized to arbitrary numbers of
previous events, and in general an $(n+1)$-dimensional array can be
used to store the various conditional probabilities. The number of
previous values observed is called the \emph{order} of the Markov
Chain.
[TO DO: write the Haskell code to implement this]
\subsection{Training Data}
Instead of generating the conditional probability table ourselves,
another approach is to use \emph{training data} from which the
conditional probabilities can be \emph{inferred}. This is handy for
music, because it means that we can feed in a bunch of melodies that
we like, including melodies written by the masters, and use that as a
stochastic basis for generating new melodies.
[TO DO: Give some pointers to the literatue, in particular David
Cope's work.]
The |Data.MarkovChain| library provides this functionality through a
function called |run|, whose type signature is:
\begin{spec}
run :: (Ord a, RandomGen g) =>
Int -- order of Markov Chain
-> [a] -- training sequence (treated as circular list)
-> Int -- index to start within the training sequence
-> g -- random number generator
-> [a]
\end{spec}
The |runMulti| function is similar, except that it takes a list of
training sequences as input, and returns a list of lists as its
result, each being an independent random walk whose probabilities are
based on the training data. The following examples demonstrate how to
use these functions.
\begin{code}
-- some sample training sequences
ps0,ps1,ps2 :: [Pitch]
ps0 = [(C,4), (D,4), (E,4)]
ps1 = [(C,4), (D,4), (E,4), (F,4), (G,4), (A,4), (B,4)]
ps2 = [ (C,4), (E,4), (G,4), (E,4), (F,4), (A,4), (G,4), (E,4),
(C,4), (E,4), (G,4), (E,4), (F,4), (D,4), (C,4)]
-- functions to package up |run| and |runMulti|
mc ps n = mkLine3 (M.run n ps 0 (mkStdGen 42))
mcm pss n = mkLine3 (concat (M.runMulti n pss 0
(mkStdGen 42)))
-- music-making functions
mkNote3 :: Pitch -> Music Pitch
mkNote3 = note tn
mkLine3 :: [Pitch] -> Music Pitch
mkLine3 ps = line (take 64 (map mkNote3 ps))
\end{code}
\out{
\begin{code}
-- testing the Markov output directly
lc ps n = take 1000 (M.run n ps 0 (mkStdGen 42))
lcl pss n m = take 1000 (M.runMulti n pss 0 (mkStdGen 42) !! m)
\end{code}
}
Here are some things to try with the above definitions:
\begin{itemize}
\item
|mc ps0 0| will generate a completely random sequence, since it is a
``zeroth-order'' Markov Chain that does not look at any previous
output.
\item
|mc ps0 1| looks back one value, which is enough in the case of this
simple training sequence to generate an endless sequence of notes that
sounds just like the training data. Using any order higher than 1
generates the same result.
\item
|mc ps1 1| also generates a result that sounds just like its training
data.
\item
|mc ps2 1|, on the other hand, has some (random) variety to it,
because the training data has more than one occurrence of most of the
notes. If we increase the order, however, the output will sound more
and more like the training data.
\item
|mcm [ps0,ps2] 1| and |mcm [ps1,ps2] 1| generate perhaps the most
interesting results yet, in which you can hear aspects of both the
ascending melodic nature of |ps0| and |ps1|, and the harmonic
structure of |ps2|.
\item
|mcm [ps1,reverse ps1] 1| has, not suprisingly, both ascending and
descending lines in it, as reflected in the training data.
\end{itemize}
\vspace{.1in}\hrule
\begin{exercise}{\em
Play with Markov Chains. Use them to generate more melodies, or to
control other aspects of the music, such as rhythm. Also consider
other kinds of training data rather than simply sequences of pitches.}
\end{exercise}
\vspace{.1in}\hrule