aboutsummaryrefslogtreecommitdiff
path: root/guile/starlet/open-sound-control/utils.scm
diff options
context:
space:
mode:
Diffstat (limited to 'guile/starlet/open-sound-control/utils.scm')
-rw-r--r--guile/starlet/open-sound-control/utils.scm467
1 files changed, 467 insertions, 0 deletions
diff --git a/guile/starlet/open-sound-control/utils.scm b/guile/starlet/open-sound-control/utils.scm
new file mode 100644
index 0000000..567c2b3
--- /dev/null
+++ b/guile/starlet/open-sound-control/utils.scm
@@ -0,0 +1,467 @@
+;;
+;; starlet/open-sound-control/utils.scm
+;;
+;; Copyright © 2020-2023 Thomas White <taw@bitwiz.org.uk>
+;;
+;; This file is part of Starlet.
+;;
+;; Starlet is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+;;
+(define-module (starlet open-sound-control utils)
+ #:use-module (starlet attributes)
+ #:use-module (starlet playback)
+ #:use-module (starlet selection)
+ #:use-module (starlet fixture)
+ #:use-module (starlet engine)
+ #:use-module (starlet state)
+ #:use-module (starlet utils)
+ #:use-module (starlet colours)
+ #:use-module (open-sound-control client)
+ #:use-module (open-sound-control server-thread)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-26)
+ #:use-module (ice-9 receive)
+ #:export (osc-playback-controls
+ osc-select-button
+ osc-parameter-encoder
+ osc-smart-potentiometer
+ osc-cmy-potentiometer
+ osc-state-fader
+ send-selection-updates-to))
+
+
+(define* (osc-playback-controls pb server addr go-button stop-button back-button
+ #:key (min-time-between-presses 0.2))
+
+ (let ((time-last-press 0))
+ (add-osc-method
+ server
+ (string-append go-button "/press")
+ ""
+ (lambda ()
+ (let ((time-this-press (hirestime)))
+ (if (> time-this-press (+ time-last-press min-time-between-presses))
+ (go! pb)
+ (display "Too soon after last press!\n"))
+ (set! time-last-press time-this-press)))))
+
+ (add-osc-method server (string-append stop-button "/press") "" (lambda () (stop! pb)))
+ (add-osc-method server (string-append back-button "/press") "" (lambda () (back! pb)))
+
+ ;; LEDs
+ (osc-send addr (string-append back-button "/set-led") 'green)
+
+ (add-and-run-hook!
+ (state-change-hook pb)
+ (lambda (new-state)
+
+ (if (eq? new-state 'running)
+ (osc-send addr (string-append stop-button "/set-led") 'green)
+ (osc-send addr (string-append stop-button "/set-led") 'off))
+
+ (cond
+ ((eq? new-state 'pause)
+ (osc-send addr (string-append go-button "/set-led") 'orange))
+ ((eq? new-state 'ready)
+ (osc-send addr (string-append go-button "/set-led") 'green))
+ ((eq? new-state 'running)
+ (osc-send addr (string-append go-button "/set-led") 'green))
+ (else
+ (osc-send addr (string-append go-button "/set-led") 'off))))
+
+ (playback-state pb)))
+
+
+(define (osc-select-button fix server addr button)
+
+ (add-osc-method
+ server
+ (string-append button "/press")
+ ""
+ (lambda ()
+ (toggle-sel fix)))
+
+ (add-and-run-hook!
+ selection-hook
+ (lambda (sel)
+ (if (selected? fix)
+ (osc-send addr (string-append button "/set-led") 'orange)
+ (osc-send addr (string-append button "/set-led") 'red)))
+ (get-selection)))
+
+
+(define (encoder-inc attr-id n)
+ (for-each
+ (lambda (fix)
+ (let ((attr (find-attr fix attr-id))
+ (cval (current-value fix attr-id)))
+ (cond
+ ((eq? 'continuous (get-attr-type attr))
+ (at fix attr-id (+ cval n)))
+ ((eq? 'list (get-attr-type attr))
+ (if (> n 0)
+ (at fix attr-id (next-attr-item attr cval))
+ (at fix attr-id (prev-attr-item attr cval)))))))
+ (get-selection)))
+
+
+(define (osc-parameter-encoder attr server addr encoder)
+
+ (add-osc-method server (string-append encoder "/inc") ""
+ (lambda () (encoder-inc attr 3)))
+
+ (add-osc-method server (string-append encoder "/dec") ""
+ (lambda () (encoder-inc attr -3)))
+
+ (add-osc-method server (string-append encoder "/inc-fine") ""
+ (lambda () (encoder-inc attr 1)))
+
+ (add-osc-method server (string-append encoder "/dec-fine") ""
+ (lambda () (encoder-inc attr -1)))
+
+ (add-and-run-hook!
+ selection-hook
+ (lambda (sel)
+ (if (any
+ (lambda (fix)
+ (fixture-has-attr? fix attr))
+ (get-selection))
+ (osc-send addr (string-append encoder "/set-led") 'green)
+ (osc-send addr (string-append encoder "/set-led") 'off)))
+ (get-selection)))
+
+
+(define (ccval->percent n)
+ (/ (* n 100) 127))
+
+
+(define (osc-state-fader server addr fader state)
+ (let ((fader-val 0))
+ (register-state!
+ (lighting-state
+ (state-for-each
+ (lambda (fix attr val)
+ (at fix attr
+ (lambda ()
+
+ (if (intensity? attr)
+
+ ;; Intensity parameters get scaled according to the fader
+ (* 0.01 val (ccval->percent fader-val))
+
+ ;; Non-intensity parameters just get set in our new state,
+ ;; but only if the fader is up!
+ (if (> fader-val 0)
+ val
+ 'no-value)))))
+ state)))
+
+ (osc-send addr (string-append fader "/enable"))
+ (osc-send addr (string-append fader "/set-pickup") 0)
+ (add-osc-method server (string-append fader "/value-change") "i"
+ (lambda (v) (set! fader-val v)))))
+
+
+(define (send-selection-updates-to addr)
+ (add-hook!
+ selection-hook
+ (lambda (sel)
+ (osc-send
+ addr
+ "/starlet/selection/update"
+ (get-selection-as-string)))))
+
+
+(define (fader-up-gradients initial-vals
+ max-vals
+ congruent-val)
+ (map (lambda (initial-val attr-max)
+ (if (< congruent-val 127)
+ (/ (- attr-max initial-val)
+ (- 127 congruent-val))
+ 0))
+ initial-vals
+ max-vals))
+
+
+(define (fader-down-gradients initial-vals
+ min-vals
+ congruent-val)
+ (map (lambda (initial-val attr-min)
+ (if (> congruent-val 0)
+ (/ (- initial-val attr-min)
+ congruent-val)
+ 0))
+ initial-vals
+ min-vals))
+
+
+(define (fixtures-with-attr fixture-list attr-name)
+ (let ((fix-attrs
+ (map (lambda (fix)
+ (let ((attr (find-attr fix attr-name)))
+ (if attr
+ (cons fix attr)
+ (cons #f #f))))
+ fixture-list)))
+ (values
+ (filter (lambda (x) x) (map car fix-attrs))
+ (filter (lambda (x) x) (map cdr fix-attrs)))))
+
+
+(define (current-values fixture-list attr-name)
+ (map (lambda (fix)
+ (current-value fix attr-name))
+ fixture-list))
+
+
+(define-record-type <smart-potentiometer>
+ (smart-pot-record addr
+ pot-method
+ initial-vals
+ min-vals
+ max-vals
+ congruent-val
+ up-gradients
+ dn-gradients)
+ smart-pot?
+ (addr get-target-addr)
+ (pot-method get-method)
+ (initial-vals get-initial-vals set-initial-vals)
+ (min-vals get-min-vals set-min-vals)
+ (max-vals get-max-vals set-max-vals)
+ (congruent-val get-congruent-val set-congruent-val)
+ (up-gradients get-up-gradients set-up-gradients)
+ (dn-gradients get-dn-gradients set-dn-gradients))
+
+
+(define (make-smart-potentiometer server addr pot-method callback)
+
+ (let ((sp (smart-pot-record addr pot-method '() '() '() 0 '() '())))
+
+ (add-osc-method
+ server
+ (string-append pot-method "/value-change")
+ "i"
+ (lambda (new-cc-value)
+ (callback
+ (map
+ (lambda (initial-val gradient)
+ (+ initial-val
+ (* gradient
+ (- new-cc-value (get-congruent-val sp)))))
+ (get-initial-vals sp)
+ (if (> new-cc-value (get-congruent-val sp))
+ (get-up-gradients sp)
+ (get-dn-gradients sp))))))
+
+ sp))
+
+
+(define (reset-gradients sp)
+ (unless (nil? (get-initial-vals sp))
+ (set-congruent-val sp
+ (mean
+ (map
+ (lambda (val min-val max-val)
+ (scale-to-range val (list min-val max-val) '(0 127)))
+ (get-initial-vals sp)
+ (get-min-vals sp)
+ (get-max-vals sp))))
+ (set-up-gradients sp
+ (fader-up-gradients
+ (get-initial-vals sp)
+ (get-max-vals sp)
+ (get-congruent-val sp)))
+ (set-dn-gradients sp
+ (fader-up-gradients
+ (get-initial-vals sp)
+ (get-min-vals sp)
+ (get-congruent-val sp)))
+ (osc-send
+ (get-target-addr sp)
+ (string-append (get-method sp) "/set-pickup")
+ (get-congruent-val sp))))
+
+
+(define (osc-smart-potentiometer attr-name
+ server
+ addr
+ potentiometer)
+
+ (let ((fixtures '()))
+
+ ;; First, create a smart potentiometer object and tell it to
+ ;; set the attribute values in the programmer state
+ (let ((smart-pot
+ (make-smart-potentiometer
+ server
+ addr
+ potentiometer
+ (lambda (new-vals)
+ (for-each
+ (lambda (fix new-val)
+ (set-in-state! programmer-state
+ fix
+ attr-name
+ new-val
+ potentiometer))
+ fixtures new-vals)))))
+
+ ;; Next, set up a selection hook callback to update the list of
+ ;; fixtures we are controlling
+ (add-and-run-hook!
+ selection-hook
+ (lambda (selection)
+ (receive
+ (new-fixtures attrs)
+ (fixtures-with-attr selection attr-name)
+ (if (nil? new-fixtures)
+ (osc-send addr (string-append potentiometer "/disable"))
+ (begin
+ (set! fixtures new-fixtures)
+ (let ((ranges (map get-attr-range attrs)))
+ (set-min-vals smart-pot (map first ranges))
+ (set-max-vals smart-pot (map second ranges)))
+ (set-initial-vals smart-pot (current-values fixtures attr-name))
+ (reset-gradients smart-pot)
+ (osc-send addr (string-append potentiometer "/enable"))))))
+ (get-selection))
+
+ ;; Finally, arrange for the smart potentiometer object to be notified
+ ;; if the values change externally
+ (add-update-hook!
+ programmer-state
+ (lambda (source)
+ (unless (eq? source potentiometer)
+ (set-initial-vals smart-pot (current-values fixtures attr-name))
+ (reset-gradients smart-pot)))))))
+
+
+(define (osc-cmy-potentiometer attr-name server addr c-pot-method m-pot-method y-pot-method)
+
+ (let ((fixtures '())
+ (colours '()))
+
+ (let ((c-pot
+ (make-smart-potentiometer
+ server
+ addr
+ c-pot-method
+ (lambda (new-vals)
+ (set! colours
+ (map
+ (lambda (old-colour new-c)
+ (cmy new-c
+ (magenta old-colour)
+ (yellow old-colour)))
+ (map colour-as-cmy colours) new-vals))
+ (for-each
+ (lambda (fix colour)
+ (set-in-state! programmer-state
+ fix
+ attr-name
+ colour
+ c-pot-method))
+ fixtures colours))))
+
+ (m-pot
+ (make-smart-potentiometer
+ server
+ addr
+ m-pot-method
+ (lambda (new-vals)
+ (set! colours
+ (map
+ (lambda (old-colour new-m)
+ (cmy (cyan old-colour)
+ new-m
+ (yellow old-colour)))
+ (map colour-as-cmy colours) new-vals))
+ (for-each
+ (lambda (fix colour)
+ (set-in-state! programmer-state
+ fix
+ attr-name
+ colour
+ m-pot-method))
+ fixtures colours))))
+
+ (y-pot
+ (make-smart-potentiometer
+ server
+ addr
+ y-pot-method
+ (lambda (new-vals)
+ (set! colours
+ (map
+ (lambda (old-colour new-y)
+ (cmy (cyan old-colour)
+ (magenta old-colour)
+ new-y))
+ (map colour-as-cmy colours) new-vals))
+ (for-each
+ (lambda (fix colour)
+ (set-in-state! programmer-state
+ fix
+ attr-name
+ colour
+ y-pot-method))
+ fixtures colours)))))
+
+ (add-and-run-hook!
+ selection-hook
+ (lambda (selection)
+ (receive
+ (new-fixtures attrs)
+ (fixtures-with-attr selection attr-name)
+ (if (nil? new-fixtures)
+ (begin
+ (osc-send addr (string-append c-pot-method "/disable"))
+ (osc-send addr (string-append m-pot-method "/disable"))
+ (osc-send addr (string-append y-pot-method "/disable")))
+ (begin
+ (set! fixtures new-fixtures)
+ (set-min-vals c-pot (map (lambda (x) 0) fixtures))
+ (set-min-vals m-pot (map (lambda (x) 0) fixtures))
+ (set-min-vals y-pot (map (lambda (x) 0) fixtures))
+ (set-max-vals c-pot (map (lambda (x) 100) fixtures))
+ (set-max-vals m-pot (map (lambda (x) 100) fixtures))
+ (set-max-vals y-pot (map (lambda (x) 100) fixtures))
+ (set! colours (current-values fixtures attr-name))
+ (set-initial-vals c-pot (map cyan (map colour-as-cmy colours)))
+ (set-initial-vals m-pot (map magenta (map colour-as-cmy colours)))
+ (set-initial-vals y-pot (map yellow (map colour-as-cmy colours)))
+ (reset-gradients c-pot)
+ (reset-gradients m-pot)
+ (reset-gradients y-pot)
+ (osc-send addr (string-append c-pot-method "/enable"))
+ (osc-send addr (string-append m-pot-method "/enable"))
+ (osc-send addr (string-append y-pot-method "/enable"))))))
+ (get-selection))
+
+ (add-update-hook!
+ programmer-state
+ (lambda (source)
+ (unless (or (eq? source c-pot-method)
+ (eq? source m-pot-method)
+ (eq? source y-pot-method))
+ (set! colours (current-values fixtures attr-name))
+ (set-initial-vals c-pot (map cyan (map colour-as-cmy colours)))
+ (set-initial-vals m-pot (map magenta (map colour-as-cmy colours)))
+ (set-initial-vals y-pot (map yellow (map colour-as-cmy colours)))
+ (reset-gradients c-pot)
+ (reset-gradients m-pot)
+ (reset-gradients y-pot)))))))