Formatting rhythms for real-time stretch

The goal of this notebook is to come up with an algorithm to re-format original and target rhythms such that real-time time-stretching (or even real-time stuttering) is possible.

Setup


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]:
'Original rhythm: [1, 0, 0, 1, 0, 0, 1, 0]    Target rhythm: [1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1]'

In [4]:
lcm = (8*13) / fibonaccistretch.euclid(8, 13)
lcm


Out[4]:
104

In [5]:
8*13


Out[5]:
104

"Equalize": Make rhythms the same length

Use LCM to "equalize" rhythms so that they're of equal length.

e.g.

a = [1,0,0,1]

b = [1,1,0]

become

equalized_a = [1,-,-,0,-,-,0,-,-,1,-,-]

equalized_b = [1,-,-,-,1,-,-,-,0,-,-,-]


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))


Original rhythm: [1, 0, 0, 1, 0, 0, 1, 0]    Target rhythm: [1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1]
lcm=104, original_scale_factor=12, target_scale_factor=7
Out[6]:
(104, 104)

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]:
([0, 39, 78], [0, 16, 24, 40, 56, 64, 80, 96])

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)


([0, 13, 26, 39, 52, 65, 78, 91], [0, 16, 24, 40, 56, 64, 80, 96])

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; original pulse at 26 comes after target pulse at 24 (diff=2)
Oh no; original pulse at 65 comes after target pulse at 64 (diff=1)

Oh no... how do we fix this??

  • One solution is to just nudge them over, especially since they only differ by 1/104 to 2/104ths of a measure in this case.
  • Another solution would be to use the same data from the original pulse if there's not a new pulse available. Hmmmm
  • Or use as much of the original buffer as we can...?

First pass at format_rhythm(), without fixing rhythm

But first let's define a function for end-to-end formatting:


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])


lcm=24, original_scale_factor=2, target_scale_factor=7
Formatted original: 1--0--0--1--0--0--1--0--
Formatted target:   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])


lcm=88, original_scale_factor=10, target_scale_factor=7
Formatted original: 1----------0----------0----------1----------0----------0----------1----------0----------
Formatted target:   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])


lcm=20, original_scale_factor=4, target_scale_factor=1
Formatted original: 1----0----1----0----
Formatted target:   1-0-0-1-0-0-1-0-0-0-
--------
lcm=10, original_scale_factor=4, target_scale_factor=0
Oh no; original pulse at 5 comes after target pulse at 3 (diff=2)
Formatted original: 1----1----
Formatted target:   1001001000

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")


lcm=20, original_scale_factor=4, target_scale_factor=1

Original: A----B----C----D----
Target:   A-0-0-B-0-0-C-0-0-0-

--------

lcm=10, original_scale_factor=4, target_scale_factor=0
Oh no; original pulse at 5 comes after target pulse at 3 (diff=2)

Original: A----B----
Target:   A00B00C000

In [16]:
formatted = format_rhythms(original_rhythm, target_rhythm, format_method="alphabet")


lcm=104, original_scale_factor=12, target_scale_factor=7
Oh no; original pulse at 26 comes after target pulse at 24 (diff=2)
Oh no; original pulse at 65 comes after target pulse at 64 (diff=1)

Original: A------------B------------C------------D------------E------------F------------G------------H------------
Target:   A-------0-------B-------C-------0-------D-------0-------E-------F-------0-------G-------0-------H-------

Exploring adjustment options

Let's use this example to explore adjustment options:


In [17]:
print("Original rhythm: {}\nTarget rhythm:   {}\n".format(original_rhythm, target_rhythm))
formatted = format_rhythms(original_rhythm, target_rhythm, format_method="alphabet")


Original rhythm: [1, 0, 0, 1, 0, 0, 1, 0]
Target rhythm:   [1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1]

lcm=104, original_scale_factor=12, target_scale_factor=7
Oh no; original pulse at 26 comes after target pulse at 24 (diff=2)
Oh no; original pulse at 65 comes after target pulse at 64 (diff=1)

Original: A------------B------------C------------D------------E------------F------------G------------H------------
Target:   A-------0-------B-------C-------0-------D-------0-------E-------F-------0-------G-------0-------H-------

In all the following cases only the target changes, not the original:

1. For every problematic pulse (e.g. C), just re-use the previous pulse

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:

  • It's simple

Cons:

  • We end up losing pulse H.

