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,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'
|