<title> Haskore Tutorial: Chords</title>
The Haskore TutorialI have described how to represent chords as values of type Music. However, sometimes it is convenient to treat chords more abstractly. Rather than think of a chord in terms of its actual notes, it is useful to think of it in terms of its chord "quality," coupled with the key it is played in and the particular voicing used. For example, we can describe a chord as being a "major triad in root position, with root middle C." Several approaches have been put forth for representing this information, and we cannot cover all of them here. Rather, I will describe two basic representations, leaving other alternatives to the skill and imagination of the reader. (For example, Forte prescribes normal forms for chords in an atonal setting [For73].)
First, one could use a pitch representation, where each note is represented as its distance from some fixed pitch. 0 is the obvious fixed pitch to use, and thus, for example, [0,4,7] represents a major triad in root position. The first zero is in some sense redundant, of course, but it serves to remind us that the chord is in "normal form." For example, when forming and transforming chords, we may end up with a representation such as [2,6,9], which is not normalized; its normal form is in fact [0,4,7]. Thus we define:
A chord is in pitch normal form if the first pitch is zero, and the subsequent pitches are monotonically increasing.
One could also represent a chord intervalically; i.e. as a sequence of intervals. A major triad in root position, for example, would be represented as [4,3,-7], where the last interval "returns" us to the "origin." Like the 0 in the pitch representation, the last interval is redundant, but allows us to define another sense of normal form:
A chord is in interval normal form if the intervals are all greater than zero, except for the last which must be equal to the negation of the sum of the others.In either case, we can define a chord type as:
We might ask whether there is some advantage, computationally, of
using one of these representations over the other. However, there is
an invertible linear transformation between them, as defined by the
following functions, and thus there is in fact little advantage of one
over the other:
> pitToInt :: Chord -> Chord
> pitToInt ch = aux ch
> where aux (n1:n2:ns) = (n2-n1) : aux (n2:ns)
> aux [n] = [head ch - n]
>
> intToPit :: Chord -> Chord
> intToPit ch = 0 : aux 0 ch
> where aux p [n] = []
> aux p (n:ns) = n' : aux n' ns where n' = p+n
Exercise
Show that pitToInt and intToPit are inverses in the
following sense: for any chord ch1 in pitch normal form, and
ch2 in interval normal form, each of length at least two:
Another operation we may wish to perform is a test for equality on chords, which can be done at many levels: based only on chord quality, taking inversion into account, absolute equality, etc. Since the above normal forms guarantee a unique representation, equality of chords with respect to chord quality and inversion is simple: it is just the standard (overloaded) equality operator on lists. On the other hand, to measure equality based on chord quality alone, we need to account for the notion of an inversion.
Using the pitch representation, the inversion of a chord can be
defined as follows:
> pitInvert (p1:p2:ps) = 0 : map (subtract p2) ps ++ [12-p2]
Although we could also directly define a function to invert
a chord given in interval representation, we will simply
define it in terms of functions already defined:
> intInvert = pitToInt . pitInvert . intToPit
We can now determine whether a chord in normal form has the same
quality (but possibly different inversion) as another chord in normal
form, as follows: simply test whether one chord is equal either to the
other chord or to one of its inversions. Since there is only a finite
number of inversions, this is well defined. In Haskell:
> samePitChord ch1 ch2 =
> let invs = take (length ch1) (iterate pitInvert ch1)
> in or (map (==ch2) invs)
>
> sameIntChord ch1 ch2 =
> let invs = take (length ch1) (iterate intInvert ch1)
> in or (map (==ch2) invs)
For example, samePitChord [0,4,7] [0,5,9] returns True
(since [0,5,9] is the pitch normal form for the second inversion
of [0,4,7]).