-
Notifications
You must be signed in to change notification settings - Fork 0
Notes
We'll start our composition at the lowest level, with just a note.
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)
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 Note
s.
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.
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']
The get_note_data()
function returns a list of each piece of our Note
s 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.
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
.