]> git.donarmstrong.com Git - lilypond.git/commitdiff
Set the sequence name in MIDI using title information from
authorHeikki Tauriainen <g034737@welho.com>
Sat, 15 Aug 2015 19:33:31 +0000 (21:33 +0200)
committerThomas Morley <thomasmorley65@gmail.com>
Sat, 15 Aug 2015 19:37:41 +0000 (21:37 +0200)
 \header block

issue 4539

This patch adds support for setting the MIDI sequence name for MIDI output files
using the value of the "midititle" field from a relevant \header block, and
falling back to using the "title" field if "midititle"
has not been defined.  (This should be analogous to how "pdftitle" and "title"
work for customizing PDF metadata.)

The purpose of the change is to improve the previous behavior where the name of
every MIDI sequence created by LilyPond used to be shown by MIDI synthesizers as
"control track" (previously, this string was hard-coded as the name of every
initial track of MIDI files created by LilyPond by Control_track_performer).

The patch
  * extends every Performance instance with a reference to a \header block
associated with the performance, adds Scheme library routines for getting and
setting the associated \header (modeled after corresponding routines available
for the Score class), and updates the Book::process_score function to initialize
the header information of Performance objects attached to a score;
  * adds a "name" parameter to the Performance output routines, used for
updating the track name in the performance's first Audio_staff (assumed to
represent the control track) before outputting MIDI; and
  * changes the write-performances-midis function (in scm/midi.scm) to query the
MIDI sequence name for a performance from the performance's \header block
(adapted from the handle-metadata function in scm/framework-ps.scm).
  * adds two regtests

Documentation/changes.tely
input/regression/midi/sequence-name-scoping.ly [new file with mode: 0644]
input/regression/midi/sequence-name.ly [new file with mode: 0644]
lily/book.cc
lily/control-track-performer.cc
lily/include/audio-staff.hh
lily/include/performance.hh
lily/performance-scheme.cc
lily/performance.cc
scm/midi.scm

index 6182a59323b5d69f0d0dfa5ed2d2b9ba283e1ab0..ca142c3286fc8bbe11b116ac05ddbdbd52e8da45 100644 (file)
@@ -61,6 +61,19 @@ which scares away people.
 
 @end ignore
 
+@item
+When outputting MIDI, LilyPond will now store the @code{title}
+defined in a score's @code{\header} block (or, if there is no
+such definition on the @code{\score} level, the first such
+definition found in a @code{\header} block of the score's
+enclosing @code{\bookpart}, @code{\book}, or top-level scope)
+as the name of the MIDI sequence in the MIDI file.  Optionally,
+the name of the MIDI sequence can be overridden using the new
+@code{midititle} @code{\header} field independently of
+@code{title} (for example, in case @code{title} contains markup
+code which does not render as plain text in a satisfactory way
+automatically).
+
 @item
 Music (and scheme and void) functions and markup commands that
 just supply the final parameters to a chain of overrides, music
