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
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
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
.
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
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
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
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