1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3 
4   Copyright (C) 1998--2020 Jan Nieuwenhuizen <janneke@gnu.org>
5 
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10 
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15 
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "engraver.hh"
21 
22 #include "context.hh"
23 #include "duration.hh"
24 #include "grob-array.hh"
25 #include "item.hh"
26 #include "music.hh"
27 #include "stream-event.hh"
28 #include "text-interface.hh"
29 
30 #include "translator.icc"
31 
32 #include <cctype>
33 
34 class Metronome_mark_engraver : public Engraver
35 {
36   Item *text_;
37   Grob *support_;
38   Grob *bar_;
39   Stream_event *tempo_ev_;
40 
41 public:
42   TRANSLATOR_DECLARATIONS (Metronome_mark_engraver);
43 
44 protected:
45   void stop_translation_timestep ();
46   void process_music ();
47 
48   void acknowledge_break_aligned (Grob_info);
49   void acknowledge_break_alignment (Grob_info);
50   void acknowledge_grob (Grob_info) override;
51 
52   void listen_tempo_change (Stream_event *);
53 };
54 
Metronome_mark_engraver(Context * c)55 Metronome_mark_engraver::Metronome_mark_engraver (Context *c)
56   : Engraver (c)
57 {
58   text_ = 0;
59   support_ = 0;
60   bar_ = 0;
61   tempo_ev_ = 0;
62 }
63 
64 void
listen_tempo_change(Stream_event * ev)65 Metronome_mark_engraver::listen_tempo_change (Stream_event *ev)
66 {
67   ASSIGN_EVENT_ONCE (tempo_ev_, ev);
68 }
69 
70 static bool
safe_is_member(SCM scm,SCM lst)71 safe_is_member (SCM scm, SCM lst)
72 {
73   return ly_is_list (lst) && scm_is_true (scm_member (scm, lst));
74 }
75 
76 void
acknowledge_break_aligned(Grob_info info)77 Metronome_mark_engraver::acknowledge_break_aligned (Grob_info info)
78 {
79   Grob *g = info.grob ();
80 
81   if (text_
82       && scm_is_eq (get_property (g, "break-align-symbol"),
83                     ly_symbol2scm ("staff-bar")))
84     bar_ = g;
85   else if (text_ && !support_
86            && safe_is_member (get_property (g, "break-align-symbol"),
87                               get_property (text_, "break-align-symbols")))
88     {
89       support_ = g;
90       text_->set_x_parent (g);
91     }
92   if (bar_ || support_)
93     set_property (text_, "non-musical", SCM_BOOL_T);
94 }
95 
96 void
acknowledge_break_alignment(Grob_info info)97 Metronome_mark_engraver::acknowledge_break_alignment (Grob_info info)
98 {
99   Grob *g = info.grob ();
100 
101   if (text_
102       && support_
103       && dynamic_cast<Item *> (g))
104     text_->set_x_parent (g);
105 }
106 
107 void
acknowledge_grob(Grob_info info)108 Metronome_mark_engraver::acknowledge_grob (Grob_info info)
109 {
110   Grob *g = info.grob ();
111 
112   if (text_)
113     for (SCM s = get_property (text_, "non-break-align-symbols");
114          scm_is_pair (s);
115          s = scm_cdr (s))
116       if (g->internal_has_interface (scm_car (s)))
117         text_->set_x_parent (g);
118 }
119 
120 void
stop_translation_timestep()121 Metronome_mark_engraver::stop_translation_timestep ()
122 {
123   if (text_)
124     {
125       if (text_->get_x_parent ()
126           && text_->get_x_parent ()->internal_has_interface (ly_symbol2scm ("multi-measure-rest-interface"))
127           && bar_)
128         text_->set_x_parent (bar_);
129       else if (!support_)
130         {
131           /*
132             Gardner Read "Music Notation", p.278
133 
134             Align the metronome mark over the time signature (or the
135             first notational element of the measure if no time
136             signature is present in that measure).
137           */
138           if (Grob *mc = unsmob<Grob> (get_property (this, "currentMusicalColumn")))
139             text_->set_x_parent (mc);
140           else if (Grob *cc = unsmob<Grob> (get_property (this, "currentCommandColumn")))
141             text_->set_x_parent (cc);
142         }
143       set_object (text_, "side-support-elements",
144                   grob_list_to_grob_array (get_property (this, "stavesFound")));
145       text_ = 0;
146       support_ = 0;
147       bar_ = 0;
148       tempo_ev_ = 0;
149     }
150 }
151 
152 void
process_music()153 Metronome_mark_engraver::process_music ()
154 {
155   if (tempo_ev_)
156     {
157       text_ = make_item ("MetronomeMark", tempo_ev_->self_scm ());
158 
159       SCM proc = get_property (this, "metronomeMarkFormatter");
160       SCM result = scm_call_2 (proc,
161                                tempo_ev_->self_scm (),
162                                context ()->self_scm ());
163 
164       set_property (text_, "text", result);
165     }
166 }
167 
168 void
boot()169 Metronome_mark_engraver::boot ()
170 {
171   ADD_LISTENER (Metronome_mark_engraver, tempo_change);
172   ADD_ACKNOWLEDGER (Metronome_mark_engraver, break_aligned);
173   ADD_ACKNOWLEDGER (Metronome_mark_engraver, break_alignment);
174   ADD_ACKNOWLEDGER (Metronome_mark_engraver, grob);
175 }
176 
177 ADD_TRANSLATOR (Metronome_mark_engraver,
178                 /* doc */
179                 "Engrave metronome marking.  This delegates the formatting"
180                 " work to the function in the @code{metronomeMarkFormatter}"
181                 " property.  The mark is put over all staves.  The staves are"
182                 " taken from the @code{stavesFound} property, which is"
183                 " maintained by @ref{Staff_collecting_engraver}.",
184 
185                 /* create */
186                 "MetronomeMark ",
187 
188                 /* read */
189                 "currentCommandColumn "
190                 "currentMusicalColumn "
191                 "metronomeMarkFormatter "
192                 "stavesFound "
193                 "tempoHideNote ",
194 
195                 /* write */
196                 ""
197                );
198