module Csound.Exp.Mix(
-- * Container for sounds (triggered with notes and mixers)
Mix(..), M(..), nchnls,
effect, effectS,
sco, mix, --, midi, pgmidi
rescaleCsdEventListM
) where
import Data.Traversable(traverse)
import qualified Data.Map as M
import Csound.Tfm.Tab
import Csound.Exp
import Csound.Exp.Wrapper
import Csound.Exp.SE
import Csound.Exp.GE
import Csound.Exp.Instr
import Csound.Exp.Arg
import Csound.Exp.Tuple(Out(..), CsdTuple, fromCsdTuple, toCsdTuple, outArity)
import Csound.Exp.Options
import Csound.Exp.EventList
newtype Mix a = Mix { unMix :: GE M }
data M
= Snd InstrId (CsdEventList Note)
| Eff InstrId (CsdEventList M)
nchnls :: Out a => f (Mix a) -> Int
nchnls = outArity . proxy
where proxy :: f (Mix a) -> a
proxy = undefined
-- | Play a bunch of notes with the given instrument.
--
-- > res = sco instrument scores
--
-- * @instrument@ is a function that takes notes and produces a
-- tuple of signals (maybe with some side effect)
--
-- * @scores@ are some notes (see the module "Temporal.Media"
-- on how to build complex scores out of simple ones)
--
-- Let's try to understand the type of the output. It's @Score (Mix (NoSE a))@.
-- What does it mean? Let's look at the different parts of this type:
--
-- * @Score a@ - you can think of it as a container of some values of
-- type @a@ (every value of type @a@ starts at some time and lasts
-- for some time in seconds)
--
-- * @Mix a@ - is an output of Csound instrument it can be one or several
-- signals ('Csound.Base.Sig' or 'Csound.Base.CsdTuple').
--
-- *NoSE a* - it's a tricky part of the output. 'NoSE' means literaly 'no SE'.
-- It tells to the type checker that it can skip the 'Csound.Base.SE' wrapper
-- from the type 'a' so that @SE a@ becomes just @a@ or @SE (a, SE b, c)@
-- becomes @(a, b, c)@. Why should it be? I need 'SE' to deduce the order of the
-- instruments that have side effects. I need it within one instrument. But when
-- instrument is rendered i no longer need 'SE' type. So 'NoSE' lets me drop it
-- from the output type.
sco :: (Arg a, Out b, CsdSco f) => (a -> b) -> f a -> f (Mix (NoSE b))
sco instr notes = singleCsdEvent 0 (csdEventListDur events) $ Mix $ do
events' <- traverse renderNote events
instrId <- saveSourceInstrCached instr soundSourceExp
return $ Snd instrId events'
where events = toCsdEventList notes
renderNote :: (Arg a) => a -> GE [Prim]
renderNote a = tfmNoteStrs =<< tfmNoteTabs (toNote a)
tfmNoteTabs :: Note -> GE Note
tfmNoteTabs xs = do
opt <- getOptions
let xs' = defineNoteTabs (tabFi opt) xs
tabs = getPrimTabs =<< xs'
ids <- mapM saveTab tabs
let tabMap = M.fromList $ zip tabs ids
return $ substNoteTabs tabMap xs'
tfmNoteStrs :: Note -> GE Note
tfmNoteStrs xs = do
ids <- mapM saveStr strs
let strMap = M.fromList $ zip strs ids
return $ substNoteStrs strMap xs
where strs = getStrings xs
-- | Applies an effect to the sound. Effect is applied to the sound on the give track.
--
-- > res = mix effect sco
--
-- * @effect@ - a function that takes a tuple of signals and produces
-- a tuple of signals.
--
-- * @sco@ - something that is constructed with 'Csound.Base.sco' or
-- 'Csound.Base.mix' or 'Csound.Base.midi'.
--
-- With the function 'Csound.Base.mix' you can apply a reverb or adjust the
-- level of the signal. It functions like a mixing board but unlike mixing
-- board it produces the value that you can arrange with functions from the
-- module "Temporal.Media". You can delay it mix with some other track and
-- apply some another effect on top of it!
mix :: (Out a, Out b, CsdSco f) => (a -> b) -> f (Mix a) -> f (Mix (NoSE b))
mix eff sigs = singleCsdEvent 0 (csdEventListDur events) $ Mix $ do
notes <- traverse unMix events
instrId <- saveMixerInstr =<< effectExp eff
return $ Eff instrId notes
where events = toCsdEventList sigs
{-
-- | Triggers a midi-instrument (like Csound's massign). The result type
-- is a fake one. It's wrapped in the 'Csound.Base.Score' for the ease of mixing.
-- you can not delay or stretch it. The only operation that is meaningful
-- for it is 'Temporal.Media.chord'. But you can add effects to it with 'Csound.Base.mix'!
midi :: (Out a) => Channel -> (Msg -> a) -> Score (Mix (NoSE a))
midi = genMidi Massign
-- | Triggers a - midi-instrument (like Csound's pgmassign).
pgmidi :: (Out a) => Maybe Int -> Channel -> (Msg -> a) -> Score (Mix (NoSE a))
pgmidi mchn = genMidi (Pgmassign mchn)
genMidi :: (Out a) => MidiType -> Channel -> (Msg -> a) -> Score (Mix (NoSE a))
genMidi midiType chn f = temp $ Mid $ mkInstr getMidiArity Msg f (Just (midiType, chn))
where getMidiArity = mkArity (const 0) outArity
-}
-- | Constructs the effect that applies a given function on every channel.
effect :: (CsdTuple a, Out a) => (Sig -> Sig) -> (a -> a)
effect f = toCsdTuple . fmap (toE . f . fromE) . fromCsdTuple
-- | Constructs the effect that applies a given function with side effect
-- (it uses random opcodes or delays) on every channel.
effectS :: (CsdTuple a, Out a) => (Sig -> SE Sig) -> (a -> SE a)
effectS f a = fmap fromOut $ mapM f =<< toOut a
rescaleCsdEventListM :: CsdEventList M -> CsdEventList M
rescaleCsdEventListM es =
es { csdEventListNotes = fmap rescaleCsdEventM $ csdEventListNotes es }
rescaleCsdEventM :: CsdEvent M -> CsdEvent M
rescaleCsdEventM (start, dur, evt) = (start, dur, phi evt)
where phi x = case x of
Snd n evts -> Snd n $ rescaleCsdEventList (dur/localDur) evts
Eff n evts -> Eff n $ rescaleCsdEventListM $ rescaleCsdEventList (dur/localDur) evts
where localDur = case x of
Snd _ evts -> csdEventListDur evts
Eff _ evts -> csdEventListDur evts