2. For every problematic pulse, re-use prev pulse, but on the next step (i.e. 0) use newest pulse

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:

  • We use all the pulses (if we have enough 0s)

Cons:

  • We kind of end up obfuscating the pulses actual desired target rhythm. In this case
      10110 10110 101
    
    becomes
      10111 10111 101

3. For every problematic pulse, just nudge the corresponding target pulse

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:

  • We avoid having to repeat any pulses (repeated pulses could sound weird)
  • We use all the pulses

Cons:

  • The rhythm becomes metrically incorrect and could sound unnatural/bad.

4. Subdivide original rhythm further until we don't have problematic pulses anymore


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")


lcm=104, original_scale_factor=12, target_scale_factor=7
Oh no; original pulse at 26 comes after target pulse at 24 (diff=2)
Oh no; original pulse at 65 comes after target pulse at 64 (diff=1)

Original: A------------B------------C------------D------------E------------F------------G------------H------------
Target:   A-------0-------B-------C-------0-------D-------0-------E-------F-------0-------G-------0-------H-------

--------

lcm=208, original_scale_factor=12, target_scale_factor=15

Original: A------------B------------C------------D------------E------------F------------G------------H------------I------------J------------K------------L------------M------------N------------O------------P------------
Target:   A---------------0---------------B---------------C---------------0---------------D---------------0---------------E---------------F---------------0---------------G---------------0---------------H---------------

Needs more work.

5. Method 4, but use 0s of target rhythm as pulses too

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:

  • We preserve both rhythms AND avoid repeating segments, which is good

Cons:

  • We end up scrapping a lot of the latter pulses (pulses N, O, and P)

6. Use 0s of target rhythm as pulses too, if necessary

Orig: A------------B------------C------------D------------E------------F------------G------------H------------
Trgt: A-------0-------B-------C-------0-------D-------0-------E-------F-------0-------G-------0-------H-------

...

7. Just always use the most recent pulse we have, when needed

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:

  • Preserve rhythm shape, and it's simple

Cons:

  • We lose intermediate pulses, in this case C and F

8. Combine methods 4 and 7

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:

  • Again, we preserve rhythm shape

Cons:

  • We lose intermediate pulses F, L, and P

9. Method 7 (use most recent available pulse), but stretch instead of repeat pulses

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:

  • Avoid having to repeat pulses

Cons:

  • We lose intermediate pulses, in this case C and F
  • We lose rhythm shape, as
      10110 10110 101
    
    becomes
      10100 10100 101

10. Method 2, but stretch instead of repeat. So stretch, and use new pulse on next available step (i.e. 0)

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:

  • Avoid having to repeat pulses
  • We use all the pulses!

Cons:

  • We lose rhythm shape, as
      10110 10110 101
    
    becomes
      10101 10101 101

Method 11: Just don't let the user create target rhythms with problematic pulses

Mmhmm din

So What?

So it's a tradeoff between:

  • a) Using all the pulses
  • b) Preserving target rhythm shape
  • c) Avoiding repeating of pulses (which could sound unnatural)

Method 10 achieves (a) and (c).

Method 5 achieves (b) and (c).

Method 2 achieves (a).

Method 7 achieves (b), while 9 achieves (c).

Trying out method 10

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")


Original rhythm: [1, 0, 0, 1, 0, 0, 1, 0]
Target rhythm:   [1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1]

lcm=104, original_scale_factor=12, target_scale_factor=7
eq_original_step_indices: [0, 13, 26, 39, 52, 65, 78, 91]
eq_target_pulse_indices:  [0, 16, 24, 40, 56, 64, 80, 96]
eq_target_step_indices:   [0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96]
0: osi@0=0, tpi@0=0
set to 1
8: osi@0=0, tpi@1=16
set to 0
16: osi@1=13, tpi@1=16
set to 1
24: osi@1=13, tpi@2=24
set to 0
32: osi@2=26, tpi@2=24
set to 1
40: osi@3=39, tpi@3=40
set to 1
48: osi@3=39, tpi@4=56
set to 0
56: osi@4=52, tpi@4=56
set to 1
64: osi@4=52, tpi@5=64
set to 0
72: osi@5=65, tpi@5=64
set to 1
80: osi@6=78, tpi@6=80
set to 1
88: osi@6=78, tpi@7=96
set to 0
96: osi@7=91, tpi@7=96
set to 1

