More beat patterns

Our drum program is now functional. You can load drum samples and define a beat pattern and our drum machine will play it out. Let us now extend our drum program so that we are able to create more than one pattern in the same program.

Rather than a single drum pattern, now we will have a list of patterns. While playing the patterns, a user will be able to switch between many different beat patterns. This will allow the drummer to add variations to the performance.

Engage Thrusters

  1. The first thing we need to do is add a Spinbox widget in the top bar (as shown in the following screenshot), which will keep count of the number of patterns. We also add an Entry widget next to the Spinbox widget to keep track of the pattern name, which is decided by the number selected in the spin box.
    Engage Thrusters

    This is added to the create_top_bar method (refer to the code in 3.08.py):

    Label(top_bar_frame, text='Pattern Number:').grid(row=0, column=1)
    self.patt = IntVar()
    self.patt.set(0)
    self.prevpatvalue = 0 # to trace last click
    Spinbox(top_bar_frame, from_=0, to=9, width=5, textvariable=self.patt, command=self.record_pattern).grid(row=0, column=2)
    self.pat_name = Entry(top_bar_frame)
    self.pat_name.grid(row=0, column=3, padx=7,pady=2)
    self.pat_name.insert(0, 'Pattern %s'%self.patt.get())
    self.pat_name.config(state='readonly')

    The description of the code is listed as follows:

    • The pattern number is stored in a Tkinter integer variable as self.patt.
    • The Entry widget that stores the corresponding pattern name is called self.pat_name. This widget is marked as "read only", as we do not want to allow the user to modify the name.
    • The Spinbox widget has a command callback to a new method record_pattern.
  2. Let us now code the record_pattern method. The role of this method is to keep track of the state of a given pattern. Thus, for every pattern it needs to track the pattern number, units, BPU, drum samples loaded, and the beat pattern defined by the user for that pattern number. We will store this information in a list named self.pattern_list.

    Our pattern spin box allows for adding 10 patterns. Therefore, we first initialize self.pattern_list as an empty list comprising of 10 empty spaces.

    We initialize it in our class __init__ method as follows (also seen in the code 3.08.py):

    self.pattern_list = [None]*10

    Let us now code the record_pattern method:

    def record_pattern(self):
      pattern_num, bpu, units = self.patt.get(),self.bpu.get(), self.units.get()
      self.pat_name.config(state='normal')
      self.pat_name.delete(0, END)
      self.pat_name.insert(0, 'Pattern %s'%pattern_num)
      self.pat_name.config(state='readonly')
      prevpval = self.prevpatvalue
      self.prevpatvalue = pattern_num
      c = bpu*units
      self.buttonpickleformat =[[0] * c for x in range MAX_DRUM_NUM)]
      for i in range(MAX_DRUM_NUM):
        for j in range(c):
          if self.button[i][j].config('bg')[-1] == 'green':
            self.buttonpickleformat[i][j] = 'active'
            self.pattern_list[prevpval] = {'df': self.widget_drum_file_name, 'bl': self.buttonpickleformat, 'bpu':bpu, 'units':units}
      self.reconstruct_pattern(pattern_num, bpu, units)

    The description of the code is listed as follows:

    • The first line simply fetches the value of the current pattern number, bout, and units for the pattern to be recorded.
    • The next four lines of this code do one simple job. For every change in pattern, it simply updates the corresponding Entry widget with the new name of the pattern. Since the Entry widget is "read only", we first configure its state as normal to allow us to enter text into the Entry widget. We then delete anything that might already be written in the widget and enter the new pattern name with the Python string formatting of pattern_num'Pattern %s'%pattern_num. Finally, we restore the entry widget to a read only state.
    • The next two lines keep track of the last Spinbox widget number.
    • The next four lines of code actually record the state of the user-defined pattern in a two-dimensional list named self.buttonpickleformat. The list is first initialized to an empty two-dimensional matrix, taking into consideration the size of the pattern maker.
    • The loop then goes through every single button in the current pattern. If the button is not selected (not green), it leaves the value as 0. If the button is selected (green), the value at the corresponding place is changed from 0 to active. Using this list we can then easily reproduce the user-defined pattern later on.
    • Finally, all of this pattern-related data is stored as a list of the dictionary: self.pattern_list[prevpval] = {'df': self.widget_drum_file_name, 'bl': self.buttonpickleformat, 'bpu':bpu, 'units':units}
    • The key df stores the list of drum filenames. The key bl stores the pattern defined by the button. The key bpu stores the BPU for that pattern, and the key units stores the units for that pattern.
    • Now that all of these items for a pattern are stored as a dictionary, we can easily use the dictionary to reconstruct the pattern. The last line calls the method reconstruct_pattern(), which actually does the reconstruction for us.
  3. Now that we have stored pattern records, we need some method to reconstruct those patterns on our drum board. We define a new method reconstruct_pattern to handle it, as shown in the following code see the code in 3.08.py):
        def reconstruct_pattern(self,pattern_num, bpu, units):
            self.widget_drum_file_name = [0]*MAX_DRUM_NUM
            try:
                self.df = self.pattern_list[pattern_num]['df']
                for i in range(len(self.df)):
                        file_name = self.df[i]
                        if file_name == 0:
                            self.widget_drum_name[i].delete(0, END)
                            continue
                        self.widget_drum_file_name.insert(i, file_name)
                        drum_name = os.path.basename(file_name)
                        self.widget_drum_name[i].delete(0, END)
                        self.widget_drum_name[i].insert(0, drum_name)
            except:            
                    for i in range(MAX_DRUM_NUM):
                        try: self.df
                        except:self.widget_drum_name[i].delete(0, END)
            try:
                bpu = self.pattern_list[pattern_num]['bpu']
                units = self.pattern_list[pattern_num]['units']
            except:
                return
            self.bpu_widget.delete(0, END)
            self.bpu_widget.insert(0, bpu)
            self.units_widget.delete(0, END)
            self.units_widget.insert(0, units)
            self.create_right_pad()
            c = bpu * units
            self.create_right_pad()
            try:
                for i in range(MAX_DRUM_NUM):
                    for j in range(c):
                        if self.pattern_list[pattern_num]['bl'][i][j] == 'active':
                           self.button[i][j].config(bg='green')
            except:return

    This code can be broken into three broad parts:

    • Reconstructing drum sample uploads
    • Reconstructing BPU and units
    • Reconstructing beat patterns

    Having reconstructed these three things, we can easily replay any beat pattern. A brief description of each of these is as follows:

    • The list of drum filenames for a given pattern can easily be acquired from the key-value pair of the dictionary item self.pattern_list[pattern_num]['df']. We then iterate through items in this list and fill up the Entry widgets with each drum sample's filename.
    • We then fetch the value of BPU and units from the dictionary keys self.pattern_list[pattern_num]['bpu'] and self.pattern_list[pattern_num]['units']. We insert these values in their respective Spinbox widgets and then call the create_right_pad() method, which places the desired number of buttons on the right pad.
    • In the last iteration, we fetch the value of dictionary key self.pattern_list[pattern_num]['bl'], which gives us the position of the green buttons. Iterating through a loop, we check if a particular button is to be set to active. If yes, we change the color of the button to green.
    • Combined together, we can now load the previously recorded drum samples, set their Units and BPU values, and reconstruct the beat pattern as per previously set values.
    • At each stage, the code checks if it cannot reconstruct a particular pattern because of invalid file markup. If it does find some invalid markup, it breaks out of the code using appropriate exception handling.

Hit the Play button and the drum machine will start rolling sound. Change the pattern number and define a new beat pattern. The new pattern will start playing. Revert to older patterns and the older patterns start playing again (refer to the code in 3.08.py).

Objective Complete – Mini Debriefing

We've completed coding our drum machine to support the storing of multiple beat patterns, and the ability to play these patterns simply by changing the pattern number. This gives the user the ability to make different beats for the intro, verse, chorus, bridge, and other parts of a song.

In the process, we saw how to use Python's built-in data types to store custom data and to reproduce them in any required way.

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

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