[Haskell-cafe] dynamic code compilation and loading

10 views
Skip to first unread message

Dennis Raddle

unread,
Jan 15, 2017, 3:39:02 PM1/15/17
to haskell-cafe
Hello,
I wrote a Haskell program which reads a MusicXML score and plays it through real-time MIDI messages (using the portmidi module) via some algorithms that are customized depending on the MIDI synthesizer that is receiving the messages. The program also plays back the music with musically expressive nuances. 

In the next version of this program, I am going to add more complicated algorithms for the expressive nuances, and it would be helpful to configure them and express them in actual Haskell code. My workflow will look like this:

A. write a score using the music typesetting program Sibelius

B. write some Haskell code to provide expressive nuances

C. play the music through a MIDI synthesizer and listen.

D. based on what I hear and where I want to take the music, modify the score and the Haskell code 

E. loop back to C

Therefore, if I don't want to recompile the program each time I do step D, I need to compile and load code dynamically.

Let's say that a data structure which contains a MusicXML score and a nominal rendition into MIDI messages together with time tags is of type Score. Then I want to write, and dynamically compile/load, a function

vary :: Score -> Score

which varies the MIDI messages and time tags to add expressive nuances.

Let's say 'vary' is contained in Vary.hs.

I want to write 'vary' in terms of already-compiled functions that provide the basic music-processing operations. Let's say that these functions are in the module MusicBase. Then Vary.hs will look like

import MusicBase

vary = ... operations in MusicBase ...

Can I get some basic idea of how to do this?

D

Brandon Allbery

unread,
Jan 15, 2017, 3:45:10 PM1/15/17
to Dennis Raddle, haskell-cafe

On Sun, Jan 15, 2017 at 3:37 PM, Dennis Raddle <dennis...@gmail.com> wrote:
Therefore, if I don't want to recompile the program each time I do step D, I need to compile and load code dynamically.

Take a look at the hint and plugins packages. There's also the recompile/exec trick used by Dyre and xmonad that preserves a session across mostly-transparent recompiles.

--
brandon s allbery kf8nh                               sine nomine associates
allb...@gmail.com                                  ball...@sinenomine.net
unix, openafs, kerberos, infrastructure, xmonad        http://sinenomine.net

Evan Laforge

unread,
Jan 16, 2017, 6:41:17 PM1/16/17
to Dennis Raddle, haskell-cafe
I used to use hint for a somewhat similar situation (I wanted to give
a REPL to my program, which incidentally is also concerned with
interpreting music). I found that hint didn't give enough control (it
wanted to reload everything on every expression, instead of only
reloading when I told it to) and it was too hard to dig through it's
ghc compatibility layers, so I directly used the GHC API and it was a
lot easier than expected.

However, that's for inserting code into a running program. If you
want to just run a function to transform a Score, couldn't you just
load the support modules in ghci, and run the main function from
there? Change the source, type :r, and type 'main' again. You could
hook up your editor to that automatically on every save.

By the way, I'm also interested in expressive score realization, since
I'm doing something similar, though in my case I'm using a custom
score format rather than staff notation. If you have anything to show
off I'd be interested to see.

> _______________________________________________
> Haskell-Cafe mailing list
> To (un)subscribe, modify options or view archives go to:
> http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
> Only members subscribed via the mailman list are allowed to post.
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.

Dennis Raddle

unread,
Jan 17, 2017, 2:19:20 AM1/17/17
to haskell-cafe
I'm not clear on everything you've saying.

My program, at present, opens some MIDI ports and leaves them open while it is running. It presents me with a command line prompt from which I can configure the program and initiate MIDI playback. I can also halt playback in progress. Every time I change the score (in the Sibelius typesetter program), I initiate a new playback action.

What you are saying sounds like running the program once for each playback. I guess the program wouldn't leave the MIDI ports open, as it has to close them before it exits. But that should work fine. I would also need a different way of configuring the program -- the current settings would have to be stored in a file so they won't be lost when the program exits. 

I wonder about two things. First, I would like the majority of the program to be compiled. It needs speed. Is there a way to compile everything but the module that "varies" a Score?

Second, the Vary.hs module would need to be located on the disk in the same directory as the music score, to keep things organized. But on my disk (MacBook Pro with SS drive) there is one tree for Haskell source, /Users/Dennis/haskell. I use the option "-i/Users/Dennis/haskell" which compiling or running ghci. The musical scores are all stored in a different tree, "/Users/Dennis/Dropbox/music/comp".

So I don't know how to "import" a file that is not in my Haskell source tree, and more important, is not determined until run time. 

Oh yeah, I don't specify the specific score when I run my program, but rather let it search the /Users/Dennis/Dropbox/music/comp tree for the most recently modified score. This is convenient, because over the course of an hour or two that I compose, I would switch several times between scores, and as soon as I modify and save a particular score, that one becomes the source for playback. 

So the program would locate a recently modified Sibelius score, say $MUSIC/piano/tocatta1.sib. (Where $MUSIC is /Users/Dennis/Dropbox/music/comp). Then the program would assume there is some Haskell source in the same directory, called "toccata1.hs". This source would contain functions that vary the expression in ways I like for that particular composition. 

Does this sound doable with ghci? Or the GHC API?

About what I've learned about expression score realization, I could perhaps share some things at some point. Unfortunately I have bad work habits... I often don't take care to finish projects in a way that is presentable, so I have many years of half-finished compositions that can't even be played back with the current version of my program. I'm 48 years old and finally realizing just how important it is to organize and finish things. And to collaborate. I found another musician to collaborate with -- I will make computer "interpretations" of his compositions. 

