Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/acoustics/musical_acoustics.tex
51 views
unlisted
1
% Musical Acoustics Analysis
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{Musical Acoustics\\Harmonic Analysis and Instrument Modeling}
16
\author{Music Technology Laboratory}
17
\date{\today}
18
19
\begin{document}
20
\maketitle
21
22
\begin{abstract}
23
Computational analysis of musical acoustics including harmonic series, string vibrations, wind instrument resonances, and psychoacoustic phenomena.
24
\end{abstract}
25
26
\section{Introduction}
27
28
Musical instruments produce sound through vibrating systems generating harmonic spectra.
29
30
\begin{pycode}
31
import numpy as np
32
import matplotlib.pyplot as plt
33
from scipy.signal import sawtooth, square
34
from scipy.fft import fft, fftfreq
35
plt.rcParams['text.usetex'] = True
36
plt.rcParams['font.family'] = 'serif'
37
38
c = 343
39
fs = 44100
40
\end{pycode}
41
42
\section{Harmonic Series}
43
44
$f_n = n f_1$
45
46
\begin{pycode}
47
f1 = 110
48
n_harmonics = 16
49
harmonics = np.arange(1, n_harmonics + 1) * f1
50
51
fig, ax = plt.subplots(figsize=(12, 5))
52
ax.bar(range(1, n_harmonics + 1), harmonics, color='steelblue')
53
ax.set_xlabel('Harmonic Number')
54
ax.set_ylabel('Frequency (Hz)')
55
ax.set_title(f'Harmonic Series of A2 ({f1} Hz)')
56
ax.grid(True, alpha=0.3, axis='y')
57
plt.tight_layout()
58
plt.savefig('harmonic_series.pdf', dpi=150, bbox_inches='tight')
59
plt.close()
60
\end{pycode}
61
62
\begin{figure}[H]
63
\centering
64
\includegraphics[width=0.95\textwidth]{harmonic_series.pdf}
65
\caption{Harmonic series for 110 Hz fundamental.}
66
\end{figure}
67
68
\section{String Vibration Modes}
69
70
\begin{pycode}
71
L = 0.65
72
T = 70
73
mu = 0.00531
74
f1_string = (1 / (2 * L)) * np.sqrt(T / mu)
75
76
x = np.linspace(0, L, 500)
77
modes = [1, 2, 3, 4, 5]
78
79
fig, axes = plt.subplots(len(modes), 1, figsize=(10, 8), sharex=True)
80
for ax, n in zip(axes, modes):
81
y = np.sin(n * np.pi * x / L)
82
ax.plot(x * 100, y, 'b-', linewidth=1.5)
83
ax.fill_between(x * 100, y, alpha=0.3)
84
ax.set_ylabel(f'Mode {n}')
85
ax.axhline(y=0, color='k', linewidth=0.5)
86
fn = n * f1_string
87
ax.text(0.98, 0.8, f'$f_{n}$ = {fn:.1f} Hz', transform=ax.transAxes, ha='right')
88
axes[-1].set_xlabel('Position (cm)')
89
plt.tight_layout()
90
plt.savefig('string_modes.pdf', dpi=150, bbox_inches='tight')
91
plt.close()
92
\end{pycode}
93
94
\begin{figure}[H]
95
\centering
96
\includegraphics[width=0.85\textwidth]{string_modes.pdf}
97
\caption{String vibration mode shapes.}
98
\end{figure}
99
100
\section{Waveform Synthesis}
101
102
\begin{pycode}
103
duration = 0.02
104
t = np.linspace(0, duration, int(fs * duration))
105
f0 = 220
106
107
waveforms = {
108
'Sine': np.sin(2 * np.pi * f0 * t),
109
'Triangle': sawtooth(2 * np.pi * f0 * t, 0.5),
110
'Sawtooth': sawtooth(2 * np.pi * f0 * t),
111
'Square': square(2 * np.pi * f0 * t)
112
}
113
114
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
115
for ax, (name, wave) in zip(axes.flatten(), waveforms.items()):
116
ax.plot(t * 1000, wave, 'b-', linewidth=1)
117
ax.set_xlabel('Time (ms)')
118
ax.set_ylabel('Amplitude')
119
ax.set_title(f'{name} Wave')
120
ax.grid(True, alpha=0.3)
121
plt.tight_layout()
122
plt.savefig('waveforms.pdf', dpi=150, bbox_inches='tight')
123
plt.close()
124
\end{pycode}
125
126
\begin{figure}[H]
127
\centering
128
\includegraphics[width=0.9\textwidth]{waveforms.pdf}
129
\caption{Musical waveform types.}
130
\end{figure}
131
132
\section{Spectral Analysis}
133
134
\begin{pycode}
135
duration_fft = 0.1
136
t_fft = np.linspace(0, duration_fft, int(fs * duration_fft))
137
N = len(t_fft)
138
139
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
140
waveforms_fft = {
141
'Sine': np.sin(2 * np.pi * f0 * t_fft),
142
'Triangle': sawtooth(2 * np.pi * f0 * t_fft, 0.5),
143
'Sawtooth': sawtooth(2 * np.pi * f0 * t_fft),
144
'Square': square(2 * np.pi * f0 * t_fft)
145
}
146
147
for ax, (name, wave) in zip(axes.flatten(), waveforms_fft.items()):
148
yf = np.abs(fft(wave))[:N//2]
149
xf = fftfreq(N, 1/fs)[:N//2]
150
yf = yf / np.max(yf)
151
ax.plot(xf, yf, 'b-', linewidth=0.8)
152
ax.set_xlabel('Frequency (Hz)')
153
ax.set_ylabel('Magnitude')
154
ax.set_title(f'{name} Spectrum')
155
ax.set_xlim([0, 3000])
156
plt.tight_layout()
157
plt.savefig('spectra.pdf', dpi=150, bbox_inches='tight')
158
plt.close()
159
\end{pycode}
160
161
\begin{figure}[H]
162
\centering
163
\includegraphics[width=0.9\textwidth]{spectra.pdf}
164
\caption{Frequency spectra showing harmonic content.}
165
\end{figure}
166
167
\section{Pipe Resonances}
168
169
\begin{pycode}
170
L_pipe = 0.5
171
n_res = 8
172
f_open = np.array([n * c / (2 * L_pipe) for n in range(1, n_res + 1)])
173
f_closed = np.array([(2*n - 1) * c / (4 * L_pipe) for n in range(1, n_res + 1)])
174
175
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
176
ax1.bar(range(1, n_res + 1), f_open, color='forestgreen')
177
ax1.set_xlabel('Mode')
178
ax1.set_ylabel('Frequency (Hz)')
179
ax1.set_title('Open Pipe')
180
181
ax2.bar(range(1, n_res + 1), f_closed, color='darkorange')
182
ax2.set_xlabel('Mode')
183
ax2.set_ylabel('Frequency (Hz)')
184
ax2.set_title('Closed Pipe')
185
plt.tight_layout()
186
plt.savefig('pipe_resonances.pdf', dpi=150, bbox_inches='tight')
187
plt.close()
188
\end{pycode}
189
190
\begin{figure}[H]
191
\centering
192
\includegraphics[width=0.95\textwidth]{pipe_resonances.pdf}
193
\caption{Pipe resonance frequencies.}
194
\end{figure}
195
196
\section{Beating Phenomenon}
197
198
\begin{pycode}
199
f1_beat, f2_beat = 440, 443
200
beat_freq = abs(f1_beat - f2_beat)
201
duration_beat = 1.0
202
t_beat = np.linspace(0, duration_beat, int(fs * duration_beat))
203
204
y1 = np.cos(2 * np.pi * f1_beat * t_beat)
205
y2 = np.cos(2 * np.pi * f2_beat * t_beat)
206
y_sum = y1 + y2
207
208
fig, axes = plt.subplots(3, 1, figsize=(12, 8), sharex=True)
209
axes[0].plot(t_beat, y1, 'b-', linewidth=0.5)
210
axes[0].set_ylabel('$f_1$ = 440 Hz')
211
axes[1].plot(t_beat, y2, 'r-', linewidth=0.5)
212
axes[1].set_ylabel('$f_2$ = 443 Hz')
213
axes[2].plot(t_beat, y_sum, 'g-', linewidth=0.5)
214
axes[2].set_ylabel('Sum')
215
axes[2].set_xlabel('Time (s)')
216
for ax in axes:
217
ax.set_xlim([0, 0.5])
218
plt.tight_layout()
219
plt.savefig('beating.pdf', dpi=150, bbox_inches='tight')
220
plt.close()
221
\end{pycode}
222
223
\begin{figure}[H]
224
\centering
225
\includegraphics[width=0.95\textwidth]{beating.pdf}
226
\caption{Beating with \py{beat_freq} Hz beat frequency.}
227
\end{figure}
228
229
\section{Tuning Systems}
230
231
\begin{pycode}
232
intervals = ['Unison', 'Minor 2nd', 'Major 2nd', 'Minor 3rd', 'Major 3rd', 'Perfect 4th', 'Tritone', 'Perfect 5th']
233
equal_temp = [2**(i/12) for i in range(8)]
234
just_int = [1, 16/15, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2]
235
cents_diff = [1200 * np.log2(j/e) if e != 0 else 0 for j, e in zip(just_int, equal_temp)]
236
237
fig, ax = plt.subplots(figsize=(12, 6))
238
x = np.arange(len(intervals))
239
width = 0.35
240
ax.bar(x - width/2, equal_temp, width, label='Equal Temperament')
241
ax.bar(x + width/2, just_int, width, label='Just Intonation')
242
ax.set_xlabel('Interval')
243
ax.set_ylabel('Frequency Ratio')
244
ax.set_title('Tuning Systems Comparison')
245
ax.set_xticks(x)
246
ax.set_xticklabels(intervals, rotation=45, ha='right')
247
ax.legend()
248
plt.tight_layout()
249
plt.savefig('tuning_comparison.pdf', dpi=150, bbox_inches='tight')
250
plt.close()
251
\end{pycode}
252
253
\begin{figure}[H]
254
\centering
255
\includegraphics[width=0.95\textwidth]{tuning_comparison.pdf}
256
\caption{Tuning system comparison.}
257
\end{figure}
258
259
\section{ADSR Envelope}
260
261
\begin{pycode}
262
attack, decay, sustain_level, sustain_time, release = 0.05, 0.1, 0.7, 0.3, 0.2
263
t_env = np.linspace(0, attack + decay + sustain_time + release, 1000)
264
envelope = np.zeros_like(t_env)
265
266
for i, t in enumerate(t_env):
267
if t < attack:
268
envelope[i] = t / attack
269
elif t < attack + decay:
270
envelope[i] = 1 - (1 - sustain_level) * (t - attack) / decay
271
elif t < attack + decay + sustain_time:
272
envelope[i] = sustain_level
273
else:
274
envelope[i] = sustain_level * (1 - (t - attack - decay - sustain_time) / release)
275
276
fig, ax = plt.subplots(figsize=(10, 5))
277
ax.plot(t_env * 1000, envelope, 'b-', linewidth=2)
278
ax.fill_between(t_env * 1000, envelope, alpha=0.3)
279
ax.set_xlabel('Time (ms)')
280
ax.set_ylabel('Amplitude')
281
ax.set_title('ADSR Envelope')
282
ax.grid(True, alpha=0.3)
283
plt.tight_layout()
284
plt.savefig('adsr_envelope.pdf', dpi=150, bbox_inches='tight')
285
plt.close()
286
\end{pycode}
287
288
\begin{figure}[H]
289
\centering
290
\includegraphics[width=0.85\textwidth]{adsr_envelope.pdf}
291
\caption{ADSR amplitude envelope.}
292
\end{figure}
293
294
\section{Results}
295
296
\begin{pycode}
297
print(r'\begin{table}[H]')
298
print(r'\centering')
299
print(r'\caption{Tuning Comparison}')
300
print(r'\begin{tabular}{@{}lccc@{}}')
301
print(r'\toprule')
302
print(r'Interval & Equal Temp & Just Int & Diff (cents) \\')
303
print(r'\midrule')
304
for i in range(len(intervals)):
305
print(f"{intervals[i]} & {equal_temp[i]:.4f} & {just_int[i]:.4f} & {cents_diff[i]:.1f} \\\\")
306
print(r'\bottomrule')
307
print(r'\end{tabular}')
308
print(r'\end{table}')
309
\end{pycode}
310
311
\section{Conclusions}
312
313
This analysis covers fundamental musical acoustics including harmonic generation, vibration modes, and tuning systems.
314
315
\end{document}
316
317