Navigation
Home & news
Random page
All pages
Site search:
Databases
Fortune cookies
Haikus
SID themes
Page collections
Blag
Chip music
Chipophone
Games
Hardware projects
Music downloads
Obfuscated programming
Piano music
Sane programming
Scene productions
SID related pages
Software downloads
Video downloads
Featured pages
A Chipful of Love For You
Air on a Rasterline
Autosokoban
Beagleboard VGA
Binary Art
Bitbuf
Brainfuck
Chipophone
Chopin romance
Craft
Elements of Chip Music
Fratres
GCR decoding on the fly
Gravazoid
Hardsync
Kernighan's lever
Klämrisk Hero
Live at LCP 2011
Parallelogram
Phasor
Pipe Logic
Poems for bugs
Rasp64
Raster paper
Reverberations
Safe VSP
Shards of Fancy
Sidreloc
Spindle
Swan
Think Twice III
TTY demystified
We learn the nibbles
VIC Timing Chart
Vim + ^Z
Zeugma
Don't miss

Leaky redaction
Forum
Register
Log in
Latest comments
Syndication
RSS feed
Feedback
  • Swedish content
  • Personal content
  • Offensive content

Functional music

This is a paper I wrote when taking a course in functional programming. The text is interspersed with Haskell code, but you should be able to understand much of the theory without knowing Haskell.

The task was to create a program that would take a sequence of chords (expressed as "A", "Cm" etc.), and generate rudimentary accompaniment according to some predefined rules. Along with these instructions, we (the students) were also given a short introduction to music and harmony, expressed in a scientific way. Unfortunately, this introduction was terribly wrong on some points. I decided to ignore it and start from scratch, explaining all the theory my way in the paper.

I passed the course...

Download the paper here, or read it online:

Functional Music, Linus Åkesson, d00la@efd.lth.se

-----------------------------------------------------------------

        Talking about music is like fishing about architecture.
        -- Frank Zappa


This is an introductory essay on the subject of the harmonics of
modern Western major/minor based music. Starting out with a small
set of basic rules and assumptions, the text will go on
explaining basic notes, sharp and flat notes, note sets,
modulation, keys, scales, chords, chord notation, and finally
present an algorithm which generates rudimentary accompaniment by
interpreting a sequence of chords.

As hinted by Zappa's remark above, our approach to harmonics will
be purely operational. We will deal with notes, rythm and
harmony, the same tools that musicians deal with -- but rather
than use the tools to create something new, we'll simply analyze
and play around with the tools for our personal insight and
enjoyment.


> module AutoComp where
> import Haskore hiding (chord, Key)    -- I want those names.


The starting point - seven basic notes
======================================

In this section, and at a few other places throughout the essay,
assumptions will be made. There is nothing inexplicable, nothing
axiomatic about these assumptions -- in the end everything boils
down to the physical theory of overtones. However, explaining
this is beyond the scope of this text.

In Western music, barring certain exceptions, one works with
seven notes at a time. These notes are usually arranged in what
we call a major scale. As a convenience, names have been given to
seven frequencies, so that these seven frequencies form such a
major scale. The names are different in different countries; in
Germany and Scandinavia, for instance, the names are C, D, E, F,
G, A, H, whereas in France they are do, re, mi, fa, so, la, si.
Since this text is written in English, we'll use the English
naming system:

        C       D       E       F       G       A       B

The notes in the major scale are not equidistant. In fact, there
are two distances (ratios between frequencies, actually): The
tone and the semitone. In the following diagram, t means tone and
s means semitone:

    C       D       E       F       G       A       B       C'
    |<--t-->|<--t-->|<--s-->|<--t-->|<--t-->|<--t-->|<--s-->|

C' is the note whose frequency is twice the frequency of C. In
music speak, you'd say that C' is one octave above C. Two notes
that are an octave apart sound very similar, and for our purposes
we'll regard them as being equal.

