haskore-supercollider-0.3: src/Haskore/Interface/SuperCollider/Render.hs
module Haskore.Interface.SuperCollider.Render (
fileFromMelody, fileFromMelodyMonad,
byteStringFromSchedule, runSCSynth,
HeaderFormat(..), SampleFormat(..),
) where
import qualified Haskore.Interface.SuperCollider.Schedule as Schedule
import qualified Haskore.Interface.SuperCollider.Play as Play
import Haskore.Interface.SuperCollider.Schedule (Time)
import Haskore.Interface.SuperCollider.SoundMap (Instrument)
import qualified Sound.SC3.Server.NRT as SCNRT
import qualified Sound.SC3.Server.PlayEasy as SCPlay
-- import Sound.OpenSoundControl.Transport (Transport)
-- import qualified Sound.OpenSoundControl.Transport.Monad as Trans
import qualified Sound.OSC.Type as OSC
import qualified Sound.OSC.Time as OSCTime
import qualified Data.ByteString.Lazy as B
import qualified Haskore.Melody as Melody
import qualified Haskore.RealTime.Timer.Immediate as TimerImmediate
import qualified Haskore.RealTime.EventList.TimeBody as TimeList
import qualified Data.EventList.Absolute.TimeBody as AbsoluteEventList
import qualified Numeric.NonNegative.Wrapper as NonNeg
import System.Process (rawSystem)
import System.Exit (ExitCode)
import Data.Array(Array, Ix, (!), listArray)
{- * Generate audio files for various representations of music. -}
fileFromMelody :: FilePath -> Instrument -> Melody.T () -> IO ()
fileFromMelody fileName sound =
B.writeFile fileName .
byteStringFromSchedule .
Schedule.fromMelody sound
fileFromMelodyMonad :: FilePath -> Instrument -> Melody.T () -> IO ()
fileFromMelodyMonad fileName sound =
SCPlay.withSC3File fileName .
Play.scheduleWithPlayer
(Play.messagesGrouped TimerImmediate.timer 0) .
Schedule.fromMelody sound
{- * invocation of SuperCollider program -}
data HeaderFormat = AIFF | Wave | NeXT
deriving (Read, Show, Eq, Ord, Ix, Bounded)
headerExtension :: Array HeaderFormat String
headerExtension =
listArray (minBound,maxBound) ["aiff", "wav", "au"]
headerName :: Array HeaderFormat String
headerName =
listArray (minBound,maxBound) ["AIFF", "WAVE", "NeXT"]
data SampleFormat = Int16 | Int24 | Int32 | Float | Double
deriving (Read, Show, Eq, Ord, Ix, Bounded)
sampleName :: Array SampleFormat String
sampleName =
listArray (minBound,maxBound)
["int16", "int24", "int32", "float", "double"]
{-
because there are so much parameters,
an option list would be the better choice
-}
runSCSynth ::
[String] ->
Int {- ^ number of (stereo) channels -} ->
HeaderFormat ->
SampleFormat ->
Int {- ^ sample rate -} ->
FilePath ->
IO ExitCode
runSCSynth
options numChannels headerFormat sampleFormat sampleRate fileName =
rawSystem "scsynth"
(options ++
["-D", "0", "-o", show numChannels, "-N",
fileName++".osc", "_", fileName ++ '.' : headerExtension ! headerFormat,
show sampleRate, headerName ! headerFormat, sampleName ! sampleFormat])
{- * helper functions -}
byteStringFromSchedule :: Schedule.T -> B.ByteString
byteStringFromSchedule =
SCNRT.encodeNRT . scheduleToStream
scheduleToStream ::
Schedule.T ->
SCNRT.NRT
scheduleToStream sc =
SCNRT.NRT $
timeStamp 0 (Schedule.initial sc) :
messagesToStream (Schedule.body sc)
messagesToStream ::
TimeList.T Time OSC.Message ->
[OSC.Bundle]
messagesToStream =
map (uncurry timeStamp) .
AbsoluteEventList.toPairList .
{- first absolutize, then collectCoincident in order to catch
coincidences caused by rounding -}
AbsoluteEventList.collectCoincident .
TimeList.toAbsoluteEventList 0
timeStamp :: Time -> [OSC.Message] -> OSC.Bundle
timeStamp = OSC.Bundle . OSCTime.ut_to_ntpr . NonNeg.toNumber