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