#!/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)