Tportmidi
Thin bindings to the portmidi library.
Consult the portmidi documentation and an example.
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
.
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.
module Device_info : sig ... end
Device information.
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.
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.
delete_virtual_device did
delete the virtual device did
.
Warning. You must not use streams after a terminate
this may lead to segfaults.
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.
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.
synchronize s
resynchronizes the stream s
.
module Events : sig ... end
Buffers of events
read s evs ~event_count
reads at most event_count
events in evs
from s
and returns the number of events effectively read.
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
.
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 ())