""" 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 116""" initial = 'C' scale = ['C','G','D','A','E','B'] def divide(division, cache, depth_limit=2): """ Elaborate a division """ if not isinstance(division, str): if len(division) == 1: return divide(division[0], cache, depth_limit) return [ divide(division[0], cache, depth_limit-1) ] + \ [ divide(item, cache) for item in division[1:] ] if depth_limit <= 0: return division if division not in cache: i = scale.index(division.upper()) pool = scale[max(0,i-1):min(len(scale),i+2)] pool += [ i.lower() for i in pool ] pool = [ random.choice(pool), random.choice(pool) ] cache[division] = [ [ random.choice(pool) for i in xrange(random.choice([1,2,3])) ] for j in xrange(random.choice([1,2])) ] return random.choice( cache[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.6) 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(initial, 3) # Flush final beat show_note('z',1,64) f.close() os.system( 'abc2midi drum.abc >/dev/null && timidity drum1.mid' )