OrigPlse: 1------------0------------0------------1------------0------------0------------1------------0------------
OrigStep: A------------B------------C------------D------------E------------F------------G------------H------------
TrgtPlse: A-------0-------B-------C-------0-------D-------0-------E-------F-------0-------G-------0-------H-------
FixdPlse: A-------0-------B-------0-------C-------D-------0-------E-------0-------F-------G-------0-------H-------

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")


lcm=20, original_scale_factor=4, target_scale_factor=3
eq_original_step_indices: [0, 5, 10, 15]
eq_target_pulse_indices:  [0, 4, 8, 12]
eq_target_step_indices:   [0, 4, 8, 12, 16]
0: osi@0=0, tpi@0=0
set to 1
4: osi@0=0, tpi@1=4
set to 0
8: osi@1=5, tpi@1=4
set to 1
12: osi@2=10, tpi@2=8
set to 1
16: osi@3=15, tpi@3=12
set to 1

OrigPlse: 1----0----1----1----
OrigStep: A----B----C----D----
TrgtPlse: A---B---C---D---0---
FixdPlse: A---0---B---C---D---

In [22]:
formatted = format_rhythms([1,0,1,1], [1,0,1,0,1,1], format_method="alphabet")


lcm=12, original_scale_factor=2, target_scale_factor=1
eq_original_step_indices: [0, 3, 6, 9]
eq_target_pulse_indices:  [0, 4, 8, 10]
eq_target_step_indices:   [0, 2, 4, 6, 8, 10]
0: osi@0=0, tpi@0=0
set to 1
2: osi@0=0, tpi@1=4
set to 0
4: osi@1=3, tpi@1=4
set to 1
6: osi@2=6, tpi@2=8
set to 0
8: osi@2=6, tpi@2=8
set to 1
10: osi@3=9, tpi@3=10
set to 1

OrigPlse: 1--0--1--1--
OrigStep: A--B--C--D--
TrgtPlse: A-0-B-0-C-D-
FixdPlse: A-0-B-0-C-D-

In [23]:
formatted = format_rhythms([1,0,1,1,0,1], [1,0,1,1,0,1,0], format_method="alphabet")


lcm=42, original_scale_factor=6, target_scale_factor=5
eq_original_step_indices: [0, 7, 14, 21, 28, 35]
eq_target_pulse_indices:  [0, 6, 12, 18, 24, 30]
eq_target_step_indices:   [0, 6, 12, 18, 24, 30, 36]
0: osi@0=0, tpi@0=0
set to 1
6: osi@0=0, tpi@1=6
set to 0
12: osi@1=7, tpi@1=6
set to 1
18: osi@2=14, tpi@2=12
set to 1
24: osi@3=21, tpi@3=18
set to 1
30: osi@4=28, tpi@4=24
set to 1
36: osi@5=35, tpi@5=30
set to 1

OrigPlse: 1------0------1------1------0------1------
OrigStep: A------B------C------D------E------F------
TrgtPlse: A-----B-----C-----D-----E-----F-----0-----
FixdPlse: A-----0-----B-----C-----D-----E-----F-----

In [24]:
formatted = format_rhythms([1,0,1,1,0,1], [0], format_method="alphabet")


lcm=24, original_scale_factor=3, target_scale_factor=2
eq_original_step_indices: [0, 4, 8, 12, 16, 20]
eq_target_pulse_indices:  [0, 3, 6, 12, 15, 18]
eq_target_step_indices:   [0, 3, 6, 9, 12, 15, 18, 21]
0: osi@0=0, tpi@0=0
set to 1
3: osi@0=0, tpi@1=3
set to 0
6: osi@1=4, tpi@1=3
set to 1
9: osi@2=8, tpi@2=6
set to 1
12: osi@3=12, tpi@3=12
set to 1
15: osi@3=12, tpi@4=15
set to 0
18: osi@4=16, tpi@4=15
set to 1
21: osi@5=20, tpi@5=18
set to 1

OrigPlse: 1---0---1---1---0---1---
OrigStep: A---B---C---D---E---F---
TrgtPlse: A--B--C--0--D--E--F--0--
FixdPlse: A--0--B--C--D--0--E--F--

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.

Trying out method 7


In [25]:
# TODO: Try with method 7 vs. method 10

Misc noodles


In [26]:
np.indices?

In [27]:
"0"*4
"-".join([str(x) for x in [2,3,4]])


Out[27]:
'2-3-4'

In [ ]:


In [28]:
print("a"); print("b")


a
b

In [ ]: