|
|
|
#!/usr/bin/env python3
|
|
|
|
scriptName = "Auto-Techno Generator"
|
|
|
|
version = "0.1.1"
|
|
|
|
author = "Chris Beckstrom"
|
|
|
|
author_email = "chris@chrisbeckstrom.com"
|
|
|
|
# a deterministic techno generator for tidalcycles
|
|
|
|
# input an integer value as a seed
|
|
|
|
# and it spits out some techno ready to play
|
|
|
|
#
|
|
|
|
# use it in emacs like this:
|
|
|
|
# it will generate a random seed
|
|
|
|
#
|
|
|
|
# (defun autotechno()
|
|
|
|
# (interactive)
|
|
|
|
# (insert (shell-command-to-string (concat "/usr/bin/python ~/tidal-auto-techno/auto-techno.py " (number-to-string(random))))))
|
|
|
|
#
|
|
|
|
##################################################
|
|
|
|
########### SETTINGS #############################
|
|
|
|
# what are the lowest and highest bpm values you want?
|
|
|
|
lowestBpm = 92
|
|
|
|
highestBpm = 135
|
|
|
|
|
|
|
|
|
|
|
|
# swing
|
|
|
|
# see https://tidalcycles.org/docs/patternlib/tour/time/#swing
|
|
|
|
minSwing = 0
|
|
|
|
maxSwing = 0.2
|
|
|
|
|
|
|
|
# force everything to happen within 1 bar
|
|
|
|
oneBar = 1
|
|
|
|
|
|
|
|
# stack name
|
|
|
|
# this doesn't matter, can be any string
|
|
|
|
stackName = "techno"
|
|
|
|
|
|
|
|
|
|
|
|
####### PERCUSSION ##############################
|
|
|
|
# some settings for rhythm generation
|
|
|
|
# for euclidean rhythms, define the choices for the 2nd number
|
|
|
|
# eg "[t(3,NN)]"
|
|
|
|
#dividers = [4,8,16]
|
|
|
|
dividers = [16]
|
|
|
|
# how many percussive/rhythmic elements to generate?
|
|
|
|
percCount = 5 # this setting doesn't do anything right now, use "percNames" instead
|
|
|
|
percMidiChanStart = 0 # the starting MIDI channel for perc tracks
|
|
|
|
|
|
|
|
# perc track names
|
|
|
|
# a percussion track will be generated for each item in this list
|
|
|
|
percNames = ["kick", "hh","sd","ohh","rim","cp","tom","ride","cymbal"]
|
|
|
|
# for use generating sequences of 0's and 1's
|
|
|
|
#
|
|
|
|
# perc notes
|
|
|
|
# set some notes for percussion tracks
|
|
|
|
# these notes correspond to notes (C3, C#3, etc)
|
|
|
|
# each percussion track will pick one of these notes
|
|
|
|
# and send note on/off messages to that note
|
|
|
|
percNotes = [0,1,2,3,4,5,6,7]
|
|
|
|
|
|
|
|
#
|
|
|
|
####### MELODY #################################
|
|
|
|
# NOTE about melodic sequences:
|
|
|
|
# as of October 04, 2021 all melodic sequences are all notes
|
|
|
|
# ie no rests. the rhythm/rests is determined by a "struct" value
|
|
|
|
# just like with percussion
|
|
|
|
|
|
|
|
# for melodic sequences, define the choices for range of the notes
|
|
|
|
# eg 12 is one octave, 24 is two, etc
|
|
|
|
noteRange = [12,24]
|
|
|
|
# define the shortest and longest sequence you want
|
|
|
|
# eg if shortest is 7, there will be no melodic sequences shorter than 7
|
|
|
|
shortestMelodic = 3
|
|
|
|
longestMelodic = 10
|
|
|
|
|
|
|
|
# for melodic lines, what divisions do you want to choose from?
|
|
|
|
# eg 4 = quarter notes, 8 = eighth notes
|
|
|
|
# tip: adding more of the same number will weight the randomness toward that
|
|
|
|
melodicDivisions = [8,16,16,16]
|
|
|
|
|
|
|
|
# keep melodies within 1 bar
|
|
|
|
#melodicOneBar = 1
|
|
|
|
# how many
|
|
|
|
# how many melodic elements to generate?
|
|
|
|
melodyCount = 1
|
|
|
|
melodyMidiChanStart = 10
|
|
|
|
|
|
|
|
|
|
|
|
############### PADS ###########################
|
|
|
|
# how many pad tracks to generate
|
|
|
|
padCount = 1
|
|
|
|
|
|
|
|
# how long should the pad pattern be?
|
|
|
|
# tip: add more of the same number to weight that choice
|
|
|
|
padBars = [1,1,1,2,4]
|
|
|
|
# at what speed should the pad chord change?
|
|
|
|
# every bar = 1, every 2 bars = 2, etc
|
|
|
|
padSpeed = [1,2,2,2,4]
|
|
|
|
|
|
|
|
# what MIDI channel should it start on?
|
|
|
|
# padMidiChanStart = melodyMidiChanStart + 1 will start it right after the last melody track
|
|
|
|
padMidiChanStart = melodyMidiChanStart + 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
################ MIDI #####################
|
|
|
|
# on what channel should we send MIDI CC messages?
|
|
|
|
midiCCChannel = 15
|
|
|
|
# list of MIDI CC's:
|
|
|
|
midiCCs = [0,1,2,3,4,5,6]
|
|
|
|
|
|
|
|
|
|
|
|
################# SETUP #################
|
|
|
|
## import stuff
|
|
|
|
import sys
|
|
|
|
import random
|
|
|
|
import math
|
|
|
|
import re
|
|
|
|
import time
|
|
|
|
import decimal
|
|
|
|
from decimal import Decimal
|
|
|
|
|
|
|
|
|
|
|
|
############## PARSE ARGUMENT(S) ###############
|
|
|
|
# this is the 1st argument given via command line
|
|
|
|
# this is used to calculate everything
|
|
|
|
rawinput = sys.argv[1]
|
|
|
|
|
|
|
|
# let's assume it's a hex value
|
|
|
|
# convert to decimal
|
|
|
|
#input = int(hex,int(89))
|
|
|
|
|
|
|
|
################ SEED HEX #####################
|
|
|
|
# make sure the seed is a hex value
|
|
|
|
# why? it doesn't really matter technically
|
|
|
|
# I just think it would be cool
|
|
|
|
# plus, hex values for long numbers are shorter
|
|
|
|
# and easier to read/type
|
|
|
|
# and might be good track names
|
|
|
|
# TODO - this is in progress
|
|
|
|
# if the input is a hex value, we'll know because it starts with 0x
|
|
|
|
if (rawinput.startswith("0x")):
|
|
|
|
# the input is a hex number
|
|
|
|
#print("string starts with 0x")
|
|
|
|
seed = rawinput.upper()
|
|
|
|
else:
|
|
|
|
# the input is NOT a hex number
|
|
|
|
# convert to a hex number
|
|
|
|
seed = hex(int(rawinput)).upper()
|
|
|
|
|
|
|
|
# by now the seed should start with 0X
|
|
|
|
# let's change that X to lowercase
|
|
|
|
seed = seed.replace("X","x")
|
|
|
|
#print("seed: "+seed)
|
|
|
|
|
|
|
|
# here is the seed in decimal format (eg 3892423)
|
|
|
|
decimalSeed = int(seed,16)
|
|
|
|
|
|
|
|
#################### FRONTMATTER ###
|
|
|
|
# put some stuff at the top of the tidal code
|
|
|
|
print("-- raw input: " + str(rawinput))
|
|
|
|
print("-- hex seed: "+seed)
|
|
|
|
print("-- decimal seed: "+str(decimalSeed))
|
|
|
|
print("-- generated at "+str(time.time()))
|
|
|
|
print("-- "+scriptName+" v"+str(version))
|
|
|
|
print("")
|
|
|
|
|
|
|
|
|
|
|
|
########### THIS CHANGES EVERYTHING! #############
|
|
|
|
# set the random seed based on user input
|
|
|
|
random.seed(decimalSeed)
|
|
|
|
##################################################
|
|
|
|
|
|
|
|
indent = " "
|
|
|
|
|
|
|
|
# generate a bpm between lowest and highest
|
|
|
|
# looks like: cps = (BPM/60/4)
|
|
|
|
bpm = "setcps ("+str(random.randrange(lowestBpm, highestBpm, 1))+"/60/4)"
|
|
|
|
#print(bpm)
|
|
|
|
|
|
|
|
|
|
|
|
####################### FUNCTIONS ###################
|
|
|
|
|
|
|
|
def swing(minSwing,maxSwing):
|
|
|
|
"""
|
|
|
|
Generate a swing value for the entire stack
|
|
|
|
"""
|
|
|
|
# the goal is something like this:
|
|
|
|
# swingBy 0.2 8
|
|
|
|
#print("We will swing!!")
|
|
|
|
# choose the first value
|
|
|
|
#firstSwing = (decimal.Decimal(random.range())).quantize(Decimal('.001'))
|
|
|
|
firstSwing = str(decimal.Decimal(random.randrange(0, 20))/100)
|
|
|
|
secondSwing = 8 # if 8: swing 16th notes, if 4: swing 8th notes
|
|
|
|
return "swingBy "+str(firstSwing)+" "+str(secondSwing)
|
|
|
|
|
|
|
|
|
|
|
|
def generateBinary(busyness):
|
|
|
|
"""
|
|
|
|
busy-ness (sic)
|
|
|
|
Create a list of 0's and 1's from which patterns can be
|
|
|
|
created. The higher the busyness, the more 1's in the list,
|
|
|
|
which in turn creates more events in the generated sequence
|
|
|
|
busyness range from 0 to 9, where 9 is most busy,
|
|
|
|
and 0 is no activity at all
|
|
|
|
"""
|
|
|
|
if (busyness == 0):
|
|
|
|
binary = [0,0,0,0,0,0,0,0,0,0,0,1]
|
|
|
|
elif (busyness == 1):
|
|
|
|
binary = [0,0,0,0,0,0,0,0,1]
|
|
|
|
elif (busyness == 2):
|
|
|
|
binary = [0,0,0,0,0,0,0,1,1]
|
|
|
|
elif (busyness == 3):
|
|
|
|
binary = [0,0,0,0,0,0,1,1,1]
|
|
|
|
elif (busyness == 4):
|
|
|
|
binary = [0,0,0,0,0,1,1,1,1]
|
|
|
|
elif (busyness == 5):
|
|
|
|
binary = [0,0,0,0,1,1,1,1,1]
|
|
|
|
elif (busyness == 6):
|
|
|
|
binary = [0,0,0,1,1,1,1,1,1]
|
|
|
|
elif (busyness == 7):
|
|
|
|
binary = [0,0,1,1,1,1,1,1,1]
|
|
|
|
elif (busyness == 8):
|
|
|
|
binary = [0,0,0,0,0,0,1,1,1,1,1,1,1]
|
|
|
|
elif (busyness == 9):
|
|
|
|
binary = [0,0,0,0,1,1,1,1,1,1,1,1,1]
|
|
|
|
return binary
|
|
|
|
|
|
|
|
def genOnOff(limit,busyness,struct):
|
|
|
|
"generate a series of 0's and 1's"
|
|
|
|
zeroesOnes = ""
|
|
|
|
|
|
|
|
# here we can change the list of 0's and 1's
|
|
|
|
# the more 1's, the busier the rhythm will be
|
|
|
|
# and we want to set this per-track
|
|
|
|
# so...
|
|
|
|
#
|
|
|
|
for i in range(limit):
|
|
|
|
zeroesOnes = zeroesOnes + str(random.choice(generateBinary(busyness)))
|
|
|
|
|
|
|
|
# TODO
|
|
|
|
# put a space between each 4 characters
|
|
|
|
# https://stackoverflow.com/questions/9475241/split-string-every-nth-character#9475354
|
|
|
|
#>>> n = 2
|
|
|
|
#>>> [line[i:i+n] for i in range(0, len(line), n)]
|
|
|
|
#['12', '34', '56', '78', '90']
|
|
|
|
#n = 4
|
|
|
|
#zeroesOnes = [zeroesOnes[i:i+n] for i in range(0, len(zeroesOnes), n)]
|
|
|
|
# >>> re.findall('.{1,2}', '123456789')
|
|
|
|
# ['12', '34', '56', '78', '9']
|
|
|
|
#zeroesOnes = re.findall('.{1,2}', zeroesOnes)
|
|
|
|
if (struct == 1):
|
|
|
|
#zeroesOnes = "struct \"{"+str(zeroesOnes)+"}%16\""
|
|
|
|
zeroesOnes = "[{"+str(zeroesOnes)+"}%16]"
|
|
|
|
return(zeroesOnes)
|
|
|
|
else:
|
|
|
|
return(zeroesOnes)
|
|
|
|
|
|
|
|
def genVelocity(limit):
|
|
|
|
"""
|
|
|
|
Generate a series of decimals < 1 to control velocity
|
|
|
|
This adds a more organic feel to the rhythms, and makes
|
|
|
|
them about 20% funkier
|
|
|
|
"""
|
|
|
|
velocities = ""
|
|
|
|
for i in range(limit):
|
|
|
|
#velocities = str((decimal.Decimal(random.choice([0.5,0.75,1],[0.5,75,1]]))).quantize(Decimal('.01')))+" "+str(velocities)
|
|
|
|
velocities = str(random.choice([0.85,0.9,1]))+" "+velocities
|
|
|
|
|
|
|
|
degradeAmt = (decimal.Decimal(random.random())).quantize(Decimal('.01'))
|
|
|
|
# remove trailing space
|
|
|
|
velocities = str(velocities).rstrip()
|
|
|
|
|
|
|
|
return velocities
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def euclid(divider,max):
|
|
|
|
"""
|
|
|
|
generate a euclidean rhythm for tidalcycles
|
|
|
|
the "divider" argument is the 2nd number, and
|
|
|
|
the "max" argument is the maximum integer of the 1st number
|
|
|
|
... because we don't always want a super dense rhythm!
|
|
|
|
"""
|
|
|
|
# like struct "[t(3,8)]"
|
|
|
|
# 3,8 10,16 4,16
|
|
|
|
#secondEuclid = str(random.choice(dividers))
|
|
|
|
secondEuclid = divider
|
|
|
|
firstEuclid = str(random.randrange(2,max))
|
|
|
|
#print("firstEuclid:"+firstEuclid)
|
|
|
|
#print("secondEuclid:"+str(secondEuclid))
|
|
|
|
|
|
|
|
euclidSeed = random.random()
|
|
|
|
# decide whether there are going to be changes in the euclid seq
|
|
|
|
#if (random.random() < random.random()):
|
|
|
|
if (1 > 0):
|
|
|
|
# the first and the second euclid numbers will stay the same
|
|
|
|
# eg no <3 6 5>
|
|
|
|
firstEuclid = firstEuclid
|
|
|
|
else:
|
|
|
|
# the first euclid number WILL sequence (eg <4,8>)
|
|
|
|
#firstEuclid = "<"+str(random.randrange(2,int(random.choice(dividers)))+" "+str(random.randrange(2,int(random.choice(dividers))))+">"
|
|
|
|
if (percName == "kick"):
|
|
|
|
divider = 5 # limit kicks to 5
|
|
|
|
firstEuclid = "<"+str(random.randrange(2,max))+" "+str(random.randrange(2,16))+">"
|
|
|
|
# decide whether the second number will seq
|
|
|
|
if (random.random() > random.random()):
|
|
|
|
# we will do like [t(??,<16,8>)]
|
|
|
|
secondEuclid = "<"+str(random.choice(dividers))+" "+str(random.choice(dividers))+">"
|
|
|
|
# [t(<3 8>,16)]
|
|
|
|
|
|
|
|
#return ("struct \"[t("+str(firstEuclid)+","+str(secondEuclid)+")]\"")
|
|
|
|
return ("[t("+str(firstEuclid)+","+str(secondEuclid)+")]")
|
|
|
|
|
|
|
|
def genNotes(melodicOneBar):
|
|
|
|
"""
|
|
|
|
Generate a series of numbers for use as midi notes
|
|
|
|
accepts 1 or 0, where 1 limits the melody to 16 16th notes
|
|
|
|
and 0 sets the melody to somewhere between the values
|
|
|
|
of shortestMelodic and longestMelodic
|
|
|
|
"""
|
|
|
|
# between let's say 4 notes and 16 notes
|
|
|
|
if (melodicOneBar == 1):
|
|
|
|
melodyLength = 16
|
|
|
|
else:
|
|
|
|
melodyLength = random.randrange(shortestMelodic,longestMelodic)
|
|
|
|
|
|
|
|
melody = ""
|
|
|
|
for i in range(melodyLength):
|
|
|
|
melody = str(random.randrange(0,random.choice(noteRange))) + " " + str(melody)
|
|
|
|
return ("n "+"\"[{"+melody+"}%"+str(random.choice(melodicDivisions))+"]\"")
|
|
|
|
|
|
|
|
def offset():
|
|
|
|
amounts = ["1/4","1/8","1/16","1/16","1/16"]
|
|
|
|
return ("(\""+str(random.choice(amounts))+"\" ~>)")
|
|
|
|
|
|
|
|
def every():
|
|
|
|
# TODO add a switch to choose divisible by 2/4/8 or not
|
|
|
|
bars = random.randint(2,16)
|
|
|
|
return (indent+indent+"$ every "+str(bars)+" ")
|
|
|
|
|
|
|
|
|
|
|
|
def within():
|
|
|
|
firstnum = random.choice([0,0.25,0.5,0.75])
|
|
|
|
secondnum = random.choice([0.25,0.5,0.75,1])
|
|
|
|
return (indent+indent+"within ("+str(firstnum)+","+str(secondnum)+" ")
|
|
|
|
|
|
|
|
|
|
|
|
def scramble():
|
|
|
|
divisions = [4,8,16]
|
|
|
|
return ("scramble "+str(random.choice(divisions)))
|
|
|
|
|
|
|
|
def whenmod():
|
|
|
|
limit = 16
|
|
|
|
lower = random.randint(4,limit)
|
|
|
|
return ("$ whenmod "+str(limit)+" "+str(lower)+" ")
|
|
|
|
|
|
|
|
def stut():
|
|
|
|
"""
|
|
|
|
Stutter
|
|
|
|
stut REPEATS VOLUME TIME
|
|
|
|
"""
|
|
|
|
# choose how many repeats
|
|
|
|
repeats = random.choice([1,1,1,1,1,1,2,2,2,3,3,3,4,4,4,4,4,5])
|
|
|
|
time = random.choice([0.125,0.125,0.125,0.0625,0.0625])
|
|
|
|
return("stut "+str(repeats)+" 1 \""+str(time)+"\"")
|
|
|
|
|
|
|
|
def bite(style):
|
|
|
|
"""
|
|
|
|
Generate something like
|
|
|
|
bite 16 "{2*4 1*2 10*4 8*8}%16
|
|
|
|
if style = nice, just do quarter note repeats
|
|
|
|
if style = wild, do whatever
|
|
|
|
"""
|
|
|
|
|
|
|
|
if (style == "nice"):
|
|
|
|
# do something nice here
|
|
|
|
# either repeat half notes or quarter notes
|
|
|
|
number = random.choice([2,4])
|
|
|
|
# which beat to repeat?
|
|
|
|
beat = random.choice([0,1,2,3])
|
|
|
|
# the goal is something like
|
|
|
|
# bite 4 "{0 0 0 0}%4"
|
|
|
|
bit = beat+beat+beat+beat
|
|
|
|
bit = str(bit)
|
|
|
|
|
|
|
|
else:
|
|
|
|
divisions = [4,8]
|
|
|
|
# number = the divisions to use
|
|
|
|
# like 'bite 16' where 16 is the 'number'
|
|
|
|
number = str(random.choice(divisions))
|
|
|
|
|
|
|
|
# generate like 1*4 8*4 2*2 1*2 9*1
|
|
|
|
repeatChoices = [1,2,4]
|
|
|
|
# how long should it be?
|
|
|
|
length = int(random.randint(4,8))
|
|
|
|
bit = ""
|
|
|
|
for i in range(length):
|
|
|
|
bit = str(random.randint(0,int(number)))+"*"+str(random.choice(repeatChoices))+" "+bit
|
|
|
|
|
|
|
|
# since bite will always be encapsulated by ( )
|
|
|
|
# eg whenmod 8 7 (bite 4 "2*4")
|
|
|
|
# we don't need to prepend with $ or
|
|
|
|
return ("bite "+str(number)+ " \"{"+bit+"}%"+str(number)+"\"")
|
|
|
|
|
|
|
|
def offset():
|
|
|
|
offsetChoices = [0.125,0.125,0.125,0.125,0.1875,0.1875,0.1875]
|
|
|
|
return ("("+str(random.choice(offsetChoices))+" ~>)")
|
|
|
|
|
|
|
|
def degrade():
|
|
|
|
degradeAmt = (decimal.Decimal(random.random())).quantize(Decimal('.01'))
|
|
|
|
return "degradeBy "+str(degradeAmt)
|
|
|
|
|
|
|
|
def fills():
|
|
|
|
"""
|
|
|
|
Generate some things that would be cool at the end of phrases,
|
|
|
|
eg whenmod 8 7 (do something)
|
|
|
|
But nothing too intense
|
|
|
|
"""
|
|
|
|
# like whenmod 8 7
|
|
|
|
fillChoices = ["rev","rev",scramble(),bite("nice"),bite("nice")]
|
|
|
|
# how many filles do we want?
|
|
|
|
fills = ""
|
|
|
|
for i in range(1,10):
|
|
|
|
# roll the dice
|
|
|
|
whenmods = ["8 7","16 15","16 14","32 31","64 63","32 30","8 7"]
|
|
|
|
fills = str(random.choice(fillChoices))+" "+fills
|
|
|
|
return "whenmod "+str(random.choice(whenmods))+" ("+fills.rstrip()+")"
|
|
|
|
|
|
|
|
def sometimesBeQuiet():
|
|
|
|
"""
|
|
|
|
Generate a gain pattern so that sometimes a track
|
|
|
|
is silent, and other times it isn't
|
|
|
|
"""
|
|
|
|
#def genOnOff(limit,busyness,struct):
|
|
|
|
# decide whether to do this at all
|
|
|
|
if (random.randint(0,10) > 0):
|
|
|
|
# for now, 8 bar phrases
|
|
|
|
# (# gain "[0 1 0 0 1]/8")
|
|
|
|
# how many cycles to make?
|
|
|
|
quietCycles = random.choice([8])
|
|
|
|
|
|
|
|
# this generates a sequence like 011010110
|
|
|
|
quietBinary = genOnOff(quietCycles,4,0)
|
|
|
|
# split 001001 into 0 0 1 0 0 1
|
|
|
|
quietBinary = list(quietBinary)
|
|
|
|
finalQuietBinary = ""
|
|
|
|
for i in quietBinary:
|
|
|
|
finalQuietBinary = i + " " + finalQuietBinary
|
|
|
|
|
|
|
|
# now the pattern looks like 0 1 0 0 1 0 0 1
|
|
|
|
print(indent+indent+"$ degradeBy \"<"+str(finalQuietBinary).rstrip()+">/8\"")
|
|
|
|
|
|
|
|
########################### LISTS ##########################
|
|
|
|
|
|
|
|
# a collection of typical-ish kick drum patterns
|
|
|
|
bdPats=["[t*4]","[tttt*2]","[tttt*2?]","t(3,8)","[t~~~~~<~t~~>~~~t~~<~t>~~]"]
|
|
|
|
hhPats=["[t*16]","[tt*2]*4","[~t]*4","[t*16]"]
|
|
|
|
sdPats=["[~t]*2","[t(3,8)]","[[~~~t][~t]]*2"]
|
|
|
|
cpPats=["[~t]*2","[t(3,8)]","[[~~~t][~t]]*2"]
|
|
|
|
cyPats=["[t*4]","[t*8]","[t~~~]","[t~~[~~~t]]","[<t~~~>~~~]"]
|
|
|
|
|
|
|
|
# a collection of misc rhythms
|
|
|
|
rhythmLibrary = ["[1*4]","[1*8]","[1*16]","[01]*2","[0101]","[1*16]","[1*16?]","[111111[1001][01]]/2","[{100}%16]","[{101011010101}%16]","[{10100}%16]","[{10001}%16]","[{10101}%16]","[{1001010}%16]","[{1011010}%16]","[{10101000100}%16]","[{10101001010}%16]","[{10010010010}%16]","[{1010101001010}%16]","[{1001001001010}%16]","[11*2]*4","[100000<0100>000100<01>00]","[1[10000001]]","[1111*2]","[[0001][01]]*2","[0010000100000001]","[[0001][01]]*2","[[01]1[1001][01]]","[[01]1[1001][0001]]","[11000010]*2","[[0001]000]","[[0001]]*4"]
|
|
|
|
|
|
|
|
############### It's assemble time!! ###################
|
|
|
|
|
|
|
|
print("do")
|
|
|
|
#print("")
|
|
|
|
|
|
|
|
# create "let" statements
|
|
|
|
print(indent+"let fourToTheFloor = 0")
|
|
|
|
print(indent+" -- control density per track")
|
|
|
|
for i in percNames:
|
|
|
|
#print("let "+i+"M = \"["+genOnOff(4,5,0)+"]\"")
|
|
|
|
print(indent+" "+i+"M = \"[1111]\"")
|
|
|
|
|
|
|
|
#print("")
|
|
|
|
|
|
|
|
|
|
|
|
# actually assemble the stack!
|
|
|
|
print(indent+"p \""+stackName+"\" ")
|
|
|
|
|
|
|
|
# swing?
|
|
|
|
# roll the dice to see if there will be any swing at all
|
|
|
|
if (random.randint(0,10) > 7):
|
|
|
|
print(indent+indent+"$ "+str(swing(minSwing,maxSwing)))
|
|
|
|
|
|
|
|
# stack stuff like bite, scramble, etc. will go here
|
|
|
|
print(indent+indent+"-- fills")
|
|
|
|
# roll the dice up to N times
|
|
|
|
for i in range(1,8):
|
|
|
|
if (random.randint(0,10) > 3):
|
|
|
|
print(indent+indent+"$ "+fills())
|
|
|
|
|
|
|
|
# how many "fun" things to add?
|
|
|
|
# a list of things to do to patterns to make them more interesting
|
|
|
|
# these happen BEFORE struct, note, etc.
|
|
|
|
#interestingThings = [stut(),bite(),"rev",scramble(),offset()]
|
|
|
|
interestingThings = [stut(),"rev",scramble(),offset(),degrade()]
|
|
|
|
def funThings():
|
|
|
|
wholeEnchilada = ""
|
|
|
|
# add between 1 and 4 "fun" things
|
|
|
|
# (things like `every 3 (scramble 4)`)
|
|
|
|
funThings = random.randint(1,3)
|
|
|
|
for i in range(funThings):
|
|
|
|
#print(str(every) + "$ " + str(output))
|
|
|
|
|
|
|
|
# for each iteration, choose an interestingThing
|
|
|
|
# indent + "every N" + ( rev )
|
|
|
|
wholeEnchilada = str(every()+"("+str(random.choice(interestingThings))+")\n"+wholeEnchilada).rstrip()
|
|
|
|
return wholeEnchilada
|
|
|
|
|
|
|
|
# the top of the stack
|
|
|
|
print(indent+indent+"$ stack [")
|
|
|
|
|
|
|
|
# this is an empty track to make dealing with commas easier
|
|
|
|
# just 4 to the floor in case you want it
|
|
|
|
print(indent+indent+"-- four to the floor")
|
|
|
|
print(indent+indent+"degradeBy fourToTheFloor $ struct \"[t*4]\" $ n \"0\" # midichan 0,")
|
|
|
|
|
|
|
|
# generate rhythm tracks
|
|
|
|
# set the starting midi channel
|
|
|
|
percMidiChan = 0
|
|
|
|
|
|
|
|
########################################################
|
|
|
|
## this is where each "track" is generated
|
|
|
|
## percCount = how many rhythmic (non-melodic) tracks
|
|
|
|
## will be created
|
|
|
|
for i in percNames:
|
|
|
|
|
|
|
|
percName = i
|
|
|
|
|
|
|
|
# print the name of the track (from the percNames list)
|
|
|
|
print(indent+indent+"-------------- "+i+" ---------------")
|
|
|
|
|
|
|
|
# MASK: make this so once the entire thing is generated, it's easy to
|
|
|
|
# alter how dense the pattern is, by altering "let nnM = "[1111]
|
|
|
|
print(indent+indent+"mask "+i+"M")
|
|
|
|
|
|
|
|
# sometimes be quiet
|
|
|
|
#if (i != "kick"):
|
|
|
|
# sometimesBeQuiet()
|
|
|
|
|
|
|
|
#### VARIATIONS #####
|
|
|
|
# insert some funStuff, maybe
|
|
|
|
if (random.randint(0,10) > 3):
|
|
|
|
print(funThings())
|
|
|
|
|
|
|
|
|
|
|
|
#### OFFSET ###
|
|
|
|
# offset the pattern by a certain amount
|
|
|
|
# this can make things much funkier
|
|
|
|
if (random.randint(0,10) > 6):
|
|
|
|
print(indent+indent+"$ "+offset())
|
|
|
|
|
|
|
|
|
|
|
|
##### THE RHYTHMS #####
|
|
|
|
# how many bars to create?
|
|
|
|
rhythmLengthChoices = [1,1,1,1,1,1,2,2]
|
|
|
|
rhythmLength = random.choice(rhythmLengthChoices)
|
|
|
|
|
|
|
|
# for the number of bars, do this:
|
|
|
|
fullRhythm = ""
|
|
|
|
for i in range(rhythmLength):
|
|
|
|
|
|
|
|
# choose between random, type-specific (bd,hh,etc),
|
|
|
|
# rhythm library, or euclidean rhythms
|
|
|
|
rChoice = random.choice([0,1,1,1,2,3])
|
|
|
|
rhythm = "" # placeholder for the rhythm
|
|
|
|
if (rChoice == 0):
|
|
|
|
# if choice = 0, choose hardcoded rhythms
|
|
|
|
# if this is the first iteration, ie "kick"
|
|
|
|
# choose from some hard-coded kick rhythms
|
|
|
|
# 2nd? same for hh
|
|
|
|
# 3rd? same for sd
|
|
|
|
if (i == 0):
|
|
|
|
rhythm = str(random.choice(bdPats))
|
|
|
|
elif (i == 1):
|
|
|
|
rhythm = str(random.choice(hhPats))
|
|
|
|
elif (i == 2):
|
|
|
|
rhythm = str(random.choice(sdPats))
|
|
|
|
elif (i == 3):
|
|
|
|
rhythm = str(random.choice(cpPats))
|
|
|
|
elif (i == 4):
|
|
|
|
rhythm = str(random.choice(cyPats))
|
|
|
|
else:
|
|
|
|
# print("LIBRARY")
|
|
|
|
rhythm = str(random.choice(rhythmLibrary))
|
|
|
|
#rhythm = "struct \""+rhythm+"\""
|
|
|
|
|
|
|
|
elif (rChoice == 1):
|
|
|
|
# GENERATE A SERIES OF 0's and 1's
|
|
|
|
# choose how long the rhythm will be
|
|
|
|
# if (oneBar == 1):
|
|
|
|
# percLength = 16
|
|
|
|
# else:
|
|
|
|
# percLength = int(random.randrange(4,8))
|
|
|
|
percLength = int(random.randrange(4,10))
|
|
|
|
#percSequence = genOnOff(percLength)
|
|
|
|
#print(","+str(genOnOff(10)))
|
|
|
|
#print(str(percSequence))
|
|
|
|
# how busy will it be?a
|
|
|
|
# choose a random value
|
|
|
|
# guide the random value based on the name
|
|
|
|
# since we don't really want a kick on every 16th note
|
|
|
|
# lots of hh = good, lots of cymbal = maybe not
|
|
|
|
if (percName == "kick"):
|
|
|
|
busyness = random.randrange(1,3)
|
|
|
|
elif (percName == "sd"):
|
|
|
|
busyness = random.randrange(0,4)
|
|
|
|
elif (percName == "cp"):
|
|
|
|
busyness = random.randrange(0,4)
|
|
|
|
elif (percName == "hh"):
|
|
|
|
busyness = random.randrange(3,9)
|
|
|
|
elif (percName == "ride"):
|
|
|
|
busyness = random.randrange(0,6)
|
|
|
|
elif (percName == "cymbal"):
|
|
|
|
busyness = random.randrange(0,1)
|
|
|
|
else:
|
|
|
|
busyness = random.randrange(0,7)
|
|
|
|
generateBinary(busyness)
|
|
|
|
rhythm = str(genOnOff(percLength,busyness,1))
|
|
|
|
|
|
|
|
elif (rChoice == 2):
|
|
|
|
# GENERATE EUCLIDEAN RHYTHMS
|
|
|
|
|
|
|
|
# treating the kick like the other tracks often generates
|
|
|
|
# way too many events (like [t(11,16)]) which is usually
|
|
|
|
# way too much for a techno kick!
|
|
|
|
# here's I'm trying to moderate that
|
|
|
|
if (percName == "kick"):
|
|
|
|
# do something
|
|
|
|
maxEuclidEvents = 5
|
|
|
|
else:
|
|
|
|
maxEuclidEvents = 13
|
|
|
|
|
|
|
|
#print("MAX EUCLID EVENTS: "+str(maxEuclidEvents))
|
|
|
|
rhythm = str(euclid(random.choice(dividers),maxEuclidEvents))
|
|
|
|
#print(indent+indent+euclid(random.choice(dividers)))
|
|
|
|
#
|
|
|
|
else:
|
|
|
|
# print("LIBRARY")
|
|
|
|
rhythm = str(random.choice(rhythmLibrary))
|
|
|
|
#rhythm = "struct \""+rhythm+"\""
|
|
|
|
|
|
|
|
fullRhythm = str(rhythm) + " " + fullRhythm
|
|
|
|
# remove the trailing space
|
|
|
|
fullRhythm = fullRhythm.rstrip()
|
|
|
|
|
|
|
|
# add "struct" to the beginning
|
|
|
|
# and enclose in < >
|
|
|
|
print(indent+indent+"$ struct \"<"+fullRhythm+">\"")
|
|
|
|
|
|
|
|
# roll the dice to get a length of the velocity pattern
|
|
|
|
velocityLength = random.randint(2,16)
|
|
|
|
|
|
|
|
notes = indent+indent+"$ n "+str(random.choice(percNotes))
|
|
|
|
velocity = indent+indent+"# gain \"{"+genVelocity(int(random.randrange(2,16)))+"}%16\""
|
|
|
|
midi = indent+indent+"# midichan "+str(percMidiChan)
|
|
|
|
#print (# midichan "+str(percMidiChan)+" \n"+indent+indent"# gain \"{"+genVelocity(int(random.randrange(2,16)))+"}%16"+",")
|
|
|
|
print(notes+"\n"+velocity+"\n"+midi+",")
|
|
|
|
percMidiChan = percMidiChan + 1
|
|
|
|
|
|
|
|
# generate melodic tracks
|
|
|
|
melodicMidiChan = melodyMidiChanStart
|
|
|
|
#melodyMidiChanStart = 7
|
|
|
|
for i in range(melodyCount):
|
|
|
|
print(indent+indent+"------------ bassline -------")
|
|
|
|
print(indent+indent+"degradeBy 0 ")
|
|
|
|
#print(", ")
|
|
|
|
busyness = random.choice([0,1,2,3,4,5,6,6,6,6,7,7,7,7,8,8,8,9,9])
|
|
|
|
if (oneBar == 1):
|
|
|
|
print(indent+indent+"$ struct \""+genOnOff(16,(busyness),1)+"\"")
|
|
|
|
notes = genNotes(1)
|
|
|
|
print(indent+indent+"$ "+notes)
|
|
|
|
else:
|
|
|
|
# how many 16th notes?
|
|
|
|
thisLength = random.randint(3,16)
|
|
|
|
#genOnOff(8,(busyness))
|
|
|
|
print(indent+indent+"$ struct \""+genOnOff(thisLength,(busyness),1)+"\"")
|
|
|
|
notes = genNotes(0)
|
|
|
|
print(indent+indent+"$ "+str(notes))
|
|
|
|
print(indent+indent+"# midichan "+str(melodicMidiChan)+" + n (-24),")
|
|
|
|
melodicMidiChan = melodicMidiChan + 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### generate pads/chords
|
|
|
|
# method: grab some of the notes from the last melodic/bassline
|
|
|
|
# and build some chords with them that go with the bassline
|
|
|
|
# sort of like the reverb trick, to create a pad from any melodic material
|
|
|
|
|
|
|
|
# after we're done making the bassline, the melodicMidiChan will be
|
|
|
|
# the last midi channel +1, so we can use that for the pad channel
|
|
|
|
padMidiChan = melodicMidiChan
|
|
|
|
for i in range(padCount):
|
|
|
|
print(indent+indent+"------------ pad -------")
|
|
|
|
|
|
|
|
# the goal:
|
|
|
|
# n "[3,12,20,34]"
|
|
|
|
# where all those numbers are played by the bassline
|
|
|
|
|
|
|
|
# get the numbers from the bassline
|
|
|
|
# it's already stored in the variable "notes"
|
|
|
|
# but with brackets like this: [{23 6 5 23}%16]
|
|
|
|
# we'll use regex to remove the extra stuff
|
|
|
|
padNotes = notes.replace('[{','')
|
|
|
|
padNotes = padNotes.replace(']','')
|
|
|
|
padNotes = padNotes.replace('}','')
|
|
|
|
padNotes = padNotes.replace('"','')
|
|
|
|
padNotes = padNotes.replace('%16','')
|
|
|
|
padNotes = padNotes.replace('n ','')
|
|
|
|
padNotes = padNotes.rstrip()
|
|
|
|
|
|
|
|
|
|
|
|
#print("notes:"+str(padNotes))
|
|
|
|
|
|
|
|
# now that we have the notes, as strings, put them into a list
|
|
|
|
padNotesList = padNotes.split(" ")
|
|
|
|
# convert this list to a dictionary so there aren't duplicates
|
|
|
|
# https://www.w3schools.com/python/python_howto_remove_duplicates.asp
|
|
|
|
padNotes = list(dict.fromkeys(padNotes))
|
|
|
|
|
|
|
|
#print(str(padNotesList))
|
|
|
|
|
|
|
|
# make a chord
|
|
|
|
padsStart = "\"<"
|
|
|
|
padsEnd = ">"
|
|
|
|
|
|
|
|
# generate a sequence of chords
|
|
|
|
|
|
|
|
pads = ""
|
|
|
|
for i in range(random.choice(padBars)):
|
|
|
|
pads = "["+ random.choice(padNotesList)+","+random.choice(padNotesList)+","+random.choice(padNotesList)+","+random.choice(padNotesList)+"]"+str(pads)
|
|
|
|
|
|
|
|
# build the track
|
|
|
|
print(indent+indent+"degradeBy 0 ")
|
|
|
|
sometimesBeQuiet()
|
|
|
|
|
|
|
|
# roll the dice, choose between a long steady pad
|
|
|
|
# or more rhythmic hits
|
|
|
|
#if (random.choice([1,1]) == 1):
|
|
|
|
if (random.choice([0,1]) == 1):
|
|
|
|
print(indent+indent+"$ struct \""+genOnOff(16,(busyness),1)+"\"")
|
|
|
|
|
|
|
|
print(indent+indent+"$ n "+padsStart+pads+padsEnd+"/"+str(random.choice(padSpeed))+"\"")
|
|
|
|
print(indent+indent+"# midichan "+str(padMidiChan)+" + n (0),")
|
|
|
|
|
|
|
|
############## MIDI CCS #############
|
|
|
|
# the goal:
|
|
|
|
# do reverb/delay throws
|
|
|
|
# basically create step sequences that modulate CC values
|
|
|
|
# this assumes that midi cc 0-N are mapped to effects wherever
|
|
|
|
# the output of this script is being sent
|
|
|
|
# eg delay sends, reverb sends, etc
|
|
|
|
|
|
|
|
print(indent+indent+"--------- MIDI CC's ---------------")
|
|
|
|
print(indent+indent+"stack [")
|
|
|
|
|
|
|
|
# the kick is on midi channel 0
|
|
|
|
# don't do midi CC's for that track, start at 1
|
|
|
|
midiChannel = 1
|
|
|
|
|
|
|
|
for i in midiCCs:
|
|
|
|
# generate MIDI ccs rhythmically or over time
|
|
|
|
|
|
|
|
|
|
|
|
# how many steps?
|
|
|
|
ccLength = random.randint(2,16)
|
|
|
|
|
|
|
|
if (random.randint(0,1) == 0):
|
|
|
|
# don't do any CC value > 0
|
|
|
|
ccFinalPattern = "struct \"t*1\" $ ccn "+str(i)+" # ccv \"{0}%"
|
|
|
|
ccEndNumber = 1
|
|
|
|
|
|
|
|
else:
|
|
|
|
ccPattern = str(genOnOff(ccLength,5,0))
|
|
|
|
|
|
|
|
ccPattern = ""
|
|
|
|
for v in range(ccLength):
|
|
|
|
ccPattern = str(random.randint(0,127))+" "+str(ccPattern)
|
|
|
|
|
|
|
|
# how fast should these change?
|
|
|
|
ccSpeed = random.choice([0,0,0,0,0,0,0])
|
|
|
|
if (ccSpeed == 0):
|
|
|
|
# slow
|
|
|
|
ccEndNumber = random.choice([1,2,3,4,5])
|
|
|
|
else:
|
|
|
|
# fast
|
|
|
|
ccEndNumber = random.choice([8,16])
|
|
|
|
|
|
|
|
ccPattern = ccPattern.rstrip()
|
|
|