add a MTC/sysex test sequence
[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 = 0, audio_out = 0}, }
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_run (_, _, n_samples)
57         assert (type(midiin) == "table")
58         assert (type(midiout) == "table")
59
60         local pos = self:shmem():atomic_get_int(0)
61         local buffer = self:shmem():to_int(1):array()
62
63         -- passthrough midi data, and fill the event buffer
64         for i, d in pairs(midiin) do
65                 local ev = d["data"]
66                 midiout[i] = { time = d["time"], data = ev }
67                 pos = pos % ringsize + 1
68                 for j = 1, math.min(#ev,evlen) do
69                         buffer[(pos-1)*evlen + j] = ev[j]
70                 end
71                 for j = #ev+1, evlen do
72                         buffer[(pos-1)*evlen + j] = 0
73                 end
74         end
75
76         self:shmem():atomic_set_int(0, pos)
77
78         self:queue_draw ()
79 end
80
81 local txt = nil -- a pango context
82 local cursize = 0
83 local hex = nil
84 local show_scm = nil
85
86 function show_midi(ctx, x, y, buffer, event)
87         local base = (event - 1) * evlen
88         if buffer[base+1] == -1 then return end
89         local evtype = buffer[base + 1] >> 4
90         local channel = (buffer[base + 1] & 15) + 1 -- for System Common Messages this has no use
91         if evtype == 8 then
92                 txt:set_text(string.format("%02u \u{2669}Off" .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
93         elseif evtype == 9 then
94                 txt:set_text(string.format("%02u \u{2669}On " .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
95         elseif evtype == 10 then
96                 txt:set_text(string.format("%02u \u{2669}KP " .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
97         elseif evtype == 11 then
98                 txt:set_text(string.format("%02u CC  " .. hex .. hex, channel, buffer[base+2], buffer[base+3]))
99         elseif evtype == 12 then
100                 txt:set_text(string.format("%02u PRG " .. hex, channel, buffer[base+2]))
101         elseif evtype == 13 then
102                 txt:set_text(string.format("%02u  KP " .. hex, channel, buffer[base+2]))
103         elseif evtype == 14 then
104                 txt:set_text(string.format("%02u PBnd" .. hex, channel, buffer[base+2] | buffer[base+3] << 7))
105         elseif show_scm > 0 then -- System Common Message
106                 local message = buffer[base + 1] & 15
107                 if message == 0 then
108                         txt:set_text("-- SysEx")
109                 elseif message == 1 then
110                         txt:set_text(string.format("-- Time Code" .. hex, buffer[base+2]))
111                 elseif message == 2 then
112                         txt:set_text(string.format("-- Song Pos" .. hex, buffer[base+2] | buffer[base+3] << 7))
113                 elseif message == 3 then
114                         txt:set_text(string.format("-- Select Song" .. hex, buffer[base+2]))
115                 elseif message == 6 then
116                         txt:set_text("-- Tune Rq")
117                 elseif message == 8 then
118                         txt:set_text("-- Timing")
119                 elseif message == 10 then
120                         txt:set_text("-- Start")
121                 elseif message == 11 then
122                         txt:set_text("-- Continue")
123                 elseif message == 12 then
124                         txt:set_text("-- Stop")
125                 elseif message == 14 then
126                         txt:set_text("-- Active")
127                 elseif message == 15 then
128                         txt:set_text("-- Reset")
129                 end
130         end
131         ctx:move_to (x, y)
132         txt:show_in_cairo_context (ctx)
133 end
134
135 function render_inline (ctx, displaywidth, max_h)
136         local ctrl = CtrlPorts:array ()
137         local pos = self:shmem():atomic_get_int(0)
138         local buffer = self:shmem():to_int(1):array()
139         local count = ctrl[2]
140
141         if not txt or cursize ~= ctrl[1] then
142                 cursize = math.floor(ctrl[1])
143                 txt = Cairo.PangoLayout (ctx, "Mono " .. cursize)
144         end
145
146         if ctrl[3] > 0 then hex = " %2X" else hex = " %3u" end
147         show_scm = ctrl[4]
148
149         -- compute the size of the display
150         txt:set_text("0")
151         local _, lineheight = txt:get_pixel_size()
152         local displayheight = math.min(vpadding + (lineheight + vpadding) * count, max_h)
153
154         -- compute starting position (pango anchors text at north-west corner)
155         local x, y = hpadding, displayheight - lineheight - vpadding
156
157         -- clear background
158         ctx:rectangle (0, 0, displaywidth, displayheight)
159         ctx:set_source_rgba (.2, .2, .2, 1.0)
160         ctx:fill ()
161
162         -- print latest event
163         ctx:set_source_rgba (1.0, 1.0, 1.0, 1.0)
164         show_midi(ctx, x, y, buffer, pos)
165         y = y - lineheight - vpadding
166
167         -- and remaining events
168         ctx:set_source_rgba (.8, .8, .8, 1.0)
169         for i = pos-1, 1, -1 do
170                 if y < 0 then break end
171                 show_midi(ctx, x, y, buffer, i)
172                 y = y - lineheight - vpadding
173         end
174         for i = ringsize, pos+1, -1 do
175                 if y < 0 then break end
176                 show_midi(ctx, x, y, buffer, i)
177                 y = y - lineheight - vpadding
178         end
179
180         return {displaywidth, displayheight}
181 end