screw up MIDI control "automation" tracks quite a bit while trying to improve menu structure BUT add a MIDI tracer window and fix a crashing bug caused by regions prematurely being destroyed

git-svn-id: svn://localhost/ardour2/branches/3.0@6465 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2010-01-06 21:56:23 +00:00
parent 285e4c648a
commit ed6545eb5b
9 changed files with 517 additions and 31 deletions

View file

@ -116,6 +116,7 @@ MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess,
_view = new MidiStreamView (*this);
ignore_toggle = false;
_ignore_toggle_parameter = false;
mute_button->set_active (false);
solo_button->set_active (false);
@ -371,17 +372,57 @@ MidiTimeAxisView::build_automation_action_menu ()
MenuList& automation_items = automation_action_menu->items();
automation_items.push_back (SeparatorElem());
automation_items.push_back (MenuElem (_("Controller..."),
automation_items.push_back (MenuElem (_("Show Controller..."),
sigc::mem_fun(*this, &MidiTimeAxisView::add_cc_track)));
automation_items.push_back (MenuElem (_("Program Change"),
sigc::bind(sigc::mem_fun(*this, &MidiTimeAxisView::add_parameter_track),
Evoral::Parameter(MidiPgmChangeAutomation))));
automation_items.push_back (MenuElem (_("Bender"),
sigc::bind(sigc::mem_fun(*this, &MidiTimeAxisView::add_parameter_track),
Evoral::Parameter(MidiPitchBenderAutomation))));
automation_items.push_back (MenuElem (_("Pressure"),
sigc::bind(sigc::mem_fun(*this, &MidiTimeAxisView::add_parameter_track),
Evoral::Parameter(MidiChannelPressureAutomation))));
add_basic_parameter_menu_item (automation_items, _("Program Change"), Evoral::Parameter (MidiPgmChangeAutomation));
add_basic_parameter_menu_item (automation_items, _("Bender"), Evoral::Parameter (MidiPitchBenderAutomation));
add_basic_parameter_menu_item (automation_items, _("Pressure"), Evoral::Parameter (MidiChannelPressureAutomation));
}
void
MidiTimeAxisView::add_basic_parameter_menu_item (Menu_Helpers::MenuList& items, const string& label, Evoral::Parameter param)
{
items.push_back (Menu_Helpers::CheckMenuElem
(label, sigc::bind (sigc::mem_fun
(*this, &MidiTimeAxisView::toggle_parameter_track), param)));
// cerr << "Create a new menu item from " << param.type() << '/' << param.id() << '/' << (int) param.channel() << endl;
uint16_t selected_channels = _channel_selector.get_selected_channels();
bool visible = false;
for (uint8_t i = 0; i < 16; i++) {
if (selected_channels & (0x0001 << i)) {
Evoral::Parameter param_with_channel(param.type(), i, param.id());
// cerr << "\tChecking on channel " << (int) i << " via " << param_with_channel.type() << '/' << param_with_channel.id() << endl;
RouteAutomationNode* node = automation_track (param_with_channel);
if (node) {
if (node->track->marked_for_display()) {
visible = true;
// cerr << "\tGot a track, and it appears visible\n";
break;
} else {
// cerr << "\tGot a track, and it appears hidden\n";
}
} else {
// cerr << "\tno track found\n";
}
}
}
CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&items.back());
cmi->set_active (visible);
pair<ParameterMenuMap::iterator,bool> result;
result = parameter_menu_map.insert (pair<Evoral::Parameter,CheckMenuItem*> (param, cmi));
if (!result.second) {
/* it already exists, but we're asssigning a new menu item to it */
result.first->second = cmi;
}
}
Gtk::Menu*
@ -533,29 +574,63 @@ MidiTimeAxisView::add_cc_track()
create_automation_child(param, true);
}
/** Add an automation track for the given parameter (pitch bend, channel pressure).
/** Toggle an automation track for the given parameter (pitch bend, channel pressure).
* Will add track if necessary.
*/
void
MidiTimeAxisView::add_parameter_track(const Evoral::Parameter& param)
MidiTimeAxisView::toggle_parameter_track(const Evoral::Parameter& param)
{
if (_ignore_toggle_parameter) {
return;
}
cerr << "CHANGE VISIBILITY OF " << param.type() << '/' << param.id() << '/' << (int) param.channel() << endl;
if ( !EventTypeMap::instance().is_midi_parameter(param) ) {
error << "MidiTimeAxisView: unknown automation child "
<< ARDOUR::EventTypeMap::instance().to_symbol(param) << endmsg;
return;
}
map<Evoral::Parameter,CheckMenuItem*>::iterator x = parameter_menu_map.find (param);
if (x == parameter_menu_map.end()) {
cerr << "Param not found in pm map\n";
return;
}
bool yn = x->second->get_active ();
cerr << "Menu item state for " << param.type() << '/' << param.id() << '/' << (int) param.channel() << ' ' << yn << endl;
cerr << "toggle param " << param.type() << '/' << param.id() << '/' << (int) param.channel() << " from " << !yn << " to " << yn << endl;
// create the parameter lane for each selected channel
uint16_t selected_channels = _channel_selector.get_selected_channels();
for (uint8_t i = 0; i < 16; i++) {
if (selected_channels & (0x0001 << i)) {
Evoral::Parameter param_with_channel(param.type(), i, param.id());
create_automation_child(param_with_channel, true);
RouteAutomationNode* node = automation_track (param_with_channel);
if (!node) {
cerr << "\tNO EXISTING TRACK FOR chn " << (int) i << endl;
if (yn) {
create_automation_child (param_with_channel, true);
}
} else {
cerr << "\tTRACK EXISTS, set its menu item to " << yn << " to change its visibilty\n";
node->menu_item->set_active (yn);
}
}
}
_ignore_toggle_parameter = true;
x->second->set_active (yn);
_ignore_toggle_parameter = false;
}
/** Hide an automation track for the given parameter (pitch bend, channel pressure).
*/
void
MidiTimeAxisView::create_automation_child (const Evoral::Parameter& param, bool show)
{

View file

@ -72,7 +72,7 @@ class MidiTimeAxisView : public RouteTimeAxisView
void show_all_automation ();
void show_existing_automation ();
void add_cc_track ();
void add_parameter_track (const Evoral::Parameter& param);
void toggle_parameter_track (const Evoral::Parameter& param);
void create_automation_child (const Evoral::Parameter& param, bool show);
ARDOUR::NoteMode note_mode() const { return _note_mode; }
@ -140,6 +140,12 @@ class MidiTimeAxisView : public RouteTimeAxisView
Gtk::Menu* build_def_channel_menu();
void set_default_channel (int);
void toggle_midi_thru ();
void add_basic_parameter_menu_item (Gtk::Menu_Helpers::MenuList& items, const std::string& label, Evoral::Parameter param);
bool _ignore_toggle_parameter;
typedef std::map<Evoral::Parameter,Gtk::CheckMenuItem*> ParameterMenuMap;
ParameterMenuMap parameter_menu_map;
};
#endif /* __ardour_midi_time_axis_h__ */

