gen~ and sah~ and phase-shifting

brendan mccloskey's icon

Hi team
in response to a recent request, leafcutter kindly provided me with a small gen~ thing which does the following: generate a phasor ramp of variable duration, with independent control over interonset time - it's for a granulator I'm working on.

The common method of creating an overlapping grain window would be via [+~ 0.5] --> [%~ 1] and thence to the window generator (in my case, [cos~] --> [!-~ 1] --> [*~ 0.5]), but due to the imposition of an interonset value in the phasor ramp, this is not possible - the "off" period translates to 0.5 in the shifted copy.

Max Patch
Copy patch and select New From Clipboard in Max.

Therefore, I've added a variable delay inside the gen, which works well, except that deriving a window from this shifted ramp results in spikes in the window, so I have resorted to shifting the original window *outside* the gen object, using an allpass delay. The small excerpt patch below describes the problem; as I am a newcomer to gen it is possible I've made a simple error - although it looks clean to me.

Thanks all
Brendan

5002.gensah.maxpat
Max Patch
woyteg's icon

Hello Brendan!
Hapy to hear about this topic, since i ran into similar problems recently. I will look at your patch more closely if i have time but i wanted to let you know that i came to the conlcusion that working with the oscilloscope will cause problemse if it's about single samples that could cause problems. e.g. in your case, you have a spike in the window function because when the phasor goes down, it does not do
y[n]=1, y[n+1]=0, so without anything in bewteen. There is one sample with the value of about 0.5, that's the reason for the spike.

Max Patch
Copy patch and select New From Clipboard in Max.

you can see this if you record your signlas and view them closely with [waveform]( i have pasted it down there.)
Iseem to remember this problem and hope i can write you soon what solution i came up with..
cheres!

5003.Untitled1.png
png
woyteg's icon

(sorry for the huge toggle in the screenshot, i use this all the time since i find it annoying if i have no space to the bottom and right of what i'm dong)

brendan mccloskey's icon

your monitor screen must be huge :)

As gen is running at the single sample level, should I try to avoid mixing up my timings then? I tried to build my windows INSIDE the gen object, using the same cos function but they came out all wonky (a technical term meaning "wonky")

B

pid's icon

it is because the [delay] object in your gen~ is using the default linear interpolation. set it to @interp none

my guess is the allpass~ on the right somehow smooths this.

Max Patch
Copy patch and select New From Clipboard in Max.

really you should stick the whole thing in gen, then you wouldn't need theses annoying msp~ sah~s. also, your feedback loop is vector delay, which does not mach well with single sample delay in gen~.

brendan mccloskey's icon

@pid

I could (and very possibly WILL) kiss you!!

I was just about to post a patch, with the windowing etc done in gen, with an annotation suggesting that the problem lies in the delay object (not a fan of delay/allpass in this situation - it always seemed a bit heavy-handed or amateur).

Works like a charm, thanks a million.

Max Patch
Copy patch and select New From Clipboard in Max.

A final minor issue remains, in that it is still possible to interrupt the phase-shifted ramp by rapidly varying the grain size parameter; I've added sah and history to the gen code, and the scope illustrates the ramp interruption. I'm going to now patch this revision into the larger granulator and if the issue isn't audible then all well and good; but the new version is below fwiw.

Brendan

pid's icon

On trusty android so cannot see new patch just now but: woohoo!

brendan mccloskey's icon

Ok, thanks - the ramp interruptions have disappeared thanks to your "interp off" solution but an annoying issue persists, mainly due to my limited grasp of the minutiae of DSP.

It is a wider issue, and does not result from the current discussion but perhaps you have some thoughts? We often rely on overlapping hanning windows to help ameliorate amplitude modulation artefacts in granular playback engines, correct? The subtle yet persistent ringMod/Dalek effect that is the BANE of my working life!!! Anyhoo, on my system, where I use 2 windows (phase-shift by half period) ramp/grain durations of 40, 80ms have the desired effect - no ampMod, really clean, but any other small duration (c. 20~100) and the noise returns. If I use 3 overlapping windows (source -->shift 1/3 -->shift 1/3) the result is exactly the same, but substitute 60 and 90ms. I am referring of course to normal speed playback at original pitch. Naturally I would expect *some* artefacts at varied pitch/speed, but I've solved that issue, via some subtle random noise.

Previous related threads on this topic have suggested switching between "regular" playback for normal speed/pitch then shifting to grains for the granular stuff......nah, not for me, seems a bit heavy-handed; plus, this seems to be a phenomenon related to granular synthesis which I would like to understand better and perhaps solve. Alchemy, arcana or merely n00bosity?

Hope you have some valuable insights (beyond "read Microsound") - yeah, maybe I'm answering my own question....RTFM:)

Brendan

brendan mccloskey's icon

and after some patching/reading/patching, the subjective nature of the sidebands/overtones in the spectra appears to depend largely on the source material - ie different soundfiles. I could spend all day choosing the "best" grain duration for one file, only to find it doesn't have the same smoothing effect on another soundfile. I'm avoiding percussive/rhythmic audiofiles anyway. I may well have to go down the "two playback sources" option I referred to above. I'll also try a variety of windowing functions too.

Dave Mollen's icon

It might be interesting to not only randomize the start position but also the grainsize. I don't know if you've tried that already. Lately I find these grainsize below 40ms very useful. They're indeed useful for percussive sounds, but also for sounds with a lot of texture. I think granular synthesis is really appropriate for those type of sounds.

Anyway, thanks for the genpatch! I never used the interonset parameter.

