用python制作88键赛博钢琴(能用鼠标键盘进行弹奏)

用python制作88键赛博钢琴

前言

恭喜这位博主终于想起了自己的账号密码!

时光荏苒,转眼间已逾一年未曾在此留下墨香。尽管这一年间,博主投身于无尽的忙碌与挑战之中,但令人欣慰的是,那份初心与热情似乎并未因岁月的流转而有丝毫减退,依旧保持着与往昔相同的热情与活力。

提及趣事,前不久博主精心筹备,欲在女友生辰之际,以一份特别的礼物------一台37键的童趣钢琴,为她编织一段温馨的记忆。怎料,这份心意与紧随其后的七夕佳节完美邂逅,却因工作的突然召唤,让博主不得不带着遗憾踏上异乡的征途,错过了亲自弹奏《两只老虎》的温馨时刻。

望着视频中女友指尖跳跃,旋律悠扬,那份未能亲临现场的遗憾化作了创新的火花。博主灵机一动,决定跨越千山万水,用指尖下的键盘,在数字世界中续写音乐的浪漫。说干就干,经过一番不懈的努力与探索,几个小时后,一个别出心裁的"键盘钢琴"奇迹般地诞生了!

请允许我们一同见证这创意的结晶。

效果图

功能实现

使用一个JSON文件作为核心,来控制整体界面布局、每个键对应的mp3文件、简谱标识、键盘映射等。

使用PyQt5实现界面绘制。

使用pygame库播放音乐,会更加流畅、连贯。

使用keyboard实现键盘监控。

使用Thread多线程,防止pygame和PyQt5线程冲突

最终实现的功能很简单,鼠标点击或键盘敲击对应的键即可进行弹奏。

源代码

python 复制代码
import sys
import keyboard
import pygame
import json

from threading import Thread
from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QApplication, QLabel

# 读取数据文件
piano_key = json.load(open('JSON/piano_key.json', 'r', encoding='utf8'))