The diagram tells us that the product of all the steps (ttsttts)
should be equal to two (since C' = 2C). A common approximation is
to set t = ss, which yields s = 2^(1/12) = 1.05946...

However, t is not exactly equal to ss (i.e. one tone is not equal
to two semitones). Instead, we wish G to be the pure fifth to C,
which is a musician's way of saying that G = 3/2C. This gives us
the following two equations:

        ttsttts = 2
        ttst    = 3/2

The roots are:

        t       = 9/8           = 1.12500
        s       = 256/243       = 1.05350...


Modifying the basic notes
=========================

Let's see what happens when we multiply each of the base
frequencies with ttts:

    C       D       E       F       G       A       B

becomes:

    G       A       B       C'      D'      E'     ???

Since the total distance between C and G is three t's and one s
(G = Cttts), the C becomes a G. Similar relations hold for each
of the basic notes except B, which doesn't become F' (the only
note that's left) since F' = Bttss, not Bttts.

So, when we multiply each of the seven base notes by ttts (music
speak: transpose them one fifth upwards), and then disregard any
octave marks ('), we end up with the same set of notes, except
that the F is missing, and a new note has appeared, which is
actually Ft/s. This note is called F sharp.

Of course, we can perform the same operation on the new set of
notes:

    G       A       B       C       D       E       Ft/s

        becomes

    D       E       Ft/s    G       A       B       Ct/s

and so on. This can go on forever. At one point all of the notes
will be sharp, and then, when we multiply B sharp with ttts, we
end up with F*(t/s)*(t/s), which is called F double-sharp.

The inverse of this operation would be to divide each of the
seven frequencies by ttts. Starting with our initial set of
notes, we would end up with:

    F       G       A       Bs/t    C       D       E

This time, we end up without the B, and with a new note, Bs/t,
called B flat.


Some definitions
================

At this point, we could define a Haskell data type to represent a
note:

> type Note             = (Int, Int)

where the first Int is in the range 0 to 6, and tells us which of
the seven basic notes to start from, and the second Int tells us
how many times this note should be sharpened (if positive) or
flattened (if negative).

We could also define a function, showNote, that converts Note
objects into human readable strings. We'll use the conventional
"#" and "b" suffices to indicate sharp and flat notes
respectively.

> showNote :: Note -> String

> showNote (base, offs) = ("CDEFGAB" !! mod base 7)
>                       : replicate (abs offs)
>                               (if offs > 0 then '#' else 'b')

The set of basic notes would be:

> baseSet               = zip [0..6] [0,0..] :: [Note]


The circle of fifths
====================

The operation introduced above, in which all of the seven
frequencies in our current working set are multiplied or divided
by ttts (3/2), is called modulation. Multiplying by ttts is
called upwards modulation, and dividing by ttts is called
downwards modulation.

Imagine an infinite sequence of note sets:

...
D   E   F#  G   A   B   C#
G   A   B   C   D   E   F#
C   D   E   F   G   A   B      <- starting point
F   G   A   Bb  C   D   E
Bb  C   D   Eb  F   G   A
...

To simplify things, we'll give each note set in this sequence a
name, consisting of the first note in the set, followed by the
word "major". In other words, "C D E F G A B" is the note set of
C major, and "Bb C D Eb F G A" is the note set of B flat major.

To make things complicated, every note set has an additional
name, consisting of the sixth note in the set followed by the
word "minor". So, C major is the same note set as A minor. (NB:
There is also a scale called C major, and a scale called A minor,
and these scales differ. More about this later.)

After six modulations in each direction, we end up with the sets
of F# major and Gb major. F# and Gb would have the same frequency
using the aforementioned t = ss approximation, but in reality
they don't. If we assume that they are equal, however, we can
fold the infinite sequence of note sets into a circle:

                          C maj = a min
            F maj = d min               G maj = e min


    Bb maj = g min                              D maj = b min


 Eb maj = c min                                    A maj = f# min


    Ab maj = f min                              E maj = c# min

            Db maj = bb min             B maj = g# min
                          F# maj = d# min 
                       or Gb maj = eb min

This is called the circle of fifths. Moving clockwise in the
circle corresponds to modulating upwards, and moving
anti-clockwise corresponds to modulating downwards.

In our program, we don't use the t = ss approximation (yet), so
we'll stick to the infinite sequence representation. Let's first
define some useful functions:

> upFifth, dnFifth :: Note -> Note

> upFifth (base, offs)  = (
>                               mod (base + 4) 7,
>                               offs + if base == 6 then 1 else 0)

> dnFifth (base, offs)  = (
>                               mod (base + 3) 7,
>                               offs - if base == 3 then 1 else 0)

The upFifth function will transpose a given note one fifth
upwards, and the dnFifth function will do the opposite. To
modulate a whole set of notes, we simply map these functions to
each element of the set:

> modulateUp, modulateDn :: [Note] -> [Note]

> modulateUp set        = map upFifth set
> modulateDn set        = map dnFifth set

Now we can play around a bit, with the functions defined so far:

        > map showNote baseSet

        ["C","D","E","F","G","A","B"]

        > map showNote (modulateUp baseSet)

        ["G","A","B","C","D","E","F#"]



Keys and scales
===============

Every piece of music that we will deal with has a key. The key
tells us what notes are generally used in the piece. So, if a
piece of music is in G major, we generally stick to the notes
found in the G major note set. If the key of the piece had been E
minor, we would have used the same note set, since G major and E
minor are different names for the same note set. (The key of the
piece carries other information as well, so it would be wrong to
say that a piece is written in G major if indeed it is written in
E minor. For our purposes, however, these keys are indifferent.)

Thus, a key is a set of notes:

> type Key              = [Note]

As we shall see, it's a good idea to form the infinite "circle of
fifths" using the key of the piece as a starting point, instead
of always starting from the set of C major. The following
function builds the "circle" around a given note set,
representing it as an infinite list of pairs:

> type Circle           = [(Key, Key)]

> circle :: Key -> Circle

> circle key            = zip
>                               (iterate modulateUp key)
>                               (iterate modulateDn key)

Example: If n is a note set, then (circle n !! 4) would be a pair
of note sets corresponding to n modulated upwards four times, and
n modulated downwards four times, respectively.

So far we've used the same naming scheme (note name followed by
"major" or "minor") for both keys and note sets. This naming
scheme is also used when naming scales. The difference between a
scale and a set of notes, is that the scale is ordered, starting
with a certain note and going strictly upwards frequency-wise.
For every note set, there are seven scales (starting from each of
the seven notes), of which two are more important to us than the
rest: the major scale and the melodic minor scale.

The major scale is ordered so that the ratios between the
successive notes are:

        t  t  s  t  t  t (s)

The melodic minor scale is ordered so that the ratios between the
successive notes are:

        t  s  t  t  s  t (t)

Notes may be transposed by whole octaves at will, to make the
above predicates hold. The last ratio (in parenthesis) is the
ratio between the last note of the scale and the base note. It is
included merely to illustrate an important fact: The two
sequences above are the same, except that they are out of phase
with each other.

This means, that if we make sure to always keep the note sets in
the same order as the original set (the seven base notes, which,
as you recall, were chosen so that they would form a major
scale), then the set itself would be equal to its corresponding
major scale, and if we were to rotate it two steps to the right,
it would be equal to its corresponding minor scale. Example: The
C major / a minor note set (C D E F G A B) corresponds to the
following scales:

        C major:        C D E F G A B
        a minor:        A B C D E F G

The following function builds a scale from a note set and a note
to start from. When necessary, it will transpose some notes one
octave upwards, by adding 7 to their first Int. This contradicts
what I wrote in the Note type definition, but it turns out to be
of practical value later on.

> set2scale :: Note -> [Note] -> [Note]

> set2scale (base, _) set
>                       = [(scale, o) |
>                               scale <- [base .. base + 6],
>                               (b, o) <- set,
>                               b == mod scale 7]

My implementation of this function assumes that the set contains
seven notes, each of which is based on a different base note.
This holds for any note set formed by modulating the basic C
major note set (baseSet).

Here's an example of how the function may be used (note that some
pairs have base note values larger than 6, and that showNote
knows how to handle this):

        > set2scale (3, 0) baseSet

        [(3,0),(4,0),(5,0),(6,0),(7,0),(8,0),(9,0)]

        > map showNote (set2scale (3, 0) baseSet)

        ["F","G","A","B","C","D","E"]



Chords
======

By picking some notes from a note set, we may form chords. In the
music we're dealing with, mainly two types of chords are used:
The major triad and the minor triad. From a given note set
(ordered as above), the major triad consists of the first, third
and fifth notes, whereas the minor triad consists of the sixth,
first and third notes. The major triad is named after the first
note in the note set, but the minor triad is named after the
sixth note, much like the corresponding minor scale and key.

So, assuming we have a function, "chord subset pos base", which
picks a given subset of notes from the key containing a certain
note at a certain position, the following functions may be
defined:

> major, minor :: Note -> [Note]

> major                 = chord [0, 2, 4] 0
> minor                 = chord [5, 0, 2] 5

(Note that major and minor are functions, accepting the "base"
argument which is missing from the halfway applications of the
chord function.)

What this chord function must do, then, is to find the correct
note set and then pick out and return the wanted notes. To find
the correct note set, we'll use a helper function, to traverse
the infinite circle of fifths in both directions until a note set
matching a given predicate is found. This function is called
"nearest", because if there are several note sets matching the
predicate, the function will only return the one that is closest
to the starting point of the circle.

> nearest :: ([Note] -> Bool) -> Circle -> [Note]

> nearest pred ((sh, fl) : restCircle)
>
>       | pred sh       = sh
>       | pred fl       = fl
>       | otherwise     = nearest pred restCircle

Since the chord predicates (the first or the sixth note of the
set being equal to some given note) won't ever match more than
one note set, it might seem like "nearest" is an odd name for
this function. We will however use this function again in a
little while, and at that point this stay-near behaviour is
crucial.

The chord function looks like this:

> chord :: [Int] -> Int -> Note -> [Note]

> chord subset pos base = map (set !!) subset
>                               where set = nearest
>                                       ((base ==) . (!! pos))
>                                       (circle baseSet)

So, this is what an E flat major and a D double-sharp minor look
like:

        > map showNote (major (2, -1))

        ["Eb","G","Bb"]

        > map showNote (minor (1, 2))

        ["D##","F##","A##"]



Chord notation and accompaniment
================================

The music with which we are concerned consists of

        1) a melody, and
        2) a sequence of chords.

