#!/usr/bin/env python3 # 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 # force everything to happen within 1 bar oneBar = 1 # stack name # this doesn't matter, can be any string stackName = "techno" ####### PERCUSSION ############################## # for euclidean rhythms, define the choices for the 2nd number # eg "[t(3,NN)]" dividers = [4,8,16] # how many percussive/rhythmic elements to generate? percCount = 5 percMidiChanStart = 0 # perc note # set some notes for percussion tracks percNotes = [0,1,2,3] # perc track names percNames = ["kick", "hh", "sd", "cp", "five"] # for use generating sequences of 0's and 1's # # # ####### 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,36] # define the shortest and longest sequence you want # eg if shortest is 7, there will be no melodic sequences shorter than 7 shortestMelodic = 4 longestMelodic = 16 # 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 = 7 ## import stuff import sys import random import math import re import time ################################################## # this is the 1st argument given via command line # this is used to calculate everything # seeds should be 6 digits or longer input = sys.argv[1] ### FRONTMATTER ### print("-- seed: " + str(input)) print("-- generated at "+str(time.time())) print("") ########### THIS CHANGES EVERYTHING! ############# # set the random seed based on user input random.seed(input) ################################################## 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 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,1,1,1,1,1,1,1,1] elif (busyness == 9): binary = [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\"" return(zeroesOnes) else: return(zeroesOnes) def euclid(divider): "generate a euclidean rhythm for tidalcycles" # like struct "[t(3,8)]" # 3,8 10,16 4,16 #secondEuclid = str(random.choice(dividers)) secondEuclid = divider firstEuclid = str(random.randrange(2,divider)) #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))))+">" firstEuclid = "<"+str(random.randrange(2,divider))+" "+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)+")]\"") 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 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(["1/4","1/8","1/16","1/16","1/16"]) return("stut "+str(repeats)+" 1 \""+str(time)+"\"") def bite(): """ Generate something like bite 16 "{2*4 1*2 10*4 8*8}%16 """ divisions = [4,8] 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,16)) 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 "+number+ " \"{"+bit+"}%"+number+"\"") def offset(): offsetChoices = [0.125,0.125,0.125,0.125,0.1875,0.1875,0.1875] return ("("+str(random.choice(offsetChoices))+" ~>)") 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 = [stut(),"rev",scramble()] # how many filles do we want? fills = "" for i in range(1,10): # roll the dice whenmods = ["8 7","4 3","16 15","16 14","32 31"] fills = str(random.choice(fillChoices))+" "+fills return "whenmod "+str(random.choice(whenmods))+" ("+fills.rstrip()+")" ########################### LISTS ########################## # a collection of typical-ish kick drum patterns bdPats= ["[t*4]","[t t t t*2]","[t t t t*2?]","t(3,8)","[t~~~ ~~<~t~~>~ ~~t~ ~<~t>~~]"] hhPats= ["[t*16]","[t t*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]]","[~~~]"] ############### It's assemble time!! ################### print("do") #print("") # create "let" statements for i in percNames: #print("let "+i+"M = \"["+genOnOff(4,5,0)+"]\"") print(indent+"let "+i+"M = \"[1111]\"") #print("") # actually assemble the stack! print(indent+"p \""+stackName+"\" ") # stack stuff like bite, scramble, etc. will go here # roll the dice up to N times for i in range(1,8): if (random.randint(0,10) > 4): 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()] 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() # using "every" #return (every()+"("+bite()+")") return wholeEnchilada # uncomment this for some wildness #funThings() # 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+"(#gain 0) $ struct \"[t*4]\" $ n \"0\" # midichan 0,") #print(",struct \"[t*4]\" $ n 0 # midichan 0") # generate rhythm tracks percMidiChan = 0 ######################################################## ## this is where each "track" is generated ## percCount = how many rhythmic (non-melodic) tracks ## will be created for i in percNames: print(indent+indent+"-- "+percNames[percMidiChan]+" -----------------") # this is here so there is the same thing at the start of # each new track (so we know "whenmod" for instance always # starts with $ and not ,) #print(indent+indent+"degradeBy 0 ") print(indent+indent+"mask "+i+"M") #### FUN STUFF ##### # insert some funStuff, maybe if (random.randint(0,10) > 8): print(funThings()) #### OFFSET #### if (random.randint(0,10) > 6): print(indent+indent+"$ "+offset()) ##### THE RHYTHMS ##### # choose between random, hardcoded, or euclidean rhythms rChoice = random.choice([0,1,2]) 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: rhythm = "t*4" 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,16)) #percSequence = genOnOff(percLength) #print(","+str(genOnOff(10))) #print(str(percSequence)) # how busy will it be?a # choose a random value between 0 and 9 busyness = random.randrange(0,7) generateBinary(busyness) rhythm = str(genOnOff(percLength,busyness,1)) elif (rChoice == 2): # GENERATE EUCLIDEAN RHYTHMS rhythm = str(euclid(random.choice(dividers))) #print(indent+indent+euclid(random.choice(dividers))) # else: rhythm = "struct [t*4]" print(indent+indent+"$ "+rhythm) print (indent+indent+"$ n "+str(random.choice(percNotes))+" # midichan "+str(percMidiChan)+",") percMidiChan = percMidiChan + 1 # generate melodic tracks melodicMidiChan = melodyMidiChanStart #melodyMidiChanStart = 7 for i in range(melodyCount): print(indent+indent+"-----------------------") 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+"$ "+genOnOff(16,(busyness),1)) print(indent+indent+"$ "+genNotes(1)) else: genOnOff(32,(busyness)) print(indent+indent+"$ "+genNotes(1)) print(indent+indent+"# midichan "+str(melodicMidiChan)+" + n (-36)") melodicMidiChan = melodicMidiChan + 1 print(indent+indent+"] # sunvox")