put oled stuff in redraw(). if you don’t put it in redraw() it will splatter over the menu screen when you go to the menu.

don’t put non-oled stuff in redraw(). or more generally, anything you want to keep happening while in menu mode

2 Likes

make a grid redraw function on a metro, use dirty flags. this pattern is demonstrated in many scripts.

Thanks!

Was tired, apparently looking at the wrong things, and didn’t find a simple reference to remind myself how to do this.

1 Like

Are there any gentle introductions to using lua/lib/pattern_time.lua for pattern recording? I’m interested in implementing a recorder in one of my scripts, but studying examples makes me feel like it’s still above my head somewhat. No worries if there ain’t…happy to keep studying :sweat_smile:

1 Like

i haven’t used it personally, but it seems rasonably straightforward. i’m looking at earthsea as an example.

each pattern maintains:

  • a list of time-deltas,
  • a matching list of “events”, each of which can be any table,
  • a metro for playback
  • a process function which is called for each event on playback.

e.g. in the case of earthsea, each event includes fields x, y, id, which correspond to grid position and note ID, but these could just as easily be midi events with (msg, channel, data1, data2) or whatever.

usage goes like:

  • p = pattern_time.new makes a new pattern
  • p.process = function(event)...end - set the event handler function
  • p:watch(event) - record the event and its timing, but only if p is in “recording” state
  • p:start(), :stop(), :rec_start(), :rec_stop() - manipulate state
  • p.rec, p.play - check state flags
7 Likes

As always, :bowing_man: This is really approachable.

1 Like

also! in case it helps, here’s a quick earthsea-style annotated study that i meant to write up months ago…

pattern_time study
-- pattern_time study

pattern_time = require 'pattern_time' -- use the pattern_time lib in this script

function init()
  grid_pattern = pattern_time.new() -- establish a pattern recorder
  grid_pattern.process = grid_pattern_execute -- assign the function to be executed when the pattern plays back
  grid_redraw()
end

g = grid.connect() -- hardware: grid connect

g.key = function(x,y,z) -- hardware: grid event (eg 'what happens when a button is pressed')
  if z == 1 and y == 8 and x == 1 then -- the bottom left key is the pattern control key, if i press it...
    if grid_pattern.rec == 1 then -- if we're recording...
      grid_pattern:rec_stop() -- stop recording
      grid_pattern:start() -- start playing
    elseif grid_pattern.count == 0 then -- otherwise, if there are no events recorded..
      grid_pattern:rec_start() -- start recording
    elseif grid_pattern.play == 1 then -- if we're playing...
      grid_pattern:stop() -- stop playing
    else -- if by this point, we're not playing...
      grid_pattern:start() -- start playing
    end
  elseif z == 1 and y == 8 and x == 2 then -- the key to the right of the pattern control key...
    grid_pattern:rec_stop() -- stops recording
    grid_pattern:stop() -- stops playback
    grid_pattern:clear() -- clears the pattern
    g:all(0) -- clear the grid
  end
  if z == 1 and y ~= 8 then -- if we press any key above the bottom row...
    record_this = {} -- create a table called "record_this" to put our events!
    record_this.x = x -- here's an event, the key's x position
    record_this.y = y -- here's another event, the key's y position
    grid_pattern:watch(record_this) -- tell the pattern recorder to watch these events + commit them to memory
    g:all(0)
    g:led(x,y,z*15)
  end
  grid_redraw()
  g:refresh()
end

function grid_pattern_execute(recorded) -- when the pattern plays back, do the following with each entry we recorded in 31-35
  g:all(0)
  g:led(recorded.x,recorded.y,15) -- remember "record_this.x" and "record_this.y"? here, we're using that data and doing something with it!
  grid_redraw()
  g:refresh()
end

function grid_redraw()
  if grid_pattern.rec == 1 then -- if we're recording...
    g:led(1,8,10) -- medium-high brightness
  elseif grid_pattern.play == 1 then -- if we're playing...
    g:led(1,8,15) -- highest brightness
  elseif grid_pattern.play == 0 and grid_pattern.count > 0 then -- if we're not playing, but the pattern isn't empty...
    g:led(1,8,5) -- lower brightness
  else -- otherwise, if we're not recording/playing and the pattern is empty...
    g:led(1,8,3) -- lowest brightness
  end
  g:refresh()
end

gist, for convenience

8 Likes

I’m afraid I lack a bit of music theory knowledge, so maybe this question makes no sense, but if I want to make a sequencer, let’s say 16 steps for now to easily map to a grid, what would be the best way to handle sub-steps like ratchets or delays?
Again assuming a grid I’d like to use a column for these sub-steps, so that would mean subdivide the 16 steps by 8, so we get 128 (sub)steps total and then simply use the position within these 128 (sub)steps to trigger? Or would that not make musical sense because you don’t want fixed sub-steps for ratchets (but want increasingly faster ones, let’s say 1/16, 1/20, 1/24, 1/32, 1/40, etc)?

