A Bottom-up Approach to Complex Rhythms


In [1]:
%load_ext abjadext.ipython
import abjad

In [2]:
import random
durations = []
numerators = range(16)
denominators = [2**x for x in range(1, 5)]
for x in range(10):
    numerator = random.choice(numerators)
    denominator = random.choice(denominators)
    duration_token = (numerator, denominator)
    duration = abjad.Duration(duration_token)
    durations.append(duration)

for d in durations:
    print(d)


5/2
1/2
7/8
3/4
5/2
1/2
0
9/16
15/4
3/8

Assignability is a thing:


In [3]:
duration = abjad.Duration(5,4)
note = abjad.Note(0, (duration))


---------------------------------------------------------------------------
AssignabilityError                        Traceback (most recent call last)
<ipython-input-3-819c34ecad27> in <module>
      1 duration = abjad.Duration(5,4)
----> 2 note = abjad.Note(0, (duration))

~/Programação/Abjad/abjad-master/abjad/core/Note.py in __init__(self, multiplier, tag, *arguments)
     99         else:
    100             raise ValueError("can not initialize note from {arguments!r}.")
--> 101         Leaf.__init__(self, written_duration, multiplier=multiplier, tag=tag)
    102         if written_pitch is not None:
    103             if written_pitch not in drums:

~/Programação/Abjad/abjad-master/abjad/core/Leaf.py in __init__(self, written_duration, multiplier, tag)
     57         self._before_grace_container = None
     58         self.multiplier = multiplier
---> 59         self.written_duration = written_duration
     60 
     61     ### SPECIAL METHODS ###

~/Programação/Abjad/abjad-master/abjad/core/Note.py in written_duration(self, argument)
    240     @written_duration.setter
    241     def written_duration(self, argument):
--> 242         return Leaf.written_duration.fset(self, argument)
    243 
    244     @property

~/Programação/Abjad/abjad-master/abjad/core/Leaf.py in written_duration(self, argument)
    537         if not duration.is_assignable:
    538             message = f"not assignable duration: {duration!r}."
--> 539             raise exceptions.AssignabilityError(message)
    540         self._written_duration = duration

AssignabilityError: not assignable duration: Duration(5, 4).

The LeafMaker class helps us around the assignability error:


In [4]:
maker = abjad.LeafMaker()
pitches = [None]
durations = [abjad.Duration(3, 8), abjad.Duration(5, 8)]
leaves = maker(pitches, durations)
leaves


Out[4]:
Selection([Rest('r4.'), Rest('r2'), Rest('r8')])

Tuplets

Use the Tuplet class to make tuplets (of any non-binary division). The Tuplet class provides an interface for working with complex rhythms in a bottom-up way:


In [5]:
tuplet = abjad.Tuplet(abjad.Multiplier(2, 3), "c'8 d'8 e'8")

In [6]:
abjad.show(tuplet)


3

You can also pack existing leaves into a Tuplet instance.


In [7]:
leaves = [abjad.Note("fs'8"), abjad.Note("g'8"), abjad.Rest('r8')]

In [8]:
tuplet = abjad.Tuplet(abjad.Multiplier(2, 3), leaves)

In [9]:
abjad.show(tuplet)


3

Tuplet instances all have a multiplier and components.


In [10]:
tuplet


Out[10]:
Tuplet(Multiplier(2, 3), "fs'8 g'8 r8")

Understanding Augmentation and Diminution

Remember that any tuplet can be represented as an augmentation or a diminution relative to the written notes' default values. Our example tuplet's multiplier of (2,3) for three eighth notes means that each written eighth note lasts for 2/3rds its written value. Because the original durations have been reduced, this is a diminution:


In [11]:
tuplet.diminution()


Out[11]:
True

A tuplet with a multiplier greater than 1, on the other hand, would be an augmentation:


In [12]:
tuplet = abjad.Tuplet((4,3), "fs'16 g'16 r16")

In [13]:
tuplet.diminution()


Out[13]:
False

In [14]:
abjad.show(tuplet)


3:4

