zwirn-0.2.2.0: src/zwirn-core/Zwirn/Core/Lib/Map.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module Zwirn.Core.Lib.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.Cord (Cord, stack)
import Zwirn.Core.Core
import Zwirn.Core.Lib.Cord (echoWith)
import Zwirn.Core.Lib.Core
import Zwirn.Core.Lib.Modulate (fastcat, slow)
import Zwirn.Core.Lib.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 of two maps, if a key exists in both, the value will come from the right
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 (flip 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 :: (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 (apply 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) `innerApply` 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 0 _ m = m
_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 + if n == 1 then 1 else 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) `innerApply` x
where
_striateBy n g mz = fastcat $ map (offset . fromIntegral) [0 .. n - 1]
where
offset k = _mergePlayRange (slot * k, (slot * k) + g) <$> mz
slot = (1 - g) / 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) `innerApply` x
where
_striate n z = fastcat $ map offset [0 .. n - 1]
where
offset k = _mergePlayRange (fromIntegral k / fromIntegral n, fromIntegral (k + 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'
juxBy :: (Fractional a, Ord k, IsString k) => Cord st i a -> Cord st i (Cord st i (Map k a) -> Cord st i (Map k a)) -> Cord st i (Map k a) -> Cord st i (Map k a)
juxBy nz fz xz = stack [modifyPanL xz nz, modifyPanR (apply fz xz) nz]
where
modifyPanL = liftA2 (\x n -> Map.alter (pannerL n) "pan" x)
modifyPanR = liftA2 (\x n -> Map.alter (pannerR n) "pan" x)
pannerL n (Just p) = Just $ p + 0.5 + n / 2
pannerL n Nothing = Just $ 0.5 + n / 2
pannerR n (Just p) = Just $ p + 0.5 - n / 2
pannerR n Nothing = Just $ 0.5 - n / 2
jux :: (Fractional a, Ord k, IsString k) => Cord st i (Cord st i (Map k a) -> Cord st i (Map k a)) -> Cord st i (Map k a) -> Cord st i (Map k a)
jux = juxBy (pure 1)
echo :: (Fractional a, Ord k, IsString k) => Cord st i Int -> Cord st i Time -> Cord st i a -> Cord st i (Map k a) -> Cord st i (Map k a)
echo cz tz ez = echoWith cz tz (pure (liftA2 (\e m -> Map.alter (modGain e) "gain" m) ez))
where
modGain e (Just g) = Just $ g * e
modGain e Nothing = Just e