packages feed

csound-expression-5.4.3: src/Csound/IO.hs

{-# Language FlexibleInstances, UndecidableInstances, CPP #-}
-- | Rendering of Csound files and playing the music in real time.
--
-- How are we going to get the sound out of Haskell code?
-- Instruments are ready and we have written all the scores for them.
-- Now, it's time to use the rendering functions. We can render haskell expressions
-- to Csound code. A rendering function takes a value that represents a sound (it's a tuple of signals)
-- and produces a string with Csound code. It can take a value that represents
-- the flags for the csound compiler and global settings ('Csound.Options').
-- Then we can save this string to file and convert it to sound with csound compiler
--
-- > csound -o music.wav music.csd
--
-- Or we can play it in real time with -odac flag. It sends the sound directly to
-- soundcard. It's usefull when we are using midi or tweek the parameters in real time
-- with sliders or knobs.
--
-- > csound -odac music.csd
--
-- The main function of this module is 'Csound.IO.renderCsdBy'. Other function are nothing but
-- wrappers that produce the Csound code and make something useful with it (saving to file,
-- playing with specific player or in real time).
module Csound.IO (
    -- * Rendering
    RenderCsd(..),
    CsdArity(..),
    renderCsd,
    writeCsd, writeCsdBy,
    writeSnd, writeSndBy,

    -- * Playing the sound
    playCsd, playCsdBy,
    mplayer, mplayerBy, totem, totemBy,

    -- * Live performance
    dac, dacBy, vdac, vdacBy,

    -- * Render and run
    csd, csdBy,

    -- * Save user options
    saveUserOptions,

    -- * Render and run with cabbage
    runCabbage, runCabbageBy,

    -- * Aliases for type inference
    -- | Sometimes the type class @RenderCsd@ is too whide for us.
    -- It cn be hard to use in the interpreter without explicit signatures.
    -- There are functions to help the type inference.
    -- ** For processing inputs
    onCard1, onCard2, onCard4, onCard6, onCard8,

    -- * Config with command line arguments
    -- | With the functions we can add global config parameters to the rendered file.
    -- We can supply different parameters with @--omacro@ flag.
    --
    -- An example:
    --
    -- > dac $ osc (sig $ readMacrosDouble "FREQ" 440)
    --
    -- Here we define frequency as a global parameter. It's available by name @"FREQ"@.
    -- If we run the program with no flags it would play the default 440 Hz. But we can change that like this:
    --
    -- > csound tmp.csd --omacro:FREQ=330
    --
    -- We can update the macro-arguments with flag @--omacro:NAME=VALUE@.
    readMacrosString, readMacrosDouble, readMacrosInt
) where

--import Control.Concurrent
import Control.Monad

import System.Process
import qualified Control.Exception as E

import Data.Default
import Csound.Typed
import Csound.Control.Gui

import Csound.Options(setSilent, setDac, setAdc, setDacBy, setAdcBy, setCabbage)
import Temporal.Class(Harmony(..))

import qualified Data.List as L

render :: Sigs a => Options -> SE a -> IO String
render = renderOutBy

render_ :: Options -> SE () -> IO String
render_ = renderOutBy_

data CsdArity = CsdArity
  { csdArity'inputs  :: Int
  , csdArity'outputs :: Int
  } deriving (Show, Eq)

class RenderCsd a where
    renderCsdBy :: Options -> a -> IO String
    csdArity :: a -> CsdArity

hasInputs :: RenderCsd a => a -> Bool
hasInputs = ( > 0) . csdArity'inputs . csdArity

hasOutputs :: RenderCsd a => a -> Bool
hasOutputs = ( > 0) . csdArity'outputs . csdArity

instance {-# OVERLAPPING #-} RenderCsd (SE ()) where
    renderCsdBy = render_
    csdArity _ = CsdArity 0 0

#if __GLASGOW_HASKELL__ >= 710
instance {-# OVERLAPPABLE #-} Sigs a => RenderCsd a where
  renderCsdBy opt a = render opt (return a)
  csdArity a = CsdArity 0 (tupleArity a)

instance {-# OVERLAPPABLE #-} Sigs a => RenderCsd (SE a) where
  renderCsdBy opt a = render opt a
  csdArity a = CsdArity 0 (outArity a)

instance {-# OVERLAPPABLE #-} Sigs a => RenderCsd (Source a) where
  renderCsdBy opt a = renderCsdBy opt (fromSource a)
  csdArity a = CsdArity 0 (tupleArity $ proxySource a)
      where

instance {-# OVERLAPPABLE #-} Sigs a => RenderCsd (Source (SE a)) where
  renderCsdBy opt a = renderCsdBy opt (fromSourceSE a)
  csdArity a = CsdArity 0 (tupleArity $ proxySE $ proxySource a)

instance {-# OVERLAPPABLE #-} Sigs a => RenderCsd (Sco (Mix a)) where
  renderCsdBy opt a = renderCsdBy opt (mix a)
  csdArity a = CsdArity 0 (tupleArity $ proxy a)
    where
      proxy :: Sco (Mix a) -> a
      proxy = const undefined

instance {-# OVERLAPPABLE #-} Sigs a => RenderCsd [Sco (Mix a)] where
  renderCsdBy opt a = renderCsdBy opt (mix $ har a)
  csdArity a = CsdArity 0 (tupleArity $ proxy a)
    where
      proxy :: [Sco (Mix a)] -> a
      proxy = const undefined

#endif

proxySource :: Source a -> a
proxySource = const undefined

proxySE :: SE a -> a
proxySE = const undefined

proxyFun :: (a -> b) -> (a, b)
proxyFun = const undefined

proxyIn :: (a -> b) -> a
proxyIn = fst . proxyFun

proxyOut :: (a -> b) -> b
proxyOut = snd . proxyFun

instance {-# OVERLAPPABLE #-} (Sigs a, Sigs b) => RenderCsd (a -> b) where
    renderCsdBy opt f = renderEffBy opt (return . f)
    csdArity a = CsdArity (tupleArity $ proxyIn a) (tupleArity $ proxyOut a)

instance {-# OVERLAPPABLE #-} (Sigs a, Sigs b) => RenderCsd (a -> SE b) where
    renderCsdBy opt f = renderEffBy opt f
    csdArity a = CsdArity (tupleArity $ proxyIn a) (tupleArity $ proxySE $ proxyOut a)

instance {-# OVERLAPPABLE #-} (Sigs a, Sigs b) => RenderCsd (a -> Source b) where
    renderCsdBy opt f = renderEffBy opt (fromSource . f)
    csdArity a = CsdArity (tupleArity $ proxyIn a) (tupleArity $ proxySource $ proxyOut a)

instance (Sigs a, Sigs b) => RenderCsd (a -> Source (SE b)) where
    renderCsdBy opt f = renderEffBy opt (fromSourceSE . f)
    csdArity a = CsdArity (tupleArity $ proxyIn a) (tupleArity $ proxySE $ proxySource $ proxyOut a)

instance {-# OVERLAPPING #-} (Sigs a) => RenderCsd (a -> Source (SE Sig2)) where
    renderCsdBy opt f = renderEffBy opt (fromSourceSE . f)
    csdArity a = CsdArity (tupleArity $ proxyIn a) (tupleArity $ proxySE $ proxySource $ proxyOut a)

instance {-# OVERLAPPING #-} RenderCsd (Source ()) where
    renderCsdBy opt src = renderCsdBy opt $ do
        (ui, _) <- src
        panel ui
    csdArity _ = CsdArity 0 0

instance {-# OVERLAPPING #-} RenderCsd (Source (SE ())) where
    renderCsdBy opt src = renderCsdBy opt (joinSource src)
    csdArity _ = CsdArity 0 0

-- | Renders Csound file.
renderCsd :: RenderCsd a => a -> IO String
renderCsd = renderCsdBy def

-- | Render Csound file and save it to the give file.
writeCsd :: RenderCsd a => String -> a -> IO ()
writeCsd file a = writeFile file =<< renderCsd a

-- | Render Csound file with options and save it to the give file.
writeCsdBy :: RenderCsd a => Options -> String -> a -> IO ()
writeCsdBy opt file a = writeFile file =<< renderCsdBy opt a

-- | Render Csound file and save result sound to the wav-file.
writeSnd :: RenderCsd a => String -> a -> IO ()
writeSnd = writeSndBy def

-- | Render Csound file with options and save result sound to the wav-file.
writeSndBy :: RenderCsd a => Options -> String -> a -> IO ()
writeSndBy opt file a = do
    writeCsdBy opt fileCsd a
    runWithUserInterrupt (postSetup opt) $ unwords ["csound -o", file, fileCsd, logTrace opt]
    where fileCsd = "tmp.csd"

logTrace :: Options -> String
logTrace opt
  | csdNeedTrace opt = ""
  | otherwise        = "--logfile=null"

-- | Renders Csound file, saves it to the given file, renders with csound command and plays it with the given program.
--
-- > playCsd program file csd
--
-- Produces files @file.csd@ (with 'Csound.Render.Mix.renderCsd') and @file.wav@ (with @csound@) and then invokes:
--
-- > program "file.wav"
playCsd :: (RenderCsd a) => (String -> IO ()) -> String -> a -> IO ()
playCsd = playCsdBy def

-- | Works just like 'Csound.Render.Mix.playCsd' but you can supply csound options.
playCsdBy :: (RenderCsd a) => Options -> (String -> IO ()) -> String -> a -> IO ()
playCsdBy opt player file a = do
    writeCsdBy opt fileCsd a
    runWithUserInterrupt (postSetup opt) $ unwords ["csound -o", fileWav, fileCsd, logTrace opt]
    player fileWav
    return ()
    where fileCsd = file ++ ".csd"
          fileWav = file ++ ".wav"

simplePlayCsdBy :: (RenderCsd a) => Options -> String -> String -> a -> IO ()
simplePlayCsdBy opt player = playCsdBy opt phi
    where phi file = do
            runWithUserInterrupt (pure ()) $ unwords [player, file]

-- | Renders csound code to file @tmp.csd@ with flags set to @-odac@, @-iadc@ and @-Ma@
-- (sound output goes to soundcard in real time).
dac :: (RenderCsd a) => a -> IO ()
dac = dacBy def

-- | 'Csound.Base.dac' with options.
dacBy :: (RenderCsd a) => Options -> a -> IO ()
dacBy opt' a = do
    writeCsdBy opt "tmp.csd" a
    runWithUserInterrupt (postSetup opt') $ unwords ["csound tmp.csd", logTrace opt']
    where
      opt = mconcat [opt', withDac, withAdc]

      withDac
        | hasJackConnections opt' = setDacBy "null"
        | hasOutputs a            = setDac
        | otherwise               = mempty

      withAdc
        | hasJackConnections opt' = setAdcBy "null"
        | hasInputs a             = setAdc
        | otherwise               = mempty

-- | Output to dac with virtual midi keyboard.
vdac :: (RenderCsd a) => a -> IO ()
vdac = dacBy (setVirtual def)

-- | Output to dac with virtual midi keyboard with specified options.
vdacBy :: (RenderCsd a) => Options -> a -> IO ()
vdacBy opt = dacBy (setVirtual opt)

setVirtual :: Options -> Options
setVirtual a = a { csdFlags = (csdFlags a) { rtmidi = Just VirtualMidi, midiRT = m { midiDevice = Just "0" } } }
    where m = midiRT $ csdFlags a

-- | Renders to file @tmp.csd@ and invokes the csound on it.
csd :: (RenderCsd a) => a -> IO ()
csd = csdBy setSilent

-- | Renders to file @tmp.csd@ and invokes the csound on it.
csdBy :: (RenderCsd a) => Options -> a -> IO ()
csdBy options a = do
    writeCsdBy (setSilent `mappend` options) "tmp.csd" a
    runWithUserInterrupt (postSetup options) $ unwords ["csound tmp.csd", logTrace options]

postSetup :: Options -> IO ()
postSetup opt = jackConnect opt

jackConnect :: Options -> IO ()
jackConnect opt
  | Just conns <- csdJackConnect opt = case conns of
                                         [] -> pure ()
                                         _  -> void $ runCommand $ jackCmd conns
  | otherwise                        = pure ()
  where
    addSleep = ("sleep 0.1; " `mappend` )

    jackCmd = addSleep . L.intercalate ";" . fmap jackConn
    jackConn (port1, port2) = unwords ["jack_connect", port1, port2]

hasJackConnections :: Options -> Bool
hasJackConnections opt
  | Just conns <- csdJackConnect opt = not $ null conns
  | otherwise                        = False

----------------------------------------------------------
-- players

-- | Renders to tmp.csd and tmp.wav and plays with mplayer.
mplayer :: (RenderCsd a) => a -> IO ()
mplayer = mplayerBy def

-- | Renders to tmp.csd and tmp.wav and plays with mplayer.
mplayerBy :: (RenderCsd a) => Options -> a -> IO ()
mplayerBy opt = simplePlayCsdBy opt "mplayer" "tmp"

-- | Renders to tmp.csd and tmp.wav and plays with totem player.
totem :: (RenderCsd a) => a -> IO ()
totem = totemBy def

-- | Renders to tmp.csd and tmp.wav and plays with totem player.
totemBy :: (RenderCsd a) => Options -> a -> IO ()
totemBy opt = simplePlayCsdBy opt "totem" "tmp"

----------------------------------------------------------
-- handle user interrupts

runWithUserInterrupt :: IO () -> String -> IO ()
runWithUserInterrupt setup cmd = do
    pid <- runCommand cmd
    setup
    E.catch (waitForProcess pid >> return ()) (onUserInterrupt pid)
    where
        onUserInterrupt :: ProcessHandle -> E.AsyncException -> IO ()
        onUserInterrupt pid x = case x of
            E.UserInterrupt -> terminateProcess pid
            e               -> E.throw e

----------------------------------------------------------

-- | Runs the csound files with cabbage engine.
-- It invokes the Cabbage command line utility and setts all default cabbage flags.
runCabbage :: (RenderCsd a) => a -> IO ()
runCabbage = runCabbageBy def

-- | Runs the csound files with cabbage engine with user defined options.
-- It invokes the Cabbage command line utility and setts all default cabbage flags.
runCabbageBy :: (RenderCsd a) => Options -> a -> IO ()
runCabbageBy opt' a = do
    writeCsdBy opt "tmp.csd" a
    runWithUserInterrupt (pure ()) $ "Cabbage tmp.csd"
    where opt = opt' `mappend` setCabbage

------------------------------

-- | Alias to process inputs of single input audio-card.
onCard1 :: (Sig -> a) -> (Sig -> a)
onCard1= id

-- | Alias to process inputs of stereo input audio-card.
onCard2 :: (Sig2 -> a) -> (Sig2 -> a)
onCard2= id

-- | Alias to process inputs of audio-card with 4 inputs.
onCard4 :: (Sig4 -> a) -> (Sig4 -> a)
onCard4= id

-- | Alias to process inputs of audio-card with 6 inputs.
onCard6 :: (Sig6 -> a) -> (Sig6 -> a)
onCard6= id

-- | Alias to process inputs of audio-card with 8 inputs.
onCard8 :: (Sig8 -> a) -> (Sig8 -> a)
onCard8= id


#if __GLASGOW_HASKELL__ < 710

-- Sig
setArity n a = CsdArity 0 n

instance RenderCsd Sig                  where { renderCsdBy opt a = render opt (return a)             , csdArity = setArity 1 }
instance RenderCsd (SE Sig)             where { renderCsdBy opt a = render opt a }                    , csdArity = setArity 1 }
instance RenderCsd (Source Sig)         where { renderCsdBy opt a = renderCsdBy opt (fromSource a)    , csdArity = setArity 1 }
instance RenderCsd (Source (SE Sig))    where { renderCsdBy opt a = renderCsdBy opt (fromSourceSE a)  , csdArity = setArity 1 }

-- Sig2

instance RenderCsd Sig2                  where { renderCsdBy opt a = render opt (return a)            , csdArity = setArity 2 }
instance RenderCsd (SE Sig2)             where { renderCsdBy opt a = render opt a                     , csdArity = setArity 2 }
instance RenderCsd (Source Sig2)         where { renderCsdBy opt a = renderCsdBy opt (fromSource a)   , csdArity = setArity 2 }
instance RenderCsd (Source (SE Sig2))    where { renderCsdBy opt a = renderCsdBy opt (fromSourceSE a) , csdArity = setArity 2 }

-- Sig3

instance RenderCsd Sig3                  where { renderCsdBy opt a = render opt (return a)            , csdArity = setArity 3 }
instance RenderCsd (SE Sig3)             where { renderCsdBy opt a = render opt a                     , csdArity = setArity 3 }
instance RenderCsd (Source Sig3)         where { renderCsdBy opt a = renderCsdBy opt (fromSource a)   , csdArity = setArity 3 }
instance RenderCsd (Source (SE Sig3))    where { renderCsdBy opt a = renderCsdBy opt (fromSourceSE a) , csdArity = setArity 3 }

-- Sig4

instance RenderCsd Sig4                  where { renderCsdBy opt a = render opt (return a)            , csdArity = setArity 4 }
instance RenderCsd (SE Sig4)             where { renderCsdBy opt a = render opt a                     , csdArity = setArity 4 }
instance RenderCsd (Source Sig4)         where { renderCsdBy opt a = renderCsdBy opt (fromSource a)   , csdArity = setArity 4 }
instance RenderCsd (Source (SE Sig4))    where { renderCsdBy opt a = renderCsdBy opt (fromSourceSE a) , csdArity = setArity 4 }

-- Sig5

instance RenderCsd Sig5                  where { renderCsdBy opt a = render opt (return a)            , csdArity = setArity 5 }
instance RenderCsd (SE Sig5)             where { renderCsdBy opt a = render opt a                     , csdArity = setArity 5 }
instance RenderCsd (Source Sig5)         where { renderCsdBy opt a = renderCsdBy opt (fromSource a)   , csdArity = setArity 5 }
instance RenderCsd (Source (SE Sig5))    where { renderCsdBy opt a = renderCsdBy opt (fromSourceSE a) , csdArity = setArity 5 }

-- Sig6

instance RenderCsd Sig6                  where { renderCsdBy opt a = render opt (return a)            , csdArity = setArity 6 }
instance RenderCsd (SE Sig6)             where { renderCsdBy opt a = render opt a                     , csdArity = setArity 6 }
instance RenderCsd (Source Sig6)         where { renderCsdBy opt a = renderCsdBy opt (fromSource a)   , csdArity = setArity 6 }
instance RenderCsd (Source (SE Sig6))    where { renderCsdBy opt a = renderCsdBy opt (fromSourceSE a) , csdArity = setArity 6 }

-- Sig7

instance RenderCsd Sig7                  where { renderCsdBy opt a = render opt (return a)            , csdArity = setArity 7 }
instance RenderCsd (SE Sig7)             where { renderCsdBy opt a = render opt a                     , csdArity = setArity 7 }
instance RenderCsd (Source Sig7)         where { renderCsdBy opt a = renderCsdBy opt (fromSource a)   , csdArity = setArity 7 }
instance RenderCsd (Source (SE Sig7))    where { renderCsdBy opt a = renderCsdBy opt (fromSourceSE a) , csdArity = setArity 7 }


-- Sig8

instance RenderCsd Sig8                  where { renderCsdBy opt a = render opt (return a)            , csdArity = setArity 8 }
instance RenderCsd (SE Sig8)             where { renderCsdBy opt a = render opt a                     , csdArity = setArity 8 }
instance RenderCsd (Source Sig8)         where { renderCsdBy opt a = renderCsdBy opt (fromSource a)   , csdArity = setArity 8 }
instance RenderCsd (Source (SE Sig8))    where { renderCsdBy opt a = renderCsdBy opt (fromSourceSE a) , csdArity = setArity 8 }


instance RenderCsd (Sco (Mix Sig))       where { renderCsdBy opt a = renderCsdBy opt $ mix a , csdArity = setArity 1 }
instance RenderCsd (Sco (Mix Sig2))      where { renderCsdBy opt a = renderCsdBy opt $ mix a , csdArity = setArity 2 }
instance RenderCsd (Sco (Mix Sig3))      where { renderCsdBy opt a = renderCsdBy opt $ mix a , csdArity = setArity 3 }
instance RenderCsd (Sco (Mix Sig4))      where { renderCsdBy opt a = renderCsdBy opt $ mix a , csdArity = setArity 4 }
instance RenderCsd (Sco (Mix Sig5))      where { renderCsdBy opt a = renderCsdBy opt $ mix a , csdArity = setArity 5 }

instance RenderCsd [Sco (Mix Sig)]       where { renderCsdBy opt a = renderCsdBy opt $ mix $ har a , csdArity = setArity 1 }
instance RenderCsd [Sco (Mix Sig2)]      where { renderCsdBy opt a = renderCsdBy opt $ mix $ har a , csdArity = setArity 2 }
instance RenderCsd [Sco (Mix Sig3)]      where { renderCsdBy opt a = renderCsdBy opt $ mix $ har a , csdArity = setArity 3 }
instance RenderCsd [Sco (Mix Sig4)]      where { renderCsdBy opt a = renderCsdBy opt $ mix $ har a , csdArity = setArity 4 }
instance RenderCsd [Sco (Mix Sig5)]      where { renderCsdBy opt a = renderCsdBy opt $ mix $ har a , csdArity = setArity 5 }

#endif