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 "paper-column.hh"
11 #include "dimensions.hh"
12 #include "paper-def.hh"
14 #include "paper-score.hh"
15 #include "line-of-score.hh"
17 #include "separation-item.hh"
21 class New_spacing_spanner
24 static void set_interface (Grob*);
25 static void do_measure (Grob*,Link_array<Grob> *) ;
26 static void stretch_to_regularity (Grob*, Array<Spring> *, Link_array<Grob> const &);
27 static void breakable_column_spacing (Item* l, Item *r);
28 DECLARE_SCHEME_CALLBACK (set_springs, (SCM ));
29 static Real stem_dir_correction (Grob*,Grob*,Grob*) ;
30 static Real default_bar_spacing (Grob*,Grob*,Grob*,Moment) ;
31 static Real note_spacing (Grob*,Grob*,Grob*,Moment) ;
32 static Real get_duration_space (Grob*,Moment dur, Moment shortest) ;
33 static void prune_loose_colunms (Link_array<Grob>*);
37 New_spacing_spanner::set_interface (Grob*me)
39 me->set_extent_callback (SCM_EOL, X_AXIS);
40 me->set_extent_callback (SCM_EOL, Y_AXIS) ;
44 Remove all columns that are not tightly
45 fitting part of the spacing problem.
48 New_spacing_spanner::prune_loose_colunms (Link_array<Grob> *cols)
50 for (int i = cols->size(); i--;)
52 SCM between = cols->elem(i)->get_grob_property ("between-cols");
53 if (!gh_pair_p (between))
56 Item * l = dynamic_cast<Item*> (unsmob_grob (gh_car (between)));
57 Item * r = dynamic_cast<Item*> (unsmob_grob (gh_cdr (between)));
58 if (l->column_l () != cols->elem (i-1)
59 || r->column_l () != cols->elem (i +1))
68 The algorithm is partly taken from :
70 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
71 OSU-CISRC-10/87-TR35, Department of Computer and Information
72 Science, The Ohio State University, 1987.
80 New_spacing_spanner::do_measure (Grob*me, Link_array<Grob> *cols)
82 Moment shortest_in_measure;
85 space as if this duration is present.
87 Moment base_shortest_duration = *unsmob_moment (me->get_grob_property ("maximum-duration-for-spacing"));
88 shortest_in_measure.set_infinite (1);
90 prune_loose_colunms (cols);
92 for (int i =0 ; i < cols->size (); i++)
94 if (Paper_column::musical_b (cols->elem (i)))
96 Moment *when = unsmob_moment (cols->elem (i)->get_grob_property ("when"));
99 ignore grace notes for shortest notes.
101 if (when && when->grace_part_)
104 SCM st = cols->elem (i)->get_grob_property ("shortest-starter-duration");
105 Moment this_shortest = *unsmob_moment (st);
106 shortest_in_measure = shortest_in_measure <? this_shortest;
110 Array<Spring> springs;
112 Item * first_col = 0;
113 for (int i= 0; i < cols->size () - 1; i++)
115 Item * l = dynamic_cast<Item*> (cols->elem (i));
117 if (!first_col && Paper_column::musical_b (l))
120 Item * r = dynamic_cast<Item*> (cols->elem (i+1));
121 Paper_column * lc = dynamic_cast<Paper_column*> (l);
122 Paper_column *rc = dynamic_cast<Paper_column*> (r);
126 cout << "params for cols " << Paper_column::rank_i (l) << " " << Paper_column::rank_i (r) << endl;
127 cout << " musical: " << Paper_column::musical_b (l) << " " << Paper_column::musical_b (r) << endl;
130 if (!Paper_column::musical_b (l))
132 breakable_column_spacing (l, r);
134 l = l->find_prebroken_piece (RIGHT);
136 breakable_column_spacing (l,r);
141 Real note_space = note_spacing (me,lc, rc, shortest_in_measure <? base_shortest_duration);
142 Real hinterfleisch = note_space;
143 Real headwid = gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
145 SCM seq = lc->get_grob_property ("spacing-sequence");
147 Moment dt = Paper_column::when_mom (r) - Paper_column::when_mom (l);
150 hinterfleisch = hind-meat = amount of space following a note.
153 We adjust the space following a note only if the next note
154 happens after the current note (this is set in the grob
155 property SPACING-SEQUENCE. */
157 Real stretch_distance = note_space;
158 if (shortest_in_measure <= dt)
161 currently SPACING-SEQUENCE is set in
162 Separating_group_spanner::find_musical_sequences (), which
163 works neatly for one-voice-per staff, however,
165 it can't find out the actual duration of the notes on a
166 staff, so when putting tuplets and normal patterns it gets
167 confused, (ie. believes that < { c8 c8 } { d8 d8 d8 }*2/3
168 > contains 1/12 notes. ).
170 here we kludge, by checking if the distance we're spacing
171 for is less than the shortest note.
175 Move SPACING-SEQUENCE detection into a voice
176 level-engraver --or-- make sure that every column has
177 access to the note head.
180 for (SCM s = seq; gh_pair_p (s); s = ly_cdr (s))
182 Grob *lm = unsmob_grob (ly_caar (s));
183 Grob *rm = unsmob_grob (ly_cdar (s));
185 // TODO; configgable.
186 hinterfleisch += -headwid + Separation_item::my_width (lm)[RIGHT] -
187 0.5 * Separation_item::my_width (rm)[LEFT];
190 hinterfleisch += stem_dir_correction (me, l, r);
195 stretch_distance -= headwid;
198 s.distance_f_ = hinterfleisch;
199 s.strength_f_ = 1 / stretch_distance;
201 s.item_l_drul_[LEFT] = l;
202 s.item_l_drul_[RIGHT] = r;
205 if (r->find_prebroken_piece (LEFT))
207 s.item_l_drul_[RIGHT] = r->find_prebroken_piece(LEFT);
215 Read hints from L (todo: R) and generate springs.
218 New_spacing_spanner::breakable_column_spacing (Item* l, Item *r)
222 Real break_dist = 0.0;
223 SCM espace = l->get_grob_property ("extra-space");
224 if (gh_pair_p (espace))
225 break_dist += gh_scm2double (ly_cdr (espace));
230 Real break_stretch = 0.0;
232 // todo: naming of "distance"
233 espace = l->get_grob_property ("stretch-distance");
234 if (gh_pair_p (espace))
235 break_stretch += gh_scm2double (ly_cdr (espace));
240 s.distance_f_ = break_dist;
241 s.strength_f_ = 1/break_stretch;
242 s.item_l_drul_[LEFT] = l;
243 s.item_l_drul_[RIGHT] = r;
249 Look at COLS, searching for columns that have 'regular-distance-to
250 set. A sequence of columns that have this property set should have
251 an equal distance (an equispaced run). Extract the projected
252 distance from SPRINGS, and scale SPRINGS for the equispaced run, to the
253 widest space necessary.
258 -- inefficient code; maybe it is easier to twiddle with the springs
259 after they've become grob properties (ie. have their
260 minimum-distances set)
262 -- does not adjust strength field of the springs very well: result
263 awkward spacing at the start of a line. (?)
265 -- will be confused when there are multiple equispaced runs in a measure.
267 -- dealing with springs for line breaks is a little tricky; in any
268 case, we will only space per measure.
270 -- we scale to actual distances, not to optical effects. Eg. if the
271 equispaced run contains optical corrections, then the scaling will
274 -- Regular_spacing_engraver doesn't mark the first column of the
275 next bar, making the space before a barline too short, in this case
279 x(8th) x(8th) <- equispaced run.
284 New_spacing_spanner::stretch_to_regularity (Grob *me,
285 Array<Spring> * springs,
286 Link_array<Grob> const & cols)
289 Find the starting column of the run. REGULAR-DISTANCE-TO points
290 back to a previous column, so we look ahead to find a column
291 pointing back to the first one.
294 Grob * first_regular_spaced_col = 0;
295 for (int i = 0 ; i < cols.size () && !first_regular_spaced_col; i++)
297 SCM rdt = cols[i]->get_grob_property ("regular-distance-to");
298 if (cols.find_l (dynamic_cast<Item*> (unsmob_grob (rdt))))
299 first_regular_spaced_col = unsmob_grob (rdt);
301 for (int i = springs->size (); i-- ;)
302 springs->elem (i).set_to_cols ();
305 for (i = 0; i < springs->size ()
306 && springs->elem (i).item_l_drul_[RIGHT] != first_regular_spaced_col;
311 if (i==springs->size ())
316 Grob *last_col = first_regular_spaced_col;
317 Grob *last_regular_spaced_col = first_regular_spaced_col;
321 find the max distance for this run.
323 for (int j = i; j < springs->size (); j++)
325 Spring *s = &(springs->elem_ref (j));
326 if (s->item_l_drul_[LEFT] != last_col)
329 dist += s->distance_f_;
331 last_col = s->item_l_drul_[RIGHT];
332 SCM rdt = last_col->get_grob_property ("regular-distance-to");
333 if (unsmob_grob (rdt) == last_regular_spaced_col)
335 maxdist = maxdist >? dist;
337 last_regular_spaced_col = last_col;
346 last_col = first_regular_spaced_col;
347 last_regular_spaced_col = first_regular_spaced_col;
348 for (int j = i; j < springs->size (); j++)
350 Spring *s = &springs->elem_ref (j);
351 if (s->item_l_drul_[LEFT] != last_col)
353 dist += s->distance_f_;
355 last_col = s->item_l_drul_[RIGHT];
356 SCM rdt = last_col->get_grob_property ("regular-distance-to");
357 if (unsmob_grob (rdt) == last_regular_spaced_col)
360 springs->elem_ref (i).distance_f_ *= maxdist / dist;
361 springs->elem_ref (i).strength_f_ *= dist / maxdist;
363 last_regular_spaced_col = last_col;
370 Do something if breakable column has no spacing hints set.
373 New_spacing_spanner::default_bar_spacing (Grob*me, Grob *lc, Grob *rc,
376 Real symbol_distance = lc->extent (lc,X_AXIS)[RIGHT] ;
377 Real durational_distance = 0;
378 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
381 ugh should use shortest_playing distance
383 if (delta_t.to_bool ())
385 durational_distance = get_duration_space (me, delta_t, shortest);
388 return symbol_distance >? durational_distance;
393 Get the measure wide ant for arithmetic spacing.
396 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
397 OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
398 The Ohio State University, 1987.
402 New_spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest)
404 Real log = log_2 (shortest.main_part_);
405 Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
408 Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
410 return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
415 New_spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
418 Moment shortest_playing_len = 0;
419 SCM s = lc->get_grob_property ("shortest-playing-duration");
422 if (unsmob_moment (s))
423 shortest_playing_len = *unsmob_moment (s);
425 if (! shortest_playing_len.to_bool ())
427 programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
428 shortest_playing_len = 1;
431 if (! shortest.to_bool ())
433 programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
436 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
439 if (delta_t.main_part_)
441 dist = get_duration_space (me, shortest_playing_len, shortest);
442 dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_);
444 else if (delta_t.grace_part_)
446 dist = get_duration_space (me, shortest, shortest);
448 Real grace_fact = 1.0;
449 SCM gf = me->get_grob_property ("grace-space-factor");
450 if (gh_number_p (gf))
451 grace_fact = gh_scm2double (gf);
458 TODO: figure out how to space grace notes.
462 + grace_fact * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
465 Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
466 Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
470 if (lm->grace_part_ && rm->grace_part_)
472 else if (!rm->grace_part_ && lm->grace_part_)
482 Correct for optical illusions. See [Wanske] p. 138. The combination
483 up-stem + down-stem should get extra space, the combination
484 down-stem + up-stem less.
486 This should be more advanced, since relative heights of the note
487 heads also influence required correction.
489 Also might not work correctly in case of multi voices or staff
492 TODO: lookup correction distances? More advanced correction?
493 Possibly turn this off?
495 TODO: have to check wether the stems are in the same staff.
497 This routine reads the DIR-LIST property of both its L and R arguments. */
499 New_spacing_spanner::stem_dir_correction (Grob*me, Grob*l, Grob*r)
501 SCM dl = l->get_grob_property ("dir-list");
502 SCM dr = r->get_grob_property ("dir-list");
504 if (scm_ilength (dl) != 1 || scm_ilength (dr) != 1)
510 assert (gh_number_p (dl) && gh_number_p (dr));
511 int d1 = gh_scm2int (dl);
512 int d2 = gh_scm2int (dr);
518 Real correction = 0.0;
519 Real ssc = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
521 if (d1 && d2 && d1 * d2 == -1)
523 correction = d1 * ssc;
526 programming_error ("Stem directions not set correctly for optical correction");
531 MAKE_SCHEME_CALLBACK (New_spacing_spanner, set_springs,1);
533 New_spacing_spanner::set_springs (SCM smob)
535 Grob *me = unsmob_grob (smob);
536 Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
540 for (int i = 1; i < all.size (); i++)
543 if (Item::breakable_b (sc))
545 Link_array<Grob> measure (all.slice (j, i+1));
546 do_measure (me, &measure);
552 farewell, cruel world
555 return SCM_UNSPECIFIED;
561 maximum-duration-for-spacing
562 From: bf250@freenet.carleton.ca (John Sankey)
563 To: gnu-music-discuss@gnu.org
564 Subject: note spacing suggestion
565 Date: Mon, 10 Jul 2000 11:28:03 -0400 (EDT)
567 Currently, Lily spaces notes by starting with a basic distance,
568 arithmetic_multiplier, which it applies to the minimum duration note
569 of the bar. Then she adds a logarithmic increment, scaled from
570 arithmetic_basicspace, for longer notes. (Then, columns are aligned
571 and justified.) Fundamentally, this matches visual spacing to musical
572 weight and works well.
574 A lot of the time in music, I see a section basically in melodic
575 notes that occasionally has a rapid ornamental run (scale). So, there
576 will be a section in 1/4 notes, then a brief passage in 1/32nds, then
577 a return to long notes. Currently, Lily gives the same horizontal
578 space to the 1/32nd notes in their bar (even if set in small size as
579 is commonly done for cadenzii) as she gives to 1/4 notes in bars
580 where 1/4 note is the minimum duration. The resulting visual weight
581 does not match the musical weight over the page.
583 Looking at the music I am typesetting, I feel that Lily's spacing
584 could be significantly improved if, with no change in the basic
585 method used, arithmetic_multiplier could be applied referred to the
586 same duration throughout a piece. Of course, the current method
587 should be retained for those who have already set music in it, so I
588 suggest a property called something like arithmetic_base=16 to fix
589 1/16 duration as the reference for arithmetic_multiplier; the default
590 would be a dynamic base is it is now.
592 Does anyone else feel that this would be a useful improvement for
593 their music? (Of course, if arithmetic_multiplier became a regular
594 property, this could be used to achieve a similar result by