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
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