From: Heikki Tauriainen Date: Sat, 15 Aug 2015 19:33:31 +0000 (+0200) Subject: Set the sequence name in MIDI using title information from X-Git-Tag: release/2.19.26-1~44 X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=ab6b5b6312019ac4d6ea8af57b2ba5aa4295b42f;p=lilypond.git Set the sequence name in MIDI using title information from \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 --- diff --git a/Documentation/changes.tely b/Documentation/changes.tely index 6182a59323..ca142c3286 100644 --- a/Documentation/changes.tely +++ b/Documentation/changes.tely @@ -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 index 0000000000..239d55c0bc --- /dev/null +++ b/input/regression/midi/sequence-name-scoping.ly @@ -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 index 0000000000..f10590db16 --- /dev/null +++ b/input/regression/midi/sequence-name.ly @@ -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 { } + } + } + +} diff --git a/lily/book.cc b/lily/book.cc index cb145f0436..c7f5dd0237 100644 --- a/lily/book.cc +++ b/lily/book.cc @@ -229,7 +229,18 @@ Book::process_score (SCM s, Paper_book *output_paper_book, Output_def *layout) Music_output *output = unsmob (scm_car (outputs)); if (Performance *perf = dynamic_cast (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 (output)) { if (ly_is_module (score->get_header ())) diff --git a/lily/control-track-performer.cc b/lily/control-track-performer.cc index 9b0d676870..82beebe122 100644 --- a/lily/control-track-performer.cc +++ b/lily/control-track-performer.cc @@ -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); diff --git a/lily/include/audio-staff.hh b/lily/include/audio-staff.hh index 13124cfc09..a4a9d8a0d1 100644 --- a/lily/include/audio-staff.hh +++ b/lily/include/audio-staff.hh @@ -36,4 +36,10 @@ struct Audio_staff : public Audio_element vector 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 diff --git a/lily/include/performance.hh b/lily/include/performance.hh index 3f9c5d6f1b..f9c53ff2e8 100644 --- a/lily/include/performance.hh +++ b/lily/include/performance.hh @@ -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_staffs_; vector audio_elements_; Output_def *midi_; bool ports_; + SCM header_; }; #endif /* PERFORMANCE_HH */ diff --git a/lily/performance-scheme.cc b/lily/performance-scheme.cc index 9ab087a2c3..4d3c1e6512 100644 --- a/lily/performance-scheme.cc +++ b/lily/performance-scheme.cc @@ -19,14 +19,38 @@ #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); + 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); + 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)->write_output (ly_scm2string (filename)); + unsmob (performance)->write_output (ly_scm2string (filename), + ly_scm2string (name)); return SCM_UNSPECIFIED; } - diff --git a/lily/performance.cc b/lily/performance.cc index f462c33229..9f840efd25 100644 --- a/lily/performance.cc +++ b/lily/performance.cc @@ -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(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(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"); } diff --git a/scm/midi.scm b/scm/midi.scm index a3ca13f3fd..3564a709b3 100644 --- a/scm/midi.scm +++ b/scm/midi.scm @@ -290,6 +290,17 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; +;;; 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 @@ -297,10 +308,11 @@ ((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)))))))