Cabbage Logo
Back to Cabbage Site

Audio retrigger effect in Csound

I don’t know if Csound can do this, but I would like to make an audio effect that slices/chops/retriggers audio coming in and replays back it in a loop at a defined rate. As in, if I play 4 even notes in one measure, the enabled effect would replay the first note, and could replay that same note twice or four times as fast until the next measure.

I imagine I would need to tell Csound to sample the audio input into an a-rate buffer variable or something and have it loop that recorded sample into the output until the defined rate makes it go back to the audio input and resample the next incoming values.

How could I accomplish this?

Okay, so I dug around in Csound, and found bbcuts and sndloop. bbcuts is alright, but not exactly what I want, and sndloop is more like it.

For anyone interested, if you want to make this effect and have it be dynamic, you have to set ktrig to have an automatic retrigger. This can be done with a metronome by setting it to 1 - metro(1) or something similar, to constantly pass 1s and 0s. I found that a range from metro(1) to metro(8) works best. Anything higher causes some… questionable stuff.

Full code example:

; get audio input
a1 inch 1
a2 inch 2

if kEnabled == 1 then ;perform retrigger
    ; audio_out, recording_indicator sndloop audio_in, pitch, trigger, duration, crossfade
    ; rates are ak sndloop akkii
    a1, krec1 sndloop a1, 1, 1 - metro(1), 0.1, 0.05
    a2, krec2 sndloop a2, 1, 1 - metro(1), 0.1, 0.05
endif

;send audio output
outs a1, a2

I just wish you could pass a k-rate variable for the duration and crossfade. Or be able to get a k-rate value once for an i-rate variable.

You might get some mileage out of this UDO. I wanted to create that retrigger slice effect you get in audio trackers.

/*
trackerSplice - signal splicer

DESCRIPTION
This UDO performs realtime re-triggering and reversing of samples in a style similar to that found on most audio trackers. The main difference here is that the samples are being created on the fly during run-time.  

SYNTAX
ares trackerSplice asig, ksegLength, kmode

PERFORMANCE
asig - input signal
ksegLength - length of re-triggered sample in seconds, for now max=1, if you need longer just change the size of your function table 
kmode - either 0, 1, 2 depending on what you want. 0 does no processing, 1 will re-trigger and 2 will reverse  

CREDITS
Rory Walsh. Mar 2011
*/

opcode trackerSplice, a, akk
asig, kseglength, kmode xin

setksmps 1
kindx init 0
ksamp init 1
aout init 0

itbl ftgenonce 0, 0, 2^16, 7, 0, 2^16, 0	;create table to hold samples
kseglength = kseglength*sr			;convert length to samples
andx phasor sr/ftlen(itbl)			;ensure phasor is set to correct freq
tabw asig, andx*ftlen(itbl), itbl		;write signal to table
andx1 delay andx, 1				;insert a 1 sample delay so that the read point
						;always stays one sample behind the write pointer
apos samphold andx1*ftlen(itbl), ksamp		;hold sample position whem ksamp=0

if(kmode>=1 && kmode <2) then 				;do retrigger when kmode==1
	kpos downsamp apos
	kindx = (kindx>kseglength ? 0 : kindx+1)
	if(kindx+kpos> ftlen(itbl)) then
	kindx = -kseglength
	endif
	aout table apos+kindx, itbl, 0, 1
	ksamp = 0

elseif(kmode>=2 && kmode<3) then				;do reverse when kmode==2 
	kpos downsamp apos
	kindx = ((kindx+kpos)<=0 ? ftlen(itbl)-kpos : kindx-1)
	aout table apos+kindx, itbl, 0, 1
	ksamp = 0

else 						;when kmode==0 simple pass signal through
	ksamp = 1
	aout = asig
endif
xout aout
endop
2 Likes

This… is amazing. I will use this instead and credit you with a link back to this page once I get this plugin finalized.

Another question, how do I get i-rate variables out of k-rate values like knobs? Is there any way to map the duration and crossfade of sndloop? I saw chnget:i("example") in another topic, would this work?

You can use an i-rate version of cabbagetGetValue or as you’ve shown, chnget

instr 1
    iAtt cabbageGetValue "attack"
    kEnv madsr iAtt, .2, .6, .4
    aOut vco2 p5, p4
    outs aOut*kEnv, aOut*kEnv
endin
1 Like

I wanted to try the trackerSplice, I’m using it like this:

instr GLOBAL_OUTPUT

    kAmp portk gkAmp, 0.1
    kState portk gkState, 0.1
    kRandLength init 1   ; = int(rnd(16))
    kSegLength init 0.5   ;max(gkBPM / 60 / kRandLength, 1)
    kRand = rnd(kState)
    if (kRand < 0.2) then
        kMode = 0
    else
        kRand2 = rnd(1)
        if kRand2 > 0.5 then
            kMode = 2
        else
            kMode = 1
        endif
    endif
    
    aOutL   trackerSplice gaGlobalOutputL, .5, kMode
    aOutR   trackerSplice gaGlobalOutputR, .5, kMode
    
    outs aOutL * kAmp, aOutR * kAmp
    ;outs gaGlobalOutputL * kAmp, gaGlobalOutputR * kAmp
    clear gaGlobalOutputL, gaGlobalOutputR
    
endin

but when it activates I get lots of noise. gaGlobalOutputL/R is my global audio bus (other instruments are writing into it) that should go out to the speakers.
I tried several kSegLength as you can see in my attempts above (now I hardcode a 0.5 length but still no luck)
What am I doing wrong?

Edit: mm I am starting to realize that maybe using it with a global audio variable is itself the cause of the noise :sob:

Edit2: FYI I also had to swap ftgenonce with ftgen since I don’t want to use plugins, I’m on CsoundUnity. Not sure if that can be the cause of the issue instead. Will try again tomorrow passing an existing function as parameter to the opcode

I tried taking out the table and pass it as a parameter, I tried writing to it from the instrument instead of the UDO, but still I get the same noise.
I tried feeding it with a simple sine like this:

aOutL trackerSplice oscili(0.4, 440, giSine), .5, kMode ; moving kMode with a slider between 0, 1 and 2

but still I get only noise when it activates.
Any clues? I’m out of ideas :man_shrugging:

Sorry @giovannibedetti, I thought you had this sorted out. Here is a version using a simple oscillator as source.

trackerSplice.csd (2.5 KB)

It’s incredible, I tried the same exact code and it doesn’t work :confused:
I will send you a PM since it’s a massive csd (I’m not ready to share it publicly yet)

So the issue is in how I set the kMode, but I’m not sure why that is an issue.
If I hardcode 0 1 or 2 all is good. But if I switch through them with the code above it is not :confused:

Ok so the issue is changing the mode too quickly (caused by random)! Fixed, thanks!

I was going to suggest adding a printk2 to see if the random was firing too quickly. But to be fair, you brought me right to the solution when you said it was something to do with kMode :rofl:

1 Like