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