五線譜を演奏する?プログラム続き。

  • 音符に尻尾がついた
  • 音符を右クリックすると長さが変更されるように(4→2→1→4…)
  • なんとなく4分音符を0.25秒に

一応音楽っぽいものが出るようになった。画像は「むすんでひらいて」
まだあまりにも汚いプログラムなんでもう少し可愛くしたい。

#!/usr/bin/env python

import Tkinter
import math
import sndPlay

class scorePlayer:
    def __init__(self):
        self.width = 800
        self.height = 200
        self.window = Tkinter.Tk()
        self.canvas = Tkinter.Canvas(self.window,bg='white',
                                     width=self.width,height=self.height)
        self.canvas.bind("<Motion>",self.motionCanvas)
        self.canvas.bind("<ButtonPress-1>",self.press1Canvas)
        self.canvas.bind("<ButtonPress-3>",self.press3Canvas)

        freqMap = [{'inc':3,'color':'white'},          # C
                   {'inc':5,'color':'white'},          # D
                   {'inc':7,'color':'black'},          # E
                   {'inc':8,'color':'white'},          # F
                   {'inc':10,'color':'black'},         # G
                   {'inc':12,'color':'white'},         # A
                   {'inc':14,'color':'black'},         # B
                   {'inc':15,'color':'white'},         # C
                   {'inc':17,'color':'black'},         # D
                   {'inc':19,'color':'white'},         # E
                   {'inc':20,'color':'black'},         # F
                   {'inc':22,'color':'white'},         # G
                   {'inc':24,'color':'white'},         # A
                   ]
        inc = math.pow(2,1.0/12)
        A = 440
        self.toneMap = [{'index':ii,'freq':math.pow(inc,freqMap[ii]['inc'])*A,
                         'color':freqMap[ii]['color']} for ii in range(len(freqMap))]
        self.toneMap.reverse()
        self.notes = []
        self.canvas.pack()
        self.currentNote = None
        self.mouseOverNote = None
        self.xStart = 40
        self.currentX = 0
        self.xInterval = 30
        self.scoreYInterval = 10
        self.noteWidth,self.noteHeight = 15,8

        self.paint()
        self.playButton = Tkinter.Button(self.window,text="play",
                                         command=self.play)
        self.clearButton = Tkinter.Button(self.window,text='clear',
                                          command=self.clear)
        self.playButton.pack(side=Tkinter.RIGHT)
        self.clearButton.pack(side=Tkinter.RIGHT)
        self.window.mainloop()

    def clear(self):
        for note in self.notes:
            self.canvas.delete(note['index']['bar'])
            self.canvas.delete(note['index']['body'])
        self.notes = []
        self.currentX = 0

    def itemConfig(self,id,arg):
        print id,arg
        self.canvas.itemconfigure(id,**arg)

    def play(self):
        s = sndPlay.sndPlay()
        s.A = 440
        for note in self.notes:
            print note
            s.addWave(note['tone']['freq'],1.0/note['length'])
            s.addWave(0,0.1)
        s.closeFile()

    def convertYtoTone(self,y):
        interval = self.scoreYInterval / 2
        for tone in self.toneMap:
            jj = tone['y']
            if jj-interval < y < jj+interval: return tone
        return None

    def repaintNote(self,note):
        barId,bodyId = note['index']['bar'],note['index']['body']
        length = note['length']
        if length==1:
            self.canvas.itemconfigure(barId,fill='white')
            self.canvas.itemconfigure(bodyId,fill='white',outline='black')
        elif length==2:
            self.canvas.itemconfigure(barId,fill='black')
            self.canvas.itemconfigure(bodyId,fill='white',outline='black')
        elif length==4:
            self.canvas.itemconfigure(barId,fill='black')
            self.canvas.itemconfigure(bodyId,fill='black',outline='black')
        

    def press3Canvas(self,event):
        x,y = event.x,event.y
        note = self.findNote(x,y)
        if note==None:
            return
        print note
        length = note['length']
        if length > 1:
            length = length / 2
        else:
            length = 4
        note['length'] = length
        self.repaintNote(note)
            
        
    def press1Canvas(self,event):
        x = event.x
        y = event.y
        note=self.findNote(x,y)
        if note!=None:
            print note
        else:
            width,height = self.noteWidth,self.noteHeight
            tone = self.convertYtoTone(event.y)
            if tone==None: return
            y = tone['y']
            x = self.currentX*self.xInterval+self.xStart
            barHeight = 30
            index = {'body':self.canvas.create_oval(x-width/2,y-height/2,
                                                    x+width/2,y+height/2,
                                                    fill='black',
                                                    width=2),
                     'bar':self.canvas.create_line(x+width/2,y,
                                                   x+width/2,y-barHeight,
                                                   width=1)}
            self.currentX = self.currentX + 1
            self.notes.append({'tone':tone,'index':index,'length':4})

    def findNote(self,x,y):
        for note in self.notes:
            barId = note['index']['bar']
            bodyId = note['index']['body']
            sx,sy,ex,ey = self.canvas.coords(bodyId)
            if sx <= x <= ex and sy <= y <= ey:
                return note
        
    def motionCanvas(self,event):
        x = event.x
        y = event.y
        note=self.findNote(x,y)
        if note!=None:
            if self.currentNote!=None:
                self.canvas.delete(self.currentNote)
                self.currentNote = None
            bodyId = note['index']['body']
            barId = note['index']['bar']
            self.canvas.itemconfigure(bodyId,fill='red',outline='red')
            self.canvas.itemconfigure(barId,fill='red')
            self.mouseOverNote = note
        else:
            if self.mouseOverNote!=None:
                self.repaintNote(self.mouseOverNote)
            width,height = self.noteWidth,self.noteHeight
            tone = self.convertYtoTone(event.y)
            if tone==None: return
            y = tone['y']
            if self.currentNote == None:
                self.currentNote = self.canvas.create_oval(x-width/2,y-height/2,
                                                           x+width/2,y+height/2,
                                                           outline='gray',
                                                           fill='gray',
                                                           width=2)
            else:
                self.canvas.coords(self.currentNote,
                                   x-width/2,y-height/2,
                                   x+width/2,y+height/2)

    def paint(self):
        xMargin = 30
        yMargin = 30
        scoreYInterval = self.scoreYInterval
        for ii in range(len(self.toneMap)):
            sx = xMargin
            ex = self.width - xMargin
            sy = ey = yMargin + scoreYInterval/2*ii
            self.canvas.create_line(sx,sy,ex,ey,fill=self.toneMap[ii]['color'],width=1)
            self.toneMap[ii]['y'] = sy

if __name__ == '__main__':
    scorePlayer()