This last tuplet is an augmentation in which each of the written sixteenth notes lasts for 4/3rds of its written duration. The sounding result would be identical, and these are just two ways of writing the same thing, the former of which happens to be conventional.

Remember that object-oriented programming gives us objects with characteristics and behaviors. We can use the dot-chaining syntax to read and write the tuplet's multiplier attribute:


In [15]:
tuplet = abjad.Tuplet(abjad.Multiplier(2, 3), "fs'8 g' r8")

In [16]:
tuplet.multiplier


Out[16]:
Multiplier(2, 3)

In [17]:
tuplet.multiplier = abjad.Multiplier(4, 5)

In [18]:
tuplet


Out[18]:
Tuplet(Multiplier(4, 5), "fs'8 g'8 r8")

In [19]:
abjad.show(tuplet)


5

Adding Leaves

The multiplier is a reasonable characteristic for our tuplet to have, but what about behaviors? Well, you probably want to be able to build up tuplets by adding leaves to them, one or several a time. The append method adds leaves to the end of a tuplet (and to any Python list), one leaf at a time:


In [20]:
tuplet.append(abjad.Note("e'4."))

In [21]:
abjad.show(tuplet)


5

...or using a LilyPond string:


In [22]:
tuplet.append("bf8")

In [23]:
abjad.show(tuplet)


5

Likewise, the extend method adds two or more leaves at a time:


In [24]:
notes = [abjad.Note("fs'32"), abjad.Note("e'32"), abjad.Note("d'32"), abjad.Rest((1, 32))]

In [25]:
tuplet.extend(notes)

In [26]:
abjad.show(tuplet)


5

And you can use a LilyPond string with extend, too:


In [27]:
tuplet.extend("gs'8 a8")

In [28]:
abjad.show(tuplet)


5

Removing Leaves

You can remove tuplet components by reference using the remove method:


In [29]:
tuplet.remove(tuplet[3])

In [30]:
abjad.show(tuplet)


5

If you want to remove a component by index and then keep it to do something else with it, use the pop method instead of remove:


In [31]:
popped = tuplet.pop(2)
popped


Out[31]:
Rest('r8')

In [32]:
abjad.show(tuplet)


5

Indexing Leaves

Tuplets support indexing, if you'd like to do something to the nth component of a tuplet:


In [33]:
tuplet[1]


Out[33]:
Note("g'8")

If you've added an existing list to a tuplet's components, and you'd like to see where a component of that list is in the tuplet, you can use the tuplet's index method - in our case, we'll use our notes list from above:


In [34]:
notes


Out[34]:
[Note("fs'32"), Note("e'32"), Note("d'32"), Rest('r32')]

In [35]:
notes[1]


Out[35]:
Note("e'32")

In [36]:
tuplet.index(notes[1]) # Where is the second element of notes in tuplet?


Out[36]:
4

In [37]:
abjad.show(tuplet) # it's at index 4


5

Making Tuplets from Durations and Ratios

The Tuplet class also provides a method for constructing a tuplet from a duration and a ratio, where the ratio is a list of integers, the sum of which determines the number of equally spaced pulses within the duration:


In [38]:
tuplet = abjad.Tuplet()
tuplet = abjad.Tuplet.from_duration_and_ratio((1,4), [1,3,1])

In [39]:
staff = abjad.Staff([tuplet], lilypond_type='RhythmicStaff')

In [40]:
abjad.show(staff)


5

This might not seem like much, but we can write functions that use these kinds of basic functionalities to do more complicated things, like we'll do in this next example, taken from a real score:

Brian Ferneyhough - Unsichtbare Farben

Mikhial Malt analyzes the rhythmic materials of Ferneyhough’s solo violin composition, Unsichtbare Farben, in The OM Composer’s Book 2.

Malt explains that Ferneyhough used OpenMusic to create an “exhaustive catalogue of rhythmic cells” such that:

Each cell consists of the same duration divided into two pulses related by a durational proportion ranging from 1:1 to 1:11.

