Compare commits

...

27 Commits

Author SHA1 Message Date
Paul Davis dc8382c0b5 some enforcement of NoInsert KeyEnforcementPolicy 2021-11-18 16:44:16 -07:00
Paul Davis e8baad9efa more centralized code for deciding if a note is visible or note based on key 2021-11-18 15:41:59 -07:00
Paul Davis d0cdd0c9db somewhat functional menu to control KeyEnforcementPolicy
And yes, this should likely be global, not per track
2021-11-18 14:17:21 -07:00
Paul Davis bf28ecfc4c fix enum registration for key enforcement policy (it's a bitmask) 2021-11-18 14:16:37 -07:00
Paul Davis 407936277f NOOP: whitespace 2021-11-18 11:35:01 -07:00
Paul Davis bfe52ca668 key enforcement: if NoInsert is in effect, MIDI mouse editing does not insert non-scale notes 2021-11-18 11:34:50 -07:00
Paul Davis a3a8f355ee key enforcement: use NoDraw to help decide whether or not to show ghost notes 2021-11-18 11:25:42 -07:00
Paul Davis 9f4bf02980 change type of MidiTrack's key enforcement to new enum type 2021-11-18 11:16:50 -07:00
Paul Davis 987c4de725 extend set of key enforcement policy flags 2021-11-18 11:13:29 -07:00
Paul Davis ae8c755d51 add new KeyEnforcementPolicy enum type 2021-11-18 11:00:00 -07:00
Paul Davis 331377212e tweak menu item names to be more reflective of common (western) practice 2021-11-18 10:36:31 -07:00
Paul Davis eb0c1984de midi region view: don't show ghost note if it is not in key 2021-11-18 10:36:07 -07:00
Paul Davis 2bb880aac0 midi track redisplay when mode/root is changed 2021-11-18 08:30:46 -07:00
Paul Davis 1793c9a61a add new properties for musical mode & root 2021-11-18 08:30:46 -07:00
Paul Davis 4648785935 prefer major/minor to ionian/aeolian (but show both names) 2021-11-18 08:30:46 -07:00
Paul Davis 6cdbd4174d Implement mode/root context menus and modification methods to MidiTimeAxisView 2021-11-18 08:30:46 -07:00
Paul Davis 9f76d688fd provide missing implementations for MidiTrack key API 2021-11-18 08:30:46 -07:00
Paul Davis 1555896d8e Extend API for MusicalMode and MusicalScale to expose mode type as a member 2021-11-18 08:30:46 -07:00
Paul Davis 55a0aadc74 Modify ParameterDescriptor::midi_note_name() to offer chance to get a non-octave-version 2021-11-18 08:30:45 -07:00
Paul Davis 22efa2bf17 use color on MIDI tracks to show off-key notes 2021-11-18 08:30:45 -07:00
Paul Davis 15e4ba5123 add new color name for offkey notes 2021-11-18 08:30:45 -07:00
Paul Davis 7f88c71bd3 implement MusicalKey::in_key()
Limited to 12 tones per octave for now
2021-11-18 08:30:45 -07:00
Paul Davis fb7dd71c45 add default key and boolean to define whether or not to enforce it 2021-11-18 08:30:45 -07:00
Paul Davis 4801f488c5 reflect name change in files 2021-11-18 08:30:45 -07:00
Paul Davis a7907d9e99 reflect rename in libs/ardour/wscript 2021-11-18 08:30:45 -07:00
Paul Davis 46242a0079 rename scale to key
Scale is just a silly biased overly limiting name for Mode, so use
Key for "Mode + Root"
2021-11-18 08:30:45 -07:00
Paul Davis b4d6b888bb initial data structure for musical scales (derived from mode)
This is built around a MIDI-like assumption that we have a fixed (integer) number of
note IDs and a semitone between them. We will need to expand this definition in the
future to handle Bohlen-Pierce, maqams and various other non-12TET scale systems
2021-11-18 08:30:45 -07:00
17 changed files with 625 additions and 9 deletions

View File

@ -856,6 +856,13 @@ MidiRegionView::create_note_at (timepos_t const & t, double y, Temporal::Beats l
Temporal::Beats region_start = t.beats();
const double note = view->y_to_note(y);
if (mtv->midi_track()->key_enforcment_policy() & NoInsert) {
if (!mtv->midi_track()->key().in_key (note)) {
return;
}
}
const uint8_t chan = get_channel_for_add(region_start);
const uint8_t velocity = get_velocity_for_add (region_start);
@ -1638,6 +1645,15 @@ MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
const timepos_t note_start (note->time());
timepos_t note_end (note->end_time());
MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
MusicalKey const & key (mtv->midi_track()->key());
if (!(mtv->midi_track()->key_enforcment_policy() & NoDraw) || key.in_key (note->note())) {
ev->show ();
} else {
ev->hide ();
return;
}
/* The note is drawn as a child item of this region view, so its
* coordinate system is relative to the region view. This means that x0
* and x1 are pixel offsets relative to beginning of the region (view)
@ -2681,6 +2697,9 @@ MidiRegionView::note_dropped(NoteBase *, timecnt_t const & d_qn, int8_t dnote, b
uint8_t highest_note_in_selection = 0;
uint8_t highest_note_difference = 0;
MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
MusicalKey const & key (mtv->midi_track()->key());
if (!copy) {
// find highest and lowest notes first
@ -2726,7 +2745,11 @@ MidiRegionView::note_dropped(NoteBase *, timecnt_t const & d_qn, int8_t dnote, b
lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
if (!(mtv->midi_track()->key_enforcment_policy() & NoInsert) || key.in_key (new_pitch)) {
note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
} else {
/* XXX remove it? or what? */
}
}
} else {
@ -2769,6 +2792,10 @@ MidiRegionView::note_dropped(NoteBase *, timecnt_t const & d_qn, int8_t dnote, b
lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
if (!(mtv->midi_track()->key_enforcment_policy() & NoInsert) || key.in_key (new_pitch)) {
note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
}
note_diff_add_note ((*i)->note(), true);
delete *i;
@ -4219,7 +4246,7 @@ MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value
name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
(int) note_value,
(int) n->channel() + 1,
(int) n->velocity()); //we display velocity 0-based; velocity 0 is a 'note-off' so the user just sees values 1..127 which 'looks' 1-based
(int) n->velocity()); //we display velocity 0-based; velocity 0 is a 'note-off' so the user just sees values 1..127 which 'looks' 1-based
return buf;
}

