[Haskell-beginners] help with music application

Christopher Allen cma at bitemyapp.com
Tue Dec 1 23:13:21 UTC 2015


Could you post the code to a public repository? Ideally with some example
input and expected output if you're that far along.

On Tue, Dec 1, 2015 at 5:11 PM, Dennis Raddle <dennis.raddle at gmail.com>
wrote:

> I realize my current implementation of my music application is not pretty
> and I want to learn to use Haskell more effectively.
>
> 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.
>
> The outline of my program is this:
>
> 1. Read the output of Sibeliusl
>
> 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.
>
> 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.
>
> So the data looks like this:
>
> The basic time stamp type is Loc, indicating a measure number and a beat
> within that measure.
>
> type MeasureNum = Int
> type MeasureBeat = Rational
> data Loc = Loc MeasureNum MeasureBeat
>
> type PartName = String
> data Score = Score ScoreRelatedData (Map PartName Part)
>
> 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.
>
> -- The Chords in a Part occur in time at a Loc. There can be more
> -- than one Chord at a Loc
> data Part = PartRelatedData (Map Loc [Chord])
> data PartRelatedData -- data about musical expression that applies to a
> Part as a whole
>
> 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.
> data Chord = ChordRelatedData VoiceNumber [Note]
> 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.
>
> data Note = Note NoteRelatedData Pitch
> data NoteRelatedData -- this includes expression markings that apply to a
> Note only
>
> 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:
>
> 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.
>
> 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 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.
>
> 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
>
> 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.
>
> And that's just the beginning. There are something like two dozen patterns
> that need to be identified.
>
> 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.
>
> This is bug-prone and unwieldy.
>
> 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
>
> (1) I don't need an extra field
> (2) I don't have to worry about that extra field becoming invalid or
> remaining uninitialized.
>
> 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.
>
> So what data do I pass to my MIDI-conversion algorithm? I could create
> something called ContextNote like this
>
> data ContextNote = ContextNote Score Part Chord Note
>
> 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.
>
> allContextNotes :: Score -> [ContextNote]
>
> This list would have one entry for every note in the Score.
>
> Also I would probably use named fields. So I would have something like
>
> data Note = Note
>   { nPitch :: Int
>   , nVolume :: Int
>   , ...
>   }
>
> 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
>
> class HasNoteData a where
>   pitch :: a -> Int
>
> Then
>
> instance HasNoteData ContextNote where
>   pitch (ContextNote _ _ note) = nPitch note
>
> I could create a bunch of accessor functions like that for every field in
> the ContextNote.
>
> For data that should probably be computed once, I could create Memo data.
> Maybe I would have
>
> data ContextNote = ContextNote Memo Score Part Chord Note
>
> or something like that.
>
> So if you have read this far, thank you very much. Any suggestions welcome.
>
> D
>
>
>
>
>
>
>
>
>
>
>
>
>
>
> _______________________________________________
> Beginners mailing list
> Beginners at haskell.org
> http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners
>
>


-- 
Chris Allen
Currently working on http://haskellbook.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/beginners/attachments/20151201/539b57c0/attachment-0001.html>


More information about the Beginners mailing list