ardour {
   ["type"]    = "dsp",
   name        = "Arpeggiator (Barlow)",
   category    = "Effect",
   author      = "Albert Gräf",
   license     = "GPL",
   description = [[barlow_arp v0.3

Simple monophonic arpeggiator example with sample-accurate triggering and velocities computed using Barlow's indispensability formula. This automatically adjusts to the current time signature and division to produce rhythmic accents in accordance with the meter by varying the note velocities in a given range.

In memory of Clarence Barlow (27 December 1945 – 29 June 2023).
]]
}

-- Copyright (c) 2023 Albert Gräf, GPLv3+

-- This is basically the same as simple_arp.lua (which see), but computes note
-- velocities using the Barlow indispensability formula which produces more
-- detailed rhythmic accents and handles arbitrary time signatures with ease.
-- It also offers a pulse filter which lets you filter notes by normalized
-- pulse strengths. Any pulse with a strength below/above the given
-- minimum/maximum values in the 0-1 range will be skipped. In this case you
-- can set the gate value to 0 a.k.a. "legato" to have notes extend over
-- skipped steps until the next note arrives. This only makes an audible
-- difference if the pulse filter is in effect, otherwise a gate value of 0
-- has effectively the same meaning as 1.

-- NOTE: A limitation of the present algorithm is that only subdivisions <= 7
-- (a.k.a. septuplets) are supported, but if you really need more, then you
-- may also just change the time signature accordingly. Also, there's no swing
-- control, but you can easily get a triplet feel with the pulse filter
-- instead (e.g., in 4/4 try a triplet division along with a minimum pulse
-- strength of 0.3).

function dsp_ioconfig ()
   return { { midi_in = 1, midi_out = 1, audio_in = -1, audio_out = -1}, }
end

function dsp_options ()
   return { time_info = true }
end

function dsp_params ()
   return
      {
	 { type = "input", name = "Division", min = 1, max = 7, default = 1, integer = true, doc = "number of subdivisions of the beat" },
	 { type = "input", name = "Octave up", min = 0, max = 5, default = 0, integer = true, doc = "octave range up" },
	 { type = "input", name = "Octave down", min = 0, max = 5, default = 0, integer = true, doc = "octave range down" },
	 { type = "input", name = "Pattern", min = 1, max = 6, default = 1, integer = true, doc = "pattern style",
	   scalepoints =
	      {	["1 up"] = 1, ["2 down"] = 2, ["3 exclusive"] = 3, ["4 inclusive"] = 4, ["5 order"] = 5, ["6 random"] = 6 } },
	 { type = "input", name = "Min Velocity", min = 0, max = 127, default = 60, integer = true, doc = "minimum velocity" },
	 { type = "input", name = "Max Velocity", min = 0, max = 127, default = 120, integer = true, doc = "maximum velocity" },
	 { type = "input", name = "Min Filter", min = 0, max = 1, default = 0, doc = "minimum pulse strength" },
	 { type = "input", name = "Max Filter", min = 0, max = 1, default = 1, doc = "maximum pulse strength" },
	 { type = "input", name = "Latch", min = 0, max = 1, default = 0, toggled = true, doc = "toggle latch mode" },
	 { type = "input", name = "Sync", min = 0, max = 1, default = 0, toggled = true, doc = "toggle sync mode" },
	 { type = "input", name = "Bypass", min = 0, max = 1, default = 0, toggled = true, doc = "bypass the arpeggiator, pass through input notes" },
	 { type = "input", name = "Gate", min = 0, max = 1, default = 1, doc = "gate as fraction of pulse length", scalepoints = { legato = 0 } },
      }
end

function presets()
   -- just a few basic examples for now, we'll add more stuff here later
   return
      {
	 { name = "0 default", params = { Division = 1, ["Octave up"] = 0, ["Octave down"] = 0, Pattern = 1, ["Min Velocity"] = 60, ["Max Velocity"] = 120, ["Min Filter"] = 0, ["Max Filter"] = 1, Latch = 0, Sync = 0, Gate = 1 } },
	 { name = "1 latch", params = { Latch = 1, Sync = 0 } },
	 { name = "2 latch and sync", params = { Latch = 1, Sync = 1 } },
	 { name = "3 bass", params = { Division = 1, ["Octave up"] = 0, ["Octave down"] = 1, Pattern = 1, ["Min Filter"] = 0, ["Max Filter"] = 1, Gate = 1 } },
	 { name = "4 triplet feel #1 - synth", params = { Division = 3, ["Octave up"] = 1, ["Octave down"] = 1, Pattern = 3, ["Min Filter"] = 0.2, ["Max Filter"] = 1, Gate = 0 } },
	 { name = "5 triplet feel #2 - drums", params = { Division = 3, ["Octave up"] = 0, ["Octave down"] = 0, Pattern = 1, ["Min Filter"] = 0.2, ["Max Filter"] = 1, Gate = 0 } },
      }
