Cabbage Logo
Back to Cabbage Site

Changing audio buffer size in ftgen?

I’m struggling to wrap my head around this :confounded:, but let’s say the loop point starts at 0.5 and reads to 1 through the ftgen. Wouldn’t this just loop the second half of the ftgen twice before the ftgen updates with new values?

gaWritePointerL  phasor  (sr/ftlen(giTableLWrite1))

;Write to tables
tablew  ainL, gaWritePointerL, giTableLWrite1, 1, 0, 1

;Reading input table with reversal
arevInputL1     table    -gaWritePointerL, giTableLWrite1, 1, 0, 1

This is how my reverse table-reading is set up atm

Are you constantly grabbing incoming audio? I thought you were simply recording a loop, and then playing it back once the recording has finished.

Constantly grabbing incoming audio yeah.

Would it make sense to increase the speed of the phasor to “simulate” the change in the ftgen size?

gaWritePointerL phasor (sr/ftlen(giTableLWrite1))*2

If you change the speed of the phasor you will change the pitch of the audio. I wouldn’t use a phasor at all. Although I’m sure it would be possible with a phasor. :thinking: I don’t have time to prepare something tonight, but just so we’re clear, you want a signal reverser that also provide pitch controls?

Sorry that I explain this so crappy. What I’m trying to do is not providing pitch controls, but just letting the user be able to choose different lengths of the audio buffer for audio reversal. The length of the ftgen normally decides this right? The less amount of samples in a ftgen, the less time it takes before the reversed audio starts playback, giving the user a bit control of how long each reversed chunk of audio is.

CrapDrawing

Kilohearts Reverser and Reverse by Initial Audio does this

Something like this right?

<Cabbage>
form caption("Untitled") size(400, 300), guiMode("queue") pluginId("def1")
rslider bounds(296, 162, 100, 100), channel("BufferSize"), range(-2, -1, -2, 1, 1), text("BufferSize"), trackerColour("lime"), outlineColour(0, 0, 0, 50), textColour("black")
hslider bounds(129, 67, 150, 50) channel("hslider10000") range(0, 1, 0, 1, 0.001)
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d 
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1

giTableLen1 = 2^16

giTable  ftgen 1,0, giTableLen1 ,2,0 ;Audio left

instr 1
ainL inch 1

kbuffer cabbageGetValue "BufferSize"
abuffer upsamp kbuffer
abuffer tone abuffer, 10

;Write-pointer with customizable buffer-size
gaWritePointer  phasor  (sr/ftlen(giTable))*(1/abs(abuffer))

;Write to tables
tablew  ainL, gaWritePointer, giTable, 1, 0, 1

;Reading input table with reversal
arevInputL1     table    -gaWritePointer, giTable, 1, 0, 1

;Write writepointer to horizontal slider
kWritePointer downsamp gaWritePointer
cabbageSetValue "hslider10000", kWritePointer

;out stereo
outs arevInputL1, arevInputL1

endin

</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
;starts instrument 1 and runs it for a week
i1 0 [60*60*24*7] 
</CsScore>
</CsoundSynthesizer>

Should be correct? Any improvements? (Ignoring the clicking at start/end) The drop/rise in pitch when changing the size I think is fine.

Here’s one with a phasor that doesn’t change the playback speed. It also needs some windowing but maybe it’s something you can work with.

backwardsSig.csd (1.2 KB)

Nice one, but instead of “changing the buffer size”, at smaller sizes it loops the audio over and over again creating a stutter effect. While I think in the example I posted you can change the size without creating this stuttering? Sounds pretty cool though

<Cabbage>
form caption("Reverse") size(400, 300), guiMode("queue"), colour(58, 110, 182), pluginId("def1")
rslider bounds(26, 28, 60, 60) channel("length") range(0, 2, 1, 1, 0.001), text("Length")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d --midi-key=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>
ksmps = 32
nchnls = 2
0dbfs = 1


giTable ftgen  91, 0, 32768*2, 7, 0, 32768*2, 0
giEnvTable ftgen  92, 0, 32768*2, 9, 0.5, 1, 0

opcode Reverse, a, aiik

    setksmps 1

    aIn, iFn, iEnvTable, kLength  xin

    kWritePointer init 0
    kReadPointer init 0

    kTableLength = ftlen(iFn)*kLength
    tablew aIn, a(kWritePointer), iFn  
    aSignal table  ftlen(iFn)-a(kReadPointer), iFn
    
    kWritePointer = kWritePointer < ftlen(iFn) ? kWritePointer + 1 : 0
    kReadPointer = kReadPointer < kTableLength ? kReadPointer + 1 : 0

    xout aSignal

