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 Slur_engraver. Check if fixes | 38 This is largely similar to 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 |
| 43 For info on SlurStubs, check out slur-engraver.cc. |
42 | 44 |
43 */ | 45 */ |
44 | 46 |
45 /* | 47 /* |
46 It is possible that a slur starts and ends on the same note. At | 48 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 | 49 least, it is for phrasing slurs: a note can be both beginning and |
48 ending of a phrase. | 50 ending of a phrase. |
49 */ | 51 */ |
50 class Phrasing_slur_engraver : public Engraver | 52 class Phrasing_slur_engraver : public Engraver |
51 { | 53 { |
52 vector<Stream_event *> start_events_; | 54 vector<Stream_event *> start_events_; |
53 vector<Stream_event *> stop_events_; | 55 vector<Stream_event *> stop_events_; |
54 vector<Grob *> slurs_; | 56 vector<Slur_info> slur_infos_; |
55 vector<Grob *> end_slurs_; | 57 vector<Slur_info> end_slur_infos_; |
56 map<Grob *, vector<Grob *> > slur_stubs_; | |
57 vector<Grob_info> objects_to_acknowledge_; | 58 vector<Grob_info> objects_to_acknowledge_; |
58 | 59 |
59 protected: | 60 protected: |
60 DECLARE_TRANSLATOR_LISTENER (phrasing_slur); | 61 DECLARE_TRANSLATOR_LISTENER (phrasing_slur); |
61 DECLARE_ACKNOWLEDGER (inline_accidental); | 62 DECLARE_ACKNOWLEDGER (inline_accidental); |
62 DECLARE_ACKNOWLEDGER (fingering); | 63 DECLARE_ACKNOWLEDGER (fingering); |
63 DECLARE_ACKNOWLEDGER (note_column); | 64 DECLARE_ACKNOWLEDGER (note_column); |
64 DECLARE_ACKNOWLEDGER (slur); | 65 DECLARE_ACKNOWLEDGER (slur); |
65 DECLARE_ACKNOWLEDGER (script); | 66 DECLARE_ACKNOWLEDGER (script); |
66 DECLARE_ACKNOWLEDGER (dots); | 67 DECLARE_ACKNOWLEDGER (dots); |
67 DECLARE_ACKNOWLEDGER (text_script); | 68 DECLARE_ACKNOWLEDGER (text_script); |
68 DECLARE_ACKNOWLEDGER (tie); | 69 DECLARE_END_ACKNOWLEDGER (tie); |
69 DECLARE_ACKNOWLEDGER (tuplet_number); | 70 DECLARE_ACKNOWLEDGER (tuplet_number); |
70 | 71 |
71 void acknowledge_extra_object (Grob_info); | 72 void acknowledge_extra_object (Grob_info); |
72 void stop_translation_timestep (); | 73 void stop_translation_timestep (); |
73 void process_music (); | 74 void process_music (); |
74 | 75 |
75 virtual void finalize (); | 76 virtual void finalize (); |
76 virtual void derived_mark () const; | 77 virtual void derived_mark () const; |
77 | 78 |
78 public: | 79 public: |
(...skipping 23 matching lines...) Expand all Loading... |
102 else if (d == STOP) | 103 else if (d == STOP) |
103 stop_events_.push_back (ev); | 104 stop_events_.push_back (ev); |
104 else ev->origin ()->warning (_f ("direction of %s invalid: %d", | 105 else ev->origin ()->warning (_f ("direction of %s invalid: %d", |
105 "phrasing-slur-event", int (d))); | 106 "phrasing-slur-event", int (d))); |
106 } | 107 } |
107 | 108 |
108 void | 109 void |
109 Phrasing_slur_engraver::acknowledge_note_column (Grob_info info) | 110 Phrasing_slur_engraver::acknowledge_note_column (Grob_info info) |
110 { | 111 { |
111 Grob *e = info.grob (); | 112 Grob *e = info.grob (); |
112 for (vsize i = slurs_.size (); i--;) | 113 for (vsize i = slur_infos_.size (); i--;) |
113 Slur::add_column (slurs_[i], e); | 114 { |
114 for (vsize i = end_slurs_.size (); i--;) | 115 Slur::add_column (slur_infos_[i].slur_, e); |
115 Slur::add_column (end_slurs_[i], e); | 116 Grob *stub = make_spanner ("SlurStub", slur_infos_[i].slur_->self_scm ()); |
116 | 117 slur_infos_[i].stubs_.push_back (stub); |
117 for (vsize j = 0; j < slurs_.size (); j++) | 118 } |
118 { | 119 for (vsize i = end_slur_infos_.size (); i--;) |
119 if (slur_stubs_.find (slurs_[j]) == slur_stubs_.end ()) | 120 { |
120 slur_stubs_[slurs_[j]] = vector<Grob *> (); | 121 Slur::add_column (end_slur_infos_[i].slur_, e); |
121 | 122 Grob *stub = make_spanner ("SlurStub", slur_infos_[i].slur_->self_scm ()); |
122 Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ()); | 123 end_slur_infos_[i].stubs_.push_back (stub); |
123 slur_stubs_[slurs_[j]].push_back (stub); | |
124 } | |
125 | |
126 for (vsize j = 0; j < end_slurs_.size (); j++) | |
127 { | |
128 if (slur_stubs_.find (end_slurs_[j]) == slur_stubs_.end ()) | |
129 slur_stubs_[end_slurs_[j]] = vector<Grob *> (); | |
130 | |
131 Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ()); | |
132 slur_stubs_[end_slurs_[j]].push_back (stub); | |
133 } | 124 } |
134 } | 125 } |
135 | 126 |
136 void | 127 void |
137 Phrasing_slur_engraver::acknowledge_extra_object (Grob_info info) | 128 Phrasing_slur_engraver::acknowledge_extra_object (Grob_info info) |
138 { | 129 { |
139 objects_to_acknowledge_.push_back (info); | 130 objects_to_acknowledge_.push_back (info); |
140 } | 131 } |
141 | 132 |
142 void | 133 void |
(...skipping 28 matching lines...) Expand all Loading... |
171 acknowledge_extra_object (info); | 162 acknowledge_extra_object (info); |
172 } | 163 } |
173 | 164 |
174 void | 165 void |
175 Phrasing_slur_engraver::acknowledge_text_script (Grob_info info) | 166 Phrasing_slur_engraver::acknowledge_text_script (Grob_info info) |
176 { | 167 { |
177 acknowledge_extra_object (info); | 168 acknowledge_extra_object (info); |
178 } | 169 } |
179 | 170 |
180 void | 171 void |
181 Phrasing_slur_engraver::acknowledge_tie (Grob_info info) | 172 Phrasing_slur_engraver::acknowledge_end_tie (Grob_info info) |
182 { | 173 { |
183 acknowledge_extra_object (info); | 174 acknowledge_extra_object (info); |
184 } | 175 } |
185 | 176 |
186 void | 177 void |
187 Phrasing_slur_engraver::acknowledge_slur (Grob_info info) | 178 Phrasing_slur_engraver::acknowledge_slur (Grob_info info) |
188 { | 179 { |
189 if (!info.grob ()->internal_has_interface (ly_symbol2scm ("slur-stub-interface
"))) | 180 if (!info.grob ()->internal_has_interface (ly_symbol2scm ("cross-staff-stub-in
terface"))) |
190 acknowledge_extra_object (info); | 181 acknowledge_extra_object (info); |
191 } | 182 } |
192 | 183 |
193 void | 184 void |
194 Phrasing_slur_engraver::finalize () | 185 Phrasing_slur_engraver::finalize () |
195 { | 186 { |
196 for (vsize i = 0; i < slurs_.size (); i++) | 187 for (vsize i = 0; i < slur_infos_.size (); i++) |
197 { | 188 { |
198 slurs_[i]->warning (_ ("unterminated phrasing slur")); | 189 slur_infos_[i].slur_->warning (_ ("unterminated phrasing slur")); |
199 slurs_[i]->suicide (); | 190 slur_infos_[i].slur_->suicide (); |
200 } | 191 for (vsize j = 0; j < slur_infos_[i].stubs_.size (); j++) |
201 slurs_.clear (); | 192 slur_infos_[i].stubs_[j]->suicide (); |
202 slur_stubs_.clear (); | 193 } |
| 194 slur_infos_.clear (); |
203 } | 195 } |
204 | 196 |
205 void | 197 void |
206 Phrasing_slur_engraver::process_music () | 198 Phrasing_slur_engraver::process_music () |
207 { | 199 { |
208 for (vsize i = 0; i < stop_events_.size (); i++) | 200 for (vsize i = 0; i < stop_events_.size (); i++) |
209 { | 201 { |
210 Stream_event *ev = stop_events_[i]; | 202 Stream_event *ev = stop_events_[i]; |
211 string id = robust_scm2string (ev->get_property ("spanner-id"), ""); | 203 string id = robust_scm2string (ev->get_property ("spanner-id"), ""); |
212 | 204 |
213 // Find the slurs that are ended with this event (by checking the spanner-
id) | 205 // Find the slurs that are ended with this event (by checking the spanner-
id) |
214 bool ended = false; | 206 bool ended = false; |
215 for (vsize j = slurs_.size (); j--;) | 207 for (vsize j = slur_infos_.size (); j--;) |
216 { | 208 { |
217 if (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), "
")) | 209 if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("span
ner-id"), "")) |
218 { | 210 { |
219 ended = true; | 211 ended = true; |
220 end_slurs_.push_back (slurs_[j]); | 212 end_slur_infos_.push_back (slur_infos_[j].slur_); |
221 slurs_.erase (slurs_.begin () + j); | 213 slur_infos_.erase (slur_infos_.begin () + j); |
222 } | 214 } |
223 } | 215 } |
224 if (ended) | 216 if (ended) |
225 { | 217 { |
226 // Ignore redundant stop events for this id | 218 // Ignore redundant stop events for this id |
227 for (vsize j = stop_events_.size (); --j > i;) | 219 for (vsize j = stop_events_.size (); --j > i;) |
228 { | 220 { |
229 if (id == robust_scm2string (stop_events_[j]->get_property ("spann
er-id"), "")) | 221 if (id == robust_scm2string (stop_events_[j]->get_property ("spann
er-id"), "")) |
230 stop_events_.erase (stop_events_.begin () + j); | 222 stop_events_.erase (stop_events_.begin () + j); |
231 } | 223 } |
232 } | 224 } |
233 else | 225 else |
234 ev->origin ()->warning (_ ("cannot end phrasing slur")); | 226 ev->origin ()->warning (_ ("cannot end phrasing slur")); |
235 } | 227 } |
236 | 228 |
237 vsize old_slurs = slurs_.size (); | 229 vsize old_slurs = slur_infos_.size (); |
238 for (vsize i = start_events_.size (); i--;) | 230 for (vsize i = start_events_.size (); i--;) |
239 { | 231 { |
240 Stream_event *ev = start_events_[i]; | 232 Stream_event *ev = start_events_[i]; |
241 string id = robust_scm2string (ev->get_property ("spanner-id"), ""); | 233 string id = robust_scm2string (ev->get_property ("spanner-id"), ""); |
242 Direction updown = to_dir (ev->get_property ("direction")); | 234 Direction updown = to_dir (ev->get_property ("direction")); |
243 | 235 |
244 bool completed; | 236 bool completed; |
245 for (vsize j = slurs_.size (); !(completed = (j-- == 0));) | 237 for (vsize j = slur_infos_.size (); !(completed = (j-- == 0));) |
246 { | 238 { |
247 // Check if we already have a slur with the same spanner-id. | 239 // Check if we already have a slur with the same spanner-id. |
248 if (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), "
")) | 240 if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("span
ner-id"), "")) |
249 { | 241 { |
250 if (j < old_slurs) | 242 if (j < old_slurs) |
251 { | 243 { |
252 // We already have an old slur, so give a warning | 244 // We already have an old slur, so give a warning |
253 // and completely ignore the new slur. | 245 // and completely ignore the new slur. |
254 ev->origin ()->warning (_ ("already have phrasing slur")); | 246 ev->origin ()->warning (_ ("already have phrasing slur")); |
255 start_events_.erase (start_events_.begin () + i); | 247 start_events_.erase (start_events_.begin () + i); |
256 break; | 248 break; |
257 } | 249 } |
258 | 250 |
259 // If this slur event has no direction, it will not | 251 // If this slur event has no direction, it will not |
260 // contribute anything new to the existing slur(s), so | 252 // contribute anything new to the existing slur(s), so |
261 // we can ignore it. | 253 // we can ignore it. |
262 | 254 |
263 if (!updown) | 255 if (!updown) |
264 break; | 256 break; |
265 | 257 |
266 Stream_event *c = unsmob_stream_event (slurs_[j]->get_property ("c
ause")); | 258 Stream_event *c = unsmob_stream_event (slur_infos_[j].slur_->get_p
roperty ("cause")); |
267 | 259 |
268 if (!c) | 260 if (!c) |
269 { | 261 { |
270 slurs_[j]->programming_error ("phrasing slur without a cause")
; | 262 slur_infos_[j].slur_->programming_error ("phrasing slur withou
t a cause"); |
271 continue; | 263 continue; |
272 } | 264 } |
273 | 265 |
274 Direction slur_dir = to_dir (c->get_property ("direction")); | 266 Direction slur_dir = to_dir (c->get_property ("direction")); |
275 | 267 |
276 // If the existing slur does not have a direction yet, | 268 // If the existing slur does not have a direction yet, |
277 // we'd rather take the new one. | 269 // we'd rather take the new one. |
278 | 270 |
279 if (!slur_dir) | 271 if (!slur_dir) |
280 { | 272 { |
281 slurs_[j]->suicide (); | 273 slur_infos_[j].slur_->suicide (); |
282 slurs_.erase (slurs_.begin () + j); | 274 for (vsize k = 0; k < slur_infos_[i].stubs_.size (); k++) |
| 275 slur_infos_[j].stubs_[k]->suicide (); |
| 276 slur_infos_.erase (slur_infos_.begin () + j); |
283 continue; | 277 continue; |
284 } | 278 } |
285 | 279 |
286 // If the existing slur has the same direction as ours, drop ours | 280 // If the existing slur has the same direction as ours, drop ours |
287 | 281 |
288 if (slur_dir == updown) | 282 if (slur_dir == updown) |
289 break; | 283 break; |
290 } | 284 } |
291 } | 285 } |
292 // If the loop completed, our slur is new | 286 // If the loop completed, our slur is new |
293 if (completed) | 287 if (completed) |
294 { | 288 { |
295 Grob *slur = make_spanner ("PhrasingSlur", ev->self_scm ()); | 289 Grob *slur = make_spanner ("PhrasingSlur", ev->self_scm ()); |
296 slur->set_property ("spanner-id", ly_string2scm (id)); | 290 slur->set_property ("spanner-id", ly_string2scm (id)); |
297 if (updown) | 291 if (updown) |
298 set_grob_direction (slur, updown); | 292 set_grob_direction (slur, updown); |
299 slurs_.push_back (slur); | 293 slur_infos_.push_back (slur); |
300 } | 294 } |
301 } | 295 } |
302 } | 296 } |
303 | 297 |
304 void | 298 void |
305 Phrasing_slur_engraver::stop_translation_timestep () | 299 Phrasing_slur_engraver::stop_translation_timestep () |
306 { | 300 { |
307 if (Grob *g = unsmob_grob (get_property ("currentCommandColumn"))) | 301 if (Grob *g = unsmob_grob (get_property ("currentCommandColumn"))) |
308 { | 302 { |
309 for (vsize i = 0; i < end_slurs_.size (); i++) | 303 for (vsize i = 0; i < end_slur_infos_.size (); i++) |
310 Slur::add_extra_encompass (end_slurs_[i], g); | 304 Slur::add_extra_encompass (end_slur_infos_[i].slur_, g); |
311 | 305 |
312 if (!start_events_.size ()) | 306 if (!start_events_.size ()) |
313 for (vsize i = 0; i < slurs_.size (); i++) | 307 for (vsize i = 0; i < slur_infos_.size (); i++) |
314 Slur::add_extra_encompass (slurs_[i], g); | 308 Slur::add_extra_encompass (slur_infos_[i].slur_, g); |
315 } | 309 } |
316 | 310 |
317 for (vsize i = 0; i < end_slurs_.size (); i++) | 311 for (vsize i = 0; i < end_slur_infos_.size (); i++) |
318 { | 312 { |
319 Spanner *s = dynamic_cast<Spanner *> (end_slurs_[i]); | 313 Spanner *s = dynamic_cast<Spanner *> (end_slur_infos_[i].slur_); |
320 if (!s->get_bound (RIGHT)) | 314 if (!s->get_bound (RIGHT)) |
321 s->set_bound (RIGHT, unsmob_grob (get_property ("currentMusicalColumn"))
); | 315 s->set_bound (RIGHT, unsmob_grob (get_property ("currentMusicalColumn"))
); |
322 announce_end_grob (s, SCM_EOL); | 316 announce_end_grob (s, SCM_EOL); |
323 } | 317 } |
324 | 318 |
325 for (vsize i = 0; i < objects_to_acknowledge_.size (); i++) | 319 for (vsize i = 0; i < objects_to_acknowledge_.size (); i++) |
326 Slur::auxiliary_acknowledge_extra_object (objects_to_acknowledge_[i], slurs_
, end_slurs_); | 320 Slur::auxiliary_acknowledge_extra_object (objects_to_acknowledge_[i], slur_i
nfos_, end_slur_infos_); |
327 | 321 |
328 for (vsize i = 0; i < end_slurs_.size (); i++) | 322 for (vsize i = 0; i < end_slur_infos_.size (); i++) |
329 if (slur_stubs_.find (end_slurs_[i]) != slur_stubs_.end ()) | 323 { |
330 { | 324 // There are likely SlurStubs we don't need. Get rid of them. |
331 // We likely SlurStubs we don't need. Get rid of them. | 325 vector<Grob *> vags; |
332 map<Grob *, Grob *> vag_to_slur; | 326 vector<Grob *> stubs; |
333 for (vsize j = 0; j < slur_stubs_[end_slurs_[i]].size (); j++) | 327 for (vsize j = 0; j < end_slur_infos_[i].stubs_.size (); j++) |
334 { | 328 { |
335 Grob *vag = Grob::get_vertical_axis_group (slur_stubs_[end_slurs_[i]
][j]); | 329 Grob *stub = end_slur_infos_[i].stubs_[j]; |
336 if (vag) | 330 Grob *vag = Grob::get_vertical_axis_group (stub); |
337 { | 331 if (vag) |
338 if (vag_to_slur.find (vag) != vag_to_slur.end ()) | 332 { |
339 slur_stubs_[end_slurs_[i]][j]->suicide (); | 333 vector<Grob *>::const_iterator it = |
340 else | 334 find (vags.begin (), vags.end (), vag); |
341 vag_to_slur[vag] = slur_stubs_[end_slurs_[i]][j]; | 335 if (it != vags.end ()) |
342 } | 336 stub->suicide (); |
343 else | 337 else |
344 { | 338 { |
345 end_slurs_[i]->programming_error ("Cannot find vertical axis gro
up for NoteColumn."); | 339 vags.push_back (vag); |
346 slur_stubs_[end_slurs_[i]][j]->suicide (); | 340 stubs.push_back (stub); |
347 } | 341 } |
348 } | 342 } |
349 map<Grob *, Grob *>::const_iterator it; | 343 else |
350 for (it = vag_to_slur.begin (); | 344 { |
351 it != vag_to_slur.end (); | 345 end_slur_infos_[i].slur_->programming_error ("Cannot find vertical
axis group for NoteColumn."); |
352 it++) | 346 stub->suicide (); |
353 Slur::main_to_stub (end_slurs_[i], it->second); | 347 } |
354 | 348 } |
355 slur_stubs_.erase (end_slurs_[i]); | 349 for (vsize j = 0; j < stubs.size (); j++) |
356 } | 350 Slur::main_to_stub (end_slur_infos_[i].slur_, stubs[j]); |
| 351 } |
357 | 352 |
358 objects_to_acknowledge_.clear (); | 353 objects_to_acknowledge_.clear (); |
359 end_slurs_.clear (); | 354 end_slur_infos_.clear (); |
360 start_events_.clear (); | 355 start_events_.clear (); |
361 stop_events_.clear (); | 356 stop_events_.clear (); |
362 } | 357 } |
363 | 358 |
364 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, inline_accidental); | 359 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, inline_accidental); |
365 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, fingering) | 360 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, fingering) |
366 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, note_column); | 361 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, note_column); |
367 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, slur); | 362 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, slur); |
368 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, script); | 363 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, script); |
369 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, dots); | 364 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, dots); |
370 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, text_script); | 365 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, text_script); |
371 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, tie); | 366 ADD_END_ACKNOWLEDGER (Phrasing_slur_engraver, tie); |
372 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, tuplet_number); | 367 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, tuplet_number); |
373 | 368 |
374 ADD_TRANSLATOR (Phrasing_slur_engraver, | 369 ADD_TRANSLATOR (Phrasing_slur_engraver, |
375 /* doc */ | 370 /* doc */ |
376 "Print phrasing slurs. Similar to @ref{Slur_engraver}.", | 371 "Print phrasing slurs. Similar to @ref{Slur_engraver}.", |
377 | 372 |
378 /* create */ | 373 /* create */ |
379 "PhrasingSlur ", | 374 "PhrasingSlur ", |
380 | 375 |
381 /* read */ | 376 /* read */ |
382 "", | 377 "", |
383 | 378 |
384 /* write */ | 379 /* write */ |
385 "" | 380 "" |
386 ); | 381 ); |
LEFT | RIGHT |