Credit Miroslav for improved/simplified vp9 webm
[goodguy/cinelerra.git] / cinelerra-5.1 / plugins / spectrogram / spectrogram.C
1 /*
2  * CINELERRA
3  * Copyright (C) 1997-2019 Adam Williams <broadcast at earthling dot net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  */
20
21 #include "bcdisplayinfo.h"
22 #include "clip.h"
23 #include "cursors.h"
24 #include "bchash.h"
25 #include "filexml.h"
26 #include "language.h"
27 #include "bccolors.h"
28 #include "samples.h"
29 #include "spectrogram.h"
30 #include "theme.h"
31 #include "transportque.inc"
32 #include "units.h"
33 #include "vframe.h"
34
35
36 #include <string.h>
37
38
39
40 REGISTER_PLUGIN(Spectrogram)
41
42
43 #define HALF_WINDOW (config.window_size / 2)
44
45 SpectrogramConfig::SpectrogramConfig()
46 {
47         level = 0.0;
48         window_size = MAX_WINDOW;
49         xzoom = 1;
50         frequency = 440;
51         normalize = 0;
52         mode = VERTICAL;
53         history_size = 4;
54 }
55
56 int SpectrogramConfig::equivalent(SpectrogramConfig &that)
57 {
58         return EQUIV(level, that.level) &&
59                 xzoom == that.xzoom &&
60                 frequency == that.frequency &&
61                 window_size == that.window_size &&
62                 normalize == that.normalize &&
63                 mode == that.mode &&
64                 history_size == that.history_size;
65 }
66
67 void SpectrogramConfig::copy_from(SpectrogramConfig &that)
68 {
69         level = that.level;
70         xzoom = that.xzoom;
71         frequency = that.frequency;
72         window_size = that.window_size;
73         normalize = that.normalize;
74         mode = that.mode;
75         history_size = that.history_size;
76
77         CLAMP(history_size, MIN_HISTORY, MAX_HISTORY);
78         CLAMP(frequency, MIN_FREQ, MAX_FREQ);
79         CLAMP(xzoom, MIN_XZOOM, MAX_XZOOM);
80 }
81
82 void SpectrogramConfig::interpolate(SpectrogramConfig &prev,
83         SpectrogramConfig &next,
84         int64_t prev_frame,
85         int64_t next_frame,
86         int64_t current_frame)
87 {
88         copy_from(prev);
89 }
90
91
92
93 SpectrogramFrame::SpectrogramFrame(int data_size)
94  : PluginClientFrame()
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                 SpectrogramWindow *window = (SpectrogramWindow*)plugin->thread->window;
144                 window->probe_x = -1;
145                 window->probe_y = -1;
146                 plugin->config.mode = text_to_mode(get_text());
147                 window->canvas->clear_box(0, 0, window->canvas->get_w(), window->canvas->get_h());
148                 plugin->send_configure_change();
149         }
150         return 1;
151 }
152
153 const char* SpectrogramMode::mode_to_text(int mode)
154 {
155         switch( mode )
156         {
157                 case VERTICAL:
158                         return _("Vertical");
159                 case HORIZONTAL:
160                 default:
161                         return _("Horizontal");
162         }
163 }
164
165 int SpectrogramMode::text_to_mode(const char *text)
166 {
167         if( !strcmp(mode_to_text(VERTICAL), text) ) return VERTICAL;
168         return HORIZONTAL;
169 }
170
171
172
173
174 SpectrogramHistory::SpectrogramHistory(Spectrogram *plugin,
175         int x,
176         int y)
177  : BC_IPot(x, y, plugin->config.history_size, MIN_HISTORY, MAX_HISTORY)
178 {
179         this->plugin = plugin;
180 }
181
182 int SpectrogramHistory::handle_event()
183 {
184         plugin->config.history_size = get_value();
185         plugin->send_configure_change();
186         return 1;
187 }
188
189
190
191
192
193
194 SpectrogramWindowSize::SpectrogramWindowSize(Spectrogram *plugin,
195         int x, int y, char *text)
196  : BC_PopupMenu(x, y, xS(120), text)
197 {
198         this->plugin = plugin;
199 }
200
201 int SpectrogramWindowSize::handle_event()
202 {
203         plugin->config.window_size = atoi(get_text());
204         plugin->send_configure_change();
205         return 1;
206 }
207
208
209 SpectrogramWindowSizeTumbler::SpectrogramWindowSizeTumbler(Spectrogram *plugin, int x, int y)
210  : BC_Tumbler(x,
211         y)
212 {
213         this->plugin = plugin;
214 }
215
216 int SpectrogramWindowSizeTumbler::handle_up_event()
217 {
218         plugin->config.window_size *= 2;
219         if( plugin->config.window_size > MAX_WINDOW )
220                 plugin->config.window_size = MAX_WINDOW;
221         char string[BCTEXTLEN];
222         sprintf(string, "%d", plugin->config.window_size);
223         ((SpectrogramWindow*)plugin->get_thread()->get_window())->window_size->set_text(string);
224         plugin->send_configure_change();
225         return 0;
226 }
227
228 int SpectrogramWindowSizeTumbler::handle_down_event()
229 {
230         plugin->config.window_size /= 2;
231         if( plugin->config.window_size < MIN_WINDOW )
232                 plugin->config.window_size = MIN_WINDOW;
233         char string[BCTEXTLEN];
234         sprintf(string, "%d", plugin->config.window_size);
235         ((SpectrogramWindow*)plugin->get_thread()->get_window())->window_size->set_text(string);
236         plugin->send_configure_change();
237         return 1;
238 }
239
240
241
242
243
244 SpectrogramNormalize::SpectrogramNormalize(Spectrogram *plugin, int x, int y)
245  : BC_CheckBox(x, y, plugin->config.normalize, _("Normalize"))
246 {
247         this->plugin = plugin;
248 }
249
250 int SpectrogramNormalize::handle_event()
251 {
252         plugin->config.normalize = get_value();
253         plugin->send_configure_change();
254         return 1;
255 }
256
257
258
259
260 SpectrogramFreq::SpectrogramFreq(Spectrogram *plugin, int x, int y)
261  : BC_TextBox(x, y, xS(100), 1,
262                 (int)plugin->config.frequency)
263 {
264         this->plugin = plugin;
265 }
266
267 int SpectrogramFreq::handle_event()
268 {
269         plugin->config.frequency = atoi(get_text());
270         CLAMP(plugin->config.frequency, MIN_FREQ, MAX_FREQ);
271         plugin->send_configure_change();
272         return 1;
273 }
274
275
276
277
278
279 SpectrogramXZoom::SpectrogramXZoom(Spectrogram *plugin, int x, int y)
280  : BC_IPot(x, y, plugin->config.xzoom, MIN_XZOOM, MAX_XZOOM)
281 {
282         this->plugin = plugin;
283 }
284
285 int SpectrogramXZoom::handle_event()
286 {
287         plugin->config.xzoom = get_value();
288         plugin->send_configure_change();
289         return 1;
290 }
291
292
293
294 SpectrogramCanvas::SpectrogramCanvas(Spectrogram *plugin, int x, int y, int w, int h)
295  : BC_SubWindow(x, y, w, h, BLACK)
296 {
297         this->plugin = plugin;
298         current_operation = NONE;
299 }
300
301 int SpectrogramCanvas::button_press_event()
302 {
303         if( is_event_win() && cursor_inside() ) {
304                 calculate_point();
305                 current_operation = DRAG;
306                 plugin->send_configure_change();
307                 return 1;
308         }
309         return 0;
310 }
311
312 int SpectrogramCanvas::button_release_event()
313 {
314         if( current_operation == DRAG ) {
315                 current_operation = NONE;
316                 return 1;
317         }
318         return 0;
319 }
320
321 int SpectrogramCanvas::cursor_motion_event()
322 {
323         if( current_operation == DRAG ) {
324                 calculate_point();
325         }
326         return 0;
327 }
328
329
330 void SpectrogramCanvas::calculate_point()
331 {
332         int x = get_cursor_x();
333         int y = get_cursor_y();
334         CLAMP(x, 0, get_w()-1);
335         CLAMP(y, 0, get_h()-1);
336
337         ((SpectrogramWindow*)plugin->thread->window)->calculate_frequency(
338                 x,
339                 y,
340                 1);
341
342 //printf("SpectrogramCanvas::calculate_point %d %d\n", __LINE__, Freq::tofreq(freq_index));
343 }
344
345 void SpectrogramCanvas::draw_overlay()
346 {
347         SpectrogramWindow *window = (SpectrogramWindow*)plugin->thread->window;
348         if( window->probe_x >= 0 || window->probe_y >= 0 ) {
349                 set_color(GREEN);
350                 set_inverse();
351                 if( plugin->config.mode == HORIZONTAL ) draw_line(0, window->probe_y, get_w(), window->probe_y);
352                 draw_line(window->probe_x, 0, window->probe_x, get_h());
353                 set_opaque();
354         }
355 }
356
357
358
359
360
361
362
363
364 SpectrogramWindow::SpectrogramWindow(Spectrogram *plugin)
365  : PluginClientWindow(plugin, plugin->w, plugin->h,
366         xS(320), yS(320), 1)
367 {
368         this->plugin = plugin;
369         probe_x = probe_y = -1;
370 }
371
372 SpectrogramWindow::~SpectrogramWindow()
373 {
374 }
375
376 void SpectrogramWindow::create_objects()
377 {
378         int x = plugin->get_theme()->widget_border;
379         int y = x;
380         int x1 = x;
381         int y1 = y;
382         char string[BCTEXTLEN];
383
384
385
386         add_subwindow(canvas = new SpectrogramCanvas(plugin,
387                 0, 0, get_w(), get_h() - BC_Pot::calculate_h() * 2 -
388                         plugin->get_theme()->widget_border * 3));
389         canvas->set_cursor(CROSS_CURSOR, 0, 0);
390
391         x = plugin->get_theme()->widget_border;
392         y = canvas->get_y() + canvas->get_h() + plugin->get_theme()->widget_border;
393
394         x1 = x;
395         y1 = y;
396         add_subwindow(level_title = new BC_Title(x, y, _("Level:")));
397         x += level_title->get_w() + plugin->get_theme()->widget_border;
398         add_subwindow(level = new SpectrogramLevel(plugin, x, y));
399         x += level->get_w() + plugin->get_theme()->widget_border;
400         y += level->get_h() + plugin->get_theme()->widget_border;
401
402         add_subwindow(normalize = new SpectrogramNormalize(plugin, x1, y));
403
404         x = x1 + level_title->get_w() + level->get_w() + plugin->get_theme()->widget_border * 2;
405         x1 = x;
406         y = y1;
407
408         sprintf(string, "%d", plugin->config.window_size);
409         add_subwindow(window_size_title = new BC_Title(x, y, _("Window size:")));
410
411         x += window_size_title->get_w() + plugin->get_theme()->widget_border;
412         add_subwindow(window_size = new SpectrogramWindowSize(plugin, x, y, string));
413         x += window_size->get_w();
414         add_subwindow(window_size_tumbler = new SpectrogramWindowSizeTumbler(plugin, x, y));
415
416         for( int i = MIN_WINDOW; i <= MAX_WINDOW; i *= 2 ) {
417                 sprintf(string, "%d", i);
418                 window_size->add_item(new BC_MenuItem(string));
419         }
420
421 //      x += window_size_tumbler->get_w() + plugin->get_theme()->widget_border;
422         x = x1;
423         y += window_size->get_h() + plugin->get_theme()->widget_border;
424
425
426
427         add_subwindow(mode_title = new BC_Title(x, y, _("Mode:")));
428         x += mode_title->get_w() + plugin->get_theme()->widget_border;
429         add_subwindow(mode = new SpectrogramMode(plugin,
430                 x,
431                 y));
432         mode->create_objects();
433         x += mode->get_w() + plugin->get_theme()->widget_border;
434
435         x = x1 = window_size_tumbler->get_x() +
436                 window_size_tumbler->get_w() +
437                 plugin->get_theme()->widget_border;
438         y = y1;
439         add_subwindow(history_title = new BC_Title(x, y, _("History:")));
440         x += history_title->get_w() + plugin->get_theme()->widget_border;
441         add_subwindow(history = new SpectrogramHistory(plugin,
442                 x,
443                 y));
444
445         x = x1;
446         y += history->get_h() + plugin->get_theme()->widget_border;
447         add_subwindow(xzoom_title = new BC_Title(x, y, _("X Zoom:")));
448         x += xzoom_title->get_w() + plugin->get_theme()->widget_border;
449         add_subwindow(xzoom = new SpectrogramXZoom(plugin, x, y));
450         x += xzoom->get_w() + plugin->get_theme()->widget_border;
451
452         y = y1;
453         x1 = x;
454         add_subwindow(freq_title = new BC_Title(x1, y, _("Freq: 0 Hz")));
455 //      x += freq_title->get_w() + plugin->get_theme()->widget_border;
456         y += freq_title->get_h() + plugin->get_theme()->widget_border;
457 //      add_subwindow(freq = new SpectrogramFreq(plugin, x, y));
458 //      y += freq->get_h() + plugin->get_theme()->widget_border;
459         x = x1;
460         add_subwindow(amplitude_title = new BC_Title(x, y, _("Amplitude: 0 dB")));
461
462
463
464         show_window();
465 }
466
467 int SpectrogramWindow::resize_event(int w, int h)
468 {
469         int canvas_h = canvas->get_h();
470         int canvas_difference = get_h() - canvas_h;
471
472         canvas->reposition_window(0,
473                 0,
474                 w,
475                 h - canvas_difference);
476         canvas->clear_box(0, 0, canvas->get_w(), canvas->get_h());
477         probe_x = -1;
478         probe_y = -1;
479
480         int y_diff = -canvas_h + canvas->get_h();
481
482 // Remove all columns which may be a different size.
483 //      plugin->frame_buffer.remove_all_objects();
484         plugin->frame_history.remove_all_objects();
485
486         level_title->reposition_window(
487                 level_title->get_x(),
488                 level_title->get_y() + y_diff);
489         level->reposition_window(level->get_x(),
490                 level->get_y() + y_diff);
491
492         window_size_title->reposition_window(
493                 window_size_title->get_x(),
494                 window_size_title->get_y() + y_diff);
495
496         normalize->reposition_window(normalize->get_x(),
497                 normalize->get_y() + y_diff);
498         window_size->reposition_window(window_size->get_x(),
499                 window_size->get_y() + y_diff);
500         window_size_tumbler->reposition_window(window_size_tumbler->get_x(),
501                 window_size_tumbler->get_y() + y_diff);
502
503
504
505         mode_title->reposition_window(mode_title->get_x(),
506                 mode_title->get_y() + y_diff);
507         mode->reposition_window(mode->get_x(),
508                 mode->get_y() + y_diff);
509
510
511         history_title->reposition_window(history_title->get_x(),
512                 history_title->get_y() + y_diff);
513         history->reposition_window(history->get_x(),
514                 history->get_y() + y_diff);
515
516         xzoom_title->reposition_window(xzoom_title->get_x(),
517                 xzoom_title->get_y() + y_diff);
518         xzoom->reposition_window(xzoom->get_x(),
519                 xzoom->get_y() + y_diff);
520         freq_title->reposition_window(freq_title->get_x(),
521                 freq_title->get_y() + y_diff);
522 //      freq->reposition_window(freq->get_x(),
523 //              freq->get_y() + y_diff);
524         amplitude_title->reposition_window(amplitude_title->get_x(),
525                 amplitude_title->get_y() + y_diff);
526
527         flush();
528         plugin->w = w;
529         plugin->h = h;
530         return 0;
531 }
532
533
534 void SpectrogramWindow::calculate_frequency(int x, int y, int do_overlay)
535 {
536         if( x < 0 && y < 0 ) return;
537
538 // Clear previous overlay
539         if( do_overlay ) canvas->draw_overlay();
540
541 // New probe position
542         probe_x = x;
543         probe_y = y;
544
545 // Convert to coordinates in frame history
546         int freq_pixel, time_pixel;
547         if( plugin->config.mode == VERTICAL ) {
548                 freq_pixel = get_w() - x;
549                 time_pixel = 0;
550         }
551         else {
552                 freq_pixel = y;
553                 time_pixel = get_w() - x;
554         }
555
556         CLAMP(time_pixel, 0, plugin->frame_history.size() - 1);
557         if( plugin->frame_history.size() ) {
558                 SpectrogramFrame *ptr = plugin->frame_history.get(
559                         plugin->frame_history.size() - time_pixel - 1);
560
561                 int pixels;
562                 int freq_index;
563                 if( plugin->config.mode == VERTICAL ) {
564                         pixels = canvas->get_w();
565                         freq_index = (pixels - freq_pixel) * TOTALFREQS / pixels;
566                 }
567                 else {
568                         pixels = canvas->get_h();
569                         freq_index = (pixels - freq_pixel)  * TOTALFREQS / pixels;
570                 }
571
572                 int freq = Freq::tofreq(freq_index);
573
574
575                 CLAMP(freq_pixel, 0, ptr->data_size - 1);
576                 double level = ptr->data[freq_pixel];
577
578                 char string[BCTEXTLEN];
579                 sprintf(string, _("Freq: %d Hz"), freq);
580                 freq_title->update(string);
581
582                 sprintf(string, _("Amplitude: %.2f dB"), level);
583                 amplitude_title->update(string);
584         }
585
586         if( do_overlay ) {
587                 canvas->draw_overlay();
588                 canvas->flash();
589         }
590 }
591
592
593
594 void SpectrogramWindow::update_gui()
595 {
596         char string[BCTEXTLEN];
597         level->update(plugin->config.level);
598         sprintf(string, "%d", plugin->config.window_size);
599         window_size->set_text(string);
600
601         mode->set_text(mode->mode_to_text(plugin->config.mode));
602         history->update(plugin->config.history_size);
603
604 //      sprintf(string, "%d", plugin->config.window_fragment);
605 //      window_fragment->set_text(string);
606
607         normalize->set_value(plugin->config.normalize);
608 }
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634 Spectrogram::Spectrogram(PluginServer *server)
635  : PluginAClient(server)
636 {
637         reset();
638         w = xS(640);
639         h = yS(480);
640 }
641
642 Spectrogram::~Spectrogram()
643 {
644         delete fft;
645         delete audio_buffer;
646         delete [] freq_real;
647         delete [] freq_imag;
648         frame_history.remove_all_objects();
649 }
650
651
652 void Spectrogram::reset()
653 {
654         thread = 0;
655         fft = 0;
656         done = 0;
657         audio_buffer = 0;
658         audio_buffer_start = -MAX_WINDOW * 2;
659         freq_real = 0;
660         freq_imag = 0;
661         allocated_data = 0;
662 }
663
664
665 const char* Spectrogram::plugin_title() { return N_("Spectrogram"); }
666 int Spectrogram::is_realtime() { return 1; }
667
668 int Spectrogram::process_buffer(int64_t size,
669                 Samples *buffer,
670                 int64_t start_position,
671                 int sample_rate)
672 {
673         int dir = get_direction() == PLAY_REVERSE ? -1 : 1;
674         double start_pos = (double)start_position / sample_rate;
675
676 // Pass through
677         read_samples(buffer, 0, sample_rate, start_position, size);
678
679         load_configuration();
680 // Reset audio buffer
681         if( window_size != config.window_size ) {
682                 render_stop();
683                 window_size = config.window_size;
684         }
685
686         if( !fft )
687                 fft = new FFT;
688
689         if( !freq_real ) {
690                 freq_real = new double[MAX_WINDOW];
691                 freq_imag = new double[MAX_WINDOW];
692         }
693
694         if( !audio_buffer )
695                 audio_buffer = new Samples(MAX_WINDOW);
696
697
698 // Allocate more audio buffer
699         int needed = buffer_size + size;
700         if( audio_buffer->get_allocated() < needed ) {
701                 Samples *new_samples = new Samples(needed);
702                 memcpy(new_samples->get_data(),
703                         audio_buffer->get_data(),
704                         sizeof(double) * buffer_size);
705                 delete audio_buffer;
706                 audio_buffer = new_samples;
707         }
708
709         double *audio_samples = audio_buffer->get_data();
710         memcpy(audio_samples + buffer_size,
711                 buffer->get_data(), sizeof(double) * size);
712         buffer_size += size;
713
714 // Append a windows to end of GUI buffer
715         while(buffer_size >= window_size) {
716 // Process FFT
717                 fft->do_fft(window_size,  // must be a power of 2
718                         0,               // 0 = forward FFT, 1 = inverse
719                         audio_samples,   // array of input's real samples
720                         0,       // array of input's imag samples
721                         freq_real,      // array of output's reals
722                         freq_imag);
723
724 // Get peak in waveform
725                 double max = 0;
726                 for( int i = 0; i < window_size; i++ ) {
727                         double sample = fabs(audio_samples[i]);
728                         if( sample > max ) max = sample;
729                 }
730
731
732 // send to the GUI
733                 SpectrogramFrame *frame = new SpectrogramFrame(HALF_WINDOW + 1);
734                 double widx = get_gui_frames();
735                 frame->position = start_pos + dir * window_size * widx / get_samplerate();
736                 frame->data[0] =  max;
737                 for( int i = 0; i < HALF_WINDOW; i++ ) {
738                         frame->data[i + 1] = hypot(freq_real[i], freq_imag[i]);
739                 }
740                 frame->window_size = window_size;
741                 frame->sample_rate = sample_rate;
742                 frame->level = DB::fromdb(config.level);
743                 add_gui_frame(frame);
744
745 // Shift audio buffer out
746                 memcpy(audio_samples,
747                         audio_samples + window_size,
748                         (buffer_size - window_size) * sizeof(double));
749
750                 buffer_size -= window_size;
751         }
752
753         last_position = start_position + dir * size;
754         return 0;
755 }
756
757 void Spectrogram::render_stop()
758 {
759         buffer_size = 0;
760         audio_buffer_start = -MAX_WINDOW * 2;
761 //      frame_buffer.remove_all_objects();
762         frame_history.remove_all_objects();
763 }
764
765 NEW_WINDOW_MACRO(Spectrogram, SpectrogramWindow)
766
767 void Spectrogram::update_gui()
768 {
769         if( !thread ) return;
770         SpectrogramWindow *window = (SpectrogramWindow*)thread->get_window();
771         if( !window ) return;
772         int result = load_configuration();
773         int total_frames = pending_gui_frames();
774         if( !result && !total_frames ) return;
775
776         window->lock_window("Spectrogram::update_gui");
777         if( result ) // widgets
778                 window->update_gui();
779         if( total_frames ) { // spectrogram
780                 SpectrogramCanvas *canvas = (SpectrogramCanvas*)window->canvas;
781                 canvas->draw_overlay();
782
783                 if( config.mode == HORIZONTAL ) {
784 // Shift left
785                         int pixels = canvas->get_h();
786                         canvas->copy_area(total_frames * config.xzoom,
787                                 0, 0, 0,
788                                 canvas->get_w() - total_frames * config.xzoom,
789                                 canvas->get_h());
790
791
792 // Draw new columns
793                         for( int frame = 0; frame < total_frames; frame++ ) {
794                                 int x = canvas->get_w() - (total_frames - frame) * config.xzoom;
795                                 SpectrogramFrame *ptr = (SpectrogramFrame*)get_gui_frame(-1, 0);
796                                 fix_gui_frame(ptr);
797
798                                 for( int i = 0; i < pixels; i++ ) {
799                                         float db = ptr->data[MIN(i, ptr->data_size - 1)];
800                                         float h, s, v;
801                                         float r_out, g_out, b_out;
802                                         int r, g, b;
803 #define DIVISION1 0.0
804 #define DIVISION2 -20.0
805 #define DIVISION3 INFINITYGAIN
806                                         if( db > DIVISION2 ) {
807                                                 h = 240 - (float)(db - DIVISION2) / (DIVISION1 - DIVISION2) *
808                                                         240;
809                                                 CLAMP(h, 0, 240);
810                                                 s = 1.0;
811                                                 v = 1.0;
812                                                 HSV::hsv_to_rgb(r_out, g_out, b_out, h, s, v);
813                                                 r = (int)(r_out * 0xff);
814                                                 g = (int)(g_out * 0xff);
815                                                 b = (int)(b_out * 0xff);
816                                         }
817                                         else if( db > DIVISION3 ) {
818                                                 h = 0.0;
819                                                 s = 0.0;
820                                                 v = (float)(db - DIVISION3) / (DIVISION2 - DIVISION3);
821                                                 HSV::hsv_to_rgb(r_out, g_out, b_out, h, s, v);
822                                                 r = (int)(r_out * 0xff);
823                                                 g = (int)(g_out * 0xff);
824                                                 b = (int)(b_out * 0xff);
825                                         }
826                                         else {
827                                                 r = g = b = 0;
828                                         }
829
830                                         canvas->set_color((r << 16) | (g << 8) | (b));
831                                         if( config.xzoom == 1 )
832                                                 canvas->draw_pixel(x, i);
833                                         else
834                                                 canvas->draw_line(x, i, x + config.xzoom, i);
835                                 }
836
837 // Copy a frame into history for each pixel
838                                 for( int i = 0; i < config.xzoom; i++ ) {
839                                         SpectrogramFrame *new_frame = new SpectrogramFrame(
840                                                 ptr->data_size);
841                                         frame_history.append(new_frame);
842                                         memcpy(new_frame->data, ptr->data,
843                                                 sizeof(float) * ptr->data_size);
844                                 }
845 // Clip history to canvas size
846                                 while(frame_history.size() > canvas->get_w())
847                                         frame_history.remove_object_number(0);
848
849                                 delete ptr;
850                         }
851                 }
852                 else {
853 // mode == VERTICAL
854 // Shift frames into history buffer
855                         for( int frame = 0; frame < total_frames; frame++ ) {
856                                 SpectrogramFrame *ptr = (SpectrogramFrame*)get_gui_frame(-1, 0);
857                                 fix_gui_frame(ptr);
858                                 frame_history.append(ptr);
859                         }
860 // Reduce history size
861                         while(frame_history.size() > config.history_size)
862                                 frame_history.remove_object_number(0);
863
864 // Draw frames from history
865                         canvas->clear_box(0, 0, canvas->get_w(), canvas->get_h());
866                         for( int frame = 0; frame < frame_history.size(); frame++ ) {
867                                 SpectrogramFrame *ptr = frame_history.get(frame);
868 //printf("%d %d\n", canvas->get_w(), ptr->data_size);
869
870                                 int luma = (frame + 1) * 0x80 / frame_history.size();
871                                 if( frame == frame_history.size() - 1 ) {
872                                         canvas->set_color(WHITE);
873                                         canvas->set_line_width(2);
874                                 }
875                                 else
876                                         canvas->set_color((luma << 16) | (luma << 8) | luma);
877                                 int x1 = 0, y1 = 0;
878                                 int w = canvas->get_w();
879                                 int h = canvas->get_h();
880                                 int number = 0;
881
882 //printf("Spectrogram::update_gui %d ", __LINE__);
883
884                                 for( int x2 = 0; x2 < w; x2++ ) {
885                                         float db = ptr->data[
886                                                 MIN((w - x2), ptr->data_size - 1)];
887 //if( x2 > w - 10 ) printf("%.02f ", ptr->data[x2]);
888                                         int y2 = h - 1 - (int)((db - INFINITYGAIN) /
889                                                 (0 - INFINITYGAIN) *
890                                                 h);
891                                         CLAMP(y2, 0, h - 1);
892
893                                         if( number ) {
894                                                 canvas->draw_line(x1, y1, x2, y2);
895                                         }
896                                         else {
897                                                 number++;
898                                         }
899                                         x1 = x2;
900                                         y1 = y2;
901                                 }
902
903                                 canvas->set_line_width(1);
904 //printf("\n");
905                         }
906
907                 }
908 // Recompute probe level
909                 window->calculate_frequency(window->probe_x, window->probe_y, 0);
910
911                 canvas->draw_overlay();
912                 canvas->flash();
913         }
914
915         window->unlock_window();
916 }
917
918
919 // convert GUI frame to canvas dimensions & normalized DB
920 void Spectrogram::fix_gui_frame(SpectrogramFrame *frame)
921 {
922         int niquist = get_project_samplerate() / 2;
923         int total_slots = frame->window_size / 2;
924         int max_slot = total_slots - 1;
925         BC_SubWindow *canvas = ((SpectrogramWindow*)thread->get_window())->canvas;
926         int pixels = canvas->get_w();
927         if( config.mode == HORIZONTAL ) pixels = canvas->get_h();
928
929 // allocate new frame
930         float *out_data = new float[pixels];
931         float *in_data = frame->data;
932
933 // Scale slots to pixels
934         for( int i = 0; i < pixels; i++ ) {
935 // Low frequency of row
936                 int freq_index1 = (int)((pixels - i) * TOTALFREQS / pixels);
937 // High frequency of row
938                 int freq_index2 = (int)((pixels - i + 1) * TOTALFREQS / pixels);
939                 int freq1 = Freq::tofreq(freq_index1);
940                 int freq2 = Freq::tofreq(freq_index2);
941                 float slot1_f = (float)freq1 * max_slot / niquist;
942                 float slot2_f = (float)freq2 * max_slot / niquist;
943                 int slot1 = (int)(slot1_f);
944                 int slot2 = (int)(slot2_f);
945                 slot1 = MIN(slot1, max_slot);
946                 slot2 = MIN(slot2, max_slot);
947                 double sum = 0;
948
949 // Accumulate multiple slots in the same pixel
950                 if( slot2 > slot1 + 1 ) {
951                         for( int j = slot1; j <= slot2; j++ )
952                                 sum += in_data[j];
953
954                         sum /= slot2 - slot1 + 1;
955                 }
956                 else {
957 // Blend 2 slots to create pixel
958                         float weight = slot1_f - floor(slot1_f);
959                         int slot3 = MIN(slot1 + 1, max_slot);
960                         sum = in_data[slot1] * (1.0 - weight) +
961                                 in_data[slot3] * weight;
962                 }
963
964                 out_data[i] = sum;
965         }
966
967
968 // Normalize
969         if( config.normalize ) {
970 // Get the maximum level in the spectrogram
971                 float max = 0;
972                 for( int i = 0; i < pixels; i++ ) {
973                         if( out_data[i] > max ) max = out_data[i];
974                 }
975
976 // Scale all levels
977                 for( int i = 0; i < pixels; i++ ) {
978                         out_data[i] = frame->level *
979                                 out_data[i] /
980                                 max;
981                 }
982         }
983         else {
984 // Get the maximum level in the spectrogram
985                 float max = 0;
986                 for( int i = 0; i < pixels; i++ ) {
987                         if( out_data[i] > max ) max = out_data[i];
988                 }
989
990 // Maximum level in spectrogram is the maximum waveform level
991                 float frame_max = in_data[0];
992                 for( int i = 0; i < pixels; i++ ) {
993                         out_data[i] = frame->level *
994                                 frame_max * out_data[i] / max;
995                 }
996         }
997
998 // DB conversion
999 //printf("Spectrogram::render_gui %d ", __LINE__);
1000         for( int i = 0; i < pixels; i++ ) {
1001                 out_data[i] = DB::todb(out_data[i]);
1002 //if( i > pixels - 10 ) printf("%.02f ", ptr->data[i]);
1003
1004         }
1005
1006         delete [] in_data;
1007         frame->data = out_data;
1008         frame->data_size = pixels;
1009 }
1010
1011
1012 LOAD_CONFIGURATION_MACRO(Spectrogram, SpectrogramConfig)
1013
1014 void Spectrogram::read_data(KeyFrame *keyframe)
1015 {
1016 //printf("Spectrogram::read_data %d this=%p\n", __LINE__, this);
1017         FileXML input;
1018         input.set_shared_input(keyframe->xbuf);
1019
1020         int result = 0;
1021         while( !(result = input.read_tag()) ) {
1022                 if( input.tag.title_is("SPECTROGRAM") ) {
1023                         config.level = input.tag.get_property("LEVEL", config.level);
1024                         config.normalize = input.tag.get_property("NORMALIZE", config.normalize);
1025                         config.window_size = input.tag.get_property("WINDOW_SIZE", config.window_size);
1026                         config.xzoom = input.tag.get_property("XZOOM", config.xzoom);
1027                         config.mode = input.tag.get_property("MODE", config.mode);
1028                         config.history_size = input.tag.get_property("HISTORY_SIZE", config.history_size);
1029                         if( is_defaults() ) {
1030                                 w = input.tag.get_property("W", w);
1031                                 h = input.tag.get_property("H", h);
1032                         }
1033                 }
1034         }
1035 }
1036
1037 void Spectrogram::save_data(KeyFrame *keyframe)
1038 {
1039         FileXML output;
1040         output.set_shared_output(keyframe->xbuf);
1041
1042         output.tag.set_title("SPECTROGRAM");
1043         output.tag.set_property("LEVEL", (double)config.level);
1044         output.tag.set_property("NORMALIZE", (double)config.normalize);
1045         output.tag.set_property("WINDOW_SIZE", (int)config.window_size);
1046         output.tag.set_property("XZOOM", (int)config.xzoom);
1047         output.tag.set_property("MODE", (int)config.mode);
1048         output.tag.set_property("HISTORY_SIZE", (int)config.history_size);
1049         output.tag.set_property("W", (int)w);
1050         output.tag.set_property("H", (int)h);
1051         output.append_tag();
1052         output.tag.set_title("/SPECTROGRAM");
1053         output.append_tag();
1054         output.append_newline();
1055         output.terminate_string();
1056 }
1057