"""
Generator class for some basic Process-based algorithms for rhythmic generation.
"""
import os
import random
import inspect
from pydub import AudioSegment
from GenerIter.process import Process
from GenerIter.util import debug, nextPowerOf2
import GenerIter.excepts as robox
[docs]class Basic(Process):
def __init__(self):
super().__init__()
debug('Basic()')
[docs] def beatsbassdrone(self):
""" This is a developmental prototype variant of the algorithm01 in the original
DJProcessor code.
As the name suggests, it expects to find categories in the sample inventory mapped into the *Beats*, *Bass* and *Drone* voices.
Otherise this algorithm will fail.
Warning:
This is example code used during early development, and should really only be used for reference and study.
"""
# These could be parameterised in the config
declick = 10
drums_vol_range = (14, 18)
bass_vol_range = (18, 22)
pads_vol_range = (18, 22)
# How many times do you want this to run?
iterations = int(self._config["tracks"])
# Need to be able to pad the correct number of zeroes
#digits = self.intwidth(iterations)
# What's the base root name of the outputs?
#track = self._config["track"]
# Where are we sending the outputs?
#dest = self._destination
# Track repeats
repeats = int(self._config["repeats"])
for ctr in range(iterations):
# Randomly select our samples
drums_sample = self._inventory.selectRandom("Beats")
bass_sample = self._inventory.selectRandom("Bass")
pads_sample = self._inventory.selectRandom("Drone")
# Instantiate the audio segments
drums_audio = self.getsegment(drums_sample, drums_vol_range, declick)
bass_audio = self.getsegment(bass_sample, bass_vol_range, declick)
pads_audio = self.getsegment(pads_sample, pads_vol_range, declick)
bb_overlay = drums_audio.overlay(bass_audio)
composite_01 = bb_overlay + bb_overlay + bb_overlay + bb_overlay
dbb_overlay = composite_01.overlay(pads_audio)
for ctr2 in range(repeats):
dbb_overlay = dbb_overlay + dbb_overlay
fname = inspect.currentframe().f_code.co_name
self.write(algorithm=fname, counter=ctr, source=dbb_overlay)
[docs] def voices3(self):
""" This developmental prototype algorithm uses the same algorithm as beatsbassdrone,
but allows the user to designate the 3 voices to be used in the compose file.
The number of voices allowed is hard-coded to 3 only.
Warning:
This is example code used during early development, and should really only be used for reference and study.
"""
# These could be parameterised in the config
declick = 10
aa_vol_range = (14, 18)
ba_vol_range = (18, 22)
ca_vol_range = (18, 22)
# How many times do you want this to run?
iterations = int(self._config["tracks"])
# Need to be able to pad the correct number of zeroes
#digits = self.intwidth(iterations)
# What's the base root name of the outputs?
#track = self._config["track"]
# Where are we sending the outputs?
#dest = self._destination
# Track repeats
repeats = int(self._config["repeats"])
voices = self._config["voices"]
for ctr in range(iterations):
# Set a soft threshold for the size of the piece
size_limit = self.threshold()
# Randomly select our samples
aa_sample = self._inventory.selectRandom(voices[0])
ba_sample = self._inventory.selectRandom(voices[1])
ca_sample = self._inventory.selectRandom(voices[2])
# Instantiate the audio segments
aa_audio = self.getsegment(aa_sample, aa_vol_range, declick)
ba_audio = self.getsegment(ba_sample, ba_vol_range, declick)
ca_audio = self.getsegment(ca_sample, ca_vol_range, declick)
bb_overlay = aa_audio.overlay(ba_audio)
composite_01 = bb_overlay + bb_overlay + bb_overlay + bb_overlay
dbb_overlay = composite_01.overlay(ca_audio)
for ctr2 in range(repeats):
dbb_overlay = dbb_overlay + dbb_overlay
fname = inspect.currentframe().f_code.co_name
self.write(algorithm=fname, counter=ctr, source=dbb_overlay)
[docs] def voices(self):
""" This developmental prototype algorithm uses the same algorithm as voices3,
but allows the user to designate the 3 or more voices to be used in the compose file.
Warning:
This is example code used during early development, and should really only be used for reference and study.
"""
# These could be parameterised in the config
declick = 10
vol_range = (14, 22)
# How many times do you want this to run?
iterations = int(self._config["tracks"])
# Track repeats
repeats = int(self._config["repeats"])
voices = self._config["voices"]
nvoices = len(voices)
if nvoices < 3:
raise robox.GIParameterErr("Insufficient voices specified for the 'voices' algorithm")
for ctr in range(iterations):
audios = []
# Randomly select our samples
for voice in voices:
sample = self._inventory.selectRandom(voice)
audio = self.getsegment(sample, vol_range, declick)
audios.append(audio)
summation = audios[0]
for nctr in range(nvoices-2):
summation = summation.overlay(audios[nctr+1])
composite = summation + summation + summation + summation
final_overlay = composite.overlay(audios[nvoices-1])
for ctr2 in range(repeats):
final_overlay = final_overlay + final_overlay
fname = inspect.currentframe().f_code.co_name
self.write(algorithm=fname, counter=ctr, source=final_overlay)
[docs] def voices_shifted(self):
""" This algorithm uses the same algorithm as voices,
but implements a sequential shift as each new voice is incorporated.
Warning:
This is example code used during early development, and should really only be used for reference and study.
"""
# These could be parameterised in the config
declick = 10
vol_range = (14, 22)
# How many times do you want this to run?
iterations = int(self._config["tracks"])
# Track repeats
repeats = int(self._config["repeats"])
voices = self._config["voices"]
nvoices = len(voices)
if nvoices < 3:
raise robox.GIParameterErr("Insufficient voices specified for the 'voices' algorithm")
for ctr in range(iterations):
audios = []
# Randomly select our samples
for voice in voices:
sample = self._inventory.selectRandom(voice)
audio = self.getsegment(sample, vol_range, declick)
audios.append(audio)
summation = audios[0]
for nctr in range(nvoices-1):
summation = summation.overlay(audios[nctr+1])
summation = summation + summation
for ctr2 in range(repeats):
summation = summation + summation
fname = inspect.currentframe().f_code.co_name
self.write(algorithm=fname, counter=ctr, source=summation)
[docs] def groove(self):
"""The `Basic.groove` algorithm is used to form a rhythmic backbone to a composition. This algorithm attempts to make sensible alignment
choices for the samples in use. It is considered a *production* algorithm.
.. image:: images/Basic.groove.jpg
The number of voices can be 3 or more. In this example we are using 4, which we can think of as **A**, **B**, **C** and **D** corresponding
to their declaration position in the configuration list.
Segments **A** and **B** are randomly selected from their inventory categories. These are then length-aligned. That is, whichever is the
shorter of the two is padded with exactly the right amount of silence to make the frame lengths of both segments equal. These are then overlayed
to form a composite rhythmic unit referred to as **AB**.
The basic rhythmic beat is created by replicating a sequence of these **AB** segments end-to-end until the track size limit is surpassed.
This is the `groove_base`.
The `cycle` parameter specifies the number of these **AB** components tahe represent a higher level repetition unit called the `cycle` (obviously).
Subsequent voices are also length-aligned (or curtailed) to match the size of the **AB** unit. A new segment, `groove_layer` is created,
into which the later voices **C**, **D**, etc. are injected in a cyclic sequence, aligned precisely to the `groove_base` cycles and beats.
Finally, the `groove_base` is overlayed with the `groove_layer` to create a composite whole track which is output as an audio file.
Parameters:
tracks (int) : number of times the process is to run.
voices (list) : list of voice categories to select and use in the track
cycle (int) : length of a repeat cycle in terms of groove_base *beats*
Example:
.. code:: json
{
"Basic" : {
"groove" : {
"tracks" : 50,
"cycle" : 4,
"voices" : [
"Bass",
"Beat",
"Percussion",
"Pad"
]
}
}
}
Raises:
GenerIter.excepts.GIParameterErr
"""
declick = 10
# How many times do you want this to run?
iterations = int(self._config["tracks"])
voices = self._config["voices"]
nvoices = len(voices)
cycle = self._config["cycle"]
if nvoices < 3:
raise robox.GIParameterErr("Insufficient voices specified for the groove algorithm")
# Need to keep all overlays below the clipping threshold
mute = 6.0 * nextPowerOf2(nvoices)
for ctr in range(iterations):
audios = []
maxlen = 0
# Set a soft threshold for the size of the piece
size_limit = self.threshold()
# Randomly select our samples
for voice in voices:
sample = self._inventory.selectRandom(voice)
audio = self.getsegmentm(sample, mute, declick)
audios.append(audio)
for aud in range(2):
if len(audios[aud]) > maxlen:
maxlen = len(audios[aud])
for aud in range(2, nvoices):
audios[aud] = self.padtolength(audios[aud], maxlen, declick)
# Create the groove's rhythmic base layer
rhythm = audios[0].overlay(audios[1])
rlen = len(rhythm)
groove_base = rhythm
print("{0} < {1}".format(str(groove_base.duration_seconds), str(size_limit)))
while groove_base.duration_seconds < size_limit:
#groove_base = groove_base + rhythm
groove_base = groove_base + rhythm
# Create a blank segment of same size and frame rate
groove_layer = AudioSegment.silent(duration=len(groove_base),
frame_rate=groove_base.frame_rate)
for ctr2 in range(2, nvoices):
compound = AudioSegment.silent(duration=cycle*len(rhythm),
frame_rate=groove_layer.frame_rate)
offset = ((ctr2 - 2) % cycle) * rlen
compound = compound.overlay(audios[ctr2], position=offset)
groove_layer = groove_layer.overlay(compound, loop=True)
summation = groove_base.overlay(groove_layer)
fname = inspect.currentframe().f_code.co_name
self.write(algorithm=fname, counter=ctr, source=summation)