752 lines
25 KiB
Lua
752 lines
25 KiB
Lua
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
|