def simple_transpose(c, full, interval, up):
    # determine whether can transpose by a full octave (only want to 
    # transpose to nearest octave)
    # TODO: introduce recursive calls so track transposes itself 
    # until it is within range, using full octave jumps unless 
    # necessary to do half steps
    if (full):
        for i in range(len(c)):
            for j in range(len(c[i])):
                for k in range(len(c[i][j])):
                    for l in range(len(c[i][j][k][2])):
                        if (up):
                            c[i][j][k][2][l].octave_up()  
                        else:
                            c[i][j][k][2][l].octave_down()  
    
    else: 
        for i in range(len(c)):
            for j in range(len(c[i])):
                for k in range(len(c[i][j])):
                    for l in range(len(c[i][j][k][2])):
                        n = Note()
                        if (up):
                            c[i][j][k][2][l] = n.from_int(Note.__int__(c[i][j][k][2][l]) + interval) 
                        else:
                            c[i][j][k][2][l] = n.from_int(Note.__int__(c[i][j][k][2][l]) - interval) 
    '''
    else:
        for i in range(len(c)):
            c[i] = c[i].transpose(interval, up)
    '''
             
    return c
def notes_to_lsa_bin(notes, xylo, lsa_ordering):
    # convert to int so that sharps of one note and flat of next mean 
    # the same thing
    keys = [ Note.__int__(Note(n)) for n in lsa_ordering ]
    vals = range(len(lsa_ordering))
    # first lsa's note mapped to 0, second lsa's note mapped to 1, ...
    lsa_dict = dict(zip(keys, vals))
    bnry = ['0'] * len(lsa_dict)
    for note in notes:
        # flip LSA to 'on' state for that note
        bnry[lsa_dict[Note.__int__(note)]] = '1'
    # so that number of leading zeros do not affect place of 'on' LSAs
    bnry.reverse()
    bnry = ''.join(bnry)
    # int whose binary represents the on and off states of all LSAs
    # at a given time
    combo = int(bnry, 2)
    return combo
def transposer(c, lowest, highest, xylo):
    '''
    5 cases:
        1) lowest < xylo.range[0] and highest > xylo.range[1]
        2) lowest < xylo.range[0] and highest < xylo.range[0]
        3) lowest > xylo.range[1] and highest > xylo.range[1]
        4) lowest < xylo.range[0]
        5) highest > xylo.range[1]
        
    (1) definitely requires better_transpose
    (2) and (4) require better_transpose if highest + (xylo.range[0] - lowest) > xylo.range[1]
    (3) and (5) require better_transpose if lowest - (highest - xylo.range[1]) < xylo.range[0]  
    '''
    xylo_low = Note.__int__(xylo.range[0])
    xylo_high = Note.__int__(xylo.range[1])
    if (lowest < xylo_low and highest > xylo_high):
        c = better_transpose('foo', 'bar')
    
    elif (lowest < xylo_low):
        # minimum number of half steps to transpose by such that lowest
        # note in the entire composition is within the range of the 
        # instrument
        interval = xylo_low - lowest 
        if (highest + interval > xylo_high):
            c = better_transpose('foo', 'bar')
        else: 
            # whether you can transpose a full octave
            full = False
            if (highest + interval + 12 <= xylo_high): full = True
            c = simple_transpose(c, full, interval, up = True)
            
    elif (highest > xylo_high):
        interval = highest - xylo_high
        if (lowest - interval < xylo_low):
            c = better_transpose('foo', 'bar')
        else:
            full = False
            if (lowest - interval - 12 >= xylo_low): full = True
            c = simple_transpose(c, full, interval, up = False)
            
    return c    
def rebuild_composition(old, xylo, cutoff):
    lowest = Note.__int__(Note('C', 8))
    highest = Note.__int__(Note('C', 0))
    new = Composition()
    for i in old.selected_tracks:
        t = Track()
        t.instrument = xylo
        for bar in old[i]:
            b = Bar()
            b.key.name = bar.key.name
            b.set_meter(bar.meter)
            # note value per beat == the denominator in time signature
            dem = b.meter[1]
            for lst in bar:
                value = lst[1]
                if (is_garbage(value, dem, cutoff[old.selected_tracks.index(i)])):
                    continue
                # do not include lists without notes
                if (not lst[2]):
                    continue
                nc = NoteContainer()
                for note in lst[2]:
                    if (Note.__int__(note) < lowest): lowest = Note.__int__(note)
                    if (Note.__int__(note) > highest): highest = Note.__int__(note)
                    nc + note
                b.place_notes(nc, value)
            t.add_bar(b)
        new.add_track(t)
    # can't do the transposing until all notes in all tracks have been
    # compared against lowest and highest, which is why it is done here
    n1 = Note()
    n2 = Note()
    low = n1.from_int(lowest)
    high = n2.from_int(highest)
    # print("lowest and highest notes:", low, high)
    if (not xylo.notes_in_range([low, high])):
        new = transposer(new, lowest, highest, xylo)
    return new