Make MIDI monitor a pass-through for audio and midi
[ardour.git] / scripts / midimon.lua
1 ardour {
2         ["type"]    = "dsp",
3         name        = "MIDI Monitor",
4         category    = "Visualization",
5         license     = "GPLv2",
6         author      = "Ardour Team",
7         description = [[Display recent MIDIĀ events inline in the mixer strip]]
8 }
9
10 local maxevents = 20
11 local ringsize = maxevents * 3
12 local evlen = 3
13 local hpadding, vpadding = 4, 2
14
15 function dsp_ioconfig ()
16         return { { audio_in = -1, audio_out = -1}, }
17 end
18
19 function dsp_has_midi_input () return true end
20 function dsp_has_midi_output () return true end
21
22 function dsp_params ()
23         return
24         {
25                 { ["type"] = "input",
26                         name = "Font size",
27                         doc = "Text size used by the monitor to display midi events",
28                         min = 4, max = 12, default = 7, integer = true },
29                 { ["type"] = "input",
30                         name = "Line count",
31                         doc = "How many events will be shown at most",
32                         min = 1, max = maxevents, default = 6, integer = true },
33                 { ["type"] = "input",
34                         name = "Hexadecimal",
35                         doc = "If enabled, values will be printed in hexadecimal notation",
36                         min = 0, max = 1, default = 0, toggled = true },
37                 { ["type"] = "input",
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 }
41         }
42 end
43
44 function dsp_init (rate)
45         -- create a shmem space to hold latest midi events
46         -- a int representing the index of the last event, and
47         -- a C-table as storage for events.
48         self:shmem():allocate(1 + ringsize*evlen)
49         self:shmem():atomic_set_int(0, 1)
50         local buffer = self:shmem():to_int(1):array()
51         for i = 1, ringsize*evlen do
52                 buffer[i] = -1 -- sentinel for empty slot
53         end
54 end
55
56 function dsp_runmap (bufs, in_map, out_map, n_samples, offset)
57         local pos = self:shmem():atomic_get_int(0)
58         local buffer = self:shmem():to_int(1):array()
59
60         -- passthrough all data
61         ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("audio"))
62         ARDOUR.DSP.process_map (bufs, in_map, out_map, n_samples, offset, ARDOUR.DataType ("midi"))
63
64         -- then fill the event buffer
65         local ib = in_map:get (ARDOUR.DataType ("midi"), 0) -- index of 1st midi input
66
67         if ib ~= ARDOUR.ChanMapping.Invalid then
68                 local events = bufs:get_midi (ib):table () -- copy event list into a lua table
69
70                 -- iterate over all MIDI events
71                 for _, e in pairs (events) do
72                         local ev = e:buffer():array()
73                         pos = pos % ringsize + 1
74                         -- copy the data
75                         for j = 1, math.min(e:size(),evlen) do
76                                 buffer[(pos-1)*evlen + j] = ev[j]
77                         end
78                         -- zero unused slots
79                         for j = e:size()+1, evlen do
80                                 buffer[(pos-1)*evlen + j] = 0
81                         end
82                 end
83         end
84
85         self:shmem():atomic_set_int(0, pos)
86
87         self:queue_draw ()
88 end
89
90 local txt = nil -- a pango context
91 local cursize = 0
92 local hex = nil
93 local show_scm = nil
94
95 function show_midi(ctx, x, y, buffer, event)
96         local base = (event - 1) * evlen
97         if buffer[base+1] == -1 then return false end
98         local evtype = buffer[base + 1] >> 4
99         local channel = (buffer[base + 1] & 15) + 1 -- for System Common Messages this has no use
100         if evtype == 8 then
101                 txt:set_text(string.format("%02u \u{2669}Off" .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
102         elseif evtype == 9 then
103                 txt:set_text(string.format("%02u \u{2669}On " .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
104         elseif evtype == 10 then
105                 txt:set_text(string.format("%02u \u{2669}KP " .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
106         elseif evtype == 11 then
107                 txt:set_text(string.format("%02u CC  " .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
108         elseif evtype == 12 then
109                 txt:set_text(string.format("%02u PRG " .. hex, channel, buffer[base+2]))
110         elseif evtype == 13 then
111                 txt:set_text(string.format("%02u  KP " .. hex, channel, buffer[base+2]))
112         elseif evtype == 14 then
113                 txt:set_text(string.format("%02u PBnd" .. hex, channel, buffer[base+2] | buffer[base+3] << 7))
114         elseif show_scm > 0 then -- System Common Message
115                 local message = buffer[base + 1] & 15
116                 if message == 0 then
117                         txt:set_text("-- SysEx")
118                 elseif message == 1 then
119                         txt:set_text(string.format("-- Time Code" .. hex, buffer[base+2]))
120                 elseif message == 2 then
121                         txt:set_text(string.format("-- Song Pos" .. hex, buffer[base+2] | buffer[base+3] << 7))
122                 elseif message == 3 then
123                         txt:set_text(string.format("-- Select Song" .. hex, buffer[base+2]))
124                 elseif message == 6 then
125                         txt:set_text("-- Tune Rq")
126                 elseif message == 8 then
127                         txt:set_text("-- Timing")
128                 elseif message == 10 then
129                         txt:set_text("-- Start")
130                 elseif message == 11 then
131                         txt:set_text("-- Continue")
132                 elseif message == 12 then
133                         txt:set_text("-- Stop")
134                 elseif message == 14 then
135                         txt:set_text("-- Active")
136                 elseif message == 15 then
137                         txt:set_text("-- Reset")
138                 else
139                         return false
140                 end
141         else
142                 return false
143         end
144         ctx:move_to (x, y)
145         txt:show_in_cairo_context (ctx)
146         return true
147 end
148
149 function render_inline (ctx, displaywidth, max_h)
150         local ctrl = CtrlPorts:array ()
151         local pos = self:shmem():atomic_get_int(0)
152         local buffer = self:shmem():to_int(1):array()
153         local count = ctrl[2]
154
155         if not txt or cursize ~= ctrl[1] then
156                 cursize = math.floor(ctrl[1])
157                 txt = Cairo.PangoLayout (ctx, "Mono " .. cursize)
158         end
159
160         if ctrl[3] > 0 then hex = " %02X" else hex = " %3u" end
161         show_scm = ctrl[4]
162
163         -- compute the size of the display
164         txt:set_text("0")
165         local _, lineheight = txt:get_pixel_size()
166         local displayheight = math.min(vpadding + (lineheight + vpadding) * count, max_h)
167
168         -- compute starting position (pango anchors text at north-west corner)
169         local x, y = hpadding, displayheight - lineheight - vpadding
170
171         -- clear background
172         ctx:rectangle (0, 0, displaywidth, displayheight)
173         ctx:set_source_rgba (.2, .2, .2, 1.0)
174         ctx:fill ()
175
176         -- color of latest event
177         ctx:set_source_rgba (1.0, 1.0, 1.0, 1.0)
178
179         -- print events
180         for i = pos, 1, -1 do
181                 if y < 0 then break end
182                 if show_midi(ctx, x, y, buffer, i) then
183                         y = y - lineheight - vpadding
184                         ctx:set_source_rgba (.8, .8, .8, 1.0)
185                 end
186         end
187         for i = ringsize, pos+1, -1 do
188                 if y < 0 then break end
189                 if show_midi(ctx, x, y, buffer, i) then
190                         y = y - lineheight - vpadding
191                         ctx:set_source_rgba (.8, .8, .8, 1.0)
192                 end
193         end
194
195         return {displaywidth, displayheight}
196 end