summaryrefslogtreecommitdiff
path: root/share/scripts/_tx_raw_midi_from_file.lua
blob: d75f3cca49f67b8ea50274850743abf906e6e945 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
ardour {
	["type"]    = "EditorAction",
	name        = "Send Raw MIDI from File",
	license     = "MIT",
	author      = "Ardour Team",
	description = [[Read raw binary midi (.syx) from file and send it to a control 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

	local dialog_options = {
		{ type = "file", key = "file", title = "Select .syx 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

	local f = io.open (rv["file"], "rb")

	if not f then
		LuaDialog.Message ("Raw MIDI Tx", "File Not Found", LuaDialog.MessageType.Error, LuaDialog.ButtonType.Close):run ()
		goto out
	end

	do  -- scope for 'local'
		local size = f:seek("end") -- determine file size
		f:seek("set", 0)

		if size > 1048576 then
			local ok = LuaDialog.Message ("Raw MIDI Tx",
				string.format ("File is larger than 1MB.\nFile-size = %.1f kB\n\nContinue?", size / 1024),
				LuaDialog.MessageType.Question, LuaDialog.ButtonType.Yes_No):run ()
			if ok ~= LuaDialog.Response.Yes then
				f:close ()
				goto out
			end
		end
	end

	do -- scope for 'local'
		local midi_byte_count = 0
		local total_read = 0
		local message_count = 0
		local long_message = false

		local async_midi_port = rv["port"] -- reference to port
		local parser = ARDOUR.RawMidiParser () -- construct a MIDI parser

		while true do
			-- read file in 64byte chunks
			local bytes = f:read (64)
			if not bytes then break end
			total_read = total_read + #bytes

			-- parse MIDI data byte-by-byte
			for i = 1, #bytes do
				if parser:process_byte (bytes:byte (i)) then
					if parser:buffer_size () > 255 then
						long_message = true
						print ("WARNING -- single large message > 255, bytes: ", parser:buffer_size ())
					end
					-- parsed complete normalized MIDI message, send it
					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 ())

					-- count msgs and valid bytes sent
					midi_byte_count = midi_byte_count + parser:buffer_size ()
					message_count = message_count + 1
					if 0 == message_count % 50 then
						-- print() wakes up the GUI, prevent stalling the event loop
						print ("Sent", message_count, "messages, bytes so far: ", midi_byte_count)
					end
				end
			end
		end

		f:close ()
		print ("Sent", message_count, "messages, total bytes: ", midi_byte_count, "/", total_read)

		if long_message then
			LuaDialog.Message ("Raw MIDI Tx", "Dataset contained messages longer than 127 bytes. Which may or may not have been transmitted successfully.", LuaDialog.MessageType.Warning, LuaDialog.ButtonType.Close):run ()
		end
	end

	::out::
end end

function icon (params) return function (ctx, width, height, fg)
	ctx:set_source_rgba (ARDOUR.LuaAPI.color_to_rgba (fg))
	local txt = Cairo.PangoLayout (ctx, "ArdourMono ".. math.ceil(math.min (width, height) * .45) .. "px")
	txt:set_text ("S")
	ctx:move_to (1, 1)
	txt:show_in_cairo_context (ctx)

	txt:set_text ("Y")
	local tw, th = txt:get_pixel_size ()
	ctx:move_to (.5 * (width - tw), .5 * (height - th))
	txt:show_in_cairo_context (ctx)

	txt:set_text ("X")
	tw, th = txt:get_pixel_size ()
	ctx:move_to ((width - tw - 1), (height - th -1))
	txt:show_in_cairo_context (ctx)
end end