{-# Language
TypeSynonymInstances,
FlexibleInstances #-}
module Csound.Exp.Wrapper(
Out, Sig, I, D, Str, BoolSig(..), Spec, ToSig(..),
SE, se, se_, runSE, execSE,
Arg(..), ArgMethods(..), toArg, makeArgMethods,
CsdTuple(..), multiOuts,
Val(..),
str, double, int, ir,
tfm, pref, prim, p,
isMultiOutSignature,
noRate, setRate, getRates,
readVar, writeVar, gOutVar,
Channel
) where
import Control.Applicative
import Control.Monad(ap)
import Control.Monad.Trans.State
import Data.String
import Data.Fix
import Control.Monad.Trans.State
import Csound.Exp
type Channel = Int
-- | Output of the instrument.
type Out = SE [Sig]
-- | Audio or control rate signals.
newtype Sig = Sig { unSig :: E }
-- | Integers.
newtype I = I { unI :: E }
-- | Doubles.
newtype D = D { unD :: E }
-- | Strings.
newtype Str = Str { unStr :: E }
-- | Boolean signals. Use functions from the module "Data.Boolean" to make boolean signals
-- out of simple signals.
newtype BoolSig = BoolSig { unBoolSig :: E }
-- | Spectrum of the signal (see "FFT and Spectral Processing" in the "Csound.Opcode.Advanced").
newtype Spec = Spec { unSpec :: E }
------------------------------------------------
-- side effects
-- | Csound's synonym for 'IO'-monad. 'SE' means Side Effect.
-- You will bump into 'SE' trying to read and write to delay lines,
-- making random signals or trying to save your audio to file.
-- Instrument is expected to return a value of @SE [Sig]@.
-- So it's okay to do some side effects when playing a note.
newtype SE a = SE { unSE :: State E a }
instance Functor SE where
fmap f = SE . fmap f . unSE
instance Applicative SE where
pure = return
(<*>) = ap
instance Monad SE where
return = SE . return
ma >>= mf = SE $ unSE ma >>= unSE . mf
runSE :: SE a -> (a, E)
runSE a = runState (unSE a) (unD (p 3 :: D))
execSE :: SE a -> E
execSE = snd . runSE
------------------------------------------------
-- basic constructors
noRate :: Val a => Exp E -> a
noRate = ratedExp Nothing
withRate :: Val a => Rate -> Exp E -> a
withRate r = ratedExp (Just r)
ratedExp :: Val a => Maybe Rate -> Exp E -> a
ratedExp r = wrap . RatedExp r Nothing
prim :: Val a => Prim -> a
prim = wrap . noRate . ExpPrim
pref :: Name -> Signature -> Info
pref name signature = Info name signature Prefix Nothing
inf :: Name -> Signature -> Info
inf name signature = Info name signature Infix Nothing
tfm :: Val a => Info -> [RatedExp E] -> a
tfm info args = wrap $ noRate $ Tfm info $ map Fix args
gvar, var :: Val a => Rate -> Name -> a
var = mkVar LocalVar
gvar = mkVar GlobalVar
mkVar :: Val a => VarType -> Rate -> String -> a
mkVar ty rate name = wrap $ noRate $ ReadVar (Var ty rate name)
p :: Val a => Int -> a
p = prim . P
-- | Converts Haskell's integers to Csound's integers
int :: Int -> I
int = prim . PrimInt
-- | Converts Haskell's doubles to Csound's doubles
double :: Double -> D
double = prim . PrimDouble
-- | Converts Haskell's strings to Csound's strings
str :: String -> Str
str = prim . PrimString
writeVar :: (Val a) => Var -> a -> SE ()
writeVar v x = se_ $ noRate $ WriteVar v $ Fix $ unwrap x
readVar :: (Val a) => Var -> a
readVar v = noRate $ ReadVar v
gOutVar :: Int -> Int -> Var
gOutVar instrId portId = Var GlobalVar Ar (gOutName instrId portId)
where gOutName instrId portId = "Out" ++ show instrId ++ "_" ++ show portId
-------------------------------
-- side effects
se :: (Val a) => E -> SE a
se a = SE $ state $ \s ->
let x = (unwrap a) { ratedExpDepends = Just s }
in (wrap x, Fix $ x)
se_ :: E -> SE ()
se_ = fmap (const ()) . (se :: E -> SE E)
------------------------------------------------
-- basic destructors
getPrimUnsafe :: Val a => a -> Prim
getPrimUnsafe a = case ratedExpExp $ unwrap a of
ExpPrim p -> p
--------------------------------------------
-- signals from primitive types
-- | Values that can be converted to signals.
class ToSig a where
ar :: a -> Sig -- ^ Forces signal to audio rate.
kr :: a -> Sig -- ^ Forces signal to control rate.
instance ToSig I where
ar = setRate Ar
kr = setRate Kr
instance ToSig D where
ar = setRate Ar
kr = setRate Kr
instance ToSig Sig where
ar = setRate Ar
kr = setRate Kr
instance ToSig Int where
ar = ar . int
kr = kr . int
instance ToSig Double where
ar = ar . double
kr = kr . double
--------------------------------------------
-- rate conversion
setRate :: (Val a, Val b) => Rate -> a -> b
setRate r a = wrap $ (\x -> x { ratedExpRate = Just r }) $ unwrap a
-- | Converts signal to double.
ir :: Sig -> D
ir = setRate Ir
------------------------------------------------------
-- values
class Val a where
wrap :: RatedExp E -> a
unwrap :: a -> RatedExp E
instance Val (RatedExp E) where
wrap = id
unwrap = id
instance Val E where
wrap = Fix
unwrap = unFix
instance Val Sig where
wrap = Sig . Fix
unwrap = unFix . unSig
instance Val I where
wrap = I . Fix
unwrap = unFix . unI
instance Val D where
wrap = D . Fix
unwrap = unFix . unD
instance Val Str where
wrap = Str . Fix
unwrap = unFix . unStr
instance Val Tab where
wrap = undefined
unwrap = prim . PrimTab
instance Val BoolSig where
wrap = BoolSig . Fix
unwrap = unFix . unBoolSig
instance Val Spec where
wrap = Spec . Fix
unwrap = unFix . unSpec
------------------------------------------------
-- arguments
-- | The abstract type of methods for the class 'Arg'.
data ArgMethods a = ArgMethods
{ arg :: Int -> a
, toNote :: a -> [Prim]
, arity :: a -> Int
}
toArg :: Arg a => a
toArg = arg argMethods 4
-- | Defines instance of type class 'Arg' for a new type in terms of an old one.
makeArgMethods :: (Arg a) => (a -> b) -> (b -> a) -> ArgMethods b
makeArgMethods to from = ArgMethods {
arg = to . arg argMethods,
toNote = toNote argMethods . from,
arity = arity argMethods . from }
-- | Describes all Csound values that can be used in the score section.
-- Instruments are triggered with the values from this type class.
-- Actual methods are hidden, but you can easily make instances for your own types
-- with function 'makeArgMethods'. You need to describe the new instance in terms
-- of some existing one. For example:
--
-- > data Note = Note
-- > { noteAmplitude :: D
-- > , notePitch :: D
-- > , noteVibrato :: D
-- > , noteSample :: S
-- > }
-- >
-- > instance Arg Note where
-- > argMethods = makeArgMethods to from
-- > where to (amp, pch, vibr, sample) = Note amp pch vibr sample
-- > from (Note amp pch vibr sample) = (amp, pch, vibr, sample)
--
-- Then you can use this type in an instrument definition.
--
-- > instr :: Note -> Out
-- > instr x = ...
class Arg a where
argMethods :: ArgMethods a
instance Arg () where
argMethods = ArgMethods
{ arg = const ()
, toNote = const []
, arity = const 0 }
instance Arg I where
argMethods = ArgMethods {
arg = p,
toNote = pure . getPrimUnsafe,
arity = const 1 }
instance Arg D where
argMethods = ArgMethods {
arg = p,
toNote = pure . getPrimUnsafe,
arity = const 1 }
instance Arg Str where
argMethods = ArgMethods {
arg = p,
toNote = pure . getPrimUnsafe,
arity = const 1 }
instance Arg Tab where
argMethods = ArgMethods {
arg = p,
toNote = pure . getPrimUnsafe,
arity = const 1 }
instance (Arg a, Arg b) => Arg (a, b) where
argMethods = ArgMethods arg' toNote' arity'
where arg' n = (a, b)
where a = arg argMethods n
b = arg argMethods (n + arity argMethods a)
toNote' (a, b) = toNote argMethods a ++ toNote argMethods b
arity' (a, b) = arity argMethods a + arity argMethods b
instance (Arg a, Arg b, Arg c) => Arg (a, b, c) where
argMethods = makeArgMethods to from
where to (a, (b, c)) = (a, b, c)
from (a, b, c) = (a, (b, c))
instance (Arg a, Arg b, Arg c, Arg d) => Arg (a, b, c, d) where
argMethods = makeArgMethods to from
where to (a, (b, c, d)) = (a, b, c, d)
from (a, b, c, d) = (a, (b, c, d))
instance (Arg a, Arg b, Arg c, Arg d, Arg e) => Arg (a, b, c, d, e) where
argMethods = makeArgMethods to from
where to (a, (b, c, d, e)) = (a, b, c, d, e)
from (a, b, c, d, e) = (a, (b, c, d, e))
instance (Arg a, Arg b, Arg c, Arg d, Arg e, Arg f) => Arg (a, b, c, d, e, f) where
argMethods = makeArgMethods to from
where to (a, (b, c, d, e, f)) = (a, b, c, d, e, f)
from (a, b, c, d, e, f) = (a, (b, c, d, e, f))
instance (Arg a, Arg b, Arg c, Arg d, Arg e, Arg f, Arg g) => Arg (a, b, c, d, e, f, g) where
argMethods = makeArgMethods to from
where to (a, (b, c, d, e, f, g)) = (a, b, c, d, e, f, g)
from (a, b, c, d, e, f, g) = (a, (b, c, d, e, f, g))
instance (Arg a, Arg b, Arg c, Arg d, Arg e, Arg f, Arg g, Arg h) => Arg (a, b, c, d, e, f, g, h) where
argMethods = makeArgMethods to from
where to (a, (b, c, d, e, f, g, h)) = (a, b, c, d, e, f, g, h)
from (a, b, c, d, e, f, g, h) = (a, (b, c, d, e, f, g, h))
------------------------------------------------
-- tuples
-- | Describes tuples of Csound values. It's used for functions that can return
-- several results (such as 'soundin' or 'diskin2'). Tuples can be nested.
class CsdTuple a where
fromCsdTuple :: a -> [E]
toCsdTuple :: [E] -> a
arityCsdTuple :: a -> Int
instance CsdTuple Sig where
fromCsdTuple = return . Fix . unwrap
toCsdTuple = wrap . unFix . head
arityCsdTuple = const 1
instance CsdTuple I where
fromCsdTuple = return . Fix . unwrap
toCsdTuple = wrap . unFix . head
arityCsdTuple = const 1
instance CsdTuple D where
fromCsdTuple = return . Fix . unwrap
toCsdTuple = wrap . unFix . head
arityCsdTuple = const 1
instance CsdTuple Str where
fromCsdTuple = return . Fix . unwrap
toCsdTuple = wrap . unFix . head
arityCsdTuple = const 1
instance CsdTuple Spec where
fromCsdTuple = return . Fix . unwrap
toCsdTuple = wrap . unFix . head
arityCsdTuple = const 1
instance (CsdTuple a, CsdTuple b) => CsdTuple (a, b) where
fromCsdTuple (a, b) = fromCsdTuple a ++ fromCsdTuple b
arityCsdTuple (a, b) = arityCsdTuple a + arityCsdTuple b
toCsdTuple xs = (a, b)
where a = toCsdTuple $ take (arityCsdTuple a) xs
xsb = drop (arityCsdTuple a) xs
b = toCsdTuple (take (arityCsdTuple b) xsb)
instance (CsdTuple a, CsdTuple b, CsdTuple c) => CsdTuple (a, b, c) where
fromCsdTuple (a, b, c) = fromCsdTuple (a, (b, c))
arityCsdTuple (a, b, c) = arityCsdTuple (a, (b, c))
toCsdTuple = (\(a, (b, c)) -> (a, b, c)) . toCsdTuple
instance (CsdTuple a, CsdTuple b, CsdTuple c, CsdTuple d) => CsdTuple (a, b, c, d) where
fromCsdTuple (a, b, c, d) = fromCsdTuple (a, (b, c, d))
arityCsdTuple (a, b, c, d) = arityCsdTuple (a, (b, c, d))
toCsdTuple = (\(a, (b, c, d)) -> (a, b, c, d)) . toCsdTuple
instance (CsdTuple a, CsdTuple b, CsdTuple c, CsdTuple d, CsdTuple e) => CsdTuple (a, b, c, d, e) where
fromCsdTuple (a, b, c, d, e) = fromCsdTuple (a, (b, c, d, e))
arityCsdTuple (a, b, c, d, e) = arityCsdTuple (a, (b, c, d, e))
toCsdTuple = (\(a, (b, c, d, e)) -> (a, b, c, d, e)) . toCsdTuple
instance (CsdTuple a, CsdTuple b, CsdTuple c, CsdTuple d, CsdTuple e, CsdTuple f) => CsdTuple (a, b, c, d, e, f) where
fromCsdTuple (a, b, c, d, e, f) = fromCsdTuple (a, (b, c, d, e, f))
arityCsdTuple (a, b, c, d, e, f) = arityCsdTuple (a, (b, c, d, e, f))
toCsdTuple = (\(a, (b, c, d, e, f)) -> (a, b, c, d, e, f)) . toCsdTuple
instance (CsdTuple a, CsdTuple b, CsdTuple c, CsdTuple d, CsdTuple e, CsdTuple f, CsdTuple g) => CsdTuple (a, b, c, d, e, f, g) where
fromCsdTuple (a, b, c, d, e, f, g) = fromCsdTuple (a, (b, c, d, e, f, g))
arityCsdTuple (a, b, c, d, e, f, g) = arityCsdTuple (a, (b, c, d, e, f, g))
toCsdTuple = (\(a, (b, c, d, e, f, g)) -> (a, b, c, d, e, f, g)) . toCsdTuple
instance (CsdTuple a, CsdTuple b, CsdTuple c, CsdTuple d, CsdTuple e, CsdTuple f, CsdTuple g, CsdTuple h) => CsdTuple (a, b, c, d, e, f, g, h) where
fromCsdTuple (a, b, c, d, e, f, g, h) = fromCsdTuple (a, (b, c, d, e, f, g, h))
arityCsdTuple (a, b, c, d, e, f, g, h) = arityCsdTuple (a, (b, c, d, e, f, g, h))
toCsdTuple = (\(a, (b, c, d, e, f, g, h)) -> (a, b, c, d, e, f, g, h)) . toCsdTuple
------------------------------------------------
-- multiple outs
multiOuts :: CsdTuple a => E -> a
multiOuts exp = res
where res = toCsdTuple $ multiOutsSection (arityCsdTuple res) exp
multiOutsSection :: Int -> E -> [E]
multiOutsSection n e = zipWith (\n r -> select n r e') [0 ..] rates
where rates = take n $ getRates $ ratedExpExp $ unFix e
e' = Fix $ onExp (setMultiRate rates) $ unFix e
setMultiRate rates (Tfm info xs) = Tfm (info{ infoSignature = MultiRate rates ins }) xs
where MultiRate _ ins = infoSignature info
select n r e = withRate r $ Select r n e
getRates :: Exp a -> [Rate]
getRates (Tfm info _) = case infoSignature info of
MultiRate outs _ -> outs
isMultiOutSignature :: Signature -> Bool
isMultiOutSignature x = case x of
MultiRate _ _ -> True
_ -> False