diff --git a/input/regression/midi/sequence-name-scoping.ly b/input/regression/midi/sequence-name-scoping.ly
new file mode 100644 (file)
index 0000000..239d55c
--- /dev/null
@@ -0,0 +1,82 @@
+\version "2.19.25"
+
+\header {
+  texidoc="If a score has a @code{\header} block which defines a title,
+  this title should override any title defined in a @code{\header} block
+  of the score's enclosing @code{\bookpart} or @code{\book} (or a title
+  defined in a top-level @code{\header} block) when naming the MIDI
+  sequence generated from the score.  Otherwise, if the score has no title
+  defined, the MIDI sequence generated from the score should get named
+  using the title defined in the @code{\header} block of the nearest
+  enclosing @code{\bookpart}, @code{\book}, or top-level scope that
+  contains a title definition."
+  title = "Top-level title"
+}
+
+music = \new Staff { c1 }
+
+% Book with a title defined in a \header block, and book parts
+\book {
+  \header { title = "Book" }
+
+  % score without a \header block outside of any book part -- the MIDI
+  % sequence should get the title of the book as its name
+  \score {
+    \music
+    \layout { }
+    \midi { }
+  }
+
+  % score with a \header and a title outside of any book part -- the MIDI
+  % sequence should be named with the title from this \header block
+  \score {
+    \music
+    \header { title = "Score in a \book" }
+    \layout { }
+    \midi { }
+  }
+
+  % Book part with a \header block and a title
+  \bookpart {
+    \header { title = "Book part" }
+
+    % score without a \header block -- the MIDI sequence should get its name
+    % from the title of the enclosing book part
+    \score {
+      \music
+      \layout { }
+      \midi { }
+    }
+
+    % score with a \header (and a title) of its own -- the MIDI sequence
+    % should get its name from the title in this \header block
+    \score {
+      \music
+      \header { title = "Score in a book part (w/ a title) of a book" }
+      \layout { }
+      \midi { }
+    }
+  }
+
+  % Book part without a \header block
+  \bookpart {
+
+    % score without a \header block -- the MIDI sequence should be named
+    % using the title from the enclosing book
+    \score {
+      \music
+      \layout { }
+      \midi { }
+    }
+
+    % score with a \header block and title -- the MIDI sequence should get
+    % its name from the title in this \header block
+    \score {
+      \music
+      \header { title = "Score in a book part (w/o a title) of a book" }
+      \layout { }
+      \midi { }
+    }
+  }
+
+}
diff --git a/input/regression/midi/sequence-name.ly b/input/regression/midi/sequence-name.ly
new file mode 100644 (file)
index 0000000..f10590d
--- /dev/null
@@ -0,0 +1,63 @@
+\version "2.19.25"
+
+\header {
+  texidoc="The MIDI sequence generated from a score should get its name
+  from the title defined in the score's @code{\header} block (if any).
+  The title used for layout can be overridden for MIDI output by
+  specifying a separate @code{midititle} in the @code{\header} block.
+  If the score does not define a title of its own, and has no enclosing
+  @code{\bookpart}, @code{\book}, or top-level scope with a @code{\header}
+  block that defines a title, either, the MIDI sequence should get the
+  default name."
+}
+
+music = \new Staff { c1 }
+
+% Book without a \header block
+\book {
+
+  % score with a \header block including a title -- the MIDI sequence
+  % should get its name from this \header block
+  \score {
+    \music
+    \header { title = "Title shared between layout and MIDI" }
+    \layout { }
+    \midi { }
+  }
+
+  % score with no title, but a midititle defined in a \header block --
+  % the MIDI sequence should be named using the midititle in this \header
+  % block
+  \score {
+    \music
+    \header { midititle = "No title for layout, but a title for MIDI" }
+    \layout { }
+    \midi { }
+  }
+
+  % score with a title and a midititle defined in a \header block -- the
+  % MIDI sequence should get the midititle in this \header block as its
+  % name
+  \score {
+    \music
+    \header {
+      title = "Title for layout"
+      midititle = "Title for MIDI"
+    }
+    \layout { }
+    \midi { }
+  }
+
+  % Book part with no \header block
+  \bookpart {
+
+    % score without a \header -- the MIDI sequence should get the default
+    % name
+    \score {
+      \music
+      \layout { }
+      \midi { }
+    }
+  }
+
+}
index cb145f0436d12f36ab1617bde76d274ac524eade..c7f5dd02378372ba377320d9c1a94dec2899d17b 100644 (file)
@@ -229,7 +229,18 @@ Book::process_score (SCM s, Paper_book *output_paper_book, Output_def *layout)
           Music_output *output = unsmob<Music_output> (scm_car (outputs));
 
           if (Performance *perf = dynamic_cast<Performance *> (output))
