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>
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 class New_spacing_spanner
25 static void set_interface (Grob*);
26 static void do_measure (Grob*,Link_array<Grob> *) ;
27 static void stretch_to_regularity (Grob*, Array<Spring> *, Link_array<Grob> const &);
28 static void breakable_column_spacing (Item* l, Item *r);
29 DECLARE_SCHEME_CALLBACK (set_springs, (SCM ));
30 static Real stem_dir_correction (Grob*,Grob*,Grob*) ;
31 static Real default_bar_spacing (Grob*,Grob*,Grob*,Moment) ;
32 static Real note_spacing (Grob*,Grob*,Grob*,Moment) ;
33 static Real get_duration_space (Grob*,Moment dur, Moment shortest) ;
34 static void prune_loose_colunms (Link_array<Grob>*);
38 New_spacing_spanner::set_interface (Grob*me)
40 me->set_extent_callback (SCM_EOL, X_AXIS);
41 me->set_extent_callback (SCM_EOL, Y_AXIS) ;
45 Remove all columns that are not tightly
46 fitting part of the spacing problem.
49 New_spacing_spanner::prune_loose_colunms (Link_array<Grob> *cols)
51 for (int i = cols->size(); i--;)
53 SCM between = cols->elem(i)->get_grob_property ("between-cols");
54 if (!gh_pair_p (between))
57 Item * l = dynamic_cast<Item*> (unsmob_grob (gh_car (between)));
58 Item * r = dynamic_cast<Item*> (unsmob_grob (gh_cdr (between)));
59 if (l->column_l () != cols->elem (i-1)
60 || r->column_l () != cols->elem (i +1))
69 The algorithm is partly taken from :
71 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
72 OSU-CISRC-10/87-TR35, Department of Computer and Information
73 Science, The Ohio State University, 1987.
81 New_spacing_spanner::do_measure (Grob*me, Link_array<Grob> *cols)
83 Moment shortest_in_measure;
86 space as if this duration is present.
88 Moment base_shortest_duration = *unsmob_moment (me->get_grob_property ("maximum-duration-for-spacing"));
89 shortest_in_measure.set_infinite (1);
91 prune_loose_colunms (cols);
93 for (int i =0 ; i < cols->size (); i++)
95 if (Paper_column::musical_b (cols->elem (i)))
97 Moment *when = unsmob_moment (cols->elem (i)->get_grob_property ("when"));
100 ignore grace notes for shortest notes.
102 if (when && when->grace_part_)
105 SCM st = cols->elem (i)->get_grob_property ("shortest-starter-duration");
106 Moment this_shortest = *unsmob_moment (st);
107 shortest_in_measure = shortest_in_measure <? this_shortest;
111 Array<Spring> springs;
113 Item * first_col = 0;
114 for (int i= 0; i < cols->size () - 1; i++)
116 Item * l = dynamic_cast<Item*> (cols->elem (i));
118 if (!first_col && Paper_column::musical_b (l))
121 Item * r = dynamic_cast<Item*> (cols->elem (i+1));
122 Paper_column * lc = dynamic_cast<Paper_column*> (l);
123 Paper_column *rc = dynamic_cast<Paper_column*> (r);
127 cout << "params for cols " << Paper_column::rank_i (l) << " " << Paper_column::rank_i (r) << endl;
128 cout << " musical: " << Paper_column::musical_b (l) << " " << Paper_column::musical_b (r) << endl;
131 if (!Paper_column::musical_b (l))
133 breakable_column_spacing (l, r);
135 l = l->find_prebroken_piece (RIGHT);
137 breakable_column_spacing (l,r);
142 Real note_space = note_spacing (me,lc, rc, shortest_in_measure <? base_shortest_duration);
143 Real hinterfleisch = note_space;
144 Real headwid = gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
146 SCM seq = lc->get_grob_property ("spacing-sequence");
148 Moment dt = Paper_column::when_mom (r) - Paper_column::when_mom (l);
151 hinterfleisch = hind-meat = amount of space following a note.
154 We adjust the space following a note only if the next note
155 happens after the current note (this is set in the grob
156 property SPACING-SEQUENCE. */
158 Real stretch_distance = note_space;
159 if (shortest_in_measure <= dt)
162 currently SPACING-SEQUENCE is set in
163 Separating_group_spanner::find_musical_sequences (), which
164 works neatly for one-voice-per staff, however,
166 it can't find out the actual duration of the notes on a
167 staff, so when putting tuplets and normal patterns it gets
168 confused, (ie. believes that < { c8 c8 } { d8 d8 d8 }*2/3
169 > contains 1/12 notes. ).
171 here we kludge, by checking if the distance we're spacing
172 for is less than the shortest note.
176 Move SPACING-SEQUENCE detection into a voice
177 level-engraver --or-- make sure that every column has
178 access to the note head.
181 for (SCM s = seq; gh_pair_p (s); s = ly_cdr (s))
183 Grob *lm = unsmob_grob (ly_caar (s));
184 Grob *rm = unsmob_grob (ly_cdar (s));
186 // TODO; configgable.
187 hinterfleisch += -headwid + Separation_item::my_width (lm)[RIGHT] -
188 0.5 * Separation_item::my_width (rm)[LEFT];
191 hinterfleisch += stem_dir_correction (me, l, r);
196 stretch_distance -= headwid;
199 s.distance_f_ = hinterfleisch;
200 s.strength_f_ = 1 / stretch_distance;
202 s.item_l_drul_[LEFT] = l;
203 s.item_l_drul_[RIGHT] = r;
206 if (r->find_prebroken_piece (LEFT))
208 s.item_l_drul_[RIGHT] = r->find_prebroken_piece(LEFT);
216 Read hints from L (todo: R) and generate springs.
219 New_spacing_spanner::breakable_column_spacing (Item* l, Item *r)
223 Real break_dist = 0.0;
224 SCM espace = l->get_grob_property ("extra-space");
225 if (gh_pair_p (espace))
226 break_dist += gh_scm2double (ly_cdr (espace));
231 Real break_stretch = 0.0;
233 // todo: naming of "distance"
234 espace = l->get_grob_property ("stretch-distance");
235 if (gh_pair_p (espace))
236 break_stretch += gh_scm2double (ly_cdr (espace));
241 s.distance_f_ = break_dist;
242 s.strength_f_ = 1/break_stretch;
243 s.item_l_drul_[LEFT] = l;
244 s.item_l_drul_[RIGHT] = r;
250 Look at COLS, searching for columns that have 'regular-distance-to
251 set. A sequence of columns that have this property set should have
252 an equal distance (an equispaced run). Extract the projected
253 distance from SPRINGS, and scale SPRINGS for the equispaced run, to the
254 widest space necessary.
259 -- inefficient code; maybe it is easier to twiddle with the springs
260 after they've become grob properties (ie. have their
261 minimum-distances set)
263 -- does not adjust strength field of the springs very well: result
264 awkward spacing at the start of a line. (?)
266 -- will be confused when there are multiple equispaced runs in a measure.
268 -- dealing with springs for line breaks is a little tricky; in any
269 case, we will only space per measure.
271 -- we scale to actual distances, not to optical effects. Eg. if the
272 equispaced run contains optical corrections, then the scaling will
275 -- Regular_spacing_engraver doesn't mark the first column of the
276 next bar, making the space before a barline too short, in this case
280 x(8th) x(8th) <- equispaced run.
285 New_spacing_spanner::stretch_to_regularity (Grob *me,
286 Array<Spring> * springs,
287 Link_array<Grob> const & cols)
290 Find the starting column of the run. REGULAR-DISTANCE-TO points
291 back to a previous column, so we look ahead to find a column
292 pointing back to the first one.
295 Grob * first_regular_spaced_col = 0;
296 for (int i = 0 ; i < cols.size () && !first_regular_spaced_col; i++)
298 SCM rdt = cols[i]->get_grob_property ("regular-distance-to");
299 if (cols.find_l (dynamic_cast<Item*> (unsmob_grob (rdt))))
300 first_regular_spaced_col = unsmob_grob (rdt);
302 for (int i = springs->size (); i-- ;)
303 springs->elem (i).set_to_cols ();
306 for (i = 0; i < springs->size ()
307 && springs->elem (i).item_l_drul_[RIGHT] != first_regular_spaced_col;
312 if (i==springs->size ())
317 Grob *last_col = first_regular_spaced_col;
318 Grob *last_regular_spaced_col = first_regular_spaced_col;
322 find the max distance for this run.
324 for (int j = i; j < springs->size (); j++)
326 Spring *s = &(springs->elem_ref (j));
327 if (s->item_l_drul_[LEFT] != last_col)
330 dist += s->distance_f_;
332 last_col = s->item_l_drul_[RIGHT];
333 SCM rdt = last_col->get_grob_property ("regular-distance-to");
334 if (unsmob_grob (rdt) == last_regular_spaced_col)
336 maxdist = maxdist >? dist;
338 last_regular_spaced_col = last_col;
347 last_col = first_regular_spaced_col;
348 last_regular_spaced_col = first_regular_spaced_col;
349 for (int j = i; j < springs->size (); j++)
351 Spring *s = &springs->elem_ref (j);
352 if (s->item_l_drul_[LEFT] != last_col)
354 dist += s->distance_f_;
356 last_col = s->item_l_drul_[RIGHT];
357 SCM rdt = last_col->get_grob_property ("regular-distance-to");
358 if (unsmob_grob (rdt) == last_regular_spaced_col)
361 springs->elem_ref (i).distance_f_ *= maxdist / dist;
362 springs->elem_ref (i).strength_f_ *= dist / maxdist;
364 last_regular_spaced_col = last_col;
371 Do something if breakable column has no spacing hints set.
374 New_spacing_spanner::default_bar_spacing (Grob*me, Grob *lc, Grob *rc,
377 Real symbol_distance = lc->extent (lc,X_AXIS)[RIGHT] ;
378 Real durational_distance = 0;
379 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
382 ugh should use shortest_playing distance
384 if (delta_t.to_bool ())
386 durational_distance = get_duration_space (me, delta_t, shortest);
389 return symbol_distance >? durational_distance;
394 Get the measure wide ant for arithmetic spacing.
397 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
398 OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
399 The Ohio State University, 1987.
403 New_spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest)
405 Real log = log_2 (shortest.main_part_);
406 Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
409 Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
411 return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
416 New_spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
419 Moment shortest_playing_len = 0;
420 SCM s = lc->get_grob_property ("shortest-playing-duration");
423 if (unsmob_moment (s))
424 shortest_playing_len = *unsmob_moment (s);
426 if (! shortest_playing_len.to_bool ())
428 programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
429 shortest_playing_len = 1;
432 if (! shortest.to_bool ())
434 programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
437 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
440 if (delta_t.main_part_)
442 dist = get_duration_space (me, shortest_playing_len, shortest);
443 dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_);
445 else if (delta_t.grace_part_)
447 dist = get_duration_space (me, shortest, shortest);
449 Real grace_fact = 1.0;
450 SCM gf = me->get_grob_property ("grace-space-factor");
451 if (gh_number_p (gf))
452 grace_fact = gh_scm2double (gf);
459 TODO: figure out how to space grace notes.
463 + grace_fact * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
466 Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
467 Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
471 if (lm->grace_part_ && rm->grace_part_)
473 else if (!rm->grace_part_ && lm->grace_part_)
483 Correct for optical illusions. See [Wanske] p. 138. The combination
484 up-stem + down-stem should get extra space, the combination
485 down-stem + up-stem less.
487 This should be more advanced, since relative heights of the note
488 heads also influence required correction.
490 Also might not work correctly in case of multi voices or staff
493 TODO: lookup correction distances? More advanced correction?
494 Possibly turn this off?
496 TODO: have to check wether the stems are in the same staff.
498 This routine reads the DIR-LIST property of both its L and R arguments. */
500 New_spacing_spanner::stem_dir_correction (Grob*me, Grob*l, Grob*r)
502 SCM dl = l->get_grob_property ("dir-list");
503 SCM dr = r->get_grob_property ("dir-list");
505 if (scm_ilength (dl) != 1 || scm_ilength (dr) != 1)
511 assert (gh_number_p (dl) && gh_number_p (dr));
512 int d1 = gh_scm2int (dl);
513 int d2 = gh_scm2int (dr);
519 Real correction = 0.0;
520 Real ssc = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
522 if (d1 && d2 && d1 * d2 == -1)
524 correction = d1 * ssc;
527 programming_error ("Stem directions not set correctly for optical correction");
532 MAKE_SCHEME_CALLBACK (New_spacing_spanner, set_springs,1);
534 New_spacing_spanner::set_springs (SCM smob)
536 Grob *me = unsmob_grob (smob);
537 Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
541 for (int i = 1; i < all.size (); i++)
544 if (Item::breakable_b (sc))
546 Link_array<Grob> measure (all.slice (j, i+1));
547 do_measure (me, &measure);
553 farewell, cruel world
556 return SCM_UNSPECIFIED;
562 maximum-duration-for-spacing
563 From: bf250@freenet.carleton.ca (John Sankey)
564 To: gnu-music-discuss@gnu.org
565 Subject: note spacing suggestion
566 Date: Mon, 10 Jul 2000 11:28:03 -0400 (EDT)
568 Currently, Lily spaces notes by starting with a basic distance,
569 arithmetic_multiplier, which it applies to the minimum duration note
570 of the bar. Then she adds a logarithmic increment, scaled from
571 arithmetic_basicspace, for longer notes. (Then, columns are aligned
572 and justified.) Fundamentally, this matches visual spacing to musical
573 weight and works well.
575 A lot of the time in music, I see a section basically in melodic
576 notes that occasionally has a rapid ornamental run (scale). So, there
577 will be a section in 1/4 notes, then a brief passage in 1/32nds, then
578 a return to long notes. Currently, Lily gives the same horizontal
579 space to the 1/32nd notes in their bar (even if set in small size as
580 is commonly done for cadenzii) as she gives to 1/4 notes in bars
581 where 1/4 note is the minimum duration. The resulting visual weight
582 does not match the musical weight over the page.
584 Looking at the music I am typesetting, I feel that Lily's spacing
585 could be significantly improved if, with no change in the basic
586 method used, arithmetic_multiplier could be applied referred to the
587 same duration throughout a piece. Of course, the current method
588 should be retained for those who have already set music in it, so I
589 suggest a property called something like arithmetic_base=16 to fix
590 1/16 duration as the reference for arithmetic_multiplier; the default
591 would be a dynamic base is it is now.
593 Does anyone else feel that this would be a useful improvement for
594 their music? (Of course, if arithmetic_multiplier became a regular
595 property, this could be used to achieve a similar result by