新闻详情

新闻详情

首页 / 资讯中心 / 详情

用 time.sleep 播 MIDI 踩的坑:一个 pyglet 钢琴卷帘的播放器折腾记

发布时间:2026/9/8 8:17:24来源:尧图网络
用 time.sleep 播 MIDI 踩的坑:一个 pyglet 钢琴卷帘的播放器折腾记
起因写过一个小型钢琴卷帘 DAWpyglet 渲染鼠标在网格上画音符、拖动、改长度播放头跟着走。界面部分一天就写完了真正耗掉大量时间的是把音符播出来这一步。播放器前后折腾了三版方案每一版都有各自的坑。这篇只讲播放器界面部分不展开。第一版mido 走 MIDI 端口最初的思路是标准 MIDI 路线mido 打开输出端口按节拍把音符发出去。当时的实现逻辑mido 枚举输出端口取第一个可用端口找不到端口时回退到mido.open_output()开虚拟端口起一个后台线程按start_beat排序音符time.sleep等到时间点发 note_on再 sleep 一个音符时长发 note_off代码结构大致是这样fornoteinsorted_notes:wait_timenote.start_beat*(self.tempo/1000000.0)elapsedtime.time()-start_timeifwait_timeelapsed:time.sleep(wait_time-elapsed)self.note_on(note.pitch,note.velocity)durationnote.duration*(self.tempo/1000000.0)time.sleep(duration)self.note_off(note.pitch)这一版踩了四个坑设备依赖没有 MIDI 输出设备的机器上整条路径直接哑火。虚拟端口只是能开不代表有声音出来sleep 精度time.sleep在 macOS 上精度只能到毫秒级且误差会沿时间轴累积。音符多、跨度长的时候后半段节奏明显对不上串行播放一个音符播完才播下一个同一时刻只能响一个音。两个音符 start_beat 相同后发的会直接把前一个覆盖掉和声根本做不出来停止收尾停止时遍历 128 个音高全发 note_off再对线程join(timeout1)。逻辑粗暴但这是当时能想到的最可靠收尾方式第二版pyglet 合成正弦波MIDI 路线被设备依赖卡死之后换成了纯软件合成pyglet 自带pyglet.media.synthesis.Sine可以直接生成指定频率的正弦波音源从 MIDI 音号换算频率播放。当时的实现按(note, duration)缓存生成的音源避免重复合成每触发一个音符新建一个pyglet.media.Player播放音量用 velocity / 127 映射这一版踩的坑更隐蔽synthesis.Sine在不同 pyglet 版本里 API 不稳定有的版本直接不可用。于是被迫写了完整兜底纯 Python 手算 ADSR 包络用array生成 int16 采样wave模块写进 BytesIO再pyglet.media.load(tone.wav, filebuffer)从文件对象加载每响一个音就 new 一个 Playeractive_players越积越多靠pyglet.clock.schedule_once延迟清理。长会话里内存只涨不降批量播放用pyglet.clock.schedule_once在 GUI 线程里调度窗口拖动卡一下音频就跟着卡一下兜底那段生成 WAV 的代码反而成了整个播放器最有价值的部分defenvelope_at(i:int)-float:ifattack_samples0andiattack_samples:returni/attack_samplesifdecay_samples0andiattack_samplesdecay_samples:t(i-attack_samples)/decay_samplesreturn1.0(sustain_level-1.0)*tifrelease_samples0andin_samples-release_samples:t(i-(n_samples-release_samples))/release_samplesreturnsustain_level*(1.0-t)returnsustain_level第三版放弃实时回到事件驱动最终版其实是个妥协不在后台线程里做时序而是把到点触发交给 pyglet 的事件循环。播放时按start_beat排序音符记录播放位置每帧更新里把start_beat play_position的音符逐个触发音符只播一次不排队、不调度代码简化成defupdate(self,dt:float):self.editor.play_positiondt*self.beats_per_secondwhileself.play_indexlen(self.notes_to_play):noteself.notes_to_play[self.play_index]ifnote.start_beatself.editor.play_position:self.preview_note(note)self.play_index1else:break这一版把时序问题从精确调度降级成了到点就响演示够用节奏对得上。但本质问题没解决GUI 线程承担音频调度事件循环一卡声音就卡。而且每个音符仍是独立 Player多声部依然做不了。结论Python 实时音频的边界三版下来结论很明确time.sleep做不了精确时序误差沿时间轴累积GUI 事件循环做音频调度卡顿会传导pyglet 的合成器 API 不稳定兜底代码比主路径还长设备依赖MIDI 端口会直接废掉整条播放路径Python 做演示级播放可以做工具级实时播放不行。这组限制正是后来把整个项目迁移到 Rust cpal ringbuf 的直接原因——音频线程独立、无锁队列传命令、回调里直接混音这些在 Python 生态里要么没有要么得靠一堆 C 扩展拼装。如果重来一次播放器部分会直接按独立音频线程 命令队列 回调混音的模型设计而不是先 MIDI、再合成、再事件驱动地绕三圈。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

PS5扩容实战:致态Ti600s 2TB加装与实测全攻略 2026/9/8 9:02:41

PS5扩容实战:致态Ti600s 2TB加装与实测全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
dnSpy实操指南:Unity无源码项目反编译、修改与调试全攻略 2026/9/8 9:02:41

dnSpy实操指南:Unity无源码项目反编译、修改与调试全攻略

简介:在.NET与Unity开发中,程序集逆向分析是一项重要的工程能力。以dnSpy为代表的托管代码反编译工具,能够将Mono后端编译生成的Assembly-CSharp.dll等托管程序集还原为可读的C#代码,并支持直接修改IL后重新保存。其核心原理在于.…

阅读更多 →
Repast Simphony 示例项目实战:从环境搭建到批量实验 2026/9/8 9:02:41

Repast Simphony 示例项目实战:从环境搭建到批量实验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
红包超发问题解析:分布式一致性下的金额强一致方案 2026/9/8 9:02:41

红包超发问题解析:分布式一致性下的金额强一致方案

之前在做红包类活动时,遇到过一件让产品和开发都冒冷汗的事:运营人员发起一个 200 元的红包活动,活动结束后一算账,实际发出去了 250 元。多出的 50 元不是预算问题,也不是人为操作失误,而是在并发领取场景…

阅读更多 →
nvidia-smi为何发现不了vLLM KV Cache泄漏?——从显存监控到逻辑内存池诊断 2026/9/8 9:02:41

nvidia-smi为何发现不了vLLM KV Cache泄漏?——从显存监控到逻辑内存池诊断

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Qt MinGW环境下从源码编译PCL:从Boost到VTK全流程指南 2026/9/8 8:59:40

Qt MinGW环境下从源码编译PCL:从Boost到VTK全流程指南

简介:面向Qt与MinGW环境下进行三维点云开发的工程师,这套资源将PCL及Boost、Eigen、FLANN、Qhull、VTK等依赖库的头文件统一打包,解决手工编译依赖链复杂、版本匹配难的问题,使开发者能直接在Qt Creator中完成点云读取、预处理、特…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