Compare commits

..

6 Commits

Author SHA1 Message Date
Christopher Arndt 2d8533d122 feat: wrap transport API and add transport query example
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2023-05-22 19:58:43 +02:00
Christopher Arndt ada9b13857 fix: use fixed-size integer types for custom types
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2023-05-22 19:58:43 +02:00
Christopher Arndt e06f62da8c docs: for deprecated functions/types show original C decl in comment
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2023-05-22 19:58:43 +02:00
Christopher Arndt 309e45e22a refactor: various formatting tweaks
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2023-05-22 19:58:43 +02:00
Christopher Arndt 08c05c71d4 refactor: remove superfluous semicolons from end of comments
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2023-05-22 19:58:43 +02:00
Christopher Arndt 5c4af1a90a feat: wrap MIDI API and add example MIDI client
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
2023-05-22 19:58:43 +02:00
4 changed files with 240 additions and 59 deletions

View File

@ -7,10 +7,10 @@ A [Nim] wrapper for the [JACK] [C API]
This software is in *alpha status* and has no official release yet.
The basic JACK APIs (client lifecycle, ports, callbacks) have been wrapped and
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
before the first public release.
The basic JACK APIs (client lifecycle, ports, callbacks, MIDI, transport) have
been wrapped and are functional (see [examples]), but latency, threading and
meta-data APIs still need wrapping. Also, symbol names may still be changed
and things moved around before the first public release.
Also, I plan to add a higher-level abstraction on top of the direct mapping
from Nim procs and types to C functions and types, probably in the form of

View File

@ -74,7 +74,7 @@ proc main() =
jclient.onShutdown(shutdownCb, nil)
# Create output port
midiPort = jclient.portRegister("out_1", JACK_DEFAULT_MIDI_TYPE, PortIsInput.ord, 0)
midiPort = jclient.portRegister("midi_in", JACK_DEFAULT_MIDI_TYPE, PortIsInput.ord, 0)
# Activate JACK client ...
if jclient.activate() == 0:

View File

@ -0,0 +1,54 @@
import std/[logging, strformat]
import jacket
var
jclient: ClientP
status: cint
pos: Position
transportState: TransportState
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_info", NullOption.ord, status.addr)
debug "JACK server status: " & $status
if jclient == nil:
error getJackStatusErrorString(status)
quit 1
transportState = transportQuery(jclient, pos.addr)
echo fmt"usecs: {pos.usecs}"
echo fmt"frameRate: {pos.frameRate}"
echo fmt"frame: {pos.frame}"
echo fmt"valid: {pos.valid.ord}"
if bool(pos.valid.ord and PositionBBT.ord):
echo fmt"bar: {pos.bar}"
echo fmt"beat: {pos.beat}"
echo fmt"tick: {pos.tick}"
echo fmt"barStartTick: {pos.barStartTick}"
echo fmt"beatsPerBar: {pos.beatsPerBar}"
echo fmt"beatType: {pos.beatType}"
echo fmt"beatsPerMinute: {pos.beatsPerMinute}"
case transportState
of TransportStopped:
echo "JACK transport stopped, starting it now."
transportStart(jclient)
of TransportRolling:
echo "JACK transport rolling, stopping it now."
transportStop(jclient)
of TransportStarting:
echo "JACK transport starting, nothing to do."
else:
echo "Unknown JACK transport state."
discard jclient.clientClose

View File

