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