From d1c9dc8b5e9f2c141e8edf661a236ee4eddbbf31 Mon Sep 17 00:00:00 2001 From: Jeffrey Benjamin Brown Date: Thu, 16 Nov 2023 21:56:51 -0500 Subject: [PATCH 01/11] toward scheduling multiple events in one shot --- BootTidal.hs | 1 + src/Sound/Tidal/Transition.hs | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/BootTidal.hs b/BootTidal.hs index 1157ec015..f4d8b7158 100644 --- a/BootTidal.hs +++ b/BootTidal.hs @@ -36,6 +36,7 @@ let only = (hush >>) histpan i t = transition tidal True (Sound.Tidal.Transition.histpan t) i wait i t = transition tidal True (Sound.Tidal.Transition.wait t) i waitT i f t = transition tidal True (Sound.Tidal.Transition.waitT f t) i + sched i s = transition tidal True (Sound.Tidal.Transition.sched s) i jump i = transition tidal True (Sound.Tidal.Transition.jump) i jumpIn i t = transition tidal True (Sound.Tidal.Transition.jumpIn t) i jumpIn' i t = transition tidal True (Sound.Tidal.Transition.jumpIn' t) i diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs index c4139325b..6ce6f86fa 100644 --- a/src/Sound/Tidal/Transition.hs +++ b/src/Sound/Tidal/Transition.hs @@ -4,6 +4,7 @@ module Sound.Tidal.Transition where import Prelude hiding ((<*), (*>)) +import qualified Data.List as L import Control.Concurrent.MVar (modifyMVar_) import qualified Data.Map.Strict as Map @@ -50,6 +51,56 @@ mortalOverlay t now (pat:ps) = overlay (pop ps) (playFor s (s+t) pat) where pop (x:_) = x s = sam (now - fromIntegral (floor now `mod` floor t :: Int)) + sam t +{- | This lets a user schedule a number of transitions at once. +It still needs some work -- see the TODO statement below. + +Demonstration: +@ +let p0 = s "bd sn:1" + +do setcps 1 + d1 $ s "numbers" |* n (slow 8 $ run 8) + d2 p0 + +sched 2 [ (2, s "[bd,hc*4]"), + (4, s "[bd,hc*3]"), + (6, s p0) ] + p0 -- TODO : This is the pattern that gets pushed onto the front + -- of Tidal's global list of ControlPatterns. + -- Rather than requiring the user to supply it, + -- this should be the result of the stack of filterWhen statements + -- computed by sched. +@ +-} +sched :: [ -- ^ the schedule + ( Time, -- ^ Each of these is like the `delay` argument to `wash`. + -- PITFALL: Each delay should be distinct. + Pattern a) ] + -> Time -- ^ just like the `now` argumnt to `wash` + -> [Pattern a] -- ^ just like `[Pattern a]` argumnt to `wash` + -> Pattern a +sched _ _ [] = silence +sched _ _ (pat:[]) = pat +sched + s0 now (_:pat:_) = + -- Unlike `wash`, `sched` ignores the head of the [Pattern a] it is passed. + -- That's because it's not transitioning between two patterns, + -- as in `wash`, but rather through all the patterns specified in `s0`. + let + -- The first pattern to play is `pat`. + between lo hi x = (x >= lo) && (x < hi) + s = L.sortOn fst s0 -- earlier patterns are earlier in s, + -- whereas they could be anywhere in s0 + firstPat = filterWhen (< (now + t)) pat + where (t,_) = head s + lastPat = filterWhen (>= (now + t)) p + where (t, p) = last s + middle = [ filterWhen (between (now + t0) $ now + t1) p + | ( (t0,p), + (t1,_) ) <- zip s $ tail s ] + in stack $ + firstPat : lastPat : middle -- order doesn't matter to `stack` + {-| Washes away the current pattern after a certain delay by applying a function to it over time, then switching over to the next pattern to which another function is applied. From a643f46f4fcf896921c1a9b1ad6c6c6d19793537 Mon Sep 17 00:00:00 2001 From: Jeffrey Benjamin Brown Date: Sat, 18 Nov 2023 13:55:24 -0500 Subject: [PATCH 02/11] minor doc fix --- src/Sound/Tidal/Transition.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs index 6ce6f86fa..16b65edcb 100644 --- a/src/Sound/Tidal/Transition.hs +++ b/src/Sound/Tidal/Transition.hs @@ -64,7 +64,7 @@ do setcps 1 sched 2 [ (2, s "[bd,hc*4]"), (4, s "[bd,hc*3]"), - (6, s p0) ] + (6, p0) ] p0 -- TODO : This is the pattern that gets pushed onto the front -- of Tidal's global list of ControlPatterns. -- Rather than requiring the user to supply it, @@ -72,6 +72,7 @@ sched 2 [ (2, s "[bd,hc*4]"), -- computed by sched. @ -} + sched :: [ -- ^ the schedule ( Time, -- ^ Each of these is like the `delay` argument to `wash`. -- PITFALL: Each delay should be distinct. From 734ffe4678b0c638ff723400fdf197f566d3645e Mon Sep 17 00:00:00 2001 From: Jeffrey Benjamin Brown Date: Sat, 18 Nov 2023 14:25:49 -0500 Subject: [PATCH 03/11] toward scheduling multiple events in one shot more --- BootTidal.hs | 19 ++++++++- src/Sound/Tidal/Transition.hs | 75 ++++++++++++++--------------------- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/BootTidal.hs b/BootTidal.hs index f4d8b7158..627b4565e 100644 --- a/BootTidal.hs +++ b/BootTidal.hs @@ -1,8 +1,9 @@ -:set -XOverloadedStrings +:set -XOverloadedStrings -XScopedTypeVariables :set prompt "" import Sound.Tidal.Context +import Data.List (sortOn) import System.IO (hSetEncoding, stdout, utf8) hSetEncoding stdout utf8 @@ -31,6 +32,22 @@ let only = (hush >>) setcps = asap . cps getcps = streamGetcps tidal getnow = streamGetnow tidal + + {- An example of `sched`: + do setcps 1 + d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) + sched 2 [ (2, s "[lt*5]"), + (4, s "[ht*4]"), + (6, s "[hc*3]") ] + -} + sched i s = do + now <- getnow + let d = fst $ head s + p = scheduleToPat + $ sortOn fst -- earlier patterns closer to head + $ delaySchedule_toAbsoluteSchedule (toTime now) s + transition tidal True (STTrans.jumpFrac d) i p + xfade i = transition tidal True (Sound.Tidal.Transition.xfadeIn 4) i xfadeIn i t = transition tidal True (Sound.Tidal.Transition.xfadeIn t) i histpan i t = transition tidal True (Sound.Tidal.Transition.histpan t) i diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs index 16b65edcb..444e7235c 100644 --- a/src/Sound/Tidal/Transition.hs +++ b/src/Sound/Tidal/Transition.hs @@ -51,56 +51,41 @@ mortalOverlay t now (pat:ps) = overlay (pop ps) (playFor s (s+t) pat) where pop (x:_) = x s = sam (now - fromIntegral (floor now `mod` floor t :: Int)) + sam t -{- | This lets a user schedule a number of transitions at once. -It still needs some work -- see the TODO statement below. - -Demonstration: -@ -let p0 = s "bd sn:1" - -do setcps 1 - d1 $ s "numbers" |* n (slow 8 $ run 8) - d2 p0 - -sched 2 [ (2, s "[bd,hc*4]"), - (4, s "[bd,hc*3]"), - (6, p0) ] - p0 -- TODO : This is the pattern that gets pushed onto the front - -- of Tidal's global list of ControlPatterns. - -- Rather than requiring the user to supply it, - -- this should be the result of the stack of filterWhen statements - -- computed by sched. -@ --} - -sched :: [ -- ^ the schedule - ( Time, -- ^ Each of these is like the `delay` argument to `wash`. - -- PITFALL: Each delay should be distinct. - Pattern a) ] - -> Time -- ^ just like the `now` argumnt to `wash` - -> [Pattern a] -- ^ just like `[Pattern a]` argumnt to `wash` +scheduleToPat :: + [ ( -- ^ A schedule, with offsets relative to the current time. + Time, -- ^ Absolute time, not time relative to now. + -- It is when the new pattern starts, not when the old one ends. + -- PITFALL: Each should be distinct. + Pattern a -- ^ What starts when the associated `Time` is reached. + ) ] -> Pattern a -sched _ _ [] = silence -sched _ _ (pat:[]) = pat -sched - s0 now (_:pat:_) = - -- Unlike `wash`, `sched` ignores the head of the [Pattern a] it is passed. - -- That's because it's not transitioning between two patterns, - -- as in `wash`, but rather through all the patterns specified in `s0`. +scheduleToPat s = let - -- The first pattern to play is `pat`. between lo hi x = (x >= lo) && (x < hi) - s = L.sortOn fst s0 -- earlier patterns are earlier in s, - -- whereas they could be anywhere in s0 - firstPat = filterWhen (< (now + t)) pat - where (t,_) = head s - lastPat = filterWhen (>= (now + t)) p + lastPat = filterWhen (>= t) p where (t, p) = last s - middle = [ filterWhen (between (now + t0) $ now + t1) p - | ( (t0,p), - (t1,_) ) <- zip s $ tail s ] + patternsBeforeLast = [ filterWhen (between t0 t1) p + | ( (t0,p), + (t1,_) ) <- zip s $ tail s ] in stack $ - firstPat : lastPat : middle -- order doesn't matter to `stack` + lastPat : patternsBeforeLast -- order doesn't matter to `stack` + +delaySchedule_toAbsoluteSchedule :: + Time -> -- ^ now + [ (Time, Pattern a) ] -> -- ^ a schedule defined by times relative to now + [ (Time, Pattern a) ] -- ^ a schedule defined by absolute times +delaySchedule_toAbsoluteSchedule now s = + [ (t + now, p) | (t,p) <- s ] + +jumpFrac :: Time -- ^ how long to wait + -> Time -- ^ not supplied by user! + -> [Pattern a] -- ^ not supplied by user! + -> Pattern a +jumpFrac _ _ [] = silence +jumpFrac _ _ (pat:[]) = pat +jumpFrac wait now (pat':pat:_) = + stack [ filterWhen (< (now + wait)) pat + , filterWhen (>= (now + wait)) pat' ] {-| Washes away the current pattern after a certain delay by applying a function to it over time, then switching over to the next pattern to From 2f08fa33b75b5e9c9289d118a3c59d50b151c436 Mon Sep 17 00:00:00 2001 From: Jeffrey Benjamin Brown Date: Sat, 18 Nov 2023 14:25:49 -0500 Subject: [PATCH 04/11] sched : works! That's scheduling with delays amounts. Now I need delays modulo some divisor. --- BootTidal.hs | 21 +++++++++- src/Sound/Tidal/Transition.hs | 78 ++++++++++++++--------------------- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/BootTidal.hs b/BootTidal.hs index f4d8b7158..05b554263 100644 --- a/BootTidal.hs +++ b/BootTidal.hs @@ -1,8 +1,9 @@ -:set -XOverloadedStrings +:set -XOverloadedStrings -XScopedTypeVariables :set prompt "" import Sound.Tidal.Context +import Data.List (sortOn) import System.IO (hSetEncoding, stdout, utf8) hSetEncoding stdout utf8 @@ -31,12 +32,28 @@ let only = (hush >>) setcps = asap . cps getcps = streamGetcps tidal getnow = streamGetnow tidal + + {- An example of `sched`: + do setcps 1 + d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) + sched 2 [ (2, s "[lt*5]"), + (4, s "[ht*4]"), + (6, s "[hc*3]") ] + -} + sched i s = do + now <- getnow + let t = fst $ head s + p = absScheduleToPat + $ sortOn fst -- earlier patterns closer to head + ( delaySchedule_toAbsoluteSchedule + (toTime now) s ) + transition tidal True (Sound.Tidal.Transition.jumpFrac t) i p + xfade i = transition tidal True (Sound.Tidal.Transition.xfadeIn 4) i xfadeIn i t = transition tidal True (Sound.Tidal.Transition.xfadeIn t) i histpan i t = transition tidal True (Sound.Tidal.Transition.histpan t) i wait i t = transition tidal True (Sound.Tidal.Transition.wait t) i waitT i f t = transition tidal True (Sound.Tidal.Transition.waitT f t) i - sched i s = transition tidal True (Sound.Tidal.Transition.sched s) i jump i = transition tidal True (Sound.Tidal.Transition.jump) i jumpIn i t = transition tidal True (Sound.Tidal.Transition.jumpIn t) i jumpIn' i t = transition tidal True (Sound.Tidal.Transition.jumpIn' t) i diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs index 16b65edcb..20b5dfae9 100644 --- a/src/Sound/Tidal/Transition.hs +++ b/src/Sound/Tidal/Transition.hs @@ -4,7 +4,6 @@ module Sound.Tidal.Transition where import Prelude hiding ((<*), (*>)) -import qualified Data.List as L import Control.Concurrent.MVar (modifyMVar_) import qualified Data.Map.Strict as Map @@ -51,56 +50,43 @@ mortalOverlay t now (pat:ps) = overlay (pop ps) (playFor s (s+t) pat) where pop (x:_) = x s = sam (now - fromIntegral (floor now `mod` floor t :: Int)) + sam t -{- | This lets a user schedule a number of transitions at once. -It still needs some work -- see the TODO statement below. - -Demonstration: -@ -let p0 = s "bd sn:1" - -do setcps 1 - d1 $ s "numbers" |* n (slow 8 $ run 8) - d2 p0 - -sched 2 [ (2, s "[bd,hc*4]"), - (4, s "[bd,hc*3]"), - (6, p0) ] - p0 -- TODO : This is the pattern that gets pushed onto the front - -- of Tidal's global list of ControlPatterns. - -- Rather than requiring the user to supply it, - -- this should be the result of the stack of filterWhen statements - -- computed by sched. -@ --} - -sched :: [ -- ^ the schedule - ( Time, -- ^ Each of these is like the `delay` argument to `wash`. - -- PITFALL: Each delay should be distinct. - Pattern a) ] - -> Time -- ^ just like the `now` argumnt to `wash` - -> [Pattern a] -- ^ just like `[Pattern a]` argumnt to `wash` +absScheduleToPat :: + [ ( -- ^ A schedule, with offsets relative to the current time. + Time, -- ^ Absolute time, not time relative to now. + -- It is when the new pattern starts, not when the old one ends. + -- PITFALL: Each should be distinct. + Pattern a -- ^ What starts when the associated `Time` is reached. + ) ] -> Pattern a -sched _ _ [] = silence -sched _ _ (pat:[]) = pat -sched - s0 now (_:pat:_) = - -- Unlike `wash`, `sched` ignores the head of the [Pattern a] it is passed. - -- That's because it's not transitioning between two patterns, - -- as in `wash`, but rather through all the patterns specified in `s0`. +absScheduleToPat s = let - -- The first pattern to play is `pat`. between lo hi x = (x >= lo) && (x < hi) - s = L.sortOn fst s0 -- earlier patterns are earlier in s, - -- whereas they could be anywhere in s0 - firstPat = filterWhen (< (now + t)) pat - where (t,_) = head s - lastPat = filterWhen (>= (now + t)) p + lastPat = filterWhen (>= t) p where (t, p) = last s - middle = [ filterWhen (between (now + t0) $ now + t1) p - | ( (t0,p), - (t1,_) ) <- zip s $ tail s ] + patternsBeforeLast = [ filterWhen (between t0 t1) p + | ( (t0,p), + (t1,_) ) <- zip s $ tail s ] in stack $ - firstPat : lastPat : middle -- order doesn't matter to `stack` + lastPat : patternsBeforeLast -- order doesn't matter to `stack` + +delaySchedule_toAbsoluteSchedule :: + Time -> -- ^ now + [ (Time, Pattern a) ] -> -- ^ a schedule defined by times relative to now + [ (Time, Pattern a) ] -- ^ a schedule defined by absolute times +delaySchedule_toAbsoluteSchedule now s = + [ (t + now, p) + | (t,p) <- s ] + +-- | Unlike `jumpIn`, `jumpFrac` accepts fractional delays. +jumpFrac :: Time -- ^ how long to wait + -> Time -- ^ not supplied by user! + -> [Pattern a] -- ^ not supplied by user! + -> Pattern a +jumpFrac _ _ [] = silence +jumpFrac _ _ (pat:[]) = pat +jumpFrac wait now (pat':pat:_) = + stack [ filterWhen (< (now + wait)) pat + , filterWhen (>= (now + wait)) pat' ] {-| Washes away the current pattern after a certain delay by applying a function to it over time, then switching over to the next pattern to From ce54c55fe998e9b9d5b4f2ff5b29974daabfceea Mon Sep 17 00:00:00 2001 From: Jeffrey Benjamin Brown Date: Sat, 18 Nov 2023 15:48:25 -0500 Subject: [PATCH 05/11] scheduling modulo a divisor: works! --- BootTidal.hs | 23 +++++++++++++++++++++-- src/Sound/Tidal/Transition.hs | 26 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/BootTidal.hs b/BootTidal.hs index 05b554263..d6482f47f 100644 --- a/BootTidal.hs +++ b/BootTidal.hs @@ -38,8 +38,7 @@ let only = (hush >>) d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) sched 2 [ (2, s "[lt*5]"), (4, s "[ht*4]"), - (6, s "[hc*3]") ] - -} + (6, s "[hc*3]") ] -} sched i s = do now <- getnow let t = fst $ head s @@ -49,6 +48,26 @@ let only = (hush >>) (toTime now) s ) transition tidal True (Sound.Tidal.Transition.jumpFrac t) i p + {- an example of `schod`: + do setcps 1 + d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) + schod 2 8 [ (2, s "[lt*5]"), + (4, s "[ht*4]"), + (6, s "[hc*3]"), + (8, s "~ sn:1" ), + (12, s "~ ~ sn:1" ), + (16, silence) ]-} + schod :: ID -> Time -> [ (Time, ControlPattern) ] -> IO () + schod i divisor sRel = do -- like `sched` but modulo a divisor + now <- getnow + let sAbs :: [ (Time, ControlPattern) ] = + sortOn fst -- earlier patterns closer to head + ( delayModSchedules_toAbsoluteSchedule + (toTime now) divisor sRel ) + d :: Time = fst $ head sAbs + p :: ControlPattern = absScheduleToPat sAbs + transition tidal True (Sound.Tidal.Transition.jumpFracAbs d) i p + xfade i = transition tidal True (Sound.Tidal.Transition.xfadeIn 4) i xfadeIn i t = transition tidal True (Sound.Tidal.Transition.xfadeIn t) i histpan i t = transition tidal True (Sound.Tidal.Transition.histpan t) i diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs index 20b5dfae9..3e527e5c8 100644 --- a/src/Sound/Tidal/Transition.hs +++ b/src/Sound/Tidal/Transition.hs @@ -5,6 +5,7 @@ module Sound.Tidal.Transition where import Prelude hiding ((<*), (*>)) import Control.Concurrent.MVar (modifyMVar_) +import Data.Fixed (mod', div') import qualified Data.Map.Strict as Map -- import Data.Maybe (fromJust) @@ -77,6 +78,31 @@ delaySchedule_toAbsoluteSchedule now s = [ (t + now, p) | (t,p) <- s ] +delayModSchedules_toAbsoluteSchedule :: + Time -> -- ^ now + Time -> -- ^ A divisor. Probably an integer. + [ (Time, Pattern a) ] -> {- ^ A schedule defined by times relative to the most recent time divisible by the divisor. Note that these `Time` values can be greater than the divisor -- indeed they can be arbitrarily high, and order and measure among them will be respected. -} + [ (Time, Pattern a) ] -- ^ a schedule defined by absolute times +delayModSchedules_toAbsoluteSchedule now divisor s = + -- PITFALL: If, for some t in the schedule, rem is greater than t, + -- then the pattern associated with t will play immediately. + let rem = mod' now divisor + in [ (now - rem + t, p) + | (t,p) <- s ] + +-- | Unlike `jumpIn`, `jumpFrac` accepts fractional start times. +-- Unlike `jumpFrac`, this takes an absolute time, +-- not a delay to be added to the current time. +jumpFracAbs :: Time -- ^ when to transition + -> Time -- ^ not supplied by user! + -> [Pattern a] -- ^ not supplied by user! + -> Pattern a +jumpFracAbs _ _ [] = silence +jumpFracAbs _ _ (pat:[]) = pat +jumpFracAbs wait _ (pat':pat:_) = + stack [ filterWhen (< wait) pat + , filterWhen (>= wait) pat' ] + -- | Unlike `jumpIn`, `jumpFrac` accepts fractional delays. jumpFrac :: Time -- ^ how long to wait -> Time -- ^ not supplied by user! From de705938c314b82794c88a08f8bbbad7c2f448b6 Mon Sep 17 00:00:00 2001 From: Jeffrey Benjamin Brown Date: Sat, 18 Nov 2023 16:11:14 -0500 Subject: [PATCH 06/11] keep BootTidal clean by moving logic into Sound.Tidal.Transitions --- BootTidal.hs | 39 ++----------------------- src/Sound/Tidal/Transition.hs | 55 ++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 38 deletions(-) diff --git a/BootTidal.hs b/BootTidal.hs index d6482f47f..a412fcf9e 100644 --- a/BootTidal.hs +++ b/BootTidal.hs @@ -3,7 +3,6 @@ import Sound.Tidal.Context -import Data.List (sortOn) import System.IO (hSetEncoding, stdout, utf8) hSetEncoding stdout utf8 @@ -32,42 +31,6 @@ let only = (hush >>) setcps = asap . cps getcps = streamGetcps tidal getnow = streamGetnow tidal - - {- An example of `sched`: - do setcps 1 - d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) - sched 2 [ (2, s "[lt*5]"), - (4, s "[ht*4]"), - (6, s "[hc*3]") ] -} - sched i s = do - now <- getnow - let t = fst $ head s - p = absScheduleToPat - $ sortOn fst -- earlier patterns closer to head - ( delaySchedule_toAbsoluteSchedule - (toTime now) s ) - transition tidal True (Sound.Tidal.Transition.jumpFrac t) i p - - {- an example of `schod`: - do setcps 1 - d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) - schod 2 8 [ (2, s "[lt*5]"), - (4, s "[ht*4]"), - (6, s "[hc*3]"), - (8, s "~ sn:1" ), - (12, s "~ ~ sn:1" ), - (16, silence) ]-} - schod :: ID -> Time -> [ (Time, ControlPattern) ] -> IO () - schod i divisor sRel = do -- like `sched` but modulo a divisor - now <- getnow - let sAbs :: [ (Time, ControlPattern) ] = - sortOn fst -- earlier patterns closer to head - ( delayModSchedules_toAbsoluteSchedule - (toTime now) divisor sRel ) - d :: Time = fst $ head sAbs - p :: ControlPattern = absScheduleToPat sAbs - transition tidal True (Sound.Tidal.Transition.jumpFracAbs d) i p - xfade i = transition tidal True (Sound.Tidal.Transition.xfadeIn 4) i xfadeIn i t = transition tidal True (Sound.Tidal.Transition.xfadeIn t) i histpan i t = transition tidal True (Sound.Tidal.Transition.histpan t) i @@ -78,6 +41,8 @@ let only = (hush >>) jumpIn' i t = transition tidal True (Sound.Tidal.Transition.jumpIn' t) i jumpMod i t = transition tidal True (Sound.Tidal.Transition.jumpMod t) i jumpMod' i t p = transition tidal True (Sound.Tidal.Transition.jumpMod' t p) i + sched = Sound.Tidal.Transition.sched tidal getnow + schod = Sound.Tidal.Transition.schod tidal getnow mortal i lifespan release = transition tidal True (Sound.Tidal.Transition.mortal lifespan release) i interpolate i = transition tidal True (Sound.Tidal.Transition.interpolate) i interpolateIn i t = transition tidal True (Sound.Tidal.Transition.interpolateIn t) i diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs index 3e527e5c8..9ba47a39b 100644 --- a/src/Sound/Tidal/Transition.hs +++ b/src/Sound/Tidal/Transition.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE BangPatterns, ScopedTypeVariables #-} module Sound.Tidal.Transition where @@ -6,6 +6,7 @@ import Prelude hiding ((<*), (*>)) import Control.Concurrent.MVar (modifyMVar_) import Data.Fixed (mod', div') +import Data.List (sortOn) import qualified Data.Map.Strict as Map -- import Data.Maybe (fromJust) @@ -51,6 +52,58 @@ mortalOverlay t now (pat:ps) = overlay (pop ps) (playFor s (s+t) pat) where pop (x:_) = x s = sam (now - fromIntegral (floor now `mod` floor t :: Int)) + sam t +{- | Schedule a pattern, relative to the current cycle. +@ +do setcps 1 + d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) + sched 2 [ (2, s "[lt*5]"), + (4, s "[ht*4]"), + (6, s "[hc*3]") ] +@ +-} +sched :: Stream -- ^ PITFALL: Not provided by user! + -> IO Double -- ^ PITFALL: Not provided by user! + -> ID -- ^ voice to affect + -> [(Time, ControlPattern)] -- ^ schedule + -> IO () +sched tidal getnow i s = do + now <- getnow + let t = fst $ head s + p = absScheduleToPat + $ sortOn fst -- earlier patterns closer to head + ( delaySchedule_toAbsoluteSchedule + (toTime now) s ) + transition tidal True (Sound.Tidal.Transition.jumpFrac t) i p + +{- | Schedule a pattern, relative to the most recent time +that was divisible by the divisor. +@ +do setcps 1 + d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) + schod 2 8 [ (2, s "[lt*5]"), + (4, s "[ht*4]"), + (6, s "[hc*3]"), + (8, s "~ sn:1" ), + (12, s "~ ~ sn:1" ), + (16, silence) ] +@ +-} +schod :: Stream -- ^ PITFALL: Not provided by user! + -> IO Double -- ^ PITFALL: Not provided by user! + -> ID -- ^ voice to affect + -> Time -- ^ divisor + -> [(Time, ControlPattern)] -- ^ schedule + -> IO () +schod tidal getnow i divisor sRel = do + now <- getnow + let sAbs :: [ (Time, ControlPattern) ] = + sortOn fst -- earlier patterns closer to head + ( delayModSchedules_toAbsoluteSchedule + (toTime now) divisor sRel ) + d :: Time = fst $ head sAbs + p :: ControlPattern = absScheduleToPat sAbs + transition tidal True (Sound.Tidal.Transition.jumpFracAbs d) i p + absScheduleToPat :: [ ( -- ^ A schedule, with offsets relative to the current time. Time, -- ^ Absolute time, not time relative to now. From d0adb3d2344fe725842bdc7ad7660eb80b7c9362 Mon Sep 17 00:00:00 2001 From: Jeffrey Benjamin Brown Date: Sat, 18 Nov 2023 16:13:15 -0500 Subject: [PATCH 07/11] move a function --- src/Sound/Tidal/Transition.hs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs index 9ba47a39b..671663146 100644 --- a/src/Sound/Tidal/Transition.hs +++ b/src/Sound/Tidal/Transition.hs @@ -143,6 +143,17 @@ delayModSchedules_toAbsoluteSchedule now divisor s = in [ (now - rem + t, p) | (t,p) <- s ] +-- | Unlike `jumpIn`, `jumpFrac` accepts fractional delays. +jumpFrac :: Time -- ^ how long to wait + -> Time -- ^ not supplied by user! + -> [Pattern a] -- ^ not supplied by user! + -> Pattern a +jumpFrac _ _ [] = silence +jumpFrac _ _ (pat:[]) = pat +jumpFrac wait now (pat':pat:_) = + stack [ filterWhen (< (now + wait)) pat + , filterWhen (>= (now + wait)) pat' ] + -- | Unlike `jumpIn`, `jumpFrac` accepts fractional start times. -- Unlike `jumpFrac`, this takes an absolute time, -- not a delay to be added to the current time. @@ -156,17 +167,6 @@ jumpFracAbs wait _ (pat':pat:_) = stack [ filterWhen (< wait) pat , filterWhen (>= wait) pat' ] --- | Unlike `jumpIn`, `jumpFrac` accepts fractional delays. -jumpFrac :: Time -- ^ how long to wait - -> Time -- ^ not supplied by user! - -> [Pattern a] -- ^ not supplied by user! - -> Pattern a -jumpFrac _ _ [] = silence -jumpFrac _ _ (pat:[]) = pat -jumpFrac wait now (pat':pat:_) = - stack [ filterWhen (< (now + wait)) pat - , filterWhen (>= (now + wait)) pat' ] - {-| Washes away the current pattern after a certain delay by applying a function to it over time, then switching over to the next pattern to which another function is applied. From fe90ea3492b5f1d466d3ed6e6cf782a46c50940a Mon Sep 17 00:00:00 2001 From: Jeffrey Benjamin Brown Date: Sat, 18 Nov 2023 16:14:01 -0500 Subject: [PATCH 08/11] fix a name it was plural for no reason --- src/Sound/Tidal/Transition.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs index 671663146..c6335cc45 100644 --- a/src/Sound/Tidal/Transition.hs +++ b/src/Sound/Tidal/Transition.hs @@ -98,7 +98,7 @@ schod tidal getnow i divisor sRel = do now <- getnow let sAbs :: [ (Time, ControlPattern) ] = sortOn fst -- earlier patterns closer to head - ( delayModSchedules_toAbsoluteSchedule + ( delayModSchedule_toAbsoluteSchedule (toTime now) divisor sRel ) d :: Time = fst $ head sAbs p :: ControlPattern = absScheduleToPat sAbs @@ -131,12 +131,12 @@ delaySchedule_toAbsoluteSchedule now s = [ (t + now, p) | (t,p) <- s ] -delayModSchedules_toAbsoluteSchedule :: +delayModSchedule_toAbsoluteSchedule :: Time -> -- ^ now Time -> -- ^ A divisor. Probably an integer. [ (Time, Pattern a) ] -> {- ^ A schedule defined by times relative to the most recent time divisible by the divisor. Note that these `Time` values can be greater than the divisor -- indeed they can be arbitrarily high, and order and measure among them will be respected. -} [ (Time, Pattern a) ] -- ^ a schedule defined by absolute times -delayModSchedules_toAbsoluteSchedule now divisor s = +delayModSchedule_toAbsoluteSchedule now divisor s = -- PITFALL: If, for some t in the schedule, rem is greater than t, -- then the pattern associated with t will play immediately. let rem = mod' now divisor From c67e234c2f71e0046f212e1352fa19fad9d1d802 Mon Sep 17 00:00:00 2001 From: Jeffrey Benjamin Brown Date: Sat, 18 Nov 2023 16:23:09 -0500 Subject: [PATCH 09/11] comments --- BootTidal.hs | 2 +- src/Sound/Tidal/Transition.hs | 35 +++++++++++++++++++---------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/BootTidal.hs b/BootTidal.hs index a412fcf9e..2272dd9ce 100644 --- a/BootTidal.hs +++ b/BootTidal.hs @@ -1,4 +1,4 @@ -:set -XOverloadedStrings -XScopedTypeVariables +:set -XOverloadedStrings :set prompt "" import Sound.Tidal.Context diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs index c6335cc45..05beacc04 100644 --- a/src/Sound/Tidal/Transition.hs +++ b/src/Sound/Tidal/Transition.hs @@ -52,7 +52,8 @@ mortalOverlay t now (pat:ps) = overlay (pop ps) (playFor s (s+t) pat) where pop (x:_) = x s = sam (now - fromIntegral (floor now `mod` floor t :: Int)) + sam t -{- | Schedule a pattern, relative to the current cycle. +{- | Schedule some patterns (all for the same voice, e.g. `d1`), +relative to the current cycle. @ do setcps 1 d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) @@ -61,8 +62,8 @@ do setcps 1 (6, s "[hc*3]") ] @ -} -sched :: Stream -- ^ PITFALL: Not provided by user! - -> IO Double -- ^ PITFALL: Not provided by user! +sched :: Stream -- ^ PITFALL: Not provided by user. + -> IO Double -- ^ PITFALL: Not provided by user. -> ID -- ^ voice to affect -> [(Time, ControlPattern)] -- ^ schedule -> IO () @@ -75,8 +76,8 @@ sched tidal getnow i s = do (toTime now) s ) transition tidal True (Sound.Tidal.Transition.jumpFrac t) i p -{- | Schedule a pattern, relative to the most recent time -that was divisible by the divisor. +{- | Schedule some patterns (all for the same voice, e.g. `d1`), +relative to the most recent time that was divisible by the divisor. @ do setcps 1 d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) @@ -88,8 +89,8 @@ do setcps 1 (16, silence) ] @ -} -schod :: Stream -- ^ PITFALL: Not provided by user! - -> IO Double -- ^ PITFALL: Not provided by user! +schod :: Stream -- ^ PITFALL: Not provided by user. + -> IO Double -- ^ PITFALL: Not provided by user. -> ID -- ^ voice to affect -> Time -- ^ divisor -> [(Time, ControlPattern)] -- ^ schedule @@ -105,10 +106,12 @@ schod tidal getnow i divisor sRel = do transition tidal True (Sound.Tidal.Transition.jumpFracAbs d) i p absScheduleToPat :: - [ ( -- ^ A schedule, with offsets relative to the current time. + [ ( -- ^ A schedule in terms of absolute times + -- (as opposed to delays relative to the current time). Time, -- ^ Absolute time, not time relative to now. -- It is when the new pattern starts, not when the old one ends. - -- PITFALL: Each should be distinct. + -- PITFALL: Each of the `Time`s in these tuples should be distinct. + -- Otherwise one pattern will clobber another. Pattern a -- ^ What starts when the associated `Time` is reached. ) ] -> Pattern a @@ -145,8 +148,8 @@ delayModSchedule_toAbsoluteSchedule now divisor s = -- | Unlike `jumpIn`, `jumpFrac` accepts fractional delays. jumpFrac :: Time -- ^ how long to wait - -> Time -- ^ not supplied by user! - -> [Pattern a] -- ^ not supplied by user! + -> Time -- ^ PITFALL: Not provided by the user. + -> [Pattern a] -- ^ PITFALL: Not provided by the user. -> Pattern a jumpFrac _ _ [] = silence jumpFrac _ _ (pat:[]) = pat @@ -154,12 +157,12 @@ jumpFrac wait now (pat':pat:_) = stack [ filterWhen (< (now + wait)) pat , filterWhen (>= (now + wait)) pat' ] --- | Unlike `jumpIn`, `jumpFrac` accepts fractional start times. --- Unlike `jumpFrac`, this takes an absolute time, --- not a delay to be added to the current time. +-- | Unlike `jumpIn`, `jumpFracAbs` accepts fractional start times. +-- Unlike `jumpFrac`, `jumpFracAbs` takes an absolute time, +-- rather than a delay to be added to the current time. jumpFracAbs :: Time -- ^ when to transition - -> Time -- ^ not supplied by user! - -> [Pattern a] -- ^ not supplied by user! + -> Time -- ^ PITFALL: Not provided by the user. + -> [Pattern a] -- ^ PITFALL: Not provided by the user. -> Pattern a jumpFracAbs _ _ [] = silence jumpFracAbs _ _ (pat:[]) = pat From 928128da298b8c218a1a68af65977178a9ac5067 Mon Sep 17 00:00:00 2001 From: Jeffrey Benjamin Brown Date: Sat, 18 Nov 2023 21:49:44 -0500 Subject: [PATCH 10/11] simplify --- BootTidal.hs | 4 ++-- src/Sound/Tidal/Transition.hs | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/BootTidal.hs b/BootTidal.hs index 2272dd9ce..a9ba04373 100644 --- a/BootTidal.hs +++ b/BootTidal.hs @@ -41,8 +41,8 @@ let only = (hush >>) jumpIn' i t = transition tidal True (Sound.Tidal.Transition.jumpIn' t) i jumpMod i t = transition tidal True (Sound.Tidal.Transition.jumpMod t) i jumpMod' i t p = transition tidal True (Sound.Tidal.Transition.jumpMod' t p) i - sched = Sound.Tidal.Transition.sched tidal getnow - schod = Sound.Tidal.Transition.schod tidal getnow + sched = Sound.Tidal.Transition.sched tidal + schod = Sound.Tidal.Transition.schod tidal mortal i lifespan release = transition tidal True (Sound.Tidal.Transition.mortal lifespan release) i interpolate i = transition tidal True (Sound.Tidal.Transition.interpolate) i interpolateIn i t = transition tidal True (Sound.Tidal.Transition.interpolateIn t) i diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs index 05beacc04..5b0b824f3 100644 --- a/src/Sound/Tidal/Transition.hs +++ b/src/Sound/Tidal/Transition.hs @@ -63,12 +63,11 @@ do setcps 1 @ -} sched :: Stream -- ^ PITFALL: Not provided by user. - -> IO Double -- ^ PITFALL: Not provided by user. -> ID -- ^ voice to affect -> [(Time, ControlPattern)] -- ^ schedule -> IO () -sched tidal getnow i s = do - now <- getnow +sched tidal i s = do + now <- streamGetnow tidal let t = fst $ head s p = absScheduleToPat $ sortOn fst -- earlier patterns closer to head @@ -90,13 +89,12 @@ do setcps 1 @ -} schod :: Stream -- ^ PITFALL: Not provided by user. - -> IO Double -- ^ PITFALL: Not provided by user. -> ID -- ^ voice to affect -> Time -- ^ divisor -> [(Time, ControlPattern)] -- ^ schedule -> IO () -schod tidal getnow i divisor sRel = do - now <- getnow +schod tidal i divisor sRel = do + now <- streamGetnow tidal let sAbs :: [ (Time, ControlPattern) ] = sortOn fst -- earlier patterns closer to head ( delayModSchedule_toAbsoluteSchedule From 84116b04cd3b5603392c7a0563fe0b7d51fd539a Mon Sep 17 00:00:00 2001 From: Jeffrey Benjamin Brown Date: Sun, 19 Nov 2023 13:14:09 -0500 Subject: [PATCH 11/11] Edit some comments. Remove some unneeded brackets. Improve spacing of layout. --- src/Sound/Tidal/Transition.hs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs index 5b0b824f3..001b4e93a 100644 --- a/src/Sound/Tidal/Transition.hs +++ b/src/Sound/Tidal/Transition.hs @@ -57,9 +57,9 @@ relative to the current cycle. @ do setcps 1 d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) - sched 2 [ (2, s "[lt*5]"), - (4, s "[ht*4]"), - (6, s "[hc*3]") ] + sched 2 [ (2, s "lt*5"), + (4, s "ht*4"), + (6, s "hc*3") ] @ -} sched :: Stream -- ^ PITFALL: Not provided by user. @@ -80,12 +80,12 @@ relative to the most recent time that was divisible by the divisor. @ do setcps 1 d1 $ s "[bd,numbers]" |* n (slow 8 $ run 8) - schod 2 8 [ (2, s "[lt*5]"), - (4, s "[ht*4]"), - (6, s "[hc*3]"), - (8, s "~ sn:1" ), - (12, s "~ ~ sn:1" ), - (16, silence) ] + schod 2 8 [ (2, s "lt*5"), + (4, s "ht*4"), + (6, s "hc*3"), + (8, s "~ sn:1" ), + (12, s "~ ~ sn:1" ), + (16, silence) ] @ -} schod :: Stream -- ^ PITFALL: Not provided by user.