WaveSwissKnife 0.2.0.20120302
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +4 -0
- data/ChangeLog +31 -0
- data/Credits +3 -0
- data/LICENSE +31 -0
- data/README +15 -0
- data/ReleaseInfo +8 -0
- data/bin/WSK.rb +14 -0
- data/ext/WSK/AnalyzeUtils/AnalyzeUtils.c +272 -0
- data/ext/WSK/AnalyzeUtils/extconf.rb +7 -0
- data/ext/WSK/ArithmUtils/ArithmUtils.c +862 -0
- data/ext/WSK/ArithmUtils/extconf.rb +15 -0
- data/ext/WSK/CommonBuild.rb +29 -0
- data/ext/WSK/FFTUtils/FFTUtils.c +662 -0
- data/ext/WSK/FFTUtils/extconf.rb +15 -0
- data/ext/WSK/FunctionUtils/FunctionUtils.c +182 -0
- data/ext/WSK/FunctionUtils/extconf.rb +15 -0
- data/ext/WSK/SilentUtils/SilentUtils.c +431 -0
- data/ext/WSK/SilentUtils/extconf.rb +7 -0
- data/ext/WSK/VolumeUtils/VolumeUtils.c +494 -0
- data/ext/WSK/VolumeUtils/extconf.rb +15 -0
- data/external/CommonUtils/build.rb +28 -0
- data/external/CommonUtils/include/CommonUtils.h +177 -0
- data/external/CommonUtils/src/CommonUtils.c +639 -0
- data/lib/WSK/Actions/Analyze.rb +176 -0
- data/lib/WSK/Actions/ApplyMap.desc.rb +15 -0
- data/lib/WSK/Actions/ApplyMap.rb +57 -0
- data/lib/WSK/Actions/ApplyVolumeFct.desc.rb +30 -0
- data/lib/WSK/Actions/ApplyVolumeFct.rb +72 -0
- data/lib/WSK/Actions/Compare.desc.rb +25 -0
- data/lib/WSK/Actions/Compare.rb +238 -0
- data/lib/WSK/Actions/ConstantCompare.desc.rb +20 -0
- data/lib/WSK/Actions/ConstantCompare.rb +61 -0
- data/lib/WSK/Actions/Cut.desc.rb +20 -0
- data/lib/WSK/Actions/Cut.rb +60 -0
- data/lib/WSK/Actions/CutFirstSignal.desc.rb +25 -0
- data/lib/WSK/Actions/CutFirstSignal.rb +72 -0
- data/lib/WSK/Actions/DCShifter.desc.rb +15 -0
- data/lib/WSK/Actions/DCShifter.rb +67 -0
- data/lib/WSK/Actions/DrawFct.desc.rb +20 -0
- data/lib/WSK/Actions/DrawFct.rb +59 -0
- data/lib/WSK/Actions/FFT.rb +104 -0
- data/lib/WSK/Actions/GenAllValues.rb +67 -0
- data/lib/WSK/Actions/GenConstant.desc.rb +20 -0
- data/lib/WSK/Actions/GenConstant.rb +56 -0
- data/lib/WSK/Actions/GenSawtooth.rb +57 -0
- data/lib/WSK/Actions/GenSine.desc.rb +20 -0
- data/lib/WSK/Actions/GenSine.rb +73 -0
- data/lib/WSK/Actions/Identity.rb +43 -0
- data/lib/WSK/Actions/Mix.desc.rb +15 -0
- data/lib/WSK/Actions/Mix.rb +149 -0
- data/lib/WSK/Actions/Multiply.desc.rb +15 -0
- data/lib/WSK/Actions/Multiply.rb +73 -0
- data/lib/WSK/Actions/NoiseGate.desc.rb +35 -0
- data/lib/WSK/Actions/NoiseGate.rb +129 -0
- data/lib/WSK/Actions/SilenceInserter.desc.rb +20 -0
- data/lib/WSK/Actions/SilenceInserter.rb +87 -0
- data/lib/WSK/Actions/SilenceRemover.desc.rb +30 -0
- data/lib/WSK/Actions/SilenceRemover.rb +74 -0
- data/lib/WSK/Actions/VolumeProfile.desc.rb +35 -0
- data/lib/WSK/Actions/VolumeProfile.rb +63 -0
- data/lib/WSK/Common.rb +292 -0
- data/lib/WSK/FFT.rb +527 -0
- data/lib/WSK/Functions.rb +770 -0
- data/lib/WSK/Launcher.rb +216 -0
- data/lib/WSK/Maps.rb +29 -0
- data/lib/WSK/Model/CachedBufferReader.rb +151 -0
- data/lib/WSK/Model/Header.rb +133 -0
- data/lib/WSK/Model/InputData.rb +193 -0
- data/lib/WSK/Model/RawReader.rb +78 -0
- data/lib/WSK/Model/WaveReader.rb +91 -0
- data/lib/WSK/OutputInterfaces/DirectStream.rb +146 -0
- data/lib/WSK/RIFFReader.rb +60 -0
- metadata +155 -0
data/lib/WSK/FFT.rb
ADDED
@@ -0,0 +1,527 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 - 2012 Muriel Salvan (muriel@x-aeon.com)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
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
|
+
## log_debug "[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
|
+
log_debug "== Looking for the previous sample matching FFT before #{iIdxFirstSample}, with a limit on sample #{iIdxLastPossibleSample} and a FFT distance of #{iMaxFFTDistance} ..."
|
154
|
+
else
|
155
|
+
log_debug "== 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.each_reverse_raw_buffer(lIdxBeginFFTSample, lIdxEndFFTSample, :nbr_samples_prefetch => 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.each_raw_buffer(lIdxBeginFFTSample, lIdxEndFFTSample, :nbr_samples_prefetch => 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
|
+
log_debug "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
|
+
log_debug "== Previous sample matching FFT before #{iIdxFirstSample} was found at #{rCurrentSample}."
|
284
|
+
else
|
285
|
+
log_debug "== Next sample matching FFT after #{iIdxFirstSample} was found at #{rCurrentSample}."
|
286
|
+
end
|
287
|
+
when 1
|
288
|
+
if (iBackwardsSearch)
|
289
|
+
log_debug "== Previous sample matching FFT before #{iIdxFirstSample} could not be found because a sample exceeded thresholds meanwhile: #{rCurrentSample}."
|
290
|
+
else
|
291
|
+
log_debug "== 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
|
+
log_debug "== Previous sample matching FFT before #{iIdxFirstSample} could not be found before hitting limit of #{iIdxLastPossibleSample}."
|
296
|
+
else
|
297
|
+
log_debug "== Next sample matching FFT after #{iIdxFirstSample} could not be found before hitting limit of #{iIdxLastPossibleSample}."
|
298
|
+
end
|
299
|
+
else
|
300
|
+
log_err "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
|
+
log_debug "=== Looking for the previous silent sample before #{iIdxStartSample}, of minimal length #{iMinSilenceSamples} ..."
|
327
|
+
else
|
328
|
+
log_debug "=== 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
|
+
log_debug("Thresholds matching did not find any silence starting at sample #{iIdxStartSample}.")
|
341
|
+
else
|
342
|
+
log_debug("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
|
+
log_debug("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
|
+
log_debug("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
|
+
log_debug("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
|
+
log_warn("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
|
+
log_debug("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
|
+
log_debug "=== 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
|
+
log_debug "=== 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
|
+
log_debug "=== Looking for the previous signal sample before #{iIdxStartSample} ..."
|
438
|
+
else
|
439
|
+
log_debug "=== 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.each_reverse_raw_buffer(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.each_raw_buffer(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
|
+
log_debug("Thresholds matching did not find any signal starting at sample #{iIdxStartSample}.")
|
467
|
+
else
|
468
|
+
log_debug("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
|
+
log_debug("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
|
+
log_err("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
|
+
log_debug("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
|
+
log_debug "=== Previous signal sample before #{iIdxStartSample} was found at #{rIdxSampleOut}, with a sample beyond thresholds at #{rIdxSampleOutThresholds}."
|
502
|
+
else
|
503
|
+
log_debug "=== 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
|