From c9a2ab250e836f7842a71509511bcef3848d20b1 Mon Sep 17 00:00:00 2001 From: Arne Brasseur Date: Sat, 18 Nov 2023 13:07:30 +0100 Subject: [PATCH 1/9] Get MiniBeast running again, still some render glitches but starting to work --- .gitignore | 5 + bb.edn | 1 + bin/launchpad | 4 + deps.edn | 7 + project.clj | 8 - src/core.clj | 468 +++++++++++++++++++------------------ src/minibeast/core.clj | 26 +-- src/minibeast/mbsynth.clj | 13 +- test/synth_0/test/core.clj | 6 - 9 files changed, 280 insertions(+), 258 deletions(-) create mode 100644 bb.edn create mode 100755 bin/launchpad create mode 100644 deps.edn delete mode 100644 project.clj delete mode 100644 test/synth_0/test/core.clj diff --git a/.gitignore b/.gitignore index 63ff7ea..bd72ffd 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ pom.xml /data/*jpg target .DS_store +native +.cpcache +hs_err*.log +tmp_background.png +tmp-background.png diff --git a/bb.edn b/bb.edn new file mode 100644 index 0000000..062f720 --- /dev/null +++ b/bb.edn @@ -0,0 +1 @@ +{:deps {com.lambdaisland/launchpad {:mvn/version "0.17.93-alpha"}}} diff --git a/bin/launchpad b/bin/launchpad new file mode 100755 index 0000000..e08abe0 --- /dev/null +++ b/bin/launchpad @@ -0,0 +1,4 @@ +#!/usr/bin/env -S pw-jack bb +(require '[lambdaisland.launchpad :as launchpad]) + +(launchpad/main {}) diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..12022e7 --- /dev/null +++ b/deps.edn @@ -0,0 +1,7 @@ +{:paths ["src" "data"] + :deps + {org.clojure/clojure {:mvn/version "1.11.1"}, + overtone/overtone {:mvn/version "0.11.0"}, + quil/quil {:mvn/version "4.3.1323"}, + commons-collections/commons-collections {:mvn/version "20040616"}}, + :aliases {}} diff --git a/project.clj b/project.clj deleted file mode 100644 index c85899f..0000000 --- a/project.clj +++ /dev/null @@ -1,8 +0,0 @@ -(defproject mini-beast "0.0.1-SNAPSHOT" - :description "mini-beast" - :dependencies [[org.clojure/clojure "1.4.0"] - [overtone "0.9.1"] - [quil "1.6.0"] - [commons-collections "3.0-dev2"]] - :main ^{:skip-aot true} minibeast.core - :repl-init minibeast.core) diff --git a/src/core.clj b/src/core.clj index f9334bb..f6fc317 100644 --- a/src/core.clj +++ b/src/core.clj @@ -1,20 +1,27 @@ (ns minibeast.core - (:import [javax.swing JFileChooser JMenuBar JMenu JMenuItem] - [javax.swing.filechooser FileNameExtensionFilter] - [java.awt.event ActionListener] - [java.io File] - [java.awt.Toolkit]) - (:use [overtone.live :exclude (mouse-button mouse-x mouse-y rotate fill)] - [overtone.helpers.system :only [mac-os?]] - [quil.core :exclude (abs acos asin atan atan2 ceil cos - exp line log - ;; mouse-button mouse-x mouse-y - pow round scale sin sqrt tan triangle - TWO-PI)] - [minibeast.version :only [BEAST-VERSION-STR]] - [clojure.set :only [difference]] - [quil.applet] - [minibeast.mbsynth])) + (:require [quil.applet :as ap]) + (:use + [overtone.live :exclude [mouse-button mouse-x mouse-y rotate fill]] + [overtone.helpers.system :only [mac-os?]] + [quil.core :as q + :exclude [abs acos asin atan atan2 ceil cos + exp line log + ;; mouse-button mouse-x mouse-y + pow round scale sin sqrt tan triangle + TWO-PI floor clear clip load-image debug]] + [minibeast.version :only [BEAST-VERSION-STR]] + [clojure.set :only [difference]] + [quil.applet] + [minibeast.mbsynth]) + (:import + (javax.swing JFileChooser JMenuBar JMenu JMenuItem) + (javax.swing.filechooser FileNameExtensionFilter) + (java.awt.event ActionListener) + (java.io File) + (java.awt Toolkit) + javax.imageio.ImageIO + processing.awt.PImageAWT) + (:require [clojure.java.io :as io])) ;; Create some synth instruments to be used by voices. @@ -48,7 +55,7 @@ arp-note-bus-a)) (range 4)))) (def synth-voices-b - (doall (map (fn [_] (voice [:head voice-g] + (doall (map (fn [_] (voice [:head voice-g] voice-bus-b lfo-bus-b arp-trig-bus-b @@ -98,6 +105,7 @@ ;; to the operation of the ui of the synth. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defonce quil-state (atom {})) (defonce ui-state (atom {:a {} :b {}})) (defonce selected-control (atom nil)) (defonce dragged-control (atom nil)) @@ -136,8 +144,12 @@ (defonce voices-a (ref ())) (defonce voices-b (ref ())) +(defn debug [& args] + (when false + (apply println args))) + (defn update-ui-state [m] - (println "update-ui-state " m) + (debug "update-ui-state " m) (swap! ui-state (partial merge-with merge) {@selected-split m})) (defn update-control @@ -228,7 +240,7 @@ (let [voices (if (< note @split-note) voices-a voices-b)] (if-not (some #(= (:note %) note) @voices) (let [synth (getsynth note voices)] - (println "playing " note " with synth " (:id synth)) + (debug "playing " note " with synth " (:id synth)) ;; turn off the old note. Maybe this is a reused synth (ctl synth :gate 0.0) ;; turn on the new note @@ -253,7 +265,7 @@ msg :msg cmd :command :as control-msg}] - ;; (println control-msg) + ;; (debug control-msg) (if (nil? @selected-control) ;; lookup a control matching the device, channel, note, and cmd (if-let [matched-control (@dev-chan-note-cmd->control @@ -263,7 +275,7 @@ :cmd cmd})] (update-control matched-control vel)) (do - (println "assigning assoc " + (debug "assigning assoc " {:device-name device-name :chan chan :note note :cmd cmd} @selected-control) ;; make the association between the midi event and the synth control @@ -659,27 +671,27 @@ (Control. 408 398 :knob (mk-pos-only-knob "Rate") synth-voices :vibrato-rate (fn [val] (/ val 8.0))) ;; Vibrato type selector (AdvancedControl. 424 338 :selector {:ui-aux-fn (fn [] (shape (state :trill-up-shape) 445 345) - (shape (state :sin-shape) 445 355) - (shape (state :trill-down-shape) 445 365))} + (shape (state :sin-shape) 445 355) + (shape (state :trill-down-shape) 445 365))} :vibrato-fn (fn [val] (let [old-fn (:vibrato-fn @synth-state) new-state (alter-state - #(assoc % :vibrato-fn - (if (zero? val) - ;; button press; switch to next fn - (next-vibrato-fn old-fn) - ;; knob or slider; calc fn - (let [num-fns (count vibrato-fns)] - (vibrato-fns (int (constrain (* num-fns (/ val 127.0)) - 0 (dec num-fns)))))))) + #(assoc % :vibrato-fn + (if (zero? val) + ;; button press; switch to next fn + (next-vibrato-fn old-fn) + ;; knob or slider; calc fn + (let [num-fns (count vibrato-fns)] + (vibrato-fns (int (constrain (* num-fns (/ val 127.0)) + 0 (dec num-fns)))))))) new-fn (:vibrato-fn new-state) _ (update-ui-state {:vibrato-fn (case new-fn - :trill-up 1 - :vibrato 70 - :trill-down 127)}) + :trill-up 1 + :vibrato 70 + :trill-down 127)}) mod-wheel-pos (:mod-wheel-pos @synth-state)] (case (:vibrato-fn @synth-state) - :vibrato [[synth-voices :vibrato-amp (/ mod-wheel-pos 127.0)] + :vibrato [[synth-voices :vibrato-amp (/ mod-wheel-pos 127.0)] [synth-voices :vibrato-trill 0]] :trill-up [[synth-voices :vibrato-amp 0.0] [synth-voices :vibrato-trill (int (/ mod-wheel-pos 10))]] @@ -692,27 +704,27 @@ ;; Mod wheel function (AdvancedControl. 281 335 :selector {:caption "MOD Wheel" :ui-aux-fn (fn [] (text "Cutoff" 311 348) - (text "Vibrato" 312 358) - (text "LFOAmt" 313 368))} + (text "Vibrato" 312 358) + (text "LFOAmt" 313 368))} :mod-wheel-fn (fn [val] (let [old-fn (:mod-wheel-fn @synth-state) - new-state (alter-state - #(assoc % :mod-wheel-fn - (if (zero? val) - ;; button press; switch to next fn - (next-mod-wheel-fn old-fn) - ;; knob or slider; calc fn - (let [num-fns (count mod-wheel-fns)] - (mod-wheel-fns (int (constrain (* num-fns (/ val 127.0)) - 0 (dec num-fns)))))))) - new-fn (:mod-wheel-fn new-state) - _ (update-ui-state {:mod-wheel-fn (case new-fn - :cutoff 1 - :vibrato 65 - :lfo-amount 127)})] - [])) + new-state (alter-state + #(assoc % :mod-wheel-fn + (if (zero? val) + ;; button press; switch to next fn + (next-mod-wheel-fn old-fn) + ;; knob or slider; calc fn + (let [num-fns (count mod-wheel-fns)] + (mod-wheel-fns (int (constrain (* num-fns (/ val 127.0)) + 0 (dec num-fns)))))))) + new-fn (:mod-wheel-fn new-state) + _ (update-ui-state {:mod-wheel-fn (case new-fn + :cutoff 1 + :vibrato 65 + :lfo-amount 127)})] + [])) (fn [val] (case (:mod-wheel-fn @synth-state) - :cutoff 0 :vibrato 10 :lfo-amount 20))) + :cutoff 0 :vibrato 10 :lfo-amount 20))) ;; Show modulation panel (AdvancedControl. 64 434 :small-button {:caption "Toggle panel"} :toggle-modulation-controls @@ -731,66 +743,66 @@ (Control. 544 332 :knob (mk-plus-minus-knob "Pitch") synth-voices :lfo2pitch (fn [val] (- (/ val 2.0) 32.0))) (Control. 612 332 :knob (mk-plus-minus-knob "Filter") synth-voices :lfo2filter (fn [val] (* (- val 64.0) 400.0))) (Control. 681 332 :knob (mk-plus-minus-knob "Amp" - {:caption-dx -25 - :caption-dy -55}) synth-voices :lfo2amp (fn [val] (- (/ val 32) 1.98))) + {:caption-dx -25 + :caption-dy -55}) synth-voices :lfo2amp (fn [val] (- (/ val 32) 1.98))) ;; LFO waveform knob (AdvancedControl. 478 398 :knob {:caption "Wave" :ui-aux-fn #(doall - (map-indexed - (fn [i e] (apply shape - (state e) - (selector-knob-label-pos 478 398 i))) - [:sin-shape :tri-shape :saw-shape :square-shape - :random-shape :random-slew-shape]))} + (map-indexed + (fn [i e] (apply shape + (state e) + (selector-knob-label-pos 478 398 i))) + [:sin-shape :tri-shape :saw-shape :square-shape + :random-shape :random-slew-shape]))} :lfo-waveform (fn [val] (let [old-waveform (:lfo-waveform @synth-state) - new-state (alter-state - #(assoc % :lfo-waveform - (if (zero? val) - ;; button press; switch to next waveform - (next-lfo-waveform (:lfo-waveform %)) - ;; knob or slider; calculate waveform - (lfo-waveforms (int (* (/ val 128.0) (count lfo-waveforms))))))) - new-waveform (:lfo-waveform new-state) - _ (update-ui-state {:lfo-waveform (case new-waveform - :lfo-sin 1 - :lfo-tri 23 - :lfo-saw 45 - :lfo-square 67 - :lfo-rand 89 - :lfo-slew-rand 127)})] - ;; Toggle lfo waveform - [[lfo-synth old-waveform 0] [lfo-synth new-waveform (:lfo-amp @synth-state)]])) + new-state (alter-state + #(assoc % :lfo-waveform + (if (zero? val) + ;; button press; switch to next waveform + (next-lfo-waveform (:lfo-waveform %)) + ;; knob or slider; calculate waveform + (lfo-waveforms (int (* (/ val 128.0) (count lfo-waveforms))))))) + new-waveform (:lfo-waveform new-state) + _ (update-ui-state {:lfo-waveform (case new-waveform + :lfo-sin 1 + :lfo-tri 23 + :lfo-saw 45 + :lfo-square 67 + :lfo-rand 89 + :lfo-slew-rand 127)})] + ;; Toggle lfo waveform + [[lfo-synth old-waveform 0] [lfo-synth new-waveform (:lfo-amp @synth-state)]])) (fn [val] (case (:lfo-waveform @synth-state) - :lfo-sin 60 :lfo-tri 75 :lfo-saw 89 :lfo-square 100 :lfo-rand 115 :lfo-slew-rand 130))) + :lfo-sin 60 :lfo-tri 75 :lfo-saw 89 :lfo-square 100 :lfo-rand 115 :lfo-slew-rand 130))) ;; lfo-arp sync selector (AdvancedControl. 620 398 :selector {:caption "Clock" :ui-aux-fn (fn [] (text "Free" 645 410) - (text "Sync" 645 426))} + (text "Sync" 645 426))} :lfo-arp-sync (fn [val] (let [last-val (:lfo-arp-sync @synth-state) new-state (alter-state - #(assoc % :lfo-arp-sync - (if (zero? val) - ;; button press; switch to next val - (case last-val - 0 1 - 1 0) - ;; knob or slider; calculate val - ([0 1] (int (constrain (* 2.0 (/ val 127.0)) 0 1)))))) - new-val (:lfo-arp-sync new-state) - _ (update-ui-state {:lfo-arp-sync (case new-val - 0 1 - 1 127)})] - ;; Toggle flag - [[lfo-synth :lfo-arp-sync new-val]])) + #(assoc % :lfo-arp-sync + (if (zero? val) + ;; button press; switch to next val + (case last-val + 0 1 + 1 0) + ;; knob or slider; calculate val + ([0 1] (int (constrain (* 2.0 (/ val 127.0)) 0 1)))))) + new-val (:lfo-arp-sync new-state) + _ (update-ui-state {:lfo-arp-sync (case new-val + 0 1 + 1 127)})] + ;; Toggle flag + [[lfo-synth :lfo-arp-sync new-val]])) (fn [val] (case (:lfo-arp-sync @synth-state) - 0 0 1 16)))]) + 0 0 1 16)))]) (defn arp-controls [] - [(Control. 885 332 :knob (mk-pos-only-knob "Tempo") arp-synth :arp-rate + [(Control. 885 332 :knob (mk-pos-only-knob "Tempo") arp-synth :arp-rate (fn [val] (let [rate (/ val 5.0)] - (println "arp-rate " (* (/ 60 8) rate) " bmp") + (debug "arp-rate " (* (/ 60 8) rate) " bmp") rate))) (Control. 829 398 :knob (mk-pos-only-knob "Swing") arp-synth :arp-swing-phase (fn [val] (* 360 (/ val 127.0)))) ;; Arp mode selector @@ -798,9 +810,9 @@ :ui-aux-fn #(do (text-align :left) (doall - (map-indexed - (fn [i e](apply text e (selector-knob-label-pos 743 400 i))) - ["Off" "Up" "Down" "Up/Dwn" "Rand"])) + (map-indexed + (fn [i e](apply text e (selector-knob-label-pos 743 400 i))) + ["Off" "Up" "Down" "Up/Dwn" "Rand"])) (text-align :center))} :arp-mode (fn [val] (let [old-mode (:arp-mode @synth-state) @@ -813,70 +825,70 @@ (int (constrain (* 5.0 (/ val 127.0)) 0 4))))) new-mode (:arp-mode new-state) _ (update-ui-state {:arp-mode (case new-mode - 0 1 - 1 30 - 2 56 - 3 82 - 4 127)}) - _ (println "new-mode " new-mode)] - [[arp-synth :arp-mode new-mode]])) + 0 1 + 1 30 + 2 56 + 3 82 + 4 127)}) + _ (debug "new-mode " new-mode)] + [[arp-synth :arp-mode new-mode]])) (fn [val] (case (int (:arp-mode @synth-state)) - 0 62 1 75 2 85 3 98 4 109))) + 0 62 1 75 2 85 3 98 4 109))) ;; Arp range selector (AdvancedControl. 681 398 :knob {:caption "Octave" :ui-aux-fn #(doall - (map-indexed - (fn [i e](apply text e (selector-knob-label-pos 686 403 i))) - ["1" "2" "3" "4"]))} + (map-indexed + (fn [i e](apply text e (selector-knob-label-pos 686 403 i))) + ["1" "2" "3" "4"]))} :arp-range (fn [val] (let [old-range (:arp-range @synth-state) new-state (alter-state - #(assoc % :arp-range - (if (zero? val) - ;; button press; switch to next range - (inc (mod old-range 4)) - ;; knob or slider; calculate range - (inc (int (* (/ (inc val) 129.0) 4)))))) - new-range (:arp-range new-state) - _ (update-ui-state {:arp-range (case new-range - 1 1 - 2 33 - 3 65 - 4 127)})] - [[arp-synth :arp-range new-range]])) + #(assoc % :arp-range + (if (zero? val) + ;; button press; switch to next range + (inc (mod old-range 4)) + ;; knob or slider; calculate range + (inc (int (* (/ (inc val) 129.0) 4)))))) + new-range (:arp-range new-state) + _ (update-ui-state {:arp-range (case new-range + 1 1 + 2 33 + 3 65 + 4 127)})] + [[arp-synth :arp-range new-range]])) (fn [val] (case (:arp-range @synth-state) - 1 60 2 75 3 88 4 101))) + 1 60 2 75 3 88 4 101))) ;; Arp step selector (AdvancedControl. 810 336 :knob {:caption "Step" :ui-aux-fn #(doall - (map-indexed - (fn [i e](apply text e (selector-knob-label-pos 820 340 i))) - ["1/4" "1/8" "1/16" "1/4T" "1/8T" "1/16T"]))} + (map-indexed + (fn [i e](apply text e (selector-knob-label-pos 820 340 i))) + ["1/4" "1/8" "1/16" "1/4T" "1/8T" "1/16T"]))} :arp-step (fn [val] (let [old-step (:arp-step @synth-state) new-state (alter-state - #(assoc % :arp-step - (if (zero? val) - ;; button press; switch to next step - (mod (inc old-step) 6) - ;; knob or slider; calculate range - (int (* (/ (inc val) 129.0) 6))))) - new-step (:arp-step new-state) - _ (update-ui-state {:arp-step (case new-step - 0 1 - 1 23 - 2 45 - 3 67 - 4 89 - 5 127)})] - [[arp-synth :arp-step new-step]])) + #(assoc % :arp-step + (if (zero? val) + ;; button press; switch to next step + (mod (inc old-step) 6) + ;; knob or slider; calculate range + (int (* (/ (inc val) 129.0) 6))))) + new-step (:arp-step new-state) + _ (update-ui-state {:arp-step (case new-step + 0 1 + 1 23 + 2 45 + 3 67 + 4 89 + 5 127)})] + [[arp-synth :arp-step new-step]])) (fn [val] (case (:arp-step @synth-state) - 0 65 1 75 2 84 3 98 4 111 5 125))) + 0 65 1 75 2 84 3 98 4 111 5 125))) ;; Arp tap tempo button (AdvancedControl. 889 407 :button {:caption "Tap"} :arp-tap-tempo - (fn [val] + (fn [val] (let [dt (- (now) (:arp-tap-time @synth-state))] (alter-state #(assoc % :arp-tap-time (now))) (if (< dt 2000) @@ -919,39 +931,39 @@ (Control. 60 566 :knob (mk-pos-only-knob "Mix") mb-synth :reverb-mix (fn [val] (/ val 127.0))) (Control. 130 566 :knob (mk-pos-only-knob "Size") mb-synth :reverb-size (fn [val] (/ val 127.0))) (Control. 200 566 :knob (mk-pos-only-knob "Damp") mb-synth :reverb-damp (fn [val] (/ val 127.0))) - + (AdvancedControl. 70 652 :selector {:caption "Split Select" :ui-aux-fn (fn [] (text "Left" 96 664) - (text "Right" 96 680))} + (text "Right" 96 680))} :selected-split (fn [val] (do (reset! selected-split (if (zero? val) - ;; button press; switch to next mode - (case @selected-split - :a :b - :b :a) + ;; button press; switch to next mode + (case @selected-split + :a :b + :b :a) ;; knob or slider; calculate mode (let [splits [:a :b] num-splits (count splits)] (splits (int (constrain (* num-splits (/ val 127.0)) 0 (dec num-splits))))))) [])) (fn [val] (case @selected-split - :a 0 :b 16))) + :a 0 :b 16))) (AdvancedControl. 120 642 :knob {:caption "Split Note" :pos-indicator? true :ui-aux-fn (fn [] (fill (color 0 0 0 255)) - (rect 180 658 40 24) - (fill (color 255 0 0 255)) - (text-size 16) - (text (str (find-note-name (note @split-note))) 199 676) - (text-size 8) - (fill (color 255 255 255 255)))} - :split-note - (fn [val] (do - (reset! split-note (int val)) - [])) - (fn [val] val))]) + (rect 180 658 40 24) + (fill (color 255 0 0 255)) + (text-size 16) + (text (str (find-note-name (note @split-note))) 199 676) + (text-size 8) + (fill (color 255 255 255 255)))} + :split-note + (fn [val] (do + (reset! split-note (int val)) + [])) + (fn [val] val))]) (defn octave-controls [] [(AdvancedControl. 100 392 :small-button {:caption "Down"} :octave-down @@ -965,37 +977,37 @@ (defn get-mod-controls [] (let [get-mod-controls-helper - (memoize (fn [] - (map (fn [c] (if (= (type c) Control) - (control->advanced-control c) - c)) (mod-controls))))] + (memoize (fn [] + (map (fn [c] (if (= (type c) Control) + (control->advanced-control c) + c)) (mod-controls))))] (get-mod-controls-helper))) -(defn get-controls +(defn get-controls ([] (get-controls @show-modulation-controls?)) ([show-modulation-controls?] - (let [get-controls-helper - (memoize - (fn [show-mod-controls?] - (map (fn [c] (if (= (type c) Control) - (control->advanced-control c) - c)) - (concat (osc-controls) - (filter-controls) - (osc-mix-controls) - (filter-asdr-controls) - (amp-adsr-controls) - (misc-controls) - (lfo-contols) - (arp-controls) - (volume-controls) - (wheel-controls) - (octave-controls) + (let [get-controls-helper + (memoize + (fn [show-mod-controls?] + (map (fn [c] (if (= (type c) Control) + (control->advanced-control c) + c)) + (concat (osc-controls) + (filter-controls) + (osc-mix-controls) + (filter-asdr-controls) + (amp-adsr-controls) + (misc-controls) + (lfo-contols) + (arp-controls) + (volume-controls) + (wheel-controls) + (octave-controls) (if show-mod-controls? - (mod-controls) - [])))))] - (get-controls-helper show-modulation-controls?)))) + (mod-controls) + [])))))] + (get-controls-helper show-modulation-controls?)))) ;; populate the map with ALL the controls. (def ctl->control (into {} (map (fn [e] {(:name e) e}) (get-controls true)))) @@ -1061,7 +1073,7 @@ (swap! ui-state merge {@selected-split {}}) (reset-synth-defaults mbsynth) (doall (map (fn [[k v]] - (println "Setting " k) + (debug "Setting " k) (let [control (ctl->control k)] ;; zero values are considered clicks. Just make them 1's instead. (update-control control (max 1 v)))) patch)))) @@ -1092,6 +1104,9 @@ "Save Patch" (save-synth-settings-to-file) "Open Patch" (apply-synth-settings-from-file))) +(defn load-image [fname] + (PImageAWT. (ImageIO/read (io/resource fname)))) + (defn setup [] (smooth) (frame-rate 10) @@ -1146,7 +1161,7 @@ draw-background? true tmp-background "tmp-background.png"] (reset! first-draw? false) - (println "--> Compositing background...") + (debug "--> Compositing background...") (set-image 0 0 background-img) (tint overtone-tint) (image overtone-circle-img 65 20) @@ -1155,8 +1170,8 @@ (image mini-beast-text-img 125 50) (image logo-img 120 80) (doall (map #(draw-control % draw-foreground? draw-background?) (get-controls false))) - (save tmp-background) - (println "--> Loading new background...") + (save (str "data/" tmp-background)) + (debug "--> Loading new background...") (reset! composite-background-img (load-image tmp-background)))) (set-image 0 0 @composite-background-img) @@ -1166,13 +1181,13 @@ (:color k) (some (fn [v] (= (:note v) (note (:note k)))) (concat @voices-a @voices-b)))) (sort-by (fn [k] (case (:color k) - :white 0 - :black 1)) ui-keys))) + :white 0 + :black 1)) ui-keys))) (tint (color 255 255 255 255)) ;; draw modulation panel (when @show-modulation-controls? - ; draw background + ; draw background (image (state :mod-panel-img) 50 472) (doall (map #(draw-control % true true) (get-mod-controls)))) @@ -1225,7 +1240,7 @@ | | | + [x y] | | | - +---------+ + +---------+ [s t]" [x y u v s t] (and (> x u) @@ -1264,8 +1279,8 @@ (let [button (if @control-key-pressed :right (mouse-button))] - (println button) - (println "ctrl? " @control-key-pressed) + (debug button) + (debug "ctrl? " @control-key-pressed) (case button :left (when-let [matched-control (control-at-xy (mouse-x) (mouse-y))] (update-control matched-control 0)) @@ -1273,8 +1288,8 @@ (let [x (mouse-x) y (mouse-y) c (control-at-xy x y)] - (println "right click at [" x ", " y "]") - (println "found control " c) + (debug "right click at [" x ", " y "]") + (debug "found control " c) (reset! selected-control c)) (reset! selected-control nil)) nil))) @@ -1287,8 +1302,8 @@ (when-let [c (if (nil? @dragged-control) (reset! dragged-control (control-at-xy x y)) @dragged-control)] - ;; move sliders 1-to-1 with the ui (* 1/0.6) - ;; move selectors at an increased rate (8x) + ;; move sliders 1-to-1 with the ui (* 1/0.6) + ;; move selectors at an increased rate (8x) (let [dy (* (case (:type c) :slider (/ 1.0 0.6) :selector -8.0 @@ -1298,14 +1313,14 @@ ;; constrain new-val to 1.0-127.0 ;; don't actually get to zero because it is reserved for button presses new-val (constrain (- last-val dy) 1.0 127.0)] - (println "last-val " last-val " new-val " new-val) + (debug "last-val " last-val " new-val " new-val) (when (not-any? (:type c) [:button :small-button]) (update-control c new-val))))))) ;; keyboard key press using mouse (defn mouse-pressed [] (when-let [note (key-note-at-xy (mouse-x) (mouse-y))] - (println "Playing " (find-note-name note)) + (debug "Playing " (find-note-name note)) (reset! mouse-pressed-note note) (keydown note 1.0))) @@ -1356,21 +1371,21 @@ (defn key-pressed [] (when-let [note (key-code->note (raw-key))] - (println "Playing " (find-note-name note)) + (debug "Playing " (find-note-name note)) (keydown note 1.0)) (when (= java.awt.event.KeyEvent/VK_CONTROL (key-code)) - (println "control pressed") + (debug "control pressed") (reset! control-key-pressed true))) (defn key-released [] (when-let [note (key-code->note (raw-key))] (keyup note)) (when (= java.awt.event.KeyEvent/VK_CONTROL (key-code)) - (println "control released") + (debug "control released") (reset! control-key-pressed false))) (defn close [] - (println "--> Beast stopped...") + (debug "--> Beast stopped...") (kill-server)) (defn register-midi-handlers @@ -1414,8 +1429,8 @@ (.addActionListener listener)))))) sk (sketch :title "MiniBeast" - :setup setup - :draw draw + :setup #'setup + :draw #'draw :mouse-clicked mouse-clicked :mouse-dragged mouse-dragged :mouse-pressed mouse-pressed @@ -1424,22 +1439,27 @@ :key-released key-released :on-close close :decor true - :size [1036 850]) - frame (-> sk meta :target-obj deref) + :size [1036 850] + :state quil-state) + _ (def sk sk) + ;;frame (-> sk meta :target-obj deref) icon (.createImage (java.awt.Toolkit/getDefaultToolkit) "data/icon.png")] - (doto frame - (.setJMenuBar mb) - (.setVisible true)) - (when* (mac-os?) - (import com.apple.eawt.Application) - (try - (.setDockIconImage (com.apple.eawt.Application/getApplication) icon) - (catch Exception e - false))))) - -(defn -main - [& args] + #_(doto frame + (.setJMenuBar mb) + (.setVisible true)) + #_(when* (mac-os?) + (import com.apple.eawt.Application) + (try + (.setDockIconImage (com.apple.eawt.Application/getApplication) icon) + (catch Exception e + false))))) + +(defn -main [& args] (println "--> Starting The MiniBeast...") (register-midi-handlers) (start-gui) (println "--> Beast started...")) + +(comment + + ) diff --git a/src/minibeast/core.clj b/src/minibeast/core.clj index 96f7677..28102c9 100644 --- a/src/minibeast/core.clj +++ b/src/minibeast/core.clj @@ -6,11 +6,11 @@ [java.awt.Toolkit]) (:use [overtone.live :exclude (mouse-button mouse-x mouse-y)] [overtone.helpers.system :only [mac-os?]] - [quil.core :exclude (abs acos asin atan atan2 ceil cos - exp line log - ;; mouse-button mouse-x mouse-y - pow round scale sin sqrt tan triangle - TWO-PI)] + [quil.core :exclude [abs acos asin atan atan2 ceil cos + exp line log + ;; mouse-button mouse-x mouse-y + pow round scale sin sqrt tan triangle + TWO-PI]] [minibeast.version :only [BEAST-VERSION-STR]] [clojure.set :only [difference]] [quil.applet] @@ -678,7 +678,7 @@ :trill-down 127)}) mod-wheel-pos (:mod-wheel-pos @synth-state)] (case (:vibrato-fn @synth-state) - :vibrato [[synth-voices :vibrato-amp (/ mod-wheel-pos 127.0)] + :vibrato [[synth-voices :vibrato-amp (/ mod-wheel-pos 127.0)] [synth-voices :vibrato-trill 0]] :trill-up [[synth-voices :vibrato-amp 0.0] [synth-voices :vibrato-trill (int (/ mod-wheel-pos 10))]] @@ -736,7 +736,7 @@ (AdvancedControl. 478 398 :knob {:caption "Wave" :ui-aux-fn #(doall (map-indexed - (fn [i e] (apply shape + (fn [i e] (apply shape (state e) (selector-knob-label-pos 478 398 i))) [:sin-shape :tri-shape :saw-shape :square-shape @@ -787,7 +787,7 @@ 0 0 1 16)))]) (defn arp-controls [] - [(Control. 885 332 :knob (mk-pos-only-knob "Tempo") arp-synth :arp-rate + [(Control. 885 332 :knob (mk-pos-only-knob "Tempo") arp-synth :arp-rate (fn [val] (let [rate (/ val 5.0)] (println "arp-rate " (* (/ 60 8) rate) " bmp") rate))) @@ -875,7 +875,7 @@ 0 65 1 75 2 84 3 98 4 111 5 125))) ;; Arp tap tempo button (AdvancedControl. 889 407 :button {:caption "Tap"} :arp-tap-tempo - (fn [val] + (fn [val] (let [dt (- (now) (:arp-tap-time @synth-state))] (alter-state #(assoc % :arp-tap-time (now))) (if (< dt 2000) @@ -918,7 +918,7 @@ (Control. 60 566 :knob (mk-pos-only-knob "Mix") mb-synth :reverb-mix (fn [val] (/ val 127.0))) (Control. 130 566 :knob (mk-pos-only-knob "Size") mb-synth :reverb-size (fn [val] (/ val 127.0))) (Control. 200 566 :knob (mk-pos-only-knob "Damp") mb-synth :reverb-damp (fn [val] (/ val 127.0))) - + (AdvancedControl. 70 652 :selector {:caption "Split Select" :ui-aux-fn (fn [] (text "Left" 96 664) (text "Right" 96 680))} @@ -964,13 +964,13 @@ (defn get-mod-controls [] (let [get-mod-controls-helper - (memoize (fn [] + (memoize (fn [] (map (fn [c] (if (= (type c) Control) (control->advanced-control c) c)) (mod-controls))))] (get-mod-controls-helper))) -(defn get-controls +(defn get-controls ([] (get-controls @show-modulation-controls?)) ([show-modulation-controls?] @@ -1224,7 +1224,7 @@ | | | + [x y] | | | - +---------+ + +---------+ [s t]" [x y u v s t] (and (> x u) diff --git a/src/minibeast/mbsynth.clj b/src/minibeast/mbsynth.clj index 7f0ec8c..60cbfbc 100644 --- a/src/minibeast/mbsynth.clj +++ b/src/minibeast/mbsynth.clj @@ -16,7 +16,7 @@ arp-tap (tap :arp 10 (lag-ud arp-trig 0 0.9)) arp-scale-up (dseries 0 (dseq [ 7 5] INF) (+ (* arp-range 2) 1)) arp-scale-down (dseries (* 12 arp-range) (dseq [-5 -7] INF) (+ (* arp-range 2) 1)) - arp-notes (dswitch1 [(donce 0) + arp-notes (dswitch1 [#_(donce 0) ;; "Donce , a demand-rate UGen with no identifiable purpose, is deprecated. It was most likely used in the production of electronic donce music." (dseq arp-scale-up INF) (dseq arp-scale-down INF) ;; notes at the top and bottom repeat. May be due to @@ -27,7 +27,7 @@ arp-out (demand arp-trig 0 arp-notes)] (out:kr arp-trig-bus (* arp-on? arp-trig)) (out:kr arp-note-bus arp-out))) - + (defsynth LFO [lfo-bus {:default 16 :doc "bus to output LFO"} arp-trig-bus {:default 1 :doc "bus to input arp trigger"} @@ -40,7 +40,7 @@ lfo-slew-rand {:default 0.0 :doc "LFO smoothed random amount"} lfo-arp-sync {:default 0 :doc "0 = no sync, 1 = sync"}] (let [ - rand-lfo (t-rand -1.0 1.0 (sin-osc lfo-rate)) + rand-lfo (t-rand -1.0 1.0 (sin-osc lfo-rate)) arp-trig (in:kr arp-trig-bus 1) phase-reset (* lfo-arp-sync (+ 1 (* -1 arp-trig))) LFO (slew (+ (* lfo-sin (sin-osc lfo-rate phase-reset)) @@ -132,7 +132,7 @@ glide-rate glide-rate) sub-note-freq (* note-freq sub-osc-coeff) TRI-FOLD-THRESH (max 0.01 (+ (* FILTER-ADSR tri-fold-env) tri-fold-thresh)) - VCO (+ (* osc-saw (+ (lf-saw note-freq) + VCO (+ (* osc-saw (+ (lf-saw note-freq) (* saw-detune-amp (lf-saw (+ note-freq saw-detune)) (lf-saw (- note-freq saw-detune)) @@ -145,7 +145,7 @@ (* sub-osc-square (square sub-note-freq (* LFO lfo2pwm))) (* osc-audio-in (sound-in))) - VCO+fback (+ VCO (* feedback-amp (local-in))) + VCO+fback (+ VCO (* feedback-amp (local-in))) vcf-freq (min 10000 (max 20 (+ cutoff (* lfo2filter LFO) (* cutoff-tracking note-freq) @@ -158,7 +158,7 @@ VIBRATO-LFO (+ 1 (* vibrato-amp (sin-osc:kr vibrato-rate))) VCA (* (+ 1 (* lfo2amp LFO)) - AMP-ADSR + AMP-ADSR VIBRATO-LFO) OUT (softclip (* velocity VCA VCF)) _ (local-out OUT) @@ -189,4 +189,3 @@ _ (local-out delay-sig) ] (out 0 (pan2 OUT)))) - diff --git a/test/synth_0/test/core.clj b/test/synth_0/test/core.clj deleted file mode 100644 index 08cd801..0000000 --- a/test/synth_0/test/core.clj +++ /dev/null @@ -1,6 +0,0 @@ -(ns synth-0.test.core - (:use [synth-0.core]) - (:use [clojure.test])) - -(deftest replace-me ;; FIXME: write - (is false "No tests have been written.")) From b70a7650fba1557467bc2821da0dad6327e76721 Mon Sep 17 00:00:00 2001 From: Arne Brasseur Date: Sat, 18 Nov 2023 13:08:35 +0100 Subject: [PATCH 2/9] Remove duplicate namespace in wrong location --- src/core.clj | 1465 ---------------------------------------- src/minibeast/core.clj | 483 ++++++------- 2 files changed, 252 insertions(+), 1696 deletions(-) delete mode 100644 src/core.clj diff --git a/src/core.clj b/src/core.clj deleted file mode 100644 index f6fc317..0000000 --- a/src/core.clj +++ /dev/null @@ -1,1465 +0,0 @@ -(ns minibeast.core - (:require [quil.applet :as ap]) - (:use - [overtone.live :exclude [mouse-button mouse-x mouse-y rotate fill]] - [overtone.helpers.system :only [mac-os?]] - [quil.core :as q - :exclude [abs acos asin atan atan2 ceil cos - exp line log - ;; mouse-button mouse-x mouse-y - pow round scale sin sqrt tan triangle - TWO-PI floor clear clip load-image debug]] - [minibeast.version :only [BEAST-VERSION-STR]] - [clojure.set :only [difference]] - [quil.applet] - [minibeast.mbsynth]) - (:import - (javax.swing JFileChooser JMenuBar JMenu JMenuItem) - (javax.swing.filechooser FileNameExtensionFilter) - (java.awt.event ActionListener) - (java.io File) - (java.awt Toolkit) - javax.imageio.ImageIO - processing.awt.PImageAWT) - (:require [clojure.java.io :as io])) - -;; Create some synth instruments to be used by voices. - -(def main-g (group)) -(def arp-g (group :head main-g)) -(def lfo-g (group :after arp-g)) -(def voice-g (group :after lfo-g)) -(def synth-g (group :after voice-g)) - -(def lfo-bus-a (control-bus)) -(def arp-trig-bus-a (control-bus)) -(def arp-note-bus-a (control-bus)) -(def voice-bus-a (audio-bus)) - -(def lfo-bus-b (control-bus)) -(def arp-trig-bus-b (control-bus)) -(def arp-note-bus-b (control-bus)) -(def voice-bus-b (audio-bus)) - -(def arp-synth-a (darp [:tail arp-g] arp-trig-bus-a arp-note-bus-a)) -(def arp-synth-b (darp [:tail arp-g] arp-trig-bus-b arp-note-bus-b)) - -(def lfo-synth-a (LFO [:tail lfo-g] lfo-bus-a arp-trig-bus-a)) -(def lfo-synth-b (LFO [:tail lfo-g] lfo-bus-b arp-trig-bus-b)) - -(def synth-voices-a - (doall (map (fn [_] (voice [:head voice-g] - voice-bus-a - lfo-bus-a - arp-trig-bus-a - arp-note-bus-a)) - (range 4)))) -(def synth-voices-b - (doall (map (fn [_] (voice [:head voice-g] - voice-bus-b - lfo-bus-b - arp-trig-bus-b - arp-note-bus-b)) - (range 4)))) - -(def mb-synth-a (mbsynth [:head synth-g] voice-bus-a)) -(def mb-synth-b (mbsynth [:head synth-g] voice-bus-b)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Internal synth state -;; -;; These definitions store and maniupulate state internal -;; to the operation of the state of the synth. -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defrecord SynthState - [sub-osc-waveform - sub-osc-amp - sub-osc-oct - octave-transpose - lfo-waveform - lfo-amp - lfo-arp-sync - filter-type - mod-wheel-fn - mod-wheel-pos - vibrato-fn - bend-range - env-speed - arp-mode - arp-range - arp-step - arp-tap-time]) - -(def synth-state (ref (SynthState. :sub-osc-square 0.0 1 0 :lfo-sin 1.0 0 :low-pass :cutoff 0.0 :vibrato 12 :fast 0 2 0 0))) - -(defn alter-state [f & more] - "Alters the state of the synth." - (dosync - (apply alter synth-state f more))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Internal ui state -;; -;; These definitions store and maniupulate state internal -;; to the operation of the ui of the synth. -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defonce quil-state (atom {})) -(defonce ui-state (atom {:a {} :b {}})) -(defonce selected-control (atom nil)) -(defonce dragged-control (atom nil)) -(defonce dev-chan-note-cmd->control (atom {})) -(defonce mouse-pressed-note (atom nil)) -(defonce show-modulation-controls? (atom false)) -(defonce selected-split (atom :a)) -(defonce split-note (atom 60)) -(defonce first-draw? (atom true)) -(defonce composite-background-img (atom nil)) -(defonce control-key-pressed (atom false)) - -(defn arp-synth [] - (case @selected-split - :a arp-synth-a - :b arp-synth-b)) - -(defn lfo-synth [] - (case @selected-split - :a lfo-synth-a - :b lfo-synth-b)) - -(defn synth-voices [] - (case @selected-split - :a synth-voices-a - :b synth-voices-b)) - -(defn mb-synth [] - (case @selected-split - :a mb-synth-a - :b mb-synth-b)) - -;; Keep a record of the last eight key presses -;; in a map of {:synth s, :note n} -(def voices-max 4) -(defonce voices-a (ref ())) -(defonce voices-b (ref ())) - -(defn debug [& args] - (when false - (apply println args))) - -(defn update-ui-state [m] - (debug "update-ui-state " m) - (swap! ui-state (partial merge-with merge) {@selected-split m})) - -(defn update-control - "Control ui and synth parameters" - [control new-val] - ;; find the synth parameter this control controls - (let [control-name (:name control) - _ (update-ui-state {control-name new-val}) - synth-ctl-vals ((:synth-fn control) new-val) - _ (doall (map (fn [synth-ctl-val] - (let [synths ((first synth-ctl-val)) - synth-ctl (second synth-ctl-val) - synth-val (last synth-ctl-val)] - (ctl synths synth-ctl synth-val))) synth-ctl-vals))])) - -(def sub-osc-waveforms [:sub-osc-square :sub-osc-sin]) -(defn next-sub-osc-waveform [w] - "Given a sub-octave waveform, return the next waveform that can be selected." - (let [waveforms (cycle sub-osc-waveforms )] - (nth waveforms (inc (.indexOf waveforms w))))) - -(def lfo-waveforms [:lfo-sin :lfo-tri :lfo-saw :lfo-square :lfo-rand :lfo-slew-rand]) -(defn next-lfo-waveform [w] - "Given an lfo waveform, return the next waveform that can be selected." - (let [waveforms (cycle lfo-waveforms )] - (nth waveforms (inc (.indexOf waveforms w))))) - -(def filter-types [:low-pass :band-pass :high-pass :notch]) -(defn next-filter-type [m] - "Given a filter, return the next filter that can be selected." - (let [modes (cycle filter-types)] - (nth modes (inc (.indexOf modes m))))) - - -(def mod-wheel-fns [:cutoff :vibrato :lfo-amount]) -(defn next-mod-wheel-fn [f] - "Given mod wheel fn, return the next fn that can be selected." - (let [fns (cycle mod-wheel-fns)] - (nth fns (inc (.indexOf fns f))))) - -(def vibrato-fns [:trill-up :vibrato :trill-down]) -(defn next-vibrato-fn [f] - "Given vibrato fn, return the next fn that can be selected." - (let [fns (cycle vibrato-fns)] - (nth fns (inc (.indexOf fns f))))) - -(defn queue [q e] - "Add the element e at the end of the queue q." - (dosync - (alter q conj e))) - -(defn dequeue [q] - "Remove the least recently added element from the queue q." - (dosync - (let [e (first @q)] - (alter q rest) - e))) - -(defn remove-first-match [pred q] - "Remove the element satisfying (pred e) from the queue q." - (dosync - (let [s (group-by pred (ensure q)) - t (rest (s true)) ;; take off the head of the list of matched elements - f (s false)] ;; keep the unmatched elements - (ref-set q (concat t f)) - (first (s true))))) - -(defn getsynth [note voices] - "Find a free synth or reclaim an old one, add the note and synth to - the voices list and return the synth that was free or reclaimed." - (dosync - (if (>= (count @voices) voices-max) - (let [voice (dequeue voices) - new-voice (assoc voice :note note)] - (queue voices new-voice) - (:synth new-voice)) - (let [synth-voices (if (< note @split-note) synth-voices-a synth-voices-b) - first-unused-synth (first (difference (set synth-voices) - (map (fn [e] (:synth e)) @voices))) - new-voice {:synth first-unused-synth :note note}] - (queue voices new-voice) - (:synth new-voice))))) - - -;; Find a synth and turn it on. Turn off an old synth if we need to free one up. -(defn keydown [note velocity] - (dosync - (let [voices (if (< note @split-note) voices-a voices-b)] - (if-not (some #(= (:note %) note) @voices) - (let [synth (getsynth note voices)] - (debug "playing " note " with synth " (:id synth)) - ;; turn off the old note. Maybe this is a reused synth - (ctl synth :gate 0.0) - ;; turn on the new note - (ctl synth :note note) - (ctl synth :velocity velocity) - (ctl synth :gate 1.0)))))) - -;; Find a synth to turn off -(defn keyup [note] - (let [split (if (< note @split-note) :a :b) - voices (case split :a voices-a :b voices-b)] - (if-let [voice (remove-first-match #(= (:note %) note) voices)] - (ctl (:synth voice) :gate 0.0)))) - -(defn handle-control - "Typically called with an incoming MIDI control message." - [{{device-name :name} :device - note :note - chan :channel - vel :velocity - ts :timestamp - msg :msg - cmd :command - :as control-msg}] - ;; (debug control-msg) - (if (nil? @selected-control) - ;; lookup a control matching the device, channel, note, and cmd - (if-let [matched-control (@dev-chan-note-cmd->control - {:device-name device-name - :chan chan - :note note - :cmd cmd})] - (update-control matched-control vel)) - (do - (debug "assigning assoc " - {:device-name device-name :chan chan :note note :cmd cmd} - @selected-control) - ;; make the association between the midi event and the synth control - (swap! dev-chan-note-cmd->control - assoc - {:device-name device-name :chan chan :note note :cmd cmd} - @selected-control) - ;; clear the selected control value - (reset! selected-control nil)))) - -;; x,y screen postion of the control -;; type :knob, :slider, :selector -;; ui-hints map of hints to draw the control {pos-indicator? start-sym end-sym zero? caption} -;; synths a list of synths to control -;; ctl specifier of which synth parameter to control -;; f function to transform midi velocity value to synth param value -(defrecord Control [x y type ui-hints synths ctl f]) - -(defrecord AdvancedControl [x y type ui-hints name synth-fn ui-fn]) - -(defmacro do-transformation - "Saves current transformation, performs body, restores current - transformation on exit." - [& body] - `(do - (push-matrix) - ~@body - (pop-matrix))) - -(defmacro let-transformation - "Performs the bindings then performs do-transformation on the body" - [bindings & body] - `(let* ~(destructure bindings) - (do-transformation ~@body))) - -(def selected-tint [255 100 100]) - -(defn draw-key [x y key-color selected?] - (if selected? - (apply tint selected-tint) - (tint (color 255 255 255 255))) - (image (case key-color - :white (state :white-key-img) - :black (state :black-key-img)) - x y)) - -(defn draw-knob [x y amount selected? draw-foreground? draw-background? - pos-indicator? start-sym end-sym sym-dx sym-dy zero? - caption caption-dx caption-dy] - (let-transformation - [w2 (/ 50 2.0) - tl [(+ x w2) (+ y w2)] - knob-background-img (state :knob-background-img) - knob-img (state :knob-img)] - (apply translate tl) - (when draw-background? - (text-size 8) - (text-align :center) - (when pos-indicator? - (image knob-background-img (- 0 w2) (- 1 w2))) - (when-not (nil? start-sym) - ;; draw start sym - (text start-sym (- -23 (or sym-dx 0)) (+ 27 (or sym-dy 0)))) - (when-not (nil? end-sym) - ;; draw end sym - (text end-sym (+ 24 (or sym-dx 0)) (+ 27 (or sym-dy 0)))) - (when zero? - ;; draw zero - (text "0" 0 -27)) - (when-not (nil? caption) - (let [cdx (or caption-dx 0) - cdy (or caption-dy 0)] - ;; draw caption - (text caption (+ cdx 0) (+ cdy 30))))) - (when draw-foreground? - (rotate (- (* amount 0.038) 2.4)) - (translate (- w2) (- w2)) - (when selected? - (apply tint selected-tint)) - (image knob-img 0 0) - (tint 255 255 255)))) - -(defn draw-button [x y amount selected? draw-foreground? draw-background? caption caption-dx caption-dy] - "x: x position - y: y position - amount: not used" - (let [button-img (state :button-img)] - (when draw-background? - (when-not (nil? caption) - (let [cdx (or caption-dx 0) - cdy (or caption-dy 0)] - (text-size 8) - (text-align :center) - (text caption (+ 22 cdx x) (+ cdy y 46))))) - (when draw-foreground? - (when selected? - (apply tint selected-tint)) - (image button-img x y) - (tint 255 255 255)))) - -(defn draw-small-button [x y amount selected? draw-foreground? draw-background? caption caption-dx caption-dy] - "x: x position - y: y position - amount: not used" - (let [button-img (state :small-button-img)] - (when draw-background? - (when-not (nil? caption) - (let [cdx (or caption-dx 0) - cdy (or caption-dy 0)] - (text-size 8) - (text-align :center) - (text caption (+ 22 cdx x) (+ cdy y 30))))) - (when draw-foreground? - (when selected? - (apply tint selected-tint)) - (image button-img x y) - (tint 255 255 255)))) - -(defn draw-slider [x y amount selected? draw-foreground? draw-background? caption caption-dx caption-dy] - "x: x position - y: y position - amount: 0.0-127.0" - (let [slider-background-img (state :slider-background-img) - slider-img (state :slider-img)] - (when draw-background? - (image slider-background-img (- x 5) (- y 77)) - (when-not (nil? caption) - (let [cdx (or caption-dx 0) - cdy (or caption-dy 0)] - (text-size 8) - (text-align :center) - (text caption (+ 16 cdx x) (+ cdy y 40))))) - (when draw-foreground? - (when selected? - (apply tint selected-tint)) - (image slider-img x (+ y (* -0.6 amount))) - (tint 255 255 255)))) - -(defn draw-selector [x y pos selected? draw-foreground? draw-background? - caption caption-dx caption-dy] - "x: x position - y: y position - pos: y position offet" - (let [selector-background-img (state :selector-background-img) - selector-img (state :selector-img) ] - (when draw-background? - (image selector-background-img (+ x 1) (+ y 3)) - (when-not (nil? caption) - (let [cdx (or caption-dx 0) - cdy (or caption-dy 0)] - (text-size 8) - (text-align :center) - (text caption (+ 10 cdx x) (+ y cdy 44))))) - (when draw-foreground? - (when selected? - (apply tint selected-tint)) - (image selector-img x (+ y pos)) - (tint 255 255 255)))) - -(defn draw-wheel [x y amount selected? _ _ caption caption-dx caption-dy] - "x: x position on screen - y: y position on screen - amount: 0.0-127.0 - selected?: draw control in selected mode" - (let [wheel-dimple-background-img (state :wheel-dimple-background-img) - wheel-img (state :wheel-img) - wheel-dimple-img (state :wheel-dimple-img) - wheel-dimple-inv-img (state :wheel-dimple-inv-img)] - (when-not (nil? caption) - (let [cdx (or caption-dx 0) - cdy (or caption-dy 0)] - (text-size 8) - (text-align :center) - (text caption (+ 23 cdx x) (+ y cdy 144)))) - (let [tcs (if selected? - selected-tint - [255 255 255])] - (apply tint tcs) - (image wheel-img x y) - (let [dx (+ x 10) - dy (+ y 103 (* -0.71 amount)) - t (* 2 amount)] - (image wheel-dimple-background-img dx dy) - (apply tint (conj tcs t)) - (image wheel-dimple-img dx dy) - (apply tint (conj tcs (- 255 t))) - (image wheel-dimple-inv-img dx dy) - (tint 255 255 255 255))))) - -(defn draw-control [control draw-foreground? draw-background?] - (let [selected? (= (:name @selected-control) (:name control)) - ui-val (or ((:ui-fn control) (get (get @ui-state @selected-split) (:name control))) 0) - args (conj ((juxt :x :y #((:ui-fn %) ui-val)) control) selected? draw-foreground? draw-background?) - ui-hints (:ui-hints control)] - (case (:type control) - :knob (apply draw-knob (apply conj args - ((juxt :pos-indicator? :start-sym :end-sym :sym-dx :sym-dy - :zero? :caption :caption-dx :caption-dy) ui-hints))) - :button (apply draw-button (apply conj args ((juxt :caption :caption-dx :caption-dy) ui-hints))) - :small-button (apply draw-small-button (apply conj args ((juxt :caption :caption-dx :caption-dy) ui-hints))) - :slider (apply draw-slider (apply conj args ((juxt :caption :caption-dx :caption-dy) ui-hints))) - :selector (apply draw-selector (apply conj args ((juxt :caption :caption-dx :caption-dy) ui-hints))) - :wheel (apply draw-wheel (apply conj args ((juxt :caption :caption-dx :caption-dy) ui-hints)))) - (when-let [ui-aux-fn (-> control :ui-hints :ui-aux-fn )] - (ui-aux-fn)))) - -(defn control->advanced-control [control] - ;; AcvancedContol => [x y type ui-hints name synth-fn ui-fn] - (AdvancedControl. - (:x control) - (:y control) - (:type control) - (:ui-hints control) - (:ctl control) - (fn [val] [[(:synths control) (:ctl control) ((:f control) val)]]) - identity)) - -(defn mk-ui-hint-builder - [default] - (fn builder - ([caption] (builder caption {})) - ([caption m] - (merge default {:caption caption} m)))) - -(def mk-pos-only-knob - "Returns a ui-hints map which is a merger between m and the default - map" - (mk-ui-hint-builder - {:pos-indicator? true})) - -(def mk-plus-minus-knob - (mk-ui-hint-builder - {:pos-indicator? true :start-sym "-" :end-sym "+" :zero? true})) - -(defn selector-knob-label-pos - "Return [x y] position of label for selector knob. - x,y - position of knob. - i - index of label. 0...n-1" - [x y i] - [(+ x 20 (* -29 (Math/cos (+ (/ i 2.0) 1.5)))) - (+ y 22 (* -27 (Math/sin (+ (/ i 2.0) 1.5))))]) - - -(defn osc-controls [] - [(Control. 479 35 :knob (mk-pos-only-knob "Tri fold") synth-voices :tri-fold-thresh (fn [val] (+ (/ val -127.0) 1))) - (Control. 341 35 :knob (mk-pos-only-knob "Detune Amt") synth-voices :saw-detune-amp (fn [val] (/ val 127.0))) - (Control. 407 38 :knob (mk-pos-only-knob "Pulse Width" - {:start-sym "50%" - :end-sym "90%" - :sym-dy -5}) synth-voices :osc-square-pw (fn [val] (+ (/ val 255.0) 0.5))) - (Control. 341 102 :knob (mk-pos-only-knob "Detune Rate") synth-voices :saw-detune (fn [val] (/ val 8.0))) - (Control. 409 102 :knob (mk-plus-minus-knob "ENV Amt") synth-voices :osc-square-pw-env (fn [val] (- (/ val 64.0) 1.0))) - (Control. 479 102 :knob (mk-plus-minus-knob "ENV Amt") synth-voices :tri-fold-env (fn [val] (- (/ val 64.0) 1.0))) - ;; sub-octave osc waveform selector - (AdvancedControl. 290 46 :selector {:caption "WAVE" - :ui-aux-fn (fn [] (shape (state :square-shape) 310 51) - (shape (state :sin-shape) 310 65))} - :sub-osc-waveform - (fn [val] (let [old-waveform (:sub-osc-waveform @synth-state) - new-state (alter-state - #(assoc % :sub-osc-waveform - (if (zero? val) - ;; button press; switch to next waveform - (next-sub-osc-waveform (:sub-osc-waveform %)) - ;; knob or slider; calculate waveform - (sub-osc-waveforms (int (constrain (* 2.0 (/ val 127.0)) 0 1)))))) - new-waveform (:sub-osc-waveform new-state) - _ (update-ui-state {:sub-osc-waveform (case new-waveform - :sub-osc-square 1 - :sub-osc-sin 127)})] - ;; Toggle sub-osc waveform - [[synth-voices old-waveform 0] [synth-voices new-waveform (:sub-osc-amp @synth-state)]])) - (fn [val] (case (:sub-osc-waveform @synth-state) - :sub-osc-square 0 :sub-osc-sin 16 -10))) - ;; sub-osc octave selector - (AdvancedControl. 290 106 :selector {:caption "OCTAVE" - :ui-aux-fn (fn [] (text "-1" 315 119) - (text "-2" 315 135))} - :sub-osc-oct - (fn [val] (let [old-oct (:sub-osc-oct @synth-state) - new-state (alter-state - #(assoc % :sub-osc-oct - (if (zero? val) - ;; button press; switch to next oct - (case old-oct - 1 2 - 2 1) - ;; knob or slider; calculate waveform - ([1 2] (int (constrain (* 2.0 (/ val 127.0)) 0 1)))))) - new-oct (:sub-osc-oct new-state) - _ (update-ui-state {:sub-osc-oct (case new-oct - 1 1 - 2 127)})] - ;; Toggle sub-osc octave - [[synth-voices :sub-osc-coeff (case (:sub-osc-oct @synth-state) - 1 0.5 - 2 0.25)]])) - (fn [val] (case (:sub-osc-oct @synth-state) - 1 0 2 16)))]) - - -(defn filter-controls [] - [(Control. 545 35 :knob (mk-pos-only-knob "Cutoff") synth-voices :cutoff (fn [val] (* (- val 10) 100.0))) - (Control. 613 35 :knob (mk-pos-only-knob "Resonance") synth-voices :resonance (fn [val] (/ val 127.0))) - (Control. 545 102 :knob (mk-plus-minus-knob "ENV Amt") synth-voices :cutoff-env (fn [val] (* (- val 64.0) 200.0))) - (Control. 613 102 :knob (mk-plus-minus-knob "KBD Tracking" {:start-sym "0%" - :end-sym "200%" - :sym-dx 3 - :sym-dy -5}) - synth-voices :cutoff-tracking (fn [val] (/ val 64.0))) - ;; Filter type knob - (AdvancedControl. 670 35 :knob {:caption "Mode" - :ui-aux-fn #(do - (text-align :left) - (doall - (map-indexed - (fn [i e] (apply text e (selector-knob-label-pos 670 38 i))) - ["LP" "BP" "HP" "Notch"])) - (text-align :center))} - :filter-type - (fn [val] (let [old-mode (:filter-type @synth-state) - new-state (alter-state - #(assoc % :filter-type - (if (zero? val) - ;; button press; switch to next mode - (next-filter-type (:filter-type %)) - ;; knob or slider; calculate mode - (filter-types (int (* (/ val 128.0) (count filter-types))))))) - new-mode (:filter-type new-state) - _ (update-ui-state {:filter-type (case new-mode - :low-pass 1 - :band-pass 33 - :high-pass 65 - :notch 127)})] - ;; Toggle filter mode - [[synth-voices :filter-type (.indexOf filter-types new-mode)]])) - (fn [val] (case (:filter-type @synth-state) - :low-pass 60 :band-pass 72 :high-pass 83 :notch 96))) - ;; envelope speed selector - (AdvancedControl. 690 103 :selector {:caption "ENV Speed" - :ui-aux-fn (fn [] (text "Fast" 716 114) - (text "Slow" 716 130))} - :env-speed - (fn [val] (let [old-speed (:env-speed @synth-state) - new-state (alter-state - #(assoc % :env-speed - (if (zero? val) - ;; button press; switch to next sped - (case old-speed - :fast :slow - :slow :fast) - ;; knob or slider; calculate speed - ([:fast :slow] (int (constrain (* 2.0 (/ val 127.0)) 0 1)))))) - new-speed (:env-speed new-state) - _ (update-ui-state {:env-speed (case new-speed - :fast 1 - :slow 127)})] - ;; Toggle env-speed - [[synth-voices :env-speed (case (:env-speed @synth-state) - :fast 1 - :slow 10)]])) - (fn [val] (case (:env-speed @synth-state) - :fast 0 :slow 16)))]) - -(defn osc-mix-controls [] - [(Control. 320 265 :slider {} synth-voices :osc-saw (fn [val] (/ val 127.0))) - (Control. 360 265 :slider {} synth-voices :osc-square (fn [val] (/ val 127.0))) - (Control. 400 265 :slider {} synth-voices :osc-tri (fn [val] (/ val 127.0))) - (Control. 440 265 :slider {} synth-voices :osc-noise (fn [val] (/ val 127.0))) - (Control. 480 265 :slider {} synth-voices :osc-audio-in(fn [val] (/ val 127.0))) - ;; Sub-octave amount - (AdvancedControl. 280 265 :slider {} :sub-osc-amp - (fn [val] (let [new-state (alter-state #(assoc % :sub-osc-amp (/ val 127.0)))] - [[synth-voices (:sub-osc-waveform @synth-state) (/ val 127.0)]])) - (fn [val] (* 127.0 (:sub-osc-amp @synth-state))))]) - -(defn filter-asdr-controls [] - [(Control. 565 265 :slider {:caption "Attack"} synth-voices :filter-attack (fn [val] (/ (- (Math/pow 1.01 (* val 5.0)) 0.9) 12.0))) - (Control. 605 265 :slider {:caption "Decay"} synth-voices :filter-decay (fn [val] (/ (- (Math/pow 1.01 (* val 5.0)) 1.0) 12.0))) - (Control. 645 265 :slider {:caption "Sustain"} synth-voices :filter-sustain (fn [val] (/ val 127.0))) - (Control. 685 265 :slider {:caption "Release"} synth-voices :filter-release (fn [val] (/ (- (Math/pow 1.01 (* val 5.0)) 1.0) 12.0)))]) - - -(defn amp-adsr-controls [] - [(Control. 770 265 :slider {:caption "Attack"} synth-voices :amp-attack (fn [val] (/ (- (Math/pow 1.01 (* val 5.0)) 0.9) 12.0))) - (Control. 810 265 :slider {:caption "Decay"} synth-voices :amp-decay (fn [val] (/ (- (Math/pow 1.01 (* val 5.0)) 1.0) 12.0))) - (Control. 850 265 :slider {:caption "Sustain"} synth-voices :amp-sustain (fn [val] (/ val 127.0))) - (Control. 890 265 :slider {:caption "Release"} synth-voices :amp-release (fn [val] (/ (- (Math/pow 1.01 (* val 5.0)) 1.0) 12.0)))]) - -(defn misc-controls [] - [(Control. 338 398 :knob (mk-pos-only-knob "Glide") synth-voices :portamento (fn [val] (/ val 1270.0))) - (Control. 268 398 :knob (mk-pos-only-knob "Bend Range") synth-voices :bend-range (fn [val] (* 12.0 (/ val 127.0)))) - (Control. 408 398 :knob (mk-pos-only-knob "Rate") synth-voices :vibrato-rate (fn [val] (/ val 8.0))) - ;; Vibrato type selector - (AdvancedControl. 424 338 :selector {:ui-aux-fn (fn [] (shape (state :trill-up-shape) 445 345) - (shape (state :sin-shape) 445 355) - (shape (state :trill-down-shape) 445 365))} - :vibrato-fn - (fn [val] (let [old-fn (:vibrato-fn @synth-state) - new-state (alter-state - #(assoc % :vibrato-fn - (if (zero? val) - ;; button press; switch to next fn - (next-vibrato-fn old-fn) - ;; knob or slider; calc fn - (let [num-fns (count vibrato-fns)] - (vibrato-fns (int (constrain (* num-fns (/ val 127.0)) - 0 (dec num-fns)))))))) - new-fn (:vibrato-fn new-state) - _ (update-ui-state {:vibrato-fn (case new-fn - :trill-up 1 - :vibrato 70 - :trill-down 127)}) - mod-wheel-pos (:mod-wheel-pos @synth-state)] - (case (:vibrato-fn @synth-state) - :vibrato [[synth-voices :vibrato-amp (/ mod-wheel-pos 127.0)] - [synth-voices :vibrato-trill 0]] - :trill-up [[synth-voices :vibrato-amp 0.0] - [synth-voices :vibrato-trill (int (/ mod-wheel-pos 10))]] - :trill-down [[synth-voices :vibrato-amp 0.0] - [synth-voices :vibrato-trill (- (int (/ mod-wheel-pos 10)))]]))) - (fn [val] (case (:vibrato-fn @synth-state) - :vibrato 10 - :trill-up 0 - :trill-down 20))) - ;; Mod wheel function - (AdvancedControl. 281 335 :selector {:caption "MOD Wheel" - :ui-aux-fn (fn [] (text "Cutoff" 311 348) - (text "Vibrato" 312 358) - (text "LFOAmt" 313 368))} - :mod-wheel-fn - (fn [val] (let [old-fn (:mod-wheel-fn @synth-state) - new-state (alter-state - #(assoc % :mod-wheel-fn - (if (zero? val) - ;; button press; switch to next fn - (next-mod-wheel-fn old-fn) - ;; knob or slider; calc fn - (let [num-fns (count mod-wheel-fns)] - (mod-wheel-fns (int (constrain (* num-fns (/ val 127.0)) - 0 (dec num-fns)))))))) - new-fn (:mod-wheel-fn new-state) - _ (update-ui-state {:mod-wheel-fn (case new-fn - :cutoff 1 - :vibrato 65 - :lfo-amount 127)})] - [])) - (fn [val] (case (:mod-wheel-fn @synth-state) - :cutoff 0 :vibrato 10 :lfo-amount 20))) - ;; Show modulation panel - (AdvancedControl. 64 434 :small-button {:caption "Toggle panel"} - :toggle-modulation-controls - (fn [val] - (if (zero? val) - (swap! show-modulation-controls? not) - (reset! show-modulation-controls? (> val 64.0))) - []) - (fn [val] 0))]) - -(defn lfo-contols [] - [(Control. 546 398 :knob (mk-pos-only-knob "Rate") lfo-synth :lfo-rate - (fn [val] (/ (- (Math/pow 1.01 (* val 5.0)) 1.0) 3.0))) - - (Control. 475 332 :knob (mk-plus-minus-knob "PWM") synth-voices :lfo2pwm (fn [val] (- (/ val 32.0) 1.98))) - (Control. 544 332 :knob (mk-plus-minus-knob "Pitch") synth-voices :lfo2pitch (fn [val] (- (/ val 2.0) 32.0))) - (Control. 612 332 :knob (mk-plus-minus-knob "Filter") synth-voices :lfo2filter (fn [val] (* (- val 64.0) 400.0))) - (Control. 681 332 :knob (mk-plus-minus-knob "Amp" - {:caption-dx -25 - :caption-dy -55}) synth-voices :lfo2amp (fn [val] (- (/ val 32) 1.98))) - ;; LFO waveform knob - (AdvancedControl. 478 398 :knob {:caption "Wave" - :ui-aux-fn #(doall - (map-indexed - (fn [i e] (apply shape - (state e) - (selector-knob-label-pos 478 398 i))) - [:sin-shape :tri-shape :saw-shape :square-shape - :random-shape :random-slew-shape]))} - :lfo-waveform - (fn [val] (let [old-waveform (:lfo-waveform @synth-state) - new-state (alter-state - #(assoc % :lfo-waveform - (if (zero? val) - ;; button press; switch to next waveform - (next-lfo-waveform (:lfo-waveform %)) - ;; knob or slider; calculate waveform - (lfo-waveforms (int (* (/ val 128.0) (count lfo-waveforms))))))) - new-waveform (:lfo-waveform new-state) - _ (update-ui-state {:lfo-waveform (case new-waveform - :lfo-sin 1 - :lfo-tri 23 - :lfo-saw 45 - :lfo-square 67 - :lfo-rand 89 - :lfo-slew-rand 127)})] - ;; Toggle lfo waveform - [[lfo-synth old-waveform 0] [lfo-synth new-waveform (:lfo-amp @synth-state)]])) - (fn [val] (case (:lfo-waveform @synth-state) - :lfo-sin 60 :lfo-tri 75 :lfo-saw 89 :lfo-square 100 :lfo-rand 115 :lfo-slew-rand 130))) - ;; lfo-arp sync selector - (AdvancedControl. 620 398 :selector {:caption "Clock" - :ui-aux-fn (fn [] (text "Free" 645 410) - (text "Sync" 645 426))} - :lfo-arp-sync - (fn [val] (let [last-val (:lfo-arp-sync @synth-state) - new-state (alter-state - #(assoc % :lfo-arp-sync - (if (zero? val) - ;; button press; switch to next val - (case last-val - 0 1 - 1 0) - ;; knob or slider; calculate val - ([0 1] (int (constrain (* 2.0 (/ val 127.0)) 0 1)))))) - new-val (:lfo-arp-sync new-state) - _ (update-ui-state {:lfo-arp-sync (case new-val - 0 1 - 1 127)})] - ;; Toggle flag - [[lfo-synth :lfo-arp-sync new-val]])) - (fn [val] (case (:lfo-arp-sync @synth-state) - 0 0 1 16)))]) - -(defn arp-controls [] - [(Control. 885 332 :knob (mk-pos-only-knob "Tempo") arp-synth :arp-rate - (fn [val] (let [rate (/ val 5.0)] - (debug "arp-rate " (* (/ 60 8) rate) " bmp") - rate))) - (Control. 829 398 :knob (mk-pos-only-knob "Swing") arp-synth :arp-swing-phase (fn [val] (* 360 (/ val 127.0)))) - ;; Arp mode selector - (AdvancedControl. 742 398 :knob {:caption "Mode" - :ui-aux-fn #(do - (text-align :left) - (doall - (map-indexed - (fn [i e](apply text e (selector-knob-label-pos 743 400 i))) - ["Off" "Up" "Down" "Up/Dwn" "Rand"])) - (text-align :center))} - :arp-mode - (fn [val] (let [old-mode (:arp-mode @synth-state) - new-state (alter-state - #(assoc % :arp-mode - (if (zero? val) - ;; button press; switch to next mode - (mod (inc old-mode) 5) - ;; knob or slider; calculate mode - (int (constrain (* 5.0 (/ val 127.0)) 0 4))))) - new-mode (:arp-mode new-state) - _ (update-ui-state {:arp-mode (case new-mode - 0 1 - 1 30 - 2 56 - 3 82 - 4 127)}) - _ (debug "new-mode " new-mode)] - [[arp-synth :arp-mode new-mode]])) - (fn [val] (case (int (:arp-mode @synth-state)) - 0 62 1 75 2 85 3 98 4 109))) - - ;; Arp range selector - (AdvancedControl. 681 398 :knob {:caption "Octave" - :ui-aux-fn #(doall - (map-indexed - (fn [i e](apply text e (selector-knob-label-pos 686 403 i))) - ["1" "2" "3" "4"]))} - :arp-range - (fn [val] (let [old-range (:arp-range @synth-state) - new-state (alter-state - #(assoc % :arp-range - (if (zero? val) - ;; button press; switch to next range - (inc (mod old-range 4)) - ;; knob or slider; calculate range - (inc (int (* (/ (inc val) 129.0) 4)))))) - new-range (:arp-range new-state) - _ (update-ui-state {:arp-range (case new-range - 1 1 - 2 33 - 3 65 - 4 127)})] - [[arp-synth :arp-range new-range]])) - (fn [val] (case (:arp-range @synth-state) - 1 60 2 75 3 88 4 101))) - - ;; Arp step selector - (AdvancedControl. 810 336 :knob {:caption "Step" - :ui-aux-fn #(doall - (map-indexed - (fn [i e](apply text e (selector-knob-label-pos 820 340 i))) - ["1/4" "1/8" "1/16" "1/4T" "1/8T" "1/16T"]))} - :arp-step - (fn [val] (let [old-step (:arp-step @synth-state) - new-state (alter-state - #(assoc % :arp-step - (if (zero? val) - ;; button press; switch to next step - (mod (inc old-step) 6) - ;; knob or slider; calculate range - (int (* (/ (inc val) 129.0) 6))))) - new-step (:arp-step new-state) - _ (update-ui-state {:arp-step (case new-step - 0 1 - 1 23 - 2 45 - 3 67 - 4 89 - 5 127)})] - [[arp-synth :arp-step new-step]])) - (fn [val] (case (:arp-step @synth-state) - 0 65 1 75 2 84 3 98 4 111 5 125))) - ;; Arp tap tempo button - (AdvancedControl. 889 407 :button {:caption "Tap"} :arp-tap-tempo - (fn [val] - (let [dt (- (now) (:arp-tap-time @synth-state))] - (alter-state #(assoc % :arp-tap-time (now))) - (if (< dt 2000) - [[arp-synth :arp-rate (/ 1000 dt)]] - []))) - (fn [val] val))]) - -(defn volume-controls [] - [(Control. 885 38 :knob (mk-pos-only-knob "Master Volume") mb-synth :volume (fn [val] (/ val 12.0))) - (Control. 750 38 :knob (mk-pos-only-knob "Feedback Amt") synth-voices :feedback-amp (fn [val] (/ val 120.0)))]) - -(defn wheel-controls [] - ;; Ptch bend wheel - [(AdvancedControl. 100 165 :wheel {:caption "Pitch"} :pitch-wheel - ;; bend fn val:127->1.0 val:64->0.0 val:0->-1.0 - (fn [val] [[synth-voices :bend (- (* 2.0 (/ val 127.0)) 1.0)]]) - (fn [val] val)) - ;; Mod-wheel - (AdvancedControl. 170 165 :wheel {:caption "Modulation"} :mod-wheel - (fn [val] (alter-state #(assoc % :mod-wheel-pos val)) - (case (:mod-wheel-fn @synth-state) - :cutoff [[synth-voices :cutoff (* (- val 10) 100.0)]] - :vibrato (case (:vibrato-fn @synth-state) - :vibrato [[synth-voices :vibrato-amp (/ val 127.0)] - [synth-voices :vibrato-trill 0]] - :trill-up [[synth-voices :vibrato-amp 0.0] - [synth-voices :vibrato-trill (int (/ val 10))]] - :trill-down [[synth-voices :vibrato-amp 0.0] - [synth-voices :vibrato-trill (- (int (/ val 10)))]]) - :lfo-amount (do - (alter-state #(assoc % :lfo-amp (/ val 127.0))) - [[lfo-synth (:lfo-waveform @synth-state) (/ val 127.0)]]))) - (fn [val] val))]) - -(defn mod-controls [] - [(Control. 60 491 :knob (mk-pos-only-knob "Level") mb-synth :delay-mix (fn [val] (/ val 127.0))) - (Control. 130 491 :knob (mk-pos-only-knob "F.Back") mb-synth :delay-feedback (fn [val] (/ val 127.0))) - (Control. 200 491 :knob (mk-pos-only-knob "Time") mb-synth :delay-time (fn [val] (/ val 127.0))) - - (Control. 60 566 :knob (mk-pos-only-knob "Mix") mb-synth :reverb-mix (fn [val] (/ val 127.0))) - (Control. 130 566 :knob (mk-pos-only-knob "Size") mb-synth :reverb-size (fn [val] (/ val 127.0))) - (Control. 200 566 :knob (mk-pos-only-knob "Damp") mb-synth :reverb-damp (fn [val] (/ val 127.0))) - - (AdvancedControl. 70 652 :selector {:caption "Split Select" - :ui-aux-fn (fn [] (text "Left" 96 664) - (text "Right" 96 680))} - :selected-split - (fn [val] (do - (reset! selected-split - (if (zero? val) - ;; button press; switch to next mode - (case @selected-split - :a :b - :b :a) - ;; knob or slider; calculate mode - (let [splits [:a :b] - num-splits (count splits)] - (splits (int (constrain (* num-splits (/ val 127.0)) 0 (dec num-splits))))))) - [])) - (fn [val] (case @selected-split - :a 0 :b 16))) - (AdvancedControl. 120 642 :knob {:caption "Split Note" - :pos-indicator? true - :ui-aux-fn (fn [] (fill (color 0 0 0 255)) - (rect 180 658 40 24) - (fill (color 255 0 0 255)) - (text-size 16) - (text (str (find-note-name (note @split-note))) 199 676) - (text-size 8) - (fill (color 255 255 255 255)))} - :split-note - (fn [val] (do - (reset! split-note (int val)) - [])) - (fn [val] val))]) - -(defn octave-controls [] - [(AdvancedControl. 100 392 :small-button {:caption "Down"} :octave-down - (fn [val] (let [new-state (alter-state #(assoc % :octave-transpose (max -2 (dec (:octave-transpose %)))))] - [[synth-voices :octave-transpose (:octave-transpose new-state)]])) - (fn [val] 0)) - (AdvancedControl. 175 392 :small-button {:caption "Up"} :octave-up - (fn [val] (let [new-state (alter-state #(assoc % :octave-transpose (min 2 (inc (:octave-transpose %)))))] - [[synth-voices :octave-transpose (:octave-transpose new-state)]])) - (fn [val] 0))]) - -(defn get-mod-controls [] - (let [get-mod-controls-helper - (memoize (fn [] - (map (fn [c] (if (= (type c) Control) - (control->advanced-control c) - c)) (mod-controls))))] - (get-mod-controls-helper))) - -(defn get-controls - ([] - (get-controls @show-modulation-controls?)) - ([show-modulation-controls?] - (let [get-controls-helper - (memoize - (fn [show-mod-controls?] - (map (fn [c] (if (= (type c) Control) - (control->advanced-control c) - c)) - (concat (osc-controls) - (filter-controls) - (osc-mix-controls) - (filter-asdr-controls) - (amp-adsr-controls) - (misc-controls) - (lfo-contols) - (arp-controls) - (volume-controls) - (wheel-controls) - (octave-controls) - (if show-mod-controls? - (mod-controls) - [])))))] - (get-controls-helper show-modulation-controls?)))) - -;; populate the map with ALL the controls. -(def ctl->control (into {} (map (fn [e] {(:name e) e}) (get-controls true)))) - -(def ui-keys - [;; black keys - {:color :black :coords [101 472 136 700] :note :C#3} - {:color :black :coords [172 472 201 700] :note :D#3} - - {:color :black :coords [282 472 316 700] :note :F#3} - {:color :black :coords [349 472 382 700] :note :G#3} - {:color :black :coords [416 472 452 700] :note :A#3} - - {:color :black :coords [524 472 559 700] :note :C#4} - {:color :black :coords [596 472 634 700] :note :D#4} - - {:color :black :coords [704 472 744 700] :note :F#4} - {:color :black :coords [774 472 806 700] :note :G#4} - {:color :black :coords [841 472 875 700] :note :A#4} - - ;; white keys - {:color :white :coords [ 65 472 124 824] :note :C3} - {:color :white :coords [124 472 184 824] :note :D3} - {:color :white :coords [184 472 246 824] :note :E3} - {:color :white :coords [246 472 303 824] :note :F3} - {:color :white :coords [303 472 366 824] :note :G3} - {:color :white :coords [366 472 430 824] :note :A3} - {:color :white :coords [428 472 490 824] :note :B3} - {:color :white :coords [490 472 549 824] :note :C4} - {:color :white :coords [549 472 610 824] :note :D4} - {:color :white :coords [610 472 669 824] :note :E4} - {:color :white :coords [669 472 728 824] :note :F4} - {:color :white :coords [728 472 791 824] :note :G4} - {:color :white :coords [791 472 852 824] :note :A4} - {:color :white :coords [852 472 910 824] :note :B4} - {:color :white :coords [910 472 972 824] :note :C5}]) - -(defn apply-control-map-from-file [] - (let [extFilter (FileNameExtensionFilter. "Bindings (*.ctl)" (into-array ["ctl"])) - filechooser (doto (JFileChooser. "./presets") - (.setFileFilter extFilter)) - retval (.showOpenDialog filechooser nil)] - (if (= retval JFileChooser/APPROVE_OPTION) - (let [path (-> filechooser .getSelectedFile .getPath) - ctl-map (-> path slurp read-string)] - (reset! dev-chan-note-cmd->control - (into {} (map (fn [[k v]] [k (ctl->control v)]) ctl-map))))))) - -(defn save-control-map-to-file [] - (let [extFilter (FileNameExtensionFilter. "Bindings (*.ctl)" (into-array ["ctl"])) - filechooser (doto (JFileChooser. "./presets") - (.setFileFilter extFilter) - (.setSelectedFile (File. "Untitled.ctl"))) - retval (.showSaveDialog filechooser nil)] - (if (= retval JFileChooser/APPROVE_OPTION) - (let [path (-> filechooser .getSelectedFile .getPath) - out-obj (into {} (map (fn [[k v]] [k (:ctl v)]) @dev-chan-note-cmd->control))] - (->> out-obj pr-str (spit path)))))) - -(defn load-synth-settings-from-file [path] - (let [patch (-> path slurp read-string)] - ;; reset the ui and synth to pre-file-loaded values - (swap! ui-state merge {@selected-split {}}) - (reset-synth-defaults mbsynth) - (doall (map (fn [[k v]] - (debug "Setting " k) - (let [control (ctl->control k)] - ;; zero values are considered clicks. Just make them 1's instead. - (update-control control (max 1 v)))) patch)))) - -(defn apply-synth-settings-from-file [] - (let [extFilter (FileNameExtensionFilter. "Patch (*.patch)" (into-array ["patch"])) - filechooser (doto (JFileChooser. "./presets") - (.setFileFilter extFilter)) - retval (.showOpenDialog filechooser nil)] - (if (= retval JFileChooser/APPROVE_OPTION) - (let [path (-> filechooser .getSelectedFile .getPath)] - (load-synth-settings-from-file path))))) - -(defn save-synth-settings-to-file [] - (let [extFilter (FileNameExtensionFilter. "Patch (*.patch)" (into-array ["patch"])) - filechooser (doto (JFileChooser. "./presets") - (.setFileFilter extFilter) - (.setSelectedFile (File. "Untitled.patch"))) - retval (.showSaveDialog filechooser nil)] - (if (= retval JFileChooser/APPROVE_OPTION) - (let [path (-> filechooser .getSelectedFile .getPath)] - (->> (get @ui-state @selected-split) pr-str (spit path)))))) - -(defn menuitem-selected [event] - (case (.getActionCommand event) - "Save Bindings" (save-control-map-to-file) - "Open Bindings" (apply-control-map-from-file) - "Save Patch" (save-synth-settings-to-file) - "Open Patch" (apply-synth-settings-from-file))) - -(defn load-image [fname] - (PImageAWT. (ImageIO/read (io/resource fname)))) - -(defn setup [] - (smooth) - (frame-rate 10) - (background 0) - ;; load the most bad-est preset possible! - (load-synth-settings-from-file "./presets/way-huge.patch") - (reset! selected-split :b) - (load-synth-settings-from-file "./presets/bass.patch") - (reset! selected-split :a) - (set-state! :background-img (load-image "background.png") - :mod-panel-img (load-image "mod-panel.png") - :button-img (load-image "button.png") - :small-button-img (load-image "small-button.png") - :knob-img (load-image "knob.png") - :knob-background-img (load-image "knob-background.png") - :slider-img (load-image "slider.png") - :slider-background-img (load-image "slider-background.png") - :selector-img (load-image "selector.png") - :selector-background-img (load-image "selector-background.png") - :wheel-img (load-image "wheel.png") - :wheel-dimple-img (load-image "wheel-dimple.png") - :wheel-dimple-inv-img (load-image "wheel-dimple-inv.png") - :wheel-dimple-background-img (load-image "wheel-dimple-background.png") - :white-key-img (load-image "white-key.png") - :black-key-img (load-image "black-key.png") - :led-img (load-image "led.png") - :led-background-img (load-image "led-background.png") - :overtone-circle-img (load-image "overtone-circle.png") - :overtone-text-img (load-image "overtone-text.png") - :mini-beast-text-img (load-image "mini-beast-text.png") - :logo-img (load-image "logo.png") - :random-slew-shape (load-shape "random-slew.svg") - :random-shape (load-shape "random.svg") - :sin-shape (load-shape "sin.svg") - :square-shape (load-shape "square.svg") - :tri-shape (load-shape "tri.svg") - :saw-shape (load-shape "saw.svg") - :trill-down-shape (load-shape "trill-down.svg") - :trill-up-shape (load-shape "trill-up.svg"))) - -(defn draw [] - (let [background-img (state :background-img) - led-background-img (state :led-background-img) - led-img (state :led-img) - overtone-circle-img (state :overtone-circle-img) - overtone-text-img (state :overtone-text-img) - mini-beast-text-img (state :mini-beast-text-img) - logo-img (state :logo-img) - overtone-tint (color 253 0 147)] - (if @first-draw? - (let [draw-foreground? false - draw-background? true - tmp-background "tmp-background.png"] - (reset! first-draw? false) - (debug "--> Compositing background...") - (set-image 0 0 background-img) - (tint overtone-tint) - (image overtone-circle-img 65 20) - (tint 255 255 255) - (image overtone-text-img 65 20) - (image mini-beast-text-img 125 50) - (image logo-img 120 80) - (doall (map #(draw-control % draw-foreground? draw-background?) (get-controls false))) - (save (str "data/" tmp-background)) - (debug "--> Loading new background...") - (reset! composite-background-img (load-image tmp-background)))) - - (set-image 0 0 @composite-background-img) - ;; draw all keys - (doall (map (fn [k] (draw-key (first (:coords k)) - (second (:coords k)) - (:color k) - (some (fn [v] (= (:note v) (note (:note k)))) (concat @voices-a @voices-b)))) - (sort-by (fn [k] (case (:color k) - :white 0 - :black 1)) ui-keys))) - (tint (color 255 255 255 255)) - - ;; draw modulation panel - (when @show-modulation-controls? - ; draw background - (image (state :mod-panel-img) 50 472) - (doall (map #(draw-control % true true) (get-mod-controls)))) - - ;; draw regular controls - (let [draw-foreground? true - draw-background? false] - (doall (map #(draw-control % draw-foreground? draw-background?) (get-controls false)))) - - (let [lfo (or @(-> (lfo-synth) :taps :lfo) 0) - lfo-tint (color 255 0 0 (* 255 lfo)) - amp (apply max 0 (map (fn [s] @(-> s :taps :amp-adsr)) (concat synth-voices-a synth-voices-b))) - amp-tint (color 0 255 0 (* 255 amp)) - fil (apply max 0 (map (fn [s] @(-> s :taps :filter-adsr)) (concat synth-voices-a synth-voices-b))) - filter-tint (color 0 255 0 (* 255 fil)) - arp (or @(-> (arp-synth) :taps :arp) 0) - arp-tint (color 255 0 0 (* 255 arp)) - off-tint (color 65 65 65 255) - down-2-oct-tint (color 255 0 0 (if (= -2 (:octave-transpose @synth-state)) 255 66)) - down-1-oct-tint (color 255 255 0 (if (= -1 (:octave-transpose @synth-state)) 255 66)) - down-0-oct-tint (color 0 255 0 (if (= 0 (:octave-transpose @synth-state)) 255 66)) - up-1-oct-tint (color 255 255 0 (if (= 1 (:octave-transpose @synth-state)) 255 66)) - up-2-oct-tint (color 255 0 0 (if (= 2 (:octave-transpose @synth-state)) 255 66)) - draw-led (fn [x y t] - (tint off-tint) - (image led-background-img (+ 10 x) (+ 10 y)) - (tint t) - (image led-img x y))] - - (doall (map (partial apply draw-led) - [[590 388 lfo-tint] - [705 170 filter-tint] - [910 170 amp-tint] - [898 383 arp-tint] - [102 354 down-2-oct-tint] - [122 354 down-1-oct-tint] - [142 354 down-0-oct-tint] - [162 354 up-1-oct-tint] - [182 354 up-2-oct-tint]])) - (tint (color 255 255 255 255)) - (doall (map (fn [t] (apply text ((juxt :t :x :y) t))) [{:x 114 :y 360 :t "-2"} - {:x 134 :y 360 :t "-1"} - {:x 156 :y 360 :t "0"} - {:x 174 :y 360 :t "+1"} - {:x 194 :y 360 :t "+2"}]))))) - -(defn in-box? - "is point [x y] inside the box bounded by [u v] [s t]? - [u v] - +---------+ - | | - | + [x y] | - | | - +---------+ - [s t]" - [x y u v s t] - (and (> x u) - (> y v) - (< x s) - (< y t))) - - - -(defn key-note-at-xy - "Return the note of the key that occupies the point at (x y). - Nil if no key occupies the space." - [x y] - (if-let [k (first (filter (fn [k] (and (apply in-box? x y (:coords k)) - (if @show-modulation-controls? - (not (in-box? x y 49 470 263 711)) - true))) ui-keys))] - (note (:note k)) - nil)) - -(defn control-at-xy - "Return the first control that occupies the point at (x y). - Nil if no control occupies the space." - [x y] - (first (filter (fn [c] (case (:type c) - :knob (< (dist x y (+ (:x c) 25) (+ (:y c) 25)) 30) - :small-button (in-box? x y (:x c) (:y c) (+ (:x c) 43) (+ (:y c) 22)) - :button (in-box? x y (:x c) (:y c) (+ (:x c) 45) (+ (:y c) 37)) - :slider (in-box? x y (:x c) (- (:y c) 70) (+ (:x c) 26) (+ (:y c) 31)) - :selector (in-box? x y (:x c) (:y c) (+ (:x c) 14) (+ (:y c) 32)) - :wheel (in-box? x y (:x c) (:y c) (+ (:x c) 35) (+ (:y c) 137)))) - (get-controls)))) - -(defn mouse-clicked [] - ;; toggle selected-control on mouse click - (let [button (if @control-key-pressed - :right - (mouse-button))] - (debug button) - (debug "ctrl? " @control-key-pressed) - (case button - :left (when-let [matched-control (control-at-xy (mouse-x) (mouse-y))] - (update-control matched-control 0)) - :right (if (nil? @selected-control) - (let [x (mouse-x) - y (mouse-y) - c (control-at-xy x y)] - (debug "right click at [" x ", " y "]") - (debug "found control " c) - (reset! selected-control c)) - (reset! selected-control nil)) - nil))) - -(defn mouse-dragged [] - "For dragging controls around using mouse" - (when (= (mouse-button) :left) - (let [x (mouse-x) - y (mouse-y)] - (when-let [c (if (nil? @dragged-control) - (reset! dragged-control (control-at-xy x y)) - @dragged-control)] - ;; move sliders 1-to-1 with the ui (* 1/0.6) - ;; move selectors at an increased rate (8x) - (let [dy (* (case (:type c) - :slider (/ 1.0 0.6) - :selector -8.0 - 1.0) - (- y (pmouse-y))) - last-val (or (get (get @ui-state @selected-split) (:name c)) 0) - ;; constrain new-val to 1.0-127.0 - ;; don't actually get to zero because it is reserved for button presses - new-val (constrain (- last-val dy) 1.0 127.0)] - (debug "last-val " last-val " new-val " new-val) - (when (not-any? (:type c) [:button :small-button]) - (update-control c new-val))))))) - -;; keyboard key press using mouse -(defn mouse-pressed [] - (when-let [note (key-note-at-xy (mouse-x) (mouse-y))] - (debug "Playing " (find-note-name note)) - (reset! mouse-pressed-note note) - (keydown note 1.0))) - -;; stop dragging & keyup if over key -(defn mouse-released [] - (reset! dragged-control nil) - (when-let [note @mouse-pressed-note] - (keyup note))) - -(defn key-code->note [key-code] - (get ;; Row one - {\q (note :C3) - \2 (note :C#3) - \w (note :D3) - \3 (note :D#3) - \e (note :E3) - \r (note :F3) - \5 (note :F#3) - \t (note :G3) - \6 (note :G#3) - \y (note :A3) - \7 (note :A#3) - \u (note :B3) - \i (note :C4) - \9 (note :C#4) - \o (note :D4) - \0 (note :D#4) - \p (note :E4) - ;; Row two - \z (note :C2) - \s (note :C#2) - \x (note :D2) - \d (note :D#2) - \c (note :E2) - \v (note :F2) - \g (note :F#2) - \b (note :G2) - \h (note :G#2) - \n (note :A2) - \j (note :A#2) - \m (note :B2) - \, (note :C3) - \l (note :C#3) - \. (note :D3) - \; (note :D#3) - \/ (note :E3)} key-code)) - - -(defn key-pressed [] - (when-let [note (key-code->note (raw-key))] - (debug "Playing " (find-note-name note)) - (keydown note 1.0)) - (when (= java.awt.event.KeyEvent/VK_CONTROL (key-code)) - (debug "control pressed") - (reset! control-key-pressed true))) - -(defn key-released [] - (when-let [note (key-code->note (raw-key))] - (keyup note)) - (when (= java.awt.event.KeyEvent/VK_CONTROL (key-code)) - (debug "control released") - (reset! control-key-pressed false))) - -(defn close [] - (debug "--> Beast stopped...") - (kill-server)) - -(defn register-midi-handlers - [] - (on-sync-event [:midi :note-on] - (fn [{note :note velocity :velocity}] - (keydown note (/ velocity 127.0))) - ::note-on-handler) - - (on-sync-event [:midi :note-off] - (fn [{note :note}] - (keyup note)) - ::note-off-handler) - - (on-sync-event [:midi :control-change] - handle-control - ::ctl-event-handler) - - (on-sync-event [:midi :pitch-bend] - handle-control - ::bend-event-handler)) - -(defmacro when* [test & body] - (when test - ``(~~@body))) - -(defn start-gui - [] - (let [listener (proxy [ActionListener] [] - (actionPerformed [event] - (menuitem-selected event))) - mb (doto (JMenuBar.) - (.add (doto (JMenu. "File") - (.add (doto (JMenuItem. "Open Bindings") - (.addActionListener listener))) - (.add (doto (JMenuItem. "Save Bindings") - (.addActionListener listener))) - (.add (doto (JMenuItem. "Open Patch") - (.addActionListener listener))) - (.add (doto (JMenuItem. "Save Patch") - (.addActionListener listener)))))) - sk (sketch - :title "MiniBeast" - :setup #'setup - :draw #'draw - :mouse-clicked mouse-clicked - :mouse-dragged mouse-dragged - :mouse-pressed mouse-pressed - :mouse-released mouse-released - :key-pressed key-pressed - :key-released key-released - :on-close close - :decor true - :size [1036 850] - :state quil-state) - _ (def sk sk) - ;;frame (-> sk meta :target-obj deref) - icon (.createImage (java.awt.Toolkit/getDefaultToolkit) "data/icon.png")] - #_(doto frame - (.setJMenuBar mb) - (.setVisible true)) - #_(when* (mac-os?) - (import com.apple.eawt.Application) - (try - (.setDockIconImage (com.apple.eawt.Application/getApplication) icon) - (catch Exception e - false))))) - -(defn -main [& args] - (println "--> Starting The MiniBeast...") - (register-midi-handlers) - (start-gui) - (println "--> Beast started...")) - -(comment - - ) diff --git a/src/minibeast/core.clj b/src/minibeast/core.clj index 28102c9..f6fc317 100644 --- a/src/minibeast/core.clj +++ b/src/minibeast/core.clj @@ -1,22 +1,36 @@ (ns minibeast.core - (:import [javax.swing JFileChooser JMenuBar JMenu JMenuItem] - [javax.swing.filechooser FileNameExtensionFilter] - [java.awt.event ActionListener] - [java.io File] - [java.awt.Toolkit]) - (:use [overtone.live :exclude (mouse-button mouse-x mouse-y)] - [overtone.helpers.system :only [mac-os?]] - [quil.core :exclude [abs acos asin atan atan2 ceil cos - exp line log - ;; mouse-button mouse-x mouse-y - pow round scale sin sqrt tan triangle - TWO-PI]] - [minibeast.version :only [BEAST-VERSION-STR]] - [clojure.set :only [difference]] - [quil.applet] - [minibeast.mbsynth])) + (:require [quil.applet :as ap]) + (:use + [overtone.live :exclude [mouse-button mouse-x mouse-y rotate fill]] + [overtone.helpers.system :only [mac-os?]] + [quil.core :as q + :exclude [abs acos asin atan atan2 ceil cos + exp line log + ;; mouse-button mouse-x mouse-y + pow round scale sin sqrt tan triangle + TWO-PI floor clear clip load-image debug]] + [minibeast.version :only [BEAST-VERSION-STR]] + [clojure.set :only [difference]] + [quil.applet] + [minibeast.mbsynth]) + (:import + (javax.swing JFileChooser JMenuBar JMenu JMenuItem) + (javax.swing.filechooser FileNameExtensionFilter) + (java.awt.event ActionListener) + (java.io File) + (java.awt Toolkit) + javax.imageio.ImageIO + processing.awt.PImageAWT) + (:require [clojure.java.io :as io])) ;; Create some synth instruments to be used by voices. + +(def main-g (group)) +(def arp-g (group :head main-g)) +(def lfo-g (group :after arp-g)) +(def voice-g (group :after lfo-g)) +(def synth-g (group :after voice-g)) + (def lfo-bus-a (control-bus)) (def arp-trig-bus-a (control-bus)) (def arp-note-bus-a (control-bus)) @@ -27,35 +41,29 @@ (def arp-note-bus-b (control-bus)) (def voice-bus-b (audio-bus)) -(def arp-synth-a (darp :position :head arp-trig-bus-a arp-note-bus-a)) -(def arp-synth-b (darp :position :head arp-trig-bus-b arp-note-bus-b)) +(def arp-synth-a (darp [:tail arp-g] arp-trig-bus-a arp-note-bus-a)) +(def arp-synth-b (darp [:tail arp-g] arp-trig-bus-b arp-note-bus-b)) -(def lfo-synth-a (LFO :position :after - :target arp-synth-a - lfo-bus-a arp-trig-bus-a)) -(def lfo-synth-b (LFO :position :after - :target arp-synth-b - lfo-bus-b arp-trig-bus-b)) +(def lfo-synth-a (LFO [:tail lfo-g] lfo-bus-a arp-trig-bus-a)) +(def lfo-synth-b (LFO [:tail lfo-g] lfo-bus-b arp-trig-bus-b)) (def synth-voices-a - (doall (map (fn [_] (voice :position :after - :target lfo-synth-a + (doall (map (fn [_] (voice [:head voice-g] voice-bus-a lfo-bus-a arp-trig-bus-a arp-note-bus-a)) (range 4)))) (def synth-voices-b - (doall (map (fn [_] (voice :position :after - :target lfo-synth-b + (doall (map (fn [_] (voice [:head voice-g] voice-bus-b lfo-bus-b arp-trig-bus-b arp-note-bus-b)) (range 4)))) -(def mb-synth-a (mbsynth :position :tail voice-bus-a)) -(def mb-synth-b (mbsynth :position :tail voice-bus-b)) +(def mb-synth-a (mbsynth [:head synth-g] voice-bus-a)) +(def mb-synth-b (mbsynth [:head synth-g] voice-bus-b)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Internal synth state @@ -97,6 +105,7 @@ ;; to the operation of the ui of the synth. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defonce quil-state (atom {})) (defonce ui-state (atom {:a {} :b {}})) (defonce selected-control (atom nil)) (defonce dragged-control (atom nil)) @@ -135,8 +144,12 @@ (defonce voices-a (ref ())) (defonce voices-b (ref ())) +(defn debug [& args] + (when false + (apply println args))) + (defn update-ui-state [m] - (println "update-ui-state " m) + (debug "update-ui-state " m) (swap! ui-state (partial merge-with merge) {@selected-split m})) (defn update-control @@ -227,7 +240,7 @@ (let [voices (if (< note @split-note) voices-a voices-b)] (if-not (some #(= (:note %) note) @voices) (let [synth (getsynth note voices)] - (println "playing " note " with synth " (:id synth)) + (debug "playing " note " with synth " (:id synth)) ;; turn off the old note. Maybe this is a reused synth (ctl synth :gate 0.0) ;; turn on the new note @@ -252,7 +265,7 @@ msg :msg cmd :command :as control-msg}] - ;; (println control-msg) + ;; (debug control-msg) (if (nil? @selected-control) ;; lookup a control matching the device, channel, note, and cmd (if-let [matched-control (@dev-chan-note-cmd->control @@ -262,7 +275,7 @@ :cmd cmd})] (update-control matched-control vel)) (do - (println "assigning assoc " + (debug "assigning assoc " {:device-name device-name :chan chan :note note :cmd cmd} @selected-control) ;; make the association between the midi event and the synth control @@ -658,24 +671,24 @@ (Control. 408 398 :knob (mk-pos-only-knob "Rate") synth-voices :vibrato-rate (fn [val] (/ val 8.0))) ;; Vibrato type selector (AdvancedControl. 424 338 :selector {:ui-aux-fn (fn [] (shape (state :trill-up-shape) 445 345) - (shape (state :sin-shape) 445 355) - (shape (state :trill-down-shape) 445 365))} + (shape (state :sin-shape) 445 355) + (shape (state :trill-down-shape) 445 365))} :vibrato-fn (fn [val] (let [old-fn (:vibrato-fn @synth-state) new-state (alter-state - #(assoc % :vibrato-fn - (if (zero? val) - ;; button press; switch to next fn - (next-vibrato-fn old-fn) - ;; knob or slider; calc fn - (let [num-fns (count vibrato-fns)] - (vibrato-fns (int (constrain (* num-fns (/ val 127.0)) - 0 (dec num-fns)))))))) + #(assoc % :vibrato-fn + (if (zero? val) + ;; button press; switch to next fn + (next-vibrato-fn old-fn) + ;; knob or slider; calc fn + (let [num-fns (count vibrato-fns)] + (vibrato-fns (int (constrain (* num-fns (/ val 127.0)) + 0 (dec num-fns)))))))) new-fn (:vibrato-fn new-state) _ (update-ui-state {:vibrato-fn (case new-fn - :trill-up 1 - :vibrato 70 - :trill-down 127)}) + :trill-up 1 + :vibrato 70 + :trill-down 127)}) mod-wheel-pos (:mod-wheel-pos @synth-state)] (case (:vibrato-fn @synth-state) :vibrato [[synth-voices :vibrato-amp (/ mod-wheel-pos 127.0)] @@ -691,27 +704,27 @@ ;; Mod wheel function (AdvancedControl. 281 335 :selector {:caption "MOD Wheel" :ui-aux-fn (fn [] (text "Cutoff" 311 348) - (text "Vibrato" 312 358) - (text "LFOAmt" 313 368))} + (text "Vibrato" 312 358) + (text "LFOAmt" 313 368))} :mod-wheel-fn (fn [val] (let [old-fn (:mod-wheel-fn @synth-state) - new-state (alter-state - #(assoc % :mod-wheel-fn - (if (zero? val) - ;; button press; switch to next fn - (next-mod-wheel-fn old-fn) - ;; knob or slider; calc fn - (let [num-fns (count mod-wheel-fns)] - (mod-wheel-fns (int (constrain (* num-fns (/ val 127.0)) - 0 (dec num-fns)))))))) - new-fn (:mod-wheel-fn new-state) - _ (update-ui-state {:mod-wheel-fn (case new-fn - :cutoff 1 - :vibrato 65 - :lfo-amount 127)})] - [])) + new-state (alter-state + #(assoc % :mod-wheel-fn + (if (zero? val) + ;; button press; switch to next fn + (next-mod-wheel-fn old-fn) + ;; knob or slider; calc fn + (let [num-fns (count mod-wheel-fns)] + (mod-wheel-fns (int (constrain (* num-fns (/ val 127.0)) + 0 (dec num-fns)))))))) + new-fn (:mod-wheel-fn new-state) + _ (update-ui-state {:mod-wheel-fn (case new-fn + :cutoff 1 + :vibrato 65 + :lfo-amount 127)})] + [])) (fn [val] (case (:mod-wheel-fn @synth-state) - :cutoff 0 :vibrato 10 :lfo-amount 20))) + :cutoff 0 :vibrato 10 :lfo-amount 20))) ;; Show modulation panel (AdvancedControl. 64 434 :small-button {:caption "Toggle panel"} :toggle-modulation-controls @@ -730,66 +743,66 @@ (Control. 544 332 :knob (mk-plus-minus-knob "Pitch") synth-voices :lfo2pitch (fn [val] (- (/ val 2.0) 32.0))) (Control. 612 332 :knob (mk-plus-minus-knob "Filter") synth-voices :lfo2filter (fn [val] (* (- val 64.0) 400.0))) (Control. 681 332 :knob (mk-plus-minus-knob "Amp" - {:caption-dx -25 - :caption-dy -55}) synth-voices :lfo2amp (fn [val] (- (/ val 32) 1.98))) + {:caption-dx -25 + :caption-dy -55}) synth-voices :lfo2amp (fn [val] (- (/ val 32) 1.98))) ;; LFO waveform knob (AdvancedControl. 478 398 :knob {:caption "Wave" :ui-aux-fn #(doall - (map-indexed - (fn [i e] (apply shape - (state e) - (selector-knob-label-pos 478 398 i))) - [:sin-shape :tri-shape :saw-shape :square-shape - :random-shape :random-slew-shape]))} + (map-indexed + (fn [i e] (apply shape + (state e) + (selector-knob-label-pos 478 398 i))) + [:sin-shape :tri-shape :saw-shape :square-shape + :random-shape :random-slew-shape]))} :lfo-waveform (fn [val] (let [old-waveform (:lfo-waveform @synth-state) - new-state (alter-state - #(assoc % :lfo-waveform - (if (zero? val) - ;; button press; switch to next waveform - (next-lfo-waveform (:lfo-waveform %)) - ;; knob or slider; calculate waveform - (lfo-waveforms (int (* (/ val 128.0) (count lfo-waveforms))))))) - new-waveform (:lfo-waveform new-state) - _ (update-ui-state {:lfo-waveform (case new-waveform - :lfo-sin 1 - :lfo-tri 23 - :lfo-saw 45 - :lfo-square 67 - :lfo-rand 89 - :lfo-slew-rand 127)})] - ;; Toggle lfo waveform - [[lfo-synth old-waveform 0] [lfo-synth new-waveform (:lfo-amp @synth-state)]])) + new-state (alter-state + #(assoc % :lfo-waveform + (if (zero? val) + ;; button press; switch to next waveform + (next-lfo-waveform (:lfo-waveform %)) + ;; knob or slider; calculate waveform + (lfo-waveforms (int (* (/ val 128.0) (count lfo-waveforms))))))) + new-waveform (:lfo-waveform new-state) + _ (update-ui-state {:lfo-waveform (case new-waveform + :lfo-sin 1 + :lfo-tri 23 + :lfo-saw 45 + :lfo-square 67 + :lfo-rand 89 + :lfo-slew-rand 127)})] + ;; Toggle lfo waveform + [[lfo-synth old-waveform 0] [lfo-synth new-waveform (:lfo-amp @synth-state)]])) (fn [val] (case (:lfo-waveform @synth-state) - :lfo-sin 60 :lfo-tri 75 :lfo-saw 89 :lfo-square 100 :lfo-rand 115 :lfo-slew-rand 130))) + :lfo-sin 60 :lfo-tri 75 :lfo-saw 89 :lfo-square 100 :lfo-rand 115 :lfo-slew-rand 130))) ;; lfo-arp sync selector (AdvancedControl. 620 398 :selector {:caption "Clock" :ui-aux-fn (fn [] (text "Free" 645 410) - (text "Sync" 645 426))} + (text "Sync" 645 426))} :lfo-arp-sync (fn [val] (let [last-val (:lfo-arp-sync @synth-state) new-state (alter-state - #(assoc % :lfo-arp-sync - (if (zero? val) - ;; button press; switch to next val - (case last-val - 0 1 - 1 0) - ;; knob or slider; calculate val - ([0 1] (int (constrain (* 2.0 (/ val 127.0)) 0 1)))))) - new-val (:lfo-arp-sync new-state) - _ (update-ui-state {:lfo-arp-sync (case new-val - 0 1 - 1 127)})] - ;; Toggle flag - [[lfo-synth :lfo-arp-sync new-val]])) + #(assoc % :lfo-arp-sync + (if (zero? val) + ;; button press; switch to next val + (case last-val + 0 1 + 1 0) + ;; knob or slider; calculate val + ([0 1] (int (constrain (* 2.0 (/ val 127.0)) 0 1)))))) + new-val (:lfo-arp-sync new-state) + _ (update-ui-state {:lfo-arp-sync (case new-val + 0 1 + 1 127)})] + ;; Toggle flag + [[lfo-synth :lfo-arp-sync new-val]])) (fn [val] (case (:lfo-arp-sync @synth-state) - 0 0 1 16)))]) + 0 0 1 16)))]) (defn arp-controls [] [(Control. 885 332 :knob (mk-pos-only-knob "Tempo") arp-synth :arp-rate (fn [val] (let [rate (/ val 5.0)] - (println "arp-rate " (* (/ 60 8) rate) " bmp") + (debug "arp-rate " (* (/ 60 8) rate) " bmp") rate))) (Control. 829 398 :knob (mk-pos-only-knob "Swing") arp-synth :arp-swing-phase (fn [val] (* 360 (/ val 127.0)))) ;; Arp mode selector @@ -797,9 +810,9 @@ :ui-aux-fn #(do (text-align :left) (doall - (map-indexed - (fn [i e](apply text e (selector-knob-label-pos 743 400 i))) - ["Off" "Up" "Down" "Up/Dwn" "Rand"])) + (map-indexed + (fn [i e](apply text e (selector-knob-label-pos 743 400 i))) + ["Off" "Up" "Down" "Up/Dwn" "Rand"])) (text-align :center))} :arp-mode (fn [val] (let [old-mode (:arp-mode @synth-state) @@ -812,67 +825,67 @@ (int (constrain (* 5.0 (/ val 127.0)) 0 4))))) new-mode (:arp-mode new-state) _ (update-ui-state {:arp-mode (case new-mode - 0 1 - 1 30 - 2 56 - 3 82 - 4 127)}) - _ (println "new-mode " new-mode)] - [[arp-synth :arp-mode new-mode]])) + 0 1 + 1 30 + 2 56 + 3 82 + 4 127)}) + _ (debug "new-mode " new-mode)] + [[arp-synth :arp-mode new-mode]])) (fn [val] (case (int (:arp-mode @synth-state)) - 0 62 1 75 2 85 3 98 4 109))) + 0 62 1 75 2 85 3 98 4 109))) ;; Arp range selector (AdvancedControl. 681 398 :knob {:caption "Octave" :ui-aux-fn #(doall - (map-indexed - (fn [i e](apply text e (selector-knob-label-pos 686 403 i))) - ["1" "2" "3" "4"]))} + (map-indexed + (fn [i e](apply text e (selector-knob-label-pos 686 403 i))) + ["1" "2" "3" "4"]))} :arp-range (fn [val] (let [old-range (:arp-range @synth-state) new-state (alter-state - #(assoc % :arp-range - (if (zero? val) - ;; button press; switch to next range - (inc (mod old-range 4)) - ;; knob or slider; calculate range - (inc (int (* (/ (inc val) 129.0) 4)))))) - new-range (:arp-range new-state) - _ (update-ui-state {:arp-range (case new-range - 1 1 - 2 33 - 3 65 - 4 127)})] - [[arp-synth :arp-range new-range]])) + #(assoc % :arp-range + (if (zero? val) + ;; button press; switch to next range + (inc (mod old-range 4)) + ;; knob or slider; calculate range + (inc (int (* (/ (inc val) 129.0) 4)))))) + new-range (:arp-range new-state) + _ (update-ui-state {:arp-range (case new-range + 1 1 + 2 33 + 3 65 + 4 127)})] + [[arp-synth :arp-range new-range]])) (fn [val] (case (:arp-range @synth-state) - 1 60 2 75 3 88 4 101))) + 1 60 2 75 3 88 4 101))) ;; Arp step selector (AdvancedControl. 810 336 :knob {:caption "Step" :ui-aux-fn #(doall - (map-indexed - (fn [i e](apply text e (selector-knob-label-pos 820 340 i))) - ["1/4" "1/8" "1/16" "1/4T" "1/8T" "1/16T"]))} + (map-indexed + (fn [i e](apply text e (selector-knob-label-pos 820 340 i))) + ["1/4" "1/8" "1/16" "1/4T" "1/8T" "1/16T"]))} :arp-step (fn [val] (let [old-step (:arp-step @synth-state) new-state (alter-state - #(assoc % :arp-step - (if (zero? val) - ;; button press; switch to next step - (mod (inc old-step) 6) - ;; knob or slider; calculate range - (int (* (/ (inc val) 129.0) 6))))) - new-step (:arp-step new-state) - _ (update-ui-state {:arp-step (case new-step - 0 1 - 1 23 - 2 45 - 3 67 - 4 89 - 5 127)})] - [[arp-synth :arp-step new-step]])) + #(assoc % :arp-step + (if (zero? val) + ;; button press; switch to next step + (mod (inc old-step) 6) + ;; knob or slider; calculate range + (int (* (/ (inc val) 129.0) 6))))) + new-step (:arp-step new-state) + _ (update-ui-state {:arp-step (case new-step + 0 1 + 1 23 + 2 45 + 3 67 + 4 89 + 5 127)})] + [[arp-synth :arp-step new-step]])) (fn [val] (case (:arp-step @synth-state) - 0 65 1 75 2 84 3 98 4 111 5 125))) + 0 65 1 75 2 84 3 98 4 111 5 125))) ;; Arp tap tempo button (AdvancedControl. 889 407 :button {:caption "Tap"} :arp-tap-tempo (fn [val] @@ -921,36 +934,36 @@ (AdvancedControl. 70 652 :selector {:caption "Split Select" :ui-aux-fn (fn [] (text "Left" 96 664) - (text "Right" 96 680))} + (text "Right" 96 680))} :selected-split (fn [val] (do (reset! selected-split (if (zero? val) - ;; button press; switch to next mode - (case @selected-split - :a :b - :b :a) + ;; button press; switch to next mode + (case @selected-split + :a :b + :b :a) ;; knob or slider; calculate mode (let [splits [:a :b] num-splits (count splits)] (splits (int (constrain (* num-splits (/ val 127.0)) 0 (dec num-splits))))))) [])) (fn [val] (case @selected-split - :a 0 :b 16))) + :a 0 :b 16))) (AdvancedControl. 120 642 :knob {:caption "Split Note" :pos-indicator? true :ui-aux-fn (fn [] (fill (color 0 0 0 255)) - (rect 180 658 40 24) - (fill (color 255 0 0 255)) - (text-size 16) - (text (str (find-note-name (note @split-note))) 199 676) - (text-size 8) - (fill (color 255 255 255 255)))} - :split-note - (fn [val] (do - (reset! split-note (int val)) - [])) - (fn [val] val))]) + (rect 180 658 40 24) + (fill (color 255 0 0 255)) + (text-size 16) + (text (str (find-note-name (note @split-note))) 199 676) + (text-size 8) + (fill (color 255 255 255 255)))} + :split-note + (fn [val] (do + (reset! split-note (int val)) + [])) + (fn [val] val))]) (defn octave-controls [] [(AdvancedControl. 100 392 :small-button {:caption "Down"} :octave-down @@ -964,37 +977,37 @@ (defn get-mod-controls [] (let [get-mod-controls-helper - (memoize (fn [] - (map (fn [c] (if (= (type c) Control) - (control->advanced-control c) - c)) (mod-controls))))] + (memoize (fn [] + (map (fn [c] (if (= (type c) Control) + (control->advanced-control c) + c)) (mod-controls))))] (get-mod-controls-helper))) (defn get-controls ([] (get-controls @show-modulation-controls?)) ([show-modulation-controls?] - (let [get-controls-helper - (memoize - (fn [show-mod-controls?] - (map (fn [c] (if (= (type c) Control) - (control->advanced-control c) - c)) - (concat (osc-controls) - (filter-controls) - (osc-mix-controls) - (filter-asdr-controls) - (amp-adsr-controls) - (misc-controls) - (lfo-contols) - (arp-controls) - (volume-controls) - (wheel-controls) - (octave-controls) + (let [get-controls-helper + (memoize + (fn [show-mod-controls?] + (map (fn [c] (if (= (type c) Control) + (control->advanced-control c) + c)) + (concat (osc-controls) + (filter-controls) + (osc-mix-controls) + (filter-asdr-controls) + (amp-adsr-controls) + (misc-controls) + (lfo-contols) + (arp-controls) + (volume-controls) + (wheel-controls) + (octave-controls) (if show-mod-controls? - (mod-controls) - [])))))] - (get-controls-helper show-modulation-controls?)))) + (mod-controls) + [])))))] + (get-controls-helper show-modulation-controls?)))) ;; populate the map with ALL the controls. (def ctl->control (into {} (map (fn [e] {(:name e) e}) (get-controls true)))) @@ -1060,7 +1073,7 @@ (swap! ui-state merge {@selected-split {}}) (reset-synth-defaults mbsynth) (doall (map (fn [[k v]] - (println "Setting " k) + (debug "Setting " k) (let [control (ctl->control k)] ;; zero values are considered clicks. Just make them 1's instead. (update-control control (max 1 v)))) patch)))) @@ -1091,6 +1104,9 @@ "Save Patch" (save-synth-settings-to-file) "Open Patch" (apply-synth-settings-from-file))) +(defn load-image [fname] + (PImageAWT. (ImageIO/read (io/resource fname)))) + (defn setup [] (smooth) (frame-rate 10) @@ -1145,7 +1161,7 @@ draw-background? true tmp-background "tmp-background.png"] (reset! first-draw? false) - (println "--> Compositing background...") + (debug "--> Compositing background...") (set-image 0 0 background-img) (tint overtone-tint) (image overtone-circle-img 65 20) @@ -1154,8 +1170,8 @@ (image mini-beast-text-img 125 50) (image logo-img 120 80) (doall (map #(draw-control % draw-foreground? draw-background?) (get-controls false))) - (save tmp-background) - (println "--> Loading new background...") + (save (str "data/" tmp-background)) + (debug "--> Loading new background...") (reset! composite-background-img (load-image tmp-background)))) (set-image 0 0 @composite-background-img) @@ -1165,13 +1181,13 @@ (:color k) (some (fn [v] (= (:note v) (note (:note k)))) (concat @voices-a @voices-b)))) (sort-by (fn [k] (case (:color k) - :white 0 - :black 1)) ui-keys))) + :white 0 + :black 1)) ui-keys))) (tint (color 255 255 255 255)) ;; draw modulation panel (when @show-modulation-controls? - ; draw background + ; draw background (image (state :mod-panel-img) 50 472) (doall (map #(draw-control % true true) (get-mod-controls)))) @@ -1263,8 +1279,8 @@ (let [button (if @control-key-pressed :right (mouse-button))] - (println button) - (println "ctrl? " @control-key-pressed) + (debug button) + (debug "ctrl? " @control-key-pressed) (case button :left (when-let [matched-control (control-at-xy (mouse-x) (mouse-y))] (update-control matched-control 0)) @@ -1272,8 +1288,8 @@ (let [x (mouse-x) y (mouse-y) c (control-at-xy x y)] - (println "right click at [" x ", " y "]") - (println "found control " c) + (debug "right click at [" x ", " y "]") + (debug "found control " c) (reset! selected-control c)) (reset! selected-control nil)) nil))) @@ -1286,8 +1302,8 @@ (when-let [c (if (nil? @dragged-control) (reset! dragged-control (control-at-xy x y)) @dragged-control)] - ;; move sliders 1-to-1 with the ui (* 1/0.6) - ;; move selectors at an increased rate (8x) + ;; move sliders 1-to-1 with the ui (* 1/0.6) + ;; move selectors at an increased rate (8x) (let [dy (* (case (:type c) :slider (/ 1.0 0.6) :selector -8.0 @@ -1297,14 +1313,14 @@ ;; constrain new-val to 1.0-127.0 ;; don't actually get to zero because it is reserved for button presses new-val (constrain (- last-val dy) 1.0 127.0)] - (println "last-val " last-val " new-val " new-val) + (debug "last-val " last-val " new-val " new-val) (when (not-any? (:type c) [:button :small-button]) (update-control c new-val))))))) ;; keyboard key press using mouse (defn mouse-pressed [] (when-let [note (key-note-at-xy (mouse-x) (mouse-y))] - (println "Playing " (find-note-name note)) + (debug "Playing " (find-note-name note)) (reset! mouse-pressed-note note) (keydown note 1.0))) @@ -1355,21 +1371,21 @@ (defn key-pressed [] (when-let [note (key-code->note (raw-key))] - (println "Playing " (find-note-name note)) + (debug "Playing " (find-note-name note)) (keydown note 1.0)) (when (= java.awt.event.KeyEvent/VK_CONTROL (key-code)) - (println "control pressed") + (debug "control pressed") (reset! control-key-pressed true))) (defn key-released [] (when-let [note (key-code->note (raw-key))] (keyup note)) (when (= java.awt.event.KeyEvent/VK_CONTROL (key-code)) - (println "control released") + (debug "control released") (reset! control-key-pressed false))) (defn close [] - (println "--> Beast stopped...") + (debug "--> Beast stopped...") (kill-server)) (defn register-midi-handlers @@ -1413,8 +1429,8 @@ (.addActionListener listener)))))) sk (sketch :title "MiniBeast" - :setup setup - :draw draw + :setup #'setup + :draw #'draw :mouse-clicked mouse-clicked :mouse-dragged mouse-dragged :mouse-pressed mouse-pressed @@ -1423,22 +1439,27 @@ :key-released key-released :on-close close :decor true - :size [1036 850]) - frame (-> sk meta :target-obj deref) + :size [1036 850] + :state quil-state) + _ (def sk sk) + ;;frame (-> sk meta :target-obj deref) icon (.createImage (java.awt.Toolkit/getDefaultToolkit) "data/icon.png")] - (doto frame - (.setJMenuBar mb) - (.setVisible true)) - (when* (mac-os?) - (import com.apple.eawt.Application) - (try - (.setDockIconImage (com.apple.eawt.Application/getApplication) icon) - (catch Exception e - false))))) - -(defn -main - [& args] + #_(doto frame + (.setJMenuBar mb) + (.setVisible true)) + #_(when* (mac-os?) + (import com.apple.eawt.Application) + (try + (.setDockIconImage (com.apple.eawt.Application/getApplication) icon) + (catch Exception e + false))))) + +(defn -main [& args] (println "--> Starting The MiniBeast...") (register-midi-handlers) (start-gui) (println "--> Beast started...")) + +(comment + + ) From 5fe835562411b86ddd4c9420d784f0f2585bebd2 Mon Sep 17 00:00:00 2001 From: Arne Brasseur Date: Sat, 18 Nov 2023 14:51:40 +0100 Subject: [PATCH 3/9] Add CLI flags, more tweaks, document better --- .nrepl-port | 1 + README.md | 20 +++- deps.edn | 15 +-- src/minibeast/core.clj | 190 +++++++++++++++++++++------------ src/minibeast/mbsynth.clj | 216 ++++++++++++++++++-------------------- src/minibeast/version.clj | 2 +- 6 files changed, 255 insertions(+), 189 deletions(-) create mode 100644 .nrepl-port diff --git a/.nrepl-port b/.nrepl-port new file mode 100644 index 0000000..af0d42b --- /dev/null +++ b/.nrepl-port @@ -0,0 +1 @@ +41697 \ No newline at end of file diff --git a/README.md b/README.md index 30c1f29..3fb473a 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,14 @@ Digital clone of an analog synthesizer using Overtone and Quil MiniBeast Screenshot +## Prerequisites + +Install the [Clojure CLI tools](https://clojure.org/guides/install_clojure) + ## Running git clone https://github.com/overtone/mini-beast.git cd mini-beast - lein run + clojure -M -m minibeast.core --help ## Tips * Make sure your MIDI devices are connected before starting the beast @@ -16,6 +20,20 @@ Digital clone of an analog synthesizer using Overtone and Quil * The beast remembers your control bindings, so once you've bound one control, bind another! * You can save and load bindings and presets using the `File` menu. +## Troubleshooting + +If the embedded SuperCollider doesn't work for some reason, then install +SuperCollider manually (it's packaged for virtually any operating system). If +`scsynth` is on your path you can try `--sc-boot-external`, or start it yourself +(`scsynth -u 12345`), then connect to it with `--sc-udp-port 12345`. + +On Linux when using PipeWire it might help to run MiniBeast with `pw-jack` + +``` +scsynth -u 12345 +pw-jack clojure -M -m minibeast-core --sc-udp-port 12345 +``` + ## A call for patches MiniBeast loads a default patch, but we need your help! There is a world of sounds and timbres waiting to be explored. When you happen upon an unusual or interesting patch, please save it and share it. Pull requests work or post them to diff --git a/deps.edn b/deps.edn index 12022e7..5945b70 100644 --- a/deps.edn +++ b/deps.edn @@ -1,7 +1,10 @@ -{:paths ["src" "data"] +{:paths + ["src" "data" "presets"] + :deps - {org.clojure/clojure {:mvn/version "1.11.1"}, - overtone/overtone {:mvn/version "0.11.0"}, - quil/quil {:mvn/version "4.3.1323"}, - commons-collections/commons-collections {:mvn/version "20040616"}}, - :aliases {}} + {org.clojure/clojure {:mvn/version "1.11.1"} + overtone/overtone {:mvn/version "0.11.0" + #_#_:local/root "../overtone"} + quil/quil {:mvn/version "4.3.1323"} + commons-collections/commons-collections {:mvn/version "20040616"} + org.clojure/tools.cli {:mvn/version "1.0.219"}}} diff --git a/src/minibeast/core.clj b/src/minibeast/core.clj index f6fc317..881ceee 100644 --- a/src/minibeast/core.clj +++ b/src/minibeast/core.clj @@ -1,18 +1,18 @@ (ns minibeast.core - (:require [quil.applet :as ap]) + (:require [quil.applet :as ap] + [clojure.string :as str]) (:use - [overtone.live :exclude [mouse-button mouse-x mouse-y rotate fill]] [overtone.helpers.system :only [mac-os?]] [quil.core :as q :exclude [abs acos asin atan atan2 ceil cos exp line log ;; mouse-button mouse-x mouse-y pow round scale sin sqrt tan triangle - TWO-PI floor clear clip load-image debug]] + TWO-PI floor clear clip load-image debug fill rotate + mouse-x mouse-y mouse-button]] [minibeast.version :only [BEAST-VERSION-STR]] [clojure.set :only [difference]] - [quil.applet] - [minibeast.mbsynth]) + [quil.applet]) (:import (javax.swing JFileChooser JMenuBar JMenu JMenuItem) (javax.swing.filechooser FileNameExtensionFilter) @@ -21,7 +21,49 @@ (java.awt Toolkit) javax.imageio.ImageIO processing.awt.PImageAWT) - (:require [clojure.java.io :as io])) + (:require + [clojure.java.io :as io] + [clojure.tools.cli :as cli])) + +(binding [*out* (java.io.StringWriter.) + *err* (java.io.StringWriter.)] + (load "/overtone/api")) +((resolve 'overtone.api/immigrate-overtone-api)) + + +(def cli-opts + [["-x" "--sc-boot-external" "Boot a separate SuperCollider server process, instead of starting an embedded server."] + ["-u" "--sc-udp-port PORT" "Connect to an external SuperCollider server over UDP at the given port, instead of starting an embedded server." + :parse-fn parse-long] + ["-v" "--verbose" "Verbosity level" + :id :verbosity + :default 0 + :update-fn inc] + ["-h" "--help"]]) + +(defn banner [& args] + (println " -~~=::[ " (str/join " " args) " ]::=~~-")) + +(let [{:keys [options arguments summary errors]} + (cli/parse-opts *command-line-args* cli-opts)] + (when (or (:help options) + (seq arguments) + errors) + (println) + (banner "The MiniBeast") + (println) + (println summary) + (System/exit (if errors 1 0))) + (cond + (:sc-boot-external options) + (boot-external-server) + (:sc-udp-port options) + (connect-external-server (:sc-udp-port options)) + :else + (boot-internal-server))) + +;; We have to boot overtone before we can start defines synths +(use '[minibeast.mbsynth]) ;; Create some synth instruments to be used by voices. @@ -105,7 +147,6 @@ ;; to the operation of the ui of the synth. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defonce quil-state (atom {})) (defonce ui-state (atom {:a {} :b {}})) (defonce selected-control (atom nil)) (defonce dragged-control (atom nil)) @@ -143,9 +184,10 @@ (def voices-max 4) (defonce voices-a (ref ())) (defonce voices-b (ref ())) +(defonce verbosity (atom 0)) (defn debug [& args] - (when false + (when (< 0 @verbosity) (apply println args))) (defn update-ui-state [m] @@ -316,11 +358,11 @@ (defn draw-key [x y key-color selected?] (if selected? (apply tint selected-tint) - (tint (color 255 255 255 255))) + (tint 255 255)) (image (case key-color :white (state :white-key-img) :black (state :black-key-img)) - x y)) + x y)) (defn draw-knob [x y amount selected? draw-foreground? draw-background? pos-indicator? start-sym end-sym sym-dx sym-dy zero? @@ -351,12 +393,13 @@ ;; draw caption (text caption (+ cdx 0) (+ cdy 30))))) (when draw-foreground? - (rotate (- (* amount 0.038) 2.4)) + (q/rotate (- (* amount 0.038) 2.4)) (translate (- w2) (- w2)) - (when selected? - (apply tint selected-tint)) + (if selected? + (apply tint selected-tint) + (tint 255 255)) (image knob-img 0 0) - (tint 255 255 255)))) + (tint 255 255)))) (defn draw-button [x y amount selected? draw-foreground? draw-background? caption caption-dx caption-dy] "x: x position @@ -374,7 +417,7 @@ (when selected? (apply tint selected-tint)) (image button-img x y) - (tint 255 255 255)))) + (tint 255 255)))) (defn draw-small-button [x y amount selected? draw-foreground? draw-background? caption caption-dx caption-dy] "x: x position @@ -392,7 +435,7 @@ (when selected? (apply tint selected-tint)) (image button-img x y) - (tint 255 255 255)))) + (tint 255 255)))) (defn draw-slider [x y amount selected? draw-foreground? draw-background? caption caption-dx caption-dy] "x: x position @@ -412,7 +455,7 @@ (when selected? (apply tint selected-tint)) (image slider-img x (+ y (* -0.6 amount))) - (tint 255 255 255)))) + (tint 255 255)))) (defn draw-selector [x y pos selected? draw-foreground? draw-background? caption caption-dx caption-dy] @@ -433,7 +476,7 @@ (when selected? (apply tint selected-tint)) (image selector-img x (+ y pos)) - (tint 255 255 255)))) + (tint 255 255)))) (defn draw-wheel [x y amount selected? _ _ caption caption-dx caption-dy] "x: x position on screen @@ -952,13 +995,14 @@ :a 0 :b 16))) (AdvancedControl. 120 642 :knob {:caption "Split Note" :pos-indicator? true - :ui-aux-fn (fn [] (fill (color 0 0 0 255)) + :ui-aux-fn (fn [] + (q/fill 70) (rect 180 658 40 24) - (fill (color 255 0 0 255)) + (q/fill 255) (text-size 16) - (text (str (find-note-name (note @split-note))) 199 676) + (text (name (find-note-name (note @split-note))) 199 676) (text-size 8) - (fill (color 255 255 255 255)))} + (q/fill (color 255 255 255 255)))} :split-note (fn [val] (do (reset! split-note (int val)) @@ -1165,36 +1209,44 @@ (set-image 0 0 background-img) (tint overtone-tint) (image overtone-circle-img 65 20) - (tint 255 255 255) + (tint 255 255) (image overtone-text-img 65 20) (image mini-beast-text-img 125 50) (image logo-img 120 80) (doall (map #(draw-control % draw-foreground? draw-background?) (get-controls false))) + (q/fill 255) + (doseq [{:keys [t x y]} [{:x 114 :y 360 :t "-2"} + {:x 134 :y 360 :t "-1"} + {:x 156 :y 360 :t "0"} + {:x 174 :y 360 :t "+1"} + {:x 194 :y 360 :t "+2"}]] + (text t x y)) (save (str "data/" tmp-background)) (debug "--> Loading new background...") - (reset! composite-background-img (load-image tmp-background)))) + (reset! composite-background-img (load-image tmp-background)) + )) (set-image 0 0 @composite-background-img) ;; draw all keys - (doall (map (fn [k] (draw-key (first (:coords k)) - (second (:coords k)) - (:color k) - (some (fn [v] (= (:note v) (note (:note k)))) (concat @voices-a @voices-b)))) - (sort-by (fn [k] (case (:color k) - :white 0 - :black 1)) ui-keys))) - (tint (color 255 255 255 255)) + (doseq [k (sort-by (fn [k] (case (:color k) :white 0 :black 1)) ui-keys)] + (draw-key (first (:coords k)) + (second (:coords k)) + (:color k) + (some (fn [v] (= (:note v) (note (:note k)))) (concat @voices-a @voices-b)))) ;; draw modulation panel (when @show-modulation-controls? - ; draw background + ;; draw background + (tint 255 255) (image (state :mod-panel-img) 50 472) - (doall (map #(draw-control % true true) (get-mod-controls)))) + (doseq [ctl (get-mod-controls)] + (draw-control ctl true true))) ;; draw regular controls (let [draw-foreground? true draw-background? false] - (doall (map #(draw-control % draw-foreground? draw-background?) (get-controls false)))) + (doseq [ctl (get-controls false)] + (draw-control ctl draw-foreground? draw-background?))) (let [lfo (or @(-> (lfo-synth) :taps :lfo) 0) lfo-tint (color 255 0 0 (* 255 lfo)) @@ -1216,22 +1268,18 @@ (tint t) (image led-img x y))] - (doall (map (partial apply draw-led) - [[590 388 lfo-tint] - [705 170 filter-tint] - [910 170 amp-tint] - [898 383 arp-tint] - [102 354 down-2-oct-tint] - [122 354 down-1-oct-tint] - [142 354 down-0-oct-tint] - [162 354 up-1-oct-tint] - [182 354 up-2-oct-tint]])) - (tint (color 255 255 255 255)) - (doall (map (fn [t] (apply text ((juxt :t :x :y) t))) [{:x 114 :y 360 :t "-2"} - {:x 134 :y 360 :t "-1"} - {:x 156 :y 360 :t "0"} - {:x 174 :y 360 :t "+1"} - {:x 194 :y 360 :t "+2"}]))))) + (doseq [[x y led] [[590 388 lfo-tint] + [705 170 filter-tint] + [910 170 amp-tint] + [898 383 arp-tint] + [102 354 down-2-oct-tint] + [122 354 down-1-oct-tint] + [142 354 down-0-oct-tint] + [162 354 up-1-oct-tint] + [182 354 up-2-oct-tint]]] + (draw-led x y led)) + + ))) (defn in-box? "is point [x y] inside the box bounded by [u v] [s t]? @@ -1278,15 +1326,15 @@ ;; toggle selected-control on mouse click (let [button (if @control-key-pressed :right - (mouse-button))] + (q/mouse-button))] (debug button) (debug "ctrl? " @control-key-pressed) (case button - :left (when-let [matched-control (control-at-xy (mouse-x) (mouse-y))] + :left (when-let [matched-control (control-at-xy (q/mouse-x) (q/mouse-y))] (update-control matched-control 0)) :right (if (nil? @selected-control) - (let [x (mouse-x) - y (mouse-y) + (let [x (q/mouse-x) + y (q/mouse-y) c (control-at-xy x y)] (debug "right click at [" x ", " y "]") (debug "found control " c) @@ -1296,9 +1344,9 @@ (defn mouse-dragged [] "For dragging controls around using mouse" - (when (= (mouse-button) :left) - (let [x (mouse-x) - y (mouse-y)] + (when (= (q/mouse-button) :left) + (let [x (q/mouse-x) + y (q/mouse-y)] (when-let [c (if (nil? @dragged-control) (reset! dragged-control (control-at-xy x y)) @dragged-control)] @@ -1319,7 +1367,7 @@ ;; keyboard key press using mouse (defn mouse-pressed [] - (when-let [note (key-note-at-xy (mouse-x) (mouse-y))] + (when-let [note (key-note-at-xy (q/mouse-x) (q/mouse-y))] (debug "Playing " (find-note-name note)) (reset! mouse-pressed-note note) (keydown note 1.0))) @@ -1386,7 +1434,8 @@ (defn close [] (debug "--> Beast stopped...") - (kill-server)) + (kill-server) + (System/exit 0)) (defn register-midi-handlers [] @@ -1438,15 +1487,16 @@ :key-pressed key-pressed :key-released key-released :on-close close - :decor true :size [1036 850] - :state quil-state) - _ (def sk sk) + ) + _ (def sk sk) + ;; Hack, makes some assumptions about Processing internals + frame (.getFrame (.getNative (.getSurface sk))) ;;frame (-> sk meta :target-obj deref) icon (.createImage (java.awt.Toolkit/getDefaultToolkit) "data/icon.png")] - #_(doto frame - (.setJMenuBar mb) - (.setVisible true)) + (doto frame + (.setJMenuBar mb) + (.setVisible true)) #_(when* (mac-os?) (import com.apple.eawt.Application) (try @@ -1455,11 +1505,13 @@ false))))) (defn -main [& args] - (println "--> Starting The MiniBeast...") (register-midi-handlers) (start-gui) - (println "--> Beast started...")) + (banner "The MiniBeast is a go")) (comment + (-main) + + (:state (meta sk)) - ) + (System/exit 0)) diff --git a/src/minibeast/mbsynth.clj b/src/minibeast/mbsynth.clj index 60cbfbc..f236f94 100644 --- a/src/minibeast/mbsynth.clj +++ b/src/minibeast/mbsynth.clj @@ -1,30 +1,30 @@ (ns minibeast.mbsynth - (:use [overtone.live])) + (:use [overtone.core])) (defsynth darp - [arp-trig-bus {:default 1 :doc "bus to output arp trigger"} - arp-note-bus {:default 1 :doc "bus to output arp notes"} - arp-rate {:default 2.0 :doc "Rate of arpeggiation in Hz"} - arp-swing-phase {:default 0 :doc "phase offset of swung (swinged?) notes. Degrees."} - arp-range {:default 2 :doc "Octave range of arpeggiation"} - arp-mode {:default 0 :doc "0 = off, 1 = up, 2 = down, 3 = up/down, 4 = random"} - arp-step {:default 0 :doc "0 = 1/4, 1 = 1/8, 2 = 1/16, 3 = 1/4T, 4 = 1/8T 5 = 1/16T"}] - (let [arp-on? (min arp-mode 1) - rate (* arp-rate (select:kr arp-step [(dc:kr 1) (dc:kr 2) (dc:kr 4) (dc:kr 3) (dc:kr 6) (dc:kr 12)])) - arp-trig (+ (impulse (/ rate 2)) - (impulse (/ rate 2) (+ (* arp-swing-phase (/ 1 360.0)) 0.5))) - arp-tap (tap :arp 10 (lag-ud arp-trig 0 0.9)) - arp-scale-up (dseries 0 (dseq [ 7 5] INF) (+ (* arp-range 2) 1)) - arp-scale-down (dseries (* 12 arp-range) (dseq [-5 -7] INF) (+ (* arp-range 2) 1)) - arp-notes (dswitch1 [#_(donce 0) ;; "Donce , a demand-rate UGen with no identifiable purpose, is deprecated. It was most likely used in the production of electronic donce music." - (dseq arp-scale-up INF) - (dseq arp-scale-down INF) - ;; notes at the top and bottom repeat. May be due to - ;; http://goo.gl/iGVJb - (dswitch [(dser arp-scale-up (* 2 arp-range)) - (dser arp-scale-down (* 2 arp-range))] (dseq [0 1] INF)) - (dshuf arp-scale-up INF)] arp-mode) - arp-out (demand arp-trig 0 arp-notes)] + [arp-trig-bus {:default 1 :doc "bus to output arp trigger"} + arp-note-bus {:default 1 :doc "bus to output arp notes"} + arp-rate {:default 2.0 :doc "Rate of arpeggiation in Hz"} + arp-swing-phase {:default 0 :doc "phase offset of swung (swinged?) notes. Degrees."} + arp-range {:default 2 :doc "Octave range of arpeggiation"} + arp-mode {:default 0 :doc "0 = off, 1 = up, 2 = down, 3 = up/down, 4 = random"} + arp-step {:default 0 :doc "0 = 1/4, 1 = 1/8, 2 = 1/16, 3 = 1/4T, 4 = 1/8T 5 = 1/16T"}] + (let [arp-on? (min arp-mode 1) + rate (* arp-rate (select:kr arp-step [(dc:kr 1) (dc:kr 2) (dc:kr 4) (dc:kr 3) (dc:kr 6) (dc:kr 12)])) + arp-trig (+ (impulse (/ rate 2)) + (impulse (/ rate 2) (+ (* arp-swing-phase (/ 1 360.0)) 0.5))) + arp-tap (tap :arp 10 (lag-ud arp-trig 0 0.9)) + arp-scale-up (dseries 0 (dseq [ 7 5] INF) (+ (* arp-range 2) 1)) + arp-scale-down (dseries (* 12 arp-range) (dseq [-5 -7] INF) (+ (* arp-range 2) 1)) + arp-notes (dswitch1 [#_(donce 0) ;; "Donce , a demand-rate UGen with no identifiable purpose, is deprecated. It was most likely used in the production of electronic donce music." + (dseq arp-scale-up INF) + (dseq arp-scale-down INF) + ;; notes at the top and bottom repeat. May be due to + ;; http://goo.gl/iGVJb + (dswitch [(dser arp-scale-up (* 2 arp-range)) + (dser arp-scale-down (* 2 arp-range))] (dseq [0 1] INF)) + (dshuf arp-scale-up INF)] arp-mode) + arp-out (demand arp-trig 0 arp-notes)] (out:kr arp-trig-bus (* arp-on? arp-trig)) (out:kr arp-note-bus arp-out))) @@ -54,58 +54,55 @@ (lag-ud LFO 0 0.9))] (out lfo-bus LFO))) - (defsynth voice - [voice-bus {:default 0 :doc "bus to output voice"} - lfo-bus {:default 16 :doc "bus of lfo"} - arp-trig-bus {:default 17 :doc "bus of arp trigger"} - arp-note-bus {:default 17 :doc "bus of arp notes"} - note {:default 60 :doc "midi note value"} - bend {:default 0.0 :doc "-1 to 1"} - bend-range {:default 12.0 :doc "number of semitones of a maximum bend"} - velocity {:default 1.0 :doc "gain for the current note"} - octave-transpose {:default 0.0 :doc "number of octaves to transpose notes. -2 to 2"} - portamento {:default 0.0 :doc "rate to change to new note"} - gate {:default 0.0 :doc "ADSR trigger"} + [voice-bus {:default 0 :doc "bus to output voice"} + lfo-bus {:default 16 :doc "bus of lfo"} + arp-trig-bus {:default 17 :doc "bus of arp trigger"} + arp-note-bus {:default 17 :doc "bus of arp notes"} + note {:default 60 :doc "midi note value"} + bend {:default 0.0 :doc "-1 to 1"} + bend-range {:default 12.0 :doc "number of semitones of a maximum bend"} + velocity {:default 1.0 :doc "gain for the current note"} + octave-transpose {:default 0.0 :doc "number of octaves to transpose notes. -2 to 2"} + portamento {:default 0.0 :doc "rate to change to new note"} + gate {:default 0.0 :doc "ADSR trigger"} cutoff {:default 1000.0 :doc "cutoff frequency of the VCF"} - resonance {:default 1.0 :doc "resonance of the VCF"} - env-speed {:default 1.0 :doc "envelope speed modifier. Modifies ADR speeds by multiplying."} - filter-type {:default 0 :doc "0 = low pass, 1 = bandpass, 2 = highpass, 3 = notch"} - filter-attack {:default 0.0 :doc "Filter envelope attack"} - filter-decay {:default 0.0 :doc "Filter envelope decay"} - filter-sustain {:default 0.0 :doc "Filter envelope sustain"} - filter-release {:default 0.0 :doc "Filter envelope release"} - amp-attack {:default 0.1 :doc "Amp envelope attack"} - amp-decay {:default 0.2 :doc "Amp envelope decay"} - amp-sustain {:default 0.2 :doc "Amp envelope sustain"} - amp-release {:default 0.2 :doc "Amp envelope release"} - osc-saw {:default 1.0 :doc "osc saw amount"} - osc-square {:default 0.0 :doc "osc square amount"} - osc-tri {:default 0.0 :doc "osc triangle amount"} - osc-noise {:default 0.0 :doc "osc noise amount"} - osc-audio-in {:default 0.0 :doc "audio-in amount"} - sub-osc-sin {:default 0.0 :doc "sin sub-oscillator"} - sub-osc-square {:default 0.0 :doc "square sub-oscillator"} - sub-osc-coeff {:default 0.5 :doc "0.5 = -1 octave 0.25 = -2 octave"} - saw-detune {:default 1.0 :doc "phase offset of parallel saw oscs"} - saw-detune-amp {:default 1.0 :doc "amount of detuned saw osc in the mix"} - osc-square-pw {:default 0.5 :doc "square osc pulse width"} - osc-square-pw-env {:default 0.0 :doc "pw modulation by amp envelope"} - tri-fold-thresh {:default 1.0 :doc "fold threshold for triangle osc"} - tri-fold-env {:default 0.0 :doc "fold threshold env modulation for triangle osc"} - cutoff-env {:default 0.0 :doc "cutoff envelope modulation"} - cutoff-tracking {:default 1.0 :doc "keyboard tracking amount for filter cutoff"} - lfo2pitch {:default 0.0 :doc "LFO pitch modulation"} - lfo2filter {:default 0.0 :doc "LFO filter modulation"} - lfo2amp {:default 0.0 :doc "LFO amp modulation"} - lfo2pwm {:default 0.0 :doc "LFO PWM modulation"} - vibrato-rate {:default 0.0 :doc "vibrato rate Hz"} - vibrato-amp {:default 0.0 :doc "amount of vibrato modulation 0.0-1.0"} - vibrato-trill {:default 0.0 :doc "trill amount. 0 is no trill. 1 is trill up one half-step. -1 is trill down one half-step"} - feedback-amp {:default 0.0 :doc "feedback amount"} - ] - (let [ - arp-trig (in:kr arp-trig-bus 1) + resonance {:default 1.0 :doc "resonance of the VCF"} + env-speed {:default 1.0 :doc "envelope speed modifier. Modifies ADR speeds by multiplying."} + filter-type {:default 0 :doc "0 = low pass, 1 = bandpass, 2 = highpass, 3 = notch"} + filter-attack {:default 0.0 :doc "Filter envelope attack"} + filter-decay {:default 0.0 :doc "Filter envelope decay"} + filter-sustain {:default 0.0 :doc "Filter envelope sustain"} + filter-release {:default 0.0 :doc "Filter envelope release"} + amp-attack {:default 0.1 :doc "Amp envelope attack"} + amp-decay {:default 0.2 :doc "Amp envelope decay"} + amp-sustain {:default 0.2 :doc "Amp envelope sustain"} + amp-release {:default 0.2 :doc "Amp envelope release"} + osc-saw {:default 1.0 :doc "osc saw amount"} + osc-square {:default 0.0 :doc "osc square amount"} + osc-tri {:default 0.0 :doc "osc triangle amount"} + osc-noise {:default 0.0 :doc "osc noise amount"} + osc-audio-in {:default 0.0 :doc "audio-in amount"} + sub-osc-sin {:default 0.0 :doc "sin sub-oscillator"} + sub-osc-square {:default 0.0 :doc "square sub-oscillator"} + sub-osc-coeff {:default 0.5 :doc "0.5 = -1 octave 0.25 = -2 octave"} + saw-detune {:default 1.0 :doc "phase offset of parallel saw oscs"} + saw-detune-amp {:default 1.0 :doc "amount of detuned saw osc in the mix"} + osc-square-pw {:default 0.5 :doc "square osc pulse width"} + osc-square-pw-env {:default 0.0 :doc "pw modulation by amp envelope"} + tri-fold-thresh {:default 1.0 :doc "fold threshold for triangle osc"} + tri-fold-env {:default 0.0 :doc "fold threshold env modulation for triangle osc"} + cutoff-env {:default 0.0 :doc "cutoff envelope modulation"} + cutoff-tracking {:default 1.0 :doc "keyboard tracking amount for filter cutoff"} + lfo2pitch {:default 0.0 :doc "LFO pitch modulation"} + lfo2filter {:default 0.0 :doc "LFO filter modulation"} + lfo2amp {:default 0.0 :doc "LFO amp modulation"} + lfo2pwm {:default 0.0 :doc "LFO PWM modulation"} + vibrato-rate {:default 0.0 :doc "vibrato rate Hz"} + vibrato-amp {:default 0.0 :doc "amount of vibrato modulation 0.0-1.0"} + vibrato-trill {:default 0.0 :doc "trill amount. 0 is no trill. 1 is trill up one half-step. -1 is trill down one half-step"} + feedback-amp {:default 0.0 :doc "feedback amount"}] + (let [arp-trig (in:kr arp-trig-bus 1) arp-notes (in:kr arp-note-bus 1) gate-with-arp (- gate arp-trig) AMP-ADSR (env-gen (adsr (* amp-attack env-speed) @@ -145,47 +142,42 @@ (* sub-osc-square (square sub-note-freq (* LFO lfo2pwm))) (* osc-audio-in (sound-in))) - VCO+fback (+ VCO (* feedback-amp (local-in))) - vcf-freq (min 10000 (max 20 (+ cutoff - (* lfo2filter LFO) - (* cutoff-tracking note-freq) - (* cutoff-env FILTER-ADSR)))) - filter-bank [(rlpf:ar VCO+fback vcf-freq (- 1.0 resonance)) - (bpf:ar VCO+fback vcf-freq (- 1.1 resonance)) - (rhpf:ar VCO+fback vcf-freq (- 1.0 resonance)) - (brf:ar VCO+fback vcf-freq (- 1.0 resonance))] - VCF (select filter-type filter-bank) + VCO+fback (+ VCO (* feedback-amp (local-in))) + vcf-freq (min 10000 (max 20 (+ cutoff + (* lfo2filter LFO) + (* cutoff-tracking note-freq) + (* cutoff-env FILTER-ADSR)))) + filter-bank [(rlpf:ar VCO+fback vcf-freq (- 1.0 resonance)) + (bpf:ar VCO+fback vcf-freq (- 1.1 resonance)) + (rhpf:ar VCO+fback vcf-freq (- 1.0 resonance)) + (brf:ar VCO+fback vcf-freq (- 1.0 resonance))] + VCF (select filter-type filter-bank) - VIBRATO-LFO (+ 1 (* vibrato-amp (sin-osc:kr vibrato-rate))) - VCA (* (+ 1 (* lfo2amp LFO)) - AMP-ADSR - VIBRATO-LFO) - OUT (softclip (* velocity VCA VCF)) - _ (local-out OUT) - ] + VIBRATO-LFO (+ 1 (* vibrato-amp (sin-osc:kr vibrato-rate))) + VCA (* (+ 1 (* lfo2amp LFO)) + AMP-ADSR + VIBRATO-LFO) + OUT (softclip (* velocity VCA VCF)) + _ (local-out OUT)] (out voice-bus OUT))) (defsynth mbsynth - [ - voice-bus {:default 18 :doc "bus of voices"} - volume {:default 1.0 :doc "gain of the output"} - reverb-mix {:default 0.5 :doc "wet-dry mix"} - reverb-size {:default 0.3 :doc "reverb room size"} - reverb-damp {:default 0.8 :doc "reverb dampening"} - delay-mix {:default 0.0 :doc "wet-dry mix"} - delay-feedback {:default 0.2 :doc "amount of delay to feedback into delay loop. 0..1"} - delay-time {:default 0.8 :doc "delay time in seconds"} - ] - (let [ - voices (in voice-bus 1) - delay-in (local-in) - mix (* volume voices) - delay-sig (delay-n (+ mix - (* delay-feedback delay-in)) - 1.0 delay-time) - delay-mix (softclip (+ (* delay-mix delay-sig) mix)) - OUT (free-verb delay-mix reverb-mix reverb-size reverb-damp) - _ (local-out delay-sig) - ] + [voice-bus {:default 18 :doc "bus of voices"} + volume {:default 1.0 :doc "gain of the output"} + reverb-mix {:default 0.5 :doc "wet-dry mix"} + reverb-size {:default 0.3 :doc "reverb room size"} + reverb-damp {:default 0.8 :doc "reverb dampening"} + delay-mix {:default 0.0 :doc "wet-dry mix"} + delay-feedback {:default 0.2 :doc "amount of delay to feedback into delay loop. 0..1"} + delay-time {:default 0.8 :doc "delay time in seconds"}] + (let [voices (in voice-bus 1) + delay-in (local-in) + mix (* volume voices) + delay-sig (delay-n (+ mix + (* delay-feedback delay-in)) + 1.0 delay-time) + delay-mix (softclip (+ (* delay-mix delay-sig) mix)) + OUT (free-verb delay-mix reverb-mix reverb-size reverb-damp) + _ (local-out delay-sig)] (out 0 (pan2 OUT)))) diff --git a/src/minibeast/version.clj b/src/minibeast/version.clj index 6b19828..3479b1e 100644 --- a/src/minibeast/version.clj +++ b/src/minibeast/version.clj @@ -1,7 +1,7 @@ (ns minibeast.version) (def BEAST-VERSION {:major 0 - :minor 0 + :minor 1 :patch 1 :snapshot true}) From 28b0f54d17fbd73398f3df8b252976f9e761fed3 Mon Sep 17 00:00:00 2001 From: Arne Brasseur Date: Sat, 18 Nov 2023 14:52:03 +0100 Subject: [PATCH 4/9] remove .nrepl-port; --- .nrepl-port | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .nrepl-port diff --git a/.nrepl-port b/.nrepl-port deleted file mode 100644 index af0d42b..0000000 --- a/.nrepl-port +++ /dev/null @@ -1 +0,0 @@ -41697 \ No newline at end of file From b315addc1fc1b3125fce778a953500089c908781 Mon Sep 17 00:00:00 2001 From: Arne Brasseur Date: Sat, 18 Nov 2023 14:52:16 +0100 Subject: [PATCH 5/9] .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bd72ffd..3eb11c1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ native hs_err*.log tmp_background.png tmp-background.png +.nrepl-port From 94ef1a3d83cb73a69b1f4322f71056396a42844e Mon Sep 17 00:00:00 2001 From: Arne Brasseur Date: Sun, 19 Nov 2023 09:44:19 +0100 Subject: [PATCH 6/9] Fix colors, unpack presets There were two problems that were messing up the colors - the tint method in quil had a bug in the rgba arity - quil no longer allows passing a `color` to `tint` Tint is also stateful, making it easy for an earlier setting to "bleed" into other parts of the code. This introduces a `with-fill` macro to keep the fill setting local. When running not directly from source, MiniBeast crashes cause it can't find the presets. Unpack them on first run. Allow passing presets on the CLI --- src/minibeast/core.clj | 230 ++++++++++++++++++++++++----------------- 1 file changed, 134 insertions(+), 96 deletions(-) diff --git a/src/minibeast/core.clj b/src/minibeast/core.clj index 881ceee..706be94 100644 --- a/src/minibeast/core.clj +++ b/src/minibeast/core.clj @@ -6,6 +6,7 @@ [quil.core :as q :exclude [abs acos asin atan atan2 ceil cos exp line log + tint ;; mouse-button mouse-x mouse-y pow round scale sin sqrt tan triangle TWO-PI floor clear clip load-image debug fill rotate @@ -25,11 +26,14 @@ [clojure.java.io :as io] [clojure.tools.cli :as cli])) -(binding [*out* (java.io.StringWriter.) - *err* (java.io.StringWriter.)] - (load "/overtone/api")) -((resolve 'overtone.api/immigrate-overtone-api)) +(defonce verbosity (atom 0)) +(defonce __load_overtone + (do + (binding [*out* (java.io.StringWriter.) + *err* (java.io.StringWriter.)] + (load "/overtone/api")) + ((resolve 'overtone.api/immigrate-overtone-api)))) (def cli-opts [["-x" "--sc-boot-external" "Boot a separate SuperCollider server process, instead of starting an embedded server."] @@ -41,26 +45,33 @@ :update-fn inc] ["-h" "--help"]]) +(def preset-dir (io/file (System/getProperty "user.dir") "presets")) + (defn banner [& args] (println " -~~=::[ " (str/join " " args) " ]::=~~-")) -(let [{:keys [options arguments summary errors]} - (cli/parse-opts *command-line-args* cli-opts)] - (when (or (:help options) - (seq arguments) - errors) - (println) - (banner "The MiniBeast") - (println) - (println summary) - (System/exit (if errors 1 0))) - (cond - (:sc-boot-external options) - (boot-external-server) - (:sc-udp-port options) - (connect-external-server (:sc-udp-port options)) - :else - (boot-internal-server))) +(defonce __handle-cli-args + (let [{:keys [options arguments summary errors]} + (cli/parse-opts *command-line-args* cli-opts)] + (when (or (:help options) + errors) + (println) + (banner "The MiniBeast") + (println "mini-beast [options] [patch-a] [patch-b]") + (println) + (println summary) + (System/exit (if errors 1 0))) + (let [[a b] arguments] + (def patch-a (or a (io/file preset-dir "way-huge.patch"))) + (def patch-b (or b (io/file preset-dir "bass.patch")))) + (reset! verbosity (:verbose options)) + (cond + (:sc-boot-external options) + (boot-external-server) + (:sc-udp-port options) + (connect-external-server (:sc-udp-port options)) + :else + (boot-internal-server)))) ;; We have to boot overtone before we can start defines synths (use '[minibeast.mbsynth]) @@ -184,7 +195,6 @@ (def voices-max 4) (defonce voices-a (ref ())) (defonce voices-b (ref ())) -(defonce verbosity (atom 0)) (defn debug [& args] (when (< 0 @verbosity) @@ -351,18 +361,35 @@ "Performs the bindings then performs do-transformation on the body" [bindings & body] `(let* ~(destructure bindings) - (do-transformation ~@body))) + (do-transformation ~@body))) (def selected-tint [255 100 100]) +(def white-tint [255 255]) +(def overtone-tint [255 0 147]) + +;; Can be removed when https://github.com/quil/quil/pull/397 is released +(defn tint + ([gray] (q/tint gray)) + ([gray alpha] (q/tint gray alpha)) + ([r g b] (q/tint r g b)) + ([r g b a] (.tint (q/current-graphics) (float r) (float g) (float b) (float a)))) + +;; Tint stack, so we can actually scope it. Quil doesn't give us a way to read the tint. +(def ^:dynamic *current-tint* white-tint) + +(defmacro with-tint [new-tint & body] + `(let [new-tint# ~new-tint] + (binding [*current-tint* new-tint#] + (apply tint new-tint#) + ~@body) + (apply tint *current-tint*))) (defn draw-key [x y key-color selected?] - (if selected? - (apply tint selected-tint) - (tint 255 255)) - (image (case key-color - :white (state :white-key-img) - :black (state :black-key-img)) - x y)) + (with-tint (if selected? selected-tint white-tint) + (image (case key-color + :white (state :white-key-img) + :black (state :black-key-img)) + x y))) (defn draw-knob [x y amount selected? draw-foreground? draw-background? pos-indicator? start-sym end-sym sym-dx sym-dy zero? @@ -395,11 +422,8 @@ (when draw-foreground? (q/rotate (- (* amount 0.038) 2.4)) (translate (- w2) (- w2)) - (if selected? - (apply tint selected-tint) - (tint 255 255)) - (image knob-img 0 0) - (tint 255 255)))) + (with-tint (if selected? selected-tint white-tint) + (image knob-img 0 0))))) (defn draw-button [x y amount selected? draw-foreground? draw-background? caption caption-dx caption-dy] "x: x position @@ -414,10 +438,8 @@ (text-align :center) (text caption (+ 22 cdx x) (+ cdy y 46))))) (when draw-foreground? - (when selected? - (apply tint selected-tint)) - (image button-img x y) - (tint 255 255)))) + (with-tint (if selected? selected-tint white-tint) + (image button-img x y))))) (defn draw-small-button [x y amount selected? draw-foreground? draw-background? caption caption-dx caption-dy] "x: x position @@ -432,15 +454,14 @@ (text-align :center) (text caption (+ 22 cdx x) (+ cdy y 30))))) (when draw-foreground? - (when selected? - (apply tint selected-tint)) - (image button-img x y) - (tint 255 255)))) + (with-tint (if selected? selected-tint white-tint) + (image button-img x y))))) -(defn draw-slider [x y amount selected? draw-foreground? draw-background? caption caption-dx caption-dy] +(defn draw-slider "x: x position - y: y position - amount: 0.0-127.0" + y: y position + amount: 0.0-127.0" + [x y amount selected? draw-foreground? draw-background? caption caption-dx caption-dy] (let [slider-background-img (state :slider-background-img) slider-img (state :slider-img)] (when draw-background? @@ -452,16 +473,15 @@ (text-align :center) (text caption (+ 16 cdx x) (+ cdy y 40))))) (when draw-foreground? - (when selected? - (apply tint selected-tint)) - (image slider-img x (+ y (* -0.6 amount))) - (tint 255 255)))) + (with-tint (if selected? selected-tint white-tint) + (image slider-img x (+ y (* -0.6 amount))))))) -(defn draw-selector [x y pos selected? draw-foreground? draw-background? - caption caption-dx caption-dy] +(defn draw-selector "x: x position - y: y position - pos: y position offet" + y: y position + pos: y position offet" + [x y pos selected? draw-foreground? draw-background? + caption caption-dx caption-dy] (let [selector-background-img (state :selector-background-img) selector-img (state :selector-img) ] (when draw-background? @@ -473,16 +493,15 @@ (text-align :center) (text caption (+ 10 cdx x) (+ y cdy 44))))) (when draw-foreground? - (when selected? - (apply tint selected-tint)) - (image selector-img x (+ y pos)) - (tint 255 255)))) + (with-tint (if selected? selected-tint white-tint) + (image selector-img x (+ y pos)))))) -(defn draw-wheel [x y amount selected? _ _ caption caption-dx caption-dy] +(defn draw-wheel "x: x position on screen y: y position on screen amount: 0.0-127.0 selected?: draw control in selected mode" + [x y amount selected? _ _ caption caption-dx caption-dy] (let [wheel-dimple-background-img (state :wheel-dimple-background-img) wheel-img (state :wheel-img) wheel-dimple-img (state :wheel-dimple-img) @@ -505,8 +524,7 @@ (apply tint (conj tcs t)) (image wheel-dimple-img dx dy) (apply tint (conj tcs (- 255 t))) - (image wheel-dimple-inv-img dx dy) - (tint 255 255 255 255))))) + (image wheel-dimple-inv-img dx dy))))) (defn draw-control [control draw-foreground? draw-background?] (let [selected? (= (:name @selected-control) (:name control)) @@ -515,14 +533,14 @@ ui-hints (:ui-hints control)] (case (:type control) :knob (apply draw-knob (apply conj args - ((juxt :pos-indicator? :start-sym :end-sym :sym-dx :sym-dy - :zero? :caption :caption-dx :caption-dy) ui-hints))) + ((juxt :pos-indicator? :start-sym :end-sym :sym-dx :sym-dy + :zero? :caption :caption-dx :caption-dy) ui-hints))) :button (apply draw-button (apply conj args ((juxt :caption :caption-dx :caption-dy) ui-hints))) :small-button (apply draw-small-button (apply conj args ((juxt :caption :caption-dx :caption-dy) ui-hints))) :slider (apply draw-slider (apply conj args ((juxt :caption :caption-dx :caption-dy) ui-hints))) :selector (apply draw-selector (apply conj args ((juxt :caption :caption-dx :caption-dy) ui-hints))) :wheel (apply draw-wheel (apply conj args ((juxt :caption :caption-dx :caption-dy) ui-hints)))) - (when-let [ui-aux-fn (-> control :ui-hints :ui-aux-fn )] + (when-let [ui-aux-fn (-> control :ui-hints :ui-aux-fn)] (ui-aux-fn)))) (defn control->advanced-control [control] @@ -1002,7 +1020,7 @@ (text-size 16) (text (name (find-note-name (note @split-note))) 199 676) (text-size 8) - (q/fill (color 255 255 255 255)))} + (q/fill 255 255 255 255))} :split-note (fn [val] (do (reset! split-note (int val)) @@ -1151,14 +1169,34 @@ (defn load-image [fname] (PImageAWT. (ImageIO/read (io/resource fname)))) +(def presets + ["way-huge.patch" + "arppy.patch" + "distorted.patch" + "bass.patch" + "pluck.patch" + "tri-melody.patch" + "horn.patch" + "pipes.patch"]) + +(defn unpack-presets! + "Create a presets directory in the current working directory, and fill it with + presets found on the classpath." + [] + (when (not (.isDirectory preset-dir)) + (.mkdir preset-dir) + (doseq [p presets] + (spit (io/file preset-dir p) (slurp (io/resource (str "presets/" p))))))) + (defn setup [] (smooth) (frame-rate 10) (background 0) ;; load the most bad-est preset possible! - (load-synth-settings-from-file "./presets/way-huge.patch") + (unpack-presets!) + (load-synth-settings-from-file patch-a) (reset! selected-split :b) - (load-synth-settings-from-file "./presets/bass.patch") + (load-synth-settings-from-file patch-b) (reset! selected-split :a) (set-state! :background-img (load-image "background.png") :mod-panel-img (load-image "mod-panel.png") @@ -1198,8 +1236,7 @@ overtone-circle-img (state :overtone-circle-img) overtone-text-img (state :overtone-text-img) mini-beast-text-img (state :mini-beast-text-img) - logo-img (state :logo-img) - overtone-tint (color 253 0 147)] + logo-img (state :logo-img)] (if @first-draw? (let [draw-foreground? false draw-background? true @@ -1207,12 +1244,12 @@ (reset! first-draw? false) (debug "--> Compositing background...") (set-image 0 0 background-img) - (tint overtone-tint) - (image overtone-circle-img 65 20) - (tint 255 255) - (image overtone-text-img 65 20) - (image mini-beast-text-img 125 50) - (image logo-img 120 80) + (with-tint overtone-tint + (image overtone-circle-img 65 20)) + (with-tint white-tint + (image overtone-text-img 65 20) + (image mini-beast-text-img 125 50) + (image logo-img 120 80)) (doall (map #(draw-control % draw-foreground? draw-background?) (get-controls false))) (q/fill 255) (doseq [{:keys [t x y]} [{:x 114 :y 360 :t "-2"} @@ -1223,8 +1260,7 @@ (text t x y)) (save (str "data/" tmp-background)) (debug "--> Loading new background...") - (reset! composite-background-img (load-image tmp-background)) - )) + (reset! composite-background-img (load-image tmp-background)))) (set-image 0 0 @composite-background-img) ;; draw all keys @@ -1237,7 +1273,6 @@ ;; draw modulation panel (when @show-modulation-controls? ;; draw background - (tint 255 255) (image (state :mod-panel-img) 50 472) (doseq [ctl (get-mod-controls)] (draw-control ctl true true))) @@ -1249,23 +1284,23 @@ (draw-control ctl draw-foreground? draw-background?))) (let [lfo (or @(-> (lfo-synth) :taps :lfo) 0) - lfo-tint (color 255 0 0 (* 255 lfo)) + lfo-tint [255 0 0 (* 255 lfo)] amp (apply max 0 (map (fn [s] @(-> s :taps :amp-adsr)) (concat synth-voices-a synth-voices-b))) - amp-tint (color 0 255 0 (* 255 amp)) + amp-tint [0 255 0 (* 255 amp)] fil (apply max 0 (map (fn [s] @(-> s :taps :filter-adsr)) (concat synth-voices-a synth-voices-b))) - filter-tint (color 0 255 0 (* 255 fil)) + filter-tint [0 255 0 (* 255 fil)] arp (or @(-> (arp-synth) :taps :arp) 0) - arp-tint (color 255 0 0 (* 255 arp)) - off-tint (color 65 65 65 255) - down-2-oct-tint (color 255 0 0 (if (= -2 (:octave-transpose @synth-state)) 255 66)) - down-1-oct-tint (color 255 255 0 (if (= -1 (:octave-transpose @synth-state)) 255 66)) - down-0-oct-tint (color 0 255 0 (if (= 0 (:octave-transpose @synth-state)) 255 66)) - up-1-oct-tint (color 255 255 0 (if (= 1 (:octave-transpose @synth-state)) 255 66)) - up-2-oct-tint (color 255 0 0 (if (= 2 (:octave-transpose @synth-state)) 255 66)) + arp-tint [255 0 0 (* 255 arp)] + off-tint [65 65 65 255] + down-2-oct-tint [255 0 0 (if (= -2 (:octave-transpose @synth-state)) 255 66)] + down-1-oct-tint [255 255 0 (if (= -1 (:octave-transpose @synth-state)) 255 66)] + down-0-oct-tint [0 255 0 (if (= 0 (:octave-transpose @synth-state)) 255 66)] + up-1-oct-tint [255 255 0 (if (= 1 (:octave-transpose @synth-state)) 255 66)] + up-2-oct-tint [255 0 0 (if (= 2 (:octave-transpose @synth-state)) 255 66)] draw-led (fn [x y t] - (tint off-tint) + (apply tint off-tint) (image led-background-img (+ 10 x) (+ 10 y)) - (tint t) + (apply tint t) (image led-img x y))] (doseq [[x y led] [[590 388 lfo-tint] @@ -1277,9 +1312,7 @@ [142 354 down-0-oct-tint] [162 354 up-1-oct-tint] [182 354 up-2-oct-tint]]] - (draw-led x y led)) - - ))) + (draw-led x y led))))) (defn in-box? "is point [x y] inside the box bounded by [u v] [s t]? @@ -1504,9 +1537,12 @@ (catch Exception e false))))) -(defn -main [& args] +(defn start! [] (register-midi-handlers) - (start-gui) + (start-gui)) + +(defn -main [& args] + (start!) (banner "The MiniBeast is a go")) (comment @@ -1514,4 +1550,6 @@ (:state (meta sk)) - (System/exit 0)) + (System/exit 0) + + (reset! first-draw? true)) From 375cc99fcb5869e88af34057747ed4f26d34a933 Mon Sep 17 00:00:00 2001 From: Arne Brasseur Date: Sun, 19 Nov 2023 10:02:35 +0100 Subject: [PATCH 7/9] Fix verbosity and preset handling --- src/minibeast/core.clj | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/minibeast/core.clj b/src/minibeast/core.clj index 706be94..998583a 100644 --- a/src/minibeast/core.clj +++ b/src/minibeast/core.clj @@ -57,14 +57,21 @@ errors) (println) (banner "The MiniBeast") + (println) (println "mini-beast [options] [patch-a] [patch-b]") (println) (println summary) (System/exit (if errors 1 0))) (let [[a b] arguments] - (def patch-a (or a (io/file preset-dir "way-huge.patch"))) - (def patch-b (or b (io/file preset-dir "bass.patch")))) - (reset! verbosity (:verbose options)) + (def patch-a (or (and a (.exists (io/file a)) a) + (and a (.exists (io/file preset-dir a)) (io/file preset-dir a)) + (and a (io/resource a)) + (io/file preset-dir "way-huge.patch"))) + (def patch-b (or (and b (.exists (io/file b)) b) + (and b (.exists (io/file preset-dir b)) (io/file preset-dir b)) + (and b (io/resource b)) + (io/file preset-dir "bass.patch")))) + (reset! verbosity (:verbosity options)) (cond (:sc-boot-external options) (boot-external-server) @@ -1130,7 +1137,9 @@ (->> out-obj pr-str (spit path)))))) (defn load-synth-settings-from-file [path] + (debug "Loading" (str path)) (let [patch (-> path slurp read-string)] + (debug "--> patch" patch) ;; reset the ui and synth to pre-file-loaded values (swap! ui-state merge {@selected-split {}}) (reset-synth-defaults mbsynth) @@ -1184,9 +1193,11 @@ presets found on the classpath." [] (when (not (.isDirectory preset-dir)) - (.mkdir preset-dir) - (doseq [p presets] - (spit (io/file preset-dir p) (slurp (io/resource (str "presets/" p))))))) + (.mkdir preset-dir)) + (doseq [p presets + :let [f (io/file preset-dir p)]] + (when-not (.exists f) + (spit f (slurp (io/resource p)))))) (defn setup [] (smooth) From ea5894e074eb1f42dc1fa9cbb2b31fcc3783f909 Mon Sep 17 00:00:00 2001 From: Arne Brasseur Date: Sun, 19 Nov 2023 10:18:17 +0100 Subject: [PATCH 8/9] Fix background handling when not running from source --- src/minibeast/core.clj | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/minibeast/core.clj b/src/minibeast/core.clj index 998583a..e064fc1 100644 --- a/src/minibeast/core.clj +++ b/src/minibeast/core.clj @@ -1176,7 +1176,9 @@ "Open Patch" (apply-synth-settings-from-file))) (defn load-image [fname] - (PImageAWT. (ImageIO/read (io/resource fname)))) + (if (instance? java.io.File fname) + (q/load-image fname) + (PImageAWT. (ImageIO/read (io/resource fname))))) (def presets ["way-huge.patch" @@ -1251,7 +1253,7 @@ (if @first-draw? (let [draw-foreground? false draw-background? true - tmp-background "tmp-background.png"] + tmp-background (java.io.File/createTempFile "tmp-background" ".png")] (reset! first-draw? false) (debug "--> Compositing background...") (set-image 0 0 background-img) @@ -1269,7 +1271,7 @@ {:x 174 :y 360 :t "+1"} {:x 194 :y 360 :t "+2"}]] (text t x y)) - (save (str "data/" tmp-background)) + (save tmp-background) (debug "--> Loading new background...") (reset! composite-background-img (load-image tmp-background)))) @@ -1554,10 +1556,10 @@ (defn -main [& args] (start!) - (banner "The MiniBeast is a go")) + (banner "! ~ ~ ·Let· ·The· ·mBeast· ·Go· ~ ~ !")) (comment - (-main) + (start!) (:state (meta sk)) From 9f0637af7be6dd4763078263ed112c35d31a8395 Mon Sep 17 00:00:00 2001 From: Arne Brasseur Date: Sun, 19 Nov 2023 10:29:29 +0100 Subject: [PATCH 9/9] Add more troubleshooting instructions --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3fb473a..66200ea 100644 --- a/README.md +++ b/README.md @@ -10,34 +10,63 @@ Digital clone of an analog synthesizer using Overtone and Quil Install the [Clojure CLI tools](https://clojure.org/guides/install_clojure) ## Running - git clone https://github.com/overtone/mini-beast.git - cd mini-beast - clojure -M -m minibeast.core --help + +``` +git clone https://github.com/overtone/mini-beast.git +cd mini-beast +clojure -M -m minibeast.core +``` + +This will try to use the Overtone internal (embedded) SuperCollider. There are +several scenarios in which this may not work (Mac M1/M2, Windows 64 bit, Linux +with Pipewire). Check the [[Troubleshooting]] section below for how to fix that. ## Tips + * Make sure your MIDI devices are connected before starting the beast -* Click on a knob slider, selector, or wheel and adjust the control on your MIDI device. +* Right click on a knob slider, selector, or wheel and adjust the control on your MIDI device. * The beast remembers your control bindings, so once you've bound one control, bind another! * You can save and load bindings and presets using the `File` menu. ## Troubleshooting -If the embedded SuperCollider doesn't work for some reason, then install -SuperCollider manually (it's packaged for virtually any operating system). If -`scsynth` is on your path you can try `--sc-boot-external`, or start it yourself -(`scsynth -u 12345`), then connect to it with `--sc-udp-port 12345`. +### Can't start SuperCollider + +If the embedded SuperCollider doesn't work for some reason, then [install +SuperCollider](https://supercollider.github.io/downloads.html) manually. It's +packaged for virtually every operating system, so check your package manager +first. + +Debian/Ubuntu: -On Linux when using PipeWire it might help to run MiniBeast with `pw-jack` +``` +sudo apt-get install supercollider-server sc3-plugins +scsynth -v +``` + +Once `scsynth` is on your path you can try `--sc-boot-external`, or start +it yourself (`scsynth -u 12345`), then connect to it with `--sc-udp-port 12345`. + +On Linux when using PipeWire it might help to run MiniBeast with `pw-jack`, part +of the `pipewire-jack` package. ``` scsynth -u 12345 pw-jack clojure -M -m minibeast-core --sc-udp-port 12345 ``` +### It runs but there's no sound + +Check that your outputs are connected. On Linux this is done by connecting +Overtone to your audio interface through Jack or PipeWire. Programs that can do +this are `qjackctl` (Jack or Pipewire) or `qpwgraph` (Pipewire only). + ## A call for patches -MiniBeast loads a default patch, but we need your help! There is a world of sounds and timbres waiting to be explored. -When you happen upon an unusual or interesting patch, please save it and share it. Pull requests work or post them to -the Overtone [mailing list](http://groups.google.com/group/overtone). + +MiniBeast loads a default patch, but we need your help! There is a world of +sounds and timbres waiting to be explored. When you happen upon an unusual or +interesting patch, please save it and share it. Pull requests work or post them +to the Overtone [mailing list](http://groups.google.com/group/overtone). Enjoy making new sounds!