1 /*
2 * Copyright (C) 2002 - David W. Durham
3 *
4 * This file is part of ReZound, an audio editing application.
5 *
6 * ReZound is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 2 of the License,
9 * or (at your option) any later version.
10 *
11 * ReZound is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 */
20
21 #include "FXGraphParamValue.h"
22
23 #include <exception>
24 #include <stdexcept>
25 #include <string>
26 #include <algorithm>
27
28 #include <istring>
29
30 #include <CNestedDataFile/CNestedDataFile.h>
31
32 #include "utils.h"
33
34 #include "../backend/CSound.h"
35
36 #include "settings.h"
37
38 #include "CFOXIcons.h"
39
40 #define NODE_RADIUS 4
41
42 /* TODO:
43 *
44 * - I probably need to limit the minimum size as it could cause divide by zero errors, and really be unusable anyway
45 *
46 * - I would have liked to reuse code (without copy/pasting) from FXRezWaveView, but it's currently
47 * too much tied to CLoadedSound for start and stop positions and such...
48 * - At least I pulled draw portion out not to have to rewrite that
49 * - but it would be nice if all the scrolling and zooming capabilities where also there....
50 *
51 * - I should also onMouseMove print the time that the cursor is over
52 * - clear it onExit
53 *
54 * - draw a vertical to the time and a horizontal line to the ruler while dragging the nodes
55 *
56 * - still a glitch with the drawing of the bottom tick in the ruler.. I might draw more values in the ticks
57 * I think I've fixed this now
58 *
59 * - I might ought to draw the play position in there if I can get the event
60 *
61 * - I might want to avoid redrawing everything. Alternatively I could:
62 * - blit only the sections between the nodes involving the change
63 * I think I do now ???
64 *
65 */
66
67
68 // --- declaration of CHorzRuler -----------------------------------------------
69
70 class CHorzRuler : public FXHorizontalFrame
71 {
72 FXDECLARE(CHorzRuler)
73 public:
CHorzRuler(FXComposite * p,FXGraphParamValue * _parent)74 CHorzRuler(FXComposite *p,FXGraphParamValue *_parent) :
75 FXHorizontalFrame(p,FRAME_NONE | LAYOUT_FILL_X | LAYOUT_FIX_HEIGHT | LAYOUT_SIDE_TOP, 0,0,0,17),
76
77 parent(_parent),
78
79 font(getApp()->getNormalFont())
80 {
81 enable();
82 flags|=FLAG_SHOWN; // I have to do this, or it will not show up.. like height is 0 or something
83
84 FXFontDesc d;
85 font->getFontDesc(d);
86 d.weight=FONTWEIGHT_LIGHT;
87 d.size=65;
88 font=new FXFont(getApp(),d);
89 }
90
~CHorzRuler()91 virtual ~CHorzRuler()
92 {
93 delete font;
94 }
95
96
create()97 virtual void create()
98 {
99 FXHorizontalFrame::create();
100 font->create();
101 }
102
103 // events I get by message
104
onPaint(FXObject * object,FXSelector sel,void * ptr)105 long onPaint(FXObject *object,FXSelector sel,void *ptr)
106 {
107 FXHorizontalFrame::onPaint(object,sel,ptr);
108
109 FXEvent *ev=(FXEvent*)ptr;
110
111 FXDCWindow dc(this,ev);
112 dc.setForeground(FXRGB(20,20,20));
113 dc.compat_setFont(font);
114
115 #define H_TICK_FREQ 50
116
117 // N is the number of ticks to label
118 int N=(parent->getGraphPanelWidth()/H_TICK_FREQ); // we lable always an odd number of ticks so there is always a middle tick
119 if(N%2==0)
120 N++;
121
122 // make sure there's at least 3
123 if(N<3)
124 N=3;
125
126 const int s=0;
127 const int e=parent->getGraphPanelWidth()-1;
128
129 // x actually goes from the event's min-10 to max+10 incase part of some text needs be be redrawn (and we limit it to 0..height)
130 const int maxX=min(parent->getGraphPanelWidth(),(ev->rect.w+ev->rect.x)+10);
131 for(int x=max(0,(ev->rect.x)-10);x<=maxX;x++)
132 {
133 // this is, y=s+(((e-s)*t)/(N-1)) [0..N interpolated across s..e] solved for t, and then we get the remainder of the resulting division instead of the actual quotient)
134 const double t=fmod((double)((x-s)*(N-1)),(double)(e-s));
135 const int renderX=x+parent->vertRuler->getWidth();
136
137 if(t<(N-1))
138 { // draw and label this tick
139 dc.drawLine(renderX,getHeight()-2,renderX,getHeight()-10);
140 //dc.drawLine(renderX+H_TICK_FREQ/2,getHeight()-2,renderX+H_TICK_FREQ/2,getHeight()-4);
141
142 const string sValue=parent->getHorzValueString(parent->screenToNodeHorzValue(x,false));
143
144 int offset=font->getTextWidth(sValue.c_str(),sValue.length()); // put text left of the tick mark
145
146 dc.drawText(renderX-offset-1,font->getFontHeight()+2,sValue.c_str(),sValue.length());
147 }
148 // else, every 2 ticks (i.e. in between labled ticks)
149 //dc.drawLine(getWidth()-2,renderY,getWidth()-5,renderY); // half way tick between labled ticks
150 }
151
152 return 0;
153 }
154
155 protected:
CHorzRuler()156 CHorzRuler() {}
157
158 private:
159 FXGraphParamValue *parent;
160
161 FXFont *font;
162
163 };
164
165 FXDEFMAP(CHorzRuler) CHorzRulerMap[]=
166 {
167 //Message_Type ID Message_Handler
168 FXMAPFUNC(SEL_PAINT, 0, CHorzRuler::onPaint),
169 };
170
171 FXIMPLEMENT(CHorzRuler,FXHorizontalFrame,CHorzRulerMap,ARRAYNUMBER(CHorzRulerMap))
172
173
174
175 // --- declaration of CVertRuler -----------------------------------------------
176
177 class CVertRuler : public FXHorizontalFrame
178 {
179 FXDECLARE(CVertRuler)
180 public:
CVertRuler(FXComposite * p,FXGraphParamValue * _parent)181 CVertRuler(FXComposite *p,FXGraphParamValue *_parent) :
182 FXHorizontalFrame(p,FRAME_NONE | LAYOUT_FILL_Y | LAYOUT_FIX_WIDTH | LAYOUT_SIDE_LEFT, 0,0,42),
183
184 parent(_parent),
185
186 font(getApp()->getNormalFont())
187 {
188 enable();
189 flags|=FLAG_SHOWN; // I have to do this, or it will not show up.. like height is 0 or something
190
191 FXFontDesc d;
192 font->getFontDesc(d);
193 d.weight=FONTWEIGHT_LIGHT;
194 d.size=65;
195 font=new FXFont(getApp(),d);
196 }
197
~CVertRuler()198 virtual ~CVertRuler()
199 {
200 delete font;
201 }
202
create()203 virtual void create()
204 {
205 FXHorizontalFrame::create();
206 font->create();
207 }
208
209 // events I get by message
210
onPaint(FXObject * object,FXSelector sel,void * ptr)211 long onPaint(FXObject *object,FXSelector sel,void *ptr)
212 {
213 FXHorizontalFrame::onPaint(object,sel,ptr);
214
215 FXEvent *ev=(FXEvent*)ptr;
216
217 FXDCWindow dc(this,ev);
218 dc.setForeground(FXRGB(20,20,20));
219 dc.compat_setFont(font);
220
221 #define V_TICK_FREQ 20
222
223 // N is the number of ticks to label
224 int N=(parent->getGraphPanelHeight()/V_TICK_FREQ); // we lable always an odd number of ticks so there is always a middle tick
225 if(N%2==0)
226 N++;
227
228 // make sure there's at least 3
229 if(N<3)
230 N=3;
231
232 const int s=0;
233 const int e=parent->getGraphPanelHeight()-1;
234
235 // y actually goes from the event's min-10 to max+10 incase part of some text needs be be redrawn (and we limit it to 0..height)
236 const int maxY=min(parent->getGraphPanelHeight(),(ev->rect.h+ev->rect.y)+10);
237 for(int y=max(0,(ev->rect.y)-10);y<=maxY;y++)
238 {
239 // this is, y=s+(((e-s)*t)/(N-1)) [0..N interpolated across s..e] solved for t, and then we get the remainder of the resulting division instead of the actual quotient)
240 const double t=fmod((double)((y-s)*(N-1)),(double)(e-s));
241 const int renderY=y+(((FXPacker *)(parent->graphCanvas->getParent()))->getPadTop());
242
243 if(t<(N-1))
244 { // draw and label this tick
245 dc.drawLine(getWidth()-2,renderY,getWidth()-10,renderY);
246
247 const string s=parent->getVertValueString(parent->screenToNodeVertValue(y,false));
248
249 int yoffset=font->getFontHeight(); // put text below the tick mark
250 int xoffset=font->getTextWidth(s.c_str(),s.length());
251 dc.drawText(getWidth()-xoffset-7,renderY+yoffset,s.c_str(),s.length());
252 }
253 // else, every 2 ticks (i.e. in between labled ticks)
254 //dc.drawLine(getWidth()-2,renderY,getWidth()-5,renderY); // half way tick between labled ticks
255 }
256
257 return 0;
258 }
259
260 protected:
CVertRuler()261 CVertRuler() {}
262
263 private:
264 FXGraphParamValue *parent;
265
266 FXFont *font;
267
268 };
269
270 FXDEFMAP(CVertRuler) CVertRulerMap[]=
271 {
272 //Message_Type ID Message_Handler
273 FXMAPFUNC(SEL_PAINT, 0, CVertRuler::onPaint),
274 };
275
276 FXIMPLEMENT(CVertRuler,FXHorizontalFrame,CVertRulerMap,ARRAYNUMBER(CVertRulerMap))
277
278
279
280 // -----------------------------------------------
281
282 FXDEFMAP(FXGraphParamValue) FXGraphParamValueMap[]=
283 {
284 //Message_Type ID Message_Handler
285
286 FXMAPFUNC(SEL_PAINT, FXGraphParamValue::ID_GRAPH_CANVAS, FXGraphParamValue::onGraphPanelPaint),
287
288 FXMAPFUNC(SEL_CONFIGURE, FXGraphParamValue::ID_GRAPH_CANVAS, FXGraphParamValue::onGraphPanelResize),
289
290 FXMAPFUNC(SEL_LEFTBUTTONPRESS, FXGraphParamValue::ID_GRAPH_CANVAS, FXGraphParamValue::onCreateOrStartDragNode),
291 FXMAPFUNC(SEL_MOTION, FXGraphParamValue::ID_GRAPH_CANVAS, FXGraphParamValue::onDragNode),
292 FXMAPFUNC(SEL_LEFTBUTTONRELEASE, FXGraphParamValue::ID_GRAPH_CANVAS, FXGraphParamValue::onStopDragNode),
293 FXMAPFUNC(SEL_RIGHTBUTTONRELEASE, FXGraphParamValue::ID_GRAPH_CANVAS, FXGraphParamValue::onDestroyNode),
294
295 FXMAPFUNC(SEL_COMMAND, FXGraphParamValue::ID_SCALAR_SPINNER, FXGraphParamValue::onScalarSpinnerChange),
296
297 FXMAPFUNC(SEL_COMMAND, FXGraphParamValue::ID_CLEAR_BUTTON, FXGraphParamValue::onPatternButton),
298 FXMAPFUNC(SEL_COMMAND, FXGraphParamValue::ID_HORZ_FLIP_BUTTON, FXGraphParamValue::onPatternButton),
299 FXMAPFUNC(SEL_COMMAND, FXGraphParamValue::ID_VERT_FLIP_BUTTON, FXGraphParamValue::onPatternButton),
300 FXMAPFUNC(SEL_COMMAND, FXGraphParamValue::ID_SMOOTH_BUTTON, FXGraphParamValue::onPatternButton),
301
302 FXMAPFUNC(SEL_CHANGED, FXGraphParamValue::ID_HORZ_DEFORM_SLIDER,FXGraphParamValue::onHorzDeformSliderChange),
303 FXMAPFUNC(SEL_RIGHTBUTTONRELEASE, FXGraphParamValue::ID_HORZ_DEFORM_SLIDER,FXGraphParamValue::onHorzDeformSliderReset),
304 FXMAPFUNC(SEL_CHANGED, FXGraphParamValue::ID_VERT_DEFORM_SLIDER,FXGraphParamValue::onVertDeformSliderChange),
305 FXMAPFUNC(SEL_RIGHTBUTTONRELEASE, FXGraphParamValue::ID_VERT_DEFORM_SLIDER,FXGraphParamValue::onVertDeformSliderReset),
306 };
307
FXIMPLEMENT(FXGraphParamValue,FXVerticalFrame,FXGraphParamValueMap,ARRAYNUMBER (FXGraphParamValueMap))308 FXIMPLEMENT(FXGraphParamValue,FXVerticalFrame,FXGraphParamValueMap,ARRAYNUMBER(FXGraphParamValueMap))
309
310 FXGraphParamValue::FXGraphParamValue(const char *_name,FXComposite *p,int opts,int x,int y,int w,int h) :
311 FXVerticalFrame(p,opts|FRAME_RAISED,x,y,w,h, 0,0,0,0, 0,0),
312
313 name(_name),
314
315 graphPanel(new FXPacker(this,FRAME_NONE | LAYOUT_FILL_X|LAYOUT_FILL_Y, 0,0,0,0, 0,0,0,0, 0,0)),
316 vertDeformPanel(new FXVerticalFrame(graphPanel,FRAME_NONE | LAYOUT_SIDE_RIGHT|LAYOUT_FILL_Y, 0,0,0,0, 2,2,0,0, 0,0)),
317 vertDeformSlider(new FXSlider(vertDeformPanel,this,ID_VERT_DEFORM_SLIDER, FRAME_NONE | LAYOUT_FILL_Y | SLIDER_VERTICAL|SLIDER_TICKS_LEFT|SLIDER_ARROW_LEFT, 0,0,0,0, 0,0,0,0)),
318 horzRuler(new CHorzRuler(graphPanel,this)),
319 vertRuler(new CVertRuler(graphPanel,this)),
320 horzDeformPanel(new FXHorizontalFrame(graphPanel,FRAME_NONE | LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X, 0,0,0,0, 0,0,2,0, 0,0)),
321 horzDeformSlider(new FXSlider(horzDeformPanel,this,ID_HORZ_DEFORM_SLIDER, FRAME_NONE | LAYOUT_FILL_X | SLIDER_HORIZONTAL|SLIDER_TICKS_TOP|SLIDER_ARROW_UP,0,0,0,0, 0,0,0,0)),
322 graphCanvas(new FXCanvas(graphPanel,this,ID_GRAPH_CANVAS,LAYOUT_FILL_X|LAYOUT_FILL_Y)),
323
324 statusPanel(new FXHorizontalFrame(this,FRAME_NONE | LAYOUT_FILL_X, 0,0,0,0, 4,4,0,0, 4,0)),
325 horzValueLabel(new FXLabel(statusPanel,": ",NULL,LAYOUT_LEFT)),
326 vertValueLabel(new FXLabel(statusPanel,": ",NULL,LAYOUT_LEFT)),
327 pointCountLabel(new FXLabel(statusPanel,"",NULL,LAYOUT_RIGHT)),
328
329 buttonPanel(new FXHorizontalFrame(this,FRAME_NONE | LAYOUT_FILL_X, 0,0,0,0, 4,4,2,4)),
330 scalarLabel(NULL),
331 scalarSpinner(NULL),
332
333 draggingNode(-1),
334 dragOffsetX(0),dragOffsetY(0),
335
336 sound(NULL),
337 start(0),
338 stop(0),
339
340 horzAxisLabel(""),
341 horzUnits(""),
342 horzValueMapper(NULL),
343
344 vertAxisLabel(""),
345 vertUnits(""),
346 vertValueMapper(NULL),
347
348 backBuffer(new FXImage(getApp())),
349
350 textFont(getApp()->getNormalFont()),
351
352 minWidth(500),
353 minHeight(300)
354 {
355 // create a smaller font to use
356 FXFontDesc d;
357 textFont->getFontDesc(d);
358 d.size-=10;
359 textFont=new FXFont(getApp(),d);
360
361
362 const int deformSliderRange=4000;
363
364 horzDeformSlider->setRange(-deformSliderRange,deformSliderRange); // symmetry is assumed when the slider is used (and horz and very should be the same
365 #if FOX_MAJOR>=1
366 horzDeformSlider->setTickDelta((deformSliderRange*2+1)/(11-1)); // 11 tick marks
367 #endif
368 horzDeformSlider->setValue(0);
369 horzDeformSlider->setTipText("Horizontally Deform the Nodes Toward One Side or the Other\nRight-Click to Reset to the Middle");
370
371 vertDeformSlider->setRange(-deformSliderRange,deformSliderRange); // symmetry is assumed when the slider is used (and horz and very should be the same
372 #if FOX_MAJOR>=1
373 vertDeformSlider->setTickDelta((deformSliderRange*2+1)/(11-1)); // 11 tick marks
374 #endif
375 vertDeformSlider->setValue(0);
376 vertDeformSlider->setTipText("Vertically Deform the Nodes Toward the Top or Bottom\nRight-Click to Reset to the Middle");
377
378
379
380 new FXButton(buttonPanel,(string("\t")+_("Clear to Normal")).c_str(),FOXIcons->graph_clear,this,ID_CLEAR_BUTTON);
381 new FXButton(buttonPanel,(string("\t")+_("Flip Graph Horizontally")).c_str(),FOXIcons->graph_horz_flip,this,ID_HORZ_FLIP_BUTTON);
382 new FXButton(buttonPanel,(string("\t")+_("Flip Graph Vertically")).c_str(),FOXIcons->graph_vert_flip,this,ID_VERT_FLIP_BUTTON);
383 new FXButton(buttonPanel,(string("\t")+_("Smooth Graph Nodes")).c_str(),FOXIcons->graph_smooth,this,ID_SMOOTH_BUTTON);
384
385 nodes.reserve(100);
386
387 backBuffer->create();
388
389 clearNodes();
390
391 setFontOfAllChildren(buttonPanel,textFont);
392 setFontOfAllChildren(statusPanel,textFont);
393 }
394
~FXGraphParamValue()395 FXGraphParamValue::~FXGraphParamValue()
396 {
397 delete textFont;
398 }
399
400 // return the default w & h of this widget
getDefaultWidth()401 FXint FXGraphParamValue::getDefaultWidth() { return minWidth; }
getDefaultHeight()402 FXint FXGraphParamValue::getDefaultHeight() { return minHeight; }
403
setMinSize(FXint _minWidth,FXint _minHeight)404 void FXGraphParamValue::setMinSize(FXint _minWidth,FXint _minHeight)
405 {
406 minWidth=_minWidth;
407 minHeight=_minHeight;
408 }
409
setSound(CSound * _sound,sample_pos_t _start,sample_pos_t _stop)410 void FXGraphParamValue::setSound(CSound *_sound,sample_pos_t _start,sample_pos_t _stop)
411 {
412 sound=_sound;
413 start=_start;
414 stop=_stop;
415
416 horzAxisLabel="Time";
417 horzUnits="s";
418
419 // make sure that the back buffer re-renders
420 onGraphPanelResize(NULL,0,NULL);
421
422 clearStatus();
423 }
424
setHorzParameters(const string horzAxisLabel,const string horzUnits,AActionParamMapper * _horzValueMapper)425 void FXGraphParamValue::setHorzParameters(const string horzAxisLabel,const string horzUnits,AActionParamMapper *_horzValueMapper)
426 {
427 this->horzAxisLabel=horzAxisLabel;
428 this->horzUnits=horzUnits;
429 horzValueMapper=_horzValueMapper;
430
431 clearStatus();
432 updateNumbers();
433 }
434
setVertParameters(const string vertAxisLabel,const string vertUnits,AActionParamMapper * _vertValueMapper)435 void FXGraphParamValue::setVertParameters(const string vertAxisLabel,const string vertUnits,AActionParamMapper *_vertValueMapper)
436 {
437 this->vertAxisLabel=vertAxisLabel;
438 this->vertUnits=vertUnits;
439 vertValueMapper=_vertValueMapper;
440
441 delete scalarLabel;
442 delete scalarSpinner;
443 if(vertValueMapper->getMinScalar()!=vertValueMapper->getMaxScalar())
444 {
445 scalarLabel=new FXLabel(buttonPanel,_("Scalar"),NULL,LABEL_NORMAL|LAYOUT_CENTER_Y);
446 scalarSpinner=new FXSpinner(buttonPanel,5,this,ID_SCALAR_SPINNER,SPIN_NORMAL|FRAME_SUNKEN|FRAME_THICK|LAYOUT_CENTER_Y);
447 scalarSpinner->setRange(vertValueMapper->getMinScalar(),vertValueMapper->getMaxScalar());
448 scalarSpinner->setValue(vertValueMapper->getDefaultScalar());
449 }
450
451 clearStatus();
452 updateNumbers();
453 }
454
clearNodes()455 void FXGraphParamValue::clearNodes()
456 {
457 nodes.clear();
458
459 // add endpoints
460 CGraphParamValueNode first(0.0,vertValueMapper==NULL ? 0.5 : vertValueMapper->uninterpretValue(vertValueMapper->getDefaultValue()));
461 nodes.push_back(first);
462
463 CGraphParamValueNode last(1.0,vertValueMapper==NULL ? 0.5 : vertValueMapper->uninterpretValue(vertValueMapper->getDefaultValue()));
464 nodes.push_back(last);
465
466 graphCanvas->update();
467 }
468
469
onScalarSpinnerChange(FXObject * sender,FXSelector sel,void * ptr)470 long FXGraphParamValue::onScalarSpinnerChange(FXObject *sender,FXSelector sel,void *ptr)
471 {
472 vertValueMapper->setScalar(scalarSpinner->getValue());
473 scalarSpinner->setValue(vertValueMapper->getScalar());
474 updateNumbers();
475 return 1;
476 }
477
onPatternButton(FXObject * sender,FXSelector sel,void * ptr)478 long FXGraphParamValue::onPatternButton(FXObject *sender,FXSelector sel,void *ptr)
479 {
480 switch(FXSELID(sel))
481 {
482 case ID_CLEAR_BUTTON:
483 horzDeformSlider->setValue(0);
484 vertDeformSlider->setValue(0);
485 clearNodes();
486 break;
487
488 case ID_HORZ_FLIP_BUTTON:
489 horzDeformSlider->setValue(-horzDeformSlider->getValue());
490 nodes=flipGraphNodesHorizontally(nodes);
491 break;
492
493 case ID_VERT_FLIP_BUTTON:
494 vertDeformSlider->setValue(-vertDeformSlider->getValue());
495 nodes=flipGraphNodesVertically(nodes);
496 break;
497
498 case ID_SMOOTH_BUTTON:
499 nodes=smoothGraphNodes(nodes);
500 break;
501
502 // ??? need a button that can change bandpass to notch
503
504 }
505
506 graphCanvas->update();
507 updateNumbers();
508 return 1;
509 }
510
onHorzDeformSliderChange(FXObject * sender,FXSelector sel,void * ptr)511 long FXGraphParamValue::onHorzDeformSliderChange(FXObject *sender,FXSelector sel,void *ptr)
512 {
513 graphCanvas->update();
514 return 1;
515 }
516
onVertDeformSliderChange(FXObject * sender,FXSelector sel,void * ptr)517 long FXGraphParamValue::onVertDeformSliderChange(FXObject *sender,FXSelector sel,void *ptr)
518 {
519 graphCanvas->update();
520 return 1;
521 }
522
onHorzDeformSliderReset(FXObject * sender,FXSelector sel,void * ptr)523 long FXGraphParamValue::onHorzDeformSliderReset(FXObject *sender,FXSelector sel,void *ptr)
524 {
525 horzDeformSlider->setValue(0);
526 graphCanvas->update();
527 return 1;
528 }
529
onVertDeformSliderReset(FXObject * sender,FXSelector sel,void * ptr)530 long FXGraphParamValue::onVertDeformSliderReset(FXObject *sender,FXSelector sel,void *ptr)
531 {
532 vertDeformSlider->setValue(0);
533 graphCanvas->update();
534 return 1;
535 }
536
537
onGraphPanelPaint(FXObject * sender,FXSelector sel,void * ptr)538 long FXGraphParamValue::onGraphPanelPaint(FXObject *sender,FXSelector sel,void *ptr)
539 {
540 FXDCWindow dc(graphCanvas);
541
542 // draw the whole background
543 dc.drawImage(backBuffer,0,0);
544
545 // draw the connection between the nodes
546 dc.setForeground(FXRGB(255,64,64));
547 //if(0)
548 { // draw lines
549 for(size_t t=1;t<nodes.size();t++)
550 {
551 const CGraphParamValueNode &n1=nodes[t-1];
552 const CGraphParamValueNode &n2=nodes[t];
553
554 const int x1=nodeToScreenX(n1);
555 const int y1=nodeToScreenY(n1);
556 const int x2=nodeToScreenX(n2);
557 const int y2=nodeToScreenY(n2);
558
559 dc.drawLine(x1,y1,x2,y2);
560 }
561 }
562 /*
563 Okay, here i have coded cubic and cosine based curves to connect the dots instead of or in addition to
564 the straight line segments. However, after playing around with with them a few issues arise.
565 1) Cosine always has a horizontal tangent at each node
566 2) Cubic curves have a couple of problems.
567 - The top or bottom of a local maximum can extend above or below the screen
568 - It's hard to control the concavity of each direction change by adding nodes without affecting the shape
569 A better overall solution may be to have a 'smooth' button for the line segments or to use bezier curves (except they
570 don't pass thru the control points
571
572 else
573 { // draw a cubic curve (http://astronomy.swin.edu.au/~pbourke/analysis/interpolation/index.html)
574 int prevX=nodeToScreenX(nodes[0]);
575 int prevY=nodeToScreenY(nodes[0]);
576 for(int t=1;t<(int)nodes.size();t++)
577 {
578 const CGraphParamValueNode &n0=nodes[max(0,t-2)];
579 const CGraphParamValueNode &n1=nodes[max(0,t-1)];
580 const CGraphParamValueNode &n2=nodes[t];
581 const CGraphParamValueNode &n3=nodes[min((int)nodes.size()-1,t+1)];
582
583 //const int x0=nodeToScreenX(n0);
584 const int y0=nodeToScreenY(n0);
585 const int x1=nodeToScreenX(n1);
586 const int y1=nodeToScreenY(n1);
587 const int x2=nodeToScreenX(n2);
588 const int y2=nodeToScreenY(n2);
589 //const int x3=nodeToScreenX(n3);
590 const int y3=nodeToScreenY(n3);
591
592 for(int x=x1;x<x2;x++)
593 {
594 const float mu=(float)(x-x1)/(float)(x2-x1);
595 const float mu2=mu*mu;
596 const float a0=y3-y2-y0+y1;
597 const float a1=y0-y1-a0;
598 const float a2=y2-y0;
599 const float a3=y1;
600
601 const float y=a0*mu*mu2+a1*mu2+a2*mu+a3;
602
603 dc.drawLine(prevX,prevY,x,(int)y);
604 prevX=x;
605 prevY=(int)y;
606 }
607
608 }
609 }
610 else
611 { // draw a cosine interpolated curve (http://astronomy.swin.edu.au/~pbourke/analysis/interpolation/index.html)
612 int prevX=nodeToScreenX(nodes[0]);
613 int prevY=nodeToScreenY(nodes[0]);
614 for(size_t t=1;t<nodes.size();t++)
615 {
616 const CGraphParamValueNode &n1=nodes[t-1];
617 const CGraphParamValueNode &n2=nodes[t];
618
619 const int x1=nodeToScreenX(n1);
620 const int y1=nodeToScreenY(n1);
621 const int x2=nodeToScreenX(n2);
622 const int y2=nodeToScreenY(n2);
623
624 for(int x=x1;x<x2;x++)
625 {
626 const float mu=(float)(x-x1)/(float)(x2-x1);
627 const float mu2=(1.0-cos(mu*M_PI))/2.0;
628
629 const float y=y1*(1.0-mu2)+y2*mu2;
630
631 dc.drawLine(prevX,prevY,x,(int)y);
632 prevX=x;
633 prevY=(int)y;
634 }
635 }
636 }
637 */
638
639 // draw the nodes
640 for(size_t t=0;t<nodes.size();t++)
641 {
642 CGraphParamValueNode &n=nodes[t];
643
644 const int x=nodeToScreenX(n);
645 const int y=nodeToScreenY(n);
646
647 if(t==0 || t==nodes.size()-1)
648 dc.setForeground(FXRGB(0,191,255)); // end point node
649 else
650 dc.setForeground(FXRGB(0,255,0));
651
652 dc.fillArc(x-NODE_RADIUS,y-NODE_RADIUS,NODE_RADIUS*2,NODE_RADIUS*2,0*64,360*64);
653 }
654
655 return 1;
656 }
657
658 #include "drawPortion.h"
onGraphPanelResize(FXObject * sender,FXSelector sel,void * ptr)659 long FXGraphParamValue::onGraphPanelResize(FXObject *sender,FXSelector sel,void *ptr)
660 {
661 // render waveform to the backbuffer
662 backBuffer->resize(graphCanvas->getWidth(),graphCanvas->getHeight());
663 FXDCWindow dc(backBuffer);
664 if(sound!=NULL)
665 {
666 const int canvasWidth=graphCanvas->getWidth();
667 const int canvasHeight=graphCanvas->getHeight();
668 const sample_pos_t length=stop-start+1;
669 const double hScalar=(double)((sample_fpos_t)length/canvasWidth);
670 const int hOffset=(int)(start/hScalar);
671 const float vScalar=MAX_WAVE_HEIGHT/(float)canvasHeight;
672
673 drawPortion(0,canvasWidth,&dc,sound,canvasWidth,canvasHeight,-1,-1,hScalar,hOffset,vScalar,0,true);
674 }
675 else
676 {
677 dc.setForeground(FXRGB(0,0,0));
678 dc.setFillStyle(FILL_SOLID);
679 dc.fillRectangle(0,0,graphCanvas->getWidth(),graphCanvas->getHeight());
680 }
681
682 // make the vertical deform slider's top and bottom appear at the top and bottom of the graph canvas
683 int totalPad=graphCanvas->getParent()->getHeight()-graphCanvas->getHeight();
684 vertDeformPanel->setPadTop(graphCanvas->getY());
685 vertDeformPanel->setPadBottom(totalPad-graphCanvas->getY());
686
687 return 1;
688 }
689
690 // always returns an odd value >=1
getGraphPanelWidth() const691 int FXGraphParamValue::getGraphPanelWidth() const
692 {
693 int w=graphCanvas->getWidth();
694 if(w%2==0)
695 w--;
696 return max(w,1);
697 }
698
699 // always returns an odd value >=1
getGraphPanelHeight() const700 int FXGraphParamValue::getGraphPanelHeight() const
701 {
702 int h=graphCanvas->getHeight();
703 if(h%2==0)
704 h--;
705 return max(h,1);
706 }
707
insertIntoNodes(const CGraphParamValueNode & node)708 int FXGraphParamValue::insertIntoNodes(const CGraphParamValueNode &node)
709 {
710 // sanity checks
711 if(nodes.size()<2)
712 throw runtime_error(string(__func__)+" -- nodes doesn't already contain at least 2 items");
713 if(node.x<0.0 || node.x>1.0)
714 throw runtime_error(string(__func__)+" -- node's x is out of range: "+istring(node.x));
715
716 int insertedIndex=-1;
717
718 // - basically, this function inserts the node into sorted order first by the nodes' postions
719
720 // - insert into the first position to keep the node's position in non-desreasing order
721 for(size_t t1=1;t1<nodes.size();t1++)
722 {
723 CGraphParamValueNode &temp=nodes[t1];
724 if(temp.x>=node.x)
725 {
726 nodes.insert(nodes.begin()+t1,node);
727 insertedIndex=t1;
728 break;
729 }
730 }
731
732 return insertedIndex;
733 }
734
onCreateOrStartDragNode(FXObject * sender,FXSelector sel,void * ptr)735 long FXGraphParamValue::onCreateOrStartDragNode(FXObject *sender,FXSelector sel,void *ptr)
736 {
737 FXEvent *ev=(FXEvent *)ptr;
738
739 int nodeIndex=findNodeAt(ev->win_x,ev->win_y);
740 if(nodeIndex==-1)
741 { // create a new node
742 CGraphParamValueNode node(screenToNodeHorzValue(ev->win_x),screenToNodeVertValue(ev->win_y));
743 draggingNode=insertIntoNodes(node);
744
745 dragOffsetX=0;
746 dragOffsetY=0;
747 }
748 else
749 {
750 dragOffsetX=ev->win_x-nodeToScreenX(nodes[nodeIndex]);
751 dragOffsetY=ev->win_y-nodeToScreenY(nodes[nodeIndex]);
752 draggingNode=nodeIndex;
753 }
754
755 updateStatus();
756 graphCanvas->update();
757 return 1;
758 }
759
onDragNode(FXObject * sender,FXSelector sel,void * ptr)760 long FXGraphParamValue::onDragNode(FXObject *sender,FXSelector sel,void *ptr)
761 {
762 if(draggingNode!=-1)
763 {
764 FXEvent *ev=(FXEvent *)ptr;
765
766 bool lockX=(draggingNode==0 || draggingNode==(int)nodes.size()-1) ? true : false; // lock X for endpoints
767 bool lockY=false;
768
769 // if shift or control is held, lock the x or y position
770 if(ev->state&SHIFTMASK)
771 lockX=true;
772 else if(ev->state&CONTROLMASK)
773 lockY=true;
774
775
776 CGraphParamValueNode node=nodes[draggingNode];
777
778 // calculate the new positions
779 node.x= lockX ? node.x : screenToNodeHorzValue(ev->win_x-dragOffsetX);
780 node.y= lockY ? node.y : screenToNodeVertValue(ev->win_y-dragOffsetY);
781
782 // update the node within the vector
783 if(draggingNode!=0 && draggingNode!=(int)nodes.size()-1)
784 { // not an end point
785 // re-insert the node so that the nodes vector will be in the proper order
786 nodes.erase(nodes.begin()+draggingNode);
787 draggingNode=insertIntoNodes(node);
788 }
789 else
790 nodes[draggingNode]=node;
791
792 // redraw
793 updateStatus();
794 graphCanvas->update();
795 }
796 return 0;
797 }
798
onStopDragNode(FXObject * sender,FXSelector sel,void * ptr)799 long FXGraphParamValue::onStopDragNode(FXObject *sender,FXSelector sel,void *ptr)
800 {
801 draggingNode=-1;
802 clearStatus();
803 return 1;
804 }
805
onDestroyNode(FXObject * sender,FXSelector sel,void * ptr)806 long FXGraphParamValue::onDestroyNode(FXObject *sender,FXSelector sel,void *ptr)
807 {
808 FXEvent *ev=(FXEvent *)ptr;
809 int nodeIndex=findNodeAt(ev->win_x,ev->win_y);
810 if(nodeIndex!=-1 && nodeIndex!=0 && nodeIndex!=(int)(nodes.size()-1)) // not the first or the last node
811 {
812 nodes.erase(nodes.begin()+nodeIndex);
813 draggingNode=-1;
814 graphCanvas->update();
815 updateNumbers();
816 }
817 return 1;
818 }
819
findNodeAt(int x,int y)820 int FXGraphParamValue::findNodeAt(int x,int y)
821 {
822 for(size_t t=0;t<nodes.size();t++)
823 {
824 CGraphParamValueNode &n=nodes[t];
825
826 const int nx=nodeToScreenX(n);
827 const int ny=nodeToScreenY(n);
828
829 // see if the node's position is <= the NODE_RADIUS from the give x,y position
830 if( ((nx-x)*(nx-x)+(ny-y)*(ny-y)) <= (NODE_RADIUS*NODE_RADIUS) )
831 return t;
832 }
833
834 return -1;
835 }
836
deformNodeX(double x,FXint sliderValue) const837 double FXGraphParamValue::deformNodeX(double x,FXint sliderValue) const
838 {
839 /*
840 This deformation takes x:[0,1] and translates it to a deformation of x yet still with the range of [0,1]
841 The deformation is to plug x into one quadrant of the unit circle given by the graph of f(x)=cos(asin(x)) where x:[0,1]
842 Then I use the rule that cos(asin(x)) == sqrt(1-x^2)
843 So, f(x) = sqrt(1-x^2)
844
845 Then I let the slider define a variable, p, that adjusts how much from a straight line to a more and more taught curve
846 to deform x by. This comes from generalizing the f(x) above to:
847 f(x) = ( 1-x^p ) ^ (1/p) (that is, p takes the place of '2' in the previous f(x))
848
849 So, now p can go from 1 (where f(x) produces the line, y=x) and upwards where elbow of the curve is more and more and more bent toward (1,0)
850
851 So, the x coord of the node plugged into this f(x) where s==1 does no deforming and where s is greater, the x is more bent in one direction
852
853 So the slider going from [-N,N] is scaled to s: [-1,1] and when s>=0 I let s define p as p=(K*s)+1
854 But when s<0 I let be defined as p=(K*(-s))+1, but I flip the f(x) equation horizontally and vertically so that the bend tends toward (0,1)
855 This way, the slider deforms the nodes' x values leftwards or rightwards depending on the slider's value being - or +
856
857 And as a side note: I square s (but maintain the sign) so that more values in the middle will be available but s is will [-1,1]
858
859 */
860
861 FXint lo,hi; // assuming symmetry on low and hi and vert and horz are the same
862 horzDeformSlider->getRange(lo,hi);
863
864 // s=[-1,1]
865 double s=(double)sliderValue/(double)hi;
866 s=fabs(s)*s;
867
868 if(s>=0)
869 {
870 double p=(10.0*s)+1.0;
871 return pow( 1.0-pow(1.0-x,p) , 1.0/p );
872 }
873 else
874 {
875 double p=(10.0*(-s))+1.0;
876 return 1.0-pow( 1.0-pow(x,p) , 1.0/p );
877 }
878
879 }
880
undeformNodeX(double x,FXint sliderValue) const881 double FXGraphParamValue::undeformNodeX(double x,FXint sliderValue) const
882 {
883 /* this computes the inverse of the previous deformNodeX method above */
884 /* which simplifies to just calling deformNodeX with the negated sliderValue */
885 return deformNodeX(x,-sliderValue);
886 }
887
nodeToScreenX(const CGraphParamValueNode & node)888 int FXGraphParamValue::nodeToScreenX(const CGraphParamValueNode &node)
889 {
890 return (int)(deformNodeX(node.x,horzDeformSlider->getValue())*(graphCanvas->getWidth()-1));
891 }
892
nodeToScreenY(const CGraphParamValueNode & node)893 int FXGraphParamValue::nodeToScreenY(const CGraphParamValueNode &node)
894 {
895 return (int)((1.0-deformNodeX(node.y,vertDeformSlider->getValue()))*(getGraphPanelHeight()));
896 }
897
screenToNodeHorzValue(int x,bool undeform)898 double FXGraphParamValue::screenToNodeHorzValue(int x,bool undeform)
899 {
900 double v=((double)x/(double)(graphCanvas->getWidth()-1));
901 if(v<0.0)
902 v=0.0;
903 else if(v>1.0)
904 v=1.0;
905
906 if(undeform)
907 return undeformNodeX(v,horzDeformSlider->getValue());
908 else
909 return v;
910 }
911
screenToNodeVertValue(int y,bool undeform)912 double FXGraphParamValue::screenToNodeVertValue(int y,bool undeform)
913 {
914 double v=(1.0-((double)y/(double)(getGraphPanelHeight()-1)));
915 if(v<0.0)
916 v=0.0;
917 else if(v>1.0)
918 v=1.0;
919
920 if(undeform)
921 return undeformNodeX(v,vertDeformSlider->getValue());
922 else
923 return v;
924 }
925
updateNumbers()926 void FXGraphParamValue::updateNumbers()
927 {
928 horzRuler->update();
929 vertRuler->update();
930 updateStatus();
931 }
932
getNodes() const933 const CGraphParamValueNodeList &FXGraphParamValue::getNodes() const
934 {
935 retNodes=nodes;
936 for(size_t t=0;t<retNodes.size();t++)
937 {
938 retNodes[t].x=deformNodeX(retNodes[t].x,horzDeformSlider->getValue());
939 retNodes[t].y=deformNodeX(retNodes[t].y,vertDeformSlider->getValue());
940
941 if(horzValueMapper!=NULL)
942 retNodes[t].x=horzValueMapper->interpretValue(retNodes[t].x);
943 if(vertValueMapper!=NULL)
944 retNodes[t].y=vertValueMapper->interpretValue(retNodes[t].y);
945 }
946
947 return retNodes;
948 }
949
950 /*
951 * only copies nodes and deformation parameters and scalars, but not interpret functions and such
952 */
copyFrom(const FXGraphParamValue * src)953 void FXGraphParamValue::copyFrom(const FXGraphParamValue *src)
954 {
955 // make undo stack copy when implemented ???
956
957 // copy deformation values
958 horzDeformSlider->setValue(src->horzDeformSlider->getValue());
959 vertDeformSlider->setValue(src->vertDeformSlider->getValue());
960
961 // copy scalar values
962 setScalar(src->getScalar());
963
964 // copy nodes
965 nodes.clear();
966 for(size_t t=0;t<src->nodes.size();t++)
967 nodes.push_back(src->nodes[t]);
968
969 // cause everything to refresh
970 updateNumbers();
971 update();
972 graphCanvas->update();
973 }
974
swapWith(FXGraphParamValue * src)975 void FXGraphParamValue::swapWith(FXGraphParamValue *src)
976 {
977 int t;
978
979 // make undo stack copy (for both) when implemented ???
980
981 // swap deformation values
982 t=horzDeformSlider->getValue();
983 horzDeformSlider->setValue(src->horzDeformSlider->getValue());
984 src->horzDeformSlider->setValue(t);
985
986 t=vertDeformSlider->getValue();
987 vertDeformSlider->setValue(src->vertDeformSlider->getValue());
988 src->vertDeformSlider->setValue(t);
989
990
991 // copy scalar values
992 t=getScalar();
993 setScalar(src->getScalar());
994 src->setScalar(t);
995
996 // copy nodes
997 swap(nodes,src->nodes);
998
999 // cause everything to refresh
1000 updateNumbers();
1001 update();
1002 graphCanvas->update();
1003
1004 src->updateNumbers();
1005 src->update();
1006 src->graphCanvas->update();
1007 }
1008
getVertValueString(double vertValue) const1009 const string FXGraphParamValue::getVertValueString(double vertValue) const
1010 {
1011 return istring(vertValueMapper->interpretValue(vertValue),5,3);
1012 }
1013
getHorzValueString(double horzValue) const1014 const string FXGraphParamValue::getHorzValueString(double horzValue) const
1015 {
1016 if(sound==NULL)
1017 return istring(horzValueMapper->interpretValue(horzValue),5,3);
1018 else
1019 return sound->getTimePosition((sample_pos_t)((sample_fpos_t)(stop-start+1)*horzValue+start),3,false);
1020 }
1021
1022
updateStatus()1023 void FXGraphParamValue::updateStatus()
1024 {
1025 pointCountLabel->setText((istring(nodes.size())+" "+_("points")).c_str());
1026
1027 if(draggingNode==-1)
1028 return;
1029
1030 const CGraphParamValueNode &n=nodes[draggingNode];
1031
1032 // draw status
1033 horzValueLabel->setText((horzAxisLabel+": "+getHorzValueString(deformNodeX(n.x,horzDeformSlider->getValue()))+horzUnits).c_str());
1034 vertValueLabel->setText((vertAxisLabel+": "+getVertValueString(deformNodeX(n.y,vertDeformSlider->getValue()))+vertUnits).c_str());
1035 }
1036
clearStatus()1037 void FXGraphParamValue::clearStatus()
1038 {
1039 horzValueLabel->setText((horzAxisLabel+": #"+horzUnits).c_str());
1040 vertValueLabel->setText((vertAxisLabel+": #"+vertUnits).c_str());
1041 pointCountLabel->setText((istring(nodes.size())+" "+_("points")).c_str());
1042 }
1043
1044
getScalar() const1045 const int FXGraphParamValue::getScalar() const
1046 {
1047 return scalarSpinner==NULL ? vertValueMapper->getDefaultScalar() : vertValueMapper->getScalar();
1048 }
1049
setScalar(const int scalar)1050 void FXGraphParamValue::setScalar(const int scalar)
1051 {
1052 vertValueMapper->setScalar(scalar);
1053 if(scalarSpinner!=NULL) scalarSpinner->setValue(vertValueMapper->getScalar());
1054 }
1055
getMinScalar() const1056 const int FXGraphParamValue::getMinScalar() const
1057 {
1058 /*
1059 FXint lo=0,hi=0;
1060 if(scalarSpinner!=NULL)
1061 scalarSpinner->getRange(lo,hi);
1062 return lo;
1063 */
1064 return vertValueMapper->getMinScalar();
1065 }
1066
getMaxScalar() const1067 const int FXGraphParamValue::getMaxScalar() const
1068 {
1069 /*
1070 FXint lo=0,hi=0;
1071 if(scalarSpinner!=NULL)
1072 scalarSpinner->getRange(lo,hi);
1073 return hi;
1074 */
1075 return vertValueMapper->getMaxScalar();
1076 }
1077
getName() const1078 const string FXGraphParamValue::getName() const
1079 {
1080 return name;
1081 }
1082
readFromFile(const string & prefix,CNestedDataFile * f)1083 void FXGraphParamValue::readFromFile(const string &prefix,CNestedDataFile *f)
1084 {
1085 const string key=prefix DOT getName();
1086
1087 if(f->keyExists(key DOT "scalar"))
1088 setScalar(f->getValue<int>(key DOT "scalar"));
1089 else
1090 setScalar(vertValueMapper->getDefaultScalar());
1091
1092 nodes.clear();
1093
1094 const string k1=key DOT "node_positions";
1095 const string k2=key DOT "node_values";
1096 if(f->keyExists(k1)==CNestedDataFile::ktValue && f->keyExists(k2)==CNestedDataFile::ktValue)
1097 {
1098 const vector<double> positions=f->getValue<vector<double> >(k1);
1099 const vector<double> values=f->getValue<vector<double> >(k2);
1100
1101 // ??? I could either save the node x and values as [0,1], or I could use save the actual values ( <-- currently)
1102 for(size_t t=0;t<positions.size();t++)
1103 nodes.push_back(CGraphParamValueNode(positions[t],values[t]));
1104
1105 }
1106 else
1107 clearNodes();
1108
1109 if(f->keyExists(key DOT "horzDeformSlider")==CNestedDataFile::ktValue)
1110 horzDeformSlider->setValue(f->getValue<int>(key DOT "horzDeformSlider"));
1111 if(f->keyExists(key DOT "vertDeformSlider")==CNestedDataFile::ktValue)
1112 vertDeformSlider->setValue(f->getValue<int>(key DOT "vertDeformSlider"));
1113
1114 updateNumbers();
1115 update();
1116 graphCanvas->update();
1117 }
1118
writeToFile(const string & prefix,CNestedDataFile * f) const1119 void FXGraphParamValue::writeToFile(const string &prefix,CNestedDataFile *f) const
1120 {
1121 const string key=prefix DOT getName();
1122
1123 if(getMinScalar()!=getMaxScalar())
1124 f->setValue<int>(key DOT "scalar",getScalar());
1125
1126 vector<double> positions,values;
1127 for(size_t t=0;t<nodes.size();t++)
1128 {
1129 positions.push_back(nodes[t].x);
1130 values.push_back(nodes[t].y);
1131 }
1132
1133 const string k1=key DOT "node_positions";
1134 f->setValue<vector<double> >(k1,positions);
1135
1136 const string k2=key DOT "node_values";
1137 f->setValue<vector<double> >(k2,values);
1138
1139 if(getMinScalar()!=getMaxScalar())
1140 f->setValue<int>(key DOT "scalar",getScalar());
1141
1142 f->setValue<int>(key DOT "horzDeformSlider",horzDeformSlider->getValue());
1143 f->setValue<int>(key DOT "vertDeformSlider",vertDeformSlider->getValue());
1144 }
1145
1146