The second pulse is then subdivided successively into 1, 2, 3, 4, 5 and 6 equal parts.

Let’s recreate Malt’s results in Abjad.

The Proportions

We use a list comprehension to describe a list of (1,n) tuples, each of which will describe the durational proportion between a cell's first and second pulse:


In [41]:
proportions = [(1, n) for n in range(1, 11 + 1)]

In [42]:
proportions


Out[42]:
[(1, 1),
 (1, 2),
 (1, 3),
 (1, 4),
 (1, 5),
 (1, 6),
 (1, 7),
 (1, 8),
 (1, 9),
 (1, 10),
 (1, 11)]

The Transforms

Next we’ll show how to divide a quarter note into various ratios, and then divide the final logical tie of the resulting tuplet into yet another ratio:


In [43]:
def make_nested_tuplet(
    tuplet_duration,
    outer_tuplet_proportions,
    inner_tuplet_subdivision_count,
    ):
    r'''Makes nested tuplet.
    '''

    outer_tuplet = abjad.Tuplet.from_duration_and_ratio(
        tuplet_duration, outer_tuplet_proportions)
    inner_tuplet_proportions = inner_tuplet_subdivision_count * [1]    
    last_leaf = next(abjad.iterate(outer_tuplet).leaves(reverse=True))
    right_logical_tie = abjad.inspect(last_leaf).logical_tie()
    right_logical_tie.to_tuplet(inner_tuplet_proportions)
    return outer_tuplet

And of course it's easier to see what this function does with an example of use:


In [44]:
tuplet = make_nested_tuplet(abjad.Duration(1, 4), (1, 1), 5)

In [45]:
staff = abjad.Staff([tuplet], lilypond_type='RhythmicStaff')

In [46]:
abjad.show(staff)


5 1:1

We see that a duration of a quarter note (the first argument) has been divided into two pulses with a durational proportion of 1:1 (second argument), the second pulse of which has then been divided into five equally spaced parts (the third argument). Try changing the arguments and see what happens.


In [47]:
tuplet = make_nested_tuplet(abjad.Duration(1, 4), (13, 28), 11)

In [48]:
staff = abjad.Staff([tuplet], lilypond_type='RhythmicStaff')

In [49]:
abjad.show(staff)


41 11:7

In [50]:
tuplet = make_nested_tuplet(abjad.Duration(1, 4), (3, 1), 5)

In [51]:
staff = abjad.Staff([tuplet], lilypond_type='RhythmicStaff')

In [52]:
abjad.show(staff)


5 1:1

Logical Ties Solve the Problem of Five

A logical tie is a selection of notes or chords connected by ties. It lets us talk about a notated rhythm of 5/16, for example, which can not be expressed with only a single leaf.

Note how we can divide a tuplet whose outer proportions are 3/5, where the second logical tie requires two notes to express the 5/16 duration:


In [53]:
outer_tuplet = abjad.Tuplet.from_duration_and_ratio(abjad.Duration(1, 4), (3, 5))

In [54]:
staff = abjad.Staff([outer_tuplet], lilypond_type='RhythmicStaff')

In [55]:
abjad.show(staff)


1:1

In [56]:
subdivided_tuplet = make_nested_tuplet(abjad.Duration(1, 4), (3, 5), 3)

In [57]:
staff = abjad.Staff([subdivided_tuplet], lilypond_type='RhythmicStaff')

In [58]:
abjad.show(staff)


6:5 1:1

Do you see which objects and methods in our make_nested_tuplet function convert a logical tie into a tuplet?

The Rhythms

Now that we know how to make the basic building block, let’s make a lot of tuplets all at once.

We’ll set the duration of each tuplet equal to a quarter note:


In [59]:
duration = abjad.Fraction(1,4)

Reusing our make_nested_tuplet function, we make one row of rhythms, with the last logical tie increasingly subdivided:


