zoukankan      html  css  js  c++  java
  • x01.piano: 钢琴练习

    钢琴练习,只需键盘即可,但添加音阶(scale),和弦(chord),和弦进行(chord progression)的处理,五线谱(score)的显示,似乎也还不错。本以为 tkinter 的界面编程偏弱,但通过学习,发现其快速强大,丝毫不弱!

    1. 效果图

            

    2. 代码

    import os
    import sys
    import tkinter as tk
    from tkinter import colorchooser, filedialog, messagebox
    
    # 为引用 utils,在 site-packages 目录下新建 mypath.pth 文件,
    # 添加所需导入模块的目录路径, 如 ‘x01.lab/py/’ 所在路径。
    import utils
    
    sys.path.append(utils.R.CurrentDir)
    
    from piano.core import PianoFrame, R
    
    class MainWindow(tk.Tk):
        Title = 'x01.piano'
        
        def __init__(self):
            super().__init__()
            self.title(self.Title)
            utils.R.win_center(self,w=R.WinWidth, h=R.WinHeight)
            self.resizable(False, False)
    
            self.menu = tk.Menu(self)
            utils.R.generate_menus(self,['file', 'help'])
            self.configure(menu=self.menu)
    
            self.piano_frame = PianoFrame(self, width=R.WinWidth, height=R.WinHeight)
            self.piano_frame.pack()
    
        def file_quit(self):
            self.destroy()
    
       
    
    if __name__ == "__main__": 
        win = MainWindow()
        win.mainloop()
    main.py
    import json
    import os
    import sys
    import time
    import tkinter as tk
    import tkinter.ttk as ttk
    from collections import OrderedDict
    from tkinter import colorchooser, filedialog, messagebox
    import itertools
    from functools import partial 
    
    import simpleaudio
    
    import utils
    from _thread import start_new_thread
    
    
    class R:
        WinWidth = 560
        ModeHeight = 50
        ScoreHeight = 110
        ControlHeight = 100
        KeyHeight = 160
        WinHeight = ModeHeight + ControlHeight + KeyHeight + ScoreHeight
    
        Choices = ['Scales', 'Chords', 'Chord Progressions']
        ImagePath = os.path.join(utils.R.CurrentDir, 'piano/img/')
        SoundsPath = os.path.join(utils.R.CurrentDir, 'piano/sounds/')
        JsonPath = os.path.join(utils.R.CurrentDir, 'piano/json/')
    
        WhiteKeyNames = ['C1','D1', 'E1', 'F1', 'G1','A1', 'B1', 'C2','D2', 'E2', 'F2', 'G2','A2', 'B2']
        BlackKeyNames = ['C#1', 'D#1', 'F#1', 'G#1', 'A#1', 'C#2', 'D#2', 'F#2', 'G#2', 'A#2']
        WhiteKeyXCoordinates = [0,40, 80,120, 160, 200, 240,280, 320, 360, 400, 440, 480,520]
        BlackKeyXCoordinates = [30,70,150,190, 230, 310, 350, 430,470, 510]
    
        AllKeys= ['C1','C#1','D1','D#1','E1','F1','F#1','G1','G#1','A1', 
                'A#1','B1', 'C2','C#2','D2','D#2','E2','F2','F#2','G2',
                'G#2','A2','A#2','B2']
        Keys = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']
        
        Romans = { 'I':0, 'II': 2, 'III':4, 'IV':5, 'V': 7, 'VI':9, 'VII': 11,
            'i':0, 'ii': 2, 'iii':4, 'iv':5, 'v': 7, 'vi':9, 'vii': 11}    
    
    
    class PianoFrame(tk.Frame):
        def __init__(self, master=None, cnf=None, **kw):
            super().__init__(master=master, cnf=cnf, **kw)
            self.master = master 
            self.images = {
                'black_key': tk.PhotoImage(file=os.path.join(R.ImagePath, 'black_key.gif')),
                'white_key': tk.PhotoImage(file=os.path.join(R.ImagePath, 'white_key.gif')),
                'black_key_pressed': tk.PhotoImage(file=os.path.join(R.ImagePath, 'black_key_pressed.gif')),
                'white_key_pressed': tk.PhotoImage(file=os.path.join(R.ImagePath, 'white_key_pressed.gif'))
            }
            self.player = AudioPlayer()
            self.keys = []
            self.highlight_keys = []
            self.progression_buttons = []
    
            self.scales = self.load_json_files(filename=os.path.join(R.JsonPath, 'scales.json'))
            self.chords = self.load_json_files(filename=os.path.join(R.JsonPath, 'chords.json'))
            self.progressions = self.load_json_files(filename=os.path.join(R.JsonPath, 'progressions.json'))
            
            self.create_mode_frame()
            self.create_score_frame()
            self.create_control_frame()
            self.create_key_frame()
            
            self.create_chords_frame()
            self.create_progression_frame()
            self.create_scales_frame()
    
            self.find_scale()
    
    
        def create_mode_frame(self):
            frame = tk.Frame(self, width=R.WinWidth, height=R.ModeHeight)
            frame.grid_propagate(False)
            mode_combox = ttk.Combobox(frame, values=R.Choices)
            mode_combox.bind('<<ComboboxSelected>>', self.mode_selected)
            mode_combox.current(0)
            mode_combox.grid()
            frame.grid(row=0,column=0)
            self.mode_combox = mode_combox
    
        def mode_selected(self, e=None):
            mode = self.mode_combox.get()
            if mode == 'Scales':
                self.show_scales_frame()
            elif mode == 'Chords':
                self.show_chords_frame()
            elif mode == 'Chord Progressions':
                self.show_progression_frame()
    
        def show_scales_frame(self):
            self.chords_frame.grid_remove()
            self.progression_frame.grid_remove()
            self.scales_frame.grid()
    
        def show_chords_frame(self):
            self.scales_frame.grid_remove()
            self.progression_frame.grid_remove()
            self.chords_frame.grid()
    
        def show_progression_frame(self):
            self.scales_frame.grid_remove()
            self.chords_frame.grid_remove()
            self.progression_frame.grid()
    
        def create_score_frame(self): 
            frame = tk.Frame(self, width=R.WinWidth, height=R.ScoreHeight)
            frame.grid_propagate(False)
            frame.grid(row=1,column=0)
            self.score_frame = frame 
            self.score_maker = ScoreMaker(self.score_frame)
    
        def create_control_frame(self): 
            frame = tk.Frame(self, width=R.WinWidth, height=R.ControlHeight)
            frame.grid_propagate(False)
            frame.grid(row=2,column=0)
            self.control_frame = frame
    
        def create_key_frame(self):
            frame = tk.Frame(self, width=R.WinWidth, height=R.KeyHeight, background='LavenderBlush2')
            frame.grid_propagate(False)
            tk.Label(frame, text='placeholder for key frame').grid()
            frame.grid(row=4,column=0, sticky='nsew')
            self.key_frame = frame 
            for i, key in enumerate(R.WhiteKeyNames):
                x = R.WhiteKeyXCoordinates[i]
                self.create_key(self.images['white_key'], key, x)
            for i, key in enumerate(R.BlackKeyNames):
                x = R.BlackKeyXCoordinates[i]
                self.create_key(self.images['black_key'], key, x)
    
        def create_key(self, image, key, x):
            label = tk.Label(self.key_frame, image=image, border=0)
            label.place(x=x, y=0)
            label.name = key 
            label.bind('<Button-1>', self.key_pressed)
            label.bind('<ButtonRelease-1>', self.key_released)
            self.keys.append(label)
            return label 
    
        def key_pressed(self, e=None):
            self.player.play_note(e.widget.name)
            if len(e.widget.name) == 2:
                img = self.images['white_key_pressed']        
            elif len(e.widget.name) == 3:
                img = self.images['black_key_pressed']
            e.widget.config(image=img)
    
        def key_released(self, e=None): 
            if len(e.widget.name) == 2:
                img = self.images['white_key']        
            elif len(e.widget.name) == 3:
                img = self.images['black_key']
            e.widget.config(image=img)
    
        def create_scales_frame(self): 
            frame = tk.Frame(self.control_frame, width=R.WinWidth, height=R.ControlHeight)
            tk.Label(frame, text='Select scale').grid(row=0,column=1,stick='w',padx=10,pady=1)
            scale_combox = ttk.Combobox(frame, values=[k for k in self.scales.keys()])
            scale_combox.current(0)
            scale_combox.bind('<<ComboboxSelected>>', self.scale_changed)
            scale_combox.grid(row=1, column=1, sticky='e', padx=10, pady=10)
            tk.Label(frame, text='In the key of').grid(row=0, column=2, sticky='w', padx=10, pady=1)
            scale_key_combox = ttk.Combobox(frame, values=[k for k in R.Keys])
            scale_key_combox.current(0)
            scale_key_combox.bind('<<ComboboxSelected>>', self.scale_key_changed)
            scale_key_combox.grid(row=1, column=2, sticky='e', padx=10, pady=10)
            frame.grid(row=1,column=0, sticky='nsew')
            self.scales_frame = frame 
            self.scale_combox = scale_combox
            self.scale_key_combox = scale_key_combox
    
        def scale_changed(self, e=None):
            self.remove_all_key_highlight()
            self.find_scale(e)
    
        def scale_key_changed(self, e=None):
            self.remove_all_key_highlight()
            self.find_scale(e)
    
        def remove_all_key_highlight(self):
            for key in self.highlight_keys:
                self.remove_key_highlight(key)
            self.highlight_keys = []
    
        def remove_key_highlight(self, key):
            if len(key) == 2:
                img = self.images['white_key']
            elif len(key) == 3:
                img = self.images['black_key']
            for w in self.keys:
                if w.name == key:
                    w.configure(image=img)
    
        def find_scale(self, e=None):
            self.selected_scale = self.scale_combox.get()
            self.scale_selected_key = self.scale_key_combox.get()
            index = R.Keys.index(self.scale_selected_key)
            self.highlight_keys = [R.AllKeys[i+index] for i in self.scales[self.selected_scale]]
            self.highlight_list_of_keys(self.highlight_keys)
            self.player.play_scale_in_new_thread(self.highlight_keys)
            self.score_maker.draw_notes(self.highlight_keys)
    
        def highlight_list_of_keys(self, key_names):
            for key in key_names:
                self.highlight_key(key)
    
        def highlight_key(self, key):
            if len(key) == 2:
                img = self.images['white_key_pressed']
            elif len(key) == 3:
                img = self.images['black_key_pressed']
            for w in self.keys:
                if w.name == key:
                    w.configure(image=img)
    
    
        def create_chords_frame(self): 
            frame = tk.Frame(self.control_frame, width=R.WinWidth, height=R.ControlHeight)
            frame.grid_propagate(False)
            frame.grid(row=1,column=0, sticky='nsew')
            tk.Label(frame, text='Selected Chord').grid(row=0,column=1, sticky='w', padx=10, pady=1)
            chords_combox = ttk.Combobox(frame, values=[k for k in self.chords.keys()])
            chords_combox.current(0)
            chords_combox.bind('<<ComboboxSelected>>', self.chord_changed)
            chords_combox.grid(row=1, column=1, sticky='e', padx=10, pady=10)
            tk.Label(frame, text='in the key of').grid(row=0, column=2, sticky='w', padx=10, pady=1)
            chords_key_combox = ttk.Combobox(frame, values=[k for k in R.Keys])
            chords_key_combox.current(0)
            chords_key_combox.bind('<<ComboboxSelected>>', self.chords_key_changed)
            chords_key_combox.grid(row=1, column=2, sticky='e', padx=10, pady=10)
            self.chords_combox = chords_combox
            self.chords_key_combox = chords_key_combox
            self.chords_frame = frame 
    
        def chord_changed(self, e=None):
            self.remove_all_key_highlight()
            self.find_chord(e)
    
        def chords_key_changed(self, e=None):
            self.remove_all_key_highlight()
            self.find_chord(e)
    
        def find_chord(self, e=None):
            self.selected_chord = self.chords_combox.get()
            self.chords_selected_key = self.chords_key_combox.get()
            index = R.Keys.index(self.chords_selected_key)
            self.highlight_keys = [R.AllKeys[i+index] for i in self.chords[self.selected_chord]]
            self.score_maker.draw_chord(self.highlight_keys)
            self.highlight_list_of_keys(self.highlight_keys)
            self.player.play_chord_in_new_thread(self.highlight_keys)
    
        def create_progression_frame(self): 
            frame = tk.Frame(self.control_frame, width=R.WinWidth, height=R.ControlHeight)
            frame.grid_propagate(False)
            frame.grid(row=1,column=0, sticky='nsew')
            tk.Label(frame, text='Select Scales').grid(row=0, column=1, sticky='w', padx=10, pady=1)
            tk.Label(frame, text='Select Progression').grid(row=0,column=2,sticky='w', padx=10, pady=1)
            tk.Label(frame, text='in the key of').grid(row=0, column=3, sticky='w', padx=10, pady=1)
            progression_scale_combox = ttk.Combobox(frame, values=[k for k in self.progressions.keys()], width=18)
            progression_scale_combox.bind('<<ComboboxSelected>>', self.progression_scale_changed)
            progression_scale_combox.current(0)
            progression_scale_combox.grid(row=1, column=1, sticky='w', padx=10, pady=5)
            progression_combbox = ttk.Combobox(frame, values=[k for k in self.progressions['Major'].keys()], width=18)
            progression_combbox.bind('<<ComboboxSelected>>', self.progression_changed)
            progression_combbox.current(0)
            progression_combbox.grid(row=1, column=2, sticky='w', padx=10, pady=5)
            progression_key_combox = ttk.Combobox(frame, values=R.Keys, width=18)
            progression_key_combox.current(0)
            progression_key_combox.bind('<<ComboboxSelected>>', self.progression_key_changed)
            progression_key_combox.grid(row=1, column=3, sticky='w', padx=10, pady=5)
            self.progression_combbox = progression_combbox
            self.progression_key_combox = progression_key_combox
            self.progression_scale_combox = progression_scale_combox
            self.progression_frame = frame 
    
        def progression_changed(self, e=None): 
            self.show_progression_buttons()
    
        def progression_key_changed(self, e=None):
            self.show_progression_buttons()
    
        def progression_scale_changed(self, e=None): 
            selected_progression_scale = self.progression_scale_combox.get()
            progressions = [k for k in self.progressions[selected_progression_scale].keys()]
            self.progression_combbox['values'] = progressions
            self.progression_combbox.current(0)
            self.show_progression_buttons()
    
        def show_progression_buttons(self):
            self.destory_current_progression_buttons()
            selected_progression_scale = self.progression_scale_combox.get()
            selected_progression = self.progression_combbox.get().split('-')
            self.progression_buttons = []
            for i in range(len(selected_progression)):
                self.progression_buttons.append(tk.Button(self.progression_frame, text=selected_progression[i],
                    command=partial(self.progression_button_clicked, i)))
                sticky = ('w' if i == 0 else 'e')
                col = (i if i>1 else 1)
                self.progression_buttons[i].grid(row=2, column=col, sticky=sticky, padx=5)
    
        def progression_button_clicked(self, i):
            self.remove_all_key_highlight()
            selected_progression = self.progression_combbox.get().split('-')[i]
            if any(x.isupper() for x in selected_progression):
                selected_chord = 'Major'
            else:
                selected_chord = 'Minor'
            key_offset = R.Romans[selected_progression]
            selected_key = self.progression_key_combox.get()
            index = (R.Keys.index(selected_key) + key_offset) % 12
            self.highlight_keys = [R.AllKeys[j + index] for j in self.chords[selected_chord]]
            self.score_maker.draw_chord(self.highlight_keys)
            self.highlight_list_of_keys(self.highlight_keys)
            self.player.play_chord_in_new_thread(self.highlight_keys)
    
        def destory_current_progression_buttons(self): 
            for b in self.progression_buttons:
                b.destroy()
    
        def load_json_files(self, filename):
            with open(filename, 'r') as f:
                data = json.load(f, object_pairs_hook=OrderedDict)
                return data 
    
    class AudioPlayer:
        def play_note(self, note_name):
            wave = simpleaudio.WaveObject.from_wave_file(os.path.join(R.SoundsPath, note_name + '.wav'))
            wave.play()
    
        def play_scale(self, scale):
            for note in scale:
                self.play_note(note)
                time.sleep(0.5)
    
        def play_scale_in_new_thread(self, scale):
            start_new_thread(self.play_scale, (scale, ))
    
        def play_chord(self, chord):
            for note in chord:
                self.play_note(note)
    
        def play_chord_in_new_thread(self, chord):
            start_new_thread(self.play_chord, (chord, ))
    
    class ScoreMaker(tk.Frame):
        def  __init__(self, master=None):
            super().__init__(master=master)
            self.canvas = tk.Canvas(self.master, width=500, height=R.ScoreHeight)
            self.canvas.grid(row=0, column=1)
            self.master = master 
            master.update_idletasks()
            self.canvas_width = R.WinWidth
            self.sharp_image = tk.PhotoImage(file=os.path.join(R.ImagePath, 'sharp.gif'))
            self.treble_clef_image = tk.PhotoImage(file=os.path.join(R.ImagePath, 'treble_clef.gif'))
            self.x_counter = itertools.count(start=50, step=30)
    
        def _clean_score_sheet(self):
            self.x_counter = itertools.count(start=50, step=30)
            self.canvas.delete('all')
    
        def _create_treble_staff(self):
            self._draw_five_lines()
            self.canvas.create_image(10,20,image=self.treble_clef_image, anchor='nw')
    
        def draw_chord(self, chord):
            self._clean_score_sheet()
            self._create_treble_staff()
            for note in chord:
                self._draw_single_note(note, is_in_chord=True)
    
        def _draw_five_lines(self):
            w = self.canvas_width
            for i in range(5):
                self.canvas.create_line(0,40+i*10, w, 40+i*10, fill='#555')
    
        def draw_notes(self, notes):
            self._clean_score_sheet()
            self._create_treble_staff()
            for note in notes:
                self._draw_single_note(note)
    
        def _draw_single_note(self, note, is_in_chord=False):
            is_sharp = '#' in note 
            note = note.replace('#', '')
            radius = 9
            if is_in_chord:
                x = 75
            else:
                x = next(self.x_counter)
            i = R.WhiteKeyNames.index(note)
            y = 85 - 5*i 
            self.canvas.create_oval(x,y,x+radius, y+radius, fill='#555')
            if is_sharp:
                self.canvas.create_image(x-10, y, image=self.sharp_image, anchor='nw')
            if note == 'C1':
                self.canvas.create_line(x-5, 90, x+15, 90, fill='#555')
            elif note == 'G2':
                self.canvas.create_line(x-5, 35, x+15, 35, fill='#555')
            elif note == 'A2':
                self.canvas.create_line(x-5, 35, x+15, 35, fill='#555')
            elif note == 'B2':
                self.canvas.create_line(x-5, 35, x+15, 35, fill='#555')
                self.canvas.create_line(x-5, 25, x+15, 25, fill='#555')
    core.py

    3. 下载

    x01.lab/py/piano

  • 相关阅读:
    软件开发各列阶段需要达到的目标和生成的成果
    SQL Server 2005 Express附加数据库为“只读”的解决方法
    System.Web.HttpException: Request timed out.
    [收藏]javascript keycode大全
    MS SQL Server中的CONVERT日期格式化大全
    转贴 对于大型公司项目平台选择j2ee的几层认识(一)
    项目经理:做好项目开始阶段的九条经验(1) 项目 技术应用
    .Net Core 实现账户充值,还款,用户登录(WebApi的安全)
    JS如何通过月份,计算月份相差几个月
    .Net core Api后台获取数据,异步方法中,数据需采用Linq分页
  • 原文地址:https://www.cnblogs.com/china_x01/p/12986099.html
Copyright © 2011-2022 走看看