which papers exactly? can you post them again? I guess it's time to read it
-
looking for velvet noise generator
-
@ddw_music said:
Uses else's del~ because delread~ requires a message-based delay time, which can't update fast enough, and delread4~ interpolates, which smears the impulses. With del~, I could at least round the delay time to an integer number of samples, and AFAICS it then doesn't interpolate.
Actually delread4~ doesn't smear if the delay is always an integer number of samples (which makes sense from the cubic interpolation formula -- if the fractional part of the index is 0, then you just get the sample value at the integer index).
It's easier to ensure an integer-sample delay time using else/del~'s -samps flag
I've definitely not dug deeply enough into ELSE -- so many things that are a PITA in vanilla become straightforward. (I'd have complained a lot less four-five years ago if I had known about this library from the beginning!)
hjh
-
@porres it's mentioned in the valimaki one I posted above: https://www.researchgate.net/publication/260701331_A_Perceptual_Study_on_Velvet_Noise_and_Its_Variants_at_Different_Pulse_Densities
(novel variants on page 7)
edit: it mentions the 'regularity' but not bias.. I can't remember where that came from, maybe that one wasn't in a paper but it's necessary to make it into a pulse train
or it may have come from a different paper on 'extended velvet noise'.. been a few years -
I can't feel any perceptual difference with just positive impulses, for instance....
-
which makes me ask, what would be the purpose of having also negative impulses? what is it for?
-
@porres that's quite a philosophical question.. what are the positive ones for?
edit: as a serious answer, I guess anything that's polarity-specific. you could maybe combine multiple generators in different ways?
I guess you could always multiply by -1 but to me it seems more natural to have it go from 1 to -1 since that's the output range of digital audio and it would take extra objects.. easier just to allow negative values -
@seb-harmonik.ar said:
I do think @manuels original idea of having a sample-increment offset still works without missing periods?
Why don't you think it works with non-integer period lengths?a phasor~ is always guaranteed to have its last sample be within the last sample increment regardless of whether an integer-period or not, and
[wrap~]
will pretty much perfectly wrap the phasor~'s phase..With non-integer period lengths you have effectively a changing number of samples per period. That in itself I don't think would be a problem, but adding a random value and wrapping the result can give you sample-increments that are either smaller or bigger than the calculated value.
Maybe an extreme example can help to clarify: Consider a period length of 2.5 samples. The sample increment is in this case 0.4. If you have a period with the sample values 0.3 and 0.7, add 0.8 as a random value and wrap, what you get is 0.1 and 0.5, so the last sample doesn't get above 1 when you add the calculated sample-increment of 0.4. Am I missing something?
@ben.wes Did you do the testing with non-integer fractions of your sampling frequency? You mentioned 3kHz at 48kHz SR as an example, which shouldn't make problems ....
-
@manuels hmm yeah I guess it doesn't quite work due to the phase discontinuities..
maybe have to do a bit more tinkering with the offset/phase reset part
I wonder if it would work to just discard the wrapped around part somehow, and set the limit based on the 'non-fractional' part of the period -
@manuels said:
@ben.wes Did you do the testing with non-integer fractions of your sampling frequency? You mentioned 3kHz at 48kHz SR as an example, which shouldn't make problems ....
sorry for the confusion! - it weren't exactly 3kHz, since I'm setting the frequency with a logarithmic slider. tested once more with 3026.2Hz here now (which was probably also the value yesterday). i'm displaying the last 3 outlets here that i added to your patch (additionally offsetting the phasor for visual clarity):
-
@porres said:
I can't feel any perceptual difference with just positive impulses, for instance....
not sure if i understand correctly ... you mean you don't hear a difference between the mixed impulses and just positive ones?
in that case, you might need to check higher frequencies and tune it louder.spectrograph~ (with db and log display) for both versions at ~3kHz:
"balanced":
positive only (missing some lower frequencies):
about the purpose: https://acris.aalto.fi/ws/portalfiles/portal/13412521/applsci_07_00483_v2.pdf talks about an application for late reverberation. i didn't completely read it, i admit - but i assume, for that purpose, the impulses should have both polarities.
-
@ben.wes Thanks! So no surprise that it doesn't work ....
-
@ben.wes Please don't laugh...... I shouldn't wade into this subject as I do almost no sample-wise work in Pd.
Just applying the rules as I understand them...... regular single sample impulse...... randomly +1 or -1.
I assume that to qualify as an impulse the sample must be followed by a zero.As [noise~] seems never to output zero it seems to be a good choice for randomising +/-
[phasor~] seems to cause problems at wraparound..... as always.
[vline~] needs control rate input..... and it must be best to stay in the audio thread.
So...? please check this but [print~] produces output consistent with the rules as I understand them.....
I suppose the next stage would be to determine a period and randomly pack with zeros to displace the impulse, if that is really a requirement.
David.
velvety.pd
-
@whale-av said:
So...? please check this but [print~] produces output consistent with the rules as I understand them.....
i think, you built a version at nyquist frequency!
probably, i should read more into it (no time right now unfortunately) ... i'm not sure if velvet noise needs to meet the requirement of the random position of the impulses in each cycle. obviously, that's not possible anymore when a cycle is just 2 samples.
the idea in the version mostly discussed above is exactly that though: there's one impulse per cycle and this impulse should be placed at a random position in its cycle with random polarity. and the cycles can be of any length (samplerate / frequency).
this graph in https://acris.aalto.fi/ws/portalfiles/portal/13412521/applsci_07_00483_v2.pdf (page 4) is showing that quite well:
EDIT: this brings up an important question though (which is also probably answered in the papers): are consecutive values of
1, 1
,1, -1
,-1, -1
or-1, 1
allowed? not sure ... but these wouldn't be actual impulses anymore then, right? -
@ben.wes Then I cannot see how [vline~] can be avoided.
I still have no interval adjustment, and have to go out...... but it should be a trivial modification to the [random] argument and a correlated [block~] size.I would think that writing an external would also be simple.
I had tried using [zexy/dirac~] but it was expensive.I cant' hear the difference between [velvety-1] previous post) and [velvety-2] but of course if it were used to set reverb delays then the distribution would be important.
David.
velvety-2.pd
-
here's a completely vanilla version of the delay variant avoiding [expr~] (with some rather ugly hacks to achieve exact values of -1, 0 and 1). it works for all frequency input < nyquist (for nyquist, the > 0.5 check fails):
EDIT: seeing now that this doesn't really add anything to @ddw_music's approach at https://forum.pdpatchrepo.info/topic/13317/looking-for-velvet-noise-generator/52 - except for my failed try to achieve non-interpolated values with delread4~.
-
As I was imagining, as a C code, this is all hassle free, very simple, and never misses a period. And the code is pretty simple. And Multichannel capable
So far this is what I have....
// Porres 2024
#include "m_pd.h"
#include <stdlib.h>
#include "random.h"#define MAXLEN 1024
typedef struct _velvet{
t_object x_obj;
double *x_phase;
double *x_lastphase;
t_random_state x_rstate;
int x_id;
t_float *x_rand;
int x_nchans;
t_float x_hz;
t_int x_n;
t_int x_ch2;
t_int x_ch3;
t_inlet *x_inlet_reg;
t_inlet *x_inlet_bias;
t_outlet *x_outlet;
double x_sr_rec;
}t_velvet;static t_class *velvet_class;
static void velvet_seed(t_velvet *x, t_symbol *s, int ac, t_atom *av){
random_init(&x->x_rstate, get_seed(s, ac, av, x->x_id));
}static t_int *velvet_perform(t_int *w){
t_velvet *x = (t_velvet *)(w[1]);
t_float *in1 = (t_float *)(w[2]);
// t_float *in2 = (t_float *)(w[3]); // bias placeholder
// t_float *in3 = (t_float *)(w[4]); // regularity placeholder
t_float *out = (t_float *)(w[5]);
double phase = x->x_phase;
double lastphase = x->x_lastphase;
for(int j = 0; j < x->x_nchans; j++){
for(int i = 0, n = x->x_n; i < n; i++){
double hz = in1[jn + i];
double step = hz * x->x_sr_rec; // phase step
step = step > 1 ? 1 : step < 0 ? 0 : step; // clipped phase_step
out[jn + i] = ((phase[j] + x->x_rand[j]) >= 1.) && ((lastphase[j] + x->x_rand[j]) < 1.);
if(phase[j] >= 1.){
uint32_t *s1 = &x->x_rstate.s1;
uint32_t *s2 = &x->x_rstate.s2;
uint32_t *s3 = &x->x_rstate.s3;
x->x_rand[j] = (t_float)(random_frand(s1, s2, s3)) * 0.5 + 0.5;
post("phase = %f", phase[j]);
post("random = %f", x->x_rand[j]);
phase[j] -= 1; // wrapped phase
}
lastphase[j] = phase[j];
phase[j] += step;
}
}
x->x_phase = phase;
x->x_lastphase = lastphase;
return(w+6);
}static void velvet_dsp(t_velvet *x, t_signal **sp){
x->x_n = sp[0]->s_n, x->x_sr_rec = 1.0 / (double)sp[0]->s_sr;
int chs = sp[0]->s_nchans;
x->x_ch2 = sp[1]->s_nchans, x->x_ch3 = sp[2]->s_nchans;
if(x->x_nchans != chs){
x->x_lastphase = (double *)resizebytes(x->x_lastphase,
x->x_nchans * sizeof(double), chs * sizeof(double));
x->x_phase = (double *)resizebytes(x->x_phase,
x->x_nchans * sizeof(double), chs * sizeof(double));
x->x_rand = (t_float )resizebytes(x->x_rand,
x->x_nchans * sizeof(t_float), chs * sizeof(t_float));
x->x_nchans = chs;
}
signal_setmultiout(&sp[3], x->x_nchans);
if((x->x_ch2 > 1 && x->x_ch2 != x->x_nchans)
|| (x->x_ch3 > 1 && x->x_ch3 != x->x_nchans)){
dsp_add_zero(sp[3]->s_vec, x->x_nchansx->x_n);
pd_error(x, "[velvet~]: channel sizes mismatch");
return;
}
dsp_add(velvet_perform, 5, x, sp[0]->s_vec,
sp[1]->s_vec, sp[2]->s_vec, sp[3]->s_vec);
}static void *velvet_free(t_velvet *x){
inlet_free(x->x_inlet_bias);
inlet_free(x->x_inlet_reg);
outlet_free(x->x_outlet);
freebytes(x->x_phase, x->x_nchans * sizeof(*x->x_phase));
freebytes(x->x_lastphase, x->x_nchans * sizeof(*x->x_lastphase));
freebytes(x->x_rand, x->x_nchans * sizeof(*x->x_rand));
return(void *)x;
}static void *velvet_new(t_symbol *s, int ac, t_atom *av){
s = NULL;
t_velvet *x = (t_velvet *)pd_new(velvet_class);
x->x_id = random_get_id();
x->x_phase = (double *)getbytes(sizeof(*x->x_phase));
x->x_lastphase = (double *)getbytes(sizeof(*x->x_lastphase));
x->x_rand = (t_float *)getbytes(sizeof(*x->x_rand));
x->x_hz = x->x_phase[0] = x->x_lastphase[0] = x->x_rand[0] = 0;
velvet_seed(x, s, 0, NULL);
if(ac){
while(av->a_type == A_SYMBOL){
if(ac >= 2 && atom_getsymbol(av) == gensym("-seed")){
t_atom at[1];
SETFLOAT(at, atom_getfloat(av+1));
ac-=2, av+=2;
velvet_seed(x, s, 1, at);
}
else
goto errstate;
}
if(ac && av->a_type == A_FLOAT){
x->x_hz = av->a_w.w_float;
ac--, av++;
}
}
x->x_inlet_bias = inlet_new((t_object *)x, (t_pd *)x, &s_signal, &s_signal);
pd_float((t_pd *)x->x_inlet_bias, 0);
x->x_inlet_reg = inlet_new((t_object *)x, (t_pd *)x, &s_signal, &s_signal);
pd_float((t_pd *)x->x_inlet_reg, x->x_phase[0]);
x->x_outlet = outlet_new(&x->x_obj, &s_signal);
return(x);
errstate:
post("[velvet~]: improper args");
return(NULL);
}void velvet_tilde_setup(void){
velvet_class = class_new(gensym("velvet~"), (t_newmethod)velvet_new, (t_method)velvet_free,
sizeof(t_velvet), CLASS_MULTICHANNEL, A_GIMME, 0);
CLASS_MAINSIGNALIN(velvet_class, t_velvet, x_hz);
class_addmethod(velvet_class, (t_method)velvet_dsp, gensym("dsp"), A_CANT, 0);
class_addmethod(velvet_class, (t_method)velvet_seed, gensym("seed"), A_GIMME, 0);
} -
I think the above algorithm could be done as a patch in Vanilla, but you have to go in a lower level than using [phasor~].
I endorse all the struggle in implementing this as a Vanilla patch, but we gotta realize that sometimes life is short and how Pd is not actually well suited to implement DSP algorithms, specially Vanilla.
I have many low level objects in ELSE that make life so much easier, and even so, sometimes you gotta go into C
-
improved a bit, now it does have negative impulses, and made some more intense tests, it all works well and up to the nyquist frequency, where it becomes a "clip noise" (same as [else/white~ -clip]).
-
@ben.wes EDIT: this brings up an important question though (which is also probably answered in the papers): are consecutive values of 1, 1, 1, -1, -1, -1 or -1, 1 allowed? not sure ... but these wouldn't be actual impulses anymore then, right?
Yes, it is answered in the paper.
@porres No, they are not allowed, according to the paper @ben.wes linked to..... https://acris.aalto.fi/ws/portalfiles/portal/13412521/applsci_07_00483_v2.pdf
The note to the screenshot he posted states.......
"Figure 1a shows the first 500 samples of an example velvet-noise sequence with these parameters.
There is only one non-zero sample seen between any two grid boundaries."
The graph has an average pulse distance (interval) of 20 samples (the chosen parameter).So all the space between impulses in the chosen interval must be zero.
For a Pd block size of 64 (if that is chosen as the interval size) there should be just one impulse per block.
Yes.... [phasor~] is a disaster zone.... why I went for [vline~] even though that needed a control rate input.
A single impulse per interval should be easy now you have done the hard work in C.
Maybe you could add the interval size to your [./velvet~] external as a creation argument?
My patch [velvety-2] puts a lot of -0 values which looks very messy, but with an interval of 64 samples [print~] should produce just one non-zero sample per block....
For an interval of 2 samples the output should be as per my [velvety] above.....
.... but it is not useful as there can be no time variation within the interval....
.... and of course an interval of 1 sample is impossible.... as in your current external...... because they cannot be impulses without being followed by a zero.
Your print~ above is a time modulated square wave.Great work though.
I am jealous of those of you that have learnt to program in C.
David. -
well, what if the frequency is equal to the sample rate? then it's gotta be a bunch of 1/-1 ("clip noise")