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>
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 class New_spacing_spanner
25 static void set_interface (Grob*);
26 static void do_measure (Grob*,Link_array<Grob> *) ;
27 static void stretch_to_regularity (Grob*, Array<Spring> *, Link_array<Grob> const &);
28 static void breakable_column_spacing (Item* l, Item *r);
29 DECLARE_SCHEME_CALLBACK (set_springs, (SCM ));
30 static Real default_bar_spacing (Grob*,Grob*,Grob*,Moment) ;
31 static Real note_spacing (Grob*,Grob*,Grob*,Moment) ;
32 static Real get_duration_space (Grob*,Moment dur, Moment shortest) ;
33 static void prune_loose_colunms (Link_array<Grob>*);
37 New_spacing_spanner::set_interface (Grob*me)
39 me->set_extent_callback (SCM_EOL, X_AXIS);
40 me->set_extent_callback (SCM_EOL, Y_AXIS) ;
44 Remove all columns that are not tightly
45 fitting part of the spacing problem.
48 New_spacing_spanner::prune_loose_colunms (Link_array<Grob> *cols)
50 for (int i = cols->size(); i--;)
52 SCM between = cols->elem(i)->get_grob_property ("between-cols");
53 if (!gh_pair_p (between))
56 Item * l = dynamic_cast<Item*> (unsmob_grob (gh_car (between)));
57 Item * r = dynamic_cast<Item*> (unsmob_grob (gh_cdr (between)));
58 if (l->column_l () != cols->elem (i-1)
59 || r->column_l () != cols->elem (i +1))
68 The algorithm is partly taken from :
70 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
71 OSU-CISRC-10/87-TR35, Department of Computer and Information
72 Science, The Ohio State University, 1987.
80 New_spacing_spanner::do_measure (Grob*me, Link_array<Grob> *cols)
82 Moment shortest_in_measure;
85 space as if this duration is present.
87 Moment base_shortest_duration = *unsmob_moment (me->get_grob_property ("maximum-duration-for-spacing"));
88 shortest_in_measure.set_infinite (1);
90 prune_loose_colunms (cols);
92 for (int i =0 ; i < cols->size (); i++)
94 if (Paper_column::musical_b (cols->elem (i)))
96 Moment *when = unsmob_moment (cols->elem (i)->get_grob_property ("when"));
99 ignore grace notes for shortest notes.
101 if (when && when->grace_part_)
104 SCM st = cols->elem (i)->get_grob_property ("shortest-starter-duration");
105 Moment this_shortest = *unsmob_moment (st);
106 shortest_in_measure = shortest_in_measure <? this_shortest;
110 Array<Spring> springs;
112 Item * first_col = 0;
113 for (int i= 0; i < cols->size () - 1; i++)
115 Item * l = dynamic_cast<Item*> (cols->elem (i));
117 if (!first_col && Paper_column::musical_b (l))
120 Item * r = dynamic_cast<Item*> (cols->elem (i+1));
121 Paper_column * lc = dynamic_cast<Paper_column*> (l);
122 Paper_column *rc = dynamic_cast<Paper_column*> (r);
126 cout << "params for cols " << Paper_column::rank_i (l) << " " << Paper_column::rank_i (r) << endl;
127 cout << " musical: " << Paper_column::musical_b (l) << " " << Paper_column::musical_b (r) << endl;
130 if (!Paper_column::musical_b (l))
132 breakable_column_spacing (l, r);
134 l = l->find_prebroken_piece (RIGHT);
136 breakable_column_spacing (l,r);
141 Real note_space = note_spacing (me,lc, rc, shortest_in_measure <? base_shortest_duration);
142 Real hinterfleisch = note_space;
143 Real headwid = gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
145 SCM seq = lc->get_grob_property ("spacing-sequence");
147 Moment dt = Paper_column::when_mom (r) - Paper_column::when_mom (l);
150 hinterfleisch = hind-meat = amount of space following a note.
153 We adjust the space following a note only if the next note
154 happens after the current note (this is set in the grob
155 property SPACING-SEQUENCE. */
157 Real stretch_distance = note_space;
158 if (shortest_in_measure <= dt)
161 currently SPACING-SEQUENCE is set in
162 Separating_group_spanner::find_musical_sequences (), which
163 works neatly for one-voice-per staff, however,
165 it can't find out the actual duration of the notes on a
166 staff, so when putting tuplets and normal patterns it gets
167 confused, (ie. believes that < { c8 c8 } { d8 d8 d8 }*2/3
168 > contains 1/12 notes. ).
170 here we kludge, by checking if the distance we're spacing
171 for is less than the shortest note.
175 Move SPACING-SEQUENCE detection into a voice
176 level-engraver --or-- make sure that every column has
177 access to the note head.
180 for (SCM s = seq; gh_pair_p (s); s = ly_cdr (s))
182 Grob *lm = unsmob_grob (ly_caar (s));
183 Grob *rm = unsmob_grob (ly_cdar (s));
185 // TODO; configgable.
186 hinterfleisch += -headwid + Separation_item::my_width (lm)[RIGHT] -
187 0.5 * Separation_item::my_width (rm)[LEFT];
193 stretch_distance -= headwid;
196 s.distance_f_ = hinterfleisch;
197 s.strength_f_ = 1 / stretch_distance;
199 s.item_l_drul_[LEFT] = l;
200 s.item_l_drul_[RIGHT] = r;
203 if (r->find_prebroken_piece (LEFT))
205 s.item_l_drul_[RIGHT] = r->find_prebroken_piece(LEFT);
213 Read hints from L (todo: R) and generate springs.
216 New_spacing_spanner::breakable_column_spacing (Item* l, Item *r)
220 Real break_dist = 0.0;
221 SCM espace = l->get_grob_property ("extra-space");
222 if (gh_pair_p (espace))
223 break_dist += gh_scm2double (ly_cdr (espace));
228 Real break_stretch = 0.0;
230 // todo: naming of "distance"
231 espace = l->get_grob_property ("stretch-distance");
232 if (gh_pair_p (espace))
233 break_stretch += gh_scm2double (ly_cdr (espace));
238 s.distance_f_ = break_dist;
239 s.strength_f_ = 1/break_stretch;
240 s.item_l_drul_[LEFT] = l;
241 s.item_l_drul_[RIGHT] = r;
247 Look at COLS, searching for columns that have 'regular-distance-to
248 set. A sequence of columns that have this property set should have
249 an equal distance (an equispaced run). Extract the projected
250 distance from SPRINGS, and scale SPRINGS for the equispaced run, to the
251 widest space necessary.
256 -- inefficient code; maybe it is easier to twiddle with the springs
257 after they've become grob properties (ie. have their
258 minimum-distances set)
260 -- does not adjust strength field of the springs very well: result
261 awkward spacing at the start of a line. (?)
263 -- will be confused when there are multiple equispaced runs in a measure.
265 -- dealing with springs for line breaks is a little tricky; in any
266 case, we will only space per measure.
268 -- we scale to actual distances, not to optical effects. Eg. if the
269 equispaced run contains optical corrections, then the scaling will
272 -- Regular_spacing_engraver doesn't mark the first column of the
273 next bar, making the space before a barline too short, in this case
277 x(8th) x(8th) <- equispaced run.
282 New_spacing_spanner::stretch_to_regularity (Grob *me,
283 Array<Spring> * springs,
284 Link_array<Grob> const & cols)
287 Find the starting column of the run. REGULAR-DISTANCE-TO points
288 back to a previous column, so we look ahead to find a column
289 pointing back to the first one.
292 Grob * first_regular_spaced_col = 0;
293 for (int i = 0 ; i < cols.size () && !first_regular_spaced_col; i++)
295 SCM rdt = cols[i]->get_grob_property ("regular-distance-to");
296 if (cols.find_l (dynamic_cast<Item*> (unsmob_grob (rdt))))
297 first_regular_spaced_col = unsmob_grob (rdt);
299 for (int i = springs->size (); i-- ;)
300 springs->elem (i).set_to_cols ();
303 for (i = 0; i < springs->size ()
304 && springs->elem (i).item_l_drul_[RIGHT] != first_regular_spaced_col;
309 if (i==springs->size ())
314 Grob *last_col = first_regular_spaced_col;
315 Grob *last_regular_spaced_col = first_regular_spaced_col;
319 find the max distance for this run.
321 for (int j = i; j < springs->size (); j++)
323 Spring *s = &(springs->elem_ref (j));
324 if (s->item_l_drul_[LEFT] != last_col)
327 dist += s->distance_f_;
329 last_col = s->item_l_drul_[RIGHT];
330 SCM rdt = last_col->get_grob_property ("regular-distance-to");
331 if (unsmob_grob (rdt) == last_regular_spaced_col)
333 maxdist = maxdist >? dist;
335 last_regular_spaced_col = last_col;
344 last_col = first_regular_spaced_col;
345 last_regular_spaced_col = first_regular_spaced_col;
346 for (int j = i; j < springs->size (); j++)
348 Spring *s = &springs->elem_ref (j);
349 if (s->item_l_drul_[LEFT] != last_col)
351 dist += s->distance_f_;
353 last_col = s->item_l_drul_[RIGHT];
354 SCM rdt = last_col->get_grob_property ("regular-distance-to");
355 if (unsmob_grob (rdt) == last_regular_spaced_col)
358 springs->elem_ref (i).distance_f_ *= maxdist / dist;
359 springs->elem_ref (i).strength_f_ *= dist / maxdist;
361 last_regular_spaced_col = last_col;
368 Do something if breakable column has no spacing hints set.
371 New_spacing_spanner::default_bar_spacing (Grob*me, Grob *lc, Grob *rc,
374 Real symbol_distance = lc->extent (lc,X_AXIS)[RIGHT] ;
375 Real durational_distance = 0;
376 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
379 ugh should use shortest_playing distance
381 if (delta_t.to_bool ())
383 durational_distance = get_duration_space (me, delta_t, shortest);
386 return symbol_distance >? durational_distance;
391 Get the measure wide ant for arithmetic spacing.
394 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
395 OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
396 The Ohio State University, 1987.
400 New_spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest)
402 Real log = log_2 (shortest.main_part_);
403 Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
406 Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
408 return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
413 New_spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
416 Moment shortest_playing_len = 0;
417 SCM s = lc->get_grob_property ("shortest-playing-duration");
420 if (unsmob_moment (s))
421 shortest_playing_len = *unsmob_moment (s);
423 if (! shortest_playing_len.to_bool ())
425 programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
426 shortest_playing_len = 1;
429 if (! shortest.to_bool ())
431 programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
434 Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
437 if (delta_t.main_part_)
439 dist = get_duration_space (me, shortest_playing_len, shortest);
440 dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_);
442 else if (delta_t.grace_part_)
444 dist = get_duration_space (me, shortest, shortest);
446 Real grace_fact = 1.0;
447 SCM gf = me->get_grob_property ("grace-space-factor");
448 if (gh_number_p (gf))
449 grace_fact = gh_scm2double (gf);
456 TODO: figure out how to space grace notes.
460 + grace_fact * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
463 Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
464 Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
468 if (lm->grace_part_ && rm->grace_part_)
470 else if (!rm->grace_part_ && lm->grace_part_)
480 MAKE_SCHEME_CALLBACK (New_spacing_spanner, set_springs,1);
482 New_spacing_spanner::set_springs (SCM smob)
484 Grob *me = unsmob_grob (smob);
485 Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
489 for (int i = 1; i < all.size (); i++)
492 if (Item::breakable_b (sc))
494 Link_array<Grob> measure (all.slice (j, i+1));
495 do_measure (me, &measure);
501 farewell, cruel world
504 return SCM_UNSPECIFIED;
510 maximum-duration-for-spacing
511 From: bf250@freenet.carleton.ca (John Sankey)
512 To: gnu-music-discuss@gnu.org
513 Subject: note spacing suggestion
514 Date: Mon, 10 Jul 2000 11:28:03 -0400 (EDT)
516 Currently, Lily spaces notes by starting with a basic distance,
517 arithmetic_multiplier, which it applies to the minimum duration note
518 of the bar. Then she adds a logarithmic increment, scaled from
519 arithmetic_basicspace, for longer notes. (Then, columns are aligned
520 and justified.) Fundamentally, this matches visual spacing to musical
521 weight and works well.
523 A lot of the time in music, I see a section basically in melodic
524 notes that occasionally has a rapid ornamental run (scale). So, there
525 will be a section in 1/4 notes, then a brief passage in 1/32nds, then
526 a return to long notes. Currently, Lily gives the same horizontal
527 space to the 1/32nd notes in their bar (even if set in small size as
528 is commonly done for cadenzii) as she gives to 1/4 notes in bars
529 where 1/4 note is the minimum duration. The resulting visual weight
530 does not match the musical weight over the page.
532 Looking at the music I am typesetting, I feel that Lily's spacing
533 could be significantly improved if, with no change in the basic
534 method used, arithmetic_multiplier could be applied referred to the
535 same duration throughout a piece. Of course, the current method
536 should be retained for those who have already set music in it, so I
537 suggest a property called something like arithmetic_base=16 to fix
538 1/16 duration as the reference for arithmetic_multiplier; the default
539 would be a dynamic base is it is now.
541 Does anyone else feel that this would be a useful improvement for
542 their music? (Of course, if arithmetic_multiplier became a regular
543 property, this could be used to achieve a similar result by