zwirn-core-0.1.1.0: src/Zwirn/Core/Map.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module Zwirn.Core.Map where
{-
Map.hs - lifting functions on maps to signals, some adapted
from https://github.com/tidalcycles/Tidal/blob/dev/src/Sound/Tidal/Control.hs
Copyright (C) 2025, 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 Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
import Data.String (IsString)
import Zwirn.Core.Core
import Zwirn.Core.Modulate (fastcat, slow)
import Zwirn.Core.Structure (run)
import Zwirn.Core.Time (Time)
import Zwirn.Core.Types
import Prelude hiding ((*>))
-- | create a singleton map with specific key
singleton :: (MultiApplicative m) => ZwirnT m st i k -> ZwirnT m st i a -> ZwirnT m st i (Map k a)
singleton = liftA2Right Map.singleton
union :: (Applicative m, Ord k) => ZwirnT m st i (Map k a) -> ZwirnT m st i (Map k a) -> ZwirnT m st i (Map k a)
union = liftA2 Map.union
-- | lookup a value via key
lookup :: (HasSilence m, MultiMonad m, Ord k) => ZwirnT m st i k -> ZwirnT m st i (Map k a) -> ZwirnT m st i a
lookup tz xz = outerJoin $ liftA2Right (\t x -> fromLookup $ Map.lookup t x) tz xz
where
fromLookup (Just x) = pure x
fromLookup _ = silence
insert :: (Applicative m, Ord k) => ZwirnT m st i k -> ZwirnT m st i a -> ZwirnT m st i (Map k a) -> ZwirnT m st i (Map k a)
insert k a m = Map.insert <$> k <*> a <*> m
-- | apply a function to a specific key, if key is absent, return the original map
fix :: (HasSilence m, MultiMonad m, Ord k) => ZwirnT m st i k -> ZwirnT m st i (ZwirnT m st i a -> ZwirnT m st i a) -> ZwirnT m st i (Map k a) -> ZwirnT m st i (Map k a)
fix kz fz mz = outerJoin $ fromLookup <$> lookupMaybe kz mz
where
fromLookup (Just x) = insert kz (squeezeApply fz (pure x)) mz
fromLookup Nothing = mz
lookupMaybe = liftA2Right Map.lookup
chop :: (Fractional a, MultiMonad m, HasSilence m, Ord k, IsString k) => ZwirnT m st i Int -> ZwirnT m st i (Map k a) -> ZwirnT m st i (Map k a)
chop nz = squeezeMap (quickslice nz (run nz))
quickslice :: (Fractional a, MultiMonad m, Ord k, IsString k) => ZwirnT m st i Int -> ZwirnT m st i Int -> ZwirnT m st i (Map k a) -> ZwirnT m st i (Map k a)
quickslice nz iz = squeezeMap (slice nz iz)
loopAt :: (Fractional a, IsString a, HasSilence m, Monad m, Ord k, IsString k) => ZwirnT m st i Time -> ZwirnT m st i (Map k a) -> ZwirnT m st i (Map k a)
loopAt zt zx = _loopAt <$> zt <$$> zx
where
_loopAt 0 _ = silence
_loopAt t x = Map.alter a "speed" . Map.insert "unit" "c" <$> slow (pure t) x
where
a (Just s) = Just (s / realToFrac t)
a Nothing = Just (1 / realToFrac t)
slice :: (Fractional a, MultiApplicative m, Ord k, IsString k) => ZwirnT m st i Int -> ZwirnT m st i Int -> ZwirnT m st i (Map k a) -> ZwirnT m st i (Map k a)
slice nz iz zm = _slice <$> nz *> iz <*> zm
where
_slice n i m = Map.unions [Map.singleton "begin" newb, Map.singleton "end" newe, m]
where
b = fromMaybe 0 $ Map.lookup "begin" m
e = fromMaybe 1 $ Map.lookup "end" m
newrange x = e * x + (1 - x) * b
newb = newrange $ div' i n
newe = newrange $ div' i n + div' 1 n
div' num den = fromIntegral (num `mod` den) / fromIntegral den
striateBy :: (Fractional a, Monad m, HasSilence m, Ord k, IsString k) => ZwirnT m st i Int -> ZwirnT m st i a -> ZwirnT m st i (Map k a) -> ZwirnT m st i (Map k a)
striateBy i f x = _striateBy <$> i <*> f <$$> x
where
_striateBy n f mz = fastcat $ map (offset . fromIntegral) [0 .. n - 1]
where
offset i = mergePlayRange (slot * i, (slot * i) + f) <$> mz
slot = (1 - f) / fromIntegral (n - 1)
striate :: (Fractional a, Monad m, HasSilence m, Ord k, IsString k) => ZwirnT m st i Int -> ZwirnT m st i (Map k a) -> ZwirnT m st i (Map k a)
striate i x = _striate <$> i <$$> x
where
_striate n z = fastcat $ map offset [0 .. n - 1]
where
offset i = mergePlayRange (fromIntegral i / fromIntegral n, fromIntegral (i + 1) / fromIntegral n) <$> z
mergePlayRange :: (Fractional a, Ord k, IsString k) => (a, a) -> Map k a -> Map k a
mergePlayRange (b, e) cm = Map.insert "begin" ((b * d') + b') $ Map.insert "end" ((e * d') + b') cm
where
b' = fromMaybe 0 $ Map.lookup "begin" cm
e' = fromMaybe 1 $ Map.lookup "end" cm
d' = e' - b'