gen~ and sah~ and phase-shifting

    Feb 07 2013 | 9:10 am
    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.
    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

    • Feb 07 2013 | 9:42 am
      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.
      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..
    • Feb 07 2013 | 9:46 am
      (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)
    • Feb 07 2013 | 9:58 am
      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")
    • Feb 07 2013 | 1:06 pm
      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.
      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~.
    • Feb 07 2013 | 1:18 pm
      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.
      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.
    • Feb 08 2013 | 1:25 am
      On trusty android so cannot see new patch just now but: woohoo!
    • Feb 08 2013 | 11:50 am
      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:)
    • Feb 08 2013 | 2:39 pm
      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.
    • Feb 08 2013 | 9:39 pm
      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.
    • Feb 08 2013 | 10:35 pm
      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
      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.
    • Feb 09 2013 | 5:45 am
      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.
    • Feb 09 2013 | 9:26 pm
      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!
    • Feb 09 2013 | 9:43 pm
      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...
    • Feb 09 2013 | 9:50 pm
      Ooh ooh ooh. The gen examples. All of them. Yes please
    • Feb 09 2013 | 10:22 pm
      ....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"
    • Feb 11 2013 | 11:57 am
      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?
      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.
    • Feb 11 2013 | 2:15 pm
      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
    • Feb 11 2013 | 2:24 pm
      "rapidly hurtles"
      as opposed to what, exactly........slowly hurtles?
    • Feb 11 2013 | 5:49 pm
      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.
    • Feb 11 2013 | 7:13 pm
      Hallo again Peter and thanks for chipping in
      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.
    • Feb 11 2013 | 7:20 pm
      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?
    • Feb 11 2013 | 7:46 pm
      Yes of course, phase-modulation causes frequency changes.
      So, the concept rather than the implementation is a little flawed.
      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?
    • Feb 11 2013 | 7:48 pm
      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 :)
    • Feb 11 2013 | 8:17 pm
      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?
    • Feb 11 2013 | 10:13 pm
      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