endop

instr 1
    a1 inch 1
    a2 inch 2
    
    aRevL Reverse a1, giTable, giEnvTable,chnget:k("length") 
    aRevR Reverse a2, giTable, giEnvTable,chnget:k("length") 
    
    outs aRevL, aRevR
endin


</CsInstruments>
<CsScore>
f1 0 4096 10 1 .5
;causes Csound to run for about 7000 years...
i1 0 z
f0 z
</CsScore>
</CsoundSynthesizer>

That’s true, because it is still writing to the full length table. Plus, I’m always reading from the end of the table back to the start point. I guess if you only wrote from the start point to the end of the table you wouldn’t get the stutter. :thinking:

<Cabbage>
form caption("Reverse") size(400, 300), guiMode("queue"), colour(58, 110, 182), pluginId("def1")
rslider bounds(26, 28, 60, 60) channel("length") range(0, 1, 1, 1, 0.001), text("Length")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d --midi-key=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>
ksmps = 32
nchnls = 2
0dbfs = 1

giTable ftgen  91, 0, 32768*2, 7, 0, 32768*2, 0
giEnvTable ftgen  92, 0, 32768*2, 9, 0.5, 1, 0

opcode Reverse, a, aiik

    setksmps 1

    aIn, iFn, iEnvTable, kLength  xin

    kWritePointer init 0
    kReadPointer init 0

    kTableLength = ftlen(iFn)*kLength
    tablew aIn, a(kWritePointer), iFn  
    aSignal table  ftlen(iFn)-a(kReadPointer), iFn
    
    kWritePointer = kWritePointer < ftlen(iFn) ? kWritePointer + 1 : (0 + (ftlen(iFn) - ftlen(iFn)*kLength))
    kReadPointer = kReadPointer < kTableLength ? kReadPointer + 1 : 0
    
    printk 1, kWritePointer

    xout aSignal

endop

instr 1
    a1 inch 1
    a2 inch 2
    
    aRevL Reverse a1, giTable, giEnvTable,chnget:k("length") 
    aRevR Reverse a2, giTable, giEnvTable,chnget:k("length") 
    
    outs aRevL, aRevR
endin


</CsInstruments>
<CsScore>
f1 0 4096 10 1 .5
;causes Csound to run for about 7000 years...
i1 0 z
f0 z
</CsScore>
</CsoundSynthesizer>

Something like this? Wouldn’t I need to move the start reading point too? If you make the length short with this code, you can still hear some of the non-reversed audio going through.

Here’s one that doesn’t give that stuttering effect. There is an envelop in there too that you can uncomment to hear.

<Cabbage>
form caption("Reverse") size(400, 300), guiMode("queue"), colour(58, 110, 182), pluginId("def1")
rslider bounds(26, 28, 60, 60) channel("length") range(0, 2, 1, 1, 0.001), text("Length")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d --midi-key=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>
ksmps = 32
nchnls = 2
0dbfs = 1

giTable ftgen  91, 0, 32768*2, 7, 0, 32768*2, 0
giEnvTable ftgen  92, 0, 32768*2, 9, 0.5, 1, 0

opcode Reverse, a, aiik

    setksmps 1

    aIn, iFn, iEnvTable, kLength  xin

    kWritePointer init 0
    kReadPointer init 0

    kTableLength = ftlen(iFn)*kLength
    tablew aIn, a(kReadPointer), iFn  
    aSignal table  kTableLength-a(kReadPointer), iFn
    
    kWritePointer = kWritePointer < kTableLength ? kWritePointer + 1 : 0
    kReadPointer = kReadPointer < kTableLength ? kReadPointer + 1 : 0

;    aEnvPhs phasor sr/kTableLength
;    aEnv table aEnvPhs, iEnvTable, 1
;    xout aSignal*aEnv
    xout aSignal

endop

instr 1
    a1, a2 diskin2 "DutchLadyTalking.aif", 1, 0, 1
    aRev Reverse a1, giTable, giEnvTable, chnget:k("length")
    outs aRev, aRev
endin


</CsInstruments>
<CsScore>
f1 0 4096 10 1 .5
i1 0 z
</CsScore>
</CsoundSynthesizer>

Nice one! Ksmps is the number of samples in a control period, but what exactly is a control period, and why are you setting it to 1 instead of 32?