As mentioned, a chord is a set of notes, and the chords in the
chord sequence are supposed to be played along with the melody.
Usually, the chord sequence is written above the melody as a
series of chord mnemonics: A note name followed by "m" represents
the minor chord of that name, whereas a note name all by itself
represents the major chord of that name. Sometimes, lower case is
used to indicate minor chords. In Haskell, the following types
may be used instead, to represent chords:

> type ChordKind        = Note -> [Note]

> type Chord            = (Note, ChordKind)

The "major" and "minor" functions defined earlier are of the
ChordKind type, as they transform a base note into the set of
notes that make out the corresponding chord. 

Every chord in the sequence has a starting point in time, as well
as a duration. In a written score, the starting point is apparent
by the position of the chord mnemonic, and each chord is held
until the next chord mnemonic appears. We will use a different
approach: the duration of each chord is explicitly stated, and
the first chord is struck at the beginning of the piece.

The chord sequence for a piece of music could then be encoded in
the following way:

> type ChordProgression = [(Chord, Ratio Int)]

where the Ratio Int reflects the duration of each chord, measured
in bars.

Our grand task is to write a function, autoComp, which will
interpret a ChordProgression object, generating rudimentary
accompaniment in the form of a Haskore Music object. The
accompaniment will consist of two parts: bass line and chord
voicing. Let us begin with the bass line.