And what would make sense for storing this info in the context of norns, keeping in mind that this should be easily translatable to something that can be shown on a grid. A single 128 size array? A 16 size array containing 8 size arrays?
Does anyone have any suggestions/ideas?

Keep in mind that the way you represent your sequence data will be intimately tied to the overall sequencer clocking. I suggest that you look into standard clocking approaches such as 24 PPQN (6 sub steps per step), 48 PPQN (12 sub steps per step), 96 PPQN etc. Of course nothing forces you to do it this way, but those are good starting points. Since you want to implement ratchets and delays, you need to make sure there is a consistent time interval between two of you smallest “sub steps”, and that’s the timing that your main clock/timer/metro will be using. Using the standard numbers from above, 8 or 16 sub steps per step is closer to triplets etc.

In terms of storing your data, a flat array, or nested arrays, are two approaches that make sense. Depending on which one you choose, some operations will be easier to code, and other less so. It’s tradeoffs. But neither approach is inherently bad. I suggest you pick one and move forward, and depending on the specific features of your sequencer, you will realize over time which tradeoff makes more sense. And finally, keep in mind that there are no rules and just suggestions. Experiment!

1 Like

have you tried paging? for example, parc has a separate retrigger page for each of the 16 steps, each step can be divided and repeated for up to 7 times. depending on your requirements you can have multiple pages for delays or anything else too.

Thanks for the pointer. Just to check that I understand it correctly, do you mean a page that shows the retriggers for all steps or a retrigger page per step?

Maybe I should’ve worded my question clearer, but pages that show some property like retrigger, delay, etc for all steps (like how Kria works) were sort of implied for me. Pages seem pretty much the only way to do this I think.

The former. In parc, each ‘channel’ has 4 pages - notes, retriggers, velocity, control. So each column in a page has a value (randomly chosen from toggled buttons) from 1 to 7, which maps to note, number of retriggers per step, etc.

So in your case, a ‘lengths’ page and ‘number of repeats’ page might make sense, if you want separate control over note length and ratchets.

1 Like

I think I’ve found a combination that works for me. Using 24ppqn, a flat array consisting of 96 ticks which are iterated through on every clock cycle and then the step position is derived from there.
I tried an array of arrays but didn’t really like it. It also felt like I was storing the data that way because of how it’ll be presented which I don’t think is a good idea.
The pages to show the repeats/delays (basically the 6 pulses for a 16th note) are also working well.
We’ll see how well this holds up and if I’ll still like it after some more work on it :slight_smile:

Thanks for the help @Artaos and @artfwo!

Not sure where to ask clock related questions.

I have a script that draws in time to a clock. Previously while using beatclock, this worked without an issue. When trying to update to using the new clock, I’m seeing the drawing calls happening even when hitting k1 to get to the main menu. I can post a video if that helps, but heres s simplified script:

function init()
  clock.run(draw)
end

x = 1

function draw()
  while true do
    clock.sync(1/4)
    screen.clear()
    screen.move(0,40)
    screen.level(15)
    screen.text(x)
    screen.update()
  
    x = x + 1
  end
end

If you run this script you’ll continue to see the text even after hitting k1 to get to the main menu. The main menu is still navigable, but difficult to see what you’re doing.

Is this expected behavior? Should I move my in-time screen drawing code back to beatclock?

Thanks.

The actual code that calls screen functions must be wrapped in a function called redraw. This allows the menu to disable and re enable it

Thanks for the quick reply. I put the code in draw instead of redraw because when it’s in redraw I get the error below. Any suggestions?

attempt to yield from outside a coroutine
stack traceback:
	[C]: in function 'coroutine.yield'
	/home/we/norns/lua/core/clock.lua:58: in function 'core/clock.sync'
	/home/we/dust/code/carter/rain/clock_draw.lua:11: in function 'redraw'
	/home/we/norns/lua/core/menu.lua:158: in field 'set_mode'
	/home/we/norns/lua/core/menu.lua:92: in field 'init_done'
	/home/we/norns/lua/core/engine.lua:92: in function </home/we/norns/lua/core/engine.lua:89>
1 Like

Maybe something like this instead?

function init()
  clock.run(bang)
end

x = 1

function bang()
  while true do
    clock.sync(1/4)
    x = x + 1
    redraw()
  end
end

function redraw()
  screen.clear()
  screen.move(0,40)
  screen.level(15)
  screen.text(x)
  screen.update()
end

Just implemented global clock in a script of mine and that approach seems to work ok.

6 Likes

Yes! That works. Thank you!

1 Like

Since the latest parameters update, I’ve been leaning heavily into using params for state management in my scripts. Before I paint myself into a corner, does anyone know if there are any performance implications to reading and writing to params compared to using local variables? I’m assuming it’s all in memory anyway and the only overhead I’m adding is some string testing when reading and writing.

1 Like

heavy use of param is perfectly good!

now with groups (and hidden params) your menu should also be able to stay sane

3 Likes