TwilightRain | AI写歌,怒怼老资历MIDI

AI写歌,怒怼老资历MIDI

约 3050 字 · 阅读约 8 分钟

有天突发奇想,能不能让AI直接给我写一首MIDI曲子?这个念头本身平平无奇,真正魔幻的是接下来一整个下午的连环事件。装技能,写歌,发现bug,给开源项目发issue,从网上下了一个老掉牙的MIDI文件,然后被那份陈年数据坑到怀疑人生。最后居然全都解决了,忍不住写下来纪念一下。


一,装技能,16次安装量的小众神作

需求很直接,AI帮我生成MIDI歌曲文件。我本地装了27个技能,没一个干这活的,于是去技能商店搜“midi”。

搜索结果挺热闹,但点开一看,全是挂羊头卖狗肉的。

真正对口的只有一个,tubone24/midi-agent-skill@midi-generation。安装量16,冷门中的冷门,但描述写的就是“用GM乐器和乐理生成MIDI文件”。工作方式是本地Python脚本加midiutil库生成.mid文件,免费,不用API。

按惯例先过一遍安全审查,SKILL.md内容干净,四个脚本通读一遍没啥恶意,requirements.txt里只有midiutil。装!

冒烟测试一次过,三轨多乐器的小样,228字节,跑起来了。


二,写ZUN歌,1分20秒

G:\Downloads里躺着一份主旋律.txt,内容写的是「ZUN节」,车万里那段经典旋律。简谱长这样,-6 2 3 5 3 2循环三遍,再加个-7 1 -7 -5的尾巴。

第一版用的C大调,120BPM,大钢琴两轨(右手旋律,左手分解和弦),引子加主题四遍加尾声,总共40小节,正好80秒。拿mido库验证时长,80.0秒,完美。

然后我开始迭代,翻倍速度到240BPM,40秒播完,再把最后10秒剪到中间当bridge。就是从这一步开始,事情不对劲了。


三,踩坑,休止符被吞,旋律凭空短了10秒

变速重排之后验证音轨,发现旋律轨30秒就播完了,伴奏轨却还在响。最后10秒只有伴奏孤零零地撑着场面。

排查下来,是技能的一个隐蔽bug,它的Note对象只有pitchduration两个字段,生成脚本里音符时间是靠time += duration累加出来的,它根本没有表达休止符的能力。AI作曲的时候跳过休止拍,所有音符就全往前挪了,休止拍被吞掉,旋律整体前移。

第一版其实就有这毛病,但那会儿我只验证了总时长,没细听。80秒的文件,旋律早就结束了,我只听了开头觉得还不错,愣是没发现,直到重排结构时才暴露出来。

解决办法很简单,给技能打了个本地补丁,Note增加一个可选字段time,用来指定绝对拍位置,向后兼容。修完重新生成,各轨终于对位了。


四,迭代,从两轨钢琴到六轨小乐队

第一版只是开始。接下来我折腾了好几轮,

变速加bridge重排。 想听快点,BPM 直接翻倍到 240,让80秒缩到40秒,又嫌结构平淡,再把最后10秒剪到中间当bridge,前20秒铺垫,中间10秒对比段,后面10秒接回主题,三段式结构有了,不过,也顺便埋了上一节那个大坑。

补终止。 剪完发现结尾停在D4上,悬在半空不落地。补了个G-C终止,G和弦分解上行,C琶音上滚,C5长音收尾,四秒搞定。

加音色。 纯钢琴太素了。我加了弦乐群铺和声长音,跟和弦走根音和五音,不抢旋律频段。又加了钢片琴在高八度点缀,只在bridge和终止段出现,带点空灵的味道。四轨了。

换军鼓,加bass。 钢片琴点缀完,总觉得还缺节奏层。换成军鼓,结果发现技能根本没有打击乐通道的概念,它的脚本会特意跳过GM标准的9号通道(打击乐专用)。于是第二次给技能打补丁,Note增加可选channel字段,指定通道就覆盖自动分配。顺手捡了个便宜,军鼓的MIDI音号是38,正好等于音名D2,填"D2"就行,不用改解析器。bass也补上了,根音跟和弦走,低音区撑住。

小号回归。 30秒处小号进,主旋律整体高八度再吹一遍,明亮的音色接回主题,跟钢片琴在终止段撞出一个八度齐奏的亮丽结尾。bass再往后压到20秒才进,让每个声部错峰登场。

最终定格在六轨,大钢琴旋律,大钢琴分解,弦乐pad,军鼓,bass,小号,44秒,240BPM。每一版我都用mido实测验证,时长,音符数,音轨位置全对得上才收工。诺,这里。

ZUN_Festival_Ensemble_Full.mid


五,发issue,冷门项目也有被认真对待的一天

修完顺手想,这个bug对原项目也是有用的,写个issue报上去吧。

按《提问的智慧》那套流程来,先查仓库有没有现成issue,其实没有,25颗星的小仓库很安静。然后套六要点模板,目标,环境,报错,已做功课,复现步骤,实际与预期。核心事实全是实测数据,160拍的曲子,旋律轨55.5秒结束,伴奏轨79.5秒。附上修复diff,标注了“未验证,可开PR”。

英文项目就写英文。发完。


六,老MIDI的复仇,2秒起,噪音炸了

issue发完,我转头想继续玩MIDI,从网上下了一份众神眷恋的幻想乡。202秒,3个音轨,931加882个音符。看文件头,GBK中文曲名,乐器名写着“Microsoft GS波表软件合成器”,千禧年代初的。给老资历跪了。

mido一读,?!直接崩溃!?data byte must be in range 0..127

这可不是小问题。MIDI文件里出现了规范外的字节,严格解析器直接拒收。我用耳朵加PotPlayer试听,那噪音楼上张阿姨都听得见。

于是开始跟这份老文件斗智斗勇。先怀疑文件损坏,逐字节检查chunk边界,结构完好,三个音轨的长度字段和文件大小精确吻合。再怀疑音符数据损坏,我手动写了个容错解析器,音符流全干净。那到底在哪崩溃的?

答案藏在两个地方,

第一,track0有16个CC7音量消息,值全是0xFF(255)。 MIDI规范里CC值只允许0-127,255是非法的,严格解析器当场暴毙。微软波表播放器大概见多识广,默默钳位成127继续播。

第二,也是最精彩的,track2有19个CC64延音踏板消息,从1.74秒开始,值从85,86,87……一路递增到104,然后就没然后了,从头到尾没有释放。

延音踏板是开关量,半踏板最多也是固定值。85→104,每秒两三个地递增,这不是踏板,这是别的数据被错标成了踏板,大概率是音量或表情的渐变,被某个老工具映射到了CC64。

而我听到的“从2-3秒开始的大噪音”,正是从1.74秒踏板踩下开始的。track2的每一个音符都被无限延音,持续叠加,两秒之后就混成了一片,越积越浑浊。时间线严丝合缝。

修复倒简单,删掉那19个CC64,把CC7改成127,保存。严格模式读取通过,音符一个不少,时长一分不差。试播,Windows自带播放器,PotPlayer,全都正常了。


七,事后

一整天下来,其实只干了一件小事,让AI写了一首MIDI。但沿途捡到了这些,

哦对,还有一版修复好的众神眷恋的幻想乡 touhou_fixed.mid 躺在下载目录里。有缘人拿去吧,老曲子的修复版,免费。

评论