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
6.8KB

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