<div dir="ltr">Could you post the code to a public repository? Ideally with some example input and expected output if you're that far along.</div><div class="gmail_extra"><br><div class="gmail_quote">On Tue, Dec 1, 2015 at 5:11 PM, Dennis Raddle <span dir="ltr"><<a href="mailto:dennis.raddle@gmail.com" target="_blank">dennis.raddle@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><font face="monospace, monospace">I realize my current implementation of my music application is not pretty and I want to learn to use Haskell more effectively.</font><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">My application reads a musical score exported by the typesetter Sibelius. A score is a high-level representation of music that includes structures, like grouping notes for each instrument, and directions that affect how the musuc will be played, like tempo indications, indications to gradually speed up, gradually get louder, etc. My program translates it into low-level time stamped MIDI events, which say things like "turn on this note", "turn off this note", "change the volume setting on this instrument," etc. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">The outline of my program is this:</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">1. Read the output of Sibeliusl</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">2. Construct a Score. A Score has several Parts (individual instruments). A part consists of a list of time stamped Chords, which represent a list of Notes that are sounded at the same time (roughly). There are expression marks in the music that apply to Chords as a whole and some apply only to Notes. Notes are the most basic sound-generating unit -- they have a pitch, a duration, some indicators of musical expression like performance techniques, a time offset (a Note may start slightly ahead or behind the time stamp on the Chord), and more. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">3. Modify the Score to account for forms of musical expression and for a few strange things in the way Sibelius exports its data. Right now I process the score through a LOT of passes, maybe two dozen. Also I have given a ton of extra fields to Notes and Chords that essentially are cache or memos of the results of processing that looks for patterns. So now things are quite unwieldy.. I have two dozen extra fields between Chords and Notes, and I need to make two dozen modification passes on the Score. It's bug prone and hard to understand because the order of the passes is important and I could easily put the Score into an invalid state. <br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">So the data looks like this:</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">The basic time stamp type is Loc, indicating a measure number and a beat within that measure.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">type MeasureNum = Int</font></div><div><font face="monospace, monospace">type MeasureBeat = Rational</font></div><div><font face="monospace, monospace">data Loc = Loc MeasureNum MeasureBeat</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">type PartName = String</font></div><div><font face="monospace, monospace">data Score = Score ScoreRelatedData (Map PartName Part)</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">data ScoreRelatedData = -- this is data that applies to the score as a whole, like a measure-by-measure list of tempos, time signatures, and a time map that allows the translation of Loc to a number of seconds. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">-- The Chords in a Part occur in time at a Loc. There can be more </font></div><div><font face="monospace, monospace">-- than one Chord at a Loc</font></div><div><font face="monospace, monospace">data Part = PartRelatedData (Map Loc [Chord])</font></div><div><font face="monospace, monospace">data PartRelatedData -- data about musical expression that applies to a Part as a whole</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">data VoiceNumber Int -- even within a single Part there can be different "voices" which means independent sequential Chords, independent in the sense they may have different volume levels, different durations, etc.</font></div><div><font face="monospace, monospace">data Chord = ChordRelatedData VoiceNumber [Note]</font></div><div><font face="monospace, monospace">data ChordRelatedData -- this includes data about the Chord as a whole, such as playback techniqes that applies to the whole Chord. It also contains a list of Notes.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">data Note = Note NoteRelatedData Pitch</font></div><div><font face="monospace, monospace">data NoteRelatedData -- this includes expression markings that apply to a Note only</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">So there are some patterns within the Score that need to be identified before it can be translated to MIDI events. Here are some examples:</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">1. Tied notes. Some Notes within a Chord X are tied to a Note in the following Chord Y. That means they represent a single sound unit that should be sustained from the beginning of Chord X to the end of Chord Y. But actually the note in Chord Y can be tied to Chord Z and so on for any number of sequential Chords. When I want to find the true ending Loc of a Note I need to follow the tie chain. Some Notes in the same Chord may be tied, and others may not. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">2. Double tremolos. Sometimes two sequential chords are actually supposed to played together--actually the player will rapidly alternate between the chords. When I first read the score there will be a marking on the first chord X </font><span style="font-family:monospace,monospace">of the double tremolo. I have to look for a Chord Y that immediately follows X, has the same VoiceNumber and the same duration, and I can infer that's the second chord in the double tremolo. Note that the timing and notes of a double tremolo, when translated to MIDI, look hardly anything like the original data -- the original data just has two Chords, but the playback will contains lots of sequential notes that are drawn from both chords.</span></div><div><span style="font-family:monospace,monospace"><br></span></div><div><span style="font-family:monospace,monospace">3. Arpeggios. Sometime the notes in a chord are "rolled" -- played with the lowest note first, followed by a time-staggered playback of the other notes, going up in pitch. There might be an arpeggio marking that spans several Parts, meaning I have to look at all the parts to compute the time offsets for the notes in an arpeggio</span></div><div><span style="font-family:monospace,monospace"><br></span></div><div><span style="font-family:monospace,monospace">4. problems in the data export. Sibelius has some bugs, so I need to find problems in its output and fix them by removing or altering certain Chords.</span></div><div><span style="font-family:monospace,monospace"><br></span></div><div><span style="font-family:monospace,monospace">And that's just the beginning. There are something like two dozen patterns that need to be identified.</span></div><div><span style="font-family:monospace,monospace"><br></span></div><div><span style="font-family:monospace,monospace">What I'm doing now is adding all sorts of fields to Parts, Chords, and Notes to hold memos of the results of this processing. These memo fields have to be initialized to something, like zero, or an empty Map, or whatever. Then I set them via the processing passes. </span></div><div><span style="font-family:monospace,monospace"><br></span></div><div><span style="font-family:monospace,monospace">This is bug-prone and unwieldy. </span></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">What I am realizing is that I don't necessarily need to store every field. For isntance, consider tie chains. I don't really need to process them all ahead of time. I can follow a tie chain only in the places where I need to know the full duration of a Note. I might end up with some redundant processing, but the advantage is </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">(1) I don't need an extra field</font></div><div><font face="monospace, monospace">(2) I don't have to worry about that extra field becoming invalid or remaining uninitialized.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">But there are some processing data that probably should be done once and memoized. For instance, I need a Map of the END LOCATION of each Chord to the Chord itself to look up certain things. That is expensive to construct. </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">So what data do I pass to my MIDI-conversion algorithm? I could create something called ContextNote like this</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">data ContextNote = ContextNote Score Part Chord Note</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Then I could write a function that converts a score to a list of context notes, and pass all of them to the conversion routine.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">allContextNotes :: Score -> [ContextNote]</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">This list would have one entry for every note in the Score.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Also I would probably use named fields. So I would have something like</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">data Note = Note</font></div><div><font face="monospace, monospace"> { nPitch :: Int</font></div><div><font face="monospace, monospace"> , nVolume :: Int </font></div><div><font face="monospace, monospace"> , ...</font></div><div><font face="monospace, monospace"> }</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">When I'm accessing the data on ContextNote, I don't want to have to type several accessor functions to get down to the Note fields. So I could do something like</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">class HasNoteData a where</font></div><div><font face="monospace, monospace"> pitch :: a -> Int</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">Then </font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">instance HasNoteData ContextNote where</font></div><div><font face="monospace, monospace"> pitch (ContextNote _ _ note) = nPitch note</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">I could create a bunch of accessor functions like that for every field in the ContextNote.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">For data that should probably be computed once, I could create Memo data. Maybe I would have</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">data ContextNote = ContextNote Memo Score Part Chord Note</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">or something like that.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">So if you have read this far, thank you very much. Any suggestions welcome.</font></div><span class="HOEnZb"><font color="#888888"><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">D</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div></font></span></div>
<br>_______________________________________________<br>
Beginners mailing list<br>
<a href="mailto:Beginners@haskell.org">Beginners@haskell.org</a><br>
<a href="http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners" rel="noreferrer" target="_blank">http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners</a><br>
<br></blockquote></div><br><br clear="all"><div><br></div>-- <br><div class="gmail_signature"><div dir="ltr"><div><div dir="ltr"><div dir="ltr">Chris Allen<br><div><span style="font-size:12.8000001907349px">Currently working on </span><a href="http://haskellbook.com" target="_blank">http://haskellbook.com</a></div></div></div></div></div></div>
</div>