WaveSwissKnife 0.2.0.20120302

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/AUTHORS +4 -0
  2. data/ChangeLog +31 -0
  3. data/Credits +3 -0
  4. data/LICENSE +31 -0
  5. data/README +15 -0
  6. data/ReleaseInfo +8 -0
  7. data/bin/WSK.rb +14 -0
  8. data/ext/WSK/AnalyzeUtils/AnalyzeUtils.c +272 -0
  9. data/ext/WSK/AnalyzeUtils/extconf.rb +7 -0
  10. data/ext/WSK/ArithmUtils/ArithmUtils.c +862 -0
  11. data/ext/WSK/ArithmUtils/extconf.rb +15 -0
  12. data/ext/WSK/CommonBuild.rb +29 -0
  13. data/ext/WSK/FFTUtils/FFTUtils.c +662 -0
  14. data/ext/WSK/FFTUtils/extconf.rb +15 -0
  15. data/ext/WSK/FunctionUtils/FunctionUtils.c +182 -0
  16. data/ext/WSK/FunctionUtils/extconf.rb +15 -0
  17. data/ext/WSK/SilentUtils/SilentUtils.c +431 -0
  18. data/ext/WSK/SilentUtils/extconf.rb +7 -0
  19. data/ext/WSK/VolumeUtils/VolumeUtils.c +494 -0
  20. data/ext/WSK/VolumeUtils/extconf.rb +15 -0
  21. data/external/CommonUtils/build.rb +28 -0
  22. data/external/CommonUtils/include/CommonUtils.h +177 -0
  23. data/external/CommonUtils/src/CommonUtils.c +639 -0
  24. data/lib/WSK/Actions/Analyze.rb +176 -0
  25. data/lib/WSK/Actions/ApplyMap.desc.rb +15 -0
  26. data/lib/WSK/Actions/ApplyMap.rb +57 -0
  27. data/lib/WSK/Actions/ApplyVolumeFct.desc.rb +30 -0
  28. data/lib/WSK/Actions/ApplyVolumeFct.rb +72 -0
  29. data/lib/WSK/Actions/Compare.desc.rb +25 -0
  30. data/lib/WSK/Actions/Compare.rb +238 -0
  31. data/lib/WSK/Actions/ConstantCompare.desc.rb +20 -0
  32. data/lib/WSK/Actions/ConstantCompare.rb +61 -0
  33. data/lib/WSK/Actions/Cut.desc.rb +20 -0
  34. data/lib/WSK/Actions/Cut.rb +60 -0
  35. data/lib/WSK/Actions/CutFirstSignal.desc.rb +25 -0
  36. data/lib/WSK/Actions/CutFirstSignal.rb +72 -0
  37. data/lib/WSK/Actions/DCShifter.desc.rb +15 -0
  38. data/lib/WSK/Actions/DCShifter.rb +67 -0
  39. data/lib/WSK/Actions/DrawFct.desc.rb +20 -0
  40. data/lib/WSK/Actions/DrawFct.rb +59 -0
  41. data/lib/WSK/Actions/FFT.rb +104 -0
  42. data/lib/WSK/Actions/GenAllValues.rb +67 -0
  43. data/lib/WSK/Actions/GenConstant.desc.rb +20 -0
  44. data/lib/WSK/Actions/GenConstant.rb +56 -0
  45. data/lib/WSK/Actions/GenSawtooth.rb +57 -0
  46. data/lib/WSK/Actions/GenSine.desc.rb +20 -0
  47. data/lib/WSK/Actions/GenSine.rb +73 -0
  48. data/lib/WSK/Actions/Identity.rb +43 -0
  49. data/lib/WSK/Actions/Mix.desc.rb +15 -0
  50. data/lib/WSK/Actions/Mix.rb +149 -0
  51. data/lib/WSK/Actions/Multiply.desc.rb +15 -0
  52. data/lib/WSK/Actions/Multiply.rb +73 -0
  53. data/lib/WSK/Actions/NoiseGate.desc.rb +35 -0
  54. data/lib/WSK/Actions/NoiseGate.rb +129 -0
  55. data/lib/WSK/Actions/SilenceInserter.desc.rb +20 -0
  56. data/lib/WSK/Actions/SilenceInserter.rb +87 -0
  57. data/lib/WSK/Actions/SilenceRemover.desc.rb +30 -0
  58. data/lib/WSK/Actions/SilenceRemover.rb +74 -0
  59. data/lib/WSK/Actions/VolumeProfile.desc.rb +35 -0
  60. data/lib/WSK/Actions/VolumeProfile.rb +63 -0
  61. data/lib/WSK/Common.rb +292 -0
  62. data/lib/WSK/FFT.rb +527 -0
  63. data/lib/WSK/Functions.rb +770 -0
  64. data/lib/WSK/Launcher.rb +216 -0
  65. data/lib/WSK/Maps.rb +29 -0
  66. data/lib/WSK/Model/CachedBufferReader.rb +151 -0
  67. data/lib/WSK/Model/Header.rb +133 -0
  68. data/lib/WSK/Model/InputData.rb +193 -0
  69. data/lib/WSK/Model/RawReader.rb +78 -0
  70. data/lib/WSK/Model/WaveReader.rb +91 -0
  71. data/lib/WSK/OutputInterfaces/DirectStream.rb +146 -0
  72. data/lib/WSK/RIFFReader.rb +60 -0
  73. metadata +155 -0
