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.

277 lines
8.5KB

  1. # Copyright 2012 Olivier Gillet.
  2. #
  3. # Author: Olivier Gillet (ol.gillet@gmail.com)
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a copy
  6. # of this software and associated documentation files (the "Software"), to deal
  7. # in the Software without restriction, including without limitation the rights
  8. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. # copies of the Software, and to permit persons to whom the Software is
  10. # furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. # THE SOFTWARE.
  22. #
  23. # See http://creativecommons.org/licenses/MIT/ for more information.
  24. #
  25. # -----------------------------------------------------------------------------
  26. #
  27. # Lookup table definitions.
  28. import numpy
  29. lookup_tables = []
  30. lookup_tables_signed = []
  31. lookup_tables_32 = []
  32. sample_rate = 96000
  33. excursion = 65536 * 65536.0
  34. # Create table for pitch.
  35. a4_midi = 69
  36. a4_pitch = 440.0
  37. highest_octave = 128
  38. notes = numpy.arange(
  39. highest_octave * 128.0,
  40. (highest_octave + 12) * 128.0 + 16,
  41. 16)
  42. pitches = a4_pitch * 2 ** ((notes - a4_midi * 128) / (128 * 12))
  43. increments = excursion / sample_rate * pitches
  44. delays = sample_rate / pitches * 65536 * 4096
  45. lookup_tables_32.append(
  46. ('oscillator_increments', increments.astype(int)))
  47. lookup_tables_32.append(
  48. ('oscillator_delays', delays.astype(int)))
  49. """----------------------------------------------------------------------------
  50. Resonator coefficients
  51. ----------------------------------------------------------------------------"""
  52. cutoff = 440.0 * 2 ** ((numpy.arange(0, 129) - 69) / 12.0)
  53. f = cutoff / (sample_rate / 2)
  54. max_resonance = 0.99985
  55. f[f > 0.25] = 0.25
  56. bandpass_coeff_1 = -2 * numpy.cos(2 * numpy.pi * f)
  57. bandpass_coeff_gain = []
  58. for f in list(f):
  59. sample = 1.0
  60. y1 = 0.0
  61. y2 = 0.0
  62. n = numpy.arange(2000)
  63. response = numpy.sin((n + 1) * 2 * numpy.pi * f) / numpy.sin(2 * numpy.pi * f)
  64. response *= max_resonance ** n
  65. response /= (2 * f) ** 0.5
  66. gain = numpy.abs(response).max()
  67. bandpass_coeff_gain.append(gain)
  68. bandpass_coeff_gain = numpy.maximum(1,
  69. numpy.minimum(
  70. 256,
  71. 16384.0 / numpy.array(bandpass_coeff_gain)))
  72. lookup_tables.append(
  73. ('resonator_coefficient', -bandpass_coeff_1 * 32768.0)
  74. )
  75. lookup_tables.append(
  76. ('resonator_scale', bandpass_coeff_gain)
  77. )
  78. """----------------------------------------------------------------------------
  79. SVF coefficients
  80. ----------------------------------------------------------------------------"""
  81. cutoff = 440.0 * 2 ** ((numpy.arange(0, 257) - 69) / 12.0)
  82. f = cutoff / sample_rate
  83. f[f > 1 / 8.0] = 1 / 8.0
  84. f = 2 * numpy.sin(numpy.pi * f)
  85. resonance = numpy.arange(0, 257) / 260.0
  86. damp = numpy.minimum(2 * (1 - resonance ** 0.25),
  87. numpy.minimum(2, 2 / f - f * 0.5))
  88. lookup_tables.append(
  89. ('svf_cutoff', f * 32767.0)
  90. )
  91. lookup_tables.append(
  92. ('svf_damp', damp * 32767.0)
  93. )
  94. lookup_tables.append(
  95. ('svf_scale', ((damp / 2) ** 0.5) * 32767.0)
  96. )
  97. """----------------------------------------------------------------------------
  98. Envelope for granular synthesis
  99. ----------------------------------------------------------------------------"""
  100. granular_envelope = list(numpy.hanning(257) * 32767.0)
  101. granular_envelope += [0] * 256
  102. lookup_tables.append(
  103. ('granular_envelope', granular_envelope)
  104. )
  105. granular_envelope_rate = 2 ** (numpy.arange(0, 257) / 64.0) * (1 << 14)
  106. lookup_tables.append(
  107. ('granular_envelope_rate', granular_envelope_rate / 8)
  108. )
  109. """----------------------------------------------------------------------------
  110. Bowing envelope and friction curve
  111. ----------------------------------------------------------------------------"""
  112. attack = numpy.linspace(0, 1, int(sample_rate * 0.025 / 4)) * 0.2 * 32768
  113. decay = numpy.linspace(1, 0.8, int(sample_rate * 0.005 / 4)) * 0.2 * 32768
  114. bowing_envelope = list(attack) + list(decay)
  115. # Add a guard to factor the border check out of the sample block loop
  116. bowing_envelope += [decay[-1]] * 32
  117. lookup_tables.append(
  118. ('bowing_envelope', bowing_envelope)
  119. )
  120. delta = numpy.arange(0, 257) / 64.0
  121. friction = 1 / ((numpy.abs(delta) + 0.75) ** 4)
  122. friction = numpy.minimum(friction, 1.0)
  123. lookup_tables.append(
  124. ('bowing_friction', friction * 32768.0)
  125. )
  126. attack = numpy.linspace(0, 1, int(sample_rate * 0.005 / 4)) * 1.3 * 16384
  127. decay = numpy.linspace(1, 0.8, int(sample_rate * 0.01 / 4)) * 1.3 * 16384
  128. blowing_envelope = list(attack) + list(decay)
  129. # Add a guard to factor the border check out of the sample block loop
  130. blowing_envelope += [decay[-1]] * 32
  131. lookup_tables.append(
  132. ('blowing_envelope', blowing_envelope)
  133. )
  134. delta = numpy.arange(0, 257) / 128.0
  135. jet = delta ** 3 - delta
  136. jet = numpy.minimum(jet, 1.0)
  137. lookup_tables_signed.append(
  138. ('blowing_jet', jet * 32767.0)
  139. )
  140. flute_body_filter = 4096 * numpy.minimum(
  141. 0.7, 0.4 * 2 ** ((numpy.arange(0, 128) - 69) / 12.0))
  142. lookup_tables.append(
  143. ('flute_body_filter', flute_body_filter)
  144. )
  145. """----------------------------------------------------------------------------
  146. Quantizer for FM frequencies.
  147. ----------------------------------------------------------------------------"""
  148. fm_frequency_ratios = [ 0.125, 0.25, 0.5, 0.5 * 2 ** (16 / 1200.0),
  149. numpy.sqrt(2) / 2, numpy.pi / 4, 1.0, 1.0 * 2 ** (16 / 1200.0), numpy.sqrt(2),
  150. numpy.pi / 2, 7.0 / 4, 2, 2 * 2 ** (16 / 1200.0), 9.0 / 4, 11.0 / 4,
  151. 2 * numpy.sqrt(2), 3, numpy.pi, numpy.sqrt(3) * 2, 4, numpy.sqrt(2) * 3,
  152. numpy.pi * 3 / 2, 5, numpy.sqrt(2) * 4, 8]
  153. scale = []
  154. for ratio in fm_frequency_ratios:
  155. ratio = 256 * 12 * numpy.log2(ratio) + 16384
  156. scale.extend([ratio, ratio, ratio])
  157. target_size = int(2 ** numpy.ceil(numpy.log2(len(scale))))
  158. while len(scale) < target_size:
  159. gap = numpy.argmax(numpy.diff(scale))
  160. scale = scale[:gap + 1] + [(scale[gap] + scale[gap + 1]) / 2] + \
  161. scale[gap + 1:]
  162. scale.append(scale[-1])
  163. lookup_tables.append(
  164. ('fm_frequency_quantizer', scale)
  165. )
  166. """----------------------------------------------------------------------------
  167. Simulates VCO detuning
  168. ----------------------------------------------------------------------------"""
  169. modified_pitch = []
  170. for i in xrange(257):
  171. frequency = 440 * 2 ** ((i / 2.0 - 69) / 12.0)
  172. # Simulates an offset current in the integrator.
  173. frequency -= 0.6
  174. # Simulates the integrator cap reset time.
  175. time = 1 / frequency
  176. time += 9e-6
  177. frequency = 1 / time
  178. midi_pitch = 128 * (69 + 12 * numpy.log2(frequency / 440.0))
  179. midi_pitch = max(midi_pitch, 0)
  180. modified_pitch.append(midi_pitch)
  181. modified_pitch = numpy.array(modified_pitch)
  182. modified_pitch += (60 << 7) - modified_pitch[120]
  183. lookup_tables.append(
  184. ('vco_detune', modified_pitch)
  185. )
  186. """----------------------------------------------------------------------------
  187. Bell envelopes for VOSIM and FOF
  188. ----------------------------------------------------------------------------"""
  189. def bell(size, ratio):
  190. n = size / ratio
  191. first_half = numpy.hanning(n * 2)[:n] * 65535
  192. r = size - n
  193. second_half = numpy.hanning(r * 2)[r:] * 65535
  194. bell = list(first_half) + list(second_half) + [0]
  195. return bell
  196. lookup_tables.append(('bell', bell(256, 16)))
  197. """----------------------------------------------------------------------------
  198. Envelope increments.
  199. ----------------------------------------------------------------------------"""
  200. sample_rate = 48000
  201. control_rate = sample_rate / 24.0
  202. max_time = 12.0 # seconds
  203. min_time = 3.0 / control_rate # seconds
  204. gamma = 0.175
  205. min_increment = excursion / (max_time * control_rate)
  206. max_increment = excursion / (min_time * control_rate)
  207. rates = numpy.linspace(numpy.power(max_increment, -gamma),
  208. numpy.power(min_increment, -gamma), 128)
  209. values = numpy.power(rates, -1/gamma).astype(int)
  210. lookup_tables_32.append(
  211. ('env_portamento_increments', values)
  212. )
  213. """----------------------------------------------------------------------------
  214. Envelope curves
  215. -----------------------------------------------------------------------------"""
  216. env_linear = numpy.arange(0, 257.0) / 256.0
  217. env_expo = 1.0 - numpy.exp(-4 * env_linear)
  218. lookup_tables.append(('env_expo', env_expo / env_expo.max() * 65535.0))