news 2026/9/23 16:15:00

DeepSeek生成JSON转MIDI:AI作曲数据清洗与Python落地全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DeepSeek生成JSON转MIDI:AI作曲数据清洗与Python落地全流程

简介:面向AI音乐创作者与有一定编程基础的技术开发者,这份PDF围绕“DeepSeek+MIDI”提供了一套从零生成原创音乐的完整实战方案。文档共26页,先梳理AI作曲背景与DeepSeek能力特点,再深入讲解MIDI文件头、轨道、事件结构及解析方法,完整演示MIDI数据清洗、音符/节奏/和弦特征提取,以及将音乐数据文本化后交给DeepSeek推理的流程。随后介绍作曲模型架构设计、训练循环、损失函数与优化器选择、超参数调优和结构/风格/情感等指标评估,并给出音乐种子处理到MIDI文件保存的完整代码示例,还配有游戏配乐、影视配乐、个性化推荐等实际应用展示,便于读者按章节系统研读。资源为1个PDF文件,压缩包约1.97MB,便携清晰;目前已有377人学习,适合AI作曲入门到进阶的读者参考。

1. AI作曲实战:先分清DeepSeek写什么、MIDI管什么,再谈原创

把DeepSeek和MIDI放进同一个工作流里做AI作曲,最大的好处是“创作可以推翻重来,数据不会丢”。DeepSeek负责生成音乐素材——旋律动机、和弦走向、节奏型,输出的是带语义的文本和结构化JSON;MIDI负责把这些素材固化成任何DAW都能打开的标准文件。做出来的原创音乐,你可以继续在宿主软件里逐音符编辑,也可以导出成音频交给剪辑线。这个方向适合三类人:需要批量产出BGM的短视频从业者、不懂乐理但想让程序按自己要求出旋律的开发者、以及研究AI生成内容可控性的技术爱好者。很多人一上来就让AI直接生成音频,结果既没法精确改谱又没法换音色。我的建议是先让DeepSeek出谱子数据,再把谱子数据转成MIDI,最后用音源渲染成音频。

2. DeepSeek出谱、代码出MIDI:提示词与JSON格式的约束设计

2.1 先把调式、拍号、音域和BPM写进提示词,别让模型自由发挥

AI作曲最容易出现的问题不是“模型不会写歌”,而是“模型什么都懂但什么都不精”。DeepSeek在音乐理论上知道C大调、自然小调、属七和弦这些概念,但它不会主动替创作者考虑MIDI的底层约束。如果提示词里只有“唯美”“伤感”这种情绪词,模型返回的旋律可能音域横跨五个八度,拍号前后还不一致。因此我每次让DeepSeek生成音乐数据前,都会先给它划定一个可执行的创作边界。

边界通常用一行自然语言描述,例如:

请以C大调创作一段8小节、4/4拍的钢琴旋律,速度100BPM, 音域控制在C4到C6之间,前四小节平静,后四小节逐步加入和弦。

这句提示词的实际作用是把MIDI的底层参数提前锁死。C大调对应根音C,标准的MIDI音符编号是60;4/4拍对应每小节4个四分音符;BPM对应MIDI文件头部的tempo元事件。只要这些参数在提示词阶段确定下来,后面JSON解析脚本就有了稳定的映射基础。转成MIDI后如果发现音域冲到C8,不用怀疑代码有bug,大概率是提示词里没限制音域。

我还会刻意避免让DeepSeek一次写完一整首歌。常见做法是分段调用:先让它生成前四小节的旋律和和弦,再要求它基于前四小节续写后四小节。每段返回的JSON规模控制在20到50个音符之间,解析和校验都更省事。Midjourney的“修图式”迭代思路在这个场景同样成立——每次只改一部分,出错时能明确知道是哪一段的问题。

2.2 要求DeepSeek输出JSON:给schema,不给散文

