"""Tests and modifications""" from random import randint as _randint, shuffle as _shuffle def _reorder(func): """Reorder the result of a function to match the input order""" def wrapper3(seq, *args): "As above" sortseq = sorted(seq) inds = [sortseq.index(i) for i in seq] func(seq, *args) seq.sort() seq.order = [seq.order[i] for i in inds] wrapper3.__name__ = func.__name__ wrapper3.__doc__ = func.__doc__ return wrapper3 def _stepwrap(func): """Decorator to reinitialise a sequence with new step values.""" def wrapper4(seq, *args): "As above" if len(seq) > 1: base = min(seq) steps = seq.all_steps() func(steps, *args) seq.__init__(*steps, cardinal=seq.cardinal, steps=True) seq.transpose(base - min(seq)) wrapper4.__name__ = func.__name__ wrapper4.__doc__ = func.__doc__ return wrapper4 ###Tests #Boolean def openscale(seq): "No consecutive semitones. Boolean." return seq.open_scale def closedscale(seq): "No room for non-consecutive semitones. Boolean." return seq.closed_scale def primes(seq): "Show unique results in prime form. Boolean." return seq.is_prime() def antiprimes(seq): "Show unique results in prime form. Boolean." return seq == seq.antiprime def fortes (seq): "Show only Forte primes. Boolean." return seq.is_forte def mirror(seq): "Sequences whose primes equal their prime inversions. Boolean." return seq.is_mirror def follow(seq): "Influenced by leader instances. Boolean." if any(v is not None for v in seq.master_dict.values()): return all(getattr(seq, attr) == seq.master_dict[attr] for attr in seq.master_dict) #List def includes(seq, alist): "Step values to be included. Repeatable." return all(i in seq.prime_steps for i in alist) def excludes(seq, alist): "Step values to be excluded. Repeatable." return not any(i in seq.prime_steps for i in alist) def only(seq, alist): "Step values to be exclusively included. Repeatable." return set(seq.prime_steps) <= set(alist) def allonly(seq, alist): "Step values to all be exclusively included. Repeatable." return set(seq.prime_steps) == set(alist) ##Pcset filters def degrees(seq, alist): "Intervals to be included. Repeatable." return all(i in seq.pcset() for i in alist) def nondegrees(seq, alist): "Intervals to be excluded. Repeatable." return not any(i in seq.pcset() for i in alist) #Range def length(seq, minmax): "Values or dash-separated ranges (x-y) for sequence length." return minmax[0] <= len(seq) <= minmax[1] def symmetry(seq, minmax): "Values or dash-separated ranges (x-y) for sequence symmetry." return minmax[0] <= seq.symmetry <= minmax[1] def variety(seq, minmax): "Values or dash-separated ranges (x-y) for number of different step-values." return minmax[0] <= seq.variety <= minmax[1] def zdepth(seq, minmax): """Values or dash-separated ranges (x-y) for number of different values in sequence's Z-vector.""" return minmax[0] <= seq.z_depth <= minmax[1] def zvariety(seq, minmax): """Values or dash-separated ranges (x-y) for number of non-zero values in sequence's Z-vector.""" return minmax[0] <= seq.z_variety <= minmax[1] def consonance(seq, minmax): "Values or dash-separated ranges (x-y) for sequence consonance." return minmax[0] <= seq.consonance <= minmax[1] #Value and range def fine(seq, alist): """Step values followed by single value or dash-separated range (x y-z) for number of occurences. Repeatable.""" for num in range(len(alist) // 3): nums = [alist[3 * num]] + sorted(alist[3 * num + 1:3 * num + 3]) if not nums[1] <= seq.prime_steps.count(nums[0]) <= nums[2]: return False return True ##Z-vector filters def ztest(seq, alist): """Z-vector values, followed by single value or dash-separated range (x y-z) for number of occurences. Repeatable.""" for num in range(len(alist) // 3): nums = [alist[3 * num]] + sorted(alist[3 * num + 1:3 * num + 3]) if 0 < nums[0] <= len(seq.vector): if not nums[1] <= seq.vector[nums[0] - 1] <= nums[2]: return False return True ###Modifications: #Boolean def primify(seq): "Convert results to prime form. Boolean." if seq: seq.order = list(range(len(seq.prime))) seq.transpose(-min(seq)) def antiprimify(seq): "Convert results to antiprime form. Boolean." seq.__init__(*seq.antiprime, cardinal=seq.cardinal) def invert(seq): "Invert sequence. Boolean." seq.__init__(*seq.inverted(), cardinal=seq.cardinal) def backwards(seq): "Reverse output. Boolean." seq.order.reverse() @_reorder def svoice(seq): "Simple voicing algorithm. Boolean." seq.order = [i + len(seq.prime) * (1, 0, -1)[i % 3] for i in seq.order] @_reorder def compand(seq, value): "Put all values within given octave range." if seq and value: ran = range(abs(value)) seq.order = [j % len(seq.prime) + len(seq.prime) * ran[i % value] * abs(value) // value for i, j in enumerate(seq.order)] def lead(seq): "Influences follower instances. Boolean." for name in seq.master_dict: seq.master_dict[name] = getattr(seq, name) def rmultiply(seq): "Multiply sequence elements by a random integer (2-4). Boolean." seq *= _randint(2, 4) ##Modify via either pcset or steps def rand(seq): "Randomise sequence order. Boolean." _shuffle(seq.order) @_stepwrap def srand(steps): "Randomise step order. Boolean." _shuffle(steps) def split(seq): "Alternating high and low values. Boolean." seq.order = [seq.order.pop(-(i % 2)) for i in range(len(seq))] @_stepwrap def ssplit(steps): "Alternating big and small steps. Boolean." steps[:] = [steps.pop(-(i % 2)) for i in range(len(steps))] def sort(seq): "Sort sequence. Boolean." seq.sort() @_stepwrap def ssort(steps): "Sort steps. Boolean." steps.sort(key=abs) def updown(seq, value): "Notes go up then down n times." forward = value > 0 value %= len(seq) if seq and value: piles = [[] for _ in range(value)] for i, j in enumerate(seq.order): piles[i % len(piles)].append(j) for i, j in enumerate(piles): if (i % 2 and forward) or (not i % 2 and not forward): j.reverse() seq.order = sum(piles, []) @_stepwrap def supdown(steps, value): "Steps go up and down n times." forward = value > 0 value %= len(steps) if value: piles = [[] for _ in range(value)] for i, j in enumerate(steps): piles[i % len(piles)].append(j) for i, j in enumerate(piles): if (i % 2 and forward) or (not i % 2 and not forward): j.reverse() steps[:] = sum(piles, []) #Single value def transpose(seq, value): "The number of semitones to transpose output." seq.transpose(value) def strans(seq, value): "Transpose by steps" seq.transpose(sum(seq.steps()[:value])) def spin(seq, value): "The number to transpose each sequence element within its range." old_order = seq.order seq.transpose(value) seq.order = old_order def pcspin(seq, value): "The number of steps to rotate the underlying pitch-classes." seq.order = [(rem + value) % len(seq.prime) + octave * len(seq.prime) for octave, rem in (divmod(i, len(seq.prime)) for i in seq.order)] @_reorder def rvoice(seq, value): "The range to voice notes beyond the octave." if value and len(seq) > 1: limits = ((value, seq.cardinal) if value < 0 else (0, seq.cardinal + value)) octs = value // seq.cardinal + ( -1 if value < 0 else 2) compand(seq, 1) for i, note in enumerate(seq): if (i % octs and limits[0] <= note + (seq.cardinal * (i % octs)) <= limits[1]): seq.voice(i, i % octs) @_reorder def ivoice(seq, value): "Voicing by minimum interval between notes." for i in range(len(seq) - 1): seq.sort(reverse=value < 0) for j in range(i + 1, len(seq)): while abs(seq[j] - seq[i]) < abs(value): seq.voice(j, abs(value)//value) def multiply(seq, value): "Multiply sequence by an integer." seq *= value def emultiply(seq, value): "Multiply sequence elements by an integer." seq.emult(value) ##Modify via either pcset or steps def rotate(seq, value): "The number of elements to rotate each sequence." seq.order = seq.order[-value:] + seq.order[:-value] def srotate(seq, value): "The number of steps to rotate each sequence." seq.order = ([i - len(seq.prime) for i in seq.order[-value:]] + seq.order[:-value]) def shuffle(seq, value): "A value determining the element shuffle offset." rev = value < 0 value %= len(seq) if seq and value: piles = [[] for _ in range(value)] for i, j in enumerate(seq.order): piles[i % len(piles)].append(j) if rev: piles.reverse() seq.order = sum(piles, []) @_stepwrap def sshuffle(steps, value): "A number determining the step shuffle offset." rev = value < 0 value %= len(steps) if value: piles = [[] for _ in range(value)] for i, j in enumerate(steps): piles[i % len(piles)].append(j) if rev: piles.reverse() steps[:] = sum(piles, []) #List def reorder(seq, alist): """Space-separated sequence replacing the pitches; the order stays the same. Repeatable.""" old_order = seq.order seq.__init__(*alist, cardinal=seq.cardinal) seq.order = old_order def sreorder(seq, alist): """Space-separated sequence replacing the steps; the order stays the same. Repeatable.""" if seq: base = min(seq) old_order = seq.order seq.__init__(*alist, cardinal=seq.cardinal, steps=True) seq.order = old_order seq.transpose(base - min(seq)) ##Modify via either pcset or steps def pattern(seq, alist): """Space-separated sequence index numbers determining the order of elements. Repeatable""" if seq: seq.order = [seq.order[i % len(seq)] for i in alist] @_stepwrap def spattern(steps, alist): """Space-separated sequence index numbers determining the order of steps. Repeatable""" steps[:] = [steps[i % len(steps)] for i in alist]