Extra "ports" of juce-based plugins using the distrho build system
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

158 lines
3.8KB

  1. --[[
  2. name: Pitch Shift - Bernsee Algo
  3. description: "Simple" FFT pitch shifter after S.Bernsee's famous article.
  4. author: osar.fr
  5. --]]
  6. require "include/protoplug"
  7. stereoFx.init()
  8. fftlib = script.ffiLoad("libfftw3.so.3", "libfftw3-3")
  9. -- params
  10. local shift = 1 -- param=0.5 -> shift=1.0 (no shift)
  11. -- settings
  12. local fftSize = 1024 -- 1024 seems good
  13. local steps = 8 -- 4=low-fi, 16=high cpu
  14. -- useful constants
  15. local lineMax = fftSize
  16. local rescale = 1/(fftSize*steps)
  17. local cplxSize = math.floor(fftSize/2+1)
  18. local stepSize = fftSize/steps
  19. local expct = 2*math.pi*stepSize/fftSize;
  20. ffi.cdef[[
  21. typedef double fftw_complex[2];
  22. void *fftw_plan_dft_r2c_1d(int n, double *in, fftw_complex *out, unsigned int flags);
  23. void *fftw_plan_dft_c2r_1d(int n, fftw_complex *in, double *out, unsigned int flags);
  24. void fftw_execute(void *plan);
  25. ]]
  26. -- global buffers
  27. local dbuf = ffi.new("double[?]", fftSize)
  28. local spectrum = ffi.new("fftw_complex[?]", cplxSize)
  29. local anaMagn = ffi.new("double[?]", cplxSize)
  30. local anaFreq = ffi.new("double[?]", cplxSize)
  31. local synMagn = ffi.new("double[?]", cplxSize)
  32. local synFreq = ffi.new("double[?]", cplxSize)
  33. local hw = ffi.new("double[?]", fftSize) -- Hann window
  34. for i = 0,fftSize-1 do
  35. hw[i] = (1 - math.cos(2*math.pi*i/(fftSize-1)))*rescale
  36. end
  37. local function applyWindow (samples)
  38. for i = 0,fftSize-1 do
  39. samples[i] = samples[i] * hw[i]
  40. end
  41. end
  42. -- fftw plans
  43. local r2c = fftlib.fftw_plan_dft_r2c_1d(fftSize, dbuf, spectrum, 64)
  44. local c2r = fftlib.fftw_plan_dft_c2r_1d(fftSize, spectrum, dbuf, 64)
  45. -- per-channel buffers
  46. function stereoFx.Channel:init()
  47. self.inbuf = ffi.new("double[?]", lineMax)
  48. self.outbuf = ffi.new("double[?]", lineMax)
  49. self.bufi = 0
  50. self.inphase = ffi.new("double[?]", cplxSize)
  51. self.outphase = ffi.new("double[?]", cplxSize)
  52. end
  53. -- shift data already in the "spectrum" global
  54. local function applyFilter (inphase, outphase)
  55. -- setup
  56. for i=0,cplxSize-1 do
  57. synMagn[i] = 0
  58. synFreq[i] = 0
  59. end
  60. -- analysis
  61. for i=0,cplxSize-1 do
  62. local real = spectrum[i][0]
  63. local imag = spectrum[i][1]
  64. local magn = 2*math.sqrt(real*real+imag*imag)
  65. local phase = math.atan2(imag, real)
  66. local x = phase - inphase[i]
  67. inphase[i] = phase
  68. x = x - i*expct
  69. x = (x+math.pi)%(math.pi*2)-math.pi
  70. x = steps*x/(2*math.pi)
  71. x = i + x
  72. anaMagn[i] = magn
  73. anaFreq[i] = x
  74. end
  75. -- loop-merging optimization, not sure if useful
  76. if shift>=1 then
  77. for i=0,cplxSize-1 do
  78. shiftAndSynth(i, outphase)
  79. end
  80. else
  81. for i=cplxSize-1,0,-1 do
  82. shiftAndSynth(i, outphase)
  83. end
  84. end
  85. end
  86. function shiftAndSynth(i, outphase)
  87. -- processing
  88. local i2 = math.floor(i*shift+0.5) -- bigger
  89. if i2<cplxSize and i2>0 then -- only for backward
  90. synMagn[i2] = anaMagn[i] + synMagn[i2]
  91. synFreq[i2] = anaFreq[i] * shift
  92. end
  93. -- resynthesis
  94. local magn = synMagn[i]
  95. x = synFreq[i]
  96. x = x - i
  97. x = 2*math.pi*x/steps
  98. x = x + i*expct
  99. outphase[i] = outphase[i] + x
  100. local phase = outphase[i]
  101. spectrum[i][0] = magn * math.cos(phase)
  102. spectrum[i][1] = magn * math.sin(phase)
  103. end
  104. function wrap (i)
  105. return (i>lineMax-1) and i-lineMax or i
  106. end
  107. function stereoFx.Channel:processBlock(s, smax)
  108. for i = 0,smax do
  109. self.inbuf[self.bufi] = s[i]
  110. s[i] = self.outbuf[self.bufi]
  111. self.outbuf[self.bufi] = 0
  112. if self.bufi%stepSize==0 then
  113. for j=0,fftSize-1 do
  114. dbuf[j] = self.inbuf[wrap(self.bufi+j)]
  115. end
  116. -- revive cdata (inexplicably required, todo narrow down the cause):
  117. tostring(dbuf); tostring(spectrum)
  118. fftlib.fftw_execute(r2c)
  119. applyFilter (self.inphase, self.outphase)
  120. fftlib.fftw_execute(c2r)
  121. applyWindow(dbuf)
  122. for j=0,fftSize-1 do
  123. self.outbuf[wrap(self.bufi+j)] =
  124. self.outbuf[wrap(self.bufi+j)] + dbuf[j]
  125. end
  126. end
  127. self.bufi = wrap(self.bufi+1)
  128. end
  129. end
  130. plugin.manageParams {
  131. {
  132. name = "Shift";
  133. changed = function(val)
  134. if val<0.5 then
  135. shift = (val+0.1)/0.6
  136. else
  137. shift = val*8-3
  138. end
  139. end;
  140. };
  141. }