guitarsounds.interface

   1from IPython.display import display, clear_output, HTML
   2from IPython import get_ipython
   3from guitarsounds.analysis import Plot, Signal, Sound, SoundPack
   4from guitarsounds.utils import generate_error_widget
   5import ipywidgets as widgets
   6import matplotlib.pyplot as plt
   7import io
   8import wave
   9import struct
  10import numpy as np
  11
  12
  13
  14
  15class guitarGUI(object):
  16    """ Main Graphical user interface class """
  17    # Output layout
  18    out_layout = {'border': '1px solid black'}
  19
  20    # Box Layout
  21    box_layout = widgets.Layout(align_items='stretch', flex_flow='line', width='50%')
  22
  23    # Fundamental input style
  24    style = {'description_width': 'initial'}
  25
  26    # Attribute for output layout
  27    output = widgets.Output(layout={'border': '1px solid black'})
  28
  29    # List of plot methods
  30    plot_methods = [Plot.signal, 
  31                    Plot.envelope, 
  32                    Plot.log_envelope, 
  33                    Plot.fft, 
  34                    Plot.fft_hist, 
  35                    Plot.peaks, 
  36                    Plot.peak_damping,
  37                    Plot.integral,
  38                    Plot.time_damping, ]
  39    bin_ticks_methods = [Plot.fft, Plot.fft_hist, Plot.peaks, Plot.peak_damping, ]
  40
  41    # Plot info dict
  42    plot_info_dict = {'signal': Plot.signal,
  43                      'envelope': Plot.envelope,
  44                      'log envelope': Plot.log_envelope,
  45                      'fft': Plot.fft,
  46                      'fft hist': Plot.fft_hist,
  47                      'peaks': Plot.peaks,
  48                      'peak damping': Plot.peak_damping,
  49                      'time damping': Plot.time_damping,
  50                      'integral': Plot.integral}
  51
  52    # analysis dropdowns
  53    # Single analysis drop down
  54    options = [('', 1),
  55               ('Listen Sound', Signal.listen),
  56               ('Listen frequency bins', Sound.listen_freq_bins),
  57               ('Frequency bin plot', Sound.plot_freq_bins),
  58               ('Frequency bin histogram', Sound.bin_hist),
  59               ('Signal plot', Plot.signal),
  60               ('envelope plot', Plot.envelope),
  61               ('Log-envelope plot', Plot.log_envelope),
  62               ('Fourier transform plot', Plot.fft),
  63               ('Fourier transform histogram', Plot.fft_hist),
  64               ('Peaks plot', Plot.peaks),
  65               ('Peak damping plot', Plot.peak_damping),
  66               ('Time damping plot', Plot.time_damping),
  67               ('Cumulative integral plot', Plot.integral),
  68               ('Frequency damping values', Sound.peak_damping), ]
  69
  70    drop_down_style = {'description_width': '150px'}
  71    single_drop_down = widgets.Dropdown(options=options, value=1, style=drop_down_style,
  72                                        description='Choose an analysis : ')
  73    single_drop_down.rank = 'first'
  74
  75    unique_plot_methods = [SoundPack.compare_peaks, SoundPack.fft_mirror, SoundPack.fft_diff, SoundPack.plot,
  76                           SoundPack.bin_power_hist, ]
  77
  78    # Dual analysis drop down
  79    options = [('', 1),
  80               ('Compare Peaks', SoundPack.compare_peaks),
  81               ('Mirror FFT', SoundPack.fft_mirror),
  82               ('FFT difference', SoundPack.fft_diff),
  83               ('Bin power comparison', SoundPack.integral_compare),
  84               ('Stacked plot', SoundPack.plot),
  85               ('Compared plot', SoundPack.compare_plot),
  86               ('Bin power plot', SoundPack.integral_plot),
  87               ('Bin power table', SoundPack.bin_power_table),
  88               ('Bin power histogram', SoundPack.bin_power_hist),
  89               ('Frequency Bin plot', SoundPack.freq_bin_plot),
  90               ('Print Fundamentals', SoundPack.fundamentals), ]
  91
  92    dual_drop_down = widgets.Dropdown(options=options, value=1, style=drop_down_style,
  93                                      description='Choose an analysis : ')
  94    dual_drop_down.rank = 'first'
  95
  96    # Multiple analysis drop down
  97    options = [('', 1),
  98               ('Stacked plot', SoundPack.plot),
  99               ('Compared plot', SoundPack.compare_plot),
 100               ('Frequency Bin plot', SoundPack.freq_bin_plot),
 101               ('Print Fundamentals', SoundPack.fundamentals),
 102               ('Bin power plot', SoundPack.integral_plot),
 103               ('Print bin powers', SoundPack.bin_power_table),
 104               ('Bin power histogram', SoundPack.bin_power_hist), ]
 105
 106    DM_bin_choice_methods = [SoundPack.freq_bin_plot, SoundPack.integral_plot, SoundPack.integral_compare]
 107
 108    mult_drop_down = widgets.Dropdown(options=options, value=1, style=drop_down_style,
 109                                      description='Choose an analysis : ')
 110    mult_drop_down.rank = 'first'
 111
 112    # Frequency bin choice drop down
 113    options = [('', 1),
 114               ('all', 'all'),
 115               ('bass', 'bass'),
 116               ('mid', 'mid'),
 117               ('highmid', 'highmid'),
 118               ('uppermid', 'uppermid'),
 119               ('presence', 'presence'),
 120               ('brillance', 'brillance'), ]
 121
 122    bin_drop_down = widgets.Dropdown(options=options, value='all', style=drop_down_style,
 123                                     description='Choose a frequency bin: ')
 124    bin_drop_down.rank = 'second'
 125    bin_drop_down.name = 'bin'
 126
 127    # Plot type choice drop down
 128    options = [('', 1),
 129               ('Signal', 'signal'),
 130               ('envelope', 'envelope'),
 131               ('Log Scale envelope', 'log envelope'),
 132               ('Fourier Transform', 'fft'),
 133               ('Fourier Transform Histogram', 'fft hist'),
 134               ('Fourier Transform Peaks', 'peaks'),
 135               ('Peak Damping', 'peak damping'),
 136               ('Time Damping', 'time damping'),
 137               ('Cumulative integral', 'integral'), ]
 138
 139    plot_drop_down = widgets.Dropdown(options=options, value='signal', style=drop_down_style,
 140                                      description='Choose a plot type: ')
 141    plot_drop_down.rank = 'second'
 142    plot_drop_down.name = 'plot'
 143
 144    def __init__(self):
 145        """
 146        Constructor for the Graphical User Interface class
 147
 148        Upon instanciation of the class, we display the three file choosing buttons matched with the
 149        three types of analyses when one is clicked the user is prompted
 150        to choose files.
 151
 152        When files are chosen  the user press the 'Ok' Button and the
 153        Interface  advances to defining names see `.on_ok_button_clicked_1`.
 154        """
 155
 156        # __ Buttons __
 157        # Number of sound choice buttons
 158        self.button1 = widgets.Button(description="Single Sound")
 159        self.button2 = widgets.Button(description="Dual Sounds")
 160        self.button3 = widgets.Button(description="Multiple Sounds")
 161
 162        # Ok, Done and Go Buttons
 163        self.ok_button = widgets.Button(description="Ok")
 164        self.done_button = widgets.Button(description="Done")
 165        self.go_button = widgets.Button(description='Go')
 166
 167        # Normalize toggle button
 168        self.toggle_normalize_button = widgets.Button(description='Normalize')
 169        # Associated attribute to normalize the Sounds for the method called
 170        self.normalize = False
 171
 172        # Info button
 173        self.info_button = widgets.Button(description='Info')
 174
 175        # Button box when the GUI starts
 176        self.button_box = widgets.Box(children=[self.button1,
 177                                                self.button2,
 178                                                self.button3,
 179                                                self.ok_button], layout=self.box_layout)
 180
 181        # Load bar when importing sounds
 182        self.load_bar = widgets.IntProgress(value=5, min=0, max=10,
 183                                            description='Importing sound files :',
 184                                            style={'bar_color': '#6495ED',
 185                                                   'description_width': '140px'}, )
 186
 187        # File selectors for uploading files into the program
 188        self.single_file_selector = widgets.FileUpload(accept='.wav', multiple=False)
 189        self.dual_file_selector_1 = widgets.FileUpload(accept='.wav', multiple=False)
 190        self.dual_file_selector_2 = widgets.FileUpload(accept='.wav', multiple=False)
 191        self.mult_file_selector = widgets.FileUpload(accept='.wav', multiple=True)
 192
 193        # Dict with dropdown methods to display the menu associated with
 194        # the analysis
 195        self.first_level_drop_down = {'Single': self.single_drop_down,
 196                                      'Dual': self.dual_drop_down,
 197                                      'Multiple': self.mult_drop_down}
 198
 199        # Initiate name spaces
 200        self.analysis = None
 201        self.display = None
 202        self.current_drop_down = None
 203        self.Pack = None
 204        self.analysis_tuple = None
 205        self.file_names = None
 206        self.sound_name_inputs = None
 207        self.sound_fundamental_inputs = None
 208        self.sounds = None
 209
 210        # Define the current state of the program
 211        self.state = 'start'
 212
 213        # Listen for clicks on the first button panel
 214        self.button1.on_click(self.on_single_button_clicked)
 215        self.button2.on_click(self.on_dual_button_clicked)
 216        self.button3.on_click(self.on_multiple_button_clicked)
 217        self.ok_button.on_click(self.on_ok_button_clicked_1)
 218        self.change_file_selection_state(False)
 219
 220        # display the buttons
 221        display(self.button_box)
 222
 223    """
 224    File Choosing Interface Button Click Methods
 225    """
 226
 227    def on_single_button_clicked(self, b):
 228        """
 229        Displays the single file selector, allowing the user to choose
 230        one file.
 231        :param: b the ipywidget button object for which this method is executed when it is clicked
 232        """
 233        if b is not None:
 234            pass
 235        clear_output(wait=True)
 236
 237        output = widgets.Output(layout={'border': '1px solid black'})
 238        self.change_file_selection_state(True)
 239        with output:
 240            display(self.single_file_selector)
 241
 242        self.analysis = 'Single'
 243        self.state = 'file entry'
 244
 245        display(self.button_box)
 246        display(output)
 247
 248    def on_dual_button_clicked(self, b):
 249        """
 250        Displays two single file selectors, allowing the user
 251        to choose two files.
 252        :param: b the ipywidget button object for which this method is executed when it is clicked
 253        """
 254        if b is not None:
 255            pass
 256        clear_output(wait=True)
 257
 258        output = widgets.Output(layout={'border': '1px solid black'})
 259        self.change_file_selection_state(True)
 260        with output:
 261            display(self.dual_file_selector_1)
 262            display(self.dual_file_selector_2)
 263
 264        self.analysis = 'Dual'
 265        self.state = 'file entry'
 266
 267        display(self.button_box)
 268        display(output)
 269
 270    def on_multiple_button_clicked(self, b):
 271        """
 272        Displays a multiple file selector allowing the user
 273        to select multiple files
 274        :param: b the ipywidget button object for which this method is executed when it is clicked
 275        """
 276        if b is not None:
 277            pass
 278        clear_output(wait=True)
 279
 280        output = widgets.Output(layout={'border': '1px solid black'})
 281        self.change_file_selection_state(True)
 282        with output:
 283            display(self.mult_file_selector)
 284
 285        self.analysis = 'Multiple'
 286        self.state = 'file entry'
 287
 288        display(self.button_box)
 289        display(output)
 290
 291    def change_file_selection_state(self, state):
 292        """
 293        Change the state of the file selection buttons
 294        :param state: bool state to which the selection should be changed
 295        """
 296        self.button1.disabled = state
 297        self.button2.disabled = state
 298        self.button3.disabled = state
 299
 300    def on_ok_button_clicked_1(self, b):
 301        """
 302        The user clicks this button when he is done choosing files and when
 303        he is done defining names
 304        :param: b the ipywidget button object for which this method is executed when it is clicked
 305        """
 306        if b is not None:
 307            pass
 308        # Clear the output
 309        clear_output(wait=True)
 310
 311        # Check if the user did good when choosing files
 312        file_selectors = [self.single_file_selector,
 313                          self.dual_file_selector_1,
 314                          self.dual_file_selector_2,
 315                          self.mult_file_selector]
 316        files_where_chosen = False
 317        for file_selector in file_selectors:
 318            if file_selector.value != {}:
 319                files_where_chosen = True
 320
 321        # If the file were chosen the user is taken to the "define name" interface
 322        if files_where_chosen:
 323            self.define_sound_names()
 324
 325        # if not we go back to file selection
 326        else:
 327            output = widgets.Output(layout={'border': '1px solid black'})
 328            with output:
 329                if self.analysis == 'Single':
 330                    display(self.single_file_selector)
 331                elif self.analysis == 'Dual':
 332                    display(self.dual_file_selector_1)
 333                    display(self.dual_file_selector_2)
 334                elif self.analysis == 'Multiple':
 335                    display(self.mult_file_selector)
 336                else:
 337                    error = generate_error_widget('Chose an analysis type')
 338                    display(error)
 339
 340                # Display an error if a file selector was clicked but no file was chosen
 341                if self.analysis in ['Single', 'Dual', 'Multiple']:
 342                    error = generate_error_widget('No sound was chosen')
 343                    display(error)
 344
 345            display(self.button_box)
 346            display(output)
 347
 348    """ 
 349    Analysis interface button click methods
 350    """
 351
 352    def on_ok_button_clicked_2(self, b):
 353        """
 354        Method to make the "Ok" button interact with the
 355        analysis method choice.
 356
 357        __ when interface.state = 'method choice' __
 358        - The "Ok" and "Go" buttons appears after the loading bar is done
 359        - The dropdown corresponds to the methods associated with
 360        the analysis
 361        :param: b the ipywidget button object for which this method is executed when it is clicked
 362        """
 363        if b is not None:
 364            pass
 365        # Clear the Output
 366        clear_output(wait=True)
 367        output = widgets.Output(layout=self.out_layout)
 368
 369        # Save the dropdown value
 370        drop_down_value = self.current_drop_down.value
 371        
 372        # enable the info button when coming back from display
 373        if self.state != 'display':
 374            self.info_button.disabled = False
 375            self.toggle_normalize_button.disabled = False
 376
 377        # Deactivate the info button if it was activated
 378        if self.info_button.button_style == 'info':
 379            self.info_button.button_style = ''
 380
 381        if self.state == 'method choice':  # State when the user is choosing the analysis method
 382
 383            # If we only analyse a single sound
 384            if self.analysis == 'Single':
 385
 386                # Special case when the method is the frequency bin plot
 387                if drop_down_value == Sound.plot_freq_bins:
 388                    self.analysis_tuple = [drop_down_value]  # Store the method
 389                    # Change the dropdown to frequency bin choice
 390                    self.current_drop_down = self.bin_drop_down
 391                    self.state = 'method choice 2'  # a second choice is needed
 392                    self.display = 'plot'
 393
 394                # Case for the methods without plotting
 395                elif drop_down_value in [Sound.peak_damping, Sound.listen_freq_bins, Signal.listen]:
 396                    self.analysis_tuple = [drop_down_value]  # store the method
 397                    self.state = 'display'  # ready to display
 398                    self.display = 'print'
 399
 400                # Signal.plot.method() methods
 401                elif drop_down_value in [*self.plot_methods, Sound.bin_hist]:
 402                    # store method and arg in a list
 403                    self.analysis_tuple = [drop_down_value]
 404                    self.state = 'display'  # ready to display
 405                    self.display = 'plot'
 406
 407                # Error when no method is chosen
 408                elif drop_down_value == 1:
 409                    error = generate_error_widget('No analysis method was chosen')
 410                    with output:
 411                        display(error)
 412
 413            # Case when two sounds or multiple sounds are being analysed
 414            elif self.analysis in ['Dual', 'Multiple']:
 415
 416                # Special case for the frequency bin plot
 417                if drop_down_value in self.DM_bin_choice_methods:
 418                    self.analysis_tuple = [drop_down_value]  # Store the method
 419                    # Update the dropdown to frequency bin choice
 420                    self.current_drop_down = self.bin_drop_down
 421                    self.state = 'method choice 2'  # a second choice is needed
 422                    self.display = 'plot'
 423
 424                # Case for plot methods
 425                elif (drop_down_value == SoundPack.plot) or (drop_down_value == SoundPack.compare_plot):
 426                    self.analysis_tuple = [drop_down_value]  # Store the method
 427                    # Update the dropdown to the plot dropdown
 428                    self.current_drop_down = self.plot_drop_down
 429                    self.state = 'method choice 2'  # a second choice is needed
 430                    self.display = 'plot'
 431
 432                # Error when no method is chosen
 433                elif drop_down_value == 1:
 434                    error = generate_error_widget('No analysis method was chosen')
 435                    with output:
 436                        display(error)
 437
 438                # Case for methods with no arguments
 439                else:
 440                    if drop_down_value == SoundPack.fundamentals:
 441                        self.display = 'print'
 442                    else:
 443                        self.display = 'plot'
 444                    self.analysis_tuple = [drop_down_value]  # store the method
 445                    self.state = 'display'
 446
 447        # Case when the method is chosen and an argument needs to be added 'method choice 2'
 448        elif self.state == 'method choice 2':
 449
 450            # add the arg part to the analysis tuple
 451            self.analysis_tuple.append(self.current_drop_down.value)
 452            self.state = 'display'
 453
 454        # if we are coming back from the display the state is redefined, and we restart
 455        elif self.state == 'analysis displayed':
 456            self.state = 'method choice'
 457
 458        # If the button is pressed and the method is defined, the go button is enabled
 459        if self.state == 'display':
 460            self.go_button.disabled = False
 461            self.ok_button.disabled = True
 462            self.info_button.disabled = True
 463            self.toggle_normalize_button.disabled = True
 464
 465        # Actualize the button box and display
 466        children = [self.ok_button, self.go_button, self.toggle_normalize_button, self.info_button]
 467        self.button_box = widgets.Box(children=children, layout=self.box_layout)
 468
 469        # Put the updated dropdown in the output
 470        with output:
 471            display(self.current_drop_down)
 472
 473        display(self.button_box, output)
 474
 475    def on_info_button_clicked(self, info):
 476        """
 477        Method called when the info button is clicked
 478        Displays the help string associated with the current dropdown method
 479        :param info:  the ipywidget button object for which this method is executed when it is clicked
 480        """
 481        if info.button_style == '':
 482            # change the style to make the button blue
 483            info.button_style = 'info'
 484
 485            # Clear the Output
 486            clear_output(wait=True)
 487            output = widgets.Output(layout=self.out_layout)
 488
 489            # Case when the user is selecting the first method
 490            if self.state == 'method choice':
 491
 492                # if the method is a tuple with an argument
 493                if type(self.current_drop_down.value) == tuple:
 494                    with output:
 495                        display(help(self.current_drop_down.value[0]))
 496
 497                # if no method was selected
 498                elif type(self.current_drop_down.value) == int:
 499                    error = generate_error_widget('No analysis was selected')
 500                    with output:
 501                        display(error)
 502
 503                # a method not in a tuple was selected
 504                else:
 505                    with output:
 506                        display(help(self.current_drop_down.value))
 507
 508                # display every thing
 509                display(self.button_box, output)
 510
 511            # case when the user is doing a secondary selection
 512            elif self.state == 'method choice 2':
 513
 514                # case for the plot type dropdown
 515                if self.current_drop_down.name == 'plot':
 516                    with output:
 517                        display(help(self.plot_info_dict[self.current_drop_down.value]))
 518
 519                # case for bin type dropdown (display the previous method)
 520                elif self.current_drop_down.name == 'bin':
 521                    with output:
 522                        display(help(self.analysis_tuple[0]))
 523
 524                display(self.button_box, output)
 525
 526        elif info.button_style == 'info':
 527            info.button_style = ''
 528
 529            # Clear the Output
 530            clear_output(wait=True)
 531            output = widgets.Output(layout=self.out_layout)
 532            with output:
 533                display(self.current_drop_down)
 534
 535            # display every thing
 536            display(self.button_box, output)
 537
 538    def on_normalize_button_clicked(self, toggle):
 539        """
 540        Method called when the normalize button is clicked
 541        The normalized attribute is inverted according to the current value
 542        :param toggle: the ipywidget button object for which this method is executed when pressed
 543        """
 544        if toggle.button_style == '':
 545            toggle.button_style = 'success'
 546            toggle.icon = 'check'
 547            self.normalize = True
 548
 549        elif toggle.button_style == 'success':
 550            toggle.button_style = ''
 551            toggle.icon = ''
 552            self.normalize = False
 553
 554    def on_done_button_clicked(self, b):
 555        """
 556        When the done button is clicked after the user had
 557        the option to define custom names this function is executed
 558
 559        A load bar is displayed while te files are loaded, when the
 560        load bar is done the `.on_loaded_bar()` method is called.
 561        :param b: the ipywidget button object for which this method is executed when pressed
 562        """
 563        if b is not None:
 564            pass
 565        clear_output(wait=True)
 566
 567        display(self.load_bar)
 568        self.load_bar.observe(self.on_loaded_bar, names="value")
 569
 570        self.load_bar.value += 1
 571        self.import_sound_files()
 572
 573    def on_go_button_clicked(self, b):
 574        """
 575        Go button to display the analysis when all choices are made
 576
 577        What happens :
 578        ___________________________________
 579        1. The output is cleared
 580        2. An output widget to store the output is instanced
 581        3. The method in `self.analysis_tuple` is called
 582        4. The display is added to the output
 583        5. The 'Ok' button is enabled and the 'Go' button is disabled
 584        6. The dropdown is set back to its default value
 585        7. The buttons and output are displayed
 586        :param b: the ipywidget button object corresponding to the go button
 587        """
 588        if b is not None:
 589            pass
 590        # Always clear the output
 591        clear_output(wait=True)
 592        output = widgets.Output(layout=self.out_layout)  # Create a output
 593
 594        # Change the GUI state
 595        self.state = 'analysis displayed'
 596
 597        # Set the matplotlib display method
 598        get_ipython().run_line_magic('matplotlib', 'inline')
 599
 600        # Case for a single sound
 601        if self.analysis == 'Single':
 602
 603            # Case for Sound.plot_freq_bins method
 604            if self.analysis_tuple[0] == Sound.plot_freq_bins:
 605                # change interface
 606                get_ipython().run_line_magic('matplotlib', 'notebook')
 607                # create a figure
 608                plt.figure(figsize=(8, 6))
 609                # Call the method
 610                self.analysis_tuple[0](self.sounds, bins=[self.analysis_tuple[1]])
 611
 612                # Define the title according to the chosen bin
 613                if self.analysis_tuple[1] == 'all':
 614                    plt.title('Frequency bin plot for ' + self.sounds.name)
 615                else:
 616                    plt.title(self.analysis_tuple[1] + ' bin plot for ' + self.sounds.name)
 617
 618                    plt.show()
 619
 620            # Case for the Sound.peak_damping method (print only)
 621            elif self.analysis_tuple[0] in [Sound.peak_damping, Sound.listen_freq_bins]:
 622                with output:
 623                    self.analysis_tuple[0](self.sounds)  # add print to output
 624
 625            # Case for the Signal.plot method
 626            elif self.analysis_tuple[0] in self.plot_methods:
 627                # change plot interface
 628                get_ipython().run_line_magic('matplotlib', 'notebook')
 629                # create a figure
 630                plt.figure(figsize=(8, 6))
 631                # Add the fill argument if there is just one plot
 632                kwargs = {}
 633                # Call the method according to normalization
 634                if not self.normalize:
 635                    self.analysis_tuple[0](self.sounds.signal.plot, **kwargs)
 636                elif self.normalize:
 637                    self.analysis_tuple[0](self.sounds.signal.normalize().plot, **kwargs)
 638
 639                if self.analysis_tuple[0] == Plot.time_damping:
 640                    zeta = np.around(self.sounds.signal.time_damping(), 5)
 641                    plt.title(self.current_drop_down.label + ' for ' + self.sounds.name + ' Zeta = ' + str(zeta))
 642                # Define a title from the signal.plot(kind)
 643                else:
 644                    plt.title(self.current_drop_down.label + ' for ' + self.sounds.name)
 645
 646                # make the x-axis ticks the frequency bins if the axe is frequency
 647                if self.analysis_tuple[0] in self.bin_ticks_methods:
 648                    Plot.set_bin_ticks(self.sounds.signal.plot)
 649                # add to output
 650                with output:
 651                    plt.show()
 652
 653            # Case for the Sound.bin_hist method
 654            elif self.analysis_tuple[0] == Sound.bin_hist:
 655                # change plot interface
 656                get_ipython().run_line_magic('matplotlib', 'notebook')
 657                # call the method
 658                self.analysis_tuple[0](self.sounds)
 659                # set a title
 660                plt.title(self.current_drop_down.label + ' for ' + self.sounds.name)
 661                # add to output
 662                with output:
 663                    plt.show()
 664
 665            # Case for the Signal.listen method
 666            elif self.analysis_tuple[0] == Signal.listen:
 667                # add to output
 668                with output:
 669                    # Call the method according to normalization
 670                    if not self.normalize:
 671                        self.analysis_tuple[0](self.sounds.signal)
 672                    elif self.normalize:
 673                        self.analysis_tuple[0](self.sounds.signal.normalize())
 674
 675        # Case for Dual and Multiple analyses
 676        elif self.analysis in ['Dual', 'Multiple']:
 677
 678            # normalize the sound_pack if self.normalize is True
 679            if self.normalize:
 680                sound_pack = self.Pack.normalize()
 681            else:
 682                sound_pack = self.Pack
 683
 684            # if the analysis method is a unique plot, make matplotlib interactive
 685            get_ipython().run_line_magic('matplotlib', 'inline')
 686            if self.analysis_tuple[0] in self.unique_plot_methods:
 687                get_ipython().run_line_magic('matplotlib', 'notebook')
 688
 689            # Call with no arguments
 690            if len(self.analysis_tuple) == 1:
 691                # Case for a print output
 692                if self.display == 'print':
 693                    with output:
 694                        self.analysis_tuple[0](sound_pack)  # add print to output
 695
 696                # special case to have bins ticks for the fft_diff method
 697                elif self.analysis_tuple[0] == SoundPack.fft_diff:
 698                    self.analysis_tuple[0](sound_pack, ticks='bins')
 699                    with output:
 700                        plt.show()  # display plot in output
 701
 702                # Case for a plot output
 703                elif self.display == 'plot':
 704                    self.analysis_tuple[0](sound_pack)
 705                    with output:
 706                        plt.show()  # display plot in output
 707
 708            # Call with arguments
 709            elif len(self.analysis_tuple) == 2:
 710                self.analysis_tuple[0](sound_pack, self.analysis_tuple[1])
 711                with output:
 712                    plt.show()
 713
 714        # Set up the dropdown to go back to method choice
 715        self.current_drop_down.value = 1
 716        self.current_drop_down = self.first_level_drop_down[self.analysis]
 717        self.current_drop_down.value = 1
 718
 719        # Set the Go and Ok buttons to default value
 720        self.go_button.disabled = True
 721        self.ok_button.disabled = False
 722
 723        # Set the normalization button to not normalized
 724        self.toggle_normalize_button.button_style = ''
 725        self.toggle_normalize_button.icon = ''
 726        self.normalize = False
 727
 728        # display
 729        display(self.button_box, output)
 730        # Make the window larger
 731        display(HTML("<style>div.output_scroll { height: 44em; }</style>"))
 732
 733    """
 734    Observe methods
 735    """
 736
 737    def on_loaded_bar(self, change):
 738        """
 739        This method monitors the value of the load bar used
 740        when loading files.
 741
 742        When the load bar is complete (value = 10), the
 743        button box is displayed with the "Ok" and "Go" buttons
 744        The "Go" button is disabled
 745        The dropdown with the methods according to the
 746        current analysis is displayed.
 747        :param change: the object containing the current value of the load bar
 748        """
 749        # When the bar reaches the end
 750        if change["new"] >= 10:
 751            clear_output(wait=True)
 752
 753            # disable the go_button
 754            self.state = 'method choice'
 755
 756            # Actualize the button box and display
 757            children = [self.ok_button, self.go_button, self.toggle_normalize_button, self.info_button]
 758            self.button_box = widgets.Box(children=children, layout=self.box_layout)
 759            self.ok_button.on_click(self.on_ok_button_clicked_2)
 760            self.go_button.on_click(self.on_go_button_clicked)
 761            self.toggle_normalize_button.on_click(self.on_normalize_button_clicked)
 762            self.info_button.on_click(self.on_info_button_clicked)
 763            self.go_button.disabled = True
 764            display(self.button_box)
 765
 766            # create the output
 767            output = widgets.Output(layout=self.out_layout)
 768
 769            # display the dropdown associated with the current analysis
 770            self.current_drop_down = self.first_level_drop_down[self.analysis]
 771            with output:
 772                display(self.current_drop_down)
 773
 774            display(output)
 775
 776    """
 777    Back end functions
 778    """
 779
 780    def define_sound_names(self):
 781        """
 782        A method to define sound names and fundamentals
 783        """
 784
 785        # Clear the output and define the new one
 786        clear_output(wait=True)
 787        output = widgets.Output(layout=self.out_layout)
 788
 789        # Style for the text inputs
 790        style = {'description_width': 'initial'}
 791
 792        # Small string 'Hz' to indicate units
 793        HZ_string = widgets.HTML('<p>' + 'Hz' + '</p>')
 794
 795        # Define the button box
 796        self.button_box = widgets.Box(children=[self.done_button], layout=self.box_layout)
 797
 798        # Define the output with the text inputs
 799        with output:
 800
 801            # Case for a single sound analysis
 802            if self.analysis == 'Single':
 803
 804                # get the filenames
 805                self.file_names = [ky for ky in self.single_file_selector.value.keys()]
 806
 807                # make a sound name input widget
 808                sound_name_input = widgets.Text(value='',
 809                                                placeholder='sound name',
 810                                                description=self.file_names[0],
 811                                                layout=widgets.Layout(width='40%'),
 812                                                style=style,
 813                                                )
 814
 815                # make a fundamental input widget
 816                fundamental_input = widgets.FloatText(value=0,
 817                                                      description='Fundamental :',
 818                                                      style=style,
 819                                                      layout=widgets.Layout(width='20%')
 820                                                      )
 821
 822                # children that go in the name box
 823                children = [sound_name_input, fundamental_input, HZ_string]
 824
 825                # define a name box widget
 826                name_box_layout = widgets.Layout(align_items='stretch', flex_flow='line', width='75%')
 827                name_box = widgets.Box(children=children, layout=name_box_layout)
 828
 829                # display the box
 830                display(name_box)
 831
 832                # store the input to refer them later
 833                self.sound_name_inputs = [sound_name_input]
 834                self.sound_fundamental_inputs = [fundamental_input]
 835
 836            # Case for dual sound analysis
 837            elif self.analysis in ['Dual', 'Multiple']:
 838
 839                if self.analysis == 'Dual':
 840                    # get the file names
 841                    name1 = [ky for ky in self.dual_file_selector_1.value.keys()][0]
 842                    name2 = [ky for ky in self.dual_file_selector_2.value.keys()][0]
 843                    self.file_names = [name1, name2]
 844
 845                elif self.analysis == 'Multiple':
 846                    self.file_names = [ky for ky in self.mult_file_selector.value.keys()]
 847
 848                # create empty lists for the inputs
 849                self.sound_name_inputs = []
 850                self.sound_fundamental_inputs = []
 851
 852                for file in self.file_names:
 853                    # make a text input widget
 854                    sound_name_input = widgets.Text(value='',
 855                                                    placeholder='sound name',
 856                                                    description=file,
 857                                                    layout=widgets.Layout(width='40%'),
 858                                                    style=style,
 859                                                    )
 860
 861                    # make a fundamental input widget
 862                    fundamental_input = widgets.FloatText(value=0,
 863                                                          description='Fundamental :',
 864                                                          layout=widgets.Layout(width='20%'),
 865                                                          style=style
 866                                                          )
 867
 868                    # children that go in the name box
 869                    children = [sound_name_input, fundamental_input, HZ_string]
 870
 871                    # define a name box widget
 872                    name_box_layout = widgets.Layout(align_items='stretch', flex_flow='line', width='75%')
 873                    name_box = widgets.Box(children=children, layout=name_box_layout)
 874
 875                    # display the box
 876                    display(name_box)
 877
 878                    # append the inputs
 879                    self.sound_name_inputs.append(sound_name_input)
 880                    self.sound_fundamental_inputs.append(fundamental_input)
 881
 882        self.done_button.on_click(self.on_done_button_clicked)
 883
 884        # display everything
 885        display(self.button_box, output)
 886
 887    def import_sound_files(self):
 888        """
 889        Method to import the soundfile vectors into the program
 890        after the files and names where defined.
 891        *Only works with .wav files*
 892        """
 893
 894        # Case for when only a single file is imported
 895        if self.analysis == 'Single':
 896            # Loading Bar Value = 0
 897            # Get the filename values from the file selector
 898            file_values = self.single_file_selector.value[self.file_names[0]]
 899
 900            # Get the signal audio bytes
 901            bites = file_values['content']
 902
 903            # Convert to wav audio object
 904            audio = wave.open(io.BytesIO(bites))
 905
 906            sr = audio.getframerate()  # save the frame rate
 907
 908            samples = []
 909            self.load_bar.value += 1  # LoadBar value = 1
 910            n = audio.getnframes()
 911            milestones = [int(i) for i in np.linspace(0, n, 5)][1:]
 912            for _ in range(audio.getnframes()):
 913                frame = audio.readframes(1)
 914                samples.append(struct.unpack("h", frame)[0])
 915                if _ in milestones:
 916                    self.load_bar.value += 1  # LoadBar value increases to 5 in loop
 917
 918            self.load_bar.value += 1  # LoadBar value = 7
 919            signal = np.array(samples) / 32768
 920            Sound_Input = (signal, sr)
 921            self.load_bar.value += 1  # LoadBar value = 8
 922
 923            # Get the sound name
 924            if self.sound_name_inputs[0].value == '':
 925                name = self.file_names[0].replace('.wav', '')
 926            else:
 927                name = self.sound_name_inputs[0].value
 928
 929            # Get the sound fundamental
 930            if self.sound_fundamental_inputs[0].value == 0:
 931                fundamental = None
 932            else:
 933                fundamental = self.sound_fundamental_inputs[0].value
 934
 935            self.load_bar.value += 1  # LoadBar value = 9
 936            # This takes a long time
 937            sound = Sound(Sound_Input, name=name, fundamental=fundamental)
 938            self.sounds = sound.condition(return_self=True, verbose=False)
 939            self.load_bar.value += 2  # Load-bar = 10
 940
 941        # Case for two files from two file selectors
 942        elif self.analysis == 'Dual':
 943            # LoadBar = 0
 944            self.sounds = []
 945            file_dicts = [self.dual_file_selector_1.value, self.dual_file_selector_2.value]
 946            self.load_bar.value += 2  # LoadBar = 1
 947
 948            # zipped iterator
 949            iterator = zip(self.file_names, file_dicts, self.sound_name_inputs, self.sound_fundamental_inputs)
 950
 951            #  Create a sound for every file
 952            for file, dic, name_input, fundamental_input in iterator:
 953
 954                file_values = dic[file]
 955                bites = file_values['content']
 956                audio = wave.open(io.BytesIO(bites))
 957                sr = audio.getframerate()
 958                samples = []
 959                self.load_bar.value += 1  # LoadBar +=2
 960                for _ in range(audio.getnframes()):
 961                    frame = audio.readframes(1)
 962                    samples.append(struct.unpack("h", frame)[0])
 963                self.load_bar.value += 1  # LoadBar +=2
 964                signal = np.array(samples) / 32768
 965                Sound_Input = (signal, sr)
 966
 967                # get the name value
 968                if name_input.value == '':
 969                    name = file.replace('.wav', '')
 970                else:
 971                    name = name_input.value
 972
 973                # get the fundamental value
 974                if fundamental_input.value == 0:
 975                    fundamental = None
 976                else:
 977                    fundamental = fundamental_input.value
 978
 979                sound = Sound(Sound_Input, name=name, fundamental=fundamental)
 980                sound.condition(verbose=False)
 981                self.sounds.append(sound)
 982                self.load_bar.value += 1  # LoadBar +=2
 983            # Load Bar = 8
 984            self.Pack = SoundPack(self.sounds, names=[sound.name for sound in self.sounds])
 985            self.load_bar.value += 2  # Load Bar = 10
 986
 987        # Case for multiple files
 988        elif self.analysis == 'Multiple':
 989            # LoadBar = 0
 990            self.sounds = []
 991            self.load_bar.value += 1  # LoadBar = 1
 992
 993            # zipped iterator
 994            iterator = zip(self.file_names, self.sound_name_inputs, self.sound_fundamental_inputs)
 995
 996            for file, name_input, fundamental_input in iterator:
 997                file_values = self.mult_file_selector.value[file]
 998                bites = file_values['content']
 999                audio = wave.open(io.BytesIO(bites))
