react-native-audio-api 0.4.10 → 0.4.12-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/android/build.gradle +2 -0
  2. package/android/src/main/cpp/{core/AudioAPIInstaller.cpp → AudioAPIModule.cpp} +12 -11
  3. package/android/src/main/cpp/{core/AudioAPIInstaller.h → AudioAPIModule.h} +9 -11
  4. package/android/src/main/cpp/OnLoad.cpp +2 -2
  5. package/android/src/main/cpp/core/AudioDecoder.cpp +5 -5
  6. package/android/src/main/cpp/core/AudioPlayer.cpp +12 -0
  7. package/android/src/main/cpp/core/AudioPlayer.h +2 -0
  8. package/android/src/main/java/com/swmansion/audioapi/{module/AudioAPIInstaller.kt → AudioAPIModule.kt} +22 -10
  9. package/android/src/main/java/com/swmansion/audioapi/AudioAPIPackage.kt +31 -6
  10. package/android/src/oldarch/NativeAudioAPIModuleSpec.java +37 -0
  11. package/common/cpp/HostObjects/AudioBufferSourceNodeHostObject.h +1 -2
  12. package/common/cpp/HostObjects/AudioContextHostObject.h +34 -1
  13. package/common/cpp/HostObjects/BaseAudioContextHostObject.h +8 -0
  14. package/common/cpp/HostObjects/GainNodeHostObject.h +2 -2
  15. package/common/cpp/HostObjects/StretcherNodeHostObject.h +35 -0
  16. package/common/cpp/core/AnalyserNode.cpp +2 -2
  17. package/common/cpp/core/AnalyserNode.h +1 -1
  18. package/common/cpp/core/AudioBuffer.cpp +4 -2
  19. package/common/cpp/core/AudioBuffer.h +1 -1
  20. package/common/cpp/core/AudioBufferSourceNode.cpp +3 -3
  21. package/common/cpp/core/AudioBufferSourceNode.h +3 -3
  22. package/common/cpp/core/AudioBus.cpp +8 -0
  23. package/common/cpp/core/AudioBus.h +3 -0
  24. package/common/cpp/core/AudioContext.cpp +10 -0
  25. package/common/cpp/core/AudioContext.h +2 -0
  26. package/common/cpp/core/AudioDecoder.h +2 -1
  27. package/common/cpp/core/AudioDestinationNode.cpp +1 -1
  28. package/common/cpp/core/AudioDestinationNode.h +1 -1
  29. package/common/cpp/core/AudioNode.cpp +10 -6
  30. package/common/cpp/core/AudioNode.h +5 -3
  31. package/common/cpp/core/AudioScheduledSourceNode.cpp +1 -1
  32. package/common/cpp/core/AudioScheduledSourceNode.h +1 -1
  33. package/common/cpp/core/BaseAudioContext.cpp +7 -0
  34. package/common/cpp/core/BaseAudioContext.h +2 -0
  35. package/common/cpp/core/BiquadFilterNode.cpp +1 -1
  36. package/common/cpp/core/BiquadFilterNode.h +1 -1
  37. package/common/cpp/core/GainNode.cpp +3 -1
  38. package/common/cpp/core/GainNode.h +1 -1
  39. package/common/cpp/core/OscillatorNode.cpp +3 -1
  40. package/common/cpp/core/OscillatorNode.h +1 -1
  41. package/common/cpp/core/StereoPannerNode.cpp +1 -1
  42. package/common/cpp/core/StereoPannerNode.h +1 -1
  43. package/common/cpp/core/StretcherNode.cpp +96 -0
  44. package/common/cpp/core/StretcherNode.h +63 -0
  45. package/common/cpp/installer/AudioAPIModuleInstaller.h +49 -0
  46. package/common/cpp/libs/dsp/LICENSE.txt +21 -0
  47. package/common/cpp/libs/dsp/README.md +40 -0
  48. package/common/cpp/libs/dsp/common.h +47 -0
  49. package/common/cpp/libs/dsp/curves.h +371 -0
  50. package/common/cpp/libs/dsp/delay.h +717 -0
  51. package/common/cpp/libs/dsp/envelopes.h +523 -0
  52. package/common/cpp/libs/dsp/fft.h +523 -0
  53. package/common/cpp/libs/dsp/filters.h +436 -0
  54. package/common/cpp/libs/dsp/mix.h +218 -0
  55. package/common/cpp/libs/dsp/perf.h +84 -0
  56. package/common/cpp/libs/dsp/rates.h +184 -0
  57. package/common/cpp/libs/dsp/spectral.h +496 -0
  58. package/common/cpp/libs/dsp/windows.h +219 -0
  59. package/common/cpp/libs/signalsmith-stretch.h +637 -0
  60. package/common/cpp/types/TimeStretchType.h +6 -0
  61. package/ios/AudioAPIModule.h +1 -1
  62. package/ios/AudioAPIModule.mm +10 -3
  63. package/ios/core/AudioDecoder.mm +2 -3
  64. package/ios/core/AudioPlayer.h +14 -0
  65. package/ios/core/AudioPlayer.m +86 -25
  66. package/ios/core/IOSAudioPlayer.h +2 -0
  67. package/ios/core/IOSAudioPlayer.mm +10 -0
  68. package/lib/module/core/AudioContext.js +7 -1
  69. package/lib/module/core/AudioContext.js.map +1 -1
  70. package/lib/module/core/BaseAudioContext.js +4 -0
  71. package/lib/module/core/BaseAudioContext.js.map +1 -1
  72. package/lib/module/core/StretcherNode.js +12 -0
  73. package/lib/module/core/StretcherNode.js.map +1 -0
  74. package/lib/module/index.js +12 -3
  75. package/lib/module/index.js.map +1 -1
  76. package/lib/module/index.web.js +1 -1
  77. package/lib/module/index.web.js.map +1 -1
  78. package/lib/module/specs/NativeAudioAPIModule.js +5 -0
  79. package/lib/module/specs/NativeAudioAPIModule.js.map +1 -0
  80. package/lib/module/web-core/AudioContext.js +6 -0
  81. package/lib/module/web-core/AudioContext.js.map +1 -1
  82. package/lib/typescript/core/AudioContext.d.ts +2 -0
  83. package/lib/typescript/core/AudioContext.d.ts.map +1 -1
  84. package/lib/typescript/core/BaseAudioContext.d.ts +2 -0
  85. package/lib/typescript/core/BaseAudioContext.d.ts.map +1 -1
  86. package/lib/typescript/core/StretcherNode.d.ts +10 -0
  87. package/lib/typescript/core/StretcherNode.d.ts.map +1 -0
  88. package/lib/typescript/index.d.ts +5 -0
  89. package/lib/typescript/index.d.ts.map +1 -1
  90. package/lib/typescript/index.web.d.ts +1 -1
  91. package/lib/typescript/index.web.d.ts.map +1 -1
  92. package/lib/typescript/interfaces.d.ts +11 -1
  93. package/lib/typescript/interfaces.d.ts.map +1 -1
  94. package/lib/typescript/specs/NativeAudioAPIModule.d.ts +7 -0
  95. package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -0
  96. package/lib/typescript/web-core/AudioContext.d.ts +3 -1
  97. package/lib/typescript/web-core/AudioContext.d.ts.map +1 -1
  98. package/package.json +9 -7
  99. package/src/core/AudioContext.ts +9 -1
  100. package/src/core/BaseAudioContext.ts +5 -0
  101. package/src/core/StretcherNode.ts +15 -0
  102. package/src/index.ts +17 -3
  103. package/src/index.web.ts +1 -0
  104. package/src/interfaces.ts +13 -1
  105. package/src/specs/NativeAudioAPIModule.ts +7 -0
  106. package/src/web-core/AudioContext.tsx +9 -1
  107. package/android/src/main/java/com/swmansion/audioapi/nativemodules/AudioAPIModule.kt +0 -26
  108. package/common/cpp/HostObjects/AudioAPIInstallerHostObject.h +0 -56
  109. package/lib/module/specs/global.d.js +0 -4
  110. package/lib/module/specs/global.d.js.map +0 -1
  111. package/lib/module/specs/install.js +0 -18
  112. package/lib/module/specs/install.js.map +0 -1
  113. package/lib/typescript/specs/install.d.ts +0 -7
  114. package/lib/typescript/specs/install.d.ts.map +0 -1
  115. package/src/specs/global.d.ts +0 -12
  116. package/src/specs/install.ts +0 -32
