2023-08-11 09:45:28 -04:00
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 ( )
2023-11-20 08:03:19 -05:00
return { time_info = true , regular_block_length = true }
2023-08-11 09:45:28 -04:00
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