CS170: Programming for the World Around Us - Graphics and Sound with Python

Activity Goals

The goals of this activity are:
  1. To use graphics and sound capabilities in Python

The Activity

Directions

Consider the activity models and answer the questions provided. First reflect on these questions on your own briefly, before discussing and comparing your thoughts with your group. Appoint one member of your group to discuss your findings with the class, and the rest of the group should help that member prepare their response. Answer each question individually from the activity, and compare with your group to prepare for our whole-class discussion. After class, think about the questions in the reflective prompt and respond to those individually in your notebook. Report out on areas of disagreement or items for which you and your group identified alternative approaches. Write down and report out questions you encountered along the way for group discussion.

Model 1: Graphics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from ezgraphics import GraphicsWindow
 
win = GraphicsWindow(640, 480)
win.setTitle("My First Drawing")
 
canvas = win.canvas()
 
canvas.setFill(255, 0, 0)
canvas.drawRectangle(40, 40, 100, 200)
 
canvas.setFill(255, 255, 255)
canvas.setOutline(0, 255, 0)
canvas.drawRectangle(200, 200, 150, 50)
 
win.wait()

Questions

  1. Run this code in your terminal: http://www.ezgraphics.org/uploads/Software/Download/ezgraphics-2.2.tar.gz && pip install ezgraphics-2.2.tar.gz to install the ezgraphics library, and run this program. What does it do?
  2. Experiment with the functions and generate your own shapes. Can you draw a house or a stick figure? Make a function that does this, given the x and y midpoint as parameters.

Model 2: Animation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from ezgraphics import GraphicsWindow
import random
 
win = GraphicsWindow(640, 480)
win.setTitle("My First Drawing")
 
i = 0
j = 0
lasti = -1
lastj = -1
 
canvas = win.canvas()
 
canvas.setFill(0,0,0)
canvas.drawRectangle(0, 0, 640, 480)
 
while True:
  if i > 0 and j > 0:
    canvas.setFill(0,0,0)
    canvas.drawRectangle(i-1, j-1, 10, 20)
     
  canvas.setFill(255, 255, 255)
  canvas.drawRectangle(i, j, 10, 20)
 
  lasti = i
  lastj = j
   
  i = i + random.randint(-1, 1)
  j = j + random.randint(-1, 1)
 
  if i > 640:
    i = 639
  if j > 480:
    j = 479
 
  if i < 0:
    i = 1
  if j < 0:
    j = 1
 
  win.pause(1)
   
win.wait()

Questions

  1. Comment this program; what does it do? You will find it helpful to run the program first, and may find it helpful to set a breakpoint and use the debugger!

Model 3: Sound

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
 
"""Play a fixed frequency sound."""
from __future__ import division
import math
from scipy import signal
from pyaudio import PyAudio # sudo apt-get install python{,3}-pyaudio
 
try:
    from itertools import izip
except ImportError: # Python 3
    izip = zip
    xrange = range
 
def sine_tone(frequency, duration, volume=1, sample_rate=22050):
    n_samples = int(sample_rate * duration)
    restframes = n_samples % sample_rate
 
    p = PyAudio()
    stream = p.open(format=p.get_format_from_width(1), # 8bit
                    channels=1, # mono
                    rate=sample_rate,
                    output=True)
                     
    s = lambda t: volume * math.sin(2 * math.pi * frequency * t / sample_rate)
    samples = (int(s(t) * 0x7f + 0x80) for t in xrange(n_samples))
 
    if duration >= 1:
        sample_size = sample_rate
    else:
        sample_size = int(sample_rate * duration)
 
    for buf in izip(*[samples]*sample_size): # write several samples at a time
        stream.write(bytes(bytearray(buf)))
 
    # fill remainder of frameset with silence
    stream.write(b'\x80' * restframes)
 
    stream.stop_stream()
    stream.close()
    p.terminate()
 
def square_tone(frequency, duration, volume=1, sample_rate=22050):
    n_samples = int(sample_rate * duration)
    restframes = n_samples % sample_rate
 
    p = PyAudio()
    stream = p.open(format=p.get_format_from_width(1), # 8bit
                    channels=1, # mono
                    rate=sample_rate,
                    output=True)
 
    s = lambda t: volume * signal.square(2 * math.pi * frequency * t / sample_rate)
    samples = (int(s(t) * 0x7f + 0x80) for t in xrange(n_samples))
 
    if duration >= 1:
        sample_size = sample_rate
    else:
        sample_size = int(sample_rate * duration)
 
    for buf in izip(*[samples]*sample_size): # write several samples at a time
        stream.write(bytes(bytearray(buf)))
 
    # fill remainder of frameset with silence
    stream.write(b'\x80' * restframes)
 
    stream.stop_stream()
    stream.close()
    p.terminate()   
 