1000                sr = audio.getframerate()
1001                samples = []
1002                for _ in range(audio.getnframes()):
1003                    frame = audio.readframes(1)
1004                    samples.append(struct.unpack("h", frame)[0])
1005                signal = np.array(samples) / 32768
1006                Sound_Input = (signal, sr)
1007
1008                # get the sound names
1009                if name_input.value == '':
1010                    name = file.replace('.wav', '')
1011                else:
1012                    name = name_input.value
1013
1014                # get the fundamental values
1015                if fundamental_input.value == 0:
1016                    fundamental = None
1017                else:
1018                    fundamental = fundamental_input.value
1019
1020                sound = Sound(Sound_Input, name=name, fundamental=fundamental)
1021                sound.condition(verbose=False)
1022                self.sounds.append(sound)
1023                if self.load_bar.value < 9:
1024                    self.load_bar.value += 1
1025
1026            self.Pack = SoundPack(self.sounds, names=[sound.name for sound in self.sounds])
1027            while self.load_bar.value < 10:
1028                self.load_bar.value += 1  # LoadBar = 10
class guitarGUI:
  16class guitarGUI(object):
  17    """ Main Graphical user interface class """
  18    # Output layout
  19    out_layout = {'border': '1px solid black'}
  20
  21    # Box Layout
  22    box_layout = widgets.Layout(align_items='stretch', flex_flow='line', width='50%')
  23
  24    # Fundamental input style
  25    style = {'description_width': 'initial'}
  26
  27    # Attribute for output layout
  28    output = widgets.Output(layout={'border': '1px solid black'})
  29
  30    # List of plot methods
  31    plot_methods = [Plot.signal, 
  32                    Plot.envelope, 
  33                    Plot.log_envelope, 
  34                    Plot.fft, 
  35                    Plot.fft_hist, 
  36                    Plot.peaks, 
  37                    Plot.peak_damping,
  38                    Plot.integral,
  39                    Plot.time_damping, ]
  40    bin_ticks_methods = [Plot.fft, Plot.fft_hist, Plot.peaks, Plot.peak_damping, ]
  41
  42    # Plot info dict
  43    plot_info_dict = {'signal': Plot.signal,
  44                      'envelope': Plot.envelope,
  45                      'log envelope': Plot.log_envelope,
  46                      'fft': Plot.fft,
  47                      'fft hist': Plot.fft_hist,
  48                      'peaks': Plot.peaks,
  49                      'peak damping': Plot.peak_damping,
  50                      'time damping': Plot.time_damping,
  51                      'integral': Plot.integral}
  52
  53    # analysis dropdowns
  54    # Single analysis drop down
  55    options = [('', 1),
  56               ('Listen Sound', Signal.listen),
  57               ('Listen frequency bins', Sound.listen_freq_bins),
  58               ('Frequency bin plot', Sound.plot_freq_bins),
  59               ('Frequency bin histogram', Sound.bin_hist),
  60               ('Signal plot', Plot.signal),
  61               ('envelope plot', Plot.envelope),
  62               ('Log-envelope plot', Plot.log_envelope),
  63               ('Fourier transform plot', Plot.fft),
  64               ('Fourier transform histogram', Plot.fft_hist),
  65               ('Peaks plot', Plot.peaks),
  66               ('Peak damping plot', Plot.peak_damping),
  67               ('Time damping plot', Plot.time_damping),
  68               ('Cumulative integral plot', Plot.integral),
  69               ('Frequency damping values', Sound.peak_damping), ]
  70
  71    drop_down_style = {'description_width': '150px'}
  72    single_drop_down = widgets.Dropdown(options=options, value=1, style=drop_down_style,
  73                                        description='Choose an analysis : ')
  74    single_drop_down.rank = 'first'
  75
  76    unique_plot_methods = [SoundPack.compare_peaks, SoundPack.fft_mirror, SoundPack.fft_diff, SoundPack.plot,
  77                           SoundPack.bin_power_hist, ]
  78
  79    # Dual analysis drop down
  80    options = [('', 1),
  81               ('Compare Peaks', SoundPack.compare_peaks),
  82               ('Mirror FFT', SoundPack.fft_mirror),
  83               ('FFT difference', SoundPack.fft_diff),
  84               ('Bin power comparison', SoundPack.integral_compare),
  85               ('Stacked plot', SoundPack.plot),
  86               ('Compared plot', SoundPack.compare_plot),
  87               ('Bin power plot', SoundPack.integral_plot),
  88               ('Bin power table', SoundPack.bin_power_table),
  89               ('Bin power histogram', SoundPack.bin_power_hist),
  90               ('Frequency Bin plot', SoundPack.freq_bin_plot),
  91               ('Print Fundamentals', SoundPack.fundamentals), ]
  92
  93    dual_drop_down = widgets.Dropdown(options=options, value=1, style=drop_down_style,
  94                                      description='Choose an analysis : ')
  95    dual_drop_down.rank = 'first'
  96
  97    # Multiple analysis drop down
  98    options = [('', 1),
  99               ('Stacked plot', SoundPack.plot),
 100               ('Compared plot', SoundPack.compare_plot),
 101               ('Frequency Bin plot', SoundPack.freq_bin_plot),
 102               ('Print Fundamentals', SoundPack.fundamentals),
 103               ('Bin power plot', SoundPack.integral_plot),
 104               ('Print bin powers', SoundPack.bin_power_table),
 105               ('Bin power histogram', SoundPack.bin_power_hist), ]
 106
 107    DM_bin_choice_methods = [SoundPack.freq_bin_plot, SoundPack.integral_plot, SoundPack.integral_compare]
 108
 109    mult_drop_down = widgets.Dropdown(options=options, value=1, style=drop_down_style,
 110                                      description='Choose an analysis : ')
 111    mult_drop_down.rank = 'first'
 112
 113    # Frequency bin choice drop down
 114    options = [('', 1),
 115               ('all', 'all'),
 116               ('bass', 'bass'),
 117               ('mid', 'mid'),
 118               ('highmid', 'highmid'),
 119               ('uppermid', 'uppermid'),
 120               ('presence', 'presence'),
 121               ('brillance', 'brillance'), ]
 122
 123    bin_drop_down = widgets.Dropdown(options=options, value='all', style=drop_down_style,
 124                                     description='Choose a frequency bin: ')
 125    bin_drop_down.rank = 'second'
 126    bin_drop_down.name = 'bin'
 127
 128    # Plot type choice drop down
 129    options = [('', 1),
 130               ('Signal', 'signal'),
 131               ('envelope', 'envelope'),
 132               ('Log Scale envelope', 'log envelope'),
 133               ('Fourier Transform', 'fft'),
 134               ('Fourier Transform Histogram', 'fft hist'),
 135               ('Fourier Transform Peaks', 'peaks'),
 136               ('Peak Damping', 'peak damping'),
 137               ('Time Damping', 'time damping'),
 138               ('Cumulative integral', 'integral'), ]
 139
 140    plot_drop_down = widgets.Dropdown(options=options, value='signal', style=drop_down_style,
 141                                      description='Choose a plot type: ')
 142    plot_drop_down.rank = 'second'
 143    plot_drop_down.name = 'plot'
 144
 145    def __init__(self):
 146        """
 147        Constructor for the Graphical User Interface class
 148
 149        Upon instanciation of the class, we display the three file choosing buttons matched with the
 150        three types of analyses when one is clicked the user is prompted
 151        to choose files.
 152
 153        When files are chosen  the user press the 'Ok' Button and the
 154        Interface  advances to defining names see `.on_ok_button_clicked_1`.
 155        """
 156
 157        # __ Buttons __
 158        # Number of sound choice buttons
 159        self.button1 = widgets.Button(description="Single Sound")
 160        self.button2 = widgets.Button(description="Dual Sounds")
 161        self.button3 = widgets.Button(description="Multiple Sounds")
 162
 163        # Ok, Done and Go Buttons
 164        self.ok_button = widgets.Button(description="Ok")
 165        self.done_button = widgets.Button(description="Done")
 166        self.go_button = widgets.Button(description='Go')
 167
 168        # Normalize toggle button
 169        self.toggle_normalize_button = widgets.Button(description='Normalize')
 170        # Associated attribute to normalize the Sounds for the method called
 171        self.normalize = False
 172
 173        # Info button
 174        self.info_button = widgets.Button(description='Info')
 175
 176        # Button box when the GUI starts
 177        self.button_box = widgets.Box(children=[self.button1,
 178                                                self.button2,
 179                                                self.button3,
 180                                                self.ok_button], layout=self.box_layout)
 181
 182        # Load bar when importing sounds
 183        self.load_bar = widgets.IntProgress(value=5, min=0, max=10,
 184                                            description='Importing sound files :',
 185                                            style={'bar_color': '#6495ED',
 186                                                   'description_width': '140px'}, )
 187
 188        # File selectors for uploading files into the program
 189        self.single_file_selector = widgets.FileUpload(accept='.wav', multiple=False)
 190        self.dual_file_selector_1 = widgets.FileUpload(accept='.wav', multiple=False)
 191        self.dual_file_selector_2 = widgets.FileUpload(accept='.wav', multiple=False)
 192        self.mult_file_selector = widgets.FileUpload(accept='.wav', multiple=True)
 193
 194        # Dict with dropdown methods to display the menu associated with
 195        # the analysis
 196        self.first_level_drop_down = {'Single': self.single_drop_down,
 197                                      'Dual': self.dual_drop_down,
 198                                      'Multiple': self.mult_drop_down}
 199
 200        # Initiate name spaces
 201        self.analysis = None
 202        self.display = None
 203        self.current_drop_down = None
 204        self.Pack = None
 205        self.analysis_tuple = None
 206        self.file_names = None
 207        self.sound_name_inputs = None
 208        self.sound_fundamental_inputs = None
 209        self.sounds = None
 210
 211        # Define the current state of the program
 212        self.state = 'start'
 213
 214        # Listen for clicks on the first button panel
 215        self.button1.on_click(self.on_single_button_clicked)
 216        self.button2.on_click(self.on_dual_button_clicked)
 217        self.button3.on_click(self.on_multiple_button_clicked)
 218        self.ok_button.on_click(self.on_ok_button_clicked_1)
 219        self.change_file_selection_state(False)
 220
 221        # display the buttons
 222        display(self.button_box)
 223
 224    """
 225    File Choosing Interface Button Click Methods
 226    """
 227
 228    def on_single_button_clicked(self, b):
 229        """
 230        Displays the single file selector, allowing the user to choose
 231        one file.
 232        :param: b the ipywidget button object for which this method is executed when it is clicked
 233        """
 234        if b is not None:
 235            pass
 236        clear_output(wait=True)
 237
 238        output = widgets.Output(layout={'border': '1px solid black'})
 239        self.change_file_selection_state(True)
 240        with output:
 241            display(self.single_file_selector)
 242
 243        self.analysis = 'Single'
 244        self.state = 'file entry'
 245
 246        display(self.button_box)
 247        display(output)
 248
 249    def on_dual_button_clicked(self, b):
 250        """
 251        Displays two single file selectors, allowing the user
 252        to choose two files.
 253        :param: b the ipywidget button object for which this method is executed when it is clicked
 254        """
 255        if b is not None:
 256            pass
 257        clear_output(wait=True)
 258
 259        output = widgets.Output(layout={'border': '1px solid black'})
 260        self.change_file_selection_state(True)
 261        with output:
 262            display(self.dual_file_selector_1)
 263            display(self.dual_file_selector_2)
 264
 265        self.analysis = 'Dual'
 266        self.state = 'file entry'
 267
 268        display(self.button_box)
 269        display(output)
 270
 271    def on_multiple_button_clicked(self, b):
 272        """
 273        Displays a multiple file selector allowing the user
 274        to select multiple files
 275        :param: b the ipywidget button object for which this method is executed when it is clicked
 276        """
 277        if b is not None:
 278            pass
 279        clear_output(wait=True)
 280
 281        output = widgets.Output(layout={'border': '1px solid black'})
 282        self.change_file_selection_state(True)
 283        with output:
 284            display(self.mult_file_selector)
 285
 286        self.analysis = 'Multiple'
 287        self.state = 'file entry'
 288
 289        display(self.button_box)
 290        display(output)
 291
 292    def change_file_selection_state(self, state):
 293        """
 294        Change the state of the file selection buttons
 295        :param state: bool state to which the selection should be changed
 296        """
 297        self.button1.disabled = state
 298        self.button2.disabled = state
 299        self.button3.disabled = state
 300
 301    def on_ok_button_clicked_1(self, b):
 302        """
 303        The user clicks this button when he is done choosing files and when
 304        he is done defining names
 305        :param: b the ipywidget button object for which this method is executed when it is clicked
 306        """
 307        if b is not None:
 308            pass
 309        # Clear the output
 310        clear_output(wait=True)
 311
 312        # Check if the user did good when choosing files
 313        file_selectors = [self.single_file_selector,
 314                          self.dual_file_selector_1,
 315                          self.dual_file_selector_2,
 316                          self.mult_file_selector]
 317        files_where_chosen = False
 318        for file_selector in file_selectors:
 319            if file_selector.value != {}:
 320                files_where_chosen = True
 321
 322        # If the file were chosen the user is taken to the "define name" interface
 323        if files_where_chosen:
 324            self.define_sound_names()
 325
 326        # if not we go back to file selection
 327        else:
 328            output = widgets.Output(layout={'border': '1px solid black'})
 329            with output:
 330                if self.analysis == 'Single':
 331                    display(self.single_file_selector)
 332                elif self.analysis == 'Dual':
 333                    display(self.dual_file_selector_1)
 334                    display(self.dual_file_selector_2)
 335                elif self.analysis == 'Multiple':
 336                    display(self.mult_file_selector)
 337                else:
 338                    error = generate_error_widget('Chose an analysis type')
 339                    display(error)
 340
 341                # Display an error if a file selector was clicked but no file was chosen
 342                if self.analysis in ['Single', 'Dual', 'Multiple']:
 343                    error = generate_error_widget('No sound was chosen')
 344                    display(error)
 345
 346            display(self.button_box)
 347            display(output)
 348
 349    """ 
 350    Analysis interface button click methods
 351    """
 352
 353    def on_ok_button_clicked_2(self, b):
 354        """
 355        Method to make the "Ok" button interact with the
 356        analysis method choice.
 357
 358        __ when interface.state = 'method choice' __
 359        - The "Ok" and "Go" buttons appears after the loading bar is done
 360        - The dropdown corresponds to the methods associated with
 361        the analysis
 362        :param: b the ipywidget button object for which this method is executed when it is clicked
 363        """
 364        if b is not None:
 365            pass
 366        # Clear the Output
 367        clear_output(wait=True)
 368        output = widgets.Output(layout=self.out_layout)
 369
 370        # Save the dropdown value
 371        drop_down_value = self.current_drop_down.value
 372        
 373        # enable the info button when coming back from display
 374        if self.state != 'display':
 375            self.info_button.disabled = False
 376            self.toggle_normalize_button.disabled = False
 377
 378        # Deactivate the info button if it was activated
 379        if self.info_button.button_style == 'info':
 380            self.info_button.button_style = ''
 381
 382        if self.state == 'method choice':  # State when the user is choosing the analysis method
 383
 384            # If we only analyse a single sound
 385            if self.analysis == 'Single':
 386
 387                # Special case when the method is the frequency bin plot
 388                if drop_down_value == Sound.plot_freq_bins:
 389                    self.analysis_tuple = [drop_down_value]  # Store the method
 390                    # Change the dropdown to frequency bin choice
 391                    self.current_drop_down = self.bin_drop_down
 392                    self.state = 'method choice 2'  # a second choice is needed
 393                    self.display = 'plot'
 394
 395                # Case for the methods without plotting
 396                elif drop_down_value in [Sound.peak_damping, Sound.listen_freq_bins, Signal.listen]:
 397                    self.analysis_tuple = [drop_down_value]  # store the method
 398                    self.state = 'display'  # ready to display
 399                    self.display = 'print'
 400
 401                # Signal.plot.method() methods
 402                elif drop_down_value in [*self.plot_methods, Sound.bin_hist]:
 403                    # store method and arg in a list
 404                    self.analysis_tuple = [drop_down_value]
 405                    self.state = 'display'  # ready to display
 406                    self.display = 'plot'
 407
 408                # Error when no method is chosen
 409                elif drop_down_value == 1:
 410                    error = generate_error_widget('No analysis method was chosen')
 411                    with output:
 412                        display(error)
 413
 414            # Case when two sounds or multiple sounds are being analysed
 415            elif self.analysis in ['Dual', 'Multiple']:
 416
 417                # Special case for the frequency bin plot
 418                if drop_down_value in self.DM_bin_choice_methods:
 419                    self.analysis_tuple = [drop_down_value]  # Store the method
 420                    # Update the dropdown to frequency bin choice
 421                    self.current_drop_down = self.bin_drop_down
 422                    self.state = 'method choice 2'  # a second choice is needed
 423                    self.display = 'plot'
 424
 425                # Case for plot methods
 426                elif (drop_down_value == SoundPack.plot) or (drop_down_value == SoundPack.compare_plot):
 427                    self.analysis_tuple = [drop_down_value]  # Store the method
 428                    # Update the dropdown to the plot dropdown
 429                    self.current_drop_down = self.plot_drop_down
 430                    self.state = 'method choice 2'  # a second choice is needed
 431                    self.display = 'plot'
 432
 433                # Error when no method is chosen
 434                elif drop_down_value == 1:
 435                    error = generate_error_widget('No analysis method was chosen')
 436                    with output:
 437                        display(error)
 438
 439                # Case for methods with no arguments
 440                else:
 441                    if drop_down_value == SoundPack.fundamentals:
 442                        self.display = 'print'
 443                    else:
 444                        self.display = 'plot'
 445                    self.analysis_tuple = [drop_down_value]  # store the method
 446                    self.state = 'display'
 447
 448        # Case when the method is chosen and an argument needs to be added 'method choice 2'
 449        elif self.state == 'method choice 2':
 450
 451            # add the arg part to the analysis tuple
 452            self.analysis_tuple.append(self.current_drop_down.value)
 453            self.state = 'display'
 454
 455        # if we are coming back from the display the state is redefined, and we restart
 456        elif self.state == 'analysis displayed':
 457            self.state = 'method choice'
 458
 459        # If the button is pressed and the method is defined, the go button is enabled
 460        if self.state == 'display':
 461            self.go_button.disabled = False
 462            self.ok_button.disabled = True
 463            self.info_button.disabled = True
 464            self.toggle_normalize_button.disabled = True
 465
 466        # Actualize the button box and display
 467        children = [self.ok_button, self.go_button, self.toggle_normalize_button, self.info_button]
 468        self.button_box = widgets.Box(children=children, layout=self.box_layout)
 469
 470        # Put the updated dropdown in the output
 471        with output:
 472            display(self.current_drop_down)
 473
 474        display(self.button_box, output)
 475
 476    def on_info_button_clicked(self, info):
 477        """
 478        Method called when the info button is clicked
 479        Displays the help string associated with the current dropdown method
 480        :param info:  the ipywidget button object for which this method is executed when it is clicked
 481        """
 482        if info.button_style == '':
 483            # change the style to make the button blue
 484            info.button_style = 'info'
 485
 486            # Clear the Output
 487            clear_output(wait=True)
 488            output = widgets.Output(layout=self.out_layout)
 489
 490            # Case when the user is selecting the first method
 491            if self.state == 'method choice':
 492
 493                # if the method is a tuple with an argument
 494                if type(self.current_drop_down.value) == tuple:
 495                    with output:
 496                        display(help(self.current_drop_down.value[0]))
 497
 498                # if no method was selected
 499                elif type(self.current_drop_down.value) == int:
 500                    error = generate_error_widget('No analysis was selected')
 501                    with output:
 502                        display(error)
 503
 504                # a method not in a tuple was selected
 505                else:
 506                    with output:
 507                        display(help(self.current_drop_down.value))
 508
 509                # display every thing
 510                display(self.button_box, output)
 511
 512            # case when the user is doing a secondary selection
 513            elif self.state == 'method choice 2':
 514
 515                # case for the plot type dropdown
 516                if self.current_drop_down.name == 'plot':
 517                    with output:
 518                        display(help(self.plot_info_dict[self.current_drop_down.value]))
 519
 520                # case for bin type dropdown (display the previous method)
 521                elif self.current_drop_down.name == 'bin':
 522                    with output:
 523                        display(help(self.analysis_tuple[0]))
 524
 525                display(self.button_box, output)
 526
 527        elif info.button_style == 'info':
 528            info.button_style = ''
 529
 530            # Clear the Output
 531            clear_output(wait=True)
 532            output = widgets.Output(layout=self.out_layout)
 533            with output:
 534                display(self.current_drop_down)
 535
 536            # display every thing
 537            display(self.button_box, output)
 538
 539    def on_normalize_button_clicked(self, toggle):
 540        """
 541        Method called when the normalize button is clicked
 542        The normalized attribute is inverted according to the current value
 543        :param toggle: the ipywidget button object for which this method is executed when pressed
 544        """
 545        if toggle.button_style == '':
 546            toggle.button_style = 'success'
 547            toggle.icon = 'check'
 548            self.normalize = True
 549
 550        elif toggle.button_style == 'success':
 551            toggle.button_style = ''
 552            toggle.icon = ''
 553            self.normalize = False
 554
 555    def on_done_button_clicked(self, b):
 556        """
 557        When the done button is clicked after the user had
 558        the option to define custom names this function is executed
 559
 560        A load bar is displayed while te files are loaded, when the
 561        load bar is done the `.on_loaded_bar()` method is called.
 562        :param b: the ipywidget button object for which this method is executed when pressed
 563        """
 564        if b is not None:
 565            pass
 566        clear_output(wait=True)
 567
 568        display(self.load_bar)
 569        self.load_bar.observe(self.on_loaded_bar, names="value")
 570
 571        self.load_bar.value += 1
 572        self.import_sound_files()
 573
 574    def on_go_button_clicked(self, b):
 575        """
 576        Go button to display the analysis when all choices are made
 577
 578        What happens :
 579        ___________________________________
 580        1. The output is cleared
 581        2. An output widget to store the output is instanced
 582        3. The method in `self.analysis_tuple` is called
 583        4. The display is added to the output
 584        5. The 'Ok' button is enabled and the 'Go' button is disabled
 585        6. The dropdown is set back to its default value
 586        7. The buttons and output are displayed
 587        :param b: the ipywidget button object corresponding to the go button
 588        """
 589        if b is not None:
 590            pass
 591        # Always clear the output
 592        clear_output(wait=True)
 593        output = widgets.Output(layout=self.out_layout)  # Create a output
 594
 595        # Change the GUI state
 596        self.state = 'analysis displayed'
 597
 598        # Set the matplotlib display method
 599        get_ipython().run_line_magic('matplotlib', 'inline')
 600
 601        # Case for a single sound
 602        if self.analysis == 'Single':
 603
 604            # Case for Sound.plot_freq_bins method
 605            if self.analysis_tuple[0] == Sound.plot_freq_bins:
 606                # change interface
 607                get_ipython().run_line_magic('matplotlib', 'notebook')
 608                # create a figure
 609                plt.figure(figsize=(8, 6))
 610                # Call the method
 611                self.analysis_tuple[0](self.sounds, bins=[self.analysis_tuple[1]])
 612
 613                # Define the title according to the chosen bin
 614                if self.analysis_tuple[1] == 'all':
 615                    plt.title('Frequency bin plot for ' + self.sounds.name)
 616                else:
 617                    plt.title(self.analysis_tuple[1] + ' bin plot for ' + self.sounds.name)
 618
 619                    plt.show()
 620
 621            # Case for the Sound.peak_damping method (print only)
 622            elif self.analysis_tuple[0] in [Sound.peak_damping, Sound.listen_freq_bins]:
 623                with output:
 624                    self.analysis_tuple[0](self.sounds)  # add print to output
 625
 626            # Case for the Signal.plot method
 627            elif self.analysis_tuple[0] in self.plot_methods:
 628                # change plot interface
 629                get_ipython().run_line_magic('matplotlib', 'notebook')
 630                # create a figure
 631                plt.figure(figsize=(8, 6))
 632                # Add the fill argument if there is just one plot
 633                kwargs = {}
 634                # Call the method according to normalization
 635                if not self.normalize:
 636                    self.analysis_tuple[0](self.sounds.signal.plot, **kwargs)
 637                elif self.normalize:
 638                    self.analysis_tuple[0](self.sounds.signal.normalize().plot, **kwargs)
 639
 640                if self.analysis_tuple[0] == Plot.time_damping:
 641                    zeta = np.around(self.sounds.signal.time_damping(), 5)
 642                    plt.title(self.current_drop_down.label + ' for ' + self.sounds.name + ' Zeta = ' + str(zeta))
 643                # Define a title from the signal.plot(kind)
 644                else:
 645                    plt.title(self.current_drop_down.label + ' for ' + self.sounds.name)
 646
 647                # make the x-axis ticks the frequency bins if the axe is frequency
 648                if self.analysis_tuple[0] in self.bin_ticks_methods:
 649                    Plot.set_bin_ticks(self.sounds.signal.plot)
 650                # add to output
 651                with output:
 652                    plt.show()
 653
 654            # Case for the Sound.bin_hist method
 655            elif self.analysis_tuple[0] == Sound.bin_hist:
 656                # change plot interface
 657                get_ipython().run_line_magic('matplotlib', 'notebook')
 658                # call the method
 659                self.analysis_tuple[0](self.sounds)
 660                # set a title
 661                plt.title(self.current_drop_down.label + ' for ' + self.sounds.name)
 662                # add to output
 663                with output:
 664                    plt.show()
 665
 666            # Case for the Signal.listen method
 667            elif self.analysis_tuple[0] == Signal.listen:
 668                # add to output
 669                with output:
 670                    # Call the method according to normalization
 671                    if not self.normalize:
 672                        self.analysis_tuple[0](self.sounds.signal)
 673                    elif self.normalize:
 674                        self.analysis_tuple[0](self.sounds.signal.normalize())
 675
 676        # Case for Dual and Multiple analyses
 677        elif self.analysis in ['Dual', 'Multiple']:
 678
 679            # normalize the sound_pack if self.normalize is True
 680            if self.normalize:
 681                sound_pack = self.Pack.normalize()
 682            else:
 683                sound_pack = self.Pack
 684
 685            # if the analysis method is a unique plot, make matplotlib interactive
 686            get_ipython().run_line_magic('matplotlib', 'inline')
 687            if self.analysis_tuple[0] in self.unique_plot_methods:
 688                get_ipython().run_line_magic('matplotlib', 'notebook')
 689
 690            # Call with no arguments
 691            if len(self.analysis_tuple) == 1:
 692                # Case for a print output
 693                if self.display == 'print':
 694                    with output:
 695                        self.analysis_tuple[0](sound_pack)  # add print to output
 696
 697                # special case to have bins ticks for the fft_diff method
 698                elif self.analysis_tuple[0] == SoundPack.fft_diff:
 699                    self.analysis_tuple[0](sound_pack, ticks='bins')
 700                    with output:
 701                        plt.show()  # display plot in output
 702
 703                # Case for a plot output
 704                elif self.display == 'plot':
 705                    self.analysis_tuple[0](sound_pack)
 706                    with output:
 707                        plt.show()  # display plot in output
 708
 709            # Call with arguments
 710            elif len(self.analysis_tuple) == 2:
 711                self.analysis_tuple[0](sound_pack, self.analysis_tuple[1])
 712                with output:
 713                    plt.show()
 714
 715        # Set up the dropdown to go back to method choice
 716        self.current_drop_down.value = 1
 717        self.current_drop_down = self.first_level_drop_down[self.analysis]
 718        self.current_drop_down.value = 1
 719
 720        # Set the Go and Ok buttons to default value
 721        self.go_button.disabled = True
 722        self.ok_button.disabled = False
 723
 724        # Set the normalization button to not normalized
 725        self.toggle_normalize_button.button_style = ''
 726        self.toggle_normalize_button.icon = ''
 727        self.normalize = False
 728
 729        # display
 730        display(self.button_box, output)
 731        # Make the window larger
 732        display(HTML("<style>div.output_scroll { height: 44em; }</style>"))
 733
 734    """
 735    Observe methods
 736    """
 737
 738    def on_loaded_bar(self, change):
 739        """
 740        This method monitors the value of the load bar used
 741        when loading files.
 742
 743        When the load bar is complete (value = 10), the
 744        button box is displayed with the "Ok" and "Go" buttons
 745        The "Go" button is disabled
 746        The dropdown with the methods according to the
 747        current analysis is displayed.
 748        :param change: the object containing the current value of the load bar
 749        """
 750        # When the bar reaches the end
 751        if change["new"] >= 10:
 752            clear_output(wait=True)
 753
 754            # disable the go_button
 755            self.state = 'method choice'
 756
 757            # Actualize the button box and display
 758            children = [self.ok_button, self.go_button, self.toggle_normalize_button, self.info_button]
 759            self.button_box = widgets.Box(children=children, layout=self.box_layout)
 760            self.ok_button.on_click(self.on_ok_button_clicked_2)
 761            self.go_button.on_click(self.on_go_button_clicked)
 762            self.toggle_normalize_button.on_click(self.on_normalize_button_clicked)
 763            self.info_button.on_click(self.on_info_button_clicked)
 764            self.go_button.disabled = True
 765            display(self.button_box)
 766
 767            # create the output
 768            output = widgets.Output(layout=self.out_layout)
 769
 770            # display the dropdown associated with the current analysis
 771            self.current_drop_down = self.first_level_drop_down[self.analysis]
 772            with output:
 773                display(self.current_drop_down)
 774
 775            display(output)
 776
 777    """
 778    Back end functions
 779    """
 780
 781    def define_sound_names(self):
 782        """
 783        A method to define sound names and fundamentals
 784        """
 785
 786        # Clear the output and define the new one
 787        clear_output(wait=True)
 788        output = widgets.Output(layout=self.out_layout)
 789
 790        # Style for the text inputs
 791        style = {'description_width': 'initial'}
 792
 793        # Small string 'Hz' to indicate units
 794        HZ_string = widgets.HTML('<p>' + 'Hz' + '</p>')
 795
 796        # Define the button box
 797        self.button_box = widgets.Box(children=[self.done_button], layout=self.box_layout)
 798
 799        # Define the output with the text inputs
 800        with output:
 801
 802            # Case for a single sound analysis
 803            if self.analysis == 'Single':
 804
 805                # get the filenames
 806                self.file_names = [ky for ky in self.single_file_selector.value.keys()]
 807
 808                # make a sound name input widget
 809                sound_name_input = widgets.Text(value='',
 810                                                placeholder='sound name',
 811                                                description=self.file_names[0],
 812                                                layout=widgets.Layout(width='40%'),
 813                                                style=style,
 814                                                )
 815
 816                # make a fundamental input widget
 817                fundamental_input = widgets.FloatText(value=0,
 818                                                      description='Fundamental :',
 819                                                      style=style,
 820                                                      layout=widgets.Layout(width='20%')
 821                                                      )
 822
 823                # children that go in the name box
 824                children = [sound_name_input, fundamental_input, HZ_string]
 825
 826                # define a name box widget
 827                name_box_layout = widgets.Layout(align_items='stretch', flex_flow='line', width='75%')
 828                name_box = widgets.Box(children=children, layout=name_box_layout)
 829
 830                # display the box
 831                display(name_box)
 832
 833                # store the input to refer them later
 834                self.sound_name_inputs = [sound_name_input]
 835                self.sound_fundamental_inputs = [fundamental_input]
 836
 837            # Case for dual sound analysis
 838            elif self.analysis in ['Dual', 'Multiple']:
 839
 840                if self.analysis == 'Dual':
 841                    # get the file names
 842                    name1 = [ky for ky in self.dual_file_selector_1.value.keys()][0]
 843                    name2 = [ky for ky in self.dual_file_selector_2.value.keys()][0]
 844                    self.file_names = [name1, name2]
 845
 846                elif self.analysis == 'Multiple':
 847                    self.file_names = [ky for ky in self.mult_file_selector.value.keys()]
 848
 849                # create empty lists for the inputs
 850                self.sound_name_inputs = []
 851                self.sound_fundamental_inputs = []
 852
 853                for file in self.file_names:
 854                    # make a text input widget
 855                    sound_name_input = widgets.Text(value='',
 856                                                    placeholder='sound name',
 857                                                    description=file,
 858                                                    layout=widgets.Layout(width='40%'),
 859                                                    style=style,
 860                                                    )
 861
 862                    # make a fundamental input widget
 863                    fundamental_input = widgets.FloatText(value=0,
 864                                                          description='Fundamental :',
 865                                                          layout=widgets.Layout(width='20%'),
 866                                                          style=style
 867                                                          )
 868
 869                    # children that go in the name box
 870                    children = [sound_name_input, fundamental_input, HZ_string]
 871
 872                    # define a name box widget
 873                    name_box_layout = widgets.Layout(align_items='stretch', flex_flow='line', width='75%')
 874                    name_box = widgets.Box(children=children, layout=name_box_layout)
 875
 876                    # display the box
 877                    display(name_box)
 878
 879                    # append the inputs
 880                    self.sound_name_inputs.append(sound_name_input)
 881                    self.sound_fundamental_inputs.append(fundamental_input)
 882
 883        self.done_button.on_click(self.on_done_button_clicked)
 884
 885        # display everything
 886        display(self.button_box, output)
 887
 888    def import_sound_files(self):
 889        """
 890        Method to import the soundfile vectors into the program
 891        after the files and names where defined.
 892        *Only works with .wav files*
 893        """
 894
 895        # Case for when only a single file is imported
 896        if self.analysis == 'Single':
 897            # Loading Bar Value = 0
 898            # Get the filename values from the file selector
 899            file_values = self.single_file_selector.value[self.file_names[0]]
 900
 901            # Get the signal audio bytes
 902            bites = file_values['content']
 903
 904            # Convert to wav audio object
 905            audio = wave.open(io.BytesIO(bites))
 906
 907            sr = audio.getframerate()  # save the frame rate
 908
 909            samples = []
 910            self.load_bar.value += 1  # LoadBar value = 1
 911            n = audio.getnframes()
 912            milestones = [int(i) for i in np.linspace(0, n, 5)][1:]
 913            for _ in range(audio.getnframes()):
 914                frame = audio.readframes(1)
 915                samples.append(struct.unpack("h", frame)[0])
 916                if _ in milestones:
 917                    self.load_bar.value += 1  # LoadBar value increases to 5 in loop
 918
 919            self.load_bar.value += 1  # LoadBar value = 7
 920            signal = np.array(samples) / 32768
 921            Sound_Input = (signal, sr)
 922            self.load_bar.value += 1  # LoadBar value = 8
 923
 924            # Get the sound name
 925            if self.sound_name_inputs[0].value == '':
 926                name = self.file_names[0].replace('.wav', '')
 927            else:
 928                name = self.sound_name_inputs[0].value
 929
 930            # Get the sound fundamental
 931            if self.sound_fundamental_inputs[0].value == 0:
 932                fundamental = None
 933            else:
 934                fundamental = self.sound_fundamental_inputs[0].value
 935
 936            self.load_bar.value += 1  # LoadBar value = 9
 937            # This takes a long time
 938            sound = Sound(Sound_Input, name=name, fundamental=fundamental)
 939            self.sounds = sound.condition(return_self=True, verbose=False)
 940            self.load_bar.value += 2  # Load-bar = 10
 941
 942        # Case for two files from two file selectors
 943        elif self.analysis == 'Dual':
 944            # LoadBar = 0
 945            self.sounds = []
 946            file_dicts = [self.dual_file_selector_1.value, self.dual_file_selector_2.value]
 947            self.load_bar.value += 2  # LoadBar = 1
 948
 949            # zipped iterator
 950            iterator = zip(self.file_names, file_dicts, self.sound_name_inputs, self.sound_fundamental_inputs)
 951
 952            #  Create a sound for every file
 953            for file, dic, name_input, fundamental_input in iterator:
 954
 955                file_values = dic[file]
 956                bites = file_values['content']
 957                audio = wave.open(io.BytesIO(bites))
 958                sr = audio.getframerate()
 959                samples = []
 960                self.load_bar.value += 1  # LoadBar +=2
 961                for _ in range(audio.getnframes()):
 962                    frame = audio.readframes(1)
 963                    samples.append(struct.unpack("h", frame)[0])
 964                self.load_bar.value += 1  # LoadBar +=2
 965                signal = np.array(samples) / 32768
 966                Sound_Input = (signal, sr)
 967
 968                # get the name value
 969                if name_input.value == '':
 970                    name = file.replace('.wav', '')
 971                else:
 972                    name = name_input.value
 973
 974                # get the fundamental value
 975                if fundamental_input.value == 0:
 976                    fundamental = None
 977                else:
 978                    fundamental = fundamental_input.value
 979
 980                sound = Sound(Sound_Input, name=name, fundamental=fundamental)
 981                sound.condition(verbose=False)
 982                self.sounds.append(sound)
 983                self.load_bar.value += 1  # LoadBar +=2
 984            # Load Bar = 8
 985            self.Pack = SoundPack(self.sounds, names=[sound.name for sound in self.sounds])
 986            self.load_bar.value += 2  # Load Bar = 10
 987
 988        # Case for multiple files
 989        elif self.analysis == 'Multiple':
 990            # LoadBar = 0
 991            self.sounds = []
 992            self.load_bar.value += 1  # LoadBar = 1
 993
 994            # zipped iterator
 995            iterator = zip(self.file_names, self.sound_name_inputs, self.sound_fundamental_inputs)
 996
 997            for file, name_input, fundamental_input in iterator:
 998                file_values = self.mult_file_selector.value[file]
 999                bites = file_values['content']
