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