Further updates to Scala to MTS script:
* allow non-realtime bulk transfers * debug mode, dump .syx file * add some comments and references
This commit is contained in:
parent
452607711b
commit
6f0e0000e6
@ -42,16 +42,35 @@ function factory () return function ()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- calculate MIDI note-number and cent-offset for a given frequency
|
-- calculate MIDI note-number and cent-offset for a given frequency
|
||||||
|
--
|
||||||
|
-- "The first byte of the frequency data word specifies the nearest equal-tempered
|
||||||
|
-- semitone below the frequency. The next two bytes (14 bits) specify the fraction
|
||||||
|
-- of 100 cents above the semitone at which the frequency lies."
|
||||||
|
--
|
||||||
|
-- 68 7F 7F = 439.9984 Hz
|
||||||
|
-- 69 00 00 = 440.0000 Hz
|
||||||
|
-- 69 00 01 = 440.0016 Hz
|
||||||
|
--
|
||||||
|
-- NB. 7F 7F 7F = no change (reserved)
|
||||||
|
--
|
||||||
function freq_to_mts (hz)
|
function freq_to_mts (hz)
|
||||||
local note = math.floor (12. * log2 (hz / 440) + 69.0)
|
local note = math.floor (12. * log2 (hz / 440) + 69.0)
|
||||||
local freq = 440.0 * 2.0 ^ ((note - 69) / 12);
|
local freq = 440.0 * 2.0 ^ ((note - 69) / 12);
|
||||||
assert (freq > note)
|
|
||||||
local cent = 1200.0 * log2 (hz / freq)
|
local cent = 1200.0 * log2 (hz / freq)
|
||||||
|
-- fixup rounding errors
|
||||||
|
if cent >= 99.99 then
|
||||||
|
note = note + 1
|
||||||
|
cent = 0
|
||||||
|
end
|
||||||
|
if cent < 0 then
|
||||||
|
cent = 0
|
||||||
|
end
|
||||||
return note, cent
|
return note, cent
|
||||||
end
|
end
|
||||||
|
|
||||||
local dialog_options = {
|
local dialog_options = {
|
||||||
{ type = "file", key = "file", title = "Select .scl file" },
|
{ type = "file", key = "file", title = "Select .scl file" },
|
||||||
|
{ type = "checkbox", key = "bulk", default = false, title = "Bulk Transfer (not realtime)" },
|
||||||
{ type = "dropdown", key = "tx", title = "MIDI SysEx Target", values = midi_targets () }
|
{ type = "dropdown", key = "tx", title = "MIDI SysEx Target", values = midi_targets () }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,29 +134,84 @@ function factory () return function ()
|
|||||||
assert (expected_len + 2 == ln)
|
assert (expected_len + 2 == ln)
|
||||||
assert (expected_len > 0)
|
assert (expected_len > 0)
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
-- TODO consider reading a .kbm file or make these configurable in the dialog
|
-- TODO consider reading a .kbm file or make these configurable in the dialog
|
||||||
-- http://www.huygens-fokker.org/scala/help.htm#mappings
|
-- http://www.huygens-fokker.org/scala/help.htm#mappings
|
||||||
local ref_root = 60 -- middle C
|
local ref_note = 69 -- Reference note for which frequency is given
|
||||||
local ref_note = 69 -- A4
|
local ref_freq = 440.0 -- Frequency to tune the above note to
|
||||||
local ref_freq = 440.0
|
local ref_root = 60 -- root-note of the scale, note where the first entry of the scale is mapped to
|
||||||
|
local note_start = 0
|
||||||
|
local note_end = 127
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- prepare sending data
|
||||||
|
local send_bulk = rv['bulk']
|
||||||
|
local tx = rv["tx"] -- output port
|
||||||
|
local parser = ARDOUR.RawMidiParser () -- construct a MIDI parser
|
||||||
|
local checksum = 0
|
||||||
|
|
||||||
|
if send_bulk then
|
||||||
|
note_start = 0
|
||||||
|
note_end = 127
|
||||||
|
end
|
||||||
|
|
||||||
|
--local dump = io.open ("/tmp/dump.syx", "wb")
|
||||||
|
|
||||||
|
-- helper function to send MIDI
|
||||||
|
function tx_midi (syx, len, hdr)
|
||||||
|
for b = 1, len do
|
||||||
|
--dump:write (string.char(syx:byte (b)))
|
||||||
|
|
||||||
|
-- calculate checksum, xor of all payload data
|
||||||
|
-- (excluding the 0xf0, 0xf7, and the checksum field)
|
||||||
|
if b >= hdr then
|
||||||
|
checksum = checksum ~ syx:byte (b)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- parse message to C/C++ uint8_t* array (Validate message correctness. This
|
||||||
|
-- also returns C/C++ uint8_t* array for direct use with write_immediate_event.)
|
||||||
|
if parser:process_byte (syx:byte (b)) then
|
||||||
|
tx:write_immediate_event (parser:buffer_size (), parser:midi_buffer ())
|
||||||
|
-- Slow things down a bit to ensure that no messages as lost.
|
||||||
|
-- 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
|
||||||
|
end
|
||||||
|
|
||||||
|
-- show progress dialog
|
||||||
|
local pdialog = LuaDialog.ProgressWindow ("Scala to MIDI Tuning", true)
|
||||||
|
pdialog:progress (0, "Tuning");
|
||||||
|
|
||||||
-- calculate frequency at ref_root
|
-- calculate frequency at ref_root
|
||||||
local delta = ref_note - ref_root
|
local delta = ref_note - ref_root
|
||||||
local delta_octv = math.floor (delta / expected_len)
|
local delta_octv = math.floor (delta / expected_len)
|
||||||
local delta_note = delta % expected_len
|
local delta_note = delta % expected_len
|
||||||
|
|
||||||
|
-- inverse mapping, ref_note will have the specified frequency in the target scale,
|
||||||
|
-- while the scale itself will start at ref_root
|
||||||
local ref_base = ref_freq * 2 ^ ((freqtbl[delta_note + 1] + freqtbl[expected_len + 1] * delta_octv) / -1200)
|
local ref_base = ref_freq * 2 ^ ((freqtbl[delta_note + 1] + freqtbl[expected_len + 1] * delta_octv) / -1200)
|
||||||
|
|
||||||
-- prepare sending data
|
if send_bulk then
|
||||||
local tx = rv["tx"] -- output port
|
-- MIDI Tuning message
|
||||||
local parser = ARDOUR.RawMidiParser () -- construct a MIDI parser
|
-- http://technogems.blogspot.com/2018/07/using-midi-tuning-specification-mts.html
|
||||||
|
-- http://www.ludovico.net/download/materiale_didattico/midi/08_midi_tuning.pdf
|
||||||
|
local syx = string.char (
|
||||||
|
0xf0, 0x7e, -- non-realtime sysex
|
||||||
|
0x00, -- target-id
|
||||||
|
0x08, 0x01, -- tuning, bulk dump reply
|
||||||
|
0x00, -- tuning program number 0 to 127 in hexadecimal
|
||||||
|
-- 16 chars name (zero padded)
|
||||||
|
0x53, 0x63, 0x6C, 0x2D, 0x4D, 0x54, 0x53, 0x00, -- Scl-MTS
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
)
|
||||||
|
tx_midi (syx, 22, 1)
|
||||||
|
end
|
||||||
|
|
||||||
-- show progress dialog
|
-- iterate over MIDI notes
|
||||||
local pdialog = LuaDialog.ProgressWindow ("Scala to MIDI Tuning", true)
|
for nn = note_start, note_end do
|
||||||
pdialog:progress (0, "Tuning");
|
|
||||||
|
|
||||||
-- iterate over all MIDI notes
|
|
||||||
for nn = 0, 127 do
|
|
||||||
if pdialog:canceled () then break end
|
if pdialog:canceled () then break end
|
||||||
|
|
||||||
-- calculate the note relative to kbm's ref_root
|
-- calculate the note relative to kbm's ref_root
|
||||||
@ -153,19 +227,42 @@ function factory () return function ()
|
|||||||
|
|
||||||
-- MTS uses two MIDI bytes (2^14) for cents
|
-- MTS uses two MIDI bytes (2^14) for cents
|
||||||
local cc = math.floor (163.83 * cent + 0.5) | 0
|
local cc = math.floor (163.83 * cent + 0.5) | 0
|
||||||
local cent_lsb = (cc >> 7) & 127
|
local cent_msb = (cc >> 7) & 127
|
||||||
local cent_msb = cc & 127
|
local cent_lsb = cc & 127
|
||||||
|
|
||||||
--print ("MIDI Note:", nn, "scale-note:", delta_note, "Octave:", delta_octv, "-> Freq:", fq, "= note:", base, "+", cent, "cents (", cc, ")")
|
--[[
|
||||||
|
print (string.format ("MIDI-Note %3d | Octv: %+d Note: %2d -> Freq: %8.2f Hz = note: %3d + %6.3f ct (0x%02x 0x%02x 0x%02x)",
|
||||||
|
nn, delta_octv, delta_note, fq, base, cent, base, cent_msb, cent_lsb))
|
||||||
|
--]]
|
||||||
|
|
||||||
if (base < 0 or base > 127) then
|
if (base < 0 or base > 127) then
|
||||||
-- skip out of bounds MIDI notes
|
if send_bulk then
|
||||||
goto continue
|
if base < 0 then
|
||||||
|
base = 0
|
||||||
|
else
|
||||||
|
base = 127
|
||||||
|
end
|
||||||
|
cent_msb = 0
|
||||||
|
cent_lsb = 0
|
||||||
|
else
|
||||||
|
-- skip out of bounds MIDI notes
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- MIDI Tuning message
|
if send_bulk then
|
||||||
-- http://www.microtonal-synthesis.com/MIDItuning.html
|
local syx = string.char (
|
||||||
local syx = string.char (
|
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
|
||||||
|
)
|
||||||
|
tx_midi (syx, 3, 0)
|
||||||
|
else
|
||||||
|
checksum = 0x07 -- really unused
|
||||||
|
-- MIDI Tuning message
|
||||||
|
-- http://www.microtonal-synthesis.com/MIDItuning.html
|
||||||
|
local syx = string.char (
|
||||||
0xf0, 0x7f, -- realtime sysex
|
0xf0, 0x7f, -- realtime sysex
|
||||||
0x7f, -- target-id
|
0x7f, -- target-id
|
||||||
0x08, 0x02, -- tuning, note change request
|
0x08, 0x02, -- tuning, note change request
|
||||||
@ -176,19 +273,8 @@ function factory () return function ()
|
|||||||
cent_msb, -- MSB of fractional part (1/128 semitone = 100/128 cents = .78125 cent units)
|
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)
|
cent_lsb, -- LSB of fractional part (1/16384 semitone = 100/16384 cents = .0061 cent units)
|
||||||
0xf7
|
0xf7
|
||||||
)
|
)
|
||||||
|
tx_midi (syx, 12, 0)
|
||||||
-- parse message to C/C++ uint8_t* array (Validate message correctness. This
|
|
||||||
-- also returns C/C++ uint8_t* array for direct use with write_immediate_event.)
|
|
||||||
for b = 1, 12 do
|
|
||||||
if parser:process_byte (syx:byte (b)) then
|
|
||||||
tx:write_immediate_event (parser:buffer_size (), parser:midi_buffer ())
|
|
||||||
-- Slow things down a bit to ensure that no messages as lost.
|
|
||||||
-- 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
|
end
|
||||||
|
|
||||||
-- show progress
|
-- show progress
|
||||||
@ -198,9 +284,15 @@ function factory () return function ()
|
|||||||
::continue::
|
::continue::
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if send_bulk and not pdialog:canceled () then
|
||||||
|
tx_midi (string.char ((checksum & 127), 0xf7), 2, 2)
|
||||||
|
end
|
||||||
|
|
||||||
-- hide modal progress dialog and destroy it
|
-- hide modal progress dialog and destroy it
|
||||||
pdialog:done ();
|
pdialog:done ();
|
||||||
|
|
||||||
|
--dump:close ()
|
||||||
|
|
||||||
end end
|
end end
|
||||||
|
|
||||||
-- simple icon
|
-- simple icon
|
||||||
|
Loading…
Reference in New Issue
Block a user