In [60]:
def make_row_of_nested_tuplets(
    tuplet_duration,
    outer_tuplet_proportions,
    column_count,
    ):
    r'''Makes row of nested tuplets.
    '''

    assert 0 < column_count
    row_of_nested_tuplets = []
    for n in range(column_count):
        inner_tuplet_subdivision_count = n + 1
        nested_tuplet = make_nested_tuplet(
            tuplet_duration,
            outer_tuplet_proportions,
            inner_tuplet_subdivision_count,
            )
        row_of_nested_tuplets.append(nested_tuplet)
    return row_of_nested_tuplets

In [61]:
tuplets = make_row_of_nested_tuplets(duration, (2, 1), 6)

In [62]:
staff = abjad.Staff(tuplets, lilypond_type='RhythmicStaff')

In [63]:
abjad.show(staff)


3 5 3 1:1 3 3 3 3 1:1 3 1:1 3

If we can make one single row of rhythms, we can make many rows of rhythms. We reuse this last function to make another function:


In [64]:
def make_rows_of_nested_tuplets(tuplet_duration, row_count, column_count):
    r'''Makes rows of nested tuplets.
    '''

    assert 0 < row_count
    rows_of_nested_tuplets = []
    for n in range(row_count):
        outer_tuplet_proportions = (1, n + 1)
        row_of_nested_tuplets = make_row_of_nested_tuplets(
            tuplet_duration, outer_tuplet_proportions, column_count)
        rows_of_nested_tuplets.append(row_of_nested_tuplets)
    return rows_of_nested_tuplets

In [65]:
score = abjad.Score()

In [66]:
for tuplet_row in make_rows_of_nested_tuplets(duration, 10, 6):
    score.append(abjad.Staff(tuplet_row, lilypond_type='RhythmicStaff'))

In [67]:
abjad.show(score)


16:9 1:1 8:7 4:3 8:5 1:1 4:3 1:1 1:1 1:1 3 1:1 5 1:1 3 1:1 5 3 7 1:1 9 3 5 11 8:5 5 5 1:1 3 1:1 5 3 7 1:1 9 5 11 7 1:1 9 5 11 1:1 10:9 5 10:7 5:3 1:1 5 5:3 1:1 4:3 1:1 1:1 1:1 3 1:1 5 3 7 1:1 9 5 11 8:5 16:9 1:1 3 1:1 5 3 7 1:1 9 5 11 8:5 16:9 1:1 8:7 4:3 8:5 9 5 11 6:5 4:3 3 12:7 1:1 6:5 3 1:1 3 3 1:1 8:7 4:3 8:5 1:1 4:3 1:1 1:1 1:1 3 1:1 5 3 7 1:1 6:5 4:3 3 12:7 1:1 6:5 3 1:1 3 3

This example illustrates how simpler bottom-up rhythmic construction might be abstracted to explore the potential of a bottom-up rhythmic idea.

Meter


In [68]:
### Chopping Durations with Durations

It's easy to make durations that don't neatly fit into a particular time signature:


In [69]:
staff = abjad.Staff("c'4. c'4. c'4. c'4. c'4. c'4. c'4. c'4.")
abjad.f(staff)


\new Staff
{
    c'4.
    c'4.
    c'4.
    c'4.
    c'4.
    c'4.
    c'4.
    c'4.
}

Let's make this metrically legible by chopping the leaves every whole note using mutate().split().

Note that this function returns selections full of the kind of component you split: if you want to split leaves in place, pass in staff[:], and if you want to return Selections containing new Staffs, pass in staff.

To keep splitting by the same duration until we get through all of the leaves, set the keyword argument cyclic equal to True.


In [70]:
abjad.mutate(staff[:]).split([abjad.Duration(4,4)], cyclic=True)
abjad.f(staff)


\new Staff
{
    c'4.
    c'4.
    c'4
    ~
    c'8
    c'4.
    c'4.
    c'8
    ~
    c'4
    c'4.
    c'4.
}

This is better -- the music now plays nice with LilyPond's barlines and isn't incorrectly notated -- but in Python, we still don't have any Measure objects, and our staff still iterates through leaves:


In [71]:
for leaf in staff[:5]:
    print(leaf)


c'4.
c'4.
c'4
c'8
c'4.

