import matplotlib
if not hasattr(matplotlib.RcParams, "_get"):
matplotlib.RcParams._get = dict.get
2.2 Vectorized computation and NumPy#
The loop above works, but it has two problems. First, it is slow: Python’s interpreter dispatches every math.sin call individually, and at 44,100 calls per second of audio that adds up quickly. Second, it is verbose: four lines of bookkeeping to do something that, conceptually, is just “compute the sine of these timestamps.”
Both problems are solved by vectorized computation: instead of writing a for loop that operates on one number at a time, we describe an operation on an entire array at once. Under the hood, that single operation dispatches into precompiled, often SIMD-accelerated machine code, leaving Python’s interpreter out of the inner loop.
In Python, NumPy is the de facto standard vectorization library across many domains of scientific computing. The same 440 Hz sine in NumPy:
import math
import numpy as np
# In vanilla Python as a for loop
samples = [0.0] * N # sample buffer
for n in range(N):
samples[n] = math.sin(2.0 * math.pi * f * (n / f_s))
# As vectorized operation in NumPy
n = np.arange(N) # array of sample indices: 0, 1, ..., N-1
samples = np.sin(2 * np.pi * f * (n / f_s))
Notice the high-level difference: instead of calling math.sin 44,100 times, we apply np.sin once to the whole array of sample indices. The result is identical, but the code is shorter, the intent is clearer, and a modern CPU can churn through it many times faster. Vectorized array operations are the working dialect of computer music in Python, and the rest of this chapter is about gaining more experience with them.