3 name = "a-MIDI Monitor",
4 category = "Visualization",
6 author = "Ardour Team",
7 description = [[Display recent MIDIĀ events inline in the mixer strip]]
11 local ringsize = maxevents * 3
13 local hpadding, vpadding = 4, 2
15 function dsp_ioconfig ()
16 return { { midi_in = 1, midi_out = 1, audio_in = -1, audio_out = -1}, }
19 function dsp_params ()
24 doc = "Text size used by the monitor to display midi events",
25 min = 4, max = 12, default = 7, integer = true },
28 doc = "How many events will be shown at most",
29 min = 1, max = maxevents, default = 6, integer = true },
32 doc = "If enabled, values will be printed in hexadecimal notation",
33 min = 0, max = 1, default = 0, toggled = true },
35 name = "System messages",
36 doc = "If enabled, the monitor will show System Control and Real-Time messages",
37 min = 0, max = 1, default = 0, toggled = true },
39 name = "Numeric Notes",
40 doc = "If enabled, note-events displayed numerically",
41 min = 0, max = 1, default = 0, toggled = true },
45 function dsp_init (rate)
46 -- create a shmem space to hold latest midi events
47 -- a int representing the index of the last event, and
48 -- a C-table as storage for events.
49 self:shmem():allocate(1 + ringsize*evlen)
50 self:shmem():atomic_set_int(0, 1)
51 local buffer = self:shmem():to_int(1):array()
52 for i = 1, ringsize*evlen do
53 buffer[i] = -1 -- sentinel for empty slot
57 function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
58 local pos = self:shmem():atomic_get_int(0)
59 local buffer = self:shmem():to_int(1):array()
61 -- passthrough all data
62 ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("audio"))
63 ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("midi"))
65 -- then fill the event buffer
66 local ib = in_map:get (ARDOUR.DataType ("midi"), 0) -- index of 1st midi input
68 if ib ~= ARDOUR.ChanMapping.Invalid then
69 local events = bufs:get_midi (ib):table () -- copy event list into a lua table
71 -- iterate over all MIDI events
72 for _, e in pairs (events) do
73 local ev = e:buffer():array()
74 pos = pos % ringsize + 1
76 for j = 1, math.min(e:size(),evlen) do
77 buffer[(pos-1)*evlen + j] = ev[j]
80 for j = e:size()+1, evlen do
81 buffer[(pos-1)*evlen + j] = 0
86 self:shmem():atomic_set_int(0, pos)
91 local txt = nil -- a pango context
94 local format_note = nil
97 function format_note_name(b)
98 return string.format ("%5s", ARDOUR.ParameterDescriptor.midi_note_name (b))
101 function format_note_num(b)
102 return string.format (hex, b)
106 function show_midi(ctx, x, y, buffer, event)
107 local base = (event - 1) * evlen
108 if buffer[base+1] == -1 then return false end
109 local evtype = buffer[base + 1] >> 4
110 local channel = (buffer[base + 1] & 15) + 1 -- for System Common Messages this has no use
112 txt:set_text(string.format("%02u \u{2669}Off%s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
113 elseif evtype == 9 then
114 txt:set_text(string.format("%02u \u{2669}On %s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
115 elseif evtype == 10 then
116 txt:set_text(string.format("%02u \u{2669}KP %s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
117 elseif evtype == 11 then
118 txt:set_text(string.format("%02u CC " .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
119 elseif evtype == 12 then
120 txt:set_text(string.format("%02u PRG " .. hex, channel, buffer[base+2]))
121 elseif evtype == 13 then
122 txt:set_text(string.format("%02u KP " .. hex, channel, buffer[base+2]))
123 elseif evtype == 14 then
124 txt:set_text(string.format("%02u PBnd" .. hex, channel, buffer[base+2] | buffer[base+3] << 7))
125 elseif show_scm > 0 then -- System Common Message
126 local message = buffer[base + 1] & 15
128 txt:set_text("-- SysEx")
129 elseif message == 1 then
130 txt:set_text(string.format("-- Time Code" .. hex, buffer[base+2]))
131 elseif message == 2 then
132 txt:set_text(string.format("-- Song Pos" .. hex, buffer[base+2] | buffer[base+3] << 7))
133 elseif message == 3 then
134 txt:set_text(string.format("-- Select Song" .. hex, buffer[base+2]))
135 elseif message == 6 then
136 txt:set_text("-- Tune Rq")
137 elseif message == 8 then
138 txt:set_text("-- Timing")
139 elseif message == 10 then
140 txt:set_text("-- Start")
141 elseif message == 11 then
142 txt:set_text("-- Continue")
143 elseif message == 12 then
144 txt:set_text("-- Stop")
145 elseif message == 14 then
146 txt:set_text("-- Active")
147 elseif message == 15 then
148 txt:set_text("-- Reset")
156 txt:show_in_cairo_context (ctx)
160 function render_inline (ctx, displaywidth, max_h)
161 local ctrl = CtrlPorts:array ()
162 local pos = self:shmem():atomic_get_int(0)
163 local buffer = self:shmem():to_int(1):array()
164 local count = ctrl[2]
166 if not txt or cursize ~= ctrl[1] then
167 cursize = math.floor(ctrl[1])
168 txt = Cairo.PangoLayout (ctx, "Mono " .. cursize)
171 if ctrl[3] > 0 then hex = " %02X" else hex = " %3u" end
175 format_note = format_note_num
177 format_note = format_note_name
180 -- compute the size of the display
182 local _, lineheight = txt:get_pixel_size()
183 local displayheight = math.min(vpadding + (lineheight + vpadding) * count, max_h)
185 -- compute starting position (pango anchors text at north-west corner)
186 local x, y = hpadding, displayheight - lineheight - vpadding
189 ctx:rectangle (0, 0, displaywidth, displayheight)
190 ctx:set_source_rgba (.2, .2, .2, 1.0)
193 -- color of latest event
194 ctx:set_source_rgba (1.0, 1.0, 1.0, 1.0)
197 for i = pos, 1, -1 do
198 if y < 0 then break end
199 if show_midi(ctx, x, y, buffer, i) then
200 y = y - lineheight - vpadding
201 ctx:set_source_rgba (.8, .8, .8, 1.0)
204 for i = ringsize, pos+1, -1 do
205 if y < 0 then break end
206 if show_midi(ctx, x, y, buffer, i) then
207 y = y - lineheight - vpadding
208 ctx:set_source_rgba (.8, .8, .8, 1.0)
212 return {displaywidth, displayheight}