From d68183ade14404ab2384549073fe032f68072ab9 Mon Sep 17 00:00:00 2001 From: Michael Welsh Duggan Date: Tue, 10 May 2011 20:39:04 -0700 Subject: [PATCH] MIDI: handle overlapping notes; issue 1647 When notes overlap on the same pitch on the same MIDI channel, re-arrange the note-off events for MIDI players that cannot handle overlap, but if midiMergeUnisons = #t, merge such notes. --- input/regression/midi-unisons.ly | 20 +++++++++++++ lily/audio-staff.cc | 2 +- lily/include/audio-staff.hh | 1 + lily/include/midi-walker.hh | 2 +- lily/midi-walker.cc | 48 +++++++++++++++++++------------ lily/staff-performer.cc | 2 ++ scm/define-context-properties.scm | 2 ++ 7 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 input/regression/midi-unisons.ly diff --git a/input/regression/midi-unisons.ly b/input/regression/midi-unisons.ly new file mode 100644 index 0000000000..8e3048d127 --- /dev/null +++ b/input/regression/midi-unisons.ly @@ -0,0 +1,20 @@ +\header { + + texidoc = "In overlapping unisons, within a single MIDI channel, +either the first note is truncated, or the notes are merged if +@code{midiMergeUnisons} is @code{#t}. Run +@code{timidity -idvvv file.midi |grep Midi} to see midi events." + +} + +\version "2.15.0" + +\score { + { + \set Score.midiChannelMapping = #'staff + \new Staff << {r8 g'4.} \\ {g'4. r8} >> + \new Staff \with { midiMergeUnisons = ##t } << {r8 a'4.} \\ {a'4. r8} >> + } + \midi {} + \layout {} +} diff --git a/lily/audio-staff.cc b/lily/audio-staff.cc index d40220ca1c..ddab7e04bc 100644 --- a/lily/audio-staff.cc +++ b/lily/audio-staff.cc @@ -30,7 +30,7 @@ Audio_staff::add_audio_item (Audio_item *ai) } Audio_staff::Audio_staff () - : percussion_ (false) + : percussion_ (false), merge_unisons_ (false) { } diff --git a/lily/include/audio-staff.hh b/lily/include/audio-staff.hh index acf31b17a4..665593edd6 100644 --- a/lily/include/audio-staff.hh +++ b/lily/include/audio-staff.hh @@ -32,6 +32,7 @@ struct Audio_staff : public Audio_element Audio_staff (); bool percussion_; + bool merge_unisons_; vector audio_items_; }; diff --git a/lily/include/midi-walker.hh b/lily/include/midi-walker.hh index 4fd1ae39fc..3d99b34021 100644 --- a/lily/include/midi-walker.hh +++ b/lily/include/midi-walker.hh @@ -53,8 +53,8 @@ private: void output_event (int, Midi_item *l); Midi_item *get_midi (Audio_item*); Midi_track *track_; - Audio_staff *staff_; bool percussion_; + bool merge_unisons_; vsize index_; vector items_; PQueue stop_note_queue; diff --git a/lily/midi-walker.cc b/lily/midi-walker.cc index fdafd91b1d..6fbb780a1b 100644 --- a/lily/midi-walker.cc +++ b/lily/midi-walker.cc @@ -60,6 +60,7 @@ Midi_walker::Midi_walker (Audio_staff *audio_staff, Midi_track *track) vector_sort (items_, audio_item_less); last_tick_ = 0; percussion_ = audio_staff->percussion_; + merge_unisons_ = audio_staff->merge_unisons_; } Midi_walker::~Midi_walker () @@ -81,31 +82,43 @@ Midi_walker::do_start_note (Midi_note *note) { Audio_item *ptr = items_[index_]; assert (note->audio_ == ptr); - int stop_ticks = int (moment_to_real (note->audio_->length_mom_) * Real (384 * 4)) - + ptr->audio_column_->ticks (); - - bool play_start = true; + int now_ticks = ptr->audio_column_->ticks (); + int stop_ticks = int (moment_to_real (note->audio_->length_mom_) * + Real (384 * 4)) + now_ticks; for (vsize i = 0; i < stop_note_queue.size (); i++) { - /* if this pith already in queue */ + /* if this pitch already in queue */ if (stop_note_queue[i].val->get_semitone_pitch () == note->get_semitone_pitch ()) { - if (stop_note_queue[i].key < stop_ticks) + int queued_ticks + = stop_note_queue[i].val->audio_->audio_column_->ticks (); + // If the two notes started at the same time, or option is set, + if (now_ticks == queued_ticks || merge_unisons_) { - /* let stopnote in queue be ignored, - new stop note wins */ - stop_note_queue[i].ignore_ = true; - - /* don't replay start note, */ - play_start = false; + // merge them. + if (stop_note_queue[i].key < stop_ticks) + { + Midi_note_event e; + e.val = stop_note_queue[i].val; + e.key = stop_ticks; + stop_note_queue[i].ignore_ = true; + stop_note_queue.insert (e); + } + note = 0; break; } else { - /* skip this stopnote, - don't play the start note */ - note = 0; + // A note was played that interruped a played note. + // Stop the old note, and continue to the greatest moment + // between the two. + if (stop_note_queue[i].key > stop_ticks) + { + stop_ticks = stop_note_queue[i].key; + } + output_event (now_ticks, stop_note_queue[i].val); + stop_note_queue[i].ignore_ = true; break; } } @@ -117,11 +130,10 @@ Midi_walker::do_start_note (Midi_note *note) e.val = new Midi_note_off (note); midi_events_.push_back (e.val); - e.key = int (stop_ticks); + e.key = stop_ticks; stop_note_queue.insert (e); - if (play_start) - output_event (ptr->audio_column_->ticks (), note); + output_event (now_ticks, note); } } diff --git a/lily/staff-performer.cc b/lily/staff-performer.cc index 502c289e0c..bab8ae0257 100644 --- a/lily/staff-performer.cc +++ b/lily/staff-performer.cc @@ -105,6 +105,8 @@ Audio_staff* Staff_performer::new_audio_staff (string voice) { Audio_staff* audio_staff = new Audio_staff; + audio_staff->merge_unisons_ + = to_boolean (get_property ("midiMergeUnisons")); string track_name = context ()->id_string () + ":" + voice; if (track_name != ":") { diff --git a/scm/define-context-properties.scm b/scm/define-context-properties.scm index e9484ba194..dab5211f14 100644 --- a/scm/define-context-properties.scm +++ b/scm/define-context-properties.scm @@ -358,6 +358,8 @@ is used for ottava brackets.") half staff-spaces. Usually determined by looking at @code{middleCClefPosition} and @code{middleCOffset}.") (midiInstrument ,string? "Name of the MIDI instrument to use.") + (midiMergeUnisons ,boolean? "If true, output only one MIDI note-on +event when notes with the same pitch, in the same MIDI-file track, overlap.") (midiMaximumVolume ,number? "Analogous to @code{midiMinimumVolume}.") (midiMinimumVolume ,number? "Set the minimum loudness for MIDI. -- 2.39.2