{
   "labels" : [
      "glitch",
      "looper",
      "repeat"
   ],
   "id" : "1-5if",
   "is_private" : null,
   "code" : "//by Santi Vilanova\r\n//October 2024\r\n//www.playmodes.com\r\n\r\n(\r\nSynthDef(\\beatrepeat, {\r\n    arg in=0, out=0, bufferSize=4;\r\n    var onoff=\\go.kr(0), rate=\\rate.kr(1);\r\n    var reset=\\reset.kr(0);  // Reset parameter\r\n    var amp=1;\r\n    var recms=\\repms.kr(1000);\r\n    var gate=1-\\gate.kr(1);\r\n    var ratestep=\\pitchstep.kr(0);\r\n    var reverse=((1-\\reverse.kr(0))*2)-1;\r\n    var envtime;\r\n    var input, buffer, playhead, output, loopOutput;\r\n    var recTrig, recTime, playTrig, bufferEnd, isLooping;\r\n    var fixedTimeGate, fixedTimeEnd;\r\n    var numChannels = 2;\r\n    var mixPhase, resetTrig;\r\n    var declickEnv;\r\n    var loopPhase;\r\n    var mix, play, wetGate;\r\n    var bufferPlayRate, actualPlayPos, loopDur, loopReset, phasor, combinedResetTrig;\r\n    var declick = \\declick.kr(0.005);  // Declick time in seconds, default to 5ms\r\n    var loopCount, currentRate;\r\n    var onoffTrig;\r\n\tvar mixedOutput;\r\n\tvar transitionEnv;\r\n\tvar ramp;\r\n\r\n    // Create a local buffer\r\n    buffer = LocalBuf(SampleRate.ir * bufferSize, numChannels).clear;\r\n\r\n    // Input signal\r\n    input = In.ar(in, numChannels);\r\n\r\n    // Detect start of recording and create reset trigger\r\n    onoffTrig = Trig1.kr(onoff);\r\n    resetTrig = Trig1.kr(reset);\r\n    recTrig = onoffTrig;\r\n\r\n    // Combined reset trigger for both 'go' and 'reset'\r\n    combinedResetTrig = Trig1.ar(K2A.ar(onoff) + K2A.ar(reset), ControlDur.ir);\r\n\r\n    // Create a gate that closes after fixed time\r\n    fixedTimeGate = EnvGen.kr(Env([0, 1, 0], [0, recms/1000], ['step', 'step']), recTrig);\r\n    fixedTimeEnd = TDelay.kr(1 - fixedTimeGate, recms/1000);\r\n\r\n    // Link mix to onoff, but delayed by recms\r\n    mix = DelayN.kr(onoff, recms/1000, recms/1000);\r\n\r\n    // Create a gate for the wet signal that opens after recms\r\n    wetGate = DelayN.kr(onoff, recms/1000, recms/1000);\r\n\r\n    // Link play to onoff with delay\r\n    play = onoff;\r\n\r\n    // Detect end of recording\r\n    playTrig = Trig1.kr(fixedTimeEnd);\r\n\r\n    // Determine buffer end and loop duration\r\n    bufferEnd = recms / 1000;\r\n    loopDur = bufferEnd;\r\n\r\n    // Record to buffer\r\n    RecordBuf.ar(input, buffer, loop: 0, trigger: recTrig);\r\n\r\n    // Create a loop reset trigger using Phasor\r\n    phasor = Phasor.ar(combinedResetTrig, 1 / (SampleRate.ir * loopDur), 0, 1);\r\n    loopReset = (phasor - Delay1.ar(phasor) > 0).clip(0, 1);\r\n\r\n    // Count loop cycles, resetting when either 'go' or 'reset' triggers\r\n    loopCount = PulseCount.ar(loopReset, combinedResetTrig);\r\n\r\n    // Calculate current rate based on ratestep (in semitones)\r\n    currentRate = rate * (2 ** (ratestep * loopCount / 12));\r\n\r\n    // Calculate buffer play rate\r\n    bufferPlayRate = currentRate * BufRateScale.kr(buffer);\r\n\r\n    // Playhead for buffer playback\r\n    playhead = Phasor.ar(\r\n        trig: loopReset,\r\n        rate: bufferPlayRate * reverse,\r\n        start: 0,\r\n        end: bufferEnd * SampleRate.ir,\r\n        resetPos: 0\r\n    );\r\n\r\n    // Calculate actual play position, clamping for rates < 1\r\n    actualPlayPos = if(\r\n        currentRate < 1,\r\n        playhead.clip(0, bufferEnd * currentRate * SampleRate.ir),\r\n        playhead % (bufferEnd * SampleRate.ir)\r\n    );\r\n\r\n    // Create the envelope\r\n    envtime = (recms/1000) - (2 * declick);\r\n    declickEnv = EnvGen.ar(\r\n        Env(\r\n            levels: [0, 1, 1, 0],\r\n            times: [declick, envtime-(envtime*gate), declick],\r\n            curve: [\\lin, \\step, \\lin]\r\n        ),\r\n        gate: loopReset,\r\n        levelScale: 1,\r\n        levelBias: 0,\r\n        timeScale: 1\r\n    );\r\n\r\n    // Play from buffer only if we're looping\r\n    loopOutput = BufRd.ar(numChannels, buffer, actualPlayPos, loop: 1) * play * declickEnv * wetGate;\r\n\r\n    // Mix input and loop output\r\n    mixedOutput = (input * (1 - mix)) + (loopOutput * mix);\r\n\r\n    // Create a fast ramping envelope for smooth transitions\r\n    transitionEnv = Env.asr(declick, 1, declick, 'sine');\r\n    ramp = EnvGen.ar(transitionEnv, K2A.ar(onoff));\r\n\r\n    // Use SelectX for smooth transition between dry and wet signals\r\n    output = SelectX.ar(ramp, [input, mixedOutput]);\r\n\r\n    Out.ar(out, output * amp);\r\n}).add();\r\n)",
   "author" : "santi",
   "name" : "beat repeat with gate and pitch increment",
   "ancestor_list" : [],
   "description" : "Use this synthdef as an effect and just make the \\go parameter from 0 to 1 to activate. \r\nWhen 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.\r\nThe \\gate parameter controls how much silence is left at the end of the chunk, for a strong gate effect.\r\n\\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)\r\n\\pitchstep will make that every new loop starts with an increment/decrement of semitones defiend by this parameter\r\n\\declick...well... declicks....\r\nand \\reverse reverses the played chunk"
}
