JOCLoudness 1.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 233b016a7113d6d0d79533b247d6962922467c1a
4
+ data.tar.gz: 3aae6b46fdf8eb78b818b3318fd9a46562d5f9d1
5
+ SHA512:
6
+ metadata.gz: afa516ea690e777fff9c5bef87bae79fddf1750022f5c4bc42135cbec06152d6784f510349bf6b9f54c6d3dbfeb92c959f15e6b87a6180338cf50f84d654822e
7
+ data.tar.gz: af9f65f0f1ae9df978eadf8acfb4aba34c2d9c1fcada4418a87df2624608c47e1f3c19491358a34e6625ecbe9d3828f557f561ce3cfe2a4f888e4e8e441b59dc
@@ -0,0 +1,4 @@
1
+ #file: JOCLoudness.rb
2
+ require 'logger'
3
+ require_relative './WavReader/wavfile'
4
+ require_relative './Loudness/jocLoud.rb'
@@ -0,0 +1,85 @@
1
+ #file: IIRFilter.rb
2
+
3
+
4
+ class IIRFilter
5
+
6
+ # Ini obj
7
+ def initialize(order)
8
+ @OldSamples = nil
9
+ @ACoefs= nil
10
+ @BCoefs= nil
11
+ @Order = 0
12
+
13
+ @Order = order
14
+
15
+ @OldSamples = Array.new(@Order) {0.0}
16
+
17
+ end
18
+
19
+ #Set filters coeficients
20
+ def SetCoefs (b, a)
21
+ if ((a.kind_of?(Array) == false) or (b.kind_of?(Array) == false))
22
+ raise 'Filter coeficinets must be an array'
23
+ end
24
+ if ((a.length != @Order + 1) or (b.length != @Order + 1))
25
+ raise "Incorrect number of coeficients. A filter of order #{@Order} must have #{@Order+1} coeficients"
26
+ end
27
+
28
+ @ACoefs = a
29
+ @BCoefs = b
30
+
31
+ end
32
+
33
+ #Filter 1 sample
34
+ def FilterSample (sampleIn)
35
+
36
+ nFilterCoefs = @Order + 1;
37
+ sx = sampleIn;
38
+ spx = 0.0;
39
+ ox = 0.0;
40
+
41
+ #A coefs
42
+ nA = 0;
43
+ while nA < nFilterCoefs do
44
+ if (nA == 0)
45
+ spx = sx.to_f;
46
+ else
47
+ spx = spx.to_f - @ACoefs[nA].to_f * @OldSamples[nA - 1].to_f #Spx - m_pACoefs[nA]*m_pOldSamples[nA-1];
48
+ end
49
+
50
+ nA = nA + 1
51
+ end
52
+
53
+ #B coefs
54
+ nB = 0;
55
+ while nB < nFilterCoefs do
56
+ if (nB == 0)
57
+ ox = ox.to_f + @BCoefs[nB].to_f * spx.to_f #Ox = Ox + m_pBCoefs[nB]*Spx;
58
+ else
59
+ ox = ox.to_f + @BCoefs[nB].to_f * @OldSamples[nB - 1].to_f #Ox = Ox + m_pBCoefs[nB]*m_pOldSamples[nB-1];
60
+ end
61
+
62
+ nB = nB + 1
63
+ end
64
+
65
+ #Set Old samples
66
+ nS = @Order
67
+ while nS > 0 do
68
+ if ((nS - 2) < 0)
69
+ @OldSamples[nS - 1] = spx.to_f
70
+ else
71
+ @OldSamples[nS - 1] = @OldSamples[nS - 2].to_f
72
+ end
73
+
74
+ nS = nS - 1
75
+ end
76
+
77
+ return ox
78
+ end
79
+
80
+ #Show coefs
81
+ def Info
82
+ return "IIR Filter Order #{@Order}\nACoefs #{@ACoefs}\nBCoefs #{@BCoefs}\n"
83
+ end
84
+
85
+ end
@@ -0,0 +1,163 @@
1
+ #file: jocLoud.rb
2
+ require_relative './RlbFilter/RLBFilter.rb'
3
+ require_relative './PreFilter/PREFilter.rb'
4
+ require_relative './Mean/MeanFast.rb'
5
+
6
+ class JOCLoud
7
+
8
+ # Ini obj
9
+ def initialize
10
+ @logger = nil
11
+
12
+ @fs = 48000
13
+ @nchannels = 2
14
+
15
+ @channelweights = [1.0, 1.0, 1.0, 1.41, 1.41, 0, 0, 0]
16
+
17
+ @prefilters = nil
18
+ @rlbfilters = nil
19
+
20
+ @meansqfiltersPlain = nil
21
+
22
+ @calculatedLKFS = -Float::INFINITY
23
+
24
+ @iscomputingLKFS = false
25
+ end
26
+
27
+ #Set logger
28
+ def Setlogger(logger)
29
+ @logger = logger
30
+ end
31
+
32
+ def ini(fs, nchannels)
33
+
34
+ @fs = fs
35
+ @nchannels = nchannels
36
+
37
+ Log(Logger::INFO,"Starting ini fs: #{@fs}, Num. channels: #{@nchannels}")
38
+
39
+ #Ini all components
40
+ @prefilters = Array.new
41
+ @rlbfilters = Array.new
42
+ @meansqfiltersPlain = Array.new
43
+
44
+ nc = 0
45
+ while nc < @nchannels do
46
+ #Ini PRE filters (one per channel)
47
+ prefilter = PREFilter.new(@fs)
48
+ @prefilters.insert(-1, prefilter) #Insert at loast position
49
+ Log(Logger::INFO,"Channel #{@nc} PREFilter info: #{prefilter.Info}")
50
+
51
+ #Ini RLB filters (one per channel)
52
+ rlbfilter = RLBFilter.new(@fs)
53
+ @rlbfilters.insert(-1, rlbfilter) #Insert at loast position
54
+ Log(Logger::INFO,"Channel #{@nc} RLB Filter info: #{rlbfilter.Info}")
55
+
56
+ #Plain mean (initializes in TOTAL mode)
57
+ mfast = MeanFast.new
58
+ @meansqfiltersPlain.insert(-1, mfast) #Insert at loast position
59
+
60
+ nc = nc + 1
61
+ end
62
+
63
+ @calculatedLKFS = -Float::INFINITY
64
+
65
+ @iscomputingLKFS = false
66
+
67
+ Log(Logger::DEBUG,"Endini")
68
+ end
69
+
70
+ def SetChannelWeigths(channelweights)
71
+
72
+ if (@iscomputingLKFS == true)
73
+ raise 'Ivalid state. You cannot change channelwheights during a loudness calculation'
74
+ end
75
+
76
+ if (channelweights.kind_of?(Array) == false)
77
+ raise 'Channel wrights must be an array'
78
+ end
79
+ if (channelweights.length < @nchannels)
80
+ raise 'Channelweigts length must be equal or greater as number of channels'
81
+ end
82
+
83
+ @channelweights = channelweights
84
+
85
+ Log(Logger::INFO,"Set new channel wheights: #{@channelweights}")
86
+ end
87
+
88
+ def Addsample (dsample)
89
+ @iscomputingLKFS = true
90
+
91
+ if (dsample.kind_of?(Array) == false)
92
+ raise 'dsample must be an array'
93
+ end
94
+ if (dsample.length < @nchannels)
95
+ raise 'dsample length must be equal or greater as number of channels'
96
+ end
97
+
98
+ nc = 0
99
+ while nc < @nchannels do
100
+
101
+ presampled = @prefilters[nc].FilterSample(dsample[nc])
102
+ rlbsampled = @rlbfilters[nc].FilterSample(presampled)
103
+ @meansqfiltersPlain[nc].AddSample(rlbsampled)
104
+
105
+ Log(Logger::DEBUG,"Enter sample #{dsample[nc]}, presample = #{presampled}, rlbsmample = #{rlbsampled}")
106
+
107
+ nc = nc + 1
108
+ end
109
+ end
110
+
111
+ def GetLoudnessLKFS
112
+ meanvals = Array.new
113
+ nc = 0
114
+ while nc < @nchannels do
115
+ mean = @meansqfiltersPlain[nc].GetMeanVal
116
+ meanvals.insert(-1, mean) #Insert at last position
117
+
118
+ nc = nc + 1
119
+ end
120
+
121
+ Log(Logger::DEBUG,"Enter GetLoudnessLKFS channel means: #{mean}, Channel Weights: #{@channelweights}")
122
+
123
+ #Calc Loudness [LKFS]
124
+ nc = 0
125
+ dsum = 0.0
126
+ while nc < @nchannels do
127
+ dsum = dsum + @channelweights[nc].to_f * meanvals[nc].to_f
128
+
129
+ nc = nc + 1
130
+ end
131
+
132
+ @calculatedLKFS = -0.691 + 10.0 * Math.log10(dsum.to_f)
133
+
134
+ Log(Logger::DEBUG,"All sum: #{dsum}, Channel Weights: #{@channelweights}")
135
+
136
+ Log(Logger::INFO,"Loudness: #{@calculatedLKFS} LKFS")
137
+
138
+ return @calculatedLKFS
139
+ end
140
+
141
+ private
142
+
143
+ def Log (type, message)
144
+
145
+ if (@logger != nil)
146
+ if (type == Logger::FATAL)
147
+ @logger.fatal(message)
148
+ end
149
+ if (type == Logger::ERROR)
150
+ @logger.error(message)
151
+ end
152
+ if (type == Logger::WARN)
153
+ @logger.warn(message)
154
+ end
155
+ if (type == Logger::INFO)
156
+ @logger.info(message)
157
+ end
158
+ if (type == Logger::DEBUG)
159
+ @logger.debug(message)
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,123 @@
1
+ #file: MeanFast.rb
2
+
3
+ # define MeanFast
4
+ class MeanFast
5
+
6
+ module MEAN_MODE
7
+ MEAN_OVERLAPPED = 1
8
+ MEAN_TOTAL = 2
9
+ end
10
+
11
+ # Ini obj
12
+ def initialize
13
+ @bIni = false
14
+ @mode = MEAN_MODE::MEAN_TOTAL
15
+
16
+ #Vars used in Overlapped mode
17
+ @lSamplesInInterval = 0
18
+ @lSamplesOverlapped = 0
19
+ @lGetMeanEveryXSamples = 0
20
+ @nSums = 0
21
+ @Sums = 0
22
+ @lNumSamplesLoaded = 0
23
+ @nSumIndex = 0
24
+ @queueMean = Array.new
25
+
26
+ #Vars used in TOTAL mode
27
+ @lNumAcumulatedSamplesMean = 0
28
+ @dAccumulatedMean = 0.0
29
+
30
+ #if @mode == MEAN_MODE::MEAN_TOTAL the fs, lMeanIntervalms, dOverlappingFactor does not take effect
31
+ Ini(@mode, 48000, 400, 0.75)
32
+ end
33
+
34
+ def Ini(mode, fs, lMeanIntervalms, dOverlappingFactor)
35
+
36
+ if (@bIni == true)
37
+ raise 'ERROR bad state'
38
+ end
39
+
40
+ if (fs <= 0) or (lMeanIntervalms <= 0) or (dOverlappingFactor <= 0.0) or (dOverlappingFactor >= 1.0)
41
+ raise 'ERROR bad arguments'
42
+ end
43
+
44
+ @mode = mode
45
+
46
+ if (@mode == MEAN_MODE::MEAN_OVERLAPPED)
47
+ @lSamplesInInterval = Integer(fs.to_f * (lMeanIntervalms.to_f / 1000.0))
48
+ @lSamplesOverlapped = Integer(@lSamplesInInterval.to_f * dOverlappingFactor.to_f)
49
+ @lGetMeanEveryXSamples = @lSamplesInInterval - @lSamplesOverlapped;
50
+
51
+ if (@lGetMeanEveryXSamples <= 0)
52
+ raise 'ERROR calculating lGetMeanEveryXSamples (bad arguments)'
53
+ end
54
+
55
+ @nSums = @lSamplesInInterval / @lGetMeanEveryXSamples;
56
+ @Sums = Array.new(@nSums) {|i| i = 0}
57
+
58
+ @lNumSamplesLoaded = 0;
59
+ @nSumIndex = 0;
60
+ else
61
+ @lNumAcumulatedSamplesMean = 0;
62
+ @dAccumulatedMean = 0.0;
63
+ end
64
+
65
+ @bIni = true;
66
+ end
67
+
68
+ def AddSample (dSample)
69
+ rc = false
70
+
71
+ if (@mode == MEAN_MODE::MEAN_OVERLAPPED)
72
+ ni = 0
73
+ while ni < @nSums do
74
+ @Sums[ni] = @Sums[ni].to_f + (dSample.to_f * dSample.to_f)
75
+
76
+ ni = ni + 1
77
+ end
78
+
79
+ @lNumSamplesLoaded = @lNumSamplesLoaded + 1
80
+
81
+ if ((@lNumSamplesLoaded % @lGetMeanEveryXSamples) == 0)
82
+ dMean = @Sums[@nSumIndex].to_f / @lSamplesInInterval.to_f
83
+
84
+ @queueMean.insert(-1, dMean) #Insert at loast position
85
+
86
+ rc = true
87
+ @Sums[@nSumIndex] = 0.0
88
+
89
+ @nSumIndex = @nSumIndex + 1
90
+ if (@nSumIndex >= @nSums)
91
+ @nSumIndex = 0
92
+ end
93
+ end
94
+
95
+ if (@lNumSamplesLoaded >= @lSamplesInInterval)
96
+ @lNumSamplesLoaded = 0
97
+ end
98
+ else
99
+ dAddVal = dSample.to_f * dSample.to_f
100
+
101
+ if (@lNumAcumulatedSamplesMean > 0)
102
+ dMul = @lNumAcumulatedSamplesMean.to_f / (@lNumAcumulatedSamplesMean + 1).to_f
103
+ @dAccumulatedMean = (@dAccumulatedMean.to_f * dMul.to_f) + dAddVal.to_f / (@lNumAcumulatedSamplesMean + 1).to_f
104
+ else
105
+ @dAccumulatedMean = dAddVal
106
+ end
107
+ @lNumAcumulatedSamplesMean = @lNumAcumulatedSamplesMean + 1
108
+
109
+ rc = true
110
+ end
111
+
112
+ return rc
113
+ end
114
+
115
+ def GetMeanVal
116
+ if (@mode == MEAN_MODE::MEAN_OVERLAPPED)
117
+ raise 'ERROR function not valid in overlapped mean mode'
118
+ end
119
+
120
+ return @dAccumulatedMean
121
+ end
122
+
123
+ end
@@ -0,0 +1,58 @@
1
+ #file: PREFilter.rb
2
+
3
+ require_relative '../IIRFilter/IIRFilter.rb'
4
+
5
+ # define PRE filter
6
+ class PREFilter < IIRFilter
7
+
8
+ # Ini obj
9
+ def initialize(fs)
10
+ super 2
11
+
12
+ SetFreqSampling(fs)
13
+ end
14
+
15
+ def SetFreqSampling (fs)
16
+ @fs = fs
17
+
18
+ #PRE FILTER - Order 2 = 3coefs
19
+ #Coefs Fs = 48KHz (ITU 1770 published coefs for fs = 48KHz)
20
+ mACoefs = [1.0, -1.69065929318241, 0.73248077421585]
21
+ mBCoefs = [1.53512485958697, -2.69169618940638, 1.19839281085285]
22
+
23
+ if (@fs != 48000)
24
+ #Parameters of second order HF shelvoing filter
25
+ nGdb = 4 #Gain in DB
26
+ dFc = 1650 #Center freq
27
+
28
+ mBCoefs , mACoefs = CalcCoefs fs, nGdb, dFc
29
+ end
30
+
31
+ SetCoefs mBCoefs,mACoefs
32
+ end
33
+
34
+ def CalcCoefs (fs, nGdb, dFc)
35
+
36
+ dK = Math.tan( (Math::PI * dFc.to_f) / fs.to_f )
37
+ dV0 = (10.0 ** (nGdb.to_f / 20.0))
38
+ dsqrt2 = (2 ** 0.5)
39
+ dDivider = (1.0 + dsqrt2.to_f * dK.to_f + (dK.to_f ** 2))
40
+
41
+ mACoefs = [0,0,0]
42
+ mACoefs[0] = 1.0;
43
+ mACoefs[1] = (2.0 * ((dK.to_f ** 2.0) - 1)) / dDivider.to_f;
44
+ mACoefs[2] = (1.0 - (dsqrt2.to_f * dK.to_f) + (dK.to_f ** 2.0)) / dDivider.to_f;
45
+
46
+ mBCoefs = [0,0,0]
47
+ mBCoefs[0] = (dV0.to_f + ((2.0 * dV0.to_f) ** 0.5) * dK.to_f + (dK.to_f ** 2.0)) / dDivider.to_f;
48
+ mBCoefs[1] = (2.0 * ((dK.to_f ** 2.0) - dV0.to_f)) / dDivider.to_f;
49
+ mBCoefs[2] = (dV0 - ((2.0 * dV0.to_f) ** 0.5) * dK.to_f + (dK.to_f ** 2.0)) / dDivider.to_f;
50
+
51
+ return mBCoefs, mACoefs
52
+ end
53
+
54
+ #Show info
55
+ def Info
56
+ return "PRE Filter--------\nFs = #{@fs} Hz\n#{super}\n------------------\n"
57
+ end
58
+ end
@@ -0,0 +1,56 @@
1
+ #file: RLBFilter.rb
2
+
3
+ require_relative '../IIRFilter/IIRFilter.rb'
4
+
5
+ # define RLB filter
6
+ class RLBFilter < IIRFilter
7
+
8
+ # Ini obj
9
+ def initialize(fs)
10
+ super 2
11
+
12
+ SetFreqSampling(fs)
13
+ end
14
+
15
+ def SetFreqSampling (fs)
16
+ @fs = fs
17
+
18
+ #RLB FILTER - Order 2 = 3coefs
19
+ #Coefs Fs = 48KHz (ITU 1770 published coefs for fs = 48KHz)
20
+ mACoefs = [1.0, -1.99004745483398, 0.99007225036621]
21
+ mBCoefs = [1.0, -2.0, 1.0]
22
+
23
+ if (@fs != 48000)
24
+ #Parameters of second order HPF
25
+ dFc = 60 #Center freq
26
+
27
+ mBCoefs , mACoefs = CalcCoefs fs,dFc
28
+ end
29
+
30
+ SetCoefs mBCoefs,mACoefs
31
+ end
32
+
33
+ def CalcCoefs (fs, dFc)
34
+ dK = Math.tan( (Math::PI * dFc.to_f) / fs.to_f )
35
+ dsqrt2 = 2 ** 0.5
36
+ dDivider = (1.0 + dsqrt2.to_f * dK.to_f + (dK ** 2))
37
+
38
+ #ACoefs
39
+ mACoefs = [0,0,0]
40
+ mACoefs[0] = 1.0;
41
+ mACoefs[1] = (2.0 * ((dK.to_f ** 2.0) - 1)) / dDivider.to_f;
42
+ mACoefs[2] = (1.0 - (dsqrt2.to_f * dK.to_f) + (dK.to_f ** 2.0))/dDivider.to_f;
43
+
44
+ mBCoefs = [0,0,0]
45
+ mBCoefs[0] = 1.0 / dDivider.to_f;
46
+ mBCoefs[1] = -2.0 / dDivider.to_f;
47
+ mBCoefs[2] = 1.0 / dDivider.to_f;
48
+
49
+ return mBCoefs, mACoefs
50
+ end
51
+
52
+ #Show info
53
+ def Info
54
+ return "RLB Filter--------\nFs = #{@fs} Hz\n#{super}\n------------------\n"
55
+ end
56
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: JOCLoudness
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jordi Cenzano
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bindata
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ - - '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 2.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.1'
30
+ - - '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 2.1.0
33
+ description: This ruby native gem is able to load a wav file and calculate its LKFS
34
+ following the ITU-R BS.1770
35
+ email: jordicenzano@gmail.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - lib/JOCLoudness.rb
41
+ - lib/Loudness/IIRFilter/IIRFilter.rb
42
+ - lib/Loudness/JOCLoud.rb
43
+ - lib/Loudness/Mean/MeanFast.rb
44
+ - lib/Loudness/PreFilter/PREFilter.rb
45
+ - lib/Loudness/RlbFilter/RLBFilter.rb
46
+ homepage: http://rubygems.org/gems/JOCLoudness
47
+ licenses:
48
+ - MIT
49
+ metadata: {}
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 2.2.2
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: It calculates the LKFS of a wav file
70
+ test_files: []