So my top priority over the next several months is to look over back projects and finish things enough to get some sound, then archive the *sound files* -- so they don't become obsolete. (Assuming wav or mp3 is trustworthy for a while.)

D

Evan Laforge

unread,
Jan 17, 2017, 3:35:58 AM1/17/17
to Dennis Raddle, haskell-cafe
On Mon, Jan 16, 2017 at 11:16 PM, Dennis Raddle <dennis...@gmail.com> wrote:
> I'm not clear on everything you've saying.
>
> My program, at present, opens some MIDI ports and leaves them open while it
> is running. It presents me with a command line prompt from which I can
> configure the program and initiate MIDI playback. I can also halt playback
> in progress. Every time I change the score (in the Sibelius typesetter
> program), I initiate a new playback action.
>
> What you are saying sounds like running the program once for each playback.
> I guess the program wouldn't leave the MIDI ports open, as it has to close
> them before it exits. But that should work fine. I would also need a
> different way of configuring the program -- the current settings would have
> to be stored in a file so they won't be lost when the program exits.

I was thinking you'd have ghci running persistently. Start it up, run
the initialize function which will configure MIDI. Then every time
you want to process a score, just rerun the "process" function.

> I wonder about two things. First, I would like the majority of the program
> to be compiled. It needs speed. Is there a way to compile everything but the
> module that "varies" a Score?

Yes, you can compile modules and ghci will load the compiled version.
When you change one and do a :r, it will recompile the changed module
and its dependents. Normally it uses bytecode for this, but you can
also have it compile to binary. See the ghci section of the ghc
manual.

> Second, the Vary.hs module would need to be located on the disk in the same
> directory as the music score, to keep things organized. But on my disk
> (MacBook Pro with SS drive) there is one tree for Haskell source,
> /Users/Dennis/haskell. I use the option "-i/Users/Dennis/haskell" which
> compiling or running ghci. The musical scores are all stored in a different
> tree, "/Users/Dennis/Dropbox/music/comp".
>
> So I don't know how to "import" a file that is not in my Haskell source
> tree, and more important, is not determined until run time.

You can set flags from ghci, so you can add a -i at runtime. :l also
understands plain paths, so you could give it the complete path, or
generate it via :def.

> Oh yeah, I don't specify the specific score when I run my program, but
> rather let it search the /Users/Dennis/Dropbox/music/comp tree for the most
> recently modified score. This is convenient, because over the course of an
> hour or two that I compose, I would switch several times between scores, and
> as soon as I modify and save a particular score, that one becomes the source
> for playback.
>
> So the program would locate a recently modified Sibelius score, say
> $MUSIC/piano/tocatta1.sib. (Where $MUSIC is
> /Users/Dennis/Dropbox/music/comp). Then the program would assume there is
> some Haskell source in the same directory, called "toccata1.hs". This source
> would contain functions that vary the expression in ways I like for that
> particular composition.
>
> Does this sound doable with ghci? Or the GHC API?

ghci has some limited but surprisingly flexible scripting ability.
For instance, you could do :def L (\_ -> loadNewest), and then define
a loadNewest function that looks for the relevant .sib file and emits
the path to a parallel .hs file. I actually use a :L macro to load
the currently edited file in vim, so I'd probably make a vim macro to
load (or create) an .hs for the "current" .sib file, and then use :L
to load it. For interactive work, my :l is overridden to :m +Utils so
I can get a bunch of interactive utils without having to import them
into every module.

Also, you are not limited to just rerunning a single "vary" function.
You can use the full power of haskell to configure the realization. I
use the REPL for analysis and debugging as well.

> About what I've learned about expression score realization, I could perhaps
> share some things at some point. Unfortunately I have bad work habits... I
> often don't take care to finish projects in a way that is presentable, so I
> have many years of half-finished compositions that can't even be played back
> with the current version of my program. I'm 48 years old and finally
> realizing just how important it is to organize and finish things. And to
> collaborate. I found another musician to collaborate with -- I will make
> computer "interpretations" of his compositions.

I keep each score saved with a "known good" version of the MIDI
output, along with a commit ID for the time it was generated. So it
not only archives a known good realization, it also acts as a
regression test against code changes (checking all saved scores is
part of the commit validation, in addition to the more usual unit
tests), and if all else fails, I can get back to the original
performance by rewinding the repo. mp3 is good for archiving of
course, but you'll never be able to change it again.

Dennis Raddle

unread,
Jan 17, 2017, 5:19:37 AM1/17/17
to Evan Laforge, haskell-cafe
Thanks, I will refer to your email as I try to develop my working method.
Ah, yes, those are good ideas. But to get things to sound right, I have to configure my software synthesizers (such as samplers, modeled pianos, and analog synth emulators). I can set up all the synthesizers as VST plugins in Reaper, but versions change and there may be configuration elements not captured in the Reaper file, so I really need to archive a mp3 as a record of what I did. I can and should take greater care in managing the versions of the Haskell source, but it's harder to manage the configuration of the software synths and I would rather not invest the effort to reach a point of high confidence... when it's easy to archive an mp3.

If I really want to revisit something I did a while back, it might take some work to get it going again with the current version of my Haskell source, but that's not such a bad thing... often I can learn things serendipitously from unintended software behavior.

But the other approach, the one Knuth took with TeX, which is to make it 100% backward compatible for its entire life, is fascinating. I'm just one guy messing around as a hobby so it's obviously much more important for a widely used community resource like TeX.

D

Reply all
Reply to author
Forward
0 new messages