Bass patterns
=============

A bass line, in this exercise, is a series of notes picked using
a certain algorithm. Bass lines are governed by two parameters:
the current chord, and the overall style of the accompaniment.
The chord is transformed into what we'll refer to as a bass
scale, and the style is just a rythmified pattern of indices into
this scale.

Here are a few bass styles:

> type Style            = [(Int, Ratio Int)]

> basic, calypso, boogie :: Style

> basic                 = [(0, 1%2), (4, 1%2)]
> calypso               = [
>                               (-1, 1%4), (0, 1%8), (2, 1%8),
>                               (-1, 1%4), (0, 1%8), (2, 1%8)]
> boogie                = [
>                               (0, 1%8), (4, 1%8),
>                               (5, 1%8), (4, 1%8),
>                               (0, 1%8), (4, 1%8),
>                               (5, 1%8), (4, 1%8)]

Explanation: Every style is a list of bass notes, where every
note consists of an index into the bass scale and a duration. As
a special case, an index of -1 means that no bass note should be
played.

I wish I could tell you that the patterns above are just some
random patterns that happen to produce nice accomponiments.
Instead, I'll say that the patterns above are just some random
patterns.


Obtaining the bass scale
========================

How do we obtain the correct bass scale, given a chord? The
answer is not, as one might first think, that we simply pick the
major or melodic minor scale corresponding to the chord. It's
actually a bit trickier than that, but not much.