1000                audio = wave.open(io.BytesIO(bites))
1001                sr = audio.getframerate()
1002                samples = []
1003                for _ in range(audio.getnframes()):
1004                    frame = audio.readframes(1)
1005                    samples.append(struct.unpack("h", frame)[0])
1006                signal = np.array(samples) / 32768
1007                Sound_Input = (signal, sr)
1008
1009                # get the sound names
1010                if name_input.value == '':
1011                    name = file.replace('.wav', '')
1012                else:
1013                    name = name_input.value
1014
1015                # get the fundamental values
1016                if fundamental_input.value == 0:
1017                    fundamental = None
1018                else:
1019                    fundamental = fundamental_input.value
1020
1021                sound = Sound(Sound_Input, name=name, fundamental=fundamental)
1022                sound.condition(verbose=False)
1023                self.sounds.append(sound)
1024                if self.load_bar.value < 9:
1025                    self.load_bar.value += 1
1026
1027            self.Pack = SoundPack(self.sounds, names=[sound.name for sound in self.sounds])
1028            while self.load_bar.value < 10:
1029                self.load_bar.value += 1  # LoadBar = 10

Main Graphical user interface class

guitarGUI()
145    def __init__(self):
146        """
147        Constructor for the Graphical User Interface class
148
149        Upon instanciation of the class, we display the three file choosing buttons matched with the
150        three types of analyses when one is clicked the user is prompted
151        to choose files.
152
153        When files are chosen  the user press the 'Ok' Button and the
154        Interface  advances to defining names see `.on_ok_button_clicked_1`.
155        """
156
157        # __ Buttons __
158        # Number of sound choice buttons
159        self.button1 = widgets.Button(description="Single Sound")
160        self.button2 = widgets.Button(description="Dual Sounds")
161        self.button3 = widgets.Button(description="Multiple Sounds")
162
163        # Ok, Done and Go Buttons
164        self.ok_button = widgets.Button(description="Ok")
165        self.done_button = widgets.Button(description="Done")
166        self.go_button = widgets.Button(description='Go')
167
168        # Normalize toggle button
169        self.toggle_normalize_button = widgets.Button(description='Normalize')
170        # Associated attribute to normalize the Sounds for the method called
171        self.normalize = False
172
173        # Info button
174        self.info_button = widgets.Button(description='Info')
175
176        # Button box when the GUI starts
177        self.button_box = widgets.Box(children=[self.button1,
178                                                self.button2,
179                                                self.button3,
180                                                self.ok_button], layout=self.box_layout)
181
182        # Load bar when importing sounds
183        self.load_bar = widgets.IntProgress(value=5, min=0, max=10,
184                                            description='Importing sound files :',
185                                            style={'bar_color': '#6495ED',
186                                                   'description_width': '140px'}, )
187
188        # File selectors for uploading files into the program
189        self.single_file_selector = widgets.FileUpload(accept='.wav', multiple=False)
190        self.dual_file_selector_1 = widgets.FileUpload(accept='.wav', multiple=False)
191        self.dual_file_selector_2 = widgets.FileUpload(accept='.wav', multiple=False)
192        self.mult_file_selector = widgets.FileUpload(accept='.wav', multiple=True)
193
194        # Dict with dropdown methods to display the menu associated with
195        # the analysis
196        self.first_level_drop_down = {'Single': self.single_drop_down,
197                                      'Dual': self.dual_drop_down,
198                                      'Multiple': self.mult_drop_down}
199
200        # Initiate name spaces
201        self.analysis = None
202        self.display = None
203        self.current_drop_down = None
204        self.Pack = None
205        self.analysis_tuple = None
206        self.file_names = None
207        self.sound_name_inputs = None
208        self.sound_fundamental_inputs = None
209        self.sounds = None
210
211        # Define the current state of the program
212        self.state = 'start'
213
214        # Listen for clicks on the first button panel
215        self.button1.on_click(self.on_single_button_clicked)
216        self.button2.on_click(self.on_dual_button_clicked)
217        self.button3.on_click(self.on_multiple_button_clicked)
218        self.ok_button.on_click(self.on_ok_button_clicked_1)
219        self.change_file_selection_state(False)
220
221        # display the buttons
222        display(self.button_box)

