1 /*
2  * Copyright (C) 2010-2011 Carl Hetherington <carl@carlh.net>
3  * Copyright (C) 2010-2018 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2011 David Robillard <d@drobilla.net>
5  * Copyright (C) 2013 Michael Fisher <mfisher31@gmail.com>
6  * Copyright (C) 2014-2015 Robin Gareus <robin@gareus.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22 
23 #include <stdint.h>
24 
25 #include <sstream>
26 #include <sys/time.h>
27 #include <time.h>
28 
29 #include "pbd/localtime_r.h"
30 #include "pbd/timersub.h"
31 
32 #include "midi++/parser.h"
33 
34 #include "ardour/async_midi_port.h"
35 #include "ardour/midi_port.h"
36 #include "ardour/audioengine.h"
37 #include "ardour/transport_master_manager.h"
38 
39 #include "midi_tracer.h"
40 #include "gui_thread.h"
41 #include "pbd/i18n.h"
42 
43 unsigned int MidiTracer::window_count = 0;
44 
45 using namespace Gtk;
46 using namespace std;
47 using namespace MIDI;
48 using namespace Glib;
49 
MidiTracer()50 MidiTracer::MidiTracer ()
51 	: ArdourWindow (_("MIDI Tracer"))
52 	, line_count_adjustment (200, 1, 2000, 1, 10)
53 	, line_count_spinner (line_count_adjustment)
54 	, line_count_label (_("Line history: "))
55 	, _last_receipt (0)
56 	, autoscroll (true)
57 	, show_hex (true)
58 	, show_delta_time (false)
59 	, fifo (1024)
60 	, buffer_pool ("miditracer", buffer_size, 1024) // 1024 256 byte buffers
61 	, autoscroll_button (_("Auto-Scroll"))
62 	, base_button (_("Decimal"))
63 	, collect_button (_("Enabled"))
64 	, delta_time_button (_("Delta times"))
65 {
66 	g_atomic_int_set (&_update_queued, 0);
67 
68 	std::string portname (string_compose(X_("MIDI Tracer %1"), ++window_count));
69 	tracer_port = ARDOUR::AudioEngine::instance()->register_input_port (ARDOUR::DataType::MIDI, portname, false, ARDOUR::IsInput);
70 
71 	ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect
72 		(_manager_connection, invalidator (*this), boost::bind (&MidiTracer::ports_changed, this), gui_context());
73 
74 	VBox* vbox = manage (new VBox);
75 	vbox->set_spacing (4);
76 
77 	HBox* pbox = manage (new HBox);
78 	pbox->set_spacing (6);
79 	pbox->pack_start (*manage (new Label (_("Port:"))), false, false);
80 
81 	_port_combo.signal_changed().connect (sigc::mem_fun (*this, &MidiTracer::port_changed));
82 	pbox->pack_start (_port_combo);
83 	pbox->show_all ();
84 	vbox->pack_start (*pbox, false, false);
85 
86 	scroller.add (text);
87 	vbox->set_border_width (12);
88 	vbox->pack_start (scroller, true, true);
89 
90 	text.show ();
91 	text.set_name ("MidiTracerTextView");
92 	scroller.show ();
93 	scroller.set_size_request (400, 400);
94 
95 	collect_button.set_active (true);
96 	base_button.set_active (false);
97 	autoscroll_button.set_active (true);
98 
99 	line_count_box.set_spacing (6);
100 	line_count_box.pack_start (line_count_label, false, false);
101 	line_count_box.pack_start (line_count_spinner, false, false);
102 
103 	line_count_spinner.show ();
104 	line_count_label.show ();
105 	line_count_box.show ();
106 
107 	HBox* bbox = manage (new HBox);
108 	bbox->add (line_count_box);
109 	bbox->add (delta_time_button);
110 	bbox->add (base_button);
111 	bbox->add (collect_button);
112 	bbox->add (autoscroll_button);
113 	bbox->show ();
114 
115 	vbox->pack_start (*bbox, false, false);
116 
117 	add (*vbox);
118 
119 	base_button.signal_toggled().connect (sigc::mem_fun (*this, &MidiTracer::base_toggle));
120 	collect_button.signal_toggled().connect (sigc::mem_fun (*this, &MidiTracer::collect_toggle));
121 	autoscroll_button.signal_toggled().connect (sigc::mem_fun (*this, &MidiTracer::autoscroll_toggle));
122 	delta_time_button.signal_toggled().connect (sigc::mem_fun (*this, &MidiTracer::delta_toggle));
123 
124 	base_button.show ();
125 	collect_button.show ();
126 	autoscroll_button.show ();
127 
128 	ports_changed ();
129 	port_changed ();
130 }
131 
~MidiTracer()132 MidiTracer::~MidiTracer()
133 {
134 }
135 
136 void
ports_changed()137 MidiTracer::ports_changed ()
138 {
139 	string const c = _port_combo.get_active_text ();
140 	_port_combo.clear ();
141 
142 	ARDOUR::PortManager::PortList pl;
143 	ARDOUR::AudioEngine::instance()->get_ports (ARDOUR::DataType::MIDI, pl);
144 
145 	if (pl.empty()) {
146 		_port_combo.set_active_text ("");
147 		return;
148 	}
149 
150 	for (ARDOUR::PortManager::PortList::const_iterator i = pl.begin(); i != pl.end(); ++i) {
151 		_port_combo.append_text ((*i)->name());
152 	}
153 
154 	if (c.empty()) {
155 		_port_combo.set_active_text (pl.front()->name());
156 	} else {
157 		_port_combo.set_active_text (c);
158 	}
159 }
160 
161 void
port_changed()162 MidiTracer::port_changed ()
163 {
164 	using namespace ARDOUR;
165 
166 	disconnect ();
167 
168 	if (_port_combo.get_active_text().empty()) {
169 		return;
170 	}
171 
172 	boost::shared_ptr<ARDOUR::Port> p = AudioEngine::instance()->get_port_by_name (_port_combo.get_active_text());
173 
174 	if (!p) {
175 		std::cerr << "port not found: " << _port_combo.get_active_text() << "\n";
176 		return;
177 	}
178 
179 	/* The inheritance heirarchy makes this messy. AsyncMIDIPort has two
180 	 * available MIDI::Parsers what we could connect to, ::self_parser()
181 	 * (from ARDOUR::MidiPort) and ::parser() from MIDI::Port. One day,
182 	 * this mess will all go away ...
183 	 */
184 
185 	/* Some ports have a parser avaiable (Transport Masters and ASYNC ports)
186 	 * and some do not. If the port has a parser already, just attach to it.
187 	 * If not use our local parser and tell the port that we need it to be called.
188 	 */
189 
190 	boost::shared_ptr<AsyncMIDIPort> async = boost::dynamic_pointer_cast<AsyncMIDIPort> (p);
191 
192 	if (!async) {
193 
194 		boost::shared_ptr<ARDOUR::MidiPort> mp = boost::dynamic_pointer_cast<ARDOUR::MidiPort> (p);
195 		if (mp) {
196 			if (mp->flags() & TransportMasterPort) {
197 				boost::shared_ptr<TransportMaster> tm = TransportMasterManager::instance().master_by_port(boost::dynamic_pointer_cast<ARDOUR::Port> (p));
198 				boost::shared_ptr<TransportMasterViaMIDI> tm_midi = boost::dynamic_pointer_cast<TransportMasterViaMIDI> (tm);
199 				if (tm_midi) {
200 					tm_midi->transport_parser().any.connect_same_thread(_parser_connection, boost::bind (&MidiTracer::tracer, this, _1, _2, _3, _4));
201 				}
202 			}
203 			else {
204 				my_parser.any.connect_same_thread (_parser_connection, boost::bind (&MidiTracer::tracer, this, _1, _2, _3, _4));
205 				mp->set_trace (&my_parser);
206 				traced_port = mp;
207 			}
208 		}
209 
210 	} else {
211 		async->parser()->any.connect_same_thread (_parser_connection, boost::bind (&MidiTracer::tracer, this, _1, _2, _3, _4));
212 	}
213 }
214 
215 void
disconnect()216 MidiTracer::disconnect ()
217 {
218 	_parser_connection.disconnect ();
219 
220 	if (traced_port) {
221 		traced_port->set_trace (0);
222 		traced_port.reset ();
223 	}
224 }
225 
226 void
tracer(Parser &,MIDI::byte * msg,size_t len,samplecnt_t now)227 MidiTracer::tracer (Parser&, MIDI::byte* msg, size_t len, samplecnt_t now)
228 {
229 	stringstream ss;
230 	char* buf;
231 	size_t bufsize;
232 	size_t s;
233 
234 	buf = (char *) buffer_pool.alloc ();
235 	bufsize = buffer_size;
236 
237 	if (_last_receipt != 0 && show_delta_time) {
238 		s = snprintf (buf, bufsize, "+%12" PRId64, now - _last_receipt);
239 		bufsize -= s;
240 	} else {
241 		s = snprintf (buf, bufsize, "%12" PRId64, now);
242 		bufsize -= s;
243 	}
244 
245 	_last_receipt = now;
246 
247 	switch ((eventType) msg[0]&0xf0) {
248 	case off:
249 		if (show_hex) {
250 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x %02x\n", "NoteOff", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
251 		} else {
252 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %-3d %-3d\n", "NoteOff", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
253 		}
254 		break;
255 
256 	case on:
257 		if (show_hex) {
258 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x %02x\n", "NoteOn", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
259 		} else {
260 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %-3d %-3d\n", "NoteOn", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
261 		}
262 		break;
263 
264 	case polypress:
265 		if (show_hex) {
266 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x\n", "PolyPressure", (msg[0]&0xf)+1, (int) msg[1]);
267 		} else {
268 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %-3d\n", "PolyPressure", (msg[0]&0xf)+1, (int) msg[1]);
269 		}
270 		break;
271 
272 	case MIDI::controller:
273 		if (show_hex) {
274 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x %02x\n", "Controller", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
275 		} else {
276 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %2d %-3d\n", "Controller", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
277 		}
278 		break;
279 
280 	case program:
281 		if (show_hex) {
282 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x\n", "Program Change", (msg[0]&0xf)+1, (int) msg[1]);
283 		} else {
284 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %-3d\n", "Program Change", (msg[0]&0xf)+1, (int) msg[1]);
285 		}
286 		break;
287 
288 	case chanpress:
289 		if (show_hex) {
290 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x/%-3d\n", "Channel Pressure", (msg[0]&0xf)+1, (int) msg[1], (int) msg[1]);
291 		} else {
292 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x/%-3d\n", "Channel Pressure", (msg[0]&0xf)+1, (int) msg[1], (int) msg[1]);
293 		}
294 		break;
295 
296 	case MIDI::pitchbend:
297 		if (show_hex) {
298 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x %02x\n", "Pitch Bend", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
299 		} else {
300 			s += snprintf (&buf[s], bufsize, "%16s chn %2d %-3d %-3d\n", "Pitch Bend", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
301 		}
302 		break;
303 
304 	case MIDI::sysex:
305 		if (len == 1) {
306 			switch (msg[0]) {
307 			case 0xf8:
308 				s += snprintf (&buf[s], bufsize, "%16s\n", "Clock");
309 				break;
310 			case 0xfa:
311 				s += snprintf (&buf[s], bufsize, "%16s\n", "Start");
312 				break;
313 			case 0xfb:
314 				s += snprintf (&buf[s], bufsize, "%16s\n", "Continue");
315 				break;
316 			case 0xfc:
317 				s += snprintf (&buf[s], bufsize, "%16s\n", "Stop");
318 				break;
319 			case 0xfe:
320 				s += snprintf (&buf[s], bufsize, "%16s\n", "Active Sense");
321 				break;
322 			case 0xff:
323 				s += snprintf (&buf[s], bufsize, "%16s\n", "Reset");
324 				break;
325 			default:
326 				s += snprintf (&buf[s], bufsize, "%16s %02x\n", "Sysex", (int) msg[1]);
327 				break;
328 			}
329 
330 		} else if (len > 5 && msg[0] == 0xf0 && msg[1] == 0x7f && msg[3] == 0x06) {
331 			/* MMC */
332 			int cmd = msg[4];
333 			if (cmd == 0x44 && msg[5] == 0x06 && msg[6] == 0x01) {
334 				s += snprintf (
335 					&buf[s], bufsize, " MMC locate to %02d:%02d:%02d:%02d.%02d\n",
336 					msg[7], msg[8], msg[9], msg[10], msg[11]
337 					);
338 			} else {
339 				std::string name;
340 				if (cmd == 0x1) {
341 					name = "STOP";
342 				} else if (cmd == 0x3) {
343 					name = "DEFERRED PLAY";
344 				} else if (cmd == 0x6) {
345 					name = "RECORD STROBE";
346 				} else if (cmd == 0x7) {
347 					name = "RECORD EXIT";
348 				} else if (cmd == 0x8) {
349 					name = "RECORD PAUSE";
350 				}
351 				if (!name.empty()) {
352 					s += snprintf (&buf[s], bufsize, " MMC command %s\n", name.c_str());
353 				} else {
354 					s += snprintf (&buf[s], bufsize, " MMC command %02x\n", cmd);
355 				}
356 			}
357 
358 		} else if (len == 10 && msg[0] == 0xf0 && msg[1] == 0x7f && msg[9] == 0xf7)  {
359 
360 			/* MTC full sample */
361 			s += snprintf (&buf[s], bufsize, " MTC full sample to %02d:%02d:%02d:%02d\n", msg[5] & 0x1f, msg[6], msg[7], msg[8]);
362 		} else if (len == 3 && msg[0] == MIDI::position) {
363 
364 			/* MIDI Song Position */
365 			int midi_beats = (msg[2] << 7) | msg[1];
366 			s += snprintf (&buf[s], bufsize, "%16s %d\n", "Position", (int) midi_beats);
367 		} else if (len == 2 && msg[0] == MIDI::mtc_quarter) {
368 
369 			s += snprintf (&buf[s], bufsize, "%16s %02x\n", "MTC Quarter", msg[1]);
370 
371 		} else {
372 
373 			/* other sys-ex */
374 
375 			s += snprintf (&buf[s], bufsize, "%16s (%" PRId64 ") = [", "Sysex", len);
376 			bufsize -= s;
377 
378 			for (unsigned int i = 0; i < len && bufsize > 3; ++i) {
379 				if (i > 0) {
380 					s += snprintf (&buf[s], bufsize, " %02x", msg[i]);
381 				} else {
382 					s += snprintf (&buf[s], bufsize, "%02x", msg[i]);
383 				}
384 				bufsize -= s;
385 			}
386 			s += snprintf (&buf[s], bufsize, "]\n");
387 		}
388 		break;
389 
390 	case MIDI::song:
391 		s += snprintf (&buf[s], bufsize, "%16s\n", "Song");
392 		break;
393 
394 	case MIDI::tune:
395 		s += snprintf (&buf[s], bufsize, "%16s\n", "Tune");
396 		break;
397 
398 	case MIDI::eox:
399 		s += snprintf (&buf[s], bufsize, "%16s\n", "EOX");
400 		break;
401 
402 	case MIDI::timing:
403 		s += snprintf (&buf[s], bufsize, "%16s\n", "Timing");
404 		break;
405 
406 	case MIDI::start:
407 		s += snprintf (&buf[s], bufsize, "%16s\n", "Start");
408 		break;
409 
410 	case MIDI::stop:
411 		s += snprintf (&buf[s], bufsize, "%16s\n", "Stop");
412 		break;
413 
414 	case MIDI::contineu:
415 		s += snprintf (&buf[s], bufsize, "%16s\n", "Continue");
416 		break;
417 
418 	case active:
419 		s += snprintf (&buf[s], bufsize, "%16s\n", "Active Sense");
420 		break;
421 
422 	default:
423 		s += snprintf (&buf[s], bufsize, "%16s\n", "Unknown");
424 		break;
425 	}
426 
427 	// If you want to append more to the line, uncomment this first
428 	// bufsize -= s;
429 
430 	assert(s <= buffer_size); // clang dead-assignment
431 
432 	fifo.write (&buf, 1);
433 
434 	if (g_atomic_int_compare_and_exchange (&_update_queued, 0, 1)) {
435 		gui_context()->call_slot (invalidator (*this), boost::bind (&MidiTracer::update, this));
436 	}
437 }
438 
439 void
update()440 MidiTracer::update ()
441 {
442 	bool updated = false;
443 	g_atomic_int_set (&_update_queued, 0);
444 
445 	RefPtr<TextBuffer> buf (text.get_buffer());
446 
447 	int excess = buf->get_line_count() - line_count_adjustment.get_value();
448 
449 	if (excess > 0) {
450 		buf->erase (buf->begin(), buf->get_iter_at_line (excess));
451 	}
452 
453 	char *str;
454 
455 	while (fifo.read (&str, 1)) {
456 		buf->insert (buf->end(), string (str));
457 		buffer_pool.release (str);
458 		updated = true;
459 	}
460 
461 	if (updated && autoscroll) {
462 		scroller.get_vadjustment()->set_value (scroller.get_vadjustment()->get_upper());
463 	}
464 }
465 
466 void
base_toggle()467 MidiTracer::base_toggle ()
468 {
469 	show_hex = !base_button.get_active();
470 }
471 
472 void
delta_toggle()473 MidiTracer::delta_toggle ()
474 {
475 	show_delta_time = delta_time_button.get_active();
476 }
477 
478 void
collect_toggle()479 MidiTracer::collect_toggle ()
480 {
481 	if (collect_button.get_active ()) {
482 		port_changed ();
483 	} else {
484 		disconnect ();
485 	}
486 }
487 
488 void
autoscroll_toggle()489 MidiTracer::autoscroll_toggle ()
490 {
491 	autoscroll = autoscroll_button.get_active ();
492 }
493