15730e646ce42619df170a07b4a219a7630e620a
[goodguy/cinelerra.git] / cinelerra-5.1 / plugins / spectrogram / spectrogram.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 1997-2011 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 "cursors.h"
25 #include "bchash.h"
26 #include "filexml.h"
27 #include "language.h"
28 #include "bccolors.h"
29 #include "samples.h"
30 #include "spectrogram.h"
31 #include "theme.h"
32 #include "transportque.inc"
33 #include "units.h"
34 #include "vframe.h"
35
36
37 #include <string.h>
38
39
40
41 REGISTER_PLUGIN(Spectrogram)
42
43
44 #define HALF_WINDOW (config.window_size / 2)
45
46 SpectrogramConfig::SpectrogramConfig()
47 {
48         level = 0.0;
49         window_size = MAX_WINDOW;
50         xzoom = 1;
51         frequency = 440;
52         normalize = 0;
53         mode = VERTICAL;
54         history_size = 4;
55 }
56
57 int SpectrogramConfig::equivalent(SpectrogramConfig &that)
58 {
59         return EQUIV(level, that.level) &&
60                 xzoom == that.xzoom &&
61                 frequency == that.frequency &&
62                 window_size == that.window_size &&
63                 normalize == that.normalize &&
64                 mode == that.mode &&
65                 history_size == that.history_size;
66 }
67
68 void SpectrogramConfig::copy_from(SpectrogramConfig &that)
69 {
70         level = that.level;
71         xzoom = that.xzoom;
72         frequency = that.frequency;
73         window_size = that.window_size;
74         normalize = that.normalize;
75         mode = that.mode;
76         history_size = that.history_size;
77
78         CLAMP(history_size, MIN_HISTORY, MAX_HISTORY);
79         CLAMP(frequency, MIN_FREQ, MAX_FREQ);
80         CLAMP(xzoom, MIN_XZOOM, MAX_XZOOM);
81 }
82
83 void SpectrogramConfig::interpolate(SpectrogramConfig &prev,
84         SpectrogramConfig &next,
85         int64_t prev_frame,
86         int64_t next_frame,
87         int64_t current_frame)
88 {
89         copy_from(prev);
90 }
91
92
93
94 SpectrogramFrame::SpectrogramFrame(int data_size)
95 {
96         this->data_size = data_size;
97         data = new float[data_size];
98         force = 0;
99 }
100
101 SpectrogramFrame::~SpectrogramFrame()
102 {
103         delete [] data;
104 }
105
106
107
108 SpectrogramLevel::SpectrogramLevel(Spectrogram *plugin, int x, int y)
109  : BC_FPot(x, y, plugin->config.level, INFINITYGAIN, 40)
110 {
111         this->plugin = plugin;
112 }
113
114 int SpectrogramLevel::handle_event()
115 {
116         plugin->config.level = get_value();
117         plugin->send_configure_change();
118         return 1;
119 }
120
121
122
123
124
125 SpectrogramMode::SpectrogramMode(Spectrogram *plugin,
126         int x,
127         int y)
128  : BC_PopupMenu(x, y, xS(120),
129         mode_to_text(plugin->config.mode))
130 {
131         this->plugin = plugin;
132 }
133
134 void SpectrogramMode::create_objects()
135 {
136         add_item(new BC_MenuItem(mode_to_text(VERTICAL)));
137         add_item(new BC_MenuItem(mode_to_text(HORIZONTAL)));
138 }
139
140 int SpectrogramMode::handle_event()
141 {
142         if(plugin->config.mode != text_to_mode(get_text()))
143         {
144                 SpectrogramWindow *window = (SpectrogramWindow*)plugin->thread->window;
145                 window->probe_x = -1;
146                 window->probe_y = -1;
147                 plugin->config.mode = text_to_mode(get_text());
148                 window->canvas->clear_box(0, 0, window->canvas->get_w(), window->canvas->get_h());
149                 plugin->send_configure_change();
150         }
151         return 1;
152 }
153
154 const char* SpectrogramMode::mode_to_text(int mode)
155 {
156         switch(mode)
157         {
158                 case VERTICAL:
159                         return _("Vertical");
160                 case HORIZONTAL:
161                 default:
162                         return _("Horizontal");
163         }
164 }
165
166 int SpectrogramMode::text_to_mode(const char *text)
167 {
168         if(!strcmp(mode_to_text(VERTICAL), text)) return VERTICAL;
169         return HORIZONTAL;
170 }
171
172
173
174
175 SpectrogramHistory::SpectrogramHistory(Spectrogram *plugin,
176         int x,
177         int y)
178  : BC_IPot(x, y, plugin->config.history_size, MIN_HISTORY, MAX_HISTORY)
179 {
180         this->plugin = plugin;
181 }
182
183 int SpectrogramHistory::handle_event()
184 {
185         plugin->config.history_size = get_value();
186         plugin->send_configure_change();
187         return 1;
188 }
189
190
191
192
193
194
195 SpectrogramWindowSize::SpectrogramWindowSize(Spectrogram *plugin,
196         int x, int y, char *text)
197  : BC_PopupMenu(x, y, xS(120), text)
198 {
199         this->plugin = plugin;
200 }
201
202 int SpectrogramWindowSize::handle_event()
203 {
204         plugin->config.window_size = atoi(get_text());
205         plugin->send_configure_change();
206         return 1;
207 }
208
209
210 SpectrogramWindowSizeTumbler::SpectrogramWindowSizeTumbler(Spectrogram *plugin, int x, int y)
211  : BC_Tumbler(x,
212         y)
213 {
214         this->plugin = plugin;
215 }
216
217 int SpectrogramWindowSizeTumbler::handle_up_event()
218 {
219         plugin->config.window_size *= 2;
220         if(plugin->config.window_size > MAX_WINDOW)
221                 plugin->config.window_size = MAX_WINDOW;
222         char string[BCTEXTLEN];
223         sprintf(string, "%d", plugin->config.window_size);
224         ((SpectrogramWindow*)plugin->get_thread()->get_window())->window_size->set_text(string);
225         plugin->send_configure_change();
226         return 0;
227 }
228
229 int SpectrogramWindowSizeTumbler::handle_down_event()
230 {
231         plugin->config.window_size /= 2;
232         if(plugin->config.window_size < MIN_WINDOW)
233                 plugin->config.window_size = MIN_WINDOW;
234         char string[BCTEXTLEN];
235         sprintf(string, "%d", plugin->config.window_size);
236         ((SpectrogramWindow*)plugin->get_thread()->get_window())->window_size->set_text(string);
237         plugin->send_configure_change();
238         return 1;
239 }
240
241
242
243
244
245 SpectrogramNormalize::SpectrogramNormalize(Spectrogram *plugin, int x, int y)
246  : BC_CheckBox(x, y, plugin->config.normalize, _("Normalize"))
247 {
248         this->plugin = plugin;
249 }
250
251 int SpectrogramNormalize::handle_event()
252 {
253         plugin->config.normalize = get_value();
254         plugin->send_configure_change();
255         return 1;
256 }
257
258
259
260
261 SpectrogramFreq::SpectrogramFreq(Spectrogram *plugin, int x, int y)
262  : BC_TextBox(x, y, xS(100), 1,
263                 (int)plugin->config.frequency)
264 {
265         this->plugin = plugin;
266 }
267
268 int SpectrogramFreq::handle_event()
269 {
270         plugin->config.frequency = atoi(get_text());
271         CLAMP(plugin->config.frequency, MIN_FREQ, MAX_FREQ);
272         plugin->send_configure_change();
273         return 1;
274 }
275
276
277
278
279
280 SpectrogramXZoom::SpectrogramXZoom(Spectrogram *plugin, int x, int y)
281  : BC_IPot(x, y, plugin->config.xzoom, MIN_XZOOM, MAX_XZOOM)
282 {
283         this->plugin = plugin;
284 }
285
286 int SpectrogramXZoom::handle_event()
287 {
288         plugin->config.xzoom = get_value();
289         plugin->send_configure_change();
290         return 1;
291 }
292
293
294
295 SpectrogramCanvas::SpectrogramCanvas(Spectrogram *plugin, int x, int y, int w, int h)
296  : BC_SubWindow(x, y, w, h, BLACK)
297 {
298         this->plugin = plugin;
299         current_operation = NONE;
300 }
301
302 int SpectrogramCanvas::button_press_event()
303 {
304         if(is_event_win() && cursor_inside())
305         {
306                 calculate_point();
307                 current_operation = DRAG;
308                 plugin->send_configure_change();
309                 return 1;
310         }
311         return 0;
312 }
313
314 int SpectrogramCanvas::button_release_event()
315 {
316         if(current_operation == DRAG)
317         {
318                 current_operation = NONE;
319                 return 1;
320         }
321         return 0;
322 }
323
324 int SpectrogramCanvas::cursor_motion_event()
325 {
326         if(current_operation == DRAG)
327         {
328                 calculate_point();
329         }
330         return 0;
331 }
332
333
334 void SpectrogramCanvas::calculate_point()
335 {
336         int x = get_cursor_x();
337         int y = get_cursor_y();
338         CLAMP(x, 0, get_w()-1);
339         CLAMP(y, 0, get_h()-1);
340
341         ((SpectrogramWindow*)plugin->thread->window)->calculate_frequency(
342                 x,
343                 y,
344                 1);
345
346 //printf("SpectrogramCanvas::calculate_point %d %d\n", __LINE__, Freq::tofreq(freq_index));
347 }
348
349 void SpectrogramCanvas::draw_overlay()
350 {
351         SpectrogramWindow *window = (SpectrogramWindow*)plugin->thread->window;
352         if(window->probe_x >= 0 || window->probe_y >= 0)
353         {
354                 set_color(GREEN);
355                 set_inverse();
356                 if(plugin->config.mode == HORIZONTAL) draw_line(0, window->probe_y, get_w(), window->probe_y);
357                 draw_line(window->probe_x, 0, window->probe_x, get_h());
358                 set_opaque();
359         }
360 }
361
362
363
364
365
366
367
368
369 SpectrogramWindow::SpectrogramWindow(Spectrogram *plugin)
370  : PluginClientWindow(plugin, plugin->w, plugin->h,
371         xS(320), yS(320), 1)
372 {
373         this->plugin = plugin;
374         probe_x = probe_y = -1;
375 }
376
377 SpectrogramWindow::~SpectrogramWindow()
378 {
379 }
380
381 void SpectrogramWindow::create_objects()
382 {
383         int x = plugin->get_theme()->widget_border;
384         int y = x;
385         int x1 = x;
386         int y1 = y;
387         char string[BCTEXTLEN];
388
389
390
391         add_subwindow(canvas = new SpectrogramCanvas(plugin,
392                 0, 0, get_w(), get_h() - BC_Pot::calculate_h() * 2 -
393                         plugin->get_theme()->widget_border * 3));
394         canvas->set_cursor(CROSS_CURSOR, 0, 0);
395
396         x = plugin->get_theme()->widget_border;
397         y = canvas->get_y() + canvas->get_h() + plugin->get_theme()->widget_border;
398
399         x1 = x;
400         y1 = y;
401         add_subwindow(level_title = new BC_Title(x, y, _("Level:")));
402         x += level_title->get_w() + plugin->get_theme()->widget_border;
403         add_subwindow(level = new SpectrogramLevel(plugin, x, y));
404         x += level->get_w() + plugin->get_theme()->widget_border;
405         y += level->get_h() + plugin->get_theme()->widget_border;
406
407         add_subwindow(normalize = new SpectrogramNormalize(plugin, x1, y));
408
409         x = x1 + level_title->get_w() + level->get_w() + plugin->get_theme()->widget_border * 2;
410         x1 = x;
411         y = y1;
412
413         sprintf(string, "%d", plugin->config.window_size);
414         add_subwindow(window_size_title = new BC_Title(x, y, _("Window size:")));
415
416         x += window_size_title->get_w() + plugin->get_theme()->widget_border;
417         add_subwindow(window_size = new SpectrogramWindowSize(plugin, x, y, string));
418         x += window_size->get_w();
419         add_subwindow(window_size_tumbler = new SpectrogramWindowSizeTumbler(plugin, x, y));
420
421         for(int i = MIN_WINDOW; i <= MAX_WINDOW; i *= 2)
422         {
423                 sprintf(string, "%d", i);
424                 window_size->add_item(new BC_MenuItem(string));
425         }
426
427 //      x += window_size_tumbler->get_w() + plugin->get_theme()->widget_border;
428         x = x1;
429         y += window_size->get_h() + plugin->get_theme()->widget_border;
430
431
432
433         add_subwindow(mode_title = new BC_Title(x, y, _("Mode:")));
434         x += mode_title->get_w() + plugin->get_theme()->widget_border;
435         add_subwindow(mode = new SpectrogramMode(plugin,
436                 x,
437                 y));
438         mode->create_objects();
439         x += mode->get_w() + plugin->get_theme()->widget_border;
440
441         x = x1 = window_size_tumbler->get_x() +
442                 window_size_tumbler->get_w() +
443                 plugin->get_theme()->widget_border;
444         y = y1;
445         add_subwindow(history_title = new BC_Title(x, y, _("History:")));
446         x += history_title->get_w() + plugin->get_theme()->widget_border;
447         add_subwindow(history = new SpectrogramHistory(plugin,
448                 x,
449                 y));
450
451         x = x1;
452         y += history->get_h() + plugin->get_theme()->widget_border;
453         add_subwindow(xzoom_title = new BC_Title(x, y, _("X Zoom:")));
454         x += xzoom_title->get_w() + plugin->get_theme()->widget_border;
455         add_subwindow(xzoom = new SpectrogramXZoom(plugin, x, y));
456         x += xzoom->get_w() + plugin->get_theme()->widget_border;
457
458         y = y1;
459         x1 = x;
460         add_subwindow(freq_title = new BC_Title(x1, y, _("Freq: 0 Hz")));
461 //      x += freq_title->get_w() + plugin->get_theme()->widget_border;
462         y += freq_title->get_h() + plugin->get_theme()->widget_border;
463 //      add_subwindow(freq = new SpectrogramFreq(plugin, x, y));
464 //      y += freq->get_h() + plugin->get_theme()->widget_border;
465         x = x1;
466         add_subwindow(amplitude_title = new BC_Title(x, y, _("Amplitude: 0 dB")));
467
468
469
470         show_window();
471 }
472
473 int SpectrogramWindow::resize_event(int w, int h)
474 {
475         int canvas_h = canvas->get_h();
476         int canvas_difference = get_h() - canvas_h;
477
478         canvas->reposition_window(0,
479                 0,
480                 w,
481                 h - canvas_difference);
482         canvas->clear_box(0, 0, canvas->get_w(), canvas->get_h());
483         probe_x = -1;
484         probe_y = -1;
485
486         int y_diff = -canvas_h + canvas->get_h();
487
488 // Remove all columns which may be a different size.
489         plugin->frame_buffer.remove_all_objects();
490         plugin->frame_history.remove_all_objects();
491
492         level_title->reposition_window(
493                 level_title->get_x(),
494                 level_title->get_y() + y_diff);
495         level->reposition_window(level->get_x(),
496                 level->get_y() + y_diff);
497
498         window_size_title->reposition_window(
499                 window_size_title->get_x(),
500                 window_size_title->get_y() + y_diff);
501
502         normalize->reposition_window(normalize->get_x(),
503                 normalize->get_y() + y_diff);
504         window_size->reposition_window(window_size->get_x(),
505                 window_size->get_y() + y_diff);
506         window_size_tumbler->reposition_window(window_size_tumbler->get_x(),
507                 window_size_tumbler->get_y() + y_diff);
508
509
510
511         mode_title->reposition_window(mode_title->get_x(),
512                 mode_title->get_y() + y_diff);
513         mode->reposition_window(mode->get_x(),
514                 mode->get_y() + y_diff);
515
516
517         history_title->reposition_window(history_title->get_x(),
518                 history_title->get_y() + y_diff);
519         history->reposition_window(history->get_x(),
520                 history->get_y() + y_diff);
521
522         xzoom_title->reposition_window(xzoom_title->get_x(),
523                 xzoom_title->get_y() + y_diff);
524         xzoom->reposition_window(xzoom->get_x(),
525                 xzoom->get_y() + y_diff);
526         freq_title->reposition_window(freq_title->get_x(),
527                 freq_title->get_y() + y_diff);
528 //      freq->reposition_window(freq->get_x(),
529 //              freq->get_y() + y_diff);
530         amplitude_title->reposition_window(amplitude_title->get_x(),
531                 amplitude_title->get_y() + y_diff);
532
533         flush();
534         plugin->w = w;
535         plugin->h = h;
536         return 0;
537 }
538
539
540 void SpectrogramWindow::calculate_frequency(int x, int y, int do_overlay)
541 {
542         if(x < 0 && y < 0) return;
543
544 // Clear previous overlay
545         if(do_overlay) canvas->draw_overlay();
546
547 // New probe position
548         probe_x = x;
549         probe_y = y;
550
551 // Convert to coordinates in frame history
552         int freq_pixel, time_pixel;
553         if(plugin->config.mode == VERTICAL)
554         {
555                 freq_pixel = get_w() - x;
556                 time_pixel = 0;
557         }
558         else
559         {
560                 freq_pixel = y;
561                 time_pixel = get_w() - x;
562         }
563
564         CLAMP(time_pixel, 0, plugin->frame_history.size() - 1);
565         if(plugin->frame_history.size())
566         {
567                 SpectrogramFrame *ptr = plugin->frame_history.get(
568                         plugin->frame_history.size() - time_pixel - 1);
569
570                 int pixels;
571                 int freq_index;
572                 if(plugin->config.mode == VERTICAL)
573                 {
574                         pixels = canvas->get_w();
575                         freq_index = (pixels - freq_pixel) * TOTALFREQS / pixels;
576                 }
577                 else
578                 {
579                         pixels = canvas->get_h();
580                         freq_index = (pixels - freq_pixel)  * TOTALFREQS / pixels;
581                 }
582
583                 int freq = Freq::tofreq(freq_index);
584
585
586                 CLAMP(freq_pixel, 0, ptr->data_size - 1);
587                 double level = ptr->data[freq_pixel];
588
589                 char string[BCTEXTLEN];
590                 sprintf(string, _("Freq: %d Hz"), freq);
591                 freq_title->update(string);
592
593                 sprintf(string, _("Amplitude: %.2f dB"), level);
594                 amplitude_title->update(string);
595         }
596
597         if(do_overlay)
598         {
599                 canvas->draw_overlay();
600                 canvas->flash();
601         }
602 }
603
604
605
606 void SpectrogramWindow::update_gui()
607 {
608         char string[BCTEXTLEN];
609         level->update(plugin->config.level);
610         sprintf(string, "%d", plugin->config.window_size);
611         window_size->set_text(string);
612
613         mode->set_text(mode->mode_to_text(plugin->config.mode));
614         history->update(plugin->config.history_size);
615
616 //      sprintf(string, "%d", plugin->config.window_fragment);
617 //      window_fragment->set_text(string);
618
619         normalize->set_value(plugin->config.normalize);
620 }
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646 Spectrogram::Spectrogram(PluginServer *server)
647  : PluginAClient(server)
648 {
649         reset();
650         timer = new Timer;
651         w = xS(640);
652         h = yS(480);
653 }
654
655 Spectrogram::~Spectrogram()
656 {
657         delete fft;
658         delete [] data;
659         delete audio_buffer;
660         delete [] freq_real;
661         delete [] freq_imag;
662         delete timer;
663         frame_buffer.remove_all_objects();
664         frame_history.remove_all_objects();
665 }
666
667
668 void Spectrogram::reset()
669 {
670         thread = 0;
671         fft = 0;
672         done = 0;
673         data = 0;
674         audio_buffer = 0;
675         audio_buffer_start = -MAX_WINDOW * 2;
676         freq_real = 0;
677         freq_imag = 0;
678         allocated_data = 0;
679         total_windows = 0;
680         bzero(&header, sizeof(data_header_t));
681 }
682
683
684 const char* Spectrogram::plugin_title() { return N_("Spectrogram"); }
685 int Spectrogram::is_realtime() { return 1; }
686
687 int Spectrogram::process_buffer(int64_t size,
688                 Samples *buffer,
689                 int64_t start_position,
690                 int sample_rate)
691 {
692 // Pass through
693         read_samples(buffer,
694                 0,
695                 sample_rate,
696                 start_position,
697                 size);
698
699
700         load_configuration();
701
702 // Reset audio buffer
703         if(window_size != config.window_size)
704         {
705                 render_stop();
706                 window_size = config.window_size;
707         }
708
709
710
711
712
713         if(!fft)
714         {
715                 fft = new FFT;
716         }
717
718         if(!data)
719         {
720                 data = new unsigned char[sizeof(data_header_t)];
721                 allocated_data = 0;
722         }
723
724         if(!freq_real)
725         {
726                 freq_real = new double[MAX_WINDOW];
727                 freq_imag = new double[MAX_WINDOW];
728         }
729
730         if(!audio_buffer)
731         {
732                 audio_buffer = new Samples(MAX_WINDOW);
733         }
734
735
736 // Accumulate audio
737         int needed = buffer_size + size;
738         if(audio_buffer->get_allocated() < needed)
739         {
740                 Samples *new_samples = new Samples(needed);
741                 memcpy(new_samples->get_data(),
742                         audio_buffer->get_data(),
743                         sizeof(double) * buffer_size);
744                 delete audio_buffer;
745                 audio_buffer = new_samples;
746         }
747
748         double *audio_samples = audio_buffer->get_data();
749         memcpy(audio_samples + buffer_size,
750                 buffer->get_data(),
751                 sizeof(double) * size);
752         buffer_size += size;
753
754
755 //printf("Spectrogram::process_buffer %d %d\n", __LINE__, buffer_size);
756
757 // Append a windows to end of GUI buffer
758         total_windows = 0;
759         while(buffer_size >= window_size)
760         {
761 // Process FFT
762                 fft->do_fft(window_size,  // must be a power of 2
763                 0,         // 0 = forward FFT, 1 = inverse
764                 audio_samples,     // array of input's real samples
765                 0,     // array of input's imag samples
766                 freq_real,    // array of output's reals
767                 freq_imag);
768
769 // Get peak in waveform
770                 double max = 0;
771                 for(int i = 0; i < window_size; i++)
772                 {
773                         double sample = fabs(audio_samples[i]);
774                         if(sample > max) max = sample;
775                 }
776
777 // Append to end of data buffer
778                 if(allocated_data < (total_windows + 1) * (HALF_WINDOW + 1))
779                 {
780                         int new_allocation = (total_windows + 1) * (HALF_WINDOW + 1);
781                         unsigned char *new_data = new unsigned char[sizeof(data_header_t) +
782                                 sizeof(float) * new_allocation];
783                         data_header_t *new_header = (data_header_t*)new_data;
784                         data_header_t *old_header = (data_header_t*)data;
785                         memcpy(new_header->samples,
786                                 old_header->samples,
787                                 sizeof(float) * allocated_data);
788                         delete [] data;
789                         data = new_data;
790                         allocated_data = new_allocation;
791                 }
792
793                 data_header_t *header = (data_header_t*)data;
794                 float *sample_output = header->samples + total_windows * (HALF_WINDOW + 1);
795 // 1st sample is maximum
796                 sample_output[0] = max;
797                 for(int i = 0; i < HALF_WINDOW; i++)
798                 {
799                         sample_output[i + 1] = sqrt(freq_real[i] * freq_real[i] +
800                                 freq_imag[i] * freq_imag[i]);
801 //                      sample_output[i + 1] = freq_real[i];
802                 }
803
804 // Shift audio buffer out
805                 memcpy(audio_samples,
806                         audio_samples + window_size,
807                         (buffer_size - window_size) * sizeof(double));
808
809                 total_windows++;
810                 buffer_size -= window_size;
811         }
812
813         data_header_t *header = (data_header_t*)data;
814         header->window_size = window_size;
815         header->sample_rate = sample_rate;
816         header->total_windows = total_windows;
817 // Linear output level
818         header->level = DB::fromdb(config.level);
819
820         send_render_gui(data,
821                 sizeof(data_header_t) +
822                         sizeof(float) * total_windows * (HALF_WINDOW + 1));
823
824         return 0;
825 }
826
827 void Spectrogram::render_stop()
828 {
829         buffer_size = 0;
830         audio_buffer_start = -MAX_WINDOW * 2;
831         frame_buffer.remove_all_objects();
832         frame_history.remove_all_objects();
833 }
834
835
836
837
838 NEW_WINDOW_MACRO(Spectrogram, SpectrogramWindow)
839
840 void Spectrogram::update_gui()
841 {
842         if(thread)
843         {
844                 int result = load_configuration();
845                 SpectrogramWindow *window = (SpectrogramWindow*)thread->get_window();
846                 window->lock_window("Spectrogram::update_gui");
847                 if(result) window->update_gui();
848
849
850 //printf("Spectrogram::update_gui %d\n", __LINE__);
851 // Shift in accumulated canvas columns
852                 if(frame_buffer.size())
853                 {
854                         SpectrogramCanvas *canvas = (SpectrogramCanvas*)window->canvas;
855                         canvas->draw_overlay();
856 // Z to draw in this iteration
857                         int total_frames = timer->get_difference() *
858                                 header.sample_rate /
859                                 header.window_size /
860                                 1000;
861
862 //printf("Spectrogram::update_gui %d %d %ld\n", __LINE__, frame_buffer.size(), timer->get_difference());
863                         if(total_frames) timer->subtract(total_frames *
864                                 header.window_size *
865                                 1000 /
866                                 header.sample_rate);
867
868 // Add forced column drawing
869                         for(int i = 0; i < frame_buffer.size(); i++)
870                                 if(frame_buffer.get(i)->force) total_frames++;
871                         total_frames = MIN(frame_buffer.size(), total_frames);
872
873 // Limit to canvas width
874                         if(config.mode == HORIZONTAL)
875                                 total_frames = MIN(canvas->get_w(), total_frames);
876
877
878
879                         if(config.mode == HORIZONTAL)
880                         {
881 // Shift left
882                                 int pixels = canvas->get_h();
883                                 canvas->copy_area(total_frames * config.xzoom,
884                                         0,
885                                         0,
886                                         0,
887                                         canvas->get_w() - total_frames * config.xzoom,
888                                         canvas->get_h());
889
890
891 // Draw new columns
892                                 for(int frame = 0;
893                                         frame < total_frames;
894                                         frame++)
895                                 {
896                                         int x = canvas->get_w() - (total_frames - frame) * config.xzoom;
897                                         SpectrogramFrame *ptr = frame_buffer.get(0);
898
899                                         for(int i = 0; i < pixels; i++)
900                                         {
901                                                 float db = ptr->data[
902                                                         MIN(i, ptr->data_size - 1)];
903                                                 float h, s, v;
904                                                 float r_out, g_out, b_out;
905                                                 int r, g, b;
906
907 #define DIVISION1 0.0
908 #define DIVISION2 -20.0
909 #define DIVISION3 INFINITYGAIN
910                                                 if(db > DIVISION2)
911                                                 {
912                                                         h = 240 - (float)(db - DIVISION2) / (DIVISION1 - DIVISION2) *
913                                                                 240;
914                                                         CLAMP(h, 0, 240);
915                                                         s = 1.0;
916                                                         v = 1.0;
917                                                         HSV::hsv_to_rgb(r_out, g_out, b_out, h, s, v);
918                                                         r = (int)(r_out * 0xff);
919                                                         g = (int)(g_out * 0xff);
920                                                         b = (int)(b_out * 0xff);
921                                                 }
922                                                 else
923                                                 if(db > DIVISION3)
924                                                 {
925                                                         h = 0.0;
926                                                         s = 0.0;
927                                                         v = (float)(db - DIVISION3) / (DIVISION2 - DIVISION3);
928                                                         HSV::hsv_to_rgb(r_out, g_out, b_out, h, s, v);
929                                                         r = (int)(r_out * 0xff);
930                                                         g = (int)(g_out * 0xff);
931                                                         b = (int)(b_out * 0xff);
932                                                 }
933                                                 else
934                                                 {
935                                                         r = g = b = 0;
936                                                 }
937
938                                                 canvas->set_color((r << 16) |
939                                                         (g << 8) |
940                                                         (b));
941                                                 if(config.xzoom == 1)
942                                                         canvas->draw_pixel(x, i);
943                                                 else
944                                                         canvas->draw_line(x,
945                                                                 i,
946                                                                 x + config.xzoom,
947                                                                 i);
948                                         }
949
950 // Push frames onto history
951                                         for(int i = 0; i < config.xzoom; i++)
952                                         {
953                                                 SpectrogramFrame *new_frame = new SpectrogramFrame(
954                                                         ptr->data_size);
955                                                 frame_history.append(new_frame);
956                                                 memcpy(new_frame->data, ptr->data, sizeof(float) * ptr->data_size);
957                                         }
958
959 // Clip history to canvas size
960                                         while(frame_history.size() > canvas->get_w())
961                                                 frame_history.remove_object_number(0);
962
963                                         frame_buffer.remove_object_number(0);
964                                 }
965                         }
966                         else
967 // mode == VERTICAL
968                         {
969 // Shift frames into history
970                                 for(int frame = 0; frame < total_frames; frame++)
971                                 {
972                                         if(frame_history.size() >= config.history_size)
973                                                 frame_history.remove_object_number(0);
974
975                                         frame_history.append(frame_buffer.get(0));
976                                         frame_buffer.remove_number(0);
977                                 }
978
979 // Reduce history size
980                                 while(frame_history.size() > config.history_size)
981                                         frame_history.remove_object_number(0);
982
983 // Draw frames from history
984                                 canvas->clear_box(0, 0, canvas->get_w(), canvas->get_h());
985                                 for(int frame = 0; frame < frame_history.size(); frame++)
986                                 {
987                                         SpectrogramFrame *ptr = frame_history.get(frame);
988 //printf("%d %d\n", canvas->get_w(), ptr->data_size);
989
990                                         int luma = (frame + 1) * 0x80 / frame_history.size();
991                                         if(frame == frame_history.size() - 1)
992                                         {
993                                                 canvas->set_color(WHITE);
994                                                 canvas->set_line_width(2);
995                                         }
996                                         else
997                                                 canvas->set_color((luma << 16) |
998                                                         (luma << 8) |
999                                                         luma);
1000
1001
1002                                         int x1 = 0;
1003                                         int y1 = 0;
1004                                         int w = canvas->get_w();
1005                                         int h = canvas->get_h();
1006                                         int number = 0;
1007
1008 //printf("Spectrogram::update_gui %d ", __LINE__);
1009
1010                                         for(int x2 = 0; x2 < w; x2++)
1011                                         {
1012                                                 float db = ptr->data[
1013                                                         MIN((w - x2), ptr->data_size - 1)];
1014 //if(x2 > w - 10) printf("%.02f ", ptr->data[x2]);
1015                                                 int y2 = h-1 - (int)((db - INFINITYGAIN) /
1016                                                         (0 - INFINITYGAIN) *
1017                                                         h);
1018                                                 CLAMP(y2, 0, h-1);
1019
1020                                                 if(number)
1021                                                 {
1022                                                         canvas->draw_line(x1, y1, x2, y2);
1023                                                 }
1024                                                 else
1025                                                 {
1026                                                         number++;
1027                                                 }
1028
1029                                                 x1 = x2;
1030                                                 y1 = y2;
1031                                         }
1032
1033                                         canvas->set_line_width(1);
1034 //printf("\n");
1035                                 }
1036
1037                         }
1038
1039
1040 // Recompute probe level
1041                         window->calculate_frequency(window->probe_x, window->probe_y, 0);
1042
1043                         canvas->draw_overlay();
1044                         canvas->flash();
1045                 }
1046
1047                 while(frame_buffer.size() > MAX_COLUMNS)
1048                         frame_buffer.remove_object_number(0);
1049
1050                 window->unlock_window();
1051         }
1052 }
1053
1054 void Spectrogram::render_gui(void *data, int size)
1055 {
1056         if(thread)
1057         {
1058                 thread->get_window()->lock_window("Spectrogram::render_gui");
1059                 data_header_t *header = (data_header_t*)data;
1060                 memcpy(&this->header, header, sizeof(data_header_t));
1061                 BC_SubWindow *canvas = ((SpectrogramWindow*)thread->get_window())->canvas;
1062                 int pixels = canvas->get_w();
1063                 if(config.mode == HORIZONTAL) pixels = canvas->get_h();
1064
1065 // Set all previous columns to draw immediately
1066                 for(int i = 0; i < frame_buffer.size(); i++)
1067                         frame_buffer.get(i)->force = 1;
1068
1069
1070                 for(int current_window = 0;
1071                         current_window < header->total_windows;
1072                         current_window++)
1073                 {
1074                         float *frame = header->samples +
1075                                 current_window * (header->window_size / 2 + 1);
1076                         float frame_max = *frame;
1077                         frame++;
1078                         int niquist = get_project_samplerate() / 2;
1079                         int total_slots = header->window_size / 2;
1080                         int max_slot = total_slots - 1;
1081 //                      int slot1 = total_slots - 1;
1082                         SpectrogramFrame *ptr =
1083                                 new SpectrogramFrame(
1084                                         pixels);
1085
1086 // Scale slots to pixels
1087                         for(int i = 0; i < pixels; i++)
1088                         {
1089 // Low frequency of row
1090                                 int freq_index1 = (int)((pixels - i) * TOTALFREQS / pixels);
1091 // High frequency of row
1092                                 int freq_index2 = (int)((pixels - i + 1) * TOTALFREQS / pixels);
1093                                 int freq1 = Freq::tofreq(freq_index1);
1094                                 int freq2 = Freq::tofreq(freq_index2);
1095                                 float slot1_f = (float)freq1 * max_slot / niquist;
1096                                 float slot2_f = (float)freq2 * max_slot / niquist;
1097                                 int slot1 = (int)(slot1_f);
1098                                 int slot2 = (int)(slot2_f);
1099                                 slot1 = MIN(slot1, max_slot);
1100                                 slot2 = MIN(slot2, max_slot);
1101                                 double sum = 0;
1102
1103 // Accumulate multiple slots in the same pixel
1104                                 if(slot2 > slot1 + 1)
1105                                 {
1106                                         for(int j = slot1; j <= slot2; j++)
1107                                                 sum += frame[j];
1108
1109                                         sum /= slot2 - slot1 + 1;
1110                                 }
1111                                 else
1112 // Blend 2 slots to create pixel
1113                                 {
1114                                         float weight = slot1_f - floor(slot1_f);
1115                                         int slot3 = MIN(slot1 + 1, max_slot);
1116                                         sum = frame[slot1] * (1.0 - weight) +
1117                                                 frame[slot3] * weight;
1118                                 }
1119
1120                                 ptr->data[i] = sum;
1121 //                              slot1 = slot2;
1122                         }
1123
1124 // Normalize
1125                         if(config.normalize)
1126                         {
1127 // Get the maximum level in the spectrogram
1128                                 float max = 0;
1129                                 for(int i = 0; i < pixels; i++)
1130                                 {
1131                                         if(ptr->data[i] > max) max = ptr->data[i];
1132                                 }
1133
1134 // Scale all levels
1135                                 for(int i = 0; i < pixels; i++)
1136                                 {
1137                                         ptr->data[i] = header->level *
1138                                                 ptr->data[i] /
1139                                                 max;
1140                                 }
1141                         }
1142                         else
1143                         {
1144 // Get the maximum level in the spectrogram
1145                                 float max = 0;
1146                                 for(int i = 0; i < pixels; i++)
1147                                 {
1148                                         if(ptr->data[i] > max) max = ptr->data[i];
1149                                 }
1150
1151 // Maximum level in spectrogram is the maximum waveform level
1152                                 for(int i = 0; i < pixels; i++)
1153                                 {
1154                                         ptr->data[i] = header->level *
1155                                                 frame_max *
1156                                                 ptr->data[i] /
1157                                                 max;
1158                                 }
1159
1160
1161 //                              for(int i = 0; i < pixels; i++)
1162 //                              {
1163 //                                      ptr->data[i] = header->level * ptr->data[i];
1164 //                              }
1165                         }
1166
1167 // DB conversion
1168 //printf("Spectrogram::render_gui %d ", __LINE__);
1169                         for(int i = 0; i < pixels; i++)
1170                         {
1171                                 ptr->data[i] = DB::todb(ptr->data[i]);
1172 //if(i > pixels - 10) printf("%.02f ", ptr->data[i]);
1173
1174                         }
1175 //printf("\n");
1176
1177
1178                         frame_buffer.append(ptr);
1179                         total_windows++;
1180                 }
1181
1182                 timer->update();
1183                 thread->get_window()->unlock_window();
1184         }
1185 }
1186
1187 LOAD_CONFIGURATION_MACRO(Spectrogram, SpectrogramConfig)
1188
1189 void Spectrogram::read_data(KeyFrame *keyframe)
1190 {
1191         FileXML input;
1192         input.set_shared_input(keyframe->xbuf);
1193
1194         int result = 0;
1195         while(!result)
1196         {
1197                 result = input.read_tag();
1198
1199                 if(!result)
1200                 {
1201                         if(input.tag.title_is("SPECTROGRAM"))
1202                         {
1203                                 config.level = input.tag.get_property("LEVEL", config.level);
1204                                 config.normalize = input.tag.get_property("NORMALIZE", config.normalize);
1205                                 config.window_size = input.tag.get_property("WINDOW_SIZE", config.window_size);
1206                                 config.xzoom = input.tag.get_property("XZOOM", config.xzoom);
1207                                 config.mode = input.tag.get_property("MODE", config.mode);
1208                                 config.history_size = input.tag.get_property("HISTORY_SIZE", config.history_size);
1209                                 if(is_defaults())
1210                                 {
1211                                         w = input.tag.get_property("W", w);
1212                                         h = input.tag.get_property("H", h);
1213                                 }
1214                         }
1215                 }
1216         }
1217 }
1218
1219 void Spectrogram::save_data(KeyFrame *keyframe)
1220 {
1221         FileXML output;
1222         output.set_shared_output(keyframe->xbuf);
1223
1224         output.tag.set_title("SPECTROGRAM");
1225         output.tag.set_property("LEVEL", (double)config.level);
1226         output.tag.set_property("NORMALIZE", (double)config.normalize);
1227         output.tag.set_property("WINDOW_SIZE", (int)config.window_size);
1228         output.tag.set_property("XZOOM", (int)config.xzoom);
1229         output.tag.set_property("MODE", (int)config.mode);
1230         output.tag.set_property("HISTORY_SIZE", (int)config.history_size);
1231         output.tag.set_property("W", (int)w);
1232         output.tag.set_property("H", (int)h);
1233         output.append_tag();
1234         output.tag.set_title("/SPECTROGRAM");
1235         output.append_tag();
1236         output.append_newline();
1237         output.terminate_string();
1238 }
1239
1240
1241
1242
1243