Constructor for the Graphical User Interface class

Upon instanciation of the class, we display the three file choosing buttons matched with the three types of analyses when one is clicked the user is prompted to choose files.

When files are chosen the user press the 'Ok' Button and the Interface advances to defining names see .on_ok_button_clicked_1.

def on_single_button_clicked(self, b):
228    def on_single_button_clicked(self, b):
229        """
230        Displays the single file selector, allowing the user to choose
231        one file.
232        :param: b the ipywidget button object for which this method is executed when it is clicked
233        """
234        if b is not None:
235            pass
236        clear_output(wait=True)
237
238        output = widgets.Output(layout={'border': '1px solid black'})
239        self.change_file_selection_state(True)
240        with output:
241            display(self.single_file_selector)
242
243        self.analysis = 'Single'
244        self.state = 'file entry'
245
246        display(self.button_box)
247        display(output)

Displays the single file selector, allowing the user to choose one file.

Parameters
  • b the ipywidget button object for which this method is executed when it is clicked
def on_dual_button_clicked(self, b):
249    def on_dual_button_clicked(self, b):
250        """
251        Displays two single file selectors, allowing the user
252        to choose two files.
253        :param: b the ipywidget button object for which this method is executed when it is clicked
254        """
255        if b is not None:
256            pass
257        clear_output(wait=True)
258
259        output = widgets.Output(layout={'border': '1px solid black'})
260        self.change_file_selection_state(True)
261        with output:
262            display(self.dual_file_selector_1)
263            display(self.dual_file_selector_2)
264
265        self.analysis = 'Dual'
266        self.state = 'file entry'
267
268        display(self.button_box)
269        display(output)

