summaryrefslogtreecommitdiff
path: root/scripts/midimon.lua
blob: 7f58fd34de74de8b38c21b566164d3fa1b13760e (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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
ardour {
	["type"]    = "dsp",
	name        = "a-MIDI Monitor",
	category    = "Visualization",
	license     = "GPLv2",
	author      = "Ardour Team",
	description = [[Display recent MIDIĀ events inline in the mixer strip]]
}

local maxevents = 20
local ringsize = maxevents * 3
local evlen = 3
local hpadding, vpadding = 4, 2

function dsp_ioconfig ()
	return { { midi_in = 1, midi_out = 1, audio_in = -1, audio_out = -1}, }
end

function dsp_params ()
	return
	{
		{ ["type"] = "input",
			name = "Font size",
			doc = "Text size used by the monitor to display midi events",
			min = 4, max = 12, default = 7, integer = true },
		{ ["type"] = "input",
			name = "Line count",
			doc = "How many events will be shown at most",
			min = 1, max = maxevents, default = 6, integer = true },
		{ ["type"] = "input",
			name = "Hexadecimal",
			doc = "If enabled, values will be printed in hexadecimal notation",
			min = 0, max = 1, default = 0, toggled = true },
		{ ["type"] = "input",
			name = "System messages",
			doc = "If enabled, the monitor will show System Control and Real-Time messages",
			min = 0, max = 1, default = 0, toggled = true },
		{ ["type"] = "input",
			name = "Numeric Notes",
			doc = "If enabled, note-events displayed numerically",
			min = 0, max = 1, default = 0, toggled = true },
	}
end

function dsp_init (rate)
	-- create a shmem space to hold latest midi events
	-- a int representing the index of the last event, and
	-- a C-table as storage for events.
	self:shmem():allocate(1 + ringsize*evlen)
	self:shmem():atomic_set_int(0, 1)
	local buffer = self:shmem():to_int(1):array()
	for i = 1, ringsize*evlen do
		buffer[i] = -1 -- sentinel for empty slot
	end
end

function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
	local pos = self:shmem():atomic_get_int(0)
	local buffer = self:shmem():to_int(1):array()

	-- passthrough all data
	ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("audio"))
	ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("midi"))

	-- then fill the event buffer
	local ib = in_map:get (ARDOUR.DataType ("midi"), 0) -- index of 1st midi input

	if ib ~= ARDOUR.ChanMapping.Invalid then
		local events = bufs:get_midi (ib):table () -- copy event list into a lua table

		-- iterate over all MIDI events
		for _, e in pairs (events) do
			local ev = e:buffer():array()
			pos = pos % ringsize + 1
			-- copy the data
			for j = 1, math.min(e:size(),evlen) do
				buffer[(pos-1)*evlen + j] = ev[j]
			end
			-- zero unused slots
			for j = e:size()+1, evlen do
				buffer[(pos-1)*evlen + j] = 0
			end
		end
	end

	self:shmem():atomic_set_int(0, pos)

	self:queue_draw ()
end

local txt = nil -- a pango context
local cursize = 0
local hex = nil
local format_note = nil
local show_scm = nil

function format_note_name(b)
	return string.format ("%5s", ARDOUR.ParameterDescriptor.midi_note_name (b))
end

function format_note_num(b)
	return string.format (hex, b)
end


function show_midi(ctx, x, y, buffer, event)
	local base = (event - 1) * evlen
	if buffer[base+1] == -1 then return false end
	local evtype = buffer[base + 1] >> 4
	local channel = (buffer[base + 1] & 15) + 1 -- for System Common Messages this has no use
	if evtype == 8 then
		txt:set_text(string.format("%02u \u{2669}Off%s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
	elseif evtype == 9 then
		txt:set_text(string.format("%02u \u{2669}On %s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
	elseif evtype == 10 then
		txt:set_text(string.format("%02u \u{2669}KP %s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
	elseif evtype == 11 then
		txt:set_text(string.format("%02u CC  " .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
	elseif evtype == 12 then
		txt:set_text(string.format("%02u PRG " .. hex, channel, buffer[base+2]))
	elseif evtype == 13 then
		txt:set_text(string.format("%02u  KP " .. hex, channel, buffer[base+2]))
	elseif evtype == 14 then
		txt:set_text(string.format("%02u PBnd" .. hex, channel, buffer[base+2] | buffer[base+3] << 7))
	elseif show_scm > 0 then -- System Common Message
		local message = buffer[base + 1] & 15
		if message == 0 then
			txt:set_text("-- SysEx")
		elseif message == 1 then
			txt:set_text(string.format("-- Time Code" .. hex, buffer[base+2]))
		elseif message == 2 then
			txt:set_text(string.format("-- Song Pos" .. hex, buffer[base+2] | buffer[base+3] << 7))
		elseif message == 3 then
			txt:set_text(string.format("-- Select Song" .. hex, buffer[base+2]))
		elseif message == 6 then
			txt:set_text("-- Tune Rq")
		elseif message == 8 then
			txt:set_text("-- Timing")
		elseif message == 10 then
			txt:set_text("-- Start")
		elseif message == 11 then
			txt:set_text("-- Continue")
		elseif message == 12 then
			txt:set_text("-- Stop")
		elseif message == 14 then
			txt:set_text("-- Active")
		elseif message == 15 then
			txt:set_text("-- Reset")
		else
			return false
		end
	else
		return false
	end
	ctx:move_to (x, y)
	txt:show_in_cairo_context (ctx)
	return true
end

function render_inline (ctx, displaywidth, max_h)
	local ctrl = CtrlPorts:array ()
	local pos = self:shmem():atomic_get_int(0)
	local buffer = self:shmem():to_int(1):array()
	local count = ctrl[2]

	if not txt or cursize ~= ctrl[1] then
		cursize = math.floor(ctrl[1])
		txt = Cairo.PangoLayout (ctx, "Mono " .. cursize)
	end

	if ctrl[3] > 0 then hex = " %02X" else hex = " %3u" end
	show_scm = ctrl[4]

	if ctrl[5] > 0 then
		format_note = format_note_num
	else
		format_note = format_note_name
	end

	-- compute the size of the display
	txt:set_text("0")
	local _, lineheight = txt:get_pixel_size()
	local displayheight = math.min(vpadding + (lineheight + vpadding) * count, max_h)

	-- compute starting position (pango anchors text at north-west corner)
	local x, y = hpadding, displayheight - lineheight - vpadding

	-- clear background
	ctx:rectangle (0, 0, displaywidth, displayheight)
	ctx:set_source_rgba (.2, .2, .2, 1.0)
	ctx:fill ()

	-- color of latest event
	ctx:set_source_rgba (1.0, 1.0, 1.0, 1.0)

	-- print events
	for i = pos, 1, -1 do
		if y < 0 then break end
		if show_midi(ctx, x, y, buffer, i) then
			y = y - lineheight - vpadding
			ctx:set_source_rgba (.8, .8, .8, 1.0)
		end
	end
	for i = ringsize, pos+1, -1 do
		if y < 0 then break end
		if show_midi(ctx, x, y, buffer, i) then
			y = y - lineheight - vpadding
			ctx:set_source_rgba (.8, .8, .8, 1.0)
		end
	end

	return {displaywidth, displayheight}
end