zwirn-0.2.3.1: src/zwirn-lang/Zwirn/Language/Play.hs
{-# LANGUAGE DeriveFunctor #-}
module Zwirn.Language.Play where
{-
Play.hs - defines active zwirns
Copyright (C) 2026, Martin Gius
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this library. If not, see <http://www.gnu.org/licenses/>.
-}
import Control.Concurrent.MVar (MVar, modifyMVar_)
import qualified Data.Map as Map
import Data.Text (Text)
import qualified Data.Text as T
import Data.Version (showVersion)
import Paths_zwirn (version)
import Zwirn.Core.Lib.Core (apply)
import Zwirn.Core.Lib.Structure (segment)
import Zwirn.Language.Evaluate.Expression (Expression, Zwirn)
data PlayEnv = PlayEnv
{ playMap :: MVar PlayMap,
actionMap :: MVar ActionMap,
busMap :: MVar BusMap
}
type TargetName = Text
-- | wraps a type in a list of target names
data Targeted a
= Targeted
{ targets :: [TargetName],
tValue :: a
}
deriving (Functor)
data PlayState
= Normal
| Solo
| Mute
deriving (Eq, Show)
-- | type for overloading channel IDs
data Identifier
= TextID Text
| NumID Int
deriving (Eq, Show, Ord)
-- | a playmap assosciates an channel ID with a PlayState, a zwirn and an fx function to be applied to the zwirn before querying
type PlayMap =
Map.Map Identifier (Targeted (PlayState, Zwirn Expression, Maybe (Zwirn (Zwirn Expression -> Zwirn Expression))))
-- | an actionmap associates an ID with an zwirn of actions (i.e. values of type IO ())
type ActionMap = Map.Map Identifier (Zwirn Expression)
-- | a busmap associates a busnumber with a zwirn of numbers
type BusMap = Map.Map Int (Targeted (Zwirn Expression))
playReplace :: PlayEnv -> Targeted Identifier -> Zwirn Expression -> IO ()
playReplace _ (Targeted _ (TextID "_all")) _ = return ()
playReplace _ (Targeted _ (TextID "_none")) _ = return ()
playReplace penv (Targeted ts key) p = modifyMVar_ (playMap penv) (return . Map.alter alterFunc key)
where
alterFunc Nothing = Just (Targeted ts (Normal, p, Nothing))
alterFunc (Just (Targeted _ (_, _, fx))) = Just (Targeted ts (Normal, p, fx))
playReplaceBus :: PlayEnv -> Targeted Int -> Zwirn Expression -> IO ()
playReplaceBus penv (Targeted ts key) p = modifyMVar_ (busMap penv) (return . Map.insert key (Targeted ts $ segment (pure 128) p))
playReplaceAction :: PlayEnv -> Identifier -> Zwirn Expression -> IO ()
playReplaceAction _ (TextID "_all") _ = return ()
playReplaceAction _ (TextID "_none") _ = return ()
playReplaceAction penv key p = modifyMVar_ (actionMap penv) (return . Map.insert key p)
playHush :: PlayEnv -> IO ()
playHush penv = do
modifyMVar_ (playMap penv) (return . const Map.empty)
modifyMVar_ (actionMap penv) (return . const Map.empty)
modifyMVar_ (busMap penv) (return . const Map.empty)
playSetFx :: PlayEnv -> Identifier -> Zwirn (Zwirn Expression -> Zwirn Expression) -> IO ()
playSetFx penv (TextID "_all") fx = modifyMVar_ (playMap penv) (return . fmap (fmap (\(st, p, _) -> (st, p, Just fx))))
playSetFx penv (TextID "_none") _ = modifyMVar_ (playMap penv) (return . fmap (fmap (\(st, p, _) -> (st, p, Nothing))))
playSetFx penv key fx = modifyMVar_ (playMap penv) (return . Map.update (\(Targeted ts (st, p, _)) -> Just $ Targeted ts (st, p, Just fx)) key)
playToggle :: PlayEnv -> Identifier -> IO ()
playToggle penv key = case key of
(TextID "_all") -> modifyMVar_ (playMap penv) (return . fmap (fmap toggle))
_ -> modifyMVar_ (playMap penv) (return . Map.adjust (fmap toggle) key)
where
toggle (Mute, p, fx) = (Normal, p, fx)
toggle (_, p, fx) = (Mute, p, fx)
playMute :: PlayEnv -> Identifier -> IO ()
playMute penv key = case key of
TextID "_all" -> modifyMVar_ (playMap penv) (return . fmap (fmap toggle))
TextID "_none" -> playUnmute penv (TextID "_all")
_ -> modifyMVar_ (playMap penv) (return . Map.adjust (fmap toggle) key)
where
toggle (Normal, p, fx) = (Mute, p, fx)
toggle (Solo, p, fx) = (Mute, p, fx)
toggle x = x
playUnmute :: PlayEnv -> Identifier -> IO ()
playUnmute penv key = case key of
TextID "_all" -> modifyMVar_ (playMap penv) (return . fmap (fmap toggle))
TextID "_none" -> playMute penv (TextID "_all")
_ -> modifyMVar_ (playMap penv) (return . Map.adjust (fmap toggle) key)
where
toggle (Mute, p, fx) = (Normal, p, fx)
toggle x = x
playSolo :: PlayEnv -> Identifier -> IO ()
playSolo penv key = case key of
(TextID "_all") -> modifyMVar_ (playMap penv) (return . fmap (fmap toggle))
(TextID "_none") -> playUnsolo penv (TextID "_all")
_ -> modifyMVar_ (playMap penv) (return . Map.adjust (fmap toggle) key)
where
toggle (Normal, p, fx) = (Solo, p, fx)
toggle (Mute, p, fx) = (Solo, p, fx)
toggle x = x
playUnsolo :: PlayEnv -> Identifier -> IO ()
playUnsolo penv key = case key of
(TextID "_all") -> modifyMVar_ (playMap penv) (return . fmap (fmap toggle))
(TextID "_none") -> playSolo penv (TextID "_all")
_ -> modifyMVar_ (playMap penv) (return . Map.adjust (fmap toggle) key)
where
toggle (Solo, p, fx) = (Normal, p, fx)
toggle x = x
playToggleSolo :: PlayEnv -> Identifier -> IO ()
playToggleSolo penv key = case key of
(TextID "_all") -> modifyMVar_ (playMap penv) (return . fmap (fmap toggle))
_ -> modifyMVar_ (playMap penv) (return . Map.adjust (fmap toggle) key)
where
toggle (Solo, p, fx) = (Normal, p, fx)
toggle (_, p, fx) = (Solo, p, fx)
applyFx :: Targeted (PlayState, Zwirn Expression, Maybe (Zwirn (Zwirn Expression -> Zwirn Expression))) -> Targeted (Zwirn Expression)
applyFx (Targeted ts (_, x, Nothing)) = Targeted ts x
applyFx (Targeted ts (_, x, Just fx)) = Targeted ts (apply fx x)
resolvePlayMap :: PlayMap -> [Targeted (Zwirn Expression)]
resolvePlayMap pm = if null ss then map applyFx rs else map applyFx ss
where
ps = Map.elems pm
ss = filter (\(Targeted _ (x, _, _)) -> x == Solo) ps
rs = filter (\(Targeted _ (x, _, _)) -> x == Normal) ps
renderStatus :: Double -> PlayMap -> Text
renderStatus b pm = "zwirn " <> T.pack (showVersion version) <> "\ntempo: " <> T.pack (show b) <> "bpm\n" <> if null pm then "" else "active: " <> T.intercalate " | " ps
where
ps = map (\(key, Targeted _ (st, _, _)) -> ppID key <> renderState sol st) $ Map.toList pm
sol = noSolo pm
renderState True Normal = ""
renderState False Normal = " (not soloed)"
renderState _ Solo = " (solo)"
renderState _ Mute = " (muted)"
ppID (TextID t) = t
ppID (NumID i) = T.show i
-- | true if no pattern has a solo status
noSolo :: PlayMap -> Bool
noSolo pm = all (\(_, Targeted _ (ps, _, _)) -> ps /= Solo) $ Map.toList pm