4 * Copyright (C) 2009 Adam Williams <broadcast at earthling dot net>
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.
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.
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
22 #include "bcdisplayinfo.h"
27 #include "mainprogress.h"
31 #include "timestretch.h"
32 #include "timestretchengine.h"
33 #include "transportque.inc"
39 #define WINDOW_SIZE 4096
40 // Time stretch window time
41 #define WINDOW_TIME 40
42 #define INPUT_SIZE 65536
45 REGISTER_PLUGIN(TimeStretch)
51 TimeStretchFraction::TimeStretchFraction(TimeStretch *plugin, int x, int y)
52 : BC_TextBox(x, y, xS(100), 1, (float)(1.0 / plugin->scale))
54 this->plugin = plugin;
57 int TimeStretchFraction::handle_event()
59 plugin->scale = 1.0 / atof(get_text());
67 TimeStretchFreq::TimeStretchFreq(TimeStretch *plugin,
68 TimeStretchWindow *gui,
71 : BC_Radial(x, y, plugin->use_fft, _("Use fast fourier transform"))
73 this->plugin = plugin;
77 int TimeStretchFreq::handle_event()
90 TimeStretchTime::TimeStretchTime(TimeStretch *plugin,
91 TimeStretchWindow *gui,
94 : BC_Radial(x, y, !plugin->use_fft, _("Use overlapping windows"))
96 this->plugin = plugin;
100 int TimeStretchTime::handle_event()
104 gui->freq->update(0);
119 TimeStretchWindow::TimeStretchWindow(TimeStretch *plugin, int x, int y)
120 : BC_Window(_(PROGRAM_NAME ": Time stretch"),
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");
138 TimeStretchWindow::~TimeStretchWindow()
142 void TimeStretchWindow::create_objects()
144 int x = xS(10), y = yS(10);
147 add_subwindow(title = new BC_Title(x, y, _("Fraction of original speed:")));
148 y += title->get_h() + plugin->get_theme()->widget_border;
150 TimeStretchFraction *fraction;
151 add_subwindow(fraction = new TimeStretchFraction(plugin, x, y));
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));
158 add_subwindow(new BC_OKButton(this));
159 add_subwindow(new BC_CancelButton(this));
176 PitchEngine::PitchEngine(TimeStretch *plugin)
179 this->plugin = plugin;
183 current_position = 0;
187 PitchEngine::~PitchEngine()
189 if(input_buffer) delete [] input_buffer;
190 if(temp) delete [] temp;
193 int PitchEngine::read_samples(int64_t output_sample,
197 plugin->resample->resample(buffer,
200 (int)(1000000 * plugin->scale),
204 // while(input_size < samples)
206 // if(!temp) temp = new double[INPUT_SIZE];
208 // plugin->read_samples(temp,
210 // plugin->get_source_start() + current_position,
212 // current_position += INPUT_SIZE;
214 // plugin->resample->resample(buffer,
217 // (int)(1000000 * plugin->scale),
220 // int fragment_size = plugin->resample->get_output_size(0);
222 // if(input_size + fragment_size > input_allocated)
224 // int new_allocated = input_size + fragment_size;
225 // double *new_buffer = new double[new_allocated];
228 // memcpy(new_buffer, input_buffer, input_size * sizeof(double));
229 // delete [] input_buffer;
231 // input_buffer = new_buffer;
232 // input_allocated = new_allocated;
236 // plugin->resample->read_output(input_buffer + input_size,
239 // input_size += fragment_size;
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;
249 int PitchEngine::signal_process()
254 ((double)plugin->PluginAClient::project_sample_rate /
259 if(plugin->scale < 1)
261 for(int i = min_freq; i < window_size / 2; i++)
263 double destination = i * plugin->scale;
264 int dest_i = (int)(destination + 0.5);
267 if(dest_i <= window_size / 2)
269 freq_real[dest_i] = freq_real[i];
270 freq_imag[dest_i] = freq_imag[i];
278 if(plugin->scale > 1)
280 for(int i = window_size / 2 - 1; i >= min_freq; i--)
282 double destination = i * plugin->scale;
283 int dest_i = (int)(destination + 0.5);
286 if(dest_i <= window_size / 2)
288 freq_real[dest_i] = freq_real[i];
289 freq_imag[dest_i] = freq_imag[i];
297 symmetry(window_size, freq_real, freq_imag);
308 TimeStretchResample::TimeStretchResample(TimeStretch *plugin)
310 this->plugin = plugin;
314 int TimeStretchResample::read_samples(Samples *buffer,
318 return plugin->read_samples(buffer,
320 start + plugin->get_source_start(),
334 TimeStretch::TimeStretch(PluginServer *server)
335 : PluginAClient(server)
346 TimeStretch::~TimeStretch()
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;
357 const char* TimeStretch::plugin_title() { return N_("Time stretch"); }
359 int TimeStretch::get_parameters()
362 TimeStretchWindow window(this, info.get_abs_cursor_x(), info.get_abs_cursor_y());
363 window.create_objects();
364 int result = window.run_window();
369 int TimeStretch::start_loop()
371 scaled_size = (int64_t)(get_total_len() * scale);
372 if(PluginClient::interactive)
374 char string[BCTEXTLEN];
375 sprintf(string, "%s...", plugin_title());
376 progress = start_progress(string, scaled_size);
379 current_position = get_source_start();
388 pitch = new PitchEngine(this);
389 pitch->initialize(WINDOW_SIZE);
390 resample = new TimeStretchResample(this);
393 // The windowing case
395 // Must be short enough to mask beating but long enough to mask humming.
396 stretch = new TimeStretchEngine(scale,
397 PluginAClient::project_sample_rate,
407 int TimeStretch::stop_loop()
409 if(PluginClient::interactive)
411 progress->stop_progress();
417 int TimeStretch::process_loop(Samples *buffer, int64_t &write_length)
420 int64_t predicted_total = (int64_t)((double)get_total_len() * scale + 0.5);
427 int samples_rendered = 0;
438 samples_rendered = get_buffer_size();
439 pitch->process_buffer(total_written,
445 // The windowing case
447 // Length to read based on desired output size
448 int64_t size = (int64_t)((double)get_buffer_size() / scale);
450 if(input_allocated < size)
452 if(input) delete input;
453 input = new Samples(size);
454 input_allocated = size;
457 read_samples(input, 0, current_position, size);
458 current_position += size;
460 samples_rendered = stretch->process(input, size);
463 samples_rendered = MIN(samples_rendered, get_buffer_size());
464 stretch->read_output(buffer, samples_rendered);
469 total_written += samples_rendered;
471 // Trim output to predicted length of stretched selection.
472 if(total_written > predicted_total)
474 samples_rendered -= total_written - predicted_total;
479 write_length = samples_rendered;
480 if(PluginClient::interactive) result = progress->update(total_written);
487 int TimeStretch::load_defaults()
489 char directory[BCTEXTLEN];
491 // set the default directory
492 sprintf(directory, "%s/timestretch.rc", File::get_config_path());
494 defaults = new BC_Hash(directory);
497 scale = defaults->get("SCALE", (double)1);
498 use_fft = defaults->get("USE_FFT", 0);
502 int TimeStretch::save_defaults()
504 defaults->update("SCALE", scale);
505 defaults->update("USE_FFT", use_fft);