1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3 
4   Copyright (C) 2000--2021 Han-Wen Nienhuys <hanwen@xs4all.nl>, Erik Sandberg <mandolaerik@gmail.com>
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 #include "global-context.hh"
22 #include "international.hh"
23 #include "item.hh"
24 #include "side-position-interface.hh"
25 #include "spanner.hh"
26 #include "stream-event.hh"
27 #include "warn.hh"
28 
29 #include "translator.icc"
30 
31 class Percent_repeat_engraver : public Engraver
32 {
33   void typeset_perc ();
34 
35 public:
36   TRANSLATOR_DECLARATIONS (Percent_repeat_engraver);
37 
38 protected:
39   Stream_event *percent_event_ = nullptr;
40 
41   // moment (global time) where percent started
42   Moment start_mom_;
43   // moment (global time) where percent should end
44   Moment stop_mom_;
45 
46   Spanner *percent_ = nullptr;
47   Spanner *percent_counter_ = nullptr;
48 
49   Grob *first_command_column_ = nullptr;
50   Moment command_moment_ {-1};
51 
52   void initialize () override;
53   void finalize () override;
54   void listen_percent (Stream_event *);
55 
56   void maybe_start ();
57   void start_translation_timestep ();
58   void stop_translation_timestep ();
59   void process_music ();
60 };
61 
Percent_repeat_engraver(Context * c)62 Percent_repeat_engraver::Percent_repeat_engraver (Context *c)
63   : Engraver (c)
64 {
65 }
66 
67 void
initialize()68 Percent_repeat_engraver::initialize ()
69 {
70   if (now_mom ().main_part_ >= 0)
71     {
72       // This is happening during a timestep, so we might (or always will?)
73       // miss the start announcement that our state machine requires.
74       //
75       // TODO: Investigate whether this could be solved more generally, with
76       // the translator framework guaranteeing that start_translation_timestep
77       // () is called exactly once per timestep in any case.
78       maybe_start ();
79     }
80 }
81 
82 void
start_translation_timestep()83 Percent_repeat_engraver::start_translation_timestep ()
84 {
85   maybe_start ();
86 }
87 
88 // The "maybe" part of this is that the developers are not sure whether it is
89 // possible for it to be called more than once in a given timestep.
90 void
maybe_start()91 Percent_repeat_engraver::maybe_start ()
92 {
93   if (now_mom ().main_part_ != command_moment_.main_part_)
94     {
95       first_command_column_
96         = unsmob<Grob> (get_property (this, "currentCommandColumn"));
97       command_moment_ = now_mom ();
98     }
99 
100   if (stop_mom_.main_part_ == now_mom ().main_part_)
101     {
102       if (percent_)
103         typeset_perc ();
104       percent_event_ = nullptr;
105     }
106 }
107 
108 void
listen_percent(Stream_event * ev)109 Percent_repeat_engraver::listen_percent (Stream_event *ev)
110 {
111   if (!percent_event_)
112     {
113       auto body_length = get_event_length (ev);
114       start_mom_ = now_mom ();
115       stop_mom_ = now_mom () + body_length;
116       find_global_context ()->add_moment_to_process (stop_mom_);
117       percent_event_ = ev;
118     }
119   else
120     {
121       /*
122         print a warning: no assignment happens because
123         percent_event_ != 0
124       */
125       ASSIGN_EVENT_ONCE (percent_event_, ev);
126     }
127 }
128 
129 void
process_music()130 Percent_repeat_engraver::process_music ()
131 {
132   if (percent_event_
133       && now_mom ().main_part_ == start_mom_.main_part_)
134     {
135       if (percent_)
136         typeset_perc ();
137 
138       percent_ = make_spanner ("PercentRepeat", percent_event_->self_scm ());
139 
140       Grob *col = first_command_column_;
141       percent_->set_bound (LEFT, col);
142 
143       SCM count = get_property (percent_event_, "repeat-count");
144       if (!scm_is_null (count) && from_scm<bool> (get_property (this, "countPercentRepeats"))
145           && check_repeat_count_visibility (context (), count))
146         {
147           percent_counter_ = make_spanner ("PercentRepeatCounter",
148                                            percent_event_->self_scm ());
149 
150           SCM text = scm_number_to_string (count, to_scm (10));
151           set_property (percent_counter_, "text", text);
152           percent_counter_->set_bound (LEFT, col);
153           Side_position_interface::add_support (percent_counter_, percent_);
154           percent_counter_->set_y_parent (percent_);
155           percent_counter_->set_x_parent (percent_);
156         }
157       else
158         percent_counter_ = nullptr;
159     }
160 }
161 
162 void
finalize()163 Percent_repeat_engraver::finalize ()
164 {
165   if (percent_)
166     {
167       percent_event_->warning (_ ("unterminated percent repeat"));
168       percent_->suicide ();
169       percent_counter_->suicide ();
170     }
171 }
172 
173 void
typeset_perc()174 Percent_repeat_engraver::typeset_perc ()
175 {
176   Grob *col = first_command_column_;
177 
178   percent_->set_bound (RIGHT, col);
179   percent_ = nullptr;
180 
181   if (percent_counter_)
182     percent_counter_->set_bound (RIGHT, col);
183   percent_counter_ = nullptr;
184 }
185 
186 void
stop_translation_timestep()187 Percent_repeat_engraver::stop_translation_timestep ()
188 {
189 }
190 
191 void
boot()192 Percent_repeat_engraver::boot ()
193 {
194   ADD_LISTENER (Percent_repeat_engraver, percent);
195 }
196 
197 ADD_TRANSLATOR (Percent_repeat_engraver,
198                 /* doc */
199                 "Make whole measure repeats.",
200 
201                 /* create */
202                 "PercentRepeat "
203                 "PercentRepeatCounter ",
204 
205                 /* read */
206                 "countPercentRepeats "
207                 "currentCommandColumn "
208                 "repeatCountVisibility ",
209 
210                 /* write */
211                 ""
212                );
213