13
0

Add script to read scala files and generate MTS sysex

So far this script can only send MTS to async MIDI output ports
(from the GUI thread).
This commit is contained in:
Robin Gareus 2020-06-16 03:25:20 +02:00
parent 3294b1ce08
commit 59fb0aed36
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04

View File

@ -0,0 +1,162 @@
ardour {
["type"] = "EditorAction",
name = "Scala to MIDI Tuning",
license = "MIT",
author = "Ardour Team",
description = [[Read scala (.scl) tuning from a file, generate MIDI tuning standard (MTS) messages and send them to a MIDI port]]
}
function factory () return function ()
function portlist ()
local rv = {}
local a = Session:engine()
local _, t = a:get_ports (ARDOUR.DataType("midi"), ARDOUR.PortList())
for p in t[2]:iter() do
local amp = p:to_asyncmidiport ()
if amp:isnil() or not amp:sends_output() then goto continue end
rv[amp:name()] = amp
--print (amp:name(), amp:sends_output())
::continue::
end
return rv
end
function log2 (v)
return math.log (v) / math.log (2)
end
function freq_to_mts (hz)
local note = math.floor (12. * log2 (hz / 440) + 69.0)
local freq = 440.0 * 2.0 ^ ((note - 69) / 12);
assert (freq > note)
local cent = 1200.0 * log2 (hz / freq)
return note, cent
end
function calc_freq (hz, cent, octave)
return hz * 2 ^ ((cent + 1200 * octave) / 1200)
end
local dialog_options = {
{ type = "file", key = "file", title = "Select .scl MIDI file" },
{ type = "dropdown", key = "port", title = "Target Port", values = portlist () }
}
local rv = LuaDialog.Dialog ("Select Taget", dialog_options):run ()
dialog_options = nil -- drop references (ports, shared ptr)
collectgarbage () -- and release the references immediately
if not rv then return end -- user cancelled
-- read the scl file
local freqtbl = {}
local ln = 0
local expected_len = 0
local f = io.open (rv["file"], "r")
if not f then
LuaDialog.Message ("Scala to MTS", "File Not Found", LuaDialog.MessageType.Error, LuaDialog.ButtonType.Close):run ()
goto out
end
-- http://www.huygens-fokker.org/scala/scl_format.html
freqtbl[1] = 0.0
for line in f:lines () do
line = string.gsub (line, "%s", "") -- remove all whitespace
if line:sub(0,1) == '!' then goto nextline end -- comment
ln = ln + 1
if ln < 2 then goto nextline end -- name
if ln < 3 then
expected_len = tonumber (line)
goto nextline
end
local cents
if string.find (line, ".", 1, true) then
cents = tonumber (line)
else
local n, d = string.match(line, "(%d+)/(%d+)")
if n then
cents = 1200 * log2 (n / d)
else
local n = tonumber (line)
cents = 1200 * log2 (n)
end
end
--print ("SCL", ln - 2, cents)
freqtbl[ln - 1] = cents
::nextline::
end
f:close ()
assert (expected_len + 2 == ln)
assert (expected_len > 0)
-- last entry should be an octave
assert (freqtbl[expected_len + 1] == 1200)
-- TODO consider kbm or make these configurable
-- http://www.huygens-fokker.org/scala/help.htm#mappings
local ref_root = 60 -- middle C
local ref_note = 69 -- A4
local ref_freq = 440.0
-- calc frequency at ref_root
local ref_base = ref_freq * 2.0 ^ ((ref_root - ref_note) / 12);
local async_midi_port = rv["port"] -- reference to port
local parser = ARDOUR.RawMidiParser () -- construct a MIDI parser
-- show progress dialog
local pdialog = LuaDialog.ProgressWindow ("Scala to MIDI Tuning", true)
pdialog:progress (0, "Tuning");
for nn = 0, 127 do
if pdialog:canceled () then break end
local delta = nn - ref_root
local delta_octv = math.floor (delta / expected_len)
local delta_note = delta % expected_len
local fq = calc_freq (ref_base, freqtbl [ delta_note + 1 ], delta_octv)
local base, cent = freq_to_mts (fq)
local cc = math.floor (163.83 * cent + 0.5) | 0
--print ("MIDI Note:", nn, "scale-note:", delta_note, "Octave:", delta_octv, "-> Freq:", fq, "= note:", base, "+", cent, "cent (", cc, ")")
local cent_lsb = (cc >> 7) & 127
local cent_msb = cc & 127
local syx = string.char (
0xf0, 0x7f, -- realtime sysex
0x7f, -- target-id
0x08, 0x02, -- tuning, note change request
0x00, -- tuning program number 0 to 127 in hexadecimal
0x01, -- number of notes to be changed
nn, -- note number to be changed
base, -- semitone (MIDI note number to retune to, unit is 100 cents)
cent_msb, -- MSB of fractional part (1/128 semitone = 100/128 cents = .78125 cent units)
cent_lsb, -- LSB of fractional part (1/16384 semitone = 100/16384 cents = .0061 cent units)
0xf7
)
for b = 1, 12 do
if parser:process_byte (syx:byte (b)) then
async_midi_port:write (parser:midi_buffer (), parser:buffer_size (), 0)
-- Physical MIDI is sent at 31.25kBaud.
-- Every message is sent as 10bit message on the wire,
-- so every MIDI byte needs 320usec.
ARDOUR.LuaAPI.usleep (400 * parser:buffer_size ())
end
end
pdialog:progress (nn / 127, string.format ("Note %d freq: %.2f (%d + %d)", nn, fq, base, cc))
if pdialog:canceled () then break end
end
-- hide modal progress dialog and destroy it
pdialog:done ();
::out::
end end