In [1]:
import librosa
import numpy as np
import pardir; pardir.pardir() # Allow imports from parent directory
import bjorklund
import fibonaccistretch
In [2]:
librosa.effects.time_stretch??
librosa.core.phase_vocoder??
fibonaccistretch.euclidean_stretch??
In [ ]:
In [3]:
# Generate rhythms based on parameters
def generate_original_and_target_rhythms(num_pulses, original_length, target_length):
original_rhythm = bjorklund.bjorklund(pulses=num_pulses, steps=original_length)
target_rhythm = bjorklund.bjorklund(pulses=len(original_rhythm), steps=target_length) # We use len(original_rhythm) instead of num_pulses because we're doing Euclidean stretch here
return (original_rhythm, target_rhythm)
original_rhythm, target_rhythm = generate_original_and_target_rhythms(3, 8, 13)
"Original rhythm: {} Target rhythm: {}".format(original_rhythm, target_rhythm)
Out[3]:
In [4]:
lcm = (8*13) / fibonaccistretch.euclid(8, 13)
lcm
Out[4]:
In [5]:
8*13
Out[5]:
In [6]:
# "Equalize" (i.e. scale rhythms so they're of equal length)
def equalize_rhythm_subdivisions(original_rhythm, target_rhythm, delimiter="-"):
original_length = len(original_rhythm)
target_length = len(target_rhythm)
lcm = (original_length*target_length) / fibonaccistretch.euclid(original_length, target_length)
original_scale_factor = (lcm / original_length) - 1
target_scale_factor = (lcm / target_length) - 1
print("lcm={}, original_scale_factor={}, target_scale_factor={}").format(lcm, original_scale_factor, target_scale_factor)
delimiter = str(delimiter)
original_rhythm = list((delimiter*original_scale_factor).join([str(x) for x in original_rhythm]))
target_rhythm = list((delimiter*target_scale_factor).join([str(x) for x in target_rhythm]))
original_rhythm.extend(list(delimiter*original_scale_factor))
target_rhythm.extend(list(delimiter*target_scale_factor))
return (original_rhythm, target_rhythm)
# print(scale_rhythm_subdivisions(original_rhythm, target_rhythm))
original_rhythm, target_rhythm = generate_original_and_target_rhythms(3, 8, 13)
print("Original rhythm: {} Target rhythm: {}".format(original_rhythm, target_rhythm))
equalized_original_rhythm, equalized_target_rhythm = equalize_rhythm_subdivisions(original_rhythm, target_rhythm)
(len(equalized_original_rhythm), len(equalized_target_rhythm))
Out[6]:
Get pulse indices so we can see how the equalized original and target relate. In particular, our goal is to create a relationship such that the original pulse indices always come first (so that they're bufferable in real-time)
In [7]:
def get_pulse_indices_for_rhythm(rhythm, pulse_symbols=[1]):
pulse_symbols = [str(s) for s in pulse_symbols]
rhythm = [str(x) for x in rhythm]
pulse_indices = [i for i,symbol in enumerate(rhythm) if symbol in pulse_symbols]
return pulse_indices
equalized_original_pulse_indices = get_pulse_indices_for_rhythm(equalized_original_rhythm)
equalized_target_pulse_indices = get_pulse_indices_for_rhythm(equalized_target_rhythm)
(equalized_original_pulse_indices, equalized_target_pulse_indices)
Out[7]:
For original we'll actually use ALL the steps instead of just pulses though. So:
In [8]:
equalized_original_pulse_indices = get_pulse_indices_for_rhythm(equalized_original_rhythm, [1,0])
equalized_target_pulse_indices = get_pulse_indices_for_rhythm(equalized_target_rhythm, [1])
print(equalized_original_pulse_indices, equalized_target_pulse_indices)
Now we can check to see if all the original pulse indices come first (this is our goal):
In [9]:
for i in range(len(equalized_original_pulse_indices)):
opi = equalized_original_pulse_indices[i]
tpi = equalized_target_pulse_indices[i]
if (opi > tpi):
print("Oh no; original pulse at {} comes after target pulse at {} (diff={})".format(opi, tpi, opi-tpi))
Oh no... how do we fix this??
In [10]:
# Format original and target rhythms for real-time manipulation
def format_rhythms(original_rhythm, target_rhythm):
# Equalize rhythm lengths and get pulse indices
eor, etr = equalize_rhythm_subdivisions(original_rhythm, target_rhythm)
eopi = get_pulse_indices_for_rhythm(eor, pulse_symbols=[1,0])
etpi = get_pulse_indices_for_rhythm(etr, pulse_symbols=[1])
# Find all the ones with problematic pulses (note that we're using *pulses* of target but *steps* of original)
for i in range(min(len(eopi), len(etpi))):
opi = eopi[i]
tpi = etpi[i]
if (opi > tpi):
print("Oh no; original pulse at {} comes after target pulse at {} (diff={})".format(opi, tpi, opi-tpi))
# TODO: Fix problematic pulses
#
print("Formatted original: {}".format(rtos(eor)))
print("Formatted target: {}".format(rtos(etr)))
return (eor, etr)
# Rhythm to string
def rtos(rhythm):
return "".join(rhythm)
Alright let's try this out:
In [11]:
# len(original) > len(target)
formatted = format_rhythms([1,0,0,1,0,0,1,0], [1,0,1])
In [12]:
# len(original) < len(target)
formatted = format_rhythms([1,0,0,1,0,0,1,0], [1,0,0,1,1,0,0,1,1,1,1])
In [13]:
# Trying [1,0,1,0] and [1,1] as originals, with the same target
formatted = format_rhythms([1,0,1,0], [1,0,0,1,0,0,1,0,0,0])
print("--------")
formatted = format_rhythms([1,1], [1,0,0,1,0,0,1,0,0,0])
To make things a bit clearer maybe we'll try the abcd
format for rtos()
In [14]:
# Rhythm to string
# Method: "str", "alphabet"
def rtos(rhythm, format_method="str", pulse_symbols=["1"]):
pulse_symbols = [str(s) for s in pulse_symbols]
if format_method == "str":
return "".join(rhythm)
elif format_method == "alphabet":
alphabet = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
output = []
ai = 0
for i,x in enumerate(rhythm):
if str(x) in pulse_symbols:
output.append(alphabet[ai%len(alphabet)])
ai += 1
else:
output.append(x)
return "".join(output)
else:
return rhythm
# Format original and target rhythms for real-time manipulation
def format_rhythms(original_rhythm, target_rhythm, format_method="str", pulse_symbols=["1"]):
# Equalize rhythm lengths and get pulse indices
eor, etr = equalize_rhythm_subdivisions(original_rhythm, target_rhythm)
eopi = get_pulse_indices_for_rhythm(eor, pulse_symbols=[1,0])
etpi = get_pulse_indices_for_rhythm(etr, pulse_symbols=[1])
# Find all the ones with problematic pulses (note that we're using *pulses* of target but *steps* of original)
for i in range(min(len(eopi), len(etpi))):
opi = eopi[i]
tpi = etpi[i]
if (opi > tpi):
print("Oh no; original pulse at {} comes after target pulse at {} (diff={})".format(opi, tpi, opi-tpi))
# TODO: Fix problematic pulses
#
print("")
print("Original: {}".format(rtos(eor, format_method=format_method, pulse_symbols=[1,0])))
print("Target: {}".format(rtos(etr, format_method=format_method, pulse_symbols=[1])))
return (eor, etr)
In [15]:
# Trying [1,0,1,0] and [1,1] as originals, with the same target
formatted = format_rhythms([1,0,1,0], [1,0,0,1,0,0,1,0,0,0], format_method="alphabet")
print("\n--------\n")
formatted = format_rhythms([1,1], [1,0,0,1,0,0,1,0,0,0], format_method="alphabet")
In [16]:
formatted = format_rhythms(original_rhythm, target_rhythm, format_method="alphabet")
In [17]:
print("Original rhythm: {}\nTarget rhythm: {}\n".format(original_rhythm, target_rhythm))
formatted = format_rhythms(original_rhythm, target_rhythm, format_method="alphabet")
In all the following cases only the target changes, not the original:
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------C-------0-------D-------0-------E-------F-------0-------G-------0-------H-------
becomes
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------B-------0-------C-------0-------D-------E-------0-------F-------0-------G-------
Pros:
Cons:
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------C-------0-------D-------0-------E-------F-------0-------G-------0-------H-------
becomes
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------B-------C-------D-------0-------E-------E-------F-------G-------0-------H-------
Pros:
Cons:
10110 10110 101
becomes
10111 10111 101
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------C-------0-------D-------0-------E-------F-------0-------G-------0-------H-------
becomes
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B---------C-----0-------D-------0-------E--------F------0-------G-------0-------H-------
Pros:
Cons:
In [18]:
formatted = format_rhythms(original_rhythm, target_rhythm, format_method="alphabet")
print("\n--------\n")
formatted = format_rhythms([1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0], target_rhythm, format_method="alphabet")
Needs more work.
Orig: A------------B------------C------------D------------E------------F------------G------------H------------I------------J------------K------------L------------M------------N------------O------------P------------
Trgt: A---------------0---------------B---------------C---------------0---------------D---------------0---------------E---------------F---------------0---------------G---------------0---------------H---------------
becomes
Orig: A------------B------------C------------D------------E------------F------------G------------H------------I------------J------------K------------L------------M------------N------------O------------P------------
Trgt: A---------------B---------------C---------------D---------------E---------------F---------------G---------------H---------------I---------------J---------------K---------------L---------------M---------------
Pros:
Cons:
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------C-------0-------D-------0-------E-------F-------0-------G-------0-------H-------
becomes
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------B-------0-------D-------0-------E-------E-------0-------G-------0-------H-------
Pros:
Cons:
Orig: A------------B------------C------------D------------E------------F------------G------------H------------I------------J------------K------------L------------M------------N------------O------------P------------
Trgt: A---------------0---------------B---------------C---------------0---------------D---------------0---------------E---------------F---------------0---------------G---------------0---------------H---------------
becomes
Orig: A------------B------------C------------D------------E------------F------------G------------H------------I------------J------------K------------L------------M------------N------------O------------P------------
Trgt: A---------------B---------------C---------------D---------------E---------------G---------------H---------------I---------------J---------------K---------------M---------------N---------------O---------------
Pros:
Cons:
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------C-------0-------D-------0-------E-------F-------0-------G-------0-------H-------
becomes
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------0-------0-------D-------0-------E-------0-------0-------G-------0-------H-------
Pros:
Cons:
10110 10110 101
becomes
10100 10100 101
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------C-------0-------D-------0-------E-------F-------0-------G-------0-------H-------
becomes
Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------0-------C-------D-------0-------E-------0-------F-------G-------0-------H-------
Pros:
Cons:
10110 10110 101
becomes
10101 10101 101
So it's a tradeoff between:
Method 10 achieves (a) and (c).
Method 5 achieves (b) and (c).
Method 2 achieves (a).
Method 7 achieves (b), while 9 achieves (c).
Let's try Method 10 for now. We'll redefine our rtos
method:
In [19]:
# Rhythm to string
# Method: "str", "alphabet"
def rtos(rhythm, format_method="str", pulse_symbols=["1"]):
pulse_symbols = [str(s) for s in pulse_symbols]
rhythm = [str(x) for x in rhythm]
if format_method == "str":
return "".join(rhythm)
elif format_method == "alphabet":
alphabet = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
output = []
ai = 0
for i,x in enumerate(rhythm):
if str(x) in pulse_symbols:
output.append(alphabet[ai%len(alphabet)])
ai += 1
else:
output.append(x)
return "".join(output)
else:
return rhythm
# Format original and target rhythms for real-time manipulation
def format_rhythms(original_rhythm, target_rhythm, format_method="str", original_pulse_symbols=[1,0], target_pulse_symbols=["1"], delimiter="-"):
# Convert all arrays to string arrays
original_rhythm = [str(x) for x in original_rhythm]
target_rhythm = [str(x) for x in target_rhythm]
original_pulse_symbols = [str(x) for x in original_pulse_symbols]
target_pulse_symbols = [str(x) for x in target_pulse_symbols]
# Adjust target rhythm if inappropriate number of pulses
if len(original_rhythm) > len([x for x in target_rhythm if x in target_pulse_symbols]):
while len(target_rhythm) < len(original_rhythm):
target_rhythm.extend(target_rhythm)
target_rhythm = bjorklund.bjorklund(pulses=len(original_rhythm), steps=len(target_rhythm))
target_rhythm = [str(x) for x in target_rhythm]
# Equalize rhythm lengths and get pulse indices
eq_original_rhythm, eq_target_rhythm = equalize_rhythm_subdivisions(original_rhythm, target_rhythm, delimiter=delimiter)
eq_original_step_indices = get_pulse_indices_for_rhythm(eq_original_rhythm, pulse_symbols=[1,0])
eq_target_pulse_indices = get_pulse_indices_for_rhythm(eq_target_rhythm, pulse_symbols=[1])
# Assertion that may not hold up
# assert(len(eq_original_step_indices) == len(target_pulse_indices))
# Find all the ones with problematic pulses (note that we're using *pulses* of target but *steps* of original)
# for i in range(min(len(eq_original_step_indices), len(eq_target_pulse_indices))):
# osi = eq_original_step_indices[i]
# tpi = eq_target_pulse_indices[i]
# if (osi > tpi):
# print("Oh no; original pulse at {} comes after target pulse at {} (diff={})".format(osi, tpi, osi-tpi))
# Fix problematic pulses using method 10
# (Starting to do this in a more C++ style so it's easier to port)
fixed_eq_target_rhythm = list(delimiter * len(eq_target_rhythm))
eq_target_step_indices = get_pulse_indices_for_rhythm(eq_target_rhythm, pulse_symbols=[1,0])
print("eq_original_step_indices: {}".format(eq_original_step_indices))
print("eq_target_pulse_indices: {}".format(eq_target_pulse_indices))
print("eq_target_step_indices: {}".format(eq_target_step_indices))
osi_idx = -1
tpi_idx = 0
for i in range(len(eq_target_rhythm)):
# print(i)
# Update index for original step indices
if i in eq_original_step_indices:
# osi_idx = min(osi_idx+1, len(eq_original_step_indices)-1)
osi_idx += 1
# Adjust
if i in eq_target_step_indices:
osi = eq_original_step_indices[osi_idx]
tpi = eq_target_pulse_indices[tpi_idx]
print("{}: osi@{}={}, tpi@{}={}".format(i, osi_idx, osi, tpi_idx, tpi))
# Make sure current position isn't earlier than original's,
# and that target pulse position isn't ahead of original pulse position
if i >= osi and i >= tpi and osi_idx >= tpi_idx:
fixed_eq_target_rhythm[i] = 1
tpi_idx += 1
print("set to 1")
continue
else:
fixed_eq_target_rhythm[i] = 0
print("set to 0")
continue
# Otherwise, it's a delimiter, so we just put a delimiter there
fixed_eq_target_rhythm[i] = delimiter
print("")
print("OrigPlse: {}").format(rtos(eq_original_rhythm, format_method="str", pulse_symbols=[1]))
print("OrigStep: {}".format(rtos(eq_original_rhythm, format_method=format_method, pulse_symbols=[1,0])))
print("TrgtPlse: {}".format(rtos(eq_target_rhythm, format_method=format_method, pulse_symbols=[1])))
print("FixdPlse: {}".format(rtos(fixed_eq_target_rhythm, format_method=format_method, pulse_symbols=[1])))
return (eq_original_rhythm, fixed_eq_target_rhythm)
In [20]:
print("Original rhythm: {}\nTarget rhythm: {}\n".format(original_rhythm, target_rhythm))
formatted = format_rhythms(original_rhythm, target_rhythm, format_method="alphabet")
That looks more like it! Let's try a few more:
In [21]:
formatted = format_rhythms([1,0,1,1], [1,0,1,0,1], format_method="alphabet")
In [22]:
formatted = format_rhythms([1,0,1,1], [1,0,1,0,1,1], format_method="alphabet")
In [23]:
formatted = format_rhythms([1,0,1,1,0,1], [1,0,1,1,0,1,0], format_method="alphabet")
In [24]:
formatted = format_rhythms([1,0,1,1,0,1], [0], format_method="alphabet")
In the previous two examples, the fixed rhythm doesn't resemble the target rhythm at all... I guess this is because we're using the Bjorklund fix too, to fix pulses.
But if we're going through all this trouble to "fix" rhythms, maybe we should just place constraints on what the user can do in the first place?
I guess we should have both.
e.g. if they put a pulse before the original position, it'll show up red or something.
Hmm, but in that case, maybe we do want to preserve the original rhythm. And just have it repeat or something? In other words, use method 7.
In [25]:
# TODO: Try with method 7 vs. method 10
In [26]:
np.indices?
In [27]:
"0"*4
"-".join([str(x) for x in [2,3,4]])
Out[27]:
In [ ]:
In [28]:
print("a"); print("b")
In [ ]: