{-# Language
TypeSynonymInstances,
FlexibleInstances #-}
module Csound.Exp.Wrapper(
Out(..), Outs, Sig, D, Str, BoolSig(..), BoolD(..), Spec, ToSig(..),
Sig2, Sig3, Sig4,
SE, se, se_, runSE, execSE,
Arg(..), ArgMethods(..), toArg, makeArgMethods,
CsdTuple(..), multiOuts,
Val(..),
str, double, ir,
tfm, pref, prim, p,
isMultiOutSignature,
noRate, setRate,
getRates, tabMap, updateTabSize, defineInstrTabs, defineScoreTabs, substInstrTabs, substScoreTabs,
readVar, writeVar, gOutVar,
Channel
) where
import Control.Applicative
import Control.Monad(ap, join)
import Control.Monad.Trans.State
import Data.List(nub)
import Data.String
import Data.Fix
import Control.Monad.Trans.State
import qualified Data.Map as M
import Data.Foldable(foldMap)
import Csound.Exp
type Channel = Int
type Outs = SE [Sig]
type Sig2 = (Sig, Sig)
type Sig3 = (Sig, Sig, Sig)
type Sig4 = (Sig, Sig, Sig, Sig)
-- | Output of the instrument.
class Out a where
toOut :: a -> SE [Sig]
-- | Audio or control rate signals.
newtype Sig = Sig { unSig :: E }
-- | Doubles.
newtype D = D { unD :: E }
-- | Strings.
newtype Str = Str { unStr :: E }
-- | Boolean signals.
newtype BoolSig = BoolSig { unBoolSig :: E }
-- | Boolean constants.
newtype BoolD = BoolD { unBoolD :: E }
-- | Spectrum of the signal (see FFT and Spectral Processing at "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 (toPrimOr . 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 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 $ toPrimOr $ 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 extractors
getPrimUnsafe :: Val a => a -> Prim
getPrimUnsafe a = case ratedExpExp $ unwrap a of
ExpPrim p -> p
tabMap :: [E] -> [[Event Note]] -> TabMap
tabMap es ps = M.fromList $ zip (nub $ (concat $ mapM (getScoreTabs =<< ) ps) ++ (getInstrTabs =<< es)) [1 ..]
getInstrTabs :: E -> [LowTab]
getInstrTabs = cata $ \re -> (maybe [] id $ ratedExpDepends re) ++ case fmap fromPrimOr $ ratedExpExp re of
ExpPrim p -> getPrimTabs p
Tfm _ as -> concat as
ConvertRate _ _ a -> a
ExpNum a -> foldMap id a
Select _ _ a -> a
If info a b -> foldMap id info ++ a ++ b
ReadVar _ -> []
WriteVar _ a -> a
where fromPrimOr x = case unPrimOr x of
Left p -> getPrimTabs p
Right a -> a
getScoreTabs :: Event Note -> [LowTab]
getScoreTabs = (getPrimTabs =<< ) . eventContent
getPrimTabs :: Prim -> [LowTab]
getPrimTabs x = case x of
PrimTab (Right t) -> [t]
_ -> []
substPrimTab :: TabMap -> Prim -> Prim
substPrimTab m x = case x of
PrimTab (Right tab) -> PrimInt (m M.! tab)
_ -> x
substInstrTabs :: TabMap -> E -> E
substInstrTabs m = cata $ \re -> Fix $ re { ratedExpExp = fmap phi $ ratedExpExp re }
where phi x = case unPrimOr x of
Left p -> PrimOr $ Left $ substPrimTab m p
_ -> x
substScoreTabs :: TabMap -> [Event Note] -> [Event Note]
substScoreTabs m = fmap (fmap (fmap (substPrimTab m)))
defineScoreTabs :: Int -> [Event Note] -> [Event Note]
defineScoreTabs n = fmap (fmap (fmap (definePrimTab n)))
defineInstrTabs :: Int -> E -> E
defineInstrTabs n = cata $ \re -> Fix $ re { ratedExpExp = fmap phi $ ratedExpExp re }
where phi x = case unPrimOr x of
Left p -> PrimOr $ Left $ definePrimTab n p
_ -> x
definePrimTab :: Int -> Prim -> Prim
definePrimTab n x = case x of
PrimTab (Left tab) -> PrimTab (Right $ defineTab n tab)
_ -> x
defineTab :: Int -> Tab -> LowTab
defineTab midSize tab = LowTab size (tabGen tab) args
where size = defineTabSize midSize (tabSize tab)
args = defineTabArgs size (tabArgs tab)
defineTabArgs :: Int -> TabArgs -> [Double]
defineTabArgs size args = case args of
ArgsPlain as -> as
ArgsRelative as -> fromRelative size as
where fromRelative n as = substEvens (mkRelative n $ getEvens as) as
getEvens xs = case xs of
[] -> []
a:[] -> []
a:b:as -> b : getEvens as
substEvens evens xs = case (evens, xs) of
([], xs) -> xs
(es, []) -> []
(e:es, a:b:as) -> a : e : substEvens es as
mkRelative n as = fmap (fromIntegral . round . (s * )) as
where s = fromIntegral n / sum as
defineTabSize :: Int -> TabSize -> Int
defineTabSize base x = case x of
SizePlain n -> n
SizeDegree guardPoint degree ->
byGuardPoint guardPoint $
byDegree base degree
where byGuardPoint guardPoint
| guardPoint = (+ 1)
| otherwise = id
byDegree base n
| n == 0 = base
| n > 0 = base * (2 ^ n)
| n < 0 = base `div` (2 ^ abs n)
updateTabSize :: (TabSize -> TabSize) -> Tab -> Tab
updateTabSize phi x = case x of
TabExp _ -> error "you can change size only for primitive tables (made with gen-routines)"
primTab -> primTab{ tabSize = phi $ tabSize primTab }
--------------------------------------------
-- 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 D where
ar = setRate Ar
kr = setRate Kr
instance ToSig Sig where
ar = setRate Ar
kr = setRate Kr
instance ToSig Int where
ar = ar . double . fromIntegral
kr = kr . double . fromIntegral
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 D where
wrap = D . Fix
unwrap = unFix . unD
instance Val Str where
wrap = Str . Fix
unwrap = unFix . unStr
instance Val Tab where
wrap = TabExp . Fix
unwrap x = case x of
TabExp e -> unFix e
primTab -> (prim . PrimTab . Left) primTab
instance Val BoolSig where
wrap = BoolSig . Fix
unwrap = unFix . unBoolSig
instance Val BoolD where
wrap = BoolD . Fix
unwrap = unFix . unBoolD
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 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 D where
fromCsdTuple = return . Fix . unwrap
toCsdTuple = wrap . unFix . head
arityCsdTuple = const 1
instance CsdTuple Tab 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 (PrimOr $ Right e)
getRates :: MainExp a -> [Rate]
getRates (Tfm info _) = case infoSignature info of
MultiRate outs _ -> outs
isMultiOutSignature :: Signature -> Bool
isMultiOutSignature x = case x of
MultiRate _ _ -> True
_ -> False
------------------------------------------------
-- instrument outs
instance Out Sig where
toOut = return . return
instance Out a => Out [a] where
toOut = fmap concat . mapM toOut
instance Out a => Out (SE a) where
toOut = join . fmap toOut
instance (Out a, Out b) => Out (a, b) where
toOut (a, b) = liftA2 (++) (toOut a) (toOut b)
instance (Out a, Out b, Out c) => Out (a, b, c) where
toOut (a, b, c) = toOut (a, (b, c))
instance (Out a, Out b, Out c, Out d) => Out (a, b, c, d) where
toOut (a, b, c, d) = toOut (a, (b, c, d))
instance (Out a, Out b, Out c, Out d, Out e) => Out (a, b, c, d, e) where
toOut (a, b, c, d, e) = toOut (a, (b, c, d, e))
instance (Out a, Out b, Out c, Out d, Out e, Out f) => Out (a, b, c, d, e, f) where
toOut (a, b, c, d, e, f) = toOut (a, (b, c, d, e, f))
instance (Out a, Out b, Out c, Out d, Out e, Out f, Out g) => Out (a, b, c, d, e, f, g) where
toOut (a, b, c, d, e, f, g) = toOut (a, (b, c, d, e, f, g))
instance (Out a, Out b, Out c, Out d, Out e, Out f, Out g, Out h) => Out (a, b, c, d, e, f, g, h) where
toOut (a, b, c, d, e, f, g, h) = toOut (a, (b, c, d, e, f, g, h))