Spectral Delay Gen


    DominikK's icon
    DominikK's icon
    Mar 15 2017 | 3:53 pm
    Hey folks,
    I spent the day porting my (not so nice sounding) feedback spectral delay to Gen. After getting quite close to what i wanted but not really achieving it I looked at the provided gen examples dealing with this topic "spectraldelay" and "spectraldelay_feedback". Unfortunately the examples provided have the same issues I'm facing in my implementation.
    If I send a signal through the delay, no feedback, equally delaying all bins, I should get a delayed version of the input. Right? Now this works fine in the patch without feedback implemented. If I do the same thing in the implementation which includes a feedback chain, the original signal sounds somewhat blurry. This seems to occur even if the feedback amount is set to 0. I have difficulties wrapping my head around this problem.
    I also noticed that the example patches use two different ways to calculate the phase differences. I might have a bad day but from my understanding these produce different results. The way it's done in the no-feedback example, subtracting the current phase from the previous, is the way I'm familiar with. What's the reason for these two different calculations?
    I have a feeling there's a fundemental flaw in my understanding how a spectral delay feedback chain works.
    thank you! d

    • brendan mccloskey's icon
      brendan mccloskey's icon
      brendan mccloskey
      Mar 15 2017 | 5:26 pm
      "not so nice sounding"
      patch would be nice please
    • DominikK's icon
      DominikK's icon
      Mar 16 2017 | 11:17 am
      Thanks for your reply, Brendan. Unfortunately it's rather difficult to extract the patch since it's embedded in a bigger control structure. The problems occured mainly from the Max-only approach because I couldn't find a solution to do a sample precise delay in the feedback chain in order to feed back into the right bins (delay~ allows no feedback). The Gen implementation solves that but leaves open questions I cannot answer. My implementation is basically equal to the ones in the gen examples, so we can ignore my patch and take these as the basis for discussion. I was hoping someone with more insight in the spectral domain than me could tell me more about the phenomena mentioned above.
    • volker böhm's icon
      volker böhm's icon
      volker böhm
      Mar 16 2017 | 1:56 pm
      the gen spectral delay uses a phase vocoder implementation, i.e. it calculates phase diffs and accumulates them later. the pvoc sounds a little smoother when you plan to use dynamic delay time changes, but it also results in the blurry sound you are hearing. i would advise against it in this situation. simply throw out the phase diff/accum stuff and you can even get rid of cartopol/poltocar to save some processing power, and delay real and imag signals directly. also, add a "@feedback 0" to the [delay samplerate] objects - it's on by default, which is not a good idead IMHO. then you can also kick out the [+ 1] in the delay time calculations on the right to achieve 0 delay times if needed. hth, vb
    • Graham Wakefield's icon
      Graham Wakefield's icon
      Graham Wakefield
      Mar 16 2017 | 8:51 pm
      Responding to earlier q: the feedback & non-feedback cases are actually computed the same way: the framedelta is calculated as the change in phase (by subtracting by a delay of vectorsize samples), and then accumulated again after the main delay. The only difference is that there's a feedback path from after the accumulation back into the input. You can try moving the framedelta / frameaccum stages around before & after the feedback loop to see how it changes the result.
      True that adding @feedback 0 will allow zero throughput delay, but only if you have no feedback paths in the patcher that run through the delay. And true that skipping the framedelta/frameaccum can actually sound better for a lot of sonic material, esp. percussion (and if skipped, yes can also skip the cartopol/poltocar).
      But I would not remove them if there will be any feedback or other changes in the delay path, it could really crunch up the phases...
    • DominikK's icon
      DominikK's icon
      Mar 20 2017 | 8:59 am
      Thank you both for your answers! I was wondering about the delay object and it's feedback ability but figured that it has to have a 1 sample delay unless an another argument is given. The difference in sound qualitiy between the fb and non-fb example is still not 100% clear to me but I'll play around maybe I can fingure it out. These things are somehow hard to visualize. Especially in the FFT world there is a number of things I basically understand but struggle to get them visualized in my head. The difference between understanding the math and actually absorbing how it works...
    • Graham Wakefield's icon
      Graham Wakefield's icon
      Graham Wakefield
      Mar 20 2017 | 8:38 pm
      The @feedback attribute basically determines whether writing into the delay line happens before or after reading. For @feedback 0, write happens before read. This means you can set delay times of less than 1 sample, right down to 0 samples (pass through). But you include the output into a feedback loop back to the input within the genpatcher. For @feedback 1, write happens after read. This means you can route patch cords into a loop, but you can't have a delay time less than 1 sample. It may be helpful to open the code view panel (the C button at the side) and select the [delay] object to see what it actually does.
      Best way to think of the fourier domain is as a bank of sine oscillators, each one at specific frequency, one for each bin in the spectrum. A spectrum has an amplitude of each oscillator, but also a phase of each oscillator (its angular rotation). Usually we look at the amplitude spectrum visually because it's pretty intuitive -- the left side is how much low frequency energy there is, the right side is how much high frequency energy. But the scale is logarithmic in frequency, which means most of the interesting stuff is way over to the left.
      The inputs & outputs for pfft~ subpatchers process each bin of the spectrum in turn. The 3rd outlet of the fftin~ object is the bin number. Bin 0 is the lowest frequency component. You can convert this to the centre frequency of a bin with [* samplerate/fftsize] (see the gen~.bark example).
      Within pfft~, the "vectorsize" global variable in gen~ corresponds to the number of bins in the spectrum. So delaying by exactly this number effectively spans an entire cycle of the spectrum; which means each bin is delayed by one frame. That's why the spectral delay examples are full of [delay vectorsize] objects. They're used in much the same way as a [history] object would be used in a normal gen~ patcher. (Delaying by values that are not whole multiples of "vectorsize" would effectively map different bins onto each other, creating a frequency shifter.)
      The 1st & 2nd outlets of [fftin~] are the real/imaginary components of a particular bin. You can convert the real/imaginary components to amplitude/phase by [cartopol]. Then amplitude you can do whatever you want to just like a regular signal, except that it only affects that frequency bin. Phase is not as easy to manipulate. Phase coherency is important to maintain sharp transients (high frequency attacks). If phases slip against each other, these sharp attacks become spread out in time, sounding blurry or watery. But if phases are not coherent between subsequent frames you can also get a different kind of burble noise. The phase frame delta/frame accum stuff in the spectral delay is there to reduce this burbliness, by effectively turning absolute phase into phase difference between frames, then accumulating these differences back to absolute phase in the output. But by doing so it can blur the transients.