[LV2] [PATCH 1/2] eg05-scope: Atom buffer capacity check & documentation
Robin Gareus
robin at gareus.org
Thu Dec 26 04:30:34 PST 2013
* explain 'magic' buffer-size numbers
* print warning (on GUI) if the buffer is to small
* log warning only once
* plugin: process (forward) audio even if buffers are insufficient
(don't return early from run() if buffersize is insufficient)
@drobilla: please x-check, the numbers are correct, the explanation
(types, casts and English language) may not be.
Signed-off-by: Robin Gareus <robin at gareus.org>
---
plugins/eg05-scope.lv2/examploscope.c | 104 +++++++++++++++++++++++++---
plugins/eg05-scope.lv2/examploscope.ttl.in | 4 +-
plugins/eg05-scope.lv2/examploscope_ui.c | 30 +++++++-
3 files changed, 127 insertions(+), 11 deletions(-)
diff --git a/plugins/eg05-scope.lv2/examploscope.c b/plugins/eg05-scope.lv2/examploscope.c
index ff64866..50435a1 100644
--- a/plugins/eg05-scope.lv2/examploscope.c
+++ b/plugins/eg05-scope.lv2/examploscope.c
@@ -55,6 +55,9 @@ typedef struct {
bool send_settings_to_ui;
float ui_amp;
uint32_t ui_spp;
+
+ // notify user only once, avoid repetitive messages
+ bool printed_capacity_warning;
} EgScope;
/** Port indices. */
@@ -108,6 +111,8 @@ instantiate(const LV2_Descriptor* descriptor,
return NULL;
}
+ self->printed_capacity_warning = false;
+
// Initialise local variables
self->ui_active = false;
self->send_settings_to_ui = false;
@@ -195,22 +200,105 @@ static void
run(LV2_Handle handle, uint32_t n_samples)
{
EgScope* self = (EgScope*)handle;
+ bool capacity_ok = true;
/* Ensure notify port buffer is large enough to hold all audio-samples and
- configuration settings. A minimum size was requested in the .ttl file,
- but check here just to be sure.
+ configuration settings. A minimum size was requested in the .ttl file,
+ but check here just to be sure that the data can be delivered to the GUI
+ (and display a warning if it cannot). In case the buffer is too small,
+ the forge itself will prevent buffer overflows, but the GUI will not
+ receive incomplete data and the plugin may not function properly.
+
+
+ After initialization with lv2_atom_forge_sequence_head() one can verify
+ the current buffer usage by looking at self->notify->atom.size after every
+ successfully forged message.
+
+ The better approach however is to establish the required size explicitly.
+ In this case the upper limit of data written to the Atom port is:
+
+ atom-sequence-header
+ + Atom-sequence-time-stamp
+ + UI-state-settings
+ + raw-audio-array data * number-of-channels
+
+ The sequence header is 8 bytes.
+
+ sizeof(LV2_Atom_Sequence) - sizeof(LV2_Atom_Sequence_Body) = sizeof (LV2_Atom)
+
+ The LV2_Atom specifies the type (uint32_t) and the unit of the timestamps (uint32_t)
+ (both are mapped URI identifies).
+
+ All data in the sequence is sent at the time time (sample 0).
+ This would be different for e.g. MIDI-data, but in this case for transferring
+ data to to the GUI, sub-cycles sample-accurate timing is not required. A single
+ time-stamp is used for all data in the sequence. LV2 Timestamps are 8 bytes:
+
+ sizeof(uint64_t) [Atom-seq. frame-time 8 ]
+
+ The UI state settings (if present) is 96 bytes. It is an Atom Object with three
+ key/value pairs. The actual data is 92 bytes, but since all LV2 data needs to be
+ 64bit aligned there are 4 bytes padding at the end.
+
+ Note, the LV2_Atom_Property includes the LV2_Atom_Property_Body which in turn
+ includes the LV2_Atom header to describes the payload data.
+ see also lv2plug.in/ns/ext/atom/atom.h
+
+ Hence a property + value pair =
+ sizeof(LV2_Atom_Property) + sizeof(LV2_Atom_Int) - sizeof(LV2_Atom)
+ = sizeof(LV2_Atom_Property) + sizeof (uint32_t)
+
+ The UI-state settings byte-size is calculated as follows:
+
+ LV2_Atom_Object (LV2+Atom only || Atom-seq. frame time ??) [ 8 ]
+ + sizeof(LV2_Atom_Property) [ 24 ] + sizeof(int32_t) [ 4 ]
+ + sizeof(LV2_Atom_Property) [ 24 ] + sizeof(float) [ 4 ]
+ + sizeof(LV2_Atom_Property) [ 24 ] + sizeof(float) [ 4 ]
+ + padding [ 4 ]
+ = 96 bytes
+
+ The sequence header (8), the ui-state (96) and the timestamp (8) result in the
+ fixed size (112 bytes) compared below: 'if (space < size + 104)'.
+
+ The audio-data is similarly straight forward to compute. Per channel:
+
+ sizeof(LV2_Atom) [Atom wrapper 8]
+ + sizeof(LV2_Atom_Property) [channel-id attribute 24 ]
+ + sizeof(uint32_t) [LV2_Atom_Int 4]
+ + sizeof(LV2_Atom_Property) [atom-vector attribute 24 ]
+ + sizeof(float) * n_samples [raw audio data]
+ + padding [ 4 ]
+
+ = 64 + sizeof(float) * n_samples
+
+ Depending on the number of samples, the padding is either 0 or 4 bytes.
+ The data up until the raw-audio-data is 4 bytes short of the 64 bit padding.
+ Since sizeof(float) = 4, the overall alignment is retained if the number of
+ audio-samples is an even number and a padding of 4 bytes is to be added.
+ If the number of audio-samples is an odd number, the padding is zero.
+ We're interested in the worst-case scenario and hence add 4.
+
+ JACK's maximum period size is 8192 samples (per port).
+ Hence the upper limit of data to be transmitted to the GUI will is
+ 112 + (sizeof(float) * 8192 + 64) * n_channels =
+ mono: 32944 bytes
+ stereo: 65776 bytes
+ These numbers are requested in the .ttl files using
+ http://lv2plug.in/ns/ext/resize-port#minimumSize
- TODO: Explain these magic numbers.
*/
const size_t size = (sizeof(float) * n_samples + 64) * self->n_channels;
const uint32_t space = self->notify->atom.size;
- if (space < size + 128) {
+ if (space < size + 112) {
+ capacity_ok = false;
/* Insufficient space, report error and do nothing. Note that a
real-time production plugin mustn't call log functions in run(), but
this can be useful for debugging and example purposes.
*/
- lv2_log_error(&self->logger, "Buffer size is insufficient\n");
- return;
+ if (!self->printed_capacity_warning) {
+ self->printed_capacity_warning = true;
+ lv2_log_error(&self->logger, "Buffer size is insufficient\n");
+ }
}
// Prepare forge buffer and initialize atom-sequence
@@ -236,7 +324,7 @@ run(LV2_Handle handle, uint32_t n_samples)
lv2_atom_forge_property_head(&self->forge, self->uris.ui_amp, 0);
lv2_atom_forge_float(&self->forge, self->ui_amp);
lv2_atom_forge_property_head(&self->forge, self->uris.param_sampleRate, 0);
- lv2_atom_forge_float(&self->forge, self->rate);
+ lv2_atom_forge_float(&self->forge, capacity_ok ? self->rate : 0);
lv2_atom_forge_pop(&self->forge, &frame);
}
@@ -278,7 +366,7 @@ run(LV2_Handle handle, uint32_t n_samples)
// Process audio data
for (uint32_t c = 0; c < self->n_channels; ++c) {
- if (self->ui_active) {
+ if (self->ui_active && capacity_ok) {
// If UI is active, send raw audio data to UI
tx_rawaudio(&self->forge, &self->uris, c, n_samples, self->input[c]);
}
diff --git a/plugins/eg05-scope.lv2/examploscope.ttl.in b/plugins/eg05-scope.lv2/examploscope.ttl.in
index 0b76962..825e61c 100644
--- a/plugins/eg05-scope.lv2/examploscope.ttl.in
+++ b/plugins/eg05-scope.lv2/examploscope.ttl.in
@@ -47,7 +47,7 @@ egscope:Mono
lv2:symbol "notify" ;
lv2:name "Notify" ;
# 8192 * sizeof(float) + LV2-Atoms
- rsz:minimumSize 32832;
+ rsz:minimumSize 32944;
] , [
a lv2:AudioPort ,
lv2:InputPort ;
@@ -88,7 +88,7 @@ egscope:Stereo
lv2:index 1 ;
lv2:symbol "notify" ;
lv2:name "Notify" ;
- rsz:minimumSize 65664;
+ rsz:minimumSize 65776;
] , [
a lv2:AudioPort ,
lv2:InputPort ;
diff --git a/plugins/eg05-scope.lv2/examploscope_ui.c b/plugins/eg05-scope.lv2/examploscope_ui.c
index 4061ff9..626c161 100644
--- a/plugins/eg05-scope.lv2/examploscope_ui.c
+++ b/plugins/eg05-scope.lv2/examploscope_ui.c
@@ -71,6 +71,7 @@ typedef struct {
uint32_t n_channels;
bool paused;
float rate;
+ bool error;
} EgScopeUI;
@@ -184,6 +185,26 @@ on_expose_event(GtkWidget* widget, GdkEventExpose* ev, gpointer data)
cairo_rectangle(cr, 0, 0, DAWIDTH, DAHEIGHT * ui->n_channels);
cairo_fill(cr);
+ if (ui->error) {
+ cairo_set_source_rgba (cr, 0.7, 0, 0, .5);
+ cairo_rectangle (cr, 0, 0, DAWIDTH, DAHEIGHT * ui->n_channels);
+ cairo_fill(cr);
+ /* simple cairo toy text -- http://cairographics.org/manual/cairo-text.html */
+ cairo_set_source_rgba (cr, 1, 1, 1, 1);
+ cairo_select_font_face(cr, "cairo:monospace", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_set_font_size(cr, 10.0);
+ cairo_move_to(cr, 20, -18 + (DAHEIGHT * ui->n_channels) / 2.0);
+ cairo_show_text(cr, "UI is not available.");
+ cairo_move_to(cr, 20, -6 + (DAHEIGHT * ui->n_channels) / 2.0);
+ cairo_show_text(cr, "The LV2-host does not provide sufficient buffers.");
+ cairo_move_to(cr, 20, 6 + (DAHEIGHT * ui->n_channels) / 2.0);
+ cairo_show_text(cr, "Please ask the author of the LV2-host to support");
+ cairo_move_to(cr, 20, 18 + (DAHEIGHT * ui->n_channels) / 2.0);
+ cairo_show_text(cr, "http://lv2plug.in/ns/ext/resize-port#minimumSize");
+ cairo_destroy(cr);
+ return TRUE;
+ }
+
cairo_set_line_width(cr, 1.0);
const uint32_t start = ev->area.x;
@@ -453,6 +474,7 @@ instantiate(const LV2UI_Descriptor* descriptor,
ui->stride = 25;
ui->paused = false;
ui->rate = 48000;
+ ui->error = false;
ui->chn[0].idx = 0;
ui->chn[0].sub = 0;
@@ -594,7 +616,13 @@ recv_ui_state(EgScopeUI* ui, const LV2_Atom_Object* obj)
// Update UI
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->rate = rate;
+ if (rate > 0) {
+ ui->rate = rate;
+ ui->error = false;
+ } else {
+ ui->error = true;
+ gtk_widget_queue_draw(ui->darea);
+ }
return 0;
}
--
1.7.10.4
More information about the Devel
mailing list