In [13]:
import abjad
import abjadext.rmakers
%reload_ext abjadext.ipython
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.
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
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)
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)
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)
And we can also play our score back:
In [22]:
abjad.play(score)