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 { { audio_in = -1, audio_out = -1}, }
19 function dsp_has_midi_input () return true end
20 function dsp_has_midi_output () return true end
22 function dsp_params ()
27 doc = "Text size used by the monitor to display midi events",
28 min = 4, max = 12, default = 7, integer = true },
31 doc = "How many events will be shown at most",
32 min = 1, max = maxevents, default = 6, integer = true },
35 doc = "If enabled, values will be printed in hexadecimal notation",
36 min = 0, max = 1, default = 0, toggled = true },
38 name = "System messages",
39 doc = "If enabled, the monitor will show System Control and Real-Time messages",
40 min = 0, max = 1, default = 0, toggled = true },
42 name = "Numeric Notes",
43 doc = "If enabled, note-events displayed numerically",
44 min = 0, max = 1, default = 0, toggled = true },
48 function dsp_init (rate)
49 -- create a shmem space to hold latest midi events
50 -- a int representing the index of the last event, and
51 -- a C-table as storage for events.
52 self:shmem():allocate(1 + ringsize*evlen)
53 self:shmem():atomic_set_int(0, 1)
54 local buffer = self:shmem():to_int(1):array()
55 for i = 1, ringsize*evlen do
56 buffer[i] = -1 -- sentinel for empty slot
60 function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
61 local pos = self:shmem():atomic_get_int(0)
62 local buffer = self:shmem():to_int(1):array()
64 -- passthrough all data
65 ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("audio"))
66 ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("midi"))
68 -- then fill the event buffer
69 local ib = in_map:get (ARDOUR.DataType ("midi"), 0) -- index of 1st midi input
71 if ib ~= ARDOUR.ChanMapping.Invalid then
72 local events = bufs:get_midi (ib):table () -- copy event list into a lua table
74 -- iterate over all MIDI events
75 for _, e in pairs (events) do
76 local ev = e:buffer():array()
77 pos = pos % ringsize + 1
79 for j = 1, math.min(e:size(),evlen) do
80 buffer[(pos-1)*evlen + j] = ev[j]
83 for j = e:size()+1, evlen do
84 buffer[(pos-1)*evlen + j] = 0
89 self:shmem():atomic_set_int(0, pos)
94 local txt = nil -- a pango context
97 local format_note = nil
100 function format_note_name(b)
101 return string.format ("%5s", ARDOUR.ParameterDescriptor.midi_note_name (b))
104 function format_note_num(b)
105 return string.format (hex, b)
109 function show_midi(ctx, x, y, buffer, event)
110 local base = (event - 1) * evlen
111 if buffer[base+1] == -1 then return false end
112 local evtype = buffer[base + 1] >> 4
113 local channel = (buffer[base + 1] & 15) + 1 -- for System Common Messages this has no use
115 txt:set_text(string.format("%02u \u{2669}Off%s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
116 elseif evtype == 9 then
117 txt:set_text(string.format("%02u \u{2669}On %s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
118 elseif evtype == 10 then
119 txt:set_text(string.format("%02u \u{2669}KP %s" .. hex, channel, format_note(buffer[base+2]), buffer[base+3]))
120 elseif evtype == 11 then
121 txt:set_text(string.format("%02u CC " .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
122 elseif evtype == 12 then
123 txt:set_text(string.format("%02u PRG " .. hex, channel, buffer[base+2]))
124 elseif evtype == 13 then
125 txt:set_text(string.format("%02u KP " .. hex, channel, buffer[base+2]))
126 elseif evtype == 14 then
127 txt:set_text(string.format("%02u PBnd" .. hex, channel, buffer[base+2] | buffer[base+3] << 7))
128 elseif show_scm > 0 then -- System Common Message
129 local message = buffer[base + 1] & 15
131 txt:set_text("-- SysEx")
132 elseif message == 1 then
133 txt:set_text(string.format("-- Time Code" .. hex, buffer[base+2]))
134 elseif message == 2 then
135 txt:set_text(string.format("-- Song Pos" .. hex, buffer[base+2] | buffer[base+3] << 7))
136 elseif message == 3 then
137 txt:set_text(string.format("-- Select Song" .. hex, buffer[base+2]))
138 elseif message == 6 then
139 txt:set_text("-- Tune Rq")
140 elseif message == 8 then
141 txt:set_text("-- Timing")
142 elseif message == 10 then
143 txt:set_text("-- Start")
144 elseif message == 11 then
145 txt:set_text("-- Continue")
146 elseif message == 12 then
147 txt:set_text("-- Stop")
148 elseif message == 14 then
149 txt:set_text("-- Active")
150 elseif message == 15 then
151 txt:set_text("-- Reset")
159 txt:show_in_cairo_context (ctx)
163 function render_inline (ctx, displaywidth, max_h)
164 local ctrl = CtrlPorts:array ()
165 local pos = self:shmem():atomic_get_int(0)
166 local buffer = self:shmem():to_int(1):array()
167 local count = ctrl[2]
169 if not txt or cursize ~= ctrl[1] then
170 cursize = math.floor(ctrl[1])
171 txt = Cairo.PangoLayout (ctx, "Mono " .. cursize)
174 if ctrl[3] > 0 then hex = " %02X" else hex = " %3u" end
178 format_note = format_note_num
180 format_note = format_note_name
183 -- compute the size of the display
185 local _, lineheight = txt:get_pixel_size()
186 local displayheight = math.min(vpadding + (lineheight + vpadding) * count, max_h)
188 -- compute starting position (pango anchors text at north-west corner)
189 local x, y = hpadding, displayheight - lineheight - vpadding
192 ctx:rectangle (0, 0, displaywidth, displayheight)
193 ctx:set_source_rgba (.2, .2, .2, 1.0)
196 -- color of latest event
197 ctx:set_source_rgba (1.0, 1.0, 1.0, 1.0)
200 for i = pos, 1, -1 do
201 if y < 0 then break end
202 if show_midi(ctx, x, y, buffer, i) then
203 y = y - lineheight - vpadding
204 ctx:set_source_rgba (.8, .8, .8, 1.0)
207 for i = ringsize, pos+1, -1 do
208 if y < 0 then break end
209 if show_midi(ctx, x, y, buffer, i) then
210 y = y - lineheight - vpadding
211 ctx:set_source_rgba (.8, .8, .8, 1.0)
215 return {displaywidth, displayheight}