<jupyter_start><jupyter_text>[exercises](intro.ipynb)<jupyter_code>import numpy as np
np.arange(6)
np.arange(0, 0.6, 0.1), np.arange(6) * 0.1  # two possibilities
np.arange(0.5, 1.1, 0.1), "<-- wrong result!"
np.arange(5, 11) * 0.1, "<-- that's right!"
np.linspace(0, 6, 7)
np.linspace(0, 6, 6, endpoint=False), np.linspace(0, 5, 6)  # two possibilities
np.linspace(0, 0.6, 6, endpoint=False), np.linspace(0, 0.5, 6)  # again two possibilities
np.linspace(0.5, 1.1, 6, endpoint=False), np.linspace(0.5, 1, 6)  # and again ...<jupyter_output><empty_output><jupyter_text>If the number of elements is known and the step size should be obtained automatically $\Rightarrow$ `np.linspace()`  If the step size is known an if it's an integer and the number of elements should be obtained automatically $\Rightarrow$ `np.arange()`If the step size is not an integer:* If the step size is a fraction of integers, you can use `np.arange()` with integers and divide the result accordingly.* If that's not feasible, calculate the expected number of elements beforehand and use `np.linspace()`<jupyter_code>dur, amp, freq, fs = 1, 0.3, 500, 44100
t = np.arange(np.ceil(dur * fs)) / fs
y = amp * np.sin(2 * np.pi * freq * t)<jupyter_output><empty_output><jupyter_text>alternative (but inferior) methods to get $t$:<jupyter_code>t1 = np.arange(0, dur, 1/fs)  # implicit rounding of dur!
t2 = np.arange(0, np.round(dur), 1/fs)  # still problematic: arange with floats
# wrong if dur isn't an integer multiple of 1/fs:
t3 = np.linspace(0, dur, np.round(dur * fs), endpoint=False)<jupyter_output><empty_output><jupyter_text>Length of `y` must be *exactly* 44100 (using a half-open interval for $t$), not 44101 (which would be longer than 1 second).Plotting: 2 ways to zoom (there are probably more): draw a rectangle, drag with the right mouse button in pan/zoom mode.Clicks? Because of discontinuities (also in the derivatives) $\Rightarrow$ Fade in/out! See [tools.fade()](tools.py).<jupyter_code>import sounddevice as sd
import tools

def myplay(data):
    """Apply fade in/out and play with 44.1 kHz."""
    data = tools.fade(data, 2000, 5000)
    sd.play(data, 44100)
myplay(y)
def mysine(frequency, amplitude, duration):
    """Generate sine tone with the given parameters @ 44.1 kHz."""
    samplerate = 44100
    times = np.arange(np.ceil(duration * samplerate)) / samplerate
    return amplitude * np.sin(2 * np.pi * frequency * times)
z = mysine(440, 0.4, 3)
myplay(z)
%matplotlib
import matplotlib.pyplot as plt

def myplot(data):
    """Create a simple plot @ 44.1 kHz."""
    samplerate = 44100
    times = np.arange(len(data)) / samplerate
    plt.plot(times, data)
    plt.xlabel("Time / Seconds")
myplot(mysine(440, 0.4, 3))
import soundfile as sf

dur, amp = 1, 0.3
frequencies = 400, 500, 600  # Hz
fadetime = 2000  # samples

for freq in frequencies:
    sig = mysine(freq, amp, dur)
    sig = tools.fade(sig, fadetime)
    sf.write("sine_{}hz.wav".format(freq), sig, 44100)
from scipy import signal

f0, f1 = 100, 5000  # Hz
amp = 0.2
dur = 2  # seconds
fadetime = 2000  # samples
fs = 44100

t = np.arange(np.ceil(dur * fs)) / fs

for method in 'linear', 'log':
    sweep = amp * signal.chirp(t, f0, dur, f1, method)
    sweep = tools.fade(sweep, fadetime)
    sf.write('sweep_{}.wav'.format(method), sweep, fs)
sinetone = mysine(frequency=500, amplitude=0.3, duration=1.5)
noise = np.random.normal(scale=0.1, size=len(sinetone))
sine_plus_noise = sinetone + noise
myplay(sine_plus_noise)
myplot(sine_plus_noise)
dur = 2
amp = 0.2

