LEFT | RIGHT |
1 /* | 1 /* |
2 This file is part of LilyPond, the GNU music typesetter. | 2 This file is part of LilyPond, the GNU music typesetter. |
3 | 3 |
4 Copyright (C) 1997--2012 Han-Wen Nienhuys <hanwen@xs4all.nl> | 4 Copyright (C) 1997--2012 Han-Wen Nienhuys <hanwen@xs4all.nl> |
5 | 5 |
6 LilyPond is free software: you can redistribute it and/or modify | 6 LilyPond is free software: you can redistribute it and/or modify |
7 it under the terms of the GNU General Public License as published by | 7 it under the terms of the GNU General Public License as published by |
8 the Free Software Foundation, either version 3 of the License, or | 8 the Free Software Foundation, either version 3 of the License, or |
9 (at your option) any later version. | 9 (at your option) any later version. |
10 | 10 |
(...skipping 12 matching lines...) Expand all Loading... |
23 #include "directional-element-interface.hh" | 23 #include "directional-element-interface.hh" |
24 #include "international.hh" | 24 #include "international.hh" |
25 #include "note-column.hh" | 25 #include "note-column.hh" |
26 #include "slur.hh" | 26 #include "slur.hh" |
27 #include "spanner.hh" | 27 #include "spanner.hh" |
28 #include "stream-event.hh" | 28 #include "stream-event.hh" |
29 #include "warn.hh" | 29 #include "warn.hh" |
30 | 30 |
31 #include "translator.icc" | 31 #include "translator.icc" |
32 | 32 |
33 #include <map> | 33 #include <algorithm> |
34 | 34 |
35 /* | 35 /* |
36 NOTE NOTE NOTE | 36 NOTE NOTE NOTE |
37 | 37 |
38 This is largely similar to Phrasing_slur_engraver. Check if fixes | 38 This is largely similar to Phrasing_slur_engraver. Check if fixes |
39 apply there too. | 39 apply there too. |
40 | 40 |
41 (on principle, engravers don't use inheritance for code sharing) | 41 (on principle, engravers don't use inheritance for code sharing) |
42 | 42 |
43 */ | 43 */ |
44 | 44 |
45 /* | 45 /* |
46 It is possible that a slur starts and ends on the same note. At | 46 It is possible that a slur starts and ends on the same note. At |
47 least, it is for phrasing slurs: a note can be both beginning and | 47 least, it is for phrasing slurs: a note can be both beginning and |
48 ending of a phrase. | 48 ending of a phrase. |
49 */ | 49 */ |
| 50 |
| 51 Slur_info::Slur_info (Grob *slur) |
| 52 { |
| 53 slur_ = slur; |
| 54 } |
| 55 |
50 class Slur_engraver : public Engraver | 56 class Slur_engraver : public Engraver |
51 { | 57 { |
52 vector<Stream_event *> start_events_; | 58 vector<Stream_event *> start_events_; |
53 vector<Stream_event *> stop_events_; | 59 vector<Stream_event *> stop_events_; |
54 vector<Grob *> slurs_; | 60 vector<Slur_info> slur_infos_; |
55 vector<Grob *> end_slurs_; | 61 vector<Slur_info> end_slur_infos_; |
56 map<Grob *, vector<Grob *> > slur_stubs_; | |
57 vector<Grob_info> objects_to_acknowledge_; | 62 vector<Grob_info> objects_to_acknowledge_; |
58 | 63 |
59 void set_melisma (bool); | 64 void set_melisma (bool); |
60 | 65 |
61 protected: | 66 protected: |
62 DECLARE_TRANSLATOR_LISTENER (slur); | 67 DECLARE_TRANSLATOR_LISTENER (slur); |
63 DECLARE_ACKNOWLEDGER (inline_accidental); | 68 DECLARE_ACKNOWLEDGER (inline_accidental); |
64 DECLARE_ACKNOWLEDGER (fingering); | 69 DECLARE_ACKNOWLEDGER (fingering); |
65 DECLARE_ACKNOWLEDGER (note_column); | 70 DECLARE_ACKNOWLEDGER (note_column); |
66 DECLARE_ACKNOWLEDGER (script); | 71 DECLARE_ACKNOWLEDGER (script); |
67 DECLARE_ACKNOWLEDGER (dots); | 72 DECLARE_ACKNOWLEDGER (dots); |
68 DECLARE_ACKNOWLEDGER (text_script); | 73 DECLARE_ACKNOWLEDGER (text_script); |
69 DECLARE_ACKNOWLEDGER (tie); | 74 DECLARE_END_ACKNOWLEDGER (tie); |
70 DECLARE_ACKNOWLEDGER (tuplet_number); | 75 DECLARE_ACKNOWLEDGER (tuplet_number); |
71 | 76 |
72 void acknowledge_extra_object (Grob_info); | 77 void acknowledge_extra_object (Grob_info); |
73 void stop_translation_timestep (); | 78 void stop_translation_timestep (); |
74 void process_music (); | 79 void process_music (); |
75 | 80 |
76 virtual void finalize (); | 81 virtual void finalize (); |
77 virtual void derived_mark () const; | 82 virtual void derived_mark () const; |
78 | 83 |
79 public: | 84 public: |
(...skipping 28 matching lines...) Expand all Loading... |
108 | 113 |
109 void | 114 void |
110 Slur_engraver::set_melisma (bool m) | 115 Slur_engraver::set_melisma (bool m) |
111 { | 116 { |
112 context ()->set_property ("slurMelismaBusy", m ? SCM_BOOL_T : SCM_BOOL_F); | 117 context ()->set_property ("slurMelismaBusy", m ? SCM_BOOL_T : SCM_BOOL_F); |
113 } | 118 } |
114 | 119 |
115 void | 120 void |
116 Slur_engraver::acknowledge_note_column (Grob_info info) | 121 Slur_engraver::acknowledge_note_column (Grob_info info) |
117 { | 122 { |
| 123 /* |
| 124 * For every active slur, we create a slur stub. |
| 125 * As we do not yet know what vertical axis groups note columns belong to, |
| 126 * we create a stub for each note and then suicide duplicate stubs on |
| 127 * axis groups. |
| 128 * These slurs should be used ONLY to approximate cross-staff slurs |
| 129 * in vertical skylines. |
| 130 */ |
| 131 ·· |
118 Grob *e = info.grob (); | 132 Grob *e = info.grob (); |
119 for (vsize i = slurs_.size (); i--;) | 133 for (vsize i = slur_infos_.size (); i--;) |
120 Slur::add_column (slurs_[i], e); | 134 { |
121 for (vsize i = end_slurs_.size (); i--;) | 135 Slur::add_column (slur_infos_[i].slur_, e); |
122 Slur::add_column (end_slurs_[i], e); | 136 Grob *stub = make_spanner ("SlurStub", slur_infos_[i].slur_->self_scm ()); |
123 | 137 slur_infos_[i].stubs_.push_back (stub); |
124 for (vsize j = 0; j < slurs_.size (); j++) | 138 } |
125 { | 139 for (vsize i = end_slur_infos_.size (); i--;) |
126 if (slur_stubs_.find (slurs_[j]) == slur_stubs_.end ()) | 140 { |
127 slur_stubs_[slurs_[j]] = vector<Grob *> (); | 141 Slur::add_column (end_slur_infos_[i].slur_, e); |
128 | 142 Grob *stub = make_spanner ("SlurStub", slur_infos_[i].slur_->self_scm ()); |
129 Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ()); | 143 end_slur_infos_[i].stubs_.push_back (stub); |
130 slur_stubs_[slurs_[j]].push_back (stub); | |
131 } | |
132 | |
133 for (vsize j = 0; j < end_slurs_.size (); j++) | |
134 { | |
135 if (slur_stubs_.find (end_slurs_[j]) == slur_stubs_.end ()) | |
136 slur_stubs_[end_slurs_[j]] = vector<Grob *> (); | |
137 | |
138 Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ()); | |
139 slur_stubs_[end_slurs_[j]].push_back (stub); | |
140 } | 144 } |
141 } | 145 } |
142 | 146 |
143 void | 147 void |
144 Slur_engraver::acknowledge_extra_object (Grob_info info) | 148 Slur_engraver::acknowledge_extra_object (Grob_info info) |
145 { | 149 { |
146 objects_to_acknowledge_.push_back (info); | 150 objects_to_acknowledge_.push_back (info); |
147 } | 151 } |
148 | 152 |
149 void | 153 void |
(...skipping 27 matching lines...) Expand all Loading... |
177 acknowledge_extra_object (info); | 181 acknowledge_extra_object (info); |
178 } | 182 } |
179 | 183 |
180 void | 184 void |
181 Slur_engraver::acknowledge_text_script (Grob_info info) | 185 Slur_engraver::acknowledge_text_script (Grob_info info) |
182 { | 186 { |
183 acknowledge_extra_object (info); | 187 acknowledge_extra_object (info); |
184 } | 188 } |
185 | 189 |
186 void | 190 void |
187 Slur_engraver::acknowledge_tie (Grob_info info) | 191 Slur_engraver::acknowledge_end_tie (Grob_info info) |
188 { | 192 { |
189 acknowledge_extra_object (info); | 193 acknowledge_extra_object (info); |
190 } | 194 } |
191 | 195 |
192 void | 196 void |
193 Slur_engraver::finalize () | 197 Slur_engraver::finalize () |
194 { | 198 { |
195 for (vsize i = 0; i < slurs_.size (); i++) | 199 for (vsize i = 0; i < slur_infos_.size (); i++) |
196 { | 200 { |
197 slurs_[i]->warning (_ ("unterminated slur")); | 201 slur_infos_[i].slur_->warning (_ ("unterminated slur")); |
198 slurs_[i]->suicide (); | 202 slur_infos_[i].slur_->suicide (); |
199 } | 203 for (vsize j = 0; j < slur_infos_[i].stubs_.size (); j++) |
200 slurs_.clear (); | 204 slur_infos_[i].stubs_[j]->suicide (); |
201 slur_stubs_.clear (); | 205 } |
| 206 |
| 207 slur_infos_.clear (); |
202 } | 208 } |
203 | 209 |
204 void | 210 void |
205 Slur_engraver::process_music () | 211 Slur_engraver::process_music () |
206 { | 212 { |
207 for (vsize i = 0; i < stop_events_.size (); i++) | 213 for (vsize i = 0; i < stop_events_.size (); i++) |
208 { | 214 { |
209 Stream_event *ev = stop_events_[i]; | 215 Stream_event *ev = stop_events_[i]; |
210 string id = robust_scm2string (ev->get_property ("spanner-id"), ""); | 216 string id = robust_scm2string (ev->get_property ("spanner-id"), ""); |
211 | 217 |
212 // Find the slurs that are ended with this event (by checking the spanner-
id) | 218 // Find the slurs that are ended with this event (by checking the spanner-
id) |
213 bool ended = false; | 219 bool ended = false; |
214 for (vsize j = slurs_.size (); j--;) | 220 for (vsize j = slur_infos_.size (); j--;) |
215 { | 221 { |
216 if (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), "
")) | 222 if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("span
ner-id"), "")) |
217 { | 223 { |
218 ended = true; | 224 ended = true; |
219 end_slurs_.push_back (slurs_[j]); | 225 end_slur_infos_.push_back (slur_infos_[j]); |
220 slurs_.erase (slurs_.begin () + j); | 226 slur_infos_.erase (slur_infos_.begin () + j); |
221 } | 227 } |
222 } | 228 } |
223 if (ended) | 229 if (ended) |
224 { | 230 { |
225 // Ignore redundant stop events for this id | 231 // Ignore redundant stop events for this id |
226 for (vsize j = stop_events_.size (); --j > i;) | 232 for (vsize j = stop_events_.size (); --j > i;) |
227 { | 233 { |
228 if (id == robust_scm2string (stop_events_[j]->get_property ("spann
er-id"), "")) | 234 if (id == robust_scm2string (stop_events_[j]->get_property ("spann
er-id"), "")) |
229 stop_events_.erase (stop_events_.begin () + j); | 235 stop_events_.erase (stop_events_.begin () + j); |
230 } | 236 } |
231 } | 237 } |
232 else | 238 else |
233 ev->origin ()->warning (_ ("cannot end slur")); | 239 ev->origin ()->warning (_ ("cannot end slur")); |
234 } | 240 } |
235 | 241 |
236 vsize old_slurs = slurs_.size (); | 242 vsize old_slurs = slur_infos_.size (); |
237 for (vsize i = start_events_.size (); i--;) | 243 for (vsize i = start_events_.size (); i--;) |
238 { | 244 { |
239 Stream_event *ev = start_events_[i]; | 245 Stream_event *ev = start_events_[i]; |
240 string id = robust_scm2string (ev->get_property ("spanner-id"), ""); | 246 string id = robust_scm2string (ev->get_property ("spanner-id"), ""); |
241 Direction updown = to_dir (ev->get_property ("direction")); | 247 Direction updown = to_dir (ev->get_property ("direction")); |
242 | 248 |
243 bool completed; | 249 bool completed; |
244 for (vsize j = slurs_.size (); !(completed = (j-- == 0));) | 250 for (vsize j = slur_infos_.size (); !(completed = (j-- == 0));) |
245 { | 251 { |
246 // Check if we already have a slur with the same spanner-id. | 252 // Check if we already have a slur with the same spanner-id. |
247 if (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), "
")) | 253 if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("span
ner-id"), "")) |
248 { | 254 { |
249 if (j < old_slurs) | 255 if (j < old_slurs) |
250 { | 256 { |
251 // We already have an old slur, so give a warning | 257 // We already have an old slur, so give a warning |
252 // and completely ignore the new slur. | 258 // and completely ignore the new slur. |
253 ev->origin ()->warning (_ ("already have slur")); | 259 ev->origin ()->warning (_ ("already have slur")); |
254 start_events_.erase (start_events_.begin () + i); | 260 start_events_.erase (start_events_.begin () + i); |
255 break; | 261 break; |
256 } | 262 } |
257 | 263 |
258 // If this slur event has no direction, it will not | 264 // If this slur event has no direction, it will not |
259 // contribute anything new to the existing slur(s), so | 265 // contribute anything new to the existing slur(s), so |
260 // we can ignore it. | 266 // we can ignore it. |
261 | 267 |
262 if (!updown) | 268 if (!updown) |
263 break; | 269 break; |
264 | 270 |
265 Stream_event *c = unsmob_stream_event (slurs_[j]->get_property ("c
ause")); | 271 Stream_event *c = unsmob_stream_event (slur_infos_[j].slur_->get_p
roperty ("cause")); |
266 | 272 |
267 if (!c) | 273 if (!c) |
268 { | 274 { |
269 slurs_[j]->programming_error ("slur without a cause"); | 275 slur_infos_[j].slur_->programming_error ("slur without a cause
"); |
270 continue; | 276 continue; |
271 } | 277 } |
272 | 278 |
273 Direction slur_dir = to_dir (c->get_property ("direction")); | 279 Direction slur_dir = to_dir (c->get_property ("direction")); |
274 | 280 |
275 // If the existing slur does not have a direction yet, | 281 // If the existing slur does not have a direction yet, |
276 // we'd rather take the new one. | 282 // we'd rather take the new one. |
277 | 283 |
278 if (!slur_dir) | 284 if (!slur_dir) |
279 { | 285 { |
280 slurs_[j]->suicide (); | 286 slur_infos_[j].slur_->suicide (); |
281 slurs_.erase (slurs_.begin () + j); | 287 for (vsize k = 0; k < slur_infos_[j].stubs_.size (); k++) |
| 288 slur_infos_[j].stubs_[k]->suicide (); |
| 289 slur_infos_.erase (slur_infos_.begin () + j); |
282 continue; | 290 continue; |
283 } | 291 } |
284 | 292 |
285 // If the existing slur has the same direction as ours, drop ours | 293 // If the existing slur has the same direction as ours, drop ours |
286 | 294 |
287 if (slur_dir == updown) | 295 if (slur_dir == updown) |
288 break; | 296 break; |
289 } | 297 } |
290 } | 298 } |
291 // If the loop completed, our slur is new | 299 // If the loop completed, our slur is new |
292 if (completed) | 300 if (completed) |
293 { | 301 { |
294 Grob *slur = make_spanner ("Slur", ev->self_scm ()); | 302 Grob *slur = make_spanner ("Slur", ev->self_scm ()); |
295 slur->set_property ("spanner-id", ly_string2scm (id)); | 303 slur->set_property ("spanner-id", ly_string2scm (id)); |
296 if (updown) | 304 if (updown) |
297 set_grob_direction (slur, updown); | 305 set_grob_direction (slur, updown); |
298 slurs_.push_back (slur); | 306 slur_infos_.push_back (Slur_info (slur)); |
299 | 307 |
300 if (to_boolean (get_property ("doubleSlurs"))) | 308 if (to_boolean (get_property ("doubleSlurs"))) |
301 { | 309 { |
302 set_grob_direction (slur, DOWN); | 310 set_grob_direction (slur, DOWN); |
303 slur = make_spanner ("Slur", ev->self_scm ()); | 311 slur = make_spanner ("Slur", ev->self_scm ()); |
304 slur->set_property ("spanner-id", ly_string2scm (id)); | 312 slur->set_property ("spanner-id", ly_string2scm (id)); |
305 set_grob_direction (slur, UP); | 313 set_grob_direction (slur, UP); |
306 slurs_.push_back (slur); | 314 slur_infos_.push_back (Slur_info (slur)); |
307 } | 315 } |
308 } | 316 } |
309 } | 317 } |
310 set_melisma (slurs_.size ()); | 318 set_melisma (slur_infos_.size ()); |
311 } | 319 } |
312 | 320 |
313 void | 321 void |
314 Slur_engraver::stop_translation_timestep () | 322 Slur_engraver::stop_translation_timestep () |
315 { | 323 { |
316 if (Grob *g = unsmob_grob (get_property ("currentCommandColumn"))) | 324 if (Grob *g = unsmob_grob (get_property ("currentCommandColumn"))) |
317 { | 325 { |
318 for (vsize i = 0; i < end_slurs_.size (); i++) | 326 for (vsize i = 0; i < end_slur_infos_.size (); i++) |
319 Slur::add_extra_encompass (end_slurs_[i], g); | 327 Slur::add_extra_encompass (end_slur_infos_[i].slur_, g); |
320 | 328 |
321 if (!start_events_.size ()) | 329 if (!start_events_.size ()) |
322 for (vsize i = 0; i < slurs_.size (); i++) | 330 for (vsize i = 0; i < slur_infos_.size (); i++) |
323 Slur::add_extra_encompass (slurs_[i], g); | 331 Slur::add_extra_encompass (slur_infos_[i].slur_, g); |
324 } | 332 } |
325 | 333 |
326 for (vsize i = 0; i < end_slurs_.size (); i++) | 334 for (vsize i = 0; i < end_slur_infos_.size (); i++) |
327 { | 335 { |
328 Spanner *s = dynamic_cast<Spanner *> (end_slurs_[i]); | 336 Spanner *s = dynamic_cast<Spanner *> (end_slur_infos_[i].slur_); |
329 if (!s->get_bound (RIGHT)) | 337 if (!s->get_bound (RIGHT)) |
330 s->set_bound (RIGHT, unsmob_grob (get_property ("currentMusicalColumn"))
); | 338 s->set_bound (RIGHT, unsmob_grob (get_property ("currentMusicalColumn"))
); |
331 announce_end_grob (s, SCM_EOL); | 339 announce_end_grob (s, SCM_EOL); |
332 } | 340 } |
333 | 341 |
334 for (vsize i = 0; i < objects_to_acknowledge_.size (); i++) | 342 for (vsize i = 0; i < objects_to_acknowledge_.size (); i++) |
335 Slur::auxiliary_acknowledge_extra_object (objects_to_acknowledge_[i], slurs_
, end_slurs_); | 343 Slur::auxiliary_acknowledge_extra_object (objects_to_acknowledge_[i], slur_i
nfos_, end_slur_infos_); |
336 | 344 |
337 for (vsize i = 0; i < end_slurs_.size (); i++) | 345 for (vsize i = 0; i < end_slur_infos_.size (); i++) |
338 if (slur_stubs_.find (end_slurs_[i]) != slur_stubs_.end ()) | 346 { |
339 { | 347 // There are likely SlurStubs we don't need. Get rid of them |
340 // We likely SlurStubs we don't need. Get rid of them. | 348 // and only keep one per VerticalAxisGroup. |
341 map<Grob *, Grob *> vag_to_slur; | 349 vector<Grob *> vags; |
342 for (vsize j = 0; j < slur_stubs_[end_slurs_[i]].size (); j++) | 350 vector<Grob *> stubs; |
343 { | 351 for (vsize j = 0; j < end_slur_infos_[i].stubs_.size (); j++) |
344 Grob *vag = Grob::get_vertical_axis_group (slur_stubs_[end_slurs_[i]
][j]); | 352 { |
345 if (vag) | 353 Grob *stub = end_slur_infos_[i].stubs_[j]; |
346 { | 354 Grob *vag = Grob::get_vertical_axis_group (stub); |
347 if (vag_to_slur.find (vag) != vag_to_slur.end ()) | 355 if (vag) |
348 slur_stubs_[end_slurs_[i]][j]->suicide (); | 356 { |
349 else | 357 vector<Grob *>::const_iterator it = |
350 vag_to_slur[vag] = slur_stubs_[end_slurs_[i]][j]; | 358 find (vags.begin (), vags.end (), vag); |
351 } | 359 if (it != vags.end ()) |
352 else | 360 stub->suicide (); |
353 { | 361 else |
354 end_slurs_[i]->programming_error ("Cannot find vertical axis gro
up for NoteColumn."); | 362 { |
355 slur_stubs_[end_slurs_[i]][j]->suicide (); | 363 vags.push_back (vag); |
356 } | 364 stubs.push_back (stub); |
357 } | 365 } |
358 map<Grob *, Grob *>::const_iterator it; | 366 } |
359 for (it = vag_to_slur.begin (); | 367 else |
360 it != vag_to_slur.end (); | 368 { |
361 it++) | 369 end_slur_infos_[i].slur_->programming_error ("Cannot find vertical
axis group for NoteColumn."); |
362 Slur::main_to_stub (end_slurs_[i], it->second); | 370 stub->suicide (); |
363 | 371 } |
364 slur_stubs_.erase (end_slurs_[i]); | 372 } |
365 } | 373 for (vsize j = 0; j < stubs.size (); j++) |
| 374 Slur::main_to_stub (end_slur_infos_[i].slur_, stubs[j]); |
| 375 } |
366 | 376 |
367 objects_to_acknowledge_.clear (); | 377 objects_to_acknowledge_.clear (); |
368 end_slurs_.clear (); | 378 end_slur_infos_.clear (); |
369 start_events_.clear (); | 379 start_events_.clear (); |
370 stop_events_.clear (); | 380 stop_events_.clear (); |
371 } | 381 } |
372 | 382 |
373 ADD_ACKNOWLEDGER (Slur_engraver, inline_accidental); | 383 ADD_ACKNOWLEDGER (Slur_engraver, inline_accidental); |
374 ADD_ACKNOWLEDGER (Slur_engraver, fingering); | 384 ADD_ACKNOWLEDGER (Slur_engraver, fingering); |
375 ADD_ACKNOWLEDGER (Slur_engraver, note_column); | 385 ADD_ACKNOWLEDGER (Slur_engraver, note_column); |
376 ADD_ACKNOWLEDGER (Slur_engraver, script); | 386 ADD_ACKNOWLEDGER (Slur_engraver, script); |
377 ADD_ACKNOWLEDGER (Slur_engraver, text_script); | 387 ADD_ACKNOWLEDGER (Slur_engraver, text_script); |
378 ADD_ACKNOWLEDGER (Slur_engraver, dots); | 388 ADD_ACKNOWLEDGER (Slur_engraver, dots); |
379 ADD_ACKNOWLEDGER (Slur_engraver, tie); | 389 ADD_END_ACKNOWLEDGER (Slur_engraver, tie); |
380 ADD_ACKNOWLEDGER (Slur_engraver, tuplet_number); | 390 ADD_ACKNOWLEDGER (Slur_engraver, tuplet_number); |
381 ADD_TRANSLATOR (Slur_engraver, | 391 ADD_TRANSLATOR (Slur_engraver, |
382 /* doc */ | 392 /* doc */ |
383 "Build slur grobs from slur events.", | 393 "Build slur grobs from slur events.", |
384 | 394 |
385 /* create */ | 395 /* create */ |
386 "Slur ", | 396 "Slur ", |
387 | 397 |
388 /* read */ | 398 /* read */ |
389 "slurMelismaBusy " | 399 "slurMelismaBusy " |
390 "doubleSlurs ", | 400 "doubleSlurs ", |
391 | 401 |
392 /* write */ | 402 /* write */ |
393 "" | 403 "" |
394 ); | 404 ); |
LEFT | RIGHT |