class FormantPlot: def __init__(self, parent, path, root, plotID, formApp): self.plotHeight = PLOTHEIGHT self.plotWidth = PLOTWIDTH self.root = root self.formApp = formApp self.vowelType = formApp.vowelType self.parent = parent self.path = path self.id = plotID self.buttonCounter = 0 if plotID == 0 or plotID == 1: self.gender = "male" else: self.gender = "female" self.hasFormants = False #plot Setup: self.updateRate = 30 self.setupPlot() self.drawGoldStandardMonophthongs(True) self.createKey() self.createAxis() self.prevX = 0 self.prevY = 0 # sound Setup: self.initialiseSounds() self.formantRadius = 2 self.formantColour = 'black' self.isRecording = False self.lineHiddens = False self.loadedPlots = False self.hasloadedPlots = False self.backgroundNoiseDistance = 50 self.count = 0 self.notStopped = False def setupPlot(self): #Plot self.formantPlotFrame = self.parent # self.formantPlotCanvas = Canvas(self.formantPlotFrame, height=PLOTHEIGHT-60, width=PLOTWIDTH, bg='white') self.formantPlotCanvas = Canvas(self.formantPlotFrame, height=PLOTHEIGHT, width=PLOTWIDTH, bg='white') #self.formantPlotCanvas.grid(row=0 ,column=0, sticky=N+S+E+W) self.formantPlotCanvas.pack(fill=BOTH, expand=YES) self.formantPlotCanvas.bind("<Button-1>", self.requestRecord) #Creates the loudnessMeter on the formant plot canvas. self.loudnessMeter = LoudnessMeter(self.formantPlotCanvas, YSHIFT) self.createBoxs() def createButtons(self): self.buttonCounter += 1 self.formantPlotCanvas.delete('buttons') height = (int)((float)(2) * (self.plotHeight / PLOTHEIGHT)) font = ('Arial', '12') self.formantPlotCanvas.create_rectangle(0, self.plotHeight - height * 30 - 8, self.plotWidth + 10, self.plotHeight + 10, tags='buttons', fill='#d3d3d3') text = " Back to Menu " helpButton = Button(self.parent, text=text, command=self.goBackToMenu, font=font) helpButton.configure(height=height, activebackground='green', anchor=W, relief=GROOVE) helpButtonWindow = self.formantPlotCanvas.create_window( 2, self.plotHeight - 4 * (self.plotHeight / PLOTHEIGHT), anchor=SW, tags=('backButton', 'buttons'), window=helpButton) text = text = 14 * " " + "Record" + 14 * " " self.recordButton = Button(self.parent, text=text, font=font, command=self.requestRecord) self.recordButton.configure(height=height, activebackground='green', anchor=W, relief=GROOVE, bd=3) self.formantPlotCanvas.create_window( (self.plotWidth / 2), self.plotHeight - 4 * (self.plotHeight / PLOTHEIGHT) - 25, anchor=CENTER, tags=('recordButton', 'buttons'), window=self.recordButton) if self.buttonCounter > 2 and self.formantPlotCanvas.winfo_height( ) <= 580: self.formantPlotCanvas.delete("recordButton") self.formantPlotCanvas.create_window( (self.plotWidth / 2), self.plotHeight - 4 * (self.plotHeight / PLOTHEIGHT) - 15, anchor=CENTER, tags=('recordButton', 'buttons'), window=self.recordButton) if self.vowelType == 'short': text = " Short Vowels " else: text = ' Long Vowels ' self.vowelButton = Button(self.parent, text=text, font=font, command=self.toggleVowelType) self.vowelButton.configure(height=height, activebackground='green', anchor=W, relief=GROOVE, bd='3') self.formantPlotCanvas.create_window(self.plotWidth, self.plotHeight - 4 * (self.plotHeight / PLOTHEIGHT), anchor=SE, tags=('vowelButton', 'buttons'), window=self.vowelButton) self.formantPlotCanvas.itemconfig('buttons', state='normal') def goBackToMenu(self): self.formApp.backToMenu() def toggleVowelType(self, *event): if self.isRecording == False: self.formApp.toggleVowelType() def loadHelp(self): pass def createBoxs(self): self.formantPlotCanvas.delete('boxes') boxWidth = 150 font = ('Arial', '15') x1 = (PLOTWIDTH / 2) * (self.plotWidth / PLOTWIDTH) - (boxWidth) y1 = 2 x2 = (PLOTWIDTH / 2) * (self.plotWidth / PLOTWIDTH) y2 = 32 self.recordingBox = self.formantPlotCanvas.create_rectangle( x1, y1, x2, y2, tag=('boxes', 'Recording'), fill='orange', outline='black') self.recordingBoxText = self.formantPlotCanvas.create_text( (x1 + x2) / 2, (y1 + y2) / 2, tag=('boxes', 'recordingText'), text='Loading...', font=font, fill='black') self.formantPlotCanvas.itemconfig('Recording', state='hidden') self.formantPlotCanvas.itemconfig('recordingText', state='hidden') x1 = (PLOTWIDTH / 2) * (self.plotWidth / PLOTWIDTH) y1 = 2 x2 = (PLOTWIDTH / 2) * (self.plotWidth / PLOTWIDTH) + boxWidth y2 = 32 self.recordingBox = self.formantPlotCanvas.create_rectangle( x1, y1, x2, y2, tag=('boxes', 'Loudness'), outline='black') self.recordingBoxText = self.formantPlotCanvas.create_text( (x1 + x2) / 2, (y1 + y2) / 2, tag=('boxes', 'toLoud'), text='Too Loud.', font=font, fill='black') self.recordingBoxText = self.formantPlotCanvas.create_text( (x1 + x2) / 2, (y1 + y2) / 2, tag=('boxes', 'toQuiet'), text='Too Quiet.', font=font, fill='black') self.formantPlotCanvas.itemconfig('toLoud', state='hidden') self.formantPlotCanvas.itemconfig('toQuiet', state='hidden') self.formantPlotCanvas.itemconfig('Loudness', state='hidden') def resize(self, width, height): print self.formantPlotCanvas.winfo_height() print self.formantPlotCanvas.winfo_width() self.plotWidth = (float)(width) self.plotHeight = (float)(height) self.drawGoldStandardMonophthongs(True) self.redrawPlotInfo() self.createBoxs() self.createButtons() def createKey(self, ): self.formantPlotCanvas.delete("legend") font = ('Arial', '13') radius = 4 x = self.plotWidth - 160 y = 15 self.formantPlotCanvas.create_text(x + 80, y, text="Key:", tag='legend', font=font) self.formantPlotCanvas.create_oval(x - radius, y + 20 - radius, x + radius, y + 20 + radius, tag='legend', fill='black') self.formantPlotCanvas.create_text(x + 80, y + 20, text='Recorded Vowels', tag='legend', font=font) self.formantPlotCanvas.create_oval(x - radius, y + 40 - radius, x + radius, y + 40 + radius, tag='legend', fill='yellow') self.formantPlotCanvas.create_text(x + 80, y + 40, text='Loaded Vowels', tag='legend', font=font) self.formantPlotCanvas.create_line(x - 15, 0, x - 15, y + 50, tag='legend') self.formantPlotCanvas.create_line(x - 15, y + 50, self.plotWidth + 50, y + 50, tag='legend') def createAxis(self): self.formantPlotCanvas.delete("formantAxes") xFrameScale = (self.plotWidth / PLOTWIDTH) yFrameScale = (self.plotHeight / PLOTHEIGHT) self.formantPlotCanvas.create_line( XSHIFT * xFrameScale, (self.plotHeight - YSHIFT * yFrameScale), self.plotWidth, (self.plotHeight - YSHIFT * yFrameScale), tags="formantAxes") yAxisCount = 15 yIncrement = ((self.plotHeight - YSHIFT) / yAxisCount) for yIndex in range(yAxisCount): y = yIndex * yIncrement self.formantPlotCanvas.create_line(XSHIFT * xFrameScale - 8, y, XSHIFT * xFrameScale, y, tags="formantAxes") self.formantPlotCanvas.create_line(XSHIFT * xFrameScale, 0, XSHIFT * xFrameScale, self.plotHeight - YSHIFT * yFrameScale, tags="formantAxes") xAxisCount = 20 xIncrement = (self.plotWidth - XSHIFT * xFrameScale) / xAxisCount for xIndex in range(xAxisCount): x = xIndex * xIncrement self.formantPlotCanvas.create_line( XSHIFT * xFrameScale + x, self.plotHeight - YSHIFT * yFrameScale + 8, XSHIFT * xFrameScale + x, self.plotHeight - YSHIFT * yFrameScale, tags="formantAxes") self.formantPlotCanvas.create_text( (XSHIFT * xFrameScale + self.plotWidth) / 2, self.plotHeight - YSHIFT * yFrameScale + 20, text="Tongue Position", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text(XSHIFT * xFrameScale + 22, self.plotHeight - YSHIFT * yFrameScale + 15, text="Front", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text(self.plotWidth - 30, self.plotHeight - YSHIFT * yFrameScale + 15, text="Back", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text( XSHIFT * xFrameScale / 2, (self.plotHeight - YSHIFT * yFrameScale) / 2, text="Mouth", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text( XSHIFT * xFrameScale / 2, ((self.plotHeight - YSHIFT * yFrameScale) / 2) + 15, text="Openness", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text(XSHIFT * xFrameScale / 2, self.plotHeight - YSHIFT * yFrameScale - 15, text="Open", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text(XSHIFT * xFrameScale / 2, 20, text="Closed", font=('Arial', '13'), tags="formantAxes") def drawGoldStandardMonophthongs(self, toFill): self.formantPlotCanvas.delete("ellipses") vowelType = self.vowelType (xScale, xShift, yScale, yShift) = self.getPlotScaleInfo(vowelType) data = self.goldStandardMonophthongs(self.id, vowelType) xFrameScale = (self.plotWidth / PLOTWIDTH) yFrameScale = (self.plotHeight / PLOTHEIGHT) std = 1 for f1mean, f1sd, f2mean, f2sd, vowel in data: font = ('Arial', '20', 'bold') x1 = ((f2mean - std * f2sd) * xScale - xShift) * xFrameScale y1 = ((f1mean - std * f1sd) * yScale - yShift) * yFrameScale x2 = ((f2mean + std * f2sd) * xScale - xShift) * xFrameScale y2 = ((f1mean + std * f1sd) * yScale - yShift) * yFrameScale if toFill: currentOval = self.formantPlotCanvas.create_oval( x1, y1, x2, y2, outline='black', tag='ellipses', fill='#ffffff', activefill='#e3c5d6') else: currentOval = self.formantPlotCanvas.create_oval( x1, y1, x2, y2, outline='black', tag='ellipses', activefill='#e3c5d6') ovalText = self.formantPlotCanvas.create_text( (f2mean * xScale - xShift) * xFrameScale, (f1mean * yScale - yShift) * yFrameScale, fill='red', font=font, tag='ellipses', text=vowel) #creating the overlap for f1mean, f1sd, f2mean, f2sd, vowel in data: x1 = ((f2mean - std * f2sd) * xScale - xShift) * xFrameScale y1 = ((f1mean - std * f1sd) * yScale - yShift) * yFrameScale x2 = ((f2mean + std * f2sd) * xScale - xShift) * xFrameScale y2 = ((f1mean + std * f1sd) * yScale - yShift) * yFrameScale currentOval = self.formantPlotCanvas.create_oval(x1, y1, x2, y2, outline='black', tag='ellipses') def redrawPlotInfo(self): self.drawGoldStandardMonophthongs(False) self.createAxis() self.createKey() self.replotFormants() self.replotloadedPlots() def getPlotScaleInfo(self, vowelType): plotID = self.id if plotID == 0: if vowelType == 'short': xScale = 1.5 xShift = 400 yScale = 2 yShift = 100 else: xScale = 1.5 xShift = 400 yScale = 2 yShift = 100 elif plotID == 1: if vowelType == 'short': xScale = 1.5 xShift = 400 yScale = 3 yShift = 200 else: xScale = 1.5 xShift = 400 yScale = 3 yShift = 200 elif plotID == 2: if vowelType == 'short': xScale = 1.2 xShift = 130 yScale = 1.8 yShift = 130 else: xScale = 1.2 xShift = 130 yScale = 1.8 yShift = 130 elif plotID == 3: if vowelType == 'short': xScale = 1.2 xShift = 150 yScale = 2.3 yShift = 175 else: xScale = 1.2 xShift = 150 yScale = 2.3 yShift = 175 else: print "ERROR ID INVALID" xScale = 0 xShift = 0 yScale = 0 yShift = 0 return (xScale, xShift, yScale, yShift) def goldStandardMonophthongs(self, plotID, vowelType): path = self.path if plotID == 0: if vowelType == 'short': data = self.appendData(path, 'data\maori\monDataNativeMale.txt') else: data = self.appendData(path, 'data\maori\longVowelNativeMale.txt') elif plotID == 1: if vowelType == 'short': data = self.appendData(path, 'data\maori\monDataModernMale.txt') else: data = self.appendData(path, 'data\maori\longVowelModernMale.txt') elif plotID == 2: if vowelType == 'short': data = self.appendData(path, 'data\maori\monDataNativeFemale.txt') else: data = self.appendData(path, 'data\maori\longVowelNativeFemale.txt') elif plotID == 3: if vowelType == 'short': data = self.appendData(path, 'data\maori\monDataModernFemale.txt') else: data = self.appendData(path, 'data\maori\longVowelModernFemale.txt') return data def appendData(self, pat, str): MonophthongDataPath = os.path.join(pat, str) MonophthongFile = open(MonophthongDataPath, 'r') MonophthongData = [] #Code from ywan478 2010 SoftEng206 Project #Load in the monophthong data for males for monophthongDataLine in MonophthongFile: monophthongDataLine = monophthongDataLine.split() #F1 mean and standard deviation monophthongDataLine[0] = (int(float(monophthongDataLine[0])) / SCALEDOWN) monophthongDataLine[1] = (int(float(monophthongDataLine[1])) / SCALEDOWN) #F2 mean and standard deviation monophthongDataLine[2] = PLOTWIDTH - (int( float(monophthongDataLine[2])) / SCALEDOWN) + XSHIFT + XOFFSET monophthongDataLine[3] = (int(float(monophthongDataLine[3])) / SCALEDOWN) MonophthongData.append(monophthongDataLine) #End code from ywan478 return MonophthongData #*********************************************************************************************************** # Sound ''' Creates the Snack Sound objects used in the formant plot. ''' def initialiseSounds(self): tkSnack.initializeSnack(self.root) self.recordedAudio = Sound() self.soundCopy = Sound() self.loadedAudio = Sound() def replotFormants(self): if (self.hasFormants == True): self.formantPlotCanvas.delete("userformants") radius = self.formantRadius color = self.formantColour for index in range(len(self.xPlotList)): x = self.xPlotList[index] y = self.yPlotList[index] self.formantPlotCanvas.create_oval( x * (self.plotWidth / PLOTWIDTH) - radius, y * (self.plotHeight / PLOTHEIGHT) - radius, x * (self.plotWidth / PLOTWIDTH) + radius, y * (self.plotHeight / PLOTHEIGHT) + radius, fill=color, tags="userformants") def replotloadedPlots(self): if (self.hasloadedPlots == True): self.formantPlotCanvas.delete("loadedPlots") self.formantPlotCanvas.delete("loadedLines") radius = self.formantRadius color = 'yellow' prevX = self.xLoadedPlotList[0] prevY = self.yLoadedPlotList[0] for index in range(len(self.xLoadedPlotList)): x = self.xLoadedPlotList[index] y = self.yLoadedPlotList[index] xScaled = x * (self.plotWidth / PLOTWIDTH) yScaled = y * (self.plotHeight / PLOTHEIGHT) prevXScaled = prevX * (self.plotWidth / PLOTWIDTH) prevYScaled = prevY * (self.plotHeight / PLOTHEIGHT) self.formantPlotCanvas.create_oval( x * (self.plotWidth / PLOTWIDTH) - radius, y * (self.plotHeight / PLOTHEIGHT) - radius, x * (self.plotWidth / PLOTWIDTH) + radius, y * (self.plotHeight / PLOTHEIGHT) + radius, fill=color, tags="loadedPlots") if ((((x - prevX)**2 + (y - prevY)**2)**(0.5)) < self.backgroundNoiseDistance): self.formantPlotCanvas.create_line(prevXScaled, prevYScaled, xScaled, yScaled, tags='loadedLines') prevX = x prevY = y """ Plot Fomrants takes a sound file and plots the last formant in the file. """ def plotFormants(self, sound): #Gets the probablity of sound being a voice for the last formant in the sound file. (false means last formant, true means all formants) probabilityOfVoicing = SoundProcessing.getProbabilityOfVoicing( sound, False) if probabilityOfVoicing == 1.0: formant = SoundProcessing.getFormants(sound, self.id, False) #Only plot the latest formants of the sound if there's a good chance that it is the user speaking instead of background noise. if formant != None: (xScale, xShift, yScale, yShift) = self.getPlotScaleInfo(self.vowelType) #GetUser default or user defined colour and radius for the individual formants radius = self.formantRadius color = self.formantColour yPreScale = formant[0] / SCALEDOWN xPreScale = PLOTWIDTH - (formant[1] / SCALEDOWN) + XSHIFT + XOFFSET x = (xPreScale * xScale - xShift) y = (yPreScale * yScale - yShift) self.count += 1 #Only plot of the Points are on the Grid. They can go over the axis though. if (x > 0) and (y < PLOTHEIGHT): distance = (((x - self.prevX)**2 + (y - self.prevY)**2)**(0.5)) if (distance < self.backgroundNoiseDistance) and distance > 0: self.hasFormants = True self.formantPlotCanvas.create_oval( x * (self.plotWidth / PLOTWIDTH) - radius, y * (self.plotHeight / PLOTHEIGHT) - radius, x * (self.plotWidth / PLOTWIDTH) + radius, y * (self.plotHeight / PLOTHEIGHT) + radius, fill=color, tags="userformants") self.xPlotList.append(x) self.yPlotList.append(y) self.prevX = x self.prevY = y if not self.isRecording: if formant != None: pass """ Determines if to record or stop. """ def requestRecord(self, *event): if self.isRecording: if self.isLoading: pass else: self.stop() else: self.record() """ record is called when the record button is pressed it starts recording the users sounds and makes the formant plot react accordingly. """ def record(self): self.isLoading = True text = 15 * " " + "Stop" + 15 * " " self.recordButton.config(text=text, bg='orange', state='disabled') self.vowelButton.config(state='disabled') self.hasFormants = True self.xPlotList = [] self.yPlotList = [] self.root.resizable( False, False ) #DISALLOWS the window to be resized both verically and horizonally. self.notStopped = True self.recordedAudio.record() self.isRecording = True #self.stopButton.config(state='normal') #self.clearScreenButton.config(state='disabled') self.formantPlotCanvas.itemconfig('Recording', state='normal', fill='orange') self.formantPlotCanvas.itemconfig('recordingText', state='normal', text='Loading...') thread.start_new_thread(self.multiThreadUpdateCanvas, ("Thread-1", self.notStopped)) self.count2 = 0 def multiThreadUpdateCanvas(self, threadName, notStopped): self.clear() sleep(1.2) self.isLoading = False text = 15 * " " + "Stop" + 15 * " " self.recordButton.config(text=text, bg='#CE2029', state='normal') self.formantPlotCanvas.itemconfig('Recording', fill='red') self.formantPlotCanvas.itemconfig('recordingText', text='Recording') try: while self.notStopped: self.count2 += 1 self.soundCopy.copy(self.recordedAudio) SoundProcessing.crop(self.soundCopy) self.plotFormants(self.soundCopy) if self.count2 % 10 == 0: self.updateLoudnessMeter(self.soundCopy) except Exception: import traceback print traceback.format_exc() def stop(self): self.isRecording = False self.recordButton.config(text=" Record ", bg='white') self.notStopped = False self.formantPlotCanvas.itemconfig('boxes', state='hidden') self.loudnessMeter.clearMeter self.recordedAudio.stop() self.root.after(200, self.removeLoudness) self.count = 0 self.root.resizable( True, True ) #Allows the window to be resized both verically and horizonally. self.vowelButton.config(state='normal') def removeLoudness(self): self.loudnessMeter.clearMeter() self.formantPlotCanvas.itemconfig('toLoud', state='hidden') self.formantPlotCanvas.itemconfig('toQuiet', state='hidden') self.formantPlotCanvas.itemconfig('Loudness', state='hidden') """ Clears the screen of all plots, lines and loaded plots. """ def clear(self): self.hasFormants = False self.hasloadedPlots = False self.formantPlotCanvas.delete('userformants') self.formantPlotCanvas.delete('loadedPlots') self.formantPlotCanvas.delete('loadedLines') def changeColor(self): self.formantColour = 'pink' ''' Update the formant plot and loudness meter if the user is still recording. ''' def updateAllCanvases(self): self.soundCopy.copy(self.recordedAudio) SoundProcessing.crop(self.soundCopy) self.plotFormants(self.soundCopy) self.updateLoudnessMeter(self.soundCopy) if self.isRecording: self.updateAllCanvases() def updateLoudnessMeter(self, sound): try: self.loudnessMeter.updateMeter(sound) except IndexError: pass def playAudioFile(self): self.loadedAudio.play() def loadAudioFile(self): self.formantPlotCanvas.delete("loadedLines") os.chdir("../..") self.xLoadedPlotList = [] self.yLoadedPlotList = [] self.loadedPlots = True print os.getcwd() filename = tkFileDialog.askopenfilename(initialdir="Recording") radius = self.formantRadius self.formantPlotCanvas.delete('loadedPlots') self.loadedAudio.config(load=filename) os.chdir("Formant/dist") import wave import contextlib fname = filename with contextlib.closing(wave.open(fname, 'r')) as f: frames = f.getnframes() rate = f.getframerate() duration = float(frames) / float(rate) loadedPlots = SoundProcessing.getFormants(self.loadedAudio, self.gender, True) probabilityOfVoicingList = SoundProcessing.getProbabilityOfVoicing( self.loadedAudio, True) self.formantPlotCanvas.delete('loadedPlots') sleeptime = float(duration) / len(probabilityOfVoicingList) thread.start_new_thread( self.multiThreadLoader, ("Thread-2", sleeptime, probabilityOfVoicingList, loadedPlots)) def multiThreadPlayer(self, threadName, sound): sound.play() ''' multiThreadLoader plots the formant plots onto the formant plot using an additional thread to allow the application not to freeze especially if the audio file is very large. ''' def multiThreadLoader(self, threadName, delay, probabilityOfVoicingList, loadedPlots): self.hasloadedPlots = True self.count = 0 try: radius = self.formantRadius for i in range(0, len(loadedPlots)): probabilityOfVoicing = probabilityOfVoicingList[i] if probabilityOfVoicing == 1.0: formant = loadedPlots[i] latestF1 = formant[0] / SCALEDOWN latestF2 = PLOTWIDTH - (formant[1] / SCALEDOWN) + XSHIFT + XOFFSET (xScale, xShift, yScale, yShift) = self.getPlotScaleInfo(self.vowelType) if latestF2 > XSHIFT and latestF1 < PLOTHEIGHT - YSHIFT: self.prevplotted = True self.prevX = latestF2 * xScale - xShift self.prevY = latestF1 * yScale - yShift break for i in range(0, len(loadedPlots)): probabilityOfVoicing = probabilityOfVoicingList[i] if probabilityOfVoicing == 1.0: formant = loadedPlots[i] latestF1 = formant[0] / SCALEDOWN latestF2 = PLOTWIDTH - (formant[1] / SCALEDOWN) + XSHIFT + XOFFSET (xScale, xShift, yScale, yShift) = self.getPlotScaleInfo(self.vowelType) if latestF2 > XSHIFT and latestF1 < PLOTHEIGHT - YSHIFT: x = latestF2 * xScale - xShift y = latestF1 * yScale - yShift if ((((x - self.prevX)**2 + (y - self.prevY)**2) **(0.5)) < self.backgroundNoiseDistance): self.xLoadedPlotList.append(x) self.yLoadedPlotList.append(y) x1 = x * (self.plotWidth / PLOTWIDTH) - radius y1 = y * (self.plotHeight / PLOTHEIGHT) - radius x2 = x * (self.plotWidth / PLOTWIDTH) + radius y2 = y * (self.plotHeight / PLOTHEIGHT) + radius if (self.prevplotted == True): self.formantPlotCanvas.create_line( self.prevX * (self.plotWidth / PLOTWIDTH), self.prevY * (self.plotHeight / PLOTHEIGHT), x * (self.plotWidth / PLOTWIDTH), y * (self.plotHeight / PLOTHEIGHT), fill='black', tags='loadedLines') pass xCoord = x1 self.formantPlotCanvas.create_oval( x1, y1, x2, y2, fill='yellow', tags="loadedPlots") self.prevplotted = True self.count += 1 else: self.prevplotter = False self.prevX = x self.prevY = y except Exception: import traceback print traceback.format_exc() self.formantPlotCanvas.itemconfig("loadedLines", state='hidden') self.lineHiddens = True self.redrawPlotInfo() self.count = 0 def toggleLoadedPlots(self): if self.loadedPlots: self.formantPlotCanvas.itemconfig("loadedPlots", state="hidden") self.loadedPlots = False else: self.formantPlotCanvas.itemconfig("loadedPlots", state="normal") self.loadedPlots = True def toggleLines(self): if self.lineHiddens: self.formantPlotCanvas.itemconfig("loadedLines", state="normal") self.lineHiddens = False else: self.formantPlotCanvas.itemconfig("loadedLines", state="hidden") self.lineHiddens = True def toggleBackgroundNoise(self, level): if level == 0: self.backgroundNoiseDistance = 10000 elif level == 1: self.backgroundNoiseDistance = 100 elif level == 2: self.backgroundNoiseDistance = 50 elif level == 3: self.backgroundNoiseDistance = 30 else: self.backgroundNoiseDistance = 10 def callback(self, event): print "canvas clicked"
class FormantPlot: ''' Initialise the formant plot. Arguments: parent - the widget that the formant plot is contained in. path - the directory path where the application is in. root - the Tk widget which is the base of all the other widgets. gender - user's gender ''' def __init__(self, parent, path, root,gender): self.root = root self.parent = parent self.gender=gender self.appDirectory = path self.setUpWidgets() self.getGoldStandardMonophthongs(path) self.drawGoldStandardMonophthongs(gender) self.drawFormantPlotAxes() self.drawFormantPlotLegend() self.initialiseSounds() self.initialiseKeyboardShortcuts() self.isRecording=False self.previousF1 = 0 self.previousF2 = 0 ''' Initialises all the widgets that are associated with the formant plot. ''' def setUpWidgets(self): self.formantPlotFrame = Frame(self.parent, width=FORMANTPLOTFRAMEWIDTH, height=FORMANTPLOTFRAMEHEIGHT) self.formantPlotFrame.grid() self.formantPlotCanvas = SnackCanvas(self.formantPlotFrame, height=FORMANTPLOTHEIGHT, width=FORMANTPLOTWIDTH, bg='white') self.formantPlotCanvas.grid(row=0,column=0) self.loudnessMeterCanvas = SnackCanvas(self.formantPlotFrame, height=FORMANTPLOTHEIGHT, width=FORMANTPLOTFRAMEWIDTH-FORMANTPLOTWIDTH, bg='white') self.loudnessMeterCanvas.grid(row=0,column=1) self.formantPlotControlFrame = Frame(self.formantPlotFrame) self.formantPlotControlFrame.grid(row=1,column=0,sticky='w'+'e',columnspan=2) self.formantPlotControlFrame.columnconfigure(0, minsize=FORMANTPLOTWIDTH/3) self.formantPlotControlFrame.columnconfigure(1, minsize=FORMANTPLOTWIDTH/3) self.formantPlotControlFrame.columnconfigure(2, minsize=FORMANTPLOTWIDTH/3) self.formantPlotControlFrame.columnconfigure(3, minsize=FORMANTPLOTFRAMEWIDTH-FORMANTPLOTWIDTH) self.isLabelsOn = BooleanVar() self.toggleLabelCheckbox = Checkbutton(self.formantPlotControlFrame,text='Show Labels',variable=self.isLabelsOn,onvalue=True,offvalue=False,state='active',command=(lambda:self.togglePlot(self.isLabelsOn.get(),'ellipseLabel'))) self.toggleLabelCheckbox.grid(row=0,column=0) self.toggleLabelCheckbox.select() self.isEllipsesOn = BooleanVar() self.toggleEllipsesCheckbox = Checkbutton(self.formantPlotControlFrame,text='Show Ellipses',variable=self.isEllipsesOn,onvalue=True,offvalue=False,state='active',command=(lambda:self.togglePlot(self.isEllipsesOn.get(),'ellipses'))) self.toggleEllipsesCheckbox.grid(row=0,column=1) self.toggleEllipsesCheckbox.select() self.isLoadedFormantsOn = BooleanVar() self.toggleLoadedFormantsCheckbox = Checkbutton(self.formantPlotControlFrame,text='Show Loaded Formants',variable=self.isLoadedFormantsOn,onvalue=True,offvalue=False,state='active',command=(lambda:self.togglePlot(self.isLoadedFormantsOn.get(),'loadedformants'))) self.toggleLoadedFormantsCheckbox.grid(row=0,column=2) self.toggleLoadedFormantsCheckbox.select() self.recButton = ttk.Button(self.formantPlotControlFrame, text='Record', command=self.record,style='Fun.TButton') self.recButton.grid(row=1,column=0,sticky='w'+'e',padx=10) self.stopButton = ttk.Button(self.formantPlotControlFrame, text='Stop', command=self.stop,state='disabled',style='Fun.TButton') self.stopButton.grid(row=1,column=1,sticky='w'+'e',padx=10) self.playRecordButton = ttk.Button(self.formantPlotControlFrame, text='Play Last Recorded Sound', command=(lambda:(self.play(self.recordedAudio))),state='disabled',style='Fun.TButton') self.playRecordButton.grid(row=1,column=2,sticky='w'+'e',padx=10) self.loadAudioButton = ttk.Button(self.formantPlotControlFrame, text='Load Audio File', command=self.loadAudioFile,state='normal',style='Fun.TButton') self.loadAudioButton.grid(row=2,column=1,sticky='w'+'e',padx=10) self.saveButton = ttk.Button(self.formantPlotControlFrame, text='Save', command=self.save,state='disabled',style='Fun.TButton') self.saveButton.grid(row=2,column=0,sticky='w'+'e',padx=10) self.playLoadButton = ttk.Button(self.formantPlotControlFrame, text='Play Loaded File', command=(lambda:(self.play(self.loadedAudio))),state='disabled',style='Fun.TButton') self.playLoadButton.grid(row=2,column=2,sticky='w'+'e',padx=10) self.clearScreenButton = ttk.Button(self.formantPlotControlFrame, text='Clear Plot', command=self.clearUserFormants,state='normal',style='Fun.TButton') self.clearScreenButton.grid(row=1,column=3,sticky='w'+'e'+'n'+'s', rowspan=2) self.loudnessMeter = LoudnessMeter(self.loudnessMeterCanvas) ''' Creates the Snack Sound objects used in the formant plot. ''' def initialiseSounds(self): initializeSnack(self.root) self.recordedAudio = Sound() self.soundCopy = Sound() self.loadedAudio = Sound() def initialiseKeyboardShortcuts(self): self.root.bind('<space>',self.executeSpacebarInput) ''' Draw the axes for the formant plot. ''' def drawFormantPlotAxes(self): #X axis self.formantPlotCanvas.create_line(XSHIFT,FORMANTPLOTHEIGHT-YSHIFT,FORMANTPLOTWIDTH,FORMANTPLOTHEIGHT-YSHIFT, tags="formantAxes") #Y axis self.formantPlotCanvas.create_line(XSHIFT,0,XSHIFT,FORMANTPLOTHEIGHT-YSHIFT, tags="formantAxes") #X axis label self.formantPlotCanvas.create_text((XSHIFT+FORMANTPLOTWIDTH)/2, FORMANTPLOTHEIGHT-YSHIFT+30,text = "Tongue Position", tags="formantAxes") self.formantPlotCanvas.create_text(XSHIFT+12, FORMANTPLOTHEIGHT-YSHIFT+10,text = "Front", tags="formantAxes") self.formantPlotCanvas.create_text(FORMANTPLOTWIDTH-15, FORMANTPLOTHEIGHT-YSHIFT+10,text = "Back", tags="formantAxes") #Y axis label self.formantPlotCanvas.create_text(XSHIFT/2, (FORMANTPLOTHEIGHT-YSHIFT)/2,text = "Mouth", tags="formantAxes") self.formantPlotCanvas.create_text(XSHIFT/2, ((FORMANTPLOTHEIGHT-YSHIFT)/2)+20,text = "Openness", tags="formantAxes") self.formantPlotCanvas.create_text(XSHIFT/2, FORMANTPLOTHEIGHT-YSHIFT-10,text = "Open", tags="formantAxes") self.formantPlotCanvas.create_text(XSHIFT/2, 10,text = "Closed", tags="formantAxes") def drawFormantPlotLegend(self): self.formantPlotCanvas.create_oval(FORMANTPLOTWIDTH-120-3,10-3,FORMANTPLOTWIDTH-120+3,10+3,tag='legend',fill='black') self.formantPlotCanvas.create_text(FORMANTPLOTWIDTH-60,10,text='Recorded Formants',tag='legend') self.formantPlotCanvas.create_oval(FORMANTPLOTWIDTH-120-3,30-3,FORMANTPLOTWIDTH-120+3,30+3,tag='legend',fill='red') self.formantPlotCanvas.create_text(FORMANTPLOTWIDTH-64,30,text='Loaded Formants',tag='legend') ''' Loads the monophthong data. Arguments: path - directory path of where the application is stored in. ''' def getGoldStandardMonophthongs(self, path): #Initialise the male data self.maleMonophthongData = [] maleMonophthongDataPath = os.path.join(path, 'data\maori\monDataMale.txt') maleMonophthongFile = open(maleMonophthongDataPath,'r') #Initialise the female data self.femaleMonophthongData = [] femaleMonophthongDataPath = os.path.join(path, 'data\maori\monDataFemale.txt') femaleMonophthongFile = open(femaleMonophthongDataPath,'r') #Code from ywan478 2010 SoftEng206 Project #Load in the monophthong data for males for monophthongDataLine in maleMonophthongFile: monophthongDataLine = monophthongDataLine.split() #F1 mean and standard deviation monophthongDataLine[0]= (int(float(monophthongDataLine[0]))/SCALEDOWN) monophthongDataLine[1]= (int(float(monophthongDataLine[1]))/SCALEDOWN) #F2 mean and standard deviation monophthongDataLine[2]= FORMANTPLOTWIDTH - (int(float(monophthongDataLine[2]))/SCALEDOWN) + XSHIFT + XOFFSET monophthongDataLine[3]= (int(float(monophthongDataLine[3]))/SCALEDOWN) self.maleMonophthongData.append(monophthongDataLine) #Load in the monophthong data for females for monophthongDataLine in femaleMonophthongFile: monophthongDataLine = monophthongDataLine.split() #F1 mean and standard deviation monophthongDataLine[0]= (int(float(monophthongDataLine[0]))/SCALEDOWN) monophthongDataLine[1]= (int(float(monophthongDataLine[1]))/SCALEDOWN) #F2 mean and standard deviation monophthongDataLine[2]= FORMANTPLOTWIDTH - (int(float(monophthongDataLine[2]))/SCALEDOWN) + XSHIFT + XOFFSET monophthongDataLine[3]= (int(float(monophthongDataLine[3]))/SCALEDOWN) self.femaleMonophthongData.append(monophthongDataLine) #End code from ywan478 ''' Draw the gold standard monophthong data into the formant plot. Arguments: gender - gender of the user. True if male. False if female. ''' def drawGoldStandardMonophthongs(self, gender): if gender==True: data = self.maleMonophthongData else: data = self.femaleMonophthongData #Code from ywan478 2010 SoftEng206 Project for f1mean, f1sd, f2mean, f2sd, vowel in data: font = ('Arial','10','bold') self.formantPlotCanvas.create_oval(f2mean-f2sd,f1mean-f1sd,f2mean+f2sd,f1mean+f1sd, outline='black', tag='ellipses') self.formantPlotCanvas.create_text(f2mean,f1mean,fill='red',font=font,tag='ellipseLabel', text=vowel) #End code from ywan478 ''' Plots the last formant of the recording sound into the formant plot. Arguments: sound - the sound that is being recorded. ''' def plotFormants(self, sound): probabilityOfVoicing = SoundProcessing.getProbabilityOfVoicing(sound,False) if probabilityOfVoicing == 1.0: formants = SoundProcessing.getFormants(sound,self.gender,False) #Only plot the latest formants of the sound if there's a good chance that it is the user speaking instead of background noise. if formants != None: #self.f1List.append(formants[0]) #self.f2List.append(formants[1]) latestF1 = formants[0]/SCALEDOWN latestF2 = FORMANTPLOTWIDTH - (formants[1]/SCALEDOWN) + XSHIFT + XOFFSET #if abs(self.previousF1-latestF1) < 150 and abs(self.previousF2-latestF2) < 150: #print latestF1 #print latestF2 #print "\n\n" if latestF2 > XSHIFT and latestF1 < FORMANTPLOTHEIGHT-YSHIFT: self.formantPlotCanvas.create_oval(latestF2-2,latestF1-2,latestF2+2,latestF1+2, fill='black', tags="userformants") #self.previousF1 = latestF1 #self.previousF2 = latestF2 if not self.isRecording: if formants != None: #print self.soundCopy.formant() #print self.f1List #print self.f2List print "" ''' Update the loudness level in the loudness meter. Arguments: sound - the sound that is currently being recorded. ''' def updateLoudnessMeter(self, sound): try: self.loudnessMeter.updateMeter(sound) except IndexError: print "No sound available to get loudness yet" ''' Update the formant plot and loudness meter if the user is still recording. ''' def updateAllCanvases(self): self.soundCopy.copy(self.recordedAudio) SoundProcessing.crop(self.soundCopy) self.plotFormants(self.soundCopy) self.updateLoudnessMeter(self.soundCopy) if self.isRecording: self.root.after(30,self.updateAllCanvases) ''' Clears the user's formant data from the formant plot. ''' def clearUserFormants(self): self.formantPlotCanvas.delete('userformants') ''' Clears the gold standard data from the formant plot. ''' def clearGoldStandard(self): self.formantPlotCanvas.delete('ellipseLabel') self.formantPlotCanvas.delete('ellipses') ''' Redraws the gold standard data on the formant plot if the user's gender is changed. Arguments: gender - user's gender. ''' def updateGoldStandard(self, gender): self.clearGoldStandard() self.drawGoldStandardMonophthongs(gender) def updateGender(self,gender): self.gender = gender def play(self,sound): sound.play(blocking=True) ''' Stops the recording. ''' def stop(self): self.recordedAudio.stop() self.recButton.config(state='normal') self.saveButton.config(state='normal') self.playRecordButton.config(state='normal') self.stopButton.config(state='disabled') self.isRecording = False self.root.after(200,self.loudnessMeter.clearMeter) ''' Start recording the user's sounds and make the formant plot react accordingly. ''' def record(self): #Record sound file self.clearUserFormants(); self.recordedAudio.record() self.isRecording = True self.recButton.config(state='disabled') self.stopButton.config(state='normal') self.root.after(200,self.updateAllCanvases) ''' Saves the latest recording from the user. ''' def save(self): saveName = tkFileDialog.asksaveasfilename(defaultextension=".wav") self.recordedAudio.write(saveName,start=0,end=-1) def loadAudioFile(self): filename = tkFileDialog.askopenfilename(initialdir=self.appDirectory) self.formantPlotCanvas.delete('loadedformants') self.loadedAudio.config(load=filename) self.playLoadButton.config(state='normal') loadedFormants = SoundProcessing.getFormants(self.loadedAudio,self.gender,True) probabilityOfVoicingList = SoundProcessing.getProbabilityOfVoicing(self.loadedAudio,True) self.formantPlotCanvas.delete('loadedformants') #print len(loadedFormants) #print len(probabilityOfVoicingList) try: for i in range(0,len(loadedFormants)): probabilityOfVoicing = probabilityOfVoicingList[i] if probabilityOfVoicing == 1.0: formant = loadedFormants[i] f1 = formant[0]/SCALEDOWN f2 = FORMANTPLOTWIDTH - (formant[1]/SCALEDOWN) + XSHIFT + XOFFSET self.formantPlotCanvas.create_oval(f2-2,f1-2,f2+2,f1+2, fill='red', tags="loadedformants") except IndexError: print "" ''' Displays/hides a specified aspect of the formant plot presentation. Arguments: boolean - true/false indicates whether the plot should display/hide the specified thing. tag - the thing that is going to be displayed/hidden. ''' def togglePlot(self,boolean,tag): if boolean: self.formantPlotCanvas.itemconfig(tag, state='normal') else: self.formantPlotCanvas.itemconfig(tag, state='hidden') def executeSpacebarInput(self,event): if self.isRecording == False: self.record() else: self.stop()
class FormantPlot: """ Initialise the formant plot. Arguments: parent - the widget that the formant plot is contained in. path - the directory path where the application is in. root - the Tk widget which is the base of all the other widgets. gender - user's gender """ def __init__(self, parent, path, root, gender, id): self.root = root self.parent = parent self.gender = gender self.appDirectory = path self.id = id self.setUpWidgets() self.getGoldStandardMonophthongs(path) self.drawGoldStandardMonophthongs(id) self.drawFormantPlotAxes() self.drawFormantPlotLegend() self.initialiseSounds() # self.initialiseKeyboardShortcuts() self.isRecording = False self.previousF1 = 0 self.previousF2 = 0 """ Initialises all the widgets that are associated with the formant plot. """ def setUpWidgets(self): self.formantPlotFrame = Frame(self.parent, width=FORMANTPLOTFRAMEWIDTH, height=FORMANTPLOTFRAMEHEIGHT) self.formantPlotFrame.grid() self.formantPlotCanvas = SnackCanvas( self.formantPlotFrame, height=FORMANTPLOTHEIGHT, width=FORMANTPLOTWIDTH, bg="white" ) self.formantPlotCanvas.grid(row=0, column=0) self.loudnessMeterCanvas = SnackCanvas(self.formantPlotFrame, height=FORMANTPLOTHEIGHT, width=10) self.loudnessMeterCanvas.grid(row=0, column=1) self.formantPlotControlFrame = Frame(self.formantPlotFrame) self.formantPlotControlFrame.grid(row=1, column=0, sticky="w" + "e", columnspan=2) self.formantPlotControlFrame.columnconfigure(0, minsize=FORMANTPLOTWIDTH / 3) self.formantPlotControlFrame.columnconfigure(1, minsize=FORMANTPLOTWIDTH / 3) self.formantPlotControlFrame.columnconfigure(2, minsize=FORMANTPLOTWIDTH / 3) self.recButton = ttk.Button( self.formantPlotControlFrame, text="Record", command=self.record, style="Fun.TButton" ) self.recButton.grid(row=1, column=0, sticky="w" + "e", padx=10) self.stopButton = ttk.Button( self.formantPlotControlFrame, text="Stop", command=self.stop, state="disabled", style="Fun.TButton" ) self.stopButton.grid(row=1, column=1, sticky="w" + "e", padx=10) self.clearScreenButton = ttk.Button( self.formantPlotControlFrame, text="Clear Plot", command=self.clearUserFormants, state="normal", style="Fun.TButton", ) self.clearScreenButton.grid(row=1, column=2, sticky="w" + "e" + "n" + "s") self.loudnessMeter = LoudnessMeter(self.loudnessMeterCanvas) """ Creates the Snack Sound objects used in the formant plot. """ def initialiseSounds(self): initializeSnack(self.root) self.recordedAudio = Sound() self.soundCopy = Sound() self.loadedAudio = Sound() def initialiseKeyboardShortcuts(self): self.root.bind("<space>", self.executeSpacebarInput) """ Draw the axes for the formant plot. """ def drawFormantPlotAxes(self): self.formantPlotCanvas.create_line( XSHIFT, FORMANTPLOTHEIGHT - YSHIFT, FORMANTPLOTWIDTH, FORMANTPLOTHEIGHT - YSHIFT, tags="formantAxes" ) for y in range(0, FORMANTPLOTHEIGHT - YSHIFT, 20): self.formantPlotCanvas.create_line(XSHIFT - 8, 0 + y, XSHIFT, 0 + y, tags="formantAxes") self.formantPlotCanvas.create_line(XSHIFT, 0, XSHIFT, FORMANTPLOTHEIGHT - YSHIFT, tags="formantAxes") for x in range(0, 900, 20): self.formantPlotCanvas.create_line( XSHIFT + x, FORMANTPLOTHEIGHT - YSHIFT, XSHIFT + x, FORMANTPLOTHEIGHT - YSHIFT + 8 ) self.formantPlotCanvas.create_text( (XSHIFT + FORMANTPLOTWIDTH) / 2, FORMANTPLOTHEIGHT - YSHIFT + 25, text="Tongue Position", font=("Arial", "13"), tags="formantAxes", ) self.formantPlotCanvas.create_text( XSHIFT + 22, FORMANTPLOTHEIGHT - YSHIFT + 20, text="Front", font=("Arial", "13"), tags="formantAxes" ) self.formantPlotCanvas.create_text( FORMANTPLOTWIDTH - 30, FORMANTPLOTHEIGHT - YSHIFT + 20, text="Back", font=("Arial", "13"), tags="formantAxes", ) self.formantPlotCanvas.create_text( XSHIFT / 2, (FORMANTPLOTHEIGHT - YSHIFT) / 2, text="Mouth", font=("Arial", "13"), tags="formantAxes" ) self.formantPlotCanvas.create_text( XSHIFT / 2, ((FORMANTPLOTHEIGHT - YSHIFT) / 2) + 20, text="Openness", font=("Arial", "13"), tags="formantAxes", ) self.formantPlotCanvas.create_text( XSHIFT / 2, FORMANTPLOTHEIGHT - YSHIFT - 20, text="Open", font=("Arial", "13"), tags="formantAxes" ) self.formantPlotCanvas.create_text(XSHIFT / 2, 20, text="Closed", font=("Arial", "13"), tags="formantAxes") def drawFormantPlotLegend(self): font = ("Arial", "13") self.formantPlotCanvas.create_oval( FORMANTPLOTWIDTH - 160 - 3, 10 - 3, FORMANTPLOTWIDTH - 160 + 3, 10 + 3, tag="legend", fill="black" ) self.formantPlotCanvas.create_text(FORMANTPLOTWIDTH - 80, 10, text="Recorded Vowels", tag="legend", font=font) self.formantPlotCanvas.create_oval( FORMANTPLOTWIDTH - 160 - 3, 30 - 3, FORMANTPLOTWIDTH - 160 + 3, 30 + 3, tag="legend", fill="red" ) self.formantPlotCanvas.create_text(FORMANTPLOTWIDTH - 80, 30, text="Loaded Vowels", tag="legend", font=font) def appendData(self, pat, str): MonophthongDataPath = os.path.join(pat, str) MonophthongFile = open(MonophthongDataPath, "r") MonophthongData = [] # Code from ywan478 2010 SoftEng206 Project # Load in the monophthong data for males for monophthongDataLine in MonophthongFile: monophthongDataLine = monophthongDataLine.split() # F1 mean and standard deviation monophthongDataLine[0] = int(float(monophthongDataLine[0])) / SCALEDOWN monophthongDataLine[1] = int(float(monophthongDataLine[1])) / SCALEDOWN # F2 mean and standard deviation monophthongDataLine[2] = ( FORMANTPLOTWIDTH - (int(float(monophthongDataLine[2])) / SCALEDOWN) + XSHIFT + XOFFSET ) monophthongDataLine[3] = int(float(monophthongDataLine[3])) / SCALEDOWN MonophthongData.append(monophthongDataLine) # End code from ywan478 return MonophthongData """ Loads the monophthong data. Arguments: path - directory path of where the application is stored in. """ def getGoldStandardMonophthongs(self, path): # Initialise the male data self.maleMonophthongData = self.appendData(path, "data\maori\monDataMale.txt") self.maleMonophthongData1 = self.appendData(path, "data\maori\monDataMaleYoung.txt") # Initialise the female data self.femaleMonophthongData = self.appendData(path, "data\maori\monDataFemale.txt") self.femaleMonophthongData1 = self.appendData(path, "data\maori\monDataFemaleYoung.txt") """ Draw the gold standard monophthong data into the formant plot. Arguments: gender - gender of the user. True if male. False if female. """ def drawGoldStandardMonophthongs(self, id): if id == 0: data = self.maleMonophthongData for f1mean, f1sd, f2mean, f2sd, vowel in data: font = ("Arial", "20", "bold") self.formantPlotCanvas.create_oval( (f2mean - f2sd) * 1.5 - 400, (f1mean - f1sd) * 2 - 100, (f2mean + f2sd) * 1.5 - 400, (f1mean + f1sd) * 2 - 100, outline="black", tag="ellipses", ) self.formantPlotCanvas.create_text( f2mean * 1.5 - 400, f1mean * 2 - 100, fill="red", font=font, tag="ellipseLabel", text=vowel ) elif id == 1: data = self.maleMonophthongData1 for f1mean, f1sd, f2mean, f2sd, vowel in data: font = ("Arial", "20", "bold") self.formantPlotCanvas.create_oval( (f2mean - f2sd) * 1.5 - 400, (f1mean - f1sd) * 2.7 - 300, (f2mean + f2sd) * 1.5 - 400, (f1mean + f1sd) * 2.7 - 300, outline="black", tag="ellipses", ) self.formantPlotCanvas.create_text( f2mean * 1.5 - 400, f1mean * 2.7 - 300, fill="red", font=font, tag="ellipseLabel", text=vowel ) elif id == 2: data = self.femaleMonophthongData for f1mean, f1sd, f2mean, f2sd, vowel in data: font = ("Arial", "20", "bold") self.formantPlotCanvas.create_oval( (f2mean - f2sd) * 1.2 - 130, (f1mean - f1sd) * 1.8 - 130, (f2mean + f2sd) * 1.2 - 130, (f1mean + f1sd) * 1.8 - 130, outline="black", tag="ellipses", ) self.formantPlotCanvas.create_text( f2mean * 1.2 - 130, f1mean * 1.8 - 130, fill="red", font=font, tag="ellipseLabel", text=vowel ) else: data = self.femaleMonophthongData1 for f1mean, f1sd, f2mean, f2sd, vowel in data: font = ("Arial", "20", "bold") self.formantPlotCanvas.create_oval( (f2mean - f2sd) * 1.5 - 330, (f1mean - f1sd) * 2.7 - 300, (f2mean + f2sd) * 1.5 - 330, (f1mean + f1sd) * 2.7 - 300, outline="black", tag="ellipses", ) self.formantPlotCanvas.create_text( f2mean * 1.5 - 330, f1mean * 2.7 - 300, fill="red", font=font, tag="ellipseLabel", text=vowel ) # Code from ywan478 2010 SoftEng206 Project # End code from ywan478 """ Plots the last formant of the recording sound into the formant plot. Arguments: sound - the sound that is being recorded. """ def plotFormants(self, sound): probabilityOfVoicing = SoundProcessing.getProbabilityOfVoicing(sound, False) if probabilityOfVoicing == 1.0: formants = SoundProcessing.getFormants(sound, self.gender, False) # Only plot the latest formants of the sound if there's a good chance that it is the user speaking instead of background noise. if formants != None: latestF1 = formants[0] / SCALEDOWN latestF2 = FORMANTPLOTWIDTH - (formants[1] / SCALEDOWN) + XSHIFT + XOFFSET if self.id == 0: latestF2 = latestF2 * 1.5 - 400 latestF1 = latestF1 * 2 - 100 elif self.id == 1: latestF2 = latestF2 * 1.5 - 400 latestF1 = latestF1 * 2.7 - 300 elif self.id == 2: latestF2 = latestF2 * 1.2 - 130 latestF1 = latestF1 * 1.8 - 130 elif self.id == 3: latestF2 = latestF2 * 1.5 - 330 latestF1 = latestF1 * 2.7 - 300 if latestF2 > XSHIFT and latestF1 < FORMANTPLOTHEIGHT - YSHIFT: self.formantPlotCanvas.create_oval( latestF2 - 2, latestF1 - 2, latestF2 + 2, latestF1 + 2, fill="black", tags="userformants" ) if not self.isRecording: if formants != None: print "" """ Update the loudness level in the loudness meter. Arguments: sound - the sound that is currently being recorded. """ def updateLoudnessMeter(self, sound): try: self.loudnessMeter.updateMeter(sound) except IndexError: print "No sound available to get loudness yet" """ Update the formant plot and loudness meter if the user is still recording. """ def updateAllCanvases(self): self.soundCopy.copy(self.recordedAudio) SoundProcessing.crop(self.soundCopy) self.plotFormants(self.soundCopy) self.updateLoudnessMeter(self.soundCopy) if self.isRecording: self.root.after(30, self.updateAllCanvases) """ Clears the user's formant data from the formant plot. """ def clearUserFormants(self): self.formantPlotCanvas.delete("userformants") """ Clears the gold standard data from the formant plot. """ def clearGoldStandard(self): self.formantPlotCanvas.delete("ellipseLabel") self.formantPlotCanvas.delete("ellipses") """ Redraws the gold standard data on the formant plot if the user's gender is changed. Arguments: gender - user's gender. """ def updateGoldStandard(self, id): self.clearGoldStandard() self.drawGoldStandardMonophthongs(id) def updateGender(self, gender): self.gender = gender def play(self, sound, id): sound.play(blocking=True) """ Stops the recording. """ def stop(self): self.recordedAudio.stop() self.recButton.config(state="normal") self.stopButton.config(state="disabled") self.isRecording = False self.root.after(200, self.loudnessMeter.clearMeter) """ Start recording the user's sounds and make the formant plot react accordingly. """ def record(self): # Record sound file self.clearUserFormants() self.recordedAudio.record() self.isRecording = True self.recButton.config(state="disabled") self.stopButton.config(state="normal") self.root.after(200, self.updateAllCanvases) """ Saves the latest recording from the user. """ def save(self): saveName = tkFileDialog.asksaveasfilename(defaultextension=".wav") self.recordedAudio.write(saveName, start=0, end=-1) def loadAudioFile(self): filename = tkFileDialog.askopenfilename(initialdir=self.appDirectory) self.formantPlotCanvas.delete("loadedformants") self.loadedAudio.config(load=filename) # self.playLoadButton.config(state='normal') loadedFormants = SoundProcessing.getFormants(self.loadedAudio, self.gender, True) probabilityOfVoicingList = SoundProcessing.getProbabilityOfVoicing(self.loadedAudio, True) self.formantPlotCanvas.delete("loadedformants") try: for i in range(0, len(loadedFormants)): probabilityOfVoicing = probabilityOfVoicingList[i] if probabilityOfVoicing == 1.0: formant = loadedFormants[i] f1 = formant[0] / SCALEDOWN f2 = FORMANTPLOTWIDTH - (formant[1] / SCALEDOWN) + XSHIFT + XOFFSET if f2 - 100 > XSHIFT and f1 + 100 < FORMANTPLOTHEIGHT - YSHIFT: self.formantPlotCanvas.create_oval( f2 - 100 - 2, f1 + 100 - 2, f2 - 100 + 2, f1 + 100 + 2, fill="yellow", tags="loadedformants" ) except IndexError: print "" """ Displays/hides a specified aspect of the formant plot presentation. Arguments: boolean - true/false indicates whether the plot should display/hide the specified thing. tag - the thing that is going to be displayed/hidden. def togglePlot(self,boolean,tag): if boolean: self.formantPlotCanvas.itemconfig(tag, state='normal') else: self.formantPlotCanvas.itemconfig(tag, state='hidden') """ def executeSpacebarInput(self, event): if self.isRecording == False: self.record() else: self.stop()
class FormantPlot: ''' Initialise the formant plot. Arguments: parent - the widget that the formant plot is contained in. path - the directory path where the application is in. root - the Tk widget which is the base of all the other widgets. gender - user's gender ''' def __init__(self, parent, path, root, gender, id): self.root = root self.parent = parent self.gender = gender self.appDirectory = path self.id = id self.setUpWidgets() self.getGoldStandardMonophthongs(path) self.drawGoldStandardMonophthongs(id) self.drawFormantPlotAxes() self.drawFormantPlotLegend() self.initialiseSounds() #self.initialiseKeyboardShortcuts() self.isRecording = False self.previousF1 = 0 self.previousF2 = 0 ''' Initialises all the widgets that are associated with the formant plot. ''' def setUpWidgets(self): self.formantPlotFrame = Frame(self.parent, width=FORMANTPLOTFRAMEWIDTH, height=FORMANTPLOTFRAMEHEIGHT) self.formantPlotFrame.grid() self.formantPlotCanvas = SnackCanvas(self.formantPlotFrame, height=FORMANTPLOTHEIGHT, width=FORMANTPLOTWIDTH, bg='white') self.formantPlotCanvas.grid(row=0, column=0) self.loudnessMeterCanvas = SnackCanvas(self.formantPlotFrame, height=FORMANTPLOTHEIGHT, width=10) self.loudnessMeterCanvas.grid(row=0, column=1) self.formantPlotControlFrame = Frame(self.formantPlotFrame) self.formantPlotControlFrame.grid(row=1, column=0, sticky='w' + 'e', columnspan=2) self.formantPlotControlFrame.columnconfigure(0, minsize=FORMANTPLOTWIDTH / 3) self.formantPlotControlFrame.columnconfigure(1, minsize=FORMANTPLOTWIDTH / 3) self.formantPlotControlFrame.columnconfigure(2, minsize=FORMANTPLOTWIDTH / 3) self.recButton = ttk.Button(self.formantPlotControlFrame, text='Record', command=self.record, style='Fun.TButton') self.recButton.grid(row=1, column=0, sticky='w' + 'e', padx=10) self.stopButton = ttk.Button(self.formantPlotControlFrame, text='Stop', command=self.stop, state='disabled', style='Fun.TButton') self.stopButton.grid(row=1, column=1, sticky='w' + 'e', padx=10) self.clearScreenButton = ttk.Button(self.formantPlotControlFrame, text='Clear Plot', command=self.clearUserFormants, state='normal', style='Fun.TButton') self.clearScreenButton.grid(row=1, column=2, sticky='w' + 'e' + 'n' + 's') self.loudnessMeter = LoudnessMeter(self.loudnessMeterCanvas) ''' Creates the Snack Sound objects used in the formant plot. ''' def initialiseSounds(self): initializeSnack(self.root) self.recordedAudio = Sound() self.soundCopy = Sound() self.loadedAudio = Sound() def initialiseKeyboardShortcuts(self): self.root.bind('<space>', self.executeSpacebarInput) ''' Draw the axes for the formant plot. ''' def drawFormantPlotAxes(self): self.formantPlotCanvas.create_line(XSHIFT, FORMANTPLOTHEIGHT - YSHIFT, FORMANTPLOTWIDTH, FORMANTPLOTHEIGHT - YSHIFT, tags="formantAxes") for y in range(0, FORMANTPLOTHEIGHT - YSHIFT, 20): self.formantPlotCanvas.create_line(XSHIFT - 8, 0 + y, XSHIFT, 0 + y, tags="formantAxes") self.formantPlotCanvas.create_line(XSHIFT, 0, XSHIFT, FORMANTPLOTHEIGHT - YSHIFT, tags="formantAxes") for x in range(0, 900, 20): self.formantPlotCanvas.create_line(XSHIFT + x, FORMANTPLOTHEIGHT - YSHIFT, XSHIFT + x, FORMANTPLOTHEIGHT - YSHIFT + 8) self.formantPlotCanvas.create_text((XSHIFT + FORMANTPLOTWIDTH) / 2, FORMANTPLOTHEIGHT - YSHIFT + 25, text="Tongue Position", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text(XSHIFT + 22, FORMANTPLOTHEIGHT - YSHIFT + 20, text="Front", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text(FORMANTPLOTWIDTH - 30, FORMANTPLOTHEIGHT - YSHIFT + 20, text="Back", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text(XSHIFT / 2, (FORMANTPLOTHEIGHT - YSHIFT) / 2, text="Mouth", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text( XSHIFT / 2, ((FORMANTPLOTHEIGHT - YSHIFT) / 2) + 20, text="Openness", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text(XSHIFT / 2, FORMANTPLOTHEIGHT - YSHIFT - 20, text="Open", font=('Arial', '13'), tags="formantAxes") self.formantPlotCanvas.create_text(XSHIFT / 2, 20, text="Closed", font=('Arial', '13'), tags="formantAxes") def drawFormantPlotLegend(self): font = ('Arial', '13') self.formantPlotCanvas.create_oval(FORMANTPLOTWIDTH - 160 - 3, 10 - 3, FORMANTPLOTWIDTH - 160 + 3, 10 + 3, tag='legend', fill='black') self.formantPlotCanvas.create_text(FORMANTPLOTWIDTH - 80, 10, text='Recorded Vowels', tag='legend', font=font) self.formantPlotCanvas.create_oval(FORMANTPLOTWIDTH - 160 - 3, 30 - 3, FORMANTPLOTWIDTH - 160 + 3, 30 + 3, tag='legend', fill='red') self.formantPlotCanvas.create_text(FORMANTPLOTWIDTH - 80, 30, text='Loaded Vowels', tag='legend', font=font) def appendData(self, pat, str): MonophthongDataPath = os.path.join(pat, str) MonophthongFile = open(MonophthongDataPath, 'r') MonophthongData = [] #Code from ywan478 2010 SoftEng206 Project #Load in the monophthong data for males for monophthongDataLine in MonophthongFile: monophthongDataLine = monophthongDataLine.split() #F1 mean and standard deviation monophthongDataLine[0] = (int(float(monophthongDataLine[0])) / SCALEDOWN) monophthongDataLine[1] = (int(float(monophthongDataLine[1])) / SCALEDOWN) #F2 mean and standard deviation monophthongDataLine[2] = FORMANTPLOTWIDTH - (int( float(monophthongDataLine[2])) / SCALEDOWN) + XSHIFT + XOFFSET monophthongDataLine[3] = (int(float(monophthongDataLine[3])) / SCALEDOWN) MonophthongData.append(monophthongDataLine) #End code from ywan478 return MonophthongData ''' Loads the monophthong data. Arguments: path - directory path of where the application is stored in. ''' def getGoldStandardMonophthongs(self, path): #Initialise the male data self.maleMonophthongData = self.appendData( path, 'data\maori\monDataMale.txt') self.maleMonophthongData1 = self.appendData( path, 'data\maori\monDataMaleYoung.txt') #Initialise the female data self.femaleMonophthongData = self.appendData( path, 'data\maori\monDataFemale.txt') self.femaleMonophthongData1 = self.appendData( path, 'data\maori\monDataFemaleYoung.txt') ''' Draw the gold standard monophthong data into the formant plot. Arguments: gender - gender of the user. True if male. False if female. ''' def drawGoldStandardMonophthongs(self, id): if id == 0: data = self.maleMonophthongData for f1mean, f1sd, f2mean, f2sd, vowel in data: font = ('Arial', '20', 'bold') self.formantPlotCanvas.create_oval( (f2mean - f2sd) * 1.5 - 400, (f1mean - f1sd) * 2 - 100, (f2mean + f2sd) * 1.5 - 400, (f1mean + f1sd) * 2 - 100, outline='black', tag='ellipses') self.formantPlotCanvas.create_text(f2mean * 1.5 - 400, f1mean * 2 - 100, fill='red', font=font, tag='ellipseLabel', text=vowel) elif id == 1: data = self.maleMonophthongData1 for f1mean, f1sd, f2mean, f2sd, vowel in data: font = ('Arial', '20', 'bold') self.formantPlotCanvas.create_oval( (f2mean - f2sd) * 1.5 - 400, (f1mean - f1sd) * 2.7 - 300, (f2mean + f2sd) * 1.5 - 400, (f1mean + f1sd) * 2.7 - 300, outline='black', tag='ellipses') self.formantPlotCanvas.create_text(f2mean * 1.5 - 400, f1mean * 2.7 - 300, fill='red', font=font, tag='ellipseLabel', text=vowel) elif id == 2: data = self.femaleMonophthongData for f1mean, f1sd, f2mean, f2sd, vowel in data: font = ('Arial', '20', 'bold') self.formantPlotCanvas.create_oval( (f2mean - f2sd) * 1.2 - 130, (f1mean - f1sd) * 1.8 - 130, (f2mean + f2sd) * 1.2 - 130, (f1mean + f1sd) * 1.8 - 130, outline='black', tag='ellipses') self.formantPlotCanvas.create_text(f2mean * 1.2 - 130, f1mean * 1.8 - 130, fill='red', font=font, tag='ellipseLabel', text=vowel) else: data = self.femaleMonophthongData1 for f1mean, f1sd, f2mean, f2sd, vowel in data: font = ('Arial', '20', 'bold') self.formantPlotCanvas.create_oval( (f2mean - f2sd) * 1.5 - 330, (f1mean - f1sd) * 2.7 - 300, (f2mean + f2sd) * 1.5 - 330, (f1mean + f1sd) * 2.7 - 300, outline='black', tag='ellipses') self.formantPlotCanvas.create_text(f2mean * 1.5 - 330, f1mean * 2.7 - 300, fill='red', font=font, tag='ellipseLabel', text=vowel) #Code from ywan478 2010 SoftEng206 Project #End code from ywan478 ''' Plots the last formant of the recording sound into the formant plot. Arguments: sound - the sound that is being recorded. ''' def plotFormants(self, sound): probabilityOfVoicing = SoundProcessing.getProbabilityOfVoicing( sound, False) if probabilityOfVoicing == 1.0: formants = SoundProcessing.getFormants(sound, self.gender, False) #Only plot the latest formants of the sound if there's a good chance that it is the user speaking instead of background noise. if formants != None: latestF1 = formants[0] / SCALEDOWN latestF2 = FORMANTPLOTWIDTH - (formants[1] / SCALEDOWN) + XSHIFT + XOFFSET if self.id == 0: latestF2 = latestF2 * 1.5 - 400 latestF1 = latestF1 * 2 - 100 elif self.id == 1: latestF2 = latestF2 * 1.5 - 400 latestF1 = latestF1 * 2.7 - 300 elif self.id == 2: latestF2 = latestF2 * 1.2 - 130 latestF1 = latestF1 * 1.8 - 130 elif self.id == 3: latestF2 = latestF2 * 1.5 - 330 latestF1 = latestF1 * 2.7 - 300 if latestF2 > XSHIFT and latestF1 < FORMANTPLOTHEIGHT - YSHIFT: self.formantPlotCanvas.create_oval(latestF2 - 2, latestF1 - 2, latestF2 + 2, latestF1 + 2, fill='black', tags="userformants") if not self.isRecording: if formants != None: print "" ''' Update the loudness level in the loudness meter. Arguments: sound - the sound that is currently being recorded. ''' def updateLoudnessMeter(self, sound): try: self.loudnessMeter.updateMeter(sound) except IndexError: print "No sound available to get loudness yet" ''' Update the formant plot and loudness meter if the user is still recording. ''' def updateAllCanvases(self): self.soundCopy.copy(self.recordedAudio) SoundProcessing.crop(self.soundCopy) self.plotFormants(self.soundCopy) self.updateLoudnessMeter(self.soundCopy) if self.isRecording: self.root.after(30, self.updateAllCanvases) ''' Clears the user's formant data from the formant plot. ''' def clearUserFormants(self): self.formantPlotCanvas.delete('userformants') ''' Clears the gold standard data from the formant plot. ''' def clearGoldStandard(self): self.formantPlotCanvas.delete('ellipseLabel') self.formantPlotCanvas.delete('ellipses') ''' Redraws the gold standard data on the formant plot if the user's gender is changed. Arguments: gender - user's gender. ''' def updateGoldStandard(self, id): self.clearGoldStandard() self.drawGoldStandardMonophthongs(id) def updateGender(self, gender): self.gender = gender def play(self, sound, id): sound.play(blocking=True) ''' Stops the recording. ''' def stop(self): self.recordedAudio.stop() self.recButton.config(state='normal') self.stopButton.config(state='disabled') self.isRecording = False self.root.after(200, self.loudnessMeter.clearMeter) ''' Start recording the user's sounds and make the formant plot react accordingly. ''' def record(self): #Record sound file self.clearUserFormants() self.recordedAudio.record() self.isRecording = True self.recButton.config(state='disabled') self.stopButton.config(state='normal') self.root.after(200, self.updateAllCanvases) ''' Saves the latest recording from the user. ''' def save(self): saveName = tkFileDialog.asksaveasfilename(defaultextension=".wav") self.recordedAudio.write(saveName, start=0, end=-1) def loadAudioFile(self): filename = tkFileDialog.askopenfilename(initialdir=self.appDirectory) self.formantPlotCanvas.delete('loadedformants') self.loadedAudio.config(load=filename) #self.playLoadButton.config(state='normal') loadedFormants = SoundProcessing.getFormants(self.loadedAudio, self.gender, True) probabilityOfVoicingList = SoundProcessing.getProbabilityOfVoicing( self.loadedAudio, True) self.formantPlotCanvas.delete('loadedformants') try: for i in range(0, len(loadedFormants)): probabilityOfVoicing = probabilityOfVoicingList[i] if probabilityOfVoicing == 1.0: formant = loadedFormants[i] f1 = formant[0] / SCALEDOWN f2 = FORMANTPLOTWIDTH - (formant[1] / SCALEDOWN) + XSHIFT + XOFFSET if f2 - 100 > XSHIFT and f1 + 100 < FORMANTPLOTHEIGHT - YSHIFT: self.formantPlotCanvas.create_oval( f2 - 100 - 2, f1 + 100 - 2, f2 - 100 + 2, f1 + 100 + 2, fill='yellow', tags="loadedformants") except IndexError: print "" ''' Displays/hides a specified aspect of the formant plot presentation. Arguments: boolean - true/false indicates whether the plot should display/hide the specified thing. tag - the thing that is going to be displayed/hidden. def togglePlot(self,boolean,tag): if boolean: self.formantPlotCanvas.itemconfig(tag, state='normal') else: self.formantPlotCanvas.itemconfig(tag, state='hidden') ''' def executeSpacebarInput(self, event): if self.isRecording == False: self.record() else: self.stop()
class VowelPlot: def __init__(self, parent, path, root, id, formApp, vowelScorer, vowel, width, height): configValues = VowelFileHandler.getDataFromFile() self.centreOval = configValues[0] self.middleOval = configValues[1] self.outerOval = configValues[2] self.targetSizeRatio = configValues[3] self.axisButtonClicked = False self.vowelScorer = vowelScorer self.width = width self.height = height self.root = root self.formApp = formApp self.parent = parent self.path = path self.id = id self.vowel = vowel self.hasPlots = False self.hasScore = False self.firstTime = True #plot Setup: self.setupPlot() self.plottedInfo = [0, 0, 0, 0, 0] self.prevX = 0 self.prevY = 0 # sound Setup: self.initialiseSounds() self.Recording = False self.Waiting = False self.drawTarget(vowel) ''' switchCoorSystems is a function which converts coordinates in the vowel space into the target space. Its purpose is to remove the need for individual functions to handle there own shifting and scaling. This is done via first reflecting in the x Axis, then shifting so the new top left corner is at the origin of the target space. Then scales to the width and height of the target space. ''' def switchCoorSystems(self, x, y): #Reflex in Y Axis x = (-1) * x #calculate vowel space size xSize, ySize = self.vowelSpaceSize() #calculate shifts xShift, yShift = self.calculateShift(xSize, ySize) #shift coords onto the target space origin. x = x + xShift + xSize # + as we reflected in x, which means to get to the 1st Quadrant(in the target coord system) we need to add its width y = y - yShift # - as we do not need to reflect in y, do to the nature of the python canvas already have small at the top and large at the bottom as required. #As the target space coord system is our normal x,y coord system just reflected in x. #scale xScale, yScale = self.calculateScale(xSize, ySize) x = x * xScale y = y * yScale return x, y def vowelSpaceSize(self): return self.outerOval * 2 * self.f2sd * ( 1 / self.targetSizeRatio), self.outerOval * 2 * self.f1sd * ( 1 / self.targetSizeRatio) def calculateShift(self, xSize, ySize): #XSHIFT topLeftXCoord = self.f2mean - xSize / 2 #YSHIFT topLeftYCoord = self.f1mean - ySize / 2 return topLeftXCoord, topLeftYCoord def calculateScale(self, xSize, ySize): #xScale xScale = self.width / xSize #yScale yScale = self.height / ySize return xScale, yScale def setupPlot(self): #Plot self.vowelPlotFrame = self.parent self.vowelPlotCanvas = Canvas(self.vowelPlotFrame, height=self.width, width=self.height, bg='white') self.vowelPlotCanvas.delete('Loudness') self.vowelPlotCanvas.delete('toLoud') self.vowelPlotCanvas.delete('toQuiet') self.vowelPlotCanvas.pack(fill='both', expand=1) #Creates the loudnessMeter on the formant plot canvas. self.loudnessMeter = LoudnessMeter(self.vowelPlotCanvas, YSHIFT) font = ('Arial', '14') boxWidth = 150 self.font = font x1 = (self.height / 2) - (boxWidth) y1 = 2 x2 = (self.height / 2) y2 = 32 self.recordingBox = self.vowelPlotCanvas.create_rectangle( x1, y1, x2, y2, tag='recording', fill='red', outline='white') self.recordingBoxText = self.vowelPlotCanvas.create_text( (x1 + x2) / 2, (y1 + y2) / 2, tag='recordingText', text='Recording.', font=font, fill='black') self.vowelPlotCanvas.itemconfig('recording', state='hidden') self.vowelPlotCanvas.itemconfig('recordingText', state='hidden') x1 = (self.height / 2) y1 = 2 x2 = (self.height / 2) + boxWidth y2 = 32 self.recordingBox = self.vowelPlotCanvas.create_rectangle( x1, y1, x2, y2, tag='Loudness', outline='black') self.recordingBoxText = self.vowelPlotCanvas.create_text( (x1 + x2) / 2, (y1 + y2) / 2, tag='toLoud', text='Too Loud.', font=font, fill='black') self.recordingBoxText = self.vowelPlotCanvas.create_text( (x1 + x2) / 2, (y1 + y2) / 2, tag='toQuiet', text='Too Quiet.', font=font, fill='black') self.vowelPlotCanvas.itemconfig('Loudness', state='hidden') self.vowelPlotCanvas.itemconfig('toLoud', state='hidden') self.vowelPlotCanvas.itemconfig('toQuiet', state='hidden') def createText(self): self.vowelPlotCanvas.delete('helptext') self.vowelPlotCanvas.delete('tophelptext') self.vowelPlotCanvas.create_text(self.width / 2, self.height - 50, tag='helptext', font=self.font, text="Click on the target to begin.") self.vowelPlotCanvas.create_text( self.width / 2, self.height - 30, tag='helptext', font=self.font, text="After 250 Plots the round will finish.") self.vowelPlotCanvas.create_text(self.width / 2, 10, tag='tophelptext', font=self.font, text="Choose a Vowel to practice.") def setUpButtonsFirstTime(self): self.vowelPlotCanvas.delete("firstButtons") font = ('Arial', '20') vowel = ['a:', 'e:', 'i:', 'o:', 'u:'] aButton = Button(self.parent, text=vowel[0], font=font, command=lambda vow=vowel[0]: self.changeVowel(vow)) aButton.configure(activebackground="#FA4A4A", relief=GROOVE) aButtonWindow = self.vowelPlotCanvas.create_window(self.width / 2 - 160, 50, window=aButton, tags="firstButtons") eButton = Button(self.parent, text=vowel[1], font=font, command=lambda vow=vowel[1]: self.changeVowel(vow)) eButton.configure(activebackground="#FA4A4A", relief=GROOVE) eButtonWindow = self.vowelPlotCanvas.create_window(self.width / 2 - 80, 50, window=eButton, tags="firstButtons") iButton = Button(self.parent, text=vowel[2] + " ", font=font, command=lambda vow=vowel[2]: self.changeVowel(vow)) iButton.configure(activebackground="#FA4A4A", relief=GROOVE) iButtonWindow = self.vowelPlotCanvas.create_window(self.width / 2, 50, window=iButton, tags="firstButtons") oButton = Button(self.parent, text=vowel[3], font=font, command=lambda vow=vowel[3]: self.changeVowel(vow)) oButton.configure(activebackground="#FA4A4A", relief=GROOVE) oButtonWindow = self.vowelPlotCanvas.create_window(self.width / 2 + 80, 50, window=oButton, tags="firstButtons") uButton = Button(self.parent, text=vowel[4], font=font, command=lambda vow=vowel[4]: self.changeVowel(vow)) uButton.configure(activebackground="#FA4A4A", relief=GROOVE) uButtonWindow = self.vowelPlotCanvas.create_window(self.width / 2 + 160, 50, window=uButton, tags="firstButtons") if self.vowel == 'a:': aButton.config(bg='#FA4A4A', relief="sunken") elif self.vowel == 'e:': eButton.config(bg='#FA4A4A', relief="sunken") elif self.vowel == 'i:': iButton.config(bg='#FA4A4A', relief="sunken") elif self.vowel == 'o:': oButton.config(bg='#FA4A4A', relief="sunken") elif self.vowel == 'u:': uButton.config(bg='#FA4A4A', relief="sunken") analysisButton = Button(self.parent, text=" Analysis \nand\ngo back", command=self.requestQuit, font=('Arial', '15')) analysisButton.configure(activebackground='#FA4A4A', anchor=W, relief=GROOVE) analysisButtonWindow = self.vowelPlotCanvas.create_window( 4, self.height - 4, anchor=SW, tags=('analysisButton', 'firstButtons'), window=analysisButton) def requestQuit(self): self.formApp.quitApp() #handles the Resizing of the application and all its child. currently disabled. def onResize(self, width, height): self.width = width self.height = height self.drawTarget(self.vowel) self.createText() self.setUpButtonsFirstTime() #rebind configure event being fired from root changes. if (self.hasScore): self.redrawScore() if (self.hasPlots): self.replotFormants() def changeVowel(self, vowel): self.formApp.loadVowelPlot(self.id, vowel, self.width, self.height) """ setUpScore Sets the current score to be 0, creates the score text. """ def setUpScore(self): self.hasScore = True font = ('Arial', '18') self.vowelPlotCanvas.delete('score') self.textID = self.vowelPlotCanvas.create_text(self.width / 2, self.height - 60, font=font, tag='score', text=" ---- %") self.score = 0 self.rawScore = 0 self.scoreCounter = 0 def redrawScore(self): font = ('Arial', '18') self.vowelPlotCanvas.delete('score') if self.score == 0: scoreText = ' ---- %' else: scoreText = (str)(self.score) + " %" self.textID = self.vowelPlotCanvas.create_text( self.width / 2, self.height - (60 * (self.height / DEFAULTHEIGHT)), tag='score', font=font, text=scoreText) def updateScore(self, score): self.hasScore = True self.scoreCounter += 1 self.rawScore += score self.score = (int)((float)(self.rawScore) / (float)(self.scoreCounter)) scoreText = (str)(self.score) + " %" self.vowelPlotCanvas.itemconfig(self.textID, text=scoreText) def displayFinalScore(self, score): scoreText = "Score: " + (str)(score) + " %" self.vowelPlotCanvas.itemconfig(self.textID, text=scoreText) self.score = score self.vowelPlotCanvas.itemconfig('Loudness', state='hidden') self.vowelPlotCanvas.itemconfig('toLoud', state='hidden') self.vowelPlotCanvas.itemconfig('toQuiet', state='hidden') ''' Converts a distance on the vowel space to score. ''' def distanceToScore(self, xCoord, yCoord): self.plottedInfo[0] += 1 x, y = self.switchCoorSystems(xCoord, yCoord) distance = ((x - self.width / 2)**2 + (y - self.height / 2)**2)**0.5 scoringZoneDistance = distance - self.width * self.targetSizeRatio * 0.5 * self.centreOval / self.outerOval scoringZone = self.width * self.targetSizeRatio * 0.5 - self.width * self.targetSizeRatio * 0.5 * self.centreOval / self.outerOval if distance < self.width * self.targetSizeRatio * 0.5 * self.centreOval / self.outerOval: score = 100 elif distance < self.width * self.targetSizeRatio * 0.5: self.plottedInfo[1] += 1 score = (1 - (scoringZoneDistance / scoringZone)) * 100 else: self.plottedInfo[1] -= 1 return 0 self.updateScore((int)(score)) def drawTarget(self, letter): self.vowelPlotCanvas.delete("vowelOval") id = self.id font = ('Arial', '20') data = self.goldStandardDiphthongs(id) for f1mean, f1sd, f2mean, f2sd, vowel in data: if vowel == letter: vowel = letter self.f1sd = f1sd self.f2sd = f2sd self.f1mean = f1mean self.f2mean = f2mean xSd, ySd = self.switchCoorSystems(f2sd, f1sd) #DistanceTo OuterOval self.xIdeal = self.outerOval * xSd self.yIdeal = self.outerOval * ySd #MEAN self.x = self.width / 2 self.y = self.height / 2 colour = ['#ADD8E6', '#ff4c4c', '#ffff66'] activeColour = ['#DFFFFF', '#ff7f7f', '#ffff99'] i = 0 for scale in [ self.outerOval, self.middleOval, self.centreOval ]: x1, y1 = self.switchCoorSystems(f2mean - scale * f2sd, f1mean - scale * f1sd) x2, y2 = self.switchCoorSystems(f2mean + scale * f2sd, f1mean + scale * f1sd) self.vowelPlotCanvas.create_oval( x1, y1, x2, y2, outline='black', tag='vowelOval', fill=colour[i], activefill=activeColour[i]) i += 1 self.vowelPlotCanvas.create_text(self.width / 2, self.height / 2, fill='#00007f', font=font, tag='vowelOval', text=vowel) self.vowelPlotCanvas.tag_bind( 'vowelOval', "<Button-1>", lambda event, : self.toggleRecord(event)) self.vowelPlotCanvas.delete('questionMarkButton') self.questionMarkButton = Button(self.parent, text=" Show Axis", command=self.showAxisClicked, font=('Arial', '13')) self.questionMarkButton.configure(activebackground='#FA4A4A', bg='white', anchor=W, relief=GROOVE) questionMarkButtonWindow = self.vowelPlotCanvas.create_window( self.width - 2, self.height - 2, anchor=SE, tags=('questionMarkButton'), window=self.questionMarkButton) self.questionMarkButton.bind("<Enter>", self.showAxisHoovering) self.questionMarkButton.bind("<Leave>", self.hideAxisHoovering) self.drawAxis() def showAxisHoovering(self, *args): if not self.axisButtonClicked: self.questionMarkButton.config(relief=SUNKEN) self.showAxis() def hideAxisHoovering(self, *args): if not self.axisButtonClicked: self.questionMarkButton.config(relief=GROOVE, bg='white') self.hideAxis() def showAxisClicked(self, *args): if self.axisButtonClicked: self.questionMarkButton.config(bg='white') self.hideAxis self.axisButtonClicked = False else: self.questionMarkButton.config(relief=SUNKEN, bg='#FA4A4A') self.showAxis self.axisButtonClicked = True def showAxis(self): self.vowelPlotCanvas.itemconfig('axis', state='normal') def hideAxis(self): self.vowelPlotCanvas.itemconfig('axis', state='hidden') def drawAxis(self): xSd, ySd = self.switchCoorSystems(self.f2sd, self.f1sd) self.vowelPlotCanvas.delete('axis') axisFont = ('Arial', '13') #Draw y Axis xAxis = self.width / 2 yAxis1 = (self.height / 2) - self.targetSizeRatio * self.height / 2 - 25 yAxis2 = (self.height / 2) - self.targetSizeRatio * self.height * self.centreOval / ( self.outerOval * 2) yAxis3 = (self.height / 2) + self.targetSizeRatio * self.height * self.centreOval / ( self.outerOval * 2) yAxis4 = (self.height / 2) + self.targetSizeRatio * self.height / 2 + 25 self.vowelPlotCanvas.create_line(xAxis, yAxis1, xAxis, yAxis2, tags='axis', width=2) self.vowelPlotCanvas.create_line(xAxis, yAxis3, xAxis, yAxis4, tags='axis', width=2) # for index in range(2,10): # yCoor = index * ((self.yIdeal*self.outerOval)/10) # self.vowelPlotCanvas.create_line( xAxis, self.height/2 + yCoor, xAxis-6, self.height/2 + yCoor, tags = 'axis', width = 2) # self.vowelPlotCanvas.create_line( xAxis, self.height/2 - yCoor, xAxis-6, self.height/2 - yCoor, tags = 'axis', width = 2) self.vowelPlotCanvas.create_text(xAxis + 10, yAxis1 - 15, text="Mouth Less Open", tags='axis', font=axisFont, anchor=CENTER) self.vowelPlotCanvas.create_text(xAxis + 10, yAxis4 + 10, text="Mouth More Open", tags='axis', font=axisFont, anchor=CENTER) #ArrowHead y self.vowelPlotCanvas.create_polygon( (self.width / 2, yAxis4 + 3, self.width / 2 - 10, yAxis4 - 17, self.width / 2 + 10, yAxis4 - 17), tags='axis', fill='#000000') self.vowelPlotCanvas.create_polygon( (self.width / 2, yAxis1 - 3, self.width / 2 - 10, yAxis1 + 17, self.width / 2 + 10, yAxis1 + 17), tags='axis', fill='#000000') #Draw x Axis yAxis = self.height / 2 xAxis1 = (self.width / 2) - self.targetSizeRatio * self.width / 2 - 25 xAxis2 = (self.width / 2) - self.targetSizeRatio * self.width * self.centreOval / ( self.outerOval * 2) xAxis3 = (self.width / 2) + self.targetSizeRatio * self.width * self.centreOval / ( self.outerOval * 2) xAxis4 = (self.width / 2) + self.targetSizeRatio * self.width / 2 + 25 self.vowelPlotCanvas.create_line(xAxis1, yAxis, xAxis2, yAxis, tags='axis', width=2) self.vowelPlotCanvas.create_line(xAxis3, yAxis, xAxis4, yAxis, tags='axis', width=2) # for index in range(2,10): # xCoor = index * ((self.xIdeal*self.outerOval)/10) # self.vowelPlotCanvas.create_line(self.width/2 + xCoor, yAxis, self.width/2 + xCoor, yAxis+6,tags = 'axis', width = 2) # self.vowelPlotCanvas.create_line(self.width/2 - xCoor, yAxis, self.width/2 - xCoor, yAxis+6,tags = 'axis', width = 2) self.vowelPlotCanvas.create_text(xAxis4 + 40, yAxis - 20, text="Tongue", tags='axis', font=axisFont, anchor=CENTER) self.vowelPlotCanvas.create_text(xAxis4 + 40, yAxis, text="Less", tags='axis', font=axisFont, anchor=CENTER) self.vowelPlotCanvas.create_text(xAxis4 + 40, yAxis + 20, text="Forward", tags='axis', font=axisFont, anchor=CENTER) self.vowelPlotCanvas.create_text(xAxis1 - 40, yAxis - 20, text="Tongue", tags='axis', font=axisFont, anchor=CENTER) self.vowelPlotCanvas.create_text(xAxis1 - 40, yAxis, text="More", tags='axis', font=axisFont, anchor=CENTER) self.vowelPlotCanvas.create_text(xAxis1 - 40, yAxis + 20, text="Forward", tags='axis', font=axisFont, anchor=CENTER) #ArrowHead x self.vowelPlotCanvas.create_polygon( (xAxis1 - 3, self.height / 2, xAxis1 + 17, self.height / 2 - 10, xAxis1 + 17, self.height / 2 + 10), tags='axis', fill='#000000') self.vowelPlotCanvas.create_polygon( (xAxis4 + 3, self.height / 2, xAxis4 - 17, self.height / 2 - 10, xAxis4 - 17, self.height / 2 + 10), tags='axis', fill='#000000') if self.axisButtonClicked: self.vowelPlotCanvas.itemconfig('axis', state='normal') self.questionMarkButton.config(relief=SUNKEN, bg='#FA4A4A') else: self.vowelPlotCanvas.itemconfig('axis', state='hidden') self.questionMarkButton.config(relief=GROOVE, bg='white') def toggleRecord(self, event): if self.Recording: self.stop() else: self.record() def goldStandardDiphthongs(self, id): path = self.path if id == 0: data = self.appendData(path, 'data\maori\longVowelNativeMale.txt') elif id == 1: data = self.appendData(path, 'data\maori\longVowelModernMale.txt') elif id == 2: data = self.appendData(path, 'data\maori\longVowelNativeFemale.txt') elif id == 3: data = self.appendData(path, 'data\maori\longVowelModernFemale.txt') return data """ appendData retrieves the data from the longVowelFile for a langType and voiceType. This code was edited from code written by ywan478 during his SoftEng206 Project 2010. """ def appendData(self, path, str): longVowelDataPath = os.path.join(path, str) longVowelFile = open(longVowelDataPath, 'r') longVowelData = [] #Load in the longVowel data for longVowelDataLine in longVowelFile: longVowelDataLine = longVowelDataLine.split() #F1 mean and standard deviation longVowelDataLine[0] = (float)(longVowelDataLine[0]) longVowelDataLine[1] = (float)(longVowelDataLine[1]) #F2 mean and standard deviation longVowelDataLine[2] = (float)(longVowelDataLine[2]) longVowelDataLine[3] = (float)(longVowelDataLine[3]) longVowelData.append(longVowelDataLine) return longVowelData #*********************************************************************************************************** # Sound ''' Creates the Snack Sound objects used in the formant plot. ''' def initialiseSounds(self): tkSnack.initializeSnack(self.root) self.recordedAudio = Sound() self.soundCopy = Sound() self.loadedAudio = Sound() """ Plot Fomrants takes a sound file and plots the last formant in the file. """ def plotFormants(self, sound): #SCALEREFIXTHIS self.hasPlots = True self.vowelPlotCanvas.delete('arrow') #Gets the probablity of sound being a voice for the last formant in the sound file. (false means last formant, true means all formants) probabilityOfVoicing = SoundProcessing.getProbabilityOfVoicing( sound, False) if True: #probabilityOfVoicing == 1.0: formant = SoundProcessing.getFormants(sound, self.id, False) #Only plot the latest formants of the sound if there's a good chance that it is the user speaking instead of background noise. if formant != None: radius = 3 color = 'black' yFormant = formant[0] xFormant = formant[1] (x, y) = self.switchCoorSystems(xFormant, yFormant) #Remove some background noise. if ((self.prevX - x)**2 + (self.prevY - y)**2)**0.5 < 28: self.vowelPlotCanvas.create_oval(x - radius, y - radius, x + radius, y + radius, fill=color, tags="userformants") self.xFormantList.append(xFormant) self.yFormantList.append(yFormant) self.plotCount += 1 if self.plotCount > 250: self.stop() #self.root.after(100 , self.displayFinalScore) self.distanceToScore(xFormant, yFormant) if (abs(y - self.y) > self.height): pass self.prevX = x self.prevY = y def replotFormants(self): #SCALEREFIXTHIS self.vowelPlotCanvas.delete("userformants") color = 'black' radius = 3 for index in range(len(self.xFormantList)): xFormant = self.xFormantList[index] yFormant = self.yFormantList[index] (x, y) = self.switchCoorSystems(xFormant, yFormant) self.vowelPlotCanvas.create_oval(x - radius, y - radius, x + radius, y + radius, fill=color, tags="userformants") """ record is called whent eh record button is pressed it starts recording the users sounds and makes the formant plot react accordingly. """ def record(self): try: if self.vowelScorer.safeToRecord(): self.Recording = True self.Waiting = False self.vowelPlotCanvas.itemconfig('questionMarkButton', state='hidden') self.formApp.preventResizing() self.xFormantList = [] self.yFormantList = [] self.recordedAudio = Sound() self.clear() self.plotCount = 0 self.notStopped = True self.vowelPlotCanvas.itemconfig('waitingText', state='hidden') #self.vowelPlotCanvas.itemconfig('axis', state='normal') self.vowelPlotCanvas.itemconfig('analysisButton', state='hidden') self.vowelPlotCanvas.itemconfig('firstButtons', state='hidden') self.vowelPlotCanvas.itemconfig('recording', state='normal') self.vowelPlotCanvas.itemconfig('Loudness', state='hidden') self.vowelPlotCanvas.itemconfig('tophelptext', state='hidden') self.vowelPlotCanvas.itemconfig('helptext', state='hidden') self.vowelPlotCanvas.itemconfig('score', state='hidden') self.recordedAudio.record() self.count2 = 0 self.vowelPlotCanvas.itemconfig('recording', state='normal', fill='orange') self.vowelPlotCanvas.itemconfig('recordingText', state='normal', text='Loading...') #LOADING AXIS. thread.start_new_thread(self.multiThreadUpdateCanvas, ("Thread-1", self.notStopped)) else: print "Not SafeToRecord, please Wait..." except Exception: self.requestQuit() def multiThreadUpdateCanvas(self, threadName, notStopped): sleep(1.2) self.setUpScore() self.isLoading = False #self.vowelPlotCanvas.itemconfig('axis', state='hidden') self.vowelPlotCanvas.itemconfig('score', state='normal') try: self.vowelPlotCanvas.itemconfig('recording', fill='red') self.vowelPlotCanvas.itemconfig('recordingText', text='Recording') while self.notStopped: self.count2 += 1 self.soundCopy.copy(self.recordedAudio) SoundProcessing.crop(self.soundCopy) self.plotFormants(self.soundCopy) if self.count2 % 10 == 0: self.updateLoudnessMeter(self.soundCopy) except Exception: import traceback print traceback.format_exc() def stop(self): if self.vowelScorer.safeToRecord(): self.notStopped = False self.vowelPlotCanvas.itemconfig('recording', state='hidden') self.vowelPlotCanvas.itemconfig('recordingText', state='hidden') self.vowelPlotCanvas.itemconfig('waitingText', state='normal') self.vowelPlotCanvas.itemconfig('toLoud', state='hidden') self.vowelPlotCanvas.itemconfig('toQuiet', state='hidden') self.vowelPlotCanvas.itemconfig('Loudness', state='hidden') self.vowelPlotCanvas.itemconfig('analysisButton', state='normal') self.vowelPlotCanvas.itemconfig('firstButtons', state='normal') self.vowelPlotCanvas.itemconfig('tophelptext', state='hidden') self.vowelPlotCanvas.itemconfig('helptext', state='hidden') self.vowelPlotCanvas.itemconfig('score', state='normal') self.vowelPlotCanvas.itemconfig('questionMarkButton', state='normal') self.recordedAudio.stop() self.root.after(100, self.loudnessMeter.clearMeter) self.Recording = False self.Waiting = True self.vowelScorer.updateScore(self.vowel, self.rawScore, self.plottedInfo) self.rawScore = 0 self.plotCounter = 0 self.plottedInfo = [0, 0, 0, 0, 0] self.root.after(500, self.requestFinalScore) self.vowelPlotCanvas.itemconfig('toLoud', state='hidden') self.vowelPlotCanvas.itemconfig('toQuiet', state='hidden') self.vowelPlotCanvas.itemconfig('Loudness', state='hidden') def requestFinalScore(self): self.displayFinalScore(self.vowelScorer.getLastScore()) self.vowelPlotCanvas.itemconfig('toLoud', state='hidden') self.vowelPlotCanvas.itemconfig('toQuiet', state='hidden') self.vowelPlotCanvas.itemconfig('Loudness', state='hidden') def clear(self): self.vowelPlotCanvas.delete('userformants') self.hasPlots = False def updateLoudnessMeter(self, sound): try: self.loudnessMeter.updateMeter(sound) except IndexError: print "No sound available to get loudness yet"