Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/acoustics/room_acoustics.tex
51 views
unlisted
1
% Room Acoustics Analysis with PythonTeX
2
\documentclass[11pt,a4paper]{article}
3
\usepackage[utf8]{inputenc}
4
\usepackage[T1]{fontenc}
5
\usepackage{amsmath,amssymb}
6
\usepackage{graphicx}
7
\usepackage{booktabs}
8
\usepackage{siunitx}
9
\usepackage{geometry}
10
\geometry{margin=1in}
11
\usepackage{pythontex}
12
\usepackage{hyperref}
13
\usepackage{float}
14
15
\title{Room Acoustics Analysis\\Reverberation and Sound Field Modeling}
16
\author{Acoustics Engineering Laboratory}
17
\date{\today}
18
19
\begin{document}
20
\maketitle
21
22
\begin{abstract}
23
This technical report presents computational analysis of room acoustics including reverberation time calculations using Sabine and Eyring equations, sound absorption modeling, and acoustic parameter optimization.
24
\end{abstract}
25
26
\section{Introduction}
27
28
Room acoustics determines sound perception quality in enclosed spaces. The reverberation time $T_{60}$ is the primary metric.
29
30
\begin{pycode}
31
import numpy as np
32
import matplotlib.pyplot as plt
33
plt.rcParams['text.usetex'] = True
34
plt.rcParams['font.family'] = 'serif'
35
plt.rcParams['font.size'] = 10
36
37
# Room parameters
38
room_length, room_width, room_height = 15.0, 10.0, 4.0
39
room_volume = room_length * room_width * room_height
40
floor_area = room_length * room_width
41
ceiling_area = floor_area
42
wall_area = 2 * (room_length + room_width) * room_height
43
total_surface = floor_area + ceiling_area + wall_area
44
c = 343 # m/s
45
46
frequencies = np.array([125, 250, 500, 1000, 2000, 4000])
47
materials = {
48
'Concrete': np.array([0.01, 0.01, 0.02, 0.02, 0.02, 0.03]),
49
'Carpet': np.array([0.08, 0.24, 0.57, 0.69, 0.71, 0.73]),
50
'Acoustic Tiles': np.array([0.29, 0.44, 0.60, 0.77, 0.86, 0.84]),
51
'Glass': np.array([0.35, 0.25, 0.18, 0.12, 0.07, 0.04]),
52
}
53
54
fig, ax = plt.subplots(figsize=(8, 5))
55
for material, alpha in materials.items():
56
ax.semilogx(frequencies, alpha, 'o-', label=material, linewidth=1.5)
57
ax.set_xlabel('Frequency (Hz)')
58
ax.set_ylabel('Absorption Coefficient $\\alpha$')
59
ax.set_title('Sound Absorption Coefficients')
60
ax.legend(loc='upper right', fontsize=8)
61
ax.grid(True, alpha=0.3)
62
plt.tight_layout()
63
plt.savefig('absorption_coefficients.pdf', dpi=150, bbox_inches='tight')
64
plt.close()
65
\end{pycode}
66
67
\begin{figure}[H]
68
\centering
69
\includegraphics[width=0.85\textwidth]{absorption_coefficients.pdf}
70
\caption{Frequency-dependent absorption coefficients for common materials.}
71
\end{figure}
72
73
\section{Sabine Reverberation Time}
74
75
The Sabine equation: $T_{60} = \frac{0.161 V}{A}$
76
77
\begin{pycode}
78
alpha_walls = materials['Concrete']
79
alpha_floor = materials['Carpet']
80
alpha_ceiling = materials['Acoustic Tiles']
81
82
total_absorption = wall_area * alpha_walls + floor_area * alpha_floor + ceiling_area * alpha_ceiling
83
RT60_sabine = 0.161 * room_volume / total_absorption
84
85
fig, ax = plt.subplots(figsize=(8, 5))
86
ax.semilogx(frequencies, RT60_sabine, 'b-o', linewidth=2, label='Sabine $T_{60}$')
87
ax.axhline(y=0.5, color='g', linestyle='--', label='Optimal (Speech)')
88
ax.axhline(y=1.5, color='r', linestyle='--', label='Optimal (Music)')
89
ax.set_xlabel('Frequency (Hz)')
90
ax.set_ylabel('Reverberation Time $T_{60}$ (s)')
91
ax.set_title('Sabine Reverberation Time vs Frequency')
92
ax.legend()
93
ax.grid(True, alpha=0.3)
94
plt.tight_layout()
95
plt.savefig('rt60_sabine.pdf', dpi=150, bbox_inches='tight')
96
plt.close()
97
\end{pycode}
98
99
\begin{figure}[H]
100
\centering
101
\includegraphics[width=0.85\textwidth]{rt60_sabine.pdf}
102
\caption{Sabine reverberation time across frequency bands.}
103
\end{figure}
104
105
\section{Eyring Reverberation Time}
106
107
\begin{pycode}
108
alpha_avg = total_absorption / total_surface
109
RT60_eyring = 0.161 * room_volume / (-total_surface * np.log(1 - alpha_avg))
110
111
fig, ax = plt.subplots(figsize=(8, 5))
112
ax.semilogx(frequencies, RT60_sabine, 'b-o', linewidth=2, label='Sabine')
113
ax.semilogx(frequencies, RT60_eyring, 'r-s', linewidth=2, label='Eyring')
114
ax.set_xlabel('Frequency (Hz)')
115
ax.set_ylabel('$T_{60}$ (s)')
116
ax.set_title('Comparison of Sabine and Eyring')
117
ax.legend()
118
ax.grid(True, alpha=0.3)
119
plt.tight_layout()
120
plt.savefig('rt60_comparison.pdf', dpi=150, bbox_inches='tight')
121
plt.close()
122
\end{pycode}
123
124
\begin{figure}[H]
125
\centering
126
\includegraphics[width=0.85\textwidth]{rt60_comparison.pdf}
127
\caption{Comparison of Sabine and Eyring predictions.}
128
\end{figure}
129
130
\section{Sound Pressure Level Distribution}
131
132
\begin{pycode}
133
idx_1k = 3
134
A_1k = total_absorption[idx_1k]
135
alpha_avg_1k = alpha_avg[idx_1k]
136
R_room = A_1k / (1 - alpha_avg_1k)
137
L_W, Q = 90, 2
138
r = np.linspace(0.5, 15, 100)
139
140
direct_field = Q / (4 * np.pi * r**2)
141
reverb_field = 4 / R_room
142
L_p = L_W + 10 * np.log10(direct_field + reverb_field)
143
r_c = np.sqrt(Q * R_room / (16 * np.pi))
144
145
fig, ax = plt.subplots(figsize=(8, 5))
146
ax.plot(r, L_p, 'b-', linewidth=2, label='Total SPL')
147
ax.axvline(x=r_c, color='k', linestyle='--', label=f'$r_c$ = {r_c:.2f} m')
148
ax.set_xlabel('Distance from Source (m)')
149
ax.set_ylabel('Sound Pressure Level (dB)')
150
ax.set_title('SPL Distribution in Room')
151
ax.legend()
152
ax.grid(True, alpha=0.3)
153
plt.tight_layout()
154
plt.savefig('spl_distribution.pdf', dpi=150, bbox_inches='tight')
155
plt.close()
156
\end{pycode}
157
158
\begin{figure}[H]
159
\centering
160
\includegraphics[width=0.85\textwidth]{spl_distribution.pdf}
161
\caption{Sound pressure level as function of distance.}
162
\end{figure}
163
164
\section{Room Modes}
165
166
\begin{pycode}
167
modes = []
168
for nx in range(0, 5):
169
for ny in range(0, 5):
170
for nz in range(0, 5):
171
if nx == 0 and ny == 0 and nz == 0:
172
continue
173
f = (c/2) * np.sqrt((nx/room_length)**2 + (ny/room_width)**2 + (nz/room_height)**2)
174
if f <= 200:
175
modes.append(f)
176
modes.sort()
177
178
fig, ax = plt.subplots(figsize=(10, 3))
179
ax.eventplot([modes], lineoffsets=0.5, linelengths=0.8, colors='blue')
180
ax.set_xlabel('Frequency (Hz)')
181
ax.set_title('Room Mode Distribution (0-200 Hz)')
182
ax.set_xlim([0, 200])
183
ax.set_ylim([0, 1])
184
ax.set_yticks([])
185
plt.tight_layout()
186
plt.savefig('room_modes.pdf', dpi=150, bbox_inches='tight')
187
plt.close()
188
189
f_schroeder = 2000 * np.sqrt(RT60_sabine[idx_1k] / room_volume)
190
\end{pycode}
191
192
\begin{figure}[H]
193
\centering
194
\includegraphics[width=0.9\textwidth]{room_modes.pdf}
195
\caption{Room mode distribution in low-frequency range.}
196
\end{figure}
197
198
\section{Clarity Indices}
199
200
\begin{pycode}
201
t_ir = np.linspace(0, 2, 1000)
202
decay_rate = 6.91 / RT60_sabine[idx_1k]
203
impulse_response = np.exp(-decay_rate * t_ir)
204
205
idx_50 = np.argmin(np.abs(t_ir - 0.050))
206
energy_early = np.trapz(impulse_response[:idx_50]**2, t_ir[:idx_50])
207
energy_late = np.trapz(impulse_response[idx_50:]**2, t_ir[idx_50:])
208
C50 = 10 * np.log10(energy_early / energy_late)
209
210
fig, ax = plt.subplots(figsize=(8, 5))
211
ax.plot(t_ir * 1000, 20*np.log10(impulse_response + 1e-10), 'b-', linewidth=1.5)
212
ax.axvline(x=50, color='g', linestyle='--', label='50 ms')
213
ax.set_xlabel('Time (ms)')
214
ax.set_ylabel('Level (dB)')
215
ax.set_title('Room Impulse Response Energy Decay')
216
ax.legend()
217
ax.grid(True, alpha=0.3)
218
ax.set_xlim([0, 500])
219
plt.tight_layout()
220
plt.savefig('impulse_response.pdf', dpi=150, bbox_inches='tight')
221
plt.close()
222
\end{pycode}
223
224
\begin{figure}[H]
225
\centering
226
\includegraphics[width=0.85\textwidth]{impulse_response.pdf}
227
\caption{Room impulse response energy decay curve.}
228
\end{figure}
229
230
\section{Optimization}
231
232
\begin{pycode}
233
ceiling_coverage = np.linspace(0, 1, 50)
234
RT60_optimized = []
235
for coverage in ceiling_coverage:
236
alpha_opt = coverage * materials['Acoustic Tiles'] + (1 - coverage) * materials['Concrete']
237
A_opt = wall_area * alpha_walls + floor_area * alpha_floor + ceiling_area * alpha_opt
238
RT60_optimized.append(0.161 * room_volume / A_opt[idx_1k])
239
RT60_optimized = np.array(RT60_optimized)
240
241
target_RT60 = 0.8
242
optimal_idx = np.argmin(np.abs(RT60_optimized - target_RT60))
243
optimal_coverage = ceiling_coverage[optimal_idx]
244
245
fig, ax = plt.subplots(figsize=(8, 5))
246
ax.plot(ceiling_coverage * 100, RT60_optimized, 'b-', linewidth=2)
247
ax.axhline(y=target_RT60, color='r', linestyle='--', label=f'Target = {target_RT60} s')
248
ax.set_xlabel('Acoustic Tile Coverage (\\%)')
249
ax.set_ylabel('$T_{60}$ (s)')
250
ax.set_title('$T_{60}$ Optimization')
251
ax.legend()
252
ax.grid(True, alpha=0.3)
253
plt.tight_layout()
254
plt.savefig('rt60_optimization.pdf', dpi=150, bbox_inches='tight')
255
plt.close()
256
\end{pycode}
257
258
\begin{figure}[H]
259
\centering
260
\includegraphics[width=0.85\textwidth]{rt60_optimization.pdf}
261
\caption{$T_{60}$ optimization via ceiling treatment.}
262
\end{figure}
263
264
\section{Results}
265
266
\begin{pycode}
267
print(r'\begin{table}[H]')
268
print(r'\centering')
269
print(r'\caption{Room Acoustic Parameters}')
270
print(r'\begin{tabular}{@{}ccc@{}}')
271
print(r'\toprule')
272
print(r'Frequency (Hz) & $T_{60}$ Sabine (s) & $T_{60}$ Eyring (s) \\')
273
print(r'\midrule')
274
for i, f in enumerate(frequencies):
275
print(f'{f} & {RT60_sabine[i]:.2f} & {RT60_eyring[i]:.2f} \\\\')
276
print(r'\bottomrule')
277
print(r'\end{tabular}')
278
print(r'\end{table}')
279
\end{pycode}
280
281
Key metrics: $r_c = \py{round(r_c, 2)}$ m, $C_{50} = \py{round(C50, 1)}$ dB, $f_s = \py{round(f_schroeder, 1)}$ Hz.
282
283
\section{Conclusions}
284
285
The room configuration provides acceptable reverberation for multipurpose use. Optimal ceiling coverage is \py{round(optimal_coverage * 100, 0)}\%.
286
287
\end{document}
288
289