diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 27dd783e5..c3ed7b9f1 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -93,6 +93,7 @@ set(PLUGIN_DIRS GlitchUGens JoshUGens LoopBufUGens + LFSRNoiseUGens MCLDUGens MdaUGens Neuromodules diff --git a/source/LFSRNoiseUGens/LFSRNoise.cpp b/source/LFSRNoiseUGens/LFSRNoise.cpp new file mode 100644 index 000000000..2b0184c1d --- /dev/null +++ b/source/LFSRNoiseUGens/LFSRNoise.cpp @@ -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(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(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); +} diff --git a/source/LFSRNoiseUGens/sc/HelpSource/Classes/LFSRNoise.schelp b/source/LFSRNoiseUGens/sc/HelpSource/Classes/LFSRNoise.schelp new file mode 100644 index 000000000..cb1176c4d --- /dev/null +++ b/source/LFSRNoiseUGens/sc/HelpSource/Classes/LFSRNoise.schelp @@ -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; +) + +:: diff --git a/source/LFSRNoiseUGens/sc/LFSRNoise.sc b/source/LFSRNoiseUGens/sc/LFSRNoise.sc new file mode 100644 index 000000000..4ec881b6a --- /dev/null +++ b/source/LFSRNoiseUGens/sc/LFSRNoise.sc @@ -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); + } +}