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