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.
- data/AUTHORS +1 -0
- data/ChangeLog +5 -0
- data/Credits +3 -0
- data/LICENSE +31 -0
- data/README +18 -0
- data/ReleaseInfo +8 -0
- data/TODO +2 -0
- data/bin/WSK.rb +14 -0
- data/ext/WSK/AnalyzeUtils/AnalyzeUtils.c +272 -0
- data/ext/WSK/AnalyzeUtils/AnalyzeUtils.o +0 -0
- data/ext/WSK/AnalyzeUtils/AnalyzeUtils.so +0 -0
- data/ext/WSK/AnalyzeUtils/Makefile +149 -0
- data/ext/WSK/AnalyzeUtils/build.rb +18 -0
- data/ext/WSK/ArithmUtils/ArithmUtils.c +862 -0
- data/ext/WSK/ArithmUtils/ArithmUtils.o +0 -0
- data/ext/WSK/ArithmUtils/ArithmUtils.so +0 -0
- data/ext/WSK/ArithmUtils/Makefile +149 -0
- data/ext/WSK/ArithmUtils/build.rb +20 -0
- data/ext/WSK/FFTUtils/FFTUtils.c +662 -0
- data/ext/WSK/FFTUtils/FFTUtils.o +0 -0
- data/ext/WSK/FFTUtils/FFTUtils.so +0 -0
- data/ext/WSK/FFTUtils/Makefile +149 -0
- data/ext/WSK/FFTUtils/build.rb +20 -0
- data/ext/WSK/FunctionUtils/FunctionUtils.c +182 -0
- data/ext/WSK/FunctionUtils/FunctionUtils.o +0 -0
- data/ext/WSK/FunctionUtils/FunctionUtils.so +0 -0
- data/ext/WSK/FunctionUtils/Makefile +149 -0
- data/ext/WSK/FunctionUtils/build.rb +20 -0
- data/ext/WSK/SilentUtils/Makefile +149 -0
- data/ext/WSK/SilentUtils/SilentUtils.c +431 -0
- data/ext/WSK/SilentUtils/SilentUtils.o +0 -0
- data/ext/WSK/SilentUtils/SilentUtils.so +0 -0
- data/ext/WSK/SilentUtils/build.rb +18 -0
- data/ext/WSK/VolumeUtils/Makefile +149 -0
- data/ext/WSK/VolumeUtils/VolumeUtils.c +494 -0
- data/ext/WSK/VolumeUtils/VolumeUtils.o +0 -0
- data/ext/WSK/VolumeUtils/VolumeUtils.so +0 -0
- data/ext/WSK/VolumeUtils/build.rb +20 -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 +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
|