rework canvas zoom, add 3 plugins from 7.2, tweak cwdw boundry, vdevicex11 dupl close...
[goodguy/cinelerra.git] / cinelerra-5.1 / plugins / flanger / flanger.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2017-2019 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 "clip.h"
23 #include "bcdisplayinfo.h"
24 #include "bchash.h"
25 #include "bcsignals.h"
26 #include "filexml.h"
27 #include "flanger.h"
28 #include "guicast.h"
29 #include "language.h"
30 #include "samples.h"
31 #include "theme.h"
32 #include "transportque.inc"
33 #include "units.h"
34
35
36 #include <math.h>
37 #include <string.h>
38 #include <time.h>
39 #include <unistd.h>
40
41
42 #define MIN_RATE 0.1
43 #define MAX_RATE 10.0
44 #define MIN_OFFSET 0.0
45 #define MAX_OFFSET 100.0
46 #define MIN_DEPTH 0.0
47 #define MAX_DEPTH 100.0
48 #define MIN_STARTING_PHASE 0
49 #define MAX_STARTING_PHASE 100
50
51
52 PluginClient* new_plugin(PluginServer *server)
53 {
54         return new Flanger(server);
55 }
56
57
58
59 Flanger::Flanger(PluginServer *server)
60  : PluginAClient(server)
61 {
62         need_reconfigure = 1;
63     dsp_in = 0;
64     dsp_in_allocated = 0;
65     last_position = -1;
66     flanging_table = 0;
67     table_size = 0;
68     history_buffer = 0;
69     history_size = 0;
70 }
71
72 Flanger::~Flanger()
73 {
74     if(dsp_in)
75     {
76         delete [] dsp_in;
77     }
78
79     if(history_buffer)
80     {
81         delete [] history_buffer;
82     }
83
84     delete [] flanging_table;
85 }
86
87 const char* Flanger::plugin_title() { return N_("Flanger"); }
88 int Flanger::is_realtime() { return 1; }
89 int Flanger::is_multichannel() { return 0; }
90 int Flanger::is_synthesis() { return 0; }
91 // phyllis VFrame* Flanger::new_picon() { return 0; }
92
93
94 int Flanger::process_buffer(int64_t size, 
95         Samples *buffer, 
96         int64_t start_position,
97         int sample_rate)
98 {
99     need_reconfigure |= load_configuration();
100 // printf("Flanger::process_buffer %d start_position=%ld size=%ld\n",
101 // __LINE__,
102 // start_position, 
103 // size);
104
105
106
107 // reset after seeking & configuring
108     if(last_position != start_position || need_reconfigure)
109     {
110         need_reconfigure = 0;
111
112         if(flanging_table)
113         {
114             delete [] flanging_table;
115         }
116
117 // flanging waveform is a whole number of samples that repeats
118         table_size = (int)((double)sample_rate / config.rate);
119         if(table_size % 2)
120         {
121             table_size++;
122         }
123
124         flanging_table = new flange_sample_t[table_size];
125         double depth_samples = config.depth * 
126             sample_rate / 1000;
127 // read behind so the flange can work in realtime
128         double ratio = (double)depth_samples /
129             (table_size / 2);
130 // printf("Flanger::process_buffer %d %f %f\n", 
131 // __LINE__, 
132 // depth_samples,
133 // sample_rate / 2 - depth_samples);
134         for(int i = 0; i <= table_size / 2; i++)
135         {
136             double input_sample = -i * ratio;
137 // printf("Flanger::process_buffer %d i=%d input_sample=%f ratio=%f\n", 
138 // __LINE__, 
139 // i, 
140 // input_sample,
141 // ratio);
142             flanging_table[i].input_sample = input_sample;
143             flanging_table[i].input_period = ratio;
144         }
145         
146         for(int i = table_size / 2 + 1; i < table_size; i++)
147         {
148             double input_sample = -ratio * (table_size - i);
149             flanging_table[i].input_sample = input_sample;
150             flanging_table[i].input_period = ratio;
151 // printf("Flanger::process_buffer %d i=%d input_sample=%f ratio=%f\n", 
152 // __LINE__, 
153 // i, 
154 // input_sample,
155 // ratio);
156         }
157
158
159
160 // compute the phase position from the keyframe position & the phase offset
161                 int64_t prev_position = edl_to_local(
162                         get_prev_keyframe(
163                                 get_source_position())->position);
164
165                 if(prev_position == 0)
166                 {
167                         prev_position = get_source_start();
168                 }
169
170         voice.table_offset = (int64_t)(start_position - 
171             prev_position + 
172             config.starting_phase * table_size / 100) % table_size;
173 // printf("Flanger::process_buffer %d start_position=%ld table_offset=%d table_size=%d\n", 
174 // __LINE__,
175 // start_position,
176 // voice.table_offset,
177 // table_size);
178     }
179
180     int starting_offset = (int)(config.offset * sample_rate / 1000);
181 //phyllis    int depth_offset = (int)(config.depth * sample_rate / 1000);
182     reallocate_dsp(size);
183 //    reallocate_history(starting_offset + depth_offset + 1);
184 // always use the maximum history, in case of keyframes
185     reallocate_history((MAX_OFFSET + MAX_DEPTH) * sample_rate / 1000 + 1);
186
187 // read the input
188         read_samples(buffer,
189                 0,
190                 sample_rate,
191                 start_position,
192                 size);
193
194
195
196 // paint the voices
197     double wetness = DB::fromdb(config.wetness);
198     if(config.wetness <= INFINITYGAIN)
199     {
200         wetness = 0;
201     }
202
203     double *output = dsp_in;
204     double *input = buffer->get_data();
205
206 // input signal
207     for(int j = 0; j < size; j++)
208     {
209         output[j] = input[j] * wetness;
210     }
211
212
213 // delayed signal
214     int table_offset = voice.table_offset;
215     for(int j = 0; j < size; j++)
216     {
217         flange_sample_t *table = &flanging_table[table_offset];
218         double input_sample = j - starting_offset + table->input_sample;
219 //phyllis        double input_period = table->input_period;
220
221 // if(j == 0)
222 // printf("Flanger::process_buffer %d input_sample=%f\n", 
223 // __LINE__,
224 // input_sample);
225
226 // values to interpolate
227         double sample1;
228         double sample2;
229         int input_sample1 = (int)(input_sample);
230         int input_sample2 = (int)(input_sample + 1);
231         double fraction1 = (double)((int)(input_sample + 1)) - input_sample;
232         double fraction2 = 1.0 - fraction1;
233         if(input_sample1 < 0)
234         {
235             sample1 = history_buffer[history_size + input_sample1];
236         }
237         else
238         {
239             sample1 = input[input_sample1];
240         }
241
242         if(input_sample2 < 0)
243         {
244             sample2 = history_buffer[history_size + input_sample2];
245         }
246         else
247         {
248             sample2 = input[input_sample2];
249         }
250         output[j] += sample1 * fraction1 + sample2 * fraction2;
251
252         table_offset++;
253         table_offset %= table_size;
254     }
255     voice.table_offset = table_offset;
256
257 // history is bigger than input buffer.  Copy entire input buffer.
258     if(history_size > size)
259     {
260         memcpy(history_buffer, 
261             history_buffer + size,
262             (history_size - size) * sizeof(double));
263         memcpy(history_buffer + (history_size - size),
264             buffer->get_data(),
265             size * sizeof(double));
266     }
267     else
268     {
269 // input is bigger than history buffer.  Copy only history size
270        memcpy(history_buffer,
271             buffer->get_data() + size - history_size,
272             history_size * sizeof(double));
273     }
274 //printf("Flanger::process_buffer %d\n", 
275 //__LINE__);
276
277
278 // copy the DSP buffer to the output
279     memcpy(buffer->get_data(), dsp_in, size * sizeof(double));
280
281
282     if(get_direction() == PLAY_FORWARD)
283     {
284         last_position = start_position + size;
285     }
286     else
287     {
288         last_position = start_position - size;
289     }
290
291     
292
293     return 0;
294 }
295
296
297 void Flanger::reallocate_dsp(int new_dsp_allocated)
298 {
299     if(new_dsp_allocated > dsp_in_allocated)
300     {
301         if(dsp_in)
302         {
303             delete [] dsp_in;
304         }
305         dsp_in = new double[new_dsp_allocated];
306         dsp_in_allocated = new_dsp_allocated;
307     }
308 }
309
310 void Flanger::reallocate_history(int new_size)
311 {
312     if(new_size != history_size)
313     {
314 // copy samples already read into the new buffers
315         double *new_history = new double[new_size];
316         bzero(new_history, sizeof(double) * new_size);
317         if(history_buffer)
318         {
319             int copy_size = MIN(new_size, history_size);
320             memcpy(new_history, 
321                 history_buffer + history_size - copy_size, 
322                 sizeof(double) * copy_size);
323             delete [] history_buffer;
324         }
325         history_buffer = new_history;
326         history_size = new_size;
327     }
328 }
329
330
331 NEW_WINDOW_MACRO(Flanger, FlangerWindow)
332 LOAD_CONFIGURATION_MACRO(Flanger, FlangerConfig)
333
334
335 void Flanger::save_data(KeyFrame *keyframe)
336 {
337         FileXML output;
338
339 // cause xml file to store data directly in text
340         output.set_shared_output(keyframe->xbuf);
341
342         output.tag.set_title("FLANGER");
343         output.tag.set_property("OFFSET", config.offset);
344         output.tag.set_property("STARTING_PHASE", config.starting_phase);
345         output.tag.set_property("DEPTH", config.depth);
346         output.tag.set_property("RATE", config.rate);
347         output.tag.set_property("WETNESS", config.wetness);
348         output.append_tag();
349         output.append_newline();
350         
351         
352         
353         output.terminate_string();
354 }
355
356 void Flanger::read_data(KeyFrame *keyframe)
357 {
358         FileXML input;
359 // cause xml file to read directly from text
360         input.set_shared_input(keyframe->xbuf);
361         int result = 0;
362
363         result = input.read_tag();
364
365         if(!result)
366         {
367                 if(input.tag.title_is("FLANGER"))
368                 {
369                         config.offset = input.tag.get_property("OFFSET", config.offset);
370                         config.starting_phase = input.tag.get_property("STARTING_PHASE", config.starting_phase);
371                         config.depth = input.tag.get_property("DEPTH", config.depth);
372                         config.rate = input.tag.get_property("RATE", config.rate);
373                         config.wetness = input.tag.get_property("WETNESS", config.wetness);
374                 }
375         }
376
377         config.boundaries();
378 }
379
380 void Flanger::update_gui()
381 {
382         if(thread)
383         {
384                 if(load_configuration())
385                 {
386                         thread->window->lock_window("Flanger::update_gui 1");
387             ((FlangerWindow*)thread->window)->update();
388                         thread->window->unlock_window();
389                 }
390         }
391 }
392
393
394
395
396 Voice::Voice()
397 {
398 }
399
400
401
402
403
404
405 FlangerConfig::FlangerConfig()
406 {
407         offset = 0.00;
408         starting_phase = 0;
409         depth = 10.0;
410         rate = 0.20;
411         wetness = 0;
412 }
413
414 int FlangerConfig::equivalent(FlangerConfig &that)
415 {
416         return EQUIV(offset, that.offset) &&
417                 EQUIV(starting_phase, that.starting_phase) &&
418                 EQUIV(depth, that.depth) &&
419                 EQUIV(rate, that.rate) &&
420                 EQUIV(wetness, that.wetness);
421 }
422
423 void FlangerConfig::copy_from(FlangerConfig &that)
424 {
425         offset = that.offset;
426         starting_phase = that.starting_phase;
427         depth = that.depth;
428         rate = that.rate;
429         wetness = that.wetness;
430 }
431
432 void FlangerConfig::interpolate(FlangerConfig &prev, 
433         FlangerConfig &next, 
434         int64_t prev_frame, 
435         int64_t next_frame, 
436         int64_t current_frame)
437 {
438         copy_from(prev);
439 }
440
441 void FlangerConfig::boundaries()
442 {
443         CLAMP(offset, MIN_OFFSET, MAX_OFFSET);
444         CLAMP(starting_phase, MIN_STARTING_PHASE, MAX_STARTING_PHASE);
445         CLAMP(depth, MIN_DEPTH, MAX_DEPTH);
446         CLAMP(rate, MIN_RATE, MAX_RATE);
447         CLAMP(wetness, INFINITYGAIN, 0.0);
448 }
449
450
451
452
453
454
455
456
457 #define WINDOW_W xS(400)
458 #define WINDOW_H yS(165)
459
460 FlangerWindow::FlangerWindow(Flanger *plugin)
461  : PluginClientWindow(plugin, 
462         WINDOW_W, 
463         WINDOW_H, 
464         WINDOW_W, 
465         WINDOW_H, 
466         0)
467
468         this->plugin = plugin; 
469 }
470
471 FlangerWindow::~FlangerWindow()
472 {
473     delete offset;
474     delete starting_phase;
475     delete depth;
476     delete rate;
477     delete wetness;
478 }
479
480 void FlangerWindow::create_objects()
481 {
482         int margin = client->get_theme()->widget_border + xS(4);
483     int x1 = margin;
484         int x2 = xS(200), y = margin - xS(4);
485     int x3 = x2 + BC_Pot::calculate_w() + margin;
486     int x4 = x3 + BC_Pot::calculate_w() + margin;
487     int text_w = get_w() - margin - x4;
488     int height = BC_TextBox::calculate_h(this, MEDIUMFONT, 1, 1) + margin - xS(4);
489
490
491     offset = new PluginParam(plugin,
492         this,
493         x1, 
494         x3,
495         x4,
496         y, 
497         text_w,
498         0,  // output_i
499         &plugin->config.offset, // output_f
500         0, // output_q
501         "Phase offset (ms):",
502         MIN_OFFSET, // min
503         MAX_OFFSET); // max
504     offset->set_precision(3);
505     offset->initialize();
506     y += height;
507
508
509     starting_phase = new PluginParam(plugin,
510         this,
511         x1, 
512         x2,
513         x4,
514         y, 
515         text_w,
516         0,  // output_i
517         &plugin->config.starting_phase, // output_f
518         0, // output_q
519         "Starting phase (%):",
520         MIN_STARTING_PHASE, // min
521         MAX_STARTING_PHASE); // max
522     starting_phase->set_precision(3);
523     starting_phase->initialize();
524     y += height;
525
526
527
528     depth = new PluginParam(plugin,
529         this,
530         x1, 
531         x3,
532         x4,
533         y, 
534         text_w,
535         0,  // output_i
536         &plugin->config.depth, // output_f
537         0, // output_q
538         "Depth (ms):",
539         MIN_DEPTH, // min
540         MAX_DEPTH); // max
541     depth->set_precision(3);
542     depth->initialize();
543     y += height;
544
545
546
547     rate = new PluginParam(plugin,
548         this,
549         x1, 
550         x2,
551         x4,
552         y, 
553         text_w,
554         0,  // output_i
555         &plugin->config.rate, // output_f
556         0, // output_q
557         "Rate (Hz):",
558         MIN_RATE, // min
559         MAX_RATE); // max
560     rate->set_precision(3);
561     rate->initialize();
562     y += height;
563
564
565
566     wetness = new PluginParam(plugin,
567         this,
568         x1, 
569         x3,
570         x4,
571         y, 
572         text_w,
573         0,  // output_i
574         &plugin->config.wetness, // output_f
575         0, // output_q
576         "Wetness (db):",
577         INFINITYGAIN, // min
578         0); // max
579     wetness->set_precision(3);
580     wetness->initialize();
581     y += height;
582
583         show_window();
584 }
585
586 void FlangerWindow::update()
587 {
588     offset->update(0, 0);
589     starting_phase->update(0, 0);
590     depth->update(0, 0);
591     rate->update(0, 0);
592     wetness->update(0, 0);
593 }
594
595 void FlangerWindow::param_updated()
596 {
597 }
598