end

-- debug level (1: print beat information in the log window, 2: also print the
-- current pattern whenever it changes, 3: also print note information, 4:
-- print everything)
local debug = 0

local chan = 0 -- MIDI output channel
local last_rolling -- last transport status, to detect changes
local last_beat -- last beat number
local last_num -- last note
local last_chan -- MIDI channel of last note
local last_gate -- off time of last note
local last_up, last_down, last_mode, last_sync, last_bypass -- previous params, to detect changes
local chord = {} -- current chord (note store)
local chord_index = 0 -- index of last chord note (0 if none)
local latched = {} -- latched notes
local pattern = {} -- current pattern
local index = 0 -- current pattern index (reset when pattern changes)

-- Meter object
Meter = {}
Meter.__index = Meter

function Meter:new(m) -- constructor
   -- n = maximum subdivision, septoles seem to work reasonably well
   -- meter = meter, {4} a.k.a. common time is default
   -- indisp = indispensability tables, computed below
   local x = setmetatable({ n = 7, meter = {4}, indisp = {} }, Meter)
   x:compute(m)
   return x
end

-- Computes the best subdivision q in the range 1..n and pulse p in the range
-- 0..q so that p/q matches the given phase f in the floating point range 0..1
-- as closely as possible. Returns p, q and the absolute difference between f
-- and p/q. NB: Seems to work best for q values up to 7.

local function subdiv(n, f)
   local best_p, best_q, best = 0, 0, 1
   for q = 1, n do
      local p = math.floor(f*q+0.5) -- round towards nearest pulse
      local diff = math.abs(f-p/q)
      if diff < best then
	 best_p, best_q, best = p, q, diff
      end
   end
   return best_p, best_q, best
end

-- prime factors of integers
local function factor(n)
   local factors = {}
   if n<0 then n = -n end
   while n % 2 == 0 do
      table.insert(factors, 2)
      n = math.floor(n / 2)
   end
   local p = 3
   while p <= math.sqrt(n) do
      while n % p == 0 do
	 table.insert(factors, p)
	 n = math.floor(n / p)
      end
      p = p + 2
   end
   if n > 1 then -- n must be prime
      table.insert(factors, n)
   end
   return factors
end

-- reverse a table

local function reverse(list)
   local res = {}
   for k, v in ipairs(list) do
      table.insert(res, 1, v)
   end
   return res
end

-- arithmetic sequences

local function seq(from, to, step)
   step = step or 1;
   local sgn = step>=0 and 1 or -1
   local res = {}
   while sgn*(to-from) >= 0 do
      table.insert(res, from)
      from = from + step
   end
   return res
end

-- some functional programming goodies

local function map(list, fn)
   local res = {}
   for k, v in ipairs(list) do
      table.insert(res, fn(v))
   end
   return res
end

local function reduce(list, acc, fn)
   for k, v in ipairs(list) do
      acc = fn(acc, v)
   end
   return acc
end

local function collect(list, acc, fn)
   local res = {acc}
   for k, v in ipairs(list) do
      acc = fn(acc, v)
      table.insert(res, acc)
   end
   return res
end

local function sum(list)
   return reduce(list, 0, function(a,b) return a+b end)
end

local function prd(list)
   return reduce(list, 1, function(a,b) return a*b end)
end

local function sums(list)
   return collect(list, 0, function(a,b) return a+b end)
end

local function prds(list)
   return collect(list, 1, function(a,b) return a*b end)
end

