diff options
author | Dan Eble <nine.fierce.ballads@gmail.com> | 2016-06-15 18:40:01 -0400 |
---|---|---|
committer | Dan Eble <nine.fierce.ballads@gmail.com> | 2016-07-19 07:50:08 -0400 |
commit | 14f464d957b6a68710f0364fc7507b63eb0fcee6 (patch) | |
tree | 38892a876676d77382c9afa92f16a022e7338c8b | |
parent | 3c35dd1538ca2b3a3a446e60ae9dc6b06234d78b (diff) |
Issue 4048 (2/5) Dynamic_performer: represent dynamics as a piecewise
linear function rather than a collection of discrete points
-rw-r--r-- | lily/audio-item.cc | 95 | ||||
-rw-r--r-- | lily/dynamic-performer.cc | 181 | ||||
-rw-r--r-- | lily/include/audio-item.hh | 36 | ||||
-rw-r--r-- | lily/include/midi-item.hh | 11 | ||||
-rw-r--r-- | lily/midi-item.cc | 41 | ||||
-rw-r--r-- | lily/staff-performer.cc | 78 |
6 files changed, 202 insertions, 240 deletions
diff --git a/lily/audio-item.cc b/lily/audio-item.cc index 97a15d552a..06603584b9 100644 --- a/lily/audio-item.cc +++ b/lily/audio-item.cc @@ -21,6 +21,7 @@ #include "midi-item.hh" #include "audio-column.hh" +#include "international.hh" Audio_instrument::Audio_instrument (string instrument_string) { @@ -101,24 +102,15 @@ Audio_key::Audio_key (int acc, bool major) major_ = major; } -Audio_dynamic::Audio_dynamic () - : volume_ (-1), - silent_ (false) -{ -} - -Audio_span_dynamic::Audio_span_dynamic (Real min_volume, Real max_volume) -{ - grow_dir_ = CENTER; - min_volume_ = min_volume; - max_volume_ = max_volume; -} +const Real Audio_span_dynamic::MINIMUM_VOLUME; +const Real Audio_span_dynamic::MAXIMUM_VOLUME; +const Real Audio_span_dynamic::DEFAULT_VOLUME; -void -Audio_span_dynamic::add_absolute (Audio_dynamic *d) +Audio_span_dynamic::Audio_span_dynamic (Moment mom, Real volume) + : start_moment_ (mom), + duration_ (0) { - assert (d); - dynamics_.push_back (d); + set_volume (volume, volume); } Moment @@ -140,53 +132,60 @@ moment_to_ticks (Moment m) return int (moment_to_real (m) * 384 * 4); } -void -Audio_span_dynamic::render () +void Audio_span_dynamic::set_end_moment(Moment mom) { - if (dynamics_.size () <= 1) - return; - - assert (dynamics_[0]->volume_ >= 0); - - while (dynamics_.back ()->volume_ > 0 - && dynamics_.size () > 1 - && sign (dynamics_.back ()->volume_ - dynamics_[0]->volume_) != grow_dir_) + if (mom < start_moment_) { - dynamics_.erase (dynamics_.end () - 1); + programming_error (_f ("end moment (%s) < start moment (%s)", + mom.to_string ().c_str (), + start_moment_.to_string ().c_str ())); + mom = start_moment_; } - if (dynamics_.size () <= 1) + duration_ = moment_to_real (mom - start_moment_); +} + +void +Audio_span_dynamic::set_volume (Real start, Real target) +{ + if (!(start >= 0)) { - programming_error ("Impossible or ambiguous (de)crescendo in MIDI."); - return; + programming_error (_f ("invalid start volume: %f", start)); + start = DEFAULT_VOLUME; } - Real start_v = dynamics_[0]->volume_; - if (dynamics_.back ()->volume_ < 0) + if (!(target >= 0)) { - // The dynamic spanner does not end with an explicit dynamic script - // event. Adjust the end volume by at most 1/4 of the available - // volume range in this case. - dynamics_.back ()->volume_ = max (min (start_v + grow_dir_ * (max_volume_ - min_volume_) * 0.25, max_volume_), min_volume_); + programming_error (_f ("invalid target volume: %f", target)); + target = start; } - Real delta_v = dynamics_.back ()->volume_ - dynamics_[0]->volume_; - - Moment start = dynamics_[0]->get_column ()->when (); + start_volume_ = start; + gain_ = target - start; +} - Real total_t = moment_to_real (dynamics_.back ()->get_column ()->when () - start); +Real Audio_span_dynamic::get_volume (Moment mom) const +{ + const Real when = moment_to_real (mom - start_moment_); - for (vsize i = 1; i < dynamics_.size (); i++) + if (when <= 0) { - Moment dt_moment = dynamics_[i]->get_column ()->when () - - start; - - Real dt = moment_to_real (dt_moment); - - Real v = start_v + delta_v * (dt / total_t); + if (when < 0) + programming_error (_f ("asked to compute volume at %f for dynamic span of duration %f starting at %s", + when, duration_, + start_moment_.to_string ().c_str ())); + return start_volume_; + } - dynamics_[i]->volume_ = v; + if (when >= duration_) + { + programming_error (_f ("asked to compute volume at +%f for dynamic span of duration %f starting at %s", + when, duration_, + start_moment_.to_string ().c_str ())); + return start_volume_ + gain_; } + + return start_volume_ + gain_ * (when / duration_); } Audio_tempo::Audio_tempo (int per_minute_4) diff --git a/lily/dynamic-performer.cc b/lily/dynamic-performer.cc index 5ce67f0463..ab0005b8f4 100644 --- a/lily/dynamic-performer.cc +++ b/lily/dynamic-performer.cc @@ -19,6 +19,7 @@ #include "performer.hh" #include "audio-item.hh" +#include "std-vector.hh" #include "stream-event.hh" #include "international.hh" @@ -29,6 +30,7 @@ class Dynamic_performer : public Performer public: TRANSLATOR_DECLARATIONS (Dynamic_performer); protected: + virtual void finalize (); void stop_translation_timestep (); void process_music (); Real equalize_volume (Real); @@ -36,25 +38,67 @@ protected: void listen_decrescendo (Stream_event *); void listen_crescendo (Stream_event *); void listen_absolute_dynamic (Stream_event *); + +private: + // next_vol < 0 means select a target dynamic based on growth direction. + // return actual next volume (computed if not provided) + Real end_span (Real next_vol = -1.0); + private: Stream_event *script_event_; Drul_array<Stream_event *> span_events_; - Drul_array<Direction> grow_dir_; - Real last_volume_; - Audio_dynamic *absolute_; + Direction next_grow_dir_; Audio_span_dynamic *span_dynamic_; - Audio_span_dynamic *finished_span_dynamic_; + Direction grow_dir_; // of span_dynamic_ }; Dynamic_performer::Dynamic_performer () { - last_volume_ = -1; script_event_ = 0; - absolute_ = 0; span_events_[LEFT] = span_events_[RIGHT] = 0; + next_grow_dir_ = CENTER; span_dynamic_ = 0; - finished_span_dynamic_ = 0; + grow_dir_ = CENTER; +} + +Real Dynamic_performer::end_span (Real next_vol) +{ + if (!span_dynamic_) + { + programming_error("no dynamic span to end"); + return next_vol; + } + + Real start_vol = span_dynamic_->get_start_volume (); + Real target_vol = start_vol; + + if (grow_dir_ != CENTER) { + // If the target dynamic is not specified, grow to a reasonable target + // in the desired direction. Do the same for cases like mf < p. + // + // TODO To improve on this, keep a queue of Audio_span_dynamics and compute + // multiple intermediate targets based on the next explicit dynamic. + // Consider cases like mf < ... < ff with only mf and ff specified. + // Consider growing in proportion to the duration of each (de)crescendo in + // the sequence, which may be separated by spans with no change in volume. + if ((next_vol < 0) || (sign(next_vol - start_vol) != grow_dir_)) + { + Real min_vol = equalize_volume (0.1); + Real max_vol = equalize_volume (Audio_span_dynamic::MAXIMUM_VOLUME); + target_vol = max (min (start_vol + grow_dir_ * (max_vol - min_vol) * 0.25, max_vol), min_vol); + } + else + { + target_vol = next_vol; + } + } + + span_dynamic_->set_end_moment (now_mom ()); + span_dynamic_->set_volume (start_vol, target_vol); + span_dynamic_ = 0; + + return (next_vol >= 0) ? next_vol : target_vol; } Real @@ -67,7 +111,8 @@ Dynamic_performer::equalize_volume (Real volume) SCM max = get_property ("midiMaximumVolume"); if (scm_is_number (min) || scm_is_number (max)) { - Interval iv (0, 1); + Interval iv (Audio_span_dynamic::MINIMUM_VOLUME, + Audio_span_dynamic::MAXIMUM_VOLUME); if (scm_is_number (min)) iv[MIN] = scm_to_double (min); if (scm_is_number (max)) @@ -97,125 +142,93 @@ Dynamic_performer::equalize_volume (Real volume) volume = iv[MIN] + iv.length () * volume; } } - return volume; + return std::max (std::min (volume, Audio_span_dynamic::MAXIMUM_VOLUME), + Audio_span_dynamic::MINIMUM_VOLUME); } void -Dynamic_performer::process_music () +Dynamic_performer::finalize () { - if (span_events_[START] || span_events_[STOP] || script_event_) + if (span_dynamic_) { - // End the previous spanner when a new one begins or at an explicit stop - // or absolute dynamic. - finished_span_dynamic_ = span_dynamic_; - span_dynamic_ = 0; + end_span (); } +} - if (span_events_[START]) - { - // Start of a dynamic spanner. Create a new Audio_span_dynamic for - // collecting changes in dynamics within this spanner. - span_dynamic_ = new Audio_span_dynamic (equalize_volume (0.1), equalize_volume (1.0)); - announce_element (Audio_element_info (span_dynamic_, span_events_[START])); - - span_dynamic_->grow_dir_ = grow_dir_[START]; - } +void +Dynamic_performer::process_music () +{ + Real volume = -1; - if (script_event_ - || span_dynamic_ - || finished_span_dynamic_) + if (script_event_) { - // New change in dynamics. - absolute_ = new Audio_dynamic (); + // Explicit dynamic script event: determine the volume. + SCM proc = get_property ("dynamicAbsoluteVolumeFunction"); - if (script_event_) + SCM svolume = SCM_EOL; + if (ly_is_procedure (proc)) { - // Explicit dynamic script event: determine the volume. - SCM proc = get_property ("dynamicAbsoluteVolumeFunction"); - - SCM svolume = SCM_EOL; - if (ly_is_procedure (proc)) - { - // urg - svolume = scm_call_1 (proc, script_event_->get_property ("text")); - } - - Real volume = robust_scm2double (svolume, 0.5); - - last_volume_ - = absolute_->volume_ = equalize_volume (volume); + // urg + svolume = scm_call_1 (proc, script_event_->get_property ("text")); } - Audio_element_info info (absolute_, script_event_); - announce_element (info); + volume = equalize_volume (robust_scm2double (svolume, Audio_span_dynamic::DEFAULT_VOLUME)); } - - if (last_volume_ < 0) + else if (!span_dynamic_) // first time through { - absolute_ = new Audio_dynamic (); - - last_volume_ - = absolute_->volume_ = equalize_volume (0.71); // Backward compatible - - Audio_element_info info (absolute_, script_event_); - announce_element (info); + volume = equalize_volume (Audio_span_dynamic::DEFAULT_VOLUME); } - if (span_dynamic_) - span_dynamic_->add_absolute (absolute_); - - if (finished_span_dynamic_) - finished_span_dynamic_->add_absolute (absolute_); -} - -void -Dynamic_performer::stop_translation_timestep () -{ - if (finished_span_dynamic_) + // end the current span at relevant points + if (span_dynamic_ + && (span_events_[START] || span_events_[STOP] || script_event_)) { - finished_span_dynamic_->render (); - finished_span_dynamic_ = 0; + volume = end_span (volume); } - if (absolute_) + // start a new span so that some dynamic is always in effect + if (!span_dynamic_) { - if (absolute_->volume_ < 0) - { - absolute_->volume_ = last_volume_; - } - else - { - last_volume_ = absolute_->volume_; - } + Stream_event *cause = + span_events_[START] ? span_events_[START] : + script_event_ ? script_event_ : + span_events_[STOP]; + + span_dynamic_ = new Audio_span_dynamic (now_mom (), volume); + grow_dir_ = next_grow_dir_; + announce_element (Audio_element_info (span_dynamic_, cause)); } +} - absolute_ = 0; +void +Dynamic_performer::stop_translation_timestep () +{ script_event_ = 0; span_events_[LEFT] = span_events_[RIGHT] = 0; + next_grow_dir_ = CENTER; } void Dynamic_performer::listen_decrescendo (Stream_event *r) { Direction d = to_dir (r->get_property ("span-direction")); - span_events_[d] = r; - grow_dir_[d] = SMALLER; + if (ASSIGN_EVENT_ONCE (span_events_[d], r) && (d == START)) + next_grow_dir_ = SMALLER; } void Dynamic_performer::listen_crescendo (Stream_event *r) { Direction d = to_dir (r->get_property ("span-direction")); - span_events_[d] = r; - grow_dir_[d] = BIGGER; + if (ASSIGN_EVENT_ONCE (span_events_[d], r) && (d == START)) + next_grow_dir_ = BIGGER; } void Dynamic_performer::listen_absolute_dynamic (Stream_event *r) { - if (!script_event_) - script_event_ = r; + ASSIGN_EVENT_ONCE (script_event_, r); } void diff --git a/lily/include/audio-item.hh b/lily/include/audio-item.hh index 8c41d18a52..ae5c96ae95 100644 --- a/lily/include/audio-item.hh +++ b/lily/include/audio-item.hh @@ -40,26 +40,30 @@ private: Audio_item &operator = (Audio_item const &); }; -class Audio_dynamic : public Audio_item +// Audio_span_dynamic is open at the end of the interval, so the volume +// grows/diminshes toward a target, but whether it reaches it depends on the +// next Audio_span_dynamic in the performance. For example, a crescendo +// notated as mf < p is represented as [mf < x) [p ...) i.e. growth to some +// volume louder than mf followed by an abrupt change to p. +class Audio_span_dynamic : public Audio_element { public: - Audio_dynamic (); + static const Real MINIMUM_VOLUME = 0.0; + static const Real MAXIMUM_VOLUME = 1.0; + static const Real DEFAULT_VOLUME = 90.0 / 127.0; - Real volume_; - bool silent_; -}; +private: + Moment start_moment_; + Real start_volume_; + Real duration_; // = target moment - start moment + Real gain_; // = target volume - start volume -class Audio_span_dynamic : public Audio_element -{ public: - Direction grow_dir_; - vector<Audio_dynamic *> dynamics_; - Real min_volume_; - Real max_volume_; - - virtual void render (); - void add_absolute (Audio_dynamic *); - Audio_span_dynamic (Real min_volume, Real max_volume); + Real get_start_volume () const { return start_volume_; } + void set_end_moment (Moment); + void set_volume (Real start, Real target); + Real get_volume (Moment) const; + Audio_span_dynamic (Moment mom, Real volume); }; class Audio_key : public Audio_item @@ -92,7 +96,7 @@ public: Pitch pitch_; Moment length_mom_; Pitch transposing_; - Audio_dynamic *dynamic_; + Audio_span_dynamic *dynamic_; int extra_velocity_; Audio_note *tied_; diff --git a/lily/include/midi-item.hh b/lily/include/midi-item.hh index b593ce1527..dff3f4df9b 100644 --- a/lily/include/midi-item.hh +++ b/lily/include/midi-item.hh @@ -157,17 +157,6 @@ public: Audio_text *audio_; }; -class Midi_dynamic : public Midi_channel_item -{ -public: - Midi_dynamic (Audio_dynamic *); - DECLARE_CLASSNAME (Midi_dynamic); - - virtual string to_string () const; - - Audio_dynamic *audio_; -}; - class Midi_piano_pedal : public Midi_channel_item { public: diff --git a/lily/midi-item.cc b/lily/midi-item.cc index 33dd9f11bd..7f064e24a8 100644 --- a/lily/midi-item.cc +++ b/lily/midi-item.cc @@ -19,6 +19,7 @@ #include "midi-item.hh" +#include "audio-column.hh" #include "duration.hh" #include "international.hh" #include "libc-extension.hh" @@ -42,8 +43,6 @@ Midi_item::get_midi (Audio_item *a) return i->str_.length () ? new Midi_instrument (i) : 0; else if (Audio_note *i = dynamic_cast<Audio_note *> (a)) return new Midi_note (i); - else if (Audio_dynamic *i = dynamic_cast<Audio_dynamic *> (a)) - return new Midi_dynamic (i); else if (Audio_piano_pedal *i = dynamic_cast<Audio_piano_pedal *> (a)) return new Midi_piano_pedal (i); else if (Audio_tempo *i = dynamic_cast<Audio_tempo *> (a)) @@ -193,8 +192,8 @@ Midi_time_signature::to_string () const Midi_note::Midi_note (Audio_note *a) : Midi_channel_item (a), audio_ (a), - dynamic_byte_ (min (max (Byte ((a->dynamic_ && a->dynamic_->volume_ >= 0 - ? a->dynamic_->volume_ * 0x7f : 0x5a) + dynamic_byte_ (min (max (Byte ((a->dynamic_ + ? a->dynamic_->get_volume (a->audio_column_->when ()) * 0x7f : 0x5a) + a->extra_velocity_), Byte (0)), Byte (0x7f))) { @@ -275,40 +274,6 @@ Midi_note_off::to_string () const return str; } -Midi_dynamic::Midi_dynamic (Audio_dynamic *a) - : Midi_channel_item (a), - audio_ (a) -{ -} - -string -Midi_dynamic::to_string () const -{ - Byte status_byte = (char) (0xB0 + channel_); - string str = ::to_string ((char)status_byte); - - /* - Main volume controller (per channel): - 07 MSB - 27 LSB - */ - static Real const full_scale = 127; - - int volume = (int) (audio_->volume_ * full_scale); - if (volume <= 0) - volume = 1; - if (volume > full_scale) - volume = (int)full_scale; - - int const volume_default = 100; - if (audio_->volume_ < 0 || audio_->silent_) - volume = volume_default; - - str += ::to_string ((char)0x07); - str += ::to_string ((char)volume); - return str; -} - Midi_piano_pedal::Midi_piano_pedal (Audio_piano_pedal *a) : Midi_channel_item (a), audio_ (a) diff --git a/lily/staff-performer.cc b/lily/staff-performer.cc index 3a05cbd0e2..505da3adba 100644 --- a/lily/staff-performer.cc +++ b/lily/staff-performer.cc @@ -54,7 +54,7 @@ private: int get_channel (const string &instrument); Audio_staff *get_audio_staff (const string &voice); Audio_staff *new_audio_staff (const string &voice); - Audio_dynamic *get_dynamic (const string &voice); + Audio_span_dynamic *get_dynamic (const string &voice); string instrument_string_; int channel_; @@ -65,7 +65,7 @@ private: map<string, deque<Audio_note *> > note_map_; map<string, Audio_staff *> staff_map_; map<string, int> channel_map_; - map<string, Audio_dynamic *> dynamic_map_; + map<string, Audio_span_dynamic *> dynamic_map_; // Would prefer to have the following two items be // members of the containing class Performance, // so they can be reset for each new midi file output. @@ -184,10 +184,10 @@ Staff_performer::get_audio_staff (const string &voice) return new_audio_staff (voice); } -Audio_dynamic * +Audio_span_dynamic * Staff_performer::get_dynamic (const string &voice) { - map<string, Audio_dynamic *>::const_iterator i = dynamic_map_.find (voice); + map<string, Audio_span_dynamic *>::const_iterator i = dynamic_map_.find (voice); if (i != dynamic_map_.end ()) return i->second; return 0; @@ -227,13 +227,12 @@ Staff_performer::stop_translation_timestep () instrument_name_ = 0; instrument_ = 0; // For each voice with a note played in the current translation time step, - // check if the voice has an Audio_dynamic registered: if yes, apply this - // dynamic to every note played in the voice in the current translation time - // step. + // check if the voice has a dynamic registered: if yes, apply the dynamic + // to every note played in the voice in the current translation time step. for (map<string, deque<Audio_note *> >::iterator vi = note_map_.begin (); vi != note_map_.end (); ++vi) { - Audio_dynamic *d = get_dynamic (vi->first); + Audio_span_dynamic *d = get_dynamic (vi->first); if (d) { for (deque<Audio_note *>::iterator ni = vi->second.begin (); @@ -316,47 +315,40 @@ Staff_performer::get_channel (const string &instrument) void Staff_performer::acknowledge_audio_element (Audio_element_info inf) { - if (Audio_item *ai = dynamic_cast<Audio_item *> (inf.elem_)) + /* map each context (voice) to its own track */ + Context *c = inf.origin_contexts (this)[0]; + string voice; + if (c->is_alias (ly_symbol2scm ("Voice"))) + voice = c->id_string (); + SCM channel_mapping = get_property ("midiChannelMapping"); + string str = new_instrument_string (); + if (!scm_is_eq (channel_mapping, ly_symbol2scm ("instrument"))) + channel_ = get_channel (voice); + else if (channel_ < 0 && str.empty ()) + channel_ = get_channel (str); + if (str.length ()) { - /* map each context (voice) to its own track */ - Context *c = inf.origin_contexts (this)[0]; - string voice; - if (c->is_alias (ly_symbol2scm ("Voice"))) - voice = c->id_string (); - SCM channel_mapping = get_property ("midiChannelMapping"); - string str = new_instrument_string (); - if (!scm_is_eq (channel_mapping, ly_symbol2scm ("instrument"))) - channel_ = get_channel (voice); - else if (channel_ < 0 && str.empty ()) + if (!scm_is_eq (channel_mapping, ly_symbol2scm ("voice"))) channel_ = get_channel (str); - if (str.length ()) - { - if (!scm_is_eq (channel_mapping, ly_symbol2scm ("voice"))) - channel_ = get_channel (str); - set_instrument (channel_, voice); - set_instrument_name (voice); - } + set_instrument (channel_, voice); + set_instrument_name (voice); + } + Audio_staff *audio_staff = get_audio_staff (voice); + if (Audio_item *ai = dynamic_cast<Audio_item *> (inf.elem_)) + { ai->channel_ = channel_; - Audio_staff *audio_staff = get_audio_staff (voice); - bool encode_dynamics_as_velocity_ = true; - if (encode_dynamics_as_velocity_) + if (Audio_note *n = dynamic_cast<Audio_note *> (inf.elem_)) { - if (Audio_note *n = dynamic_cast<Audio_note *> (inf.elem_)) - { - // Keep track of the notes played in the current voice in this - // translation time step (for adjusting their dynamics later in - // stop_translation_timestep). - note_map_[voice].push_back (n); - } - else if (Audio_dynamic *d = dynamic_cast<Audio_dynamic *> (inf.elem_)) - { - dynamic_map_[voice] = d; - // Output volume as velocity: skip Midi_dynamic output for the - // current element. - return; - } + // Keep track of the notes played in the current voice in this + // translation time step (for adjusting their dynamics later in + // stop_translation_timestep). + note_map_[voice].push_back (n); } audio_staff->add_audio_item (ai); } + else if (Audio_span_dynamic *d = dynamic_cast<Audio_span_dynamic *> (inf.elem_)) + { + dynamic_map_[voice] = d; + } } |