#!/usr/bin/python

"""This program displays all possible groupings of a number, in keeping
with (but not restricted to) pitch- and beat-class set theory, can bend them
according to your whim, and can express them as music.

All options listed below as "controllable" may be given multiple values which
will be cycled through, or may be followed by a quoted set of
sub-options to control their behaviour.

Options listed as repeatable, if repeated, will cycle through the lists of
values given; otherwise, if options are repeated, the last value given will
apply."""

from time import sleep, time
from sys import argv
from random import randint, shuffle
from os import path, listdir, remove, getpid
from subprocess import Popen, PIPE
from threading import Thread, Timer
from socket import socket, AF_INET, SOCK_STREAM
from copy import deepcopy
from optparse import OptionParser
#import psyco
#psyco.full()

#Process options

def optparse(option_list):

    """Process options"""
    parser = OptionParser(description = __doc__)

    #Simple options
    parser.add_option("-I", "--inputs", action="store_true",
    help="""Type in your own sequences for processing.""")
    parser.add_option("-s", "--step", action="store_true",
    help="""Show results as consecutive steps instead of intervals.""")
    parser.add_option("-R", "--perc", action="store_true",
    help="""Play/print sequences as rhythm.""")
    parser.add_option( "--zip", action="store_true",
    help="""Truncate sequences to fit rhythm """)
    parser.add_option("-n", "--number-lines", action="store_true",
    help="""Number lines""")
    parser.add_option("-u", "--untrans", action="store_true",
    help="""Do not display transpositions""")
    parser.add_option( "--infinite", action="store_true",
    help="""Sequence generator repeats indefinitely!""")

    #"Value" options which cycle through the list of numbers given
    parser.add_option("-N", "--number", action="callback",
    callback=forgive_callback, dest="cardinal",
    help="""The number (of notes/beats) to be processed.""")
    parser.add_option("-V", "--voice", action="callback",
    callback=forgive_callback, dest="voice",
    help="""The range to voice notes beyond the octave. Controllable.""",
     default=[0])
    parser.add_option("--ivoice", action="callback",
    callback=forgive_callback, dest="ivoice",
    help="""Voicing by minimum interval between notes. Controllable.""",
     default=[0])
    parser.add_option("-T", "--translate", action="callback",
    callback=forgive_callback, dest="translate",
    help="""The number of steps to transpose each sequence within its range, or
to slip beats across. Controllable.""")
    parser.add_option("--rotate", action="callback",
    callback=forgive_callback, dest="rotate",
    help="""The number of steps to rotate each sequence. Controllable.""")
    parser.add_option("-O", "--octave", action="callback",
    callback=forgive_callback, dest="divisor",
    help="""The number to divide the octave by. Controllable.""",
    default=[12])
    parser.add_option("--metat", action="callback",
    callback=forgive_callback, dest="metat",
    help="""Bar duration control. Controllable.""")
    parser.add_option("-D", "--transpose", action="callback",
    callback=forgive_callback, dest="transpose", default =[0],
    help="""The number of semitones to transpose output. Controllable.""")

    #"List" options; if repeated, cycle through lists
    parser.add_option("-i", "--include", action="callback",
    callback=forgive_callback, dest="include",
    help="""Step values to be included (space-separated). Controllable.
Repeatable.""")
    parser.add_option("-e", "--exclude", action="callback",
    callback=forgive_callback, dest="exclude",
    help="""Step values to be excluded (space-separated).
Controllable. Repeatable.""")
    parser.add_option("-d", "--degrees", action="callback",
    callback=forgive_callback, dest="degrees",
    help="""Intervals to be included (space-separated). Controllable.
Repeatable.""")
    parser.add_option("--nd", "--nondegrees", action="callback",
    callback=forgive_callback, dest="nondegrees",
    help="""Intervals to be excluded (space-separated). Controllable.
Repeatable.""")
    parser.add_option("-o", "--only", action="callback",
    callback=forgive_callback, dest="only",
    help="""Step values to be exclusively included (space-separated).
Controllable. Repeatable.""")
    parser.add_option("-a", "--all", action="callback",
    callback=forgive_callback, dest="all_check",
    help="""Step values to be exclusively included
and all present (space-separated). Controllable. Repeatable.""")
    parser.add_option("--pattern", action="callback",
    callback=forgive_callback, dest="pattern",
    help="""Space-separated sequence index numbers
determining the order of output. Controllable. Repeatable""")
    parser.add_option("--rhythm", "-r", action="callback",
    callback=forgive_callback, dest="rhythm",
    help="""Space-separated sequence determining the duration of each
note (space separated). Controllable. Repeatable.""")

    #Options cycle through values or ranges ('x-y') given
    parser.add_option("-l", "--length", action="callback",
    callback=range_callback, dest="length",
    help="""Value or dash-separated ranges for sequence length.
Controllable.""")
    parser.add_option("-v", "--variety", action="callback",
    callback=range_callback, dest="variety",
    help="""Value or dash-separated range for number of
different step-values in sequences. Controllable.""")
    parser.add_option("--Zv", action="callback", callback=range_callback,
    dest="z_variety", help="""Value or dash-separated range for number of
non-zero values in sequence's Z-vector. Controllable.""")
    parser.add_option("--Zdepth",  action="callback",
    callback=range_callback, dest="z_depth",
    help="""Value or dash-separated range for number of
different values in sequence's Z-vector. Controllable.""")

   # """Takes list of N/2 values or ranges ('x-y') (completes or truncates as
#required); if repeated, cycles through lists"""
    parser.add_option("-Z",  action="callback",
    callback=range_callback, dest="z_test",
     help="""Display Z-vector with each sequence, or if followed by space
separated Z-vector values, show sequences with that vector. Controllable.
Repeatable.""")
   # """Takes list as described below"""
    parser.add_option("-f", "--fine", action="callback",
    callback=range_callback, dest="finelist",
    help="""Fine control: step value followed by single value or dash-separated
range for number of occurences. May be repeated. Controllable. Repeatable.""")

 #   """Boolean options which cycle through numbers given and apply if sequence
