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,176 @@
|
|
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 Analyze
|
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
|
+
return 0
|
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
|
+
lMaxValue = 2**(iInputData.Header.NbrBitsPerSample-1)-1
|
35
|
+
lMinValue = 2**(iInputData.Header.NbrBitsPerSample-1)
|
36
|
+
|
37
|
+
# Require C extension
|
38
|
+
require 'WSK/AnalyzeUtils/AnalyzeUtils'
|
39
|
+
lAnalyzeUtils = AnalyzeUtils::AnalyzeUtils.new
|
40
|
+
# Initialize computing arrays
|
41
|
+
lCMaxValues = lAnalyzeUtils.init64bitsArray(iInputData.Header.NbrChannels, 0);
|
42
|
+
lCMinValues = lAnalyzeUtils.init64bitsArray(iInputData.Header.NbrChannels, lMinValue);
|
43
|
+
lCSumValues = lAnalyzeUtils.init64bitsArray(iInputData.Header.NbrChannels, 0);
|
44
|
+
lCAbsSumValues = lAnalyzeUtils.init64bitsArray(iInputData.Header.NbrChannels, 0);
|
45
|
+
lCSquareSumValues = lAnalyzeUtils.init128bitsArray(iInputData.Header.NbrChannels);
|
46
|
+
# Gather data
|
47
|
+
lNbrSamplesProcessed = 0
|
48
|
+
iInputData.each_raw_buffer do |iRawBuffer, iNbrSamples, iNbrChannels|
|
49
|
+
lAnalyzeUtils.completeAnalyze(iRawBuffer, iInputData.Header.NbrBitsPerSample, iNbrSamples, iNbrChannels, lCMaxValues, lCMinValues, lCSumValues, lCAbsSumValues, lCSquareSumValues)
|
50
|
+
lNbrSamplesProcessed += iNbrSamples
|
51
|
+
$stdout.write("#{(lNbrSamplesProcessed*100)/iInputData.NbrSamples} %\015")
|
52
|
+
$stdout.flush
|
53
|
+
end
|
54
|
+
# Get the Ruby arrays from the C ones
|
55
|
+
lMaxValues = lAnalyzeUtils.getRuby64bitsArray(lCMaxValues, iInputData.Header.NbrChannels);
|
56
|
+
lMinValues = lAnalyzeUtils.getRuby64bitsArray(lCMinValues, iInputData.Header.NbrChannels);
|
57
|
+
lSumValues = lAnalyzeUtils.getRuby64bitsArray(lCSumValues, iInputData.Header.NbrChannels);
|
58
|
+
lAbsSumValues = lAnalyzeUtils.getRuby64bitsArray(lCAbsSumValues, iInputData.Header.NbrChannels);
|
59
|
+
lSquareSumValues = lAnalyzeUtils.getRuby128bitsArray(lCSquareSumValues, iInputData.Header.NbrChannels);
|
60
|
+
# Compute values in DB scale also
|
61
|
+
lDBMinValues = []
|
62
|
+
lMinValues.each do |iValue|
|
63
|
+
lDBMinValues << val2db(iValue, lMinValue)[0]
|
64
|
+
end
|
65
|
+
lDBMaxValues = []
|
66
|
+
lMaxValues.each do |iValue|
|
67
|
+
lDBMaxValues << val2db(iValue, lMaxValue)[0]
|
68
|
+
end
|
69
|
+
lMoyValues = []
|
70
|
+
lDBMoyValues = []
|
71
|
+
lSumValues.each do |iValue|
|
72
|
+
lMoyValue = Float(iValue)/Float(iInputData.NbrSamples)
|
73
|
+
lMoyValues << lMoyValue
|
74
|
+
lDBMoyValues << val2db(lMoyValue, lMinValue)[0]
|
75
|
+
end
|
76
|
+
lAbsMoyValues = []
|
77
|
+
lDBAbsMoyValues = []
|
78
|
+
lAbsSumValues.each do |iValue|
|
79
|
+
lAbsMoyValue = Float(iValue)/Float(iInputData.NbrSamples)
|
80
|
+
lAbsMoyValues << lAbsMoyValue
|
81
|
+
lDBAbsMoyValues << val2db(lAbsMoyValue, lMinValue)[0]
|
82
|
+
end
|
83
|
+
lAbsMaxValues = []
|
84
|
+
lDBAbsMaxValues = []
|
85
|
+
lMinValues.each_with_index do |iValue, iIdx|
|
86
|
+
lAbsMinValue = iValue.abs
|
87
|
+
lAbsMaxValue = lMaxValues[iIdx].abs
|
88
|
+
lAbsMax = nil
|
89
|
+
if (lAbsMinValue > lAbsMaxValue)
|
90
|
+
lAbsMax = lAbsMinValue
|
91
|
+
else
|
92
|
+
lAbsMax = lAbsMaxValue
|
93
|
+
end
|
94
|
+
lAbsMaxValues << lAbsMax
|
95
|
+
lDBAbsMaxValues << val2db(lAbsMax, lMinValue)[0]
|
96
|
+
end
|
97
|
+
lAbsMaxValue = lAbsMaxValues.sort[-1]
|
98
|
+
lAbsMaxValueDB, lAbsMaxValuePC = val2db(lAbsMaxValue, lMinValue)
|
99
|
+
lAbsMoyValue = 0
|
100
|
+
lAbsMoyValues.each do |iValue|
|
101
|
+
lAbsMoyValue += iValue
|
102
|
+
end
|
103
|
+
lAbsMoyValue /= lAbsMoyValues.size
|
104
|
+
lAbsMoyValueDB, lAbsMoyValuePC = val2db(lAbsMoyValue, lMinValue)
|
105
|
+
lRMSValues = []
|
106
|
+
lDBRMSValues = []
|
107
|
+
lSquareSumValues.each do |iValue|
|
108
|
+
lRMSValue = Math.sqrt(iValue/iInputData.NbrSamples).round
|
109
|
+
lRMSValues << lRMSValue
|
110
|
+
lDBRMSValues << val2db(lRMSValue, lMinValue)[0]
|
111
|
+
end
|
112
|
+
lResult = {
|
113
|
+
:MinValues => lMinValues,
|
114
|
+
:MaxValues => lMaxValues,
|
115
|
+
:MoyValues => lMoyValues,
|
116
|
+
:DBMinValues => lDBMinValues,
|
117
|
+
:DBMaxValues => lDBMaxValues,
|
118
|
+
:DBMoyValues => lDBMoyValues,
|
119
|
+
:AbsMaxValues => lAbsMaxValues,
|
120
|
+
:AbsMoyValues => lAbsMoyValues,
|
121
|
+
:DBAbsMaxValues => lDBAbsMaxValues,
|
122
|
+
:DBAbsMoyValues => lDBAbsMoyValues,
|
123
|
+
:SampleRate => iInputData.Header.SampleRate,
|
124
|
+
:SampleSize => iInputData.Header.NbrBitsPerSample,
|
125
|
+
:NbrChannels => iInputData.Header.NbrChannels,
|
126
|
+
:DataRate => ((iInputData.Header.NbrChannels*iInputData.Header.NbrBitsPerSample)/8)*iInputData.Header.SampleRate,
|
127
|
+
:NbrDataSamples => iInputData.NbrSamples,
|
128
|
+
:DataLength => Float(iInputData.NbrSamples)/Float(iInputData.Header.SampleRate),
|
129
|
+
:AbsMaxValue => lAbsMaxValue,
|
130
|
+
:DBAbsMaxValue => lAbsMaxValueDB,
|
131
|
+
:PCAbsMaxValue => lAbsMaxValuePC,
|
132
|
+
:AbsMoyValue => lAbsMoyValue,
|
133
|
+
:DBAbsMoyValue => lAbsMoyValueDB,
|
134
|
+
:PCAbsMoyValue => lAbsMoyValuePC,
|
135
|
+
:MaxPossibleValue => lMaxValue,
|
136
|
+
:MinPossibleValue => lMinValue,
|
137
|
+
:RMSValues => lRMSValues,
|
138
|
+
:DBRMSValues => lDBRMSValues
|
139
|
+
}
|
140
|
+
# Display
|
141
|
+
log_info "Min values: #{lResult[:MinValues].join(', ')}"
|
142
|
+
log_info "Min values (db): #{lResult[:DBMinValues].join(', ')}"
|
143
|
+
log_info "Max values: #{lResult[:MaxValues].join(', ')}"
|
144
|
+
log_info "Max values (db): #{lResult[:DBMaxValues].join(', ')}"
|
145
|
+
log_info "Moy values (DC offset): #{lResult[:MoyValues].join(', ')}"
|
146
|
+
log_info "Moy values (DC offset) (db): #{lResult[:DBMoyValues].join(', ')}"
|
147
|
+
log_info "RMS values: #{lResult[:RMSValues].join(', ')}"
|
148
|
+
log_info "RMS values (db): #{lResult[:DBRMSValues].join(', ')}"
|
149
|
+
log_info "Abs Max values: #{lResult[:AbsMaxValues].join(', ')}"
|
150
|
+
log_info "Abs Max values (db): #{lResult[:DBAbsMaxValues].join(', ')}"
|
151
|
+
log_info "Abs Moy values: #{lResult[:AbsMoyValues].join(', ')}"
|
152
|
+
log_info "Abs Moy values (db): #{lResult[:DBAbsMoyValues].join(', ')}"
|
153
|
+
log_info ''
|
154
|
+
log_info 'Header:'
|
155
|
+
log_info "Sample rate: #{lResult[:SampleRate]}"
|
156
|
+
log_info "Sample size (bits): #{lResult[:SampleSize]}"
|
157
|
+
log_info "Number of channels: #{lResult[:NbrChannels]}"
|
158
|
+
log_info "Data rate: #{lResult[:DataRate]} bytes/sec"
|
159
|
+
log_info ''
|
160
|
+
log_info 'Data:'
|
161
|
+
log_info "Number of data samples: #{lResult[:NbrDataSamples]} (#{lResult[:DataLength]} secs)"
|
162
|
+
log_info "Maximal absolute value: #{lResult[:AbsMaxValue]} (#{lResult[:DBAbsMaxValue]} db) (#{lResult[:PCAbsMaxValue]} %)"
|
163
|
+
log_info "Mean absolute value: #{lResult[:AbsMoyValue]} (#{lResult[:DBAbsMoyValue]} db) (#{lResult[:PCAbsMoyValue]} %)"
|
164
|
+
# Write a result file
|
165
|
+
File.open('analyze.result', 'wb') do |oFile|
|
166
|
+
oFile.write(Marshal.dump(lResult))
|
167
|
+
end
|
168
|
+
|
169
|
+
return nil
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
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
|
+
:MapFileName => [
|
10
|
+
'--transformmap <MapFileName>', String,
|
11
|
+
'<MapFileName>: Name of the map file corresponding to the transform',
|
12
|
+
'Specify the file to take the transformation from.'
|
13
|
+
]
|
14
|
+
}
|
15
|
+
}
|
@@ -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 ApplyMap
|
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
|
+
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
|
+
lTransformMap = nil
|
35
|
+
File.open(@MapFileName, 'rb') do |iFile|
|
36
|
+
lTransformMap = Marshal.load(iFile.read)
|
37
|
+
end
|
38
|
+
iInputData.each_buffer do |iBuffer, iNbrSamples, iNbrChannels|
|
39
|
+
lTransformedBuffer = iBuffer.map do |iValue|
|
40
|
+
if (lTransformMap[iValue] == nil)
|
41
|
+
log_warn "Unknown value from the transform map: #{iValue}. Keeping it."
|
42
|
+
next iValue
|
43
|
+
else
|
44
|
+
next lTransformMap[iValue]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
oOutputData.pushBuffer(lTransformedBuffer)
|
48
|
+
end
|
49
|
+
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,30 @@
|
|
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
|
+
:FctFileName => [
|
10
|
+
'--function <FunctionFileName>', String,
|
11
|
+
'<FunctionFileName>: File containing the function definition',
|
12
|
+
'Specify the function to apply'
|
13
|
+
],
|
14
|
+
:Begin => [
|
15
|
+
'--begin <BeginPos>', String,
|
16
|
+
'<BeginPos>: Position to apply volume transformation from. Can be specified as a sample number or a float seconds (ie. 12.3s).',
|
17
|
+
'Specify the first sample that will have the function applied'
|
18
|
+
],
|
19
|
+
:End => [
|
20
|
+
'--end <EndPos>', String,
|
21
|
+
'<EndPos>: Position to apply volume transformation to. Can be specified as a sample number or a float seconds (ie. 12.3s). -1 means to the end of file.',
|
22
|
+
'Specify the last sample that will have the function applied'
|
23
|
+
],
|
24
|
+
:UnitDB => [
|
25
|
+
'--unitdb <Switch>', Integer,
|
26
|
+
'<Switch>: 0 means that units used in the function are ratios. 1 means that units used in the functions are db.',
|
27
|
+
'Specify the unit used in the function'
|
28
|
+
]
|
29
|
+
}
|
30
|
+
}
|
@@ -0,0 +1,72 @@
|
|
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 ApplyVolumeFct
|
11
|
+
|
12
|
+
include WSK::Common
|
13
|
+
include WSK::Functions
|
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
|
+
rError = nil
|
36
|
+
|
37
|
+
lIdxBegin = readDuration(@Begin, iInputData.Header.SampleRate)
|
38
|
+
lIdxEnd = readDuration(@End, iInputData.Header.SampleRate)
|
39
|
+
if (lIdxEnd == -1)
|
40
|
+
lIdxEnd = iInputData.NbrSamples - 1
|
41
|
+
end
|
42
|
+
if (lIdxEnd >= iInputData.NbrSamples)
|
43
|
+
rError = RuntimeError.new("Transformation ends at #{lIdxEnd}, superceeding last sample (#{iInputData.NbrSamples-1})")
|
44
|
+
else
|
45
|
+
lFunction = WSK::Functions::Function.new
|
46
|
+
begin
|
47
|
+
lFunction.read_from_file(@FctFileName)
|
48
|
+
rescue Exception
|
49
|
+
rError = $!
|
50
|
+
end
|
51
|
+
if (rError == nil)
|
52
|
+
# First, write samples before
|
53
|
+
iInputData.each_raw_buffer(0, lIdxBegin-1) do |iInputRawBuffer, iNbrSamples, iNbrChannels|
|
54
|
+
oOutputData.pushRawBuffer(iInputRawBuffer)
|
55
|
+
end
|
56
|
+
# Then apply volume transformation
|
57
|
+
lFunction.apply_on_volume(iInputData, oOutputData, lIdxBegin, lIdxEnd, (@UnitDB == 1))
|
58
|
+
# Last, write samples after
|
59
|
+
iInputData.each_raw_buffer(lIdxEnd+1) do |iInputRawBuffer, iNbrSamples, iNbrChannels|
|
60
|
+
oOutputData.pushRawBuffer(iInputRawBuffer)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
return rError
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,25 @@
|
|
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
|
+
:InputFileName2 => [
|
10
|
+
'--inputfile2 <FileName>', String,
|
11
|
+
'<FileName>: Second file name to compare with.',
|
12
|
+
'Specify the file to compare with. The resulting file will be (inputfile - inputfile2)*Coefficient.'
|
13
|
+
],
|
14
|
+
:Coeff => [
|
15
|
+
'--coeff <Coefficient>', Integer,
|
16
|
+
'<Coefficient>: Coefficient to multiply the differences [default = 1]',
|
17
|
+
'Specify the multiplying coefficient.'
|
18
|
+
],
|
19
|
+
:GenMap => [
|
20
|
+
'--genmap <Switch>', Integer,
|
21
|
+
'<Switch>: 0 or 1, turning off or on the map generation [default = 0]',
|
22
|
+
'If 1, a file (named Output.map) will be created, storing the distortion for each encountered value.'
|
23
|
+
]
|
24
|
+
}
|
25
|
+
}
|
@@ -0,0 +1,238 @@
|
|
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 Compare
|
11
|
+
|
12
|
+
include WSK::Common
|
13
|
+
include WSK::Maps
|
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
|
+
rNbrSamples = iInputData.NbrSamples
|
25
|
+
|
26
|
+
# Get the second input file
|
27
|
+
lError = accessInputWaveFile(@InputFileName2) do |iInputHeader2, iInputData2|
|
28
|
+
rSubError = nil
|
29
|
+
# First check that headers are the same
|
30
|
+
if (iInputHeader2 != iInputData.Header)
|
31
|
+
rSubError = RuntimeError.new("Mismatch headers: First input file: #{iInputData.Header.inspect} Second input file: #{iInputHeader2.inspect}")
|
32
|
+
end
|
33
|
+
# Then we will return the maximal samples
|
34
|
+
if (iInputData2.NbrSamples > iInputData.NbrSamples)
|
35
|
+
log_warn "Second file has more samples (#{iInputData2.NbrSamples} > #{iInputData.NbrSamples})."
|
36
|
+
rNbrSamples = iInputData2.NbrSamples
|
37
|
+
elsif (iInputData2.NbrSamples < iInputData.NbrSamples)
|
38
|
+
log_warn "Second file has less samples (#{iInputData2.NbrSamples} < #{iInputData.NbrSamples})."
|
39
|
+
else
|
40
|
+
log_info 'Files have the same number of samples.'
|
41
|
+
end
|
42
|
+
next rSubError
|
43
|
+
end
|
44
|
+
if (lError != nil)
|
45
|
+
raise lError
|
46
|
+
end
|
47
|
+
@TotalNbrSamples = rNbrSamples
|
48
|
+
|
49
|
+
return rNbrSamples
|
50
|
+
end
|
51
|
+
|
52
|
+
# Execute
|
53
|
+
#
|
54
|
+
# Parameters::
|
55
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
56
|
+
# * *oOutputData* (_Object_): The output data to fill
|
57
|
+
# Return::
|
58
|
+
# * _Exception_: An error, or nil if success
|
59
|
+
def execute(iInputData, oOutputData)
|
60
|
+
@NbrBitsPerSample = iInputData.Header.NbrBitsPerSample
|
61
|
+
@NbrChannels = iInputData.Header.NbrChannels
|
62
|
+
if (@GenMap == 1)
|
63
|
+
# We want to generate the map
|
64
|
+
@DistortionMap = [nil]*(2**@NbrBitsPerSample)
|
65
|
+
else
|
66
|
+
@DistortionMap = nil
|
67
|
+
end
|
68
|
+
# Measure the cumulative errors
|
69
|
+
@CumulativeErrors = 0
|
70
|
+
@MaxValue = 2**(@NbrBitsPerSample-1)-1
|
71
|
+
@MinValue = -2**(@NbrBitsPerSample-1)
|
72
|
+
# Get the second input file
|
73
|
+
rError = accessInputWaveFile(@InputFileName2) do |iInputHeader2, iInputData2|
|
74
|
+
# Loop on both files.
|
75
|
+
# !!! We count on the same buffer size for both files.
|
76
|
+
require 'WSK/ArithmUtils/ArithmUtils'
|
77
|
+
@ArithmUtils = WSK::ArithmUtils::ArithmUtils.new
|
78
|
+
# Initialize buffers
|
79
|
+
lNbrSamplesProcessed = 0
|
80
|
+
iInputData.each_raw_buffer do |iRawBuffer, iNbrSamples, iNbrChannels|
|
81
|
+
break
|
82
|
+
end
|
83
|
+
lRawBuffer1, lNbrSamples1, lNbrChannels = iInputData.get_current_raw_buffer
|
84
|
+
iInputData2.each_raw_buffer do |iRawBuffer, iNbrSamples, iNbrChannels|
|
85
|
+
break
|
86
|
+
end
|
87
|
+
lRawBuffer2, lNbrSamples2, lNbrChannels = iInputData2.get_current_raw_buffer
|
88
|
+
while ((lRawBuffer1 != nil) or
|
89
|
+
(lRawBuffer2 != nil))
|
90
|
+
if (lRawBuffer1 == nil)
|
91
|
+
oOutputData.pushRawBuffer(lRawBuffer2)
|
92
|
+
lNbrSamplesProcessed += lNbrSamples2
|
93
|
+
if (lNbrSamplesProcessed == @TotalNbrSamples)
|
94
|
+
lRawBuffer2 = nil
|
95
|
+
end
|
96
|
+
elsif (lRawBuffer2 == nil)
|
97
|
+
computeInverseMap
|
98
|
+
oOutputData.pushRawBuffer(@ArithmUtils.applyMap(@InverseMap, lRawBuffer1, @NbrBitsPerSample, lNbrSamples1))
|
99
|
+
lNbrSamplesProcessed += lNbrSamples1
|
100
|
+
if (lNbrSamplesProcessed == @TotalNbrSamples)
|
101
|
+
lRawBuffer1 = nil
|
102
|
+
end
|
103
|
+
elsif (lNbrSamples1 == lNbrSamples2)
|
104
|
+
lOutputBuffer, lCumulativeErrors = @ArithmUtils.compareBuffers(
|
105
|
+
lRawBuffer1,
|
106
|
+
lRawBuffer2,
|
107
|
+
@NbrBitsPerSample,
|
108
|
+
@NbrChannels,
|
109
|
+
lNbrSamples1,
|
110
|
+
@Coeff,
|
111
|
+
@DistortionMap
|
112
|
+
)
|
113
|
+
oOutputData.pushRawBuffer(lOutputBuffer)
|
114
|
+
@CumulativeErrors += lCumulativeErrors
|
115
|
+
lNbrSamplesProcessed += lNbrSamples1
|
116
|
+
if (lNbrSamplesProcessed == @TotalNbrSamples)
|
117
|
+
lRawBuffer1 = nil
|
118
|
+
lRawBuffer2 = nil
|
119
|
+
end
|
120
|
+
elsif (lNbrSamples1 > lNbrSamples2)
|
121
|
+
lOutputBuffer, lCumulativeErrors = @ArithmUtils.compareBuffers(
|
122
|
+
lRawBuffer1[0..lRawBuffer2.size-1],
|
123
|
+
lRawBuffer2,
|
124
|
+
@NbrBitsPerSample,
|
125
|
+
@NbrChannels,
|
126
|
+
lNbrSamples1,
|
127
|
+
@Coeff,
|
128
|
+
@DistortionMap
|
129
|
+
)
|
130
|
+
oOutputData.pushRawBuffer(lOutputBuffer)
|
131
|
+
@CumulativeErrors += lCumulativeErrors
|
132
|
+
# Write remaining buffer (-Buffer1)
|
133
|
+
computeInverseMap
|
134
|
+
oOutputData.pushRawBuffer(@ArithmUtils.applyMap(@InverseMap, lRawBuffer1[lRawBuffer2.size..-1], @NbrBitsPerSample, lNbrSamples2 - lNbrSamples1))
|
135
|
+
# Buffer2 is finished
|
136
|
+
lRawBuffer2 = nil
|
137
|
+
lNbrSamplesProcessed += lNbrSamples1
|
138
|
+
else
|
139
|
+
lOutputBuffer, lCumulativeErrors = @ArithmUtils.compareBuffers(
|
140
|
+
lRawBuffer1,
|
141
|
+
lRawBuffer2[0..lRawBuffer1.size-1],
|
142
|
+
@NbrBitsPerSample,
|
143
|
+
@NbrChannels,
|
144
|
+
lNbrSamples1,
|
145
|
+
@Coeff,
|
146
|
+
@DistortionMap
|
147
|
+
)
|
148
|
+
oOutputData.pushRawBuffer(lOutputBuffer)
|
149
|
+
@CumulativeErrors += lCumulativeErrors
|
150
|
+
# Write remaining buffer (Buffer2)
|
151
|
+
oOutputData.pushRawBuffer(lRawBuffer2[lRawBuffer1.size..-1])
|
152
|
+
# Buffer1 is finished
|
153
|
+
lRawBuffer1 = nil
|
154
|
+
lNbrSamplesProcessed += lNbrSamples2
|
155
|
+
end
|
156
|
+
# Read next buffers if they are not finished
|
157
|
+
if (lRawBuffer1 != nil)
|
158
|
+
iInputData.each_raw_buffer(lNbrSamplesProcessed) do |iRawBuffer, iNbrSamples, iNbrChannels|
|
159
|
+
break
|
160
|
+
end
|
161
|
+
lRawBuffer1, lNbrSamples1, lNbrChannels = iInputData.get_current_raw_buffer
|
162
|
+
end
|
163
|
+
if (lRawBuffer2 != nil)
|
164
|
+
iInputData2.each_raw_buffer(lNbrSamplesProcessed) do |iRawBuffer, iNbrSamples, iNbrChannels|
|
165
|
+
break
|
166
|
+
end
|
167
|
+
lRawBuffer2, lNbrSamples2, lNbrChannels = iInputData2.get_current_raw_buffer
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
if (@DistortionMap != nil)
|
172
|
+
# Write the distortion map
|
173
|
+
log_info 'Generate distortion map in distortion.diffmap'
|
174
|
+
File.open('distortion.diffmap', 'wb') do |oFile|
|
175
|
+
oFile.write(Marshal.dump(@DistortionMap))
|
176
|
+
end
|
177
|
+
log_info 'Generate invert map in invert.map'
|
178
|
+
# We want to spot the values that are missing, and the duplicate values
|
179
|
+
lInvertMap = [nil]*(2**@NbrBitsPerSample)
|
180
|
+
(@MinValue .. @MaxValue).each do |iValue|
|
181
|
+
if (@DistortionMap[iValue] == nil)
|
182
|
+
log_warn "Value #{iValue} was not part of the input file"
|
183
|
+
else
|
184
|
+
lRecordedValue = iValue + @DistortionMap[iValue]
|
185
|
+
if (lInvertMap[lRecordedValue] == nil)
|
186
|
+
lInvertMap[lRecordedValue] = iValue
|
187
|
+
else
|
188
|
+
if (iValue.abs < lInvertMap[lRecordedValue].abs)
|
189
|
+
log_warn "Recorded value #{lRecordedValue} is used for both input values #{iValue} and #{lInvertMap[lRecordedValue]}. Setting it to #{iValue}."
|
190
|
+
lInvertMap[lRecordedValue] = iValue
|
191
|
+
else
|
192
|
+
log_warn "Recorded value #{lRecordedValue} is used for both input values #{iValue} and #{lInvertMap[lRecordedValue]}. Keeping it to #{lInvertMap[lRecordedValue]}."
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
(@MinValue .. @MaxValue).each do |iValue|
|
198
|
+
if (lInvertMap[iValue] == nil)
|
199
|
+
log_warn "Missing value that has never been recorded: #{iValue}"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
File.open('invert.map', 'wb') do |oFile|
|
203
|
+
oFile.write(Marshal.dump(lInvertMap))
|
204
|
+
end
|
205
|
+
end
|
206
|
+
puts "Cumulative errors: #{@CumulativeErrors} (#{Float(@CumulativeErrors*100)/Float(iInputData.NbrSamples*(2**@NbrBitsPerSample))} %)"
|
207
|
+
|
208
|
+
return rError
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
# Compute the inverse map
|
214
|
+
def computeInverseMap
|
215
|
+
if (defined?(@InverseMap) == nil)
|
216
|
+
# Compute the function that will perform inversion
|
217
|
+
lMaxValue = 2**(@NbrBitsPerSample-1) - 1
|
218
|
+
lMinValue = -2**(@NbrBitsPerSample-1)
|
219
|
+
@InverseMap = @ArithmUtils.createMapFromFunctions(
|
220
|
+
@NbrBitsPerSample,
|
221
|
+
[ {
|
222
|
+
:FunctionType => WSK::Functions::FCTTYPE_PIECEWISE_LINEAR,
|
223
|
+
:MinValue => lMinValue,
|
224
|
+
:MaxValue => lMaxValue,
|
225
|
+
:Points => {
|
226
|
+
lMinValue => -lMinValue,
|
227
|
+
lMaxValue => -lMaxValue
|
228
|
+
}
|
229
|
+
} ] * @NbrChannels
|
230
|
+
)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
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
|
+
:Value => [
|
10
|
+
'--value <Value>', Integer,
|
11
|
+
'<Value>: Constant value to compare with',
|
12
|
+
'Specify the value to compare with.'
|
13
|
+
],
|
14
|
+
:NbrSamples => [
|
15
|
+
'--nbrsamples <NbrSamples>', Integer,
|
16
|
+
'<NbrSamples>: Number of samples used to compare with',
|
17
|
+
'Specify the number of samples the file should have.'
|
18
|
+
]
|
19
|
+
}
|
20
|
+
}
|