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.

189 lines
4.5KB

  1. --[[
  2. name: Pitch Distort
  3. description: >
  4. graphical pitch distortion,
  5. shifts each frequency band by a different factor
  6. author: osar.fr
  7. --]]
  8. require "include/protoplug"
  9. require "include/Pickle"
  10. stereoFx.init()
  11. fftlib = script.ffiLoad("libfftw3.so.3", "libfftw3-3")
  12. ffi.cdef[[
  13. typedef double fftw_complex[2];
  14. void *fftw_plan_dft_r2c_1d(int n, double *in, fftw_complex *out, unsigned int flags);
  15. void *fftw_plan_dft_c2r_1d(int n, fftw_complex *in, double *out, unsigned int flags);
  16. void fftw_execute(void *plan);
  17. ]]
  18. -- settings
  19. local fftSize = 1024 -- 1024 seems good
  20. local steps = 8 -- 4=low-fi, 16=high cpu
  21. local xPixels = 400
  22. local yPixels = 300
  23. -- useful constants
  24. local lineMax = fftSize
  25. local rescale = 0.5/(fftSize*steps)
  26. local cplxSize = math.floor(fftSize/2+1)
  27. local stepSize = fftSize/steps
  28. local expct = 2*math.pi*stepSize/fftSize;
  29. -- global buffers
  30. local graph = ffi.new ("double[?]", cplxSize)
  31. for i = 0,cplxSize-1 do graph[i] = 0.25 end
  32. local dbuf = ffi.new("double[?]", fftSize)
  33. local spectrum = ffi.new("fftw_complex[?]", cplxSize)
  34. local r2c = fftlib.fftw_plan_dft_r2c_1d(fftSize, dbuf, spectrum, 64)
  35. local c2r = fftlib.fftw_plan_dft_c2r_1d(fftSize, spectrum, dbuf, 64)
  36. local anaMagn = ffi.new("double[?]", cplxSize)
  37. local anaFreq = ffi.new("double[?]", cplxSize)
  38. local synMagn = ffi.new("double[?]", cplxSize)
  39. local synFreq = ffi.new("double[?]", cplxSize)
  40. local hw = ffi.new("double[?]", fftSize) -- Hann window
  41. for i = 0,fftSize-1 do
  42. hw[i] = (1 - math.cos(2*math.pi*i/(fftSize-1)))*rescale
  43. end
  44. local function ApplyWindow (samples)
  45. for i = 0,fftSize-1 do
  46. samples[i] = samples[i] * hw[i]
  47. end
  48. end
  49. -- channel buffers
  50. function stereoFx.Channel:init()
  51. self.inbuf = ffi.new("double[?]", lineMax)
  52. self.outbuf = ffi.new("double[?]", lineMax)
  53. self.bufi = 0
  54. self.inphase = ffi.new("double[?]", cplxSize)
  55. self.outphase = ffi.new("double[?]", cplxSize)
  56. end
  57. -- filter the "spectrum" global given a channel's phases
  58. local function ApplyFilter (inphase, outphase)
  59. -- setup
  60. for i=0,cplxSize-1 do
  61. synMagn[i] = 0
  62. synFreq[i] = 0
  63. end
  64. -- analysis
  65. for i=0,cplxSize-1 do
  66. local real = spectrum[i][0]
  67. local imag = spectrum[i][1]
  68. local magn = 2*math.sqrt(real*real+imag*imag)
  69. local phase = math.atan2(imag, real)
  70. local x = phase - inphase[i]
  71. inphase[i] = phase
  72. x = x - i*expct
  73. x = (x+math.pi)%(math.pi*2)-math.pi
  74. x = steps*x/(2*math.pi)
  75. x = i + x
  76. anaMagn[i] = magn
  77. anaFreq[i] = x
  78. end
  79. -- processing
  80. for i=0,cplxSize-1 do
  81. local shift = graph[i]*2+0.5
  82. local i2 = math.floor(i*shift)
  83. if i2<cplxSize and i2>0 then
  84. synMagn[i2] = anaMagn[i] + synMagn[i2]
  85. synFreq[i2] = anaFreq[i] * shift
  86. end
  87. end
  88. -- resynthesis
  89. for i=0,cplxSize-1 do
  90. local magn = synMagn[i]
  91. x = synFreq[i]
  92. x = x - i
  93. x = 2*math.pi*x/steps
  94. x = x + i*expct
  95. outphase[i] = outphase[i] + x
  96. local phase = outphase[i]
  97. spectrum[i][0] = magn * math.cos(phase)
  98. spectrum[i][1] = magn * math.sin(phase)
  99. end
  100. end
  101. function wrap (i)
  102. return (i>lineMax-1) and i-lineMax or i
  103. end
  104. function stereoFx.Channel:processBlock(s, smax)
  105. for i = 0,smax do
  106. self.inbuf[self.bufi] = s[i]
  107. s[i] = self.outbuf[self.bufi]
  108. self.outbuf[self.bufi] = 0
  109. if self.bufi%stepSize==0 then
  110. for j=0,fftSize-1 do
  111. dbuf[j] = self.inbuf[wrap(self.bufi+j)]
  112. end
  113. -- revive cdata (inexplicably required, todo-narrow down the cause):
  114. tostring(dbuf); tostring(spectrum)
  115. fftlib.fftw_execute(r2c)
  116. ApplyFilter (self.inphase, self.outphase)
  117. fftlib.fftw_execute(c2r)
  118. ApplyWindow(dbuf)
  119. for j=0,fftSize-1 do
  120. self.outbuf[wrap(self.bufi+j)] =
  121. self.outbuf[wrap(self.bufi+j)] + dbuf[j]
  122. end
  123. end
  124. self.bufi = wrap(self.bufi+1)
  125. end
  126. end
  127. -- Graphics --
  128. local Freqgraph = require "include/pac/freqgraph"
  129. local J = require "include/protojuce"
  130. local fg = Freqgraph {
  131. title = "Pitch distortion";
  132. data = graph;
  133. dataSize = cplxSize;
  134. yAxis = {
  135. name = "shift (%)";
  136. values = {
  137. [0] = "50";
  138. [0.25] = "100";
  139. [0.5] = "150";
  140. [1] = "250";
  141. }
  142. }
  143. }
  144. function gui.paint(g)
  145. g:fillAll()
  146. fg:paint(g)
  147. end
  148. -- Save & load --
  149. local header = "pac pitch distort 1"
  150. function script.loadData(data)
  151. -- check data begins with our header
  152. if string.sub(data, 1, string.len(header)) ~= header then return end
  153. data = unpickle(string.sub(data, string.len(header)+1, -1))
  154. -- check string was turned into a table without errors
  155. if data==nil then return end
  156. for i=0,cplxSize-1 do
  157. if data[i] ~= nil then
  158. graph[i] = data[i]
  159. end
  160. end
  161. end
  162. function script.saveData()
  163. local picktable = {}
  164. for i=0,cplxSize-1 do
  165. picktable[i] = graph[i]
  166. end
  167. return header..pickle(picktable)
  168. end