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

#!/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")