A degenerate case that has probably no utility but that i post for fun.
The [block~ 32 2] (reblocked down overlapped twice)???
Notice that here the letters represents 16 samples (64/4). And since it is always complete at each tick just one of them is required. The result is strange. Each DSP vector is sliced in four pieces, and combined. Is it normal. I don't know? But it can be awesome! For what?
TBH I did that kind of thing only to be sure that i didn't make an error while refactoring Pure Data.
[a b c d] ! [A B - -] [- C D -] [- - A B] [D - - C] --> [A+D B+C A+D B+C]
// [block~ 32 2] BLOCK FREQUENCY+4 BLOCK PERIOD+1 INLET SIZE+64 INLET WRITE+64 INLET HOP+64 OUTLET SIZE+64 OUTLET HOP+16 #+0# P+0/+64 R+0/+32 W+0/+32 R+32/+32 W+16/+32 R+0/+32 W+32/+32 R+32/+32 W+48/+32 E+0/+64 #+1# ...
Today [block~ 512 4] (i.e. 4 overlaps).
I let you decipher the reports. You should be able if you understand the previous examples.
Notice that the shift here is done onto 6 blocks (384 samples).
Thus [1 2 3 4 5 6 7 8] becomes [3 4 5 6 7 8 7 8].
Here for convenience i don't make the difference between concecutive computations onto the same signal. I mean when it is (4A) it is in fact (A+A'+A''+A''') if i had kept notation in previous [block~ 128 2] example.
[- - - - - - - a] ! [- - - - - - - A ] #0 ^ ^ [- - - - - a b a] [- - - - - - - A ] #1 ^ ^ [- - - - - a b c] ! [B C - - - - - 2A] #2 ^ ^ [- - - a b c d c] [B C - - - - - 2A] #3 ^ ^ [- - - a b c d e] ! [2B 2C D E - - - 3A] #4 ^ ^ [- a b c d e f e] [2B 2C D E - - - 3A] #5 ^ ^ [- a b c d e f g] ! [3B 3C 2D 2E F G - 4A] #6 ^ ^ [b c d e f g h g] [3B 3C 2D 2E F G - 4A] #7 ^ ^ [b c d e f g h i] ! [4B 4C 3D 3E 2F 2G H I ] #8 ^ ^ [d e f g h i j i] [- 4C 3D 3E 2F 2G H I ] #9 ^ ^ [d e f g h i j k] ! [J K 4D 4E 3F 3G 2H 2I] #10 ^ ^ [f g h i j k l k] [J K - 4E 3F 3G 2H 2I] #11 ^ ^ [f g h i j k l m] ! [2J 2K L M 4F 4G 3H 3I] #12 ^ ^ [h i j k l m n m] [2J 2K L M - 4G 3H 3I] #13 ^ ^ [h i j k l m n o] ! [3J 3K 2L 2M N O 4H 4I] #14 ^ ^ [j k l m n o p o] [3J 3K 2L 2M N O - 4I] #15 ^ ^ [j k l m n o p q] ! [4J 4K 3L 3M 2N 2O P Q ] #16 ^ ^ [l m n o p q r q] [- 4K 3L 3M 2N 2O P Q ] #17 ^ ^ [l m n o p q r s] ! [R S 4L 4M 3N 3O 2P 2Q] #18 ^ ^
a / [- - - - - - - a] c / [- - - - - a b c] e / [- - - a b c d e] g / [- a b c d e f g] i / [b c d e f g h i] k / [d e f g h i j k] m / [f g h i j k l m] o / [h i j k l m n o] q / [j k l m n o p q] s / [l m n o p q r s]
a b c d e f g h i j k l m n o p q r s - - - - - - - 4A 4B 4C 4D 4E 4F 4G 4H 4I 4J 4K 4L
// [block~ 512 4] BLOCK FREQUENCY+1 BLOCK PERIOD+2 INLET SIZE+512 INLET WRITE+448 INLET HOP+128 OUTLET SIZE+512 OUTLET HOP+128 #+0# P+448/+64 R+0/+512 W+0/+512 E+0/+64 #+1# SHIFT+128/+384 // Shift last 384 samples (6 * 64) at start. P+384/+64 E+64/+64 #+2# P+448/+64 R+0/+512 W+128/+512 E+128/+64 #+3# SHIFT+128/+384 P+384/+64 E+192/+64 #+4# P+448/+64 R+0/+512 W+256/+512 E+256/+64 #+5# SHIFT+128/+384 P+384/+64 E+320/+64 #+6# P+448/+64 R+0/+512 W+384/+512 E+384/+64 #+7# SHIFT+128/+384 P+384/+64 E+448/+64 #+8# P+448/+64 R+0/+512 W+0/+512 E+0/+64 #+9# SHIFT+128/+384 P+384/+64 E+64/+64 #+10# P+448/+64 R+0/+512 W+128/+512 E+128/+64 #+11# SHIFT+128/+384 P+384/+64 E+192/+64 #+12# P+448/+64 R+0/+512 W+256/+512 E+256/+64 #+13# SHIFT+128/+384 P+384/+64 E+320/+64 #+14# P+448/+64 R+0/+512 W+384/+512 E+384/+64 #+15# SHIFT+128/+384 P+384/+64 E+448/+64 #+16# P+448/+64 R+0/+512 W+0/+512 E+0/+64 #+17# SHIFT+128/+384 P+384/+64 E+64/+64 #+18# P+448/+64 R+0/+512 W+128/+512 E+128/+64 #+19# SHIFT+128/+384 P+384/+64 E+192/+64 #+20# P+448/+64 R+0/+512 W+256/+512 E+256/+64 #+21# SHIFT+128/+384 P+384/+64 E+320/+64 #+22# ...
Those values have no mathematic meaning. They correspond to C code variables. Thus the "f = 1 / T" is not true here. It is just integers that are used to manage the gears between the parent and the child. Don't try to interpret them whitout looking into the sources at the same time. Notice that in that case, one of both (period or frequency) must be always equal to 1 as it is checked here: < https://github.com/Spaghettis/Spaghettis/blob/054786098f340d8683efd9b5b5f2c1df4c8f1f56/src/dsp/graph/d_block.c#L72 >. The machinery in my fork is very closed to the Pure Data's one. The only difference is that in Spaghettis the DSP can not be stopped at the middle of the block quantum (i.e. it will wait the end of the blocksize stride to be switched off).
The arrows point to the place where the signal is put (during the prologue) into the subpatch buffer in, and from where the signal is pulled out (during the epilogue) from the subpatch buffer out. Note that each letter represents 64 samples (oops, i should have said this first).
The #numbers refer to those in the log file (i.e. starting with "BLOCK FREQUENCY") for each example.
It is the DSP tick tag (e.g. "#+0#").
The exclamation marks when the DSP is triggered (computed) in the subpatch.
On the left "a b c d e f g h i" is the signal in, whereas on the right "- - - A B C D E F"" is the signal out. That means that for the first 64 samples labeled "a" you get 64 samples of "-" (zeros). Then for "b" you have zeros again, for "c" also. For the fourth vector "d" you obtain "A" (that is the computed signal of "a").
BLOCK FREQUENCY ... OUTLET HOP
Those are messages logged in my fork when the DSP graph is builded. It helps me to understand what's going on.
< https://github.com/Spaghettis/Spaghettis/blob/054786098f340d8683efd9b5b5f2c1df4c8f1f56/src/dsp/graph/d_block.c#L85 >
BLOCK FREQUENCY is the number of times the child ticks for each DSP tick in parent.
BLOCK PERIOD at contrary is the number of times the parent ticks for one tick in child.
INLET SIZE is the size of the buffer in.
INLET WRITE is the position to write into the buffer at start.
INLET HOP ... is the hop.
OUTLET SIZE is the size of the buffer out.
OUTLET HOP ... is the hop too!
With overlap things are getting more weird. TBH i'm not a FFT guru and thus i'll not talk about the benefits of doing that. But when i started to change the DSP core, i had to be sure to not mess/break the stuff! The only way for a newbie like me was to compare the results one by one (and thus those experiments). Don't hesitate to comment if you are such a FFT native speaker.
Today the [block~ 128 2] case.
When a new vector is added, previous vector is shift left, and the new one is put at end. Thus the DSP in the reblocked subpatch is performed at each tick with a sliding window.
a ! [- a] b ! [a b] c ! [b c] d ! [c d] e ! [d e]
// [block~ 128 2] BLOCK FREQUENCY+1 // Note that 128 samples are computed here BLOCK PERIOD+1 // each time than 64 are done in parent. INLET SIZE+128 INLET WRITE+128 INLET HOP+64 // The hop size is the reason. OUTLET SIZE+128 OUTLET HOP+64 #+0# SHIFT+64/+64 // Copy previous last 64 samples to start. P+64/+64 // Write 64 samples at end. R+0/+128 W+0/+128 // Note that writes are ACCUMULATED. E+0/+64 // Read 64 samples at start (ZEORED once done). #+1# SHIFT+64/+64 P+64/+64 R+0/+128 W+64/+128 // Write 128 at 64 (returns to 0 when end is reached). E+64/+64 // Read 64 samples at end. #+2# SHIFT+64/+64 P+64/+64 R+0/+128 W+0/+128 E+0/+64 #+3# SHIFT+64/+64 P+64/+64 R+0/+128 W+64/+128 E+64/+64 #+4# SHIFT+64/+64 P+64/+64 R+0/+128 W+0/+128 E+0/+64 #+5# ...
The result of the computation is added to the buffer out one time normally [1 2], and one time reversed [2 1]. Note that it is not zeored before, but accumulated. The output vector is also swapped each time (note that it is zeroed once done).
[- a] ! [- A] #0 ^ ^ [a b] ! [B A+A'] #1 ^ ^ [b c] ! [B+B' C] #2 ^ ^ [c d] ! [D C+C'] #3 ^ ^ [d e] ! [D+D' E] #4 ^ ^
The input vs output stream is shown below. I wrote A and A' to emphasize that even if it is the same data used at entry, the output is the result of two different processings.
a b c d e / - A+A' B+B' C+C' D+D'
Here a patch to illustrate the stuff with a basic step signal (of course it has no sense by itself).
"Des questions ?"
Basic upsampling and downsampling is obvious also. The up/down sampling is done before/after (the prologue/epilogue) in anothers buffers. The DSP computation is performed in one shot with the required block size.
// [block~ 32 1 0.5] BLOCK FREQUENCY+1 BLOCK PERIOD+1 INLET SIZE+32 // Downsampling is done before. INLET WRITE+32 INLET HOP+32 OUTLET SIZE+32 // Upsampling is done after. OUTLET HOP+32 #+0# P+0/+32 // Read 32 samples already downsampled from 64. R+0/+32 W+0/+32 E+0/+32 // Write 32 samples that will be upsampled to 64. #+1# P+0/+32 R+0/+32 W+0/+32 E+0/+32
// [block~ 128 1 2] BLOCK FREQUENCY+1 BLOCK PERIOD+1 INLET SIZE+128 // Upsampling is done before. INLET WRITE+128 INLET HOP+128 OUTLET SIZE+128 // Downsampling is done after. OUTLET HOP+128 #+0# P+0/+128 R+0/+128 W+0/+128 E+0/+128 #+1# P+0/+128 // Read 128 samples already upsampled from 64. R+0/+128 W+0/+128 E+0/+128 // Write 128 samples that will be downsampled to 64.
For reblocked down (e.g. [block~32]) it is rather easy.
At each DSP tick the parent's vector (64 samples) is simply processed right away piece by piece.
// [block~ 32] BLOCK FREQUENCY+2 BLOCK PERIOD+1 INLET SIZE+64 INLET WRITE+64 INLETHOP+64 OUTLET SIZE+64 OUTLET HOP+32 #+0# P+0/+64 // Prologue: write 64 samples in the buffer in. R+0/+32 // Proceed first half part (32 samples). W+0/+32 R+32/+32 // Proceed secondth half part (32 samples). W+32/+32 E+0/+64 // Epilogue: read 64 samples from the buffer out. #+1# P+0/+64 R+0/+32 W+0/+32 R+32/+32 W+32/+32 E+0/+64
Following a previous discussion < https://forum.pdpatchrepo.info/topic/12905/bang-bangs-before-the-end-of-a-dsp-block-at-startup > i'll post there a series of examples to explain what's going on under the hood with reblocked subpatches. It is based on experiments i done with my fork, but AFAIK it is exactly the same in Pure Data (there are minor differences but IIRC it doesn't change global behaviour). Please correct me if you thing something is wrong in order to avoid to make things more impenetrable in the future that it is already now.
- Up: [block~256]
- Down: [block~ 32]
- Downsample: [block~ 32 1 0.5]
- Upsample: [block~ 128 1 2]
- Overlap: [block~ 128 2]
- Overlap: [block~ 512 4]
I do that because i always forget how it is done, and must bang my head again and again.
Thus: first reblocking up (i.e. [block~ 256])!
[---a] ! [---A] #0 ^ ^ [b---] [---A] #1 ^ ^ [bc-a] [---A] #2 ^ ^ [bcda] [---A] #3 ^ ^ [bcde] ! [BCDE] #4 ^ ^ [fcde] [-CDE] ^ ^ [fgde] [--DE] ^ ^ [fghe] [---E] ^ ^ [fghi] ! [FGHI] ^ ^
a b c d e f g h i / - - - A B C D E F
// [block~ 256] BLOCK FREQUENCY+1 BLOCK PERIOD+4 INLET SIZE+256 INLET WRITE+192 INLET HOP+256 OUTLET SIZE+256 OUTLET HOP+256 #+0# // First tick. P+192/+64 // Prologue: write 64 samples at 192 of the buffer in. R+0/+256 // Read 256 samples from the buffer in to be processed. W+0/+256 // Write the result in the buffer out. E+0/+64 // Epilogue: read 64 samples at start from the buffer out. #+1# P+0/+64 // Prologue: write 64 samples at start of the buffer in. E+64/+64 // Epilogue: read 64 samples at 64 from the buffer out. #+2# P+64/+64 E+128/+64 #+3# P+128/+64 E+192/+64 #+4# P+192/+64 R+0/+256 W+0/+256 E+0/+64 #+5# ...
But... what's in it?
After a quick look in the code it seems that switch by-passes all the synchronisation mechanism that glue the perform rate inside with buffering from outside < https://github.com/pure-data/pure-data/blob/d5766fd0600a5444a7e26754bed4f175d96ac568/src/d_ugen.c#L266 >. It could be the origin of discontinuities when it is put ON/OFF/ON. That's pure speculation that would requires more investigations.