{-# Language FlexibleInstances #-}
-- | Defines instance of 'Csound.Base.CsdSco' for 'Temporal.Music.Score.Score' and
--  reexports all functions from packages csound-expression and temporal-music-notation-western.
--
-- We can trigger Csound orchestra with 'Temporal.Music.Score.Score'.
--
-- How to put the values in the container 'Temporal.Music.Score.Score'? There are many functions to construct the 'Temporal.Music.Score.Score'.
--
-- They live in the module "Temporal.Music.Score". If you are not familiar with it, you can start with six basic functions. 
--
-- * 'Temporal.Music.Score.rest' -- makes a pause that lasts for some time (in seconds).
--
-- * 'Temporal.Music.Score.temp' -- makes a score of one note that lasts for one second.    
--
-- * 'Temporal.Music.Score.mel' -- plays a list of notes in sequence (one after the other, short for @melody@).
-- 
-- * 'Temporal.Music.Score.har' -- plays a list of notes in parallel (at the same time, short for @harmony@).
--
-- * 'Temporal.Music.Score.del' -- delays all notes for some time (short for @delay@).
--
-- * 'Temporal.Music.Score.str' -- change the tempo for all notes by the given ratio (short for @stretch@).
--
-- Let's play something:
--
-- > res = str 0.5 $ mel [ temp a, str 2 $ temp b, rest 1, har [temp a, temp b] ]
--
-- There are two handy infix operators for delay and stretch: @(+|)@ and @(*|)@. So we can write the previous score:
--
-- > res = 0.5 *| mel [ temp a, 2 *| temp b, 1 +| har [temp a, temp b] ]
--
-- There are shortcuts for notes in western notation (a is 440 Hz).
--
-- > a, b, c, d, e, f, g
--
-- Notes reside in the same octave. To get the notes in higher or lower octaves
-- we can apply the functions:
--
-- * 'Temporal.Music.Score.high', 'Temporal.Music.Score.low' -- take note an octaver higher or lower
--
-- * 'Temporal.Music.Score.higher' n, 'Temporal.Music.Score.lower' n -- take note for @n@ octaves higher or lower
--
-- There are shortcuts for stretching the notes and rests:
--
-- > bn, wn, qn, en, sn -- brevis, whole, quarter, eight, sixteenth notes
--
-- and for rests
--
-- > bnr, wnr, qnr, enr, snr
--
-- These functions transform the melodies with given factors.
-- We can construct melodies:
--
-- > melody = mel [qn $ mel [c, e, g], bn $ har [c, e, g, high c], wnr]
--
-- Then we can apply a csound instrument to the melody to get the signal.
--
-- > res = notes someInstr melody
--
-- Now let's mix it to the signal and send the output to speakers:
--
-- > dac $ mix res 
--
-- WARNING: The function 'dac' spawns a csound process in the background which
-- can run forever. If your haskell build tool doesn't kills the child processes with
-- haskell-runing process (As far as I know Sublime Editor doesn't, but vim does) 
-- it's better to run the program from ghci and to stop it press @Ctrl+C@:
--
-- > % ghci MyMusic
-- > MyMusic> main 
-- >
-- >    ... The programm runs ... press Ctrl+C to stop it
-- >
-- 
-- @runhaskell@ doesn't stop the child process. So it's better to use the
-- @dac@ function with terminal.
-- 
-- If signal is to loud or to quiet we can scale it:
--
-- > dac $ mul factor $ mix res 
-- 
-- We can make it brighter with reverb ('Csound.Air.smallRoom', 'Csound.Air.smallHall', 'Csound.Air.largeHall', 'Csound.Air.reverTime')
--
-- > dac $ mul 0.2 $ smallHall $ mix res 

module Csound (
    
    -- * Converters
    CsdNote(..), csdNote, CsdDrum, csdDrum, N, Dr,

    -- * Scores
    --
    -- | Funxtions that apply instruments to scores. 
    --
    -- Notes on signatures:
    --
    -- * The class 'Csound.Base.Outs' includes the tuples of signals 
    --   that have side effects or have no side effects.
    --
    -- * @SigOuts@ -- means an underlying tuple of signals.
    --   For instance, it can be @Sig@  or @SE Sig@, the @SigOuts@
    --   converts it to the @Sig@. The @SigOuts@ removes the 
    --   prefix @SE@ if it is present.
    --
    -- * To get the final signal out of the type @Score (Mix (SigOuts b))@
    --   we should apply the function 'Csound.Base.mix' to it:
    --
    -- > mix :: (CsdSco f, Sigs a) => f (Mix a) -> a
    -- 
    --  Or we can continue to build the track of signals with 
    --  functions loke 'Temporal.Music.Score.mel', 'Temporal.Music.Score.har', 'Temporal.Music.Score.str'.
    notes, drums,    

    -- * Midis   
    --
    -- | Plays instruments with midi devices.
    --
    -- > import Csound 
    -- > import Csound.Patch(vibraphone2)
    -- >
    -- > -- | Plays with virtual midi device (if you have a midi device
    -- > -- you can substitute @vdac@ for @dac@).
    -- > main = vdac $ mul 0.1 $ largeHall $ onMidi vibraphone2
    -- 
    onMidi, onMidin, onPgmidi, 
    onMidiWith, onMidinWith, onPgmidiWith,    
    
    module Temporal.Music.Western.P12,
    module Csound.Base
) where

import Temporal.Media(Track)
import Csound.Base
import Temporal.Music.Western.P12 hiding (delay, line, tone)

instance CsdSco (Track Double) where
    toCsdEventList x = CsdEventList (dur x) (fmap toEvt $ render x)
        where toEvt a = (eventStart a, eventDur a, eventContent a)
    singleCsdEvent (start, dt, a) = del start $ str dt $ temp a

type N  = CsdNote Unit
type Dr = CsdDrum Unit

-- | Contains amplitude, frequency and auxiliary parameters.
--
-- > (amplitude, frequencyInHz, timbralParameters)
type CsdNote a = (D, D, a)

-- | Contains amplitude and auxiliary parameters.
--
-- > (amplitude, timbralParameters)
type CsdDrum a = (D, a)

-- | Converts the @Note@ to low level @CsdNote@.
csdNote :: Default a => Note a -> CsdNote a
csdNote a =  	
	( double $ volumeAsDouble $ noteVolume a
    , double $ absPitch $ notePitch a
    , maybe def id $ noteParam a)

-- | Converts the @Note@ to low level @CsdNote@.
csdDrum :: Default a => Drum a -> CsdDrum a
csdDrum a =  	
	( double $ volumeAsDouble $ drumVolume a
    , maybe def id $ drumParam a)

-- | Plays the notes with csound instrument.
notes :: (Arg a, Default a, Outs b) => (CsdNote a -> b) -> Score (Note a) -> Score (Mix (SigOuts b))
notes g f = sco (toOuts . g) (fmap csdNote f)

-- | Plays the drum notes with csound instrument.
drums :: (Arg a, Default a, Outs b) => (CsdDrum a -> b) -> Score (Drum a) -> Score (Mix (SigOuts b))
drums g f = sco (toOuts . g) (fmap csdDrum f)

toMidiInstr :: (Default a, Outs b) => (CsdNote a -> b) -> (Msg -> SE (SigOuts b))
toMidiInstr f = \msg -> toOuts $ f (ampmidi msg 1, cpsmidi msg, def) 

toMidiInstrWith :: (Outs b) => a -> (CsdNote a -> b) -> (Msg -> SE (SigOuts b))
toMidiInstrWith defVal f = \msg -> toOuts $ f (ampmidi msg 1, cpsmidi msg, defVal) 

-- | Triggers an instrument on all midi-channels.
onMidi :: (Default a, Outs b) => (CsdNote a -> b) -> SigOuts b
onMidi f = midi $ toMidiInstr f

-- | Triggers an instrument on the given midi-channel.
onMidin :: (Default a, Outs b) => Channel -> (CsdNote a -> b) -> SigOuts b
onMidin chn f = midin chn $ toMidiInstr f

-- | Triggers an instrument on channel and programm bank.
onPgmidi :: (Default a, Outs b) => Maybe Int -> Channel -> (CsdNote a -> b) -> SigOuts b
onPgmidi pgm chn f = pgmidi pgm chn $ toMidiInstr f

-- | Just like @onMidi@ but takes a value for default auxiliary parameters.
onMidiWith :: (Outs b) => a -> (CsdNote a -> b) -> SigOuts b
onMidiWith defVal f = midi $ toMidiInstrWith defVal f

-- | Just like @onMidin@ but takes a value for default auxiliary parameters.
onMidinWith :: (Outs b) => a -> Channel -> (CsdNote a -> b) -> SigOuts b
onMidinWith defVal chn f = midin chn $ toMidiInstrWith defVal f

-- | Just like @onPgmidi@ but takes a value for default auxiliary parameters.
onPgmidiWith :: (Outs b) => a -> Maybe Int -> Channel -> (CsdNote a -> b) -> SigOuts b
onPgmidiWith defVal pgm chn f = pgmidi pgm chn $ toMidiInstrWith defVal f