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,87 @@
|
|
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 SilenceInserter
|
11
|
+
|
12
|
+
include WSK::Common
|
13
|
+
|
14
|
+
# Number of samples in the silent buffer
|
15
|
+
# Integer
|
16
|
+
SILENT_BUFFER_SIZE = 2097152
|
17
|
+
|
18
|
+
# Get the number of samples that will be written.
|
19
|
+
# This is called before execute, as it is needed to write the output file.
|
20
|
+
# It is possible to give a majoration: it will be padded with silence.
|
21
|
+
#
|
22
|
+
# Parameters::
|
23
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
24
|
+
# Return::
|
25
|
+
# * _Integer_: The number of samples to be written
|
26
|
+
def get_nbr_samples(iInputData)
|
27
|
+
@NbrBeginSilentSamples = readDuration(@BeginSilenceLength, iInputData.Header.SampleRate)
|
28
|
+
@NbrEndSilentSamples = readDuration(@EndSilenceLength, iInputData.Header.SampleRate)
|
29
|
+
|
30
|
+
return iInputData.NbrSamples+@NbrBeginSilentSamples+@NbrEndSilentSamples
|
31
|
+
end
|
32
|
+
|
33
|
+
# Execute
|
34
|
+
#
|
35
|
+
# Parameters::
|
36
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
37
|
+
# * *oOutputData* (_Object_): The output data to fill
|
38
|
+
# Return::
|
39
|
+
# * _Exception_: An error, or nil if success
|
40
|
+
def execute(iInputData, oOutputData)
|
41
|
+
pushSilence(iInputData, oOutputData, @NbrBeginSilentSamples)
|
42
|
+
pushFile(iInputData, oOutputData)
|
43
|
+
pushSilence(iInputData, oOutputData, @NbrEndSilentSamples)
|
44
|
+
|
45
|
+
return nil
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Push silence in the file
|
51
|
+
#
|
52
|
+
# Parameters::
|
53
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
54
|
+
# * *oOutputData* (_Object_): The output data to fill
|
55
|
+
# * *iNbrSamples* (_Integer_): The number of silent samples to insert
|
56
|
+
def pushSilence(iInputData, oOutputData, iNbrSamples)
|
57
|
+
lRawSampleSize = (iInputData.Header.NbrChannels*iInputData.Header.NbrBitsPerSample)/8
|
58
|
+
lNbrCompleteBuffers = iNbrSamples/SILENT_BUFFER_SIZE
|
59
|
+
if (lNbrCompleteBuffers > 0)
|
60
|
+
lCompleteRawBuffer = "\000"*SILENT_BUFFER_SIZE*lRawSampleSize
|
61
|
+
lNbrCompleteBuffers.times do |iIdx|
|
62
|
+
oOutputData.pushRawBuffer(lCompleteRawBuffer)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
lLastBufferSize = iNbrSamples % SILENT_BUFFER_SIZE
|
66
|
+
if (lLastBufferSize > 0)
|
67
|
+
oOutputData.pushRawBuffer("\000"*lLastBufferSize*lRawSampleSize)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Push the file
|
72
|
+
#
|
73
|
+
# Parameters::
|
74
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
75
|
+
# * *oOutputData* (_Object_): The output data to fill
|
76
|
+
def pushFile(iInputData, oOutputData)
|
77
|
+
# Then write the file
|
78
|
+
iInputData.each_raw_buffer do |iInputRawBuffer, iNbrSamples, iNbrChannels|
|
79
|
+
oOutputData.pushRawBuffer(iInputRawBuffer)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
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
|
+
: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 keep 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 keep the noise after the non-silent part.'
|
23
|
+
],
|
24
|
+
:NoiseFFTFileName => [
|
25
|
+
'--noisefft <FFTFile>', String,
|
26
|
+
'<FFTFile>: File containing the FFT profile of the reference noise.',
|
27
|
+
'This is used to compare potential noise profile with the real noise profile.'
|
28
|
+
]
|
29
|
+
}
|
30
|
+
}
|
@@ -0,0 +1,74 @@
|
|
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 SilenceRemover
|
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
|
+
lSilenceThresholds = readThresholds(@SilenceThreshold, iInputData.Header.NbrChannels)
|
25
|
+
@IdxFirstSample, lNextAboveThresholds = getNextNonSilentSample(iInputData, 0, lSilenceThresholds, nil, nil, false)
|
26
|
+
if (@IdxFirstSample == nil)
|
27
|
+
log_info 'The whole file is silent'
|
28
|
+
@IdxFirstSample = 0
|
29
|
+
@IdxLastSample = 0
|
30
|
+
else
|
31
|
+
lNoiseFFTMaxDistance, lNoiseFFTProfile = readFFTProfile(@NoiseFFTFileName)
|
32
|
+
@IdxLastSample, lNextAboveThresholds = getNextNonSilentSample(iInputData, iInputData.NbrSamples-1, lSilenceThresholds, lNoiseFFTProfile, lNoiseFFTMaxDistance, true)
|
33
|
+
if (@IdxLastSample == nil)
|
34
|
+
log_err "A beginning sample has been found (#{@IdxFirstSample}), but no ending sample could. This is a bug."
|
35
|
+
raise RuntimeError.new("A beginning sample has been found (#{@IdxFirstSample}), but no ending sample could. This is a bug.")
|
36
|
+
end
|
37
|
+
# Compute the limits of fadein and fadeout
|
38
|
+
lNbrAttack = readDuration(@Attack, iInputData.Header.SampleRate)
|
39
|
+
lNbrRelease = readDuration(@Release, iInputData.Header.SampleRate)
|
40
|
+
@IdxFirstSample -= lNbrAttack
|
41
|
+
if (@IdxFirstSample < 0)
|
42
|
+
log_warn "Attack duration #{lNbrAttack} makes first non silent sample negative. Setting it to 0. Please consider decreasing attack duration."
|
43
|
+
@IdxFirstSample = 0
|
44
|
+
end
|
45
|
+
@IdxLastSample += lNbrRelease
|
46
|
+
if (@IdxLastSample >= iInputData.NbrSamples)
|
47
|
+
log_warn "Release duration #{lNbrRelease} makes last non silent sample overflow. Setting it to the last sample (#{iInputData.NbrSamples - 1}). Please consider decreasing release duration."
|
48
|
+
@IdxLastSample = iInputData.NbrSamples - 1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
return @IdxLastSample-@IdxFirstSample+1
|
53
|
+
end
|
54
|
+
|
55
|
+
# Execute
|
56
|
+
#
|
57
|
+
# Parameters::
|
58
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The input data
|
59
|
+
# * *oOutputData* (_Object_): The output data to fill
|
60
|
+
# Return::
|
61
|
+
# * _Exception_: An error, or nil if success
|
62
|
+
def execute(iInputData, oOutputData)
|
63
|
+
iInputData.each_raw_buffer(@IdxFirstSample, @IdxLastSample) do |iInputRawBuffer, iNbrSamples, iNbrChannels|
|
64
|
+
oOutputData.pushRawBuffer(iInputRawBuffer)
|
65
|
+
end
|
66
|
+
|
67
|
+
return nil
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
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
|
+
:FctFileName => [
|
10
|
+
'--function <FunctionFileName>', String,
|
11
|
+
'<FunctionFileName>: File that will contain the volume profile',
|
12
|
+
'Specify the file to write with the profile function'
|
13
|
+
],
|
14
|
+
:Begin => [
|
15
|
+
'--begin <BeginPos>', String,
|
16
|
+
'<BeginPos>: Position to begin profiling volume from. Can be specified as a sample number or a float seconds (ie. 12.3s).',
|
17
|
+
'Specify the beginning of the profile'
|
18
|
+
],
|
19
|
+
:End => [
|
20
|
+
'--end <EndPos>', String,
|
21
|
+
'<EndPos>: Position to end profiling volume to. Can be specified as a sample number or a float seconds (ie. 12.3s). -1 means to the end.',
|
22
|
+
'Specify the ending of the profile'
|
23
|
+
],
|
24
|
+
:Interval => [
|
25
|
+
'--interval <Interval>', String,
|
26
|
+
'<Interval>: Number of samples defining an interval for the volume measurement. Can be specified as a sample number or a float seconds (ie. 12.3s).',
|
27
|
+
'Specify the granularity of the volume profile'
|
28
|
+
],
|
29
|
+
:RMSRatio => [
|
30
|
+
'--rmsratio <Ratio>', Float,
|
31
|
+
'<Ratio>: Ratio of RMS measure vs Peak level measure, expressed in floats of range [0.0 .. 1.0]. 0.0 = Only Peak. 1.0 = Only RMS.',
|
32
|
+
'Specify the way the level is measured.'
|
33
|
+
]
|
34
|
+
}
|
35
|
+
}
|
@@ -0,0 +1,63 @@
|
|
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 VolumeProfile
|
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
|
+
rError = nil
|
35
|
+
|
36
|
+
lIdxBegin = readDuration(@Begin, iInputData.Header.SampleRate)
|
37
|
+
lIdxEnd = readDuration(@End, iInputData.Header.SampleRate)
|
38
|
+
lInterval = readDuration(@Interval, iInputData.Header.SampleRate)
|
39
|
+
if (lIdxEnd == -1)
|
40
|
+
lIdxEnd = iInputData.NbrSamples - 1
|
41
|
+
end
|
42
|
+
if (lIdxEnd >= iInputData.NbrSamples)
|
43
|
+
rError = RuntimeError.new("Profile ends at #{lIdxEnd}, superceeding last sample (#{iInputData.NbrSamples-1})")
|
44
|
+
else
|
45
|
+
lFunction = WSK::Functions::Function.new
|
46
|
+
lFunction.read_from_input_volume(iInputData, lIdxBegin, lIdxEnd, lInterval, @RMSRatio)
|
47
|
+
# Normalize the volume function on a [-1..1] scale
|
48
|
+
lFunction.divide_by(Rational(2)**(iInputData.Header.NbrBitsPerSample-1))
|
49
|
+
lMinX, lMinY, lMaxX, lMaxY = lFunction.get_bounds
|
50
|
+
lDBMinY = lFunction.value_val_2_db(lMinY, Rational(1))
|
51
|
+
lDBMaxY = lFunction.value_val_2_db(lMaxY, Rational(1))
|
52
|
+
log_info "Dynamic range: [#{sprintf('%.2f',lMinY)} - #{sprintf('%.2f',lMaxY)}] ([#{sprintf('%.2f',lDBMinY)}db - #{sprintf('%.2f',lDBMaxY)}db] = #{sprintf('%.2f',lDBMaxY-lDBMinY)}db)"
|
53
|
+
lFunction.write_to_file(@FctFileName)
|
54
|
+
end
|
55
|
+
|
56
|
+
return rError
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
data/lib/WSK/Common.rb
ADDED
@@ -0,0 +1,292 @@
|
|
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
|
+
# Common methods
|
9
|
+
module Common
|
10
|
+
|
11
|
+
# Parse plugins
|
12
|
+
def parsePlugins
|
13
|
+
# Protect from re-entrance to avoid useless error messages in regression
|
14
|
+
if (defined?($WSK_PluginsParsed) == nil)
|
15
|
+
lLibDir = File.expand_path(File.dirname(__FILE__))
|
16
|
+
require 'rUtilAnts/Plugins'
|
17
|
+
RUtilAnts::Plugins::install_plugins_on_object
|
18
|
+
parse_plugins_from_dir('Actions', "#{lLibDir}/Actions", 'WSK::Actions')
|
19
|
+
parse_plugins_from_dir('OutputInterfaces', "#{lLibDir}/OutputInterfaces", 'WSK::OutputInterfaces')
|
20
|
+
$WSK_PluginsParsed = true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Access a WAVE file for read access.
|
25
|
+
# Give its header information as well as a proxy to access its data.
|
26
|
+
# Proxies are used to cache accesses as they might be time consuming.
|
27
|
+
#
|
28
|
+
# Parameters::
|
29
|
+
# * *iFileName* (_String_): The file name to open
|
30
|
+
# * *CodeBlock*: The code block called when accessing the file:
|
31
|
+
# * *iHeader* (<em>WSK::Model::Header</em>): The file header information
|
32
|
+
# * *iInputData* (<em>WSK::Model::InputData</em>): The file data proxy
|
33
|
+
# * Return::
|
34
|
+
# * _Exception_: Error, or nil in case of success
|
35
|
+
# Return::
|
36
|
+
# * _Exception_: Error, or nil in case of success
|
37
|
+
def accessInputWaveFile(iFileName)
|
38
|
+
rError = nil
|
39
|
+
|
40
|
+
File.open(iFileName, 'rb') do |iFile|
|
41
|
+
log_info "Access #{iFileName}"
|
42
|
+
rError, lHeader, lInputData = getWaveFileAccesses(iFile)
|
43
|
+
if (rError == nil)
|
44
|
+
rError = yield(lHeader, lInputData)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
return rError
|
49
|
+
end
|
50
|
+
|
51
|
+
# Give Header and Data access from an opened Wave file
|
52
|
+
#
|
53
|
+
# Parameters::
|
54
|
+
# * *iFile* (_IO_): The IO handler
|
55
|
+
# Return::
|
56
|
+
# * _Exception_: An error, or nil in case of success
|
57
|
+
# * <em>WSK::Model::Header</em>: The header
|
58
|
+
# * <em>WSK::Model::InputData</em>: The input data
|
59
|
+
def getWaveFileAccesses(iFile)
|
60
|
+
rError = nil
|
61
|
+
rHeader = nil
|
62
|
+
rInputData = nil
|
63
|
+
|
64
|
+
# Read header
|
65
|
+
rError, rHeader = readHeader(iFile)
|
66
|
+
log_debug "Header: #{rHeader.inspect}"
|
67
|
+
if (rError == nil)
|
68
|
+
# Get a data handle
|
69
|
+
rInputData = WSK::Model::InputData.new(iFile, rHeader)
|
70
|
+
rError = rInputData.init_cursor
|
71
|
+
end
|
72
|
+
|
73
|
+
return rError, rHeader, rInputData
|
74
|
+
end
|
75
|
+
|
76
|
+
# Access a WAVE file for write access.
|
77
|
+
# Give its header information as well as a proxy to access its data.
|
78
|
+
# Proxies are used to cache accesses as they might be time consuming.
|
79
|
+
#
|
80
|
+
# Parameters::
|
81
|
+
# * *iFileName* (_String_): The file name to write
|
82
|
+
# * *iHeader* (<em>WSK::Model::Header</em>): The file header information to write
|
83
|
+
# * *iOutputInterface* (_Object_): The output interface
|
84
|
+
# * *iNbrOutputDataSamples* (_Integer_): The number of output data samples
|
85
|
+
# * *CodeBlock*: The code block called when accessing the file
|
86
|
+
# * Return::
|
87
|
+
# * _Exception_: Error, or nil in case of success
|
88
|
+
# Return::
|
89
|
+
# * _Exception_: Error, or nil in case of success
|
90
|
+
def accessOutputWaveFile(iFileName, iHeader, iOutputInterface, iNbrOutputDataSamples)
|
91
|
+
rError = nil
|
92
|
+
|
93
|
+
File.open(iFileName, 'wb') do |oFile|
|
94
|
+
# Initialize the output interface
|
95
|
+
rError = iOutputInterface.initInterface(oFile, iHeader, iNbrOutputDataSamples)
|
96
|
+
if (rError == nil)
|
97
|
+
# Write header
|
98
|
+
log_info "Write header in #{iFileName}"
|
99
|
+
rError = writeHeader(oFile, iHeader, iNbrOutputDataSamples)
|
100
|
+
if (rError == nil)
|
101
|
+
# Call client code
|
102
|
+
rError = yield
|
103
|
+
if (rError == nil)
|
104
|
+
# Finalize the output interface
|
105
|
+
lNbrSamplesWritten = iOutputInterface.finalize
|
106
|
+
# Pad with \x00 if lNbrSamplesWritten is below iNbrOutputDataSamples
|
107
|
+
if (lNbrSamplesWritten < iNbrOutputDataSamples)
|
108
|
+
log_warn "#{lNbrSamplesWritten} samples written out of #{iNbrOutputDataSamples}: padding with silence."
|
109
|
+
oFile.write(iHeader.getEncodedString([0]*(iNbrOutputDataSamples-lNbrSamplesWritten)*iHeader.NbrChannels))
|
110
|
+
elsif (lNbrSamplesWritten > iNbrOutputDataSamples)
|
111
|
+
log_warn "#{lNbrSamplesWritten} samples written, but #{iNbrOutputDataSamples} only were expected. #{lNbrSamplesWritten - iNbrOutputDataSamples} samples more."
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
return rError
|
119
|
+
end
|
120
|
+
|
121
|
+
# Read a duration and give its corresponding value in samples
|
122
|
+
# Throws an exception in case of bad format.
|
123
|
+
#
|
124
|
+
# Parameters::
|
125
|
+
# * *iStrDuration* (_String_): The duration to read
|
126
|
+
# * *iSampleRate* (_Integer_): Sample rate of the file for which this duration applies
|
127
|
+
# Return::
|
128
|
+
# * _Integer_: The number of samples corresponding to this duration
|
129
|
+
def readDuration(iStrDuration, iSampleRate)
|
130
|
+
rNbrSamples = nil
|
131
|
+
|
132
|
+
if (iStrDuration[-1..-1] == 's')
|
133
|
+
rNbrSamples = ((iStrDuration[0..-2].to_f)*iSampleRate).round
|
134
|
+
else
|
135
|
+
rNbrSamples = iStrDuration.to_i
|
136
|
+
end
|
137
|
+
|
138
|
+
return rNbrSamples
|
139
|
+
end
|
140
|
+
|
141
|
+
# Read a given threshold indication on the command line.
|
142
|
+
#
|
143
|
+
# Parameters::
|
144
|
+
# * *iStrThresholds* (_String_): The thresholds to read
|
145
|
+
# * *iNbrChannels* (_Integer_): Number of channels for the file being decoded
|
146
|
+
# Return::
|
147
|
+
# * <em>list< [Integer,Integer] ></em>: The list of min and max values, per channel
|
148
|
+
def readThresholds(iStrThresholds, iNbrChannels)
|
149
|
+
rThresholds = nil
|
150
|
+
|
151
|
+
if (iStrThresholds.split('|').size == 1)
|
152
|
+
if (iStrThresholds.split(',').size == 1)
|
153
|
+
rThresholds = [ [ -iStrThresholds.to_i, iStrThresholds.to_i ] ] * iNbrChannels
|
154
|
+
else
|
155
|
+
rThresholds = [iStrThresholds.split(',').map { |iStrValue| iStrValue.to_i }] * iNbrChannels
|
156
|
+
end
|
157
|
+
else
|
158
|
+
rThresholds = []
|
159
|
+
iStrThresholds.split('|').each do |iThresholdInfo|
|
160
|
+
if (iThresholdInfo.split(',').size == 1)
|
161
|
+
rThresholds << [ -iThresholdInfo.to_i, iThresholdInfo.to_i ]
|
162
|
+
else
|
163
|
+
rThresholds << iThresholdInfo.split(',').map { |iStrValue| iStrValue.to_i }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
return rThresholds
|
169
|
+
end
|
170
|
+
|
171
|
+
# Read an FFT profile file
|
172
|
+
#
|
173
|
+
# Parameters::
|
174
|
+
# * *iFileName* (_String_): Name of the FFT profile file, or 'none' if none.
|
175
|
+
# Return::
|
176
|
+
# * _Integer_: Maximal FFT distance beyond which we consider being too far from the FFT profile
|
177
|
+
# * <em>[Integer,Integer,list<list<Integer>>]</em>: The FFT profile
|
178
|
+
def readFFTProfile(iFileName)
|
179
|
+
rFFTMaxDistance = nil
|
180
|
+
rFFTProfile = nil
|
181
|
+
|
182
|
+
if (iFileName != 'none')
|
183
|
+
if (File.exists?(iFileName))
|
184
|
+
# Load the reference FFT profile
|
185
|
+
File.open(iFileName, 'rb') do |iFile|
|
186
|
+
rFFTMaxDistance, rFFTProfile = Marshal.load(iFile.read)
|
187
|
+
end
|
188
|
+
# We add an arbitrary percentage to the average distance.
|
189
|
+
rFFTMaxDistance = (rFFTMaxDistance*1.01).to_i
|
190
|
+
else
|
191
|
+
log_err "Missing file #{iFileName}. Ignoring FFT."
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
return rFFTMaxDistance, rFFTProfile
|
196
|
+
end
|
197
|
+
|
198
|
+
# Convert a value to its db notation and % notation
|
199
|
+
#
|
200
|
+
# Parameters::
|
201
|
+
# * *iValue* (_Integer_): The value
|
202
|
+
# * *iMaxValue* (_Integer_): The maximal possible value
|
203
|
+
# Return::
|
204
|
+
# * _Float_: Its corresponding db
|
205
|
+
# * _Float_: Its corresponding percentage
|
206
|
+
def val2db(iValue, iMaxValue)
|
207
|
+
if (iValue == 0)
|
208
|
+
return -1.0/0, 0.0
|
209
|
+
else
|
210
|
+
if (defined?(@Log2) == nil)
|
211
|
+
@Log2 = Math.log(2.0)
|
212
|
+
end
|
213
|
+
return -6*(Math.log(Float(iMaxValue))-Math.log(Float(iValue.abs)))/@Log2, (Float(iValue.abs)*100)/Float(iMaxValue)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
# Write the header to a file.
|
220
|
+
#
|
221
|
+
# Parameters::
|
222
|
+
# * *oFile* (_IO_): File to write
|
223
|
+
# * *iHeader* (<em>WSK::Model::Header</em>): The header to write
|
224
|
+
# * *iNbrOutputDataSamples* (_Integer_): The number of output data samples
|
225
|
+
# Return::
|
226
|
+
# * _Exception_: The exception, or nil in case of success
|
227
|
+
def writeHeader(oFile, iHeader, iNbrOutputDataSamples)
|
228
|
+
rError = nil
|
229
|
+
|
230
|
+
lBlockAlign = (iHeader.NbrChannels*iHeader.NbrBitsPerSample)/8
|
231
|
+
lOutputDataSize = iNbrOutputDataSamples * lBlockAlign
|
232
|
+
oFile.write("RIFF#{[lOutputDataSize+36].pack('V')}WAVEfmt #{[16, iHeader.AudioFormat, iHeader.NbrChannels, iHeader.SampleRate, iHeader.SampleRate * lBlockAlign, lBlockAlign, iHeader.NbrBitsPerSample].pack('VvvVVvv')}data#{[lOutputDataSize].pack('V')}")
|
233
|
+
|
234
|
+
return rError
|
235
|
+
end
|
236
|
+
|
237
|
+
# Get the header from a file.
|
238
|
+
# This also checks for unsupported types.
|
239
|
+
#
|
240
|
+
# Parameters::
|
241
|
+
# * *iFile* (_IO_): File to read
|
242
|
+
# Return::
|
243
|
+
# * _Exception_: The exception, or nil in case of success
|
244
|
+
# * <em>WSK::Model::Header</em>: The corresponding header, or nil in case of failure
|
245
|
+
def readHeader(iFile)
|
246
|
+
rError = nil
|
247
|
+
rHeader = nil
|
248
|
+
|
249
|
+
iFile.seek(0)
|
250
|
+
lBinaryHeader = iFile.read(12)
|
251
|
+
# Check if the format is ok
|
252
|
+
if (lBinaryHeader[0..3] != 'RIFF')
|
253
|
+
rError = RuntimeError.new('Invalid header: not RIFF')
|
254
|
+
elsif (lBinaryHeader[8..11] != 'WAVE')
|
255
|
+
rError = RuntimeError.new('Invalid header: not WAVE')
|
256
|
+
else
|
257
|
+
lReader = RIFFReader.new(iFile)
|
258
|
+
rError, lFMTSize = lReader.setFilePos('fmt ')
|
259
|
+
if (rError == nil)
|
260
|
+
# lFMTSize should be 18
|
261
|
+
if (lFMTSize >= 16)
|
262
|
+
lBinFormat = iFile.read(lFMTSize)
|
263
|
+
# Read values
|
264
|
+
lAudioFormat, lNbrChannels, lSampleRate, lByteRate, lBlockAlign, lNbrBitsPerSample = lBinFormat[0..17].unpack('vvVVvv')
|
265
|
+
# Check values
|
266
|
+
if (lBlockAlign != (lNbrChannels*lNbrBitsPerSample)/8)
|
267
|
+
rError = RuntimeError.new("Invalid header: Block alignment (#{lBlockAlign}) should be #{(lNbrChannels*lNbrBitsPerSample)/8}.")
|
268
|
+
elsif (lByteRate != lBlockAlign*lSampleRate)
|
269
|
+
rError = RuntimeError.new("Invalid header: Byte rate (#{lByteRate}) should be #{lBlockAlign*lSampleRate}.")
|
270
|
+
else
|
271
|
+
# OK, header is valid
|
272
|
+
rHeader = WSK::Model::Header.new(lAudioFormat, lNbrChannels, lSampleRate, lNbrBitsPerSample)
|
273
|
+
end
|
274
|
+
else
|
275
|
+
rError = RuntimeError.new("Invalid fmt header size: #{lFMTSize} should be >= 16.")
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
return rError, rHeader
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
require 'WSK/RIFFReader'
|
288
|
+
require 'WSK/Model/InputData'
|
289
|
+
require 'WSK/Model/Header'
|
290
|
+
require 'WSK/FFT'
|
291
|
+
require 'WSK/Maps'
|
292
|
+
require 'WSK/Functions'
|