#count is divisible by number. If invoked without arguments, default to 1 """
    parser.add_option("-p", "--prime", action="callback",
    callback=maybe_callback, dest="prime",
    help="""Show unique results in prime form. Controllable.""")
    parser.add_option("-F", "--Forte", action="callback",
    callback=maybe_callback, dest="forte" ,
    help="""Show only Forte primes. Controllable.""")
    parser.add_option("--inversions", action="callback",
    callback=maybe_callback, dest="inv",
    help="""Show each sequence with its inversion. Controllable.""")
    parser.add_option("-m", "--mirrors", action="callback",
    callback=maybe_callback, dest="mirrors",
    help="""Show only sequences which whose primes are
equal to their prime inversions. Controllable.""")
    parser.add_option("--relatives", action="store_true" ,
    help="""Append all Z-related sequences to the output""")
    parser.add_option("--partitions", action="callback",
    callback=maybe_callback, dest="part" ,
    help="""Only show partitions of the number. Controllable.""")
    parser.add_option("-c", "--small-scaly", action="callback",
    callback=maybe_callback, dest="small_scaly" ,
    help="""No consecutive semitones. Controllable.""")
    parser.add_option("-k", "--large-scaly", action="callback",
    callback=maybe_callback, dest="large_scaly" ,
    help="""No room for non-consecutive semitones. Controllable.""")
    parser.add_option("--descend", action="callback",
    callback=maybe_callback, dest="descend",
    help="""Reverse output. Controllable.""")
    parser.add_option("--updown", action="callback", callback=maybe_callback,
    dest="updown", help="""Notes go up and down. Controllable.""")
    parser.add_option("--rand", action="callback", callback=maybe_callback,
    dest="rand", help="""Randomise sequence order. Controllable.""")
    parser.add_option("--shuffle", action="callback",
    callback=forgive_callback, dest="shuffle_phrase",
    help="""A number determining the shuffle offset. Controllable.""")
    parser.add_option("--split", action="callback", callback=maybe_callback,
    dest="split", help="""Alternating high and low. Controllable.""")
    parser.add_option("--randomt", action="callback",
    callback=maybe_callback, dest="randomt",
    help="""Random note durations. Controllable.""")
    parser.add_option("--metarandomt", action="callback",
    callback=maybe_callback, dest="metarandomt",
    help="""Random bar durations. Controllable.""")
    parser.add_option("--svoice", action="callback",
    callback=maybe_callback, dest="svoice",
    help="""Simple voicing algorithm. Controllable.""")

    #Playback and score options

    ##Simple
    parser.add_option("-L", "--score", action="store_true" ,
    help="""Create PDF score file in home directory""")
    parser.add_option("--midi", action="store_true" ,
    help="""Create midi file in home directory""")
    parser.add_option("--overwrite", action="store_true" ,
    help="""Overwrite existing PDF or midi score""")
    parser.add_option( "--loop", action="store_true" , help="""Loop playback""")
    parser.add_option("--instrument-list", action="store_true" ,
    help="""Show list of avaiable instruments""")


    ##Value options
    parser.add_option("-t", "--tempo", action="callback",
    callback=forgive_callback, dest="tempo", default=[120] ,
    help="""Tempo in BPM. Controllable.""")
    parser.add_option("--program", action="callback",
    callback=forgive_callback, dest="program", default=[0] ,
    help="""MIDI program number. Controllable.""")
    parser.add_option("--channel", action="callback",
    callback=forgive_callback, dest="channel", default=[0],
    help="""MIDI channel. Controllable.""")
    parser.add_option("--bank", action="callback",
        callback=forgive_callback,
    dest="bank", default=[0], help="""MIDI bank. Controllable.""")
    parser.add_option("--font", action="callback",
        callback=forgive_callback,
    dest="font", default=[1], help="""Sounfont I.D. number""")
    parser.add_option("--volume", action="callback",
    callback=forgive_callback, dest="volume" ,
    help="""Volume, 0-10. Default 10. Controllable.""")

#    """Boolean, if invoked without arguments, pause defaults to 4, display to
#16, else to 1. """
    parser.add_option("--bell", action="callback", callback=maybe_callback,
    dest="bell" ,
    help="""Play bell sound at the start of each rhythmic bar.
Controllable. Boolean.""")
    parser.add_option("--click", action="callback", callback=maybe_callback,
    dest="click" ,
    help="""Play click on each beat of rhythmic bars. Controllable. Boolean.""")
    parser.add_option("--chord", action="callback", callback=maybe_callback,
    dest="chord" , help="""Play sequences as chords. Controllable. Boolean.""")
    parser.add_option("--pause", action="callback", callback=maybe_callback,
    dest="pause",
    help="""Pause between bars for four quavers or the number entered.
Controllable.""", default=[0])
    parser.add_option("--display",  action="callback",
    callback=maybe_callback, dest="display" ,
    help="""Create PDF score file in home directory refreshing each 16 bars,
or number of bars entered""")

    ##String options, -P defaults to 'synth' if invoked with no argument
    parser.add_option("--driver", default="alsa",
    help="""Driver for fluidsynth""")
    parser.add_option("-P", "--play", action="callback",
    callback=play_callback, dest="play"  ,
    help="""Play output with sox synth,
or give path to a soundfont for fluidsynth playback,
or path to a directory containing samples for sample playback""")


    options = parser.parse_args(option_list)[0]
    parser.destroy()
    if not options.cardinal:
        if options.perc:
            options.cardinal = [8]
        else:
            options.cardinal = [12]

    if all(i == 0 for i in options.divisor):
        options.divisor = [12]

    cardinal = options.cardinal[0]
    z_test = options.z_test
    if z_test and z_test[0] != "C":
        for subz in z_test:
            if len(subz) >  cardinal/2:
                subz = subz[:cardinal/2]
            while len(subz) < cardinal/2:
                subz += [[0, cardinal]]

    if options.score or options.midi or (options.play and options.display):

        if not all(i==12 for i in options.divisor):
            print "\nScores and midi files of non-12-pitch music \
            not implemented.\n"
            options.display = None
            options.score = None
            options.midi = None

        if cardinal > 108:
            print "\nN too large for midi or score files.\n"
            options.display = None
            options.score = None
            options.midi = None

    if options.play == "fluidsynth":
        if cardinal > 108:
            print "\nN too large for midi playback. Switching to synth.\n"
            options.play = "synth"
        else:
            host = "localhost"
            port = 9800
            fluid = socket(AF_INET, SOCK_STREAM)
            options.fluid = fluid
            try:
                fluid.connect((host, port))
            except BaseException:
                print "Connecting to fluidsynth..."
                soundfont =  options.soundfont
                driver = options.driver
                Popen(["fluidsynth", "-i", "-s", "-g", "0.5",
                "-C", "1",  "-R", "1", "-l", "-a", driver, "-j", soundfont],
                stderr=PIPE)
                timeout = 1500
                while 1:
                    timeout -= 1
                    if timeout == 0:
                        print "Problem with fluidsynth: switching to synth."
                        options.play = "synth"
                        break
                    try:
                        fluid.connect((host, port))
                    except BaseException:
                        sleep(0.005)
                        continue
                    else:
                        break

    return options

def forgive_callback(option, opt_str, value, parser):
    "Process options which take arguments; but ignore if not there"

    list_ctrls = ('all_check', 'degrees', 'exclude', 'finelist', 'include',
                    'nondegrees', 'only', 'pattern', 'rhythm')
    value = []
    rargs = parser.rargs
    try:
        if rargs[0] == "C" :
            setattr(parser.values, option.dest, ["C"] + rargs[1].split())
            del rargs[:2]
        else:
            while 1:
                value.append(int(rargs[0]))
                del rargs[0]
    except(IndexError, ValueError, TypeError):
        if value:
            if option.dest in list_ctrls:
                current_val = getattr(parser.values, option.dest)
                if current_val:
                    value = current_val + [value]
                else:
                    value = [value]
            setattr(parser.values, option.dest, value)
        else:
            print "\n" + opt_str + " needs arguments."
    cleanup(rargs)

def maybe_callback(option, opt_str, value, parser):
    "Process options which may take an argument or not"
    value = []
    rargs = parser.rargs
    try:
        if rargs[0] == "C" :
            setattr(parser.values, option.dest, ["C"] + rargs[1].split())
            del rargs[:2]
        else:
            while 1:
                value.append(abs(int(rargs[0])))
                del rargs[0]
    except(IndexError, ValueError):
        if not value:
            if opt_str == "--pause":
                value = [4]
            elif opt_str == "--display":
                value = [16]
            else:
                value = [1]
        setattr(parser.values, option.dest, value)
    cleanup(rargs)

