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.

235 lines
7.4KB

  1. #!/usr/bin/python2.5
  2. #
  3. # Copyright 2014 Olivier Gillet.
  4. #
  5. # Author: Olivier Gillet (ol.gillet@gmail.com)
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining a copy
  8. # of this software and associated documentation files (the "Software"), to deal
  9. # in the Software without restriction, including without limitation the rights
  10. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. # copies of the Software, and to permit persons to whom the Software is
  12. # furnished to do so, subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be included in
  15. # all copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. # THE SOFTWARE.
  24. #
  25. # See http://creativecommons.org/licenses/MIT/ for more information.
  26. #
  27. # -----------------------------------------------------------------------------
  28. #
  29. # Lookup table definitions.
  30. import numpy as np
  31. import pylab
  32. import scipy.signal
  33. lookup_tables = []
  34. """----------------------------------------------------------------------------
  35. Sine table.
  36. ----------------------------------------------------------------------------"""
  37. size = 1024
  38. t = np.arange(0, size + size / 4 + 1) / float(size) * np.pi * 2
  39. lookup_tables.append(('sin', np.sin(t)))
  40. """----------------------------------------------------------------------------
  41. Arcsine table.
  42. ----------------------------------------------------------------------------"""
  43. size = 256
  44. t = np.arange(-size/2, size/2+1) / float(size)*2
  45. lookup_tables.append(('arcsin', np.arcsin(t)*2/np.pi))
  46. """----------------------------------------------------------------------------
  47. XFade table
  48. ----------------------------------------------------------------------------"""
  49. size = 257
  50. t = np.arange(0, size) / float(size)
  51. t = 1.04 * t - 0.02
  52. t[t < 0] = 0
  53. t[t >= 1] = 1
  54. t *= np.pi / 2
  55. lookup_tables.append(('xfade_in', np.sin(t) * (2 ** -0.5)))
  56. lookup_tables.append(('xfade_out', np.cos(t) * (2 ** -0.5)))
  57. """----------------------------------------------------------------------------
  58. Wavefolder LUT.
  59. ----------------------------------------------------------------------------"""
  60. WAVETABLE_SIZE = 4096
  61. t = np.arange(0.0, WAVETABLE_SIZE + 1) / WAVETABLE_SIZE
  62. # Waveshape based on Tides
  63. # x = 2 * t - 1
  64. # x = x * 8.0
  65. # x = x / (1 + np.abs(x))
  66. # sine = np.sin(8 * np.pi * x)
  67. # window = np.exp(-x * x * 4) ** 2
  68. # bipolar_fold = sine * window + np.arctan(3 * x) * (1 - window)
  69. # bipolar_fold /= np.abs(bipolar_fold).max()
  70. # bipolar_fold *= 0.8
  71. x = 2 * t - 1
  72. central_bump = np.exp(-x * x * 4) ** 2
  73. x = x * central_bump + (1 - central_bump) * ((t + t ** 1.3) - 1)
  74. x = x * 8.0
  75. x = x / (1 + np.abs(x))
  76. timbre = np.fromfile('warps/resources/timbre.raw', dtype=np.float32)
  77. bipolar_fold = []
  78. for value in x:
  79. value = (value + 1) * 0.5 * 4095
  80. value_integral = int(value)
  81. value_fractional = value - value_integral
  82. a = timbre[value_integral]
  83. b = timbre[value_integral + 1]
  84. bipolar_fold.append(a + (b - a) * value_fractional)
  85. bipolar_fold = np.array(bipolar_fold) * 2.2
  86. lookup_tables += [('bipolar_fold', bipolar_fold)]
  87. """----------------------------------------------------------------------------
  88. MIDI to normalized frequency table.
  89. ----------------------------------------------------------------------------"""
  90. SAMPLE_RATE = 96000
  91. TABLE_SIZE = 256
  92. midi_note = np.arange(0, TABLE_SIZE) - 48
  93. frequency = 440 * 2 ** ((midi_note - 69) / 12.0)
  94. max_frequency = min(12000, SAMPLE_RATE / 2)
  95. frequency[frequency >= max_frequency] = max_frequency
  96. frequency /= SAMPLE_RATE
  97. semitone = 2 ** (np.arange(0, TABLE_SIZE) / 256.0 / 12.0)
  98. lookup_tables.append(('midi_to_f_high', frequency))
  99. lookup_tables.append(('midi_to_f_low', semitone))
  100. """----------------------------------------------------------------------------
  101. Potentiometer compensation.
  102. ----------------------------------------------------------------------------"""
  103. source = [0, 0.006, 0.014, 0.16, 0.33, 0.5, 0.67, 0.84, 0.96, 0.994, 1.00]
  104. dest = [0, 0.1, 0.125, 0.25, 0.375, 0.5, 0.625, 0.750, 0.875, 0.9, 1.00]
  105. n = len(source) - 1
  106. pot_curve = []
  107. for i in xrange(513):
  108. frac = i / 512.0
  109. n = 0
  110. while (frac > source[n + 1]):
  111. n += 1
  112. position = (frac - source[n]) / (source[n + 1] - source[n])
  113. x = dest[n] + position * (dest[n + 1] - dest[n])
  114. pot_curve += [x]
  115. lookup_tables.append(('pot_curve', pot_curve))
  116. """----------------------------------------------------------------------------
  117. Allpass filter network for phase shifter.
  118. ----------------------------------------------------------------------------"""
  119. BANDWIDTH = 0.495
  120. ORDER = 17
  121. PASSBAND = [10.0, 20000.0]
  122. FFT_SIZE = 65536
  123. def iir_lpf_poles(cutoff, order, warp=True):
  124. wp = cutoff * np.pi
  125. ws = np.pi - wp
  126. # C. Britton Rorabaugh, "Digital Filter Designer's Handbook", p. 94
  127. k = np.tan(0.5 * wp) / np.tan(0.5 * ws) if warp else wp / ws
  128. u = 0.5 * (1 - (1 - k ** 2) ** 0.25) / (1 + (1 - k ** 2) ** 0.25)
  129. q = u + 2 * u ** 5 + 15 * u ** 9 + 150 * u ** 13
  130. poles = []
  131. for i in xrange((order - 1) / 2):
  132. w = (i + 1) * np.pi / order
  133. num = np.sum(((-1) ** m) * q ** (m * (m + 1)) * np.sin((2 * m + 1) * w) for m in xrange(0, 7))
  134. den = np.sum(((-1) ** m) * q ** (m * m) * np.cos(2 * m * w) for m in xrange(1, 7))
  135. l = 2 * q ** 0.25 * num / (1 + 2 * den)
  136. b = ((1 - k * l ** 2) * (1 - l ** 2 / k)) ** 0.5
  137. c = 2 * b / (1 + l ** 2)
  138. poles += [(2 - c) / (2 + c)]
  139. return poles
  140. def warped_lp_to_ap(lp_poles, band):
  141. w = np.pi * band
  142. beta = (np.tan(w[0]) * np.tan(w[1])) ** 0.5
  143. b = (beta - 1) / (beta + 1)
  144. ap_poles = []
  145. # Create array of AP poles and do frequency warping.
  146. for pole in lp_poles:
  147. pole = np.sqrt(pole)
  148. conjugates = [-pole, pole] if pole != 0.0 else [pole]
  149. ap_poles += [(-c + b) / (-c * b + 1) for c in conjugates]
  150. return -np.sort(ap_poles)
  151. def phase_shift(x, y):
  152. xf = np.fft.rfft(x)
  153. yf = np.fft.rfft(y)
  154. f = np.arange(0, FFT_SIZE / 2 + 1) / float(FFT_SIZE) * SAMPLE_RATE
  155. error = np.mod(np.angle(yf / xf) + 2 * np.pi, 2 * np.pi)
  156. return f, error / np.pi * 180
  157. def iq_decomposition(ap_poles, x=np.eye(FFT_SIZE, 1).ravel()):
  158. x = [x, x + 0]
  159. for i, b in enumerate(ap_poles):
  160. x[i & 1] = scipy.signal.lfilter([-b, 1], [1, -b], x[i & 1])
  161. return x[0], x[1]
  162. # Coefficients taken from Sean Costello's "hilbert" Csound opcode.
  163. UGSC_FREQS = [0.3609, 1.2524, 2.7412, 5.5671, 11.1573, 22.3423, 44.7581,
  164. 89.6271, 179.6242, 364.7914, 798.4578, 2770.1114]
  165. UGSC_FREQS = np.array(UGSC_FREQS) * 15 * np.pi / SAMPLE_RATE
  166. UGSC_POLES = [(1 - alpha) / (1 + alpha) for alpha in UGSC_FREQS]
  167. # Coefficients computed from the all-pass decomposition of an elliptic halfband
  168. # filter.
  169. AP_POLES = warped_lp_to_ap(
  170. iir_lpf_poles(BANDWIDTH, ORDER) + [0.0],
  171. np.array(PASSBAND) / SAMPLE_RATE)
  172. if __name__ == '__main__':
  173. f, error_1 = phase_shift(*iq_decomposition(UGSC_POLES))
  174. _, error_2 = phase_shift(*iq_decomposition(AP_POLES))
  175. pylab.semilogx(f, error_1, 'r')
  176. pylab.semilogx(f, error_2, 'g')
  177. pylab.xlim([2, 48000])
  178. pylab.xlabel('Frequency (Hz)')
  179. pylab.ylim([90 * 0.99, 90 * 1.01])
  180. pylab.ylabel('Phase shift (degrees)')
  181. pylab.show()
  182. lookup_tables.append(('ap_poles', AP_POLES))