JOCLoudness 1.0.3 → 1.0.4
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 +4 -4
- data/README.md +51 -1
- data/lib/JOCLoudness.rb +80 -4
- data/lib/WavReader/wavfile.rb +66 -75
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb8af76bd2940127a19168cf9cfb55fffd518e3b
|
4
|
+
data.tar.gz: f9899fe2b6742691e424c191858501fcfa08ae5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2035f0f602259e61903eaf6c99f90832dfc1fcfd4c84ccd046fd09e97597428139b2501702cfd32e060f6097d38ad6d4d2424db28efbd2db944fcb61b736395
|
7
|
+
data.tar.gz: aa8f1600df07654bb25df636d7322ce660781bcbe3a02f7813df25614ae0534ecd4d042b2795291eb62ffe0e8a47717aa724b08ca21e102b9578a05f444dd483
|
data/README.md
CHANGED
@@ -1,4 +1,54 @@
|
|
1
1
|
JOCLoudnessRuby
|
2
2
|
===============
|
3
3
|
|
4
|
-
The JOCLoudnessRuby is a native
|
4
|
+
The JOCLoudnessRuby is a native ruby code able to load a wav file and calculate its Loudness in LKFS following the ITU-R BS.1770
|
5
|
+
|
6
|
+
Accepted formats (.WAV):
|
7
|
+
* Headers of 16, 18, 40 bytes
|
8
|
+
* Any sampling frequency
|
9
|
+
* Any number of channels
|
10
|
+
* PCM 8,16,24 bits per sample or FLOAT 32 bits per sample
|
11
|
+
|
12
|
+
Note 1: This module recalculates automatically the filter coefficients depending on input file sampling frequency
|
13
|
+
Note 2: ITU-R BS.1770 is a NOT gated measure
|
14
|
+
|
15
|
+
|
16
|
+
Usage examples:
|
17
|
+
|
18
|
+
- Simple:
|
19
|
+
require 'JOCLoudness'
|
20
|
+
|
21
|
+
wavfilename = "c:\\Test.wav"
|
22
|
+
|
23
|
+
begin
|
24
|
+
loud = JOCLoudness.new(wavfilename)
|
25
|
+
|
26
|
+
lkfs = loud.CalcLoudness()
|
27
|
+
|
28
|
+
loud.Close()
|
29
|
+
|
30
|
+
puts "Loudness of #{ARGV[0]} = #{lkfs.round(1)} LKFS"
|
31
|
+
|
32
|
+
rescue Exception => e
|
33
|
+
puts "Error: #{e.message}, Trace: #{e.backtrace.inspect}"
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
- Advanced:
|
38
|
+
require 'JOCLoudness'
|
39
|
+
|
40
|
+
wavfilename = ARGV[0]
|
41
|
+
logfilename = ARGV[1]
|
42
|
+
|
43
|
+
begin
|
44
|
+
loud = JOCLoudness.new(wavfilename, logfilename, Logger::DEBUG)
|
45
|
+
|
46
|
+
lkfs = loud.CalcLoudness()
|
47
|
+
|
48
|
+
loud.Close()
|
49
|
+
|
50
|
+
puts "Loudness of #{ARGV[0]} = #{lkfs.round(1)} LKFS"
|
51
|
+
|
52
|
+
rescue Exception => e
|
53
|
+
puts "Error: #{e.message}, Trace: #{e.backtrace.inspect}"
|
54
|
+
end
|
data/lib/JOCLoudness.rb
CHANGED
@@ -9,7 +9,83 @@ require 'logger'
|
|
9
9
|
require_relative './WavReader/wavfile'
|
10
10
|
require_relative './Loudness/jocLoud.rb'
|
11
11
|
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
#
|
12
|
+
#JOCLoudness class, used to compute the loudness of a wav file (Based on ITU-R BS.1770)
|
13
|
+
class JOCLoudness
|
14
|
+
|
15
|
+
#Initialize JOCLoudness
|
16
|
+
# @param filename [String] audio wav file to analize
|
17
|
+
# @param logfile [String] log file to save the logs (optional)
|
18
|
+
# @param loglevel log level, could be Logger::FATAL, Logger::ERROR, Logger::WARN, Logger::INFO, Logger::DEBUG (optional)
|
19
|
+
def initialize (filename, logfile = nil, loglevel = Logger::INFO)
|
20
|
+
#Instanciate wav reader
|
21
|
+
@wav = Wavfile.new(filename)
|
22
|
+
|
23
|
+
#Start logger
|
24
|
+
# Keep data for today and the past 3 days.
|
25
|
+
@log = nil
|
26
|
+
if (logfile != nil)
|
27
|
+
@log = Logger.new(logfile)
|
28
|
+
@log.level = loglevel
|
29
|
+
|
30
|
+
#Set logger (optional)
|
31
|
+
@wav.Setlogger(@log)
|
32
|
+
end
|
33
|
+
|
34
|
+
#Read wav header
|
35
|
+
@wav.ReadHeader()
|
36
|
+
|
37
|
+
#Get file info (fs, number of channels)
|
38
|
+
@fs, @nch = @wav.GetFileInfo()
|
39
|
+
end
|
40
|
+
|
41
|
+
#Calculates the loudness of the audio file
|
42
|
+
# @return [Float] the resulting loudness in LKFS
|
43
|
+
def CalcLoudness ()
|
44
|
+
#Instantiate & initialize loudness moudule
|
45
|
+
loudness = JOCLoud.new
|
46
|
+
|
47
|
+
#Set logger
|
48
|
+
if (@log != nil)
|
49
|
+
loudness.Setlogger(@log)
|
50
|
+
end
|
51
|
+
|
52
|
+
loudness.ini(@fs,@nch)
|
53
|
+
|
54
|
+
#Read wav file and send the samples to loudness module
|
55
|
+
n = 0
|
56
|
+
samples = 1
|
57
|
+
|
58
|
+
while samples != nil
|
59
|
+
samples = @wav.GetAudioSamples(n)
|
60
|
+
if (samples != nil)
|
61
|
+
if (n % @fs == 0)
|
62
|
+
puts "Processed #{n / @fs} secs"
|
63
|
+
end
|
64
|
+
|
65
|
+
loudness.Addsample(samples)
|
66
|
+
|
67
|
+
n = n + 1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
loudnessLKFS = loudness.GetLoudnessLKFS()
|
72
|
+
|
73
|
+
if (@log != nil)
|
74
|
+
strloudnessdata = "Loudness = #{loudnessLKFS.round(1)} LKFS"
|
75
|
+
@log.debug(strloudnessdata)
|
76
|
+
end
|
77
|
+
|
78
|
+
return loudnessLKFS
|
79
|
+
end
|
80
|
+
|
81
|
+
#Closes the opened file
|
82
|
+
def Close()
|
83
|
+
if (@log!=nil)
|
84
|
+
@log.debug("End logger")
|
85
|
+
end
|
86
|
+
|
87
|
+
if (@wav != nil)
|
88
|
+
@wav.Close()
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/WavReader/wavfile.rb
CHANGED
@@ -7,7 +7,7 @@ require_relative './wavsamples'
|
|
7
7
|
#* Any sampling frequency
|
8
8
|
#* Any number of channels
|
9
9
|
#* PCM 8,16,24 bits per sample
|
10
|
-
#* FLOAT
|
10
|
+
#* FLOAT 32 bits per sample
|
11
11
|
#*Headers of 16, 18, 40 bytes
|
12
12
|
class Wavfile
|
13
13
|
|
@@ -31,23 +31,18 @@ class Wavfile
|
|
31
31
|
# Reads the wav header file
|
32
32
|
def ReadHeader
|
33
33
|
Log(Logger::DEBUG,"Start reading wav header of #{@filename}")
|
34
|
+
|
35
|
+
#Read wav header
|
36
|
+
@mainheader = nil
|
37
|
+
@mainheader = Wavheader.new
|
38
|
+
|
39
|
+
@wavfile = File.open(@filename, 'r')
|
40
|
+
@mainheader.read(@wavfile)
|
34
41
|
|
35
|
-
|
36
|
-
#Read wav header
|
37
|
-
@mainheader = nil
|
38
|
-
@mainheader = Wavheader.new
|
39
|
-
|
40
|
-
@wavfile = File.open(@filename, 'r')
|
41
|
-
@mainheader.read(@wavfile)
|
42
|
-
|
43
|
-
@lHeaderOffsetbytes = @mainheader.cfirstdatabyte.abs_offset;
|
44
|
-
|
45
|
-
Log(Logger::DEBUG,"lHeaderOffsetbytes: #{@lHeaderOffsetbytes}")
|
46
|
-
|
47
|
-
rescue Exception => e
|
48
|
-
Log(Logger::ERROR,"Error reading wav header of #{e.message}, Trace: #{e.backtrace.inspect}")
|
49
|
-
end
|
42
|
+
@lHeaderOffsetbytes = @mainheader.cfirstdatabyte.abs_offset;
|
50
43
|
|
44
|
+
Log(Logger::DEBUG,"lHeaderOffsetbytes: #{@lHeaderOffsetbytes}")
|
45
|
+
|
51
46
|
Log(Logger::DEBUG,@mainheader)
|
52
47
|
|
53
48
|
end
|
@@ -66,70 +61,66 @@ class Wavfile
|
|
66
61
|
if (@wavfile == nil)
|
67
62
|
Log(Logger::WARN, "The file is not opened")
|
68
63
|
else
|
69
|
-
|
70
|
-
|
64
|
+
Log(Logger::DEBUG,"Start reading sample pos #{@sampleindex}")
|
65
|
+
|
66
|
+
#Seek to sample position channel 0
|
67
|
+
lsamplesizebytes = (@mainheader.sBitsPerSample / 8)
|
68
|
+
loffsetbytes = @lHeaderOffsetbytes + sampleindex * (@mainheader.nchannels * lsamplesizebytes)
|
69
|
+
|
70
|
+
Log(Logger::DEBUG,"loffsetbytes: #{loffsetbytes} of #{@wavfile.size}")
|
71
|
+
|
72
|
+
if (loffsetbytes >= @wavfile.size)
|
73
|
+
Log(Logger::DEBUG,"EOF reached!")
|
74
|
+
nil
|
75
|
+
else
|
76
|
+
@wavfile.seek(loffsetbytes)
|
77
|
+
|
78
|
+
#Create PCM samples
|
79
|
+
samples = nil
|
80
|
+
nmax = 1;
|
71
81
|
|
72
|
-
|
73
|
-
|
74
|
-
|
82
|
+
if (@mainheader.nformattag == Wavheader::PCM)
|
83
|
+
#Read PCM sample 8 bits/sample (-128 ... 0 ... +127)
|
84
|
+
if (@mainheader.sBitsPerSample == 8)
|
85
|
+
samples = Wavsample8b.new(:num_channels => @mainheader.nchannels)
|
86
|
+
nmax = 128
|
87
|
+
end
|
88
|
+
#Read PCM sample 16 bits/sample (-32768 ... 0 ... +32767)
|
89
|
+
if (@mainheader.sBitsPerSample == 16)
|
90
|
+
samples = Wavsample16b.new(:num_channels => @mainheader.nchannels)
|
91
|
+
nmax = 32768
|
92
|
+
end
|
93
|
+
#Read PCM sample 24 bits/sample (-8388608 ... 0 ... +8388607)
|
94
|
+
if (@mainheader.sBitsPerSample == 24)
|
95
|
+
samples = Wavsample24b.new(:num_channels => @mainheader.nchannels)
|
96
|
+
nmax = 8388608
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#Create FLOAT samples
|
101
|
+
if (@mainheader.nformattag == Wavheader::FLOAT)
|
102
|
+
samples = WavsampleFloat.new(:num_channels => @mainheader.nchannels)
|
103
|
+
end
|
75
104
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
Log(Logger::DEBUG,"EOF reached!")
|
80
|
-
nil
|
105
|
+
if (samples != nil)
|
106
|
+
#Read sample from file
|
107
|
+
samples.read(@wavfile)
|
81
108
|
else
|
82
|
-
|
109
|
+
raise 'Sample not compatible (Is not PCM neither FLOAT)'
|
110
|
+
end
|
83
111
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
if (@mainheader.sBitsPerSample == 8)
|
91
|
-
samples = Wavsample8b.new(:num_channels => @mainheader.nchannels)
|
92
|
-
nmax = 128
|
93
|
-
end
|
94
|
-
#Read PCM sample 16 bits/sample (-32768 ... 0 ... +32767)
|
95
|
-
if (@mainheader.sBitsPerSample == 16)
|
96
|
-
samples = Wavsample16b.new(:num_channels => @mainheader.nchannels)
|
97
|
-
nmax = 32768
|
98
|
-
end
|
99
|
-
#Read PCM sample 24 bits/sample (-8388608 ... 0 ... +8388607)
|
100
|
-
if (@mainheader.sBitsPerSample == 24)
|
101
|
-
samples = Wavsample24b.new(:num_channels => @mainheader.nchannels)
|
102
|
-
nmax = 8388608
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
#Create FLOAT samples
|
107
|
-
if (@mainheader.nformattag == Wavheader::FLOAT)
|
108
|
-
samples = WavsampleFloat.new(:num_channels => @mainheader.nchannels)
|
109
|
-
end
|
110
|
-
|
111
|
-
if (samples != nil)
|
112
|
-
#Read sample from file
|
113
|
-
samples.read(@wavfile)
|
114
|
-
else
|
115
|
-
raise 'Sample not compatible (Is not PCM neither FLOAT)'
|
116
|
-
end
|
117
|
-
|
118
|
-
Log(Logger::DEBUG,samples)
|
119
|
-
|
120
|
-
samplesRetArray = samples.samples
|
121
|
-
|
122
|
-
if (samplesRetArray != nil) and (@mainheader.nformattag == Wavheader::PCM) and (bNormalize == true)
|
123
|
-
samplesRetArray = samplesRetArray.collect {|x| x.to_f / nmax.to_f}
|
124
|
-
end
|
125
|
-
|
126
|
-
Log(Logger::DEBUG,"Samples normalized = #{samplesRetArray.to_s}")
|
127
|
-
|
128
|
-
samplesRetArray
|
129
|
-
|
112
|
+
Log(Logger::DEBUG,samples)
|
113
|
+
|
114
|
+
samplesRetArray = samples.samples
|
115
|
+
|
116
|
+
if (samplesRetArray != nil) and (@mainheader.nformattag == Wavheader::PCM) and (bNormalize == true)
|
117
|
+
samplesRetArray = samplesRetArray.collect {|x| x.to_f / nmax.to_f}
|
130
118
|
end
|
131
|
-
|
132
|
-
|
119
|
+
|
120
|
+
Log(Logger::DEBUG,"Samples normalized = #{samplesRetArray.to_s}")
|
121
|
+
|
122
|
+
samplesRetArray
|
123
|
+
|
133
124
|
end
|
134
125
|
end
|
135
126
|
end
|