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<Item> 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;
80 for (int i= 0; i < cols.size () - 1; i++)
82 Item * l = dynamic_cast<Item*> (cols[i]);
84 if (!first_col && Paper_column::musical_b (l))
87 SCM between = cols[i]->get_grob_property ("between-cols");
88 if (gh_pair_p (between)
91 && (gh_cdr (between) != cols[i+1]->self_scm ()
92 || gh_car (between) != cols[i-1]->self_scm ())
97 for (; j < cols.size () - 1; j++)
99 if (Paper_column::musical_b (cols[j]))
102 SCM between = cols[j]->get_grob_property ("between-cols");
103 if (!gh_pair_p (between))
106 if (gh_car (between) == cols[i]->self_scm () )
110 Item * r = dynamic_cast<Item*> (cols[j]);
111 Paper_column * lc = dynamic_cast<Paper_column*> (l);
112 Paper_column *rc = dynamic_cast<Paper_column*> (r);
115 cout << "params for cols " << Paper_column::rank_i (l) << " " << Paper_column::rank_i (r) << endl;
116 cout << " musical: " << Paper_column::musical_b (l) << " " << Paper_column::musical_b (r) << endl;
117 if (!Paper_column::musical_b (l))
119 breakable_column_spacing (l, r);
121 l = l->find_prebroken_piece (RIGHT);
123 breakable_column_spacing (l,r);
128 Real note_space = note_spacing (me,lc, rc, shortest <? base_shortest_duration);
129 Real hinterfleisch = note_space;
130 Real headwid = gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
132 for (SCM s = lc->get_grob_property ("spacing-sequence"); gh_pair_p (s); s = gh_cdr (s))
134 Grob *lm = unsmob_grob (gh_caar (s));
135 Grob *rm = unsmob_grob (gh_cdar (s));
137 // TODO; configgable.
138 hinterfleisch += -headwid + Separation_item::my_width (lm)[RIGHT] -
139 0.5 * Separation_item::my_width (rm)[LEFT];
146 // if (delta_t > Moment (1,32))
147 hinterfleisch += stem_dir_correction (me, l, r);
150 Real stretch_distance = note_space - headwid;
152 s.distance_f_ = hinterfleisch;
153 s.strength_f_ = 1 / stretch_distance;
155 s.item_l_drul_[LEFT] = l;
156 s.item_l_drul_[RIGHT] = r;
159 if (r->find_prebroken_piece (LEFT))
161 s.item_l_drul_[RIGHT] = r->find_prebroken_piece(LEFT);
169 Read hints from L (todo: R) and generate springs.
172 New_spacing_spanner::breakable_column_spacing (Item* l, Item *r)
176 Real break_dist = 0.0;
177 SCM espace = l->get_grob_property ("extra-space");
178 if (gh_pair_p (espace))
179 break_dist += gh_scm2double (gh_cdr (espace));
184 Real break_stretch = 0.0;
186 // todo: naming of "distance"
187 espace = l->get_grob_property ("stretch-distance");
188 if (gh_pair_p (espace))
189 break_stretch += gh_scm2double (gh_cdr (espace));
194 s.distance_f_ = break_dist;
195 s.strength_f_ = 1/break_stretch;
196 s.item_l_drul_[LEFT] = l;
197 s.item_l_drul_[RIGHT] = r;
203 Look at COLS, searching for columns that have 'regular-distance-to
204 set. A sequence of columns that have this property set should have
205 an equal distance (an equispaced run). Extract the projected
206 distance from SPRINGS, and scale SPRINGS for the equispaced run, to the
207 widest space necessary.
212 -- inefficient code; maybe it is easier to twiddle with the springs
213 after they've become grob properties (ie. have their
214 minimum-distances set)
216 -- does not adjust strength field of the springs very well: result
217 awkward spacing at the start of a line. (?)
219 -- will be confused when there are multiple equispaced runs in a measure.
221 -- dealing with springs for line breaks is a little tricky; in any
222 case, we will only space per measure.
224 -- we scale to actual distances, not to optical effects. Eg. if the
225 equispaced run contains optical corrections, then the scaling will
228 -- Regular_spacing_engraver doesn't mark the first column of the
229 next bar, making the space before a barline too short, in this case
233 x(8th) x(8th) <- equispaced run.
238 New_spacing_spanner::stretch_to_regularity (Grob *me,
239 Array<Spring> * springs,
240 Link_array<Item> const & cols)
243 Find the starting column of the run. REGULAR-DISTANCE-TO points
244 back to a previous column, so we look ahead to find a column
245 pointing back to the first one.
248 Grob * first_regular_spaced_col = 0;
249 for (int i = 0 ; i < cols.size () && !first_regular_spaced_col; i++)
251 SCM rdt = cols[i]->get_grob_property ("regular-distance-to");
252 if (cols.find_l (dynamic_cast<Item*> (unsmob_grob (rdt))))
253 first_regular_spaced_col = unsmob_grob (rdt);
255 for (int i = springs->size (); i-- ;)
256 springs->elem (i).set_to_cols ();
259 for (i = 0; i < springs->size ()
260 && springs->elem (i).item_l_drul_[RIGHT] != first_regular_spaced_col;
265 if (i==springs->size ())
270 Grob *last_col = first_regular_spaced_col;
271 Grob *last_regular_spaced_col = first_regular_spaced_col;
275 find the max distance for this run.
277 for (int j = i; j < springs->size (); j++)
279 Spring *s = &(springs->elem_ref (j));
280 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)
289 maxdist = maxdist >? dist;
291 last_regular_spaced_col = last_col;
300 last_col = first_regular_spaced_col;
301 last_regular_spaced_col = first_regular_spaced_col;
302 for (int j = i; j < springs->size (); j++)
304 Spring *s = &springs->elem_ref (j);
305 if (s->item_l_drul_[LEFT] != last_col)
307 dist += s->distance_f_;
309 last_col = s->item_l_drul_[RIGHT];
310 SCM rdt = last_col->get_grob_property ("regular-distance-to");
311 if (unsmob_grob (rdt) == last_regular_spaced_col)
314 springs->elem_ref (i).distance_f_ *= maxdist / dist;
315 springs->elem_ref (i).strength_f_ *= dist / maxdist;
317 last_regular_spaced_col = last_col;
324 Do something if breakable column has no spacing hints set.
327 New_spacing_spanner::default_bar_spacing (Grob*me, Grob *lc, Grob *rc,
330 Real symbol_distance = lc->extent (lc,X_AXIS)[RIGHT] ;
331 Real durational_distance = 0;
332 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
335 ugh should use shortest_playing distance
337 if (delta_t.to_bool ())
339 durational_distance = get_duration_space (me, delta_t, shortest);
342 return symbol_distance >? durational_distance;
347 Get the measure wide ant for arithmetic spacing.
350 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
351 OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
352 The Ohio State University, 1987.
356 New_spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest)
358 Real log = log_2 (shortest.main_part_);
359 Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
362 Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
364 return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
369 New_spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
372 Moment shortest_playing_len = 0;
373 SCM s = lc->get_grob_property ("shortest-playing-duration");
375 // SCM s = lc->get_grob_property ("mean-playing-duration");
376 if (unsmob_moment (s))
377 shortest_playing_len = *unsmob_moment (s);
379 if (! shortest_playing_len.to_bool ())
381 programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
382 shortest_playing_len = 1;
385 if (! shortest.to_bool ())
387 programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
390 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
391 Real dist = get_duration_space (me, shortest_playing_len, shortest);
395 ugh: 0.1 is an arbitrary distance.
397 dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_)
398 + 0.1 * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
402 Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
403 Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
407 if (lm->grace_part_ && rm->grace_part_)
409 else if (!rm->grace_part_ && lm->grace_part_)
419 Correct for optical illusions. See [Wanske] p. 138. The combination
420 up-stem + down-stem should get extra space, the combination
421 down-stem + up-stem less.
423 This should be more advanced, since relative heights of the note
424 heads also influence required correction.
426 Also might not work correctly in case of multi voices or staff
429 TODO: lookup correction distances? More advanced correction?
430 Possibly turn this off?
432 TODO: have to check wether the stems are in the same staff.
434 This routine reads the DIR-LIST property of both its L and R arguments. */
436 New_spacing_spanner::stem_dir_correction (Grob*me, Grob*l, Grob*r)
438 SCM dl = l->get_grob_property ("dir-list");
439 SCM dr = r->get_grob_property ("dir-list");
441 if (scm_ilength (dl) != 1 || scm_ilength (dr) != 1)
447 assert (gh_number_p (dl) && gh_number_p (dr));
448 int d1 = gh_scm2int (dl);
449 int d2 = gh_scm2int (dr);
455 Real correction = 0.0;
456 Real ssc = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
458 if (d1 && d2 && d1 * d2 == -1)
460 correction = d1 * ssc;
463 programming_error ("Stem directions not set correctly for optical correction");
468 MAKE_SCHEME_CALLBACK (New_spacing_spanner, set_springs,1);
470 New_spacing_spanner::set_springs (SCM smob)
472 Grob *me = unsmob_grob (smob);
473 Link_array<Item> all (me->pscore_l_->line_l_->column_l_arr ()) ;
477 for (int i = 1; i < all.size (); i++)
480 if (Item::breakable_b (sc))
482 Link_array<Item> measure (all.slice (j, i+1));
483 do_measure (me, measure);
489 farewell, cruel world
492 return SCM_UNSPECIFIED;
498 maximum-duration-for-spacing
499 From: bf250@freenet.carleton.ca (John Sankey)
500 To: gnu-music-discuss@gnu.org
501 Subject: note spacing suggestion
502 Date: Mon, 10 Jul 2000 11:28:03 -0400 (EDT)
504 Currently, Lily spaces notes by starting with a basic distance,
505 arithmetic_multiplier, which it applies to the minimum duration note
506 of the bar. Then she adds a logarithmic increment, scaled from
507 arithmetic_basicspace, for longer notes. (Then, columns are aligned
508 and justified.) Fundamentally, this matches visual spacing to musical
509 weight and works well.
511 A lot of the time in music, I see a section basically in melodic
512 notes that occasionally has a rapid ornamental run (scale). So, there
513 will be a section in 1/4 notes, then a brief passage in 1/32nds, then
514 a return to long notes. Currently, Lily gives the same horizontal
515 space to the 1/32nd notes in their bar (even if set in small size as
516 is commonly done for cadenzii) as she gives to 1/4 notes in bars
517 where 1/4 note is the minimum duration. The resulting visual weight
518 does not match the musical weight over the page.
520 Looking at the music I am typesetting, I feel that Lily's spacing
521 could be significantly improved if, with no change in the basic
522 method used, arithmetic_multiplier could be applied referred to the
523 same duration throughout a piece. Of course, the current method
524 should be retained for those who have already set music in it, so I
525 suggest a property called something like arithmetic_base=16 to fix
526 1/16 duration as the reference for arithmetic_multiplier; the default
527 would be a dynamic base is it is now.
529 Does anyone else feel that this would be a useful improvement for
530 their music? (Of course, if arithmetic_multiplier became a regular
531 property, this could be used to achieve a similar result by