""" Generation of drum beats by division, a la Arbeau Creates an "abc" file that is then converted to MIDI by abc2midi and played by timidity (with eawpatches). """ import os, random f = open('drum.abc','wt') # ABC Song header print >> f, """\ X:1 T:Divisions M:1/1 Q:20 K:C %%MIDI program 115""" def divide(division): """ Elaborate a division """ if not isinstance(division, str): return [ divide(item) for item in division ] n = random.choice([1,2,3]) pool = [ random.choice(['C','C','C','e','g','g']) for i in xrange(n-1) ] + [division] return [ random.choice(pool) for i in xrange(n-1) ] + [ division ] last_note = 'z' last_vol = 64 def show_note(note, divisor, volume): """ Ass-backwards note output, since we want to specify the delay *before* the note, not after. """ global last_note, last_vol print >> f, '%%%%MIDI beat %d %d %d 1' % (last_vol,last_vol,last_vol) print >> f, '%s/%d' % (last_note, divisor) last_note = note last_vol = volume def render_division(division, divisor, volume=1.0): if isinstance(division, str): show_note(division, divisor, int(volume*127)) else: for part in division[:-1]: render_division(part, divisor*len(division), volume*0.5) render_division(division[-1], divisor*len(division), volume) def norm_form(x): """ Used to avoid repeating ourselves """ if isinstance(x,str): return None if len(x) == 1: return norm_form(x[0]) return tuple([ norm_form(item) for item in x ]) seen = { } def new_division(d): while True: d = divide(d) if norm_form(d) not in seen: seen[norm_form(d)] = True return d def subsong(d, depth): """ Generate a set of elaborations on a beat pattern. """ print d seen[norm_form(d)] = True render_division(d,1) render_division(d,1) for i in xrange(2): if depth: subsong(new_division(d), depth-1) render_division(d,1) subsong('C', 3) # Flush final beat show_note('z',1,64) f.close() os.system( 'abc2midi drum.abc && timidity drum1.mid' )