读者如果用过DeepSeek的对话界面,应该见过这种输出:先写一段“好的,这是一个关于爱情的小调”,再给你一段像模像样的简谱,最后补一句“希望你喜欢”。这种对话形态在聊天场景里很合理,但在程序消费的场景里就是灾难。要让DeepSeek输出能被脚本直接解析的内容,提示词里必须包含格式约束。

我的标准写法是直接给出JSON字段schema,并限定返回结构:

请为一段钢琴即兴曲输出JSON数组,字段如下: note:MIDI音符编号,范围0-127 start:音符起始时间,以四分音符为单位,0表示第一拍 duration:音符持续时长,以四分音符为单位 velocity:力度值,范围1-127 channel:音轨编号,0为主旋律,1为和弦,2为低音 要求:只返回JSON数组本身,不要输出任何解释性文字。

这里有一个容易被忽略的设计:startduration的单位要尽量用“四分音符”而不是“秒”。MIDI文件底层有自己独立的tick计数体系,不同设备换算秒时会因为分辨率产生微小误差,而“四分音符”是MIDI生态里通用的中间表示。一个四分音符记为1,一个八分音符记为0.5,一个附点四分音符记为1.5,后续在脚本里乘以配置文件里的ticks_per_beat就能精确换算。我早期直接用秒做单位,换了个播放器就对不上拍子,改成四分音符制之后再也没有这种问题。

另一个细节是让DeepSeek标注和弦的转位形态。比如要求“C大调的二级和弦请写成Dm7/F或者中文注明F在低音”,否则模型大概率给出根音在最低位的原位和弦。连续几个原位和弦堆在一起,听感会非常“堵”,这也是AI生成音乐听感生硬的重要原因。JSON里加一个chord字段,方便后续脚本对旋律和和弦做音程校验。DeepSeek对这类结构化请求的响应质量,远高于泛泛的“帮我写一段和弦”。

2.3 DeepSeek返回了JSON之外的文字:清洗与校验的最小脚本

即便提示词里说了“只返回JSON数组”,DeepSeek偶尔还是会夹带私货,比如“以下是生成的JSON:”或者结尾补一句“祝您创作愉快”。这类文本放在对话里没问题,直接扔给json.loads就会当场抛异常。更麻烦的情况是模型返回的JSON里带注释、尾逗号,甚至把字段名从蛇形变成驼峰。这些东西不属于JSON标准,但确实会发生。

我应对的办法分两层。第一层是清洗:定位第一个[{,再截取最后一个]},把首尾杂质剪掉。第二层是校验:逐字段检查音符编号、力度、时值的合法性,不合法就直接报错而不是带着脏数据往下走。整个逻辑见下面的脚本。

import json def extract_json(llm_text: str) -> list: # 裁剪出模型输出中的JSON数组,忽略首尾的文字杂质 start = llm_text.find("[") end = llm_text.rfind("]") if start == -1 or end == -1 or end <= start: raise ValueError("未找到合法的JSON数组,需要让模型重新输出") return json.loads(llm_text[start:end + 1]) def validate_notes(notes: list) -> None: for i, n in enumerate(notes): # MIDI音符编号合法范围是0-127,超出这个范围说明模型乱写了 if n["note"] < 0 or n["note"] > 127: raise ValueError(f"第{i}个音符编号越界: {n['note']}") # 力度0通常表示静音或音符关闭,出现在生成结果里多半是异常 if n["velocity"] < 1 or n["velocity"] > 127: raise ValueError(f"第{i}个音符力度非法: {n['velocity']}") # 时值必须是正数,休止符应该用start间隔表达,而不是duration为0 if n["duration"] <= 0: raise ValueError(f"第{i}个音符时值非法: {n['duration']}") if __name__ == "__main__": raw_output = "好的,以下是JSON:\n[{\"note\":60,\"start\":0,\"duration\":1,\"velocity\":90,\"channel\":0}]" note_list = extract_json(raw_output) validate_notes(note_list) print(f"校验通过,共{len(note_list)}个音符")

