; REBEAT
;
; simple pedal-like looper
;
; code based on TabRecPlay example by Iain McCurdy

; (s) Severák 2019
; licensed with MIT license
<Cabbage>
form caption("REBEAT") size(300, 200), colour(58, 110, 182), pluginid("rebe")
button bounds(48, 164, 192, 27) text("REBEAT", "REBEAT")  channel("rebeat") identchannel("rebeatWidget") fontcolour:0(128, 128, 128, 255)
gentable bounds(2, 6, 296, 131) identchannel("gentable") tablenumber(101.0)
label bounds(50, 144, 191, 16) text("waiting...") identchannel("statusbar")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d 
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1

gistorageL	ftgen	0,0,1048576,-7,0	;AUDIO DATA STORAGE SPACE (ABOUT 23 SECONDS)
gistorageR	ftgen	0,0,1048576,-7,0	;AUDIO DATA STORAGE SPACE (ABOUT 23 SECONDS)
gkRecDur	init	0

; ---
gkRecord init 0
gkPause init 0
gkPlayLoop init 0

; ---
gkInGain init 1
gkLoopBeg init 0
gkLoopEnd init 1
gkSpeed init 1
gkOutGain init 1


instr GUI
    ; 0 = stopped; 1 = recording; 2 = looping
    kStatus init 0
    kRebeat chnget "rebeat"
    Scolor init ""
    
    gitablelen	=	ftlen(gistorageL)
    
    if changed:k(kRebeat)==1 then
        if kStatus==0 then
            chnset "text(\"recording...\")", "statusbar"
            Scolor strcatk "fontcolour:0(255, 0, 0, 255), fontcolour:1(255, 0, 0, 255)", ""
            kStatus = 1
            event "i", "Record", 0, 23
            printks "recording...\n", 0
        elseif kStatus==1 then
        	chnset "text(\"playing...\")", "statusbar"
            Scolor strcatk "fontcolour:0(0, 100, 0, 255), fontcolour:1(0, 100, 0, 255)", ""
            kStatus = 2
            printks "playing...\n", 0
            gkPlayLoop = 1
            event "i", "PlayLoop", 0, 3600
        elseif kStatus==2 then
            chnset "text(\"waiting...\")", "statusbar"
            Scolor strcatk "fontcolour:0(128,128,128,255),fontcolour:1(128,128,128,255)", ")"
            kStatus = 0
            gkPlayLoop = 0
            printks "waiting...\n", 0
        endif

        chnset Scolor, "rebeatWidget"
        
    endif
endin

;instr setTable
;    Stablenum sprintf "tablenumber(%d)", gistorageL
;    printks "tablenumber(%d)", 0, gistorageL
;    chnset Stablenum, "gentable"
;endin

instr	Record
        if gkPlayLoop == 1 then
            turnoff
        endif
        if	gkPause == 1	goto SKIP_RECORD		;IF PAUSE BUTTON IS ACTIVATED TEMPORARILY SKIP RECORDING PROCESS
        ;printks "rec...\n", 2

	    ainL,ainR	ins					;READ AUDIO FROM LIVE INPUT CHANNEL 1
	    aRecNdx	line		0,gitablelen/sr,1	;CREATE A POINTER FOR WRITING TO TABLE - FREQUENCY OF POINTER IS DEPENDENT UPON TABLE LENGTH AND SAMPLE RATE
	    aRecNdx	=		aRecNdx*gitablelen	;RESCALE POINTER ACCORDING TO LENGTH OF FUNCTION TABLE 
	    gkRecDur	downsamp	aRecNdx			;CREATE A K-RATE GLOBAL VARIABLE THAT WILL BE USED BY THE 'PLAYBACK' INSTRUMENT TO DETERMINE THE LENGTH OF RECORDED DATA			
	  		tablew		ainL*gkInGain,  aRecNdx, gistorageL;WRITE AUDIO TO AUDIO STORAGE TABLE
	  		tablew		ainR*gkInGain,  aRecNdx, gistorageR;WRITE AUDIO TO AUDIO STORAGE TABLE
	
	    ; scruber
	    Sscruber sprintfk "scrubberposition(%d, %d), tablenumber(%d)", k(aRecNdx), k(gistorageL), k(gistorageL)
	    chnset Sscruber, "gentable"
	
	    if	gkRecDur>=gitablelen	then			;IF MAXIMUM RECORD TIME IS REACHED...
		    kRecord=0
		    printks "limit reached!\n", 0
		    turnoff
	    endif						;END OF CONDITIONAL BRANCH
	    SKIP_RECORD:
endin

instr	PlayLoop
	if gkPlayLoop==0 then
	 turnoff
	endif
	if	gkPause=1	goto SKIP_PLAY_LOOP			;IF PAUSE BUTTON IS ACTIVATED SKIP ALL RECORDING AND PLAYBACK...


	kporttime	linseg	0,0.001,0.05			;PORTAMENTO TIME RAMPS UP RAPIDLY TO A HELD VALUE
	kLoopBeg	portk	gkLoopBeg, kporttime		;APPLY PORTAMENTO SMOOTHING TO CHANGES OF LOOP BEGIN SLIDER
	kLoopEnd	portk	gkLoopEnd, kporttime		;APPLY PORTAMENTO SMOOTHING TO CHANGES OF LOOP END SLIDER
	kLoopBeg	=	kLoopBeg * gkRecDur		;RESCALE gkLoopBeg (RANGE 0-1) TO BE WITHIN THE RANGE 0-FILE_LENGTH. NEW OUTPUT VARIABLE kLoopBeg.
	kLoopEnd	=	kLoopEnd * gkRecDur		;RESCALE gkLoopEnd (RANGE 0-1) TO BE WITHIN THE RANGE 0-FILE_LENGTH. NEW OUTPUT VARIABLE kLoopEnd.
	kLoopLen	=	kLoopEnd - kLoopBeg		;DERIVE LOOP LENGTH FROM LOOP START AND END POINTS
	kPlayPhasFrq	divz	gkSpeed,    (kLoopLen/sr),       0.00001	;SAFELY DIVIDE, PROVIDING ALTERNATIVE VALUE INCASE DENOMINATOR IS ZERO 
	aPlayNdx		phasor	kPlayPhasFrq				;DEFINE PHASOR POINTER FOR TABLE INDEX
	kLoopBeg	=	(kLoopBeg < kLoopEnd ? kLoopBeg : kLoopEnd)	;CHECK IF LOOP-BEGINNING AND LOOP-END SLIDERS HAVE BEEN REVERSED
	aLoopLen	interp	abs(kLoopLen)
	aLoopBeg	interp	kLoopBeg
	aPlayNdx	=	(aPlayNdx*aLoopLen) + aLoopBeg	;RESCALE INDEX POINTER ACCORDING TO LOOP LENGTH AND LOOP BEGINING
	
	; scruber
	Sscruber sprintfk "scrubberposition(%d, %d), tablenumber(%d)", k(aPlayNdx), k(gistorageL), k(gistorageL)
	chnset Sscruber, "gentable"
	
	aL		tablei	aPlayNdx,    gistorageL		;READ AUDIO FROM AUDIO STORAGE FUNCTION TABLE
	aR		tablei	aPlayNdx,    gistorageR		;READ AUDIO FROM AUDIO STORAGE FUNCTION TABLE
	    		outs	aL*gkOutGain,aR*gkOutGain		;SEND AUDIO TO OUTPUTS

	SKIP_PLAY_LOOP:
endin

</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
;starts instrument 1 and runs it for a week
i "GUI" 0 z
;i "setTable" 0 1
</CsScore>
</CsoundSynthesizer>