def range_callback(option, opt_str, value, parser):

    "Process options which take a single or range value"
    value = []
    rargs = parser.rargs
    try:
        if rargs[ 0 ] == "C" :
            setattr(parser.values, option.dest, ["C"] + rargs[1].split())
            del rargs[:2]
        else:
            while 1:
                elt = []
                if option.dest == 'finelist':
                    elt.append(abs(int(rargs[0])))
                    del rargs[0]
                if '-' in rargs[0]:
                    dash = rargs[0].index("-")
                    elt += sorted([ abs(int(rargs[0][ : dash ])),
                    abs(int(rargs[0][  dash+1 : ])) ])
                else:
                    elt += [ abs(int(rargs[0])) ] * 2
                value.append(elt)
                del rargs[ 0 ]
    except(IndexError, ValueError):
        if value or option.dest == 'z_test' :
            if option.dest == 'z_test' or option.dest == 'finelist':
                current_val = getattr(parser.values, option.dest)
                if current_val:
                    value = current_val + [value]
                else:
                    value = [value]
            setattr(parser.values, option.dest, value)
        else:
            if option.dest == "finelist":
                mesg_str = "step values followed by "
            else:
                mesg_str = ""
            print ("\n" + opt_str + " needs " +
                    mesg_str + "numbers or ranges (x-y).")
    while rargs:
        if rargs[0][0] == '-':
            break
        else:
            del rargs[0]

def play_callback(option, opt_str, value, parser):

    "Play option processing"
    rargs = parser.rargs
    try:
        next_arg = rargs[0]
        if path.isfile(next_arg):
            parser.values.soundfont = next_arg
            value = "fluidsynth"
        elif path.isdir(next_arg):
            sample_dict = {}
            samples = sorted(listdir(next_arg))
            index = 0
            for sample in samples:
                if path.isfile(next_arg + "/" + sample):
                    sample_dict[index] = next_arg + "/" + sample
                    index += 1
            parser.values.samples = sample_dict
            value = "samples"
        elif next_arg[0] != "-":
            print next_arg + ": unknown argument to " + opt_str + "."
            print "Switching to synth"
            value = "synth"
        else:
            value = "synth"
    except(IndexError):
        value = "synth"
    setattr(parser.values, option.dest, value)
    cleanup(rargs)

def cleanup(rargs):
    """Remove noise from commandline"""
    while rargs:

        if rargs[0][0] == '-':
            break
        else:
            del rargs[0]

def iterizer(opt, value, cardinal=None):
    """Convert controllable option values to generators"""

    range_ctrls = ('length', 'variety', 'z_depth', 'z_variety')
    numerical_ctrls = ('bank', 'bell', 'channel', 'click', 'chord', 'descend',
    'divisor', 'forte', 'inv', 'ivoice', 'large_scaly', 'metarandomt', 'metat',
    'mirrors', 'part', 'pause', 'prime', 'program', 'rand', 'randomt',
    'rotate', 'shuffle_phrase', 'split', 'small_scaly', 'svoice',  'tempo',
    'translate', 'transpose', 'updown' , 'voice', 'volume')

    if value[0] == "C":
        ctrl_opts = optparse(value[1:])
        iterator = repeat(sequence_engine, ctrl_opts)
        if opt in numerical_ctrls:
            iterator = elt_iter(iterator)
        elif opt in range_ctrls:
            iterator = range_iter(iterator)
        elif opt == "finelist":
            iterator = fine_iter(iterator)
        elif opt == "z_test":
            iterator = z_iter(iterator, cardinal)
    else:
        iterator = elt_iter(dummy_ctrl(value))

    return iterator

def repeat(iterobj, options=None):
    """Restart exhausted generators"""
    while 1:
        try:
            stop_flag = True
            iterator = iterobj(options)
            while 1:
                try:
                    yield iterator.next()
                    stop_flag = False
                except StopIteration:
                    if stop_flag:
                        raise ValueError
                    else:
                        break
        except ValueError:
            break

def elt_iter(iterator):
    """Generate one element at a time from a list-producing iterator"""
    for number_list in iterator:
        for number in number_list:
            yield number

def range_iter(iterator):
    """Generate lists of two elements from a list-producing iterator"""
    range_list = []
    for number_list in iterator:
        for number in number_list:
            range_list.append(number)
            if len(range_list) == 2:
                range_list.sort()
                yield range_list
                range_list = []

def fine_iter(iterator):
    """Generate lists of lists of three elements
    from a list-producing iterator"""
    for number_list in iterator:
        fine_list = []
        while len(number_list) > 1:
            if len(number_list) == 4 or len(number_list) == 2:
                number_list.insert(1, number_list[1])
            sub_fine = number_list[:3]
            sub_fine = sub_fine[:1]+sorted(sub_fine[1:])
            del number_list[:3]
            fine_list.append(sub_fine)
        yield fine_list


def z_iter(iterator, cardinal):
    """Produce z-vectors"""
    z_list = []
    range_list = []
    for number_list in iterator:
        for number in number_list:
            range_list.append(number)
            if len(range_list) == 2:
                range_list.sort()
                z_list.append(range_list)
                if len(z_list) == cardinal/2:
                    yield z_list
                    z_list = []
                range_list = []

def dummy_ctrl(value):
    """Repeatedly yield input value"""
    while 1:
        yield value

