3-Music-Maker-v3



In [13]:
import abjad
import abjadext.rmakers
%reload_ext abjadext.ipython

Introduction to Object Orientation

Almost all complex projects created with Abjad dependn on a custom set of music-generating classes, and its essential to understand object-oriented programming to begin scaling your ideas into complex musical projects.

Procedural code views the world as a collection of verbs (actions): procedural code passes data into functions and functions pass altered data out to other functions, until the desired result has been achieved.

Object-oriented programming, on the other hand, views the world as a collection of nouns (objects) that act in an environment. The objects have both characteristics (attributes) and characteristic behaviors (methods).

In this notebook, we're going to take our procedural code from the previous notebook and turn our functions into the characteristic behaviors (methods) of a music-generating class.

1. The Class Definition

Now we'll define a MusicMaker() class. When we create an instance of this class by calling the class's initializer (my_musicmaker = MusicMaker(counts, denoms, pitches)), the class instance will be able to do the four characteristic behaviors we've given it (make basic rhythms, chop at meter, paint on pitches, and add compositional detail, not to mention use these all together in a make_music() method):


In [14]:
class MusicMaker(object):

    def __init__(
        self, 
        counts, 
        denominator, 
        pitches,
        clef,
        ):
        self.counts = counts
        self.denominator = denominator
        self.pitches = pitches
        self.clef = clef

    def make_basic_rhythm(self, time_signature_pairs, counts, denominator, clef):
        # THIS IS HOW WE MAKE THE BASIC RHYTHM
        total_duration = sum(abjad.Duration(pair) for pair in time_signature_pairs)
        talea = abjadext.rmakers.Talea(counts=counts, denominator=denominator)
        talea_index = 0
        all_leaves = [] # create an empty list for generated leaves
        current_duration = abjad.Duration(0) # keep track of the total duration as we generate each new leaf
        while current_duration < total_duration: # generate leaves until they add up to the total duration
            leaf_duration = talea[talea_index] # get a fraction from the talea
            if leaf_duration > 0: 
                pitch = abjad.NamedPitch("c'") # assign the leaf a pitch of middle C
            else:
                pitch = None # if the leaf is a rest, don't assign a pitch
            leaf_duration = abs(leaf_duration) # cancel the minus sign on the duration
            if (leaf_duration + current_duration) > total_duration:
                leaf_duration = total_duration - current_duration # catch any end condition by truncating the last duration
            current_leaves = abjad.LeafMaker()([pitch], [leaf_duration])   # make the leaves
            all_leaves.extend(current_leaves) # add the new leaves to the list of leaves
            current_duration += leaf_duration # advance the total duration
            talea_index += 1 # advance the talea index to the next fraction
        music = abjad.Container(all_leaves) 
        abjad.attach(abjad.Clef(clef), music[0])
        return music


    def clean_up_rhythm(self, music, time_signature_pairs):
        # THIS IS HOW WE CLEAN UP THE RHYTHM
        time_signatures = []
        for item in time_signature_pairs:
            time_signatures.append(abjad.TimeSignature(item))

        splits = abjad.mutate(music[:]).split(time_signature_pairs, cyclic=True)
        for time, measure in zip(time_signatures, splits):
            abjad.attach(time, measure[0])
    
        selector = abjad.select(music).leaves()
        measures = selector.group_by_measure()
        for time, measure in zip(time_signatures, measures):
            abjad.mutate(measure).rewrite_meter(time)
        return music
            

    def add_pitches(self, music, pitches):
        # THIS IS HOW WE ADD PITCHES
        pitches = abjad.CyclicTuple(pitches)
        logical_ties = abjad.iterate(music).logical_ties(pitched=True)
        for i, logical_tie in enumerate(logical_ties):
            pitch = pitches[i]
            for note in logical_tie:
                note.written_pitch = pitch
        return music


    def add_attachments(self, music):
        # THIS IS HOW WE ADD DYNAMICS AND ACCENTS
        for run in abjad.select(music).runs():
            abjad.attach(abjad.Articulation('accent'), run[0])
            if 1 < len(run):
                abjad.hairpin('p < f', run)
                abjad.override(run[0]).dynamic_line_spanner.staff_padding = 3
            else:
                abjad.attach(abjad.Dynamic('ppp'), run[0])
        return music


    def make_music(self, time_signature_pairs):
        music = self.make_basic_rhythm(
            time_signature_pairs,
            self.counts,
            self.denominator,
            self.clef
            )
        music = self.clean_up_rhythm(music, time_signature_pairs)
        music = self.add_pitches(music, self.pitches)
        music = self.add_attachments(music)
        return music

