Link search Menu Expand Document

reflection

description

Record clock-synced changes to data over time, with variable-rate playback, overdubbing, and pattern management tools.

The basic architecture of the reflection library includes:

  • a watch method, which ingests a table of data and assigns it a beat-timestamp for future playback

  • a process function, which parses the recorded data into meaningful action within a script

functions

The following assumes a script has invoked reflection via:

reflection = require 'reflection'
Syntax Description
my_pattern = reflection.new() Assign a variable to record and play back a pattern
my_pattern.process = name_of_function A script-defined function which parses the recorded pattern data
my_pattern:watch({events}) Commits a table of events to the pattern, if recording is enabled
my_pattern:set_rec(rec_state, duration, beat_sync) Enable / disable record head. rec_state is 1 for recording, 2 for queued recording or 0 for not recording. duration (optional) is duration in beats for recording. beat_sync (optional) is a beat value to sync recording start.
my_pattern:start(beat_sync, offset) Start playback. beat_sync (optional) is a beat value to sync playback start. offset (optional) will be added to the beat_sync value.
my_pattern:stop() Stop playback.
my_pattern:set_loop(loop) Enable (1) / disable (0) looping
my_pattern:set_quantization(q) Set quantization of pattern data playback, defaults to 1/48
my_pattern:set_length(beats) Set length of pattern
my_pattern:undo() Undo previous overdub
my_pattern:clear() Clear recorded pattern data
my_pattern:save(filepath) Save a pattern to disk
my_pattern:load(filepath) Read a pattern from disk

variables

Syntax Description
my_pattern.rec Returns the current record state (1 or 0)
my_pattern.play Returns the current play state (1 or 0)
my_pattern.event Returns a table of the recorded event data
my_pattern.count Returns the total event count
my_pattern.step Returns the current pattern step
my_pattern.loop Returns the current loop state (1 or 0)
my_pattern.quantize Returns the pattern data playback quantization value

user script callbacks

Syntax Description
my_pattern.start_callback Executes whenever the pattern starts
my_pattern.step_callback Executes with every pattern step
my_pattern.end_of_loop_callback Executes at the end of every pattern loop
my_pattern.end_of_rec_callback Executes when recording has finished
my_pattern.end_callback Executes when the pattern stops

example

-- reflection scripting example

_r = require 'reflection' -- import the library
my_pattern = _r.new() -- make a pattern

g = grid.connect() -- connect a grid

function init()
  lit = {} -- lit keys for grid presses
  my_pattern.process = process_press -- the process which the pattern will execute upon playback
  initialize_parameters() -- init params
  grid_redraw() -- redraw the connected grid
end

function my_pattern.start_callback() -- user-script callback
  print('playback started', clock.get_beats())
  playback_queued = false
  grid_redraw()
end

function my_pattern.end_of_rec_callback() -- user-script callback
  print('recording finished', clock.get_beats())
  grid_redraw()
end

function my_pattern.end_of_loop_callback() -- user-script callback
  print('loop ended', clock.get_beats())
  grid_redraw()
end

function my_pattern.end_callback() -- user-script callback
  print('playback ended')
  if my_pattern.loop == 0 then
    overdubbing = false
  end
  lit = {}
  grid_redraw()
end

-- bottom-left grid key: initialize recording / playback
-- above that: loop toggle
-- above that: overdub toggle
function g.key(x,y,z)
  if x == 1 and y == g.rows then
    if z == 1 then
      if my_pattern.rec == 0 and my_pattern.queued_rec == nil and my_pattern.count == 0 then
        my_pattern:set_rec(hold_rec and 2 or 1, record_duration > 0 and record_duration or nil, rec_sync_value)
      elseif my_pattern.count > 0 and my_pattern.play == 0 then
        if play_sync_value ~= nil then
          playback_queued = true
          my_pattern:start(play_sync_value)
        else
          my_pattern:start()
        end
      elseif my_pattern.play == 1 then
        my_pattern:stop()
      else
        my_pattern:set_rec(0)
      end
    end
  elseif x == 1 and y == g.rows - 1 then
    if z == 1 then
      params:set("loop", params:get("loop") == 1 and 2 or 1)
    end
  elseif x == 1 and y == g.rows - 2 then
    if z == 1 then
      if my_pattern.count > 0 and my_pattern.play == 1 then
        overdubbing = not overdubbing
        my_pattern:set_rec(overdubbing and 1 or 0)
      end
    end
  else
    local event = {
      id = x*8 + y,
      x = x,
      y = y,
      z = z
    }
    my_pattern:watch(event)
    process_press(event)
  end
  grid_redraw()
