---
jupytext:
  formats: ipynb,md:myst,py:light
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.13.8
kernelspec:
  display_name: Python 3 (math-583)
  language: python
  name: math-583
---

```{code-cell}
:tags: [hide-cell]

import mmf_setup; mmf_setup.nbinit()
import os
from pathlib import Path
FIG_DIR = Path(mmf_setup.ROOT) / '../Docs/_build/figures/'
os.makedirs(FIG_DIR, exist_ok=True)
import logging; logging.getLogger("matplotlib").setLevel(logging.CRITICAL)
%matplotlib inline
import numpy as np, matplotlib.pyplot as plt
try: from myst_nb import glue
except: glue = None
```

(sec:Sound)=
Sound
=====

Self-similarity can produce some interesting auditory effects.  For example, in Chapter
3, {cite}`Schroeder:1991` discusses sounds based on Weierstrass functions which behave
in a counter-intuitive way: The waveform in Fig. 12 for example has the property that
doubling the frequency leads to the same pitch.

Related is the [Shepard tone][] which consists of a series of notes separated by octaves
such that the lowest and highest notes are outside of the threshold of hearing.  As the
pitch is continually increased, the lowest note enters the auditory range while the
highest note leaves, resulting in exactly the same perceived tone after continually
increasing for an entire octave: 

<iframe 
  width="560" height="315"
  src="https://www.youtube.com/embed/BzNzgsAE4F0?si=dyX-K8H8B08xrwia" 
  title="YouTube video player" frameborder="0" 
  allow="autoplay; encrypted-media; picture-in-picture; web-share" 
  allowfullscreen></iframe>



```{code-cell}
from matplotlib.gridspec import GridSpec

f_min = 1
f_max = 32

def envelope(f, f_min=f_min, f_max=f_max, alpha=1.0):
    """Return a smooth (C_infinity) envelope function between `f_min` and `f_max`."""
    x = 2*(f - f_min)/(f_max - f_min) - 1
    with np.errstate(over='ignore'):
        return np.where(
            np.logical_or(f<=f_min, f>=f_max),
            0,
            1/np.cosh(alpha*np.tan(x*np.pi/2)))

#f = np.linspace(0, 110)
#plt.plot(f, envelope(f))
fs = 2**np.arange(np.floor(np.log2(f_min)) - 4, 
                  np.ceil(np.log2(f_max)) + 4)
def f(t, fs=fs):
    A = envelope(fs[..., None])
    w = 2*np.pi * fs[..., None]
    return (A * np.sin(w*t)).sum(axis=0)

ts = np.linspace(0, 1/f_min, 203)
Noctave = 4
octaves = np.linspace(0, Noctave, 102)
As = f(ts, (2**octaves)[None, :] * fs[:, None])
#plt.plot(ts, f(ts, fs))
#plt.plot(ts, f(ts, 2fs))
fig = plt.figure(figsize=(8, 3))
gs = GridSpec(Noctave, 2, width_ratios=(2, 1))
ax0 = fig.add_subplot(gs[:, 0])
ax0.pcolormesh(ts, octaves, As)
for n in range(Noctave):
    ax0.axhline(n, ls='-', c=f'C{n}')
    ax = fig.add_subplot(gs[n, 1])
    A = f(ts, 2**n * fs)
    ax.plot(ts, A, f'-C{n}')
    
    
    

```
[Shepard tone]: <https://en.wikipedia.org/wiki/Shepard_tone>
