Tidal 2.0?

As mentioned in overhaul of the parser by polymorphicengine · Pull Request #952 · tidalcycles/Tidal · GitHub, _ and @ are aliases nowadays. Should Tidal 2 remove either _ or @? Possibly give one of them a new behavior if we have one that could fit?

1 Like

I suggested to my students to look into tidal-2, and some might take up the challenge. Where could they start? I think that extra eyeballs on a project are always useful. (This is an end-of-term project, in January only.)

NB: students will read this, but don't necessarily post (but I can be a proxy). (It's forbidden by German privacy law to compel students to make/use accounts or publish messages on third-party servers - and I agree with that.)

1 Like
  1. what is the plan? (is there a design document)
  2. what is the status? Is the cycseq branch supposed to be buildable? what test cases should work?
  3. what are immediate questions where independent testing would help?
  4. what is the best place for this discussion?

ad 2: I get

$ git log -n 1
commit 496cc60162eb87fcfdcb4f7796ae7d532801f032 (HEAD -> cycseq, origin/cycseq)
Merge: 6285403d 5ab4d84e
Author: Alex McLean <alex@slab.org>
Date:   Sun Jan 15 16:18:34 2023 +0000


$ cabal build
Build profile: -w ghc-9.2.5 -O1
In order, the following will be built (use -v for more details):
 - tidal-2.0.0 (lib) (first run)
Preprocessing library for tidal-2.0.0..
Building library for tidal-2.0.0..
[26 of 28] Compiling Sound.Tidal.Stream ( src/Sound/Tidal/Stream.hs, /home/waldmann/software/music/Tidal/dist-newstyle/build/x86_64-linux/ghc-9.2.5/tidal-2.0.0/build/Sound/Tidal/Stream.o, /home/waldmann/software/music/Tidal/dist-newstyle/build/x86_64-linux/ghc-9.2.5/tidal-2.0.0/build/Sound/Tidal/Stream.dyn_o )

src/Sound/Tidal/Stream.hs:421:16: error:
    Variable not in scope:
      withQueryControls
        :: (Map.Map String Value -> Map.Map String Value)
           -> Signal ValueMap -> b
    |
421 |         pat' = withQueryControls (Map.union patControls)
    |                ^^^^^^^^^^^^^^^^^

and there are some more undefined names. The compiler can ignore these (in effect, replace missing definitions with undefined) via


$ git diff
diff --git a/tidal.cabal b/tidal.cabal
index f361cb4c..5bdff480 100644
--- a/tidal.cabal
+++ b/tidal.cabal
@@ -19,7 +19,7 @@ data-files:          BootTidal.hs
 Extra-source-files: README.md CHANGELOG.md tidal.el
 
 library
-  ghc-options: -Wall
+  ghc-options: -Wall -fdefer-type-errors
   hs-source-dirs:

then I cabal build and cabal repl but I cannot load the boot file.

Ok, 28 modules loaded.
ghci> :script BootTidal.hs 
[TidalCycles version 2.0.0-pre]
Installed in /home/waldmann/software/music/Tidal/./.
Listening for external controls on 127.0.0.1:6010
Conn
ectedo SuperD1mBooitTrti.da
l.hs:54:38: error:
    Not in scope: ‘Sound.Tidal.Transition.xfadeIn’
    No module named ‘Sound.Tidal.Transition’ is imported.
   |
54 |     xfade i = transition tidal True (Sound.Tidal.Transition.xfadeIn 4) i
   |                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

...

I'm a bit distant from it all at the moment but will get back to it soon ! It's not in a working state though I thought it was passing tests.. There isn't a design document.

Thanks for replying. Then I guess it's not suitable for a student project (right now). I am still interested! So, whenever you have something for us to look at, send a message.

Ok I finally got things working to the point where I managed to do a performance with it last Friday.

Here's a quick test stream:

Most things aren't working, e.g. I somehow broke polys/centres/expands that are supposed to align patterns in an alternative way to stack. But it's a lot of fun to play with the way beats automatically align.

There's still some things to do, e.g.

