WaveSwissKnife 0.2.0.20120302
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 +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
|
+
}
|