-- indispensabilities (Barlow's formula)
local function indisp(q)
   function ind(q, k)
      -- prime indispensabilities
      function pind(q, k)
	 function ind1(q, k)
	    local i = ind(reverse(factor(q-1)), k)
	    local j = i >= math.floor(q / 4) and 1 or 0;
	    return i+j
	 end
	 if q <= 3 then
	    return (k-1) % q
	 elseif k == q-2 then
	    return math.floor(q / 4)
	 elseif k == q-1 then
	    return ind1(q, k-1)
	 else
	    return ind1(q, k)
	 end
      end
      local s = prds(q)
      local t = reverse(prds(reverse(q)))
      return
	 sum(
	    map(seq(1, #q),
		function(i)
		   return s[i] *
		      pind(q[i], (math.floor((k-1) % t[1] / t[i+1]) + 1) % q[i])
		end
	 ))
   end
   if type(q) == "number" then
      q = factor(q)
   end
   if type(q) ~= "table" then
      error("invalid argument, must be an integer or table of primes")
   else
      return map(seq(0,prd(q)-1), function(k) return ind(q,k) end)
   end
end

local function tableconcat(t1,t2)
   local res = {}
   for i=1,#t1 do
      table.insert(res, t1[i])
   end
   for i=1,#t2 do
      table.insert(res, t2[i])
   end
   return res
end

-- This optionally takes a new meter as argument and (re)computes the
-- indispensability tables. NOTE: This can be called (and the meter be
-- changed) at any time.
function Meter:compute(meter)
   meter = meter or self.meter
   -- a number is interpreted as a singleton list
   meter = type(meter) == "number" and {meter} or meter
   self.meter = meter
   local n = 1
   local m = {}
   for i,q in ipairs(meter) do
      if q ~= math.floor(q) then
	 error("meter: levels must be integer")
      elseif q < 1 then
	 error("meter: levels must be positive")
      end
      -- factorize each level as Barlow's formula assumes primes
      m = tableconcat(m, factor(q))
      n = n*q
   end
   self.beats = n
   self.last_q = nil
   if self.beats > 1 then
      self.indisp[1] = indisp(m)
      for q = 2, self.n do
	 local qs = tableconcat(m, factor(q))
	 self.indisp[q] = indisp(qs)
      end
   else
      self.indisp[1] = {0}
      for q = 2, self.n do
	 self.indisp[q] = indisp(q)
      end
   end
end

-- This takes the (possibly fractional) pulse and returns the pulse strength
-- along with the total number of beats.
function Meter:pulse(f)
   if type(f) ~= "number" then
      error("meter: beat index must be a number")
   elseif f < 0 then
      error("meter: beat index must be nonnegative")
   end
   local beat, f = math.modf(f)
   -- take the beat index modulo the total number of beats
   beat = beat % self.beats
   if self.n > 0 then
      local p, q = subdiv(self.n, f)
      if self.last_q then
	 local x = self.last_q / q
	 if math.floor(x) == x then
	    -- If the current best match divides the previous one, stick to
	    -- it, in order to prevent the algorithm from quickly changing
	    -- back to the root meter at each base pulse. XXFIXME: This may
	    -- stick around indefinitely until the meter changes. Maybe we'd
	    -- rather want to reset this automatically after some time (such
	    -- as a complete bar without non-zero phases)?
	    p, q = x*p, x*q
	 end
      end
      self.last_q = q
      -- The overall zero-based pulse index is beat*q + p. We add 1 to
      -- that to get a 1-based index into the indispensabilities table.
      local w = self.indisp[q][beat*q+p+1]
      return w, self.beats*q
   else
      local w = self.indisp[1][beat+1]
      return w, self.beats
   end
end

-- NOTE: Computing the necessary tables for the Barlow meter is a fairly
-- cpu-intensive operation, so changing the time signature mid-flight might
-- cause some cpu spikes and thus x-runs. To mitigate this, we cache each
-- meter as soon as we first encounter it, so that no costly recomputations
-- are needed later. An initial scan of the timeline makes sure that the cache
-- is well-populated from the get-go.

local last_mdiv
-- cached Barlow meters
local barlow_meters = { [4] = Meter:new() } -- common time
-- current Barlow meter
local barlow_meter = barlow_meters[4]

function dsp_init (rate)
   local loc = Session:locations():session_range_location()
   if loc then
      local tm = Temporal.TempoMap.read ()
      local a, b = loc:start():beats(), loc:_end():beats()
      if debug >= 1 then
	 print(loc:name(), a, b)
      end
      -- Scan through the timeline to find all time signatures and cache the
      -- resulting Barlow meters. Note that only care about the number of
      -- divisions here, that's all the algorithm needs.
      while a <= b do
	 local m = tm:meter_at_beats(a)
	 local mdiv = m:divisions_per_bar()
	 if not barlow_meters[mdiv] then
	    if debug >= 1 then
	       print(a, string.format("%d/%d", mdiv, m:note_value()))
	    end
	    barlow_meters[mdiv] = Meter:new(mdiv)
	 end
	 a = a:next_beat()
      end
   elseif debug >= 1 then
      print("empty session")
   end
end

function dsp_run (_, _, n_samples)
   assert (type(midiout) == "table")
   assert (type(time) == "table")
   assert (type(midiout) == "table")

   local ctrl = CtrlPorts:array ()
   -- We need to make sure that these are integer values. (The GUI enforces
   -- this, but fractional values may occur through automation.)
   local subdiv, up, down, mode = math.floor(ctrl[1]), math.floor(ctrl[2]), math.floor(ctrl[3]), math.floor(ctrl[4])
   local minvel, maxvel = math.floor(ctrl[5]), math.floor(ctrl[6])
   -- these are floating point values in the 0-1 range
   local minw, maxw = ctrl[7], ctrl[8]
   local gate = ctrl[12]
   -- latch toggle
   local latch = ctrl[9] > 0
   -- sync toggle
   local sync = ctrl[10] > 0
   -- bypass toggle
   local bypass = ctrl[11] > 0
   -- rolling state: It seems that we need to check the transport state (as
   -- given by Ardour's "transport finite state machine" = TFSM) here, even if
   -- the transport is not actually moving yet. Otherwise some input notes may
   -- errorneously slip through before playback really starts.
   local rolling = Session:transport_state_rolling ()
   -- whether the pattern must be recomputed, due to parameter changes or MIDI
   -- input
   local changed = false

   if up ~= last_up or down ~= last_down or mode ~= last_mode then
      last_up = up
      last_down = down
      last_mode = mode
      changed = true
   end

   if sync ~= last_sync then
      last_sync = sync
      index = 0
   end

   if not latch and next(latched) ~= nil then
      latched = {}
      changed = true
   end

   local all_notes_off = false
   if bypass ~= last_bypass then
      last_bypass = bypass
      all_notes_off = true
   end

   if last_rolling ~= rolling then
      last_rolling = rolling
      -- transport change, send all-notes off (we only do this when transport
      -- starts rolling, to silence any notes that may have been passed
      -- through beforehand; note that Ardour automatically sends
      -- all-notes-off to all MIDI channels anyway when transport is stopped)
      if rolling then
	 all_notes_off = true
      end
   end

   local k = 1
   if all_notes_off then
      --print("all-notes-off", chan)
      midiout[k] = { time = 1, data = { 0xb0+chan, 123, 0 } }
      k = k+1
   end

   for _,ev in ipairs (midiin) do
      local status, num, val = table.unpack(ev.data)
      local ch = status & 0xf
      status = status & 0xf0
      if not rolling or bypass then
	 -- arpeggiator is just listening, pass through all MIDI data
	 midiout[k] = ev
	 --print(string.format("[%d] %0x %d %d", ev.time, ev.data[1], ev.data[2], ev.data[3]))
	 k = k+1
      elseif status >= 0xb0 then
	 -- arpeggiator is playing, pass through all MIDI data that's not
	 -- note-related, i.e., control change, program change, channel
	 -- pressure, pitch wheel, and system messages
	 midiout[k] = ev
	 k = k+1
      end
      if status == 0x80 or status == 0x90 and val == 0 then
	 if debug >= 4 then
	    print("note off", num, val)
	 end
	 -- keep track of latched notes
	 if latch then
	    latched[num] = chord[num]
	 else
	    changed = true
	 end
	 chord[num] = nil
      elseif status == 0x90 then
	 if debug >= 4 then
	    print("note on", num, val, "ch", ch)
	 end
	 if latch and next(chord) == nil then
	    -- new pattern, get rid of latched notes
	    latched = {}
	 end
	 chord_index = chord_index+1
	 chord[num] = chord_index
	 if latch and latched[num] then
	    -- avoid double notes in latch mode
	    latched[num] = nil
	 else
	    changed = true
	 end
	 chan = ch
      elseif status == 0xb0 and num == 123 and ch == chan then
	 if debug >= 4 then
	    print("all notes off")
	 end
	 chord = {}
	 latched = {}
	 changed = true
      end
   end
   if changed then
      -- update the pattern
      pattern = {}
      function pattern_from_chord(pattern, chord)
	 for num, val in pairs(chord) do
	    table.insert(pattern, num)
	    for i = 1, down do
	       if num-i*12 >= 0 then
		  table.insert(pattern, num-i*12)
	       end
	    end
	    for i = 1, up do
	       if num+i*12 <= 127 then
		  table.insert(pattern, num+i*12)
	       end
	    end
	 end
      end
      pattern_from_chord(pattern, chord)
      if latch then
	 -- add any latched notes
	 pattern_from_chord(pattern, latched)
      end
      table.sort(pattern) -- order by ascending notes (up pattern)
      local n = #pattern
      if n > 0 then
	 if mode == 2 then
	    -- down pattern, reverse the list
	    table.sort(pattern, function(a,b) return a > b end)
	 elseif mode == 3 then
	    -- add the reversal of the list excluding the last element
	    for i = 1, n-2 do
	       table.insert(pattern, pattern[n-i])
	    end
	 elseif mode == 4 then
	    -- add the reversal of the list including the last element
	    for i = 1, n-1 do
	       table.insert(pattern, pattern[n-i+1])
	    end
	 elseif mode == 5 then
	    -- order the pattern by chord indices
	    local k = chord_index+1
	    local idx = {}
	    -- build a table of indices which also includes octaves up and
	    -- down, ordering them first by octave and then by index
	    function index_from_chord(idx, chord)
	       for num, val in pairs(chord) do
		  for i = 1, down do
		     if num-i*12 >= 0 then
			idx[num-i*12] = val - i*k
		     end
		  end
		  idx[num] = val
		  for i = 1, up do
		     if num+i*12 <= 127 then
			idx[num+i*12] = val + i*k
		     end
		  end
	       end
	    end
	    index_from_chord(idx, chord)
	    if latch then
	       index_from_chord(idx, latched)
	    end
	    table.sort(pattern, function(a,b) return idx[a] < idx[b] end)
	 elseif mode == 6 then
	    -- random order
	    for i = n, 2, -1 do
	       local j = math.random(i)
	       pattern[i], pattern[j] = pattern[j], pattern[i]
	    end
	 end
	 if debug >= 2 then
	    local s = "pattern:"
	    for i, num in ipairs(pattern) do
	       s = s .. " " .. num
	    end
	    print(s)
	 end
	 index = 0 -- reset pattern to the start
      else
	 chord_index = 0 -- pattern is empty, reset the chord index
	 if debug >= 2 then
	    print("pattern: <empty>")
	 end
      end
   end

   if rolling and not bypass then
      -- transport is rolling, not bypassed, so the arpeggiator is playing
      if last_gate and last_num and
	 last_gate >= time.sample and last_gate < time.sample_end then
	 -- Gated notes don't normally fall on a beat, so we detect them
	 -- here. (If the gate time hasn't been set or we miss it, then the
	 -- note-off will be taken care of when the next note gets triggered,
	 -- see below.)
	 if debug >= 3 then
	    print("note off", last_num)
	 end
	 -- sample-accurate "off" time
	 local ts = last_gate - time.sample + 1
	 midiout[k] = { time = ts, data = { 0x80+last_chan, last_num, 100 } }
	 last_num = nil
	 k = k+1
      end
      -- Check whether a beat is due, so that we trigger the next note. We
      -- want to do this in a sample-accurate manner in order to avoid jitter,
      -- which makes things a little complicated.  There are three cases to
      -- consider here:
      -- (1) Transport just started rolling or the playhead moved for some
      -- reason, in which case we *must* output the note immediately in order
      -- to not miss a beat (even if we're a bit late).
      -- (2) The beat occurs exactly at the beginning of a processing cycle,
      -- so we output the note immediately.
      -- (3) The beat happens some time during the cycle, in which case we
      -- calculate the sample at which the note is due.
      local denom = time.ts_denominator * subdiv
      -- beat numbers at start and end, scaled by base pulses and subdivisions
      local b1, b2 = denom/4*time.beat, denom/4*time.beat_end
      -- integral part of these
      local bf1, bf2 = math.floor(b1), math.floor(b2)
      -- sample times at start and end
      local s1, s2 = time.sample, time.sample_end
      -- current (nominal, i.e., unscaled) beat number, and its sample time
      local bt, ts
      if last_beat ~= math.floor(time.beat) or bf1 == b1 then
	 -- next beat is due immediately
	 bt, ts = time.beat, time.sample
      elseif bf2 > bf1 and bf2 ~= b2 then
	 -- next beat is due some time in this cycle (we're assuming contant
	 -- tempo here, hence this number may be off in case the tempo is
	 -- changing very quickly during the cycle -- so don't do that)
	 local d = math.ceil((b2-bf2)/(b2-b1)*(s2-s1))
	 assert(d > 0)
	 bt, ts = time.beat_end, time.sample_end - d
      end
      if ts then
	 -- save the last nominal beat so that we can detect sudden changes of
	 -- the playhead later (e.g., when transport starts rolling, or at the
	 -- end of a loop when the playhead wraps around to the beginning)
	 last_beat = math.floor(bt)
	 -- get the tempo map information
	 local tm = Temporal.TempoMap.read ()
	 local pos = Temporal.timepos_t (ts)
	 local bbt = tm:bbt_at (pos)
	 local meter = tm:meter_at (pos)
	 local tempo = tm:tempo_at (pos)
	 -- calculate the note-off time in samples, this is used if the gate
	 -- control is neither 0 nor 1
	 local gate_ts = ts + math.floor(tm:bbt_duration_at(pos, Temporal.BBT_Offset(0,1,0)):samples() / subdiv * gate)
	 local n = #pattern
	 ts = ts - time.sample + 1
	 if debug >= 1 then
	    -- print some debugging information: bbt, fractional beat number,
	    -- sample offset, current meter, current tempo
	    print (string.format("%s - %g [%d] - %d/%d - %g bpm", bbt:str(),
				 math.floor(denom*bt)/denom, ts-1,
				 meter:divisions_per_bar(), meter:note_value(),
				 tempo:quarter_notes_per_minute()))
	 end
	 -- we take a very small gate value (close to 0) to mean legato
	 -- instead, in which case notes extend to the next unfiltered note
	 local legato =  gate_ts < time.sample_end
	 function note_off()
	    if last_num then
	       -- kill the old note
	       if debug >= 3 then
		  print("note off", last_num)
	       end
	       midiout[k] = { time = ts, data = { 0x80+last_chan, last_num, 100 } }
	       last_num = nil
	       k = k+1
	    end
	 end
	 if not legato then
	    note_off()
	 end
	 if n > 0 then
	    -- calculate a fractional pulse number from the current bbt
	    local p = bbt.beats-1 + math.max(0, bbt.ticks) / Temporal.ticks_per_beat
	    -- Detect meter changes and update the Barlow meter object
	    -- accordingly.
	    local mdiv = meter:divisions_per_bar()
	    if mdiv ~= last_mdiv then
	       if not barlow_meters[mdiv] then
		  if debug >= 1 then
		     print(bt, string.format("%d/%d", mdiv, meter:note_value()))
		  end
		  barlow_meters[mdiv] = Meter:new(mdiv)
	       end
	       barlow_meter = barlow_meters[mdiv]
	       last_mdiv = mdiv
	    end
	    -- Use the algorithm to determine the pulse weight.
	    local w, npulses = barlow_meter:pulse (p)
	    if debug >= 4 then
	       print(" Beat:", p, " Weight =", w, "/", npulses-1)
	    end
	    -- normalize the weight to the 0-1 range
	    w = w/(npulses-1)
	    -- filter notes
	    if w >= minw and w <= maxw then
	       if legato then
		  note_off()
	       end
	       -- compute the velocity, round to nearest integer
	       local v = minvel + w * (maxvel-minvel)
	       v = math.floor(v+0.5)
	       --print("p", p, "v", v)
	       -- trigger the new note
	       if sync then
		  -- sync pattern to the bbt
		  local l = #pattern
		  local k = math.floor(p*subdiv+0.5) -- current index in bar
		  local n = math.floor(l/npulses) -- bars in pattern
		  if n > 0 then
		     k = k + index*npulses
		     if (k+1) % npulses == 0  then
			-- next bar
			index = (index+1) % n
		     end
		  end
		  num = pattern[k%l+1]
	       else
		  index = index%n + 1
		  num = pattern[index]
	       end
	       if debug >= 3 then
		  print("note on", num, v)
	       end
	       midiout[k] = { time = ts, data = { 0x90+chan, num, v } }
	       last_num = num
	       last_chan = chan
	       if gate < 1 and not legato then
		  -- Set the sample time at which the note-off is due.
		  last_gate = gate_ts
	       else
		  -- Otherwise don't set the off time in which case the
		  -- note-off gets triggered automatically above.
		  last_gate = nil
	       end
	    end
	 end
      end
   else
      -- transport not rolling or bypass; reset the last beat number
      last_beat = nil
   end

   if debug >= 1 and #midiout > 0 then
      -- monitor memory usage of the Lua interpreter
      print(string.format("mem: %0.2f KB", collectgarbage("count")))
   end

end
