[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