OneLoopDrumMachine

From XPUB & Lens-Based wiki

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:

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


File:Beatpattern wb.png

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)