Compare commits

...

3 Commits

Author SHA1 Message Date
Christopher Arndt c1d00decc9 refactor: remove 'Jack' prefix from callback types and two more enums
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2023-05-24 07:49:35 +02:00
Christopher Arndt 34d3093505 feat: wrap metadata API and add one usage example
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2023-05-24 07:49:14 +02:00
Christopher Arndt 31b83223fc feat: add MIDI receiver example showing use of threads/channels
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2023-05-23 01:26:50 +02:00
3 changed files with 267 additions and 41 deletions

View File

@ -0,0 +1,46 @@
import std/[logging, strformat]
import jacket
var
jclient: ClientP
status: cint
descs: ptr UncheckedArray[Description]
var log = newConsoleLogger(when defined(release): lvlInfo else: lvlDebug)
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
addHandler(log)
setErrorFunction(errorCb)
jclient = clientOpen("jacket_property", NullOption.ord, status.addr)
debug "JACK server status: " & $status
if jclient == nil:
error getJackStatusErrorString(status)
quit 1
let numDescs = getAllProperties(descs)
if numDescs != -1:
var desc: Description
for i in 0..<numDescs:
desc = descs[i]
echo fmt"Subject: {desc.subject}"
if desc.property_cnt > 0:
for p in 0..<desc.property_cnt:
var prop = desc.properties[p]
echo fmt"* {prop.key}: {prop.data} (type: {prop.type})"
echo ""
freeDescription(desc.addr, 0)
free(descs)
else:
error "Could not get properties!"
discard jclient.clientClose

View File

@ -0,0 +1,125 @@
import std/[logging, os, strutils]
import signal
import jacket
var
jclient: ClientP
event: MidiEvent
midiPort: PortP
midiEventChan: Channel[MidiEvent]
midiEventPrinter: Thread[void]
status: cint
exitSignalled: bool = false
var log = newConsoleLogger(when defined(release): lvlInfo else: lvlDebug)
proc cleanup() =
debug "Cleaning up..."
if jclient != nil:
debug "Deactivating JACK client..."
discard jclient.deactivate()
if midiEventPrinter.running:
debug "Stopping MIDI event printer thread..."
# Receiving an invalid event causes receiving thread to wake up and
# break its endless loop
event.size = 0
midiEventChan.send(event)
midiEventPrinter.joinThread()
debug "Closing MIDI event channel..."
midiEventChan.close()
if jclient != nil:
debug "Closing JACK client..."
discard jclient.clientClose()
jclient = nil
debug "Bye."
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 midiEventPrinterProc() =
var event: MidiEvent
while true:
event = midiEventChan.recv()
if event.size == 0:
break
elif 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:
if not midiEventChan.trySend(event):
warn "MIDI event channel overflow!"
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)
# Set up a thread, which receives MIDI events from process callback via a
# Channel and prints them without danger of blocking the process callback
midiEventChan.open()
createThread(midiEventPrinter, midiEventPrinterProc)
# 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(200)
cleanup()
when isMainModule:
main()

View File

