Building the chord progression tutor

The GUI component of the chord progression section is slightly more evolved than the previous two sections. Here's how a typical chord progression GUI looks like:

Notice that this section has the combobox as opposed to two for the earlier sections.  Depending on what progression is chosen in the middle combobox, we need to draw a number of buttons, each button representing one chord in the complete chord progression. 

In the preceding screenshot, note that the progression combobox has a value of I-V-vi-IV. This is a total of four roman numbers separated by a dash. This means that this chord progression is made up of four chords. Also notice that a few of the roman numbers (I, V, IV, and so on) are written in capital letters and others (vi) are written in small letters.  All capital letters in the series denote major chords, while each small letter represents a minor chord. 

Next, let us take a look at the progressions.json file from the json folder:

{
"Major": {
"I-IV-V": [ "0", "5", "7" ],
"ii-V-I": [ "2", "7", "0" ],
"I-V-vi-IV": [ "0", "7", "9", "5" ],
... more here},
"Minor": {
"i-VI-VII": [ "0", "9", "11"],
"i-iv-VII": [ "0", "5", "11"],
"i-iv-v": [ "0", "5", "7" ],
..more here
}
}

As a first observation, the chord progressions are broadly of two types—major and minor. Each type has a list of chord progressions, which is identified by a set of roman numerals.

Let's see an example of how this would work.

Say we want to display the major chord progression ii-V-I in the key of C, as shown in the following screenshot:

The JSON file lists the progression under the Major section as:

 "ii-V-I": [ "2", "7", "0" ]

Let's first lay down the 12 notes in a table starting at the key of the chord progression (C in our example). We need to pick up the 2nd,  7th, and  0th keys for this progression:

0 1 2 3 4 5 6 7 8 9 10 11
C C# D D# E F F# G G# A A# B

The keys are D(2nd), G(7th), and C(0th). With the keys in hand—we next need to identify if each of the keys plays a major or minor chord. This is simple. Those roman numbers written in lower case play a minor chord, while those written in capitals play a major chord.

Given this rule, our final chords in the chord progression in the key of C are:

D Minor - G Major - C Major

Having identified these, our program should dynamically create three buttons. Clicking on these buttons should then play the preceding three chords, respectively.

Let's code this feature. We begin by defining the location of the chords progression file in 7.06 constants.py:

PROGRESSIONS_JSON_FILE = '../json/progressions.json'

We then load it from the method load_json_files() into an attribute named self.progressions:

 with open(PROGRESSIONS_JSON_FILE, 'r') as f:
self.progressions = json.load(f, object_pairs_hook=OrderedDict)

Next, we modify the progression frame to add three combobox elements. See the build_progressions_frame  code 7.06 view.py.

The three combobox are attached to the following three callbacks:

def on_progression_scale_changed(self, event):
selected_progression_scale = self.progression_scale_selector.get()
progressions = [k for k in
self.progressions[selected_progression_scale].keys()]
self.progression_selector['values'] = progressions
self.progression_selector.current(0)
self.show_progression_buttons()

def on_progression_key_changed(self,event):
self.show_progression_buttons()

def on_progression_changed(self,event):
self.show_progression_buttons()

The most complex of the three combobox is the progression scale combobox. It lets you choose between Major and Minor progression scales. Depending on the choice you make there,  it populates the second combobox with the progression values from the JSON file. This is what the first four lines of the on_progression_scale_changed method do.

Other than that, all three preceding callback methods defined make a call to the show_progression_buttons method, which is defined as follows:

def show_progression_buttons(self):
self.destroy_current_progression_buttons()
selected_progression_scale = self.progression_scale_selector.get()
selected_progression = self.progression_selector.get().split('-')
self.progression_buttons = []
for i in range(len(selected_progression)):
self.progression_buttons.append(Button(self.progressions_frame,
text=selected_progression[i],
command=partial(self.on_progression_button_clicked, i)))
sticky = 'W' if i == 0 else 'E'
col = i if i > 1 else 1
self.progression_buttons[i].grid(column=col, row=2, sticky=sticky,
padx=10)

The preceding code dynamically creates buttons—one for each chord in the chord progression and stores all the buttons in a list named self.progression_buttons.  We will keep this reference because we will have to destroy the buttons and create fresh ones every time a new chord progression is selected.

Note the use of the partial method from the functools module to define the button command callbacks. Since the buttons are being created dynamically, we need to keep track of the button number. We use this handy method partials that lets us call a method with only a partial number of arguments. Quoting from Python's documentation - The partial() function is used for partial function application, which freezes some portion of a function's arguments and/or keywords resulting in a new object with a simplified signature. You can read more about partials at https://docs.python.org/3/library/functools.html#functools.partial.

The preceding code calls a destroy_button method whose task is to clear the frame for drawing the next set of buttons, in case a new progression is selected.  The code is as follows:

def destroy_current_progression_buttons(self):
for buttons in self.progression_buttons:
buttons.destroy()

Finally, we want to display an individual chord from the chord progression when a button is clicked. This is defined as follows:

def on_progression_button_clicked(self, i):
self.remove_all_key_highlights()
selected_progression = self.progression_selector.get().split('-')[i]
if any(x.isupper() for x in selected_progression):
selected_chord = 'Major'
else:
selected_chord = 'Minor'
key_offset = ROMAN_TO_NUMBER[selected_progression]
selected_key = self.progression_key_selector.get()
index_of_selected_key = (KEYS.index(selected_key)+ key_offset)% 12
self.keys_to_highlight = [ ALL_KEYS[j+index_of_selected_key] for j in
self.chords[selected_chord]]
self.highlight_list_of_keys(self.keys_to_highlight)
play_chord_in_new_thread(self.keys_to_highlight)

Here's a brief description of the preceding code:

  • We first split the text, say ii-V-I, using the dash (-) delimiter. We then loop through the list and check if it is in uppercase or lowercase. If it is uppercase, the selected_chord variable is set to Major, if not it is set to Minor.
  • The index of the keys is calculated by adding the key to the numbers mentioned in the JSON file. We apply modulo operator (%) to the added value to ensure that the value does not exceed the limits of 12 notes.
  • Since the numbers are stored as roman numerals (this is the convention used by musicians), we need to convert it to integers. We do that by defining a simple key-value mapping in 7.05/constants.py:
ROMAN_TO_NUMBER = { 'I':0, 'II': 2, 'III':4, 'IV':5, 'V': 7, 'VI':9, 'VII': 11, 'i':0, 'ii': 2, 'iii':4, 'iv':5, 'v': 7, 'vi':9, 'vii': 11}
  • Note that we have mapped all numbers starting at 0 and the mapping follows the Major scale pattern (W W H W W S), where W stands for whole tone (two keys jump) and S stands for semitone (one key jump).
  • Now that we know that if the chord is a major or a minor, the rest of the code is exactly the same as we earlier used to identify the individual chords. We then highlight and play the individual chord.

To end, we modify the on_mode_changed  to add a call to show_progression_buttons() so that every time we switch to the chord progression section, the first chord progression buttons are laid down for us.

This completes the iteration. Our chord progression section is ready. Run code 7.06/view.py. Inside the chord progression tutor, you can select the chord progression type (major or minor), the progression, and its key from the comboboxes and it will create one button for each of the chords in the chord progression. Press the individual buttons and it will play you the chords in that progression.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.165.70