"""

   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'
)

