WaveSwissKnife 0.0.1.20101110-x86-cygwin

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. data/AUTHORS +1 -0
  2. data/ChangeLog +5 -0
  3. data/Credits +3 -0
  4. data/LICENSE +31 -0
  5. data/README +18 -0
  6. data/ReleaseInfo +8 -0
  7. data/TODO +2 -0
  8. data/bin/WSK.rb +14 -0
  9. data/ext/WSK/AnalyzeUtils/AnalyzeUtils.c +272 -0
  10. data/ext/WSK/AnalyzeUtils/AnalyzeUtils.o +0 -0
  11. data/ext/WSK/AnalyzeUtils/AnalyzeUtils.so +0 -0
  12. data/ext/WSK/AnalyzeUtils/Makefile +149 -0
  13. data/ext/WSK/AnalyzeUtils/build.rb +18 -0
  14. data/ext/WSK/ArithmUtils/ArithmUtils.c +862 -0
  15. data/ext/WSK/ArithmUtils/ArithmUtils.o +0 -0
  16. data/ext/WSK/ArithmUtils/ArithmUtils.so +0 -0
  17. data/ext/WSK/ArithmUtils/Makefile +149 -0
  18. data/ext/WSK/ArithmUtils/build.rb +20 -0
  19. data/ext/WSK/FFTUtils/FFTUtils.c +662 -0
  20. data/ext/WSK/FFTUtils/FFTUtils.o +0 -0
  21. data/ext/WSK/FFTUtils/FFTUtils.so +0 -0
  22. data/ext/WSK/FFTUtils/Makefile +149 -0
  23. data/ext/WSK/FFTUtils/build.rb +20 -0
  24. data/ext/WSK/FunctionUtils/FunctionUtils.c +182 -0
  25. data/ext/WSK/FunctionUtils/FunctionUtils.o +0 -0
  26. data/ext/WSK/FunctionUtils/FunctionUtils.so +0 -0
  27. data/ext/WSK/FunctionUtils/Makefile +149 -0
  28. data/ext/WSK/FunctionUtils/build.rb +20 -0
  29. data/ext/WSK/SilentUtils/Makefile +149 -0
  30. data/ext/WSK/SilentUtils/SilentUtils.c +431 -0
  31. data/ext/WSK/SilentUtils/SilentUtils.o +0 -0
  32. data/ext/WSK/SilentUtils/SilentUtils.so +0 -0
  33. data/ext/WSK/SilentUtils/build.rb +18 -0
  34. data/ext/WSK/VolumeUtils/Makefile +149 -0
  35. data/ext/WSK/VolumeUtils/VolumeUtils.c +494 -0
  36. data/ext/WSK/VolumeUtils/VolumeUtils.o +0 -0
  37. data/ext/WSK/VolumeUtils/VolumeUtils.so +0 -0
  38. data/ext/WSK/VolumeUtils/build.rb +20 -0
  39. data/lib/WSK/Actions/Analyze.rb +176 -0
  40. data/lib/WSK/Actions/ApplyMap.desc.rb +15 -0
  41. data/lib/WSK/Actions/ApplyMap.rb +57 -0
  42. data/lib/WSK/Actions/ApplyVolumeFct.desc.rb +30 -0
  43. data/lib/WSK/Actions/ApplyVolumeFct.rb +72 -0
  44. data/lib/WSK/Actions/Compare.desc.rb +25 -0
  45. data/lib/WSK/Actions/Compare.rb +238 -0
  46. data/lib/WSK/Actions/ConstantCompare.desc.rb +20 -0
  47. data/lib/WSK/Actions/ConstantCompare.rb +61 -0
  48. data/lib/WSK/Actions/Cut.desc.rb +20 -0
  49. data/lib/WSK/Actions/Cut.rb +60 -0
  50. data/lib/WSK/Actions/CutFirstSignal.desc.rb +25 -0
  51. data/lib/WSK/Actions/CutFirstSignal.rb +72 -0
  52. data/lib/WSK/Actions/DCShifter.desc.rb +15 -0
  53. data/lib/WSK/Actions/DCShifter.rb +67 -0
  54. data/lib/WSK/Actions/DrawFct.desc.rb +20 -0
  55. data/lib/WSK/Actions/DrawFct.rb +59 -0
  56. data/lib/WSK/Actions/FFT.rb +104 -0
  57. data/lib/WSK/Actions/GenAllValues.rb +67 -0
  58. data/lib/WSK/Actions/GenConstant.desc.rb +20 -0
  59. data/lib/WSK/Actions/GenConstant.rb +56 -0
  60. data/lib/WSK/Actions/GenSawtooth.rb +57 -0
  61. data/lib/WSK/Actions/GenSine.desc.rb +20 -0
  62. data/lib/WSK/Actions/GenSine.rb +73 -0
  63. data/lib/WSK/Actions/Identity.rb +43 -0
  64. data/lib/WSK/Actions/Mix.desc.rb +15 -0
  65. data/lib/WSK/Actions/Mix.rb +149 -0
  66. data/lib/WSK/Actions/Multiply.desc.rb +15 -0
  67. data/lib/WSK/Actions/Multiply.rb +73 -0
  68. data/lib/WSK/Actions/NoiseGate.desc.rb +35 -0
  69. data/lib/WSK/Actions/NoiseGate.rb +129 -0
  70. data/lib/WSK/Actions/SilenceInserter.desc.rb +20 -0
  71. data/lib/WSK/Actions/SilenceInserter.rb +87 -0
  72. data/lib/WSK/Actions/SilenceRemover.desc.rb +30 -0
  73. data/lib/WSK/Actions/SilenceRemover.rb +74 -0
  74. data/lib/WSK/Actions/VolumeProfile.desc.rb +35 -0
  75. data/lib/WSK/Actions/VolumeProfile.rb +63 -0
  76. data/lib/WSK/Common.rb +292 -0
  77. data/lib/WSK/FFT.rb +527 -0
  78. data/lib/WSK/Functions.rb +770 -0
  79. data/lib/WSK/Launcher.rb +216 -0
  80. data/lib/WSK/Maps.rb +29 -0
  81. data/lib/WSK/Model/CachedBufferReader.rb +151 -0
  82. data/lib/WSK/Model/Header.rb +133 -0
  83. data/lib/WSK/Model/InputData.rb +193 -0
  84. data/lib/WSK/Model/RawReader.rb +78 -0
  85. data/lib/WSK/Model/WaveReader.rb +91 -0
  86. data/lib/WSK/OutputInterfaces/DirectStream.rb +146 -0
  87. data/lib/WSK/RIFFReader.rb +60 -0
  88. metadata +151 -0
