RFC: Improving the interface for interacting with Tidal Streams

Hello, all! I've been experimentally building a Tidal editor (drawing inspiration from @polymorphic.engine's GSoC work) and thinking through how tidal (particularly the internal Stream.hs logic) could better integrate with external tools.

Here's a sketch of some things I'm interested in. I'd be willing to work on the development, but wanted to see which ideas other folks like or have critiques of first. Since some of these involve breaking changes, it would be good to coordinate a nice release, etc. Particularly interested in @ndr_brt's thoughts in terms of the Atom plugin, and @yaxu's thoughts how this could be informed by his remake experiments. But, I'd also love to hear any other thoughts, comments, requests, wild dreams that anyone else has!

A more specific type for pattern (etc) ids

Right now, patterns can be named by any Showable ID, which is very useful for numbering patterns, but gets weird for other things—for example, the ID of the pattern p "bev" $ s "cp" is not "bev", but rather "\"bev\"".

I've already implemented this, but then got distracted trying to figure out git rebase. Sorry about that. One cool result is that it would enable things like this:

-- Silence d1, d2 & d3
p [1..3] $ silence

Mutable targets

Most fields of the Stream object are already MVars, so should be easy to change the sCxs field to be an MVar without disrupting too much. This way, one target can come from BootTidal.hs, another can be added by an editor, others can be added/reconfigured during live performance, etc.

I think the tidiest interface would be something like this, resembling how patterns work:

startStream :: Config -> IO Stream

-- Add target to context list, open UDP port, etc
streamAddTarget :: Stream -> String id -> Target -> IO ()

-- Remove from context list and close UDP port, etc
streamRemoveTarget :: Stream -> String id -> IO ()

I think startTidal stay the same for backwards compatibility, but this would be a breaking change for anyone who's used startStream directly.

Mechanism for "subscribing" to stream changes

Speaking of MVars, one limitation is that there's not a good way to wait for an MVar to change if, for example, you want the editor to display changes to /ctrl values.

Ideally, State fields can be written to and read from, like now, but also would allow some sort of subscription where the first read gets the current value, and then all subsequent reads block a thread until the value changes. I think the closest thing is the "skip channel" described in the concurrent Haskell paper, but would love to hear if others know of existing mechanisms for achieving this.

1 Like

Sounds good!

Also sounds useful. We didn't get around to taking the old startTidal from the default BootTidal.hs yet, so I think breaking changes here are fine.

My only thought is that Stream.hs is probably the messiest part of Tidal. I think the large number of MVars is part of this, and it all needs refactoring really to allow for things like independent cps for different patterns to become implementable. So I'd say feel free to add this, but if you have the urge to tidy up/rewrite parts, don't hold back. Actually I have been pondering whether to restructure it all around the Link library by default (by linking with it, not using the external carabiner tool), but am short of time for the next couple of months..

I don't have other suggestions for this offhand.

1 Like

Yeah, I stepped into this issue when I was implementing the tidal-listener support inside the atom plugin: pattern id with double quotes · Issue #19 · tidalcycles/tidal-listener · GitHub

I'm pretty interested in all the 3 features you proposed, I have not a lot of ideas in how to do these (I'm an Haskell beginner). If I remember well you already opened a PR for the first one? Maybe it could be merged?

1 Like

Mechanism for "subscribing" to stream changes

Hate to pitch in without actually being any use but an (apparently) basic feature tidal lacks is adding callbacks to incoming osc messages. These could be as simple as a -> IO () functions.

One example: setting CPS files from external harware. One can get as far as sending the correct /ctrl message, but since setcps reads the recorded value at invocation time, any further messages won't cause tidal to update its own cps.

What should happen is for the user to declare setcps as a listener over a message name, but there's no easy way to achieve this AFAICT. Would be pretty useful for all kinds of integrations and frankly expand quite a bit tidal's integration capabilities

Good idea @ghales! I have reported it as a feature request in Extendable OSC controller · Issue #982 · tidalcycles/Tidal · GitHub

1 Like

Any updates on this? I just also wanted to point out it's similarity to this: OSC controller map similar to targets map · Issue #677 · tidalcycles/Tidal · GitHub