]> git.donarmstrong.com Git - lilypond.git/blob - lily/ledger-line-spanner.cc
Merge branch 'master' of /home/jcharles/GIT/Lily/. into translation
[lilypond.git] / lily / ledger-line-spanner.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2004--2015 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 <map>
21 using namespace std;
22
23 #include "note-head.hh"
24 #include "staff-symbol-referencer.hh"
25 #include "staff-symbol.hh"
26 #include "lookup.hh"
27 #include "spanner.hh"
28 #include "pointer-group-interface.hh"
29 #include "paper-column.hh"
30
31 struct Ledger_line_spanner
32 {
33   DECLARE_SCHEME_CALLBACK (print, (SCM));
34   DECLARE_SCHEME_CALLBACK (set_spacing_rods, (SCM));
35 };
36
37 static void
38 set_rods (Drul_array<Interval> const &current_extents,
39           Drul_array<Interval> const &previous_extents,
40           Item *current_column,
41           Item *previous_column,
42           Real min_length)
43 {
44   for (UP_and_DOWN (d))
45     {
46       if (!current_extents[d].is_empty ()
47           && !previous_extents[d].is_empty ())
48         {
49           Rod rod;
50           rod.distance_ = 2 * min_length
51                           /*
52                             we go from right to left.
53                           */
54                           - previous_extents[d][LEFT]
55                           + current_extents[d][RIGHT];
56
57           rod.item_drul_[LEFT] = current_column;
58           rod.item_drul_[RIGHT] = previous_column;
59           rod.add_to_cols ();
60         }
61     }
62 }
63
64 MAKE_SCHEME_CALLBACK (Ledger_line_spanner, set_spacing_rods, 1);
65 SCM
66 Ledger_line_spanner::set_spacing_rods (SCM smob)
67 {
68   Spanner *me = unsmob<Spanner> (smob);
69
70   // find size of note heads.
71   Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
72   if (!staff)
73     {
74       me->suicide ();
75       return SCM_EOL;
76     }
77
78   Real min_length_fraction
79     = robust_scm2double (me->get_property ("minimum-length-fraction"), 0.15);
80
81   Drul_array<Interval> current_extents;
82   Drul_array<Interval> previous_extents;
83   Real current_head_width = 0.0;
84   Item *previous_column = 0;
85   Item *current_column = 0;
86
87   Real halfspace = Staff_symbol::staff_space (staff) / 2;
88
89   Interval staff_extent = staff->extent (staff, Y_AXIS);
90   staff_extent *= 1 / halfspace;
91
92   /*
93     Run through heads using a loop. Since Ledger_line_spanner can
94     contain a lot of noteheads, superlinear performance is too slow.
95   */
96   extract_item_set (me, "note-heads", heads);
97   for (vsize i = heads.size (); i--;)
98     {
99       Item *h = heads[i];
100
101       int pos = Staff_symbol_referencer::get_rounded_position (h);
102       if  (Staff_symbol::ledger_positions (staff, pos).empty ())
103         continue;
104
105       /* Ambitus heads can appear out-of-order in heads[],
106        * but as part of prefatory matter, they need no rods */
107       if (h->internal_has_interface (ly_symbol2scm ("ambitus-interface")))
108         continue;
109
110       Item *column = h->get_column ();
111       if (current_column != column)
112         {
113           set_rods (current_extents, previous_extents,
114                     current_column, previous_column,
115                     current_head_width * min_length_fraction);
116
117           previous_column = current_column;
118           current_column = column;
119           previous_extents = current_extents;
120
121           current_extents[DOWN].set_empty ();
122           current_extents[UP].set_empty ();
123           current_head_width = 0.0;
124         }
125
126       Interval head_extent = h->extent (column, X_AXIS);
127       Direction vdir = Direction (sign (pos));
128       if (!vdir)
129         continue;
130
131       current_extents[vdir].unite (head_extent);
132       current_head_width = max (current_head_width, head_extent.length ());
133     }
134
135   if (previous_column && current_column)
136     set_rods (current_extents, previous_extents,
137               current_column, previous_column,
138               current_head_width * min_length_fraction);
139
140   return SCM_UNSPECIFIED;
141 }
142
143 struct Head_data
144 {
145   int position_;
146   vector<Real> ledger_positions_;
147   Interval head_extent_;
148   Interval ledger_extent_;
149   Interval accidental_extent_;
150   Head_data ()
151   {
152     head_extent_.set_empty ();
153     ledger_extent_.set_empty ();
154     accidental_extent_.set_empty ();
155   }
156 };
157
158 struct Ledger_request
159 {
160   Interval max_ledger_extent_;
161   Interval max_head_extent_;
162   int max_position_;
163   vector <Head_data> heads_;
164   map <Real, Interval> ledger_extents_;
165   Ledger_request ()
166   {
167     max_ledger_extent_.set_empty ();
168     max_head_extent_.set_empty ();
169     max_position_ = 0;
170   }
171 };
172
173 typedef map < int, Drul_array<Ledger_request> > Ledger_requests;
174
175 /*
176   TODO: ledger share a lot of info. Lots of room to optimize away
177   common use of objects/variables.
178 */
179 MAKE_SCHEME_CALLBACK (Ledger_line_spanner, print, 1);
180 SCM
181 Ledger_line_spanner::print (SCM smob)
182 {
183   Spanner *me = unsmob<Spanner> (smob);
184
185   // Generate ledger requests from note head properties, etc.
186   extract_grob_set (me, "note-heads", heads);
187
188   if (heads.empty ())
189     return SCM_EOL;
190
191   Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
192   if (!staff)
193     return SCM_EOL;
194
195   Real halfspace = Staff_symbol::staff_space (staff) / 2;
196
197   Interval staff_extent = staff->extent (staff, Y_AXIS);
198   staff_extent *= 1 / halfspace;
199
200   Real length_fraction
201     = robust_scm2double (me->get_property ("length-fraction"), 0.25);
202
203   Grob *common_x = common_refpoint_of_array (heads, me, X_AXIS);
204   for (vsize i = heads.size (); i--;)
205     {
206       if (Grob *g = unsmob<Grob> (heads[i]->get_object ("accidental-grob")))
207         common_x = common_x->common_refpoint (g, X_AXIS);
208     }
209
210   Ledger_requests reqs;
211   for (vsize i = heads.size (); i--;)
212     {
213       Item *h = dynamic_cast<Item *> (heads[i]);
214
215       int pos = Staff_symbol_referencer::get_rounded_position (h);
216       vector<Real> ledger_positions =
217         Staff_symbol::ledger_positions (staff, pos);
218
219       // We work with all notes that produce ledgers and any notes that
220       // fall outside the staff that do not produce ledgers, such as
221       // notes in the first space just beyond the staff.
222       if (ledger_positions.size () != 0 || !staff_extent.contains (pos))
223         {
224           Interval head_extent = h->extent (common_x, X_AXIS);
225           Interval ledger_extent = head_extent;
226           ledger_extent.widen (length_fraction * head_extent.length ());
227
228           Direction vdir = Direction (sign (pos != 0 ? pos : 1));
229           int rank = h->get_column ()->get_rank ();
230
231           reqs[rank][vdir].max_ledger_extent_.unite (ledger_extent);
232           reqs[rank][vdir].max_head_extent_.unite (head_extent);
233           reqs[rank][vdir].max_position_
234             = vdir * max (vdir * reqs[rank][vdir].max_position_,
235                           vdir * pos);
236           Head_data hd;
237           hd.position_ = pos;
238           hd.ledger_positions_ = ledger_positions;
239           hd.ledger_extent_ = ledger_extent;
240           hd.head_extent_ = head_extent;
241           if (Grob *g = unsmob<Grob> (h->get_object ("accidental-grob")))
242             hd.accidental_extent_ = g->extent (common_x, X_AXIS);
243           reqs[rank][vdir].heads_.push_back(hd);
244         }
245     }
246
247   if (reqs.size () == 0)
248     return SCM_EOL;
249
250   // Iterate through ledger requests and when ledger lines will be
251   // too close together horizontally, shorten max_ledger_extent to
252   // produce more space between them.
253   Real gap = robust_scm2double (me->get_property ("gap"), 0.1);
254   Ledger_requests::iterator last (reqs.end ());
255   for (Ledger_requests::iterator i (reqs.begin ());
256        i != reqs.end (); last = i++)
257     {
258       if (last == reqs.end ())
259         continue;
260
261       for (DOWN_and_UP (d))
262         {
263           // Some rank--> vdir--> reqs will be 'empty' because notes
264           // will not be above AND below the staff for a given rank.
265           if (!staff_extent.contains (last->second[d].max_position_)
266               && !staff_extent.contains (i->second[d].max_position_))
267             {
268               // Midpoint between the furthest bounds of the two heads.
269               Real center
270                 = (last->second[d].max_head_extent_[RIGHT]
271                    + i->second[d].max_head_extent_[LEFT]) / 2;
272
273               // Do both reqs have notes further than the first space
274               // beyond the staff?
275               // (due tilt of quarter note-heads)
276               /* FIXME */
277               bool both
278                 = (!staff_extent.contains (last->second[d].max_position_
279                                            - sign (last->second[d].max_position_))
280                    && !staff_extent.contains (i->second[d].max_position_
281                                               - sign (i->second[d].max_position_)));
282
283               for (LEFT_and_RIGHT (which))
284                 {
285                   Ledger_request &lr = ((which == LEFT) ? * last : *i).second[d];
286
287                   Real limit = (center + (both ? which * gap / 2 : 0));
288                   lr.max_ledger_extent_.at (-which)
289                     = which * max (which * lr.max_ledger_extent_[-which],
290                                    which * limit);
291                 }
292             }
293         }
294     }
295
296   // Iterate through ledger requests and the data they have about each
297   // note head to generate the final extents for all ledger lines.
298   // Note heads that are different widths produce different ledger
299   // extents and these are merged so the widest extent prevails
300   // (the union of the intervals) for each ledger line.
301   for (Ledger_requests::iterator i (reqs.begin ());
302        i != reqs.end (); i++)
303     {
304       for (DOWN_and_UP (d))
305         {
306           Ledger_request &lr = i->second[d];
307           for (vsize h = 0; h < lr.heads_.size (); h++)
308             {
309               vector<Real> &ledger_posns = lr.heads_[h].ledger_positions_;
310               Interval &ledger_size = lr.heads_[h].ledger_extent_;
311               Interval &head_size = lr.heads_[h].head_extent_;
312               Interval &acc_extent = lr.heads_[h].accidental_extent_;
313
314               // Limit ledger extents to a maximum to preserve space
315               // between ledgers when note heads get close.
316               if (!lr.max_ledger_extent_.is_empty ())
317                 ledger_size.intersect (lr.max_ledger_extent_);
318
319               // Iterate through the ledgers for a given note head.
320               for (vsize l = 0; l < ledger_posns.size (); l++)
321                 {
322                   Real lpos = ledger_posns[l];
323                   Interval x_extent = ledger_size;
324
325                   // Notes with accidental signs get shorter ledgers.
326                   // (Only happens for the furthest note in the column.)
327                   if (l == 0 && !acc_extent.is_empty ())
328                     {
329                       Real dist
330                         = linear_combination (Drul_array<Real> (acc_extent[RIGHT],
331                                                                 head_size[LEFT]),
332                                               0.0);
333
334                       Real left_shorten = max (-ledger_size[LEFT] + dist, 0.0);
335                       x_extent[LEFT] += left_shorten;
336                       /*
337                         TODO: shorten 2 ledger lines for the case
338                         natural + downstem.
339                       */
340                     }
341                   if (lr.ledger_extents_.find (lpos) == lr.ledger_extents_.end ())
342                     lr.ledger_extents_[lpos] = x_extent;
343                   else
344                     lr.ledger_extents_[lpos].unite (x_extent);
345                 }
346             }
347         }
348     }
349
350   // Create the stencil for the ledger line spanner by iterating
351   // through the ledger requests and their data on ledger extents.
352   Stencil ledgers;
353   Real ledgerlinethickness
354     = Staff_symbol::get_ledger_line_thickness (staff);
355
356   for (Ledger_requests::iterator i (reqs.begin ());
357        i != reqs.end (); i++)
358     {
359       for (DOWN_and_UP (d))
360         {
361           map<Real, Interval> &lex = i->second[d].ledger_extents_;
362           for (map<Real, Interval>::iterator k = lex.begin ();
363                k != lex.end (); k++)
364             {
365               Real blotdiameter = ledgerlinethickness;
366               Real lpos = k->first;
367               Interval x_extent = k->second;
368               Interval y_extent
369                 = Interval (-0.5 * (ledgerlinethickness),
370                             +0.5 * (ledgerlinethickness));
371               Stencil ledger_line
372                 = Lookup::round_filled_box (Box (x_extent, y_extent), blotdiameter);
373
374               ledger_line.translate_axis ( lpos * halfspace, Y_AXIS);
375               ledgers.add_stencil (ledger_line);
376             }
377         }
378     }
379
380   ledgers.translate_axis (-me->relative_coordinate (common_x, X_AXIS),
381                           X_AXIS);
382
383   return ledgers.smobbed_copy ();
384 }
385
386 ADD_INTERFACE (Ledger_line_spanner,
387                "This spanner draws the ledger lines of a staff.  This is a"
388                " separate grob because it has to process all potential"
389                " collisions between all note heads.  The thickness of ledger"
390                " lines is controlled by the @code{ledger-line-thickness}"
391                " property of the @ref{StaffSymbol} grob.",
392
393                /* properties */
394                "gap "
395                "length-fraction "
396                "minimum-length-fraction "
397                "note-heads "
398               );
399
400 struct Ledgered_interface
401 {
402 };
403
404 ADD_INTERFACE (Ledgered_interface,
405                "Objects that need ledger lines, typically note heads.  See"
406                " also @ref{ledger-line-spanner-interface}.",
407
408                /* properties */
409                "no-ledgers "
410               );