{ "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-1." ] }, { "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 }