283
gtk2_ardour/midi_tracer.cc Normal file
View file

@ -0,0 +1,283 @@
#define __STDC_FORMAT_MACROS 1
#include <stdint.h>
#include <sstream>
#include <sys/time.h>
#include <time.h>
#include "midi++/parser.h"
#include "midi_tracer.h"
#include "gui_thread.h"
#include "i18n.h"
using namespace Gtk;
using namespace std;
using namespace MIDI;
using namespace Glib;
MidiTracer::MidiTracer (const std::string& name, Parser& p)
: ArdourDialog (string_compose (_("MIDI Trace %1"), name))
, parser (p)
, line_count_adjustment (200, 1, 2000, 1, 10)
, line_count_spinner (line_count_adjustment)
, line_count_label (_("Store this many lines: "))
, autoscroll (true)
, show_hex (true)
, collect (true)
, autoscroll_button (_("Auto-Scroll"))
, base_button (_("Decimal"))
, collect_button (_("Enabled"))
{
scroller.add (text);
get_vbox()->set_border_width (12);
get_vbox()->pack_start (scroller, true, true);
text.show ();
scroller.show ();
scroller.set_size_request (400, 400);
collect_button.set_active (true);
base_button.set_active (false);
autoscroll_button.set_active (true);
line_count_box.set_spacing (6);
line_count_box.pack_start (line_count_label, false, false);
line_count_box.pack_start (line_count_spinner, false, false);
line_count_spinner.show ();
line_count_label.show ();
line_count_box.show ();
get_action_area()->add (line_count_box);
get_action_area()->add (base_button);
get_action_area()->add(collect_button);
get_action_area()->add (autoscroll_button);
base_button.signal_toggled().connect (sigc::mem_fun (*this, &MidiTracer::base_toggle));
collect_button.signal_toggled().connect (sigc::mem_fun (*this, &MidiTracer::collect_toggle));
autoscroll_button.signal_toggled().connect (sigc::mem_fun (*this, &MidiTracer::autoscroll_toggle));
base_button.show ();
collect_button.show ();
autoscroll_button.show ();
connect ();
}
MidiTracer::~MidiTracer()
{
}
void
MidiTracer::connect ()
{
disconnect ();
parser.any.connect_same_thread (connection, boost::bind (&MidiTracer::tracer, this, _1, _2, _3));
}
void
MidiTracer::disconnect ()
{
connection.disconnect ();
}
void
MidiTracer::tracer (Parser&, byte* msg, size_t len)
{
stringstream ss;
struct timeval tv;
char buf[256];
struct tm now;
size_t bufsize;
size_t s;
gettimeofday (&tv, 0);
localtime_r (&tv.tv_sec, &now);
s = strftime (buf, sizeof (buf), "%H:%M:%S", &now);
bufsize = sizeof (buf) - s;
s += snprintf (&buf[s], bufsize, ".%-9" PRId64, (int64_t) tv.tv_usec);
bufsize = sizeof (buf) - s;
switch ((eventType) msg[0]&0xf0) {
case off:
if (show_hex) {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x %02x\n", "NoteOff", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
} else {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %-3d %-3d\n", "NoteOff", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
}
break;
case on:
if (show_hex) {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x %02x\n", "NoteOn", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
} else {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %-3d %-3d\n", "NoteOn", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
}
break;
case polypress:
if (show_hex) {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x\n", "PolyPressure", (msg[0]&0xf)+1, (int) msg[1]);
} else {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %-3d\n", "PolyPressure", (msg[0]&0xf)+1, (int) msg[1]);
}
break;
case MIDI::controller:
if (show_hex) {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x %02x\n", "Controller", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
} else {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %2d %-3d\n", "Controller", (msg[0]&0xf)+1, (int) msg[1], (int) msg[2]);
}
break;
case program:
if (show_hex) {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x\n", "Program Change", (msg[0]&0xf)+1, (int) msg[1]);
} else {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %-3d\n", "Program Change", (msg[0]&0xf)+1, (int) msg[1]);
}
break;
case chanpress:
if (show_hex) {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x/%-3d\n", "Channel Pressure", (msg[0]&0xf)+1, (int) msg[1], (int) msg[1]);
} else {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x/%-3d\n", "Channel Pressure", (msg[0]&0xf)+1, (int) msg[1], (int) msg[1]);
}
break;
case MIDI::pitchbend:
if (show_hex) {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %02x\n", "Pitch Bend", (msg[0]&0xf)+1, (int) msg[1]);
} else {
s += snprintf (&buf[s], bufsize, "%16s chn %2d %-3d\n", "Pitch Bend", (msg[0]&0xf)+1, (int) msg[1]);
}
break;
case MIDI::sysex:
if (len == 1) {
switch (msg[0]) {
case 0xf8:
s += snprintf (&buf[s], bufsize, "%16s\n", "Clock");
break;
case 0xfa:
s += snprintf (&buf[s], bufsize, "%16s\n", "Start");
break;
case 0xfb:
s += snprintf (&buf[s], bufsize, "%16s\n", "Continue");
break;
case 0xfc:
s += snprintf (&buf[s], bufsize, "%16s\n", "Stop");
break;
case 0xfe:
s += snprintf (&buf[s], bufsize, "%16s\n", "Active Sense");
break;
case 0xff:
s += snprintf (&buf[s], bufsize, "%16s\n", "Reset");
break;
default:
s += snprintf (&buf[s], bufsize, "%16s %02x\n", "Sysex", (int) msg[1]);
break;
}
bufsize = sizeof (buf) - s;
} else {
s += snprintf (&buf[s], bufsize, " %16s (%d) = [", "Sysex", (int) len);
bufsize = sizeof (buf) - s;
for (unsigned int i = 0; i < len && s < sizeof (buf)-3; ++i) {
if (i > 0) {
s += snprintf (&buf[s], bufsize, " %02x", msg[i]);
} else {
s += snprintf (&buf[s], bufsize, "%02x", msg[i]);
}
bufsize = sizeof (buf) - s;
}
s += snprintf (&buf[s], bufsize, "]\n");
}
break;
case MIDI::song:
s += snprintf (&buf[s], bufsize, "%16s\n", "Song");
break;
case MIDI::tune:
s += snprintf (&buf[s], bufsize, "%16s\n", "Tune");
break;
case MIDI::eox:
s += snprintf (&buf[s], bufsize, "%16s\n", "EOX");
break;
case MIDI::timing:
s += snprintf (&buf[s], bufsize, "%16s\n", "Timing");
break;
case MIDI::start:
s += snprintf (&buf[s], bufsize, "%16s\n", "Start");
break;
case MIDI::stop:
s += snprintf (&buf[s], bufsize, "%16s\n", "Stop");
break;
case MIDI::contineu:
s += snprintf (&buf[s], bufsize, "%16s\n", "Continue");
break;
case active:
s += snprintf (&buf[s], bufsize, "%16s\n", "Active Sense");
break;
default:
s += snprintf (&buf[s], bufsize, "%16s\n", "Unknown");
break;
}
// If you want to append more to the line, uncomment this first
// bufsize = sizeof (buf) - s;
gui_context()->call_slot (boost::bind (&MidiTracer::add_string, this, string (buf)));
}
void
MidiTracer::add_string (std::string s)
{
RefPtr<TextBuffer> buf (text.get_buffer());
int excess = buf->get_line_count() - line_count_adjustment.get_value();
if (excess > 0) {
buf->erase (buf->begin(), buf->get_iter_at_line (excess));
}
buf->insert (buf->end(), s);
if (autoscroll) {
scroller.get_vadjustment()->set_value (scroller.get_vadjustment()->get_upper());
}
}
void
MidiTracer::base_toggle ()
{
show_hex = !base_button.get_active();
}
void
MidiTracer::collect_toggle ()
{
if (collect_button.get_active ()) {
connect ();
} else {
disconnect ();
}
}
void
MidiTracer::autoscroll_toggle ()
{
autoscroll = autoscroll_button.get_active ();
}

