简介面向AI音乐创作开发者的实战指南聚焦DeepSeek与MIDI技术的融合应用系统讲解从MIDI数据采集、清洗、特征提取到模型架构设计、训练调优再到音乐参数生成与MIDI文件输出的完整链路。文档共26页以“理论—预处理—建模—生成—评估—应用”为脉络详细介绍了MIDI文件结构与解析、音符/节奏/和弦特征提取、DeepSeek输入所需的文本化表示与分词方法并给出训练循环、超参数调优、损失函数选择等关键实现细节以及游戏配乐、影视配乐、个性化音乐推荐等多个实战案例同时覆盖环境搭建、模型加载、输入处理与推理流程附有可迁移的完整代码示例各章节层层递进便于按需查阅。资源为单个PDF文件约1.97MB目录完整清晰目前已吸引377人学习下载适合具备一定编程基础、希望借助AI提升作曲效率的开发者参考帮助快速掌握AI作曲的实操方法。1. AI作曲实战DeepSeekMIDI这条路线值得认真做AI作曲实战这个标题最容易让人误会的地方在于它既不需要你会乐理也不需要你懂音频合成但你必须先接受一个反直觉的结论DeepSeek这类大模型生成的其实是乐谱文本真正让音符发声的是MIDI文件。MIDI不直接发音它只记录“什么时候、用什么音高、以多大力气、弹多久”。恰恰是这种“不发音”的属性让AI作曲变得可控——你可以换音色、改速度、抽走一段旋律重新编曲而不是抱着一整段不可编辑的音频干瞪眼。对做游戏音频、短视频配乐、编曲Demo的人来说这是一条成本最低、最容易被验证的落地路线用DeepSeek写出结构和乐理正确的乐谱再转成MIDI剩下的交给DAW去渲染。2. 先把AI作曲的工程链路对齐DeepSeek负责作曲MIDI负责记谱2.1 为什么不干脆让AI直接出音频你如果让DeepSeek直接“生成一段音频”它做不到它是文本模型只能输出文本。市面上那些AI音乐产品比如Suno走的是“文本生成音频”的闭源链路你拿不到中间产物也就没法在吉他换钢琴、把120BPM改成90BPM、把副歌剪掉两小节。用DeepSeekMIDI的方案本质上是把“创作”和“渲染”拆开创作交给大模型、渲染交给DAW或音源库。这个拆法的好处有三个。第一可控性MIDI里的每个音符都可以单独编辑错了就改一个数字不用整段重录。第二可复现性同一个提示词、同一个seed能得到完全相同的结果这对批量生成配乐素材很重要。第三成本极低一次请求生成几十个音符也就是几K的token量比音频生成模型动辄按秒计费便宜得多。代价是你需要自己做一次“乐谱文本→MIDI文件”的转换这也是这个标题里最核心的工程步骤后面几章会详细展开。2.2 MIDI到底在记录什么MIDI文件SMFStandard MIDI File本质上是一串带时间戳的事件流。你不需要把每个字节都背下来但需要理解四类核心事件事件类型作用常见参数Note On音符开始发声通道、音高0-127、力度0-127Note Off音符结束通道、音高、释放力度Program Change切换乐器音色通道、音色编号对应GM音色表Control Change控制表情或效果控制器编号如CC1调制轮、CC7主音量音高编号和钢琴键的对应关系是中央CC4 60往上一个半音加1A4 69。力度Velocity则决定这个音弹得多响。理解这四类事件之后你就知道“AI作曲”实际上要产出什么——无非是一串按时间排列的“音高起始时间持续时间力度”四元组。怎么让大模型稳定输出这组数据是第3章要解决的问题。2.3 DeepSeek做这件事的定位乐理助手不是音频引擎DeepSeek真正擅长的是音乐理论知识——调式、和弦进行、终止式、节奏型。它见过海量乐谱文本和乐理教程能写出结构完整的旋律和和声编排。所以正确的用法是把它当作“一个懂乐理的搭档”而不是“一键出歌机器”。你需要用提示词约束它输出结构化乐谱而不是让它自由发挥一段散文式描述。这里我最常用的是DeepSeek的API接口底模用deepseek-chat就够不需要本地部署。原因后面第5章会细说根本问题是显存和推理速度。API调用方式跟OpenAI SDK兼容最小调用代码长这样import os from openai import OpenAI client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com ) resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 你是资深的作曲编曲助手只输出结构化的乐谱文本。}, {role: user, content: 写一段4/4拍、C大调、8小节的钢琴旋律速度90BPM。} ], temperature0.8, max_tokens1024, # seedNone # 不固定seed时每次结果都不同 ) print(resp.choices[0].message.content)这段代码的思路是先用system角色锁死输出边界再用user角色把创作要求说清楚。base_url必须指向DeepSeek的服务地址模型名用deepseek-chat这个对应的是公开的对话模型费用和上下文长度以官网说明为准。温度参数temperature0.8是我在“稳定”和“创意”之间取的折中低于0.4旋律容易重复高于1.2结构会散。max_tokens1024对一段8小节旋律绰绰有余。注意seed参数在DeepSeek API上不一定每次都能精确复现但固定种子能显著缩小随机范围。要精确复现同一段旋律最可靠的做法是把你满意的那段乐谱文本保存下来不做二次生成。2.4 别急着挂Agent或Harness看到“AI作曲”这个需求有人第一反应是上Agent编排框架给大模型挂一堆工具——一个智能体负责写旋律、一个负责配和弦、一个负责导出MIDI。我在这个标题下的实践经验是过度设计。作曲这个任务目前的核心瓶颈不在“多智能体协作”而在于“单次生成结果的正确性”。先让DeepSeek在一次对话里稳定输出一份能通过解析的乐谱比什么都重要。等你真的碰到“需要先听完第一段再决定第二段怎么写”的场景再考虑用harness之类的东西做多轮编排也不迟。现在这一章先把链路跑通。3. 乐谱格式选型DeepSeek输出“MiniABC”脚本负责转MIDI3.1 为什么不让DeepSeek直接输出MIDI二进制最自然的想法是“让DeepSeek直接生成MIDI文件”但这条路走不通。MIDI文件是二进制格式大模型输出文本时很容易产生幻觉直接生成二进制字节串几乎必然损坏。更理智的做法是让DeepSeek输出一种“人可读、结构严格、容易解析”的乐谱文本。我选了两种格式第一种叫MiniABC借鉴了ABC记谱法的行头风格但音符部分改用“音名八度数字时值数字”的显式记法。它最大的优点是对大模型友好——音符信息一目了然不会像标准ABC那样出现大量省略规则和音高歧义。第二种是JSON音符数组适合需要程序化处理精确节奏的场景但提示词写起来更啰嗦。日常首选MiniABC。3.2 MiniABC的语法约定一份MiniABC乐谱长这样X:1 T:demo M:4/4 L:1/4 K:C C4 D4 E4 G4 | A4 G4 E4 D4 | C2 E2 D2 F2 | G2 G4 z2 |逐行解释X:是曲子编号T:是标题M:是拍号L:是默认音符时值K:是调号。音符部分我从第6行开始写C4表示中央CMIDI 60D4表示中央D62数字4表示四分音符C2是二分音符G8是八分音符竖线|是小节线解析时忽略z2表示二分休止符[CEG]4表示四分音符的和弦同时发声。八度处理用数字直接标C4是中央CC5就是高一个八度C3是低一个八度。提示MiniABC不是标准ABC只是一个我为了绕开大模型幻觉设计的简化方言。它的价值在于“让DeepSeek能稳定输出让脚本好解析”。如果你之后要跟其他音乐软件互通建议用Python把MiniABC转成标准MIDI再导入。3.3 解析MiniABC到MIDImido方案下面这个脚本用mido库把MiniABC文本转成MIDI文件是整套流程的承重墙。import re from mido import MidiFile, MidiTrack, Message, MetaMessage NOTE_MAP { C: 0, D: 2, E: 4, F: 5, G: 7, A: 9, B: 11 } def note_to_midi(note_str: str) - int: 把 C4 这样的记号转成 MIDI 音高编号C460。 if note_str z: return None m re.match(r^([A-G])(\d)$, note_str) if not m: raise ValueError(f非法音符: {note_str}) letter, octave m.group(1), int(m.group(2)) # C4 是中央C(60)C3 是 48每升降八度差12 return NOTE_MAP[letter] (octave 1) * 12 def note_to_ticks(note_str: str, ticks_per_beat: int) - int: 把时值数字转成 MIDI tick 数。 if note_str z: return ticks_per_beat * 2 # z2 由调用方处理 m re.search(r(\d)$, note_str) if not m: return ticks_per_beat # 缺省当四分音符 return ticks_per_beat * 4 // int(m.group(1)) def miniabc_to_midi(text: str, output_path: str, bpm: int 90): ticks_per_beat 480 mid MidiFile(ticks_per_beatticks_per_beat) track MidiTrack() mid.tracks.append(track) # 设置速度和拍号 track.append(MetaMessage(set_tempo, tempoint(60_000_000 / bpm), time0)) track.append(MetaMessage(time_signature, numerator4, denominator4, time0)) current_tick 0 lines text.strip().splitlines() for line in lines: line line.strip() if not line or line[0] in XTLMK: continue # 音符区处理和弦与单音 tokens re.findall(r\[[A-Gz0-9]\]?\d*|[A-Gz]\d*, line) for token in tokens: if token |: continue if token.startswith([): # 和弦例如 [CEG]4 inner re.search(r\[([A-G])\](\d*), token) notes_str inner.group(1) dur_str inner.group(2) or 4 duration ticks_per_beat * 4 // int(dur_str) for ch in notes_str: midi_note note_to_midi(ch 4) # 和弦默认在中央八度 track.append(Message(note_on, notemidi_note, velocity72, timecurrent_tick if midi_note else 0)) track.append(Message(note_off, notemidi_note, velocity72, timeduration)) current_tick 0 else: note_str token if note_str z: duration ticks_per_beat * 4 // int(token[1:] or 4) current_tick duration continue midi_note note_to_midi(note_str) duration note_to_ticks(note_str, ticks_per_beat) if midi_note is not None: track.append(Message(note_on, notemidi_note, velocity72, time0)) track.append(Message(note_off, notemidi_note, velocity72, timeduration)) else: current_tick duration mid.save(output_path) print(f已保存: {output_path})这段代码的核心逻辑分三步。第一步用正则把每行音符区拆成独立token支持单音、和弦和休止符。第二步把C4这类音高记号映射到MIDI音高编号这里的关键是(octave 1) * 12这个公式——C4对应 (41)*1260C3对应48保证中央C的映射正确。第三步把时值数字换算成tick数tick是MIDI内部的时间单位ticks_per_beat480表示一个四分音符占480个tickE8就是480*4//8240个tick。这段代码刻意省略了节奏修正逻辑也就是说它假设DeepSeek输出的每个小节内时值总和刚好等于拍号要求。现实是它会算错所以第5章会专门讲怎么应对。第4章先把完整链路跑起来。3.4 备选方案JSON音符数组什么时候用当旋律包含大量切分音、三连音、附点时MiniABC会变得很难写。比如三连音在MiniABC里没有原生的语法糖硬写会牺牲可读性。这时候我会切换成JSON方案[ {note: 60, start: 0, duration: 480, velocity: 72}, {note: 62, start: 480, duration: 240, velocity: 72}, {note: 64, start: 720, duration: 240, velocity: 80} ]JSON方案的好处是每个音符的时间信息都显式给出解析逻辑更简单DeepSeek输出这种结构的鲁棒性也不错。缺点是提示词会长很多你得把“start和duration都用tick表示四分音符480”写清楚。我的实践经验是80%的旋律场景用MiniABC就够了遇到诡异节奏再上JSON不要一开始就搞得过于复杂。4. 完整跑通一个例子从一句中文提示词到一首原创MIDI4.1 提示词模板直接抄下面这个模板是我反复调过之后比较稳定的版本核心是把“音乐风格、结构、调式、节奏”拆成四个约束点并强制要求输出MiniABC格式你是资深作曲编曲助理。请根据以下要求创作一段原创钢琴旋律并严格按照MiniABC格式输出 - 调式C大调 - 拍号4/4 - 速度90 BPM - 结构8小节前4小节为主题后4小节为主题变化 - 情绪明亮、舒缓适合旅行短片 MiniABC输出要求 1. 第一行X:1第二行T:标题第三行M:4/4第四行L:1/4第五行K:C 2. 从第六行开始写音符每行写4小节小节之间用竖线|分隔 3. 音符格式音名八度数字时值数字例如C4表示四分音符的中央CA2表示二分音符的A3 4. 允许使用的音符时值1、2、4、8分别对应全音符、二分音符、四分音符、八分音符 5. 休止符用z加时值表示例如z4 6. 每个小节音符时值总和必须等于4个四分音符 7. 只用MiniABC输出不要附加任何解释关键点在第6条“每个小节音符时值总和必须等于4个四分音符”这是语义层面的约束也是DeepSeek最容易出错的地方。没有这句话它输出的节奏会对不上拍号下文会详谈。另外如果你想要的和弦是“带伴奏的主旋律”要在提示词里显式说明“左手低音声部每小节一个根音用C2/G2这种时值较长的音符”否则DeepSeek会默认只写单音旋律。4.2 用脚本把“API调用→MiniABC→MIDI”串起来这一节把第2章的API调用和第3章的解析器合并成一个完整脚本一次执行直接产出MIDI文件import os from openai import OpenAI from miniabc import miniabc_to_midi # 假设你把上一章的解析器存成 miniabc.py PROMPT 这里放4.1节的完整提示词 client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com ) resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: PROMPT}], temperature0.8, max_tokens2048, ) abc_text resp.choices[0].message.content # 把DeepSeek可能包在代码块里的内容剥出来 if in abc_text: abc_text abc_text.split()[1] if abc_text.startswith(miniabc): abc_text abc_text[7:].strip() abc_text abc_text.strip() with open(output/song.abc, w, encodingutf-8) as f: f.write(abc_text) miniabc_to_midi(abc_text, output/song.mid, bpm90) print(完成output/song.mid)这段脚本的工程要点有两个。第一个是“代码块剥离”DeepSeek有时会把MiniABC文本包在Markdown代码块里如果不剥离就传给解析器解析器会在X:1之前看见三个反引号直接报错。我这边用if in abc_text做了防御。第二个是中间产物落盘song.abc这个文本文件是后悔药——如果在MIDI试听环节发现问题你可以直接改这个文本重新生成不用再花一次API调用。4.3 拿到MIDI之后的验证转简谱做快速预览MIDI文件靠肉眼看不出问题最直接的验证方式是转成简谱文本打印出来快速检查有没有离谱的音高跳跃或节奏错误from mido import MidiFile def midi_to_jianpu(mid_path: str): mid MidiFile(mid_path) notes [] for track in mid.tracks: tick 0 for msg in track: tick msg.time if msg.type note_on and msg.velocity 0: # MIDI 60 C4转成简谱相对唱名 semitone msg.note - 60 scale_degree (semitone % 12) names [1, #1, 2, #2, 3, 4, #4, 5, #5, 6, #6, 7] note_name names[scale_degree] octave_offset semitone // 12 if octave_offset 0: note_name * octave_offset elif octave_offset 0: note_name , * (-octave_offset) notes.append((tick, note_name)) for t, n in notes: print(f{t:6d} {n}) midi_to_jianpu(output/song.mid)这段输出的效果是每一行“tick值简谱唱名”1是do3是mi带是高音带,是低音。你不需要懂五线谱扫一眼旋律走向就能发现大问题——比如相邻两个音符差了12个半音那大概率是DeepSeek把八度写错了。如果手边有DAW小样或音源库把这个MIDI拖进去换钢琴音色听一遍是最快的最终验证。4.4 三个必调参数的优先级参数优先度作用我的经验取值temperature最高控制旋律的随机程度0.7-0.9追求稳定用0.6max_tokens高防止长曲子被截断8小节204816小节4096提示词中的“小节时值约束”高让拍号正确必须显式写“每小节时值总和4”max_tokens这个参数容易被忽略。DeepSeek的输出长度有限如果生成16小节但max_tokens只给1024旋律会在第10小节戛然而止不会向你报错你只会得到一份残缺乐谱。我的经验是8小节给204816小节给4096留足余量。5. AI作曲的6个翻车现场常见问题与排查5.1 API报错messages tool calls need immediate results现象代码跑到client.chat.completions.create这一步控制台抛出以messages tool calls need immediate results开头的一长串错误连续几次都一样。原因这个标题下最常见的触发场景不是曲子本身而是你在同一个messages数组里混入了工具调用消息但没提供对应的工具结果。如果你把上一轮带tool_calls的助手消息原样塞进下一轮请求而缺失了role: tool的返回消息API会直接拒绝后续生成。DeepSeek在对话接口上对消息序列校验很严格。解决调用前过滤掉消息历史里所有finish_reasontool_calls的助手消息或者补齐对应的工具结果。就这个项目而言根本不需要工具调用把多轮对话拆成每次独立的API请求只保留system和user两条消息是最干净的规避方式。5.2 乐谱时值总和总对不上拍号现象生成的MIDI导入DAW后有的小节长了半拍有的小节短了一拍整段旋律的节拍像喝了酒。原因DeepSeek在生成过程中对数字计算不敏感它可能在小节里写了一个二分音符加两个四分音符再加一个八分音符合计3.5拍而拍号是4/4。提示词里第6条约束只能降低出错概率不能彻底消除。解决第一道防线是在解析器里加时值校验每个小节音符时值求和不等于拍号时就打印警告。第二道防线是让DeepSeek做自检——在提示词末尾加“请检查每个小节的时值总和是否为4拍如有错误立即修正”。第三道防线是人工检查转好的简谱跳过那些时值异常的段落手动改song.abc文本里的音符时值重新生成MIDI。最后这步往往比重新调API更快。5.3 旋律能听但和声终止式总是“悬空”现象旋律走向没问题但结尾缺乏结束感像是话说到一半被人打断。原因DeepSeek理解的“结束”可能是音符偏高或者停在非主音上。在C大调里最稳的终止式是停在C和弦的根音或三音上但大模型可能会把这个位置交给E、G或者B制造出一种未解决的不安感。解决在提示词里显式指定终止式例如“最后两小节使用V-I终止式落在C4上”。V-I终止式就是G和弦接C和弦这是和声学里最经典的完满终止。指定了之后绝大多数情况下结尾会变得干净利落。5.4 力度变化像过山车或像电子节拍器现象MIDI播放时有的音符音量突然大得吓人有的软得像被棉花捂住整段听起来忽大忽小。原因大模型生成的力度值容易走极端它不知道“弱起”应该给多轻、“重音”应该给多重。而这会让音源库渲染出来的声音非常不稳定。解决解析时对velocity做归一化处理。我的做法是把所有力度值约束在56到88之间旋律重音位给80以上经过音给60左右。公式很简单velocity max(56, min(88, note_velocity))。这个区间内大部分音源都能保持自然响应不会出现爆音或断音。5.5 风格越来越“AI味”几首曲子听着都像一个人写的现象不同提示词生成的旋律听完之后总觉得有相似的味道尤其是节奏型和音程走向。原因这是temperature低和提示词里的风格词太泛的双重结果。你写“流行风格”DeepSeek会调到它见过最常出现的流行旋律模板本质上是在复读高频模式。解决把风格描述从“形容词”改成“音型约束”。比如“使用附点节奏”“旋律以三度跳进为主”“避免连续三次同方向级进”这类乐理层面的约束会让旋律跳出模板化。另外把温度调到0.9以上会给随机性留出空间。但注意温度太高结构会松散我的上限是1.1。5.6 本地部署DeepSeek反而卡在显存和速度上现象有人为了省钱或数据隔离把DeepSeek系列的开源权重拉到本地结果生成一段8小节的旋律要等两分钟女生笔记本风扇狂转。原因作曲任务实际生成几百个token用API一次往返也就几秒钟。本地部署小模型质量不够大模型显存要求高推理速度又慢属于两头不讨好。解决这标题下直接用API接口省下的时间用来调提示词比什么都值。如果你确实有离网需求优先选DeepSeek蒸馏系列里7B/14B级别的量化版本4bit量化后显存占用能压到8GB以内。但你需要接受一个现实小模型的乐理能力和格式遵循度会明显下降MiniABC的报错率会翻倍得不偿失。6. 进阶用法把单旋律变成多轨编曲以及我的验证习惯当单旋律能稳定产出之后下一步是让DeepSeek帮你编曲。我一般会让它分别输出三个声部的MiniABC主旋律、和弦铺垫、低音根音然后通过脚本合并成多轨MIDI。提示词里加一句“主旋律用八分音符和弦声部每小节一个柱式和弦低音声部每小节一个二分音符根音”就能得到层次分明的三轨结构。合并时给每个音轨分配不同的MIDI通道和Program Change音色编号钢琴配80、贝斯配32、弦乐配48一轨抒情钢琴曲立刻变成有空间感的小编制乐队。但脚本能自动化的始终有限我的验证习惯永远是“先机器检查再人耳听”。机器检查分三步读回MIDI文件看音符总数是否和预期一致转简谱扫一眼音域有没有超过C3-C6检查有没有单音符长达两拍以上的“长音断层”。人耳听则固定在MIDI播放器里循环三遍——第一遍听旋律走向第二遍听节奏稳定第三遍闭眼检查情绪是否跟提示词的方向一致。这个标题做下来我最深的一条教训是AI作曲90%的时间不是在“作曲”而是在调提示词的格式约束和解析器的防御逻辑。DeepSeek永远不会在第一次生成时就给你一份完美的乐谱但只要你把“格式约束前置、错误处理兜底”它产出的旋律骨架确实能让普通人跨过从0到1的创作门槛。希望帮到你。本文还有配套的精品资源点击获取
