-- | Vivid-specific OSC Bundle encoding {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} module Vivid.OSC.Bundles ( encodeOSCBundles , initTreeCommand ) where import Vivid.OSC -- import Vivid.OSC.Old (encodedOSC_addLength) import qualified Vivid.SC.Server.Commands as SCCmd import Vivid.SC.Server.Types (NodeId(..)) import Data.ByteString (ByteString) import qualified Data.List as L import Data.Monoid -- TEMP: import Data.Word import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString as BS import Data.Binary (encode) encodedOSC_addLength :: ByteString -> ByteString encodedOSC_addLength bs = BSL.toStrict (encode (toEnum (BS.length bs) :: Word32)) <> bs -- | Encode OSC bundles, specifically for NRT synthesis. -- (It's more than just \"mconcat . map 'encodeOSCBundle'\"). -- -- Note also that the last action is when the song ends - so if you want -- e.g. a note to hold at the end you need to add a "wait" encodeOSCBundles :: [OSCBundle] -> ByteString encodeOSCBundles bundles = mconcat . map (encodedOSC_addLength . encodeOSCBundle) $ withEnd where sortedBundles :: [OSCBundle] sortedBundles = L.sortBy (\(OSCBundle t0 _) (OSCBundle t1 _) -> compare t0 t1) bundles sortedBundlesWithDefinitionsFirst :: [OSCBundle] sortedBundlesWithDefinitionsFirst = map putDefinitionsFirst joinedByTime where -- Note we aren't assuming there aren't bundles with the same timestamp. -- (Which isnt an issue if we got the bundles with 'runNRT', but it's good -- to check): joinedByTime :: [OSCBundle] joinedByTime = (flip map) groupedByTime $ \case as@(OSCBundle t _:_) -> OSCBundle t (concatMap (\(OSCBundle _ a) -> a) as) [] -> error "Should be impossible" groupedByTime :: [[OSCBundle]] groupedByTime = L.groupBy (\(OSCBundle t0 _) (OSCBundle t1 _) -> t1 == t0) sortedBundles -- If there are "/d_recv" actions and other actions at the same timestamp, we -- put the "/d_recv"s before the other actions: putDefinitionsFirst :: OSCBundle -> OSCBundle putDefinitionsFirst (OSCBundle t actions) = OSCBundle t $ (\(a,b)->a<>b) $ (flip L.partition) actions $ \case Right (OSC "/d_recv" _) -> True _ -> False lastTimestamp = (\(OSCBundle t _) ->t) $ last sortedBundles withEnd = mconcat [ [OSCBundle (Timestamp 0) [Right initTreeCommand]] ,sortedBundlesWithDefinitionsFirst ,[OSCBundle lastTimestamp [Right $ OSC "" []]] ] initTreeCommand :: OSC initTreeCommand = SCCmd.g_new (NodeId 1) SCCmd.AddToHead (NodeId 0)