OneLoopDrumMachine

From XPUB & Lens-Based wiki

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/

Hydrogen

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)