tidal 1.5.2 → 1.6.0
raw patch · 12 files changed
+228/−49 lines, 12 filesdep ~basedep ~deepseqdep ~hosc
Dependency ranges changed: base, deepseq, hosc
Files
- BootTidal.hs +2/−0
- CHANGELOG.md +11/−0
- src/Sound/Tidal/Control.hs +5/−2
- src/Sound/Tidal/Core.hs +5/−2
- src/Sound/Tidal/Pattern.hs +12/−22
- src/Sound/Tidal/Stream.hs +42/−16
- src/Sound/Tidal/UI.hs +17/−4
- src/Sound/Tidal/Version.hs +1/−1
- test/Sound/Tidal/ExceptionsTest.hs +73/−0
- test/Test.hs +2/−0
- test/dontcrash.hs +46/−0
- tidal.cabal +12/−2
BootTidal.hs view
@@ -14,6 +14,8 @@ let only = (hush >>) p = streamReplace tidal hush = streamHush tidal+ panic = do hush+ once $ sound "superpanic" list = streamList tidal mute = streamMute tidal unmute = streamUnmute tidal
CHANGELOG.md view
@@ -1,5 +1,16 @@ # TidalCycles log of changes ++## 1.6.0 - Keep live coding live+ * Rollback to previous pattern on parse error @jwaldmann+ * Increased strictness to catch parse errors earlier @jwaldmann @yaxu+ * Support for superdirt 'panic' @yaxu+ * Increase hosc upper bounds to admin 0.18+ * New function 'splat' @yaxu+ * `quantise` now uses round, add qfloor, qceiling variants and qround alis @lwlsn+ * Add ghc 8.8.3 to travis @jwaldmann+ * Switch `substruct` to use binary pattern @yaxu+ ## 1.5.2 - Rivelin * Fix streamAll
src/Sound/Tidal/Control.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverloadedStrings, FlexibleContexts #-}+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverloadedStrings, FlexibleContexts, BangPatterns #-} module Sound.Tidal.Control where @@ -287,7 +287,7 @@ loopAt n p = slow n p |* P.speed (fromRational <$> (1/n)) # P.unit (pure "c") hurry :: Pattern Rational -> ControlPattern -> ControlPattern-hurry x = (|* P.speed (fromRational <$> x)) . fast x+hurry !x = (|* P.speed (fromRational <$> x)) . fast x {- | Smash is a combination of `spread` and `striate` - it cuts the samples into the given number of bits, and then cuts between playing the loop@@ -402,3 +402,6 @@ offset st = fromMaybe (pure 0) $ do p <- Map.lookup ctrl (controls st) return $ ((f . fromMaybe 0 . getR) <$> p) ctrl = "_t_" ++ show k++splat :: Pattern Int -> ControlPattern -> ControlPattern -> ControlPattern+splat slices epat pat = (chop slices pat) # bite 1 (const 0 <$> pat) epat
src/Sound/Tidal/Core.hs view
@@ -367,8 +367,11 @@ compressTo :: (Time,Time) -> Pattern a -> Pattern a compressTo (s,e) = compressArcTo (Arc s e) -repeatCycles :: Int -> Pattern a -> Pattern a-repeatCycles n p = cat (replicate n p)+repeatCycles :: Pattern Int -> Pattern a -> Pattern a+repeatCycles = tParam _repeatCycles++_repeatCycles :: Int -> Pattern a -> Pattern a+_repeatCycles n p = cat (replicate n p) fastRepeatCycles :: Int -> Pattern a -> Pattern a fastRepeatCycles n p = cat (replicate n p)
src/Sound/Tidal/Pattern.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DeriveDataTypeable, FlexibleInstances, TypeSynonymInstances #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Sound.Tidal.Pattern where@@ -9,6 +10,7 @@ import Control.Applicative (liftA2) --import Data.Bifunctor (Bifunctor(..)) import Data.Data (Data) -- toConstr+import GHC.Generics import Data.List (delete, findIndex, sort) import qualified Data.Map.Strict as Map import Data.Maybe (isJust, fromJust, catMaybes, mapMaybe)@@ -42,13 +44,11 @@ data ArcF a = Arc { start :: a , stop :: a- } deriving (Eq, Ord, Functor, Show)+ } deriving (Eq, Ord, Functor, Show, Generic) type Arc = ArcF Time -instance NFData a => - NFData (ArcF a) where - rnf (Arc s e) = rnf s `seq` rnf e+instance NFData a => NFData (ArcF a) instance Num a => Num (ArcF a) where negate = fmap negate@@ -133,10 +133,9 @@ isIn (Arc s e) t = t >= s && t < e data Context = Context {contextPosition :: [((Int, Int), (Int, Int))]}- deriving (Eq, Ord)+ deriving (Eq, Ord, Generic) -instance NFData Context where - rnf (Context c) = rnf c+instance NFData Context combineContexts :: [Context] -> Context combineContexts = Context . concatMap contextPosition@@ -159,13 +158,11 @@ , whole :: Maybe a , part :: a , value :: b- } deriving (Eq, Ord, Functor)+ } deriving (Eq, Ord, Functor, Generic) type Event a = EventF (ArcF Time) a -instance (NFData a, NFData b) => - NFData (EventF a b) where - rnf (Event c w p v) = rnf c `seq` rnf w `seq` rnf p `seq` rnf v+instance (NFData a, NFData b) => NFData (EventF a b) {-instance Bifunctor EventF where bimap f g (Event w p e) = Event (f w) (f p) (g e)@@ -254,6 +251,7 @@ -- | A datatype that's basically a query data Pattern a = Pattern {query :: Query a}+ deriving Generic data Value = VS { svalue :: String } | VF { fvalue :: Double }@@ -261,18 +259,12 @@ | VI { ivalue :: Int } | VB { bvalue :: Bool } | VX { xvalue :: [Word8] } -- Used for OSC 'blobs'- deriving (Typeable,Data)+ deriving (Typeable,Data, Generic) class Valuable a where toValue :: a -> Value -instance NFData Value where - rnf (VS s) = rnf s - rnf (VF f) = rnf f - rnf (VR r) = rnf r - rnf (VI i) = rnf i - rnf (VB b) = rnf b- rnf (VX xs) = rnf xs+instance NFData Value instance Valuable String where toValue = VS@@ -334,9 +326,7 @@ ------------------------------------------------------------------------ -- * Instances -instance NFData a => - NFData (Pattern a) where - rnf (Pattern q) = rnf $ \s -> q s+instance NFData a => NFData (Pattern a) instance Functor Pattern where -- | apply a function to all the values in a pattern
src/Sound/Tidal/Stream.hs view
@@ -1,11 +1,13 @@ {-# LANGUAGE ConstraintKinds, GeneralizedNewtypeDeriving, FlexibleContexts, ScopedTypeVariables, BangPatterns #-} {-# OPTIONS_GHC -fno-warn-missing-fields #-}+{-# language DeriveGeneric, StandaloneDeriving #-} module Sound.Tidal.Stream where import Control.Applicative ((<|>)) import Control.Concurrent.MVar import Control.Concurrent+import Control.Monad (forM_) import qualified Data.Map.Strict as Map import Data.Maybe (fromJust, fromMaybe, catMaybes) import qualified Control.Exception as E@@ -275,13 +277,32 @@ state = T.State {T.ticks = 0, T.start = now, T.nowTimespan = (now, now + (1/cps)),+ T.starting = True, -- really? T.nowArc = (Arc 0 1) } doTick True (stream {sPMapMV = pMapMV}) state +-- | Query the current pattern (contained in argument @stream :: Stream@)+-- for the events in the current arc (contained in argument @st :: T.State@),+-- translate them to OSC messages, and send these.+--+-- If an exception occurs during sending,+-- this functions prints a warning and continues, because+-- the likely reason is that the backend (supercollider) isn't running.+-- +-- If any exception occurs before or outside sending+-- (e.g., while querying the pattern, while computing a message),+-- this function prints a warning and resets the current pattern+-- to the previous one (or to silence if there isn't one) and continues,+-- because the likely reason is that something is wrong with the current pattern. doTick :: Bool -> Stream -> T.State -> IO () doTick fake stream st =- do tempo <- takeMVar (sTempoMV stream)+ E.handle (\ (e :: E.SomeException) -> do+ putStrLn $ "Failed to Stream.doTick: " ++ show e+ putStrLn $ "Return to previous pattern."+ setPreviousPatternOrSilence stream+ ) $+ modifyMVar_ (sTempoMV stream) $ \ tempo -> do pMap <- readMVar (sPMapMV stream) sMap <- readMVar (sInput stream) sGlobalF <- readMVar (sGlobalFMV stream)@@ -308,22 +329,27 @@ -- TODO onset is calculated in toOSC as well.. on e tempo'' = (sched tempo'' $ start $ wholeOrPart e) (tes, tempo') = processCps tempo es- mapM_ (\cx@(Cx target _ oscs) ->- (do let latency = oLatency target + extraLatency- ms = concatMap (\(t, e) ->- if (fake || (on e t) < frameEnd)- then catMaybes $ map (toOSC latency e t) oscs- else []- ) tes- E.catch $ mapM_ (send cx) ms- )- (\(e ::E.SomeException)- -> putStrLn $ "Failed to send. Is the '" ++ oName target ++ "' target running? " ++ show e- )- ) cxs- putMVar (sTempoMV stream) tempo'- return ()+ forM_ cxs $ \cx@(Cx target _ oscs) -> do+ let latency = oLatency target + extraLatency+ ms = concatMap (\(t, e) ->+ if (fake || (on e t) < frameEnd)+ then catMaybes $ map (toOSC latency e t) oscs+ else []+ ) tes+ forM_ ms $ \ m -> send cx m `E.catch` \ (e :: E.SomeException) -> do+ putStrLn $ "Failed to send. Is the '" ++ oName target ++ "' target running? " ++ show e + tempo' `seq` return tempo'+++setPreviousPatternOrSilence :: Stream -> IO ()+setPreviousPatternOrSilence stream =+ modifyMVar_ (sPMapMV stream) $ return+ . Map.map ( \ pMap -> case history pMap of+ _:p:ps -> pMap { pattern = p, history = p:ps }+ _ -> pMap { pattern = silence, history = [silence] }+ )+ send :: Cx -> (Double, O.Message) -> IO () send cx (time, m) | oSchedule target == Pre BundleStamp = O.sendBundle u $ O.Bundle time [m]
src/Sound/Tidal/UI.hs view
@@ -988,10 +988,11 @@ struct ps pv = filterJust $ (\a b -> if a then Just b else Nothing ) <$> ps <* pv -- | @substruct a b@: similar to @struct@, but each event in pattern @a@ gets replaced with pattern @b@, compressed to fit the timespan of the event.-substruct :: Pattern String -> Pattern b -> Pattern b++substruct :: Pattern Bool -> Pattern b -> Pattern b substruct s p = p {query = f} where f st =- concatMap ((\a' -> queryArc (compressArcTo a' p) a') . fromJust . whole) $ filter isDigital $ (query s st)+ concatMap ((\a' -> queryArc (compressArcTo a' p) a') . wholeOrPart) $ filter value $ query s st randArcs :: Int -> Pattern [Arc] randArcs n =@@ -1007,6 +1008,7 @@ pairUp' [a, _] = [Arc a 1] pairUp' (a:b:xs) = Arc a b: pairUp' (b:xs) + -- TODO - what does this do? Something for @stripe@ .. randStruct :: Int -> Pattern Int randStruct n = splitQueries $ Pattern {query = f}@@ -1274,7 +1276,7 @@ {- | Internal function used by shuffle and scramble -} _rearrangeWith :: Pattern Int -> Int -> Pattern a -> Pattern a-_rearrangeWith ipat n pat = innerJoin $ (\i -> _fast nT $ repeatCycles n $ pats !! i) <$> ipat+_rearrangeWith ipat n pat = innerJoin $ (\i -> _fast nT $ _repeatCycles n $ pats !! i) <$> ipat where pats = map (\i -> zoom (fromIntegral i / nT, fromIntegral (i+1) / nT) pat) [0 .. (n-1)] nT :: Time@@ -1315,6 +1317,7 @@ toEv (a',v) = do a'' <- subArc a a' return $ Event (Context []) (Just a') a'' v + ur :: Time -> Pattern String -> [(String, Pattern a)] -> [(String, Pattern a -> Pattern a)] -> Pattern a ur t outer_p ps fs = _slow t $ unwrap $ adjust <$> timedValues (getPat . split <$> outer_p) where split = wordsBy (==':')@@ -1754,7 +1757,17 @@ -- | limit values in a Pattern (or other Functor) to n equally spaced -- divisions of 1. quantise :: (Functor f, RealFrac b) => b -> f b -> f b-quantise n = fmap ((/n) . (fromIntegral :: RealFrac b => Int -> b) . floor . (*n))+quantise n = fmap ((/n) . (fromIntegral :: RealFrac b => Int -> b) . round . (*n))++-- quantise but with floor+qfloor :: (Functor f, RealFrac b) => b -> f b -> f b+qfloor n = fmap ((/n) . (fromIntegral :: RealFrac b => Int -> b) . floor . (*n))++qceiling :: (Functor f, RealFrac b) => b -> f b -> f b+qceiling n = fmap ((/n) . (fromIntegral :: RealFrac b => Int -> b) . ceiling . (*n))++qround :: (Functor f, RealFrac b) => b -> f b -> f b+qround = quantise -- | Inverts all the values in a boolean pattern inv :: Functor f => f Bool -> f Bool
src/Sound/Tidal/Version.hs view
@@ -1,4 +1,4 @@ module Sound.Tidal.Version where tidal_version :: String-tidal_version = "1.5.2"+tidal_version = "1.6.0"
+ test/Sound/Tidal/ExceptionsTest.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings, CPP #-}++module Sound.Tidal.ExceptionsTest where++import TestUtils+import Test.Microspec+import Control.Exception+import Control.DeepSeq+import Data.Typeable (typeOf)+import Prelude hiding ((<*), (*>))++import qualified Data.Map.Strict as Map++-- import Sound.Tidal.Pattern+import Sound.Tidal.Control+import Sound.Tidal.Core+import Sound.Tidal.Params+import Sound.Tidal.ParseBP+import Sound.Tidal.Pattern+import Sound.Tidal.UI++run :: Microspec ()+run =+ describe "NFData, forcing and catching exceptions" $ do+ describe "instance NFData (Pattern a)" $ do+ it "rnf forces argument" $ do+ evaluate (rnf (Pattern undefined :: Pattern ()))+ `shouldThrow` anyException+++-- copied from http://hackage.haskell.org/package/hspec-expectations-0.8.2/docs/src/Test-Hspec-Expectations.html#shouldThrow++shouldThrow :: (Exception e) => IO a -> Selector e -> Microspec ()+action `shouldThrow` p = prop "shouldThrow" $ monadicIO $ do+ r <- Test.Microspec.run $ try action+ case r of+ Right _ ->+ -- "finished normally, but should throw exception: " ++ exceptionType+ Test.Microspec.assert False+ Left e ->+ -- "threw exception that did not meet expectation")+ Test.Microspec.assert $ p e+ where+ -- a string repsentation of the expected exception's type+ exceptionType = (show . typeOf . instanceOf) p+ where+ instanceOf :: Selector a -> a+ instanceOf _ = error "Test.Hspec.Expectations.shouldThrow: broken Typeable instance"++-- |+-- A @Selector@ is a predicate; it can simultaneously constrain the type and+-- value of an exception.++type Selector a = (a -> Bool)++anyException :: Selector SomeException+anyException = const True++anyErrorCall :: Selector ErrorCall+anyErrorCall = const True++errorCall :: String -> Selector ErrorCall+#if MIN_VERSION_base(4,9,0)+errorCall s (ErrorCallWithLocation msg _) = s == msg+#else+errorCall s (ErrorCall msg) = s == msg+#endif++anyIOException :: Selector IOException+anyIOException = const True++anyArithException :: Selector ArithException+anyArithException = const True
test/Test.hs view
@@ -9,6 +9,7 @@ import Sound.Tidal.ScalesTest import Sound.Tidal.UITest import Sound.Tidal.UtilsTest+import Sound.Tidal.ExceptionsTest main :: IO () main = microspec $ do@@ -19,3 +20,4 @@ Sound.Tidal.ScalesTest.run Sound.Tidal.UITest.run Sound.Tidal.UtilsTest.run+ Sound.Tidal.ExceptionsTest.run
+ test/dontcrash.hs view
@@ -0,0 +1,46 @@+-- | test cases collected from some "Crash bugs"++{-# language OverloadedStrings #-}++import Control.Concurrent (threadDelay)+import Control.Monad (forM_)++import Sound.Tidal.Context+++main = do+ tidal <- startTidal (superdirtTarget {oLatency = 0.1, oAddress = "127.0.0.1", oPort = 57120}) (defaultConfig {cFrameTimespan = 1/20})+ let p = streamReplace tidal+ d1 = p 1 . (|< orbit 0)++ -- This will execute patterns-that-crash-tidal one after another,+ -- interspersed with a simple pattern.+ -- The test is whether we hear that simple pattern each time,+ -- indicating that the Tidal main loop is still usable.+ let go ps = forM_ (zip [0::Int ..] ps) $ \ (k,p) -> do+ let wait s = threadDelay $ s * 10^6+ simple = s "[bd*4, 808cy*8]"+ putStrLn $ "--- playing test pattern " ++ show k ++ " -----"+ d1 $ p ; wait 2+ putStrLn $ "---------------- playing simple pattern"+ d1 $ simple ; wait 2++ go [ "cr"++ -- https://github.com/tidalcycles/Tidal/issues/606#issue-563234396+ , gain (unwrap $ fmap (["1", "0."]!!) $ "{0 0@7 0 1@7}%16") # s "harmor" # midichan 11++ -- https://github.com/tidalcycles/Tidal/issues/606#issuecomment-598776256+ , superimpose (hurry "<0.5 2?") $ sound "bd"++ -- https://github.com/tidalcycles/Tidal/issues/477#issue-411754641+ , let mkpat name pattern = (name,pattern)+ mkfx name fx = (name,fx)+ structure = cat [+ "kicks@8 [kicks,snares]@7 kicks:backrush"+ , "[kicks@3 [kicks@3 kicks(3,8,1):r]]@4 [kicks]@4 [kicks]@7 kicks:r"+ ]+ pats = [ mkpat "kicks" $ sometimes ghost $ s "bd(<4 5 3 6>,16,<0 1 0 3>)" ]+ fx = [ mkfx "r" (# speed "-1") ]+ in ur 16 structure pats fx+ ]
tidal.cabal view
@@ -1,5 +1,5 @@ name: tidal-version: 1.5.2+version: 1.6.0 synopsis: Pattern language for improvised music -- description: homepage: http://tidalcycles.org/@@ -52,7 +52,7 @@ base >=4.8 && <5 , containers < 0.7 , colour < 2.4- , hosc >= 0.17 && < 0.18+ , hosc >= 0.17 && < 0.19 , text < 1.3 , parsec >= 3.1.12 && < 3.2 , network < 3.2@@ -83,6 +83,7 @@ Sound.Tidal.ScalesTest Sound.Tidal.UITest Sound.Tidal.UtilsTest+ Sound.Tidal.ExceptionsTest TestUtils build-depends: base ==4.*@@ -90,8 +91,17 @@ , containers , parsec , tidal+ , deepseq default-language: Haskell2010++-- not useful for automation since it requires running sclang+test-suite dontcrash+ type: exitcode-stdio-1.0+ main-is: dontcrash.hs+ hs-source-dirs: test+ build-depends: base, tidal+ default-language: Haskell2010 benchmark bench-speed type: exitcode-stdio-1.0