You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

547 lines
18 KiB

#!/usr/bin/env python3
scriptName = "Auto-Techno Generator"
version = 0.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
# force everything to happen within 1 bar
oneBar = 0
# 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]
dividers = [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
# a percussion track will be generated for each item in this list
percNames = ["kick", "hh", "sd", "cp", "ohh","ride","cymbal"]
# 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 = 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 = 7
## import stuff
import sys
import random
import math
import re
import time
import decimal
from decimal import Decimal
##################################################
# 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)
decimalSeed = int(seed,16)
### FRONTMATTER ###
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 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\""
zeroesOnes = "[{"+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)+")]\"")
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():
"""
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,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 "+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 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 = [stut(),"rev",scramble()]
# 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 62","64 63","32 30"]
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) > 5):
# for now, 8 bar phrases
# (# gain "[0 1 0 0 1]/8")
# how many cycles to make?
quietCycles = random.choice([2,3,4])
quietBinary = genOnOff(quietCycles,2,0)
# split 001001 into 0 0 1 0 0 1
quietBinary = list(quietBinary)
finalQuietBinary = ""
for i in quietBinary:
finalQuietBinary = i + " " + finalQuietBinary
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*9]","[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+"\" ")
# 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) > 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()]
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:
# 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 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:
# 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+">\"")
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+"$ struct \""+genOnOff(16,(busyness),1)+"\"")
print(indent+indent+"$ "+genNotes(1))
else:
# how many 16th notes?
thisLength = random.randint(3,16)
#genOnOff(8,(busyness))
print(indent+indent+"$ struct \""+genOnOff(thisLength,(busyness),1)+"\"")
print(indent+indent+"$ "+genNotes(0))
print(indent+indent+"# midichan "+str(melodicMidiChan)+" + n (-42)")
melodicMidiChan = melodicMidiChan + 1
print(indent+indent+"] # sunvox")