no longer need ffmpeg patch0 which was for Termux
[goodguy/cinelerra.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, xS(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 - xS(160),
122                                 y - yS(75),
123                                 xS(320),
124                                 yS(200),
125                                 xS(320),
126                                 yS(200),
127                                 0,
128                                 0,
129                                 1)
130 {
131         this->plugin = plugin;
132 // *** CONTEXT_HELP ***
133         if(plugin) context_help_set_keyword(plugin->plugin_title());
134         else       context_help_set_keyword("Rendered Audio Effects");
135 }
136
137
138 TimeStretchWindow::~TimeStretchWindow()
139 {
140 }
141
142 void TimeStretchWindow::create_objects()
143 {
144         int x = xS(10), y = yS(10);
145
146         BC_Title *title;
147         add_subwindow(title = new BC_Title(x, y, _("Fraction of original speed:")));
148         y += title->get_h() + plugin->get_theme()->widget_border;
149
150         TimeStretchFraction *fraction;
151         add_subwindow(fraction = new TimeStretchFraction(plugin, x, y));
152
153         y += fraction->get_h() + plugin->get_theme()->widget_border;
154         add_subwindow(freq = new TimeStretchFreq(plugin, this, x, y));
155         y += freq->get_h() + plugin->get_theme()->widget_border;
156         add_subwindow(time = new TimeStretchTime(plugin, this, x, y));
157
158         add_subwindow(new BC_OKButton(this));
159         add_subwindow(new BC_CancelButton(this));
160         show_window();
161
162
163
164         flush();
165 }
166
167
168
169
170
171
172
173
174
175
176 PitchEngine::PitchEngine(TimeStretch *plugin)
177  : CrossfadeFFT()
178 {
179         this->plugin = plugin;
180         input_buffer = 0;
181         input_size = 0;
182         input_allocated = 0;
183         current_position = 0;
184         temp = 0;
185 }
186
187 PitchEngine::~PitchEngine()
188 {
189         if(input_buffer) delete [] input_buffer;
190         if(temp) delete [] temp;
191 }
192
193 int PitchEngine::read_samples(int64_t output_sample,
194         int samples,
195         Samples *buffer)
196 {
197         plugin->resample->resample(buffer,
198                 samples,
199                 1000000,
200                 (int)(1000000 * plugin->scale),
201                 output_sample,
202                 PLAY_FORWARD);
203
204 //      while(input_size < samples)
205 //      {
206 //              if(!temp) temp = new double[INPUT_SIZE];
207 //
208 //              plugin->read_samples(temp,
209 //                      0,
210 //                      plugin->get_source_start() + current_position,
211 //                      INPUT_SIZE);
212 //              current_position += INPUT_SIZE;
213 //
214 //              plugin->resample->resample(buffer,
215 //                      INPUT_SIZE,
216 //                      1000000,
217 //                      (int)(1000000 * plugin->scale),
218 //                      0);
219 //
220 //              int fragment_size = plugin->resample->get_output_size(0);
221 //
222 //              if(input_size + fragment_size > input_allocated)
223 //              {
224 //                      int new_allocated = input_size + fragment_size;
225 //                      double *new_buffer = new double[new_allocated];
226 //                      if(input_buffer)
227 //                      {
228 //                              memcpy(new_buffer, input_buffer, input_size * sizeof(double));
229 //                              delete [] input_buffer;
230 //                      }
231 //                      input_buffer = new_buffer;
232 //                      input_allocated = new_allocated;
233 //              }
234 //
235 //
236 //              plugin->resample->read_output(input_buffer + input_size,
237 //                      0,
238 //                      fragment_size);
239 //              input_size += fragment_size;
240 //      }
241 //      memcpy(buffer, input_buffer, samples * sizeof(int64_t));
242 //      memcpy(input_buffer,
243 //              input_buffer + samples,
244 //              sizeof(int64_t) * (input_size - samples));
245 //      input_size -= samples;
246         return 0;
247 }
248
249 int PitchEngine::signal_process()
250 {
251
252         int min_freq =
253                 1 + (int)(20.0 /
254                                 ((double)plugin->PluginAClient::project_sample_rate /
255                                         window_size *
256                                         2) +
257                                 0.5);
258
259         if(plugin->scale < 1)
260         {
261                 for(int i = min_freq; i < window_size / 2; i++)
262                 {
263                         double destination = i * plugin->scale;
264                         int dest_i = (int)(destination + 0.5);
265                         if(dest_i != i)
266                         {
267                                 if(dest_i <= window_size / 2)
268                                 {
269                                         freq_real[dest_i] = freq_real[i];
270                                         freq_imag[dest_i] = freq_imag[i];
271                                 }
272                                 freq_real[i] = 0;
273                                 freq_imag[i] = 0;
274                         }
275                 }
276         }
277         else
278         if(plugin->scale > 1)
279         {
280                 for(int i = window_size / 2 - 1; i >= min_freq; i--)
281                 {
282                         double destination = i * plugin->scale;
283                         int dest_i = (int)(destination + 0.5);
284                         if(dest_i != i)
285                         {
286                                 if(dest_i <= window_size / 2)
287                                 {
288                                         freq_real[dest_i] = freq_real[i];
289                                         freq_imag[dest_i] = freq_imag[i];
290                                 }
291                                 freq_real[i] = 0;
292                                 freq_imag[i] = 0;
293                         }
294                 }
295         }
296
297         symmetry(window_size, freq_real, freq_imag);
298         return 0;
299 }
300
301
302
303
304
305
306
307
308 TimeStretchResample::TimeStretchResample(TimeStretch *plugin)
309 {
310         this->plugin = plugin;
311 }
312
313
314 int TimeStretchResample::read_samples(Samples *buffer,
315         int64_t start,
316         int64_t len)
317 {
318         return plugin->read_samples(buffer,
319                 0,
320                 start + plugin->get_source_start(),
321                 len);
322 }
323
324
325
326
327
328
329
330
331
332
333
334 TimeStretch::TimeStretch(PluginServer *server)
335  : PluginAClient(server)
336 {
337         temp = 0;
338         pitch = 0;
339         resample = 0;
340         stretch = 0;
341         input = 0;
342         input_allocated = 0;
343 }
344
345
346 TimeStretch::~TimeStretch()
347 {
348         if(temp) delete [] temp;
349         if(input) delete input;
350         if(pitch) delete pitch;
351         if(resample) delete resample;
352         if(stretch) delete stretch;
353 }
354
355
356
357 const char* TimeStretch::plugin_title() { return N_("Time stretch"); }
358
359 int TimeStretch::get_parameters()
360 {
361         BC_DisplayInfo info;
362         TimeStretchWindow window(this, info.get_abs_cursor_x(), info.get_abs_cursor_y());
363         window.create_objects();
364         int result = window.run_window();
365
366         return result;
367 }
368
369 int TimeStretch::start_loop()
370 {
371         scaled_size = (int64_t)(get_total_len() * scale);
372         if(PluginClient::interactive)
373         {
374                 char string[BCTEXTLEN];
375                 sprintf(string, "%s...", plugin_title());
376                 progress = start_progress(string, scaled_size);
377         }
378
379         current_position = get_source_start();
380         total_written = 0;
381         total_read = 0;
382
383
384
385 // The FFT case
386         if(use_fft)
387         {
388                 pitch = new PitchEngine(this);
389                 pitch->initialize(WINDOW_SIZE);
390                 resample = new TimeStretchResample(this);
391         }
392         else
393 // The windowing case
394         {
395 // Must be short enough to mask beating but long enough to mask humming.
396                 stretch = new TimeStretchEngine(scale,
397                         PluginAClient::project_sample_rate,
398                         WINDOW_TIME);
399         }
400
401
402
403
404         return 0;
405 }
406
407 int TimeStretch::stop_loop()
408 {
409         if(PluginClient::interactive)
410         {
411                 progress->stop_progress();
412                 delete progress;
413         }
414         return 0;
415 }
416
417 int TimeStretch::process_loop(Samples *buffer, int64_t &write_length)
418 {
419         int result = 0;
420         int64_t predicted_total = (int64_t)((double)get_total_len() * scale + 0.5);
421
422
423
424
425
426
427         int samples_rendered = 0;
428
429
430
431
432
433
434
435 // The FFT case
436         if(use_fft)
437         {
438                 samples_rendered = get_buffer_size();
439                 pitch->process_buffer(total_written,
440                                         samples_rendered,
441                                         buffer,
442                                         PLAY_FORWARD);
443         }
444         else
445 // The windowing case
446         {
447 // Length to read based on desired output size
448                 int64_t size = (int64_t)((double)get_buffer_size() / scale);
449
450                 if(input_allocated < size)
451                 {
452                         if(input) delete input;
453                         input = new Samples(size);
454                         input_allocated = size;
455                 }
456
457                 read_samples(input, 0, current_position, size);
458                 current_position += size;
459
460                 samples_rendered = stretch->process(input, size);
461                 if(samples_rendered)
462                 {
463                         samples_rendered = MIN(samples_rendered, get_buffer_size());
464                         stretch->read_output(buffer, samples_rendered);
465                 }
466         }
467
468
469         total_written += samples_rendered;
470
471 // Trim output to predicted length of stretched selection.
472         if(total_written > predicted_total)
473         {
474                 samples_rendered -= total_written - predicted_total;
475                 result = 1;
476         }
477
478
479         write_length = samples_rendered;
480         if(PluginClient::interactive) result = progress->update(total_written);
481
482         return result;
483 }
484
485
486
487 int TimeStretch::load_defaults()
488 {
489         char directory[BCTEXTLEN];
490
491 // set the default directory
492         sprintf(directory, "%s/timestretch.rc", File::get_config_path());
493 // load the defaults
494         defaults = new BC_Hash(directory);
495         defaults->load();
496
497         scale = defaults->get("SCALE", (double)1);
498         use_fft = defaults->get("USE_FFT", 0);
499         return 0;
500 }
501
502 int TimeStretch::save_defaults()
503 {
504         defaults->update("SCALE", scale);
505         defaults->update("USE_FFT", use_fft);
506         defaults->save();
507         return 0;
508 }