aboutsummaryrefslogtreecommitdiff
path: root/guile
diff options
context:
space:
mode:
Diffstat (limited to 'guile')
-rw-r--r--guile/starlet/attributes.scm104
-rw-r--r--guile/starlet/clock.scm6
-rw-r--r--guile/starlet/colours.scm77
-rw-r--r--guile/starlet/crossfade.scm261
-rw-r--r--guile/starlet/cue-list.scm217
-rw-r--r--guile/starlet/cue-part.scm35
-rw-r--r--guile/starlet/effects.scm27
-rw-r--r--guile/starlet/engine.scm227
-rw-r--r--guile/starlet/fixture-library/adj/mega-tripar-profile.scm63
-rw-r--r--guile/starlet/fixture-library/chauvet/mav2.scm50
-rw-r--r--guile/starlet/fixture-library/chauvet/mav2/32chan.scm49
-rw-r--r--guile/starlet/fixture-library/generic/any-rgb.scm62
-rw-r--r--guile/starlet/fixture-library/generic/dimmer.scm20
-rw-r--r--guile/starlet/fixture-library/generic/rgb.scm51
-rw-r--r--guile/starlet/fixture-library/lightmaxx/led-cob.scm45
-rw-r--r--guile/starlet/fixture-library/robe/dl7s.scm241
-rw-r--r--guile/starlet/fixture-library/robe/dl7s/mode1.scm65
-rw-r--r--guile/starlet/fixture-library/robe/mmxspot.scm87
-rw-r--r--guile/starlet/fixture-library/robe/mmxspot/mode1.scm87
-rw-r--r--guile/starlet/fixture-library/robe/mmxwashbeam.scm94
-rw-r--r--guile/starlet/fixture-library/robe/mmxwashbeam/mode1.scm94
-rw-r--r--guile/starlet/fixture-library/stairville/octagon-theater-cw-ww.scm51
-rw-r--r--guile/starlet/fixture-library/stairville/z120m.scm69
-rw-r--r--guile/starlet/fixture-library/tadm/led-bar.scm24
-rw-r--r--guile/starlet/fixture-library/tadm/led-foh.scm46
-rw-r--r--guile/starlet/fixture.scm109
-rw-r--r--guile/starlet/midi-control/base.scm294
-rw-r--r--guile/starlet/midi-control/button-utils.scm93
-rw-r--r--guile/starlet/midi-control/faders.scm361
-rw-r--r--guile/starlet/open-sound-control/utils.scm467
-rw-r--r--guile/starlet/playback.scm679
-rw-r--r--guile/starlet/scanout.scm370
-rw-r--r--guile/starlet/selection.scm97
-rw-r--r--guile/starlet/snap-transition.scm51
-rw-r--r--guile/starlet/state.scm262
-rw-r--r--guile/starlet/utils.scm132
36 files changed, 2843 insertions, 2224 deletions
diff --git a/guile/starlet/attributes.scm b/guile/starlet/attributes.scm
new file mode 100644
index 0000000..e139040
--- /dev/null
+++ b/guile/starlet/attributes.scm
@@ -0,0 +1,104 @@
+;;
+;; starlet/attributes.scm
+;;
+;; Copyright © 2022-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 attributes)
+ #:use-module (oop goops)
+ #:export (<starlet-attribute>
+ make-attribute
+ attribute?
+ intensity?
+ canonical-name
+ friendly))
+
+
+(define-class <starlet-attribute> (<object>)
+ (canonical-name
+ #:init-keyword #:name
+ #:getter canonical-name)
+ (friendly
+ #:init-keyword #:friendly
+ #:getter friendly))
+
+(define (make-attribute canonical-name friendly)
+ (make <starlet-attribute>
+ #:name canonical-name
+ #:friendly friendly))
+
+(define (attribute? a)
+ (is-a? a <starlet-attribute>))
+
+(define-method (write (attribute <starlet-attribute>) port)
+ (write
+ (canonical-name attribute)
+ port))
+
+(define-method (canonical-name whatever)
+ whatever)
+
+
+(define-syntax define-attribute
+ (syntax-rules ()
+ ((_ name friendly-name)
+ (define-public name (make-attribute (quote name) friendly-name)))))
+
+
+;; The standard attribute names
+(define-attribute intensity "Intensity (percentage of brightest)")
+(define-attribute colour "Colour (colour object)")
+(define-attribute colour-temperature "Colour temperature (K)")
+(define-attribute strobe "Strobe active (boolean)")
+(define-attribute strobe-frequency "Strobe rate (Hz)")
+(define-attribute pan "Moving head pan angle (degrees +/- from home)")
+(define-attribute tilt "Moving head tilt angle (degrees +/- from home)")
+(define-attribute prism "Prism active (boolean)")
+(define-attribute prism-rotation-speed "Prism rotation speed (+/- percentage of fastest, clockwise)")
+(define-attribute frost "Frost active (percentage of maximum frost)")
+(define-attribute hotspot "Hot spot (percentage of maximum peakiness)")
+(define-attribute iris "Iris (percentage of maximum tightness (perhaps completely closed)")
+(define-attribute zoom "Zoom (percentage of tightest zoom)")
+(define-attribute focus "Focus (percentage of nearest focus)")
+(define-attribute barndoor-all-rotation "Rotation of all barndoors together (degrees +/- from home)")
+(define-attribute barndoor1 "Barndoor 1 position (percentage of fully in position)")
+(define-attribute barndoor2 "Barndoor 2 position (percentage of fully in position)")
+(define-attribute barndoor3 "Barndoor 3 position (percentage of fully in position)")
+(define-attribute barndoor4 "Barndoor 4 position (percentage of fully in position)")
+(define-attribute barndoor1-rotation "Barndoor 1 rotation (degrees +/- from home)")
+(define-attribute barndoor2-rotation "Barndoor 2 rotation (degrees +/- from home)")
+(define-attribute barndoor3-rotation "Barndoor 3 rotation (degrees +/- from home)")
+(define-attribute barndoor4-rotation "Barndoor 4 rotation (degrees +/- from home)")
+(define-attribute beamtype "Beam type")
+(define-attribute colwheel "Colour wheel selection (#f or gel name)")
+(define-attribute gobo "Gobo selection (#f or gobo name)")
+(define-attribute gobo-shift "Fine position of gobo (percentage of maximum shift)")
+(define-attribute animation-wheel "Animation wheel active (boolean)")
+(define-attribute animation-wheel-position "Animation wheel position (-100 to 100, 0=central)")
+(define-attribute animation-wheel-speed "Animation wheel rotation speed and direction (+/- percentage of fastest, clockwise)")
+(define-attribute rotating-gobo "Rotating gobo selection (#f or gobo name)")
+(define-attribute rotating-gobo-speed "Gobo rotation speed (+/- percentage of maximum speed, clockwise)")
+(define-attribute white-weirdness "Weirdness of white (percentage of maximum weirdness)")
+
+;; Duplicate names for convenience...
+(define-public color colour)
+(define-public color-temperature colour-temperature)
+
+
+(define (intensity? a)
+ (eq? intensity a))
+
diff --git a/guile/starlet/clock.scm b/guile/starlet/clock.scm
index c139d66..2e6e2ff 100644
--- a/guile/starlet/clock.scm
+++ b/guile/starlet/clock.scm
@@ -92,8 +92,10 @@
(define (clock-expired? clock)
- (> (elapsed-time clock)
- (expiration-time clock)))
+ (and
+ clock
+ (> (elapsed-time clock)
+ (expiration-time clock))))
(define-method (elapsed-time (clock <starlet-clock>))
diff --git a/guile/starlet/colours.scm b/guile/starlet/colours.scm
index c7d1de0..2162322 100644
--- a/guile/starlet/colours.scm
+++ b/guile/starlet/colours.scm
@@ -23,10 +23,11 @@
#:use-module (ice-9 exceptions)
#:export (<colour>
colour?
- make-colour-cmy
- make-colour-rgb
+ cmy
+ rgb
colour-as-cmy
colour-as-rgb
+ colour-as-rgbw
cyan
magenta
@@ -36,13 +37,7 @@
blue
interpolate-colour
- white
-
- <colour-component-id>
- colour-component-id?
- colour-component-id
- get-colour-component
- extract-colour-component))
+ white))
(define-class <colour> (<object>)
@@ -74,29 +69,31 @@
(colour-type col)
(colour-value col)))
+(define (three-sf n)
+ (/ (round (* (exact->inexact n) 10)) 10))
(define-method (write (col <colour>) port)
(let ((cmy (colour-as-cmy col)))
- (format port "(make-colour-cmy ~a ~a ~a)"
- (cyan cmy)
- (magenta cmy)
- (yellow cmy))))
+ (format port "(cmy ~a ~a ~a)"
+ (three-sf (cyan cmy))
+ (three-sf (magenta cmy))
+ (three-sf (yellow cmy)))))
-(define (make-colour-cmy c m y)
+(define (cmy c m y)
(make <colour>
#:type 'cmy
#:value (list c m y)))
-(define (make-colour-rgb r g b)
+(define (rgb r g b)
(make <colour>
#:type 'rgb
#:value (list r g b)))
(define white
- (make-colour-cmy 0 0 0))
+ (cmy 0 0 0))
(define (colour-as-rgb col)
@@ -117,6 +114,15 @@
(make-exception-with-irritants (colour-type col))))))))
+(define (colour-as-rgbw col)
+ (let ((rgb (colour-as-rgb col)))
+ (let ((w (apply min rgb)))
+ (list (- (red rgb) w)
+ (- (green rgb) w)
+ (- (blue rgb) w)
+ w))))
+
+
(define (colour-as-cmy col)
(let ((val (colour-value col)))
(case (colour-type col)
@@ -138,7 +144,7 @@
(define (interpolate-cmy a b frac)
(let ((cmy1 (colour-as-cmy a))
(cmy2 (colour-as-cmy b)))
- (make-colour-cmy
+ (cmy
(+ (cyan cmy1) (* frac (- (cyan cmy2) (cyan cmy1))))
(+ (magenta cmy1) (* frac (- (magenta cmy2) (magenta cmy1))))
(+ (yellow cmy1) (* frac (- (yellow cmy2) (yellow cmy1)))))))
@@ -155,40 +161,3 @@
(make-exception-with-message
"Unrecognised colour interpolation type")
(make-exception-with-irritants interpolation-type))))))
-
-
-(define-class <colour-component-id> (<object>)
- (component
- #:init-form (error "Colour component must be specified")
- #:init-keyword #:component
- #:getter get-colour-component))
-
-
-(define (colour-component-id? a)
- (is-a? a <colour-component-id>))
-
-
-(define (colour-component-id a)
- (make <colour-component-id>
- #:component a))
-
-
-(define (extract-colour-component col component-id)
- (cond
- ((eq? (get-colour-component component-id) 'cyan)
- (cyan (colour-as-cmy col)))
- ((eq? (get-colour-component component-id) 'magenta)
- (magenta (colour-as-cmy col)))
- ((eq? (get-colour-component component-id) 'yellow)
- (yellow (colour-as-cmy col)))
- ((eq? (get-colour-component component-id) 'red)
- (red (colour-as-rgb col)))
- ((eq? (get-colour-component component-id) 'green)
- (green (colour-as-rgb col)))
- ((eq? (get-colour-component component-id) 'blue)
- (blue (colour-as-rgb col)))
- (else (raise-exception (make-exception
- (make-exception-with-message
- "Invalid colour component ID")
- (make-exception-with-irritants
- (get-colour-component component-id)))))))
diff --git a/guile/starlet/crossfade.scm b/guile/starlet/crossfade.scm
new file mode 100644
index 0000000..65393b7
--- /dev/null
+++ b/guile/starlet/crossfade.scm
@@ -0,0 +1,261 @@
+;;
+;; starlet/crossfade.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 crossfade)
+ #:use-module (oop goops)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-26)
+ #:use-module (ice-9 exceptions)
+ #:use-module (starlet clock)
+ #:use-module (starlet cue-part)
+ #:use-module (starlet colours)
+ #:use-module (starlet fixture)
+ #:use-module (starlet state)
+ #:use-module (starlet attributes)
+ #:export (crossfade))
+
+
+(define-record-type <fade-times>
+ (make-fade-times up-time
+ down-time
+ attr-time
+ up-delay
+ down-delay
+ attr-delay)
+ fade-times?
+ (up-time get-fade-up-time)
+ (down-time get-fade-down-time)
+ (attr-time get-fade-attr-time)
+ (up-delay get-fade-up-delay)
+ (down-delay get-fade-down-delay)
+ (attr-delay get-fade-attr-delay))
+
+
+(define (snap-fade start-val
+ target-val
+ clock)
+ (if (> (elapsed-fraction clock) 0)
+ target-val
+ start-val))
+
+
+(define (colour-fade start-val
+ end-val
+ clock)
+
+ (unless (and (colour? start-val)
+ (colour? end-val))
+ (raise-exception (make-exception
+ (make-exception-with-message
+ "Non-colour arguments given to colour-fade")
+ (make-exception-with-irritants
+ (list start-val end-val)))))
+
+ (interpolate-colour start-val
+ end-val
+ (elapsed-fraction clock)
+ #:interpolation-type 'linear-cmy))
+
+
+(define (simple-fade start-val
+ end-val
+ clock)
+
+ (unless (and (number? start-val)
+ (number? end-val))
+ (raise-exception (make-exception
+ (make-exception-with-message
+ "Non-number arguments given to simple-fade")
+ (make-exception-with-irritants
+ (list start-val end-val)))))
+
+ (+ start-val
+ (* (- end-val start-val)
+ (elapsed-fraction clock))))
+
+
+(define (replace-noval val replacement)
+ (if (eq? 'no-value val) replacement val))
+
+
+(define (make-intensity-fade prev-val
+ target-val-in
+ up-clock
+ down-clock)
+ (let ((target-val (replace-noval target-val-in 0.0)))
+
+ (cond
+
+ ;; Number to number, fading up
+ ((and (number? target-val)
+ (number? prev-val)
+ (> target-val prev-val))
+ (lambda ()
+ (simple-fade prev-val
+ target-val
+ up-clock)))
+
+ ;; Number to number, fading down
+ ((and (number? target-val)
+ (number? prev-val)
+ (< target-val prev-val))
+ (lambda ()
+ (simple-fade prev-val
+ target-val
+ down-clock)))
+
+ ;; Number to number, staying the same
+ ;; NB We still need a static value so that fade-start-val can "unwrap" it
+ ((and (number? target-val)
+ (number? prev-val))
+ (lambda () prev-val))
+
+ ;; Everything else, e.g. number to effect
+ (else
+ (lambda ()
+ (max
+ (simple-fade (value->number prev-val)
+ 0
+ down-clock)
+ (simple-fade 0
+ (value->number target-val)
+ up-clock)))))))
+
+
+(define (make-list-attr-fade start-val
+ target-val
+ clock)
+ (lambda ()
+ (snap-fade start-val
+ target-val
+ clock)))
+
+
+(define (make-general-fade fade-func
+ start-val
+ target-val
+ clock)
+
+ (if (and (not (procedure? target-val))
+ (not (eq? target-val 'no-value))
+ (not (eq? start-val 'no-value)))
+
+ ;; It makes sense to do a fade
+ (let ((real-start-val (value->number start-val)))
+ (lambda ()
+ (fade-func real-start-val
+ target-val
+ clock)))
+
+ ;; A fade doesn't make sense, so make do with a snap transition
+ (lambda ()
+ (snap-fade start-val
+ target-val
+ clock))))
+
+
+(define (fade-start-val pb fix attr)
+ (let ((val-in-pb (state-find fix attr pb)))
+ (if (eq? val-in-pb 'no-value)
+
+ ;; Not currently in playback - fade from home value
+ (get-attr-home-val fix attr)
+
+ ;; Currently in playback - fade from current value
+ ;; by running the outer crossfade function
+ (val-in-pb))))
+
+
+(define (dark? a)
+ (or (eq? a 'no-value)
+ (and (number? a)
+ (< a 1))))
+
+
+(define (make-fade-for-attribute-type type)
+ (cond
+ ((eq? type 'continuous) (cut make-general-fade simple-fade <...>))
+ ((eq? type 'list) make-list-attr-fade)
+ ((eq? type 'colour) (cut make-general-fade colour-fade <...>))
+ (else
+ (raise-exception (make-exception
+ (make-exception-with-message
+ "Unrecognised attribute type")
+ (make-exception-with-irritants type))))))
+
+
+(define* (crossfade-real incoming-state up-time #:optional (down-time up-time)
+ #:key
+ (attr-time (min up-time down-time))
+ (up-delay 0)
+ (down-delay 0)
+ (attr-delay 0))
+ (cue-part
+ incoming-state
+ (lambda (incoming-state current-state clock)
+ (let ((up-clock (make-delayed-clock clock up-delay up-time))
+ (down-clock (make-delayed-clock clock down-delay down-time))
+ (attribute-clock (make-delayed-clock clock attr-delay attr-time)))
+ (let ((overlay-state (make-empty-state)))
+ (state-for-each
+ (lambda (fixture attr target-val)
+
+ (let ((start-val (fade-start-val current-state fixture attr)))
+
+ (if (intensity? attr)
+
+ ;; Intensity attribute
+ (set-in-state! overlay-state fixture attr
+ (make-intensity-fade start-val
+ target-val
+ up-clock
+ down-clock))
+
+ ;; Non-intensity attribute
+ (let ((attribute-obj (find-attr fixture attr)))
+
+ (unless attribute-obj
+ (raise-exception (make-exception
+ (make-exception-with-message
+ "Attribute not found")
+ (make-exception-with-irritants
+ (list fixture attr)))))
+
+ (let* ((atype (get-attr-type attribute-obj))
+ (make-fade-func (make-fade-for-attribute-type atype)))
+
+ (set-in-state! overlay-state fixture attr
+ (make-fade-func start-val
+ target-val
+ attribute-clock)))))))
+
+ incoming-state)
+ (values overlay-state
+ (max
+ (+ up-time up-delay)
+ (+ down-time down-delay)
+ (+ attr-time attr-delay))))))))
+
+
+;; Rearrange the arguments to put the lighting state (last argument)
+;; at the beginning. This makes optional arguments in crossfade-real possible.
+(define (crossfade . args)
+ (apply crossfade-real (last args) (drop-right args 1)))
diff --git a/guile/starlet/cue-list.scm b/guile/starlet/cue-list.scm
new file mode 100644
index 0000000..b029713
--- /dev/null
+++ b/guile/starlet/cue-list.scm
@@ -0,0 +1,217 @@
+;;
+;; starlet/cue-list.scm
+;;
+;; Copyright © 2020-2022 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 cue-list)
+ #:use-module (oop goops)
+ #:use-module (ice-9 optargs)
+ #:use-module (ice-9 receive)
+ #:use-module (ice-9 exceptions)
+ #:use-module (ice-9 atomic)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-26)
+ #:use-module (srfi srfi-43)
+ #:use-module (starlet fixture)
+ #:use-module (starlet state)
+ #:use-module (starlet clock)
+ #:use-module (starlet utils)
+ #:use-module (starlet attributes)
+ #:use-module (starlet cue-part)
+ #:use-module (starlet snap-transition)
+ #:use-module (starlet crossfade)
+ #:export (cue
+ cue-list
+ qnum
+ get-cue-number
+ get-cue-parts
+ get-cue-clock
+ get-preset-state
+ cue-number-to-index
+ cue-index-to-number
+ current-cue-clock
+ read-cue-list-file
+ num-cues)
+ #:re-export (snap crossfade))
+
+
+(define-record-type <cue>
+ (make-cue number
+ preset-state
+ track-intensities
+ cue-parts
+ cue-clock)
+ cue?
+ (number get-cue-number)
+ (preset-state get-preset-state
+ set-preset-state!)
+ (track-intensities track-intensities?)
+ (cue-parts get-cue-parts)
+ (cue-clock get-cue-clock))
+
+
+(define-method (num-cues (l <vector>))
+ (vector-length l))
+
+
+(define (qnum a)
+ (/ (inexact->exact (* a 1000)) 1000))
+
+
+(define (cue-index-to-number cue-list cue-index)
+ (get-cue-number (vector-ref cue-list cue-index)))
+
+
+(define (cue-number-to-index cue-list cue-number)
+ (vector-index (lambda (a)
+ (eqv? (get-cue-number a)
+ cue-number))
+ cue-list))
+
+
+(define (fix-attr-eq fa1 fa2)
+ (and (eq? (car fa1) (car fa2))
+ (eq? (cdr fa1) (cdr fa2))))
+
+
+(define (fix-attrs-in-state state)
+ (state-map->list
+ (lambda (fix attr val) (cons fix attr))
+ state))
+
+
+(define (add-fix-attrs-to-list state old-list)
+ (lset-union fix-attr-eq
+ old-list
+ (fix-attrs-in-state state)))
+
+
+
+(define current-cue-clock (make-parameter #f))
+
+(define-syntax cue
+ (syntax-rules (track-intensities)
+ ((_ number track-intensities body ...)
+ (parameterize ((current-cue-clock (make-clock #:stopped #t)))
+ (make-cue (qnum number)
+ #f ;; preset state, to be filled later
+ #t ;; DO track intensities
+ (list body ...)
+ (current-cue-clock))))
+ ((_ number body ...)
+ (parameterize ((current-cue-clock (make-clock #:stopped #t)))
+ (make-cue (qnum number)
+ #f ;; preset state, to be filled later
+ #f ;; don't track intensities
+ (list body ...)
+ (current-cue-clock))))))
+
+
+(define (track-all-cues! the-cue-list)
+ (vector-fold
+ (lambda (idx prev-state the-cue)
+ (let ((the-tracked-state (lighting-state
+ (apply-state prev-state)
+ (unless (track-intensities? the-cue)
+ (blackout!))
+ (apply-state
+ (get-cue-part-state
+ (car (get-cue-parts the-cue)))))))
+ (set-cue-part-state! (car (get-cue-parts the-cue))
+ the-tracked-state)
+ (lighting-state
+ (apply-state the-tracked-state)
+ (for-each
+ (lambda (part)
+ (apply-state (get-cue-part-state part)))
+ (cdr (get-cue-parts the-cue))))))
+ (make-empty-state)
+ the-cue-list))
+
+
+(define (dark? a)
+ (or (eq? a 'no-value)
+ (and (number? a)
+ (< a 1))))
+
+
+(define (fixture-dark-in-cue? fix the-cue)
+ (every
+ (lambda (part)
+ (dark? (state-find fix intensity (get-cue-part-state part))))
+ (get-cue-parts the-cue)))
+
+
+(define-syntax for-each-cue-part
+ (syntax-rules ()
+ ((_ the-cue (part) body ...)
+ (for-each
+ (lambda (part)
+ body ...)
+ (get-cue-parts the-cue)))))
+
+
+(define-syntax for-every-attr-in-cue
+ (syntax-rules ()
+ ((_ the-cue (fix attr val) body ...)
+ (for-each-cue-part
+ the-cue (part)
+ (state-for-each
+ (lambda (fix attr val)
+ body ...)
+ (get-cue-part-state part))))))
+
+
+(define (preset-all-cues! the-cue-list)
+ (let loop ((idx 0))
+ (let ((the-cue (vector-ref the-cue-list idx))
+ (next-cue (vector-ref the-cue-list (1+ idx)))
+ (preset-state (make-empty-state)))
+ (for-every-attr-in-cue
+ next-cue (fix attr val)
+ (unless (intensity? attr)
+ (when (fixture-dark-in-cue? fix the-cue)
+ (set-in-state! preset-state fix attr val))))
+ (set-preset-state! the-cue preset-state))
+ (if (< (+ 2 idx) (vector-length the-cue-list))
+ (loop (1+ idx))
+ (set-preset-state!
+ (vector-ref the-cue-list (1+ idx))
+ (make-empty-state)))))
+
+
+(define-syntax cue-list
+ (syntax-rules ()
+ ((_ body ...)
+ (let ((the-cue-list
+ (list->vector
+ (remove unspecified?
+ (list
+ (cue 0 (snap blackout))
+ body ...)))))
+ (track-all-cues! the-cue-list)
+ (preset-all-cues! the-cue-list)
+ the-cue-list))))
+
+
+(define (read-cue-list-file filename)
+ (call-with-input-file
+ filename
+ (lambda (port)
+ (eval (read port) (interaction-environment)))))
diff --git a/guile/starlet/cue-part.scm b/guile/starlet/cue-part.scm
new file mode 100644
index 0000000..e98e422
--- /dev/null
+++ b/guile/starlet/cue-part.scm
@@ -0,0 +1,35 @@
+;;
+;; starlet/cue-part
+;;
+;; 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 cue-part)
+ #:use-module (srfi srfi-9)
+ #:export (cue-part
+ <cue-part>
+ get-cue-part-state
+ get-cue-part-transition
+ set-cue-part-state!))
+
+
+(define-record-type <cue-part>
+ (cue-part state transition)
+ cue-part?
+ (state get-cue-part-state
+ set-cue-part-state!)
+ (transition get-cue-part-transition))
diff --git a/guile/starlet/effects.scm b/guile/starlet/effects.scm
index c14f5a0..0a23dee 100644
--- a/guile/starlet/effects.scm
+++ b/guile/starlet/effects.scm
@@ -20,8 +20,11 @@
;;
(define-module (starlet effects)
#:use-module (starlet clock)
+ #:use-module (starlet state)
+ #:use-module (starlet attributes)
#:export (flash
- sinewave))
+ sinewave
+ flash-chase))
(define pi (* 2 (acos 0)))
@@ -45,3 +48,25 @@
(+ range-min
(* (/ (- range-max range-min) 2)
(+ 1 (sin (* 2 pi hz (elapsed-time clock)))))))))
+
+
+(define (hump t on-time)
+ (cond
+ ((< t 0.0) 0.0)
+ ((> t on-time) 0.0)
+ (else (* 100 (sin (* pi (/ t on-time)))))))
+
+
+(define* (flash-chase group
+ #:key (repeat-time 2) (offset-time 0.3) (on-time 0.5))
+ (let ((clock (make-clock)))
+ (for-each
+ (lambda (fix idx)
+ (at fix intensity
+ (lambda ()
+ (hump (- (euclidean-remainder (elapsed-time clock)
+ repeat-time)
+ (* idx offset-time))
+ on-time))))
+ group
+ (iota (length group)))))
diff --git a/guile/starlet/engine.scm b/guile/starlet/engine.scm
new file mode 100644
index 0000000..fcd63a5
--- /dev/null
+++ b/guile/starlet/engine.scm
@@ -0,0 +1,227 @@
+;;
+;; starlet/engine.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 engine)
+ #:use-module (starlet fixture)
+ #:use-module (starlet state)
+ #:use-module (starlet utils)
+ #:use-module (starlet attributes)
+ #:use-module (oop goops)
+ #:use-module (ice-9 threads)
+ #:use-module (ice-9 atomic)
+ #:use-module (ice-9 exceptions)
+ #:use-module (srfi srfi-1)
+ #:export (patch-fixture!
+ patch-many!
+ engine-freq
+ total-num-attrs
+ register-state!
+ current-value
+ current-value-state
+ patched-fixture-names
+ patched-fixtures))
+
+
+;; The list of patched fixtures
+(define fixtures (make-atomic-box '()))
+
+;; List of states being scanned out
+(define state-list (make-atomic-box '()))
+
+;; Association list of names to states
+(define state-names (make-atomic-box '()))
+
+;; Current values (literal, not functions) of active attributes
+(define current-values (make-atomic-box (make-empty-state)))
+
+
+(define (patched-fixture-names)
+ (map get-fixture-name (atomic-box-ref fixtures)))
+
+
+(define (current-value-state)
+ (atomic-box-ref current-values))
+
+
+(define (patched-fixtures)
+ (atomic-box-ref fixtures))
+
+
+(define (total-num-attrs)
+ (fold (lambda (fix prev)
+ (+ prev (length (get-fixture-attrs fix))))
+ 0
+ (atomic-box-ref fixtures)))
+
+
+(define (get-state-name st)
+ (assq-ref (atomic-box-ref state-names)
+ st))
+
+
+(define (set-state-name! st name)
+ (atomic-box-set! state-names
+ (assq-set! (atomic-box-ref state-names)
+ st
+ name)))
+
+
+;; Patch a new fixture
+(define* (patch-real name
+ class
+ start-addr
+ #:key (universe 0))
+ (let ((new-fixture (make class
+ #:name name
+ #:sa start-addr
+ #:uni universe)))
+ (atomic-box-set! fixtures (cons new-fixture
+ (atomic-box-ref fixtures)))
+ new-fixture))
+
+
+(define-syntax patch-fixture!
+ (syntax-rules ()
+ ((_ name stuff ...)
+ (define name (patch-real (quote name) stuff ...)))))
+
+
+;; Patch several new fixtures
+(define* (patch-many-real name
+ class
+ start-addrs
+ #:key (universe 0))
+ (map (lambda (start-addr n)
+ (patch-real `(list-ref ,name ,n)
+ class
+ start-addr
+ #:universe universe))
+ start-addrs
+ (iota (length start-addrs))))
+
+
+(define-syntax patch-many!
+ (syntax-rules ()
+ ((_ name stuff ...)
+ (define name (patch-many-real (quote name) stuff ...)))))
+
+
+(define (current-value fix attr-name)
+ (let ((v (state-find fix attr-name (current-value-state))))
+ (if (eq? v 'no-value)
+ (get-attr-home-val fix attr-name)
+ v)))
+
+
+(define (append-or-replace-named-state orig-list name new-state)
+ (let ((new-list (map (lambda (st)
+ (if (eq? (get-state-name st) name)
+ (begin
+ new-state)
+ st))
+ orig-list)))
+
+ ;; If there is no state with this name in the list,
+ ;; the replacement above will have no effect.
+ ;; Check again and add in the normal way if so.
+ (if (find (lambda (st) (eq? (get-state-name st)
+ name))
+ new-list)
+ new-list
+ (append orig-list (list new-state)))))
+
+
+(define* (register-state! new-state
+ #:key (unique-name #f))
+ (if unique-name
+ (begin (set-state-name! new-state unique-name)
+ (atomic-box-set! state-list
+ (append-or-replace-named-state (atomic-box-ref state-list)
+ unique-name
+ new-state)))
+ (atomic-box-set! state-list
+ (append (atomic-box-ref state-list)
+ (list new-state)))))
+
+
+(define engine-thread #f)
+(define engine-freq 0)
+
+
+(define (htp-attr? attr)
+ (eq? attr intensity))
+
+
+(define (engine-loop start-time count)
+
+ ;; Combine all the active attributes and send it out
+ (atomic-box-swap! current-values
+ (combine-states
+ (let ((states (atomic-box-ref state-list)))
+ (for-each update-state! states)
+ (fold
+ (lambda (incoming-state combined-state)
+ (state-for-each
+ (lambda (fix attr val)
+ (let ((incoming-val (value->number val))
+ (current-val (state-find fix attr combined-state)))
+ (unless (eq? incoming-val 'no-value)
+ (if (eq? current-val 'no-value)
+ (set-in-state! combined-state fix attr incoming-val)
+ (set-in-state! combined-state fix attr
+ (if (htp-attr? attr)
+ (max incoming-val current-val)
+ incoming-val))))))
+ incoming-state)
+ combined-state)
+ (make-empty-state)
+ states))
+ programmer-state))
+
+ (usleep 20000)
+
+ ;; Update output rate every 1000 cycles
+ (if (eq? count 100)
+ (begin
+ (set! engine-freq
+ (exact->inexact (/ 100
+ (- (hirestime) start-time))))
+ (engine-loop (hirestime) 0))
+ (engine-loop start-time (+ count 1))))
+
+
+(define (start-engine)
+ (if engine-thread
+ (format #t "Engine thread is already running\n")
+ (let ((start-time (hirestime)))
+ (set! engine-thread
+ (begin-thread
+ (with-exception-handler
+ (lambda (exn)
+ (display "Error in engine thread:\n")
+ (set! engine-thread #f)
+ (backtrace)
+ (raise-exception exn))
+ (lambda ()
+ (engine-loop start-time 0))
+ #:unwind? #f))))))
+
+
+(start-engine)
diff --git a/guile/starlet/fixture-library/adj/mega-tripar-profile.scm b/guile/starlet/fixture-library/adj/mega-tripar-profile.scm
new file mode 100644
index 0000000..4815fa9
--- /dev/null
+++ b/guile/starlet/fixture-library/adj/mega-tripar-profile.scm
@@ -0,0 +1,63 @@
+;;
+;; starlet/fixture-library/adj/mega-tripar-profile.scm
+;;
+;; Copyright © 2020-2022 Thomas White <taw@bitwiz.me.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 fixture-library adj mega-tripar-profile)
+ #:use-module (starlet scanout)
+ #:use-module (starlet fixture)
+ #:use-module (starlet attributes)
+ #:use-module (starlet utils)
+ #:use-module (starlet colours)
+ #:export (<adj-mega-tripar-profile-3ch>
+ <adj-mega-tripar-profile-4ch>))
+
+
+;; 3 channel mode (RGB direct control)
+
+(define-fixture
+
+ <adj-mega-tripar-profile-3ch>
+
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-colour colour white))
+
+ (let ((intensity (/ (get-attr intensity) 100))
+ (rgb (colour-as-rgb (get-attr colour))))
+ (set-chan8 1 (percent->dmxval8 (* intensity (car rgb))))
+ (set-chan8 2 (percent->dmxval8 (* intensity (cadr rgb))))
+ (set-chan8 3 (percent->dmxval8 (* intensity (caddr rgb))))))
+
+
+;; 4 channel mode (RGB + separate intensity)
+
+(define-fixture
+
+ <adj-mega-tripar-profile-4ch>
+
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-colour colour white))
+
+ (let ((rgb (colour-as-rgb (get-attr colour))))
+ (set-chan8 1 (percent->dmxval8 (get-attr intensity)))
+ (set-chan8 2 (percent->dmxval8 (car rgb)))
+ (set-chan8 3 (percent->dmxval8 (cadr rgb)))
+ (set-chan8 4 (percent->dmxval8 (caddr rgb)))))
+
diff --git a/guile/starlet/fixture-library/chauvet/mav2.scm b/guile/starlet/fixture-library/chauvet/mav2.scm
new file mode 100644
index 0000000..5fae168
--- /dev/null
+++ b/guile/starlet/fixture-library/chauvet/mav2.scm
@@ -0,0 +1,50 @@
+;;
+;; starlet/fixture-library/chauvet.scm
+;;
+;; Copyright © 2020-2021 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 fixture-library chauvet mav2)
+ #:use-module (starlet scanout)
+ #:use-module (starlet fixture)
+ #:use-module (starlet attributes)
+ #:use-module (starlet utils)
+ #:use-module (starlet colours)
+ #:export (<chauvet-mav2-32ch>))
+
+
+(define-fixture
+
+ <chauvet-mav2-32ch>
+
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-continuous pan '(0 540) 270)
+ (attr-continuous tilt '(0 270) 135)
+ (attr-continuous cyan '(0 100) 0)
+ (attr-continuous magenta '(0 100) 0)
+ (attr-continuous yellow '(0 100) 0))
+
+ (set-chan-16bit 1 (get-attr pan) 540)
+ (set-chan-16bit 3 (get-attr tilt) 270)
+ (set-chan-16bit 6 (get-attr intensity) 100)
+
+ (set-chan 10 (percent->dmxval (get-attr cyan)))
+ (set-chan 11 (percent->dmxval (get-attr magenta)))
+ (set-chan 12 (percent->dmxval (get-attr yellow)))
+
+ (set-chan 8 255))
diff --git a/guile/starlet/fixture-library/chauvet/mav2/32chan.scm b/guile/starlet/fixture-library/chauvet/mav2/32chan.scm
deleted file mode 100644
index 5868587..0000000
--- a/guile/starlet/fixture-library/chauvet/mav2/32chan.scm
+++ /dev/null
@@ -1,49 +0,0 @@
-;;
-;; starlet/fixture-library/chauvet.scm
-;;
-;; Copyright © 2020-2021 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 fixture-library chauvet)
- #:use-module (oop goops)
- #:use-module (starlet fixture)
- #:export (<chauvet-mav2-32ch>))
-
-
-(define-class <chauvet-mav2-32ch> (<fixture>)
- (attributes
- #:init-form (list
- (attr-continuous 'intensity '(0 100) 0)
- (attr-continuous 'pan '(0 540) 270)
- (attr-continuous 'tilt '(0 270) 135)
- (attr-continuous 'cyan '(0 100) 0)
- (attr-continuous 'magenta '(0 100) 0)
- (attr-continuous 'yellow '(0 100) 0))))
-
-
-(define-method (scanout-fixture (fixture <chauvet-mav2-32ch>)
- get-attr set-chan set-chan-16bit)
-
- (set-chan-16bit 1 (get-attr 'pan) 540)
- (set-chan-16bit 3 (get-attr 'tilt) 270)
- (set-chan-16bit 6 (get-attr 'intensity) 100)
-
- (set-chan 10 (percent->dmxval (get-attr 'cyan)))
- (set-chan 11 (percent->dmxval (get-attr 'magenta)))
- (set-chan 12 (percent->dmxval (get-attr 'yellow)))
-
- (set-chan 8 255))
diff --git a/guile/starlet/fixture-library/generic/any-rgb.scm b/guile/starlet/fixture-library/generic/any-rgb.scm
deleted file mode 100644
index 1fc3fe2..0000000
--- a/guile/starlet/fixture-library/generic/any-rgb.scm
+++ /dev/null
@@ -1,62 +0,0 @@
-;;
-;; starlet/fixture-library/generic/any-rgb.scm
-;;
-;; Copyright © 2020-2021 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 fixture-library generic any-rgb)
- #:use-module (oop goops)
- #:use-module (starlet fixture)
- #:export (make-any-rgb))
-
-
-(define (chan->attr chan)
- (attr-continuous chan '(0 100) 0))
-
-
-(define (make-any-rgb chans)
-
- (let ((new-class (make-class
- (list <fixture>)
- (list (cons 'attributes
- (list #:init-thunk
- (lambda ()
- (map chan->attr chans)))))
- #:name 'generic-rgb)))
-
- (add-method!
- scanout-fixture
- (method ((fix new-class) get-attr set-chan8 set-chan16)
- (for-each
-
- (lambda (chan offset)
-
- (cond
-
- ((eq? chan '0)
- (set-chan8 offset 0))
-
- ((eq? chan 'FL)
- (set-chan8 offset 255))
-
- (else (set-chan8 offset
- (percent->dmxval8
- (get-attr chan))))))
-
- chans (iota (length chans) 1))))
-
- new-class))
diff --git a/guile/starlet/fixture-library/generic/dimmer.scm b/guile/starlet/fixture-library/generic/dimmer.scm
index 65e6d99..6b25c15 100644
--- a/guile/starlet/fixture-library/generic/dimmer.scm
+++ b/guile/starlet/fixture-library/generic/dimmer.scm
@@ -1,7 +1,7 @@
;;
;; starlet/fixture-library/generic/dimmer.scm
;;
-;; Copyright © 2020-2021 Thomas White <taw@bitwiz.org.uk>
+;; Copyright © 2020-2022 Thomas White <taw@bitwiz.org.uk>
;;
;; This file is part of Starlet.
;;
@@ -19,20 +19,18 @@
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
(define-module (starlet fixture-library generic dimmer)
- #:use-module (oop goops)
+ #:use-module (starlet scanout)
#:use-module (starlet fixture)
+ #:use-module (starlet utils)
+ #:use-module (starlet attributes)
#:export (<generic-dimmer>))
+(define-fixture
-(define-class <generic-dimmer> (<fixture>)
- (attributes
- #:init-form (list
- (attr-continuous 'intensity '(0 100) 0))))
+ <generic-dimmer>
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0))
-(define-method (scanout-fixture (fixture <generic-dimmer>)
- get-attr set-chan8 set-chan16)
-
- ;; Set DMX value for intensity
- (set-chan8 1 (percent->dmxval8 (get-attr 'intensity))))
+ (set-chan8 1 (percent->dmxval8 (get-attr intensity))))
diff --git a/guile/starlet/fixture-library/generic/rgb.scm b/guile/starlet/fixture-library/generic/rgb.scm
index 6fa281d..a47b48d 100644
--- a/guile/starlet/fixture-library/generic/rgb.scm
+++ b/guile/starlet/fixture-library/generic/rgb.scm
@@ -1,7 +1,7 @@
;;
;; starlet/fixture-library/generic/rgb.scm
;;
-;; Copyright © 2020-2021 Thomas White <taw@bitwiz.org.uk>
+;; Copyright © 2020-2022 Thomas White <taw@bitwiz.org.uk>
;;
;; This file is part of Starlet.
;;
@@ -19,25 +19,52 @@
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
(define-module (starlet fixture-library generic rgb)
- #:use-module (oop goops)
+ #:use-module (starlet scanout)
#:use-module (starlet fixture)
+ #:use-module (starlet attributes)
+ #:use-module (starlet utils)
#:use-module (starlet colours)
- #:export (<generic-rgb>))
+ #:export (<generic-rgb>
+ <generic-rgbw>))
-(define-class <generic-rgb> (<fixture>)
- (attributes
- #:init-form (list
- (attr-continuous 'intensity '(0 100) 0)
- (attr-colour 'colour white))))
+(define (colour-as-rgbw-weirdness col weirdness)
+ (let ((rgb (colour-as-rgb col)))
+ (let ((w (* (- 1 weirdness) (apply min rgb))))
+ (list (- (red rgb) w)
+ (- (green rgb) w)
+ (- (blue rgb) w)
+ w))))
-(define-method (scanout-fixture (fixture <generic-rgb>)
- get-attr set-chan8 set-chan16)
+(define-fixture
- (let ((intensity (get-attr 'intensity))
- (rgb (colour-as-rgb (get-attr 'colour))))
+ <generic-rgb>
+
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-colour colour white))
+
+ (let ((intensity (get-attr intensity))
+ (rgb (colour-as-rgb (get-attr colour))))
(set-chan8 1 (percent->dmxval8 (* intensity 0.01 (car rgb))))
(set-chan8 2 (percent->dmxval8 (* intensity 0.01 (cadr rgb))))
(set-chan8 3 (percent->dmxval8 (* intensity 0.01 (caddr rgb))))))
+
+(define-fixture
+
+ <generic-rgbw>
+
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-colour colour white)
+ (attr-continuous white-weirdness '(0 100) 0))
+
+ (let ((intensity (get-attr intensity))
+ (rgbw (colour-as-rgbw-weirdness (get-attr colour)
+ (/ (get-attr white-weirdness) 100))))
+ (set-chan8 1 (percent->dmxval8 (* 0.01 intensity (car rgbw))))
+ (set-chan8 2 (percent->dmxval8 (* 0.01 intensity (cadr rgbw))))
+ (set-chan8 3 (percent->dmxval8 (* 0.01 intensity (caddr rgbw))))
+ (set-chan8 4 (percent->dmxval8 (* 0.01 intensity (cadddr rgbw))))))
diff --git a/guile/starlet/fixture-library/lightmaxx/led-cob.scm b/guile/starlet/fixture-library/lightmaxx/led-cob.scm
new file mode 100644
index 0000000..5ce5879
--- /dev/null
+++ b/guile/starlet/fixture-library/lightmaxx/led-cob.scm
@@ -0,0 +1,45 @@
+;;
+;; starlet/fixture-library/lightmaxx-led-cob.scm
+;;
+;; Copyright © 2020-2022 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 fixture-library lightmaxx led-cob)
+ #:use-module (starlet scanout)
+ #:use-module (starlet fixture)
+ #:use-module (starlet attributes)
+ #:use-module (starlet colours)
+ #:use-module (starlet utils)
+ #:use-module (starlet attributes)
+ #:export (<lightmaxx-ledcob-5ch>))
+
+(define-fixture
+
+ <lightmaxx-ledcob-5ch>
+
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-colour colour white))
+
+ (let ((intensity (get-attr intensity))
+ (rgb (colour-as-rgb (get-attr colour))))
+ (set-chan8 1 (percent->dmxval8 (car rgb)))
+ (set-chan8 2 (percent->dmxval8 (cadr rgb)))
+ (set-chan8 3 (percent->dmxval8 (caddr rgb)))
+ (set-chan8 4 (percent->dmxval8 intensity))
+ (set-chan8 5 0)))
+
diff --git a/guile/starlet/fixture-library/robe/dl7s.scm b/guile/starlet/fixture-library/robe/dl7s.scm
new file mode 100644
index 0000000..0f9b10d
--- /dev/null
+++ b/guile/starlet/fixture-library/robe/dl7s.scm
@@ -0,0 +1,241 @@
+;;
+;; starlet/fixture-library/robe/dl7s/mode1.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 fixture-library robe dl7s)
+ #:use-module (oop goops)
+ #:use-module (starlet fixture)
+ #:use-module (starlet utils)
+ #:use-module (starlet attributes)
+ #:use-module (starlet colours)
+ #:use-module (starlet scanout)
+ #:export (<robe-dl7s-mode1>))
+
+
+(define virtual-colour-wheel
+ '((#f 0)
+ (lee4-medium-bastard-amber 2)
+ (lee10-medium-yellow 4)
+ (lee19-fire 6)
+ (lee26-bright-red 8)
+ (lee58-lavender 10)
+ (lee68-sky-blue 12)
+ (lee71-tokyo-blue 14)
+ (lee79-just-blue 16)
+ (lee88-lime-green 18)
+ (lee90-dark-yellow-green 20)
+ (lee100-spring-yellow 22)
+ (lee101-yellow 24)
+ (lee102-light-amber 26)
+ (lee103-straw 28)
+ (lee104-deep-amber 30)
+ (lee105-orange 32)
+ (lee106-primary-red 34)
+ (lee111-dark-pink 36)
+ (lee115-peacock-blue 38)
+ (lee116-medium-blue-green 40)
+ (lee117-steel-blue 42)
+ (lee118-light-blue 44)
+ (lee119-dark-blue 46)
+ (lee120-deep-blue 48)
+ (lee121-lee-green 50)
+ (lee128-bright-pink 52)
+ (lee131-marine-blue 54)
+ (lee132-medium-blue 56)
+ (lee134-golden-amber 58)
+ (lee135-deep-golden-amber 60)
+ (lee136-pale-lavender 62)
+ (lee137-special-lavender 64)
+ (lee138-pale-green 66)
+ (lee139-primary-green 68)
+ (lee141-bright-blue 70)
+ (lee147-apricot 72)
+ (lee148-bright-rose 74)
+ (lee152-pale-gold 76)
+ (lee154-pale-rose 78)
+ (lee157-pink 80)
+ (lee158-deep-orange 82)
+ (lee162-bastard-amber 84)
+ (lee164-flame-red 86)
+ (lee165-daylight-blue 88)
+ (lee169-lilac-tint 90)
+ (lee170-deep-lavender 92)
+ (lee172-lagoon-blue 94)
+ (lee179-chrome-orange 96)
+ (lee180-dark-lavender 98)
+ (lee181-congo-blue 100)
+ (lee197-alice-blue 102)
+ (lee201-full-ct-blue 104)
+ (lee202-half-ct-blue 106)
+ (lee203-quarter-ct-blue 108)
+ (lee204-full-ct-orange 110)
+ (lee205-half-ct-orange 112)
+ (lee206-quarter-ct-orange 114)
+ (lee247-lee-minus-green 116)
+ (lee247-half-minus-green 118)
+ (lee281-threequarter-ct-blue 120)
+ (lee285-threequarter-ct-orange 122)
+ (lee352-glacier-blue 124)
+ (lee353-lighter-blue 126)
+ (lee715-cabana-blue 128)
+ (lee778-millennium-gold 130)
+ (lee793-vanity-fair 132)
+ (deep-red 193)
+ (deep-blue 195)
+ (orange 197)
+ (green 199)
+ (magenta 201)
+ (congo-blue 203)
+ (pink 205)
+ (lavender 207)
+ (laser-green 209)
+ (ctb 211)
+ (minus-green 213)
+ (minus-half-green 215)))
+
+(define static-gobo-wheel
+ '((#f 0)
+ (water 7)
+ (rugged-isles 14)
+ (quadrangle-screen 21)
+ (whirl 28)
+ (breakup 36)
+ (blur-breakup 43)
+ (knitting 50)
+ (grit 57)))
+
+(define rotating-gobo-wheel
+ '((#f 0)
+ (rose 7)
+ (water-line 11)
+ (tree-trunk 15)
+ (high-window 20)
+ (grid 24)
+ (clouds 29)))
+
+
+;; FIXME: Gobo shaking (both wheels)
+;; FIXME: Rainbow effect on colour wheel (???)
+;; FIXME: Fine control iris, zoom
+(define-fixture
+
+ <robe-dl7s-mode1>
+
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-continuous pan '(0 540) 270)
+ (attr-continuous tilt '(0 270) 135)
+ (attr-list strobe '(#f on random) #f)
+ (attr-continuous strobe-frequency '(0 25) 25 "Frequencies not calibrated")
+ (attr-list prism '(#t #f) #f)
+ (attr-colour colour white)
+ (attr-list colwheel (map car virtual-colour-wheel) #f "Has priority over 'colour' attribute")
+ (attr-continuous colour-temperature '(2700 8000) 3200)
+ (attr-list animation-wheel '(#t #f) #f)
+ (attr-continuous animation-wheel-position '(-100 100) 0)
+ (attr-continuous animation-wheel-speed '(-100 100) 0)
+ (attr-list gobo (map car static-gobo-wheel) #f)
+ (attr-continuous gobo-shift '(0 100) 0)
+ (attr-list rotating-gobo (map car rotating-gobo-wheel) #f)
+ (attr-continuous rotating-gobo-speed '(-100 100) 0)
+ (attr-continuous prism-rotation-speed '(-100 100) 0)
+ (attr-continuous frost '(0 100) 0)
+ (attr-continuous zoom '(0 100) 50)
+ (attr-continuous iris '(0 100) 0)
+ (attr-continuous barndoor-all-rotation '(-45 45) 0)
+ (attr-continuous barndoor1 '(0 100) 0)
+ (attr-continuous barndoor2 '(0 100) 0)
+ (attr-continuous barndoor3 '(0 100) 0)
+ (attr-continuous barndoor4 '(0 100) 0)
+ (attr-continuous barndoor1-rotation '(-25 25) 0)
+ (attr-continuous barndoor2-rotation '(-25 25) 0)
+ (attr-continuous barndoor3-rotation '(-25 25) 0)
+ (attr-continuous barndoor4-rotation '(-25 25) 0)
+ (attr-continuous focus '(0 100) 50))
+
+ (set-chan16 1 (scale-to-range (get-attr pan) '(0 540) '(0 65535)))
+ (set-chan16 3 (scale-to-range (get-attr tilt) '(0 270) '(0 65535)))
+
+ (set-chan8 5 0) ;; Pan/tilt speed/time: maximum speed
+ (set-chan8 6 0) ;; Power/special function: default
+ (set-chan8 7 0) ;; Colour mode: default
+
+ (set-chan8 8 (lookup (get-attr colwheel) virtual-colour-wheel))
+
+ (let ((cmy (colour-as-cmy (get-attr colour))))
+ (set-chan16 9 (percent->dmxval16 (cyan cmy)))
+ (set-chan16 11 (percent->dmxval16 (magenta cmy)))
+ (set-chan16 13 (percent->dmxval16 (yellow cmy))))
+
+ (set-chan8 15
+ (scale-and-clamp-to-range (get-attr colour-temperature)
+ '(8000 2700) '(0 255)))
+
+ (set-chan8 16 0) ;; Green correction: uncorrected white
+ (set-chan8 17 0) ;; Colour mix control: virtual colour wheel has priority
+ (set-chan8 18 0) ;; Rotating gobo selection speed: maximum
+ (set-chan8 19 0) ;; Everything time: off (???)
+
+ (let ((ani-active (get-attr animation-wheel)))
+ (set-chan8 20 (if ani-active
+ (scale-to-range (get-attr animation-wheel-position)
+ '(-100 100) '(19 127))
+ 0)) ;; 73 = stop
+ (set-chan8 21 (scale-to-range (get-attr animation-wheel-speed)
+ '(100 -100) '(1 255)))) ;; 128 = stop
+ (set-chan8 22 0) ;; Animation wheel macro: no function
+
+ (set-chan8 23 (lookup (get-attr gobo) static-gobo-wheel))
+ (set-chan8 24 (percent->dmxval8 (get-attr gobo-shift)))
+
+ (set-chan8 25 (lookup (get-attr rotating-gobo) rotating-gobo-wheel))
+ (set-chan8 26 (scale-to-range (get-attr rotating-gobo-speed)
+ '(-100 100) '(1 255))) ;; 128 = stop
+ (set-chan8 27 0) ;; Rotating gobo fine adjustment (default)
+
+ (set-chan8 28 (if (get-attr prism) 50 0))
+ (set-chan8 29 (scale-to-range (get-attr prism-rotation-speed)
+ '(100 -100) '(1 255))) ;; 128 = stop, <128=forwards
+ (set-chan8 30 (scale-to-range (get-attr frost) '(0 100) '(0 180)))
+ (set-chan8 31 (scale-to-range (get-attr iris) '(0 100) '(0 180)))
+ (set-chan16 33 (percent->dmxval16 (get-attr zoom)))
+ (set-chan16 35 (percent->dmxval16 (get-attr focus)))
+
+ (set-chan8 38 (scale-to-range (get-attr barndoor-all-rotation) '(-45 45) '(0 255)))
+ (set-chan8 39 (percent->dmxval8 (get-attr barndoor1)))
+ (set-chan8 40 (scale-to-range (get-attr barndoor1-rotation) '(-25 25) '(0 255)))
+ (set-chan8 41 (percent->dmxval8 (get-attr barndoor2)))
+ (set-chan8 42 (scale-to-range (get-attr barndoor2-rotation) '(-25 25) '(0 255)))
+ (set-chan8 43 (percent->dmxval8 (get-attr barndoor3)))
+ (set-chan8 44 (scale-to-range (get-attr barndoor3-rotation) '(-25 25) '(0 255)))
+ (set-chan8 45 (percent->dmxval8 (get-attr barndoor4)))
+ (set-chan8 46 (scale-to-range (get-attr barndoor4-rotation) '(-25 25) '(0 255)))
+ (set-chan8 47 0) ;; Framing shutter macro: no function
+ (set-chan8 48 128) ;; Framing shutter macro speed: default
+
+ (let ((strb (get-attr strobe)))
+ (set-chan8 49
+ (cond
+ ((not strb) 32)
+ ((eq? strb 'on)
+ (scale-to-range (get-attr strobe-frequency) '(1 25) '(64 95)))
+ ((eq? strb 'random)
+ (scale-to-range (get-attr strobe-frequency) '(1 25) '(192 223))))))
+
+ (set-chan16 50 (percent->dmxval16 (get-attr intensity))))
diff --git a/guile/starlet/fixture-library/robe/dl7s/mode1.scm b/guile/starlet/fixture-library/robe/dl7s/mode1.scm
deleted file mode 100644
index e9d5a9a..0000000
--- a/guile/starlet/fixture-library/robe/dl7s/mode1.scm
+++ /dev/null
@@ -1,65 +0,0 @@
-;;
-;; starlet/fixture-library/robe/dl7s/mode1.scm
-;;
-;; Copyright © 2020-2021 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 fixture-library robe dl7s mode1)
- #:use-module (oop goops)
- #:use-module (starlet fixture)
- #:use-module (starlet colours)
- #:export (<robe-dl7s-mode1>))
-
-
-(define-class <robe-dl7s-mode1> (<fixture>)
- (attributes
- #:init-form (list
- (attr-continuous 'intensity '(0 100) 0)
- (attr-continuous 'pan '(0 540) 270)
- (attr-continuous 'tilt '(0 270) 135)
- (attr-list 'strobe '(#t #f) #f)
- (attr-list 'prism '(#t #f) #f)
- (attr-list 'tungsten-watts-emulation '(750 1000 1200 2000 2500 #f) #f)
- (attr-colour 'colour white)
- (attr-continuous 'colour-temperature-correction '(2700 8000) 8000)
- (attr-continuous 'green-correction '(-100 100) 0))))
-
-
-(define-method (scanout-fixture (fixture <robe-dl7s-mode1>)
- get-attr set-chan8 set-chan16)
-
- (set-chan16 50 (percent->dmxval16 (get-attr 'intensity)))
-
- (set-chan16 1 (scale-to-range (get-attr 'pan) '(0 540) '(0 65535)))
- (set-chan16 3 (scale-to-range (get-attr 'tilt) (0 270) '(0 65535)))
-
- (set-chan8 49 (if (get-attr 'strobe) 95 32))
-
- (set-chan8 28 (if (get-attr 'prism) 50 0))
-
- (set-chan8 7 (assv-ref '((750 . 82)
- (1000 . 88)
- (1200 . 92)
- (2000 . 97)
- (2500 . 102)
- (#f . 107))
- (get-attr 'tungsten-watts-emulation)))
-
- (let ((cmy (colour-as-cmy (get-attr 'colour))))
- (set-chan8 9 (percent->dmxval8 (car cmy)))
- (set-chan8 11 (percent->dmxval8 (cadr cmy)))
- (set-chan8 13 (percent->dmxval8 (caddr cmy)))))
diff --git a/guile/starlet/fixture-library/robe/mmxspot.scm b/guile/starlet/fixture-library/robe/mmxspot.scm
new file mode 100644
index 0000000..1f37299
--- /dev/null
+++ b/guile/starlet/fixture-library/robe/mmxspot.scm
@@ -0,0 +1,87 @@
+;;
+;; starlet/fixture-library/robe/mmxspot/mode1.scm
+;;
+;; Copyright © 2020-2021 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 fixture-library robe mmxspot)
+ #:use-module (oop goops)
+ #:use-module (starlet fixture)
+ #:use-module (starlet attributes)
+ #:use-module (starlet colours)
+ #:export (<robe-mmxspot-mode1>))
+
+
+(define-fixture
+
+ <robe-mmxspot-mode1>
+
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-continuous pan '(0 540) 270)
+ (attr-continuous tilt '(0 270) 135)
+ (attr-list colwheel '(#f red blue orange green amber uv) #f)
+ (attr-list prism '(#t #f) #f)
+ (attr-list strobe '(off on random zap) off)
+ (attr-continuous strobe-frequency '(0 100) 50)
+ (attr-colour colour white)
+ (attr-continuous iris '(0 100) 0)
+ (attr-continuous zoom '(0 100) 0)
+ (attr-continuous focus '(0 100) 0)
+ (attr-continuous hotspot '(0 100) 0)
+ (attr-continuous frost '(0 100) 0)
+ (attr-continuous cto '(3200 6900) 6900))
+
+ (set-chan16 37 (percent->dmxval16 (get-attr intensity)))
+
+ (set-chan16 1 (scale-to-range (get-attr pan) '(0 540) '(0 65535)))
+
+ (set-chan16 3 (scale-to-range (get-attr tilt) '(0 270) '(0 65535)))
+
+ (set-chan16 28 (scale-to-range (get-attr iris) '(0 100) '(0 45567)))
+ (set-chan16 30 (percent->dmxval16 (get-attr zoom)))
+ (set-chan16 32 (percent->dmxval16 (get-attr focus)))
+
+ (set-chan8 36
+ (let ((strb (get-attr strobe))
+ (spd (get-attr strobe-speed)))
+ (cond
+ ;; FIXME: Check the frequencies
+ ((eq? strb 'on) (scale-to-range spd '(0 100) '(64 95)))
+ ((eq? strb 'random) (scale-to-range spd '(0 100) '(192 223)))
+ ((eq? strb 'zap) (scale-to-range spd '(0 100) '(160 191)))
+ (else 255))))
+
+ (set-chan8 25 (if (get-attr prism) 20 0))
+
+ (set-chan8 7 (assv-ref '((#f . 0)
+ (red . 18)
+ (blue . 37)
+ (orange . 55)
+ (green . 73)
+ (amber . 91)
+ (uv . 110))
+ (get-attr colwheel)))
+
+ (let ((cmy (colour-as-cmy (get-attr colour))))
+ (set-chan8 9 (percent->dmxval8 (car cmy)))
+ (set-chan8 10 (percent->dmxval8 (cadr cmy)))
+ (set-chan8 11 (percent->dmxval8 (caddr cmy))))
+
+ (set-chan8 35 (percent->dmxval8 (get-attr hotspot)))
+ (set-chan8 12 (scale-to-range (get-attr cto) '(3200 6900) '(0 255)))
+ (set-chan8 27 (scale-to-range (get-attr frost) '(0 100) '(0 179))))
diff --git a/guile/starlet/fixture-library/robe/mmxspot/mode1.scm b/guile/starlet/fixture-library/robe/mmxspot/mode1.scm
deleted file mode 100644
index 9fedde4..0000000
--- a/guile/starlet/fixture-library/robe/mmxspot/mode1.scm
+++ /dev/null
@@ -1,87 +0,0 @@
-;;
-;; starlet/fixture-library/robe/mmxspot/mode1.scm
-;;
-;; Copyright © 2020-2021 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 fixture-library robe mmxspot mode1)
- #:use-module (oop goops)
- #:use-module (starlet fixture)
- #:use-module (starlet colours)
- #:export (<robe-mmxspot-mode1>))
-
-
-(define-class <robe-mmxspot-mode1> (<fixture>)
- (attributes
- #:init-form (list
- (attr-continuous 'intensity '(0 100) 0)
- (attr-continuous 'pan '(0 540) 270)
- (attr-continuous 'tilt '(0 270) 135)
- (attr-list 'colwheel '(#f red blue orange green amber uv) #f)
- (attr-list 'prism '(#t #f) #f)
- (attr-list 'strobe '(#f #t random zap) #f)
- (attr-continuous 'strobe-speed '(0 100) 50)
- (attr-colour 'colour white)
- (attr-continuous 'iris '(0 100) 0)
- (attr-continuous 'zoom '(0 100) 0)
- (attr-continuous 'focus '(0 100) 0)
- (attr-continuous 'hotspot '(0 100) 0)
- (attr-continuous 'frost '(0 100) 0)
- (attr-continuous 'cto '(3200 6900) 6900))))
-
-
-(define-method (scanout-fixture (fixture <robe-mmxspot-mode1>)
- get-attr set-chan8 set-chan16)
-
- (set-chan16 37 (percent->dmxval16 (get-attr 'intensity)))
-
- (set-chan16 1 (scale-to-range (get-attr 'pan) '(0 540) '(0 65535)))
-
- (set-chan16 3 (scale-to-range (get-attr 'tilt) '(0 270) '(0 65535)))
-
- (set-chan16 28 (scale-to-range (get-attr 'iris) '(0 100) '(0 45567)))
- (set-chan16 30 (percent->dmxval16 (get-attr 'zoom)))
- (set-chan16 32 (percent->dmxval16 (get-attr 'focus)))
-
- (set-chan8 36
- (let ((strb (get-attr 'strobe))
- (spd (get-attr 'strobe-speed)))
- (cond
- ((eq? strb #t) (scale-to-range spd '(0 100) '(64 95)))
- ((eq? strb 'random) (scale-to-range spd '(0 100) '(192 223)))
- ((eq? strb 'zap) (scale-to-range spd '(0 100) '(160 191)))
- (else 255))))
-
- (set-chan8 25 (if (get-attr 'prism) 20 0))
-
- (set-chan8 7 (assv-ref '((#f . 0)
- (red . 18)
- (blue . 37)
- (orange . 55)
- (green . 73)
- (amber . 91)
- (uv . 110))
- (get-attr 'colwheel)))
-
- (let ((cmy (colour-as-cmy (get-attr 'colour))))
- (set-chan8 9 (percent->dmxval8 (car cmy)))
- (set-chan8 10 (percent->dmxval8 (cadr cmy)))
- (set-chan8 11 (percent->dmxval8 (caddr cmy))))
-
- (set-chan8 35 (percent->dmxval8 (get-attr 'hotspot)))
- (set-chan8 12 (scale-to-range (get-attr 'cto) '(3200 6900) '(0 255)))
- (set-chan8 27 (scale-to-range (get-attr 'frost) '(0 100) '(0 179))))
diff --git a/guile/starlet/fixture-library/robe/mmxwashbeam.scm b/guile/starlet/fixture-library/robe/mmxwashbeam.scm
new file mode 100644
index 0000000..a41c80d
--- /dev/null
+++ b/guile/starlet/fixture-library/robe/mmxwashbeam.scm
@@ -0,0 +1,94 @@
+;;
+;; starlet/fixture-library/robe/mmxwashbeam.scm
+;;
+;; Copyright © 2020-2022 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 fixture-library robe mmxwashbeam)
+ #:use-module (starlet scanout)
+ #:use-module (starlet fixture)
+ #:use-module (starlet attributes)
+ #:use-module (starlet utils)
+ #:use-module (starlet colours)
+ #:export (<robe-mmxwashbeam-mode1>))
+
+
+(define-fixture
+
+ <robe-mmxwashbeam-mode1>
+
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-continuous pan '(0 540) 270)
+ (attr-continuous tilt '(0 270) 135)
+ (attr-list strobe '(#t #f) #f)
+ (attr-list colwheel '(#f red blue orange green amber uv) #f)
+ (attr-list gobo '(#f iris gobo1 gobo2 gobo3 gobo4 gobo5 gobo6) #f)
+ (attr-list beamtype '(beam beamwash beamwashext) 'beam)
+ (attr-colour colour white)
+ (attr-continuous zoom '(0 100) 0)
+ (attr-continuous focus '(0 100) 0)
+ (attr-continuous barndoor-rot '(0 180) 90)
+ (attr-continuous barndoor1 '(0 180) 0)
+ (attr-continuous barndoor2 '(0 100) 0)
+ (attr-continuous barndoor3 '(0 100) 0)
+ (attr-continuous barndoor4 '(0 100) 0))
+
+ (set-chan16 33 (percent->dmxval16 (get-attr intensity)))
+
+ (set-chan16 1 (scale-to-range (get-attr pan) '(0 540) '(0 65535)))
+ (set-chan16 3 (scale-to-range (get-attr tilt) '(0 270) '(0 65535)))
+
+ (set-chan8 32 (if (get-attr strobe) 70 255))
+
+ (set-chan16 19 (percent->dmxval16 (get-attr zoom)))
+ (set-chan16 21 (percent->dmxval16 (get-attr focus)))
+
+ ;;(set-chan 24 (number->dmxval (get-attr barndoor-rot) '(0 180)))
+ (set-chan8 25 (percent->dmxval8 (get-attr barndoor1)))
+ (set-chan8 26 (percent->dmxval8 (get-attr barndoor2)))
+ (set-chan8 27 (percent->dmxval8 (get-attr barndoor3)))
+ (set-chan8 28 (percent->dmxval8 (get-attr barndoor4)))
+
+ (set-chan8 7 (assv-ref '((#f . 0)
+ (red . 18)
+ (blue . 37)
+ (orange . 55)
+ (green . 73)
+ (amber . 91)
+ (uv . 110))
+ (get-attr colwheel)))
+
+ (set-chan8 15 (assv-ref '((#f . 0)
+ (iris . 5)
+ (gobo1 . 10)
+ (gobo2 . 14)
+ (gobo3 . 18)
+ (gobo4 . 22)
+ (gobo5 . 26)
+ (gobo6 . 30))
+ (get-attr gobo)))
+
+ (set-chan8 18 (assv-ref '((beam . 0)
+ (beamwash . 35)
+ (beamwashext . 45))
+ (get-attr beamtype)))
+
+ (let ((cmy (colour-as-cmy (get-attr colour))))
+ (set-chan8 9 (percent->dmxval8 (car cmy)))
+ (set-chan8 10 (percent->dmxval8 (cadr cmy)))
+ (set-chan8 11 (percent->dmxval8 (caddr cmy)))))
diff --git a/guile/starlet/fixture-library/robe/mmxwashbeam/mode1.scm b/guile/starlet/fixture-library/robe/mmxwashbeam/mode1.scm
deleted file mode 100644
index b412a24..0000000
--- a/guile/starlet/fixture-library/robe/mmxwashbeam/mode1.scm
+++ /dev/null
@@ -1,94 +0,0 @@
-;;
-;; starlet/fixture-library/robe/mmxwashbeam/mode1.scm
-;;
-;; Copyright © 2020-2021 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 fixture-library robe mmxwashbeam mode1)
- #:use-module (oop goops)
- #:use-module (starlet fixture)
- #:use-module (starlet colours)
- #:export (<robe-mmxwashbeam-mode1>))
-
-
-(define-class <robe-mmxwashbeam-mode1> (<fixture>)
- (attributes
- #:init-form (list
- (attr-continuous 'intensity '(0 100) 0)
- (attr-continuous 'pan '(0 540) 270)
- (attr-continuous 'tilt '(0 270) 135)
- (attr-list 'strobe '(#t #f) #f)
- (attr-list 'colwheel '(#f red blue orange green amber uv) #f)
- (attr-list 'gobo '(#f iris gobo1 gobo2 gobo3 gobo4 gobo5 gobo6) #f)
- (attr-list 'beamtype '(beam beamwash beamwashext) 'beam)
- (attr-colour 'colour white)
- (attr-continuous 'zoom '(0 100) 0)
- (attr-continuous 'focus '(0 100) 0)
- (attr-continuous 'barndoor-rot '(0 180) 90)
- (attr-continuous 'barndoor1 '(0 180) 0)
- (attr-continuous 'barndoor2 '(0 100) 0)
- (attr-continuous 'barndoor3 '(0 100) 0)
- (attr-continuous 'barndoor4 '(0 100) 0))))
-
-
-(define-method (scanout-fixture (fixture <robe-mmxwashbeam-mode1>)
- get-attr set-chan8 set-chan16)
-
- (set-chan16 33 (percent->dmxval16 (get-attr 'intensity)))
-
- (set-chan16 1 (scale-to-range (get-attr 'pan) '(0 540) '(0 65535)))
- (set-chan16 3 (scale-to-range (get-attr 'tilt) '(0 270) '(0 65535)))
-
- (set-chan8 32 (if (get-attr 'strobe) 70 255))
-
- (set-chan16 19 (percent->dmxval16 (get-attr 'zoom)))
- (set-chan16 21 (percent->dmxval16 (get-attr 'focus)))
-
- ;;(set-chan 24 (number->dmxval (get-attr 'barndoor-rot) '(0 180)))
- (set-chan8 25 (percent->dmxval8 (get-attr 'barndoor1)))
- (set-chan8 26 (percent->dmxval8 (get-attr 'barndoor2)))
- (set-chan8 27 (percent->dmxval8 (get-attr 'barndoor3)))
- (set-chan8 28 (percent->dmxval8 (get-attr 'barndoor4)))
-
- (set-chan8 7 (assv-ref '((#f . 0)
- (red . 18)
- (blue . 37)
- (orange . 55)
- (green . 73)
- (amber . 91)
- (uv . 110))
- (get-attr 'colwheel)))
-
- (set-chan8 15 (assv-ref '((#f . 0)
- (iris . 5)
- (gobo1 . 10)
- (gobo2 . 14)
- (gobo3 . 18)
- (gobo4 . 22)
- (gobo5 . 26)
- (gobo6 . 30))
- (get-attr 'gobo)))
-
- (set-chan8 18 (assv-ref '((beam . 0)
- (beamwash . 35)
- (beamwashext . 45))
- (get-attr 'beamtype)))
-
- (let ((cmy (colour-as-cmy (get-attr 'colour))))
- (set-chan8 9 (percent->dmxval8 (car cmy)))
- (set-chan8 10 (percent->dmxval8 (cadr cmy)))
- (set-chan8 11 (percent->dmxval8 (caddr cmy)))))
diff --git a/guile/starlet/fixture-library/stairville/octagon-theater-cw-ww.scm b/guile/starlet/fixture-library/stairville/octagon-theater-cw-ww.scm
new file mode 100644
index 0000000..b3320b2
--- /dev/null
+++ b/guile/starlet/fixture-library/stairville/octagon-theater-cw-ww.scm
@@ -0,0 +1,51 @@
+;;
+;; starlet/fixture-library/stairville/octagon-theater-cw-ww.scm
+;;
+;; Copyright © 2020-2022 Thomas White <taw@bitwiz.me.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 fixture-library stairville octagon-theater-cw-ww)
+ #:use-module (starlet scanout)
+ #:use-module (starlet fixture)
+ #:use-module (starlet attributes)
+ #:use-module (starlet utils)
+ #:export (<stairville-octagon-theater-cw-ww>))
+
+(define-fixture
+
+ <stairville-octagon-theater-cw-ww>
+
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-continuous colour-temperature '(2800 6400) 3200)
+ (attr-list strobe '(#f #t) #f)
+ ;; FIXME: Strobe frequency is not stated in manual.
+ ;; I've assumed that "slow" means 1 Hz, "fast" 25 Hz
+ (attr-continuous strobe-frequency '(1 25) 1))
+
+ (let ((coltemp (get-attr colour-temperature)))
+ (set-chan8 1 (scale-and-clamp-to-range coltemp '(2800 6400) '(0 255)))
+ (set-chan8 2 (scale-and-clamp-to-range coltemp '(2800 6400) '(255 0))))
+ (if (get-attr strobe)
+ (set-chan8 3 (scale-and-clamp-to-range
+ (get-attr strobe-frequency)
+ '(1 25)
+ '(16 255)))
+ (set-chan8 3 0))
+ (set-chan8 3 0) ;; Strobe
+ (set-chan8 4 0) ;; Mode (0-15 = direct control)
+ (set-chan8 5 (percent->dmxval8 (get-attr intensity))))
diff --git a/guile/starlet/fixture-library/stairville/z120m.scm b/guile/starlet/fixture-library/stairville/z120m.scm
new file mode 100644
index 0000000..00fb476
--- /dev/null
+++ b/guile/starlet/fixture-library/stairville/z120m.scm
@@ -0,0 +1,69 @@
+;;
+;; starlet/fixture-library/stairville/z120m.scm
+;;
+;; Copyright © 2020-2022 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 fixture-library stairville z120m)
+ #:use-module (starlet scanout)
+ #:use-module (starlet fixture)
+ #:use-module (starlet attributes)
+ #:use-module (starlet utils)
+ #:use-module (starlet colours)
+ #:export (<stairville-z120m-6ch>))
+
+
+(define (colour-as-rgbw-weirdness col weirdness)
+ (let ((rgb (colour-as-rgb col)))
+ (let ((w (* (- 1 weirdness) (apply min rgb))))
+ (list (- (red rgb) w)
+ (- (green rgb) w)
+ (- (blue rgb) w)
+ w))))
+
+
+(define-fixture
+
+ <stairville-z120m-6ch>
+
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-colour colour white)
+ (attr-continuous strobe-frequency '(1 25) 1)
+ (attr-list strobe '(off on random) 'off)
+ (attr-continuous white-weirdness '(0 100) 0))
+
+ (let ((intensity (get-attr intensity))
+ (rgbw (colour-as-rgbw-weirdness (get-attr colour)
+ (/ (get-attr white-weirdness) 100))))
+ (set-chan8 1 (percent->dmxval8 intensity))
+ (set-chan8 3 (percent->dmxval8 (car rgbw)))
+ (set-chan8 4 (percent->dmxval8 (cadr rgbw)))
+ (set-chan8 5 (percent->dmxval8 (caddr rgbw)))
+ (set-chan8 6 (percent->dmxval8 (cadddr rgbw))))
+ (cond
+ ((eq? (get-attr strobe) 'on)
+ (set-chan8 2 (scale-and-clamp-to-range
+ (get-attr 'strobe-frequency)
+ '(1 25)
+ '(106 165))))
+ ((eq? (get-attr strobe) 'random)
+ (set-chan8 2 (scale-and-clamp-to-range
+ (get-attr strobe-frequency)
+ '(1 25)
+ '(181 240))))
+ (else (set-chan8 2 255))))
diff --git a/guile/starlet/fixture-library/tadm/led-bar.scm b/guile/starlet/fixture-library/tadm/led-bar.scm
index a04ed5e..45c4e34 100644
--- a/guile/starlet/fixture-library/tadm/led-bar.scm
+++ b/guile/starlet/fixture-library/tadm/led-bar.scm
@@ -1,7 +1,7 @@
;;
;; starlet/fixture-library/tadm/led-bar.scm
;;
-;; Copyright © 2020-2021 Thomas White <taw@bitwiz.org.uk>
+;; Copyright © 2020-2022 Thomas White <taw@bitwiz.org.uk>
;;
;; This file is part of Starlet.
;;
@@ -19,24 +19,24 @@
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
(define-module (starlet fixture-library tadm led-bar)
- #:use-module (oop goops)
+ #:use-module (starlet scanout)
#:use-module (starlet fixture)
+ #:use-module (starlet attributes)
#:use-module (starlet colours)
+ #:use-module (starlet utils)
+ #:use-module (starlet attributes)
#:export (<tadm-led-bar>))
+(define-fixture
-(define-class <tadm-led-bar> (<fixture>)
- (attributes
- #:init-form (list
- (attr-continuous 'intensity '(0 100) 0)
- (attr-colour 'colour white))))
+ <tadm-led-bar>
+ (fixture-attributes
+ (attr-continuous intensity '(0 100) 0)
+ (attr-colour colour white))
-(define-method (scanout-fixture (fixture <tadm-led-bar>)
- get-attr set-chan8 set-chan16)
-
- (let ((intensity (get-attr 'intensity))
- (rgb (colour-as-rgb (get-attr 'colour))))
+ (let ((intensity (get-attr intensity))
+ (rgb (colour-as-rgb (get-attr colour))))
(set-chan8 1 17)
(set-chan8 2 (percent->dmxval8 intensity))
(set-chan8 3 0)
diff --git a/guile/starlet/fixture-library/tadm/led-foh.scm b/guile/starlet/fixture-library/tadm/led-foh.scm
deleted file mode 100644
index 18ad15f..0000000
--- a/guile/starlet/fixture-library/tadm/led-foh.scm
+++ /dev/null
@@ -1,46 +0,0 @@
-;;
-;; starlet/fixture-library/tadm/led-foh.scm
-;;
-;; Copyright © 2020-2021 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 fixture-library tadm led-foh)
- #:use-module (oop goops)
- #:use-module (starlet fixture)
- #:use-module (starlet colours)
- #:export (<tadm-led-foh>))
-
-
-(define-class <tadm-led-foh> (<fixture>)
- (attributes
- #:init-form (list
- (attr-continuous 'intensity '(0 100) 0)
- (attr-colour 'colour white))))
-
-
-(define-method (scanout-fixture (fixture <tadm-led-foh>)
- get-attr set-chan8 set-chan16)
-
- (let ((intensity (get-attr 'intensity))
- (rgb (colour-as-rgb (get-attr 'colour))))
- (set-chan8 1 (percent->dmxval8 intensity))
- (set-chan8 2 255)
- (set-chan8 3 (percent->dmxval8 (car rgb)))
- (set-chan8 4 (percent->dmxval8 (cadr rgb)))
- (set-chan8 5 (percent->dmxval8 (caddr rgb)))
- (set-chan8 6 0)))
-
diff --git a/guile/starlet/fixture.scm b/guile/starlet/fixture.scm
index 9f58f25..524d78b 100644
--- a/guile/starlet/fixture.scm
+++ b/guile/starlet/fixture.scm
@@ -20,7 +20,10 @@
;;
(define-module (starlet fixture)
#:use-module (starlet colours)
+ #:use-module (starlet utils)
+ #:use-module (starlet attributes)
#:use-module (oop goops)
+ #:use-module (ice-9 exceptions)
#:use-module (srfi srfi-1)
#:export (<fixture>
get-fixture-name
@@ -28,23 +31,24 @@
get-fixture-universe
get-fixture-attrs
find-attr
+ fixture-has-attr?
+
fixture?
scanout-fixture
attr-continuous
attr-list
attr-colour
+ define-fixture
+
get-attr-type
get-attr-range
get-attr-home-val
continuous-attribute?
colour-attribute?
- intensity?
- scale-to-range
- round-dmx
- percent->dmxval8
- percent->dmxval16))
+ next-attr-item
+ prev-attr-item))
(define-class <fixture-attribute> (<object>)
@@ -66,7 +70,12 @@
(home-value
#:init-value 0
#:init-keyword #:home-value
- #:getter attr-home-value))
+ #:getter attr-home-value)
+
+ (comment
+ #:init-value ""
+ #:init-keyword #:comment
+ #:getter attr-comment))
(define-class <fixture> (<object>)
@@ -87,23 +96,21 @@
#:getter get-fixture-addr
#:setter set-fixture-addr!)
- (friendly-name
- #:init-value "Fixture"
- #:init-keyword #:friendly-name
- #:getter get-fixture-friendly-name
- #:setter set-fixture-friendly-name!)
-
(scanout-func
#:init-value (lambda (universe start-addr value set-dmx) #f)
#:init-keyword #:scanout-func
#:getter get-scanout-func))
-(define-generic scanout-fixture)
-
-
(define-syntax attr-continuous
(syntax-rules ()
+ ((_ attr-name attr-range attr-home-value comment)
+ (make <fixture-attribute>
+ #:name attr-name
+ #:range attr-range
+ #:type 'continuous
+ #:home-value attr-home-value
+ #:comment comment))
((_ attr-name attr-range attr-home-value)
(make <fixture-attribute>
#:name attr-name
@@ -119,11 +126,24 @@
#:name attr-name
#:range attr-allowed-values
#:type 'list
- #:home-value attr-home-value))))
+ #:home-value attr-home-value))
+ ((_ attr-name attr-allowed-values attr-home-value comment)
+ (make <fixture-attribute>
+ #:name attr-name
+ #:range attr-allowed-values
+ #:type 'list
+ #:home-value attr-home-value
+ #:comment comment))))
(define-syntax attr-colour
(syntax-rules ()
+ ((_ attr-name attr-home-value comment)
+ (make <fixture-attribute>
+ #:name attr-name
+ #:type 'colour
+ #:home-value attr-home-value
+ #:comment comment))
((_ attr-name attr-home-value)
(make <fixture-attribute>
#:name attr-name
@@ -131,6 +151,9 @@
#:home-value attr-home-value))))
+(define-generic scanout-fixture)
+
+
(define (get-fixture-attrs fix)
(slot-ref fix 'attributes))
@@ -139,34 +162,20 @@
(is-a? f <fixture>))
-(define-method (find-attr (fix <fixture>) (attr-name <symbol>))
+(define (find-attr fix attr-name)
(find (lambda (a)
(eq? (get-attr-name a)
attr-name))
(get-fixture-attrs fix)))
-(define-method (find-attr (fix <fixture>) (attr-name <colour-component-id>))
- (find-attr fix 'colour))
-
-
-(define-method (get-attr-home-val (fix <fixture>) (attr <symbol>))
+(define (get-attr-home-val fix attr)
(let ((attr-obj (find-attr fix attr)))
(if attr-obj
(attr-home-value attr-obj)
'fixture-does-not-have-attribute)))
-(define-method (get-attr-home-val (fix <fixture>) (attr <colour-component-id>))
- (extract-colour-component
- (get-attr-home-val fix 'colour)
- attr))
-
-
-(define (intensity? a)
- (eq? 'intensity a))
-
-
(define (continuous-attribute? aobj)
(eq? 'continuous
(get-attr-type aobj)))
@@ -177,28 +186,30 @@
(get-attr-type aobj)))
-;; Helper functions for fixture scanout routines
-(define (percent->dmxval8 val)
- (round-dmx
- (scale-to-range val '(0 100) '(0 255))))
-
+(define-syntax define-fixture
+ (syntax-rules (fixture-attributes)
-(define (percent->dmxval16 val)
- (scale-to-range val '(0 100) '(0 65535)))
+ ((_ classname
+ (fixture-attributes attr ...)
+ scanout-code ...)
+ (begin
+ (define-class classname (<fixture>)
+ (attributes #:init-form (list attr ...)))
+ (define-method (scanout-fixture (fixture classname))
+ scanout-code ...)))))
-(define (round-dmx a)
- (inexact->exact
- (min 255 (max 0 (round a)))))
+(define fixture-has-attr? find-attr)
-(define (scale-to-range val orig-range dest-range)
- (define (range r)
- (- (cadr r) (car r)))
+(define (next-attr-item attr cval)
+ (next-item-in-list
+ (get-attr-range attr)
+ cval))
- (+ (car dest-range)
- (* (range dest-range)
- (/ (- val (car orig-range))
- (range orig-range)))))
+(define (prev-attr-item attr cval)
+ (next-item-in-list
+ (reverse (get-attr-range attr))
+ cval))
diff --git a/guile/starlet/midi-control/base.scm b/guile/starlet/midi-control/base.scm
deleted file mode 100644
index bed1a1f..0000000
--- a/guile/starlet/midi-control/base.scm
+++ /dev/null
@@ -1,294 +0,0 @@
-;;
-;; starlet/midi-control/base.scm
-;;
-;; Copyright © 2020-2021 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 midi-control base)
- #:use-module (oop goops)
- #:use-module (ice-9 atomic)
- #:use-module (ice-9 threads)
- #:use-module (ice-9 exceptions)
- #:use-module (ice-9 binary-ports)
- #:use-module (srfi srfi-1)
- #:export (make-midi-controller
- get-cc-value
- ccval->percent
- percent->ccval
- send-note-on
- send-note-off
- register-midi-note-callback!
- register-midi-cc-callback!
- remove-midi-callback!
- get-parameter-controller
- get-controller-sensitivity
- set-parameter-controller!
- make-sensitivity-knob))
-
-
-(define-class <midi-control-surface> (<object>)
- (cc-values
- #:init-form (make-vector 128 #f)
- #:getter get-cc-values)
-
- (channel
- #:init-form (error "MIDI channel must be specified for controller")
- #:init-keyword #:channel
- #:getter get-channel)
-
- (callbacks
- #:init-form (make-atomic-box '())
- #:getter get-callbacks)
-
- (send-queue
- #:init-form (make-atomic-box '())
- #:getter get-send-queue)
-
- (parameter-controller
- #:init-value #f
- #:getter get-parameter-controller
- #:setter set-parameter-controller!)
-
- (sensitivity
- #:init-value 3
- #:getter get-controller-sensitivity
- #:setter set-controller-sensitivity!))
-
-
-(define-class <midi-callback> (<object>)
- (type
- #:init-keyword #:type
- #:getter get-type)
-
- (note-or-cc-number
- #:init-keyword #:note-or-cc-number
- #:getter get-note-or-cc-number)
-
- (callback
- #:init-keyword #:func
- #:getter get-callback-func))
-
-
-(define (find-cc-callbacks controller cc-number)
- (filter (lambda (a)
- (and (eq? cc-number (get-note-or-cc-number a))
- (eq? 'cc (get-type a))))
- (atomic-box-ref (get-callbacks controller))))
-
-
-(define (find-note-callbacks controller note-number)
- (filter (lambda (a)
- (and (eq? note-number (get-note-or-cc-number a))
- (eq? 'note (get-type a))))
- (atomic-box-ref (get-callbacks controller))))
-
-
-(define (remove-midi-callback! controller callback)
- (when controller
- (atomic-box-set! (get-callbacks controller)
- (delq callback
- (atomic-box-ref (get-callbacks controller))))))
-
-
-(define (register-midi-callback! controller
- type
- note-or-cc-number
- func)
- (let ((new-callback (make <midi-callback>
- #:type type
- #:note-or-cc-number note-or-cc-number
- #:func func)))
- (let ((callback-list-box (get-callbacks controller)))
- (atomic-box-set! callback-list-box
- (cons new-callback
- (atomic-box-ref callback-list-box))))
- new-callback))
-
-
-(define* (register-midi-note-callback!
- controller
- #:key (note-number 1) (func #f) (unique #t))
- (when controller
- (when unique
- (for-each (lambda (callback)
- (remove-midi-callback! controller callback))
- (find-note-callbacks
- controller
- note-number)))
- (register-midi-callback! controller 'note note-number func)))
-
-
-(define* (register-midi-cc-callback!
- controller
- #:key (cc-number 1) (func #f) (unique #t))
- (when controller
- (when unique
- (for-each (lambda (callback)
- (remove-midi-callback! controller callback))
- (find-cc-callbacks
- controller
- cc-number)))
- (register-midi-callback! controller 'cc cc-number func)))
-
-
-(define enqueue-midi-bytes!
- (lambda (controller . bytes)
- (let* ((send-queue (get-send-queue controller))
- (old-queue (atomic-box-ref send-queue))
- (new-queue (append old-queue bytes)))
- (unless (eq? (atomic-box-compare-and-swap! send-queue
- old-queue
- new-queue)
- old-queue)
- (apply enqueue-midi-bytes! (cons controller bytes))))))
-
-
-(define* (send-note-on controller note)
- (when (and controller note)
- (enqueue-midi-bytes! controller
- (+ #b10010000 (get-channel controller))
- note
- 127)))
-
-
-(define* (send-note-off controller note)
- (when (and controller note)
- (enqueue-midi-bytes! controller
- (+ #b10000000 (get-channel controller))
- note
- 0)))
-
-
-(define (all-notes-off! controller)
- (for-each (lambda (l)
- (enqueue-midi-bytes! controller
- (+ #b10000000 (get-channel controller))
- l
- 0))
- (iota 128)))
-
-
-(define (check-cc-callbacks controller cc-number old-val new-val)
- (for-each (lambda (a) ((get-callback-func a) old-val new-val))
- (find-cc-callbacks controller cc-number)))
-
-
-(define (handle-cc-change! controller cc-number value)
- (let* ((ccvals (get-cc-values controller))
- (old-value (vector-ref ccvals cc-number)))
- (vector-set! ccvals cc-number value)
- (check-cc-callbacks controller cc-number old-value value)))
-
-
-(define* (get-cc-value controller cc-number)
- (if controller
- (vector-ref (get-cc-values controller) cc-number)
- #f))
-
-
-(define (check-note-callbacks controller note-number)
- (for-each (lambda (a) ((get-callback-func a)))
- (find-note-callbacks controller note-number)))
-
-
-(define (ccval->percent n)
- (/ (* n 100) 127))
-
-
-(define (percent->ccval n)
- (inexact->exact (round (/ (* n 127) 100))))
-
-
-(define (make-midi-controller-real device-name channel)
- (let ((controller (make <midi-control-surface>
- #:channel channel)))
- (let ((midi-port (open-file device-name "r+0b")))
-
- ;; Read thread
- (begin-thread
- (with-exception-handler
- (lambda (exn)
- (backtrace)
- (raise-exception exn))
- (lambda ()
- (let again ()
-
- (let* ((status-byte (get-u8 midi-port))
- (channel (bit-extract status-byte 0 4))
- (command (bit-extract status-byte 4 8)))
-
- (case command
-
- ;; Note on
- ((9) (let* ((note (get-u8 midi-port))
- (vel (get-u8 midi-port)))
- (check-note-callbacks controller note)))
-
- ;; Control value
- ((11) (let* ((cc-number (get-u8 midi-port))
- (value (get-u8 midi-port)))
- (handle-cc-change! controller
- cc-number
- value))))
-
- (yield)
- (again))))))
-
- ;; Write thread
- (begin-thread
- (let again ()
- (let ((bytes-to-send
- (atomic-box-swap!
- (get-send-queue controller)
- '())))
- (for-each (lambda (a)
- (put-u8 midi-port a)
- (usleep 1))
- bytes-to-send)
- (usleep 1000)
- (again))))
-
- (all-notes-off! controller)
- controller)))
-
-
-(define* (make-midi-controller device-name channel)
- (with-exception-handler
-
- (lambda (exn)
- (format #t "Couldn't start MIDI ~a\n"
- (exception-irritants exn))
- #f)
-
- (lambda ()
- (make-midi-controller-real device-name channel))
-
- #:unwind? #t))
-
-
-(define (set-sensitivity controller prev new)
- (set-controller-sensitivity!
- controller
- (min 5 (max 1 (+ (if (= new 127) -1 1)
- (get-controller-sensitivity controller))))))
-
-
-(define (make-sensitivity-knob controller cc-num)
- (register-midi-callback!
- controller 'cc cc-num
- (lambda (prev new)
- (set-sensitivity controller prev new))))
diff --git a/guile/starlet/midi-control/button-utils.scm b/guile/starlet/midi-control/button-utils.scm
deleted file mode 100644
index 8462e3e..0000000
--- a/guile/starlet/midi-control/button-utils.scm
+++ /dev/null
@@ -1,93 +0,0 @@
-;;
-;; starlet/midi-control/button-utils.scm
-;;
-;; Copyright © 2020-2021 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 midi-control button-utils)
- #:use-module (starlet midi-control base)
- #:use-module (starlet state)
- #:use-module (starlet playback)
- #:export (make-go-button
- make-stop-button
- make-back-button
- select-on-button))
-
-
-(define* (make-go-button controller pb button
- #:key
- (ready-note #f)
- (pause-note #f))
- (register-midi-note-callback!
- controller
- #:note-number button
- #:func (lambda () (go! pb)))
-
- (when (or ready-note pause-note)
- (add-hook!
- (state-change-hook pb)
- (lambda (new-state)
- (cond
- ((eq? new-state 'pause)
- (send-note-on controller pause-note))
- ((eq? new-state 'ready)
- (send-note-on controller ready-note))
- ((eq? new-state 'running)
- (send-note-on controller ready-note))
- (else
- (send-note-off controller ready-note)))))))
-
-
-(define* (make-stop-button controller pb button
- #:key
- (ready-note #f))
- (register-midi-note-callback!
- controller
- #:note-number button
- #:func (lambda () (stop! pb)))
-
- (when ready-note
- (add-hook!
- (state-change-hook pb)
- (lambda (new-state)
- (if (eq? new-state 'running)
- (send-note-on controller ready-note)
- (send-note-off controller ready-note))))))
-
-
-(define* (make-back-button controller pb button
- #:key
- (ready-note #f))
- (register-midi-note-callback!
- controller
- #:note-number button
- #:func (lambda () (back! pb)))
-
- (when ready-note
- (send-note-on controller ready-note)))
-
-
-(define* (select-on-button controller button fixture
- #:key
- (ready-note #f))
- (register-midi-note-callback!
- controller
- #:note-number button
- #:func (lambda () (sel fixture)))
-
- (when ready-note
- (send-note-on controller ready-note)))
diff --git a/guile/starlet/midi-control/faders.scm b/guile/starlet/midi-control/faders.scm
deleted file mode 100644
index e5f9cd4..0000000
--- a/guile/starlet/midi-control/faders.scm
+++ /dev/null
@@ -1,361 +0,0 @@
-;;
-;; starlet/midi-control/faders.scm
-;;
-;; Copyright © 2020-2021 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 midi-control faders)
- #:use-module (starlet midi-control base)
- #:use-module (starlet state)
- #:use-module (starlet fixture)
- #:use-module (starlet colours)
- #:use-module (starlet scanout)
- #:use-module (starlet utils)
- #:use-module (srfi srfi-1)
- #:use-module (srfi srfi-26)
- #:use-module (oop goops)
- #:export (set-midi-control-map!
- state-on-fader))
-
-
-(define-class <parameter-controller> (<object>)
- (callbacks
- #:init-keyword #:callbacks
- #:getter get-callbacks
- #:setter set-callbacks!)
-
- (control-map
- #:init-keyword #:control-map
- #:getter get-control-map))
-
-
-(define (name-for-fader-state controller cc-number)
- (call-with-output-string
- (lambda (port)
- (format port "faderstate-~a-cc~a"
- controller
- cc-number))))
-
-
-(define* (state-on-fader controller
- cc-number
- state)
- (register-state!
- (lighting-state
- (state-for-each
- (lambda (fix attr val)
- (at fix attr
- (lambda ()
-
- (let ((cc-val (get-cc-value controller cc-number)))
-
- ;; Fader position known?
- (if cc-val
-
- (if (intensity? attr)
-
- ;; Intensity parameters get scaled according to the fader
- (* 0.01 val (ccval->percent cc-val))
-
- ;; Non-intensity parameters just get set in our new state,
- ;; but only if the fader is up!
- (if (> cc-val 0)
- val
- 'no-value))
-
- ;; Fader position unknown
- 'no-value)))))
-
- state))
- #:unique-name (name-for-fader-state controller cc-number)))
-
-
-(define (current-values fixture-list attr-name)
- (map (lambda (fix)
- (current-value fix attr-name))
- fixture-list))
-
-
-(define (fixtures-with-attr fixture-list attr-name)
- (let ((attrs (map (cut find-attr <> attr-name) fixture-list)))
- (fold (lambda (fix attr old)
- (if attr
- (cons (cons fix (car old))
- (cons attr (cdr old)))
- old))
- (cons '() '())
- fixture-list attrs)))
-
-
-(define (clamp-to-attr-range attr-obj val)
- (let ((r (get-attr-range-maybe-colour attr-obj)))
- (max (car r)
- (min (cadr r)
- val))))
-
-
-(define (attr-scale controller attr)
- (let ((sens-level (get-controller-sensitivity controller)))
- (cond
- ((= sens-level 1) 0.02)
- ((= sens-level 2) 0.1)
- ((= sens-level 3) 0.5)
- ((= sens-level 4) 1.5)
- ((= sens-level 5) 3.0))))
-
-
-(define* (at-midi-jogwheel controller
- fixture-list
- attr
- cc-number
- #:key (led #f))
-
- (define (ccval->offset controller a)
- (if (eq? a 127)
- (- (attr-scale controller attr))
- (attr-scale controller attr)))
-
- (let ((fixtures (car (fixtures-with-attr fixture-list attr))))
- (unless (null? fixtures)
-
- (when led
- (send-note-on controller led))
-
- (let ((old-vals (current-values fixtures attr))
- (offset 0))
- (register-midi-cc-callback!
- controller
- #:cc-number cc-number
- #:func (lambda (prev-cc-val new-cc-value)
- (set! offset (+ offset (ccval->offset controller
- new-cc-value)))
- (for-each (lambda (fix old-val)
- (let ((attr-obj (find-attr fix attr)))
- (when (and attr-obj
- (continuous-attribute? attr-obj))
- (set-in-state! programmer-state
- fix
- attr
- (clamp-to-attr-range
- attr-obj
- (+ old-val offset))
- controller))))
- fixtures old-vals)))))))
-
-
-(define (get-attr-range-maybe-colour attr-obj)
- (if (colour-attribute? attr-obj)
- '(0 100)
- (get-attr-range attr-obj)))
-
-
-(define (fader-congruent vals attrs)
- (mean (map (lambda (val attr)
- (scale-to-range val
- (get-attr-range-maybe-colour attr)
- '(0 127)))
- vals attrs)))
-
-
-(define (fader-up-gradients initial-vals
- attrs
- congruent-val)
- (map (lambda (initial-val attr)
- (let ((attr-max (cadr (get-attr-range-maybe-colour attr))))
- (if (< congruent-val 127)
- (/ (- attr-max initial-val)
- (- 127 congruent-val))
- 0)))
- initial-vals
- attrs))
-
-
-(define (fader-down-gradients initial-vals
- attrs
- congruent-val)
- (map (lambda (initial-val attr)
- (let ((attr-min (car (get-attr-range-maybe-colour attr))))
- (if (> congruent-val 0)
- (/ (- initial-val attr-min)
- congruent-val)
- 0)))
-
- initial-vals
- attrs))
-
-
-(define (apply-fader cc-offset
- attr-name
- gradients
- initial-vals
- fixtures
- controller)
- (for-each (lambda (fix initial-val gradient)
- (set-in-state! programmer-state
- fix
- attr-name
- (+ initial-val
- (* gradient cc-offset))
- controller))
- fixtures
- initial-vals
- gradients))
-
-
-(define* (at-midi-fader controller
- fixture-list
- attr-name
- cc-number
- #:key
- (led-incongruent #f)
- (led #f))
-
- (let ((fixtures-attrs (fixtures-with-attr fixture-list attr-name)))
- (unless (null? (car fixtures-attrs))
- (let* ((fixtures (car fixtures-attrs))
- (attrs (cdr fixtures-attrs))
- (initial-vals (current-values fixtures attr-name))
- (congruent-val (fader-congruent initial-vals attrs))
- (up-gradients (fader-up-gradients initial-vals attrs congruent-val))
- (dn-gradients (fader-down-gradients initial-vals attrs congruent-val))
- (cc-val (get-cc-value controller cc-number))
- (congruent (and cc-val (= cc-val congruent-val))))
-
- (if congruent
- (send-note-on controller led)
- (send-note-on controller led-incongruent))
-
- (register-midi-cc-callback!
- controller
- #:cc-number cc-number
- #:func (lambda (prev-cc-val new-cc-value)
-
- (if congruent
-
- (cond
- ((> new-cc-value congruent-val)
- (apply-fader (- new-cc-value congruent-val)
- attr-name
- up-gradients
- initial-vals
- fixtures
- controller))
- ((<= new-cc-value congruent-val)
- (apply-fader (- new-cc-value congruent-val)
- attr-name
- dn-gradients
- initial-vals
- fixtures
- controller)))
-
- (when (or (and (not prev-cc-val)
- (= new-cc-value congruent-val))
- (and prev-cc-val new-cc-value
- (in-range congruent-val
- prev-cc-val
- new-cc-value)))
- (set! congruent #t)
- (send-note-on controller led)))))))))
-
-
-(define (midi-control-attr controller control-spec fixture-list)
- (cond
-
- ((eq? 'jogwheel (cadr control-spec))
- (at-midi-jogwheel controller
- fixture-list
- (car control-spec)
- (caddr control-spec)
- #:led (cadddr control-spec)))
-
- ((eq? 'fader (cadr control-spec))
- (at-midi-fader controller
- fixture-list
- (car control-spec)
- (caddr control-spec)
- #:led (car (cadddr control-spec))
- #:led-incongruent (cadr (cadddr control-spec))))))
-
-
-(define (led-off controller leds)
- (cond
- ((list? leds)
- (for-each (lambda (note)
- (send-note-off controller note))
- leds))
- ((number? leds)
- (send-note-off controller leds))))
-
-
-(define (scrub-parameter-controller! controller parameter-controller)
-
- ;; Remove all the old callbacks
- (for-each (lambda (callback)
- (remove-midi-callback! controller callback))
- (get-callbacks parameter-controller))
-
- ;; Switch off all the old LEDs
- (for-each (lambda (control-spec)
- (led-off controller (cadddr control-spec)))
- (get-control-map parameter-controller)))
-
-
-(define (update-midi-controls controller fixture-list)
-
- (scrub-parameter-controller! controller
- (get-parameter-controller controller))
-
- (set-callbacks!
- (get-parameter-controller controller)
- (map (lambda (control-spec)
- (midi-control-attr controller control-spec fixture-list))
- (get-control-map (get-parameter-controller controller)))))
-
-
-(define (set-midi-control-map! controller new-control-map)
- (when controller
- (let ((old-parameter-controller (get-parameter-controller controller)))
-
- ;; Remove the old parameter controller
- (when old-parameter-controller
- (scrub-parameter-controller! controller old-parameter-controller))
-
- (set-parameter-controller!
- controller
- (make <parameter-controller>
- #:callbacks '()
- #:control-map new-control-map))
-
- ;; If this is the first time, add the callbacks
- (unless old-parameter-controller
-
- ;; Selection changed
- (add-hook!
- selection-hook
- (lambda (fixture-list)
- (update-midi-controls controller fixture-list)))
-
- ;; Value changed
- (add-update-hook! programmer-state
- (lambda (fix attr value source)
- (unless (eq? source controller)
- (update-midi-controls controller (get-selection))))))
-
- ;; If there is a selection, run the callback now
- (let ((current-selection (get-selection)))
- (when current-selection
- (update-midi-controls controller current-selection))))))
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)))))))
diff --git a/guile/starlet/playback.scm b/guile/starlet/playback.scm
index 10a5848..423abd2 100644
--- a/guile/starlet/playback.scm
+++ b/guile/starlet/playback.scm
@@ -30,13 +30,14 @@
#:use-module (srfi srfi-43)
#:use-module (starlet fixture)
#:use-module (starlet state)
- #:use-module (starlet scanout)
+ #:use-module (starlet engine)
#:use-module (starlet utils)
#:use-module (starlet clock)
+ #:use-module (starlet cue-list)
+ #:use-module (starlet cue-part)
#:use-module (starlet colours)
+ #:use-module (starlet attributes)
#:export (make-playback
- cue
- cue-part
cut-to-cue-number!
get-playback-cue-number
run-cue-number!
@@ -44,12 +45,10 @@
cut!
stop!
back!
- cue-list
reload-cue-list!
reassert-current-cue!
- print-playback
state-change-hook
- current-cue-clock))
+ playback-state))
;; A "playback" is a state which knows how to run cues
@@ -65,6 +64,10 @@
#:getter get-playback-cue-list-file
#:setter set-playback-cue-list-file!)
+ (recovery-file
+ #:init-keyword #:recovery-file
+ #:getter get-playback-recovery-file)
+
(next-cue-index
#:init-value 0
#:getter get-next-cue-index
@@ -89,54 +92,6 @@
#:getter state-change-hook))
-(define-record-type <cue-part>
- (make-cue-part attr-list
- fade-times)
- cue-part?
- (attr-list get-cue-part-attr-list)
- (fade-times get-cue-part-fade-times))
-
-
-(define-record-type <fade-times>
- (make-fade-times up-time
- down-time
- attr-time
- up-delay
- down-delay
- attr-delay)
- fade-times?
- (up-time get-fade-up-time)
- (down-time get-fade-down-time)
- (attr-time get-fade-attr-time)
- (up-delay get-fade-up-delay)
- (down-delay get-fade-down-delay)
- (attr-delay get-fade-attr-delay))
-
-
-(define-record-type <cue>
- (make-cue number
- state
- tracked-state
- preset-state
- fade-times
- preset-time
- track-intensities
- cue-parts
- cue-clock)
- cue?
- (number get-cue-number)
- (state get-cue-state)
- (tracked-state get-tracked-state
- set-tracked-state!)
- (preset-state get-preset-state
- set-preset-state!)
- (fade-times get-cue-fade-times)
- (preset-time get-cue-preset-time)
- (track-intensities track-intensities)
- (cue-parts get-cue-parts)
- (cue-clock get-cue-clock))
-
-
(define (get-playback-cue-number pb)
(let ((cue-idx (get-next-cue-index pb)))
(if cue-idx
@@ -144,16 +99,6 @@
(max 0 (- cue-idx 1)))
#f)))
-(define (qnum a)
- (/ (inexact->exact (* a 1000)) 1000))
-
-
-(define (read-cue-list-file filename)
- (call-with-input-file
- filename
- (lambda (port)
- (eval (read port) (interaction-environment)))))
-
(define (reload-cue-list! pb)
(let ((filename (get-playback-cue-list-file pb)))
@@ -177,29 +122,52 @@
'playback-without-cue-list-file)))
+(define (read-recovery-file! pb)
+ (with-exception-handler
+ (lambda (exn)
+ (display "Failed to read recovery file - going to cue zero\n")
+ (cut-to-cue-index! pb 0))
+ (lambda ()
+ (call-with-input-file
+ (get-playback-recovery-file pb)
+ (lambda (port)
+ (let ((val (read port)))
+ (if (number? val)
+ (cut-to-cue-number! pb val)
+ (cut-to-cue-index! pb 0))))))
+ #:unwind? #t))
+
+
+(define (write-recovery-file! pb the-cue-number)
+ (with-exception-handler
+ (lambda (exn)
+ (display "Failed to write recovery file (just FYI)\n")
+ (display exn))
+ (lambda ()
+ (call-with-output-file
+ (get-playback-recovery-file pb)
+ (lambda (port)
+ (write (qnum the-cue-number) port))))
+ #:unwind? #t))
+
+
(define* (make-playback #:key
(cue-list-file #f)
- (cue-list #f))
+ (cue-list #f)
+ (recovery-file #f))
(let ((new-playback (make <starlet-playback>
#:cue-list (if cue-list-file
(read-cue-list-file cue-list-file)
cue-list)
- #:cue-list-file cue-list-file)))
+ #:cue-list-file cue-list-file
+ #:recovery-file recovery-file)))
(register-state! new-playback)
+ (if recovery-file
+ (read-recovery-file! new-playback)
+ (cut-to-cue-index! new-playback 0))
new-playback))
-(define (cue-index-to-number cue-list cue-index)
- (get-cue-number (vector-ref cue-list cue-index)))
-
-
-(define (cue-number-to-index cue-list cue-number)
- (vector-index (lambda (a)
- (eqv? (get-cue-number a)
- cue-number))
- cue-list))
-
-
(define (set-playback-state! pb state)
(atomic-box-set! (state-box pb) state)
(run-hook (state-change-hook pb) state))
@@ -212,19 +180,24 @@
(set-running-cue! pb #f)
(set-playback-state! pb 'ready)
- ;; Set the actual state
- (state-for-each
- (lambda (fix attr val)
- (set-in-state! pb fix attr (lambda () val)))
- (get-tracked-state (vector-ref (get-playback-cue-list pb)
- cue-index)))
+ (let ((the-cue (vector-ref (get-playback-cue-list pb)
+ cue-index)))
+ ;; Set the actual state
+ (for-each
+ (lambda (part)
+ (state-for-each
+ (lambda (fix attr val)
+ (set-in-state! pb fix attr (lambda () val)))
+ (get-cue-part-state part)))
+ (get-cue-parts the-cue))
- ;; Set the preset state on top
- (state-for-each
- (lambda (fix attr val)
- (set-in-state! pb fix attr (lambda () val)))
- (get-preset-state (vector-ref (get-playback-cue-list pb)
- cue-index))))
+ ;; Set the preset state on top
+ (state-for-each
+ (lambda (fix attr val)
+ (set-in-state! pb fix attr (lambda () val)))
+ (get-preset-state the-cue))
+
+ (write-recovery-file! pb (get-cue-number the-cue))))
(define (cut-to-cue-number! pb cue-number)
@@ -285,7 +258,12 @@
(define (cut! pb)
- (cut-to-cue-index! pb (get-next-cue-index pb)))
+ (let ((nci (get-next-cue-index pb)))
+ (if nci
+ (if (< nci (vector-length (get-playback-cue-list pb)))
+ (cut-to-cue-index! pb (get-next-cue-index pb))
+ 'no-more-cues-in-list)
+ 'next-cue-unspecified)))
(define (stop! pb)
@@ -306,456 +284,113 @@
'next-cue-unspecified))
-(define (snap-fade start-val
- target-val
- clock)
- (if (> (elapsed-fraction clock) 0)
- target-val
- start-val))
-
-
-(define (colour-fade start-val
- end-val
- clock)
-
- (unless (and (colour? start-val)
- (colour? end-val))
- (raise-exception (make-exception
- (make-exception-with-message
- "Non-colour arguments given to colour-fade")
- (make-exception-with-irritants
- (list start-val end-val)))))
-
- (interpolate-colour start-val
- end-val
- (elapsed-fraction clock)
- #:interpolation-type 'linear-cmy))
-
-
-(define (simple-fade start-val
- end-val
- clock)
-
- (unless (and (number? start-val)
- (number? end-val))
- (raise-exception (make-exception
- (make-exception-with-message
- "Non-number arguments given to simple-fade")
- (make-exception-with-irritants
- (list start-val end-val)))))
-
- (+ start-val
- (* (- end-val start-val)
- (elapsed-fraction clock))))
-
-
-(define (replace-noval val replacement)
- (if (eq? 'no-value val) replacement val))
-
-
-(define (make-intensity-fade prev-val
- target-val-in
- up-clock
- down-clock)
- (let ((target-val (replace-noval target-val-in 0.0)))
-
- (cond
-
- ;; Number to number, fading up
- ((and (number? target-val)
- (number? prev-val)
- (> target-val prev-val))
- (lambda ()
- (simple-fade prev-val
- target-val
- up-clock)))
-
- ;; Number to number, fading down
- ((and (number? target-val)
- (number? prev-val)
- (< target-val prev-val))
- (lambda ()
- (simple-fade prev-val
- target-val
- down-clock)))
-
- ;; Number to number, staying the same
- ;; NB We still need a static value so that fade-start-val can "unwrap" it
- ((and (number? target-val)
- (number? prev-val))
- (lambda () prev-val))
-
- ;; Everything else, e.g. number to effect
- (else
- (lambda ()
- (max
- (simple-fade (value->number prev-val)
- 0
- down-clock)
- (simple-fade 0
- (value->number target-val)
- up-clock)))))))
-
-
-(define (make-list-attr-fade start-val
- target-val
- clock)
- (lambda ()
- (snap-fade start-val
- target-val
- clock)))
-
-
-(define (make-general-fade fade-func
- start-val
- target-val
- clock)
-
- (if (and (not (procedure? target-val))
- (not (eq? target-val 'no-value))
- (not (eq? start-val 'no-value)))
-
- ;; It makes sense to do a fade
- (let ((real-start-val (value->number start-val)))
- (lambda ()
- (fade-func real-start-val
- target-val
- clock)))
-
- ;; A fade doesn't make sense, so make do with a snap transition
- (lambda ()
- (snap-fade start-val
- target-val
- clock))))
-
-
-(define (match-fix-attr attr-el fix attr)
- (cond
-
- ((fixture? attr-el)
- (eq? attr-el fix))
-
- ((and (pair? attr-el)
- (fixture? (car attr-el))
- (symbol? (cdr attr-el)))
- (and (eq? (car attr-el) fix)
- (eq? (cdr attr-el) attr)))
-
- ((list? attr-el)
- (and (memq fix attr-el)
- (memq attr attr-el)))
-
- (else #f)))
-
-
-(define (in-cue-part? cue-part fix attr)
- (find (lambda (p) (match-fix-attr p fix attr))
- (get-cue-part-attr-list cue-part)))
-
-
-(define (cue-part-fade-times the-cue fix attr)
-
- (let ((the-cue-part
- (find (lambda (p) (in-cue-part? p fix attr))
- (get-cue-parts the-cue))))
-
- (if (cue-part? the-cue-part)
- (get-cue-part-fade-times the-cue-part)
- (get-cue-fade-times the-cue))))
-
-
-(define (fade-start-val pb fix attr)
- (let ((val-in-pb (state-find fix attr pb)))
- (if (eq? val-in-pb 'no-value)
-
- ;; Not currently in playback - fade from home value
- (get-attr-home-val fix attr)
-
- ;; Currently in playback - fade from current value
- ;; by running the outer crossfade function
- (val-in-pb))))
-
-
-(define (dark? a)
- (or (eq? a 'no-value)
- (and (number? a)
- (< a 1))))
-
-
-(define (longest-fade-time fade-times)
- (max
- (+ (get-fade-down-time fade-times)
- (get-fade-down-delay fade-times))
- (+ (get-fade-up-time fade-times)
- (get-fade-up-delay fade-times))
- (+ (get-fade-attr-time fade-times)
- (get-fade-attr-delay fade-times))))
-
-
-;; Work out how long it will take before we can forget about this cue
-(define (cue-total-time the-cue)
- (let ((fade-times (cons (get-cue-fade-times the-cue)
- (map get-cue-part-fade-times
- (get-cue-parts the-cue)))))
- (fold max
- 0
- (map longest-fade-time fade-times))))
-
-
-(define (fix-attr-eq fa1 fa2)
- (and (eq? (car fa1) (car fa2))
- (eq? (cdr fa1) (cdr fa2))))
-
-
-(define (fix-attrs-in-state state)
- (state-map (lambda (fix attr val) (cons fix attr))
- state))
-
-
-(define (add-fix-attrs-to-list state old-list)
- (lset-union fix-attr-eq
- old-list
- (fix-attrs-in-state state)))
-
-
-(define (fix-attrs-involved . states)
- (fold add-fix-attrs-to-list '() states))
-
-
-(define (make-fade-for-attribute-type type)
- (cond
- ((eq? type 'continuous) (cut make-general-fade simple-fade <...>))
- ((eq? type 'list) make-list-attr-fade)
- ((eq? type 'colour) (cut make-general-fade colour-fade <...>))
- (else
- (raise-exception (make-exception
- (make-exception-with-message
- "Unrecognised attribute type")
- (make-exception-with-irritants type))))))
+(define (blank-everything state)
+ (state-map
+ (lambda (fix attr val)
+ (if (intensity? attr)
+ 0.0
+ 'no-value))
+ state))
(define (run-cue-index! pb cue-index)
(let* ((the-cue (vector-ref (get-playback-cue-list pb) cue-index))
- (this-cue-state (get-tracked-state the-cue))
(overlay-state (make-empty-state))
- (cue-clock (get-cue-clock the-cue)))
-
+ (cue-clock (get-cue-clock the-cue))
+ (fade-time 0))
+
+ ;; Start by fading the previous contents of the playback down, using the
+ ;; "main" transition effect
+ (receive
+ (overlay-part transition-time)
+ ((get-cue-part-transition
+ (car (get-cue-parts the-cue)))
+ (blank-everything pb)
+ pb
+ cue-clock)
+ (atomically-overlay-state!
+ overlay-state
+ overlay-part)
+ (set! fade-time transition-time))
+
+ ;; Stack all the cue parts on top
(for-each
- (lambda (fix-attr)
-
- (let* ((fix (car fix-attr))
- (attr (cdr fix-attr))
- (fade-times (cue-part-fade-times the-cue fix attr))
-
- ;; The values for fading
- (start-val (fade-start-val pb fix attr))
- (target-val (state-find fix attr this-cue-state))
- ;; The clocks for things in this cue part
- (up-clock (make-delayed-clock cue-clock
- (get-fade-up-delay fade-times)
- (get-fade-up-time fade-times)))
-
- (down-clock (make-delayed-clock cue-clock
- (get-fade-down-delay fade-times)
- (get-fade-down-time fade-times)))
-
- (attribute-clock (make-delayed-clock cue-clock
- (get-fade-attr-delay fade-times)
- (get-fade-attr-time fade-times))))
-
- (if (intensity? attr)
-
- ;; Intensity attribute
- (set-in-state! overlay-state fix attr
- (make-intensity-fade start-val
- target-val
- up-clock
- down-clock))
-
- ;; Non-intensity attribute
- (let ((attribute-obj (find-attr fix attr)))
-
- (unless attribute-obj
- (raise-exception (make-exception
- (make-exception-with-message
- "Attribute not found")
- (make-exception-with-irritants
- (list fix attr)))))
-
- (let* ((atype (get-attr-type attribute-obj))
- (make-fade-func (make-fade-for-attribute-type atype)))
-
- (set-in-state! overlay-state fix attr
- (make-fade-func start-val
- target-val
- attribute-clock)))))))
-
- (fix-attrs-involved pb this-cue-state))
-
+ (lambda (part)
+ (receive
+ (overlay-part transition-time)
+ ((get-cue-part-transition part)
+ (get-cue-part-state part)
+ pb
+ cue-clock)
+ (atomically-overlay-state!
+ overlay-state
+ overlay-part)
+ (set! fade-time (max fade-time transition-time))))
+ (get-cue-parts the-cue))
+
+ (set-clock-expiration-time! cue-clock fade-time)
(atomically-overlay-state! pb overlay-state)
(set-pb-cue-clock! pb cue-clock)
(set-running-cue! pb the-cue)
(reset-clock! cue-clock)
(start-clock! cue-clock)
- (set-playback-state! pb 'running)))
+ (set-playback-state! pb 'running)
+ (write-recovery-file! pb (get-cue-number the-cue))))
-(define (print-playback pb)
- (format #t "Playback ~a:\n" pb)
- ;;(format #t " Cue list ~a\n" (get-playback-cue-list pb))
- (if (get-next-cue-index pb)
- (if (< (get-next-cue-index pb)
- (vector-length (get-playback-cue-list pb)))
- (let ((the-cue (vector-ref (get-playback-cue-list pb)
- (get-next-cue-index pb))))
- (format #t " Next cue index ~a (~a)\n"
- (get-next-cue-index pb)
- the-cue))
- (format #t " End of cue list.\n"))
- (format #t " Next cue index is unspecified.\n"))
- *unspecified*)
-
-
-;;; ******************** Cue lists ********************
-
-(define-syntax cue-part
- (syntax-rules ()
- ((_ (fixtures ...) params ...)
- (make-cue-part-obj (list fixtures ...)
- params ...))))
-
-
-(define* (make-cue-part-obj attr-list
- #:key
- (up-time 5)
- (down-time 5)
- (attr-time 0)
- (up-delay 0)
- (down-delay 0)
- (attr-delay 0))
- (make-cue-part attr-list
- (make-fade-times
- up-time
- down-time
- attr-time
- up-delay
- down-delay
- attr-delay)))
-
-
-(define cue-proc
- (lambda (number state . rest)
- (receive (cue-parts rest-minus-cue-parts)
- (partition cue-part? rest)
- (let-keywords rest-minus-cue-parts #f
- ((up-time 5)
- (down-time 5)
- (attr-time 0)
- (up-delay 0)
- (down-delay 0)
- (attr-delay 0)
- (preset-time 1)
- (track-intensities #f))
-
- (let ((the-cue (make-cue (qnum number)
- state
- #f ;; tracked state
- #f ;; preset state
- (make-fade-times
- up-time
- down-time
- attr-time
- up-delay
- down-delay
- attr-delay)
- preset-time
- track-intensities
- cue-parts
- (current-cue-clock))))
-
- (set-clock-expiration-time! (current-cue-clock)
- (cue-total-time the-cue))
- the-cue)))))
-
-
-(define current-cue-clock (make-parameter #f))
-
-(define-syntax cue
- (syntax-rules ()
- ((_ body ...)
- (parameterize ((current-cue-clock (make-clock #:stopped #t)))
- (cue-proc body ...)))))
-
-
-(define (track-all-cues! the-cue-list)
- (vector-fold
- (lambda (idx prev-state the-cue)
- (let ((the-tracked-state (lighting-state
- (apply-state prev-state)
- (unless (track-intensities the-cue)
- (blackout!))
- (apply-state (get-cue-state the-cue)))))
- (set-tracked-state! the-cue the-tracked-state)
- the-tracked-state))
- (make-empty-state)
- the-cue-list))
-
-
-(define (fixture-dark-in-state? fix state)
- (dark? (state-find fix 'intensity state)))
-
-
-(define (preset-all-cues! the-cue-list)
- (vector-fold-right
- (lambda (idx next-state the-cue)
- (let ((preset-state (make-empty-state)))
-
- (state-for-each
- (lambda (fix attr val)
- (unless (intensity? attr)
- (when (fixture-dark-in-state? fix (get-tracked-state the-cue))
- (set-in-state! preset-state fix attr val))))
- next-state)
-
- (set-preset-state! the-cue preset-state))
+(define-method (num-cues (pb <starlet-playback>))
+ (num-cues (get-playback-cue-list pb)))
- ;; Pass the raw state from this cue to the previous one
- (get-cue-state the-cue))
- (make-empty-state)
- the-cue-list))
+(define (start-fixture-preset! pb)
+ (let ((st (get-preset-state (get-running-cue pb))))
+ (state-for-each
+ (lambda (fix attr val)
+ (set-in-state! pb fix attr (lambda () val)))
+ st)))
(define-method (update-state! (pb <starlet-playback>))
- (when (and (get-pb-cue-clock pb)
- (clock-expired? (get-pb-cue-clock pb))
- (eq? 'running (atomic-box-ref (state-box pb))))
- (when (eq? 'running (atomic-box-compare-and-swap! (state-box pb)
- 'running
- 'ready))
- (run-hook (state-change-hook pb) 'ready)
- (let ((st (get-preset-state (get-running-cue pb))))
- (state-for-each
- (lambda (fix attr val)
- (set-in-state! pb fix attr (lambda () val)))
- st))
- (set-running-cue! pb #f))))
-
-
-(define-syntax cue-list
- (syntax-rules ()
- ((_ body ...)
- (let ((the-cue-list (vector (cue 0
- (make-empty-state)
- #:up-time 0
- #:down-time 0
- #:attr-time 0
- #:preset-time 0)
- body ...)))
- (track-all-cues! the-cue-list)
- (preset-all-cues! the-cue-list)
- the-cue-list))))
+ (when
+ (and (clock-expired? (get-pb-cue-clock pb))
+ (eq? 'running (atomic-box-compare-and-swap! (state-box pb)
+ 'running
+ 'ready)))
+ (run-hook (state-change-hook pb) 'ready)
+ (start-fixture-preset! pb)
+ (set-running-cue! pb #f)))
+
+
+(define (next-cue-number pb)
+ (let ((next-cue-index (get-next-cue-index pb))
+ (the-cue-list (get-playback-cue-list pb)))
+ (if (< next-cue-index (vector-length the-cue-list))
+ (exact->inexact
+ (cue-index-to-number
+ the-cue-list
+ next-cue-index))
+ 'no-more-cues-in-list)))
+
+
+(define (playback-state pb)
+ (atomic-box-ref (state-box pb)))
+
+
+(define-method (write (pb <starlet-playback>) port)
+ (let ((cur-cue (get-playback-cue-number pb)))
+ (format port
+ "#<<starlet-playback> state: ~a current-cue: ~a next-cue: ~a>"
+ (playback-state pb)
+ (if cur-cue
+ (exact->inexact cur-cue)
+ 'current-cue-unspecified)
+ (if cur-cue
+ (next-cue-number pb)
+ 'next-cue-unspecified))))
(define (reassert-current-cue! pb)
- (cut-to-cue-number! pb (get-playback-cue-number pb)))
+ (let ((cur-cue (get-playback-cue-number pb)))
+ (if cur-cue
+ (cut-to-cue-number! pb cur-cue)
+ 'current-cue-unspecified)))
diff --git a/guile/starlet/scanout.scm b/guile/starlet/scanout.scm
index 4b7a2e1..69f3e9c 100644
--- a/guile/starlet/scanout.scm
+++ b/guile/starlet/scanout.scm
@@ -1,7 +1,7 @@
;;
;; starlet/scanout.scm
;;
-;; Copyright © 2020-2021 Thomas White <taw@bitwiz.org.uk>
+;; Copyright © 2020-2023 Thomas White <taw@bitwiz.org.uk>
;;
;; This file is part of Starlet.
;;
@@ -19,294 +19,140 @@
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
(define-module (starlet scanout)
+ #:use-module (starlet engine)
#:use-module (starlet fixture)
#:use-module (starlet state)
#:use-module (starlet utils)
- #:use-module (starlet colours)
+ #:use-module (starlet attributes)
#:use-module (starlet guile-ola)
- #:use-module (oop goops)
#:use-module (ice-9 threads)
#:use-module (ice-9 atomic)
#:use-module (ice-9 exceptions)
- #:use-module (srfi srfi-1)
- #:export (patch-fixture!
- patch-many!
- scanout-freq
- total-num-attrs
- register-state!
- current-value
- patched-fixture-names))
+ #:export (scanout-freq
+ get-attr
+ set-chan8
+ set-chan16))
-;; The list of patched fixtures
-(define fixtures (make-atomic-box '()))
-
-;; List of states being scanned out
-(define state-list (make-atomic-box '()))
-
-;; Association list of names to states
-(define state-names (make-atomic-box '()))
-
-
-(define (patched-fixture-names)
- (map get-fixture-name (atomic-box-ref fixtures)))
-
-
-(define (total-num-attrs)
- (fold (lambda (fix prev)
- (+ prev (length (get-fixture-attrs fix))))
- 0
- (atomic-box-ref fixtures)))
-
-
-(define (get-state-name st)
- (assq-ref (atomic-box-ref state-names)
- st))
-
-
-(define (set-state-name! st name)
- (atomic-box-set! state-names
- (assq-set! (atomic-box-ref state-names)
- st
- name)))
-
-
-;; Patch a new fixture
-(define* (patch-real name
- class
- start-addr
- #:key (universe 0) (friendly-name "Fixture"))
- (let ((new-fixture (make class
- #:name name
- #:sa start-addr
- #:uni universe
- #:friendly-name friendly-name)))
- (atomic-box-set! fixtures (cons new-fixture
- (atomic-box-ref fixtures)))
- new-fixture))
-
-
-(define-syntax patch-fixture!
- (syntax-rules ()
- ((_ name stuff ...)
- (define name (patch-real (quote name) stuff ...)))))
-
-
-;; Patch several new fixtures
-(define* (patch-many-real name
- class
- start-addrs
- #:key (universe 0) (friendly-name "Fixture"))
- (map (lambda (start-addr n)
- (patch-real `(list-ref ,name ,n)
- class
- start-addr
- #:universe universe
- #:friendly-name friendly-name))
- start-addrs
- (iota (length start-addrs))))
-
-
-(define-syntax patch-many!
- (syntax-rules ()
- ((_ name stuff ...)
- (define name (patch-many-real (quote name) stuff ...)))))
-
-
-(define (state-has-fix-attr fix attr state)
- (let ((val (state-find fix attr state)))
- (if (eq? 'no-value val)
- #f
- (not (eq? 'no-value (value->number val))))))
-
-
-(define (first-val fix attr state-list)
- (let ((first-state (find (lambda (state)
- (state-has-fix-attr fix attr state))
- state-list)))
- (if first-state
- (state-find fix attr first-state)
- 'no-value)))
-
-
-(define-method (current-value (fix <fixture>) (attr-name <symbol>))
- (let ((programmer-val (state-find fix attr-name programmer-state)))
- (if (eq? 'no-value programmer-val)
-
- ;; Look in the states
- (if (intensity? attr-name)
-
- ;; HTP for intensity
- (fold (lambda (state prev)
- (let ((val (state-find fix attr-name state)))
- (if (eq? 'no-value val)
- prev
- (let ((real-val (value->number val)))
- (if (eq? 'no-value real-val)
- prev
- (max real-val prev))))))
- 0.0
- (atomic-box-ref state-list))
-
- ;; Priority order for everything else
- (let ((val (first-val fix attr-name (atomic-box-ref state-list))))
- (if (eq? 'no-value val)
- (get-attr-home-val fix attr-name)
- (value->number val))))
-
- ;; Use programmer value, if we have it
- (value->number programmer-val))))
-
-
-(define-method (current-value (fix <fixture>) (attr-name <colour-component-id>))
- (let ((colour (current-value fix 'colour)))
- (extract-colour-component colour attr-name)))
-
-
-(define (append-or-replace-named-state orig-list name new-state)
- (let ((new-list (map (lambda (st)
- (if (eq? (get-state-name st) name)
- (begin
- new-state)
- st))
- orig-list)))
-
- ;; If there is no state with this name in the list,
- ;; the replacement above will have no effect.
- ;; Check again and add in the normal way if so.
- (if (find (lambda (st) (eq? (get-state-name st)
- name))
- new-list)
- new-list
- (append orig-list (list new-state)))))
+(define scanout-thread #f)
+(define scanout-freq 0)
+(define current-scanout-fixture (make-parameter #f))
+(define current-scanout-universe (make-parameter #f))
+(define current-scanout-addr (make-parameter #f))
+(define current-scanout-state (make-parameter (make-empty-state)))
-(define* (register-state! new-state
- #:key (unique-name #f))
- (if unique-name
- (begin (set-state-name! new-state unique-name)
- (atomic-box-set! state-list
- (append-or-replace-named-state (atomic-box-ref state-list)
- unique-name
- new-state)))
- (atomic-box-set! state-list
- (append (atomic-box-ref state-list)
- (list new-state)))))
+(define (get-attr attr-name)
+ (let ((v (state-find (current-scanout-fixture)
+ attr-name
+ (current-scanout-state))))
+ (if (eq? v 'no-value)
+ (get-attr-home-val (current-scanout-fixture) attr-name)
+ v)))
-(define (msb val)
- (round-dmx (euclidean-quotient val 256)))
-(define (lsb val)
- (round-dmx (euclidean-remainder val 256)))
+(define (set-dmx universe addr value)
+ (ensure-number value (list universe addr value))
+ ;; Create DMX array for universe if it doesn't exist already
+ (set-ola-dmx-buffer! universe
+ (- addr 1) ; OLA indexing starts from zero
+ (round-dmx value)))
-(define (send-to-ola ola-client universe-buffer-pair)
- (let ((uni (car universe-buffer-pair))
- (buf (cdr universe-buffer-pair)))
- (send-streaming-dmx-data! ola-client uni buf)))
+(define (set-chan8 relative-channel-number value)
+ (ensure-number
+ value
+ (list (current-scanout-fixture)
+ relative-channel-number
+ value))
+ (set-dmx
+ (current-scanout-universe)
+ (+ (current-scanout-addr)
+ relative-channel-number
+ -1)
+ value))
-(define (ensure-number value irritating)
- (unless (number? value)
- (raise-exception (make-exception
- (make-exception-with-message "Value is not a number")
- (make-exception-with-irritants irritating)))))
+(define (set-chan16 relative-channel-number value)
+ (ensure-number
+ value
+ (list (current-scanout-fixture)
+ relative-channel-number
+ value))
+ (set-chan8 relative-channel-number (msb value))
+ (set-chan8 (+ relative-channel-number 1) (lsb value)))
-(define scanout-freq 0)
-(define ola-thread #f)
-(define (scanout-loop ola-client start-time count previous-universes)
+(define (scanout-loop ola-client start-time previous-universes count)
(let ((universes '()))
- ;; Helper function for scanout functions to set individual DMX values
- (define (set-dmx universe addr value)
- (ensure-number value (list universe addr value))
+ (parameterize
+ ((current-scanout-state (current-value-state)))
+ (for-each
+ (lambda (fix)
- ;; Create DMX array for universe if it doesn't exist already
- (unless (assq universe universes)
- (set! universes (acons universe
- (make-ola-dmx-buffer)
- universes)))
+ ;; Ensure the DMX array exists for this fixture's universe
+ (unless (assq (get-fixture-universe fix) universes)
+ (set! universes (acons (get-fixture-universe fix)
+ (make-ola-dmx-buffer)
+ universes)))
- (set-ola-dmx-buffer! (assq-ref universes universe)
- (- addr 1) ; OLA indexing starts from zero
- (round-dmx value)))
+ (parameterize
+ ((current-scanout-fixture fix)
+ (current-scanout-universe (assq-ref
+ universes
+ (get-fixture-universe fix)))
+ (current-scanout-addr (get-fixture-addr fix)))
+ (scanout-fixture fix)))
- (for-each update-state! (atomic-box-ref state-list))
+ (patched-fixtures)))
(for-each
- (lambda (fix)
-
- (let ((univ (get-fixture-universe fix))
- (addr (get-fixture-addr fix)))
-
- ;; Helper function to get a value for this
- ;; fixture in the current state
- (define (get-attr attr-name)
- (current-value fix attr-name))
-
- ;; Helper function to set 8-bit DMX value
- (define (set-chan relative-channel-number value)
- (ensure-number value (list fix relative-channel-number value))
- (set-dmx univ (+ addr relative-channel-number -1) value))
-
- ;; Helper function to set 16-bit DMX value
- (define (set-chan-16bit relative-channel-number value)
- (ensure-number value (list fix relative-channel-number value))
- (set-chan relative-channel-number (msb value))
- (set-chan (+ relative-channel-number 1) (lsb value)))
-
- (scanout-fixture fix get-attr set-chan set-chan-16bit)))
-
- (atomic-box-ref fixtures))
-
- ;; Send everything to OLA
- (for-each (lambda (uni-buf-pair)
- (let ((uni (car uni-buf-pair))
- (buf (cdr uni-buf-pair)))
- (let ((prev-buf (assv-ref previous-universes uni)))
-
- ;; Do not send exactly the same data every time,
- ;; but do send an update once every 100 loops, just to
- ;; make sure OLA does not forget about us.
- (unless (and prev-buf
- (ola-dmx-buffers-equal? buf prev-buf)
- (not (= count 0)))
- (send-streaming-dmx-data! ola-client uni buf)))))
- universes)
-
- (usleep 10000)
-
- ;; Update scanout rate every 1000 cycles
+ (lambda (uni-buf-pair)
+ (let ((uni (car uni-buf-pair))
+ (buf (cdr uni-buf-pair)))
+ (let ((prev-buf (assv-ref previous-universes uni)))
+
+ ;; Do not send exactly the same data every time,
+ ;; but do send an update once every 100 loops, just to
+ ;; make sure OLA does not forget about us.
+ (unless (and prev-buf
+ (ola-dmx-buffers-equal? buf prev-buf)
+ (not (= count 0)))
+ (send-streaming-dmx-data! ola-client uni buf)))))
+ universes)
+
+ (usleep 20000)
+
+ ;; Update output rate every 1000 cycles
(if (eq? count 100)
- (begin
- (set! scanout-freq
- (exact->inexact (/ 100
- (- (hirestime) start-time))))
- (scanout-loop ola-client (hirestime) 0 universes))
- (scanout-loop ola-client start-time (+ count 1) universes))))
-
-
-(define (start-ola-output)
- (if ola-thread
- (format #t "OLA output already running\n")
- (let* ((ola-client (make-ola-streaming-client))
- (start-time (hirestime)))
-
- (set! ola-thread
- (begin-thread
- (with-exception-handler
- (lambda (exn)
- (display "Error in OLA output thread:\n")
- (set! ola-thread #f)
- (backtrace)
- (raise-exception exn))
- (lambda ()
- (scanout-loop ola-client start-time 0 '()))
- #:unwind? #f))))))
-
-
-(start-ola-output)
+ (begin
+ (set! scanout-freq
+ (exact->inexact (/ 100
+ (- (hirestime) start-time))))
+ (scanout-loop ola-client (hirestime) universes 0))
+ (scanout-loop ola-client start-time universes (+ count 1)))))
+
+
+(define (start-scanout)
+ (if scanout-thread
+ (format #t "Scanout thread is already running\n")
+ (let ((start-time (hirestime))
+ (ola-client (make-ola-streaming-client)))
+ (set! scanout-thread
+ (begin-thread
+ (with-exception-handler
+ (lambda (exn)
+ (display "Error in scanout thread:\n")
+ (set! scanout-thread #f)
+ (backtrace)
+ (raise-exception exn))
+ (lambda ()
+ (scanout-loop ola-client start-time '() 0))
+ #:unwind? #f))))))
+
+
+(start-scanout)
diff --git a/guile/starlet/selection.scm b/guile/starlet/selection.scm
new file mode 100644
index 0000000..2798fc1
--- /dev/null
+++ b/guile/starlet/selection.scm
@@ -0,0 +1,97 @@
+;;
+;; starlet/selection.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 selection)
+ #:use-module (starlet utils)
+ #:use-module (starlet fixture)
+ #:use-module (srfi srfi-1)
+ #:export (sel
+ add-sel
+ toggle-sel
+ desel
+ selection-hook
+ get-selection
+ get-selection-as-string
+ selected?))
+
+
+(define selection-hook (make-hook 1))
+
+(define selection '())
+
+
+(define (get-selection)
+ selection)
+
+
+(define (dotted-fixture-name s)
+ (with-output-to-string
+ (lambda ()
+ (format #t "~a.~a" (second s) (third s)))))
+
+
+(define (get-selection-as-string)
+ (cat-with-spaces
+ (map
+ (lambda (s)
+ (let ((name (get-fixture-name s)))
+ (if (symbol? name)
+ (symbol->string name)
+ (dotted-fixture-name name))))
+ selection)))
+
+
+(define (sel . fixture-list)
+ (if (nil? fixture-list)
+ (set! selection '())
+ (if (not (car fixture-list))
+ (set! selection '())
+ (set! selection (flatten-sublists fixture-list))))
+ (run-hook selection-hook selection))
+
+
+(define (toggle-sel . fixture-list)
+ (if (selected? fixture-list)
+ (desel fixture-list)
+ (add-sel fixture-list)))
+
+
+(define (add-sel . fixture-list)
+ (set! selection
+ (append selection
+ (filter (lambda (fix)
+ (not (selected? fix)))
+ (flatten-sublists fixture-list))))
+ (run-hook selection-hook selection))
+
+
+(define (selected? . fixture-list)
+ (every (lambda (fix)
+ (memq fix selection))
+ (flatten-sublists fixture-list)))
+
+
+(define (desel . fixture-list)
+ (let ((remove-us (flatten-sublists fixture-list)))
+ (set! selection
+ (filter (lambda (fix)
+ (not (memq fix remove-us)))
+ selection)))
+ (run-hook selection-hook selection))
diff --git a/guile/starlet/snap-transition.scm b/guile/starlet/snap-transition.scm
new file mode 100644
index 0000000..46993cd
--- /dev/null
+++ b/guile/starlet/snap-transition.scm
@@ -0,0 +1,51 @@
+;;
+;; starlet/snap-transition.scm
+;;
+;; Copyright © 2021-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 snap-transition)
+ #:use-module (starlet cue-part)
+ #:use-module (starlet state)
+ #:use-module (starlet attributes)
+ #:export (snap))
+
+
+(define (blank-everything in-state)
+ (let ((out-state (make-empty-state)))
+ (state-for-each
+ (lambda (fix attr val)
+ (if (intensity? attr)
+ (set-in-state! out-state fix attr (lambda () 0.0))
+ (set-in-state! out-state fix attr (lambda () 'no-value))))
+ in-state)
+ out-state))
+
+
+(define (snap to-state)
+ (cue-part
+ to-state
+ (lambda (incoming-state current-state clock)
+ (let ((overlay-state (blank-everything current-state)))
+ (state-for-each
+ (lambda (fix attr val)
+ (set-in-state! overlay-state
+ fix
+ attr
+ (lambda () val)))
+ incoming-state)
+ (values overlay-state 0)))))
diff --git a/guile/starlet/state.scm b/guile/starlet/state.scm
index 6abd3c1..588e887 100644
--- a/guile/starlet/state.scm
+++ b/guile/starlet/state.scm
@@ -1,7 +1,7 @@
;;
;; starlet/state.scm
;;
-;; Copyright © 2020-2021 Thomas White <taw@bitwiz.org.uk>
+;; Copyright © 2020-2022 Thomas White <taw@bitwiz.org.uk>
;;
;; This file is part of Starlet.
;;
@@ -20,18 +20,22 @@
;;
(define-module (starlet state)
#:use-module (starlet fixture)
- #:use-module (starlet colours)
#:use-module (starlet utils)
+ #:use-module (starlet attributes)
+ #:use-module (starlet selection)
#:use-module (oop goops)
#:use-module (ice-9 pretty-print)
#:use-module (ice-9 atomic)
#:use-module (ice-9 receive)
#:use-module (ice-9 exceptions)
#:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
#:export (<starlet-state>
make-empty-state
+ lighting-state?
get-state-name
state-for-each
+ state-map->list
state-map
copy-state
clear-state!
@@ -42,22 +46,26 @@
current-state
at
apply-state
+ combine-states
show-state
lighting-state
programmer-state
+ ps
home-fixture!
+ blackout
blackout!
- sel
- selection-hook
- get-selection
value->number
atomically-overlay-state!
update-state!
- add-update-hook!))
+ add-update-hook!
+ state-empty?
+ remove-fixtures-from-state!
+ remove-fixture-from-state!
+ remove-selection-from-programmer!))
;; A "state" is an atomically-updating container for an immutable
-;; hash table mapping (fixture-object . attribute-symbol) pairs to values
+;; hash table mapping (fixture-object . attribute-name-object) pairs to values
;; which can be numbers, symbols, colours, boolean values and more
;; depending on the type of attribute. Values can also be
;; functions which provide the value.
@@ -66,12 +74,17 @@
#:init-form (make-atomic-box (make-hash-table))
#:getter get-ht-box)
(update-hook
- #:init-form (make-hook 4)
+ #:init-form (make-hook 1)
#:getter get-update-hook))
+(define (lighting-state? a)
+ (is-a? a <starlet-state>))
+
+
;; The state used to build a new scene for recording
(define programmer-state (make <starlet-state>))
+(define ps programmer-state)
(define (add-update-hook! state proc)
@@ -80,10 +93,10 @@
(define (find-colour state fix)
- (let ((col (state-find fix 'colour state)))
+ (let ((col (state-find fix colour state)))
(if (eq? 'no-value col)
- (let ((home-col (get-attr-home-val fix 'colour)))
+ (let ((home-col (get-attr-home-val fix colour)))
(if (eq? 'fixture-does-not-have-attribute home-col)
(raise-exception (make-exception
(make-exception-with-message
@@ -101,66 +114,7 @@
(define-method (set-in-state! (state <starlet-state>)
(fix <fixture>)
- (attr <colour-component-id>)
- new-val
- source)
- (let ((current-colour (find-colour state fix))
- (colour-component (get-colour-component attr)))
-
- (cond
-
- ((eq? colour-component 'cyan)
- (let ((orig-colour (colour-as-cmy current-colour)))
- (set-in-state! state fix 'colour
- (make-colour-cmy new-val
- (magenta orig-colour)
- (yellow orig-colour))
- source)))
-
- ((eq? colour-component 'magenta)
- (let ((orig-colour (colour-as-cmy current-colour)))
- (set-in-state! state fix 'colour
- (make-colour-cmy (cyan orig-colour)
- new-val
- (yellow orig-colour))
- source)))
-
- ((eq? colour-component 'yellow)
- (let ((orig-colour (colour-as-cmy current-colour)))
- (set-in-state! state fix 'colour
- (make-colour-cmy (cyan orig-colour)
- (magenta orig-colour)
- new-val)
- source)))
-
- ((eq? colour-component 'red)
- (let ((orig-colour (colour-as-rgb current-colour)))
- (set-in-state! state fix 'colour
- (make-colour-rgb new-val
- (green orig-colour)
- (blue orig-colour))
- source)))
-
- ((eq? colour-component 'green)
- (let ((orig-colour (colour-as-rgb current-colour)))
- (set-in-state! state fix 'colour
- (make-colour-rgb (red orig-colour)
- new-val
- (blue orig-colour))
- source)))
-
- ((eq? colour-component 'blue)
- (let ((orig-colour (colour-as-rgb current-colour)))
- (set-in-state! state fix 'colour
- (make-colour-rgb (red orig-colour)
- (green orig-colour)
- new-val)
- source))))))
-
-
-(define-method (set-in-state! (state <starlet-state>)
- (fix <fixture>)
- (attr <symbol>)
+ (attr <starlet-attribute>)
value
source)
(let* ((old-ht (atomic-box-ref (get-ht-box state)))
@@ -175,27 +129,16 @@
old-ht)
(set-in-state! state fix attr)) ;; Try again
- (run-hook (get-update-hook state)
- fix
- attr
- value
- source)))
+ (run-hook (get-update-hook state) source)))
(define-method (set-in-state! (state <starlet-state>)
(fix <fixture>)
- (attr <symbol>)
+ (attr <starlet-attribute>)
value)
(set-in-state! state fix attr value #f))
-(define-method (set-in-state! (state <starlet-state>)
- (fix <fixture>)
- (attr <colour-component-id>)
- new-val)
- (set-in-state! state fix attr new-val #f))
-
-
;; Set any intensity attributes in the current state to zero
(define (blackout!)
(let ((state (current-state)))
@@ -228,6 +171,10 @@
(make <starlet-state>))
+(define blackout
+ (make-empty-state))
+
+
(define (state-for-each func state)
(hash-for-each (lambda (key value)
(func (car key)
@@ -236,24 +183,13 @@
(atomic-box-ref (get-ht-box state))))
-(define-method (state-find (fix <fixture>)
- (attr <symbol>)
- (state <starlet-state>))
+(define (state-find fix attr state)
(hash-ref (atomic-box-ref (get-ht-box state))
(cons fix attr)
'no-value))
-(define-method (state-find (fix <fixture>)
- (attr <colour-component-id>)
- (state <starlet-state>))
- (let ((col (state-find fix 'colour state)))
- (if (eq? 'no-value col)
- 'no-value
- (extract-colour-component col attr))))
-
-
-(define (state-map func state)
+(define (state-map->list func state)
(hash-map->list (lambda (key value)
(func (car key)
(cdr key)
@@ -261,6 +197,21 @@
(atomic-box-ref (get-ht-box state))))
+(define (state-map func state)
+ (let ((out-state (make-empty-state)))
+ (hash-for-each
+ (lambda (key value)
+ (set-in-state!
+ out-state
+ (car key)
+ (cdr key)
+ (func (car key)
+ (cdr key)
+ value)))
+ (atomic-box-ref (get-ht-box state)))
+ out-state))
+
+
(define (apply-state state)
"Apply the contents of 'state' to the current state, on top of the \
pre-existing contents."
@@ -301,9 +252,20 @@ pre-existing contents."
(current-state)))))
+(define (combine-states a b)
+ (lighting-state
+ (apply-state a)
+ (apply-state b)))
+
+
(define (print-state a)
(pretty-print (state-source a)))
+(define-method (write (st <starlet-state>) port)
+ (write
+ (state-source st)
+ port))
+
(define (clamp-to-attr-range fix attr val)
(if (number? val)
@@ -316,14 +278,21 @@ pre-existing contents."
val))
+(define (quote-if-symbol a)
+ (if (symbol? a)
+ (list 'quote a)
+ a))
+
+
(define (state-source a)
(cons 'lighting-state
- (state-map (lambda (fix attr val)
- (list 'at
- (get-fixture-name fix)
- (list 'quote attr)
- (clamp-to-attr-range fix attr val)))
- a)))
+ (state-map->list (lambda (fix attr val)
+ (list 'at
+ (get-fixture-name fix)
+ (canonical-name attr)
+ (quote-if-symbol
+ (clamp-to-attr-range fix attr val))))
+ a)))
;; Coerce something from a state object into a number for scanout
@@ -343,8 +312,7 @@ pre-existing contents."
old-ht)
(clear-state! state))) ;; Try again
- (run-hook (get-update-hook state)
- '() #f #f #f))
+ (run-hook (get-update-hook state) #f))
(define (partition3 pred1 pred2 input)
@@ -355,69 +323,87 @@ pre-existing contents."
(values output1 output2 others))))
-(define (set-fixtures fixtures attr-name value)
- (for-each (lambda (fix)
- (set-in-state! (current-state)
- fix
- (car attr-name)
- (clamp-to-attr-range fix
- (car attr-name)
- (car value))))
- fixtures))
+(define (set-fixtures fixtures attribute value)
+ (for-each
+ (lambda (fix)
+ (if (fixture-has-attr? fix attribute)
+ (set-in-state! (current-state)
+ fix
+ attribute
+ (clamp-to-attr-range fix attribute value))
+ (error "Fixture does not have attribute"
+ (get-fixture-name fix)
+ (canonical-name attribute))))
+ fixtures))
;; (at <fixtures/groups> [<attribute>] <level> [<attribute> <level>...])
;; (at fix1 100) <-- Set intensity of single fixture
-;; (at fix1 'intensity 100) <-- Explicit attribute name
+;; (at fix1 intensity 100) <-- Explicit attribute name
;; (at fix1 fix2 100) <-- Multiple fixtures
-;; (at fix1 fix2 'pan 36) <-- Multiple fixtures + explicit attribute
-;; (at group1 fix1 'intensity 100) <-- Groups can be used instead of fixtures
-;; (at fix1 100 'pan 36) <-- Set multiple attributes
-;; NB Can't set multiple fixtures and attributes: (at fix1 'pan 35 fix2 'tilt 22)
+;; (at fix1 fix2 pan 36) <-- Multiple fixtures + explicit attribute
+;; (at group1 fix1 intensity 100) <-- Groups can be used instead of fixtures
+;; (at fix1 100 pan 36) <-- Set multiple attributes
+;; NB Can't set multiple fixtures and attributes: (at fix1 pan 35 fix2 tilt 22)
(define (at . args)
- (receive (fixtures attr-name value)
- (partition3 fixture? symbol? (flatten-sublists args))
+ (let ((selection (get-selection)))
+ (receive (fixtures attribute value)
+ (partition3 fixture? attribute? (flatten-sublists args))
(cond
((nil? value)
(error "at: Value not specified"))
((or (more-than-one value)
- (more-than-one attr-name))
+ (more-than-one attribute))
(error "at: Only one attribute or value name"))
((and (nil? fixtures)
- (nil? attr-name))
+ (nil? attribute))
(if (nil? selection)
'no-fixtures-selected
- (set-fixtures selection '(intensity) value)))
+ (set-fixtures selection intensity (car value))))
- ((nil? attr-name)
- (set-fixtures fixtures '(intensity) value))
+ ((nil? attribute)
+ (set-fixtures fixtures intensity (car value)))
((nil? fixtures)
(if (nil? selection)
'no-fixtures-selected
- (set-fixtures selection attr-name value)))
+ (set-fixtures selection (car attribute) (car value))))
(else
- (set-fixtures fixtures attr-name value)))))
+ (set-fixtures fixtures (car attribute) (car value)))))))
-(define selection-hook (make-hook 1))
+(define (state-empty? st)
+ (hash-table-empty?
+ (atomic-box-ref
+ (get-ht-box st))))
-(define selection '())
-(define (get-selection)
- selection)
+(define (remove-fixtures-from-state! st fixture-list)
+ (let ((new-ht (make-hash-table))
+ (old-ht (atomic-box-ref (get-ht-box st))))
+ (state-for-each
+ (lambda (fix attr val)
+ (unless (memq fix fixture-list)
+ (hash-set! new-ht (cons fix attr) val)))
+ st)
+ (if (eq? old-ht (atomic-box-compare-and-swap!
+ (get-ht-box st)
+ old-ht
+ new-ht))
+ (run-hook (get-update-hook st) #f)
+ (remove-fixtures-from-state! st fixture-list))))
+
+(define (remove-fixture-from-state! st fix)
+ (remove-fixtures-from-state! st (list fix)))
-(define (sel . fixture-list)
- (if (nil? fixture-list)
- (set! selection '())
- (if (not (car fixture-list))
- (set! selection '())
- (set! selection (flatten-sublists fixture-list))))
- (run-hook selection-hook selection))
+(define (remove-selection-from-programmer!)
+ (remove-fixtures-from-state!
+ programmer-state
+ (get-selection)))
diff --git a/guile/starlet/utils.scm b/guile/starlet/utils.scm
index 16e3364..1506553 100644
--- a/guile/starlet/utils.scm
+++ b/guile/starlet/utils.scm
@@ -20,13 +20,30 @@
;;
(define-module (starlet utils)
#:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-8)
+ #:use-module (ice-9 exceptions)
+ #:use-module (ice-9 control)
#:export (print-hash-table
copy-hash-table
in-range
mean
flatten-sublists
more-than-one
- hirestime))
+ hirestime
+ lsb
+ msb
+ ensure-number
+ round-dmx
+ scale-to-range
+ scale-and-clamp-to-range
+ percent->dmxval8
+ percent->dmxval16
+ comment
+ hash-table-empty?
+ lookup
+ add-and-run-hook!
+ cat-with-spaces
+ next-item-in-list))
(define (print-hash-table ht)
@@ -60,15 +77,12 @@
(define (flatten-sublists l)
-
- (define (listify a)
- (if (list? a)
- a
- (list a)))
-
- (fold (lambda (a prev)
- (append prev (listify a)))
- '() l))
+ (fold
+ (lambda (el prev)
+ (if (list? el)
+ (append (flatten-sublists el) prev)
+ (cons el prev)))
+ '() l))
(define (more-than-one a)
@@ -83,3 +97,101 @@
(/ (cdr a)
1000000))))
+
+(define (msb val)
+ (round-dmx (euclidean-quotient val 256)))
+
+(define (lsb val)
+ (round-dmx (euclidean-remainder val 256)))
+
+
+(define (round-dmx a)
+ (inexact->exact
+ (min 255 (max 0 (round a)))))
+
+
+(define (ensure-number value irritating)
+ (unless (number? value)
+ (raise-exception (make-exception
+ (make-exception-with-message "Value is not a number")
+ (make-exception-with-irritants irritating)))))
+
+
+(define (percent->dmxval8 val)
+ (round-dmx
+ (scale-to-range val '(0 100) '(0 255))))
+
+
+(define (percent->dmxval16 val)
+ (scale-to-range val '(0 100) '(0 65535)))
+
+
+(define (scale-to-range val orig-range dest-range)
+
+ (define (range r)
+ (- (cadr r) (car r)))
+
+ (+ (car dest-range)
+ (* (range dest-range)
+ (/ (- val (car orig-range))
+ (range orig-range)))))
+
+
+(define (clamp-to-range val val1 val2)
+ (let ((minval (min val1 val2))
+ (maxval (max val1 val2)))
+ (max minval
+ (min val maxval))))
+
+
+;; Like scale-to-range, but result is clamped within dest-range
+(define (scale-and-clamp-to-range val orig-range dest-range)
+ (clamp-to-range
+ (scale-to-range val orig-range dest-range)
+ (car dest-range)
+ (cadr dest-range)))
+
+
+(define-syntax comment
+ (syntax-rules ()
+ ((_ body ...)
+ #f)))
+
+
+(define (hash-table-empty? ht)
+ (let/ec
+ return
+ (hash-for-each-handle
+ (lambda (key)
+ (return #f))
+ ht)
+ #t))
+
+
+(define (lookup key dictionary)
+ (cond
+ ((nil? dictionary)
+ #f)
+ ((eq? key (caar dictionary))
+ (cadr (car dictionary)))
+ (else
+ (lookup key (cdr dictionary)))))
+
+
+(define (add-and-run-hook! hook proc . initial-args)
+ (add-hook! hook proc)
+ (apply proc initial-args))
+
+
+(define (cat-with-spaces lst)
+ (reduce
+ (lambda (b a)
+ (string-append a " " b))
+ "" lst))
+
+
+(define (next-item-in-list the-list cval)
+ (let ((sl (memq cval the-list)))
+ (if (nil? (cdr sl))
+ (first the-list)
+ (second sl))))