X-Git-Url: https://git.rrq.au/?a=blobdiff_plain;f=alsa-dispatcher.lsp;h=df8863ba06ed028f9cc33bede1f09522072096bb;hb=e28406969e4f31d27e1c902e3fb8c3b3e774817d;hp=8468a12c02392bb6295a3cf427daaf1929215268;hpb=d13b34b016a6b4b63de2720a7574527fa1c94b46;p=rrq%2Fnewlisp%2Falsa-dispatcher.git diff --git a/alsa-dispatcher.lsp b/alsa-dispatcher.lsp old mode 100644 new mode 100755 index 8468a12..df8863b --- a/alsa-dispatcher.lsp +++ b/alsa-dispatcher.lsp @@ -1,3 +1,5 @@ +#!/usr/bin/newlisp + ;; This is the main script for the pcm-dispatch tool ;; last main-arg nominates the configuration file @@ -7,35 +9,35 @@ (signal 2 (fn (x) (exit 0))) (signal 15 (fn (x) (exit 0))) +; Optionally print output to stderr and optionally exit with code (define (die N) (when (args) (write-line 2 (join (map string (args)) " "))) (when (number? N) (exit N))) -(define (enlist X) (if (list? X) X (list X))) - -(define (comment? LINE) (and (regex "^\\s*(#|$)" LINE 0) true)) - -(define (first-word LINE) (and (regex "^\\s*(\\S+)" LINE 0) $1)) +; Return non-nil for comment line (starts with # or is blank) +(define (comment? LINE) (regex "^\\s*(#|$)" LINE 0)) -(define (prog1 X) X) +; Return list of space-separated words on a line +(define (words LINE) + (parse (trim LINE) "\\s+" 0)) -(define (read-config-line LINE) - (map (fn (x) (if (regex "^([^=]+)=(.*)" x) (list $1 $2) x)) - (map trim (find-all "([^, ]+)" LINE $1 0)))) +; Read the configuration file and return it as list of plugs or the default +(define (read-config FILE) + (or + (if (read-file FILE) (map words (clean comment? (parse $it "\n")))) + '(("plughw")) ; the default priority list + )) ;; ############################################################ ;; Load Configuration (~/.alsa-dispatcher) ; Format: one-liners for each option, ignoring comment lines starting ; with # and blank lines. (constant - 'HOME (env "HOME") - 'CONFIG (format "%s/.alsa-dispatcher" HOME) - 'CFGLINES (if (read-file CONFIG) (clean comment? (parse $it "\n" )) - '("bt,latency=1000" "plughw")) - 'CFGMAP (map read-config-line CFGLINES) - 'PCM-LIST (map first-word CFGLINES) + 'CFGMAP (read-config (format "%s/.alsa-dispatcher" (env "HOME"))) + 'PCM-LIST (map first CFGMAP) ) +; Return value of configuration setting KEY for plug PCM, or DEFAULT. (define (cfg-lookup PCM KEY DEFAULT) (if (if (assoc PCM CFGMAP) (lookup KEY $it)) (read-expr $it) DEFAULT)) @@ -66,8 +68,7 @@ ) ;; (snd_pcm_close PCM) - Close PCM handle. Closes the given PCM handle -;; and frees all associated resources. (The PCM reference is probably -;; "invalid" after closing) +;; and frees all associated resources. (import libasound.so "snd_pcm_close" "int" "void*" ; snd_pcm_t *pcm ) @@ -105,15 +106,6 @@ "unsigned int" ; unsigned int latency ) -;; (snd_pcm_wait PCM TIMEOUT) - Wait for a PCM to become ready. -;; Returns a positive value on success otherwise a negative error code -;; (-EPIPE [-32] for the xrun and -ESTRPIPE [-86] for the suspended -;; status, others for general errors) -(import libasound.so "snd_pcm_wait" "int" - "void*" ; snd_pcm_t *pcm - "int" ; int timeout - ) - ;; (snd_pcm_writei PCM BUFFER FRAMES) - Write interleaved frames to a ;; PCM. If the blocking behaviour is selected and the PCM is running, ;; then routine waits until all requested frames are played or put to @@ -125,61 +117,54 @@ "long" ; snd_pcm_uframes_t size ) -;; Open a PCM by name +;; Open a PCM by name. Returns list of (NAME PCM) or nil. (define (open-pcm NAME) - (snd_pcm_open NAME SND_PCM_STREAM_PLAYBACK SND_PCM_MODE_BLOCK)) - -;; Setup PCM -(define (setup-pcm PCM) - (snd_pcm_set_params PCM - (cfg-lookup PCM "format" SND_PCM_FORMAT_S16_LE) - SND_PCM_ACCESS_RW_INTERLEAVED - (cfg-lookup PCM "channels" 2) - (cfg-lookup PCM "rate" 48000) - 1 ; soft resample (0/1) - (cfg-lookup PCM "latency" 100000) ; (microseconds) - )) + (if (snd_pcm_open NAME SND_PCM_STREAM_PLAYBACK SND_PCM_MODE_BLOCK) + (list NAME $it))) + +;; Setup PCM. Preset format. access, channels, rate and soft resample. +;; Configurable latency. +(define (setup-pcm PCM NAME) + (let ((LATENCY (cfg-lookup NAME "latency" 100000))) + (snd_pcm_set_params PCM SND_PCM_FORMAT_S16_LE SND_PCM_ACCESS_RW_INTERLEAVED + 2 48000 1 LATENCY ))) ;; ############################################################ ; The main program -;; redirect stdout/err to /dev/null -(when nil +; redirect stdout/err to /dev/null (flagged for debugging purposes) +(when true (let ((REDIR (open "/dev/null" "append"))) (when (< REDIR) (exit 1)) - (when (< (libc:dup2 REDIR 2)) (exit 1)) - (when (< (libc:dup2 REDIR 1)) (exit 1)) + (when (< (dup2 REDIR 2)) (exit 1)) + (when (< (dup2 REDIR 1)) (exit 1)) (close REDIR) )) ; find the first usable PCM -(setf PCM (unless (dolist (NAME PCM-LIST (open-pcm NAME))) - (die 1 "No PCM available"))) +(map set '(NAME PCM) (unless (dolist (N PCM-LIST (open-pcm N))) + (die 1 "No PCM available"))) ; configure the PCM -(when (!= (setf E (setup-pcm PCM))) +(when (!= (setf E (setup-pcm PCM NAME))) (snd_pcm_close PCM) - (die 1 "set params" E)) + (die 1 "setup pcm" E)) -(setf N 0 x 0 E 0 i 0) - -(setf CACHE nil) -(while (> (or (setf N (read 0 BUFFER 20000000)) 0)) - ;(println "reading " N) - (setf CACHE BUFFER) - (while (> N) - ;;(ready PCM) - (when (< (setf E (snd_pcm_writei PCM CACHE (/ N 4)))) +; channel stdin audio onto selected pcm +(while (> (or (setf N (read 0 BUFFER 2000000)) 0)) + (while (> N) + ; N is in bytes while snd_pcm_writei counts "frames" of 4 bytes + (when (< (setf E (snd_pcm_writei PCM BUFFER (/ N 4)))) (snd_pcm_close PCM) - (die 1 "writing" E N)) + (die 1 "writing failed")) (setf E (* E 4)) - (setf CACHE (E CACHE)) - ;(println "wrote " E) + (setf BUFFER (E BUFFER)) (dec N E) ) ) -(snd_pcm_wait PCM -1) +; let the pcm complete its output (snd_pcm_drain PCM) + (snd_pcm_close PCM) (exit 0)