]> git.donarmstrong.com Git - lilypond.git/commitdiff
Issue 4938 (2/3) Refactor handling of MIDI control changes
authorHeikki Tauriainen <g034737@welho.com>
Thu, 7 Jan 2016 20:34:56 +0000 (22:34 +0200)
committerJames Lowe <pkx166h@gmail.com>
Wed, 27 Jul 2016 21:54:15 +0000 (22:54 +0100)
Handle the MIDI control value initialization from context properties
(Staff_performer::new_audio_staff), control value changes
(Midi_control_function_performer::announce_function_value_change), and
value conversion for output
(Midi_control_function_value_change::to_string) in the new
Midi_control_change_announcer class.

All MIDI control changes are now encoded using
{Audio,Midi}_control_change items.  This change makes the old
{Audio,Midi}_control_function_value_change classes obsolete.

lily/audio-item.cc
lily/include/audio-item.hh
lily/include/lily-proto.hh
lily/include/midi-cc-announcer.hh [new file with mode: 0644]
lily/include/midi-item.hh
lily/midi-cc-announcer.cc [new file with mode: 0644]
lily/midi-control-function-performer.cc
lily/midi-item.cc
lily/staff-performer.cc

index 5c713c43ed5cdf18be6d066de203a89ea1cdb8a6..a8a78199c73096c15375110f5c50020b28912d13 100644 (file)
@@ -205,24 +205,6 @@ Audio_text::Audio_text (Audio_text::Type type, const string &text_string)
   type_ = type;
 }
 
-Audio_control_function_value_change
-::Audio_control_function_value_change (Control control, Real value)
-  : control_ (control), value_ (value)
-{
-}
-
-const Audio_control_function_value_change::Context_property
-Audio_control_function_value_change::context_properties_[] = {
-  // property name, enum constant, lower bound for range, upper bound for range
-  { "midiBalance",     BALANCE,      -1.0, 1.0 },
-  { "midiPanPosition", PAN_POSITION, -1.0, 1.0 },
-  { "midiExpression",  EXPRESSION,    0.0, 1.0 },
-  { "midiReverbLevel", REVERB_LEVEL,  0.0, 1.0 },
-  { "midiChorusLevel", CHORUS_LEVEL,  0.0, 1.0 },
-  // extra element to signify the end of the mapping, must be kept last
-  { 0,                 NUM_CONTROLS,  0.0, 0.0 }
-};
-
 Audio_control_change::Audio_control_change (int control, int value)
   : control_ (control),
     value_ (value)
index 551d43f1c58715941e7c4f20a7d1eb2f07aa0dd4..f3a97a2949d9805f1edc480474f78262fe9c5f3e 100644 (file)
@@ -144,38 +144,6 @@ public:
   int one_beat_;
 };
 
