文章编写背景

玩了N久的Freepiano,碍于本人没有天赋,左右手一直没法协调。
于是,突然奇想,也是代码设计的思路:

用多线程的方式,开两个线程,然后通过按键模拟的方式,分别模拟左右手去演奏。觉得可行,于是开干

依赖

import time
import pyautogui
import keyboard
from threading import Thread

大致分析

python 窗口上加两个按钮 python多窗口按键同步_开发语言

按键模拟

首先要研究按键模拟,就是获取所有键盘的Mapping值。然后发现很多坑:
1、部分依赖不支持小键盘(键盘最右边)
2、部分按键不能模拟(menu,right crtl等)
3、键盘上的按键是不一样的,但是他们的mapping值是一样的(home page up 等)

这时候需要分两步走,获取所有按键对应的mapping值,然后配置一个Mapping 常量,我们可以借助下面的片段:

import keyboard
# keyboard.press_and_release('num lock')  # 小键盘锁键
def on_key_press(event):
    print("按键:", event.name)

keyboard.on_press(on_key_press)
keyboard.wait()

运行后,任意按键会打印对应的Mapping值,这里注意的是小键盘的锁切换后会不一样
然后得到一个mapping常量(按自身实际配置):

left_hand_mapping = {
    '1': 'q',
    '111': '8',
    '2': 'w',
    "222": "9",
    '3': 'e',
    '33': '3',
    '4': 'r',
    '44': '4',
    '5': 't',
    '55': '5',
    '6': 'y',
    '66': '6',
    '7': 'u',
    '77': '7',
    'P': ' '
}

right_hand_mapping = {
    '1': 'end',
    '2': 'down',
    '3': 'pagedown',
    '333': 'alt',
    '4': 'left',
    '5': 'clear',
    '555': "f2",
    '6': 'right',
    '666': 'f1',
    '666#': 'backspace',
    '7': 'home',
    'P': ' '
}

综合上面的坑,要对freepiano进行键位修改,这里的思路是放弃小键盘的锁键,还有HOME等功能键,见下图:

python 窗口上加两个按钮 python多窗口按键同步_小键盘_02


PS:这里要注意新按键的通道,因为左右手的力度是不一样的。

线程模拟

开始编码模拟左右手,因为左右手的演奏速度是不一样的,所以这里要做区分。
左手按键与松开:

def play_left_hand(melody):
    for note in melody:
        if note == "-":
            time.sleep(stop_note)
        press_key(note, 'left')
        time.sleep(note_duration_left)
        release_key_left(note)

def release_key_left(note):
    if note in left_hand_mapping:
        keyboard.release(left_hand_mapping[note])

右手按键与松开:

def play_right_hand(melody):
    for note in melody:
        if note == "-":
            time.sleep(stop_note)
        press_key(note, 'right')
        time.sleep(note_duration_right)
        release_key_right(note)
def release_key_right(note):
    if note in right_hand_mapping:
        keyboard.release(right_hand_mapping[note])

按键方法:

def press_key(note, hand):
    if hand == 'left' and note in left_hand_mapping:
        left_hand_key = left_hand_mapping[note]
        keyboard.press(left_hand_key)

    if hand == 'right' and note in right_hand_mapping:
        right_hand_key = right_hand_mapping[note]
        keyboard.press(right_hand_key)

乐谱设计

这里是最重要的,这里我们用两个列表分别存左右手的音符。具体字符需要根据实际mapping值来设计

left_hand_melody = ['33', '66', '77', '111', '-', '44', '66', '77', '111', '-', '55', '66', '222', '111', '77']
    right_hand_melody = ["666", "666", "666", "-", "666", "666#", "666#", "666#", "555", "555", "555", "-", "555",
                         "333", "333", "333", "333"]

这是一段某游戏登录前奏。乐谱地址:简谱

具体效果

效果不理想,休止音符和节奏没设计好,下版本更新。。。。
具体效果:


python演奏Freepiano(双手合奏)DEMO1



。TBD

完整代码

key_mapping.py:

left_hand_mapping = {
    '1': 'q',
    '111': '8',
    '2': 'w',
    "222": "9",
    '3': 'e',
    '33': '3',
    '4': 'r',
    '44': '4',
    '5': 't',
    '55': '5',
    '6': 'y',
    '66': '6',
    '7': 'u',
    '77': '7',
    'P': ' '
}

right_hand_mapping = {
    '1': 'end',
    '2': 'down',
    '3': 'pagedown',
    '333': 'alt',
    '4': 'left',
    '5': 'clear',
    '555': "f2",
    '6': 'right',
    '666': 'f1',
    '666#': 'backspace',
    '7': 'home',
    'P': ' '
}

piano.py

import time
import pyautogui
import keyboard
from threading import Thread
from key_mapping import *


def play_left_hand(melody):
    for note in melody:
        if note == "-":
            time.sleep(stop_note)
        press_key(note, 'left')
        time.sleep(note_duration_left)
        release_key_left(note)


def play_right_hand(melody):
    for note in melody:
        if note == "-":
            time.sleep(stop_note)
        press_key(note, 'right')
        time.sleep(note_duration_right)
        release_key_right(note)


def press_key(note, hand):
    if hand == 'left' and note in left_hand_mapping:
        left_hand_key = left_hand_mapping[note]
        keyboard.press(left_hand_key)

    if hand == 'right' and note in right_hand_mapping:
        right_hand_key = right_hand_mapping[note]
        keyboard.press(right_hand_key)


def release_key_left(note):
    if note in left_hand_mapping:
        keyboard.release(left_hand_mapping[note])


def release_key_right(note):
    if note in right_hand_mapping:
        keyboard.release(right_hand_mapping[note])


if __name__ == '__main__':
    repeat = True
    right_first = True
    left_hand_melody = ['33', '66', '77', '111', '-', '44', '66', '77', '111', '-', '55', '66', '222', '111', '77']
    right_hand_melody = ["666", "666", "666", "-", "666", "666#", "666#", "666#", "555", "555", "555", "-", "555",
                         "333", "333", "333", "333"]

    note_duration_left = 0.4  # 每个音符的持续时间(秒) 左手
    note_duration_right = 0.3  # 每个音符的持续时间(秒) 左手
    stop_note = 0.1  # 休止音符
    # 切换到FreePiano界面
    time.sleep(2)
    freepiano_window = pyautogui.getWindowsWithTitle("FreePiano")[0]
    freepiano_window.activate()
    # 简谱是否重复
    if repeat:
        left_hand_melody.append("-")
        left_hand_melody += left_hand_melody
        right_hand_melody.append("-")
        right_hand_melody += right_hand_melody
    # 启动左手伴奏和右手主旋律线程
    left_hand_thread = Thread(target=play_left_hand, args=(left_hand_melody,))
    right_hand_thread = Thread(target=play_right_hand, args=(right_hand_melody,))

    left_hand_thread.start()
    right_hand_thread.start()

    left_hand_thread.join()
    right_hand_thread.join()