OneLoopDrumMachine
using PyGame
Drum machines (and software simulating them), typically work with spreadsheet-like timeline views that allow you to trigger playback of different percussive sounds as a pattern in time.
Example drum machine software: http://www.hydrogen-music.org/
In a simple single-loop program, there is no explicit timeline. So you have to simulate it using variables as state.
For instance, you might create a variable called beat to represent what portion (column) of the timeline / pattern you are currently in.
Programming a simple drum machine in PyGame
First, you need some sound samples. You could pull them from a program like Hydrogen (just make sure they're either OGG or raw wav format for PyGame to load. Or you can use ["sox"] to generate some retro 8bit sounds.
So playing the sound with PyGame:
import pygame
pygame.init()
pygame.mixer.init()
sweep = pygame.mixer.Sound("sweep_up.wav")
sweep.play()
while True:
pass
Now with a clock to control the rhythm:
import pygame
pygame.init()
pygame.mixer.init()
sweep = pygame.mixer.Sound("sweep.wav")
cow = pygame.mixer.Sound("sweep_up_long.wav")
clock = pygame.time.Clock()
bpm = 180
fps = float(bpm) / 60
while True:
cow.play() ## plays in background (like & )
clock.tick(fps)
Exercise: How, with this single loop, might you implement a simple drum pattern such as:
bell | x | x | |||
beep | x | x | x | x |
import pygame
pygame.init()
pygame.mixer.init()
beep = pygame.mixer.Sound("08.wav")
noise = pygame.mixer.Sound("noise.wav")
clock = pygame.time.Clock()
bpm = 120
fps = float(bpm) / 60
beat = 0
while True:
print beat
beep.play()
# lbeat = beat % 4
if beat == 0 or beat==2:
noise.play()
beat += 1
if beat==4: beat=0
clock.tick(fps)
This is a buggy version of an attempt to make the rhythm loop more responsive to the keyboard:
import sys, pygame
from pygame.locals import *
pygame.init()
pygame.mixer.init()
beep = pygame.mixer.Sound("08.wav")
noise = pygame.mixer.Sound("noise.wav")
cowbell = pygame.mixer.Sound("hi.wav")
clock = pygame.time.Clock()
bpm = 120
fps = float(bpm) / 60
beat = 0
screen = pygame.display.set_mode((320, 240))
frame = 0
playCow = False
while True:
print frame, beat
# HANDLE EVENTS
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
if event.type == KEYDOWN and event.key == K_SPACE:
cowbell.play()
# print beat
beep.play()
# lbeat = beat % 4
if beat == 0 or beat==2:
noise.play()
if frame == 15:
# UPDATE BEAT
beat += 1
if beat==4: beat=0
frame = 0
# add frame counter
frame += 1
clock.tick(30)
The solution is to move the "drum machine" code (the two play commands) inside the if block (so only when the beat changes, are new sounds triggered).
import sys, pygame
from pygame.locals import *
pygame.init()
pygame.mixer.init()
beep = pygame.mixer.Sound("08.wav")
noise = pygame.mixer.Sound("noise.wav")
cowbell = pygame.mixer.Sound("hi.wav")
clock = pygame.time.Clock()
bpm = 120
fps = float(bpm) / 60
beat = 0
screen = pygame.display.set_mode((320, 240))
frame = 0
playCow = False
while True:
print frame, beat
# HANDLE EVENTS
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
if event.type == KEYDOWN and event.key == K_SPACE:
cowbell.play()
if frame == 15:
# UPDATE BEAT
beat += 1
if beat==4: beat=0
frame = 0
# print beat
beep.play()
# lbeat = beat % 4
if beat == 0 or beat==2:
noise.play()
# add frame counter
frame += 1
clock.tick(30)