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