This is what we do: Looking at the circle of fifths, we start at
the note set corresponding to the key of the piece. Then, looking
in both directions, we search for the nearest note set, in which
all notes of the chord appear.

        Note that this is a very simple, operational approach,
        which is sufficient for our purposes. It doesn't always
        work; under some circumstances, for instance, when a
        piece of music is in a minor key, the sixth and/or
        seventh note in a note set may be temporarily raised. If
        -- as in one of my example files -- the seventh note is
        raised throughout the whole piece, we can raise it in the
        key set. (It would be the fifth element of the base set,
        since the minor scale is a rotated version of the base
        set.) But generally, we don't pay attention to this in
        our implementation.

        Indeed, any operational musical model must be inadequate,
        since writing good music is all about breaking the rules.

So anyway, if a piece of music is in C major, we stick to the key
note set whenever the following chords appear: C, F, G, am, dm,
em, because these chords only contain notes that can be found in
the C major note set. However, should a D major or B minor chord
appear, we'd be forced to modulate upwards once, and whenever we
get to an E flat major, for instance, we have to modulate
downwards twice.

The note set we end up with is then converted into a scale,
whose starting point is the base note of the chord.

Example: The key is C major. To get the bass pattern
corresponding to a B minor chord, we search the circle of fifths
for a note set containing the notes B, D and F#. There are three
note sets matching this predicate, but G major is the one closest
to C major. The G major note set contains the notes G, A, B, C,
D, E, F#. Since B is the base of the chord, we transform the note
set into a scale, starting with B:

        B, C, D, E, F#, G, A

Expressed as two Haskell functions:

> findChord :: [Note] -> Key -> [Note]

> findChord chord key   = nearest inSet (circle key)
>                               where
>                         inSet set = all (flip elem set) chord

> bassScale :: Chord -> Key -> [Note]

> bassScale (base, kind) key
>                       = set2scale base
>                               (findChord (kind base) key)

And this is what it looks like in action. Note that the bass
scale corresponding to a B minor chord is different from the one
corresponding to a C flat minor chord -- the C and the Db, as
well as the G and the Ab, are entirely different notes! This is a
very tangible example of the shortcomings of the t = ss
approximation.

        > bassScale ((6, 0), minor) baseSet

        [(6,0),(7,0),(8,0),(9,0),(10,1),(11,0),(12,0)]

        > map showNote $ bassScale ((6, 0), minor) baseSet

        ["B","C","D","E","F#","G","A"]

        > map showNote $ bassScale ((0, -1), minor) baseSet

        ["Cb","Db","Ebb","Fb","Gb","Ab","Bbb"]


Generating the bass line
========================

So, with a style, a chord progression, and information about the
key of the piece, we should now be able to generate a bass line.

Haskore uses a different note representation scheme, so we'll
need a routine to convert our Note objects into integers, where
the integers describe distances (in semitones) from the lowest
possible note in Haskore.

The list of integers used in this function (see below) is of
course a major scale, expressed as semitone distances from the
base of the scale. Hence, this is where we have to fall back to
the t = ss approximation.

> semitonify :: Note -> Int

> semitonify (b, o)     = [0, 2, 4, 5, 7, 9, 11] !! mod b 7
>                       + 12 * quot b 7 + o + 36

Likewise, we need a wrapper routine to carry out all the Haskore
specific stuff that needs to be done to the base line. There is
one important algorithmical detail in the following routine,
though: The style pattern is repeated ad infinitum.

> autoBass :: Style -> Key -> ChordProgression -> Music

