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
@@ -0,0 +1,57 @@
|
|
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 Actions
|
9
|
+
|
10
|
+
class GenSawtooth
|
11
|
+
|
12
|
+
# Get the number of samples that will be written.
|
13
|
+
# This is called before execute, as it is needed to write the output file.
|
14
|
+
# It is possible to give a majoration: it will be padded with silence.
|
15
|
+
#
|
16
|
+
# Parameters::
|
17
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
18
|
+
# Return::
|
19
|
+
# * _Integer_: The number of samples to be written
|
20
|
+
def get_nbr_samples(iInputData)
|
21
|
+
return iInputData.Header.SampleRate
|
22
|
+
end
|
23
|
+
|
24
|
+
# Execute
|
25
|
+
#
|
26
|
+
# Parameters::
|
27
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
28
|
+
# * *oOutputData* (_Object_): The output data to fill
|
29
|
+
# Return::
|
30
|
+
# * _Exception_: An error, or nil if success
|
31
|
+
def execute(iInputData, oOutputData)
|
32
|
+
# Compute values used to create sawtooth
|
33
|
+
lMaxValue = 2**(iInputData.Header.NbrBitsPerSample-1)-1
|
34
|
+
lMinValue = -2**(iInputData.Header.NbrBitsPerSample-1)
|
35
|
+
lMiddleSample = iInputData.Header.SampleRate/2
|
36
|
+
# Create buffer
|
37
|
+
lBuffer = []
|
38
|
+
iInputData.Header.SampleRate.times do |iIdxSample|
|
39
|
+
iInputData.Header.NbrChannels.times do |iIdxChannel|
|
40
|
+
if (iIdxSample < lMiddleSample)
|
41
|
+
lBuffer << (iIdxSample*lMaxValue)/lMiddleSample
|
42
|
+
else
|
43
|
+
lBuffer << lMinValue+(lMiddleSample-iIdxSample)*lMinValue/(iInputData.Header.SampleRate-lMiddleSample)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
# Write buffer
|
48
|
+
oOutputData.pushBuffer(lBuffer)
|
49
|
+
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,20 @@
|
|
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
|
+
{
|
7
|
+
:OutputInterface => 'DirectStream',
|
8
|
+
:Options => {
|
9
|
+
:Frequency => [
|
10
|
+
'--frequency <Frequency>', Integer,
|
11
|
+
'<Frequency>: Frequency of the sine wave (in Hz)',
|
12
|
+
'Specify the frequency of the generated sine wave.'
|
13
|
+
],
|
14
|
+
:NbrSamples => [
|
15
|
+
'--nbrsamples <NbrSamples>', Integer,
|
16
|
+
'<NbrSamples>: Number of samples used to write this value',
|
17
|
+
'Specify the number of samples during the value will be written.'
|
18
|
+
]
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,73 @@
|
|
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 Actions
|
9
|
+
|
10
|
+
class GenSine
|
11
|
+
|
12
|
+
# Get the number of samples that will be written.
|
13
|
+
# This is called before execute, as it is needed to write the output file.
|
14
|
+
# It is possible to give a majoration: it will be padded with silence.
|
15
|
+
#
|
16
|
+
# Parameters::
|
17
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
18
|
+
# Return::
|
19
|
+
# * _Integer_: The number of samples to be written
|
20
|
+
def get_nbr_samples(iInputData)
|
21
|
+
return @NbrSamples
|
22
|
+
end
|
23
|
+
|
24
|
+
# Execute
|
25
|
+
#
|
26
|
+
# Parameters::
|
27
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
28
|
+
# * *oOutputData* (_Object_): The output data to fill
|
29
|
+
# Return::
|
30
|
+
# * _Exception_: An error, or nil if success
|
31
|
+
def execute(iInputData, oOutputData)
|
32
|
+
# Compute values used to create sawtooth
|
33
|
+
lMaxValue = 2**(iInputData.Header.NbrBitsPerSample-1)-1
|
34
|
+
# Compute the number of complete periods to put in the samples we want
|
35
|
+
lNbrSamplesPeriod = iInputData.Header.SampleRate/@Frequency
|
36
|
+
lNbrPeriods = @NbrSamples/lNbrSamplesPeriod
|
37
|
+
lBuffer = nil
|
38
|
+
if (lNbrPeriods > 0)
|
39
|
+
# Generate a buffer with a omplete period in it
|
40
|
+
lBuffer = []
|
41
|
+
lNbrSamplesPeriod.times do |iIdx|
|
42
|
+
lBuffer.concat( [(Math.sin((2*Math::PI*iIdx)/lNbrSamplesPeriod)*lMaxValue).round] * iInputData.Header.NbrChannels )
|
43
|
+
end
|
44
|
+
# Write them
|
45
|
+
lNbrPeriods.times do |iIdx|
|
46
|
+
oOutputData.pushBuffer(lBuffer)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
lRemainingSamples = @NbrSamples % lNbrSamplesPeriod
|
50
|
+
if (lRemainingSamples > 0)
|
51
|
+
# Add the remaining part of the buffer
|
52
|
+
if (lBuffer == nil)
|
53
|
+
# Generate a part of the buffer
|
54
|
+
lBuffer = []
|
55
|
+
lRemainingSamples.times do |iIdx|
|
56
|
+
lBuffer.concat( [(Math.sin((2*Math::PI*iIdx)/lNbrSamplesPeriod)*lMaxValue).round] * iInputData.Header.NbrChannels )
|
57
|
+
end
|
58
|
+
# Write it
|
59
|
+
oOutputData.pushBuffer(lBuffer)
|
60
|
+
else
|
61
|
+
# Write a part of the already generated buffer
|
62
|
+
oOutputData.pushBuffer(lBuffer[0..iInputData.Header.NbrChannels*lRemainingSamples-1])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,43 @@
|
|
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 Actions
|
9
|
+
|
10
|
+
class Identity
|
11
|
+
|
12
|
+
# Get the number of samples that will be written.
|
13
|
+
# This is called before execute, as it is needed to write the output file.
|
14
|
+
# It is possible to give a majoration: it will be padded with silence.
|
15
|
+
#
|
16
|
+
# Parameters::
|
17
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
18
|
+
# Return::
|
19
|
+
# * _Integer_: The number of samples to be written
|
20
|
+
def get_nbr_samples(iInputData)
|
21
|
+
return iInputData.NbrSamples
|
22
|
+
end
|
23
|
+
|
24
|
+
# Execute
|
25
|
+
#
|
26
|
+
# Parameters::
|
27
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
28
|
+
# * *oOutputData* (_Object_): The output data to fill
|
29
|
+
# Return::
|
30
|
+
# * _Exception_: An error, or nil if success
|
31
|
+
def execute(iInputData, oOutputData)
|
32
|
+
iInputData.each_raw_buffer do |iInputRawBuffer, iNbrSamples, iNbrChannels|
|
33
|
+
oOutputData.pushRawBuffer(iInputRawBuffer)
|
34
|
+
end
|
35
|
+
|
36
|
+
return nil
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,15 @@
|
|
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
|
+
{
|
7
|
+
:OutputInterface => 'DirectStream',
|
8
|
+
:Options => {
|
9
|
+
:MixFiles => [
|
10
|
+
'--files <FilesList>', String,
|
11
|
+
'<FilesList>: List of files to mix with the input file, with floating coefficients, separated with | (example: File1.wav|2|File2.wav|1.4|File3.wav|-1)',
|
12
|
+
'Specify the list of files to mix along with their coefficient. The input file has the coefficient 1. The coefficient can be negative to invert the file while mixing.'
|
13
|
+
]
|
14
|
+
}
|
15
|
+
}
|
@@ -0,0 +1,149 @@
|
|
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 Actions
|
9
|
+
|
10
|
+
class Mix
|
11
|
+
|
12
|
+
include WSK::Common
|
13
|
+
|
14
|
+
# Get the number of samples that will be written.
|
15
|
+
# This is called before execute, as it is needed to write the output file.
|
16
|
+
# It is possible to give a majoration: it will be padded with silence.
|
17
|
+
#
|
18
|
+
# Parameters::
|
19
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
20
|
+
# Return::
|
21
|
+
# * _Integer_: The number of samples to be written
|
22
|
+
def get_nbr_samples(iInputData)
|
23
|
+
@NbrSamples = iInputData.NbrSamples
|
24
|
+
|
25
|
+
# Decode the files list
|
26
|
+
# list< [ String, Float ] >
|
27
|
+
@LstFiles = []
|
28
|
+
lLstParams = @MixFiles.split('|')
|
29
|
+
if (lLstParams.size % 2 != 0)
|
30
|
+
raise RuntimeError, 'Invalid mix parameters. Example: File1.wav|1|File2.wav|0.4'
|
31
|
+
else
|
32
|
+
(lLstParams.size/2).times do |iIdxFile|
|
33
|
+
lFileName = lLstParams[iIdxFile*2]
|
34
|
+
lCoeff = lLstParams[iIdxFile*2+1].to_f
|
35
|
+
if (lCoeff == 0)
|
36
|
+
log_warn "File #{lFileName} has a null coefficient. It won't be part of the mix."
|
37
|
+
else
|
38
|
+
# Check if the file exists
|
39
|
+
if (File.exists?(lFileName))
|
40
|
+
# Check the file's header
|
41
|
+
lError = accessInputWaveFile(lFileName) do |iInputHeader2, iInputData2|
|
42
|
+
rSubError = nil
|
43
|
+
# Check that headers are the same
|
44
|
+
if (iInputHeader2 != iInputData.Header)
|
45
|
+
rSubError = RuntimeError.new("Mismatch headers with file #{lFileName}: First input file: #{iInputData.Header.inspect} Mix file: #{iInputHeader2.inspect}")
|
46
|
+
end
|
47
|
+
# OK, keep this file
|
48
|
+
@LstFiles << [ lFileName, lCoeff ]
|
49
|
+
if (iInputData2.NbrSamples > @NbrSamples)
|
50
|
+
@NbrSamples = iInputData2.NbrSamples
|
51
|
+
end
|
52
|
+
next rSubError
|
53
|
+
end
|
54
|
+
if (lError != nil)
|
55
|
+
raise lError
|
56
|
+
end
|
57
|
+
else
|
58
|
+
raise RuntimeError, "Missing file: #{lFileName}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
return @NbrSamples
|
65
|
+
end
|
66
|
+
|
67
|
+
# Execute
|
68
|
+
#
|
69
|
+
# Parameters::
|
70
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
71
|
+
# * *oOutputData* (_Object_): The output data to fill
|
72
|
+
# Return::
|
73
|
+
# * _Exception_: An error, or nil if success
|
74
|
+
def execute(iInputData, oOutputData)
|
75
|
+
rError = nil
|
76
|
+
|
77
|
+
# Store the list of opened files, and initialize it with the input data (first file)
|
78
|
+
# list< [ IO, InputData, Coeff, Buffer, NbrSamplesInBuffer ] >
|
79
|
+
lLstOpenedFiles = [ [ nil, iInputData, 1.0, nil, nil ] ]
|
80
|
+
@LstFiles.each do |iFileInfo|
|
81
|
+
iFileName, iCoeff = iFileInfo
|
82
|
+
lFileHandle = File.open(iFileName, 'rb')
|
83
|
+
rError, lHeader, lInputData = getWaveFileAccesses(lFileHandle)
|
84
|
+
if (rError == nil)
|
85
|
+
lLstOpenedFiles << [ lFileHandle, lInputData, iCoeff, nil, nil ]
|
86
|
+
else
|
87
|
+
break
|
88
|
+
end
|
89
|
+
end
|
90
|
+
if (rError == nil)
|
91
|
+
require 'WSK/ArithmUtils/ArithmUtils'
|
92
|
+
lArithmUtils = WSK::ArithmUtils::ArithmUtils.new
|
93
|
+
# Loop until we meet the maximal number of samples
|
94
|
+
# !!! We assume that buffers have the same size when read
|
95
|
+
# Initialize buffers
|
96
|
+
lLstOpenedFiles.each do |ioFileInfo|
|
97
|
+
lFileHandle, lInputData, lCoeff, lBuffer = ioFileInfo
|
98
|
+
lInputData.each_raw_buffer do |iRawBuffer, iNbrSamples, iNbrChannels|
|
99
|
+
break
|
100
|
+
end
|
101
|
+
lRawBuffer, lNbrSamples, lNbrChannels2 = lInputData.get_current_raw_buffer
|
102
|
+
ioFileInfo[3] = lRawBuffer
|
103
|
+
ioFileInfo[4] = lNbrSamples
|
104
|
+
end
|
105
|
+
# Sort the list based on the number of samples of each file.
|
106
|
+
# This is a prerequisite of the C function mixing.
|
107
|
+
lLstOpenedFiles.sort! do |iOF1, iOF2|
|
108
|
+
next (iOF2[1].NbrSamples <=> iOF1[1].NbrSamples)
|
109
|
+
end
|
110
|
+
lLstRemainingOpenedFiles = lLstOpenedFiles.clone
|
111
|
+
lNbrSamplesProcessed = 0
|
112
|
+
while (!lLstRemainingOpenedFiles.empty?)
|
113
|
+
# Mix all buffers
|
114
|
+
lMixRawBuffer, lNbrSamplesWritten = lArithmUtils.mixBuffers(lLstRemainingOpenedFiles, iInputData.Header.NbrBitsPerSample, iInputData.Header.NbrChannels)
|
115
|
+
# Remove the ones that don't have data anymore
|
116
|
+
lLstRemainingOpenedFiles.delete_if do |ioFileInfo|
|
117
|
+
lFileHandle, lInputData, lCoeff, lRawBuffer = ioFileInfo
|
118
|
+
rToBeDeleted = false
|
119
|
+
# Set the next buffer of this file
|
120
|
+
if (lNbrSamplesProcessed + lNbrSamplesWritten >= lInputData.NbrSamples)
|
121
|
+
# Close the handle if it is not the main input
|
122
|
+
if (lFileHandle != nil)
|
123
|
+
lFileHandle.close
|
124
|
+
end
|
125
|
+
rToBeDeleted = true
|
126
|
+
else
|
127
|
+
# Read next Buffer
|
128
|
+
lInputData.each_raw_buffer(lNbrSamplesProcessed + lNbrSamplesWritten) do |iRawBuffer, iNbrSamples, iNbrChannels|
|
129
|
+
break
|
130
|
+
end
|
131
|
+
lRawBuffer, lNbrSamples, lNbrChannels2 = lInputData.get_current_raw_buffer
|
132
|
+
ioFileInfo[3] = lRawBuffer
|
133
|
+
ioFileInfo[4] = lNbrSamples
|
134
|
+
end
|
135
|
+
next rToBeDeleted
|
136
|
+
end
|
137
|
+
oOutputData.pushRawBuffer(lMixRawBuffer)
|
138
|
+
lNbrSamplesProcessed += lNbrSamplesWritten
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
return rError
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
@@ -0,0 +1,15 @@
|
|
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
|
+
{
|
7
|
+
:OutputInterface => 'DirectStream',
|
8
|
+
:Options => {
|
9
|
+
:Coeff => [
|
10
|
+
'--coeff <Coeff>', String,
|
11
|
+
'<Coeff>: Coefficient to apply in the form X/Y (ie. 4/3) or in db (ie. -3db)',
|
12
|
+
'Specify the multiplying coefficient'
|
13
|
+
]
|
14
|
+
}
|
15
|
+
}
|
@@ -0,0 +1,73 @@
|
|
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 Actions
|
9
|
+
|
10
|
+
class Multiply
|
11
|
+
|
12
|
+
include WSK::Maps
|
13
|
+
|
14
|
+
# Get the number of samples that will be written.
|
15
|
+
# This is called before execute, as it is needed to write the output file.
|
16
|
+
# It is possible to give a majoration: it will be padded with silence.
|
17
|
+
#
|
18
|
+
# Parameters::
|
19
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
20
|
+
# Return::
|
21
|
+
# * _Integer_: The number of samples to be written
|
22
|
+
def get_nbr_samples(iInputData)
|
23
|
+
return iInputData.NbrSamples
|
24
|
+
end
|
25
|
+
|
26
|
+
# Execute
|
27
|
+
#
|
28
|
+
# Parameters::
|
29
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
30
|
+
# * *oOutputData* (_Object_): The output data to fill
|
31
|
+
# Return::
|
32
|
+
# * _Exception_: An error, or nil if success
|
33
|
+
def execute(iInputData, oOutputData)
|
34
|
+
rError = nil
|
35
|
+
|
36
|
+
lCoeff = nil
|
37
|
+
lMatch = @Coeff.match(/^(.*)db$/)
|
38
|
+
if (lMatch == nil)
|
39
|
+
lMatch = @Coeff.match(/^(\d*)\/(\d*)$/)
|
40
|
+
if (lMatch == nil)
|
41
|
+
log_err "Incorrect coefficient: #{@Coeff} is not in the form X/Y or in the form Xdb"
|
42
|
+
rError = RuntimeError.new("Incorrect coefficient: #{@Coeff} is not in the form X/Y or in the form Xdb")
|
43
|
+
else
|
44
|
+
lNum, lDenom = lMatch[1..2].map { |iStrValue| iStrValue.to_i }
|
45
|
+
lCoeff = Rational(lNum, lDenom)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
lCoeff = 2**(lMatch[1].to_f/6)
|
49
|
+
end
|
50
|
+
|
51
|
+
if (rError == nil)
|
52
|
+
lMaxValue = 2**(iInputData.Header.NbrBitsPerSample-1) - 1
|
53
|
+
lMinValue = -2**(iInputData.Header.NbrBitsPerSample-1)
|
54
|
+
lFunction = {
|
55
|
+
:FunctionType => WSK::Functions::FCTTYPE_PIECEWISE_LINEAR,
|
56
|
+
:MinValue => lMinValue,
|
57
|
+
:MaxValue => lMaxValue,
|
58
|
+
:Points => {
|
59
|
+
lMinValue => (lMinValue*lCoeff).to_i,
|
60
|
+
lMaxValue => (lMaxValue*lCoeff).to_i
|
61
|
+
}
|
62
|
+
}
|
63
|
+
apply_map_functions(iInputData, oOutputData, [lFunction]*iInputData.Header.NbrChannels)
|
64
|
+
end
|
65
|
+
|
66
|
+
return rError
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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
|
+
{
|
7
|
+
:OutputInterface => 'DirectStream',
|
8
|
+
:Options => {
|
9
|
+
:SilenceThreshold => [
|
10
|
+
'--silencethreshold <SilenceThreshold>', String,
|
11
|
+
'<SilenceThreshold>: Threshold to use to identify silent parts [default = 0]. It is possible to specify several values, for each channel, sperated with | (ie. 34|35). It is also possible to specify a range instead of a threshold with , (ie. -128,126 or -128,126|-127,132)',
|
12
|
+
'Specify the silence threshold'
|
13
|
+
],
|
14
|
+
:Attack => [
|
15
|
+
'--attack <AttackDuration>', String,
|
16
|
+
'<AttackDuration>: Attack duration in samples or in float seconds (ie. 234 or 25.3s).',
|
17
|
+
'Specify the attack duration after the silence. This will fadein the noise before the non-silent part.'
|
18
|
+
],
|
19
|
+
:Release => [
|
20
|
+
'--release <ReleaseDuration>', String,
|
21
|
+
'<ReleaseDuration>: Release duration in samples or in float seconds (ie. 234 or 25.3s).',
|
22
|
+
'Specify the release duration before the silence. This will fadeout the noise after the non-silent part.'
|
23
|
+
],
|
24
|
+
:SilenceMin => [
|
25
|
+
'--silencemin <SilenceDuration>', String,
|
26
|
+
'<SilenceDuration>: Silence duration in samples or in float seconds (ie. 234 or 25.3s).',
|
27
|
+
'Specify the minimum duration a silent part must have to be interpreted as a silence.'
|
28
|
+
],
|
29
|
+
:NoiseFFTFileName => [
|
30
|
+
'--noisefft <FFTFile>', String,
|
31
|
+
'<FFTFile>: File containing the FFT profile of the reference noise.',
|
32
|
+
'This is used to compare potential noise profile with the real noise profile.'
|
33
|
+
]
|
34
|
+
}
|
35
|
+
}
|
@@ -0,0 +1,129 @@
|
|
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 Actions
|
9
|
+
|
10
|
+
class NoiseGate
|
11
|
+
|
12
|
+
include WSK::Common
|
13
|
+
include WSK::FFT
|
14
|
+
|
15
|
+
# Get the number of samples that will be written.
|
16
|
+
# This is called before execute, as it is needed to write the output file.
|
17
|
+
# It is possible to give a majoration: it will be padded with silence.
|
18
|
+
#
|
19
|
+
# Parameters::
|
20
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
21
|
+
# Return::
|
22
|
+
# * _Integer_: The number of samples to be written
|
23
|
+
def get_nbr_samples(iInputData)
|
24
|
+
return iInputData.NbrSamples
|
25
|
+
end
|
26
|
+
|
27
|
+
# Execute
|
28
|
+
#
|
29
|
+
# Parameters::
|
30
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
31
|
+
# * *oOutputData* (_Object_): The output data to fill
|
32
|
+
# Return::
|
33
|
+
# * _Exception_: An error, or nil if success
|
34
|
+
def execute(iInputData, oOutputData)
|
35
|
+
lSilenceThresholds = readThresholds(@SilenceThreshold, iInputData.Header.NbrChannels)
|
36
|
+
lAttackDuration = readDuration(@Attack, iInputData.Header.SampleRate)
|
37
|
+
lReleaseDuration = readDuration(@Release, iInputData.Header.SampleRate)
|
38
|
+
lSilenceDuration = readDuration(@SilenceMin, iInputData.Header.SampleRate)
|
39
|
+
lNoiseFFTMaxDistance, lNoiseFFTProfile = readFFTProfile(@NoiseFFTFileName)
|
40
|
+
# Create a map of the non silent parts
|
41
|
+
# list< [ Integer, Integer ] >
|
42
|
+
# list< [ IdxBeginNonSilentSample, IdxEndNonSilentSample ] >
|
43
|
+
lNonSilentParts = []
|
44
|
+
lIdxSample = 0
|
45
|
+
while (lIdxSample != nil)
|
46
|
+
lIdxNextSilence, lSilenceLength, lIdxNextBeyondThresholds = getNextSilentSample(iInputData, lIdxSample, lSilenceThresholds, lSilenceDuration, lNoiseFFTProfile, lNoiseFFTMaxDistance, false)
|
47
|
+
if (lIdxNextSilence == nil)
|
48
|
+
lNonSilentParts << [lIdxSample, iInputData.NbrSamples-1]
|
49
|
+
else
|
50
|
+
lNonSilentParts << [lIdxSample, lIdxNextSilence-1]
|
51
|
+
end
|
52
|
+
lIdxSample = lIdxNextBeyondThresholds
|
53
|
+
end
|
54
|
+
lStrNonSilentParts = lNonSilentParts.map { |iNonSilentInfo| "[#{iNonSilentInfo[0]/iInputData.Header.SampleRate}s - #{iNonSilentInfo[1]/iInputData.Header.SampleRate}s]" }
|
55
|
+
log_info "#{lNonSilentParts.size} non silent parts: #{lStrNonSilentParts[0..9].join(', ')}"
|
56
|
+
lStrDbgNonSilentParts = lNonSilentParts.map { |iNonSilentInfo| "[#{iNonSilentInfo[0]} - #{iNonSilentInfo[1]}]" }
|
57
|
+
log_debug "#{lNonSilentParts.size} non silent parts: #{lStrDbgNonSilentParts[0..9].join(', ')}"
|
58
|
+
# Now we write the non-silent parts, spaced with nulled parts, with fadeins and fadeouts around.
|
59
|
+
lNextSampleToWrite = 0
|
60
|
+
lNonSilentParts.each do |iNonSilentInfo|
|
61
|
+
iIdxBegin, iIdxEnd = iNonSilentInfo
|
62
|
+
# Compute the fadein buffer
|
63
|
+
lIdxBeginFadeIn = iIdxBegin - lAttackDuration
|
64
|
+
if (lIdxBeginFadeIn < 0)
|
65
|
+
lIdxBeginFadeIn = 0
|
66
|
+
end
|
67
|
+
# Write a blank buffer if needed
|
68
|
+
if (lIdxBeginFadeIn > lNextSampleToWrite)
|
69
|
+
log_debug "Write #{lIdxBeginFadeIn - lNextSampleToWrite} samples of silence"
|
70
|
+
oOutputData.pushRawBuffer("\000" * (((lIdxBeginFadeIn - lNextSampleToWrite)*iInputData.Header.NbrChannels*iInputData.Header.NbrBitsPerSample)/8))
|
71
|
+
end
|
72
|
+
lFadeInSize = iIdxBegin-lIdxBeginFadeIn
|
73
|
+
if (lFadeInSize > 0)
|
74
|
+
lBuffer = []
|
75
|
+
lIdxFadeSample = 0
|
76
|
+
iInputData.each(lIdxBeginFadeIn) do |iChannelValues|
|
77
|
+
if (lIdxFadeSample == lFadeInSize)
|
78
|
+
break
|
79
|
+
end
|
80
|
+
lBuffer.concat(iChannelValues.map { |iValue| (iValue*lIdxFadeSample)/lFadeInSize })
|
81
|
+
lIdxFadeSample += 1
|
82
|
+
end
|
83
|
+
log_debug "Write #{lBuffer.size/iInputData.Header.NbrChannels} samples of fadein."
|
84
|
+
oOutputData.pushBuffer(lBuffer)
|
85
|
+
else
|
86
|
+
log_debug 'Ignore empty fadein.'
|
87
|
+
end
|
88
|
+
# Write the file
|
89
|
+
log_debug "Write #{iIdxEnd-iIdxBegin+1} samples of audio."
|
90
|
+
iInputData.each_raw_buffer(iIdxBegin, iIdxEnd) do |iInputRawBuffer, iNbrSamples, iNbrChannels|
|
91
|
+
oOutputData.pushRawBuffer(iInputRawBuffer)
|
92
|
+
end
|
93
|
+
# Write the fadeout buffer
|
94
|
+
lIdxEndFadeOut = iIdxEnd + lReleaseDuration
|
95
|
+
if (lIdxEndFadeOut >= iInputData.NbrSamples)
|
96
|
+
lIdxEndFadeOut = iInputData.NbrSamples - 1
|
97
|
+
end
|
98
|
+
lFadeOutSize = lIdxEndFadeOut-iIdxEnd
|
99
|
+
if (lFadeOutSize > 0)
|
100
|
+
lBuffer = []
|
101
|
+
lIdxFadeSample = 0
|
102
|
+
iInputData.each(iIdxEnd+1) do |iChannelValues|
|
103
|
+
if (lIdxFadeSample == lFadeOutSize)
|
104
|
+
break
|
105
|
+
end
|
106
|
+
lBuffer.concat(iChannelValues.map { |iValue| (iValue*(lFadeOutSize-lIdxFadeSample))/lFadeOutSize })
|
107
|
+
lIdxFadeSample += 1
|
108
|
+
end
|
109
|
+
log_debug "Write #{lBuffer.size/iInputData.Header.NbrChannels} samples of fadeout."
|
110
|
+
oOutputData.pushBuffer(lBuffer)
|
111
|
+
else
|
112
|
+
log_debug 'Ignore empty fadeout.'
|
113
|
+
end
|
114
|
+
lNextSampleToWrite = lIdxEndFadeOut + 1
|
115
|
+
end
|
116
|
+
# If there is remaining silence, write it
|
117
|
+
if (lNextSampleToWrite < iInputData.NbrSamples)
|
118
|
+
log_debug "Write #{iInputData.NbrSamples - lNextSampleToWrite} samples of last silence"
|
119
|
+
oOutputData.pushRawBuffer("\000" * (((iInputData.NbrSamples - lNextSampleToWrite)*iInputData.Header.NbrChannels*iInputData.Header.NbrBitsPerSample)/8))
|
120
|
+
end
|
121
|
+
|
122
|
+
return nil
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
@@ -0,0 +1,20 @@
|
|
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
|
+
{
|
7
|
+
:OutputInterface => 'DirectStream',
|
8
|
+
:Options => {
|
9
|
+
:BeginSilenceLength => [
|
10
|
+
'--begin <SilenceLength>', String,
|
11
|
+
'<SilenceLength>: Length of silence to insert in samples or in float seconds (ie. 234 or 25.3s)',
|
12
|
+
'Specify the number of samples to insert at the beginning of the audio data'
|
13
|
+
],
|
14
|
+
:EndSilenceLength => [
|
15
|
+
'--end <SilenceLength>', String,
|
16
|
+
'<SilenceLength>: Length of silence to insert in samples or in float seconds (ie. 234 or 25.3s)',
|
17
|
+
'Specify the number of samples to insert at the end of the audio data'
|
18
|
+
]
|
19
|
+
}
|
20
|
+
}
|