]> git.donarmstrong.com Git - lilypond.git/blob - lily/hairpin.cc
Merge branch 'master' into lilypond/translation
[lilypond.git] / lily / hairpin.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1997--2011 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 "hairpin.hh"
21
22 #include "axis-group-interface.hh"
23 #include "dimensions.hh"
24 #include "international.hh"
25 #include "line-interface.hh"
26 #include "output-def.hh"
27 #include "paper-column.hh"
28 #include "pointer-group-interface.hh"
29 #include "spanner.hh"
30 #include "staff-symbol-referencer.hh"
31 #include "text-interface.hh"
32 #include "note-column.hh"
33 #include "warn.hh"
34
35 MAKE_SCHEME_CALLBACK (Hairpin, pure_height, 3);
36 SCM
37 Hairpin::pure_height (SCM smob, SCM, SCM)
38 {
39   Grob *me = unsmob_grob (smob);
40   Real height = robust_scm2double (me->get_property ("height"), 0.0)
41                 * Staff_symbol_referencer::staff_space (me);
42
43   Real thickness = robust_scm2double (me->get_property ("thickness"), 1)
44                    * Staff_symbol_referencer::line_thickness (me);
45
46   height += thickness / 2;
47   return ly_interval2scm (Interval (-height, height));
48 }
49
50 MAKE_SCHEME_CALLBACK (Hairpin, print, 1);
51 SCM
52 Hairpin::print (SCM smob)
53 {
54   Spanner *me = unsmob_spanner (smob);
55
56   SCM s = me->get_property ("grow-direction");
57   if (!is_direction (s))
58     {
59       me->suicide ();
60       return SCM_EOL;
61     }
62
63   Direction grow_dir = to_dir (s);
64   Real padding = robust_scm2double (me->get_property ("bound-padding"), 0.5);
65
66   Drul_array<bool> broken;
67   Drul_array<Item *> bounds;
68   Direction d = LEFT;
69   do
70     {
71       bounds[d] = me->get_bound (d);
72       broken[d] = bounds[d]->break_status_dir () != CENTER;
73     }
74   while (flip (&d) != LEFT);
75
76   broken[RIGHT] = broken[RIGHT] && me->broken_neighbor (RIGHT);
77   broken[RIGHT] = broken[RIGHT] && me->broken_neighbor (RIGHT)->is_live ();
78
79   if (broken[RIGHT])
80     {
81       Spanner *next = me->broken_neighbor (RIGHT);
82       Stencil *s = next->get_stencil ();
83       if (!s || s->is_empty ())
84         broken[RIGHT] = false;
85     }
86
87   Grob *common = bounds[LEFT]->common_refpoint (bounds[RIGHT], X_AXIS);
88   Drul_array<Real> x_points;
89
90   /*
91     Use the height and thickness of the hairpin when making a circled tip
92   */
93   bool circled_tip = ly_scm2bool (me->get_property ("circled-tip"));
94   Real height = robust_scm2double (me->get_property ("height"), 0.2)
95                 * Staff_symbol_referencer::staff_space (me);
96   /*
97     FIXME: 0.525 is still just a guess...
98   */
99   Real rad = height * 0.525;
100   Real thick = 1.0;
101   if (circled_tip)
102     thick = robust_scm2double (me->get_property ("thickness"), 1.0)
103             * Staff_symbol_referencer::line_thickness (me);
104
105   do
106     {
107       Item *b = bounds[d];
108       x_points[d] = b->relative_coordinate (common, X_AXIS);
109       if (broken [d])
110         {
111           if (d == LEFT)
112             x_points[d] = b->extent (common, X_AXIS)[RIGHT];
113         }
114       else
115         {
116           if (Text_interface::has_interface (b))
117             {
118               Interval e = b->extent (common, X_AXIS);
119               if (!e.is_empty ())
120                 x_points[d] = e[-d] - d * padding;
121             }
122           else
123             {
124               bool neighbor_found = false;
125               Spanner *adjacent = NULL;
126               extract_grob_set (me, "adjacent-spanners", neighbors);
127               for (vsize i = 0; i < neighbors.size (); i++)
128                 {
129                   /*
130                     FIXME: this will fuck up in case of polyphonic
131                     notes in other voices. Need to look at note-columns
132                     in the current staff/voice.
133                   */
134                   adjacent = dynamic_cast<Spanner *> (neighbors[i]);
135                   if (adjacent
136                       && (adjacent->get_bound (-d)->get_column ()
137                           == b->get_column ()))
138                     {
139                       neighbor_found = true;
140                       break;
141                     }
142                 }
143
144               Interval e = (Axis_group_interface::has_interface (b)
145                             ? Axis_group_interface::generic_bound_extent (b, common, X_AXIS)
146                             : robust_relative_extent (b, common, X_AXIS));
147               if (neighbor_found)
148                 {
149                   if (Hairpin::has_interface (adjacent))
150                     {
151                       /*
152                         Handle back-to-back hairpins with a circle in the middle
153                       */
154                       if (circled_tip && (grow_dir != d))
155                         x_points[d] = e.center () + d * (rad - thick / 2.0);
156                       /*
157                         If we're hung on a paper column, that means we're not
158                         adjacent to a text-dynamic, and we may move closer. We
159                         make the padding a little smaller, here.
160                       */
161                       else
162                         x_points[d] = e.center () - d * padding / 3;
163                     }
164                   // Our neighbor is a dynamic text spanner, so add the
165                   // same amount of padding as for text dynamics
166                   else
167                     x_points[d] = e[-d] - d * padding;
168                 }
169               else
170                 {
171                   if (Note_column::has_interface (b)
172                       && Note_column::has_rests (b))
173                     x_points[d] = e[-d];
174                   else
175                     x_points[d] = e[d];
176
177                   Item *bound = me->get_bound (d);
178                   if (bound->is_non_musical (bound))
179                     x_points[d] -= d * padding;
180                 }
181             }
182         }
183     }
184   while (flip (&d) != LEFT);
185
186   Real width = x_points[RIGHT] - x_points[LEFT];
187   if (width < 0)
188     {
189       me->warning (_ ((grow_dir < 0) ? "decrescendo too small"
190                       : "crescendo too small"));
191       width = 0;
192     }
193
194   bool continued = broken[Direction (-grow_dir)];
195
196   Real starth = 0;
197   Real endh = 0;
198   if (grow_dir < 0)
199     {
200       starth = height;
201       endh = continued ? height / 2 : 0.0;
202     }
203   else
204     {
205       starth = continued ? height / 2 : 0.0;
206       endh = height;
207     }
208
209   /*
210     should do relative to staff-symbol staff-space?
211   */
212   Stencil mol;
213   Real x = 0.0;
214
215   /*
216     Compensate for size of circle
217   */
218   Direction tip_dir = -grow_dir;
219   if (circled_tip && !broken[tip_dir])
220     {
221       if (grow_dir > 0)
222         x = rad * 2.0;
223       else if (grow_dir < 0)
224         width -= rad * 2.0;
225     }
226   mol = Line_interface::line (me, Offset (x, starth), Offset (width, endh));
227   mol.add_stencil (Line_interface::line (me,
228                                          Offset (x, -starth),
229                                          Offset (width, -endh)));
230
231   /*
232     Support al/del niente notation by putting a circle at the
233     tip of the (de)crescendo.
234   */
235   if (circled_tip)
236     {
237       Box extent (Interval (-rad, rad), Interval (-rad, rad));
238
239       /* Hmmm, perhaps we should have a Lookup::circle () method? */
240       Stencil circle (extent,
241                       scm_list_4 (ly_symbol2scm ("circle"),
242                                   scm_from_double (rad),
243                                   scm_from_double (thick),
244                                   SCM_BOOL_F));
245
246       /*
247         don't add another circle if the hairpin is broken
248       */
249       if (!broken[tip_dir])
250         mol.add_at_edge (X_AXIS, tip_dir, Stencil (circle), 0);
251     }
252
253   mol.translate_axis (x_points[LEFT]
254                       - bounds[LEFT]->relative_coordinate (common, X_AXIS),
255                       X_AXIS);
256   return mol.smobbed_copy ();
257 }
258
259 ADD_INTERFACE (Hairpin,
260                "A hairpin crescendo or decrescendo.",
261
262                /* properties */
263                "adjacent-spanners "
264                "circled-tip "
265                "bound-padding "
266                "grow-direction "
267                "height "
268               );