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,770 @@
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 'rational'
7
+
8
+ # Convert a float to a Rational
9
+ class Float
10
+
11
+ # Convert to a Rational
12
+ #
13
+ # Return::
14
+ # * _Rational_: Corresponding Rational
15
+ def to_r
16
+ rResult = nil
17
+
18
+ if (self.nan?)
19
+ rResult = Rational(0,0) # Div by zero error
20
+ elsif (self.infinite?)
21
+ rResult = Rational(self<0 ? -1 : 1,0) # Div by zero error
22
+ else
23
+ lSign, lExponent, lFloat = [self].pack("G").unpack("B*").first.unpack("AA11A52")
24
+ lSign = (-1)**lSign.to_i
25
+ lExponent = lExponent.to_i(2)
26
+ if ((lExponent.nonzero?) and
27
+ (lExponent<2047))
28
+ rResult = Rational(lSign)*Rational(2)**(lExponent-1023)*Rational("1#{lFloat}".to_i(2),0x10000000000000)
29
+ elsif (lExponent.zero?)
30
+ rResult = Rational(lSign)*Rational(2)**(-1024)*Rational("0#{lFloat}".to_i(2),0x10000000000000)
31
+ end
32
+ end
33
+
34
+ return rResult
35
+ end
36
+
37
+ end
38
+
39
+ module WSK
40
+
41
+ module Functions
42
+
43
+ # Function type piecewise linear.
44
+ # Here are the possible attributes used by this type:
45
+ # *:MinValue* (_Rational_): Minimal value of the plots of the function [optional = Minimal value of points]
46
+ # *:MaxValue* (_Rational_): Maximal value of the plots of the function [optional = Maximal value of points]
47
+ # *:Points* (<em>map<Rational,Rational></em>): Coordinates of points indicating each linear part
48
+ FCTTYPE_PIECEWISE_LINEAR = 0
49
+
50
+ # Class implementing a mathematical function that can then be used in many contexts
51
+ class Function
52
+
53
+ include WSK::Common
54
+
55
+ # Constructor
56
+ def initialize
57
+ # The underlying Ruby function
58
+ @Function = nil
59
+ # The C libraries
60
+ @FunctionUtils = nil
61
+ @VolumeUtils = nil
62
+ end
63
+
64
+ # Read from a file
65
+ #
66
+ # Parameters::
67
+ # * *iFileName* (_String_): File name
68
+ def read_from_file(iFileName)
69
+ lStrFunction = nil
70
+ if (File.exists?(iFileName))
71
+ File.open(iFileName, 'r') do |iFile|
72
+ lStrFunction = iFile.read
73
+ end
74
+ else
75
+ raise RuntimeError.new("Missing file #{iFileName} to load function.")
76
+ end
77
+ begin
78
+ @Function = eval(lStrFunction)
79
+ rescue Exception
80
+ raise RuntimeError.new("Invalid function specified in file #{iFileName}: #{$!}")
81
+ end
82
+ convertDataTypes
83
+ optimize
84
+ end
85
+
86
+ # Read a function from the volume of an input data
87
+ #
88
+ # Parameters::
89
+ # * *iInputData* (<em>WSK::Model::InputData</em>): The input data
90
+ # * *iIdxBeginSample* (_Integer_): Index of the first sample beginning the volume reading
91
+ # * *iIdxEndSample* (_Integer_): Index of the last sample ending the volume reading
92
+ # * *iInterval* (_Integer_): The number of samples used as an interval in measuring the volume
93
+ # * *iRMSRatio* (_Float_): The ratio of RMS measure vs Peak measure (0.0 = only peak, 1.0 = only RMS)
94
+ def read_from_input_volume(iInputData, iIdxBeginSample, iIdxEndSample, iInterval, iRMSRatio)
95
+ @Function = {
96
+ :FunctionType => FCTTYPE_PIECEWISE_LINEAR,
97
+ :Points => []
98
+ }
99
+ # Profile
100
+ prepareVolumeUtils
101
+ lIdxCurrentSample = iIdxBeginSample
102
+ while (lIdxCurrentSample <= iIdxEndSample)
103
+ lIdxCurrentEndSample = lIdxCurrentSample + iInterval - 1
104
+ if (lIdxCurrentEndSample > iIdxEndSample)
105
+ lIdxCurrentEndSample = iIdxEndSample
106
+ end
107
+ lRawBuffer = ''
108
+ iInputData.each_raw_buffer(lIdxCurrentSample, lIdxCurrentEndSample, :nbr_samples_prefetch => iIdxEndSample-lIdxCurrentSample) do |iInputRawBuffer, iNbrSamples, iNbrChannels|
109
+ lRawBuffer += iInputRawBuffer
110
+ end
111
+ # Profile this buffer
112
+ lChannelLevelValues = @VolumeUtils.measureLevel(lRawBuffer, iInputData.Header.NbrBitsPerSample, iInputData.Header.NbrChannels, lIdxCurrentEndSample - lIdxCurrentSample + 1, iRMSRatio)
113
+ # Combine the channel levels based on the RMS ratio also
114
+ lMaxValue = Rational(0, 1)
115
+ lRMSValue = Rational(0, 1)
116
+ lChannelLevelValues.each do |iLevelValue|
117
+ if (iLevelValue > lMaxValue)
118
+ lMaxValue = iLevelValue
119
+ end
120
+ lRMSValue += iLevelValue*iLevelValue
121
+ end
122
+ lRMSValue = Math.sqrt(lRMSValue/lChannelLevelValues.size).to_r
123
+ lLevelValue = lRMSValue*(iRMSRatio.to_r) + lMaxValue*(Rational(1)-iRMSRatio.to_r)
124
+ #log_debug "[#{lIdxCurrentSample} - #{lIdxCurrentEndSample}] - Level: #{lLevelValue}"
125
+ # If intervals are of length 1, the function is exactly the profile: no need to make intermediate points
126
+ if (lIdxCurrentEndSample == lIdxCurrentSample)
127
+ @Function[:Points] << [ Rational(lIdxCurrentSample), lLevelValue]
128
+ lIdxCurrentSample += 1
129
+ else
130
+ # Complete the function
131
+ if (@Function[:Points].empty?)
132
+ # First points: add also the point 0
133
+ @Function[:Points] = [ [ Rational(0, 1), lLevelValue] ]
134
+ end
135
+ # Add a point to the function in the middle of this interval
136
+ lPointX = lIdxCurrentSample - iIdxBeginSample + Rational(lIdxCurrentEndSample - lIdxCurrentSample + 1, 2)
137
+ @Function[:Points] << [lPointX, lLevelValue]
138
+ # Increment the cursor
139
+ lIdxCurrentSample = lIdxCurrentEndSample + 1
140
+ if (lIdxCurrentSample == iIdxEndSample + 1)
141
+ # The last point: add the ending one
142
+ @Function[:Points] << [Rational(iIdxEndSample - iIdxBeginSample, 1), lLevelValue]
143
+ end
144
+ end
145
+ $stdout.write("#{(lIdxCurrentSample*100)/(iIdxEndSample - iIdxBeginSample + 1)} %\015")
146
+ $stdout.flush
147
+ end
148
+ optimize
149
+ end
150
+
151
+ # Set directly a function from a hash
152
+ #
153
+ # Parameters::
154
+ # * *iHashFunction* (<em>map<Symbol,Object></em>): The hashed function
155
+ def set(iHashFunction)
156
+ @Function = iHashFunction
157
+ convertDataTypes
158
+ optimize
159
+ end
160
+
161
+ # Apply the function on the volume of a raw buffer
162
+ #
163
+ # Parameters::
164
+ # * *iInputData* (<em>WSK::Model::InputData</em>): The input data
165
+ # * *oOutputData* (<em>WSK::Model::DirectStream</em>): The output data
166
+ # * *iIdxBeginSample* (_Integer_): Index of the first sample beginning the volume transformation
167
+ # * *iIdxEndSample* (_Integer_): Index of the last sample ending the volume transformation
168
+ # * *iUnitDB* (_Boolean_): Are function values to be interpreted as DB units ?
169
+ def apply_on_volume(iInputData, oOutputData, iIdxBeginSample, iIdxEndSample, iUnitDB)
170
+ prepareFunctionUtils
171
+ lCFunction = @FunctionUtils.createCFunction(@Function, iIdxBeginSample, iIdxEndSample)
172
+ lIdxBufferSample = iIdxBeginSample
173
+ iInputData.each_raw_buffer(iIdxBeginSample, iIdxEndSample) do |iInputRawBuffer, iNbrSamples, iNbrChannels|
174
+ prepareVolumeUtils
175
+ oOutputData.pushRawBuffer(@VolumeUtils.applyVolumeFct(lCFunction, iInputRawBuffer, iInputData.Header.NbrBitsPerSample, iInputData.Header.NbrChannels, iNbrSamples, lIdxBufferSample, iUnitDB))
176
+ lIdxBufferSample += iNbrSamples
177
+ end
178
+ end
179
+
180
+ # Draw the function into a raw buffer
181
+ #
182
+ # Parameters::
183
+ # * *iInputData* (<em>WSK::Model::InputData</em>): The input data
184
+ # * *oOutputData* (<em>WSK::Model::DirectStream</em>): The output data
185
+ # * *iIdxBeginSample* (_Integer_): Index of the first sample beginning the volume transformation
186
+ # * *iIdxEndSample* (_Integer_): Index of the last sample ending the volume transformation
187
+ # * *iUnitDB* (_Boolean_): Are function values to be interpreted as DB units ?
188
+ # * *iMedianValue* (_Integer_): Median value to draw function ratio of 1.
189
+ def draw(iInputData, oOutputData, iIdxBeginSample, iIdxEndSample, iUnitDB, iMedianValue)
190
+ prepareFunctionUtils
191
+ lCFunction = @FunctionUtils.createCFunction(@Function, iIdxBeginSample, iIdxEndSample)
192
+ oOutputData.each_buffer(iIdxBeginSample, iIdxEndSample) do |iIdxBeginBufferSample, iIdxEndBufferSample|
193
+ prepareVolumeUtils
194
+ oOutputData.pushRawBuffer(@VolumeUtils.drawVolumeFct(lCFunction, iInputData.Header.NbrBitsPerSample, iInputData.Header.NbrChannels, iIdxEndBufferSample-iIdxBeginBufferSample+1, iIdxBeginBufferSample, iUnitDB, iMedianValue))
195
+ end
196
+ end
197
+
198
+ # Divide values by a given factor
199
+ #
200
+ # Parameters::
201
+ # * *iFactor* (_Rational_): Factor to divide by
202
+ def divide_by(iFactor)
203
+ case @Function[:FunctionType]
204
+ when FCTTYPE_PIECEWISE_LINEAR
205
+ @Function[:Points].each do |ioPoint|
206
+ ioPoint[1] /= iFactor
207
+ end
208
+ else
209
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
210
+ end
211
+ end
212
+
213
+ # Convert the Y units in DB equivalent
214
+ #
215
+ # Parameters::
216
+ # * *iMaxYValue* (_Rational_): Maximal Y value
217
+ def convert_to_db(iMaxYValue)
218
+ case @Function[:FunctionType]
219
+ when FCTTYPE_PIECEWISE_LINEAR
220
+ # Prepare variables for log computations
221
+ @Log2 = Math::log(2).to_r
222
+ @LogMax = value_log(iMaxYValue)
223
+ @Function[:Points].each do |ioPoint|
224
+ ioPoint[1] = value_val_2_db_Internal(ioPoint[1])
225
+ end
226
+ else
227
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
228
+ end
229
+ end
230
+
231
+ # Round values to a given precision
232
+ #
233
+ # Parameters::
234
+ # * *iPrecisionX* (_Rational_): The desired precision for X values (1000 will round to E-3)
235
+ # * *iPrecisionY* (_Rational_): The desired precision for Y values (1000 will round to E-3)
236
+ def round_to_precision(iPrecisionX, iPrecisionY)
237
+ case @Function[:FunctionType]
238
+ when FCTTYPE_PIECEWISE_LINEAR
239
+ @Function[:Points] = @Function[:Points].map do |iPoint|
240
+ next [ (Rational((iPoint[0]*iPrecisionX).round, 1))/iPrecisionX, (Rational((iPoint[1]*iPrecisionY).round, 1))/iPrecisionY ]
241
+ end
242
+ else
243
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
244
+ end
245
+ optimize
246
+ end
247
+
248
+ # Apply damping.
249
+ #
250
+ # Parameters::
251
+ # * *iSlopeUp* (_Rational_): The maximal value of slope when increasing (should be > 0), or nil if none
252
+ # * *iSlopeDown* (_Rational_): The minimal value of slope when decreasing (should be < 0), or nil if none
253
+ def apply_damping(iSlopeUp, iSlopeDown)
254
+ if ((iSlopeUp != nil) and
255
+ (iSlopeUp <= 0))
256
+ log_err "Upward slope (#{iSlopeUp}) has to be > 0"
257
+ elsif ((iSlopeDown != nil) and
258
+ (iSlopeDown >= 0))
259
+ log_err "Downward slope (#{iSlopeDown}) has to be < 0"
260
+ else
261
+ case @Function[:FunctionType]
262
+ when FCTTYPE_PIECEWISE_LINEAR
263
+ # Keep the first point
264
+ lNewPoints = [ @Function[:Points][0] ]
265
+ lIdxSegment = 0
266
+ while (lIdxSegment < @Function[:Points].size - 1)
267
+ # Compute the slope of this segment
268
+ #puts "lIdxSegment=#{lIdxSegment}/#{@Function[:Points].size} Points=[ [ #{sprintf('%.2f',@Function[:Points][lIdxSegment][0])}, #{sprintf('%.2f',@Function[:Points][lIdxSegment][1])} ], [ #{sprintf('%.2f',@Function[:Points][lIdxSegment+1][0])}, #{sprintf('%.2f',@Function[:Points][lIdxSegment+1][1])} ] ]"
269
+ lSegmentSlope = (@Function[:Points][lIdxSegment+1][1]-@Function[:Points][lIdxSegment][1])/(@Function[:Points][lIdxSegment+1][0]-@Function[:Points][lIdxSegment][0])
270
+ #puts "lIdxSegment=#{lIdxSegment}/#{@Function[:Points].size} Slope=#{sprintf('%.2f',lSegmentSlope)} (#{lSegmentSlope.precs.inspect})"
271
+ if (((lSegmentSlope > 0) and
272
+ (iSlopeUp != nil) and
273
+ (lSegmentSlope > iSlopeUp)) or
274
+ ((lSegmentSlope < 0) and
275
+ (iSlopeDown != nil) and
276
+ (lSegmentSlope < iSlopeDown)))
277
+ # Choose the correct damping slope depending on the direction
278
+ lSlope = nil
279
+ if (lSegmentSlope > 0)
280
+ lSlope = iSlopeUp
281
+ else
282
+ lSlope = iSlopeDown
283
+ end
284
+ # We have to apply damping starting the beginning of this segment.
285
+ # Find the next intersection between the damped segment and our function.
286
+ # The abscisse of the intersection
287
+ lIntersectX = nil
288
+ # A constant for the next loop
289
+ lDampedSegmentOffsetY = @Function[:Points][lIdxSegment][1] - @Function[:Points][lIdxSegment][0]*lSlope
290
+ lIdxSegmentIntersect = lIdxSegment + 1
291
+ while (lIdxSegmentIntersect < @Function[:Points].size - 1)
292
+ # Find if there is an intersection
293
+ lSegmentIntersectDistX = @Function[:Points][lIdxSegmentIntersect+1][0] - @Function[:Points][lIdxSegmentIntersect][0]
294
+ lSegmentIntersectDistY = @Function[:Points][lIdxSegmentIntersect+1][1] - @Function[:Points][lIdxSegmentIntersect][1]
295
+ lIntersectX = ((lDampedSegmentOffsetY - @Function[:Points][lIdxSegmentIntersect][1])*lSegmentIntersectDistX + @Function[:Points][lIdxSegmentIntersect][0]*lSegmentIntersectDistY)/(lSegmentIntersectDistY - lSlope*lSegmentIntersectDistX)
296
+ # Is lIntersectX among our range ?
297
+ if ((lIntersectX >= @Function[:Points][lIdxSegmentIntersect][0]) and
298
+ (lIntersectX <= @Function[:Points][lIdxSegmentIntersect+1][0]))
299
+ # We have an intersection in the segment beginning at point n. lIdxSegmentIntersect, exactly at abscisse lIntersectX.
300
+ break
301
+ else
302
+ # Erase it as we will test for it after the loop
303
+ lIntersectX = nil
304
+ end
305
+ lIdxSegmentIntersect += 1
306
+ end
307
+ # Here, lIdxSegmentIntersect can point to the last point if no intersection was found
308
+ if (lIntersectX == nil)
309
+ # We could not find any intersection
310
+ # We consider adding a point following the damped slope till the end of the function
311
+ lIntersectX = @Function[:Points][-1][0]
312
+ end
313
+ # Add the intersecting point (could be the last one)
314
+ lIntersectPoint = [ lIntersectX, (lIntersectX - @Function[:Points][lIdxSegment][0])*lSlope + @Function[:Points][lIdxSegment][1] ]
315
+ #puts "lIntersectX=#{lIntersectX.to_f} @Function[:Points][lIdxSegment][0]=#{@Function[:Points][lIdxSegment][0].to_f} lSlope=#{lSlope.to_f} @Function[:Points][lIdxSegment][1]=#{@Function[:Points][lIdxSegment][1].to_f} lIntersectPoint[1]=#{lIntersectPoint[1].to_f}"
316
+ lNewPoints << lIntersectPoint
317
+ # Continue after this intersection (we create also the intersecting point on our old points by modifying them)
318
+ @Function[:Points][lIdxSegmentIntersect] = lIntersectPoint
319
+ lIdxSegment = lIdxSegmentIntersect
320
+ else
321
+ # The slope is ok, keep this segment as it is
322
+ lNewPoints << @Function[:Points][lIdxSegment+1]
323
+ lIdxSegment += 1
324
+ end
325
+ end
326
+ # Replace our points with new ones
327
+ @Function[:Points] = lNewPoints
328
+ else
329
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
330
+ end
331
+ end
332
+ optimize
333
+ end
334
+
335
+ # Invert the abscisses of a function
336
+ def invert_abscisses
337
+ case @Function[:FunctionType]
338
+ when FCTTYPE_PIECEWISE_LINEAR
339
+ lNewPoints = []
340
+ lMinMaxX = @Function[:Points][0][0] + @Function[:Points][-1][0]
341
+ @Function[:Points].reverse_each do |iPoint|
342
+ lNewPoints << [lMinMaxX - iPoint[0], iPoint[1]]
343
+ end
344
+ @Function[:Points] = lNewPoints
345
+ else
346
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
347
+ end
348
+ end
349
+
350
+ # Get the function bounds
351
+ #
352
+ # Return::
353
+ # * _Rational_: Minimal X
354
+ # * _Rational_: Minimal Y
355
+ # * _Rational_: Maximal X
356
+ # * _Rational_: Maximal Y
357
+ def get_bounds
358
+ rMinX = nil
359
+ rMinY = nil
360
+ rMaxX = nil
361
+ rMaxY = nil
362
+
363
+ case @Function[:FunctionType]
364
+ when FCTTYPE_PIECEWISE_LINEAR
365
+ rMinX = @Function[:Points][0][0]
366
+ rMaxX = @Function[:Points][-1][0]
367
+ @Function[:Points].each do |iPoint|
368
+ if (rMinY == nil)
369
+ rMinY = iPoint[1]
370
+ rMaxY = iPoint[1]
371
+ else
372
+ if (rMinY > iPoint[1])
373
+ rMinY = iPoint[1]
374
+ end
375
+ if (rMaxY < iPoint[1])
376
+ rMaxY = iPoint[1]
377
+ end
378
+ end
379
+ end
380
+ else
381
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
382
+ end
383
+
384
+ return rMinX, rMinY, rMaxX, rMaxY
385
+ end
386
+
387
+ # Write the function to a file
388
+ #
389
+ # Parameters::
390
+ # * *iFileName* (_String_): File name to write
391
+ # * *iParams* (<em>map<Symbol,Object></em>): Additional parameters [optional = {}]:
392
+ # * *:floats* (_Boolean_): Do we write Float values ? [optional = false]
393
+ def write_to_file(iFileName, iParams = {})
394
+ lParams = {
395
+ # Default value
396
+ :floats => false
397
+ }.merge(iParams)
398
+ case @Function[:FunctionType]
399
+ when FCTTYPE_PIECEWISE_LINEAR
400
+ require 'pp'
401
+ lData = @Function
402
+ if (lParams[:floats])
403
+ # Convert to Floats
404
+ lData[:Points].map! do |iPoint|
405
+ next [ iPoint[0].to_f, iPoint[1].to_f ]
406
+ end
407
+ end
408
+ File.open(iFileName, 'w') do |oFile|
409
+ oFile.write(lData.pretty_inspect)
410
+ end
411
+ else
412
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
413
+ end
414
+ end
415
+
416
+ # Apply a mapping function to this function.
417
+ #
418
+ # Parameters::
419
+ # * *iMapFunction* (_Function_): The mapping function
420
+ def apply_map_function(iMapFunction)
421
+ case @Function[:FunctionType]
422
+ when FCTTYPE_PIECEWISE_LINEAR
423
+ case iMapFunction.function_data[:FunctionType]
424
+ when FCTTYPE_PIECEWISE_LINEAR
425
+ # Both functions are piecewise linear
426
+ # Algorithm:
427
+ # * For each segment of our function:
428
+ # * We look at the segments from the map function.
429
+ # * For each found segment:
430
+ # *** We find at which abscisses this segment will change values
431
+ # *** We change the sub-segment between those abscisses
432
+ lPoints = @Function[:Points]
433
+ lMapPoints = iMapFunction.function_data[:Points]
434
+ lNewPoints = []
435
+ lIdxSegment = 0
436
+ while (lIdxSegment < lPoints.size-1)
437
+ lBeginX = lPoints[lIdxSegment][0]
438
+ lBeginY = lPoints[lIdxSegment][1]
439
+ lEndX = lPoints[lIdxSegment+1][0]
440
+ lEndY = lPoints[lIdxSegment+1][1]
441
+ # The direction in which we are going to look for the map segments
442
+ lIncMapSegment = nil
443
+ if (lEndY >= lBeginY)
444
+ lIncMapSegment = true
445
+ else
446
+ lIncMapSegment = false
447
+ end
448
+ # Find the map function's segment containing the beginning of our segment
449
+ lIdxMapSegment = 0
450
+ if (lBeginY == lMapPoints[-1][0])
451
+ lIdxMapSegment = lMapPoints.size - 2
452
+ else
453
+ while (lBeginY >= lMapPoints[lIdxMapSegment+1][0])
454
+ lIdxMapSegment += 1
455
+ end
456
+ end
457
+ # Compute the new value of our segment beginning
458
+ lNewBeginY = lMapPoints[lIdxMapSegment][1] + ((lMapPoints[lIdxMapSegment+1][1]-lMapPoints[lIdxMapSegment][1])*(lBeginY-lMapPoints[lIdxMapSegment][0]))/(lMapPoints[lIdxMapSegment+1][0]-lMapPoints[lIdxMapSegment][0])
459
+ lNewPoints << [ lBeginX, lNewBeginY ]
460
+ # Get the next map segments unless we reach our segment's end
461
+ # !!! Find the next map segment according to the direction
462
+ if (lIncMapSegment)
463
+ while (lEndY > lMapPoints[lIdxMapSegment+1][0])
464
+ # We have a new map segment to consider in our segment
465
+ # Find the absciss at which our Y coordinates get the value lMapPoints[lIdxMapSegment+1][0]
466
+ lNewSegmentX = lBeginX + ((lEndX-lBeginX)*(lMapPoints[lIdxMapSegment+1][0] - lBeginY))/(lEndY-lBeginY)
467
+ lNewPoints << [ lNewSegmentX, lMapPoints[lIdxMapSegment+1][1] ]
468
+ lIdxMapSegment += 1
469
+ end
470
+ # Our segment ends before next map segment
471
+ else
472
+ while (lEndY < lMapPoints[lIdxMapSegment][0])
473
+ # We have a new map segment to consider in our segment
474
+ # Find the absciss at which our Y coordinates get the value lMapPoints[lIdxMapSegment][0]
475
+ lNewSegmentX = lBeginX + ((lEndX-lBeginX)*(lMapPoints[lIdxMapSegment][0] - lBeginY))/(lEndY-lBeginY)
476
+ lNewPoints << [ lNewSegmentX, lMapPoints[lIdxMapSegment][1] ]
477
+ lIdxMapSegment -= 1
478
+ end
479
+ # Our segment ends before previous map segment
480
+ end
481
+ # Write the segment end if it is the last one (otherwise it will be written by the next iteration)
482
+ if (lIdxSegment == lPoints.size-2)
483
+ lNewEndY = lMapPoints[lIdxMapSegment][1] + ((lMapPoints[lIdxMapSegment+1][1]-lMapPoints[lIdxMapSegment][1])*(lEndY-lMapPoints[lIdxMapSegment][0]))/(lMapPoints[lIdxMapSegment+1][0]-lMapPoints[lIdxMapSegment][0])
484
+ lNewPoints << [ lEndX, lNewEndY ]
485
+ end
486
+ lIdxSegment += 1
487
+ end
488
+ # Replace with new points
489
+ @Function[:Points] = lNewPoints
490
+ else
491
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
492
+ end
493
+ else
494
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
495
+ end
496
+ optimize
497
+ end
498
+
499
+ # Remove intermediate abscisses that are too close to each other
500
+ #
501
+ # Parameters::
502
+ # * *iMinDistance* (_Rational_): Minimal distance for abscisses triplets to have
503
+ def remove_noise_abscisses(iMinDistance)
504
+ case @Function[:FunctionType]
505
+ when FCTTYPE_PIECEWISE_LINEAR
506
+ lNewPoints = [ @Function[:Points][0] ]
507
+ lIdxPoint = 0
508
+ while (lIdxPoint < @Function[:Points].size - 1)
509
+ # Now we skip the next last point among iMinDistance range
510
+ lPointX = @Function[:Points][lIdxPoint][0]
511
+ lIdxOtherPoint = lIdxPoint + 1
512
+ while ((lIdxOtherPoint < @Function[:Points].size) and
513
+ (@Function[:Points][lIdxOtherPoint][0] - lPointX < iMinDistance))
514
+ lIdxOtherPoint += 1
515
+ end
516
+ # Either lIdxOtherPoint is beyond the end, or it points to the first point that is beyond iMinDistance
517
+ # We add the previous point if it is not already ours
518
+ if (lIdxOtherPoint-1 > lIdxPoint)
519
+ lNewPoints << @Function[:Points][lIdxOtherPoint-1]
520
+ # And we continue searching from this new added point
521
+ lIdxPoint = lIdxOtherPoint-1
522
+ else
523
+ # It is our point, continue on to the next one
524
+ lNewPoints << @Function[:Points][lIdxOtherPoint]
525
+ lIdxPoint = lIdxOtherPoint
526
+ end
527
+ end
528
+ @Function[:Points] = lNewPoints
529
+ else
530
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
531
+ end
532
+ optimize
533
+ end
534
+
535
+ # Substract a function to this function
536
+ #
537
+ # Parameters::
538
+ # * *iSubFunction* (_Function_): The function to substract
539
+ def substract_function(iSubFunction)
540
+ case @Function[:FunctionType]
541
+ when FCTTYPE_PIECEWISE_LINEAR
542
+ case iSubFunction.function_data[:FunctionType]
543
+ when FCTTYPE_PIECEWISE_LINEAR
544
+ lNewPoints = []
545
+ unionXWithFunction_PiecewiseLinear(iSubFunction) do |iX, iY, iOtherY|
546
+ if (iY == nil)
547
+ lNewPoints << [ iX, -iOtherY ]
548
+ elsif (iOtherY == nil)
549
+ lNewPoints << [ iX, iY ]
550
+ else
551
+ lNewPoints << [ iX, iY - iOtherY ]
552
+ end
553
+ end
554
+ # Replace with new points
555
+ @Function[:Points] = lNewPoints
556
+ else
557
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
558
+ end
559
+ else
560
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
561
+ end
562
+ optimize
563
+ end
564
+
565
+ # Divide this function by another function
566
+ #
567
+ # Parameters::
568
+ # * *iDivFunction* (_Function_): The function that divides
569
+ def divide_by_function(iDivFunction)
570
+ case @Function[:FunctionType]
571
+ when FCTTYPE_PIECEWISE_LINEAR
572
+ case iDivFunction.function_data[:FunctionType]
573
+ when FCTTYPE_PIECEWISE_LINEAR
574
+ lNewPoints = []
575
+ unionXWithFunction_PiecewiseLinear(iDivFunction) do |iX, iY, iOtherY|
576
+ if (iY == nil)
577
+ lNewPoints << [ iX, 0 ]
578
+ elsif (iOtherY == nil)
579
+ lNewPoints << [ iX, 0 ]
580
+ else
581
+ lNewPoints << [ iX, iY / iOtherY ]
582
+ end
583
+ end
584
+ # Replace with new points
585
+ @Function[:Points] = lNewPoints
586
+ else
587
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
588
+ end
589
+ else
590
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
591
+ end
592
+ optimize
593
+ end
594
+
595
+ # Get the internal function data
596
+ #
597
+ # Return::
598
+ # * <em>map<Symbol,Object></em>: The internal function data
599
+ def function_data
600
+ return @Function
601
+ end
602
+
603
+ # Compute the log of a function value.
604
+ #
605
+ # Parameters::
606
+ # * *iValue* (_Rational_): The value
607
+ def value_log(iValue)
608
+ return Math::log(iValue).to_r
609
+ end
610
+
611
+ # Compute a DB value out of a ratio using function values
612
+ #
613
+ # Parameters::
614
+ # * *iValue* (_Rational_): The value
615
+ # * *iMaxValue* (_Rational_): The maximal value
616
+ # Return::
617
+ # * _Rational_: Its corresponding db
618
+ def value_val_2_db(iValue, iMaxValue)
619
+ @Log2 = Math::log(2).to_r
620
+ @LogMax = value_log(iMaxValue)
621
+
622
+ return value_val_2_db_Internal(iValue)
623
+ end
624
+
625
+ private
626
+
627
+ # Optimize the function internal representation without modifying it.
628
+ def optimize
629
+ case @Function[:FunctionType]
630
+ when FCTTYPE_PIECEWISE_LINEAR
631
+ # Join segments that have the same slope
632
+ lNewPoints = [ @Function[:Points][0] ]
633
+ lLastSlope = (@Function[:Points][1][1]-@Function[:Points][0][1])/(@Function[:Points][1][0]-@Function[:Points][0][0])
634
+ lIdxSegment = 1
635
+ while (lIdxSegment < @Function[:Points].size - 1)
636
+ # Compute this segment's slope
637
+ lSlope = (@Function[:Points][lIdxSegment+1][1]-@Function[:Points][lIdxSegment][1])/(@Function[:Points][lIdxSegment+1][0]-@Function[:Points][lIdxSegment][0])
638
+ if (lLastSlope != lSlope)
639
+ # We are changing slopes
640
+ lNewPoints << @Function[:Points][lIdxSegment]
641
+ lLastSlope = lSlope
642
+ end
643
+ lIdxSegment += 1
644
+ end
645
+ # Add last point
646
+ lNewPoints << @Function[:Points][-1]
647
+ # Change points
648
+ @Function[:Points] = lNewPoints
649
+ else
650
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
651
+ end
652
+ end
653
+
654
+ # Convert contained objects into the types used for function values
655
+ def convertDataTypes
656
+ case @Function[:FunctionType]
657
+ when FCTTYPE_PIECEWISE_LINEAR
658
+ @Function[:Points] = @Function[:Points].map do |iPoint|
659
+ lNewPointX = nil
660
+ lNewPointY = nil
661
+ if (iPoint[0].is_a?(Rational))
662
+ lNewPointX = iPoint[0]
663
+ elsif (iPoint[0].is_a?(Float))
664
+ lNewPointX = iPoint[0].to_r
665
+ else
666
+ lNewPointX = Rational(iPoint[0])
667
+ end
668
+ if (iPoint[1].is_a?(Rational))
669
+ lNewPointY = iPoint[1]
670
+ elsif (iPoint[1].is_a?(Float))
671
+ lNewPointY = iPoint[1].to_r
672
+ else
673
+ lNewPointY = Rational(iPoint[1])
674
+ end
675
+ next [ lNewPointX, lNewPointY ]
676
+ end
677
+ else
678
+ log_err "Unknown function type: #{@Function[:FunctionType]}"
679
+ end
680
+ end
681
+
682
+ # Prepare the Function utils C library.
683
+ # This can be called several times.
684
+ def prepareFunctionUtils
685
+ if (@FunctionUtils == nil)
686
+ require 'WSK/FunctionUtils/FunctionUtils'
687
+ @FunctionUtils = WSK::FunctionUtils::FunctionUtils.new
688
+ end
689
+ end
690
+
691
+ # Prepare the Volume utils C library.
692
+ # This can be called several times.
693
+ def prepareVolumeUtils
694
+ if (@VolumeUtils == nil)
695
+ require 'WSK/VolumeUtils/VolumeUtils'
696
+ @VolumeUtils = WSK::VolumeUtils::VolumeUtils.new
697
+ end
698
+ end
699
+
700
+ # Find abscisses of both functions and the corresponding Y values
701
+ #
702
+ # Parameters::
703
+ # * *iOtherFunction* (_Function_): The other function
704
+ # * *CodeBlock*: Code called for each point found:
705
+ # * *iX* (_Rational_): The corresponding abscisse (can be nil if none)
706
+ # * *iY* (_Rational_): The corresponding Y value for this function (can be nil if none)
707
+ # * *iOtherY* (_Rational_): The corresponding Y value for the other function
708
+ def unionXWithFunction_PiecewiseLinear(iOtherFunction)
709
+ lPoints = @Function[:Points]
710
+ lOtherPoints = iOtherFunction.function_data[:Points]
711
+ # Get all the abscisses sorted
712
+ lXList = (lPoints.map { |iPoint| next iPoint[0] } + lOtherPoints.map { |iPoint| next iPoint[0] }).sort.uniq
713
+ # Read segments abscisse by abscisse
714
+ lIdxSegment = 0
715
+ lIdxOtherSegment = 0
716
+ lXList.each do |iX|
717
+ if (lPoints[lIdxSegment] == nil)
718
+ # No abscisse on lPoints for this iX
719
+ # Forcefully we have lOtherPoints[lIdxOtherSegment][0] == iX
720
+ yield(iX, nil, lOtherPoints[lIdxOtherSegment][1])
721
+ lIdxOtherSegment += 1
722
+ elsif (lOtherPoints[lIdxOtherSegment] == nil)
723
+ # No abscisse on lOtherPoints for this iX
724
+ # Forcefully we have lPoints[lIdxSegment][0] == iX
725
+ yield(iX, lPoints[lIdxSegment][1], nil)
726
+ lIdxSegment += 1
727
+ elsif (lPoints[lIdxSegment][0] == iX)
728
+ # lPoints has this abscisse
729
+ if (lOtherPoints[lIdxOtherSegment][0] == iX)
730
+ # If both functions have a point here, it's easy.
731
+ yield(iX, lPoints[lIdxSegment][1], lOtherPoints[lIdxOtherSegment][1])
732
+ lIdxOtherSegment += 1
733
+ else
734
+ # Compute the Y value for the other function
735
+ yield(iX, lPoints[lIdxSegment][1], lOtherPoints[lIdxOtherSegment-1][1] + ((lOtherPoints[lIdxOtherSegment][1] - lOtherPoints[lIdxOtherSegment-1][1])*(iX - lOtherPoints[lIdxOtherSegment-1][0]))/(lOtherPoints[lIdxOtherSegment][0] - lOtherPoints[lIdxOtherSegment-1][0]))
736
+ end
737
+ lIdxSegment += 1
738
+ else
739
+ # We have forcefully lOtherPoints[lIdxOtherSegment][0] == iX
740
+ # Compute the Y value for this function
741
+ yield(iX, lPoints[lIdxSegment-1][1] + ((lPoints[lIdxSegment][1] - lPoints[lIdxSegment-1][1])*(iX - lPoints[lIdxSegment-1][0]))/(lPoints[lIdxSegment][0] - lPoints[lIdxSegment-1][0]), lOtherPoints[lIdxOtherSegment][1])
742
+ lIdxOtherSegment += 1
743
+ end
744
+ end
745
+ end
746
+
747
+ # Convert a value to its db notation.
748
+ # Operate on the function values numbers.
749
+ # !!! Prerequisites: The following variables have to be set before calling this function
750
+ # * @Log2: Contains log(2)
751
+ # * @LogMax: Contains log(MaximalValue)
752
+ #
753
+ # Parameters::
754
+ # * *iValue* (_Rational_): The value
755
+ # Return::
756
+ # * _Rational_: Its corresponding db
757
+ def value_val_2_db_Internal(iValue)
758
+ if (iValue == 0)
759
+ # -Infinity
760
+ return -1.0/0.0
761
+ else
762
+ return -6*(@LogMax-value_log(iValue.abs))/@Log2
763
+ end
764
+ end
765
+
766
+ end
767
+
768
+ end
769
+
770
+ end