csound-expression-4.3: src/Csound/Control/Gui/Widget.hs
-- | Primitive GUI elements.
--
-- There is a convention that constructors take only parameters that
-- specify the logic of the widget. The view is set for GUI-elements with
-- other functions.
module Csound.Control.Gui.Widget (
-- * Common properties
ValDiap(..), ValStep, ValScaleType(..), ValSpan(..),
linSpan, expSpan, uspan, bspan, uspanExp,
-- * Valuators
count, countSig, joy,
knob, KnobType(..), setKnobType,
roller,
slider, sliderBank, SliderType(..), setSliderType,
numeric, TextType(..), setTextType,
-- * Other widgets
box, BoxType(..), setBoxType,
button, ButtonType(..), setButtonType,
toggle, butBank, toggleSig, butBankSig,
butBank1, butBankSig1,
radioButton, matrixButton, funnyRadio, funnyMatrix,
setNumeric, meter,
setToggle, setToggleSig,
-- * Transformers
setTitle,
-- * Keyboard
KeyEvt(..), Key(..), keyIn, charOn, charOff,
-- * Easy to use widgets
uknob, xknob, uslider, xslider, ujoy,
hradio, vradio, hradioSig, vradioSig,
-- * Number selectors
-- | Widgets for sample and hold functions
hnumbers, vnumbers,
-- * The 2D matrix of widgets
knobPad, togglePad, buttonPad, genPad
) where
import Control.Monad
import Data.List(transpose)
import Data.Boolean
import Csound.Typed.Gui
import Csound.Typed.Types
import Csound.Control.SE
import Csound.Control.Evt(listAt)
--------------------------------------------------------------------
-- aux widgets
readMatrix :: Int -> Int -> [a] -> [a]
readMatrix xn yn as = transp $ take (xn * yn) $ as ++ repeat (head as)
where
transp xs = concat $ transpose $ parts yn xn xs
parts x y qs
| x == 0 = []
| otherwise = a : parts (x - 1) y b
where (a, b) = splitAt y qs
-- | A radio button. It takes a list of values with labels.
radioButton :: Arg a => String -> [(String, a)] -> Int -> Source (Evt a)
radioButton title as initVal = source $ do
(g, ind) <- butBank1 "" 1 (length as) (0, initVal)
gnames <- mapM box names
let val = listAt vals ind
gui <- setTitle title $ padding 0 $ hor [sca 0.15 g, ver gnames]
return (gui, val)
where (names, vals) = unzip as
-- | A matrix of values.
matrixButton :: Arg a => String -> Int -> Int -> [a] -> (Int, Int) -> Source (Evt a)
matrixButton name xn yn vals initVal = source $ do
(gui, ind) <- butBank1 name xn yn initVal
let val = listAt allVals ind
return (gui, val)
where allVals = readMatrix xn yn vals
-- | Radio button that returns functions. Useful for picking a waveform or type of filter.
funnyRadio :: Tuple b => String -> [(String, a -> b)] -> Int -> Source (a -> b)
funnyRadio name as initVal = source $ do
(gui, ind) <- radioButton name (zip names (fmap int [0 ..])) initVal
contInd <- stepper (sig $ int initVal) $ fmap sig ind
let instr x = guardedTuple (
zipWith (\n f -> (contInd ==* (sig $ int n), f x)) [0 ..] funs
) (head funs x)
return (gui, instr)
where (names, funs) = unzip as
-- | Matrix of functional values.
funnyMatrix :: Tuple b => String -> Int -> Int -> [(a -> b)] -> (Int, Int) -> Source (a -> b)
funnyMatrix name xn yn funs initVal@(x0, y0) = source $ do
(gui, ind) <- butBank1 name xn yn initVal
contInd <- stepper flattenInitVal $ fmap sig ind
let instr x = guardedTuple (
zipWith (\n f -> (contInd ==* (sig $ int n), f x)) [0 ..] allFuns
) (head allFuns x)
return (gui, instr)
where
allFuns = readMatrix xn yn funs
flattenInitVal = sig $ int $ y0 + x0 * yn
-- | Shortcut for press 'CharKey' events.
charOn :: Char -> Evt Unit
charOn = keyIn . Press . CharKey
-- | Shortcut for release 'CharKey' events.
charOff :: Char -> Evt Unit
charOff = keyIn . Release . CharKey
-- | Unipolar linear slider. The value belongs to the interval [0, 1].
-- The argument is for initial value.
uslider :: Double -> Source Sig
uslider = slider "" (linSpan 0 1)
-- | Unipolar linear knob. The value belongs to the interval [0, 1].
-- The argument is for initial value.
uknob :: Double -> Source Sig
uknob = knob "" (linSpan 0 1)
-- | Exponential slider (usefull for exploring frequencies or decibels).
--
-- > xknob min max initVal
--
-- The value belongs to the interval [min, max].
-- The last argument is for initial value.
xslider :: Double -> Double -> Double -> Source Sig
xslider a b initVal = slider "" (expSpan a b) initVal
-- | Exponential knob (usefull for exploring frequencies or decibels).
--
-- > xknob min max initVal
--
-- The value belongs to the interval [min, max].
-- The last argument is for initial value.
xknob :: Double -> Double -> Double -> Source Sig
xknob a b initVal = knob "" (expSpan a b) initVal
-- | Unit linear joystick.
ujoy :: (Double, Double) -> Source (Sig, Sig)
ujoy = joy (linSpan 0 1) (linSpan 0 1)
---------------------------------------------------------------
-- sample and hold
-- | The sample and hold widget. You can pick a value from the list of doubles.
-- The original value is a head of the list (the first element).
-- The visual grouping is horizontal (notice the prefix @h@).
-- It's common to use it with function @selector@.
hnumbers :: [Double] -> Source Sig
hnumbers = genNumbers hor
-- | The sample and hold widget. You can pick a value from the list of doubles.
-- The original value is a head of the list (the first element).
-- The visual grouping is vertical (notice the prefix @v@).
-- It's common to use it with function @selector@.
vnumbers :: [Double] -> Source Sig
vnumbers = genNumbers ver
genNumbers :: ([Gui] -> Gui) -> [Double] -> Source Sig
genNumbers gx as@(d:ds) = source $ do
ref <- newGlobalSERef (sig $ double d)
(gs, evts) <- fmap unzip $ mapM (button . show) as
zipWithM_ (\x e -> runEvt e $ \_ -> writeSERef ref (sig $ double x)) as evts
res <- readSERef ref
return (gx gs, res)
-------------------------------------------------------------------
-- 2D matrix of widgets
-- | The matrix of unipolar knobs.
--
-- > knobPad columnNum rowNum names initVals
--
-- It takes in the dimensions of matrix, the names (we can leave it empty
-- if names are not important) and list of init values.
-- It returns a function that takes in indices and produces the signal in
-- the corresponding cell.
knobPad :: Int -> Int -> [String] -> [Double] -> Source (Int -> Int -> Sig)
knobPad = genPad mkKnob 0.5
where mkKnob name = knob name uspan
-- | The matrix of toggle buttons.
--
-- > togglePad columnNum rowNum names initVals
--
-- It takes in the dimensions of matrix, the names (we can leave it empty
-- if names are not important) and list of init values (on/off booleans).
-- It returns a function that takes in indices and produces the event stream in
-- the corresponding cell.
togglePad :: Int -> Int -> [String] -> [Bool] -> Source (Int -> Int -> Evt D)
togglePad = genPad toggle False
-- | The matrix of buttons.
--
-- > buttonPad columnNum rowNum names
--
-- It takes in the dimensions of matrix, the names (we can leave it empty
-- if names are not important).
-- It returns a function that takes in indices and produces the event stream in
-- the corresponding cell.
buttonPad :: Int -> Int -> [String] -> Source (Int -> Int -> Evt Unit)
buttonPad width height names = genPad mkButton False width height names []
where mkButton name _ = button name
-- | A generic constructor for matrixes of sound source widgets.
-- It takes the constructor of the widget, a default initial value,
-- the dimensions of the matrix, the list of names and the list of initial values.
-- It produces the function that maps indices to corresponding values.
genPad :: (String -> a -> Source b) -> a -> Int -> Int -> [String] -> [a] -> Source (Int -> Int -> b)
genPad mk initVal width height names as = source $ do
(gui, vals) <- fmap reGroupCol $ mapM mkRow inits
let f x y = (vals !! y) !! x
return $ (gui, f)
where
mkRow xs = fmap reGroupRow $ mapM (uncurry mk) xs
inits = split height width $ zip (names ++ repeat "") (as ++ repeat initVal)
split m n xs = case m of
0 -> []
a -> (take n xs) : split (a - 1) n (drop n xs)
reGroupCol = reGroup ver
reGroupRow = reGroup hor
reGroup f as = (f xs, ys)
where (xs, ys) = unzip as
-- | Horizontal radio group.
hradio :: [String] -> Int -> Source (Evt D)
hradio = radioGroup hor
-- | Vertical radio group.
vradio :: [String] -> Int -> Source (Evt D)
vradio = radioGroup ver
-- | Horizontal radio group.
hradioSig :: [String] -> Int -> Source Sig
hradioSig = radioGroupSig hor
-- | Vertical radio group.
vradioSig :: [String] -> Int -> Source Sig
vradioSig = radioGroupSig ver
radioGroup :: ([Gui] -> Gui) -> [String] -> Int -> Source (Evt D)
radioGroup gcat names initVal = mapSource snaps $ radioGroupSig gcat names initVal
radioGroupSig :: ([Gui] -> Gui) -> [String] -> Int -> Source Sig
radioGroupSig gcat names initVal = source $ do
(guis, writes, reads) <- fmap unzip3 $ mapM (\(i, tag) -> flip setToggleSig (i == initVal) tag) $ zip [0 ..] names
curRef <- newGlobalSERef (sig $ int initVal)
current <- readSERef curRef
zipWithM_ (\w i -> w $ ifB (current ==* i) 1 0) writes ids
zipWithM_ (\r i -> runEvt (snaps r) $ \x -> do
when1 (sig x ==* 1) $ do
writeSERef curRef i
when1 (sig x ==* 0 &&* current ==* i) $ do
writeSERef curRef i
) reads ids
res <- readSERef curRef
return (gcat guis, res)
where
ids = fmap (sig . int) [0 .. length names - 1]