HTML('''<script>
code_show=true;
function code_toggle() {
if (code_show){
$('div.input').hide();
} else {
$('div.input').show();
}
code_show = !code_show
}
$( document ).ready(code_toggle);
</script>
<form action="javascript:code_toggle()"><input type="submit" value="Click here to toggle on/off the raw code."></form>''')
import numpy
import matplotlib.pyplot as plt
from IPython.display import Audio
from IPython.display import Image
from scipy import signal
from scipy.fft import fftshift
from scipy.fft import fft, fftfreq
from scipy.io import wavfile
plt.rcParams['figure.figsize'] = [12, 4]
from IPython.core.display import HTML
from matplotlib import animation, rc
from IPython.display import HTML
from IPython.display import clear_output
HTML("""
<style>
.output_png {
display: table-cell;
text-align: center;
vertical-align: middle;
}
</style>
""")
The observation upon which the direct digital synthesis algorithm is based is that a variable overflowing is isomorphic to one rotation of a phasor. Recall from some math class years ago that a sine wave is generated by projecting a rotating phasor onto the imaginary axis, as shown below. We see the rotating phasor and it's associated angle in red, the projection onto the imaginary axis in green, and the generated sine wave streaming off in orange. Note that, after the phasor has rotated 360 degrees, we have completed one period of the sine wave and the whole thing repeats.
Now, suppose that we represented the angle of that phasor, from the positive x-axis, with a 32-bit number that we'll call the accumulator. A scaled version of the phase angle will be stored in the accumulator. An angle of 0 degrees from the positive x-axis will correspond to an accumulator value of 0 (a 0 in each of the 32 bits). An angle of 360 degrees from the positive x-axis will correspond to an accumulator value of $2^{32}-1$ (a 1 in each of the 32 bits). Then, overflowing the accumulator corresponds to completing a full rotation of the phasor.
We'll see why this is useful in the next section. For now, all that we've done is scaled the range of phasor angles from $0 \rightarrow 2\pi$ radians to instead $0 \rightarrow 2^{32}-1$ units, and stored that rescaled value in a 32-bit variable that we are calling accumulator.
angles = numpy.linspace(0, 2*numpy.pi, 256)
# First set up the figure, the axis, and the plot element we want to animate
fig, ax = plt.subplots(figsize=(21, 7.77))
ax.set_xlim((-1.57, 7.85))
ax.set_ylim((-1.57, 2.0))
# ax.plot([-1.3, -0.6], [1.33, 1.33], 'b-')
ann = ax.annotate("Phase angle (rad): 0", xy=(-1.3, 1.78), xytext=(-1.3, 1.78), fontsize=18, alpha=0.4)
ann1 = ax.annotate("Phase angle, scaled to 0-$2^{32}$ and represented in binary (\"Accumulator\")",
xy=(-1.3, 1.5), xytext=(-1.3, 1.5), fontsize=18, alpha=0.4)
ann2 = ax.annotate('{0:032b}'.format(0), xy=(-1.3, 1.35), xytext=(-1.3, 1.35), fontsize=18, alpha=0.4)
# ann3 = ax.annotate('indexes into 256-entry sine table',
# xy=(-0.95, 1.33),
# xytext=(0.01, 0.75), textcoords='axes fraction',
# arrowprops=dict(facecolor='black', shrink=0.01),
# horizontalalignment='left', verticalalignment='top')
line, = ax.plot(numpy.cos(angles), numpy.sin(angles))
line1, = ax.plot([],[], 'black', alpha=0.1)
line2, = ax.plot([], [], 'black', alpha=0.1)
line3, = ax.plot([],[], 'green', linewidth=2)
line4, = ax.plot([], [], 'r.')
line5, = ax.plot([], [], 'orange')
line6, = ax.plot([], [], 'r-')
line7, = ax.plot([], [], 'r-')
# initialization function: plot the background of each frame
def init():
line.set_data(numpy.cos(angles), numpy.sin(angles))
line1.set_data([-1.1, 1.1], [0, 0])
line2.set_data([0, 0], [-1.1, 1.1])
line3.set_data([0, 0],[0, numpy.sin(0)])
line4.set_data(0, numpy.sin(0))
line5.set_data(numpy.linspace(0, 7.85, 256), numpy.sin(0 - numpy.linspace(0, 7.85, 256)))
line6.set_data([0, numpy.cos(0)], [0, numpy.sin(0)])
line7.set_data([], [])
return (line, line1, line2, line3, line4, line5, line6, line7,)
def animate(i):
global ann
global ann2
ann.remove()
ann2.remove()
line.set_data(numpy.cos(angles), numpy.sin(angles))
line1.set_data([-1.1, 1.1], [0, 0])
line2.set_data([0, 0], [-1.1, 1.1])
line3.set_data([0, 0],[0, numpy.sin(angles[i])])
line4.set_data(0, numpy.sin(angles[i]))
line5.set_data(numpy.linspace(0, 7.85, 256), numpy.sin(angles[i] - numpy.linspace(0, 7.85, 256)))
line6.set_data([0, numpy.cos(angles[i])], [0, numpy.sin(angles[i])])
line7.set_data(numpy.cos(numpy.linspace(0, angles[i], 100))*0.1, numpy.sin(numpy.linspace(0, angles[i], 100))*0.1)
ann = ax.annotate("Phase angle (rad): "+str(angles[i]),
xy=(-1.3, 1.78), xytext=(-1.3, 1.78), fontsize=18, alpha=0.4)
OldRange = 2*numpy.pi
NewRange = (2**32.)
NewValue = (((angles[i]) * NewRange) / OldRange)
ann2 = ax.annotate('{0:032b}'.format(int(NewValue)), xy=(-1.3, 1.35),
xytext=(-1.3, 1.35), fontsize=18, alpha=0.4)
return (line, line1, line2, line3, line4, line5, line6, line7,)
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=256, interval=20, blit=True)
rc('animation', html='jshtml')
plt.close()
anim