Displays two single file selectors, allowing the user to choose two files.

Parameters
  • b the ipywidget button object for which this method is executed when it is clicked
def on_multiple_button_clicked(self, b):
271    def on_multiple_button_clicked(self, b):
272        """
273        Displays a multiple file selector allowing the user
274        to select multiple files
275        :param: b the ipywidget button object for which this method is executed when it is clicked
276        """
277        if b is not None:
278            pass
279        clear_output(wait=True)
280
281        output = widgets.Output(layout={'border': '1px solid black'})
282        self.change_file_selection_state(True)
283        with output:
284            display(self.mult_file_selector)
285
286        self.analysis = 'Multiple'
287        self.state = 'file entry'
288
289        display(self.button_box)
290        display(output)

Displays a multiple file selector allowing the user to select multiple files

Parameters
  • b the ipywidget button object for which this method is executed when it is clicked
def change_file_selection_state(self, state):
292    def change_file_selection_state(self, state):
293        """
294        Change the state of the file selection buttons
295        :param state: bool state to which the selection should be changed
296        """
297        self.button1.disabled = state
298        self.button2.disabled = state
299        self.button3.disabled = state

Change the state of the file selection buttons

Parameters
  • state: bool state to which the selection should be changed
def on_ok_button_clicked_1(self, b):
301    def on_ok_button_clicked_1(self, b):
302        """
303        The user clicks this button when he is done choosing files and when
304        he is done defining names
305        :param: b the ipywidget button object for which this method is executed when it is clicked
306        """
307        if b is not None:
308            pass
309        # Clear the output
310        clear_output(wait=True)
311
312        # Check if the user did good when choosing files
313        file_selectors = [self.single_file_selector,
314                          self.dual_file_selector_1,
315                          self.dual_file_selector_2,
316                          self.mult_file_selector]
317        files_where_chosen = False
318        for file_selector in file_selectors:
319            if file_selector.value != {}:
320                files_where_chosen = True
321
322        # If the file were chosen the user is taken to the "define name" interface
323        if files_where_chosen:
324            self.define_sound_names()
325
326        # if not we go back to file selection
327        else:
328            output = widgets.Output(layout={'border': '1px solid black'})
329            with output:
330                if self.analysis == 'Single':
331                    display(self.single_file_selector)
332                elif self.analysis == 'Dual':
333                    display(self.dual_file_selector_1)
334                    display(self.dual_file_selector_2)
335                elif self.analysis == 'Multiple':
336                    display(self.mult_file_selector)
337                else:
338                    error = generate_error_widget('Chose an analysis type')
339                    display(error)
340
341                # Display an error if a file selector was clicked but no file was chosen
342                if self.analysis in ['Single', 'Dual', 'Multiple']:
343                    error = generate_error_widget('No sound was chosen')
344                    display(error)
345
346            display(self.button_box)
347            display(output)

