I’m working on the TidalCycles project for making music with Haskell, and trying to rejig things so they’re more flexible.
Tidal is all about representing musical patterns that can be combined in a variety of ways. There are two instances of a Pattern
class, broadly Signal
for representing patterns as functions of time (in a FRP manner), and Sequence
for representing them as lists of events with durations.
There are many ways of combining patterns, particularly with sequences. You can align two sequences in a variety of ways (currently: justifyleft, justifyright, justifyboth, expand, truncateleft, truncateright, truncaterepeat, rep, centre, squeezein, squeezeout
), and after that combine them with different strategies that preserve the structure from either the left- or righthand sequence.
However, Haskell seems to work against the idea that there can be different ways of combining things, where a monad only has one bind. If you want different behaviour you have to define a whole different type (as with ZipList
). This approach seems too unwieldy for a code interface for making music.
I’m trying to work around this by adding extra fields to the datatypes that say how a pattern should be combined with another. The >>=
bind can then use this information for deciding how to combine two sequences (or signals), and the <*>
can use it for deciding how sequences should be aligned before being combined.
With this I can e.g. do the equivalent of (rep [1,2,3]) + [10 20]
to repeat both sides to the lowest common multiple [11, 22,13, 21,12, 23]
, or e.g. (expand [1,2,3]) + [10 20]
to expand the shortest sequence to the duration of the longest, which results in fragments of events (which is a bit hard to visualise in text).
Hopefully this isn’t breaking too many monad laws…
Anyway I am a self-taught Haskell programmer and would really appreciate some feedback on this approach. I think this should be a common problem, are there common solutions?