]> git.donarmstrong.com Git - lilypond.git/blob - lily/ledger-line-spanner.cc
Issue 4828/1: Allow override of NoteHead.ledger-positions
[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       int pos = Staff_symbol_referencer::get_rounded_position (h);
215       vector<Real> ledger_positions =
216         Staff_symbol::ledger_positions (staff, pos, h);
217
218       // We work with all notes that produce ledgers and any notes that
219       // fall outside the staff that do not produce ledgers, such as
220       // notes in the first space just beyond the staff.
221       if (ledger_positions.size () != 0 || !staff_extent.contains (pos))
222         {
223           Interval head_extent = h->extent (common_x, X_AXIS);
224           Interval ledger_extent = head_extent;
225           ledger_extent.widen (length_fraction * head_extent.length ());
226
227           Direction vdir = Direction (sign (pos != 0 ? pos : 1));
228           int rank = h->get_column ()->get_rank ();
229
230           reqs[rank][vdir].max_ledger_extent_.unite (ledger_extent);
231           reqs[rank][vdir].max_head_extent_.unite (head_extent);
232           reqs[rank][vdir].max_position_
233             = vdir * max (vdir * reqs[rank][vdir].max_position_,
234                           vdir * pos);
235           Head_data hd;
236           hd.position_ = pos;
237           hd.ledger_positions_ = ledger_positions;
238           hd.ledger_extent_ = ledger_extent;
239           hd.head_extent_ = head_extent;
240           if (Grob *g = unsmob<Grob> (h->get_object ("accidental-grob")))
241             hd.accidental_extent_ = g->extent (common_x, X_AXIS);
242           reqs[rank][vdir].heads_.push_back(hd);
243         }
244     }
245
246   if (reqs.size () == 0)
247     return SCM_EOL;
248
249   // Iterate through ledger requests and when ledger lines will be
250   // too close together horizontally, shorten max_ledger_extent to
251   // produce more space between them.
252   Real gap = robust_scm2double (me->get_property ("gap"), 0.1);
253   Ledger_requests::iterator last (reqs.end ());
254   for (Ledger_requests::iterator i (reqs.begin ());
255        i != reqs.end (); last = i++)
256     {
257       if (last == reqs.end ())
258         continue;
259
260       for (DOWN_and_UP (d))
261         {
262           // Some rank--> vdir--> reqs will be 'empty' because notes
263           // will not be above AND below the staff for a given rank.
264           if (!staff_extent.contains (last->second[d].max_position_)
265               && !staff_extent.contains (i->second[d].max_position_))
266             {
267               // Midpoint between the furthest bounds of the two heads.
268               Real center
269                 = (last->second[d].max_head_extent_[RIGHT]
270                    + i->second[d].max_head_extent_[LEFT]) / 2;
271
272               // Do both reqs have notes further than the first space
273               // beyond the staff?
274               // (due tilt of quarter note-heads)
275               /* FIXME */
276               bool both
277                 = (!staff_extent.contains (last->second[d].max_position_
278                                            - sign (last->second[d].max_position_))
279                    && !staff_extent.contains (i->second[d].max_position_
280                                               - sign (i->second[d].max_position_)));
281
282               for (LEFT_and_RIGHT (which))
283                 {
284                   Ledger_request &lr = ((which == LEFT) ? * last : *i).second[d];
285
286                   Real limit = (center + (both ? which * gap / 2 : 0));
287                   lr.max_ledger_extent_.at (-which)
288                     = which * max (which * lr.max_ledger_extent_[-which],
289                                    which * limit);
290                 }
291             }
292         }
293     }
294
295   // Iterate through ledger requests and the data they have about each
296   // note head to generate the final extents for all ledger lines.
297   // Note heads that are different widths produce different ledger
298   // extents and these are merged so the widest extent prevails
299   // (the union of the intervals) for each ledger line.
300   for (Ledger_requests::iterator i (reqs.begin ());
301        i != reqs.end (); i++)
302     {
303       for (DOWN_and_UP (d))
304         {
305           Ledger_request &lr = i->second[d];
306           for (vsize h = 0; h < lr.heads_.size (); h++)
307             {
308               vector<Real> &ledger_posns = lr.heads_[h].ledger_positions_;
309               Interval &ledger_size = lr.heads_[h].ledger_extent_;
310               Interval &head_size = lr.heads_[h].head_extent_;
311               Interval &acc_extent = lr.heads_[h].accidental_extent_;
312
313               // Limit ledger extents to a maximum to preserve space
314               // between ledgers when note heads get close.
315               if (!lr.max_ledger_extent_.is_empty ())
316                 ledger_size.intersect (lr.max_ledger_extent_);
317
318               // Iterate through the ledgers for a given note head.
319               for (vsize l = 0; l < ledger_posns.size (); l++)
320                 {
321                   Real lpos = ledger_posns[l];
322                   Interval x_extent = ledger_size;
323
324                   // Notes with accidental signs get shorter ledgers.
325                   // (Only happens for the furthest note in the column.)
326                   if (l == 0 && !acc_extent.is_empty ())
327                     {
328                       Real dist
329                         = linear_combination (Drul_array<Real> (acc_extent[RIGHT],
330                                                                 head_size[LEFT]),
331                                               0.0);
332
333                       Real left_shorten = max (-ledger_size[LEFT] + dist, 0.0);
334                       x_extent[LEFT] += left_shorten;
335                       /*
336                         TODO: shorten 2 ledger lines for the case
337                         natural + downstem.
338                       */
339                     }
340                   if (lr.ledger_extents_.find (lpos) == lr.ledger_extents_.end ())
341                     lr.ledger_extents_[lpos] = x_extent;
342                   else
343                     lr.ledger_extents_[lpos].unite (x_extent);
344                 }
345             }
346         }
347     }
348
349   // Create the stencil for the ledger line spanner by iterating
350   // through the ledger requests and their data on ledger extents.
351   Stencil ledgers;
352   Real ledgerlinethickness
353     = Staff_symbol::get_ledger_line_thickness (staff);
354
355   for (Ledger_requests::iterator i (reqs.begin ());
356        i != reqs.end (); i++)
357     {
358       for (DOWN_and_UP (d))
359         {
360           map<Real, Interval> &lex = i->second[d].ledger_extents_;
361           for (map<Real, Interval>::iterator k = lex.begin ();
362                k != lex.end (); k++)
363             {
364               Real blotdiameter = ledgerlinethickness;
365               Real lpos = k->first;
366               Interval x_extent = k->second;
367               Interval y_extent
368                 = Interval (-0.5 * (ledgerlinethickness),
369                             +0.5 * (ledgerlinethickness));
370               Stencil ledger_line
371                 = Lookup::round_filled_box (Box (x_extent, y_extent), blotdiameter);
372
373               ledger_line.translate_axis ( lpos * halfspace, Y_AXIS);
374               ledgers.add_stencil (ledger_line);
375             }
376         }
377     }
378
379   ledgers.translate_axis (-me->relative_coordinate (common_x, X_AXIS),
380                           X_AXIS);
381
382   return ledgers.smobbed_copy ();
383 }
384
385 ADD_INTERFACE (Ledger_line_spanner,
386                "This spanner draws the ledger lines of a staff.  This is a"
387                " separate grob because it has to process all potential"
388                " collisions between all note heads.  The thickness of ledger"
389                " lines is controlled by the @code{ledger-line-thickness}"
390                " property of the @ref{StaffSymbol} grob.",
391
392                /* properties */
393                "gap "
394                "length-fraction "
395                "minimum-length-fraction "
396                "note-heads "
397               );
398
399 struct Ledgered_interface
400 {
401 };
402
403 ADD_INTERFACE (Ledgered_interface,
404                "Objects that need ledger lines, typically note heads.  See"
405                " also @ref{ledger-line-spanner-interface}.",
406
407                /* properties */
408                "no-ledgers "
409               );