Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rolled function with variants #820

Merged
merged 12 commits into from
Feb 16, 2022
42 changes: 41 additions & 1 deletion src/Sound/Tidal/UI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@ import Prelude hiding ((<*), (*>))

import Data.Char (digitToInt, isDigit, ord)
import Data.Bits (testBit, Bits, xor, shiftL, shiftR)

-- import System.Random (randoms, mkStdGen)
-- import Control.Monad.ST
-- import Control.Monad.Primitive (PrimState, PrimMonad)
-- import qualified Data.Vector as V
-- import Data.Word (Word32)
import Data.Ratio ((%), Ratio)
import Data.Fixed (mod')
import Data.Ratio ((%))
import Data.List (sort, sortOn, findIndices, elemIndex, groupBy, transpose, intercalate, findIndex)
import Data.Maybe (isJust, fromJust, fromMaybe, mapMaybe)
import qualified Data.Text as T
Expand Down Expand Up @@ -1439,6 +1445,40 @@ _arp name p = arpWith f p
thumbup xs = concatMap (\x -> [thumb,x]) $ tail xs
where thumb = head xs

{- | `rolled` plays each note of a chord quickly in order, as opposed to simultaneously; to give a chord a harp-like effect.
This will played from the lowest note to the highest note of the chord
@
rolled $ n "c'maj'4" # s "superpiano"
@


And you can use `rolledBy` or `rolledBy'` to specify the length of the roll. The value in the passed pattern
is the divisor of the cycle length. A negative value will play the arpeggio in reverse order.

@
rolledBy "<1 -0.5 0.25 -0.125>" $ note "c'maj9" # s "superpiano"
@
-}

rolledWith :: Ratio Integer -> Pattern a -> Pattern a
rolledWith t = withEvents aux
where aux es = concatMap (steppityIn) (groupBy (\a b -> whole a == whole b) $ ((isRev t) es))
isRev b = (\x -> if x > 0 then id else reverse ) b
Copy link
Contributor Author

@thgrund thgrund Jan 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to control the order (playing in reverse or not) within the mini notation. With this lambda it's possible to do this rolledBy "-0.5" $ n ("[0,1,2,3]"). This will result in:

(0>1)|n: Note {unNote = 3.0}n
(⅛>1)|n: Note {unNote = 2.0}n
(¼>1)|n: Note {unNote = 1.0}n
(⅜>1)|n: Note {unNote = 0.0}n

steppityIn xs = mapMaybe (\(n, ev) -> (timeguard n xs ev t)) $ enumerate xs
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this zero check to make this possible:

s "superpiano" <| n (rolledBy "0" "[0,1,2,3]")

Otherwise we would receive an exception anyways.

timeguard _ _ ev 0 = return ev
timeguard n xs ev _ = (shiftIt n (length xs) ev)
shiftIt n d (Event c (Just (Arc s e)) a' v) = do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gives a 'Pattern match(es) are non-exhaustive' warning, I think this will break on continuous events (those with whole arc of Nothing). Probably adding a catchall of shiftIt _ _ ev = ev is enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I resolved this issue. This will ignore the rolled function for continuous events like

n (rolledBy "1" (irand 8) |+ "[0,8, 12]")

-- ~0>1~|n: Note {unNote = 2.0}n
-- ~0>1~|n: Note {unNote = 10.0}n
-- ~0>1~|n: Note {unNote = 14.0}n

a'' <- subArc (Arc newS e) a'
return (Event c (Just $ Arc newS e) a'' v)
where newS = s + (dur * fromIntegral n)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will result in patterns which aren't well formed, by moving events outside the original query.
Fixing this would be a little complicated I think. The query would need to be made bigger, subtracting the roll duration from the start and adding the duration to the end of the query arc. Then after the calculation done, resulting events trimmed to the original query, and removed completely if they're outside that original query.

Copy link
Contributor Author

@thgrund thgrund Jan 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @yaxu ! I'm just taking the time to try to finish this topic. My understanding is that the behavior you describe should already be implemented. For example s "superpiano" <| n (rolledBy "[1.5]" "[0,1,2]")

will result in

(0>1)|n: Note {unNote = 0.0}n, s: "superpiano"
(½>1)|n: Note {unNote = 1.0}n, s: "superpiano

So, practically, when the time arc of the pattern gets bigger (in theory), the overhanging events are truncated.

And that's what you meant, right?

Copy link
Member

@yaxu yaxu Feb 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm finding it hard to get my head around this in the time I have available..

But what I meant is something like.. Tidal calculates events in a window given by the query. This makes functions like this quite difficult, when we want to cause a new event to happen after an existing one, in at least two ways. We might query a timespan that does not contain any events, but there was one happening just before that timespan that should cause one in the window we're asking for. But we don't know about that. The other problem is that we might have an event in the window but it should cause an event outside that window. From what I could tell, both are an issue here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll merge for now and will try to make a failling test at some point

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I play with it, it seems I was misunderstanding something, it seems to work fine. Sorry for holding this up!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work - thanks Thomas and Alex

dur = ((e - s)) / ((1/ (abs t))*fromIntegral d)
shiftIt _ _ ev = return ev

rolledBy :: Pattern (Ratio Integer) -> Pattern a -> Pattern a
rolledBy pt = tParam rolledWith (segment 1 $ pt)

rolled :: Pattern a -> Pattern a
rolled = rolledBy (1/4)

{- TODO !

Expand Down
38 changes: 38 additions & 0 deletions test/Sound/Tidal/UITest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,44 @@ run =
("0*4" :: Pattern Double)
-}

describe "rolledBy" $ do
it "shifts each start of events in a list correctly" $ do
let
overTimeSpan = (Arc 0 1)
testMe = rolledBy "0.5" $ n ("[0,1,2,3]")
expectedResult = n "[0, ~ 1@7, ~@2 2@6, ~@3 3@5]"
in
compareP overTimeSpan testMe expectedResult
it "shifts each start of events in a list correctly in reverse order" $ do
let
overTimeSpan = (Arc 0 1)
testMe = rolledBy "-0.5" $ n ("[0,1,2,3]")
expectedResult = n "[3, ~ 2@7, ~@2 1@6, ~@3 0@5]"
in
compareP overTimeSpan testMe expectedResult
it "trims the result pattern if it becomes larger than the original pattern" $ do
let
overTimeSpan = (Arc 0 1)
testMe = rolledBy "1.5" $ n ("[0,1,2]")
expectedResult = n "[0, ~ 1]"
in
compareP overTimeSpan testMe expectedResult
it "does nothing for continous functions" $ do
let
overTimeSpan = (Arc 0 1)
testMe = n (rolledBy "0.25" (irand 0) |+ "[0,12]")
expectedResult = n (irand 0) |+ n "[0, 12]"
in
compareP overTimeSpan testMe expectedResult
it "does nothing when passing zero as time value" $ do
let
overTimeSpan = (Arc 0 1)
testMe = n (rolledBy "0" "[0,1,2,3]")
expectedResult = n "[0,1,2,3]"
in
compareP overTimeSpan testMe expectedResult


describe "sometimesBy" $ do
it "does nothing when set at 0% probability" $ do
let
Expand Down