I’ve heard that setting ksmps to a smaller number increases CPU-usage? Is this true? Anyway around that in this case or should I just live with it?

It’s how often k-rate signals get updated.

The smaller the number of audio samples in your control period the quicker your k-rate variables are updated. So smaller control sizes lead to more calculations a second, which leads to more CPU being used.

I’m setting it to 1 so that I can operate on a sample by sample basic with a k-rate variable. This is not possible with audio rate variables because they are vectors (a bunch of values), where k vars are scalars (a single value).

Using UDOs means you can use a higher ksmps for a particular operation. The global ksmps will still be applied everywhere else in the code. This helps to stop your instruments becoming to CPU hungry, although in this instance you have nothing to work about. This little UDO isn’t going to destroy any machines. :laughing:

1 Like

Thanks for the info!

1 Like

If I wanted to add a X-fade between two a-rate variables, normally you would just add a offset to the table-opcode, add the tables together and it would be fine. However it seems to be a bit more tricky here because of the kLength variable. It seems like the ixoff-parameter in the table-opcode only accepts raw integers and floats and ftlen() of tables, which makes sense since it is i-rate…

Do you have any suggestion for how one could do this? Maybe another table opcode that could do the offset at k-rate?

Normally I would do something like this while normalizing the table values from 0-1:

aSignalXFade table gkTableLength-a(kReadPointer), iFn, 1, 0.5, 0

But since the ftgen isn’t really changing size, it screws up the offset timing when changing kLength…

Just use the same method I used in my example and offset one of the phasers by half a phase.

You don’t mean like this do you?

aEnvPhs phasor sr/gkTableLength
aEnv1 table aEnvPhs, iEnvTable, 1
aEnv2 table aEnvPhs, iEnvTable, 1, 0.5
xout aSignal*aEnv1 + aSignal*aEnv2

How would you offset the phasor by half a phase without the phasor going under 0 or beyond 1?

aEnvPhs phasor sr/gkTableLength, 0.5

That will cause it to start at half phase, i.e, 0.5. So use two:

aEnvPhs1 phasor sr/gkTableLength
aEnvPhs2 phasor sr/gkTableLength, 0.5

*iphs* (optional) -- initial phase

:see_no_evil:

I need to start reading more thoroughly…

1 Like

Does this seem reasonable then?

<Cabbage>
form caption("Reverse") size(400, 300), guiMode("queue"), colour(58, 110, 182), pluginId("def1")
rslider bounds(60, 30, 60, 60) channel("length") range(0.1, 1, 1, 1, 0.1), text("Length")
gentable bounds(110, 134, 198, 108), tableNumber(93.0)

</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d --midi-key=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>
ksmps = 32
nchnls = 2
0dbfs = 1

giTableLen = 2^16

giTableL ftgen  91, 0, giTableLen, 7, 0, giTableLen, 0
giTableR ftgen  92, 0, giTableLen, 7, 0, giTableLen, 0

giEnvTable ftgen  93, 0, giTableLen, 9, 0.5, 1, 0

opcode Reverse, a, aiik

    setksmps 1

    aIn, iFn, iEnvTable, kLength  xin

    kWritePointer init 0
    kReadPointer init 0

    gkTableLength = ftlen(iFn)*kLength
    tablew aIn, a(kReadPointer), iFn  
    aSignal table  a(gkTableLength)-a(kReadPointer), iFn
    
    kWritePointer = kWritePointer < gkTableLength ? kWritePointer + 1 : 0
    kReadPointer = kReadPointer < gkTableLength ? kReadPointer + 1 : 0
    
    aEnvPhs1 phasor sr/gkTableLength
    aEnvPhs2 phasor sr/gkTableLength, 0.5
    aEnv1 table aEnvPhs1*2, iEnvTable, 1
    aEnv2 table aEnvPhs2*2, iEnvTable, 1
    xout aSignal*aEnv1 + aSignal*aEnv2

endop

instr 1

kLength cabbageGetValue "length"

a1 inch 1
a2 inch 2

aRevL Reverse a1, giTableL, giEnvTable, kLength
aRevR Reverse a2, giTableR, giEnvTable, kLength

outs aRevL, aRevR
endin

</CsInstruments>
<CsScore>
i1 0 [60*60*24*7] 
</CsScore>
</CsoundSynthesizer>

I notice sometimes when you change the length-parameter, it seems like either the audio table or the window shape envelope get’s thrown off, since it creates a small click at the start of each loop. Does something need to be upsampled or interpolated?