WaveSwissKnife 0.2.0.20120302
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +4 -0
- data/ChangeLog +31 -0
- data/Credits +3 -0
- data/LICENSE +31 -0
- data/README +15 -0
- data/ReleaseInfo +8 -0
- data/bin/WSK.rb +14 -0
- data/ext/WSK/AnalyzeUtils/AnalyzeUtils.c +272 -0
- data/ext/WSK/AnalyzeUtils/extconf.rb +7 -0
- data/ext/WSK/ArithmUtils/ArithmUtils.c +862 -0
- data/ext/WSK/ArithmUtils/extconf.rb +15 -0
- data/ext/WSK/CommonBuild.rb +29 -0
- data/ext/WSK/FFTUtils/FFTUtils.c +662 -0
- data/ext/WSK/FFTUtils/extconf.rb +15 -0
- data/ext/WSK/FunctionUtils/FunctionUtils.c +182 -0
- data/ext/WSK/FunctionUtils/extconf.rb +15 -0
- data/ext/WSK/SilentUtils/SilentUtils.c +431 -0
- data/ext/WSK/SilentUtils/extconf.rb +7 -0
- data/ext/WSK/VolumeUtils/VolumeUtils.c +494 -0
- data/ext/WSK/VolumeUtils/extconf.rb +15 -0
- data/external/CommonUtils/build.rb +28 -0
- data/external/CommonUtils/include/CommonUtils.h +177 -0
- data/external/CommonUtils/src/CommonUtils.c +639 -0
- data/lib/WSK/Actions/Analyze.rb +176 -0
- data/lib/WSK/Actions/ApplyMap.desc.rb +15 -0
- data/lib/WSK/Actions/ApplyMap.rb +57 -0
- data/lib/WSK/Actions/ApplyVolumeFct.desc.rb +30 -0
- data/lib/WSK/Actions/ApplyVolumeFct.rb +72 -0
- data/lib/WSK/Actions/Compare.desc.rb +25 -0
- data/lib/WSK/Actions/Compare.rb +238 -0
- data/lib/WSK/Actions/ConstantCompare.desc.rb +20 -0
- data/lib/WSK/Actions/ConstantCompare.rb +61 -0
- data/lib/WSK/Actions/Cut.desc.rb +20 -0
- data/lib/WSK/Actions/Cut.rb +60 -0
- data/lib/WSK/Actions/CutFirstSignal.desc.rb +25 -0
- data/lib/WSK/Actions/CutFirstSignal.rb +72 -0
- data/lib/WSK/Actions/DCShifter.desc.rb +15 -0
- data/lib/WSK/Actions/DCShifter.rb +67 -0
- data/lib/WSK/Actions/DrawFct.desc.rb +20 -0
- data/lib/WSK/Actions/DrawFct.rb +59 -0
- data/lib/WSK/Actions/FFT.rb +104 -0
- data/lib/WSK/Actions/GenAllValues.rb +67 -0
- data/lib/WSK/Actions/GenConstant.desc.rb +20 -0
- data/lib/WSK/Actions/GenConstant.rb +56 -0
- data/lib/WSK/Actions/GenSawtooth.rb +57 -0
- data/lib/WSK/Actions/GenSine.desc.rb +20 -0
- data/lib/WSK/Actions/GenSine.rb +73 -0
- data/lib/WSK/Actions/Identity.rb +43 -0
- data/lib/WSK/Actions/Mix.desc.rb +15 -0
- data/lib/WSK/Actions/Mix.rb +149 -0
- data/lib/WSK/Actions/Multiply.desc.rb +15 -0
- data/lib/WSK/Actions/Multiply.rb +73 -0
- data/lib/WSK/Actions/NoiseGate.desc.rb +35 -0
- data/lib/WSK/Actions/NoiseGate.rb +129 -0
- data/lib/WSK/Actions/SilenceInserter.desc.rb +20 -0
- data/lib/WSK/Actions/SilenceInserter.rb +87 -0
- data/lib/WSK/Actions/SilenceRemover.desc.rb +30 -0
- data/lib/WSK/Actions/SilenceRemover.rb +74 -0
- data/lib/WSK/Actions/VolumeProfile.desc.rb +35 -0
- data/lib/WSK/Actions/VolumeProfile.rb +63 -0
- data/lib/WSK/Common.rb +292 -0
- data/lib/WSK/FFT.rb +527 -0
- data/lib/WSK/Functions.rb +770 -0
- data/lib/WSK/Launcher.rb +216 -0
- data/lib/WSK/Maps.rb +29 -0
- data/lib/WSK/Model/CachedBufferReader.rb +151 -0
- data/lib/WSK/Model/Header.rb +133 -0
- data/lib/WSK/Model/InputData.rb +193 -0
- data/lib/WSK/Model/RawReader.rb +78 -0
- data/lib/WSK/Model/WaveReader.rb +91 -0
- data/lib/WSK/OutputInterfaces/DirectStream.rb +146 -0
- data/lib/WSK/RIFFReader.rb +60 -0
- 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
|
+
}
|