@@ -0,0 +1,15 @@
1
+ #--
2
+ # Copyright (c) 2009 - 2012 Muriel Salvan (muriel@x-aeon.com)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ require "#{File.dirname(__FILE__)}/../CommonBuild"
7
+ # TODO (Cygwin): Adding -L/usr/local/lib is due to some Cygwin installs that do not include it with gcc
8
+ $LDFLAGS += ' -L/usr/local/lib '
9
+ begin
10
+ have_library('gmp')
11
+ rescue Exception
12
+ puts "\n\n!!! Missing library gmp in this system. Please install it from http://gmplib.org/\n\n"
13
+ raise
14
+ end
15
+ create_makefile('ArithmUtils')
@@ -0,0 +1,29 @@
1
+ #--
2
+ # Copyright (c) 2012 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ # Build external libraries.
7
+ # Set CFLAGS and LDFLAGS accordingly.
8
+ #
9
+ # Parameters::
10
+ # * *iLstExternalLibs* (<em>list<String></em>): List of external libraries names (taken from the external directory)
11
+ def build_external_libs(*iLstExternalLibs)
12
+ require 'rUtilAnts/Misc'
13
+ RUtilAnts::Misc::install_misc_on_object
14
+ iLstExternalLibs.each do |iLibName|
15
+ lLibDir = File.expand_path("#{File.dirname(__FILE__)}/../../external/#{iLibName}")
16
+ # Build the external library first
17
+ # Don't do it from this environment, as it can modify global compilation variables
18
+ change_dir(lLibDir) do
19
+ lCmd = 'ruby -w build.rb'
20
+ raise "Unable to build external library #{iLibName} (using #{lCmd}): #{$?.inspect}" if (!system(lCmd)) or (($? != nil) and ($? != 0))
21
+ end
22
+ $CFLAGS += " -I#{lLibDir}/include "
23
+ $LDFLAGS += " -L#{lLibDir}/lib -l#{iLibName} "
24
+ end
25
+ end
26
+
27
+ require 'mkmf'
28
+ $CFLAGS += ' -Wall '
29
+ build_external_libs('CommonUtils')
@@ -0,0 +1,662 @@
1
+ /**
2
+ * Copyright (c) 2009 - 2012 Muriel Salvan (muriel@x-aeon.com)
3
+ * Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ **/
5
+
6
+ #include "ruby.h"
7
+ #include <math.h>
8
+ #include <stdio.h>
9
+ #include <CommonUtils.h>
10
+
11
+ #include <gmp.h>
12
+
13
+ // Type used to compute FFT calculations
14
+ typedef long long int tFFTValue;
15
+
16
+ // Struct used to convey data among iterators in the completeSumCosSin method
17
+ typedef struct {
18
+ int nbrFreq;
19
+ double* w;
20
+ tFFTValue* sumCos;
21
+ tFFTValue* sumSin;
22
+ int* ptrIdxSum;
23
+ int nbrChannels;
24
+ double** cosCache;
25
+ double** sinCache;
26
+ } tCompleteSumCosSinStruct;
27
+
28
+ // Struct that contains the trigo cache
29
+ typedef struct {
30
+ int nbrFreq;
31
+ double** cosCache;
32
+ double** sinCache;
33
+ } tTrigoCache;
34
+
35
+ // Struct that contains a C FFT profile
36
+ typedef struct {
37
+ int nbrFreq;
38
+ int nbrChannels;
39
+ mpf_t** profile;
40
+ mpf_t maxFFTValue;
41
+ } tFFTProfile;
42
+
43
+ /** Create a ruby object storing the Wi coefficients used to compute the sin and cos sums
44
+ *
45
+ * Parameters::
46
+ * * *iSelf* (_FFTUtils_): The object containing this method
47
+ * * *iValIdxFirstFreq* (_Integer_): First frequency index to generate the array of Wi
48
+ * * *iValIdxLastFreq* (_Integer_): Last frequency index to generate the array of Wi
49
+ * * *iValSampleRate* (_Integer_): The sample rate
50
+ * Return::
51
+ * * _Object_: The container of Wi series
52
+ **/
53
+ static VALUE fftutils_createWi(
54
+ VALUE iSelf,
55
+ VALUE iValIdxFirstFreq,
56
+ VALUE iValIdxLastFreq,
57
+ VALUE iValSampleRate) {
58
+ int lIdxFirstFreq = FIX2INT(iValIdxFirstFreq);
59
+ int lIdxLastFreq = FIX2INT(iValIdxLastFreq);
60
+ int lSampleRate = FIX2INT(iValSampleRate);
61
+
62
+ // For each frequency index i, we have
63
+ // Fi = Sum(t=0..N-1, Xt * cos( Wi * t ) )^2 + Sum(t=0..N-1, Xt * sin( Wi * t ) )^2
64
+ // With N = Number of samples, Xt the sample number t, and Wi = -2*Pi*440*2^(i/12)/S, with S = sample rate
65
+ double * lW = ALLOC_N(double, lIdxLastFreq-lIdxFirstFreq+1);
66
+ // Define the common multipler (-880*PI)
67
+ double lCommonMultiplier = -3520.0*atan2(1.0, 1.0);
68
+
69
+ int lIdxFreq;
70
+ double lDblSampleRate = (double)lSampleRate;
71
+ for (lIdxFreq = lIdxFirstFreq; lIdxFreq < lIdxLastFreq + 1; ++lIdxFreq) {
72
+ lW[lIdxFreq-lIdxFirstFreq] = (lCommonMultiplier*(pow(2.0,(((double)lIdxFreq)/12.0))))/lDblSampleRate;
73
+ }
74
+
75
+ // Encapsulate it
76
+ return Data_Wrap_Struct(rb_cObject, NULL, free, lW);
77
+ }
78
+
79
+ /** Create empty arrays of tFFTValues to be used for sin and cos sums
80
+ *
81
+ * Parameters::
82
+ * * *iSelf* (_FFTUtils_): Self
83
+ * * *iValNbrFreq* (_Integer_): Number of frequencies to store
84
+ * * *iValNbrChannels* (_Integer_): Number of channels
85
+ * Return::
86
+ * * _Object_: An encapsulated array for computation
87
+ **/
88
+ static VALUE fftutils_initSumArray(
89
+ VALUE iSelf,
90
+ VALUE iValNbrFreq,
91
+ VALUE iValNbrChannels) {
92
+ VALUE rValContainer;
93
+ int lNbrFreq = FIX2INT(iValNbrFreq);
94
+ int lNbrChannels = FIX2INT(iValNbrChannels);
95
+
96
+ tFFTValue * lSumArray = ALLOC_N(tFFTValue, lNbrFreq*lNbrChannels);
97
+ // Fill it with 0
98
+ memset(lSumArray, 0, lNbrFreq*lNbrChannels*sizeof(tFFTValue));
99
+
100
+ // Encapsulate it
101
+ rValContainer = Data_Wrap_Struct(rb_cObject, NULL, free, lSumArray);
102
+
103
+ return rValContainer;
104
+ }
105
+
106
+ /**
107
+ * Process a value read from an input buffer for the CompleteSumCosSin function.
108
+ *
109
+ * Parameters::
110
+ * * *iValue* (<em>const tSampleValue</em>): The value being read
111
+ * * *iIdxSample* (<em>const tSampleIndex</em>): Index of this sample
112
+ * * *iIdxChannel* (<em>const int</em>): Channel corresponding to the value being read
113
+ * * *iPtrArgs* (<em>void*</em>): additional arguments. In fact a <em>tNextSilentStruct*</em>.
114
+ * Return::
115
+ * * _int_: The return code:
116
+ * ** 0: Continue iteration
117
+ * ** 1: Break all iterations
118
+ * ** 2: Skip directly to the next sample (don't call us for other channels of this sample)
119
+ */
120
+ int fftutils_processValue_CompleteSumCosSin(
121
+ const tSampleValue iValue,
122
+ const tSampleIndex iIdxSample,
123
+ const int iIdxChannel,
124
+ void* iPtrArgs) {
125
+ // Interpret parameters
126
+ tCompleteSumCosSinStruct* lPtrVariables = (tCompleteSumCosSinStruct*)iPtrArgs;
127
+
128
+ if (iIdxChannel == 0) {
129
+ *(lPtrVariables->ptrIdxSum) = 0;
130
+ }
131
+ long double lTrigoValue;
132
+ int lIdxW;
133
+ for (lIdxW = 0; lIdxW < lPtrVariables->nbrFreq; ++lIdxW) {
134
+ lTrigoValue = ((long double)lPtrVariables->w[lIdxW]) * ((long double)iIdxSample);
135
+ lPtrVariables->sumCos[*(lPtrVariables->ptrIdxSum)] += (tFFTValue)(iValue*cos(lTrigoValue));
136
+ lPtrVariables->sumSin[*(lPtrVariables->ptrIdxSum)] += (tFFTValue)(iValue*sin(lTrigoValue));
137
+ ++(*(lPtrVariables->ptrIdxSum));
138
+ }
139
+
140
+ return 0;
141
+ }
142
+
143
+ /**
144
+ * Process a value read from an input buffer for the CompleteSumCosSin function.
145
+ * Use the trigo cache.
146
+ *
147
+ * Parameters::
148
+ * * *iValue* (<em>const tSampleValue</em>): The value being read
149
+ * * *iIdxSample* (<em>const tSampleIndex</em>): Index of this sample
150
+ * * *iIdxChannel* (<em>const int</em>): Channel corresponding to the value being read
151
+ * * *iPtrArgs* (<em>void*</em>): additional arguments. In fact a <em>tNextSilentStruct*</em>.
152
+ * Return::
153
+ * * _int_: The return code:
154
+ * ** 0: Continue iteration
155
+ * ** 1: Break all iterations
156
+ * ** 2: Skip directly to the next sample (don't call us for other channels of this sample)
157
+ */
158
+ int fftutils_processValue_CompleteSumCosSinWithCache(
159
+ const tSampleValue iValue,
160
+ const tSampleIndex iIdxSample,
161
+ const int iIdxChannel,
162
+ void* iPtrArgs) {
163
+ // Interpret parameters
164
+ tCompleteSumCosSinStruct* lPtrVariables = (tCompleteSumCosSinStruct*)iPtrArgs;
165
+
166
+ if (iIdxChannel == 0) {
167
+ *(lPtrVariables->ptrIdxSum) = 0;
168
+ }
169
+ int lIdxW;
170
+ for (lIdxW = 0; lIdxW < lPtrVariables->nbrFreq; ++lIdxW) {
171
+ lPtrVariables->sumCos[*(lPtrVariables->ptrIdxSum)] += (tFFTValue)(iValue*lPtrVariables->cosCache[lIdxW][iIdxSample]);
172
+ lPtrVariables->sumSin[*(lPtrVariables->ptrIdxSum)] += (tFFTValue)(iValue*lPtrVariables->sinCache[lIdxW][iIdxSample]);
173
+ ++(*(lPtrVariables->ptrIdxSum));
174
+ }
175
+
176
+ return 0;
177
+ }
178
+
179
+ /** Complete the cosinus et sinus sums to compute the FFT
180
+ *
181
+ * Parameters::
182
+ * * *iSelf* (_FFTUtils_): Self
183
+ * * *iValInputRawBuffer* (_String_): The input raw buffer
184
+ * * *iValIdxSample* (_Integer_): The current sample index (to be used when several buffers are used for the same FFT)
185
+ * * *iValNbrBitsPerSample* (_Integer_): The number of bits per sample
186
+ * * *iValNbrSamples* (_Integer_): The number of samples
187
+ * * *iValNbrChannels* (_Integer_): The number of channels
188
+ * * *iValNbrFreq* (_Integer_): The number of frequencies to compute (size of array contained in iValW)
189
+ * * *iValW* (_Object_): Container of the Wi (should be initialized with createWi), or nil if none. Either use W or TrigoCache.
190
+ * * *iValTrigoCache* (_Object_): Container of the trigo cache (should be initialized with initTrigoCache), or nil if none. Either use W or TrigoCache.
191
+ * * *ioValSumCos* (_Object_): Container of the cos sums (should be initialized with initSumArray)
192
+ * * *ioValSumSin* (_Object_): Container of the sin sums (should be initialized with initSumArray)
193
+ **/
194
+ static VALUE fftutils_completeSumCosSin(
195
+ VALUE iSelf,
196
+ VALUE iValInputRawBuffer,
197
+ VALUE iValIdxSample,
198
+ VALUE iValNbrBitsPerSample,
199
+ VALUE iValNbrSamples,
200
+ VALUE iValNbrChannels,
201
+ VALUE iValNbrFreq,
202
+ VALUE iValW,
203
+ VALUE iValTrigoCache,
204
+ VALUE ioValSumCos,
205
+ VALUE ioValSumSin) {
206
+ // Translate Ruby objects
207
+ tSampleIndex iNbrSamples = FIX2LONG(iValNbrSamples);
208
+ int iNbrChannels = FIX2INT(iValNbrChannels);
209
+ int iNbrFreq = FIX2INT(iValNbrFreq);
210
+ int iNbrBitsPerSample = FIX2INT(iValNbrBitsPerSample);
211
+ char* lPtrRawBuffer = RSTRING_PTR(iValInputRawBuffer);
212
+ tSampleIndex iIdxSample = FIX2LONG(iValIdxSample);
213
+ // Get the lW array
214
+ double * lW = NULL;
215
+ if (iValW != Qnil) {
216
+ Data_Get_Struct(iValW, double, lW);
217
+ }
218
+ // Get the trigo cache
219
+ tTrigoCache* lPtrTrigoCache = NULL;
220
+ if (iValTrigoCache != Qnil) {
221
+ Data_Get_Struct(iValTrigoCache, tTrigoCache, lPtrTrigoCache);
222
+ }
223
+ // Get the cos and sin sum arrays
224
+ tFFTValue * lSumCos;
225
+ tFFTValue * lSumSin;
226
+ Data_Get_Struct(ioValSumCos, tFFTValue, lSumCos);
227
+ Data_Get_Struct(ioValSumSin, tFFTValue, lSumSin);
228
+
229
+ // Parse the data. This is done differently depending on the data structure
230
+ // Define variables outside the loops to not allocate and initialize heap size for nothing
231
+ int lIdxSum = 0;
232
+
233
+ // Set variables to give to the process
234
+ tCompleteSumCosSinStruct lProcessVariables;
235
+ lProcessVariables.nbrFreq = iNbrFreq;
236
+ lProcessVariables.w = lW;
237
+ lProcessVariables.sumCos = lSumCos;
238
+ lProcessVariables.sumSin = lSumSin;
239
+ lProcessVariables.ptrIdxSum = &lIdxSum;
240
+ lProcessVariables.nbrChannels = iNbrChannels;
241
+ if (lPtrTrigoCache == NULL) {
242
+ // Iterate through the raw buffer
243
+ commonutils_iterateThroughRawBuffer(
244
+ lPtrRawBuffer,
245
+ iNbrBitsPerSample,
246
+ iNbrChannels,
247
+ iNbrSamples,
248
+ iIdxSample,
249
+ &fftutils_processValue_CompleteSumCosSin,
250
+ &lProcessVariables
251
+ );
252
+ } else {
253
+ lProcessVariables.cosCache = lPtrTrigoCache->cosCache;
254
+ lProcessVariables.sinCache = lPtrTrigoCache->sinCache;
255
+ // Iterate through the raw buffer by using the cache
256
+ commonutils_iterateThroughRawBuffer(
257
+ lPtrRawBuffer,
258
+ iNbrBitsPerSample,
259
+ iNbrChannels,
260
+ iNbrSamples,
261
+ iIdxSample,
262
+ &fftutils_processValue_CompleteSumCosSinWithCache,
263
+ &lProcessVariables
264
+ );
265
+ }
266
+
267
+ return Qnil;
268
+ }
269
+
270
+ /** Compute the final FFT coefficients in Ruby integers, per channel and per frequency.
271
+ * Use previously computed cos and sin sum arrays.
272
+ *
273
+ * Parameters::
274
+ * * *iSelf* (_FFTUtils_): Self
275
+ * * *iValNbrChannels* (_Integer_): The number of channels
276
+ * * *iValNbrFreq* (_Integer_): The number of frequencies to compute (size of array contained in iValW)
277
+ * * *iValSumCos* (_Object_): Container of the cos sums (should be initialized with initSumArray)
278
+ * * *iValSumSin* (_Object_): Container of the sin sums (should be initialized with initSumArray)
279
+ * Return::
280
+ * * <em>list<list<Integer>></em>: List of FFT coefficients, per channel, per frequency
281
+ **/
282
+ static VALUE fftutils_computeFFT(
283
+ VALUE iSelf,
284
+ VALUE iValNbrChannels,
285
+ VALUE iValNbrFreq,
286
+ VALUE iValSumCos,
287
+ VALUE iValSumSin) {
288
+ // Translate Ruby objects
289
+ int lNbrChannels = FIX2INT(iValNbrChannels);
290
+ int lNbrFreq = FIX2INT(iValNbrFreq);
291
+ // Get the cos and sin sum arrays
292
+ tFFTValue * lSumCos;
293
+ tFFTValue * lSumSin;
294
+ Data_Get_Struct(iValSumCos, tFFTValue, lSumCos);
295
+ Data_Get_Struct(iValSumSin, tFFTValue, lSumSin);
296
+ // The C-array of the final result
297
+ VALUE lValFFT[lNbrFreq];
298
+
299
+ int lIdxFreq;
300
+ int lIdxChannel;
301
+ int lIdxSum;
302
+ // Buffer that stores string representation of tFFTValue for Ruby RBigNum
303
+ char lStrValue[128];
304
+ // The bignums to put in the result
305
+ VALUE lValChannelFFTs[lNbrChannels];
306
+ mpz_t lSinSin;
307
+ mpz_init(lSinSin);
308
+ mpz_t lFFTCoeff;
309
+ mpz_init(lFFTCoeff);
310
+ // Put back the cos and sin values in the result, summing their square values
311
+ for (lIdxFreq = 0; lIdxFreq < lNbrFreq; ++lIdxFreq) {
312
+ lIdxSum = lIdxFreq;
313
+ for (lIdxChannel = 0; lIdxChannel < lNbrChannels; ++lIdxChannel) {
314
+ // Initialize MPZ with char* as they don't accept long long int.
315
+ sprintf(lStrValue, "%lld", lSumSin[lIdxSum]);
316
+ mpz_set_str(lSinSin, lStrValue, 10);
317
+ mpz_mul(lSinSin, lSinSin, lSinSin);
318
+ sprintf(lStrValue, "%lld", lSumCos[lIdxSum]);
319
+ mpz_set_str(lFFTCoeff, lStrValue, 10);
320
+ mpz_mul(lFFTCoeff, lFFTCoeff, lFFTCoeff);
321
+ mpz_add(lFFTCoeff, lFFTCoeff, lSinSin);
322
+ lValChannelFFTs[lIdxChannel] = rb_cstr2inum(mpz_get_str(lStrValue, 16, lFFTCoeff), 16);
323
+ lIdxSum += lNbrFreq;
324
+ }
325
+ lValFFT[lIdxFreq] = rb_ary_new4(lNbrChannels, lValChannelFFTs);
326
+ }
327
+ mpz_clear(lFFTCoeff);
328
+ mpz_clear(lSinSin);
329
+
330
+ return rb_ary_new4(lNbrFreq, lValFFT);
331
+ }
332
+ /* To be used if GMP library is absent.
333
+ static VALUE fftutils_computeFFT(
334
+ VALUE iSelf,
335
+ VALUE iValNbrChannels,
336
+ VALUE iValNbrFreq,
337
+ VALUE iValSumCos,
338
+ VALUE iValSumSin) {
339
+ // Translate Ruby objects
340
+ int lNbrChannels = FIX2INT(iValNbrChannels);
341
+ int lNbrFreq = FIX2INT(iValNbrFreq);
342
+ // Get the cos and sin sum arrays
343
+ tFFTValue * lSumCos;
344
+ tFFTValue * lSumSin;
345
+ Data_Get_Struct(iValSumCos, tFFTValue, lSumCos);
346
+ Data_Get_Struct(iValSumSin, tFFTValue, lSumSin);
347
+ // The C-array of the final result
348
+ VALUE lValFFT[lNbrFreq];
349
+ ID lPlusID = rb_intern("+");
350
+ ID lMultiplyID = rb_intern("*");
351
+
352
+ int lIdxFreq;
353
+ int lIdxChannel;
354
+ int lIdxSum;
355
+ // Buffer that stores string representation of tFFTValue for Ruby RBigNum
356
+ char lStrValue[128];
357
+ // RBigNums that will store temprary arithmetic results
358
+ VALUE lValCos;
359
+ VALUE lValSin;
360
+ VALUE lValCosCos;
361
+ VALUE lValSinSin;
362
+ // The bignums to put in the result
363
+ VALUE lValChannelFFTs[lNbrChannels];
364
+ // Put back the cos and sin values in the result, summing their square values
365
+ for (lIdxFreq = 0; lIdxFreq < lNbrFreq; ++lIdxFreq) {
366
+ lIdxSum = lIdxFreq;
367
+ for (lIdxChannel = 0; lIdxChannel < lNbrChannels; ++lIdxChannel) {
368
+ // Initialize Ruby objects for the arithmetic, as C will not treat numbers greater than 64 bits.
369
+ sprintf(lStrValue, "%lld", lSumCos[lIdxSum]);
370
+ lValCos = rb_cstr2inum(lStrValue, 10);
371
+ sprintf(lStrValue, "%lld", lSumSin[lIdxSum]);
372
+ lValSin = rb_cstr2inum(lStrValue, 10);
373
+ lValCosCos = rb_funcall(lValCos, lMultiplyID, 1, lValCos);
374
+ lValSinSin = rb_funcall(lValSin, lMultiplyID, 1, lValSin);
375
+ lValChannelFFTs[lIdxChannel] = rb_funcall(lValCosCos, lPlusID, 1, lValSinSin);
376
+ lIdxSum += lNbrFreq;
377
+ }
378
+ lValFFT[lIdxFreq] = rb_ary_new4(lNbrChannels, lValChannelFFTs);
379
+ }
380
+
381
+ return rb_ary_new4(lNbrFreq, lValFFT);
382
+ }
383
+ */
384
+
385
+ /**
386
+ * Free a trigonometric cache.
387
+ * This method is called by Ruby GC.
388
+ *
389
+ * Parameters::
390
+ * * *iPtrTrigoCache* (<em>void*</em>): The trigo cache to free (in fact a <em>tTrigoCache*</em>)
391
+ */
392
+ static void fftutils_freeTrigoCache(void* iPtrTrigoCache) {
393
+ tTrigoCache* lPtrTrigoCache = (tTrigoCache*)iPtrTrigoCache;
394
+
395
+ int lIdxW;
396
+ for (lIdxW = 0; lIdxW < lPtrTrigoCache->nbrFreq; ++lIdxW) {
397
+ // Free it
398
+ free(lPtrTrigoCache->cosCache[lIdxW]);
399
+ free(lPtrTrigoCache->sinCache[lIdxW]);
400
+ }
401
+ free(lPtrTrigoCache->cosCache);
402
+ free(lPtrTrigoCache->sinCache);
403
+ }
404
+
405
+ /**
406
+ * Create a cache of trigonometric values that will be then used in completeSumCosSin method
407
+ *
408
+ * Parameters::
409
+ * * *iSelf* (_FFTUtils_): Self
410
+ * * *iValW* (_Object_): Container of the W coefficients (initialized using createWi)
411
+ * * *iValNbrFreq* (_Integer_): The number of frequencies in the W coefficients
412
+ * * *iValNbrSamples* (_Integer_): Number of samples for which we create the cache
413
+ * Return::
414
+ * * _Object_: Container of the trigonometric cache
415
+ */
416
+ static VALUE fftutils_initTrigoCache(
417
+ VALUE iSelf,
418
+ VALUE iValW,
419
+ VALUE iValNbrFreq,
420
+ VALUE iValNbrSamples) {
421
+ // Translate parameters in C types
422
+ int iNbrFreq = FIX2INT(iValNbrFreq);
423
+ tSampleIndex iNbrSamples = FIX2LONG(iValNbrSamples);
424
+ // Get the lW array
425
+ double * lW;
426
+ Data_Get_Struct(iValW, double, lW);
427
+
428
+ // Create the cache
429
+ int lIdxW;
430
+ tSampleIndex lIdxSample;
431
+ double* lTmpSamplesValuesCos;
432
+ double* lTmpSamplesValuesSin;
433
+ double lTrigoValue;
434
+ tTrigoCache* lPtrTrigoCache = ALLOC(tTrigoCache);
435
+ lPtrTrigoCache->cosCache = ALLOC_N(double*, iNbrFreq);
436
+ lPtrTrigoCache->sinCache = ALLOC_N(double*, iNbrFreq);
437
+ lPtrTrigoCache->nbrFreq = iNbrFreq;
438
+ for (lIdxW = 0; lIdxW < iNbrFreq; ++lIdxW) {
439
+ // Allocate the double array storing values for each sample
440
+ lTmpSamplesValuesCos = ALLOC_N(double, iNbrSamples);
441
+ lTmpSamplesValuesSin = ALLOC_N(double, iNbrSamples);
442
+ // Fill it
443
+ for (lIdxSample = 0; lIdxSample < iNbrSamples; ++lIdxSample) {
444
+ lTrigoValue = lIdxSample*lW[lIdxW];
445
+ lTmpSamplesValuesCos[lIdxSample] = cos(lTrigoValue);
446
+ lTmpSamplesValuesSin[lIdxSample] = sin(lTrigoValue);
447
+ }
448
+ // Store it
449
+ lPtrTrigoCache->cosCache[lIdxW] = lTmpSamplesValuesCos;
450
+ lPtrTrigoCache->sinCache[lIdxW] = lTmpSamplesValuesSin;
451
+ }
452
+
453
+ // Encapsulate it in a Ruby object
454
+ return Data_Wrap_Struct(rb_cObject, NULL, fftutils_freeTrigoCache, lPtrTrigoCache);
455
+ }
456
+
457
+ /**
458
+ * Initialize an MPF number based on a Ruby's fixnum.
459
+ *
460
+ * Parameters::
461
+ * * *ioMPF* (<em>mpf_t</em>): The mpf to initialize
462
+ * * *iValInt* (_Integer_): The Ruby integer
463
+ * Return::
464
+ * * _int_: The result code of the set
465
+ */
466
+ inline int initMPF(
467
+ mpf_t ioMPF,
468
+ VALUE iValInt) {
469
+ return mpf_init_set_str(ioMPF, RSTRING_PTR(rb_big2str(iValInt, 16)), 16);
470
+ }
471
+
472
+ /**
473
+ * Get a Ruby integer based on an MPF storing an integer value.
474
+ * Prerequisite: The MPF value must be truncated before calling this function.
475
+ *
476
+ * Parameters::
477
+ * * *iMPF* (<em>mpf_t</em>): The mpf to read
478
+ * Return::
479
+ * * _Integer_: The Ruby integer
480
+ */
481
+ #define MAX_NUMBER_DIGITS 256
482
+ VALUE mpf2RubyInt(
483
+ mpf_t iMPF) {
484
+ // The buffer where it will be written
485
+ char lStrNumber[MAX_NUMBER_DIGITS];
486
+ // The exponent part. Used to add trailing 0s.
487
+ mp_exp_t lExp;
488
+ // Fill the string with the mantissa part
489
+ mpf_get_str(lStrNumber, &lExp, 16, MAX_NUMBER_DIGITS, iMPF);
490
+ int lStrSize = strlen(lStrNumber);
491
+ if (lExp-lStrSize > 0) {
492
+ // We need to add (lExp-lStrSize) trailing 0s.
493
+ char* lPtrStrNumber = lStrNumber + lStrSize;
494
+ memset(lPtrStrNumber, '0', lExp-lStrSize);
495
+ lPtrStrNumber[lExp-lStrSize] = 0;
496
+ }
497
+
498
+ return rb_cstr2inum(lStrNumber, 16);
499
+ }
500
+
501
+ /**
502
+ * Free an FFT profile.
503
+ * This method is called by Ruby GC.
504
+ *
505
+ * Parameters::
506
+ * * *iPtrFFTProfile* (<em>void*</em>): The FFT profile to free (in fact a <em>tFFTProfile*</em>)
507
+ */
508
+ static void fftutils_freeFFTProfile(void* iPtrFFTProfile) {
509
+ tFFTProfile* lPtrFFTProfile = (tFFTProfile*)iPtrFFTProfile;
510
+
511
+ int lIdxFreq;
512
+ int lIdxChannel;
513
+ mpf_t* lPtrChannelValues;
514
+ for (lIdxFreq = 0; lIdxFreq < lPtrFFTProfile->nbrFreq; ++lIdxFreq) {
515
+ lPtrChannelValues = lPtrFFTProfile->profile[lIdxFreq];
516
+ for (lIdxChannel = 0; lIdxChannel < lPtrFFTProfile->nbrChannels; ++lIdxChannel) {
517
+ mpf_clear(lPtrChannelValues[lIdxChannel]);
518
+ }
519
+ // Free it
520
+ free(lPtrFFTProfile->profile[lIdxFreq]);
521
+ }
522
+ mpf_clear(lPtrFFTProfile->maxFFTValue);
523
+ free(lPtrFFTProfile->profile);
524
+ }
525
+
526
+ /**
527
+ * Initialize a C object storing a profile
528
+ *
529
+ * Parameters::
530
+ * * *iSelf* (_FFTUtils_): Self
531
+ * * *iValFFTProfile* (<em>[Integer,Integer,list<list<Integer>>]</em>): FFT Profile
532
+ * Return::
533
+ * * _Object_: Object storing a C FFT Profile, to be used with other C functions
534
+ */
535
+ static VALUE fftutils_createCFFTProfile(
536
+ VALUE iSelf,
537
+ VALUE iValFFTProfile) {
538
+ // The C profile
539
+ tFFTProfile* lPtrFFTProfile = ALLOC(tFFTProfile);
540
+ int lNbrBitsPerSample = FIX2INT(rb_ary_entry(iValFFTProfile, 0));
541
+ tSampleIndex lNbrSamples = FIX2LONG(rb_ary_entry(iValFFTProfile, 1));
542
+ VALUE lValFFTCoeffs = rb_ary_entry(iValFFTProfile, 2);
543
+ lPtrFFTProfile->nbrFreq = RARRAY(lValFFTCoeffs)->len;
544
+ lPtrFFTProfile->nbrChannels = RARRAY(rb_ary_entry(lValFFTCoeffs, 0))->len;
545
+
546
+ // Compute the maximal values
547
+ mpf_init_set_ui(lPtrFFTProfile->maxFFTValue, 1 << (lNbrBitsPerSample-1));
548
+ mpf_mul_ui(lPtrFFTProfile->maxFFTValue, lPtrFFTProfile->maxFFTValue, lNbrSamples);
549
+ mpf_mul(lPtrFFTProfile->maxFFTValue, lPtrFFTProfile->maxFFTValue, lPtrFFTProfile->maxFFTValue);
550
+ mpf_add(lPtrFFTProfile->maxFFTValue, lPtrFFTProfile->maxFFTValue, lPtrFFTProfile->maxFFTValue);
551
+
552
+ // Fill the C structure
553
+ lPtrFFTProfile->profile = ALLOC_N(mpf_t*, lPtrFFTProfile->nbrFreq);
554
+ mpf_t* lPtrChannelValues;
555
+ VALUE lValChannelValues;
556
+ int lConvertResult = 0;
557
+ int lIdxFreq;
558
+ int lIdxChannel;
559
+ for (lIdxFreq = 0; lIdxFreq < lPtrFFTProfile->nbrFreq; ++lIdxFreq) {
560
+ lValChannelValues = rb_ary_entry(lValFFTCoeffs, lIdxFreq);
561
+ lPtrChannelValues = ALLOC_N(mpf_t, lPtrFFTProfile->nbrChannels);
562
+ for (lIdxChannel = 0; lIdxChannel < lPtrFFTProfile->nbrChannels; ++lIdxChannel) {
563
+ lConvertResult += initMPF(lPtrChannelValues[lIdxChannel], rb_ary_entry(lValChannelValues, lIdxChannel));
564
+ }
565
+ lPtrFFTProfile->profile[lIdxFreq] = lPtrChannelValues;
566
+ }
567
+ if (lConvertResult != 0) {
568
+ // Errors occured
569
+ char lLogMessage[256];
570
+ sprintf(lLogMessage, "%d errors occurred while creating the C FFT profile.", -lConvertResult);
571
+ rb_funcall(iSelf, rb_intern("log_err"), 1, rb_str_new2(lLogMessage));
572
+ }
573
+
574
+ // Encapsulate it in a Ruby object
575
+ return Data_Wrap_Struct(rb_cObject, NULL, fftutils_freeFFTProfile, lPtrFFTProfile);
576
+ }
577
+
578
+ /**
579
+ * Compare 2 FFT profiles and measure their distance.
580
+ * Here is an FFT profile structure:
581
+ * [ Integer, Integer, list<list<Integer>> ]
582
+ * [ NbrBitsPerSample, NbrSamples, FFTValues ]
583
+ * FFTValues are declined per channel, per frequency index.
584
+ * Bits per sample and number of samples are taken into account to relatively compare the profiles.
585
+ *
586
+ * Parameters::
587
+ * * *iSelf* (_FFTUtils_): Self
588
+ * * *iValProfile1* (_Object_): Profile 1, initialized by createCFFTProfile.
589
+ * * *iValProfile2* (_Object_): Profile 2, initialized by createCFFTProfile.
590
+ * * *iValScale* (_Integer_): The scale used to compute values
591
+ * Return::
592
+ * * _Integer_: Distance (Profile 2 - Profile 1).
593
+ */
594
+ static VALUE fftutils_distFFTProfiles(
595
+ VALUE iSelf,
596
+ VALUE iValProfile1,
597
+ VALUE iValProfile2,
598
+ VALUE iValScale) {
599
+ // Translate parameters in C types
600
+ mpf_t iScale;
601
+ initMPF(iScale, iValScale);
602
+ // Get the FFT Profiles
603
+ tFFTProfile* lPtrFFTProfile1;
604
+ Data_Get_Struct(iValProfile1, tFFTProfile, lPtrFFTProfile1);
605
+ tFFTProfile* lPtrFFTProfile2;
606
+ Data_Get_Struct(iValProfile2, tFFTProfile, lPtrFFTProfile2);
607
+
608
+ // Return the max of the distances of each frequency coefficient
609
+ mpf_t lMaxDist;
610
+ mpf_init_set_ui(lMaxDist, 0);
611
+
612
+ int lIdxFreq;
613
+ int lIdxChannel;
614
+ mpf_t* lPtrChannelValues1;
615
+ mpf_t* lPtrChannelValues2;
616
+ mpf_t lDist;
617
+ mpf_init(lDist);
618
+ mpf_t lDist2;
619
+ mpf_init(lDist2);
620
+
621
+ for (lIdxFreq = 0; lIdxFreq < lPtrFFTProfile1->nbrFreq; ++lIdxFreq) {
622
+ lPtrChannelValues1 = lPtrFFTProfile1->profile[lIdxFreq];
623
+ lPtrChannelValues2 = lPtrFFTProfile2->profile[lIdxFreq];
624
+ for (lIdxChannel = 0; lIdxChannel < lPtrFFTProfile1->nbrChannels; ++lIdxChannel) {
625
+ // Compute iFFT2Value - iFFT1Value, on a scale of iScale
626
+ mpf_div(lDist, lPtrChannelValues2[lIdxChannel], lPtrFFTProfile2->maxFFTValue);
627
+ mpf_div(lDist2, lPtrChannelValues1[lIdxChannel], lPtrFFTProfile1->maxFFTValue);
628
+ mpf_sub(lDist, lDist, lDist2);
629
+ if (mpf_cmp(lDist, lMaxDist) > 0) {
630
+ mpf_set(lMaxDist, lDist);
631
+ }
632
+ }
633
+ }
634
+ // Apply the scale
635
+ mpf_mul(lMaxDist, lMaxDist, iScale);
636
+ mpf_trunc(lMaxDist, lMaxDist);
637
+ // Get the Ruby result
638
+ VALUE rValDistance = mpf2RubyInt(lMaxDist);
639
+
640
+ // Clean memory
641
+ mpf_clear(lDist2);
642
+ mpf_clear(lDist);
643
+ mpf_clear(lMaxDist);
644
+ mpf_clear(iScale);
645
+
646
+ return rValDistance;
647
+ }
648
+
649
+ // Initialize the module
650
+ void Init_FFTUtils() {
651
+ VALUE lWSKModule = rb_define_module("WSK");
652
+ VALUE lFFTUtilsModule = rb_define_module_under(lWSKModule, "FFTUtils");
653
+ VALUE lFFTUtilsClass = rb_define_class_under(lFFTUtilsModule, "FFTUtils", rb_cObject);
654
+
655
+ rb_define_method(lFFTUtilsClass, "completeSumCosSin", fftutils_completeSumCosSin, 10);
656
+ rb_define_method(lFFTUtilsClass, "createWi", fftutils_createWi, 3);
657
+ rb_define_method(lFFTUtilsClass, "initSumArray", fftutils_initSumArray, 2);
658
+ rb_define_method(lFFTUtilsClass, "initTrigoCache", fftutils_initTrigoCache, 3);
659
+ rb_define_method(lFFTUtilsClass, "computeFFT", fftutils_computeFFT, 4);
660
+ rb_define_method(lFFTUtilsClass, "createCFFTProfile", fftutils_createCFFTProfile, 1);
661
+ rb_define_method(lFFTUtilsClass, "distFFTProfiles", fftutils_distFFTProfiles, 3);
662
+ }