-class Audio_control_function_value_change : public Audio_item
-{
-public:
-  // Supported control functions.
-  enum Control
-  {
-    BALANCE = 0, PAN_POSITION, EXPRESSION, REVERB_LEVEL, CHORUS_LEVEL,
-    // pseudo value for representing the size of the enum; must be kept last
-    NUM_CONTROLS
-  };
-
-  Audio_control_function_value_change (Control control, Real value);
-
-  // Information about a context property corresponding to a control function
-  // (name, the corresponding enumeration value, and the allowed range for the
-  // value of the context property).
-  struct Context_property
-  {
-    const char *name_;
-    Control control_;
-    Real range_min_;
-    Real range_max_;
-  };
-
-  // Mapping from supported control functions to the corresponding context
-  // properties.
-  static const Context_property context_properties_[];
-
-  Control control_;
-  Real value_;
-};
-
 class Audio_control_change : public Audio_item
 {
 public:
index f41c8f5a3f192f469f718c5b9e684d6523f93c13..6de69e16b92157352f6bd7955196ece89cd2cc93 100644 (file)
@@ -25,7 +25,6 @@
 class All_font_metrics;
 class Audio_column;
 class Audio_control_change;
-class Audio_control_function_value_change;
 class Audio_dynamic;
 class Audio_element;
 class Audio_instrument;
@@ -92,7 +91,7 @@ class Lyric_performer;
 class Mensural_ligature_engraver;
 class Midi_chunk;
 class Midi_control_change;
-class Midi_control_function_value_change;
+class Midi_control_change_announcer;
 class Midi_duration;
 class Midi_dynamic;
 class Midi_event;
diff --git a/lily/include/midi-cc-announcer.hh b/lily/include/midi-cc-announcer.hh
new file mode 100644 (file)
index 0000000..38cb93f
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+  This file is part of LilyPond, the GNU music typesetter.
+
+  Copyright (C) 2016 by Heikki Tauriainen <g034737@welho.com>.
+
+  LilyPond 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 3 of the License, or
+  (at your option) any later version.
+
+  LilyPond 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 LilyPond.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MIDI_CC_ANNOUNCER_HH
+#define MIDI_CC_ANNOUNCER_HH
+
+#include "input.hh"
+#include "performer.hh"
+#include "audio-item.hh"
+
+/* Base class for announcing MIDI control changes. */
+class Midi_control_change_announcer
+{
+public:
+  /* Constructor.  The optional parameter can be used to specify an Input
+     to use for relativizing warning messages about out-of-range values. */
+  Midi_control_change_announcer (Input *origin = 0);
+  virtual ~Midi_control_change_announcer ();
+
+  void announce_from_context_properties ();
+
+  /* Announces MIDI CC changes by creating new Audio_control_change events
+     from them, and calling 'do_announce' on each event.  Control change
+     events will be created from every supported MIDI context property for
+     which the 'get_property_value' function returns a value that is
+     compatible with the expected type of the context property's value. */
+  void announce_control_changes ();
+
+private:
+  virtual SCM get_property_value (const char *property_name) = 0;
+  virtual void do_announce (Audio_control_change *item) = 0;
+  void warn (const string &message);
+
+  Input *origin_;
+
+  struct Control_spec
+  {
+    const char *const context_property_name_;
+    const Real range_min_;
+    const Real range_max_;
+    const int msb_control_number_;
+    const int lsb_control_number_;
+  };
+
+  static const Control_spec controls_[];
+};
+
+#endif // MIDI_CC_ANNOUNCER_HH
index b979bca4ba60ee2ab3f2e88f99315720b0cc9764..57bb2d4da22eb4d7840b0b85b0ac87d9a07a61d8 100644 (file)
@@ -52,20 +52,6 @@ public:
   Midi_channel_item (Audio_item *ai);
 };
 
