1 /**
2 * staff.cpp
3 * functions dealing with whole staffs
4 *
5 * for Denemo, a gtk+ frontend to GNU Lilypond
6 * (c) 1999-2005 Matthew Hiller
7 */
8
9 #include "command/chord.h"
10 #include "command/contexts.h"
11 #include <denemo/denemo.h>
12 #include "ui/dialogs.h"
13 #include "command/measure.h"
14 #include "ui/moveviewport.h"
15 #include "command/object.h"
16 #include "command/processstaffname.h"
17 #include "command/staff.h"
18 #include <stdlib.h>
19 #include <string.h>
20 #include "display/calculatepositions.h"
21 #include "command/commandfuncs.h"
22 #include "command/lilydirectives.h"
23 #include "display/displayanimation.h"
24 #include "command/select.h"
25 #include "core/cache.h"
26 #include "core/utils.h"
27 #include "command/lyric.h"
28
29 gboolean
signal_structural_change(DenemoProject * project)30 signal_structural_change (DenemoProject * project)
31 {
32 project->layout_sync = project->changecount;
33 return TRUE;
34 }
35
36
37 /**
38 * Return the first measure node of the given staffops
39 * @param thestaff a staffnode
40 * @return the first measure node of the staff
41 */
42 measurenode *
staff_first_measure_node(staffnode * thestaff)43 staff_first_measure_node (staffnode * thestaff)
44 {
45 return ((DenemoStaff *) thestaff->data)->themeasures;
46 }
47
48 /**
49 * Return the nth measure of the given staff
50 * @param thestaff a staffnode
51 * @param n the number of the measure to return
52 * @return the nth measure of the staff
53 */
54 measurenode *
staff_nth_measure_node(staffnode * thestaff,gint n)55 staff_nth_measure_node (staffnode * thestaff, gint n)
56 {
57 return g_list_nth (((DenemoStaff *) thestaff->data)->themeasures, n);
58 }
59
60 /**
61 * Reset movement->currentprimarystaff based on current value of
62 * movement->currentstaff
63 * @param movement a scoreinfo structure
64 * @return none
65 */
66 void
staff_set_current_primary(DenemoMovement * movement)67 staff_set_current_primary (DenemoMovement * movement)
68 {
69 for (movement->currentprimarystaff = movement->currentstaff; movement->currentprimarystaff && (((DenemoStaff *) movement->currentprimarystaff->data)->voicecontrol & DENEMO_SECONDARY); movement->currentprimarystaff = movement->currentprimarystaff->prev)
70 ;
71 }
72
73 /**
74 * Copies the staff data from a source staff to destination staff
75 * @param src the source staff
76 * @param dest the destination staff
77 * @return none
78 */
79 static void
staff_copy_bits(DenemoStaff * src,DenemoStaff * dest)80 staff_copy_bits (DenemoStaff * src, DenemoStaff * dest)
81 {
82 dest->clef.type = src->clef.type; //other fields - take care if dynamic
83 dest->keysig.number = src->keysig.number;
84 dest->keysig.isminor = src->keysig.isminor;
85 memcpy (dest->keysig.accs, src->keysig.accs, SEVENGINTS);
86 dest->timesig.time1 = src->timesig.time1;
87 dest->timesig.time2 = src->timesig.time2;
88 dest->volume = 127;
89 dest->no_of_lines = 5;
90 dest->transposition = 0;
91 dest->hide_lyrics = src->hide_lyrics;
92 dest->space_above = 0;
93 dest->space_below = 0;
94 dest->context = DENEMO_NONE;
95 }
96
97 /**
98 * Copies a staffs parameters from source to destination FIXME: only for a new voice - does not really copy
99 * @param src the source staff
100 * @param dest the destination staff
101 * @return none
102 */
103 static void
staff_copy_properties(DenemoStaff * src,DenemoStaff * dest)104 staff_copy_properties (DenemoStaff * src, DenemoStaff * dest)
105 {
106 dest->midi_instrument = g_string_new (src->midi_instrument->str);
107 dest->space_above = src->space_above;
108 dest->space_shorten = src->space_shorten;
109 dest->space_below = src->space_below;
110 dest->no_of_lines = src->no_of_lines;
111 dest->transposition = src->transposition;
112 dest->hide_lyrics = src->hide_lyrics;
113
114 dest->volume = src->volume;
115 dest->voicecontrol = DENEMO_SECONDARY;
116 staff_beams_and_stems_dirs (dest);
117 }
118
119
120
121 /* copies a staff without its music data to another staff */
122 void
staff_copy(DenemoStaff * src,DenemoStaff * dest)123 staff_copy (DenemoStaff * src, DenemoStaff * dest)
124 {
125 dest->denemo_name = g_string_new (src->denemo_name->str);
126 dest->lily_name = g_string_new (src->lily_name->str);
127 dest->midi_instrument = g_string_new (src->midi_instrument->str);
128 dest->midi_channel = src->midi_channel;
129 dest->midi_prognum = src->midi_prognum;
130 dest->hide_lyrics = src->hide_lyrics;
131
132 dest->no_of_lines = src->no_of_lines;
133 dest->transposition = src->transposition;
134 dest->space_above = src->space_above; /**< space above the staff used in the denemo gui */
135 dest->space_shorten = src->space_shorten; /**< space by the staff is shorter in height because of few staff lines */
136 dest->space_below = src->space_below; /**< space below the staff used in the denemo gui */
137 dest->range = src->range;/**<TRUE if range_hi,lo should be observed. */
138 dest->range_hi = src->range_hi;/**< highest note playable by instrument, mid_c_offset */
139 dest->range_lo = src->range_lo;/**< lowest note playable by instrument, mid_c_offset */
140 dest->volume = src->volume;
141 dest->voicecontrol = src->voicecontrol;
142 dest->clef.type = src->clef.type;
143 dest->leftmost_clefcontext = &dest->clef;
144
145 dest->staff_directives = clone_directives (src->staff_directives);
146 dest->voice_directives = clone_directives (src->voice_directives);
147
148 dest->clef.directives = clone_directives (src->clef.directives);
149 dest->keysig.directives = clone_directives (src->keysig.directives);
150 dest->timesig.directives = clone_directives (src->timesig.directives);
151
152
153 dest->keysig.number = src->keysig.number;
154 dest->keysig.isminor = src->keysig.isminor;
155 memcpy (dest->keysig.accs, src->keysig.accs, SEVENGINTS);
156 dest->leftmost_keysig = &dest->keysig;
157
158 dest->timesig.time1 = src->timesig.time1;
159 dest->timesig.time2 = src->timesig.time2;
160
161 dest->transposition = src->transposition;
162
163 dest->space_above = 0;
164 dest->space_below = 0;
165 dest->context = src->context;
166 }
167
168 /**
169 * Insert a new staff into the score
170 * @param movement the scoreinfo structure
171 * @param staff the staff to insert
172 * @param action where to insert the new staff
173 * @param addat the position to insert at
174 * @return none
175 */
176 static void
staff_insert(DenemoMovement * movement,DenemoStaff * staff,enum newstaffcallbackaction action,gint addat)177 staff_insert (DenemoMovement * movement, DenemoStaff * staff, enum newstaffcallbackaction action, gint addat)
178 {
179 movement->thescore = g_list_insert (movement->thescore, staff, addat - 1);
180 if (action != BEFORE)
181 if (action != AFTER)
182 {
183 movement->currentstaff = g_list_nth (movement->thescore, addat - 1);
184 movement->currentstaffnum = addat;
185 staff_set_current_primary (movement);
186 find_leftmost_staffcontext (staff, movement);
187 }
188 set_staff_transition (20);
189 }
190
191 /**
192 * Create and insert a new staff into the score
193 * @param movement the scoreinfo structure
194 * @param action the staffs type / where to insert it
195 * @param context the staffs contexts
196 * @return the newly created staff
197 */
198 DenemoStaff*
staff_new(DenemoProject * project,enum newstaffcallbackaction action,DenemoContext context)199 staff_new (DenemoProject * project, enum newstaffcallbackaction action, DenemoContext context)
200 {
201 DenemoMovement *movement = project->movement;
202 if (movement == NULL) return NULL;
203 take_snapshot ();
204 DenemoStaff *staff = (DenemoStaff *) g_malloc (sizeof (DenemoStaff));
205
206 if(!Denemo.non_interactive){
207 staff->staffmenu = (GtkMenu *) gtk_menu_new ();
208 staff->voicemenu = (GtkMenu *) gtk_menu_new ();
209 }
210
211 measurenode *themeasures = NULL; /* Initial set of measures in staff */
212 gint numstaffs = g_list_length (movement->thescore);
213 gint i, addat = 1;
214 //g_debug ("newstaff: Num staffs %d", numstaffs);
215 if (numstaffs == 0)
216 {
217 action = INITIAL;
218
219 staff->clef.type = DENEMO_TREBLE_CLEF;
220 staff->keysig.number = 0;
221 staff->keysig.isminor = FALSE;
222 memset (staff->keysig.accs, 0, SEVENGINTS);
223 staff->timesig.time1 = 4;
224 staff->timesig.time2 = 4;
225 staff->volume = 127;
226 staff->no_of_lines = 5;
227 staff->transposition = 0;
228
229 staff->space_above = 20;
230 staff->space_below = 0;
231 staff->nummeasures = 1;
232 staff->midi_channel = 0;
233 #if 0
234 movement->measurewidths = g_list_append (movement->measurewidths, GINT_TO_POINTER (movement->measurewidth));
235 #else
236 movement->measurewidths = g_list_append (NULL, GINT_TO_POINTER (movement->measurewidth)); //FIXME free old measurewidths
237 #endif
238 }
239 else
240 {
241 /* how did this work before? a new staff must have the same number of measures as the present one(s) */
242 staff->nummeasures = g_list_length (staff_first_measure_node (movement->thescore));
243 staff_copy_bits ((DenemoStaff *) movement->currentstaff->data, staff);
244 staff->midi_channel = (numstaffs < 9 ? numstaffs : numstaffs + 1) & 0xF;
245 }
246
247 if (action == NEWVOICE)
248 {
249 staff->voicecontrol = DENEMO_SECONDARY;
250 staff->nummeasures = g_list_length (staff_first_measure_node (movement->currentstaff)); //FIXME redundant
251 }
252 else
253 {
254 staff->voicecontrol = DENEMO_PRIMARY;
255 };
256
257 for (i = 0; i < staff->nummeasures; i++)
258 {
259 themeasures = g_list_append (themeasures, (gpointer)g_malloc0(sizeof(DenemoMeasure)));
260 };
261
262 if (action == INITIAL || action == ADDFROMLOAD)
263 {
264 movement->currentmeasure = themeasures;
265 }
266
267 /* Now fix the stuff that shouldn't be directly copied from
268 * the current staff, if this staff was non-initial and that
269 * was done to begin with */
270
271 staff->themeasures = themeasures;
272 staff->denemo_name = g_string_new ("");
273 staff->lily_name = g_string_new ("");
274
275 staff->context = context;
276 if (action == INITIAL)
277 g_string_sprintf (staff->denemo_name, _("Part 1"));
278 else
279 g_string_sprintf (staff->denemo_name, _("Part %d"), numstaffs + 1);
280 set_lily_name (staff->denemo_name, staff->lily_name);
281 staff->midi_instrument = g_string_new ("");
282 staff->device_port = g_string_new ("NONE");
283 staff->leftmost_timesig = &staff->timesig;
284
285 /* In what position should the scrollbar be added? */
286 switch (action)
287 {
288 case INITIAL:
289 addat = 1;
290 break;
291 case LAST:
292 case ADDFROMLOAD:
293 addat = numstaffs + 1;
294 break;
295 case BEFORE:
296 addat = movement->currentstaffnum;
297 break;
298 case AFTER:
299 case NEWVOICE:
300 case LYRICSTAFF:
301 case FIGURESTAFF:
302 addat = movement->currentstaffnum + 1;
303 break;
304 case CHORDSTAFF:
305 addat = movement->currentstaffnum + 1;
306 break;
307 case FIRST:
308 addat = 1;
309 break;
310 default:
311 break;
312 }
313
314 if (action != INITIAL && action != ADDFROMLOAD)
315 {
316 if (action == NEWVOICE)
317 {
318 staff_copy_properties ((DenemoStaff *) movement->currentstaff->data, staff);
319 set_lily_name (staff->denemo_name, staff->lily_name); //this should be re-done if the denemo_name is reset.
320 staff_insert (movement, staff, action, addat);
321 }
322 else
323 {
324 // ret = staff_properties_change (&itp);
325 // if (ret)
326 {
327 /*
328 If staff_properties_change returns false,
329 then the staff should probably be removed
330 Fixed 09042005 Adam Tee
331 */
332 staff_insert (movement, staff, action, addat);
333 movement->currentmeasurenum = 1;
334 /*
335 Reset leftmeasure num to 1 to be at the start of
336 the next staff.
337 */
338 movement->leftmeasurenum = 1;
339 }
340 // else
341 // {
342 /*
343 * Free the staff struct as it has not been inserted
344 * into the score
345 */
346 // g_free (staff);
347 // }
348 }
349 }
350 else // is INITIAL or ADDFROMLOAD
351 {
352 staff_insert (movement, staff, action, addat);
353 movement->leftmeasurenum = 1;
354 }
355 if ( addat==1)
356 staff->space_above = 20;
357 cache_staff (g_list_find (Denemo.project->movement->thescore, staff));
358 return staff;
359 }
freemeasure(DenemoMeasure * meas)360 static void freemeasure (DenemoMeasure *meas)
361 {
362 //g_list_foreach (meas->objects, freeobjlist, NULL);
363 freeobjlist (meas->objects, NULL);
364 }
365 /**
366 * Remove the project->movement->currentstaff from the piece project and reset movement->currentstaff
367 * if only one staff, inserts a new empty one
368 * if interactive checks for custom_scoreblock
369 * if a staff is deleted, updates the changecount
370 * @param project the DenemoProject structure
371 * @return nothing
372 */
373 void
staff_delete(DenemoProject * project,gboolean interactive)374 staff_delete (DenemoProject * project, gboolean interactive)
375 {
376 DenemoMovement *movement = project->movement;
377 DenemoStaff *curstaffstruct = movement->currentstaff->data;
378 gboolean has_next = (movement->currentstaff->next != NULL);
379 (void) signal_structural_change (project);
380 if (movement->currentstaff == NULL)
381 return;
382
383 gboolean give_info = FALSE; //give info about removing matching context
384 if (interactive && (curstaffstruct->context != DENEMO_NONE) && (!confirm (_("A context is set on this staff"), _("You will need to alter/delete the matching staff; Proceed?"))))
385 return;
386 if (interactive && (curstaffstruct->context != DENEMO_NONE))
387 give_info = TRUE;
388 take_snapshot ();
389 gboolean isprimary = (gboolean) (curstaffstruct->voicecontrol & DENEMO_PRIMARY);
390 //FIXME free_staff()
391
392 free_directives (curstaffstruct->staff_directives);
393 free_directives (curstaffstruct->timesig.directives);
394 free_directives (curstaffstruct->keysig.directives);
395
396 if(!Denemo.non_interactive){
397 gtk_widget_destroy ((GtkWidget *) (curstaffstruct->staffmenu));
398 gtk_widget_destroy ((GtkWidget *) (curstaffstruct->voicemenu));
399 }
400 //FIXME DANGER
401 g_list_foreach (curstaffstruct->themeasures, (GFunc)freemeasure, NULL);
402 g_list_free_full (curstaffstruct->themeasures, g_free);
403 g_string_free (curstaffstruct->denemo_name, FALSE); //FIXME these should all be TRUE??
404 g_string_free (curstaffstruct->lily_name, FALSE);
405 g_string_free (curstaffstruct->midi_instrument, FALSE);
406 // g_list_foreach (curstaffstruct->verse_views, (GFunc)destroy_parent, NULL);//FIXME it is enough to destroy the notebook, here we are only destroying the GtkTextViews
407 if (curstaffstruct->verse_views)
408 gtk_widget_destroy (gtk_widget_get_parent (gtk_widget_get_parent (curstaffstruct->verse_views->data)));
409
410 g_free (curstaffstruct);
411
412
413 if (movement->currentstaff == g_list_last (movement->thescore))
414 movement->currentstaffnum--; //deleting the last, so the currentstaffnum must decrease
415 else
416 set_staff_transition (20);
417 movement->thescore = g_list_delete_link (movement->thescore, movement->currentstaff);
418 if (movement->thescore == NULL)
419 {
420 staff_new (project, INITIAL, DENEMO_NONE);
421 }
422 movement->currentstaff = g_list_nth (movement->thescore, movement->currentstaffnum - 1);
423
424
425 if (isprimary && has_next) // we deleted the primary, so the next one (which is present) must become the primary
426 {
427 ((DenemoStaff *) movement->currentstaff->data)->voicecontrol = DENEMO_PRIMARY;
428 movement->currentprimarystaff = movement->currentstaff;
429 }
430 else
431 {
432 staff_set_current_primary (movement);
433 }
434 setcurrents (movement);
435 if (movement->markstaffnum)
436 calcmarkboundaries (movement);
437 if (project->movement->currentstaffnum < project->movement->top_staff)
438 project->movement->top_staff = project->movement->currentstaffnum;
439 show_lyrics ();
440
441 if(!Denemo.non_interactive){
442 update_vscrollbar (project);
443 displayhelper (project);
444 score_status (project, TRUE);
445 }
446
447 if (give_info)
448 infodialog (_("The staff deleted had a start/end context; if you still have the staff with the matching end/start context\n then you should remove it (or its context) now.\nSee Staff->properties->context\nYou will not be able to print with miss-matched contexts."));
449 return;
450 }
451
452 /**
453 * Sets the beams and stem directions across the given staff
454 * @param thestaff a staff structure
455 * @return none
456 */
457 void
staff_beams_and_stems_dirs(DenemoStaff * thestaff)458 staff_beams_and_stems_dirs (DenemoStaff * thestaff)
459 {
460 measurenode *curmeasure;
461
462 for (curmeasure = thestaff->themeasures; curmeasure; curmeasure = curmeasure->next)
463 {
464 calculatebeamsandstemdirs ((DenemoMeasure*)curmeasure->data);
465 }
466 }
467
468 /**
469 * Sets which accidentals to show across a staff on a key sig change
470 * @param thestaff a staff stucture
471 * @return none
472 */
473 void
staff_show_which_accidentals(DenemoStaff * thestaff)474 staff_show_which_accidentals (DenemoStaff * thestaff)
475 {
476 measurenode *curmeasure;
477 for (curmeasure = thestaff->themeasures; curmeasure; curmeasure = curmeasure->next)
478 showwhichaccidentals ((objnode *) ((DenemoMeasure*)curmeasure->data)->objects);
479 }
480
481 /**
482 * Function to set the note positions on the given staff when there is a clef change
483 * @param thestaff a staff structure
484 * @return none
485 */
486 void
staff_fix_note_heights(DenemoStaff * thestaff)487 staff_fix_note_heights (DenemoStaff * thestaff)
488 {
489 //gint nclef = thestaff->clef.type;
490 //gint time1 = thestaff->stime1;//USELESS
491 //gint time2 = thestaff->stime2;//USELESS
492 //gint initialclef;//USELESS
493 measurenode *curmeasure;
494 objnode *curobj;
495 DenemoObject *theobj;
496 g_warning ("Staff fix note heights called uselessly?");
497 for (curmeasure = thestaff->themeasures; curmeasure; curmeasure = curmeasure->next)
498 {
499 //initialclef = nclef;
500 for (curobj = (objnode *) ((DenemoMeasure *)curmeasure->data)->objects; curobj; curobj = curobj->next)
501 {
502 theobj = (DenemoObject *) curobj->data;
503 switch (theobj->type)
504 {
505 case CHORD:
506 newclefify (theobj);
507 break;
508 default:
509 break;
510 }
511 } /* End for */
512 } /* End for */
513 staff_beams_and_stems_dirs (thestaff);
514 }
515
516 /**
517 * Callback function to insert a staff in the initial position
518 * @param action a Gtk Action
519 * @param project the DenemoProject structure
520 * @return none
521 */
522 void
staff_new_initial(GtkAction * action,DenemoScriptParam * param)523 staff_new_initial (GtkAction * action, DenemoScriptParam * param)
524 {
525 DenemoProject *project = Denemo.project;
526 while (project->movement->currentstaff && project->movement->currentstaff->prev)
527 movetostaffup (NULL, NULL);
528 staff_new_before (action, NULL);
529 }
530
531 /**
532 Callback function to insert a staff before the current staff
533 * @param action a Gtk Action
534 * @param project the DenemoProject structure
535 * @return none
536 */
537 void
staff_new_before(GtkAction * action,DenemoScriptParam * param)538 staff_new_before (GtkAction * action, DenemoScriptParam * param)
539 {
540 DenemoProject *project = Denemo.project;
541 (void) signal_structural_change (project);
542
543 movetostart (NULL, NULL);
544 staff_new (project, BEFORE, DENEMO_NONE);
545 if (project->movement->currentstaffnum >= project->movement->top_staff)
546 project->movement->top_staff++;
547 project->movement->currentstaffnum++;
548 project->movement->bottom_staff++;
549 set_bottom_staff (project);
550 move_viewport_down (project);
551
552 movetostaffup (NULL, NULL);
553 displayhelper (project);
554
555 }
556
557 /**
558 * Callback function to insert a staff after the current staff
559 * @param action a Gtk Action
560 * @param project the DenemoProject structure
561 * @return none
562 */
563 void
staff_new_after(GtkAction * action,DenemoScriptParam * param)564 staff_new_after (GtkAction * action, DenemoScriptParam * param)
565 {
566 DenemoProject *project = Denemo.project;
567 if (!signal_structural_change (project))
568 return;
569 movetostart (NULL, NULL);
570 staff_new (project, AFTER, DENEMO_NONE);
571 if(!Denemo.non_interactive){
572 set_bottom_staff (project);
573 update_vscrollbar (project);
574 }
575 movetostaffdown (NULL, NULL);
576 displayhelper (project);
577 }
578
579 /**
580 * Callback function to insert a staff at the bottom of the score
581 * @param action a Gtk Action
582 * @param project the DenemoProject structure
583 * @return none
584 */
585 void
staff_new_last(GtkAction * action,DenemoScriptParam * param)586 staff_new_last (GtkAction * action, DenemoScriptParam * param)
587 {
588 DenemoProject *project = Denemo.project;
589 while (project->movement->currentstaff && project->movement->currentstaff->next)
590 movetostaffdown (NULL, NULL);
591 staff_new_after (action, param);
592 }
593
594 /**
595 * Callback function to add a new voice to the current staff
596 * @param action a Gtk Action
597 * @param project the DenemoProject structure
598 * @return none
599 */
600 void
staff_new_voice(GtkAction * action,DenemoScriptParam * param)601 staff_new_voice (GtkAction * action, DenemoScriptParam * param)
602 {
603 DenemoProject *project = Denemo.project;
604 staff_new (project, NEWVOICE, DENEMO_NONE);
605 set_bottom_staff (project);
606 update_vscrollbar (project);
607 setcurrents (project->movement);
608 if (project->movement->markstaffnum)
609 calcmarkboundaries (project->movement);
610 displayhelper (project);
611 }
612