To run these examples, first download
cmu_112_graphics.py
and be sure it is in the same folder as the file you are running.
That file has version numbers. As we release updates,
be sure you are using the most-recent version!
This is a new animation framework. Which means it may have
some bugs that we will fix as we go. Also, it is similar to previous
semesters, but different in important ways. Be aware of this if you
are reviewing previous semesters' materials!
As with Tkinter graphics, the examples here will not
run using Brython in your browser.
Installing required modules (PIL/Pillow and Requests)
To use cmu_112_graphics.py, you need
to have some modules installed. If they are not installed, you will see
a message like this (or a similar one for "requests" instead of "PIL"):
**********************************************************
** Cannot import PIL -- it seems you need to install pillow
** This may result in limited functionality or even a runtime error.
**********************************************************
You can try to use 'pip' to install the missing modules, but it can be
complicated making sure you are installing these modules for the same version
of Python that you are running. Here are some more-reliable steps
that should work for you:
For Windows:
Run this Python code block in your main Python file (it will print the commands you need to paste into your command prompt):
Copy-paste each of the two commands printed in step 1 into the
command prompt you opened in step 2.
If you see a lock and a password is requested, type in the same password that you use to log into your computer.
Close the terminal and close Python.
Re-open Python, and you're set (hopefully)!
If these steps do not work for you, please go to OH and we will be happy
to assist.
Keyboard Shortcuts
from cmu_112_graphics import *
defappStarted(app):
app.counter = 0deftimerFired(app):
app.counter += 1defredrawAll(app, canvas):
canvas.create_text(200, 50,
text='Keyboard Shortcut Demo', fill='black')
canvas.create_text(200, 100,
text='Press control-p to pause/unpause', fill='black')
canvas.create_text(200, 150,
text='Press control-s to save a snapshot', fill='black')
canvas.create_text(200, 200,
text='Press control-q to quit', fill='black')
canvas.create_text(200, 250,
text='Press control-x to hard exit', fill='black')
canvas.create_text(200, 300, text=f'{app.counter}', fill='black')
runApp(width=400, height=400) # quit still runs next one, exit does not
runApp(width=600, height=600)
Events
from cmu_112_graphics import *
defappStarted(app):
app.messages = ['appStarted']
defappStopped(app):
app.messages.append('appStopped')
print('appStopped!')
defkeyPressed(app, event):
app.messages.append('keyPressed: ' + event.key)
defkeyReleased(app, event):
app.messages.append('keyReleased: ' + event.key)
defmousePressed(app, event):
app.messages.append(f'mousePressed at {(event.x, event.y)}')
defmouseReleased(app, event):
app.messages.append(f'mouseReleased at {(event.x, event.y)}')
defmouseMoved(app, event):
app.messages.append(f'mouseMoved at {(event.x, event.y)}')
defmouseDragged(app, event):
app.messages.append(f'mouseDragged at {(event.x, event.y)}')
defsizeChanged(app):
app.messages.append(f'sizeChanged to {(app.width, app.height)}')
defredrawAll(app, canvas):
font = 'Arial 20 bold'
canvas.create_text(app.width/2, 30, text='Events Demo',
font=font, fill='black')
n = min(10, len(app.messages))
i0 = len(app.messages)-n
for i in range(i0, len(app.messages)):
canvas.create_text(app.width/2, 100+50*(i-i0),
text=f'#{i}: {app.messages[i]}',
font=font, fill='black')
runApp(width=600, height=600)
Input and Output Methods
# This demos app.getUserInput(prompt) and app.showMessage(message)from cmu_112_graphics import *
defappStarted(app):
app.message = 'Click the mouse to enter your name!'defmousePressed(app, event):
name = app.getUserInput('What is your name?')
if (name == None):
app.message = 'You canceled!'else:
app.showMessage('You entered: ' + name)
app.message = f'Hi, {name}!'defredrawAll(app, canvas):
font = 'Arial 24 bold'
canvas.create_text(app.width/2, app.height/2,
text=app.message, font=font, fill='black')
runApp(width=500, height=300)
Image Methods
loadImage and scaleImage (using url)
# This demos loadImage and scaleImage from a urlfrom cmu_112_graphics import *
defappStarted(app):
url = 'https://tinyurl.com/great-pitch-gif'
app.image1 = app.loadImage(url)
app.image2 = app.scaleImage(app.image1, 2/3)
defredrawAll(app, canvas):
canvas.create_image(200, 300, image=ImageTk.PhotoImage(app.image1))
canvas.create_image(500, 300, image=ImageTk.PhotoImage(app.image2))
runApp(width=700, height=600)
loadImage and scaleImage (using local file)
Let's do that again, only this time using an image stored locally.
To run this version, you must first download this image
(testImage2.gif)
and save it in the same folder as your Python code:
# This demos loadImage and scaleImage from a local filefrom cmu_112_graphics import *
defappStarted(app):
app.image1 = app.loadImage('testImage2.gif')
app.image2 = app.scaleImage(app.image1, 2/3)
defredrawAll(app, canvas):
canvas.create_image(200, 300, image=ImageTk.PhotoImage(app.image1))
canvas.create_image(500, 300, image=ImageTk.PhotoImage(app.image2))
runApp(width=700, height=600)
# This demos using transpose to flip an imagefrom cmu_112_graphics import *
defappStarted(app):
url = 'https://tinyurl.com/great-pitch-gif'
app.image1 = app.loadImage(url)
app.image2 = app.image1.transpose(Image.FLIP_LEFT_RIGHT)
defredrawAll(app, canvas):
canvas.create_image(200, 300, image=ImageTk.PhotoImage(app.image1))
canvas.create_image(500, 300, image=ImageTk.PhotoImage(app.image2))
runApp(width=700, height=600)
Using getpixel and putpixel
# This demos using getpixel and putpixelfrom cmu_112_graphics import *
defappStarted(app):
url = 'https://tinyurl.com/great-pitch-gif'
app.image1 = app.loadImage(url)
# now let's make a copy that only uses the red part of each rgb pixel:
app.image1 = app.image1.convert('RGB')
app.image2 = Image.new(mode='RGB', size=app.image1.size)
for x in range(app.image2.width):
for y in range(app.image2.height):
r,g,b = app.image1.getpixel((x,y))
app.image2.putpixel((x,y),(r,0,0))
defredrawAll(app, canvas):
canvas.create_image(200, 300, image=ImageTk.PhotoImage(app.image1))
canvas.create_image(500, 300, image=ImageTk.PhotoImage(app.image2))
runApp(width=700, height=600)
Making new images and using ImageDraw
Now let's look see how to create a new image (rather than load an image),
and to use the ImageDraw module to draw (lines, etc) on an image:
# This demos creating a new blank image and using PIL ImageDrawfrom cmu_112_graphics import *
defappStarted(app):
imageWidth, imageHeight = app.width//3, app.height//2
bgColor = (0, 255, 255) # cyan
app.image1 = Image.new('RGB', (imageWidth, imageHeight), bgColor)
# Now that we created the image, let's use ImageDraw to draw in it# See https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html
draw = ImageDraw.Draw(app.image1)
draw.line((0, 0, imageWidth, imageHeight), width=10, fill=(255, 0, 0))
draw.line((0, imageHeight, imageWidth, 0), width=10, fill=(0, 0, 255))
# And now we will create a scaled copy to show this is a normal image
app.image2 = app.scaleImage(app.image1, 2/3)
defredrawAll(app, canvas):
canvas.create_image(200, 300, image=ImageTk.PhotoImage(app.image1))
canvas.create_image(500, 300, image=ImageTk.PhotoImage(app.image2))
runApp(width=700, height=600)
getSnapshot and saveSnapshot
Now let's look at getSnapshot and saveSnapshot:
Note: this feature does not work especially well on some platforms.
# This demos getSnapshot and saveSnapshotfrom cmu_112_graphics import *
defappStarted(app):
app.image = NonedefkeyPressed(app, event):if (event.key == 'g'):
snapshotImage = app.getSnapshot()
app.image = app.scaleImage(snapshotImage, 0.4)
elif (event.key == 's'):
app.saveSnapshot()
defredrawAll(app, canvas):
canvas.create_text(350, 20, text='Press g to getSnapshot', fill='black')
canvas.create_text(350, 40, text='Press s to saveSnapshot', fill='black')
canvas.create_rectangle(50, 100, 250, 500, fill='cyan')
if (app.image != None):
canvas.create_image(525, 300, image=ImageTk.PhotoImage(app.image))
runApp(width=700, height=600)
Spritesheets using Pillow/PIL methods (such as image.crop)
We can use
Pillow/PIL methods
such
as image.crop() (among many others!), which we use here to use a spritestrip (a kind
of spritesheet) by cropping each sub-image from this single image:
# This demos sprites using Pillow/PIL images# See here for more details:# https://pillow.readthedocs.io/en/stable/reference/Image.html# This uses a spritestrip from this tutorial:# https://www.codeandweb.com/texturepacker/tutorials/how-to-create-a-sprite-sheetfrom cmu_112_graphics import *
defappStarted(app):
url = 'http://www.cs.cmu.edu/~112/notes/sample-spritestrip.png'
spritestrip = app.loadImage(url)
app.sprites = [ ]
for i in range(6):
sprite = spritestrip.crop((30+260*i, 30, 230+260*i, 250))
app.sprites.append(sprite)
app.spriteCounter = 0deftimerFired(app):
app.spriteCounter = (1 + app.spriteCounter) % len(app.sprites)
defredrawAll(app, canvas):
sprite = app.sprites[app.spriteCounter]
canvas.create_image(200, 200, image=ImageTk.PhotoImage(sprite))
runApp(width=400, height=400)
Animated Gifs
We can use animated gifs, too! For example, download this animated gif:
Then run this:
from cmu_112_graphics import *
defappStarted(app):
app.spritePhotoImages = loadAnimatedGif('sample-animatedGif.gif')
app.spriteCounter = 0defloadAnimatedGif(path):# load first sprite outside of try/except to raise file-related exceptions
spritePhotoImages = [ PhotoImage(file=path, format='gif -index 0') ]
i = 1whileTrue:
try:
spritePhotoImages.append(PhotoImage(file=path,
format=f'gif -index {i}'))
i += 1except Exception as e:
return spritePhotoImages
deftimerFired(app):
app.spriteCounter = (1 + app.spriteCounter) % len(app.spritePhotoImages)
defredrawAll(app, canvas):
photoImage = app.spritePhotoImages[app.spriteCounter]
canvas.create_image(200, 200, image=photoImage)
runApp(width=400, height=400)
Caching PhotoImages for increased speed
If you are using a lot of images, then the call to ImageTk.PhotoImage(image)
can slow things down, so we can cache the results of that, like so:
# This demos caching PhotoImages for increased speed# when using a LOT of images (2500 here)from cmu_112_graphics import *
import time
defmake2dList(rows, cols):return [ ([0] * cols) for row in range(rows) ]
defappStarted(app):
url = 'https://tinyurl.com/great-pitch-gif'
app.image1 = app.loadImage(url)
app.margin = 20
app.rows = app.cols = 50
app.images = make2dList(app.rows, app.cols)
for row in range(app.rows):
for col in range(app.cols):
app.images[row][col] = app.scaleImage(app.image1, 0.1)
app.counter = 0
app.timerDelay = 1
app.timerResult = 'Counting to 10...'
app.useCachedImages = False
resetTimer(app)
defresetTimer(app):
app.time0 = time.time()
app.counter = 0deftimerFired(app):
app.counter += 1if (app.counter == 10):
duration = time.time() - app.time0
app.timerResult = f'Last time to 10: {round(duration,1)}s'
app.useCachedImages = not app.useCachedImages
resetTimer(app)
# from www.cs.cmu.edu/~112/notes/notes-animations-part1.html#exampleGridsdefgetCellBounds(app, row, col):# aka "modelToView"# returns (x0, y0, x1, y1) corners/bounding box of given cell in grid
gridWidth = app.width - 2*app.margin
gridHeight = app.height - 2*app.margin
columnWidth = gridWidth / app.cols
rowHeight = gridHeight / app.rows
x0 = app.margin + col * columnWidth
x1 = app.margin + (col+1) * columnWidth
y0 = app.margin + row * rowHeight
y1 = app.margin + (row+1) * rowHeight
return (x0, y0, x1, y1)
defgetCachedPhotoImage(app, image):# stores a cached version of the PhotoImage in the PIL/Pillow imageif ('cachedPhotoImage'notin image.__dict__):
image.cachedPhotoImage = ImageTk.PhotoImage(image)
return image.cachedPhotoImage
defredrawAll(app, canvas):for row in range(app.rows):
for col in range(app.cols):
(x0, y0, x1, y1) = getCellBounds(app, row, col)
cx, cy = (x0 + x1)/2, (y0 + y1)/2
image = app.images[row][col]
if (app.useCachedImages):
photoImage = getCachedPhotoImage(app, image)
else:
photoImage = ImageTk.PhotoImage(image)
canvas.create_image(cx, cy, image=photoImage)
canvas.create_rectangle(app.width/2-250, app.height/2-100,
app.width/2+250, app.height/2+100,
fill='lightYellow')
canvas.create_text(app.width/2, app.height/2-50,
text=f'Using cached images = {app.useCachedImages}',
font='Arial 30 bold', fill='black')
canvas.create_text(app.width/2, app.height/2,
text=app.timerResult,
font='Arial 30 bold', fill='black')
canvas.create_text(app.width/2, app.height/2+50,
text=str(app.counter),
font='Arial 30 bold', fill='black')
runApp(width=700, height=600)
Using Modes (aka Screens)
# This demos using modes (aka screens).from cmu_112_graphics import *
import random
########################################### Splash Screen Mode##########################################defsplashScreenMode_redrawAll(app, canvas):
font = 'Arial 26 bold'
canvas.create_text(app.width/2, 150, text='This demos a ModalApp!',
font=font, fill='black')
canvas.create_text(app.width/2, 200, text='This is a modal splash screen!',
font=font, fill='black')
canvas.create_text(app.width/2, 250, text='Press any key for the game!',
font=font, fill='black')
defsplashScreenMode_keyPressed(app, event):
app.mode = 'gameMode'########################################### Game Mode##########################################defgameMode_redrawAll(app, canvas):
font = 'Arial 26 bold'
canvas.create_text(app.width/2, 20, text=f'Score: {app.score}',
font=font, fill='black')
canvas.create_text(app.width/2, 60, text='Click on the dot!',
font=font, fill='black')
canvas.create_text(app.width/2, 100, text='Press h for help screen!',
font=font, fill='black')
canvas.create_text(app.width/2, 140, text='Press v for an MVC Violation!',
font=font, fill='black')
canvas.create_oval(app.x-app.r, app.y-app.r, app.x+app.r, app.y+app.r,
fill=app.color)
if app.makeAnMVCViolation:
app.ohNo = 'This is an MVC Violation!'defgameMode_timerFired(app):
moveDot(app)
defgameMode_mousePressed(app, event):
d = ((app.x - event.x)**2 + (app.y - event.y)**2)**0.5if (d <= app.r):
app.score += 1
randomizeDot(app)
elif (app.score > 0):
app.score -= 1defgameMode_keyPressed(app, event):if (event.key == 'h'):
app.mode = 'helpMode'elif (event.key == 'v'):
app.makeAnMVCViolation = True########################################### Help Mode##########################################defhelpMode_redrawAll(app, canvas):
font = 'Arial 26 bold'
canvas.create_text(app.width/2, 150, text='This is the help screen!',
font=font, fill='black')
canvas.create_text(app.width/2, 250, text='(Insert helpful message here)',
font=font, fill='black')
canvas.create_text(app.width/2, 350, text='Press any key to return to the game!',
font=font, fill='black')
defhelpMode_keyPressed(app, event):
app.mode = 'gameMode'########################################### Main App##########################################defappStarted(app):
app.mode = 'splashScreenMode'
app.score = 0
app.timerDelay = 50
app.makeAnMVCViolation = False
randomizeDot(app)
defrandomizeDot(app):
app.x = random.randint(20, app.width-20)
app.y = random.randint(20, app.height-20)
app.r = random.randint(10, 20)
app.color = random.choice(['red', 'orange', 'yellow', 'green', 'blue'])
app.dx = random.choice([+1,-1])*random.randint(3,6)
app.dy = random.choice([+1,-1])*random.randint(3,6)
defmoveDot(app):
app.x += app.dx
if (app.x < 0) or (app.x > app.width): app.dx = -app.dx
app.y += app.dy
if (app.y < 0) or (app.y > app.height): app.dy = -app.dy
runApp(width=600, height=500)
Example: Sidescrollers 1-3
SideScroller1
# SideScroller1:from cmu_112_graphics import *
import random
defappStarted(app):
app.scrollX = 0
app.dots = [(random.randrange(app.width),
random.randrange(60, app.height)) for _ in range(50)]
defkeyPressed(app, event):if (event.key == "Left"): app.scrollX -= 5elif (event.key == "Right"): app.scrollX += 5defredrawAll(app, canvas):# draw the player fixed to the center of the scrolled canvas
cx, cy, r = app.width/2, app.height/2, 10
canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='cyan')
# draw the dots, shifted by the scrollX offsetfor (cx, cy) in app.dots:
cx -= app.scrollX # <-- This is where we scroll each dot!!!
canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='lightGreen')
# draw the x and y axes
x = app.width/2 - app.scrollX # <-- This is where we scroll the axis!
y = app.height/2
canvas.create_line(x, 0, x, app.height)
canvas.create_line(0, y, app.width, y)
# draw the instructions and the current scrollX
x = app.width/2
canvas.create_text(x, 20, text='Use arrows to move left or right',
fill='black')
canvas.create_text(x, 40, text=f'app.scrollX = {app.scrollX}',
fill='black')
runApp(width=300, height=300)
SideScroller2
# SideScroller2:# Now with a scroll margin, so player does not stay fixed# at the center of the scrolled canvas, and we only scroll# if the player's center (in this case) gets closer than the# margin to the left or right edge of the canvas.from cmu_112_graphics import *
import random
defappStarted(app):
app.scrollX = 0
app.scrollMargin = 50
app.playerX = app.width//2# player's center
app.dots = [(random.randrange(app.width),
random.randrange(60, app.height)) for _ in range(50)]
defmakePlayerVisible(app):# scroll to make player visible as neededif (app.playerX < app.scrollX + app.scrollMargin):
app.scrollX = app.playerX - app.scrollMargin
if (app.playerX > app.scrollX + app.width - app.scrollMargin):
app.scrollX = app.playerX - app.width + app.scrollMargin
defmovePlayer(app, dx, dy):
app.playerX += dx
makePlayerVisible(app)
defkeyPressed(app, event):if (event.key == "Left"): movePlayer(app, -5, 0)
elif (event.key == "Right"): movePlayer(app, +5, 0)
defredrawAll(app, canvas):# draw the player, shifted by the scrollX offset
cx, cy, r = app.playerX, app.height/2, 10
cx -= app.scrollX # <-- This is where we scroll the player!!!
canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='cyan')
# draw the dots, shifted by the scrollX offsetfor (cx, cy) in app.dots:
cx -= app.scrollX # <-- This is where we scroll each dot!!!
canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='lightGreen')
# draw the x and y axes
x = app.width/2 - app.scrollX # <-- This is where we scroll the axis!
y = app.height/2
canvas.create_line(x, 0, x, app.height)
canvas.create_line(0, y, app.width, y)
# draw the instructions and the current scrollX
x = app.width/2
canvas.create_text(x, 20, text='Use arrows to move left or right',
fill='black')
canvas.create_text(x, 40, text=f'app.scrollX = {app.scrollX}',
fill='black')
runApp(width=300, height=300)
SideScroller3
# SideScroller3:# Now with walls that track when you run into them (but# ignore while you are still crossing them).from cmu_112_graphics import *
defappStarted(app):
app.scrollX = 0
app.scrollMargin = 50
app.playerX = app.scrollMargin
app.playerY = 0
app.playerWidth = 10
app.playerHeight = 20
app.walls = 5
app.wallPoints = [0]*app.walls
app.wallWidth = 20
app.wallHeight = 40
app.wallSpacing = 90# wall left edges are at 90, 180, 270,...
app.currentWallHit = -1# start out not hitting a walldefgetPlayerBounds(app):# returns absolute bounds, not taking scrollX into account
(x0, y1) = (app.playerX, app.height/2 - app.playerY)
(x1, y0) = (x0 + app.playerWidth, y1 - app.playerHeight)
return (x0, y0, x1, y1)
defgetWallBounds(app, wall):# returns absolute bounds, not taking scrollX into account
(x0, y1) = ((1+wall) * app.wallSpacing, app.height/2)
(x1, y0) = (x0 + app.wallWidth, y1 - app.wallHeight)
return (x0, y0, x1, y1)
defgetWallHit(app):# return wall that player is currently hitting# note: this should be optimized to only check the walls that are visible# or even just directly compute the wall without a loop
playerBounds = getPlayerBounds(app)
for wall in range(app.walls):
wallBounds = getWallBounds(app, wall)
if (boundsIntersect(app, playerBounds, wallBounds) == True):
return wall
return -1defboundsIntersect(app, boundsA, boundsB):# return l2<=r1 and t2<=b1 and l1<=r2 and t1<=b2
(ax0, ay0, ax1, ay1) = boundsA
(bx0, by0, bx1, by1) = boundsB
return ((ax1 >= bx0) and (bx1 >= ax0) and
(ay1 >= by0) and (by1 >= ay0))
defcheckForNewWallHit(app):# check if we are hitting a new wall for the first time
wall = getWallHit(app)
if (wall != app.currentWallHit):
app.currentWallHit = wall
if (wall >= 0):
app.wallPoints[wall] += 1defmakePlayerVisible(app):# scroll to make player visible as neededif (app.playerX < app.scrollX + app.scrollMargin):
app.scrollX = app.playerX - app.scrollMargin
if (app.playerX > app.scrollX + app.width - app.scrollMargin):
app.scrollX = app.playerX - app.width + app.scrollMargin
defmovePlayer(app, dx, dy):
app.playerX += dx
app.playerY += dy
makePlayerVisible(app)
checkForNewWallHit(app)
defsizeChanged(app):
makePlayerVisible(app)
defmousePressed(app, event):
app.playerX = event.x + app.scrollX
checkForNewWallHit(app)
defkeyPressed(app, event):if (event.key == "Left"): movePlayer(app, -5, 0)
elif (event.key == "Right"): movePlayer(app, +5, 0)
elif (event.key == "Up"): movePlayer(app, 0, +5)
elif (event.key == "Down"): movePlayer(app, 0, -5)
defredrawAll(app, canvas):# draw the base line
lineY = app.height/2
lineHeight = 5
canvas.create_rectangle(0, lineY, app.width, lineY+lineHeight,fill="black")
# draw the walls# (Note: should optimize to only consider walls that can be visible now!)
sx = app.scrollX
for wall in range(app.walls):
(x0, y0, x1, y1) = getWallBounds(app, wall)
fill = "orange"if (wall == app.currentWallHit) else"pink"
canvas.create_rectangle(x0-sx, y0, x1-sx, y1, fill=fill)
(cx, cy) = ((x0+x1)/2 - sx, (y0 + y1)/2)
canvas.create_text(cx, cy, text=str(app.wallPoints[wall]), fill='black')
cy = lineY + 5
canvas.create_text(cx, cy, text=str(wall), anchor=N, fill='black')
# draw the player
(x0, y0, x1, y1) = getPlayerBounds(app)
canvas.create_oval(x0 - sx, y0, x1 - sx, y1, fill="cyan")
# draw the instructions
msg = "Use arrows to move, hit walls to score"
canvas.create_text(app.width/2, 20, text=msg, fill='black')
runApp(width=300, height=300)
Playing Sounds
We will wrap this up with two ways to play sounds. There are more sophisticated
ways to play sounds. This just meant to be a quick way to quickly get some
sounds in your app.
In order to run these examples, first download
button.mp3
Playing Sounds with Pygame
Note that you may not use Pygame in general before MVP, but you may
use Pygame even before then if it is strictly used for sounds in this way:
# This demos playing sounds using Pygame:from cmu_112_graphics import *
import pygame
classSound(object):def__init__(self, path):
self.path = path
self.loops = 1
pygame.mixer.music.load(path)
# Returns True if the sound is currently playingdefisPlaying(self):return bool(pygame.mixer.music.get_busy())
# Loops = number of times to loop the sound.# If loops = 1 or 1, play it once.# If loops > 1, play it loops + 1 times.# If loops = -1, loop forever.defstart(self, loops=1):
self.loops = loops
pygame.mixer.music.play(loops=loops)
# Stops the current sound from playingdefstop(self):
pygame.mixer.music.stop()
defappStarted(app):
pygame.mixer.init()
app.sound = Sound("button.mp3")
defappStopped(app):
app.sound.stop()
defkeyPressed(app, event):if (event.key == 's'):
if app.sound.isPlaying(): app.sound.stop()
else: app.sound.start()
elif (event.key == 'l'):
app.sound.start(loops=-1)
elif event.key.isdigit():
app.sound.start(loops=int(event.key))
deftimerFired(app):passdefredrawAll(app, canvas):
canvas.create_text(app.width/2, app.height/2-60,
text=f'{app.sound.path} (loops = {app.sound.loops})',
font='Arial 30 bold', fill='black')
canvas.create_text(app.width/2, app.height/2-20,
text=f'sound is playing = {app.sound.isPlaying()}',
font='Arial 30 bold', fill='black')
canvas.create_text(app.width/2, app.height/2+20,
text='Press s to start/stop sound',
font='Arial 30 bold', fill='black')
canvas.create_text(app.width/2, app.height/2+60,
text='Press l to loop sound',
font='Arial 30 bold', fill='black')
runApp(width=600, height=200)
Playing Sounds with afplay (Mac only)
If Pygame does not work for you, here is an alternative that only runs on Macs
and is a bit slower, but runs without Pygame:
# This demos playing sounds using afplay (Mac only)import subprocess, threading, time
from cmu_112_graphics import *
classSound(object):def__init__(self, path):
self.path = path
self.process = None
self.loop = FalsedefisPlaying(self):return (self.process isnotNone)
defcheckProcess(self):# This method is run inside a separate thread# so the main thread does not hang while this runs.while self.process isnotNone:
if (self.process.poll() isnotNone):
self.process = Noneelse:
time.sleep(0.2)
if self.loop:
self.start(loop=True)
defstart(self, loop=False):
self.stop()
self.loop = loop
self.process = subprocess.Popen(['afplay', self.path])
threading.Thread(target=self.checkProcess).start()
defstop(self):
process = self.process
self.loop = False
self.process = Noneif (process isnotNone):
try: process.kill()
except: passdefappStarted(app):
app.sound = Sound('button.mp3')
defappStopped(app):
app.sound.stop()
defkeyPressed(app, event):if (event.key == 's'):
if app.sound.isPlaying(): app.sound.stop()
else: app.sound.start()
elif (event.key == 'l'):
app.sound.start(loop=True)
defredrawAll(app, canvas):
canvas.create_text(app.width/2, app.height/2-60,
text=f'{app.sound.path} (loop = {app.sound.loop})',
font='Arial 30 bold', fill='black')
canvas.create_text(app.width/2, app.height/2-20,
text=f'sound is playing = {app.sound.isPlaying()}',
font='Arial 30 bold', fill='black')
canvas.create_text(app.width/2, app.height/2+20,
text='Press s to start/stop sound',
font='Arial 30 bold', fill='black')
canvas.create_text(app.width/2, app.height/2+60,
text='Press l to loop sound',
font='Arial 30 bold', fill='black')
runApp(width=600, height=200)