diff options
author | Carl Hetherington <carl@carlh.net> | 2010-12-28 18:19:40 +0000 |
---|---|---|
committer | Carl Hetherington <carl@carlh.net> | 2010-12-28 18:19:40 +0000 |
commit | f8ebb4582d4d881fbda75a6bc9cd9c50f5c921f3 (patch) | |
tree | 90608a3cb424866b2a164c354d1665a7b0a9d79a | |
parent | 390f18c1152f2007b790a77c873b50ef48209f44 (diff) |
Unify program change and bank handling so that they are manipulated together.
git-svn-id: svn://localhost/ardour2/branches/3.0@8346 d708f5d6-7413-0410-9779-e7cbd77b26cf
-rw-r--r-- | gtk2_ardour/ardour.menus.in | 4 | ||||
-rw-r--r-- | gtk2_ardour/ardour3_ui_default.conf | 4 | ||||
-rw-r--r-- | gtk2_ardour/canvas_vars.h | 4 | ||||
-rw-r--r-- | gtk2_ardour/editor.h | 2 | ||||
-rw-r--r-- | gtk2_ardour/editor_actions.cc | 2 | ||||
-rw-r--r-- | gtk2_ardour/editor_drag.cc | 24 | ||||
-rw-r--r-- | gtk2_ardour/editor_drag.h | 10 | ||||
-rw-r--r-- | gtk2_ardour/editor_ops.cc | 14 | ||||
-rw-r--r-- | gtk2_ardour/midi_region_view.cc | 279 | ||||
-rw-r--r-- | gtk2_ardour/midi_region_view.h | 62 | ||||
-rw-r--r-- | gtk2_ardour/midi_time_axis.cc | 9 | ||||
-rw-r--r-- | gtk2_ardour/wscript | 4 | ||||
-rw-r--r-- | libs/ardour/ardour/midi_model.h | 59 | ||||
-rw-r--r-- | libs/ardour/enums.cc | 6 | ||||
-rw-r--r-- | libs/ardour/midi_model.cc | 435 | ||||
-rw-r--r-- | libs/ardour/session_state.cc | 22 | ||||
-rw-r--r-- | libs/evoral/evoral/Sequence.hpp | 65 | ||||
-rw-r--r-- | libs/evoral/src/SMF.cpp | 13 | ||||
-rw-r--r-- | libs/evoral/src/Sequence.cpp | 126 |
19 files changed, 910 insertions, 234 deletions
diff --git a/gtk2_ardour/ardour.menus.in b/gtk2_ardour/ardour.menus.in index ffbdfcc131..428160c0f5 100644 --- a/gtk2_ardour/ardour.menus.in +++ b/gtk2_ardour/ardour.menus.in @@ -258,7 +258,7 @@ <menuitem action='lower-region-to-bottom'/> </menu> <menu action='RegionMenuMIDI'> - <menuitem action='insert-program-change'/> + <menuitem action='insert-patch-change'/> <menuitem action='quantize-region'/> <menuitem action='fork-region'/> <menuitem action='show-region-list-editor'/> @@ -593,7 +593,7 @@ <menuitem action='lower-region-to-bottom'/> </menu> <menu action='RegionMenuMIDI'> - <menuitem action='insert-program-change'/> + <menuitem action='insert-patch-change'/> <menuitem action='quantize-region'/> <menuitem action='fork-region'/> <menuitem action='show-region-list-editor'/> diff --git a/gtk2_ardour/ardour3_ui_default.conf b/gtk2_ardour/ardour3_ui_default.conf index 438aad3c69..afd0f68979 100644 --- a/gtk2_ardour/ardour3_ui_default.conf +++ b/gtk2_ardour/ardour3_ui_default.conf @@ -71,8 +71,8 @@ <Option name="selected midi note color max" value="ff0000ff"/> <Option name="midi note selected" value="ff3a48ff"/> <Option name="midi note velocity text" value="f4f214bc"/> - <Option name="midi program change fill" value="0000ffa0"/> - <Option name="midi program change outline" value="a7a7d4ff"/> + <Option name="midi patch change fill" value="0000ffa0"/> + <Option name="midi patch change outline" value="a7a7d4ff"/> <Option name="midi sysex fill" value="f1e139a0"/> <Option name="midi sysex outline" value="a7a7d4ff"/> <Option name="midi select rect fill" value="8888ff88"/> diff --git a/gtk2_ardour/canvas_vars.h b/gtk2_ardour/canvas_vars.h index a8d7b9cfa3..bfc4b0305c 100644 --- a/gtk2_ardour/canvas_vars.h +++ b/gtk2_ardour/canvas_vars.h @@ -65,8 +65,8 @@ CANVAS_VARIABLE(canvasvar_SelectedMidiNoteColorMid, "selected midi note color mi CANVAS_VARIABLE(canvasvar_SelectedMidiNoteColorTop, "selected midi note color max") CANVAS_VARIABLE(canvasvar_MidiNoteSelected, "midi note selected") CANVAS_VARIABLE(canvasvar_MidiNoteVelocityText, "midi note velocity text") -CANVAS_VARIABLE(canvasvar_MidiProgramChangeFill, "midi program change fill") -CANVAS_VARIABLE(canvasvar_MidiProgramChangeOutline, "midi program change outline") +CANVAS_VARIABLE(canvasvar_MidiPatchChangeFill, "midi patch change fill") +CANVAS_VARIABLE(canvasvar_MidiPatchChangeOutline, "midi patch change outline") CANVAS_VARIABLE(canvasvar_MidiSysExFill, "midi sysex fill") CANVAS_VARIABLE(canvasvar_MidiSysExOutline, "midi sysex outline") CANVAS_VARIABLE(canvasvar_MidiSelectRectFill, "midi select rect fill") diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 57bfd5205b..a7de0498a0 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -1090,7 +1090,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD void reset_region_scale_amplitude (); void adjust_region_gain (bool up); void quantize_region (); - void insert_program_change (); + void insert_patch_change (); void fork_region (); void do_insert_time (); diff --git a/gtk2_ardour/editor_actions.cc b/gtk2_ardour/editor_actions.cc index e88f5c9c31..c2ff86d1a8 100644 --- a/gtk2_ardour/editor_actions.cc +++ b/gtk2_ardour/editor_actions.cc @@ -1469,7 +1469,7 @@ Editor::register_region_actions () ); reg_sens (_region_actions, "quantize-region", _("Quantize"), sigc::mem_fun (*this, &Editor::quantize_region)); - reg_sens (_region_actions, "insert-program-change", _("Insert Program Change..."), sigc::mem_fun (*this, &Editor::insert_program_change)); + reg_sens (_region_actions, "insert-patch-change", _("Insert Patch Change..."), sigc::mem_fun (*this, &Editor::insert_patch_change)); reg_sens (_region_actions, "fork-region", _("Fork"), sigc::mem_fun (*this, &Editor::fork_region)); reg_sens (_region_actions, "strip-region-silence", _("Strip Silence..."), sigc::mem_fun (*this, &Editor::strip_region_silence)); reg_sens (_region_actions, "set-selection-from-region", _("Set Range Selection"), sigc::mem_fun (*this, &Editor::set_selection_from_region)); diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 67367e8b65..07fe0ff09a 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -4093,17 +4093,17 @@ DraggingView::DraggingView (RegionView* v, RegionDrag* parent) initial_end = v->region()->position () + v->region()->length (); } -ProgramChangeDrag::ProgramChangeDrag (Editor* e, CanvasProgramChange* i, MidiRegionView* r) +PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionView* r) : Drag (e, i) , _region_view (r) - , _program_change (i) + , _patch_change (i) , _cumulative_dx (0) { - DEBUG_TRACE (DEBUG::Drags, "New ProgramChangeDrag\n"); + DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n"); } void -ProgramChangeDrag::motion (GdkEvent* ev, bool) +PatchChangeDrag::motion (GdkEvent* ev, bool) { framepos_t f = adjusted_current_frame (ev); boost::shared_ptr<Region> r = _region_view->region (); @@ -4112,12 +4112,12 @@ ProgramChangeDrag::motion (GdkEvent* ev, bool) framecnt_t const dxf = f - grab_frame(); double const dxu = _editor->frame_to_unit (dxf); - _program_change->move (dxu - _cumulative_dx, 0); + _patch_change->move (dxu - _cumulative_dx, 0); _cumulative_dx = dxu; } void -ProgramChangeDrag::finished (GdkEvent* ev, bool movement_occurred) +PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred) { if (!movement_occurred) { return; @@ -4129,22 +4129,22 @@ ProgramChangeDrag::finished (GdkEvent* ev, bool movement_occurred) f = max (f, r->position ()); f = min (f, r->last_frame ()); - _region_view->move_program_change ( - MidiRegionView::PCEvent (_program_change->event_time(), _program_change->program(), _program_change->channel()), + _region_view->move_patch_change ( + *_patch_change, _region_view->frames_to_beats (f - r->position() - r->start()) ); } void -ProgramChangeDrag::aborted () +PatchChangeDrag::aborted () { - _program_change->move (-_cumulative_dx, 0); + _patch_change->move (-_cumulative_dx, 0); } void -ProgramChangeDrag::setup_pointer_frame_offset () +PatchChangeDrag::setup_pointer_frame_offset () { boost::shared_ptr<Region> region = _region_view->region (); - _pointer_frame_offset = raw_grab_frame() - _region_view->beats_to_frames (_program_change->event_time()) - region->position() + region->start(); + _pointer_frame_offset = raw_grab_frame() - _region_view->beats_to_frames (_patch_change->patch()->time()) - region->position() + region->start(); } diff --git a/gtk2_ardour/editor_drag.h b/gtk2_ardour/editor_drag.h index 3323a8b741..1f6109bcd2 100644 --- a/gtk2_ardour/editor_drag.h +++ b/gtk2_ardour/editor_drag.h @@ -42,7 +42,7 @@ namespace PBD { namespace Gnome { namespace Canvas { class CanvasNoteEvent; - class CanvasProgramChange; + class CanvasPatchChange; } } @@ -451,11 +451,11 @@ class NoteDrag : public Drag double _note_height; }; -/** Drag to move MIDI program changes */ -class ProgramChangeDrag : public Drag +/** Drag to move MIDI patch changes */ +class PatchChangeDrag : public Drag { public: - ProgramChangeDrag (Editor *, ArdourCanvas::CanvasProgramChange *, MidiRegionView *); + PatchChangeDrag (Editor *, ArdourCanvas::CanvasPatchChange *, MidiRegionView *); void motion (GdkEvent *, bool); void finished (GdkEvent *, bool); @@ -469,7 +469,7 @@ public: private: MidiRegionView* _region_view; - ArdourCanvas::CanvasProgramChange* _program_change; + ArdourCanvas::CanvasPatchChange* _patch_change; double _cumulative_dx; }; diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index b70636f58b..ed903b9d7b 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -84,7 +84,7 @@ #include "normalize_dialog.h" #include "editor_cursors.h" #include "mouse_cursors.h" -#include "program_change_dialog.h" +#include "patch_change_dialog.h" #include "i18n.h" @@ -4706,25 +4706,27 @@ Editor::quantize_region () } void -Editor::insert_program_change () +Editor::insert_patch_change () { RegionSelection rs = get_regions_from_selection_and_entered (); if (rs.empty ()) { return; } - ProgramChangeDialog d; + framepos_t const p = get_preferred_edit_position (false); + + Evoral::PatchChange<Evoral::MusicalTime> empty (0, 0, 0, 0); + PatchChangeDialog d (0, _session, empty, Gtk::Stock::ADD); + if (d.run() == RESPONSE_CANCEL) { return; } - framepos_t const p = get_preferred_edit_position (false); - for (RegionSelection::iterator i = rs.begin (); i != rs.end(); ++i) { MidiRegionView* const mrv = dynamic_cast<MidiRegionView*> (*i); if (mrv) { if (p >= mrv->region()->first_frame() && p <= mrv->region()->last_frame()) { - mrv->add_program_change (p - mrv->region()->position(), d.program ()); + mrv->add_patch_change (p - mrv->region()->position(), d.patch ()); } } } diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index dced1cd3a8..3cf59eed2c 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -48,7 +48,7 @@ #include "automation_time_axis.h" #include "canvas-hit.h" #include "canvas-note.h" -#include "canvas-program-change.h" +#include "canvas_patch_change.h" #include "editor.h" #include "ghostregion.h" #include "gui_thread.h" @@ -67,6 +67,7 @@ #include "streamview.h" #include "utils.h" #include "mouse_cursors.h" +#include "patch_change_dialog.h" #include "i18n.h" @@ -778,7 +779,7 @@ MidiRegionView::clear_events() } _events.clear(); - _pgm_changes.clear(); + _patch_changes.clear(); _sys_exes.clear(); _optimization_iterator = _events.end(); } @@ -1019,11 +1020,11 @@ MidiRegionView::redisplay_model() } } - _pgm_changes.clear(); + _patch_changes.clear(); _sys_exes.clear(); display_sysexes(); - display_program_changes(); + display_patch_changes (); _marked_for_selection.clear (); _marked_for_velocity.clear (); @@ -1037,66 +1038,40 @@ MidiRegionView::redisplay_model() } void -MidiRegionView::display_program_changes() +MidiRegionView::display_patch_changes () { MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview); uint16_t chn_mask = mtv->channel_selector().get_selected_channels(); for (uint8_t i = 0; i < 16; ++i) { if (chn_mask & (1<<i)) { - display_program_changes_on_channel (i); + display_patch_changes_on_channel (i); } } } void -MidiRegionView::display_program_changes_on_channel(uint8_t channel) +MidiRegionView::display_patch_changes_on_channel (uint8_t channel) { - boost::shared_ptr<Evoral::Control> control = - _model->control(Evoral::MIDI::ProgramChange (MidiPgmChangeAutomation, channel)); + for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) { - if (!control) { - return; - } - - Glib::Mutex::Lock lock (control->list()->lock()); - - for (AutomationList::const_iterator event = control->list()->begin(); - event != control->list()->end(); ++event) { - double event_time = (*event)->when; - double program_number = floor((*event)->value + 0.5); - - // Get current value of bank select MSB at time of the program change - Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK); - boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb); - uint8_t msb = 0; - if (msb_control != 0) { - msb = uint8_t(floor(msb_control->get_double(true, event_time) + 0.5)); - } - - // Get current value of bank select LSB at time of the program change - Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK); - boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb); - uint8_t lsb = 0; - if (lsb_control != 0) { - lsb = uint8_t(floor(lsb_control->get_double(true, event_time) + 0.5)); + if ((*i)->channel() != channel) { + continue; } - - MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number); + + MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ()); boost::shared_ptr<MIDI::Name::Patch> patch = MIDI::Name::MidiPatchManager::instance().find_patch( _model_name, _custom_device_mode, channel, patch_key); - PCEvent program_change(event_time, uint8_t(program_number), channel); - if (patch != 0) { - add_canvas_program_change (program_change, patch->name()); + add_canvas_patch_change (*i, patch->name()); } else { char buf[4]; // program_number is zero-based: convert to one-based - snprintf(buf, 4, "%d", int(program_number+1)); - add_canvas_program_change (program_change, buf); + snprintf (buf, 4, "%d", (*i)->program() + 1); + add_canvas_patch_change (*i, buf); } } } @@ -1125,7 +1100,7 @@ MidiRegionView::display_sysexes() boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>( new CanvasSysEx(*this, *_note_group, text, height, x, 1.0)); - // Show unless program change is beyond the region bounds + // Show unless patch change is beyond the region bounds if (time - _region->start() >= _region->length() || time < _region->start()) { sysex->hide(); } else { @@ -1205,7 +1180,7 @@ MidiRegionView::set_height (double height) name_pixbuf->raise_to_top(); } - for (PgmChanges::iterator x = _pgm_changes.begin(); x != _pgm_changes.end(); ++x) { + for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) { (*x)->set_height (midi_stream_view()->contents_height()); } @@ -1563,163 +1538,163 @@ MidiRegionView::step_sustain (Evoral::MusicalTime beats) } void -MidiRegionView::add_canvas_program_change (PCEvent& program, const string& displaytext) +MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext) { - assert(program.time >= 0); + assert (patch->time() >= 0); - const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time)); + const double x = trackview.editor().frame_to_pixel (beats_to_frames (patch->time())); - double height = midi_stream_view()->contents_height(); + double const height = midi_stream_view()->contents_height(); - boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>( - new CanvasProgramChange(*this, *_note_group, + boost::shared_ptr<CanvasPatchChange> patch_change = boost::shared_ptr<CanvasPatchChange>( + new CanvasPatchChange(*this, *_note_group, displaytext, height, x, 1.0, _model_name, _custom_device_mode, - program.time, program.channel, program.value)); + patch) + ); - // Show unless program change is beyond the region bounds - if (program.time - _region->start() >= _region->length() || program.time < _region->start()) { - pgm_change->hide(); + // Show unless patch change is beyond the region bounds + if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) { + patch_change->hide(); } else { - pgm_change->show(); + patch_change->show(); } - _pgm_changes.push_back(pgm_change); + _patch_changes.push_back (patch_change); } void -MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) +MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) { - Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK); - boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb); - double msb = 0.0; - if (msb_control != 0) { - msb = int(msb_control->get_double(true, time)); - } - - Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK); - boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb); - double lsb = 0.0; - if (lsb_control != 0) { - lsb = lsb_control->get_double(true, time); + MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time); + while (i != _model->patch_changes().end() && (*i)->channel() != channel) { + ++i; } - - Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0); - boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change); - double program_number = -1.0; - if (program_control != 0) { - program_number = program_control->get_double(true, time); + + if (i != _model->patch_changes().end()) { + key.msb = (*i)->bank_msb (); + key.lsb = (*i)->bank_lsb (); + key.program_number = (*i)->program (); + } else { + key.msb = key.lsb = key.program_number = 0; } - - key.msb = (int) floor(msb + 0.5); - key.lsb = (int) floor(lsb + 0.5); - key.program_number = (int) floor(program_number + 0.5); - assert(key.is_sane()); + + assert (key.is_sane()); } void -MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch) +MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch) { - // TODO: Get the real event here and alter them at the original times - Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK); - boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb); - if (msb_control != 0) { - msb_control->set_double(double(new_patch.msb), true, old_program.time); + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change")); + + if (pc.patch()->program() != new_patch.program_number) { + c->change_program (pc.patch (), new_patch.program_number); } - // TODO: Get the real event here and alter them at the original times - Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK); - boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb); - if (lsb_control != 0) { - lsb_control->set_double(double(new_patch.lsb), true, old_program.time); + int const new_bank = (new_patch.msb << 7) | new_patch.lsb; + if (pc.patch()->bank() != new_bank) { + c->change_bank (pc.patch (), new_bank); } - Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0); - boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change); - - assert(program_control != 0); - program_control->set_double(float(new_patch.program_number), true, old_program.time); + _model->apply_command (*trackview.session(), c); - _pgm_changes.clear (); - display_program_changes (); // XXX would be nice to limit to just old_program.channel + _patch_changes.clear (); + display_patch_changes (); } -/** @param t Time in frames relative to region position */ void -MidiRegionView::add_program_change (framecnt_t t, uint8_t value) +MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::MusicalTime> & new_change) { - boost::shared_ptr<Evoral::Control> control = midi_region()->model()->control ( - Evoral::Parameter (MidiPgmChangeAutomation, get_channel_for_add (), 0), true - ); + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change")); + + if (old_change->time() != new_change.time()) { + c->change_time (old_change, new_change.time()); + } + + if (old_change->channel() != new_change.channel()) { + c->change_channel (old_change, new_change.channel()); + } - assert (control); + if (old_change->program() != new_change.program()) { + c->change_program (old_change, new_change.program()); + } - Evoral::MusicalTime const b = frames_to_beats (t + midi_region()->start()); + if (old_change->bank() != new_change.bank()) { + c->change_bank (old_change, new_change.bank()); + } - control->list()->add (b, value); + _model->apply_command (*trackview.session(), c); - _pgm_changes.clear (); - display_program_changes (); + _patch_changes.clear (); + display_patch_changes (); } +/** Add a patch change to the region. + * @param t Time in frames relative to region position + * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from + * get_channel_for_add()) + */ void -MidiRegionView::move_program_change (PCEvent pc, Evoral::MusicalTime t) +MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::MusicalTime> const & patch) { - boost::shared_ptr<Evoral::Control> control = _model->control (Evoral::Parameter (MidiPgmChangeAutomation, pc.channel, 0)); - assert (control); - - control->list()->erase (pc.time, pc.value); - control->list()->add (t, pc.value); - - _pgm_changes.clear (); - display_program_changes (); + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change")); + c->add (MidiModel::PatchChangePtr ( + new Evoral::PatchChange<Evoral::MusicalTime> ( + frames_to_beats (t + midi_region()->start()), get_channel_for_add(), patch.program(), patch.bank() + ) + )); + + _model->apply_command (*trackview.session(), c); + + _patch_changes.clear (); + display_patch_changes (); } void -MidiRegionView::delete_program_change (CanvasProgramChange* pc) +MidiRegionView::move_patch_change (CanvasPatchChange& pc, Evoral::MusicalTime t) { - boost::shared_ptr<Evoral::Control> control = _model->control (Evoral::Parameter (MidiPgmChangeAutomation, pc->channel(), 0)); - assert (control); + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change")); + c->change_time (pc.patch (), t); + _model->apply_command (*trackview.session(), c); - control->list()->erase (pc->event_time(), pc->program()); - _pgm_changes.clear (); - display_program_changes (); + _patch_changes.clear (); + display_patch_changes (); } void -MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch) +MidiRegionView::delete_patch_change (CanvasPatchChange* pc) { - PCEvent program_change_event(program.event_time(), program.program(), program.channel()); - alter_program_change(program_change_event, new_patch); -} + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change")); + c->remove (pc->patch ()); + _model->apply_command (*trackview.session(), c); + _patch_changes.clear (); + display_patch_changes (); +} + void -MidiRegionView::previous_program(CanvasProgramChange& program) +MidiRegionView::previous_patch (CanvasPatchChange& patch) { - if (program.program() < 127) { + if (patch.patch()->program() < 127) { MIDI::Name::PatchPrimaryKey key; - get_patch_key_at(program.event_time(), program.channel(), key); - PCEvent program_change_event(program.event_time(), program.program(), program.channel()); - + get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key); key.program_number++; - alter_program_change(program_change_event, key); + change_patch_change (patch, key); } } void -MidiRegionView::next_program(CanvasProgramChange& program) +MidiRegionView::next_patch (CanvasPatchChange& patch) { - if (program.program() > 0) { + if (patch.patch()->program() > 0) { MIDI::Name::PatchPrimaryKey key; - get_patch_key_at(program.event_time(), program.channel(), key); - PCEvent program_change_event(program.event_time(), program.program(), program.channel()); - + get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key); key.program_number--; - alter_program_change(program_change_event, key); + change_patch_change (patch, key); } } @@ -2760,6 +2735,20 @@ MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*) } void +MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev) +{ + ostringstream s; + s << ((int) ev->patch()->program() + 1) << ":" << (ev->patch()->bank() + 1); + trackview.editor().show_verbose_canvas_cursor_with (s.str().c_str(), 10, 20); +} + +void +MidiRegionView::patch_left (ArdourCanvas::CanvasPatchChange *) +{ + trackview.editor().hide_verbose_canvas_cursor (); +} + +void MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor) { Editor* editor = dynamic_cast<Editor*>(&trackview.editor()); @@ -2806,6 +2795,9 @@ MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask) } _last_channel_selection = mask; + + _patch_changes.clear (); + display_patch_changes (); } void @@ -3303,3 +3295,14 @@ MidiRegionView::get_channel_for_add () const return channel; } + +void +MidiRegionView::edit_patch_change (ArdourCanvas::CanvasPatchChange* pc) +{ + PatchChangeDialog d (&_time_converter, trackview.session(), *pc->patch (), Gtk::Stock::APPLY); + if (d.run () != Gtk::RESPONSE_ACCEPT) { + return; + } + + change_patch_change (pc->patch(), d.patch ()); +} diff --git a/gtk2_ardour/midi_region_view.h b/gtk2_ardour/midi_region_view.h index ed8e382c4f..42b544b97d 100644 --- a/gtk2_ardour/midi_region_view.h +++ b/gtk2_ardour/midi_region_view.h @@ -43,7 +43,7 @@ #include "canvas-hit.h" #include "canvas-note.h" #include "canvas-note-event.h" -#include "canvas-program-change.h" +#include "canvas_patch_change.h" #include "canvas-sysex.h" namespace ARDOUR { @@ -120,57 +120,43 @@ class MidiRegionView : public RegionView void cut_copy_clear (Editing::CutCopyOp); void paste (framepos_t pos, float times, const MidiCutBuffer&); - struct PCEvent { - PCEvent(double a_time, uint8_t a_value, uint8_t a_channel) - : time(a_time), value(a_value), channel(a_channel) {} - - double time; - uint8_t value; - uint8_t channel; - }; - - /** Add a new program change flag to the canvas. - * @param program the MidiRegionView::PCEvent to add + /** Add a new patch change flag to the canvas. + * @param patch the patch change to add * @param the text to display in the flag */ - void add_canvas_program_change (PCEvent& program, const std::string& displaytext); + void add_canvas_patch_change (ARDOUR::MidiModel::PatchChangePtr patch, const std::string& displaytext); /** Look up the given time and channel in the 'automation' and set keys accordingly. - * @param time the time of the program change event + * @param time the time of the patch change event * @param channel the MIDI channel of the event * @key a reference to an instance of MIDI::Name::PatchPrimaryKey whose fields will * will be set according to the result of the lookup */ void get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key); - /** Change the 'automation' data of old_program to new values which correspond to new_patch. - * @param old_program the program change event which is to be altered - * @param new_patch the new lsb, msb and program number which are to be set + /** Change old_patch to new_patch. + * @param old_patch the canvas patch change which is to be altered + * @param new_patch new patch */ - void alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch); + void change_patch_change (ArdourCanvas::CanvasPatchChange& old_patch, const MIDI::Name::PatchPrimaryKey& new_patch); + void change_patch_change (ARDOUR::MidiModel::PatchChangePtr, Evoral::PatchChange<Evoral::MusicalTime> const &); - void add_program_change (framecnt_t, uint8_t); - void move_program_change (PCEvent, Evoral::MusicalTime); - void delete_program_change (ArdourCanvas::CanvasProgramChange *); - - /** Alter a given program to the new given one. - * (Called on context menu select on CanvasProgramChange) - */ - void program_selected( - ArdourCanvas::CanvasProgramChange& program, - const MIDI::Name::PatchPrimaryKey& new_patch); + void add_patch_change (framecnt_t, Evoral::PatchChange<Evoral::MusicalTime> const &); + void move_patch_change (ArdourCanvas::CanvasPatchChange &, Evoral::MusicalTime); + void delete_patch_change (ArdourCanvas::CanvasPatchChange *); + void edit_patch_change (ArdourCanvas::CanvasPatchChange *); - /** Alter a given program to be its predecessor in the MIDNAM file. + /** Alter a given patch to be its predecessor in the MIDNAM file. */ - void previous_program(ArdourCanvas::CanvasProgramChange& program); + void previous_patch (ArdourCanvas::CanvasPatchChange &); - /** Alters a given program to be its successor in the MIDNAM file. + /** Alters a given patch to be its successor in the MIDNAM file. */ - void next_program(ArdourCanvas::CanvasProgramChange& program); + void next_patch (ArdourCanvas::CanvasPatchChange &); - /** Displays all program change events in the region as flags on the canvas. + /** Displays all patch change events in the region as flags on the canvas. */ - void display_program_changes(); + void display_patch_changes(); /** Displays all system exclusive events in the region as flags on the canvas. */ @@ -196,6 +182,8 @@ class MidiRegionView : public RegionView void note_entered(ArdourCanvas::CanvasNoteEvent* ev); void note_left(ArdourCanvas::CanvasNoteEvent* ev); + void patch_entered (ArdourCanvas::CanvasPatchChange *); + void patch_left (ArdourCanvas::CanvasPatchChange *); void note_mouse_position (float xfraction, float yfraction, bool can_set_cursor=true); void unique_select(ArdourCanvas::CanvasNoteEvent* ev); void note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend=false); @@ -360,12 +348,12 @@ class MidiRegionView : public RegionView std::string _custom_device_mode; typedef std::list<ArdourCanvas::CanvasNoteEvent*> Events; - typedef std::vector< boost::shared_ptr<ArdourCanvas::CanvasProgramChange> > PgmChanges; + typedef std::vector< boost::shared_ptr<ArdourCanvas::CanvasPatchChange> > PatchChanges; typedef std::vector< boost::shared_ptr<ArdourCanvas::CanvasSysEx> > SysExes; boost::shared_ptr<ARDOUR::MidiModel> _model; Events _events; - PgmChanges _pgm_changes; + PatchChanges _patch_changes; SysExes _sys_exes; ArdourCanvas::CanvasNote** _active_notes; ArdourCanvas::Group* _note_group; @@ -444,7 +432,7 @@ class MidiRegionView : public RegionView void maybe_select_by_position (GdkEventButton* ev, double x, double y); void get_events (Events& e, Evoral::Sequence<Evoral::MusicalTime>::NoteOperator op, uint8_t val, int chan_mask = 0); - void display_program_changes_on_channel (uint8_t); + void display_patch_changes_on_channel (uint8_t); void connect_to_diskstream (); void data_recorded (boost::shared_ptr<ARDOUR::MidiBuffer>, boost::weak_ptr<ARDOUR::MidiSource>); diff --git a/gtk2_ardour/midi_time_axis.cc b/gtk2_ardour/midi_time_axis.cc index 6504c0429f..59cfe4abbd 100644 --- a/gtk2_ardour/midi_time_axis.cc +++ b/gtk2_ardour/midi_time_axis.cc @@ -591,7 +591,8 @@ MidiTimeAxisView::build_controller_menu () } } - /* loop over all 127 MIDI controllers, in groups of 16 */ + /* loop over all 127 MIDI controllers, in groups of 16; except don't offer + bank select controllers, as they are handled by the `patch' code */ for (int i = 0; i < 127; i += 16) { @@ -603,6 +604,10 @@ MidiTimeAxisView::build_controller_menu () for (int ctl = i; ctl < i+16; ++ctl) { + if (ctl == MIDI_CTL_MSB_BANK || ctl == MIDI_CTL_LSB_BANK) { + continue; + } + if (chn_cnt > 1) { /* multiple channels - create a submenu, with 1 item per channel */ @@ -985,7 +990,7 @@ MidiTimeAxisView::set_channel_mode (ChannelMode, uint16_t) no_redraw = false; - /* TODO: Bender, PgmChange, Pressure */ + /* TODO: Bender, Pressure */ /* invalidate the controller menu, so that we rebuilt it next time */ _controller_menu_map.clear (); diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index 8b71a1fdd2..2d88f9ced7 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -55,7 +55,7 @@ gtk2_ardour_sources = [ 'canvas-hit.cc', 'canvas-note-event.cc', 'canvas-note.cc', - 'canvas-program-change.cc', + 'canvas_patch_change.cc', 'canvas-simpleline.c', 'canvas-simplerect.c', 'canvas-sysex.cc', @@ -173,7 +173,7 @@ gtk2_ardour_sources = [ 'port_matrix_labels.cc', 'port_matrix_row_labels.cc', 'processor_box.cc', - 'program_change_dialog.cc', + 'patch_change_dialog.cc', 'progress_reporter.cc', 'prompter.cc', 'public_editor.cc', diff --git a/libs/ardour/ardour/midi_model.h b/libs/ardour/ardour/midi_model.h index 83fe00567a..23e4b9de56 100644 --- a/libs/ardour/ardour/midi_model.h +++ b/libs/ardour/ardour/midi_model.h @@ -172,8 +172,66 @@ public: Change unmarshal_change (XMLNode *); }; + class PatchChangeDiffCommand : public DiffCommand { + public: + PatchChangeDiffCommand (boost::shared_ptr<MidiModel>, const std::string &); + PatchChangeDiffCommand (boost::shared_ptr<MidiModel>, const XMLNode &); + + int set_state (const XMLNode &, int version); + XMLNode & get_state (); + + void operator() (); + void undo (); + + void add (PatchChangePtr); + void remove (PatchChangePtr); + void change_time (PatchChangePtr, TimeType); + void change_channel (PatchChangePtr, uint8_t); + void change_program (PatchChangePtr, uint8_t); + void change_bank (PatchChangePtr, int); + + enum Property { + Time, + Channel, + Program, + Bank + }; + + private: + + struct Change { + PatchChangePtr patch; + Property property; + union { + TimeType old_time; + uint8_t old_channel; + int old_bank; + uint8_t old_program; + }; + union { + uint8_t new_channel; + TimeType new_time; + uint8_t new_program; + int new_bank; + }; + }; + + typedef std::list<Change> ChangeList; + ChangeList _changes; + + std::list<PatchChangePtr> _added; + std::list<PatchChangePtr> _removed; + + XMLNode & marshal_change (const Change &); + Change unmarshal_change (XMLNode *); + + XMLNode & marshal_patch_change (constPatchChangePtr); + PatchChangePtr unmarshal_patch_change (XMLNode *); + }; + MidiModel::NoteDiffCommand* new_note_diff_command (const std::string name = "midi edit"); MidiModel::SysExDiffCommand* new_sysex_diff_command (const std::string name = "midi edit"); + MidiModel::PatchChangeDiffCommand* new_patch_change_diff_command (const std::string name = "midi edit"); void apply_command (Session& session, Command* cmd); void apply_command_as_subcommand (Session& session, Command* cmd); @@ -193,6 +251,7 @@ public: void set_midi_source (boost::shared_ptr<MidiSource>); boost::shared_ptr<Evoral::Note<TimeType> > find_note (NotePtr); + PatchChangePtr find_patch_change (Evoral::event_id_t); boost::shared_ptr<Evoral::Note<TimeType> > find_note (gint note_id); boost::shared_ptr<Evoral::Event<TimeType> > find_sysex (gint); diff --git a/libs/ardour/enums.cc b/libs/ardour/enums.cc index 9fc1ef96ce..cde3f418fd 100644 --- a/libs/ardour/enums.cc +++ b/libs/ardour/enums.cc @@ -114,6 +114,7 @@ setup_enum_writer () MuteMaster::MutePoint _MuteMaster_MutePoint; MidiModel::NoteDiffCommand::Property _MidiModel_NoteDiffCommand_Property; MidiModel::SysExDiffCommand::Property _MidiModel_SysExDiffCommand_Property; + MidiModel::PatchChangeDiffCommand::Property _MidiModel_PatchChangeDiffCommand_Property; WaveformScale _WaveformScale; WaveformShape _WaveformShape; QuantizeType _QuantizeType; @@ -536,6 +537,11 @@ setup_enum_writer () REGISTER_CLASS_ENUM (MidiModel::SysExDiffCommand, Time); REGISTER (_MidiModel_SysExDiffCommand_Property); + + REGISTER_CLASS_ENUM (MidiModel::PatchChangeDiffCommand, Time); + REGISTER_CLASS_ENUM (MidiModel::PatchChangeDiffCommand, Program); + REGISTER_CLASS_ENUM (MidiModel::PatchChangeDiffCommand, Bank); + REGISTER (_MidiModel_PatchChangeDiffCommand_Property); REGISTER_ENUM(Linear); REGISTER_ENUM(Logarithmic); diff --git a/libs/ardour/midi_model.cc b/libs/ardour/midi_model.cc index e185300cca..2cdf6b45da 100644 --- a/libs/ardour/midi_model.cc +++ b/libs/ardour/midi_model.cc @@ -70,6 +70,16 @@ MidiModel::new_sysex_diff_command (const string name) return new SysExDiffCommand (ms->model(), name); } +/** Start a new PatchChangeDiff command */ +MidiModel::PatchChangeDiffCommand* +MidiModel::new_patch_change_diff_command (const string name) +{ + boost::shared_ptr<MidiSource> ms = _midi_source.lock (); + assert (ms); + + return new PatchChangeDiffCommand (ms->model(), name); +} + /** Apply a command. * @@ -107,6 +117,10 @@ MidiModel::apply_command_as_subcommand(Session& session, Command* cmd) #define SIDE_EFFECT_REMOVALS_ELEMENT "SideEffectRemovals" #define SYSEX_DIFF_COMMAND_ELEMENT "SysExDiffCommand" #define DIFF_SYSEXES_ELEMENT "ChangedSysExes" +#define PATCH_CHANGE_DIFF_COMMAND_ELEMENT "PatchChangeDiffCommand" +#define ADDED_PATCH_CHANGES_ELEMENT "AddedPatchChanges" +#define REMOVED_PATCH_CHANGES_ELEMENT "RemovedPatchChanges" +#define DIFF_PATCH_CHANGES_ELEMENT "ChangedPatchChanges" MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const std::string& name) : Command (name) @@ -890,6 +904,415 @@ MidiModel::SysExDiffCommand::get_state () return *diff_command; } +MidiModel::PatchChangeDiffCommand::PatchChangeDiffCommand (boost::shared_ptr<MidiModel> m, const string& name) + : DiffCommand (m, name) +{ + assert (_model); +} + +MidiModel::PatchChangeDiffCommand::PatchChangeDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode & node) + : DiffCommand (m, "") +{ + assert (_model); + set_state (node, Stateful::loading_state_version); +} + +void +MidiModel::PatchChangeDiffCommand::add (PatchChangePtr p) +{ + _added.push_back (p); +} + +void +MidiModel::PatchChangeDiffCommand::remove (PatchChangePtr p) +{ + _removed.push_back (p); +} + +void +MidiModel::PatchChangeDiffCommand::change_time (PatchChangePtr patch, TimeType t) +{ + Change c; + c.property = Time; + c.patch = patch; + c.old_time = patch->time (); + c.new_time = t; + + _changes.push_back (c); +} + +void +MidiModel::PatchChangeDiffCommand::change_channel (PatchChangePtr patch, uint8_t channel) +{ + Change c; + c.property = Channel; + c.patch = patch; + c.old_channel = patch->channel (); + c.new_channel = channel; + + _changes.push_back (c); +} + +void +MidiModel::PatchChangeDiffCommand::change_program (PatchChangePtr patch, uint8_t program) +{ + Change c; + c.property = Program; + c.patch = patch; + c.old_program = patch->program (); + c.new_program = program; + + _changes.push_back (c); +} + +void +MidiModel::PatchChangeDiffCommand::change_bank (PatchChangePtr patch, int bank) +{ + Change c; + c.property = Bank; + c.patch = patch; + c.old_bank = patch->bank (); + c.new_bank = bank; + + _changes.push_back (c); +} + +void +MidiModel::PatchChangeDiffCommand::operator() () +{ + { + MidiModel::WriteLock lock (_model->edit_lock ()); + + for (list<PatchChangePtr>::iterator i = _added.begin(); i != _added.end(); ++i) { + _model->add_patch_change_unlocked (*i); + } + + for (list<PatchChangePtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) { + _model->remove_patch_change_unlocked (*i); + } + + set<PatchChangePtr> temporary_removals; + + for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) { + switch (i->property) { + case Time: + if (temporary_removals.find (i->patch) == temporary_removals.end()) { + _model->remove_patch_change_unlocked (i->patch); + temporary_removals.insert (i->patch); + } + i->patch->set_time (i->new_time); + break; + + case Channel: + i->patch->set_channel (i->new_channel); + break; + + case Program: + i->patch->set_program (i->new_program); + break; + + case Bank: + i->patch->set_bank (i->new_bank); + break; + } + } + + for (set<PatchChangePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) { + _model->add_patch_change_unlocked (*i); + } + } + + _model->ContentsChanged (); /* EMIT SIGNAL */ +} + +void +MidiModel::PatchChangeDiffCommand::undo () +{ + { + MidiModel::WriteLock lock (_model->edit_lock()); + + for (list<PatchChangePtr>::iterator i = _added.begin(); i != _added.end(); ++i) { + _model->remove_patch_change_unlocked (*i); + } + + for (list<PatchChangePtr>::iterator i = _removed.begin(); i != _removed.end(); ++i) { + _model->add_patch_change_unlocked (*i); + } + + set<PatchChangePtr> temporary_removals; + + for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) { + switch (i->property) { + case Time: + if (temporary_removals.find (i->patch) == temporary_removals.end()) { + _model->remove_patch_change_unlocked (i->patch); + temporary_removals.insert (i->patch); + } + i->patch->set_time (i->old_time); + break; + + case Channel: + i->patch->set_channel (i->old_channel); + break; + + case Program: + i->patch->set_program (i->old_program); + break; + + case Bank: + i->patch->set_bank (i->old_bank); + break; + } + } + + for (set<PatchChangePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) { + _model->add_patch_change_unlocked (*i); + } + + } + + _model->ContentsChanged (); /* EMIT SIGNAL */ +} + +XMLNode & +MidiModel::PatchChangeDiffCommand::marshal_patch_change (constPatchChangePtr p) +{ + XMLNode* n = new XMLNode ("patch-change"); + + { + ostringstream s (ios::ate); + s << int (p->id ()); + n->add_property ("id", s.str()); + } + + { + ostringstream s (ios::ate); + s << p->time (); + n->add_property ("time", s.str ()); + } + + { + ostringstream s (ios::ate); + s << int (p->channel ()); + n->add_property ("channel", s.str ()); + } + + { + ostringstream s (ios::ate); + s << int (p->program ()); + n->add_property ("program", s.str ()); + } + + { + ostringstream s (ios::ate); + s << int (p->bank ()); + n->add_property ("bank", s.str ()); + } + + return *n; +} + +XMLNode& +MidiModel::PatchChangeDiffCommand::marshal_change (const Change& c) +{ + XMLNode* n = new XMLNode (X_("Change")); + + n->add_property (X_("property"), enum_2_string (c.property)); + + { + ostringstream s (ios::ate); + if (c.property == Time) { + s << c.old_time; + } else if (c.property == Channel) { + s << c.old_channel; + } else if (c.property == Program) { + s << int (c.old_program); + } else if (c.property == Bank) { + s << c.old_bank; + } + + n->add_property (X_("old"), s.str ()); + } + + { + ostringstream s (ios::ate); + + if (c.property == Time) { + s << c.new_time; + } else if (c.property == Channel) { + s << c.new_channel; + } else if (c.property == Program) { + s << int (c.new_program); + } else if (c.property == Bank) { + s << c.new_bank; + } + + n->add_property (X_("new"), s.str ()); + } + + { + ostringstream s; + s << c.patch->id (); + n->add_property ("id", s.str ()); + } + + return *n; +} + +MidiModel::PatchChangePtr +MidiModel::PatchChangeDiffCommand::unmarshal_patch_change (XMLNode* n) +{ + XMLProperty* prop; + Evoral::event_id_t id; + Evoral::MusicalTime time = 0; + uint8_t channel = 0; + uint8_t program = 0; + int bank = 0; + + if ((prop = n->property ("id")) != 0) { + istringstream s (prop->value()); + s >> id; + } + + if ((prop = n->property ("time")) != 0) { + istringstream s (prop->value ()); + s >> time; + } + + if ((prop = n->property ("channel")) != 0) { + istringstream s (prop->value ()); + s >> channel; + } + + if ((prop = n->property ("program")) != 0) { + istringstream s (prop->value ()); + s >> program; + } + + if ((prop = n->property ("bank")) != 0) { + istringstream s (prop->value ()); + s >> bank; + } + + PatchChangePtr p (new Evoral::PatchChange<TimeType> (time, channel, program, bank)); + p->set_id (id); + return p; +} + +MidiModel::PatchChangeDiffCommand::Change +MidiModel::PatchChangeDiffCommand::unmarshal_change (XMLNode* n) +{ + XMLProperty* prop; + Change c; + + prop = n->property ("property"); + assert (prop); + c.property = (Property) string_2_enum (prop->value(), c.property); + + prop = n->property ("id"); + assert (prop); + Evoral::event_id_t const id = atoi (prop->value().c_str()); + + prop = n->property ("old"); + assert (prop); + { + istringstream s (prop->value ()); + if (c.property == Time) { + s >> c.old_time; + } else if (c.property == Channel) { + s >> c.old_channel; + } else if (c.property == Program) { + s >> c.old_program; + } else if (c.property == Bank) { + s >> c.old_bank; + } + } + + prop = n->property ("new"); + assert (prop); + { + istringstream s (prop->value ()); + if (c.property == Time) { + s >> c.new_time; + } else if (c.property == Channel) { + s >> c.new_channel; + } else if (c.property == Program) { + s >> c.new_program; + } else if (c.property == Bank) { + s >> c.new_bank; + } + } + + c.patch = _model->find_patch_change (id); + assert (c.patch); + + return c; +} + +int +MidiModel::PatchChangeDiffCommand::set_state (const XMLNode& diff_command, int /*version*/) +{ + if (diff_command.name() != PATCH_CHANGE_DIFF_COMMAND_ELEMENT) { + return 1; + } + + _added.clear (); + XMLNode* added = diff_command.child (ADDED_PATCH_CHANGES_ELEMENT); + if (added) { + XMLNodeList p = added->children (); + transform (p.begin(), p.end(), back_inserter (_added), boost::bind (&PatchChangeDiffCommand::unmarshal_patch_change, this, _1)); + } + + _removed.clear (); + XMLNode* removed = diff_command.child (REMOVED_PATCH_CHANGES_ELEMENT); + if (removed) { + XMLNodeList p = removed->children (); + transform (p.begin(), p.end(), back_inserter (_removed), boost::bind (&PatchChangeDiffCommand::unmarshal_patch_change, this, _1)); + } + + _changes.clear (); + XMLNode* changed = diff_command.child (DIFF_PATCH_CHANGES_ELEMENT); + if (changed) { + XMLNodeList p = changed->children (); + transform (p.begin(), p.end(), back_inserter (_changes), boost::bind (&PatchChangeDiffCommand::unmarshal_change, this, _1)); + } + + return 0; +} + +XMLNode & +MidiModel::PatchChangeDiffCommand::get_state () +{ + XMLNode* diff_command = new XMLNode (PATCH_CHANGE_DIFF_COMMAND_ELEMENT); + diff_command->add_property("midi-source", _model->midi_source()->id().to_s()); + + XMLNode* added = diff_command->add_child (ADDED_PATCH_CHANGES_ELEMENT); + for_each (_added.begin(), _added.end(), + boost::bind ( + boost::bind (&XMLNode::add_child_nocopy, added, _1), + boost::bind (&PatchChangeDiffCommand::marshal_patch_change, this, _1) + ) + ); + + XMLNode* removed = diff_command->add_child (REMOVED_PATCH_CHANGES_ELEMENT); + for_each (_removed.begin(), _removed.end(), + boost::bind ( + boost::bind (&XMLNode::add_child_nocopy, removed, _1), + boost::bind (&PatchChangeDiffCommand::marshal_patch_change, this, _1) + ) + ); + + XMLNode* changes = diff_command->add_child (DIFF_PATCH_CHANGES_ELEMENT); + for_each (_changes.begin(), _changes.end(), + boost::bind ( + boost::bind (&XMLNode::add_child_nocopy, changes, _1), + boost::bind (&PatchChangeDiffCommand::marshal_change, this, _1) + ) + ); + + return *diff_command; +} + /** Write all of the model to a MidiSource (i.e. save the model). * This is different from manually using read to write to a source in that * note off events are written regardless of the track mode. This is so the @@ -1084,6 +1507,18 @@ MidiModel::find_note (gint note_id) return NotePtr(); } +MidiModel::PatchChangePtr +MidiModel::find_patch_change (Evoral::event_id_t id) +{ + for (PatchChanges::iterator i = patch_changes().begin(); i != patch_changes().end(); ++i) { + if ((*i)->id() == id) { + return *i; + } + } + + return PatchChangePtr (); +} + boost::shared_ptr<Evoral::Event<MidiModel::TimeType> > MidiModel::find_sysex (gint sysex_id) { diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 93245d6d01..115d451023 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -3196,6 +3196,28 @@ Session::restore_history (string snapshot_name) error << _("Failed to downcast MidiSource for NoteDiffCommand") << endmsg; } + } else if (n->name() == "SysExDiffCommand") { + + PBD::ID id (n->property("midi-source")->value()); + boost::shared_ptr<MidiSource> midi_source = + boost::dynamic_pointer_cast<MidiSource, Source>(source_by_id(id)); + if (midi_source) { + ut->add_command (new MidiModel::SysExDiffCommand (midi_source->model(), *n)); + } else { + error << _("Failed to downcast MidiSource for SysExDiffCommand") << endmsg; + } + + } else if (n->name() == "PatchChangeDiffCommand") { + + PBD::ID id (n->property("midi-source")->value()); + boost::shared_ptr<MidiSource> midi_source = + boost::dynamic_pointer_cast<MidiSource, Source>(source_by_id(id)); + if (midi_source) { + ut->add_command (new MidiModel::PatchChangeDiffCommand (midi_source->model(), *n)); + } else { + error << _("Failed to downcast MidiSource for PatchChangeDiffCommand") << endmsg; + } + } else if (n->name() == "StatefulDiffCommand") { if ((c = stateful_diff_command_factory (n))) { ut->add_command (c); diff --git a/libs/evoral/evoral/Sequence.hpp b/libs/evoral/evoral/Sequence.hpp index abc147af8d..77d06a673e 100644 --- a/libs/evoral/evoral/Sequence.hpp +++ b/libs/evoral/evoral/Sequence.hpp @@ -31,6 +31,7 @@ #include "evoral/Parameter.hpp" #include "evoral/ControlSet.hpp" #include "evoral/ControlList.hpp" +#include "evoral/PatchChange.hpp" namespace Evoral { @@ -101,7 +102,7 @@ public: void append(const Event<Time>& ev, Evoral::event_id_t evid); inline size_t n_notes() const { return _notes.size(); } - inline bool empty() const { return _notes.size() == 0 && ControlSet::controls_empty(); } + inline bool empty() const { return _notes.empty() && _sysexes.empty() && _patch_changes.empty() && ControlSet::controls_empty(); } inline static bool note_time_comparator(const boost::shared_ptr< const Note<Time> >& a, const boost::shared_ptr< const Note<Time> >& b) { @@ -177,6 +178,19 @@ public: inline SysExes& sysexes() { return _sysexes; } inline const SysExes& sysexes() const { return _sysexes; } + typedef boost::shared_ptr<PatchChange<Time> > PatchChangePtr; + typedef boost::shared_ptr<const PatchChange<Time> > constPatchChangePtr; + + struct EarlierPatchChangeComparator { + inline bool operator() (constPatchChangePtr a, constPatchChangePtr b) const { + return a->time() < b->time(); + } + }; + + typedef std::multiset<PatchChangePtr, EarlierPatchChangeComparator> PatchChanges; + inline PatchChanges& patch_changes () { return _patch_changes; } + inline const PatchChanges& patch_changes () const { return _patch_changes; } + private: typedef std::priority_queue<NotePtr, std::deque<NotePtr>, LaterNoteEndComparator> ActiveNotes; public: @@ -208,19 +222,24 @@ public: friend class Sequence<Time>; typedef std::vector<ControlIterator> ControlIterators; - enum MIDIMessageType { NIL, NOTE_ON, NOTE_OFF, CONTROL, SYSEX }; - - const Sequence<Time>* _seq; - boost::shared_ptr< Event<Time> > _event; - mutable ActiveNotes _active_notes; - MIDIMessageType _type; - bool _is_end; - typename Sequence::ReadLock _lock; - typename Notes::const_iterator _note_iter; - typename SysExes::const_iterator _sysex_iter; - ControlIterators _control_iters; - ControlIterators::iterator _control_iter; - bool _force_discrete; + enum MIDIMessageType { NIL, NOTE_ON, NOTE_OFF, CONTROL, SYSEX, PATCH_CHANGE }; + + const Sequence<Time>* _seq; + boost::shared_ptr< Event<Time> > _event; + mutable ActiveNotes _active_notes; + /** If the iterator is pointing at a patch change, this is the index of the + * sub-message within that change. + */ + int _active_patch_change_message; + MIDIMessageType _type; + bool _is_end; + typename Sequence::ReadLock _lock; + typename Notes::const_iterator _note_iter; + typename SysExes::const_iterator _sysex_iter; + typename PatchChanges::const_iterator _patch_change_iter; + ControlIterators _control_iters; + ControlIterators::iterator _control_iter; + bool _force_discrete; }; const_iterator begin ( @@ -233,6 +252,7 @@ public: const const_iterator& end() const { return _end_iter; } typename Notes::const_iterator note_lower_bound (Time t) const; + typename PatchChanges::const_iterator patch_change_lower_bound (Time t) const; bool control_to_midi_event(boost::shared_ptr< Event<Time> >& ev, const ControlIterator& iter) const; @@ -247,6 +267,9 @@ public: bool add_note_unlocked (const NotePtr note, void* arg = 0); void remove_note_unlocked(const constNotePtr note); + void add_patch_change_unlocked (const PatchChangePtr); + void remove_patch_change_unlocked (const constPatchChangePtr); + uint8_t lowest_note() const { return _lowest_note; } uint8_t highest_note() const { return _highest_note; } @@ -276,6 +299,7 @@ private: void append_note_off_unlocked(NotePtr); void append_control_unlocked(const Parameter& param, Time time, double value, Evoral::event_id_t); void append_sysex_unlocked(const MIDIEvent<Time>& ev, Evoral::event_id_t); + void append_patch_change_unlocked (const PatchChange<Time>&, Evoral::event_id_t); void get_notes_by_pitch (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const; void get_notes_by_velocity (Notes&, NoteOperator, uint8_t val, int chan_mask = 0) const; @@ -284,13 +308,20 @@ private: const TypeMap& _type_map; - Notes _notes; // notes indexed by time - Pitches _pitches[16]; // notes indexed by channel+pitch - SysExes _sysexes; + Notes _notes; // notes indexed by time + Pitches _pitches[16]; // notes indexed by channel+pitch + SysExes _sysexes; + PatchChanges _patch_changes; typedef std::multiset<NotePtr, EarlierNoteComparator> WriteNotes; WriteNotes _write_notes[16]; + /** Current bank number on each channel so that we know what + * to put in PatchChange events when program changes are + * seen. + */ + int _bank[16]; + const const_iterator _end_iter; bool _percussive; diff --git a/libs/evoral/src/SMF.cpp b/libs/evoral/src/SMF.cpp index e58090f2a3..8cc14290f7 100644 --- a/libs/evoral/src/SMF.cpp +++ b/libs/evoral/src/SMF.cpp @@ -281,10 +281,18 @@ SMF::append_event_delta(uint32_t delta_t, uint32_t size, const uint8_t* buf, eve smf_event_t* event; - /* XXX july 2010: currently only store event ID's for notes + /* XXX july 2010: currently only store event ID's for notes, program changes and bank changes */ - if (((buf[0] & 0xf0) == MIDI_CMD_NOTE_ON || ((buf[0] & 0xf0) == MIDI_CMD_NOTE_OFF)) && note_id >= 0) { + uint8_t const c = buf[0] & 0xf0; + bool const store_id = ( + c == MIDI_CMD_NOTE_ON || + c == MIDI_CMD_NOTE_OFF || + c == MIDI_CMD_PGM_CHANGE || + (c == MIDI_CMD_CONTROL && (buf[1] == MIDI_CTL_MSB_BANK || buf[1] == MIDI_CTL_LSB_BANK)) + ); + + if (store_id && note_id >= 0) { int idlen; int lenlen; uint8_t idbuf[16]; @@ -293,7 +301,6 @@ SMF::append_event_delta(uint32_t delta_t, uint32_t size, const uint8_t* buf, eve event = smf_event_new (); assert(event != NULL); - /* generate VLQ representation of note ID */ idlen = smf_format_vlq (idbuf, sizeof(idbuf), note_id); diff --git a/libs/evoral/src/Sequence.cpp b/libs/evoral/src/Sequence.cpp index b71e6920d8..78c04a6759 100644 --- a/libs/evoral/src/Sequence.cpp +++ b/libs/evoral/src/Sequence.cpp @@ -64,10 +64,12 @@ Sequence<Time>::const_iterator::const_iterator() template<typename Time> Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq, Time t, bool force_discrete, std::set<Evoral::Parameter> const & filtered) : _seq(&seq) + , _active_patch_change_message (0) , _type(NIL) , _is_end((t == DBL_MAX) || seq.empty()) , _note_iter(seq.notes().end()) , _sysex_iter(seq.sysexes().end()) + , _patch_change_iter(seq.patch_changes().end()) , _control_iter(_control_iters.end()) , _force_discrete (force_discrete) { @@ -94,6 +96,15 @@ Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq, Time t } assert(_sysex_iter == seq.sysexes().end() || (*_sysex_iter)->time() >= t); + // Find first patch event at or after t + for (typename Sequence<Time>::PatchChanges::const_iterator i = seq.patch_changes().begin(); i != seq.patch_changes().end(); ++i) { + if ((*i)->time() >= t) { + _patch_change_iter = i; + break; + } + } + assert (_patch_change_iter == seq.patch_changes().end() || (*_patch_change_iter)->time() >= t); + // Find first control event after t ControlIterator earliest_control(boost::shared_ptr<ControlList>(), DBL_MAX, 0.0); _control_iters.reserve(seq._controls.size()); @@ -163,6 +174,11 @@ Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq, Time t earliest_t = (*_sysex_iter)->time(); } + if (_patch_change_iter != seq.patch_changes().end() && ((*_patch_change_iter)->time() < earliest_t || _type == NIL)) { + _type = PATCH_CHANGE; + earliest_t = (*_patch_change_iter)->time (); + } + if (_control_iter != _control_iters.end() && earliest_control.list && earliest_control.x >= t && (earliest_control.x < earliest_t || _type == NIL)) { @@ -185,6 +201,10 @@ Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq, Time t DEBUG_TRACE (DEBUG::Sequence, string_compose ("Starting at control event @ %1\n", earliest_t)); seq.control_to_midi_event(_event, earliest_control); break; + case PATCH_CHANGE: + DEBUG_TRACE (DEBUG::Sequence, string_compose ("Starting at patch change event @ %1\n", earliest_t)); + _event = boost::shared_ptr<Event<Time> > (new Event<Time> ((*_patch_change_iter)->message (_active_patch_change_message), true)); + break; default: break; } @@ -194,12 +214,14 @@ Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq, Time t _type = NIL; _is_end = true; } else { - DEBUG_TRACE (DEBUG::Sequence, string_compose ("New iterator = 0x%x : 0x%x @ %f\n", + DEBUG_TRACE (DEBUG::Sequence, string_compose ("New iterator = 0x%1 : 0x%2 @ %3\n", (int)_event->event_type(), (int)((MIDIEvent<Time>*)_event.get())->type(), _event->time())); + assert(midi_event_is_valid(_event->buffer(), _event->size())); } + } template<typename Time> @@ -219,6 +241,8 @@ Sequence<Time>::const_iterator::invalidate() if (_seq) { _note_iter = _seq->notes().end(); _sysex_iter = _seq->sysexes().end(); + _patch_change_iter = _seq->patch_changes().end(); + _active_patch_change_message = 0; } _control_iter = _control_iters.end(); _lock.reset(); @@ -291,6 +315,13 @@ Sequence<Time>::const_iterator::operator++() case SYSEX: ++_sysex_iter; break; + case PATCH_CHANGE: + ++_active_patch_change_message; + if (_active_patch_change_message == (*_patch_change_iter)->messages()) { + ++_patch_change_iter; + _active_patch_change_message = 0; + } + break; default: assert(false); } @@ -329,6 +360,14 @@ Sequence<Time>::const_iterator::operator++() } } + // Use the next earliest patch change iff it's earlier than the SysEx + if (_patch_change_iter != _seq->patch_changes().end()) { + if (_type == NIL || (*_patch_change_iter)->time() < earliest_t) { + _type = PATCH_CHANGE; + earliest_t = (*_patch_change_iter)->time(); + } + } + // Set event to reflect new position switch (_type) { case NOTE_ON: @@ -350,6 +389,10 @@ Sequence<Time>::const_iterator::operator++() DEBUG_TRACE(DEBUG::Sequence, "iterator = sysex\n"); *_event = *(*_sysex_iter); break; + case PATCH_CHANGE: + DEBUG_TRACE(DEBUG::Sequence, "iterator = patch change\n"); + *_event = (*_patch_change_iter)->message (_active_patch_change_message); + break; default: DEBUG_TRACE(DEBUG::Sequence, "iterator = end\n"); _is_end = true; @@ -386,8 +429,10 @@ Sequence<Time>::const_iterator::operator=(const const_iterator& other) _is_end = other._is_end; _note_iter = other._note_iter; _sysex_iter = other._sysex_iter; + _patch_change_iter = other._patch_change_iter; _control_iters = other._control_iters; _force_discrete = other._force_discrete; + _active_patch_change_message = other._active_patch_change_message; if (other._lock) _lock = _seq->read_lock(); @@ -421,6 +466,10 @@ Sequence<Time>::Sequence(const TypeMap& type_map) DEBUG_TRACE (DEBUG::Sequence, string_compose ("Sequence constructed: %1\n", this)); assert(_end_iter._is_end); assert( ! _end_iter._lock); + + for (int i = 0; i < 16; ++i) { + _bank[i] = 0; + } } template<typename Time> @@ -446,6 +495,15 @@ Sequence<Time>::Sequence(const Sequence<Time>& other) _sysexes.push_back (n); } + for (typename PatchChanges::const_iterator i = other._patch_changes.begin(); i != other._patch_changes.end(); ++i) { + PatchChangePtr n (new PatchChange<Time> (**i)); + _patch_changes.insert (n); + } + + for (int i = 0; i < 16; ++i) { + _bank[i] = other._bank[i]; + } + DEBUG_TRACE (DEBUG::Sequence, string_compose ("Sequence copied: %1\n", this)); assert(_end_iter._is_end); assert(! _end_iter._lock); @@ -697,6 +755,34 @@ Sequence<Time>::remove_note_unlocked(const constNotePtr note) } } +template<typename Time> +void +Sequence<Time>::add_patch_change_unlocked (PatchChangePtr p) +{ + _patch_changes.insert (p); + if (p->id () < 0) { + p->set_id (Evoral::next_event_id ()); + } +} + +template<typename Time> +void +Sequence<Time>::remove_patch_change_unlocked (const constPatchChangePtr p) +{ + typename Sequence<Time>::PatchChanges::iterator i = patch_change_lower_bound (p->time ()); + while (i != _patch_changes.end() && (*i)->time() == p->time()) { + + typename Sequence<Time>::PatchChanges::iterator tmp = i; + ++tmp; + + if (*i == p) { + _patch_changes.erase (i); + } + + i = tmp; + } +} + /** Append \a ev to model. NOT realtime safe. * * The timestamp of event is expected to be relative to @@ -730,14 +816,22 @@ Sequence<Time>::append(const Event<Time>& event, event_id_t evid) append_note_off_unlocked (note); } else if (ev.is_sysex()) { append_sysex_unlocked(ev, evid); + } else if (ev.is_cc() && (ev.cc_number() == MIDI_CTL_MSB_BANK || ev.cc_number() == MIDI_CTL_LSB_BANK)) { + /* note bank numbers in our _bank[] array, so that we can write an event when the program change arrives */ + if (ev.cc_number() == MIDI_CTL_MSB_BANK) { + _bank[ev.channel()] &= (0x7f << 7); + _bank[ev.channel()] |= ev.cc_value() << 7; + } else { + _bank[ev.channel()] &= 0x7f; + _bank[ev.channel()] |= ev.cc_value(); + } } else if (ev.is_cc()) { append_control_unlocked( Evoral::MIDI::ContinuousController(ev.event_type(), ev.channel(), ev.cc_number()), ev.time(), ev.cc_value(), evid); } else if (ev.is_pgm_change()) { - append_control_unlocked( - Evoral::MIDI::ProgramChange(ev.event_type(), ev.channel()), - ev.time(), ev.pgm_number(), evid); + /* write a patch change with this program change and any previously set-up bank number */ + append_patch_change_unlocked (PatchChange<Time> (ev.time(), ev.channel(), ev.pgm_number(), _bank[ev.channel()]), evid); } else if (ev.is_pitch_bender()) { append_control_unlocked( Evoral::MIDI::PitchBender(ev.event_type(), ev.channel()), @@ -874,6 +968,19 @@ Sequence<Time>::append_sysex_unlocked(const MIDIEvent<Time>& ev, event_id_t /* e } template<typename Time> +void +Sequence<Time>::append_patch_change_unlocked (const PatchChange<Time>& ev, event_id_t id) +{ + PatchChangePtr p (new PatchChange<Time> (ev)); + + if (p->id() < 0) { + p->set_id (id); + } + + _patch_changes.insert (p); +} + +template<typename Time> bool Sequence<Time>::contains (const NotePtr& note) const { @@ -956,6 +1063,17 @@ Sequence<Time>::note_lower_bound (Time t) const return i; } +/** Return the earliest patch change with time >= t */ +template<typename Time> +typename Sequence<Time>::PatchChanges::const_iterator +Sequence<Time>::patch_change_lower_bound (Time t) const +{ + PatchChangePtr search (new PatchChange<Time> (t, 0, 0, 0)); + typename Sequence<Time>::PatchChanges::const_iterator i = _patch_changes.lower_bound (search); + assert (i == _patch_changes.end() || (*i)->time() >= t); + return i; +} + template<typename Time> void Sequence<Time>::get_notes (Notes& n, NoteOperator op, uint8_t val, int chan_mask) const |