-- grid studies: seamstress
-- grid-study-3-5.lua

g = grid.connect(1) -- '1' is technically optional.
-- without an argument, seamstress will always connect to the first-registered grid.

-- we'll use musicutil for easy MIDI note formatting + quantization:
MU = require("musicutil")

function init()
  if g then
    cols = g.cols
    rows = g.rows
    grid_connected = true
  else
    cols = 16
    rows = 8
    grid_connected = false
  end

  sequencer_rows = rows - 1

  step = {}
  for r = 1, rows do
    step[r] = {}
    for c = 1, cols do
      step[r][c] = 0
    end
  end

  circle_queue = {}
  screen_dirty = true
  screen_redraw = metro.init(
    redraw, -- function to execute
    1 / 30, -- how often (here, 30 fps)
    -1 -- how many times (here, forever)
  )
  screen_redraw:start() -- start the timer

  play_position = 0
  playhead = clock.run(play)
  grid_dirty = true
  grid_redraw = metro.init(draw_grid, 1 / 60, -1)
  grid_redraw:start()

  -- we'll connect to virtual port 1, which is seamstress's MIDI device:
  m = midi.connect(1)
  -- for a more robust example of MIDI scaffolding,
  --   check out the 'hello_midi' example!

  active_notes = {} -- to keep track of 'note on' messages, for paired 'note off'

  -- build scales for quantized note selection:
  scale_names = {}
  for i = 1, #MU.SCALES do
    table.insert(scale_names, string.lower(MU.SCALES[i].name))
  end

  params:add_control(
    "root_note", -- scripting ID
    "root note", -- UI name
    controlspec.new(0, 127, "lin", 1, 72, nil, 1 / 127), -- controlspec
    function(param) -- UI formatter
      return MU.note_num_to_name(param:get(), true)
    end
  )
  params:set_action("root_note", function()
    build_scale()
  end)
  params:add_option("scale", "scale", scale_names, 5)
  params:set_action("scale", function()
    build_scale()
  end)

  -- important! since our script relies on the output of our parameter actions,
  --   we'll want to fire them off in the init:
  params:bang()
end

function grid.add(dev)
  cols = dev.cols
  rows = dev.rows
  sequencer_rows = rows - 1
  grid_connected = true
end

function grid.remove(dev)
  grid_connected = false
end

function build_scale()
  all_notes = MU.generate_scale(params:get("root_note"), params:get("scale"), 2)
  screen_dirty = true
end

function play()
  while true do
    -- perform actions
    -- NEW //
    if cutting then
      play_position = next_position
      cutting = false
    else
      play_position = util.wrap(play_position + 1, 1, cols)
    end
    -- // NEW
    for y = 1, rows do
      if step[y][play_position] == 1 then
        trigger(y)
      end
    end
    screen_dirty = true
    clock.sync(1 / 4)

    for active = 1, #active_notes do
      m:note_off(active_notes[active], nil, 1)
    end
    active_notes = {}

    grid_dirty = true
  end
end

function trigger(i)
  table.insert(circle_queue, {
    x = math.random(256),
    y = math.random(128),
    r = math.random(40, 190),
    g = math.random(255),
    b = math.random(128, 255),
    outer_radius = i * 10,
    inner_radius = i * 5,
  })

  local maximum_count = sequencer_rows + 1
  local note = all_notes[maximum_count - i]
  m:note_on(note, 127, 1)
  table.insert(active_notes, note)
end

function redraw()
  if screen_dirty then
    screen.clear()
    for k, v in pairs(circle_queue) do
      screen.move(v.x, v.y)
      screen.color(v.r, v.g, v.b)
      screen.circle(v.outer_radius)
      screen.circle_fill(v.inner_radius)
    end
    circle_queue = {}
    screen_dirty = false
    screen.refresh()
  end
end

function g.key(x, y, z)
  if z == 1 and y <= sequencer_rows then
    -- when step value is 0, set it to 1 ; when step value is 1, set it to 0
    step[y][x] = math.abs(step[y][x] - 1)
  -- NEW //
  -- grid presses on bottom row cut playhead:
  elseif y == rows and z == 1 then
    cutting = true
    next_position = x
    -- // NEW
  end
  grid_dirty = true
end

function draw_grid()
  if grid_dirty then
    g:all(0) -- clear grid LEDs
    -- queue grid LEDs
    -- display steps:
    for x = 1, cols do
      local highlight
      if x == play_position then
        highlight = 4
      else
        highlight = 0
      end

      -- jump row
      local jump_row = sequencer_rows + 1
      g:led(x, jump_row, 4)

      for y = 1, sequencer_rows do
        g:led(x, y, step[y][x] * 11 + highlight)
      end
    end

    -- NEW //
    -- draw play position
    g:led(play_position, rows, 15)
    -- // NEW

    g:refresh() -- draw grid LEDs
    grid_dirty = false -- reset flag
  end
end

function all_notes_off()
  m:cc(123, 1)
  active_notes = {}
end

function cleanup()
  all_notes_off()
  g:all(0)
  g:refresh()
end
