• 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

  • 相关阅读:
    exchange 2013 versions
    Outlook Web App Customization
    Using the FullCalendar Plugin in SharePoint 2013 Apps
    erlide的调试设置
    MySQLProxy
    Flex Socket安全策略<policyfilerequest/>及应对方法
    一些flex教程
    CentOS apache 配置
    C++与Flex之间socket通信policyfilerequest解决方案
    免费使用Adobe Flash Builder 4.5方法
  • 原文地址:https://www.cnblogs.com/china_x01/p/12986099.html
Copyright © 2020-2023  润新知