2 spacing-spanner.cc -- implement Spacing_spanner
4 source file of the GNU LilyPond music typesetter
6 (c) 1999--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
9 #include "line-of-score.hh"
10 #include "paper-score.hh"
11 #include "paper-column.hh"
14 #include "note-spacing.hh"
21 right-neighbors = List of spacing-wish grobs that are close to the
27 (TODO: property-doc these!)
29 Don't be confused by right-items: each spacing wish can also contain
30 a number of items, with which a spacing constraint may be kept. It's
31 a little baroque, but it might come in handy later on?
35 class Third_spacing_spanner
38 static Real default_bar_spacing (Grob*,Grob*,Grob*,Moment) ;
39 static Real note_spacing (Grob*,Grob*,Grob*,Moment) ;
40 static Real get_duration_space (Grob*,Moment dur, Moment shortest) ;
42 static void breakable_column_spacing (Item* l, Item *r);
43 static void find_loose_columns () {}
44 static void prune_loose_colunms (Link_array<Grob> *cols);
45 static void find_loose_columns (Link_array<Grob> cols);
46 static void set_explicit_neighbor_columns (Link_array<Grob> cols);
47 static void set_implicit_neighbor_columns (Link_array<Grob> cols);
48 static void do_measure (Grob*me,Link_array<Grob> *cols);
49 DECLARE_SCHEME_CALLBACK (set_springs, (SCM ));
55 Return whether COL is fixed to its neighbors by some kind of spacing
59 loose_column (Grob *l, Grob *c, Grob *r)
61 SCM rns = c->get_grob_property ("right-neighbors");
62 SCM lns = c->get_grob_property ("left-neighbors");
65 If this column doesn't have a proper neighbor, we should really
66 make it loose, but spacing it correctly is more than we can
69 (this happens in the following situation:
80 the column containing the clef is really loose, and should be
81 attached right to the first column, but that is a lot of work for
82 such a borderline case.
87 if (!gh_pair_p (lns) || !gh_pair_p (rns))
90 Item * l_neighbor = dynamic_cast<Item*> (unsmob_grob (gh_car (lns)));
91 Item * r_neighbor = dynamic_cast<Item*> (unsmob_grob (gh_car (rns)));
93 if (!l_neighbor || !r_neighbor)
96 l_neighbor = l_neighbor->column_l();
97 r_neighbor = dynamic_cast<Item*> (Note_spacing::right_column (r_neighbor));
99 if (l == l_neighbor && r == r_neighbor)
102 if (!l_neighbor || !r_neighbor)
106 Only declare loose if the bounds make a little sense. This means
107 some cases (two isolated, consecutive clef changes) won't be
108 nicely folded, but hey, then don't do that.
110 if( (Paper_column::musical_b (l_neighbor) || Item::breakable_b (l_neighbor))
111 && (Paper_column::musical_b (r_neighbor) || Item::breakable_b (r_neighbor)))
118 If in doubt: we're not loose; the spacing engine should space for
119 it, risking suboptimal spacing.
121 (Otherwise, we might risk core dumps, and other weird stuff.)
129 Remove columns that are not tightly fitting from COLS. In the
130 removed columns, set 'between-cols to the columns where it is in
134 Third_spacing_spanner::prune_loose_colunms (Link_array<Grob> *cols)
136 Link_array<Grob> newcols;
138 for (int i=0; i < cols->size (); i++)
140 if (Item::breakable_b (cols->elem(i)) || Paper_column::musical_b (cols->elem (i)))
142 newcols.push (cols->elem(i));
146 Grob *c = cols->elem(i);
147 if (loose_column (cols->elem (i-1), c, cols->elem (i+1)))
149 SCM lns = c->get_grob_property ("left-neighbors");
150 lns = gh_pair_p (lns) ? gh_car (lns) : SCM_BOOL_F;
152 SCM rns = c->get_grob_property ("right-neighbors");
153 rns = gh_pair_p (rns) ? gh_car (rns) : SCM_BOOL_F;
156 Either object can be non existent, if the score ends
159 rns = gh_car (unsmob_grob (rns)->get_grob_property ("right-items"));
160 c->set_grob_property ("between-cols", gh_cons (lns,
164 Set distance constraints for loose columns
166 Drul_array<Grob*> next_door;
167 next_door[LEFT] =cols->elem (i - 1);
168 next_door[RIGHT] =cols->elem (i + 1);
170 Drul_array<Real> dists(0,0);
175 Grob *lc = d == LEFT ? lc : c;
176 Grob *rc = d == LEFT ? c : rc;
178 for (SCM s = lc->get_grob_property ("spacing-wishes");
179 gh_pair_p (s); s = gh_cdr (s))
181 Grob *sp = unsmob_grob (gh_car (s));
182 if (Note_spacing::left_column (sp) != lc
183 || Note_spacing::right_column (sp) != rc)
186 dists[d] = dists[d] >? Note_spacing::get_spacing (sp);
189 while (flip (&d) != LEFT);
192 r.distance_f_ = dists[LEFT] + dists[RIGHT];
193 r.item_l_drul_[LEFT] = dynamic_cast<Item*> (cols->elem(i-1));
194 r.item_l_drul_[RIGHT] = dynamic_cast<Item*> (cols->elem (i+1));
208 Set neighboring columns determined by the spacing-wishes grob property.
211 Third_spacing_spanner::set_explicit_neighbor_columns (Link_array<Grob> cols)
213 for (int i=0; i < cols.size(); i++)
215 SCM right_neighbors = SCM_EOL;
216 int min_rank = 100000; // inf.
219 SCM wishes= cols[i]->get_grob_property ("spacing-wishes");
220 for (SCM s =wishes; gh_pair_p (s); s = gh_cdr (s))
222 Item * wish = dynamic_cast<Item*> (unsmob_grob (gh_car (s)));
224 Item * lc = wish->column_l ();
225 Grob * right = Note_spacing::right_column (wish);
230 Item * rc = dynamic_cast<Item*> (right);
232 int right_rank = Paper_column::rank_i (rc);
233 int left_rank = Paper_column::rank_i (lc);
236 update the left column.
238 if (right_rank <= min_rank)
240 if (right_rank < min_rank)
241 right_neighbors =SCM_EOL;
243 min_rank = right_rank;
244 right_neighbors = gh_cons (wish->self_scm (), right_neighbors);
248 update the right column of the wish.
251 SCM left_neighs = rc->get_grob_property ("left-neighbors");
252 if (gh_pair_p (left_neighs)
253 && unsmob_grob (gh_car (left_neighs)))
255 Item * it = dynamic_cast<Item*> (unsmob_grob (gh_car (left_neighs)));
256 maxrank = Paper_column::rank_i (it->column_l());
259 if (left_rank >= maxrank)
261 if (left_rank > maxrank)
262 left_neighs = SCM_EOL;
264 left_neighs = gh_cons (wish->self_scm (), left_neighs);
265 rc->set_grob_property ("left-neighbors", right_neighbors);
269 if (gh_pair_p (right_neighbors))
271 cols[i]->set_grob_property ("right-neighbors", right_neighbors);
277 Set neighboring columns that have no left/right-neighbor set
278 yet. Only do breakable non-musical columns, and musical columns.
281 Third_spacing_spanner::set_implicit_neighbor_columns (Link_array<Grob> cols)
283 for (int i = 0; i < cols.size (); i++)
285 Item * it = dynamic_cast<Item*>(cols[i]);
286 if (!Item::breakable_b (it) && !Paper_column::musical_b (it))
289 // it->breakable || it->musical
290 SCM ln = cols[i] ->get_grob_property ("left-neighbors");
291 if (!gh_pair_p (ln) && i )
293 cols[i]->set_grob_property ("left-neighbors", cols[i-1]->self_scm());
296 SCM rn = cols[i] ->get_grob_property ("right-neighbors");
297 if (!gh_pair_p (rn) && i < cols.size () - 1)
299 cols[i]->set_grob_property ("right-neighbors", cols[i + 1]->self_scm());
305 MAKE_SCHEME_CALLBACK (Third_spacing_spanner, set_springs,1);
307 Third_spacing_spanner::set_springs (SCM smob)
309 Grob *me = unsmob_grob (smob);
311 Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
313 set_explicit_neighbor_columns (all);
314 prune_loose_colunms (&all);
315 set_implicit_neighbor_columns (all);
318 for (int i = 1; i < all.size (); i++)
321 if (Item::breakable_b (sc))
323 Link_array<Grob> measure (all.slice (j, i+1));
324 do_measure (me, &measure);
329 return SCM_UNSPECIFIED;
334 Third_spacing_spanner::do_measure (Grob*me, Link_array<Grob> *cols)
336 Moment shortest_in_measure;
339 space as if this duration is present.
341 Moment base_shortest_duration = *unsmob_moment (me->get_grob_property ("maximum-duration-for-spacing"));
342 shortest_in_measure.set_infinite (1);
344 for (int i =0 ; i < cols->size (); i++)
346 if (Paper_column::musical_b (cols->elem (i)))
348 Moment *when = unsmob_moment (cols->elem (i)->get_grob_property ("when"));
351 ignore grace notes for shortest notes.
353 if (when && when->grace_part_)
356 SCM st = cols->elem (i)->get_grob_property ("shortest-starter-duration");
357 Moment this_shortest = *unsmob_moment (st);
358 shortest_in_measure = shortest_in_measure <? this_shortest;
362 Array<Spring> springs;
364 for (int i= 0; i < cols->size () - 1; i++)
366 Item * l = dynamic_cast<Item*> (cols->elem (i));
367 Item * r = dynamic_cast<Item*> (cols->elem (i+1));
369 Paper_column * lc = dynamic_cast<Paper_column*> (l);
370 Paper_column * rc = dynamic_cast<Paper_column*> (r);
372 if (!Paper_column::musical_b (l))
374 breakable_column_spacing (l, r);
378 The case that the right part is broken as well is rather
379 rare, but it is possible, eg. with a single empty measure,
380 or if one staff finishes a tad earlier than the rest.
383 Item *lb = l->find_prebroken_piece (RIGHT);
384 Item *rb = r->find_prebroken_piece (LEFT);
387 breakable_column_spacing (lb,r);
390 breakable_column_spacing (l, rb);
392 breakable_column_spacing (lb, rb);
397 Real note_space = note_spacing (me,lc, rc, shortest_in_measure <? base_shortest_duration);
398 Real hinterfleisch = note_space;
399 Real headwid = gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
401 SCM seq = lc->get_grob_property ("right-neighbors");
404 hinterfleisch = hind-meat = amount of space following a note.
407 We adjust the space following a note only if the next note
408 happens after the current note (this is set in the grob
409 property SPACING-SEQUENCE. */
411 Real stretch_distance = note_space;
413 hinterfleisch = -1.0;
414 Real max_factor = 0.0;
415 for (SCM s = seq; gh_pair_p (s); s = ly_cdr (s))
417 Grob * wish = unsmob_grob (gh_car (s));
419 if (Note_spacing::left_column (wish) != lc
420 || Note_spacing::right_column (wish) != rc)
424 This is probably a waste of time in the case of polyphonic
426 if (Note_spacing::has_interface (wish))
428 hinterfleisch = hinterfleisch >?
431 (note_space + Note_spacing::get_spacing (wish))
432 *gh_scm2double (wish->get_grob_property ("space-factor"))
434 + Note_spacing::stem_dir_correction (wish));
438 if (hinterfleisch < 0)
440 // maybe should issue a programming error.
441 hinterfleisch = note_space;
444 stretch_distance -= headwid; // why?
446 if (max_factor == 0.0)
450 s.distance_f_ = max_factor * hinterfleisch;
451 s.strength_f_ = 1 / stretch_distance;
453 s.item_l_drul_[LEFT] = l;
454 s.item_l_drul_[RIGHT] = r;
457 if (r->find_prebroken_piece (LEFT))
459 s.item_l_drul_[RIGHT] = r->find_prebroken_piece(LEFT);
468 Read hints from L (todo: R) and generate springs.
471 Third_spacing_spanner::breakable_column_spacing (Item* l, Item *r)
475 Real break_dist = 0.0;
476 SCM espace = l->get_grob_property ("extra-space");
477 if (gh_pair_p (espace))
478 break_dist += gh_scm2double (ly_cdr (espace));
483 Real break_stretch = 0.0;
485 // todo: naming of "distance"
486 espace = l->get_grob_property ("stretch-distance");
487 if (gh_pair_p (espace))
488 break_stretch += gh_scm2double (ly_cdr (espace));
493 s.distance_f_ = break_dist;
494 s.strength_f_ = 1/break_stretch;
495 s.item_l_drul_[LEFT] = l;
496 s.item_l_drul_[RIGHT] = r;
503 Get the measure wide ant for arithmetic spacing.
506 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
507 OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
508 The Ohio State University, 1987.
512 Third_spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest)
514 Real log = log_2 (shortest.main_part_);
515 Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
518 Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
520 return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
525 Third_spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
528 Moment shortest_playing_len = 0;
529 SCM s = lc->get_grob_property ("shortest-playing-duration");
532 if (unsmob_moment (s))
533 shortest_playing_len = *unsmob_moment (s);
535 if (! shortest_playing_len.to_bool ())
537 programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
538 shortest_playing_len = 1;
541 if (! shortest.to_bool ())
543 programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
546 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
549 if (delta_t.main_part_)
551 dist = get_duration_space (me, shortest_playing_len, shortest);
552 dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_);
554 else if (delta_t.grace_part_)
556 dist = get_duration_space (me, shortest, shortest);
558 Real grace_fact = 1.0;
559 SCM gf = me->get_grob_property ("grace-space-factor");
560 if (gh_number_p (gf))
561 grace_fact = gh_scm2double (gf);
568 TODO: figure out how to space grace notes.
572 + grace_fact * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
575 Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
576 Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
580 if (lm->grace_part_ && rm->grace_part_)
582 else if (!rm->grace_part_ && lm->grace_part_)