-/**
-   Midi control function value changes.
-*/
-class Midi_control_function_value_change : public Midi_channel_item
-{
-public:
-  DECLARE_CLASSNAME (Midi_control_function_value_change);
-  Midi_control_function_value_change (Audio_control_function_value_change *ai);
-  virtual ~Midi_control_function_value_change ();
-  virtual string to_string () const;
-  Audio_control_function_value_change::Control control_;
-  Real value_;
-};
-
 class Midi_duration : public Midi_item
 {
 public:
diff --git a/lily/midi-cc-announcer.cc b/lily/midi-cc-announcer.cc
new file mode 100644 (file)
index 0000000..7fb4ed6
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+  This file is part of LilyPond, the GNU music typesetter.
+
+  Copyright (C) 2016 by Heikki Tauriainen <g034737@welho.com>.
+
+  LilyPond 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 3 of the License, or
+  (at your option) any later version.
+
+  LilyPond 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 LilyPond.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "audio-item.hh"
+#include "input.hh"
+#include "international.hh"
+#include "libc-extension.hh"
+#include "midi-cc-announcer.hh"
+
+/*
+  Context properties for setting MIDI controls.  Each MIDI control
+  specification has the following components:
+    1. The name of the LilyPond context property used to change the value of
+       the MIDI control.
+    2. The lower bound for the numeric range of the LilyPond context property.
+    3. The upper bound for the numeric range of the LilyPond context property.
+    4. The MIDI control number for setting the most significant 7 bits of the
+       control value.
+    5. The MIDI control number for setting the least significant 7 bits of the
+       control value, if the control supports 14-bit ("fine") resolution.  If
+       the control supports only 7-bit ("coarse") resolution, the LSB control
+       number should be negative.
+*/
+const Midi_control_change_announcer::Control_spec
+Midi_control_change_announcer::controls_[]
+=
+{
+  { "midiBalance", -1.0, 1.0, 8, 40 },
+  { "midiPanPosition", -1.0, 1.0, 10, 42 },
+  { "midiExpression", 0.0, 1.0, 11, 43 },
+  { "midiReverbLevel", 0.0, 1.0, 91, -1 },
+  { "midiChorusLevel", 0.0, 1.0, 93, -1 },
+  // This element should be kept last in the array.
+  { 0, 0.0, 0.0, 0, 0 }
+};
+
+Midi_control_change_announcer::Midi_control_change_announcer (Input *origin)
+  : origin_ (origin)
+{
+}
+
+Midi_control_change_announcer::~Midi_control_change_announcer ()
+{
+}
+
+void Midi_control_change_announcer::announce_control_changes ()
+{
+  for (const Control_spec *spec = controls_; spec->context_property_name_;
+       ++spec)
+    {
+      SCM value = get_property_value (spec->context_property_name_);
+      if (!scm_is_number (value))
+        continue;
+      Real val = scm_to_double (value);
+      if (val >= spec->range_min_ && val <= spec->range_max_)
+        {
+          // Normalize the value to the 0.0 to 1.0 range.
+          val = ((val - spec->range_min_)
+                 / (spec->range_max_ - spec->range_min_));
+          // Transform the normalized context property value into a 14-bit or
+          // a 7-bit (non-negative) integer depending on the MIDI control's
+          // resolution.  For directional value changes, #CENTER will
+          // correspond to 0.5 exactly, and my_round rounds upwards when in
+          // case of doubt.  That means that center position will round to
+          // 0x40 or 0x2000 by a hair's breadth.
+          const Real full_fine_scale = 0x3FFF;
+          const Real full_coarse_scale = 0x7F;
+          const bool fine_resolution = (spec->lsb_control_number_ >= 0);
+          const int v = (int) (my_round (val * (fine_resolution
+                                                ? full_fine_scale
+                                                : full_coarse_scale)));
+          // Announce a control change for the most significant 7 bits of the
+          // control value (and, if the control supports fine resolution, for
+          // the least significant 7 bits as well).
+          do_announce (new Audio_control_change (spec->msb_control_number_,
+                                                 fine_resolution
+                                                 ? (v >> 7) : v));
+          if (fine_resolution)
+            do_announce (new Audio_control_change (spec->lsb_control_number_,
+                                                   v & 0x7F));
+        }
+      else
+        warn (_f ("ignoring out-of-range value change for MIDI property `%s'",
+                  spec->context_property_name_));
+    }
+}
+
+void Midi_control_change_announcer::warn (const string &message)
+{
+  if (origin_)
+    origin_->warning (message);
+  else
+    warning (message);
+}
index a112e6bceffb7daaaf54d9340a265dab20dbbb87..48037e9f9720053a54cf0f59805ade24852d47c3 100644 (file)
@@ -1,7 +1,7 @@
 /*
   This file is part of LilyPond, the GNU music typesetter.
 
-  Copyright (C) 2013--2015 by Heikki Tauriainen <g034737@welho.com>.
+  Copyright (C) 2013--2016 by Heikki Tauriainen <g034737@welho.com>.
   Adapted from performer implementations
   Copyright (C) 1996--2015 Jan Nieuwenhuizen <janneke@gnu.org>,
   Han-Wen Nienhyus <hanwen@xs4all.nl> and others.
@@ -27,6 +27,7 @@
 #include "dispatcher.hh"
 #include "international.hh"
 #include "listener.hh"
+#include "midi-cc-announcer.hh"
 #include "stream-event.hh"
 
 #include "translator.icc"
@@ -44,6 +45,22 @@ public:
 
   void connect_to_context (Context *c);
   void disconnect_from_context (Context *c);
+
+private:
+  class Control_change_announcer : public Midi_control_change_announcer
+  {
+  public:
+    Control_change_announcer (Midi_control_function_performer *p,
+                              Stream_event *ev, const string &s);
+
+    SCM get_property_value (const char *property_name);
+    void do_announce (Audio_control_change *item);
+
+  private:
+    Midi_control_function_performer *performer_;
+    Stream_event *event_;
+    string symbol_;
+  };
 };
 
 Midi_control_function_performer::Midi_control_function_performer ()
@@ -58,16 +75,18 @@ void
 Midi_control_function_performer::connect_to_context (Context *c)
 {
   c->events_below ()->
-    add_listener (GET_LISTENER (Midi_control_function_performer, announce_function_value_change),
-                  ly_symbol2scm ("SetProperty"));
+  add_listener (GET_LISTENER (Midi_control_function_performer,
+                              announce_function_value_change),
+                ly_symbol2scm ("SetProperty"));
 }
 
 void
 Midi_control_function_performer::disconnect_from_context (Context *c)
 {
   c->events_below ()->
-    remove_listener (GET_LISTENER (Midi_control_function_performer, announce_function_value_change),
-                     ly_symbol2scm ("SetProperty"));
+  remove_listener (GET_LISTENER (Midi_control_function_performer,
+                                 announce_function_value_change),
+                   ly_symbol2scm ("SetProperty"));
 }
 
 void
@@ -78,39 +97,30 @@ Midi_control_function_performer::announce_function_value_change (SCM sev)
   if (!scm_is_symbol (sym))
     return;
 
-  // Search for a matching context property; if found, check that the value
-  // of the property is within the allowed range, and announce a possible
-  // change in the value of the corresponding control function.
-  string symbol = ly_symbol2string (sym);
-  for (const Audio_control_function_value_change::Context_property *p
-         = Audio_control_function_value_change::context_properties_;
-       p->name_; ++p)
-    {
-      if (symbol == p->name_)
-        {
-          SCM value = ev->get_property ("value");
-          if (scm_is_number (value))
-            {
-              Real val = scm_to_double (value);
-              if (val >= p->range_min_ && val <= p->range_max_)
-                {
-                  // Normalize the value to the 0.0 to 1.0 range.
-                  val = ((val - p->range_min_)
-                         / (p->range_max_ - p->range_min_));
-                  Audio_control_function_value_change *item
-                    = new Audio_control_function_value_change (p->control_,
-                                                               val);
-                  announce_element (Audio_element_info (item, 0));
-                }
-              else
-                ev->origin ()->
-                  warning (_f ("ignoring out-of-range value change for MIDI "
-                               "property `%s'",
-                               p->name_));
-            }
-          break;
-        }
-    }
+  Control_change_announcer a (this, ev, ly_symbol2string (sym));
+  a.announce_control_changes ();
+}
+
+Midi_control_function_performer::Control_change_announcer::Control_change_announcer
+(Midi_control_function_performer *p, Stream_event *ev, const string &s)
+  : Midi_control_change_announcer (ev->origin ()),
+    performer_ (p),
+    event_ (ev),
+    symbol_ (s)
+{
+}
+
+SCM
+Midi_control_function_performer::Control_change_announcer::get_property_value
+(const char *property_name)
+{
+  return symbol_ == property_name ? event_->get_property ("value") : SCM_EOL;
+}
+
+void Midi_control_function_performer::Control_change_announcer::do_announce
+(Audio_control_change *item)
+{
+  performer_->announce_element (Audio_element_info (item, 0));
 }
 
 void
@@ -121,7 +131,9 @@ Midi_control_function_performer::boot ()
 
 ADD_TRANSLATOR (Midi_control_function_performer,
                 /* doc */
-                "",
+                "This performer listens to SetProperty events on context "
+                "properties for generating MIDI control changes and "
+                "prepares them for MIDI output.",
 
                 /* create */
                 "",
index 47ccd787c3533d091fb839d2e4a5bcdf0891165e..ba29ba0ba59264a2d51682489a34f3e1300eb7db 100644 (file)
@@ -51,9 +51,6 @@ Midi_item::get_midi (Audio_item *a)
     return new Midi_time_signature (i);
   else if (Audio_text *i = dynamic_cast<Audio_text *> (a))
     return new Midi_text (i);
-  else if (Audio_control_function_value_change *i
-           = dynamic_cast<Audio_control_function_value_change *> (a))
-    return new Midi_control_function_value_change (i);
   else if (Audio_control_change *i = dynamic_cast<Audio_control_change *> (a))
     return new Midi_control_change (i);
   else
@@ -107,12 +104,6 @@ Midi_channel_item::Midi_channel_item (Audio_item *ai)
 {
 }
 
-Midi_control_function_value_change
-::Midi_control_function_value_change (Audio_control_function_value_change *ai)
-  : Midi_channel_item (ai), control_ (ai->control_), value_ (ai->value_)
-{
-}
-
 Midi_control_change::Midi_control_change (Audio_control_change *ai)
   : Midi_channel_item (ai),
     audio_ (ai)
@@ -127,10 +118,6 @@ Midi_channel_item::~Midi_channel_item ()
 {
 }
 
-Midi_control_function_value_change::~Midi_control_function_value_change ()
-{
-}
-
 Midi_control_change::~Midi_control_change ()
 {
 }
@@ -339,67 +326,6 @@ Midi_text::to_string () const
   return str;
 }
 
