Fix time / positioning of PC flags (beat time).
Fix needless string copying in flag stuff. Clean up. git-svn-id: svn://localhost/ardour2/branches/3.0@4601 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
de88640598
commit
31a6e0b254
|
@ -5,7 +5,6 @@
|
|||
using namespace Gnome::Canvas;
|
||||
using namespace std;
|
||||
|
||||
|
||||
void
|
||||
CanvasFlag::delete_allocated_objects()
|
||||
{
|
||||
|
@ -20,7 +19,7 @@ CanvasFlag::delete_allocated_objects()
|
|||
}
|
||||
|
||||
void
|
||||
CanvasFlag::set_text(string& a_text)
|
||||
CanvasFlag::set_text(const string& a_text)
|
||||
{
|
||||
delete_allocated_objects();
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef CANVASFLAG_H_
|
||||
#define CANVASFLAG_H_
|
||||
|
||||
#include <string>
|
||||
#include <libgnomecanvasmm/group.h>
|
||||
#include <libgnomecanvasmm/widget.h>
|
||||
|
||||
|
@ -19,41 +20,41 @@ class CanvasFlag : public Group, public InteractiveItem
|
|||
{
|
||||
public:
|
||||
CanvasFlag(
|
||||
MidiRegionView& region,
|
||||
Group& parent,
|
||||
double height,
|
||||
guint outline_color_rgba = 0xc0c0c0ff,
|
||||
guint fill_color_rgba = 0x07070707,
|
||||
double x = 0.0,
|
||||
double y = 0.0
|
||||
) : Group(parent, x, y)
|
||||
, _text(0)
|
||||
, _height(height)
|
||||
, _outline_color_rgba(outline_color_rgba)
|
||||
, _fill_color_rgba(fill_color_rgba)
|
||||
, _region(region)
|
||||
, _line(0)
|
||||
, _rect(0)
|
||||
MidiRegionView& region,
|
||||
Group& parent,
|
||||
double height,
|
||||
guint outline_color_rgba = 0xc0c0c0ff,
|
||||
guint fill_color_rgba = 0x07070707,
|
||||
double x = 0.0,
|
||||
double y = 0.0)
|
||||
: Group(parent, x, y)
|
||||
, _text(0)
|
||||
, _height(height)
|
||||
, _outline_color_rgba(outline_color_rgba)
|
||||
, _fill_color_rgba(fill_color_rgba)
|
||||
, _region(region)
|
||||
, _line(0)
|
||||
, _rect(0)
|
||||
{}
|
||||
|
||||
virtual ~CanvasFlag();
|
||||
|
||||
virtual bool on_event(GdkEvent* ev);
|
||||
|
||||
void set_text(string& a_text);
|
||||
void set_text(const std::string& a_text);
|
||||
|
||||
protected:
|
||||
InteractiveText* _text;
|
||||
double _height;
|
||||
guint _outline_color_rgba;
|
||||
guint _fill_color_rgba;
|
||||
MidiRegionView& _region;
|
||||
InteractiveText* _text;
|
||||
double _height;
|
||||
guint _outline_color_rgba;
|
||||
guint _fill_color_rgba;
|
||||
MidiRegionView& _region;
|
||||
|
||||
private:
|
||||
void delete_allocated_objects();
|
||||
|
||||
SimpleLine* _line;
|
||||
InteractiveRect* _rect;
|
||||
SimpleLine* _line;
|
||||
InteractiveRect* _rect;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ using namespace std;
|
|||
CanvasProgramChange::CanvasProgramChange(
|
||||
MidiRegionView& region,
|
||||
Group& parent,
|
||||
string& text,
|
||||
const string& text,
|
||||
double height,
|
||||
double x,
|
||||
double y,
|
||||
|
|
|
@ -20,7 +20,7 @@ public:
|
|||
CanvasProgramChange(
|
||||
MidiRegionView& region,
|
||||
Group& parent,
|
||||
string& text,
|
||||
const string& text,
|
||||
double height,
|
||||
double x,
|
||||
double y,
|
||||
|
|
|
@ -609,8 +609,7 @@ MidiRegionView::display_program_change_flags()
|
|||
MIDI::Name::MidiPatchManager::instance().find_patch(
|
||||
_model_name, _custom_device_mode, channel, patch_key);
|
||||
|
||||
ControlEvent program_change(beats_to_frames(event_time),
|
||||
uint8_t(program_number), channel);
|
||||
PCEvent program_change(event_time, uint8_t(program_number), channel);
|
||||
|
||||
if (patch != 0) {
|
||||
add_pgm_change(program_change, patch->name());
|
||||
|
@ -753,7 +752,7 @@ MidiRegionView::add_ghost (TimeAxisView& tv)
|
|||
|
||||
if (mtv && mtv->midi_view()) {
|
||||
/* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
|
||||
to allow having midi notes on top of note lines and waveforms under it.
|
||||
to allow having midi notes on top of note lines and waveforms.
|
||||
*/
|
||||
ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
|
||||
} else {
|
||||
|
@ -921,10 +920,7 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
|
|||
if (_active_notes[note->note()]) {
|
||||
CanvasNote* const old_rect = _active_notes[note->note()];
|
||||
boost::shared_ptr<NoteType> old_note = old_rect->note();
|
||||
cerr << "MidiModel: WARNING: Note has length 0: chan " << old_note->channel()
|
||||
<< "note " << (int)old_note->note() << " @ " << old_note->time() << endl;
|
||||
/* FIXME: How large to make it? Make it a diamond? */
|
||||
old_rect->property_x2() = old_rect->property_x1() + 2.0;
|
||||
old_rect->property_x2() = x;
|
||||
old_rect->property_outline_what() = (guint32) 0xF;
|
||||
}
|
||||
_active_notes[note->note()] = ev_rect;
|
||||
|
@ -971,16 +967,12 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
|
|||
}
|
||||
|
||||
void
|
||||
MidiRegionView::add_pgm_change(ControlEvent& program, string displaytext)
|
||||
MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
|
||||
{
|
||||
assert(program.time >= 0);
|
||||
|
||||
// dont display program changes beyond the region bounds
|
||||
if (program.time - _region->start() >= _region->length() || program.time < _region->start())
|
||||
return;
|
||||
|
||||
ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
|
||||
const double x = trackview.editor().frame_to_pixel((nframes64_t)program.time - _region->start());
|
||||
const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
|
||||
|
||||
double height = midi_stream_view()->contents_height();
|
||||
|
||||
|
@ -993,6 +985,13 @@ MidiRegionView::add_pgm_change(ControlEvent& program, string displaytext)
|
|||
_custom_device_mode,
|
||||
program.time, program.channel, program.value));
|
||||
|
||||
// Show unless program change is beyond the region bounds
|
||||
if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
|
||||
pgm_change->hide();
|
||||
} else {
|
||||
pgm_change->show();
|
||||
}
|
||||
|
||||
_pgm_changes.push_back(pgm_change);
|
||||
}
|
||||
|
||||
|
@ -1001,7 +1000,7 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch
|
|||
{
|
||||
cerr << "getting patch key at " << time << " for channel " << channel << endl;
|
||||
Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
|
||||
boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
|
||||
boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
|
||||
float msb = -1.0;
|
||||
if (msb_control != 0) {
|
||||
msb = int(msb_control->get_float(true, time));
|
||||
|
@ -1009,7 +1008,7 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch
|
|||
}
|
||||
|
||||
Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
|
||||
boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
|
||||
boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
|
||||
float lsb = -1.0;
|
||||
if (lsb_control != 0) {
|
||||
lsb = lsb_control->get_float(true, time);
|
||||
|
@ -1017,7 +1016,7 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch
|
|||
}
|
||||
|
||||
Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
|
||||
boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
|
||||
boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
|
||||
float program_number = -1.0;
|
||||
if (program_control != 0) {
|
||||
program_number = program_control->get_float(true, time);
|
||||
|
@ -1032,24 +1031,24 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch
|
|||
|
||||
|
||||
void
|
||||
MidiRegionView::alter_program_change(ControlEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
|
||||
MidiRegionView::alter_program_change(PCEvent& old_program, 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);
|
||||
boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
|
||||
if (msb_control != 0) {
|
||||
msb_control->set_float(float(new_patch.msb), true, old_program.time);
|
||||
}
|
||||
|
||||
// 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);
|
||||
boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
|
||||
if (lsb_control != 0) {
|
||||
lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
|
||||
}
|
||||
|
||||
Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
|
||||
boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
|
||||
boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
|
||||
|
||||
assert(program_control != 0);
|
||||
program_control->set_float(float(new_patch.program_number), true, old_program.time);
|
||||
|
@ -1060,7 +1059,7 @@ MidiRegionView::alter_program_change(ControlEvent& old_program, const MIDI::Name
|
|||
void
|
||||
MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
|
||||
{
|
||||
ControlEvent program_change_event(program.event_time(), program.program(), program.channel());
|
||||
PCEvent program_change_event(program.event_time(), program.program(), program.channel());
|
||||
alter_program_change(program_change_event, new_patch);
|
||||
}
|
||||
|
||||
|
@ -1077,7 +1076,7 @@ MidiRegionView::previous_program(CanvasProgramChange& program)
|
|||
program.channel(),
|
||||
key);
|
||||
|
||||
ControlEvent program_change_event(program.event_time(), program.program(), program.channel());
|
||||
PCEvent program_change_event(program.event_time(), program.program(), program.channel());
|
||||
if (patch) {
|
||||
alter_program_change(program_change_event, patch->patch_primary_key());
|
||||
}
|
||||
|
@ -1095,7 +1094,8 @@ MidiRegionView::next_program(CanvasProgramChange& program)
|
|||
_custom_device_mode,
|
||||
program.channel(),
|
||||
key);
|
||||
ControlEvent program_change_event(program.event_time(), program.program(), program.channel());
|
||||
|
||||
PCEvent program_change_event(program.event_time(), program.program(), program.channel());
|
||||
if (patch) {
|
||||
alter_program_change(program_change_event, patch->patch_primary_key());
|
||||
}
|
||||
|
@ -1246,81 +1246,83 @@ void
|
|||
MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote)
|
||||
{
|
||||
// TODO: This would be faster/nicer with a MoveCommand that doesn't need to copy...
|
||||
if (_selection.find(ev) != _selection.end()) {
|
||||
uint8_t lowest_note_in_selection = midi_stream_view()->lowest_note();
|
||||
uint8_t highest_note_in_selection = midi_stream_view()->highest_note();
|
||||
uint8_t highest_note_difference = 0;
|
||||
if (_selection.find(ev) == _selection.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find highest and lowest notes first
|
||||
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
|
||||
uint8_t pitch = (*i)->note()->note();
|
||||
lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
|
||||
highest_note_in_selection = std::max(highest_note_in_selection, pitch);
|
||||
uint8_t lowest_note_in_selection = midi_stream_view()->lowest_note();
|
||||
uint8_t highest_note_in_selection = midi_stream_view()->highest_note();
|
||||
uint8_t highest_note_difference = 0;
|
||||
|
||||
// find highest and lowest notes first
|
||||
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
|
||||
uint8_t pitch = (*i)->note()->note();
|
||||
lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
|
||||
highest_note_in_selection = std::max(highest_note_in_selection, pitch);
|
||||
}
|
||||
|
||||
/*
|
||||
cerr << "dnote: " << (int) dnote << endl;
|
||||
cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
|
||||
<< " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
|
||||
cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
|
||||
<< int(highest_note_in_selection) << endl;
|
||||
cerr << "selection size: " << _selection.size() << endl;
|
||||
cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
|
||||
*/
|
||||
|
||||
// Make sure the note pitch does not exceed the MIDI standard range
|
||||
if (dnote <= 127 && (highest_note_in_selection + dnote > 127)) {
|
||||
highest_note_difference = highest_note_in_selection - 127;
|
||||
}
|
||||
|
||||
start_delta_command(_("move notes"));
|
||||
|
||||
for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ) {
|
||||
Selection::iterator next = i;
|
||||
++next;
|
||||
|
||||
const boost::shared_ptr<NoteType> copy(new NoteType(*(*i)->note().get()));
|
||||
|
||||
// we need to snap here again in nframes64_t in order to be sample accurate
|
||||
double start_frames = snap_to_frame(beats_to_frames((*i)->note()->time()) + dt);
|
||||
|
||||
// keep notes inside region if dragged beyond left region bound
|
||||
if (start_frames < _region->start()) {
|
||||
start_frames = _region->start();
|
||||
}
|
||||
|
||||
/*
|
||||
cerr << "dnote: " << (int) dnote << endl;
|
||||
cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
|
||||
<< " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
|
||||
cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
|
||||
<< int(highest_note_in_selection) << endl;
|
||||
cerr << "selection size: " << _selection.size() << endl;
|
||||
cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
|
||||
*/
|
||||
copy->set_time(frames_to_beats(start_frames));
|
||||
|
||||
uint8_t original_pitch = (*i)->note()->note();
|
||||
uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
|
||||
|
||||
// Make sure the note pitch does not exceed the MIDI standard range
|
||||
if (dnote <= 127 && (highest_note_in_selection + dnote > 127)) {
|
||||
highest_note_difference = highest_note_in_selection - 127;
|
||||
}
|
||||
// keep notes in standard midi range
|
||||
clamp_to_0_127(new_pitch);
|
||||
|
||||
start_delta_command(_("move notes"));
|
||||
|
||||
for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ) {
|
||||
Selection::iterator next = i;
|
||||
++next;
|
||||
|
||||
const boost::shared_ptr<NoteType> copy(new NoteType(*(*i)->note().get()));
|
||||
|
||||
// we need to snap here again in nframes64_t in order to be sample accurate
|
||||
double start_frames = snap_to_frame(beats_to_frames((*i)->note()->time()) + dt);
|
||||
|
||||
// keep notes inside region if dragged beyond left region bound
|
||||
if (start_frames < _region->start()) {
|
||||
start_frames = _region->start();
|
||||
}
|
||||
|
||||
copy->set_time(frames_to_beats(start_frames));
|
||||
|
||||
uint8_t original_pitch = (*i)->note()->note();
|
||||
uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
|
||||
|
||||
// keep notes in standard midi range
|
||||
clamp_to_0_127(new_pitch);
|
||||
|
||||
// keep original pitch if note is dragged outside valid midi range
|
||||
if ((original_pitch != 0 && new_pitch == 0)
|
||||
|| (original_pitch != 127 && new_pitch == 127)) {
|
||||
new_pitch = original_pitch;
|
||||
}
|
||||
|
||||
lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
|
||||
highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
|
||||
|
||||
copy->set_note(new_pitch);
|
||||
|
||||
command_remove_note(*i);
|
||||
command_add_note(copy, (*i)->selected());
|
||||
|
||||
i = next;
|
||||
// keep original pitch if note is dragged outside valid midi range
|
||||
if ((original_pitch != 0 && new_pitch == 0)
|
||||
|| (original_pitch != 127 && new_pitch == 127)) {
|
||||
new_pitch = original_pitch;
|
||||
}
|
||||
|
||||
apply_command();
|
||||
lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
|
||||
highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
|
||||
|
||||
copy->set_note(new_pitch);
|
||||
|
||||
// care about notes being moved beyond the upper/lower bounds on the canvas
|
||||
if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
|
||||
highest_note_in_selection > midi_stream_view()->highest_note()) {
|
||||
midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
|
||||
}
|
||||
command_remove_note(*i);
|
||||
command_add_note(copy, (*i)->selected());
|
||||
|
||||
i = next;
|
||||
}
|
||||
|
||||
apply_command();
|
||||
|
||||
// care about notes being moved beyond the upper/lower bounds on the canvas
|
||||
if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
|
||||
highest_note_in_selection > midi_stream_view()->highest_note()) {
|
||||
midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#ifndef __gtk_ardour_midi_region_view_h__
|
||||
#define __gtk_ardour_midi_region_view_h__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <libgnomecanvasmm.h>
|
||||
|
@ -96,28 +97,20 @@ class MidiRegionView : public RegionView
|
|||
void add_note(const boost::shared_ptr<NoteType> note);
|
||||
void resolve_note(uint8_t note_num, double end_time);
|
||||
|
||||
struct ControlEvent
|
||||
{
|
||||
nframes_t time;
|
||||
uint8_t value;
|
||||
uint8_t channel;
|
||||
|
||||
ControlEvent(nframes_t a_time, uint8_t a_value, uint8_t a_channel)
|
||||
struct PCEvent {
|
||||
PCEvent(double a_time, uint8_t a_value, uint8_t a_channel)
|
||||
: time(a_time), value(a_value), channel(a_channel) {}
|
||||
|
||||
ControlEvent& operator=(const ControlEvent& other) {
|
||||
time = other.time;
|
||||
value = other.value;
|
||||
channel = other.channel;
|
||||
return *this;
|
||||
}
|
||||
|
||||
double time;
|
||||
uint8_t value;
|
||||
uint8_t channel;
|
||||
};
|
||||
|
||||
/** Add a new program change flag to the canvas.
|
||||
* @param program the MidiRegionView::ControlEvent to add
|
||||
* @param program the MidiRegionView::PCEvent to add
|
||||
* @param the text to display in the flag
|
||||
*/
|
||||
void add_pgm_change(ControlEvent& program, string displaytext);
|
||||
void add_pgm_change(PCEvent& program, 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
|
||||
|
@ -128,10 +121,10 @@ class MidiRegionView : public RegionView
|
|||
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 identifies the program change event which is to be altered
|
||||
* @param new_patch defines the new lsb, msb and program number which are to be set in the automation list data
|
||||
* @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
|
||||
*/
|
||||
void alter_program_change(ControlEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch);
|
||||
void alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch);
|
||||
|
||||
/** Alter a given program to the new given one.
|
||||
* (Called on context menu select on CanvasProgramChange)
|
||||
|
@ -160,7 +153,7 @@ class MidiRegionView : public RegionView
|
|||
|
||||
void display_model(boost::shared_ptr<ARDOUR::MidiModel> model);
|
||||
|
||||
void start_delta_command(string name = "midi edit");
|
||||
void start_delta_command(std::string name = "midi edit");
|
||||
void command_add_note(const boost::shared_ptr<NoteType> note, bool selected);
|
||||
void command_remove_note(ArdourCanvas::CanvasNoteEvent* ev);
|
||||
|
||||
|
@ -308,10 +301,10 @@ class MidiRegionView : public RegionView
|
|||
uint8_t _current_range_max;
|
||||
|
||||
/// MIDNAM information of the current track: Model name of MIDNAM file
|
||||
string _model_name;
|
||||
std::string _model_name;
|
||||
|
||||
/// MIDNAM information of the current track: CustomDeviceMode
|
||||
string _custom_device_mode;
|
||||
std::string _custom_device_mode;
|
||||
|
||||
typedef std::vector<ArdourCanvas::CanvasNoteEvent*> Events;
|
||||
typedef std::vector< boost::shared_ptr<ArdourCanvas::CanvasProgramChange> > PgmChanges;
|
||||
|
|
Loading…
Reference in New Issue