class Sequence(list):
    """doc"""
    relatives_list = []

    def __init__(self, sequence):
        """doc"""
        super(self.__class__, self).__init__(sequence)
        self.inverted = False
        self.vector = None
        self.transpose = 0
        self.cardinal = 12

    def __getslice__(self, start, stop):
        return self.__class__(super(self.__class__,
        self).__getslice__(start, stop))

    def __getitem__(self, key):
        if isinstance(key, slice):
            return self.__class__(super(self.__class__,
            self).__getitem__(key))
        else:
            return super(self.__class__, self).__getitem__(key)

    def __add__(self, other):
        return self.__class__(super(self.__class__,
        self).__add__(other))

    def __mul__(self, other):
        return self.__class__(super(self.__class__,
        self).__mul__(other))

    def include(self, include):
        """doc"""
        if include:
            if set(include) <= set(self):
                return True
        else:
            return True

    def exclude(self, exclude):
        """doc"""
        if exclude:
            if not set(exclude) & set(self):
                return True
        else:
            return True

    def all_check(self, all_check):
        """doc"""
        if all_check:
            if set(all_check) == set(self):
                return True
        else:
            return True

    def only(self, only):
        """doc"""
        if only:
            if set(only) >= set(self):
                return True
        else:
            return True

    def length(self, length):
        """doc"""
        if length:
            if length[0] <= len(self) <= length[1]:
                return True
        else:
            return True

    def fine(self, finelist):
        """doc"""
        if finelist:
            for nums in finelist:
                if not (nums[1] <= self.count(nums[0]) <= nums[2]):
                    return
        return True

    def variety(self, variety):
        """doc"""
        if variety:
            if (variety[0] <= len(set(self)) <= variety[1]):
                return True
        else:
            return True

    def large_scaly_first(self, large_scaly, cardinal):
        """doc"""
        if large_scaly:
            if (max(self) <= 3 and self.count(3) < cardinal/2) :
                return True
        else:
            return True

    def small_scaly_first(self, small_scaly, cardinal):
        """doc"""
        if small_scaly:
            if self.count(1) <= cardinal/2:
                return True
        else:
            return True

    def prime(self):
        """doc"""
        self[:] = sorted (
                    [
                    self[k:] + self[:k] for k in range(len(self))
                    if self[k-1] is max(self)
                        ]
                       )[0]

    def primed(self):
        """doc"""
        return sorted ([
                    self[k:] + self[:k] for k in range(len(self))
                    if self[k-1] is max(self)
                        ])[0]

    def degrees(self, degrees):
        """doc"""
        if degrees:
            k = [
            sum(self[:n])
            for n in range(len(self))
            ]
            if set(degrees) <= set(k):
                return True
        else:
            return True

    def nondegrees(self, nondegrees):
        """doc"""
        if nondegrees:
            k = [
            sum(self[:n])
            for n in range(len(self))
            ]
            if not set(nondegrees) & set(k):
                return True
        else:
            return True

    def small_scaly(self, small_scaly):
        """doc"""
        if small_scaly:
            for i in enumerate(self):
                if i[1] == 1 and self[ i[0]-1 ] == 1:
                    return
        return True

    def large_scaly(self, large_scaly):
        """doc"""
        if large_scaly :

            for i in enumerate(self):
                if i[1] == 3:
                    if not(self[i[0]-1] == 1
                        and self[ (i[0]+1) % len(self)] == 1):
                        return
        return True

    def mirrors(self, mirrors, prime):
        """doc"""
        if mirrors:
            forward = self[:]
            if not prime:
                forward.prime()
            backward = self[:]
            backward.reverse()
            backward.prime()
            if forward == backward:
                return True
        else:
            return True

    def forte(self, forte):
        """doc"""
        if forte:
            k = self[:]
            k.reverse()
            k.prime()
            if k <=  self:
                return True
        else:
            return True

    def z_vector(self, cardinal):
        """doc"""
        z_vector = [0] * (cardinal / 2)
        for i in enumerate(self):
            interval = 0
            for i in self[ i[0] + 1:]:
                interval += i
                if interval > cardinal / 2:
                    final_interval = cardinal - interval
                else:
                    final_interval = interval
                z_vector[final_interval - 1] += 1
        self.vector = tuple(z_vector)


    def relatives(self, z_vector):
        """doc"""
        self.relatives_list.append((z_vector, tuple(self)))


    def z_test(self, z_test, z_vector):
        """doc"""
        if z_test:
            for pair in zip(z_vector, z_test):
                if not(pair[1][0] <= pair[0] <= pair[1][1]):
                    return
        return True

    def z_variety(self, z_variety, z_vector):
        """doc"""
        if z_variety:
            if  z_variety[0] <= len(z_vector) - z_vector.count(0) \
            <= z_variety[1] :
                return True
        else:
            return True

    def z_depth(self, z_depth, z_vector):
        """doc"""
        if z_depth:
            z_vector = [i for i in z_vector if i != 0]
            if z_depth[0] <= len(set(z_vector)) <= z_depth[1]:
                return True
        else:
            return True

    def inv(self, prime):
        """doc"""
        invert = self[:]
        invert.reverse()
        if prime:
            invert.prime()
        return invert

    def setify(self):
        """doc"""
        self[:] = [ sum(self[:n]) for n in range(len(self)) ]

    def stepify(self, cardinal, voice):
        """doc"""
        norm = sorted([i - min(self) for i in self])
        self[:] = [
        norm[n+1] - norm[n]
        for n in range(len(norm)-1) ] + [cardinal + voice - norm[-1] ]

    def voicify(self, voice, divisor):
        """doc"""

        octs = abs((voice-1)/divisor) + 2
        k = []
        if len (self) > 1 and self [1] - self[0] < 3:
            start = 1
        else:
            start = 0

        for note in self:
            new_note = note + (divisor * ((self.index(note) + start) % octs))
            if max(self) < new_note < voice + divisor :
                k.append(new_note)
            else:
                k.append(note)
        k.sort()
        self[:] = k
        self.trans  ( - (((max(self) - min(self)) / 2) / divisor) * divisor)

    def svoice(self, divisor):
        """doc"""

        k = []
        direction = 1
        for note in self:
            new_note = note
            if direction:
                while  min(self) <= new_note <= max(self) or new_note in k :
                    new_note += direction * divisor
                if direction == 1:
                    direction = -1
                elif direction == -1:
                    direction = 0
            else:
                direction = 1
            k.append(new_note)

        k.sort()
        self[:] = k

    def ivoice(self, interval, divisor):
        """doc"""
        k = [self[0]]
        for note in self[1:]:
            inc = 0
            while any(abs(note + inc - i) < interval for i in k):
                inc  += divisor
            k.append(note + inc)
        k.sort()
        self[:] = k
        self.trans( - (((max(self) - min(self)) / 2) / divisor) * divisor)

    def updown(self):
        """doc"""
        k = []
        for index in range(len(self) /2):
            k.append(self.pop(index))
        self.reverse()
        self[:] = k + self

    def translate(self, translate, perc, cardinal):
        """doc"""
        if perc:
            while translate >=  self[-1]:
                translate -= self[-1]
                self.insert(0, self.pop(-1))
            if translate > 0:
                self.insert(0, translate)
                self[-1] -= translate
        else:
            self[:] = sorted([((i+translate) % cardinal) for i in self])

    def pattern(self, pattern):
        """doc"""
        self[:] = [self[ i % len(self) ] for i in pattern]

    def rotate(self, rotate):
        """doc"""
        length = len(self)
        self[:] = self[(rotate % length):]+ self[:(rotate % length)]

    def shuffle_phrase(self, shuffle_phrase):
        """doc"""
        k = []
        length = len(self)
        factor = length * shuffle_phrase
        lcd = factor
        while factor > length:
            factor -= length
            if not factor % shuffle_phrase:
                lcd = factor
        times = lcd/shuffle_phrase
        index = 0
        for _ in range(length / times):
            for _ in range(times):
                k.append(self[index])
                index = (index + shuffle_phrase) % length
            index += 1
        self[:] = k

    def split(self):
        """doc"""
        k = []
        index = 0
        while self:
            k.append(self.pop(index))
            if index == 0:
                index  = -1
            else:
                index = 0
        self[:] = k

    def trans(self, trans):
        '''doc'''
        self[:] =  [i + trans for i in self]

class Event(list):

    """Events produced within bar_builder"""

    cardinal = 12
    volume = 10
    duration = 1
    divisor = 12
    transpose = 0
    inverted = False
    vector = None
    voice = 0
    seq_counter = 0


