«beat repeat with gate and pitch increment» by santi

on 11 Oct'24 02:52 in glitchlooperrepeat

Use this synthdef as an effect and just make the \go parameter from 0 to 1 to activate. When you transition from 0 to 1, it will record a buffer of length \repms (in miliseconds) and will loop this recorded chunk for as long as \go=1. The \gate parameter controls how much silence is left at the end of the chunk, for a strong gate effect. \rate allows you to change the speed of the sampled chunk, without affecting the loop duration (it will omit part of the end of the loop for rates <1, and it will loop for rates>1, resyncing when \repms time has passed) \pitchstep will make that every new loop starts with an increment/decrement of semitones defiend by this parameter \declick...well... declicks.... and \reverse reverses the played chunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//by Santi Vilanova
//October 2024
//www.playmodes.com

(
SynthDef(\beatrepeat, {
    arg in=0, out=0, bufferSize=4;
    var onoff=\go.kr(0), rate=\rate.kr(1);
    var reset=\reset.kr(0);  // Reset parameter
    var amp=1;
    var recms=\repms.kr(1000);
    var gate=1-\gate.kr(1);
    var ratestep=\pitchstep.kr(0);
    var reverse=((1-\reverse.kr(0))*2)-1;
    var envtime;
    var input, buffer, playhead, output, loopOutput;
    var recTrig, recTime, playTrig, bufferEnd, isLooping;
    var fixedTimeGate, fixedTimeEnd;
    var numChannels = 2;
    var mixPhase, resetTrig;
    var declickEnv;
    var loopPhase;
    var mix, play, wetGate;
    var bufferPlayRate, actualPlayPos, loopDur, loopReset, phasor, combinedResetTrig;
    var declick = \declick.kr(0.005);  // Declick time in seconds, default to 5ms
    var loopCount, currentRate;
    var onoffTrig;
	var mixedOutput;
	var transitionEnv;
	var ramp;

    // Create a local buffer
    buffer = LocalBuf(SampleRate.ir * bufferSize, numChannels).clear;

    // Input signal
    input = In.ar(in, numChannels);

    // Detect start of recording and create reset trigger
    onoffTrig = Trig1.kr(onoff);
    resetTrig = Trig1.kr(reset);
    recTrig = onoffTrig;

    // Combined reset trigger for both 'go' and 'reset'
    combinedResetTrig = Trig1.ar(K2A.ar(onoff) + K2A.ar(reset), ControlDur.ir);

    // Create a gate that closes after fixed time
    fixedTimeGate = EnvGen.kr(Env([0, 1, 0], [0, recms/1000], ['step', 'step']), recTrig);
    fixedTimeEnd = TDelay.kr(1 - fixedTimeGate, recms/1000);

    // Link mix to onoff, but delayed by recms
    mix = DelayN.kr(onoff, recms/1000, recms/1000);

    // Create a gate for the wet signal that opens after recms
    wetGate = DelayN.kr(onoff, recms/1000, recms/1000);

    // Link play to onoff with delay
    play = onoff;

    // Detect end of recording
    playTrig = Trig1.kr(fixedTimeEnd);

    // Determine buffer end and loop duration
    bufferEnd = recms / 1000;
    loopDur = bufferEnd;

    // Record to buffer
    RecordBuf.ar(input, buffer, loop: 0, trigger: recTrig);

    // Create a loop reset trigger using Phasor
    phasor = Phasor.ar(combinedResetTrig, 1 / (SampleRate.ir * loopDur), 0, 1);
    loopReset = (phasor - Delay1.ar(phasor) > 0).clip(0, 1);

    // Count loop cycles, resetting when either 'go' or 'reset' triggers
    loopCount = PulseCount.ar(loopReset, combinedResetTrig);

    // Calculate current rate based on ratestep (in semitones)
    currentRate = rate * (2 ** (ratestep * loopCount / 12));

    // Calculate buffer play rate
    bufferPlayRate = currentRate * BufRateScale.kr(buffer);

    // Playhead for buffer playback
    playhead = Phasor.ar(
        trig: loopReset,
        rate: bufferPlayRate * reverse,
        start: 0,
        end: bufferEnd * SampleRate.ir,
        resetPos: 0
    );

    // Calculate actual play position, clamping for rates < 1
    actualPlayPos = if(
        currentRate < 1,
        playhead.clip(0, bufferEnd * currentRate * SampleRate.ir),
        playhead % (bufferEnd * SampleRate.ir)
    );

    // Create the envelope
    envtime = (recms/1000) - (2 * declick);
    declickEnv = EnvGen.ar(
        Env(
            levels: [0, 1, 1, 0],
            times: [declick, envtime-(envtime*gate), declick],
            curve: [\lin, \step, \lin]
        ),
        gate: loopReset,
        levelScale: 1,
        levelBias: 0,
        timeScale: 1
    );

    // Play from buffer only if we're looping
    loopOutput = BufRd.ar(numChannels, buffer, actualPlayPos, loop: 1) * play * declickEnv * wetGate;

    // Mix input and loop output
    mixedOutput = (input * (1 - mix)) + (loopOutput * mix);

    // Create a fast ramping envelope for smooth transitions
    transitionEnv = Env.asr(declick, 1, declick, 'sine');
    ramp = EnvGen.ar(transitionEnv, K2A.ar(onoff));

    // Use SelectX for smooth transition between dry and wet signals
    output = SelectX.ar(ramp, [input, mixedOutput]);

    Out.ar(out, output * amp);
}).add();
)
raw 4150 chars (focus & ctrl+a+c to copy)
reception
comments