55
gtk2_ardour/midi_tracer.h Normal file
View file

@ -0,0 +1,55 @@
#ifndef __ardour_gtk_midi_tracer_h__
#define __ardour_gtk_midi_tracer_h__
#include <types.h>
#include <gtkmm/textview.h>
#include <gtkmm/scrolledwindow.h>
#include <gtkmm/togglebutton.h>
#include <gtkmm/adjustment.h>
#include <gtkmm/spinbutton.h>
#include <gtkmm/label.h>
#include "pbd/signals.h"
#include "midi++/types.h"
#include "ardour_dialog.h"
namespace MIDI {
class Parser;
}
class MidiTracer : public ArdourDialog
{
public:
MidiTracer (const std::string&, MIDI::Parser&);
~MidiTracer();
private:
MIDI::Parser& parser;
Gtk::TextView text;
Gtk::ScrolledWindow scroller;
Gtk::Adjustment line_count_adjustment;
Gtk::SpinButton line_count_spinner;
Gtk::Label line_count_label;
Gtk::HBox line_count_box;
bool autoscroll;
bool show_hex;
bool collect;
void tracer (MIDI::Parser&, MIDI::byte*, size_t);
void add_string (std::string);
Gtk::ToggleButton autoscroll_button;
Gtk::ToggleButton base_button;
Gtk::ToggleButton collect_button;
void base_toggle ();
void autoscroll_toggle ();
void collect_toggle ();
void connect ();
void disconnect ();
PBD::Connection connection;
};
#endif /* __ardour_gtk_midi_tracer_h__ */

