]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
Web-ja: update introduction
[lilypond.git] / lily / note-spacing.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2001--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 "note-spacing.hh"
21
22 #include "accidental-placement.hh"
23 #include "bar-line.hh"
24 #include "directional-element-interface.hh"
25 #include "grob-array.hh"
26 #include "moment.hh"
27 #include "note-column.hh"
28 #include "output-def.hh"
29 #include "paper-column.hh"
30 #include "pointer-group-interface.hh"
31 #include "separation-item.hh"
32 #include "spacing-interface.hh"
33 #include "staff-spacing.hh"
34 #include "staff-symbol-referencer.hh"
35 #include "stem.hh"
36 #include "warn.hh"
37
38 /*
39   Adjust the ideal and minimum distance between note columns,
40   based on the notehead size, skylines, and optical illusions.
41 */
42 Spring
43 Note_spacing::get_spacing (Grob *me, Item *right_col,
44                            Spring base, Real increment)
45 {
46   vector<Item *> note_columns = Spacing_interface::left_note_columns (me);
47   Real left_head_end = 0;
48
49   for (vsize i = 0; i < note_columns.size (); i++)
50     {
51       SCM r = note_columns[i]->get_object ("rest");
52       Grob *g = unsmob<Grob> (r);
53       Grob *col = note_columns[i]->get_column ();
54
55       if (!g)
56         g = Note_column::first_head (note_columns[i]);
57
58       /*
59         Ugh. If Stem is switched off, we don't know what the
60         first note head will be.
61       */
62       if (g)
63         {
64           if (g->common_refpoint (col, X_AXIS) != col)
65             programming_error ("Note_spacing::get_spacing (): Common refpoint incorrect");
66           else
67             left_head_end = g->extent (col, X_AXIS)[RIGHT];
68         }
69     }
70
71   /*
72     The main factor that determines the amount of space is the width of the
73     note head (or the rest). For example, a quarter rest gets almost 0.5 ss
74     less horizontal space than a note.
75   */
76   Real ideal = base.distance () - increment + left_head_end;
77   Drul_array<Skyline> skys = Spacing_interface::skylines (me, right_col);
78   Real distance = skys[LEFT].distance (skys[RIGHT], robust_scm2double (right_col->get_property ("skyline-vertical-padding"), 0.0));
79   Real min_dist = max (0.0, distance);
80   base.set_min_distance (min_dist);
81
82   /* If we have a NonMusical column on the right, we measure the ideal distance
83      to the bar-line (if present), not the start of the column. */
84   if (!Paper_column::is_musical (right_col)
85       && !skys[RIGHT].is_empty ()
86       && to_boolean (me->get_property ("space-to-barline")))
87     {
88       Grob *bar = Pointer_group_interface::find_grob (right_col,
89                                                       ly_symbol2scm ("elements"),
90                                                       Bar_line::non_empty_barline);
91
92       if (bar)
93         ideal -= bar->extent (right_col, X_AXIS)[LEFT];
94       else
95         {
96           /* Measure ideal distance to the right side of the NonMusical column
97              but keep at least half the gap we would have had to a note */
98           Real min_desired_space = (ideal + min_dist) / 2.0;
99           ideal -= right_col->extent (right_col, X_AXIS)[RIGHT];
100           ideal = max (ideal, min_desired_space);
101         }
102     }
103
104   stem_dir_correction (me, right_col, increment, &ideal);
105
106   base.set_distance (max (0.0, ideal));
107   return base;
108 }
109
110 static Real
111 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
112 {
113   Real note_head_width = increment;
114   Grob *head = right_stem ? Stem::support_head (right_stem) : 0;
115   Grob *rcolumn = dynamic_cast<Item *> (head)->get_column ();
116
117   Interval head_extent;
118   if (head)
119     {
120       head_extent = head->extent (rcolumn, X_AXIS);
121
122       if (!head_extent.is_empty ())
123         note_head_width = head_extent[RIGHT];
124
125       note_head_width -= Stem::thickness (right_stem);
126     }
127
128   return -note_head_width * get_grob_direction (right_stem)
129          * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
130 }
131
132 static Real
133 different_directions_correction (Grob *note_spacing,
134                                  Drul_array<Interval> stem_posns,
135                                  Direction left_stem_dir)
136 {
137   Real ret = 0.0;
138   Interval intersect = stem_posns[LEFT];
139   intersect.intersect (stem_posns[RIGHT]);
140
141   if (!intersect.is_empty ())
142     {
143       ret = abs (intersect.length ());
144
145       /*
146         Ugh. 7 is hardcoded.
147       */
148       ret = min (ret / 7, 1.0)
149             * left_stem_dir
150             * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
151     }
152   return ret;
153 }
154
155 static Real
156 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
157 {
158   /*
159     Correct for the following situation:
160
161     X      X
162     |      |
163     |      |
164     |   X  |
165     |  |   |
166     ========
167
168     ^ move the center one to the left.
169
170
171     this effect seems to be much more subtle than the
172     stem-direction stuff (why?), and also does not scale with the
173     difference in stem length.
174
175   */
176
177   Interval hp = head_posns[LEFT];
178   hp.intersect (head_posns[RIGHT]);
179   if (!hp.is_empty ())
180     return 0;
181
182   Direction lowest
183     = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
184
185   Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
186   Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
187
188   return (delta > 1) ? -lowest * corr : 0;
189 }
190
191 /*
192   Correct for optical illusions. See [Wanske] p. 138. The combination
193   up-stem + down-stem should get extra space, the combination
194   down-stem + up-stem less.
195
196   TODO: have to check whether the stems are in the same staff.
197 */
198 void
199 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
200                                    Real increment,
201                                    Real *space)
202 {
203   Drul_array<Direction> stem_dirs (CENTER, CENTER);
204   Drul_array<Interval> stem_posns;
205   Drul_array<Interval> head_posns;
206   Drul_array<SCM> props (me->get_object ("left-items"),
207                          me->get_object ("right-items"));
208
209   Drul_array<Spanner *> beams_drul (0, 0);
210   Drul_array<Grob *> stems_drul (0, 0);
211
212   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
213   Interval intersect;
214   Interval bar_xextent;
215   Interval bar_yextent;
216
217   bool acc_right = false;
218
219   Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT,
220                                                               rcolumn->break_status_dir (),
221                                                               &bar_xextent);
222   if (bar && dynamic_cast<Item *> (bar)->get_column () == rcolumn)
223     bar_yextent = Staff_spacing::bar_y_positions (bar);
224
225   for (LEFT_and_RIGHT (d))
226     {
227       vector<Grob *> const &items (ly_scm2link_array (props [d]));
228       for (vsize i = 0; i < items.size (); i++)
229         {
230           Item *it = dynamic_cast<Item *> (items[i]);
231           if (!has_interface<Note_column> (it))
232             continue;
233           if (d == RIGHT && it->get_column () != rcolumn)
234             continue;
235
236           /*
237             Find accidentals which are sticking out of the right side.
238           */
239           if (d == RIGHT)
240             acc_right = acc_right || Note_column::accidentals (it);
241
242           Grob *stem = Note_column::get_stem (it);
243
244           if (!stem || !stem->is_live () || Stem::is_invisible (stem))
245             return;
246
247           stems_drul[d] = stem;
248           beams_drul[d] = Stem::get_beam (stem);
249
250           Direction stem_dir = get_grob_direction (stem);
251           if (stem_dirs[d] && stem_dirs[d] != stem_dir)
252             return;
253
254           stem_dirs[d] = stem_dir;
255
256           /*
257             Correction doesn't seem appropriate  when there is a large flag
258             hanging from the note.
259           */
260           if (d == LEFT
261               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
262             return;
263
264           Interval hp = Stem::head_positions (stem);
265           if (!hp.is_empty ())
266             {
267               Real ss = Staff_symbol_referencer::staff_space (stem);
268               stem_posns[d] = stem->pure_y_extent (stem, 0, INT_MAX) * (2 / ss);
269               head_posns[d].unite (hp);
270             }
271         }
272     }
273
274   Real correction = 0.0;
275
276   if (!bar_yextent.is_empty ())
277     {
278       stem_dirs[RIGHT] = -stem_dirs[LEFT];
279       stem_posns[RIGHT] = bar_yextent;
280       stem_posns[RIGHT] *= 2;
281     }
282
283   if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
284     {
285       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
286         {
287           correction = knee_correction (me, stems_drul[RIGHT], increment);
288         }
289       else
290         {
291           correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
292
293           if (!bar_yextent.is_empty ())
294             correction *= 0.5;
295         }
296     }
297   /*
298     Only apply same direction correction if there are no
299     accidentals sticking out of the right hand side.
300   */
301   else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1
302            && !acc_right)
303     correction = same_direction_correction (me, head_posns);
304
305   *space += correction;
306
307   /* there used to be a correction for bar_xextent () here, but
308      it's unclear what that was good for ?
309   */
310 }
311
312 ADD_INTERFACE (Note_spacing,
313                "This object calculates spacing wishes for individual voices.",
314
315                /* properties */
316                "knee-spacing-correction "
317                "left-items "
318                "right-items "
319                "same-direction-correction "
320                "stem-spacing-correction "
321                "space-to-barline "
322               );
323