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