View file

@ -12,6 +12,7 @@
#include "control_protocol/control_protocol.h"
#include "gui_thread.h"
#include "midi_tracer.h"
#include "rc_option_editor.h"
#include "utils.h"
#include "midi_port_dialog.h"
@ -65,6 +66,10 @@ public:
private:
typedef std::map<MIDI::Port*,MidiTracer*> PortTraceMap;
PortTraceMap port_input_trace_map;
PortTraceMap port_output_trace_map;
void model_changed (TreeModel::Path const &, TreeModel::iterator const & i)
{
TreeModel::Row r = *i;
@ -81,14 +86,32 @@ private:
}
if (r[_model.trace_input] != port->input()->tracing()) {
port->input()->trace (r[_model.trace_input], &cerr, string (port->name()) + _(" input: "));
PortTraceMap::iterator x = port_input_trace_map.find (port);
MidiTracer* mt;
if (x == port_input_trace_map.end()) {
mt = new MidiTracer (port->name() + string (" [input]"), *port->input());
port_input_trace_map.insert (pair<MIDI::Port*,MidiTracer*> (port, mt));
} else {
mt = x->second;
}
mt->present ();
}
}
if (port->output()) {
if (r[_model.trace_output] != port->output()->tracing()) {
port->output()->trace (r[_model.trace_output], &cerr, string (port->name()) + _(" output: "));
PortTraceMap::iterator x = port_output_trace_map.find (port);
MidiTracer* mt;
if (x == port_output_trace_map.end()) {
mt = new MidiTracer (port->name() + string (" [output]"), *port->output());
port_output_trace_map.insert (pair<MIDI::Port*,MidiTracer*> (port, mt));
} else {
mt = x->second;
}
mt->present ();
}
}

