initial commit
[goodguy/history.git] / cinelerra-5.0 / plugins / pitch / pitch.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2008 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 "filexml.h"
26 #include "language.h"
27 #include "pitch.h"
28 #include "samples.h"
29 #include "theme.h"
30 #include "units.h"
31 #include "vframe.h"
32
33 #include <math.h>
34 #include <string.h>
35
36
37 // It needs to be at least 40Hz yet high enough to have enough precision
38 #define WINDOW_SIZE 2048
39
40
41 //#define WINDOW_SIZE 131072
42
43 REGISTER_PLUGIN(PitchEffect);
44
45
46
47
48
49 PitchEffect::PitchEffect(PluginServer *server)
50  : PluginAClient(server)
51 {
52         
53         reset();
54 }
55
56 PitchEffect::~PitchEffect()
57 {
58         
59
60         if(fft) delete fft;
61 }
62
63 const char* PitchEffect::plugin_title() { return N_("Pitch shift"); }
64 int PitchEffect::is_realtime() { return 1; }
65
66
67
68 void PitchEffect::read_data(KeyFrame *keyframe)
69 {
70         FileXML input;
71         input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
72
73         int result = 0;
74         while(!result)
75         {
76                 result = input.read_tag();
77
78                 if(!result)
79                 {
80                         if(input.tag.title_is("PITCH"))
81                         {
82                                 config.scale = input.tag.get_property("SCALE", config.scale);
83                                 config.size = input.tag.get_property("SIZE", config.size);
84                         }
85                 }
86         }
87 }
88
89 void PitchEffect::save_data(KeyFrame *keyframe)
90 {
91         FileXML output;
92         output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
93
94         output.tag.set_title("PITCH");
95         output.tag.set_property("SCALE", config.scale);
96         output.tag.set_property("SIZE", config.size);
97         output.append_tag();
98         output.append_newline();
99
100         output.terminate_string();
101 }
102
103
104
105 LOAD_CONFIGURATION_MACRO(PitchEffect, PitchConfig)
106
107 NEW_WINDOW_MACRO(PitchEffect, PitchWindow)
108
109
110
111 void PitchEffect::reset()
112 {
113         fft = 0;
114 }
115
116 void PitchEffect::update_gui()
117 {
118         if(thread)
119         {
120                 if(load_configuration())
121                 {
122                         thread->window->lock_window("PitchEffect::update_gui");
123                         ((PitchWindow*)thread->window)->update();
124                         thread->window->unlock_window();
125                 }
126         }
127 }
128
129
130
131 int PitchEffect::process_buffer(int64_t size, 
132                 Samples *buffer,
133                 int64_t start_position,
134                 int sample_rate)
135 {
136 //printf("PitchEffect::process_buffer %d\n", __LINE__);
137         load_configuration();
138
139         if(fft && config.size != fft->window_size)
140         {
141                 delete fft;
142                 fft = 0;
143         }
144
145         if(!fft)
146         {
147                 fft = new PitchFFT(this);
148                 fft->initialize(config.size);
149         }
150
151         fft->process_buffer(start_position,
152                 size, 
153                 buffer,
154                 get_direction());
155
156         return 0;
157 }
158
159
160
161
162
163
164
165
166
167 PitchFFT::PitchFFT(PitchEffect *plugin)
168  : CrossfadeFFT()
169 {
170         this->plugin = plugin;
171         last_phase = 0;
172         new_freq = 0;
173         new_magn = 0;
174         sum_phase = 0;
175         anal_magn = 0;
176         anal_freq = 0;
177 }
178
179 PitchFFT::~PitchFFT()
180 {
181         delete [] last_phase;
182         delete [] new_freq;
183         delete [] new_magn;
184         delete [] sum_phase;
185         delete [] anal_magn;
186         delete [] anal_freq;
187 }
188
189
190 int PitchFFT::signal_process()
191 {
192 #if 0
193         double scale = plugin->config.scale;
194         double oversample = 1.0;
195
196         if(!new_freq)
197         {
198                 last_phase = new double[window_size];
199                 new_freq = new double[window_size];
200                 new_magn = new double[window_size];
201                 sum_phase = new double[window_size];
202                 anal_magn = new double[window_size];
203                 anal_freq = new double[window_size];
204                 memset (last_phase, 0, window_size * sizeof(double));
205                 memset (sum_phase, 0, window_size * sizeof(double));
206         }
207
208         memset(new_freq, 0, window_size * sizeof(double));
209         memset(new_magn, 0, window_size * sizeof(double));
210         
211 // expected phase difference between windows
212         double expected_phase_diff = 2.0 * M_PI / oversample; 
213 // frequency per bin
214         double freq_per_bin = (double)plugin->PluginAClient::project_sample_rate / window_size;
215
216         for (int i = 0; i < window_size / 2; i++) 
217         {
218 // Convert to magnitude and phase
219                 double magn = sqrt(freq_real[i] * freq_real[i] + freq_imag[i] * freq_imag[i]);
220                 double phase = atan2(freq_imag[i], freq_real[i]);
221
222 // Remember last phase
223                 double temp = phase - last_phase[i];
224                 last_phase[i] = phase;
225
226 // Substract the expected advancement of phase
227                 temp -= (double)i * expected_phase_diff;
228
229
230 // wrap temp into -/+ PI ...  good trick!
231                 int qpd = (int)(temp/M_PI);
232                 if (qpd >= 0) 
233                         qpd += qpd & 1;
234                 else 
235                         qpd -= qpd & 1;
236                 temp -= M_PI * (double)qpd;     
237
238 // Deviation from bin frequency 
239                 temp = oversample * temp / (2.0 * M_PI);
240
241                 temp = (double)(temp + i) * freq_per_bin;
242
243 // Now temp is the real freq... move it!
244                 int new_bin = (int)(i * scale);
245                 if (new_bin >= 0 && new_bin < window_size / 2)
246                 {
247                         new_freq[new_bin] = temp*scale;
248                         new_magn[new_bin] += magn;
249                 }
250
251         }
252
253
254
255
256 // Synthesize back the fft window 
257         for (int i = 0; i < window_size / 2; i++) 
258         {
259                 double magn = new_magn[i];
260                 double temp = new_freq[i];
261 // substract the bin frequency
262                 temp -= (double)(i) * freq_per_bin;
263
264 // get bin deviation from freq deviation
265                 temp /= freq_per_bin;
266
267 // oversample 
268                 temp = 2.0 * M_PI * temp / oversample;
269
270 // add back the expected phase difference (that we substracted in analysis)
271                 temp += (double)(i) * expected_phase_diff;
272
273 // accumulate delta phase, to get bin phase
274                 sum_phase[i] += temp;
275
276                 double phase = sum_phase[i];
277
278                 freq_real[i] = magn * cos(phase);
279                 freq_imag[i] = magn * sin(phase);
280         }
281
282         for (int i = window_size / 2; i < window_size; i++)
283         {
284                 freq_real[i] = 0;
285                 freq_imag[i] = 0;
286         }
287 #endif
288
289
290 #if 1
291         int min_freq = 
292                 1 + (int)(20.0 / ((double)plugin->PluginAClient::project_sample_rate / 
293                         window_size * 2) + 0.5);
294         if(plugin->config.scale < 1)
295         {
296                 for(int i = min_freq; i < window_size / 2; i++)
297                 {
298                         double destination = i * plugin->config.scale;
299                         int dest_i = (int)(destination + 0.5);
300                         if(dest_i != i)
301                         {
302                                 if(dest_i <= window_size / 2)
303                                 {
304                                         freq_real[dest_i] = freq_real[i];
305                                         freq_imag[dest_i] = freq_imag[i];
306                                 }
307                                 freq_real[i] = 0;
308                                 freq_imag[i] = 0;
309                         }
310                 }
311         }
312         else
313         if(plugin->config.scale > 1)
314         {
315                 for(int i = window_size / 2 - 1; i >= min_freq; i--)
316                 {
317                         double destination = i * plugin->config.scale;
318                         int dest_i = (int)(destination + 0.5);
319                         if(dest_i != i)
320                         {
321                                 if(dest_i <= window_size / 2)
322                                 {
323                                         freq_real[dest_i] = freq_real[i];
324                                         freq_imag[dest_i] = freq_imag[i];
325                                 }
326                                 freq_real[i] = 0;
327                                 freq_imag[i] = 0;
328                         }
329                 }
330         }
331
332         symmetry(window_size, freq_real, freq_imag);
333 #endif
334
335
336         return 0;
337 }
338
339 int PitchFFT::read_samples(int64_t output_sample, 
340         int samples, 
341         Samples *buffer)
342 {
343         return plugin->read_samples(buffer,
344                 0,
345                 plugin->get_samplerate(),
346                 output_sample,
347                 samples);
348 }
349
350
351
352
353
354
355
356 PitchConfig::PitchConfig()
357 {
358         scale = 1.0;
359         size = 2048;
360 }
361
362 int PitchConfig::equivalent(PitchConfig &that)
363 {
364         return EQUIV(scale, that.scale) && size == that.size;
365 }
366
367 void PitchConfig::copy_from(PitchConfig &that)
368 {
369         scale = that.scale;
370         size = that.size;
371 }
372
373 void PitchConfig::interpolate(PitchConfig &prev, 
374         PitchConfig &next, 
375         int64_t prev_frame, 
376         int64_t next_frame, 
377         int64_t current_frame)
378 {
379         double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
380         double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
381         scale = prev.scale * prev_scale + next.scale * next_scale;
382         size = prev.size;
383 }
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401 PitchWindow::PitchWindow(PitchEffect *plugin)
402  : PluginClientWindow(plugin, 
403         150, 
404         100, 
405         150, 
406         100,
407         0)
408 {
409         this->plugin = plugin;
410 }
411
412 void PitchWindow::create_objects()
413 {
414         int x1 = 10, x = 10, y = 10;
415         
416         BC_Title *title;
417         add_subwindow(title = new BC_Title(x, y, _("Scale:")));
418         x += title->get_w() + plugin->get_theme()->widget_border;
419         add_subwindow(scale = new PitchScale(plugin, x, y));
420         x = x1;
421         y += scale->get_h() + plugin->get_theme()->widget_border;
422         add_subwindow(title = new BC_Title(x, y, _("Window Size:")));
423         y += title->get_h() + plugin->get_theme()->widget_border;
424         add_subwindow(size = new PitchSize(this, plugin, x, y));
425         size->create_objects();
426         size->update(plugin->config.size);
427         show_window();
428         flush();
429 }
430
431
432
433 void PitchWindow::update()
434 {
435         scale->update(plugin->config.scale);
436         size->update(plugin->config.size);
437 }
438
439
440
441
442
443
444
445
446
447
448
449
450 PitchScale::PitchScale(PitchEffect *plugin, int x, int y)
451  : BC_FPot(x, y, (float)plugin->config.scale, .5, 1.5)
452 {
453         this->plugin = plugin;
454         set_precision(0.01);
455 }
456
457 int PitchScale::handle_event()
458 {
459         plugin->config.scale = get_value();
460         plugin->send_configure_change();
461         return 1;
462 }
463
464
465
466 PitchSize::PitchSize(PitchWindow *window, PitchEffect *plugin, int x, int y)
467  : BC_PopupMenu(x, y, 100, "4096", 1)
468 {
469         this->plugin = plugin;
470 }
471
472 int PitchSize::handle_event()
473 {
474         plugin->config.size = atoi(get_text());
475         plugin->send_configure_change();
476         return 1;
477 }
478
479 void PitchSize::create_objects()
480 {
481         add_item(new BC_MenuItem("2048"));
482         add_item(new BC_MenuItem("4096"));
483         add_item(new BC_MenuItem("8192"));
484         add_item(new BC_MenuItem("16384"));
485         add_item(new BC_MenuItem("32768"));
486         add_item(new BC_MenuItem("65536"));
487         add_item(new BC_MenuItem("131072"));
488         add_item(new BC_MenuItem("262144"));
489 }
490
491 void PitchSize::update(int size)
492 {
493         char string[BCTEXTLEN];
494         sprintf(string, "%d", size);
495         set_text(string);
496 }
497
498
499
500
501
502
503
504
505
506
507
508
509
510