summaryrefslogtreecommitdiff
path: root/share/scripts/tx_raw_midi_from_file.lua
blob: 303767a656135b82f6bed611dae63ea7ddf7fcf2 (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
127
128
129
130
131
132
133
134
ardour {
	["type"]    = "EditorAction",
	name        = "Send Raw MIDI from File",
	license     = "MIT",
	author      = "Ardour Team",
	description = [[Read raw binary midi (.syx) from a file and send it 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

	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

	local size
	do  -- scope for '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

		-- prepare progress dialog
		local pdialog = LuaDialog.ProgressWindow ("Tx MIDI", true)
		pdialog:progress (0, "Transmitting");

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

		while true do
			if pdialog:canceled () then break end
			-- 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 % 10 then
						pdialog:progress (total_read / size, string.format ("Transmitting %.1f kB", midi_byte_count / 1024))
					end
					if pdialog:canceled () then break end
				end
			end
		end

		f:close ()
		-- hide modal progress dialog and destroy it
		pdialog:done ();
		print ("Sent", message_count, "messages,  bytes: ", midi_byte_count, " read:", total_read, "/", size)

		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