@ -77,7 +77,7 @@ type
ClientZombie = 0x1000 ClientZombie = 0x1000
type type
JackPortFlags* {.size: sizeof(culong) pure.} = enum PortFlags* {.size: sizeof(culong) pure.} = enum
PortIsInput = 0x01, PortIsInput = 0x01,
PortIsOutput = 0x02, PortIsOutput = 0x02,
PortIsPhysical = 0x04, PortIsPhysical = 0x04,
@ -85,7 +85,7 @@ type
PortIsTerminal = 0x10 PortIsTerminal = 0x10
type type
JackLatencyCallbackMode* {.size: sizeof(cint) pure.} = enum LatencyCallbackMode* {.size: sizeof(cint) pure.} = enum
CaptureLatency, CaptureLatency,
PlaybackLatency PlaybackLatency
@ -167,31 +167,54 @@ const
EXTENDED_TIME_INFO* = true EXTENDED_TIME_INFO* = true
JACK_TICK_DOUBLE* = true JACK_TICK_DOUBLE* = true
# Metadata
type
Property* = object
key*: cstring
data*: cstring
`type`*: cstring
PropertyChange* {.size: sizeof(cint).} = enum
PropertyCreated,
PropertyChanged,
PropertyDeleted
Description* = object
subject*: Uuid
property_cnt*: uint32
properties*: ptr UncheckedArray[Property]
property_size*: uint32
DescriptionP* = ptr Description
# Callback function types # Callback function types
type type
JackProcessCallback* = proc (nframes: NFrames; arg: pointer): cint {.cdecl.} ProcessCallback* = proc (nframes: NFrames; arg: pointer): cint {.cdecl.}
JackThreadCallback* = proc (arg: pointer): pointer {.cdecl.} ThreadCallback* = proc (arg: pointer): pointer {.cdecl.}
JackThreadInitCallback* = proc (arg: pointer) {.cdecl.} ThreadInitCallback* = proc (arg: pointer) {.cdecl.}
JackGraphOrderCallback* = proc (arg: pointer): cint {.cdecl.} GraphOrderCallback* = proc (arg: pointer): cint {.cdecl.}
JackXRunCallback* = proc (arg: pointer): cint {.cdecl.} XRunCallback* = proc (arg: pointer): cint {.cdecl.}
JackBufferSizeCallback* = proc (nframes: NFrames; arg: pointer): cint {.cdecl.} BufferSizeCallback* = proc (nframes: NFrames; arg: pointer): cint {.cdecl.}
JackSampleRateCallback* = proc (nframes: NFrames; arg: pointer): cint {.cdecl.} SampleRateCallback* = proc (nframes: NFrames; arg: pointer): cint {.cdecl.}
JackPortRegistrationCallback* = proc (port: PortId; flag: cint; arg: pointer) {.cdecl.} PortRegistrationCallback* = proc (port: PortId; flag: cint; arg: pointer) {.cdecl.}
JackClientRegistrationCallback* = proc (name: cstring; flag: cint; arg: pointer) {.cdecl.} ClientRegistrationCallback* = proc (name: cstring; flag: cint; arg: pointer) {.cdecl.}
JackPortConnectCallback* = proc (portA: PortId; portB: PortId; connect: cint; arg: pointer) {.cdecl.} PortConnectCallback* = proc (portA: PortId; portB: PortId; connect: cint; arg: pointer) {.cdecl.}
JackPortRenameCallback* = proc (port: PortId; oldName: cstring; newName: cstring; arg: pointer) {.cdecl.} PortRenameCallback* = proc (port: PortId; oldName: cstring; newName: cstring; arg: pointer) {.cdecl.}
JackFreewheelCallback* = proc (starting: cint; arg: pointer) {.cdecl.} FreewheelCallback* = proc (starting: cint; arg: pointer) {.cdecl.}
JackShutdownCallback* = proc (arg: pointer) {.cdecl.} ShutdownCallback* = proc (arg: pointer) {.cdecl.}
JackInfoShutdownCallback* = proc (code: JackStatus; reason: cstring; arg: pointer) {.cdecl.} InfoShutdownCallback* = proc (code: JackStatus; reason: cstring; arg: pointer) {.cdecl.}
JackLatencyCallback* = proc (mode: JackLatencyCallbackMode; arg: pointer) {.cdecl.} LatencyCallback* = proc (mode: LatencyCallbackMode; arg: pointer) {.cdecl.}
JackInfoCallback* = proc (msg: cstring) {.cdecl.} InfoCallback* = proc (msg: cstring) {.cdecl.}
JackErrorCallback* = proc (msg: cstring) {.cdecl.} ErrorCallback* = proc (msg: cstring) {.cdecl.}
JackSyncCallback* = proc (state: TransportState; pos: ptr Position; arg: pointer): cint {.cdecl.} SyncCallback* = proc (state: TransportState; pos: ptr Position; arg: pointer): cint {.cdecl.}
JackTimebaseCallback* = proc (state: TransportState; nframes: NFrames; pos: ptr Position; newPos: cint; TimebaseCallback* = proc (state: TransportState; nframes: NFrames; pos: ptr Position; newPos: cint;
arg: pointer) {.cdecl.} arg: pointer) {.cdecl.}
PropertyChangeCallback* = proc (subject: Uuid, key: cstring, change: PropertyChange, arg: pointer) {.cdecl.}
# ----------------------------- Version info ------------------------------ # ----------------------------- Version info ------------------------------
@ -273,51 +296,51 @@ proc cycleSignal*(client: ClientP; status: cint) {.importc: "jack_cycle_signal".
# ------------------------------- Callbacks ------------------------------- # ------------------------------- Callbacks -------------------------------
proc setProcessThread*(client: ClientP; threadCallback: JackThreadCallback; arg: pointer): cint {. proc setProcessThread*(client: ClientP; threadCallback: ThreadCallback; arg: pointer): cint {.
importc: "jack_set_process_thread".} importc: "jack_set_process_thread".}
proc setThreadInitCallback*(client: ClientP; threadInitCallback: JackThreadInitCallback; arg: pointer): cint {. proc setThreadInitCallback*(client: ClientP; threadInitCallback: ThreadInitCallback; arg: pointer): cint {.
importc: "jack_set_thread_init_callback".} importc: "jack_set_thread_init_callback".}
proc onShutdown*(client: ClientP; shutdownCallback: JackShutdownCallback; arg: pointer) {. proc onShutdown*(client: ClientP; shutdownCallback: ShutdownCallback; arg: pointer) {.
importc: "jack_on_shutdown".} importc: "jack_on_shutdown".}
proc onInfoShutdown*(client: ClientP; shutdownCallback: JackInfoShutdownCallback; arg: pointer) {. proc onInfoShutdown*(client: ClientP; shutdownCallback: InfoShutdownCallback; arg: pointer) {.
importc: "jack_on_info_shutdown".} importc: "jack_on_info_shutdown".}
proc setProcessCallback*(client: ClientP; processCallback: JackProcessCallback; arg: pointer): cint {. proc setProcessCallback*(client: ClientP; processCallback: ProcessCallback; arg: pointer): cint {.
importc: "jack_set_process_callback".} importc: "jack_set_process_callback".}
proc setFreewheelCallback*(client: ClientP; freewheelCallback: JackFreewheelCallback; arg: pointer): cint {. proc setFreewheelCallback*(client: ClientP; freewheelCallback: FreewheelCallback; arg: pointer): cint {.
importc: "jack_set_freewheel_callback".} importc: "jack_set_freewheel_callback".}
proc setBufferSizeCallback*(client: ClientP; bufsizeCallback: JackBufferSizeCallback; arg: pointer): cint {. proc setBufferSizeCallback*(client: ClientP; bufsizeCallback: BufferSizeCallback; arg: pointer): cint {.
importc: "jack_set_buffer_size_callback".} importc: "jack_set_buffer_size_callback".}
proc setSampleRateCallback*(client: ClientP; srateCallback: JackSampleRateCallback; arg: pointer): cint {. proc setSampleRateCallback*(client: ClientP; srateCallback: SampleRateCallback; arg: pointer): cint {.
importc: "jack_set_sample_rate_callback".} importc: "jack_set_sample_rate_callback".}
proc setClientRegistrationCallback*(client: ClientP; registrationCallback: JackClientRegistrationCallback; proc setClientRegistrationCallback*(client: ClientP; registrationCallback: ClientRegistrationCallback;
arg: pointer): cint {. arg: pointer): cint {.
importc: "jack_set_client_registration_callback".} importc: "jack_set_client_registration_callback".}
proc setPortRegistrationCallback*(client: ClientP; registrationCallback: JackPortRegistrationCallback; proc setPortRegistrationCallback*(client: ClientP; registrationCallback: PortRegistrationCallback;
arg: pointer): cint {. arg: pointer): cint {.
importc: "jack_set_port_registration_callback".} importc: "jack_set_port_registration_callback".}
proc setPortConnectCallback*(client: ClientP; connectCallback: JackPortConnectCallback; arg: pointer): cint {. proc setPortConnectCallback*(client: ClientP; connectCallback: PortConnectCallback; arg: pointer): cint {.
importc: "jack_set_port_connect_callback".} importc: "jack_set_port_connect_callback".}
proc setPortRenameCallback*(client: ClientP; renameCallback: JackPortRenameCallback; arg: pointer): cint {. proc setPortRenameCallback*(client: ClientP; renameCallback: PortRenameCallback; arg: pointer): cint {.
importc: "jack_set_port_rename_callback".} importc: "jack_set_port_rename_callback".}
proc setGraphOrderCallback*(client: ClientP; graphCallback: JackGraphOrderCallback; a3: pointer): cint {. proc setGraphOrderCallback*(client: ClientP; graphCallback: GraphOrderCallback; a3: pointer): cint {.
importc: "jack_set_graph_order_callback".} importc: "jack_set_graph_order_callback".}
proc setXrunCallback*(client: ClientP; xrunCallback: JackXRunCallback; arg: pointer): cint {. proc setXrunCallback*(client: ClientP; xrunCallback: XRunCallback; arg: pointer): cint {.
importc: "jack_set_xrun_callback".} importc: "jack_set_xrun_callback".}
proc setLatencyCallback*(client: ClientP; latencyCallback: JackLatencyCallback; arg: pointer): cint {. proc setLatencyCallback*(client: ClientP; latencyCallback: LatencyCallback; arg: pointer): cint {.
importc: "jack_set_latency_callback".} importc: "jack_set_latency_callback".}
@ -566,7 +589,7 @@ proc getTime*(): Time {.importc: "jack_get_time".}
proc releaseTimebase*(client: ClientP): cint {.importc: "jack_release_timebase".} proc releaseTimebase*(client: ClientP): cint {.importc: "jack_release_timebase".}
# int jack_set_sync_callback (jack_client_t *client, JackSyncCallback sync_callback, void *arg) # int jack_set_sync_callback (jack_client_t *client, JackSyncCallback sync_callback, void *arg)
proc setSyncCallback*(client: ClientP; syncCallback: JackSyncCallback; arg: pointer): cint {. proc setSyncCallback*(client: ClientP; syncCallback: SyncCallback; arg: pointer): cint {.
importc: "jack_set_sync_callback".} importc: "jack_set_sync_callback".}
# int jack_set_sync_timeout (jack_client_t *client, jack_time_t timeout) # int jack_set_sync_timeout (jack_client_t *client, jack_time_t timeout)
@ -576,7 +599,7 @@ proc setSyncTimeout*(client: ClientP; timeout: Time): cint {.importc: "jack_set_
# int conditional, # int conditional,
# JackTimebaseCallback timebase_callback, # JackTimebaseCallback timebase_callback,
# void *arg) # void *arg)
proc setTimebaseCallback*(client: ClientP; conditional: cint; timebaseCallback: JackTimebaseCallback; proc setTimebaseCallback*(client: ClientP; conditional: cint; timebaseCallback: TimebaseCallback;
arg: pointer): cint {. arg: pointer): cint {.
importc: "jack_set_timebase_callback".} importc: "jack_set_timebase_callback".}
@ -603,14 +626,46 @@ void jack_get_transport_info (jack_client_t *client, jack_transport_info_t *tinf
void jack_set_transport_info (jack_client_t *client, jack_transport_info_t *tinfo) void jack_set_transport_info (jack_client_t *client, jack_transport_info_t *tinfo)
]# ]#
# ------------------------------- Metadata --------------------------------
# int jack_set_property (jack_client_t*, jack_uuid_t subject, const char* key, const char* value, const char* type)
proc setProperty*(client: ClientP, subject: Uuid, key, value, `type`: cstring): cint {.importc: "jack_set_property".}
# int jack_get_property (jack_uuid_t subject, const char* key, char** value, char** type)
proc getProperty*(subject: Uuid, key: cstring, value, `type`: ptr cstring): cint {.importc: "jack_get_property".}
# void jack_free_description (jack_description_t* desc, int free_description_itself)
proc freeDescription*(desc: DescriptionP, freeDescriptionItself: cint) {.importc: "jack_free_description".}
# int jack_get_properties (jack_uuid_t subject, jack_description_t* desc)
proc getProperties*(subject: Uuid, desc: DescriptionP): cint {.importc: "jack_get_properties".}
# int jack_get_all_properties (jack_description_t** descs)
proc getAllProperties*(descs: var ptr UncheckedArray[Description]): cint {.importc: "jack_get_all_properties".}
# int jack_remove_property (jack_client_t* client, jack_uuid_t subject, const char* key)
proc removeProperty*(client: ClientP, subject: Uuid): cint {.importc: "jack_remove_property".}
# int jack_remove_properties (jack_client_t* client, jack_uuid_t subject)
proc removeProperties*(client: ClientP, subject: Uuid): cint {.importc: "jack_remove_properties".}
# int jack_remove_all_properties (jack_client_t* client)
proc removeAllProperties*(client: ClientP): cint {.importc: "jack_remove_all_properties".}
# int jack_set_property_change_callback (jack_client_t* client, JackPropertyChangeCallback callback, void* arg)
proc setPropertyChangeCallback*(client: ClientP, callback: PropertyChangeCallback, arg: pointer): cint {.
importc: "jack_set_property_change_callback".}
# ---------------------------- Error handling ----------------------------- # ---------------------------- Error handling -----------------------------
proc setErrorFunction*(errorCallback: JackErrorCallback) {.importc: "jack_set_error_function".} proc setErrorFunction*(errorCallback: ErrorCallback) {.importc: "jack_set_error_function".}
proc setInfoFunction*(infoCallback: JackInfoCallback) {.importc: "jack_set_info_function".} proc setInfoFunction*(infoCallback: InfoCallback) {.importc: "jack_set_info_function".}
{.pop.} {.pop.}
# --------------------------- Helper functions ---------------------------- # --------------------------- Helper functions ----------------------------
proc getJackStatusErrorString*(status: cint): string = proc getJackStatusErrorString*(status: cint): string =