How the samples are distributed: `uniform` fills the range evenly, `normal` clusters around the offset, `pink` weights the low frequencies as living signals do.
amplitude
float
1
-1000000 … 1000000
Scales every sample, so the noise spans minus this to plus this.
offset
float
0
-1000000 … 1000000
Added to every sample, so the noise sits around a value other than zero.
seed
int
-1
-1 … 2147483647
Fixes the stream so a patch replays the same noise; -1 takes a fresh one.
output
Name
Type
Default
Range
Doc
mode
string
value
value | block
`value` emits one sample per channel per update, which a param reference reads as a number; `block` emits the samples elapsed at `sfreq`, which is a signal.
sfreq
float
250
1 … 10000
Sample rate within an emitted block, in Hz. `value` mode ignores it.
channels
int
1
1 … 64
How many independent noise streams to emit, one per channel.
common
Name
Type
Default
Range
Doc
autotrigger
bool
true
Run on the node's own schedule, instead of waiting for an input frame. Turn this on for sources; leave it off for transforms driven by their input.
max_frequency
float
30
0 … 1000
How many frames a second to emit. Bound to the patch's `default_ufreq` variable, so editing that variable re-rates every generator at once.
frequency_mode
string
updates_per_second
updates_per_second | seconds_per_update
How to read `max_frequency`: as a rate in Hz (updates per second), or as a period in seconds between updates — convenient for very slow nodes.
Source
The current source of this node, as it stands in the goofi repository at node-bundles/signal/Noise.rs.
node-bundles/signal/Noise.rs
//! Noise — uniform, normal or pink, one independent stream per channel, paced like the LFO: a//! sample per update, or the block real time advanced by.usegoofi_core::{Data,Meta,SlotType};usegoofi_signal_sdk::{ExprDecl,ExprMode,Inputs,Manifest,Node,NodeCtx,NodeResult,OutputDecl,Outputs,ParamDecl,ParamKey,Params,ParamSpec,Tag};/// One channel's generator state: a 64-bit stream, plus the poles pink noise needs.
#[derive(Clone,Copy)]structSource{state:u64,pink:[f64;3],/// The second of a Box-Muller pair, held for the next normal sample.spare:Option<f64>,}implSource{fnnew(seed:u64)->Source{Source{state:seed|1,pink:[0.0;3],spare:None}}/// xorshift64*, which needs no dependency and is far beyond what a noise source asks of it.fnnext(&mutself)->f64{self.state^=self.state>>12;self.state^=self.state<<25;self.state^=self.state>>27;(self.state.wrapping_mul(0x2545_f491_4f6c_dd1d)>>11)asf64/(1u64<<53)asf64}fnuniform(&mutself)->f64{2.0*self.next()-1.0}fnnormal(&mutself)->f64{ifletSome(z)=self.spare.take(){returnz;}let(u,v)=(self.next().max(1e-12),self.next());let(r,theta)=((-2.0*u.ln()).sqrt(),std::f64::consts::TAU*v);self.spare=Some(r*theta.sin());r*theta.cos()}/// Three one-pole filters over white noise: about 1/f across the audible decades.fnpink(&mutself)->f64{letw=self.uniform();self.pink[0]=0.99765*self.pink[0]+w*0.0990460;self.pink[1]=0.96300*self.pink[1]+w*0.2965164;self.pink[2]=0.57000*self.pink[2]+w*1.0526913;(self.pink[0]+self.pink[1]+self.pink[2]+w*0.1848)*0.25}fnsample(&mutself,mode:&str)->f64{matchmode{"normal"=>self.normal(),"pink"=>self.pink(),_=>self.uniform(),}}}
#[derive(Default)]structNoise{sources:Vec<Source>,/// The seed the sources were built from, so a change rebuilds them.seeded:i64,start:Option<f64>,emitted:u64,}implNoise{fnreseed(&mutself,channels:usize,seed:i64){letbase=ifseed<0{std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d|d.as_nanos()asu64).unwrap_or(0x9e37_79b9_7f4a_7c15)}else{seedasu64};// Each channel walks the golden-ratio stride off the base, so no two streams line up.self.sources=(0..channels).map(|c|Source::new(base.wrapping_add((casu64+1).wrapping_mul(0x9e37_79b9_7f4a_7c15)))).collect();self.seeded=seed;}}implNodeforNoise{fnprocess(&mutself,_inp:&Inputs<'_>,out:&mutOutputs<'_>,c:&mutNodeCtx,p:&Params<'_>,)->NodeResult{letchannels=p.i64("output","channels").unwrap_or(1).clamp(1,64)asusize;letseed=p.i64("noise","seed").unwrap_or(-1);ifself.sources.len()!=channels||self.seeded!=seed{self.reseed(channels,seed);}letamp=p.f64("noise","amplitude").unwrap_or(1.0);letoffset=p.f64("noise","offset").unwrap_or(0.0);if!amp.is_finite()||!offset.is_finite(){returnErr(format!("non-finite drive: amplitude={amp}, offset={offset}").into());}letmode=p.str("noise","mode").unwrap_or("uniform");letsfreq=p.f64("output","sfreq").unwrap_or(250.0).max(1.0);ifp.str("output","mode").unwrap_or("value")!="block"{letmutbuf=Vec::with_capacity(channels*4);forsin&mutself.sources{buf.extend_from_slice(&((s.sample(mode)*amp+offset)asf32).to_le_bytes());}out.set("out",Data::array_f32(vec![channels],buf,Meta::new()).map_err(|e|e.to_string())?);returnOk(());}letstart=*self.start.get_or_insert(c.now);lettotal=(sfreq*(c.now-start)).round().max(0.0)asu64;letn=total.saturating_sub(self.emitted)asusize;ifn==0{returnOk(());}self.emitted=total;letmutbuf=Vec::with_capacity(channels*n*4);forsin&mutself.sources{for_in0..n{buf.extend_from_slice(&((s.sample(mode)*amp+offset)asf32).to_le_bytes());}}letmeta=Meta::new().with_sfreq(Some(sfreq));out.set("out",Data::array_f32(vec![channels,n],buf,meta).map_err(|e|e.to_string())?);Ok(())}fnon_param_changed(&mutself,key:&ParamKey,_v:&goofi_core::Param)->NodeResult{ifkey.group=="output"{self.start=None;self.emitted=0;}Ok(())}}staticPARAMS:&[ParamDecl]=&[ParamDecl{group:"noise",name:"mode",spec:ParamSpec::Str{default:"uniform",options:&["uniform","normal","pink"],refresh:false},expression:None,doc:Some("How the samples are distributed: `uniform` fills the range evenly, `normal` clusters \
around the offset, `pink` weights the low frequencies as living signals do.",),section:0,show:None,},ParamDecl{group:"noise",name:"amplitude",spec:ParamSpec::Float{default:1.0,min:-1.0e6,max:1.0e6},expression:None,doc:Some("Scales every sample, so the noise spans minus this to plus this."),section:0,show:None,},ParamDecl{group:"noise",name:"offset",spec:ParamSpec::Float{default:0.0,min:-1.0e6,max:1.0e6},expression:None,doc:Some("Added to every sample, so the noise sits around a value other than zero."),section:0,show:None,},ParamDecl{group:"noise",name:"seed",spec:ParamSpec::Int{default:-1,min:-1,max:i32::MAXasi64,options:&[]},expression:None,doc:Some("Fixes the stream so a patch replays the same noise; -1 takes a fresh one."),section:0,show:None,},ParamDecl{group:"output",name:"mode",spec:ParamSpec::Str{default:"value",options:&["value","block"],refresh:false},expression:None,doc:Some("`value` emits one sample per channel per update, which a param reference reads as a \
number; `block` emits the samples elapsed at `sfreq`, which is a signal.",),section:0,show:None,},ParamDecl{group:"output",name:"sfreq",spec:ParamSpec::Float{default:250.0,min:1.0,max:10_000.0},expression:None,doc:Some("Sample rate within an emitted block, in Hz. `value` mode ignores it."),section:0,show:None,},ParamDecl{group:"output",name:"channels",spec:ParamSpec::Int{default:1,min:1,max:64,options:&[]},expression:None,doc:Some("How many independent noise streams to emit, one per channel."),section:0,show:None,},// A manifest's own `common.*` is never overwritten by the universal declaration, and the// universal default is uncapped — which makes a block one sample long.ParamDecl{group:"common",name:"max_frequency",spec:ParamSpec::Float{default:30.0,min:0.0,max:1000.0},expression:Some(ExprDecl{source:"variables.system.default_ufreq",mode:ExprMode::On,trigger:true}),doc:Some("How many frames a second to emit. Bound to the patch's `default_ufreq` variable, so \
editing that variable re-rates every generator at once.",),section:0,show:None,},];staticOUTPUTS:&[OutputDecl]=&[OutputDecl{name:"out",kind:SlotType::Array}];staticMANIFEST:Manifest=Manifest{tags:&[Tag::Generator],doc:"Uniform, normal or pink noise, one independent stream per channel.",inputs:&[],outputs:OUTPUTS,params:PARAMS,producer:true,};goofi_signal_sdk::export!(Noise,MANIFEST);