def dtmf_tone(frequency1, frequency2, duration, volume=1, sample_rate=22050):
    n_samples = int(sample_rate * duration)
    restframes = n_samples % sample_rate
 
    p = PyAudio()
    stream = p.open(format=p.get_format_from_width(1), # 8bit
                    channels=1, # mono
                    rate=sample_rate,
                    output=True)
                     
    s1 = lambda t: volume * math.sin(2 * math.pi * frequency1 * t / sample_rate)
    s2 = lambda t: volume * math.sin(2 * math.pi * frequency2 * t / sample_rate)
    samples = (int(s1(t) * 0x3f + 0x40) + int(s2(t) * 0x3f + 0x40) for t in xrange(n_samples)) # scale to 0-255
 
    if duration >= 1:
        sample_size = sample_rate
    else:
        sample_size = int(sample_rate * duration)
 
    for buf in izip(*[samples]*sample_size): # write several samples at a time
        stream.write(bytes(bytearray(buf)))
 
    # fill remainder of frameset with silence
    stream.write(b'\x80' * restframes)
 
    stream.stop_stream()
    stream.close()
    p.terminate()
 
def sine_chord(frequencies, duration, volume=1, sample_rate=22050):
    if len(frequencies) >= 6:
        frequencies = frequencies[:6]
 
    n_samples = int(sample_rate * duration)
    restframes = n_samples % sample_rate
 
    p = PyAudio()
    stream = p.open(format=p.get_format_from_width(1), # 8bit
                    channels=1, # mono
                    rate=sample_rate,
                    output=True)
 
    s = []
    k = len(frequencies)
    for i in range(k):               
        f = frequencies[i]
        sf = lambda t: volume * math.sin((2 * math.pi * f * t / sample_rate))
        s.append(sf)
 
    z = lambda t: sum([int(x(t) * (2**(8-k)-1) + 2**(8-k)) for x in s])
    samples = (z(t) for t in xrange(n_samples)) # scale to 0-255
 
    if duration >= 1:
        sample_size = sample_rate
    else:
        sample_size = int(sample_rate * duration)
 
    for buf in izip(*[samples]*sample_size): # write several samples at a time
        stream.write(bytes(bytearray(buf)))
 
    # fill remainder of frameset with silence
    stream.write(b'\x80' * restframes)
 
    stream.stop_stream()
    stream.close()
    p.terminate()   

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import sounds
 
DTMF_TABLE = {
    "1": [1209, 697],
    "2": [1336, 697],
    "3": [1477, 697],
    "A": [1633, 697],
    "4": [1209, 770],
    "5": [1336, 770],
    "6": [1477, 770],
    "B": [1633, 770],
    "7": [1209, 852],
    "8": [1336, 852],
    "9": [1477, 852],
    "C": [1633, 852],
    "*": [1209, 941],
    "0": [1336, 941],
    "#": [1477, 941],
    "D": [1633, 941],
    "dial_tone": [350, 440]
}
 
#sounds.sine_tone(2000, 0.5, volume=0.25)
#sounds.square_tone(2000, 0.25, volume=0.25)
 
sounds.dtmf_tone(*DTMF_TABLE['dial_tone'], 2, volume=0.25) # dial tone: 350, 400
sounds.dtmf_tone(*DTMF_TABLE['6'], 0.25, volume=0.25)
sounds.dtmf_tone(*DTMF_TABLE['1'], 0.25, volume=0.25)
sounds.dtmf_tone(*DTMF_TABLE['0'], 0.25, volume=0.25)
sounds.dtmf_tone(*DTMF_TABLE['4'], 0.25, volume=0.25)
sounds.dtmf_tone(*DTMF_TABLE['0'], 0.25, volume=0.25)
sounds.dtmf_tone(*DTMF_TABLE['9'], 0.25, volume=0.25)
sounds.dtmf_tone(*DTMF_TABLE['3'], 0.25, volume=0.25)
sounds.dtmf_tone(*DTMF_TABLE['2'], 0.25, volume=0.25)
sounds.dtmf_tone(*DTMF_TABLE['6'], 0.25, volume=0.25)
sounds.dtmf_tone(*DTMF_TABLE['8'], 0.25, volume=0.25)
 
C = 261.63
E = 329.63
G = 392
sounds.sine_chord([C, E, G], 1, volume=0.25)

Questions

  1. Save these two files into your program (call the first one sounds.py since we import sounds in the second file!) and run it.
  2. Look up DTMF tones; what is this program doing?
  3. Make a song with some tones and play the program. You might try this with the microbit to see what kinds of notes it plays when you play a melody; you can copy the frequencies into this program.

Submission

I encourage you to submit your answers to the questions (and ask your own questions!) using the Class Activity Questions discussion board. You may also respond to questions or comments made by others, or ask follow-up questions there. Answer any reflective prompt questions in the Reflective Journal section of your OneNote Classroom personal section. You can find the link to the class notebook on the syllabus.