class Bar(list):

    """Bars produced by bar_builder"""

    number_lines = False
    play_method = None
    samples = None
    fluid = None
    display_path = None
    off = ''
    origin = 0
    step = False
    perc = False
    elapsed = 0
    bar_counter = 0
    program = 0
    pause = 0
    click = False
    channel = 0
    bell = False
    tempo = 120
    bank = 0
    font = 1
    rhythm_on = True

    def screen(self):
        """Wrapper to delay screen output till we hear it"""
        if self.play_method:
            if self.elapsed == 0:
                delay_time = 0
            else:
                delay_time = (self.elapsed * 30.0 / self.tempo + self.origin -
                                time())
            timer = Timer(delay_time,  self.__screen)
            timer.start()
        else:
            self.__screen()

    def __screen(self):
        """Display output nicely on-screen"""
        step = self.step
        perc = self.perc
        number_lines = self.number_lines
        rhythm_on = self.rhythm_on
        shelf = deepcopy(self)

        if perc:

            if number_lines:
                print shelf[0].seq_counter, ": ",
            if shelf[0].inverted:
                print "I:",
            for event in shelf:
                print event.duration,
            if shelf[0].vector:
                print " ", shelf[0].vector
            else:
                print

        else:

            if step:
                if any(len(i) > 1 for i in shelf):
                    for event in shelf:
                        copy = Sequence(event)
                        copy.stepify(event.cardinal, event.voice)
                        event[:] = copy
                else:
                    shelf.sort()
                    notes = Sequence([i[0] for i in shelf])
                    notes.stepify(self[0].cardinal, self[0].voice)
                    for i in range(len(shelf)):
                        shelf[i][:] = [notes[i]]

            elif shelf.untrans:
                for event in shelf:
                    event[:] = [ i - event.transpose for i in event ]

            if any(len(i) > 1 for i in shelf):
                if len(shelf) > 1:
                    print
                for event in shelf:
                    if not step:
                        event.sort()
                    if number_lines:
                        print event.seq_counter, ": ",
                    if event.inverted:
                        print "I:",
                    print event,
                    if rhythm_on:
                        print "(" + str(event.duration) + ")",
                    if event.vector:
                        print " ", event.vector
                    else:
                        print
            else:

                if number_lines:
                    print shelf[0].seq_counter, ": ",
                if shelf[0].inverted:
                    print "I:",
                for event in shelf:
                    print event[0],
                    if rhythm_on:
                        print "(" + str(event.duration) + ")",
                if shelf[0].vector:
                    print " ", shelf[0].vector
                else:
                    print


    def play(self) :
        """Play a bar"""
        play_method = self.play_method
        pulse = 30.0/self.tempo
        pause = self.pause
        perc = self.perc
        counter = self.bar_counter
        elapsed = self.elapsed

        if perc:
            bell = self.bell
            click = self.click
            if bell and not counter % bell:
                bell = True
            else:
                bell = False

            if click and not counter % click:
                click = True
            else:
                click = False

        for event in self:
            transpose = event.transpose
            divisor = event.divisor

            if play_method == "samples":
                while transpose < -2 * divisor:
                    transpose += divisor
                while transpose > 2 * divisor:
                    transpose -= divisor
                event.transpose = transpose
            else:
                highest = max(event)
                lowest = min(event)
                if highest > 48:
                    adjust = -((highest - 48) / divisor) * divisor - divisor
                elif lowest  < -60 :
                    adjust = -((lowest + 60) / divisor) * divisor
                else:
                    adjust = 0
                event[:] = [note + adjust for note in event]

        if play_method == "fluidsynth":

            fluid = self.fluid
            channel = str(self.channel % 16)
            program = str(self.program % 128)
            font = str(self.font)
            if perc:
                bank = " 128 "
                fluid.send("select 9 " + " " + font + bank + program + "  \n")
                click_note = str(42 + transpose)
                note = str(75 + transpose)

                for beat in self:
                    duration = beat.duration
                    if not duration:
                        continue
                    volume = beat.volume
                    if 0 <= volume <= 10:
                        velocity = volume * 10
                    else:
                        velocity = 100

                    if elapsed > 0:
                        sleeptime =  elapsed * pulse + self.origin - time()
                        if sleeptime > 0:
                            sleep(sleeptime)
                    else:
                        Bar.origin = time()

                    if bell and beat is self[0]:
                        bellnote = str(34 + transpose)
                        fluid.send("noteon 9  " + bellnote + " " +
                        str(velocity) + "\n")

                    fluid.send("noteon 9 "+ note + " " + str(velocity) + " \n")
                    if click:
                        fluid.send("noteon 9 " + click_note + " " +
                            str(velocity)+ " \n")
                        subdivisions = 0
                        while subdivisions < duration:
                            sleeptime =  elapsed * pulse + self.origin - time()
                            if sleeptime > 0:
                                sleep(sleeptime)
                            fluid.send("noteon 9 " + click_note + " " +
                            str(velocity)+ " \n")
                            elapsed += 1
                            subdivisions += 1
                    else:
                        elapsed += duration

            else:
                bank = str(self.bank)
                for event in self:
                    duration = event.duration
                    if not duration:
                        continue
                    init_message = ["select " + channel + " " + font
                +  " " + bank+ " " + program +
                "  \ntuning name 0 " + program + " \n"]
                    for note in event:
                        pitch = str((((note) * 12.0 / event.divisor)
                                            + 60) * 100)
                        init_message.append( "tune  " + bank +" " + program
                                + " " + str(note + 60) + " " + pitch + "\n")
                    init_message.append("settuning " + channel + " " + bank
                                                    + " " + program + "\n")
                    init_message = ''.join(init_message)
                    fluid.send(init_message)

                    volume = event.volume
                    if 0 <=  volume <= 10:
                        velocity = volume * 10
                    else:
                        velocity = 100
                    velocity = str(velocity / ((len(event) ** 0.3)))
                    midi_message = []
                    for note in event:
                        midi_message.append("noteon " + channel + " " +
                        str(note + 60) + " " + velocity + " \n")
                    midi_message = ''.join(midi_message)
                    if elapsed > 0 :
                        sleeptime = elapsed * pulse + self.origin - time()
                        if sleeptime > 0:
                            sleep(sleeptime)
                        fluid.send(self.off)
                    else:
                        Bar.origin = time()
                    fluid.send(midi_message)
                    elapsed += duration
                    Bar.off = midi_message.replace("on", "off")

        elif play_method == "samples":

            sample_dict = self.samples

            if  perc :
                bellargs = ["play", "-q", sample_dict[(self[0].transpose + 3) %
                len(sample_dict) ], "vol" ]
                beatargs = ["play", "-q", sample_dict[(self[0].transpose + 2) %
                len(sample_dict) ], "vol" ]
                clickargs = ["play", "-q", sample_dict[(self[0].transpose + 1)%
                len(sample_dict) ], "vol"]

                for beat in self:
                    duration = beat.duration
                    if not duration:
                        continue

                    volume = beat.volume
                    if 0 <= volume <= 10:
                        volume = (10 - volume) ** 2
                        velocity = str(-9 - volume)+"db"
                    else:
                        velocity = "-9db"

                    if elapsed > 0:
                        sleeptime =  elapsed * pulse + self.origin - time()
                        if sleeptime > 0:
                            sleep(sleeptime)
                    else:
                        Bar.origin = time()

                    if bell and beat is self[0]:
                        Popen(bellargs + [velocity])
                    Popen(beatargs + [velocity])
                    if click:
                        Popen(clickargs + [velocity])
                        subdivisions = 0
                        while subdivisions < duration:
                            sleeptime =  elapsed * pulse + self.origin - time()
                            if sleeptime > 0:
                                sleep(sleeptime)
                            Popen(clickargs + [velocity])
                            elapsed += 1
                            subdivisions += 1

                    else:
                        elapsed += duration

            else:
                for event in self:
                    duration = event.duration
                    trans = event.transpose

                    if not duration:
                        continue
                    if  len(event) > 1:
                        arglist = ["play", "-q", "-m", "vol", "key",
                        str( trans * 1200.0 / event.divisor) ]
                        max_vol = 3
                    else:
                        arglist = ["play", "-q", "vol", "key", str(
                        trans * 1200.0 / self[0].divisor) ]
                        max_vol = -6

                    volume = event.volume
                    if 0 <= volume <= 10:
                        volume = (10 - volume) ** 2
                        velocity = str(max_vol - volume)+"db"
                    else:
                        velocity = str(max_vol)+"db"

                    insert = arglist.index("vol")
                    arglist.insert(insert + 1, velocity)

                    for i in event:
                        sample = sample_dict[ (i - trans) % len(sample_dict) ]
                        arglist.insert(insert, sample)

                    if elapsed > 0:
                        sleeptime = elapsed * pulse + self.origin - time()
                        if sleeptime > 0:
                            sleep(sleeptime)
                    else:

                        Bar.origin = time()
                    Popen(arglist, stderr=PIPE)
                    elapsed += duration

        else:
            if  perc :
                key = str(self[0].transpose * 100)
                bellargs = ["play", "-q", "-n", "synth", "0.02", "whitenoise",
                "key", key, "vol"]
                beatargs = ["play", "-q", "-n", "synth", "0.02", "pinknoise",
                "key", key, "vol"]
                clickargs = ["play", "-q", "-n", "synth", "0.02", "brownnoise",
                "key", key, "vol"]


                for beat in self:
                    duration = beat.duration
                    if not duration:
                        continue
                    volume = beat.volume
                    if 0 <= volume <= 10:
                        volume = (10 - volume) ** 2
                        velocity = str(-3 - volume)+"db"
                    else:
                        velocity = "-3db"

                    if elapsed > 0:
                        sleeptime =  elapsed * pulse + self.origin - time()
                        if sleeptime > 0:
                            sleep(sleeptime)
                    else:
                        Bar.origin = time()
                    if bell and beat is self[0]:
                        Popen(bellargs + [velocity])
                    Popen(beatargs + [velocity])

                    if click:
                        Popen(clickargs + [velocity])
                        subdivisions = 0
                        while subdivisions < duration:
                            sleeptime =  elapsed * pulse + self.origin - time()
                            if sleeptime > 0:
                                sleep(sleeptime)
                            Popen(clickargs + [velocity])
                            elapsed += 1
                            subdivisions += 1

                    else:
                        elapsed += duration

            else:
                for event in self:
                    duration = event.duration
                    if not duration:
                        continue
                    divisor = event.divisor
                    volume = event.volume
                    if 0 <= volume <= 10:
                        volume = (10 - volume) ** 2
                        velocity = str(-15 - volume)+"db"
                    else:
                        velocity = "-15db"
                    synthargs = ["play", "-q", "-n", "-c1", "synth",  "vol",
                    velocity]
                    notedur = duration * pulse
                    if duration:
                        synthargs[5:5] =   [str(notedur)]
                        for i in event:
                            synthargs[6:6] = ["sin", "%" +
                            str(12.0 * (i) / divisor)]
                        if elapsed > 0:
                            sleeptime = elapsed * pulse + self.origin - time()
                            if sleeptime > 0:
                                sleep(sleeptime)
                        else:
                            Bar.origin = time()
                        Popen(synthargs)
                        elapsed += duration

        if pause:
            sleep(pause * pulse)

