feat: wrap MIDI API and add example MIDI client

Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
This commit is contained in:
Christopher Arndt 2023-05-22 07:09:35 +02:00
parent 08c199d5b3
commit 5c4af1a90a
3 changed files with 124 additions and 1 deletions

View File

@ -8,7 +8,7 @@ A [Nim] wrapper for the [JACK] [C API]
This software is in *alpha status* and has no official release yet. This software is in *alpha status* and has no official release yet.
The basic JACK APIs (client lifecycle, ports, callbacks) have been wrapped and The basic JACK APIs (client lifecycle, ports, callbacks) have been wrapped and
are functional (see [examples]), but MIDI, transport and meta-data APIs still are functional (see [examples]), but latency, transport and meta-data APIs still
need wrapping. Also, symbol names may still be changed and things moved around need wrapping. Also, symbol names may still be changed and things moved around
before the first public release. before the first public release.

View File

@ -0,0 +1,89 @@
import std/[logging, os, strutils]
import signal
import jacket
var jclient: ClientP
var event: MidiEvent
var midiPort: PortP
var status: cint
var exitSignalled: bool = false
var log = newConsoleLogger(when defined(release): lvlInfo else: lvlDebug)
proc cleanup() =
debug "Cleaning up..."
if jclient != nil:
discard jclient.deactivate()
discard jclient.clientClose()
jclient = nil
proc errorCb(msg: cstring) {.cdecl.} =
# Suppress verbose JACK error messages when server is not available by
# default. Pass ``lvlAll`` when creating the logger to enable them.
debug "JACK error: " & $msg
proc signalCb(sig: cint) {.noconv.} =
info "Received signal: " & $sig
exitSignalled = true
proc shutdownCb(arg: pointer = nil) {.cdecl.} =
info "JACK server has shut down."
exitSignalled = true
proc printMidiEvent(event: var MidiEvent) =
if event.size <= 3:
for i in 0..<event.size:
stdout.write(event.buffer[i].toHex)
stdout.write("h ")
stdout.write("\n")
stdout.flushFile()
proc processCb*(nFrames: NFrames, arg: pointer): cint {.cdecl.} =
let inbuf = portGetBuffer(midiPort, nFrames)
let count = midiGetEventCount(inbuf)
for i in 0..<count:
if midiEventGet(event.addr, inbuf, i.uint32) == 0:
printMidiEvent(event)
proc main() =
addHandler(log)
# Create JACK client
setErrorFunction(errorCb)
jclient = clientOpen("jacket_midi_print", NoStartServer.ord or UseExactName.ord, status.addr)
debug "JACK server status: " & $status
if jclient == nil:
error getJackStatusErrorString(status)
quit QuitFailure
# Set up signal handlers to clean up on exit
when defined(windows):
setSignalProc(signalCb, SIGABRT, SIGINT, SIGTERM)
else:
setSignalProc(signalCb, SIGABRT, SIGHUP, SIGINT, SIGQUIT, SIGTERM)
# Register JACK callbacks
if jclient.setProcessCallback(processCb, nil) != 0:
error "Could not set JACK process callback function."
cleanup()
quit QuitFailure
jclient.onShutdown(shutdownCb, nil)
# Create output port
midiPort = jclient.portRegister("midi_in", JACK_DEFAULT_MIDI_TYPE, PortIsInput.ord, 0)
# Activate JACK client ...
if jclient.activate() == 0:
# ... and keep running until a signal is received
while not exitSignalled:
sleep(50)
cleanup()
when isMainModule:
main()

View File

@ -40,6 +40,14 @@ type
Port = distinct object Port = distinct object
PortP* = ptr Port PortP* = ptr Port
type
MidiData* = uint8
MidiEvent* = object
time*: NFrames
size*: csize_t
buffer*: ptr UncheckedArray[MidiData]
MidiEventP* = ptr MidiEvent
type type
JackOptions* {.size: sizeof(cint) pure.} = enum JackOptions* {.size: sizeof(cint) pure.} = enum
NullOption = 0x00, NullOption = 0x00,
@ -372,6 +380,32 @@ proc portTypeSize*(): cint {.importc: "jack_port_type_size".}
proc portTypeGetBufferSize*(client: ClientP; portType: cstring): csize_t {. proc portTypeGetBufferSize*(client: ClientP; portType: cstring): csize_t {.
importc: "jack_port_type_get_buffer_size".} importc: "jack_port_type_get_buffer_size".}
# --------------------------------- MIDI ----------------------------------
# jack_nframes_t jack_midi_get_event_count (void *port_buffer)
proc midiGetEventCount*(portBuffer: pointer): NFrames {.importc: "jack_midi_get_event_count".}
# int jack_midi_event_get (jack_midi_event_t *event, void *port_buffer, uint32_t event_index)
proc midiEventGet*(event: MidiEventP, portBuffer: pointer, eventIndex: uint32): cint {.
importc: "jack_midi_event_get".}
# void jack_midi_clear_buffer (void *port_buffer)
proc midiClearBuffer*(portBuffer: pointer) {.importc: "jack_midi_clear_buffer".}
# size_t jack_midi_max_event_size (void *port_buffer)
proc midiMaxEventSize*(portBuffer: pointer): csize_t {.importc: "jack_midi_max_event_size".}
# jack_midi_data_t * jack_midi_event_reserve (void *port_buffer, jack_nframes_t time, size_t data_size)
proc midiEventReserve*(portBuffer: pointer, time: NFrames, dataSize: csize_t): ptr MidiData {.
importc: "jack_midi_event_reserve".}
# int jack_midi_event_write (void *port_buffer, jack_nframes_t time, const jack_midi_data_t *data, size_t data_size)
proc midiEventWrite*(portBuffer: pointer, time: NFrames, data: ptr MidiData, dataSize: csize_t): int {.
importc: "jack_midi_event_write".}
# uint32_t jack_midi_get_lost_event_count (void *port_buffer)
proc midiGetLostEventCount*(portBuffer: pointer): uint32 {.importc: "jack_midi_get_lost_event_count".}
# -------------------------------- Latency -------------------------------- # -------------------------------- Latency --------------------------------
#[ FIXME: not implemented yet #[ FIXME: not implemented yet