{-# OPTIONS_HADDOCK show-extensions #-}

-- {-# LANGUAGE BangPatterns #-}
-- {-# LANGUAGE FlexibleInstances #-}
-- {-# LANGUAGE InstanceSigs #-}
-- {-# LANGUAGE KindSignatures #-}
-- {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
-- {-# LANGUAGE ScopedTypeVariables #-}
-- {-# LANGUAGE TypeSynonymInstances #-}

{-# LANGUAGE NoIncoherentInstances #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE NoRebindableSyntax #-}
{-# LANGUAGE NoUndecidableInstances #-}


-- | Library for interacting with the SuperCollider server.
-- 
--   You don't need to use much of this day-to-day
-- 
--   There's a toplevel 'scServerState' that stores the current state of the SC server
module Vivid.SCServer (
     cmdPeriod
   , freeAll
   , Timestamp(..)
     
   -- * Nodes

   , NodeId(..)
   , Synth(..)
   , Group(..)
   , defaultGroup

   -- * Buffers

   , BufferId(..)
   , makeBuffer
   , makeBufferFromFile
   , newBuffer
   , newBufferFromFile
   , saveBuffer
   , writeBuffer
   , writeBufferWith
   , WriteBufArgs(..)
   , defaultWBArgs
   , closeBuf
   , closeBuffer
   , zeroBuf

   -- * Manual management of SC server connection

   , createSCServerConnection
   , closeSCServerConnection
   , SCConnectConfig(..)
   , defaultConnectConfig

   , module Vivid.SCServer.State

   , shrinkSynthArgs

   ) where

import Vivid.OSC
import Vivid.OSC.Bundles (initTreeCommand)
import qualified Vivid.SC.Server.Commands as SCCmd
import Vivid.SC.Server.Types (Group(..))
import qualified Vivid.SC.Server.Commands as SCCmd

import Vivid.Actions.Class
import Vivid.SCServer.Connection
import Vivid.SCServer.State
import Vivid.SCServer.Types

import qualified Data.ByteString.UTF8 as UTF8 (fromString)
import Data.Int (Int32)
-- BBP hack:
import Prelude


-- | Your \"emergency\" button. Run this and everything playing on the SC server
--   will be freed -- silence!
-- 
--   Corresponds to the cmd-. \/ ctrl-.  key command in the SuperCollider IDE
cmdPeriod :: (VividAction m) => m ()
cmdPeriod = do
   callOSC $ SCCmd.g_freeAll [NodeId 1] -- 1 instead of 0 is temp! (is it? 1 is default group...)
   callOSC $ SCCmd.clearSched
   initTree
   
-- | Alias of 'cmdPeriod'
freeAll :: VividAction m => m ()
freeAll = cmdPeriod

initTree :: (VividAction m) => m ()
initTree = callOSC initTreeCommand

-- | Make an empty buffer
-- 
--   The Int32 is the buffer length /in samples/. Multiply seconds by
--   the default sample rate of the server (usually 48000) to get the number
--   of samples
-- 
--   Note that this is synchronous -- it doesn't return until the buffer is allocated
--   (in theory, this could hang if e.g. the UDP packet is lost)
newBuffer :: VividAction m => Int32 -> m BufferId
newBuffer bufferLength = do
   bufId <- newBufferId
   oscWSync $ \syncId ->
      callOSC $
         SCCmd.b_alloc bufId bufferLength 1 (Just $ SCCmd.sync syncId)
   return bufId

-- | Make a buffer and fill it with sound data from a file
-- 
--   The file path should be absolute (not relative), and if you're connecting to
--   a non-localhost server don't expect it to be able to read files from your
--   hard drive!
-- 
--   Note that like "makeBuffer" this is synchronous
newBufferFromFile :: (VividAction m) => FilePath -> m BufferId
newBufferFromFile fPath = do
   bufId <- newBufferId
   oscWSync $ \syncId -> callOSC $
      SCCmd.b_allocRead bufId fPath 0 Nothing (Just $ SCCmd.sync syncId)
   return bufId

makeBufferFromFile :: (VividAction m) => FilePath -> m BufferId
makeBufferFromFile = newBufferFromFile

makeBuffer :: (VividAction m) => Int32 -> m BufferId
makeBuffer = newBuffer

-- | Write a buffer to a file
-- 
--   Alias of 'writeBuffer'
-- 
--   Synchronous.
saveBuffer :: (VividAction m) => BufferId -> FilePath -> m ()
saveBuffer = writeBuffer

writeBuffer :: VividAction m => BufferId -> FilePath -> m ()
writeBuffer = writeBufferWith defaultWBArgs

writeBufferWith :: VividAction m => WriteBufArgs -> BufferId -> FilePath -> m ()
writeBufferWith args bufId fPath =
   oscWSync $ \syncId -> callOSC $
      SCCmd.b_write
         bufId
         fPath
         "wav"
         "float"
         -- Num frames:
         Nothing
         -- Start frame:
         0
         -- Whether to leave the file open (useful for diskOut)
         (_wb_keepOpen args)
         -- We make this synchronous because what if you send a
         -- "/b_write" then a "/quit"?(!):
         (Just $ SCCmd.sync syncId)

-- | We may add arguments in the future ; to future-proof your code, just update
--   fields of 'defaultWBArgs'
data WriteBufArgs
   = WriteBufArgs {
    _wb_keepOpen :: Bool
   }
 deriving (Show, Read, Eq, Ord)

defaultWBArgs :: WriteBufArgs
defaultWBArgs = WriteBufArgs {
     _wb_keepOpen = False
   }

-- | Close an open soundfile and write header information
-- 
--   Synchronous
closeBuffer :: VividAction m => BufferId -> m ()
closeBuffer bufId = oscWSync $ \syncId ->
   callOSC $ SCCmd.b_close bufId (Just $ SCCmd.sync syncId)

closeBuf :: VividAction m => BufferId -> m ()
closeBuf = closeBuffer

-- | Zero the sample data in a buffer
-- 
--   Synchronous
zeroBuf :: VividAction m => BufferId -> m ()
zeroBuf bufId = oscWSync $ \syncId ->
   callOSC $ SCCmd.b_zero bufId (Just $ SCCmd.sync syncId)

-- More info is available in HelpSource/Reference/default_group.schelp
defaultGroup :: Group ; defaultGroup = Group (NodeId 1)