How to "compile" a `.tidal` file/project from a specified `ControlPattern -> IO ()` entry point?

Hi folks!

I love Tidal's Pattern abstraction!

However, I'm less interested in "live coding", and more interested in composing large, declarative, generative systems that I can keep coming back to and working on over time.

As a result, I'd love to be able to break my Tidal projects up into functions (perhaps even separate modules) to make projects easier to maintain and errors a little clearer, rather than trying to build a single giant complex expression with no whitespace in between and no modularity.

If I do break my Tidal project up into functions, it seems I need to manually submit each function in order of dependency to the interpreter before I can finally submit the ControlPattern -> IO () function which updates the stream.

What I'd love to be able to do is hover over the ControlPattern -> IO () function in my editor and execute some command to "compile" the function as if it were an entry point to my tidal project, and have it automatically compile all necessary types and functions before finally updating the stream.

I'm still quite new to Haskell and especially GHCi, so there might be an obvious approach that I'm missing here! Any thoughts or advice appreciated :pray:

Re: BootTidal.hs

I've noticed that some folk tend to recommend moving the stuff you want to reuse into your BootTidal.hs. However, there appears to be a severe tradeoff where, if we want to go and work on those functions again while working on some Tidal project, we need to reboot the entirety of Tidal (specifically, the scheduler) which interrupts the interactive flow while working on a composition.

2 Likes

Welcome! Good questions :100:

When running Tidal in your editor, your commands are sent to an instance of GHCI - the Haskell REPL.

When you boot Tidal, the editor launches ghci:

ghci -XOverloadedStrings

and evaluates all of the code in BootTidal.hs. When you evaluate a line in the editor, that line is sent to GHCI unprocessed.

To evaluate external code, :load / :l can be used. Keep in mind though that everything is lost when you do that - see haskell - How can I persist the environment between GHCi reloads? - Stack Overflow. This would also break the flow.

I believe your idea is not easy to implement with the tools we have available today. But I think it's a good use case and we should as a community think about how to address it. One idea that I have contemplated is to implement Jupyter Notebook style editing.

d1, p etc. are defined in BootTidal.hs. Behind the scenes:

If your problem is merely sequencing, the ur function might be what you're looking for. There's also some set function, I think, that lets you set which cycle you're on.

In an ideal world you could run

do $ setPlayhead 0 -- or whatever it's called
     runMyProject

and always have the same effect. In practice I'm not sure whether there might be a little bit of lag between those two commands, or even whether they'll happen in the same order. If that's a problem you could start at cycle -1 and then just edit the resulting file (after you record it, say, in your DAW).

If you want to load different functions for different projects, you can add your own modules under Sound.Tidal. You could have Sound/Tidal/Project_1.hs, which you load with import Sound.Tidal.Project_1 when you're working on Project 1. That won't cause you to lose any definitions already loaded the way :l does.

1 Like