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,
165 TODO: we should have distance constraints for loose
166 columns anyway, and the placement could be improved. Clefs
167 easily run into their neigbhors when folded into too
181 Set neighboring columns determined by the spacing-wishes grob property.
184 Third_spacing_spanner::set_explicit_neighbor_columns (Link_array<Grob> cols)
186 for (int i=0; i < cols.size(); i++)
188 SCM right_neighbors = SCM_EOL;
189 int min_rank = 100000; // inf.
192 SCM wishes= cols[i]->get_grob_property ("spacing-wishes");
193 for (SCM s =wishes; gh_pair_p (s); s = gh_cdr (s))
195 Item * wish = dynamic_cast<Item*> (unsmob_grob (gh_car (s)));
197 Item * lc = wish->column_l ();
198 Grob * right = Note_spacing::right_column (wish);
203 Item * rc = dynamic_cast<Item*> (right);
205 int right_rank = Paper_column::rank_i (rc);
206 int left_rank = Paper_column::rank_i (lc);
209 update the left column.
211 if (right_rank <= min_rank)
213 if (right_rank < min_rank)
214 right_neighbors =SCM_EOL;
216 min_rank = right_rank;
217 right_neighbors = gh_cons (wish->self_scm (), right_neighbors);
221 update the right column of the wish.
224 SCM left_neighs = rc->get_grob_property ("left-neighbors");
225 if (gh_pair_p (left_neighs)
226 && unsmob_grob (gh_car (left_neighs)))
228 Item * it = dynamic_cast<Item*> (unsmob_grob (gh_car (left_neighs)));
229 maxrank = Paper_column::rank_i (it->column_l());
232 if (left_rank >= maxrank)
234 if (left_rank > maxrank)
235 left_neighs = SCM_EOL;
237 left_neighs = gh_cons (wish->self_scm (), left_neighs);
238 rc->set_grob_property ("left-neighbors", right_neighbors);
242 if (gh_pair_p (right_neighbors))
244 cols[i]->set_grob_property ("right-neighbors", right_neighbors);
250 Set neighboring columns that have no left/right-neighbor set
251 yet. Only do breakable non-musical columns, and musical columns.
254 Third_spacing_spanner::set_implicit_neighbor_columns (Link_array<Grob> cols)
256 for (int i = 0; i < cols.size (); i++)
258 Item * it = dynamic_cast<Item*>(cols[i]);
259 if (!Item::breakable_b (it) && !Paper_column::musical_b (it))
262 // it->breakable || it->musical
263 SCM ln = cols[i] ->get_grob_property ("left-neighbors");
264 if (!gh_pair_p (ln) && i )
266 cols[i]->set_grob_property ("left-neighbors", cols[i-1]->self_scm());
269 SCM rn = cols[i] ->get_grob_property ("right-neighbors");
270 if (!gh_pair_p (rn) && i < cols.size () - 1)
272 cols[i]->set_grob_property ("right-neighbors", cols[i + 1]->self_scm());
278 MAKE_SCHEME_CALLBACK (Third_spacing_spanner, set_springs,1);
280 Third_spacing_spanner::set_springs (SCM smob)
282 Grob *me = unsmob_grob (smob);
284 Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
286 set_explicit_neighbor_columns (all);
287 prune_loose_colunms (&all);
288 set_implicit_neighbor_columns (all);
291 for (int i = 1; i < all.size (); i++)
294 if (Item::breakable_b (sc))
296 Link_array<Grob> measure (all.slice (j, i+1));
297 do_measure (me, &measure);
302 return SCM_UNSPECIFIED;
307 Third_spacing_spanner::do_measure (Grob*me, Link_array<Grob> *cols)
309 Moment shortest_in_measure;
312 space as if this duration is present.
314 Moment base_shortest_duration = *unsmob_moment (me->get_grob_property ("maximum-duration-for-spacing"));
315 shortest_in_measure.set_infinite (1);
317 for (int i =0 ; i < cols->size (); i++)
319 if (Paper_column::musical_b (cols->elem (i)))
321 Moment *when = unsmob_moment (cols->elem (i)->get_grob_property ("when"));
324 ignore grace notes for shortest notes.
326 if (when && when->grace_part_)
329 SCM st = cols->elem (i)->get_grob_property ("shortest-starter-duration");
330 Moment this_shortest = *unsmob_moment (st);
331 shortest_in_measure = shortest_in_measure <? this_shortest;
335 Array<Spring> springs;
337 for (int i= 0; i < cols->size () - 1; i++)
339 Item * l = dynamic_cast<Item*> (cols->elem (i));
340 Item * r = dynamic_cast<Item*> (cols->elem (i+1));
342 Paper_column * lc = dynamic_cast<Paper_column*> (l);
343 Paper_column * rc = dynamic_cast<Paper_column*> (r);
345 if (!Paper_column::musical_b (l))
347 breakable_column_spacing (l, r);
351 The case that the right part is broken as well is rather
352 rare, but it is possible, eg. with a single empty measure,
353 or if one staff finishes a tad earlier than the rest.
356 Item *lb = l->find_prebroken_piece (RIGHT);
357 Item *rb = r->find_prebroken_piece (LEFT);
360 breakable_column_spacing (lb,r);
363 breakable_column_spacing (l, rb);
365 breakable_column_spacing (lb, rb);
370 Real note_space = note_spacing (me,lc, rc, shortest_in_measure <? base_shortest_duration);
371 Real hinterfleisch = note_space;
372 Real headwid = gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
374 SCM seq = lc->get_grob_property ("right-neighbors");
377 hinterfleisch = hind-meat = amount of space following a note.
380 We adjust the space following a note only if the next note
381 happens after the current note (this is set in the grob
382 property SPACING-SEQUENCE. */
384 Real stretch_distance = note_space;
386 hinterfleisch = -1.0;
387 Real max_factor = 0.0;
388 for (SCM s = seq; gh_pair_p (s); s = ly_cdr (s))
390 Grob * wish = unsmob_grob (gh_car (s));
392 if (Note_spacing::left_column (wish) != lc
393 || Note_spacing::right_column (wish) != rc)
397 This is probably a waste of time in the case of polyphonic
399 if (Note_spacing::has_interface (wish))
401 hinterfleisch = hinterfleisch >?
404 (note_space + Note_spacing::get_spacing (wish))
405 *gh_scm2double (wish->get_grob_property ("space-factor"))
407 + Note_spacing::stem_dir_correction (wish));
411 if (hinterfleisch < 0)
413 // maybe should issue a programming error.
414 hinterfleisch = note_space;
417 stretch_distance -= headwid; // why?
419 if (max_factor == 0.0)
423 s.distance_f_ = max_factor * hinterfleisch;
424 s.strength_f_ = 1 / stretch_distance;
426 s.item_l_drul_[LEFT] = l;
427 s.item_l_drul_[RIGHT] = r;
430 if (r->find_prebroken_piece (LEFT))
432 s.item_l_drul_[RIGHT] = r->find_prebroken_piece(LEFT);
441 Read hints from L (todo: R) and generate springs.
444 Third_spacing_spanner::breakable_column_spacing (Item* l, Item *r)
448 Real break_dist = 0.0;
449 SCM espace = l->get_grob_property ("extra-space");
450 if (gh_pair_p (espace))
451 break_dist += gh_scm2double (ly_cdr (espace));
456 Real break_stretch = 0.0;
458 // todo: naming of "distance"
459 espace = l->get_grob_property ("stretch-distance");
460 if (gh_pair_p (espace))
461 break_stretch += gh_scm2double (ly_cdr (espace));
466 s.distance_f_ = break_dist;
467 s.strength_f_ = 1/break_stretch;
468 s.item_l_drul_[LEFT] = l;
469 s.item_l_drul_[RIGHT] = r;
476 Get the measure wide ant for arithmetic spacing.
479 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
480 OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
481 The Ohio State University, 1987.
485 Third_spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest)
487 Real log = log_2 (shortest.main_part_);
488 Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
491 Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
493 return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
498 Third_spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
501 Moment shortest_playing_len = 0;
502 SCM s = lc->get_grob_property ("shortest-playing-duration");
505 if (unsmob_moment (s))
506 shortest_playing_len = *unsmob_moment (s);
508 if (! shortest_playing_len.to_bool ())
510 programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
511 shortest_playing_len = 1;
514 if (! shortest.to_bool ())
516 programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
519 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
522 if (delta_t.main_part_)
524 dist = get_duration_space (me, shortest_playing_len, shortest);
525 dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_);
527 else if (delta_t.grace_part_)
529 dist = get_duration_space (me, shortest, shortest);
531 Real grace_fact = 1.0;
532 SCM gf = me->get_grob_property ("grace-space-factor");
533 if (gh_number_p (gf))
534 grace_fact = gh_scm2double (gf);
541 TODO: figure out how to space grace notes.
545 + grace_fact * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
548 Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
549 Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
553 if (lm->grace_part_ && rm->grace_part_)
555 else if (!rm->grace_part_ && lm->grace_part_)