*extending* pattern_time's functions: help wanted :)

i’ve been messing around with a rewrite of pattern_time which cleans up its methods a bit and captures some of the things I’ve been baking into cheat codes 2. prompted by @okyeron’s ping and my own feeling of “stuckness”, i figured it’d be good to the WIP state to snag feedback + help cleaning it up and getting it wrapped :revolving_hearts:

this is not release-ready or for general use – expect dragons :slight_smile:

proposed scripting function changes

  • :rec_start() + :rec_stop() become :rec(state), where 1 is start and 0 is stop
  • :start() + :stop() become :play(state), where 1 is start and 0 is stop
  • :set_overdub() becomes :overdub(state), where 1 is on and 0 is off
  • add :loop(state), where 1 is looping playback and 0 is 1-shot playback

proposed scripting variable additions (useful for grid states)

  • .rec_state returns 1 (recording) or 0 (not recording)
  • .play_state returns 1 (playing) or 0 (not playing)
  • .overdub_state returns 1 (overdub enabled) or 0 (overdub disabled)
  • .loop_state returns 1 (looping playback) or 0 (1-shot playback)
  • .record_pending returns true or false, depending on sync record to clock? param
  • .start_pending returns true or false, depending on sync launch to clock? param
  • .name returns the name of the pattern (see param note)

proposed parameter menu additions

when adding a pattern to a script, we’ll use longPatternName = pattern_time.new("name"), where the “name” is passed to the new PATTERNS param menu.

if pattern time is added to a script, a PATTERNS param menu is created and inherits the names of the script’s patterns. if no name is provided *

  • sync record to clock? / *alias*_sync_recording: use the global clock to sync pattern recording to start on beat and record for a fixed number of beats
    • —> rec duration (beats) / *alias*_rec_duration: set the number of beats that the clock-synced recording should last (up to 256 beats)
  • sync launch to clock? / *alias*_sync_start: wait until a clock event before launching pattern playback
    • —> timing / *alias*_launch_timing: wait till next beat or next bar to launch pattern playback?
  • quantization / *alias*_quantization: turn quantization on/off

quantization methodology

passive stuff, all working in the background
as a recording is being made (:rec_event), each entry commits the .time between itself and the event before it. after this is calculated, that duration is divided by clock.get_beat_sec() to give us .time_beats.

after the recording is committed, :calculate_quantum is automatically called, which rounds the .time_beats to the nearest eighth note value. this is committed as an entry’s .quantum.

as the pattern plays back, it is either using the .time or .quantum to determine when to move to the next event (using either :next_event or :quantized_advance).

active stuff
if :quantize(1) is executed, then the metro that drives unquantized playback stops and the clock-synced :quantized_advanced function takes over.

what happens is basically a “runner” system – since our quantum is measured in 1/8th notes, we total how many 1/8th steps an event should last for and a runner keeps apace at an 1/8th note count – so if an event’s duration is 3 quantum, the runner will have to reach 3 before moving onto the next event in the pattern.

what’s wonky?

most of this feels really solid, but i would love some eyes on the quantization stuff. in the above repo, i’ve included a revised copy of earthsea that uses the proposed changes. on the grid: 1,1 is record start; 1,2 is playback; 1,3 is overdub; 1,7 is loop enable; 1,8 is clear.

  • if you keep maiden open while recording a pattern, you’ll see a printout of the calculated quantum and time total. oddly, when clamping recording to 8 beats at 120 bpm, you’ll see a record time total of 4.0003619194031 (which is passable) and a quantum total: 8.375. could use some help debugging this.
  • quantization also just doesn’t feel right for earthsea, where expressive + dynamic timing is much more critical. 1/16th notes felt ineffective, 1/4 notes a bore, but 1/8th doesn’t 100% gel either…
  • things also get wiggly when overdubbing comes into play, since note-ons and note-offs can become confused. i can’t tell how much of this could be mitigated in pattern_time and how much should be handled by the script.

also, none of what i’m sharing has to be the baseline – if someone can approach things from a totally different angle with more desirable results, that’d be super! i just could use some extra hands + brains on this challenge and figured sharing some work might help :slight_smile:

10 Likes

ok… as a pattern noob, questions:

  • what is actually stored when recording a “pattern”? an event table? Can this be anything?

  • what might happen when mutiple events happen at the same time? (say maybe a chord being played, or notes and controller data at the same time)

  • would multiple patterns be possible running at the same time (synced or not)?


as for the new stuff above - this looks really good so far.

Can patterns be saved? If not, then this would be a very good addition. So then… :thinking: How the heck do you go about storing a big table?

Would a function to return the length of the pattern be helpful (I think it might)?

Can the beat be rounded to other values besides an eighth note? Maybe one pattern I’d want to quantize at 1/16th notes and another at 1/4notes.

Presuming it can handle the quantization of both starts and stops, should

be .play_pending?

1 Like

I wanna help!

My first thought is to add the parameterized stop/play/loop/overdub functions rather than remove the old ones. This would preserve compatibility with older scripts. I’m also a fan of the existing method names, but see having dynamic control with a variable would be helpful.

1 Like