The user clicks this button when he is done choosing files and when he is done defining names

Parameters
  • b the ipywidget button object for which this method is executed when it is clicked
def on_ok_button_clicked_2(self, b):
353    def on_ok_button_clicked_2(self, b):
354        """
355        Method to make the "Ok" button interact with the
356        analysis method choice.
357
358        __ when interface.state = 'method choice' __
359        - The "Ok" and "Go" buttons appears after the loading bar is done
360        - The dropdown corresponds to the methods associated with
361        the analysis
362        :param: b the ipywidget button object for which this method is executed when it is clicked
363        """
364        if b is not None:
365            pass
366        # Clear the Output
367        clear_output(wait=True)
368        output = widgets.Output(layout=self.out_layout)
369
370        # Save the dropdown value
371        drop_down_value = self.current_drop_down.value
372        
373        # enable the info button when coming back from display
374        if self.state != 'display':
375            self.info_button.disabled = False
376            self.toggle_normalize_button.disabled = False
377
378        # Deactivate the info button if it was activated
379        if self.info_button.button_style == 'info':
380            self.info_button.button_style = ''
381
382        if self.state == 'method choice':  # State when the user is choosing the analysis method
383
384            # If we only analyse a single sound
385            if self.analysis == 'Single':
386
387                # Special case when the method is the frequency bin plot
388                if drop_down_value == Sound.plot_freq_bins:
389                    self.analysis_tuple = [drop_down_value]  # Store the method
390                    # Change the dropdown to frequency bin choice
391                    self.current_drop_down = self.bin_drop_down
392                    self.state = 'method choice 2'  # a second choice is needed
393                    self.display = 'plot'
394
395                # Case for the methods without plotting
396                elif drop_down_value in [Sound.peak_damping, Sound.listen_freq_bins, Signal.listen]:
397                    self.analysis_tuple = [drop_down_value]  # store the method
398                    self.state = 'display'  # ready to display
399                    self.display = 'print'
400
401                # Signal.plot.method() methods
402                elif drop_down_value in [*self.plot_methods, Sound.bin_hist]:
403                    # store method and arg in a list
404                    self.analysis_tuple = [drop_down_value]
405                    self.state = 'display'  # ready to display
406                    self.display = 'plot'
407
408                # Error when no method is chosen
409                elif drop_down_value == 1:
410                    error = generate_error_widget('No analysis method was chosen')
411                    with output:
412                        display(error)
413
414            # Case when two sounds or multiple sounds are being analysed
415            elif self.analysis in ['Dual', 'Multiple']:
416
417                # Special case for the frequency bin plot
418                if drop_down_value in self.DM_bin_choice_methods:
419                    self.analysis_tuple = [drop_down_value]  # Store the method
420                    # Update the dropdown to frequency bin choice
421                    self.current_drop_down = self.bin_drop_down
422                    self.state = 'method choice 2'  # a second choice is needed
423                    self.display = 'plot'
424
425                # Case for plot methods
426                elif (drop_down_value == SoundPack.plot) or (drop_down_value == SoundPack.compare_plot):
427                    self.analysis_tuple = [drop_down_value]  # Store the method
428                    # Update the dropdown to the plot dropdown
429                    self.current_drop_down = self.plot_drop_down
430                    self.state = 'method choice 2'  # a second choice is needed
431                    self.display = 'plot'
432
433                # Error when no method is chosen
434                elif drop_down_value == 1:
435                    error = generate_error_widget('No analysis method was chosen')
436                    with output:
437                        display(error)
438
439                # Case for methods with no arguments
440                else:
441                    if drop_down_value == SoundPack.fundamentals:
442                        self.display = 'print'
443                    else:
444                        self.display = 'plot'
445                    self.analysis_tuple = [drop_down_value]  # store the method
446                    self.state = 'display'
447
448        # Case when the method is chosen and an argument needs to be added 'method choice 2'
449        elif self.state == 'method choice 2':
450
451            # add the arg part to the analysis tuple
452            self.analysis_tuple.append(self.current_drop_down.value)
453            self.state = 'display'
454
455        # if we are coming back from the display the state is redefined, and we restart
456        elif self.state == 'analysis displayed':
457            self.state = 'method choice'
458
459        # If the button is pressed and the method is defined, the go button is enabled
460        if self.state == 'display':
461            self.go_button.disabled = False
462            self.ok_button.disabled = True
463            self.info_button.disabled = True
464            self.toggle_normalize_button.disabled = True
465
466        # Actualize the button box and display
467        children = [self.ok_button, self.go_button, self.toggle_normalize_button, self.info_button]
468        self.button_box = widgets.Box(children=children, layout=self.box_layout)
469
470        # Put the updated dropdown in the output
471        with output:
472            display(self.current_drop_down)
473
474        display(self.button_box, output)