What should we do if we want to iterate through our music measure by measure?

Wrapping Leaves in Measures

We can create Measure objects by wrapping each split Selection in a Container:


In [72]:
duration = abjad.Duration(4,4)
for shard in abjad.mutate(staff[:]).split([duration] , cyclic=True):
    abjad.mutate(shard).wrap(abjad.Container())
abjad.f(staff)
abjad.show(staff)


\new Staff
{
    {
        c'4.
        c'4.
        c'4
        ~
    }
    {
        c'8
        c'4.
        c'4.
        c'8
        ~
    }
    {
        c'4
        c'4.
        c'4.
    }
}

Now we have measures in our score tree, and our staff will iterate through measures by default.


In [73]:
for measure in staff:
    print(measure)


Container("c'4. c'4. c'4 ~")
Container("c'8 c'4. c'4. c'8 ~")
Container("c'4 c'4. c'4.")

If we want to iterate by leaves, now we need to use abjad.iterate().


In [74]:
for leaf in abjad.iterate(staff).leaves():
    print(leaf)


c'4.
c'4.
c'4
c'8
c'4.
c'4.
c'8
c'4
c'4.
c'4.

Rewriting Rhythms with Metric Hierarchies

This is looking better, but the internal rhythms of our music still fail to comport with conventions of notation that dictate that we should always show beat three in a measure of (4,4). To do this, we need to impose a hierarchical model of meter onto our music, measure by measure.


In [75]:
four = abjad.Meter()
for measure in staff:
    abjad.mutate(measure).rewrite_meter(four)
abjad.f(staff)


\new Staff
{
    {
        c'4.
        c'4.
        c'4
        ~
    }
    {
        c'8
        c'4.
        c'4.
        c'8
        ~
    }
    {
        c'4
        c'4.
        c'4.
    }
}

This didn't change anything, because Abjad's default (4,4) hierarchy is just four quarter notes:


In [76]:
abjad.graph(four)


G cluster_offsets node_0 4/4 node_1 1/4 node_0->node_1 node_2 1/4 node_0->node_2 node_3 1/4 node_0->node_3 node_4 1/4 node_0->node_4 node_5_0 0 ++ node_1->node_5_0 node_5_1 1/4 + node_1->node_5_1 node_2->node_5_1 node_5_2 1/2 + node_2->node_5_2 node_3->node_5_2 node_5_3 3/4 + node_3->node_5_3 node_4->node_5_3 node_5_4 1 ++ node_4->node_5_4

We need to define a custom meter that includes another level of metric hierarchy and rewrite according to that. To do this, we'll use IRCAM's rhythm tree syntax:


In [77]:
other_four = abjad.Meter('(4/4 ((2/4 (1/4 1/4)) (2/4 (1/4 1/4)) ))')
for measure in staff:
    abjad.mutate(measure).rewrite_meter(other_four)
abjad.f(staff)


\new Staff
{
    {
        c'4.
        c'8
        ~
        c'4
        c'4
        ~
    }
    {
        c'8
        c'4.
        c'4.
        c'8
        ~
    }
    {
        c'4
        c'4
        ~
        c'8
        c'4.
    }
}

This works, because our new meter contains a second level of metric hierarchy.


In [78]:
abjad.graph(other_four)


G cluster_offsets node_0 4/4 node_1 2/4 node_0->node_1 node_4 2/4 node_0->node_4 node_2 1/4 node_1->node_2 node_3 1/4 node_1->node_3 node_7_0 0 +++ node_2->node_7_0 node_7_1 1/4 + node_2->node_7_1 node_3->node_7_1 node_7_2 1/2 ++ node_3->node_7_2 node_5 1/4 node_4->node_5 node_6 1/4 node_4->node_6 node_5->node_7_2 node_7_3 3/4 + node_5->node_7_3 node_6->node_7_3 node_7_4 1 +++ node_6->node_7_4

Now you can start to model your metric habits. For further useful customizations, including dot-count, see MutationAgent.rewrite_meter().


In [ ]: