Tuesday, October 6, 2015

Elm and Web-MIDI

This post is about attempting to learn two new technologies and one old one.  The two new ones are Web-MIDI (which allows you to plug MIDI devices into your computer and do things with music in the browser) and the Elm programming language (which promises at last to bring some coherence to the challenge of writing web applications using the witches brew which is HTML, CSS and JavaScript).  The old one is in fact JavaScript itself - I've always avoided it like the plague but now I feel there is a reason for getting to grips with it.

Update for Elm 0.17

Now that Elm 0.17 has been released, the description of elm in this post no longer applies. Signals have been removed, the rules are starting to change for writing native modules.  From today (May 14th), I have deprecated elm-webmidi.

Web-MIDI

Considering how long MIDI has been in existence, you would think that handling it in browsers would be second-nature by now, but sadly this is not the case. Working draft 17 of the Web MIDI API was only published in March of this year, and at the time of writing, only Chrome has an implementation.  It is not a large document.  The most important features are illustrated by these two functions:
   
    function midiConnect () {
      // request MIDI access and then connect
      if (navigator.requestMIDIAccess) {
         navigator.requestMIDIAccess().then(onMIDISuccess)
      } 
    }

    // Set up all the signals we expect if MIDI is supported
    function onMIDISuccess(midiAccess) {
        var inputs = midiAccess.inputs.values();       

        // loop over any register inputs and listen for data on each
        midiAccess.inputs.forEach( function( input, id, inputMap ) {   
          registerInput(input);       
          input.onmidimessage = onMIDIMessage;     
        });      

        // listen for connect/disconnect message
        midiAccess.onstatechange = onStateChange;
    }  
The requestMIDIAccess function detects whether MIDI is supported in the browser and then hands control to an asynchronous function onMIDISuccess if it finds there is support. This allows you to discover all the MIDI input devices that are connected, to register them and also register a callback which will respond to MIDI messages provided by that device (for example key presses on a keyboard). You can handle MIDI output devices the same way, but I will not cover that here. Finally you can register another callback that listens to connection or disconnection messages as devices are unplugged or plugged back in to your computer.

Elm 0.16

Elm is a Functional Reactive Programming language.  It replaces the traditional callbacks used by JavaScript with the concept of a Signal. Such a signal might be, for instance, a succession of mouse clicks or keyboard presses - in other words it represents a stream of input values over the passage of time. What Elm forces you to do is to merge all the signals that you encounter in your program and then it routes this composite signal to one central place.  Here, a single function, foldp, operates which has the following signature:
   
   foldp : (a -> s -> s) -> s -> Signal a -> Signal s