# 主窗口
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        # 获取桌面尺寸
        desktop = QApplication.desktop()
        screen_rect = desktop.screenGeometry()
        # 设置主窗口比例
        main_width = int(screen_rect.width() * 0.9)
        main_height = int(screen_rect.height() * 0.4)
        self.resize(main_width, main_height)
        # 固定窗口大小
        self.setFixedSize(self.width(), self.height())
        # 窗口居中
        self.move((screen_rect.width() - main_width) // 2, (screen_rect.height() - main_height) // 2)

        # 状态栏和标题
        self.status = self.statusBar()
        self.status.showMessage('不是88键买不起,而是赛博钢琴更有性价比!')
        self.setWindowTitle('赛博钢琴')

        # 创建容器存放琴键
        container = QWidget(self)
        self.setCentralWidget(container)

        # 遍历查询黑白键的数量,用于计算每个键宽度
        black_key_num = sum(1 for key in piano_key if 's' in key['sound'])
        white_key_num = len(piano_key) - black_key_num

        self.buttons = []
        button_width = main_width / white_key_num
        white_key_index = 0

        for index, key in enumerate(piano_key):
            button = QPushButton(container)
            button.setObjectName(key['sound'])
            button.clicked.connect(self.on_button_clicked)
            self.set_button_style(button, 's' in key['sound'])

            if 's' in key['sound']:
                button.resize(button_width * 0.8, main_height * 0.6)
                button.move((white_key_index - 1) * button_width + button_width * 0.6, 0)
                button.raise_()
            else:
                button.resize(button_width, main_height)
                button.move(white_key_index * button_width, 0)
                button.lower()
                white_key_index += 1

                self.add_label(container, key, white_key_index, button_width, main_height)

            self.buttons.append(button)

    # 匹配并添加label
    def add_label(self, container, key, white_key_index, button_width, main_height):
        label_map = {
            'a': '6', 'A': '6',
            'b': '7', 'B': '7',
            'c': '1', 'C': '1',
            'd': '2', 'D': '2',
            'e': '3', 'E': '3',
            'f': '4', 'F': '4',
            'g': '5', 'G': '5'
        }
        label_text = label_map.get(key['sound'][0], '') + f"\n{key['key']}"
        label = QLabel(label_text, container)
        label.move(white_key_index * button_width - button_width * 0.5, main_height - 60)

    # 初始化黑白键样式
    def set_button_style(self, button, is_black_key):
        if is_black_key:
            button.setStyleSheet("""
                QPushButton {
                    background-color: black;
                    color: white;
                    border: 1px solid black;
                    padding: 0;
                    margin: 0;
                    text-align: center;
                }
                QPushButton::hover {
                    background-color: lightgray;
                }
                QPushButton:pressed {
                    background-color: gray;
                }
            """)
        else:
            button.setStyleSheet("""
                QPushButton {
                    background-color: white;
                    color: black;
                    border: 1px solid black;
                    padding: 0;
                    margin: 0;
                    text-align: center;
                }
                QPushButton::hover {
                    background-color: lightgray;
                }
                QPushButton:pressed {
                    background-color: gray;
                }
            """)

    # 键盘按下改变样式
    def change_button_color(self, index):
        self.buttons[index].setStyleSheet("background-color: gray;")

    # 抬起后恢复样式
    def release_button_color(self, index, is_black_key):
        self.set_button_style(self.buttons[index], is_black_key)

    # 鼠标点击播放
    def on_button_clicked(self):
        button = self.sender()
        pygame.mixer.Sound('MP3/' + button.objectName()).play()


# 初始化 PyQt 应用
app = QApplication(sys.argv)
# 实例化窗口
form = MainWindow()


# 键盘按下触发
def on_action(event):
    try:
        sound = next(item['sound'] for item in piano_key if item['key'] == event.name)
        index = next(index for index, item in enumerate(piano_key) if item['key'] == event.name)

        if event.event_type == keyboard.KEY_DOWN:
            pygame.mixer.Sound('MP3/' + sound).play()
            form.change_button_color(index)
        elif event.event_type == keyboard.KEY_UP:
            form.release_button_color(index, 's' in sound)

    except StopIteration:
        print(f"No sound file found for key: {event.name}")


# 键盘监听
def start_keyboard_listener():
    keyboard.hook(on_action)
    keyboard.wait()


def main():
    # 显示窗口
    form.show()
    # 初始化 Pygame 混音器
    pygame.mixer.init()
    # 启动键盘监听线程
    listener_thread = Thread(target=start_keyboard_listener)
    listener_thread.daemon = True
    listener_thread.start()
    # 进入事件循环
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

未来功能扩展

1.自定义功能:用户可以自定义每个琴键对应的键盘值,并保存,这也是我使用JSON文件控制整体的原因。

2.丰富标识:目前琴键上只有简谱的标识,后续会添加Do、Ra、C4、D4等标识。

3.自动弹奏:用户可以以某种方式将谱子录入或导入,程序根据谱子自动弹奏。

4.边弹边记:开启后,接下来的一段弹奏会以乐谱的形式保存下来。

5.趣味玩法:可能会像节奏大师那样?

6.有奇思妙想的兄弟,可以评论区或私信告诉我,我可能会将它实现并放到下一篇博客中。

结束语

在创意与爱的交织下,这不仅仅是一段代码的展现,更是一次心灵的触碰与跨越。期待未来能在CSDN这片沃土上,继续播种灵感,收获更多温馨与惊喜。每一次的回归,都是新旅程的开始,愿与每一位读者共享知识的盛宴。

相关推荐
沉到海底去吧Go23 分钟前
【PDF识别改名】PDF指定区域OCR识别重命名工具使用教程和注意事项
python·pdf·ocr
面朝大海,春不暖,花不开27 分钟前
管理数据洪流:自动化处理与归档每日数据文件的策略与实践
运维·python·自动化
YYXZZ。。1 小时前
PyTorch——搭建小实战和Sequential的使用(7)
人工智能·pytorch·python
四川兔兔1 小时前
pytorch 与 张量的处理
人工智能·pytorch·python
AI蜗牛之家5 小时前
Qwen系列之Qwen3解读:最强开源模型的细节拆解
人工智能·python
whyeekkk5 小时前
python打卡第48天
开发语言·python
Eiceblue8 小时前
Python读取PDF:文本、图片与文档属性
数据库·python·pdf
weixin_527550408 小时前
初级程序员入门指南
javascript·python·算法
程序员的世界你不懂8 小时前
Appium+python自动化(十)- 元素定位
python·appium·自动化
CryptoPP9 小时前
使用WebSocket实时获取印度股票数据源(无调用次数限制)实战
后端·python·websocket·网络协议·区块链