packages feed

vivid-0.5.0.0: Vivid/SCServer.hs

{-# 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(..)
   , ParGroup(..)
   , defaultGroup

   -- * Buffers

   , BufferId(..)
   , makeBuffer
   , makeBufferFromFile
   , newBuffer
   , newBufferFromFile
   , newBufferFromFileBetween
   , 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(..), ParGroup(..))

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 local hard drive!
-- 
--   Note that like 'makeBuffer' this is synchronous
newBufferFromFile :: (VividAction m) => FilePath -> m BufferId
newBufferFromFile = newBufferFromFileBetween 0 Nothing

newBufferFromFileBetween :: VividAction m => Int32 -> Maybe Int32 -> FilePath -> m BufferId
newBufferFromFileBetween startTime endTimeMay fPath = do
   bufId <- newBufferId
   oscWSync $ \syncId -> callOSC $
      SCCmd.b_allocRead bufId fPath startTime endTimeMay (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" -- TODO!
         "float" -- less important, but TODO
         -- 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)