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:
parent
3294b1ce08
commit
59fb0aed36
162
share/scripts/_scl_to_mts.lua
Normal file
162
share/scripts/_scl_to_mts.lua
Normal 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
|
Loading…
Reference in New Issue
Block a user