Here, as a very first article, we want to establish a couple of conventions and clear up some basics. In order to do so, we will construct a digital sine wave and listen to it. The main point here though is how properly construct time dependent signals in python/numpy.
All of these articles are produced using Jupyter-Lab and Python. If you want to follow along, I recommend using anaconda to install Jupyter-Lab and python. All of these tools are freely available online and state-of-the-art tools for exploring signal processing topics. If you are new to this, I recommend first looking into some online tutorial on python, numpy, Jupyter Notebooks as not all the details of working with these tools are explained here.
First things first, a couple of imports, namely:
%pylab inline
this imports many things at once, namely matplotlib
and numpy
. This makes life easier for small projects and is not recommended for bigger projects.from IPython.display import Audio
this import is later used to actually listen to our sine wave.style.use("website")
You can ignore this. It just configures the colors of the plots to fit this website using a custom matplotlib style.# "%pylab": importing a lot of things at once. Great for small projects, not recommended for bigger things.
%pylab inline
style.use("website") #used to correct plot style for website. Ignore/Remove if you follow this tutorial.
from IPython.display import Audio
π = pi #sets the greek pi symbol to the value of pi (3.14 ...).
Populating the interactive namespace from numpy and matplotlib
Before actually creating a sine wave to listen to, let's have a look at the sine function:
sin(123456) #evaluating the sine for some arbitrary number.
-0.7402834538866575
We can evaluate the sine function for multiple values at once by passing in a numpy array. For example lets make an array called $x$ that contains 10 numbers between 0 and $2\pi$ (remeber, the sine function repeats after an angle of $2 \pi$ radians.)
x = linspace(0, 2*π, 10)
print(x)
[0. 0.6981317 1.3962634 2.0943951 2.7925268 3.4906585 4.1887902 4.88692191 5.58505361 6.28318531]
We could now evaluate our sine function for all of these values in $x$. What we should get is one cycle of a digital sine wave.
y = sin(x)
print(y)
[ 0.00000000e+00 6.42787610e-01 9.84807753e-01 8.66025404e-01 3.42020143e-01 -3.42020143e-01 -8.66025404e-01 -9.84807753e-01 -6.42787610e-01 -2.44929360e-16]
These values are describing a sine wave. But its hard to tell, so let's plot them.
plot(x, y, 'ro') # plotting the values. the 'ro' gives us red('r') circles ('o').
xlabel('$x$') #axis labeling
ylabel('$y$') #axis labeling
show()
The Problem with what we have here is that we have not defined anything that has any relation to time. Also, we have produced just 10 values.
In digital audio, we need many values per second, many samples. The sampling rate, $f_s$, describes how many values per second (in Hz) we use.
In code, out of a habit and sticking to common conventions, I typically use either f_s
or sr
as variable names for the sampling rate.
Let's use the common (CD-quality) sampling rate of 44100.
If we now want to synthesize 1 second of Audio, we obviously want to produce 44100 values. But let's make this more flexible. Let's use a variable $T$ to define how many seconds of Audio we want. The number of values we will need will then just be the multiplication of the sampling rate $f_s$ and the duration $T$:
$$ N = T \cdot f_s $$
f_s = 41100 #Sampling rate [Hz]
T = 3 # Duration [seconds]
N = T*f_s #Number of samples.
print(N)
123300
There are multiple ways to go over the next step. The problem we are trying to solve is that we want to create $N$ values at wich we want to evaluate the sine wave. But what values do we use?
It would be great to have a time index array. So an array, that for each sample, tells us the time in seconds.
We could go about this in a similar way as before. We could just use a linspace
function: linspace(0, T, N)
, creating $N$ values that go from 0 to $T$.
I typically go about this differently:
ns = arange(N) #sample index. integers going from 0 to N-1
t = ns/f_s #time index going from 0 to T, always telling us the time in seconds.
Before we move on, let's just plot $t$, just tome make sure we are on the same page:
plot(t)
xlabel('array index of $t$')
ylabel('$t$')
show()
Now, we could try evaluating our sine wave on the time $$y = sin(t)$$
y = sin(t)
plot(t, y)
xlabel('$t$ [seconds]')
ylabel('$y$')
show()
As we know, the sine function only starts repeating when we go over $2\pi \approx 6.28$. The maximum we shove into our sine is 3, so we don't get to see the full wave. Let's change that. Let's make it so after 1 second, it already starts repeating. We can easily achieve this by multiplying $t$ with $2\pi$: $$ y = sin(2\pi t)$$
y = sin(2*π*t)
plot(t, y)
xlabel('$t$ [seconds]')
ylabel('$y$')
show()
We have created a 1 Hz sine wave at a sampling rate of 44100 Hz! Now, let's introduce another variable to control the frequency $f_0$, since the human hearing has difficulties with frequencies under 20 Hz. By multiplying what we have in the sine wave with $f_0$, we can make time run seemingly faster for the sine wave, increasing the frequency:
$$ y = sin(2\pi f_0 t)$$
f_0 = 170 # Frequency of our sine wave [Hz]
y = sin(2*π*f_0*t)
plot(t, y)
xlabel('$t$ [seconds]')
ylabel('$y$')
xlim([0, 0.1]) # important! Zooming in to see our wave, otherwise the plot is so full of lines that we don't see anything.
show()
And so, we successfully synthesized a sine wave at audible frequencies! Note that we zoomed in in the above plot, so we can actually properly see the waveform. This is also reflected in the values of the x-axis, showing us only the first 100 milliseconds of our signal. Let's finally listen to this!
Audio(y, rate=f_s) #using the Audio function we imported at the very top. Giving it an array (our sine) and telling it the sampling rate.
There is a little more to sine waves. We have not talked about its phase, amplitude, complex sines and much more. This little article was mostly to introduce the very basics. Also its main aim was to introduce how timing arrays and the sampling rate will be use in all examples to come. So, more than the basics of the sine wave, the takeaway should be:
f_s = 44100
T = 3
. This has to be in seconds for the rest of teh calculations to make sense.N = f_s*T
. Note: This should always be an integer. If you want to synthesize 3.5 seconds, you are responsible for dealing with this, e.g. by using N = int(f_s*T)
.ns = arange(N)
t = ns/f_s
All of this together will be seen in most examples on this site:
f_s = 41100 #Sampling rate [Hz]
T = 3 # Duration [seconds]
N = T*f_s #Number of samples.
ns = arange(N) #sample index. integers going from 0 to N-1
t = ns/f_s #time index going from 0 to T, always telling us the time in seconds.