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.

234 lines
6.8KB

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