def partitions(num):
    """Produce number partitions"""
    if num == 0:
        yield []
        return
    for part in partitions(num-1):
        yield Sequence([1] + part)
        if part and (len(part) < 2 or part[1] > part[0]):
            yield Sequence([part[0] + 1] + part[1:])

def multiperm(seq):
    """Produce multiset permutations"""
    dic = {}
    for x in seq:
        dic[x] = dic.get(x,0)+1
    for result in genperm(dic):
         yield Sequence(result)

def genperm(dic, result = []):
    """Subfunction for multiperm"""
    if not max(dic.values()):
        yield result
    else:
        for k,v in dic.items():
            if v:
                dic[k] -= 1
                for g in genperm(dic, result + [k]):
                    yield g
                dic[k] += 1

def sequence_engine(options):
    """Produce sequences"""

    cardinal = options.cardinal[0]
    perc = options.perc
    inputs = options.inputs
    rel = options.relatives

    engine_opts = ('all_check', 'length', 'variety', 'degrees',
                            'descend', 'divisor', 'exclude', 'finelist',
                            'forte', 'include', 'inv', 'ivoice', 'large_scaly',
                            'mirrors', 'nondegrees', 'only', 'part',
                            'pattern', 'prime', 'rand', 'rotate',
                            'shuffle_phrase', 'small_scaly', 'split', 'svoice',
                            'translate', 'transpose', 'updown', 'voice',
                            'z_depth', 'z_test', 'z_variety')

    generators = {}
    seq_vars = {}

    for option in engine_opts:
        value = getattr(options, option)
        if value:
            if value[0] == 'C' or len(value) > 1:
                generators[option] = iterizer(option, value, cardinal)
                value = generators[option].next()
                if option == "divisor":
                    while value == 0:
                        value = generators[option].next()
            else:
                value = value[0]
        seq_vars[option] = value

    counter = 1

    for partition in partitions(cardinal):
        # EITHER get sequences from user input...
        if inputs:
            sequences = []
            while 1:
                intervals = Sequence ([int(i) % (cardinal)
                for i in raw_input(
                """\nEnter some space-separated numbers,
or just press enter to run the program:\n\n"""
                ).split() if i.isdigit() ])

                if not intervals:
                    break
                if  perc:
                    while sum(intervals) >= cardinal:
                        intervals.pop(-1)
                    intervals.append(cardinal-sum(intervals))
                else:
                    intervals[:] = list(set(intervals))
                    intervals.stepify(cardinal, 0)

                if seq_vars["prime"] and not counter % seq_vars["prime"]:
                    intervals.prime()
                sequences.append(intervals)

        #OR filter partitions....
        else:

            if not (
            partition.include(seq_vars["include"])
            and partition.exclude(seq_vars["exclude"])
            and partition.all_check(seq_vars["all_check"])
            and partition.only(seq_vars["only"])
            and partition.length(seq_vars["length"])
            and partition.fine(seq_vars["finelist"])
            and partition.variety(seq_vars["variety"])
                ):

                continue
            if (
                (seq_vars["small_scaly"] and not
               counter % seq_vars["small_scaly"] and not
               partition.small_scaly_first(seq_vars["small_scaly"], cardinal))
                or (seq_vars["large_scaly"] and not
                counter % seq_vars["large_scaly"] and not
                partition.large_scaly_first(seq_vars["large_scaly"], cardinal))
                ):

                continue


            part_filters = ['include', 'exclude', 'all_check',
                                     'only', 'length', 'fine', 'variety']

            for option in generators.keys():
                if option in part_filters:
                    value = generators[option].next()
                    if option == "divisor":
                        while value == 0:
                            value = generators[option].next()
                    seq_vars[option] = value

            #  ...and generate sequences from partitions
            if seq_vars["part"] and not counter % seq_vars["part"]:
                sequences = (partition,)

            elif ((seq_vars["prime"] and not counter % seq_vars["prime"]) or
                    (seq_vars["forte"] and not
                    counter % seq_vars["forte"])) and len(partition) > 1:
                maxi = partition.pop(partition.index(max(partition)))
                if maxi in partition and (partition.count(maxi) !=
                                                        len(partition)):
                    sequences = (Sequence(i) for i in set(
                                 tuple((sequence + [maxi]).primed())
                                 for sequence in multiperm(partition)
                                ))
                else:
                    sequences = (sequence + [maxi]
                    for sequence in multiperm(partition))
            else:
                sequences = (sequence for sequence in multiperm(partition))

        #Filter  sequences....

        for sequence in sequences:

            if (
                (seq_vars["small_scaly"] and not
                    counter % seq_vars["small_scaly"]
                and not sequence.small_scaly(seq_vars["small_scaly"])) or
            (seq_vars["large_scaly"] and not counter % seq_vars["large_scaly"]
            and not sequence.large_scaly(seq_vars["large_scaly"])) or
            (seq_vars["mirrors"] and not counter % seq_vars["mirrors"]
            and not sequence.mirrors(seq_vars["mirrors"], seq_vars["prime"])) or
            (seq_vars["forte"] and not counter % seq_vars["forte"]
            and not sequence.forte(seq_vars["forte"])) or
            not sequence.degrees(seq_vars["degrees"]) or
            not sequence.nondegrees(seq_vars["nondegrees"])
                ):

                continue


            if (options.relatives or options.z_depth
                or options.z_variety or options.z_test):
                sequence.z_vector(cardinal)
                z_vector = sequence.vector

                if not (
                    sequence.z_variety(seq_vars["z_variety"], z_vector) and
                    sequence.z_depth (seq_vars["z_depth"], z_vector) and
                    sequence.z_test(seq_vars["z_test"], z_vector)
                   ): continue

        #...and modify

            if seq_vars["inv"] and not counter % seq_vars["inv"]:
                invert = sequence.inv(seq_vars["prime"])
                invert.inverted = True
                invert.vector = sequence.vector
                hack = (sequence, invert)

            else:
                hack = (sequence, )

            for sequence in hack:
                sequence.voice = seq_vars["voice"]
                sequence.divisor = seq_vars["divisor"]
                sequence.transpose = seq_vars["transpose"]
                if not perc:
                    sequence.setify()
                if seq_vars["translate"]:
                    sequence.translate(seq_vars["translate"], perc, cardinal)
                if not perc:
                    if seq_vars["voice"]:
                        sequence.voicify(seq_vars["voice"], seq_vars["divisor"])
                    if seq_vars["svoice"] and not counter % seq_vars["svoice"]:
                        sequence.svoice(seq_vars["divisor"])
                    if seq_vars["ivoice"]:
                        sequence.ivoice(seq_vars["ivoice"], seq_vars["divisor"])

                if seq_vars["pattern"]:
                    sequence.pattern (seq_vars["pattern"])
                if seq_vars["split"] and not counter % seq_vars["split"]:
                    sequence.split()
                if seq_vars["descend"] and not counter % seq_vars["descend"]:
                    sequence.reverse()
                if seq_vars["updown"] and not counter % seq_vars["updown"]:
                    sequence.updown()
                if seq_vars["rand"] and not counter % seq_vars["rand"]:
                    shuffle(sequence)
                if seq_vars["shuffle_phrase"]:
                    sequence.shuffle_phrase(seq_vars["shuffle_phrase"])
                if seq_vars["rotate"]:
                    sequence.rotate(seq_vars["rotate"])

                if seq_vars["transpose"] and not perc:
                    sequence.trans(seq_vars["transpose"])

                if rel:
                    sequence.relatives(z_vector)
                yield sequence
                counter += 1

                for option in generators.keys():
                    if option not in part_filters:
                        value = generators[option].next()
                        if option == "divisor":
                            while value == 0:
                                value = generators[option].next()
                        seq_vars[option] = value
        if inputs:
            break

