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, 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;
135 TimeStretchWindow::~TimeStretchWindow()
139 void TimeStretchWindow::create_objects()
144 add_subwindow(title = new BC_Title(x, y, _("Fraction of original speed:")));
145 y += title->get_h() + plugin->get_theme()->widget_border;
147 TimeStretchFraction *fraction;
148 add_subwindow(fraction = new TimeStretchFraction(plugin, x, y));
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));
155 add_subwindow(new BC_OKButton(this));
156 add_subwindow(new BC_CancelButton(this));
173 PitchEngine::PitchEngine(TimeStretch *plugin)
176 this->plugin = plugin;
180 current_position = 0;
184 PitchEngine::~PitchEngine()
186 if(input_buffer) delete [] input_buffer;
187 if(temp) delete [] temp;
190 int PitchEngine::read_samples(int64_t output_sample,
194 plugin->resample->resample(buffer,
197 (int)(1000000 * plugin->scale),
201 // while(input_size < samples)
203 // if(!temp) temp = new double[INPUT_SIZE];
205 // plugin->read_samples(temp,
207 // plugin->get_source_start() + current_position,
209 // current_position += INPUT_SIZE;
211 // plugin->resample->resample(buffer,
214 // (int)(1000000 * plugin->scale),
217 // int fragment_size = plugin->resample->get_output_size(0);
219 // if(input_size + fragment_size > input_allocated)
221 // int new_allocated = input_size + fragment_size;
222 // double *new_buffer = new double[new_allocated];
225 // memcpy(new_buffer, input_buffer, input_size * sizeof(double));
226 // delete [] input_buffer;
228 // input_buffer = new_buffer;
229 // input_allocated = new_allocated;
233 // plugin->resample->read_output(input_buffer + input_size,
236 // input_size += fragment_size;
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;
246 int PitchEngine::signal_process()
251 ((double)plugin->PluginAClient::project_sample_rate /
256 if(plugin->scale < 1)
258 for(int i = min_freq; i < window_size / 2; i++)
260 double destination = i * plugin->scale;
261 int dest_i = (int)(destination + 0.5);
264 if(dest_i <= window_size / 2)
266 freq_real[dest_i] = freq_real[i];
267 freq_imag[dest_i] = freq_imag[i];
275 if(plugin->scale > 1)
277 for(int i = window_size / 2 - 1; i >= min_freq; i--)
279 double destination = i * plugin->scale;
280 int dest_i = (int)(destination + 0.5);
283 if(dest_i <= window_size / 2)
285 freq_real[dest_i] = freq_real[i];
286 freq_imag[dest_i] = freq_imag[i];
294 symmetry(window_size, freq_real, freq_imag);
305 TimeStretchResample::TimeStretchResample(TimeStretch *plugin)
307 this->plugin = plugin;
311 int TimeStretchResample::read_samples(Samples *buffer,
315 return plugin->read_samples(buffer,
317 start + plugin->get_source_start(),
331 TimeStretch::TimeStretch(PluginServer *server)
332 : PluginAClient(server)
343 TimeStretch::~TimeStretch()
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;
354 const char* TimeStretch::plugin_title() { return _("Time stretch"); }
356 int TimeStretch::get_parameters()
359 TimeStretchWindow window(this, info.get_abs_cursor_x(), info.get_abs_cursor_y());
360 window.create_objects();
361 int result = window.run_window();
366 int TimeStretch::start_loop()
368 scaled_size = (int64_t)(get_total_len() * scale);
369 if(PluginClient::interactive)
371 char string[BCTEXTLEN];
372 sprintf(string, "%s...", plugin_title());
373 progress = start_progress(string, scaled_size);
376 current_position = get_source_start();
385 pitch = new PitchEngine(this);
386 pitch->initialize(WINDOW_SIZE);
387 resample = new TimeStretchResample(this);
390 // The windowing case
392 // Must be short enough to mask beating but long enough to mask humming.
393 stretch = new TimeStretchEngine(scale,
394 PluginAClient::project_sample_rate,
404 int TimeStretch::stop_loop()
406 if(PluginClient::interactive)
408 progress->stop_progress();
414 int TimeStretch::process_loop(Samples *buffer, int64_t &write_length)
417 int64_t predicted_total = (int64_t)((double)get_total_len() * scale + 0.5);
424 int samples_rendered = 0;
435 samples_rendered = get_buffer_size();
436 pitch->process_buffer(total_written,
442 // The windowing case
444 // Length to read based on desired output size
445 int64_t size = (int64_t)((double)get_buffer_size() / scale);
447 if(input_allocated < size)
449 if(input) delete input;
450 input = new Samples(size);
451 input_allocated = size;
454 read_samples(input, 0, current_position, size);
455 current_position += size;
457 samples_rendered = stretch->process(input, size);
460 samples_rendered = MIN(samples_rendered, get_buffer_size());
461 stretch->read_output(buffer, samples_rendered);
466 total_written += samples_rendered;
468 // Trim output to predicted length of stretched selection.
469 if(total_written > predicted_total)
471 samples_rendered -= total_written - predicted_total;
476 write_length = samples_rendered;
477 if(PluginClient::interactive) result = progress->update(total_written);
484 int TimeStretch::load_defaults()
486 char directory[BCTEXTLEN];
488 // set the default directory
489 sprintf(directory, "%s/timestretch.rc", File::get_config_path());
491 defaults = new BC_Hash(directory);
494 scale = defaults->get("SCALE", (double)1);
495 use_fft = defaults->get("USE_FFT", 0);
499 int TimeStretch::save_defaults()
501 defaults->update("SCALE", scale);
502 defaults->update("USE_FFT", use_fft);