def __init__(self, parent, sound, width, height): tk.Frame.__init__(self, parent, background="white") # Call the superclass' constructor method self.parent = parent self.parent.title("Vowel Shapes") #self.pack(fill=tk.BOTH, expand=1) # set expand=0 if you don't want the window to be resizable # CJR make the sound object of tkSnack part of the Application class self.snd = sound #initialize the defaults for the sound self.sound_length = 1024 self.sound_pos = 0 self.id = None # Dimensions self.width = width self.height = height # The graphWin canvas self.graphWin = None self.setupCanvas() # The menubar self.menubar = tk.Menu(self.parent) self.parent.config(menu=self.menubar) # The menus self.setupFileMenu() self.setupActionMenu() self.setupDemoVowelMenu() self.setupDemoVizMenu() self.setupMicMenu() self.setupHelpMenu() # from the tkSnack demo files #Button(f, bitmap='snackRecord', fg='red', command=start).pack(side='left') #Button(f, bitmap='snackStop', command=stop).pack(side='left') #Button(f, text='Exit', command=root.quit).pack(side='left') # The buttons self.recordButton = tk.Button(self.parent, text="Record", command=self.record) self.recordButton.grid(column=0, row=1) self.saveVowelAnnotation = None self.annotationButton = tk.Button(self.parent, text="Set Save Name", command=self.vowelAnnotationBox) self.annotationButton.grid(column=0, row=2) self.playButton = tk.Button(self.parent, text="Play", command=self.play) self.playButton.grid(column=1, row=1) # Demo matching vowel self.matchVowel = tk.Label(self.parent, text="") self.matchVowel.grid(column=1, row=2) # CJR # options for the file dialogs self.save_file_opt = options = {} options['filetypes'] = [('all files', '.*'), ('audio files', '.mp3')] options['initialfile'] = 'myfile_i.mp3' options['parent'] = self.parent # read the application configuration file self.defaultSetup = self.readConfiguration("./vowelShapeConfig.txt") # Vowel objects self.loadDefaultVowel() # loads the example vowel and corrects the display self.activeVowel = None #initialize the graphics module self.graphModule = GraphicsModule(self.graphWin, self.defaultSetup.viz, self.exampleVowel.getAnnotation(), self.exampleVowel.getF()) self.setupGraph(self.defaultSetup.viz) self.graphModule.drawMatchingViz(self.defaultSetup.defFormants)
#c.update_idletasks() id = root.after(100,draw) def readConfiguration(filename): configVars = VowelShapeConfig(filename) print(configVars.viz, configVars.mode) print(configVars.defFormants, configVars.defVowel) return configVars if __name__ == '__main__': # read the configuration file defaultSetup = readConfiguration("./vowelShapeConfig.txt") # initialize the state of the application stateOfApp = VowelShapesState(defaultSetup.viz, sound=snd) #initialize the graphics module graphModule = GraphicsModule(stateOfApp.useViz, defaultSetup.defVowel, defaultSetup.defFormants, w, h) if graphModule.originViz : graphModule.axesDraw() if (defaultSetup.mode == "Practice") : graphModule.drawMatchingViz(defaultSetup.defFormants) #c = SnackCanvas(height=h, width=w, bg='black') #c.pack() #f = Frame() #f.pack() #draw() start() #Button(f, bitmap='snackRecord', fg='red', command=start).pack(side='left') #Button(f, bitmap='snackStop', command=stop).pack(side='left') #Button(f, text='Exit', command=root.quit).pack(side='left') #root.mainloop() #draw()
class Application(tk.Frame): def __init__(self, parent, sound, width, height): tk.Frame.__init__(self, parent, background="white") # Call the superclass' constructor method self.parent = parent self.parent.title("Vowel Shapes") #self.pack(fill=tk.BOTH, expand=1) # set expand=0 if you don't want the window to be resizable # CJR make the sound object of tkSnack part of the Application class self.snd = sound #initialize the defaults for the sound self.sound_length = 1024 self.sound_pos = 0 self.id = None # Dimensions self.width = width self.height = height # The graphWin canvas self.graphWin = None self.setupCanvas() # The menubar self.menubar = tk.Menu(self.parent) self.parent.config(menu=self.menubar) # The menus self.setupFileMenu() self.setupActionMenu() self.setupDemoVowelMenu() self.setupDemoVizMenu() self.setupMicMenu() self.setupHelpMenu() # from the tkSnack demo files #Button(f, bitmap='snackRecord', fg='red', command=start).pack(side='left') #Button(f, bitmap='snackStop', command=stop).pack(side='left') #Button(f, text='Exit', command=root.quit).pack(side='left') # The buttons self.recordButton = tk.Button(self.parent, text="Record", command=self.record) self.recordButton.grid(column=0, row=1) self.saveVowelAnnotation = None self.annotationButton = tk.Button(self.parent, text="Set Save Name", command=self.vowelAnnotationBox) self.annotationButton.grid(column=0, row=2) self.playButton = tk.Button(self.parent, text="Play", command=self.play) self.playButton.grid(column=1, row=1) # Demo matching vowel self.matchVowel = tk.Label(self.parent, text="") self.matchVowel.grid(column=1, row=2) # CJR # options for the file dialogs self.save_file_opt = options = {} options['filetypes'] = [('all files', '.*'), ('audio files', '.mp3')] options['initialfile'] = 'myfile_i.mp3' options['parent'] = self.parent # read the application configuration file self.defaultSetup = self.readConfiguration("./vowelShapeConfig.txt") # Vowel objects self.loadDefaultVowel() # loads the example vowel and corrects the display self.activeVowel = None #initialize the graphics module self.graphModule = GraphicsModule(self.graphWin, self.defaultSetup.viz, self.exampleVowel.getAnnotation(), self.exampleVowel.getF()) self.setupGraph(self.defaultSetup.viz) self.graphModule.drawMatchingViz(self.defaultSetup.defFormants) def setupCanvas(self): self.graphWin = g.GraphWin(self) #self.graphWin.setCoords(0,0,100,75) self.graphWin.setCoords(0,0,100,75) self.graphWin.grid(column=0,row=0,columnspan=2) # Menus def setupFileMenu(self): fileMenu = tk.Menu(self.menubar) fileMenu.add_command(label="About", command=self.showAbout) fileMenu.add_command(label="Save Vowel", command=self.saveVowel) fileMenu.add_command(label="Load Vowel", command=self.loadVowel) fileMenu.add_command(label="Load Vowel Sound", command=self.loadVowelSound) fileMenu.add_command(label="Clear Vowel", command=self.clearVowel) fileMenu.add_command(label="Exit", command=self.exitApp) # CJR added an exit function self.menubar.add_cascade(label="File", menu=fileMenu) def setupActionMenu(self): actionMenu = tk.Menu(self.menubar) #actionMenu.add_command(label="Test Canvas", command = self.testCanvas) actionMenu.add_radiobutton(label="Mentor", command = self.mentorMode) actionMenu.add_radiobutton(label="Study", command = self.studyMode) actionMenu.add_radiobutton(label="Practice", command = self.practiceMode) actionMenu.add_radiobutton(label="Review", command = self.reviewMode) self.menubar.add_cascade(label="Mode", menu = actionMenu) def setupHelpMenu(self): helpMenu = tk.Menu(self.menubar) helpMenu.add_command(label="The Different Modes", command=self.modesHelp) self.menubar.add_cascade(label="Help", menu=helpMenu) # only for the Demo - Vowels and Viza def setupDemoVowelMenu(self): demoMenu = tk.Menu(self.menubar) demoMenu.add_command(label="i", command=self.loadi) demoMenu.add_command(label="I", command=self.loadI) demoMenu.add_command(label="E", command=self.loadE) demoMenu.add_command(label="ae", command=self.loadae) demoMenu.add_command(label="as", command=self.loadas) demoMenu.add_command(label="o", command=self.loado) demoMenu.add_command(label="u", command=self.loadu) self.menubar.add_cascade(label="Vowel", menu=demoMenu) def setupDemoVizMenu(self): demovizMenu = tk.Menu(self.menubar) demovizMenu.add_command(label="Graph", command=self.doGraph) demovizMenu.add_command(label="Triangle", command=self.doTriangle) demovizMenu.add_command(label="Oval", command=self.doOval) self.menubar.add_cascade(label="Viz", menu=demovizMenu) def setupMicMenu(self): micMenu = tk.Menu(self.menubar) if (useTkSnack) : # build the menu using the microphones listed self.inputDevices = tkSnack.audio.inputDevices() self.inputSelected = [] count = 0 for mic in self.inputDevices : print("mic ", mic, " count ", count) micMenu.add_radiobutton(label=mic, command=lambda index=count : self.setInputDevice(index)) count = count + 1 self.menubar.add_cascade(label="Input Devices", menu=micMenu) def setInputDevice(self, item): print("select ", item) tkSnack.audio.selectInput(self.inputDevices[item]) # menu commands def saveVowel(self): # make sure there is something to save if (self.snd.length() > 0) : # should also check the the formants exist ??? # need a name to save with the vowel - is this the annotation also? if (self.saveVowelAnnotation) : self.activeVowel.setAnnotation(self.saveVowelAnnotation) #path = tk.filedialog.asksaveasfilename() # CJR Windows needed this form with the import at the start of the file fileSave = filedialog.asksaveasfilename() if (fileSave) : # the user did not cancel the operation if (self.snd.length() > 0) : # this save the formant values of the last note # we could resample the snd object and get the formants from # the whole clip ??? self.activeVowel.saveToFile(fileSave) # this saves the audio as a wav file # it is saved in the save directory with the annotation name path, filename = os.path.split(fileSave) # WARNING - this is platform dependent ??? sndFileName = self.activeVowel.getAnnotation() + ".wav" sndPath = path + "/" + sndFileName os.path.join(path, sndFileName) self.snd.write(sndPath) self.snd.flush() self.saveVowelAnnotation = None # CJR is this the correct action? reload the default vowel as # the example vowel self.clearVowel() self.loadDefaultVowel() self.graphModule.drawMatchingViz(self.defaultSetup.defFormants) else : #request that the user supply an annotation messagebox.showinfo("Need an annotation", "Please click on the Set Save Name button and enter an annotation for this vowel") else : #request that the user record a vowel first messagebox.showinfo("Record a Vowel", "Please record a vowel and use the Set Save Name button to associate an annotation") self.parent.focus_force() def loadVowel(self): # path = tk.filedialog.askopenfilename() # CJR Windows needed this form with the import at the start of the file filename = filedialog.askopenfilename() if (filename) : # clear the old vowel self.graphModule.unDrawVowels() # the selected file is the formant file self.exampleVowel = Vowel(0,0,0, filename) # add the vowel annotation to the Label self.loadAnyVowel(self.exampleVowel.getF(), self.exampleVowel.annotation) #self.matchVowel.config(text=self.exampleVowel.getAnnotation()) #self.setupGraph(self.defaultSetup.viz) self.parent.focus_force() # load a previously saved sound file def loadVowelSound(self): fileName = filedialog.askopenfilename() if (fileName) : # load the file into the sound object # disable record and enable play if (useTkSnack) : self.snd.flush() self.snd.read(fileName) self.recordButton.config(state=tk.NORMAL) self.playButton.config(state=tk.NORMAL) # load the filename into the annotation path, filename = os.path.split(fileName) self.matchVowel.config(text=filename) self.defaultSetup.soundFile = fileName self.defaultSetup.soundFilename = filename self.parent.focus_force() def clearVowel(self): self.exampleVowel = None self.defaultSetup.soundFile = "" self.defaultSetup.soundFilename = "" self.recordButton.config(state=tk.NORMAL) self.playButton.config(state=tk.NORMAL) # remove the vowel text from the Label self.matchVowel.config(text="") # undraw all the vowels - active and example self.graphModule.unDrawVowels() # CJR added an exit function def exitApp(self): # CJR how do we exit a Tcl application cleanly? self.parent.destroy() self.parent.quit() def modesHelp(self): f = open("modesHelp.txt", "r") msg = f.read() self.vowelAnnotationBox(msg, False) def showAbout(self): f = open("about.txt", "r") msg = f.read() # Use Cyndi's vowelAnnotationBox to display the about information self.vowelAnnotationBox(msg, False) # demo only - viz and vowel # demo changing of the matching vowel def loadi(self): formants = [ [274.2, 2022.0, 3012.4] ] self.loadAnyVowel(formants, "i") def loadI(self): formants = [ [268.8, 2353.4, 3420.8] ] self.loadAnyVowel(formants, "I") def loadE(self): formants = [ [492.7, 2088.3, 2656.1] ] self.loadAnyVowel(formants, "E") def loadae(self): formants = [ [753.9, 1619.9, 2494.4] ] self.loadAnyVowel(formants, "ae") def loadas(self): formants = [ [707.6, 1027.2, 2695.7] ] self.loadAnyVowel(formants, "as") def loado(self): formants = [ [405.6, 696.7, 2779.6] ] self.loadAnyVowel(formants, "o") def loadu(self): formants = [ [360.2, 858.6, 2654.7] ] self.loadAnyVowel(formants, "u") # load any vowel with formant and annotation def loadAnyVowel(self, formantList, annotation): self.defaultSetup.defFormants = formantList self.defaultSetup.defVowel = annotation self.loadDefaultVowel() # undraw all the old vowels - active and example self.graphModule.unDrawVowels() self.graphModule.drawMatchingViz(self.defaultSetup.defFormants) # load the vowel that is the application default vowel def loadDefaultVowel(self): f1 = self.defaultSetup.defFormants[0][0] f2 = self.defaultSetup.defFormants[0][1] f3 = self.defaultSetup.defFormants[0][2] self.exampleVowel = Vowel(f1, f2, f3, '') self.exampleVowel.setAnnotation(self.defaultSetup.defVowel) if (len(self.defaultSetup.soundFile)>0) : self.matchVowel.config(text=self.defaultSetup.soundFilename) else : self.matchVowel.config(text=self.defaultSetup.defVowel) # for demo vizs menu items def doGraph(self): self.setupGraph("Graph") def doTriangle(self): self.setupGraph("Triangle") def doOval(self): self.setupGraph("Oval") # Action menu items - the modes of the application # Practice mode - How should this work? def practiceMode(self): # if coming from the mentor mode reload the default vowel if (self.defaultSetup.mode == "Mentor") : self.loadDefaultVowel() self.graphModule.drawMatchingViz(self.defaultSetup.defFormants) # Practice mode should enable and disable the correct buttons # and should make sure that there is a vowel loaded if (self.exampleVowel) : # there is a sample vowel loaded - diable the Play button self.playButton.config(state=tk.DISABLED) self.recordButton.config(state=tk.NORMAL) # set the state in the defaault configuration self.defaultSetup.mode = "Practice" else : # no sample vowel let the user know to do this first messagebox.showinfo("Need to Load Vowel", "Practice Mode requires a vowel to be loaded. Please load a vowel with File->Load Vowel") self.parent.focus_force() # Mentor mode - How should this work? def mentorMode(self): # it should also clear all previous vowel drawings - clearVowels resets # the buttons - do it first self.clearVowel() # Then Mentor mode should enable and disable the correct buttons self.playButton.config(state=tk.DISABLED) self.recordButton.config(state=tk.NORMAL) # set the state in the defaault configuration self.defaultSetup.mode = "Mentor" # Study mode - How should this work def studyMode(self): # if coming from the mentor mode reload the default vowel if (self.defaultSetup.mode == "Mentor") : self.loadDefaultVowel() self.graphModule.drawMatchingViz(self.defaultSetup.defFormants) # Study mode should enable and disable the correct buttons # and should make sure that there is a vowel sound loaded if ( self.exampleVowel and len(self.defaultSetup.soundFile) > 0) : # there is a sample vowel sound selected - diable the record button self.recordButton.config(state=tk.DISABLED) self.playButton.config(state=tk.NORMAL) # load the sound file self.snd.read(self.defaultSetup.soundFile) # CJR also need to associate an annotation with the sound file somehow - name of file? # set the state in the defaault configuration self.defaultSetup.mode = "Study" else : # no sample vowel let the user know to do this first messagebox.showinfo("Need to Load Vowel Sound", "Study Mode requires a vowel to be loaded. Please load a vowel sound with File->Load Vowel Sound") self.parent.focus_force() # Review mode - How should this work def reviewMode(self): # if coming from the mentor mode reload the default vowel if (self.defaultSetup.mode == "Mentor") : self.loadDefaultVowel() self.graphModule.drawMatchingViz(self.defaultSetup.defFormants) # Review mode should enable and disable the correct buttons # and should make sure that there is a vowel loaded if (self.exampleVowel) : # there is a sample vowel loaded - diable the Play button self.recordButton.config(state=tk.DISABLED) self.playButton.config(state=tk.NORMAL) # set the state in the defaault configuration self.defaultSetup.mode = "Review" else : # no sample vowel let the user know to do this first messagebox.showinfo("Need to Load Vowel", "Review Mode requires a vowel to be loaded. Please load a vowel with File->Load Vowel") self.parent.focus_force() def testCanvas(self): box = g.Rectangle( Point(1,1), Point(99,74)) box.draw(self.graphWin) # Button commands def record(self): print("in the record method ", self.id) if (self.recordButton["text"] == "Record") : self.recordButton.config(text="Stop") self.playButton.config(state=tk.DISABLED) # initialize the activeVowel self.activeVowel = Vowel(10,10,10,"") # CJR add in the tkSnack commands to start the recording if (useTkSnack) : self.snd.flush() self.snd.record() self.start() else: print("Stop") self.recordButton.config(text="Record") # check the state - do not make the Record button normal # when in Study/Review mode if (self.defaultSetup.mode == "Practice" or self.defaultSetup.mode == "Mentor") : self.playButton.config(state=tk.NORMAL) # CJR add in the tkSnack commands to stop the recording if (useTkSnack) : self.snd.stop() self.stop() print("exiting the record method ", self.id) def start(self): self.id = self.parent.after(100,self.draw) def stop(self): self.parent.after_cancel(self.id) def play(self): if self.playButton["text"] == "Play": self.playButton.config(text="Stop") self.recordButton.config(state=tk.DISABLED) if (useTkSnack) : self.snd.play() else: self.playButton.config(text="Play") self.recordButton.config(state=tk.NORMAL) if (useTkSnack) : self.snd.stop() # CJR window methods def draw(self): #print("draw ", self.id) if (useTkSnack) : if (self.snd.length() > self.sound_length) : self.sound_pos = self.snd.length() - self.sound_length formants = self.snd.formant(start=self.sound_pos,numformants=4, framelength=0.005, windowtype='Hanning', windowlength=0.024, lpctype=1) #print(formants[0][0], formants[0][1], formants[0][2], formants[0][3] ) fSum = [ sum(x) for x in zip(*formants) ] fLength = len(formants) fAvg = [x/fLength for x in fSum] audioData = [ [ fAvg[0], fAvg[1], fAvg[2] ] ] formantList = [ fAvg[0], fAvg[1], fAvg[2] ] self.activeVowel.setF(formantList) #print(fLength, formantList) else : # CJR [f1, f2, f3] duplicate for now - change later when Mac works audioData = [ [274.2, 2022.0, 3012.4], #i [268.8, 2353.4, 3420.8], #I [492.7, 2088.3, 2656.1], #E [753.9, 1619.9, 2494.4], #ae [707.6, 1027.2, 2695.7], #\as [405.6, 696.7, 2779.6], #o [360.2, 858.6, 2654.7] #u ] else: # [f1, f2, f3] audioData = [ [274.2, 2022.0, 3012.4], #i [268.8, 2353.4, 3420.8], #I [492.7, 2088.3, 2656.1], #E [753.9, 1619.9, 2494.4], #ae [707.6, 1027.2, 2695.7], #\as [405.6, 696.7, 2779.6], #o [360.2, 858.6, 2654.7] #u ] if self.graphModule.useViz == "Graph" : self.graphModule.drawWithGraph(audioData) elif self.graphModule.useViz == "Oval" : self.graphModule.drawWithOval(audioData) elif self.graphModule.useViz == "Triangle" : self.graphModule.drawWithTriangle(audioData) # if (useTkSnack) : # if (self.snd.length(unit='sec') > 20) : # print("calling stop") # # CJR calling record as if it was clicked will stop the recording # # as the predetermined time. # self.record() # # CJR let's see if pausing for a second helps the jitter display # time.sleep(0.25) # else : # time.sleep(1) self.id = self.parent.after(100,self.draw) # CJR how to stop the process when the window is closed with the X def close(self): self.parent.quit() # CJR undraw and draw the new configuration def setupGraph(self, viz): self.graphModule.unDrawVowels() self.graphModule.reDraw(viz) if self.graphModule.originViz : self.graphModule.axesDraw() if (self.defaultSetup.mode == "Practice") : self.graphModule.drawMatchingViz(self.exampleVowel.getF()) # CJR application setup and configuration methods. def readConfiguration(self, filename): configVars = VowelShapeConfig(filename) #print(configVars.viz, configVars.mode) #print(configVars.defFormants, configVars.defVowel) return configVars # CJR pop up window to collect the name to save a vowel under # complements of # http://stackoverflow.com/questions/10057672/correct-way-to-implement-a-custom-popup-tkinter-dialog-box # def vowelAnnotationBox(self, msg='Enter an annotation for the Vowel', extra=True): top = self.top = tk.Toplevel(self) label0 = tk.Label(top, text=msg) label0.pack() if extra: self.entry0 = tk.Entry(top) self.entry0.pack() self.entry0.focus_set() button2 = tk.Button(top, text='Submit', command=self.submitVowelName) button2.pack() button3 = tk.Button(top, text='Cancel', command=lambda: self.top.destroy()) button3.pack() top.focus_force() def submitVowelName(self): data = self.entry0.get() if data: self.saveVowelAnnotation = data self.top.destroy()