Coverage report: /home/ellis/comp/core/ffi/jack/pkg.lisp
Kind | Covered | All | % |
expression | 10 | 511 | 2.0 |
branch | 0 | 20 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; pkg.lisp --- JACK Audio Connection Kit FFI
7
(:use :cl :std :log :sb-alien)
9
:jack-get-version-string
12
:jack-client-name-size
15
:jack-port-type-get-buffer-size
27
:jack-set-process-callback
28
:jack-midi-clear-buffer
29
:jack-midi-event-reserve
40
:jack-ringbuffer-create
41
:jack-ringbuffer-reset
42
:jack-ringbuffer-get-write-vector
44
:jack-ringbuffer-write-advance
45
:jack-ringbuffer-write-space
46
:jack-ringbuffer-write
47
:jack-ringbuffer-get-read-vector
49
:jack-ringbuffer-read-space
50
:*jack-midi-output-port*
51
:*jack-midi-input-port*
56
:jack-add-event-this-period
57
:jack-add-event-this-frame
60
:seqhash-midi-note-off
61
:seqhash-midi-program-change
62
:seqhash-midi-control-change
63
:seqhash-midi-pitch-wheel-msg
64
:seqhash-clear-note-offs
65
:jack-start-dur-to-frames
69
:jack-all-notes-off-and-kill-seq
72
:jack-seq-hush-this-seq
73
:jack-seq-hush-all-seqs
76
:jack-handle-event-seqs
81
:*jack-audio-input-channels*
82
:*jack-audio-output-channels*
83
:*jack-audio-input-ports*
84
:*jack-audio-output-ports*
86
:jack-process-callback-silence
87
:jack-connect-audio-client-to-system-output
91
:+jack-default-midi-type+
92
:+jack-default-audio-type+))
96
(defconstant +jack-max-frames+ 4294967295)
97
(define-alien-loader :jack "/usr/lib/")
99
(defar jack-get-version-string c-string)
101
(define-alien-type jack-nframes-t unsigned-int)
103
(define-alien-type jack-port-t (* t))
104
(define-alien-type jack-options-t (* t))
105
(define-alien-type jack-time-t unsigned-long)
106
(define-alien-type jack-midi-data-t unsigned-char)
108
(define-constant +jack-default-audio-type+ "32 bit float mono audio" :test 'string=)
109
(define-constant +jack-default-midi-type+ "8 bit raw midi" :test 'string=)
111
(define-alien-type jack-default-audio-sample-t float)
113
(define-alien-enum (jackoptions int)
115
:no-start-server #x01
122
(define-alien-enum (jackportflags int)
129
(defar jack-client-name-size int)
130
(defar jack-client-open (* t)
135
(defar jack-get-sample-rate int
138
(defar jack-port-type-get-buffer-size size-t
140
(port-type c-string))
142
(defar jack-get-buffer-size jack-nframes-t
145
(defar jack-get-client-name c-string
148
(defar jack-port-get-buffer (* t)
150
(frames jack-nframes-t))
152
(defar jack-port-name c-string
153
(port (* jack-port-t)))
155
(defar jack-connect int
157
(source-port c-string)
158
(destination-port c-string))
160
(defar jack-disconnect int
162
(source-port c-string)
163
(destination-port c-string))
165
(defar jack-get-ports (* t)
167
(port_name_pattern c-string)
168
(type_name_pattern c-string)
169
(flags unsigned-long))
171
(defar jack-port-register (* t)
175
(flags unsigned-long)
176
(buffer-size unsigned-long))
178
(defar jack-client-close int
181
(defar jack-activate int
184
(defar jack-deactivate int
187
(defar jack-set-process-callback int
189
(process_callback (* t))
192
(defar jack-midi-clear-buffer void
195
(defar jack-midi-event-reserve (* t)
198
(data-size unsigned-char))
201
(defar jack-get-time jack-time-t)
203
(defar jack-frames-to-time jack-time-t
205
(frames jack-nframes-t))
207
(defar jack-time-to-frames jack-nframes-t
211
(defar jack-last-frame-time jack-nframes-t
214
(defar jack-frame-time jack-nframes-t
217
(define-alien-type jack-ringbuffer
218
(struct jack-ringbuffer-t
226
(define-alien-type jack-ringbuffer-data
227
(struct jack-ringbuffer-data-t
232
(defun rb-data-buf (arr index) ;index is 0 or 1 from jack
234
(sb-alien:deref (sb-alien:cast arr (* jack-ringbuffer-data)) index)
238
(defun rb-data-len (arr index)
240
(sb-alien:deref (sb-alien:cast arr (* jack-ringbuffer-data)) index)
243
;;(rb-data-len vec 0)
244
(defun rb-data-len-p (arr index) ;len=0 := nothing to get
245
(plusp (rb-data-len arr index)))
247
(defar jack-ringbuffer-create (* jack-ringbuffer)
250
(defar jack-ringbuffer-reset void
251
(rb (* jack-ringbuffer)))
253
(defar jack-ringbuffer-get-write-vector void
254
(rb (* jack-ringbuffer))
255
(vec (* jack-ringbuffer-data)))
257
(defar jack-ringbuffer-free void
258
(rb (* jack-ringbuffer)))
260
(defar jack-ringbuffer-write-advance void
261
(rb (* jack-ringbuffer))
264
(defar jack-ringbuffer-write-space size-t
265
(rb (* jack-ringbuffer)))
267
(defar jack-ringbuffer-write size-t
268
(rb (* jack-ringbuffer))
272
(defar jack-ringbuffer-get-read-vector void
273
(rb (* jack-ringbuffer))
274
(vec (* jack-ringbuffer-data)))
276
(defar jack-ringbuffer-read size-t
277
(rb (* jack-ringbuffer))
281
(defar jack-ringbuffer-read-space size-t
282
(rb (* jack-ringbuffer)))
285
;; default global client-name
286
(defparameter *jack-client* nil)
289
(defvar *jack-midi-output-port* nil)
290
(defvar *jack-midi-input-port* nil)
292
;;; global pool of seqs for this client, for separate control [start/stop/pause...]:
293
(defun make-jack-seqs () (make-hash-table :size 1500
295
:rehash-threshold 0.7
297
(defparameter *jack-seqs* (make-jack-seqs))
299
;;; event-seq is a hash-table, keys are frameno at jacks' start-of-period (ie: jack-last-frame-time)
300
(defun make-jack-seq () (make-hash-table))
302
;;; provide one default seq for global queues, external schedulers etc:
305
(setf (gethash '*jack-seq* *jack-seqs*) (make-jack-seq)))
309
;; TODO 2025-03-25: use dat/midi
310
;; TODO: expand with support for all midi-messages
312
(defun jack-add-event-this-period (seq period event)
313
(setf (gethash period seq)
314
(sort (nconc (gethash period seq) (list event))
315
#'(lambda (a b) (< (car a) (car b))))))
317
(defun jack-add-event-this-frame (seq frame event)
318
(push event (gethash frame seq)))
320
;;; SEQUENCING EVENTS
322
;; seq is a hashtable, key'ing on frame-numbers
324
;; version hashing on frame-number
325
(defun seqhash-midi-event (seq frame event)
326
(jack-add-event-this-frame seq frame event))
328
;;; using midi-classes:
330
(defun seqhash-midi-note-on (seq frame noteno velocity &optional (channel 1))
331
(let ((event (make-instance 'midi:note-on-message frame noteno velocity channel)))
332
(seqhash-midi-event seq frame event)))
334
(defun seqhash-midi-note-off (seq frame noteno velocity &optional (channel 1))
335
(let ((event (make-instance 'midi:note-off-message frame noteno velocity channel)))
336
(seqhash-midi-event seq frame event)))
338
(defun seqhash-midi-program-change (seq frame program &optional (channel 1))
339
(let ((event (make-instance 'midi:program-change-message :time frame :program program :status channel)))
340
(seqhash-midi-event seq frame event)))
342
(defun seqhash-midi-control-change (seq frame control value &optional (channel 1))
343
(let ((event (make-instance 'midi::control-change-message frame control value channel)))
344
(seqhash-midi-event seq frame event)))
346
(defun seqhash-midi-pitch-wheel-msg (seq frame bend &optional (channel 1))
347
;; expects values between -8192->8191
348
(let ((event (make-instance 'midi:pitch-bend-message :time frame :value bend :status channel)))
349
(seqhash-midi-event seq frame event)))
351
;; erase pending note-offs for interval - don't shut off later arriving notes
352
(defun seqhash-clear-note-offs (seq startframe endframe noteno &optional (channel 1))
353
(maphash #'(lambda (key val)
354
(let ((event (car val)))
355
(when (and (<= startframe key endframe)
356
(typep event 'midi::note-off-message)
357
(eql (midi::message-key event) noteno)
358
(eql (midi::message-channel event) channel))
362
;; interface to higher-level funcs:
364
(defun jack-start-dur-to-frames (start dur)
365
(let* ((dur-frames (sec->frame dur))
366
(startframe (jack-frame-now start))
367
(endframe (+ startframe dur-frames -1)))
368
(values startframe endframe)))
370
(defun jack-play-event (seq start event)
371
(seqhash-midi-event seq (jack-frame-now start) event))
373
(defun jack-play-note (seq start dur noteno &optional (vel 80) (chan 0))
374
(let* ((startframe (jack-frame-now start))
375
(endframe (+ startframe (sec->frame dur) -1)))
376
(seqhash-clear-note-offs seq startframe endframe noteno chan)
377
(seqhash-midi-note-on seq startframe noteno vel chan)
378
;; (sleep (/ (jack-get-buffer-size *jack-client*)
379
;; (jack-get-sample-rate *jack-client*)))
380
(seqhash-midi-note-off seq endframe noteno 0 chan)))
382
(defun jack-all-notes-off (seq)
383
(let ((sounding-notes '()))
384
(maphash #'(lambda (key val)
385
(declare (ignore key))
386
(mapc #'(lambda (ev) (push (list (midi:message-key ev) (1- (midi:message-channel ev)))
391
(mapc #'(lambda (note)
392
(seqhash-midi-note-off seq (jack-frame-now) (car note) 0 (cadr note)))
395
(defun jack-all-notes-off-and-kill-seq (seq)
396
(jack-all-notes-off seq)
398
(jack-get-buffer-size *jack-client*)
399
(jack-get-sample-rate *jack-client*))))
400
(remhash seq *jack-seqs*))
402
(defun jack-reset (&optional (seq *jack-seq*))
405
(seqhash-midi-note-off seq (jack-frame-now) key 0 ch))))
409
(defun jack-reset-channels ()
410
(loop for ch from 0 to 16
411
do (seqhash-midi-program-change *jack-seq* (jack-frame-now) ch ch)))
413
;;(jack-reset-channels)
415
(defun jack-seq-hush-this-seq (seq)
416
(jack-all-notes-off seq))
418
(defun jack-seq-hush-all-seqs ()
419
(maphash #'(lambda (key seq)
420
(declare (ignore key))
421
(jack-all-notes-off-and-kill-seq seq))
424
(defparameter *jack-playing* t) ;nil=shut up
425
;; (setf *playing* nil)
427
(defun play-from-seq (port-buf seq)
429
(let ((this-period (jack-last-frame-time *jack-client*)))
430
(loop for offset from 0 below (jack-get-buffer-size *jack-client*)
431
for key from this-period ;events hashed on frameno
432
for events = (gethash key seq)
435
(dolist (midimsg events)
436
(let ((buffer (jack-midi-event-reserve port-buf offset 3))) ;offset inside period
437
(unless (null-alien buffer)
438
(setf (deref buffer) (midi::write-message midimsg)))))
439
(remhash key seq)))))
441
;; callback function handles seq-events, plugged into jacks
444
(defun jack-handle-event-seqs (nframes)
445
(let ((port-buf (jack-port-get-buffer *jack-midi-output-port* nframes)))
446
(jack-midi-clear-buffer port-buf)
447
;;(play-from-seq port-buf *jack-seq*)
448
(maphash #'(lambda (key seq)
449
(declare (ignore key))
450
(play-from-seq port-buf seq))
453
(defun jack-init-midi ()
454
;; get up and running
455
(unless *jack-client*
456
(setf *jack-client* (jack-client-open "lisp-jack" 0 0)))
457
(setf *jack-midi-output-port*
458
(let ((port (jack-port-register *jack-client*
460
+jack-default-midi-type+
461
(jackportflags :is-output)
463
(when (zerop (sb-sys:sap-int (alien-sap port))) ;0 if not allocated
465
(cerror "Set *jack-midi-output-port* to -1" "*jack-midi-output-port* for Jack not allocated - check jack-server"))
469
(defun jack-period-now (&optional sek)
470
(+ (jack-last-frame-time *jack-client*)
471
(jack-get-buffer-size *jack-client*)
472
(round (if sek (* sek (jack-get-sample-rate *jack-client*)) 0))))
474
;;; too late to schedule things inside current period, this looks up
475
;;; current frame with exactly one period latency:
477
(defun jack-frame-now (&optional sek)
478
(round (+ (jack-frame-time *jack-client*)
479
(jack-get-buffer-size *jack-client*)
480
(if sek (* sek (jack-get-sample-rate *jack-client*)) 0))))
482
(defun ms->frame (ms)
483
(round (* ms (jack-get-sample-rate *jack-client*)) 1000))
485
(defun sec->frame (sec)
486
(round (* sec (jack-get-sample-rate *jack-client*))))
488
(defun frame->period-offset (frame)
489
"returns 2 frame nos: start of period & offset within period"
490
(let ((bufsiz (jack-get-buffer-size *jack-client*)))
491
(multiple-value-bind (n rem)
493
(values (* n bufsiz) rem))))
495
(defparameter *jack-audio-input-channels* 2)
496
(defparameter *jack-audio-output-channels* 2)
498
(defparameter *jack-audio-input-ports* nil)
499
(defparameter *jack-audio-output-ports* nil)
501
(defun jack-init-audio ()
502
(unless *jack-client*
503
(setf *jack-client* (jack-client-open "lisp-jack" 0 0)))
504
(setf *jack-audio-input-ports*
505
(loop for chan from 0 below *jack-audio-output-channels*
507
(let ((port (jack-port-register
509
(format nil "in_~A" chan)
510
+jack-default-audio-type+
511
(jackportflags :is-input)
513
(when (zerop (sb-sys:sap-int (alien-sap port))) ;0 if not allocated
515
(cerror (format nil "Set jack-input-port ~A to -1" chan)
516
"*jack-audio-input-ports* not allocated"))
518
*jack-audio-output-ports*
519
(loop for chan from 0 below *jack-audio-output-channels*
521
(let ((port (jack-port-register
523
(format nil "out_~A" chan)
524
+jack-default-audio-type+
525
(jackportflags :is-output)
527
(when (zerop (sb-sys:sap-int (alien-sap port))) ;0 if not allocated
529
(cerror (format nil "Set jack-output-port ~A to -1" chan)
530
"*jack-audio-output-ports* not allocated"))
533
;; provide default-callback which just copies in to out:
534
(define-alien-callable jack-process-callback-silence int ((nframes jack-nframes-t) (arg (* t)))
535
(when (fboundp 'jack-handle-event-seqs) (jack-handle-event-seqs nframes))
536
(loop for inport in *jack-audio-input-ports*
537
for outport in *jack-audio-output-ports*
539
(let ((in (jack-port-get-buffer inport nframes))
540
(out (jack-port-get-buffer outport nframes)))
542
(* nframes #.(std/alien::foreign-type-size 'size-t)))))
545
(defun jack-connect-audio-client-to-system-output ()
546
(let ((ports (take 2 *jack-audio-output-ports*)))
547
(with-alien ((l jack-port-t (pop ports))
548
(r jack-port-t (pop ports)))
549
(or (not (minusp (jack-connect *jack-client* #1=(jack-port-name (addr l)) "playback_FL")))
550
(warn "could not connect JACK port ~A to output-port playback_FL" #1#))
551
(or (not (minusp (jack-connect *jack-client* #2=(jack-port-name (addr r)) "playback_FR")))
552
(warn "could not connect JACK port ~A to output-port playback_FR" #2#)))))