brendan mccloskey's icon

Hi Dave
yes I've been experimenting with various types of noise sources, at both the grainsize signal: cosine wave with randomized frequency, noise~, rampsmooth~ etc, and also on the final output (using an allpass filter @ +/- 20ms) but there is a "threshold" beyond which (depending on the shape of the noise) chorus/phasing type effects become evident - not unpleasant, just not needed. Bearing in mind that this is going to be a sort of hybrid quasi-synchronous/time-based granular engine, I'm probably fixating on the normal-speed/normal-pitch side of things too much. Puritanical.

The gen interonset solution is leafcutter's, and as you can see from this thread pid contributed too.

Thanks for your thoughts

Brendan

ps
re the choice of windowing function, for pure granular synthesis hanning/triangle windows may be applicable for various reasons, but for time-based granular playback (time/pitch stretching and grain/loop dispersal) a "half-sine" window works best *for me* - it is the most open window I have found, letting more of the original samples through, particularly with three overlaps: phasor-->trapezoid 0.5 0.5-->*1.5-->sinx.

Peter McCulloch's icon

FWIW, there's an example of doing this in the Max Gen examples: gen~.pitchshift. (randomizing timing to reduce amp modulation sidebands)

Another way of doing the wrapping for the offset is to do something like >=~ 1 into right inlet of -~. (so subtract one when >=1) Only works within a constrained range, but it should be faster than modulo.

brendan mccloskey's icon

Hi Peter,
Nice suggestions, thanks. May I ask why the >=~ 1 is optimally better than % ? Less CPU cycles?

Using this method gives me my two additional offsets, ta!

Brendan

Peter McCulloch's icon

Comparison and addition are really cheap, whereas % involves division (unless %~ has some special optimization for 1.) At least it was that way when I was doing some C++ DSP stuff a while back...

brendan mccloskey's icon

Ooh ooh ooh. The gen examples. All of them. Yes please

brendan mccloskey's icon

....for those of you watching at home, the gen examples Peter cited use some really effective blurring methods - which blow my sh1t out of the water...way beyond "granularized.maxpat"

brendan mccloskey's icon

ps
Peter, I've tried, unsuccessfully, to add phase-modulating noise to my own patch, using the gen example you suggested. Can't seem to get my head round how to properly add noise/sah/history inside gen, I end up generating either audible noise or pitch modulation. Would you be able to help out if I supplied a small simple example patch?

pps
I am still using delay to phase shift the grain ramp, as [>=~ 1] . . . [-~] won't work with my (read: leafcutter's) "sleepy" phasor; works fine with phasor though.

regards
Brendan

brendan mccloskey's icon
Max Patch
Copy patch and select New From Clipboard in Max.

Here's an annotated patch:

Essentially, I'm trying to apply the blurring effect found in the gen-pitchshift example, via noise-based phase-modulation and I'm not sure that I've used sah/history correctly. I haven't used gen enough to become fluent yet, it does appear to require a slightly enhanced or different skill set and, as my PhD rapidly hurtles towards conclusion, I fear I just don't have time to self-train in gen.

Thanks everyone

Brendan

brendan mccloskey's icon

"rapidly hurtles"

as opposed to what, exactly........slowly hurtles?

Peter McCulloch's icon

I think the problem is that you're trying to randomly phase-shift factoring in the duration of the phasor.

Imagine that two phasors are 180 degrees out of phase with no pause in their cycle. If the offset phasor can only change when it's done playing, it can't change its offset to anything less without creating a discontinuity! -- you could, however, come up with some algorithm to have it "sit out" a cycle if it would create a discontinuity. (at least, that's what I think is the case...)

You can jump around in the time after the phasor, but not during, so if you change your random to use that range, instead of the phasor dur, you should get the correct result.

brendan mccloskey's icon

Hallo again Peter and thanks for chipping in

Max Patch
Copy patch and select New From Clipboard in Max.

I think I understand your explanation, and to demonstrate I have here a simple MSP patch showing what I wish to do inside gen, modulate the offset/shift amount using noise. In MSP it does so without clicks-pops-n-farts, in gen I think I must be doing something wrong with sah because it doesn't work as anticipated. Forgive my crushing ignorance of gen, but I simply don't have time to crash-course it.

Brendan

Peter McCulloch's icon
Max Patch
Copy patch and select New From Clipboard in Max.

You're using rand~, and that changes at a much slower rate! It's working visibly because it's changing over time, though if you see what's happening with the phasor, you'll notice that it has the effect of changing its rate too, since continuous phase changes produce frequency change. Here's code that demonstrates. You'll hear the slowing up and speeding down of the drum, which I think isn't what you want, correct?

brendan mccloskey's icon

Yes of course, phase-modulation causes frequency changes.

So, the concept rather than the implementation is a little flawed.

Max Patch
Copy patch and select New From Clipboard in Max.

Do you see any flaws in these small changes, which avoid pitch modulation (at least, subjectively):

If this is solid, do you have the time/inclination to offer a gen version, or some clues thereto?

Regards
Brendan

brendan mccloskey's icon

ps

it's just that the implementation in the gen-pitchshift example is very attractive and I hoped it would help solve/alleviate my granular Dalek issue :)

Peter McCulloch's icon

Inclination, but not the time today unfortunately. It's doable, but it smells like it'll be a weird solution.

Do you mind if the second one skips cycles to prevent artifacts?

brendan mccloskey's icon

I think even at small durations this might be noticeable/audible? I'm going to try this non-gen version for now, then swot up when I (also) have time :)

Many thanks