-- | Recording scsynth.
module Sound.Sc3.Server.Recorder where

import Sound.Osc.Datum {- hosc -}
import Sound.Osc.Packet {- hosc -}

import Sound.Sc3.Common.Rate

import Sound.Sc3.Ugen.Bindings
import Sound.Sc3.Ugen.Util

import Sound.Sc3.Server.Command
import Sound.Sc3.Server.Enum
import Sound.Sc3.Server.Nrt
import Sound.Sc3.Server.Synthdef

-- | Parameters for recording scsynth.
data SC3_Recorder = SC3_Recorder
  { rec_sftype :: SoundFileFormat
  -- ^ Sound file format.
  , rec_coding :: SampleFormat
  -- ^ Sample format.
  , rec_fname :: FilePath
  -- ^ File name.
  , rec_nc :: Int
  -- ^ Number of channels.
  , rec_bus :: Int
  -- ^ Bus number.
  , rec_buf_id :: Int
  -- ^ ID of buffer to allocate.
  , rec_buf_frames :: Int
  -- ^ Number of frames at buffer.
  , rec_node_id :: Int
  -- ^ ID to allocate for node.
  , rec_group_id :: Int
  -- ^ Group to allocate node within.
  , rec_dur :: Maybe Time
  -- ^ Recoring duration if fixed.
  }

-- | Default recording structure.
default_SC3_Recorder :: SC3_Recorder
default_SC3_Recorder =
  SC3_Recorder
    { rec_sftype = Wave
    , rec_coding = PcmFloat
    , rec_fname = "/tmp/sc3-recorder.wav"
    , rec_nc = 2
    , rec_bus = 0
    , rec_buf_id = 10
    , rec_buf_frames = 65536
    , rec_node_id = 2001
    , rec_group_id = 0
    , rec_dur = Just 60
    }

-- | The name indicates the number of channels.
rec_synthdef_nm :: Int -> String
rec_synthdef_nm nc = "sc3-recorder-" ++ show nc

{- | Generate 'Synthdef' with required number of channels.

> Sound.Sc3.Ugen.Dot.draw (rec_synthdef 2)
-}
rec_synthdef :: Int -> Synthdef
rec_synthdef nc =
  let bufnum = control kr "bufnum" 0
      bus = control kr "bus" 0
  in synthdef (rec_synthdef_nm nc) (diskOut bufnum (in' nc ar bus))

{- | Asyncronous initialisation 'Message's ('d_recv', 'b_alloc' and 'b_write').

> withSc3 (sendBundle (bundle immediately (rec_init_m def)))
-}
rec_init_m :: SC3_Recorder -> [Message]
rec_init_m r =
  let buf = rec_buf_id r
  in [ d_recv (rec_synthdef (rec_nc r))
     , b_alloc buf (rec_buf_frames r) (rec_nc r)
     , b_write buf (rec_fname r) (rec_sftype r) (rec_coding r) (-1) 0 True
     ]

{- | Begin recording 'Message' ('s_new').

> withSc3 (sendMessage (rec_begin_m def))
-}
rec_begin_m :: SC3_Recorder -> Message
rec_begin_m r =
  s_new
    (rec_synthdef_nm (rec_nc r))
    (rec_node_id r)
    AddToTail
    (rec_group_id r)
    [ ("bus", fromIntegral (rec_bus r))
    , ("bufnum", fromIntegral (rec_buf_id r))
    ]

{- | End recording 'Message's ('n_free', 'b_close' and 'b_free').

> withSc3 (sendBundle (bundle immediately (rec_end_m def)))
-}
rec_end_m :: SC3_Recorder -> [Message]
rec_end_m r =
  [ n_free [rec_node_id r]
  , b_close (rec_buf_id r)
  , b_free (rec_buf_id r)
  ]

{- | 'Nrt' score for recorder, if 'rec_dur' is given schedule 'rec_end_m'.

> import Sound.Sc3
> withSc3 (Sound.Osc.sendMessage (dumpOSC TextPrinter))
> audition (out 0 (sinOsc ar (mce2 440 441) 0 * 0.1))
> let rc = default_SC3_Recorder {rec_dur = Just 5.0}
> nrt_audition (sc3_recorder rc)
-}
sc3_recorder :: SC3_Recorder -> Nrt
sc3_recorder r =
  let b0 = bundle 0 (rec_init_m r ++ [rec_begin_m r])
  in case rec_dur r of
      Nothing -> Nrt [b0]
      Just d -> Nrt [b0, bundle d (rec_end_m r)]
