diff --git a/README.md b/README.md index 5d23373da..2ba73a670 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + Tidal [![Build Status](https://travis-ci.org/tidalcycles/Tidal.svg?branch=master)](https://travis-ci.org/tidalcycles/Tidal) ===== diff --git a/Sound/Tidal/Bjorklund.hs b/Sound/Tidal/Bjorklund.hs new file mode 100644 index 000000000..39c2b2a9f --- /dev/null +++ b/Sound/Tidal/Bjorklund.hs @@ -0,0 +1,34 @@ +module Sound.Tidal.Bjorklund (bjorklund) where + +-- The below is (c) Rohan Drape, taken from the hmt library and +-- distributed here under the terms of the GNU Public Licence. Tidal +-- used to just include the library but removed for now due to +-- dependency problems.. We could however likely benefit from other +-- parts of the library.. + +type STEP a = ((Int,Int),([[a]],[[a]])) + +left :: STEP a -> STEP a +left ((i,j),(xs,ys)) = + let (xs',xs'') = splitAt j xs + in ((j,i-j),(zipWith (++) xs' ys,xs'')) + +right :: STEP a -> STEP a +right ((i,j),(xs,ys)) = + let (ys',ys'') = splitAt i ys + in ((i,j-i),(zipWith (++) xs ys',ys'')) + +bjorklund' :: STEP a -> STEP a +bjorklund' (n,x) = + let (i,j) = n + in if min i j <= 1 + then (n,x) + else bjorklund' (if i > j then left (n,x) else right (n,x)) + +bjorklund :: (Int,Int) -> [Bool] +bjorklund (i,j') = + let j = j' - i + x = replicate i [True] + y = replicate j [False] + (_,(x',y')) = bjorklund' ((i,j),(x,y)) + in concat x' ++ concat y' diff --git a/Sound/Tidal/Chords.hs b/Sound/Tidal/Chords.hs new file mode 100644 index 000000000..3ff3ee2fe --- /dev/null +++ b/Sound/Tidal/Chords.hs @@ -0,0 +1,114 @@ +module Sound.Tidal.Chords where + +import Sound.Tidal.Pattern +import Data.Maybe +import Control.Applicative + +major :: [Int] +major = [0,4,7] +minor :: [Int] +minor = [0,3,7] +major7 :: [Int] +major7 = [0,4,7,11] +dom7 :: [Int] +dom7 = [0,4,7,10] +minor7 :: [Int] +minor7 = [0,3,7,10] +aug :: [Int] +aug = [0,4,8] +dim :: [Int] +dim = [0,3,6] +dim7 :: [Int] +dim7 = [0,3,6,9] +one :: [Int] +one = [0] +five :: [Int] +five = [0,7] +plus :: [Int] +plus = [0,4,8] +sharp5 :: [Int] +sharp5 = [0,4,8] +msharp5 :: [Int] +msharp5 = [0,3,8] +sus2 :: [Int] +sus2 = [0,2,7] +sus4 :: [Int] +sus4 = [0,5,7] +six :: [Int] +six = [0,4,7,9] +m6 :: [Int] +m6 = [0,3,7,9] +sevenSus2 :: [Int] +sevenSus2 = [0,2,7,10] +sevenSus4 :: [Int] +sevenSus4 = [0,5,7,10] +sevenFlat5 :: [Int] +sevenFlat5 = [0,4,6,10] +m7flat5 :: [Int] +m7flat5 = [0,3,6,10] +sevenSharp5 :: [Int] +sevenSharp5 = [0,4,8,10] +m7sharp5 :: [Int] +m7sharp5 = [0,3,8,10] +nine :: [Int] +nine = [0,4,7,10,14] +m9 :: [Int] +m9 = [0,3,7,10,14] +m7sharp9 :: [Int] +m7sharp9 = [0,3,7,10,14] +maj9 :: [Int] +maj9 = [0,4,7,11,14] +nineSus4 :: [Int] +nineSus4 = [0,5,7,10,14] +sixby9 :: [Int] +sixby9 = [0,4,7,9,14] +m6by9 :: [Int] +m6by9 = [0,3,9,7,14] +sevenFlat9 :: [Int] +sevenFlat9 = [0,4,7,10,13] +m7flat9 :: [Int] +m7flat9 = [0,3,7,10,13] +sevenFlat10 :: [Int] +sevenFlat10 = [0,4,7,10,15] +nineSharp5 :: [Int] +nineSharp5 = [0,1,13] +m9sharp5 :: [Int] +m9sharp5 = [0,1,14] +sevenSharp5flat9 :: [Int] +sevenSharp5flat9 = [0,4,8,10,13] +m7sharp5flat9 :: [Int] +m7sharp5flat9 = [0,3,8,10,13] +eleven :: [Int] +eleven = [0,4,7,10,14,17] +m11 :: [Int] +m11 = [0,3,7,10,14,17] +maj11 :: [Int] +maj11 = [0,4,7,11,14,17] +evelenSharp :: [Int] +evelenSharp = [0,4,7,10,14,18] +m11sharp :: [Int] +m11sharp = [0,3,7,10,14,18] +thirteen :: [Int] +thirteen = [0,4,7,10,14,17,21] +m13 :: [Int] +m13 = [0,3,7,10,14,17,21] + +-- | @chordate cs m n@ selects the @n@th "chord" (a chord is a list of Ints) +-- from a list of chords @cs@ and transposes it by @m@ +chordate :: Num b => [[b]] -> b -> Int -> [b] +chordate cs m n = map (+m) $ cs!!n + +-- | @flatpat@ takes a Pattern of lists and pulls the list elements as +-- separate Events +flatpat :: Pattern [a] -> Pattern a +flatpat p = stack [unMaybe $ fmap (`maybeInd` i) p | i <- [0..9]] + where maybeInd xs i | i < length xs = Just $ xs!!i + | otherwise = Nothing + unMaybe = (fromJust <$>) . filterValues isJust + +-- | @enchord chords pn pc@ turns every note in the note pattern @pn@ into +-- a chord, selecting from the chord lists @chords@ using the index pattern +-- @pc@. For example, @Chords.enchord [Chords.major Chords.minor] "c g" "0 1"@ +-- will create a pattern of a C-major chord followed by a G-minor chord. +enchord :: Num a => [[a]] -> Pattern a -> Pattern Int -> Pattern a +enchord chords pn pc = flatpat $ (chordate chords) <$> pn <*> pc diff --git a/Sound/Tidal/Context.hs b/Sound/Tidal/Context.hs index 0f6826892..08663019e 100644 --- a/Sound/Tidal/Context.hs +++ b/Sound/Tidal/Context.hs @@ -15,3 +15,5 @@ import Sound.Tidal.Time as C import Sound.Tidal.SuperCollider as C import Sound.Tidal.Params as C import Sound.Tidal.Transition as C +import qualified Sound.Tidal.Scales as Scales +import qualified Sound.Tidal.Chords as Chords diff --git a/Sound/Tidal/Dirt.hs b/Sound/Tidal/Dirt.hs index 67a1493f2..2572f23d9 100644 --- a/Sound/Tidal/Dirt.hs +++ b/Sound/Tidal/Dirt.hs @@ -13,7 +13,6 @@ import Data.Bits import Data.Maybe import Data.Fixed import Data.Ratio -import System.Process import Sound.Tidal.Stream import Sound.Tidal.OscStream @@ -58,7 +57,7 @@ dirt = Shape { params = [ s_p, release_p ], cpsStamp = True, - latency = 0.04 + latency = 0.3 } dirtSlang = OscSlang { @@ -102,7 +101,7 @@ superDirtSetters getNow = do ds <- superDirtState 57120 superDirts :: [Int] -> IO [(ParamPattern -> IO (), (Time -> [ParamPattern] -> ParamPattern) -> ParamPattern -> IO ())] -superDirts ports = do (_, getNow) <- bpsUtils +superDirts ports = do (_, getNow) <- cpsUtils states <- mapM (superDirtState) ports return $ map (\state -> (setter state, transition getNow state)) states @@ -118,7 +117,7 @@ dirtstream _ = dirtStream dirtToColour :: ParamPattern -> Pattern ColourD dirtToColour p = s - where s = fmap (\x -> maybe black (maybe black datumToColour) (Map.lookup (param dirt "sound") x)) p + where s = fmap (\x -> maybe black (datumToColour) (Map.lookup (param dirt "s") x)) p showToColour :: Show a => a -> ColourD showToColour = stringToColour . show @@ -198,10 +197,9 @@ striate' n f p = cat $ map (\x -> off (fromIntegral x) p) [0 .. n-1] where off i p = p # begin (atom (slot * i) :: Pattern Double) # end (atom ((slot * i) + f) :: Pattern Double) slot = (1 - f) / (fromIntegral n) -{- | _not sure what this does_, variant of `striate` -} -striateO :: ParamPattern -> Int -> Double -> ParamPattern -striateO p n o = cat $ map (\x -> off (fromIntegral x) p) [0 .. n-1] - where off i p = p # begin ((atom $ (fromIntegral i / fromIntegral n) + o) :: Pattern Double) # end ((atom $ (fromIntegral (i+1) / fromIntegral n) + o) :: Pattern Double) +{- | like `striate`, but with an offset to the begin and end values -} +striateO :: Int -> Double -> ParamPattern -> ParamPattern +striateO n o p = striate n p |+| begin (atom o :: Pattern Double) |+| end (atom o :: Pattern Double) {- | Just like `striate`, but also loops each sample chunk a number of times specified in the second argument. The primed version is just like `striate'`, where the loop count is the third argument. For example: @@ -218,11 +216,36 @@ striateL' n f l p = striate' n f p # loop (atom $ fromIntegral l) metronome = slow 2 $ sound (p "[odx, [hh]*8]") +{-| +Also degrades the current pattern and undegrades the next. +To change the number of cycles the transition takes, you can use @clutchIn@ like so: + +@ +d1 $ sound "bd(5,8)" + +t1 (clutchIn 8) $ sound "[hh*4, odx(3,8)]" +@ + +will take 8 cycles for the transition. +-} clutchIn :: Time -> Time -> [Pattern a] -> Pattern a clutchIn _ _ [] = silence clutchIn _ _ (p:[]) = p clutchIn t now (p:p':_) = overlay (fadeOut' now t p') (fadeIn' now t p) +{-| +Degrades the current pattern while undegrading the next. + +This is like @xfade@ but not by gain of samples but by randomly removing events from the current pattern and slowly adding back in missing events from the next one. + +@ +d1 $ sound "bd(3,8)" + +t1 clutch $ sound "[hh*4, odx(3,8)]" +@ + +@clutch@ takes two cycles for the transition, essentially this is @clutchIn 2@. +-} clutch :: Time -> [Pattern a] -> Pattern a clutch = clutchIn 2 @@ -241,7 +264,7 @@ xfadeIn _ _ [] = silence xfadeIn _ _ (p:[]) = p xfadeIn t now (p:p':_) = overlay (p |*| gain (now ~> (slow t envEqR))) (p' |*| gain (now ~> (slow t (envEq)))) -{- | +{- | Crossfade between old and new pattern over the next two cycles. @ @@ -255,7 +278,7 @@ t1 xfade $ sound "can*3" xfade :: Time -> [ParamPattern] -> ParamPattern xfade = xfadeIn 2 -{- | Stut applies a type of delay to a pattern. It has three parameters, +{- | Stut applies a type of delay to a pattern. It has three parameters, which could be called depth, feedback and time. Depth is an integer and the others floating point. This adds a bit of echo: @@ -263,7 +286,7 @@ and the others floating point. This adds a bit of echo: d1 $ stut 4 0.5 0.2 $ sound "bd sn" @ -The above results in 4 echos, each one 50% quieter than the last, +The above results in 4 echos, each one 50% quieter than the last, with 1/5th of a cycle between them. It is possible to reverse the echo: @ @@ -275,7 +298,13 @@ stut steps feedback time p = stack (p:(map (\x -> (((x%steps)*time) ~> (p |*| ga where scale x = ((+feedback) . (*(1-feedback)) . (/(fromIntegral steps)) . ((fromIntegral steps)-)) x -{- | _not sure what this does_, variant of `stut` +{- | Instead of just decreasing volume to produce echoes, @stut'@ allows to apply a function for each step and overlays the result delayed by the given time. + +@ +d1 $ stut' 2 (1%3) (# vowel "{a e i o u}%2") $ sound "bd sn" +@ + +In this case there are two _overlays_ delayed by 1/3 of a cycle, where each has the @vowel@ filter applied. -} stut' :: Integer -> Time -> (ParamPattern -> ParamPattern) -> ParamPattern -> ParamPattern stut' steps steptime f p | steps <= 0 = p @@ -297,3 +326,6 @@ Build up some tension, culminating in a _drop_ to the new pattern after 8 cycles -} anticipate :: Time -> [ParamPattern] -> ParamPattern anticipate = anticipateIn 8 + +{- | Copies the @n@ parameter to the @orbit@ parameter, so different sound variants or notes go to different orbits in SuperDirt. -} +nToOrbit = copyParam n_p orbit_p diff --git a/Sound/Tidal/OscStream.hs b/Sound/Tidal/OscStream.hs index 1a049fc52..6a9675b54 100644 --- a/Sound/Tidal/OscStream.hs +++ b/Sound/Tidal/OscStream.hs @@ -19,20 +19,32 @@ data OscSlang = OscSlang {path :: String, preamble :: [Datum] } -type OscMap = Map.Map Param (Maybe Datum) +type OscMap = Map.Map Param Datum -toOscDatum :: Value -> Maybe Datum -toOscDatum (VF x) = Just $ float x -toOscDatum (VI x) = Just $ int32 x -toOscDatum (VS x) = Just $ string x +toOscDatum :: Value -> Datum +toOscDatum (VF x) = float x +toOscDatum (VI x) = int32 x +toOscDatum (VS x) = string x toOscMap :: ParamMap -> OscMap -toOscMap m = Map.map (toOscDatum) (Map.mapMaybe (id) m) +toOscMap m = Map.map (toOscDatum) m -- constructs and sends an Osc Message according to the given slang -- and other params - this is essentially the same as the former -- toMessage in Stream.hs -send s slang shape change tick (o, m) = osc + +send + :: (Integral a) => + UDP + -> OscSlang + -> Shape + -> Tempo + -> a + -> (Double, + Double, + OscMap) + -> IO () +send s slang shape change tick (on, off, m) = osc where osc | timestamp slang == BundleStamp = sendOSC s $ Bundle (ut_to_ntpr logicalOnset) [Message (path slang) oscdata] @@ -41,18 +53,26 @@ send s slang shape change tick (o, m) = osc | otherwise = doAt logicalOnset $ sendOSC s $ Message (path slang) oscdata oscPreamble = cpsPrefix ++ preamble slang - oscdata | namedParams slang = oscPreamble ++ (concatMap (\(k, Just v) -> [string (name k), v] ) - $ filter (isJust . snd) $ Map.assocs m) - | otherwise = oscPreamble ++ (catMaybes $ mapMaybe (\x -> Map.lookup x m) (params shape)) - cpsPrefix | cpsStamp shape && namedParams slang = [string "cps", float (cps change)] + oscdata | namedParams slang = oscPreamble ++ (concatMap (\(k, v) -> [string (name k), v] ) + $ Map.assocs m) + | otherwise = oscPreamble ++ (catMaybes $ map (\x -> Map.lookup x m) (params shape)) + cpsPrefix | cpsStamp shape && namedParams slang = [string "cps", + float (cps change), + string "delta", + float (logicalOffset + - logicalOnset), + string "cycle", float cycle + ] | cpsStamp shape = [float (cps change)] | otherwise = [] - parameterise ds = mergelists (map (string . name) (params shape)) ds + cycle = (on + fromIntegral tick) / (fromIntegral ticksPerCycle) + _parameterise ds = mergelists (map (string . name) (params shape)) ds usec = floor $ 1000000 * (logicalOnset - (fromIntegral sec)) sec = floor logicalOnset - logicalOnset = logicalOnset' change tick o ((latency shape) + nudge) - nudge = maybe 0 (toF) (Map.lookup nudge_p m) - toF (Just (Float f)) = float2Double f + logicalOnset = logicalOnset' change tick on ((latency shape) + nudge) + logicalOffset = logicalOnset' change tick off ((latency shape) + nudge) + nudge = maybe 0 (toF) (Map.lookup nudge_p (m :: OscMap)) + toF (Float f) = float2Double f toF _ = 0 -- type OscMap = Map.Map Param (Maybe Datum) @@ -62,10 +82,10 @@ send s slang shape change tick (o, m) = osc makeConnection :: String -> Int -> OscSlang -> IO (ToMessageFunc) makeConnection address port slang = do s <- openUDP address port - return (\ shape change tick (o,m) -> do + return (\ shape change tick (on,off,m) -> do let m' = if (namedParams slang) then (Just m) else (applyShape' shape m) -- this might result in Nothing, make sure we do this first m'' <- fmap (toOscMap) m' -- to allow us to simplify `send` (no `do`) - return $ send s slang shape change tick (o,m'') + return $ send s slang shape change tick (on,off,m'') ) diff --git a/Sound/Tidal/Params.hs b/Sound/Tidal/Params.hs index 41bb83a1c..5febdea72 100644 --- a/Sound/Tidal/Params.hs +++ b/Sound/Tidal/Params.hs @@ -8,7 +8,7 @@ import Control.Applicative make' :: (a -> Value) -> Param -> Pattern a -> ParamPattern make' toValue par p = fmap (\x -> Map.singleton par (defaultV x)) p - where defaultV a = Just $ toValue a + where defaultV a = toValue a -- | group multiple params into one grp :: [Param] -> Pattern String -> ParamPattern @@ -17,15 +17,19 @@ grp params p = (fmap lookupPattern p) where lookupPattern :: String -> ParamMap lookupPattern s = Map.fromList $ map (\(param,s') -> toPV param s') $ zip params $ (split s) split s = wordsBy (==':') s - toPV :: Param -> String -> (Param, Maybe Value) - toPV param@(S _ _) s = (param, (Just $ VS s)) - toPV param@(F _ _) s = (param, (Just $ VF $ read s)) - toPV param@(I _ _) s = (param, (Just $ VI $ read s)) + toPV :: Param -> String -> (Param, Value) + toPV param@(S _ _) s = (param, (VS s)) + toPV param@(F _ _) s = (param, (VF $ read s)) + toPV param@(I _ _) s = (param, (VI $ read s)) {- | -a pattern of strings representing sound sample names (required). +A pattern of strings representing sounds or synth notes. -`sound` is a combination of the `s` and `n` parameters to allow specifying both sample name and sample variation in one: +Internally, `sound` or its shorter alias `s` is a combination of the samplebank name and number when used with samples, or synth name and note number when used with a synthesiser. For example `bd:2` specifies the third sample (not the second as you might expect, because we start counting at zero) in the `bd` sample folder. + +*Internally, `sound`/`s` is a combination of two parameters, the +hidden parameter `s'` which specifies the samplebank or synth, and the +`n` parameter which specifies the sample or note number. For example: @ d1 $ sound "bd:2 sn:0" @@ -34,11 +38,25 @@ d1 $ sound "bd:2 sn:0" is essentially the same as: @ -d1 $ s "bd sn" # n "2 0" +d1 $ s' "bd sn" # n "2 0" +@ + +`n` is therefore useful when you want to pattern the sample or note +number separately from the samplebank or synth. For example: + +@ +d1 $ n "0 5 ~ 2" # sound "drum" +@ + +is equivalent to: + +@ +d1 $ sound "drum:0 drum:5 ~ drum:2" @ -} sound :: Pattern String -> ParamPattern sound = grp [s_p, n_p] +s = sound pF name defaultV = (make' VF param, param) where param = F name defaultV @@ -72,9 +90,16 @@ d1 $ (chop 8 $ sounds "breaks125") # unit "c" # coarse "1 2 4 8 16 32 64 128" which performs a similar effect, but due to differences in implementation sounds different. -} +begin_p, channel_p, legato_p, clhatdecay_p, coarse_p, crush_p :: Param +begin, legato, clhatdecay, crush :: Pattern Double -> ParamPattern +channel, coarse :: Pattern Int -> ParamPattern (begin, begin_p) = pF "begin" (Just 0) -- | choose the physical channel the pattern is sent to, this is super dirt specific (channel, channel_p) = pI "channel" Nothing + +--legato controls the amount of overlap between two adjacent synth sounds +(legato, legato_p) = pF "legato" (Just 1) + (clhatdecay, clhatdecay_p) = pF "clhatdecay" (Just 0) -- | fake-resampling, a pattern of numbers for lowering the sample rate, i.e. 1 for original 2 for half, 3 for a third and so on. (coarse, coarse_p) = pI "coarse" (Just 0) @@ -150,7 +175,7 @@ Using `cut "0"` is effectively _no_ cutgroup. -} (lock, lock_p) = pF "lock" (Just 0) -- | loops the sample (from `begin` to `end`) the specified number of times. -(loop, loop_p) = pI "loop" (Just 1) +(loop, loop_p) = pF "loop" (Just 1) (lophat, lophat_p) = pF "lophat" (Just 0) (lsnare, lsnare_p) = pF "lsnare" (Just 0) -- | specifies the sample variation to be used @@ -165,8 +190,20 @@ d1 $ stack [ ] # nudge "[0 0.04]*4" @ -Low values will give a more _human_ feeling, high values might result in quite the contrary. --} +--pitch model -} + +degree, mtranspose, ctranspose, harmonic, stepsPerOctave, octaveRatio :: Pattern Double -> ParamPattern +degree_p, mtranspose_p, ctranspose_p, harmonic_p, stepsPerOctave_p, octaveRatio_p :: Param +(degree, degree_p) = pF "degree" Nothing +(mtranspose, mtranspose_p) = pF "mtranspose" Nothing +(ctranspose, ctranspose_p) = pF "ctranspose" Nothing +(harmonic, harmonic_p) = pF "ctranspose" Nothing +(stepsPerOctave, stepsPerOctave_p) = pF "stepsPerOctave" Nothing +(octaveRatio, octaveRatio_p) = pF "octaveRatio" Nothing + + +--Low values will give a more _human_ feeling, high values might result in quite the contrary. + (nudge, nudge_p) = pF "nudge" (Just 0) (octave, octave_p) = pI "octave" (Just 3) (offset, offset_p) = pF "offset" (Just 0) @@ -174,8 +211,17 @@ Low values will give a more _human_ feeling, high values might result in quite t {- | a pattern of numbers. An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share hardware output bus offset and global effects, e.g. reverb and delay. The maximum number of orbits is specified in the superdirt startup, numbers higher than maximum will wrap around. -} (orbit, orbit_p) = pI "orbit" (Just 0) --- | a pattern of numbers between 0 and 1, from left to right (assuming stereo). +-- | a pattern of numbers between 0 and 1, from left to right (assuming stereo), once round a circle (assuming multichannel) (pan, pan_p) = pF "pan" (Just 0.5) +-- | a pattern of numbers between -inf and inf, which controls how much multichannel output is fanned out (negative is backwards ordering) +(panspan, panspan_p) = pF "span" (Just 1.0) +-- | a pattern of numbers between 0.0 and 1.0, which controls the multichannel spread range (multichannel only) +(pansplay, pansplay_p) = pF "splay" (Just 1.0) +-- | a pattern of numbers between 0.0 and inf, which controls how much each channel is distributed over neighbours (multichannel only) +(panwidth, panwidth_p) = pF "panwidth" (Just 2.0) +-- | a pattern of numbers between -1.0 and 1.0, which controls the relative position of the centre pan in a pair of adjacent speakers (multichannel only) +(panorient, panorient_p) = pF "orientation" (Just 0.5) + (pitch1, pitch1_p) = pF "pitch1" (Just 0) (pitch2, pitch2_p) = pF "pitch2" (Just 0) (pitch3, pitch3_p) = pF "pitch3" (Just 0) @@ -196,15 +242,18 @@ Low values will give a more _human_ feeling, high values might result in quite t -- | a pattern of numbers from 0 to 1. Sets the perceptual size (reverb time) of the `room` to be used in reverb. (size, size_p) = pF "size" Nothing (slide, slide_p) = pF "slide" (Just 0) --- | a pattern of numbers from 0 to 1, which changes the speed of sample playback, i.e. a cheap way of changing pitch +-- | a pattern of numbers which changes the speed of sample playback, i.e. a cheap way of changing pitch. Negative values will play the sample backwards! (speed, speed_p) = pF "speed" (Just 1) -- | a pattern of strings. Selects the sample to be played. -(s, s_p) = pS "s" Nothing +(s', s_p) = pS "s" Nothing (stutterdepth, stutterdepth_p) = pF "stutterdepth" (Just 0) (stuttertime, stuttertime_p) = pF "stuttertime" (Just 0) (sustain, sustain_p) = pF "sustain" (Just 0) (tomdecay, tomdecay_p) = pF "tomdecay" (Just 0) --- | only accepts a value of "c". Used in conjunction with `speed`, it time-stretches a sample to fit in a cycle. +{- | used in conjunction with `speed`, accepts values of "r" (rate, default behavior), "c" (cycles), or "s" (seconds). +Using `unit "c"` means `speed` will be interpreted in units of cycles, e.g. `speed "1"` means samples will be stretched to fill a cycle. +Using `unit "s"` means the playback speed will be adjusted so that the duration is the number of seconds specified by `speed`. +-} (unit, unit_p) = pS "unit" (Just "rate") (velocity, velocity_p) = pF "velocity" (Just 0.5) (vcfegint, vcfegint_p) = pF "vcfegint" (Just 0) @@ -220,7 +269,23 @@ Low values will give a more _human_ feeling, high values might result in quite t (expression,expression_p) = pF "expression" (Just 1) (sustainpedal,sustainpedal_p) = pF "sustainpedal" (Just 0) +-- Tremolo Audio DSP effect | params are "tremolorate" and "tremolodepth" +tremolorate, tremolodepth :: Pattern Double -> ParamPattern +tremolorate_p, tremolodepth_p :: Param +(tremolorate,tremolorate_p) = pF "tremolorate" (Just 1) +(tremolodepth,tremolodepth_p) = pF "tremolodepth" (Just 0.5) + +-- Phaser Audio DSP effect | params are "phaserrate" and "phaserdepth" +phaserrate, phaserdepth :: Pattern Double -> ParamPattern +phaserrate_p, phaserdepth_p :: Param +(phaserrate,phaserrate_p) = pF "phaserrate" (Just 1) +(phaserdepth,phaserdepth_p) = pF "phaserdepth" (Just 0.5) + -- aliases +att, chdecay, ctf, ctfg, delayfb, delayt, lbd, lch, lcl, lcp, lcr, lfoc, lfoi + , lfop, lht, llt, loh, lsn, ohdecay, pit1, pit2, pit3, por, sag, scl, scp + , scr, sld, std, stt, sus, tdecay, vcf, vco, voi + :: Pattern Double -> ParamPattern att = attack chdecay = clhatdecay ctf = cutoff @@ -228,7 +293,7 @@ ctfg = cutoffegint delayfb = delayfeedback delayt = delaytime det = detune -gat = gate_p +gat = gate hg = hatgrain lag = lagogo lbd = lkick @@ -261,10 +326,12 @@ vcf = vcfegint vco = vcoegint voi = voice +note, midinote :: Pattern Int -> ParamPattern note = n midinote = n . ((subtract 60) <$>) -drum = n . (drumN <$>) +drum :: Pattern String -> ParamPattern +drum = midinote . (drumN <$>) drumN :: String -> Int drumN "bd" = 36 diff --git a/Sound/Tidal/Parse.hs b/Sound/Tidal/Parse.hs index 53be3a9cf..d3eb82b8b 100644 --- a/Sound/Tidal/Parse.hs +++ b/Sound/Tidal/Parse.hs @@ -1,4 +1,5 @@ -{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, OverlappingInstances, IncoherentInstances, FlexibleInstances #-} +{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, FlexibleInstances #-} +{-# LANGUAGE LambdaCase #-} module Sound.Tidal.Parse where @@ -12,53 +13,103 @@ import Data.Colour.SRGB import GHC.Exts( IsString(..) ) import Data.Monoid import Control.Exception as E -import Control.Applicative ((<$>), (<*>)) +import Control.Applicative ((<$>), (<*>), pure) import Data.Maybe import Data.List import Sound.Tidal.Pattern +import Sound.Tidal.Time (Arc, Time) + +-- | AST representation of patterns +data TPat a + = TPat_Atom a + | TPat_Density Time (TPat a) + -- We keep this distinct from 'density' because of divide-by-zero: + | TPat_Slow Time (TPat a) + | TPat_Zoom Arc (TPat a) + | TPat_DegradeBy Double (TPat a) + | TPat_Silence + | TPat_Foot + | TPat_Cat [TPat a] + | TPat_Overlay (TPat a) (TPat a) + | TPat_ShiftL Time (TPat a) + -- | TPat_E Int Int (TPat a) + | TPat_pE (TPat Int) (TPat Int) (TPat Integer) (TPat a) + deriving (Show) + +instance Monoid (TPat a) where + mempty = TPat_Silence + mappend = TPat_Overlay + +toPat :: TPat a -> Pattern a +toPat = \case + TPat_Atom x -> atom x + TPat_Density t x -> density t $ toPat x + TPat_Slow t x -> slow t $ toPat x + TPat_Zoom arc x -> zoom arc $ toPat x + TPat_DegradeBy amt x -> degradeBy amt $ toPat x + TPat_Silence -> silence + TPat_Cat xs -> cat $ map toPat xs + TPat_Overlay x0 x1 -> overlay (toPat x0) (toPat x1) + TPat_ShiftL t x -> t <~ toPat x + TPat_pE n k s thing -> + unwrap $ eoff <$> toPat n <*> toPat k <*> toPat s <*> pure (toPat thing) + TPat_Foot -> error "Can't happen, feet (.'s) only used internally.." + +p :: Parseable a => String -> Pattern a +p = toPat . parseTPat class Parseable a where - p :: String -> Pattern a + parseTPat :: String -> TPat a instance Parseable Double where - p = parseRhythm pDouble + parseTPat = parseRhythm pDouble instance Parseable String where - p = parseRhythm pVocable + parseTPat = parseRhythm pVocable instance Parseable Bool where - p = parseRhythm pBool + parseTPat = parseRhythm pBool instance Parseable Int where - p = parseRhythm pInt + parseTPat = parseRhythm pIntegral instance Parseable Integer where - p = (fromIntegral <$>) <$> parseRhythm pInt + parseTPat s = parseRhythm pIntegral s instance Parseable Rational where - p = parseRhythm pRational + parseTPat = parseRhythm pRational type ColourD = Colour Double instance Parseable ColourD where - p = parseRhythm pColour + parseTPat = parseRhythm pColour instance (Parseable a) => IsString (Pattern a) where - fromString = p + fromString = toPat . parseTPat --instance (Parseable a, Pattern p) => IsString (p a) where -- fromString = p :: String -> p a lexer = P.makeTokenParser haskellDef + +braces, brackets, parens, angles:: Parser a -> Parser a braces = P.braces lexer brackets = P.brackets lexer parens = P.parens lexer angles = P.angles lexer + +symbol :: String -> Parser String symbol = P.symbol lexer + +natural, integer :: Parser Integer natural = P.natural lexer integer = P.integer lexer + +float :: Parser Double float = P.float lexer + +naturalOrFloat :: Parser (Either Integer Double) naturalOrFloat = P.naturalOrFloat lexer data Sign = Positive | Negative @@ -89,28 +140,39 @@ r s orig = do E.handle ) (return $ p s) -parseRhythm :: Parser (Pattern a) -> String -> (Pattern a) -parseRhythm f input = either (const silence) id $ parse (pSequence f') "" input +parseRhythm :: Parser (TPat a) -> String -> TPat a +parseRhythm f input = either (const TPat_Silence) id $ parse (pSequence f') "" input where f' = f <|> do symbol "~" "rest" - return silence + return TPat_Silence -pSequenceN :: Parser (Pattern a) -> GenParser Char () (Int, Pattern a) +pSequenceN :: Parser (TPat a) -> GenParser Char () (Int, TPat a) pSequenceN f = do spaces - d <- pDensity + -- d <- pDensity ps <- many $ pPart f - return $ (length ps, density d $ cat $ concat ps) - -pSequence :: Parser (Pattern a) -> GenParser Char () (Pattern a) + <|> do symbol "." + return [TPat_Foot] + let ps' = TPat_Cat $ map TPat_Cat $ splitFeet $ concat ps + return (length ps, ps') + +-- could use splitOn here but `TPat a` isn't a member of `EQ`.. +splitFeet :: [TPat t] -> [[TPat t]] +splitFeet [] = [] +splitFeet ps = foot:(splitFeet ps') + where (foot, ps') = takeFoot ps + takeFoot [] = ([], []) + takeFoot (TPat_Foot:ps) = ([], ps) + takeFoot (p:ps) = (\(a,b) -> (p:a,b)) $ takeFoot ps + +pSequence :: Parser (TPat a) -> GenParser Char () (TPat a) pSequence f = do (_, p) <- pSequenceN f return p -pSingle :: Parser (Pattern a) -> Parser (Pattern a) +pSingle :: Parser (TPat a) -> Parser (TPat a) pSingle f = f >>= pRand >>= pMult -pPart :: Parser (Pattern a) -> Parser ([Pattern a]) -pPart f = do -- part <- parens (pSequence f) <|> pSingle f <|> pPolyIn f <|> pPolyOut f - part <- pSingle f <|> pPolyIn f <|> pPolyOut f +pPart :: Parser (TPat a) -> Parser [TPat a] +pPart f = do part <- pSingle f <|> pPolyIn f <|> pPolyOut f part <- pE part part <- pRand part spaces @@ -119,12 +181,12 @@ pPart f = do -- part <- parens (pSequence f) <|> pSingle f <|> pPolyIn f <|> pPo spaces return $ parts -pPolyIn :: Parser (Pattern a) -> Parser (Pattern a) +pPolyIn :: Parser (TPat a) -> Parser (TPat a) pPolyIn f = do ps <- brackets (pSequence f `sepBy` symbol ",") spaces pMult $ mconcat ps -pPolyOut :: Parser (Pattern a) -> Parser (Pattern a) +pPolyOut :: Parser (TPat a) -> Parser (TPat a) pPolyOut f = do ps <- braces (pSequenceN f `sepBy` symbol ",") spaces base <- do char '%' @@ -133,29 +195,35 @@ pPolyOut f = do ps <- braces (pSequenceN f `sepBy` symbol ",") return $ Just (fromIntegral i) <|> return Nothing pMult $ mconcat $ scale base ps + <|> + do ps <- angles (pSequenceN f `sepBy` symbol ",") + spaces + pMult $ mconcat $ scale (Just 1) ps where scale _ [] = [] - scale base (ps@((n,_):_)) = map (\(n',p) -> density (fromIntegral (fromMaybe n base)/ fromIntegral n') p) ps + scale base (ps@((n,_):_)) = map (\(n',p) -> TPat_Density (fromIntegral (fromMaybe n base)/ fromIntegral n') p) ps pString :: Parser (String) -pString = many1 (letter <|> oneOf "0123456789:.-_") "string" +pString = do c <- (letter <|> oneOf "0123456789") "charnum" + cs <- many (letter <|> oneOf "0123456789:.-_") "string" + return (c:cs) -pVocable :: Parser (Pattern String) +pVocable :: Parser (TPat String) pVocable = do v <- pString - return $ atom v + return $ TPat_Atom v -pDouble :: Parser (Pattern Double) +pDouble :: Parser (TPat Double) pDouble = do nf <- intOrFloat "float" let f = either fromIntegral id nf - return $ atom f + return $ TPat_Atom f -pBool :: Parser (Pattern Bool) +pBool :: Parser (TPat Bool) pBool = do oneOf "t1" - return $ atom True + return $ TPat_Atom True <|> do oneOf "f0" - return $ atom False + return $ TPat_Atom False -parseIntNote :: Parser Int +parseIntNote :: Integral i => Parser i parseIntNote = do s <- sign i <- choice [integer, parseNote] return $ applySign s $ fromIntegral i @@ -165,9 +233,8 @@ parseInt = do s <- sign i <- integer return $ applySign s $ fromIntegral i -pInt :: Parser (Pattern Int) -pInt = do i <- parseIntNote - return $ atom i +pIntegral :: Integral i => Parser (TPat i) +pIntegral = TPat_Atom <$> parseIntNote parseNote :: Integral a => Parser a parseNote = do n <- notenum @@ -176,6 +243,7 @@ parseNote = do n <- notenum let n' = foldr (+) n modifiers return $ fromIntegral $ n' + ((octave-5)*12) where + notenum :: Parser Integer notenum = choice [char 'c' >> return 0, char 'd' >> return 2, char 'e' >> return 4, @@ -184,6 +252,7 @@ parseNote = do n <- notenum char 'a' >> return 9, char 'b' >> return 11 ] + noteModifier :: Parser Integer noteModifier = choice [char 's' >> return 1, char 'f' >> return (-1), char 'n' >> return 0 @@ -192,66 +261,70 @@ parseNote = do n <- notenum fromNote :: Integral c => Pattern String -> Pattern c fromNote p = (\s -> either (const 0) id $ parse parseNote "" s) <$> p -pColour :: Parser (Pattern ColourD) +pColour :: Parser (TPat ColourD) pColour = do name <- many1 letter "colour name" colour <- readColourName name "known colour" - return $ atom colour + return $ TPat_Atom colour -pMult :: Pattern a -> Parser (Pattern a) +pMult :: TPat a -> Parser (TPat a) pMult thing = do char '*' spaces r <- pRatio - return $ density r thing + return $ TPat_Density r thing <|> do char '/' spaces r <- pRatio - return $ slow r thing + return $ TPat_Slow r thing <|> return thing -pRand :: Pattern a -> Parser (Pattern a) +pRand :: TPat a -> Parser (TPat a) pRand thing = do char '?' spaces - return $ degrade thing + return $ TPat_DegradeBy 0.5 thing <|> return thing -pE :: Pattern a -> Parser (Pattern a) +pE :: TPat a -> Parser (TPat a) pE thing = do (n,k,s) <- parens (pair) - return $ unwrap $ eoff <$> n <*> k <*> s <*> atom thing + pure $ TPat_pE n k s thing <|> return thing - where pair = do a <- pSequence pInt + where pair :: Parser (TPat Int, TPat Int, TPat Integer) + pair = do a <- pSequence pIntegral spaces symbol "," spaces - b <- pSequence pInt + b <- pSequence pIntegral c <- do symbol "," spaces - pSequence pInt - <|> return (atom 0) - return (fromIntegral <$> a, fromIntegral <$> b, fromIntegral <$> c) - eoff n k s p = ((s%(fromIntegral k)) <~) (e n k p) - + pSequence pIntegral + <|> return (TPat_Atom 0) + return (a, b, c) -pReplicate :: Pattern a -> Parser ([Pattern a]) +eoff :: Int -> Int -> Integer -> Pattern a -> Pattern a +eoff n k s p = ((s%(fromIntegral k)) <~) (e n k p) + -- TPat_ShiftL (s%(fromIntegral k)) (TPat_E n k p) + +pReplicate :: TPat a -> Parser [TPat a] pReplicate thing = do extras <- many $ do char '!' -- if a number is given (without a space) -- replicate that number of times - n <- ((read <$> many1 digit) <|> return 1) + n <- ((read <$> many1 digit) <|> return 2) spaces thing' <- pRand thing - return $ replicate (fromIntegral n) thing' + -- -1 because we already have parsed the original one + return $ replicate (fromIntegral (n-1)) thing' return (thing:concat extras) -pStretch :: Pattern a -> Parser ([Pattern a]) +pStretch :: TPat a -> Parser [TPat a] pStretch thing = do char '@' n <- ((read <$> many1 digit) <|> return 1) - return $ map (\x -> zoom (x%n,(x+1)%n) thing) [0 .. (n-1)] + return $ map (\x -> TPat_Zoom (x%n,(x+1)%n) thing) [0 .. (n-1)] pRatio :: Parser (Rational) pRatio = do n <- natural "numerator" @@ -261,12 +334,13 @@ pRatio = do n <- natural "numerator" return 1 return $ n % d -pRational :: Parser (Pattern Rational) +pRational :: Parser (TPat Rational) pRational = do r <- pRatio - return $ atom r + return $ TPat_Atom r +{- pDensity :: Parser (Rational) pDensity = angles (pRatio "ratio") <|> return (1 % 1) - +-} diff --git a/Sound/Tidal/Pattern.hs b/Sound/Tidal/Pattern.hs index 5d56a5741..31eaa58ea 100644 --- a/Sound/Tidal/Pattern.hs +++ b/Sound/Tidal/Pattern.hs @@ -16,10 +16,9 @@ import System.Random.Mersenne.Pure64 import Data.Char import qualified Data.Text as T -import Music.Theory.Bjorklund - import Sound.Tidal.Time import Sound.Tidal.Utils +import Sound.Tidal.Bjorklund -- | The pattern datatype, a function from a time @Arc@ to @Event@ -- values. For discrete patterns, this returns the events which are @@ -28,6 +27,41 @@ import Sound.Tidal.Utils data Pattern a = Pattern {arc :: Arc -> [Event a]} deriving Typeable +-- | Admit the pattern datatype to the Num, Fractional and Floating classes, +-- to allow arithmetic on them, and for bare numbers to be automatically +-- converted into patterns of numbers +instance Num a => Num (Pattern a) where + negate = fmap negate + (+) = liftA2 (+) + (*) = liftA2 (*) + fromInteger = pure . fromInteger + abs = fmap abs + signum = fmap signum + +instance (Fractional a) => Fractional (Pattern a) where + fromRational = pure . fromRational + (/) = liftA2 (/) + +instance Floating a => Floating (Pattern a) where + pi = pure pi + exp = liftA (exp) + log = liftA (log) + sqrt = liftA (sqrt) + (**) = liftA2 (**) + logBase = liftA2 (logBase) + sin = liftA (sin) + cos = liftA (cos) + tan = liftA (tan) + asin = liftA (asin) + acos = liftA (acos) + atan = liftA (atan) + sinh = liftA (sinh) + cosh = liftA (cosh) + tanh = liftA (tanh) + asinh = liftA (asinh) + acosh = liftA (acosh) + atanh = liftA (atanh) + -- | @show (p :: Pattern)@ returns a text string representing the -- event values active during the first cycle of the given pattern. instance (Show a) => Show (Pattern a) where @@ -82,7 +116,7 @@ instance Monad Pattern where return = pure -- Pattern a -> (a -> Pattern b) -> Pattern b -- Pattern Char -> (Char -> Pattern String) -> Pattern String - + p >>= f = unwrap (f <$> p) {-Pattern (\a -> concatMap (\((s,e), (s',e'), x) -> map (\ev -> ((s,e), (s',e'), thd' ev)) $ @@ -136,6 +170,16 @@ withResultArc f p = Pattern $ \a -> mapArcs f $ arc p a withResultTime :: (Time -> Time) -> Pattern a -> Pattern a withResultTime = withResultArc . mapArc +-- | @withEvent f p@ returns a new @Pattern@ with events mapped over +-- function @f@. +withEvent :: (Event a -> Event b) -> Pattern a -> Pattern b +withEvent f p = Pattern $ \a -> map f $ arc p a + +-- | @timedValues p@ returns a new @Pattern@ where values are turned +-- into tuples of @Arc@ and value. +timedValues :: Pattern a -> Pattern (Arc, a) +timedValues = withEvent (\(a,a',v) -> (a,a',(a,v))) + -- | @overlay@ combines two @Pattern@s into a new pattern, so that -- their events are combined over time. This is the same as the infix -- operator `<>`. @@ -206,20 +250,20 @@ scan n = cat $ map run [1 .. n] -- | @density@ returns the given pattern with density increased by the -- given @Time@ factor. Therefore @density 2 p@ will return a pattern --- that is twice as fast, and @density (1%3) p@ will return one three +-- that is twice as fast, and @density (1/3) p@ will return one three -- times as slow. density :: Time -> Pattern a -> Pattern a -density 0 p = silence -density 1 p = p density r p = withResultTime (/ r) $ withQueryTime (* r) p +density' :: Pattern Time -> Pattern a -> Pattern a +density' tp p = unwrap $ (\tv -> density tv p) <$> tp -- | @densityGap@ is similar to @density@ but maintains its cyclic -- alignment. For example, @densityGap 2 p@ would squash the events in -- pattern @p@ into the first half of each cycle (and the second -- halves would be empty). densityGap :: Time -> Pattern a -> Pattern a -densityGap 0 p = silence +densityGap 0 _ = silence densityGap r p = splitQueries $ withResultArc (\(s,e) -> (sam s + ((s - sam s)/r), (sam s + ((e - sam s)/r)))) $ Pattern (\a -> arc p $ mapArc (\t -> sam t + (min 1 (r * cyclePos t))) a) -- | @slow@ does the opposite of @density@, i.e. @slow 2 p@ will @@ -228,6 +272,8 @@ slow :: Time -> Pattern a -> Pattern a slow 0 = id slow t = density (1/t) +slow' tp p = density' (1/tp) p + -- | The @<~@ operator shifts (or rotates) a pattern to the left (or -- counter-clockwise) by the given @Time@ value. For example -- @(1%16) <~ p@ will return a pattern with all the events moved @@ -235,7 +281,7 @@ slow t = density (1/t) (<~) :: Time -> Pattern a -> Pattern a (<~) t p = withResultTime (subtract t) $ withQueryTime (+ t) p --- | The @~>@ operator does the same as @~>@ but shifts events to the +-- | The @~>@ operator does the same as @<~@ but shifts events to the -- right (or clockwise) rather than to the left. (~>) :: Time -> Pattern a -> Pattern a (~>) = (<~) . (0-) @@ -277,10 +323,12 @@ There is also `iter'`, which shifts the pattern in the opposite direction. -} iter :: Int -> Pattern a -> Pattern a -iter n p = slowcat $ map (\i -> ((fromIntegral i)%(fromIntegral n)) <~ p) [0 .. n] +iter n p = slowcat $ map (\i -> ((fromIntegral i)%(fromIntegral n)) <~ p) [0 .. (n-1)] +-- | @iter'@ is the same as @iter@, but decrements the starting +-- subdivision instead of incrementing it. iter' :: Int -> Pattern a -> Pattern a -iter' n p = slowcat $ map (\i -> ((fromIntegral i)%(fromIntegral n)) ~> p) [0 .. n] +iter' n p = slowcat $ map (\i -> ((fromIntegral i)%(fromIntegral n)) ~> p) [0 .. (n-1)] -- | @rev p@ returns @p@ with the event positions in each cycle -- reversed (or mirrored). @@ -291,9 +339,17 @@ rev p = splitQueries $ Pattern $ \a -> mapArcs mirrorArc (arc p (mirrorArc a)) -- the pattern alternates between forwards and backwards. palindrome p = append' p (rev p) --- | @when test f p@ applies the function @f@ to @p@, but in a way --- which only affects cycles where the @test@ function applied to the --- cycle number returns @True@. +{-| +Only `when` the given test function returns `True` the given pattern transformation is applied. The test function will be called with the current cycle as a number. + +@ +d1 $ when ((elem '4').show) + (striate 4) + $ sound "hh hc" +@ + +The above will only apply `striate 4` to the pattern if the current cycle number contains the number 4. So the fourth cycle will be striated and the fourteenth and so on. Expect lots of striates after cycle number 399. +-} when :: (Int -> Bool) -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a when test f p = splitQueries $ Pattern apply where apply a | test (floor $ fst a) = (arc $ f p) a @@ -310,29 +366,33 @@ playWhen test (Pattern f) = Pattern $ (filter (\e -> test (eventOnset e))) . f playFor :: Time -> Time -> Pattern a -> Pattern a playFor s e = playWhen (\t -> and [t >= s, t < e]) -{- | There is a similar function named `seqP` which allows you to define when +{- | The function @seqP@ allows you to define when a sound within a list starts and ends. The code below contains three -separate patterns in a "stack", but each has different start times +separate patterns in a `stack`, but each has different start times (zero cycles, eight cycles, and sixteen cycles, respectively). All patterns stop after 128 cycles: @ -d1 $ seqP [ - (0, 128, sound "bd bd*2"), - (8, 128, sound "hh*2 [sn cp] cp future*4"), +d1 $ seqP [ + (0, 128, sound "bd bd*2"), + (8, 128, sound "hh*2 [sn cp] cp future*4"), (16, 128, sound (samples "arpy*8" (run 16))) ] @ -} seqP :: [(Time, Time, Pattern a)] -> Pattern a -seqP = stack . (map (\(s, e, p) -> playFor s e ((sam s) ~> p))) +seqP ps = stack $ map (\(s, e, p) -> playFor s e ((sam s) ~> p)) ps -- | @every n f p@ applies the function @f@ to @p@, but only affects -- every @n@ cycles. every :: Int -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a -every 0 f p = p +every 0 _ p = p every n f p = when ((== 0) . (`mod` n)) f p +-- | @every n o f'@ is like @every n f@ with an offset of @o@ cycles +every' :: Int -> Int -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a +every' n o f = when ((== o) . (`mod` n)) f + -- | @foldEvery ns f p@ applies the function @f@ to @p@, and is applied for -- each cycle in @ns@. foldEvery :: [Int] -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a @@ -349,11 +409,12 @@ sig f = Pattern f' -- sinewave with frequency of one cycle, and amplitude from -1 to 1. sinewave :: Pattern Double sinewave = sig $ \t -> sin $ pi * 2 * (fromRational t) --- | @sine@ is a synonym for @sinewave. +-- | @sine@ is a synonym for @sinewave@. sine = sinewave -- | @sinerat@ is equivalent to @sinewave@ for @Rational@ values, -- suitable for use as @Time@ offsets. sinerat = fmap toRational sine +-- | @ratsine@ is a synonym for @sinerat@. ratsine = sinerat -- | @sinewave1@ is equivalent to @sinewave@, but with amplitude from 0 to 1. @@ -370,7 +431,7 @@ sinerat1 = fmap toRational sine1 sineAmp1 :: Double -> Pattern Double sineAmp1 offset = (+ offset) <$> sinewave1 --- | @sawwave@ is the equivalent of @sinewave@ for sawtooth waves. +-- | @sawwave@ is the equivalent of @sinewave@ for (ascending) sawtooth waves. sawwave :: Pattern Double sawwave = ((subtract 1) . (* 2)) <$> sawwave1 @@ -381,9 +442,15 @@ saw = sawwave -- suitable for use as @Time@ offsets. sawrat = fmap toRational saw +-- | @sawwave1@ is the equivalent of @sinewave1@ for (ascending) sawtooth waves. sawwave1 :: Pattern Double sawwave1 = sig $ \t -> mod' (fromRational t) 1 + +-- | @saw1@ is a synonym for @sawwave1@. saw1 = sawwave1 + +-- | @sawrat1@ is the same as @sawwave1@ but returns @Rational@ values +-- suitable for use as @Time@ offsets. sawrat1 = fmap toRational saw1 -- | @triwave@ is the equivalent of @sinewave@ for triangular waves. @@ -397,21 +464,30 @@ tri = triwave -- suitable for use as @Time@ offsets. trirat = fmap toRational tri +-- | @triwave1@ is the equivalent of @sinewave1@ for triangular waves. triwave1 :: Pattern Double triwave1 = append sawwave1 (rev sawwave1) +-- | @tri1@ is a synonym for @triwave1@. tri1 = triwave1 -trirat1 = fmap toRational tri1 --- todo - triangular waves again +-- | @trirat1@ is the same as @triwave1@ but returns @Rational@ values +-- suitable for use as @Time@ offsets. +trirat1 = fmap toRational tri1 +-- | @squarewave1@ is the equivalent of @sinewave1@ for square waves. squarewave1 :: Pattern Double squarewave1 = sig $ \t -> fromIntegral $ floor $ (mod' (fromRational t) 1) * 2 + +-- | @square1@ is a synonym for @squarewave1@. square1 = squarewave1 +-- | @squarewave@ is the equivalent of @sinewave@ for square waves. squarewave :: Pattern Double squarewave = ((subtract 1) . (* 2)) <$> squarewave1 + +-- | @square@ is a synonym for @squarewave@. square = squarewave -- | @envL@ is a @Pattern@ of continuous @Double@ values, representing @@ -480,17 +556,11 @@ the two speeds: d1 $ spread slow [2,4%3] $ sound "ho ho:2 ho:3 hc" @ --} -spread :: (a -> t -> Pattern b) -> [a] -> t -> Pattern b -spread f xs p = cat $ map (\x -> f x p) xs - -{- | `slowspread` takes a list of pattern transforms and applies them one at a time, per cycle, -then repeats. - -Example: +Note that if you pass ($) as the function to spread values over, you +can put functions as the list of values. For example: @ -d1 $ slowspread ($) [density 2, rev, slow 2, striate 3, (# speed "0.8")] +d1 $ spread ($) [density 2, rev, slow 2, striate 3, (# speed "0.8")] $ sound "[bd*2 [~ bd]] [sn future]*2 cp jvbass*4" @ @@ -504,8 +574,21 @@ Above, the pattern will have these transforms applied to it, one at a time, per After `(# speed "0.8")`, the transforms will repeat and start at `density 2` again. -} -slowspread :: (a -> t -> Pattern b) -> [a] -> t -> Pattern b -slowspread f xs p = slowcat $ map (\x -> f x p) xs +spread :: (a -> t -> Pattern b) -> [a] -> t -> Pattern b +spread f xs p = slowcat $ map (\x -> f x p) xs + +slowspread = spread + +{- | @fastspread@ works the same as @spread@, but the result is squashed into a single cycle. If you gave four values to @spread@, then the result would seem to speed up by a factor of four. Compare these two: + +d1 $ spread chop [4,64,32,16] $ sound "ho ho:2 ho:3 hc" + +d1 $ fastspread chop [4,64,32,16] $ sound "ho ho:2 ho:3 hc" + +There is also @slowspread@, which is an alias of @spread@. +-} +fastspread :: (a -> t -> Pattern b) -> [a] -> t -> Pattern b +fastspread f xs p = cat $ map (\x -> f x p) xs {- | There's a version of this function, `spread'` (pronounced "spread prime"), which takes a *pattern* of parameters, instead of a list: @@ -521,10 +604,17 @@ using `spread'` though is that you can provide polyphonic parameters, e.g.: d1 $ spread' slow "[2 4%3, 3]" $ sound "ho ho:2 ho:3 hc" @ -} -spread' :: (a -> Pattern b -> Pattern c) -> Pattern a -> Pattern b -> Pattern c -spread' f timepat pat = - Pattern $ \r -> concatMap (\(_,r', x) -> (arc (f x pat) r')) (rs r) - where rs r = arc (filterOnsetsInRange timepat) r +spread' :: Monad m => (a -> b -> m c) -> m a -> b -> m c +spread' f vpat pat = vpat >>= \v -> f v pat + +{- | `spreadChoose f xs p` is similar to `slowspread` but picks values from +`xs` at random, rather than cycling through them in order. It has a +shorter alias `spreadr`. +-} +spreadChoose :: (t -> t1 -> Pattern b) -> [t] -> t1 -> Pattern b +spreadChoose f vs p = do v <- discretise 1 (choose vs) + f v p +spreadr = spreadChoose filterValues :: (a -> Bool) -> Pattern a -> Pattern a filterValues f (Pattern x) = Pattern $ (filter (f . thd')) . x @@ -535,14 +625,17 @@ filterOnsets (Pattern f) = Pattern $ (filter (\e -> eventOnset e >= eventStart e)) . f -- Filter events which have onsets, which are within the given range +-- TODO - what about < e ?? filterStartInRange :: Pattern a -> Pattern a filterStartInRange (Pattern f) = - Pattern $ \(s,e) -> filter ((>= s) . eventOnset) $ f (s,e) + Pattern $ \(s,e) -> filter ((isIn (s,e)) . eventOnset) $ f (s,e) filterOnsetsInRange = filterOnsets . filterStartInRange -seqToRelOnsets :: Arc -> Pattern a -> [(Double, a)] -seqToRelOnsets (s, e) p = map (\((s', _), _, x) -> (fromRational $ (s'-s) / (e-s), x)) $ arc (filterOnsetsInRange p) (s, e) +-- Samples some events from a pattern, returning a list of onsets +-- (relative to the given arc), deltas (durations) and values. +seqToRelOnsetDeltas :: Arc -> Pattern a -> [(Double, Double, a)] +seqToRelOnsetDeltas (s, e) p = map (\((s', e'), _, x) -> (fromRational $ (s'-s) / (e-s), fromRational $ (e'-s) / (e-s), x)) $ arc (filterOnsetsInRange p) (s, e) segment :: Pattern a -> Pattern [a] segment p = Pattern $ \(s,e) -> filter (\(_,(s',e'),_) -> s' < e && e' > s) $ groupByTime (segment' (arc p (s,e))) @@ -564,6 +657,20 @@ groupByTime :: [Event a] -> [Event [a]] groupByTime es = map mrg $ groupBy ((==) `on` snd') $ sortBy (compare `on` snd') es where mrg es@((a, a', _):_) = (a, a', map thd' es) + +{-| Decide whether to apply one or another function depending on the result of a test function that is passed the current cycle as a number. + +@ +d1 $ ifp ((== 0).(flip mod 2)) + (striate 4) + (# coarse "24 48") $ + sound "hh hc" +@ + +This will apply `striate 4` for every _even_ cycle and aply `# coarse "24 48"` for every _odd_. + +Detail: As you can see the test function is arbitrary and does not rely on anything tidal specific. In fact it uses only plain haskell functionality, that is: it calculates the modulo of 2 of the current cycle which is either 0 (for even cycles) or 1. It then compares this value against 0 and returns the result, which is either `True` or `False`. This is what the `ifp` signature's first part signifies `(Int -> Bool)`, a function that takes a whole number and returns either `True` or `False`. +-} ifp :: (Int -> Bool) -> (Pattern a -> Pattern a) -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a ifp test f1 f2 p = splitQueries $ Pattern apply where apply a | test (floor $ fst a) = (arc $ f1 p) a @@ -620,7 +727,7 @@ irand i = (floor . (* (fromIntegral i))) <$> rand d1 $ sound (samples "xx(3,8)" (tom $ choose ["a", "e", "g", "c"])) @ -plays a melody randomly choosing one of the four notes: `"a"`, `"e"`, `"g"`, `"c"` +plays a melody randomly choosing one of the four notes \"a\", \"e\", \"g\", \"c\". -} choose :: [a] -> Pattern a choose xs = (xs !!) <$> (irand $ length xs) @@ -648,7 +755,7 @@ unDegradeBy x p = unMaybe $ (\a f -> toMaybe (f <= x) a) <$> p <*> rand toMaybe True a = Just a unMaybe = (fromJust <$>) . filterValues isJust -{- | Use `sometimesBy` to apply a given function "sometimes". For example, the +{- | Use @sometimesBy@ to apply a given function "sometimes". For example, the following code results in `density 2` being applied about 25% of the time: @ @@ -668,11 +775,27 @@ almostAlways = sometimesBy 0.9 sometimesBy :: Double -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a sometimesBy x f p = overlay (degradeBy x p) (f $ unDegradeBy x p) +-- | @sometimes@ is an alias for sometimesBy 0.5. sometimes = sometimesBy 0.5 +-- | @often@ is an alias for sometimesBy 0.75. often = sometimesBy 0.75 +-- | @rarely@ is an alias for sometimesBy 0.25. rarely = sometimesBy 0.25 +-- | @almostNever@ is an alias for sometimesBy 0.1 almostNever = sometimesBy 0.1 +-- | @almostAlways@ is an alias for sometimesBy 0.9 almostAlways = sometimesBy 0.9 +never = flip const +always = id + +{- | @someCyclesBy@ is a cycle-by-cycle version of @sometimesBy@. It has a +`someCycles = someCyclesBy 0.5` alias -} +someCyclesBy x = when (test x) + where test x c = (timeToRand $ fromIntegral c) < x + +somecyclesBy = someCyclesBy + +someCycles = someCyclesBy 0.5 {- | `degrade` randomly removes events from a pattern 50% of the time: @@ -704,10 +827,10 @@ degrade = degradeBy 0.5 wedge :: Time -> Pattern a -> Pattern a -> Pattern a wedge t p p' = overlay (densityGap (1/t) p) (t ~> densityGap (1/(1-t)) p') -{- | `whenmod` has a similar form and behavior to `every`, but requires an +{- | @whenmod@ has a similar form and behavior to `every`, but requires an additional number. Applies the function to the pattern, when the remainder of the current loop number divided by the first parameter, -is less than the second parameter. +is greater or equal than the second parameter. For example the following makes every other block of four loops twice as dense: @@ -743,7 +866,7 @@ superimpose f p = stack [p, f p] splitQueries :: Pattern a -> Pattern a splitQueries p = Pattern $ \a -> concatMap (arc p) $ arcCycles a -{- | Truncates a pattern so that only a fraction of the pattern is played. +{- | Truncates a pattern so that only a fraction of the pattern is played. The following example plays only the first three quarters of the pattern: @ @@ -751,12 +874,9 @@ d1 $ trunc 0.75 $ sound "bd sn*2 cp hh*4 arpy bd*2 cp bd*2" @ -} trunc :: Time -> Pattern a -> Pattern a -trunc t p = slow t $ splitQueries $ p' - where p' = Pattern $ \a -> mapArcs (stretch . trunc') $ arc p (trunc' a) - trunc' (s,e) = (min s ((sam s) + t), min e ((sam s) + t)) - stretch (s,e) = (sam s + ((s - sam s) / t), sam s + ((e - sam s) / t)) +trunc t = compress (0,t) . zoom (0,t) -{- | Plays a portion of a pattern, specified by a beginning and end arc of time. +{- | Plays a portion of a pattern, specified by a beginning and end arc of time. The new resulting pattern is played over the time period of the original pattern: @ @@ -803,6 +923,50 @@ within (s,e) f p = stack [sliceArc (0,s) p, revArc a = within a rev +{- | You can use the @e@ function to apply a Euclidean algorithm over a +complex pattern, although the structure of that pattern will be lost: + +@ +d1 $ e 3 8 $ sound "bd*2 [sn cp]" +@ + +In the above, three sounds are picked from the pattern on the right according +to the structure given by the `e 3 8`. It ends up picking two `bd` sounds, a +`cp` and missing the `sn` entirely. + +These types of sequences use "Bjorklund's algorithm", which wasn't made for +music but for an application in nuclear physics, which is exciting. More +exciting still is that it is very similar in structure to the one of the first +known algorithms written in Euclid's book of elements in 300 BC. You can read +more about this in the paper +[The Euclidean Algorithm Generates Traditional Musical Rhythms](http://cgm.cs.mcgill.ca/~godfried/publications/banff.pdf) +by Toussaint. Some examples from this paper are included below, +including rotation in some cases. + +@ +- (2,5) : A thirteenth century Persian rhythm called Khafif-e-ramal. +- (3,4) : The archetypal pattern of the Cumbia from Colombia, as well as a Calypso rhythm from Trinidad. +- (3,5,2) : Another thirteenth century Persian rhythm by the name of Khafif-e-ramal, as well as a Rumanian folk-dance rhythm. +- (3,7) : A Ruchenitza rhythm used in a Bulgarian folk-dance. +- (3,8) : The Cuban tresillo pattern. +- (4,7) : Another Ruchenitza Bulgarian folk-dance rhythm. +- (4,9) : The Aksak rhythm of Turkey. +- (4,11) : The metric pattern used by Frank Zappa in his piece titled Outside Now. +- (5,6) : Yields the York-Samai pattern, a popular Arab rhythm. +- (5,7) : The Nawakhat pattern, another popular Arab rhythm. +- (5,8) : The Cuban cinquillo pattern. +- (5,9) : A popular Arab rhythm called Agsag-Samai. +- (5,11) : The metric pattern used by Moussorgsky in Pictures at an Exhibition. +- (5,12) : The Venda clapping pattern of a South African children’s song. +- (5,16) : The Bossa-Nova rhythm necklace of Brazil. +- (7,8) : A typical rhythm played on the Bendir (frame drum). +- (7,12) : A common West African bell pattern. +- (7,16,14) : A Samba rhythm necklace from Brazil. +- (9,16) : A rhythm necklace used in the Central African Republic. +- (11,24,14) : A rhythm necklace of the Aka Pygmies of Central Africa. +- (13,24,5) : Another rhythm necklace of the Aka Pygmies of the upper Sangha. +@ +-} e :: Int -> Int -> Pattern a -> Pattern a e n k p = (flip const) <$> (filterValues (== True) $ listToPat $ bjorklund (n,k)) <*> p @@ -814,7 +978,7 @@ index :: Real b => b -> Pattern b -> Pattern c -> Pattern c index sz indexpat pat = spread' (zoom' $ toRational sz) (toRational . (*(1-sz)) <$> indexpat) pat where zoom' sz start = zoom (start, start+sz) --- | @prr rot (blen, vlen) beatPattern valuePattern@: pattern rotate/replace. +-- | @prrw f rot (blen, vlen) beatPattern valuePattern@: pattern rotate/replace. prrw :: (a -> b -> c) -> Int -> (Time, Time) -> Pattern a -> Pattern b -> Pattern c prrw f rot (blen, vlen) beatPattern valuePattern = let @@ -829,7 +993,7 @@ prrw f rot (blen, vlen) beatPattern valuePattern = (drop (rot `mod` length values) $ cycle values) -- | @prr rot (blen, vlen) beatPattern valuePattern@: pattern rotate/replace. -prr :: Int -> (Time, Time) -> Pattern a -> Pattern b -> Pattern b +prr :: Int -> (Time, Time) -> Pattern String -> Pattern b -> Pattern b prr = prrw $ flip const {-| @@ -839,6 +1003,7 @@ of @values@. Other ways of saying this are: * @values@ quantized to @beats@. Examples: + @ d1 $ sound $ preplace (1,1) "x [~ x] x x" "bd sn" d1 $ sound $ preplace (1,1) "x(3,8)" "bd sn" @@ -848,20 +1013,21 @@ d1 $ sound "[jvbass jvbass:5]*3" |+| (shape $ "1 1 1 1 1" <~> "0.2 0.9") It is assumed the pattern fits into a single cycle. This works well with pattern literals, but not always with patterns defined elsewhere. In those cases -use @prr@ and provide desired pattern lengths: +use @preplace@ and provide desired pattern lengths: @ let p = slow 2 $ "x x x" -d1 $ sound $ prr 0 (2,1) p "bd sn" +d1 $ sound $ preplace (2,1) p "bd sn" @ -} -preplace :: (Time, Time) -> Pattern a -> Pattern b -> Pattern b +preplace :: (Time, Time) -> Pattern String -> Pattern b -> Pattern b preplace = preplaceWith $ flip const +-- | @prep@ is an alias for preplace. prep = preplace -preplace1 :: Pattern a -> Pattern b -> Pattern b -preplace1 = prr 0 (1, 1) +preplace1 :: Pattern String -> Pattern b -> Pattern b +preplace1 = preplace (1, 1) preplaceWith :: (a -> b -> c) -> (Time, Time) -> Pattern a -> Pattern b -> Pattern c preplaceWith f (blen, plen) = prrw f 0 (blen, plen) @@ -873,14 +1039,14 @@ preplaceWith1 f = prrw f 0 (1, 1) prw1 = preplaceWith1 -(<~>) :: Pattern a -> Pattern b -> Pattern b +(<~>) :: Pattern String -> Pattern b -> Pattern b (<~>) = preplace (1, 1) -- | @protate len rot p@ rotates pattern @p@ by @rot@ beats to the left. -- @len@: length of the pattern, in cycles. -- Example: @d1 $ every 4 (protate 2 (-1)) $ slow 2 $ sound "bd hh hh hh"@ protate :: Time -> Int -> Pattern a -> Pattern a -protate len rot p = prr rot (len, len) p p +protate len rot p = prrw (flip const) rot (len, len) p p prot = protate prot1 = protate 1 @@ -897,6 +1063,7 @@ d1 $ sound "sn ~ hh bd" (<<~) :: Int -> Pattern a -> Pattern a (<<~) = protate 1 +-- | @~>>@ is like @<<~@ but for shifting to the right. (~>>) :: Int -> Pattern a -> Pattern a (~>>) = (<<~) . (0-) @@ -913,9 +1080,9 @@ discretise n p = density n $ (atom (id)) <*> p -- | @randcat ps@: does a @slowcat@ on the list of patterns @ps@ but -- randomises the order in which they are played. randcat :: [Pattern a] -> Pattern a -randcat ps = spread' (<~) (discretise 1 $ ((%1) . fromIntegral) <$> irand (length ps)) (slowcat ps) +randcat ps = spread' (<~) (discretise 1 $ ((%1) . fromIntegral) <$> irand (length ps - 1)) (slowcat ps) --- | @fromNote p@: converts a pattern of human-readable pitch names +-- @fromNote p@: converts a pattern of human-readable pitch names -- into pitch numbers. For example, @"cs2"@ will be parsed as C Sharp -- in the 2nd octave with the result of @11@, and @"b-3"@ as -- @-25@. Pitches can be decorated using: @@ -947,7 +1114,7 @@ toMIDI p = fromJust <$> (filterValues (isJust) (noteLookup <$> p)) sym x = lookup (init (tail x)) [("s",1),("f",-1),("n",0),("ss",2),("ff",-2)] -} --- | @tom p@: Alias for @toMIDI@. +-- @tom p@: Alias for @toMIDI@. -- tom = toMIDI @@ -977,17 +1144,24 @@ permstep steps things p = unwrap $ (\n -> listToPat $ concatMap (\x -> replicate struct :: Pattern String -> Pattern a -> Pattern a struct ps pv = (flip const) <$> 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 s p = filterStartInRange $ Pattern $ f + where f a = concatMap (\a' -> arc (compressTo a' p) a') $ (map fst' $ arc s a) + compressTo (s,e) p = compress (cyclePos s, e-(sam s)) p + -- Lindenmayer patterns, these go well with the step sequencer -- general rule parser (strings map to strings) parseLMRule :: String -> [(String,String)] parseLMRule s = map (splitOn ':') (commaSplit s) - where splitOn sep str = splitAt (fromJust $ elemIndex sep str) + where splitOn sep str = splitAt (fromJust $ elemIndex sep str) $ filter (/= sep) str commaSplit s = map T.unpack $ T.splitOn (T.pack ",") $ T.pack s -- specific parser for step sequencer (chars map to string) --- ruleset in form "a:b,b:ab" -parseLMRule' :: String -> [(Char, String)] +-- ruleset in form "a:b,b:ab" +parseLMRule' :: String -> [(Char, String)] parseLMRule' str = map fixer $ parseLMRule str where fixer (c,r) = (head c, r) @@ -995,14 +1169,12 @@ parseLMRule' str = map fixer $ parseLMRule str for example: -~~~~{haskell} - +@ lindenmayer 1 "a:b,b:ab" "ab" -> "bab" - -~~~~ +@ -} lindenmayer :: Int -> String -> String -> String -lindenmayer n r [] = [] +lindenmayer _ _ [] = [] lindenmayer 1 r (c:cs) = (fromMaybe [c] $ lookup c $ parseLMRule' r) ++ (lindenmayer 1 r cs) lindenmayer n r s = iterate (lindenmayer 1 r) s !! n @@ -1012,10 +1184,38 @@ unwrap' :: Pattern (Pattern a) -> Pattern a unwrap' pp = Pattern $ \a -> arc (stack $ map scalep (arc pp a)) a where scalep ev = compress (fst' ev) $ thd' ev --- | removes events from pattern b that don't start during an event from pattern a +{-| +Removes events from second pattern that don't start during an event from first. + +Consider this, kind of messy rhythm without any rests. + +@ +d1 $ sound (slowcat ["sn*8", "[cp*4 bd*4, hc*5]"]) # n (run 8) +@ + +If we apply a mask to it + +@ +d1 $ s (mask ("1 1 1 ~ 1 1 ~ 1" :: Pattern Bool) + (slowcat ["sn*8", "[cp*4 bd*4, bass*5]"] )) + # n (run 8) +@ + +Due to the use of `slowcat` here, the same mask is first applied to `"sn*8"` and in the next cycle to `"[cp*4 bd*4, hc*5]". + +You could achieve the same effect by adding rests within the `slowcat` patterns, but mask allows you to do this more easily. It kind of keeps the rhythmic structure and you can change the used samples independently, e.g. + +@ +d1 $ s (mask ("1 ~ 1 ~ 1 1 ~ 1" :: Pattern Bool) + (slowcat ["can*8", "[cp*4 sn*4, jvbass*16]"] )) + # n (run 8) +@ + +Detail: It is currently needed to explicitly _tell_ Tidal that the mask itself is a `Pattern Bool` as it cannot infer this by itself, otherwise it will complain as it does not know how to interpret your input. +-} mask :: Pattern a -> Pattern b -> Pattern b mask pa pb = Pattern $ \a -> concat [filterOns (subArc a $ eventArc i) (arc pb a) | i <- arc pa a] - where filterOns Nothing es = [] + where filterOns Nothing _ = [] filterOns (Just arc) es = filter (onsetIn arc) es enclosingArc :: [Arc] -> Arc @@ -1023,7 +1223,7 @@ enclosingArc [] = (0,1) enclosingArc as = (minimum (map fst as), maximum (map snd as)) stretch :: Pattern a -> Pattern a -stretch p = splitQueries $ Pattern $ \a@(s,e) -> arc +stretch p = splitQueries $ Pattern $ \a@(s,_e) -> arc (zoom (enclosingArc $ map eventArc $ arc p (sam s,nextSam s)) p) a @@ -1045,12 +1245,12 @@ which uses `chop` to break a single sample into individual pieces, which `fit'` -} fit' cyc n from to p = unwrap' $ fit n (mapMasks n from' p') to - where mapMasks n from p = [stretch $ mask (filterValues (== i) from) p + where mapMasks n from p = [stretch $ mask (filterValues (== i) from) p | i <- [0..n-1]] p' = density cyc $ p from' = density cyc $ from -{- `runWith n f p` treats the given pattern `p` as having `n` sections, and applies the function `f` to one of those sections per cycle, running from left to right. +{-| @runWith n f p@ treats the given pattern @p@ as having @n@ sections, and applies the function @f@ to one of those sections per cycle, running from left to right. @ d1 $ runWith 4 (density 4) $ sound "cp sn arpy [mt lt]" @@ -1061,8 +1261,105 @@ runWith n f p = do i <- slow (toRational n) $ run (fromIntegral n) within (i%(fromIntegral n),(i+)1%(fromIntegral n)) f p -{- `runWith'` works much the same as `runWith`, but runs from right to left. - -} +{-| @runWith'@ works much the same as `runWith`, but runs from right to left. +-} runWith' :: Integral a => a -> (Pattern b -> Pattern b) -> Pattern b -> Pattern b runWith' n f p = do i <- slow (toRational n) $ rev $ run (fromIntegral n) within (i%(fromIntegral n),(i+)1%(fromIntegral n)) f p + +inside :: Time -> (Pattern a1 -> Pattern a) -> Pattern a1 -> Pattern a +inside n f p = density n $ f (slow n p) + +outside :: Time -> (Pattern a1 -> Pattern a) -> Pattern a1 -> Pattern a +outside n = inside (1/n) + +loopFirst p = splitQueries $ Pattern f + where f a@(s,e) = mapSnds' plus $ mapFsts' plus $ arc p (minus a) + where minus = mapArc (subtract (sam s)) + plus = mapArc (+ (sam s)) + +timeLoop :: Time -> Pattern a -> Pattern a +timeLoop n = outside n loopFirst + +seqPLoop :: [(Time, Time, Pattern a)] -> Pattern a +seqPLoop ps = timeLoop (maxT - minT) $ minT <~ seqP ps + where minT = minimum $ map fst' ps + maxT = maximum $ map snd' ps + +{- | @toScale@ lets you turn a pattern of notes within a scale (expressed as a +list) to note numbers. For example `toScale [0, 4, 7] "0 1 2 3"` will turn +into the pattern `"0 4 7 12"`. It assumes your scale fits within an octave, +to change this use `toScale' size`. Example: +`toscale' 24 [0,4,7,10,14,17] (run 8)` turns into `"0 4 7 10 14 17 24 28"` +-} +toScale'::Int -> [Int] -> Pattern Int -> Pattern Int +toScale' o s p = (+) <$> fmap (s!!) notep <*> fmap (o*) octp + where notep = fmap (`mod` (length s)) p + octp = fmap (`div` (length s)) p + +toScale::[Int] -> Pattern Int -> Pattern Int +toScale = toScale' 12 + +{- | `swingBy x n` divides a cycle into `n` slices and delays the notes in +the second half of each slice by `x` fraction of a slice . @swing@ is an alias +for `swingBy (1%3)` +-} +swingBy::Time -> Time -> Pattern a -> Pattern a +swingBy x n = inside n (within (0.5,1) (x ~>)) + +swing = swingBy (1%3) + +{- | `cycleChoose` is like `choose` but only picks a new item from the list +once each cycle -} +cycleChoose::[a] -> Pattern a +cycleChoose xs = Pattern $ \(s,e) -> [((s,e),(s,e), xs!!(floor $ (dlen xs)*(ctrand s) ))] + where dlen xs = fromIntegral $ length xs + ctrand s = timeToRand $ fromIntegral $ floor $ sam s + +{- | `shuffle n p` evenly divides one cycle of the pattern `p` into `n` parts, +and returns a random permutation of the parts each cycle. For example, +`shuffle 3 "a b c"` could return `"a b c"`, `"a c b"`, `"b a c"`, `"b c a"`, +`"c a b"`, or `"c b a"`. But it will **never** return `"a a a"`, because that +is not a permutation of the parts. +-} +shuffle::Int -> Pattern a -> Pattern a +shuffle n = fit' 1 n (run n) (unwrap $ cycleChoose $ map listToPat $ + permutations [0..n-1]) + +{- | `scramble n p` is like `shuffle` but randomly selects from the parts +of `p` instead of making permutations. +For example, `scramble 3 "a b c"` will randomly select 3 parts from +`"a"` `"b"` and `"c"`, possibly repeating a single part. +-} +scramble::Int -> Pattern a -> Pattern a +scramble n = fit' 1 n (run n) (density (fromIntegral n) $ + liftA2 (+) (pure 0) $ irand n) + +ur :: Time -> Pattern String -> [Pattern a] -> Pattern a +ur t outer_p ps = slow t $ unwrap $ adjust <$> (timedValues $ (getPat . split) <$> outer_p) + where split s = wordsBy (==':') s + getPat (n:xs) = (ps' !!! read n, transform xs) + ps' = map (density t) ps + adjust (a, (p, f)) = f a p + transform (x:_) a = transform' x a + transform _ _ = id + transform' "in" (s,e) p = twiddle (fadeIn) (s,e) p + transform' "out" (s,e) p = twiddle (fadeOut) (s,e) p + transform' _ _ p = p + twiddle f (s,e) p = s ~> (f (e-s) p) + +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 s = wordsBy (==':') s + getPat (s:xs) = (match s, transform xs) + match s = fromMaybe silence $ lookup s ps' + ps' = map (fmap (density t)) ps + adjust (a, (p, f)) = f a p + transform (x:_) a = transform' x a + transform _ _ = id + transform' str (s,e) p = s ~> (inside (1/(e-s)) (matchF str) p) + matchF str = fromMaybe id $ lookup str fs + twiddle f (s,e) p = s ~> (f (e-s) p) + +inhabit :: [(String, Pattern a)] -> Pattern String -> Pattern a +inhabit ps p = unwrap' $ (\s -> fromMaybe silence $ lookup s ps) <$> p diff --git a/Sound/Tidal/Scales.hs b/Sound/Tidal/Scales.hs new file mode 100644 index 000000000..06e1ec2dc --- /dev/null +++ b/Sound/Tidal/Scales.hs @@ -0,0 +1,149 @@ +module Sound.Tidal.Scales where + +-- five notes scales +minPent :: [Int] +minPent = [0,3,5,7,10] +majPent :: [Int] +majPent = [0,2,4,7,9] + +-- another mode of major pentatonic +ritusen :: [Int] +ritusen = [0,2,5,7,9] + +-- another mode of major pentatonic +egyptian :: [Int] +egyptian = [0,2,5,7,10] + +-- +kumai :: [Int] +kumai = [0,2,3,7,9] +hirajoshi :: [Int] +hirajoshi = [0,2,3,7,8] +iwato :: [Int] +iwato = [0,1,5,6,10] +chinese :: [Int] +chinese = [0,4,6,7,11] +indian :: [Int] +indian = [0,4,5,7,10] +pelog :: [Int] +pelog = [0,1,3,7,8] + +-- +prometheus :: [Int] +prometheus = [0,2,4,6,11] +scriabin :: [Int] +scriabin = [0,1,4,7,9] + +-- han chinese pentatonic scales +gong :: [Int] +gong = [0,2,4,7,9] +shang :: [Int] +shang = [0,2,5,7,10] +jiao :: [Int] +jiao = [0,3,5,8,10] +zhi :: [Int] +zhi = [0,2,5,7,9] +yu :: [Int] +yu = [0,3,5,7,10] + +-- 6 note scales +whole :: [Int] +whole = [0,2,4,6,8,10] +augmented :: [Int] +augmented = [0,3,4,7,8,11] +augmented2 :: [Int] +augmented2 = [0,1,4,5,8,9] + +-- hexatonic modes with no tritone +hexMajor7 :: [Int] +hexMajor7 = [0,2,4,7,9,11] +hexDorian :: [Int] +hexDorian = [0,2,3,5,7,10] +hexPhrygian :: [Int] +hexPhrygian = [0,1,3,5,8,10] +hexSus :: [Int] +hexSus = [0,2,5,7,9,10] +hexMajor6 :: [Int] +hexMajor6 = [0,2,4,5,7,9] +hexAeolian :: [Int] +hexAeolian = [0,3,5,7,8,10] + +-- 7 note scales +major :: [Int] +major = [0,2,4,5,7,9,11] +ionian :: [Int] +ionian = [0,2,4,5,7,9,11] +dorian :: [Int] +dorian = [0,2,3,5,7,9,10] +phrygian :: [Int] +phrygian = [0,1,3,5,7,8,10] +lydian :: [Int] +lydian = [0,2,4,6,7,9,11] +mixolydian :: [Int] +mixolydian = [0,2,4,5,7,9,10] +aeolian :: [Int] +aeolian = [0,2,3,5,7,8,10] +minor :: [Int] +minor = [0,2,3,5,7,8,10] +locrian :: [Int] +locrian = [0,1,3,5,6,8,10] +harmonicMinor :: [Int] +harmonicMinor = [0,2,3,5,7,8,11] +harmonicMajor :: [Int] +harmonicMajor = [0,2,4,5,7,8,11] +melodicMinor :: [Int] +melodicMinor = [0,2,3,5,7,9,11] +melodicMinorDesc :: [Int] +melodicMinorDesc = [0,2,3,5,7,8,10] +melodicMajor :: [Int] +melodicMajor = [0,2,4,5,7,8,10] +bartok :: [Int] +bartok = [0,2,4,5,7,8,10] +hindu :: [Int] +hindu = [0,2,4,5,7,8,10] + +-- raga modes +todi :: [Int] +todi = [0,1,3,6,7,8,11] +purvi :: [Int] +purvi = [0,1,4,6,7,8,11] +marva :: [Int] +marva = [0,1,4,6,7,9,11] +bhairav :: [Int] +bhairav = [0,1,4,5,7,8,11] +ahirbhairav :: [Int] +ahirbhairav = [0,1,4,5,7,9,10] + +-- +superLocrian :: [Int] +superLocrian = [0,1,3,4,6,8,10] +romanianMinor :: [Int] +romanianMinor = [0,2,3,6,7,9,10] +hungarianMinor :: [Int] +hungarianMinor = [0,2,3,6,7,8,11] +neapolitanMinor :: [Int] +neapolitanMinor = [0,1,3,5,7,8,11] +enigmatic :: [Int] +enigmatic = [0,1,4,6,8,10,11] +spanish :: [Int] +spanish = [0,1,4,5,7,8,10] + +-- modes of whole tones with added note -> +leadingWhole :: [Int] +leadingWhole = [0,2,4,6,8,10,11] +lydianMinor :: [Int] +lydianMinor = [0,2,4,6,7,8,10] +neapolitanMajor :: [Int] +neapolitanMajor = [0,1,3,5,7,9,11] +locrianMajor :: [Int] +locrianMajor = [0,2,4,5,6,8,10] + +-- 8 note scales +diminished :: [Int] +diminished = [0,1,3,4,6,7,9,10] +diminished2 :: [Int] +diminished2 = [0,2,3,5,6,8,9,11] + +-- 12 note scales +chromatic :: [Int] +chromatic = [0..11] diff --git a/Sound/Tidal/Strategies.hs b/Sound/Tidal/Strategies.hs index e5a0335d0..b0c3c4ad0 100644 --- a/Sound/Tidal/Strategies.hs +++ b/Sound/Tidal/Strategies.hs @@ -1,4 +1,4 @@ -{-# OPTIONS_GHC -XNoMonomorphismRestriction #-} +{-# OPTIONS_GHC -XNoMonomorphismRestriction -XOverloadedStrings #-} module Sound.Tidal.Strategies where @@ -16,9 +16,12 @@ import Sound.Tidal.Time import Sound.Tidal.Utils import Sound.Tidal.Params import Sound.Tidal.Parse +import Data.List (transpose) +stutter :: Integral i => i -> Time -> Pattern a -> Pattern a stutter n t p = stack $ map (\i -> (t * (fromIntegral i)) ~> p) [0 .. (n-1)] +echo, triple, quad, double :: Time -> Pattern a -> Pattern a echo = stutter 2 triple = stutter 3 quad = stutter 4 @@ -46,6 +49,9 @@ juxcut f p = stack [p # pan (pure 0) # cut (pure (-1)), f $ p # pan (pure 1) # cut (pure (-2)) ] +juxcut' fs p = stack $ map (\n -> ((fs !! n) p |+| cut (pure $ 1-n)) # pan (pure $ fromIntegral n / fromIntegral l)) [0 .. l-1] + where l = length fs + {- | In addition to `jux`, `jux'` allows using a list of pattern transform. resulting patterns from each transformation will be spread via pan from left to right. For example: @@ -173,6 +179,7 @@ stripe (stripeS, stripeE) p = slow t $ Pattern $ \a -> concatMap f $ arcCycles a stretch (s,e) = (sam s + ((s - sam s) / t), sam s + ((e - sam s) / t)) -} +sawwave4, sinewave4, rand4 :: Pattern Double sawwave4 = ((*4) <$> sawwave1) sinewave4 = ((*4) <$> sinewave1) rand4 = ((*4) <$> rand) @@ -188,9 +195,6 @@ cross f p p' = Pattern $ \t -> concat [filter flt $ arc p t, ] where flt = f . cyclePos . fst . fst -} -inside n f p = density n $ f (slow n p) - - {- | `scale` will take a pattern which goes from 0 to 1 (like `sine1`), and scale it to a different range - between the first and second arguments. In the below example, `scale 1 1.5` shifts the range of `sine1` from 0 - 1 to 1 - 1.5. @ @@ -201,6 +205,11 @@ d1 $ jux (iter 4) $ sound "arpy arpy:2*2" scale :: (Functor f, Num b) => b -> b -> f b -> f b scale from to p = ((+ from) . (* (to-from))) <$> p +{- | `scalex` is an exponential version of `scale`, good for using with +frequencies. Do *not* use negative numbers or zero as arguments! -} +scalex :: (Functor f, Floating b) => b -> b -> f b -> f b +scalex from to p = exp <$> scale (log from) (log to) p + {- | `chop` granualizes every sample in place as it is played, turning a pattern of samples into a pattern of sample parts. Use an integer value to specify how many granules each sample is chopped into: @ @@ -220,9 +229,11 @@ d1 $ chop 256 $ sound "bd*4 [sn cp] [hh future]*2 [cp feel]" chop :: Int -> ParamPattern -> ParamPattern chop n p = Pattern $ \queryA -> concatMap (f queryA) $ arcCycles queryA where f queryA a = concatMap (chopEvent queryA) (arc p a) - chopEvent (queryS, queryE) (a,a',v) = map (newEvent v) $ filter (\(_, (s,e)) -> not $ or [e < queryS, s >= queryE]) (enumerate $ chopArc a n) + chopEvent (queryS, queryE) (a,_a',v) = map (newEvent v) $ filter (\(_, (s,e)) -> not $ or [e < queryS, s >= queryE]) (enumerate $ chopArc a n) newEvent :: ParamMap -> (Int, Arc) -> Event ParamMap - newEvent v (i, a) = (a,a,Map.insert (param dirt "end") (Just $ VF ((fromIntegral $ i+1)/(fromIntegral n))) $ Map.insert (param dirt "begin") (Just $ VF ((fromIntegral i)/(fromIntegral n))) v) + newEvent v (i, a) = (a,a,Map.insert (param dirt "end") (VF ((fromIntegral $ i+1)/(fromIntegral n))) $ Map.insert (param dirt "begin") (VF ((fromIntegral i)/(fromIntegral n))) v) + +chop' tp p = unwrap $ (\tv -> chop tv p) <$> tp {- | `gap` is similar to `chop` in that it granualizes every sample in place as it is played, but every other grain is silent. Use an integer value to specify how many granules @@ -235,10 +246,10 @@ d1 $ gap 16 $ sound "[jvbass drum:4]" gap :: Int -> ParamPattern -> ParamPattern gap n p = Pattern $ \queryA -> concatMap (f queryA) $ arcCycles queryA where f queryA a = concatMap (chopEvent queryA) (arc p a) - chopEvent (queryS, queryE) (a,a',v) = map (newEvent v) $ filter (\(_, (s,e)) -> not $ or [e < queryS, s >= queryE]) (enumerate $ everyOther $ chopArc a n) + chopEvent (queryS, queryE) (a,_a',v) = map (newEvent v) $ filter (\(_, (s,e)) -> not $ or [e < queryS, s >= queryE]) (enumerate $ everyOther $ chopArc a n) newEvent :: ParamMap -> (Int, Arc) -> Event ParamMap - newEvent v (i, a) = (a,a,Map.insert (param dirt "end") (Just $ VF ((fromIntegral $ i+1)/(fromIntegral n))) $ Map.insert (param dirt "begin") (Just $ VF ((fromIntegral i)/(fromIntegral n))) v) - everyOther (x:(y:xs)) = x:(everyOther xs) + newEvent v (i, a) = (a,a,Map.insert (param dirt "end") (VF ((fromIntegral $ i+1)/(fromIntegral n))) $ Map.insert (param dirt "begin") (VF ((fromIntegral i)/(fromIntegral n))) v) + everyOther (x:_:xs) = x:everyOther xs everyOther xs = xs chopArc :: Arc -> Int -> [Arc] @@ -367,3 +378,21 @@ d1 $ juxBy 0.6 (|*| speed "2") $ slowspread (loopAt) [4,6,2,3] $ chop 12 $ sound -} loopAt :: Time -> ParamPattern -> ParamPattern loopAt n p = slow n p |*| speed (pure $ fromRational $ 1/n) # unit (pure "c") + + +{- | + tabby - A more literal weaving than the `weave` function, give number + of 'threads' per cycle and two patterns, and this function will weave them + together using a plain (aka 'tabby') weave, with a simple over/under structure + -} +tabby n p p' = stack [maskedWarp n p, + maskedWeft n p' + ] + where + weft n = concatMap (\x -> [[0..n-1],(reverse [0..n-1])]) [0 .. (n `div` 2) - 1] + warp = transpose . weft + thread xs n p = slow (n%1) $ cat $ map (\i -> zoom (i%n,(i+1)%n) p) (concat xs) + weftP n p = thread (weft n) n p + warpP n p = thread (warp n) n p + maskedWeft n p = Sound.Tidal.Pattern.mask (every 2 rev $ density ((n)%2) "~ 1" :: Pattern Int) $ weftP n p + maskedWarp n p = mask (every 2 rev $ density ((n)%2) "1 ~" :: Pattern Int) $ warpP n p diff --git a/Sound/Tidal/Stream.hs b/Sound/Tidal/Stream.hs index cfacb0daa..915e38e29 100644 --- a/Sound/Tidal/Stream.hs +++ b/Sound/Tidal/Stream.hs @@ -17,9 +17,9 @@ import Sound.Tidal.Tempo (Tempo, logicalTime, clocked,clockedTick,cps) import Sound.Tidal.Utils import qualified Sound.Tidal.Time as T -import qualified Data.Map as Map +import qualified Data.Map.Strict as Map -type ToMessageFunc = Shape -> Tempo -> Int -> (Double, ParamMap) -> Maybe (IO ()) +type ToMessageFunc = Shape -> Tempo -> Int -> (Double, Double, ParamMap) -> Maybe (IO ()) data Backend a = Backend { toMessage :: ToMessageFunc, @@ -47,17 +47,16 @@ data Shape = Shape {params :: [Param], data Value = VS { svalue :: String } | VF { fvalue :: Double } | VI { ivalue :: Int } deriving (Show,Eq,Ord,Typeable) -type ParamMap = Map.Map Param (Maybe Value) +type ParamMap = Map.Map Param Value type ParamPattern = Pattern ParamMap ticksPerCycle = 8 -defaultValue :: Param -> Maybe Value -defaultValue (S _ (Just x)) = Just $ VS x -defaultValue (I _ (Just x)) = Just $ VI x -defaultValue (F _ (Just x)) = Just $ VF x -defaultValue _ = Nothing +defaultValue :: Param -> Value +defaultValue (S _ (Just x)) = VS x +defaultValue (I _ (Just x)) = VI x +defaultValue (F _ (Just x)) = VF x hasDefault :: Param -> Bool hasDefault (S _ Nothing) = False @@ -76,13 +75,14 @@ required :: Shape -> [Param] required = filter (not . hasDefault) . params hasRequired :: Shape -> ParamMap -> Bool -hasRequired s m = isSubset (required s) (Map.keys (Map.filter (\x -> x /= Nothing) m)) +hasRequired s m = isSubset (required s) (Map.keys m) isSubset :: (Eq a) => [a] -> [a] -> Bool isSubset xs ys = all (\x -> elem x ys) xs -doAt t action = do forkIO $ do now <- getCurrentTime +doAt t action = do _ <- forkIO $ do + now <- getCurrentTime let nowf = realToFrac $ utcTimeToPOSIXSeconds now threadDelay $ floor $ (t - nowf) * 1000000 action @@ -136,7 +136,7 @@ onTick backend shape patternM change ticks b = (ticks' + 1) % ticksPerCycle messages = mapMaybe (toMessage backend shape change ticks) - (seqToRelOnsets (a, b) p) + (seqToRelOnsetDeltas (a, b) p) E.catch (sequence_ messages) (\msg -> putStrLn $ "oops " ++ show (msg :: E.SomeException)) flush backend shape change ticks return () @@ -151,7 +151,7 @@ onTick' backend shape patternsM change ticks b = (ticks' + 1) % ticksPerCycle messages = mapMaybe (toM shape change ticks) - (seqToRelOnsets (a, b) $ fst ps) + (seqToRelOnsetDeltas (a, b) $ fst ps) E.catch (sequence_ messages) (\msg -> putStrLn $ "oops " ++ show (msg :: E.SomeException)) flush backend shape change ticks return () @@ -159,7 +159,7 @@ onTick' backend shape patternsM change ticks make :: (a -> Value) -> Shape -> String -> Pattern a -> ParamPattern make toValue s nm p = fmap (\x -> Map.singleton nParam (defaultV x)) p where nParam = param s nm - defaultV a = Just $ toValue a + defaultV a = toValue a --defaultV Nothing = defaultValue nParam makeS = make VS @@ -180,6 +180,7 @@ infixl 1 |=| (|=|) :: ParamPattern -> ParamPattern -> ParamPattern (|=|) = merge +infixl 1 # (#) = (|=|) mergeWith op x y = (Map.unionWithKey op) <$> x <*> y @@ -190,17 +191,16 @@ mergeWith -> f (Map.Map k a) -> f (Map.Map k a) -> f (Map.Map k a) mergeNumWith intOp floatOp = mergeWith f - where f (F _ _) (Just (VF a)) (Just (VF b)) = Just (VF $ floatOp a b) - f (I _ _) (Just (VI a)) (Just (VI b)) = Just (VI $ intOp a b) + where f (F _ _) (VF a) (VF b) = VF $ floatOp a b + f (I _ _) (VI a) (VI b) = VI $ intOp a b f _ _ b = b mergePlus = mergeWith f - where f (F _ _) (Just (VF a)) (Just (VF b)) = Just (VF $ a + b) - f (I _ _) (Just (VI a)) (Just (VI b)) = Just (VI $ a + b) - f (S _ _) (Just (VS a)) (Just (VS b)) = Just (VS $ a ++ b) + where f (F _ _) (VF a) (VF b) = VF $ a + b + f (I _ _) (VI a) (VI b) = VI $ a + b + f (S _ _) (VS a) (VS b) = VS $ a ++ b f _ _ b = b - infixl 1 |*| (|*|) :: ParamPattern -> ParamPattern -> ParamPattern (|*|) = mergeNumWith (*) (*) @@ -217,8 +217,31 @@ infixl 1 |/| (|/|) :: ParamPattern -> ParamPattern -> ParamPattern (|/|) = mergeNumWith (div) (/) +{- | These are shorthand for merging lists of patterns with @#@, @|*|@, @|+|@, +or @|/|@. Sometimes this saves a little typing and can improve readability +when passing things into other functions. As an example, instead of writing +@ +d1 $ sometimes ((|*| speed "2") . (|*| cutoff "2") . (|*| shape "1.5")) $ sound "arpy*4" # cutoff "350" # shape "0.3" +@ +you can write +@ +d1 $ sometimes (*** [speed "2", cutoff "2", shape "1.5"]) $ sound "arpy*4" ### [cutoff "350", shape "0.3"] +@ +-} +(###) = foldl (#) +(***) = foldl (|*|) +(+++) = foldl (|+|) +(///) = foldl (|/|) + setter :: MVar (a, [a]) -> a -> IO () setter ds p = do ps <- takeMVar ds putMVar ds $ (p, p:snd ps) return () +{- | Copies values from one parameter to another. Used by @nToOrbit@ in @Sound.Tidal.Dirt@. -} + +copyParam:: Param -> Param -> ParamPattern -> ParamPattern +copyParam fromParam toParam pat = f <$> pat + where f m = maybe m (updateValue m) (Map.lookup fromParam m) + updateValue m v = Map.union m (Map.fromList [(toParam,v)]) + diff --git a/Sound/Tidal/SuperCollider.hs b/Sound/Tidal/SuperCollider.hs index 004e50d5b..df19e2fe1 100644 --- a/Sound/Tidal/SuperCollider.hs +++ b/Sound/Tidal/SuperCollider.hs @@ -15,6 +15,7 @@ supercollider ps l = Shape { latency = l } +scSlang :: String -> OscSlang scSlang n = OscSlang { -- The OSC path path = "/s_new", @@ -23,10 +24,12 @@ scSlang n = OscSlang { timestamp = BundleStamp } +scBackend :: String -> IO (Backend a) scBackend n = do s <- makeConnection "127.0.0.1" 57110 (scSlang n) return $ Backend s (\_ _ _ -> return ()) +scStream :: String -> [Param] -> Double -> IO (ParamPattern -> IO (), Shape) scStream n ps l = do let shape = (supercollider ps l) backend <- scBackend n sc <- stream backend shape diff --git a/Sound/Tidal/Tempo.hs b/Sound/Tidal/Tempo.hs index a83863a57..26fa7f37d 100644 --- a/Sound/Tidal/Tempo.hs +++ b/Sound/Tidal/Tempo.hs @@ -3,39 +3,52 @@ module Sound.Tidal.Tempo where import Data.Time (getCurrentTime, UTCTime, NominalDiffTime, diffUTCTime, addUTCTime) import Data.Time.Clock.POSIX +import Control.Applicative ((<$>), (<*>)) import Control.Monad (forM_, forever, void) -import Control.Monad.IO.Class (liftIO) +--import Control.Monad.IO.Class (liftIO) import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent.MVar import Control.Monad.Trans (liftIO) +import Data.Maybe (fromMaybe, maybe) import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as T +import Data.Unique import qualified Network.WebSockets as WS import qualified Control.Exception as E +import Safe (readNote) +import System.Environment (lookupEnv) import qualified System.IO.Error as Error import GHC.Conc.Sync (ThreadId) -import System.Environment (getEnv) +import Sound.OSC.FD import Sound.Tidal.Utils data Tempo = Tempo {at :: UTCTime, beat :: Double, cps :: Double, paused :: Bool, clockLatency :: Double} -type ClientState = [WS.Connection] +type ClientState = [TConnection] -instance Eq WS.Connection +data TConnection = TConnection Unique WS.Connection + +wsConn :: TConnection -> WS.Connection +wsConn (TConnection _ c) = c + +instance Eq TConnection where + TConnection a _ == TConnection b _ = a == b instance Show Tempo where show x = show (at x) ++ "," ++ show (beat x) ++ "," ++ show (cps x) ++ "," ++ show (paused x) ++ "," ++ (show $ clockLatency x) getLatency :: IO Double -getLatency = fmap (read) (getEnvDefault "0.04" "TIDAL_CLOCK_LATENCY") +getLatency = + maybe 0.04 (readNote "latency parse") <$> lookupEnv "TIDAL_CLOCK_LATENCY" getClockIp :: IO String -getClockIp = getEnvDefault "127.0.0.1" "TIDAL_TEMPO_IP" +getClockIp = fromMaybe "127.0.0.1" <$> lookupEnv "TIDAL_TEMPO_IP" getServerPort :: IO Int -getServerPort = fmap read (getEnvDefault "9160" "TIDAL_TEMPO_PORT") +getServerPort = + maybe 9160 (readNote "port parse") <$> lookupEnv "TIDAL_TEMPO_PORT" readTempo :: String -> Tempo readTempo x = Tempo (read a) (read b) (read c) (read d) (read e) @@ -62,34 +75,45 @@ beatNow t = do now <- getCurrentTime let beatDelta = cps t * delta return $ beat t + beatDelta -clientApp :: MVar Tempo -> MVar Double -> WS.ClientApp () -clientApp mTempo mCps conn = do +clientApp :: MVar Tempo -> MVar Double -> MVar Double -> WS.ClientApp () +clientApp mTempo mCps mNudge conn = do --sink <- WS.getSink liftIO $ forkIO $ sendCps conn mTempo mCps + liftIO $ forkIO $ sendNudge conn mTempo mNudge forever loop where loop = do msg <- WS.receiveData conn let s = T.unpack msg let tempo = readTempo $ s - --putStrLn $ "to: " ++ show tempo - liftIO $ tryTakeMVar mTempo + old <- liftIO $ tryTakeMVar mTempo + -- putStrLn $ "from: " ++ show old + -- putStrLn $ "to: " ++ show tempo liftIO $ putMVar mTempo tempo +sendTempo :: WS.Connection -> Tempo -> IO () +sendTempo conn t = WS.sendTextData conn (T.pack $ show t) + sendCps :: WS.Connection -> MVar Tempo -> MVar Double -> IO () sendCps conn mTempo mCps = forever $ do cps <- takeMVar mCps t <- readMVar mTempo t' <- updateTempo t cps - WS.sendTextData conn (T.pack $ show t') + sendTempo conn t' + +sendNudge :: WS.Connection -> MVar Tempo -> MVar Double -> IO () +sendNudge conn mTempo mNudge = forever $ do + secs <- takeMVar mNudge + t <- readMVar mTempo + sendTempo conn $ nudgeTempo t secs -connectClient :: Bool -> String -> MVar Tempo -> MVar Double -> IO () -connectClient secondTry ip mTempo mCps = do +connectClient :: Bool -> String -> MVar Tempo -> MVar Double -> MVar Double -> IO () +connectClient secondTry ip mTempo mCps mNudge = do let errMsg = "Failed to connect to tidal server. Try specifying a " ++ "different port (default is 9160) setting the " ++ "environment variable TIDAL_TEMPO_PORT" serverPort <- getServerPort - WS.runClient ip serverPort "/tempo" (clientApp mTempo mCps) `E.catch` + WS.runClient ip serverPort "/tempo" (clientApp mTempo mCps mNudge) `E.catch` \(_ :: E.SomeException) -> do case secondTry of True -> error errMsg @@ -99,22 +123,28 @@ connectClient secondTry ip mTempo mCps = do Left (_ :: E.SomeException) -> error errMsg Right _ -> do threadDelay 500000 - connectClient True ip mTempo mCps + connectClient True ip mTempo mCps mNudge -runClient :: IO ((MVar Tempo, MVar Double)) +runClient :: IO ((MVar Tempo, MVar Double, MVar Double)) runClient = do clockip <- getClockIp mTempo <- newEmptyMVar mCps <- newEmptyMVar - forkIO $ connectClient False clockip mTempo mCps - return (mTempo, mCps) - -cpsUtils :: IO ((Double -> IO (), IO (Rational))) -cpsUtils = do (mTempo, mCps) <- runClient - let cpsSetter b = putMVar mCps b - currentTime = do tempo <- readMVar mTempo - now <- beatNow tempo - return $ toRational now + mNudge <- newEmptyMVar + forkIO $ connectClient False clockip mTempo mCps mNudge + return (mTempo, mCps, mNudge) + +cpsUtils' :: IO ((Double -> IO (), (Double -> IO ()), IO Rational)) +cpsUtils' = do (mTempo, mCps, mNudge) <- runClient + let cpsSetter = putMVar mCps + nudger = putMVar mNudge + currentTime = do tempo <- readMVar mTempo + now <- beatNow tempo + return $ toRational now + return (cpsSetter, nudger, currentTime) + +-- backward compatibility +cpsUtils = do (cpsSetter, _, currentTime) <- cpsUtils' return (cpsSetter, currentTime) -- Backwards compatibility @@ -157,7 +187,7 @@ clocked callback = clockedTick :: Int -> (Tempo -> Int -> IO ()) -> IO () clockedTick tpb callback = - do (mTempo, mCps) <- runClient + do (mTempo, _, mCps) <- runClient t <- readMVar mTempo now <- getCurrentTime let delta = realToFrac $ diffUTCTime now (at t) @@ -182,12 +212,17 @@ clockedTick tpb callback = let tps = (fromIntegral tpb) * cps tempo delta = realToFrac $ diffUTCTime now (at tempo) actualTick = ((fromIntegral tpb) * beat tempo) + (tps * delta) - tickDelta = (fromIntegral tick) - actualTick + -- only wait by up to two ticks + tickDelta = min 2 $ (fromIntegral tick) - actualTick delay = tickDelta / tps + -- putStrLn $ "tick delta: " ++ show tickDelta --putStrLn ("Delay: " ++ show delay ++ "s Beat: " ++ show (beat tempo)) threadDelay $ floor (delay * 1000000) callback tempo tick - return $ tick + 1 + -- putStrLn $ "hmm diff: " ++ show (abs $ (floor actualTick) - tick) + let newTick | (abs $ (floor actualTick) - tick) > 4 = floor actualTick + | otherwise = tick + 1 + return $ newTick --updateTempo :: MVar Tempo -> Maybe Double -> IO () --updateTempo mt Nothing = return () @@ -211,15 +246,16 @@ updateTempo t cps' beat'' = if cps' < 0 then 0 else beat' return $ t {at = now, beat = beat'', cps = cps', paused = (cps' <= 0)} -addClient client clients = client : clients - -removeClient :: WS.Connection -> ClientState -> ClientState +nudgeTempo :: Tempo -> Double -> Tempo +nudgeTempo t secs = t {at = addUTCTime (realToFrac secs) (at t)} + +removeClient :: TConnection -> ClientState -> ClientState removeClient client = filter (/= client) broadcast :: Text -> ClientState -> IO () broadcast message clients = do -- T.putStrLn message - forM_ clients $ \conn -> WS.sendTextData conn $ message + forM_ clients $ \conn -> WS.sendTextData (wsConn conn) $ message startServer :: IO (ThreadId) startServer = do @@ -228,21 +264,51 @@ startServer = do l <- getLatency tempoState <- newMVar (Tempo start 0 1 False l) clientState <- newMVar [] + liftIO $ oscBridge clientState forkIO $ WS.runServer "0.0.0.0" serverPort $ serverApp tempoState clientState serverApp :: MVar Tempo -> MVar ClientState -> WS.ServerApp serverApp tempoState clientState pending = do - conn <- WS.acceptRequest pending + conn <- TConnection <$> newUnique <*> WS.acceptRequest pending tempo <- liftIO $ readMVar tempoState - liftIO $ WS.sendTextData conn $ T.pack $ show tempo + liftIO $ WS.sendTextData (wsConn conn) $ T.pack $ show tempo clients <- liftIO $ readMVar clientState - liftIO $ modifyMVar_ clientState $ \s -> return $ addClient conn s + liftIO $ modifyMVar_ clientState $ return . (conn:) serverLoop conn tempoState clientState -serverLoop :: WS.Connection -> MVar Tempo -> MVar ClientState -> IO () -serverLoop conn tempoState clientState = E.handle catchDisconnect $ +oscBridge :: MVar ClientState -> IO () +oscBridge clientState = + do -- putStrLn $ "start osc bridge" + osc <- liftIO $ udpServer "0.0.0.0" 6060 + _ <- forkIO $ loop osc + return () + where loop osc = + do b <- recvBundle osc + -- putStrLn $ "received bundle" ++ (show b) + let timestamp = addUTCTime (realToFrac $ ntpr_to_ut $ bundleTime b) ut_epoch + msg = head $ bundleMessages b + -- todo - Data.Maybe version of !! + tick = datum_floating $ (messageDatum msg) !! 0 + tempo = datum_floating $ (messageDatum msg) !! 1 + address = messageAddress msg + act address timestamp tick tempo + loop osc + act "/sync" timestamp (Just tick) (Just tempo) + = do -- putStrLn $ "time " ++ show timestamp ++ " tick " ++ show tick ++ " tempo " ++ show tempo + let t = Tempo {at = timestamp, beat = tick, cps = tempo, + paused = False, + clockLatency = 0 + } + msg = T.pack $ show t + clients <- readMVar clientState + broadcast msg clients + return () + act _ _ _ _ = return () + +serverLoop :: TConnection -> MVar Tempo -> MVar ClientState -> IO () +serverLoop conn _tempoState clientState = E.handle catchDisconnect $ forever $ do - msg <- WS.receiveData conn + msg <- WS.receiveData $ wsConn conn --liftIO $ updateTempo tempoState $ maybeRead $ T.unpack msg liftIO $ readMVar clientState >>= broadcast msg --tempo <- liftIO $ readMVar tempoState diff --git a/Sound/Tidal/Transition.hs b/Sound/Tidal/Transition.hs index 5db2e6d85..2d87345f5 100644 --- a/Sound/Tidal/Transition.hs +++ b/Sound/Tidal/Transition.hs @@ -8,7 +8,9 @@ import Sound.Tidal.Utils import Control.Concurrent.MVar import Control.Applicative +import Data.Maybe +import qualified Data.Map.Strict as Map import Data.Monoid transition :: (IO Time) -> MVar (ParamPattern, [ParamPattern]) -> (Time -> [ParamPattern] -> ParamPattern) -> ParamPattern -> IO () @@ -30,18 +32,31 @@ histpan n _ ps = stack $ map (\(i,p) -> p # pan (atom $ (fromIntegral i) / (from where ps' = take n ps n' = length ps' -- in case there's fewer patterns than requested --- generalizing wash to use pattern transformers on fadeout, fadein, and delay --- to start of transition -superwash :: (Pattern a -> Pattern a) -> (Pattern a -> Pattern a) -> Time -> Time -> Time -> [Pattern a] -> Pattern a -superwash _ _ _ _ _ [] = silence -superwash _ _ _ _ _ (p:[]) = p -superwash fout fin delay dur now (p:p':_) = +{-| +A generalization of `wash`. 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. +-} +superwash :: (Pattern a -> Pattern a) -> (Pattern a -> Pattern a) -> Time -> Time -> Time -> Time -> [Pattern a] -> Pattern a +superwash _ _ _ _ _ _ [] = silence +superwash _ _ _ _ _ _ (p:[]) = p +superwash fout fin delay durin durout now (p:p':_) = (playWhen (< (now + delay)) p') <> - (playWhen (between (now + delay) (now + delay + dur)) $ fout p') <> - (playWhen (>= (now + delay + dur)) $ fin p) + (playWhen (between (now + delay) (now + delay + durin)) $ fout p') <> + (playWhen (between (now + delay + durin) (now + delay + durin + durout)) $ fin p) <> + (playWhen (>= (now + delay + durin + durout)) $ p) where between lo hi x = (x >= lo) && (x < hi) +{-| +Wash away the current pattern by applying a function to it over time, then switching over to the next. + +@ +d1 $ sound "feel ! feel:1 feel:2" + +t1 (wash (chop 8) 4) $ sound "feel*4 [feel:2 sn:2]" +@ + +Note that `chop 8` is applied to `sound "feel ! feel:1 feel:2"` for 4 cycles and then the whole pattern is replaced by `sound "feel*4 [feel:2 sn:2]` +-} wash :: (Pattern a -> Pattern a) -> Time -> Time -> [Pattern a] -> Pattern a wash _ _ _ [] = silence wash _ _ _ (p:[]) = p @@ -50,9 +65,22 @@ wash f t now (p:p':_) = overlay (playWhen (< (now + t)) $ f p') (playWhen (>= (n -- | Just stop for a bit before playing new pattern wait :: Time -> Time -> [ParamPattern] -> ParamPattern -wait t _ [] = silence +wait _ _ [] = silence wait t now (p:_) = playWhen (>= (nextSam (now+t-1))) p +{- | Just as `wait`, `wait'` stops for a bit and then applies the given transition to the playing pattern + +@ +d1 $ sound "bd" + +t1 (wait' (xfadeIn 8) 4) $ sound "hh*8" +@ +-} +wait' :: (Time -> [ParamPattern] -> ParamPattern) -> Time -> Time -> [ParamPattern] -> ParamPattern +wait' _ t _ [] = silence +wait' f t now ps@(p:_) = playWhen (>= (nextSam (now+t-1))) (f (now + t) ps) + + {- | Jumps directly into the given pattern, this is essentially the _no transition_-transition. @@ -68,18 +96,33 @@ t1 (jumpIn 2) $ sound "kick(3,8)" @ -} jumpIn :: Int -> Time -> [ParamPattern] -> ParamPattern -jumpIn n = superwash id id (fromIntegral n) 0 +jumpIn n = superwash id id (fromIntegral n) 0 0 {- | Unlike `jumpIn` the variant `jumpIn'` will only transition at cycle boundary (e.g. when the cycle count is an integer). -} jumpIn' :: Int -> Time -> [ParamPattern] -> ParamPattern -jumpIn' n now = superwash id id ((nextSam now) - now + (fromIntegral n)) 0 now +jumpIn' n now = superwash id id ((nextSam now) - now + (fromIntegral n)) 0 0 now -- | Sharp `jump` transition at next cycle boundary where cycle mod n == 0 jumpMod :: Int -> Time -> [ParamPattern] -> ParamPattern jumpMod n now = jumpIn ((n-1) - ((floor now) `mod` n)) now --- | Degrade the new pattern over time until it goes to nothing +-- | Degrade the new pattern over time until it ends in silence mortal :: Time -> Time -> Time -> [ParamPattern] -> ParamPattern mortal _ _ _ [] = silence mortal lifespan release now (p:_) = overlay (playWhen (<(now+lifespan)) p) (playWhen (>= (now+lifespan)) (fadeOut' (now + lifespan) release p)) + +combineV :: (Value -> Value -> Value) -> ParamMap -> ParamMap -> ParamMap +combineV f a b = Map.mapWithKey pairUp a + where pairUp k v | Map.notMember k b = v + | otherwise = f v (fromJust $ Map.lookup k b) + +mixNums v (VF a) (VF b) = VF $ (a * v) + (b * (1-v)) +mixNums v (VI a) (VI b) = VI $ floor $ (fromIntegral a * v) + (fromIntegral b * (1-v)) +mixNums v _ b = b + +interpolateIn :: Time -> Time -> [ParamPattern] -> ParamPattern +interpolateIn _ _ [] = silence +interpolateIn _ _ (p:[]) = p +interpolateIn t now (p:p':_) = do n <- now ~> (slow t envL) + combineV (mixNums n) <$> p <*> p' diff --git a/Sound/Tidal/Utils.hs b/Sound/Tidal/Utils.hs index 55b7ccb5c..e834add62 100644 --- a/Sound/Tidal/Utils.hs +++ b/Sound/Tidal/Utils.hs @@ -4,9 +4,7 @@ Description: Helper functions not directly specific to Tidal -} module Sound.Tidal.Utils where -import System.Environment (getEnv) import Data.Maybe (listToMaybe) -import Control.Exception {- | enumerate a list of things @@ -82,12 +80,6 @@ mapThds' = fmap . mapThd' mapArcs :: (a -> a) -> [(a, a, x)] -> [(a, a, x)] mapArcs f = (mapFsts' f) . (mapSnds' f) --- | return environment variable @var@'s value or @defValue@ -getEnvDefault :: String -> String -> IO String -getEnvDefault defValue var = do - res <- try . getEnv $ var - return $ either (const defValue) id (res :: Either IOException String) - {- | combines two lists by interleaving them >>> mergelists [1,2,3] [9,8,7] diff --git a/icon.png b/icon.png new file mode 100644 index 000000000..e3bfdeffa Binary files /dev/null and b/icon.png differ diff --git a/tidal.cabal b/tidal.cabal index a53250669..262098842 100644 --- a/tidal.cabal +++ b/tidal.cabal @@ -1,5 +1,5 @@ name: tidal -version: 0.8.2 +version: 0.9 synopsis: Pattern language for improvised music -- description: homepage: http://tidal.lurk.org/ @@ -19,7 +19,8 @@ Extra-source-files: README.md tidal.el doc/tidal.md Description: Tidal is a domain specific language for live coding pattern. library - Exposed-modules: Sound.Tidal.Strategies + Exposed-modules: Sound.Tidal.Bjorklund + Sound.Tidal.Strategies Sound.Tidal.Dirt Sound.Tidal.Pattern Sound.Tidal.Stream @@ -32,9 +33,23 @@ library Sound.Tidal.SuperCollider Sound.Tidal.Params Sound.Tidal.Transition + Sound.Tidal.Scales + Sound.Tidal.Chords + + Build-depends: + base < 5 + , containers + , hashable + , colour + , hosc > 0.13 + , text + , mersenne-random-pure64 + , time + , parsec + , safe + , websockets > 0.8 + , mtl >= 2.1 - Build-depends: base < 5, process, parsec, hosc > 0.13, hashable, colour, containers, time, websockets > 0.8, text, mtl >=2.1, transformers, mersenne-random-pure64, binary, bytestring, hmt source-repository head type: git location: https://github.com/tidalcycles/Tidal - diff --git a/tidal.el b/tidal.el index 71eeae5de..4b1e3f79b 100644 --- a/tidal.el +++ b/tidal.el @@ -55,8 +55,11 @@ tidal-interpreter-arguments) (tidal-see-output)) (tidal-send-string ":set prompt \"\"") + (tidal-send-string ":set prompt2 \"\"") (tidal-send-string ":module Sound.Tidal.Context") - (tidal-send-string "(cps, getNow) <- bpsUtils") + (tidal-send-string "import qualified Sound.Tidal.Scales as Scales") + (tidal-send-string "import qualified Sound.Tidal.Chords as Chords") + (tidal-send-string "(cps, getNow) <- cpsUtils") (tidal-send-string "(d1,t1) <- superDirtSetters getNow") (tidal-send-string "(d2,t2) <- superDirtSetters getNow") (tidal-send-string "(d3,t3) <- superDirtSetters getNow") diff --git a/vis/Sound/Tidal/Cycle.hs b/vis/Sound/Tidal/Cycle.hs new file mode 100644 index 000000000..8182eeb8d --- /dev/null +++ b/vis/Sound/Tidal/Cycle.hs @@ -0,0 +1,283 @@ +module Sound.Tidal.Cycle where + +import Control.Monad +import Control.Monad.State +import Control.Monad.Reader +import Control.Concurrent.MVar +import Data.Array.IArray + +import Graphics.UI.SDL +import Graphics.UI.SDL.Image +import qualified Graphics.UI.SDL.Framerate as FR +import qualified Graphics.UI.SDL.Primitives as SDLP +import qualified Graphics.UI.SDL.TTF.General as TTFG +import Graphics.UI.SDL.TTF.Management +import Graphics.UI.SDL.TTF.Render +import Graphics.UI.SDL.TTF.Types +import Data.Maybe (listToMaybe, fromMaybe, fromJust, isJust, catMaybes) +import GHC.Int (Int16) +import Data.List (intercalate, tails, nub, sortBy) +import Data.Colour +import Data.Colour.Names +import Data.Colour.SRGB +import Data.Colour.RGBSpace.HSV (hsv) +import qualified GHC.Word +import Data.Bits +import Data.Ratio +import Debug.Trace (trace) +import Data.Fixed (mod') +import Control.Concurrent +import System.Exit + +import Sound.OSC.FD + +import Sound.Tidal.Stream (ParamPattern) +import Sound.Tidal.Pattern +import Sound.Tidal.Parse +import Sound.Tidal.Tempo +import qualified Sound.Tidal.Time as Time +import Sound.Tidal.Utils + + +--enumerate :: [a] -> [(Int, a)] +--enumerate = zip [0..] + +maybeHead [] = Nothing +maybeHead (x:_) = Just x + +sortByFst :: Ord a => [(a, b)] -> [(a, b)] +sortByFst = sortBy (\a b -> compare (fst a) (fst b)) + +parenthesise :: String -> String +parenthesise x = "(" ++ x ++ ")" + +spaces :: Int -> String +spaces n = take n $ repeat ' ' + +single :: [a] -> Maybe a +single (x:[]) = Just x +single _ = Nothing + +fromJust' (Just x) = x +fromJust' Nothing = error "nothing is just" + +data Scene = Scene {mouseXY :: (Float, Float), + cursor :: (Float, Float) + } + +data AppConfig = AppConfig { + screen :: Surface, + font :: Font, + tempoMV :: MVar (Tempo), + fr :: FR.FPSManager, + mpat :: MVar (Pattern ColourD) +} + +type AppState = StateT Scene IO +type AppEnv = ReaderT AppConfig AppState + + + +screenWidth = 1024 +screenHeight = 768 +screenBpp = 32 +middle = (fromIntegral $ screenWidth`div`2,fromIntegral $ screenHeight`div`2) + +toScreen :: (Float, Float) -> (Int, Int) +toScreen (x, y) = (floor (x * (fromIntegral screenWidth)), + floor (y * (fromIntegral screenHeight)) + ) + +toScreen16 :: (Float, Float) -> (Int16, Int16) +toScreen16 (x, y) = (fromIntegral $ floor (x * (fromIntegral screenWidth)), + fromIntegral $ floor (y * (fromIntegral screenHeight)) + ) + +fromScreen :: (Int, Int) -> (Float, Float) +fromScreen (x, y) = ((fromIntegral x) / (fromIntegral screenWidth), + (fromIntegral y) / (fromIntegral screenHeight) + ) + +isInside :: Integral a => Rect -> a -> a -> Bool +isInside (Rect rx ry rw rh) x y = (x' > rx) && (x' < rx + rw) && (y' > ry) && (y' < ry + rh) + where (x', y') = (fromIntegral x, fromIntegral y) + +ctrlDown mods = or $ map (\x -> elem x [KeyModLeftCtrl, + KeyModRightCtrl + ] + ) mods + +shiftDown mods = or $ map (\x -> elem x [KeyModLeftShift, + KeyModRightShift, + KeyModShift + ] + ) mods + +handleEvent :: Scene -> Event -> AppEnv (Scene) +handleEvent scene (KeyDown k) = + handleKey scene (symKey k) (symUnicode k) (symModifiers k) + +handleEvent scene _ = return scene + +handleKey :: Scene -> SDLKey -> Char -> [Modifier] -> AppEnv Scene +handleKey scene SDLK_SPACE _ _ = return scene +handleKey scene _ _ _ = return scene + + +applySurface :: Int -> Int -> Surface -> Surface -> Maybe Rect -> IO Bool +applySurface x y src dst clip = blitSurface src clip dst offset + where offset = Just Rect { rectX = x, rectY = y, rectW = 0, rectH = 0 } + +initEnv :: MVar (Pattern ColourD) -> IO AppConfig +initEnv mp = do + screen <- setVideoMode screenWidth screenHeight screenBpp [SWSurface] + font <- openFont "futura.ttf" 22 + setCaption "Cycle" [] + tempoMV <- tempoMVar + fps <- FR.new + FR.init fps + FR.set fps 15 + return $ AppConfig screen font tempoMV fps mp + +blankWidth = 0.015 + +drawArc' :: Surface -> ColourD -> (Double, Double) -> (Double, Double) -> Double -> Double -> Double -> IO () +drawArc' screen c (x,y) (r,r') t o step | o <= 0 = return () + | otherwise = + do let pix = (colourToPixel c) + steps = [t, (t + step) .. (t + o)] + coords = map (\s -> (floor $ x + (r*cos(s)),floor $ y + (r*sin(s)))) steps + ++ map (\s -> (floor $ x + (r'*cos(s)),floor $ y + (r'*sin(s)))) (reverse steps) + SDLP.filledPolygon screen coords pix + --drawArc screen c (x,y) (r,r') t (o-step) step + return () + where a = max t (t + o - step) + b = t + o + + +drawArc :: Surface -> ColourD -> (Double, Double) -> (Double, Double) -> Double -> Double -> Double -> IO () +drawArc screen c (x,y) (r,r') t o step | o <= 0 = return () + | otherwise = + do let pix = (colourToPixel c) + SDLP.filledPolygon screen coords pix + drawArc screen c (x,y) (r,r') t (o-step) step + return () + where a = max t (t + o - step) + b = t + o + coords = map ((\(x',y') -> (floor $ x + x', floor $ y + y'))) + [(r * cos(a), r * sin(a)), + (r' * cos(a), r' * sin(a)), + (r' * cos(b), r' * sin(b)), + (r * cos(b), r * sin(b)) + ] + +loop :: AppEnv () +loop = do + quit <- whileEvents $ act + screen <- screen `liftM` ask + font <- font `liftM` ask + tempoM <- tempoMV `liftM` ask + fps <- fr `liftM` ask + scene <- get + mp <- mpat `liftM` ask + liftIO $ do + pat <- readMVar mp + tempo <- readMVar tempoM + beat <- beatNow tempo + bgColor <- (mapRGB . surfaceGetPixelFormat) screen 0x00 0x00 0x00 + clipRect <- Just `liftM` getClipRect screen + fillRect screen clipRect bgColor + --drawArc screen middle (100,110) ((beat) * pi) (pi/2) (pi/32) + drawPat middle (100,(fi screenHeight)/2) pat screen beat + Graphics.UI.SDL.flip screen + FR.delay fps + unless quit loop + where act e = do scene <- get + scene' <- handleEvent scene e + put $ scene' + +drawPat :: (Double, Double) -> (Double, Double) -> Pattern ColourD -> Surface -> Double -> IO () +drawPat (x, y) (r,r') p screen beat = mapM_ drawEvents es + where es = map (\(_, (s,e), evs) -> ((max s pos, min e (pos + 1)), evs)) $ arc (segment p) (pos, pos + 1) + pos = toRational $ beat / 8 + drawEvents ((s,e), cs) = + mapM_ (\(n', c) -> drawEvent (s,e) c n' (length cs)) (enumerate $ reverse cs) + drawEvent (s,e) c n' len = + do let thickness = (1 / fromIntegral len) * (r' - r) + let start = r + thickness * (fromIntegral n') + drawArc screen c middle (start,start+thickness) ((pi*2) * (fromRational (s-pos))) ((pi*2) * fromRational (e-s)) (pi/16) +{- (thickLine h (n*scale+n') (linesz/ (fromIntegral scale)) + (x1 + (xd * fromRational (e-pos))) + (y1 + (yd * fromRational (e-pos))) + (x1 + (xd * fromRational (s-pos))) + (y1 + (yd * fromRational (s-pos))) + ) + screen (colourToPixel c)-} + +segment2 :: Pattern a -> Pattern [(Bool, a)] +segment2 p = Pattern $ \(s,e) -> filter (\(_, (s',e'),_) -> s' < e && e' > s) $ groupByTime (segment2' (arc (fmap (\x -> (True, x)) p) (s,e))) + + +segment2' :: [Time.Event (Bool, a)] -> [Time.Event (Bool, a)] +segment2' es = foldr splitEs es pts + where pts = nub $ points es + +splitEs :: Time.Time -> [Time.Event (Bool, a)] -> [Time.Event (Bool, a)] +splitEs _ [] = [] +splitEs t ((ev@(a, (s,e), (h,v))):es) | t > s && t < e = (a, (s,t),(h,v)):(a, (t,e),(False,v)):(splitEs t es) + | otherwise = ev:splitEs t es + +whileEvents :: MonadIO m => (Event -> m ()) -> m Bool +whileEvents act = do + event <- liftIO pollEvent + case event of + Quit -> return True + NoEvent -> return False + _ -> do + act event + whileEvents act + +runLoop :: AppConfig -> Scene -> IO () +runLoop = evalStateT . runReaderT loop + +textSize :: String -> Font -> IO ((Float,Float)) +textSize text font = + do message <- renderTextSolid font text (Color 0 0 0) + return (fromScreen (surfaceGetWidth message, surfaceGetHeight message)) + +run = do mp <- newMVar silence + forkIO $ run' mp + return mp + + +run' mp = withInit [InitEverything] $ + do result <- TTFG.init + if not result + then putStrLn "Failed to init ttf" + else do enableUnicode True + env <- initEnv mp + --ws <- wordMenu (font env) things + let scene = Scene (0,0) (0.5,0.5) + --putStrLn $ show scene + runLoop env scene + + +-- colourToPixel :: Colour Double -> Pixel +-- colourToPixel c = rgbColor (floor $ 256*r) (floor $ 256* g) (floor $ 256*b) +-- where (RGB r g b) = toSRGB c + +colourToPixel :: Colour Double -> Pixel +colourToPixel c = rgbColor (floor $ r*255) (floor $ g*255) (floor $ b *255) -- mapRGB (surfaceGetPixelFormat screen) 255 255 255 + where (RGB r g b) = toSRGB c + +--colourToPixel :: Surface -> Colour Double -> IO Pixel +--colourToPixel s c = (mapRGB . surfaceGetPixelFormat) s (floor $ r*255) (floor $ g*255) (floor $ b*255) + + +fi a = fromIntegral a + +rgbColor :: GHC.Word.Word8 -> GHC.Word.Word8 -> GHC.Word.Word8 -> Pixel +rgbColor r g b = Pixel (shiftL (fi r) 24 .|. shiftL (fi g) 16 .|. shiftL (fi b) 8 .|. (fi 255)) + +pixel :: Surface -> (GHC.Word.Word8,GHC.Word.Word8,GHC.Word.Word8) -> IO Pixel +pixel surface (r,g,b) = mapRGB (surfaceGetPixelFormat surface) r g b diff --git a/vis/tidal-vis.cabal b/vis/tidal-vis.cabal index 777e588a5..4be699d56 100644 --- a/vis/tidal-vis.cabal +++ b/vis/tidal-vis.cabal @@ -1,5 +1,5 @@ name: tidal-vis -version: 0.1.5 +version: 0.9 synopsis: Visual rendering for Tidal patterns -- description: homepage: http://yaxu.org/tidal/ @@ -20,4 +20,4 @@ Description: Tidal is a domain specific language for live coding pattern. This p library Exposed-modules: Sound.Tidal.Vis - Build-depends: base < 5, tidal, colour, cairo + Build-depends: base, tidal==0.9, colour, cairo