two_sines = mysine(500, amp, dur) + mysine(507, amp, dur)
myplay(two_sines)
myplot(two_sines)<jupyter_output><empty_output><jupyter_text>Two sine tones with similar frequencies create "beats", see .The sum of these two tones is equivalent to an amplitude modulation with a carrier frequency of $\frac{f_1+f_2}{2}$ and a modulation frequency of $\frac{f_1-f_2}{2}$.$$\cos(2\pi f_1t)+\cos(2\pi f_2t) = 2\cos\left(2\pi\frac{f_1+f_2}{2}t\right)\cos\left(2\pi\frac{f_1-f_2}{2}t\right)$$We don't really *hear* the modulation frequency itself, we only hear the envelope of the modulation, therefore the *perceived* beat frequency is $f_{\text{beat}} = f_1-f_2$.<jupyter_code>stereo_sines = np.column_stack([mysine(400, amp, dur), mysine(600, amp, dur)])
myplay(stereo_sines)<jupyter_output><empty_output><jupyter_text>The first column should be the left channel!<jupyter_code>dur, amp = 1, 0.3
freq = 500  # Hz
delay = 0.5  # ms
fs = 44100

t = np.arange(np.ceil(dur * fs)) / fs
times = np.column_stack((t, t - delay/1000))
sig = amp * np.sin(2 * np.pi * freq * times)
myplay(sig)
dur, amp = 0.5, 0.3
frequencies = 500, 1000, 2000  # Hz
delays = 0.6, 0.4, 0.2, 0, -0.2, -0.4, -0.6  # ms
fs = 44100

t = np.arange(np.ceil(dur * fs)) / fs

for f in frequencies:
    for delay in delays:
        times = np.column_stack((t, t - delay/1000))
        sig = amp * np.sin(2 * np.pi * f * times)
        myplay(sig)
        sd.wait()<jupyter_output><empty_output><jupyter_text>This is supposed to illustrate [Lord Rayleigh's Duplex Theory](http://en.wikipedia.org/wiki/Interaural_time_differenceDuplex_theory) (at least the part about time differences).<jupyter_code>dur, amp = 2, 0.3
frequencies = np.array([200, 400, 600, 800, 1000])
fs = 44100
t = np.arange(np.ceil(dur * fs)) / fs
t.shape = -1, 1
t
amplitudes = amp * 1 / np.arange(1, len(frequencies)+1)
amplitudes
five_sines = amplitudes * np.sin(2 * np.pi * frequencies * t)
five_sines.shape
sum_of_sines = five_sines.sum(axis=1)
myplot(sum_of_sines)
myplay(five_sines[:, [0, 1, 2, 3, 4]].sum(axis=1))
myplay(five_sines[:, [0, 1, 2, 3]].sum(axis=1))
myplay(five_sines[:, [0, 1, 2, 4]].sum(axis=1))
myplay(five_sines[:, [0, 1, 3, 4]].sum(axis=1))
myplay(five_sines[:, [0, 2, 3, 4]].sum(axis=1))
myplay(five_sines[:, [1, 2, 3, 4]].sum(axis=1))<jupyter_output><empty_output><jupyter_text><jupyter_code>f0 = 200  # Hz
partials = 20

frequencies = f0 * np.arange(1, partials + 1)
frequencies
amplitudes = amp * 1 / np.arange(1, len(frequencies)+1)
amplitudes
many_sines = amplitudes * np.sin(2 * np.pi * frequencies * t)
many_sines.shape
sawtooth = many_sines.sum(axis=1)
myplot(sawtooth)
myplay(sawtooth)<jupyter_output><empty_output><jupyter_text>https://en.wikipedia.org/wiki/Sawtooth_wave<jupyter_code>square = many_sines[:, ::2].sum(axis=1)
myplot(square)
myplay(square)<jupyter_output><empty_output><jupyter_text>https://en.wikipedia.org/wiki/Square_wave<jupyter_code>c = 343
samplerate = 44100
dur = 0.01

phat = 0.2
freq = 500
omega = 2 * np.pi * freq
kx = omega / c
x = 0

time = np.arange(np.ceil(dur * fs)) / fs
p = phat * np.exp(1j*(kx*x - omega*time))

plt.plot(time*1000, np.real(p))
plt.xlabel('$t$ / ms')
plt.ylabel('$\mathcal{R}\{p(x,t)\}$ / Pa')
plt.grid()
plt.title('$f = {}$ Hz, $T = {}$ ms'.format(freq, 1000/freq));
xrange = 3
dx = 0.001
time = 0

x = np.arange(np.ceil(xrange/dx)) * dx
p = phat * np.exp(1j*(kx*x - omega*time))

plt.plot(x*100, np.real(p))
plt.xlabel('$x$ / cm')
plt.ylabel('$\mathcal{R}\{p(x,t)\}$ / Pa')
plt.grid()
plt.title('$f = {}$ Hz, $\lambda = {}$ cm'.format(freq, c*100/freq));<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16306483300589392, 'fraction_numerical': 0.054308167274768455, 'mean_word_length': 4.520526723470178, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 41, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '6665', 'license': 'CC0-1.0', 'n_tokens_mistral': 3178, 'n_tokens_neox': 2841, 'n_words': 730, 'path': 'intro-solutions.ipynb', 'repo_name': 'mgeier/communication-acoustics-exercises', 'size': '7126'}