prefix blessed scripted DSP plugins with a-*
[ardour.git] / scripts / midimon.lua
1 ardour {
2         ["type"]    = "dsp",
3         name        = "a-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 { { midi_in = 1, midi_out = 1, audio_in = -1, audio_out = -1}, }
17 end
18
19 function dsp_params ()
20         return
21         {
22                 { ["type"] = "input",
23                         name = "Font size",
24                         doc = "Text size used by the monitor to display midi events",
25                         min = 4, max = 12, default = 7, integer = true },
26                 { ["type"] = "input",
27                         name = "Line count",
28                         doc = "How many events will be shown at most",
29                         min = 1, max = maxevents, default = 6, integer = true },
30                 { ["type"] = "input",
31                         name = "Hexadecimal",
32                         doc = "If enabled, values will be printed in hexadecimal notation",
33                         min = 0, max = 1, default = 0, toggled = true },
34                 { ["type"] = "input",
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 },
38                 { ["type"] = "input",
39                         name = "Numeric Notes",
40                         doc = "If enabled, note-events displayed numerically",
41                         min = 0, max = 1, default = 0, toggled = true },
42         }
43 end
44
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
54         end
55 end
56
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()
60
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"))
64
65         -- then fill the event buffer
66         local ib = in_map:get (ARDOUR.DataType ("midi"), 0) -- index of 1st midi input
67
68         if ib ~= ARDOUR.ChanMapping.Invalid then
69                 local events = bufs:get_midi (ib):table () -- copy event list into a lua table
70
71                 -- iterate over all MIDI events
72                 for _, e in pairs (events) do
73                         local ev = e:buffer():array()
74                         pos = pos % ringsize + 1
75                         -- copy the data
76                         for j = 1, math.min(e:size(),evlen) do
77                                 buffer[(pos-1)*evlen + j] = ev[j]
78                         end
79                         -- zero unused slots
80                         for j = e:size()+1, evlen do
81                                 buffer[(pos-1)*evlen + j] = 0
82                         end
83                 end
84         end
85
86         self:shmem():atomic_set_int(0, pos)
87
88         self:queue_draw ()
89 end
90
91 local txt = nil -- a pango context
92 local cursize = 0
93 local hex = nil
94 local format_note = nil
95 local show_scm = nil
96
97 function format_note_name(b)
98         return string.format ("%5s", ARDOUR.ParameterDescriptor.midi_note_name (b))
99 end
100
101 function format_note_num(b)
102         return string.format (hex, b)
103 end
104
105
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
111         if evtype == 8 then
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
127                 if message == 0 then
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")
149                 else
150                         return false
151                 end
152         else
153                 return false
154         end
155         ctx:move_to (x, y)
156         txt:show_in_cairo_context (ctx)
157         return true
158 end
159
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]
165
166         if not txt or cursize ~= ctrl[1] then
167                 cursize = math.floor(ctrl[1])
168                 txt = Cairo.PangoLayout (ctx, "Mono " .. cursize)
169         end
170
171         if ctrl[3] > 0 then hex = " %02X" else hex = " %3u" end
172         show_scm = ctrl[4]
173
174         if ctrl[5] > 0 then
175                 format_note = format_note_num
176         else
177                 format_note = format_note_name
178         end
179
180         -- compute the size of the display
181         txt:set_text("0")
182         local _, lineheight = txt:get_pixel_size()
183         local displayheight = math.min(vpadding + (lineheight + vpadding) * count, max_h)
184
185         -- compute starting position (pango anchors text at north-west corner)
186         local x, y = hpadding, displayheight - lineheight - vpadding
187
188         -- clear background
189         ctx:rectangle (0, 0, displaywidth, displayheight)
190         ctx:set_source_rgba (.2, .2, .2, 1.0)
191         ctx:fill ()
192
193         -- color of latest event
194         ctx:set_source_rgba (1.0, 1.0, 1.0, 1.0)
195
196         -- print events
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)
202                 end
203         end
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)
209                 end
210         end
211
212         return {displaywidth, displayheight}
213 end