mj

Fast Random Noise

20251014

Decent talk by Roth Michaels, ADC 2022 Fast, High-quality Pseudo-random Numbers for Audio Developers. There is some example code at the end too.

I like that xoshiro is pretty straightforward to implement. There are code examples on the website and a paper.

The authors recommend xorshiro128+ as suitable for floating point generation. There is a reference implementation here.

    
static inline uint64_t rotl(const uint64_t x, int k) {
    return (x << k) | (x >> (64 - k));
}


static uint64_t s[2];

uint64_t next(void) {
    const uint64_t s0 = s[0];
    uint64_t s1 = s[1];
    const uint64_t result = s0 + s1;

    s1 ^= s0;
    s[0] = rotl(s0, 24) ^ s1 ^ (s1 << 16); // a, b
    s[1] = rotl(s1, 37); // c

    return result;
}
    
  

The conversion of uint64_t to a float (0 to 1) is a bit of a fiddle to get your head around, but it is neat to understand.

    
/**
* 1. Extract 32bits
* 2. Drop the 8 bit mantissa
* 3. Mutliply by float32 precision
*/
//|------ 1 -----|-- 2 --|---- 3 ----|
((uint64_t >> 32u) >> 8) * 0x1.0p-24f;
    
  

The seed should be initialised using SplitMix64. Again very straightforward to implement.

    
static uint64_t x; /* The state can be seeded with any value. */

uint64_t next() {
    uint64_t z = (x += 0x9e3779b97f4a7c15);
    z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
    z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
    return z ^ (z >> 31);
}
    
  

That just leaves the SplitMix64 state to be initialised. I use std::random_device to generate two uint32_t, and then bit shift to create a single uint64_t.

    
std::random_device rdev;

uint64_t x = rdev();
// Fill the upper 32 bits
x <<= 32;
// Fill the lower 32 bits
x |= rdev();
    
  

PCG could be used as an alternative to xoshiro. I did not look into it much as implementing xoshiro was very straightforward and it seems good enough for my needs.