Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Most efficient way to wrap 32 bit integer to 16 bit value?

I am writing some DSP code that performs wavefolding distortion on an input signal. This code applies amplitude gain (multiplies input by a gain value) then wave folds the input such that the final amplitude range is -32768 to 32767 (-1 to ~1 in 16-bit fixed point audio format). Values above 32767 wrap to -32768 and vice versa.

What is the most efficient way to perform this wrapping operation? I discovered it by accident on STM32 hardware where a 16-bit values automatically rolled over when large gain values were applied, but this version is meant to run on different hardware (NXP) and I'd like to have intentional control of what I'm doing rather than relying on a poorly documented side effect. I'd also like this to be as efficient as possible while keeping the code in C as it's a realtime DSP process and therefore runtime critical.

Please let me know what approach you would suggest TIA!

Attached is a code snippet that shows an initial approach and demonstrates what is meant to be accomplished. I don't think using the while loop is likely to be very efficient, would like to improve this to use a better method:

void wavefolding_distortion::update(void)
{
  audio_block_t *block = receiveWritable(0);

  if(block){
    for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
      int32_t in = block->data[i];

      in *= gain;

      while(in > 32767){
        in -= 65536;
      }

      while(in < -32768){
        in += 65536;
      }

      block->data[i] = in;
    }
    transmit(block);
    release(block);
  }

}
like image 249
Emmett Palaima Avatar asked Sep 30 '25 10:09

Emmett Palaima


1 Answers

From cppreference, under integral conversions:

If the destination type is signed, the value does not change if the source integer can be represented in the destination type. Otherwise the result is implementation-defined(until C++20)the unique value of the destination type equal to the source value modulo 2^n where n is the number of bits used to represent the destination type(since C++20) (note that this is different from signed integer arithmetic overflow, which is undefined).

So if you have C++20 or later enabled, static_cast<int16_t> does exactly what you want and should be maximally efficient.

If you cannot enable C++20, you could check the compiler builtins and platform intrinsics. But the reason why C++20 makes conversion to signed integers well-defined is that two's complement basically is what all processors are using nowadays. Knowing that, even if you do not have C++20 enabled, you could check at compile time that your compiler is doing the right thing on a few key values, like:

static_assert(static_cast<int16_t>(32768) == -32768);
static_assert(static_cast<int16_t>(-32769) == 32767);
// etc. Test some larger values too, whatever makes you feel comfortable.

And based on such checks, you could feel confident that static_cast<int16_t> indeed is safe and efficient for you. You could also double-check some disassembled object file to make sure that nothing funny is going on.

Until you have access to C++20, you could encapsulate the conversion into a well-documented inline function, for clarity.

like image 158
nilo Avatar answered Oct 02 '25 05:10

nilo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!