View File

@ -349,19 +349,34 @@ MidiStreamView::draw_note_lines()
* height of this note.
*/
std::string color_mod_name;
std::string color_name;
switch (i % 12) {
case 1:
case 3:
case 6:
case 8:
case 10:
color = UIConfiguration::instance().color_mod ("piano roll black", "piano roll black");
if (boost::dynamic_pointer_cast<MidiTrack>(_trackview.track())->key().in_key (i)) {
color_name = X_("piano roll black");
} else {
color_name = X_("piano roll offkey");
}
color_mod_name = X_("piano roll black");
break;
default:
color = UIConfiguration::instance().color_mod ("piano roll white", "piano roll white");
if (boost::dynamic_pointer_cast<MidiTrack>(_trackview.track())->key().in_key (i)) {
color_name = X_("piano roll white");
} else {
color_name = X_("piano roll offkey");
}
color_mod_name = X_("piano roll white");
break;
}
color = UIConfiguration::instance().color_mod (color_name, color_mod_name);
double h = y - prev_y;
double mid = y + (h/2.0);

View File

@ -640,6 +640,10 @@ MidiTimeAxisView::append_extra_display_menu_items ()
cmi->set_active (midi_track ()->restore_pgm_on_load ());
cmi->signal_activate().connect (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_restore_pgm_on_load));
items.push_back (MenuElem (_("Scale"), *build_musical_mode_menu ()));
items.push_back (MenuElem (_("Tonic (Root)"), *build_musical_root_menu ()));
items.push_back (MenuElem (_("Key Enforcement"), *build_key_enforcement_menu()));
items.push_back (MenuElem (_("Color Mode"), *build_color_mode_menu ()));
items.push_back (SeparatorElem ());
@ -1117,6 +1121,376 @@ MidiTimeAxisView::build_note_mode_menu()
return mode_menu;
}
Gtk::Menu*
MidiTimeAxisView::build_musical_root_menu ()
{
using namespace Menu_Helpers;
Menu* root_menu = manage (new Menu);
MenuList& items = root_menu->items();
root_menu->set_name ("ArdourContextMenu");
RadioMenuItem::Group root_group;
RadioMenuItem* last_item;
int r = midi_track()->key().root();
for (int n = 0; n < 12; ++n) {
items.push_back (RadioMenuElem (root_group, ParameterDescriptor::midi_note_name (n, true, false), sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_root), n)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (r == n);
}
return root_menu;
}
Gtk::Menu*
MidiTimeAxisView::build_key_enforcement_menu ()
{
using namespace Menu_Helpers;
Menu* menu = manage (new Menu);
MenuList& items = menu->items();
menu->set_name ("ArdourContextMenu");
RadioMenuItem::Group group;
CheckMenuItem* last_check_item;
RadioMenuItem* last_radio_item;
KeyEnforcementPolicy kep = midi_track()->key_enforcment_policy();
items.push_back (CheckMenuElem (_("Don't show non-scale ghost notes while drawing/editing")));
last_check_item = dynamic_cast<CheckMenuItem*>(&items.back());
last_check_item->set_active (kep & NoDraw);
last_check_item->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_key_enforcement_policy), NoDraw, last_check_item));
items.push_back (CheckMenuElem (_("Don't allow mouse edits to create non-scale notes")));
last_check_item = dynamic_cast<CheckMenuItem*>(&items.back());
last_check_item->set_active (kep & NoInsert);
last_check_item->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_key_enforcement_policy), NoInsert, last_check_item));
items.push_back (RadioMenuElem (group, _("Force note edits of non-scale notes to next lower note")));
last_radio_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_radio_item->set_active (kep & ForceLower);
last_radio_item->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_key_enforcement_policy), ForceLower, last_radio_item));
items.push_back (RadioMenuElem (group, _("Force note edits of non-scale notes to next higher note")));
last_radio_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_radio_item->set_active (kep & ForceHigher);
last_radio_item->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_key_enforcement_policy), ForceHigher, last_radio_item));
items.push_back (RadioMenuElem (group, _("Force note edits of non-scale notes to nearest note")));
last_radio_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_radio_item->set_active (kep & ForceNearest);
last_radio_item->signal_toggled().connect (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_key_enforcement_policy), ForceNearest, last_radio_item));
return menu;
}
void
MidiTimeAxisView::toggle_key_enforcement_policy (KeyEnforcementPolicy kepb, CheckMenuItem* item)
{
KeyEnforcementPolicy kep = midi_track()->key_enforcment_policy();
/* Some of the menu items that trigger this are radio menu items, and
this method will be called when they go inactive. We want to ignore
these calls.
*/
if (!item->get_active()) {
return;
}
if (kep & kepb) {
midi_track()->set_key_enforcement_policy (KeyEnforcementPolicy (kep & ~kepb));
} else {
midi_track()->set_key_enforcement_policy (KeyEnforcementPolicy (kep | kepb));
}
}
void
MidiTimeAxisView::set_musical_root (int r)
{
midi_track()->set_key (MusicalKey (midi_track()->key().type(), r));
}
Gtk::Menu*
MidiTimeAxisView::build_musical_mode_menu()
{
using namespace Menu_Helpers;
Menu* mode_menu = manage (new Menu);
MenuList& items = mode_menu->items();
mode_menu->set_name ("ArdourContextMenu");
RadioMenuItem::Group mode_group;
RadioMenuItem* last_item;
MusicalMode::Type t = midi_track()->key().type();
items.push_back (
RadioMenuElem (mode_group, _("Chromatic"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Chromatic)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Chromatic);
items.push_back (
RadioMenuElem (mode_group, _("Major (Ionian)"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::IonianMajor)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::IonianMajor);
items.push_back (
RadioMenuElem (mode_group, _("Minor (Aeolian)"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::AeolianMinor)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::AeolianMinor);
items.push_back (
RadioMenuElem (mode_group, _("Dorian"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Dorian)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Dorian);
items.push_back (
RadioMenuElem (mode_group, _("HarmonicMinor"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::HarmonicMinor)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == 30);
items.push_back (
RadioMenuElem (mode_group, _("MelodicMinorAscending"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::MelodicMinorAscending)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::MelodicMinorAscending);
items.push_back (
RadioMenuElem (mode_group, _("MelodicMinorDescending"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::MelodicMinorDescending)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::MelodicMinorDescending);
items.push_back (
RadioMenuElem (mode_group, _("Phrygian"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Phrygian)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Phrygian);
items.push_back (
RadioMenuElem (mode_group, _("Lydian"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Lydian)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Lydian);
items.push_back (
RadioMenuElem (mode_group, _("Mixolydian"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Mixolydian)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Mixolydian);
items.push_back (
RadioMenuElem (mode_group, _("Locrian"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Locrian)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Locrian);
items.push_back (
RadioMenuElem (mode_group, _("PentatonicMajor"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::PentatonicMajor)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::PentatonicMajor);
items.push_back (
RadioMenuElem (mode_group, _("PentatonicMinor"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::PentatonicMinor)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::PentatonicMinor);
items.push_back (
RadioMenuElem (mode_group, _("BluesScale"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::BluesScale)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::BluesScale);
items.push_back (
RadioMenuElem (mode_group, _("NeapolitanMinor"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::NeapolitanMinor)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::NeapolitanMinor);
items.push_back (
RadioMenuElem (mode_group, _("NeapolitanMajor"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::NeapolitanMajor)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::NeapolitanMajor);
items.push_back (
RadioMenuElem (mode_group, _("Oriental"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Oriental)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Oriental);
items.push_back (
RadioMenuElem (mode_group, _("DoubleHarmonic"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::DoubleHarmonic)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::DoubleHarmonic);
items.push_back (
RadioMenuElem (mode_group, _("Enigmatic"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Enigmatic)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Enigmatic);
items.push_back (
RadioMenuElem (mode_group, _("Hirajoshi"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Hirajoshi)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Hirajoshi);
items.push_back (
RadioMenuElem (mode_group, _("HungarianMinor"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::HungarianMinor)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::HungarianMinor);
items.push_back (
RadioMenuElem (mode_group, _("HungarianMajor"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::HungarianMajor)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::HungarianMajor);
items.push_back (
RadioMenuElem (mode_group, _("Kumoi"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Kumoi)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Kumoi);
items.push_back (
RadioMenuElem (mode_group, _("Iwato"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Iwato)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Iwato);
items.push_back (
RadioMenuElem (mode_group, _("Hindu"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Hindu)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Hindu);
items.push_back (
RadioMenuElem (mode_group, _("Spanish8Tone"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Spanish8Tone)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Spanish8Tone);
items.push_back (
RadioMenuElem (mode_group, _("Pelog"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Pelog)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Pelog);
items.push_back (
RadioMenuElem (mode_group, _("HungarianGypsy"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::HungarianGypsy)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::HungarianGypsy);
items.push_back (
RadioMenuElem (mode_group, _("Overtone"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Overtone)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Overtone);
items.push_back (
RadioMenuElem (mode_group, _("LeadingWholeTone"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::LeadingWholeTone)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::LeadingWholeTone);
items.push_back (
RadioMenuElem (mode_group, _("Arabian"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Arabian)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Arabian);
items.push_back (
RadioMenuElem (mode_group, _("Balinese"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Balinese)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Balinese);
items.push_back (
RadioMenuElem (mode_group, _("Gypsy"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Gypsy)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Gypsy);
items.push_back (
RadioMenuElem (mode_group, _("Mohammedan"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Mohammedan)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Mohammedan);
items.push_back (
RadioMenuElem (mode_group, _("Javanese"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Javanese)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Javanese);
items.push_back (
RadioMenuElem (mode_group, _("Persian"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Persian)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Persian);
items.push_back (
RadioMenuElem (mode_group, _("Algerian"),
sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_musical_mode),
MusicalMode::Algerian)));
last_item = dynamic_cast<RadioMenuItem*>(&items.back());
last_item->set_active (t == MusicalMode::Algerian);
return mode_menu;
}
void
MidiTimeAxisView::set_musical_mode (MusicalMode::Type mode_type)
{
midi_track()->set_key (MusicalKey (mode_type, midi_track()->key().root()));
}
Gtk::Menu*
MidiTimeAxisView::build_color_mode_menu()
{
@ -1781,3 +2155,13 @@ MidiTimeAxisView::get_regions_with_selected_data (RegionSelection& rs)
{
midi_view()->get_regions_with_selected_data (rs);
}
void
MidiTimeAxisView::route_property_changed (const PBD::PropertyChange& pc)
{
RouteTimeAxisView::route_property_changed (pc);
if (pc.contains (Properties::musical_mode) || pc.contains (Properties::musical_root)) {
_view->redisplay_track ();
}
}

View File

@ -117,6 +117,7 @@ protected:
void start_step_editing ();
void stop_step_editing ();
void processors_changed (ARDOUR::RouteProcessorChange);
void route_property_changed (const PBD::PropertyChange&);
private:
sigc::signal<void, std::string, std::string> _midi_patch_settings_changed;
@ -133,7 +134,13 @@ private:
void build_automation_action_menu (bool);
Gtk::Menu* build_note_mode_menu();
Gtk::Menu* build_color_mode_menu();
Gtk::Menu* build_musical_mode_menu ();
Gtk::Menu* build_musical_root_menu ();
Gtk::Menu* build_key_enforcement_menu ();
void toggle_key_enforcement_policy (ARDOUR::KeyEnforcementPolicy, Gtk::CheckMenuItem*);
void set_musical_mode (MusicalMode::Type);
void set_musical_root (int note);
void set_note_mode (ARDOUR::NoteMode mode, bool apply_to_selection = false);
void set_color_mode (ARDOUR::ColorMode, bool force = false, bool redisplay = true, bool apply_to_selection = false);
void set_note_range (MidiStreamView::VisibleNoteRange range, bool apply_to_selection = false);
@ -199,4 +206,5 @@ private:
StepEditor* _step_editor;
};
#endif /* __ardour_midi_time_axis_h__ */

View File

@ -294,6 +294,7 @@
<ColorAlias name="piano roll black" alias="theme:contrasting selection"/>
<ColorAlias name="piano roll black outline" alias="neutral:foreground2"/>
<ColorAlias name="piano roll white" alias="neutral:foreground2"/>
<ColorAlias name="piano roll offkey" alias="alert:ruddy"/>
<ColorAlias name="pinrouting custom: led active" alias="alert:ruddy"/>
<ColorAlias name="pinrouting sidechain: led active" alias="alert:green"/>
<ColorAlias name="play head" alias="theme:contrasting"/>

40
libs/ardour/ardour/key.h Normal file
View File

@ -0,0 +1,40 @@
/*
* Copyright (C) 2021 Paul Davis <paul@linuxaudiosystems.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __ardour_key_h__
#define __ardour_key_h__
#include "ardour/mode.h"
class MusicalKey : public MusicalMode
{
public:
MusicalKey (Type t, int root) : MusicalMode (t), _root (root) {}
~MusicalKey ();
int root () const { return _root; }
void set_root (int);
bool in_key (int note) const;
private:
int _root;
};
#endif /* __ardour_key_h__ */

View File

@ -24,11 +24,17 @@
#include "ardour/midi_channel_filter.h"
#include "ardour/midi_ring_buffer.h"
#include "ardour/key.h"
#include "ardour/track.h"
namespace ARDOUR
{
namespace Properties {
LIBARDOUR_API extern PBD::PropertyDescriptor<MusicalKey::Type> musical_mode;
LIBARDOUR_API extern PBD::PropertyDescriptor<int> musical_root;
}
class InterThreadInfo;
class MidiPlaylist;
class RouteGroup;
@ -43,6 +49,8 @@ public:
int init ();
static void make_property_quarks ();
void realtime_locate (bool);
void non_realtime_locate (samplepos_t);
@ -144,6 +152,12 @@ public:
void realtime_handle_transport_stopped ();
void region_edited (boost::shared_ptr<Region>);
MusicalKey const & key() const { return _key; }
void set_key (MusicalKey const & key);
KeyEnforcementPolicy key_enforcment_policy () const;
void set_key_enforcement_policy (KeyEnforcementPolicy);
protected:
XMLNode& state (bool save_template);
@ -166,6 +180,9 @@ private:
MidiChannelFilter _playback_filter;
MidiChannelFilter _capture_filter;
MusicalKey _key;
KeyEnforcementPolicy _key_enforcement_policy;
void set_state_part_two ();
void set_state_part_three ();

View File

@ -68,9 +68,11 @@ class MusicalMode
~MusicalMode ();
std::vector<float> steps;
Type type() const { return _type; }
private:
static void fill (MusicalMode&, Type);
Type _type;
};
#endif /* __ardour_mode_h__ */

View File

@ -50,7 +50,7 @@ struct LIBARDOUR_API ParameterDescriptor : public Evoral::ParameterDescriptor
HZ, ///< Frequency in Hertz
};
static std::string midi_note_name (uint8_t, bool translate=true);
static std::string midi_note_name (uint8_t, bool translate = true, bool with_octave = true);
/** Dual of midi_note_name, convert a note name into its midi note number. */
typedef std::map<std::string, uint8_t> NameNumMap;

View File

@ -811,6 +811,15 @@ enum LocateTransportDisposition {
RollIfAppropriate
};
enum KeyEnforcementPolicy {
NoDraw = 0x1, /* do not show visually as candidate for new notes */
NoInsert = 0x2,
NoPlay = 0x4,
ForceLower = 0x8,
ForceHigher = 0x10,
ForceNearest = 0x20
};
typedef std::vector<CaptureInfo*> CaptureInfos;
} // namespace ARDOUR

View File

@ -158,6 +158,7 @@ setup_enum_writer ()
Trigger::State _TriggerState;
Trigger::LaunchStyle _TriggerLaunchStyle;
Trigger::FollowAction _TriggerFollowAction;
KeyEnforcementPolicy _KeyEnforcementPolicy;
#define REGISTER(e) enum_writer.register_distinct (typeid(e).name(), i, s); i.clear(); s.clear()
#define REGISTER_BITS(e) enum_writer.register_bits (typeid(e).name(), i, s); i.clear(); s.clear()
@ -868,6 +869,13 @@ setup_enum_writer ()
REGISTER_CLASS_ENUM (Trigger, Repeat);
REGISTER (_TriggerLaunchStyle);
REGISTER_ENUM (NoDraw);
REGISTER_ENUM (NoInsert);
REGISTER_ENUM (NoPlay);
REGISTER_ENUM (ForceLower);
REGISTER_ENUM (ForceHigher);
REGISTER_ENUM (ForceNearest);
REGISTER_BITS (_KeyEnforcementPolicy);
}
} /* namespace ARDOUR */

View File

@ -108,6 +108,7 @@
#include "ardour/filesystem_paths.h"
#include "ardour/midi_patch_manager.h"
#include "ardour/midi_region.h"
#include "ardour/midi_track.h"
#include "ardour/midi_ui.h"
#include "ardour/midiport_manager.h"
#include "ardour/mix.h"
@ -563,6 +564,7 @@ ARDOUR::init (bool try_optimization, const char* localedir, bool with_gui)
PresentationInfo::make_property_quarks ();
TransportMaster::make_property_quarks ();
Trigger::make_property_quarks ();
MidiTrack::make_property_quarks ();
/* this is a useful ready to use PropertyChange that many
things need to check. This avoids having to compose

54
libs/ardour/key.cc Normal file
View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2021 Paul Davis <paul@linuxaudiosystems.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ardour/key.h"
MusicalKey::~MusicalKey ()
{
}
void
MusicalKey::set_root (int r)
{
/* force root into lowest octave. Yes, 12 tone for now */
_root = (r % 12);
}
bool
MusicalKey::in_key (int note) const
{
/* currently 12 tone based */
note = note % 12;
/* we should speed this us. Probably a bitset */
if (note == _root) {
return true;
}
for (std::vector<float>::const_iterator i = steps.begin(); i != steps.end(); ++i) {
int ii = (int) ((*i) * 2.0);
if (note == _root + ii) {
return true;
}
}
return false;
}

View File

@ -72,12 +72,27 @@ class InterThreadInfo;
class MidiSource;
class Region;
class SMFSource;
namespace Properties {
PBD::PropertyDescriptor<MusicalMode::Type> musical_mode;
PBD::PropertyDescriptor<int> musical_root;
}
}
using namespace std;
using namespace ARDOUR;
using namespace PBD;
void
MidiTrack::make_property_quarks ()
{
Properties::musical_mode.property_id = g_quark_from_static_string (X_("musical-mode"));
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for musical-mode = %1\n", Properties::musical_mode.property_id));
Properties::musical_root.property_id = g_quark_from_static_string (X_("musical-root"));
DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for musical-root = %1\n", Properties::musical_root.property_id));
}
MidiTrack::MidiTrack (Session& sess, string name, TrackMode mode)
: Track (sess, name, PresentationInfo::MidiTrack, mode, DataType::MIDI)
, _immediate_events(6096) // FIXME: size?
@ -87,6 +102,8 @@ MidiTrack::MidiTrack (Session& sess, string name, TrackMode mode)
, _step_editing (false)
, _input_active (true)
, _restore_pgm_on_load (true)
, _key (MusicalKey (MusicalMode::IonianMajor, 0))
, _key_enforcement_policy (KeyEnforcementPolicy (0))
{
_session.SessionLoaded.connect_same_thread (*this, boost::bind (&MidiTrack::restore_controls, this));
@ -120,6 +137,31 @@ MidiTrack::init ()
return 0;
}
void
MidiTrack::set_key_enforcement_policy (KeyEnforcementPolicy kep)
{
_key_enforcement_policy = kep;
}
KeyEnforcementPolicy
MidiTrack::key_enforcment_policy () const
{
return _key_enforcement_policy;
}
void
MidiTrack::set_key (MusicalKey const & k)
{
_key = k;
PropertyChange pc;
pc.add (Properties::musical_mode);
pc.add (Properties::musical_root);
PropertyChanged (pc); /* EMIT SIGNAL */
}
void
MidiTrack::data_recorded (boost::weak_ptr<MidiSource> src)
{

View File

@ -19,6 +19,7 @@
#include "ardour/mode.h"
MusicalMode::MusicalMode (MusicalMode::Type t)
: _type (t)
{
fill (*this, t);
}

View File

@ -252,7 +252,7 @@ ParameterDescriptor::update_steps()
}
std::string
ParameterDescriptor::midi_note_name (const uint8_t b, bool translate)
ParameterDescriptor::midi_note_name (const uint8_t b, bool translate, bool with_octave)
{
char buf[16];
if (b > 127) {
@ -279,9 +279,14 @@ ParameterDescriptor::midi_note_name (const uint8_t b, bool translate)
S_("Note|B")
};
/* MIDI note 0 is in octave -1 (in scientific pitch notation) */
const int octave = b / 12 - 1;
const size_t p = b % 12;
/* MIDI note 0 is in octave -1 (in scientific pitch notation) */
if (!with_octave) {
return translate ? notes[p] : en_notes[p];
}
const int octave = b / 12 - 1;
snprintf (buf, sizeof (buf), "%s%d", translate ? notes[p] : en_notes[p], octave);
return buf;
}

View File

@ -115,6 +115,7 @@ libardour_sources = [
'interpolation.cc',
'io.cc',
'io_processor.cc',
'key.cc',
'kmeterdsp.cc',
'ladspa_plugin.cc',
'latent.cc',
@ -206,7 +207,7 @@ libardour_sources = [
'route_group_member.cc',
'rb_effect.cc',
'rt_tasklist.cc',
'scene_change.cc',
'scene_change.cc',
'search_paths.cc',
'selection.cc',
'send.cc',