Creating a pattern from note event offsets (aka. note_off events)

TL;DR: Is there a function to shift right each note of a pattern by its own duration? (similar to rot (-1), but not cycle-bound)

Context: I've been trying out jarmlib for working with DAWs (Bitwig). I quickly noticed MIDI events just ring forever once hit. This is because the MIDI spec expects two events for each note, indicating when it starts (note_on) and when it ends (note_off), whereas tidal only really sends the note_on OSC message.

note_off events can be created for this target, and is pretty simple: just sending a new note with gain 0 actually kills the note if currently ringing. I came up with a workaround: ~> the pattern by an offset, set the gain of every note to zero and stack with the original pattern. Something like:

let note_length = 0.5
in p "daw1" $ stack [pat, (~> note_length) pat # gain 0]

This works, but it forces the same length for every midi event, when the wonderful thing of tidal is to manipulate aspects of notes including duration etc. This is the bit I'm stuck on right now, and I appreciate any advice. I tried other solutions in the forum, like legato but they usually requires the client to support some additional feature which in this case is not an option.

Interesting, I would have thought tidalcycles would generate a note off when going to the next token in the sequence.

In pure data there's an object that keeps track of voice allocation [ poly ] , but there might be a supercollider specific version. The idea here is to allocate on first use of note to a voice. The next dissimilar incoming note gets allocated to the next voice. It round robins till any next dissimilar note will voice steal the first non free voice.

If any incoming note has a voice allocated in the voice array, then that note must be a velocity 0 note (i.e. a note off) since keyboards only have 1 key per note. Though in reality, iirc [ poly ] uses a tuple of < note,velocity > where vel != 0 for initial index, and <note,0> will zero every instance of <note, *> out. Or that could've been my implementation when i was trying this out.

So one way of doing this would be when a new note is sent in osc, a corresponding note off is generated on the receiving end as well. Not sure how tidalcycles deals with "~" though.

Nice, interesting to see how pd~ does it. And yes, I think it's a design choice for TidalCycles not to keep track of this

The stack I'm exploring uses DrivenByMoss' OSC plugin, which does not support a length / duration / sustain parameter for incoming notes. Adding such a parameter to the receiving end requires changing both the local stream and the plugin itself. In this case it seems simpler to just calculate this via Tidal since it already knows the length of each note.

Tidal does send note offs when sending midi via superdirt. It wouldn't be too hard to write a new Haskell function to turn event offsets into zero-width events, but it doesn't exist in Tidal at the moment. Maybe you could ask the bitwig people to support durations.

1 Like

I'm also using bitwig with tidalcycles.
I still haven't used jarmlib osc integrations yet (but I definitely plan to do it!)
Currently I'm sending midi messages through superdirt and it works well. Maybe you can send notes through superdirt and other messages relevant to bitwig through osc?

Anyway, I'm really into making better bitwig-tidal integration. I'm thinking about doing something like hack your daw (HackYourDaw (Tidal+Ableton)) but for bitwig.

BTW, to sync bitwig and tidal I use @Zalastax branch GitHub - tidalcycles/Tidal at ableton-link that integrates ableton link with tidal and it works great!

2 Likes

@yaxu

It wouldn't be too hard to write a new Haskell function to turn event offsets into zero-width events

Nice! I'm interested in writing such a function but unsure where to begin. I haven't messed with event lengths / Arcs before. Would you mind pointing to a reference?

Tidal does send note offs when sending midi via superdirt

Currently I'm sending midi messages through superdirt

This makes sense, but I'm exploring setups which bypass SuperDirt/SuperCollider/Jack altogether. For that effect, it would be interesting to not rely on superdirt for this kind of midi manipulation unless there's no alternative.

@ShairR I'll check the resources you mentioned, they look nice!!
Thank you guys so much for the replies :slight_smile:

2 Likes

I'm exploring setups which bypass SuperDirt/SuperCollider/Jack altogether

Bypassing supercollider will be great. This is what hack your daw does for ableton.

I think that the best thing will be to stop using midi and do all the communication via osc. Midi has some limitations that block you from using some cool tidal features. For example, you can't do "jux rev" because you have only one control channel for panning and not per note. (BTW not all synths supports this kind of polyphony but all the bitwig devices does).
Not sure how to achieve that in bitwig (maybe creating a note grid device that parses the messeges from tidal and output notes)

1 Like

Just for future reference: this seems to be the case for other tidal target candidates, such as renoise (thread attached). The repo's README states the same issue, and could be similarly solved

Here finally is an example that sends both noteon and noteoff messages:

import Data.Maybe (fromJust)

eventHasOffset :: Event a -> Bool
eventHasOffset e | isAnalog e = False
                 | otherwise = stop (fromJust $ whole e) == stop (part e)

offsets :: Pattern a -> Pattern a
offsets pat = withEvent f $ filterEvents eventHasOffset pat
  where f (e@(Event _ Nothing _ _)) = e -- ignore analog events
        f (Event c (Just (Arc b e)) _ v) = Event c (Just a) a v
           where a = Arc e (e + (e - b))

cmd = pS "cmd"

noteonoff pat = stack [pat # cmd "noteon", offsets pat # cmd "noteoff"]

d1 $ noteonoff (note "a b")

However if you e.g. silence the pattern mid note the noteoff would never get sent. I think it would be better to do some pre-processing on the max side to schedule the note off based on the delta.