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.
902 lines
29 KiB
902 lines
29 KiB
#!/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,8,9,10,11] |
|
|
|
# |
|
####### 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] |
|
|
|
# assuming this is a bassline, and you want to do acid stuff... |
|
# what midi CC and channel should the filter cutoff be? |
|
melodyCCChannel = 10 |
|
melodyFilterCutoffCC = 20 |
|
# ^^^ map this to the filter cutoff for whatever synth you're using for bass/melody |
|
|
|
# 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,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 (bassline) |
|
melodicMidiChan = melodyMidiChanStart |
|
|
|
|
|
# acid mode: like a TR-303 playing 16th notes |
|
# roll the dice for acid mode |
|
if (random.randint(0,10) > 6): |
|
acidMode = 1 |
|
else: |
|
acidMode = 0 |
|
|
|
#melodyMidiChanStart = 7 |
|
for i in range(melodyCount): |
|
if (acidMode == 1): |
|
busyness = 9 |
|
oneBar == 1 |
|
# generate a sequence of filter cutoff values |
|
print(indent+indent+"------------ bassline {{{ACID MODE}}}-------") |
|
|
|
else: |
|
busyness = random.choice([0,1,2,3,4,5,6,6,6,6,7,7,7,7,8,8,8,9,9]) |
|
melodyCutoffSeq = "0" |
|
print(indent+indent+"------------ bassline -------") |
|
|
|
print(indent+indent+"degradeBy 0 ") |
|
#print(", ") |
|
|
|
|
|
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),") |
|
|
|
if (acidMode == 1): |
|
|
|
ccLength = 16 |
|
# create a MIDI cc track for filter cutoff |
|
print(indent+indent+"--------- bassline filter cutoff ----") |
|
# we want: struct "[t*16]" $ ccn NN # ccv "{12 45 90 126 78 23}%NN # midichan NN |
|
ccPattern = "" |
|
for v in range(ccLength): |
|
ccPattern = str(random.randint(0,90))+" "+str(ccPattern) |
|
|
|
print(indent+indent+"struct \"[t*16]\" $ ccn "+str(melodyFilterCutoffCC)+" # ccv \"{"+str(ccPattern).rstrip()+"}%16\" # midichan "+str(melodyCCChannel)+",") |
|
|
|
|
|
#melodyCCChannel = 10 |
|
#melodyFilterCutoffCC = 20 |
|
|
|
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) |
|
|
|
# should we even play the pads? |
|
if (random.randint(0,10) > 6): |
|
print(indent+indent+"degradeBy 0 ") |
|
else: |
|
print(indent+indent+"degradeBy 1 ") |
|
|
|
|
|
# build the track |
|
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): |
|
# if = 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 |
|
|
|
# TODO wrap this into a new function |
|
# 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() |
|
# right now ccPattern looks like this: |
|
# 0 12 78 127 45 60 |
|
|
|
#ccPattern = list(ccPattern) |
|
# |
|
|
|
ccPatStruct = "struct \"[t*"+str(ccEndNumber)+"]\" $ " |
|
|
|
# how likely is it that we'll actually send MIDI cc's for this value? |
|
if (random.randint(0,10) > 4): |
|
ccPatStart = ccPatStruct+"ccn "+str(i)+" # ccv \"{" |
|
else: |
|
#ccPatStart = "-- "+ccPatStruct+"ccn "+str(i)+" # ccv \"{" |
|
ccPatStart = ""+ccPatStruct+"ccn "+str(i)+" # ccv \"{" |
|
|
|
ccPatEnd = "}%" |
|
|
|
ccFinalPattern = ccPatStart+ccPattern+ccPatEnd |
|
|
|
|
|
#ccs = ccPatStart+ccFinalPattern+ccPatEnd+str(ccEndNumber)+"\" # midichan "+str(midiChannel)+"," |
|
ccs = ccFinalPattern+str(ccEndNumber)+"\"," |
|
|
|
print(indent+indent+indent+str(ccs)) |
|
|
|
midiChannel = midiChannel + 1 |
|
|
|
|
|
|
|
|
|
|
|
# end result like |
|
# ccn 0 # ccv "{0 1 0 1 1 0 0 1 0}%N" |
|
|
|
|
|
# make one more line so we don't have to worry about the trailing comma |
|
# in the last ccs line |
|
|
|
print(indent+indent+indent+"ccn 126 # ccv "+str(random.randint(0,126))+"] # midichan "+str(midiCCChannel)+",") |
|
|
|
|
|
|
|
############### EXTRA STUFF ################# |
|
# sometimes this will generate a great groove |
|
# that could be perfect with one or two extra things, |
|
# like a handclap or extra kick |
|
# This section creates an area to easily add that |
|
# extra something |
|
# hardcoded to midi channel 12 |
|
|
|
print(indent+indent+"-- EXTRA STUFF ---") |
|
print(indent+indent+"stack [") |
|
print(indent+indent+indent+"(# gain 0) $ struct \"[~ t ~ t]\" $ n \"0\",") |
|
print(indent+indent+indent+"(# gain 0) $ struct \"[~t~t~t~t]\" $ n \"1\"] # midichan 12") |
|
|
|
## put the finishing touches on the beautiful stack! :-) :-) |
|
print(indent+indent+"] # sunvox")
|
|
|