font debug env var, drag fixes, cposer hide scrollbar, plugin tool tip
[goodguy/history.git] / cinelerra-5.1 / plugins / timestretch / timestretch.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2009 Adam Williams <broadcast at earthling dot net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but 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
22 #include "bcdisplayinfo.h"
23 #include "clip.h"
24 #include "bchash.h"
25 #include "file.h"
26 #include "language.h"
27 #include "mainprogress.h"
28 #include "resample.h"
29 #include "samples.h"
30 #include "theme.h"
31 #include "timestretch.h"
32 #include "timestretchengine.h"
33 #include "transportque.inc"
34 #include "vframe.h"
35
36 #include <string.h>
37
38 // FFT window size
39 #define WINDOW_SIZE 4096
40 // Time stretch window time
41 #define WINDOW_TIME 40
42 #define INPUT_SIZE 65536
43
44
45 REGISTER_PLUGIN(TimeStretch)
46
47
48
49
50
51 TimeStretchFraction::TimeStretchFraction(TimeStretch *plugin, int x, int y)
52  : BC_TextBox(x, y, 100, 1, (float)(1.0 / plugin->scale))
53 {
54         this->plugin = plugin;
55 }
56
57 int TimeStretchFraction::handle_event()
58 {
59         plugin->scale = 1.0 / atof(get_text());
60         return 1;
61 }
62
63
64
65
66
67 TimeStretchFreq::TimeStretchFreq(TimeStretch *plugin,
68         TimeStretchWindow *gui,
69         int x,
70         int y)
71  : BC_Radial(x, y, plugin->use_fft, _("Use fast fourier transform"))
72 {
73         this->plugin = plugin;
74         this->gui = gui;
75 }
76
77 int TimeStretchFreq::handle_event()
78 {
79         plugin->use_fft = 1;
80         update(1);
81         gui->time->update(0);
82         return 1;
83 }
84
85
86
87
88
89
90 TimeStretchTime::TimeStretchTime(TimeStretch *plugin,
91         TimeStretchWindow *gui,
92         int x,
93         int y)
94  : BC_Radial(x, y, !plugin->use_fft, _("Use overlapping windows"))
95 {
96         this->plugin = plugin;
97         this->gui = gui;
98 }
99
100 int TimeStretchTime::handle_event()
101 {
102         plugin->use_fft = 0;
103         update(1);
104         gui->freq->update(0);
105         return 1;
106 }
107
108
109
110
111
112
113
114
115
116
117
118
119 TimeStretchWindow::TimeStretchWindow(TimeStretch *plugin, int x, int y)
120  : BC_Window(_(PROGRAM_NAME ": Time stretch"),
121                                 x - 160,
122                                 y - 75,
123                                 320,
124                                 200,
125                                 320,
126                                 200,
127                                 0,
128                                 0,
129                                 1)
130 {
131         this->plugin = plugin;
132 }
133
134
135 TimeStretchWindow::~TimeStretchWindow()
136 {
137 }
138
139 void TimeStretchWindow::create_objects()
140 {
141         int x = 10, y = 10;
142
143         BC_Title *title;
144         add_subwindow(title = new BC_Title(x, y, _("Fraction of original speed:")));
145         y += title->get_h() + plugin->get_theme()->widget_border;
146
147         TimeStretchFraction *fraction;
148         add_subwindow(fraction = new TimeStretchFraction(plugin, x, y));
149
150         y += fraction->get_h() + plugin->get_theme()->widget_border;
151         add_subwindow(freq = new TimeStretchFreq(plugin, this, x, y));
152         y += freq->get_h() + plugin->get_theme()->widget_border;
153         add_subwindow(time = new TimeStretchTime(plugin, this, x, y));
154
155         add_subwindow(new BC_OKButton(this));
156         add_subwindow(new BC_CancelButton(this));
157         show_window();
158
159
160
161         flush();
162 }
163
164
165
166
167
168
169
170
171
172
173 PitchEngine::PitchEngine(TimeStretch *plugin)
174  : CrossfadeFFT()
175 {
176         this->plugin = plugin;
177         input_buffer = 0;
178         input_size = 0;
179         input_allocated = 0;
180         current_position = 0;
181         temp = 0;
182 }
183
184 PitchEngine::~PitchEngine()
185 {
186         if(input_buffer) delete [] input_buffer;
187         if(temp) delete [] temp;
188 }
189
190 int PitchEngine::read_samples(int64_t output_sample,
191         int samples,
192         Samples *buffer)
193 {
194         plugin->resample->resample(buffer,
195                 samples,
196                 1000000,
197                 (int)(1000000 * plugin->scale),
198                 output_sample,
199                 PLAY_FORWARD);
200
201 //      while(input_size < samples)
202 //      {
203 //              if(!temp) temp = new double[INPUT_SIZE];
204 //
205 //              plugin->read_samples(temp,
206 //                      0,
207 //                      plugin->get_source_start() + current_position,
208 //                      INPUT_SIZE);
209 //              current_position += INPUT_SIZE;
210 //
211 //              plugin->resample->resample(buffer,
212 //                      INPUT_SIZE,
213 //                      1000000,
214 //                      (int)(1000000 * plugin->scale),
215 //                      0);
216 //
217 //              int fragment_size = plugin->resample->get_output_size(0);
218 //
219 //              if(input_size + fragment_size > input_allocated)
220 //              {
221 //                      int new_allocated = input_size + fragment_size;
222 //                      double *new_buffer = new double[new_allocated];
223 //                      if(input_buffer)
224 //                      {
225 //                              memcpy(new_buffer, input_buffer, input_size * sizeof(double));
226 //                              delete [] input_buffer;
227 //                      }
228 //                      input_buffer = new_buffer;
229 //                      input_allocated = new_allocated;
230 //              }
231 //
232 //
233 //              plugin->resample->read_output(input_buffer + input_size,
234 //                      0,
235 //                      fragment_size);
236 //              input_size += fragment_size;
237 //      }
238 //      memcpy(buffer, input_buffer, samples * sizeof(int64_t));
239 //      memcpy(input_buffer,
240 //              input_buffer + samples,
241 //              sizeof(int64_t) * (input_size - samples));
242 //      input_size -= samples;
243         return 0;
244 }
245
246 int PitchEngine::signal_process()
247 {
248
249         int min_freq =
250                 1 + (int)(20.0 /
251                                 ((double)plugin->PluginAClient::project_sample_rate /
252                                         window_size *
253                                         2) +
254                                 0.5);
255
256         if(plugin->scale < 1)
257         {
258                 for(int i = min_freq; i < window_size / 2; i++)
259                 {
260                         double destination = i * plugin->scale;
261                         int dest_i = (int)(destination + 0.5);
262                         if(dest_i != i)
263                         {
264                                 if(dest_i <= window_size / 2)
265                                 {
266                                         freq_real[dest_i] = freq_real[i];
267                                         freq_imag[dest_i] = freq_imag[i];
268                                 }
269                                 freq_real[i] = 0;
270                                 freq_imag[i] = 0;
271                         }
272                 }
273         }
274         else
275         if(plugin->scale > 1)
276         {
277                 for(int i = window_size / 2 - 1; i >= min_freq; i--)
278                 {
279                         double destination = i * plugin->scale;
280                         int dest_i = (int)(destination + 0.5);
281                         if(dest_i != i)
282                         {
283                                 if(dest_i <= window_size / 2)
284                                 {
285                                         freq_real[dest_i] = freq_real[i];
286                                         freq_imag[dest_i] = freq_imag[i];
287                                 }
288                                 freq_real[i] = 0;
289                                 freq_imag[i] = 0;
290                         }
291                 }
292         }
293
294         symmetry(window_size, freq_real, freq_imag);
295         return 0;
296 }
297
298
299
300
301
302
303
304
305 TimeStretchResample::TimeStretchResample(TimeStretch *plugin)
306 {
307         this->plugin = plugin;
308 }
309
310
311 int TimeStretchResample::read_samples(Samples *buffer,
312         int64_t start,
313         int64_t len)
314 {
315         return plugin->read_samples(buffer,
316                 0,
317                 start + plugin->get_source_start(),
318                 len);
319 }
320
321
322
323
324
325
326
327
328
329
330
331 TimeStretch::TimeStretch(PluginServer *server)
332  : PluginAClient(server)
333 {
334         temp = 0;
335         pitch = 0;
336         resample = 0;
337         stretch = 0;
338         input = 0;
339         input_allocated = 0;
340 }
341
342
343 TimeStretch::~TimeStretch()
344 {
345         if(temp) delete [] temp;
346         if(input) delete input;
347         if(pitch) delete pitch;
348         if(resample) delete resample;
349         if(stretch) delete stretch;
350 }
351
352
353
354 const char* TimeStretch::plugin_title() { return _("Time stretch"); }
355
356 int TimeStretch::get_parameters()
357 {
358         BC_DisplayInfo info;
359         TimeStretchWindow window(this, info.get_abs_cursor_x(), info.get_abs_cursor_y());
360         window.create_objects();
361         int result = window.run_window();
362
363         return result;
364 }
365
366 int TimeStretch::start_loop()
367 {
368         scaled_size = (int64_t)(get_total_len() * scale);
369         if(PluginClient::interactive)
370         {
371                 char string[BCTEXTLEN];
372                 sprintf(string, "%s...", plugin_title());
373                 progress = start_progress(string, scaled_size);
374         }
375
376         current_position = get_source_start();
377         total_written = 0;
378         total_read = 0;
379
380
381
382 // The FFT case
383         if(use_fft)
384         {
385                 pitch = new PitchEngine(this);
386                 pitch->initialize(WINDOW_SIZE);
387                 resample = new TimeStretchResample(this);
388         }
389         else
390 // The windowing case
391         {
392 // Must be short enough to mask beating but long enough to mask humming.
393                 stretch = new TimeStretchEngine(scale,
394                         PluginAClient::project_sample_rate,
395                         WINDOW_TIME);
396         }
397
398
399
400
401         return 0;
402 }
403
404 int TimeStretch::stop_loop()
405 {
406         if(PluginClient::interactive)
407         {
408                 progress->stop_progress();
409                 delete progress;
410         }
411         return 0;
412 }
413
414 int TimeStretch::process_loop(Samples *buffer, int64_t &write_length)
415 {
416         int result = 0;
417         int64_t predicted_total = (int64_t)((double)get_total_len() * scale + 0.5);
418
419
420
421
422
423
424         int samples_rendered = 0;
425
426
427
428
429
430
431
432 // The FFT case
433         if(use_fft)
434         {
435                 samples_rendered = get_buffer_size();
436                 pitch->process_buffer(total_written,
437                                         samples_rendered,
438                                         buffer,
439                                         PLAY_FORWARD);
440         }
441         else
442 // The windowing case
443         {
444 // Length to read based on desired output size
445                 int64_t size = (int64_t)((double)get_buffer_size() / scale);
446
447                 if(input_allocated < size)
448                 {
449                         if(input) delete input;
450                         input = new Samples(size);
451                         input_allocated = size;
452                 }
453
454                 read_samples(input, 0, current_position, size);
455                 current_position += size;
456
457                 samples_rendered = stretch->process(input, size);
458                 if(samples_rendered)
459                 {
460                         samples_rendered = MIN(samples_rendered, get_buffer_size());
461                         stretch->read_output(buffer, samples_rendered);
462                 }
463         }
464
465
466         total_written += samples_rendered;
467
468 // Trim output to predicted length of stretched selection.
469         if(total_written > predicted_total)
470         {
471                 samples_rendered -= total_written - predicted_total;
472                 result = 1;
473         }
474
475
476         write_length = samples_rendered;
477         if(PluginClient::interactive) result = progress->update(total_written);
478
479         return result;
480 }
481
482
483
484 int TimeStretch::load_defaults()
485 {
486         char directory[BCTEXTLEN];
487
488 // set the default directory
489         sprintf(directory, "%s/timestretch.rc", File::get_config_path());
490 // load the defaults
491         defaults = new BC_Hash(directory);
492         defaults->load();
493
494         scale = defaults->get("SCALE", (double)1);
495         use_fft = defaults->get("USE_FFT", 0);
496         return 0;
497 }
498
499 int TimeStretch::save_defaults()
500 {
501         defaults->update("SCALE", scale);
502         defaults->update("USE_FFT", use_fft);
503         defaults->save();
504         return 0;
505 }