-- | Vivid-specific OSC Bundle encoding

{-# LANGUAGE
     LambdaCase
   , OverloadedStrings
   #-}
   -- , Safe

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 :: ByteString -> ByteString
encodedOSC_addLength ByteString
bs =
   ByteString -> ByteString
BSL.toStrict (Word32 -> ByteString
forall a. Binary a => a -> ByteString
encode (Int -> Word32
forall a. Enum a => Int -> a
toEnum (ByteString -> Int
BS.length ByteString
bs) :: Word32)) ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
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 :: [OSCBundle] -> ByteString
encodeOSCBundles [OSCBundle]
bundles =
   [ByteString] -> ByteString
forall a. Monoid a => [a] -> a
mconcat ([ByteString] -> ByteString)
-> ([OSCBundle] -> [ByteString]) -> [OSCBundle] -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (OSCBundle -> ByteString) -> [OSCBundle] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map (ByteString -> ByteString
encodedOSC_addLength (ByteString -> ByteString)
-> (OSCBundle -> ByteString) -> OSCBundle -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. OSCBundle -> ByteString
encodeOSCBundle) ([OSCBundle] -> ByteString) -> [OSCBundle] -> ByteString
forall a b. (a -> b) -> a -> b
$ [OSCBundle]
withEnd
 where
   sortedBundles :: [OSCBundle]
   sortedBundles :: [OSCBundle]
sortedBundles =
      (OSCBundle -> OSCBundle -> Ordering) -> [OSCBundle] -> [OSCBundle]
forall a. (a -> a -> Ordering) -> [a] -> [a]
L.sortBy (\(OSCBundle Timestamp
t0 [Either ByteString OSC]
_) (OSCBundle Timestamp
t1 [Either ByteString OSC]
_) -> Timestamp -> Timestamp -> Ordering
forall a. Ord a => a -> a -> Ordering
compare Timestamp
t0 Timestamp
t1) [OSCBundle]
bundles
   sortedBundlesWithDefinitionsFirst :: [OSCBundle]
   sortedBundlesWithDefinitionsFirst :: [OSCBundle]
sortedBundlesWithDefinitionsFirst =
      (OSCBundle -> OSCBundle) -> [OSCBundle] -> [OSCBundle]
forall a b. (a -> b) -> [a] -> [b]
map OSCBundle -> OSCBundle
putDefinitionsFirst [OSCBundle]
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 :: [OSCBundle]
joinedByTime =
         ((([OSCBundle] -> OSCBundle) -> [[OSCBundle]] -> [OSCBundle])
-> [[OSCBundle]] -> ([OSCBundle] -> OSCBundle) -> [OSCBundle]
forall a b c. (a -> b -> c) -> b -> a -> c
flip ([OSCBundle] -> OSCBundle) -> [[OSCBundle]] -> [OSCBundle]
forall a b. (a -> b) -> [a] -> [b]
map) [[OSCBundle]]
groupedByTime (([OSCBundle] -> OSCBundle) -> [OSCBundle])
-> ([OSCBundle] -> OSCBundle) -> [OSCBundle]
forall a b. (a -> b) -> a -> b
$ \case
            as :: [OSCBundle]
as@(OSCBundle Timestamp
t [Either ByteString OSC]
_:[OSCBundle]
_) ->
               Timestamp -> [Either ByteString OSC] -> OSCBundle
OSCBundle Timestamp
t ((OSCBundle -> [Either ByteString OSC])
-> [OSCBundle] -> [Either ByteString OSC]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\(OSCBundle Timestamp
_ [Either ByteString OSC]
a) -> [Either ByteString OSC]
a) [OSCBundle]
as)
            [] -> [Char] -> OSCBundle
forall a. HasCallStack => [Char] -> a
error [Char]
"Should be impossible"
      groupedByTime :: [[OSCBundle]]
      groupedByTime :: [[OSCBundle]]
groupedByTime =
         (OSCBundle -> OSCBundle -> Bool) -> [OSCBundle] -> [[OSCBundle]]
