]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-head.cc
* lily/book.cc (to_stencil): New method.
[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 "paper-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   int interspaces = Staff_symbol_referencer::line_count (me)-1;
141   int pos = Staff_symbol_referencer::get_rounded_position (me);
142   if (with_ledgers && interspaces >= 0
143       && abs (pos) - interspaces > 1)
144     {
145       Interval ledger_size = out.extent (X_AXIS);
146       ledger_size.widen ( ledger_size.length ()/4);
147
148       Real left_shorten =0.0;
149       if (Grob * g = unsmob_grob (me->get_property ("accidental-grob")))
150         {
151           /*
152             make a little room for accidentals.
153           
154             TODO: this will look silly if a chord has ledger lines,
155             and only the bottom note has an accidental.
156           */
157
158           Grob *common = g->common_refpoint (me, X_AXIS);
159           Real d =
160             linear_combination (Drul_array<Real> (me->extent (common, X_AXIS)[LEFT],
161                                                   g->extent (common, X_AXIS)[RIGHT]),
162                                 
163                                 0.5);
164
165           left_shorten =  (-ledger_size[LEFT] + d) >?  0 ;
166
167           /*
168             TODO: shorten 2 ledger lines for the case natural +
169             downstem.
170            */
171         }
172
173       out.add_stencil (Note_head::brew_ledger_lines (me, pos, interspaces,
174                                                       ledger_size,
175                                                       left_shorten,
176                                                       false));
177     }
178   return out;
179 }
180
181
182 MAKE_SCHEME_CALLBACK (Note_head,print,1);
183 SCM
184 Note_head::print (SCM smob)  
185 {
186   Grob *me = unsmob_grob (smob);
187
188   /*
189     ledgers don't take space. See top of file.
190    */
191   return internal_print (me, true).smobbed_copy ();
192 }
193
194 /*
195   Compute the width the head without ledgers.
196
197   -- there used to be some code from the time that ledgers
198   did take space. Nowadays, we can simply take the standard extent.
199  */
200 Interval
201 Note_head::head_extent (Grob *me, Axis a)
202 {
203   SCM brewer = me->get_property ("print-function");
204   if (brewer == Note_head::print_proc)
205     {
206       Stencil mol = internal_print (me, false);
207   
208       if (!mol.is_empty ())
209         return mol.extent (a);
210     }
211   else
212     {
213       Stencil * mol = me->get_stencil ();
214       if (mol)
215         return  mol->extent (a) ;
216     }
217   
218   return Interval (0,0);
219 }
220
221 /*
222   This is necessary to prevent a cyclic dependency: the appearance of
223   the ledgers depends on positioning, so the Grob::get_stencil () can
224   not be used for determining the note head extent.
225   
226  */ 
227 MAKE_SCHEME_CALLBACK (Note_head,extent,2);
228 SCM
229 Note_head::extent (SCM smob, SCM axis)  
230 {
231   Grob *me = unsmob_grob (smob);
232
233   return ly_interval2scm (head_extent (me, (Axis) ly_scm2int (axis)));
234 }
235
236 MAKE_SCHEME_CALLBACK (Note_head,brew_ez_stencil,1);
237 SCM
238 Note_head::brew_ez_stencil (SCM smob)
239 {
240   Grob *me = unsmob_grob (smob);
241   int l = Note_head::get_balltype (me);
242
243   int b = (l >= 2);
244
245   SCM cause = me->get_property ("cause");
246   SCM spitch = unsmob_music (cause)->get_property ("pitch");
247   Pitch* pit =  unsmob_pitch (spitch);
248
249   SCM idx = scm_int2num (pit->get_notename ());
250   SCM names = me->get_property ("note-names");
251   SCM charstr = SCM_EOL;
252   if (ly_c_vector_p (names))
253     charstr = scm_vector_ref (names, idx);
254   else
255     {
256       char s[2] = "a";
257       s[0] = (pit->get_notename () + 2)%7 + 'a';
258       s[0] = toupper (s[0]);
259       charstr = scm_makfrom0str (s);
260     }
261   
262   SCM at = scm_list_n (ly_symbol2scm ("ez-ball"),
263                        charstr,
264                        scm_int2num (b),
265                        scm_int2num (1-b),
266                        SCM_UNDEFINED);
267   Box bx (Interval (0, 1.0), Interval (-0.5, 0.5));
268   Stencil m (bx, at);
269
270   int pos = Staff_symbol_referencer::get_rounded_position (me);
271   int interspaces = Staff_symbol_referencer::line_count (me)-1;
272   if (abs (pos) - interspaces > 1)
273     {
274       Interval hd = m.extent (X_AXIS);
275       hd.widen ( hd.length ()/4);
276       m.add_stencil (brew_ledger_lines (me, pos, interspaces, hd, 0, false));
277     }
278
279   return m.smobbed_copy ();
280 }
281
282
283 Real
284 Note_head::stem_attachment_coordinate (Grob *me, Axis a)
285 {
286   SCM brewer = me->get_property ("print-function");
287   Font_metric * fm  = Font_interface::get_default_font (me);
288   
289   if (brewer == Note_head::print_proc)
290     {
291       SCM style  = me->get_property ("style");
292       if (!ly_c_symbol_p (style))
293         {
294           return 0.0;
295         }
296       
297       SCM log = scm_int2num (Note_head::get_balltype (me));
298       SCM proc = me->get_property ("glyph-name-procedure");
299       SCM scm_font_char = scm_call_2 (proc, log, style);
300       String font_char = "noteheads-" + ly_scm2string (scm_font_char);
301
302       int k = fm->name_to_index (font_char) ;
303
304       if (k >= 0)
305         {
306           Box b = fm->get_indexed_char (k);
307           Offset wxwy = fm->get_indexed_wxwy (k);
308           Interval v = b[a];
309           if (!v.is_empty ())
310             return 2 * (wxwy[a] - v.center ()) / v.length ();
311         }
312     }
313   
314   /*
315     Fallback
316    */
317   SCM v = me->get_property ("stem-attachment-function");
318   if (!ly_c_procedure_p (v))
319     return 0.0;
320   
321   SCM result = scm_call_2 (v, me->self_scm (), scm_int2num (a));
322   if (!ly_c_pair_p (result))
323     return 0.0;
324
325   result = (a == X_AXIS) ? ly_car (result) : ly_cdr (result);
326   
327   return robust_scm2double (result,0);
328 }
329
330 int
331 Note_head::get_balltype (Grob*me) 
332 {
333   SCM s = me->get_property ("duration-log");
334   return ly_c_number_p (s) ? ly_scm2int (s) <? 2 : 0;
335 }
336
337 ADD_INTERFACE (Note_head,"note-head-interface",
338   "Note head",
339   "note-names glyph-name-procedure accidental-grob style stem-attachment-function");
340