--- /dev/null
+
+/*
+ * CINELERRA
+ * Copyright (C) 1997-2011 Adam Williams <broadcast at earthling dot net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "audioscope.h"
+#include "bcdisplayinfo.h"
+#include "bcsignals.h"
+#include "clip.h"
+#include "cursors.h"
+#include "bchash.h"
+#include "filexml.h"
+#include "language.h"
+#include "cicolors.h"
+#include "samples.h"
+#include "theme.h"
+#include "transportque.inc"
+#include "units.h"
+#include "vframe.h"
+
+
+#include <string.h>
+
+
+
+REGISTER_PLUGIN(AudioScope)
+
+
+AudioScopeConfig::AudioScopeConfig()
+{
+ window_size = MAX_WINDOW;
+ history_size = 4;
+ mode = XY_MODE;
+ trigger_level = 0;
+}
+
+int AudioScopeConfig::equivalent(AudioScopeConfig &that)
+{
+ return window_size == that.window_size &&
+ history_size == that.history_size &&
+ mode == that.mode &&
+ EQUIV(trigger_level, that.trigger_level);
+}
+
+void AudioScopeConfig::copy_from(AudioScopeConfig &that)
+{
+ window_size = that.window_size;
+ history_size = that.history_size;
+ mode = that.mode;
+ trigger_level = that.trigger_level;
+}
+
+void AudioScopeConfig::interpolate(AudioScopeConfig &prev,
+ AudioScopeConfig &next,
+ int64_t prev_frame,
+ int64_t next_frame,
+ int64_t current_frame)
+{
+ copy_from(prev);
+
+ CLAMP(history_size, MIN_HISTORY, MAX_HISTORY - 1);
+}
+
+
+
+AudioScopeFrame::AudioScopeFrame(int data_size, int channels)
+{
+ this->size = data_size;
+ this->channels = channels;
+ for(int i = 0; i < CHANNELS; i++)
+ data[i] = new float[data_size];
+ force = 0;
+}
+
+AudioScopeFrame::~AudioScopeFrame()
+{
+ for(int i = 0; i < CHANNELS; i++)
+ delete [] data[i];
+}
+
+
+
+
+
+
+
+AudioScopeHistory::AudioScopeHistory(AudioScope *plugin,
+ int x,
+ int y)
+ : BC_IPot(x,
+ y,
+ plugin->config.history_size,
+ MIN_HISTORY,
+ MAX_HISTORY)
+{
+ this->plugin = plugin;
+}
+
+int AudioScopeHistory::handle_event()
+{
+ plugin->config.history_size = get_value();
+ plugin->send_configure_change();
+ return 1;
+}
+
+
+
+
+
+
+
+
+AudioScopeWindowSize::AudioScopeWindowSize(AudioScope *plugin,
+ int x,
+ int y,
+ char *text)
+ : BC_PopupMenu(x,
+ y,
+ 80,
+ text)
+{
+ this->plugin = plugin;
+}
+
+int AudioScopeWindowSize::handle_event()
+{
+ plugin->config.window_size = atoi(get_text());
+ plugin->send_configure_change();
+ return 1;
+}
+
+
+AudioScopeWindowSizeTumbler::AudioScopeWindowSizeTumbler(AudioScope *plugin, int x, int y)
+ : BC_Tumbler(x,
+ y)
+{
+ this->plugin = plugin;
+}
+
+int AudioScopeWindowSizeTumbler::handle_up_event()
+{
+ plugin->config.window_size *= 2;
+ if(plugin->config.window_size > MAX_WINDOW)
+ plugin->config.window_size = MAX_WINDOW;
+ char string[BCTEXTLEN];
+ sprintf(string, "%d", plugin->config.window_size);
+ ((AudioScopeWindow*)plugin->get_thread()->get_window())->window_size->set_text(string);
+ plugin->send_configure_change();
+ return 1;
+}
+
+int AudioScopeWindowSizeTumbler::handle_down_event()
+{
+ plugin->config.window_size /= 2;
+ if(plugin->config.window_size < MIN_WINDOW)
+ plugin->config.window_size = MIN_WINDOW;
+ char string[BCTEXTLEN];
+ sprintf(string, "%d", plugin->config.window_size);
+ ((AudioScopeWindow*)plugin->get_thread()->get_window())->window_size->set_text(string);
+ plugin->send_configure_change();
+ return 1;
+}
+
+
+
+
+
+AudioScopeCanvas::AudioScopeCanvas(AudioScope *plugin,
+ int x,
+ int y,
+ int w,
+ int h)
+ : BC_SubWindow(x, y, w, h, BLACK)
+{
+ this->plugin = plugin;
+ current_operation = NONE;
+}
+
+int AudioScopeCanvas::button_press_event()
+{
+ if(is_event_win() && cursor_inside())
+ {
+ calculate_point();
+ current_operation = DRAG;
+ plugin->send_configure_change();
+ return 1;
+ }
+ return 0;
+}
+
+int AudioScopeCanvas::button_release_event()
+{
+ if(current_operation == DRAG)
+ {
+ current_operation = NONE;
+ return 1;
+ }
+ return 0;
+}
+
+int AudioScopeCanvas::cursor_motion_event()
+{
+ if(current_operation == DRAG)
+ {
+ calculate_point();
+ return 1;
+ }
+ return 0;
+}
+
+
+void AudioScopeCanvas::calculate_point()
+{
+ int x = get_cursor_x();
+ int y = get_cursor_y();
+ CLAMP(x, 0, get_w() - 1);
+ CLAMP(y, 0, get_h() - 1);
+
+ ((AudioScopeWindow*)plugin->thread->window)->calculate_probe(
+ x,
+ y,
+ 1);
+
+//printf("AudioScopeCanvas::calculate_point %d %d\n", __LINE__, Freq::tofreq(freq_index));
+}
+
+
+
+
+
+
+
+AudioScopeTriggerLevel::AudioScopeTriggerLevel(AudioScope *plugin, int x, int y)
+ : BC_FPot(x, y, plugin->config.trigger_level, (float)-1.0, (float)1.0)
+{
+ this->plugin = plugin;
+ set_precision(0.01);
+}
+
+int AudioScopeTriggerLevel::handle_event()
+{
+ AudioScopeWindow *window = (AudioScopeWindow*)plugin->thread->window;
+ window->draw_trigger();
+ plugin->config.trigger_level = get_value();
+ plugin->send_configure_change();
+ window->draw_trigger();
+ window->canvas->flash();
+ return 1;
+}
+
+
+
+
+
+AudioScopeMode::AudioScopeMode(AudioScope *plugin,
+ int x,
+ int y)
+ : BC_PopupMenu(x,
+ y,
+ 180,
+ mode_to_text(plugin->config.mode))
+{
+ this->plugin = plugin;
+}
+
+void AudioScopeMode::create_objects()
+{
+ add_item(new BC_MenuItem(mode_to_text(XY_MODE)));
+ add_item(new BC_MenuItem(mode_to_text(WAVEFORM_NO_TRIGGER)));
+ add_item(new BC_MenuItem(mode_to_text(WAVEFORM_RISING_TRIGGER)));
+ add_item(new BC_MenuItem(mode_to_text(WAVEFORM_FALLING_TRIGGER)));
+}
+
+int AudioScopeMode::handle_event()
+{
+ if(plugin->config.mode != text_to_mode(get_text()))
+ {
+ AudioScopeWindow *window = (AudioScopeWindow*)plugin->thread->window;
+ window->probe_x = -1;
+ window->probe_y = -1;
+ plugin->config.mode = text_to_mode(get_text());
+ plugin->send_configure_change();
+
+ window->canvas->clear_box(0, 0, window->canvas->get_w(), window->canvas->get_h());
+ window->draw_overlay();
+ window->canvas->flash();
+ }
+ return 1;
+}
+
+const char* AudioScopeMode::mode_to_text(int mode)
+{
+ switch(mode)
+ {
+ case XY_MODE:
+ return _("XY Mode");
+ case WAVEFORM_NO_TRIGGER:
+ return _("Waveform");
+ case WAVEFORM_RISING_TRIGGER:
+ return _("Rising Trigger");
+ case WAVEFORM_FALLING_TRIGGER:
+ default:
+ return _("Falling Trigger");
+ }
+}
+
+int AudioScopeMode::text_to_mode(const char *text)
+{
+ if(!strcmp(mode_to_text(XY_MODE), text)) return XY_MODE;
+ if(!strcmp(mode_to_text(WAVEFORM_NO_TRIGGER), text)) return WAVEFORM_NO_TRIGGER;
+ if(!strcmp(mode_to_text(WAVEFORM_RISING_TRIGGER), text)) return WAVEFORM_RISING_TRIGGER;
+ if(!strcmp(mode_to_text(WAVEFORM_FALLING_TRIGGER), text)) return WAVEFORM_FALLING_TRIGGER;
+ return XY_MODE;
+}
+
+
+
+
+
+
+
+
+
+AudioScopeWindow::AudioScopeWindow(AudioScope *plugin)
+ : PluginClientWindow(plugin,
+ plugin->w,
+ plugin->h,
+ 320,
+ 320,
+ 1)
+{
+ this->plugin = plugin;
+}
+
+AudioScopeWindow::~AudioScopeWindow()
+{
+}
+
+void AudioScopeWindow::create_objects()
+{
+ int x = plugin->get_theme()->widget_border;
+ int y = 0;
+ char string[BCTEXTLEN];
+
+ add_subwindow(canvas = new AudioScopeCanvas(plugin,
+ 0,
+ 0,
+ get_w(),
+ get_h() -
+ plugin->get_theme()->widget_border * 4 -
+ BC_Title::calculate_h(this, "X") * 3));
+ canvas->set_cursor(CROSS_CURSOR, 0, 0);
+ draw_overlay();
+
+ y += canvas->get_h() + plugin->get_theme()->widget_border;
+
+ add_subwindow(history_size_title = new BC_Title(x, y, _("History Size:")));
+ x += history_size_title->get_w() + plugin->get_theme()->widget_border;
+ add_subwindow(history_size = new AudioScopeHistory(plugin,
+ x,
+ y));
+ x += history_size->get_w() + plugin->get_theme()->widget_border;
+
+
+ int x1 = x;
+ sprintf(string, "%d", plugin->config.window_size);
+ add_subwindow(window_size_title = new BC_Title(x, y, _("Window Size:")));
+
+ x += window_size_title->get_w() + plugin->get_theme()->widget_border;
+ add_subwindow(window_size = new AudioScopeWindowSize(plugin, x, y, string));
+ x += window_size->get_w();
+ add_subwindow(window_size_tumbler = new AudioScopeWindowSizeTumbler(plugin, x, y));
+
+ for(int i = MIN_WINDOW; i <= MAX_WINDOW; i *= 2)
+ {
+ sprintf(string, "%d", i);
+ window_size->add_item(new BC_MenuItem(string));
+ }
+
+ x += window_size_tumbler->get_w() + plugin->get_theme()->widget_border;
+
+ int y1 = y;
+ x = x1;
+ y += window_size->get_h() + plugin->get_theme()->widget_border;
+ add_subwindow(mode_title = new BC_Title(x, y, _("Mode:")));
+
+ x += mode_title->get_w() + plugin->get_theme()->widget_border;
+ add_subwindow(mode = new AudioScopeMode(plugin, x, y));
+ mode->create_objects();
+
+ x += mode->get_w() + plugin->get_theme()->widget_border;
+ y = y1;
+ add_subwindow(trigger_level_title = new BC_Title(x, y, _("Trigger level:")));
+
+ x += trigger_level_title->get_w() + plugin->get_theme()->widget_border;
+ add_subwindow(trigger_level = new AudioScopeTriggerLevel(plugin, x, y));
+
+
+ x += trigger_level->get_w() + plugin->get_theme()->widget_border;
+ y = y1;
+ add_subwindow(probe_sample = new BC_Title(x, y, _("Sample: 0")));
+ y += probe_sample->get_h() + plugin->get_theme()->widget_border;
+ add_subwindow(probe_level0 = new BC_Title(x, y, _("Level 0: 0"), MEDIUMFONT, CHANNEL0_COLOR));
+ y += probe_level0->get_h() + plugin->get_theme()->widget_border;
+ add_subwindow(probe_level1 = new BC_Title(x, y, _("Level 1: 0"), MEDIUMFONT, CHANNEL1_COLOR));
+ y += probe_level1->get_h() + plugin->get_theme()->widget_border;
+
+
+
+ show_window();
+}
+
+int AudioScopeWindow::resize_event(int w, int h)
+{
+ int y_diff = h - get_h();
+
+ int canvas_factor = get_h() - canvas->get_h();
+ canvas->reposition_window(0,
+ 0,
+ w,
+ h - canvas_factor);
+ canvas->clear_box(0, 0, canvas->get_w(), canvas->get_h());
+ draw_overlay();
+ canvas->flash(0);
+
+
+// Remove all columns which may be a different size.
+ plugin->frame_buffer.remove_all_objects();
+
+ window_size_title->reposition_window(window_size_title->get_x(),
+ window_size_title->get_y() + y_diff);
+ window_size->reposition_window(window_size->get_x(),
+ window_size->get_y() + y_diff);
+ window_size_tumbler->reposition_window(window_size_tumbler->get_x(),
+ window_size_tumbler->get_y() + y_diff);
+
+
+
+ history_size_title->reposition_window(history_size_title->get_x(),
+ history_size_title->get_y() + y_diff);
+ history_size->reposition_window(history_size->get_x(),
+ history_size->get_y() + y_diff);
+
+ mode_title->reposition_window(mode_title->get_x(),
+ mode_title->get_y() + y_diff);
+ mode->reposition_window(mode->get_x(),
+ mode->get_y() + y_diff);
+
+
+ trigger_level_title->reposition_window(trigger_level_title->get_x(),
+ trigger_level_title->get_y() + y_diff);
+ trigger_level->reposition_window(trigger_level->get_x(),
+ trigger_level->get_y() + y_diff);
+
+
+ probe_sample->reposition_window(probe_sample->get_x(),
+ probe_sample->get_y() + y_diff);
+ probe_level0->reposition_window(probe_level0->get_x(),
+ probe_level0->get_y() + y_diff);
+ probe_level1->reposition_window(probe_level1->get_x(),
+ probe_level1->get_y() + y_diff);
+
+ flush();
+
+ plugin->w = w;
+ plugin->h = h;
+ return 0;
+}
+
+void AudioScopeWindow::draw_overlay()
+{
+ canvas->set_color(GREEN);
+
+ canvas->draw_line(0,
+ canvas->get_h() / 2,
+ canvas->get_w(),
+ canvas->get_h() / 2);
+ if(plugin->config.mode == XY_MODE)
+ {
+ canvas->draw_line(canvas->get_w() / 2,
+ 0,
+ canvas->get_w() / 2,
+ canvas->get_h());
+ }
+
+ draw_trigger();
+ draw_probe();
+//printf("AudioScopeWindow::draw_overlay %d\n", __LINE__);
+}
+
+void AudioScopeWindow::draw_trigger()
+{
+ if(plugin->config.mode == WAVEFORM_RISING_TRIGGER ||
+ plugin->config.mode == WAVEFORM_FALLING_TRIGGER)
+ {
+ int y = (int)(-plugin->config.trigger_level *
+ canvas->get_h() / 2 +
+ canvas->get_h() / 2);
+ CLAMP(y, 0, canvas->get_h() - 1);
+ canvas->set_inverse();
+ canvas->set_color(RED);
+ canvas->draw_line(0, y, canvas->get_w(), y);
+ canvas->set_opaque();
+ }
+}
+
+void AudioScopeWindow::draw_probe()
+{
+ if(probe_x >= 0 || probe_y >= 0)
+ {
+ canvas->set_color(GREEN);
+ canvas->set_inverse();
+
+ if(plugin->config.mode == XY_MODE)
+ {
+ canvas->draw_line(0, probe_y, get_w(), probe_y);
+ canvas->draw_line(probe_x, 0, probe_x, get_h());
+ }
+ else
+ {
+ canvas->draw_line(probe_x, 0, probe_x, get_h());
+ }
+
+ canvas->set_opaque();
+ }
+}
+
+
+
+
+void AudioScopeWindow::calculate_probe(int x, int y, int do_overlay)
+{
+ if(x < 0 && y < 0) return;
+
+// Clear previous overlay
+ if(do_overlay) draw_probe();
+
+// New probe position
+ probe_x = x;
+ probe_y = y;
+
+ int sample = 0;
+ double channel0 = 0;
+ double channel1 = 0;
+ if(plugin->config.mode == XY_MODE)
+ {
+ channel0 = (float)(probe_x - canvas->get_w() / 2) / (canvas->get_w() / 2);
+ channel1 = (float)(canvas->get_h() / 2 - probe_y) / (canvas->get_h() / 2);
+ }
+ else
+ if(plugin->current_frame)
+ {
+ sample = probe_x * plugin->current_frame->size / canvas->get_w();
+ CLAMP(sample, 0, plugin->current_frame->size - 1);
+ channel0 = plugin->current_frame->data[0][sample];
+ channel1 = plugin->current_frame->data[1][sample];
+ }
+
+
+ char string[BCTEXTLEN];
+ sprintf(string, _("Sample: %d"), sample);
+ probe_sample->update(string);
+
+ sprintf(string, _("Level 0: %.2f"), channel0);
+ probe_level0->update(string);
+
+ sprintf(string, _("Level 1: %.2f"), channel1);
+ probe_level1->update(string);
+
+
+ if(do_overlay)
+ {
+ draw_probe();
+ canvas->flash();
+ }
+}
+
+
+
+
+
+void AudioScopeWindow::update_gui()
+{
+ char string[BCTEXTLEN];
+ sprintf(string, "%d", plugin->config.window_size);
+ window_size->set_text(string);
+ history_size->update(plugin->config.history_size);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+AudioScope::AudioScope(PluginServer *server)
+ : PluginAClient(server)
+{
+ reset();
+ timer = new Timer;
+ w = 640;
+ h = 480;
+}
+
+AudioScope::~AudioScope()
+{
+ delete [] data;
+ for(int i = 0; i < CHANNELS; i++)
+ delete audio_buffer[i];
+ delete timer;
+ frame_buffer.remove_all_objects();
+ frame_history.remove_all_objects();
+}
+
+
+void AudioScope::reset()
+{
+ thread = 0;
+ data = 0;
+ for(int i = 0; i < CHANNELS; i++)
+ audio_buffer[i] = 0;
+ allocated_data = 0;
+ total_windows = 0;
+ buffer_size = 0;
+ bzero(&header, sizeof(data_header_t));
+ current_frame = 0;
+}
+
+
+const char* AudioScope::plugin_title() { return _("AudioScope"); }
+int AudioScope::is_realtime() { return 1; }
+int AudioScope::is_multichannel() { return 1; }
+
+int AudioScope::process_buffer(int64_t size,
+ Samples **buffer,
+ int64_t start_position,
+ int sample_rate)
+{
+ int channels = MIN(get_total_buffers(), CHANNELS);
+
+// Pass through
+ for(int i = 0; i < get_total_buffers(); i++)
+ {
+ read_samples(buffer[i],
+ i,
+ sample_rate,
+ start_position,
+ size);
+ }
+
+ load_configuration();
+
+// Reset audio buffer
+ if(window_size != config.window_size)
+ {
+ render_stop();
+ window_size = config.window_size;
+ }
+
+ if(!data)
+ {
+ data = new unsigned char[sizeof(data_header_t)];
+ allocated_data = 0;
+ }
+
+ if(!audio_buffer[0])
+ {
+ for(int i = 0; i < CHANNELS; i++)
+ {
+ audio_buffer[i] = new Samples(MAX_WINDOW);
+ }
+ }
+
+// Accumulate audio
+ int needed = buffer_size + size;
+ if(audio_buffer[0]->get_allocated() < needed)
+ {
+ for(int i = 0; i < CHANNELS; i++)
+ {
+ Samples *new_samples = new Samples(needed);
+ memcpy(new_samples->get_data(),
+ audio_buffer[i]->get_data(),
+ sizeof(double) * buffer_size);
+ delete audio_buffer[i];
+ audio_buffer[i] = new_samples;
+ }
+ }
+
+ for(int i = 0; i < CHANNELS; i++)
+ {
+ memcpy(audio_buffer[i]->get_data() + buffer_size,
+ buffer[MIN(i, channels - 1)]->get_data(),
+ sizeof(double) * size);
+ }
+ buffer_size += size;
+
+
+// Append a windows to end of GUI buffer
+ total_windows = 0;
+ while(buffer_size >= window_size)
+ {
+ if(allocated_data < (total_windows + 1) * window_size * CHANNELS)
+ {
+ int new_allocation = (total_windows + 1) *
+ window_size *
+ CHANNELS;
+ unsigned char *new_data = new unsigned char[sizeof(data_header_t) +
+ sizeof(float) * new_allocation];
+ data_header_t *new_header = (data_header_t*)new_data;
+ data_header_t *old_header = (data_header_t*)data;
+ memcpy(new_header->samples,
+ old_header->samples,
+ sizeof(float) * allocated_data);
+ delete [] data;
+ data = new_data;
+ allocated_data = new_allocation;
+ }
+
+// Search for trigger
+ int need_trigger = 0;
+ int got_trigger = 0;
+ int trigger_sample = 0;
+//printf("AudioScope::process_buffer %d\n", __LINE__);
+ if(config.mode == WAVEFORM_RISING_TRIGGER)
+ {
+ need_trigger = 1;
+ double *trigger_data = audio_buffer[0]->get_data();
+ for(int i = 1; i < buffer_size - window_size; i++)
+ {
+ if(trigger_data[i - 1] < config.trigger_level &&
+ trigger_data[i] >= config.trigger_level)
+ {
+ got_trigger = 1;
+ trigger_sample = i;
+ break;
+ }
+ }
+ }
+ else
+ if(config.mode == WAVEFORM_FALLING_TRIGGER)
+ {
+ need_trigger = 1;
+ double *trigger_data = audio_buffer[0]->get_data();
+ for(int i = 1; i < buffer_size - window_size; i++)
+ {
+ if(trigger_data[i - 1] > config.trigger_level &&
+ trigger_data[i] <= config.trigger_level)
+ {
+ got_trigger = 1;
+ trigger_sample = i;
+ break;
+ }
+ }
+ }
+
+//printf("AudioScope::process_buffer %d\n", __LINE__);
+// Flush buffer
+ if(need_trigger && !got_trigger)
+ {
+//printf("AudioScope::process_buffer %d\n", __LINE__);
+ for(int j = 0; j < CHANNELS; j++)
+ {
+ double *sample_input = audio_buffer[j]->get_data();
+ memcpy(sample_input,
+ sample_input + buffer_size - window_size,
+ sizeof(double) * window_size);
+ }
+
+ buffer_size = window_size;
+ if(buffer_size == window_size) break;
+//printf("AudioScope::process_buffer %d\n", __LINE__);
+ }
+ else
+ {
+ data_header_t *header = (data_header_t*)data;
+//printf("AudioScope::process_buffer %d\n", __LINE__);
+ for(int j = 0; j < CHANNELS; j++)
+ {
+ float *sample_output = header->samples +
+ total_windows * CHANNELS * window_size +
+ window_size * j;
+ double *sample_input = audio_buffer[j]->get_data();
+ for(int i = 0; i < window_size; i++)
+ {
+ sample_output[i] = sample_input[i + trigger_sample];
+ }
+
+// Shift accumulation buffer
+//printf("AudioScope::process_buffer %d\n", __LINE__);
+ memcpy(sample_input,
+ sample_input + trigger_sample + window_size,
+ sizeof(double) * (buffer_size - trigger_sample - window_size));
+//printf("AudioScope::process_buffer %d\n", __LINE__);
+ }
+
+//printf("AudioScope::process_buffer %d\n", __LINE__);
+
+ total_windows++;
+ buffer_size -= window_size + trigger_sample;
+//printf("AudioScope::process_buffer %d\n", __LINE__);
+ }
+ }
+
+ data_header_t *header = (data_header_t*)data;
+ header->window_size = window_size;
+ header->sample_rate = sample_rate;
+ header->channels = channels;
+ header->total_windows = total_windows;
+
+ send_render_gui(data,
+ sizeof(data_header_t) +
+ sizeof(float) * total_windows * window_size * CHANNELS);
+
+ return 0;
+}
+
+void AudioScope::render_stop()
+{
+ buffer_size = 0;
+ frame_buffer.remove_all_objects();
+}
+
+
+
+
+NEW_WINDOW_MACRO(AudioScope, AudioScopeWindow)
+
+void AudioScope::update_gui()
+{
+ if(thread)
+ {
+ int result = load_configuration();
+ AudioScopeWindow *window = (AudioScopeWindow*)thread->get_window();
+ window->lock_window("AudioScope::update_gui");
+ if(result) window->update_gui();
+
+// Shift in accumulated frames
+ if(frame_buffer.size())
+ {
+ BC_SubWindow *canvas = window->canvas;
+// Frames to draw in this iteration
+ int total_frames = timer->get_difference() *
+ header.sample_rate /
+ header.window_size /
+ 1000;
+ if(total_frames) timer->subtract(total_frames *
+ header.window_size *
+ 1000 /
+ header.sample_rate);
+
+// Add forced frame drawing
+ for(int i = 0; i < frame_buffer.size(); i++)
+ if(frame_buffer.get(i)->force) total_frames++;
+ total_frames = MIN(frame_buffer.size(), total_frames);
+//printf("AudioScope::update_gui %d %d\n", __LINE__, total_frames);
+
+
+
+// Shift frames into history
+ for(int frame = 0; frame < total_frames; frame++)
+ {
+ if(frame_history.size() >= config.history_size)
+ frame_history.remove_object_number(0);
+
+ frame_history.append(frame_buffer.get(0));
+ frame_buffer.remove_number(0);
+ }
+
+// Reduce history size
+ while(frame_history.size() > config.history_size)
+ frame_history.remove_object_number(0);
+
+// Point probe data at history
+ current_frame = frame_history.get(frame_history.size() - 1);
+
+// Draw frames from history
+ canvas->clear_box(0, 0, canvas->get_w(), canvas->get_h());
+ for(int frame = 0; frame < frame_history.size(); frame++)
+ {
+ AudioScopeFrame *ptr = frame_history.get(frame);
+
+ int luma = (frame + 1) * 0x80 / frame_history.size();
+ if(frame == frame_history.size() - 1)
+ {
+ canvas->set_color(WHITE);
+ canvas->set_line_width(2);
+ }
+ else
+ {
+ canvas->set_color((luma << 16) |
+ (luma << 8) |
+ luma);
+ }
+
+ int x1 = 0;
+ int y1 = 0;
+ int w = canvas->get_w();
+ int h = canvas->get_h();
+ float *channel0 = ptr->data[0];
+ float *channel1 = ptr->data[1];
+
+ if(config.mode == XY_MODE)
+ {
+
+ int number = 0;
+ for(int point = 0; point < ptr->size; point++)
+ {
+ int x2 = (int)(channel0[point] * w / 2 + w / 2);
+ int y2 = (int)(-channel1[point] * h / 2 + h / 2);
+ CLAMP(x2, 0, w - 1);
+ CLAMP(y2, 0, h - 1);
+
+ if(number)
+ {
+ canvas->draw_line(x1, y1, x2, y2);
+ }
+ else
+ {
+ number++;
+ }
+
+ x1 = x2;
+ y1 = y2;
+ }
+ }
+ else
+ {
+ for(int channel = ptr->channels - 1; channel >= 0; channel--)
+ {
+ if(channel > 0)
+ {
+ if(frame == frame_history.size() - 1)
+ {
+ canvas->set_color(PINK);
+ canvas->set_line_width(2);
+ }
+ else
+ canvas->set_color(((luma * 0xff / 0xff) << 16) |
+ ((luma * 0x80 / 0xff) << 8) |
+ ((luma * 0x80 / 0xff)));
+ }
+ else
+ {
+ if(frame == frame_history.size() - 1)
+ canvas->set_color(WHITE);
+ else
+ canvas->set_color((luma << 16) |
+ (luma << 8) |
+ luma);
+ }
+
+ if(ptr->size < w)
+ {
+ for(int point = 0; point < ptr->size; point++)
+ {
+ int x2 = point * w / ptr->size;
+ int y2 = (int)(-ptr->data[channel][point] * h / 2 + h / 2);
+ CLAMP(y2, 0, h - 1);
+ if(point > 0)
+ {
+ canvas->draw_line(x1, y1, x2, y2);
+ }
+
+ x1 = x2;
+ y1 = y2;
+ }
+ }
+ else
+ {
+ for(int x2 = 0; x2 < w; x2++)
+ {
+ int sample1 = x2 * ptr->size / w;
+ int sample2 = (x2 + 1) * ptr->size / w;
+ double min = ptr->data[channel][sample1];
+ double max = ptr->data[channel][sample1];
+ for(int i = sample1 + 1; i < sample2; i++)
+ {
+ double value = ptr->data[channel][i];
+ if(value < min) min = value;
+ if(value > max) max = value;
+ }
+
+ int min2 = (int)(-min * h / 2 + h / 2);
+ int max2 = (int)(-max * h / 2 + h / 2);
+ CLAMP(min2, 0, h - 1);
+ CLAMP(max2, 0, h - 1);
+ int y2 = (min2 + max2) / 2;
+ canvas->draw_line(x2, min2, x2, max2);
+ if(x2 > 0)
+ {
+ canvas->draw_line(x2, y1, x2, y2);
+ }
+
+ y1 = y2;
+ }
+ }
+ }
+ }
+
+ canvas->set_line_width(1);
+ }
+
+// Recompute probe level
+ window->calculate_probe(window->probe_x, window->probe_y, 0);
+
+
+// Draw overlay
+ window->draw_overlay();
+ canvas->flash();
+ }
+
+
+
+ while(frame_buffer.size() > MAX_COLUMNS)
+ frame_buffer.remove_object_number(0);
+
+ thread->get_window()->unlock_window();
+ }
+}
+
+void AudioScope::render_gui(void *data, int size)
+{
+ if(thread)
+ {
+ thread->get_window()->lock_window("AudioScope::render_gui");
+ data_header_t *header = (data_header_t*)data;
+ memcpy(&this->header, header, sizeof(data_header_t));
+ //BC_SubWindow *canvas = ((AudioScopeWindow*)thread->get_window())->canvas;
+ //int h = canvas->get_h();
+
+// Set all previous frames to draw immediately
+ for(int i = 0; i < frame_buffer.size(); i++)
+ frame_buffer.get(i)->force = 1;
+
+ for(int current_fragment = 0;
+ current_fragment < header->total_windows;
+ current_fragment++)
+ {
+ float *in_frame = header->samples +
+ current_fragment * header->window_size * CHANNELS;
+ AudioScopeFrame *out_frame = new AudioScopeFrame(
+ header->window_size,
+ header->channels);
+
+// Copy the window to the frame
+ for(int j = 0; j < CHANNELS; j++)
+ {
+ for(int i = 0; i < header->window_size; i++)
+ {
+ out_frame->data[j][i] = in_frame[header->window_size * j + i];
+ }
+ }
+
+ frame_buffer.append(out_frame);
+ total_windows++;
+ }
+
+ timer->update();
+ thread->get_window()->unlock_window();
+ }
+}
+
+
+
+
+
+
+
+
+LOAD_CONFIGURATION_MACRO(AudioScope, AudioScopeConfig)
+
+void AudioScope::read_data(KeyFrame *keyframe)
+{
+ FileXML input;
+ input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
+
+ int result = 0;
+ while(!result)
+ {
+ result = input.read_tag();
+
+ if(!result)
+ {
+ if(input.tag.title_is("AUDIOSCOPE"))
+ {
+ if(is_defaults())
+ {
+ w = input.tag.get_property("W", w);
+ h = input.tag.get_property("H", h);
+ }
+
+ config.history_size = input.tag.get_property("HISTORY_SIZE", config.history_size);
+ config.window_size = input.tag.get_property("WINDOW_SIZE", config.window_size);
+ config.mode = input.tag.get_property("MODE", config.mode);
+ config.trigger_level = input.tag.get_property("TRIGGER_LEVEL", config.trigger_level);
+ }
+ }
+ }
+}
+
+void AudioScope::save_data(KeyFrame *keyframe)
+{
+ FileXML output;
+ output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
+
+//printf("AudioScope::save_data %d %d %d\n", __LINE__, config.w, config.h);
+ output.tag.set_title("AUDIOSCOPE");
+ output.tag.set_property("W", w);
+ output.tag.set_property("H", h);
+ output.tag.set_property("HISTORY_SIZE", config.history_size);
+ output.tag.set_property("WINDOW_SIZE", (int)config.window_size);
+ output.tag.set_property("MODE", (int)config.mode);
+ output.tag.set_property("TRIGGER_LEVEL", (float)config.trigger_level);
+ output.append_tag();
+ output.tag.set_title("/AUDIOSCOPE");
+ output.append_tag();
+ output.append_newline();
+ output.terminate_string();
+}
+
+
+
+
+