1/ work out the different ways of defining bind/join with different alignments, deciding what the default should be for the patternify (formally known at tParam) functions, and how to make it so you can easily pick a different method. E.g. right now fast "1 2" "bd sd" will make something like "bd sd [bd sd]"
2/ fix up toSeq in the mininotation parser with sensible defaults
3/ figure out what to do if some combination of sequences tries create a new sequence that is too long and would fill up all available memory
4/ go through the signal modules working out what functions can be generalised and moved up to the pattern class so they also work with sequences
5/ once it's clearer what the behaviour should be, write all the tests..
6/ and more..

10 Likes

I feel bad asking such a broad question, but I really don't get it. Is it written somewhere what this rewrite aims to accomplish? What does it let you express or do that the earlier version of Tidal does not?

At some point I thought it would make room for sequences (which have order but no measure) to be distinct from rhythms (which have both order and measure) and for sequences and rhythms to share some operations. Is that the idea? If so, at what points in the demonstration video are you using sequences, and at what points are you using rhythms?

The following blog post by yaxu should help you understand it!

https://slab.org/2023/05/13/tidal-2-0-beatmode-tidalcycles-vs-tidalbeats/

Thanks, @geikha !

Alas, it doesn't ...

The closest it seems to come to defining the idea is this paragraph: "In ‘beat mode’, most things will look the same, but act differently. E.g. when you reverse a pattern in beatmode, the whole sequence gets reversed, rather than every cycle reversed independently. Even the mini-notation works, but becomes beat-oriented."

What does "beat-oriented" even mean?

There's also this: "the Signal and Sequences definitions are becoming mostly concerned with how to align and combine things of their type". That might be helpful if I could see an example of the kind of alignment at issue.

Okay! Here's my best attempt to put it into words:


Regular Tidal aka Cycle-oriented

Regular Tidal is a pattern based language, patterns are a set of events that happen within a cycle. With regular Tidal, when you write a pattern with some elements, they will always fit into a single cycle (unless slowed down, alternated, etc). When you operate between two patterns, for example:

"1 1 1 1" |+ "0 2" is equal to "1 1 3 3"

Tidal doesn't care about the sequence of notes, it only cares about which notes are active within the cycle, so the result is "1 1 3 3".

Beat-oriented aka Sequence-oriented

This contrasts with sequence-based languages such as FoxDot, or as with any other sequencer you can find in "regular" electronic music production. In these, you set a specific number of notes and the rate at which they change. With beatMode, you define the amount of notes to be played within a single cycle. Operations between sequences will be calculated note by note, wrapping back to the beginning of the sequence when it ends. So the previous example, in beatMode, would be:

beatMode 4 "1 1 1 1" |+ "0 2" is equal to "1 3 1 3".

Here's a more complicated example:

beatMode 6 "10 20 30 40" |+ "1 2 3" would generate a sequence that resolves after 2 cycles:

| 11 22 33 41 12 23 | 31 42 13 21 32 43 |

This per-note behaviour is what is meant by "beat-oriented", as opposed to "cycle-oriented". It's also nice to point out how regular Tidal tends towards polyrhythms while beat-oriented Tidal tends towards polymeters.


Key things about the new implementation:

  • Pattern: The old Pattern datatype is no more. Now Pattern is a typeclass to which both Signal and Sequence comply. For those unfamiliar with Haskell, a typeclass is roughly similar to an interface in OOP. This allows both signals and sequences to use the same functions seamlessly.

  • Signal: The Signal datatype serves the exact same purpose that the old Pattern type did. Alex documented it as "a function from a timearc (possibly with some other state) to events taking place in that timearc.".

  • Sequence: This is the new datatype that represents sequences. All sequences used in Tidal will be turned into Signals at some point.

  • beatMode: This function is actually an alias for toCycle, which turns a Sequence into a Signal. It does this (roughly speaking) by taking all the elements in the sequence, rolling them out one per cycle, and then speeding up by the amount of notes you want per-cycle. This means that even with sequences you can subdivide elements into any amount of elements. For example:

beatMode 4 "1 2 [3 6] 4 5" is roughly the same as "<1 2 [3 6] 4 5>*4"


