Audio plugin host https://kx.studio/carla
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.

331 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. namespace dsp
  21. {
  22. /**
  23. Class for efficiently approximating expensive arithmetic operations.
  24. The approximation is based on linear interpolation between pre-calculated values.
  25. The approximated function should be passed as a callable object to the constructor
  26. along with the number of data points to be pre-calculated. The accuracy of the
  27. approximation can be increased by using more points at the cost of a larger memory
  28. footprint.
  29. Consider using LookupTableTransform as an easy-to-use alternative.
  30. Example:
  31. LookupTable<float> lut ([] (size_t i) { return std::sqrt ((float) i); }, 64);
  32. auto outValue = lut[17];
  33. @see LookupTableTransform
  34. @tags{DSP}
  35. */
  36. template <typename FloatType>
  37. class LookupTable
  38. {
  39. public:
  40. /** Creates an uninitialised LookupTable object.
  41. You need to call initialise() before using the object. Prefer using the
  42. non-default constructor instead.
  43. @see initialise
  44. */
  45. LookupTable();
  46. /** Creates and initialises a LookupTable object.
  47. @param functionToApproximate The function to be approximated. This should be a
  48. mapping from the integer range [0, numPointsToUse - 1].
  49. @param numPointsToUse The number of pre-calculated values stored.
  50. */
  51. LookupTable (const std::function<FloatType (size_t)>& functionToApproximate, size_t numPointsToUse);
  52. /** Initialises or changes the parameters of a LookupTable object.
  53. This function can be used to change what function is approximated by an already
  54. constructed LookupTable along with the number of data points used. If the function
  55. to be approximated won't ever change, prefer using the non-default constructor.
  56. @param functionToApproximate The function to be approximated. This should be a
  57. mapping from the integer range [0, numPointsToUse - 1].
  58. @param numPointsToUse The number of pre-calculated values stored.
  59. */
  60. void initialise (const std::function<FloatType (size_t)>& functionToApproximate, size_t numPointsToUse);
  61. //==============================================================================
  62. /** Calculates the approximated value for the given index without range checking.
  63. Use this if you can guarantee that the index is non-negative and less than numPoints.
  64. Otherwise use get().
  65. @param index The approximation is calculated for this non-integer index.
  66. @return The approximated value at the given index.
  67. @see get, operator[]
  68. */
  69. FloatType getUnchecked (FloatType index) const noexcept
  70. {
  71. jassert (isInitialised()); // Use the non-default constructor or call initialise() before first use
  72. jassert (isPositiveAndBelow (index, FloatType (getNumPoints())));
  73. auto i = truncatePositiveToUnsignedInt (index);
  74. auto f = index - FloatType (i);
  75. jassert (isPositiveAndBelow (f, FloatType (1)));
  76. auto x0 = data.getUnchecked (static_cast<int> (i));
  77. auto x1 = data.getUnchecked (static_cast<int> (i + 1));
  78. return jmap (f, x0, x1);
  79. }
  80. //==============================================================================
  81. /** Calculates the approximated value for the given index with range checking.
  82. This can be called with any input indices. If the provided index is out-of-range
  83. either the bottom or the top element of the LookupTable is returned.
  84. If the index is guaranteed to be in range use the faster getUnchecked() instead.
  85. @param index The approximation is calculated for this non-integer index.
  86. @return The approximated value at the given index.
  87. @see getUnchecked, operator[]
  88. */
  89. FloatType get (FloatType index) const noexcept
  90. {
  91. if (index >= (FloatType) getNumPoints())
  92. index = static_cast<FloatType> (getGuardIndex());
  93. else if (index < 0)
  94. index = {};
  95. return getUnchecked (index);
  96. }
  97. //==============================================================================
  98. /** @see getUnchecked */
  99. FloatType operator[] (FloatType index) const noexcept { return getUnchecked (index); }
  100. /** Returns the size of the LookupTable, i.e., the number of pre-calculated data points. */
  101. size_t getNumPoints() const noexcept { return static_cast<size_t> (data.size()) - 1; }
  102. /** Returns true if the LookupTable is initialised and ready to be used. */
  103. bool isInitialised() const noexcept { return data.size() > 1; }
  104. private:
  105. //==============================================================================
  106. Array<FloatType> data;
  107. void prepare() noexcept;
  108. static size_t getRequiredBufferSize (size_t numPointsToUse) noexcept { return numPointsToUse + 1; }
  109. size_t getGuardIndex() const noexcept { return getRequiredBufferSize (getNumPoints()) - 1; }
  110. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookupTable)
  111. };
  112. //==============================================================================
  113. /** Class for approximating expensive arithmetic operations.
  114. Once initialised, this class can be used just like the function it approximates
  115. via operator().
  116. Example:
  117. LookupTableTransform<float> tanhApprox ([] (float x) { return std::tanh (x); }, -5.0f, 5.0f, 64);
  118. auto outValue = tanhApprox (4.2f);
  119. Note: If you try to call the function with an input outside the provided
  120. range, it will return either the first or the last recorded LookupTable value.
  121. @see LookupTable
  122. @tags{DSP}
  123. */
  124. template <typename FloatType>
  125. class LookupTableTransform
  126. {
  127. public:
  128. //==============================================================================
  129. /** Creates an uninitialised LookupTableTransform object.
  130. You need to call initialise() before using the object. Prefer using the
  131. non-default constructor instead.
  132. @see initialise
  133. */
  134. LookupTableTransform() = default;
  135. //==============================================================================
  136. /** Creates and initialises a LookupTableTransform object.
  137. @param functionToApproximate The function to be approximated. This should be a
  138. mapping from a FloatType to FloatType.
  139. @param minInputValueToUse The lowest input value used. The approximation will
  140. fail for values lower than this.
  141. @param maxInputValueToUse The highest input value used. The approximation will
  142. fail for values higher than this.
  143. @param numPoints The number of pre-calculated values stored.
  144. */
  145. LookupTableTransform (const std::function<FloatType (FloatType)>& functionToApproximate,
  146. FloatType minInputValueToUse,
  147. FloatType maxInputValueToUse,
  148. size_t numPoints)
  149. {
  150. initialise (functionToApproximate, minInputValueToUse, maxInputValueToUse, numPoints);
  151. }
  152. //==============================================================================
  153. /** Initialises or changes the parameters of a LookupTableTransform object.
  154. @param functionToApproximate The function to be approximated. This should be a
  155. mapping from a FloatType to FloatType.
  156. @param minInputValueToUse The lowest input value used. The approximation will
  157. fail for values lower than this.
  158. @param maxInputValueToUse The highest input value used. The approximation will
  159. fail for values higher than this.
  160. @param numPoints The number of pre-calculated values stored.
  161. */
  162. void initialise (const std::function<FloatType (FloatType)>& functionToApproximate,
  163. FloatType minInputValueToUse,
  164. FloatType maxInputValueToUse,
  165. size_t numPoints);
  166. //==============================================================================
  167. /** Calculates the approximated value for the given input value without range checking.
  168. Use this if you can guarantee that the input value is within the range specified
  169. in the constructor or initialise(), otherwise use processSample().
  170. @param value The approximation is calculated for this input value.
  171. @return The approximated value for the provided input value.
  172. @see processSample, operator(), operator[]
  173. */
  174. FloatType processSampleUnchecked (FloatType value) const noexcept
  175. {
  176. jassert (value >= minInputValue && value <= maxInputValue);
  177. return lookupTable[scaler * value + offset];
  178. }
  179. //==============================================================================
  180. /** Calculates the approximated value for the given input value with range checking.
  181. This can be called with any input values. Out-of-range input values will be
  182. clipped to the specified input range.
  183. If the index is guaranteed to be in range use the faster processSampleUnchecked()
  184. instead.
  185. @param value The approximation is calculated for this input value.
  186. @return The approximated value for the provided input value.
  187. @see processSampleUnchecked, operator(), operator[]
  188. */
  189. FloatType processSample (FloatType value) const noexcept
  190. {
  191. auto index = scaler * jlimit (minInputValue, maxInputValue, value) + offset;
  192. jassert (isPositiveAndBelow (index, FloatType (lookupTable.getNumPoints())));
  193. return lookupTable[index];
  194. }
  195. //==============================================================================
  196. /** @see processSampleUnchecked */
  197. FloatType operator[] (FloatType index) const noexcept { return processSampleUnchecked (index); }
  198. /** @see processSample */
  199. FloatType operator() (FloatType index) const noexcept { return processSample (index); }
  200. //==============================================================================
  201. /** Processes an array of input values without range checking
  202. @see process
  203. */
  204. void processUnchecked (const FloatType* input, FloatType* output, size_t numSamples) const noexcept
  205. {
  206. for (size_t i = 0; i < numSamples; ++i)
  207. output[i] = processSampleUnchecked (input[i]);
  208. }
  209. //==============================================================================
  210. /** Processes an array of input values with range checking
  211. @see processUnchecked
  212. */
  213. void process (const FloatType* input, FloatType* output, size_t numSamples) const noexcept
  214. {
  215. for (size_t i = 0; i < numSamples; ++i)
  216. output[i] = processSample (input[i]);
  217. }
  218. //==============================================================================
  219. /** Calculates the maximum relative error of the approximation for the specified
  220. parameter set.
  221. The closer the returned value is to zero the more accurate the approximation
  222. is.
  223. This function compares the approximated output of this class to the function
  224. it approximates at a range of points and returns the maximum relative error.
  225. This can be used to determine if the approximation is suitable for the given
  226. problem. The accuracy of the approximation can generally be improved by
  227. increasing numPoints.
  228. @param functionToApproximate The approximated function. This should be a
  229. mapping from a FloatType to FloatType.
  230. @param minInputValue The lowest input value used.
  231. @param maxInputValue The highest input value used.
  232. @param numPoints The number of pre-calculated values stored.
  233. @param numTestPoints The number of input values used for error
  234. calculation. Higher numbers can increase the
  235. accuracy of the error calculation. If it's zero
  236. then 100 * numPoints will be used.
  237. */
  238. static double calculateMaxRelativeError (const std::function<FloatType (FloatType)>& functionToApproximate,
  239. FloatType minInputValue,
  240. FloatType maxInputValue,
  241. size_t numPoints,
  242. size_t numTestPoints = 0);
  243. private:
  244. //==============================================================================
  245. static double calculateRelativeDifference (double, double) noexcept;
  246. //==============================================================================
  247. LookupTable<FloatType> lookupTable;
  248. FloatType minInputValue, maxInputValue;
  249. FloatType scaler, offset;
  250. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookupTableTransform)
  251. };
  252. } // namespace dsp
  253. } // namespace juce