ac92507a829d0eca63879fe3964eca09181fd70e
[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                 { ["type"] = "input",
42                         name = "Numeric Notes",
43                         doc = "If enabled, note-events displayed numerically",
44                         min = 0, max = 1, default = 0, toggled = true },
45         }
46 end
47
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
57         end
58 end
59
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()
63
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"))
67
68         -- then fill the event buffer
69         local ib = in_map:get (ARDOUR.DataType ("midi"), 0) -- index of 1st midi input
70
71         if ib ~= ARDOUR.ChanMapping.Invalid then
72                 local events = bufs:get_midi (ib):table () -- copy event list into a lua table
73
74                 -- iterate over all MIDI events
75                 for _, e in pairs (events) do
76                         local ev = e:buffer():array()
77                         pos = pos % ringsize + 1
78                         -- copy the data
79                         for j = 1, math.min(e:size(),evlen) do
80                                 buffer[(pos-1)*evlen + j] = ev[j]
81                         end
82                         -- zero unused slots
83                         for j = e:size()+1, evlen do
84                                 buffer[(pos-1)*evlen + j] = 0
85                         end
86                 end
87         end
88
89         self:shmem():atomic_set_int(0, pos)
90
91         self:queue_draw ()
92 end
93
94 local txt = nil -- a pango context
95 local cursize = 0
96 local hex = nil
97 local format_note = nil
98 local show_scm = nil
99
100 function format_note_name(b)
101         return string.format ("%5s", ARDOUR.ParameterDescriptor.midi_note_name (b))
102 end
103
104 function format_note_num(b)
105         return string.format (hex, b)
106 end
107
108
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
114         if evtype == 8 then
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
130                 if message == 0 then
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")
152                 else
153                         return false
154                 end
155         else
156                 return false
157         end
158         ctx:move_to (x, y)
159         txt:show_in_cairo_context (ctx)
160         return true
161 end
162
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]
168
169         if not txt or cursize ~= ctrl[1] then
170                 cursize = math.floor(ctrl[1])
171                 txt = Cairo.PangoLayout (ctx, "Mono " .. cursize)
172         end
173
174         if ctrl[3] > 0 then hex = " %02X" else hex = " %3u" end
175         show_scm = ctrl[4]
176
177         if ctrl[5] > 0 then
178                 format_note = format_note_num
179         else
180                 format_note = format_note_name
181         end
182
183         -- compute the size of the display
184         txt:set_text("0")
185         local _, lineheight = txt:get_pixel_size()
186         local displayheight = math.min(vpadding + (lineheight + vpadding) * count, max_h)
187
188         -- compute starting position (pango anchors text at north-west corner)
189         local x, y = hpadding, displayheight - lineheight - vpadding
190
191         -- clear background
192         ctx:rectangle (0, 0, displaywidth, displayheight)
193         ctx:set_source_rgba (.2, .2, .2, 1.0)
194         ctx:fill ()
195
196         -- color of latest event
197         ctx:set_source_rgba (1.0, 1.0, 1.0, 1.0)
198
199         -- print events
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)
205                 end
206         end
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)
212                 end
213         end
214
215         return {displaywidth, displayheight}
216 end