https://wiki.haskell.org/index.php?title=The_Monad.Reader/Issue2/Haskore&feed=atom&action=historyThe Monad.Reader/Issue2/Haskore - Revision history2017-04-24T07:29:15ZRevision history for this page on the wikiMediaWiki 1.19.14+dfsg-1https://wiki.haskell.org/index.php?title=The_Monad.Reader/Issue2/Haskore&diff=22401&oldid=prevLemming: show code snippets by teletype2008-08-11T19:12:30Z<p>show code snippets by teletype</p>
<a href="https://wiki.haskell.org/index.php?title=The_Monad.Reader/Issue2/Haskore&diff=22401&oldid=20829">Show changes</a>Lemminghttps://wiki.haskell.org/index.php?title=The_Monad.Reader/Issue2/Haskore&diff=20829&oldid=prevWouterSwierstra at 14:14, 9 May 20082008-05-09T14:14:14Z<p></p>
<p><b>New page</b></p><div>'''This article needs reformatting! Please help tidy it up.'''--[[User:WouterSwierstra|WouterSwierstra]] 14:14, 9 May 2008 (UTC)<br />
<br />
= Haskore =<br />
<br />
This Article will be about ''Haskore'', which is a Haskell library<br />
for describing music. It follows an approach of<br />
describing a domain specific language and thus reduces complications<br />
of arbitrary language decisions. Imagine, for example, a structure<br />
describing Music in any imperative language and compare this to the<br />
simplicity of a Haskore representation. <br />
<br />
A core of the Haskore system is Score data, which is<br />
stored as a Type called {{{Music}}}. Score data is usually<br />
represented like this:<br />
<br />
attachment:haskore1-ex1.gif<br />
<br />
This sequence of Symbols, while looking relatively simple to the<br />
musician's eye, gives us a lot of information about the music we<br />
associate with it. Some of the information encoded here would be the<br />
number of notes struck, their lengths, how they overlap or don't.<br />
<br />
<br />
Implicitly, we assume that we're dividing an octave into 12<br />
"halftones"<br />
(look at the numeric values of (12th root of 2)^''n''<br />
for ''n''=0...12 and compare to "simple" fractions to gain<br />
understanding of the significance of this<br />
number: ''n''=7 is the "fifth" (c-g), ''n''=4 the "major third",<br />
''n''=3 the "minor third" etc.), that a certain note called<br />
"a" represents 440 oscillations per second and a few other, even<br />
more arcane things.<br />
<br />
<br />
We shall see that we need to give our computer all this information to<br />
reproduce the music that we associate with these notes.<br />
<br />
== Preparation ==<br />
<br />
Get [attachment:tmr-Haskore.tar.gz Haskore]. This version of<br />
Haskore is ancient but stable. I corrected it slightly to account for<br />
features that changed in the meanwhile.<br />
<br />
Unpack the file. It provides some documentation and the Haskore sources.<br />
<br />
To use Haskore interactively, change to {{{Haskore/Src}}} and start<br />
{{{hugs}}} (you could also use {{{ghci}}}, but be sure<br />
to put the file {{{Haskore/ghc_add/IOExtensions.lhs}}} into the<br />
{{{Haskore/Src}}} Directory before.) Type {{{:l HaskoreLoader}}} and <br />
{{{:m Basics}}} to initialize Haskore for immediate<br />
Experimentation. {{{:l example}}} will load Haskore, some<br />
declarations in the examples in this text, and import<br />
{{{TestHaskore}}} which will save us some time and brains by<br />
defining reasonable defaults for some features.<br />
<br />
Follow-ups of this article will need CSound.<br />
Most distributions of operating Software will allow for a<br />
relatively easy installation.<br />
<br />
== Building blocks of Music ==<br />
<br />
Haskore offers a data type called {{{Music}}} that represents - as<br />
you might have guessed - music. The "atoms" of music, notes, can be<br />
generated by giving their "pitch class" (This is where the<br />
implicit assumption that our octave is divided into 12 pitches shows<br />
up)<br />
, octave, duration, and a List of Attributes , like in:<br />
<br />
{{{#!syntax haskell<br />
Basics> :t (c 1 (1%4) [])<br />
c 1 (1 % 4) [] :: Music<br />
}}}<br />
<br />
This snippet would represent a "c4" note, played for a fourth<br />
measure. The infix operator {{{%}}} is used to create a rational<br />
number. This way we can easily specify triplets, for example, which<br />
are harder in inherently quantized environments.<br />
<br />
The names Haskore gives to the "pitch classes" are, as one<br />
would expect, the names used in the Anglo-Saxon languages, that are,<br />
{{{a b c d e f g}}}. Sharp and Flat pitches are available via<br />
{{{as}}} and {{{af}}}, respectively. Note that this encoding is<br />
an absolute one and does not differentiate in any way among<br />
"enharmonics", like {{{es}}} and {{{f}}}<br />
<br />
Now how do we make this single note a music? We will have to combine<br />
it with other notes. There are two obvious way to do this.<br />
<br />
||<^> attachment:haskore1-ex2.gif ||<#eeeeee> {{{:+:}}} ||<^> http://www.students.uni-marburg.de/~Zapf/haskore1-ex3.gif || = ||<^> http://www.students.uni-marburg.de/~Zapf/haskore1-ex4.gif ||<br />
<br />
The sequential composition, expressed by the operator <br />
{{{(:+:) :: Music -> Music -> Music}}}<br />
results in a value that represents both values in temporal<br />
composition (I am tempted to write "played one after the other", but<br />
there is no playing going on for now, so this would be a bad idea)<br />
<br />
||<^> attachment:haskore1-ex2.gif ||<#eeeeee> {{{:=:}}} ||<^> http://www.students.uni-marburg.de/~Zapf/haskore1-ex3.gif || = ||<^> http://www.students.uni-marburg.de/~Zapf/haskore1-ex5.gif ||<br />
<br />
The parallel composition {{{:=:}}} has the same type, but composes<br />
both values to one that represents them simultaneously ("played at the<br />
same time").<br />
<br />
Using {{{:t}}}, we can see that both Operators take two<br />
{{{Music}}} values<br />
and return a {{{Music}}} value. Using these Features and the rests<br />
(which<br />
are named {{{qnr}}}, {{{hnr}}} etc., for ''quarter note<br />
rest'', ''half note rest''), we can already construct a lot of<br />
music.<br />
<br />
Other useful operators (Actually, all the "operators"<br />
mentioned are just infix type<br />
constructors for {{{Music}}} values - see {{{Basics.lhs}}}<br />
line 34...43. The semantics of the constructed Score is to be added<br />
later) are {{{Trans :: Int -> Music -> Music}}} and<br />
{{{Tempo :: Ratio Int -> Music -> Music}}}. Use them to Transpose<br />
tunes, or to change their speed.<br />
<br />
The list at the end of each note does not seem to make much sense<br />
until now. It is intended to hold notewise attributes. For example,<br />
the Volume of a Note can be kept here, since it might be different for<br />
each single Note. {{{c 4 (1%4) [Volume 50]}}}, for example, would<br />
represent a quarter "c 4", played at "Volume 50". While we have a<br />
clear definition for "c 4" and "1%4", we don't have one for<br />
"Volume 50". This will become important now, when we want to make<br />
our music audible.<br />
<br />
== Output ==<br />
<br />
What is missing now to play that music? Since there is no inherent<br />
support for Music in "Computers" (Turing-Equivalent Machines), we<br />
need to output something that a given synthesis equipment<br />
understands. A canonical choice for score data would be {{{midi}}}. The<br />
only information still missing in our {{{Music}}} Data is {{{midi}}}<br />
channel numbers.<br />
<br />
A Haskore abstraction for converting score Data to something closer to<br />
acoustical reality is a "Performance". There is a function<br />
{{{perform :: PMap -> Context -> Music -> Performance}}} that<br />
can convert {{{Music}}} to a {{{Performance}}}, given a<br />
{{{PMap}}} (a Mapping from player Names to Players) and a<br />
{{{Context}}} (which is not interesting right now, but<br />
can control how various performances will be coordinated).<br />
<br />
For example, we can turn an arbitrary {{{Music}}} Value to a<br />
{{{Performance}}} like this:<br />
<br />
{{{#!syntax haskell<br />
Main> perform (\_->defPlayer) defCon example1<br />
[Event{eTime=0.0,eInst="piano",ePitch=48,eDur=0.5,eVol=113.5,pFields=[]}]<br />
}}}<br />
<br />
Using some default Values we nicked from<br />
{{{TestHaskore.lhs}}}. We see that the {{{Volume 100}}} note<br />
attribute was converted to an event volume of {{{113.5}}}. Considering<br />
that result, it's questionable if the default values were chosen<br />
all that wisely.<br />
<br />
Using<br />
<br />
{{{#!syntax haskell<br />
Main> perform (\_->defPlayer) defCon example2<br />
[Event{eTime=0.0,eInst="piano",ePitch=48,eDur=0.5,eVol=113.5,pFields=[]},<br />
Event{eTime=0.5,eInst="piano",ePitch=48,eDur=0.5,eVol=113.5,pFields=[]},<br />
... <br />
}}}<br />
<br />
We see that a performance is a flat list of {{{Event}}}s as opposed to a<br />
{{{Score}}} value, which is rather tree-like in structure.<br />
<br />
Now we are ready to write these events out to some musical format, for<br />
example {{{midi}}}. We needed some additional information to write out the<br />
{{{midi}}} file, namely a "patch map" to map the instrument name<br />
"piano" to the {{{midi}}} "Acoustic Grand Piano" (Instrument 1) on<br />
Channel 1. For other instruments, you could just extend the<br />
list. (For a list of instrument names, see<br />
{{{Haskore/Src/GeneralMidi.lhs}}})<br />
<br />
{{{#!syntax haskell<br />
Main> outputMidiFile "example2.mid" (performToMidi (perform (\_->defPlayer) defCon example2) [("piano","Acoustic Grand Piano",1)])<br />
}}}<br />
<br />
This call gives no visible output. After that, you should, however,<br />
find {{{example2.mid}}} in your current directory. Open it with<br />
your favourite (I recommend "Rosegarden"<br />
[http://www.rosegardenmusic.com/] on Unix-derivate systems) {{{midi}}}<br />
Sequencer/Editor tool, or play it back. For ease of use i put all<br />
these bits together to a function in {{{example.lhs}}}<br />
<br />
{{{#!syntax haskell<br />
Main> midiout "example2.mid" example2<br />
}}}<br />
<br />
== Functional Music ==<br />
<br />
How could functional programming help us specify music? Haskell<br />
variables can of course take {{{Music}}} values, and build other<br />
values from them, so we can for example {{{Trans}}}pose a given<br />
piece of music.<br />
<br />
We could, for example, write a function that converts<br />
a list of intervals (integers) and a {{{Music}}} value to a chord.<br />
<br />
{{{#!syntax haskell<br />
mychord intervals base = map (\n->Trans n base) intervals<br />
minor = [0,3,7]<br />
major = [0,4,7]<br />
}}}<br />
<br />
0 is the prime, 3 the small third, 4 the large third and 7 the<br />
fifth. Now we can specify a simple chord progression:<br />
<br />
{{{#!syntax haskell<br />
example3 = (c 4 (1%4) [Volume 100]) :+:<br />
(g 4 (1%4) [Volume 100]) :+:<br />
(f 4 (1%4) [Volume 100]) :+:<br />
(c 4 (1%4) [Volume 100])<br />
example4 = mychord major example3<br />
}}}<br />
<br />
As we see, {{{mychord}}} works with any music value. What <br />
it can't do is building different chords on top of a sequence of <br />
notes. So:<br />
<br />
{{{#!syntax haskell<br />
example5 = (mychord major (c 4 (1%4) [Volume 100])) :+:<br />
(mychord minor (d 4 (1%4) [Volume 100])) :+:<br />
(mychord major (g 4 (1%4) [Volume 100])) :+:<br />
(mychord major (c 4 (1%4) [Volume 100])) <br />
}}}<br />
<br />
gives us a sequence with different kinds of chords.<br />
<br />
=== Scale Theory ===<br />
<br />
Now as one might know, different "Modes" of (European, traditional)<br />
Music use the same sequence of intervals, just starting from a<br />
different point in the sequence ({{{Mode}}}) and note ({{{Key}}},<br />
{{{Tonic}}}). Using the Major scale as the original one:<br />
<br />
{{{#!syntax haskell<br />
> maj_skips = [2,2,1,2,2,2,1] <br />
}}}<br />
<br />
we declare a helper function {{{runsum}}}, which just sums up numbers <br />
in a list continuously. <br />
<br />
{{{#!syntax haskell<br />
runsum = scanl (+) 0<br />
}}}<br />
<br />
Now we can declare all the scales based on the<br />
major scale in one function, and for example, have a look at the<br />
intervals of the minor scale.<br />
<br />
{{{#!syntax haskell<br />
scale kind = runsum (drop kind (cycle maj_skips))<br />
}}}<br />
<br />
{{{#!syntax haskell<br />
Main> take 8 (scale 5)<br />
[0,2,3,5,7,8,10,12]<br />
}}}<br />
<br />
We need cycle because scales repeat all 8 "steps" (every<br />
octave). The intervals of the major scale, taken from the sixth (since<br />
we start counting with 0: 5), give the (natural or aeolian) minor scale.<br />
<br />
We declare a simple melody "step" wise, as in "steps" of a scale<br />
(the 8th step being the octave, 12 halftones, and the other steps<br />
depending on the exact scale used) <br />
<br />
{{{#!syntax haskell<br />
simplemelody = [(0,1%4),(5,1%2),(4,1%8),(3,1%8),(2,1%4),(5,1%2),(0,1%4)]<br />
}}}<br />
<br />
<br />
<br />
* Specify a value of type {{{(Ratio Int->Music)}}} and call it<br />
{{{base}}} (as it will become the base tone (''Tonic'') of our melody,<br />
if we give it an arbitrary length)<br />
* Find out how many halftones are between the base of a scale and<br />
the "step" wanted: {{{trans n = (fromInteger (scl !! n))}}}<br />
* Transpose the base note, given a length to complete it, about that <br />
amount, to get the ultimate result.<br />
<br />
{{{#!syntax haskell<br />
realize :: Int -> (Ratio Int->Music) -> (Int,Ratio Int) -> Music<br />
realize kind base (n,len) = Trans (trans n) (base len)<br />
where<br />
trans n = (fromInteger (scl !! n)) <br />
scl = (scale kind)<br />
}}}<br />
<br />
We'll write another helper, that realizes a few notes and puts them in a sequence:<br />
<br />
{{{#!syntax haskell<br />
testrealize kind base melody = allseq $ map (realize kind base) melody<br />
}}}<br />
<br />
=== Making it Audible ===<br />
<br />
Now we can realize our melody in an arbitrary scale, on an arbitrary<br />
base pitch, like for example:<br />
<br />
{{{#!syntax haskell<br />
Main> midiout "major.mid" (testrealize 0 (\l->(c 4 l [Volume 100])) simplemelody)<br />
Main> midiout "minor.mid" (testrealize 5 (\l->(d 4 l [Volume 100])) simplemelody)<br />
}}}<br />
<br />
in c major, and then in d minor. This task (transpose and change<br />
mode) makes a nice (and often-cursed) exercise for music<br />
students. Thanks to Haskell we were able to solve it in some 20 lines<br />
of code.<br />
<br />
Now of course we also want to describe music that's not only single-voiced. For example,<br />
we could want to describe the a'th three and four note chord in our scale:<br />
<br />
{{{#!syntax haskell<br />
tri a = [a,a+2,a+4]<br />
tet a = [a,a+2,a+4,a+6]<br />
}}}<br />
<br />
and put the chords numbered 1, 5, 4 and 1 after each other (if you ever thought you couldn't<br />
tell a ''I-IV-V-I progression'' even if you saw one, now you did), putting in a four note chord <br />
here and there, and adding an octave to the last ''I'':<br />
<br />
{{{#!syntax haskell<br />
test2d:: [[(Int,Ratio Int)]]<br />
test2d = [allength (1%2) (tri 0),<br />
allength (1%2) (tet 3),<br />
allength (1%2) (tet 4),<br />
allength (1%2) (8:(tri 0))]<br />
where allength l= map (\a->(a,l))<br />
}}}<br />
<br />
Now we only need to map realize twice to that, and then fold twice (first in parallel, then serially) to make this a Music value.<br />
<br />
{{{#!syntax haskell<br />
rea2d kind base melody = allseq $ map allpar $ map (map (realize kind base)) melody<br />
}}}<br />
<br />
Try:<br />
<br />
{{{#!syntax haskell<br />
midiout "iivviprog.mid" (rea2d 5 (\l->(f 5 l [Volume 100])) test2d)<br />
}}}<br />
<br />
And listen to it.<br />
<br />
This would be all for this issue of TMR. If you should feel bored, try Haskore yourself. For example, you could:<br />
<br />
* Try to write an own melody, either using {{{realize}}} to later change scale, or without.<br />
* Put fitting chords along {{{simplemelody}}}, or put a melody along {{{test2d}}}<br />
* Read in some existing midi files using {{{readMidi}}} and try to analyze the resulting {{{Music}}} values. (for example, asking: are all notes in one scale? which ones aren't? what's their harmonic function?)<br />
* If you're really bored: get some sheet music and realize it in Haskore.<br />
<br />
Anyway, stay tuned for the next Issue of TMR. If you have any questions, join us on freenode (just point your IRC client to irc.freenode.net), channel #haskell, and don't hesitate to ask me.<br />
<br />
Bastiaan Zapf (freenode basti_)<br />
----<br />
CategoryArticle</div>WouterSwierstra