# Direct Digital Synthesis¶

In [4]:
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>''')  Out[4]: In [3]: 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> """)  Out[3]: ## Webpage table of contents¶ ## The central abstraction¶ 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 of2^{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. In [5]: 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()

In [6]:
anim

Out[6]: