# Initialize Otter
import otter
grader = otter.Notebook("lab8-audio.ipynb")
Lab 8: Audio Synthesis with Numpy#
This lab explores using loops to synthesize different types of audio waveform in python.
Entering Your Information for Credit#
To receive credit for assignments it is important we can identify your work from others. To do this we will ask you to enter your information in the following code block.
Before you begin#
Run the block of code at the top of the notebook that imports and sets up the autograder. This will allow you to check your work.
# Please provide your first name, last name, Drexel ID, and Drexel email. Make sure these are provided as strings. "STRINGS ARE TEXT ENCLOSED IN QUOTATION MARKS."
# In the assignments you will see sections of code that you need to fill in that are marked with ... (three dots). Replace the ... with your code.
first_name = ...
last_name = ...
drexel_id = ...
drexel_email = ...
grader.check("q0-Checking-Your-Name")
import matplotlib.pyplot as plt
import numpy as np
import IPython.display as ipd
Generating a Sine Wave
A sine wave \(s(t)\) with amplitude \(A\) and frequency \(f\) can be computed using the following equation:
In python, we can compute an array of values of the sine function by making \(t\) be an array of time values.
For instance, the following code sets t
to be an array of \(16000\) values lienaarly spaced from 0 to 1. (A few of these values are shown when we print t
below.)
# creates an array of 16000 time values from 0 to 1
t = np.linspace(0, 1, num=16000, endpoint=False)
print(t)
Now we can plug our t
array in into our equation to get a sine wave s
:
# amplitude of 1.0
A = 1.0
# frequency of 440.0
f = 440.0
# compute sine wave from equation
s = A * np.sin(2 * np.pi * f * t)
Now if we print s
, it might be difficult to tell that itβs a sine waveβ¦
print(s)
So letβs plot it!
# plot sinusoid s
plt.plot(s)
# show just the first 100 values
plt.xlim(0,100)
We can listen to the sine wave audio with the code below. (The sampling rate 16000 needs to be provided to specify how quickly we want to play the audio samples.)
# we must provide
ipd.Audio(s, rate=16000)
ipd.Audio(s, rate=16000)
Task 1: Approximating a Square Wave with Sinusoids
A square wave is a waveform that alternates steadily between two fixed values. We can compute a square wave \(q(t)\) with fundamental frequency \(f\) using the following infinite sum of sinusoids:
Usually it is a good option to use a for
loop when implementing a mathematical sum in python. However, this sum goes to inifinity: we canβt compute infinite loops. We can get an approximation of a square wave by summing just the first \(K\) terms:
Write python code to do the following:
Complete the function called
square_wave
which returns an approximate square wave based on input arguments (a fundamental frequencyf
and a number of termsK
).Loop over the desired number of terms
K
.For each term, calculate the appropriate sinusoid and add it to the provided starting array
q
.For the input time value \(t\) in the equation above, use the provided array of tiem values
t
.The function returns the approximate square wave q.
Your code replaces the prompt: ...
def square_wave(f,K):
# initialize our square wave q to be all zeros
q = np.zeros((16000,))
# create an array of linearly spaced time values
t = np.linspace(0, 1, num=16000, endpoint=False)
# loop over all desired values of k
# add each sinusoid to q
...
# return square wave q
return q
# plot a square wave
plt.plot(square_wave(440,200))
# show just the first 100 values
plt.xlim(0,100)
grader.check("task1-square")
Listen to a \(200\)-term square wave approximation with fundamental frequency \(440\). In electronic music, square wave synthesizers are often used to provide a hollow, distorted sound.
ipd.Audio(square_wave(440,200), rate=16000)
Task 2: Sum of Harmonic Sinusoids
The square wave is just one waveform the can be constructed using a sum of sinusoids. In general, any periodic waveform \(w(t)\) can be approximated using the following equation:
Where \(A_k\) is an amplitude value that is different for each term. Your task is to make a function that computes \(w(t)\) given a fundamental frequency \(f\) and a list of amplitude values \(A_k\). The number of terms \(K\) will be inferred by the length of the list of amplitudes.
Write python code to do the following:
Define a function called
waveform
which accepts as input a fundamental frequencyf
, and a list of amplitudesA
.First, initialize an empty array
w
of \(16000\) zeros, as in previous tasks.Then create an array
t
of \(16000\) linearly-spaced time values, as in previous tasks.Loop through all values of \(k\), adding on the appropriate sinusoid.
Return the final waveform
w
Your code replaces the prompt: ...
...
# plot a square wave with the first 3 nonzero terms
plt.plot(waveform(440,[1,0,(1/3),0,(1/5)]))
# show just the first 100 values
plt.xlim(0,100);
grader.check("task2-sinesum")
Task 3: Musical Sequence
Your final task involves using the waveform function you made to generate sequences of musical notes.
Music software often uses a protocol called MIDI (Musical Instrument Digital Interface) to format messages related to music. MIDI uses a numbering scheme for the notes on a piano, so that every note has a unique pitch value. Each pitch \(p\) is an integer in \([0,127]\). The following equation shows how to compute the fundamental frequency \(f\) for a note with MIDI pitch value \(p\):
In this problem, you will complete a function called music_sequence
which creates a waveform containing a sequence of musical notes. The input to the function will be a list of MIDI pitch values.
Write python code to do the following:
Complete the function called
music_sequence
which accepts as input a list of MIDI pitch valuesP
.Loop through all the pitch values in list
P
For each pitch \(p\), compute its frequency \(f\) using the equation above
Using the computed frequency \(f\) and the provided amplitude list
A
, generate a waveform using thewaveform
function from the previous taskAppend each waveform to the sequence list, which has been initialized for you
The provided code concatenates the sequence of notes and returns the final waveform
Your code replaces the prompt: ...
def music_sequence(P):
# initialize a empty list for our sequence of notes
sequence = []
# use this list of amplitudes A when generating waveforms
# it gives the notes an organ-like sound
A = [1, 0, 0, (1/4), 0, 0, 0, (1/8), 0, 0, 0, (1/12)]
# loop over all pitches in P and compute each frequency
# using the frequency and list A, generate a waveform
# append the waveform to the sequence list
...
return np.concatenate(sequence, axis=0)
# listen to the music sequence
ipd.Audio(music_sequence([60,62,64,65,67,69,71,72]), rate=16000)
grader.check("task3-musical-scale")
Submitting Your Assignment#
To submit your assignment please use the following link the assignment on GitHub classroom.
Use this link to navigate to the assignment on GitHub classroom.
If you need further instructions on submitting your assignment please look at Lab 1.
Viewing your score#
It is your responsibility to ensure that your grade report shows correctly. We can only provide corrections to grades if a grading error is determined. If you do not receive a grade report your grade has not been recorded. It is your responsibility either resubmit the assignment correctly or contact the instructors before the assignment due date.
Each .ipynb
file you have uploaded will have a file with the name of your file + Grade_Report.md
. You can view this file by clicking on the file name. This will show you the results of the autograder.
We have both public and hidden tests. You will be able to see the score of both tests, but not the specific details of why the test passed or failed.
Note
In python and particularly jupyter notebooks it is common that during testing you run cells in a different order, or run cells and modify them. This can cause there to be local variables needed for your solution that would not be recreated on running your code again from scratch. Your assignment will be graded based on running your code from scratch. This means before you submit your assignment you should restart the kernel and run all cells. You can do this by clicking Kernel
and selecting Restart and Run All
. If you code does not run as expected after restarting the kernel and running all cells it means you have an error in your code.