def create_waveform_and_spectrogram_from_wav_file(wavFilename, startTime=0, endTime=-1, maxFrequency=5500., onlySpectrogramColorbar=True, showPitch=False, showIntensity=False, showFormants=False, maxFormantFrequency=None, dynamicRange=None, dynamicRangeMin=None, showTextgridFormants=False, wavFormAnnotations=None, output_runtime=True): global_values.plot_counter += 1 start_time = time.perf_counter() #################################################################################### ### Check and fix arguments for problems OR to set default values (if necessary) #################################################################################### ##----------------------------------------------------------------------## ### No audio file specified... can't do anything ##----------------------------------------------------------------------## if wavFilename is None or wavFilename == '': raise Exception("praatFormants.py->scatterPlotFormants: No audio filename provided") wav_file_path = audio_file_helper.get_full_wav_file_path(wavFilename) ##----------------------------------------------------------------------## ### Check that the specified file actually exists ##----------------------------------------------------------------------## global_path_helper.verify_file_exists(wav_file_path) if wavFormAnnotations is None: wavFormAnnotations=[] # Close other existing plots to avoid issues plt.close("all") # Create subplots for waveform, spectrogram, and colorbar # get the associated figure and axes fig, axs = matplotlib.pyplot.subplots(ncols=1, nrows=2, figsize=(global_values.figure_width, global_values.figure_height), dpi=global_values.image_DPI) fig.subplots_adjust(right=0.95) # making some room for cbar ##-----------------------------------## ### Plot waveform ##-----------------------------------## waveform_start_time = time.perf_counter() create_waveform_from_wav_file(wav_file_path, startTime=startTime, endTime=endTime, isSubplot=True, ax=axs[0], annotations=wavFormAnnotations) waveform_runtime = time.perf_counter() - waveform_start_time ##-----------------------------------## ### Plot spectrogram ##-----------------------------------## spectrogram_start_time = time.perf_counter() colorImageMap = create_spectrogram_from_wav_file(wav_file_path, startTime=startTime, endTime=endTime, maxFrequency=maxFrequency, showPitch=showPitch, showIntensity=showIntensity, showFormants=showFormants, maxFormantFrequency=maxFormantFrequency, dynamicRange=dynamicRange, dynamicRangeMin=dynamicRangeMin, showTextgridFormants=showTextgridFormants, isSubplot=True, ax=axs[1]) spectrogram_runtime = time.perf_counter() - spectrogram_start_time ##-----------------------------------## ### Create/plot colorbar (for spectrogram) ##-----------------------------------## # getting the lower left (x0,y0) and upper right (x1,y1) corners: [[x00,y00],[x01,y01]] = axs[0].get_position().get_points() [[x10,y10],[x11,y11]] = axs[1].get_position().get_points() # Set width of colorbar colorbarPadding = 0.045 # Set padding between subplots and colorbar colorbarWidth = 0.0125 # If colorbar should only be height of spectrogram # otherwise, set colorbar height to be complete figure if onlySpectrogramColorbar: cbarAxis = fig.add_axes([x11+colorbarPadding, y10, colorbarWidth, y11-y10]) else: cbarAxis = fig.add_axes([x11+colorbarPadding, y10, colorbarWidth, y01-y10]) # Create colorbar using color image map from spectrogram and colorbar axis dimensions cbar = create_spectrogram_colorbar("Level (dB)", colorImageMap, theFigure=fig, theColorbarAxis=cbarAxis, theParentAxis=axs[1]) ##################### ### Put waveform axis on top so that annotations/text will be ### visible if there is any overlapping ##################### axs[0].set_zorder(50) axs[1].set_zorder(1) ##################### ### Save plot to an image ##################### # You can set the format by changing the extension # (e.g. .pdf, .svg, .eps) filename = global_path_helper.get_filename_only(wav_file_path) + '_waveform' filename = image_file_helper.fix_filename_conflicts(filename) # The frameon kwarg to savefig and the rcParams["savefig.frameon"] rcParam. # To emulate frameon = False, set facecolor to fully transparent ("none", or (0, 0, 0, 0)). fig.patch.set_alpha(0.0) axs[0].patch.set_alpha(0.0) axs[1].patch.set_alpha(0.0) plt.savefig(global_values.python_images_dir + '{0:0>2}'.format(global_values.plot_counter) + filename, format=global_values.image_format, bbox_inches='tight', dpi=global_values.image_DPI, #frameon=False, aspect='normal', pad_inches=0.15, facecolor=fig.get_facecolor(), edgecolor='none', transparent=global_values.image_transparency) ### Optimize the SVG file image_file_helper.optimize_SVG(global_values.cli_python_images_dir + '{0:0>2}'.format(global_values.plot_counter) + filename) ### Display plot in GUI window, if desired #display_plot(fig, plt) # Clear axis plt.cla() # Clear figure plt.clf() # Close a figure window plt.close() ##################### ### Print plot image to LaTeX ##################### if global_values.image_format == 'svg' or global_values.image_format == 'svgz': ### Normalize SVGz to a standarized width (defined by global_values.normalized_SVGz_width) #image_file_helper.normalize_SVGz_width(filename) ### Output \includegraphic command for Tex image_file_helper.create_TeX_include_SVG_command(filename) if output_runtime: runtimes_output = '------------------------------------\n' runtimes_output += 'Runtimes for ' + wav_file_path + '\n' runtimes_output += 'waveform plot time: {:.7f}\n'.format(waveform_runtime) runtimes_output += 'spectrogram plot time: {:.7f}\n'.format(spectrogram_runtime) runtimes_output += 'waveform+spectrogram plot time: {:.7f}\n'.format(time.perf_counter() - start_time) runtimes_output += '\n' global_path_helper.append_to_file(global_values.python_runtimes_dir + 'create_waveform_and_spectrogram_from_wav_file.log', runtimes_output)
def plotIntensity(wav_file_path, startTime=0.0, endTime=-1, minFrequency=100, maxFrequency=5000.0, sampleRate=None, limitToTextgrid=False, ax=None): #################################################################################### ### Check and fix arguments for problems OR to set default values (if necessary) #################################################################################### ##----------------------------------------------------------------------## ### No audio file specified... can't do anything ##----------------------------------------------------------------------## if wav_file_path is None or wav_file_path == '': raise Exception( "praatIntensity.py->plotIntensity: No audio filename provided") ##----------------------------------------------------------------------## ### Check that the specified file actually exists ##----------------------------------------------------------------------## global_path_helper.verify_file_exists(wav_file_path) ### Set minimum viewing frequency for pitch to 0 Hz, if not set properly if minFrequency == None: minFrequency = 100.0 ### Set maximum viewing frequency for pitch to 5000 Hz, if not set properly if maxFrequency == None: maxFrequency = 5000.0 ##----------------------------------------------------------------------## ### Create a plot object which is either matplotlib.pyplot (if the axis argument was not specified) ##### or matplotlib.ax.Axis (if the axis argument was specified) ##----------------------------------------------------------------------## if ax == None or ax is None: the_plot = plt else: the_plot = ax #################################################################################### ### Calculate all intensities using Praat in the audio file (within start/end times) ##### and then break the intensities into continuous intervals #################################################################################### ##----------------------------------------------------------------------## ### Have Praat calculate all times and intensities for audio file ##----------------------------------------------------------------------## times, intensities = praatUtil.calculateIntensity(wav_file_path, startTime=startTime, endTime=endTime, fMin=minFrequency, timeStep=0.0, subtractMean=True) ##----------------------------------------------------------------------## ### If the start time is not specified OR is less than the minimum one in Praat data, ##### then use the first time of the Praat data ##----------------------------------------------------------------------## if startTime == 0.0 or startTime < times[0]: startTime = times[0] else: cropped_start_time = numpy.unravel_index((times >= startTime).argmax(), times.shape)[0] times = times[cropped_start_time:] intensities = intensities[cropped_start_time:] ##----------------------------------------------------------------------## ### If the end time is not specified OR is greater than the maximum one in Praat data, ##### then use the last time of the Praat data ##----------------------------------------------------------------------## if endTime == -1 or endTime > times[-1]: endTime = times[-1] else: cropped_end_time = numpy.unravel_index((times >= endTime).argmax(), times.shape)[0] times = times[:cropped_end_time] intensities = intensities[:cropped_end_time] #################################################################################### ### Normalize intensities onto the spectrogram #################################################################################### ### Minimum intensity of all intensities min_intensity = numpy.amin(intensities) ### Maximum intensity of all intensities max_intensity = numpy.amax(intensities) intensities -= min_intensity intensities /= (max_intensity - min_intensity) intensities *= maxFrequency #################################################################################### ### Plot intensity data points #################################################################################### the_plot.plot( times, intensities, c='y', linewidth=2, path_effects=[pe.Stroke(linewidth=6, foreground='k'), pe.Normal()], zorder=2)
def create_pitch_and_table_from_wav_file( wavFilename, xaxisLabel="Time (s)", yaxisLabel="Frequency (Hz)", startTime=0.0, endTime=-1, timeStep=0.0, #Praat default is 0.0 (calculated) minPitch=75.0, maxPitch=600.0, silenceThreshold=0.03, #Praat default is 0.03 voicingThreshold=0.45, #Praat default is 0.45 octaveCost=0.01, #Praat default is 0.01 octaveJumpCost=0.35, #Praat default is 0.35 voicedUnvoicedCost=0.14, #Praat default is 0.14 showIntensity=False, languageBreaks=[], language=[], glossBreaks=[], glosses=[], removeMaximums=[], output_runtime=True): global_values.plot_counter += 1 start_time = time.perf_counter() #################################################################################### ### Check and fix arguments for problems OR to set default values (if necessary) #################################################################################### ##----------------------------------------------------------------------## ### No audio file specified... can't do anything ##----------------------------------------------------------------------## if wavFilename is None or wavFilename == '': raise Exception( "plotPitchTable.py->create_pitch_and_table_from_wav_file: No audio filename provided" ) wav_file_path = audio_file_helper.get_full_wav_file_path(wavFilename) ##----------------------------------------------------------------------## ### Check that the specified file actually exists ##----------------------------------------------------------------------## global_path_helper.verify_file_exists(wav_file_path) if languageBreaks is None: languageBreaks = [] if language is None: language = [] if glossBreaks is None: glossBreaks = [] if glosses is None: glosses = [] if removeMaximums is None: removeMaximums = [] ### Close other existing plots to avoid issues plt.close("all") ### Create subplots for waveform, spectrogram, and colorbar ##### get the associated figure and axes fig, axs = matplotlib.pyplot.subplots( ncols=1, nrows=1, figsize=(global_values.figure_width, global_values.figure_height), dpi=global_values.image_DPI) pitch_start_time = time.perf_counter() praatPitch.plotPurePitch( wav_file_path, startTime=startTime, endTime=endTime, timeStep=timeStep, #Praat default is 0.0 (calculated) minPitchHz=minPitch, #Praat default is 75 maxPitchHz=maxPitch, #Praat default is 500 silenceThreshold=silenceThreshold, #Praat default is 0.03 voicingThreshold=voicingThreshold, #Praat default is 0.45 octaveCost=octaveCost, #Praat default is 0.01 octaveJumpCost=octaveJumpCost, #Praat default is 0.35 voicedUnvoicedCost=voicedUnvoicedCost, #Praat default is 0.14 #silenceThreshold = 0.03,#Praat default is 0.03 #voicingThreshold = 0.45,#Praat default is 0.45 #octaveCost = 0.01,#Praat default is 0.01 #octaveJumpCost = 0.35,#Praat default is 0.35 #voicedUnvoicedCost = 0.14,#Praat default is 0.14 removeMaximums=removeMaximums, ax=axs) pitch_runtime = time.perf_counter() - pitch_start_time ##-----------------------------------## ###Show intensity (plot) ##-----------------------------------## '''if showIntensity: praatIntensity.plotIntensity(wav_file_path, startTime=startTime, endTime=endTime, maxFrequency=maxFrequency, sampleRate=samplerate, minFrequency=100, limitToTextgrid=False, ax=ax)''' #-----------------------------------## ###Create and show language/gloss table ##-----------------------------------## table_start_time = time.perf_counter() line_times = sorted(numpy.unique(languageBreaks + glossBreaks)) for line_time in line_times: plt.plot(numpy.array([line_time, line_time]), numpy.array([plt.ylim()[0], plt.ylim()[1]]), c='k', linewidth=0.25, zorder=0) glosses_column_widths = [] for index, break_point in enumerate(glossBreaks): if index == 0: glosses_column_widths.append( (break_point - startTime) / (endTime - startTime)) else: glosses_column_widths.append( (break_point - glossBreaks[index - 1]) / (endTime - startTime)) glosses_column_widths.append( (endTime - glossBreaks[-1]) / (endTime - startTime)) language_column_widths = [] for index, break_point in enumerate(languageBreaks): if index == 0: language_column_widths.append( (break_point - startTime) / (endTime - startTime)) else: language_column_widths.append( (break_point - languageBreaks[index - 1]) / (endTime - startTime)) language_column_widths.append( (endTime - languageBreaks[-1]) / (endTime - startTime)) gloss_table = axs.table(cellText=[glosses, glosses], colWidths=glosses_column_widths, cellLoc='center', loc='bottom') language_table = axs.table(cellText=[language], colWidths=language_column_widths, cellLoc='center', loc='bottom') for key, cell in gloss_table.get_celld().items(): cell.set_text_props(fontproperties=global_values.gloss_fontproperties) for key, cell in language_table.get_celld().items(): cell.set_text_props( fontproperties=global_values.language_fontproperties) for cell in gloss_table._cells: if gloss_table._cells[cell]._text.get_text() == '': gloss_table._cells[cell].set_color('lightgrey') gloss_table._cells[cell].set_edgecolor('black') else: if '{sc}' in gloss_table._cells[cell]._text.get_text(): gloss_table._cells[cell].set_text_props( fontproperties=global_values. gloss_small_caps_fontproperties) gloss_table._cells[cell]._text.set_text( gloss_table._cells[cell]._text.get_text().replace( '{sc}', '{}')) if '{}' in gloss_table._cells[cell]._text.get_text(): if gloss_table._cells[cell]._text.get_text().startswith('{}'): gloss_table._cells[cell]._loc = 'left' gloss_table._cells[cell].PAD = 0.04 gloss_table._cells[cell]._text.set_horizontalalignment( 'left') elif gloss_table._cells[cell]._text.get_text().endswith('{}'): gloss_table._cells[cell]._loc = 'right' gloss_table._cells[cell]._text.set_horizontalalignment( 'right') gloss_table._cells[cell].PAD = 0.03 gloss_table._cells[cell]._text.set_text( gloss_table._cells[cell]._text.get_text().replace( '{}', '')) for cell in language_table._cells: if language_table._cells[cell]._text.get_text() == '': language_table._cells[cell].set_color('lightgrey') language_table._cells[cell].set_edgecolor('black') else: if '{}' in language_table._cells[cell]._text.get_text(): if language_table._cells[cell]._text.get_text().startswith( '{}'): language_table._cells[cell]._loc = 'left' language_table._cells[cell].PAD = 0.04 language_table._cells[cell]._text.set_horizontalalignment( 'left') elif language_table._cells[cell]._text.get_text().endswith( '{}'): language_table._cells[cell]._loc = 'right' language_table._cells[cell]._text.set_horizontalalignment( 'right') language_table._cells[cell].PAD = 0.03 language_table._cells[cell].set_text_props( fontproperties=global_values.language_fontproperties) language_table._cells[cell]._text.set_text( language_table._cells[cell]._text.get_text().replace( '{}', '')) #gloss_table.set_fontsize(18) #language_table.set_fontsize(18) gloss_table.set_fontsize(24) language_table.set_fontsize(24) gloss_table.scale(1, 3) language_table.scale(1, 3) table_runtime = time.perf_counter() - table_start_time ##-----------------------------------## ###Format x-axis and y-axis ##-----------------------------------## plt.tick_params(axis='both', which='major', labelsize=global_values.axis_tick_font_size) for label in axs.get_xticklabels(): label.set_fontproperties(global_values.axis_tick_fontproperties) plt.ylabel(yaxisLabel, fontproperties=global_values.axis_label_fontproperties, fontsize=global_values.axis_label_font_size) plt.xlabel(xaxisLabel, fontproperties=global_values.axis_label_fontproperties, fontsize=global_values.axis_label_font_size, labelpad=-1 * int(global_values.axis_label_font_size / 3)) axs.spines['bottom'].set_position( ('data', plt.ylim()[0] - ((plt.ylim()[1] - plt.ylim()[0]) * (gloss_table.properties()['child_artists'][0].get_height() * 2)))) ##################### ###Save plot to an image ##################### #You can set the format by changing the extension #(e.g. .pdf, .svg, .eps) '''filename = global_path_helper.get_filename_only(wav_file_path) + '_pitch_plot' #filename_CMKY = filename_RGB + '_CMKY' #filename_RGB, filename_CMKY = image_file_helper.fix_RGB_and_CMYK_filenames(filename_RGB, filename_CMKY) filename = image_file_helper.fix_filename_conflicts(filename) #if global_values.image_format == 'svg' or global_values.image_format == 'svgz': #filename_CMKY = filename_RGB plt.savefig(global_values.python_images_dir + '{0:0>2}'.format(global_values.plot_counter) + filename, fontproperties=global_values.generic_fontproperties, format=global_values.image_format, bbox_inches='tight', dpi=global_values.image_DPI, #dpi=600,#global_values.image_DPI*2, frameon='false', aspect='normal', pad_inches=0.15, transparent=global_values.image_transparency)''' filename = global_path_helper.get_filename_only( wav_file_path) + '_pitch_plot' filename = image_file_helper.fix_filename_conflicts(filename) fig.patch.set_alpha(0.0) #axs[0].patch.set_alpha(0.0) #axs[1].patch.set_alpha(0.0) axs.patch.set_alpha(0.0) plt.savefig(global_values.python_images_dir + '{0:0>2}'.format(global_values.plot_counter) + filename, format=global_values.image_format, bbox_inches='tight', dpi=global_values.image_DPI, frameon=False, aspect='normal', pad_inches=0.15, facecolor=fig.get_facecolor(), edgecolor='none', transparent=global_values.image_transparency) ### Optimize the SVG file image_file_helper.optimize_SVG( global_values.cli_python_images_dir + '{0:0>2}'.format(global_values.plot_counter) + filename) #Clear axis plt.cla() #Clear figure plt.clf() #Close a figure window plt.close() ##################### ###Print plot image to LaTeX ##################### if global_values.image_format == 'svg' or global_values.image_format == 'svgz': ### Normalize SVGz to a standarized width (defined by global_values.normalized_SVGz_width) #image_file_helper.normalize_SVGz_width(filename) ### Output \includegraphic command for Tex image_file_helper.create_TeX_include_SVG_command(filename) if output_runtime: runtimes_output = '------------------------------------\n' runtimes_output += 'Runtimes for ' + wav_file_path + '\n' runtimes_output += 'pitch plot time: {:.7f}\n'.format(pitch_runtime) runtimes_output += 'table plot time: {:.7f}\n'.format(table_runtime) runtimes_output += 'pitch+table plot time: {:.7f}\n'.format( time.perf_counter() - start_time) runtimes_output += '\n' global_path_helper.append_to_file( global_values.python_runtimes_dir + 'create_pitch_and_table_from_wav_file.log', runtimes_output)
def plotSpectrogramPitch(wav_file_path, startTime=0.0, endTime=-1, minViewFrequency=0.0, maxViewFrequency=5000.0, timeStep=0.0,#Praat default is 0.0 minPitchHz=75,#Praat default is 75 maxPitchHz=600.0,#Praat default is 600 veryAccurate = False,#Praat default is False silenceThreshold = 0.0295,#Praat default is 0.03 voicingThreshold = 0.45,#Praat default is 0.45 octaveCost = 0.01,#Praat default is 0.01 octaveJumpCost = 0.25,#Praat default is 0.35 voicedUnvoicedCost = 0.26,#Praat default is 0.14 normalizePitch = False,#Need to normalize pitch for spectrograms, but not pitch plots ax=None): #################################################################################### ### Check and fix arguments for problems OR to set default values (if necessary) #################################################################################### ##----------------------------------------------------------------------## ### No audio file specified... can't do anything ##----------------------------------------------------------------------## if wav_file_path is None or wav_file_path == '': raise Exception("praatPitch.py->plotSpectrogramPitch: No audio filename provided") ##----------------------------------------------------------------------## ### Check that the specified file actually exists ##----------------------------------------------------------------------## global_path_helper.verify_file_exists(wav_file_path) ### Set minimum viewing frequency for pitch to 0 Hz, if not set properly if minViewFrequency == None: minViewFrequency = 0.0 ### Set maximum viewing frequency for pitch to 5000 Hz, if not set properly if maxViewFrequency == None: maxViewFrequency = 5000.0 ### Set minimum pitch to 75 Hz, if not set properly if minPitchHz == None: minPitchHz = 75.0 ### Set maximum pitch to 75 Hz, if not set properly if maxPitchHz == None: maxPitchHz = 600.0 ##----------------------------------------------------------------------## ### Create a plot object which is either matplotlib.pyplot (if the axis argument was not specified) ##### or matplotlib.ax.Axis (if the axis argument was specified) ##----------------------------------------------------------------------## if ax == None or ax is None: the_plot = plt else: the_plot = ax #################################################################################### ### Calculate all pitches using Praat in the audio file (within start/end times) ##### and then break the pitches into continuous intervals #################################################################################### ##----------------------------------------------------------------------## ### Have Praat calculate all times and pitches (F0) for audio file ##----------------------------------------------------------------------## times, pitches = praatUtil.calculatePitch(wav_file_path, startTime=startTime, endTime=endTime, timeStep=timeStep, fMin=minPitchHz, fMax=maxPitchHz, veryAccurate = veryAccurate, silenceThreshold = silenceThreshold, voicingThreshold = voicingThreshold, octaveCost = octaveCost, octaveJumpCost = octaveJumpCost, voicedUnvoicedCost = voicedUnvoicedCost) ##----------------------------------------------------------------------## ### If the start time is not specified OR is less than the minimum one in Praat data, ##### then use the first time of the Praat data ##----------------------------------------------------------------------## if startTime is None or startTime == 0.0 or startTime < times[0]: startTime = times[0] ##----------------------------------------------------------------------## ### If the end time is not specified OR is greater than the maximum one in Praat data, ##### then use the last time of the Praat data ##----------------------------------------------------------------------## if endTime is None or endTime == -1 or endTime > times[-1]: endTime = times[-1] ##----------------------------------------------------------------------## ### If the timeStep is not specified OR is 0.0, ##### then use the time step determined by the Praat data ##### Ignore the first time, because may be junk (due to start/end time being specified) ##----------------------------------------------------------------------## if timeStep is None or int(timeStep) == 0: timeStep = times[2]-times[1] #################################################################################### ### Separate continuous data into different lists, and store each list ##### in a single large list #################################################################################### ### List of each continuous time list croppedTimes = [] ### List of each continuous pitch list croppedPitches = [] ### (Temporary) list of continuous time nobreakTimes = [] ### (Temporary) list of continuous time nobreakPitches = [] ##----------------------------------------------------------------------## ### Loop through pitch data (which was calculated by Praat) ##----------------------------------------------------------------------## for index, pitch in enumerate(pitches): ##----------------------------------------------------------------------## ### Only use data within the specified start/end time range ##----------------------------------------------------------------------## if times[index] >= startTime and times[index] <= endTime: ##----------------------------------------------------------------------## ### If the difference of time between last an current timestamp ##### is approximately the time step used by Praat to calculate data ##### then we assume it is continuous. ##### Therefore, continue adding to the (temporary) continuous data list ##----------------------------------------------------------------------## if index > 0 and (times[index]-times[index-1]) <= (timeStep*3): nobreakTimes.append(times[index]) nobreakPitches.append(pitch) ##----------------------------------------------------------------------## ### Otherwise, it appears we reached a new set of continuous data. ##### Therefore, store the previous continuous data list into the overall ##### list of times and pitches. ##----------------------------------------------------------------------## #elif nobreakTimes is not [] and nobreakPitches is not []: else: croppedTimes.append(nobreakTimes) croppedPitches.append(nobreakPitches) nobreakTimes = [] nobreakPitches = [] ##----------------------------------------------------------------------## ### Make sure that if the final list of continuous data is stored, ##### in case it wasn't inside the above FOR loop. ##----------------------------------------------------------------------## if len(nobreakTimes) > 0: croppedTimes.append(nobreakTimes) croppedPitches.append(nobreakPitches) nobreakTimes = [] nobreakPitches = [] #################################################################################### ### Calculate the normalized pitch frequencies so that they fit properly on ##### a spectrogram. #################################################################################### normalizedPitches = [] normalizedNobreakPitches = [] ##----------------------------------------------------------------------## ### Only normalize if instructed to do so ##----------------------------------------------------------------------## if normalizePitch: ##----------------------------------------------------------------------## ### Need to iterate through each of the continuous data lists ##----------------------------------------------------------------------## for eachPitchList in croppedPitches: ##----------------------------------------------------------------------## ### Iterate through continuous pitch data ##----------------------------------------------------------------------## for pitch in eachPitchList: ##----------------------------------------------------------------------## #### Calculate the normalized pitch frequency ##### and store in temporary, continuous pitch data list ##----------------------------------------------------------------------## normalizedNobreakPitches.append(((pitch-minPitchHz)/(maxPitchHz-minPitchHz)) * (maxViewFrequency-minViewFrequency)) ### Append continuous data list to list of all continuous pitches normalizedPitches.append(normalizedNobreakPitches) ### Reset the temporary normalized continuous pitch data list normalizedNobreakPitches = [] ##----------------------------------------------------------------------## ### We were instructed not to normalize ##----------------------------------------------------------------------## else: normalizedPitches = croppedPitches #################################################################################### ### Loop through each set of continuous data points (for pitch) ##### and plot each continuous line. #################################################################################### for index, timeArray in enumerate(croppedTimes): the_plot.plot(numpy.array(timeArray), numpy.array(normalizedPitches[index]), c='b', linewidth=2, path_effects=[pe.Stroke(linewidth=4, foreground='w'), pe.Normal()], zorder=2) ##----------------------------------------------------------------------## ### Setup the y-axis ##----------------------------------------------------------------------## # Set minimum and maximum x-axis values the_plot.set_ylim(minViewFrequency, maxViewFrequency)
def plotPurePitch(wav_file_path, startTime=0.0, endTime=-1, timeStep=0.0,#Praat default is 0.0 minPitchHz=75,#Praat default is False maxPitchHz=600.0,#Praat default is 600 silenceThreshold = 0.0295,#Praat default is 0.03 voicingThreshold = 0.45,#Praat default is 0.45 octaveCost = 0.01,#Praat default is 0.01 octaveJumpCost = 0.25,#Praat default is 0.35 voicedUnvoicedCost = 0.26,#Praat default is 0.14 removeMaximums = [], ax=None): #################################################################################### ### Check and fix arguments for problems OR to set default values (if necessary) #################################################################################### ##----------------------------------------------------------------------## ### No audio file specified... can't do anything ##----------------------------------------------------------------------## if wav_file_path is None or wav_file_path == '': raise Exception("praatPitch.py->plotPurePitch: No audio filename provided") ##----------------------------------------------------------------------## ### Check that the specified file actually exists ##----------------------------------------------------------------------## global_path_helper.verify_file_exists(wav_file_path) ### Set minimum pitch to 75 Hz, if note set properly if minPitchHz == None: minPitchHz = 75.0 ### Set maximum pitch to 75 Hz, if note set properly if maxPitchHz == None: maxPitchHz = 600.0 ##----------------------------------------------------------------------## ### Create a plot object which is either matplotlib.pyplot (if the axis argument was not specified) ##### or matplotlib.ax.Axis (if the axis argument was specified) ##----------------------------------------------------------------------## if ax == None or ax is None: the_plot = plt else: the_plot = ax #################################################################################### ### Calculate all pitches using Praat in the audio file (within start/end times) ##### and then break the pitches into continuous intervals #################################################################################### ##----------------------------------------------------------------------## ### Have Praat calculate all times and pitches (F0) for audio file ##----------------------------------------------------------------------## times, pitches = praatUtil.calculatePitch(wav_file_path, startTime=startTime, endTime=endTime, timeStep=timeStep, fMin=minPitchHz, fMax=maxPitchHz, veryAccurate = True, silenceThreshold = silenceThreshold, voicingThreshold = voicingThreshold, octaveCost = octaveCost, octaveJumpCost = octaveJumpCost, voicedUnvoicedCost = voicedUnvoicedCost) ##----------------------------------------------------------------------## ### If the start time is not specified, then use the first time of the Praat data ##### OTHERWISE, fix the list of times to start at the specified start time ##----------------------------------------------------------------------## '''if startTime == 0.0: startTime = times[0] else: cropped_start_time = numpy.unravel_index((times>=startTime).argmax(), times.shape)[0] times = times[cropped_start_time:] pitches = pitches[cropped_start_time:] ##----------------------------------------------------------------------## ### Use the last time of the Praat data, if not specified ##----------------------------------------------------------------------## if endTime == -1: endTime = times[-1] else: cropped_end_time = numpy.unravel_index((times>=endTime).argmax(), times.shape)[0] times = times[:cropped_end_time] pitches = pitches[:cropped_end_time]''' ##----------------------------------------------------------------------## ### If the start time is not specified OR is less than the minimum one in Praat data, ##### then use the first time of the Praat data ##----------------------------------------------------------------------## if startTime is None or startTime == 0.0 or startTime < times[0]: startTime = times[0] ##----------------------------------------------------------------------## ### If the end time is not specified OR is greater than the maximum one in Praat data, ##### then use the last time of the Praat data ##----------------------------------------------------------------------## if endTime is None or endTime == -1 or endTime > times[-1]: endTime = times[-1] ##----------------------------------------------------------------------## ### If the timeStep is not specified OR is 0.0, ##### then use the time step determined by the Praat data ##### Ignore the first time, because may be junk (due to start/end time being specified) ##----------------------------------------------------------------------## if timeStep is None or timeStep == 0.0: timeStep = times[2]-times[1] #################################################################################### ### Separate continuous data into different lists, and store each list ##### in a single large list #################################################################################### ### List of each continuous time list croppedTimes = [] ### List of each continuous pitch list croppedPitches = [] ### (Temporary) list of continuous time nobreakTimes = [] ### (Temporary) list of continuous time nobreakPitches = [] ##----------------------------------------------------------------------## ### Loop through pitch data (which was calculated by Praat ##----------------------------------------------------------------------## for index, pitch in enumerate(pitches): ##----------------------------------------------------------------------## ### Only use data within the specified start/end time range ##----------------------------------------------------------------------## if times[index] >= startTime and times[index] <= endTime: ##----------------------------------------------------------------------## ### If the difference of time between last an current timestamp ##### is approximately the time step used by Praat to calculate data ##### then we assume it is continuous. ##### Therefore, continue adding to the (temporary) continuous data list ##----------------------------------------------------------------------## if index > 0 and (times[index]-times[index-1]) <= (timeStep*3): nobreakTimes.append(times[index]) nobreakPitches.append(pitch) ##----------------------------------------------------------------------## ### Otherwise, it appears we reached a new set of continuous data. ##### Therefore, store the previous continuous data list into the overall ##### list of times and pitches. ##----------------------------------------------------------------------## #elif nobreakTimes is not [] and nobreakPitches is not []: else: croppedTimes.append(nobreakTimes) croppedPitches.append(nobreakPitches) nobreakTimes = [] nobreakPitches = [] ##----------------------------------------------------------------------## ### Make sure that if the final list of continuous data is stored, ##### in case it wasn't inside the above FOR loop. ##----------------------------------------------------------------------## if len(nobreakTimes) > 0: croppedTimes.append(nobreakTimes) croppedPitches.append(nobreakPitches) nobreakTimes = [] nobreakPitches = [] #################################################################################### ### Calculate minimum and maximum y-axis frequencies ##### so that interval is always 10, 20, 30, etc. ##### depending on how large the maximum and minimum are #################################################################################### ### Temporarily set minimum y-axis frequency to maximum Praat pitch frequency minViewFrequency = maxPitchHz ### Temporarily set maximum y-axis frequency to minimum Praat pitch frequency maxViewFrequency = minPitchHz ##----------------------------------------------------------------------## ### Determine minimum/maximum pitch frequencies from the pitches calculated by Praat ##----------------------------------------------------------------------## for pitchList in croppedPitches: ### Ignore, unless the list of continuous pitches contains data if pitchList is not None and pitchList != []: minViewFrequency = min(pitchList) if min(pitchList) < minViewFrequency else minViewFrequency maxViewFrequency = max(pitchList) if max(pitchList) > maxViewFrequency else maxViewFrequency ##----------------------------------------------------------------------## ### Using minimum and maximum pitches from Praat, ##### - calculate the interval between y-axis ticks ##### - calculate the minimum y-axis tick value ##### - calculate the maximum y-axis tick value ##----------------------------------------------------------------------## yaxisInterval = round(math.ceil((maxViewFrequency-minViewFrequency) / 100) * 100) / 10 minViewFrequency = int(math.floor(minViewFrequency / yaxisInterval)) * yaxisInterval maxViewFrequency = int(math.ceil(maxViewFrequency / yaxisInterval)) * yaxisInterval #################################################################################### ### Loop through each set of continuous data points (for pitch) ##### and plot each continuous line. ##### Also plot the (local) maximum pitches for each continuous line ##### with a red line and red text indicating the pitch maximum. #################################################################################### # A counter for how many maximum pitches have been plotted maximum_counter = 0 # Loop through the list of lists. Outer list contains each list of continuous times for index, timeArray in enumerate(croppedTimes): # Get the corresponding list of continous pitches pitchArray = croppedPitches[index] # Ignore any empty lists if timeArray is not None and timeArray != [] \ and pitchArray is not None and pitchArray != []: ##----------------------------------------------------------------------## ### Plot a continuous list of pitches, with a blue line ##----------------------------------------------------------------------## the_plot.plot(numpy.array(timeArray), numpy.array(pitchArray), c='b', linewidth=2, path_effects=[pe.Stroke(linewidth=4, foreground='w'), pe.Normal()], zorder=2) #################################################################################### ### Plot a line and the numerical value for a (local) maximum pitch ##### near the point in the plot of pitches #################################################################################### #(Added an extra element at the beginning and end of the pitch list) #(->this is to help with finding local maximum pitches at the beginning/end of a line) prev_index_of_max = -1 for index_of_max in scipy.signal.argrelextrema(numpy.array([0]+pitchArray+[0]), numpy.greater)[0]: index_of_max -= 1# Because we added an extra element, need to access the previous element in time and pitch lists # Make sure we only added maximums that are not excluded if maximum_counter not in removeMaximums: ##----------------------------------------------------------------------## ### Plot a small horizontal at the pitch maximum ##----------------------------------------------------------------------## red_line_xvalues = [timeArray[index_of_max], timeArray[index_of_max] + ((endTime-startTime) * 0.02), timeArray[index_of_max] + ((endTime-startTime) * 0.11)] red_line_height_percent = 0.05 if prev_index_of_max > -1 and (timeArray[index_of_max] - timeArray[prev_index_of_max] < ((endTime-startTime) * 0.11)): red_line_height_percent = 0.025 upper_red_line_yvalue = pitchArray[index_of_max]+(maxViewFrequency-minViewFrequency)*red_line_height_percent red_line_yvalues = [pitchArray[index_of_max], upper_red_line_yvalue, upper_red_line_yvalue] the_plot.plot(numpy.array(red_line_xvalues), numpy.array(red_line_yvalues), c='r', linewidth=0.85, clip_on=False, zorder=2) ##----------------------------------------------------------------------## ### Add text (on the line) indicating the pitch maximum ##----------------------------------------------------------------------## the_plot.text(timeArray[index_of_max]+((endTime-startTime) * 0.065), upper_red_line_yvalue + (maxViewFrequency-minViewFrequency)*0.005, "{:.2f} Hz".format(pitchArray[index_of_max]), fontproperties=global_values.text_fontproperties, size=global_values.text_font_size, color='red', horizontalalignment='center', path_effects=[pe.Stroke(linewidth=0.45, foreground='k'), pe.Normal()], zorder=2) prev_index_of_max = index_of_max # Make sure we keep track of which maximum pitch has been plotted maximum_counter += 1 ##----------------------------------------------------------------------## ### Setup the x-axis ##----------------------------------------------------------------------## # Set minimum and maximum x-axis values the_plot.set_xlim([float("{:.4f}".format(startTime)), float("{:.4f}".format(endTime))]) # Only allow x-axis ticks for the start and end times plt.xticks([float("{:.4f}".format(startTime)), float("{:.4f}".format(endTime))], [float("{:.4f}".format(startTime)), float("{:.4f}".format(endTime))]) ##----------------------------------------------------------------------## ### Setup the y-axis ##----------------------------------------------------------------------## # Set minimum and maximum x-axis values the_plot.set_ylim(minViewFrequency, maxViewFrequency) # Set up y-axis ticks to always display maximum and minimum values ### and also have an a calculated interval (i.e. yaxisInterval) plt.yticks(numpy.arange(minViewFrequency, maxViewFrequency+yaxisInterval, yaxisInterval))
def scatterPlotFormants( wav_file_path, maxFormantFrequency=5500, #Praat default is 5500 Hz windowLength=0.025, #Praat default is 0.025 dynamicRange=30, #Praat default is 30 dB showTextgridFormants=False, ax=None): #################################################################################### ### Check and fix arguments for problems OR to set default values (if necessary) #################################################################################### ##----------------------------------------------------------------------## ### No audio file specified... can't do anything ##----------------------------------------------------------------------## if wav_file_path is None or wav_file_path == "": raise Exception( "praatFormants.py->scatterPlotFormants: No audio filename provided" ) ##----------------------------------------------------------------------## ### Check that the specified file actually exists ##----------------------------------------------------------------------## global_path_helper.verify_file_exists(wav_file_path) # assemble a Praat script to analyze the formants of the file. Make sure that # you add a backslach in front of every single quote of your Praat script (i.e., # every ' turns into /' - this does not apply here, since the Praat script below # does not contain any single quotes). Also, add a new line (backslash n) at the # end of every line in the script # # In particular, we'll create the script below. Note how the path and file names # are being replaced by variables, so that you can easily change them. # # do ("Read from file...", "/Users/ch/data/programming/python/lib/demo/AEIOU_vocalFry.wav") # do ("To Formant (burg)...", 0, 5, 5000, 0.025, 50) # do ("Save as short text file...", "/Users/ch/data/programming/python/lib/demo/AEIOU_vocalFry.Formant") if maxFormantFrequency == None: maxFormantFrequency = 5500 if windowLength == None: windowLength = 0.025 if dynamicRange == None: dynamicRange = 12.5 wav_dir, filename_only, _ = global_path_helper.split_path_filename_extension( wav_file_path) formant_file_path = wav_dir + filename_only + '.Formant' textgrid_file_path = wav_dir + filename_only + '.TextGrid' script = '' script += 'do ("Read from file...", "' + wav_file_path + '")\n' #five arguments: ##### the time step, ##### the maximum number of formants, ##### the maximum hertz, ##### the window length, ##### Pre-emphasis from (Hz) script += 'do ("To Formant (burg)...", 0, 5, ' + '{:.2f},'.format( maxFormantFrequency) + ' 0.025, 50)\n' script += 'do ("Save as short text file...", "' + str( formant_file_path) + '")\n' scriptFileName = 'tmp_formants.praat' praatUtil.runPraatScript(script, scriptFileName) # read the generated Praat formants file formants = praatUtil.PraatFormants() formants.readFile(formant_file_path) if showTextgridFormants: #First, verify .TextGrid file exists # read the accompanying Praat text grid (see the Praat TextGrid example for an # extended documentation). We expect a TextGrid that contains one IntervalTier # lableled 'vowels'. Within this IntervalTier, the occurring vowels are indicated textGrid = praatTextGrid.PraatTextGrid(0, 0) textGridFile = pathlib.Path(textgrid_file_path) if not textGridFile.exists(): showTextgridFormants = False warnings.warn("File '" + str(textgrid_file_path) + "' does not exist.\n") warnings.warn( "Continuing to show ALL formants (despite flag for .TextGrid file being true)\n" ) if showTextgridFormants: arrTiers = textGrid.readFromFile(textgrid_file_path) numTiers = len(arrTiers) if numTiers != 1: raise Exception("we expect exactly one Tier in this file") tier = arrTiers[0] if tier.getName() != 'vowels': raise Exception("unexpected tier") # parse the TextGrid: create a dictionary that stores a list of start and end # times of all intervals where that particular vowel occurs (that way we'll # cater for multiple occurrances of the same vowel in a file, should that ever # happen) arrVowels = {} for i in range(tier.getSize()): if tier.getLabel(i) != '': interval = tier.get(i) vowel = interval[2] if not vowel in arrVowels: arrVowels[vowel] = [] tStart, tEnd = interval[0], interval[1] arrVowels[vowel].append([tStart, tEnd]) n = formants.getNumFrames() arrFormants = {} arrGraphData = {} xtimes = [] yfrequency = [] intensities = [] for formantFrames in formants.getAllFormants(): for formant_dict in formantFrames: intensities.append(formant_dict.get('intensity_dB')) max_intensity = numpy.amax(intensities) intensities = None for i in range(n): t, formantData = formants.get(i) if showTextgridFormants: #SHOW ONLY TEXTGRID FORMANTS # loop over all vowels and all intervals for each vowel for vowel in arrVowels: for tStart, tEnd in arrVowels[vowel]: if t >= tStart and t <= tEnd: for formant in formantData: xtimes.append(t) yfrequency.append(formant['frequency']) else: #SHOW ALL FORMANTS for index, formant in enumerate(formantData): if index > 0 and formant[ 'intensity_dB'] > max_intensity - dynamicRange / 2: xtimes.append(t) yfrequency.append(formant['frequency']) if ax == None: plt.scatter(numpy.array(xtimes), numpy.array(yfrequency), marker="D", s=12, c='w', edgecolor='b', linewidth=0.65, zorder=3) else: ax.scatter(numpy.array(xtimes), numpy.array(yfrequency), marker="D", s=12, c='w', edgecolor='b', linewidth=0.65, zorder=3)