WaveSwissKnife 0.0.1.20101110-x86-cygwin

Sign up to get free protection for your applications and to get access to all the features.
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
data/lib/WSK/FFT.rb ADDED
@@ -0,0 +1,527 @@
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
+ module WSK
7
+
8
+ # Module including helper methods using FFT processes.
9
+ # This module uses C methods declared in FFTUtils.
10
+ module FFT
11
+
12
+ # Frequencies used to compute FFT profiles.
13
+ # !!! When changing these values, all fft.result files generated are invalidated
14
+ FREQINDEX_FIRST = -59
15
+ FREQINDEX_LAST = 79
16
+
17
+ # Scale used to measure FFT values
18
+ FFTDIST_MAX = 10000000000000
19
+
20
+ # Frequency of the FFT samples to take (Hz)
21
+ # !!! If changed, all fft.result files generated are invalidated
22
+ FFTSAMPLE_FREQ = 10
23
+ # Number of FFT buffers needed to detect a constant Moving Average.
24
+ # !!! If changed, all fft.result files generated are invalidated
25
+ FFTNBRSAMPLES_HISTORY = 5
26
+ # Number of samples to prefetch from the disk when reading.
27
+ # This should reflect the average number of FFT samples read when getNextFFTSample is invoked once.
28
+ # !!! If changed, all fft.result files generated are invalidated
29
+ FFT_SAMPLES_PREFETCH = 30
30
+ # Added tolerance percentage of distance between the maximal history distance and the average silence distance
31
+ FFTDISTANCE_MAX_HISTORY_TOLERANCE_PC = 20.0
32
+ # Added tolerance percentage of distance between the average history distance and the average silence distance
33
+ FFTDISTANCE_AVERAGE_HISTORY_TOLERANCE_PC = 0.0
34
+
35
+ class FFTComputing
36
+
37
+ # Constructor
38
+ # The trigo cache is VERY useful when several FFT of the same length are computed.
39
+ #
40
+ # Parameters:
41
+ # * *iUseTrigoCache* (_Boolean_): Do we use the trigonometric cache ?
42
+ # * *iHeader* (<em>WSK::Model::Header</em>): Header of the data we will perform FFT on.
43
+ def initialize(iUseTrigoCache, iHeader)
44
+ @UseTrigoCache, @Header = iUseTrigoCache, iHeader
45
+ require 'WSK/FFTUtils/FFTUtils'
46
+ @FFTUtils = FFTUtils::FFTUtils.new
47
+ # Initialize FFT utils objects
48
+ @W = @FFTUtils.createWi(FREQINDEX_FIRST, FREQINDEX_LAST, @Header.SampleRate)
49
+ @NbrFreq = FREQINDEX_LAST - FREQINDEX_FIRST + 1
50
+ if (@UseTrigoCache)
51
+ # Initialize the cache of trigonometric values if not done already
52
+ if ((defined?(@@TrigoCacheSampleRate) == nil) or
53
+ (@@TrigoCacheSampleRate != @Header.SampleRate))
54
+ @@TrigoCacheSampleRate = @Header.SampleRate
55
+ @@TrigoCache = @FFTUtils.initTrigoCache(@W, @NbrFreq, @Header.SampleRate/FFTSAMPLE_FREQ)
56
+ end
57
+ end
58
+ # Initialize the cos and sin arrays
59
+ resetData
60
+ end
61
+
62
+ # Reset the cos and sin arrays
63
+ def resetData
64
+ @SumCos = @FFTUtils.initSumArray(@NbrFreq, @Header.NbrChannels)
65
+ @SumSin = @FFTUtils.initSumArray(@NbrFreq, @Header.NbrChannels)
66
+ @NbrSamples = 0
67
+ end
68
+
69
+ # Add FFT coefficients based on a buffer
70
+ #
71
+ # Parameters:
72
+ # * *iRawBuffer* (_String_): The raw buffer
73
+ # * *iNbrSamples* (_Integer_): Number of samples to take from this buffer to compute the FFT
74
+ def completeFFT(iRawBuffer, iNbrSamples)
75
+ if (@UseTrigoCache)
76
+ @FFTUtils.completeSumCosSin(iRawBuffer, @NbrSamples, @Header.NbrBitsPerSample, iNbrSamples, @Header.NbrChannels, @NbrFreq, nil, @@TrigoCache, @SumCos, @SumSin)
77
+ else
78
+ @FFTUtils.completeSumCosSin(iRawBuffer, @NbrSamples, @Header.NbrBitsPerSample, iNbrSamples, @Header.NbrChannels, @NbrFreq, @W, nil, @SumCos, @SumSin)
79
+ end
80
+ @NbrSamples += iNbrSamples
81
+ end
82
+
83
+ # Get the resulting FFT profile
84
+ #
85
+ # Return:
86
+ # * <em>[Integer,Integer,list<list<Integer>></em>]: Number of bits per sample, number of samples, list of FFT coefficients, per frequency, per channel
87
+ def getFFTProfile
88
+ return [@Header.NbrBitsPerSample, @NbrSamples, @FFTUtils.computeFFT(@Header.NbrChannels, @NbrFreq, @SumCos, @SumSin)]
89
+ end
90
+
91
+ end
92
+
93
+ # To be used if GMP library is absent.
94
+ # # Compare 2 FFT profiles and measure their distance.
95
+ # # Here is an FFT profile structure:
96
+ # # [ Integer, Integer, list<list<Integer>> ]
97
+ # # [ NbrBitsPerSample, NbrSamples, FFTValues ]
98
+ # # FFTValues are declined per channel, per frequency index.
99
+ # # Bits per sample and number of samples are taken into account to relatively compare the profiles.
100
+ # #
101
+ # # Parameters:
102
+ # # * *iProfile1* (<em>[Integer,Integer,list<list<Integer>>]</em>): Profile 1
103
+ # # * *iProfile2* (<em>[Integer,Integer,list<list<Integer>>]</em>): Profile 2
104
+ # # Return:
105
+ # # * _Integer_: Distance (Profile 2 - Profile 1). The scale is given by FFTDIST_MAX.
106
+ # def distFFTProfiles(iProfile1, iProfile2)
107
+ # # Return the max of the distances of each frequency coefficient
108
+ # rMaxDist = 0.0
109
+ #
110
+ # iNbrBitsPerSample1, iNbrSamples1, iFFT1 = iProfile1
111
+ # iNbrBitsPerSample2, iNbrSamples2, iFFT2 = iProfile2
112
+ #
113
+ # # Each value is limited by the maximum value of 2*(NbrSamples*MaxAbsValue)^2
114
+ # lMaxFFTValue1 = Float(2*((iNbrSamples1*(2**(iNbrBitsPerSample1-1)))**2))
115
+ # lMaxFFTValue2 = Float(2*((iNbrSamples2*(2**(iNbrBitsPerSample2-1)))**2))
116
+ # iFFT1.each_with_index do |iFFT1ChannelValues, iIdxFreq|
117
+ # iFFT2ChannelValues = iFFT2[iIdxFreq]
118
+ # iFFT1ChannelValues.each_with_index do |iFFT1Value, iIdxChannel|
119
+ # iFFT2Value = iFFT2ChannelValues[iIdxChannel]
120
+ # # Compute iFFT2Value - iFFT1Value, on a scale of FFTDIST_MAX
121
+ # lDist = iFFT2Value/lMaxFFTValue2 - iFFT1Value/lMaxFFTValue1
122
+ ## logDebug "[Freq #{iIdxFreq}] [Ch #{iIdxChannel}] - Distance = #{lDist}"
123
+ # if (lDist > rMaxDist)
124
+ # rMaxDist = lDist
125
+ # end
126
+ # end
127
+ # end
128
+ #
129
+ # return (rMaxDist*FFTDIST_MAX).to_i
130
+ # end
131
+
132
+ # Get the next sample that has an FFT buffer similar to a given FFT profile
133
+ #
134
+ # Parameters:
135
+ # * *iIdxFirstSample* (_Integer_): First sample we are trying from
136
+ # * *iFFTProfile* (<em>[Integer,Integer,list<list<Integer>>]</em>): The FFT profile
137
+ # * *iInputData* (_InputData_): The input data to read
138
+ # * *iMaxFFTDistance* (_Integer_): Maximal acceptable distance with the FFT. Above this distance we don't consider averaging.
139
+ # * *iThresholds* (<em>list<[Integer,Integer]></em>): The thresholds that should contain the signal we are evaluating.
140
+ # * *iBackwardsSearch* (_Boolean_): Do we search backwards ?
141
+ # * *iIdxLastPossibleSample* (_Integer_): Index of the sample marking the limit of the search
142
+ # Return:
143
+ # * _Integer_: Meaning of the given sample:
144
+ # ** *0*: The sample has been found correctly and returned
145
+ # ** *1*: The sample could not be found because thresholds were hit: the first sample hitting the thresholds is returned
146
+ # ** *2*: The sample could not be found because the limit of search was hit before. The returned sample can be ignored.
147
+ # * _Integer_: Index of the sample (can be 1 after the end)
148
+ def getNextFFTSample(iIdxFirstSample, iFFTProfile, iInputData, iMaxFFTDistance, iThresholds, iBackwardsSearch, iIdxLastPossibleSample)
149
+ rResultCode = 0
150
+ rCurrentSample = iIdxFirstSample
151
+
152
+ if (iBackwardsSearch)
153
+ logDebug "== Looking for the previous sample matching FFT before #{iIdxFirstSample}, with a limit on sample #{iIdxLastPossibleSample} and a FFT distance of #{iMaxFFTDistance} ..."
154
+ else
155
+ logDebug "== Looking for the next sample matching FFT after #{iIdxFirstSample}, with a limit on sample #{iIdxLastPossibleSample} and a FFT distance of #{iMaxFFTDistance} ..."
156
+ end
157
+
158
+ # Object that will create the FFT
159
+ lFFTComputing = FFTComputing.new(true, iInputData.Header)
160
+ # Create the C FFT Profile
161
+ lFFTUtils = FFTUtils::FFTUtils.new
162
+ lReferenceFFTProfile = lFFTUtils.createCFFTProfile(iFFTProfile)
163
+ # Historical values of FFT diffs to know when it is stable
164
+ # This is the implementation of the Moving Average algorithm.
165
+ # We are just interested in the difference of 2 different Moving Averages. Therefore comparing the oldest history value with the new one is enough.
166
+ # Cycling buffer of size FFTNBRSAMPLES_HISTORY
167
+ # list< Integer >
168
+ lHistory = []
169
+ lIdxOldestHistory = 0
170
+ # The sum of all the history entries: used to compare with the maximal average distance
171
+ lSumHistory = 0
172
+ lSumMaxFFTDistance = (iMaxFFTDistance*FFTNBRSAMPLES_HISTORY*(1+FFTDISTANCE_AVERAGE_HISTORY_TOLERANCE_PC/100)).to_i
173
+ lMaxHistoryFFTDistance = (iMaxFFTDistance*(1+FFTDISTANCE_MAX_HISTORY_TOLERANCE_PC/100)).to_i
174
+ lContinueSearching = nil
175
+ if (iBackwardsSearch)
176
+ lContinueSearching = (rCurrentSample >= iIdxLastPossibleSample)
177
+ else
178
+ lContinueSearching = (rCurrentSample <= iIdxLastPossibleSample)
179
+ end
180
+ while (lContinueSearching)
181
+ # Compute the number of samples needed to have a valid FFT.
182
+ # Modify this number if it exceeds the range we have
183
+ lNbrSamplesFFTMax = iInputData.Header.SampleRate/FFTSAMPLE_FREQ
184
+ lIdxBeginFFTSample = nil
185
+ lIdxEndFFTSample = nil
186
+ if (iBackwardsSearch)
187
+ lIdxBeginFFTSample = rCurrentSample-lNbrSamplesFFTMax+1
188
+ lIdxEndFFTSample = rCurrentSample
189
+ if (lIdxBeginFFTSample <= iIdxLastPossibleSample-1)
190
+ lIdxBeginFFTSample = iIdxLastPossibleSample
191
+ end
192
+ else
193
+ lIdxBeginFFTSample = rCurrentSample
194
+ lIdxEndFFTSample = rCurrentSample+lNbrSamplesFFTMax-1
195
+ if (lIdxEndFFTSample >= iIdxLastPossibleSample+1)
196
+ lIdxEndFFTSample = iIdxLastPossibleSample
197
+ end
198
+ end
199
+ lNbrSamplesFFT = lIdxEndFFTSample-lIdxBeginFFTSample+1
200
+ # Load an FFT buffer of this
201
+ lFFTBuffer = ''
202
+ lIdxCurrentSample = rCurrentSample
203
+ if (iBackwardsSearch)
204
+ iInputData.eachReverseRawBuffer(lIdxBeginFFTSample, lIdxEndFFTSample, :NbrSamplesPrefetch => lNbrSamplesFFTMax*FFT_SAMPLES_PREFETCH ) do |iInputRawBuffer, iNbrSamples, iNbrChannels|
205
+ # First, check that we are still in the thresholds
206
+ lIdxBufferSampleOut = getSampleBeyondThresholds(iInputRawBuffer, iThresholds, iInputData.Header.NbrBitsPerSample, iNbrChannels, iNbrSamples, iBackwardsSearch)
207
+ if (lIdxBufferSampleOut != nil)
208
+ # Cancel this FFT search: the signal is out of the thresholds
209
+ rCurrentSample = lIdxCurrentSample-iNbrSamples+1+lIdxBufferSampleOut
210
+ rResultCode = 1
211
+ break
212
+ end
213
+ lFFTBuffer.insert(0, iInputRawBuffer)
214
+ lIdxCurrentSample -= iNbrSamples
215
+ end
216
+ else
217
+ iInputData.eachRawBuffer(lIdxBeginFFTSample, lIdxEndFFTSample, :NbrSamplesPrefetch => lNbrSamplesFFTMax*FFT_SAMPLES_PREFETCH) do |iInputRawBuffer, iNbrSamples, iNbrChannels|
218
+ # First, check that we are still in the thresholds
219
+ lIdxBufferSampleOut = getSampleBeyondThresholds(iInputRawBuffer, iThresholds, iInputData.Header.NbrBitsPerSample, iNbrChannels, iNbrSamples, iBackwardsSearch)
220
+ if (lIdxBufferSampleOut != nil)
221
+ # Cancel this FFT search: the signal is out of the thresholds
222
+ rCurrentSample = lIdxCurrentSample+lIdxBufferSampleOut
223
+ rResultCode = 1
224
+ break
225
+ end
226
+ lFFTBuffer.concat(iInputRawBuffer)
227
+ lIdxCurrentSample += iNbrSamples
228
+ end
229
+ end
230
+ if (rResultCode == 1)
231
+ lContinueSearching = false
232
+ else
233
+ # Compute its FFT profile
234
+ lFFTComputing.resetData
235
+ lFFTComputing.completeFFT(lFFTBuffer, lNbrSamplesFFT)
236
+ lDist = lFFTUtils.distFFTProfiles(lReferenceFFTProfile, lFFTUtils.createCFFTProfile(lFFTComputing.getFFTProfile), FFTDIST_MAX).abs
237
+ lHistoryMaxDistance = lHistory.sort[-1]
238
+ logDebug "FFT distance computed with FFT sample [#{lIdxBeginFFTSample} - #{lIdxEndFFTSample}]: #{lDist}. Sum of history: #{lSumHistory} <? #{lSumMaxFFTDistance}. Max distance of history: #{lHistoryMaxDistance} <? #{lMaxHistoryFFTDistance}"
239
+ # Detect if the Moving Average is going up and is below the maximal distance
240
+ if ((lHistory.size == FFTNBRSAMPLES_HISTORY) and
241
+ (lSumHistory < lSumMaxFFTDistance) and
242
+ (lHistoryMaxDistance < lMaxHistoryFFTDistance) and
243
+ (lHistory[lIdxOldestHistory] < lDist))
244
+ # We got it
245
+ lContinueSearching = false
246
+ else
247
+ # Check next FFT sample
248
+ if (iBackwardsSearch)
249
+ rCurrentSample = lIdxBeginFFTSample - 1
250
+ lContinueSearching = (rCurrentSample >= iIdxLastPossibleSample)
251
+ else
252
+ rCurrentSample = lIdxEndFFTSample + 1
253
+ lContinueSearching = (rCurrentSample <= iIdxLastPossibleSample)
254
+ end
255
+ if (lContinueSearching)
256
+ # Update the history with the new diff
257
+ if (lHistory[lIdxOldestHistory] == nil)
258
+ lSumHistory += lDist
259
+ else
260
+ lSumHistory += lDist - lHistory[lIdxOldestHistory]
261
+ end
262
+ lHistory[lIdxOldestHistory] = lDist
263
+ lIdxOldestHistory += 1
264
+ if (lIdxOldestHistory == FFTNBRSAMPLES_HISTORY)
265
+ lIdxOldestHistory = 0
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
271
+ if ((rResultCode == 0) and
272
+ (((iBackwardsSearch) and
273
+ (rCurrentSample == iIdxLastPossibleSample-1)) or
274
+ ((!iBackwardsSearch) and
275
+ (rCurrentSample == iIdxLastPossibleSample+1))))
276
+ # Limit was hit
277
+ rResultCode = 2
278
+ end
279
+
280
+ case rResultCode
281
+ when 0:
282
+ if (iBackwardsSearch)
283
+ logDebug "== Previous sample matching FFT before #{iIdxFirstSample} was found at #{rCurrentSample}."
284
+ else
285
+ logDebug "== Next sample matching FFT after #{iIdxFirstSample} was found at #{rCurrentSample}."
286
+ end
287
+ when 1:
288
+ if (iBackwardsSearch)
289
+ logDebug "== Previous sample matching FFT before #{iIdxFirstSample} could not be found because a sample exceeded thresholds meanwhile: #{rCurrentSample}."
290
+ else
291
+ logDebug "== Next sample matching FFT after #{iIdxFirstSample} could not be found because a sample exceeded thresholds meanwhile: #{rCurrentSample}."
292
+ end
293
+ when 2:
294
+ if (iBackwardsSearch)
295
+ logDebug "== Previous sample matching FFT before #{iIdxFirstSample} could not be found before hitting limit of #{iIdxLastPossibleSample}."
296
+ else
297
+ logDebug "== Next sample matching FFT after #{iIdxFirstSample} could not be found before hitting limit of #{iIdxLastPossibleSample}."
298
+ end
299
+ else
300
+ logErr "Unknown result code: #{rResultCode}"
301
+ end
302
+
303
+ return rResultCode, rCurrentSample
304
+ end
305
+
306
+ # Get the next silent sample from an input data
307
+ #
308
+ # Parameters:
309
+ # * *iInputData* (<em>WSK::Model::InputData</em>): The input data
310
+ # * *iIdxStartSample* (_Integer_): Index of the first sample to search from
311
+ # * *iSilenceThresholds* (<em>list<[Integer,Integer]></em>): The silence thresholds specifications
312
+ # * *iMinSilenceSamples* (_Integer_): Number of samples minimum to identify a silence
313
+ # * *iSilenceFFTProfile* (<em>[Integer,Integer,list<list<Integer>>]</em>): The silence FFT profile, or nil if none
314
+ # * *iMaxFFTDistance* (_Integer_): Max distance to consider with the FFT (ignored and can be nil if no FFT).
315
+ # * *iBackwardsSearch* (_Boolean_): Do we make a backwards search ?
316
+ # Return:
317
+ # * _Integer_: Index of the next silent sample, or nil if none
318
+ # * _Integer_: Silence length (computed only if FFT profile was provided)
319
+ # * _Integer_: Index of the next sample after the silence that is beyond thresholds (computed only if FFT profile was provided)
320
+ def getNextSilentSample(iInputData, iIdxStartSample, iSilenceThresholds, iMinSilenceSamples, iSilenceFFTProfile, iMaxFFTDistance, iBackwardsSearch)
321
+ rNextSilentSample = nil
322
+ rSilenceLength = nil
323
+ rNextSignalAboveThresholds = nil
324
+
325
+ if (iBackwardsSearch)
326
+ logDebug "=== Looking for the previous silent sample before #{iIdxStartSample}, of minimal length #{iMinSilenceSamples} ..."
327
+ else
328
+ logDebug "=== Looking for the next silent sample after #{iIdxStartSample}, of minimal length #{iMinSilenceSamples} ..."
329
+ end
330
+
331
+ lIdxSearchSample = iIdxStartSample
332
+ lContinueSearching = true
333
+ while (lContinueSearching)
334
+ # We search starting at lIdxSearchSample
335
+ lContinueSearching = false
336
+ # First find using thresholds only
337
+ require 'WSK/SilentUtils/SilentUtils'
338
+ rNextSilentSample = SilentUtils::SilentUtils.new.getNextSilentInThresholds(iInputData, lIdxSearchSample, iSilenceThresholds, iMinSilenceSamples, iBackwardsSearch)
339
+ if (rNextSilentSample == nil)
340
+ logDebug("Thresholds matching did not find any silence starting at sample #{iIdxStartSample}.")
341
+ else
342
+ logDebug("Thresholds matching found a silence starting at sample #{iIdxStartSample}, beginning at sample #{rNextSilentSample}.")
343
+ # If we want to use FFT to have a better result, do it here
344
+ if (iSilenceFFTProfile != nil)
345
+ # Check FFT
346
+ if (iBackwardsSearch)
347
+ lFFTResultCode, lIdxFFTSample = getNextFFTSample(rNextSilentSample, iSilenceFFTProfile, iInputData, iMaxFFTDistance, iSilenceThresholds, iBackwardsSearch, 0)
348
+ else
349
+ lFFTResultCode, lIdxFFTSample = getNextFFTSample(rNextSilentSample, iSilenceFFTProfile, iInputData, iMaxFFTDistance, iSilenceThresholds, iBackwardsSearch, iInputData.NbrSamples-1)
350
+ end
351
+ case lFFTResultCode
352
+ when 0
353
+ # Check that the silence lasts at least iMinSilenceSamples
354
+ lIdxNextSignal = nil
355
+ lIdxNextSignalAboveThresholds = nil
356
+ lSilenceLength = nil
357
+ if (iBackwardsSearch)
358
+ lIdxNextSignal, lIdxNextSignalAboveThresholds = getNextNonSilentSample(iInputData, lIdxFFTSample-1, iSilenceThresholds, iSilenceFFTProfile, iMaxFFTDistance, iBackwardsSearch)
359
+ if (lIdxNextSignal == nil)
360
+ # No signal was found further.
361
+ lSilenceLength = lIdxFFTSample
362
+ else
363
+ lSilenceLength = lIdxFFTSample - lIdxNextSignal - 1
364
+ end
365
+ else
366
+ lIdxNextSignal, lIdxNextSignalAboveThresholds = getNextNonSilentSample(iInputData, lIdxFFTSample+1, iSilenceThresholds, iSilenceFFTProfile, iMaxFFTDistance, iBackwardsSearch)
367
+ if (lIdxNextSignal == nil)
368
+ # No signal was found further.
369
+ lSilenceLength = iInputData.NbrSamples - 1 - lIdxFFTSample
370
+ else
371
+ lSilenceLength = lIdxNextSignal - 1 - lIdxFFTSample
372
+ end
373
+ end
374
+ if (lSilenceLength >= iMinSilenceSamples)
375
+ # We found the real one
376
+ logDebug("FFT matching found a silence starting at sample #{rNextSilentSample}, beginning at sample #{lIdxFFTSample}.")
377
+ rNextSilentSample = lIdxFFTSample
378
+ rSilenceLength = lSilenceLength
379
+ rNextSignalAboveThresholds = lIdxNextSignalAboveThresholds
380
+ elsif (lIdxNextSignal == nil)
381
+ # We arrived at the end. The silence is not long enough.
382
+ logDebug("FFT matching found a silence starting at sample #{rNextSilentSample}, beginning at sample #{lIdxFFTSample}, but its length (#{lSilenceLength}) is too small (minimum required is #{iMinSilenceSamples}). End of file reached.")
383
+ rNextSilentSample = nil
384
+ else
385
+ # We have to continue
386
+ logDebug("FFT matching found a silence starting at sample #{rNextSilentSample}, beginning at sample #{lIdxFFTSample}, but its length (#{lSilenceLength}) is too small (minimum required is #{iMinSilenceSamples}). Looking further.")
387
+ lIdxSearchSample = lIdxNextSignalAboveThresholds
388
+ lContinueSearching = true
389
+ rNextSilentSample = nil
390
+ end
391
+ when 1
392
+ # We have to search further, begin with thresholds matching
393
+ logWarn("FFT matching found a new signal beyond thresholds starting at sample #{rNextSilentSample}, beginning at sample #{lIdxFFTSample}. Maybe clip ?")
394
+ if (iBackwardsSearch)
395
+ lIdxSearchSample = lIdxFFTSample - 1
396
+ else
397
+ lIdxSearchSample = lIdxFFTSample + 1
398
+ end
399
+ lContinueSearching = true
400
+ rNextSilentSample = nil
401
+ when 2
402
+ logDebug("FFT matching could not find a silence starting at sample #{rNextSilentSample}.")
403
+ rNextSilentSample = nil
404
+ else
405
+ raise RuntimeError.new("Unknown result code: #{lFFTResultCode}")
406
+ end
407
+ end
408
+ end
409
+ end
410
+
411
+ if (iBackwardsSearch)
412
+ logDebug "=== Previous silent sample before #{iIdxStartSample} was found at #{rNextSilentSample} with a length of #{rSilenceLength}, and a signal before it above thresholds at #{rNextSignalAboveThresholds}."
413
+ else
414
+ logDebug "=== Next silent sample after #{iIdxStartSample} was found at #{rNextSilentSample} with a length of #{rSilenceLength}, and an signal after it above thresholds at #{rNextSignalAboveThresholds}."
415
+ end
416
+
417
+ return rNextSilentSample, rSilenceLength, rNextSignalAboveThresholds
418
+ end
419
+
420
+ # Get the next non silent sample from an input data
421
+ #
422
+ # Parameters:
423
+ # * *iInputData* (<em>WSK::Model::InputData</em>): The input data
424
+ # * *iIdxStartSample* (_Integer_): Index of the first sample to search from
425
+ # * *iSilenceThresholds* (<em>list<[Integer,Integer]></em>): The silence thresholds specifications
426
+ # * *iSilenceFFTProfile* (<em>[Integer,Integer,list<list<Integer>>]</em>): The silence FFT profile, or nil if none
427
+ # * *iMaxFFTDistance* (_Integer_): Max distance to consider with the FFT (ignored and can be nil if no FFT).
428
+ # * *iBackwardsSearch* (_Boolean_): Do we search backwards ?
429
+ # Return:
430
+ # * _Integer_: Index of the next non silent sample, or nil if none
431
+ # * _Integer_: Index of the next sample getting above thresholds, or nil if none
432
+ def getNextNonSilentSample(iInputData, iIdxStartSample, iSilenceThresholds, iSilenceFFTProfile, iMaxFFTDistance, iBackwardsSearch)
433
+ rIdxSampleOut = nil
434
+ rIdxSampleOutThresholds = nil
435
+
436
+ if (iBackwardsSearch)
437
+ logDebug "=== Looking for the previous signal sample before #{iIdxStartSample} ..."
438
+ else
439
+ logDebug "=== Looking for the next signal sample after #{iIdxStartSample} ..."
440
+ end
441
+
442
+ # Find the next sample getting out of the silence thresholds
443
+ lIdxCurrentSample = iIdxStartSample
444
+ if (iBackwardsSearch)
445
+ iInputData.eachReverseRawBuffer(0, iIdxStartSample) do |iInputRawBuffer, iNbrSamples, iNbrChannels|
446
+ lIdxBufferSampleOut = getSampleBeyondThresholds(iInputRawBuffer, iSilenceThresholds, iInputData.Header.NbrBitsPerSample, iNbrChannels, iNbrSamples, iBackwardsSearch)
447
+ if (lIdxBufferSampleOut != nil)
448
+ # We found it
449
+ rIdxSampleOutThresholds = lIdxCurrentSample-iNbrSamples+1+lIdxBufferSampleOut
450
+ break
451
+ end
452
+ lIdxCurrentSample -= iNbrSamples
453
+ end
454
+ else
455
+ iInputData.eachRawBuffer(iIdxStartSample) do |iInputRawBuffer, iNbrSamples, iNbrChannels|
456
+ lIdxBufferSampleOut = getSampleBeyondThresholds(iInputRawBuffer, iSilenceThresholds, iInputData.Header.NbrBitsPerSample, iNbrChannels, iNbrSamples, iBackwardsSearch)
457
+ if (lIdxBufferSampleOut != nil)
458
+ # We found it
459
+ rIdxSampleOutThresholds = lIdxCurrentSample+lIdxBufferSampleOut
460
+ break
461
+ end
462
+ lIdxCurrentSample += iNbrSamples
463
+ end
464
+ end
465
+ if (rIdxSampleOutThresholds == nil)
466
+ logDebug("Thresholds matching did not find any signal starting at sample #{iIdxStartSample}.")
467
+ else
468
+ logDebug("Thresholds matching found a signal starting at sample #{iIdxStartSample}, beginning at sample #{rIdxSampleOutThresholds}.")
469
+ # If we want to use FFT to have a better result, do it here
470
+ if (iSilenceFFTProfile == nil)
471
+ rIdxSampleOut = rIdxSampleOutThresholds
472
+ else
473
+ # Check FFT
474
+ # We search in the reverse direction to find the silence, knowing that we can't have a sample getting past our initial search sample
475
+ lFFTResultCode = nil
476
+ lIdxFFTSample = nil
477
+ if (iBackwardsSearch)
478
+ lFFTResultCode, lIdxFFTSample = getNextFFTSample(rIdxSampleOutThresholds+1, iSilenceFFTProfile, iInputData, iMaxFFTDistance, iSilenceThresholds, false, iIdxStartSample)
479
+ else
480
+ lFFTResultCode, lIdxFFTSample = getNextFFTSample(rIdxSampleOutThresholds-1, iSilenceFFTProfile, iInputData, iMaxFFTDistance, iSilenceThresholds, true, iIdxStartSample)
481
+ end
482
+ case lFFTResultCode
483
+ when 0
484
+ # We found the real one
485
+ logDebug("FFT matching found a silence starting at sample #{rIdxSampleOutThresholds}, beginning at sample #{lIdxFFTSample}.")
486
+ rIdxSampleOut = lIdxFFTSample
487
+ when 1
488
+ # Here is a bug
489
+ logErr("FFT matching found a new signal beyond thresholds starting at sample #{rIdxSampleOutThresholds}, beginning at sample #{lIdxFFTSample}. This should never happen here: the previous search using thresholds should have already returned this sample.")
490
+ raise RuntimeError.new("FFT matching found a new signal beyond thresholds starting at sample #{rIdxSampleOutThresholds}, beginning at sample #{lIdxFFTSample}. This should never happen here: the previous search using thresholds should have already returned this sample.")
491
+ when 2
492
+ logDebug("FFT matching could not find a silence starting at sample #{rIdxSampleOutThresholds}. This means that the signal is present from the start.")
493
+ rIdxSampleOut = iIdxStartSample
494
+ else
495
+ raise RuntimeError.new("Unknown result code: #{lFFTResultCode}")
496
+ end
497
+ end
498
+ end
499
+
500
+ if (iBackwardsSearch)
501
+ logDebug "=== Previous signal sample before #{iIdxStartSample} was found at #{rIdxSampleOut}, with a sample beyond thresholds at #{rIdxSampleOutThresholds}."
502
+ else
503
+ logDebug "=== Next signal sample after #{iIdxStartSample} was found at #{rIdxSampleOut}, with a sample beyond thresholds at #{rIdxSampleOutThresholds}."
504
+ end
505
+
506
+ return rIdxSampleOut, rIdxSampleOutThresholds
507
+ end
508
+
509
+ # Get the sample index that exceeds a threshold in a raw buffer.
510
+ #
511
+ # Parameters:
512
+ # * *iRawBuffer* (_String_): The raw buffer
513
+ # * *iThresholds* (<em>list<[Integer,Integer]></em>): The thresholds
514
+ # * *iNbrBitsPerSample* (_Integer_): Number of bits per sample
515
+ # * *iNbrChannels* (_Integer_): Number of channels
516
+ # * *iNbrSamples* (_Integer_): Number of samples
517
+ # * *iLastSample* (_Boolean_): Are we looking for the last sample ?
518
+ # Return:
519
+ # * _Integer_: Index of the first sample exceeding thresholds, or nil if none
520
+ def getSampleBeyondThresholds(iRawBuffer, iThresholds, iNbrBitsPerSample, iNbrChannels, iNbrSamples, iLastSample)
521
+ require 'WSK/SilentUtils/SilentUtils'
522
+ return SilentUtils::SilentUtils.new.getSampleBeyondThresholds(iRawBuffer, iThresholds, iNbrBitsPerSample, iNbrChannels, iNbrSamples, iLastSample)
523
+ end
524
+
525
+ end
526
+
527
+ end