]> git.donarmstrong.com Git - lilypond.git/blob - lily/arpeggio.cc
New engraver for braces.
[lilypond.git] / lily / arpeggio.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2000--2011 Jan Nieuwenhuizen <janneke@gnu.org>
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 "arpeggio.hh"
21
22 #include "all-font-metrics.hh"
23 #include "bezier.hh"
24 #include "font-interface.hh"
25 #include "grob.hh"
26 #include "lookup.hh"
27 #include "output-def.hh"
28 #include "pointer-group-interface.hh"
29 #include "staff-symbol-referencer.hh"
30 #include "staff-symbol.hh"
31 #include "stem.hh"
32 #include "warn.hh"
33
34 static Stencil
35 get_squiggle (Grob *me)
36 {
37   Font_metric *fm = Font_interface::get_default_font (me);
38   Stencil squiggle = fm->find_by_name ("scripts.arpeggio");
39
40   return squiggle;
41 }
42
43 Grob *
44 Arpeggio::get_common_y (Grob *me)
45 {
46   Grob *common = me;
47
48   extract_grob_set (me, "stems", stems);
49   for (vsize i = 0; i < stems.size (); i++)
50     {
51       Grob *stem = stems[i];
52       common = common->common_refpoint (Staff_symbol_referencer::get_staff_symbol (stem),
53                                         Y_AXIS);
54     }
55
56   return common;
57 }
58
59 MAKE_SCHEME_CALLBACK (Arpeggio, calc_positions, 1);
60 SCM
61 Arpeggio::calc_positions (SCM grob)
62 {
63   Grob *me = unsmob_grob (grob);
64   Grob *common = get_common_y (me);
65
66   /*
67     TODO:
68
69     Using stems here is not very convenient; should store noteheads
70     instead, and also put them into the support. Now we will mess up
71     in vicinity of a collision.
72   */
73   Interval heads;
74   Real my_y = me->relative_coordinate (common, Y_AXIS);
75
76   extract_grob_set (me, "stems", stems);
77   for (vsize i = 0; i < stems.size (); i++)
78     {
79       Grob *stem = stems[i];
80       Grob *ss = Staff_symbol_referencer::get_staff_symbol (stem);
81       Interval iv = Stem::head_positions (stem);
82       iv *= Staff_symbol_referencer::staff_space (me) / 2.0;
83       Real staff_y = ss ? ss->relative_coordinate (common, Y_AXIS) : 0.0;
84       heads.unite (iv + staff_y - my_y);
85     }
86
87   heads *= 1 / Staff_symbol_referencer::staff_space (me);
88
89   return ly_interval2scm (heads);
90 }
91
92 MAKE_SCHEME_CALLBACK (Arpeggio, print, 1);
93 SCM
94 Arpeggio::print (SCM smob)
95 {
96   Grob *me = unsmob_grob (smob);
97   Interval heads = robust_scm2interval (me->get_property ("positions"),
98                                         Interval ())
99                    * Staff_symbol_referencer::staff_space (me);
100
101   if (heads.is_empty () || heads.length () < 0.5)
102     {
103       if (to_boolean (me->get_property ("transparent")))
104         {
105           /*
106             This is part of a cross-staff/-voice span-arpeggio,
107             so we need to ensure `heads' is large enough to encompass
108             a single trill-element since the span-arpeggio depends on
109             its children to prevent collisions.
110           */
111           heads.unite (get_squiggle (me).extent (Y_AXIS));
112         }
113       else
114         {
115           me->warning ("no heads for arpeggio found?");
116           me->suicide ();
117           return SCM_EOL;
118         }
119     }
120
121   SCM ad = me->get_property ("arpeggio-direction");
122   Direction dir = CENTER;
123   if (is_direction (ad))
124     dir = to_dir (ad);
125
126   Stencil mol;
127   Stencil squiggle (get_squiggle (me));
128
129   /*
130     Compensate for rounding error which may occur when a chord
131     reaches the center line, resulting in an extra squiggle
132     being added to the arpeggio stencil.  This value is appreciably
133     larger than the rounding error, which is in the region of 1e-16
134     for a global-staff-size of 20, but small enough that it does not
135     interfere with smaller staff sizes.
136   */
137   const Real epsilon = 1e-3;
138
139   Stencil arrow;
140   if (dir)
141     {
142       Font_metric *fm = Font_interface::get_default_font (me);
143       arrow = fm->find_by_name ("scripts.arpeggio.arrow." + to_string (dir));
144       heads[dir] -= dir * arrow.extent (Y_AXIS).length ();
145     }
146
147   while (mol.extent (Y_AXIS).length () + epsilon < heads.length ())
148     mol.add_at_edge (Y_AXIS, UP, squiggle, 0.0);
149
150   mol.translate_axis (heads[LEFT], Y_AXIS);
151   if (dir)
152     mol.add_at_edge (Y_AXIS, dir, arrow, 0);
153
154   return mol.smobbed_copy ();
155 }
156
157 /* Draws a vertical bracket to the left of a chord
158    Chris Jackson <chris@fluffhouse.org.uk> */
159
160 MAKE_SCHEME_CALLBACK (Arpeggio, brew_chord_bracket, 1);
161 SCM
162 Arpeggio::brew_chord_bracket (SCM smob)
163 {
164   Grob *me = unsmob_grob (smob);
165   Interval heads = robust_scm2interval (me->get_property ("positions"),
166                                         Interval ())
167                    * Staff_symbol_referencer::staff_space (me);
168
169   Real lt = me->layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
170   Real sp = 1.5 * Staff_symbol_referencer::staff_space (me);
171   Real dy = heads.length () + sp;
172   Real x = 0.7;
173
174   Stencil mol (Lookup::bracket (Y_AXIS, Interval (0, dy), lt, x, lt));
175   mol.translate_axis (heads[LEFT] - sp / 2.0, Y_AXIS);
176   return mol.smobbed_copy ();
177 }
178
179 MAKE_SCHEME_CALLBACK (Arpeggio, brew_chord_brace, 1);
180 SCM
181 Arpeggio::brew_chord_brace (SCM smob)
182 {
183   Grob *me = unsmob_grob (smob);
184   Interval heads = robust_scm2interval (me->get_property ("positions"),
185                                         Interval ())
186     * Staff_symbol_referencer::staff_space (me);
187   int minimum_brace_height = robust_scm2int (
188                              me->get_property ("minimum-brace-height"), 1);
189   Font_metric *fm = Font_interface::get_default_font (me);
190
191   int
192     lo = 0;
193   int hi = max ((int) fm->count () - 1, 2);
194
195   /* do a binary search for each Y, not very efficient, but passable?  */
196   Box b;
197   do
198     {
199       int cmp = (lo + hi) / 2;
200       b = fm->get_indexed_char_dimensions (cmp);
201       if (b[Y_AXIS].is_empty () || b[Y_AXIS].length () > heads.length ()+1)
202         hi = cmp;
203       else
204         lo = cmp;
205     }
206   while (hi - lo > 1);
207
208   if (lo < minimum_brace_height)
209     lo = minimum_brace_height;
210
211   Stencil mol (unsmob_metrics (me->get_property ("font"))
212                               ->find_by_name ("brace" + to_string (lo)));
213   mol.translate_axis ((heads[RIGHT] + heads[LEFT]) / 2, Y_AXIS);
214
215   return mol.smobbed_copy ();
216 }
217
218 MAKE_SCHEME_CALLBACK (Arpeggio, brew_chord_slur, 1);
219 SCM
220 Arpeggio::brew_chord_slur (SCM smob)
221 {
222   Grob *me = unsmob_grob (smob);
223   SCM dash_definition = me->get_property ("dash-definition");
224   Interval heads = robust_scm2interval (me->get_property ("positions"),
225                                         Interval ())
226                    * Staff_symbol_referencer::staff_space (me);
227
228   Real lt = me->layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
229   Real dy = heads.length ();
230
231   Real height_limit = 1.5;
232   Real ratio = .33;
233   Bezier curve = slur_shape (dy, height_limit, ratio);
234   curve.rotate (M_PI / 2);
235
236   Stencil mol (Lookup::slur (curve, lt, lt, dash_definition));
237   mol.translate_axis (heads[LEFT], Y_AXIS);
238   return mol.smobbed_copy ();
239 }
240
241 /*
242   We have to do a callback, because print () triggers a
243   vertical alignment if it is cross-staff.
244 */
245 MAKE_SCHEME_CALLBACK (Arpeggio, width, 1);
246 SCM
247 Arpeggio::width (SCM smob)
248 {
249   Grob *me = unsmob_grob (smob);
250   return ly_interval2scm (get_squiggle (me).extent (X_AXIS));
251 }
252
253 MAKE_SCHEME_CALLBACK (Arpeggio, pure_height, 3);
254 SCM
255 Arpeggio::pure_height (SCM smob, SCM, SCM)
256 {
257   Grob *me = unsmob_grob (smob);
258   if (to_boolean (me->get_property ("cross-staff")))
259     return ly_interval2scm (Interval ());
260
261   return Grob::stencil_height (smob);
262 }
263
264 ADD_INTERFACE (Arpeggio,
265                "Functions and settings for drawing an arpeggio symbol.",
266
267                /* properties */
268                "arpeggio-direction "
269                "minimum-brace-height "
270                "positions "
271                "script-priority " // TODO: make around-note-interface
272                "stems "
273                "dash-definition " // TODO: make apply to non-slur arpeggios
274               );
275