-- | Auditor functions common to all sample libraries.
module Sound.SC3.Auditor where

import Data.List {- base -}
import Sound.OSC {- hosc -}
import Sound.SC3 {- hsc3 -}

-- | Amplitude
type Amplitude = Double

-- | Index
type Index = Int

-- | Number of channels
type NC = Int

type Note = (Index,Amplitude)

-- | Chord
type Chord = [Note]

-- | Duration
type Duration = Double

-- | Start time
type Start_Time = Double

-- | 'Start_Time' and 'Chord'.
type P = (Start_Time,Chord)

-- | Set of 'P'.
type PP = [P]

-- | Function to generate the set of 'OSC' commands required to load
-- the set of 'Index'ed files.
type Sample_Loader = [Index] -> [Message]

to_p :: (a -> Index,a -> Amplitude) -> (Start_Time,[a]) -> P
to_p (i,a) (t,x) =
    let f e = (i e,a e)
    in (t,map f x)

note_index :: Note -> Index
note_index = fst

note_amplitude :: Note -> Amplitude
note_amplitude = snd

chord_indices :: Chord -> [Index]
chord_indices = map fst

p_start_time :: P -> Start_Time
p_start_time = fst

p_chord :: P -> Chord
p_chord = snd

pp_start_times :: PP -> [Start_Time]
pp_start_times = map p_start_time

pp_chords :: PP -> [Chord]
pp_chords = map p_chord

-- | Start time of last 'P' at 'PP'.
pp_duration :: PP -> Duration
pp_duration = p_start_time . last

-- | The set of 'Index' referenced to by 'PP'.
pp_indices :: PP -> [Index]
pp_indices = nub . sort . concatMap (chord_indices . p_chord)

chd_osc :: Chord -> [Message]
chd_osc =
  let f n = s_new "s" (-1) AddToTail 1 [("b",fromIntegral (note_index n))
                                       ,("a",note_amplitude n)]
  in map f

p_osc :: P -> Bundle
p_osc (t,c) = bundle t (chd_osc c)

-- | Generate set of 'OSC' given 'NC', 'Sample_Loader' and 'PP'.
pp_nrt :: NC -> Sample_Loader -> PP -> NRT
pp_nrt nc ld pp =
    let b = bundle
        ix = pp_indices pp
        group_zero = g_new [(1, AddToTail, 0)]
        sc_init = let h = b 0 [group_zero,instr_osc nc]
                  in h : map (b 0 . return) (ld ix)
        sc_end = [b (pp_duration pp + 12) [g_freeAll [1]]]
  in NRT (sc_init ++ map p_osc pp ++ sc_end)

-- | Variant of 'pp_osc' that writes @NRT@ score to named file using
-- 'writeNRT'.
pp_nrt_write :: FilePath -> NC -> Sample_Loader -> PP -> IO ()
pp_nrt_write nm nc ld = writeNRT nm . pp_nrt nc ld

-- * Instrument

instr_osc :: NC -> Message
instr_osc nc = d_recv (synthdef "s" (smplr nc))

smplr :: NC -> UGen
smplr nc =
    let b = control KR "b" 0 -- buffer
        a = control KR "a" 1 -- amplitude
        o = control KR "o" 0 -- output channel
        b_d = bufDur KR b
        b_r = bufRateScale KR b
        c = envCoord [(0,1),(1,1)] b_d 1 EnvLin
        e = envGen KR 1 a 0 1 RemoveSynth c
    in out o (playBuf nc AR b b_r 1 0 NoLoop DoNothing * e)

-- * Audition

-- | Load sample playback instrument to @scsynth@ at 'Transport'.
au_load_instr :: Transport m => NC -> m ()
au_load_instr nc = do
  _ <- async (instr_osc nc)
  return ()

au_chd :: Transport m => (Chord,Duration) -> m ()
au_chd (c,d) = do
  --putStrLn ("au_chd: " ++ show d)
  mapM_ send (chd_osc c)
  pauseThread d

pp_st_to_dur :: PP -> [(Chord,Duration)]
pp_st_to_dur pp =
    let (t,x) = (pp_start_times pp,pp_chords pp)
    in zip x (zipWith (-) (drop 1 t) t ++ [0])

-- | Audition 'PP' at @scsynth@ instance at 'Transport'.
pp_audition :: (Transport m) => PP -> m ()
pp_audition pp = mapM_ au_chd (pp_st_to_dur pp)