]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-head.cc
* lily/ledger-line-engraver.cc: new file.
[lilypond.git] / lily / note-head.cc
1 /*
2   notehead.cc -- implement Note_head
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1997--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8 #include <math.h>
9 #include <ctype.h>
10
11 #include "staff-symbol.hh"
12 #include "misc.hh"
13 #include "dots.hh"
14 #include "note-head.hh"
15 #include "warn.hh"
16 #include "font-interface.hh"
17 #include "stencil.hh"
18 #include "event.hh"
19 #include "rhythmic-head.hh"
20 #include "staff-symbol-referencer.hh"
21 #include "lookup.hh"
22 #include "output-def.hh"
23
24 /*
25   Note_head contains the code for printing note heads.
26
27   Ledger lines:
28
29   It also contains the ledger lines, for historical reasons.  Ledger
30   lines are somewhat of a PITA. In some cases, they take up no space, in
31   some cases they don't:
32
33   DO take space:
34
35   - when ledgered notes are juxtaposed: there should be some white
36    space between the ledger lines.
37
38   - when accidentals are near: the accidentals should not be on the
39   ledger lines
40
41   [both tips by Heinz Stolba from Universal Edition].
42
43   DO NOT take space into account:
44
45   - for basically everything else, e.g. swapping ledgered notes on
46    clustered chords, spacing between ledgered and unledgered notes.
47   
48   TODO: fix this. It is not feasible to have a special grob for
49   ledgers, since you basically don't know if there will be ledgers,
50   unless you know at interpretation phase already 1. the Y-position,
51   2. the number of staff lines. It's not yet specced when both pieces
52   of information are there, so for now, it is probably better to build
53   special support for ledgers into the accidental and separation-item
54   code.
55
56   (Besides a separate ledger seems overkill. For what else would
57   it be useful?)
58
59 */
60
61 /*
62   TODO: ledger lines are also a property of the staff. Maybe move them
63   to there?
64  */
65 Stencil
66 Note_head::brew_ledger_lines (Grob *me,
67                               int pos,
68                               int interspaces,
69                               Interval x_extent,
70                               Real left_shorten,
71                               bool take_space)
72 {
73   Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
74   Real inter_f = Staff_symbol_referencer::staff_space (me)/2;
75   int line_count = ((abs (pos) < interspaces)
76                     ? 0
77                     : (abs (pos) - interspaces) / 2);
78   Stencil stencil;
79   if (line_count)
80     {
81       Real ledgerlinethickness =
82         Staff_symbol::get_ledger_line_thickness (staff);
83       Real blotdiameter = ledgerlinethickness;
84       Interval y_extent =
85         Interval (-0.5*(ledgerlinethickness),
86                   +0.5*(ledgerlinethickness));
87       Stencil proto_ledger_line =
88         Lookup::round_filled_box (Box (x_extent, y_extent), blotdiameter);
89
90       x_extent[LEFT] += left_shorten;
91       Stencil proto_first_line =
92         Lookup::round_filled_box (Box (x_extent, y_extent), blotdiameter);
93
94       if (!take_space)
95         {
96           proto_ledger_line.set_empty (true);
97           proto_first_line.set_empty (true);
98         }
99       
100       Direction dir = (Direction)sign (pos);
101       Real offs = (Staff_symbol_referencer::on_staffline (me, pos))
102         ? 0.0
103         : -dir * inter_f;
104       
105       for (int i = 0; i < line_count; i++)
106         {
107           Stencil ledger_line ((i == 0) 
108                                 ? proto_first_line
109                                 : proto_ledger_line
110                                 );
111           ledger_line.translate_axis (-dir * inter_f * i * 2 + offs, Y_AXIS);
112           stencil.add_stencil (ledger_line);
113         }
114     }
115
116   return stencil;
117 }
118
119 Stencil
120 internal_print (Grob *me, bool with_ledgers)
121 {
122   SCM style  = me->get_property ("style");
123   if (!ly_c_symbol_p (style))
124     {
125       return Stencil ();
126     }
127
128   SCM log = scm_int2num (Note_head::get_balltype (me));
129   SCM proc = me->get_property ("glyph-name-procedure");
130   SCM scm_font_char = scm_call_2 (proc, log, style);
131   String font_char = "noteheads-" + ly_scm2string (scm_font_char);
132
133   Font_metric * fm = Font_interface::get_default_font (me);
134   Stencil out = fm->find_by_name (font_char);
135   if (out.is_empty ())
136     {
137       me->warning (_f ("note head `%s' not found", font_char.to_str0 ()));
138     }
139
140 #if 0
141   int interspaces = Staff_symbol_referencer::line_count (me)-1;
142   int pos = Staff_symbol_referencer::get_rounded_position (me);
143   if (with_ledgers && interspaces >= 0
144       && abs (pos) - interspaces > 1)
145     {
146       Interval ledger_size = out.extent (X_AXIS);
147       ledger_size.widen ( ledger_size.length ()/4);
148
149       Real left_shorten =0.0;
150       if (Grob * g = unsmob_grob (me->get_property ("accidental-grob")))
151         {
152           /*
153             make a little room for accidentals.
154           
155             TODO: this will look silly if a chord has ledger lines,
156             and only the bottom note has an accidental.
157           */
158
159           Grob *common = g->common_refpoint (me, X_AXIS);
160           Real d =
161             linear_combination (Drul_array<Real> (me->extent (common, X_AXIS)[LEFT],
162                                                   g->extent (common, X_AXIS)[RIGHT]),
163                                 
164                                 0.5);
165
166           left_shorten =  (-ledger_size[LEFT] + d) >?  0 ;
167
168           /*
169             TODO: shorten 2 ledger lines for the case natural +
170             downstem.
171            */
172         }
173
174       out.add_stencil (Note_head::brew_ledger_lines (me, pos, interspaces,
175                                                       ledger_size,
176                                                       left_shorten,
177                                                       false));
178     }
179 #endif
180       
181   return out;
182 }
183
184
185 MAKE_SCHEME_CALLBACK (Note_head,print,1);
186 SCM
187 Note_head::print (SCM smob)  
188 {
189   Grob *me = unsmob_grob (smob);
190
191   /*
192     ledgers don't take space. See top of file.
193    */
194   return internal_print (me, true).smobbed_copy ();
195 }
196
197 /*
198   Compute the width the head without ledgers.
199
200   -- there used to be some code from the time that ledgers
201   did take space. Nowadays, we can simply take the standard extent.
202  */
203 Interval
204 Note_head::head_extent (Grob *me, Axis a)
205 {
206   SCM brewer = me->get_property ("print-function");
207   if (brewer == Note_head::print_proc)
208     {
209       Stencil mol = internal_print (me, false);
210   
211       if (!mol.is_empty ())
212         return mol.extent (a);
213     }
214   else
215     {
216       Stencil * mol = me->get_stencil ();
217       if (mol)
218         return  mol->extent (a) ;
219     }
220   
221   return Interval (0,0);
222 }
223
224 /*
225   This is necessary to prevent a cyclic dependency: the appearance of
226   the ledgers depends on positioning, so the Grob::get_stencil () can
227   not be used for determining the note head extent.
228   
229  */ 
230 MAKE_SCHEME_CALLBACK (Note_head,extent,2);
231 SCM
232 Note_head::extent (SCM smob, SCM axis)  
233 {
234   Grob *me = unsmob_grob (smob);
235
236   return ly_interval2scm (head_extent (me, (Axis) ly_scm2int (axis)));
237 }
238
239 MAKE_SCHEME_CALLBACK (Note_head,brew_ez_stencil,1);
240 SCM
241 Note_head::brew_ez_stencil (SCM smob)
242 {
243   Grob *me = unsmob_grob (smob);
244   int l = Note_head::get_balltype (me);
245
246   int b = (l >= 2);
247
248   SCM cause = me->get_property ("cause");
249   SCM spitch = unsmob_music (cause)->get_property ("pitch");
250   Pitch* pit =  unsmob_pitch (spitch);
251
252   SCM idx = scm_int2num (pit->get_notename ());
253   SCM names = me->get_property ("note-names");
254   SCM charstr = SCM_EOL;
255   if (ly_c_vector_p (names))
256     charstr = scm_vector_ref (names, idx);
257   else
258     {
259       char s[2] = "a";
260       s[0] = (pit->get_notename () + 2)%7 + 'a';
261       s[0] = toupper (s[0]);
262       charstr = scm_makfrom0str (s);
263     }
264   
265   SCM at = scm_list_n (ly_symbol2scm ("ez-ball"),
266                        charstr,
267                        scm_int2num (b),
268                        scm_int2num (1-b),
269                        SCM_UNDEFINED);
270   Box bx (Interval (0, 1.0), Interval (-0.5, 0.5));
271   Stencil m (bx, at);
272
273   int pos = Staff_symbol_referencer::get_rounded_position (me);
274   int interspaces = Staff_symbol_referencer::line_count (me)-1;
275   if (abs (pos) - interspaces > 1)
276     {
277       Interval hd = m.extent (X_AXIS);
278       hd.widen ( hd.length ()/4);
279       m.add_stencil (brew_ledger_lines (me, pos, interspaces, hd, 0, false));
280     }
281
282   return m.smobbed_copy ();
283 }
284
285
286 Real
287 Note_head::stem_attachment_coordinate (Grob *me, Axis a)
288 {
289   SCM brewer = me->get_property ("print-function");
290   Font_metric * fm  = Font_interface::get_default_font (me);
291   
292   if (brewer == Note_head::print_proc)
293     {
294       SCM style  = me->get_property ("style");
295       if (!ly_c_symbol_p (style))
296         {
297           return 0.0;
298         }
299       
300       SCM log = scm_int2num (Note_head::get_balltype (me));
301       SCM proc = me->get_property ("glyph-name-procedure");
302       SCM scm_font_char = scm_call_2 (proc, log, style);
303       String font_char = "noteheads-" + ly_scm2string (scm_font_char);
304
305       int k = fm->name_to_index (font_char) ;
306
307       if (k >= 0)
308         {
309           Box b = fm->get_indexed_char (k);
310           Offset wxwy = fm->get_indexed_wxwy (k);
311           Interval v = b[a];
312           if (!v.is_empty ())
313             return 2 * (wxwy[a] - v.center ()) / v.length ();
314         }
315     }
316   
317   /*
318     Fallback
319    */
320   SCM v = me->get_property ("stem-attachment-function");
321   if (!ly_c_procedure_p (v))
322     return 0.0;
323   
324   SCM result = scm_call_2 (v, me->self_scm (), scm_int2num (a));
325   if (!ly_c_pair_p (result))
326     return 0.0;
327
328   result = (a == X_AXIS) ? ly_car (result) : ly_cdr (result);
329   
330   return robust_scm2double (result,0);
331 }
332
333 int
334 Note_head::get_balltype (Grob*me) 
335 {
336   SCM s = me->get_property ("duration-log");
337   return ly_c_number_p (s) ? ly_scm2int (s) <? 2 : 0;
338 }
339
340 ADD_INTERFACE (Note_head,"note-head-interface",
341   "Note head",
342   "note-names glyph-name-procedure accidental-grob style stem-attachment-function");
343