@@ -0,0 +1,770 @@
1
+ #--
2
+ # Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
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 readFromFile(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 readFromInputVolume(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.eachRawBuffer(lIdxCurrentSample, lIdxCurrentEndSample, :NbrSamplesPrefetch => 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
+ #logDebug "[#{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 applyOnVolume(iInputData, oOutputData, iIdxBeginSample, iIdxEndSample, iUnitDB)
170
+ prepareFunctionUtils
171
+ lCFunction = @FunctionUtils.createCFunction(@Function, iIdxBeginSample, iIdxEndSample)
172
+ lIdxBufferSample = iIdxBeginSample
173
+ iInputData.eachRawBuffer(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.eachBuffer(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 divideBy(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
+ logErr "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 convertToDB(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 = valueLog(iMaxYValue)
223
+ @Function[:Points].each do |ioPoint|
224
+ ioPoint[1] = valueVal2db_Internal(ioPoint[1])
225
+ end
226
+ else
227
+ logErr "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 roundToPrecision(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
+ logErr "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 applyDamping(iSlopeUp, iSlopeDown)
254
+ if ((iSlopeUp != nil) and
255
+ (iSlopeUp <= 0))
256
+ logErr "Upward slope (#{iSlopeUp}) has to be > 0"
257
+ elsif ((iSlopeDown != nil) and
258
+ (iSlopeDown >= 0))
259
+ logErr "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
+ logErr "Unknown function type: #{@Function[:FunctionType]}"
330
+ end
331
+ end
332
+ optimize
333
+ end
334
+
335
+ # Invert the abscisses of a function
336
+ def invertAbscisses
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
+ logErr "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 getBounds
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
+ logErr "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 writeToFile(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
+ logErr "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 applyMapFunction(iMapFunction)
421
+ case @Function[:FunctionType]
422
+ when FCTTYPE_PIECEWISE_LINEAR
423
+ case iMapFunction.functionData[: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.functionData[: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
+ logErr "Unknown function type: #{@Function[:FunctionType]}"
492
+ end
493
+ else
494
+ logErr "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 removeNoiseAbscisses(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
+ logErr "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 substractFunction(iSubFunction)
540
+ case @Function[:FunctionType]
541
+ when FCTTYPE_PIECEWISE_LINEAR
542
+ case iSubFunction.functionData[: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
+ logErr "Unknown function type: #{@Function[:FunctionType]}"
558
+ end
559
+ else
560
+ logErr "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 divideByFunction(iDivFunction)
570
+ case @Function[:FunctionType]
571
+ when FCTTYPE_PIECEWISE_LINEAR
572
+ case iDivFunction.functionData[: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
+ logErr "Unknown function type: #{@Function[:FunctionType]}"
588
+ end
589
+ else
590
+ logErr "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 functionData
600
+ return @Function
601
+ end
602
+
603
+ # Compute the log of a function value.
604
+ #
605
+ # Parameters:
606
+ # * *iValue* (_Rational_): The value
607
+ def valueLog(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 valueVal2db(iValue, iMaxValue)
619
+ @Log2 = Math::log(2).to_r
620
+ @LogMax = valueLog(iMaxValue)
621
+
622
+ return valueVal2db_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
+ logErr "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
+ logErr "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.functionData[: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 valueVal2db_Internal(iValue)
758
+ if (iValue == 0)
759
+ # -Infinity
760
+ return -1.0/0.0
761
+ else
762
+ return -6*(@LogMax-valueLog(iValue.abs))/@Log2
763
+ end
764
+ end
765
+
766
+ end
767
+
768
+ end
769
+
770
+ end