@@ -0,0 +1,371 @@
1
+ #include "./common.h"
2
+
3
+ #ifndef SIGNALSMITH_DSP_CURVES_H
4
+ #define SIGNALSMITH_DSP_CURVES_H
5
+
6
+ #include <vector>
7
+ #include <algorithm> // std::stable_sort
8
+
9
+ namespace signalsmith {
10
+ namespace curves {
11
+ /** @defgroup Curves Curves
12
+ @brief User-defined mapping functions
13
+
14
+ @{
15
+ @file
16
+ */
17
+
18
+ /// Linear map for real values.
19
+ template<typename Sample=double>
20
+ class Linear {
21
+ Sample a1, a0;
22
+ public:
23
+ Linear() : Linear(0, 1) {}
24
+ Linear(Sample a0, Sample a1) : a1(a1), a0(a0) {}
25
+ /// Construct by from/to value pairs
26
+ Linear(Sample x0, Sample x1, Sample y0, Sample y1) : a1((x0 == x1) ? 0 : (y1 - y0)/(x1 - x0)), a0(y0 - x0*a1) {}
27
+
28
+ Sample operator ()(Sample x) const {
29
+ return a0 + x*a1;
30
+ }
31
+
32
+ Sample dx() const {
33
+ return a1;
34
+ }
35
+
36
+ /// Returns the inverse map (with some numerical error)
37
+ Linear inverse() const {
38
+ Sample invA1 = 1/a1;
39
+ return Linear(-a0*invA1, invA1);
40
+ }
41
+ };
42
+
43
+ /// A real-valued cubic curve. It has a "start" point where accuracy is highest.
44
+ template<typename Sample=double>
45
+ class Cubic {
46
+ Sample xStart, a0, a1, a2, a3;
47
+
48
+ // Only use with y0 != y1
49
+ static inline Sample gradient(Sample x0, Sample x1, Sample y0, Sample y1) {
50
+ return (y1 - y0)/(x1 - x0);
51
+ }
52
+ // Ensure a gradient produces monotonic segments
53
+ static inline void ensureMonotonic(Sample &curveGrad, Sample gradA, Sample gradB) {
54
+ if ((gradA <= 0 && gradB >= 0) || (gradA >= 0 && gradB <= 0)) {
55
+ curveGrad = 0; // point is a local minimum/maximum
56
+ } else {
57
+ if (std::abs(curveGrad) > std::abs(gradA*3)) {
58
+ curveGrad = gradA*3;
59
+ }
60
+ if (std::abs(curveGrad) > std::abs(gradB*3)) {
61
+ curveGrad = gradB*3;
62
+ }
63
+ }
64
+ }
65
+ // When we have duplicate x-values (either side) make up a gradient
66
+ static inline void chooseGradient(Sample &curveGrad, Sample grad1, Sample curveGradOther, Sample y0, Sample y1, bool monotonic) {
67
+ curveGrad = 2*grad1 - curveGradOther;
68
+ if (y0 != y1 && (y1 > y0) != (grad1 >= 0)) { // not duplicate y, but a local min/max
69
+ curveGrad = 0;
70
+ } else if (monotonic) {
71
+ if (grad1 >= 0) {
72
+ curveGrad = std::max<Sample>(0, curveGrad);
73
+ } else {
74
+ curveGrad = std::min<Sample>(0, curveGrad);
75
+ }
76
+ }
77
+ }
78
+ public:
79
+ Cubic() : Cubic(0, 0, 0, 0, 0) {}
80
+ Cubic(Sample xStart, Sample a0, Sample a1, Sample a2, Sample a3) : xStart(xStart), a0(a0), a1(a1), a2(a2), a3(a3) {}
81
+
82
+ Sample operator ()(Sample x) const {
83
+ x -= xStart;
84
+ return a0 + x*(a1 + x*(a2 + x*a3));
85
+ }
86
+ /// The reference x-value, used as the centre of the cubic expansion
87
+ Sample start() const {
88
+ return xStart;
89
+ }
90
+ /// Differentiate
91
+ Cubic dx() const {
92
+ return {xStart, a1, 2*a2, 3*a3, 0};
93
+ }
94
+ Sample dx(Sample x) const {
95
+ x -= xStart;
96
+ return a1 + x*(2*a2 + x*(3*a3));
97
+ }
98
+
99
+ /// Cubic segment based on start/end values and gradients
100
+ static Cubic hermite(Sample x0, Sample x1, Sample y0, Sample y1, Sample g0, Sample g1) {
101
+ Sample xScale = 1/(x1 - x0);
102
+ return {
103
+ x0, y0, g0,
104
+ (3*(y1 - y0)*xScale - 2*g0 - g1)*xScale,
105
+ (2*(y0 - y1)*xScale + g0 + g1)*(xScale*xScale)
106
+ };
107
+ }
108
+
109
+ /** Cubic segment (valid between `x1` and `x2`), which is smooth when applied to an adjacent set of points.
110
+ If `x0 == x1` or `x2 == x3` it will choose a gradient which continues in a quadratic curve, or 0 if the point is a local minimum/maximum.
111
+ */
112
+ static Cubic smooth(Sample x0, Sample x1, Sample x2, Sample x3, Sample y0, Sample y1, Sample y2, Sample y3, bool monotonic=false) {
113
+ if (x1 == x2) return {0, y1, 0, 0, 0}; // zero-width segment, just return constant
114
+
115
+ Sample grad1 = gradient(x1, x2, y1, y2);
116
+ Sample curveGrad1 = grad1;
117
+ bool chooseGrad1 = false;
118
+ if (x0 != x1) { // we have a defined x0-x1 gradient
119
+ Sample grad0 = gradient(x0, x1, y0, y1);
120
+ curveGrad1 = (grad0 + grad1)*Sample(0.5);
121
+ if (monotonic) ensureMonotonic(curveGrad1, grad0, grad1);
122
+ } else if (y0 != y1 && (y1 > y0) != (grad1 >= 0)) {
123
+ curveGrad1 = 0; // set to 0 if it's a min/max
124
+ } else {
125
+ curveGrad1 = 0;
126
+ chooseGrad1 = true;
127
+ }
128
+ Sample curveGrad2;
129
+ if (x2 != x3) { // we have a defined x1-x2 gradient
130
+ Sample grad2 = gradient(x2, x3, y2, y3);
131
+ curveGrad2 = (grad1 + grad2)*Sample(0.5);
132
+ if (monotonic) ensureMonotonic(curveGrad2, grad1, grad2);
133
+ } else {
134
+ chooseGradient(curveGrad2, grad1, curveGrad1, y2, y3, monotonic);
135
+ }
136
+ if (chooseGrad1) {
137
+ chooseGradient(curveGrad1, grad1, curveGrad2, y0, y1, monotonic);
138
+ }
139
+ return hermite(x1, x2, y1, y2, curveGrad1, curveGrad2);
140
+ }
141
+ };
142
+
143
+ /** Smooth interpolation (optionally monotonic) between points, using cubic segments.
144
+ \diagram{cubic-segments-example.svg,Example curve including a repeated point and an instantaneous jump. The curve is flat beyond the first/last points.}
145
+ To produce a sharp corner, use a repeated point. The gradient is flat at the edges, unless you use repeated points at the start/end.*/
146
+ template<typename Sample=double>
147
+ class CubicSegmentCurve {
148
+ struct Point {
149
+ Sample x, y;
150
+ Sample lineGrad = 0, curveGrad = 0;
151
+ bool hasCurveGrad = false;
152
+
153
+ Point() : Point(0, 0) {}
154
+ Point(Sample x, Sample y) : x(x), y(y) {}
155
+
156
+ bool operator <(const Point &other) const {
157
+ return x < other.x;
158
+ }
159
+ };
160
+ std::vector<Point> points;
161
+ Point first{0, 0}, last{0, 0};
162
+
163
+ std::vector<Cubic<Sample>> _segments{1};
164
+ // Not public because it's only valid inside the bounds
165
+ const Cubic<Sample> & findSegment(Sample x) const {
166
+ // Binary search
167
+ size_t low = 0, high = _segments.size();
168
+ while (true) {
169
+ size_t mid = (low + high)/2;
170
+ if (low == mid) break;
171
+ if (_segments[mid].start() <= x) {
172
+ low = mid;
173
+ } else {
174
+ high = mid;
175
+ }
176
+ }
177
+ return _segments[low];
178
+ }
179
+ public:
180
+ Sample lowGrad = 0;
181
+ Sample highGrad = 0;
182
+
183
+ /// Clear existing points and segments
184
+ void clear() {
185
+ points.resize(0);
186
+ _segments.resize(0);
187
+ first = last = {0, 0};
188
+ }
189
+
190
+ /// Add a new point, but does not recalculate the segments. `corner` just writes the point twice, for convenience.
191
+ CubicSegmentCurve & add(Sample x, Sample y, bool corner=false) {
192
+ points.push_back({x, y});
193
+ if (corner) points.push_back({x, y});
194
+ return *this;
195
+ }
196
+
197
+ /// Recalculates the segments.
198
+ void update(bool monotonic=false, bool extendGrad=true, Sample monotonicFactor=3) {
199
+ if (points.empty()) add(0, 0);
200
+ std::stable_sort(points.begin(), points.end()); // Ensure ascending order
201
+ _segments.resize(0);
202
+
203
+ // Calculate the point-to-point gradients
204
+ for (size_t i = 1; i < points.size(); ++i) {
205
+ auto &prev = points[i - 1];
206
+ auto &next = points[i];
207
+ if (prev.x != next.x) {
208
+ prev.lineGrad = (next.y - prev.y)/(next.x - prev.x);
209
+ } else {
210
+ prev.lineGrad = 0;
211
+ }
212
+ }
213
+
214
+ for (auto &p : points) p.hasCurveGrad = false;
215
+ points[0].curveGrad = lowGrad;
216
+ points[0].hasCurveGrad = true;
217
+ points.back().curveGrad = highGrad;
218
+ points.back().hasCurveGrad = true;
219
+
220
+ // Calculate curve gradient where we know it
221
+ for (size_t i = 1; i + 1 < points.size(); ++i) {
222
+ auto &p0 = points[i - 1];
223
+ auto &p1 = points[i];
224
+ auto &p2 = points[i + 1];
225
+ if (p0.x != p1.x && p1.x != p2.x) {
226
+ p1.curveGrad = (p0.lineGrad + p1.lineGrad)*Sample(0.5);
227
+ p1.hasCurveGrad = true;
228
+ }
229
+ }
230
+
231
+ for (size_t i = 1; i < points.size(); ++i) {
232
+ Point &p1 = points[i - 1];
233
+ Point &p2 = points[i];
234
+ if (p1.x == p2.x) continue;
235
+ if (p1.hasCurveGrad) {
236
+ if (!p2.hasCurveGrad) {
237
+ p2.curveGrad = 2*p1.lineGrad - p1.curveGrad;
238
+ }
239
+ } else if (p2.hasCurveGrad) {
240
+ p1.curveGrad = 2*p1.lineGrad - p2.curveGrad;
241
+ } else {
242
+ p1.curveGrad = p2.curveGrad = p1.lineGrad;
243
+ }
244
+ }
245
+
246
+ if (monotonic) {
247
+ for (size_t i = 1; i < points.size(); ++i) {
248
+ Point &p1 = points[i - 1];
249
+ Point &p2 = points[i];
250
+ if (p1.x != p2.x) {
251
+ if (p1.lineGrad >= 0) {
252
+ p1.curveGrad = std::max<Sample>(0, std::min(p1.curveGrad, p1.lineGrad*monotonicFactor));
253
+ p2.curveGrad = std::max<Sample>(0, std::min(p2.curveGrad, p1.lineGrad*monotonicFactor));
254
+ } else {
255
+ p1.curveGrad = std::min<Sample>(0, std::max(p1.curveGrad, p1.lineGrad*monotonicFactor));
256
+ p2.curveGrad = std::min<Sample>(0, std::max(p2.curveGrad, p1.lineGrad*monotonicFactor));
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ for (size_t i = 1; i < points.size(); ++i) {
263
+ Point &p1 = points[i - 1];
264
+ Point &p2 = points[i];
265
+ if (p1.x != p2.x) {
266
+ _segments.push_back(Segment::hermite(p1.x, p2.x, p1.y, p2.y, p1.curveGrad, p2.curveGrad));
267
+ }
268
+ }
269
+
270
+ first = points[0];
271
+ last = points.back();
272
+ if (extendGrad && _segments.size()) {
273
+ if (points[0].x != points[1].x || points[0].y == points[1].y) {
274
+ lowGrad = _segments[0].dx(first.x);
275
+ }
276
+ auto &last = points.back(), &last2 = points[points.size() - 1];
277
+ if (last.x != last2.x || last.y == last2.y) {
278
+ highGrad = _segments.back().dx(last.x);
279
+ }
280
+ }
281
+ }
282
+
283
+ /// Reads a value out from the curve.
284
+ Sample operator()(Sample x) const {
285
+ if (x <= first.x) return first.y + (x - first.x)*lowGrad;
286
+ if (x >= last.x) return last.y + (x - last.x)*highGrad;
287
+ return findSegment(x)(x);
288
+ }
289
+
290
+ CubicSegmentCurve dx() const {
291
+ CubicSegmentCurve result{*this};
292
+ result.first.y = lowGrad;
293
+ result.last.y = highGrad;
294
+ result.lowGrad = result.highGrad = 0;
295
+ for (auto &s : result._segments) {
296
+ s = s.dx();
297
+ }
298
+ return result;
299
+ }
300
+ Sample dx(Sample x) const {
301
+ if (x < first.x) return lowGrad;
302
+ if (x >= last.x) return highGrad;
303
+ return findSegment(x).dx(x);
304
+ }
305
+
306
+ using Segment = Cubic<Sample>;
307
+ std::vector<Segment> & segments() {
308
+ return _segments;
309
+ }
310
+ const std::vector<Segment> & segments() const {
311
+ return _segments;
312
+ }
313
+ };
314
+
315
+ /** A warped-range map, based on 1/x
316
+ \diagram{curves-reciprocal-example.svg}*/
317
+ template<typename Sample=double>
318
+ class Reciprocal {
319
+ Sample a, b, c, d; // (a + bx)/(c + dx)
320
+ Reciprocal(Sample a, Sample b, Sample c, Sample d) : a(a), b(b), c(c), d(d) {}
321
+ public:
322
+ /** Decent approximation to the Bark scale
323
+
324
+ The Bark index goes from 1-24, but this map is valid from approximately 0.25 - 27.5.
325
+ You can get the bandwidth by `barkScale.dx(barkIndex)`.
326
+ \diagram{curves-reciprocal-approx-bark.svg}*/
327
+ static Reciprocal<Sample> barkScale() {
328
+ return {1, 10, 24, 60, 1170, 13500};
329
+ }
330
+ /// Returns a map from 0-1 to the given (non-negative) Hz range.
331
+ static Reciprocal<Sample> barkRange(Sample lowHz, Sample highHz) {
332
+ Reciprocal bark = barkScale();
333
+ Sample lowBark = bark.inverse(lowHz), highBark = bark.inverse(highHz);
334
+ return Reciprocal(lowBark, (lowBark + highBark)/2, highBark).then(bark);
335
+ }
336
+
337
+ Reciprocal() : Reciprocal(0, 0.5, 1) {}
338
+ /// If no x-range given, default to the unit range
339
+ Reciprocal(Sample y0, Sample y1, Sample y2) : Reciprocal(0, 0.5, 1, y0, y1, y2) {}
340
+ Reciprocal(Sample x0, Sample x1, Sample x2, Sample y0, Sample y1, Sample y2) {
341
+ Sample kx = (x1 - x0)/(x2 - x1);
342
+ Sample ky = (y1 - y0)/(y2 - y1);
343
+ a = (kx*x2)*y0 - (ky*x0)*y2;
344
+ b = ky*y2 - kx*y0;
345
+ c = kx*x2 - ky*x0;
346
+ d = ky - kx;
347
+ }
348
+
349
+ Sample operator ()(double x) const {
350
+ return (a + b*x)/(c + d*x);
351
+ }
352
+ Reciprocal inverse() const {
353
+ return Reciprocal(-a, c, b, -d);
354
+ }
355
+ Sample inverse(Sample y) const {
356
+ return (c*y - a)/(b - d*y);
357
+ }
358
+ Sample dx(Sample x) const {
359
+ Sample l = (c + d*x);
360
+ return (b*c - a*d)/(l*l);
361
+ }
362
+
363
+ /// Combine two `Reciprocal`s together in sequence
364
+ Reciprocal then(const Reciprocal &other) const {
365
+ return Reciprocal(other.a*c + other.b*a, other.a*d + other.b*b, other.c*c + other.d*a, other.c*d + other.d*b);
366
+ }
367
+ };
368
+
369
+ /** @} */
370
+ }} // namespace
371
+ #endif // include guard