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> *cols)
44 Moment shortest_in_measure;
47 space as if this duration is present.
49 Moment base_shortest_duration = *unsmob_moment (me->get_grob_property ("maximum-duration-for-spacing"));
50 shortest_in_measure.set_infinite (1);
52 for (int i = cols->size(); i--;)
54 if (gh_pair_p (cols->elem(i)->get_grob_property ("between-cols")))
59 for (int i =0 ; i < cols->size (); i++)
61 if (Paper_column::musical_b (cols->elem (i)))
63 Moment *when = unsmob_moment (cols->elem (i)->get_grob_property ("when"));
66 ignore grace notes for shortest notes.
68 if (when && when->grace_part_)
71 SCM st = cols->elem (i)->get_grob_property ("shortest-starter-duration");
72 Moment this_shortest = *unsmob_moment (st);
73 shortest_in_measure = shortest_in_measure <? this_shortest;
77 Array<Spring> springs;
80 for (int i= 0; i < cols->size () - 1; i++)
82 Item * l = dynamic_cast<Item*> (cols->elem (i));
84 if (!first_col && Paper_column::musical_b (l))
87 Item * r = dynamic_cast<Item*> (cols->elem (i+1));
88 Paper_column * lc = dynamic_cast<Paper_column*> (l);
89 Paper_column *rc = dynamic_cast<Paper_column*> (r);
93 cout << "params for cols " << Paper_column::rank_i (l) << " " << Paper_column::rank_i (r) << endl;
94 cout << " musical: " << Paper_column::musical_b (l) << " " << Paper_column::musical_b (r) << endl;
97 if (!Paper_column::musical_b (l))
99 breakable_column_spacing (l, r);
101 l = l->find_prebroken_piece (RIGHT);
103 breakable_column_spacing (l,r);
108 Real note_space = note_spacing (me,lc, rc, shortest_in_measure <? base_shortest_duration);
109 Real hinterfleisch = note_space;
110 Real headwid = gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
112 SCM seq = lc->get_grob_property ("spacing-sequence");
114 Moment dt = Paper_column::when_mom (r) - Paper_column::when_mom (l);
117 hinterfleisch = hind-meat = amount of space following a note.
120 We adjust the space following a note only if the next note
121 happens after the current note (this is set in the grob
122 property SPACING-SEQUENCE. */
124 Real stretch_distance = note_space;
125 if (shortest_in_measure <= dt)
128 currently SPACING-SEQUENCE is set in
129 Separating_group_spanner::find_musical_sequences (), which
130 works neatly for one-voice-per staff, however,
132 it can't find out the actual duration of the notes on a
133 staff, so when putting tuplets and normal patterns it gets
134 confused, (ie. believes that < { c8 c8 } { d8 d8 d8 }*2/3
135 > contains 1/12 notes. ).
137 here we kludge, by checking if the distance we're spacing
138 for is less than the shortest note.
142 Move SPACING-SEQUENCE detection into a voice
143 level-engraver --or-- make sure that every column has
144 access to the note head.
147 for (SCM s = seq; gh_pair_p (s); s = ly_cdr (s))
149 Grob *lm = unsmob_grob (gh_caar (s));
150 Grob *rm = unsmob_grob (gh_cdar (s));
152 // TODO; configgable.
153 hinterfleisch += -headwid + Separation_item::my_width (lm)[RIGHT] -
154 0.5 * Separation_item::my_width (rm)[LEFT];
157 hinterfleisch += stem_dir_correction (me, l, r);
162 stretch_distance -= headwid;
165 s.distance_f_ = hinterfleisch;
166 s.strength_f_ = 1 / stretch_distance;
168 s.item_l_drul_[LEFT] = l;
169 s.item_l_drul_[RIGHT] = r;
172 if (r->find_prebroken_piece (LEFT))
174 s.item_l_drul_[RIGHT] = r->find_prebroken_piece(LEFT);
182 Read hints from L (todo: R) and generate springs.
185 New_spacing_spanner::breakable_column_spacing (Item* l, Item *r)
189 Real break_dist = 0.0;
190 SCM espace = l->get_grob_property ("extra-space");
191 if (gh_pair_p (espace))
192 break_dist += gh_scm2double (ly_cdr (espace));
197 Real break_stretch = 0.0;
199 // todo: naming of "distance"
200 espace = l->get_grob_property ("stretch-distance");
201 if (gh_pair_p (espace))
202 break_stretch += gh_scm2double (ly_cdr (espace));
207 s.distance_f_ = break_dist;
208 s.strength_f_ = 1/break_stretch;
209 s.item_l_drul_[LEFT] = l;
210 s.item_l_drul_[RIGHT] = r;
216 Look at COLS, searching for columns that have 'regular-distance-to
217 set. A sequence of columns that have this property set should have
218 an equal distance (an equispaced run). Extract the projected
219 distance from SPRINGS, and scale SPRINGS for the equispaced run, to the
220 widest space necessary.
225 -- inefficient code; maybe it is easier to twiddle with the springs
226 after they've become grob properties (ie. have their
227 minimum-distances set)
229 -- does not adjust strength field of the springs very well: result
230 awkward spacing at the start of a line. (?)
232 -- will be confused when there are multiple equispaced runs in a measure.
234 -- dealing with springs for line breaks is a little tricky; in any
235 case, we will only space per measure.
237 -- we scale to actual distances, not to optical effects. Eg. if the
238 equispaced run contains optical corrections, then the scaling will
241 -- Regular_spacing_engraver doesn't mark the first column of the
242 next bar, making the space before a barline too short, in this case
246 x(8th) x(8th) <- equispaced run.
251 New_spacing_spanner::stretch_to_regularity (Grob *me,
252 Array<Spring> * springs,
253 Link_array<Grob> const & cols)
256 Find the starting column of the run. REGULAR-DISTANCE-TO points
257 back to a previous column, so we look ahead to find a column
258 pointing back to the first one.
261 Grob * first_regular_spaced_col = 0;
262 for (int i = 0 ; i < cols.size () && !first_regular_spaced_col; i++)
264 SCM rdt = cols[i]->get_grob_property ("regular-distance-to");
265 if (cols.find_l (dynamic_cast<Item*> (unsmob_grob (rdt))))
266 first_regular_spaced_col = unsmob_grob (rdt);
268 for (int i = springs->size (); i-- ;)
269 springs->elem (i).set_to_cols ();
272 for (i = 0; i < springs->size ()
273 && springs->elem (i).item_l_drul_[RIGHT] != first_regular_spaced_col;
278 if (i==springs->size ())
283 Grob *last_col = first_regular_spaced_col;
284 Grob *last_regular_spaced_col = first_regular_spaced_col;
288 find the max distance for this run.
290 for (int j = i; j < springs->size (); j++)
292 Spring *s = &(springs->elem_ref (j));
293 if (s->item_l_drul_[LEFT] != last_col)
296 dist += s->distance_f_;
298 last_col = s->item_l_drul_[RIGHT];
299 SCM rdt = last_col->get_grob_property ("regular-distance-to");
300 if (unsmob_grob (rdt) == last_regular_spaced_col)
302 maxdist = maxdist >? dist;
304 last_regular_spaced_col = last_col;
313 last_col = first_regular_spaced_col;
314 last_regular_spaced_col = first_regular_spaced_col;
315 for (int j = i; j < springs->size (); j++)
317 Spring *s = &springs->elem_ref (j);
318 if (s->item_l_drul_[LEFT] != last_col)
320 dist += s->distance_f_;
322 last_col = s->item_l_drul_[RIGHT];
323 SCM rdt = last_col->get_grob_property ("regular-distance-to");
324 if (unsmob_grob (rdt) == last_regular_spaced_col)
327 springs->elem_ref (i).distance_f_ *= maxdist / dist;
328 springs->elem_ref (i).strength_f_ *= dist / maxdist;
330 last_regular_spaced_col = last_col;
337 Do something if breakable column has no spacing hints set.
340 New_spacing_spanner::default_bar_spacing (Grob*me, Grob *lc, Grob *rc,
343 Real symbol_distance = lc->extent (lc,X_AXIS)[RIGHT] ;
344 Real durational_distance = 0;
345 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
348 ugh should use shortest_playing distance
350 if (delta_t.to_bool ())
352 durational_distance = get_duration_space (me, delta_t, shortest);
355 return symbol_distance >? durational_distance;
360 Get the measure wide ant for arithmetic spacing.
363 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
364 OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
365 The Ohio State University, 1987.
369 New_spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest)
371 Real log = log_2 (shortest.main_part_);
372 Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
375 Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
377 return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
382 New_spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
385 Moment shortest_playing_len = 0;
386 SCM s = lc->get_grob_property ("shortest-playing-duration");
389 if (unsmob_moment (s))
390 shortest_playing_len = *unsmob_moment (s);
392 if (! shortest_playing_len.to_bool ())
394 programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
395 shortest_playing_len = 1;
398 if (! shortest.to_bool ())
400 programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
403 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
406 if (delta_t.main_part_)
408 dist = get_duration_space (me, shortest_playing_len, shortest);
409 dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_);
411 else if (delta_t.grace_part_)
413 dist = get_duration_space (me, shortest, shortest);
415 Real grace_fact = 1.0;
416 SCM gf = me->get_grob_property ("grace-space-factor");
417 if (gh_number_p (gf))
418 grace_fact = gh_scm2double (gf);
425 TODO: figure out how to space grace notes.
429 + grace_fact * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
432 Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
433 Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
437 if (lm->grace_part_ && rm->grace_part_)
439 else if (!rm->grace_part_ && lm->grace_part_)
449 Correct for optical illusions. See [Wanske] p. 138. The combination
450 up-stem + down-stem should get extra space, the combination
451 down-stem + up-stem less.
453 This should be more advanced, since relative heights of the note
454 heads also influence required correction.
456 Also might not work correctly in case of multi voices or staff
459 TODO: lookup correction distances? More advanced correction?
460 Possibly turn this off?
462 TODO: have to check wether the stems are in the same staff.
464 This routine reads the DIR-LIST property of both its L and R arguments. */
466 New_spacing_spanner::stem_dir_correction (Grob*me, Grob*l, Grob*r)
468 SCM dl = l->get_grob_property ("dir-list");
469 SCM dr = r->get_grob_property ("dir-list");
471 if (scm_ilength (dl) != 1 || scm_ilength (dr) != 1)
477 assert (gh_number_p (dl) && gh_number_p (dr));
478 int d1 = gh_scm2int (dl);
479 int d2 = gh_scm2int (dr);
485 Real correction = 0.0;
486 Real ssc = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
488 if (d1 && d2 && d1 * d2 == -1)
490 correction = d1 * ssc;
493 programming_error ("Stem directions not set correctly for optical correction");
498 MAKE_SCHEME_CALLBACK (New_spacing_spanner, set_springs,1);
500 New_spacing_spanner::set_springs (SCM smob)
502 Grob *me = unsmob_grob (smob);
503 Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
507 for (int i = 1; i < all.size (); i++)
510 if (Item::breakable_b (sc))
512 Link_array<Grob> measure (all.slice (j, i+1));
513 do_measure (me, &measure);
519 farewell, cruel world
522 return SCM_UNSPECIFIED;
528 maximum-duration-for-spacing
529 From: bf250@freenet.carleton.ca (John Sankey)
530 To: gnu-music-discuss@gnu.org
531 Subject: note spacing suggestion
532 Date: Mon, 10 Jul 2000 11:28:03 -0400 (EDT)
534 Currently, Lily spaces notes by starting with a basic distance,
535 arithmetic_multiplier, which it applies to the minimum duration note
536 of the bar. Then she adds a logarithmic increment, scaled from
537 arithmetic_basicspace, for longer notes. (Then, columns are aligned
538 and justified.) Fundamentally, this matches visual spacing to musical
539 weight and works well.
541 A lot of the time in music, I see a section basically in melodic
542 notes that occasionally has a rapid ornamental run (scale). So, there
543 will be a section in 1/4 notes, then a brief passage in 1/32nds, then
544 a return to long notes. Currently, Lily gives the same horizontal
545 space to the 1/32nd notes in their bar (even if set in small size as
546 is commonly done for cadenzii) as she gives to 1/4 notes in bars
547 where 1/4 note is the minimum duration. The resulting visual weight
548 does not match the musical weight over the page.
550 Looking at the music I am typesetting, I feel that Lily's spacing
551 could be significantly improved if, with no change in the basic
552 method used, arithmetic_multiplier could be applied referred to the
553 same duration throughout a piece. Of course, the current method
554 should be retained for those who have already set music in it, so I
555 suggest a property called something like arithmetic_base=16 to fix
556 1/16 duration as the reference for arithmetic_multiplier; the default
557 would be a dynamic base is it is now.
559 Does anyone else feel that this would be a useful improvement for
560 their music? (Of course, if arithmetic_multiplier became a regular
561 property, this could be used to achieve a similar result by