WaveSwissKnife 0.2.0.20120302

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/AUTHORS +4 -0
  2. data/ChangeLog +31 -0
  3. data/Credits +3 -0
  4. data/LICENSE +31 -0
  5. data/README +15 -0
  6. data/ReleaseInfo +8 -0
  7. data/bin/WSK.rb +14 -0
  8. data/ext/WSK/AnalyzeUtils/AnalyzeUtils.c +272 -0
  9. data/ext/WSK/AnalyzeUtils/extconf.rb +7 -0
  10. data/ext/WSK/ArithmUtils/ArithmUtils.c +862 -0
  11. data/ext/WSK/ArithmUtils/extconf.rb +15 -0
  12. data/ext/WSK/CommonBuild.rb +29 -0
  13. data/ext/WSK/FFTUtils/FFTUtils.c +662 -0
  14. data/ext/WSK/FFTUtils/extconf.rb +15 -0
  15. data/ext/WSK/FunctionUtils/FunctionUtils.c +182 -0
  16. data/ext/WSK/FunctionUtils/extconf.rb +15 -0
  17. data/ext/WSK/SilentUtils/SilentUtils.c +431 -0
  18. data/ext/WSK/SilentUtils/extconf.rb +7 -0
  19. data/ext/WSK/VolumeUtils/VolumeUtils.c +494 -0
  20. data/ext/WSK/VolumeUtils/extconf.rb +15 -0
  21. data/external/CommonUtils/build.rb +28 -0
  22. data/external/CommonUtils/include/CommonUtils.h +177 -0
  23. data/external/CommonUtils/src/CommonUtils.c +639 -0
  24. data/lib/WSK/Actions/Analyze.rb +176 -0
  25. data/lib/WSK/Actions/ApplyMap.desc.rb +15 -0
  26. data/lib/WSK/Actions/ApplyMap.rb +57 -0
  27. data/lib/WSK/Actions/ApplyVolumeFct.desc.rb +30 -0
  28. data/lib/WSK/Actions/ApplyVolumeFct.rb +72 -0
  29. data/lib/WSK/Actions/Compare.desc.rb +25 -0
  30. data/lib/WSK/Actions/Compare.rb +238 -0
  31. data/lib/WSK/Actions/ConstantCompare.desc.rb +20 -0
  32. data/lib/WSK/Actions/ConstantCompare.rb +61 -0
  33. data/lib/WSK/Actions/Cut.desc.rb +20 -0
  34. data/lib/WSK/Actions/Cut.rb +60 -0
  35. data/lib/WSK/Actions/CutFirstSignal.desc.rb +25 -0
  36. data/lib/WSK/Actions/CutFirstSignal.rb +72 -0
  37. data/lib/WSK/Actions/DCShifter.desc.rb +15 -0
  38. data/lib/WSK/Actions/DCShifter.rb +67 -0
  39. data/lib/WSK/Actions/DrawFct.desc.rb +20 -0
  40. data/lib/WSK/Actions/DrawFct.rb +59 -0
  41. data/lib/WSK/Actions/FFT.rb +104 -0
  42. data/lib/WSK/Actions/GenAllValues.rb +67 -0
  43. data/lib/WSK/Actions/GenConstant.desc.rb +20 -0
  44. data/lib/WSK/Actions/GenConstant.rb +56 -0
  45. data/lib/WSK/Actions/GenSawtooth.rb +57 -0
  46. data/lib/WSK/Actions/GenSine.desc.rb +20 -0
  47. data/lib/WSK/Actions/GenSine.rb +73 -0
  48. data/lib/WSK/Actions/Identity.rb +43 -0
  49. data/lib/WSK/Actions/Mix.desc.rb +15 -0
  50. data/lib/WSK/Actions/Mix.rb +149 -0
  51. data/lib/WSK/Actions/Multiply.desc.rb +15 -0
  52. data/lib/WSK/Actions/Multiply.rb +73 -0
  53. data/lib/WSK/Actions/NoiseGate.desc.rb +35 -0
  54. data/lib/WSK/Actions/NoiseGate.rb +129 -0
  55. data/lib/WSK/Actions/SilenceInserter.desc.rb +20 -0
  56. data/lib/WSK/Actions/SilenceInserter.rb +87 -0
  57. data/lib/WSK/Actions/SilenceRemover.desc.rb +30 -0
  58. data/lib/WSK/Actions/SilenceRemover.rb +74 -0
  59. data/lib/WSK/Actions/VolumeProfile.desc.rb +35 -0
  60. data/lib/WSK/Actions/VolumeProfile.rb +63 -0
  61. data/lib/WSK/Common.rb +292 -0
  62. data/lib/WSK/FFT.rb +527 -0
  63. data/lib/WSK/Functions.rb +770 -0
  64. data/lib/WSK/Launcher.rb +216 -0
  65. data/lib/WSK/Maps.rb +29 -0
  66. data/lib/WSK/Model/CachedBufferReader.rb +151 -0
  67. data/lib/WSK/Model/Header.rb +133 -0
  68. data/lib/WSK/Model/InputData.rb +193 -0
  69. data/lib/WSK/Model/RawReader.rb +78 -0
  70. data/lib/WSK/Model/WaveReader.rb +91 -0
  71. data/lib/WSK/OutputInterfaces/DirectStream.rb +146 -0
  72. data/lib/WSK/RIFFReader.rb +60 -0
  73. 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'