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"
26 #include "mainprogress.h"
30 #include "timestretch.h"
31 #include "timestretchengine.h"
32 #include "transportque.inc"
38 #define WINDOW_SIZE 4096
39 // Time stretch window time
40 #define WINDOW_TIME 40
41 #define INPUT_SIZE 65536
44 REGISTER_PLUGIN(TimeStretch)
50 TimeStretchFraction::TimeStretchFraction(TimeStretch *plugin, int x, int y)
51 : BC_TextBox(x, y, 100, 1, (float)(1.0 / plugin->scale))
53 this->plugin = plugin;
56 int TimeStretchFraction::handle_event()
58 plugin->scale = 1.0 / atof(get_text());
66 TimeStretchFreq::TimeStretchFreq(TimeStretch *plugin,
67 TimeStretchWindow *gui,
70 : BC_Radial(x, y, plugin->use_fft, _("Use fast fourier transform"))
72 this->plugin = plugin;
76 int TimeStretchFreq::handle_event()
89 TimeStretchTime::TimeStretchTime(TimeStretch *plugin,
90 TimeStretchWindow *gui,
93 : BC_Radial(x, y, !plugin->use_fft, _("Use overlapping windows"))
95 this->plugin = plugin;
99 int TimeStretchTime::handle_event()
103 gui->freq->update(0);
118 TimeStretchWindow::TimeStretchWindow(TimeStretch *plugin, int x, int y)
119 : BC_Window(_(PROGRAM_NAME ": Time stretch"),
130 this->plugin = plugin;
134 TimeStretchWindow::~TimeStretchWindow()
138 void TimeStretchWindow::create_objects()
143 add_subwindow(title = new BC_Title(x, y, _("Fraction of original speed:")));
144 y += title->get_h() + plugin->get_theme()->widget_border;
146 TimeStretchFraction *fraction;
147 add_subwindow(fraction = new TimeStretchFraction(plugin, x, y));
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));
154 add_subwindow(new BC_OKButton(this));
155 add_subwindow(new BC_CancelButton(this));
172 PitchEngine::PitchEngine(TimeStretch *plugin)
175 this->plugin = plugin;
179 current_position = 0;
183 PitchEngine::~PitchEngine()
185 if(input_buffer) delete [] input_buffer;
186 if(temp) delete [] temp;
189 int PitchEngine::read_samples(int64_t output_sample,
193 plugin->resample->resample(buffer,
196 (int)(1000000 * plugin->scale),
200 // while(input_size < samples)
202 // if(!temp) temp = new double[INPUT_SIZE];
204 // plugin->read_samples(temp,
206 // plugin->get_source_start() + current_position,
208 // current_position += INPUT_SIZE;
210 // plugin->resample->resample(buffer,
213 // (int)(1000000 * plugin->scale),
216 // int fragment_size = plugin->resample->get_output_size(0);
218 // if(input_size + fragment_size > input_allocated)
220 // int new_allocated = input_size + fragment_size;
221 // double *new_buffer = new double[new_allocated];
224 // memcpy(new_buffer, input_buffer, input_size * sizeof(double));
225 // delete [] input_buffer;
227 // input_buffer = new_buffer;
228 // input_allocated = new_allocated;
232 // plugin->resample->read_output(input_buffer + input_size,
235 // input_size += fragment_size;
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;
245 int PitchEngine::signal_process()
250 ((double)plugin->PluginAClient::project_sample_rate /
255 if(plugin->scale < 1)
257 for(int i = min_freq; i < window_size / 2; i++)
259 double destination = i * plugin->scale;
260 int dest_i = (int)(destination + 0.5);
263 if(dest_i <= window_size / 2)
265 freq_real[dest_i] = freq_real[i];
266 freq_imag[dest_i] = freq_imag[i];
274 if(plugin->scale > 1)
276 for(int i = window_size / 2 - 1; i >= min_freq; i--)
278 double destination = i * plugin->scale;
279 int dest_i = (int)(destination + 0.5);
282 if(dest_i <= window_size / 2)
284 freq_real[dest_i] = freq_real[i];
285 freq_imag[dest_i] = freq_imag[i];
293 symmetry(window_size, freq_real, freq_imag);
304 TimeStretchResample::TimeStretchResample(TimeStretch *plugin)
306 this->plugin = plugin;
310 int TimeStretchResample::read_samples(Samples *buffer,
314 return plugin->read_samples(buffer,
316 start + plugin->get_source_start(),
330 TimeStretch::TimeStretch(PluginServer *server)
331 : PluginAClient(server)
342 TimeStretch::~TimeStretch()
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;
353 const char* TimeStretch::plugin_title() { return _("Time stretch"); }
355 int TimeStretch::get_parameters()
358 TimeStretchWindow window(this, info.get_abs_cursor_x(), info.get_abs_cursor_y());
359 window.create_objects();
360 int result = window.run_window();
365 int TimeStretch::start_loop()
367 scaled_size = (int64_t)(get_total_len() * scale);
368 if(PluginClient::interactive)
370 char string[BCTEXTLEN];
371 sprintf(string, "%s...", plugin_title());
372 progress = start_progress(string, scaled_size);
375 current_position = get_source_start();
384 pitch = new PitchEngine(this);
385 pitch->initialize(WINDOW_SIZE);
386 resample = new TimeStretchResample(this);
389 // The windowing case
391 // Must be short enough to mask beating but long enough to mask humming.
392 stretch = new TimeStretchEngine(scale,
393 PluginAClient::project_sample_rate,
403 int TimeStretch::stop_loop()
405 if(PluginClient::interactive)
407 progress->stop_progress();
413 int TimeStretch::process_loop(Samples *buffer, int64_t &write_length)
416 int64_t predicted_total = (int64_t)((double)get_total_len() * scale + 0.5);
423 int samples_rendered = 0;
434 samples_rendered = get_buffer_size();
435 pitch->process_buffer(total_written,
441 // The windowing case
443 // Length to read based on desired output size
444 int64_t size = (int64_t)((double)get_buffer_size() / scale);
446 if(input_allocated < size)
448 if(input) delete input;
449 input = new Samples(size);
450 input_allocated = size;
453 read_samples(input, 0, current_position, size);
454 current_position += size;
456 samples_rendered = stretch->process(input, size);
459 samples_rendered = MIN(samples_rendered, get_buffer_size());
460 stretch->read_output(buffer, samples_rendered);
465 total_written += samples_rendered;
467 // Trim output to predicted length of stretched selection.
468 if(total_written > predicted_total)
470 samples_rendered -= total_written - predicted_total;
475 write_length = samples_rendered;
476 if(PluginClient::interactive) result = progress->update(total_written);
483 int TimeStretch::load_defaults()
485 char directory[BCTEXTLEN];
487 // set the default directory
488 sprintf(directory, "%stimestretch.rc", BCASTDIR);
490 defaults = new BC_Hash(directory);
493 scale = defaults->get("SCALE", (double)1);
494 use_fft = defaults->get("USE_FFT", 0);
498 int TimeStretch::save_defaults()
500 defaults->update("SCALE", scale);
501 defaults->update("USE_FFT", use_fft);