<CsoundSynthesizer>
<CsOptions>
</CsOptions>
<CsInstruments>

; This is a collection of various "autotune" patches. "autotune" is an 
; effect that pitch-shifts a monophonous input to the nearest halftone.
; 
; Autotune is either useful to correct intonation problems (eg. for
; Karaoke or Instrumental tracks that might be out-of-tune by instrumental
; principle - a violinist playing solo eg. might get out-of-tune by a few 
; cents during a longer piece due to lack of reference) or, with extreme 
; settings ("strength" close to 1, decays low) as an effect that gives the 
; sample a mechanical ("computer voice") feeling. 
;
; The Song "Believe" by "Cher", which, despite being poison to your ears, 
; was very popular in Germany, used an Autotune effect for distinctive 
; chorus vocals.
;
; In the end, the effect is very simple:
;
; a) measure the input frequency f_i
; b) round it to the nearest halftone f_target
; c) pitch shift by the ratio (f_target/f_i)
;
; Refinements to this basic effect are:
;
; - ignore or dampen broken measurements
; - make f_target "glide"
;
; The devil is, like always, in the details. Neither measuring input 
; frequency nor pitch shifting are particularly basic.
;
; There are two ways of measuring Frequency: by pvsanal ("V") and by pitchamdf 
; ("P") and two ways of pitch shifting: using harmon ("H") or using a delay 
; line ("D"). This gives four possible combinations of the autotune effect:
;
;  AutotuneVD (which is the one i'd recommended)
;  AutotuneVH
;  AutotunePH
;  AutotunePD
;
; Concerning the choice of methods:
;
; I found two satisfying ways to measure the Frequency of a sample. One uses
; pvsanal/pvspitch, which works fine even with very small FFT windows (which 
; are necissary - the pitch correction has to work on a scale of under maybe 
; 0.05 seconds - which rules out window sizes beyond 2048). However, it has
; the drawback that pitch can only be determined sensibly once a full FFT 
; window has been received. Inbetween complete FFT windows, the measured pitch 
; can not change. So a quick glide might not be corrected for.
;
; The other one uses pitchamdf, which uses a different method without such
; drawbacks. Additionally, it is computationally expensive.
;
; However, I think that both methods might be equally useful, so I
; included both.
;
; I also found two satisfying ways to pitch shift a sample. One is inspired 
; by Hans Mikelsons pitch shifter (Instrument 22 in 
; http://www.csounds.com/mikelson/multifx.orc), however improves on it by
; using two windowed "grains" and switching between them. The method is not
; free of artifacts, especially there's a periodic clicking noise. This might
; be because i don't fully understand the problem.
;
; The other one is simply using harmon, which works great, speaking in 
; terms of reaction speed and signal delay, but sadly introduces some 
; distortion. The artifacts are, however, different to the one produced by
; using delay lines.
; 

sr = 44100
ksmps = 1
nchnls = 1

		opcode AutotuneVD,ak,akkk

ifftsize = 512
iwtype = 1 

asig,kstrength,kdecayin,kdecayout  xin

; 0 <= kstrength <= 1 - 0: no effect 1: pitch shift to closest halftone as closely as possible
; kdecayin>0 (suggested: 0-3) error tolerance - higher values give additional decay time to target frequency, lower values make "off" frequency measurements break
; kdecayout>0 (suggested: 0-8) time-dependent decay of target frequency

fsig 		pvsanal		asig, ifftsize, ifftsize/4, ifftsize, iwtype
kfr, kamp 	pvspitch	fsig, 0.5

; This is the one of the magical places: round to the closest halftone.

ktarget 	= exp(0.021736895+round(12*((log(kfr))/log(2)))*log(2)/12.0)

; correct for errors, decay measured frequency: large decay times give better error tolerance but slower reaction
; measured frequency < 1 : invalidate (no motion at all) - 1 is pretty arbitrary

; Target frequency is weighted average of measured or true frequency

ktarget         = kfr*(1-kstrength)+ktarget*kstrength


kfratiolp 	init 1
kfratiolp 	= (kfr<1)?kfratiolp:(kfratiolp*(1-exp(-kdecayin))+(ktarget/kfr)*exp(-kdecayin))

; decay target frequency: decay that depends only on time

kfratiodecay 	init 1
kfratiodecay 	= kfratiodecay*(1-exp(-kdecayout))+kfratiolp*exp(-kdecayout)

; use this for a really nasty fudge factor - overdo pitch shift - don't have a clue why this should be necissary, but it simply sounds better
;kpshift		=  log(kfratiodecay)*1.7 

kpshift		=  log(kfratiodecay)

ischlapp	= 0.01 ; a fudge factor - initial delay
if1		= 0.08 ; another fudge factor - delay line length


kosc1 		phasor kpshift,0

; generate half sine at init time
ihalfsine	ftgen 0, 0, 128, 19, 0.5, 1, 0, 0

; squared sine oscillator 

ksinsq		tablei kosc1/if1,ihalfsine,1,0  ,1
ksinsq		= ksinsq*ksinsq

; read two signals from the delay line, with half a delay line difference

atmp  		delayr  if1
a1    		deltapi 1-kosc1
a2    		deltapi 0.5-(kosc1>0.5?kosc1-1:kosc1)
      		delayw  asig

; blend out signals by

aout 		= a1*ksinsq+a2*(1-ksinsq)

      		xout     aout,kfr
		endop


	opcode AutotunePD,ak,akkk

asig,kstrength,kdecayin,kdecayout  xin

kfr, kamp pitchamdf asig, 40,600

ktarget 	= exp(0.021736895+round(12*((log(kfr))/log(2)))*log(2)/12.0)
ktarget         = kfr*(1-kstrength)+ktarget*kstrength

kfratiolp 	init 1
kfratiolp 	= (kfr<1)?kfratiolp:(kfratiolp*(1-exp(-kdecayin))+(ktarget/kfr)*exp(-kdecayin))

kfratiodecay 	init 1
kfratiodecay 	= kfratiodecay*(1-exp(-kdecayout))+kfratiolp*exp(-kdecayout)

kpshift		=  log(kfratiodecay)
ischlapp	= 0.01 ; a fudge factor - initial delay
if1		= 0.08 ; another fudge factor - delay line length

kosc1 		phasor kpshift,0.25

ihalfsine	ftgen 0, 0, 128, 19, 0.5, 1, 0, 0

ksinsq		tablei kosc1/if1,ihalfsine,1,0  ,1
ksinsq		= ksinsq*ksinsq

atmp  		delayr  if1
a1    		deltapi 1-kosc1
a2    		deltapi 0.5-(kosc1>0.5?kosc1-1:kosc1)
      		delayw  asig

; blend out signals by

aout 		= a1*ksinsq+a2*(1-ksinsq)

      		xout     aout,kfr
	endop


	opcode AutotuneVH,ak,akkk

ifftsize = 512
iwtype = 1 

asig,kstrength,kdecayin,kdecayout  xin

fsig 		pvsanal		asig, ifftsize, ifftsize/4, ifftsize, iwtype
kfr, kamp 	pvspitch	fsig, 0.5

ktarget 	= exp(0.021736895+round(12*((log(kfr))/log(2)))*log(2)/12.0)
ktarget         = kfr*(1-kstrength)+ktarget*kstrength

kfrlp 		init 1
kfrlp 		= (kfr<1)?kfrlp:(kfrlp*(1-exp(-kdecayin))+kfr*exp(-kdecayin))

ktargetdecay 	init 1
ktargetdecay 	= ktargetdecay*(1-exp(-kdecayout))+kfrlp*exp(-kdecayout)

ischlapp	= 0.01 ; a fudge factor - initial delay
if1		= 0.08 ; another fudge factor - delay line length

aout 		harmon asig,(kfrlp<1?500:kfr),0.4,ktargetdecay,0,1,200,0.1

      		xout     aout,kfr
	endop


	opcode AutotunePH,ak,akkk

asig,kstrength,kdecayin,kdecayout  xin

kfr, kamp pitchamdf asig, 40,600

ktarget 	= exp(0.021736895+round(12*((log(kfr))/log(2)))*log(2)/12.0)
ktarget         = kfr*(1-kstrength)+ktarget*kstrength

kfrlp 		init 1
kfrlp 		= (kfr<1)?kfrlp:(kfrlp*(1-exp(-kdecayin))+kfr*exp(-kdecayin))

ktargetdecay 	init 1
ktargetdecay 	= ktargetdecay*(1-exp(-kdecayout))+kfrlp*exp(-kdecayout)

ischlapp	= 0.01 ; a fudge factor - initial delay
if1		= 0.08 ; another fudge factor - delay line length

aout 		harmon asig,(kfrlp<1?500:kfr),0.4,ktargetdecay,0,1,200,0.1

      		xout     aout,kfr
	endop


	instr 1
		
kf 		init 440
kf 	=	 kf*1.00001

ain 		oscili		p4,kf,1
aout,kfr 	AutotuneVD 	ain,1,1,2
		out 		aout

	endin 1

	instr 2
	
kf 		init 440
kf 	=	 kf*1.00001

aout 		oscili		p4,kf,1
		out 		aout

	endin 2

	instr 3
		
ain 		soundin "wolken-falsch.wav"

aout,kfr 	AutotuneVD 	ain,1,1,2

		out 		aout

	endin 3


</CsInstruments>
<CsScore>

f 1 0 1024 10 1 ; sine wave for sweep

i 2 0 3 30000 ; play sweep
i 1 4 3 30000 ; play autotuned sweep

i 1 8 3 15000 
i 2 8 3 15000 ; play sweep and autotuned sweep together
	      ; listen for the beats!

i 3 12 9 ; play "ueber den wolken" cher-style

e

</CsScore>
</CsoundSynthesizer>
