Skip to content
charlies-world edited this page Nov 1, 2021 · 8 revisions

We'll start our composition at the lowest level, with just a note.

Fields

A Note object has four fields, all of which will be familiar to musicians:
Letter - This is the name you would use to refer to the note, stored as a string ('C','Db','F#'...)
Octave - This is the octave the note is in (1, 2, 6...)
(note that the range of MIDI notes is C#-2 to G9)
Velocity -This is the dynamic / volume level of the note, a value ranging from 0 to 127 (quietest to loudest)
Rhythmic Value - This is the length of the note in relation to the beat, stored as a list of strings, and later compounded (such as ['quarter'] or ['halfNote','quarter'] - more on this later)

Creating a Note

To create a note, all we do is pass our data in through __init__ or the set methods, like so:

# the parameters are all optional when creating the Note, but of course will have to be set at some point before the MIDI file is written
new_note = Note(letter='C', octave=4, velocity=70, rhythmic_value=['quarter']) 

# or

new_note = Note()
new_note.set_letter('C')
new_note.set_octave(4)
new_note.set_velocity(70)
new_note.set_rhythmic_value(['quarter'])

We are now storing a C4 note with a loudness of 70 and and rhythmic duration equal to that of a quarter note. Each of these setters have associated getters, and can be used like so:

print(new_note.get_letter()) # 'C'
print(new_note.get_octave()) # 4
print(new_note.get_velocity()) # 70
print(new_note.get_rhythmic_value()) # ['quarter']

Now you may be wondering "How can I get the actual MIDI data from this as a file so I can actually hear it and use it?" We will get to doing that, once we go up a few more layers of abstraction. For now, a little bit more on Notes.

Rhythmic Durations

In order to accommodate every possible rhythmic value, comPoYse is able to take in a list of strings of simple rhythmic values, and combine them to make the total duration. Here are the rhythmic values comPoYse recognizes:

two_hundred_fifty_sixth
one_hundred_twenty_eighth
sixty_fourth
thirty_secondth
sixteenth
eighth
quarter
half
whole

and here's how we might make various rhythms:

# an eighth note
my_note.set_rhythmic_value(['eighth'])

# a dotted half note
my_note.set_rhythmic_value(['half','quarter'])

# a whole note tied to a dotted quarter tied to a two hundred and fifty sixth note
my_note.set_rhythmic_value(['whole','quarter','eighth','two_hundred_fifty_sixth'])

We can also make tuplets. The format for expressing a tuplet is: [beat_tuplet_is_occuring_on, 'rhythmic_duration_one', 'rhythmic_duration_two',...]

The 'rhythmic_durations_x' combine in the same way we discussed above. The beat_tuplet_is_occuring_on refers to the rhythmic value that contains the tuplet. If we wanted to make a complete set of quarter note triplets, this is how we would do that:

note_one.set_rhythmic_value([3, 'half'])
note_two.set_rhythmic_value([3, 'half'])
note_three.set_rhythmic_value([3, 'half'])

We see here that setting the rhythmic duration of a tuplet only sets it for one note, so ensure that your program will create the proper amount of notes in proportion to the size of the tuplet.

If you attempt to set the rhythm to an invalid value or express the parts of the tuplet in the wrong order, comPoYse will throw a ValueNotValidRhythmicValue exception.

Rests

There are times when we might want something to not play. The Rest object is simply a reduced version of the Note object - they share all the methods relating to rhythmic values. Here's how to make a rest:

new_rest = Rest()
new_rest.set_rhythmic_duration(['whole'])
print(new_rest.get_rhythmic_duration()) # ['whole']

Other functions

The get_note_data() function returns a list of each piece of our Notes data. Recalling the new_note we made above:
print(new_note.get_note_data()) # [70, 'C', 4, ['quarter']]

Instead of setting the Note to an octave and letter, we can also go straight to setting it as a MIDI value, as well as retrieve it:

note.set_midi_value(60) #a C4 note
print(note.get_midi_value()) # 60

Finally, we can also alter the velocity and octave by simply adding or subtracting their values using the alter methods:

note.set_velocity(50)
print(note.get_velocity()) # 50
note.alter_velocity(20) #increases velocity by 20 units
print(note.get_velocity()) # 70
note.alter_velocity(-40) #decreases velocity by 40 units
print(note.get_velocity()) # 30

note.set_octave(3)
print(note.get_octave()) # 3
note.alter_octave(2)
print(note.get_octave()) # 5
note.alter_octave(-3)
print(note.get_octave()) # 2

Also know that if the program attempts to set any of these values outside of the 0 to 127 range, comPoYse will throw a ValueNotValidMIDIValue exception.

Moving On

So now we know how to make some pitches! Next we will start to look at how we can group our notes together in ways that make sense to make working with them easier - and the first way we'll do that is by creating a Measure.

Next section - Measures