Skip to content

an elm module encapsulating a player for MIDI recordings

Notifications You must be signed in to change notification settings

newlandsvalley/midi-player

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

midi-player

This is an Elm 0.18 module that encapsulates a control for the playback of single-track MIDI recordings which conform to the MidiRecording format (i.e. those produced by the elm-comidi parser). The control uses stop, start and pause buttons and includes a capsule that indicates the proportion of the tune that has been played. It uses as an instrument the acoustic grand piano.

It exposes the following message type which sends a recording to the player:

SetRecording (Result String MidiRecording)

Other messages which control the buttons are autonomous and invisibe to the caller. If the recording is single track, it is played as is; if multitrack, it plays track zero.

Integrating the module

The player is implemented using ports. As such, it is not possible to produce a single build artefact that contains the complete module. The module exposes the following:

Model, Msg (SetRecording), init, update, view, subscriptions 

This can be imported into a main elm program using the normal conventions. The player will only become visible once the instructions to load the sound fonts and to set the recording have been issued to it.

The calling program

The following section describes how a calling program that (somehow) gets hold of a MIDI recording via the MIDI message might integrate the player:

import

import Midi.Player exposing (Model, Msg, init, update, view, subscriptions)

model

type alias Model =
{ 
  myStuff :....
, recording : Result String MidiRecording
, player : Midi.Player.Model
}

messages

type Msg
  = MyMessage MyStuff
  | Midi (Result String MidiRecording )  
  | PlayerMsg Midi.Player.Msg             -- delegated messages for the player

initialisation

It is important that the calling program allows the player to be initialised:

init : (Model, Cmd Msg)
init =
  let
    myStuff = ....
    (player, playerCmd) = Midi.Player.init recording
  in
    { 
      myStuff = myStuff 
    , recording = Err "not started"
    , player = player
    } ! [Cmd.map PlayerMsg playerCmd]

update

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
  
    MyMessage stuff -> ...
               
    Midi result -> 
      ( { model | recording = result }, establishRecording result )    

    PlayerMsg playerMsg -> 
      let 
        (newPlayer, cmd) = Midi.Player.update playerMsg model.player
      in 
        { model | player = newPlayer } ! [Cmd.map PlayerMsg cmd]

where establishRecording sends a command to the player which establishes the recording to play:

establishRecording : Result String MidiRecording -> Cmd Msg
establishRecording r =
  Task.perform (\_ -> NoOp) 
               (\_ -> PlayerMsg (Midi.Player.SetRecording r)) 
               (Task.succeed (\_ -> ()))

view

view : Model -> Html Msg
view model =
  div [] 
    [  
    myView ..
    ,  Html.map PlayerMsg (Midi.Player.view model.player) 
    ]

subscriptions

subscriptions : Model -> Sub Msg
subscriptions model = 
  Sub.batch 
    [  mySubs ...
    ,  Sub.map PlayerMsg (Midi.Player.subscriptions model.player)
    ]

the html

The following components are required by the player:

  • The javascript for the combined calling program and player
  • The javascript for the sound fonts called by the player through elm ports
  • The javascript for loading the sample MIDI file
  • The soundfonts used by the player, assumed to be in the directory assets/soundfonts
  • The image files used by the player widget assumed to be in the directory assets/images

The various pieces of javascript can be assembled (here for a calling program named MidiFilePlayer) in the html file as follows

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Elm 0.18 Midi Player module sample</title>

  </head>
  <body>
    <div id="elmMidiFilePlayer"></div>
    <script src="js/soundfont-player.js"></script>
    <script src="distjs/elmMidiFilePlayer.js"></script>
    <script>
      var node = document.getElementById('elmMidiFilePlayer');
      var myapp = Elm.MidiFilePlayer.embed(node);
    <!-- the javascript below is written to accept an initial parameter named node-->
    </script>
    <script src="js/nativeSoundFont.js"></script>
    <script src="js/nativeBinaryFileIO.js"></script>
  </body>
</html>

Use with sound sources other than MIDI files

Although designed for playback of MIDI files, the player can be used with other sound sources. The Header contains the following integer fields:

formatType -  set this to 0  (single track)
trackCount - set this to 1
ticksPerBeat - it is usually convenient to set this to 480

The minimal set of MidiEvents that are most usefully implemented are these:

Tempo microsecondsPerQuarterNoteBeat 

NoteOn  channel pitch velocity    

NoteOff  channel pitch velocity    

Typically, NoteOn messages define the note to be played at a time delay of zero whilst NoteOff messages define the time it is played for because of a positive time delay.In the NoteOn and NoteOff messages, channel is ignored and can be set to any integer value, pitch is the MIDI pitch number and velocity (related to gain) is a number between 0 and 127.

The tempo setting defines the number of microseconds taken by each beat (see later). This is then built into a MidiMessage:

MidiMessage = (Ticks, MidiEvent)

It is often convenient to produce both a NoteOn and NoteOff for each note where there is no delta time (in Ticks) for the NoteOn, but a positive time for NoteOff. The simplest thing to do is to set the note ticks to 480 for a whole note, 240 for a half note and so on. Then you can vary the pace of the playback simply by setting an appropriate Tempo value at the start of the track (or whenever the tempo subsequently changes)

Finally, the overall MIDI track is represented like this:

Track = List MidiMessage

and the complete MIDIRecording is simply a tuple containing the Header and the Track.

About

an elm module encapsulating a player for MIDI recordings

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published