2. Instantiating a Class Instance with the Class Initlalizer Function

We can now create an instance of our MusicMaker() class by calling our class initializer. Note that we need to pass in counts, denominators, and pitches to the initializer function, as specified in the __init__ method of our class definition.


In [15]:
# THIS IS THE INPUT TO MY MUSICAL IDEA
time_signature_pairs = [(3, 4), (5, 16), (3, 8), (4, 4)]
counts = [1, 2, -3, 4]
denominator = 16
pitches = abjad.CyclicTuple([0, 3, 7, 12, 7, 3])
clef = "treble"

In [16]:
my_musicmaker = MusicMaker(counts, denominator, pitches, clef)

Finally, we can call our maker's make_music method to generate music. Note that we need to pass in our time signature pairs, as we specified in the class definition:


In [17]:
music = my_musicmaker.make_music(time_signature_pairs)
abjad.show(music)


3. Creating Varied Musical Textures with Multiple Instances of One Class

Because we can create multiple, variously initialized instances of the same class, it's possible to create both minimal and varied a polyphonic textures with just a single class definition. First, we pass different sets of initializers into a few different instances of our class.


In [18]:
fast_music_maker = MusicMaker(
    counts=[1, 1, 1, 1, 1, -1],
    denominator=16,
    pitches=[0, 1],
    clef="treble"
    )
slow_music_maker = MusicMaker(
    counts=[3, 4, 5, -1],
    denominator=4,
    pitches=["b,", "bf,", "gf,"],
    clef='bass',
    )
stuttering_music_maker = MusicMaker(
    counts=[1, 1, -7],
    denominator=16,
    pitches=[23],
    clef="treble"
    ) 
sparkling_music_maker = MusicMaker(
    counts=[1, -5, 1, -9, 1, -5],
    denominator=16,
    pitches=[38, 39, 40],
    clef='treble^8',
    )

Let's use these four musicmakers to create a duo. We can set up a score with two staves and generate the music according to a single set of time signatures:


In [19]:
upper_staff = abjad.Staff()
lower_staff = abjad.Staff()
time_signature_pairs = [(3, 4), (5, 16), (3, 8), (4, 4)]

Next, we iterate through a list of makers, allowing each to generate music that adds up to and fits inside the succession of time signatures, and append the music to our staves. We'll generate music for the top and bottom staff independently, but it's possible to create a musicmaker that outputs music for multiple staves simultaneously (as, for example, a list of selections, one per staff).


In [20]:
for music_maker in ( 
# in itereation, each differently configured music maker gets assigned to the name "music_maker"
    fast_music_maker,
    slow_music_maker,
    stuttering_music_maker,
    sparkling_music_maker,
    ):
    music = music_maker.make_music(time_signature_pairs) 
    # the current music_maker generates music
    upper_staff.append(music) 
    # add the generated music to the staff

for music_maker in ( # repeat above for lower staff
    slow_music_maker,
    slow_music_maker,
    stuttering_music_maker,
    fast_music_maker,
    ):
    music = music_maker.make_music(time_signature_pairs)
    lower_staff.append(music)

5. Score Construction

To construct the final score, we add our two staves to a PianoStaff(), add the PianoStaff() to a Score(), and we can add a final barline to our score.


In [21]:
piano_staff = abjad.StaffGroup()
piano_staff.extend([upper_staff, lower_staff])

score = abjad.Score([piano_staff])
score.add_final_bar_line()
abjad.show(score)


4 8 12 8 8 14

And we can also play our score back:


In [22]:
abjad.play(score)