This is a bit like a traditional fold, except it operates over time. It takes three parameters - (1) a function that knows how to update global state from an incoming value, (2) the current state and (3) an incoming signal - and then it composes all these together so that you get a signal for the overall state. Whereas the traditional JavaScript model would have you deal with a set of individual callbacks which would operate on the global state of your program in often incomprehensible ways (because it is so difficult to reason about when you're in the middle of callback hell), the Elm model simply requires you to hold global state and refresh it completely each time any signal comes in. That this approach doesn't slow reactivity down to a crawl is due to one thing - Virtual DOM. An abstract version of DOM is built rather than writing it directly, and this comes with clever diffing algorithms so that when you want to view your state as HTML, only a small amount of rewriting needs to occur.

In other respects, Elm Syntax is very like Haskell, but with occasional borrowings from F# for its composition operators. What is lacking, though, is Typeclasses. This means, for example, that you can't just use map to operate on lists - you have to preface is as List.map because Elm can't distinguish it from others such as Signal.map.

Elm-WebMidi

To build a MIDI library for Elm, you have to write 'Native' JavaScript code which takes each of the callbacks described earlier and turns them into Elm signals.  I'll say a little more about how this is done later on, but for now, assume that there are three separate signals with the following type signatures:
   
   -- a connection signal
   Signal MidiConnect

   -- a disconnection signal
   Signal MidiDisconnect

   -- a note signal
   Signal MidiNote
The data types MidiConnect, MidiDisconnect and MidiNote are simply tuples that gather together the appropriate attributes from the Web-MIDI interface. MidiConnect signals are emitted by the onStateChange callback for a new connection, but they are also emitted when the web application starts up if there happen to be any devices already attached. The library allows us to write an application which lists the various devices such as MIDI keyboards as they appear and disappear and which also displays each key as it is pressed alongside its parent device.

Anatomy of an Elm Application

This sort of application is perhaps slightly simpler than other sample applications that you see on the Elm examples page because there is no direct user interaction with any widgets in the HTML view - all interaction is via the MIDI device.  It uses a standard MVC pattern. The first step is to gather together each of the three input signals. A MidiMessage algebraic data type is used to represent this disjunction, each Signal is mapped to this common type and then the Signals are joined together with Elm's mergeMany function.
   
   type MidiMessage = MC MidiConnect | MN MidiNote | MD MidiDisconnect

   -- Merged signals
   notes : Signal MidiMessage
   notes = Signal.map MN midiNoteS

   inputs : Signal MidiMessage
   inputs = Signal.map MC midiInputS

   disconnects : Signal MidiMessage
   disconnects = Signal.map MD midiDisconnectS

   midiMessages : Signal MidiMessage
   midiMessages = mergeMany [inputs, notes, disconnects]

We then need a model to represent the global state that we wish to keep. This is merely a list of input devices, and associated with each one is an optional MIDI note:
   
   -- Model
   type alias MidiInputState = 
     { midiInput: MidiConnect
     , noteM: Maybe MidiNote
     }

   type alias MidiState = List MidiInputState

and, of course, we need a view of this state. Elm's HTML primitives help to keep this terse:
   
   -- VIEW
   viewNote : MidiNote -> String
   viewNote mn = "noteOn:" ++ (toString mn.noteOn) ++ ",pitch:" ++ 
                 (toString mn.pitch) ++ ",velocity:" ++ (toString mn.velocity)

   viewPortAndNote : MidiInputState -> Html
   viewPortAndNote mis = 
     case mis.noteM of 
       Nothing ->
          li [] [ text mis.midiInput.name]
       Just min ->
          li [] [ text ( mis.midiInput.name ++ ": " ++ (viewNote min)) ]

   view : MidiState -> Html
   view ms =
     div []
       [ let inputs = List.map viewPortAndNote ms
         in ul [] inputs
       ] 
The main program applies the foldp function to produce each new state, and displays it with the view function. The initial state is just the empty list:
   
   -- Main
   midiState : Signal MidiState
   midiState = Signal.foldp stepMidi initialState midiMessages

   main : Signal Html
   main = Signal.map view midiState
All that's left to describe is the stepMidi function that recomputes the global state as each signal arrives. It deconstructs the signal into its original components using pattern-matching:
  
   stepMidi : MidiMessage -> MidiState -> MidiState
   stepMidi mm ms = 
      case mm of 
        -- an incoming MIDI input connection - add it to the list
        MC midiConnect -> 
           { midiInput = midiConnect, noteM = Nothing } :: ms
        -- an incoming note - find the appropriate MIDI input id, add the note to it
        MN midiNote ->
           let updateInputState inputState =
             if midiNote.sourceId == inputState.midiInput.id 
               then 
                 { inputState | noteM <- Just midiNote }
            else 
              inputState          
        in
           List.map updateInputState ms     
        -- a disconnect of an existing input - remove it from the list
        MD midiDisconnect ->
           List.filter (\is -> is.midiInput.id /= midiDisconnect.id) ms

Writing a Native Elm Module

There seems, as yet, to be very little documentation about how to go about this. The best approach is probably to look through the core Elm libraries on Github and adopt the conventions that these exemplify. You will need to make use of the common Runtime JavaScript that Elm will pass you and which allows access to the core features - for example List and Signal. In the Elm-WebMidi library, I made use of two main features. Firstly, Elm tuples are simply JavaScript objects with a discriminator labelled 'ctor' with the value (say) '_Tuple5' for a 5-member tuple. Secondly, signals can be built simply by using Elm.Native.Signal.make. The JavaScript then returns an object containing these three signals. Alongside the JavaScript, you need an Elm file that redefines this interface in Elm terms, but uses the JavaScript implementation. If you are interested, the Elm-WebMidi library and sample program can be found here.

5 comments:

  1. Thank you for nice posts about music and Elm. I read first one and also did some experience with WebMIDI.
    After considering API variations I went with `Task` for basic MIDI access because they plays better with Effects. Unfortunately as for v0.16 I didn't find hack to call back into ELM with Task and device still updates piggybacking on `Signal`.

    I you curious, I'm using Piano app to tinker with API.
    https://raw.githack.com/ibnHatab/WebMidi/master/examples/apps/index.html
    https://github.com/ibnHatab/web-midi

    Cheers,
    Vlad

    ReplyDelete
    Replies
    1. Hi, Vlad - yes I'm curious - I like your thorough treatment of the MIDI interface. The separation into channel and meta Signals is sensible. I was unsure whether to use Tasks or Signals - I eventually chose the latter because I was hit by a stepTask bug in Elm 0.15 (since fixed). As you see, I was only really interested in the NoteOn/NoteOff messages.

      I think your port of parts of Euterpea from the Haskell School of Music to Elm is a very good idea. You might be interested to hear that I've just written a MIDI file parser using elm-combine - https://github.com/newlandsvalley/elm-comidi. This was influenced to some extent by https://github.com/giorgidze/HCodecs which is the parser that HSoM uses.

      Delete
    2. Hi, I was looking into your works and have two followup questions.

      First is technical. What do you use / planing to use on backend. How much you couple web app with server. On the protocol above TCP; is it better described by REST or websocket?

      Second is about music expression. Music in my repo is not from HSoM but chapter from HSoE (it is same origin).
      Paul Hudak used his here style with abstraction / interpreter for domain.

      I know to build Music as stream and then interpret it via Performance.
      I can also interpret it into Stuff or both. But I don't know to change music on time of performance or to change display of notation as in editor.

      This need of dynamicnes calls for another level of abstraction like lifting into Free
      or heavy machinery like Lenses.

      Can you give judgement off top of your head - is it matter of abstraction of brute force?
      Did you have similar problem while working with tunes search engine?

      Delete
    3. Hi, again Vlad.

      Backends. I have a pre-existing RESTful backend which I wrote a while back using Scala and Spray for the routing. The frontend was written in Play Framework/Scala. I originally intended to replace the bits that play the tunes which were in Javascript with the Elm implementation. If I'd done so, I'd have tried to slot in the Elm pretty much independently of any of the Scala (I think I remember reading somewhere that someone was experimenting with an Elm plugin for Play). The Elm would just make an HTTP request to a particular RESTful URL for the tune. Anyway, all that was put on hold when I discovered that Elm's Tasks at the moment impose too much of a time penalty to play music responsively enough (i.e. if you're generating a lot of small Tasks).

      Music expression. Sorry - I didn't realise it was HSoE - it looked so familiar! All I've investigated here is a way to use HSoM/Haskell to convert a single line MIDI melody into a score (Well, into ABC format but plenty of tools exist already to convert these into scores). I pretty much used brute-force techniques - I found I had to apply a succession of heuristics because you have to guess what the score must have originally looked like in many places.

      I've not thought very deeply about how to modify music dynamically. My guess would be to use Web-Audio, but I'm now not convinced that Elm is the way to go given the overhead that Tasks produce that I mentioned earlier. It's not an area I've explored or have been particularly interested in. The tunes search engine just uses a search across a Mongo database where the tunes themselves are held essentially in a plain text format.

      Delete
    4. Hi,
      Realtime webaudio is major headache in current implementation.
      Nevertheless you can make some optimization by re-configuring audio sink. Explained in video here
      http://sriku.org/blog/2015/09/22/talk-at-jsfoo-2015-orchestrating-the-web-audio-api/




      Delete