这段代码的逻辑很直接:先把模型输出裁剪成合法的JSON数组,再对每个音符的编号、力度、时值做合法性检查。参数说明里值得注意的有三点。第一,find("[")找的是第一个左方括号,这样模型在数组前面说任何话都不会影响截取;rfind("]")找最后一个右方括号,保证拿到完整的数组结尾。第二,校验里故意不检查start字段的单调递增,因为DeepSeek可能生成多声部并行数据,硬性要求时间递增会误杀合法的和弦结构。如果要检查时间合理性,放到MIDI装配阶段做更合适。第三,json.loads失败时不要急着换ast.literal_eval,后者虽然能解析Python字面量,但对JSON的nulltrue不兼容,兜底效果有限,最简单的做法是直接让模型重新输出。

3. 把DeepSeek的音符JSON转成MIDI:Python + midiutil落地最小脚本

3.1 库选型:midiutil 比 mido 更适合这个场景

Python处理MIDI的库,绕不开mido和midiutil这两个名字。mido是底层库,几乎所有Python的MIDI读写都建立在它的消息模型上,但它把MIDI事件原样暴露给了开发者:音符开关、控制变更、SysEx,每一步都要自己操作。想让一个音符出现在轨道上,得自己算tick、自己把note_on和note_off配成对,细节多且容易出错。midiutil则是高层封装,把addNoteaddChordaddTempo这些操作直接做成方法,开发者只需要关心“哪一轨、从哪拍开始、持续多久、力度多大”。对DeepSeek生成JSON转MIDI这个场景来说,midiutil的开发效率明显更高。

但midiutil也有它的脾气。它的文档停留在“够用”的层面,很多细节比如addNote的time参数到底接受拍还是tick、默认的ticks_per_beat是多少,都要看源码确认。另外midiutil生成的MIDI文件在一些宿主软件里可能被识别为多轨但音色归零,需要自己手动指定Program Change事件。我自己用下来,最稳妥的组合是:midiutil负责写入,mido负责事后检查和修复。前者生成文件,后者读取文件验证音符数量和通道分配是否符合预期。

还有一点需要澄清:midiutil的addTempo必须写,不然MIDI播放器会采用默认的120BPM,跟DeepSeek生成数据时设定的速度对不上。虽然MIDI规范里tempo事件是可选的,但实际播放时大多数软件会强制一个默认速度,这直接导致AI音乐的听感节奏混乱。

3.2 转换脚本:四音轨、BPM、力度一次到位

下面的脚本把经过校验的音符列表写入标准MIDI文件。实现上创建了四条音轨,分别对应提示词里约定的主旋律、和弦、低音和备用轨,速度默认100BPM。

from midiutil import MIDIFile def build_midi(notes: list, bpm: int = 100, output: str = "output.mid") -> None: """ notes: DeepSeek输出的音符列表 每个元素包含 note/start/duration/velocity/channel 字段 """ midi = MIDIFile(numTracks=4) # 给每条音轨都写入BPM,避免播放器使用默认120BPM导致节奏错乱 for track in range(4): midi.addTempo(track=track, time=0, tempo=bpm) for n in notes: track = n.get("channel", 0) if track < 0 or track > 3: continue # 通道编号超出预期就跳过,不阻断整个生成流程 midi.addNote( track=track, channel=track, pitch=n["note"], time=n["start"], duration=n["duration"], volume=n["velocity"], ) with open(output, "wb") as f: midi.writeFile(f) if __name__ == "__main__": sample_notes = [ {"note": 60, "start": 0, "duration": 1, "velocity": 90, "channel": 0}, {"note": 64, "start": 1, "duration": 1, "velocity": 85, "channel": 0}, {"note": 67, "start": 2, "duration": 1, "velocity": 88, "channel": 0}, ] build_midi(sample_notes) print("MIDI文件已生成")

这段代码的职责是整个管线里最薄的一层,把清洗后的音符数据原样写入MIDI。关键参数有三处值得说明。

第一处是addTempotime=0。这个参数是“从哪个位置开始设置速度”,0表示从文件开头生效,不应该省略。曾经有人把time误以为是BPM,写出的文件打开后速度完全不对。第二处是addNotetimeduration直接使用了JSON里的四分音符数值。midiutil内部默认的ticks_per_beat是480,传入1会自动换算成480个tick,传入0.5则是八分音符的240个tick,这正是我们要求DeepSeek使用四分音符作为单位的原因。第三处是volume参数,它对应MIDI的velocity,中文叫力度而不是音量。力度影响采样音源的激振强度,钢琴音源里力度90和力度40的音色差异极其明显,DeepSeek生成的数据如果力度区间太窄,听起来就会像在敲电子琴。

3.3 生成后的自查方法:先把MIDI文件无损读回来

写完MIDI文件后,不要急着播放,我习惯先做一步“无损读回”的检查。所谓无损读回,就是直接解析这个MIDI文件,逐个核对音符数量、轨道、节拍和力度,确认文件内容跟JSON数据一致。这一轮检查能挡掉九成以上的低级错误,比如音轨顺序错乱、tick换算出错、力度被转成奇怪的数值。

用mido做这一步非常轻量:

from mido import MidiFile def inspect_midi(path: str) -> None: mid = MidiFile(path) print(f"文件格式: {mid.type}, 音轨数: {len(mid.tracks)}, 分辨率: {mid.ticks_per_beat}") for i, track in enumerate(mid.tracks): note_count = 0 for msg in track: # 统计每条音轨中真正出现的音符事件数量 if msg.type == "note_on" and msg.velocity > 0: note_count += 1 print(f" 音轨{i}: 音符{msg.note}, 力度{msg.velocity}, 时间{msg.time}") if note_count == 0: print(f" 音轨{i}: 没有任何音符,注意检查channel分配") if __name__ == "__main__": inspect_midi("output.mid")

这段检查脚本的作用是确认轨道分配是否跟预期一致。常见的问题是DeepSeek生成的JSON里,主旋律和和弦都写成了channel 0,导致转换后所有音符堆在第一条音轨上。如果检查脚本显示某条音轨音符数为0,而别的音轨被塞满了,就该回到提示词或者JSON清洗逻辑里,看看channel字段是不是在传输过程中丢了。

4. MIDI生成的避坑:从“没声音”到“没法听”的五个排查案例

4.1 现象:MIDI文件导入DAW后完全没声音

生成的MIDI文件能在播放器里跑,导入Cubase或FL Studio却没有任何声音,这是最典型的翻车场景。原因之一是MIDI文件只有音符数据,没有设置音色Program Change,宿主软件会默认使用0号音色也就是钢琴。理论上钢琴音色应该能出声,但很多DAW对多音轨MIDI的默认输出通道和音色做了特殊处理,导致某些通道静音。另一个更隐蔽的原因是力度值全为0,音符事件都在但被当作静音处理。

解决的办法分两步。第一步在提示词阶段就要求DeepSeek输出的velocity范围在70到110之间,避开极端值。第二步在转换脚本里,为每条音轨追加一个Program Change事件,显式指定音色编号。比如主旋律用钢琴Program 0,低音用贝斯Program 32,这样导入任何DAW都能复现设计意图。我早期不做音色指定,结果同样一段旋律在不同播放器里听出完全不同的味道,这属于AI作曲落地必须处理的问题。

4.2 现象:旋律、和弦和低音各放各的,合起来没法听

三个声部单独播放都没问题,叠在一起就打架,这是MIDI多轨创作的高频疑难杂症。原因通常不是音符之间的音程冲突,而是时间对齐失败。DeepSeek生成的JSON里,主旋律的start是0、1、2这种整数,但和弦的start可能是0.33、1.67这种非整数,说明模型把它理解的“琶音”直接写进了和弦的起始时间。单独听和弦本身是合理的,但主旋律和其他声部对不上拍,整个作品就显得乱。

解决方法是引入“时间量化”处理,把start值按设定的分辨率进行取整。常见做法是以0.25为一个量化格,把0.33取整为0.25或0.5。量化后各声部的拍点对齐,听感立刻变整齐。量化脚本建议放在JSON校验之后、写入MIDI之前,保持JSON清洗和MIDI写入这两个函数的单一职责。还有一个辅助技巧:在提示词里明确要求“和弦的每个音符必须共用相同的start值”,从源头减少时间错位。

