ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

DeepSeek生成JSON转MIDI:AI作曲数据清洗与Python落地全流程 简介面向AI音乐创作者与有一定编程基础的技术开发者这份PDF围绕“DeepSeekMIDI”提供了一套从零生成原创音乐的完整实战方案。文档共26页先梳理AI作曲背景与DeepSeek能力特点再深入讲解MIDI文件头、轨道、事件结构及解析方法完整演示MIDI数据清洗、音符/节奏/和弦特征提取以及将音乐数据文本化后交给DeepSeek推理的流程。随后介绍作曲模型架构设计、训练循环、损失函数与优化器选择、超参数调优和结构/风格/情感等指标评估并给出音乐种子处理到MIDI文件保存的完整代码示例还配有游戏配乐、影视配乐、个性化推荐等实际应用展示便于读者按章节系统研读。资源为1个PDF文件压缩包约1.97MB便携清晰目前已有377人学习适合AI作曲入门到进阶的读者参考。1. AI作曲实战先分清DeepSeek写什么、MIDI管什么再谈原创把DeepSeek和MIDI放进同一个工作流里做AI作曲最大的好处是“创作可以推翻重来数据不会丢”。DeepSeek负责生成音乐素材——旋律动机、和弦走向、节奏型输出的是带语义的文本和结构化JSONMIDI负责把这些素材固化成任何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音符编号是604/4拍对应每小节4个四分音符BPM对应MIDI文件头部的tempo元事件。只要这些参数在提示词阶段确定下来后面JSON解析脚本就有了稳定的映射基础。转成MIDI后如果发现音域冲到C8不用怀疑代码有bug大概率是提示词里没限制音域。我还会刻意避免让DeepSeek一次写完一整首歌。常见做法是分段调用先让它生成前四小节的旋律和和弦再要求它基于前四小节续写后四小节。每段返回的JSON规模控制在20到50个音符之间解析和校验都更省事。Midjourney的“修图式”迭代思路在这个场景同样成立——每次只改一部分出错时能明确知道是哪一段的问题。2.2 要求DeepSeek输出JSON给schema不给散文读者如果用过DeepSeek的对话界面应该见过这种输出先写一段“好的这是一个关于爱情的小调”再给你一段像模像样的简谱最后补一句“希望你喜欢”。这种对话形态在聊天场景里很合理但在程序消费的场景里就是灾难。要让DeepSeek输出能被脚本直接解析的内容提示词里必须包含格式约束。我的标准写法是直接给出JSON字段schema并限定返回结构请为一段钢琴即兴曲输出JSON数组字段如下 noteMIDI音符编号范围0-127 start音符起始时间以四分音符为单位0表示第一拍 duration音符持续时长以四分音符为单位 velocity力度值范围1-127 channel音轨编号0为主旋律1为和弦2为低音 要求只返回JSON数组本身不要输出任何解释性文字。这里有一个容易被忽略的设计start和duration的单位要尽量用“四分音符”而不是“秒”。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的null和true不兼容兜底效果有限最简单的做法是直接让模型重新输出。3. 把DeepSeek的音符JSON转成MIDIPython midiutil落地最小脚本3.1 库选型midiutil 比 mido 更适合这个场景Python处理MIDI的库绕不开mido和midiutil这两个名字。mido是底层库几乎所有Python的MIDI读写都建立在它的消息模型上但它把MIDI事件原样暴露给了开发者音符开关、控制变更、SysEx每一步都要自己操作。想让一个音符出现在轨道上得自己算tick、自己把note_on和note_off配成对细节多且容易出错。midiutil则是高层封装把addNote、addChord、addTempo这些操作直接做成方法开发者只需要关心“哪一轨、从哪拍开始、持续多久、力度多大”。对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(numTracks4) # 给每条音轨都写入BPM避免播放器使用默认120BPM导致节奏错乱 for track in range(4): midi.addTempo(tracktrack, time0, tempobpm) for n in notes: track n.get(channel, 0) if track 0 or track 3: continue # 通道编号超出预期就跳过不阻断整个生成流程 midi.addNote( tracktrack, channeltrack, pitchn[note], timen[start], durationn[duration], volumen[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。关键参数有三处值得说明。第一处是addTempo的time0。这个参数是“从哪个位置开始设置速度”0表示从文件开头生效不应该省略。曾经有人把time误以为是BPM写出的文件打开后速度完全不对。第二处是addNote的time和duration直接使用了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这类确定性格式你的作品才真正属于你。本文还有配套的精品资源点击获取
返回列表