gibber-cc / tidal.pegjs

A parsing expression grammar for the TidalCycles pattern language.
47 stars 3 forks source link

query with non zero start is wrong #7

Open felixroos opened 2 years ago

felixroos commented 2 years ago

Hello, first of all, thanks for this fantastic port of tidal syntax to JS! I am currently implementing a little live coding playground with it, and realized that when I create a pattern and start querying from a non zero start, the result has the wrong phase.

This works:

const values = (e) => e.value;
const pattern = Pattern('[A <B C>]')
expect(pattern.query(0, 1).map(values)).toEqual(['A', 'B']);
expect(pattern.query(1, 1).map(values)).toEqual(['A', 'C']);

This does not work

const values = (e) => e.value;
const pattern = Pattern('[A <B C>]')
// expect(pattern.query(0, 1).map(values)).toEqual(['A', 'B']);
expect(pattern.query(1, 1).map(values)).toEqual(['A', 'C']); // yields ['A', 'B']

I think I can work around the problem, but it would be much easier if query had no side effects. FYI I also wrote a little post about this lib.

charlieroberts commented 2 years ago

hi, thanks for the great post! I tweeted it out if you care about such things :)

Yes, unfortunately I think this is due to the query engine not using pure functions of time, and expecting (at least in the case of a oneshot aka <>) the query to be called sequentially in order to advance internal phase.

I think the PEG itself is fine, but the query engine definitely needs work! @yaxu is also interested in getting this to work correctly, potentially in a new repo / project. Maybe we could start with the existing PEG and then correct how the AST is queried? In the meantime I'll try to see if there's a simple fix for the existing query function in regards to onesteps...

yaxu commented 2 years ago

Yes I plan to look at this later this week. I had a quick look last night and I think progress will be fast based on my recent experience with python.

I'd like to standardise on naming things though.. I'd say as it stands, tidal.pegjs isn't really implementing tidal patterns, but its mininotation for sequences. In tidal land, patterns are much more about transforming things than sequencing them. To my mind, the 'query engine' and its behaviour is really core to what tidal is, and the mininotation is a convenient shorthand. The mininotation is based on the bol processor's polymetric expressions.

Anyway just flagging that up for future discussion..

felixroos commented 2 years ago

Hey, nice to have you two in here!

I think the PEG itself is fine [...] Maybe we could start with the existing PEG and then correct how the AST is queried

The problem is that the onestep query count set to 0 by default (See https://github.com/gibber-cc/tidal.pegjs/pull/8).

I'd like to standardise on naming things though

It would also be nice to have the AST nodes named according to the equivalent tidal function.

In a more general sense I am heavily interested in getting more mini notation features working (like "@"). Maybe it would be practical to have a support table for this module.

I am also playing with the thought of implementing this grammar in nearley but that would take some time as I have no skills in writing grammars so far..

For the querying part, you might be interested in how I approached event rendering from rhythm trees, which I developed before I started digging into the tidal world of things. But maybe those ideas are already second nature for you :)

felixroos commented 2 years ago

After some mindbending, I was able to find a general "formula" which calculates the branch index of any onestep node's children for any query index. This can be used for a stateless / side effect free implementation. I documented my findings in this post.

I think the problem can also be solved in a different way by expressing onestep with "slow", which I think is the way tidal is doing it. Correct me if I'm wrong, but I think tidals angle brackets are just syntactic sugar for [ ... ]/n where n is the number of elements in the brackets. In contrast, the AST of tidal.pegjs renders "/" using onestep nodes and rests.

I am already messing around with my own implementation of the query, having worked out stack (= layer), cat (= group) and onestep (=cat + slow). My goal is to have an implementation that allows adding "plugins" which act as AST transformers, based on unifiedjs. With this, I want to implement plugins like automated chord voicings, grooves or adaptive just intonation....

charlieroberts commented 2 years ago

Hi Felix, looking forward to digging through your post! The problem with expression onestep with slow is that slow doesn't work very well in my query engine :) @yaxu has made some progress on a new query engine that you might be interested in... https://github.com/yaxu/strudel

yaxu commented 2 years ago

I think the problem can also be solved in a different way by expressing onestep with "slow", which I think is the way tidal is doing it. Correct me if I'm wrong, but I think tidals angle brackets are just syntactic sugar for [ ... ]/n

Yes that's right. Otherwise it wouldn't make sense to do e.g. "[a b c]*2.25". Tidal doesn't really deal with steps.

I'm still working on the basics of strudel but there is probably enough there already to hook it up!

felixroos commented 2 years ago

I'm still working on the basics of strudel but there is probably enough there already to hook it up!

Nice to have a basic implementation of tidals ideas that can be understood by my javascript brain! I guess I'll need some time to understand the why behind the what.. do you have any non code descriptions of those ideas in more detail? I already read the tidal docs + watched some of the tidal courses, but it doesn't really shed light on the essence of how everything fits together internally.

Also, are there any communication channels (besides git commits) I can follow to stay up to date with this?

Anyway thanks for all of this!!!

yaxu commented 2 years ago

Hi @felixroos, this is a new project but I just created a #strudel channel on the tidalcycles discord, if you use that: https://discord.gg/74erNDRfbB

It's based on this similar python project, so you might find some insights from the #vortex channel too. https://github.com/tidalcycles/vortex

(some years ago google used to translate "apfelstrudel" to "apple vortex", which I always found funny)

In brief, patterns are 'queries', which are essentially functions from time to values. To support discrete events with a beginning and ending, they are functions from timespans, to values with timespans. Values with timespans are known as 'events' (or in strudel because the classname 'Event' is taken, 'haps'). Because the events that are returned can be a fragment of a larger event (due to some transformation, or because they are active only for part of the queried timespan), they actually have two timespans, one for the fragment (the 'part') and one for the original event (the 'whole'). Some values are continuous rather than discrete, in which case they don't have a 'whole'.

Then there's stuff to allow these patterns to be manipulated and combined in different ways, despite being functions rather than data structures. 'fmap' on a pattern returns a new pattern with a function applied to all values returned from the original. 'app' allows you to take a pattern of functions, and a pattern of values, and apply the values to the functions to create a new pattern of values. 'join' turns a pattern of patterns of values, and flattens it into a pattern of values (useful for making transformations that work with more than one pattern in different ways, like 'fast' which takes a pattern of time factors as well as the pattern to speed up).

It's probably not very idiomatic javascript, actually I don't have any real experience with javascript before now.. The main thing is that almost everything is 'pure' in that the functions return a new pattern or whatever, rather than manipulate an existing pattern.