]> sigrok.org Git - libsigrok.git/commitdiff
analog.c: rephrase analog feed to float conversion routine
authorGerhard Sittig <redacted>
Sun, 13 Sep 2020 06:34:45 +0000 (08:34 +0200)
committerGerhard Sittig <redacted>
Fri, 18 Sep 2020 14:42:37 +0000 (16:42 +0200)
Rephrase the sr_analog_to_float() routine to further reduce redundancy.

Check early for a match of the input data and result format, to grab the
raw data without conversion in that case. Handle optional scale/offset
calculation in that fast code path, too.

Unify the instructions which convert input data from either integer or
floating point presentations to the result format. Use common stream
readers to retrieve input data in several formats, which eliminates
local conversion buffers. Move common sub-expressions (scale/offset) out
of loops. Run more calculations on double precision data before results
get trimmed to single precision.

Add and extend comments to improve future maintenance. Include (terse)
details of unsupported input data presentations in error messages.

This implementation was neither tuned nor measured for performance.
There still is a lot of redundancy among the branches which handle a
specific input data type. Rephrasing that approach interacts with the
yet to be done performance tuning, thus needs to get addressed later.
The current phrases' verbosity is believed to improve readability.

src/analog.c

index 4ec724cfde488a0ae4a37a5c2ab0f0b414264da8..f5cc628500138f5a5dd901b3240629abbaf8dbfb 100644 (file)
@@ -160,8 +160,8 @@ SR_PRIV int sr_analog_init(struct sr_datafeed_analog *analog,
 /**
  * Convert an analog datafeed payload to an array of floats.
  *
- * Sufficient memory for outbuf must have been pre-allocated by the caller,
- * who is also responsible for freeing it when no longer needed.
+ * The caller must provide the #outbuf space for the conversion result,
+ * and is expected to free allocated space after use.
  *
  * @param[in] analog The analog payload to convert. Must not be NULL.
  *                   analog->data, analog->meaning, and analog->encoding
@@ -177,146 +177,205 @@ SR_PRIV int sr_analog_init(struct sr_datafeed_analog *analog,
 SR_API int sr_analog_to_float(const struct sr_datafeed_analog *analog,
                float *outbuf)
 {
-       unsigned int b, count;
-       gboolean bigendian;
-       uint8_t conv_buf[sizeof(double)];
-       float *conv_f = (float*)conv_buf;
-       double *conv_d = (double*)conv_buf;
-
-       if (!analog || !(analog->data) || !(analog->meaning)
-                       || !(analog->encoding) || !outbuf)
+       size_t count;
+       gboolean host_bigendian;
+       gboolean input_float, input_signed, input_bigendian;
+       size_t input_unitsize;
+       double scale, offset, value;
+       const uint8_t *data8;
+       gboolean input_is_native;
+       char type_text[10];
+
+       if (!analog || !analog->data || !analog->meaning || !analog->encoding)
+               return SR_ERR_ARG;
+       if (!outbuf)
                return SR_ERR_ARG;
 
        count = analog->num_samples * g_slist_length(analog->meaning->channels);
 
+       /*
+        * Determine properties of the input data's and the host's
+        * native formats, to simplify test conditions below.
+        * Error messages for unsupported input property combinations
+        * will only be seen by developers and maintainers of input
+        * formats or acquisition device drivers. Terse output is
+        * acceptable there, users shall never see them.
+        */
 #ifdef WORDS_BIGENDIAN
-       bigendian = TRUE;
+       host_bigendian = TRUE;
 #else
-       bigendian = FALSE;
+       host_bigendian = FALSE;
 #endif
-
-       if (!analog->encoding->is_float) {
-               float offset = analog->encoding->offset.p / (float)analog->encoding->offset.q;
-               float scale = analog->encoding->scale.p / (float)analog->encoding->scale.q;
-               gboolean is_signed = analog->encoding->is_signed;
-               gboolean is_bigendian = analog->encoding->is_bigendian;
-               int8_t *data8 = (int8_t *)(analog->data);
-               int16_t *data16 = (int16_t *)(analog->data);
-               int32_t *data32 = (int32_t *)(analog->data);
-
-               switch (analog->encoding->unitsize) {
-               case 1:
-                       if (is_signed) {
-                               for (unsigned int i = 0; i < count; i++) {
-                                       outbuf[i] = scale * data8[i];
-                                       outbuf[i] += offset;
-                               }
-                       } else {
-                               for (unsigned int i = 0; i < count; i++) {
-                                       outbuf[i] = scale * R8(data8 + i);
-                                       outbuf[i] += offset;
-                               }
-                       }
-                       break;
-               case 2:
-                       if (is_signed && is_bigendian) {
-                               for (unsigned int i = 0; i < count; i++) {
-                                       outbuf[i] = scale * RB16S(&data16[i]);
-                                       outbuf[i] += offset;
-                               }
-                       } else if (is_bigendian) {
-                               for (unsigned int i = 0; i < count; i++) {
-                                       outbuf[i] = scale * RB16(&data16[i]);
-                                       outbuf[i] += offset;
-                               }
-                       } else if (is_signed) {
-                               for (unsigned int i = 0; i < count; i++) {
-                                       outbuf[i] = scale * RL16S(&data16[i]);
-                                       outbuf[i] += offset;
-                               }
-                       } else {
-                               for (unsigned int i = 0; i < count; i++) {
-                                       outbuf[i] = scale * RL16(&data16[i]);
-                                       outbuf[i] += offset;
-                               }
-                       }
-                       break;
-               case 4:
-                       if (is_signed && is_bigendian) {
-                               for (unsigned int i = 0; i < count; i++) {
-                                       outbuf[i] = scale * RB32S(&data32[i]);
-                                       outbuf[i] += offset;
-                               }
-                       } else if (is_bigendian) {
-                               for (unsigned int i = 0; i < count; i++) {
-                                       outbuf[i] = scale * RB32(&data32[i]);
-                                       outbuf[i] += offset;
-                               }
-                       } else if (is_signed) {
-                               for (unsigned int i = 0; i < count; i++) {
-                                       outbuf[i] = scale * RL32S(&data32[i]);
-                                       outbuf[i] += offset;
-                               }
-                       } else {
-                               for (unsigned int i = 0; i < count; i++) {
-                                       outbuf[i] = scale * RL32(&data32[i]);
-                                       outbuf[i] += offset;
-                               }
+       input_float = analog->encoding->is_float;
+       input_signed = analog->encoding->is_signed;
+       input_bigendian = analog->encoding->is_bigendian;
+       input_unitsize = analog->encoding->unitsize;
+
+       /*
+        * Prepare the iteration over the sample data: Get the common
+        * scale/offset factors which apply to all individual values.
+        * Position the read pointer on the first byte of input data.
+        */
+       offset = analog->encoding->offset.p;
+       offset /= analog->encoding->offset.q;
+       scale = analog->encoding->scale.p;
+       scale /= analog->encoding->scale.q;
+       data8 = analog->data;
+
+       /*
+        * Immediately handle the special case where input data needs
+        * no conversion because it already is in the application's
+        * native format. Do apply scale/offset though when applicable
+        * on our way out.
+        */
+       input_is_native = input_float &&
+               input_unitsize == sizeof(outbuf[0]) &&
+               input_bigendian == host_bigendian;
+       if (input_is_native) {
+               memcpy(outbuf, data8, count * sizeof(outbuf[0]));
+               if (scale != 1.0 || offset != 0.0) {
+                       while (count--) {
+                               *outbuf *= scale;
+                               *outbuf += offset;
+                               outbuf++;
                        }
-                       break;
-               default:
-                       sr_err("Unsupported unit size '%d' for analog-to-float"
-                              " conversion.", analog->encoding->unitsize);
-                       return SR_ERR;
                }
                return SR_OK;
        }
 
-       if (analog->encoding->unitsize == sizeof(float)
-                       && analog->encoding->is_bigendian == bigendian
-                       && analog->encoding->scale.p == 1
-                       && analog->encoding->scale.q == 1
-                       && analog->encoding->offset.p / (float)analog->encoding->offset.q == 0) {
-               /* The data is already in the right format. */
-               memcpy(outbuf, analog->data, count * sizeof(float));
-       } else {
-               for (unsigned int i = 0; i < count; i++) {
-                       for (b = 0; b < analog->encoding->unitsize; b++) {
-                               if (analog->encoding->is_bigendian == bigendian)
-                                       conv_buf[b] =
-                                               ((uint8_t *)analog->data)[i * analog->encoding->unitsize + b];
-                               else
-                                       conv_buf[analog->encoding->unitsize - b - 1] =
-                                               ((uint8_t *)analog->data)[i * analog->encoding->unitsize + b];
-                       }
-
-                       if (analog->encoding->unitsize == sizeof(float)) {
-                               if (analog->encoding->scale.p != 1
-                                   || analog->encoding->scale.q != 1)
-                                       *conv_f = (*conv_f * analog->encoding->scale.p) / analog->encoding->scale.q;
-                               float offset = ((float)analog->encoding->offset.p / (float)analog->encoding->offset.q);
-                               *conv_f += offset;
-
-                               outbuf[i] = *conv_f;
-                       }
-                       else if (analog->encoding->unitsize == sizeof(double)) {
-                               if (analog->encoding->scale.p != 1
-                                   || analog->encoding->scale.q != 1)
-                                       *conv_d = (*conv_d * analog->encoding->scale.p) / analog->encoding->scale.q;
-                               double offset = ((double)analog->encoding->offset.p / (double)analog->encoding->offset.q);
-                               *conv_d += offset;
-
-                               outbuf[i] = *conv_d;
-                       }
-                       else {
-                               sr_err("Unsupported floating-point unit size '%d' for analog-to-float"
-                                      " conversion.", analog->encoding->unitsize);
-                               return SR_ERR;
-                       }
+       /*
+        * Accept sample values in different widths and data types and
+        * endianess formats (floating point or signed or unsigned
+        * integer, in either endianess, for a set of supported widths).
+        * Common scale/offset factors apply to all sample values.
+        *
+        * Do most internal calculations on double precision values.
+        * Only trim the result data to single precision, since that's
+        * the routine's result data type in its public API which needs
+        * to be kept for compatibility. It remains an option for later
+        * to add another public routine which returns double precision
+        * result data, call sites could migrate at their own pace.
+        */
+       if (input_float && input_unitsize == sizeof(float)) {
+               float (*reader)(const uint8_t **p);
+               if (input_bigendian)
+                       reader = read_fltbe_inc;
+               else
+                       reader = read_fltle_inc;
+               while (count--) {
+                       value = reader(&data8);
+                       value *= scale;
+                       value += offset;
+                       *outbuf++ = value;
                }
+               return SR_OK;
+       }
+       if (input_float && input_unitsize == sizeof(double)) {
+               double (*reader)(const uint8_t **p);
+               if (input_bigendian)
+                       reader = read_dblbe_inc;
+               else
+                       reader = read_dblle_inc;
+               while (count--) {
+                       value = reader(&data8);
+                       value *= scale;
+                       value += offset;
+                       *outbuf++ = value;
+               }
+               return SR_OK;
+       }
+       if (input_float) {
+               snprintf(type_text, sizeof(type_text), "%c%zu%s",
+                       'f', input_unitsize * 8, input_bigendian ? "be" : "le");
+               sr_err("Unsupported type for analog-to-float conversion: %s.",
+                       type_text);
+               return SR_ERR;
        }
 
-       return SR_OK;
+       if (input_unitsize == sizeof(uint8_t) && input_signed) {
+               int8_t (*reader)(const uint8_t **p);
+               reader = read_i8_inc;
+               while (count--) {
+                       value = reader(&data8);
+                       value *= scale;
+                       value += offset;
+                       *outbuf++ = value;
+               }
+               return SR_OK;
+       }
+       if (input_unitsize == sizeof(uint8_t)) {
+               uint8_t (*reader)(const uint8_t **p);
+               reader = read_u8_inc;
+               while (count--) {
+                       value = reader(&data8);
+                       value *= scale;
+                       value += offset;
+                       *outbuf++ = value;
+               }
+               return SR_OK;
+       }
+       if (input_unitsize == sizeof(uint16_t) && input_signed) {
+               int16_t (*reader)(const uint8_t **p);
+               if (input_bigendian)
+                       reader = read_i16be_inc;
+               else
+                       reader = read_i16le_inc;
+               while (count--) {
+                       value = reader(&data8);
+                       value *= scale;
+                       value += offset;
+                       *outbuf++ = value;
+               }
+               return SR_OK;
+       }
+       if (input_unitsize == sizeof(uint16_t)) {
+               uint16_t (*reader)(const uint8_t **p);
+               if (input_bigendian)
+                       reader = read_u16be_inc;
+               else
+                       reader = read_u16le_inc;
+               while (count--) {
+                       value = reader(&data8);
+                       value *= scale;
+                       value += offset;
+                       *outbuf++ = value;
+               }
+               return SR_OK;
+       }
+       if (input_unitsize == sizeof(uint32_t) && input_signed) {
+               int32_t (*reader)(const uint8_t **p);
+               if (input_bigendian)
+                       reader = read_i32be_inc;
+               else
+                       reader = read_i32le_inc;
+               while (count--) {
+                       value = reader(&data8);
+                       value *= scale;
+                       value += offset;
+                       *outbuf++ = value;
+               }
+               return SR_OK;
+       }
+       if (input_unitsize == sizeof(uint32_t)) {
+               uint32_t (*reader)(const uint8_t **p);
+               if (input_bigendian)
+                       reader = read_u32be_inc;
+               else
+                       reader = read_u32le_inc;
+               while (count--) {
+                       value = reader(&data8);
+                       value *= scale;
+                       value += offset;
+                       *outbuf++ = value;
+               }
+               return SR_OK;
+       }
+       snprintf(type_text, sizeof(type_text), "%c%zu%s",
+               input_float ? 'f' : input_signed ? 'i' : 'u',
+               input_unitsize * 8, input_bigendian ? "be" : "le");
+       sr_err("Unsupported type for analog-to-float conversion: %s.",
+               type_text);
+       return SR_ERR;
 }
 
 /**