2 spacing-spanner.cc -- implement Spacing_spanner
4 source file of the GNU LilyPond music typesetter
6 (c) 1999--2001 Han-Wen Nienhuys <hanwen@cs.uu.nl>
10 #include "new-spacing-spanner.hh"
11 #include "paper-column.hh"
12 #include "dimensions.hh"
13 #include "paper-def.hh"
15 #include "paper-score.hh"
16 #include "line-of-score.hh"
18 #include "separation-item.hh"
22 New_spacing_spanner::set_interface (Grob*me)
24 me->set_extent_callback (SCM_EOL, X_AXIS);
25 me->set_extent_callback (SCM_EOL, Y_AXIS) ;
30 The algorithm is partly taken from :
32 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
33 OSU-CISRC-10/87-TR35, Department of Computer and Information
34 Science, The Ohio State University, 1987.
42 New_spacing_spanner::do_measure (Grob*me, Link_array<Grob> const & cols)
48 space as if this duration is present.
50 Moment base_shortest_duration = *unsmob_moment (me->get_grob_property ("maximum-duration-for-spacing"));
51 shortest.set_infinite (1);
54 for (int i =0 ; i < cols.size (); i++)
56 if (Paper_column::musical_b (cols[i]))
58 Moment *when = unsmob_moment (cols[i]->get_grob_property ("when"));
61 ignore grace notes for shortest notes.
63 if (when && when->grace_part_)
66 SCM st = cols[i]->get_grob_property ("shortest-starter-duration");
67 Moment this_shortest = *unsmob_moment (st);
68 shortest = shortest <? this_shortest;
69 if (!mean_shortest.main_part_.infty_b ())
72 mean_shortest += this_shortest;
77 Array<Spring> springs;
82 for (int i= 0; i < cols.size () - 1; i++)
84 Item * l = dynamic_cast<Item*> (cols[i]);
86 if (!Paper_column::musical_b (l))
94 for (; j < cols.size () -1 ; j++)
95 if (Paper_column::musical_b (cols[j]))
98 Item * r = dynamic_cast<Item*> (cols[j]);
99 Paper_column * lc = dynamic_cast<Paper_column*> (l);
100 Paper_column *rc = dynamic_cast<Paper_column*> (r);
104 Real note_space = note_spacing (me,lc, rc, shortest <? base_shortest_duration);
105 Real hinterfleisch = note_space;
106 Real headwid = gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
108 for (SCM s = lc->get_grob_property ("spacing-sequence"); gh_pair_p (s); s = gh_cdr (s))
110 Grob *lm = unsmob_grob (gh_caar (s));
111 Grob *rm = unsmob_grob (gh_cdar (s));
113 // TODO; configgable.
114 hinterfleisch += -headwid + Separation_item::my_width (lm)[RIGHT] -
115 0.5 * Separation_item::my_width (rm)[LEFT];
122 // if (delta_t > Moment (1,32))
123 hinterfleisch += stem_dir_correction (me, l, r);
126 Real stretch_distance = note_space - headwid;
128 s.distance_f_ = hinterfleisch;
129 s.strength_f_ = 1 / stretch_distance;
131 s.item_l_drul_[LEFT] = l;
132 s.item_l_drul_[RIGHT] = r;
135 if (r->find_prebroken_piece (LEFT))
137 s.item_l_drul_[RIGHT] = r->find_prebroken_piece(LEFT);
142 Item * l = dynamic_cast<Item*> (cols[0])->find_prebroken_piece (RIGHT);
145 TODO read different spacing hints from break-alignment.
149 Real break_dist = 0.0;
150 SCM espace = l->get_grob_property ("extra-space");
151 if (gh_pair_p (espace))
152 break_dist += gh_scm2double (gh_cdr (espace));
157 Real break_stretch = 0.0;
159 // todo: naming of "distance"
160 espace = l->get_grob_property ("stretch-distance");
161 if (gh_pair_p (espace))
162 break_stretch += gh_scm2double (gh_cdr (espace));
167 s.distance_f_ = break_dist;
168 s.strength_f_ = 1/break_stretch;
169 s.item_l_drul_[LEFT] = l;
170 s.item_l_drul_[RIGHT] = first_col;
174 s.item_l_drul_[LEFT] = dynamic_cast<Item*>(cols[0]);
179 Look at COLS, searching for columns that have 'regular-distance-to
180 set. A sequence of columns that have this property set should have
181 an equal distance (an equispaced run). Extract the projected
182 distance from SPRINGS, and scale SPRINGS for the equispaced run, to the
183 widest space necessary.
188 -- inefficient code; maybe it is easier to twiddle with the springs
189 after they've become grob properties (ie. have their
190 minimum-distances set)
192 -- does not adjust strength field of the springs very well: result
193 awkward spacing at the start of a line. (?)
195 -- will be confused when there are multiple equispaced runs in a measure.
197 -- dealing with springs for line breaks is a little tricky; in any
198 case, we will only space per measure.
200 -- we scale to actual distances, not to optical effects. Eg. if the
201 equispaced run contains optical corrections, then the scaling will
204 -- Regular_spacing_engraver doesn't mark the first column of the
205 next bar, making the space before a barline too short, in this case
209 x(8th) x(8th) <- equispaced run.
214 New_spacing_spanner::stretch_to_regularity (Grob *me,
215 Array<Spring> * springs,
216 Link_array<Grob> const & cols)
219 Find the starting column of the run. REGULAR-DISTANCE-TO points
220 back to a previous column, so we look ahead to find a column
221 pointing back to the first one.
224 Grob * first_regular_spaced_col = 0;
225 for (int i = 0 ; i < cols.size () && !first_regular_spaced_col; i++)
227 SCM rdt = cols[i]->get_grob_property ("regular-distance-to");
228 if (cols.find_l (unsmob_grob (rdt)))
229 first_regular_spaced_col = unsmob_grob (rdt);
231 for (int i = springs->size (); i-- ;)
232 springs->elem (i).set_to_cols ();
235 for (i = 0; i < springs->size ()
236 && springs->elem (i).item_l_drul_[RIGHT] != first_regular_spaced_col;
241 if (i==springs->size ())
246 Grob *last_col = first_regular_spaced_col;
247 Grob *last_regular_spaced_col = first_regular_spaced_col;
251 find the max distance for this run.
253 for (int j = i; j < springs->size (); j++)
255 Spring *s = &(springs->elem_ref (j));
256 if (s->item_l_drul_[LEFT] != last_col)
259 dist += s->distance_f_;
261 last_col = s->item_l_drul_[RIGHT];
262 SCM rdt = last_col->get_grob_property ("regular-distance-to");
263 if (unsmob_grob (rdt) == last_regular_spaced_col)
265 maxdist = maxdist >? dist;
267 last_regular_spaced_col = last_col;
276 last_col = first_regular_spaced_col;
277 last_regular_spaced_col = first_regular_spaced_col;
278 for (int j = i; j < springs->size (); j++)
280 Spring *s = &springs->elem_ref (j);
281 if (s->item_l_drul_[LEFT] != last_col)
283 dist += s->distance_f_;
285 last_col = s->item_l_drul_[RIGHT];
286 SCM rdt = last_col->get_grob_property ("regular-distance-to");
287 if (unsmob_grob (rdt) == last_regular_spaced_col)
290 springs->elem_ref (i).distance_f_ *= maxdist / dist;
291 springs->elem_ref (i).strength_f_ *= dist / maxdist;
293 last_regular_spaced_col = last_col;
300 Do something if breakable column has no spacing hints set.
303 New_spacing_spanner::default_bar_spacing (Grob*me, Grob *lc, Grob *rc,
306 Real symbol_distance = lc->extent (lc,X_AXIS)[RIGHT] ;
307 Real durational_distance = 0;
308 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
311 ugh should use shortest_playing distance
313 if (delta_t.to_bool ())
315 durational_distance = get_duration_space (me, delta_t, shortest);
318 return symbol_distance >? durational_distance;
323 Get the measure wide ant for arithmetic spacing.
326 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
327 OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
328 The Ohio State University, 1987.
332 New_spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest)
334 Real log = log_2 (shortest.main_part_);
335 Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
338 Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
340 return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
345 New_spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
348 Moment shortest_playing_len = 0;
349 SCM s = lc->get_grob_property ("shortest-playing-duration");
351 // SCM s = lc->get_grob_property ("mean-playing-duration");
352 if (unsmob_moment (s))
353 shortest_playing_len = *unsmob_moment (s);
355 if (! shortest_playing_len.to_bool ())
357 programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
358 shortest_playing_len = 1;
361 if (! shortest.to_bool ())
363 programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
366 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
367 Real dist = get_duration_space (me, shortest_playing_len, shortest);
371 ugh: 0.1 is an arbitrary distance.
373 dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_)
374 + 0.1 * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
378 Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
379 Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
383 if (lm->grace_part_ && rm->grace_part_)
385 else if (!rm->grace_part_ && lm->grace_part_)
395 Correct for optical illusions. See [Wanske] p. 138. The combination
396 up-stem + down-stem should get extra space, the combination
397 down-stem + up-stem less.
399 This should be more advanced, since relative heights of the note
400 heads also influence required correction.
402 Also might not work correctly in case of multi voices or staff
405 TODO: lookup correction distances? More advanced correction?
406 Possibly turn this off?
408 TODO: have to check wether the stems are in the same staff.
410 This routine reads the DIR-LIST property of both its L and R arguments. */
412 New_spacing_spanner::stem_dir_correction (Grob*me, Grob*l, Grob*r)
414 SCM dl = l->get_grob_property ("dir-list");
415 SCM dr = r->get_grob_property ("dir-list");
417 if (scm_ilength (dl) != 1 || scm_ilength (dr) != 1)
423 assert (gh_number_p (dl) && gh_number_p (dr));
424 int d1 = gh_scm2int (dl);
425 int d2 = gh_scm2int (dr);
431 Real correction = 0.0;
432 Real ssc = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
434 if (d1 && d2 && d1 * d2 == -1)
436 correction = d1 * ssc;
439 programming_error ("Stem directions not set correctly for optical correction");
444 MAKE_SCHEME_CALLBACK (New_spacing_spanner, set_springs,1);
446 New_spacing_spanner::set_springs (SCM smob)
448 Grob *me = unsmob_grob (smob);
449 Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
453 for (int i = 1; i < all.size (); i++)
456 if (Item::breakable_b (sc))
458 Link_array<Grob> measure (all.slice (j, i+1));
459 do_measure (me, measure);
465 farewell, cruel world
468 return SCM_UNSPECIFIED;
474 maximum-duration-for-spacing
475 From: bf250@freenet.carleton.ca (John Sankey)
476 To: gnu-music-discuss@gnu.org
477 Subject: note spacing suggestion
478 Date: Mon, 10 Jul 2000 11:28:03 -0400 (EDT)
480 Currently, Lily spaces notes by starting with a basic distance,
481 arithmetic_multiplier, which it applies to the minimum duration note
482 of the bar. Then she adds a logarithmic increment, scaled from
483 arithmetic_basicspace, for longer notes. (Then, columns are aligned
484 and justified.) Fundamentally, this matches visual spacing to musical
485 weight and works well.
487 A lot of the time in music, I see a section basically in melodic
488 notes that occasionally has a rapid ornamental run (scale). So, there
489 will be a section in 1/4 notes, then a brief passage in 1/32nds, then
490 a return to long notes. Currently, Lily gives the same horizontal
491 space to the 1/32nd notes in their bar (even if set in small size as
492 is commonly done for cadenzii) as she gives to 1/4 notes in bars
493 where 1/4 note is the minimum duration. The resulting visual weight
494 does not match the musical weight over the page.
496 Looking at the music I am typesetting, I feel that Lily's spacing
497 could be significantly improved if, with no change in the basic
498 method used, arithmetic_multiplier could be applied referred to the
499 same duration throughout a piece. Of course, the current method
500 should be retained for those who have already set music in it, so I
501 suggest a property called something like arithmetic_base=16 to fix
502 1/16 duration as the reference for arithmetic_multiplier; the default
503 would be a dynamic base is it is now.
505 Does anyone else feel that this would be a useful improvement for
506 their music? (Of course, if arithmetic_multiplier became a regular
507 property, this could be used to achieve a similar result by