-            output_paper_book->add_performance (perf->self_scm ());
+            {
+              output_paper_book->add_performance (perf->self_scm ());
+              // Associate the performance with a \header block (if there is
+              // one in effect in the scope of the current score), to make the
+              // header metadata accessible when outputting the performance.
+              if (ly_is_module (score->get_header ()))
+                perf->set_header (score->get_header ());
+              else if (ly_is_module (output_paper_book->header_))
+                perf->set_header (output_paper_book->header_);
+              else if (ly_is_module (output_paper_book->header_0_))
+                perf->set_header (output_paper_book->header_0_);
+            }
           else if (Paper_score *pscore = dynamic_cast<Paper_score *> (output))
             {
               if (ly_is_module (score->get_header ()))
index 9b0d676870d5d08d6074e2a3d2c6090657b5a7b0..82beebe122fb002066f0fede1a4303c67fcbadd7 100644 (file)
@@ -52,11 +52,15 @@ Control_track_performer::add_text (Audio_text::Type text_type, const string &str
 void
 Control_track_performer::initialize ()
 {
-  control_track_ = new Audio_staff;
+  control_track_ = new Audio_control_track_staff;
   announce_element (Audio_element_info (control_track_, 0));
 
   string id_string = String_convert::pad_to (gnu_lilypond_version_string (), 30);
 
+  // The first audio element in the control track is a placeholder for the
+  // name of the MIDI sequence.  The actual name is stored in the element
+  // later before outputting the track (in Performance::output, see
+  // performance.cc).
   add_text (Audio_text::TRACK_NAME, "control track");
   add_text (Audio_text::TEXT, "creator: ");
   add_text (Audio_text::TEXT, id_string);
index 13124cfc09162ce4e1216b1644b9c5feaf8bfd41..a4a9d8a0d1386a9fc87e2446ba107fecefb2f54a 100644 (file)
@@ -36,4 +36,10 @@ struct Audio_staff : public Audio_element
   vector<Audio_item *> audio_items_;
 };
 
+// Subtype to identify a staff that represents the "control track" of a MIDI
+// sequence (created by Control_track_performer).
+struct Audio_control_track_staff : public Audio_staff
+{
+};
+
 #endif // AUDIO_STAFF_HH
index 3f9c5d6f1ba044d2e00fb7a92f33dc61baa720cb..f9c53ff2e85e7baa70007deb34e59c18d5697d28 100644 (file)
@@ -31,19 +31,25 @@ public:
   ~Performance ();
   DECLARE_CLASSNAME (Performance);
 
+  SCM get_header () const;
+  void set_header (SCM header);
+
+  virtual void derived_mark () const;
+
   void add_element (Audio_element *p);
   virtual void process ();
   void remap_grace_durations ();
-  void output (Midi_stream &midi_stream) const;
+  void output (Midi_stream &midi_stream, const string &performance_name) const;
   void output_header_track (Midi_stream &midi_stream) const;
 
   void print () const;
-  void write_output (string filename) const;
+  void write_output (string filename, const string &performance_name) const;
 
   vector<Audio_staff *> audio_staffs_;
   vector<Audio_element *> audio_elements_;
   Output_def *midi_;
   bool ports_;
+  SCM header_;
 };
 
 #endif /* PERFORMANCE_HH */
index 9ab087a2c3bf22e14682c59d46481a6cf0e442b6..4d3c1e651256eafb38d911131f59b38a303f6349 100644 (file)
 
 #include "performance.hh"
 
+LY_DEFINE (ly_performance_header, "ly:performance-header",
+           1, 0, 0, (SCM performance),
+           "Return header of performance.")
+{
+  LY_ASSERT_SMOB (Performance, performance, 1);
+  Performance *p = unsmob<Performance> (performance);
+  return p->get_header ();
+}
+
+LY_DEFINE (ly_performance_set_header_x, "ly:performance-set-header!",
+           2, 0, 0, (SCM performance, SCM module),
+           "Set the performance header.")
+{
+  LY_ASSERT_SMOB (Performance, performance, 1);
+  SCM_ASSERT_TYPE (ly_is_module (module), module, SCM_ARG2, __FUNCTION__,
+                   "module");
+
+  Performance *p = unsmob<Performance> (performance);
+  p->set_header (module);
+  return SCM_UNSPECIFIED;
+}
+
 LY_DEFINE (ly_performance_write, "ly:performance-write",
-           2, 0, 0, (SCM performance, SCM filename),
-           "Write @var{performance} to @var{filename}.")
+           3, 0, 0, (SCM performance, SCM filename, SCM name),
+           "Write @var{performance} to @var{filename} storing @var{name} as "
+           "the name of the performance in the file metadata.")
 {
   LY_ASSERT_SMOB (Performance, performance, 1);
   LY_ASSERT_TYPE (scm_is_string, filename, 2);
+  LY_ASSERT_TYPE (scm_is_string, name, 3);
 
-  unsmob<Performance> (performance)->write_output (ly_scm2string (filename));
+  unsmob<Performance> (performance)->write_output (ly_scm2string (filename),
+                                                   ly_scm2string (name));
   return SCM_UNSPECIFIED;
 }