Or we could make the old function names act as shortcuts to be deprecated later. I.e. function rec_start() rec(1) end . I agree compatibility to old scripts is important, but func(state) seems to be more in line with the softcut api.

I’m taking a look through the example now.

4 Likes

I need to spend some more time with the quantization stuff before I can give any suggestions on it. I left some proposed changes on code readability for now.

I was thinking you could use the new observable library to track the pattern param menu stuff. It would probably make managing multiple patterns param items easier, and make it super easy to rename them or something like that.

More thoughts:

Just brainstorming ways to simplify this lib, right now pattern_time is pretty meaty.

What if pattern_time was only in charge of managing existing patterns, and playback/quantization/clock syncing.

And pattern would be a table of (pattern_event, duration) in charge of recording/overdubbing events.

Then a pattern_event could be anything.

This is probably already close to how it works, so maybe this is just Object Oriented thinking taking over. It just seems like there are a lot of timing variables being stored in this one pattern thing at the moment.

*A side note, since I’m still looking at the code, it seems like pattern params won’t be able to be deleted inside a script at the moment.

1 Like

love the energy around this, thanks all!

thoughts on pattern_time general q's

patterns are just massive tables, yeah – in your script, you define what you want committed in a table and you pass that table to the :watch method. pattern_time then commits a relative timestamp to that event and uses the timestamp to pass changes to a metro which plays through the event queue.

multiple events are just lined up in the queue with an imperceptible amount of time between them (metro can get pretty low).

yeah, for sure! each pattern consumes a metro, so you could feasibly have 30+ patterns running at once.

thoughts on proposed change q's

for sure – i think you could just save the whole table with one fell swoop using tab.save and a bit of setup for path.

good q – there are vars in place for start/end points (.start_point and .end_point) as well as total count (.count). you can also query current step (.step). the advancing functions are all aware of these variables, so it’d be easy to wrap adjusting those variables into their own functions :slight_smile:

that’s a great goal for our release candidate! it should be trivial, but i’m overall not loving how quantization with 1/8ths works – excited by all the energy here, i’m sure we’ll figure it out!

starts, yes. stops, not yet. good idea!

10000%

yeah, what i posted is just my own fever dream of trying to wrangle this – i’m for sure not an experienced programmer, just…persistent / hard-headed when it comes to a challenge. maybe it might make sense to just start fresh with some agreed-upon requirements?

from my perspective, a golden version of pattern recording in norns would include:

  • a totally free, asynchronous recording mode
  • a totally free, asynchronous playback mode
  • a clocked recording mode
  • a clocked playback mode
  • overdub
  • overwrite
  • clock-driven quantization with variable resolutions (up to 1/32 including triplets?)
    • swing
  • clock-aware “timestretch” of an unquantized pattern (if I record a shambling pattern at 120bpm, I’d like to be able to change my bpm and have the pattern events keep relative timing but play through faster/slower)
  • an easy way to edit timing per step
  • 1-shot or loop mode
  • loop windowing (change loop points)
  • pattern copy/paste (duplicate a pattern and overwrite variations OR to double the length of a pattern to add variations to the extended section)
  • pattern save/recall
  • a “what the hell, why not” would be pattern shuffling (randomize all the recorded events) :wink:
2 Likes

overall i think this design looks great : )

a subtle suggestion if you want to cut down on members is ditching ‘rec_state’ et. al. & sending a return value back through ‘:rec()’ etc when it’s used argument-less. that really just comes down to taste though (that or ‘:get_rec()’ feel a little more standard to me in the larger world of APIs than ‘_state’, but this is nit picky)

this doesn’t need to be a 1.0 thing but eventually the param menu (haven’t looked at the code yet so not sure how it’s initialized) could take some args that add extra bits like pattern saving / recall or leave out the quantization if it’s not needed etc etc

i’ll try this out & help out if i’ve got some time when my life is put back together ! this will be super duper handy to have around

2 Likes

I’m putting together some thoughts on this too! I think the underlying ‘table of {time, usertable}’ storage is solid, it’s really just a matter of what API you want into it. @dan_derks the list of functionality you present is a great outline toward it!

There’s a bunch of details about quantized pattern recording i remember from making mlrv that i’d love to help articulate & capture in this library.

Might even be another great library to capture on crow!

2 Likes

I guess I’m trying to make the argument that, a clocked recording mode isn’t needed in order to have clocked playback. If pattern time has a clock, :rec/:play can just sync on any state change if syncing is turned on.

1 Like

i’m having a hard time trying to imagine how on earth this would work with two cv inputs so color me intrigued

If an event had arbitrary data, and the pattern a way to handle it, pretty much anything is possible. For example, if you had an input into crow, and you made an event every time the voltage changed (the voltage would be the data). You would have the voltage at each point in time from when the recording started. Then when you play back the pattern, those voltages could be sent or modified with the right timing to any of the outputs.

@dan_derks, @Galapagoose I’ve been kind of throwing together my ideas in a fork of dan’s repo. I haven’t tested any of it yet, but I think it should work atleast abstractly. I tried to model it closely to Softcut, because the core idea seems identical to me. I don’t think it’s perfect but it might help to further this development. I kept all of the “golden” features in mind.

4 Likes