1 /*
2 This file is part of LilyPond, the GNU music typesetter.
3
4 Copyright (C) 1997--2021 Han-Wen Nienhuys <hanwen@xs4all.nl>
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 "stem-tremolo.hh"
21
22 #include "spanner.hh"
23 #include "beam.hh"
24 #include "directional-element-interface.hh"
25 #include "international.hh"
26 #include "item.hh"
27 #include "lookup.hh"
28 #include "note-collision.hh"
29 #include "note-column.hh"
30 #include "output-def.hh"
31 #include "staff-symbol-referencer.hh"
32 #include "stem.hh"
33 #include "warn.hh"
34
35 using std::vector;
36
37 MAKE_SCHEME_CALLBACK (Stem_tremolo, calc_cross_staff, 1)
38 SCM
calc_cross_staff(SCM smob)39 Stem_tremolo::calc_cross_staff (SCM smob)
40 {
41 Grob *me = unsmob<Grob> (smob);
42 Grob *stem = unsmob<Grob> (get_object (me, "stem"));
43 return get_property (stem, "cross-staff");
44 }
45
46 MAKE_SCHEME_CALLBACK (Stem_tremolo, calc_slope, 1)
47 SCM
calc_slope(SCM smob)48 Stem_tremolo::calc_slope (SCM smob)
49 {
50 Grob *me = unsmob<Grob> (smob);
51 Grob *stem = unsmob<Grob> (get_object (me, "stem"));
52 Spanner *beam = Stem::get_beam (stem);
53
54 SCM style = get_property (me, "style");
55
56 if (beam && !scm_is_eq (style, ly_symbol2scm ("constant")))
57 {
58 Real dy = 0;
59 SCM s = get_property (beam, "quantized-positions");
60 if (is_number_pair (s))
61 dy = - scm_to_double (scm_car (s)) + scm_to_double (scm_cdr (s));
62
63 Grob *s2 = Beam::last_normal_stem (beam);
64 Grob *s1 = Beam::first_normal_stem (beam);
65
66 Grob *common = s1->common_refpoint (s2, X_AXIS);
67 Real dx = s2->relative_coordinate (common, X_AXIS)
68 - s1->relative_coordinate (common, X_AXIS);
69
70 return to_scm (dx ? dy / dx : 0);
71 }
72 else
73 /* down stems with flags should have more sloped trems (helps avoid
74 flag/stem collisions without making the stem very long) */
75 return to_scm ((Stem::duration_log (stem) >= 3
76 && get_grob_direction (me) == DOWN && !beam)
77 ? 0.40 : 0.25);
78 }
79
80 MAKE_SCHEME_CALLBACK (Stem_tremolo, calc_width, 1)
81 SCM
calc_width(SCM smob)82 Stem_tremolo::calc_width (SCM smob)
83 {
84 Grob *me = unsmob<Grob> (smob);
85 Grob *stem = unsmob<Grob> (get_object (me, "stem"));
86 Direction dir = get_grob_direction (me);
87 bool beam = Stem::get_beam (stem);
88 bool flag = Stem::duration_log (stem) >= 3 && !beam;
89
90 /* beamed stems and up-stems with flags have shorter tremolos */
91 return to_scm (((dir == UP && flag) || beam) ? 1.0 : 1.5);
92 }
93
94 MAKE_SCHEME_CALLBACK (Stem_tremolo, calc_shape, 1)
95 SCM
calc_shape(SCM smob)96 Stem_tremolo::calc_shape (SCM smob)
97 {
98 Grob *me = unsmob<Grob> (smob);
99 Grob *stem = unsmob<Grob> (get_object (me, "stem"));
100 Direction dir = get_grob_direction (me);
101 bool beam = Stem::get_beam (stem);
102 bool flag = Stem::duration_log (stem) >= 3 && !beam;
103 SCM style = get_property (me, "style");
104
105 return ly_symbol2scm (!scm_is_eq (style, ly_symbol2scm ("constant"))
106 && ((dir == UP && flag) || beam)
107 ? "rectangle" : "beam-like");
108 }
109
110 Real
get_beam_translation(Grob * me)111 Stem_tremolo::get_beam_translation (Grob *me)
112 {
113 Grob *stem = unsmob<Grob> (get_object (me, "stem"));
114 Spanner *beam = Stem::get_beam (stem);
115
116 return (beam && beam->is_live ())
117 ? Beam::get_beam_translation (beam)
118 : (Staff_symbol_referencer::staff_space (me)
119 * from_scm<double> (get_property (me, "length-fraction"), 1.0) * 0.81);
120 }
121
122 Stencil
raw_stencil(Grob * me,Real slope,Direction dir)123 Stem_tremolo::raw_stencil (Grob *me, Real slope, Direction dir)
124 {
125 Real ss = Staff_symbol_referencer::staff_space (me);
126 Real thick = from_scm<double> (get_property (me, "beam-thickness"), 1);
127 Real width = from_scm<double> (get_property (me, "beam-width"), 1);
128 Real blot = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
129 SCM shape = get_property (me, "shape");
130 if (!scm_is_symbol (shape))
131 shape = ly_symbol2scm ("beam-like");
132
133 width *= ss;
134 thick *= ss;
135
136 Stencil a;
137 if (scm_is_eq (shape, ly_symbol2scm ("rectangle")))
138 a = Lookup::rotated_box (slope, width, thick, blot);
139 else
140 a = Lookup::beam (slope, width, thick, blot);
141
142 a.align_to (X_AXIS, CENTER);
143 a.align_to (Y_AXIS, CENTER);
144
145 int tremolo_flags = from_scm (get_property (me, "flag-count"), 0);
146 if (!tremolo_flags)
147 {
148 programming_error ("no tremolo flags");
149
150 me->suicide ();
151 return Stencil ();
152 }
153
154 Real beam_translation = get_beam_translation (me);
155
156 Stencil mol;
157 for (int i = 0; i < tremolo_flags; i++)
158 {
159 Stencil b (a);
160 b.translate_axis (beam_translation * i * dir * -1, Y_AXIS);
161 mol.add_stencil (b);
162 }
163 return mol;
164 }
165
166 MAKE_SCHEME_CALLBACK (Stem_tremolo, pure_height, 3);
167 SCM
pure_height(SCM smob,SCM,SCM)168 Stem_tremolo::pure_height (SCM smob, SCM, SCM)
169 {
170 Item *me = unsmob<Item> (smob);
171
172 /*
173 Cannot use the real slope, since it looks at the Beam.
174 */
175 Stencil s1 (untranslated_stencil (me, 0.35));
176 Item *stem = unsmob<Item> (get_object (me, "stem"));
177 if (!stem)
178 return to_scm (s1.extent (Y_AXIS));
179
180 Direction dir = get_grob_direction (me);
181
182 Spanner *beam = Stem::get_beam (stem);
183
184 if (!beam)
185 return to_scm (s1.extent (Y_AXIS));
186
187 Interval ph = stem->pure_y_extent (stem, 0, INT_MAX);
188 if (ph.is_empty ()) // This should not really happen but does
189 return to_scm (s1.extent (Y_AXIS));
190
191 Stem_info si = Stem::get_stem_info (stem);
192 ph[-dir] = si.shortest_y_;
193 if (ph.is_empty ()) // This should not really happen either
194 return to_scm (s1.extent (Y_AXIS));
195
196 int beam_count = Stem::beam_multiplicity (stem).length () + 1;
197 Real beam_translation = get_beam_translation (me);
198
199 ph = ph - dir * std::max (beam_count, 1) * beam_translation;
200 ph = ph - ph.center (); // TODO: this nullifies the previous line?!?
201
202 return to_scm (ph);
203 }
204
205 MAKE_SCHEME_CALLBACK (Stem_tremolo, width, 1);
206 SCM
width(SCM smob)207 Stem_tremolo::width (SCM smob)
208 {
209 Grob *me = unsmob<Grob> (smob);
210
211 /*
212 Cannot use the real slope, since it looks at the Beam.
213 */
214 Stencil s1 (untranslated_stencil (me, 0.35));
215
216 return to_scm (s1.extent (X_AXIS));
217 }
218
219 Real
vertical_length(Grob * me)220 Stem_tremolo::vertical_length (Grob *me)
221 {
222 return untranslated_stencil (me, 0.35).extent (Y_AXIS).length ();
223 }
224
225 Stencil
untranslated_stencil(Grob * me,Real slope)226 Stem_tremolo::untranslated_stencil (Grob *me, Real slope)
227 {
228 Grob *stem = unsmob<Grob> (get_object (me, "stem"));
229 if (!stem)
230 {
231 programming_error ("no stem for stem-tremolo");
232 return Stencil ();
233 }
234
235 Direction dir = get_grob_direction (me);
236
237 bool whole_note = Stem::duration_log (stem) <= 0;
238
239 /* for a whole note, we position relative to the notehead, so we want the
240 stencil aligned on the flag closest to the head */
241 Direction stencil_dir = whole_note ? -dir : dir;
242 return raw_stencil (me, slope, stencil_dir);
243 }
244
245 MAKE_SCHEME_CALLBACK (Stem_tremolo, calc_y_offset, 1);
246 SCM
calc_y_offset(SCM smob)247 Stem_tremolo::calc_y_offset (SCM smob)
248 {
249 Grob *me = unsmob<Grob> (smob);
250 return to_scm (y_offset (me, false));
251 }
252
253 MAKE_SCHEME_CALLBACK (Stem_tremolo, pure_calc_y_offset, 3);
254 SCM
pure_calc_y_offset(SCM smob,SCM,SCM)255 Stem_tremolo::pure_calc_y_offset (SCM smob,
256 SCM, /* start */
257 SCM /* end */)
258 {
259 Grob *me = unsmob<Grob> (smob);
260 return to_scm (y_offset (me, true));
261 }
262
263 MAKE_SCHEME_CALLBACK (Stem_tremolo, calc_direction, 1);
264 SCM
calc_direction(SCM smob)265 Stem_tremolo::calc_direction (SCM smob)
266 {
267 Item *me = unsmob<Item> (smob);
268
269 Item *stem = unsmob<Item> (get_object (me, "stem"));
270 if (!stem)
271 return to_scm (CENTER);
272
273 Direction stemdir = get_grob_direction (stem);
274
275 vector<int> nhp = Stem::note_head_positions (stem);
276 /*
277 * We re-decide stem-dir if there may be collisions with other
278 * note heads in the staff.
279 */
280 Grob *maybe_nc = stem->get_x_parent ()->get_x_parent ();
281 bool whole_note = Stem::duration_log (stem) <= 0;
282 if (whole_note && has_interface<Note_collision_interface> (maybe_nc))
283 {
284 Drul_array<bool> avoid_me;
285 vector<int> all_nhps = Note_collision_interface::note_head_positions (maybe_nc);
286 if (all_nhps[0] < nhp[0])
287 avoid_me[DOWN] = true;
288 if (all_nhps.back () > nhp.back ())
289 avoid_me[UP] = true;
290 if (avoid_me[stemdir])
291 {
292 stemdir = -stemdir;
293 if (avoid_me[stemdir])
294 {
295 me->warning (_ ("Whole-note tremolo may collide with simultaneous notes."));
296 stemdir = -stemdir;
297 }
298 }
299 }
300 return to_scm (stemdir);
301 }
302
303 Real
y_offset(Grob * me,bool pure)304 Stem_tremolo::y_offset (Grob *me, bool pure)
305 {
306 Item *stem = unsmob<Item> (get_object (me, "stem"));
307 if (!stem)
308 return 0.0;
309
310 Direction dir = get_grob_direction (me);
311
312 Spanner *beam = Stem::get_beam (stem);
313 Real beam_translation = get_beam_translation (me);
314
315 int beam_count = beam ? (Stem::beam_multiplicity (stem).length () + 1) : 0;
316
317 if (pure && beam)
318 {
319 Interval ph = stem->pure_y_extent (stem, 0, INT_MAX);
320 Stem_info si = Stem::get_stem_info (stem);
321 ph[-dir] = si.shortest_y_;
322
323 return (ph - dir * std::max (beam_count, 1) * beam_translation)[dir] - dir * 0.5 * me->pure_y_extent (me, 0, INT_MAX).length ();
324 }
325
326 Real end_y
327 = (pure
328 ? stem->pure_y_extent (stem, 0, INT_MAX)[dir]
329 : stem->extent (stem, Y_AXIS)[dir])
330 - dir * std::max (beam_count, 1) * beam_translation
331 - Stem::beam_end_corrective (stem);
332
333 if (!beam && Stem::duration_log (stem) >= 3)
334 {
335 end_y -= dir * (Stem::duration_log (stem) - 2) * beam_translation;
336 if (dir == UP)
337 end_y -= dir * beam_translation * 0.5;
338 }
339
340 bool whole_note = Stem::duration_log (stem) <= 0;
341 if (whole_note || std::isinf (end_y))
342 {
343 /* we shouldn't position relative to the end of the stem since the stem
344 is invisible */
345 Real ss = Staff_symbol_referencer::staff_space (me);
346 vector<int> nhp = Stem::note_head_positions (stem);
347 if (nhp.empty ())
348 {
349 me->warning (_ ("stem tremolo has no note heads"));
350 end_y = 0.0;
351 }
352 else
353 {
354 Real note_head = (dir == UP ? nhp.back () : nhp[0]) * ss / 2;
355 end_y = note_head + dir * 1.5;
356 }
357 }
358
359 return end_y;
360 }
361
362 MAKE_SCHEME_CALLBACK (Stem_tremolo, print, 1);
363 SCM
print(SCM grob)364 Stem_tremolo::print (SCM grob)
365 {
366 Grob *me = unsmob<Grob> (grob);
367
368 Stencil s = untranslated_stencil (me, from_scm<double> (get_property (me, "slope"), 0.25));
369 return s.smobbed_copy ();
370 }
371
372 ADD_INTERFACE (Stem_tremolo,
373 "A beam slashing a stem to indicate a tremolo. The property"
374 " @code{shape} can be @code{beam-like} or @code{rectangle}.",
375
376 /* properties */
377 "beam-thickness "
378 "beam-width "
379 "direction "
380 "flag-count "
381 "length-fraction "
382 "stem "
383 "shape "
384 "slope "
385 );
386