def buildCfg(self): # begin by adding the global soundfont or cfg file. filename =self.currentSoundfont[0] strCfg = '' if filename.endswith('.cfg'): path, justname = os.path.split(filename) if self.audioOptsWindow and self.audioOptsWindow.getUseOldTimidity(): if ' ' in justname: midirender_util.alert("Warning: old version of Timidity probably doesn't support spaces in filenames.") if justname.lower()!='timidity.cfg' and os.path.exists(path+os.sep+'timidity.cfg'): midirender_util.alert("Warning: there is a filename timidity.cfg in this directory and so the old version of Timidity will use it.") strCfg += '\nsource %s' % (justname) else: strCfg += '\ndir "%s"\nsource "%s"' % (path, filename) else: strCfg += '\nsoundfont "%s"' % (filename) if self.audioOptsWindow is not None and self.audioOptsWindow.getPatchesTakePrecedence(): strCfg +=' order=1' # now add customization to override specific voices, if set if self.soundfontWindow is not None: strCfg += '\n' + self.soundfontWindow.getCfgResults(self.audioOptsWindow and self.audioOptsWindow.getUseOldTimidity()) if self.audioOptsWindow is not None: addedLines = self.audioOptsWindow.getAdditionalCfg() if addedLines: strCfg += '\n'+'\n'.join(addedLines) return strCfg
def onBtnSaveWave(self,e=None): if not self.isMidiLoaded: return filename = midirender_util.ask_savefile(title="Create Wav File", types=['.wav|Wav file']) if not filename: return midicopy = self.buildModifiedMidi() if self.audioOptsWindow != None: arParams = self.audioOptsWindow.createTimidityOptionsList(includeRenderOptions=True) if arParams==None: return #evidently an error occurred over there else: arParams =['-Ow'] arParams.append('-o') arParams.append(filename) #Play it synchronously, meaning that the whole program stalls while this happens... midirender_util.alert('Beginning wave process. Be patient... this may take a few moments...') objplayer = midirender_runtimidity.RenderTimidityMidiPlayer() objplayer.setConfiguration(self.buildCfg()) objplayer.setParameters(arParams) objplayer.playMidiObject(midicopy, bSynchronous=True) if self.consoleOutWindow!=None: self.consoleOutWindow.clear(); self.consoleOutWindow.writeToWindow(objplayer.strLastStdOutput) midirender_util.alert('Completed.')
def getParamsForTimidity(self, bRenderWav): directoryForOldTimidity = None if self.audioOptsWindow is not None: params = self.audioOptsWindow.getOptionsList(includeRenderOptions=bRenderWav) if params is None: midirender_util.alert('Could not get parameters.') return None, None if self.audioOptsWindow.getUseOldTimidity(): if not self.currentSoundfont[0].lower().endswith('.cfg'): midirender_util.alert('Provide a cfg with no soundfonts for old timidity.') return None, None directoryForOldTimidity = os.path.split(self.currentSoundfont[0])[0] else: if bRenderWav: # in Linux render 24bit by default (workaround for timidity++ bug 710927) if not sys.platform.startswith('win'): params = ['-Ow2'] else: params = ['-Ow'] else: params = [] if self.transposePitches is not None and (not self.audioOptsWindow or not self.audioOptsWindow.getUseOldTimidity()): params.append('--adjust-key=%d' % int(self.transposePitches)) return params, directoryForOldTimidity
def getOptionsList(self, includeRenderOptions=False): try: return self.getOptionsListImpl(includeRenderOptions) except: e = sys.exc_info() midirender_util.alert('Alert: ' + str(e)) return None
def getParamsForTimidity(self, bRenderWav): directoryForOldTimidity = None if self.audioOptsWindow is not None: params = self.audioOptsWindow.getOptionsList( includeRenderOptions=bRenderWav) if params is None: midirender_util.alert('Could not get parameters.') return None, None if self.audioOptsWindow.getUseOldTimidity(): if not self.currentSoundfont[0].lower().endswith('.cfg'): midirender_util.alert( 'Provide a cfg with no soundfonts for old timidity.') return None, None directoryForOldTimidity = os.path.split( self.currentSoundfont[0])[0] else: if bRenderWav: # in Linux render 24bit by default (workaround for timidity++ bug 710927) # this bug is now fixed in the distributions I've tested if not sys.platform.startswith('win'): params = ['-Ow2'] else: params = ['-Ow'] else: params = [] if self.transposePitches is not None and ( not self.audioOptsWindow or not self.audioOptsWindow.getUseOldTimidity()): params.append('--adjust-key=%d' % int(self.transposePitches)) return params, directoryForOldTimidity
def create_menubar(self,root): root.bind('<Control-space>', self.onBtnPlay) root.bind('<Control-r>', self.onBtnSaveWave) root.bind('<Control-f>', self.openSoundfontWindow) root.bind('<Alt-F4>', lambda x:root.quit) root.bind('<Control-o>', self.menu_openMidi) root.bind('<Control-S>', self.saveModifiedMidi) root.bind('<Control-m>', self.openMixerView) root.bind('<Control-p>', self.openAudioOptsWindow) menubar = Menu(root) menuFile = Menu(menubar, tearoff=0) menubar.add_cascade(label="File", menu=menuFile, underline=0) menuFile.add_command(label="Open MIDI", command=self.menu_openMidi, underline=0, accelerator='Ctrl+O') menuFile.add_separator() menuFile.add_command(label="Modify MIDI Events...", command=self.menuModifyRawMidi, underline=0) menuFile.add_separator() menuFile.add_command(label="Save Changes From Mixer...", command=self.saveModifiedMidi, underline=0, accelerator='Ctrl+Shift+S') menuFile.add_separator() menuFile.add_command(label="Exit", command=root.quit, underline=1) menuAudio = Menu(menubar, tearoff=0) menubar.add_cascade(label="Audio", menu=menuAudio, underline=0) menuAudio.add_command(label="Play", command=self.onBtnPlay, underline=0) menuAudio.add_command(label="Pause", command=self.onBtnPause, underline=2) menuAudio.add_command(label="Stop", command=self.onBtnStop, underline=0) menuAudio.add_separator() menuAudio.add_command(label="Audio Options...", command=self.openAudioOptsWindow, underline=6, accelerator='Ctrl+P') menuAudio.add_command(label="Change Tempo...", command=self.menu_changeTempo, underline=0) menuAudio.add_command(label="Transpose Pitch...", command=self.menu_changeTranspose, underline=0) menuAudio.add_command(label="Copy Options String", command=self.menuCopyAudioOptsString, underline=1) menuAudio.add_separator() menuAudio.add_command(label="Save Wav", command=self.onBtnSaveWave, underline=5, accelerator='Ctrl+R') menuAudio.add_separator() menuAudio.add_command(label="Choose SoundFont...", command=self.openSoundfontWindow, underline=13, accelerator='Ctrl+F') self.objOptionsDuration = IntVar() self.objOptionsDuration.set(0) self.objOptionsBarlines = IntVar() self.objOptionsBarlines.set(1) menuView = Menu(menubar, tearoff=0) menubar.add_cascade(label="View", menu=menuView, underline=0) menuView.add_command(label="Mixer", command=self.openMixerView, underline=0, accelerator='Ctrl+M') menuView.add_command(label="Console Output", command=self.menu_openConsoleWindow, underline=0) menuView.add_separator() menuView.add_command(label="SoundFont Information Tool", command=self.menu_soundFontInfoTool, underline=0) menuView.add_separator() menuView.add_checkbutton(label="Show Durations in score", variable=self.objOptionsDuration, underline=5, onvalue=1, offvalue=0) menuView.add_checkbutton(label="Show Barlines in score", variable=self.objOptionsBarlines, underline=5, onvalue=1, offvalue=0) menuHelp = Menu(menubar, tearoff=0) menuHelp.add_command(label='About', underline=0, command=(lambda: midirender_util.alert('Bmidi to wav, by Ben Fisher 2009\nA graphical frontend for Timidity and sfubar.\n\nSee the documentation at https://github.com/moltenjs/labs_youthful_projects/blob/master/benmidi/README.md\n\nSource code at https://github.com/moltenjs/labs_youthful_projects/tree/master/benmidi/bmidirender','Bmidi to wav'))) menuHelp.add_command(label='Documentation', underline=0, command=(lambda: midirender_util.alert('There are many pages of online documentation at https://github.com/moltenjs/labs_youthful_projects/blob/master/benmidi/README.md','Bmidi to wav'))) menubar.add_cascade(label="Help", menu=menuHelp, underline=0) root.config(menu=menubar)
def menu_changeTranspose(self, e=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return strPrompt = 'Adjust key (i.e., transpose the song) by n half tones. Valid range is from -24 to 24.' default = self.transposePitches if self.transposePitches else 0.0 res = midirender_util.ask_float(strPrompt, default=default, min=-24.1, max=24.1, title='Transpose') if res is not None and res is not False: self.transposePitches = int(res)
def getVariableValueAsIntOrNoneIfDefault(self, val, varname, min, max, defaultValueMapToNone): if val == '' or val==defaultValueMapToNone: return None try: val = int(val); valid = val >= min and val<=max except: valid=False if not valid: midirender_util.alert('%s must be an integer %d to %d' % (varname, min, max)) return None else: return val
def onBtnPlay(self, e=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return params, directoryForOldTimidity = self.getParamsForTimidity(False) bypassTimidity = False if self.audioOptsWindow is not None: bypassTimidity = self.audioOptsWindow.getBypassTimidity() if params is not None: self.player.actionPlay(self.buildModifiedMidi(), params, self.buildCfg(), directoryForOldTimidity, not bypassTimidity)
def menu_changeTempo(self, e=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return res = midirender_tempo.queryChangeTempo(self.objMidi, self.tempoScaleFactor) if res is None: return # canceled. if abs(res-1.0) < 0.001: # we don't need to change the tempo if it is staying the same. self.tempoScaleFactor = None else: self.tempoScaleFactor = res
def openMixerView(self, e=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return if self.mixerWindow: return # only allow one instance open at a time top = Toplevel() def callbackOnClose(): self.mixerWindow = None self.mixerWindow = midirender_mixer.BMixerWindow(top, self.objMidi, {}, callbackOnClose) top.focus_set()
def openAudioOptsWindow(self, evt=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return if self.audioOptsWindow: return # only allow one instance open at a time top = Toplevel() def callbackOnClose(): self.audioOptsWindow = None self.audioOptsWindow = midirender_audiooptions.WindowAudioOptions(top, callbackOnClose) top.focus_set()
def menu_openConsoleWindow(self): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return if self.consoleOutWindow: return # only allow one instance open at a time top = Toplevel() def callbackOnClose(): self.consoleOutWindow = None self.consoleOutWindow = midirender_consoleout.BConsoleOutWindow(top, self.consoleOutCallback, callbackOnClose=callbackOnClose) top.focus_set()
def openScoreView(self, n): if len(self.objMidi.tracks[n].notelist)==0: midirender_util.alert('No notes to show in this track.') return opts = {} opts['show_durations'] = self.objOptionsDuration.get() opts['show_barlines'] = self.objOptionsBarlines.get() opts['show_stems'] = 1; opts['prefer_flats'] = 0 opts['clefspath'] = clefspath top = Toplevel() window = scoreview.ScoreViewWindow(top, n, self.objMidi.tracks[n],self.objMidi.ticksPerQuarterNote, opts) self.scoreviews[n] = top top.focus_set()
def saveModifiedMidi(self, evt=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return filename = midirender_util.ask_savefile(title="Save Midi File", types=['.mid|Mid file']) if not filename: return mfile = self.buildModifiedMidi() if mfile: mfile.open(filename,'wb') mfile.write() mfile.close()
def saveModifiedMidi(self, evt=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return filename = midirender_util.ask_savefile(title="Save Midi File", types=['.mid|Mid file']) if not filename: return mfile = self.buildModifiedMidi() if mfile: mfile.open(filename, 'wb') mfile.write() mfile.close()
def openSoundfontWindow(self, e=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return # this is different than the list and score view - there can only be one of them open at once if self.soundfontWindow: return # only allow one instance open at a time top = Toplevel() def callbackOnClose(): self.soundfontWindow = None self.soundfontWindow = midirender_soundfont.BSoundfontWindow(top, self.currentSoundfont, self.objMidi, callbackOnClose) top.focus_set()
def menu_changeTempo(self, e=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return res = midirender_tempo.queryChangeTempo(self.objMidi, self.tempoScaleFactor) if res is None: return # canceled. if abs( res - 1.0 ) < 0.001: # we don't need to change the tempo if it is staying the same. self.tempoScaleFactor = None else: self.tempoScaleFactor = res
def menu_openMidi(self, evt=None): filename = midirender_util.ask_openfile(title="Open Midi File", types=['.mid|Mid file']) if not filename: return #first, see if it loads successfully. try: newmidi = bmidilib.BMidiFile() newmidi.open(filename, 'rb') newmidi.read() newmidi.close() except: e='' midirender_util.alert('Could not load midi: exception %s'%str(e), title='Could not load midi',icon='error') return self.lblFilename['text'] = filename self.loadMidiObj(newmidi)
def openMixerView(self, e=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return if self.mixerWindow: return # only allow one instance open at a time top = Toplevel() def callbackOnClose(): self.mixerWindow = None self.mixerWindow = midirender_mixer.BMixerWindow( top, self.objMidi, {}, callbackOnClose) top.focus_set()
def menu_openConsoleWindow(self): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return if self.consoleOutWindow: return # only allow one instance open at a time top = Toplevel() def callbackOnClose(): self.consoleOutWindow = None self.consoleOutWindow = midirender_consoleout.BConsoleOutWindow( top, self.consoleOutCallback, callbackOnClose=callbackOnClose) top.focus_set()
def openAudioOptsWindow(self, evt=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return if self.audioOptsWindow: return # only allow one instance open at a time top = Toplevel() def callbackOnClose(): self.audioOptsWindow = None self.audioOptsWindow = midirender_audiooptions.WindowAudioOptions( top, callbackOnClose) top.focus_set()
def openSoundfontWindow(self, e=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return # this is different than the list and score view - there can only be one of them open at once if self.soundfontWindow: return # only allow one instance open at a time top = Toplevel() def callbackOnClose(): self.soundfontWindow = None self.soundfontWindow = midirender_soundfont.BSoundfontWindow( top, self.currentSoundfont, self.objMidi, callbackOnClose) top.focus_set()
def getParamsForTimidity(self, bRenderWav): directoryForOldTimidity = None if self.audioOptsWindow != None: params = self.audioOptsWindow.createTimidityOptionsList(includeRenderOptions=bRenderWav) if params==None: midirender_util.alert('Unknown error.') return None, None if self.audioOptsWindow.getUseOldTimidity(): if not self.currentSoundfont[0].lower().endswith('.cfg'): midirender_util.alert('Provide a cfg with no soundfonts for old timidity.') return None, None directoryForOldTimidity = os.path.split(self.currentSoundfont[0])[0] else: if bRenderWav: params = ['-Ow'] else: params = [] return params, directoryForOldTimidity
def create_menubar(self,root): root.bind('<Control-space>', self.onBtnPlay) root.bind('<Control-r>', self.onBtnSaveWave) root.bind('<Control-f>', self.openSoundfontWindow) root.bind('<Alt-F4>', lambda x:root.quit) root.bind('<Control-o>', self.menu_openMidi) root.bind('<Control-S>', self.saveModifiedMidi) root.bind('<Control-m>', self.openMixerView) root.bind('<Control-p>', self.openAudioOptsWindow) menubar = Menu(root) menuFile = Menu(menubar, tearoff=0) menubar.add_cascade(label="File", menu=menuFile, underline=0) menuFile.add_command(label="Open Midi", command=self.menu_openMidi, underline=0, accelerator='Ctrl+O') menuFile.add_separator() menuFile.add_command(label="Modify raw midi...", command=self.menuModifyRawMidi, underline=0) menuFile.add_separator() menuFile.add_command(label="Save modified midi...", command=self.saveModifiedMidi, underline=0, accelerator='Ctrl+Shift+S') menuFile.add_separator() menuFile.add_command(label="Exit", command=root.quit, underline=1) menuAudio = Menu(menubar, tearoff=0) menubar.add_cascade(label="Audio", menu=menuAudio, underline=0) menuAudio.add_command(label="Play", command=self.onBtnPlay, underline=0) menuAudio.add_command(label="Pause", command=self.onBtnPause, underline=2) menuAudio.add_command(label="Stop", command=self.onBtnStop, underline=0) menuAudio.add_separator() menuAudio.add_command(label="Audio Options...", command=self.openAudioOptsWindow, underline=6, accelerator='Ctrl+P') menuAudio.add_command(label="Change Tempo...", command=self.menu_changeTempo, underline=0) menuAudio.add_command(label="Copy Options String", command=self.menuCopyAudioOptsString, underline=1) menuAudio.add_separator() menuAudio.add_command(label="Save Wave", command=self.onBtnSaveWave, underline=5, accelerator='Ctrl+R') menuAudio.add_separator() menuAudio.add_command(label="Choose Sound Font...", command=self.openSoundfontWindow, underline=13, accelerator='Ctrl+F') self.objOptionsDuration = IntVar(); self.objOptionsDuration.set(0) self.objOptionsBarlines = IntVar(); self.objOptionsBarlines.set(1) menuView = Menu(menubar, tearoff=0) menubar.add_cascade(label="View", menu=menuView, underline=0) menuView.add_command(label="Mixer", command=self.openMixerView, underline=0, accelerator='Ctrl+M') menuView.add_command(label="Console output", command=self.menu_openConsoleWindow, underline=0) menuView.add_separator() menuView.add_command(label="SoundFont Information Tool", command=self.menu_soundFontInfoTool, underline=0) menuView.add_separator() menuView.add_checkbutton(label="Show Durations in score", variable=self.objOptionsDuration, underline=5, onvalue=1, offvalue=0) menuView.add_checkbutton(label="Show Barlines in score", variable=self.objOptionsBarlines, underline=5, onvalue=1, offvalue=0) menuHelp = Menu(menubar, tearoff=0) menuHelp.add_command(label='About', underline=0, command=(lambda: midirender_util.alert('Bmidi to wave, by Ben Fisher 2009\nhalfhourhacks.blogspot.com\n\nA graphical frontend for Timidity.','Bmidi to wave'))) menubar.add_cascade(label="Help", menu=menuHelp, underline=0) root.config(menu=menubar)
def previewSfStart(self): if self.player!=None and self.player.isPlaying: return #don't play while something is already playing. #check for mid, see if it exists curMid = self.currentMidi if not os.path.exists(curMid): midirender_util.alert("Couldn't find midi file.") return #if soundfont couldn't be parsed. if self.objSf == None: return #get cfg data escapedfname = self.objSf.filename if self.objSf.type!='pat': sel = self.lbVoices.curselection() if self.varPrevVoiceOnly.get() and len(sel)>0: #need to map that voice to bank0, program0 voiceIndex = int(sel[0]) voiceData = self.objSf.presets[voiceIndex] try: bank = int(voiceData.bank) program = int(voiceData.presetNumber) except ValueError: midirender_util.alert("Couldn't parse bank/preset number.") return strCfg = '\nbank 0\n' strCfg += '000 %font "'+escapedfname+'" %d %d'%(bank, program) + '\n' else: strCfg = '\n' strCfg += 'soundfont "'+escapedfname+'"\n' else: strCfg = '\nbank 0\n' strCfg += '000 "'+escapedfname+'" \n' self.player = midirender_runtimidity.RenderTimidityMidiPlayer() self.player.setConfiguration(strCfg) self.player.playAsync(curMid)
def menu_openMidi(self, evt=None): filename = midirender_util.ask_openfile(title='Open Midi File', types=['.mid|Mid file']) if not filename: return # first, see if it loads successfully. try: newmidi = bmidilib.BMidiFile() newmidi.open(filename, 'rb') newmidi.read() newmidi.close() except: e = sys.exc_info()[1] midirender_util.alert('Could not load midi: exception %s' % str(e), title='Could not load midi', icon='error') return self.lblFilename['text'] = filename self.loadMidiObj(newmidi)
def onBtnSaveWave(self, e=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return filename = midirender_util.ask_savefile(title="Create Wav File", types=['.wav|Wav file']) if not filename: return midicopy = self.buildModifiedMidi() arParams, directoryForOldTimidity = self.getParamsForTimidity(True) if arParams is None: return arParams.append('-o') arParams.append(filename) # play it synchronously, meaning that the whole program stalls while this happens... midirender_util.alert('Beginning export to wave...') objplayer = midirender_runtimidity.RenderTimidityMidiPlayer() objplayer.setConfiguration(self.buildCfg(), directoryForOldTimidity) objplayer.setParameters(arParams) objplayer.playMidiObject(midicopy, bSynchronous=True) if self.consoleOutWindow is not None: self.consoleOutWindow.clear() self.consoleOutWindow.writeToWindow(objplayer.strLastStdOutput) midirender_util.alert('Completed.')
def onBtnChooseWithinSoundfont(self): index = self.getListboxIndex() state = self.arCustomizationState[index] if state['soundfont'].endswith('.pat'): midirender_util.alert( 'Patch files don\'t contain separate voices.') return elif state['soundfont'].endswith('.cfg'): midirender_util.alert( 'Patch files don\'t contain separate voices.') return dlg = midirender_soundfont_info.BSoundFontInformation( self.top, state['soundfont'], bSelectMode=True) if not dlg.result: return try: bank = int(dlg.result.bank) program = int(dlg.result.presetNumber) except ValueError as e: midirender_util.alert( 'Couldn\'t parse bank/preset for some reason.') return # refresh the fields state['sf_program'] = program state['sf_bank'] = bank self.onChangeLbProgChanges()
def onBtnSaveWave(self,e=None): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return filename = midirender_util.ask_savefile(title="Create Wav File", types=['.wav|Wav file']) if not filename: return midicopy = self.buildModifiedMidi() arParams, directoryForOldTimidity = self.getParamsForTimidity(True) if arParams is None: return arParams.append('-o') arParams.append(filename) # play it synchronously, meaning that the whole program stalls while this happens... midirender_util.alert('Beginning export to wave...') objplayer = midirender_runtimidity.RenderTimidityMidiPlayer() objplayer.setConfiguration(self.buildCfg(), directoryForOldTimidity) objplayer.setParameters(arParams) objplayer.playMidiObject(midicopy, bSynchronous=True) if self.consoleOutWindow is not None: self.consoleOutWindow.clear() self.consoleOutWindow.writeToWindow(objplayer.strLastStdOutput) midirender_util.alert('Completed.')
def buildCfg(self): # begin by adding the global soundfont or cfg file. filename = self.currentSoundfont[0] strCfg = '' if filename.endswith('.cfg'): path, justname = os.path.split(filename) if self.audioOptsWindow and self.audioOptsWindow.getUseOldTimidity( ): if ' ' in justname: midirender_util.alert( "Warning: old version of Timidity probably doesn't support spaces in filenames." ) if justname.lower() != 'timidity.cfg' and os.path.exists( path + os.sep + 'timidity.cfg'): midirender_util.alert( "Warning: there is a filename timidity.cfg in this directory and so the old version of Timidity will use it." ) strCfg += '\nsource %s' % (justname) else: strCfg += '\ndir "%s"\nsource "%s"' % (path, filename) else: strCfg += '\nsoundfont "%s"' % (filename) if self.audioOptsWindow is not None and self.audioOptsWindow.getPatchesTakePrecedence( ): strCfg += ' order=1' # now add customization to override specific voices, if set if self.soundfontWindow is not None: strCfg += '\n' + self.soundfontWindow.getCfgResults( self.audioOptsWindow and self.audioOptsWindow.getUseOldTimidity()) if self.audioOptsWindow is not None: addedLines = self.audioOptsWindow.getAdditionalCfg() if addedLines: strCfg += '\n' + '\n'.join(addedLines) return strCfg
def openScoreView(self, n): if not self.isMidiLoaded: midirender_util.alert('Please open a MIDI file first.') return if len(self.objMidi.tracks[n].notelist) == 0: midirender_util.alert('No notes to show in this track.') return opts = {} opts['show_durations'] = self.objOptionsDuration.get() opts['show_barlines'] = self.objOptionsBarlines.get() opts['show_stems'] = 1 opts['prefer_flats'] = 0 opts['clefspath'] = clefspath top = Toplevel() window = scoreview.ScoreViewWindow(top, n, self.objMidi.tracks[n], self.objMidi.ticksPerQuarterNote, opts) self.scoreviews[n] = top top.focus_set()
def createTimidityOptionsList(self, includeRenderOptions=False): sample = self.getOptValue('sample') bitrate = self.getOptValue('bitrate') lpf = self.getOptValue('lpf') delay = self.getOptValue('delay') reverb = self.getOptValue('reverb') try: psreverb = int(self.varPsReverb.get()); valid = psreverb >= 0 and psreverb<=1000 except: valid=False if not valid: midirender_util.alert('psuedo reverb must be an integer 0 to 1000'); return None stereo =self.varStereo.get() decay =self.varFastDecay.get() res = '' if includeRenderOptions: res+= ' -Ow' if stereo: res+='S' #stereo or mono else: res+='M' # if bitrate==8: res+='u' #8-bit wavs are typically unsigned # else: res+= 's' res+= 's' if bitrate==8: res+='8' #8-bit audio elif bitrate==24: res+='2' #24 bit else: res+='1' #16 bit res+='l' #l for linear encoding res+=' -s %d'%sample #sampling rate, 44100 or 22050 if decay: res+=' -f' res += ' -R %d'%psreverb res += ' --voice-lpf %s'%lpf res += ' --delay %s'%delay res += ' --reverb %s'%reverb arParams = res.split(' ') return arParams
def getCfgResults(self, bUseOldTimidity): if not self.showCustomize: return '' def intOrNothing(s): try: val = int(s) except: val = 0 return max(0, val) # get the most recent changes self.stringParamFromUI() # elsewhere, the "global soundfont" will be set. That's necessary to cover percussion, which we don't handle here. # here we only set the custom overrides. strCfg = '\nbank 0\n' for state in self.arCustomizationState: if state['soundfont'].endswith('.pat'): if not bUseOldTimidity: #if using a global soundfont, signal to Timidity that we want to override the soundfont's instrument if not self.main_soundfont_reference[0].endswith('.cfg'): strCfg += '\nfont exclude 0 %d' % state['instrument'] strCfg += '\n%d "%s" '%(state['instrument'], state['soundfont']) else: if ' ' in state['soundfont']: midirender_util.alert('Warning: old version of Timidity doesn\'t support spaces in directory names or filenames.') directoryWithPatchfile, nameOfPatchFile = os.path.split(state['soundfont']) mainDirectory = os.path.split(self.main_soundfont_reference[0])[0] if os.path.exists(mainDirectory + os.sep + nameOfPatchFile) and mainDirectory.lower() != directoryWithPatchfile.lower(): midirender_util.alert('Warning: better to use a different .pat name, old version of Timidity might use\n%s \ninstead of\n%s.'%( mainDirectory + os.sep + nameOfPatchFile, state['soundfont'])) nameOfPatchFile, _ = os.path.splitext(nameOfPatchFile) strCfg += '\ndir %s\n%d %s'%(directoryWithPatchfile, state['instrument'], nameOfPatchFile) if state['param_amp'] != '100': strCfg += ' amp=%d'%intOrNothing(state['param_amp']) elif state['soundfont'].endswith('.cfg'): # must be taking this from a global cfg, because cfgs can't be set individually... pass else: if bUseOldTimidity: midirender_util.alert('Warning: old version of Timidity doesn\'t support soundfonts, only .pat files.') # note a literal% symbol. The string %font, not intended for substitution strCfg += '\n%d ' % state['instrument'] + ' %font' strCfg += ' "%s" %d %d' % (state['soundfont'], state['sf_bank'], state['sf_program']) if state['param_amp'] != '100': strCfg += ' amp=%d' % intOrNothing(state['param_amp']) if state['param_rnddelay'] != '0': strCfg += '\nrnddelay %d %d' % (state['instrument'], intOrNothing(state['param_rnddelay'])) return strCfg
def loadSf(self,filename): self.lbVoices.delete(0, END) #clear existing voices self.lblCurrentSf['text'] = filename self.objSf = None if filename.lower().endswith('.pat'): #gus (gravis ultrasound) patch namestart, nameend = os.path.split(filename) self.lbVoices.insert(END, nameend) self.objSf = SoundFontInfo() self.objSf.type='pat' self.objSf.name = nameend self.objSf.filename = filename self.varPrevVoiceOnly.set(1) else: try: objSf = getpresets(filename) except SFInfoException, e: midirender_util.alert(str(e)) return #set labels for name in self.labelFieldNames: att = getattr(objSf, name) att = (att if att else 'None') if len(att)>80: att = att[0:80] self.lblInfoFields[name]['text'] = name+': '+ att # fill listbox for preset in objSf.presets: self.lbVoices.insert(END, str(preset)) self.objSf = objSf self.objSf.filename = filename self.objSf.type='soundfont'
def onBtnChooseWithinSoundfont(self): index = self.getListboxIndex() state = self.arCustomizationState[index] if state['soundfont'].endswith('.pat'): midirender_util.alert("Patch files don't contain separate voices."); return elif state['soundfont'].endswith('.cfg'): midirender_util.alert("Patch files don't contain separate voices."); return dlg = midirender_soundfont_info.BSoundFontInformation(self.top, state['soundfont'] , bSelectMode=True) if not dlg.result: return try: bank = int(dlg.result.bank) program = int(dlg.result.presetNumber) except ValueError, e: midirender_util.alert("Couldn't parse bank/preset for some reason.") return
def loadMidiObj(self, newmidi): self.objMidi = newmidi if self.player.isPlaying(): return False if not self.haveDrawnHeaders: self.drawColumnHeaders() if not self.isMidiLoaded: # check if Timidity is installed if sys.platform != 'win32': if not midirender_runtimidity.isTimidityInstalled(): midirender_util.alert('It appears that the program Timidity is not installed. This program is required for playing and rendering music.\n\nYou could try running something corresponding to "sudo apt-get install timidity" or "sudo yum install timidity++" in a terminal.') self.isMidiLoaded = True # close any open views for key in self.listviews: self.listviews[key].destroy() for key in self.scoreviews: self.scoreviews[key].destroy() self.listviews = {} self.scoreviews = {} self.clearModifications() # hide all of the old widgets for key in self.gridwidgets: w = self.gridwidgets[key] if w.master.is_smallframe==1: w.master.grid_forget() for key in self.gridbuttons: self.gridbuttons[key].grid_forget() def addLabel(text, y, x, isButton=False): # only create a new widget when necessary. This way, don't need to allocate every time a file is opened. if (x, y+1) not in self.gridwidgets: smallFrame = Frame(self.frameGrid, borderwidth=1, relief=RIDGE) smallFrame.is_smallframe=1 if isButton: btn = Button(smallFrame, text=text, relief=GROOVE,anchor='w') btn.config(command=midirender_util.Callable(self.onBtnChangeInstrument, y,btn)) btn.pack(anchor='w', fill=BOTH) btn['disabledforeground'] = 'black' # means that when it is disabled, looks just like a label. sweet. thewidget = btn else: lbl = Label(smallFrame, text=text) lbl.pack(anchor='w') thewidget = lbl self.gridwidgets[(x,y+1)] = thewidget self.gridwidgets[(x,y+1)]['text'] = text self.gridwidgets[(x,y+1)].master.grid(row=y+1, column=x, sticky='nsew') return self.gridwidgets[(x,y+1)] lengthTimer = bmiditools.BMidiSecondsLength(self.objMidi) overallLengthSeconds = lengthTimer.getOverallLength(self.objMidi) self.sliderTime['to'] = max(1.0, overallLengthSeconds+1.0) self.player.load(max(1.0, overallLengthSeconds+1.0)) # "loading" will also set position to 0.0 warnMultipleChannels = False for rownum in range(len(self.objMidi.tracks)): trackobj = self.objMidi.tracks[rownum] # Track Number addLabel( str(rownum), rownum, 0) # Track Name defaultname = 'Condtrack' if rownum==0 else ('Track %d'%rownum) searchFor = {'TRACKNAME':defaultname, 'INSTRUMENTS':1 } res = bmiditools.getTrackInformation(trackobj, searchFor) addLabel(res['TRACKNAME'], rownum, 1) # Track Channel(s) chanarray = self.findNoteChannels(trackobj) if len(chanarray)==0: channame='None' elif len(chanarray)>1: channame='(Many)'; warnMultipleChannels=True else: channame = str(chanarray[0]) addLabel(channame, rownum, 2) countednoteevts = len(trackobj.notelist) # this assumes notelist is valid, and notelist is only valid if we've just read from a file # Track Instrument(s) instarray = res['INSTRUMENTS'] if len(instarray)==0: instname='None' elif len(instarray)>1: instname='(Many)' else: instname = str(instarray[0]) + ' (' + bmidilib.getInstrumentName(instarray[0]) + ')' if channame == '10': instname = '(Percussion channel)' btn = addLabel( instname, rownum, 3, isButton=True) # add a button (not a label) isEnabled = channame!='10' and instname!='None' and instname!='(Many)' # countednoteevts>0 if isEnabled: btn['state'] = NORMAL btn['relief'] = GROOVE else: btn['state'] = DISABLED btn['relief'] = FLAT # if there are multiple inst. changes in a track, we don't let you change instruments because there isn't a convenient way to do that. # Track Time if len(trackobj.notelist)==0: strTime = lengthTimer.secondsToString(0) else: strTime = lengthTimer.secondsToString(lengthTimer.ticksToSeconds(trackobj.notelist[0].time)) addLabel( strTime, rownum, 4) # Track Notes addLabel( str(countednoteevts), rownum, 5) # Buttons if (rownum, 0) not in self.gridbuttons: btn = Button(self.frameGrid, text='Mixer', command=self.openMixerView) self.gridbuttons[(rownum, 0)] = btn self.gridbuttons[(rownum, 0)].grid(row=rownum+1, column=6) if (rownum, 1) not in self.gridbuttons: btn = Button(self.frameGrid, text='Score', command=midirender_util.Callable(self.openScoreView, rownum)) self.gridbuttons[(rownum, 1)] = btn self.gridbuttons[(rownum, 1)].grid(row=rownum+1, column=7) if (rownum, 2) not in self.gridbuttons: btn = Button(self.frameGrid, text='List', command=midirender_util.Callable(self.openListView, rownum)) self.gridbuttons[(rownum, 2)] = btn self.gridbuttons[(rownum, 2)].grid(row=rownum+1, column=8) if warnMultipleChannels: resp = midirender_util.ask_yesno('This midi file has notes from different channels in the same track (format 0). Click "yes" (recommended) to import it as a format 1 file, or "no" to leave it. ') if resp: newmidi = bmiditools.restructureMidi(self.objMidi) newmidi.format = 1 self.loadMidiObj(newmidi) return
def menuModifyRawMidi(self, evt=None): if sys.platform != 'win32': midirender_util.alert('Only supported on windows') return import tempfile, os, subprocess m2t = midirender_util.bmidirenderdirectory + '\\timidity\\m2t.exe' t2m = midirender_util.bmidirenderdirectory + '\\timidity\\t2m.exe' notepadexe = 'C:\\Windows\\System32\\notepad.exe' if not os.path.exists(m2t) or not os.path.exists( t2m) or not os.path.exists(notepadexe): midirender_util.alert('Could not find %s or %s or %s.' % (m2t, t2m, notepadexe)) return filenameMidInput = midirender_util.ask_openfile( title='Choose Midi File to modify', types=['.mid|Mid file']) if not filenameMidInput: return filenameText = tempfile.gettempdir() + os.sep + 'tmpout.txt' try: if os.path.exists(filenameText): os.unlink(filenameText) except: pass if os.path.exists(filenameText): midirender_util.alert('Could not clear temporary file') return args = [m2t, filenameMidInput, filenameText] retcode = subprocess.call(args, creationflags=0x08000000) if retcode: midirender_util.alert('Midi to text returned failure') return if not os.path.exists(filenameText): midirender_util.alert('Midi to text did not write text file') return midirender_util.alert( "You'll see the text file in notepad. Save and close when done editing." ) modtimebefore = os.path.getmtime(filenameText) args = [notepadexe, filenameText] retcode = subprocess.call(args) if modtimebefore == os.path.getmtime(filenameText): # looks like the user cancelled before making any edits return filenameMidOutput = midirender_util.ask_savefile( title="Choose destination for Midi File", types=['.mid|Mid file']) if not filenameMidOutput: return args = [t2m, filenameText, filenameMidOutput] retcode = subprocess.call(args, creationflags=0x08000000) if retcode: midirender_util.alert('text to midi returned failure') return if not os.path.exists(filenameMidOutput): midirender_util.alert('text to midi did not write midi file') return midirender_util.alert('Complete.')
def getCfgResults(self, bUseOldTimidity): if not self.showCustomize: return '' def intOrNothing(s): try: val = int(s) except: val = 0 return max(0, val) # get the most recent changes self.stringParamFromUI() # elsewhere, the "global soundfont" will be set. That's necessary to cover percussion, which we don't handle here. # here we only set the custom overrides. strCfg = '\nbank 0\n' for state in self.arCustomizationState: if state['soundfont'].endswith('.pat'): if not bUseOldTimidity: #if using a global soundfont, signal to Timidity that we want to override the soundfont's instrument if not self.main_soundfont_reference[0].endswith('.cfg'): strCfg += '\nfont exclude 0 %d' % state['instrument'] strCfg += '\n%d "%s" ' % (state['instrument'], state['soundfont']) else: if ' ' in state['soundfont']: midirender_util.alert( 'Warning: old version of Timidity doesn\'t support spaces in directory names or filenames.' ) directoryWithPatchfile, nameOfPatchFile = os.path.split( state['soundfont']) mainDirectory = os.path.split( self.main_soundfont_reference[0])[0] if os.path.exists(mainDirectory + os.sep + nameOfPatchFile) and mainDirectory.lower( ) != directoryWithPatchfile.lower(): midirender_util.alert( 'Warning: better to use a different .pat name, old version of Timidity might use\n%s \ninstead of\n%s.' % (mainDirectory + os.sep + nameOfPatchFile, state['soundfont'])) nameOfPatchFile, _ = os.path.splitext(nameOfPatchFile) strCfg += '\ndir %s\n%d %s' % (directoryWithPatchfile, state['instrument'], nameOfPatchFile) if state['param_amp'] != '100': strCfg += ' amp=%d' % intOrNothing(state['param_amp']) elif state['soundfont'].endswith('.cfg'): # must be taking this from a global cfg, because cfgs can't be set individually... pass else: if bUseOldTimidity: midirender_util.alert( 'Warning: old version of Timidity doesn\'t support soundfonts, only .pat files.' ) # note a literal% symbol. The string %font, not intended for substitution strCfg += '\n%d ' % state['instrument'] + ' %font' strCfg += ' "%s" %d %d' % ( state['soundfont'], state['sf_bank'], state['sf_program']) if state['param_amp'] != '100': strCfg += ' amp=%d' % intOrNothing(state['param_amp']) if state['param_rnddelay'] != '0': strCfg += '\nrnddelay %d %d' % ( state['instrument'], intOrNothing(state['param_rnddelay'])) return strCfg
def create_menubar(self, root): root.bind('<Control-space>', self.onBtnPlay) root.bind('<Control-r>', self.onBtnSaveWave) root.bind('<Control-f>', self.openSoundfontWindow) root.bind('<Alt-F4>', lambda x: root.quit) root.bind('<Control-o>', self.menu_openMidi) root.bind('<Control-S>', self.saveModifiedMidi) root.bind('<Control-m>', self.openMixerView) root.bind('<Control-p>', self.openAudioOptsWindow) menubar = Menu(root) menuFile = Menu(menubar, tearoff=0) menubar.add_cascade(label="File", menu=menuFile, underline=0) menuFile.add_command(label="Open MIDI", command=self.menu_openMidi, underline=0, accelerator='Ctrl+O') menuFile.add_separator() menuFile.add_command(label="Modify MIDI Events...", command=self.menuModifyRawMidi, underline=0) menuFile.add_separator() menuFile.add_command(label="Save Changes From Mixer...", command=self.saveModifiedMidi, underline=0, accelerator='Ctrl+Shift+S') menuFile.add_separator() menuFile.add_command(label="Exit", command=root.quit, underline=1) menuAudio = Menu(menubar, tearoff=0) menubar.add_cascade(label="Audio", menu=menuAudio, underline=0) menuAudio.add_command(label="Play", command=self.onBtnPlay, underline=0) menuAudio.add_command(label="Pause", command=self.onBtnPause, underline=2) menuAudio.add_command(label="Stop", command=self.onBtnStop, underline=0) menuAudio.add_separator() menuAudio.add_command(label="Audio Options...", command=self.openAudioOptsWindow, underline=6, accelerator='Ctrl+P') menuAudio.add_command(label="Change Tempo...", command=self.menu_changeTempo, underline=0) menuAudio.add_command(label="Transpose Pitch...", command=self.menu_changeTranspose, underline=0) menuAudio.add_command(label="Copy Options String", command=self.menuCopyAudioOptsString, underline=1) menuAudio.add_separator() menuAudio.add_command(label="Save Wav", command=self.onBtnSaveWave, underline=5, accelerator='Ctrl+R') menuAudio.add_separator() menuAudio.add_command(label="Choose SoundFont...", command=self.openSoundfontWindow, underline=13, accelerator='Ctrl+F') self.objOptionsDuration = IntVar() self.objOptionsDuration.set(0) self.objOptionsBarlines = IntVar() self.objOptionsBarlines.set(1) menuView = Menu(menubar, tearoff=0) menubar.add_cascade(label="View", menu=menuView, underline=0) menuView.add_command(label="Mixer", command=self.openMixerView, underline=0, accelerator='Ctrl+M') menuView.add_command(label="Console Output", command=self.menu_openConsoleWindow, underline=0) menuView.add_separator() menuView.add_command(label="SoundFont Information Tool", command=self.menu_soundFontInfoTool, underline=0) menuView.add_separator() menuView.add_checkbutton(label="Show Durations in score", variable=self.objOptionsDuration, underline=5, onvalue=1, offvalue=0) menuView.add_checkbutton(label="Show Barlines in score", variable=self.objOptionsBarlines, underline=5, onvalue=1, offvalue=0) menuHelp = Menu(menubar, tearoff=0) menuHelp.add_command( label='About', underline=0, command=(lambda: midirender_util.alert( 'Bmidi to wav, by Ben Fisher 2009\nA graphical frontend for Timidity and sfubar.\n\nSee the documentation at https://github.com/moltenform/labs_youthful_projects/tree/master/src/benmidi/README.md\n\nSource code at https://github.com/moltenform/labs_youthful_projects/tree/master/src/benmidi/bmidirender', 'Bmidi to wav'))) menuHelp.add_command(label='Documentation', underline=0, command=(lambda: openOnlineDocs())) menubar.add_cascade(label="Help", menu=menuHelp, underline=0) root.config(menu=menubar)
def __init__(self, top, midiObject, opts, callbackOnClose=None): #should only display tracks with note events. that way, solves some problems top.title('Mixer') frameTop = Frame(top, height=600) frameTop.pack(expand=YES, fill=BOTH) self.frameTop = frameTop ROW_NAME = 0 ROW_CHECK = 1 ROW_VOL = 2 ROW_PAN = 3 ROW_SCALEVOL = 4 ROW_TRANSPOSE = 5 Label(frameTop, text='Pan:').grid(row=ROW_PAN, column=0) Label(frameTop, text='Volume:').grid(row=ROW_VOL, column=0) Label(frameTop, text=' ').grid(row=ROW_NAME, column=0) Label(frameTop, text='Enabled:').grid(row=ROW_CHECK, column=0) Label(frameTop, text='Multiply vols:').grid(row=ROW_SCALEVOL, column=0) Label(frameTop, text='Transpose:').grid(row=ROW_TRANSPOSE, column=0) warnMultiple = [] self.state = [] col = 1 #column 0 is the text labels for trackNumber in range(len(midiObject.tracks)): track = midiObject.tracks[trackNumber] if not len(track.notelist): continue #only display tracks with note lists scpan = Scale(frameTop, from_=-63, to=63, orient=HORIZONTAL, length=32 * 2) #make it possible to set to 0. scpan.grid(row=ROW_PAN, column=col, sticky='EW') scvol = Scale(frameTop, from_=127, to=0, orient=VERTICAL) scvol.grid(row=ROW_VOL, column=col, sticky='NS') Label(frameTop, text=' Track %d ' % trackNumber).grid(row=ROW_NAME, column=col) checkvar = IntVar() Checkbutton(frameTop, text='', var=checkvar).grid(row=ROW_CHECK, column=col) scalevolvar = StringVar() scalevolvar.set('1.0') Entry(frameTop, width=4, textvariable=scalevolvar).grid(row=ROW_SCALEVOL, column=col) transposevar = StringVar() transposevar.set('0') Entry(frameTop, width=4, textvariable=transposevar).grid(row=ROW_TRANSPOSE, column=col) #defaults (firstpan, firstvol, bMultiplePans, bMultipleVols) = getFirstVolumeAndPanEvents(track) if bMultipleVols or bMultiplePans: warnMultiple.append(trackNumber) scvol.set(100 if (firstvol == None) else firstvol.velocity) scpan.set(0 if (firstpan == None) else (firstpan.velocity - 64)) checkvar.set(1) self.state.append( MixerTrackInfo(trackNumber, checkvar, scvol, scpan, transposevar, scalevolvar)) col += 1 if len(warnMultiple) != 0: midirender_util.alert( 'One or more of the tracks (%s) has multiple pan or volume events. You can still use the mixer, but it will only modify the first event.' % ','.join(str(n) for n in warnMultiple)) frameTop.grid_rowconfigure(ROW_VOL, weight=1) #~ frameTop.grid_columnconfigure(0, weight=1) if callbackOnClose != None: def callCallback(): callbackOnClose() top.destroy() top.protocol("WM_DELETE_WINDOW", callCallback) self.top = top
def createMixedMidi(self, midiObject): if midiObject.format != 1: midirender_util.alert( 'Warning: mixer will not work well for a format-0 midi.') #NOTE: modifies the midi object itself, not a copy #remove the tracks that are both in the mixer, AND not enabled for trackInfo in self.state: trackNumber = trackInfo.trackNumber if not trackInfo.enableVar.get(): #eliminate the track by making an empty one in its place midiObject.tracks[trackNumber] = bmidilib.BMidiTrack() evt = bmiditools.makeEndOfTrackEvent() midiObject.tracks[trackNumber].events.append(evt) else: trackObject = midiObject.tracks[trackNumber] volValue = trackInfo.volWidget.get() panValue = trackInfo.panWidget.get( ) + 64 #is from 0 to 127, instead of -63 to 63 try: transposeValue = int(trackInfo.transposeVar.get()) except: trackInfo.transposeVar.set('0') transposeValue = 0 try: scaleVolValue = float(trackInfo.scalevolVar.get()) except: trackInfo.scalevolVar.set('1.0') scaleVolValue = 1.0 #modify the event directly, if it exists. Otherwise, create and add a new event. (firstpan, firstvol, bMultiplePans, bMultipleVols) = getFirstVolumeAndPanEvents( midiObject.tracks[trackNumber]) if firstpan: firstpan.velocity = panValue else: #create a new pan event. evt = bmidilib.BMidiEvent() evt.type = 'CONTROLLER_CHANGE' evt.time = 0 evt.pitch = 0x0A evt.velocity = panValue evt.channel = trackObject.notelist[ -1].startEvt.channel #assume that the channel of the track is the channel of the first note. trackObject.events.insert(0, evt) if firstvol: firstvol.velocity = volValue else: #create a new vol event. evt = bmidilib.BMidiEvent() evt.type = 'CONTROLLER_CHANGE' evt.time = 0 evt.pitch = 0x07 evt.velocity = volValue evt.channel = trackObject.notelist[-1].startEvt.channel trackObject.events.insert(0, evt) #scaling volume. nice when there are many volume events. if trackInfo.scalevolVar.get() != '1.0': for evt in trackObject.events: if evt.type == 'CONTROLLER_CHANGE' and evt.pitch == 0x07: newVol = int(evt.velocity * 1.0 * scaleVolValue) evt.velocity = min(max( newVol, 0), 127) #make sure between 0 and 127 #transpose tracks, using the notelist if transposeValue != 0: for note in trackObject.notelist: nextpitch = min(max(note.pitch + transposeValue, 0), 127) #make sure between 0 and 127 note.startEvt.pitch = nextpitch note.endEvt.pitch = nextpitch note.pitch = nextpitch return None #as a signal that this modifies, not returns a copy
def loadMidiObj(self, newmidi): self.objMidi = newmidi if self.player.isPlaying(): return False if not self.haveDrawnHeaders: self.drawColumnHeaders() if not self.isMidiLoaded: # check if Timidity is installed if sys.platform != 'win32': if not midirender_runtimidity.isTimidityInstalled(): midirender_util.alert( 'It appears that the program Timidity is not installed. This program is required for playing and rendering music.\n\nYou could try running something corresponding to "sudo apt-get install timidity" or "sudo yum install timidity++" in a terminal.' ) self.isMidiLoaded = True # close any open views for key in self.listviews: self.listviews[key].destroy() for key in self.scoreviews: self.scoreviews[key].destroy() self.listviews = {} self.scoreviews = {} self.clearModifications() # hide all of the old widgets for key in self.gridwidgets: w = self.gridwidgets[key] if w.master.is_smallframe == 1: w.master.grid_forget() for key in self.gridbuttons: self.gridbuttons[key].grid_forget() def addLabel(text, y, x, isButton=False): # only create a new widget when necessary. This way, don't need to allocate every time a file is opened. if (x, y + 1) not in self.gridwidgets: smallFrame = Frame(self.frameGrid, borderwidth=1, relief=RIDGE) smallFrame.is_smallframe = 1 if isButton: btn = Button(smallFrame, text=text, relief=GROOVE, anchor='w') btn.config(command=midirender_util.Callable( self.onBtnChangeInstrument, y, btn)) btn.pack(anchor='w', fill=BOTH) btn['disabledforeground'] = 'black' # means that when it is disabled, looks just like a label. sweet. thewidget = btn else: lbl = Label(smallFrame, text=text) lbl.pack(anchor='w') thewidget = lbl self.gridwidgets[(x, y + 1)] = thewidget self.gridwidgets[(x, y + 1)]['text'] = text self.gridwidgets[(x, y + 1)].master.grid(row=y + 1, column=x, sticky='nsew') return self.gridwidgets[(x, y + 1)] lengthTimer = bmiditools.BMidiSecondsLength(self.objMidi) overallLengthSeconds = lengthTimer.getOverallLength(self.objMidi) self.sliderTime['to'] = max(1.0, overallLengthSeconds + 1.0) self.player.load(max(1.0, overallLengthSeconds + 1.0)) # "loading" will also set position to 0.0 warnMultipleChannels = False for rownum in range(len(self.objMidi.tracks)): trackobj = self.objMidi.tracks[rownum] # Track Number addLabel(str(rownum), rownum, 0) # Track Name defaultname = 'Condtrack' if rownum == 0 else ('Track %d' % rownum) searchFor = {'TRACKNAME': defaultname, 'INSTRUMENTS': 1} res = bmiditools.getTrackInformation(trackobj, searchFor) addLabel(res['TRACKNAME'], rownum, 1) # Track Channel(s) chanarray = self.findNoteChannels(trackobj) if len(chanarray) == 0: channame = 'None' elif len(chanarray) > 1: channame = '(Many)' warnMultipleChannels = True else: channame = str(chanarray[0]) addLabel(channame, rownum, 2) countednoteevts = len( trackobj.notelist ) # this assumes notelist is valid, and notelist is only valid if we've just read from a file # Track Instrument(s) instarray = res['INSTRUMENTS'] if len(instarray) == 0: instname = 'None' elif len(instarray) > 1: instname = '(Many)' else: instname = str( instarray[0]) + ' (' + bmidilib.getInstrumentName( instarray[0]) + ')' if channame == '10': instname = '(Percussion channel)' btn = addLabel(instname, rownum, 3, isButton=True) # add a button (not a label) isEnabled = channame != '10' and instname != 'None' and instname != '(Many)' # countednoteevts>0 if isEnabled: btn['state'] = NORMAL btn['relief'] = GROOVE else: btn['state'] = DISABLED btn['relief'] = FLAT # if there are multiple inst. changes in a track, we don't let you change instruments because there isn't a convenient way to do that. # Track Time if len(trackobj.notelist) == 0: strTime = lengthTimer.secondsToString(0) else: strTime = lengthTimer.secondsToString( lengthTimer.ticksToSeconds(trackobj.notelist[0].time)) addLabel(strTime, rownum, 4) # Track Notes addLabel(str(countednoteevts), rownum, 5) # Buttons if (rownum, 0) not in self.gridbuttons: btn = Button(self.frameGrid, text='Mixer', command=self.openMixerView) self.gridbuttons[(rownum, 0)] = btn self.gridbuttons[(rownum, 0)].grid(row=rownum + 1, column=6) if (rownum, 1) not in self.gridbuttons: btn = Button(self.frameGrid, text='Score', command=midirender_util.Callable( self.openScoreView, rownum)) self.gridbuttons[(rownum, 1)] = btn self.gridbuttons[(rownum, 1)].grid(row=rownum + 1, column=7) if (rownum, 2) not in self.gridbuttons: btn = Button(self.frameGrid, text='List', command=midirender_util.Callable( self.openListView, rownum)) self.gridbuttons[(rownum, 2)] = btn self.gridbuttons[(rownum, 2)].grid(row=rownum + 1, column=8) if warnMultipleChannels: resp = midirender_util.ask_yesno( 'This midi file has notes from different channels in the same track (format 0). Click "yes" (recommended) to import it as a format 1 file, or "no" to leave it. ' ) if resp: newmidi = bmiditools.restructureMidi(self.objMidi) newmidi.format = 1 self.loadMidiObj(newmidi) return
def __init__(self, top, midiObject, opts, callbackOnClose=None): # should only display tracks with note events. that way, solves some problems top.title("Mixer") frameTop = Frame(top, height=600) frameTop.pack(expand=YES, fill=BOTH) self.frameTop = frameTop ROW_NAME = 0 ROW_CHECK = 1 ROW_VOL = 2 ROW_PAN = 3 ROW_SCALEVOL = 4 ROW_TRANSPOSE = 5 Label(frameTop, text="Pan:").grid(row=ROW_PAN, column=0) Label(frameTop, text="Volume:").grid(row=ROW_VOL, column=0) Label(frameTop, text=" ").grid(row=ROW_NAME, column=0) Label(frameTop, text="Enabled:").grid(row=ROW_CHECK, column=0) Label(frameTop, text="Multiply vols:").grid(row=ROW_SCALEVOL, column=0) Label(frameTop, text="Transpose:").grid(row=ROW_TRANSPOSE, column=0) warnMultiple = [] self.state = [] col = 1 # column 0 is the text labels for trackNumber in range(len(midiObject.tracks)): track = midiObject.tracks[trackNumber] if not len(track.notelist): continue # only display tracks with note lists scpan = Scale(frameTop, from_=-63, to=63, orient=HORIZONTAL, length=32 * 2) # make it possible to set to 0. scpan.grid(row=ROW_PAN, column=col, sticky="EW") scvol = Scale(frameTop, from_=127, to=0, orient=VERTICAL) scvol.grid(row=ROW_VOL, column=col, sticky="NS") Label(frameTop, text=" Track %d " % trackNumber).grid(row=ROW_NAME, column=col) checkvar = IntVar() Checkbutton(frameTop, text="", var=checkvar).grid(row=ROW_CHECK, column=col) scalevolvar = StringVar() scalevolvar.set("1.0") Entry(frameTop, width=4, textvariable=scalevolvar).grid(row=ROW_SCALEVOL, column=col) transposevar = StringVar() transposevar.set("0") Entry(frameTop, width=4, textvariable=transposevar).grid(row=ROW_TRANSPOSE, column=col) # defaults (firstpan, firstvol, bMultiplePans, bMultipleVols) = getFirstVolumeAndPanEvents(track) if bMultipleVols or bMultiplePans: warnMultiple.append(trackNumber) scvol.set(100 if (firstvol == None) else firstvol.velocity) scpan.set(0 if (firstpan == None) else (firstpan.velocity - 64)) checkvar.set(1) self.state.append(MixerTrackInfo(trackNumber, checkvar, scvol, scpan, transposevar, scalevolvar)) col += 1 if len(warnMultiple) != 0: midirender_util.alert( "One or more of the tracks (%s) has multiple pan or volume events. You can still use the mixer, but it will only modify the first event." % ",".join(str(n) for n in warnMultiple) ) frameTop.grid_rowconfigure(ROW_VOL, weight=1) # ~ frameTop.grid_columnconfigure(0, weight=1) if callbackOnClose != None: def callCallback(): callbackOnClose() top.destroy() top.protocol("WM_DELETE_WINDOW", callCallback) self.top = top
def createMixedMidi(self, midiObject): if midiObject.format != 1: midirender_util.alert("Warning: mixer will not work well for a format-0 midi.") # NOTE: modifies the midi object itself, not a copy # remove the tracks that are both in the mixer, AND not enabled for trackInfo in self.state: trackNumber = trackInfo.trackNumber if not trackInfo.enableVar.get(): # eliminate the track by making an empty one in its place midiObject.tracks[trackNumber] = bmidilib.BMidiTrack() evt = bmidilib.BMidiEvent() evt.type = "END_OF_TRACK" evt.time = 1 evt.data = "" midiObject.tracks[trackNumber].events.append(evt) else: trackObject = midiObject.tracks[trackNumber] volValue = trackInfo.volWidget.get() panValue = trackInfo.panWidget.get() + 64 # is from 0 to 127, instead of -63 to 63 try: transposeValue = int(trackInfo.transposeVar.get()) except: trackInfo.transposeVar.set("0") transposeValue = 0 try: scaleVolValue = float(trackInfo.scalevolVar.get()) except: trackInfo.scalevolVar.set("1.0") scaleVolValue = 1.0 # modify the event directly, if it exists. Otherwise, create and add a new event. (firstpan, firstvol, bMultiplePans, bMultipleVols) = getFirstVolumeAndPanEvents( midiObject.tracks[trackNumber] ) if firstpan: firstpan.velocity = panValue else: # create a new pan event. evt = bmidilib.BMidiEvent() evt.type = "CONTROLLER_CHANGE" evt.time = 0 evt.pitch = 0x0A evt.velocity = panValue evt.channel = trackObject.notelist[ -1 ].startEvt.channel # assume that the channel of the track is the channel of the first note. trackObject.events.insert(0, evt) if firstvol: firstvol.velocity = volValue else: # create a new vol event. evt = bmidilib.BMidiEvent() evt.type = "CONTROLLER_CHANGE" evt.time = 0 evt.pitch = 0x07 evt.velocity = volValue evt.channel = trackObject.notelist[-1].startEvt.channel trackObject.events.insert(0, evt) # scaling volume. nice when there are many volume events. if trackInfo.scalevolVar.get() != "1.0": for evt in trackObject.events: if evt.type == "CONTROLLER_CHANGE" and evt.pitch == 0x07: newVol = int(evt.velocity * 1.0 * scaleVolValue) evt.velocity = min(max(newVol, 0), 127) # make sure between 0 and 127 # transpose tracks, using the notelist if transposeValue != 0: for note in trackObject.notelist: nextpitch = min(max(note.pitch + transposeValue, 0), 127) # make sure between 0 and 127 note.startEvt.pitch = nextpitch note.endEvt.pitch = nextpitch note.pitch = nextpitch return None # as a signal that this modifies, not returns a copy
def menuModifyRawMidi(self, evt=None): if sys.platform != 'win32': midirender_util.alert('Only supported on windows') return import tempfile, os, subprocess m2t = midirender_util.bmidirenderdirectory+'\\timidity\\m2t.exe' t2m = midirender_util.bmidirenderdirectory+'\\timidity\\t2m.exe' notepadexe = 'C:\\Windows\\System32\\notepad.exe' if not os.path.exists(m2t) or not os.path.exists(t2m) or not os.path.exists(notepadexe): midirender_util.alert('Could not find %s or %s or %s.'%(m2t, t2m, notepadexe)) return filenameMidInput = midirender_util.ask_openfile(title='Choose Midi File to modify', types=['.mid|Mid file']) if not filenameMidInput: return filenameText = tempfile.gettempdir() + os.sep + 'tmpout.txt' try: if os.path.exists(filenameText): os.unlink(filenameText) except: pass if os.path.exists(filenameText): midirender_util.alert('Could not clear temporary file') return args = [m2t, filenameMidInput, filenameText] retcode = subprocess.call(args, creationflags=0x08000000) if retcode: midirender_util.alert('Midi to text returned failure') return if not os.path.exists(filenameText): midirender_util.alert('Midi to text did not write text file') return midirender_util.alert("You'll see the text file in notepad. Save and close when done editing.") modtimebefore = os.path.getmtime(filenameText) args = [notepadexe, filenameText] retcode = subprocess.call(args) if modtimebefore == os.path.getmtime(filenameText): # looks like the user cancelled before making any edits return filenameMidOutput = midirender_util.ask_savefile(title="Choose destination for Midi File", types=['.mid|Mid file']) if not filenameMidOutput: return args = [t2m, filenameText, filenameMidOutput] retcode = subprocess.call(args, creationflags=0x08000000) if retcode: midirender_util.alert('text to midi returned failure') return if not os.path.exists(filenameMidOutput): midirender_util.alert('text to midi did not write midi file') return midirender_util.alert('Complete.')