4.3 现象:音符编号没有越界,但听起来音域穿透感太强

MIDI的合法音域是0到127,但人耳舒服的旋律区通常只在48到84之间。DeepSeek生成的JSON可能每个音符都在合法范围内,但跨度从36直接跳到96,听感上就是低音突然沉到底、高音突然炸出来。原因在于提示词里只约束了“合法范围”,没有约束“音乐性范围”。

解决办法是把音域约束直接写进验证逻辑:主旋律限定在60到84之间,低音限定在36到60之间,超过就直接报错或者做八度平移。八度平移是指把音符整体加减12,这样能保留旋律线形变,同时把音域拉回舒适区。这个策略值得收藏。

4.4 现象:和弦是正常的,但连续几个和弦糊成一片

和弦糊在一起通常有两个原因。第一个是缺少声部间的最小间隔时间,上个和弦的尾音还没结束,下个和弦的音符已经进来。第二个是低音区的音符过多,而且时值偏长,低频声波叠在一起产生混浊感。很多人以为这是混音问题,其实根源在生成阶段。

解决办法是在校验脚本里加一个和弦间隔检查:当前和弦的start值必须大于前一个和弦所有音符的最晚结束时间减去一个最小间隙量,比如0.05拍。不满足就把当前和弦整体后移。这个方法最能立竿见影,因为MIDI阶段解决了重叠问题,后面任何音源渲染都不会再糊。

4.5 现象:DeepSeek拒绝输出JSON,写了一段乐评

明明提示词要求只返回JSON数组,DeepSeek却输出了“这段旋律采用了五声音阶,富有东方韵味……”这类文字。原因是当前对话的上下文太长,模型注意力分散,格式约束被历史消息里的自然语言输出覆盖了。DeepSeek对长上下文的格式保持能力是有限度的,对话越久越容易跑偏。

解决办法有两个。第一个是新建会话,把格式约束放在最靠近输出的位置。第二个是在代码层加一个“重试机制”:如果extract_json找不到合法的JSON数组,就自动向DeepSeek发送一条“只返回JSON”的修正指令,最多重试两次。这算AI作曲中的玄学刀法——模型返回不稳定,那就让流程替模型兜底。我见过最严重的一回,是连续失败5次,后来把单次请求改成独立会话才稳定下来。

5. 进阶验证:主题变奏、素材库复用到MIDI转简谱

5.1 用DeepSeek做主题变奏:给定前句,续写后句

主题变奏是AI作曲里价值最高的玩法之一,因为MIDI素材可以被反复调用,与纯生成新旋律完全不同。操作方法是:把已经生成的前四小节JSON直接作为输入,要求DeepSeek基于这个主题写出变奏版本。

提示词可以这样写:

以下是主旋律JSON:<粘贴前四小节> 请生成它的变奏:节奏上使用八分音符和附点节奏, 旋律保持前四个音的核心音程,后四个音自由发挥。 只返回JSON数组。

这种方式的妙处在于,变奏和主题之间天然具有可对比的音乐关系,直接做双轨播放就能检验模型是否真的理解了音程结构,而不是简单地改变音高。如果变奏结果不理想,可以在JSON里手动调整几个关键音符,再让DeepSeek继续变奏——你反馈得越具体,模型修正得越准。

5.2 建立自己的MIDI素材库,彻底摆脱“每次都要提示词”的低效循环

当你用DeepSeek生成了一定数量的MIDI文件后,建议按照风格、速度、调式和情绪来建立素材库。这些MIDI本身是原创的,可以混合、剪切、拼接到不同项目里。素材库的维护可以直接把MIDI转成简谱或钢琴卷帘图作为索引,比听音频快得多。

5.3 用MIDI转简谱快速验证AI作品的结构

