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