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.

236 lines
6.8KB

  1. #pragma once
  2. #include "AudioMath.h"
  3. #include <algorithm>
  4. #include <assert.h>
  5. #include <cmath>
  6. #include <memory>
  7. #include <emmintrin.h>
  8. #include <functional>
  9. template <typename T> class LookupTableParams;
  10. /* Lookup table with evenly spaced lookup "bins"
  11. * Uses linear interpolation
  12. */
  13. // TODO: templatize on size?
  14. template <typename T>
  15. class LookupTable
  16. {
  17. public:
  18. LookupTable() = delete; // we are only static
  19. /**
  20. * lookup does the table lookup.
  21. * input must be in the range specified at table creation time, otherwise
  22. * it will be limited to the range.
  23. * If allowOutsideDomain, will assert when input must be limited.
  24. */
  25. static T lookup(const LookupTableParams<T>& params, T input, bool allowOutsideDomain = false);
  26. /**
  27. * init will create the entries in the lookup table
  28. * bins is the number of entries desired in the lookup table.
  29. * more bins means greater accuracy, but greater memory usage also.
  30. * xMin is the minimum input that will be passed to lookup()
  31. * xMax is the maximum input that will be passed to lookup().
  32. * xMin..xMax is the domain of the function.
  33. * f is a continuous function that the lookup table will approximate.
  34. */
  35. static void init(LookupTableParams<T>& params, int bins, T xMin, T xMax, std::function<double(double)> f);
  36. /**
  37. * initDiscrete will make a table that interpolates between discrete values.
  38. * numEntries is the number of "points" that will be interpolated.
  39. * entries are the discrete y values to be interpolated.
  40. * Very important: x values are assumed to be 0..numEntries-1. That's because
  41. * this lookup table only works with uniform x value.
  42. */
  43. static void initDiscrete(LookupTableParams<T>& params, int numEntries, const T * yEntries);
  44. private:
  45. static int cvtt(T *);
  46. #ifdef _DEBUG
  47. static void checkInput(const LookupTableParams<T>& params, const T *in, int sampleFrames)
  48. {
  49. for (int i = 0; i < sampleFrames; ++i) {
  50. f_t input = in[i];
  51. assert(input >= params.xMin && input <= params.xMax);
  52. }
  53. }
  54. #else
  55. #define checkInput __noop
  56. #endif
  57. };
  58. template<typename T>
  59. inline T LookupTable<T>::lookup(const LookupTableParams<T>& params, T input, bool allowOutsideDomain)
  60. {
  61. assert(allowOutsideDomain || (input >= params.xMin && input <= params.xMax));
  62. // won't happen in the field,
  63. // as assertions are disabled for release.
  64. input = std::min(input, params.xMax);
  65. input = std::max(input, params.xMin);
  66. assert(params.isValid());
  67. assert(input >= params.xMin && input <= params.xMax);
  68. // need to scale by bins
  69. T scaledInput = input * params.a + params.b;
  70. assert(params.a != 0);
  71. int input_int = cvtt(&scaledInput);
  72. T input_float = scaledInput - input_int;
  73. // Unfortunately, when we use float instead of doubles,
  74. // numeric precision issues get us "out of range". So
  75. // here we clamp to range. It would be more efficient if we didn't do this.
  76. // Perhaps the calculation of a and b could be done so this can't happen?
  77. if (input_float < 0) {
  78. input_float = 0;
  79. } else if (input_float > 1) {
  80. input_float = 1;
  81. }
  82. assert(input_float >= 0 && input_float <= 1);
  83. assert(input_int >= 0 && input_int <= params.numBins_i);
  84. T * entry = params.entries + (2 * input_int);
  85. T x = entry[0];
  86. x += input_float * entry[1];
  87. return x;
  88. }
  89. template<typename T>
  90. inline void LookupTable<T>::init(LookupTableParams<T>& params,
  91. int bins, T x0In, T x1In, std::function<double(double)> f)
  92. {
  93. params.alloc(bins);
  94. // f(x0 = ax + 0 == index
  95. // f(x0) = 0
  96. // f(x1) = bins
  97. params.a = (T) bins / (x1In - x0In);
  98. params.b = -params.a * x0In;
  99. if (x0In == 0) assert(params.b == 0);
  100. {
  101. assert(AudioMath::closeTo((params.a * x0In + params.b), 0, .0001));
  102. assert(AudioMath::closeTo((params.a * x1In + params.b), bins, .0001));
  103. }
  104. for (int i = 0; i <= bins; ++i) {
  105. double x0 = (i - params.b) / params.a;
  106. double x1 = ((i + 1) - params.b) / params.a;
  107. double y0 = f(x0);
  108. double y1 = f(x1);
  109. double slope = y1 - y0;
  110. T * entry = params.entries + (2 * i);
  111. entry[0] = (T) y0;
  112. entry[1] = (T) slope;
  113. }
  114. params.xMin = x0In;
  115. params.xMax = x1In;
  116. }
  117. template<typename T>
  118. inline void LookupTable<T>::initDiscrete(LookupTableParams<T>& params, int numEntries, const T * entries)
  119. {
  120. params.alloc(numEntries);
  121. // Since this version assumes x = 0, 1, 2 ....
  122. // We don't need an interpolation to find which bin we are in
  123. params.a = 1;
  124. params.b = 0;
  125. for (int i = 0; i < numEntries; ++i) {
  126. int x0 = i;
  127. int x1 = i + 1;
  128. // Ugh - this will need to get resolved when we "officially" make
  129. // all table support x values outside their range. But for now we
  130. // have a problem: if x == Xmax (numEntries), we need an extra "bin"
  131. // at the end to look up that value, so here we make a flat bit of xMax.
  132. if (i == (numEntries - 1)) {
  133. --x1;
  134. }
  135. double y0 = entries[x0];
  136. double y1 = entries[x1];
  137. double slope = y1 - y0;
  138. T * entry = params.entries + (2 * i);
  139. entry[0] = (T) y0;
  140. entry[1] = (T) slope;
  141. }
  142. params.xMin = 0;
  143. params.xMax = T(numEntries - 1);
  144. }
  145. template<>
  146. inline int LookupTable<double>::cvtt(double* input)
  147. {
  148. auto x = _mm_load_sd(input);
  149. return _mm_cvttsd_si32(x);
  150. }
  151. template<>
  152. inline int LookupTable<float>::cvtt(float* input)
  153. {
  154. auto x = _mm_load_ss(input);
  155. return _mm_cvttss_si32(x);
  156. }
  157. /***************************************************************************/
  158. extern int _numLookupParams;
  159. template <typename T>
  160. class LookupTableParams
  161. {
  162. public:
  163. int numBins_i = 0; // numBins will be number of entries (pairs of values)
  164. T a = 0, b = 0; // lookup index = a * x + b, so domain of x can be whatever we want
  165. T * entries = 0; // each entry is value, slope
  166. T xMin = 0; // minimum x value we will accept as input
  167. T xMax = 0; // max x value we will accept as input
  168. LookupTableParams()
  169. {
  170. ++_numLookupParams;
  171. }
  172. LookupTableParams(const LookupTableParams&) = delete;
  173. LookupTableParams& operator=(const LookupTableParams&) = delete;
  174. ~LookupTableParams()
  175. {
  176. free(entries);
  177. --_numLookupParams;
  178. }
  179. bool isValid() const
  180. {
  181. return ((entries != 0) &&
  182. (xMax > xMin) &&
  183. (numBins_i > 0)
  184. );
  185. }
  186. void alloc(int bins)
  187. {
  188. if (entries) free(entries);
  189. // allocate one extra, so we can index all the way to the end...
  190. entries = (T *) malloc((bins + 1) * 2 * sizeof(T));
  191. numBins_i = bins;
  192. a = 0;
  193. b = 0;
  194. }
  195. };