Module Tportmidi

Thin bindings to the portmidi library.

Consult the portmidi documentation and an example.

Errors

type error

The type for errors.

module Error : sig ... end

Errors.

val error_string : ( 'a, error ) Stdlib.result -> ( 'a, string ) Stdlib.result

error_string r is Result.map_error Error.message r.

Library setup

val initialize : unit -> ( unit, error ) Stdlib.result

initialize () initializes the library.

val terminate : unit -> ( unit, error ) Stdlib.result

terminate () terminates the library. This must be called before exiting a program that used initialize.

val bracket : ( unit -> 'a ) -> ( 'a, error ) Stdlib.result

bracket f runs f () after an initialize and calls terminate after it finished, however it returned.

Devices

module Device_info : sig ... end

Device information.

type device_id = int

The type for device ids. Range from [0; count_devices () - 1].

val count_devices : unit -> ( int, error ) Stdlib.result

count_devices () is the device count.

val get_device_info : device_id -> ( Device_info.t, error ) Stdlib.result

get_device_info did is the information for device id did.

val fold_device_infos : ( device_id -> Device_info.t -> 'a -> 'a ) -> 'a -> ( 'a, error ) Stdlib.result

fold_device_infos d acc folds over devices. Since devices may be deleted without re-enumeration this silently ignores Error.invalid_device_id errors.

Virtual devices

val create_virtual_input : name:string -> interface:string -> ( unit, error ) Stdlib.result

create_virtual_input ~name ~string creates a virtual MIDI input device.

val create_virtual_output : name:string -> interface:string -> ( unit, error ) Stdlib.result

create_virtual_output ~name ~string creates a virtual MIDI output device.

val delete_virtual_device : device_id -> ( unit, error ) Stdlib.result

delete_virtual_device did delete the virtual device did.

Streams

Warning. You must not use streams after a terminate this may lead to segfaults.

type stream

The type for MIDI streams.

Note. After a stream is closed any function on the stream raises Invalid_argument. However at the moment using an unclosed stream value after a library terminate will likely result in a segmentation fault.

val open_input : device_id -> event_buffer_size:int -> ( stream, error ) Stdlib.result

open_input did ~buffer_size opens a stream on input device did that buffers event_buffer_size MIDI events.

val open_output : device_id -> event_buffer_size:int -> latency:int -> ( stream, error ) Stdlib.result

open_output did ~buffer_size ~latency opens a stream on output device did that buffers event_buffer_size MIDI events and adds latency to event time stamps.

val close : stream -> ( unit, error ) Stdlib.result

close s closes the stream s.

val abort : stream -> ( unit, error ) Stdlib.result

abort s aborts the output stream s.

val synchronize : stream -> ( unit, error ) Stdlib.result

synchronize s resynchronizes the stream s.

Reading and writing

module Events : sig ... end

Buffers of events

val poll : stream -> ( bool, error ) Stdlib.result

poll s polls s for input data.

val read : stream -> Events.t -> event_count:int -> ( int, error ) Stdlib.result

read s evs ~event_count reads at most event_count events in evs from s and returns the number of events effectively read.

val write : stream -> Events.t -> event_count:int -> ( unit, error ) Stdlib.result

write s evs ~event_count writes event_count events from evs to s.

val write_short : stream -> when':Events.timestamp -> Events.msg -> ( unit, error ) Stdlib.result

write_short s ~when' msg writes message msg for when' to s.

val write_sysex : stream -> when':Events.timestamp -> string -> ( unit, error ) Stdlib.result

write_sysex when' msg writes sysex message msg for when' to s.

Example

This example finds the first MIDI input device, create a virtual MIDI output called mu.forward on the same interface to and forwards the MIDI input on it.

(* Forwards MIDI input to a virtual MIDI output.

   Compile with:

   ocamlfind ocamlopt \
   -package mu.tportmidi -linkpkg -o test_midi_io test_midi_io.ml

   ocamlfind ocamlc \
   -package mu.tportmidi -linkpkg -o test_midi_io test_midi_io.ml *)

let ( let* ) = Result.bind

let log fmt = Format.kfprintf (Fun.const ()) Format.std_formatter (fmt ^^ "@.")
let log_if_error ~use = function
| Ok v -> v | Error e -> log "Error: %s" (Tportmidi.Error.message e); use

let first_io_devices () =
  let first id info (i, o) =
    let i = match i with
    | None when Tportmidi.Device_info.input info -> Some (id, info)
    | Some _ | None as i ->  i
    in
    let o = match o with
    | None when Tportmidi.Device_info.output info -> Some (id, info)
    | Some _ | None as o -> o
    in
    (i, o)
  in
  Tportmidi.fold_device_infos first (None, None)

let virtual_output ~i = match i with
| None -> Ok None
| Some (_, i) ->
    let interface = Tportmidi.Device_info.interface i in
    let name = "mu.forward" in
    let* () = Tportmidi.create_virtual_output ~name ~interface in
    let get did i acc = (* Now look up its id. *)
      if Tportmidi.Device_info.name i = name then Some (did, i) else acc
    in
    Tportmidi.fold_device_infos get None

let log_device id d () =
  let open Tportmidi.Device_info in
  log "Found device %d: @[<v>%s (%s)@,in:%b out:%b virtual:%b@]"
    id (name d) (interface d) (input d) (output d) (is_virtual d)

let log_event evs k =
  let ts = Tportmidi.Events.timestamp evs k in
  let msg = Tportmidi.Events.msg evs k in
  let status = Int32.((to_int msg) land 0xFF) in
  let b0 = Int32.((to_int (shift_right msg 8)) land 0xFF) in
  let b1 = Int32.((to_int (shift_right msg 16)) land 0xFF) in
  Printf.printf "recv @ %ld: %x % 3d % 3d\n%!" ts status b0 b1

let forward stop ~i ~o = match i with
| None -> log "No MIDI input device found."; Ok ()
| Some (i, iinfo) ->
    let o, oinfo  = Option.get o in
    log_device i iinfo ();
    log_device o oinfo ();
    let c = 512 in
    let evs = Tportmidi.Events.create c in
    let* si = Tportmidi.open_input i ~event_buffer_size:c in
    let* so = Tportmidi.open_output o ~event_buffer_size:c ~latency:0 in
    let finally () = ignore (Tportmidi.close si); ignore (Tportmidi.close so) in
    Fun.protect ~finally @@ fun () ->
    let rec loop stop = match !stop with
    | true -> Ok ()
    | false ->
        let rc = log_if_error ~use:0 (Tportmidi.read si evs ~event_count:c) in
        if rc > 0 then begin
          log_if_error ~use:() (Tportmidi.write so evs ~event_count:rc);
          for k = 0 to rc - 1 do log_event evs k done
        end;
        loop stop
    in
    loop stop

let signal_stopper () =
  let stop = ref false in
  let stop_it = Sys.Signal_handle (fun _ -> stop := true) in
  Sys.set_signal Sys.sigint stop_it;
  Sys.set_signal Sys.sigabrt stop_it;
  stop

let main () =
  log_if_error ~use:1 @@ Result.join @@ Tportmidi.bracket @@ fun () ->
  let stop = signal_stopper () in
  let* i, _ = first_io_devices () in
  let* o = virtual_output ~i in
  Result.map (fun () -> 0) (forward stop ~i ~o)

let () = if !Sys.interactive then () else exit (main ())