@ -13,7 +13,7 @@ proc getJackLibName: string =
result = "(|/usr/local/lib/|/opt/homebrew/lib/|/opt/local/lib/)libjack.dylib"
else:
result = "libjack.so.0"
{.push dynlib: getJackLibName().}
# ------------------------------ Constants --------------------------------
@ -24,14 +24,15 @@ const
JACK_DEFAULT_AUDIO_TYPE* = "32 bit float mono audio"
JACK_DEFAULT_MIDI_TYPE* = "8 bit raw midi"
# ----------------------------- Custom Types ------------------------------
type
Time* = culonglong
NFrames* = culong
Uuid* = culonglong
PortId* = culong
PortTypeId* = culong
Time* = uint64
NFrames* = uint32
Uuid* = uint64
PortId* = uint32
PortTypeId* = uint32
DefaultAudioSample* = cfloat
type
@ -42,8 +43,7 @@ type
type
MidiData* = uint8
MidiEvent* {.importc: "jack_midi_event_t", header: "<jack/midiport.h>".} = object
MidiEvent* = object
time*: NFrames
size*: csize_t
buffer*: ptr UncheckedArray[MidiData]
@ -78,10 +78,10 @@ type
type
JackPortFlags* {.size: sizeof(culong) pure.} = enum
PortIsInput = 0x1,
PortIsOutput = 0x2,
PortIsPhysical = 0x4,
PortCanMonitor = 0x8,
PortIsInput = 0x01,
PortIsOutput = 0x02,
PortIsPhysical = 0x04,
PortCanMonitor = 0x08,
PortIsTerminal = 0x10
type
@ -89,7 +89,86 @@ type
CaptureLatency,
PlaybackLatency
# Transport
type
PositionBits* {.size: sizeof(cint).} = enum
PositionBBT = 0x10,
PositionTimecode = 0x20,
BBTFrameOffset = 0x40,
AudioVideoRatio = 0x80,
VideoFrameOffset = 0x100,
TickDouble = 0x200
TransportState* {.size: sizeof(cint).} = enum
TransportStopped = 0,
TransportRolling = 1,
TransportLooping = 2,
TransportStarting = 3,
TransportNetStarting = 4
type
Position* = object
unique1*: uint64
usecs*: Time
frameRate*: NFrames
frame*: NFrames
valid*: PositionBits
bar*: int32
beat*: int32
tick*: int32
barStartTick*: cdouble
beatsPerBar*: cfloat
beatType*: cfloat
ticksPerBeat*: cdouble
beatsPerMinute*: cdouble
frameTime*: cdouble
nextTime*: cdouble
bbtOffset*: NFrames
audioFramesPerVideoFrame*: cfloat
videoOffset*: NFrames
tickDouble*: cdouble
padding*: array[5, int32]
unique2*: uint64
PositionP* = ptr Position
#[ DEPRECATED
typedef enum {
JackTransportState = 0x1,
JackTransportPosition = 0x2,
JackTransportLoop = 0x4,
JackTransportSMPTE = 0x8,
JackTransportBBT = 0x10
} jack_transport_bits_t;
typedef struct {
jack_nframes_t frame_rate;
jack_time_t usecs;
jack_transport_bits_t valid;
jack_transport_state_t transport_state;
jack_nframes_t frame;
jack_nframes_t loop_start;
jack_nframes_t loop_end;
long smpte_offset
float smpte_frame_rate;
int bar;
int beat;
int tick;
double bar_start_tick;
float beats_per_bar;
float beat_type;
double ticks_per_beat;
double beats_per_minute;
} jack_transport_info_t;
]#
const
JACK_POSITION_MASK* = (PositionBBT.ord or PositionTimecode.ord)
EXTENDED_TIME_INFO* = true
JACK_TICK_DOUBLE* = true
# Callback function types
type
JackProcessCallback* = proc (nframes: NFrames; arg: pointer): cint {.cdecl.}
JackThreadCallback* = proc (arg: pointer): pointer {.cdecl.}
@ -109,6 +188,10 @@ type
JackInfoCallback* = proc (msg: cstring) {.cdecl.}
JackErrorCallback* = proc (msg: cstring) {.cdecl.}
JackSyncCallback* = proc (state: TransportState; pos: ptr Position; arg: pointer): cint {.cdecl.}
JackTimebaseCallback* = proc (state: TransportState; nframes: NFrames; pos: ptr Position; newPos: cint;
arg: pointer) {.cdecl.}
# ----------------------------- Version info ------------------------------
@ -133,8 +216,9 @@ proc free*(`ptr`: pointer) {.importc: "jack_free".}
proc clientOpen*(clientName: cstring; options: cint; status: ptr cint): ClientP {.
varargs, importc: "jack_client_open".}
# DEPRECATED
# proc clientNew*(clientName: cstring): ClientP {.importc: "jack_client_new".}
#[ DEPRECATED
jack_client_t * jack_client_new (const char *client_name)
]#
# int jack_client_close (jack_client_t *client)
proc clientClose*(client: ClientP): cint {.importc: "jack_client_close"}
@ -145,13 +229,11 @@ proc clientNameSize*(): cint {.importc: "jack_client_name_size"}
# char * jack_get_client_name (jack_client_t *client)
proc getClientName*(client: ClientP): cstring {.importc: "jack_get_client_name".}
# char *jack_get_uuid_for_client_name (jack_client_t *client,
# const char *client_name)
# char *jack_get_uuid_for_client_name (jack_client_t *client, const char *client_name)
proc getUuidForClientName*(client: ClientP; clientName: cstring): cstring {.
importc: "jack_get_uuid_for_client_name".}
# char *jack_get_client_name_by_uuid (jack_client_t *client,
# const char *client_uuid )
# char *jack_get_client_name_by_uuid (jack_client_t *client, const char *client_uuid)
proc getClientNameByUuid*(client: ClientP; clientUuid: cstring): cstring {.
importc: "jack_get_client_name_by_uuid".}
@ -173,15 +255,14 @@ proc getClientPid*(name: cstring): cint {.importc: "jack_get_client_pid".}
# FIXME: not implemented yet
# jack_native_thread_t jack_client_thread_id (jack_client_t *client)
# proc clientThreadId*(client: ClientP): NativeThread {.
# importc: "jack_client_thread_id".}
# proc clientThreadId*(client: ClientP): NativeThread {.importc: "jack_client_thread_id".}
# int jack_is_realtime (jack_client_t *client)
proc isRealtime*(client: ClientP): cint {.importc: "jack_is_realtime".}
# DEPRECATED
# proc threadWait*(client: ClientP; status: cint): NFrames {.
# importc: "jack_thread_wait".}
#[ DEPRECATED
jack_nframes_t jack_thread_wait (jack_client_t *client, int status)
]#
# jack_nframes_t jack_cycle_wait (jack_client_t* client)
proc cycleWait*(client: ClientP): NFrames {.importc: "jack_cycle_wait".}
@ -254,9 +335,9 @@ proc getSampleRate*(client: ClientP): NFrames {.importc: "jack_get_sample_rate".
# jack_nframes_t jack_get_buffer_size (jack_client_t *)
proc getBufferSize*(client: ClientP): NFrames {.importc: "jack_get_buffer_size".}
# DEPRECATED
# proc engineTakeoverTimebase*(a1: ClientP): cint {.
# importc: "jack_engine_takeover_timebase".}
#[ DEPRECATED
int jack_engine_takeover_timebase (jack_client_t *)
]#
# float jack_cpu_load (jack_client_t *client)
proc cpuLoad*(client: ClientP): cfloat {.importc: "jack_cpu_load".}
@ -309,24 +390,22 @@ proc portConnectedTo*(port: PortP; portName: cstring): cint {.importc: "jack_por
# const char ** jack_port_get_connections (const jack_port_t *port)
#
# CAVEAT: The caller is responsible for calling jack_free() on any non-NULL
# returned value.
# CAVEAT: The caller is responsible for calling jack_free() on any non-NULL returned value.
proc portGetConnections*(port: PortP): cstringArray {.importc: "jack_port_get_connections".}
# const char ** jack_port_get_all_connections (const jack_client_t *client,
# const jack_port_t *port)
#
# CAVEAT: The caller is responsible for calling jack_free() on any non-NULL
# returned value.
# CAVEAT: The caller is responsible for calling jack_free() on any non-NULL returned value.
proc portGetAllConnections*(client: ClientP; port: PortP): cstringArray {.
importc: "jack_port_get_all_connections".}
#[ DEPRECATED
proc portTie*(src: PortP; dst: PortP): cint {.importc: "jack_port_tie".}
int jack_port_tie (jack_port_t *src, jack_port_t *dst)
proc portUntie*(port: PortP): cint {.importc: "jack_port_untie".}
int jack_port_untie (jack_port_t *port)
proc portSetName*(port: PortP; portName: cstring): cint {.importc: "jack_port_set_name".}
int jack_port_set_name (jack_port_t *port, const char *port_name)
]#
# int jack_port_rename (jack_client_t* client, jack_port_t *port, const char *port_name)
@ -356,6 +435,25 @@ proc portEnsureMonitor*(port: PortP; onoff: cint): cint {.
# int jack_port_monitoring_input (jack_port_t *port)
proc portMonitoringInput*(port: PortP): cint {.importc: "jack_port_monitoring_input".}
# ------------------------------ Port Lookup ------------------------------
# const char ** jack_get_ports (jack_client_t *client,
# const char *port_name_pattern,
# const char *type_name_pattern,
# unsigned long flags)
#
# CAVEAT: The caller is responsible for calling jack_free() on any non-NULL returned value.
proc getPorts*(client: ClientP; portNamePattern: cstring;
typeNamePattern: cstring; flags: culong): cstringArray {.importc: "jack_get_ports".}
# jack_port_t * jack_port_by_name (jack_client_t *client, const char *port_name)
proc portByName*(client: ClientP; portName: cstring): PortP {.importc: "jack_port_by_name".}
# jack_port_t * jack_port_by_id (jack_client_t *client, jack_port_id_t port_id)
proc portById*(client: ClientP; portId: PortId): PortP {.importc: "jack_port_by_id".}
# ------------------------------ Connections ------------------------------
# int jack_connect (jack_client_t *client,
@ -381,6 +479,7 @@ proc portTypeSize*(): cint {.importc: "jack_port_type_size".}
proc portTypeGetBufferSize*(client: ClientP; portType: cstring): csize_t {.
importc: "jack_port_type_get_buffer_size".}
# --------------------------------- MIDI ----------------------------------
# jack_nframes_t jack_midi_get_event_count (void *port_buffer)
@ -407,6 +506,7 @@ proc midiEventWrite*(portBuffer: pointer, time: NFrames, data: ptr MidiData, dat
# uint32_t jack_midi_get_lost_event_count (void *port_buffer)
proc midiGetLostEventCount*(portBuffer: pointer): uint32 {.importc: "jack_midi_get_lost_event_count".}
# -------------------------------- Latency --------------------------------
#[ FIXME: not implemented yet
@ -429,23 +529,6 @@ proc portGetTotalLatency*(client: ClientP; port: PortP): NFrames {.importc: "jac
proc recomputeTotalLatency*(a1: ClientP; port: PortP): cint {.importc: "jack_recompute_total_latency".}
]#
# ------------------------------ Port Lookup ------------------------------
# const char ** jack_get_ports (jack_client_t *client,
# const char *port_name_pattern,
# const char *type_name_pattern,
# unsigned long flags)
#
# CAVEAT: The caller is responsible for calling jack_free() on any non-NULL
# returned value.
proc getPorts*(client: ClientP; portNamePattern: cstring;
typeNamePattern: cstring; flags: culong): cstringArray {.importc: "jack_get_ports".}
# jack_port_t * jack_port_by_name (jack_client_t *client, const char *port_name)
proc portByName*(client: ClientP; portName: cstring): PortP {.importc: "jack_port_by_name".}
# jack_port_t * jack_port_by_id (jack_client_t *client, jack_port_id_t port_id)
proc portById*(client: ClientP; portId: PortId): PortP {.importc: "jack_port_by_id".}
# ----------------------------- Time handling -----------------------------
@ -459,10 +542,10 @@ proc frameTime*(client: ClientP): NFrames {.importc: "jack_frame_time".}
proc lastFrameTime*(client: ClientP): NFrames {.importc: "jack_last_frame_time".}
# int jack_get_cycle_times(const jack_client_t *client,
# jack_nframes_t *current_frames,
# jack_time_t *current_usecs,
# jack_time_t *next_usecs,
# float *period_usecs)
# jack_nframes_t *current_frames,
# jack_time_t *current_usecs,
# jack_time_t *next_usecs,
# float *period_usecs)
proc getCycleTimes*(client: ClientP; currentFrames: ptr NFrames;
currentUsecs: ptr Time; nextUsecs: ptr Time;
periodUsecs: ptr cfloat): cint {.importc: "jack_get_cycle_times".}
@ -476,6 +559,50 @@ proc timeToFrames*(client: ClientP; time: Time): NFrames {.importc: "jack_time_t
# jack_time_t jack_get_time(void)
proc getTime*(): Time {.importc: "jack_get_time".}
# ------------------------------- Transport -------------------------------
# int jack_release_timebase (jack_client_t *client)
proc releaseTimebase*(client: ClientP): cint {.importc: "jack_release_timebase".}
# int jack_set_sync_callback (jack_client_t *client, JackSyncCallback sync_callback, void *arg)
proc setSyncCallback*(client: ClientP; syncCallback: JackSyncCallback; arg: pointer): cint {.
importc: "jack_set_sync_callback".}
# int jack_set_sync_timeout (jack_client_t *client, jack_time_t timeout)
proc setSyncTimeout*(client: ClientP; timeout: Time): cint {.importc: "jack_set_sync_timeout".}
# int jack_set_timebase_callback (jack_client_t *client,
# int conditional,
# JackTimebaseCallback timebase_callback,
# void *arg)
proc setTimebaseCallback*(client: ClientP; conditional: cint; timebaseCallback: JackTimebaseCallback;
arg: pointer): cint {.
importc: "jack_set_timebase_callback".}
# int jack_transport_locate (jack_client_t *client, jack_nframes_t frame)
proc transportLocate*(client: ClientP; frame: NFrames): cint {.importc: "jack_transport_locate".}
# jack_transport_state_t jack_transport_query (const jack_client_t *client, jack_position_t *pos)
proc transportQuery*(client: ClientP; pos: PositionP): TransportState {.importc: "jack_transport_query".}
# jack_nframes_t jack_get_current_transport_frame (const jack_client_t *client)
proc getCurrentTransportFrame*(client: ClientP): NFrames {.importc: "jack_get_current_transport_frame".}
# int jack_transport_reposition (jack_client_t *client, const jack_position_t *pos)
proc transportReposition*(client: ClientP; pos: PositionP): cint {.importc: "jack_transport_reposition".}
# void jack_transport_start (jack_client_t *client)
proc transportStart*(client: ClientP) {.importc: "jack_transport_start".}
# void jack_transport_stop (jack_client_t *client)
proc transportStop*(client: ClientP) {.importc: "jack_transport_stop".}
#[ DEPRECATED
void jack_get_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)
]#
# ---------------------------- Error handling -----------------------------
proc setErrorFunction*(errorCallback: JackErrorCallback) {.importc: "jack_set_error_function".}