-string
-Midi_control_function_value_change::to_string () const
-{
-  // MIDI control function information.  A MIDI control function may have one
-  // or two assigned control numbers depending on whether it supports coarse
-  // (7-bit) or fine (14-bit) resolution.  If the control function supports
-  // fine resolution, the first (respectively, second) member of the structure
-  // represents the control number for setting the most (least) significant 7
-  // bits of the control function's value.
-  struct Control_function
-  {
-    int msb_control_number_;
-    int lsb_control_number_;
-  };
-
-  // Mapping from supported control functions (enumeration values defined in
-  // Audio_controller_value_change::Control) to the corresponding MIDI control
-  // numbers.
-  static const Control_function control_functions[] =
-    {
-      // When adding support for new control functions, please note the
-      // following:
-      // - The order of the control number definitions should be kept
-      //   consistent with the order of the enumeration values defined in
-      //   Audio_control_function_value_change::Control.
-      // - If the control function has only coarse resolution, the function's
-      //   control number should be stored in the MSB member of the array
-      //   element, and the LSB member should be set to a negative value.
-
-      {  8, 40 }, // balance
-      { 10, 42 }, // pan position
-      { 11, 43 }, // expression
-      { 91, -1 }, // reverb level (only coarse resolution available)
-      { 93, -1 }  // chorus level (only coarse resolution available)
-    };
-
-  string str;
-  const Control_function *control_function = &control_functions[control_];
-  static const Real full_fine_scale = 0x3FFF;
-  static const Real full_coarse_scale = 0x7F;
-  bool fine_resolution = (control_function->lsb_control_number_ >= 0);
-  // value_ is in range [0.0 .. 1.0].  For directional value ranges,
-  // #CENTER will correspond to 0.5 exactly, and my_round rounds
-  // upwards when in case of doubt.  That means that center position
-  // will round to 0x40 or 0x2000 by a hair's breadth.
-  int value = (int) my_round (value_ * (fine_resolution ?
-                                        full_fine_scale : full_coarse_scale));
-  Byte status_byte = (char) (0xB0 + channel_);
-  str += ::to_string ((char)status_byte);
-  str += ::to_string ((char)(control_function->msb_control_number_));
-  str += ::to_string ((char)(fine_resolution ? (value >> 7) : value));
-  if (fine_resolution)
-    {
-      str += ::to_string ((char)0x00);
-      str += ::to_string ((char)status_byte);
-      str += ::to_string ((char)(control_function->lsb_control_number_));
-      str += ::to_string ((char)(value & 0x7F));
-    }
-  return str;
-}
-
 string
 Midi_control_change::to_string () const
 {
index 505da3adbac4270207edaf15a4cfeb6dfecdff28..d413a46835b8694fc1c9af157de340fb35059a88 100644 (file)
@@ -25,6 +25,7 @@
 #include "audio-staff.hh"
 #include "context.hh"
 #include "international.hh"
+#include "midi-cc-announcer.hh"
 #include "performer-group.hh"
 #include "warn.hh"
 #include "lily-imports.hh"
@@ -56,6 +57,22 @@ private:
   Audio_staff *new_audio_staff (const string &voice);
   Audio_span_dynamic *get_dynamic (const string &voice);
 
+  class Midi_control_initializer : public Midi_control_change_announcer
+  {
+  public:
+    Midi_control_initializer (Staff_performer *performer,
+                              Audio_staff *audio_staff,
+                              int channel);
+
+    SCM get_property_value (const char *property_name);
+    void do_announce (Audio_control_change *item);
+
+  private:
+    Staff_performer *performer_;
+    Audio_staff *audio_staff_;
+    int channel_;
+  };
+
   string instrument_string_;
   int channel_;
   Audio_instrument *instrument_;
@@ -135,32 +152,9 @@ Staff_performer::new_audio_staff (const string &voice)
   staff_map_[voice] = audio_staff;
   if (!instrument_string_.empty ())
     set_instrument (channel_, voice);
-  // Set initial values (if any) for control functions.
-  for (const Audio_control_function_value_change::Context_property *p
-         = Audio_control_function_value_change::context_properties_;
-       p->name_; ++p)
-    {
-      SCM value = get_property (p->name_);
-      if (scm_is_number (value))
-        {
-          Real val = scm_to_double (value);
-          if (val >= p->range_min_ && val <= p->range_max_)
-            {
-              // Normalize the value to the 0.0 to 1.0 range.
-              val = ((val - p->range_min_)
-                     / (p->range_max_ - p->range_min_));
-              Audio_control_function_value_change *item
-                = new Audio_control_function_value_change (p->control_, val);
-              item->channel_ = channel_;
-              audio_staff->add_audio_item (item);
-              announce_element (Audio_element_info (item, 0));
-            }
-          else
-            warning (_f ("ignoring out-of-range value change for MIDI "
-                         "property `%s'",
-                         p->name_));
-        }
-    }
+  // Set initial values (if any) for MIDI controls.
+  Midi_control_initializer i (this, audio_staff, channel_);
+  i.announce_control_changes ();
   return audio_staff;
 }
 
@@ -352,3 +346,24 @@ Staff_performer::acknowledge_audio_element (Audio_element_info inf)
     }
 }
 
+Staff_performer::Midi_control_initializer::Midi_control_initializer
+(Staff_performer *performer, Audio_staff *audio_staff, int channel)
+  : performer_ (performer),
+    audio_staff_ (audio_staff),
+    channel_ (channel)
+{
+}
+
+SCM Staff_performer::Midi_control_initializer::get_property_value
+(const char *property_name)
+{
+  return performer_->get_property (property_name);
+}
+
+void Staff_performer::Midi_control_initializer::do_announce
+(Audio_control_change *item)
+{
+  item->channel_ = channel_;
+  audio_staff_->add_audio_item (item);
+  performer_->announce_element (Audio_element_info (item, 0));
+}