From a42a4f9c507f42456b3ac361788397881b86b1a0 Mon Sep 17 00:00:00 2001 From: Devon Schudy Date: Sun, 24 Nov 2013 07:07:10 -0500 Subject: [PATCH] Support articulations and breaths in MIDI. (issue 3664) Articulations and breaths can alter the length and volume of the note. (Breaths affect the previous chord.) This is controlled by their midi-length and midi-extra-velocity properties. The standard articulations now have these properties where appropriate. (seconds->moment s context) is a convenience function for making absolute durations. --- lily/audio-item.cc | 4 +++- lily/drum-note-performer.cc | 16 ++++++++------ lily/include/audio-item.hh | 3 ++- lily/midi-item.cc | 6 ++++-- lily/note-performer.cc | 37 +++++++++++++++++++++++++++------ ly/music-functions-init.ly | 10 ++++++++- ly/script-init.ly | 25 +++++++++++++++++----- scm/define-music-properties.scm | 6 ++++++ scm/lily-library.scm | 5 +++++ 9 files changed, 90 insertions(+), 22 deletions(-) diff --git a/lily/audio-item.cc b/lily/audio-item.cc index a41357b28f..7934c34c4a 100644 --- a/lily/audio-item.cc +++ b/lily/audio-item.cc @@ -44,11 +44,13 @@ Audio_item::Audio_item () { } -Audio_note::Audio_note (Pitch p, Moment m, bool tie_event, Pitch transposing) +Audio_note::Audio_note (Pitch p, Moment m, bool tie_event, Pitch transposing, + int velocity) : pitch_ (p), length_mom_ (m), transposing_ (transposing), dynamic_ (0), + extra_velocity_ (velocity), tied_ (0), tie_event_ (tie_event) { diff --git a/lily/drum-note-performer.cc b/lily/drum-note-performer.cc index 94c2d55ea3..09ab3f7cf4 100644 --- a/lily/drum-note-performer.cc +++ b/lily/drum-note-performer.cc @@ -62,9 +62,9 @@ Drum_note_performer::process_music () { SCM articulations = n->get_property ("articulations"); Stream_event *tie_event = 0; - for (SCM s = articulations; - !tie_event && scm_is_pair (s); - s = scm_cdr (s)) + Moment len = get_event_length (n, now_mom ()); + int velocity = 0; + for (SCM s = articulations; scm_is_pair (s); s = scm_cdr (s)) { Stream_event *ev = unsmob_stream_event (scm_car (s)); if (!ev) @@ -72,12 +72,16 @@ Drum_note_performer::process_music () if (ev->in_event_class ("tie-event")) tie_event = ev; + SCM f = ev->get_property ("midi-length"); + if (ly_is_procedure (f)) + len = robust_scm2moment (scm_call_2 (f, len.smobbed_copy (), + context ()->self_scm ()), + len); + velocity += robust_scm2int (ev->get_property ("midi-extra-velocity"), 0); } - Moment len = get_event_length (n, now_mom ()); - Audio_note *p = new Audio_note (*pit, len, - tie_event, Pitch (0, 0, 0)); + tie_event, Pitch (0, 0, 0), velocity); Audio_element_info info (p, n); announce_element (info); } diff --git a/lily/include/audio-item.hh b/lily/include/audio-item.hh index 43ed2e05a9..9ded5301a1 100644 --- a/lily/include/audio-item.hh +++ b/lily/include/audio-item.hh @@ -82,7 +82,7 @@ public: class Audio_note : public Audio_item { public: - Audio_note (Pitch p, Moment m, bool tie_event, Pitch transposition); + Audio_note (Pitch p, Moment m, bool tie_event, Pitch transposition, int velocity); // with tieWaitForNote, there might be a skip between the tied notes! void tie_to (Audio_note *, Moment skip = 0); @@ -93,6 +93,7 @@ public: Moment length_mom_; Pitch transposing_; Audio_dynamic *dynamic_; + int extra_velocity_; Audio_note *tied_; bool tie_event_; diff --git a/lily/midi-item.cc b/lily/midi-item.cc index 54d00ff683..f98e3d05e1 100644 --- a/lily/midi-item.cc +++ b/lily/midi-item.cc @@ -193,8 +193,10 @@ Midi_time_signature::to_string () const Midi_note::Midi_note (Audio_note *a) : Midi_channel_item (a), audio_ (a), - dynamic_byte_ (a->dynamic_ && a->dynamic_->volume_ >= 0 - ? Byte (a->dynamic_->volume_ * 0x7f) : Byte (0x5a)) + dynamic_byte_ (min (max (Byte ((a->dynamic_ && a->dynamic_->volume_ >= 0 + ? a->dynamic_->volume_ * 0x7f : 0x5a) + + a->extra_velocity_), + Byte (0)), Byte (0x7f))) { } diff --git a/lily/note-performer.cc b/lily/note-performer.cc index 83ecb52d56..81f35d70ac 100644 --- a/lily/note-performer.cc +++ b/lily/note-performer.cc @@ -36,6 +36,7 @@ protected: void process_music (); DECLARE_TRANSLATOR_LISTENER (note); + DECLARE_TRANSLATOR_LISTENER (breathing); private: vector note_evs_; vector notes_; @@ -65,9 +66,9 @@ Note_performer::process_music () { SCM articulations = n->get_property ("articulations"); Stream_event *tie_event = 0; - for (SCM s = articulations; - !tie_event && scm_is_pair (s); - s = scm_cdr (s)) + Moment len = get_event_length (n, now_mom ()); + int velocity = 0; + for (SCM s = articulations; scm_is_pair (s); s = scm_cdr (s)) { Stream_event *ev = unsmob_stream_event (scm_car (s)); if (!ev) @@ -75,12 +76,16 @@ Note_performer::process_music () if (ev->in_event_class ("tie-event")) tie_event = ev; + SCM f = ev->get_property ("midi-length"); + if (ly_is_procedure (f)) + len = robust_scm2moment (scm_call_2 (f, len.smobbed_copy (), + context ()->self_scm ()), + len); + velocity += robust_scm2int (ev->get_property ("midi-extra-velocity"), 0); } - Moment len = get_event_length (n, now_mom ()); - Audio_note *p = new Audio_note (*pitp, len, - tie_event, transposing); + tie_event, transposing, velocity); Audio_element_info info (p, n); announce_element (info); notes_.push_back (p); @@ -128,6 +133,26 @@ Note_performer::listen_note (Stream_event *ev) note_evs_.push_back (ev); } +IMPLEMENT_TRANSLATOR_LISTENER (Note_performer, breathing) +void +Note_performer::listen_breathing (Stream_event *ev) +{ + //Shorten previous note if needed + SCM f = ev->get_property ("midi-length"); + if (ly_is_procedure (f)) + for (vsize i = 0; i < last_notes_.size (); i++) + { + Audio_note *tie_head = last_notes_[i]->tie_head (); + //Give midi-length the available time since the note started, + //including rests. It returns how much is left for the note. + Moment available = now_mom () - tie_head->audio_column_->when (); + Moment len = robust_scm2moment (scm_call_2 (f, available.smobbed_copy (), + context ()->self_scm ()), available); + if (len < tie_head->length_mom_) + tie_head->length_mom_ = len; + } +} + ADD_TRANSLATOR (Note_performer, /* doc */ "", diff --git a/ly/music-functions-init.ly b/ly/music-functions-init.ly index bb577e28b5..83c2535af5 100644 --- a/ly/music-functions-init.ly +++ b/ly/music-functions-init.ly @@ -253,7 +253,15 @@ bookOutputSuffix = breathe = #(define-music-function (parser location) () (_i "Insert a breath mark.") - (make-music 'BreathingEvent)) + (make-music 'BreathingEvent + 'midi-length + (lambda (len context) + ;;Shorten by half, or by up to a second, but always by a power of 2 + (let* ((desired (min (ly:moment-main (seconds->moment 1 context)) + (* (ly:moment-main len) 1/2))) + (scale (inexact->exact (ceiling (/ (log desired) (log 1/2))))) + (breath (ly:make-moment (expt 1/2 scale)))) + (ly:moment-sub len breath))))) clef = #(define-music-function (parser location type) (string?) diff --git a/ly/script-init.ly b/ly/script-init.ly index 89c1f5802b..3b3e39ed31 100644 --- a/ly/script-init.ly +++ b/ly/script-init.ly @@ -4,7 +4,8 @@ harmonic = #(make-music 'HarmonicEvent) -accent = #(make-articulation "accent") +accent = #(make-articulation "accent" + 'midi-extra-velocity 20) coda = #(make-articulation "coda") downbow = #(make-articulation "downbow") downmordent = #(make-articulation "downmordent") @@ -17,10 +18,15 @@ lheel = #(make-articulation "lheel") lineprall = #(make-articulation "lineprall") longfermata = #(make-articulation "longfermata") ltoe = #(make-articulation "ltoe") -marcato = #(make-articulation "marcato") +marcato = #(make-articulation "marcato" + 'midi-extra-velocity 40) mordent = #(make-articulation "mordent") open = #(make-articulation "open") -portato = #(make-articulation "portato") + +portato = #(make-articulation "portato" + 'midi-length + (lambda (len context) + (ly:moment-mul len (ly:make-moment 3/4)))) prall = #(make-articulation "prall") pralldown = #(make-articulation "pralldown") prallmordent = #(make-articulation "prallmordent") @@ -33,8 +39,17 @@ segno = #(make-articulation "segno") shortfermata = #(make-articulation "shortfermata") signumcongruentiae = #(make-articulation "signumcongruentiae") snappizzicato = #(make-articulation "snappizzicato") -staccatissimo = #(make-articulation "staccatissimo") -staccato = #(make-articulation "staccato") +staccatissimo = #(make-articulation "staccatissimo" + 'midi-length + (lambda (len context) + (seconds->moment 1/8 context)) + 'midi-extra-velocity 6) +staccato = #(make-articulation "staccato" + 'midi-length + (lambda (len context) + (moment-min (ly:moment-mul len (ly:make-moment 1/2)) + (seconds->moment 1/2 context))) + 'midi-extra-velocity 4) stopped = #(make-articulation "stopped") tenuto = #(make-articulation "tenuto") thumb = \finger \markup \scale #(cons (magstep 5) (magstep 5)) diff --git a/scm/define-music-properties.scm b/scm/define-music-properties.scm index 5c9c12538e..4469f88ff0 100644 --- a/scm/define-music-properties.scm +++ b/scm/define-music-properties.scm @@ -119,6 +119,12 @@ This property can only be defined as initializer in whether to allow, forbid or force a line break.") (metronome-count ,number-or-pair? "How many beats in a minute?") + (midi-extra-velocity ,integer? "How much louder or softer should +this note be in MIDI output? The default is 0.") + (midi-length ,procedure? "Function to determine how long to play +a note in MIDI. It should take a moment (the written length of the +note) and a context, and return a moment (the length to play the +note).") (moment ,ly:moment? "The moment at which an event happens.") (music-cause ,ly:music? "The music object that is the cause of an event.") diff --git a/scm/lily-library.scm b/scm/lily-library.scm index 43b05e57c2..79134ca7d4 100644 --- a/scm/lily-library.scm +++ b/scm/lily-library.scm @@ -80,6 +80,11 @@ (cons (ly:moment-main-numerator moment) (ly:moment-main-denominator moment))) +(define-public (seconds->moment s context) + "Return a moment equivalent to s seconds at the current tempo." + (ly:moment-mul (ly:context-property context 'tempoWholesPerMinute) + (ly:make-moment (/ s 60)))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; durations -- 2.39.5