Hi guys,
first post for me. let me say, i love CSound!
Hans Mikelson posted on CSound magazine this interesting Time Domain TimeScale algorithm based on SOLA
http://www.csounds.com/ezine/spring2000/processing/
I love his explanation and the sound is good, but i needed a dynamic pitch shifting, that is, one that can change pitch in k-rate, i messed around with his code but i made more mess than i can cleanup, so i need your help.
this is Mikelson's version. very clear and neat
<raw>
;---------------------------------------------------------------
; Pitch and Time Scaling
;---------------------------------------------------------------
instr 11
idur = p3 ; Duration
iamp = p4 ; Amplitude ;1
ipshft = p5 ; Pitch scaling factor
itstr = p6 ; Time scaling factor
iaph = p7*sr ; Amplitude of the phasor in seconds converted to samples ;.1
itab = p8 ; Sample
ismth = p9 ; Smoothing factor ;2
ipval = (ipshft-1) ; Setup for pitch shifting
ifph = sr/iaph*((itstr-1)/itstr + ipval) ; Frequency for phasor
prints "P-Coef:%f ipval:%f, ifph:%f%R%R",ipshft,ipval,ifph
aphas1 phasor ifph ; Phasor 1
aphas2 phasor ifph, .5 ; Phasor 2 shifted by 180 degrees
apos linseg 0, idur, idur/itstr*sr ; Scan this many samples of the table.
kph1 downsamp aphas1
kph2 downsamp aphas2
printks "kph1:%f kph2:%f%R",0.1,kph1,kph2
kdclk1 oscil 1, ifph, 1 ; Declick envelope matches phasor frequency
kdclk2 oscil 1, ifph, 1, .5 ; Another one shifted by 180 degrees
kdclk1 = (tanh(kdclk1*ismth)+1)*.5 ; Flatten the sine waves and offset
kdclk2 = (tanh(kdclk2*ismth)+1)*.5 ; Same for other stream
ashft1 table3 iaph*aphas1+apos, itab ; Scan the table with cubic interpolation
ashft2 table3 iaph*aphas2+apos, itab ; second stream
aout = ashft1*kdclk1 + ashft2*kdclk2 ; Combine the two streams with declicking
outs aout*iamp, aout*iamp ; Output the result
endin</raw>
now, how would you make a auto-tuner out of this?
youll have to have a pitch detection algorithm (PDA) and the complicated thing is i want to scale a sample that runs in a loop, so... this is my code with event flow at the bottom:
<raw>,<CsoundSynthesizer>
<CsOptions>
</CsOptions>
<CsInstruments>
sr =44100
ksmps = 64
nchnls = 2
;vocal sample
#define Filename #vocal_sample.aif#
instr Init
;Calculate important global vars and load FT
giproperLen filelen "$Filename"
giftsnd ftgen 0, 0, 2^(inbase), 1, "$Filename", 0, 0, 1
giftlen =ftlen(giftsnd)
endin
;---------------------------------------------------------------
; Pitch and Time Scaling - modified to play loop and support initial samp offset
;TIME STRETCH - DISABLED
;---------------------------------------------------------------
instr SOLA
idur = p3 ; Duration
iamp = p4 ; Amplitude ;1
inote = p5 ; note- convert to Pitch scaling factor
itstr = p6 ; Time scaling factor
iaph = p7*sr ; Amplitude of the phasor in seconds converted to samples ;.1
itab = p8 ; Sample
ismth = p9 ; Smoothing factor ;2
ilenSamp = p11 ;length of loop in samples
knote =k(p5)
;scale factor
kpshft = knote/gkAMDFcps
reinit PhsDeter ;extract phase from phasor and store it while a note is played
PhsDeter:
iSampOffset = int(i(gkeepPhs)*sr)
rireturn
printks "knote:%d kpshft:%f iSampOffset%f gkPhs:%f%R",0.1,knote,kpshft,iSampOffset,gkeepPhs
kpval = (kpshft-1) ; Setup for pitch shifting
gkfph = sr/iaph*(kpval) ; Frequency for phasor
aphas1 phasor gkfph ; Phasor 1
aphas2 phasor gkfph, .5 ; Phasor 2 shifted by 180 degrees
apos linseg 0, idur, idur*sr ; Scan this many samples of the table.
gashft1 table3 (iaph*aphas1+apos+iSampOffset)%ilenSamp, itab ; Scan the table with cubic interpolation
gashft2 table3 (iaph*aphas2+apos+iSampOffset)%ilenSamp, itab ; second stream
krms rms gashft1
outvalue "rms", krms
endin
;Notes - No Live Input, Orchestrated :(
instr 1
ivel = p5
a1 subinstr "SOLA" ,1 ,p4,1,0.1,giftsnd,2,1,giproperLen*sr
endin
;delay vocal until sample will hit the beat
instr Setup
event "i", "Process",0 , 65
turnoff
endin
instr Process
;Read loop
a1 lposcil 0dbfs, 1,0,giproperLen*sr, giftsnd
;Start Phasor to keep track with pitch, reset every sample Length in seconds
gkeepPhs phasor 1/giproperLen
;AMDF PDA - send global to instr SOLA
gkAMDFcps, krms pitchamdf a1, 50, 700 ,130
;fade mini-samples of SOLA
kdclk1 oscil 1, gkfph, 1 ; Declick envelope matches phasor frequency
kdclk2 oscil 1, gkfph, 1, .5 ; Another one shifted by 180 degrees
iamp=1
ismth=2
kdclk1 = (tanh(kdclk1*ismth)+1)*.5 ; Flatten the sine waves and offset
kdclk2 = (tanh(kdclk2*ismth)+1)*.5 ; Same for other stream
aout = gashft1*kdclk1 + gashft2*kdclk2 ; Combine the two streams with declicking
outs aout*iamp, aout*iamp ; Output the result
endin
</CsInstruments>
<CsScore>
f 1 0 16384 10 1
s
i "Init" 0 1
s
f0 70
i "Setup" 0 0.003
i1 0.503628 20 146.828242 120
</CsScore>
</CsoundSynthesizer>
</raw>
it became kind of global variable - p-rate nightmare ;/
the Init method initialize any global variable so the compiler wont be angry.
setup initializes the Process instrument that runs as the Always On instr while performance.
Pseudo Midi given by the note in i1.
i1 trigger the SOLA instr.
the SOLA instr takes global params from Process like the pitch of the current k-frame, and creates global signal gashft1 that is picked up by Process and mixes with the fade mechanism and heads out.
the original sound is a loop, since its beeing read by a table3 opcode, i used a phasor to keep track of where is the pointer of the sample should be on the table. the thing is, that phasor must stop running when activating a note on SOLA, so the reinit section was added with hope to solve this.
now, what is the problem? simple, no sound!
i tried measuring rms of the ga coming out of SOLA, it measured 0.2 RMS. that's wierd. im i reading the table wrong? what can cause this behavior? how is best to debug this? what tools to use? is this idea possible anyway?
thanks to all readers and answerers.