In [35]:
import abjad
%reload_ext abjadext.ipython
import abjadext.rmakers
The last notebook gave us a series of steps that together generate music, but they clearly divide up into several different code blocks that each accomplish a different specific task. Wouldn't it be nice to reuse these blocks if useful? In this notebook, we will clean up our procedural code and encapsulate it into several reusable functions.
In [36]:
# THIS IS THE INPUT TO MY MUSICAL IDEA
time_signature_pairs = [(4, 4), (3, 4), (7, 16), (6, 8)]
counts = [1, 2, -3, 4]
denominator = 16
pitches = abjad.CyclicTuple([0, 3, 7, 12, 7, 3])
In [37]:
def make_basic_rhythm(time_signature_pairs, counts, denominator):
# 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)
return music
In [38]:
def clean_up_rhythm(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
In [39]:
def add_pitches(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
In [40]:
def add_attachments(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
In [45]:
def make_music(time_signature_pairs, counts, denominator, pitches):
music = make_basic_rhythm(time_signature_pairs, counts, denominator)
music = clean_up_rhythm(music, time_signature_pairs)
music = add_pitches(music, pitches)
music = add_attachments(music)
return music
And we can call the function once to get music:
In [46]:
music = make_music(time_signature_pairs, counts, denominator, pitches)
abjad.show(music)
We've now converted our procedural code into reusable functions. You might notice that we're passing the same variables into these functions, over and over. This is usually a good hint that it might be a good idea to refactor the code with an object-oriented frame of mind. In our next notebook, these reusable functions will become the methods of a music-making class.