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