forall a. (a -> a -> Bool) -> [a] -> [[a]]
L.groupBy (\(OSCBundle Timestamp
t0 [Either ByteString OSC]
_) (OSCBundle Timestamp
t1 [Either ByteString OSC]
_) -> Timestamp
t1 Timestamp -> Timestamp -> Bool
forall a. Eq a => a -> a -> Bool
== Timestamp
t0) [OSCBundle]
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 -> OSCBundle
putDefinitionsFirst (OSCBundle Timestamp
t [Either ByteString OSC]
actions) = Timestamp -> [Either ByteString OSC] -> OSCBundle
OSCBundle Timestamp
t ([Either ByteString OSC] -> OSCBundle)
-> [Either ByteString OSC] -> OSCBundle
forall a b. (a -> b) -> a -> b
$ (\([Either ByteString OSC]
a,[Either ByteString OSC]
b)->[Either ByteString OSC]
a[Either ByteString OSC]
-> [Either ByteString OSC] -> [Either ByteString OSC]
forall a. Semigroup a => a -> a -> a
<>[Either ByteString OSC]
b) (([Either ByteString OSC], [Either ByteString OSC])
 -> [Either ByteString OSC])
-> ([Either ByteString OSC], [Either ByteString OSC])
-> [Either ByteString OSC]
forall a b. (a -> b) -> a -> b
$
         (((Either ByteString OSC -> Bool)
 -> [Either ByteString OSC]
 -> ([Either ByteString OSC], [Either ByteString OSC]))
-> [Either ByteString OSC]
-> (Either ByteString OSC -> Bool)
-> ([Either ByteString OSC], [Either ByteString OSC])
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Either ByteString OSC -> Bool)
-> [Either ByteString OSC]
-> ([Either ByteString OSC], [Either ByteString OSC])
forall a. (a -> Bool) -> [a] -> ([a], [a])
L.partition) [Either ByteString OSC]
actions ((Either ByteString OSC -> Bool)
 -> ([Either ByteString OSC], [Either ByteString OSC]))
-> (Either ByteString OSC -> Bool)
-> ([Either ByteString OSC], [Either ByteString OSC])
forall a b. (a -> b) -> a -> b
$ \case
            Right (OSC ByteString
"/d_recv" [OSCDatum]
_) -> Bool
True
            Either ByteString OSC
_ -> Bool
False
   lastTimestamp :: Timestamp
lastTimestamp = (\(OSCBundle Timestamp
t [Either ByteString OSC]
_) ->Timestamp
t) (OSCBundle -> Timestamp) -> OSCBundle -> Timestamp
forall a b. (a -> b) -> a -> b
$ [OSCBundle] -> OSCBundle
forall a. [a] -> a
last [OSCBundle]
sortedBundles

   withEnd :: [OSCBundle]
withEnd = [[OSCBundle]] -> [OSCBundle]
forall a. Monoid a => [a] -> a
mconcat [
       [Timestamp -> [Either ByteString OSC] -> OSCBundle
OSCBundle (Double -> Timestamp
Timestamp Double
0) [OSC -> Either ByteString OSC
forall a b. b -> Either a b
Right OSC
initTreeCommand]]
      ,[OSCBundle]
sortedBundlesWithDefinitionsFirst
      ,[Timestamp -> [Either ByteString OSC] -> OSCBundle
OSCBundle Timestamp
lastTimestamp [OSC -> Either ByteString OSC
forall a b. b -> Either a b
Right (OSC -> Either ByteString OSC) -> OSC -> Either ByteString OSC
forall a b. (a -> b) -> a -> b
$ ByteString -> [OSCDatum] -> OSC
OSC ByteString
"" []]]
      ]



initTreeCommand :: OSC
initTreeCommand :: OSC
initTreeCommand =
   NodeId -> AddAction -> NodeId -> OSC
SCCmd.g_new (Int32 -> NodeId
NodeId Int32
1) AddAction
SCCmd.AddToHead (Int32 -> NodeId
NodeId Int32
0)