Method to make the "Ok" button interact with the analysis method choice.

__ when interface.state = 'method choice' __

  • The "Ok" and "Go" buttons appears after the loading bar is done
  • The dropdown corresponds to the methods associated with the analysis
Parameters
  • b the ipywidget button object for which this method is executed when it is clicked
def on_info_button_clicked(self, info):
476    def on_info_button_clicked(self, info):
477        """
478        Method called when the info button is clicked
479        Displays the help string associated with the current dropdown method
480        :param info:  the ipywidget button object for which this method is executed when it is clicked
481        """
482        if info.button_style == '':
483            # change the style to make the button blue
484            info.button_style = 'info'
485
486            # Clear the Output
487            clear_output(wait=True)
488            output = widgets.Output(layout=self.out_layout)
489
490            # Case when the user is selecting the first method
491            if self.state == 'method choice':
492
493                # if the method is a tuple with an argument
494                if type(self.current_drop_down.value) == tuple:
495                    with output:
496                        display(help(self.current_drop_down.value[0]))
497
498                # if no method was selected
499                elif type(self.current_drop_down.value) == int:
500                    error = generate_error_widget('No analysis was selected')
501                    with output:
502                        display(error)
503
504                # a method not in a tuple was selected
505                else:
506                    with output:
507                        display(help(self.current_drop_down.value))
508
509                # display every thing
510                display(self.button_box, output)
511
512            # case when the user is doing a secondary selection
513            elif self.state == 'method choice 2':
514
515                # case for the plot type dropdown
516                if self.current_drop_down.name == 'plot':
517                    with output:
518                        display(help(self.plot_info_dict[self.current_drop_down.value]))
519
520                # case for bin type dropdown (display the previous method)
521                elif self.current_drop_down.name == 'bin':
522                    with output:
523                        display(help(self.analysis_tuple[0]))
524
525                display(self.button_box, output)
526
527        elif info.button_style == 'info':
528            info.button_style = ''
529
530            # Clear the Output
531            clear_output(wait=True)
532            output = widgets.Output(layout=self.out_layout)
533            with output:
534                display(self.current_drop_down)
535
536            # display every thing
537            display(self.button_box, output)

Method called when the info button is clicked Displays the help string associated with the current dropdown method

Parameters
  • info: the ipywidget button object for which this method is executed when it is clicked
def on_normalize_button_clicked(self, toggle):
539    def on_normalize_button_clicked(self, toggle):
540        """
541        Method called when the normalize button is clicked
542        The normalized attribute is inverted according to the current value
543        :param toggle: the ipywidget button object for which this method is executed when pressed
544        """
545        if toggle.button_style == '':
546            toggle.button_style = 'success'
547            toggle.icon = 'check'
548            self.normalize = True
549
550        elif toggle.button_style == 'success':
551            toggle.button_style = ''
552            toggle.icon = ''
553            self.normalize = False

Method called when the normalize button is clicked The normalized attribute is inverted according to the current value

Parameters
  • toggle: the ipywidget button object for which this method is executed when pressed
def on_done_button_clicked(self, b):
555    def on_done_button_clicked(self, b):
556        """
557        When the done button is clicked after the user had
558        the option to define custom names this function is executed
559
560        A load bar is displayed while te files are loaded, when the
561        load bar is done the `.on_loaded_bar()` method is called.
562        :param b: the ipywidget button object for which this method is executed when pressed
563        """
564        if b is not None:
565            pass
566        clear_output(wait=True)
567
568        display(self.load_bar)
569        self.load_bar.observe(self.on_loaded_bar, names="value")
570
571        self.load_bar.value += 1
572        self.import_sound_files()

When the done button is clicked after the user had the option to define custom names this function is executed

A load bar is displayed while te files are loaded, when the load bar is done the .on_loaded_bar() method is called.

Parameters
  • b: the ipywidget button object for which this method is executed when pressed
def on_go_button_clicked(self, b):
574    def on_go_button_clicked(self, b):
575        """
576        Go button to display the analysis when all choices are made
577
578        What happens :
579        ___________________________________
580        1. The output is cleared
581        2. An output widget to store the output is instanced
582        3. The method in `self.analysis_tuple` is called
583        4. The display is added to the output
584        5. The 'Ok' button is enabled and the 'Go' button is disabled
585        6. The dropdown is set back to its default value
586        7. The buttons and output are displayed
587        :param b: the ipywidget button object corresponding to the go button
588        """
589        if b is not None:
590            pass
591        # Always clear the output
592        clear_output(wait=True)
593        output = widgets.Output(layout=self.out_layout)  # Create a output
594
595        # Change the GUI state
596        self.state = 'analysis displayed'
597
598        # Set the matplotlib display method
599        get_ipython().run_line_magic('matplotlib', 'inline')
600
601        # Case for a single sound
602        if self.analysis == 'Single':
603
604            # Case for Sound.plot_freq_bins method
605            if self.analysis_tuple[0] == Sound.plot_freq_bins:
606                # change interface
607                get_ipython().run_line_magic('matplotlib', 'notebook')
608                # create a figure
609                plt.figure(figsize=(8, 6))
610                # Call the method
611                self.analysis_tuple[0](self.sounds, bins=[self.analysis_tuple[1]])
612
613                # Define the title according to the chosen bin
614                if self.analysis_tuple[1] == 'all':
615                    plt.title('Frequency bin plot for ' + self.sounds.name)
616                else:
617                    plt.title(self.analysis_tuple[1] + ' bin plot for ' + self.sounds.name)
618
619                    plt.show()
620
621            # Case for the Sound.peak_damping method (print only)
622            elif self.analysis_tuple[0] in [Sound.peak_damping, Sound.listen_freq_bins]:
623                with output:
624                    self.analysis_tuple[0](self.sounds)  # add print to output
625
626            # Case for the Signal.plot method
627            elif self.analysis_tuple[0] in self.plot_methods:
628                # change plot interface
629                get_ipython().run_line_magic('matplotlib', 'notebook')
630                # create a figure
631                plt.figure(figsize=(8, 6))
632                # Add the fill argument if there is just one plot
633                kwargs = {}
634                # Call the method according to normalization
635                if not self.normalize:
636                    self.analysis_tuple[0](self.sounds.signal.plot, **kwargs)
637                elif self.normalize:
638                    self.analysis_tuple[0](self.sounds.signal.normalize().plot, **kwargs)
639
640                if self.analysis_tuple[0] == Plot.time_damping:
641                    zeta = np.around(self.sounds.signal.time_damping(), 5)
642                    plt.title(self.current_drop_down.label + ' for ' + self.sounds.name + ' Zeta = ' + str(zeta))
643                # Define a title from the signal.plot(kind)
644                else:
645                    plt.title(self.current_drop_down.label + ' for ' + self.sounds.name)
646
647                # make the x-axis ticks the frequency bins if the axe is frequency
648                if self.analysis_tuple[0] in self.bin_ticks_methods:
649                    Plot.set_bin_ticks(self.sounds.signal.plot)
650                # add to output
651                with output:
652                    plt.show()
653
654            # Case for the Sound.bin_hist method
655            elif self.analysis_tuple[0] == Sound.bin_hist:
656                # change plot interface
657                get_ipython().run_line_magic('matplotlib', 'notebook')
658                # call the method
659                self.analysis_tuple[0](self.sounds)
660                # set a title
661                plt.title(self.current_drop_down.label + ' for ' + self.sounds.name)
662                # add to output
663                with output:
664                    plt.show()
665
666            # Case for the Signal.listen method
667            elif self.analysis_tuple[0] == Signal.listen:
668                # add to output
669                with output:
670                    # Call the method according to normalization
671                    if not self.normalize:
672                        self.analysis_tuple[0](self.sounds.signal)
673                    elif self.normalize:
674                        self.analysis_tuple[0](self.sounds.signal.normalize())
675
676        # Case for Dual and Multiple analyses
677        elif self.analysis in ['Dual', 'Multiple']:
678
679            # normalize the sound_pack if self.normalize is True
680            if self.normalize:
681                sound_pack = self.Pack.normalize()
682            else:
683                sound_pack = self.Pack
684
685            # if the analysis method is a unique plot, make matplotlib interactive
686            get_ipython().run_line_magic('matplotlib', 'inline')
687            if self.analysis_tuple[0] in self.unique_plot_methods:
688                get_ipython().run_line_magic('matplotlib', 'notebook')
689
690            # Call with no arguments
691            if len(self.analysis_tuple) == 1:
692                # Case for a print output
693                if self.display == 'print':
694                    with output:
695                        self.analysis_tuple[0](sound_pack)  # add print to output
696
697                # special case to have bins ticks for the fft_diff method
698                elif self.analysis_tuple[0] == SoundPack.fft_diff:
699                    self.analysis_tuple[0](sound_pack, ticks='bins')
700                    with output:
701                        plt.show()  # display plot in output
702
703                # Case for a plot output
704                elif self.display == 'plot':
705                    self.analysis_tuple[0](sound_pack)
706                    with output:
707                        plt.show()  # display plot in output
708
709            # Call with arguments
710            elif len(self.analysis_tuple) == 2:
711                self.analysis_tuple[0](sound_pack, self.analysis_tuple[1])
712                with output:
713                    plt.show()
714
715        # Set up the dropdown to go back to method choice
716        self.current_drop_down.value = 1
717        self.current_drop_down = self.first_level_drop_down[self.analysis]
718        self.current_drop_down.value = 1
719
720        # Set the Go and Ok buttons to default value
721        self.go_button.disabled = True
722        self.ok_button.disabled = False
723
724        # Set the normalization button to not normalized
725        self.toggle_normalize_button.button_style = ''
726        self.toggle_normalize_button.icon = ''
727        self.normalize = False
728
729        # display
730        display(self.button_box, output)
731        # Make the window larger
732        display(HTML("<style>div.output_scroll { height: 44em; }</style>"))