def bar_factory(options):
    """Combine sequences with durations to generate bars"""

    Bar.step = options.step
    Bar.perc = options.perc
    Bar.cardinal = options.cardinal[0]
    Bar.number_lines = options.number_lines
    Bar.play_method = options.play
    Bar.untrans = options.untrans
    if options.play == "samples":
        Bar.samples = options.samples
    if options.play == "fluidsynth":
        Bar.fluid = options.fluid
        Bar.font = options.font[0]

    bcounter = 0
    scounter = 0

    bar_opts = ('rhythm', 'chord', 'randomt', 'metat', 'metarandomt',
                        'volume', 'tempo', 'pause', 'channel', 'program',
                        'bank', 'bell', 'click')

    generators = {}
    bar_vars = {}

    for option in bar_opts:
        value = getattr(options, option)
        if value:
            if value[0] == 'C' or len(value) > 1:
                generators[option] = iterizer(option, value)
                if option != 'volume':
                    value = generators[option].next()
            else:
                value = value[0]
        bar_vars[option] = value

    if options.infinite:
        sequences = repeat(sequence_engine, options)
    else:
        sequences = sequence_engine(options)

    elapsed = Bar.elapsed

    while 1:

        abar = Bar([])

        if options.perc:
            sequence = sequences.next()
            scounter += 1
            for i in sequence:
                event = Event([0])
                event.duration = abs(i)
                event.seq_counter = scounter
                event.__dict__.update(sequence.__dict__)
                abar.append(event)

        else:
            rhythm = bar_vars["rhythm"]
            if bar_vars["chord"] and not bcounter % bar_vars["chord"]:
                if not rhythm:
                    if (bar_vars["randomt"] and not
                        scounter % bar_vars["randomt"]):
                        rhythm = [randint(1, 8)]
                    else:
                        rhythm = [4]
                        abar.rhythm_on = False
                for beat in rhythm:
                    sequence = sequences.next()
                    scounter += 1
                    event = Event(sequence)
                    event.__dict__ .update(sequence.__dict__)
                    event.duration = abs(beat)
                    event.seq_counter = scounter
                    abar.append(event)

            else:
                sequence = sequences.next()
                scounter += 1
                if not rhythm:
                    if (bar_vars["randomt"] and not
                        scounter % bar_vars["randomt"]):
                        rhythm = [randint(1, 8) for _ in sequence]
                    else:
                        rhythm = [1 for _ in sequence]
                        abar.rhythm_on = False

                while 1:
                    if ((len(sequence) == len(rhythm)) or
                        (options.zip and len (sequence) > len(rhythm))):
                        for index, beat in enumerate(rhythm):
                            event = Event([sequence[index]])
                            event.duration = abs(beat)
                            event.__dict__ .update(sequence.__dict__)
                            event.seq_counter = scounter
                            abar.append(event)
                        break
                    else:
                        sequence = sequences.next()

        if bar_vars["metat"]:
            factor = bar_vars["metat"]
        elif (bar_vars["metarandomt"]
        and not bcounter % bar_vars["metarandomt"]):
            factor = randint(1, 4)
        else:
            factor = 1
        for event in abar:
            event.duration *= factor
            if generators.has_key("volume"):
                bar_vars["volume"] =  generators["volume"].next()
            event.volume = bar_vars["volume"]

        abar.elapsed = elapsed
        abar.__dict__.update(bar_vars)
        bcounter += 1
        abar.bar_counter = bcounter

        yield abar

        for option in generators.keys():
            if option != 'volume':
                bar_vars[option] = generators[option].next()
        elapsed += (sum([event.duration for event in abar]) +
                        abar.pause)
        Bar.elapsed = elapsed

