]> git.donarmstrong.com Git - lilypond.git/commitdiff
Issue 4048 (2/5) Dynamic_performer: represent dynamics as a piecewise
authorDan Eble <nine.fierce.ballads@gmail.com>
Wed, 15 Jun 2016 22:40:01 +0000 (18:40 -0400)
committerDan Eble <nine.fierce.ballads@gmail.com>
Tue, 19 Jul 2016 11:50:08 +0000 (07:50 -0400)
linear function rather than a collection of discrete points

lily/audio-item.cc
lily/dynamic-performer.cc
lily/include/audio-item.hh
lily/include/midi-item.hh
lily/midi-item.cc
lily/staff-performer.cc

index 97a15d552aafaedc86053945c291d1d796d2eb7d..06603584b97663a30d2803363afcfabef69e0bf0 100644 (file)
@@ -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)
index 5ce67f0463d67176de80df71d0016d997ea704cb..ab0005b8f4d40f050981ca608517d3f6a0db594a 100644 (file)
@@ -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
index 8c41d18a526c19198687a73d6b63f8374615b1e1..ae5c96ae95e549264a36f8808cedab6e06eb9aaa 100644 (file)
@@ -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_;
index b593ce152739e7bf82eb904818213905cc8b7a67..dff3f4df9b1c8738a17641b3c0f94d9157390e67 100644 (file)
@@ -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:
index 33dd9f11bde71fedb14e67b0174fc4b0e277fc1a..7f064e24a85608b4960edce548e96b0733064944 100644 (file)
@@ -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)
index 3a05cbd0e25213673aa5577c55c7eb7d3fe5feaf..505da3adbac4270207edaf15a4cfeb6dfecdff28 100644 (file)
@@ -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;
+    }
 }