验证MIDI作品的最终手段是耳朵,但在成品验证之前,把MIDI转成简谱是效率最高的结构检查方法。我习惯的做法是把MIDI导入乐谱软件或者用Python库读取音符序列,按起始时间排列成一行行简谱,看看段落结构是否完整、终止式是否合理。有一个快速判断标准:最后一个小节的最后一个音符,应当落在主和弦的根音、三音或五音上,否则这段音乐的终止感会很差。我自己用这个标准淘汰了大概三成AI生成的结果,它们音符都对但就是“不像一首完整的曲子”。

如果以后DeepSeek推出直接推理音频的能力,MIDI和JSON之间的转换仍然有价值,因为数学化描述的音乐数据本身就能独立于生成模型存在。我现在生成的MIDI素材库已经积累了上百条原创乐句,每一段都可以再投入新项目继续加工,这比任何一次性的AI音频生成都更耐用。希望这套工作流能帮到你,实际上最大的心得是:不要跟模型要成品,跟模型要素材,剩下的把控交给MIDI这类确定性格式,你的作品才真正属于你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 16:13:44

SMS中文使用手册实战指南:从命令到排障的存储管理核心技巧

简介&#xff1a;这份《SMS中文使用手册》面向水利、水文、环境工程及地表水模拟领域的学习者与工程技术人员&#xff0c;尤其适合刚接触SMS软件、需要系统掌握操作流程的初、中级用户。手册以中文完整翻译了SMS地表水模拟系统的核心内容&#xff0c;从软件综述、界面布局讲起&…

作者头像 李华
网站建设 2026/9/23 16:13:16

从SEO到GEO的范式迁移:原理、差异与工程实践

一、为什么会出现范式迁移 过去二十年&#xff0c;企业获取线上流量的核心方式是SEO&#xff08;搜索引擎优化&#xff09;——通过优化网页&#xff0c;让自己在搜索引擎结果页中排名更靠前。 但2024年以来&#xff0c;随着大语言模型&#xff08;LLM&#xff09;的爆发&#…

作者头像 李华
网站建设 2026/9/23 16:12:51

AI为什么能处理超大Excel,却不消耗等量Token?

让AI处理十几万行Excel、生成上百MB的SQL&#xff0c;是否意味着模型必须"读完"全部内容&#xff0c;并消耗同等规模的Token&#xff1f;答案是否定的。关键在于&#xff1a;模型负责思考和编排&#xff0c;程序负责批量计算。01 | Token到底花在哪里Token可以简单理…

作者头像 李华
网站建设 2026/9/23 16:12:39

PCIe 4.0/5.0与NVMe SSD测试技术:协议、工具与实战

简介&#xff1a;这份白皮书面向从事PCIe Gen 4&5高速总线开发的芯片、模块、插卡与系统研发测试工程师&#xff0c;系统梳理协议层及以上的分析、诊断与测试工具选型思路&#xff0c;帮助解决Gen5总线问题定位、兼容性验证与测试环境搭建等实际难题。资源为单一PDF文档&am…

作者头像 李华
网站建设 2026/9/23 16:11:17

汽车制动系统故障诊断与维修:参数化排查刹车软硬抖

简介&#xff1a;这是一份主题聚焦汽车制动系统的毕业论文&#xff0c;完整梳理了制动系统的四大组成部分、盘式与鼓式制动器的结构差异及适用场景&#xff0c;并对液压制动系统的常见故障展开系统论述。文档以制动器安全系数、真空增压制动、双回路制动等关键技术为主线&#…

作者头像 李华
网站建设 2026/9/23 16:10:14

自适应模糊滑模控制实战:抖振抑制与鲁棒性提升

简介&#xff1a;本资源是一套面向自动控制领域高年级本科生、研究生及工程实践者的自适应模糊滑模控制系统MATLAB实现方案&#xff0c;聚焦于解决非线性、时变及存在参数不确定性系统的鲁棒控制问题&#xff0c;适用于机器人、电力电子、航空航天等对动态响应与抗扰性要求较高…

作者头像 李华