# -*- coding: utf-8 -*- """ Copyright (C) 2015-2017 Jonathan Taquet This file is part of Oe2sSLE (Open e2sSample.all Library Editor). Oe2sSLE is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Oe2sSLE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Oe2sSLE. If not, see <http://www.gnu.org/licenses/> """ import tkinter as tk import tkinter.filedialog import tkinter.messagebox import tkinter.ttk #import re import math import platform #import time import sys import RIFF import e2s_sample_all as e2s from VerticalScrolledFrame import VerticalScrolledFrame from e2s_sample_trim import trim import os import os.path import audio import struct import webbrowser from GUI.widgets import ROCombobox from GUI.widgets import ROSpinbox import GUI.res from GUI.stereo_to_mono import StereoToMonoDialog from GUI.wait_dialog import WaitDialog from GUI.about_dialog import AboutDialog from GUI.import_options import ImportOptionsDialog, ImportOptions from GUI.export_options import ExportOptionsDialog, ExportOptions from GUI.exchange_sample_dialog import ExchangeSampleDialog from GUI.tooltip import ToolTip import e2s_sample_import import utils from version import Oe2sSLE_VERSION, debug Oe2sSLE_dir = os.path.expanduser('~') + os.sep + '.Oe2sSLE' if not debug: class logger: log_file_path = Oe2sSLE_dir + os.sep + 'Oe2sSLE.log' def __init__(self): self.file = None self.stderr = sys.stderr self.stdout = sys.stdout sys.stderr=self sys.stdout=self def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): if self.file: self.file.write('-- Logger closed --\n') self.file.close() sys.stderr = self.stderr sys.stdout = self.stdout def write(self, data): try: if not self.file: if not os.path.exists(Oe2sSLE_dir): os.makedirs(Oe2sSLE_dir) self.file = open(self.log_file_path, 'a') self.file.write(data) self.file.flush() except BaseException as e: tk.messagebox.showerror( "Critical Error", ( 'Failed to write in log file {}:\n' 'Error: {}\n' 'This occured while trying to log following message:\n' '{}' ).format(self.log_file_path, e, data) ) else: class logger: def __init__(self): pass def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): pass def linspace(start,stop,num): for i in range(num): yield start+((stop-start)*i)/(num-1) class WaveDisplay(tk.Canvas): class LineSet: def __init__(self, first, last, loop_first=None, attack_last=None, amplitude=None): self.first=first self.last=last self.loop_first=loop_first self.attack_last=attack_last self.amplitude=amplitude def __init__(self, *arg, **kwarg): kwarg['highlightthickness']=0 super().__init__(*arg, **kwarg) self.bind("<Configure>", self.on_resize) self.height = self.winfo_reqheight() self.width = self.winfo_reqwidth() self.toRefresh = False self.refreshLineSetOnly = False self.scrollBar = None self.wav = [[int(20000*math.sin(x*2.*math.pi/self.width)) for x in range(self.width)]]*2 self.bitmap = bytearray() self.dispFrom = 0 self.dispTo = self.width-1 self.ampMax = 32767 self.ampTot = 65536 self.bgColor = (0,0,127) self.wavColor= (0,0,255) self.photo = tk.PhotoImage(width=self.width, height=self.height) self.photo_handle = self.create_image((0,0), anchor=tk.NW, image=self.photo, state="normal") self.refresh() self.lineSets = [] self.activeLineSet = None self.bind("<Motion>", self.on_motion) self.bind("<ButtonPress-1>", self.on_b1_press) self.bind("<ButtonRelease-1>", self.on_b1_release) self.bind("<B1-Motion>", self.on_b1_motion) def set_activeLineSet(self, activeLineSet=None): if self.activeLineSet != activeLineSet: self.activeLineSet=activeLineSet self.refresh(True) def add_lineSet(self, lineSet): self.lineSets.append(lineSet) self.refresh(True) def set_scrollBar(self, scrollBar): self.scrollBar = scrollBar self.update_scrollBar() def set_wav(self, wave_fmt, wave_data): if wave_fmt.formatTag != RIFF.WAVE_fmt_.WAVE_FORMAT_PCM: raise Exception('format tag') if wave_fmt.bitPerSample != 16: raise Exception('bit per sample') num_chans = wave_fmt.channels num_samples = len(wave_data.rawdata)//2//num_chans tot_num_samples = num_samples*num_chans samples = struct.unpack('<'+str(tot_num_samples)+'h', wave_data.rawdata) self.wav = [] for chan in range(num_chans): self.wav.append(samples[chan:tot_num_samples:num_chans]) self.dispFrom = 0 self.dispTo = num_samples self.activeLineSet = None self.refresh() def wav_length(self): return len(self.wav[0]) def num_channels(self): return len(self.wav) def set_disp(self, dispFrom, dispTo): assert dispFrom < dispTo if dispFrom != self.dispFrom or dispTo != self.dispTo: self.dispFrom = dispFrom self.dispTo = dispTo self.refresh() def display_sample(self, index): if index < self.dispFrom: self.set_disp(index, self.dispTo - self.dispFrom + index) elif index > self.dispTo: self.set_disp(self.dispFrom - self.dispTo + index, index) """ scroll by a step in samples """ def scroll(self, step): self.set_disp(self.dispFrom+step, self.dispTo+step) def scroll_to(self, offset): self.scroll(offset - self.dispFrom) def scroll_stop(self, step): if step < 0: if self.dispFrom >= 0: step = max(step, -self.dispFrom) else: step = 0 elif step > 0: if self.dispTo <= self.wav_length()-1: step = min(step, self.wav_length()-self.dispTo) else: step = 0 self.scroll(step) """ scroll by a step equvalent to pixels """ def scroll_pix(self, step): self.scroll(step/self.get_zoom_x()) def scroll_pix_stop(self, step): self.scroll_stop(step/self.get_zoom_x()) def set_zoom_x(self, zoom): x_mid = self.dispFrom + (self.dispTo-self.dispFrom)/2 dispFrom = int(round(x_mid-self.width/zoom/2)) dispTo = int(round(x_mid+self.width/zoom/2)) if dispFrom < -1: diff = -1-dispFrom dispFrom += diff dispTo += diff if dispTo > self.wav_length()-1: dispTo=self.wav_length()-1 self.set_disp(dispFrom,dispTo) def get_zoom_x(self): return self.width/(self.dispTo-self.dispFrom) def wav_view_length(self): return self.dispTo-self.dispFrom """ """ def on_motion(self,event): lineset = self.activeLineSet if lineset is not None: amp_active = 1 #lineset.loop_first is None if (abs(event.x-lineset._last_x) < 5 or abs(event.x-lineset._mid_x) < 5 or abs(event.x-lineset._first_x) < 5): if self.cget('cursor') != 'sb_h_double_arrow': self.configure(cursor='sb_h_double_arrow') elif (amp_active and (abs(event.y-lineset._amp_y0) < 5 or abs(event.y-lineset._amp_y1) < 5) and event.x >= lineset._amp_start_x and event.x <= lineset._amp_end_x): if self.cget('cursor') != 'sb_v_double_arrow': self.configure(cursor='sb_v_double_arrow') else: if self.cget('cursor'): self.configure(cursor='') def on_b1_press(self,event): self.drag=0 lineset = self.activeLineSet if lineset is not None: amp_active = 1 #lineset.loop_first is None if abs(event.x-lineset._last_x) < 5: self.drag=3 elif abs(event.x-lineset._mid_x) < 5: self.drag=2 elif abs(event.x-lineset._first_x) < 5: self.drag=1 elif amp_active: if abs(event.y-lineset._amp_y1) < 5: self.drag=5 elif abs(event.y-lineset._amp_y0) < 5: self.drag=4 def on_b1_release(self,event): self.drag=0 def on_b1_motion(self,event): if self.drag: lineset = self.activeLineSet x = event.x y = event.y w = self.width h = self.height fr = self.dispFrom to = self.dispTo num_chans = self.num_channels() if self.drag < 4: new_x=int(fr+x*(to-fr)/w) if self.drag == 1: lineset.first.set(new_x) elif self.drag == 2: if lineset.loop_first is not None: lineset.loop_first.set(new_x) else: lineset.attack_last.set(new_x) elif self.drag == 3: lineset.last.set(new_x) else: if self.drag == 4: new_y=int(self.ampTot-y*num_chans*self.ampTot*2/h) lineset.amplitude.set(abs(new_y)) elif self.drag == 5: new_y=int(y*num_chans*self.ampTot*2/h-self.ampTot) lineset.amplitude.set(abs(new_y)) def on_resize(self,event): if self.width == event.width and self.height == event.height: return self.width = event.width self.height = event.height self.refresh() def refresh(self, line_set_only=False): if self.refreshLineSetOnly and not line_set_only: self.refreshLineSetOnly = False if not self.toRefresh: self.toRefresh=True self.after_idle(self.draw_wav) self.update_scrollBar() def update_scrollBar(self): if self.scrollBar: self.scrollBar.set(self.dispFrom/self.wav_length(), self.dispTo/self.wav_length()) def draw_wav(self): # TODO: # - put image into object # - allow to update only parts : slice bar drawn, ... #while True: if self.toRefresh: self.toRefresh=False w = self.width h = self.height fr = self.dispFrom to = self.dispTo wstep = w*3 # from python 3.5: #header = b"P6 %d %d 255 " % (w, h) header = bytes("P6 %d %d 255 " % (w, h), "utf8") head_l = len(header) if self.wav: num_chans = self.num_channels() if not self.refreshLineSetOnly: self.refreshLineSetOnly = True self.wav_ppm = bytearray(head_l+w*h*3) ppm = self.wav_ppm ppm[0:head_l] = header # init with bg color ppm[head_l:] = self.bgColor*(w*h) # draw zero line(s) if self.wav: for chan in range(num_chans): line=int((self.ampMax/self.ampTot+chan)*(h/num_chans)) ppm[head_l+line*wstep:head_l+(line+1)*wstep] = (127,127,127)*w # draw wav if self.wav: for chan in range(num_chans): _smin=0 _smax=0 for x in range(w): start=max(0,int(fr+math.floor((to-fr)*(x)/w))) stop=min(self.wav_length(),max(start+1,int(fr+math.floor((to-fr)*(x+1.)/w)))) if stop>start: __smin=min(self.wav[chan][start:stop]) __smax=max(self.wav[chan][start:stop]) smin=min(_smax,__smin) smax=max(_smin,__smax) _smin=__smin _smax=__smax pStart=int(((self.ampMax-smax)/self.ampTot+chan)*(h/num_chans)) pStop =int(((self.ampMax-smin)/self.ampTot+chan)*(h/num_chans))+1 #for i in range(pStop-pStart): # ppm[head_l+x*3+wstep*(i+pStart)+0:head_l+x*3+wstep*(i+pStart)+3] = self.wavColor ppm[head_l+x*3+wstep*pStart+0:head_l+x*3+wstep*pStop+0:wstep] = (self.wavColor[0],)*(pStop-pStart) ppm[head_l+x*3+wstep*pStart+1:head_l+x*3+wstep*pStop+1:wstep] = (self.wavColor[1],)*(pStop-pStart) ppm[head_l+x*3+wstep*pStart+2:head_l+x*3+wstep*pStop+2:wstep] = (self.wavColor[2],)*(pStop-pStart) # draw line sets ppm = bytearray(head_l+w*h*3) ppm[:] = self.wav_ppm for active in (False, True): for lineSet in self.lineSets: if active == (lineSet is self.activeLineSet): first = lineSet.first.get() last = lineSet.last.get() if lineSet.loop_first is not None: mid = lineSet.loop_first.get() else: mid = lineSet.attack_last.get() if lineSet.amplitude is not None: amp = lineSet.amplitude.get() start_x = max(0, math.floor((first+0.25 - fr)*w/(to - fr))) end_x = min(w-1, math.ceil((last+0.75 - fr)*w/(to - fr))) if active: lineSet._amp_start_x = start_x lineSet._amp_end_x = end_x lineSet._amp_y0 = max(0,int((math.floor((self.ampTot-amp)/2)/self.ampTot)*(h/num_chans))) lineSet._amp_y1 = min(h-1,int((math.floor((self.ampTot+amp)/2)/self.ampTot)*(h/num_chans))) if end_x > start_x: for chan in range(num_chans): y0 = max(0,int((math.floor((self.ampTot-amp)/2)/self.ampTot+chan)*(h/num_chans))) y1 = min(h-1,int((math.floor((self.ampTot+amp)/2)/self.ampTot+chan)*(h/num_chans))) if active: ppm[head_l+start_x*3+wstep*y0+0:head_l+(end_x+1)*3+wstep*y0+0:3] = (255,)*(end_x-start_x+1) ppm[head_l+start_x*3+wstep*y1+0:head_l+(end_x+1)*3+wstep*y1+0:3] = (255,)*(end_x-start_x+1) else: ppm[head_l+start_x*3+wstep*y0+0:head_l+(end_x+1)*3+wstep*y0+0:3] = (127,)*(end_x-start_x+1) ppm[head_l+start_x*3+wstep*y0+1:head_l+(end_x+1)*3+wstep*y0+1:3] = (127,)*(end_x-start_x+1) ppm[head_l+start_x*3+wstep*y1+0:head_l+(end_x+1)*3+wstep*y1+0:3] = (127,)*(end_x-start_x+1) ppm[head_l+start_x*3+wstep*y1+1:head_l+(end_x+1)*3+wstep*y1+1:3] = (127,)*(end_x-start_x+1) if fr <= mid <= to: x = round((mid+0.5 - fr)*w/(to - fr)) if x >= w: x = w-1 # some green if active: ppm[head_l+x*3+1:head_l+x*3+wstep*h+1:wstep] = (255,)*h lineSet._mid_x=x else: ppm[head_l+x*3+1:head_l+x*3+wstep*h+1:wstep] = (127,)*h elif active: lineSet._mid_x=round((mid+0.5 - fr)*w/(to - fr)) if fr <= first <= to: x = math.floor((first+0.25 - fr)*w/(to - fr)) if x >= w: x = w-1 # some green and red if active: ppm[head_l+x*3+0:head_l+x*3+wstep*h+0:wstep] = (255,)*h ppm[head_l+x*3+1:head_l+x*3+wstep*h+1:wstep] = (255,)*h lineSet._first_x=x else: ppm[head_l+x*3+0:head_l+x*3+wstep*h+0:wstep] = (127,)*h ppm[head_l+x*3+1:head_l+x*3+wstep*h+1:wstep] = (127,)*h elif active: lineSet._first_x=math.floor((first+0.25 - fr)*w/(to - fr)) if fr <= last <= to: x = math.ceil((last+0.75 - fr)*w/(to - fr)) if x >= w: x = w-1 # some red if active: ppm[head_l+x*3+0:head_l+x*3+wstep*h+0:wstep] = (255,)*h lineSet._last_x=x else: ppm[head_l+x*3+0:head_l+x*3+wstep*h+0:wstep] = (127,)*h elif active: lineSet._last_x=math.ceil((last+0.75 - fr)*w/(to - fr)) self.photo.configure(data=bytes(ppm), width=w, height=h) class MaxValueEntry(tk.Entry): def __init__(self, parent, max, *arg, **kwarg): self.MVEmax = max self.MVEvar = kwarg.get('textvariable') if self.MVEvar: self.MVEvar_trace = self.MVEvar.trace('w', self._var_set) super().__init__(parent, *arg, **kwarg) self.defaultbg = self.cget('disabledbackground') def config(self, *arg, **kwarg): var = kwarg.get('textvariable') if var: if self.MVEvar: self.MVEvar.trace_vdelete('w', self.MVEvar_trace) self.MVEvar=var super().config(*arg, **kwarg) if var: self.MVEvar_trace = self.MVEvar.trace('w', self._var_set) def _var_set(self, *args): val = self.MVEvar.get() if val <= self.MVEmax: self.config(disabledbackground=self.defaultbg) else: self.config(disabledbackground="#C80000") class SampleNumSpinbox(ROSpinbox): def __init__(self, parent, *arg, **kwarg): self.SNScommand=kwarg.get('command') self.SNSvar=kwarg.get('textvariable') if self.SNSvar: self.SNSvarString=tk.StringVar() self.SNSvarString.set(self.SNSvar.get()) kwarg['textvariable'] = self.SNSvarString super().__init__(parent, *arg, **kwarg) self.config(state=tk.NORMAL) self.defaultbg = self.cget('background') self.bind("<Shift-Up>",lambda event: self.big_increase(98)) self.bind("<Shift-Down>",lambda event: self.big_increase(-98)) self.bind("<Prior>",lambda event: self.big_increase(999)) self.bind("<Next>",lambda event: self.big_increase(-999)) self.bind("<Shift-Prior>",lambda event: self.big_increase(9999)) self.bind("<Shift-Next>",lambda event: self.big_increase(-9999)) if self.SNSvar: self._safeSet=False self.SNSvar_trace = self.SNSvar.trace('w', self._var_set) self.SNSvarString_trace = self.SNSvarString.trace('w', self._varString_set) def config(self, *arg, **kwarg): command=kwarg.get('command') if command: self.SNScommand = command var=kwarg.get('textvariable','') if var != '': if self.SNSvar: self.SNSvar.trace_vdelete('w', self.SNSvar_trace) self.SNSvarString.trace_vdelete('w', self.SNSvarString_trace) self.SNSvar=var if var: self.SNSvarString=tk.StringVar() self.SNSvarString.set(self.SNSvar.get()) kwarg['textvariable'] = self.SNSvarString super().config(*arg, **kwarg) if var: self._safeSet=False self.SNSvar_trace = self.SNSvar.trace('w', self._var_set) self.SNSvarString_trace = self.SNSvarString.trace('w', self._varString_set) def _var_set(self, *args): if not self._safeSet: self.SNSvarString.set(self.SNSvar.get()) def _varString_set(self, *args): v=self.SNSvarString.get() _max=int(self.cget('to')) _min=int(self.cget('from')) if utils.isint(v) and _min <= int(v) <= _max: self.config(background=self.defaultbg) self._safeSet=True self.SNSvar.set(v) self._safeSet=False # execute the command if self.SNScommand: self.SNScommand() else: self.config(background="#C80000") def big_increase(self, increment): _curr=int(self.get()) _max=int(self.cget('to')) _min=int(self.cget('from')) _next=min(_curr+increment,_max) _next=max(_next,_min) self.tk.globalsetvar(self.cget('textvariable'),_next) if increment: self.invoke('buttonup' if increment > 0 else 'buttondown') class CVar: def __init__(self, var, min, max): self.var = var self.min = min self.max = max def set(self, value): if value < self.min: self.var.set(self.min) elif value > self.max: self.var.set(self.max) else: self.var.set(value) def get(self): return self.var.get() class Slice: def __init__(self, master, editor, sliceNum): self.master = master self.editor = editor self.sliceNum = sliceNum self.labelSlice = tk.Label(self.master, text="Slice "+str(self.sliceNum)).grid(row=sliceNum+1) self.start = tk.IntVar() self.stop = tk.IntVar() self.attack = tk.IntVar() self.amplitude = tk.IntVar() self.startTrace = None self.stopTrace = None self.attackTrace = None self.amplitudeTrace = None self.entryStart = SampleNumSpinbox(self.master, width=10, from_=0, textvariable=self.start, state='readonly') self.entryStop = SampleNumSpinbox(self.master, width=10, from_=-1, textvariable=self.stop, state='readonly') self.entryAttack = SampleNumSpinbox(self.master, width=10, from_=-1, textvariable=self.attack, state='readonly') self.entryAmplitude = SampleNumSpinbox(self.master, width=10, from_=0, textvariable=self.amplitude, state='readonly') self.buttonPlay = tk.Button(self.master, image=GUI.res.playIcon, command=self._play) ToolTip(self.buttonPlay, follow_mouse=1, text="play slice") self._selected=False self.entryStart.bind("<FocusIn>",self._focus_in,add="+") self.entryStart.bind("<FocusOut>",self._focus_out,add="+") self.entryStop.bind("<FocusIn>",self._focus_in,add="+") self.entryStop.bind("<FocusOut>",self._focus_out,add="+") self.entryAttack.bind("<FocusIn>",self._focus_in,add="+") self.entryAttack.bind("<FocusOut>",self._focus_out,add="+") self.entryAmplitude.bind("<FocusIn>",self._focus_in,add="+") self.entryAmplitude.bind("<FocusOut>",self._focus_out,add="+") self.entryStart.grid(row=sliceNum+1, column=1) self.entryStop.grid(row=sliceNum+1, column=2) self.entryAttack.grid(row=sliceNum+1, column=3) self.entryAmplitude.grid(row=sliceNum+1, column=4) self.buttonPlay.grid(row=sliceNum+1, column=5) self.lineSet = WaveDisplay.LineSet(self.start,self.stop,attack_last=self.attack,amplitude=self.amplitude) self.editor.wavDisplay.add_lineSet(self.lineSet) def set_sample(self, fmt, data, esli): if self.startTrace: self.start.trace_vdelete('w', self.startTrace) if self.stopTrace: self.stop.trace_vdelete('w', self.stopTrace) if self.attackTrace: self.attack.trace_vdelete('w', self.attackTrace) if self.amplitudeTrace: self.amplitude.trace_vdelete('w', self.amplitudeTrace) self.fmt = fmt self.data = data.rawdata self.esli = esli self.blockAlign = fmt.blockAlign self.sample_length = len(data) // self.blockAlign start=esli.slices[self.sliceNum].start + self.esli.OSC_StartPoint_address//self.blockAlign stop=start+esli.slices[self.sliceNum].length-1 attack=start+esli.slices[self.sliceNum].attack_length-1 amplitude=esli.slices[self.sliceNum].amplitude self.entryStart.config(to=self.sample_length-1) self.entryStop.config(to=self.sample_length-1) self.entryAttack.config(to=self.sample_length-1) self.entryAmplitude.config(to=65536) self.start.set(start) self.stop.set(stop) self.attack.set(attack) self.amplitude.set(amplitude) self.lineSet.first = CVar(self.start,0,self.sample_length-1) self.lineSet.last = CVar(self.stop,-1,self.sample_length-1) self.lineSet.attack_last = CVar(self.attack,-1,self.sample_length-1) self.lineSet.amplitude = CVar(self.amplitude,0,65536) self.startTrace = self.start.trace('w', self._start_set) self.stopTrace = self.stop.trace('w', self._stop_set) self.attackTrace = self.attack.trace('w', self._attack_set) self.amplitudeTrace = self.amplitude.trace('w', self._amplitude_set) self._selected=False def _focus_in(self, event): if not self._selected: self.editor.wavDisplay.set_activeLineSet(self.lineSet) self.selected = True def _focus_out(self, event): if self._selected: self.editor.wavDisplay.set_activeLineSet() self.selected = False def _start_set(self, *args): start = self.start.get() if start > self.sample_length-1: self.start.set(self.sample_length-1) start = self.sample_length-1 elif start < 0: self.start.set(0) start = 0 if start > self.stop.get()+1: self.stop.set(start-1) if start > self.attack.get()+1: self.attack.set(start-1) self.esli.slices[self.sliceNum].start = start - self.esli.OSC_StartPoint_address//self.blockAlign # update the offsets self.esli.slices[self.sliceNum].length = self.stop.get()-start+1 self.esli.slices[self.sliceNum].attack_length = self.attack.get()-start+1 self.editor.wavDisplay.refresh(True) def _stop_set(self, *args): stop = self.stop.get() if stop > self.sample_length-1: self.stop.set(self.sample_length-1) stop = self.sample_length-1 elif stop < -1: self.stop.set(-1) stop= -1 if stop < self.start.get()-1: self.start.set(stop+1) if stop < self.attack.get(): self.attack.set(stop) self.esli.slices[self.sliceNum].length = stop-self.start.get()+1 self.editor.wavDisplay.refresh(True) def _attack_set(self, *args): attack = self.attack.get() if attack > self.sample_length-1: self.attack.set(self.sample_length-1) attack = self.sample_length-1 elif attack < -1: self.attack.set(-1) attack = -1 if attack < self.start.get()-1: self.start.set(attack+1) if attack > self.stop.get(): self.stop.set(attack) self.esli.slices[self.sliceNum].attack_length = attack-self.start.get()+1 self.editor.wavDisplay.refresh(True) def _amplitude_set(self, *args): amp = self.amplitude.get() if amp > 65536: self.amplitude.set(65536) amp = 65536 elif amp < 0: self.amplitude.set(0) amp = 0 self.esli.slices[self.sliceNum].amplitude = amp self.editor.wavDisplay.refresh(True) def _play(self, *args): start=self.start.get()*self.fmt.blockAlign stop=self.stop.get()*self.fmt.blockAlign if stop > 0: audio.player.play_start(audio.Sound(self.data[start:stop],self.fmt)) class FrameSlices(tk.Frame): def __init__(self, master, editor, *arg, **kwarg): super().__init__(master, *arg, **kwarg) tk.Label(self, text="First").grid(row=0, column=1) tk.Label(self, text="Last").grid(row=0, column=2) tk.Label(self, text="?Attack?").grid(row=0, column=3) tk.Label(self, text="?Amplitude?").grid(row=0, column=4) self.slices = [Slice(self,editor,i) for i in range(64)] def set_sample(self, fmt, data, esli): for s in self.slices: s.set_sample(fmt,data,esli) class NormalSampleOptions(tk.LabelFrame): def __init__(self, parent, editor, *arg, **kwarg): super().__init__(parent, *arg, **kwarg) self.editor = editor self.sound = None self.fmt = None self.data = None self.esli = None self.sample_length = 0 tk.Label(self, text="Start").grid(row=1, column=1) tk.Label(self, text="End").grid(row=1, column=2) tk.Label(self, text="Loop start").grid(row=1, column=3) tk.Label(self, text="Play volume").grid(row=1, column=4) self.start = tk.IntVar() self.stop = tk.IntVar() self.loopStart = tk.IntVar() self.playVolume = tk.IntVar() self.start_trace = None self.stop_trace = None self.loopStart_trace = None self.playVolume_trace = None self.rootSet = None tk.Label(self, text="Sample").grid(row=2, column=0) self.startEntry = SampleNumSpinbox(self, width=10, from_=0, to=0, textvariable=self.start, state='readonly') self.startEntry.grid(row=2, column=1) self.stopEntry = SampleNumSpinbox(self, width=10, from_=0, to=0, textvariable=self.stop, state='readonly') self.stopEntry.grid(row=2, column=2) self.loopStartEntry = SampleNumSpinbox(self, width=10, from_=0, to=0, textvariable=self.loopStart, state='readonly') self.loopStartEntry.grid(row=2, column=3) self.playVolumeEntry = SampleNumSpinbox(self, width=10, from_=0, to=65535, textvariable=self.playVolume, state='readonly') self.playVolumeEntry.grid(row=2, column=4) self.buttonPlay = tk.Button(self, image=GUI.res.playIcon, command=self.play_start) self.buttonPlay.grid(row=2,column=5) self.buttonStop = tk.Button(self, image=GUI.res.stopIcon, command=self.play_stop) self.buttonStop.grid(row=2,column=6) self.buttonTrim = tk.Button(self, image=GUI.res.trimIcon, command=self.trim) self.buttonTrim.grid(row=2, column=7, padx=10, pady=2) ToolTip(self.buttonPlay, follow_mouse=1, text="play from Start to End\nand loop if not 1-shot") ToolTip(self.buttonStop, follow_mouse=1, text="stop playback") ToolTip(self.buttonTrim, follow_mouse=1, text="trim sample:\ndelete everything before 'Start' and after 'End' points") self._selected=False self.startEntry.bind("<FocusIn>",self._focus_in,add="+") self.startEntry.bind("<FocusOut>",self._focus_out,add="+") self.stopEntry.bind("<FocusIn>",self._focus_in,add="+") self.stopEntry.bind("<FocusOut>",self._focus_out,add="+") self.loopStartEntry.bind("<FocusIn>",self._focus_in,add="+") self.loopStartEntry.bind("<FocusOut>",self._focus_out,add="+") self.playVolumeEntry.bind("<FocusIn>",self._focus_in,add="+") self.playVolumeEntry.bind("<FocusOut>",self._focus_out,add="+") self.lineSet = WaveDisplay.LineSet(self.start,self.stop,loop_first=self.loopStart) self.editor.wavDisplay.add_lineSet(self.lineSet) def play_start(self): # TODO: icon pause # TODO: see how to reduce delay (i.e. buffer size) #if self.sound is not None: # self.sound.pause() # TODO: verrify meaning of each offset in esli (+ or - 1 or not ) audio.player.play_start(audio.LoopWaveSource(self.data,self.fmt,self.esli)) def play_stop(self): audio.player.play_stop() def trim(self): if tkinter.messagebox.askokcancel( 'Trim Sample', 'This operation is not reversible', icon='warning' ): audio.player.play_stop() trim(self.smpl, self.start.get(), self.stop.get()) self.editor.set_sample(self.smpl_list, self.smpl_num) self.focus() def set_sample(self, smpl_list, smpl_num): self.smpl_list = smpl_list self.smpl = smpl = smpl_list.e2s_samples[smpl_num] self.smpl_num = smpl_num fmt = smpl.get_fmt() data = smpl.get_data() esli = smpl.get_esli() self.fmt = fmt self.data = data.rawdata self.esli = esli self.blockAlign = fmt.blockAlign self.sample_length = len(data) // self.blockAlign self.oneshot = esli.OSC_OneShot if self.start_trace: self.start.trace_vdelete('w', self.start_trace) if self.stop_trace: self.stop.trace_vdelete('w', self.stop_trace) if self.loopStart_trace: self.loopStart.trace_vdelete('w', self.loopStart_trace) if self.playVolume_trace: self.playVolume.trace_vdelete('w', self.playVolume_trace) start=esli.OSC_StartPoint_address//self.blockAlign stop=start+esli.OSC_EndPoint_offset//self.blockAlign loopStart=start+esli.OSC_LoopStartPoint_offset//self.blockAlign playVolume=esli.playVolume self.startEntry.config(to=self.sample_length-1) self.stopEntry.config(to=self.sample_length-1) self.loopStartEntry.config(to=self.sample_length-1) self.start.set(start) self.stop.set(stop) self.loopStart.set(loopStart) self.playVolume.set(playVolume) self.start_trace = self.start.trace('w', self._start_set) self.stop_trace = self.stop.trace('w', self._stop_set) self.loopStart_trace = self.loopStart.trace('w', self._loopStart_set) self.playVolume_trace = self.playVolume.trace('w', self._playVolume_set) self.lineSet.first = CVar(self.start,0,self.sample_length-1) self.lineSet.last = CVar(self.stop,0,self.sample_length-1) self.lineSet.loop_first = CVar(self.loopStart,0,self.sample_length-1) self.lineSet.amplitude = CVar(self.playVolume,0,65535) self._selected=False def _focus_in(self, event): if not self._selected: self.editor.wavDisplay.set_activeLineSet(self.lineSet) self.selected = True def _focus_out(self, event): if self._selected: self.editor.wavDisplay.set_activeLineSet() self.selected = False def _start_set(self, *args): if self.rootSet is None: self.rootSet = self._start_set if self.loopStart.get() < self.start.get(): self.loopStart.set(self.start.get()) if self.stop.get() < self.start.get(): self.stop.set(self.start.get()) self.editor.wavDisplay.display_sample(self.start.get()) self.rootSet = None start = self.start.get() prev_OSC_StartPoint_address = self.esli.OSC_StartPoint_address self.esli.OSC_StartPoint_address = start*self.blockAlign # update the offsets self.esli.OSC_LoopStartPoint_offset = (self.loopStart.get()-start)*self.blockAlign self.esli.OSC_EndPoint_offset = (self.stop.get()-start)*self.blockAlign # update slices for slice in self.esli.slices: slice.start += (prev_OSC_StartPoint_address - self.esli.OSC_StartPoint_address)//self.blockAlign self.editor.wavDisplay.set_activeLineSet(self.lineSet) self.editor.wavDisplay.refresh(True) def _stop_set(self, *args): if self.rootSet is None: self.rootSet = self._stop_set if self.start.get() > self.stop.get(): self.start.set(self.stop.get()) if self.loopStart.get() > self.stop.get(): self.loopStart.set(self.stop.get()) self.editor.wavDisplay.display_sample(self.stop.get()) self.rootSet = None start = self.start.get() stop = self.stop.get() loopStart = self.loopStart.get() self.esli.OSC_EndPoint_offset = (stop-start)*self.blockAlign if self.rootSet is None and self.oneshot: # can't be oneShot and have loopStart != stop if loopStart != stop: self.esli.OSC_OneShot = False else: self.esli.OSC_OneShot = True self.smpl_list.update_sample(self.smpl_num) self.editor.wavDisplay.set_activeLineSet(self.lineSet) self.editor.wavDisplay.refresh(True) def _loopStart_set(self, *args): if self.rootSet is None: self.rootSet = self._loopStart_set if self.start.get() > self.loopStart.get(): self.start.set(self.loopStart.get()) if self.stop.get() < self.loopStart.get(): self.stop.set(self.loopStart.get()) self.editor.wavDisplay.display_sample(self.loopStart.get()) self.rootSet = None start = self.start.get() stop = self.stop.get() loopStart = self.loopStart.get() self.esli.OSC_LoopStartPoint_offset = (loopStart-start)*self.blockAlign if self.rootSet is None and self.oneshot: # can't be oneShot and have loopStart != stop if loopStart != stop: self.esli.OSC_OneShot = False else: self.esli.OSC_OneShot = True self.smpl_list.update_sample(self.smpl_num) self.editor.wavDisplay.set_activeLineSet(self.lineSet) self.editor.wavDisplay.refresh(True) def _playVolume_set(self, *args): playVolume = self.playVolume.get() if playVolume > 65535: self.playVolume.set(65535) playVolume = 65535 elif playVolume < 0: self.playVolume.set(0) playVolume = 0 self.esli.playVolume = playVolume self.editor.wavDisplay.refresh(True) class SlicedSampleOptions(tk.LabelFrame): def __init__(self, parent, editor, *arg, **kwarg): super().__init__(parent, *arg, **kwarg) self.frameSlices = FrameSlices(self, editor) self.frameSlices.pack(fill=tk.BOTH, expand=tk.YES) def set_sample(self, fmt, data, esli): self.frameSlices.set_sample(fmt,data,esli) class SliceEditor(tk.PanedWindow): def __init__(self, *args, **kwargs): super().__init__(*args, orient='vertical', **kwargs) self.esli=None # Some config width height settings canvas_width = 640 canvas_height = 240 self.frameWave = frameWave = tk.Frame(self) self.wavDisplay = WaveDisplay(frameWave, width=canvas_width, height=canvas_height) self.wavDisplay.pack(fill=tk.BOTH, expand=tk.YES, padx=2) self.h_scroll = tk.Scrollbar(frameWave, orient=tk.HORIZONTAL, command=self.scroll_wav) self.h_scroll.pack(fill=tk.X, expand=tk.NO, padx=2) self.wavDisplay.set_scrollBar(self.h_scroll) framezoom = tk.Frame(frameWave) framezoom.pack(fill=tk.X, expand=tk.NO) tk.Label(framezoom,text="Zoom:").pack(side=tk.LEFT) self.zoomVar=tk.StringVar() self.zoomVar.trace("w",self._zoom_edit) self.zoomEdit = ROSpinbox(framezoom, values=('all',), textvariable=self.zoomVar) self.zoomEdit.pack(side=tk.LEFT) frameWave.update_idletasks() self.add(frameWave, minsize=frameWave.winfo_reqheight()) self.frame = VerticalScrolledFrame(self) self.add(self.frame) self.normalSampleOptions = NormalSampleOptions(self.frame.interior, self, text="Normal sample options") self.normalSampleOptions.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES) self.slicedSampleOptions = SlicedSampleOptions(self.frame.interior, self, text="Sliced sample options") self.slicedSampleOptions.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES) frame = tk.Frame(self.frame.interior) frame.pack() self.slicedRadioV = tk.BooleanVar() self.radioUseNormal = tk.Radiobutton(frame, text="Normal sample", variable=self.slicedRadioV, value=False, state=tk.DISABLED) self.radioUseNormal.grid(row=0,column=0,sticky=tk.W) self.radioUseSliced = tk.Radiobutton(frame, text="Sliced sample", variable=self.slicedRadioV, value=True, state=tk.DISABLED) self.radioUseSliced.grid(row=1,column=0,sticky=tk.W) tk.Label(frame,text="Steps").grid(row=0,column=1,sticky=tk.E) tk.Label(frame,text="/").grid(row=0,column=2) tk.Label(frame,text="Beat").grid(row=0,column=3,sticky=tk.W) tk.Label(frame,text="Active Steps").grid(row=0,column=4) self.numSteps = tk.IntVar() self.numStepsTrace = None self.numActiveSteps = tk.IntVar() self.numActiveStepsTrace = None self.numStepsEdit = ROSpinbox(frame, from_=0, to=64, width=2, textvariable=self.numSteps) self.numStepsEdit.grid(row=1,column=1,sticky=tk.E) tk.Label(frame,text="/").grid(row=1,column=2) self.beat = tk.StringVar() self.beatTrace = None self.beatEdit = ROSpinbox(frame, values=tuple(sorted(e2s.esli_beat, key=e2s.esli_beat.get)), width=6, textvariable=self.beat) self.beatEdit.grid(row=1,column=3,sticky=tk.W) self.numActiveStepsEntry = tk.Entry(frame, width=2, textvariable=self.numActiveSteps, state=tk.DISABLED) self.numActiveStepsEntry.grid(row=1,column=4) frame = tk.Frame(self.frame.interior) frame.pack() self.activeSteps= [] self.activeStepsTrace = [] for j in range(64): self.activeSteps.append(tk.StringVar()) self.activeStepsTrace.append(None) self.activeStepsEntry = [] for j in range(4): tk.Label(frame, text="Step : ").grid(row=j*2+0, column=0) tk.Label(frame, text="Slice : ").grid(row=j*2+1, column=0) for i in range(16): tk.Label(frame, text=j*16+i+1).grid(row=j*2+0,column=i+1) self.activeStepsEntry.append(ROSpinbox(frame, values=("Off",)+tuple(range(64)), width=3, textvariable=self.activeSteps[j*16+i])) # bug? 'textvariable' must be configured later than 'values' to be used #self.activeStepsEntry[j*16+i].config(textvariable=self.activeSteps[j*16+i]) self.activeStepsEntry[j*16+i].grid(row=j*2+1,column=i+1) tk.Button(frame, text="Off them all", command=self.allActiveStepsOff).grid(row=8, column=0, columnspan=17, padx=5, pady=5) def allActiveStepsOff(self): for j in range(64): self.activeSteps[j].set("Off") def _zoom_edit(self, *args): zoomStr = self.zoomVar.get() if zoomStr == 'all': self.wavDisplay.set_disp(0,self.wavDisplay.wav_length()) else: zoom = float(zoomStr) self.wavDisplay.set_zoom_x(zoom) def scroll_wav(self, command, *args): if command == tk.MOVETO: offset = float(args[0]) scroll_tot = self.wavDisplay.wav_length() self.wavDisplay.scroll_to(scroll_tot * offset) elif command == tk.SCROLL: step = float(args[0]) what = args[1] if what == "units": self.wavDisplay.scroll_pix_stop(step*16) elif what == "pages": self.wavDisplay.scroll_pix_stop(step*self.wavDisplay.width/2) else: raise Exception("Unknown scroll unit: " + what) def set_sample(self, smpl_list, smpl_num): self.smpl = smpl = smpl_list.e2s_samples[smpl_num] fmt = smpl.get_fmt() data = smpl.get_data() esli = smpl.get_esli() self.esli = esli self.zoomVar.set('all') #compute zoom 'all' factor: zoom_all = self.wavDisplay.width/(len(data)//fmt.blockAlign) zooms = [16., 8., 4., 2., 1.] curr_zoom=0.5 while curr_zoom > zoom_all: zooms.append(curr_zoom) curr_zoom /= 2 zooms.append('all') zooms.reverse() self.zoomEdit.config(values=tuple(zooms)) #self.zoomEdit.config(values=('all', 0.0625, 0.125, 0.25, 0.5, 1. ,2. ,4., 8., 16.)) self.wavDisplay.set_wav(fmt, data) self.normalSampleOptions.set_sample(smpl_list, smpl_num) self.slicedSampleOptions.set_sample(fmt, data, esli) if self.numActiveStepsTrace: self.numActiveSteps.trace_vdelete('w', self.numActiveStepsTrace) for j in range(64): if self.activeStepsTrace[j]: self.activeSteps[j].trace_vdelete('w', self.activeStepsTrace[j]) self.activeSteps[j].set(str(esli.sliceSteps[j]) if esli.sliceSteps[j] >= 0 else "Off") self.activeStepsTrace[j] = self.activeSteps[j].trace('w', lambda *args, j=j: self._activeStepEdit(j)) if self.numStepsTrace: self.numSteps.trace_vdelete('w', self.numStepsTrace) self.numSteps.set(esli.slicingNumSteps) self.numStepsTrace = self.numSteps.trace('w', self._numStepsEdit) if self.beatTrace: self.beat.trace_vdelete('w', self.beatTrace) self.beat.set(e2s.esli_beat_to_str.get(esli.slicingBeat)) self.beatTrace = self.beat.trace('w', self._beatEdit) self.numActiveSteps.set(esli.slicesNumActiveSteps) self.numActiveStepsTrace = self.numActiveSteps.trace('w', self._numActiveStepsChanged) self.slicedRadioV.set(self.numActiveSteps.get()>0) #reset view point of vertical scroll self.frame.canvas.xview_moveto(0) self.frame.canvas.yview_moveto(0) def _activeStepEdit(self, index): value = self.activeSteps[index].get() self.esli.sliceSteps[index] = int(value) if value != "Off" else -1 self._updateSlicesNumActiveSteps() def _numStepsEdit(self, *args): numSteps = self.numSteps.get() self.esli.slicingNumSteps = numSteps self._updateSlicesNumActiveSteps() #self.slicedRadioV.set(numSteps>0) def _beatEdit(self, *args): self.esli.slicingBeat = e2s.esli_beat.get(self.beat.get()) self._limitNumSteps() self._updateSlicesNumActiveSteps() def _limitNumSteps(self): if self.beat.get() in ('8 Tri','16 Tri'): self.numStepsEdit.config(to=48) if self.numSteps.get() > 48: self.numSteps.set(48) else: self.numStepsEdit.config(to=64) def _updateSlicesNumActiveSteps(self): numActiveSteps=0 for i in range(self.numSteps.get()): numActiveSteps += 1 if self.activeSteps[i].get() != "Off" else 0 self.numActiveSteps.set(numActiveSteps) def _numActiveStepsChanged(self, *args): self.esli.slicesNumActiveSteps = self.numActiveSteps.get() self.slicedRadioV.set(self.numActiveSteps.get()>0) class SliceEditorDialog(tk.Toplevel): system = platform.system() def __init__(self, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) self.withdraw() self.transient(parent) self.protocol("WM_DELETE_WINDOW", self.on_delete) self.sliceEditor = SliceEditor(self) self.sliceEditor.pack(fill=tk.BOTH, expand=tk.YES) self.sliceEditor.update_idletasks() width, height = (self.sliceEditor.winfo_reqwidth(), self.sliceEditor.winfo_reqheight()) self.minsize(width, height) if self.system == 'Windows': def _on_mousewheel(event): self.sliceEditor.frame.canvas.yview_scroll(-1*(event.delta//120), "units") self.bind('<MouseWheel>', _on_mousewheel) elif self.system == 'Darwin': def _on_mousewheel(event): self.sliceEditor.frame.canvas.yview_scroll(-1*(event.delta), "units") self.bind('<MouseWheel>', _on_mousewheel) else: def _on_up(event): self.sliceEditor.frame.canvas.yview_scroll(-1, "units") def _on_down(event): self.sliceEditor.frame.canvas.yview_scroll(1, "units") self.bind('<Button-4>', _on_up) self.bind('<Button-5>', _on_down) def run(self): self.deiconify() self.grab_set() self.focus_set() def on_delete(self): audio.player.play_stop() self.withdraw() parent=self.master parent.grab_set() parent.focus_set() class Sample(object): OSC_caths = tuple( e2s.esli_OSC_cat_to_str[k] for k in sorted(e2s.esli_OSC_cat_to_str)) def __init__(self, master, line_num, sample_num): self.master = master self.frame = master.frame self.name = tk.StringVar() self.oscNum = tk.IntVar() self.oneShot = tk.BooleanVar() self.plus12dB = tk.BooleanVar() self.tuneVal = tk.IntVar() self.samplingFreq= tk.IntVar() self.stereo=tk.BooleanVar() self.smpSize=tk.IntVar() self.name_trace = None self.oscNum_trace = None self.oneShot_trace = None self.plus12dB_trace = None self.tuneVal_trace = None self.radioButton = tk.Radiobutton(self.frame, variable=self.master.selectV) self.replaceButton = tk.Button(self.frame, image=GUI.res.replaceIcon, command=self._on_replace) self.exportButton = tk.Button(self.frame, image=GUI.res.exportIcon, command=self._on_export) self.durationEntry = tk.Label(self.frame, width=8, state=tk.DISABLED, relief=tk.SUNKEN, anchor=tk.E) self.entryOscCat = ROCombobox(self.frame, values=Sample.OSC_caths, width=8, command=self._oscCat_set) self.entryOscNum = SampleNumSpinbox(self.frame,width=3, textvariable=self.oscNum,command=self._oscNum_command) # RIFF_korg_esli.playLogPeriod has a 0.5814686990855805 to 1536036.6940220615 frequency range self.samplingFreqEntry = SampleNumSpinbox(self.frame, width=8, textvariable=self.samplingFreq, justify=tk.RIGHT, from_=1, to=1536036, command=self._samplingFreq_command) self.entryName = tk.Entry(self.frame, width=16, textvariable=self.name) self.checkOneShot = tk.Checkbutton(self.frame, variable=self.oneShot) self.check12dB = tk.Checkbutton(self.frame, variable=self.plus12dB) self.entryTune = ROSpinbox(self.frame, from_=-63, to=63, width=3, format='%2.0f', textvariable=self.tuneVal) self.buttonPlay = tk.Button(self.frame, image=GUI.res.playIcon, command=self.play) self.checkStereo = tk.Checkbutton(self.frame, variable=self.stereo, command=self._stereo_command) self.sizeEntry = tk.Entry(self.frame, width=8, textvariable=self.smpSize, state=tk.DISABLED, justify=tk.RIGHT) self.buttonEdit = tk.Button(self.frame, image=GUI.res.editIcon, command=self._on_edit) self.buttonDelete = tk.Button(self.frame, image=GUI.res.trashIcon, command=self._on_delete) ToolTip(self.replaceButton, follow_mouse=1, text="import replacement sample") ToolTip(self.exportButton, follow_mouse=1, text="export sample") ToolTip(self.buttonPlay, follow_mouse=1, text="play full WAV content\n(start/loop/end are ignored)") ToolTip(self.buttonEdit, follow_mouse=1, text="edit loop/slices points") ToolTip(self.buttonDelete, follow_mouse=1, text="delete") self.restore(line_num, sample_num) def restore(self, line_num, sample_num): self.set_sample_num(sample_num) self.grid(line_num+1) def grid(self, row): self.radioButton.grid(row=row, column=0) self.replaceButton.grid(row=row, column=2) self.entryOscNum.grid(row=row, column=3) self.entryName.grid(row=row, column=4) self.entryOscCat.grid(row=row, column=5) self.checkOneShot.grid(row=row,column=6) self.check12dB.grid(row=row,column=7) self.entryTune.grid(row=row,column=8) self.buttonPlay.grid(row=row, column=9) self.samplingFreqEntry.grid(row=row, column=10) self.durationEntry.grid(row=row, column=11) self.checkStereo.grid(row=row, column=12) self.sizeEntry.grid(row=row, column=13) self.buttonEdit.grid(row=row, column=14, padx=10) self.exportButton.grid(row=row, column=15, padx=10) self.buttonDelete.grid(row=row, column=16, padx=10) def forget(self): self.radioButton.grid_forget() self.replaceButton.grid_forget() self.exportButton.grid_forget() self.entryOscNum.grid_forget() self.entryName.grid_forget() self.entryOscCat.grid_forget() self.checkOneShot.grid_forget() self.check12dB.grid_forget() self.entryTune.grid_forget() self.buttonPlay.grid_forget() self.samplingFreqEntry.grid_forget() self.durationEntry.grid_forget() self.checkStereo.grid_forget() self.sizeEntry.grid_forget() self.buttonEdit.grid_forget() self.buttonDelete.grid_forget() def destroy(self): self.radioButton.destroy() self.replaceButton.destroy() self.exportButton.destroy() self.entryOscNum.destroy() self.entryName.destroy() self.entryOscCat.destroy() self.checkOneShot.destroy() self.check12dB.destroy() self.entryTune.destroy() self.buttonPlay.destroy() self.samplingFreqEntry.destroy() self.durationEntry.destroy() self.checkStereo.destroy() self.sizeEntry.destroy() self.buttonEdit.destroy() self.buttonDelete.destroy() def set_sample_num(self, sample_num): self.sample_num = sample_num self.e2s_sample = self.master.e2s_samples[sample_num] self.reset_vars() def reset_vars(self): if self.name_trace: self.name.trace_vdelete('w', self.name_trace) if self.oscNum_trace: self.oscNum.trace_vdelete('w', self.oscNum_trace) self.entryOscNum.config(textvariable=None) if self.oneShot_trace: self.oneShot.trace_vdelete('w', self.oneShot_trace) if self.plus12dB_trace: self.plus12dB.trace_vdelete('w', self.plus12dB_trace) if self.tuneVal_trace: self.tuneVal.trace_vdelete('w', self.tuneVal_trace) esli = self.e2s_sample.get_esli() fmt = self.e2s_sample.get_fmt() data = self.e2s_sample.get_data() self.entryOscNum.config(from_=self.sample_num+19 if self.sample_num+19<422 else self.sample_num+19+79, to=999) self.radioButton.config(value=self.sample_num) self.name.set(esli.OSC_name.decode('ascii', 'ignore').split('\x00')[0]) self.oscNum.set(esli.OSC_0index+1) self.entryOscNum._prev = self.oscNum.get() self.entryOscCat.set(Sample.OSC_caths[esli.OSC_category]) self.oneShot.set(esli.OSC_OneShot) self.plus12dB.set(esli.playLevel12dB) self.tuneVal.set(esli.sampleTune) self.samplingFreq.set(esli.samplingFreq) if fmt.samplesPerSec != esli.samplingFreq: print("Warning: sampling frequency differs between esli and fmt") self.durationEntry.config(text="{:.4f}".format(len(data)/fmt.avgBytesPerSec if fmt.avgBytesPerSec else 0)) self.stereo.set(fmt.channels > 1) self.smpSize.set(len(data)) self.name_trace = self.name.trace('w', self._name_set) self.oscNum_trace = self.oscNum.trace('w', self._oscNum_set) self.oneShot_trace = self.oneShot.trace('w', self._oneShot_set) self.plus12dB_trace = self.plus12dB.trace('w', self._plus12dB_set) self.tuneVal_trace = self.tuneVal.trace('w', self._tuneVal_set) self.entryOscNum.config(textvariable=self.oscNum) def _name_set(self, *args): # electribe sampler uses a subset of the ascii encoding esli = self.e2s_sample.get_esli() esli.OSC_name = bytes(self.name.get(),'ascii', 'ignore') self.name.set(esli.OSC_name.decode('ascii').rstrip('\x00')) def _oscNum_set(self, *args): oscNum = self.oscNum.get() if 422 <= oscNum <= 500: if self.entryOscNum._prev < oscNum: self.oscNum.set(501) else: self.oscNum.set(421) oscNum = self.oscNum.get() self.e2s_sample.get_esli().OSC_0index = oscNum-1 self.e2s_sample.get_esli().OSC_0index1 = oscNum-1 self.entryOscNum._prev = oscNum def _oscNum_command(self): oscNum = self.oscNum.get() lN = self.sample_num e2s_samples = self.master.e2s_samples maxval = 1000-len(e2s_samples)+lN if maxval <= 500: maxval -= 79 if oscNum > maxval: self.oscNum.set(maxval) oscNum = self.oscNum.get() if lN and e2s_samples[lN-1].get_esli().get_OSCNum() >= oscNum: # was decreased # check that we will not go under 19 is not necessary while # is setself.entryOscNum.config(from_=sample_num+19, to=999) while lN and e2s_samples[lN-1].get_esli().get_OSCNum() >= e2s_samples[lN].get_esli().get_OSCNum(): nextOSCNum = e2s_samples[lN].get_esli().get_OSCNum() e2s_samples[lN-1].get_esli().set_OSCNum(nextOSCNum-1 if nextOSCNum != 501 else 421) lN -= 1 self.master.update_sample(lN) elif lN < len(e2s_samples)-1 and e2s_samples[lN+1].get_esli().get_OSCNum() <= oscNum: # was increased, look if possible #if len(samples)-1 - lN <= 999 - oscNum: while lN < len(e2s_samples)-1 and e2s_samples[lN+1].get_esli().get_OSCNum() <= e2s_samples[lN].get_esli().get_OSCNum(): prevOSCNum = e2s_samples[lN].get_esli().get_OSCNum() e2s_samples[lN+1].get_esli().set_OSCNum(prevOSCNum+1 if prevOSCNum != 421 else 501) lN += 1 self.master.update_sample(lN) #else: # self.oscNum.set(oscNum-1) def _oscCat_set(self, *args): self.e2s_sample.get_esli().OSC_category = e2s.esli_str_to_OSC_cat[self.entryOscCat.get()] def _oneShot_set(self, *args): oneShot = self.oneShot.get() if oneShot and self.e2s_sample.get_esli().OSC_LoopStartPoint_offset != self.e2s_sample.get_esli().OSC_EndPoint_offset: if tk.messagebox.askyesno("Loop Start is set", "Loop start shall be reset to allow one shot.\nContinue?"): self.e2s_sample.get_esli().OSC_LoopStartPoint_offset = self.e2s_sample.get_esli().OSC_EndPoint_offset self.e2s_sample.get_esli().OSC_OneShot = oneShot else: self.oneShot.set(False) # update checkbutton self.checkOneShot.config(state=tk.NORMAL) else: self.e2s_sample.get_esli().OSC_OneShot = oneShot def _plus12dB_set(self, *args): self.e2s_sample.get_esli().playLevel12dB = self.plus12dB.get() def _tuneVal_set(self, *args): self.e2s_sample.get_esli().sampleTune = self.tuneVal.get() def _samplingFreq_command(self, *ars): sFreq = self.samplingFreq.get() esli = self.e2s_sample.get_esli() fmt = self.e2s_sample.get_fmt() data = self.e2s_sample.get_data() esli.samplingFreq = sFreq # by default play speed is same as indicated by Frequency esli.playLogPeriod = 65535 if sFreq == 0 else max(0, int(round(63132-math.log2(sFreq)*3072))) fmt.samplesPerSec = sFreq fmt.avgBytesPerSec = sFreq*fmt.blockAlign self.durationEntry.config(text="{:.4f}".format(len(data)/fmt.avgBytesPerSec if fmt.avgBytesPerSec else 0)) def _stereo_command(self,*args): # don't switch immediately self.stereo.set(not self.stereo.get()) if not self.stereo.get(): # mono can't be set to stereo return dialog = StereoToMonoDialog(self.checkStereo, self.e2s_sample) self.master.wait_window(dialog) fmt = self.e2s_sample.get_fmt() self.stereo.set(fmt.channels > 1) data = self.e2s_sample.get_data() self.smpSize.set(len(data)) self.master.update_WAVDataSize() def _on_replace(self): filename = tk.filedialog.askopenfilename(parent=self.master.parent, title="Select replacement WAV file",filetypes=(('Wav Files','*.wav'), ('All Files','*.*'))) def fct(): num_converted = dict() res = self.master.parent._import_sample_helper(filename) if res: sample, converted_from, converted_to_mono = res esli = sample.get_esli() esli.OSC_0index = esli.OSC_0index1 = self.e2s_sample.get_esli().OSC_0index self.master.e2s_samples[self.sample_num] = sample self.master.update_sample(self.sample_num) self.master.update_WAVDataSize() if converted_from or converted_to_mono: conversion = ( ["from {} bits to 16 bits".format(converted_from)] if converted_from else [] + ["to mono"] if converted_to_mono else [] ) tk.messagebox.showinfo( "Import WAV", "'{}' file converted {}.\n".format(filename, " and ".join(conversion)) ) if filename: wd = WaitDialog(self.master.parent) wd.run(fct) def _on_export(self): self.master.export(self.e2s_sample) def _on_edit(self): self.master.edit(self.sample_num) def _on_delete(self): self.master.remove(self.sample_num) def play(self): # TODO: have a single wav player for the whole application self.master.play(self.e2s_sample) class SampleList(tk.Frame): def __init__(self, parent, *arg, **kwarg): super().__init__(*arg, **kwarg) self.vscrollbar = tk.ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.on_scroll) self.vscrollbar.pack(fill=tk.Y, side=tk.RIGHT) self.canvas = tk.Canvas(self, bd=0, highlightthickness=0, confine=0) self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.frame = tk.Frame(self.canvas) self.frame_id = self.canvas.create_window(0, 0, window=self.frame, anchor=tk.NW) self.parent = parent self.stopButton = tk.Button(self.frame, image=GUI.res.stop_smallIcon, command=self.play_stop) ToolTip(self.stopButton, follow_mouse=1, text="stop playback") tk.Label(self.frame, text="#Num").grid(row=0, column=3) tk.Label(self.frame, text="Name").grid(row=0, column=4) tk.Label(self.frame, text="Cat.").grid(row=0, column=5) tk.Label(self.frame, text="1-shot").grid(row=0, column=6) tk.Label(self.frame, text="+12dB").grid(row=0, column=7) tk.Label(self.frame, text="Tune").grid(row=0, column=8) self.stopButton.grid(row=0, column=9, padx=5) tk.Label(self.frame, text="Freq (Hz)").grid(row=0, column=10) tk.Label(self.frame, text="Time (s)").grid(row=0, column=11) tk.Label(self.frame, text="Stereo").grid(row=0,column=12) tk.Label(self.frame, text="Data Size").grid(row=0, column=13) tk.Frame(self.frame, width=2, bd=1, relief=tk.SUNKEN).grid(row=0, column=1, rowspan=999, sticky=tk.N+tk.S) self.fill = tk.Label(self.frame) self.fill.grid(row=998, column=2, columnspan=12, sticky=tk.NSEW) tk.Grid.rowconfigure(self.frame, 998, weight=1) self.fill.config(height=self.canvas.winfo_reqheight()) self.sliceEditDialog = None self.selectV = tk.IntVar() self.WAVDataSize = tk.IntVar() self.samples = [] self.samples_garbage = [] self.e2s_samples = [] self.update_scrollbar() # track changes to the canvas and frame width and sync them, def _configure_frame(event): # update the canvas's width to fit the inner frame if self.canvas.winfo_reqwidth() != self.frame.winfo_reqwidth(): self.canvas.config(width=self.frame.winfo_reqwidth()) self.frame.bind('<Configure>', _configure_frame) self.canvas.bind('<Configure>', self._on_configure) def _on_configure(self, event): if self.canvas.winfo_height() != self.fill.winfo_height(): self.fill.config(height=self.canvas.winfo_height()) if not self.samples: return # update the inner frame's height to fill the canvas new_h = event.height _, _, _, h = self.frame.grid_bbox(0,0,0,0) h_max = event.height - h _, _, _, h_line = self.frame.grid_bbox(0,1,0,1) while h_line*(len(self.samples)+1) <= h_max and len(self.samples)+1 <= len(self.e2s_samples): sample_num=self.samples[-1].sample_num+1 if sample_num >= len(self.e2s_samples): self.scroll(-1) sample_num = len(self.e2s_samples)-1 self.push_sample(sample_num) while len(self.samples)*h_line > h_max: self.pop_sample() self.update_scrollbar() # this is to handle an issue with tkinter: # if you destroy a Sample the canvas is resized to its req_height def pop_sample(self): self.samples_garbage.append(self.samples.pop()) self.samples_garbage[-1].forget() def push_sample(self, sample_num): if not self.samples_garbage: smpl = Sample(self,len(self.samples),sample_num) else: smpl = self.samples_garbage.pop() smpl.restore(len(self.samples),sample_num) self.samples.append(smpl) def find_max_sample_0index(self): # e2s_samples are currently ordered by OSC_0index if self.e2s_samples: return self.e2s_samples[-1].get_esli().OSC_0index else: return 17 def get_next_free_sample_index(self, _from=None): if _from is None: max = self.find_max_sample_0index() if 420 == max: max = 499 if max<998: return max+1 else: _from = 18 else: # max is 998, find first free # e2s_samples are currently ordered by OSC_0index next=_from for e2s_sample in self.e2s_samples: curr = e2s_sample.get_esli().OSC_0index if curr > next: break if curr >= _from: next = curr+1 if curr != 420 else 500 return next if next < 999 else ( None if _from == 18 else self.get_next_free_sample_index(18)) def get_next_free_index(self, direction=1, first=None, roll=False): if not direction: return self.get_next_free_sample_index() if first is None: if direction > 0: curr = 0 if direction < 0: curr = 998 else: curr = first # find sample number in the sample list curr_smp_num = 0 if direction > 0 else len(self.e2s_samples)-1 while (direction > 0 and curr_smp_num < len(self.e2s_samples) and self.e2s_samples[curr_smp_num].get_esli().OSC_0index < curr or direction < 0 and curr_smp_num >= 0 and self.e2s_samples[curr_smp_num].get_esli().OSC_0index > curr ): curr_smp_num += 1 if direction > 0 else -1 # find next free sample number while (direction > 0 and curr_smp_num < len(self.e2s_samples) and self.e2s_samples[curr_smp_num].get_esli().OSC_0index == curr or direction < 0 and curr_smp_num >= 0 and self.e2s_samples[curr_smp_num].get_esli().OSC_0index == curr ): curr_smp_num += 1 if direction > 0 else -1 curr += 1 if direction > 0 else -1 if 420 < curr < 500: curr = 500 if direction > 0 else 420 if curr < 18 or curr > 998: if not roll: return None else: return self.get_next_free_index(direction, 18 if direction > 0 else 998) else: return (curr_smp_num, curr) def get_selected(self): if 0 <= self.selectV.get() < len(self.e2s_samples): self.show_selected() return self.selectV.get() else: return None def update_WAVDataSize(self): self.WAVDataSize.set(sum(len(s.get_data()) for s in self.e2s_samples)) def update_scrollbar(self): if self.e2s_samples: self.vscrollbar.set(self.samples[0].sample_num/len(self.e2s_samples), (self.samples[-1].sample_num+1)/len(self.e2s_samples)) else: self.vscrollbar.set(0, 1) def on_scroll(self, command, *args): if command == tk.MOVETO: offset = float(args[0]) scroll_tot = len(self.e2s_samples) self.scroll_to(scroll_tot * offset) elif command == tk.SCROLL: step = float(args[0]) what = args[1] if what == "units": self.scroll(step) elif what == "pages": self.scroll(step*len(self.samples)) else: raise Exception("Unknown scroll unit: " + what) def scroll(self, offset): if len(self.samples): self.scroll_to(self.samples[0].sample_num+offset) def scroll_to(self, offset): sample_num = max(0,min(int(offset),len(self.e2s_samples)-len(self.samples))) for sample in self.samples: sample.set_sample_num(sample_num) sample_num += 1 self.update_scrollbar() def add_new(self, e2s_sample): self.e2s_samples.append(e2s_sample) self.WAVDataSize.set(self.WAVDataSize.get()+len(self.e2s_samples[-1].get_data())) smp_num=len(self.e2s_samples)-1 osc_num=e2s_sample.get_esli().get_OSCNum() #sort while smp_num > 0: pr_osc_num = self.e2s_samples[smp_num-1].get_esli().get_OSCNum() if osc_num > pr_osc_num: break # swap samples self.e2s_samples[smp_num], self.e2s_samples[smp_num-1] = self.e2s_samples[smp_num-1], self.e2s_samples[smp_num] smp_num -= 1 insert_num=smp_num # add new sample line if necessary n_lines = len(self.samples) _, _, _, h = self.frame.grid_bbox(0,0,0,0) h_max = self.canvas.winfo_height()-h _, _, _, h_line = self.frame.grid_bbox(0,1,0,1) if not n_lines or h_line*(n_lines+1) <= h_max and len(self.samples) < len(self.e2s_samples): self.push_sample(len(self.e2s_samples)-1) # update selected sample if len(self.e2s_samples) > 1 and self.selectV.get() >= smp_num: self.selectV.set(smp_num+1) # update sample objects while smp_num < len(self.e2s_samples): self.update_sample(smp_num) smp_num += 1 self.update_scrollbar() def remove(self, sample_num): if 0 <= sample_num < len(self.e2s_samples): e2s_sample = self.e2s_samples.pop(sample_num) self.WAVDataSize.set(self.WAVDataSize.get()-len(e2s_sample.get_data())) first = self.samples[0].sample_num last = self.samples[-1].sample_num if last >= sample_num >= first: # move samples if first > 0: # down for s in self.samples: s.set_sample_num(s.sample_num-1) else: #up for i in range(sample_num-first,last-first): self.samples[i].set_sample_num(first+i) if self.samples[-1].sample_num < len(self.e2s_samples): self.samples[-1].set_sample_num(last) else: smpl = self.samples.pop() smpl.destroy() #TODO: actualize scroll-bar if self.selectV.get() >= len(self.e2s_samples): self.selectV.set(self.selectV.get()-1) self.update_scrollbar() def clear(self): for sample in reversed(self.samples): sample.destroy() self.samples.clear() self.e2s_samples.clear() self.WAVDataSize.set(0) self.selectV.set(0) self.update_scrollbar() def update_sample(self, sample_num): if self.samples and self.samples[0].sample_num <= sample_num <= self.samples[-1].sample_num: self.samples[sample_num-self.samples[0].sample_num].set_sample_num(sample_num) def exchange(self, a, b, keep_index=False): if not keep_index: # swap osc indexes a_esli = self.e2s_samples[a].get_esli() b_esli = self.e2s_samples[b].get_esli() a_index = a_esli.OSC_0index a_esli.OSC_0index=a_esli.OSC_0index1=b_esli.OSC_0index b_esli.OSC_0index=b_esli.OSC_0index1=a_index # swap samples self.e2s_samples[a], self.e2s_samples[b] = self.e2s_samples[b], self.e2s_samples[a] # update sample objects self.update_sample(a) self.update_sample(b) def move_up(self, line_num, keep_index=False): if 0 < line_num < len(self.e2s_samples): self.exchange(line_num, line_num-1, keep_index) return True return False def move_down(self, line_num, keep_index=False): if 0 <= line_num < len(self.e2s_samples)-1: self.exchange(line_num, line_num+1, keep_index) return True return False def set_selected(self, num): self.selectV.set(num) self.show_selected() def show_selected(self): selected = self.selectV.get() if 0 <= selected and self.samples[0].sample_num > selected: self.scroll_to(selected) elif len(self.e2s_samples) > selected and self.samples[-1].sample_num < selected: self.scroll_to(1+selected-len(self.samples)) def move_up_selected(self): if self.move_up(self.selectV.get()): self.selectV.set(self.selectV.get()-1) self.show_selected() def move_down_selected(self): if self.move_down(self.selectV.get()): self.selectV.set(self.selectV.get()+1) self.show_selected() def move_up_selected_to_next_free(self): selected = self.selectV.get() if 0 <= selected < len(self.e2s_samples): res = self.get_next_free_index(-1, self.e2s_samples[selected].get_esli().OSC_0index) if res: list_idx, osc_idx = res esli = self.e2s_samples[selected].get_esli() esli.OSC_0index = esli.OSC_0index1 = osc_idx self.update_sample(selected) while selected > list_idx+1: self.move_up(selected, keep_index=True) selected -= 1 self.selectV.set(selected) self.show_selected() def move_down_selected_to_next_free(self): selected = self.selectV.get() if 0 <= selected < len(self.e2s_samples): res = self.get_next_free_index(1, self.e2s_samples[selected].get_esli().OSC_0index) if res: list_idx, osc_idx = res esli = self.e2s_samples[selected].get_esli() esli.OSC_0index = esli.OSC_0index1 = osc_idx self.update_sample(selected) while selected < list_idx-1: self.move_down(selected, keep_index=True) selected += 1 self.selectV.set(selected) self.show_selected() def play(self, e2s_sample): riff_fmt = e2s_sample.get_fmt() audio.player.play_start(audio.Sound(e2s_sample.get_data().rawdata,riff_fmt)) def play_stop(self): audio.player.play_stop() def edit(self, smpl_num): if not self.sliceEditDialog: self.sliceEditDialog = SliceEditorDialog(self.parent) self.sliceEditDialog.sliceEditor.set_sample(self, smpl_num) self.sliceEditDialog.run() def export(self, e2s_sample): oscNum=e2s_sample.get_esli().OSC_0index+1 oscName=e2s_sample.get_esli().OSC_name.decode('ascii', 'ignore').split('\x00')[0] filename = tk.filedialog.asksaveasfilename(parent=self.parent,title="Export sample as",defaultextension='.wav',filetypes=(('Wav Files','*.wav'), ('All Files','*.*')) ,initialfile="{:0>3}_{}.wav".format(oscNum,oscName)) if filename: try: with open(filename, 'wb') as f: e2s_sample.write(f, export_smpl=self.parent.export_opts.export_smpl, export_cue=self.parent.export_opts.export_cue) except Exception as e: tk.messagebox.showwarning( "Export sample as", "Cannot save sample as:\n{}\nError message:\n{}".format(filename, e) ) class ToManySamples(Exception): """Exception raised when registering a new sample while e2sSample is full.""" pass class SampleAllEditor(tk.Tk): """ TODO: - check box: import sample and keep original number - sort imported samples - check box: remove unhandled chunks - button: edit sample """ def __init__(self, *args, **kw): super().__init__(*args, **kw) GUI.res.init() self.import_opts = ImportOptions() self.export_opts = ExportOptions() # Set the window title self.wm_title("Open e2sSample.all Library Editor") self.minsize(width=600,height=500) #self.frame = VerticalScrolledFrame(self) #self.frame.pack(fill=tk.BOTH, expand=tk.YES) #self.sampleList = SampleList(self.frame.interior) fr = tk.Frame(self,borderwidth=2, relief='sunken') self.sampleList = SampleList(self, fr, borderwidth=2) fr2 = tk.Frame(fr, borderwidth=2) tk.Frame(fr2).pack(fill=tk.Y, expand=True) self.exchangeButton = tk.Button(fr2, image=GUI.res.exchangeIcon, command=self.exchange) ToolTip(self.exchangeButton, follow_mouse=1, text="exchange with another") self.exchangeButton.pack(padx=2, pady=2) tk.Frame(fr2, height=2, bd=1, relief=tk.SUNKEN).pack(fill=tk.X,padx=2, pady=5) self.moveUp100Button = tk.Button(fr2, image=GUI.res.swap_prev100Icon, command=lambda: [self.sampleList.move_up_selected() for i in range(100) ]) ToolTip(self.moveUp100Button, follow_mouse=1, text="swap up by 100") self.moveUp100Button.pack(padx=2, pady=2) self.moveUp10Button = tk.Button(fr2, image=GUI.res.swap_prev10Icon, command=lambda: [self.sampleList.move_up_selected() for i in range(10) ]) ToolTip(self.moveUp10Button, follow_mouse=1, text="swap up by 10") self.moveUp10Button.pack(padx=2, pady=2) self.moveUpButton = tk.Button(fr2, image=GUI.res.swap_prevIcon, command=self.sampleList.move_up_selected) ToolTip(self.moveUpButton, follow_mouse=1, text="swap up") self.moveUpButton.pack(padx=2, pady=2) self.moveDownButton = tk.Button(fr2, image=GUI.res.swap_nextIcon, command=self.sampleList.move_down_selected) ToolTip(self.moveDownButton, follow_mouse=1, text="swap down") self.moveDownButton.pack(padx=2, pady=2) self.moveDown10Button = tk.Button(fr2, image=GUI.res.swap_next10Icon, command=lambda: [self.sampleList.move_down_selected() for i in range(10) ]) ToolTip(self.moveDown10Button, follow_mouse=1, text="swap down by 10") self.moveDown10Button.pack(padx=2, pady=2) self.moveDown100Button = tk.Button(fr2, image=GUI.res.swap_next100Icon, command=lambda: [self.sampleList.move_down_selected() for i in range(100) ]) ToolTip(self.moveDown100Button, follow_mouse=1, text="swap down by 100") self.moveDown100Button.pack(padx=2, pady=2) tk.Frame(fr2, height=2, bd=1, relief=tk.SUNKEN).pack(fill=tk.X,padx=2, pady=5) self.moveUpFreeButton = tk.Button(fr2, image=GUI.res.prev_freeIcon, command=self.sampleList.move_up_selected_to_next_free) ToolTip(self.moveUpFreeButton, follow_mouse=1, text="move up to next free") self.moveUpFreeButton.pack(padx=2, pady=2) self.moveDownFreeButton = tk.Button(fr2, image=GUI.res.next_freeIcon, command=self.sampleList.move_down_selected_to_next_free) ToolTip(self.moveDownFreeButton, follow_mouse=1, text="move down to next free") self.moveDownFreeButton.pack(padx=2, pady=2) tk.Frame(fr2).pack(fill=tk.Y, expand=True) fr2.pack(side=tk.LEFT, fill=tk.Y) self.sampleList.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.YES) fr.pack(fill=tk.BOTH, expand=tk.YES) fr = tk.Frame(self,borderwidth=2, relief='sunken') tk.Label(fr,text='/ '+str(e2s.WAVDataMaxSize)).pack(side=tk.RIGHT) self.sizeEntry = MaxValueEntry(fr, e2s.WAVDataMaxSize, width=8, textvariable=self.sampleList.WAVDataSize, state=tk.DISABLED, justify=tk.RIGHT) self.sizeEntry.pack(side=tk.RIGHT) tk.Label(fr,text='Total Data Size : ').pack(side=tk.RIGHT) self.buttonDonateEur = tk.Button(fr, command=self.donate_eur, image=GUI.res.donateEurIcon) ToolTip(self.buttonDonateEur, follow_mouse=1, text="what about\noffering me a beer with € ?") self.buttonDonateEur.pack(side=tk.LEFT) self.buttonDonateUsd = tk.Button(fr, command=self.donate_usd, image=GUI.res.donateUsdIcon) ToolTip(self.buttonDonateUsd, follow_mouse=1, text="what about\noffering me a beer with $ ?") self.buttonDonateUsd.pack(side=tk.LEFT) self.buttonAbout=tk.Button(fr, text="About", width=10, command=self.about) tk.Frame(fr).pack(side=tk.TOP,fill=tk.Y,expand=1) self.buttonAbout.pack(side=tk.TOP) tk.Frame(fr).pack(side=tk.TOP,fill=tk.Y,expand=1) fr.pack(side=tk.BOTTOM,fill=tk.X) fr = tk.Frame(self) fr2 = tk.Frame(fr, borderwidth=2) self.buttonImport = tk.Button(fr2, text="Import wav Sample(s)", command=self.import_sample) self.buttonImport.pack(side=tk.TOP, fill=tk.BOTH) self.buttonImportE2s = tk.Button(fr2, text="Import e2sSample.all", command=self.import_all_sample) self.buttonImportE2s.pack(side=tk.TOP, fill=tk.BOTH) fr2.pack(side=tk.LEFT, expand=True, fill=tk.BOTH) fr2 = tk.Frame(fr, borderwidth=2) self.buttonImportOptions = tk.Button(fr2, text="Import Options", width=15, command=self.import_options) self.buttonImportOptions.pack(fill=None,padx=5) fr2.pack(side=tk.LEFT, fill=None) fr.pack(side=tk.TOP, fill=tk.BOTH) fr = tk.Frame(self) fr2 = tk.Frame(fr, borderwidth=2) self.buttonExpAll = tk.Button(fr2, text="Export all as wav", command=self.export_all_sample) self.buttonExpAll.pack(side=tk.TOP, fill=tk.BOTH) fr2.pack(side=tk.LEFT, expand=True, fill=tk.BOTH) fr2 = tk.Frame(fr, borderwidth=2) self.buttonExportOptions = tk.Button(fr2, text="Export Options", width=15, command=self.export_options) self.buttonExportOptions.pack(fill=None, padx=5) fr2.pack(side=tk.LEFT, fill=None) fr.pack(side=tk.TOP, fill=tk.BOTH) self.buttonLoad = tk.Button(self, text="Open", width=10, command=self.load) self.buttonLoad.pack(side=tk.LEFT,fill=tk.Y,padx=5,pady=5) self.buttonClear = tk.Button(self, text="Clear all", width=10, command=self.clear) self.buttonClear.pack(side=tk.RIGHT,padx=5,pady=5) self.buttonSaveAs = tk.Button(self, text="Save As", width=10, command=self.save_as) self.buttonSaveAs.pack(side=tk.TOP,fill=tk.Y,padx=5,pady=5) self.restore_binding() self.update_idletasks() width, height = (self.winfo_width(), self.winfo_height()) self.minsize(width, height) def exchange(self): sl = self.sampleList selected = sl.get_selected() if selected is not None: s_num = sl.e2s_samples[selected].get_esli().OSC_0index+1; s_name = sl.e2s_samples[selected].get_esli().OSC_name.decode('ascii', 'ignore').split('\x00')[0]; exchg_with = [ (sl.e2s_samples[i].get_esli().OSC_0index+1, sl.e2s_samples[i].get_esli().OSC_name.decode('ascii', 'ignore').split('\x00')[0]) for i in range(len(sl.e2s_samples)) ] dialog = ExchangeSampleDialog(self, (s_num, s_name), exchg_with) self.wait_window(dialog) if dialog.result is not None and dialog.result >= 0 and dialog.result != selected: sl.exchange(selected, dialog.result) sl.set_selected(dialog.result) def donate_eur(self): webbrowser.open('https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=L6BSNDEHYQ2HE') def donate_usd(self): webbrowser.open('https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=PG6YDQS4EZAE2') def about(self): about = AboutDialog(self) def clear(self): wd = WaitDialog(self) wd.run(self.sampleList.clear) def import_options(self): dialog = ImportOptionsDialog(self, self.import_opts) self.wait_window(dialog) def export_options(self): dialog = ExportOptionsDialog(self, self.export_opts) self.wait_window(dialog) def load(self): filename = tk.filedialog.askopenfilename(parent=self,title="Select e2s Sample.all file to open",filetypes=(('.all Files','*.all'),('All Files','*.*'))) if filename: def fct(): try: samplesAll = e2s.e2s_sample_all(filename=filename) except Exception as e: tk.messagebox.showwarning( "Open", "Cannot use this file:\n{}\nError message:\n{}".format(filename, e) ) return if samplesAll._loadErrors: tk.messagebox.showwarning( "Open", ("Recovered from {} error(s) in this file:\n{}\n" "The file is probably corrupted or you found a bug.\n" "See log file for details." ) .format(samplesAll._loadErrors, filename) ) self.sampleList.clear() for sample in samplesAll.samples: self.sampleList.add_new(sample) if len(self.sampleList.samples) == 1: self.update_idletasks() width, height = (self.winfo_reqwidth(), self.winfo_reqheight()) self.minsize(width, height) wd = WaitDialog(self) wd.run(fct) def save_as(self): if not self.sampleList.WAVDataSize.get() > e2s.WAVDataMaxSize or tk.messagebox.askyesno("Memory overflow", "Are you sure to save with memory overflow?"): filename = tk.filedialog.asksaveasfilename(parent=self,title="Save as e2s Sample.all file",defaultextension='.all',filetypes=(('.all Files','*.all'),('All Files','*.*')),initialfile='e2sSample.all') if filename: def fct(): sampleAll = e2s.e2s_sample_all() for e2s_sample in self.sampleList.e2s_samples: sampleAll.samples.append(e2s_sample) try: sampleAll.save(filename) except Exception as e: tk.messagebox.showwarning( "Save as", "Cannot save to this file:\n{}\nError message:\n{}".format(filename, e) ) wd = WaitDialog(self) wd.run(fct) def _import_sample_helper(self, filename): try: return e2s_sample_import.from_wav(filename, self.import_opts) except e2s_sample_import.NotWaveFormatPcm: tk.messagebox.showwarning( "Import WAV", "Cannot use this file:\n{}\nWAV format must be WAVE_FORMAT_PCM".format(filename) ) except e2s_sample_import.EmptyWav: tk.messagebox.showwarning( "Import WAV", "Cannot use this file:\n{}\nNo data: empty samples are not allowed".format(filename) ) except e2s_sample_import.NotSupportedBitPerSample: tk.messagebox.showwarning( "Import WAV", "Cannot use this file:\n{}\nWAV format must preferably use 16 bits per sample.\n" + "8 bits and old 24 bits per sample are also supported but will be converted to 16 bits.\n" "Convert your file before importing it.".format(filename) ) except BaseException as e: tk.messagebox.showwarning( "Import WAV", "Cannot use this file:\n{}\n" "The file is probably corrupted or you found a bug.\n" "See log file for details.\n" "Error message:\{}.".format(filename, e) ) # also report it in log file print(e) def import_sample(self): filenames = tk.filedialog.askopenfilenames(parent=self,title="Select WAV file(s) to import",filetypes=(('Wav Files','*.wav'), ('All Files','*.*'))) def fct(): num_converted = dict() for filename in filenames: res = self._import_sample_helper(filename) if not res: continue sample, converted_from, converted_to_mono = res if converted_from: num_converted[converted_from] = num_converted.get(converted_from, 0) + 1 if converted_to_mono: num_converted['mono'] = num_converted.get('mono', 0) + 1 try: self.register_new_sample(sample) except ToManySamples: tk.messagebox.showwarning( "Import WAV", "Cannot use this file:\n{}\nToo many samples.".format(filename) ) break if num_converted: tk.messagebox.showinfo( "Import WAV", ("{} file(s) converted to mono.\n".format(num_converted['mono']) if num_converted.get('mono') else "") + ("{} file(s) converted from 8 bits to 16 bits.\n".format(num_converted[8]) if num_converted.get(8) else "") + ("{} file(s) converted from 24 bits to 16 bits.\n".format(num_converted[24]) if num_converted.get(24) else "") ) wd = WaitDialog(self) wd.run(fct) def import_all_sample(self): filename = tk.filedialog.askopenfilename(parent=self,title="Select e2sSample.all file to import",filetypes=(('.all Files','*.all'),('All Files','*.*'))) if filename: def fct(): try: samplesAll = e2s.e2s_sample_all(filename=filename) except Exception as e: tk.messagebox.showwarning( "Import e2sSample.all", "Cannot use this file:\n{}\nError message:\n{}".format(filename, e) ) return if samplesAll._loadErrors: tk.messagebox.showwarning( "Import e2sSample.all", ("Recovered from {} error(s) in this file:\n{}\n" "The file is probably corrupted or you found a bug.\n" "See log file for details." ) .format(samplesAll._loadErrors, filename) ) for sample in samplesAll.samples: e2s_sample_import.apply_forced_options(sample, self.import_opts) try: self.register_new_sample(sample) except ToManySamples: tk.messagebox.showwarning( "Import e2sSample.all", "Too many samples." ) break wd = WaitDialog(self) wd.run(fct) def export_all_sample(self): if self.sampleList.samples: directory = tk.filedialog.askdirectory(parent=self,title="Export all samples to directory",mustexist=True) def fct(): if directory: # check files do not exist for e2s_sample in self.sampleList.e2s_samples: oscNum = e2s_sample.get_esli().OSC_0index+1 oscName = e2s_sample.get_esli().OSC_name.decode('ascii', 'ignore').split('\x00')[0] filename = "{:0>3}_{}.wav".format(oscNum,oscName) filename = filename.replace('/','-').replace('\\','-') filename = directory+"/"+filename # TODO: dialog to ask if replace/replace-all or select new rename if os.path.exists(filename): filename = tk.filedialog.asksaveasfilename(parent=self,title="File exists, export sample as [cancel to abort]",defaultextension='.wav',filetypes=(('Wav Files','*.wav'), ('All Files','*.*')) ,initialdir=directory,initialfile="{:0>3}_{}.wav".format(oscNum,oscName)) if not filename: break ok = False while not ok: try: with open(filename, 'wb') as f: e2s_sample.write(f, export_smpl=self.export_opts.export_smpl, export_cue=self.export_opts.export_cue) except Exception as e: tk.messagebox.showwarning( "Export sample as", "Cannot save sample as:\n{}\nError message:\n{}".format(filename, e) ) filename = tk.filedialog.asksaveasfilename(parent=self,title="Export sample as [cancel to abort]",defaultextension='.wav',filetypes=(('Wav Files','*.wav'), ('All Files','*.*')) ,initialdir=directory,initialfile="{:0>3}_{}.wav".format(oscNum,oscName)) if not filename: break ok = True if not ok: break wd = WaitDialog(self) wd.run(fct) def register_new_sample(self, e2s_sample): esli = e2s_sample.get_esli() nextsampleIndex = self.sampleList.get_next_free_sample_index(self.import_opts.smp_num_from-1) if nextsampleIndex is not None: esli.OSC_0index = esli.OSC_0index1 = nextsampleIndex self.sampleList.add_new(e2s_sample) if len(self.sampleList.samples) == 1: self.update_idletasks() width, height = (self.winfo_reqwidth(), self.winfo_reqheight()) self.minsize(width, height) else: raise ToManySamples system = platform.system() def restore_binding(self): if self.system == 'Windows': def _on_mousewheel(event): self.sampleList.scroll(-1*(event.delta//120)) return "break" self.bind('<MouseWheel>', _on_mousewheel) # TODO: add an option to select behaviour # do not scroll the interface if mouse is on a ttk Combobox #self.bind_class('TCombobox', '<MouseWheel>', lambda e: "break", "+") ## do not change ttk Combobox content on mouse wheel event self.bind_class('TCombobox', '<MouseWheel>', lambda e: None) elif self.system == 'Darwin': def _on_mousewheel(event): self.sampleList.scroll(-1*(event.delta)) self.bind('<MouseWheel>', _on_mousewheel) #self.bind_class('TCombobox', '<MouseWheel>', lambda e: "break", "+") self.bind_class('TCombobox', '<MouseWheel>', lambda e: None) else: def _on_up(event): self.sampleList.scroll(-1) def _on_down(event): self.sampleList.scroll(1) self.bind('<Button-4>', _on_up) self.bind('<Button-5>', _on_down) #self.bind_class('TCombobox', '<Button-4>', lambda e: "break", "+") #self.bind_class('TCombobox', '<Button-5>', lambda e: "break", "+") self.bind_class('TCombobox', '<Button-4>', lambda e: None) self.bind_class('TCombobox', '<Button-5>', lambda e: None) if __name__ == '__main__': # redirect outputs to a logger with logger() as log: # Create a window app = SampleAllEditor() app.mainloop() audio.terminate()