> autoBass style key prog
>                       = foldr1 (:+:) $ map toHask (bassLine
>                               key
>                               (cycle style)
>                               prog)
>                       where
>                               toHask ((-1, 0), dur) = Rest dur
>                               toHask (note, dur)    = Note
>                                       (pitch (semitonify note))
>                                       dur
>                                       []

The actual workhorse function is called bassLine, and it operates
by zipping together the style with the chord progression. Looking
at the duration of the first element of the style pattern, as
well as the duration of the first chord, it generates a note with
the shortest of these durations. Then it applies itself
recursively to whatever's left in the style and chord lists,
shortening down note durations as necessary.

> bassLine :: Key -> Style -> ChordProgression ->
>                                       [(Note, Ratio Int)]

> bassLine _ _ []       = []

> bassLine
>       key
>       style@((index, sdur):srest)
>       chords@((chord, cdur):crest)
> 
>               | sdur == 0
>                       = bassLine key srest chords
> 
>               | cdur == 0
>                       = bassLine key style crest
> 
>               | otherwise
>                       = play dur : bassLine key
>                               ((index, sdur - dur):srest)
>                               ((chord, cdur - dur):crest)
> 
>       where
>               dur             = min cdur sdur
>               play dur        = if index == -1
>                       then ((-1, 0), dur)
>                       else (bassScale chord key !! index, dur)


Chord voicing
=============

