1 /*
2  *  This program is free software; you can redistribute it and/or modify
3  *  it under the terms of the GNU General Public License as published by
4  *  the Free Software Foundation; either version 2 of the License, or
5  *  (at your option) any later version.
6  *
7  *  This program is distributed in the hope that it will be useful,
8  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *  GNU General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15  */
16 ////////////////////////////////////////////////////////////
17 // ScheduleGUI.c, by Bret Logan (c) 2008-2011
18 //A user-manipulable plot for creating Gnaural Schedule files
19 //NOTES:
20 // - SG_BackupDataPoints() is used for one-step Undo/Redo. It should be called by user usually; it
21 //   only gets called internally in cases that user can't access (mouse actions, mostly)
22 //To compile ScheduleGUI for standalone:
23 //g++ ScheduleGUI.cpp -o ScheduleGUI -D SCHEDULEGUI_FREESTANDING `pkg-config --cflags --libs gtk+-2.0`
24 //To use with another project, include ScheduleGUI.h and rename main();
25 ////////////////////////////////////////////////////////////
26 
27 //To compile:
28 //gcc -Wall -g ScheduleGUI.c -o ScheduleGUI.o -I/usr/include/gtk-2.0 -I/usr/lib/gtk-2.0/include -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -DSCHEDULEGUI_FREESTANDING `pkg-config --cflags gtk+-2.0` `pkg-config --libs gtk+-2.0`
29 
30 #include <stdlib.h>
31 #include <math.h>
32 #include <string.h>     //needed for memcpy()
33 #include <unistd.h>     //needed for sleep()
34 #include <gtk/gtk.h>
35 
36 #include "ScheduleGUI.h"
37 //#include "ScheduleXML.h"
38 //#include "BinauralBeat.h"
39 
40 #define ERROUT(a)   fprintf(stderr,"ScheduleGUI: #Error# %s\n",a);
41 
42 //TODO:
43 // - have time portion of graph show hours when needed.
44 // - add to Parameter windows: type of voice, currently file, total time
45 // - offer means to change/specify type of voice, and implement ability to use files (CD, WAV, or MP3/Ogg) as sources for voices
46 // - offer means to change/specify type of voice, and implement ability to use files (CD, WAV, or MP3/Ogg) as sources for voices
47 // - solve problem of voice description parameter file vs. Copy/Paste incompatability. (Likewise, the sound file filename storage problem.)
48 // - make way to drag along a fixed x or y axis
49 // - Be able to hide or solo (view) single voices, with goal of editing one-at-a-time (but hearing all, if user wants).
50 // - be able to paste a clump exactly at the end of a current schedule, perhaps repeatedly in one action
51 // - open gnauralfiles at the commandline
52 // - Study SG_DeleteVoice() and SG_PasteDataPoints() to see if deleting voices should be legal (I think Voice IDs and copy/paste may make it a dangerous ability)
53 // - integrate balance and volume controls better in to DP Properties box (sliders?)
54 // - add LOOPS to schedulefile's overall parameters (how'd I forget it?!)
55 // - Be sure I have means to indicated whether noise voices are stereo or not, etc.
56 // - create a fast way to auto update whenever adjusting graphs (if number of DPs and Voices are the same, it should be easy to do)
57 // - implement SG_ProgressIndicatorFlag so user can see where they are.
58 // - make selecting last point select first point, since they are the same
59 // - organize keyboard shortcuts more logically, and document them carefully (since Graphsound may only use them instead of a GUI)
60 //Long Term:
61 // - make it so I don't have to send InsertDatapoint() a summed number (it is just getting diff anyway)
62 // - multiple undos
63 // - allow user to set font/colors for graph and a menu heading to reset graph to the current (real) data BB is using
64 // - hovering mouse over a datapoint should give a tooltip with the point's info
65 // - allow for space around graph so user can grab points easier
66 // - when user deletes rightmost point on graph it gets replaced by second to rightmost (actual last SG_DataPoint) values -- that it doesn't now is non-obvious for user.
67 // - make it so that when user right-clicks (or anyclick, I guess) a datapoint it changes color or in some way let's user know it is selected
68 // - create a zoom function, also scrolling ability (via side scrollbars)
69 // - provide a function that deletes redundant points (points in which beatfreq doesn't change over three points)
70 // - allow user to choose colors, get default background from configuration
71 
72 //BUGS:
73 // - coloring current line of schedule is no longer functional (around line 473) because of multiple voices.
74 // - with IMMENSE schedules, it becomes clear that doing anything before anything else is done (like pasting then saving/moving/quiting before done) results in potential seg-faults or unpredicted behavior. Saving produces shorter files, for example. Should come up with a "Please Wait" window
75 // - updating voices duration to match total duration after a group-set requires a resize or something.
76 //- still might be places where totalscheduleduration could get set to 0 (which should cause divide-by-0 errors)
77 //- noticed that callback.cpp in main Gnaural tree isn't handling NULL returns from gtk_file_chooser_get_filename in Open and Save callbacks
78 
79 //#define SG_PRECISIONTYPE double
80 
81 //#define SG_DataPointSize 6
82 #define SG_GRAPHBOUNDARY 16
83 #define SG_GRIDY_MINSTEP  12
84 #define SG_GRIDY_MAXSTEP 32
85 #define SG_GRIDY_UNIT        1
86 #define SG_GRIDX_MINSTEP  32
87 #define SG_GRIDX_MAXSTEP 64
88 #define SG_GRIDX_UNIT        60
89 //#define SG_GraphFontSize  6
90 //#define SG_XTEXTOFFSET (SG_GraphFontSize+4)
91 //#define SG_XTEXTOFFSET 8
92 
93 //global vars:
94 int SG_DebugFlag = FALSE;       //set to true to spit-out debug info
95 SG_Voice *SG_FirstVoice = NULL;
96 SG_DataPoint *SG_CurrentDataPoint = NULL;
97 SG_BackupData SG_CopyPaste,
98  SG_UndoRedo;
99 SG_SelectionBox_type SG_SelectionBox;
100 double SG_TotalScheduleDuration = 1.0;
101 double SG_MaxScheduleBeatfrequency = 1.0;
102 double SG_MaxScheduleBasefrequency = 1.0;
103 int SG_GraphType = SG_GRAPHTYPE_BASEFREQ;       //Informs program what type of graph to draw/gather-data-through; can be SG_GRAPHTYPE_BASEFREQ, SG_GRAPHTYPE_BEATFREQ, SG_GRAPHTYPE_VOLUME, SG_GRAPHTYPE_VOLUME_BALANCE.
104 gboolean SG_GraphHasChanged = FALSE;    //lets any external code know when Graph appearance has changed (DP selection, hide/show voice); user must reset it manually
105 gboolean SG_DataHasChanged = FALSE;     //lets any external code know when it should reload data from SG; user must reset it manually
106 gboolean SG_ProgressIndicatorFlag = TRUE;       //to disallow progress indication if graph has been modified (out of sync with BB)
107 GdkPixmap *SG_pixmap = NULL;    //main drawing area, gets set in SG_Init()
108 GdkGC *SG_gc = NULL;
109 PangoLayout *SG_PangoLayout = NULL;     //controls the fonts associated with the main drawing area
110 int SG_GraphFontSize = 10;
111 int SG_DataPointSize = 6;       //keep squaresize even (I guess):
112 gboolean SG_MagneticPointerflag = FALSE;        //added 20101006 to get rid of Caps Lock hacks
113 
114 /////////////////////////////////////////////////////
115 //this is slow as hell -- for repetitious things, inlined this code
draw_text(GtkWidget * widget,gint x,gint y,gchar * s_utf8)116 void draw_text (GtkWidget * widget, gint x, gint y, gchar * s_utf8)
117 {
118  PangoLayout *layout;
119 
120  layout = pango_layout_new (gdk_pango_context_get ());
121  pango_layout_set_text (layout, s_utf8, -1);
122 
123  PangoFontDescription *fontdesc =
124   pango_font_description_copy (widget->style->font_desc);
125  //I can set the font here, but seems safer to work with what I know user has:
126  //pango_font_description_set_family (fontdesc, "Sans Bold Italic");
127  pango_font_description_set_size (fontdesc, PANGO_SCALE * 6);
128  //pango_layout_set_alignment(layout,PANGO_ALIGN_RIGHT);
129  pango_layout_set_font_description (layout, fontdesc);
130  pango_font_description_free (fontdesc);
131 
132  gdk_draw_layout (SG_pixmap, widget->style->black_gc, x, y, layout);
133 
134  g_object_unref (layout);
135 }
136 
137 /////////////////////////////////////////////////////
138 //Not used usually because it doesn't display info as readably
SG_DrawGridFast(GtkWidget * widget,double y_var)139 void SG_DrawGridFast (GtkWidget * widget, double y_var)
140 {
141  //NOTE: keeping this as an alternative: (much faster, but not as easy to read):
142  //Fast but not as friendly-to-read graph:
143  //First draw the horizontal marker lines:
144 #define SG_YSTEP 24
145 #define SG_XSTEP  64
146  char graphtext[32];            //this is used for all text rendering
147  int xtextoffset = widget->allocation.height - SG_GraphFontSize;
148  int index = widget->allocation.height;
149  double textstep = (SG_YSTEP * y_var / widget->allocation.height);
150  double textindex = 0;
151 
152  while ((index -= SG_YSTEP) > -1)
153  {
154   gdk_draw_line (SG_pixmap, SG_gc, 0, index, widget->allocation.width, index);
155   textindex += textstep;
156   sprintf (graphtext, "%6.5gHz", textindex);
157   pango_layout_set_text (SG_PangoLayout, graphtext, -1);
158   gdk_draw_layout (SG_pixmap, widget->style->black_gc, 0, index,
159                    SG_PangoLayout);
160  }
161 
162  //Next draw vertical lines:
163  index = 0;
164  textstep = (SG_XSTEP * SG_TotalScheduleDuration / widget->allocation.width);
165  textindex = 0;
166  while ((index += SG_XSTEP) < widget->allocation.width)
167  {
168   gdk_draw_line (SG_pixmap, SG_gc, index, 0, index,
169                  widget->allocation.height);
170   textindex += textstep;
171   sprintf (graphtext, "%dm %ds", ((int) (textindex + .5)) / 60,
172            ((int) (textindex + .5)) % 60);
173   pango_layout_set_text (SG_PangoLayout, graphtext, -1);
174   gdk_draw_layout (SG_pixmap, widget->style->black_gc, index, xtextoffset,
175                    SG_PangoLayout);
176  }
177 }
178 
179 /////////////////////////////////////////////////////
180 //This used to be inlined in SG_DrawGraph(); here for clarity
181 //This function could be speeded up a lot.
182 //NOTE: X is always assumed to reflect duration
SG_DrawGrid(GtkWidget * widget,double vscale,char * vtext)183 void SG_DrawGrid (GtkWidget * widget, double vscale, char *vtext)
184 {
185  static char graphtext[32];     //this is used for all text rendering -- does it really need to be static?
186  int xtextoffset = widget->allocation.height - SG_GraphFontSize;
187  double index;
188  double textindex;
189  double textstep;
190  double step;
191 
192  //First draw "friendly-to-read" (and extremely computationally intensive) horizontal grid lines (Y axis):
193  index = widget->allocation.height;
194  textindex = 0;
195  //deal with potential divide by zero issue:
196  if (vscale == 0)
197   vscale = 1.0;
198  step = index / vscale;
199  //deal with negative vscale:
200  if (vscale < 0)
201  {
202   textindex = vscale;
203   step /= -2;
204  }
205  textstep = SG_GRIDY_UNIT;      //this is the basis of one "unit" of vertical climb
206  //Bruteforce way to get grid-steps that are within a user-friendly range:
207  //these next two uglies could be speeded up in a number of ways, no?
208  if (step < SG_GRIDY_MINSTEP)
209  {
210   do
211   {
212    step *= 2;
213    textstep *= 2;
214   }
215   while (step < SG_GRIDY_MINSTEP);
216  }
217  else if (step > SG_GRIDY_MAXSTEP)
218  {
219   do
220   {
221    step *= .5;
222    textstep *= .5;
223   }
224   while (step > SG_GRIDY_MAXSTEP);
225  }
226  int ax;
227 
228  //draw y-axis text and horiz grid lines:
229  while ((ax = (int) ((index -= step) + .5)) > -1)
230  {
231   gdk_draw_line (SG_pixmap, SG_gc, 0, ax, widget->allocation.width, ax);
232   textindex += textstep;
233   sprintf (graphtext, "%g%s", textindex, vtext);
234   pango_layout_set_text (SG_PangoLayout, graphtext, -1);
235   gdk_draw_layout (SG_pixmap, widget->style->black_gc, 0, ax, SG_PangoLayout);
236  }
237 
238  //Now draw "friendly-to-read" (and extremely computationally intensive) vertical grid lines along x-axis:
239  index = 0;
240  textindex = 0;
241  textstep = SG_GRIDX_UNIT;      //this is the basis of one "unit" of horizontal movement
242  step = (SG_GRIDX_UNIT * widget->allocation.width) / SG_TotalScheduleDuration;  //BUG: THIS WILL CRASH IF SG_TotalScheduleDuration IS ZERO
243  int minutes,
244   seconds;
245 
246  //Bruteforce way to get grid-steps that are within a user-friendly range:
247  if (step < SG_GRIDX_MINSTEP)
248  {
249   do
250   {
251    step *= 2;
252    textstep *= 2;
253   }
254   while (step < SG_GRIDX_MINSTEP);
255  }
256  else if (step > SG_GRIDX_MAXSTEP)
257  {
258   do
259   {
260    step *= .5;
261    textstep *= .5;
262   }
263   while (step > SG_GRIDX_MAXSTEP);
264  }
265  while ((ax = (int) ((index += step) + .5)) < widget->allocation.width)
266  {
267   gdk_draw_line (SG_pixmap, SG_gc, ax, 0, ax, widget->allocation.height);
268   textindex += textstep;
269   //first handle if there are no minutes at all:
270   if ((minutes = ((int) (textindex + .5)) / SG_GRIDX_UNIT) < 1)
271    sprintf (graphtext, "%ds", ((int) (textindex + .5)));
272   else if ((seconds = ((int) (textindex + .5)) % SG_GRIDX_UNIT) == 0)
273    sprintf (graphtext, "%dm", minutes);
274   else
275    sprintf (graphtext, "%dm%ds", minutes, seconds);
276   pango_layout_set_text (SG_PangoLayout, graphtext, -1);
277   gdk_draw_layout (SG_pixmap, widget->style->black_gc, ax, xtextoffset,
278                    SG_PangoLayout);
279  }
280 }
281 
282 /////////////////////////////////////////////////////
283 //20100405 - separated from SG_DrawGraph so user could set it
SG_FontSetup(GtkWidget * widget)284 void SG_FontSetup (GtkWidget * widget)
285 {
286  PangoFontDescription *fontdesc =
287   pango_font_description_copy (widget->style->font_desc);
288  //   pango_font_description_copy (NULL);
289  //I can set the font here, but seems safer to work with what I know user has:
290  //pango_font_description_set_family (fontdesc, "Sans Bold Italic");
291  //  pango_font_description_set_size (fontdesc, PANGO_SCALE * SG_GraphFontSize);
292 
293  /*
294     if (FALSE == pango_font_description_get_size_is_absolute (fontdesc))
295     {
296     double dpi = gdk_screen_get_resolution (gdk_screen_get_default ());
297     SG_GraphFontSize = dpi * SG_GraphFontSize /  72.0;
298     }
299     SG_DBGOUT_INT ("New font size:", SG_GraphFontSize);
300   */
301 
302  pango_font_description_set_absolute_size (fontdesc,
303                                            PANGO_SCALE * SG_GraphFontSize);
304  //pango_layout_set_alignment(layout,PANGO_ALIGN_RIGHT);
305  pango_layout_set_font_description (SG_PangoLayout, fontdesc);
306  pango_font_description_free (fontdesc);
307 }
308 
309 /////////////////////////////////////////////////////
310 //If there is one function I should speed-up,
311 //this is it. Among other things, I could split
312 //it up in to separate functions, some of which
313 //could be left-out during high refresh periods
314 ////
315 // Draw graph on the screen
316 /////////////////////////////////////////////////////
317 #define SG_TEXTYOFFSET 8
SG_DrawGraph(GtkWidget * widget)318 void SG_DrawGraph (GtkWidget * widget)
319 {
320  int x,
321   y;
322 
323  //a20070702: needed so initializations don't cause a flood of GDK errors:
324  if (SG_pixmap == NULL)
325  {
326   return;
327  }
328 
329  // Create a GC to draw with if it isn't already created:
330  if (SG_gc == NULL)     //you delete something like this with g_object_unref(SG_gc);
331  {
332   SG_gc = gdk_gc_new (widget->window);
333  }
334 
335  //Create Pango layout and set it up to write text with if it isn't already created:
336  if (SG_PangoLayout == NULL)    //you delete something like this with g_object_unref(gc);
337  {
338   //prepare for Text writing:
339   SG_PangoLayout = pango_layout_new (gdk_pango_context_get ());
340   SG_FontSetup (widget);
341  }
342 
343  //First paint the background:
344  //NOTE: colors are between 0 and ~65000 in GDK
345  GdkColor color;
346 
347  color.red = color.green = color.blue = 65000;
348  gdk_gc_set_rgb_fg_color (SG_gc, &color);
349  gdk_draw_rectangle (SG_pixmap,
350                      //        widget->style->white_gc,
351                      SG_gc, TRUE, 0, 0, widget->allocation.width,
352                      widget->allocation.height);
353 
354  color.red = color.green = color.blue = 55555;
355  gdk_gc_set_rgb_fg_color (SG_gc, &color);
356 
357  SG_Voice *curVoice;
358 
359  //NOW draw grid:
360  // ------START GRID-----
361  switch (SG_GraphType)
362  {
363  case SG_GRAPHTYPE_BEATFREQ:
364   SG_DrawGrid (widget, SG_MaxScheduleBeatfrequency, " hz");
365   /*
366      //now be sure voicetypes inappropriate for this graphtype aren't drawn:
367      curVoice = SG_FirstVoice;
368      while (curVoice != NULL) {
369      if (curVoice->type != BB_VOICETYPE_BINAURALBEAT) curVoice->visibility = TRUE;
370      else curVoice->visibility = FALSE;
371      //advance to next voice:
372      curVoice = curVoice->NextVoice;
373      }
374    */
375   break;
376 
377  case SG_GRAPHTYPE_BASEFREQ:
378   SG_DrawGrid (widget, SG_MaxScheduleBasefrequency, " hz");
379   break;
380 
381  case SG_GRAPHTYPE_VOLUME:
382   SG_DrawGrid (widget, 1.0, " vol");
383   break;
384 
385  case SG_GRAPHTYPE_VOLUME_BALANCE:
386   SG_DrawGrid (widget, -1.0, " bal");
387   break;
388  }
389  // ------END GRID-----
390 
391  SG_DrawCurrentPointInGraph (widget);
392 
393  //==
394  //START presenting the data:
395 #define SG_ROUNDER +.5
396  //#define SG_ROUNDER
397  //First, draw the lines:
398  //Start with all the easy points (where I have a valid current and next):
399  curVoice = SG_FirstVoice;
400  while (curVoice != NULL)
401  {
402   if (curVoice->hide == FALSE)
403   {     //start "is voice visible?"
404 
405    if (curVoice->state == SG_SELECTED)
406    {
407     color.red = 65535;
408     color.green = 0;
409     color.blue = 65535;
410    }
411    else
412    {
413     color.red = 32768;
414     color.green = 32768;
415     color.blue = 65535;
416    }
417    gdk_gc_set_rgb_fg_color (SG_gc, &color);
418 
419    SG_DataPoint *curDP = curVoice->FirstDataPoint;
420    int x_next = 0,
421     y_next = (int) (curDP->y SG_ROUNDER);
422 
423    while (curDP->NextDataPoint != NULL)
424    {
425     x = (int) (curDP->x SG_ROUNDER);
426     y = (int) (curDP->y SG_ROUNDER);
427     x_next = (int) (curDP->NextDataPoint->x SG_ROUNDER);
428     y_next = (int) (curDP->NextDataPoint->y SG_ROUNDER);
429     gdk_draw_line (SG_pixmap, SG_gc, x, y, x_next, y_next);
430 
431     //advance to next point:
432     curDP = curDP->NextDataPoint;
433    }
434    ;    //end of main connect the dots loop
435    //======
436 
437    //Now draw last line from final graphpoint tranlated from the first datapoint:
438    //x=(int)(SG_FirstDataPoint->x+widget->allocation.width+-1.5);
439    x = (int) (curVoice->FirstDataPoint->x + widget->allocation.width);
440    y = (int) (curVoice->FirstDataPoint->y SG_ROUNDER);
441    gdk_draw_line (SG_pixmap, SG_gc, x_next, y_next, x, y);
442 
443    //Now start making the marker rectangles:
444    //First do square for last graphpoint, since it is a special case and
445    //the preparatory math already got done above:
446    //decide last graphpoint square color (will be same as first datapoint's):
447    if (curVoice->FirstDataPoint->state == SG_UNSELECTED)
448     color.red = color.green = color.blue = 0;
449    else
450    {
451     color.red = 65535;
452     color.green = 32768;
453     color.blue = 32768;
454    }
455    gdk_gc_set_rgb_fg_color (SG_gc, &color);
456    gdk_draw_rectangle (SG_pixmap, SG_gc, FALSE,
457                        x - (SG_DataPointSize >> 1),
458                        y - (SG_DataPointSize >> 1), SG_DataPointSize,
459                        SG_DataPointSize);
460 
461   }     //end "is voice visible?"
462   //advance to next voice:
463   curVoice = curVoice->NextVoice;
464  }
465 
466  // Now do the rest of the marker rectangles:
467  //split all the loops on 20060414 to solve problem of pasted selected DPs hiding behind
468  //unselected  parent DPs:
469  //First do unselected:
470  curVoice = SG_FirstVoice;
471  while (curVoice != NULL)
472  {
473   if (curVoice->hide == FALSE)
474   {     //start "is voice visible?"
475 
476    color.red = color.green = color.blue = 0;
477    gdk_gc_set_rgb_fg_color (SG_gc, &color);
478    SG_DataPoint *curDP = curVoice->FirstDataPoint;
479 
480    do
481    {
482     if (curDP->state == SG_UNSELECTED)
483      gdk_draw_rectangle (SG_pixmap, SG_gc, FALSE,
484                          (int) (curDP->x SG_ROUNDER) -
485                          (SG_DataPointSize >> 1),
486                          (int) (curDP->y SG_ROUNDER) -
487                          (SG_DataPointSize >> 1), SG_DataPointSize,
488                          SG_DataPointSize);
489     //advance to next point:
490     curDP = curDP->NextDataPoint;
491    }
492    while (curDP != NULL);
493   }     //end "is voice visible?"
494   //advance to next voice:
495   curVoice = curVoice->NextVoice;
496  }
497 
498  //Now do selected and draw highlighted line for BB's current entry:
499  color.red = 65535;
500  color.green = 32768;
501  color.blue = 32768;
502  gdk_gc_set_rgb_fg_color (SG_gc, &color);
503  curVoice = SG_FirstVoice;
504  while (curVoice != NULL)
505  {
506   if (curVoice->hide == FALSE)
507   {     //start "is voice visible?"
508    SG_DataPoint *curDP = curVoice->FirstDataPoint;
509 
510    do
511    {
512     //first check to see if progress line should be drawn:
513     //START of highlighted line indicating schedule progress
514     //20061124 BUG: THIS DOESN"T WORK WITH NEW MULTI-VOICE APPROACH:
515     //~ if (SG_ProgressIndicatorFlag == TRUE && count++ == bb->ScheduleCount) {
516     //~ color.red = 65535 >> 2;
517     //~ color.green = 65535;
518     //~ color.blue = 65535 >> 2;
519     //~ gdk_gc_set_rgb_fg_color(SG_gc, &color);
520     //~ if (curDP->NextDataPoint != NULL) {
521     //~ x = (int)(curDP->x SG_ROUNDER);
522     //~ y = (int)(curDP->y SG_ROUNDER);
523     //~ x_next = (int)(curDP->NextDataPoint->x SG_ROUNDER);
524     //~ y_next = (int)(curDP->NextDataPoint->y SG_ROUNDER);
525     //~ } else {
526     //~ x = (int)(SG_FirstDataPoint->x + widget->allocation.width);
527     //~ y = (int)(SG_FirstDataPoint->y SG_ROUNDER);
528     //~ x_next = (int)(curDP->x SG_ROUNDER);
529     //~ y_next = (int)(curDP->y SG_ROUNDER);
530     //~ }
531     //~ gdk_draw_line (SG_pixmap, SG_gc, x, y, x_next, y_next);
532     //~ color.red = 65535;
533     //~ color.green = 32768;
534     //~ color.blue = 32768;
535     //~ gdk_gc_set_rgb_fg_color(SG_gc, &color);
536     //~ } //END of highlighted line indicating schedule progress
537     //now draw squares:
538     if (curDP->state == SG_SELECTED)
539      gdk_draw_rectangle (SG_pixmap, SG_gc, FALSE,
540                          (int) (curDP->x SG_ROUNDER) -
541                          (SG_DataPointSize >> 1),
542                          (int) (curDP->y SG_ROUNDER) -
543                          (SG_DataPointSize >> 1), SG_DataPointSize,
544                          SG_DataPointSize);
545     //advance to next point:
546     curDP = curDP->NextDataPoint;
547    }
548    while (curDP != NULL);
549 
550   }     //end "is voice visible?"
551   //advance to next voice:
552   curVoice = curVoice->NextVoice;
553  }
554 
555  //all done, ask manager to put it out there:
556  gtk_widget_queue_draw_area (widget, 0, 0, widget->allocation.width,
557                              widget->allocation.height);
558 }
559 
560 ///////////////////////////////
561 //20070710: taken from GnauralJavaApplet:
SG_DrawCurrentPointInGraph(GtkWidget * widget)562 void SG_DrawCurrentPointInGraph (GtkWidget * widget)
563 {
564  if (BB_TotalDuration <= 0)
565  {
566   return;
567  }
568  GdkColor color;
569 
570  color.red = 65535;
571  color.green = 0;
572  color.blue = 0;
573  gdk_gc_set_rgb_fg_color (SG_gc, &color);
574  int x =
575   (int) ((widget->allocation.width / BB_TotalDuration) *
576          (BB_CurrentSampleCount / BB_AUDIOSAMPLERATE) + .5f);
577  gdk_draw_line (SG_pixmap, SG_gc, x, 0, x, widget->allocation.height);
578 }
579 
580 /////////////////////////////////////////////////////
581 //HANDLER CODE:
SG_button_release_event(GtkWidget * widget,GdkEventButton * event)582 gboolean SG_button_release_event (GtkWidget * widget, GdkEventButton * event)
583 {
584  if (event->button != 1 || SG_pixmap == NULL)
585  {
586   return TRUE;
587  }
588  //this was added 20060414. It is sort of arbitrary that it is here, so keep aware of it:
589  //removed in 20070731; the function just doesn't work very well
590  //SG_DeleteDuplicateDataPoints (widget);
591  //=============
592  //first see if we're doing bounding box selecting:
593  if (SG_SelectionBox.status != 0)
594  {
595   SG_SelectionBox.status = 0;   //tell renderer not to draw box
596   SG_DrawGraph (widget);        //erases last rendering of box
597   return TRUE;  //don't do any dp processing
598  }
599 
600  // if (SG_CurrentDataPoint!=NULL && event->button == 1 && SG_pixmap_Graph != NULL)
601 
602  if (SG_CurrentDataPoint != NULL)
603  {
604   //First the easy one: convert all Y values to Hz:
605   SG_ConvertYToData_SelectedPoints (widget);
606 
607   //User done moving, so now finally convert all X values to Durations:
608   //Can't move first data point along X axis://NOTE: I think it is no longer necessary to do this here (other functions are safe on this point). But safest to just leave it...
609   // if (SG_CurrentDataPoint!=SG_FirstDataPoint)
610   // {
611   //Now the hard one: convert SG_CurrentDataPoint's x to duration value. Hard because
612   //I also need to change PrevDataPoint's duration:
613   if (SG_TotalScheduleDuration == 0 || widget->allocation.width == 0)
614   {
615    return TRUE;
616   }
617   SG_ConvertXToDuration_AllPoints (widget);
618   // }
619 
620   SG_CurrentDataPoint = NULL;
621   SG_ConvertDataToXY (widget);  // NOTE: I need to call this both to set limits and to bring XY's outside of graph back in
622   SG_DrawGraph (widget);
623   SG_GraphHasChanged = SG_DataHasChanged = TRUE;
624  }
625  return TRUE;
626 }
627 
628 /////////////////////////////////////////////////////
629 //Tests if a mouse-click landed on a square
630 //NOTE: DP's Voice must be visible to be returned
SG_TestXYForDataPoint(gdouble x,gdouble y,int graphwidth)631 SG_DataPoint *SG_TestXYForDataPoint (gdouble x, gdouble y, int graphwidth)
632 {
633  SG_Voice *curVoice = SG_FirstVoice;
634 
635  while (curVoice != NULL)
636  {
637   if (curVoice->hide == FALSE)
638   {
639    //make all points undraggable:
640    SG_DataPoint *curDP = curVoice->FirstDataPoint;
641 
642    //first check to see if last square is clicked (since it is REALLY the first one):
643    if ((x - SG_DataPointSize) < (curDP->x + graphwidth) &&
644        (x + SG_DataPointSize) > (curDP->x + graphwidth) &&
645        (y - SG_DataPointSize) < curDP->y && (y + SG_DataPointSize) > curDP->y)
646     return curDP;
647 
648    //wasn't last square, so now trudge through all of them:
649    do
650    {
651     if ((x - SG_DataPointSize) < curDP->x &&
652         (x + SG_DataPointSize) > curDP->x &&
653         (y - SG_DataPointSize) < curDP->y
654         && (y + SG_DataPointSize) > curDP->y)
655      return curDP;
656     curDP = curDP->NextDataPoint;
657    }
658    while (curDP != NULL);
659   }
660   //advance to next voice:
661   curVoice = curVoice->NextVoice;
662  }
663  //########
664  //If it got here, user missed all existant points -- therefore it is
665  //probably a NEW DATA POINT (unless it had the same x as an existant point).
666  //########
667  return NULL;
668 }
669 
670 /////////////////////////////////////////////////////
671 //if SG_Voice * == NULL, returns currently selected voice or
672 //or NULL if it can't find one (so be sure to check!!!).
673 //Otherwise deselects all voices, then selects submitted one:
SG_SelectVoice(SG_Voice * voice)674 SG_Voice *SG_SelectVoice (SG_Voice * voice)
675 {
676  SG_Voice *curVoice = SG_FirstVoice;
677 
678  //if user passed NULL, just look for current selected voice:
679  if (voice == NULL)
680  {
681   while (curVoice != NULL)
682   {
683    if (curVoice->state == SG_SELECTED)
684    {
685     return curVoice;
686    }
687    curVoice = curVoice->NextVoice;
688   }
689   //if it got here, no selected voice was found. So
690   //try to find ANY visible voice, and if not, return NULL:
691   curVoice = SG_FirstVoice;
692   while (curVoice != NULL)
693   {
694    if (curVoice->hide == FALSE)
695    {
696     curVoice->state = SG_SELECTED;
697     return curVoice;
698    }
699    curVoice = curVoice->NextVoice;
700   }
701   //if it got here, there was NO visible voice:
702   return NULL;
703  }
704 
705  //if got here, set selected voice, but first check to see if it's
706  //visible (if not, arbitrarily look for another):
707  if (voice->hide == TRUE)
708  {
709   voice->state = SG_UNSELECTED;
710   //interesting, no? this should tell it to either find a valid
711   //selected voice or to choose an arbitary new one.
712   return SG_SelectVoice (NULL);
713  }
714 
715  curVoice = SG_FirstVoice;
716  while (curVoice != NULL)
717  {
718   curVoice->state = SG_UNSELECTED;
719   curVoice = curVoice->NextVoice;
720  }
721  voice->state = SG_SELECTED;
722  return (voice);
723 }
724 
725 /////////////////////////////////////////////////////
726 //now I figure out which data point got clicked, if any:
SG_button_press_event(GtkWidget * widget,GdkEventButton * event)727 gboolean SG_button_press_event (GtkWidget * widget, GdkEventButton * event)
728 {
729  if (SG_pixmap == NULL)
730   return TRUE;
731 
732  //############################
733  //Check for button 1: [Select point or create point]
734  //############################
735  if (event->button == 1)
736  {
737   //Button 1, if over a point, select it, (making it and other
738   //selected draggable. Otherwise unselect all and potentially create new point.
739   //Test if mouse clicked an existing point, and if so make point selected:
740   SG_CurrentDataPoint =
741    SG_TestXYForDataPoint (event->x, event->y, widget->allocation.width);
742 
743   //If !NULL, it found a suitable point:
744   if (SG_CurrentDataPoint != NULL)
745   {
746    //Backup points:
747    SG_BackupDataPoints (widget);        //a20070620
748    //First, make this SG_Voice selected:
749    SG_SelectVoice ((SG_Voice *) (SG_CurrentDataPoint->parent));
750    //OVERVIEW: If point is already selected, see if Ctrl key is pressed to unselect it. If Ctrl isn't pressed,
751    //just leave the selected point alone so that motion_notify_event can be allowed to move a clump of
752    //points. But If point is not selected, deselect all points then select this one.
753    if (SG_CurrentDataPoint->state == SG_SELECTED)
754    {
755     if ((event->state & GDK_CONTROL_MASK) != 0)
756      SG_CurrentDataPoint->state = SG_UNSELECTED;
757     return TRUE;
758    }
759    if ((event->state & GDK_SHIFT_MASK) == 0)
760    {
761     SG_DeselectDataPoints ();
762    }
763    SG_CurrentDataPoint->state = SG_SELECTED;
764    SG_DrawGraph (widget);
765    return TRUE;
766   }
767 
768   //must check for SHIFT and CTRL keys again in this context,
769   //just in case use is dragging a selection box:
770   if (0 == (event->state & GDK_SHIFT_MASK) && 0 == (event->state & GDK_CONTROL_MASK))   //lock mask added 20100727
771   {
772    //Backup points:
773    SG_BackupDataPoints (widget);        //a20070620 - this often just backs up selections
774    if (FALSE == SG_MagneticPointerflag) //added 20101006 to stop unselecting during Magnetic Pointer operations
775    {
776     SG_DeselectDataPoints ();
777    }
778   }
779 
780   //Mouse clicked a non-existant point, so first see if it was a double-click (create a new point):
781   if (event->type == GDK_2BUTTON_PRESS)
782   {
783    // SG_CurrentDataPoint=SG_InsertNewDataPointXY(widget, event->x,event->y+.5);
784    // 20060714 I think this is working now, but I said "THIS ISN'T WORKING YET!!!!! NOT FINISHED 20060504"
785    SG_Voice *tmpVoice = SG_SelectVoice (NULL);
786 
787    if (tmpVoice == NULL)
788    {
789     //got rid of next two lines 20070731:
790     //    tmpVoice = SG_FirstVoice;
791     //    SG_FirstVoice->state = SG_SELECTED;
792     return TRUE;        // not legal to create new point
793    }
794    //Backup points:
795    SG_BackupDataPoints (widget);        //a20070620
796    SG_CurrentDataPoint =
797     SG_InsertNewDataPointXY (widget, tmpVoice, event->x, event->y + .5);
798    if (SG_CurrentDataPoint != NULL)
799    {
800     SG_ProgressIndicatorFlag = FALSE;
801     //Point exists, so convert it's Y to real data (X got determined at creation):
802     SG_ConvertYToData_SelectedPoints (widget);
803     SG_DrawGraph (widget);
804     SG_GraphHasChanged = SG_DataHasChanged = TRUE;
805     return TRUE;
806    }
807   }     //END double-click portion
808 
809   //if it got here, use XY as start of a selection bounding box:
810   SG_SelectionBox.status = 1;
811   SG_SelectionBox.startX = (int) (event->x);
812   SG_SelectionBox.startY = (int) (event->y);
813   SG_DrawGraph (widget);        //I don't think this is necessary
814   return TRUE;
815  }
816  //############################
817  //Check for button 2 [delete point]:
818  //############################
819  else if (event->button == 2)
820  {
821   SG_Voice *curVoice = SG_FirstVoice;
822 
823   while (curVoice != NULL)
824   {
825    if (curVoice->hide == FALSE)
826    {
827     SG_DataPoint *curDP = curVoice->FirstDataPoint;
828 
829     do
830     {
831      if ((event->x - SG_DataPointSize) < curDP->x &&
832          (event->x + SG_DataPointSize) > curDP->x &&
833          (event->y - SG_DataPointSize) < curDP->y &&
834          (event->y + SG_DataPointSize) > curDP->y)
835      {
836       SG_ProgressIndicatorFlag = FALSE;
837       //Backup points:
838       SG_BackupDataPoints (widget);     //a20070620
839       if ((event->state & GDK_SHIFT_MASK) == 0)
840       {
841        SG_DeleteDataPoint (curDP, FALSE);
842       }
843       else
844       {
845        SG_DeleteDataPoint (curDP, TRUE);
846       }
847       SG_ConvertDataToXY (widget);
848       SG_DrawGraph (widget);
849       SG_DataHasChanged = SG_GraphHasChanged = TRUE;    //added 20070803
850       return TRUE;
851      }
852      curDP = curDP->NextDataPoint;
853     }
854     while (curDP != NULL);
855 
856     //If I got here, it must be the last square (which is REALLY the first SG_DataPoint):
857     curDP = curVoice->FirstDataPoint;
858     if ((event->x + SG_DataPointSize) > (curDP->x + widget->allocation.width) && (event->y - SG_DataPointSize) < curDP->y && (event->y + SG_DataPointSize) > curDP->y && curVoice->FirstDataPoint->NextDataPoint != NULL)       //to be sure we don't end up trying to delete the ONLY point
859     {
860      //Backup points:
861      SG_BackupDataPoints (widget);      //a20070620
862      // if ((event->state & GDK_SHIFT_MASK)==0) SG_DeleteDataPoint(curDP);
863      // else SG_DeleteDataPoint(curDP, TRUE);
864      SG_DeleteDataPoint (curDP, FALSE);
865      SG_ConvertDataToXY (widget);
866      SG_DrawGraph (widget);
867      return TRUE;
868     }
869    }
870    curVoice = curVoice->NextVoice;
871   }
872   //shouldn't actually ever get here...
873   return TRUE;
874  }
875  //############################
876  //Not button 1 or 2, so check for button 3 [bring-up a properties box for the SG_DataPoint]:
877  //############################
878  else if (event->button == 3)
879  {
880   SG_DataPoint *curDP;
881   SG_Voice *curVoice = SG_FirstVoice;
882 
883   while (curVoice != NULL)
884   {
885    if (curVoice->hide == FALSE)
886    {
887     curDP = curVoice->FirstDataPoint;
888     //run through all the DataPoints to find the one clicked (if any):
889     //NOTE: we ignore trying to click on last visible datapoint because it isn't a real one (rather, 'tis the first one)
890     do
891     {
892      if ((event->x - SG_DataPointSize) < curDP->x &&
893          (event->x + SG_DataPointSize) > curDP->x &&
894          (event->y - SG_DataPointSize) < curDP->y &&
895          (event->y + SG_DataPointSize) > curDP->y)
896      {
897       SG_BackupDataPoints (widget);
898       SG_DataPointPropertiesDialog (widget, curDP);
899       SG_ProgressIndicatorFlag = FALSE;
900       SG_ConvertDataToXY (widget);
901       SG_DrawGraph (widget);
902       SG_GraphHasChanged = SG_DataHasChanged = TRUE;
903       return TRUE;
904      }
905      curDP = curDP->NextDataPoint;
906     }
907     while (curDP != NULL);
908    }
909    curVoice = curVoice->NextVoice;
910   }
911   //if I got here, the Properties box will affect ALL selected DPs:
912   //Following part scans all selected DPs for each field category below
913   // to see if ALL already are the same, so that value can be
914   // used instead of -1 (to inform user that this is the case):
915   //NOTE: -1 means:
916   //   outgoing: "don't show anythign in box."
917   //   return:"don't change the DP value associated with that box"
918   gboolean firstselflag = FALSE;
919   SG_DataPoint propDP,
920    tempDP;
921 
922   tempDP.basefreq = tempDP.beatfreq = tempDP.volume_left =
923    tempDP.volume_right = tempDP.duration = -1;
924   curVoice = SG_FirstVoice;
925   while (curVoice != NULL)
926   {
927    if (curVoice->hide == FALSE)
928    {
929     curDP = curVoice->FirstDataPoint;
930     do
931     {
932      if (curDP->state == SG_SELECTED)
933      {
934       if (firstselflag == FALSE)
935       {
936        firstselflag = TRUE;
937        propDP.basefreq = tempDP.basefreq = curDP->basefreq;
938        propDP.beatfreq = tempDP.beatfreq = curDP->beatfreq;
939        propDP.volume_left = tempDP.volume_left = curDP->volume_left;
940        propDP.volume_right = tempDP.volume_right = curDP->volume_right;
941        propDP.duration = tempDP.duration = curDP->duration;
942       }
943       if (tempDP.basefreq != curDP->basefreq && propDP.basefreq != -1)
944        propDP.basefreq = -1;
945       if (tempDP.beatfreq != curDP->beatfreq && propDP.beatfreq != -1)
946        propDP.beatfreq = -1;
947       if (tempDP.volume_left != curDP->volume_left &&
948           propDP.volume_left != -1)
949        propDP.volume_left = -1;
950       if (tempDP.volume_right != curDP->volume_right &&
951           propDP.volume_right != -1)
952        propDP.volume_right = -1;
953       if (tempDP.duration != curDP->duration && propDP.duration != -1)
954        propDP.duration = -1;
955      }
956      curDP = curDP->NextDataPoint;
957     }
958     while (curDP != NULL);
959    }
960    curVoice = curVoice->NextVoice;
961   }
962 
963   propDP.parent = NULL; //this tells function to use different form of properites dialog
964   //now run through all the DataPoints to adjust all selected DPs (if any):
965   if (TRUE == SG_DataPointPropertiesDialog (widget, &propDP))
966   {
967    SG_BackupDataPoints (widget);
968    curVoice = SG_FirstVoice;
969    while (curVoice != NULL)
970    {
971     if (curVoice->hide == FALSE)
972     {
973      curDP = curVoice->FirstDataPoint;
974      do
975      {
976       if (curDP->state == SG_SELECTED)
977       {
978        if (propDP.basefreq != -1)
979         curDP->basefreq = propDP.basefreq;
980        if (propDP.volume_left != -1)
981         curDP->volume_left = propDP.volume_left;
982        if (propDP.volume_right != -1)
983         curDP->volume_right = propDP.volume_right;
984        if (propDP.duration != -1)
985         curDP->duration = propDP.duration;
986        if (propDP.beatfreq != -1)
987         curDP->beatfreq = propDP.beatfreq;
988       }
989       curDP = curDP->NextDataPoint;
990      }
991      while (curDP != NULL);
992     }
993     curVoice = curVoice->NextVoice;
994    }
995    SG_ProgressIndicatorFlag = FALSE;
996    SG_ConvertDataToXY (widget);
997    SG_DrawGraph (widget);
998    SG_GraphHasChanged = SG_DataHasChanged = TRUE;
999   }
1000   return TRUE;
1001  }
1002  return TRUE;
1003 }
1004 
1005 //////////////////////////////////////
1006 //this counts voices, dps, selected dps, and also how many voices have dps.
1007 //NOTE: voicecount_selecteddp is actually count of voices with selected DPs
SG_CountAllData(int * voicecount_all,int * voicecount_selecteddp,int * dpcount_all,int * dpcount_selected)1008 void SG_CountAllData (int *voicecount_all, int *voicecount_selecteddp,
1009                       int *dpcount_all, int *dpcount_selected)
1010 {
1011  //do some chores to get some numbers:
1012  *dpcount_all = 0;
1013  *dpcount_selected = 0;
1014  *voicecount_all = 0;
1015  *voicecount_selecteddp = 0;
1016  gboolean newvflag;
1017  SG_Voice *curVoice = SG_FirstVoice;
1018 
1019  while (curVoice != NULL)
1020  {
1021   ++(*voicecount_all);
1022   newvflag = TRUE;
1023   SG_DataPoint *curDP = curVoice->FirstDataPoint;
1024 
1025   while (curDP != NULL)
1026   {
1027    ++(*dpcount_all);
1028    if (curDP->state == SG_SELECTED)
1029    {
1030     ++(*dpcount_selected);
1031     if (newvflag == TRUE)
1032     {
1033      ++(*voicecount_selecteddp);
1034     }
1035     newvflag = FALSE;
1036    }
1037    curDP = curDP->NextDataPoint;
1038   }
1039   curVoice = curVoice->NextVoice;
1040  }
1041 }
1042 
1043 //////////////////////////////////////
1044 //this counts dps and selected dps in given voice
SG_CountVoiceDPs(SG_Voice * curVoice,int * dpcount_all,int * dpcount_selected)1045 void SG_CountVoiceDPs (SG_Voice * curVoice, int *dpcount_all,
1046                        int *dpcount_selected)
1047 {
1048  *dpcount_all = 0;
1049  *dpcount_selected = 0;
1050  if (curVoice != NULL)
1051  {
1052   SG_DataPoint *curDP = curVoice->FirstDataPoint;
1053 
1054   while (curDP != NULL)
1055   {
1056    ++(*dpcount_all);
1057    if (curDP->state == SG_SELECTED)
1058    {
1059     ++(*dpcount_selected);
1060    }
1061    curDP = curDP->NextDataPoint;
1062   }
1063  }
1064 }
1065 
1066 /////////////////////////////////////////////////////
1067 //If curDP->parent == NULL, properties box is tailored
1068 //for multiple DPs, otherwise for just one
SG_DataPointPropertiesDialog(GtkWidget * widget,SG_DataPoint * curDP)1069 gboolean SG_DataPointPropertiesDialog (GtkWidget * widget,
1070                                        SG_DataPoint * curDP)
1071 {
1072  gboolean res = FALSE;
1073  char tmpstring[256];
1074 
1075  if (curDP == NULL)
1076   return FALSE;
1077  if (curDP->parent != NULL)
1078   sprintf (tmpstring, "Properties: SG_DataPoint");
1079  else
1080   sprintf (tmpstring, "Properties: Selected DataPoints");
1081 
1082  GtkWidget *dialog = gtk_dialog_new_with_buttons (tmpstring,
1083                                                   NULL,
1084                                                   (GtkDialogFlags)
1085                                                   (GTK_DIALOG_MODAL |
1086                                                    GTK_DIALOG_DESTROY_WITH_PARENT),
1087                                                   GTK_STOCK_OK,
1088                                                   GTK_RESPONSE_ACCEPT,
1089                                                   GTK_STOCK_CANCEL,
1090                                                   GTK_RESPONSE_REJECT,
1091                                                   NULL);
1092 
1093  //add some stuff:
1094  GtkWidget *label_dur = gtk_label_new ("Event Duration (sec.):");
1095  GtkWidget *entry_Input_dur = gtk_entry_new ();
1096  GtkWidget *label_volume_left =
1097   gtk_label_new ("Volume Right (range 0 - 1.0):");
1098  GtkWidget *entry_Input_volume_left = gtk_entry_new ();
1099  GtkWidget *label_volume_right =
1100   gtk_label_new ("Volume Left (range 0 - 1.0):");
1101  GtkWidget *entry_Input_volume_right = gtk_entry_new ();
1102  GtkWidget *label_starts = gtk_label_new ("STARTING VALUES:");
1103  GtkWidget *label_beatfreq = gtk_label_new ("Beat Frequency (hz):");
1104  GtkWidget *entry_Input_beatfreq = gtk_entry_new ();
1105  GtkWidget *label_basefreq = gtk_label_new ("Base Frequency (hz):");
1106  GtkWidget *entry_Input_basefreq = gtk_entry_new ();
1107 
1108  if (curDP->duration != -1)
1109  {
1110   sprintf (tmpstring, "%g", curDP->duration);
1111   gtk_entry_set_text (GTK_ENTRY (entry_Input_dur), tmpstring);
1112  }
1113  if (curDP->beatfreq != -1)
1114  {
1115   sprintf (tmpstring, "%g", curDP->beatfreq);
1116   gtk_entry_set_text (GTK_ENTRY (entry_Input_beatfreq), tmpstring);
1117  }
1118  if (curDP->basefreq != -1)
1119  {
1120   sprintf (tmpstring, "%g", curDP->basefreq);
1121   gtk_entry_set_text (GTK_ENTRY (entry_Input_basefreq), tmpstring);
1122  }
1123  if (curDP->volume_left != -1)
1124  {
1125   sprintf (tmpstring, "%g", curDP->volume_left);
1126   gtk_entry_set_text (GTK_ENTRY (entry_Input_volume_left), tmpstring);
1127  }
1128  if (curDP->volume_right != -1)
1129  {
1130   sprintf (tmpstring, "%g", curDP->volume_right);
1131   gtk_entry_set_text (GTK_ENTRY (entry_Input_volume_right), tmpstring);
1132  }
1133 
1134  // Add the label, and show everything we've added to the dialog.
1135  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_dur);
1136  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
1137                     entry_Input_dur);
1138  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_starts);
1139  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
1140                     label_beatfreq);
1141  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
1142                     entry_Input_beatfreq);
1143  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
1144                     label_basefreq);
1145  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
1146                     entry_Input_basefreq);
1147  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
1148                     label_volume_left);
1149  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
1150                     entry_Input_volume_left);
1151  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
1152                     label_volume_right);
1153  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
1154                     entry_Input_volume_right);
1155 
1156  if (curDP->parent != NULL)
1157  {
1158   double times =
1159    curDP->x * (SG_TotalScheduleDuration / widget->allocation.width);
1160   sprintf (tmpstring, "\tStart time:\t%d min. \t%d sec.\t",
1161            ((int) (times + .5)) / 60, ((int) (times + .5)) % 60);
1162   GtkWidget *label_start = gtk_label_new (tmpstring);
1163 
1164   sprintf (tmpstring, "\tEnd time: \t%d min. \t%d sec.\t",
1165            ((int) (times + curDP->duration + .5)) / 60,
1166            ((int) (times + curDP->duration + .5)) % 60);
1167   GtkWidget *label_end = gtk_label_new (tmpstring);
1168 
1169   sprintf (tmpstring, "X:\t%g", curDP->x);
1170   GtkWidget *label_x = gtk_label_new (tmpstring);
1171 
1172   sprintf (tmpstring, "Y:\t%g", (widget->allocation.height - curDP->y));
1173   GtkWidget *label_y = gtk_label_new (tmpstring);
1174 
1175   sprintf (tmpstring, "SG_Voice ID# %d", ((SG_Voice *) curDP->parent)->ID);
1176   GtkWidget *label_voice = gtk_label_new (tmpstring);
1177 
1178   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_start);
1179   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_end);
1180   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_x);
1181   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_y);
1182   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_voice);
1183  }
1184  else
1185  {
1186   //do some chores to get some numbers:
1187   int dpcount = 0;
1188   int seldpcount = 0;
1189   int voicecount = 0;
1190   int selvoicecount = 0;
1191 
1192   SG_CountAllData (&voicecount, &selvoicecount, &dpcount, &seldpcount);
1193 
1194   /*
1195      gboolean newvflag;
1196      SG_Voice *curVoice = SG_FirstVoice;
1197      while (curVoice != NULL)
1198      {
1199      ++voicecount;
1200      newvflag = TRUE;
1201      SG_DataPoint *curDP = curVoice->FirstDataPoint;
1202      while (curDP != NULL)
1203      {
1204      ++dpcount;
1205      if (curDP->state == SG_SELECTED)
1206      {
1207      ++seldpcount;
1208      if (newvflag == TRUE)
1209      ++selvoicecount;
1210      newvflag = FALSE;
1211      }
1212      curDP = curDP->NextDataPoint;
1213      }
1214      curVoice = curVoice->NextVoice;
1215      }
1216    */
1217 
1218   if (seldpcount == 0)
1219   {
1220    gtk_widget_destroy (dialog);
1221    return FALSE;
1222   }
1223 
1224   sprintf (tmpstring, "Total Schedule Duration:\t%d min. \t%d sec.",
1225            ((int) (SG_TotalScheduleDuration + .5)) / 60,
1226            ((int) (SG_TotalScheduleDuration + .5)) % 60);
1227   GtkWidget *label_start = gtk_label_new (tmpstring);
1228 
1229   sprintf (tmpstring, "Total DataPoints: %d", dpcount);
1230   GtkWidget *label_end = gtk_label_new (tmpstring);
1231 
1232   sprintf (tmpstring, "Total Selected DataPoints: %d", seldpcount);
1233   GtkWidget *label_x = gtk_label_new (tmpstring);
1234 
1235   sprintf (tmpstring, "Total Voices: %d", voicecount);
1236   GtkWidget *label_y = gtk_label_new (tmpstring);
1237 
1238   sprintf (tmpstring, "\tTotal Voices with Selected DataPoints: %d\t",
1239            selvoicecount);
1240   GtkWidget *label_voice = gtk_label_new (tmpstring);
1241 
1242   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_start);
1243   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_end);
1244   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_x);
1245   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_y);
1246   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label_voice);
1247  }
1248 
1249  gtk_widget_show_all (dialog);
1250 
1251  //block until I get a response:
1252  gint result = gtk_dialog_run (GTK_DIALOG (dialog));
1253 
1254  switch (result)
1255  {
1256  case GTK_RESPONSE_ACCEPT:
1257   {
1258    double tmpd;
1259    const gchar *restr;
1260 
1261    restr = gtk_entry_get_text (GTK_ENTRY (entry_Input_dur));
1262    tmpd = (restr[0] != '\0') ? atof (restr) : -1.0;
1263    curDP->duration = tmpd;
1264 
1265    restr = gtk_entry_get_text (GTK_ENTRY (entry_Input_beatfreq));
1266    tmpd = (restr[0] != '\0') ? atof (restr) : -1.0;
1267    curDP->beatfreq = tmpd;
1268 
1269    restr = gtk_entry_get_text (GTK_ENTRY (entry_Input_volume_left));
1270    tmpd = (restr[0] != '\0') ? atof (restr) : -1.0;
1271    curDP->volume_left = tmpd;
1272    if (curDP->volume_left > 1.0)
1273     curDP->volume_left = 1.0;
1274 
1275    restr = gtk_entry_get_text (GTK_ENTRY (entry_Input_volume_right));
1276    tmpd = (restr[0] != '\0') ? atof (restr) : -1.0;
1277    curDP->volume_right = tmpd;
1278    if (curDP->volume_right > 1.0)
1279     curDP->volume_right = 1.0;
1280 
1281    restr = gtk_entry_get_text (GTK_ENTRY (entry_Input_basefreq));
1282    tmpd = (restr[0] != '\0') ? atof (restr) : -1.0;
1283    curDP->basefreq = tmpd;
1284    res = TRUE;
1285    SG_GraphHasChanged = SG_DataHasChanged = TRUE;       //added 20070109
1286    break;
1287   }
1288 
1289  default:
1290   res = FALSE;
1291   break;
1292  }
1293  gtk_widget_destroy (dialog);
1294  return res;
1295 }
1296 
1297 //////////////////////////////////////////
1298 //this inserts srcDP to the left of destDP.
SG_InsertLink(SG_DataPoint * srcDP,SG_DataPoint * destDP)1299 gboolean SG_InsertLink (SG_DataPoint * srcDP, SG_DataPoint * destDP)
1300 {       //swap two links:
1301  if (srcDP == NULL || destDP == NULL)
1302   return FALSE;
1303 
1304  SG_DataPoint *destprevDP = destDP->PrevDataPoint;
1305  SG_DataPoint *srcprevDP = srcDP->PrevDataPoint;
1306  SG_DataPoint *srcnextDP = srcDP->NextDataPoint;
1307 
1308  //     SG_DataPoint * destnextDP=destDP->NextDataPoint;
1309 
1310  //curDP is FirstDataPoint; there is nothing logical to swap-left with, so:
1311  if (destprevDP == NULL)
1312   return FALSE;
1313 
1314  destDP->PrevDataPoint = srcDP;
1315  destprevDP->NextDataPoint = srcDP;
1316  srcDP->NextDataPoint = destDP;
1317  srcDP->PrevDataPoint = destprevDP;
1318  srcprevDP->NextDataPoint = srcnextDP;
1319  //deal with special case where srcDP is LastDataPoint, in which case 2nd-to-last becomes last:
1320  if (srcnextDP != NULL)
1321   srcnextDP->PrevDataPoint = srcprevDP;
1322 
1323  return TRUE;
1324 }
1325 
1326 //////////////////////////////////////////
1327 //a20070724: Swaps links, but pays no attention to X values (that's your job)
1328 //returns false if it fails, I guess. This this was a bear
SG_SwapLinks(SG_DataPoint * curDP1,SG_DataPoint * curDP2)1329 gboolean SG_SwapLinks (SG_DataPoint * curDP1, SG_DataPoint * curDP2)
1330 {       //swap two links:
1331  if (curDP1 == NULL || curDP2 == NULL || curDP1 == curDP2)
1332  {
1333   return FALSE;
1334  }
1335  //first the easy part:
1336  SG_DataPoint *curDP1_prev = curDP1->PrevDataPoint;
1337  SG_DataPoint *curDP1_next = curDP1->NextDataPoint;
1338 
1339  curDP1->PrevDataPoint = curDP2->PrevDataPoint;
1340  curDP1->NextDataPoint = curDP2->NextDataPoint;
1341  curDP2->PrevDataPoint = curDP1_prev;
1342  curDP2->NextDataPoint = curDP1_next;
1343 
1344  //now the hard part (checking if what we did is vaid, and correcting if not):
1345  int i;
1346  SG_DataPoint *curDP = curDP1;
1347  SG_DataPoint *otherDP = curDP2;
1348 
1349  for (i = 0; i < 2; i++)
1350  {
1351   if (i != 0)
1352   {
1353    curDP = curDP2;
1354    otherDP = curDP1;
1355   }
1356 
1357   //first deal with possibility that DPs were adjacent:
1358   if (curDP->NextDataPoint == curDP)
1359   {
1360    curDP->NextDataPoint = otherDP;
1361    otherDP->PrevDataPoint = curDP;
1362   }
1363 
1364   if (curDP->PrevDataPoint == curDP)
1365   {
1366    curDP->PrevDataPoint = otherDP;
1367    otherDP->NextDataPoint = curDP;
1368   }
1369 
1370   //now deal with logical adjacent DPs:
1371   //see if there is a new FirstDataPoint:
1372   if (curDP->PrevDataPoint == NULL)
1373   {
1374    ((SG_Voice *) curDP->parent)->FirstDataPoint = curDP;
1375   }
1376   else
1377   {
1378    curDP->PrevDataPoint->NextDataPoint = curDP;
1379   }
1380   //see if there is a new last DataPoint:
1381   if (curDP->NextDataPoint == NULL)
1382   {
1383   }
1384   else
1385   {
1386    curDP->NextDataPoint->PrevDataPoint = curDP;
1387   }
1388  }
1389  return TRUE;
1390 }
1391 
1392 //////////////////////////////////////////
1393 //this swaps link you pass to it with neighbor to it's "left"
1394 //(so if you want to want to swap something with it's
1395 //neighbor to the right, pass the neighbor instead)
1396 //NOTE: this works well for the visual needs of the graph, but
1397 //is unsuitable for really determining final values for data points
1398 //because it can't compensate dur/beatfreq values losing their
1399 //original reference to neighbors. Use "InsertDatapoint"
1400 //after mouse-click is released to get real final values from
1401 //the x,y
SG_SwapLinks_left(SG_DataPoint * curDP)1402 gboolean SG_SwapLinks_left (SG_DataPoint * curDP)
1403 {       //swap two links:
1404  if (curDP == NULL)
1405   return FALSE;
1406  SG_DataPoint *prevDP = curDP->PrevDataPoint;
1407  SG_DataPoint *nextDP = curDP->NextDataPoint;
1408 
1409  if (prevDP == NULL)
1410  {      //curDP is FirstDataPoint; there is nothing logical to swap-left with, so:
1411   return FALSE;
1412  }
1413  SG_DataPoint *prevprevDP = curDP->PrevDataPoint->PrevDataPoint;
1414 
1415  if (prevprevDP == NULL)
1416  {
1417   return FALSE;
1418  }
1419  curDP->NextDataPoint = prevDP;
1420  curDP->PrevDataPoint = prevprevDP;
1421  prevDP->NextDataPoint = nextDP;
1422  prevDP->PrevDataPoint = curDP;
1423  //deal with special case where curDP is LastDataPoint, in which case 2nd-to-last becomes last:
1424  if (nextDP != NULL)
1425  {
1426   nextDP->PrevDataPoint = prevDP;
1427  }
1428  prevprevDP->NextDataPoint = curDP;
1429  return TRUE;
1430 }
1431 
1432 /*
1433    //////////////////////////////////////////
1434    //THIS IS ONLY HERE AS AN EXAMPLE OF THE INVERSE
1435    //OF THE ABOVE SWAP FUNCTION:
1436    //this swaps link you pass to it with neighbor to it's "right"
1437    //(so if you want to want to swap something with it's
1438    //neighbor to the left, you could pass the neighbor instead)
1439    gboolean SG_SwapLinks_right(SG_DataPoint * curDP)
1440    {//swap two links:
1441    if (curDP==NULL) return FALSE;
1442    SG_DataPoint * prevDP=curDP->PrevDataPoint;
1443    SG_DataPoint * nextDP=curDP->NextDataPoint;
1444    if (nextDP==NULL || prevDP==NULL) return FALSE;
1445    SG_DataPoint * nextnextDP=curDP->NextDataPoint->NextDataPoint;
1446    if (nextnextDP==NULL) return FALSE;
1447    curDP->NextDataPoint=nextnextDP;
1448    curDP->PrevDataPoint=nextDP;
1449    nextDP->PrevDataPoint=prevDP;
1450    nextDP->NextDataPoint=curDP;
1451    prevDP->NextDataPoint=nextDP;
1452    nextnextDP->PrevDataPoint=curDP;
1453    return TRUE;
1454    }
1455  */
1456 
1457 //TODO: Figure out why I must convert the values to ints for "is_hint"
1458 /////////////////////////////////////////////////////
SG_motion_notify_event(GtkWidget * widget,GdkEventMotion * event)1459 gboolean SG_motion_notify_event (GtkWidget * widget, GdkEventMotion * event)
1460 {
1461  int x,
1462   y;
1463  GdkModifierType state;
1464 
1465  if (event->is_hint)
1466   gdk_window_get_pointer (event->window, &x, &y, &state);
1467  else
1468  {
1469   x = (int) (event->x + .5);
1470   y = (int) (event->y + .5);
1471   state = (GdkModifierType) event->state;
1472  }
1473 
1474  if ((state & GDK_BUTTON1_MASK) == 0 || SG_pixmap == NULL)
1475   return TRUE;
1476 
1477  //=============
1478  //see whether to do bounding box or drag point(s):
1479  //first, is is a bounding box?:
1480  if (SG_SelectionBox.status != 0)
1481  {
1482   //first draw graph (to erase last bounding box draw):
1483   SG_DrawGraph (widget);
1484 
1485   //20100727: if main_MagneticPointerflag == TRUE, do
1486   //"Magnetic Pointer", meaning  move selected points on the pointer's
1487   //Y axis to the pointer:
1488   //  if (event->state & GDK_MOD1_MASK)
1489   //  if (event->state & GDK_LOCK_MASK)
1490   if (TRUE == SG_MagneticPointerflag)
1491   {
1492    //   SG_DBGOUT ("CAPS LOCK ON");
1493    if (0 != SG_MagneticPointer (widget, x, y))
1494    {
1495     SG_DrawGraph (widget);
1496    }
1497    return TRUE;
1498   }
1499 
1500   //now draw a box over it:
1501   GdkColor color;
1502 
1503   color.red = color.green = color.blue = 0;
1504   gdk_gc_set_rgb_fg_color (SG_gc, &color);
1505   gdk_gc_set_line_attributes (SG_gc, 1, GDK_LINE_ON_OFF_DASH,
1506                               (GdkCapStyle) 0, (GdkJoinStyle) 0);
1507   int startX = SG_SelectionBox.startX;
1508   int startY = SG_SelectionBox.startY;
1509   int endX = x - startX;
1510   int endY = y - startY;
1511 
1512   if (endX < 0)
1513   {
1514    startX = x;
1515    endX = SG_SelectionBox.startX - x;
1516   }
1517   if (endY < 0)
1518   {
1519    startY = y;
1520    endY = SG_SelectionBox.startY - y;
1521   }
1522   gdk_draw_rectangle (SG_pixmap, SG_gc, FALSE, startX, startY, endX, endY);
1523   if ((event->state & GDK_CONTROL_MASK) == 0)
1524   {
1525    SG_SelectDataPoints (startX, startY, startX + endX, startY + endY, TRUE);
1526   }
1527   else
1528   {
1529    SG_SelectDataPoints (startX, startY, startX + endX, startY + endY, FALSE);
1530   }
1531   //all done, put it out there:
1532   //NOTE: I don't need to do this, for some reason:
1533   //  gtk_widget_queue_draw_area (widget,startX,startY,endX, endY);
1534   //  gtk_widget_queue_draw_area (widget,0,0,widget->allocation.width, widget->allocation.height);
1535   gdk_gc_set_line_attributes (SG_gc, 1, GDK_LINE_SOLID, (GdkCapStyle) 0,
1536                               (GdkJoinStyle) 0);
1537  }
1538  //=============
1539  //Not bounding selection box, so deal with possibility that user was trying to move some points:
1540  //the user must always have one live SG_CurrentDataPoint to move any points, and it must
1541  //be selected, because it's movement will be the reference for all other other selected points to move
1542  else if (SG_CurrentDataPoint != NULL &&
1543           SG_CurrentDataPoint->state == SG_SELECTED)
1544  {
1545   SG_ProgressIndicatorFlag = FALSE;
1546   //Determine how far X and Y have moved, and move all selected points accordingly:
1547   SG_MoveSelectedDataPoints (widget, x - SG_CurrentDataPoint->x,
1548                              y - SG_CurrentDataPoint->y);
1549 
1550   //Finally, put the graphical result out there (save translating in to dur/beatfreq for mouse release; way too slow):
1551   SG_DrawGraph (widget);
1552  }
1553  return TRUE;
1554 }
1555 
1556 /*
1557    /////////////////////////////////////////////////////
1558    //THIS ONE ONLY WORKS DISREGARDING SELECTED/UNSELECTED STATUS
1559    //give the existing datapoint and the desired new x, this will
1560    //move it to the proper place on graph AND in the linked-list.
1561    //returns FALSE if it something illegal is being asked-for:
1562    gboolean  SG_MoveDataPoint_simple(GtkWidget *widget, SG_DataPoint * curDP, double newx, double newy)
1563    {
1564    //first do y:
1565    if (newy > widget->allocation.height)  newy = widget->allocation.height;
1566    curDP->y=newy;
1567 
1568    //now do x:
1569    if (curDP!=SG_FirstDataPoint)
1570    {
1571    //first limit-check x axis:
1572    if (newx >= 0 && newx < widget->allocation.width) curDP->x=newx;
1573    else return FALSE;
1574 
1575    //now see if point moved past a neighbor:
1576    do {
1577    if (newx < curDP->PrevDataPoint->x)  SG_SwapLinks(curDP);
1578    //Must treat the last real SG_DataPoint differently, for two reasons:
1579    //1) it can't be pulled further than right-edge of graph
1580    //2) it's NextDataPoint equals NULL, so can't swap-right
1581    else  if (curDP->NextDataPoint==NULL) break;//last DP can't swap with anything to its right
1582    if (newx > curDP->NextDataPoint->x)   SG_SwapLinks(curDP->NextDataPoint);
1583    } while (curDP->NextDataPoint!=NULL &&
1584    (newx < curDP->PrevDataPoint->x    ||
1585    newx > curDP->NextDataPoint->x));
1586    } else return FALSE;
1587    return TRUE;
1588    }
1589  */
1590 
1591 /*
1592    /////////////////////////////////////////////////////
1593    //THIS ONE TAKES IN TO CONSIDERATION SELECTED/UNSELECTED STATUS
1594    //give the existing datapoint and the desired new x, this will
1595    //move it to the proper place on graph AND in the linked-list.
1596    //NOTE: this function quadrupled in complexity once it had to
1597    //differentiate selected and unselected points and allow user to
1598    //expand length of schedule by dragging past right edge
1599    void  SG_MoveDataPoint(GtkWidget *widget, SG_DataPoint * curDP, double newx, double newy)
1600    {
1601    //first do y:
1602    if (newy > widget->allocation.height) newy = widget->allocation.height;//this means all Hz below 0 become 0
1603 
1604    curDP->y=newy;
1605 
1606    //now do x:
1607    //first, make sure it isn't the first data point, since that can't move sideways:
1608    if (curDP->PrevDataPoint==NULL) return;
1609 
1610    //first limit-check x axis:
1611    // if (newx < 0 || newx >= widget->allocation.width) return FALSE;
1612    if (newx < 0) newx=0;
1613    //else if (newx >= widget->allocation.width) newx=widget->allocation.width; //TODO: eventually, I should allow user to pull past final point
1614 
1615    //now get difference between new point and old, then assign new value:
1616    double diff=curDP->x - newx;
1617    curDP->x=newx;
1618    // fprintf(stderr,"diff: %g\n",diff);
1619 
1620    //Now see if the new X means it passed a neighbor:
1621    //Positive diff means test if point moved past an unselected neighbor to the left:
1622    SG_DataPoint * tempDP;
1623    gboolean swapflag;
1624    if (diff>0)  do {
1625    //First try to find an unselected neighbor:
1626    tempDP=curDP;
1627    swapflag=FALSE;
1628    do {
1629    tempDP=tempDP->PrevDataPoint;
1630    if (tempDP->PrevDataPoint==NULL) return;//first DP, therefore no unselected neighbor to switch with
1631    }while (tempDP->state==SG_SELECTED);
1632    if (newx < tempDP->x) {    SG_InsertLink(curDP, tempDP); swapflag=TRUE;    }
1633    //   if (newx < tempDP->x) {    SG_SwapLinks(curDP); swapflag=TRUE;    }
1634    } while (swapflag==TRUE);
1635 
1636    //Negative diff means test if point moved past an unselected neighbor to the right:
1637    else  if (diff<0 && curDP->NextDataPoint!=NULL)  do  { //remember, last DP can't swap with anything to its right)
1638    //First try to find an unselected neighbor:
1639    tempDP=curDP;
1640    swapflag=FALSE;
1641    do {
1642    tempDP=tempDP->NextDataPoint;
1643    if (tempDP==NULL) return;//no unselected neighbor to switch with
1644    }while (tempDP->state==SG_SELECTED);
1645    if (newx > tempDP->x) { SG_InsertLink(tempDP,curDP); swapflag=TRUE; }
1646    //   if (newx > tempDP->x) { SG_SwapLinks(curDP->NextDataPoint); swapflag=TRUE; }
1647    } while (swapflag==TRUE);
1648    else {
1649    //=====
1650    //here comes the most complicated set of possibilities: dealing with the last DP:
1651    //First the easy one: if I am merely moving the last data point within the current graph size,
1652    //there is nothing to do:
1653    if (curDP->x <= widget->allocation.width)  return;
1654    //But if I got here, it means that the user is pulling
1655    //last DP beyond size of graph, which turns out to be quite a complex
1656    //situation:
1657 
1658    //Problem: x and duration get out of sync. I can't know this crosser's original starting point before move,
1659    //which leaves one unknown unselected DP with an invalid duration but a valid x. Thus I need to translate x
1660    //in to dur for ALL DPs before I can increment SG_TotalScheduleDuration (the latter which is what actually
1661    //causes graph to expand), then finally translate all the DP durations back to x's (to correctly draw graph):
1662    //First, x to duration conversion:
1663    double conversion_factor=SG_TotalScheduleDuration/widget->allocation.width;
1664    SG_Voice * curVoice=SG_FirstVoice;
1665    while (curVoice !=NULL) {
1666    tempDP=curVoice->FirstDataPoint;
1667    while (tempDP->NextDataPoint != NULL) {
1668    tempDP->duration = (tempDP->NextDataPoint->x - tempDP->x) * conversion_factor;
1669    tempDP=tempDP->NextDataPoint;
1670    }
1671    //do last datapoint:
1672    tempDP->duration = 0;//zero because it reached the edge of the graph
1673    //Now increment SG_TotalScheduleDuration:
1674    SG_TotalScheduleDuration+=(curDP->x - widget->allocation.width) *conversion_factor;
1675    curVoice=curVoice->NextVoice;
1676    }
1677 
1678    //Now, convert them back (dur to x conversion):
1679    //NOTE: New problem here, since multiple points pulled in the same past-edge drag
1680    //(i.e., no button-up) will now be "moved further" for the same x-move than the first crosser,
1681    //because x scale has changed (and subsequent same-move crossers would normally just get the
1682    //original, un-rescaled x move). This apparently ONLY matters for multiple voices, since it
1683    //seems to only affect relationship between last and second-to-last DPs (which gets desynced between voices).
1684    //The most independent solution is just to have calling function check to see if TotalDuration has changed
1685    //in-between DP moves, then rescale the x move accordingly.
1686    conversion_factor=widget->allocation.width/SG_TotalScheduleDuration;
1687    curVoice=SG_FirstVoice;
1688    while (curVoice !=NULL) {
1689    double currentx=0;
1690    tempDP=curVoice->FirstDataPoint;
1691    //this is necessary because each DP's duration is now out-of-correct proportion with it's X
1692    //Durations are correct, while X has become arbitrary -- so here I make X agree with duration:
1693    do{
1694    tempDP->x=currentx;
1695    currentx+=tempDP->duration*conversion_factor;
1696    tempDP=tempDP->NextDataPoint;
1697    }while (tempDP != NULL);
1698    //now get new limits (not necessary because I incremented SG_TotalScheduleDuration directly):
1699    //    SG_GetScheduleLimits();
1700    curVoice=curVoice->NextVoice;
1701    }
1702 
1703    }
1704    return;
1705    }
1706  */
1707 
1708 /////////////////////////////////////////////////////
1709 //THIS ONE TAKES IN TO CONSIDERATION SELECTED/UNSELECTED STATUS
1710 //give the existing datapoint and the desired new x, this will
1711 //move it to the proper place on graph AND in the linked-list.
1712 //NOTE: this function quadrupled in complexity once it had to
1713 //differentiate selected and unselected points and allow user to
1714 //expand length of schedule by dragging past right edge
SG_MoveDataPoint(GtkWidget * widget,SG_DataPoint * curDP,double newx,double newy)1715 void SG_MoveDataPoint (GtkWidget * widget, SG_DataPoint * curDP, double newx,
1716                        double newy)
1717 {
1718  //first do y:
1719  if (newy > widget->allocation.height)
1720   newy = widget->allocation.height;     //this means all Hz below 0 become 0
1721 
1722  curDP->y = newy;
1723 
1724  //now do x:
1725  //first, make sure it isn't the first data point, since that can't move sideways:
1726  if (curDP->PrevDataPoint == NULL)
1727   return;
1728 
1729  //first limit-check x axis:
1730  // if (newx < 0 || newx >= widget->allocation.width) return FALSE;
1731  if (newx < 0)
1732   newx = 0;
1733  //else if (newx >= widget->allocation.width) newx=widget->allocation.width; //TODO: eventually, I should allow user to pull past final point
1734 
1735  //now get difference between new point and old, then assign new value:
1736  double diff = curDP->x - newx;
1737 
1738  curDP->x = newx;
1739  // fprintf(stderr,"diff: %g\n",diff);
1740 
1741  //Now see if the new X means it passed a neighbor:
1742  //Positive diff means test if point moved past an unselected neighbor to the left:
1743  SG_DataPoint *tempDP;
1744  gboolean swapflag;
1745 
1746  if (diff > 0)
1747   do
1748   {
1749    //First try to find an unselected neighbor:
1750    tempDP = curDP;
1751    swapflag = FALSE;
1752    do
1753    {
1754     tempDP = tempDP->PrevDataPoint;
1755     if (tempDP->PrevDataPoint == NULL)
1756      return;    //first DP, therefore no unselected neighbor to switch with
1757    }
1758    while (tempDP->state == SG_SELECTED);
1759    if (newx < tempDP->x)
1760    {
1761     SG_InsertLink (curDP, tempDP);
1762     swapflag = TRUE;
1763    }
1764    //   if (newx < tempDP->x) {    SG_SwapLinks(curDP); swapflag=TRUE;    }
1765   }
1766   while (swapflag == TRUE);
1767 
1768  //Negative diff means test if point moved past an unselected neighbor to the right:
1769  else if (diff < 0 && curDP->NextDataPoint != NULL)
1770   do
1771   {     //remember, last DP can't swap with anything to its right)
1772    //First try to find an unselected neighbor:
1773    tempDP = curDP;
1774    swapflag = FALSE;
1775    do
1776    {
1777     tempDP = tempDP->NextDataPoint;
1778     if (tempDP == NULL)
1779      return;    //no unselected neighbor to switch with
1780    }
1781    while (tempDP->state == SG_SELECTED);
1782    if (newx > tempDP->x)
1783    {
1784     SG_InsertLink (tempDP, curDP);
1785     swapflag = TRUE;
1786    }
1787    //   if (newx > tempDP->x) { SG_SwapLinks(curDP->NextDataPoint); swapflag=TRUE; }
1788   }
1789   while (swapflag == TRUE);
1790  else
1791  {
1792   //=====
1793   //here comes the most complicated set of possibilities: dealing with the last DP:
1794   //First the easy one: if I am merely moving the last data point within the current graph size,
1795   //there is nothing to do:
1796   if (curDP->x <= widget->allocation.width)
1797    return;
1798   //But if I got here, it means that the user is pulling
1799   //last DP beyond size of graph, which turns out to be quite a complex
1800   //situation:
1801 
1802   //Problem: x and duration get out of sync. I can't know this crosser's original starting point before move,
1803   //which leaves one unknown unselected DP with an invalid duration but a valid x. But I need valid durations
1804   //in order to correctly rescale graph. Thus I need to translate x in to dur for ALL DPs before I can increment
1805   //SG_TotalScheduleDuration (the latter which is what actually causes graph to expand), then finally
1806   //translate all the DP durations back to x's (to correctly draw graph):
1807   //First, x to duration conversion:
1808   double conversion_factor =
1809    SG_TotalScheduleDuration / widget->allocation.width;
1810   SG_Voice *curVoice = SG_FirstVoice;
1811 
1812   while (curVoice != NULL)
1813   {
1814    tempDP = curVoice->FirstDataPoint;
1815    while (tempDP->NextDataPoint != NULL)
1816    {
1817     tempDP->duration =
1818      (tempDP->NextDataPoint->x - tempDP->x) * conversion_factor;
1819     tempDP = tempDP->NextDataPoint;
1820    }
1821    //do last datapoint:
1822    tempDP->duration = 0;        //zero because it reached the edge of the graph
1823    //Now increment SG_TotalScheduleDuration:
1824    SG_TotalScheduleDuration +=
1825     (curDP->x - widget->allocation.width) * conversion_factor;
1826    curVoice = curVoice->NextVoice;
1827   }
1828 
1829   //Now, convert them back (dur to x conversion):
1830   //NOTE: New problem here, since multiple points pulled in the same past-edge drag
1831   //(i.e., no button-up) will now be "moved further" for the same x-move than the first crosser,
1832   //because x scale has changed (and subsequent same-move crossers would normally just get the
1833   //original, un-rescaled x move). This apparently ONLY matters for multiple voices, since it
1834   //seems to only affect relationship between last and second-to-last DPs (which gets desynced between voices).
1835   //The most independent solution is just to have calling function check to see if TotalDuration has changed
1836   //in-between DP moves, then rescale the x move accordingly.
1837   conversion_factor = widget->allocation.width / SG_TotalScheduleDuration;
1838   curVoice = SG_FirstVoice;
1839   while (curVoice != NULL)
1840   {
1841    double currentx = 0;
1842 
1843    tempDP = curVoice->FirstDataPoint;
1844    //this is necessary because each DP's duration is now out-of-correct proportion with it's X
1845    //Durations are correct, while X has become arbitrary -- so here I make X agree with duration:
1846    do
1847    {
1848     tempDP->x = currentx;
1849     currentx += tempDP->duration * conversion_factor;
1850     tempDP = tempDP->NextDataPoint;
1851    }
1852    while (tempDP != NULL);
1853    //now get new limits (not necessary because I incremented SG_TotalScheduleDuration directly):
1854    //    SG_GetScheduleLimits();
1855    curVoice = curVoice->NextVoice;
1856   }
1857 
1858  }
1859  return;
1860 }
1861 
1862 /////////////////////////////////////////////////////
1863 //recalculates durations for all datapoints according to their x position on the graph
SG_ConvertXToDuration_AllPoints(GtkWidget * widget)1864 void SG_ConvertXToDuration_AllPoints (GtkWidget * widget)
1865 {
1866  double secs_per_pixel = SG_TotalScheduleDuration / widget->allocation.width;
1867  SG_Voice *curVoice = SG_FirstVoice;
1868 
1869  while (curVoice != NULL)
1870  {
1871 
1872   SG_DataPoint *curDP = curVoice->FirstDataPoint;
1873 
1874   while (curDP->NextDataPoint != NULL)
1875   {
1876    curDP->duration = (curDP->NextDataPoint->x - curDP->x) * secs_per_pixel;
1877 
1878    // SG_DBGOUT_INT("Dur:",(int)(curDP->duration+.5));
1879 
1880    //this must be in every linked list loop:
1881    curDP = curDP->NextDataPoint;
1882   };
1883 
1884   //do last datapoint:
1885   curDP->duration = SG_TotalScheduleDuration - ((curDP->x) * secs_per_pixel);
1886   // SG_DBGOUT_INT("LastDur:",(int)(curDP->duration+.5));
1887   curVoice = curVoice->NextVoice;
1888  }
1889 }
1890 
1891 /*
1892    /////////////////////////////////////////////////////
1893    //NOTE: should change name of this function; it actually requires user
1894    //to know duration of a point if it is going to be the new last point.
1895    //This translates the X of an existing datapoint in to the correct
1896    //duration in relationship to it's neighbors:
1897    //at some point before calling this, SG_GetScheduleLimits()
1898    //should have been called once to correctly set two key vars:
1899    //SG_TotalScheduleDuration and
1900    //SG_MaxScheduleBeatfrequency
1901    void SG_ConvertXYToDurHZ(GtkWidget *widget, SG_DataPoint * curDP) {
1902    //First, convert Y to Hz:
1903    //NOTE: might want to figure out which point had the highest value again before doing this (lastDataPoint=SG_GetScheduleLimits())
1904    //curDP->beatfreq = (widget->allocation.height-curDP->y)*(SG_MaxScheduleBeatfrequency/widget->allocation.height);
1905    curDP->beatfreq = ((widget->allocation.height - curDP->y) * SG_MaxScheduleBeatfrequency) / widget->allocation.height;
1906 
1907    //Now convert X to Duration:
1908    //The hard part: figuring out new time relationships between neighboring pixels:
1909    //NOTE: rounding errors and pixel resolution have made getting this right a bit of a nightmare,
1910    //propagating/summing small errors along total duration...
1911    if (curDP->PrevDataPoint == NULL)
1912    return ;
1913    //NEW WAY (20060409):
1914    //Benefit: this one doesn't introduce NEARLY as much rounding-error/x-rez  noise
1915    double oldprevdur = curDP->PrevDataPoint->duration;
1916    curDP->PrevDataPoint->duration = (curDP->x - curDP->PrevDataPoint->x) * (SG_TotalScheduleDuration / (double)widget->allocation.width);
1917    curDP->duration = oldprevdur - curDP->PrevDataPoint->duration;
1918    }
1919  */
1920 
1921 // SG_Voice * curVoice=SG_FirstVoice;
1922 // while (curVoice !=NULL) {   curVoice=curVoice->NextVoice; }
1923 
1924 /////////////////////////////////////////////////////
1925 //this creates and adds a new SG_DataPoint to the existing linked list based on X Y mouse event,
1926 //assigning a duration relative to neighbor-proximity or (in the case of a point placed after
1927 //formerly last point) distance from end as related to Total Duration (if x was within graph width).
1928 //"Y" values will be set to merely whatever a left-neighboring DP has to offer, if one is available - therefore
1929 //it is essential for user to call SG_ConvertYToData_SingleDP() or similar after this if
1930 //a graph-view context or otherwise is used for value determination (not the case for Paste, for example).
1931 //IMPORTANT: the duration it returns will be assigned 0 if datapoint x was beyond graph width. This
1932 //is why the calling function must be responsible for checking if an x sent here is beyond the graph
1933 //width, and thus will need to give the DP returned a valid duration (and then probably then do a
1934 //convertDataToXY() to rescale graph to contain last-point's duration).
1935 //NEW 20060412: now user can create data points with same x ("0 duration" points). Became
1936 //a moot point as dragging points past each other created 0 duration points.
1937 //NOTE: 20070731: be aware that this doesn't discrimintate between visible/invisible parent voices.
1938 //I don't think I should make it aware, since so many things call it
SG_InsertNewDataPointXY(GtkWidget * widget,SG_Voice * voice,double x,double y)1939 SG_DataPoint *SG_InsertNewDataPointXY (GtkWidget * widget, SG_Voice * voice,
1940                                        double x, double y)
1941 {
1942  SG_DataPoint *newDP = NULL;
1943  SG_DataPoint *curDP = voice->FirstDataPoint;
1944 
1945  //a20070919:
1946  SG_DataPoint *nextDP = NULL;
1947  double diff;
1948 
1949  if (curDP == NULL)
1950  {
1951   return NULL;
1952  }
1953 
1954  //now see if DP is in-between two valid DPs:
1955  while (curDP->NextDataPoint != NULL)
1956  {
1957   if (curDP->x <= x && x <= curDP->NextDataPoint->x)
1958   {
1959    break;
1960   }
1961   curDP = curDP->NextDataPoint;
1962  }
1963 
1964  //if curDP->NextDataPoint == NULL, I DP was assumably to the right of the last DP.
1965  //Which gives me two possibilities: either the new DP's x is within graph
1966  //width (easy), or bigger than graph width (unsolvable)
1967 
1968  //allot the memory for the new point:
1969  newDP = (SG_DataPoint *) malloc (sizeof (SG_DataPoint));
1970 
1971  if (newDP == NULL)
1972  {
1973   return NULL;
1974  }
1975 
1976  //first make all neighboring DP's link up right:
1977  if (curDP->NextDataPoint == NULL)
1978  {      //DP had no neighbor to right, so make it last DP:
1979   newDP->NextDataPoint = NULL;
1980   //a20070919: needed to interpolate new DP's Y values:
1981   nextDP = voice->FirstDataPoint;;
1982   diff = widget->allocation.width - curDP->x;
1983  }
1984  else
1985  {      //DP was between two neighbors
1986   newDP->NextDataPoint = curDP->NextDataPoint;
1987   newDP->NextDataPoint->PrevDataPoint = newDP;
1988   //a20070919: needed to interpolate new DP's Y values:
1989   nextDP = curDP->NextDataPoint;
1990   diff = (nextDP->x - curDP->x);
1991  }
1992 
1993  //both conditions need this -- just don't change the order in which Linked List
1994  //stuff gets assigned, makes for terribly confusing bugs:
1995  newDP->PrevDataPoint = curDP;
1996  curDP->NextDataPoint = newDP;
1997 
1998  //Now fill newDP's data. Because it is new, I can reference it to it's neighbor's x
1999  //without introducing any (or at least minimal) resolution/rounding errors:
2000  newDP->x = x;
2001  newDP->y = y;
2002  newDP->parent = voice;
2003  newDP->state = SG_SELECTED;
2004 
2005  //a20070919: needed to interpolate new DP's Y values:
2006  //assign Y data according to neighboring DPs:
2007  //first figure how close to first DP this is:
2008  double fac = 0;
2009 
2010  if (0 <= diff) //should never be negative, right?
2011  {
2012   fac = (x - curDP->x) / diff;
2013  }
2014  newDP->beatfreq =
2015   (fac * (nextDP->beatfreq - curDP->beatfreq)) + curDP->beatfreq;
2016  newDP->basefreq =
2017   (fac * (nextDP->basefreq - curDP->basefreq)) + curDP->basefreq;
2018  newDP->volume_left =
2019   (fac * (nextDP->volume_left - curDP->volume_left)) + curDP->volume_left;
2020  newDP->volume_right =
2021   (fac * (nextDP->volume_right - curDP->volume_right)) + curDP->volume_right;
2022 
2023  //Determine duration for the new point from it's X:
2024  double oldprevdur = newDP->PrevDataPoint->duration;
2025 
2026  newDP->PrevDataPoint->duration = (newDP->x - newDP->PrevDataPoint->x) *
2027   (SG_TotalScheduleDuration / (double) widget->allocation.width);
2028  newDP->duration = oldprevdur - newDP->PrevDataPoint->duration;
2029 
2030  //now do the remaining stuff that is different for the two conditions (between DPs, or last DP):
2031  if (curDP->NextDataPoint == NULL)
2032  {
2033   //this next "if/else" is a bit disconcerting in one way: it says that the new DP will be assigned a zero-duration
2034   //if it is larger than graph width. This means it is up to the calling function to recognize that the
2035   //x went past, and then give it a more appropriate duration (if necessary). Importantly, if the calling
2036   //function is adding multiple points to the end, it only needs to assign a duration and convert it for the very
2037   //last point added, since the previously added  "last" ones (initially given a 0 duration) get valid durations
2038   //once they get relegated to 2nd-to-last:
2039   if (x > widget->allocation.width)
2040   {     //not solveable, since X is outside graph bounds:
2041    newDP->duration = 0; //if a valid duration is desired, calling function needs to recognize point went past right edge, then manually set duration
2042   }
2043  }
2044 
2045  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
2046  return newDP;
2047 }
2048 
2049 /*
2050    //20060714 SAVING THIS UNTIL I AM SURE THE REPLACEMENT ABOVE ACTUALLY WORKS
2051    /////////////////////////////////////////////////////
2052    //this creates and adds a new SG_DataPoint to the existing linked list based on X Y mouse event,
2053    //assigning a duration relative to neighbor-proximity or (in the case of a point placed after
2054    //formerly last point) distance from end as related to Total Duration (if x was within graph width).
2055    //"Y" values are merely whatever a left-neighboring DP has to offer, if one is available - therefore
2056    //it is essential for user to call SG_ConvertYToData_SelectedPoints() or similar after this if
2057    //a graph-view context or otherwise is being implemented.
2058    //IMPORTANT: the duration it returns will be assigned 0 if datapoint x was beyond graph width. This
2059    //is why the calling function must be responsible for checking if an x sent here is beyond the graph
2060    //width, and thus will need to give the DP returned a valid duration (and then probably then do a
2061    //convertDurHzToXY() to rescale graph to contain last-point's duration).
2062    //NEW 20060412: now user can create data points with same x ("0 duration" points). Became
2063    //a moot point as dragging points past each other created 0 duration points.
2064    SG_DataPoint * SG_InsertNewDataPointXY(GtkWidget *widget, SG_Voice * voice, double x, double y) {
2065    SG_DataPoint * newDP = NULL;
2066    SG_DataPoint * curDP = voice->FirstDataPoint;
2067    while (curDP->NextDataPoint != NULL) {
2068    if (curDP->x <= x && x <= curDP->NextDataPoint->x) {
2069    //allot the memory for the new point:
2070    newDP = (SG_DataPoint *)malloc(sizeof(SG_DataPoint));
2071 
2072    //insert it between the earlier and later points:
2073    newDP->NextDataPoint = curDP->NextDataPoint;
2074    newDP->NextDataPoint->PrevDataPoint = newDP;
2075    newDP->PrevDataPoint = curDP;
2076    curDP->NextDataPoint = newDP;
2077 
2078    //fill it's data. Because it is new, I can reference it to it's neighbor's x
2079    //without introducing any (or at least minimal) resolution/rounding errors:
2080    newDP->x = x;
2081    newDP->y = y;
2082    newDP->parent = voice;
2083    newDP->state = SG_SELECTED;
2084 
2085    //assign a Y data according to neighboring previous DP, (known in this function as "curDP"):
2086    newDP->beatfreq = curDP->beatfreq;
2087    newDP->basefreq = curDP->basefreq;
2088    newDP->volume_left = curDP->volume_left;
2089    newDP->volume_right = curDP->volume_right;
2090 
2091    //now for the hard one, X data (duration):
2092    double oldprevdur = newDP->PrevDataPoint->duration;
2093    newDP->PrevDataPoint->duration = (newDP->x - newDP->PrevDataPoint->x) *
2094    (SG_TotalScheduleDuration / (double)widget->allocation.width);
2095    newDP->duration = oldprevdur - newDP->PrevDataPoint->duration;
2096 
2097    //found the point's position and filled all its data: all done:
2098    return newDP;
2099    }
2100    //putc('n',stderr);
2101    curDP = curDP->NextDataPoint;
2102    }
2103    //if it got here, the new DP's x was greater than the formerly-last DP's x (second-to-last graphpoint).
2104    //So, need to deal with two possibilities: whether new DP's x is within graph width (easy), or bigger than graph
2105    // width (hard).
2106    //  if (curDP->x <= x && x <= widget->allocation.width) //second conditional is not needed?
2107    if (curDP->x <= x) {
2108    //allot the memory for the new point:
2109    newDP = (SG_DataPoint *) malloc(sizeof(SG_DataPoint));
2110 
2111    //put it after the last real datapoint and link it up to the first:
2112    newDP->NextDataPoint = NULL; //it is the real "last one", so NULL NextDataPoint
2113    curDP->NextDataPoint = newDP;
2114    newDP->PrevDataPoint = curDP;
2115 
2116    //fill it's data:
2117    newDP->x = x;
2118    newDP->y = y;
2119    newDP->parent = voice;
2120    newDP->state = SG_SELECTED;
2121 
2122    //assign a Y data according to neighboring previous DP, (known in this function as "curDP"):
2123    newDP->beatfreq = curDP->beatfreq;
2124    newDP->basefreq = curDP->basefreq;
2125    newDP->volume_left = curDP->volume_left;
2126    newDP->volume_right = curDP->volume_right;
2127 
2128    //now for the hard one, duration:
2129    double oldprevdur = newDP->PrevDataPoint->duration;
2130    newDP->PrevDataPoint->duration = (newDP->x - newDP->PrevDataPoint->x) *
2131    (SG_TotalScheduleDuration / (double)widget->allocation.width);
2132    //this next "if/else" is a bit disconcerting in one way: it says that the new DP will be assigned a zero-duration
2133    //if it is larger than graph width. This means it is up to the calling function to recognize that the
2134    //x went past, and then give it a more appropriate duration (if necessary). Importantly, if the calling
2135    //function is adding multiple points to the end, it only needs to assign a duration and convert it for the very
2136    //last point added, since the previously added  "last" ones (initially given a 0 duration) get valid durations
2137    //once they get relegated to 2nd-to-last:
2138    if (x <= widget->allocation.width)
2139    newDP->duration = oldprevdur - newDP->PrevDataPoint->duration;
2140    else {
2141    //    SG_DBGOUT_INT("x is bigger than window-width",(int)newDP->x);
2142    newDP->duration = 0; //if a valid duration is desired, calling function needs to recognize point went past right edge, then manually set duration
2143    }
2144    return newDP;
2145    }
2146    //Shouldn't ever get here; will be NULL if it does (which will at least give me a bug to locate the problem)
2147    return newDP;
2148    }
2149  */
2150 
2151 /////////////////////////////////////////////////////
2152 //this creates and adds a new SG_DataPoint to the
2153 //end of an existing linked list, or starts new one if
2154 //curDP == NULL. Most importantly, it gives
2155 //the curDP's (if existant) NextDataPoint the address  to this new
2156 //point, gives this new DP's PrevDataPoint address to curDP,
2157 //and sets new DP's NextDataPoint to NULL. FInally, if
2158 //the curDP you send it == NULL, it tries to attach the alloted
2159 //new DP to the DP's parent voice, if possible.
SG_AddNewDataPointToEnd(SG_DataPoint * curDP,SG_Voice * voice,double duration,double beatfreq,double volume_left,double volume_right,double basefreq,int state)2160 SG_DataPoint *SG_AddNewDataPointToEnd (SG_DataPoint * curDP,
2161                                        SG_Voice * voice,
2162                                        double duration,
2163                                        double beatfreq,
2164                                        double volume_left,
2165                                        double volume_right, double basefreq,
2166                                        int state)
2167 {
2168  SG_DataPoint *lastDP = NULL;
2169 
2170  //find end of list:
2171  while (curDP != NULL)
2172  {
2173   lastDP = curDP;
2174   curDP = curDP->NextDataPoint;
2175   //  SG_DBGOUT_INT("Stuck in Add New DP loop",0)
2176  }
2177 
2178  curDP = (SG_DataPoint *) malloc (sizeof (SG_DataPoint));
2179  curDP->PrevDataPoint = lastDP;
2180  curDP->duration = duration;
2181  curDP->beatfreq = beatfreq;
2182  curDP->x = 0;
2183  curDP->y = 0;
2184  curDP->volume_left = volume_left;      //negative means "no change"
2185  curDP->volume_right = volume_right;    //negative means "no change"
2186  curDP->basefreq = basefreq;
2187  curDP->parent = voice;
2188  curDP->state = state;
2189  curDP->NextDataPoint = NULL;
2190  if (lastDP != NULL)
2191  {
2192   lastDP->NextDataPoint = curDP;
2193  }
2194  else if (curDP->parent != NULL)
2195  {
2196   ((SG_Voice *) curDP->parent)->FirstDataPoint = curDP;
2197  }
2198  return curDP;
2199 }
2200 
2201 /*
2202    //START OF SAVED BLOCK
2203    //DON'T TOSS THESE Backup/Restore METHODS:
2204    //May be useful in future. ISSUES:
2205    //They work great, problem is that this approach to undo/redo depends (in the process
2206    //of creating duplicate linked lists) on swaping-out SG_FirstVoice to "Undo" -- which
2207    //unfortunately means that Paste, among other things, loses context for which Voices it's DPs
2208    //belong too. Which would be solvable, but basically a hacky way to approach it. instead, now
2209    //undo/redo will rely on the Copy/Paste methods, since they can use less room (but are a bit slower)
2210    /////////////////////////////////////////////////////
2211    //20060324
2212    //Creates a duplicate of the main linked-list
2213    //Implemented as a rudimentary one-step, toggle "Undo" feature
2214    //when used with a SG_RestoreDataPoints().
2215    void SG_BackupDataPoints()
2216    {
2217    //first free any previous backup:
2218    SG_CleanupVoices(SG_FirstBakVoice);
2219    SG_FirstBakVoice=NULL;
2220 
2221    SG_Voice * curVoice = SG_FirstVoice;
2222    SG_Voice * curBakVoice;
2223    SG_Voice * lastBakVoice = NULL;//MUST equal NULL!
2224    SG_DataPoint * curDP;
2225    SG_DataPoint * curBakDP;
2226    SG_DataPoint * lastBakDP = NULL;//MUST equal NULL!
2227    while (curVoice != NULL) {
2228    //Found a voice, so allot a new voice for backing it up:
2229    curBakVoice = SG_AddNewVoiceToEnd(lastBakVoice, BB_VOICETYPE_BINAURALBEAT);
2230    //copy voice to bakvoice:
2231    memcpy(curBakVoice, curVoice, sizeof(SG_Voice));
2232    //(re)set the critical items of backup voice to point to the right places:
2233    curBakVoice->NextVoice = NULL;
2234    curBakVoice->PrevVoice = lastBakVoice;
2235    //prepare lastBakVoice for next time it gets here:
2236    lastBakVoice=curBakVoice;
2237    //give it a first datapoint:
2238    curBakVoice->FirstDataPoint = SG_AddNewDataPointToEnd(NULL,curBakVoice, 10,1,-1.0, -1.0,0,0);
2239    //set SG_FirstBakVoice if we're doing first one:
2240    if (SG_FirstBakVoice == NULL)  SG_FirstBakVoice = curBakVoice;
2241 
2242    //now that we have the information, set DPs for loop:
2243    curDP = curVoice->FirstDataPoint;
2244    curBakDP = curBakVoice->FirstDataPoint;
2245    while (curDP != NULL) {
2246    //copy entire contents of curDP to curBakDP:
2247    memcpy(curBakDP, curDP,sizeof(SG_DataPoint));
2248    //now re-set curBakDP->Prev to point to it's real predecessor, and
2249    //curBakDP->Next to NULL (critical! Allows AddNewDP() to know where to add next point)
2250    curBakDP->NextDataPoint = NULL;
2251    curBakDP->PrevDataPoint = lastBakDP;
2252    //now set lastBakDP to remember next DP's predecessor:
2253    lastBakDP = curBakDP;
2254    //also for DPs crucial to reset it's parent pointer to point to its own parent:
2255    curBakDP->parent = curBakVoice;
2256    //advance to next DP:
2257    curDP = curDP->NextDataPoint;
2258    //if not at end of curDP list, allot some more memory and advance curBakDP to it:
2259    if (curDP != NULL) {
2260    curBakDP->NextDataPoint = SG_AddNewDataPointToEnd(curBakVoice->FirstDataPoint,curBakVoice,10,1,-1.0, -1.0,0,0);
2261    curBakDP = curBakDP->NextDataPoint;
2262    }
2263    }
2264    //advance to next voice:
2265    curVoice = curVoice->NextVoice;
2266    }
2267    }//END SG_BackupDataPoints()
2268 
2269    //SG_Voice * curVoice=SG_FirstVoice;
2270    //while (curVoice !=NULL) {  curVoice=curVoice->NextVoice; }
2271 
2272    /////////////////////////////////////////////////////
2273    //20060324
2274    //Restores a backup (if existant) of main linked-list
2275    //Implemented as a rudimentary one-step,toggle "Undo" feature
2276    //when used with a SG_BackupDataPoints()
2277    void SG_RestoreDataPoints(GtkWidget * widget)
2278    {
2279    //first validate SG_FirstBackupDataPoint:
2280    if (SG_FirstBakVoice == NULL || SG_FirstVoice == NULL) return;
2281 
2282    //delete old graph data:
2283    //SG_CleanupDataPoints(SG_FirstDataPoint);
2284    //20060328 -- instead of deleting old graph data (above), smarter to make
2285    //it the "Backup" data. But it is up to implementation to make clear that
2286    //restoring this "Undo" is actually a "Redo" after an "Undo":
2287    SG_Voice * tmpVoice = SG_FirstVoice;
2288    SG_FirstVoice = SG_FirstBakVoice;
2289    SG_FirstBakVoice = tmpVoice;
2290    SG_CurrentDataPoint = NULL;
2291    SG_ConvertDataToXY(widget);
2292    SG_DrawGraph(widget);
2293    }
2294  */
2295 //END OF SAVE BLOCK
2296 
2297 //SG_Voice * curVoice=SG_FirstVoice;
2298 //while (curVoice !=NULL) {  curVoice=curVoice->NextVoice; }
2299 
2300 /////////////////////////////////////////////////////
2301 //This implements a one-step "Undo" function, taking a
2302 //snapshop that SG_RestoreDataPoints can restore.
SG_BackupDataPoints(GtkWidget * widget)2303 void SG_BackupDataPoints (GtkWidget * widget)
2304 {
2305  SG_DBGOUT_INT ("SG_BackupDataPoints...", 0);
2306  SG_CopyDataPoints (widget, &SG_UndoRedo, FALSE);
2307  if (SG_UndoRedo.DPdata == NULL)
2308  {
2309   SG_DBGOUT_INT ("...Failed to fill Undo buffer", 0);
2310  }
2311 }
2312 
2313 /////////////////////////////////////////////////////
2314 //This was designed to implement a one-step "Undo" function,
2315 //restoring the snapshop taken by SG_BackupDataPoints. However,
2316 //it has also proven to be the most efficient way to load new data
2317 //from other sources (files, hard-coded, etc.) in to SG. Why?
2318 //Because this treates a restore as a total reload from scratch.
2319 //And to work, it only needs the global SG_BackupData
2320 //variable "SG_UndoRedo" to be allocated and filled. Specifically,
2321 //*DPdata and *Voice need to be allocated, then these fields filled:
2322 //SG_UndoRedo:
2323 //-  TotalDataPoints [Notably, I don't believe TotalVoices needs to be filled for the restore]
2324 // Voice[]:
2325 //-  type
2326 //-  state
2327 //-  hide
2328 //-  mute
2329 //-  mono [added 20100614]
2330 //-  description
2331 // DPdata[]:
2332 //-  duration
2333 //-  beatfreq
2334 //-  basefreq
2335 //-  volume_left
2336 //-  volume_right
2337 //-  state
2338 //-  parent
2339 //NOTE: if SG_MergeRestore == TRUE, adds restore to whatever is already
2340 //in SG; if FALSE, erases everything currently in SG
SG_RestoreDataPoints(GtkWidget * widget,gboolean MergeRestore)2341 void SG_RestoreDataPoints (GtkWidget * widget, gboolean MergeRestore)
2342 {
2343  if (SG_UndoRedo.DPdata == NULL || SG_UndoRedo.TotalDataPoints < 1)
2344  {
2345   return;
2346  }
2347 
2348  //first steal SG_UndoRedo's Undo data, then make SG_UndoRedo pose as empty to refill it with Redo data:
2349  SG_BackupData tmpUndoRedo;
2350 
2351  memcpy (&tmpUndoRedo, &SG_UndoRedo, sizeof (SG_BackupData));
2352  SG_UndoRedo.DPdata = NULL;
2353  //added following two lines on 20060604:
2354  SG_UndoRedo.Voice = NULL;
2355  SG_CopyDataPoints (widget, &SG_UndoRedo, FALSE);
2356 
2357  SG_Voice *curVoice;
2358 
2359  if (FALSE == MergeRestore)
2360  {
2361   //Delete EVERYTHING:
2362   SG_CleanupVoices (SG_FirstVoice);
2363   SG_FirstVoice = curVoice = NULL;
2364  }
2365  else
2366  {
2367   curVoice = SG_FirstVoice;
2368  }
2369 
2370  //Now reload EVERYTHING:
2371 
2372  void *placerpointer = NULL;    // parent pointer is treated strictly as arbitrary but unique number here, so it can be loaded with either a pointer or an incrementing int
2373  SG_DataPoint *curDP = NULL;
2374  int voices_used_count = -1;
2375  gboolean newDPflag = TRUE;
2376  int i;
2377 
2378  for (i = 0; i < tmpUndoRedo.TotalDataPoints; i++)
2379  {
2380   if (placerpointer != tmpUndoRedo.DPdata[i].parent)
2381   {
2382    placerpointer = tmpUndoRedo.DPdata[i].parent;
2383    ++voices_used_count;
2384    curVoice =
2385     SG_AddNewVoiceToEnd (tmpUndoRedo.Voice[voices_used_count].type,
2386                          tmpUndoRedo.Voice[voices_used_count].ID);
2387    SG_StringAllocateAndCopy (&(curVoice->description),
2388                              tmpUndoRedo.Voice[voices_used_count].
2389                              description);
2390    curVoice->state = tmpUndoRedo.Voice[voices_used_count].state;
2391    curVoice->hide = tmpUndoRedo.Voice[voices_used_count].hide;
2392    curVoice->mute = tmpUndoRedo.Voice[voices_used_count].mute;
2393    curVoice->mono = tmpUndoRedo.Voice[voices_used_count].mono;  //[added 20100614]
2394    if (SG_FirstVoice == NULL)
2395    {
2396     SG_FirstVoice = curVoice;
2397    }
2398    newDPflag = TRUE;
2399    curDP = NULL;
2400    SG_DBGOUT_INT ("Found new voice, ID:", curVoice->ID);
2401   }
2402   curDP = SG_AddNewDataPointToEnd (curDP, curVoice, tmpUndoRedo.DPdata[i].duration,     //duration
2403                                    tmpUndoRedo.DPdata[i].beatfreq,      //beatfreq
2404                                    tmpUndoRedo.DPdata[i].volume_left,   //volume_left
2405                                    tmpUndoRedo.DPdata[i].volume_right,  //volume_right
2406                                    tmpUndoRedo.DPdata[i].basefreq,      //basefreq
2407                                    tmpUndoRedo.DPdata[i].state);        //state
2408   if (newDPflag == TRUE)
2409   {
2410    newDPflag = FALSE;
2411    curVoice->FirstDataPoint = curDP;
2412    SG_DBGOUT_PNT ("Gave new voice a FirstDataPoint",
2413                   curVoice->FirstDataPoint);
2414   }
2415  }
2416 
2417  SG_ConvertDataToXY (widget);
2418  SG_DrawGraph (widget);
2419  SG_DBGOUT_INT ("Finished Restoration, Pasted Points:",
2420                 tmpUndoRedo.TotalDataPoints);
2421 
2422  //cleanup the original Undo data:
2423  //free(tmpUndoRedo.DPdata);
2424  //changed above to following on 20060604:
2425  SG_CleanupBackupData (&tmpUndoRedo);
2426  if (TRUE == MergeRestore)
2427  {
2428   SG_VoiceTestLegalSelection ();
2429  }
2430  SG_DataHasChanged = SG_GraphHasChanged = TRUE;
2431 }
2432 
2433 /////////////////////////////////////////////////////
SG_CopySelectedDataPoints(GtkWidget * widget)2434 void SG_CopySelectedDataPoints (GtkWidget * widget)
2435 {
2436  SG_CopyDataPoints (widget, &SG_CopyPaste, TRUE);
2437 }
2438 
2439 /////////////////////////////////////////////////////
SG_PasteSelectedDataPoints(GtkWidget * widget,gboolean predeselect)2440 void SG_PasteSelectedDataPoints (GtkWidget * widget, gboolean predeselect)
2441 {
2442  SG_PasteDataPoints (widget, &SG_CopyPaste, predeselect);
2443 }
2444 
2445 /////////////////////////////////////////////////////
2446 //Cleans the 4 SG_BackupData members that require variable
2447 //amounts of storage (allocated by SG_AllocateBackupData)
SG_CleanupBackupData(SG_BackupData * Bdata)2448 void SG_CleanupBackupData (SG_BackupData * Bdata)
2449 {
2450  if (Bdata == NULL)
2451  {
2452   return;
2453  }
2454  SG_DBGOUT_PNT ("Cleaningup SG_BackupData:", Bdata);
2455  if (Bdata->DPdata != NULL)
2456  {
2457   free (Bdata->DPdata);
2458   SG_DBGOUT_PNT ("\tFreed ->DPdata", Bdata->DPdata);
2459   Bdata->DPdata = NULL;
2460  }
2461 
2462  //first free all the description string data:
2463  if (Bdata->Voice != NULL)
2464  {
2465   int i;
2466 
2467   for (i = 0; i < Bdata->TotalVoices; i++)
2468   {
2469    if (Bdata->Voice[i].description != NULL)
2470    {
2471     SG_DBGOUT_STR ("Desc.:", Bdata->Voice[i].description);
2472     free (Bdata->Voice[i].description);
2473     SG_DBGOUT_PNT ("\tFreed ->VoiceDescription[]",
2474                    Bdata->Voice[i].description);
2475    }
2476   }
2477 
2478   //Now free all the remaining Voice data:
2479   free (Bdata->Voice);
2480   SG_DBGOUT_PNT ("\tFreed ->Voice", Bdata->Voice);
2481   Bdata->Voice = NULL;
2482  }
2483 
2484  Bdata->TotalDataPoints = 0;
2485 }
2486 
2487 /////////////////////////////////////////////////////////////
2488 //20070622: This allocates memory for the SG_BackupData
2489 //data members that require variable amounts of storage.
2490 //Call SG_CleanupBackupData to clean what this does.
SG_AllocateBackupData(SG_BackupData * Bdata,int TotalVoiceCount,int TotalDataPointCount)2491 int SG_AllocateBackupData (SG_BackupData * Bdata, int TotalVoiceCount,
2492                            int TotalDataPointCount)
2493 {
2494  Bdata->TotalDataPoints = TotalDataPointCount;
2495  Bdata->TotalVoices = TotalVoiceCount;
2496 
2497  //allocate the memory:
2498  Bdata->DPdata =
2499   (SG_DataPoint *) calloc (Bdata->TotalDataPoints, sizeof (SG_DataPoint));
2500  Bdata->Voice = (SG_Voice *) calloc (Bdata->TotalVoices, sizeof (SG_Voice));
2501  //make sure everything allocated properly:
2502 
2503  if (Bdata->DPdata == NULL || Bdata->Voice == NULL)
2504  {
2505   SG_CleanupBackupData (Bdata);
2506   return 0;
2507  }
2508  return 1;
2509 }
2510 
2511 /////////////////////////////////////////////////////
2512 //20060412
2513 //Copies any selected points to a simple array to create
2514 //a paste-able mass when used with a SG_PasteDataPoints().
SG_CopyDataPoints(GtkWidget * widget,SG_BackupData * Bdata,gboolean selected_only)2515 void SG_CopyDataPoints (GtkWidget * widget, SG_BackupData * Bdata,
2516                         gboolean selected_only)
2517 {
2518  //First count how many DPs to copy. One reason to do this first is to be sure
2519  //there really are selected DPs before throwing away the old buffer:
2520  SG_DataPoint *curDP;
2521  int DP_count = 0;
2522  int voice_count = 0;
2523  int voices_used_count = 0;
2524  gboolean newvoiceflag;
2525  SG_Voice *curVoice = SG_FirstVoice;
2526 
2527  while (curVoice != NULL)
2528  {
2529   curDP = curVoice->FirstDataPoint;
2530   ++voice_count;
2531   newvoiceflag = TRUE;
2532   while (curDP != NULL)
2533   {
2534    if ((selected_only == FALSE) || (curDP->state == SG_SELECTED))
2535    {
2536     ++DP_count;
2537     if (newvoiceflag == TRUE)
2538     {
2539      ++voices_used_count;
2540     }
2541     newvoiceflag = FALSE;
2542    }
2543    //all list loops need this:
2544    curDP = curDP->NextDataPoint;
2545   }
2546   curVoice = curVoice->NextVoice;
2547  }
2548 
2549  SG_DBGOUT_INT ("Copy: DataPoints to copy:", DP_count);
2550  SG_DBGOUT_INT ("Copy: Total Voices:", voice_count);
2551  SG_DBGOUT_INT ("Copy: Voices used by selected DPs:", voices_used_count);
2552 
2553  //if there are no selected DPs, don't do anything:
2554  if (DP_count < 1)
2555  {
2556   return;
2557  }
2558 
2559  //erase backup structure to prepare for new data:
2560  SG_CleanupBackupData (Bdata);
2561 
2562  //Now get scaling vars required to correctly paste data regardless of how
2563  //drawingarea dimensions may have changed:
2564  //Calling SG_GetScheduleLimits() just to be safe (it fills the DurHz vars below):
2565  SG_GetScheduleLimits ();
2566  Bdata->OrigSG_TotalScheduleDuration = SG_TotalScheduleDuration;
2567  // Bdata->OrigSG_MaxScheduleBeatfrequency = SG_MaxScheduleBeatfrequency;
2568  Bdata->OrigWidth = widget->allocation.width;
2569  Bdata->OrigHeight = widget->allocation.height;
2570  Bdata->TotalDataPoints = DP_count;
2571  // Bdata->TotalVoices = voice_count;//not sure why this was here; commented 20070630
2572  Bdata->TotalVoices = voices_used_count;
2573 
2574  SG_AllocateBackupData (Bdata, Bdata->TotalVoices, Bdata->TotalDataPoints);
2575 
2576  // Now put all the selected DPs in to the copy buffer:
2577  DP_count = 0;
2578  voice_count = 0;
2579  voices_used_count = -1;
2580  curVoice = SG_FirstVoice;
2581  while (curVoice != NULL)
2582  {
2583   curDP = curVoice->FirstDataPoint;
2584   ++voice_count;
2585   newvoiceflag = TRUE;
2586   while (curDP != NULL)
2587   {
2588    if ((selected_only == FALSE) || (curDP->state == SG_SELECTED))
2589    {
2590     memcpy (&(Bdata->DPdata[DP_count]), curDP, sizeof (SG_DataPoint));
2591     ++DP_count;
2592     if (newvoiceflag == TRUE)
2593     {
2594      ++voices_used_count;       //NOTE: I think the array count is wrong here; one-ahead of where it should be
2595      newvoiceflag = FALSE;
2596      Bdata->Voice[voices_used_count].type =
2597       ((SG_Voice *) curDP->parent)->type;
2598      Bdata->Voice[voices_used_count].ID = ((SG_Voice *) curDP->parent)->ID;
2599      Bdata->Voice[voices_used_count].state =
2600       ((SG_Voice *) curDP->parent)->state;
2601      Bdata->Voice[voices_used_count].hide =
2602       ((SG_Voice *) curDP->parent)->hide;
2603      Bdata->Voice[voices_used_count].mute =
2604       ((SG_Voice *) curDP->parent)->mute;
2605      Bdata->Voice[voices_used_count].mono = ((SG_Voice *) curDP->parent)->mono; //[added 20100614]
2606      //now deal with the annoying Voice Description:
2607      SG_StringAllocateAndCopy (&(Bdata->Voice[voices_used_count].description),
2608                                ((SG_Voice *) curDP->parent)->description);
2609      SG_DBGOUT_STR ("SG_CopyDataPoints copied string:",
2610                     Bdata->Voice[voices_used_count].description);
2611     }
2612    }
2613    //all list loops need this:
2614    curDP = curDP->NextDataPoint;
2615   }
2616   curVoice = curVoice->NextVoice;
2617  }
2618  SG_DBGOUT_INT ("Copied DataPoints:", Bdata->TotalDataPoints);
2619 }       //END SG_CopySelectedDataPoints()
2620 
2621 /////////////////////////////////////////////////////
2622 //20060412
2623 //Pastes any previously selected points copied with SG_CopyDataPoints().
2624 //BUG: Unfortunately, Paste requires each DP's knowledge of previous parent SG_Voice.
2625 //So if parent SG_Voice gets erased previous to a Paste, some horrible things will happen
SG_PasteDataPoints(GtkWidget * widget,SG_BackupData * Bdata,gboolean predeselect)2626 void SG_PasteDataPoints (GtkWidget * widget, SG_BackupData * Bdata,
2627                          gboolean predeselect)
2628 {
2629  if (Bdata->DPdata == NULL || Bdata->TotalDataPoints < 1)
2630  {
2631   return;
2632  }
2633 
2634  SG_ProgressIndicatorFlag = FALSE;
2635 
2636  //Not sure what user would want:
2637  if (predeselect == TRUE)
2638  {
2639   SG_DeselectDataPoints ();
2640  }
2641 
2642  double xscale = widget->allocation.width / (double) Bdata->OrigWidth;
2643 
2644  //  double yscale = widget->allocation.height / (double)Bdata->OrigHeight;
2645  int count;
2646  SG_DataPoint *curDP;
2647 
2648  //NOTE 20060711: since being able to change graph views, it now has to be assumed necessary to
2649  //insert each data point as if the original XY context for the points is no gone. This means
2650  //I can let InsertNewDatapointXY() do all the "current graph view" sorting:
2651 
2652  //Easiest thing to do would be just to paste the mess with the first point at x=0, but it would be
2653  //really nice to try and paste it "where it was", time-wise, in case user really just changed a few details
2654  //Scale all x's appropriately and calculate appropriate durations:
2655  xscale *= Bdata->OrigSG_TotalScheduleDuration / SG_TotalScheduleDuration;
2656  SG_DBGOUT_FLT ("Paste context \%", xscale);
2657  SG_Voice *curVoice;
2658  int voices_used_count = -1;
2659  void *placerpointer = NULL;
2660 
2661  for (count = 0; count < Bdata->TotalDataPoints; count++)
2662  {
2663   //this was hacked to make pasting independent of pointer addresses:
2664   curVoice = SG_FirstVoice;
2665   //see if this is a new (as in not yet seen) voice:
2666   if (Bdata->DPdata[count].parent != placerpointer)
2667   {
2668    placerpointer = Bdata->DPdata[count].parent;
2669    ++voices_used_count;
2670    //these aren't necessary because user may not be restoring, just pasting a few points:
2671    //   curVoice->type = Bdata->Voice[voices_used_count].type;
2672    //   curVoice->state = Bdata->Voice[voices_used_count].state;
2673    //   curVoice->hide = Bdata->Voice[voices_used_count].hide;
2674    //   curVoice->mute = Bdata->Voice[voices_used_count].mute;
2675    //   curVoice->mono = Bdata->Voice[voices_used_count].mono;
2676    SG_DBGOUT_PNT ("Paste: DP changing Voices", placerpointer);
2677   }
2678   while (curVoice != NULL &&
2679          curVoice->ID != Bdata->Voice[voices_used_count].ID)
2680   {
2681    curVoice = curVoice->NextVoice;
2682   }
2683   if (curVoice != NULL)
2684   {
2685    //old way (Still seeing if new one works in all cases)
2686    curDP = SG_InsertNewDataPointXY (widget, curVoice, Bdata->DPdata[count].x * xscale, 0);      //NOTE: I MANUALLY SET THIS
2687    //now set the stuff that SG_InsertNewDataPointXY() doesn't:
2688    curDP->beatfreq = Bdata->DPdata[count].beatfreq;
2689    curDP->volume_left = Bdata->DPdata[count].volume_left;
2690    curDP->volume_right = Bdata->DPdata[count].volume_right;
2691    curDP->basefreq = Bdata->DPdata[count].basefreq;
2692    curDP->state = Bdata->DPdata[count].state;   //state should always be SG_SELECTED, correct?
2693    if (curDP->x > widget->allocation.width)
2694    {    //deal with x's beyond current graph width:
2695     SG_DBGOUT_FLT ("Paste: Went past right-edge; rescaling", curDP->x);
2696     curDP->duration = Bdata->DPdata[count].duration;
2697    }
2698    //end old way
2699 
2700    /*
2701       //alternate way (20060511): TODO: This is a brute force approach, written because I'm not convinced that the old approach above handles last DPs right
2702       double newx = Bdata->DPdata[count].x * xscale;
2703       if (newx >= widget->allocation.width) {//deal with x's beyond current graph width:
2704       SG_AddNewDataPointToEnd(curVoice->FirstDataPoint,
2705       curVoice,
2706       Bdata->DPdata[count].duration,
2707       Bdata->DPdata[count].beatfreq,
2708       Bdata->DPdata[count].volume,
2709       Bdata->DPdata[count].basefreq,
2710       Bdata->DPdata[count].state); //state should always be SG_SELECTED, correct?
2711       } else {
2712       curDP = SG_InsertNewDataPointXY(widget,
2713       curVoice,
2714       newx,
2715       0); //NOTE: I manually set this via SG_ConvertDataToXY()
2716       //now set the stuff that SG_InsertNewDataPointXY() doesn't:
2717       curDP->beatfreq = Bdata->DPdata[count].beatfreq;
2718       curDP->volume = Bdata->DPdata[count].volume;
2719       curDP->basefreq = Bdata->DPdata[count].basefreq;
2720       curDP->state = Bdata->DPdata[count].state; //state should always be SG_SELECTED, correct?
2721       }
2722       //end alternate way (20060511)
2723     */
2724   }
2725  }
2726 
2727  //20060421: I've observed that it is wise to NOT call this function unless absolutely
2728  //necessary; it can introduce subtle but insidiously accumulating rounding errors:
2729  SG_ConvertDataToXY (widget);
2730 
2731  SG_DBGOUT_INT ("Pasted Points:", count);
2732 
2733  //draw it:
2734  SG_DrawGraph (widget);
2735  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
2736 }       //END SG_PasteSelectedDataPoints()
2737 
2738 /////////////////////////////////////////////////////
2739 //this offers a means for the user to cull invisible duplicate DPs.
2740 //NOTE: This just never really worked very well; I guess rounding errors in
2741 //floats are just too insidious. It is better to let user use a proximity
2742 //algorithm when they might want to clean up.
2743 //20070731: It had to be updated to account for multiple graph views
SG_DeleteDuplicateDataPoints(GtkWidget * widget)2744 void SG_DeleteDuplicateDataPoints (GtkWidget * widget)
2745 {
2746  int count = 0;
2747  SG_DataPoint *curDP;
2748  SG_Voice *curVoice = SG_FirstVoice;
2749 
2750  while (curVoice != NULL)
2751  {
2752   curDP = curVoice->FirstDataPoint;
2753   if (curDP != NULL)
2754    while (curDP->NextDataPoint != NULL)
2755    {
2756     if (curDP->x == curDP->NextDataPoint->x &&
2757         curDP->y == curDP->NextDataPoint->y &&
2758         curDP->duration == curDP->NextDataPoint->duration &&
2759         curDP->volume_left == curDP->NextDataPoint->volume_left &&
2760         curDP->volume_right == curDP->NextDataPoint->volume_right &&
2761         curDP->basefreq == curDP->NextDataPoint->basefreq &&
2762         curDP->beatfreq == curDP->NextDataPoint->beatfreq)
2763     {
2764      SG_DeleteDataPoint (curDP->NextDataPoint, FALSE);
2765      ++count;
2766     }
2767     else
2768      curDP = curDP->NextDataPoint;
2769    }
2770   curVoice = curVoice->NextVoice;
2771  }
2772 
2773  if (count > 0)
2774  {
2775   SG_DBGOUT_INT ("Duplicates deleted:", count);
2776   SG_DataHasChanged = SG_GraphHasChanged = TRUE;
2777  }
2778 }
2779 
2780 /////////////////////////////////////////////////////
SG_MessageBox(char * question)2781 gboolean SG_MessageBox (char *question)
2782 {
2783  GtkWidget *dialog = gtk_dialog_new_with_buttons ("A question for you...",
2784                                                   NULL,
2785                                                   (GtkDialogFlags)
2786                                                   (GTK_DIALOG_MODAL |
2787                                                    GTK_DIALOG_DESTROY_WITH_PARENT),
2788                                                   GTK_STOCK_OK,
2789                                                   GTK_RESPONSE_ACCEPT,
2790                                                   GTK_STOCK_CANCEL,
2791                                                   GTK_RESPONSE_REJECT,
2792                                                   NULL);
2793 
2794  //add some stuff:
2795  GtkWidget *label_question = gtk_label_new (question);
2796 
2797  // Add the label, and show everything we've added to the dialog.
2798  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
2799                     label_question);
2800 
2801  gtk_widget_show_all (dialog);
2802 
2803  //block until I get a response:
2804  gint result = gtk_dialog_run (GTK_DIALOG (dialog));
2805 
2806  switch (result)
2807  {
2808  case GTK_RESPONSE_ACCEPT:
2809   {
2810    result = TRUE;
2811    break;
2812   }
2813 
2814  default:
2815   result = FALSE;
2816   break;
2817  }
2818  gtk_widget_destroy (dialog);
2819  return result;
2820 }
2821 
2822 /////////////////////////////////////////////////////
SG_CleanupVoice(SG_Voice * curVoice)2823 void SG_CleanupVoice (SG_Voice * curVoice)
2824 {
2825  if (curVoice == NULL)
2826  {
2827   return;
2828  }
2829  if (curVoice->description != NULL)
2830  {
2831   SG_DBGOUT_STR ("Cleaning Voice Description:", curVoice->description);
2832   free (curVoice->description);
2833   curVoice->description = NULL;
2834  }
2835  SG_CleanupDataPoints (curVoice->FirstDataPoint);
2836  //curVoice->FirstDataPoint=NULL; //Normally, you must do this!
2837  free (curVoice);
2838 }
2839 
2840 /////////////////////////////////////////////////////
2841 //this is a bit of a scary function, but necessary for
2842 //user to remove a voice from their Schedule. Scary
2843 //for a lot of reasons that could introduce bugs, like:
2844 // - gaps in SG_Voice ID count
2845 // - unpredictable with Copy/Paste
2846 //A workaround (if this does prove to have real bugs)
2847 //might be to copy everything to a SG_BackupData,
2848 //then do SG_RestoreDataPoints (restore completely from scratch), but
2849 //simply skipping the designated "deleted" voice.
SG_DeleteVoice(SG_Voice * curVoice)2850 void SG_DeleteVoice (SG_Voice * curVoice)
2851 {
2852  if (curVoice == NULL)
2853  {
2854   return;
2855  }
2856  SG_Voice *prevVoice = curVoice->PrevVoice;
2857  SG_Voice *nextVoice = curVoice->NextVoice;
2858 
2859  if (prevVoice == NULL && nextVoice == NULL)
2860  {
2861   ERROUT ("Deleting only voice not permitted");
2862   return;
2863  }
2864 
2865  //Make sure user knows what they're doing:
2866  // if (FALSE == SG_MessageBox ("\nDo you want to delete this voice?\n")) { return; }
2867 
2868  SG_DBGOUT_INT ("Deleting SG_Voice ", curVoice->ID);
2869 
2870  SG_CleanupVoice (curVoice);
2871 
2872  if (prevVoice != NULL)
2873  {
2874   prevVoice->NextVoice = nextVoice;
2875   //Check to see if we now have a new SG_FirstVoice:
2876  }
2877  else
2878  {
2879   SG_FirstVoice = nextVoice;
2880   SG_DBGOUT_PNT ("New SG_FirstVoice", SG_FirstVoice);
2881  }
2882 
2883  if (nextVoice != NULL)
2884  {
2885   nextVoice->PrevVoice = prevVoice;
2886  }
2887  //NOTE: I might eventually need to do the following if bugs are evident.
2888  //Pasting with a missing voice is not understood, could be ugly until I take the
2889  //time to have Paste create a new voice if it can't find existing one.
2890  // SG_CleanupBackupData(&SG_CopyPaste);
2891  // SG_CleanupBackupData(&SG_UndoRedo);
2892 
2893  // SG_DrawGraph(widget); //decided not to do this here because user needs to find new SG_SELECTED voice
2894  SG_GraphHasChanged = SG_DataHasChanged = TRUE; //added 20070731
2895 }
2896 
2897 /////////////////////////////////////////////////////
2898 //User must reset SG_First*SG_Voice manually after calling this!
2899 //like SG_FirstVoice=NULL, SG_FirstBakVoice=NULL, etc.
SG_CleanupVoices(SG_Voice * FirstVoice)2900 void SG_CleanupVoices (SG_Voice * FirstVoice)
2901 {
2902  if (FirstVoice == NULL)
2903  {
2904   return;
2905  }
2906  SG_Voice *curVoice = FirstVoice;
2907  SG_Voice *nextVoice;
2908  unsigned int count = 0;
2909 
2910  while (curVoice->NextVoice != NULL)
2911  {
2912   nextVoice = curVoice->NextVoice;
2913   SG_CleanupVoice (curVoice);
2914   curVoice = nextVoice;
2915   ++count;
2916  }
2917 
2918  //still need to get the last one:
2919  if (curVoice != NULL)
2920  {
2921   //a20070621: was manually cleaning here instead of calling SG_CleanupVoice,
2922   //  SG_DBGOUT("Cleaning last voice:");
2923   SG_CleanupVoice (curVoice);
2924   ++count;
2925  }
2926  //User must NULL their FirstVoice after calling this!!!! i.e., "FirstVoice=NULL;"
2927 }
2928 
2929 /////////////////////////////////////////////////////
2930 //20060326: this was made to be able to clean any linked list of
2931 //data points (so both main and backup sets can use this func)
2932 //NOTE: making this general meant that it is now up to the calling
2933 //function to NULL FirstDataPoint sent here after done here!
SG_CleanupDataPoints(SG_DataPoint * curDP)2934 void SG_CleanupDataPoints (SG_DataPoint * curDP)
2935 {
2936  if (curDP == NULL)
2937  {
2938   return;
2939  }
2940  SG_DataPoint *nextDP;
2941  unsigned int count = 0;
2942 
2943  while (curDP->NextDataPoint != NULL)
2944  {
2945   //  SG_DBGOUT_INT("Cleanup DP", count);
2946   nextDP = curDP->NextDataPoint;
2947   free (curDP);
2948   curDP = nextDP;
2949   ++count;
2950  }
2951 
2952  //still need to get the last one:
2953  if (curDP != NULL)
2954  {
2955   //   SG_DBGOUT_INT("Cleanup Last DP", count);
2956   free (curDP);
2957   ++count;
2958  }
2959 
2960  SG_DBGOUT_INT ("DataPoints Deleted:", count);
2961  //NOTE: caller must now be sure to NULL the FirstDataPoint sent here!
2962 }
2963 
2964 /////////////////////////////////////////////////////
2965 //this fills global variables SG_MaxScheduleBeatfrequency
2966 //and SG_TotalScheduleDuration. It can be
2967 //called without a widget, making it a bit more general than
2968 //related workhorse, SG_ConvertDataToXY()
SG_GetScheduleLimits()2969 inline void SG_GetScheduleLimits ()
2970 {
2971  //SG_DBGOUT_INT("Got to SG_GetScheduleLimits\n",0);
2972  SG_TotalScheduleDuration = 0;
2973  SG_MaxScheduleBeatfrequency = 0;
2974  SG_MaxScheduleBasefrequency = 0;
2975  SG_Voice *curVoice = SG_FirstVoice;
2976 
2977  while (curVoice != NULL)
2978  {
2979   SG_DataPoint *curDP = curVoice->FirstDataPoint;
2980   double totalscheduleduration = 0;
2981 
2982   do
2983   {
2984    totalscheduleduration += curDP->duration;
2985    if (FALSE == curVoice->hide) //20110519
2986    {
2987     if (SG_MaxScheduleBeatfrequency < curDP->beatfreq)
2988      SG_MaxScheduleBeatfrequency = curDP->beatfreq;
2989     if (SG_MaxScheduleBasefrequency < curDP->basefreq)
2990      SG_MaxScheduleBasefrequency = curDP->basefreq;
2991    }
2992    curDP = curDP->NextDataPoint;
2993   }
2994   while (curDP != NULL);
2995   if (totalscheduleduration > SG_TotalScheduleDuration)
2996    SG_TotalScheduleDuration = totalscheduleduration;
2997   curVoice = curVoice->NextVoice;
2998  }
2999 
3000  //hacks to make sure we don't have divide-by-zero issues:
3001  if (SG_TotalScheduleDuration < .001)
3002   SG_TotalScheduleDuration = .001;
3003  if (SG_MaxScheduleBeatfrequency < .001)
3004   SG_MaxScheduleBeatfrequency = .001;
3005  if (SG_MaxScheduleBasefrequency < .001)
3006   SG_MaxScheduleBasefrequency = .001;
3007  SG_GraphHasChanged = TRUE;     //20110519
3008 }
3009 
3010 /*
3011    /////////////////////////////////////////////////////
3012    void SG_ConvertDataToXY(GtkWidget *widget) {
3013 
3014    SG_GetScheduleLimits();
3015 
3016    switch (SG_GraphType) {
3017    case SG_GRAPHTYPE_BEATFREQ:
3018    SG_ConvertDataToXY(widget);
3019    break;
3020 
3021    case SG_GRAPHTYPE_BASEFREQ:
3022    SG_ConvertDataToXY(widget);
3023    break;
3024 
3025    case SG_GRAPHTYPE_VOLUME:
3026    SG_ConvertDataToXY(widget);
3027    break;
3028 
3029    case SG_GRAPHTYPE_VOLUME_BALANCE:
3030    SG_ConvertDataToXY(widget);
3031    break;
3032 
3033    default:
3034    SG_GraphType = SG_GRAPHTYPE_BEATFREQ;
3035    break;
3036 
3037    }
3038    }
3039  */
3040 
SG_TestDataPointGraphLimits(GtkWidget * widget,SG_DataPoint * curDP)3041 void SG_TestDataPointGraphLimits (GtkWidget * widget, SG_DataPoint * curDP)
3042 {
3043  if (curDP->y > widget->allocation.height)
3044  {
3045   curDP->y = widget->allocation.height;
3046  }
3047  else if (curDP->y < 0)
3048  {
3049   curDP->y = 0;
3050  }
3051  if (curDP->x > widget->allocation.width)
3052  {
3053   //this one isn't necessary; it is legal to go beyond end
3054   // curDP->x = widget->allocation.width;
3055  }
3056  else if (curDP->x < 0)
3057  {
3058   curDP->x = 0;
3059  }
3060 }
3061 
3062 /////////////////////////////////////////////////////
3063 //recalculates durations for SELECTED datapoints according to their x position on the graph
SG_ConvertYToData_SelectedPoints(GtkWidget * widget)3064 void SG_ConvertYToData_SelectedPoints (GtkWidget * widget)
3065 {
3066  //double y_scaling_factor=widget->allocation.height/SG_MaxScheduleBeatfrequency;
3067  // SG_CurrentDataPoint->duration=SG_CurrentDataPoint->x_interval/x_scaling_factor;
3068  //First the easy one: convert SG_CurrentDataPoint's y to Hz value:
3069  //SG_CurrentDataPoint->beatfreq = ((widget->allocation.height-SG_CurrentDataPoint->y)*SG_MaxScheduleBeatfrequency)/widget->allocation.height;
3070  double unit_per_pixel;
3071 
3072  switch (SG_GraphType)
3073  {
3074  case SG_GRAPHTYPE_BEATFREQ:
3075   unit_per_pixel = SG_MaxScheduleBeatfrequency / widget->allocation.height;
3076   break;
3077 
3078  case SG_GRAPHTYPE_BASEFREQ:
3079   unit_per_pixel = SG_MaxScheduleBasefrequency / widget->allocation.height;
3080   break;
3081 
3082  case SG_GRAPHTYPE_VOLUME:
3083   unit_per_pixel = 1.0 / widget->allocation.height;
3084   break;
3085 
3086  case SG_GRAPHTYPE_VOLUME_BALANCE:
3087   unit_per_pixel = 1.0 / widget->allocation.height;
3088   break;
3089 
3090  default:
3091   SG_GraphType = SG_GRAPHTYPE_BEATFREQ;
3092   unit_per_pixel = 0.001;
3093   break;
3094  }
3095 
3096  SG_Voice *curVoice = SG_FirstVoice;
3097 
3098  while (curVoice != NULL)
3099  {
3100   SG_DataPoint *curDP = curVoice->FirstDataPoint;
3101 
3102   do
3103   {
3104    if (curDP->state == SG_SELECTED)
3105    {
3106     switch (SG_GraphType)
3107     {
3108     case SG_GRAPHTYPE_BEATFREQ:
3109      curDP->beatfreq =
3110       (widget->allocation.height - curDP->y) * unit_per_pixel;
3111      break;
3112 
3113     case SG_GRAPHTYPE_BASEFREQ:
3114      curDP->basefreq =
3115       (widget->allocation.height - curDP->y) * unit_per_pixel;
3116      break;
3117 
3118     case SG_GRAPHTYPE_VOLUME:
3119      SG_TestDataPointGraphLimits (widget, curDP);       //to make sure Y is within limits
3120      //idea here: changing volume sets absolute level of higher of the two channels, while
3121      //the lower gets set to a new value according to the original proportion between the two.
3122      if (curDP->volume_left > curDP->volume_right)
3123      {
3124       double ratio = curDP->volume_right / curDP->volume_left;
3125 
3126       curDP->volume_left =
3127        ((widget->allocation.height - curDP->y) * unit_per_pixel);
3128       curDP->volume_right = curDP->volume_left * ratio;
3129      }
3130      else if (curDP->volume_left < curDP->volume_right)
3131      {
3132       double ratio = curDP->volume_left / curDP->volume_right;
3133 
3134       curDP->volume_right =
3135        ((widget->allocation.height - curDP->y) * unit_per_pixel);
3136       curDP->volume_left = curDP->volume_right * ratio; //20070728 FIXED STUPID BUG (volume_right was volume_left)
3137      }
3138      else
3139      {
3140       curDP->volume_left = curDP->volume_right =
3141        ((widget->allocation.height - curDP->y) * unit_per_pixel);
3142      }
3143      break;
3144 
3145     case SG_GRAPHTYPE_VOLUME_BALANCE:
3146      SG_TestDataPointGraphLimits (widget, curDP);       //to make sure Y is within limits
3147      //###################### BUG HERE ###########################
3148      //idea here is to only decrease one side of the Volume pair depending on
3149      //whether the DP is above ("left") or below ("right") centerline, in which
3150      //case it becomes a proportion of the side that doesn't get changed. the only
3151      //tricky part is that if the user crosses the midpoint of the graph, this would
3152      //start to deplete the larger valued channel as a proportion of the smaller --
3153      //meaning that it would change (reduce) the overall volume. Thus, I must first check to
3154      //see if I am changing the larger -- and if so, give it's value to the formerly
3155      //smaller channell... yikes.
3156      {
3157       //Totally hacky solution to problem of keeping a valid ratio when both vars == 0:
3158       if (curDP->volume_right == 0 && curDP->volume_left == 0)
3159       {
3160        curDP->volume_right = curDP->volume_left = 1e-16;
3161       }
3162       double tmpval =
3163        ((widget->allocation.height - curDP->y) * unit_per_pixel) - .5;
3164       if (tmpval > 0)
3165       {
3166        //this means I am changing "right" channel (because "left" is bigger):
3167        //first see if right channel WAS the bigger (because if it is, I need to give left channel it's value!):
3168        if (curDP->volume_right > curDP->volume_left)
3169        {
3170         curDP->volume_left = curDP->volume_right;
3171         //DBGOUT("Crossed Midline to Left");
3172        }
3173        curDP->volume_right = (1.0 - (tmpval / .5)) * curDP->volume_left;
3174       }
3175       else
3176       { //this handles right > left, and also right == left:
3177        if (curDP->volume_right < curDP->volume_left)
3178        {
3179         curDP->volume_right = curDP->volume_left;
3180         //DBGOUT("Crossed Midline to Right");
3181        }
3182        curDP->volume_left = (1.0 - (-tmpval / .5)) * curDP->volume_right;
3183       }
3184      }
3185      break;
3186     }
3187    }
3188    curDP = curDP->NextDataPoint;
3189   }
3190   while (curDP != NULL);
3191   curVoice = curVoice->NextVoice;
3192  }
3193 }
3194 
3195 /////////////////////////////////////////////////////
3196 //NOTE: The following unified conversion functions turn out to be
3197 //about the most critical code to the entire functionality of
3198 //ScheduleGUI, being called virtually every time anything a graph view is presented
3199 //or anything is done to the points. It is worth really studying this one to see
3200 //where/if they introduce subtle but accumulating rounding-errors or graphpoint/datapoint
3201 //resolution errors (since for many operations, a SG_DataPoint's x determined here
3202 //gets used to recalibrate that SG_DataPoint's duration).
3203 ////////
3204 //This function first determines global duration and beatfreq limits, then uses
3205 //that info in combination with drawingarea dimensions to give exact XY
3206 //placement for DataPoints. Used after a drawingarea resize, or any time
3207 //a change in graphical context invalidates datapoints' xy's
3208 /////////////////////////////////////////////////////
SG_ConvertDataToXY(GtkWidget * widget)3209 void SG_ConvertDataToXY (GtkWidget * widget)
3210 {
3211  SG_GetScheduleLimits ();
3212 
3213  double x_scaling_factor =
3214   widget->allocation.width / SG_TotalScheduleDuration;
3215  double y_scaling_factor;
3216 
3217  switch (SG_GraphType)
3218  {
3219  case SG_GRAPHTYPE_BEATFREQ:
3220   y_scaling_factor = widget->allocation.height / SG_MaxScheduleBeatfrequency;
3221   break;
3222 
3223  case SG_GRAPHTYPE_BASEFREQ:
3224   y_scaling_factor = widget->allocation.height / SG_MaxScheduleBasefrequency;
3225   break;
3226 
3227  case SG_GRAPHTYPE_VOLUME:
3228   y_scaling_factor = widget->allocation.height;
3229   break;
3230 
3231  case SG_GRAPHTYPE_VOLUME_BALANCE:
3232   y_scaling_factor = .5 * widget->allocation.height;
3233   break;
3234 
3235  default:
3236   y_scaling_factor = 0;
3237   SG_GraphType = SG_GRAPHTYPE_BEATFREQ;
3238   break;
3239  }
3240 
3241  //Now lays all the points along a running sum along the x axis proportional to the durations:
3242  SG_Voice *curVoice = SG_FirstVoice;
3243 
3244  while (curVoice != NULL)
3245  {
3246   SG_DataPoint *curDP = curVoice->FirstDataPoint;
3247   double currentx = 0;
3248 
3249   do
3250   {
3251    curDP->x = currentx;
3252    currentx += curDP->duration * x_scaling_factor;
3253 
3254    switch (SG_GraphType)
3255    {
3256    case SG_GRAPHTYPE_BEATFREQ:
3257     curDP->y =
3258      widget->allocation.height - (curDP->beatfreq * y_scaling_factor);
3259     break;
3260 
3261    case SG_GRAPHTYPE_BASEFREQ:
3262     curDP->y =
3263      widget->allocation.height - (curDP->basefreq * y_scaling_factor);
3264     break;
3265 
3266    case SG_GRAPHTYPE_VOLUME:
3267     //philosophy here: Y is proportional to the higher of the two channels:
3268     {
3269      double higher =
3270       (curDP->volume_left >
3271        curDP->volume_right) ? curDP->volume_left : curDP->volume_right;
3272      curDP->y = widget->allocation.height - (higher * y_scaling_factor);
3273     }
3274     break;
3275 
3276    case SG_GRAPHTYPE_VOLUME_BALANCE:
3277     //philosophy here: Y is determined by the proportion between the lower
3278     //to the higher of the two channels. Confusingly, if the DP is above half-graph,
3279     //it means that the right channel is the lower than right; and vice-versa.
3280     {
3281      if (curDP->volume_left > curDP->volume_right)
3282      {  // see if left is bigger
3283       curDP->y = y_scaling_factor * curDP->volume_right / curDP->volume_left;
3284       // SG_DBGOUT_FLT("Left Bigger:", (curDP->volume_right / curDP->volume_left));
3285      }
3286      else if (curDP->volume_left < curDP->volume_right)
3287      {  // see if right is bigger:
3288       curDP->y =
3289        y_scaling_factor * (2 - (curDP->volume_left / curDP->volume_right));
3290       // SG_DBGOUT_FLT("Right Bigger:", (curDP->volume_left / curDP->volume_right));
3291      }
3292      else
3293       //logically, they must be equal -- but I don't know if volume was zero:
3294      {
3295       curDP->y = y_scaling_factor;
3296      }
3297     }
3298     break;
3299 
3300    default:
3301     curDP->y = 0;
3302     SG_GraphType = SG_GRAPHTYPE_BEATFREQ;
3303     break;
3304    }
3305 
3306    curDP = curDP->NextDataPoint;
3307   }
3308   while (curDP != NULL);
3309   curVoice = curVoice->NextVoice;
3310  }
3311  SG_GraphHasChanged = TRUE;     //added 20070731
3312 }
3313 
3314 /////////////////////////////////////////////////////
3315 //disregards voice visibility
SG_DeselectDataPoints()3316 void SG_DeselectDataPoints ()
3317 {
3318  SG_Voice *curVoice = SG_FirstVoice;
3319 
3320  while (curVoice != NULL)
3321  {
3322   SG_DataPoint *curDP = curVoice->FirstDataPoint;
3323 
3324   do
3325   {
3326    curDP->state = SG_UNSELECTED;
3327    //all list loops need this:
3328    curDP = curDP->NextDataPoint;
3329   }
3330   while (curDP != NULL);
3331   curVoice = curVoice->NextVoice;
3332  }
3333  SG_GraphHasChanged = TRUE;
3334 }
3335 
3336 /////////////////////////////////////////////////////
3337 //currently ONLY selects VISIBLE voices
SG_SelectDataPoints_All(gboolean select)3338 void SG_SelectDataPoints_All (gboolean select)
3339 {
3340  if (select == FALSE)
3341  {
3342   SG_DeselectDataPoints ();
3343  }
3344  SG_Voice *curVoice = SG_FirstVoice;
3345 
3346  while (curVoice != NULL)
3347  {
3348   if (curVoice->hide == FALSE)
3349   {
3350    SG_DataPoint *curDP = curVoice->FirstDataPoint;
3351 
3352    do
3353    {
3354     curDP->state = SG_SELECTED;
3355     //all list loops need this:
3356     curDP = curDP->NextDataPoint;
3357    }
3358    while (curDP != NULL);
3359   }
3360   curVoice = curVoice->NextVoice;
3361  }
3362  SG_GraphHasChanged = TRUE;
3363 }
3364 
3365 /////////////////////////////////////////////////////
3366 //if select is TRUE, selects all points in given
3367 //voice; otherwise deselects
SG_SelectDataPoints_Voice(SG_Voice * curVoice,gboolean select)3368 void SG_SelectDataPoints_Voice (SG_Voice * curVoice, gboolean select)
3369 {
3370  if (curVoice == NULL)
3371  {
3372   return;
3373  }
3374  SG_DataPoint *curDP = curVoice->FirstDataPoint;
3375 
3376  while (curDP != NULL)
3377  {
3378   curDP->state = ((select == TRUE) ? SG_SELECTED : SG_UNSELECTED);
3379   //all list loops need this:
3380   curDP = curDP->NextDataPoint;
3381  }
3382  SG_GraphHasChanged = TRUE;
3383 }
3384 
3385 /////////////////////////////////////////////////////
3386 //if next is TRUE, selects neighbors to the right; FALSE means left.
3387 //if deselect=TRUE, it deselects the original DataPoints when selecting their neighbors
SG_SelectNeighboringDataPoints(gboolean next,gboolean deselect)3388 void SG_SelectNeighboringDataPoints (gboolean next, gboolean deselect)
3389 {
3390  SG_DataPoint *curDP;
3391  SG_Voice *curVoice = SG_FirstVoice;
3392 
3393  while (curVoice != NULL)
3394  {
3395   if (curVoice->hide == FALSE)
3396   {
3397    curDP = curVoice->FirstDataPoint;
3398 
3399    if (next == FALSE)
3400    {
3401     do
3402     {
3403      if (curDP->state == SG_SELECTED && curDP->PrevDataPoint != NULL)
3404      {
3405       curDP->PrevDataPoint->state = SG_SELECTED;
3406      }
3407      if (deselect == TRUE)
3408       curDP->state = SG_UNSELECTED;
3409      //all list loops need this:
3410      curDP = curDP->NextDataPoint;
3411     }
3412     while (curDP != NULL);
3413    }
3414    else
3415    {
3416     //first find end:
3417     while (curDP->NextDataPoint != NULL)
3418      curDP = curDP->NextDataPoint;
3419     do
3420     {
3421      if (curDP->state == SG_SELECTED && curDP->NextDataPoint != NULL)
3422      {
3423       curDP->NextDataPoint->state = SG_SELECTED;
3424      }
3425      if (deselect == TRUE)
3426       curDP->state = SG_UNSELECTED;
3427      //all list loops need this:  curVoice=curVoice->NextVoice;
3428      curDP = curDP->PrevDataPoint;
3429     }
3430     while (curDP != NULL);
3431    }
3432   }
3433   curVoice = curVoice->NextVoice;
3434  }
3435 }
3436 
3437 /////////////////////////////////////////////////////
3438 // if DeleteTime==FALSE (default) this function adds time from deleted point
3439 //to the previous point. TRUE deletes that time, making it essential
3440 //to call SG_GetScheduleLimits() at some point before
3441 //rendering, etc. (advisable anyway).
SG_DeleteDataPoint(SG_DataPoint * curDP,gboolean DeleteTime)3442 void SG_DeleteDataPoint (SG_DataPoint * curDP, gboolean DeleteTime)
3443 {
3444  if (curDP == NULL)
3445  {
3446   return;
3447  }
3448 
3449  //to be sure we don't end up trying to delete the ONLY point:
3450  if (curDP->NextDataPoint == NULL && curDP->PrevDataPoint == NULL)
3451  {
3452   return;
3453  }
3454 
3455  SG_DataPoint *prevDP = curDP->PrevDataPoint;
3456 
3457  //first check to see if SG_FirstDataPoint is being deleted:
3458  if (prevDP == NULL)
3459  {
3460   //  SG_FirstDataPoint=SG_FirstDataPoint->NextDataPoint;
3461   ((SG_Voice *) curDP->parent)->FirstDataPoint = curDP->NextDataPoint;
3462   ((SG_Voice *) curDP->parent)->FirstDataPoint->PrevDataPoint = NULL;
3463  }
3464  else
3465  {
3466   //sum curDP's duration in to previous DataPoints:
3467   if (DeleteTime == FALSE)
3468    prevDP->duration += curDP->duration;
3469   //Make previous SG_DataPoint now link to the next SG_DataPoint after the one to be deleted:
3470   prevDP->NextDataPoint = curDP->NextDataPoint;
3471   if (curDP->NextDataPoint != NULL)     //i.e., if this point is not the last SG_DataPoint in the llist:
3472   {
3473    //Make NextDataPoint now call the prevDataPoint PrevDataPoint:
3474    curDP->NextDataPoint->PrevDataPoint = prevDP;
3475   }
3476  }
3477  //Assuming I've linked everything remaining properly, can now free the memory:
3478  free (curDP);
3479  //recalibrate everything: [TOO SLOW! Let calling function do this, in case it is calling 8000 points]
3480  // SG_ConvertDataToXY(widget);
3481  // SG_DrawGraph(widget);
3482 }
3483 
3484 /////////////////////////////////////////////////////
3485 //this can delete all except FIRST datapoint.
SG_DeleteDataPoints(GtkWidget * widget,gboolean DeleteTime,gboolean selected_only)3486 void SG_DeleteDataPoints (GtkWidget * widget, gboolean DeleteTime,
3487                           gboolean selected_only)
3488 {
3489  SG_ProgressIndicatorFlag = FALSE;
3490  SG_Voice *curVoice = SG_FirstVoice;
3491 
3492  while (curVoice != NULL)
3493  {
3494   //NOTE: I start with 2nd DP just in case user is deleting all DPs -- in which case
3495   //it would be useful to have the last (undeletable) DP be the original First DP.
3496   SG_DataPoint *curDP = curVoice->FirstDataPoint->NextDataPoint;
3497 
3498   SG_DataPoint *tmpDP;
3499 
3500   while (curDP != NULL)
3501   {
3502    if (selected_only == FALSE ||
3503        (selected_only == TRUE && curDP->state == SG_SELECTED))
3504    {
3505     //since I'm about to obliterate it, I need to advance now:
3506     tmpDP = curDP;
3507     curDP = curDP->NextDataPoint;
3508     SG_DeleteDataPoint (tmpDP, DeleteTime);
3509    }
3510    else
3511     curDP = curDP->NextDataPoint;
3512   }
3513   //get last one:
3514   if (selected_only == FALSE ||
3515       (selected_only == TRUE
3516        && curVoice->FirstDataPoint->state == SG_SELECTED))
3517   {
3518    SG_DeleteDataPoint (curVoice->FirstDataPoint, DeleteTime);
3519   }
3520   //advance to next voice:
3521   curVoice = curVoice->NextVoice;
3522  }
3523 
3524  SG_ConvertDataToXY (widget);
3525  SG_DrawGraph (widget);
3526  //SG_DBGOUT_INT("after SG_ShiftFlag",SG_ShiftFlag);
3527  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
3528 }
3529 
3530 /////////////////////////////////////////////////////
3531 //if select is TRUE, selects datapoints in visible voices
3532 //within a rectangular region of graph; otherwise deselects
SG_SelectDataPoints(int startX,int startY,int endX,int endY,gboolean select)3533 void SG_SelectDataPoints (int startX, int startY, int endX, int endY,
3534                           gboolean select)
3535 {
3536  SG_Voice *curVoice = SG_FirstVoice;
3537 
3538  while (curVoice != NULL)
3539  {
3540   if (curVoice->hide == FALSE)
3541   {
3542    SG_DataPoint *curDP = curVoice->FirstDataPoint;
3543 
3544    do
3545    {
3546     if (curDP->x > startX && curDP->x < endX &&
3547         curDP->y > startY && curDP->y < endY)
3548     {
3549      if (select == TRUE)
3550       curDP->state = SG_SELECTED;
3551      else
3552       curDP->state = SG_UNSELECTED;
3553     }
3554     else
3555     {
3556      //No need to deselect points, because the initial button_press_event handler
3557      //will have already deselected them if SHIFT isn't pressed.
3558      //curDP->state=SG_UNSELECTED;
3559     }
3560     curDP = curDP->NextDataPoint;
3561    }
3562    while (curDP != NULL);
3563   }     //end "is voice visible?"
3564   curVoice = curVoice->NextVoice;
3565  }
3566  SG_GraphHasChanged = TRUE;
3567 }
3568 
3569 /*
3570    /////////////////////////////////////////////////////
3571    gboolean SG_key_release_event (GtkWidget  *widget, GdkEventKey  *event)
3572    {
3573    //if (event->state & GDK_CONTROL_MASK)  SG_DBGOUT_INT("KeyRelease",event->keyval);//g_print ("The control key is pressed\n");
3574    // SG_DBGOUT_INT("KeyRelease",event->keyval);
3575    // SG_ShiftFlag=(event->state & GDK_SHIFT_MASK);
3576    // SG_DBGOUT_INT("Shiftflag release:",event->state & GDK_SHIFT_MASK);
3577    return FALSE;
3578    }
3579  */
3580 
3581 /////////////////////////////////////////////////////
SG_MoveSelectedDataPoints(GtkWidget * widget,double moveX,double moveY)3582 void SG_MoveSelectedDataPoints (GtkWidget * widget, double moveX,
3583                                 double moveY)
3584 {
3585  //these two vars deal with out-of-bounds points:
3586  double YFlagOffset;
3587  double XFlagOffset;
3588  SG_Voice *curVoice;
3589  SG_DataPoint *curDP = NULL;
3590  SG_DataPoint *tempDP;
3591  double tmpmoveX = moveX;
3592  double tmpmoveY = moveY;
3593  gboolean swapflag;
3594 
3595  //Theory: I do two passes through the points if any point in the cluster go out of bounds, using
3596  //whichever point went "most out of bounds" as the reference to "re-move" things back within
3597  //bounds on the second pass.
3598 
3599  //First go through all selected points and increment them according to moveX and moveY,
3600  //keeping clusters from moving past left and bottom edges, but free of top and right:
3601  while (1)
3602  {      //START out-of-bounds feedback loop. Point of this is to determine max inbounds move
3603   YFlagOffset = 0.0;
3604   XFlagOffset = 0.0;
3605   curVoice = SG_FirstVoice;
3606   while (curVoice != NULL)
3607   {
3608    curDP = curVoice->FirstDataPoint;    //there is always a firstDP, correct?
3609    do
3610    {
3611     if (curDP->state == SG_SELECTED)
3612     {
3613      //first do Y:
3614      curDP->y += tmpmoveY;
3615      //Theory here: if DP's new Y is less than zero (i.e., above the graph),
3616      //see if it is currently "the most below zero." if ultimately (after checking all) it is, it becomes
3617      //the reference (limit) for moving all the DPs in the cluster on the required second pass through here:
3618      if (curDP->y < 0.0 && curDP->y < YFlagOffset)
3619      {
3620       YFlagOffset = curDP->y;
3621      }
3622      //now do similarly in checking if DP is below the graph:
3623      else if (curDP->y > widget->allocation.height &&
3624               (curDP->y - widget->allocation.height) > YFlagOffset)
3625      {
3626       YFlagOffset = curDP->y - widget->allocation.height;
3627      }
3628      //now do X:
3629      curDP->x += tmpmoveX;
3630      //be sure first DP hasn't been moved (I am assuming here that after a Ctrl-A, user wants to move everything BUT first DP):
3631      if (curDP->PrevDataPoint == NULL)
3632      {
3633       curDP->x = 0.0;
3634      }
3635      else if (curDP->x < 0.0 && curDP->x < XFlagOffset)
3636      {
3637       XFlagOffset = curDP->x;
3638      }
3639      else if (curDP->x > widget->allocation.width &&
3640               (curDP->x - widget->allocation.width) > XFlagOffset)
3641      {
3642       XFlagOffset = curDP->x - widget->allocation.width;
3643      }
3644     }
3645     curDP = curDP->NextDataPoint;
3646    }
3647    while (curDP != NULL);
3648    //advance to next voice:
3649    curVoice = curVoice->NextVoice;
3650   }
3651 
3652   //Now with that info, use FlagOffsets to do math to re-move clusters to be back within bounds:
3653   //First see if x has gone past left edge of graph, and if so act accordingly:
3654   if (XFlagOffset < 0.0)
3655   {
3656    tmpmoveX = -XFlagOffset;
3657    moveX += tmpmoveX;
3658   }
3659   else
3660    tmpmoveX = 0.0;      //this gives green-light for user to pull points past right-edge
3661 
3662   //Now do Y:
3663   //First deal with graph views in which DPs can be pulled above graph:
3664   if (SG_GraphType == SG_GRAPHTYPE_BASEFREQ ||
3665       SG_GraphType == SG_GRAPHTYPE_BEATFREQ)
3666   {
3667    //check if Y has gone past bottom:
3668    if (YFlagOffset > 0.0)
3669    {
3670     tmpmoveY = -YFlagOffset;
3671     moveY += tmpmoveY;
3672    }
3673    else
3674     tmpmoveY = 0.0;     //this gives the green-light for user to move above graph
3675    //see if we're done here:
3676    if (XFlagOffset >= 0.0 && YFlagOffset <= 0.0)
3677     break;
3678   }
3679   else
3680    //deal with Volume graph views, where user can't go above OR below graph:
3681   {
3682    if (YFlagOffset != 0.0)
3683    {
3684     tmpmoveY = -YFlagOffset;
3685     moveY += tmpmoveY;
3686    }
3687    if (XFlagOffset >= 0.0 && YFlagOffset == 0.0)
3688     break;
3689   }
3690  }      //END out-of-bounds feedback loop
3691 
3692  // ======
3693 
3694  //Now see if the new X passed a neighbor, or went beyond a boundary:
3695  curVoice = SG_FirstVoice;
3696  while (curVoice != NULL)
3697  {      //START of Voices
3698   //assume that there is always a firstDP, and that it can't move:
3699   curDP = curVoice->FirstDataPoint->NextDataPoint;
3700   while (curDP != NULL)
3701   {
3702    if (curDP->state == SG_SELECTED)
3703    {
3704     //Negative moveX means test if point moved past an unselected neighbor to the left:
3705     if (moveX < 0)
3706      do
3707      {
3708       //First try to find an unselected neighbor:
3709       tempDP = curDP;
3710       swapflag = FALSE;
3711       do
3712       {
3713        tempDP = tempDP->PrevDataPoint;
3714        if (tempDP->PrevDataPoint == NULL)
3715         goto SkipToNext;        //first DP, therefore no unselected neighbor to switch with
3716       }
3717       while (tempDP->state == SG_SELECTED);
3718       if (curDP->x < tempDP->x)
3719       {
3720        SG_InsertLink (curDP, tempDP);
3721        swapflag = TRUE;
3722       }
3723      }
3724      while (swapflag == TRUE);
3725 
3726     //Positive moveX means test if point moved past an unselected neighbor to the right:
3727     else if (moveX > 0 && curDP->NextDataPoint != NULL)
3728      do
3729      {  //remember, last DP can't swap with anything to its right)
3730       //First try to find an unselected neighbor:
3731       tempDP = curDP;
3732       swapflag = FALSE;
3733       do
3734       {
3735        tempDP = tempDP->NextDataPoint;
3736        if (tempDP == NULL)
3737         goto SkipToNext;        //no unselected neighbor to switch with
3738       }
3739       while (tempDP->state == SG_SELECTED);
3740       if (curDP->x > tempDP->x)
3741       {
3742        SG_InsertLink (tempDP, curDP);
3743        swapflag = TRUE;
3744       }
3745      }
3746      while (swapflag == TRUE);
3747    }    //END of "is DP selected?"
3748  SkipToNext:
3749    ;
3750    curDP = curDP->NextDataPoint;
3751   }
3752   //advance to next voice:
3753   curVoice = curVoice->NextVoice;
3754  }      //END of Voices
3755 
3756  // ==========
3757 
3758  //Now deal with point dragged past right edge ("Time Stretching"):
3759  if (XFlagOffset > 0.0)
3760  {      //START of "went past right edge of graph" section
3761   //=====
3762   //The most complicated set of possibilities:  user is pulling the
3763   //last DP beyond size of graph. Turns out to be somewhat complex:
3764   //Problem: x and duration get out of sync. I can't know this crosser's original starting point before move,
3765   //which leaves one unknown unselected DP with an invalid duration but a valid x. But I need valid durations
3766   //in order to correctly rescale graph. Thus I need to translate x in to dur for ALL DPs before I can increment
3767   //SG_TotalScheduleDuration (the latter which is what actually causes graph to expand), then finally
3768   //translate all the DP durations back to x's (to correctly draw graph):
3769   //First, x to duration conversion:
3770   double conversion_factor =
3771    SG_TotalScheduleDuration / widget->allocation.width;
3772   curVoice = SG_FirstVoice;
3773   while (curVoice != NULL)
3774   {
3775    tempDP = curVoice->FirstDataPoint;
3776    while (tempDP->NextDataPoint != NULL)
3777    {
3778     tempDP->duration =
3779      (tempDP->NextDataPoint->x - tempDP->x) * conversion_factor;
3780     tempDP = tempDP->NextDataPoint;
3781    }
3782    //do last datapoint:
3783    tempDP->duration = 0.0;      //zero because it reached the edge of the graph
3784    //Now increment SG_TotalScheduleDuration:
3785    //   SG_TotalScheduleDuration+=(curDP->x - widget->allocation.width) *conversion_factor;
3786    curVoice = curVoice->NextVoice;
3787   }
3788 
3789   //Set total schedule duration to rescale graph's X axis:
3790   SG_TotalScheduleDuration += XFlagOffset * conversion_factor;
3791 
3792   //Now, convert them back (dur to x conversion):
3793   //NOTE: New problem here, since multiple points pulled in the same past-edge drag
3794   //(i.e., no button-up) will now be "moved further" for the same x-move than the first crosser,
3795   //because x scale has changed (and subsequent same-move crossers would normally just get the
3796   //original, un-rescaled x move). This apparently ONLY matters for multiple voices, since it
3797   //seems to only affect relationship between last and second-to-last DPs (which gets desynced between voices).
3798   //The most independent solution is just to have calling function check to see if TotalDuration has changed
3799   //in-between DP moves, then rescale the x move accordingly.
3800   conversion_factor = widget->allocation.width / SG_TotalScheduleDuration;
3801   curVoice = SG_FirstVoice;
3802   while (curVoice != NULL)
3803   {
3804    double currentx = 0.0;
3805 
3806    tempDP = curVoice->FirstDataPoint;
3807    //this is necessary because each DP's duration is now out-of-correct proportion with it's X
3808    //Durations are correct, while X has become arbitrary -- so here I make X agree with duration:
3809    do
3810    {
3811     tempDP->x = currentx;
3812     currentx += tempDP->duration * conversion_factor;
3813     tempDP = tempDP->NextDataPoint;
3814    }
3815    while (tempDP != NULL);
3816    //now get new limits (not necessary because I incremented SG_TotalScheduleDuration directly):
3817    //    SG_GetScheduleLimits();
3818    curVoice = curVoice->NextVoice;
3819   }
3820  }      //END of "went past right-edge of graph" section
3821 
3822  // ===============
3823  //Now deal with graph views allowing points pulled above top edge (Rescaling graph to new top Hz):
3824  if (YFlagOffset < 0.0 &&
3825      (SG_GraphType == SG_GRAPHTYPE_BASEFREQ ||
3826       SG_GraphType == SG_GRAPHTYPE_BEATFREQ))
3827  {      //START of "went above top edge of graph" section
3828   //First do Y to Hz translation for SELECTED points according to current graph scale:
3829   double conversion_factor;
3830 
3831   //first figure out conversion factor and new max:
3832   switch (SG_GraphType)
3833   {
3834   case SG_GRAPHTYPE_BEATFREQ:
3835    conversion_factor =
3836     SG_MaxScheduleBeatfrequency / widget->allocation.height;
3837    SG_MaxScheduleBeatfrequency =
3838     (widget->allocation.height - YFlagOffset) * conversion_factor;
3839    break;
3840 
3841   case SG_GRAPHTYPE_BASEFREQ:
3842    conversion_factor =
3843     SG_MaxScheduleBasefrequency / widget->allocation.height;
3844    SG_MaxScheduleBasefrequency =
3845     (widget->allocation.height - YFlagOffset) * conversion_factor;
3846    break;
3847 
3848   default:
3849    SG_GraphType = SG_GRAPHTYPE_BEATFREQ;
3850    conversion_factor =
3851     SG_MaxScheduleBeatfrequency / widget->allocation.height;
3852    SG_MaxScheduleBeatfrequency =
3853     (widget->allocation.height - YFlagOffset) * conversion_factor;
3854    break;
3855   }
3856 
3857   //now go through all selected points and scale them
3858   curVoice = SG_FirstVoice;
3859   while (curVoice != NULL)
3860   {
3861    curDP = curVoice->FirstDataPoint;
3862    do
3863    {
3864     if (curDP->state == SG_SELECTED)
3865     {
3866      switch (SG_GraphType)
3867      {
3868      case SG_GRAPHTYPE_BEATFREQ:
3869       curDP->beatfreq =
3870        (widget->allocation.height - curDP->y) * conversion_factor;
3871       break;
3872 
3873      case SG_GRAPHTYPE_BASEFREQ:
3874       curDP->basefreq =
3875        (widget->allocation.height - curDP->y) * conversion_factor;
3876       break;
3877 
3878      default:
3879       SG_GraphType = SG_GRAPHTYPE_BEATFREQ;
3880       curDP->beatfreq =
3881        (widget->allocation.height - curDP->y) * conversion_factor;
3882       break;
3883      }
3884 
3885     }
3886     curDP = curDP->NextDataPoint;
3887    }
3888    while (curDP != NULL);
3889    curVoice = curVoice->NextVoice;
3890   }
3891 
3892   //now set new conversion factor:
3893   switch (SG_GraphType)
3894   {
3895   case SG_GRAPHTYPE_BEATFREQ:
3896    conversion_factor =
3897     widget->allocation.height / SG_MaxScheduleBeatfrequency;
3898    break;
3899 
3900   case SG_GRAPHTYPE_BASEFREQ:
3901    conversion_factor =
3902     widget->allocation.height / SG_MaxScheduleBasefrequency;
3903    break;
3904 
3905   default:
3906    SG_GraphType = SG_GRAPHTYPE_BEATFREQ;
3907    conversion_factor =
3908     widget->allocation.height / SG_MaxScheduleBeatfrequency;
3909    break;
3910   }
3911 
3912   //Now do Hz to Y translation for ALL points to fit new graph scale:
3913   curVoice = SG_FirstVoice;
3914   while (curVoice != NULL)
3915   {
3916    curDP = curVoice->FirstDataPoint;
3917    do
3918    {
3919     switch (SG_GraphType)
3920     {
3921     case SG_GRAPHTYPE_BEATFREQ:
3922      curDP->y =
3923       widget->allocation.height - (curDP->beatfreq * conversion_factor);
3924      break;
3925 
3926     case SG_GRAPHTYPE_BASEFREQ:
3927      curDP->y =
3928       widget->allocation.height - (curDP->basefreq * conversion_factor);
3929      break;
3930 
3931     default:
3932      SG_GraphType = SG_GRAPHTYPE_BEATFREQ;
3933      curDP->y =
3934       widget->allocation.height - (curDP->beatfreq * conversion_factor);
3935      break;
3936     }
3937     curDP = curDP->NextDataPoint;
3938    }
3939    while (curDP != NULL);
3940    curVoice = curVoice->NextVoice;
3941   }
3942  }      //END of "went above top edge of graph" section
3943 
3944 }       //whew!
3945 
3946 /////////////////////////////////////////////////////
SG_Cleanup()3947 void SG_Cleanup ()
3948 {
3949  //done writing text, so discard this:
3950  if (SG_PangoLayout != NULL)
3951  {
3952   g_object_unref (SG_PangoLayout);
3953  }
3954  if (SG_gc != NULL)
3955  {
3956   g_object_unref (SG_gc);
3957  }
3958  if (SG_pixmap != NULL)
3959  {
3960   g_object_unref (SG_pixmap);
3961  }
3962  SG_pixmap = NULL;
3963  SG_gc = NULL;
3964  SG_PangoLayout = NULL;
3965  // SG_CleanupVoices(SG_FirstBakVoice);
3966  // SG_FirstBakVoice=NULL;
3967  SG_CleanupVoices (SG_FirstVoice);
3968  SG_FirstVoice = NULL;
3969  SG_CleanupBackupData (&SG_CopyPaste);
3970  SG_CleanupBackupData (&SG_UndoRedo);
3971  SG_DBGOUT ("Exiting ScheduleGUI Normally");
3972 }
3973 
3974 /////////////////////////////////////////////////////
3975 //Allots memory for a new SG_Voice and puts it at end of list
3976 //IMPORTANTLY, if you send it a "First SG_Voice", such as
3977 //SG_FirstVoice or SG_FirstBakVoice, you will still need to explicity
3978 //set that variable to the return from this.
3979 //
3980 //NOTE: FirstDataPoint DOES NOT get alloted here (== NULL).
3981 //[Maybe it should be? Decided NO because it doesn't serve paste or init well]
3982 //So you must call SG_AddNewDataPointToEnd() after this before
3983 //using.
3984 //20100625: ID added to try to maintain consistency across restores
SG_AddNewVoiceToEnd(int type,int ID)3985 SG_Voice *SG_AddNewVoiceToEnd (int type, int ID)
3986 {
3987  SG_Voice *lastVoice = NULL;
3988  int count = 0;
3989 
3990  gboolean UseID = TRUE;         //20100625
3991  SG_Voice *curVoice = SG_FirstVoice;    //20100625
3992 
3993  //find end of list and check if ID is valid:
3994  while (curVoice != NULL)
3995  {
3996   if (ID == curVoice->ID)
3997    UseID = FALSE;
3998   count = curVoice->ID + 1;
3999   lastVoice = curVoice;
4000   curVoice = curVoice->NextVoice;
4001  }
4002 
4003  if (TRUE == UseID)
4004  {
4005   count = ID;
4006   //SG_DBGOUT_INT ("Using User ID ", count);
4007  }
4008  else
4009  {
4010   //SG_DBGOUT_INT ("Not using User ID, using ", count);
4011  }
4012 
4013  curVoice = (SG_Voice *) malloc (sizeof (SG_Voice));
4014  curVoice->PrevVoice = lastVoice;
4015  curVoice->type = type; //masks: BB_VOICETYPE_BINAURALBEAT, BB_VOICETYPE_PINKNOISE, BB_VOICETYPE_PCM
4016  curVoice->ID = count;
4017  curVoice->hide = FALSE;
4018  curVoice->mute = FALSE;
4019  curVoice->mono = FALSE;        //[added 20100614]
4020  curVoice->state = SG_UNSELECTED;
4021  curVoice->description = NULL;  //NOTE: calling function can choose to allot a string to this
4022  curVoice->NextVoice = NULL;
4023  curVoice->FirstDataPoint = NULL;       //NOTE: calling function must allot this
4024 
4025  if (lastVoice != NULL)
4026  {
4027   lastVoice->NextVoice = curVoice;
4028  }
4029  return curVoice;
4030 }
4031 
4032 /*
4033    /////////////////////////////////////////////////////
4034    //20070621:
4035    //This allocates, but user must free result on their own.
4036    char *SG_StringAllocateAndCopy (const char *tmpstr)
4037    {
4038    char *dest = NULL;
4039    if (tmpstr != NULL)
4040    {
4041    dest = calloc ((strlen (tmpstr) + 1), 1);
4042    if (dest != NULL)
4043    {
4044    strcpy (dest, tmpstr);
4045    }
4046    }
4047    else
4048    //this kindly returns an empty valid string if tmpstr was equal to NULL:
4049    {
4050    dest = calloc (1, 1);
4051    if (dest != NULL)
4052    {
4053    dest[0] = '\0';
4054    }
4055    }
4056    // SG_DBGOUT_STR ("SG_StringAllocateAndCopy:", dest);
4057    // SG_DBGOUT_PNT ("SG_StringAllocateAndCopy address:", dest);
4058    return dest;
4059    }
4060  */
4061 
4062 /////////////////////////////////////////////////////
4063 //20070621:
4064 //give this the address of a char pointer, ie "&(char * dest)",
4065 //and this fills it with src.
4066 //IMPORTANT: if value of (*dest) is not NULL, this free's it.
4067 //So be sure to NULL it if it is "fresh" (no data)
SG_StringAllocateAndCopy(char ** dest,const char * src)4068 void SG_StringAllocateAndCopy (char **dest, const char *src)
4069 {
4070  if (NULL != (*dest))
4071  {
4072   SG_DBGOUT_STR ("Freeing", (*dest));
4073   free ((*dest));
4074   (*dest) = NULL;
4075  }
4076  if (NULL != src)
4077  {
4078   (*dest) = calloc ((strlen (src) + 1), 1);
4079   if (NULL != (*dest))
4080   {
4081    strcpy ((*dest), src);
4082   }
4083  }
4084  else
4085   //this kindly returns an empty valid string if src was equal to NULL:
4086  {
4087   (*dest) = calloc (1, 1);
4088   if (NULL != (*dest))
4089   {
4090    SG_DBGOUT ("Creating empty string");
4091    (*dest)[0] = '\0';
4092   }
4093  }
4094 }
4095 
4096 /////////////////////////////////////////////////////
4097 //THIS should be called one time at start of program.
4098 //just to put something inside SG. It can, technically,
4099 //be called before SG_Init(), since no calls to a pixmap happen.
4100 /////////////////////////////////////////////////////
SG_SetupDefaultDataPoints(int numberofvoices)4101 void SG_SetupDefaultDataPoints (int numberofvoices)
4102 {
4103  SG_SelectionBox.status = 0;
4104  SG_CleanupBackupData (&SG_CopyPaste);
4105  SG_CleanupBackupData (&SG_UndoRedo);
4106  SG_CleanupVoices (SG_FirstVoice);
4107  SG_FirstVoice = NULL;
4108 
4109  //this section just loads arbitrary crap in ScheduleGUI; needed so that SG is never left empty;
4110  //START arbitrary data creation:
4111  int entry;
4112  SG_Voice *curVoice;
4113  SG_DataPoint *curDP = NULL;
4114  int voice;
4115 
4116  for (voice = 0; voice < numberofvoices; voice++)
4117  {
4118   if (voice == 0)
4119   {
4120    curVoice = SG_AddNewVoiceToEnd (BB_VOICETYPE_BINAURALBEAT, 0);
4121    SG_StringAllocateAndCopy (&(curVoice->description), "Voice 0");
4122   }
4123   else
4124   {
4125    curVoice = SG_AddNewVoiceToEnd (BB_VOICETYPE_PINKNOISE, 0);
4126    SG_StringAllocateAndCopy (&(curVoice->description), "Pink Noise Voice");
4127   }
4128   if (curVoice == NULL)
4129   {
4130    exit (0);
4131   }
4132   if (SG_FirstVoice == NULL)
4133   {
4134    SG_FirstVoice = curVoice;    // this is getting cumbersome -- maybe I should pass address of pointer instead?!
4135   }
4136   for (entry = 0; entry < 1; entry++)
4137   {
4138    if (curVoice->type == BB_VOICETYPE_PINKNOISE)
4139    {
4140     curDP = SG_AddNewDataPointToEnd (curVoice->FirstDataPoint, curVoice, 60,    //duration
4141                                      0, //beatfreq
4142                                      .1,        //volume_left
4143                                      .1,        //volume_right
4144                                      0, //basefreq
4145                                      SG_UNSELECTED);    //state
4146    }
4147    else
4148    {
4149     curDP = SG_AddNewDataPointToEnd (curVoice->FirstDataPoint, curVoice, 60,    //duration
4150                                      1 * (1 + voice),   //beatfreq
4151                                      .5,        //volume_left
4152                                      .5,        //volume_right
4153                                      180 * (1 + voice), //basefreq
4154                                      SG_UNSELECTED);    //state
4155    }
4156    if (curVoice->FirstDataPoint == NULL)
4157     curVoice->FirstDataPoint = curDP;
4158   }
4159  }
4160  //END arbitrary data creation
4161 
4162  //I do this to give SG_MaxScheduleBeatfrequency, SG_MaxScheduleBasefrequency and
4163  //SG_TotalScheduleDuration some reasonable starting
4164  //points (they change whenever size of drawingarea changes):
4165  SG_GetScheduleLimits ();
4166 
4167  //just general housecleaning:
4168  SG_DeselectDataPoints ();
4169  SG_SelectVoice (SG_FirstVoice);
4170 }
4171 
4172 /*
4173    /////////////////////////////////////////////////////
4174    int SG_CountVoices() {
4175    int voicecount = 0;
4176    SG_Voice * curVoice = SG_FirstVoice;
4177    while (curVoice != NULL) {
4178    ++voicecount;
4179    curVoice = curVoice->NextVoice;
4180    }
4181    return voicecount;
4182    }
4183 
4184    /////////////////////////////////////////////////////
4185    //Dumps whatever is in graph to an array readable by SG_SetupDefaultDataPoints();
4186    void SG_DumpGraphToC() {
4187    // int voicecount = SG_CountVoices();
4188    SG_Voice * curVoice = SG_FirstVoice;
4189    while (curVoice != NULL) {
4190    if (curVoice->hide == TRUE) {
4191    SG_DataPoint * curDP = curVoice->FirstDataPoint;
4192    do {
4193    curDP->state = SG_SELECTED;
4194    //all list loops need this:
4195    curDP = curDP->NextDataPoint;
4196    } while (curDP != NULL);
4197    }
4198    curVoice = curVoice->NextVoice;
4199    }
4200    }
4201  */
4202 
4203 /////////////////////////////////////////////////////
4204 //call this first, then perhaps SG_SetupDefaultDataPoints(2)
SG_Init(GdkPixmap * pixmap)4205 void SG_Init (GdkPixmap * pixmap)
4206 {
4207  SG_pixmap = pixmap;
4208  if (SG_FirstVoice == NULL)
4209  {
4210   //I must NULL all these so that they can safely be fed to SG_CleanupBackupData():
4211   SG_CopyPaste.DPdata = NULL;
4212   SG_CopyPaste.Voice = NULL;
4213   SG_UndoRedo.DPdata = NULL;
4214   SG_UndoRedo.Voice = NULL;
4215  }
4216  // SG_SetupDefaultDataPoints(2);
4217 }
4218 
4219 /////////////////////////////////////////////////////
4220 //a20070625:
SG_VoiceCount()4221 int SG_VoiceCount ()
4222 {
4223  int voicecount = 0;
4224  SG_Voice *curVoice = SG_FirstVoice;
4225 
4226  while (curVoice != NULL)
4227  {
4228   ++voicecount;
4229   curVoice = curVoice->NextVoice;
4230  }
4231  return voicecount;
4232 }
4233 
4234 /////////////////////////////////////////////////////
4235 //THIS REALLY ISN'T WORKING SO GOOD
4236 //a20070626 This deselects voices AND their DPs if
4237 //the voices are NOT visible, and also checks if
4238 //a voice is selected but NOT visible. It also checks if
4239 //more than one voice is selected.
SG_VoiceTestLegalSelection()4240 void SG_VoiceTestLegalSelection ()
4241 {
4242  gboolean illegalflag = FALSE;
4243  gboolean VoiceAlreadySelected = FALSE;
4244  SG_Voice *curVoice = SG_FirstVoice;
4245 
4246  while (curVoice != NULL)
4247  {
4248   if (curVoice->hide == TRUE)
4249   {
4250    //deselect this voices DPs:
4251    SG_SelectDataPoints_Voice (curVoice, FALSE);
4252    if (curVoice->state == SG_SELECTED)
4253    {
4254     curVoice->state = SG_UNSELECTED;
4255     illegalflag = TRUE;
4256    }
4257   }
4258   else if (SG_SELECTED == curVoice->state)
4259   {
4260    if (TRUE == VoiceAlreadySelected)
4261    {
4262     curVoice->state = SG_UNSELECTED;
4263    }
4264    else
4265    {
4266     VoiceAlreadySelected = TRUE;
4267    }
4268   }
4269   curVoice = curVoice->NextVoice;
4270  }
4271  if (illegalflag != TRUE)
4272  {
4273   return;
4274  }
4275  //if we got here, need to select any visible voice, which:
4276  //SG_SelectVoice() will do atomatically:
4277  SG_SelectVoice (SG_FirstVoice);
4278  // SG_GraphHasChanged = TRUE;
4279 }
4280 
4281 /////////////////////////////////////////////////////
4282 //ONLY inverts VISIBLE voices
SG_SelectInvertDataPoints_All()4283 void SG_SelectInvertDataPoints_All ()
4284 {
4285  SG_Voice *curVoice = SG_FirstVoice;
4286 
4287  while (curVoice != NULL)
4288  {
4289   if (curVoice->hide == FALSE)
4290   {
4291    SG_SelectInvertDataPoints_Voice (curVoice);
4292   }
4293   curVoice = curVoice->NextVoice;
4294  }
4295 }
4296 
4297 /////////////////////////////////////////////////////
4298 //currently ONLY selects VISIBLE voices
SG_SelectInvertDataPoints_Voice(SG_Voice * curVoice)4299 void SG_SelectInvertDataPoints_Voice (SG_Voice * curVoice)
4300 {
4301  if (curVoice == NULL)
4302  {
4303   return;
4304  }
4305  SG_DataPoint *curDP = curVoice->FirstDataPoint;
4306 
4307  do
4308  {
4309   if (curDP->state == SG_SELECTED)
4310   {
4311    curDP->state = SG_UNSELECTED;
4312   }
4313   else
4314   {
4315    curDP->state = SG_SELECTED;
4316   }
4317   curDP = curDP->NextDataPoint;
4318  }
4319  while (curDP != NULL);
4320  SG_GraphHasChanged = TRUE;
4321 }
4322 
4323 /////////////////////////////////////////////////////
4324 //ONLY inverts VISIBLE voices
SG_SelectIntervalDataPoints_All(int interval,gboolean select,gboolean inverse_on_others)4325 void SG_SelectIntervalDataPoints_All (int interval, gboolean select,
4326                                       gboolean inverse_on_others)
4327 {
4328  SG_Voice *curVoice = SG_FirstVoice;
4329 
4330  while (curVoice != NULL)
4331  {
4332   if (curVoice->hide == FALSE)
4333   {
4334    SG_SelectIntervalDataPoints_Voice (curVoice, interval, select,
4335                                       inverse_on_others);
4336   }
4337   curVoice = curVoice->NextVoice;
4338  }
4339 }
4340 
4341 /////////////////////////////////////////////////////
4342 //currently ONLY selects VISIBLE voices
SG_SelectIntervalDataPoints_Voice(SG_Voice * curVoice,int interval,gboolean select,gboolean inverse_on_others)4343 void SG_SelectIntervalDataPoints_Voice (SG_Voice * curVoice, int interval,
4344                                         gboolean select,
4345                                         gboolean inverse_on_others)
4346 {
4347  if (curVoice == NULL)
4348  {
4349   return;
4350  }
4351 
4352  if (interval < 2)
4353  {
4354   SG_SelectDataPoints_Voice (curVoice, select);
4355   return;
4356  }
4357 
4358  SG_DataPoint *curDP = curVoice->FirstDataPoint;
4359  int count = 0;
4360 
4361  do
4362  {
4363   if (count == 0)
4364   {
4365    curDP->state = (select == TRUE) ? SG_SELECTED : SG_UNSELECTED;
4366   }
4367   else
4368   {
4369    if (inverse_on_others == TRUE)
4370    {
4371     curDP->state = (select == TRUE) ? SG_UNSELECTED : SG_SELECTED;
4372    }
4373   }
4374   ++count;
4375   if (count >= interval)
4376   {
4377    count = 0;
4378   }
4379   //all list loops need this:
4380   curDP = curDP->NextDataPoint;
4381  }
4382  while (curDP != NULL);
4383  SG_GraphHasChanged = TRUE;
4384 }
4385 
4386 /////////////////////////////////////////////////////
4387 //20101008: changed from old type to new.
4388 //NEW: Looks for first and last selected data points in ALL voices
4389 //and aligns all OTHER SELECTED datapoints in that voice between them
4390 //OLD WAY: looked for the first two selected data points in a voice,
4391 //puts ALL between them in line.
SG_AlignDataPoints(GtkWidget * widget)4392 void SG_AlignDataPoints (GtkWidget * widget)
4393 {
4394  double count;                  //holds # of selected DPs to determine slope
4395  double facX;
4396  double facY;
4397 
4398  SG_DataPoint *startDP;
4399  SG_DataPoint *endDP;
4400  SG_DataPoint *innerDP;
4401 
4402  //Go through all voices
4403  SG_Voice *curVoice = SG_FirstVoice;
4404  SG_DataPoint *curDP = NULL;
4405  while (curVoice != NULL)
4406  {
4407   count = 0;
4408   startDP = NULL;
4409   endDP = NULL;
4410   innerDP = NULL;
4411   curDP = curVoice->FirstDataPoint;
4412 
4413   //start going through all DPs in given Voice to
4414   //find each's first and last selected DPs
4415   while (curDP != NULL)
4416   {
4417    if (curDP->state == SG_SELECTED)
4418    {
4419     ++count;
4420     //get first selected DP:
4421     if (NULL == startDP)
4422     {
4423      startDP = curDP;
4424     }
4425     else
4426     {
4427      endDP = curDP;
4428     }
4429    }
4430    curDP = curDP->NextDataPoint;
4431   }
4432   //Done going through DPs looking for first and last selected
4433 
4434   //Now see if we have three or more selected datapoints to play
4435   //with, and if so, align them:
4436   if (2 < count)
4437   {
4438    facX = (endDP->x - startDP->x);
4439    facY = (endDP->y - startDP->y);
4440    innerDP = startDP;
4441    while (NULL != (innerDP = innerDP->NextDataPoint))
4442    {
4443     if (innerDP == endDP)
4444     {
4445      break;
4446     }
4447     if (SG_SELECTED == innerDP->state)
4448     {
4449      innerDP->y =
4450       (startDP->y + ((1.0 - ((endDP->x - innerDP->x) / facX)) * facY));
4451     }
4452    }
4453   }
4454   //FINALLY: do it all again for next voice:
4455   curVoice = curVoice->NextVoice;
4456  }
4457 
4458  //all done, translate the coordinates to graph units and draw:
4459  SG_ConvertYToData_SelectedPoints (widget);
4460  SG_ConvertXToDuration_AllPoints (widget);
4461  SG_DrawGraph (widget);
4462  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
4463 }
4464 
4465 /////////////////////////////////////////////////////
4466 //20100727: Change selected datapoints Y to match mouse pointer Y
4467 //returns !0 if anything got changed
SG_MagneticPointer(GtkWidget * widget,int x,int y)4468 int SG_MagneticPointer (GtkWidget * widget, int x, int y)
4469 {
4470 #define THRESH 3
4471  int xl = x - THRESH;
4472  int xr = x + THRESH;
4473  gboolean flag = FALSE;
4474  SG_DataPoint *curDP;
4475  SG_Voice *curVoice = SG_FirstVoice;
4476  while (NULL != curVoice)
4477  {
4478   if (FALSE == curVoice->hide)
4479   {
4480    curDP = curVoice->FirstDataPoint;
4481    do
4482    {
4483     if (SG_SELECTED == curDP->state)
4484     {
4485      if (xl < curDP->x && xr > curDP->x)
4486      {
4487       if (y != curDP->y)
4488       {
4489        curDP->y = y;
4490        flag = TRUE;
4491       }
4492      }
4493     }
4494     curDP = curDP->NextDataPoint;
4495    }
4496    while (NULL != curDP);
4497   }
4498   curVoice = curVoice->NextVoice;
4499  }
4500  //
4501  if (TRUE == flag)
4502  {
4503   SG_ConvertYToData_SelectedPoints (widget);
4504   SG_GraphHasChanged = SG_DataHasChanged = TRUE;
4505   return 1;
4506  }
4507  return 0;
4508 }
4509 
4510 /////////////////////////////////////////////////////
SG_ScaleDataPoints_Time(GtkWidget * widget,double scalar)4511 void SG_ScaleDataPoints_Time (GtkWidget * widget, double scalar)
4512 {
4513  SG_DataPoint *curDP;
4514  SG_Voice *curVoice = SG_FirstVoice;
4515 
4516  while (curVoice != NULL)
4517  {
4518   if (curVoice->hide == FALSE)
4519   {
4520    curDP = curVoice->FirstDataPoint;
4521    do
4522    {
4523     if (curDP->state == SG_SELECTED)
4524     {
4525      curDP->duration *= scalar;
4526     }
4527     curDP = curDP->NextDataPoint;
4528    }
4529    while (curDP != NULL);
4530   }
4531   curVoice = curVoice->NextVoice;
4532  }
4533  SG_ConvertDataToXY (widget);
4534  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
4535 }
4536 
4537 /////////////////////////////////////////////////////
SG_ScaleDataPoints_Y(GtkWidget * widget,double scalar)4538 void SG_ScaleDataPoints_Y (GtkWidget * widget, double scalar)
4539 {
4540  gboolean scalegraphflag = FALSE;
4541  SG_DataPoint *curDP;
4542  SG_Voice *curVoice = SG_FirstVoice;
4543 
4544  while (curVoice != NULL)
4545  {
4546   if (curVoice->hide == FALSE)
4547   {
4548    curDP = curVoice->FirstDataPoint;
4549    do
4550    {
4551     if (curDP->state == SG_SELECTED)
4552     {
4553      curDP->y =
4554       widget->allocation.height -
4555       ((widget->allocation.height - curDP->y) * scalar);
4556      if (curDP->y > widget->allocation.height)
4557      {
4558       curDP->y = widget->allocation.height;
4559      }
4560      if (curDP->y < 0)
4561      {
4562       scalegraphflag = TRUE;
4563      }
4564     }
4565     curDP = curDP->NextDataPoint;
4566    }
4567    while (curDP != NULL);
4568   }
4569   curVoice = curVoice->NextVoice;
4570  }
4571  SG_ConvertYToData_SelectedPoints (widget);
4572  if (scalegraphflag == TRUE)
4573  {
4574   SG_ConvertDataToXY (widget);
4575  }
4576  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
4577 }
4578 
4579 /////////////////////////////////////////////////////
4580 //adds value (or random number between +/- limit) to
4581 //y axis and/or duration of selected datapoints:
SG_AddToDataPoints(GtkWidget * widget,double limit,gboolean x_flag,gboolean y_flag,gboolean rand_flag)4582 void SG_AddToDataPoints (GtkWidget * widget, double limit,
4583                          gboolean x_flag, gboolean y_flag, gboolean rand_flag)
4584 {
4585  gboolean scalegraphflag = FALSE;
4586  SG_DataPoint *curDP;
4587  SG_Voice *curVoice = SG_FirstVoice;
4588 
4589  while (curVoice != NULL)
4590  {
4591   if (curVoice->hide == FALSE)
4592   {
4593    curDP = curVoice->FirstDataPoint;
4594    do
4595    {
4596     if (curDP->state == SG_SELECTED)
4597     {
4598      if (y_flag == TRUE)
4599      {
4600       curDP->y += (TRUE == rand_flag) ? (limit * BB_Rand_pm ()) : -limit;
4601       if (curDP->y > widget->allocation.height)
4602       {
4603        curDP->y = widget->allocation.height;
4604       }
4605       if (curDP->y < 0)
4606       {
4607        scalegraphflag = TRUE;
4608       }
4609      }
4610 
4611      //reminder: you can't just change X, you have to change durations:
4612      if (x_flag == TRUE)
4613      {
4614       curDP->duration +=
4615        (TRUE == rand_flag) ? (limit * BB_Rand_pm ()) : limit;
4616       if (curDP->duration < 0)
4617       {
4618        curDP->duration = 0;
4619       }
4620      }
4621     }
4622     curDP = curDP->NextDataPoint;
4623    }
4624    while (curDP != NULL);
4625   }
4626   curVoice = curVoice->NextVoice;
4627  }
4628 
4629  if (y_flag == TRUE)
4630  {
4631   SG_ConvertYToData_SelectedPoints (widget);
4632  }
4633  if (scalegraphflag == TRUE || x_flag == TRUE)
4634  {
4635   SG_ConvertDataToXY (widget);
4636  }
4637  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
4638 }
4639 
4640 ///////////////////////////////////
4641 //20070722:
SG_DuplicateSelectedVoice()4642 void SG_DuplicateSelectedVoice ()
4643 {
4644  SG_Voice *srcVoice = SG_SelectVoice (NULL);
4645 
4646  if (srcVoice == NULL)
4647  {
4648   return;
4649  }
4650  SG_Voice *destVoice = SG_AddNewVoiceToEnd (srcVoice->type, 0);
4651 
4652  SG_StringAllocateAndCopy (&(destVoice->description), srcVoice->description);
4653  SG_DataPoint *srcDP = srcVoice->FirstDataPoint;
4654  SG_DataPoint *destDP = destVoice->FirstDataPoint;
4655  destVoice->mute = srcVoice->mute;      //[20100616]
4656  destVoice->mono = srcVoice->mono;      //[20100616]
4657 
4658  do
4659  {
4660   destDP = SG_AddNewDataPointToEnd (destVoice->FirstDataPoint, destVoice, srcDP->duration,      //duration
4661                                     srcDP->beatfreq,    //beatfreq
4662                                     srcDP->volume_left, //volume_left
4663                                     srcDP->volume_right,        //volume_right
4664                                     srcDP->basefreq,    //basefreq
4665                                     srcDP->state);      //state
4666   srcDP = srcDP->NextDataPoint;
4667   destDP = destDP->NextDataPoint;
4668  }
4669  while (srcDP != NULL);
4670  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
4671 }
4672 
4673 ///////////////////////////////////
4674 //20070724: selects last DP in all visible voices
SG_SelectLastDP_All()4675 void SG_SelectLastDP_All ()
4676 {
4677  SG_Voice *curVoice = SG_FirstVoice;
4678 
4679  while (curVoice != NULL)
4680  {
4681   if (curVoice->hide == FALSE)
4682   {
4683    SG_SelectLastDP (curVoice);
4684   }
4685   curVoice = curVoice->NextVoice;
4686  }
4687 }
4688 
4689 ///////////////////////////////////
4690 //20070724: selects last DP in given voice
SG_SelectLastDP(SG_Voice * curVoice)4691 void SG_SelectLastDP (SG_Voice * curVoice)
4692 {
4693  if (curVoice == NULL)
4694  {
4695   return;
4696  }
4697  SG_DataPoint *curDP = curVoice->FirstDataPoint;
4698 
4699  while (curDP->NextDataPoint != NULL)
4700  {
4701   //advance to next point:
4702   curDP = curDP->NextDataPoint;
4703  }
4704  curDP->state = SG_SELECTED;
4705  SG_GraphHasChanged = TRUE;
4706 }
4707 
4708 ///////////////////////////////////
4709 //20070920: selects first DP in visible voices
SG_SelectFirstDP_All()4710 void SG_SelectFirstDP_All ()
4711 {
4712  SG_Voice *curVoice = SG_FirstVoice;
4713 
4714  while (curVoice != NULL)
4715  {
4716   if (curVoice->hide == FALSE)
4717   {
4718    curVoice->FirstDataPoint->state = SG_SELECTED;
4719   }
4720   curVoice = curVoice->NextVoice;
4721  }
4722  SG_GraphHasChanged = TRUE;
4723 }
4724 
4725 ///////////////////////////////////
4726 //20070724:
SG_ReverseVoice(GtkWidget * widget)4727 void SG_ReverseVoice (GtkWidget * widget)
4728 {
4729  SG_Voice *curVoice = SG_SelectVoice (NULL);
4730 
4731  if (curVoice == NULL || curVoice->FirstDataPoint == NULL ||
4732      curVoice->FirstDataPoint->NextDataPoint == NULL)
4733  {
4734   return;
4735  }
4736 
4737  //Backup points:
4738  // SG_BackupDataPoints (widget);
4739 
4740  int dpcount = 0;
4741  int seldpcount = 0;
4742 
4743  SG_CountVoiceDPs (curVoice, &dpcount, &seldpcount);
4744 
4745  //first set x values:
4746  SG_DataPoint *curDP = curVoice->FirstDataPoint->NextDataPoint;
4747 
4748  while (curDP != NULL)
4749  {
4750   curDP->x = widget->allocation.width - curDP->x;
4751   //advance to next point:
4752   curDP = curDP->NextDataPoint;
4753  }
4754 
4755  //Now find end:
4756  SG_DataPoint *endDP = curVoice->FirstDataPoint;
4757 
4758  while (endDP->NextDataPoint != NULL)
4759  {
4760   if (curDP != curVoice->FirstDataPoint)
4761   {
4762    //advance to next point:
4763    endDP = endDP->NextDataPoint;
4764   }
4765  }
4766 
4767  //now go forwards and backwards:
4768  SG_DataPoint *nextcurDP;
4769  SG_DataPoint *nextendDP;
4770 
4771  curDP = curVoice->FirstDataPoint->NextDataPoint;
4772  int i;
4773 
4774  for (i = 0; i < ((dpcount - 1) >> 1); i++)
4775  {
4776   nextcurDP = curDP->NextDataPoint;
4777   nextendDP = endDP->PrevDataPoint;
4778   SG_SwapLinks (curDP, endDP);
4779   //advance to next point:
4780   curDP = nextcurDP;
4781   endDP = nextendDP;
4782  }
4783 
4784  SG_ConvertXToDuration_AllPoints (widget);
4785  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
4786 }
4787 
4788 ///////////////////////////////////
4789 //20070724: Cuts (or grows) a schedule to an exact entime
SG_TruncateSchedule(GtkWidget * widget,double endtime)4790 void SG_TruncateSchedule (GtkWidget * widget, double endtime)
4791 {
4792  SG_Voice *curVoice = SG_FirstVoice;
4793  gboolean cutflag;
4794 
4795  while (curVoice != NULL)
4796  {
4797   cutflag = FALSE;
4798   SG_DataPoint *lastDP;
4799   SG_DataPoint *curDP = curVoice->FirstDataPoint;
4800   double currentduration = 0;
4801 
4802   do
4803   {
4804    lastDP = curDP;
4805    currentduration += curDP->duration;
4806    if (currentduration >= endtime)
4807    {
4808     cutflag = TRUE;
4809     break;
4810    }
4811    curDP = curDP->NextDataPoint;
4812   }
4813   while (curDP != NULL);
4814 
4815   if (cutflag == TRUE)
4816   {
4817    lastDP->duration -= (currentduration - endtime);
4818    //delete all the rest of the DPs:
4819    curDP = lastDP->NextDataPoint;
4820    lastDP->NextDataPoint = NULL;
4821    while (curDP != NULL)
4822    {
4823     //since I'm about to obliterate it, I need to advance now:
4824     lastDP = curDP;
4825     curDP = curDP->NextDataPoint;
4826     SG_DeleteDataPoint (lastDP, TRUE);
4827    }
4828   }
4829   else
4830   {
4831    //lengthen the last DP to fit schedule
4832    lastDP->duration += (endtime - currentduration);
4833   }
4834   curVoice = curVoice->NextVoice;
4835  }
4836  //SG_GetScheduleLimits();
4837  SG_ConvertDataToXY (widget);
4838  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
4839 }
4840 
4841 ////////////////////////////////////////
SG_GetLeftmostSelectedDP()4842 SG_DataPoint *SG_GetLeftmostSelectedDP ()
4843 {
4844  SG_DataPoint *curDP,
4845  *leftmostDP = NULL;
4846  SG_Voice *curVoice = SG_FirstVoice;
4847 
4848  while (curVoice != NULL)
4849  {
4850   if (curVoice->hide == FALSE)
4851   {
4852    curDP = curVoice->FirstDataPoint;
4853    while (curDP->NextDataPoint != NULL)
4854    {
4855     if (curDP->state == SG_SELECTED)
4856     {
4857      if (leftmostDP == NULL || curDP->x < leftmostDP->x)
4858      {
4859       leftmostDP = curDP;
4860       break;
4861      }
4862     }
4863     //advance to next point:
4864     curDP = curDP->NextDataPoint;
4865    }
4866   }
4867   curVoice = curVoice->NextVoice;
4868  }
4869  return leftmostDP;
4870 }
4871 
4872 ////////////////////////////////////////
4873 //This works on the principle that pasted points will be the
4874 //only selected points, then moves them to the end
SG_PasteDataPointsAtEnd(GtkWidget * widget)4875 void SG_PasteDataPointsAtEnd (GtkWidget * widget)
4876 {
4877  if (SG_CopyPaste.DPdata == NULL || SG_CopyPaste.TotalDataPoints < 1)
4878  {
4879   return;
4880  }
4881  SG_PasteSelectedDataPoints (widget, TRUE);
4882  //find "leftmost selected point":
4883  SG_DataPoint *leftmostDP = SG_GetLeftmostSelectedDP ();
4884 
4885  if (leftmostDP == NULL)
4886  {
4887   return;
4888  }
4889  //move all points distance leftmost DP is from right end of graph:
4890  SG_MoveSelectedDataPoints (widget, widget->allocation.width - leftmostDP->x,
4891                             0);
4892  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
4893 }
4894 
4895 ////////////////////////////////////////
SG_InvertY(GtkWidget * widget)4896 void SG_InvertY (GtkWidget * widget)
4897 {
4898  SG_DataPoint *curDP;
4899  SG_Voice *curVoice = SG_FirstVoice;
4900 
4901  while (curVoice != NULL)
4902  {
4903   if (curVoice->hide == FALSE)
4904   {
4905    curDP = curVoice->FirstDataPoint;
4906    do
4907    {
4908     if (curDP->state == SG_SELECTED)
4909     {
4910      curDP->y = widget->allocation.height - curDP->y;
4911     }
4912     curDP = curDP->NextDataPoint;
4913    }
4914    while (curDP != NULL);
4915   }
4916   curVoice = curVoice->NextVoice;
4917  }
4918  SG_ConvertYToData_SelectedPoints (widget);
4919  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
4920 }
4921 
4922 ////////////////////////////////////////
4923 //if duration is positive, looks for all visible DPs with
4924 //durations >= duration; if negative, for all <= -duration.
SG_SelectDuration(GtkWidget * widget,double duration)4925 void SG_SelectDuration (GtkWidget * widget, double duration)
4926 {
4927  SG_DataPoint *curDP;
4928  SG_Voice *curVoice = SG_FirstVoice;
4929 
4930  while (curVoice != NULL)
4931  {
4932   if (curVoice->hide == FALSE)
4933   {
4934    curDP = curVoice->FirstDataPoint;
4935    do
4936    {
4937     if (duration >= 0)
4938     {
4939      if (curDP->duration >= duration)
4940      {
4941       curDP->state = SG_SELECTED;
4942      }
4943     }
4944     else if (curDP->duration <= (-duration))
4945     {
4946      curDP->state = SG_SELECTED;
4947     }
4948     curDP = curDP->NextDataPoint;
4949    }
4950    while (curDP != NULL);
4951   }
4952   curVoice = curVoice->NextVoice;
4953  }
4954  SG_ConvertYToData_SelectedPoints (widget);
4955  SG_GraphHasChanged = TRUE;
4956 }
4957 
4958 /////////////////////////////////////////////////////
4959 //selects all data points that are within a given proximity
4960 //to each other.
4961 //this is actually a hellaciously complex problem, if I
4962 //really feel like implementing it right. Brute Force:
SG_SelectProximity_All(double threshold)4963 void SG_SelectProximity_All (double threshold)
4964 {
4965  SG_DataPoint *curDP;
4966  SG_Voice *curVoice = SG_FirstVoice;
4967 
4968  while (curVoice != NULL)
4969  {
4970   if (curVoice->hide == FALSE)
4971   {
4972    curDP = curVoice->FirstDataPoint;
4973    do
4974    {
4975     //###start inner loop
4976     SG_SelectProximity_SingleDP (curDP, threshold);
4977     //###end inner loop
4978     curDP = curDP->NextDataPoint;
4979    }
4980    while (curDP != NULL);
4981   }
4982   curVoice = curVoice->NextVoice;
4983  }
4984 }
4985 
4986 /////////////////////////////////////////////////////
4987 //this takes one DP and selects all DPs within it's vicinty
SG_SelectProximity_SingleDP(SG_DataPoint * innerDP,double threshold)4988 void SG_SelectProximity_SingleDP (SG_DataPoint * innerDP, double threshold)
4989 {
4990  SG_DataPoint *curDP;
4991  SG_Voice *curVoice = SG_FirstVoice;
4992 
4993  while (curVoice != NULL)
4994  {
4995   if (curVoice->hide == FALSE)
4996   {
4997    curDP = curVoice->FirstDataPoint;
4998    do
4999    {
5000     if (innerDP != curDP)
5001     {
5002      if (sqrt ((curDP->x - innerDP->x) *
5003                (curDP->x - innerDP->x) +
5004                (curDP->y - innerDP->y) *
5005                (curDP->y - innerDP->y)) <= threshold)
5006      {
5007       curDP->state = SG_SELECTED;
5008       innerDP->state = SG_SELECTED;
5009       SG_GraphHasChanged = TRUE;
5010      }
5011     }
5012     //###end inner loop
5013     curDP = curDP->NextDataPoint;
5014    }
5015    while (curDP != NULL);
5016   }
5017   curVoice = curVoice->NextVoice;
5018  }
5019 }
5020 
5021 /////////////////////////////////////////////////////
5022 //rounds to "roundingval":  1=1th, 10=10th, 100=100th
5023 //parameter can be one of:
5024 //0: duration
5025 //1: volume_left
5026 //2: volume_right
5027 //3: basefreq
5028 //4: beatfreq
SG_RoundValues_Voice(GtkWidget * widget,SG_Voice * curVoice,double roundingval,int parameter)5029 void SG_RoundValues_Voice (GtkWidget * widget,
5030                            SG_Voice * curVoice,
5031                            double roundingval, int parameter)
5032 {
5033  if (curVoice == NULL || 0 >= roundingval)
5034  {
5035   return;
5036  }
5037  double *proxyvar;
5038  int val;
5039  SG_DataPoint *curDP = curVoice->FirstDataPoint;
5040 
5041  do
5042  {
5043   if (SG_SELECTED == curDP->state)
5044   {
5045    switch (parameter)
5046    {
5047    case 0:
5048     proxyvar = &curDP->duration;
5049     break;
5050 
5051    case 1:
5052     proxyvar = &curDP->volume_left;
5053     break;
5054 
5055    case 2:
5056     proxyvar = &curDP->volume_right;
5057     break;
5058 
5059    case 3:
5060     proxyvar = &curDP->basefreq;
5061     break;
5062 
5063    case 4:
5064     proxyvar = &curDP->beatfreq;
5065     break;
5066 
5067    default:
5068     return;
5069    }
5070 
5071    val = (int) (*proxyvar * roundingval + .5);
5072    *proxyvar = (double) (val / roundingval);
5073    if (*proxyvar < 0)
5074     *proxyvar = 0;
5075   }
5076   //all list loops need this:
5077   curDP = curDP->NextDataPoint;
5078  }
5079  while (curDP != NULL);
5080  SG_ConvertDataToXY (widget);
5081  SG_GraphHasChanged = SG_DataHasChanged = TRUE;
5082 }
5083 
5084 ///////////////////////////////////
5085 //rounds to "roundingval":  1=1th, 10=10th, 100=100th
5086 //parameter can be one of:
5087 //0: duration
5088 //1: volume_left
5089 //2: volume_right
5090 //3: basefreq
5091 //4: beatfreq
SG_RoundValues_All(GtkWidget * widget,double roundingval,int parameter)5092 void SG_RoundValues_All (GtkWidget * widget,
5093                          double roundingval, int parameter)
5094 {
5095  SG_Voice *curVoice = SG_FirstVoice;
5096 
5097  while (NULL != curVoice)
5098  {
5099   if (curVoice->hide == FALSE)
5100   {
5101    SG_RoundValues_Voice (widget, curVoice, roundingval, parameter);
5102   }
5103   curVoice = curVoice->NextVoice;
5104  }
5105 }
5106