diff options
Diffstat (limited to 'plugins/eg-scope.lv2/examploscope_ui.c')
-rw-r--r-- | plugins/eg-scope.lv2/examploscope_ui.c | 954 |
1 files changed, 472 insertions, 482 deletions
diff --git a/plugins/eg-scope.lv2/examploscope_ui.c b/plugins/eg-scope.lv2/examploscope_ui.c index e9e0c6f..e601843 100644 --- a/plugins/eg-scope.lv2/examploscope_ui.c +++ b/plugins/eg-scope.lv2/examploscope_ui.c @@ -38,7 +38,7 @@ #include <string.h> // Drawing area size -#define DAWIDTH (640) +#define DAWIDTH (640) #define DAHEIGHT (200) /** @@ -53,101 +53,100 @@ given 'index' position. */ typedef struct { - float data_min[DAWIDTH]; - float data_max[DAWIDTH]; + float data_min[DAWIDTH]; + float data_max[DAWIDTH]; - uint32_t idx; - uint32_t sub; + uint32_t idx; + uint32_t sub; } ScoChan; typedef struct { - LV2_Atom_Forge forge; - LV2_URID_Map* map; - ScoLV2URIs uris; - - LV2UI_Write_Function write; - LV2UI_Controller controller; - - GtkWidget* hbox; - GtkWidget* vbox; - GtkWidget* sep[2]; - GtkWidget* darea; - GtkWidget* btn_pause; - GtkWidget* lbl_speed; - GtkWidget* lbl_amp; - GtkWidget* spb_speed; - GtkWidget* spb_amp; - GtkAdjustment* spb_speed_adj; - GtkAdjustment* spb_amp_adj; - - ScoChan chn[2]; - uint32_t stride; - uint32_t n_channels; - bool paused; - float rate; - bool updating; + LV2_Atom_Forge forge; + LV2_URID_Map* map; + ScoLV2URIs uris; + + LV2UI_Write_Function write; + LV2UI_Controller controller; + + GtkWidget* hbox; + GtkWidget* vbox; + GtkWidget* sep[2]; + GtkWidget* darea; + GtkWidget* btn_pause; + GtkWidget* lbl_speed; + GtkWidget* lbl_amp; + GtkWidget* spb_speed; + GtkWidget* spb_amp; + GtkAdjustment* spb_speed_adj; + GtkAdjustment* spb_amp_adj; + + ScoChan chn[2]; + uint32_t stride; + uint32_t n_channels; + bool paused; + float rate; + bool updating; } EgScopeUI; - /** Send current UI settings to backend. */ static void send_ui_state(LV2UI_Handle handle) { - EgScopeUI* ui = (EgScopeUI*)handle; - const float gain = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_amp)); + EgScopeUI* ui = (EgScopeUI*)handle; + const float gain = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_amp)); - // Use local buffer on the stack to build atom - uint8_t obj_buf[1024]; - lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); + // Use local buffer on the stack to build atom + uint8_t obj_buf[1024]; + lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); - // Start a ui:State object - LV2_Atom_Forge_Frame frame; - LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object( - &ui->forge, &frame, 0, ui->uris.ui_State); + // Start a ui:State object + LV2_Atom_Forge_Frame frame; + LV2_Atom* msg = + (LV2_Atom*)lv2_atom_forge_object(&ui->forge, &frame, 0, ui->uris.ui_State); - assert(msg); + assert(msg); - // msg[samples-per-pixel] = integer - lv2_atom_forge_key(&ui->forge, ui->uris.ui_spp); - lv2_atom_forge_int(&ui->forge, ui->stride); + // msg[samples-per-pixel] = integer + lv2_atom_forge_key(&ui->forge, ui->uris.ui_spp); + lv2_atom_forge_int(&ui->forge, ui->stride); - // msg[amplitude] = float - lv2_atom_forge_key(&ui->forge, ui->uris.ui_amp); - lv2_atom_forge_float(&ui->forge, gain); + // msg[amplitude] = float + lv2_atom_forge_key(&ui->forge, ui->uris.ui_amp); + lv2_atom_forge_float(&ui->forge, gain); - // Finish ui:State object - lv2_atom_forge_pop(&ui->forge, &frame); + // Finish ui:State object + lv2_atom_forge_pop(&ui->forge, &frame); - // Send message to plugin port '0' - ui->write(ui->controller, - 0, - lv2_atom_total_size(msg), - ui->uris.atom_eventTransfer, - msg); + // Send message to plugin port '0' + ui->write(ui->controller, + 0, + lv2_atom_total_size(msg), + ui->uris.atom_eventTransfer, + msg); } /** Notify backend that UI is closed. */ static void send_ui_disable(LV2UI_Handle handle) { - EgScopeUI* ui = (EgScopeUI*)handle; - send_ui_state(handle); + EgScopeUI* ui = (EgScopeUI*)handle; + send_ui_state(handle); - uint8_t obj_buf[64]; - lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); + uint8_t obj_buf[64]; + lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); - LV2_Atom_Forge_Frame frame; - LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object( - &ui->forge, &frame, 0, ui->uris.ui_Off); + LV2_Atom_Forge_Frame frame; + LV2_Atom* msg = + (LV2_Atom*)lv2_atom_forge_object(&ui->forge, &frame, 0, ui->uris.ui_Off); - assert(msg); + assert(msg); - lv2_atom_forge_pop(&ui->forge, &frame); - ui->write(ui->controller, - 0, - lv2_atom_total_size(msg), - ui->uris.atom_eventTransfer, - msg); + lv2_atom_forge_pop(&ui->forge, &frame); + ui->write(ui->controller, + 0, + lv2_atom_total_size(msg), + ui->uris.atom_eventTransfer, + msg); } /** @@ -158,35 +157,35 @@ send_ui_disable(LV2UI_Handle handle) static void send_ui_enable(LV2UI_Handle handle) { - EgScopeUI* ui = (EgScopeUI*)handle; + EgScopeUI* ui = (EgScopeUI*)handle; - uint8_t obj_buf[64]; - lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); + uint8_t obj_buf[64]; + lv2_atom_forge_set_buffer(&ui->forge, obj_buf, sizeof(obj_buf)); - LV2_Atom_Forge_Frame frame; - LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object( - &ui->forge, &frame, 0, ui->uris.ui_On); + LV2_Atom_Forge_Frame frame; + LV2_Atom* msg = + (LV2_Atom*)lv2_atom_forge_object(&ui->forge, &frame, 0, ui->uris.ui_On); - assert(msg); + assert(msg); - lv2_atom_forge_pop(&ui->forge, &frame); - ui->write(ui->controller, - 0, - lv2_atom_total_size(msg), - ui->uris.atom_eventTransfer, - msg); + lv2_atom_forge_pop(&ui->forge, &frame); + ui->write(ui->controller, + 0, + lv2_atom_total_size(msg), + ui->uris.atom_eventTransfer, + msg); } /** Gtk widget callback. */ static gboolean on_cfg_changed(GtkWidget* widget, gpointer data) { - EgScopeUI* ui = (EgScopeUI*)data; - if (!ui->updating) { - // Only send UI state if the change is from user interaction - send_ui_state(data); - } - return TRUE; + EgScopeUI* ui = (EgScopeUI*)data; + if (!ui->updating) { + // Only send UI state if the change is from user interaction + send_ui_state(data); + } + return TRUE; } /** @@ -197,127 +196,127 @@ on_cfg_changed(GtkWidget* widget, gpointer data) static gboolean on_expose_event(GtkWidget* widget, GdkEventExpose* ev, gpointer data) { - EgScopeUI* ui = (EgScopeUI*)data; - const float gain = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_amp)); - - // Get cairo type for the gtk window - cairo_t* cr = gdk_cairo_create(ui->darea->window); - - // Limit cairo-drawing to exposed area - cairo_rectangle(cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height); - cairo_clip(cr); - - // Clear background - cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); - cairo_rectangle(cr, 0, 0, DAWIDTH, DAHEIGHT * ui->n_channels); - cairo_fill(cr); - - cairo_set_line_width(cr, 1.0); - - const uint32_t start = ev->area.x; - const uint32_t end = ev->area.x + ev->area.width; - - assert(start < DAWIDTH); - assert(end <= DAWIDTH); - assert(start < end); - - for (uint32_t c = 0; c < ui->n_channels; ++c) { - ScoChan* chn = &ui->chn[c]; - - /* Drawing area Y-position of given sample-value. - * Note: cairo-pixel at 0 spans -0.5 .. +0.5, hence (DAHEIGHT / 2.0 -0.5) - * also the cairo Y-axis points upwards (hence 'minus value') - * - * == ( DAHEIGHT * (CHN) // channel offset - * + (DAHEIGHT / 2) - 0.5 // vertical center -- '0' - * - (DAHEIGHT / 2) * (VAL) * (GAIN) - * ) - */ - const float chn_y_offset = DAHEIGHT * c + DAHEIGHT * 0.5f - 0.5f; - const float chn_y_scale = DAHEIGHT * 0.5f * gain; - -#define CYPOS(VAL) (chn_y_offset - (VAL) * chn_y_scale) - - cairo_save(cr); - - /* Restrict drawing to current channel area, don't bleed drawing into - neighboring channels. */ - cairo_rectangle(cr, 0, DAHEIGHT * c, DAWIDTH, DAHEIGHT); - cairo_clip(cr); - - // Set color of wave-form - cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); - - /* This is a somewhat 'smart' mechanism to plot audio data using - alternating up/down line-directions. It works well for both cases: - 1 pixel <= 1 sample and 1 pixel represents more than 1 sample, but - is not ideal for either. */ - if (start == chn->idx) { - cairo_move_to(cr, start - 0.5, CYPOS(0)); - } else { - cairo_move_to(cr, start - 0.5, CYPOS(chn->data_max[start])); - } - - uint32_t pathlength = 0; - for (uint32_t i = start; i < end; ++i) { - if (i == chn->idx) { - continue; - } else if (i % 2) { - cairo_line_to(cr, i - .5, CYPOS(chn->data_min[i])); - cairo_line_to(cr, i - .5, CYPOS(chn->data_max[i])); - ++pathlength; - } else { - cairo_line_to(cr, i - .5, CYPOS(chn->data_max[i])); - cairo_line_to(cr, i - .5, CYPOS(chn->data_min[i])); - ++pathlength; - } - - /** Limit the max cairo path length. This is an optimization trade - off: too short path: high load CPU/GPU load. too-long path: - bad anti-aliasing, or possibly lost points */ - if (pathlength > MAX_CAIRO_PATH) { - pathlength = 0; - cairo_stroke(cr); - if (i % 2) { - cairo_move_to(cr, i - .5, CYPOS(chn->data_max[i])); - } else { - cairo_move_to(cr, i - .5, CYPOS(chn->data_min[i])); - } - } - } - - if (pathlength > 0) { - cairo_stroke(cr); - } - - // Draw current position vertical line if display is slow - if (ui->stride >= ui->rate / 4800.0f || ui->paused) { - cairo_set_source_rgba(cr, .9, .2, .2, .6); - cairo_move_to(cr, chn->idx - .5, DAHEIGHT * c); - cairo_line_to(cr, chn->idx - .5, DAHEIGHT * (c + 1)); - cairo_stroke(cr); - } - - // Undo the 'clipping' restriction - cairo_restore(cr); - - // Channel separator - if (c > 0) { - cairo_set_source_rgba(cr, .5, .5, .5, 1.0); - cairo_move_to(cr, 0, DAHEIGHT * c - .5); - cairo_line_to(cr, DAWIDTH, DAHEIGHT * c - .5); - cairo_stroke(cr); - } - - // Zero scale line - cairo_set_source_rgba(cr, .3, .3, .7, .5); - cairo_move_to(cr, 0, DAHEIGHT * (c + .5) - .5); - cairo_line_to(cr, DAWIDTH, DAHEIGHT * (c + .5) - .5); - cairo_stroke(cr); - } - - cairo_destroy(cr); - return TRUE; + EgScopeUI* ui = (EgScopeUI*)data; + const float gain = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_amp)); + + // Get cairo type for the gtk window + cairo_t* cr = gdk_cairo_create(ui->darea->window); + + // Limit cairo-drawing to exposed area + cairo_rectangle(cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height); + cairo_clip(cr); + + // Clear background + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_rectangle(cr, 0, 0, DAWIDTH, DAHEIGHT * ui->n_channels); + cairo_fill(cr); + + cairo_set_line_width(cr, 1.0); + + const uint32_t start = ev->area.x; + const uint32_t end = ev->area.x + ev->area.width; + + assert(start < DAWIDTH); + assert(end <= DAWIDTH); + assert(start < end); + + for (uint32_t c = 0; c < ui->n_channels; ++c) { + ScoChan* chn = &ui->chn[c]; + + /* Drawing area Y-position of given sample-value. + * Note: cairo-pixel at 0 spans -0.5 .. +0.5, hence (DAHEIGHT / 2.0 -0.5) + * also the cairo Y-axis points upwards (hence 'minus value') + * + * == ( DAHEIGHT * (CHN) // channel offset + * + (DAHEIGHT / 2) - 0.5 // vertical center -- '0' + * - (DAHEIGHT / 2) * (VAL) * (GAIN) + * ) + */ + const float chn_y_offset = DAHEIGHT * c + DAHEIGHT * 0.5f - 0.5f; + const float chn_y_scale = DAHEIGHT * 0.5f * gain; + +#define CYPOS(VAL) (chn_y_offset - (VAL)*chn_y_scale) + + cairo_save(cr); + + /* Restrict drawing to current channel area, don't bleed drawing into + neighboring channels. */ + cairo_rectangle(cr, 0, DAHEIGHT * c, DAWIDTH, DAHEIGHT); + cairo_clip(cr); + + // Set color of wave-form + cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); + + /* This is a somewhat 'smart' mechanism to plot audio data using + alternating up/down line-directions. It works well for both cases: + 1 pixel <= 1 sample and 1 pixel represents more than 1 sample, but + is not ideal for either. */ + if (start == chn->idx) { + cairo_move_to(cr, start - 0.5, CYPOS(0)); + } else { + cairo_move_to(cr, start - 0.5, CYPOS(chn->data_max[start])); + } + + uint32_t pathlength = 0; + for (uint32_t i = start; i < end; ++i) { + if (i == chn->idx) { + continue; + } else if (i % 2) { + cairo_line_to(cr, i - .5, CYPOS(chn->data_min[i])); + cairo_line_to(cr, i - .5, CYPOS(chn->data_max[i])); + ++pathlength; + } else { + cairo_line_to(cr, i - .5, CYPOS(chn->data_max[i])); + cairo_line_to(cr, i - .5, CYPOS(chn->data_min[i])); + ++pathlength; + } + + /** Limit the max cairo path length. This is an optimization trade + off: too short path: high load CPU/GPU load. too-long path: + bad anti-aliasing, or possibly lost points */ + if (pathlength > MAX_CAIRO_PATH) { + pathlength = 0; + cairo_stroke(cr); + if (i % 2) { + cairo_move_to(cr, i - .5, CYPOS(chn->data_max[i])); + } else { + cairo_move_to(cr, i - .5, CYPOS(chn->data_min[i])); + } + } + } + + if (pathlength > 0) { + cairo_stroke(cr); + } + + // Draw current position vertical line if display is slow + if (ui->stride >= ui->rate / 4800.0f || ui->paused) { + cairo_set_source_rgba(cr, .9, .2, .2, .6); + cairo_move_to(cr, chn->idx - .5, DAHEIGHT * c); + cairo_line_to(cr, chn->idx - .5, DAHEIGHT * (c + 1)); + cairo_stroke(cr); + } + + // Undo the 'clipping' restriction + cairo_restore(cr); + + // Channel separator + if (c > 0) { + cairo_set_source_rgba(cr, .5, .5, .5, 1.0); + cairo_move_to(cr, 0, DAHEIGHT * c - .5); + cairo_line_to(cr, DAWIDTH, DAHEIGHT * c - .5); + cairo_stroke(cr); + } + + // Zero scale line + cairo_set_source_rgba(cr, .3, .3, .7, .5); + cairo_move_to(cr, 0, DAHEIGHT * (c + .5) - .5); + cairo_line_to(cr, DAWIDTH, DAHEIGHT * (c + .5) - .5); + cairo_stroke(cr); + } + + cairo_destroy(cr); + return TRUE; } /** @@ -346,27 +345,27 @@ process_channel(EgScopeUI* ui, uint32_t* idx_start, uint32_t* idx_end) { - int overflow = 0; - *idx_start = chn->idx; - for (size_t i = 0; i < n_elem; ++i) { - if (data[i] < chn->data_min[chn->idx]) { - chn->data_min[chn->idx] = data[i]; - } - if (data[i] > chn->data_max[chn->idx]) { - chn->data_max[chn->idx] = data[i]; - } - if (++chn->sub >= ui->stride) { - chn->sub = 0; - chn->idx = (chn->idx + 1) % DAWIDTH; - if (chn->idx == 0) { - ++overflow; - } - chn->data_min[chn->idx] = 1.0f; - chn->data_max[chn->idx] = -1.0f; - } - } - *idx_end = chn->idx; - return overflow; + int overflow = 0; + *idx_start = chn->idx; + for (size_t i = 0; i < n_elem; ++i) { + if (data[i] < chn->data_min[chn->idx]) { + chn->data_min[chn->idx] = data[i]; + } + if (data[i] > chn->data_max[chn->idx]) { + chn->data_max[chn->idx] = data[i]; + } + if (++chn->sub >= ui->stride) { + chn->sub = 0; + chn->idx = (chn->idx + 1) % DAWIDTH; + if (chn->idx == 0) { + ++overflow; + } + chn->data_min[chn->idx] = 1.0f; + chn->data_max[chn->idx] = -1.0f; + } + } + *idx_end = chn->idx; + return overflow; } /** @@ -379,56 +378,57 @@ update_scope(EgScopeUI* ui, const size_t n_elem, float const* data) { - // Never trust input data which could lead to application failure. - if (channel < 0 || (uint32_t)channel > ui->n_channels) { - return; - } - - // Update state in sync with 1st channel - if (channel == 0) { - ui->stride = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_speed)); - const bool paused = gtk_toggle_button_get_active( - GTK_TOGGLE_BUTTON(ui->btn_pause)); - - if (paused != ui->paused) { - ui->paused = paused; - gtk_widget_queue_draw(ui->darea); - } - } - if (ui->paused) { - return; - } - - uint32_t idx_start = 0; // Display pixel start - uint32_t idx_end = 0; // Display pixel end - int overflow = 0; // Received more audio-data than display-pixel - - // Process this channel's audio-data for display - ScoChan* chn = &ui->chn[channel]; - overflow = process_channel(ui, chn, n_elem, data, &idx_start, &idx_end); - - // Signal gtk's main thread to redraw the widget after the last channel - if ((uint32_t)channel + 1 == ui->n_channels) { - if (overflow > 1) { - // Redraw complete widget - gtk_widget_queue_draw(ui->darea); - } else if (idx_end > idx_start) { - // Redraw area between start -> end pixel - gtk_widget_queue_draw_area(ui->darea, idx_start - 2, 0, 3 - + idx_end - idx_start, - DAHEIGHT * ui->n_channels); - } else if (idx_end < idx_start) { - // Wrap-around: redraw area between 0->start AND end->right-end - gtk_widget_queue_draw_area( - ui->darea, - idx_start - 2, 0, - 3 + DAWIDTH - idx_start, DAHEIGHT * ui->n_channels); - gtk_widget_queue_draw_area( - ui->darea, - 0, 0, - idx_end + 1, DAHEIGHT * ui->n_channels); - } - } + // Never trust input data which could lead to application failure. + if (channel < 0 || (uint32_t)channel > ui->n_channels) { + return; + } + + // Update state in sync with 1st channel + if (channel == 0) { + ui->stride = gtk_spin_button_get_value(GTK_SPIN_BUTTON(ui->spb_speed)); + const bool paused = + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ui->btn_pause)); + + if (paused != ui->paused) { + ui->paused = paused; + gtk_widget_queue_draw(ui->darea); + } + } + if (ui->paused) { + return; + } + + uint32_t idx_start = 0; // Display pixel start + uint32_t idx_end = 0; // Display pixel end + int overflow = 0; // Received more audio-data than display-pixel + + // Process this channel's audio-data for display + ScoChan* chn = &ui->chn[channel]; + overflow = process_channel(ui, chn, n_elem, data, &idx_start, &idx_end); + + // Signal gtk's main thread to redraw the widget after the last channel + if ((uint32_t)channel + 1 == ui->n_channels) { + if (overflow > 1) { + // Redraw complete widget + gtk_widget_queue_draw(ui->darea); + } else if (idx_end > idx_start) { + // Redraw area between start -> end pixel + gtk_widget_queue_draw_area(ui->darea, + idx_start - 2, + 0, + 3 + idx_end - idx_start, + DAHEIGHT * ui->n_channels); + } else if (idx_end < idx_start) { + // Wrap-around: redraw area between 0->start AND end->right-end + gtk_widget_queue_draw_area(ui->darea, + idx_start - 2, + 0, + 3 + DAWIDTH - idx_start, + DAHEIGHT * ui->n_channels); + gtk_widget_queue_draw_area( + ui->darea, 0, 0, idx_end + 1, DAHEIGHT * ui->n_channels); + } + } } static LV2UI_Handle @@ -440,199 +440,191 @@ instantiate(const LV2UI_Descriptor* descriptor, LV2UI_Widget* widget, const LV2_Feature* const* features) { - EgScopeUI* ui = (EgScopeUI*)calloc(1, sizeof(EgScopeUI)); - - if (!ui) { - fprintf(stderr, "EgScope.lv2 UI: out of memory\n"); - return NULL; - } - - ui->map = NULL; - *widget = NULL; - - if (!strcmp(plugin_uri, SCO_URI "#Mono")) { - ui->n_channels = 1; - } else if (!strcmp(plugin_uri, SCO_URI "#Stereo")) { - ui->n_channels = 2; - } else { - free(ui); - return NULL; - } - - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { - ui->map = (LV2_URID_Map*)features[i]->data; - } - } - - if (!ui->map) { - fprintf(stderr, "EgScope.lv2 UI: Host does not support urid:map\n"); - free(ui); - return NULL; - } - - // Initialize private data structure - ui->write = write_function; - ui->controller = controller; - - ui->vbox = NULL; - ui->hbox = NULL; - ui->darea = NULL; - ui->stride = 25; - ui->paused = false; - ui->rate = 48000; - - ui->chn[0].idx = 0; - ui->chn[0].sub = 0; - ui->chn[1].idx = 0; - ui->chn[1].sub = 0; - memset(ui->chn[0].data_min, 0, sizeof(float) * DAWIDTH); - memset(ui->chn[0].data_max, 0, sizeof(float) * DAWIDTH); - memset(ui->chn[1].data_min, 0, sizeof(float) * DAWIDTH); - memset(ui->chn[1].data_max, 0, sizeof(float) * DAWIDTH); - - map_sco_uris(ui->map, &ui->uris); - lv2_atom_forge_init(&ui->forge, ui->map); - - // Setup UI - ui->hbox = gtk_hbox_new(FALSE, 0); - ui->vbox = gtk_vbox_new(FALSE, 0); - - ui->darea = gtk_drawing_area_new(); - gtk_widget_set_size_request(ui->darea, DAWIDTH, DAHEIGHT * ui->n_channels); - - ui->lbl_speed = gtk_label_new("Samples/Pixel"); - ui->lbl_amp = gtk_label_new("Amplitude"); - - ui->sep[0] = gtk_hseparator_new(); - ui->sep[1] = gtk_label_new(""); - ui->btn_pause = gtk_toggle_button_new_with_label("Pause"); - - ui->spb_speed_adj = (GtkAdjustment*)gtk_adjustment_new( - 25.0, 1.0, 1000.0, 1.0, 5.0, 0.0); - ui->spb_speed = gtk_spin_button_new(ui->spb_speed_adj, 1.0, 0); - - ui->spb_amp_adj = (GtkAdjustment*)gtk_adjustment_new( - 1.0, 0.1, 6.0, 0.1, 1.0, 0.0); - ui->spb_amp = gtk_spin_button_new(ui->spb_amp_adj, 0.1, 1); - - gtk_box_pack_start(GTK_BOX(ui->hbox), ui->darea, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(ui->hbox), ui->vbox, FALSE, FALSE, 4); - - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->lbl_speed, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->spb_speed, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->sep[0], FALSE, FALSE, 8); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->lbl_amp, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->spb_amp, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->sep[1], TRUE, FALSE, 8); - gtk_box_pack_start(GTK_BOX(ui->vbox), ui->btn_pause, FALSE, FALSE, 2); - - g_signal_connect(G_OBJECT(ui->darea), "expose_event", - G_CALLBACK(on_expose_event), ui); - g_signal_connect(G_OBJECT(ui->spb_amp), "value-changed", - G_CALLBACK(on_cfg_changed), ui); - g_signal_connect(G_OBJECT(ui->spb_speed), "value-changed", - G_CALLBACK(on_cfg_changed), ui); - - *widget = ui->hbox; - - /* Send UIOn message to plugin, which will request state and enable message - transmission. */ - send_ui_enable(ui); - - return ui; + EgScopeUI* ui = (EgScopeUI*)calloc(1, sizeof(EgScopeUI)); + + if (!ui) { + fprintf(stderr, "EgScope.lv2 UI: out of memory\n"); + return NULL; + } + + ui->map = NULL; + *widget = NULL; + + if (!strcmp(plugin_uri, SCO_URI "#Mono")) { + ui->n_channels = 1; + } else if (!strcmp(plugin_uri, SCO_URI "#Stereo")) { + ui->n_channels = 2; + } else { + free(ui); + return NULL; + } + + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { + ui->map = (LV2_URID_Map*)features[i]->data; + } + } + + if (!ui->map) { + fprintf(stderr, "EgScope.lv2 UI: Host does not support urid:map\n"); + free(ui); + return NULL; + } + + // Initialize private data structure + ui->write = write_function; + ui->controller = controller; + + ui->vbox = NULL; + ui->hbox = NULL; + ui->darea = NULL; + ui->stride = 25; + ui->paused = false; + ui->rate = 48000; + + ui->chn[0].idx = 0; + ui->chn[0].sub = 0; + ui->chn[1].idx = 0; + ui->chn[1].sub = 0; + memset(ui->chn[0].data_min, 0, sizeof(float) * DAWIDTH); + memset(ui->chn[0].data_max, 0, sizeof(float) * DAWIDTH); + memset(ui->chn[1].data_min, 0, sizeof(float) * DAWIDTH); + memset(ui->chn[1].data_max, 0, sizeof(float) * DAWIDTH); + + map_sco_uris(ui->map, &ui->uris); + lv2_atom_forge_init(&ui->forge, ui->map); + + // Setup UI + ui->hbox = gtk_hbox_new(FALSE, 0); + ui->vbox = gtk_vbox_new(FALSE, 0); + + ui->darea = gtk_drawing_area_new(); + gtk_widget_set_size_request(ui->darea, DAWIDTH, DAHEIGHT * ui->n_channels); + + ui->lbl_speed = gtk_label_new("Samples/Pixel"); + ui->lbl_amp = gtk_label_new("Amplitude"); + + ui->sep[0] = gtk_hseparator_new(); + ui->sep[1] = gtk_label_new(""); + ui->btn_pause = gtk_toggle_button_new_with_label("Pause"); + + ui->spb_speed_adj = + (GtkAdjustment*)gtk_adjustment_new(25.0, 1.0, 1000.0, 1.0, 5.0, 0.0); + ui->spb_speed = gtk_spin_button_new(ui->spb_speed_adj, 1.0, 0); + + ui->spb_amp_adj = + (GtkAdjustment*)gtk_adjustment_new(1.0, 0.1, 6.0, 0.1, 1.0, 0.0); + ui->spb_amp = gtk_spin_button_new(ui->spb_amp_adj, 0.1, 1); + + gtk_box_pack_start(GTK_BOX(ui->hbox), ui->darea, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(ui->hbox), ui->vbox, FALSE, FALSE, 4); + + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->lbl_speed, FALSE, FALSE, 2); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->spb_speed, FALSE, FALSE, 2); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->sep[0], FALSE, FALSE, 8); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->lbl_amp, FALSE, FALSE, 2); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->spb_amp, FALSE, FALSE, 2); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->sep[1], TRUE, FALSE, 8); + gtk_box_pack_start(GTK_BOX(ui->vbox), ui->btn_pause, FALSE, FALSE, 2); + + g_signal_connect( + G_OBJECT(ui->darea), "expose_event", G_CALLBACK(on_expose_event), ui); + g_signal_connect( + G_OBJECT(ui->spb_amp), "value-changed", G_CALLBACK(on_cfg_changed), ui); + g_signal_connect( + G_OBJECT(ui->spb_speed), "value-changed", G_CALLBACK(on_cfg_changed), ui); + + *widget = ui->hbox; + + /* Send UIOn message to plugin, which will request state and enable message + transmission. */ + send_ui_enable(ui); + + return ui; } static void cleanup(LV2UI_Handle handle) { - EgScopeUI* ui = (EgScopeUI*)handle; - /* Send UIOff message to plugin, which will save state and disable message - * transmission. */ - send_ui_disable(ui); - gtk_widget_destroy(ui->darea); - free(ui); + EgScopeUI* ui = (EgScopeUI*)handle; + /* Send UIOff message to plugin, which will save state and disable message + * transmission. */ + send_ui_disable(ui); + gtk_widget_destroy(ui->darea); + free(ui); } static int recv_raw_audio(EgScopeUI* ui, const LV2_Atom_Object* obj) { - const LV2_Atom* chan_val = NULL; - const LV2_Atom* data_val = NULL; - - // clang-format off - const int n_props = lv2_atom_object_get( - obj, - ui->uris.channelID, &chan_val, - ui->uris.audioData, &data_val, - NULL); - // clang-format on - - if (n_props != 2 || - chan_val->type != ui->uris.atom_Int || - data_val->type != ui->uris.atom_Vector) { - // Object does not have the required properties with correct types - fprintf(stderr, "eg-scope.lv2 UI error: Corrupt audio message\n"); - return 1; - } - - // Get the values we need from the body of the property value atoms - const int32_t chn = ((const LV2_Atom_Int*)chan_val)->body; - const LV2_Atom_Vector* vec = (const LV2_Atom_Vector*)data_val; - if (vec->body.child_type != ui->uris.atom_Float) { - return 1; // Vector has incorrect element type - } - - // Number of elements = (total size - header size) / element size - const size_t n_elem = ((data_val->size - sizeof(LV2_Atom_Vector_Body)) - / sizeof(float)); - - // Float elements immediately follow the vector body header - const float* data = (const float*)(&vec->body + 1); - - // Update display - update_scope(ui, chn, n_elem, data); - return 0; + const LV2_Atom* chan_val = NULL; + const LV2_Atom* data_val = NULL; + const int n_props = lv2_atom_object_get( + obj, ui->uris.channelID, &chan_val, ui->uris.audioData, &data_val, NULL); + + if (n_props != 2 || chan_val->type != ui->uris.atom_Int || + data_val->type != ui->uris.atom_Vector) { + // Object does not have the required properties with correct types + fprintf(stderr, "eg-scope.lv2 UI error: Corrupt audio message\n"); + return 1; + } + + // Get the values we need from the body of the property value atoms + const int32_t chn = ((const LV2_Atom_Int*)chan_val)->body; + const LV2_Atom_Vector* vec = (const LV2_Atom_Vector*)data_val; + if (vec->body.child_type != ui->uris.atom_Float) { + return 1; // Vector has incorrect element type + } + + // Number of elements = (total size - header size) / element size + const size_t n_elem = + ((data_val->size - sizeof(LV2_Atom_Vector_Body)) / sizeof(float)); + + // Float elements immediately follow the vector body header + const float* data = (const float*)(&vec->body + 1); + + // Update display + update_scope(ui, chn, n_elem, data); + return 0; } static int recv_ui_state(EgScopeUI* ui, const LV2_Atom_Object* obj) { - const LV2_Atom* spp_val = NULL; - const LV2_Atom* amp_val = NULL; - const LV2_Atom* rate_val = NULL; - - // clang-format off - const int n_props = lv2_atom_object_get( - obj, - ui->uris.ui_spp, &spp_val, - ui->uris.ui_amp, &_val, - ui->uris.param_sampleRate, &rate_val, - NULL); - // clang-format on - - if (n_props != 3 || - spp_val->type != ui->uris.atom_Int || - amp_val->type != ui->uris.atom_Float || - rate_val->type != ui->uris.atom_Float) { - // Object does not have the required properties with correct types - fprintf(stderr, "eg-scope.lv2 UI error: Corrupt state message\n"); - return 1; - } - - // Get the values we need from the body of the property value atoms - const int32_t spp = ((const LV2_Atom_Int*)spp_val)->body; - const float amp = ((const LV2_Atom_Float*)amp_val)->body; - const float rate = ((const LV2_Atom_Float*)rate_val)->body; - - // Disable transmission and update UI - ui->updating = true; - gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_speed), spp); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_amp), amp); - ui->updating = false; - ui->rate = rate; - - return 0; + const LV2_Atom* spp_val = NULL; + const LV2_Atom* amp_val = NULL; + const LV2_Atom* rate_val = NULL; + + const int n_props = lv2_atom_object_get(obj, + ui->uris.ui_spp, + &spp_val, + ui->uris.ui_amp, + &_val, + ui->uris.param_sampleRate, + &rate_val, + NULL); + + if (n_props != 3 || spp_val->type != ui->uris.atom_Int || + amp_val->type != ui->uris.atom_Float || + rate_val->type != ui->uris.atom_Float) { + // Object does not have the required properties with correct types + fprintf(stderr, "eg-scope.lv2 UI error: Corrupt state message\n"); + return 1; + } + + // Get the values we need from the body of the property value atoms + const int32_t spp = ((const LV2_Atom_Int*)spp_val)->body; + const float amp = ((const LV2_Atom_Float*)amp_val)->body; + const float rate = ((const LV2_Atom_Float*)rate_val)->body; + + // Disable transmission and update UI + ui->updating = true; + gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_speed), spp); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(ui->spb_amp), amp); + ui->updating = false; + ui->rate = rate; + + return 0; } /** @@ -650,35 +642,33 @@ port_event(LV2UI_Handle handle, uint32_t format, const void* buffer) { - EgScopeUI* ui = (EgScopeUI*)handle; - const LV2_Atom* atom = (const LV2_Atom*)buffer; - - /* Check type of data received - * - format == 0: Control port event (float) - * - format > 0: Message (atom) - */ - if (format == ui->uris.atom_eventTransfer && - lv2_atom_forge_is_object_type(&ui->forge, atom->type)) { - const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom; - if (obj->body.otype == ui->uris.RawAudio) { - recv_raw_audio(ui, obj); - } else if (obj->body.otype == ui->uris.ui_State) { - recv_ui_state(ui, obj); - } - } + EgScopeUI* ui = (EgScopeUI*)handle; + const LV2_Atom* atom = (const LV2_Atom*)buffer; + + /* Check type of data received + * - format == 0: Control port event (float) + * - format > 0: Message (atom) + */ + if (format == ui->uris.atom_eventTransfer && + lv2_atom_forge_is_object_type(&ui->forge, atom->type)) { + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom; + if (obj->body.otype == ui->uris.RawAudio) { + recv_raw_audio(ui, obj); + } else if (obj->body.otype == ui->uris.ui_State) { + recv_ui_state(ui, obj); + } + } } -static const LV2UI_Descriptor descriptor = { - SCO_URI "#ui", - instantiate, - cleanup, - port_event, - NULL -}; +static const LV2UI_Descriptor descriptor = {SCO_URI "#ui", + instantiate, + cleanup, + port_event, + NULL}; LV2_SYMBOL_EXPORT const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) { - return index == 0 ? &descriptor : NULL; + return index == 0 ? &descriptor : NULL; } |