Allow linking widths and tangents to positions on a bline. It also now works with...
[synfig.git] / synfig-studio / trunk / src / gtkmm / widget_sound.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file widget_sound.cpp
3 **      \brief Widget Sound Implementation File
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **
10 **      This package is free software; you can redistribute it and/or
11 **      modify it under the terms of the GNU General Public License as
12 **      published by the Free Software Foundation; either version 2 of
13 **      the License, or (at your option) any later version.
14 **
15 **      This package is distributed in the hope that it will be useful,
16 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
17 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 **      General Public License for more details.
19 **      \endlegal
20 */
21 /* ========================================================================= */
22
23 /* === H E A D E R S ======================================================= */
24
25 #ifdef USING_PCH
26 #       include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 #       include <config.h>
30 #endif
31
32 #include <gtkmm/adjustment.h>
33
34 #include <synfig/general.h>
35 #include <ETL/clock>
36
37 #include "widget_sound.h"
38 #include "audiocontainer.h"
39
40 #include "general.h"
41
42 #endif
43
44 /* === U S I N G =========================================================== */
45
46 using namespace std;
47 using namespace etl;
48 //using namespace synfig;
49
50 using studio::AudioProfile;
51
52 /* === M A C R O S ========================================================= */
53
54 /* === G L O B A L S ======================================================= */
55
56 /* === P R O C E D U R E S ================================================= */
57
58 /* === M E T H O D S ======================================================= */
59
60 /* === E N T R Y P O I N T ================================================= */
61
62 studio::Widget_Sound::Widget_Sound()
63 {
64 }
65
66 studio::Widget_Sound::~Widget_Sound()
67 {
68 }
69
70 void studio::Widget_Sound::set_position(double t)
71 {
72         //synfig::info("Setting position to %.2lf s", t);
73         if(adj_timescale && t != adj_timescale->get_value())
74         {
75                 float upper = adj_timescale->get_upper();
76                 float lower = adj_timescale->get_lower();
77                 float framesize =  upper - lower;
78
79                 if(t < lower)
80                 {
81                         lower -= ceil((lower-t)/framesize)*framesize;
82                         upper = lower + framesize;
83                         adj_timescale->set_lower(lower); adj_timescale->set_upper(upper);
84                         adj_timescale->set_value(t);
85                         adj_timescale->changed(); adj_timescale->value_changed();
86                 }else
87                 if(t > upper)
88                 {
89                         lower += ceil((t-upper)/framesize)*framesize;
90                         upper = lower + framesize;
91                         adj_timescale->set_lower(lower); adj_timescale->set_upper(upper);
92                         adj_timescale->set_value(t);
93                         adj_timescale->changed(); adj_timescale->value_changed();
94                 }else
95                 {
96                         adj_timescale->set_value(t);
97                         adj_timescale->value_changed();
98                 }
99         }
100 }
101
102 double studio::Widget_Sound::get_position() const
103 {
104         if(adj_timescale)
105         {
106                 return adj_timescale->get_value();
107         }
108         return 0;
109 }
110
111 bool studio::Widget_Sound::set_profile(etl::handle<AudioProfile>        p)
112 {
113         clear();
114
115         //set the profile
116         audioprof = p;
117
118         if(!audioprof)
119         {
120                 clear();
121                 return false;
122         }
123
124         return true;
125 }
126
127 etl::handle<AudioProfile>       studio::Widget_Sound::get_profile() const
128 {
129         return audioprof;
130 }
131
132 void studio::Widget_Sound::clear()
133 {
134         audioprof.detach();
135 }
136
137 void studio::Widget_Sound::draw()
138 {
139         on_expose_event();
140 }
141
142 bool studio::Widget_Sound::on_expose_event(GdkEventExpose */*heh*/)
143 {
144         if(!get_window()) return false;
145
146         //clear the background to dark grey
147         Glib::RefPtr<Gdk::GC>   gc = Gdk::GC::create(get_window());
148
149         if(!gc) return false;
150
151         {
152                 Gdk::Rectangle r(0,0,get_width(),get_height());
153                 get_window()->begin_paint_rect(r);
154         }
155         Gdk::Color      c("#3f3f3f");
156         gc->set_rgb_fg_color(c);
157         gc->set_background(c);
158
159         int w = get_width();
160         int baseline = get_height()/2;
161         get_window()->draw_rectangle(gc,true,0,0,w,get_height());
162
163         //set up the color to be blue
164         c.set_rgb_p(0,0.5,1);
165         gc->set_rgb_fg_color(c);
166
167         //draw the base line
168         get_window()->draw_line(gc,0,baseline,w,baseline);
169
170         //redraw all the samples from begin to end, but only if we have samples to draw (or there is no space to draw)
171
172         //synfig::warning("Ok rendered everything, now must render actual sound wave");
173         if(!audioprof || !adj_timescale || !w)
174         {
175                 get_window()->end_paint();
176                 return true;
177         }
178
179         //draw you fool!
180         float framesize = adj_timescale->get_upper() - adj_timescale->get_lower();
181         if(framesize)
182         {
183                 float delta=0,cum=0;
184
185                 //position in sample space
186                 int begin=0,end=0;
187                 int     cur=0,maxs=0,mins=0;
188
189                 int       i=0; //pixel counter
190
191                 //etl::clock    check; check.reset();
192
193                 float position = adj_timescale->get_value();
194                 float samplerate = audioprof->get_samplerate();
195                 int             posi = 0;
196                 //enforce position inside of frame size
197                 {
198                         float offset = audioprof->get_offset();
199
200                         //clamp begin and end to framesize
201                         float beginf = adj_timescale->get_lower();
202                         float endf = adj_timescale->get_upper();
203
204                         posi = round_to_int((position-beginf)*w/framesize);
205                         //posi = (int)((position-beginf)*w/framesize);
206
207                         //calculate in sample space from seconds
208                         begin = round_to_int((beginf - offset)*samplerate);
209                         end = round_to_int((endf - offset)*samplerate);
210                         //begin = (int)((beginf - offset)*samplerate);
211                         //end = (int)((endf - offset)*samplerate);
212                 }
213
214                 delta = (end - begin)/(float)w; //samples per pixel
215
216                 /*synfig::warning("Rendering a framesize of %f secs from [%d,%d) samples to %d samples, took %f sec",
217                                                 framesize, begin, end, w, check());*/
218
219                 cur = begin;
220                 i = 0; cum = 0;
221                 for(int i=0;i<w;++i)
222                 {
223                         //get the maximum of the collected samples
224                         maxs = 0;
225                         mins = 0;
226                         for(;cum < delta; ++cum, ++cur)
227                         {
228                                 maxs = std::max(maxs,(int)(*audioprof)[cur]);
229                                 mins = std::min(mins,(int)(*audioprof)[cur]);
230                         }
231                         cum -= delta;
232
233                         //draw spike if not needed be
234                         if(maxs||mins)
235                         {
236                                 int top = maxs * baseline / 64;
237                                 int bot = mins * baseline / 64;
238
239                                 get_window()->draw_line(gc,i,baseline+bot,i,baseline+top);
240                         }
241                 }
242
243                 //synfig::warning("Drawing audio line");
244                 c.set_rgb_p(1,0,0);
245                 gc->set_rgb_fg_color(c);
246                 get_window()->draw_line(gc,posi,0,posi,get_height());
247         }
248         get_window()->end_paint();
249
250         return true;
251 }
252
253 //--- Handle the single clicking and dragging for scrubbing
254
255 bool studio::Widget_Sound::on_motion_notify_event(GdkEventMotion* event)
256 {
257         Gdk::ModifierType       mod = Gdk::ModifierType(event->state);
258
259         //if we are scrubbing
260         if(mod & Gdk::BUTTON1_MASK)
261         {
262                 //Can't do this if we don't have a time frame (heheh...)
263                 if(!adj_timescale) return false;
264
265                 double beg = adj_timescale->get_lower(), end = adj_timescale->get_upper();
266
267                 //find event position in time
268                 double t = beg + event->x * (end-beg) / get_width();
269
270                 //signal that we are scrubbing to this new value...
271                 signal_scrub()(t);
272
273
274                 // We should be able to just call
275                 // Widget_Timeslider::on_motion_notify_event(),
276                 // but that seems to cause the program to halt
277                 // for some reason. So for now, let's do the job ourselves
278                 //adj_timescale->set_value(t);
279                 //adj_timescale->changed();
280                 //return true;
281         }
282
283         return Widget_Timeslider::on_motion_notify_event(event);
284 }
285
286 bool studio::Widget_Sound::on_button_press_event(GdkEventButton *event)
287 {
288         //Assume button PRESS
289
290         //if we are starting... using left click
291         if(event->button == 1)
292         {
293                 if(!adj_timescale) return false;
294
295                 double beg = adj_timescale->get_lower(), end = adj_timescale->get_upper();
296
297                 //find event position in time
298                 double t = beg + event->x * (end-beg) / get_width();
299
300                 //signal the attached scrubbing devices...
301                 signal_start_scrubbing()(t);
302
303                 return true;
304         }
305
306         return Widget_Timeslider::on_button_press_event(event);
307 }
308
309 bool studio::Widget_Sound::on_button_release_event(GdkEventButton *event)
310 {
311         //Assume button RELEASE
312
313         //if we are ending... using left click
314         if(event->button == 1)
315         {
316                 //signal the scrubbing device... to stop
317                 signal_stop_scrubbing()();
318
319                 return true;
320         }
321
322         return Widget_Timeslider::on_button_release_event(event);
323 }