-- | Non-realtime score generation.
module Sound.SC3.Server.NRT where

import qualified Data.ByteString.Lazy as B {- bytestring -}
import Data.Maybe
import Sound.OpenSoundControl {- hosc -}
import Sound.OpenSoundControl.Coding.Byte
import System.Exit
import System.IO
import System.Process {- process -}

-- | Encode and prefix with encoded length.
oscWithSize :: Bundle -> B.ByteString
oscWithSize o =
    let b = encodeBundle o
        l = encode_i32 (fromIntegral (B.length b))
    in B.append l b

-- | An 'NRT' score is a sequence of 'Bundle's.
data NRT = NRT {nrt_bundles :: [Bundle]} deriving (Show)

-- | Encode an 'NRT' score.
encodeNRT :: NRT -> B.ByteString
encodeNRT = B.concat . map oscWithSize . nrt_bundles

-- | Write an 'NRT' score.
writeNRT :: FilePath -> NRT -> IO ()
writeNRT fn = B.writeFile fn . encodeNRT

-- | Write an 'NRT' score to a file handle.
putNRT :: Handle -> NRT -> IO ()
putNRT h = B.hPut h . encodeNRT

-- | Decode an 'NRT' 'B.ByteString' to a list of 'Bundle's.
decode_nrt_bundles :: B.ByteString -> [Bundle]
decode_nrt_bundles s =
    let (p,q) = B.splitAt 4 s
        n = fromIntegral (decode_i32 p)
        (r,s') = B.splitAt n q
        r' = decodeBundle r
    in if B.null s'
       then [r']
       else r' : decode_nrt_bundles s'

-- | Decode an 'NRT' 'B.ByteString'.
decodeNRT :: B.ByteString -> NRT
decodeNRT = NRT . decode_nrt_bundles

-- | 'decodeNRT' of 'B.readFile'.
readNRT :: FilePath -> IO NRT
readNRT = fmap decodeNRT . B.readFile

-- | File formats @scsynth@ renders to.
data NRT_File_Format = AIFF | FLAC | NeXT | WAVE deriving (Eq,Show)

-- | Sample formats @scsynth@ renders to.
data NRT_Sample_Format = I16 | I24 | I32 | F32 | F64 deriving (Eq,Show)

-- | Data required to render an 'NRT' score using @scsynth@.  The
-- input file is optional.
data NRT_Render = NRT_Render {nrt_score :: FilePath
                             ,nrt_input_file ::Maybe FilePath
                             ,nrt_output_file :: FilePath
                             ,nrt_channels :: Int
                             ,nrt_sample_rate :: Double
                             ,nrt_file_format :: NRT_File_Format
                             ,nrt_sample_format :: NRT_Sample_Format}

-- | Format 'NRT_Sample_Format' for @scsynth@.
nrt_sf_pp :: NRT_Sample_Format -> String
nrt_sf_pp f =
    case f of
         I16 -> "int16"
         I24 -> "int24"
         I32 -> "int32"
         F32 -> "float"
         F64 -> "double"

-- | Format 'NRT_Render' as list of arguments to @scsynth@.
--
-- > let {r = NRT_Render "x.osc" Nothing "x.aif" 2 44100 AIFF I16
-- >     ;a = ["-o","2","-N","x.osc","_","x.aif","44100","AIFF","int16"]}
-- > in renderNRT_opt r == a
renderNRT_opt :: NRT_Render -> [String]
renderNRT_opt (NRT_Render c_fn i_fn o_fn nc sr hdr fmt) =
    let i_fn' = fromMaybe "_" i_fn
        nc' = show nc
        sr' = show (round sr :: Integer)
        hdr' = show hdr
        fmt' = nrt_sf_pp fmt
    in ["-o",nc',"-N",c_fn,i_fn',o_fn,sr',hdr',fmt']

-- | 'renderNRT' command as 'String', with shell protected arguments.
--
-- > renderNRT_cmd [] (NRT_Render "s.osc" Nothing "s.flac" 2 44100 FLAC I24)
renderNRT_cmd :: [String] -> NRT_Render -> String
renderNRT_cmd x =
    let protect s = '\'' : s ++ "\'"
    in unwords . ("scsynth" :) . map protect . (x ++) . renderNRT_opt

-- | Run @scsynth@ to render 'NRT_Render' with given @scsynth@ options.
renderNRT :: [String] -> NRT_Render -> IO ExitCode
renderNRT o = rawSystem "scsynth" . (o ++ ) . renderNRT_opt