Go button to display the analysis when all choices are made

What happens :


  1. The output is cleared
  2. An output widget to store the output is instanced
  3. The method in self.analysis_tuple is called
  4. The display is added to the output
  5. The 'Ok' button is enabled and the 'Go' button is disabled
  6. The dropdown is set back to its default value
  7. The buttons and output are displayed
Parameters
  • b: the ipywidget button object corresponding to the go button
def on_loaded_bar(self, change):
738    def on_loaded_bar(self, change):
739        """
740        This method monitors the value of the load bar used
741        when loading files.
742
743        When the load bar is complete (value = 10), the
744        button box is displayed with the "Ok" and "Go" buttons
745        The "Go" button is disabled
746        The dropdown with the methods according to the
747        current analysis is displayed.
748        :param change: the object containing the current value of the load bar
749        """
750        # When the bar reaches the end
751        if change["new"] >= 10:
752            clear_output(wait=True)
753
754            # disable the go_button
755            self.state = 'method choice'
756
757            # Actualize the button box and display
758            children = [self.ok_button, self.go_button, self.toggle_normalize_button, self.info_button]
759            self.button_box = widgets.Box(children=children, layout=self.box_layout)
760            self.ok_button.on_click(self.on_ok_button_clicked_2)
761            self.go_button.on_click(self.on_go_button_clicked)
762            self.toggle_normalize_button.on_click(self.on_normalize_button_clicked)
763            self.info_button.on_click(self.on_info_button_clicked)
764            self.go_button.disabled = True
765            display(self.button_box)
766
767            # create the output
768            output = widgets.Output(layout=self.out_layout)
769
770            # display the dropdown associated with the current analysis
771            self.current_drop_down = self.first_level_drop_down[self.analysis]
772            with output:
773                display(self.current_drop_down)
774
775            display(output)

This method monitors the value of the load bar used when loading files.

When the load bar is complete (value = 10), the button box is displayed with the "Ok" and "Go" buttons The "Go" button is disabled The dropdown with the methods according to the current analysis is displayed.

Parameters
  • change: the object containing the current value of the load bar
def define_sound_names(self):
781    def define_sound_names(self):
782        """
783        A method to define sound names and fundamentals
784        """
785
786        # Clear the output and define the new one
787        clear_output(wait=True)
788        output = widgets.Output(layout=self.out_layout)
789
790        # Style for the text inputs
791        style = {'description_width': 'initial'}
792
793        # Small string 'Hz' to indicate units
794        HZ_string = widgets.HTML('<p>' + 'Hz' + '</p>')
795
796        # Define the button box
797        self.button_box = widgets.Box(children=[self.done_button], layout=self.box_layout)
798
799        # Define the output with the text inputs
800        with output:
801
802            # Case for a single sound analysis
803            if self.analysis == 'Single':
804
805                # get the filenames
806                self.file_names = [ky for ky in self.single_file_selector.value.keys()]
807
808                # make a sound name input widget
809                sound_name_input = widgets.Text(value='',
810                                                placeholder='sound name',
811                                                description=self.file_names[0],
812                                                layout=widgets.Layout(width='40%'),
813                                                style=style,
814                                                )
815
816                # make a fundamental input widget
817                fundamental_input = widgets.FloatText(value=0,
818                                                      description='Fundamental :',
819                                                      style=style,
820                                                      layout=widgets.Layout(width='20%')
821                                                      )
822
823                # children that go in the name box
824                children = [sound_name_input, fundamental_input, HZ_string]
825
826                # define a name box widget
827                name_box_layout = widgets.Layout(align_items='stretch', flex_flow='line', width='75%')
828                name_box = widgets.Box(children=children, layout=name_box_layout)
829
830                # display the box
831                display(name_box)
832
833                # store the input to refer them later
834                self.sound_name_inputs = [sound_name_input]
835                self.sound_fundamental_inputs = [fundamental_input]
836
837            # Case for dual sound analysis
838            elif self.analysis in ['Dual', 'Multiple']:
839
840                if self.analysis == 'Dual':
841                    # get the file names
842                    name1 = [ky for ky in self.dual_file_selector_1.value.keys()][0]
843                    name2 = [ky for ky in self.dual_file_selector_2.value.keys()][0]
844                    self.file_names = [name1, name2]
845
846                elif self.analysis == 'Multiple':
847                    self.file_names = [ky for ky in self.mult_file_selector.value.keys()]
848
849                # create empty lists for the inputs
850                self.sound_name_inputs = []
851                self.sound_fundamental_inputs = []
852
853                for file in self.file_names:
854                    # make a text input widget
855                    sound_name_input = widgets.Text(value='',
856                                                    placeholder='sound name',
857                                                    description=file,
858                                                    layout=widgets.Layout(width='40%'),
859                                                    style=style,
860                                                    )
861
862                    # make a fundamental input widget
863                    fundamental_input = widgets.FloatText(value=0,
864                                                          description='Fundamental :',
865                                                          layout=widgets.Layout(width='20%'),
866                                                          style=style
867                                                          )
868
869                    # children that go in the name box
870                    children = [sound_name_input, fundamental_input, HZ_string]
871
872                    # define a name box widget
873                    name_box_layout = widgets.Layout(align_items='stretch', flex_flow='line', width='75%')
874                    name_box = widgets.Box(children=children, layout=name_box_layout)
875
876                    # display the box
877                    display(name_box)
878
879                    # append the inputs
880                    self.sound_name_inputs.append(sound_name_input)
881                    self.sound_fundamental_inputs.append(fundamental_input)
882
883        self.done_button.on_click(self.on_done_button_clicked)
884
885        # display everything
886        display(self.button_box, output)

A method to define sound names and fundamentals

def import_sound_files(self):
 888    def import_sound_files(self):
 889        """
 890        Method to import the soundfile vectors into the program
 891        after the files and names where defined.
 892        *Only works with .wav files*
 893        """
 894
 895        # Case for when only a single file is imported
 896        if self.analysis == 'Single':
 897            # Loading Bar Value = 0
 898            # Get the filename values from the file selector
 899            file_values = self.single_file_selector.value[self.file_names[0]]
 900
 901            # Get the signal audio bytes
 902            bites = file_values['content']
 903
 904            # Convert to wav audio object
 905            audio = wave.open(io.BytesIO(bites))
 906
 907            sr = audio.getframerate()  # save the frame rate
 908
 909            samples = []
 910            self.load_bar.value += 1  # LoadBar value = 1
 911            n = audio.getnframes()
 912            milestones = [int(i) for i in np.linspace(0, n, 5)][1:]
 913            for _ in range(audio.getnframes()):
 914                frame = audio.readframes(1)
 915                samples.append(struct.unpack("h", frame)[0])
 916                if _ in milestones:
 917                    self.load_bar.value += 1  # LoadBar value increases to 5 in loop
 918
 919            self.load_bar.value += 1  # LoadBar value = 7
 920            signal = np.array(samples) / 32768
 921            Sound_Input = (signal, sr)
 922            self.load_bar.value += 1  # LoadBar value = 8
 923
 924            # Get the sound name
 925            if self.sound_name_inputs[0].value == '':
 926                name = self.file_names[0].replace('.wav', '')
 927            else:
 928                name = self.sound_name_inputs[0].value
 929
 930            # Get the sound fundamental
 931            if self.sound_fundamental_inputs[0].value == 0:
 932                fundamental = None
 933            else:
 934                fundamental = self.sound_fundamental_inputs[0].value
 935
 936            self.load_bar.value += 1  # LoadBar value = 9
 937            # This takes a long time
 938            sound = Sound(Sound_Input, name=name, fundamental=fundamental)
 939            self.sounds = sound.condition(return_self=True, verbose=False)
 940            self.load_bar.value += 2  # Load-bar = 10
 941
 942        # Case for two files from two file selectors
 943        elif self.analysis == 'Dual':
 944            # LoadBar = 0
 945            self.sounds = []
 946            file_dicts = [self.dual_file_selector_1.value, self.dual_file_selector_2.value]
 947            self.load_bar.value += 2  # LoadBar = 1
 948
 949            # zipped iterator
 950            iterator = zip(self.file_names, file_dicts, self.sound_name_inputs, self.sound_fundamental_inputs)
 951
 952            #  Create a sound for every file
 953            for file, dic, name_input, fundamental_input in iterator:
 954
 955                file_values = dic[file]
 956                bites = file_values['content']
 957                audio = wave.open(io.BytesIO(bites))
 958                sr = audio.getframerate()
 959                samples = []
 960                self.load_bar.value += 1  # LoadBar +=2
 961                for _ in range(audio.getnframes()):
 962                    frame = audio.readframes(1)
 963                    samples.append(struct.unpack("h", frame)[0])
 964                self.load_bar.value += 1  # LoadBar +=2
 965                signal = np.array(samples) / 32768
 966                Sound_Input = (signal, sr)
 967
 968                # get the name value
 969                if name_input.value == '':
 970                    name = file.replace('.wav', '')
 971                else:
 972                    name = name_input.value
 973
 974                # get the fundamental value
 975                if fundamental_input.value == 0:
 976                    fundamental = None
 977                else:
 978                    fundamental = fundamental_input.value
 979
 980                sound = Sound(Sound_Input, name=name, fundamental=fundamental)
 981                sound.condition(verbose=False)
 982                self.sounds.append(sound)
 983                self.load_bar.value += 1  # LoadBar +=2
 984            # Load Bar = 8
 985            self.Pack = SoundPack(self.sounds, names=[sound.name for sound in self.sounds])
 986            self.load_bar.value += 2  # Load Bar = 10
 987
 988        # Case for multiple files
 989        elif self.analysis == 'Multiple':
 990            # LoadBar = 0
 991            self.sounds = []
 992            self.load_bar.value += 1  # LoadBar = 1
 993
 994            # zipped iterator
 995            iterator = zip(self.file_names, self.sound_name_inputs, self.sound_fundamental_inputs)
 996
 997            for file, name_input, fundamental_input in iterator:
 998                file_values = self.mult_file_selector.value[file]
 999                bites = file_values['content']
1000                audio = wave.open(io.BytesIO(bites))
1001                sr = audio.getframerate()
1002                samples = []
1003                for _ in range(audio.getnframes()):
1004                    frame = audio.readframes(1)
1005                    samples.append(struct.unpack("h", frame)[0])
1006                signal = np.array(samples) / 32768
1007                Sound_Input = (signal, sr)
1008
1009                # get the sound names
1010                if name_input.value == '':
1011                    name = file.replace('.wav', '')
1012                else:
1013                    name = name_input.value
1014
1015                # get the fundamental values
1016                if fundamental_input.value == 0:
1017                    fundamental = None
1018                else:
1019                    fundamental = fundamental_input.value
1020
1021                sound = Sound(Sound_Input, name=name, fundamental=fundamental)
1022                sound.condition(verbose=False)
1023                self.sounds.append(sound)
1024                if self.load_bar.value < 9:
1025                    self.load_bar.value += 1
1026
1027            self.Pack = SoundPack(self.sounds, names=[sound.name for sound in self.sounds])
1028            while self.load_bar.value < 10:
1029                self.load_bar.value += 1  # LoadBar = 10

Method to import the soundfile vectors into the program after the files and names where defined. Only works with .wav files