{-# 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 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 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" "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)