end

function process_press(e)
  if e.z == 1 then
    lit[e.id] = {
      x = e.x,
      y = e.y
    }
  else
    if lit[e.id] ~= nil then
      lit[e.id] = nil
    end
  end
  grid_redraw()
end

function grid_redraw()
  g:all(0)
  if my_pattern.queued_rec ~= nil and my_pattern.queued_rec.state then
    g:led(1,g.rows,7)
  elseif playback_queued then
    g:led(1,g.rows,8)
  elseif my_pattern.rec == 1 then
    g:led(1,g.rows,15)
    g:led(1,g.rows-1,15)
  elseif my_pattern.play == 1 then
    g:led(1,g.rows,10)
  elseif my_pattern.play == 0 and my_pattern.count > 0 then
    g:led(1,g.rows,5)
  else
    g:led(1,g.rows,2)
  end
  g:led(1,g.rows-1, my_pattern.loop == 1 and 10 or 2)
  g:led(1,g.rows-2, overdubbing and 10 or 2)
  for i,e in pairs(lit) do
    g:led(e.x, e.y, 15)
  end
  g:refresh()
end

function initialize_parameters()
  params:add_option(
    "demo",
    "demo",
    {"all synced: loop","unsynced: loop", "unsynced: 1-shot"},
    1
  )
  params:set_action("demo",
    function(x)
      if x == 1 then
        params:set("record_duration", 4)
        params:set("hold_rec", 1)
        params:set("rec_sync_value", 3)
        params:set("play_sync_value", 3)
        params:set("loop", 2)
      elseif x == 2 then
        params:set("record_duration", 0)
        params:set("hold_rec", 2)
        params:set("rec_sync_value", 0)
        params:set("play_sync_value", 0)
        params:set("loop", 2)
      elseif x == 3 then
        params:set("record_duration", 0)
        params:set("hold_rec", 2)
        params:set("rec_sync_value", 0)
        params:set("play_sync_value", 0)
        params:set("loop", 1)
      end
      grid_redraw()
    end
  )
  
  record_duration = 0
  params:add_number(
    "record_duration",
    "record duration",
    0,
    128,
    record_duration,
    function(param) return (
      param:get() == 0 and 'free' or
      param:get()..' beats'
    ) end
  )
  params:set_action("record_duration", function(x) record_duration = x end)
  
  hold_rec = true
  params:add_option(
    "hold_rec",
    "hold rec for first event?",
    {"no","yes"},
    hold_rec and 2 or 1
  )
  params:set_action("hold_rec", function(x) hold_rec = x == 2 end)
  
  rec_sync_value = nil
  params:add_option(
    "rec_sync_value",
    "sync record start",
    {"free","next beat", "next bar"},
    1
  )
  params:set_action("rec_sync_value",
    function(x)
      if x == 1 then
        rec_sync_value = nil
      elseif x == 2 then
        rec_sync_value = 1
      elseif x == 3 then
        rec_sync_value = 4
      end
    end
  )
  
  play_sync_value = nil
  params:add_option(
    "play_sync_value",
    "sync play start",
    {"free","next beat", "next bar"},
    1
  )
  params:set_action("play_sync_value",
    function(x)
      if x == 1 then
        play_sync_value = nil
      elseif x == 2 then
        play_sync_value = 1
      elseif x == 3 then
        play_sync_value = 4
      end
    end
  )
  
  params:add_option(
    "loop",
    "loop playback?",
    {"no", "yes"},
    1
  )
  params:set_action("loop", function(x) my_pattern:set_loop(x-1) grid_redraw() end)
  
  params:add_trigger(
    "erase_rec",
    "erase recording?"
  )
  params:set_action("erase_rec", function(x) my_pattern:clear() lit = {} grid_redraw() end)
  
  params:add_trigger(
    "double_rec",
    "double recording"
  )
  params:set_action("double_rec", function(x) my_pattern:double() end)
  
  overdubbing = false
  playback_queued = false
  
  params:bang()
end

saving + loading patterns

Included in the library are save + load helpers:

  • save: my_pattern:save(filepath)
  • load: my_pattern:load(filepath)

Use norns.state.data to access the data filepath for the currently-running script.

When a pattern is loaded to an instance of reflection, the previous data will stop playing and clear.