<bitset> in Ugen

I would like to build a handful of Ugens involving bitshifting and bitwise logic like LFSR and turing machine. I am sure I could make them as SynthDefs, but I’d like them to be compiled C++ code.

Can I use std::bitset for Ugen development or no? I did see the note about not using STL containers, so I’m assuming that since std::bitset is a fixed size, I should not use it and prefer RTAlloc/RTFree if I go that route.

A follow-up question is is my code fast enough to just use how it is or do I need to use a buffer instead of a calculation function? I would assume this is pretty fast, but I’m not really sure. Also wondering if I’ve more or less just cheated my way around using a container with member variables and thus defeated the purpose of using a buffer…

Side note: I’m well aware I’ve left out arguments in functions that would be sensible to have arguments. I’m just going for efficiency here and a first build. I have no need right now to xor other bits or overload the bitshifting operators. A constant shift value of one will be fine for the moment. I’ll fix it later if/when I need to. This is also my first Ugen, so suggestions welcome. I haven’t actually put this into the calculation function or imported anything from the source code.

#ifndef LFSR_hpp
#define LFSR_hpp

#include <stdio.h>
#include <iostream>

class LFSR
{
    unsigned bit00 = 1;
    unsigned bit01 = 0;
    unsigned bit02 = 0;
    unsigned bit03 = 0;
    unsigned bit04 = 0;
    unsigned bit05 = 0;
    unsigned bit06 = 0;
    unsigned bit07 = 0;
    unsigned bit08 = 0;
    unsigned bit09 = 0;
    unsigned bit10 = 0;
    unsigned bit11 = 0;
    unsigned bit12 = 0;
    unsigned bit13 = 0;
    unsigned bit14 = 0;
public:
    LFSR(){}
    ~LFSR(){}
    
    //print
    inline void print(std::ostream& os);
    
    //rotate right
    inline void rotr();
    
    //xor bits 0 and 1
    inline unsigned XOR();
    
    inline void rotateAndXOR();
};

void LFSR::print(std::ostream& os)
{
    os << bit14 << bit13 << bit12 <<
    bit11 << bit10 << bit09 << bit08 <<
    bit07 << bit06 << bit05 << bit04 <<
    bit03 << bit02 << bit01 << bit00 << '\n';
}

inline void LFSR::rotr()
{
    const unsigned old = this->bit00;
    this->bit00 = this->bit01;
    this->bit01 = this->bit02;
    this->bit02 = this->bit03;
    this->bit03 = this->bit04;
    this->bit04 = this->bit05;
    this->bit05 = this->bit06;
    this->bit06 = this->bit07;
    this->bit07 = this->bit08;
    this->bit08 = this->bit09;
    this->bit09 = this->bit10;
    this->bit10 = this->bit11;
    this->bit11 = this->bit12;
    this->bit12 = this->bit13;
    this->bit13 = this->bit14;
    this->bit14 = old;
}

inline unsigned LFSR::XOR()
{
    return this->bit00 != this->bit01;
}

inline void LFSR::rotateAndXOR()
{
    const unsigned result = XOR();
    rotr();
    this->bit14 = result;
}

#endif /* LFSR_hpp */
1 Like

In most cases, if you have a data structure where you need to maintain a fixed-sized history of an incoming stream of data, what you want is a ring buffer. This precludes the need to shift all the entries over when a new entry comes in.

However, in this case, there’s no need to even use a ring buffer for a 14-bit LFSR. I would just use a uint16_t. Shifting the register once is x = x >> 1, setting the incoming 14th bit is x = x | (bit << 13). No need to overcomplicate things. That said, std::bitset should be fine in the audio thread.

Is this performance critical code and known to be a bottleneck in the intended use case? If not, then I wouldn’t worry about “going for efficiency” at this stage. Watch out for premature optimization. If you do need to do performance optimization, make sure you are doing it scientifically with a serious benchmarking tool.

1 Like

Thanks for the explanation! I haven’t really done any C++ for DSP/audio yet so this is very helpful.

+1 for using an uint16_t as @nathan said. It’s in .

Some other C++ things.

You can remove all inline words here. C++ automatically inlines methods in a class. You only need it in a free standing function.

You should probably use #pragma once than the older macro.

You need the rest of the constructors, move and copy, along with their assignments.

std::bitset can be used in real-time code as it doesn’t allocate on the heap. But what you have isn’t a bit set, you should use bitset when you have many flags you want to set.

You should use std::array rather than having multiple similar variables and naming them with numbers. std::vector isn’t allowed as it allocates on the heap, but array is fine as it’s stack based.

In the XOR method you are implicitly casting from a book to an unsigned, don’t do that, use static_cast.

Technically everything should be marked constexpr and the method that returns needs a [[nodiscard]], but that’s a little much.

1 Like

Awesome, thank you!!
I was trying to avoid the array since I saw in the documentation that the use of STL containers was discouraged but you’re saying the size of the array I would’ve used would make it ok since it’s so small?
…But also that it’s best to not even use the array and just do bitwise operations on uint16_t.

What I posted was after I thought I wasn’t supposed to use bitset, which I had already used and then thought better of it when I looked at the documentation on Ugen writing. Typing out the member variables was not something I would’ve normally done and a real pain.