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<Grob> 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);
116 cout << "params for cols " << Paper_column::rank_i (l) << " " << Paper_column::rank_i (r) << endl;
117 cout << " musical: " << Paper_column::musical_b (l) << " " << Paper_column::musical_b (r) << endl;
120 if (!Paper_column::musical_b (l))
122 breakable_column_spacing (l, r);
124 l = l->find_prebroken_piece (RIGHT);
126 breakable_column_spacing (l,r);
131 Real note_space = note_spacing (me,lc, rc, shortest <? base_shortest_duration);
132 Real hinterfleisch = note_space;
133 Real headwid = gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
135 for (SCM s = lc->get_grob_property ("spacing-sequence"); gh_pair_p (s); s = gh_cdr (s))
137 Grob *lm = unsmob_grob (gh_caar (s));
138 Grob *rm = unsmob_grob (gh_cdar (s));
140 // TODO; configgable.
141 hinterfleisch += -headwid + Separation_item::my_width (lm)[RIGHT] -
142 0.5 * Separation_item::my_width (rm)[LEFT];
149 // if (delta_t > Moment (1,32))
150 hinterfleisch += stem_dir_correction (me, l, r);
153 Real stretch_distance = note_space - headwid;
155 s.distance_f_ = hinterfleisch;
156 s.strength_f_ = 1 / stretch_distance;
158 s.item_l_drul_[LEFT] = l;
159 s.item_l_drul_[RIGHT] = r;
162 if (r->find_prebroken_piece (LEFT))
164 s.item_l_drul_[RIGHT] = r->find_prebroken_piece(LEFT);
172 Read hints from L (todo: R) and generate springs.
175 New_spacing_spanner::breakable_column_spacing (Item* l, Item *r)
179 Real break_dist = 0.0;
180 SCM espace = l->get_grob_property ("extra-space");
181 if (gh_pair_p (espace))
182 break_dist += gh_scm2double (gh_cdr (espace));
187 Real break_stretch = 0.0;
189 // todo: naming of "distance"
190 espace = l->get_grob_property ("stretch-distance");
191 if (gh_pair_p (espace))
192 break_stretch += gh_scm2double (gh_cdr (espace));
197 s.distance_f_ = break_dist;
198 s.strength_f_ = 1/break_stretch;
199 s.item_l_drul_[LEFT] = l;
200 s.item_l_drul_[RIGHT] = r;
206 Look at COLS, searching for columns that have 'regular-distance-to
207 set. A sequence of columns that have this property set should have
208 an equal distance (an equispaced run). Extract the projected
209 distance from SPRINGS, and scale SPRINGS for the equispaced run, to the
210 widest space necessary.
215 -- inefficient code; maybe it is easier to twiddle with the springs
216 after they've become grob properties (ie. have their
217 minimum-distances set)
219 -- does not adjust strength field of the springs very well: result
220 awkward spacing at the start of a line. (?)
222 -- will be confused when there are multiple equispaced runs in a measure.
224 -- dealing with springs for line breaks is a little tricky; in any
225 case, we will only space per measure.
227 -- we scale to actual distances, not to optical effects. Eg. if the
228 equispaced run contains optical corrections, then the scaling will
231 -- Regular_spacing_engraver doesn't mark the first column of the
232 next bar, making the space before a barline too short, in this case
236 x(8th) x(8th) <- equispaced run.
241 New_spacing_spanner::stretch_to_regularity (Grob *me,
242 Array<Spring> * springs,
243 Link_array<Grob> const & cols)
246 Find the starting column of the run. REGULAR-DISTANCE-TO points
247 back to a previous column, so we look ahead to find a column
248 pointing back to the first one.
251 Grob * first_regular_spaced_col = 0;
252 for (int i = 0 ; i < cols.size () && !first_regular_spaced_col; i++)
254 SCM rdt = cols[i]->get_grob_property ("regular-distance-to");
255 if (cols.find_l (dynamic_cast<Item*> (unsmob_grob (rdt))))
256 first_regular_spaced_col = unsmob_grob (rdt);
258 for (int i = springs->size (); i-- ;)
259 springs->elem (i).set_to_cols ();
262 for (i = 0; i < springs->size ()
263 && springs->elem (i).item_l_drul_[RIGHT] != first_regular_spaced_col;
268 if (i==springs->size ())
273 Grob *last_col = first_regular_spaced_col;
274 Grob *last_regular_spaced_col = first_regular_spaced_col;
278 find the max distance for this run.
280 for (int j = i; j < springs->size (); j++)
282 Spring *s = &(springs->elem_ref (j));
283 if (s->item_l_drul_[LEFT] != last_col)
286 dist += s->distance_f_;
288 last_col = s->item_l_drul_[RIGHT];
289 SCM rdt = last_col->get_grob_property ("regular-distance-to");
290 if (unsmob_grob (rdt) == last_regular_spaced_col)
292 maxdist = maxdist >? dist;
294 last_regular_spaced_col = last_col;
303 last_col = first_regular_spaced_col;
304 last_regular_spaced_col = first_regular_spaced_col;
305 for (int j = i; j < springs->size (); j++)
307 Spring *s = &springs->elem_ref (j);
308 if (s->item_l_drul_[LEFT] != last_col)
310 dist += s->distance_f_;
312 last_col = s->item_l_drul_[RIGHT];
313 SCM rdt = last_col->get_grob_property ("regular-distance-to");
314 if (unsmob_grob (rdt) == last_regular_spaced_col)
317 springs->elem_ref (i).distance_f_ *= maxdist / dist;
318 springs->elem_ref (i).strength_f_ *= dist / maxdist;
320 last_regular_spaced_col = last_col;
327 Do something if breakable column has no spacing hints set.
330 New_spacing_spanner::default_bar_spacing (Grob*me, Grob *lc, Grob *rc,
333 Real symbol_distance = lc->extent (lc,X_AXIS)[RIGHT] ;
334 Real durational_distance = 0;
335 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
338 ugh should use shortest_playing distance
340 if (delta_t.to_bool ())
342 durational_distance = get_duration_space (me, delta_t, shortest);
345 return symbol_distance >? durational_distance;
350 Get the measure wide ant for arithmetic spacing.
353 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
354 OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
355 The Ohio State University, 1987.
359 New_spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest)
361 Real log = log_2 (shortest.main_part_);
362 Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
365 Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
367 return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
372 New_spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
375 Moment shortest_playing_len = 0;
376 SCM s = lc->get_grob_property ("shortest-playing-duration");
378 // SCM s = lc->get_grob_property ("mean-playing-duration");
379 if (unsmob_moment (s))
380 shortest_playing_len = *unsmob_moment (s);
382 if (! shortest_playing_len.to_bool ())
384 programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
385 shortest_playing_len = 1;
388 if (! shortest.to_bool ())
390 programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
393 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
394 Real dist = get_duration_space (me, shortest_playing_len, shortest);
398 ugh: 0.1 is an arbitrary distance.
400 dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_)
401 + 0.1 * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
405 Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
406 Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
410 if (lm->grace_part_ && rm->grace_part_)
412 else if (!rm->grace_part_ && lm->grace_part_)
422 Correct for optical illusions. See [Wanske] p. 138. The combination
423 up-stem + down-stem should get extra space, the combination
424 down-stem + up-stem less.
426 This should be more advanced, since relative heights of the note
427 heads also influence required correction.
429 Also might not work correctly in case of multi voices or staff
432 TODO: lookup correction distances? More advanced correction?
433 Possibly turn this off?
435 TODO: have to check wether the stems are in the same staff.
437 This routine reads the DIR-LIST property of both its L and R arguments. */
439 New_spacing_spanner::stem_dir_correction (Grob*me, Grob*l, Grob*r)
441 SCM dl = l->get_grob_property ("dir-list");
442 SCM dr = r->get_grob_property ("dir-list");
444 if (scm_ilength (dl) != 1 || scm_ilength (dr) != 1)
450 assert (gh_number_p (dl) && gh_number_p (dr));
451 int d1 = gh_scm2int (dl);
452 int d2 = gh_scm2int (dr);
458 Real correction = 0.0;
459 Real ssc = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
461 if (d1 && d2 && d1 * d2 == -1)
463 correction = d1 * ssc;
466 programming_error ("Stem directions not set correctly for optical correction");
471 MAKE_SCHEME_CALLBACK (New_spacing_spanner, set_springs,1);
473 New_spacing_spanner::set_springs (SCM smob)
475 Grob *me = unsmob_grob (smob);
476 Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
480 for (int i = 1; i < all.size (); i++)
483 if (Item::breakable_b (sc))
485 Link_array<Grob> measure (all.slice (j, i+1));
486 do_measure (me, measure);
492 farewell, cruel world
495 return SCM_UNSPECIFIED;
501 maximum-duration-for-spacing
502 From: bf250@freenet.carleton.ca (John Sankey)
503 To: gnu-music-discuss@gnu.org
504 Subject: note spacing suggestion
505 Date: Mon, 10 Jul 2000 11:28:03 -0400 (EDT)
507 Currently, Lily spaces notes by starting with a basic distance,
508 arithmetic_multiplier, which it applies to the minimum duration note
509 of the bar. Then she adds a logarithmic increment, scaled from
510 arithmetic_basicspace, for longer notes. (Then, columns are aligned
511 and justified.) Fundamentally, this matches visual spacing to musical
512 weight and works well.
514 A lot of the time in music, I see a section basically in melodic
515 notes that occasionally has a rapid ornamental run (scale). So, there
516 will be a section in 1/4 notes, then a brief passage in 1/32nds, then
517 a return to long notes. Currently, Lily gives the same horizontal
518 space to the 1/32nd notes in their bar (even if set in small size as
519 is commonly done for cadenzii) as she gives to 1/4 notes in bars
520 where 1/4 note is the minimum duration. The resulting visual weight
521 does not match the musical weight over the page.
523 Looking at the music I am typesetting, I feel that Lily's spacing
524 could be significantly improved if, with no change in the basic
525 method used, arithmetic_multiplier could be applied referred to the
526 same duration throughout a piece. Of course, the current method
527 should be retained for those who have already set music in it, so I
528 suggest a property called something like arithmetic_base=16 to fix
529 1/16 duration as the reference for arithmetic_multiplier; the default
530 would be a dynamic base is it is now.
532 Does anyone else feel that this would be a useful improvement for
533 their music? (Of course, if arithmetic_multiplier became a regular
534 property, this could be used to achieve a similar result by