View file

@ -23,6 +23,7 @@
#include <algorithm>
#include <string>
#include <vector>
#include <map>
#include <utility>
#include <sigc++/bind.h>
@ -32,6 +33,7 @@
#include "pbd/whitespace.h"
#include "pbd/memento_command.h"
#include "pbd/enumwriter.h"
#include "pbd/stacktrace.h"
#include <gtkmm/menu.h>
#include <gtkmm/menuitem.h>
@ -415,6 +417,7 @@ RouteTimeAxisView::build_automation_action_menu ()
using namespace Menu_Helpers;
automation_action_menu = manage (new Menu);
cerr << "New AAM @ " << automation_action_menu << endl;
MenuList& automation_items = automation_action_menu->items();
automation_action_menu->set_name ("ArdourContextMenu");
@ -443,15 +446,47 @@ RouteTimeAxisView::build_automation_action_menu ()
map<Evoral::Parameter, RouteAutomationNode*>::iterator i;
map<string,Menu*> param_menu_map;
for (i = _automation_tracks.begin(); i != _automation_tracks.end(); ++i) {
automation_items.push_back (SeparatorElem());
string desc = _route->describe_parameter(i->second->param);
string::size_type bracket = desc.find_first_of ('[');
automation_items.push_back(CheckMenuElem (_route->describe_parameter(i->second->param),
sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::toggle_automation_track), i->second->param)));
if (bracket == string::npos) {
i->second->menu_item = static_cast<Gtk::CheckMenuItem*>(&automation_items.back());
i->second->menu_item->set_active(show_automation(i->second->param));
/* item gets its own entry in the menu */
automation_items.push_back (CheckMenuElem (desc, sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::toggle_automation_track), i->second->param)));
i->second->menu_item = static_cast<Gtk::CheckMenuItem*>(&automation_items.back());
i->second->menu_item->set_active (show_automation (i->second->param));
automation_items.push_back (SeparatorElem());
} else {
/* subgroup related items in their own submenu */
string first_part = desc.substr (0, bracket);
Menu* m;
map<string,Menu*>::iterator x;
if ((x = param_menu_map.find (first_part)) == param_menu_map.end()) {
m = manage (new Menu);
m->set_name ("ArdourContextMenu");
automation_items.push_back (MenuElem (first_part + "...", *m));
param_menu_map.insert (pair<string,Menu*>(first_part, m));
} else {
m = x->second;
}
MenuList& mi = m->items();
mi.push_back (CheckMenuElem (desc, sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::toggle_automation_track), i->second->param)));
i->second->menu_item = static_cast<Gtk::CheckMenuItem*>(&mi.back());
i->second->menu_item->set_active(show_automation(i->second->param));
}
}
}
@ -481,8 +516,11 @@ RouteTimeAxisView::build_display_menu ()
if (!Profile->get_sae()) {
items.push_back (MenuElem (_("Remote Control ID..."), sigc::mem_fun (*this, &RouteUI::open_remote_control_id_dialog)));
/* rebuild this every time */
cerr << "Build a new AAM, old was " << automation_action_menu << endl;
build_automation_action_menu ();
cerr << "Attach AAM @ " << automation_action_menu << endl;
items.push_back (MenuElem (_("Automation"), *automation_action_menu));
cerr << "Attachment is done\n";
items.push_back (SeparatorElem());
}
@ -1636,8 +1674,9 @@ RouteTimeAxisView::toggle_automation_track (Evoral::Parameter param)
{
RouteAutomationNode* node = automation_track(param);
if (!node)
if (!node) {
return;
}
bool showit = node->menu_item->get_active();

View file

@ -134,6 +134,7 @@ gtk2_ardour_sources = [
'midi_scroomer.cc',
'midi_streamview.cc',
'midi_time_axis.cc',
'midi_tracer.cc',
'mixer_group_tabs.cc',
'mixer_strip.cc',
'mixer_ui.cc',

View file

@ -1416,12 +1416,17 @@ Region::source_deleted (boost::weak_ptr<Source>)
{
_sources.clear ();
/* this is a very special case: at least one of the region's
sources has bee deleted, so invalidate all references to
ourselves.
*/
if (!_session.deletion_in_progress()) {
/* this is a very special case: at least one of the region's
sources has bee deleted, so invalidate all references to
ourselves. Do NOT do this during session deletion, because
then we run the risk that this will actually result
in this object being deleted (as refcnt goes to zero)
while emitting DropReferences.
*/
drop_references ();
drop_references ();
}
}
vector<string>

View file

@ -75,10 +75,9 @@ Source::Source (Session& s, const XMLNode& node)
Source::~Source ()
{
DEBUG_TRACE (DEBUG::Destruction, string_compose ("Source %1 destructor\n", _name));
DEBUG_TRACE (DEBUG::Destruction, string_compose ("Source %1 destructor %2\n", _name, this));
}
void
Source::fix_writable_flags ()
{