iso_1996 1.1.0 → 2.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 +4 -4
- data/README.md +169 -3
- data/lib/iso_1996/part_1_2016.rb +200 -0
- data/lib/iso_1996/{environmental_noise.rb → part_2_2017.rb} +9 -10
- data/lib/iso_1996/withdrawn/part_1_2003.rb +107 -0
- data/lib/iso_1996/withdrawn/part_2_2007.rb +87 -0
- data/lib/iso_1996/withdrawn/part_3_1987.rb +116 -0
- data/lib/iso_1996.rb +14 -6
- metadata +6 -4
- data/lib/iso_1996/basic.rb +0 -105
- data/lib/iso_1996/noise_limits.rb +0 -114
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a342d4cc66ef866a054ca283ab255f1e4042c740ab6bde20ce2e6f8a38519fe
|
4
|
+
data.tar.gz: eb2c91913088dacbeda0a4c255ba1e91bd9a2d993b20cfbafb059be7d92b0919
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a00dbcda666bd801d0652fc56b178c96778c5cc3b57a7a42a166daf25c6eeb44d8ab726066569c582cc6cca917f1226c68fc87feb929a39fdf70515a9d376c9
|
7
|
+
data.tar.gz: 3524db873c527e91422a977beb119342ee6f2360f10edd75e14d7b02234c33e1f4dd99e1e55910011b500b2b03a8dfeaef41ee74b6fbf141d817e57c97669bd5
|
data/README.md
CHANGED
@@ -1,14 +1,18 @@
|
|
1
1
|
|
2
|
-
# ISO 1996 - Acoustics - Description, measurement and assessment of environmental noise Ruby
|
2
|
+
# ISO 1996 - Acoustics - Description, measurement and assessment of environmental noise Ruby library.
|
3
3
|
|
4
4
|
[](https://badge.fury.io/rb/iso_1996)
|
5
5
|
[](https://rubydoc.info/gems/iso_1996)
|
6
6
|
|
7
|
-
Ruby implementation of ISO 1996 - Acoustics - Description, measurement and assessment of environmental noise.
|
7
|
+
Ruby implementation of ISO 1996 - Acoustics - Description, measurement and assessment of environmental noise.
|
8
|
+
Provides complete calculations for environmental noise assessment according to ISO 1996-1:2016 and ISO 1996-2:2017 standards.
|
8
9
|
|
9
10
|
## Features
|
10
11
|
|
11
|
-
- Complete implementation of
|
12
|
+
- Complete implementation of ISO 1996-1:2016 and ISO 1996-2:2017
|
13
|
+
- Calculation of all key acoustic metrics
|
14
|
+
- Environmental corrections and uncertainty analysis
|
15
|
+
- Day-evening-night level (L<sub>den</sub>) assessment
|
12
16
|
- Fully documented with RDoc comments
|
13
17
|
- Comprehensive test suite
|
14
18
|
- MIT licensed
|
@@ -31,6 +35,168 @@ gem install iso_1996
|
|
31
35
|
Full documentation available at:
|
32
36
|
https://rubydoc.info/gems/iso_1996
|
33
37
|
|
38
|
+
## Example Usage
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
require 'iso_1996'
|
42
|
+
|
43
|
+
# ----------------------------------------------------------
|
44
|
+
# STEP 1: Data Collection - 6 measurements from sound level meter
|
45
|
+
# ----------------------------------------------------------
|
46
|
+
measurements = [
|
47
|
+
# Continuous measurements (A-weighted, 1-min each)
|
48
|
+
{ time: Time.now - 300, pressure: 0.08, weighting: 'A', duration: 60 },
|
49
|
+
{ time: Time.now - 240, pressure: 0.12, weighting: 'A', duration: 60 },
|
50
|
+
{ time: Time.now - 180, pressure: 0.15, weighting: 'A', duration: 60 },
|
51
|
+
{ time: Time.now - 120, pressure: 0.10, weighting: 'A', duration: 60 },
|
52
|
+
{ time: Time.now - 60, pressure: 0.18, weighting: 'A', duration: 60 },
|
53
|
+
|
54
|
+
# Impulse measurement (C-weighted, 100ms)
|
55
|
+
{ time: Time.now, pressure: 0.25, weighting: 'C', duration: 0.1 }
|
56
|
+
]
|
57
|
+
|
58
|
+
# Background measurement (5-min average)
|
59
|
+
background = { pressure: 0.05, weighting: 'A', duration: 300 }
|
60
|
+
|
61
|
+
# ----------------------------------------------------------
|
62
|
+
# STEP 2: Calculate Basic Sound Levels (ISO 1996-1:2016)
|
63
|
+
# ----------------------------------------------------------
|
64
|
+
|
65
|
+
# Calculate SPL for each measurement
|
66
|
+
measurements.each do |m|
|
67
|
+
m[:spl] = ISO_1996::Part_1_2016.sound_pressure_level(m[:pressure])
|
68
|
+
end
|
69
|
+
|
70
|
+
# Calculate equivalent continuous sound level (L_Aeq)
|
71
|
+
a_weighted = measurements.select { |m| m[:weighting] == 'A' }
|
72
|
+
l_aeq = ISO_1996::Part_1_2016.equivalent_continuous_sound_level(
|
73
|
+
a_weighted.map { |m| m[:spl] },
|
74
|
+
a_weighted.sum { |m| m[:duration] }
|
75
|
+
)
|
76
|
+
|
77
|
+
# Calculate peak sound pressure level (L_Cpeak)
|
78
|
+
impulse = measurements.find { |m| m[:weighting] == 'C' }
|
79
|
+
l_cpeak = ISO_1996::Part_1_2016.peak_sound_pressure_level(impulse[:pressure])
|
80
|
+
|
81
|
+
# ----------------------------------------------------------
|
82
|
+
# STEP 3: Environmental Corrections (ISO 1996-2:2017)
|
83
|
+
# ----------------------------------------------------------
|
84
|
+
|
85
|
+
# Calculate background noise correction
|
86
|
+
background_spl = ISO_1996::Part_1_2016.sound_pressure_level(background[:pressure])
|
87
|
+
background_correction = ISO_1996::Part_2_2017.background_noise_correction(l_aeq, background_spl)
|
88
|
+
|
89
|
+
# Apply atmospheric absorption correction
|
90
|
+
attenuation_coefficient = 0.005 # dB/m (from ISO 9613-1 based on temp/humidity)
|
91
|
+
distance = 50.0 # meters
|
92
|
+
atmospheric_correction = ISO_1996::Part_2_2017.atmospheric_absorption_correction(
|
93
|
+
attenuation_coefficient, distance
|
94
|
+
)
|
95
|
+
|
96
|
+
# Calculate total correction
|
97
|
+
corrected_l_aeq = l_aeq - background_correction - atmospheric_correction
|
98
|
+
|
99
|
+
# ----------------------------------------------------------
|
100
|
+
# STEP 4: Day-Evening-Night Level Calculation (L_den) (ISO 1996-1:2016)
|
101
|
+
# ----------------------------------------------------------
|
102
|
+
|
103
|
+
# Normally collected separately for each period
|
104
|
+
day_measurements = [65.2, 67.1, 66.5] # 3-hour measurements
|
105
|
+
evening_measurements = [63.8, 62.4] # 2-hour measurements
|
106
|
+
night_measurements = [58.7, 57.9, 59.3] # 3-hour measurements
|
107
|
+
|
108
|
+
# Calculate L_Aeq for each period
|
109
|
+
l_day = ISO_1996::Part_1_2016.equivalent_continuous_sound_level(day_measurements, 3 * 3600)
|
110
|
+
l_evening = ISO_1996::Part_1_2016.equivalent_continuous_sound_level(evening_measurements, 2 * 3600)
|
111
|
+
l_night = ISO_1996::Part_1_2016.equivalent_continuous_sound_level(night_measurements, 3 * 3600)
|
112
|
+
|
113
|
+
# Calculate L_den with standard penalties
|
114
|
+
l_den = ISO_1996::Part_1_2016.day_evening_night_level(l_day, l_evening, l_night)
|
115
|
+
|
116
|
+
# ----------------------------------------------------------
|
117
|
+
# STEP 5: Assessment Procedures (ISO 1996-1:2016)
|
118
|
+
# ----------------------------------------------------------
|
119
|
+
|
120
|
+
# Determine adjustment factors (requires professional judgment)
|
121
|
+
is_tonal_audible = true # Based on spectral analysis
|
122
|
+
is_tonal_prominent = false
|
123
|
+
k_t = ISO_1996::Part_1_2016.tonal_adjustment_factor(
|
124
|
+
is_audible: is_tonal_audible,
|
125
|
+
is_prominent: is_tonal_prominent
|
126
|
+
)
|
127
|
+
|
128
|
+
is_impulse_audible = true
|
129
|
+
is_impulse_distinct = true
|
130
|
+
k_i = ISO_1996::Part_1_2016.impulsive_adjustment_factor(
|
131
|
+
is_audible: is_impulse_audible,
|
132
|
+
is_distinct: is_impulse_distinct
|
133
|
+
)
|
134
|
+
|
135
|
+
# Calculate assessment level
|
136
|
+
assessment_level = ISO_1996::Part_1_2016.assessment_level(corrected_l_aeq, k_t, k_i)
|
137
|
+
|
138
|
+
# ----------------------------------------------------------
|
139
|
+
# STEP 6: Compliance Evaluation
|
140
|
+
# ----------------------------------------------------------
|
141
|
+
noise_limit = 65.0 # dB(A) - local regulation
|
142
|
+
uncertainty_components = [0.5, 1.0, 0.7] # Instrument, position, environmental
|
143
|
+
measurement_uncertainty = ISO_1996::Part_2_2017.measurement_uncertainty(uncertainty_components)
|
144
|
+
|
145
|
+
is_compliant = !ISO_1996::Part_1_2016.compliance_evaluation(
|
146
|
+
assessment_level,
|
147
|
+
noise_limit,
|
148
|
+
measurement_uncertainty
|
149
|
+
)
|
150
|
+
|
151
|
+
# ----------------------------------------------------------
|
152
|
+
# STEP 7: Reporting
|
153
|
+
# ----------------------------------------------------------
|
154
|
+
puts "Environmental Noise Assessment Report"
|
155
|
+
puts "------------------------------------"
|
156
|
+
puts "Measurement Period: #{measurements.first[:time]} to #{measurements.last[:time]}"
|
157
|
+
puts "Equivalent Continuous Level (L_Aeq): #{l_aeq.round(1)} dB(A)"
|
158
|
+
puts "Background Correction: #{background_correction.round(1)} dB"
|
159
|
+
puts "Atmospheric Correction: #{atmospheric_correction.round(1)} dB"
|
160
|
+
puts "Corrected L_Aeq: #{corrected_l_aeq.round(1)} dB(A)"
|
161
|
+
puts "Tonal Adjustment (K_T): #{k_t} dB"
|
162
|
+
puts "Impulsive Adjustment (K_I): #{k_i} dB"
|
163
|
+
puts "Assessment Level (L_r): #{assessment_level.round(1)} dB(A)"
|
164
|
+
puts "Day Level (L_day): #{l_day.round(1)} dB(A)"
|
165
|
+
puts "Evening Level (L_evening): #{l_evening.round(1)} dB(A)"
|
166
|
+
puts "Night Level (L_night): #{l_night.round(1)} dB(A)"
|
167
|
+
puts "Day-Evening-Night Level (L_den): #{l_den.round(1)} dB(A)"
|
168
|
+
puts "Peak Sound Level (L_Cpeak): #{l_cpeak.round(1)} dB(C)"
|
169
|
+
puts "Noise Limit: #{noise_limit} dB(A)"
|
170
|
+
puts "Measurement Uncertainty: ±#{measurement_uncertainty.round(1)} dB"
|
171
|
+
puts "Compliance Status: #{is_compliant ? 'PASS' : 'FAIL'}"
|
172
|
+
puts "------------------------------------"
|
173
|
+
```
|
174
|
+
|
175
|
+
## Handling Non-formula Aspects of the Standard
|
176
|
+
The ISO 1996 standards contain important guidance that isn't expressed in formulas but requires professional judgment:
|
177
|
+
|
178
|
+
1. Tonal and Impulsive Character Assessment:
|
179
|
+
- Use the tonal_adjustment_factor and impulsive_adjustment_factor methods with parameters based on:
|
180
|
+
* Spectral analysis (FFT) to identify prominent tones
|
181
|
+
* Auditory assessment by trained professionals
|
182
|
+
* Historical data and community complaints
|
183
|
+
2. Meteorological Conditions:
|
184
|
+
- Measure and apply corrections for:
|
185
|
+
* Wind speed and direction (affects sound propagation)
|
186
|
+
* Temperature gradients (affects refraction)
|
187
|
+
* Humidity (affects high-frequency absorption)
|
188
|
+
* Use ISO 9613-1 for detailed atmospheric absorption coefficients
|
189
|
+
3. Measurement Uncertainty:
|
190
|
+
- Calculate using the measurement_uncertainty method with components:
|
191
|
+
* Instrument calibration uncertainty
|
192
|
+
* Measurement position uncertainty
|
193
|
+
* Environmental conditions uncertainty
|
194
|
+
4. Time Period Handling:
|
195
|
+
- For day-evening-night calculations:
|
196
|
+
* Collect separate measurements for each period
|
197
|
+
* Use local definitions for period boundaries (e.g., day: 7:00-19:00)
|
198
|
+
* Apply appropriate penalties (standard is 5 dB evening, 10 dB night)
|
199
|
+
|
34
200
|
## Contributing
|
35
201
|
Bug reports and pull requests are welcome on GitHub at:
|
36
202
|
https://github.com/ciembor/iso_1996
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ISO_1996
|
4
|
+
|
5
|
+
##
|
6
|
+
# == ISO 1996-1:2016 Acoustics - Description, measurement and assessment of environmental noise -
|
7
|
+
# Part 1: Basic quantities and assessment procedures
|
8
|
+
#
|
9
|
+
# Module implementing calculations defined in ISO 1996-1:2016
|
10
|
+
#
|
11
|
+
# Author:: Maciej Ciemborowicz
|
12
|
+
# Date:: July 13, 2025
|
13
|
+
#
|
14
|
+
module Part_1_2016
|
15
|
+
##
|
16
|
+
# Constants defined in ISO 1996-1:2016 standard
|
17
|
+
module Constants
|
18
|
+
##
|
19
|
+
# Reference sound pressure (p₀) as defined in Section 3.1.1
|
20
|
+
# Value: 20 μPa (20e-6 Pa)
|
21
|
+
REFERENCE_SOUND_PRESSURE = 20e-6 # Pa
|
22
|
+
|
23
|
+
##
|
24
|
+
# Reference time (t₀) for sound exposure level as defined in Section 3.1.8
|
25
|
+
# Value: 1 second
|
26
|
+
REFERENCE_TIME = 1.0 # s
|
27
|
+
|
28
|
+
##
|
29
|
+
# Standard durations for day, evening, night periods as defined in Annex C.2
|
30
|
+
DAY_DURATION = 12.0 # hours
|
31
|
+
EVENING_DURATION = 4.0 # hours
|
32
|
+
NIGHT_DURATION = 8.0 # hours
|
33
|
+
|
34
|
+
##
|
35
|
+
# Penalties for evening and night periods as defined in Annex C.2
|
36
|
+
EVENING_PENALTY = 5.0 # dB
|
37
|
+
NIGHT_PENALTY = 10.0 # dB
|
38
|
+
end
|
39
|
+
include Constants
|
40
|
+
|
41
|
+
##
|
42
|
+
# Calculate sound pressure level (L_p) as defined in Section 3.1.2
|
43
|
+
#
|
44
|
+
# L_p = 10 * log10(p² / p₀²) dB
|
45
|
+
#
|
46
|
+
# @param p [Float] Root-mean-square sound pressure (Pa)
|
47
|
+
# @return [Float] Sound pressure level in dB
|
48
|
+
#
|
49
|
+
# Example:
|
50
|
+
# Part_1_2016.sound_pressure_level(0.1) # => 74.0 dB
|
51
|
+
#
|
52
|
+
def self.sound_pressure_level(p)
|
53
|
+
10 * Math.log10((p ** 2) / (Constants::REFERENCE_SOUND_PRESSURE ** 2))
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Calculate sound exposure level (L_AE) as defined in Section 3.1.8
|
58
|
+
#
|
59
|
+
# L_AE = 10 * log10( (1/t₀) * ∫(p_A²(t)/p₀²) dt ) dB
|
60
|
+
#
|
61
|
+
# @param p_a [Float] A-weighted sound pressure (Pa)
|
62
|
+
# @return [Float] Sound exposure level in dB
|
63
|
+
#
|
64
|
+
# Note: This method assumes a single value for simplicity.
|
65
|
+
# For time-varying signals, integration over time is required.
|
66
|
+
#
|
67
|
+
def self.sound_exposure_level(p_a)
|
68
|
+
10 * Math.log10((1.0 / Constants::REFERENCE_TIME) * (p_a ** 2) / (Constants::REFERENCE_SOUND_PRESSURE ** 2))
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Calculate equivalent continuous sound level (L_Aeq,T) as defined in Section 3.1.7
|
73
|
+
#
|
74
|
+
# L_Aeq,T = 10 * log10( (1/T) * Σ(10^(0.1*L_i)) ) dB
|
75
|
+
#
|
76
|
+
# @param levels [Array<Float>] Array of sound pressure levels (dB)
|
77
|
+
# @param measurement_time [Float] Total measurement time (seconds)
|
78
|
+
# @return [Float] Equivalent continuous sound level in dB
|
79
|
+
#
|
80
|
+
# Example:
|
81
|
+
# levels = [65.0, 67.0, 63.0]
|
82
|
+
# Part_1_2016.equivalent_continuous_sound_level(levels, 3.0) # => ~65.1 dB
|
83
|
+
#
|
84
|
+
def self.equivalent_continuous_sound_level(levels, measurement_time)
|
85
|
+
raise ArgumentError, "Measurement time must be positive" if measurement_time <= 0
|
86
|
+
|
87
|
+
return -Float::INFINITY if levels.empty?
|
88
|
+
|
89
|
+
energy_sum = levels.sum { |l| 10 ** (l / 10.0) }
|
90
|
+
10 * Math.log10(energy_sum / measurement_time)
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Calculate peak sound pressure level (L_pC,peak) as defined in Section 3.1.10
|
95
|
+
#
|
96
|
+
# L_pC,peak = 20 * log10(p_Cmax / p₀) dB
|
97
|
+
#
|
98
|
+
# @param p_c_max [Float] Maximum C-weighted sound pressure (Pa)
|
99
|
+
# @return [Float] Peak sound pressure level in dB
|
100
|
+
#
|
101
|
+
def self.peak_sound_pressure_level(p_c_max)
|
102
|
+
20 * Math.log10(p_c_max / Constants::REFERENCE_SOUND_PRESSURE)
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Calculate day-evening-night level (L_den) as defined in Annex C.2
|
107
|
+
#
|
108
|
+
# L_den = 10 * log10( (1/24) * [t_d·10^(L_day/10) + t_e·10^((L_evening + P_e)/10) + t_n·10^((L_night + P_n)/10)] ) dB
|
109
|
+
#
|
110
|
+
# @param l_day [Float] Day-time equivalent sound level (dB)
|
111
|
+
# @param l_evening [Float] Evening-time equivalent sound level (dB)
|
112
|
+
# @param l_night [Float] Night-time equivalent sound level (dB)
|
113
|
+
# @param day_duration [Float] Duration of day period (hours)
|
114
|
+
# @param evening_duration [Float] Duration of evening period (hours)
|
115
|
+
# @param night_duration [Float] Duration of night period (hours)
|
116
|
+
# @param evening_penalty [Float] Penalty for evening period (dB)
|
117
|
+
# @param night_penalty [Float] Penalty for night period (dB)
|
118
|
+
# @return [Float] Day-evening-night level in dB
|
119
|
+
#
|
120
|
+
# Example:
|
121
|
+
# Part_1_2016.day_evening_night_level(65.0, 62.0, 58.0) # => ~67.1 dB
|
122
|
+
#
|
123
|
+
def self.day_evening_night_level(l_day, l_evening, l_night,
|
124
|
+
day_duration: Constants::DAY_DURATION,
|
125
|
+
evening_duration: Constants::EVENING_DURATION,
|
126
|
+
night_duration: Constants::NIGHT_DURATION,
|
127
|
+
evening_penalty: Constants::EVENING_PENALTY,
|
128
|
+
night_penalty: Constants::NIGHT_PENALTY)
|
129
|
+
total_hours = day_duration + evening_duration + night_duration
|
130
|
+
|
131
|
+
term_day = day_duration * 10 ** (l_day / 10.0)
|
132
|
+
term_evening = evening_duration * 10 ** ((l_evening + evening_penalty) / 10.0)
|
133
|
+
term_night = night_duration * 10 ** ((l_night + night_penalty) / 10.0)
|
134
|
+
|
135
|
+
10 * Math.log10((term_day + term_evening + term_night) / total_hours)
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Determine tonal adjustment factor (K_T) as defined in Annex D.3
|
140
|
+
#
|
141
|
+
# @param is_audible [Boolean] Whether the tone is clearly audible
|
142
|
+
# @param is_prominent [Boolean] Whether the tone is prominent
|
143
|
+
# @return [Float] Tonal adjustment factor in dB (0.0, 3.0, 6.0)
|
144
|
+
#
|
145
|
+
# According to Annex D.3:
|
146
|
+
# Prominent tone: 6 dB
|
147
|
+
# Clearly audible but not prominent: 3 dB
|
148
|
+
# Not clearly audible: 0 dB
|
149
|
+
#
|
150
|
+
def self.tonal_adjustment_factor(is_audible: false, is_prominent: false)
|
151
|
+
return 0.0 unless is_audible
|
152
|
+
return 6.0 if is_prominent
|
153
|
+
3.0
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Determine impulsive adjustment factor (K_I) as defined in Annex D.4
|
158
|
+
#
|
159
|
+
# @param is_audible [Boolean] Whether the impulsive sound is clearly audible
|
160
|
+
# @param is_distinct [Boolean] Whether the impulsive sound is distinct
|
161
|
+
# @return [Float] Impulsive adjustment factor in dB (0.0, 3.0, 6.0)
|
162
|
+
#
|
163
|
+
# According to Annex D.4:
|
164
|
+
# Distinct impulsive sound: 6 dB
|
165
|
+
# Clearly audible but not distinct: 3 dB
|
166
|
+
# Not clearly audible: 0 dB
|
167
|
+
#
|
168
|
+
def self.impulsive_adjustment_factor(is_audible: false, is_distinct: false)
|
169
|
+
return 0.0 unless is_audible
|
170
|
+
return 6.0 if is_distinct
|
171
|
+
3.0
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Calculate assessment level (L_r) as defined in Section 3.6
|
176
|
+
#
|
177
|
+
# L_r = L_AeqT + K_T + K_I dB
|
178
|
+
#
|
179
|
+
# @param l_aeq_t [Float] Equivalent continuous A-weighted sound pressure level (dB)
|
180
|
+
# @param k_t [Float] Tonal adjustment factor (dB)
|
181
|
+
# @param k_i [Float] Impulsive adjustment factor (dB)
|
182
|
+
# @return [Float] Assessment level in dB
|
183
|
+
#
|
184
|
+
def self.assessment_level(l_aeq_t, k_t, k_i)
|
185
|
+
l_aeq_t + k_t + k_i
|
186
|
+
end
|
187
|
+
|
188
|
+
##
|
189
|
+
# Evaluate compliance with noise limits as defined in Section 9.2
|
190
|
+
#
|
191
|
+
# @param assessment_level [Float] Calculated assessment level (dB)
|
192
|
+
# @param noise_limit [Float] Applicable noise limit (dB)
|
193
|
+
# @param measurement_uncertainty [Float] Measurement uncertainty (dB)
|
194
|
+
# @return [Boolean] True if limit is exceeded (assessment_level > noise_limit + measurement_uncertainty)
|
195
|
+
#
|
196
|
+
def self.compliance_evaluation(assessment_level, noise_limit, measurement_uncertainty)
|
197
|
+
assessment_level > noise_limit + measurement_uncertainty
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -3,18 +3,17 @@
|
|
3
3
|
module ISO_1996
|
4
4
|
|
5
5
|
##
|
6
|
-
# == ISO 1996-2:
|
6
|
+
# == ISO 1996-2:2017 Acoustics - Description, measurement and assessment of environmental noise -
|
7
|
+
# Part 2: Determination of sound pressure levels
|
7
8
|
#
|
8
|
-
# Module implementing calculations defined in ISO 1996-2:
|
9
|
-
# "Acoustics - Description, measurement and assessment of environmental noise -
|
10
|
-
# Part 2: Determination of sound pressure levels"
|
9
|
+
# Module implementing calculations defined in ISO 1996-2:2017
|
11
10
|
#
|
12
11
|
# Author:: Maciej Ciemborowicz
|
13
|
-
# Date:: July
|
12
|
+
# Date:: July 13, 2025
|
14
13
|
#
|
15
|
-
module
|
14
|
+
module Part_2_2017
|
16
15
|
##
|
17
|
-
# Constants defined in ISO 1996-2:
|
16
|
+
# Constants defined in ISO 1996-2:2017 standard
|
18
17
|
module Constants
|
19
18
|
##
|
20
19
|
# Minimum level difference for background correction (ΔL_min) as defined in Section 6.3
|
@@ -40,7 +39,7 @@ module ISO_1996
|
|
40
39
|
# @raise [ArgumentError] if ΔL ≤ 3 dB (measurement uncertain)
|
41
40
|
#
|
42
41
|
# Example:
|
43
|
-
#
|
42
|
+
# Part_2_2017.background_noise_correction(65, 60) # => 1.7 dB
|
44
43
|
#
|
45
44
|
def self.background_noise_correction(l_total, l_background)
|
46
45
|
delta_l = l_total - l_background
|
@@ -76,10 +75,10 @@ module ISO_1996
|
|
76
75
|
# @return [Float] Combined measurement uncertainty in dB
|
77
76
|
#
|
78
77
|
# Example:
|
79
|
-
#
|
78
|
+
# Part_2_2017.measurement_uncertainty([0.5, 1.0, 0.7]) # => 1.28 dB
|
80
79
|
#
|
81
80
|
def self.measurement_uncertainty(uncertainty_components)
|
82
81
|
Math.sqrt(uncertainty_components.sum { |c| c ** 2 })
|
83
82
|
end
|
84
83
|
end
|
85
|
-
end
|
84
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ISO_1996
|
4
|
+
module Withdrawn
|
5
|
+
|
6
|
+
##
|
7
|
+
# == ISO 1996-1:2003 Basic Quantities and Assessment Procedures
|
8
|
+
#
|
9
|
+
# Module implementing calculations defined in ISO 1996-1:2003:
|
10
|
+
# "Acoustics - Description, measurement and assessment of environmental noise -
|
11
|
+
# Part 1: Basic quantities and assessment procedures"
|
12
|
+
#
|
13
|
+
# Author:: Maciej Ciemborowicz
|
14
|
+
# Date:: July 11, 2025
|
15
|
+
#
|
16
|
+
module Part_1_2003
|
17
|
+
##
|
18
|
+
# Constants defined in ISO 1996-1:2003 standard
|
19
|
+
module Constants
|
20
|
+
##
|
21
|
+
# Reference sound pressure (p₀) as defined in Section 3.1
|
22
|
+
# Value: 20 μPa (20e-6 Pa)
|
23
|
+
REFERENCE_SOUND_PRESSURE = 20e-6 # Pa
|
24
|
+
|
25
|
+
##
|
26
|
+
# Reference time (t₀) for sound exposure level as defined in Section 3.9
|
27
|
+
# Value: 1 second
|
28
|
+
REFERENCE_TIME = 1.0 # s
|
29
|
+
end
|
30
|
+
include Constants
|
31
|
+
|
32
|
+
##
|
33
|
+
# Calculate sound pressure level (L_p) as defined in Section 3.2
|
34
|
+
#
|
35
|
+
# L_p = 10 * log10(p² / p₀²) dB
|
36
|
+
#
|
37
|
+
# @param p [Float] Root-mean-square sound pressure (Pa)
|
38
|
+
# @return [Float] Sound pressure level in dB
|
39
|
+
#
|
40
|
+
# Example:
|
41
|
+
# Basic.sound_pressure_level(0.1) # => 74.0 dB
|
42
|
+
#
|
43
|
+
def self.sound_pressure_level(p)
|
44
|
+
10 * Math.log10((p ** 2) / (Constants::REFERENCE_SOUND_PRESSURE ** 2))
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Calculate A-weighted sound pressure level (L_A) as defined in Section 3.2
|
49
|
+
#
|
50
|
+
# L_A = 10 * log10( (1/T) * ∫(p_A²(t)/p₀²) dt ) dB
|
51
|
+
#
|
52
|
+
# @param p_a [Float] A-weighted sound pressure (Pa)
|
53
|
+
# @param measurement_time [Float] Measurement time interval (seconds)
|
54
|
+
# @return [Float] A-weighted sound pressure level in dB
|
55
|
+
#
|
56
|
+
def self.a_weighted_sound_pressure_level(p_a, measurement_time: 1.0)
|
57
|
+
10 * Math.log10((1.0 / measurement_time) * (p_a ** 2) / (Constants::REFERENCE_SOUND_PRESSURE ** 2))
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Calculate sound exposure level (L_AE) as defined in Section 3.9
|
62
|
+
#
|
63
|
+
# L_AE = 10 * log10( (1/t₀) * ∫(p_A²(t)/p₀²) dt ) dB
|
64
|
+
#
|
65
|
+
# @param p_a [Float] A-weighted sound pressure (Pa)
|
66
|
+
# @return [Float] Sound exposure level in dB
|
67
|
+
#
|
68
|
+
def self.sound_exposure_level(p_a)
|
69
|
+
10 * Math.log10((1.0 / Constants::REFERENCE_TIME) * (p_a ** 2) / (Constants::REFERENCE_SOUND_PRESSURE ** 2))
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Calculate equivalent continuous sound level (L_Aeq,T) as defined in Section 3.7
|
74
|
+
#
|
75
|
+
# L_Aeq,T = 10 * log10( (1/T) * Σ(10^(0.1*L_i)) ) dB
|
76
|
+
#
|
77
|
+
# @param levels [Array<Float>] Array of sound pressure levels (dB)
|
78
|
+
# @param measurement_time [Float] Total measurement time (seconds)
|
79
|
+
# @return [Float] Equivalent continuous sound level in dB
|
80
|
+
#
|
81
|
+
# Example:
|
82
|
+
# levels = [65.0, 67.0, 63.0]
|
83
|
+
# Basic.equivalent_continuous_sound_level(levels, 3.0) # => ~65.1 dB
|
84
|
+
#
|
85
|
+
def self.equivalent_continuous_sound_level(levels, measurement_time)
|
86
|
+
raise ArgumentError, "Measurement time must be positive" if measurement_time <= 0
|
87
|
+
|
88
|
+
return -Float::INFINITY if levels.empty?
|
89
|
+
|
90
|
+
energy_sum = levels.sum { |l| 10 ** (l / 10.0) }
|
91
|
+
10 * Math.log10(energy_sum / measurement_time)
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Calculate C-weighted peak sound pressure level (L_Cpeak) as defined in Section 3.10
|
96
|
+
#
|
97
|
+
# L_Cpeak = 20 * log10(p_Cmax / p₀) dB
|
98
|
+
#
|
99
|
+
# @param p_c_max [Float] Maximum C-weighted sound pressure (Pa)
|
100
|
+
# @return [Float] C-weighted peak sound pressure level in dB
|
101
|
+
#
|
102
|
+
def self.peak_sound_pressure_level(p_c_max)
|
103
|
+
20 * Math.log10(p_c_max / Constants::REFERENCE_SOUND_PRESSURE)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ISO_1996
|
4
|
+
module Withdrawn
|
5
|
+
|
6
|
+
##
|
7
|
+
# == ISO 1996-2:2007 Determination of Sound Pressure Levels
|
8
|
+
#
|
9
|
+
# Module implementing calculations defined in ISO 1996-2:2007:
|
10
|
+
# "Acoustics - Description, measurement and assessment of environmental noise -
|
11
|
+
# Part 2: Determination of sound pressure levels"
|
12
|
+
#
|
13
|
+
# Author:: Maciej Ciemborowicz
|
14
|
+
# Date:: July 11, 2025
|
15
|
+
#
|
16
|
+
module Part_2_2007
|
17
|
+
##
|
18
|
+
# Constants defined in ISO 1996-2:2007 standard
|
19
|
+
module Constants
|
20
|
+
##
|
21
|
+
# Minimum level difference for background correction (ΔL_min) as defined in Section 6.3
|
22
|
+
# Value: 3 dB
|
23
|
+
MIN_BACKGROUND_LEVEL_DIFFERENCE = 3.0 # dB
|
24
|
+
|
25
|
+
##
|
26
|
+
# Threshold for background correction (ΔL_threshold) as defined in Section 6.3
|
27
|
+
# Value: 10 dB
|
28
|
+
BACKGROUND_CORRECTION_THRESHOLD = 10.0 # dB
|
29
|
+
end
|
30
|
+
include Constants
|
31
|
+
|
32
|
+
##
|
33
|
+
# Calculate background noise correction (K₁) as defined in Section 6.3 and Annex D
|
34
|
+
#
|
35
|
+
# K₁ = -10 * log10(1 - 10^(-0.1 * ΔL)) dB
|
36
|
+
# where ΔL = L_total - L_background
|
37
|
+
#
|
38
|
+
# @param l_total [Float] Total sound pressure level (dB)
|
39
|
+
# @param l_background [Float] Background sound pressure level (dB)
|
40
|
+
# @return [Float] Background noise correction in dB
|
41
|
+
# @raise [ArgumentError] if ΔL ≤ 3 dB (measurement uncertain)
|
42
|
+
#
|
43
|
+
# Example:
|
44
|
+
# EnvironmentalNoise.background_noise_correction(65, 60) # => 1.7 dB
|
45
|
+
#
|
46
|
+
def self.background_noise_correction(l_total, l_background)
|
47
|
+
delta_l = l_total - l_background
|
48
|
+
|
49
|
+
if delta_l <= Constants::MIN_BACKGROUND_LEVEL_DIFFERENCE
|
50
|
+
raise ArgumentError, "Measurement uncertain: ΔL ≤ #{Constants::MIN_BACKGROUND_LEVEL_DIFFERENCE} dB"
|
51
|
+
elsif delta_l >= Constants::BACKGROUND_CORRECTION_THRESHOLD
|
52
|
+
0.0
|
53
|
+
else
|
54
|
+
-10 * Math.log10(1 - 10 ** (-0.1 * delta_l))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Calculate atmospheric absorption correction (A_atm) as defined in Section 7.3 and Annex A
|
60
|
+
#
|
61
|
+
# A_atm = α * d dB
|
62
|
+
#
|
63
|
+
# @param attenuation_coefficient [Float] Atmospheric attenuation coefficient (dB/m)
|
64
|
+
# @param propagation_distance [Float] Sound propagation distance (m)
|
65
|
+
# @return [Float] Atmospheric absorption correction in dB
|
66
|
+
#
|
67
|
+
def self.atmospheric_absorption_correction(attenuation_coefficient, propagation_distance)
|
68
|
+
attenuation_coefficient * propagation_distance
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Calculate combined measurement uncertainty as defined in Section 9
|
73
|
+
#
|
74
|
+
# u_total = √(Σ(u_i²)) dB
|
75
|
+
#
|
76
|
+
# @param uncertainty_components [Array<Float>] Array of uncertainty components (dB)
|
77
|
+
# @return [Float] Combined measurement uncertainty in dB
|
78
|
+
#
|
79
|
+
# Example:
|
80
|
+
# EnvironmentalNoise.measurement_uncertainty([0.5, 1.0, 0.7]) # => 1.28 dB
|
81
|
+
#
|
82
|
+
def self.measurement_uncertainty(uncertainty_components)
|
83
|
+
Math.sqrt(uncertainty_components.sum { |c| c ** 2 })
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ISO_1996
|
4
|
+
module Withdrawn
|
5
|
+
|
6
|
+
##
|
7
|
+
# == ISO 1996-3:1987 Application to Noise Limits
|
8
|
+
#
|
9
|
+
# Module implementing calculations defined in ISO 1996-3:1987:
|
10
|
+
# "Acoustics - Description, measurement and assessment of environmental noise -
|
11
|
+
# Part 3: Application to noise limits"
|
12
|
+
#
|
13
|
+
# Author:: Maciej Ciemborowicz
|
14
|
+
# Date:: July 11, 2025
|
15
|
+
#
|
16
|
+
module Part_3_1987
|
17
|
+
##
|
18
|
+
# Constants defined in ISO 1996-3:1987 standard
|
19
|
+
module Constants
|
20
|
+
##
|
21
|
+
# Impulse correction threshold (L_Cpeak,min) as defined in Section 7.2
|
22
|
+
# Value: 130 dB
|
23
|
+
IMPULSE_CORRECTION_THRESHOLD = 130.0 # dB
|
24
|
+
|
25
|
+
##
|
26
|
+
# Standard 24-hour period as defined in Annex A
|
27
|
+
# Value: 24 hours
|
28
|
+
STANDARD_24H_PERIOD = 24.0 # hours
|
29
|
+
end
|
30
|
+
include Constants
|
31
|
+
|
32
|
+
##
|
33
|
+
# Determine tonal adjustment factor (K_T) as defined in Section 6 and Table 1
|
34
|
+
#
|
35
|
+
# @param delta_l [Float] Difference between tone level and background level (dB)
|
36
|
+
# @return [Float] Tonal adjustment factor in dB
|
37
|
+
#
|
38
|
+
# According to Table 1:
|
39
|
+
# ΔL ≥ 15 dB → 6 dB
|
40
|
+
# 10 ≤ ΔL ≤ 14 dB → 4-6 dB (default 5 dB)
|
41
|
+
# 5 ≤ ΔL ≤ 9 dB → 0-3 dB (default 2 dB)
|
42
|
+
# ΔL < 5 dB → 0 dB
|
43
|
+
#
|
44
|
+
def self.tonal_adjustment_factor(delta_l)
|
45
|
+
case delta_l
|
46
|
+
when 15..Float::INFINITY then 6.0
|
47
|
+
when 10..14 then 5.0
|
48
|
+
when 5..9 then 2.0
|
49
|
+
else 0.0
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Determine impulsive adjustment factor (K_I) as defined in Section 7.2
|
55
|
+
#
|
56
|
+
# @param l_cpeak [Float] C-weighted peak sound pressure level (dB)
|
57
|
+
# @param is_highly_annoying [Boolean] Whether noise is subjectively assessed as highly annoying
|
58
|
+
# @return [Float] Impulsive adjustment factor in dB
|
59
|
+
#
|
60
|
+
# According to version:
|
61
|
+
# K_I = 6 dB if L_Cpeak ≥ 130 dB OR noise is highly annoying
|
62
|
+
# K_I = 0 dB otherwise
|
63
|
+
#
|
64
|
+
def self.impulsive_adjustment_factor(l_cpeak, is_highly_annoying: false)
|
65
|
+
(l_cpeak >= Constants::IMPULSE_CORRECTION_THRESHOLD || is_highly_annoying) ? 6.0 : 0.0
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Calculate assessment level (L_r) as defined in Section 8
|
70
|
+
#
|
71
|
+
# L_r = L_AeqT + K_T + K_I dB
|
72
|
+
#
|
73
|
+
# @param l_aeq_t [Float] Equivalent continuous A-weighted sound pressure level (dB)
|
74
|
+
# @param k_t [Float] Tonal adjustment factor (dB)
|
75
|
+
# @param k_i [Float] Impulsive adjustment factor (dB)
|
76
|
+
# @return [Float] Assessment level in dB
|
77
|
+
#
|
78
|
+
def self.assessment_level(l_aeq_t, k_t, k_i)
|
79
|
+
l_aeq_t + k_t + k_i
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Evaluate compliance with noise limits as defined in Section 9
|
84
|
+
#
|
85
|
+
# @param l_r [Float] Assessment level (dB)
|
86
|
+
# @param noise_limit [Float] Noise limit value (dB)
|
87
|
+
# @param measurement_uncertainty [Float] Measurement uncertainty (dB)
|
88
|
+
# @return [Boolean] True if limit is exceeded (L_r > L_lim + uncertainty)
|
89
|
+
#
|
90
|
+
def self.compliance_evaluation(l_r, noise_limit, measurement_uncertainty)
|
91
|
+
l_r > noise_limit + measurement_uncertainty
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Convert sound levels between time periods as defined in Annex A
|
96
|
+
#
|
97
|
+
# L_total = 10 * log10( [Σ(t_i * 10^(0.1*L_i)] / T_total ) dB
|
98
|
+
#
|
99
|
+
# @param period_levels [Array<Hash>] Array of hashes with :level (dB) and :duration (hours)
|
100
|
+
# @param total_period [Float] Total time period for normalization (hours)
|
101
|
+
# @return [Float] Equivalent sound level for total period in dB
|
102
|
+
#
|
103
|
+
# Example:
|
104
|
+
# periods = [
|
105
|
+
# {level: 65.0, duration: 16}, # Day
|
106
|
+
# {level: 55.0, duration: 8} # Night
|
107
|
+
# ]
|
108
|
+
# NoiseLimits.time_period_conversion(periods) # => ~62.1 dB
|
109
|
+
#
|
110
|
+
def self.time_period_conversion(period_levels, total_period: Constants::STANDARD_24H_PERIOD)
|
111
|
+
energy_sum = period_levels.sum { |period| period[:duration] * (10 ** (period[:level] / 10.0)) }
|
112
|
+
10 * Math.log10(energy_sum / total_period)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/iso_1996.rb
CHANGED
@@ -4,14 +4,17 @@
|
|
4
4
|
# = ISO 1996 - Acoustics - Description, measurement and assessment of environmental noise
|
5
5
|
#
|
6
6
|
# Ruby implementation of ISO 1996 - Acoustics - Description, measurement and assessment of environmental noise.
|
7
|
-
# Includes
|
7
|
+
# Includes current parts:
|
8
|
+
# - ISO 1996-1:2016: Basic quantities and assessment procedures
|
9
|
+
# - ISO 1996-2:2017: Determination of sound pressure levels
|
10
|
+
# As well as some legacy norms:
|
8
11
|
# - ISO 1996-1:2003: Basic quantities and assessment procedures
|
9
12
|
# - ISO 1996-2:2007: Determination of sound pressure levels
|
10
13
|
# - ISO 1996-3:1987: Application to noise limits
|
11
14
|
#
|
12
15
|
# Author:: Maciej Ciemborowicz
|
13
16
|
# Date:: July 11, 2025
|
14
|
-
# Version::
|
17
|
+
# Version:: 2.0.0
|
15
18
|
# License:: MIT
|
16
19
|
#
|
17
20
|
# == Usage
|
@@ -26,16 +29,21 @@
|
|
26
29
|
# # Noise limits assessment
|
27
30
|
# assessment = ISO_1996::NoiseLimits.assessment_level(65, 2, 3)
|
28
31
|
#
|
32
|
+
# @see https://www.iso.org/standard/59765.html ISO 1996-1:2016
|
33
|
+
# @see https://www.iso.org/standard/59766.html ISO 1996-2:2017
|
34
|
+
#
|
29
35
|
# @see https://www.iso.org/standard/28633.html ISO 1996-1:2003
|
30
36
|
# @see https://www.iso.org/standard/23776.html ISO 1996-2:2007
|
31
37
|
# @see https://www.iso.org/standard/6750.html ISO 1996-3:1987
|
32
38
|
|
33
|
-
require_relative 'iso_1996/
|
34
|
-
require_relative 'iso_1996/
|
35
|
-
require_relative 'iso_1996/
|
39
|
+
require_relative 'iso_1996/withdrawn/part_1_2003'
|
40
|
+
require_relative 'iso_1996/withdrawn/part_2_2007'
|
41
|
+
require_relative 'iso_1996/withdrawn/part_3_1987'
|
42
|
+
require_relative 'iso_1996/part_1_2016'
|
43
|
+
require_relative 'iso_1996/part_2_2017'
|
36
44
|
|
37
45
|
module ISO_1996
|
38
46
|
##
|
39
47
|
# Current version of the gem
|
40
|
-
VERSION = "
|
48
|
+
VERSION = "2.0.0"
|
41
49
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iso_1996
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maciej Ciemborowicz
|
@@ -66,9 +66,11 @@ extra_rdoc_files: []
|
|
66
66
|
files:
|
67
67
|
- README.md
|
68
68
|
- lib/iso_1996.rb
|
69
|
-
- lib/iso_1996/
|
70
|
-
- lib/iso_1996/
|
71
|
-
- lib/iso_1996/
|
69
|
+
- lib/iso_1996/part_1_2016.rb
|
70
|
+
- lib/iso_1996/part_2_2017.rb
|
71
|
+
- lib/iso_1996/withdrawn/part_1_2003.rb
|
72
|
+
- lib/iso_1996/withdrawn/part_2_2007.rb
|
73
|
+
- lib/iso_1996/withdrawn/part_3_1987.rb
|
72
74
|
homepage: https://github.com/ciembor/iso_1996
|
73
75
|
licenses:
|
74
76
|
- MIT
|
data/lib/iso_1996/basic.rb
DELETED
@@ -1,105 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ISO_1996
|
4
|
-
|
5
|
-
##
|
6
|
-
# == ISO 1996-1:2003 Basic Quantities and Assessment Procedures
|
7
|
-
#
|
8
|
-
# Module implementing calculations defined in ISO 1996-1:2003:
|
9
|
-
# "Acoustics - Description, measurement and assessment of environmental noise -
|
10
|
-
# Part 1: Basic quantities and assessment procedures"
|
11
|
-
#
|
12
|
-
# Author:: Maciej Ciemborowicz
|
13
|
-
# Date:: July 11, 2025
|
14
|
-
#
|
15
|
-
module Basic
|
16
|
-
##
|
17
|
-
# Constants defined in ISO 1996-1:2003 standard
|
18
|
-
module Constants
|
19
|
-
##
|
20
|
-
# Reference sound pressure (p₀) as defined in Section 3.1
|
21
|
-
# Value: 20 μPa (20e-6 Pa)
|
22
|
-
REFERENCE_SOUND_PRESSURE = 20e-6 # Pa
|
23
|
-
|
24
|
-
##
|
25
|
-
# Reference time (t₀) for sound exposure level as defined in Section 3.9
|
26
|
-
# Value: 1 second
|
27
|
-
REFERENCE_TIME = 1.0 # s
|
28
|
-
end
|
29
|
-
include Constants
|
30
|
-
|
31
|
-
##
|
32
|
-
# Calculate sound pressure level (L_p) as defined in Section 3.2
|
33
|
-
#
|
34
|
-
# L_p = 10 * log10(p² / p₀²) dB
|
35
|
-
#
|
36
|
-
# @param p [Float] Root-mean-square sound pressure (Pa)
|
37
|
-
# @return [Float] Sound pressure level in dB
|
38
|
-
#
|
39
|
-
# Example:
|
40
|
-
# Basic.sound_pressure_level(0.1) # => 74.0 dB
|
41
|
-
#
|
42
|
-
def self.sound_pressure_level(p)
|
43
|
-
10 * Math.log10((p ** 2) / (Constants::REFERENCE_SOUND_PRESSURE ** 2))
|
44
|
-
end
|
45
|
-
|
46
|
-
##
|
47
|
-
# Calculate A-weighted sound pressure level (L_A) as defined in Section 3.2
|
48
|
-
#
|
49
|
-
# L_A = 10 * log10( (1/T) * ∫(p_A²(t)/p₀²) dt ) dB
|
50
|
-
#
|
51
|
-
# @param p_a [Float] A-weighted sound pressure (Pa)
|
52
|
-
# @param measurement_time [Float] Measurement time interval (seconds)
|
53
|
-
# @return [Float] A-weighted sound pressure level in dB
|
54
|
-
#
|
55
|
-
def self.a_weighted_sound_pressure_level(p_a, measurement_time: 1.0)
|
56
|
-
10 * Math.log10((1.0 / measurement_time) * (p_a ** 2) / (Constants::REFERENCE_SOUND_PRESSURE ** 2))
|
57
|
-
end
|
58
|
-
|
59
|
-
##
|
60
|
-
# Calculate sound exposure level (L_AE) as defined in Section 3.9
|
61
|
-
#
|
62
|
-
# L_AE = 10 * log10( (1/t₀) * ∫(p_A²(t)/p₀²) dt ) dB
|
63
|
-
#
|
64
|
-
# @param p_a [Float] A-weighted sound pressure (Pa)
|
65
|
-
# @return [Float] Sound exposure level in dB
|
66
|
-
#
|
67
|
-
def self.sound_exposure_level(p_a)
|
68
|
-
10 * Math.log10((1.0 / Constants::REFERENCE_TIME) * (p_a ** 2) / (Constants::REFERENCE_SOUND_PRESSURE ** 2))
|
69
|
-
end
|
70
|
-
|
71
|
-
##
|
72
|
-
# Calculate equivalent continuous sound level (L_Aeq,T) as defined in Section 3.7
|
73
|
-
#
|
74
|
-
# L_Aeq,T = 10 * log10( (1/T) * Σ(10^(0.1*L_i)) ) dB
|
75
|
-
#
|
76
|
-
# @param levels [Array<Float>] Array of sound pressure levels (dB)
|
77
|
-
# @param measurement_time [Float] Total measurement time (seconds)
|
78
|
-
# @return [Float] Equivalent continuous sound level in dB
|
79
|
-
#
|
80
|
-
# Example:
|
81
|
-
# levels = [65.0, 67.0, 63.0]
|
82
|
-
# Basic.equivalent_continuous_sound_level(levels, 3.0) # => ~65.1 dB
|
83
|
-
#
|
84
|
-
def self.equivalent_continuous_sound_level(levels, measurement_time)
|
85
|
-
raise ArgumentError, "Measurement time must be positive" if measurement_time <= 0
|
86
|
-
|
87
|
-
return -Float::INFINITY if levels.empty?
|
88
|
-
|
89
|
-
energy_sum = levels.sum { |l| 10 ** (l / 10.0) }
|
90
|
-
10 * Math.log10(energy_sum / measurement_time)
|
91
|
-
end
|
92
|
-
|
93
|
-
##
|
94
|
-
# Calculate C-weighted peak sound pressure level (L_Cpeak) as defined in Section 3.10
|
95
|
-
#
|
96
|
-
# L_Cpeak = 20 * log10(p_Cmax / p₀) dB
|
97
|
-
#
|
98
|
-
# @param p_c_max [Float] Maximum C-weighted sound pressure (Pa)
|
99
|
-
# @return [Float] C-weighted peak sound pressure level in dB
|
100
|
-
#
|
101
|
-
def self.peak_sound_pressure_level(p_c_max)
|
102
|
-
20 * Math.log10(p_c_max / Constants::REFERENCE_SOUND_PRESSURE)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
@@ -1,114 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ISO_1996
|
4
|
-
|
5
|
-
##
|
6
|
-
# == ISO 1996-3:1987 Application to Noise Limits
|
7
|
-
#
|
8
|
-
# Module implementing calculations defined in ISO 1996-3:1987:
|
9
|
-
# "Acoustics - Description, measurement and assessment of environmental noise -
|
10
|
-
# Part 3: Application to noise limits"
|
11
|
-
#
|
12
|
-
# Author:: Maciej Ciemborowicz
|
13
|
-
# Date:: July 11, 2025
|
14
|
-
#
|
15
|
-
module NoiseLimits
|
16
|
-
##
|
17
|
-
# Constants defined in ISO 1996-3:1987 standard
|
18
|
-
module Constants
|
19
|
-
##
|
20
|
-
# Impulse correction threshold (L_Cpeak,min) as defined in Section 7.2
|
21
|
-
# Value: 130 dB
|
22
|
-
IMPULSE_CORRECTION_THRESHOLD = 130.0 # dB
|
23
|
-
|
24
|
-
##
|
25
|
-
# Standard 24-hour period as defined in Annex A
|
26
|
-
# Value: 24 hours
|
27
|
-
STANDARD_24H_PERIOD = 24.0 # hours
|
28
|
-
end
|
29
|
-
include Constants
|
30
|
-
|
31
|
-
##
|
32
|
-
# Determine tonal adjustment factor (K_T) as defined in Section 6 and Table 1
|
33
|
-
#
|
34
|
-
# @param delta_l [Float] Difference between tone level and background level (dB)
|
35
|
-
# @return [Float] Tonal adjustment factor in dB
|
36
|
-
#
|
37
|
-
# According to Table 1:
|
38
|
-
# ΔL ≥ 15 dB → 6 dB
|
39
|
-
# 10 ≤ ΔL ≤ 14 dB → 4-6 dB (default 5 dB)
|
40
|
-
# 5 ≤ ΔL ≤ 9 dB → 0-3 dB (default 2 dB)
|
41
|
-
# ΔL < 5 dB → 0 dB
|
42
|
-
#
|
43
|
-
def self.tonal_adjustment_factor(delta_l)
|
44
|
-
case delta_l
|
45
|
-
when 15..Float::INFINITY then 6.0
|
46
|
-
when 10..14 then 5.0
|
47
|
-
when 5..9 then 2.0
|
48
|
-
else 0.0
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
##
|
53
|
-
# Determine impulsive adjustment factor (K_I) as defined in Section 7.2
|
54
|
-
#
|
55
|
-
# @param l_cpeak [Float] C-weighted peak sound pressure level (dB)
|
56
|
-
# @param is_highly_annoying [Boolean] Whether noise is subjectively assessed as highly annoying
|
57
|
-
# @return [Float] Impulsive adjustment factor in dB
|
58
|
-
#
|
59
|
-
# According to version:
|
60
|
-
# K_I = 6 dB if L_Cpeak ≥ 130 dB OR noise is highly annoying
|
61
|
-
# K_I = 0 dB otherwise
|
62
|
-
#
|
63
|
-
def self.impulsive_adjustment_factor(l_cpeak, is_highly_annoying: false)
|
64
|
-
(l_cpeak >= Constants::IMPULSE_CORRECTION_THRESHOLD || is_highly_annoying) ? 6.0 : 0.0
|
65
|
-
end
|
66
|
-
|
67
|
-
##
|
68
|
-
# Calculate assessment level (L_r) as defined in Section 8
|
69
|
-
#
|
70
|
-
# L_r = L_AeqT + K_T + K_I dB
|
71
|
-
#
|
72
|
-
# @param l_aeq_t [Float] Equivalent continuous A-weighted sound pressure level (dB)
|
73
|
-
# @param k_t [Float] Tonal adjustment factor (dB)
|
74
|
-
# @param k_i [Float] Impulsive adjustment factor (dB)
|
75
|
-
# @return [Float] Assessment level in dB
|
76
|
-
#
|
77
|
-
def self.assessment_level(l_aeq_t, k_t, k_i)
|
78
|
-
l_aeq_t + k_t + k_i
|
79
|
-
end
|
80
|
-
|
81
|
-
##
|
82
|
-
# Evaluate compliance with noise limits as defined in Section 9
|
83
|
-
#
|
84
|
-
# @param l_r [Float] Assessment level (dB)
|
85
|
-
# @param noise_limit [Float] Noise limit value (dB)
|
86
|
-
# @param measurement_uncertainty [Float] Measurement uncertainty (dB)
|
87
|
-
# @return [Boolean] True if limit is exceeded (L_r > L_lim + uncertainty)
|
88
|
-
#
|
89
|
-
def self.compliance_evaluation(l_r, noise_limit, measurement_uncertainty)
|
90
|
-
l_r > noise_limit + measurement_uncertainty
|
91
|
-
end
|
92
|
-
|
93
|
-
##
|
94
|
-
# Convert sound levels between time periods as defined in Annex A
|
95
|
-
#
|
96
|
-
# L_total = 10 * log10( [Σ(t_i * 10^(0.1*L_i)] / T_total ) dB
|
97
|
-
#
|
98
|
-
# @param period_levels [Array<Hash>] Array of hashes with :level (dB) and :duration (hours)
|
99
|
-
# @param total_period [Float] Total time period for normalization (hours)
|
100
|
-
# @return [Float] Equivalent sound level for total period in dB
|
101
|
-
#
|
102
|
-
# Example:
|
103
|
-
# periods = [
|
104
|
-
# {level: 65.0, duration: 16}, # Day
|
105
|
-
# {level: 55.0, duration: 8} # Night
|
106
|
-
# ]
|
107
|
-
# NoiseLimits.time_period_conversion(periods) # => ~62.1 dB
|
108
|
-
#
|
109
|
-
def self.time_period_conversion(period_levels, total_period: Constants::STANDARD_24H_PERIOD)
|
110
|
-
energy_sum = period_levels.sum { |period| period[:duration] * (10 ** (period[:level] / 10.0)) }
|
111
|
-
10 * Math.log10(energy_sum / total_period)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|