Edit:

  • Made the explanation better and added more details.
6 Likes

Aha! That's super helpful, thanks.

Can they be combined? Or at least, the output of one system passed as input to the other, and vice-versa?

Yes! beatMode is actually an alias for toCycle which is a function that takes a Rational number and a Sequence and returns a Signal. Signals are what cycle-based patterns are called inside Tidal. So you can use sequences anywhere you would've used a good old pattern in Tidal.

2 Likes

@yaxu is my explanation right?

++ may i suggest that we should have some very short alias for beatMode for the UI. Maybe seq would look good:

d1 $ seq 8 $ note "0 3 6 7 3" # s "arpy"

d2 $ gain (seq 16 "1 0.9 0.8 1 0.9 0.5") # s "<808ht 808lt> 808cp"

I would also like to know how will randoms and waveforms be handled with sequences? Is that already covered?

2 Likes

Yes I don't think I can fault it!

Sequences can indeed be turned into signals, but not fully vice-versa as signals are infinite in length and sequences are not. You could write a function to sample a timespan (arc) within a signal as a sequence though.

Waveforms require continuous events, so can't be represented by sequences. You could do sample-and-hold of a signal waveform though.

The name for the beatMode function isn't settled, I just added that alias to see how thinking in terms of 'modes' rather than types felt. It seems to help..

Aesthetically, somehow I'm not keen on the name 'seq', not sure why..

Despite the work I've put in I'm still not completely sure whether all this is a good way forward, but to add to your explanation.. A list of the different features of signals and sequences:

Signals

  • cycle-oriented
  • function of time
  • infinite length
  • opaque/uncountable - because you can't look at the structure of a pattern outside of a query (i.e. without calling it with a particular timespan/arc)
  • structure-less

Sequences

  • beat-oriented
  • contiguous
  • silences (events without value) are part of the structure
  • finite length
  • countable
  • hierarchical tree structure - stacks/cats within stacks/cats..
2 Likes

Despite the work I've put in I'm still not completely sure whether all this is a good way forward

Have you written about what makes you not sure about this being a good way forward?

I'm personally looking forward to this a lot, ever since I read Thoughts about better support of sequences and beat-wise (rather than cycle-wise) operations in TidalCycles

I'm just really excited for that append function! Is it correct that it will be possible to elongate notes across boundaries? In contrast to e.g cat ["1", "_"]

1 Like

Yes! I was thinking about how conceptually segment and toCycle are similar in that they take some Pattern turn it into a Signal at an N rate. So I was wondering how they will tie in with each other while coding. Can you simply:

d1 $ beatMode 8 $ note "0 1 2" # gain rand ?

Also, how does this tie in with state values? The use case of a Signal defining rhythm while taking properties from some Sequence as Tidal does now from State values seems intriguing.

Just a small note relative to the rest of your comment: seq is already a pretty important function (used for forcing evaluation,) and (yet) also already defined multiple times in other important libraries. Best worth avoiding that function name for those reasons too.

1 Like

I’m eager to see Tidal have both Signals and Sequences. Tidal is currently great for Signal-like things, and certainly quite good for doing Sequence-like things, but there are things which are difficult (or at least unnatural) to do, or left unconsidered, without a formalization of Sequences.

Sequences may need a few minor versions, back and forth with the community, to really come into its own, but it strikes me as worthwhile. Keep on truckin’!

Might be a controversial take but beatMode IMO should be the default. Reason is: at any point in time you can use "[]" to trigger even distribution. The other way around is trickier to achieve.

In that sense there's literally no difference (pre-2.0) between these two:

-- Spreading 1-4 within a cycle
d1 $ n "1 2 3 4"

-- Spreading 1-4 within a beat which _happens_ to be a cycle long
d1 $ n "[1 2 3 4]"

Unless beatMode behaviour is defaulted - suddenly there's a meaningful difference between them and how lengths get assigned.

Alternatively, toggling between beat/cycle modes could be an option at boot.tidal level. Feels like such a determining workflow choice I don't imagine switching between them often.

1 Like

i searched it in hoogle filtering for tidal and couldn't find anything named exactly "seq" that's why i proposed it