-
index f462c3322952cc2d8b0f3d49a8e04f3e5e5d1f14..9f840efd2524eedc28e7204a13d5c5f47d97a958 100644 (file)
@@ -37,7 +37,8 @@ using namespace std;
 
 Performance::Performance (bool ports)
   : midi_ (0),
-    ports_ (ports)
+    ports_ (ports),
+    header_ (SCM_EOL)
 {
 }
 
@@ -47,7 +48,27 @@ Performance::~Performance ()
 }
 
 void
-Performance::output (Midi_stream &midi_stream) const
+Performance::derived_mark () const
+{
+  scm_gc_mark (header_);
+}
+
+SCM
+Performance::get_header () const
+{
+  return header_;
+}
+
+void
+Performance::set_header (SCM module)
+{
+  assert (ly_is_module (module));
+  header_ = module;
+}
+
+void
+Performance::output (Midi_stream &midi_stream,
+                     const string &performance_name) const
 {
   int tracks_ = audio_staffs_.size ();
 
@@ -64,6 +85,21 @@ Performance::output (Midi_stream &midi_stream) const
   for (vsize i = 0; i < audio_staffs_.size (); i++)
     {
       Audio_staff *s = audio_staffs_[i];
+      if (Audio_control_track_staff *c =
+          dynamic_cast<Audio_control_track_staff *>(s))
+        {
+          // The control track, created by Control_track_performer, should
+          // contain a placeholder for the name of the MIDI sequence as its
+          // initial audio element.  Fill in the name of the sequence to
+          // this element before outputting MIDI.
+          assert (!c->audio_items_.empty ());
+          Audio_text *text =
+            dynamic_cast<Audio_text *>(c->audio_items_.front ());
+          assert (text != 0);
+          assert (text->type_ == Audio_text::TRACK_NAME);
+          assert (text->text_string_ == "control track");
+          text->text_string_ = performance_name;
+        }
       debug_output ("[" + ::to_string (i), true);
       s->output (midi_stream, i, ports_, moment_to_ticks (start_mom));
       debug_output ("]", false);
@@ -77,7 +113,7 @@ Performance::add_element (Audio_element *p)
 }
 
 void
-Performance::write_output (string out) const
+Performance::write_output (string out, const string &performance_name) const
 {
   if (out == "-")
     out = "lelie.midi";
@@ -89,7 +125,7 @@ Performance::write_output (string out) const
   Midi_stream midi_stream (out);
   message (_f ("MIDI output to `%s'...", out));
 
-  output (midi_stream);
+  output (midi_stream, performance_name);
   progress_indication ("\n");
 }
 
index a3ca13f3fdeb2a340e6783b1fd4907c6b252d515..3564a709b398456fe9e3bb8a388a5a5a2eb73418 100644 (file)
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;
 
+;;; Adapted from the handle-metadata function in framework-ps.scm
+(define (performance-name-from-header header)
+  (define (metadata-lookup-output overridevar fallbackvar)
+    (let* ((overrideval (ly:modules-lookup (list header) overridevar))
+           (fallbackval (ly:modules-lookup (list header) fallbackvar))
+           (val (if overrideval overrideval fallbackval)))
+      (if val (ly:encode-string-for-pdf (markup->string val)) "")))
+  (if (null? header)
+      ""
+      (metadata-lookup-output 'midititle 'title)))
+
 (define-public (write-performances-midis performances basename . rest)
   (let ((midi-ext (ly:get-option 'midi-extension)))
     (let
       ((perfs performances)
        (count (if (null? rest) 0 (car rest))))
       (if (pair? perfs)
-          (begin
+          (let ((perf (car perfs)))
             (ly:performance-write
-             (car perfs)
+             perf
              (if (> count 0)
                  (format #f "~a-~a.~a" basename count midi-ext)
-                 (format #f "~a.~a" basename midi-ext)))
+                 (format #f "~a.~a" basename midi-ext))
+             (performance-name-from-header (ly:performance-header perf)))
             (loop (cdr perfs) (1+ count)))))))