Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Plugin: LFSRNoiseUGens #371

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ set(PLUGIN_DIRS
GlitchUGens
JoshUGens
LoopBufUGens
LFSRNoiseUGens
MCLDUGens
MdaUGens
Neuromodules
Expand Down
101 changes: 101 additions & 0 deletions source/LFSRNoiseUGens/LFSRNoise.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// PluginLFSRNoise.cpp
// josiah sytsma (sytsma dot music at icloud dot com)

#include "SC_PlugIn.h"

static InterfaceTable *ft;

struct LFSRNoise : public Unit {
uint32_t mState; //lfsr state
uint32_t mCounter; //sample counter
uint32_t mSeed; //seed 1 or UINT32_MAX
float mPrevTrig;
float mLevel; //output
bool triggered(float inTrig);
};

void LFSRNoise_Ctor(LFSRNoise *unit);
void LFSRNoise_next(LFSRNoise *unit, int inNumSamples);

void LFSRNoise_Ctor(LFSRNoise *unit) {
//set calc function
SETCALC(LFSRNoise_next);

//initialize member variables
unit->mSeed = UINT32_MAX;
unit->mState = UINT32_MAX;
unit->mCounter = 0;
unit->mLevel = 0.f;

//calculate one sample
LFSRNoise_next(unit, 1);
}

inline bool LFSRNoise::triggered(float trigIn) {
return trigIn > 0.f && mPrevTrig <= 0.f;
}

void LFSRNoise_next(LFSRNoise *unit, int inNumSamples) {
float* out = ZOUT(0);
float freq = IN0(0);
int fbPos = sc_clip(static_cast<int>(IN0(1)), 1, 31);
float curTrig = IN0(2);
float seedType = IN0(3);

uint32 seed = unit->mSeed;
uint32 state = unit->mState;
uint32 counter = unit->mCounter;
float level = unit->mLevel;

int remain = inNumSamples;
do {
if (counter <= 0) {
counter = static_cast<int>(SAMPLERATE / sc_max(freq, .001f));
counter = sc_max(1, counter);

//check seed type
if (seedType < 0) {
seed = UINT32_MAX; // All 1's
} else if (seedType == 0) {
seed = ~(~0u << fbPos); // Up to fbIndex 1's
} else {
seed = 1; // 1
}

//reset
if (state == 0 || unit->triggered(curTrig)) {
state = seed;
}
unit->mPrevTrig = curTrig;

//step
int newbit = state & 1;
state >>= 1;
newbit ^= state & 1;

int mask = (1 << fbPos);
state = ((state & ~mask) | (newbit << fbPos));

//level value
level = (state & 1) ? 1.0f : -1.0f;
}
int nsmps = sc_min(remain, counter);
remain -= nsmps;
counter -= nsmps;
for (int i = 0; i < nsmps; i++) {
ZXP(out) = level;
}
} while (remain);

//update member variables
unit->mSeed = seed;
unit->mState = state;
unit->mLevel = level;
unit->mCounter = counter;
}


PluginLoad(InterfaceTable *inTable) {
ft = inTable;
DefineSimpleUnit(LFSRNoise);
}
133 changes: 133 additions & 0 deletions source/LFSRNoiseUGens/sc/HelpSource/Classes/LFSRNoise.schelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
class:: LFSRNoise
summary:: 32-bit linear feedback shift register (LFSR) noise
related:: Classes/LFClipNoise
categories:: UGens>Generators>Stochastic


Description::

Pseudo-random noise that generates the values -1 or +1 at a rate given by the nearest
integer division of the sample rate by the code::freq:: argument.

Generates a periodic waveform capable of a range of complex tones from square waves to pseudo-random "pitched" noise.
Probably not great for your speakers at high amplitudes. Very low frequency values will cause lengthy periods of DC offset.

classmethods::

method::ar, kr

argument::freq
Approximate rate at which to generate values.

argument::fbIndex
Bitwise index (1-31, zero-based) to insert new values. Related to LFSR cycle period.

argument::reset
Trigger. Resets the LFSR's internal integer value to a value determined by code::iState::.

argument::iState
Initial integer state to set the LFSR to when reset (see link::#Discussion:: for more details):
-1 sets all bits to 1.
0 sets only bits up to fbIndex to 1.
1 sets only the least significant bit to 1.

argument::mul
Output will be multiplied by this value.

argument::add
This value will be added to the output.

discussion::
To produce a pseudo-random number sequence, the noise generator performs a bitwise XOR on the two least-significant bits of a 32-bit integer, shifts all bits one position to the right, and inserts the result of the XOR at the index given by the code::fbIndex:: argument. The UGen's output is the right-most bit (LSB) of the integer, scaled to ±1.

Because the state of the LFSR must always be a non-zero integer to produce new values, the UGen automatically resets its internal integer value every time it reaches zero.

Note that, in general, the period length of the LFSR increases as the index value (code::fbIndex::) increases.

Examples::

code::

//defaults
{ LFSRNoise.ar(500.0, 14) }.play;

//modulate frequency
{LFSRNoise.ar(XLine.kr(1000, 10000, 10, 1, 0, 2), 14)}.play;

//modulate fbIndex
{ LFSRNoise.ar( 10000, Line.kr(0, 31, 15, doneAction: 2).ceil.poll(label: "Index") ) }.play;

//mouse freq
{ LFSRNoise.ar(MouseX.kr(1, SampleRate.ir, 1)) }.play;

//mouse fbIndex
{ LFSRNoise.ar(10000, MouseY.kr(1, 31), 1) }.play;

//reset
{ LFSRNoise.ar(10000, 29, Impulse.kr( XLine.kr(0.1, 30, 10) )) }.play;

//different iStates
{ LFSRNoise.ar(10000, 29, Impulse.kr(20), -1) }.play;
{ LFSRNoise.ar(10000, 29, Impulse.kr(20), 0) }.play;
{ LFSRNoise.ar(10000, 29, Impulse.kr(20), 1) }.play;

//mouse reign supreme
{ LFSRNoise.ar( MouseX.kr(1, SampleRate.ir), MouseY.kr(1, 31), MouseButton.kr(1, 0, 0)) }.play;

//use as a frequency control
{SinOsc.ar(LFSRNoise.kr(10, 3, 1, 0, 200, 600) )}.play;


(
SynthDef(\help_LFSRNoise, {
var sig = LFSRNoise.ar(\freq.kr(1000), \fbIndex.kr(14), \reset.tr(0), \iState.kr(-1));
var env = Env.asr(ControlDur.ir, 1, ControlDur.ir).kr(2, 1);
sig = LeakDC.ar(sig);
sig = sig * env * \amp.kr(1);
sig = Pan2.ar(sig, \pan.kr(0));
Out.ar(\out.kr(0), sig);
}).add;
)

//8 bit rave Super Colltendo style
(
t = TempoClock(3);
p = {|pattern| Pdup(Prand([4,5], inf), pattern)};
PmonoArtic(
\help_LFSRNoise,
\amp, 1,
\dur, p.(Prand([0.25, 0.5], inf)),
\freq, p.(Pexprand(1, 40) * 500),
\fbIndex, p.(Prand([6, 14], inf)),
).play(t);
)

//bass line oscillator
(
t = TempoClock(2.5);
PmonoArtic(
\help_LFSRNoise,
\dur, Prand([Pseq(0.5!2), Pseq([1]), Pseq(0.25!4)], inf),
\scale, Scale.at(\minorPentatonic),
\degree, Pwhite(0,10),
\octave, 6,
\fbIndex, 4,
\amp, Pwhite(0.8, 1.0)
).play(t);
)

//vweep vwoip brrr
(
PmonoArtic(
\help_LFSRNoise,
\dur, 0.5,
\freq, Pexprand(10000.0, 20000.0),
\reset, 1,
\iState, Pwhite(-1, 1),
\fbIndex, Pwhite(6, 31),
\amp, Pwhite(0.8, 1.0),
\legato, 1
).play;
)

::
8 changes: 8 additions & 0 deletions source/LFSRNoiseUGens/sc/LFSRNoise.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
LFSRNoise : UGen {
*ar { |freq = 500.0, fbIndex = 14, reset = 0.0, iState = -1, mul = 1.0, add = 0.0|
^this.multiNew('audio', freq, fbIndex, reset, iState).madd(mul, add);
}
*kr { |freq = 500.0, fbIndex = 14, reset = 0.0, iState = -1, mul = 1.0, add = 0.0|
^this.multiNew('control', freq, fbIndex, reset, iState).madd(mul, add);
}
}