To add some harmonic content to the, so far, very sparse
accompaniment we've generated, we'll introduce chord voicing. The
idea is simply to play the notes of the chords, but in doing so,
we're free to arrange the notes in any order we wish, and we may
transpose the notes any number of octaves in any direction. Some
configurations will sound more traditional (I won't say better)
than others, and the following rules of thumb may be used to that
effect:

* Keep all chord notes within the range 52 to 67 (as returned by
  the semitonify function).

* Keep the notes of every chord close to each other.

* When a new chord is to be played, the sum of the absolute
  changes (in semitones) of the top, middle and bottom note,
  compared to the last chord played, should be minimized.

Apparently, this time we're better off using the semitone values
than the Note objects, so the first thing we do is convert every
note in the chord:

> resolveChord :: [Note] -> [Int]

> resolveChord chord    = map semitonify chord

Next, we generate every possible chord whose notes are one or two
octaves higher than the corresponding note in the original chord:

> octaveCombinations :: [Int] -> [[Int]]

> octaveCombinations [] = [[]]

> octaveCombinations (c:rChord)
>                       = (++)
>                               (map ((c + 12) :) rest)
>                               (map ((c + 24) :) rest)
>                       where rest = octaveCombinations rChord

We can immediately get rid of any chord containing out-of-bounds
notes:

> validCombinations :: [Int] -> [[Int]]

> validCombinations rChord
>                       = [ c | c <- octaveCombinations rChord,
>                               all (>= 52) c,
>                               all (<= 67) c]

Then, we need a sorting routine to sort the notes of the
remaining chords. Here's a rather crude algorithm:

> sort :: [Int] -> [Int]

> sort []               = []

> sort list             = m: sort [ l | l <- list, l > m ]
>                               where m = minimum list

All of the above, applied to a chord:

> combinations :: [Note] -> [[Int]]

> combinations chord
>                       = map sort (validCombinations
>                               (resolveChord chord))

We end up with a list of chords (usually, at this point, there
are only two to four chords left to choose from), and using the
two latter rules in the rule set, we can put a disobediance score
on each chord. We then pick the chord with the minimum score, for
use in our chord voicing.


Calculating the disobedionce score of a chord
=============================================

First, every chord gets a static score, stemming from the
internal closeness of the notes:

> staticScore :: [Int] -> Int

> staticScore chord     = maximum chord - minimum chord

Then, every chord gets a dynamic disobedience score, being the
total semitone difference between each note in the preceding
chord and their corresponding notes in the current one:

> dynamicScore :: [Int] -> [Int] -> Int

> dynamicScore c1 c2    = sum $ map abs $ zipWith (-) c1 c2

The first chord in a piece doesn't have a predecessor, so given a
set of candidates, the following function returns the chord -- as
a list of Haskore semitone pitches -- to use, given a chord
expressed as a set of Note objects:

> firstChord :: [Note] -> [Int]

> firstChord chord      = minimize staticScore
>                               (combinations chord)

The remaining chords have predecessors, so their score is a
combination of the static and dynamic scores. I've chosen
non-weighted addition as the combination method.

> nextChord :: [Int] -> [Note] -> [Int]

> nextChord pre chord   = minimize score (combinations chord)
>                               where score c = (+)
>                                       (staticScore c)
>                                       (dynamicScore pre c)

Both these functions make use of a helper function, minimize,
which returns the least element of a list, where lessness is
judged by a caller-supplied function.

> minimize :: ([Int] -> Int) -> [[Int]] -> [Int]

> minimize func list    = fst $ head [ e | e <- doubleList,
>                               snd e == minimum
>                                       (map snd doubleList)]
>                               where doubleList = zip list
>                                       (map func list)


Generating the chord voicing
============================

Given a chord progression, we can use the functions defined in
the previous section to generate a list of (chord, duration)
pairs (where each chord is a list of semitone values). The
following functions will do that:

> voicing :: ChordProgression -> [([Int], Ratio Int)]

> voicing ((chord, dur):prog)
>                       = (first, dur) : nextVoicing first prog
>                               where
>                               first = firstChord (kind base)
>                               (base, kind) = chord

> nextVoicing :: [Int] -> ChordProgression ->
>                                       [([Int], Ratio Int)]

> nextVoicing _ []      = []

> nextVoicing pre ((chord, dur):prog)
>                       = (next, dur) : nextVoicing next prog
>                               where
>                               next = nextChord pre (kind base)
>                               (base, kind) = chord

Wrapping it all up in Haskore data types:

> autoChord :: ChordProgression -> Music

> autoChord prog        = foldr1 (:+:)
>                               (map toHask (voicing prog))
>                               where toHask (chord, dur) =
>                                       foldr1 (:=:)
>                                               (map note chord)
>                                       where note n = Note
>                                               (pitch n) dur []


I rest my case...
=================

Here's a single function to invoke all the others:

> autoComp melody chords key style
>                       = foldr1 (:=:) [
>                               (Instr "Lead 1 (square)" melody),
>                               (Instr "Acoustic Bass" bass),
>                               (Instr "Overdriven Guitar" comp)]
>                       where
>                               bass = autoBass style key chords
>                               comp = autoChord chords

To conclude this essay, let me reiterate that the operational
model presented here is nothing but a very crude description of a
harmonical system used in some music. The audible results
produced by this program are downright awful, and it would strike
me as very odd if anyone who's listened to them would actually
want to hear them again. And, as mentioned, no operational model,
no set of rules, can ever describe music, because if there were
such a set of rules, many composers would deliberately go ahead
and break them.

But you can't break the rules if you don't know them, and to know
the rules it is best to understand why they are there. In this
text, I've tried to explain a few basic concepts about Western
major/minor based music. It has been my intention to make as few
assumptions as possible, and to always explain new concepts in
terms of what has been defined earlier in the text. Hopefully,
you now have a general understanding of sharp and flat notes,
note sets, modulation and keys, major and minor, scales, chords,
and quite a few other harmonical ideas. I've also shown how to
use these ideas to generate automatic accompaniment, as a way of
putting this theory to the test.


-----------------------------------------------------------------
15 Aug 2002

Discuss this page

Disclaimer: I am not responsible for what people (other than myself) write in the forums. Please report any abuse, such as insults, slander, spam and illegal material, and I will take appropriate actions. Don't feed the trolls.

Jag tar inget ansvar för det som skrivs i forumet, förutom mina egna inlägg. Vänligen rapportera alla inlägg som bryter mot reglerna, så ska jag se vad jag kan göra. Som regelbrott räknas till exempel förolämpningar, förtal, spam och olagligt material. Mata inte trålarna.

ralph
Ralph Corderoy
Sun 25-Jul-2010 15:04
This page is generating a warning rather than showing the paper.
lft
Linus Åkesson
Sun 25-Jul-2010 16:10

ralph wrote:

This page is generating a warning rather than showing the paper.

Thank you! Fixed.