def z_relatives(relatives_list):
    """Print Z-related sequences"""
    print
    print
    print "Z-related sets:"
    print
    relatives_list = list(set(relatives_list))
    relatives_list.sort()

    while relatives_list:
        temp_list = []
        temp_list.append(relatives_list.pop())
        while 1:
            if relatives_list and relatives_list[-1][0] == temp_list[0][0]:
                temp_list.append(relatives_list.pop())
            else:
                break
        if len(temp_list) > 1 :
            print   temp_list[0][0]
            for phrases in temp_list:
                for phrase in phrases[1]:
                    print phrase,
                print
            print

def instrument_list(options):
    "Show available instruments"
    play_method = options.play
    if play_method == "fluidsynth":
        fluid = options.fluid
        fluid.send("inst 1 \n")
        sleep(1)
        inst = fluid.recv(4096)
        print
        print inst
        print
        fluid.send("quit\n")
    elif play_method == "samples":
        print
        for (num, sample) in options.samples.items():
            print num, ")", path.basename(sample)
        print
    else:
        print
        print "Sox synth.\n"
        print

def lilyprint(printlist, options, tail=''):
    """Translate output into Lilypond text and print a score or midi file"""
    pitch_dict = { 0:" c", 1:" cis", 2:" d", 3:" dis", 4:" e", 5:" f",
    6:" fis", 7:" g", 8:" gis", 9:" a", 10:" ais", 11:" b", "rest":"r" }
    duration_dict = {1:str(8), 2:str(4), 3:str(4)+"." , 4:str(2) ,
    6:str(2)+".", 7:str(2) + "." + ".", 8:str(1) }
    home = path.expanduser("~")
    last_tempo = ""
    last_time_sig = ""
    last_octave = ""
    last_clef = ""
    score = ["\n\\version \"2.10.33\" { "]

    for l_bar in printlist:

        tempo = (" \n \\tempo " + "4 = " + str(l_bar.tempo) +
        "  \\once \\override Score.MetronomeMark #'transparent = ##t \n")
        if tempo == last_tempo:
            tempo = ""
        else:
            last_tempo = tempo

        highest = max([max(event) for event in l_bar])
        lowest = min([min(event) for event in l_bar])
        middle = (highest + lowest) / 2

        if middle >= 30 :
            octave = 2
        elif middle in range(18, 30):
            octave = 1
        elif middle in range(-18, 18):
            octave = 0
        elif middle in range(-30, -18):
            octave = -1
        elif middle < -30 :
            octave  = -2

        octave =" #(set-octavation " + str(octave) +")"
        if octave == last_octave:
            octave = ""
        else:
            last_octave = octave

        if options.perc:
            clef = "percussion"
        elif middle < 0:
            clef = "bass"
        else:
            clef = "treble"
        clef = "\n \\clef "  + clef
        if clef == last_clef:
            clef = ""
        else:
            last_clef = clef

        numerator = sum([event.duration for event in l_bar])

        if (numerator != 6) and  (numerator != 12) and not numerator % 2 :
            numerator /= 2
            time_sig = " \n \\time " + str(numerator) + "/4"
        else:
            time_sig = " \n \\time " + str(numerator) + "/8"
        if time_sig == last_time_sig:
            time_sig = ""
        else:
            last_time_sig = time_sig

        newbar = ["\n",  time_sig, octave, clef, tempo, "\n"]
        for event in l_bar:
            duration = event.duration
            if duration == 0:
                continue
            if event.volume == 0:
                new_pitches = ['r']
            else:
                if len(event) > 1:
                    openchord , closechord = " < ", " > "
                else:
                    openchord , closechord = "", ""

                new_pitches = [openchord]
                for i in event :
                    tag = [""]
                    if i >= 0:
                        for _ in range(((i)/12 + 1)) :
                            tag.append("'")
                    else:
                        for _ in range(abs(((i)/12 + 1))) :
                            tag.append(",")
                    new_pitches += [pitch_dict[(i) % 12]] + tag
                new_pitches.append(closechord)
            new_pitches = ''.join(new_pitches)

            if duration == 5:
                newbar += [new_pitches, " 2", " ~", new_pitches, "8"]
            elif     duration <= 8:
                newbar += [new_pitches, duration_dict[duration]]
            else:
                semibreves = duration/8
                newbar += [new_pitches, " 1"]
                for _ in range(1, semibreves):
                    newbar += ["~", new_pitches, " 1"]
                if duration % 8 == 5:
                    newbar += [" ~", new_pitches, " 2" , "~", new_pitches, "8"]
                elif duration % 8:
                    newbar += ["~", new_pitches, duration_dict[ duration % 8]]

        score +=  newbar

    score = ''.join(score + ["\n }"])

    score_path = home+"/phraser" + tail
    while path.exists(score_path):
        score_path += "0"

    if not options.overwrite:
        tag = 1
        while path.exists(score_path + ".pdf") or \
        path.exists(score_path  + ".mid"):
            while 1:
                try:
                    int(score_path[-1])
                    score_path = score_path[0:-1]
                except ValueError:
                    break
            score_path = score_path + str(tag)
            tag = tag + 1

    if options.score:
        if tail:
            Bar.display_path = score_path
        else:
            print "\n    Creating "+ score_path + ".pdf..."

        pipe_write(score, score_path)

    if options.midi:
        print "\nCreating "+ score_path + ".mid..."
        midi = " \\score {\n "+  score +"\n \\midi \n{ }\n} "
        pipe_write(midi, score_path)

def pipe_write(score, dest):
    """Pipe to Lilypond"""
    child = Popen(["lilypond", "-ddelete-intermediate-files", "-o", dest, "-" ],
                            stdin=PIPE, stdout=PIPE, stderr=PIPE)
    child.stdin.write(score)
    child.stdin.close()
    result = child.stdout.read()
    return  result

def display(printlist, loc, interval, options, pid):
    """Display recent bars"""
    disp = Thread(group=None,
    target=lilyprint,
    args=(printlist[loc - interval:loc], options),
    kwargs={'tail':"disp" + pid})
    disp.start()


def main(options):
    """Execute"""
    print
    if options.instrument_list:
        instrument_list(options)
        exit()
    pid = str(getpid())
    looped = False
    printlist = []
    abar = Bar([])
    while 1:
        try:
            for abar in bar_factory(options):
                abar.screen()
                if options.play:
                    abar.play()
                    if (options.display
                        and not abar.bar_counter % options.display[0]):
                        display(printlist, abar.bar_counter,
                        options.display[0], options, pid)
                if ( (options.score or options.midi
                    or (options.play and options.display))
                    and not looped):
                    printlist.append(abar)
            if (options.loop and options.play):
                options = optparse(argv[1:])
                options.relatives = False
                looped = True
            else:
                break
        except KeyboardInterrupt:
            break
    #Cleanup:
    if options.play and abar:
        sleep(abar[-1].duration * 30.0 / abar.tempo)
        if options.play == "fluidsynth":
            mesg = []
            for note in range(128):
                mesg.append("noteoff " + str(abar.channel) +
                                " " + str(note) + " \n")
            mesg = ''.join(mesg)
            options.fluid.send(mesg)

    if Sequence.relatives_list:
        z_relatives(Sequence.relatives_list)
    #Print:
    if options.score or options.midi:
        lilyprint(printlist, options)

    if Bar.display_path:
        sleep(1)
        display_path = Bar.display_path + ".pdf"
        if path.exists(display_path):
            remove(display_path)
    print
    return "Done.\n"

if __name__ == '__main__':

    STATUS = main(optparse(argv[1:]))
    exit(STATUS)
