import os
from Tkinter import *
from tkMessageBox import showwarning, showinfo
from tkFileDialog import askopenfilename, asksaveasfilename
from TkExtensions.RoundSquareFrame import RoundSquareFrame
from TkExtensions.Slider import HorizSlider, VertSlider
from FileDesc import FileDesc
from SliderSet import SliderSet
import ButtonImages


## CLASSE SOUND:
## =============
# Represente un son 3MU

class Sound:

  ## ATTRIBUTS DE CLASSE:
  ## --------------------
  stdFont = '-schumacher-clean-medium-r-normal--16-160-75-75-c-80-iso8859-1'
  boldFont = '-schumacher-clean-bold-r-normal--16-160-75-75-c-80-iso8859-1'
  ## Couleurs
  bg = "#050832"
  fg = "#B2FFE8"
  roundFrameClr = "#00B2AF" # "#C6D800"
  btnClr = '#FF006F'
  zoneNamesClr = 'blue'
  whiteKeysClr = '#80FFFF'
  blackKeysClr = '#4800A6'
  ## Dimensions pour le clavier
  keybWidth = 80
  notesVDist = 15.0
  notesHDist = 40.0
  numOctaves = 9
  noteRadius = 5.0
  ## Dictionnaire des caracteristiques avec les classes associees
  ## 2eme item: 0 si valeur simple, 1 si liste; 3eme item = valeur par defaut, 4eme item = s(ound) ou n(ote)
  car2varDict = { 'attack'             : (DoubleVar,  0, 4.83,                's'), \
                  'cutoff'             : (IntVar,     0, 160,                 's'), \
                  'cutoff-envmod'      : (IntVar,     0, 0,                   's'), \
                  'decay'              : (DoubleVar,  0, 120,                 's'), \
                  'detune-envmod'      : (IntVar,     0, 0,                   's'), \
                  'filter-amount'      : (IntVar,     0, 100,                 's'), \
                  'filter-poles'       : (IntVar,     0, 2,                   's'), \
                  'filter-type'        : (StringVar,  0, 'generic',           's'), \
                  'gain'               : (IntVar,     0, 0,                   's'), \
                  'id-string'          : (StringVar,  0, '',                  's'), \
                  'note'               : (StringVar,  1, [ 'c5', 'g5' ] * 12, 'n'), \
                  'note-accent'        : (BooleanVar, 1, [ 0 ],               'n'), \
                  'note-adsr-factor'   : (IntVar,     0, 100,                 'n'), \
                  'note-attack'        : (DoubleVar,  1, [ 0 ],               'n'), \
                  'note-cutoff'        : (IntVar,     1, [ 0 ],               'n'), \
                  'note-cutoff-envmod' : (IntVar,     1, [ 0 ],               'n'), \
                  'note-cutoff-envmod-factor' : (IntVar, 0, 100,              'n'), \
                  'note-cutoff-factor' : (IntVar,     0, 100,                 'n'), \
                  'note-decay'         : (DoubleVar,  1, [ 0 ],               'n'), \
                  'note-detune'        : (IntVar,     1, [ 0 ],               'n'), \
                  'note-detune-envmod' : (IntVar,     1, [ 0 ],               'n'), \
                  'note-detune-envmod-factor' : (IntVar, 0, 100,              'n'), \
                  'note-length'        : (StringVar,  1, [ 'normal' ],        'n'), \
                  'note-mode'          : (StringVar,  1, [ 'n' ],             'n'), \
                  'note-portamento'    : (BooleanVar, 1, [ 0 ],               'n'), \
                  'note-release'       : (DoubleVar,  1, [ 0 ],               'n'), \
                  'note-resonance'     : (IntVar,     1, [ 0 ],               'n'), \
                  'note-resonance-factor' : (IntVar,  0, 100,                 'n'), \
                  'note-sustain'       : (DoubleVar,  1, [ 0 ],               'n'), \
                  'note-volume'        : (IntVar,     1, [ 100 ],             'n'), \
                  'note-volume-envmod' : (IntVar,     1, [ 0 ],               'n'), \
                  'note-volume-envmod-factor' : (IntVar, 0, 100,              'n'), \
                  'note-volume-factor' : (IntVar,     0, 100,                 'n'), \
                  'oscillator-hpf'     : (StringVar,  0, 'off',               's'), \
                  'portamento-time'    : (IntVar,     0, 25,                  's'), \
                  'release'            : (DoubleVar,  0, 6.96,                's'), \
                  'repetitions'        : (IntVar,     0, 1,                   'n'), \
                  'resonance'          : (IntVar,     0, 5,                   's'), \
                  'sustain'            : (DoubleVar,  0, 0,                   's'), \
                  'tempo'              : (IntVar,     0, 120,                 'n'), \
                  'transpose'          : (IntVar,     0, 0,                   'n'), \
                  'volume-envmod'      : (IntVar,     0, 100,                 's'), \
                  'waveform'           : (StringVar,  0, 'square',            's'), \
                  'waveform-pulseratio': (IntVar,     0, 50,                  's') }
  
  
  ## METHODE __INIT__:
  ## -----------------
  # Cree une instance de la classe
  
  def __init__(self, fileName=None):
    ## Initialisation des variables additionnelles
    self.noteNameVar = None
    self.noteOctVar = None
    self.seqLenVar = IntVar()
    self.cutNotFacWid = BooleanVar()
    self.cutNotFacSld = None
    ## Construction du dictionnaire des variables
    self.varDict = {}
    self.setDefaults()
    ## Si lecture d'un fichier demande
    if fileName <> None:
      ## On le lit
      self.read(fileName)
    
    
  ## METHODE SET.DEFAULTS:
  ## ---------------------
  # Remet le son a toutes les valeurs par defaut
  
  def setDefaults(self):
    ## Destruction des caracteristiques inconnues
    for k in self.varDict.keys():
      if not Sound.car2varDict.has_key(k): del self.varDict[k]
    ## Calcul de la longueur de la sequence
    self.seqLenVar.set(len(Sound.car2varDict['note'][2]))
    ## Remise des caracteristiques a leur valeur par defaut
    for k in Sound.car2varDict.keys():
      ## Est-ce une valeur multiple ?
      if Sound.car2varDict[k][1]:
        ## Si oui, longueur = celle de sequence
        nbItems = self.seqLenVar.get()
        ## Initialisations
        self.varDict[k] = []
        for i in range(nbItems):
          val = Sound.car2varDict[k][2]
          var = Sound.car2varDict[k][0]()
          var.set(val[i % len(val)])
          self.varDict[k].append(var)
      ## Si valeur simple
      else:
        ## On cree la variable si besoin
        if not self.varDict.has_key(k): self.varDict[k] = Sound.car2varDict[k][0]()
        ## On fixe la valeur
        self.varDict[k].set(Sound.car2varDict[k][2])
    
  
  ## METHODE DUMP:
  ## -------------
  # Sort le son sous forme de texte
  
  def dump(self, fileName=None):
    import sys
    
    if fileName == None:
      f = sys.stdout
    else:
      f = open(fileName, 'w')
    ## En-tete
    f.write("#!3MU\n\n")
    f.write("engine-name=TB-303\n")
    f.write("engine-version=1.0js\n")
    f.write("environment-name=3MU\n")
    f.write("environment-version=1.1\n")
    f.write("output-fileformat=16-bit\n")
    f.write("output-sample-frequency=44100\n")
    ## Parcours du dictionnaire des caracteristiques
    for k in self.varDict.keys():
      ## Est-ce une caracteristique connue ?
      if self.car2varDict.has_key(k):
        ## Est-ce une liste ?
        if self.car2varDict[k][1]:
          ## Cas particulier pour le note-sustain: ne rien sortir si tous sont nuls
          doOutput = 1
          if k == 'note-sustain':
            doOutput = 0
            for v in self.varDict[k]:
              if v.get() != 0.0: doOutput = 1
          ## Si OK, parcours et affichage
          if doOutput:
            f.write('%s={ ' % k)
            i = 0
            for v in self.varDict[k]:
              f.write("%s " % v.get())
              ## On met un retour toutes les 8 notes, sinon 3MU a du mal...
              i = i + 1
              if i == 8:
                f.write('\n')
                i = 0
            f.write('}\n')
        ## Si champ simple
        else:
          ## S'il y a une valeur
          if self.varDict[k].get() <> '':
            ## Cas particulier pour le sustain: ne pas le sortir s'il est nul
            if k != 'sustain' or self.varDict[k].get() != 0:
              ## Sinon, affichage simple
              f.write("%s=%s\n" % (k, self.varDict[k].get()))
      ## Caracteristique enregistree telle qu'elle
      else:
        ## Affichage simple
        f.write("%s=%s\n" % (k, self.varDict[k]))
    ## Fermeture du fichier si besoin
    if fileName <> None: f.close()
  
  
  ## METHODE READ:
  ## -------------
  # Lit le son a partir d'un fichier
  
  def read(self, fileName, what2Load='*'):
    ## Lecture du fichier
    self.file = FileDesc(fileName)
    ## Liste des caracteristiques liste trouvees
    foundListCar = []
    ## Parcours des caracteristiques lues dans le fichier
    for k in self.file.keys():
      ## Si caracteristique connue
      if Sound.car2varDict.has_key(k):
        carDesc = self.car2varDict[k]
        ## Correspond-elle a ce qu'on charge ?
        if what2Load <> '*' and carDesc[3] <> what2Load:
          ## Si non, on saute
          continue
        ## Est-ce une liste ?
        if carDesc[1]:
          ## Si oui, construction de la liste
          varList = []
          for v in self.file[k]:
            tkVar = carDesc[0]()
            tkVar.set(v)
            varList.append(tkVar)
          self.varDict[k] = varList
          ## On la retient
          foundListCar.append(k)
        ## Si valeur simple
        else:
          ## Cas particulier: le note-cutoff-factor
          if k == 'note-cutoff-factor':
            ## Recuperation de la valeur
            noteCutoffFact = int(self.file[k])
            ## Si superieur a 100, passer la caracteristique en wide
            if noteCutoffFact > 100:
              self.cutNotFacWid.set(1)
              self.widenNoteCutoffFactor()
          ## Enregistrement
          self.varDict[k].set(self.file[k])
      ## Si clef pas dans dictionnaire des caracteristiques avec variable
      else:
        ## Si 'output*', 'engine*' ou 'environment*', on laisse tomber; sinon affectation simple
        try:
          if k[:6] <> 'output' and k[:6] <> 'engine' and k[:11] <> 'environment':
            self.varDict[k] = self.file[k]
        except:
          self.varDict[k] = self.file[k]
    ## Longueur de la sequence
    self.seqLenVar.set(len(self.varDict['note']))
    ## Si chargement partiel, c'est fini
    if what2Load <> '*': return
    ## Sinon, recherche des caracteristiques liste non trouvees
    nbNotes = len(self.varDict['note'])
    for k in Sound.car2varDict.keys():
      if Sound.car2varDict[k][1] and not k in foundListCar:
        ## Mise a la valeur par defaut
        self.varDict[k] = []
        for i in range(nbNotes):
          val = Sound.car2varDict[k][2]
          var = Sound.car2varDict[k][0]()
          var.set(val[i % len(val)])
          self.varDict[k].append(var)


  ## METHODES LOAD.ALL, LOAD.NOTES, LOAD.SOUND:
  ## ------------------------------------------
  # Charger le fichier entier, les notes ou le son du fichier
  
  def loadAll(self):
    self.doLoadFile('*')
    self.what2ReadWdw.destroy()
  def loadNotes(self):
    self.doLoadFile('n')
    self.what2ReadWdw.destroy()
  def loadSound(self):
    self.doLoadFile('s')
    self.what2ReadWdw.destroy()

  ## METHODE DO.LOAD.FILE:
  ## ---------------------
  # Charge un fichier
  
  def doLoadFile(self, what2Load):
    ## Si on charge tout, remise a zero
    if what2Load == '*': self.setDefaults()
    ## Chargement du fichier
    self.read(self.file2Load, what2Load)
    ## Mise a jour de l'affichage
    self.drawNotesNAccents()
    self.vertScrollNotes('moveto', 1 - 6.5 / (Sound.numOctaves))
  
  ## METHODE LOAD.FILE:
  ## ------------------
  # Charge une description de son a partir d'un fichier
  
  def loadFile(self):
    ## Le fichier
    self.file2Load = askopenfilename(filetypes=[("3MU files", "*.3MU")])
    if self.file2Load <> '':
      ## Qu'est-ce qu'on veut lire ?
      self.what2ReadWdw = Toplevel()
      Label(self.what2ReadWdw, text="Read:").pack(side=TOP, fill=X)
      Button(self.what2ReadWdw, text="All", command=self.loadAll).pack(side=TOP, fill=X)
      Button(self.what2ReadWdw, text="Notes only", command=self.loadNotes).pack(side=TOP, fill=X)
      Button(self.what2ReadWdw, text="Sound only", command=self.loadSound).pack(side=TOP, fill=X)
      self.what2ReadWdw.geometry("+%d+%d" % (self.parentWdw.winfo_pointery(), self.parentWdw.winfo_pointerx()))
      self.what2ReadWdw.transient(self.parentWdw)
  
  
  ## METHODE SAVE.FILE:
  ## ------------------
  # Enregistre le son courant dans un fichier
  
  def saveFile(self):
    fileName = asksaveasfilename(filetypes=[("3MU files", "*.3MU")], defaultextension='.3MU')
    if fileName <> '':
      self.dump(fileName)
    
    
  ## METHODE DISPLAY:
  ## ----------------
  # Affiche le son dans un conteneur
  
  def display(self, parent):
    ## On retient le pere
    self.parentWdw = parent
    ## Creation des images
    self.genImage = BitmapImage(data=ButtonImages.generalData, foreground=Sound.zoneNamesClr)
    self.filterImage = BitmapImage(data=ButtonImages.filterData, foreground=Sound.zoneNamesClr)
    self.envImage = BitmapImage(data=ButtonImages.envelopeData, foreground=Sound.zoneNamesClr)
    self.notesImage = BitmapImage(data=ButtonImages.notesData, foreground=Sound.zoneNamesClr)
    self.loadImage = BitmapImage(data=ButtonImages.loadData, foreground=Sound.btnClr)
    self.saveImage = BitmapImage(data=ButtonImages.saveData, foreground=Sound.btnClr)
    self.hearItImage = BitmapImage(data=ButtonImages.hearItData, foreground=Sound.btnClr)
    self.quitImage = BitmapImage(data=ButtonImages.quitData, foreground=Sound.btnClr)
    ## Frame pour la partie gauche de la fenetre
    leftFrame = Frame(parent, bd=0, bg=Sound.bg)
    leftFrame.pack(side=LEFT, fill=Y)
    ## Frame pour la bordure des widgets generaux
    genBorderFrame = Frame(leftFrame, bg=Sound.bg, bd=8)
    genBorderFrame.pack(side=TOP, fill=X)
    ## Encadrement des widgets generaux
    genRoundFrame = RoundSquareFrame(genBorderFrame, bg=Sound.bg, fg=Sound.roundFrameClr)
    genRoundFrame.pack(side=TOP, fill=X, expand=1)
    ## Widgets generaux
    self.buildGenControls(genRoundFrame)
    ## Image "General"
    genBtn = Button(genRoundFrame, image=self.genImage, bg=Sound.bg, state=DISABLED, bd=0, relief=FLAT, highlightthickness=0)
    genBtn.pack(side=BOTTOM, anchor='se')
    ## Cadre pour la bordure des widgets pour le filtre
    filtBorderFrame = Frame(leftFrame, bg=Sound.bg, bd=8)
    filtBorderFrame.pack(side=TOP, fill=X)
    ## Encadrement des widgets pour le filtre
    filtRoundFrame = RoundSquareFrame(filtBorderFrame, bg=Sound.bg, fg=Sound.roundFrameClr)
    filtRoundFrame.pack(side=TOP, fill=X, expand=1)
    ## Widgets pour le filtre
    self.buildFilterControls(filtRoundFrame)
    ## Image "Filter"
    filterBtn = Button(filtRoundFrame, image=self.filterImage, bg=Sound.bg, state=DISABLED, bd=0, relief=FLAT, highlightthickness=0)
    filterBtn.pack(side=BOTTOM, anchor='se')
    ## Cadre pour la bordure des widgets pour l'enveloppe
    envBorderFrame = Frame(leftFrame, bg=Sound.bg, bd=8)
    envBorderFrame.pack(side=TOP, fill=BOTH, expand=1)
    ## Encadrement des widgets pour l'enveloppe
    filtRoundFrame = RoundSquareFrame(envBorderFrame, bg=Sound.bg, fg=Sound.roundFrameClr)
    filtRoundFrame.pack(side=TOP)
    ## Widgets pour l'enveloppe
    self.buildEnvControls(filtRoundFrame)
    ## Image "Envelope"
    envBtn = Button(filtRoundFrame, image=self.envImage, bg=Sound.bg, state=DISABLED, bd=0, relief=FLAT, highlightthickness=0)
    envBtn.pack(side=BOTTOM, anchor='se')
    ## Cadre pour la bordure de la zone des boutons
    btnBorderFrame = Frame(leftFrame, bg=Sound.bg, bd=8)
    btnBorderFrame.pack(side=TOP, fill=BOTH, expand=1)
    ## Encadrement de la zone des boutons
    btnRoundFrame = RoundSquareFrame(btnBorderFrame, bg=Sound.bg, fg=Sound.roundFrameClr)
    btnRoundFrame.pack(side=TOP, fill=X, expand=1)
    ## Bouton "Load"
    loadBtn = Button(btnRoundFrame, image=self.loadImage, command=self.loadFile, bg=Sound.bg, bd=0, \
                     highlightthickness=0, highlightcolor=Sound.btnClr)
    loadBtn.pack(side=LEFT)
    ## Bouton "Save"
    saveBtn = Button(btnRoundFrame, image=self.saveImage, command=self.saveFile, bg=Sound.bg, bd=0, \
                     highlightthickness=0, highlightcolor=Sound.btnClr)
    saveBtn.pack(side=LEFT, padx=12)
    ## Bouton "Hear It"
    hearBtn = Button(btnRoundFrame, image=self.hearItImage, command=self.playSound, bg=Sound.bg, bd=0, \
                     highlightthickness=0, highlightcolor=Sound.btnClr)
    hearBtn.pack(side=LEFT, padx=12)
    ## Bouton "Quit"
    quitBtn = Button(btnRoundFrame, image=self.quitImage, command=parent.quit, bg=Sound.bg, bd=0, \
                     highlightthickness=0, highlightcolor=Sound.btnClr)
    quitBtn.pack(side=RIGHT)
    ## Cadre pour la partie droite de la fenetre
    rightFrame = Frame(parent, bd=0, bg=Sound.bg)
    rightFrame.pack(side=LEFT, fill=BOTH, expand=1)
    ## Cadre exterieur pour la zone des notes
    notesOutFrame = Frame(rightFrame, bg=Sound.bg, bd=8)
    notesOutFrame.pack(side=TOP, fill=BOTH, expand=1)
    ## Encadrement de la zone des notes
    notesRFrame = RoundSquareFrame(notesOutFrame, bg=Sound.bg, fg=Sound.roundFrameClr)
    notesRFrame.pack(side=TOP, fill=BOTH, expand=1)
    ## Widgets pour la zone des notes
    self.buildNotesCanvas(notesRFrame)
    ## Image "Notes"
    notesBtn = Button(notesRFrame, image=self.notesImage, bg=Sound.bg, state=DISABLED, bd=0, relief=FLAT, highlightthickness=0)
    notesBtn.pack(side=BOTTOM, anchor='se')
    
    
  ## METHODE BUILD.GEN.CONTROLS:
  ## ---------------------------
  # Construit les widgets pour les parametres generaux
  
  def buildGenControls(self, parent):
    ## Cadre pour tout le monde
    genFrm = Frame(parent, bg=Sound.bg)
    genFrm.pack(side=TOP)
    ## Nom pour le son
    if not self.varDict.has_key('id-string'):
      self.varDict['id-string'] = StringVar()
      self.varDict['id-string'].set('')
    idLbl = Label(genFrm, text='Id. string:', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    idLbl.grid(row=0, column=0, sticky='w')
    idEnt = Entry(genFrm, textvariable=self.varDict['id-string'], bg=Sound.bg, fg=Sound.fg, \
                  font=Sound.stdFont, insertbackground=Sound.fg, selectbackground=Sound.fg)
    idEnt.grid(row=0, column=1, sticky='w')
    ## Gain (en dB)
    if not self.varDict.has_key('gain'):
      self.varDict['gain'] = IntVar()
      self.varDict['gain'].set(0)
    gainLbl = Label(genFrm, text='Gain (dB):', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    gainLbl.grid(row=1, column=0, sticky='w')
    gainEnt = Entry(genFrm, textvariable=self.varDict['gain'], bg=Sound.bg, fg=Sound.fg, \
                    font=Sound.stdFont, insertbackground=Sound.fg, selectbackground=Sound.fg)
    gainEnt.grid(row=1, column=1, sticky='w')    
    ## Tempo
    if not self.varDict.has_key('tempo'):
      self.varDict['tempo'] = IntVar()
      self.varDict['tempo'].set(120)
    tempoLbl = Label(genFrm, text='Tempo:', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    tempoLbl.grid(row=2, column=0, sticky='w')
    tempoEnt = Entry(genFrm, textvariable=self.varDict['tempo'], bg=Sound.bg, fg=Sound.fg, \
                     font=Sound.stdFont, insertbackground=Sound.fg, selectbackground=Sound.fg)
    tempoEnt.grid(row=2, column=1, sticky='w')    
    ## Forme d'onde
    if not self.varDict.has_key('waveform'):
      self.varDict['waveform'] = StringVar()
      self.varDict['waveform'].set('square')
    wvfLbl = Label(genFrm, text='Waveform:', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    wvfLbl.grid(row=3, column=0, sticky='w')
    wvfEnt = Menubutton(genFrm, textvariable=self.varDict['waveform'], bg=Sound.bg, fg=Sound.fg, \
                        font=Sound.stdFont, relief=RAISED)
    wvfEnt.grid(row=3, column=1, sticky='w')    
    wvfMnu = Menu(wvfEnt, bg=Sound.bg, fg=Sound.fg, tearoff=0)
    wvfMnu.add_radiobutton(label='square', value='square', variable=self.varDict['waveform'], font=Sound.stdFont)
    wvfMnu.add_radiobutton(label='sawtooth', value='sawtooth', variable=self.varDict['waveform'], font=Sound.stdFont)
    wvfEnt['menu'] = wvfMnu
    ## Taux de pulsation pour l'onde
    if not self.varDict.has_key('waveform-pulseratio'):
      self.varDict['waveform-pulseratio'] = IntVar()
      self.varDict['waveform-pulseratio'].set(50)
    wfpLbl = Label(genFrm, text='Pulseratio:', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    wfpLbl.grid(row=4, column=0, sticky='w')
    wfpSld = HorizSlider(genFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                         font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                         variable=self.varDict['waveform-pulseratio'])
    wfpSld.grid(row=4, column=1, sticky='w')
    
    
  ## METHODE BUILD.ENV.CONTROLS:
  ## ---------------------------
  # Construit les widgets pour l'enveloppe
  
  def buildEnvControls(self, parent):
    ## Cadre pour tout le monde
    envFrm = Frame(parent, bg=Sound.bg)
    envFrm.pack(side=TOP)
    ## Config pour les sliders
    sliderConfig = {'bg':Sound.bg, 'fg':Sound.fg, 'troughcolor':Sound.fg, 'activebackground':Sound.fg, \
                    'width':6, 'font':Sound.stdFont, 'resolution':0.1, 'digits':5}
    ## Attaque: variable, curseur et label
    if not self.varDict.has_key('attack'):
      self.varDict['attack'] = DoubleVar()
      self.varDict['attack'].set(4.8)
    aSlider = VertSlider(envFrm, 1, 500, sliderConfig, variable=self.varDict['attack'])
    aSlider.grid(row=0, column=0)
    aLabel = Label(envFrm, text='A', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    aLabel.grid(row=1, column=0)
    ## Decay: variable, curseur et label
    if not self.varDict.has_key('decay'):
      self.varDict['decay'] = DoubleVar()
      self.varDict['decay'].set(120)
    dSlider = VertSlider(envFrm, 1, 500, sliderConfig, variable=self.varDict['decay'])
    dSlider.grid(row=0, column=1)
    dLabel = Label(envFrm, text='D', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    dLabel.grid(row=1, column=1)
    ## Sustain: variable, curseur et label
    if not self.varDict.has_key('sustain'):
      self.varDict['sustain'] = DoubleVar()
      self.varDict['sustain'].set(0)
    sSlider = VertSlider(envFrm, 0, 500, sliderConfig, variable=self.varDict['sustain'])
    sSlider.grid(row=0, column=2)
    sLabel = Label(envFrm, text='S', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    sLabel.grid(row=1, column=2)
    ## Release: variable, curseur et label
    if not self.varDict.has_key('release'):
      self.varDict['release'] = DoubleVar()
      self.varDict['release'].set(7)
    rSlider = VertSlider(envFrm, 1, 1000, sliderConfig, variable=self.varDict['release'])
    rSlider.grid(row=0, column=3)
    rLabel = Label(envFrm, text='R', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    rLabel.grid(row=1, column=3)
    
    
  ## BUILD.FILTER.CONTROLS:
  ## ----------------------
  # Construction des widgets pour les parametres du filtre
  
  def buildFilterControls(self, parent):
    ## Cadre englobant
    filtFrm = Frame(parent, bg=Sound.bg)
    filtFrm.pack(side=TOP)
    ## Config de base pour les sliders
    sliderConfig = {'bg':Sound.bg, 'fg':Sound.fg, 'troughcolor':Sound.fg, \
                    'activebackground':Sound.fg, 'font':Sound.stdFont}
    ## Frequence de coupure: variable, label et curseur
    if not self.varDict.has_key('cutoff'):
      self.varDict['cutoff'] = IntVar()
      self.varDict['cutoff'].set(160)
    coLabel = Label(filtFrm, text='Cutoff freq. (Hz): ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    coLabel.grid(row=3, column=0, sticky='w')
    coSlider = HorizSlider(filtFrm, 20, 20000, sliderConfig, resolution=1, digits=5, width=5, \
                           variable=self.varDict['cutoff'])
    coSlider.grid(row=3, column=1, sticky='w')
    ## Taux de filtrage: variable, label et curseur
    if not self.varDict.has_key('filter-amount'):
      self.varDict['filter-amount'] = IntVar()
      self.varDict['filter-amount'].set(100)
    aLabel = Label(filtFrm, text='Amount (%): ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    aLabel.grid(row=1, column=0, sticky='w')
    aSlider = HorizSlider(filtFrm, 0, 100, sliderConfig, resolution=1, digits=3, width=3, \
                           variable=self.varDict['filter-amount'])
    aSlider.grid(row=1, column=1, sticky='w')
    ## Nombre de poles: variable, label et curseur
    if not self.varDict.has_key('filter-poles'):
      self.varDict['filter-poles'] = IntVar()
      self.varDict['filter-poles'].set(2)
    pLabel = Label(filtFrm, text='Nb poles: ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    pLabel.grid(row=2, column=0, sticky='w')
    pSlider = HorizSlider(filtFrm, 0, 10, sliderConfig, resolution=2, digits=2, width=2, \
                           variable=self.varDict['filter-poles'])
    pSlider.grid(row=2, column=1, sticky='w')
    ## Type de filtre: variable, label et menu
    if not self.varDict.has_key('filter-type'):
      self.varDict['filter-type'] = StringVar()
      self.varDict['filter-type'].set('generic')
    tLabel = Label(filtFrm, text='Type: ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    tLabel.grid(row=0, column=0, sticky='w')
    tMenuBtn = Menubutton(filtFrm, bg=Sound.bg, fg=Sound.fg, textvariable=self.varDict['filter-type'], \
                          font=Sound.stdFont, relief=RAISED)
    tMenu = Menu(tMenuBtn, bg=Sound.bg, fg=Sound.fg, tearoff=0)
    tMenu.add_radiobutton(label='generic', variable=self.varDict['filter-type'], value='generic', font=Sound.stdFont)
    tMenu.add_radiobutton(label='hamre', variable=self.varDict['filter-type'], value='hamre', font=Sound.stdFont)
    tMenuBtn['menu'] = tMenu
    tMenuBtn.grid(row=0, column=1, sticky='w')
    ## Filtre passe-haut: variable, label et case a cocher
    if not self.varDict.has_key('oscillator-hpf'):
      self.varDict['oscillator-hpf'] = StringVar()
      self.varDict['oscillator-hpf'].set('off')
    hLabel = Label(filtFrm, text='Enable HPF: ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    hLabel.grid(row=5, column=0, sticky='w')
    hButton = Checkbutton(filtFrm, bg=Sound.bg, fg=Sound.fg, onvalue='on', offvalue='off', \
                          variable=self.varDict['oscillator-hpf'])
    hButton.grid(row=5, column=1, sticky='w')
    ## Resonance: variable, label et curseur
    if not self.varDict.has_key('resonance'):
      self.varDict['resonance'] = IntVar()
      self.varDict['resonance'].set(5)
    rLabel = Label(filtFrm, text='Resonance: ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    rLabel.grid(row=4, column=0, sticky='w')
    rSlider = HorizSlider(filtFrm, 1, 50, sliderConfig, resolution=1, digits=2, width=2, \
                           variable=self.varDict['resonance'])
    rSlider.grid(row=4, column=1, sticky='w')
    

  ## METHODE DECR.SEQ.LEN:
  ## ---------------------
  # Decremente la longueur de la sequence
  
  def decrSeqLen(self):
    ## Mise a jour des variables
    for k in Sound.car2varDict.keys():
      if Sound.car2varDict[k][1]:
        self.varDict[k] = self.varDict[k][:-1]
    self.seqLenVar.set(len(self.varDict['note']))
    ## Mise a jour de l'affichage
    self.drawNotesNAccents()
    self.vertScrollNotes('moveto', 1 - 6.5 / (Sound.numOctaves))
    

  ## METHODE INCR.SEQ.LEN:
  ## ---------------------
  # Incremente la longueur de la sequence
  
  def incrSeqLen(self):
    ## Mise a jour des variables
    for k in Sound.car2varDict.keys():
      if Sound.car2varDict[k][1]:
        ## Creation d'une nouvelle variable
        var = Sound.car2varDict[k][0]()
        ## Mise a jour avec la valeur par defaut
        var.set(Sound.car2varDict[k][2][0])
        ## Ajout a la liste
        self.varDict[k].append(var)
    self.seqLenVar.set(len(self.varDict['note']))
    ## Mise a jour de l'affichage
    self.drawNotesNAccents()
    self.vertScrollNotes('moveto', 1 - 6.5 / (Sound.numOctaves))
    
    
  ## METHODE DO.REPEATS:
  ## -------------------
  # Realisation effective des repetitions indiquees
  
  def doRepeats(self):
    ## Nombre de repetions
    nbRep = self.varDict['repetitions'].get() - 1
    ## Parcours des caracteristiques
    for k in Sound.car2varDict.keys():
      ## Si caracteristique multiple
      if Sound.car2varDict[k][1]:
        ## Creation des elements supplementaires
        oldNb = len(self.varDict[k])
        for i in range(nbRep * oldNb):
          ## Creation de la variable
          newVar = Sound.car2varDict[k][0]()
          ## Initialisation avec la valeur correspondante de la liste actuelle
          newVar.set(self.varDict[k][i % oldNb].get())
          ## Ajout a la liste
          self.varDict[k].append(newVar)
    ## C'est fini: remise a 1 du nombre de repetitions
    self.varDict['repetitions'].set(1)
    ## Longueur de la sequence
    self.seqLenVar.set(len(self.varDict['note']))
    ## Remise a jour de l'affichage
    self.drawNotesNAccents()
    self.vertScrollNotes('moveto', 1 - 6.5 / (Sound.numOctaves))
  
  
  ## METHODE GLOBAL.EDIT:
  ## --------------------
  # Edition globale pour les parametres des notes
  
  def globalEdit(self):
    ## Creation de la fenetre
    globEditWdw = SliderSet('Global edit', len(self.varDict['note']))
    ## Ajout des caracteristiques pour edition globale
    globEditWdw.addEditableSet('Note cutoff', self.varDict['note-cutoff'])
    globEditWdw.addEditableSet('Note resonance', self.varDict['note-resonance'])

  ## METHODE BUILD.NOTES.CANVAS:
  ## ---------------------------
  # Construit la zone d'entree des notes
  
  def buildNotesCanvas(self, parent):
    ## Frame englobant
    self.notesFrm = Frame(parent, bg=Sound.bg)
    self.notesFrm.pack(side=TOP)
    ## Frame pour les boutons de transposition
    transFrm = Frame(self.notesFrm, bg=Sound.bg)
    transFrm.grid(row=1, column=0, sticky='we')
    ## Boutons de transposition
    self.transOctUpImg = BitmapImage(data=ButtonImages.transOctUpData, foreground=Sound.btnClr)
    self.transUpImg = BitmapImage(data=ButtonImages.transUpData, foreground=Sound.btnClr)
    self.transDownImg = BitmapImage(data=ButtonImages.transDownData, foreground=Sound.btnClr)
    self.transOctDownImg = BitmapImage(data=ButtonImages.transOctDownData, foreground=Sound.btnClr)
    transOctUpBtn = Button(transFrm, image=self.transOctUpImg, command=self.transOctUp, \
                           fg=Sound.btnClr, bg=Sound.bg, bd=0, highlightthickness=0)
    transOctUpBtn.pack(side=TOP, padx=2, pady=4)
    transUpBtn = Button(transFrm, image=self.transUpImg, command=self.transUp, \
                        fg=Sound.btnClr, bg=Sound.bg, bd=0, highlightthickness=0)
    transUpBtn.pack(side=TOP, padx=2, pady=4)
    transDownBtn = Button(transFrm, image=self.transDownImg, command=self.transDown, \
                          fg=Sound.btnClr, bg=Sound.bg, bd=0, highlightthickness=0)
    transDownBtn.pack(side=TOP, padx=2, pady=4)
    transOctDownBtn = Button(transFrm, image=self.transOctDownImg, command=self.transOctDown, \
                             fg=Sound.btnClr, bg=Sound.bg, bd=0, highlightthickness=0)
    transOctDownBtn.pack(side=TOP, padx=2, pady=4)
    ## Canvas pour le clavier
    self.keybCnv = Canvas(self.notesFrm, bg=Sound.bg, width=Sound.keybWidth, height=1)
    self.keybCnv.grid(row=1, column=1, sticky='nswe')
    ## Dessin du clavier
    self.scrollHeight = self.drawKeyboard(self.keybCnv)
    ## Configuration de la region de defilement
    self.keybCnv['scrollregion'] = (0, 0, 0, self.scrollHeight)
    ## Construction des notes et des accents
    self.drawNotesNAccents()
    ## Scrollbar verticale
    vertScr = Scrollbar(self.notesFrm, orient=VERTICAL)
    vertScr.grid(row=1, column=3, sticky='nswe')
    vertScr['command'] = self.vertScrollNotes
    self.keybCnv['yscrollcommand'] = vertScr.set
    self.notesCnv['yscrollcommand'] = vertScr.set
    ## Et on scrolle pour voir l'octave mediane
    self.vertScrollNotes('moveto', 1 - 6.5 / (Sound.numOctaves))
    ## Frame pour les autres caracteristiques
    otherFrm = Frame(self.notesFrm, bg=Sound.bg, bd=4)
    otherFrm.grid(row=3, column=0, columnspan=4, sticky='we')
    ## Bouton "Global Edit"
    self.globEditImg = BitmapImage(data=ButtonImages.globEditData, foreground=Sound.btnClr)
    globEditBtn = Button(otherFrm, image=self.globEditImg, command=self.globalEdit, fg=Sound.btnClr, bg=Sound.bg, bd=0, highlightthickness=0)
    globEditBtn.grid(row=0, column=0, padx=32)
    ## Label et entree pour la longueur de la sequence
    seqLenLbl = Label(otherFrm, text='Sequence length: ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont, \
                      anchor=W, justify=LEFT)
    seqLenLbl.grid(row=0, column=1, sticky='w')
    seqLenDecrBtn = Button(otherFrm, text='-', bg=Sound.bg, fg=Sound.fg, font=Sound.stdFont, \
                           command=self.decrSeqLen)
    seqLenDecrBtn.grid(row=0, column=2, sticky='w')
    seqLenEnt = Entry(otherFrm, bg=Sound.bg, fg=Sound.fg, font=Sound.stdFont, width=5, \
                      textvariable=self.seqLenVar, state=DISABLED, relief=FLAT)
    seqLenEnt.grid(row=0, column=3, sticky='we')
    seqLenIncrBtn = Button(otherFrm, text='+', bg=Sound.bg, fg=Sound.fg, font=Sound.stdFont, \
                           command=self.incrSeqLen)
    seqLenIncrBtn.grid(row=0, column=4, sticky='w')
    ## Nombre de repetitions
    repLbl = Label(otherFrm, text=' x ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    repLbl.grid(row=0, column=5)
    repEnt = Entry(otherFrm, textvariable=self.varDict['repetitions'], width=4, bg=Sound.bg, fg=Sound.fg, \
                   font=Sound.stdFont)
    repEnt.grid(row=0, column=6)
    ## Bouton "Do it"
    self.doItImg = BitmapImage(data=ButtonImages.doItData, foreground=Sound.btnClr)
    doItBtn = Button(otherFrm, image=self.doItImg, command=self.doRepeats, fg=Sound.btnClr, bg=Sound.bg, bd=0, highlightthickness=0)
    doItBtn.grid(row=0, column=7, padx=32)
    ## Cadre pour tout le reste
    factorFrm = Frame(self.notesFrm, bg=Sound.bg, bd=4)
    factorFrm.grid(row=4, column=0, columnspan=4, sticky='w')
    ## Temps de portamento
    prtLbl = Label(factorFrm, text='Portamento time: ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    prtLbl.grid(row=0, column=0, sticky='w')
    prtSld = HorizSlider(factorFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                         font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                         variable=self.varDict['portamento-time'])
    prtSld.grid(row=0, column=1, sticky='w')
    ## Separateur
    sep = Frame(factorFrm, bg=Sound.fg, height=2)
    sep.grid(row=1, column=0, columnspan=4, sticky='we', pady=4)
    ## Tableau des facteurs de modulation: titre des colonnes
    col1Head = Label(factorFrm, text='Env. mod.', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    col1Head.grid(row=2, column=1)
    col2Head = Label(factorFrm, text='Note', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    col2Head.grid(row=2, column=2)
    col3Head = Label(factorFrm, text='Note env. mod.', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    col3Head.grid(row=2, column=3)
    ## Enveloppe
    envFacLbl = Label(factorFrm, text='Envelope: ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    envFacLbl.grid(row=3, column=0, sticky='w')
    envNotFacSld = HorizSlider(factorFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                               font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                               variable=self.varDict['note-adsr-factor'])
    envNotFacSld.grid(row=3, column=2, sticky='w', padx=4, pady=2)
    ## Volume
    volFacLbl = Label(factorFrm, text='Volume: ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    volFacLbl.grid(row=4, column=0, sticky='w')
    volEmdFacSld = HorizSlider(factorFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                               font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                               variable=self.varDict['volume-envmod'])
    volEmdFacSld.grid(row=4, column=1, sticky='w', padx=4, pady=2)
    volNotFacSld = HorizSlider(factorFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                               font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                               variable=self.varDict['note-volume-factor'])
    volNotFacSld.grid(row=4, column=2, sticky='w', padx=4, pady=2)
    volNemFacSld = HorizSlider(factorFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                               font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                               variable=self.varDict['note-volume-envmod-factor'])
    volNemFacSld.grid(row=4, column=3, sticky='w', padx=4, pady=2)
    ## Detune
    detFacLbl = Label(factorFrm, text='Detune: ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    detFacLbl.grid(row=5, column=0, sticky='w')
    detEmdFacSld = HorizSlider(factorFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                               font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                               variable=self.varDict['detune-envmod'])
    detEmdFacSld.grid(row=5, column=1, sticky='w', padx=4, pady=2)
    detNemFacSld = HorizSlider(factorFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                               font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                               variable=self.varDict['note-detune-envmod-factor'])
    detNemFacSld.grid(row=5, column=3, sticky='w', padx=4, pady=2)
    ## Cutoff
    cutFacLbl = Label(factorFrm, text='Cutoff: ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    cutFacLbl.grid(row=6, column=0, sticky='w')
    cutEmdFacSld = HorizSlider(factorFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                               font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                               variable=self.varDict['cutoff-envmod'])
    cutEmdFacSld.grid(row=6, column=1, sticky='w', padx=4, pady=2)
    cutNotFacFrm = Frame(factorFrm, bg=Sound.bg)
    cutNotFacFrm.grid(row=6, column=2, sticky='w', padx=4, pady=2)
    self.cutNotFacSld = HorizSlider(cutNotFacFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                                    font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                                    variable=self.varDict['note-cutoff-factor'])
    self.cutNotFacSld.pack(side=TOP)
    self.cutNotFacWid.set(self.varDict['note-cutoff-factor'].get() > 100)
    self.widenNoteCutoffFactor()
    cutNotFacWid = Checkbutton(cutNotFacFrm, variable=self.cutNotFacWid, text='Wide', bg=Sound.bg, \
                               fg=Sound.fg, font=Sound.stdFont, command=self.widenNoteCutoffFactor)
    cutNotFacWid.pack(side=TOP)
    cutNemFacSld = HorizSlider(factorFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                               font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                               variable=self.varDict['note-cutoff-envmod-factor'])
    cutNemFacSld.grid(row=6, column=3, sticky='w', padx=4, pady=2)
    ## Resonance
    resFacLbl = Label(factorFrm, text='Resonance: ', bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont)
    resFacLbl.grid(row=7, column=0, sticky='w')
    resNotFacSld = HorizSlider(factorFrm, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                               font=Sound.stdFont, activebackground=Sound.fg, width=4, digits=0, \
                               variable=self.varDict['note-resonance-factor'])
    resNotFacSld.grid(row=7, column=2, sticky='w', padx=4, pady=2)
    ## Separateur
    sep = Frame(factorFrm, bg=Sound.fg, height=2)
    sep.grid(row=8, column=0, columnspan=4, sticky='we', pady=4)
  
  
  ## METHODE WIDEN.NOTE.CUTOFF.FACTOR:
  ## ---------------------------------
  # Elargit le slider pour le facteur de cutoff pour les notes
  
  def widenNoteCutoffFactor(self):
    ## Si pas de slider, rien a faire
    if self.cutNotFacSld == None: return
    ## Que faut-il faire ?
    if self.cutNotFacWid.get():
      ## Si elargir, on elargit
      self.cutNotFacSld.setTo(5000)
    else:
      ## Sinon, on remet normal
      self.cutNotFacSld.setTo(100)
  
  
  ## METHODE DRAW.KEYBOARD:
  ## ----------------------
  # Dessin du clavier dans le canvas
  
  def drawKeyboard(self, cnv):
    ## Tableau des largeurs de touches blanches
    smallWKWid = Sound.notesVDist * 1.5
    wideWKWid = Sound.notesVDist * 2
    whiteKeysDim = [ smallWKWid, wideWKWid, smallWKWid, smallWKWid, wideWKWid, wideWKWid, smallWKWid ]
    ## Calcul de la largeur d'une octave
    octWid = 0
    for w in whiteKeysDim: octWid = octWid + w
    ## Clavier = 9 octaves
    height = Sound.numOctaves * octWid
    ## Dessin des touches blanches a partir du bas
    for oct in range(0, Sound.numOctaves):
      noteBottom = height - oct * octWid
      for noteWid in whiteKeysDim:
        noteTop = noteBottom - noteWid
        if oct == 5:
          cnv.create_rectangle(0, noteTop + 1, Sound.keybWidth, noteBottom - 1, fill=Sound.whiteKeysClr)
        else:
          cnv.create_rectangle(0, noteTop + 1, Sound.keybWidth, noteBottom - 1, fill=Sound.whiteKeysClr, stipple='gray50')
        noteBottom = noteTop
    ## Demi-largeur et profondeur d'une touche noire
    blackKeyHWid = Sound.notesVDist * 0.75
    blackKeyDepth = Sound.keybWidth * 2.0 / 3.0
    ## Tableau des position centre des touches noires
    blackKeysCentres = [ wideWKWid, 2 * smallWKWid, wideWKWid, wideWKWid, 2 * smallWKWid]
    ## Position centre de la premiere touche
    centrePos = height - smallWKWid
    ## Pour toutes les octaves
    for oct in range(0, Sound.numOctaves):
      ## Dessiner toutes les touches noires
      for c in blackKeysCentres:
        if oct == 5:
          cnv.create_rectangle(0, centrePos - blackKeyHWid, blackKeyDepth, centrePos + blackKeyHWid, fill=Sound.blackKeysClr)
        else:
          cnv.create_rectangle(0, centrePos - blackKeyHWid, blackKeyDepth, centrePos + blackKeyHWid, fill=Sound.blackKeysClr, stipple='gray50')
        centrePos = centrePos - c
    ## Fini: on retourne la hauteur du cadre
    return height
    
    
  ## METHODE SPLIT.NOTE:
  ## -------------------
  # Separe une note en nom de note + modifieur + octave
  
  def splitNote(self, note):
    ## Nom de la note = premier caractere
    noteName = note[0]
    if noteName < 'a' or noteName > 'g':
      raise ValueError, "Wrong note name: %s" % note
    ## Modifieur et octave
    modifier = note[1]
    octString = note[2:]
    if modifier <> 'b' and modifier <> '#':
      modifier = ''
      octString = note[1:]
    ## Tentative de conversion de l'octave en entier
    try:
      oct = int(octString)
    except ValueError:
      raise ValueError, "Wrong octave number: %s" % note
    ## C'est fini
    return noteName, modifier, oct
    
    
  ## METHODE PITCH.TO.LINE:
  ## ----------------------
  # Calcule le numero de ligne d'une note en fonction de sa hauteur
  # pitch = chaine de la forme "<nom de note><octave>" ou "r" pour un silence (rest)
  
  def pitchToLine(self, pitch):
    ## On evacue le cas du silence
    if pitch == 'r' or pitch == '-': return None
    ## Dictionnaire des noms de note avec hauteur correspondante
    pitchDict = {'c': 0, 'c#':1, 'db':1, 'd':2, 'd#':3, 'eb':3, 'e':4, 'fb':4, 'e#':5, 'f':5, 'f#':6, \
                 'gb':6, 'g':7, 'g#':8, 'ab':8, 'a':9, 'a#':10, 'bb':10, 'b':11, 'cb':11, 'b#':12}
    ## Recherche du nom de la note
    noteName, modifier, octNum = self.splitNote(pitch)
    ## Calcul du numero de ligne
    lineNum = (Sound.numOctaves - octNum) * 12 - pitchDict[noteName + modifier]
    return lineNum
  
  
  ## METHODE DRAW.NOTES:
  ## -------------------
  # Dessin des notes
  
  def drawNotes(self):
    for n in range(len(self.varDict['note'])):
      self.drawNote(n + 1, self.pitchToLine(self.varDict['note'][n].get()))
  
  
  ## METHODE DRAW.NOTE.ZONE:
  ## -----------------------
  # Dessin de la zone des notes
  
  def drawNoteZone(self, cnv):
    ## Calcul des dimensions de la zone
    width = len(self.varDict['note']) * (Sound.notesHDist + 1)
    height = Sound.numOctaves * 12 * Sound.notesVDist
    ## Tableau des couleurs de ligne
    lineColors = [Sound.whiteKeysClr, Sound.blackKeysClr, Sound.whiteKeysClr, Sound.blackKeysClr, \
                  Sound.whiteKeysClr, Sound.whiteKeysClr, Sound.blackKeysClr, Sound.whiteKeysClr, \
                  Sound.blackKeysClr, Sound.whiteKeysClr, Sound.blackKeysClr, Sound.whiteKeysClr ]
    ## Premiere note a une demi-hauteur
    linePos = height - (Sound.notesVDist / 2.0)
    ## C'est parti
    for oct in range(0, Sound.numOctaves):
      for clr in lineColors:
        if oct == 5:
          cnv.create_line(2, linePos, width - 2, linePos, fill=clr)
        else:
          cnv.create_line(2, linePos, width - 2, linePos, fill=clr, stipple='gray50')
        linePos = linePos - Sound.notesVDist
    ## Lignes verticales
    linePos = Sound.notesHDist
    for n in range(len(self.varDict['note'])):
      cnv.create_line(linePos, 2, linePos, height - 2, fill=Sound.whiteKeysClr, stipple='gray50')
      linePos = linePos + Sound.notesHDist
    ## Dessin des notes elles-memes
    self.drawNotes()
    ## Fini: on rend la largeur
    return width
  
  
  ## METHODE DRAW.ACCENTS:
  ## ---------------------
  
  def drawAccents(self, cnv):
    ## Position initiale
    chkBtnPos = Sound.notesHDist
    ## Parcours des notes
    for n in range(len(self.varDict['note-accent'])):
      ## Creation du bouton
      chkBtn = Checkbutton(cnv, text='', variable=self.varDict['note-accent'][n], bg=Sound.bg, fg=Sound.fg, \
                           highlightthickness=0)
      ## Positionnement
      cnv.create_window(chkBtnPos, 10, window=chkBtn)
      ## Au suivant
      chkBtnPos = chkBtnPos + Sound.notesHDist
    
    
  ## METHODE DRAW.NOTES.N.ACCENTS:
  ## -----------------------------
  # Dessine les canvas pour les notes et les accents
  
  def drawNotesNAccents(self):
    ## Canvas pour les notes
    self.notesCnv = Canvas(self.notesFrm, name='notes-canvas', bg=Sound.bg, width=16 * Sound.notesHDist, \
                           height=24 * Sound.notesVDist)
    self.notesCnv.grid(row=1, column=2, sticky='nswe')
    ## Action sur clic souris
    self.notesCnv.bind("<1>", self.inputNote)
    ## Action sur double-clic
    self.notesCnv.bind("<Double-Button-1>", self.editNote)
    ## Dessin de la grille
    width = self.drawNoteZone(self.notesCnv)
    ## Configuration de la region de defilement
    self.notesCnv['scrollregion'] = (0, 0, width, self.scrollHeight)
    ## Canvas pour l'entree des accents
    self.accCnv = Canvas(self.notesFrm, name='acc-canvas', bg=Sound.bg, width=1, height=20)
    self.accCnv.grid(row=0, column=2, sticky='nswe')
    ## Dessin des accents
    self.drawAccents(self.accCnv)
    ## Configuration de la region de defilement pour les accents
    self.accCnv['scrollregion'] = (0, 0, width, 0)
    ## Scrollbar horizontale
    horizScr = Scrollbar(self.notesFrm, name='horiz-scroll', orient=HORIZONTAL)
    horizScr.grid(row=2, column=2, sticky='nswe')
    horizScr['command'] = self.horizScrollNotes
    self.accCnv['xscrollcommand'] = horizScr.set
    self.notesCnv['xscrollcommand'] = horizScr.set
    ## Si transposition demandee
    if self.varDict.has_key('transpose'):
      ## On la fait
      nbTransp = int(self.varDict['transpose'].get())
      if nbTransp > 0:
        for i in range(nbTransp): self.transUp()
      else:
        for i in range(-nbTransp): self.transDown()
      ## Et on annule la caracteristique
      self.varDict['transpose'].set(0)
    
    
  ## METHODE VERT.SCROLL.NOTES:
  ## --------------------------
  # Defilement vertical de la zone de notes
  
  def vertScrollNotes(self, *args):
    apply(self.notesCnv.yview, args)
    apply(self.keybCnv.yview, args)
    
    
  ## METHODE HORIZ.SCROLL.NOTES:
  ## ---------------------------
  # Defilement horizontal de la zone de notes
  
  def horizScrollNotes(self, *args):
    apply(self.notesCnv.xview, args)
    apply(self.accCnv.xview, args)
    
    
  ## METHODE DRAW.NOTE:
  ## ------------------
  # Dessine une note sur la "portee"
  # noteNum: numero d'ordre de la note a dessiner
  # lineNum: ligne sur laquelle dessiner la note
  
  def drawNote(self, noteNum, lineNum):
    ## Calcul du nom du tag
    tagName = "noteNum%s" % int(noteNum)
    ## Destruction de l'ancienne note a cette position
    self.notesCnv.delete(tagName)
    ## Si pas de ligne, c'est fini
    if lineNum == None: return
    ## Tableau des couleurs de ligne (inverse...)
    lineColors = [Sound.whiteKeysClr, Sound.blackKeysClr, Sound.whiteKeysClr, Sound.blackKeysClr, \
                  Sound.whiteKeysClr, Sound.blackKeysClr, Sound.whiteKeysClr, Sound.whiteKeysClr, \
                  Sound.blackKeysClr, Sound.whiteKeysClr, Sound.blackKeysClr, Sound.whiteKeysClr ]
    ## Calcul de la couleur de la note
    noteColor = lineColors[(int(lineNum) - 1) % 12]
    ## Calcul de la position de la note a afficher
    noteCenterX = Sound.notesHDist * noteNum
    noteCenterY = Sound.notesVDist * (lineNum - 0.5)
    ## Affichage de la note etiquetee
    self.notesCnv.create_oval(noteCenterX - Sound.noteRadius, noteCenterY - Sound.noteRadius, \
                              noteCenterX + Sound.noteRadius, noteCenterY + Sound.noteRadius, \
                              fill=noteColor, tags=tagName)
  
  
  ## METHODE INPUT.NOTE:
  ## -------------------
  # Appelee par un clic souris dans le canvas pour les notes
  
  def inputNote(self, event):
    ## Calcul du numero et de la hauteur de la note a afficher
    x = self.notesCnv.canvasx(event.x)
    y = self.notesCnv.canvasy(event.y)
    noteNum = round(x / Sound.notesHDist)
    noteLine = 1 + round((y - Sound.notesVDist / 2.0) / Sound.notesVDist)
    ## Calcul du nom de note en fonction du numero de ligne
    nameList = ['b', 'bb', 'a', 'g#', 'g', 'f#', 'f', 'e', 'eb', 'd', 'c#', 'c']
    noteName = nameList[int((noteLine - 1) % 12)]
    octNum = Sound.numOctaves - 1 - int((noteLine - 1) / 12)
    ## Mise a jour de la variable
    self.varDict['note'][int(noteNum) - 1].set('%s%s' % (noteName, octNum))
    ## Mise a jour des variables d'edition si besoin est
    if self.noteNameVar <> None: self.noteNameVar.set(noteName)
    if self.noteOctVar <> None:  self.noteOctVar.set(octNum)
    ## Dessin
    self.drawNote(noteNum, noteLine)
    
    
  ## METHODE SET.NOTE:
  ## -----------------
  # Appelee par une modif du nom ou de l'octave pour la note en cours d'edition
  
  def setNote(self):
    ## Recup du nom de note
    noteName = self.noteNameVar.get()
    ## Si silence, mise a jour du mode
    if noteName == '-':
      self.varDict['note-mode'][self.editedNote].set('rest')
      ## On met la note a une valeur bidon
      self.varDict['note'][self.editedNote].set('c0')
      self.noteNameVar.set('c')
      self.noteOctVar.set(0)
    ## Sinon, on concatene le numero d'octave
    else:
      self.varDict['note'][self.editedNote].set('%s%s' % (noteName, self.noteOctVar.get()))
    ## Et on redessine la zone des notes
    self.drawNotesNAccents()
    self.vertScrollNotes('moveto', 1 - 6.5 / (Sound.numOctaves))
  
  
  ## METHODE SET.NOTE.LENGTH:
  ## ------------------------
  # Appelee par une modif de la longueur pour la note en cours d'edition
  
  def setNoteLength(self):
    ## Tentative de conversion de la longueur chaine en entier
    noteLen = self.noteLenVar.get()
    try:
      self.varDict['note-length'][self.editedNote].set('%s' % int(noteLen))
      self.noteLenVar.set('(other...)')
      if noteLen == '25': self.noteLenVar.set('staccato')
      if noteLen == '40': self.noteLenVar.set('portato')
      if noteLen == '55': self.noteLenVar.set('normal')
      if noteLen == '80': self.noteLenVar.set('legato')
    ## Si ca n'a pas marche, decodage
    except ValueError:
      if noteLen[0] == 'l':
        self.varDict['note-length'][self.editedNote].set(80)
      elif noteLen[0] == 's':
        self.varDict['note-length'][self.editedNote].set(25)
      elif noteLen[0] == 'p':
        self.varDict['note-length'][self.editedNote].set(40)
      else:
        self.varDict['note-length'][self.editedNote].set(55)  


  ## METHODE BUILD.EDIT.NOTE.FRAME:
  ## ------------------------------
  # Construit le cadre d'edition pour la note courante
  
  def buildEditNoteFrame(self):
    ## Un frame pour tout le monde
    lblFrame = Frame(self.editDialog, bg=Sound.bg, name='note-car-frame')
    lblFrame.pack(side=TOP, fill=X)
    lblFrame.grid_columnconfigure(2, weight=1)
    ## Configs pour les labels et les entrees
    lblConf = {'bg':Sound.bg, 'fg':Sound.fg, 'font':Sound.boldFont, 'justify':LEFT, 'anchor':W}
    entConf = {'bg':Sound.bg, 'fg':Sound.fg, 'font':Sound.stdFont, \
               'insertbackground':Sound.fg, 'selectbackground':Sound.fg}
    ## Pour tests: caracteristiques deja affichees
    dispCar = []
    ## Ligne courante
    rowNum = 0
    ## Label pour numero de note
    numLbl = Label(lblFrame, lblConf, text='Note number')
    numLbl.grid(row=rowNum, column=0, sticky='w')
    ## Variable et entree plate et desactivee pour le numero
    numVar = IntVar()
    numVar.set(self.editedNote)
    numEnt = Entry(lblFrame, entConf, textvariable=numVar, state=DISABLED, relief=FLAT)
    numEnt.grid(row=rowNum, column=1, columnspan=2, sticky='w')
    ## Nom de la note
    dispCar = dispCar + [ 'note' ]
    rowNum = rowNum + 1
    namLbl = Label(lblFrame, lblConf, text='Note is:')
    namLbl.grid(row=rowNum, column=0, sticky='w')
    ## Deux menus, donc deux variables supplementaires pour le nom de note et l'octave
    self.noteNameVar = StringVar()
    self.noteOctVar = IntVar()
    ## Decomposition de la note
    noteDesc = self.varDict['note'][self.editedNote].get()
    if noteDesc == 'r' or noteDesc == '-':
      self.noteNameVar.set('-')
      self.noteOctVar.set(0)
    else:
      noteName, modifier, octNum = self.splitNote(noteDesc)
      self.noteNameVar.set(noteName + modifier)
      self.noteOctVar.set(octNum)
    ## Construction du menu pour le nom de note
    namMnuBtn = Menubutton(lblFrame, bg=Sound.bg, fg=Sound.fg, textvariable=self.noteNameVar, \
                           font=Sound.stdFont, relief=RAISED)
    namMnuBtn.grid(row=rowNum, column=1, sticky='we')
    namMnu = Menu(namMnuBtn, bg=Sound.bg, fg=Sound.fg, tearoff=0)
    namMnu.add_radiobutton(label='A',  value='a',  font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnu.add_radiobutton(label='Bb', value='bb', font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnu.add_radiobutton(label='B',  value='b',  font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnu.add_radiobutton(label='C',  value='c',  font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnu.add_radiobutton(label='C#', value='c#', font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnu.add_radiobutton(label='D',  value='d',  font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnu.add_radiobutton(label='Eb', value='eb', font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnu.add_radiobutton(label='E',  value='e',  font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnu.add_radiobutton(label='F',  value='f',  font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnu.add_radiobutton(label='F#', value='f#', font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnu.add_radiobutton(label='G',  value='g',  font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnu.add_radiobutton(label='G#', value='g#', font=Sound.stdFont, variable=self.noteNameVar, command=self.setNote)
    namMnuBtn['menu'] = namMnu
    ## Construction du menu pour le numero d'octave
    octMnuBtn = Menubutton(lblFrame, bg=Sound.bg, fg=Sound.fg, textvariable=self.noteOctVar, \
                           font=Sound.stdFont, relief=RAISED)
    octMnuBtn.grid(row=rowNum, column=2, sticky='w')
    octMnu = Menu(octMnuBtn, bg=Sound.bg, fg=Sound.fg, tearoff=0)
    for o in range(Sound.numOctaves):
      octMnu.add_radiobutton(label='%s' % o, value=o, font=Sound.stdFont, variable=self.noteOctVar, command=self.setNote)
    octMnuBtn['menu'] = octMnu
    ## Label pour le detune
    dispCar = dispCar + [ 'note-detune', 'note-detune-envmod' ]
    rowNum = rowNum + 1
    dtnLbl = Label(lblFrame, lblConf, text='Detune:')
    dtnLbl.grid(row=rowNum, column=0, sticky='w')
    ## Slider pour le detune
    dtnSld = HorizSlider(lblFrame, -99, 99, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                         font=Sound.stdFont, activebackground=Sound.fg, width=4, \
                         variable=self.varDict['note-detune'][self.editedNote])
    dtnSld.grid(row=rowNum, column=1, columnspan=2, sticky='w')
    ## Slider pour le facteur de detune p/r a l'enveloppe
    envDtnSld = HorizSlider(lblFrame, -100, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                            font=Sound.stdFont, activebackground=Sound.fg, width=6, \
                            variable=self.varDict['note-detune-envmod'][self.editedNote])
    envDtnSld.grid(row=rowNum, column=3, columnspan=2, sticky='w')
    ## Label pour mode de la note
    dispCar = dispCar + [ 'note-mode' ]
    rowNum = rowNum + 1
    modLbl = Label(lblFrame, lblConf, text='Note mode:')
    modLbl.grid(row=rowNum, column=0, sticky='w')
    ## On ajuste la valeur
    noteMode = self.varDict['note-mode'][self.editedNote].get()
    if noteMode[0] == 't':
      self.varDict['note-mode'][self.editedNote].set('tie')
    elif noteMode[0] == 'r':
      self.varDict['note-mode'][self.editedNote].set('rest')
    else:
      self.varDict['note-mode'][self.editedNote].set('normal')
    ## Menu pour le mode
    modMnuBtn = Menubutton(lblFrame, bg=Sound.bg, fg=Sound.fg, font=Sound.stdFont, relief=RAISED, \
                           textvariable=self.varDict['note-mode'][self.editedNote])
    modMnuBtn.grid(row=rowNum, column=1, columnspan=2, sticky='w')
    modMnu = Menu(modMnuBtn, bg=Sound.bg, fg=Sound.fg, tearoff=0)
    modMnu.add_radiobutton(label='normal', value='normal', font=Sound.stdFont, variable=self.varDict['note-mode'][self.editedNote])
    modMnu.add_radiobutton(label='rest',   value='rest',   font=Sound.stdFont, variable=self.varDict['note-mode'][self.editedNote])
    modMnu.add_radiobutton(label='tie',    value='tie',    font=Sound.stdFont, variable=self.varDict['note-mode'][self.editedNote])
    modMnuBtn['menu'] = modMnu
    ## Ligne pour les facteurs p/r a l'enveloppe
    noEnvFrm = Frame(lblFrame, bg=Sound.fg, width=2)
    noEnvFrm.grid(row=rowNum, column=4, sticky='ns')
    ## Label pour la longueur de note
    dispCar = dispCar + [ 'note-length' ]
    rowNum = rowNum + 1
    lenLbl = Label(lblFrame, lblConf, text='Length:')
    lenLbl.grid(row=rowNum, column=0, sticky='w')
    ## Nouvelle variable pour la longueur sous forme de chaine
    self.noteLenVar = StringVar()
    ## Conversion de la valeur en numerique
    self.noteLenVar.set(self.varDict['note-length'][self.editedNote].get())
    self.setNoteLength()
    ## Frame pour la longueur
    lenFrm = Frame(lblFrame, bg=Sound.bg)
    lenFrm.grid(row=rowNum, column=1, columnspan=2, sticky='nswe')
    ## Menu pour la longueur
    lenMnuBtn = Menubutton(lenFrm, bg=Sound.bg, fg=Sound.fg, font=Sound.stdFont, relief=RAISED, \
                           textvariable=self.noteLenVar)
    lenMnuBtn.pack(side=LEFT)
    lenMnu = Menu(lenMnuBtn, bg=Sound.bg, fg=Sound.fg, tearoff=0)
    lenMnu.add_radiobutton(label='staccato', value='staccato', font=Sound.stdFont, variable=self.noteLenVar, command=self.setNoteLength)
    lenMnu.add_radiobutton(label='portato',  value='portato',  font=Sound.stdFont, variable=self.noteLenVar, command=self.setNoteLength)
    lenMnu.add_radiobutton(label='normal',   value='normal',   font=Sound.stdFont, variable=self.noteLenVar, command=self.setNoteLength)
    lenMnu.add_radiobutton(label='legato',   value='legato',   font=Sound.stdFont, variable=self.noteLenVar, command=self.setNoteLength)
    lenMnuBtn['menu'] = lenMnu
    ## Entree numerique pour la longueur
    lenEnt = Entry(lenFrm, entConf, textvariable=self.varDict['note-length'][self.editedNote], width=4)
    lenEnt.pack(side=LEFT, padx=4)
    ## Ligne pour les facteurs p/r a l'enveloppe
    noEnvFrm = Frame(lblFrame, bg=Sound.fg, width=2)
    noEnvFrm.grid(row=rowNum, column=4, sticky='ns')
    ## Label pour le portamento
    dispCar = dispCar + [ 'note-portamento' ]
    rowNum = rowNum + 1
    porLbl = Label(lblFrame, lblConf, text='Portamento:')
    porLbl.grid(row=rowNum, column=0, sticky='w')
    ## Checkbox pour le portamento
    porChk = Checkbutton(lblFrame, text='', variable=self.varDict['note-portamento'][self.editedNote], \
                         bg=Sound.bg, fg=Sound.fg, highlightthickness=0)
    porChk.grid(row=rowNum, column=1, columnspan=2, sticky='w')
    ## Ligne pour les facteurs p/r a l'enveloppe
    noEnvFrm = Frame(lblFrame, bg=Sound.fg, width=2)
    noEnvFrm.grid(row=rowNum, column=4, sticky='ns')
    ## Label pour l'accent
    dispCar = dispCar + [ 'note-accent' ]
    rowNum = rowNum + 1
    accLbl = Label(lblFrame, lblConf, text='Accent:')
    accLbl.grid(row=rowNum, column=0, sticky='w')
    ## Checkbox pour l'accent
    accChk = Checkbutton(lblFrame, text='', variable=self.varDict['note-accent'][self.editedNote], \
                         bg=Sound.bg, fg=Sound.fg, highlightthickness=0)
    accChk.grid(row=rowNum, column=1, columnspan=2, sticky='w')
    ## Ligne pour les facteurs p/r a l'enveloppe
    noEnvFrm = Frame(lblFrame, bg=Sound.fg, width=2)
    noEnvFrm.grid(row=rowNum, column=4, sticky='ns')
    ## Label pour le volume
    dispCar = dispCar + [ 'note-volume', 'note-volume-envmod' ]
    rowNum = rowNum + 1
    volLbl = Label(lblFrame, lblConf, text='Volume:')
    volLbl.grid(row=rowNum, column=0, sticky='w')
    ## Slider pour le volume
    volSld = HorizSlider(lblFrame, 0, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                         font=Sound.stdFont, activebackground=Sound.fg, width=4, \
                         variable=self.varDict['note-volume'][self.editedNote])
    volSld.grid(row=rowNum, column=1, columnspan=2, sticky='w')
    ## Slider pour le facteur de volume p/r a l'enveloppe
    envVolSld = HorizSlider(lblFrame, -100, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                            font=Sound.stdFont, activebackground=Sound.fg, width=6, \
                            variable=self.varDict['note-volume-envmod'][self.editedNote])
    envVolSld.grid(row=rowNum, column=3, columnspan=2, sticky='w')
    ## Label pour le cutoff
    dispCar = dispCar + [ 'note-cutoff', 'note-cutoff-envmod' ]
    rowNum = rowNum + 1
    cutOffLbl = Label(lblFrame, lblConf, text='Cutoff incr.:')
    cutOffLbl.grid(row=rowNum, column=0, sticky='w')
    ## Slider pour le cutoff
    cutOffSld = HorizSlider(lblFrame, -1000, 1000, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                            font=Sound.stdFont, activebackground=Sound.fg, width=6, \
                            variable=self.varDict['note-cutoff'][self.editedNote])
    cutOffSld.grid(row=rowNum, column=1, columnspan=2, sticky='w')
    ## Slider pour le facteur de volume p/r a l'enveloppe
    envCutSld = HorizSlider(lblFrame, -100, 100, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                            font=Sound.stdFont, activebackground=Sound.fg, width=6, \
                            variable=self.varDict['note-cutoff-envmod'][self.editedNote])
    envCutSld.grid(row=rowNum, column=3, columnspan=2, sticky='w')
    ## Label pour la resonance
    dispCar = dispCar + [ 'note-resonance' ]
    rowNum = rowNum + 1
    resLbl = Label(lblFrame, lblConf, text='Reson. incr.:')
    resLbl.grid(row=rowNum, column=0, sticky='w')
    ## Slider pour la resonance
    resSld = HorizSlider(lblFrame, -10, 10, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                         font=Sound.stdFont, activebackground=Sound.fg, width=4, \
                         variable=self.varDict['note-resonance'][self.editedNote])
    resSld.grid(row=rowNum, column=1, columnspan=2, sticky='w')
    ## Ligne pour les facteurs p/r a l'enveloppe
    noEnvFrm = Frame(lblFrame, bg=Sound.fg, width=2)
    noEnvFrm.grid(row=rowNum, column=4, sticky='ns')
    ## Label pour l'enveloppe
    dispCar = dispCar + [ 'note-attack', 'note-decay', 'note-sustain', 'note-release' ]
    rowNum = rowNum + 1
    envLbl = Label(lblFrame, lblConf, text='Env. incr.:')
    envLbl.grid(row=rowNum, column=0, rowspan=3, sticky='w')
    ## Cadre pour l'enveloppe
    envFrm = Frame(lblFrame, bg=Sound.bg)
    envFrm.grid(row=rowNum, column=1, rowspan=3, columnspan=2, sticky='nswe')
    ## Slider pour l'attaque
    envASld = VertSlider(envFrm, -500, 500, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                         font=Sound.stdFont, activebackground=Sound.fg, width=5, \
                         variable=self.varDict['note-attack'][self.editedNote])
    envASld.pack(side=LEFT)
    ## Slider pour le decay
    envDSld = VertSlider(envFrm, -500, 500, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                         font=Sound.stdFont, activebackground=Sound.fg, width=5, \
                         variable=self.varDict['note-decay'][self.editedNote])
    envDSld.pack(side=LEFT)
    ## Slider pour le sustain
    envSSld = VertSlider(envFrm, -500, 500, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                         font=Sound.stdFont, activebackground=Sound.fg, width=5, \
                         variable=self.varDict['note-sustain'][self.editedNote])
    envSSld.pack(side=LEFT)
    ## Slider pour le release
    envRSld = VertSlider(envFrm, -1000, 1000, bg=Sound.bg, fg=Sound.fg, troughcolor=Sound.fg, \
                         font=Sound.stdFont, activebackground=Sound.fg, width=6, \
                         variable=self.varDict['note-release'][self.editedNote])
    envRSld.pack(side=LEFT)
    ## Label pour l'indicateur pour les facteurs
    envVLnkFrm = Frame(lblFrame, bg=Sound.fg, width=2)
    envVLnkFrm.grid(row=rowNum, column=4, sticky='ns')
    envHLnkFrm = Frame(lblFrame, bg=Sound.fg, height=2)
    envHLnkFrm.grid(row=rowNum + 1, column=3, sticky='we')
    envLnkLbl = Label(lblFrame, lblConf, text='Mod.')
    envLnkLbl.grid(row=rowNum + 1, column=4)

    ## Autres caracteristiques
    for k in Sound.car2varDict.keys():
      if not (k in dispCar) and Sound.car2varDict[k][1]:
        rowNum = rowNum + 1
        lbl = Label(lblFrame, text="%s" % k, bg=Sound.bg, fg=Sound.fg, font=Sound.boldFont, \
                    justify=LEFT, anchor=W)
        lbl.grid(row=rowNum, column=0, sticky='w')
        ent = Entry(lblFrame, entConf, textvariable=self.varDict[k][self.editedNote])
        ent.grid(row=rowNum, column=1, columnspan=2, sticky='w')


  ## METHODE EDIT.PREV:
  ## ------------------
  # Passe a la note precedente en edition
  
  def editPrev(self):
    self.editedNote = self.editedNote - 1
    if self.editedNote < 0: self.editedNote = 0
    self.buildEditNoteFrame()
    
    
  ## METHODE EDIT.NEXT:
  ## ------------------
  # Passe a la note suivante en edition
  
  def editNext(self):
    self.editedNote = self.editedNote + 1
    if self.editedNote >= len(self.varDict['note']):
      self.editedNote = len(self.varDict['note']) - 1
    self.buildEditNoteFrame()
    
    
  ## METHODE EDIT.NOTE:
  ## ------------------
  # Appelee par un double-clic souris dans le canvas pour les notes
  
  def editNote(self, event):
    ## Calcul du numero et de la hauteur de la note a afficher
    x = self.notesCnv.canvasx(event.x)
    y = self.notesCnv.canvasy(event.y)
    noteNum = round(x / Sound.notesHDist)
    noteLine = 1 + round((y - Sound.notesVDist / 2.0) / Sound.notesVDist)
    ## Construction du dialogue d'edition
    self.editedNote = int(noteNum) - 1
    self.editDialog = Toplevel()
    self.editDialog.title("Edit note")
    ## Construction du cadre des caracteristiques
    self.buildEditNoteFrame()
    ## Cadre pour les boutons et boutons
    btnFrame = Frame(self.editDialog, bg=Sound.bg)
    btnFrame.pack(side=BOTTOM, fill=X, expand=1)
    self.prevBtnImg = BitmapImage(data=ButtonImages.prevData, foreground=Sound.btnClr)
    prevBtn = Button(btnFrame, image=self.prevBtnImg, bg=Sound.bg, command=self.editPrev, bd=0, highlightthickness=0)
    prevBtn.pack(side=LEFT)
    self.nextBtnImg = BitmapImage(data=ButtonImages.nextData, foreground=Sound.btnClr)
    nextBtn = Button(btnFrame, image=self.nextBtnImg, bg=Sound.bg, command=self.editNext, bd=0, highlightthickness=0)
    nextBtn.pack(side=LEFT, padx=12)
    self.closeBtnImg = BitmapImage(data=ButtonImages.closeData, foreground=Sound.btnClr)
    closeBtn = Button(btnFrame, image=self.closeBtnImg, bg=Sound.bg, command=self.editDialog.destroy, bd=0, highlightthickness=0)
    closeBtn.pack(side=RIGHT)
    
    
  ## METHODE PLAY.SOUND:
  ## -------------------
  # Joue le son
  
  def playSound(self):
    ## Construction de fichier temporaire pour le son et sa description
    temp3MUFile = '/tmp/tk3MU-%s.3MU' % os.getpid()
    tempSndFile = '/tmp/tk3MU-%s.snd' % os.getpid()
    ## Sauvegarde du son dans ce fichier
    self.dump(temp3MUFile)
    ## Construction et lancement de la commande de traitement et de jeu
    cmd = "3MU %s -o %s 2>&1; playsample %s 2>&1" % (temp3MUFile, tempSndFile, tempSndFile)
    cmdOutput = "=== Command returned the following output:\n"
    p = os.popen(cmd, 'r')
    for l in p.readlines():
      cmdOutput = cmdOutput + l
    p.close()
    ## Affichage du retour de la commande
    showinfo("Play command output", cmdOutput)
    ## Menage...
    try:
      os.remove(temp3MUFile)
      os.remove(tempSndFile)
    except:
      pass
  
  
  ## METHODES DE TRANSPOSITION
  ## -------------------------
  
  def transUp(self):
    transDict = {'c':'c#', 'c#':'d', 'db':'d', 'd':'d#', 'd#':'e', 'eb':'e', 'e':'f', 'e#':'f#', 'fb':'f', \
                 'f':'f#', 'f#':'g', 'gb':'g', 'g':'g#', 'g#':'a', 'ab':'a', 'a':'bb', 'a#':'b', 'bb':'b', \
                 'b':'C', 'b#':'C#'}
    for n in self.varDict['note']:
      noteName, modifier, oct = self.splitNote(n.get())
      newNote = transDict[noteName + modifier]
      if newNote[0] == 'C':
        newNote = 'c' + newNote[1:]
        oct = oct + 1
      n.set('%s%d' % (newNote, oct))
    self.drawNotes()
    
  def transDown(self):
    transDict = {'c':'B', 'c#':'c', 'db':'c', 'd':'c#', 'd#':'d', 'eb':'d', 'e':'eb', 'e#':'e', 'fb':'e', \
                 'f':'e', 'f#':'f', 'gb':'f', 'g':'f#', 'g#':'g', 'ab':'g', 'a':'ab', 'a#':'a', 'bb':'a', \
                 'b':'bb', 'b#':'b'}
    for n in self.varDict['note']:
      noteName, modifier, oct = self.splitNote(n.get())
      newNote = transDict[noteName + modifier]
      if newNote[0] == 'B':
        newNote = 'b' + newNote[1:]
        oct = oct - 1
      n.set('%s%d' % (newNote, oct))
    self.drawNotes()

  def transOctUp(self):
    for n in self.varDict['note']:
      noteName, modifier, oct = self.splitNote(n.get())
      n.set('%s%s%d' % (noteName, modifier, oct + 1))
    self.drawNotes()
  
  def transOctDown(self):
    for n in self.varDict['note']:
      noteName, modifier, oct = self.splitNote(n.get())
      n.set('%s%s%d' % (noteName, modifier, oct - 1))
    self.drawNotes()
    
