{-# Language TypeSynonymInstances, FlexibleInstances #-}
-- | UIs for live performances
module Csound.Air.Live (
-- * Mixer
mixer, hmixer, mixMono,
-- * Effects
FxFun, FxUI(..), fxBox,
fxColor, fxVer, fxHor, fxSca, fxApp,
-- ** Fx units
uiDistort, uiChorus, uiFlanger, uiPhaser, uiDelay, uiEcho,
uiFilter, uiReverb, uiGain, uiWhite, uiPink, uiFx, uiRoom,
uiHall, uiCave, uiSig, uiMix,
-- * Static widgets
AdsrBound(..), AdsrInit(..),
linAdsr, expAdsr,
classicWaves,
masterVolume, masterVolumeKnob
) where
import Control.Monad
import Data.Colour
import Data.Boolean
import qualified Data.Colour.Names as C
import Csound.Typed
import Csound.Typed.Gui
import Csound.Control.Gui(funnyRadio, mapSource)
import Csound.Typed.Opcode hiding (space)
import Csound.SigSpace
import Csound.Air.Wave
import Csound.Air.Fx
import Csound.Air.Misc
----------------------------------------------------------------------
-- mixer
-- | The stereo signal processing function.
type FxFun = Sig2 -> SE Sig2
instance SigSpace FxFun where
mapSig f g = fmap (mapSig f) . g
-- | Widget that represents a mixer.
mixer :: [(String, SE Sig2)] -> Source Sig2
mixer = genMixer (ver, hor)
-- | Widget that represents a mixer with horizontal grouping of elements.
hmixer :: [(String, SE Sig2)] -> Source Sig2
hmixer = genMixer (hor, ver)
genMixer :: ([Gui] -> Gui, [Gui] -> Gui) -> [(String, SE Sig2)] -> Source Sig2
genMixer (parentGui, childGui) as = source $ do
gTags <- mapM box names
(gs, vols) <- fmap unzip $ mapM (const $ defSlider "") names
(gMutes, mutes) <- fmap unzip $ mapM (const $ toggleSig "" False) names
gMasterTag <- box "master"
(gMaster, masterVol) <- defSlider ""
(gMasterMute, masterMute) <- toggleSig "" False
let g = parentGui $ zipWith3 (\tag slid mute -> childGui [sca 0.8 tag, sca 8 slid, sca 1.1 mute])
(gMasterTag : gTags) (gMaster : gs) (gMasterMute : gMutes)
muteVols = zipWith appMute mutes vols
masterMuteVol = appMute masterMute masterVol
res <- mul masterMuteVol $ mean $ zipWith mul muteVols sigs
return (g, res)
where
(names, sigs) = unzip as
appMute mute vol = (port (1 - mute) 0.05) * vol
-- | Transforms the mono signal to the stereo input
-- for the mixer widget.
mixMono :: String -> Sig -> (String, SE Sig2)
mixMono name asig = (name, return (asig, asig))
defSlider :: String -> Source Sig
defSlider tag = slider tag (linSpan 0 1) 0.5
----------------------------------------------------------------------
-- effects
class FxUI a where
applyFxArgs :: a -> [Sig] -> Sig2 -> SE Sig2
arityFx :: a -> Int
instance FxUI (Sig2 -> Sig2) where
applyFxArgs f _ x = return $ f x
arityFx = const 0
instance FxUI FxFun where
applyFxArgs f _ x = f x
arityFx = const 0
instance FxUI a => FxUI (Sig -> a) where
applyFxArgs f (a:as) x = applyFxArgs (f a) as x
arityFx f = 1 + arityFx (proxy f)
where
proxy :: (a -> b) -> b
proxy _ = undefined
-- | Creates a widget that represents a stereo signal processing function.
-- The parameters of the widget are updated with sliders.
-- For example let's create a simple gain widget. It can be encoded like this:
--
-- > uiGain :: Bool -> Double -> Source FxFun
-- > uiGain isOn gain = fxBox "Gain" fx isOn [("gain", gain)]
-- > where
-- > fx :: Sig -> Sig2 -> Sig2
-- > fx = mul
--
-- Let's look at the arguments of the function
--
-- > fxBox name fx isOn args
--
-- * @name@ -- is the name of the widget
--
-- * @fx@ -- is signal processing function (see the class @FxUI@).
--
-- * @isOn@ -- whether widget in the active state
--
-- * @args@ -- list of initial values for arguments and names of the arguments.
--
-- It's cool to set the color of the widget with @fxColor@ function.
-- we can make our widgets much more intersting to look at.
fxBox :: FxUI a => String -> a -> Bool -> [(String, Double)] -> Source FxFun
fxBox name fx onOff args = source $ do
(gOff0, off) <- toggleSig name onOff
let gOff = setFontSize 25 gOff0
offRef <- newGlobalSERef (0 :: Sig)
writeSERef offRef off
let (names, initVals) = unzip $ take (arityFx fx) args
(gs, as) <- fmap unzip $ mapM (\(name, initVal) -> slider name (linSpan 0 1) initVal) $ zip names initVals
let f x = do
ref <- newSERef (0 :: Sig, 0 :: Sig)
goff <- readSERef offRef
writeSERef ref x
when1 (goff ==* 1) $ do
x2 <- readSERef ref
writeSERef ref =<< applyFxArgs fx as x2
res <- readSERef ref
return res
let gui = setBorder UpBoxBorder $ go (length names) gOff gs
return (gui, f)
where
go n gOff gs
| n == 0 = gOff
| n < 4 = f (gs ++ replicate (4 - n) space)
| otherwise = f gs
where f xs = uiGroupGui gOff (ver xs)
uiGroupGui :: Gui -> Gui -> Gui
uiGroupGui a b =ver [sca 1.7 a, sca 8 b]
sourceColor2 :: Color -> Source a -> Source a
sourceColor2 col a = source $ do
(g, x) <- a
return (setColor2 col g, x)
-- | Colors the source widgets.
fxColor :: Color -> Source a -> Source a
fxColor = sourceColor2
-- combine effects
fxGroup :: ([Gui] -> Gui) -> [Source FxFun] -> Source FxFun
fxGroup guiGroup as = do
(gs, fs) <- fmap unzip $ sequence as
return (guiGroup gs, foldl (\a b -> a >=> b) return fs)
-- | Scales the gui for signal processing widgets.
fxSca :: Double -> Source FxFun -> Source FxFun
fxSca d a = fxGroup (\xs -> sca d $ head xs) [a]
-- | Groups the signal processing widgets.
-- The functions are composed the visuals are
-- grouped horizontaly.
fxHor :: [Source FxFun] -> Source FxFun
fxHor = fxGroup hor
-- | Groups the signal processing widgets.
-- The functions are composed the visuals are
-- grouped verticaly.
fxVer :: [Source FxFun] -> Source FxFun
fxVer = fxGroup ver
-- | Applies a function to a signal processing function.
fxApp :: FxFun -> Source FxFun -> Source FxFun
fxApp f = mapSource (>=> f)
-- | The distortion widget. The arguments are
--
-- > uiDistort isOn levelOfDistortion drive tone
uiDistort :: Bool -> Double -> Double -> Double -> Source FxFun
uiDistort isOn level drive tone = sourceColor2 C.red $ fxBox "Distortion" fxDistort2 isOn
[("level", level), ("drive", drive), ("tone", tone)]
-- | The chorus widget. The arguments are
--
-- > uiChorus isOn mix rate depth width
uiChorus :: Bool -> Double -> Double -> Double -> Double -> Source FxFun
uiChorus isOn mix rate depth width = sourceColor2 C.coral $ fxBox "Chorus" stChorus2 isOn
[("mix",mix), ("rate",rate), ("depth",depth), ("width",width)]
-- | The flanger widget. The arguments are
--
-- > uiFlanger isOn mix feedback rate depth delay
uiFlanger :: Bool -> Double -> Double -> Double -> Double -> Double -> Source FxFun
uiFlanger isOn mix fback rate depth delay = sourceColor2 C.indigo $ fxBox "Flanger" fxFlanger2 isOn
[("mix", mix), ("fback", fback), ("rate",rate), ("depth",depth), ("delay",delay)]
-- | The phaser widget. The arguments are
--
-- > uiPhaser isOn mix feedback rate depth frequency
uiPhaser :: Bool -> Double -> Double -> Double -> Double -> Double -> Source FxFun
uiPhaser isOn mix fback rate depth freq = sourceColor2 C.orange $ fxBox "Phaser" fxPhaser2 isOn
[("mix", mix), ("fback", fback), ("rate",rate), ("depth",depth), ("freq", freq)]
-- | The delay widget. The arguments are
--
-- > uiDelay isOn mix feedback delayTime tone
uiDelay :: Bool -> Double -> Double -> Double -> Double -> Source FxFun
uiDelay isOn mix fback time tone = sourceColor2 C.dodgerblue $ fxBox "Delay" analogDelay2 isOn
[("mix",mix), ("fback",fback), ("time",time), ("tone",tone)]
-- | The simplified delay widget. The arguments are
--
-- > uiEcho isOn maxDelayTime delayTime feedback
uiEcho :: Bool -> D -> Double -> Double -> Source FxFun
uiEcho isOn maxDelTime time fback = sourceColor2 C.deepskyblue $ fxBox "Echo" (fxEcho2 maxDelTime) isOn
[("time", time), ("fback", fback)]
-- | The pair of low and high pass filters
--
-- > uiFilter isOn lowPassfrequency highPassFrequency gain
uiFilter :: Bool -> Double -> Double -> Double -> Source FxFun
uiFilter isOn lpf hpf gain = fxBox "Filter" fxFilter2 isOn
[("lpf",lpf), ("hpf",hpf), ("gain",gain)]
-- | The reverb widget. The arguments are:
--
-- > uiReverb mix depth
uiReverb :: Bool -> Double -> Double -> Source FxFun
uiReverb isOn mix depth = sourceColor2 C.forestgreen $ fxBox "Reverb" (\mix depth asig -> mul (1 - mix) asig + mul mix (rever2 depth asig)) isOn
[("mix", mix), ("depth", depth)]
-- | The gain widget. The arguments are
--
-- > uiGain isOn amountOfGain
uiGain :: Bool -> Double -> Source FxFun
uiGain isOn gain = sourceColor2 C.black $ fxBox "Gain" fxGain isOn [("gain", gain)]
-- | The filtered white noize widget. The arguments are
--
-- > uiWhite isOn centerFreqOfFilter amountOfNoize
uiWhite :: Bool -> Double -> Double -> Source FxFun
uiWhite isOn freq depth = sourceColor2 C.dimgray $ fxBox "White" fxWhite2 isOn
[("freq", freq), ("depth", depth)]
-- | The filtered pink noize widget. The arguments are
--
-- > uiPink isOn centerFreqOfFilter amountOfNoize
uiPink :: Bool -> Double -> Double -> Source FxFun
uiPink isOn freq depth = sourceColor2 C.deeppink $ fxBox "Pink" fxPink2 isOn
[("freq", freq), ("depth", depth)]
-- | The constructor for signal processing functions with no arguments (controlls).
uiFx :: FxUI a => String -> a -> Bool -> Source FxFun
uiFx name f isOn = fxBox name f isOn []
-- | The reverb for room.
uiRoom :: Bool -> Source FxFun
uiRoom isOn = sourceColor2 C.limegreen $ uiFx "Room" smallRoom2 isOn
-- | The reverb for hall.
uiHall :: Bool -> Source FxFun
uiHall isOn = sourceColor2 C.mediumseagreen $ uiFx "Hall" largeHall2 isOn
-- | The reverb for magic cave.
uiCave :: Bool -> Source FxFun
uiCave isOn = sourceColor2 C.darkviolet $ uiFx "Cave" magicCave2 isOn
-- | The widget for selecting a midi instrument.
uiMidi :: Bool -> [(String, Msg -> SE Sig2)] -> Source FxFun
uiMidi isOn as = sourceColor2 C.forestgreen $ undefined
-- | the widget for mixing in a signal to the signal.
uiSig :: String -> Bool -> Source Sig2 -> Source FxFun
uiSig name onOff widget = source $ do
(gs, asig) <- widget
(gOff0, off) <- toggleSig name onOff
let gOff = setFontSize 25 gOff0
f x = return $ x + mul (portk off 0.05) asig
return (setBorder UpBoxBorder $ uiGroupGui gOff gs, f)
-- | A mixer widget represented as an effect.
-- The effect sums the signals with given wieghts.
uiMix :: Bool -> [(String, SE Sig2)] -> Source FxFun
uiMix onOff as = sourceColor2 C.blue $ uiSig "Mix" onOff (mixer as)
----------------------------------------------------------------------
-- Widgets
data AdsrBound = AdsrBound
{ attBound :: Double
, decBound :: Double
, relBound :: Double }
data AdsrInit = AdsrInit
{ attInit :: Double
, decInit :: Double
, susInit :: Double
, relInit :: Double }
expEps :: Fractional a => a
expEps = 0.00001
linAdsr :: String -> AdsrBound -> AdsrInit -> Source Sig
linAdsr = genAdsr $ \a d s r -> linsegr [0, a, 1, d, s] r 0
expAdsr :: String -> AdsrBound -> AdsrInit -> Source Sig
expAdsr = genAdsr $ \a d s r -> expsegr [double expEps, a, 1, d, s] r (double expEps)
genAdsr :: (D -> D -> D -> D -> Sig)
-> String -> AdsrBound -> AdsrInit -> Source Sig
genAdsr mkAdsr name b inits = source $ do
(gatt, att) <- knob "A" (linSpan expEps $ attBound b) (attInit inits)
(gdec, dec) <- knob "D" (linSpan expEps $ decBound b) (decInit inits)
(gsus, sus) <- knob "S" (linSpan expEps 1) (susInit inits)
(grel, rel) <- knob "R" (linSpan expEps $ relBound b) (relInit inits)
let val = mkAdsr (ir att) (ir dec) (ir sus) (ir rel)
gui <- setTitle name $ hor [gatt, gdec, gsus, grel]
return (gui, val)
-- | A widget with four standard waveforms: pure tone, triangle, square and sawtooth.
-- The last parameter is a default waveform (it's set at init time).
classicWaves :: String -> Int -> Source (Sig -> Sig)
classicWaves name initVal = funnyRadio name
[ ("osc", osc)
, ("tri", tri)
, ("sqr", sqr)
, ("saw", saw)]
initVal
-- | Slider for master volume
masterVolume :: Source Sig
masterVolume = slider "master" uspan 0.5
-- | Knob for master volume
masterVolumeKnob :: Source Sig
masterVolumeKnob = knob "master" uspan 0.5