LFSR Noise Generator

I’m working on creating a Ricoh 2a03/GameBoy DMG noise channel that’s faithful to the original and I’ve seen lots of attempts at this where someone basically packs some random numbers into a wavetable or someone downsamples/bitcrushes white noise but this isn’t what I’m after and I’m trying to figure out a few matters mostly related to the use of logic in SC that I don’t really understand.

Here’s a brief overview of how the noise channel works:

It’s a 15 bit LFSR that always starts with a value of 1 and uses a seeded random to populate each initial bit.

A timing pulse set to one of a particular set of frequencies drives the LFSR. When a pulse is received the following happens:

Bit 0 is output as a signal (if the amplitude envelope is open).
Bits 1 and 0 are XOR’d and the value of the XOR is the new value to continue the sequence.
If the noise channel is in “width mode”, the sequence is 7 bits rather than 15.

What I have so far:

I am starting with an array of the integer 1 because the sequence always starts with 1 and am concatenating this array with a seeded random that generates a 14 bit sequence of the integers 1 or 0.

For the sake of efficiency, I want to store the original array as a variable and have the LFSR process iterate over a new array with the initial state being the the original array.

Then I would store the value of the output bit in a new array (the signal array) to be converted to a wavetable (then offset by -0.5 for DC correction).

In order to create the signal array, I would have to create a process where each new iteration of the LFSR process is compared to the original array and then if they are the same (because the result of the XOR would yield a repeating pattern), stop adding new points, set the size of the signal array to the number of iterations needed for the sequence to repeat for that particular seed, and then fill the array with each of the output bit values.

I assume I would need to use a routine or loop to make this happen, but the documentation is confusing when it comes to logic. Can I do an XOR on 1 and 0 integers? I suppose I could set up something like shift everything right and
“if index 0 + index 1 = 1, index 15 is 1, else index 15 is 0”

And is there a way to basically make a conditional loop that makes the LFSR run through the steps until

array a == array b

then extract the number of iterations and stop the routine? I do not understand the documentation on stopping a routine/loop unless you know in advance how long you want it to run for. And the documentation under control structures seems to conflict other documentation on using these in SC.

Thanks in advance!

Yes. Actually you could do all of this with a single integer, because SC has left shift <<, right shift >>, and xor bitXor operators, so you don’t need the array of bits at all.

See while under Control Structures | SuperCollider 3.12.2 Help

hjh

1 Like

Probably something close to this:

(
var size = 15;  // <= 30
var rightShiftMask = ((1 << (size-1)) - 1) << 1;

var rotateBits = { |int|
	(int & 1 << (size-1)) | (int & rightShiftMask >> 1)
};

var rotateAndXor = { |int|
	int = rotateBits.(int);
	(int & rightShiftMask) | (
		(int & 2 >> 1) bitXor: (int & 1)
	)
};

r = Routine {
	var int = (1 << size).rand;
	var new = rotateAndXor.(int);
	
	int.yield;
	
	while { new != int } {
		new.yield;
		new = rotateAndXor.(new);
	};
};
)

r.next & 1  // this is one bit of your output

hjh

Thanks for this, James! A few follow up questions if you don’t mind:

From here would I dump the output into an array and convert it to a signal?
And if so, will it stop writing new values to the array on its own so it creates one period?

Or is there a Ugen I would use (Trig perhaps?) to read the sequence directly as a stream clocked at the rate a GameBoy timing pulse generator would be running?

Which would be more efficient/practical and why?

Thanks again!

Note that the code says while { new != int } ... – the meaning of this is, keep going as long as the new number is different from the initial number.

When new == int, the condition is false and while loops by definition come to an end at this point.

What happens after that is… there’s nothing left in the Routine, so yes, the Routine will stop. Attempts to next after that will simply produce nil.

I’ll edit the function to package it a little more cleanly – and it’s even simpler to get rid of the routine and just collect the values within the loop. (And, the random seed can go, because the sequence will always produce all non-zero integers within the bit depth, and the sequence is deterministic, so starting with a different seed only rotates the final result. So the initial idea in your post, “always start with 1,” is correct.)

(
~genCycle = { |bits = 15|  // up to 30 only
	var rightShiftMask = ((1 << (bits-1)) - 1) << 1;
	
	var rotateBits = { |int|
		(int & 1 << (bits-1)) | (int & rightShiftMask >> 1)
	};
	
	var rotateAndXor = { |int|
		int = rotateBits.(int);
		(int & rightShiftMask) | (
			(int & 2 >> 1) bitXor: (int & 1)
		)
	};
	
	var new = 1;
	var array = [1];
	
	while {
		new = rotateAndXor.(new);
		new != 1
	} {
		array = array.add(new);
	};
	
	array
};
)

a = ~genCycle.(15);
a.size  // 32767

a contains the full integers. “Convert it into a signal” means extracting single bits. I’ll assume you wanted the least significant bit. Since SC overloads math operators (including bitwise operators) up to arrays, that’s as simple as a & 1 - 0.5 (subtracting DC offset).

To play it in the server, I’d load the sequence into a buffer and read it with BufRd. If you disable interpolation, then you would get the discrete bits.

(
s.waitForBoot {
	b = Buffer.sendCollection(s, ~genCycle.(15) & 1 - 0.5, 1);
};
)

(
a = { |bufnum|
	// try different rates by mouse position
	var rate = MouseX.kr(1000, 8000, 1);  // samples forward per second
	var phase = Phasor.ar(0, rate * SampleDur.ir, 0, BufFrames.kr(bufnum));
	var sig = BufRd.ar(1, bufnum, phase, interpolation: 1);
	(sig * 0.2).dup
}.play(args: [bufnum: b]);
)

hjh

2 Likes

Thank you for doing this James! I always appreciate your helpful and insightful responses