OneLoopDrumMachine
One Loop Drum Machine
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:
#!python
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:
#!python
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 |
attachment:beatpattern_wb.png
#!python
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:
#!python
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).
#!python
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)