212 lines
5.5 KiB
Text
212 lines
5.5 KiB
Text
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import matplotlib\n",
|
|
"import numpy as np\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"\n",
|
|
"# Show plots inline.\n",
|
|
"%matplotlib inline\n",
|
|
"\n",
|
|
"# Number of harmonics to include in subsequent plots.\n",
|
|
"Harmonics = 10\n",
|
|
"# Time in seconds.\n",
|
|
"Time = 2.0\n",
|
|
"# Plot resolution.\n",
|
|
"Steps = 200"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Array of step values.\n",
|
|
"def time_space(sec=Time, samples=Steps):\n",
|
|
" '''An array of equally spaced samples over a period of time.'''\n",
|
|
" # One extra sample to also include the endpoint.\n",
|
|
" return np.linspace(0, sec, num=samples + 1)\n",
|
|
"\n",
|
|
"def sample_rate(sec=Time, samples=Steps):\n",
|
|
" '''Sampling rate in samples per second.'''\n",
|
|
" return float(samples + 1) / float(sec)\n",
|
|
"\n",
|
|
"def sample_spacing(sec=Time, samples=Steps):\n",
|
|
" '''Time between samples.'''\n",
|
|
" return float(sec) / float(samples + 1)\n",
|
|
"\n",
|
|
"def frequency_spectrum(data):\n",
|
|
" '''Perform an FFT on `data`.'''\n",
|
|
" # Get the signal frequencies.\n",
|
|
" fft = np.abs(np.fft.fft(data))\n",
|
|
" \n",
|
|
" # Get the frequencies for a DFT plot (this doesn't depend on the actual signal) based on the \n",
|
|
" # spacing of our samples.\n",
|
|
" freqs = np.fft.fftfreq(len(fft), d=sample_spacing())\n",
|
|
" \n",
|
|
" return freqs, fft\n",
|
|
"\n",
|
|
"def plot_signal_and_spectrum(domain, data):\n",
|
|
" '''Plot the signal and spectrum in a stacked plot.'''\n",
|
|
" plt.figure(figsize=(10,6))\n",
|
|
" plt.subplot(211)\n",
|
|
" plt.axhline(color='#666666', linewidth=0.75)\n",
|
|
" plt.plot(domain, data)\n",
|
|
" \n",
|
|
" num_freqs = Harmonics * 2\n",
|
|
" freqs, mags = (x[:num_freqs] for x in frequency_spectrum(data))\n",
|
|
" plt.subplot(212)\n",
|
|
" plt.xticks(range(num_freqs))\n",
|
|
" plt.bar(freqs, mags)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Basic Waves"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Sine Wave\n",
|
|
"\n",
|
|
"Sine waves are easy. 😁 The have a single frequency, and no harmonics."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Sine wave.\n",
|
|
"\n",
|
|
"def sine(domain, freq=1.0):\n",
|
|
" return np.sin(2.0 * np.pi * freq * domain)\n",
|
|
"\n",
|
|
"wave = sine(time_space())\n",
|
|
"plot_signal_and_spectrum(time_space(), wave)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Saw Wave\n",
|
|
"\n",
|
|
"Saw waves consist of every integer harmonic _n_ above the fundamental frequency, where that harmonic's amplitude is n<sup>-1</sup>."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def saw(domain, freq=1):\n",
|
|
" harmonics = np.asarray([1.0 / h * sine(domain, freq=freq * h) for h in range(1, Harmonics)])\n",
|
|
" wave = np.sum(harmonics, axis=0)\n",
|
|
" return wave\n",
|
|
"\n",
|
|
"wave = saw(time_space())\n",
|
|
"plot_signal_and_spectrum(time_space(), wave)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"You can invert a saw wave to get what many synths call a \"blade\" wave. Instead of gradually falling and then spiking up, blade waves gradually rise and the sharply fall."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"plot_signal_and_spectrum(time_space(), -1 * saw(time_space()))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Square Wave\n",
|
|
"\n",
|
|
"Square waves are made of every other harmonic above the fundamental frequency. Again, the amplitude of each harmonic _n_ is _1 / n_."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def square(domain, freq=1):\n",
|
|
" harmonics = np.asarray([1.0 / h * sine(domain, freq=freq * h) for h in range(1, Harmonics, 2)])\n",
|
|
" wave = np.sum(harmonics, axis=0)\n",
|
|
" return wave\n",
|
|
"\n",
|
|
"wave = square(time_space())\n",
|
|
"plot_signal_and_spectrum(time_space(), wave)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Triangle Wave"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def triangle(domain, freq=1):\n",
|
|
" def label(i):\n",
|
|
" return 2. * i + 1.\n",
|
|
" harmonics = np.asarray([((-1) ** h) * (label(h) ** -2) * sine(domain, freq=freq * label(h)) for h in range(Harmonics)])\n",
|
|
" wave = np.sum(harmonics, axis=0)\n",
|
|
" return wave\n",
|
|
"\n",
|
|
"wave = triangle(time_space())\n",
|
|
"plot_signal_and_spectrum(time_space(), wave)"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.6.1"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
}
|