把“50 分钟的微醺歌单”当成一个工程问题来拆会有意思得多。你打开任何一个音乐 App搜“夏日”“日落”“放松”之类的关键词会得到成千上万个歌单但它们听起来大多差不多前五首还行第十首开始疲劳第二十分钟突然出现一首节奏完全跳脱的歌整体感受从“微醺”变成“随时准备切歌”。问题不在于选歌审美而在于编排本身。一个真正能让人从傍晚听到天黑、情绪一路平滑过渡的歌单背后是 BPM 曲线、调性亲和、响度分布、能量段结构的组合设计。而这几样东西几乎全部可以用 Python 做定量分析和自动排序。这篇文章就以一次“日落晋阳湖”场景下的 50 分钟 Amapiano 放松歌单为例拆开一个微醺 DJ Set 的完整技术链路从 Amapiano 的风格参数到情绪四阶段编排逻辑再到用 librosa 提取音频特征、根据 BPM 和调性生成接力顺序、最后可视化验证情绪曲线是否流畅。读完后你能用自己的本地音乐库跑通一套歌单编排工具而不是继续靠“随手收藏”碰运气。1. 歌单编排真正的技术难点在哪里先明确一个判断好歌单的核心不是“选了什么歌”而是“歌与歌之间怎么接”。很多曲库庞大的音乐爱好者会有这种感觉——单独听每一首都很好但放进一个播放列表之后总有几个位置听起来别扭。这不是听觉错觉。问题通常出在以下四个层面第一层是速度冲突。两首歌的 BPM 差距如果超过 8 到 10即便中间有淡入淡出身体节拍感也会出现明显断裂。尤其是 Amapiano 这种律动感极强的风格速度不匹配会在第一时间被听众察觉。第二层是调性冲突。两首歌曲即使在 BPM 上接近如果调性关系太远混音时会出现明显的“刺耳感”或“跑调感”。专业 DJ 用 Camelot 调性圆盘来规避这个问题但普通歌单工具很少考虑它。第三层是响度不均。一部分旧唱片响度低一部分现代流媒体母带响度很高。手机放歌单时你得不停手动调音量这在“放松”场景下几乎是灾难。第四层是情绪结构缺失。歌单没有设计“起点—铺垫—高点—回落”的曲线。每首歌都是高能量、每首歌都想“炸”结果是全程没有呼吸感微醺变成疲惫。这四个问题前三个是可以用工具量化的参数问题第四个是可以用编排规则解决的流程问题。这篇文章会告诉你怎么把“日落微醺”这种主观体验翻译成一组可以计算的目标函数。如果你正在准备一场户外小型派对、一个露台酒会或者只是想在夏季傍晚给自己排一段舒适的背景音乐这篇文章就是给你准备的。当然对音乐制作和 DJ 过渡技术感兴趣的前端、后端、音频开发者也能在代码部分找到可以直接跑的实验素材。2. Amapiano 的特征为什么它适合“日落微醺”场景在开始分析之前先解释一下标题中的 Amapiano 到底是什么。Amapiano 是源于南非的一种电子音乐风格字面意思是“钢琴”。它并不是单一曲风更像是 deep house、jazz、南非本土打击乐和 kwaito 的混合体。最标志性的声音是“log drum”——一种带有回弹感和木质敲击声的低频打击音色通常在 MIDI 键盘上弹奏出来像是“噗噗”的软弹低频沉稳但不炸耳。从技术参数上看Amapiano 有这些特征维度典型特征速度区间通常 100 到 112 BPM整体偏中速节奏型强调反拍和切分低频呈脉冲式旋律材料轻量钢琴和弦、电子合成器短句、爵士感 pads声场结构低频饱满但不压头高频柔和人声多为吟唱或口语化说唱能量密度中低频持续整体波动小适合长时间循环正是这些特征让 Amapiano 成为“日落 户外 社交 微醺”场景的高适配选项。它不会像 Techno 那样制造紧张感也不会像 Ballad 那样把情绪往下拉。它维持的是“平稳但持续向前”的律动类似于背景里有一个不催促你的鼓手。把它和常见的电子音乐风格对比会更容易理解它有多“中间态”风格典型 BPM能量曲线场景倾向Deep House115-125平滑为主偶有起伏深夜室内、慵懒派对Afro House115-122节奏硬朗、打击乐更突出舞池型派对、日间音乐节Amapiano100-112低频韧性强、能量持续但不高扬日落、露台、微醺社交Chillout / Downtempo70-100松弛、低冲击书房、休息、睡前所以“50 分钟 Amapiano 歌单”本质上是在一个相对狭窄的速度窗口内用有限的能量范围做出一段不无聊、不压迫、从头到尾保持微醺感的听觉旅程。它比从零开始编排一个多风格歌单更考验对细节的把控。3. 50 分钟的情绪曲线从日落前半段到天黑之后50 分钟时长对应多少首歌如果按 Amapiano 平均一首 3 分 30 秒到 4 分 30 秒计算大约是 11 到 14 首。这意味着每首歌在整段体验中占的比重都很大没有“随便放放”的空间。做歌单编排时第一步不是选歌而是先画情绪曲线。所谓“微醺”是一种慢慢进入、再慢慢淡出的状态。强烈推荐四阶段结构阶段一开场 Light0-10 分钟。听众刚到场地还在放东西、聊天、找位置。此时需要的是“环境感”不是“注意力”。选曲应该以轻量打击乐、柔和键盘、无强人声的曲子为主。BPM 可以稍低靠近 100 到 104。阶段二过渡 Groove10-25 分钟。人群稍微进入状态开始点头、晃身体。节拍可以往 106 到 108 推增加一些律动感。这个阶段的作用是“把耳朵抓进节拍里”但还不能让气氛燥起来。阶段三微醺 Peak25-40 分钟。这是情绪的核心段落。光线开始明显变暗酒精和社交氛围到位可以出现 log drum 更突出、贝斯更绵密的曲目。BPM 可以到 110 左右是整晚能量最高、但依然放松的一个区间。阶段四收尾 Sunset40-50 分钟。情绪不是“戛然而止”而是缓缓回落。最后两到三首可以降低打击乐密度增加空间感和混响让听众感觉“这一场刚刚好”。这个曲线的形状如果画出来大致是“缓升—平台—微峰—缓降”。它决定了接下来每一步选歌、测 BPM、排序、测试衔接点都要围绕这条曲线展开。这里要特别说一个真实场景中常见的问题日落不是匀速的。夏季日落在 40 到 60 分钟内颜色变化最明显的是最后 20 分钟。你的歌单可以不必严格匹配太阳高度但如果你把最高能量段安排在天色已经全黑的最后 10 分钟就会浪费日落场景的光线变化带来的天然情绪加成。这也是为什么“50 分钟”和“日落”这两个限定词在编排中同样重要——它们共同约束了情绪曲线的峰值应该落在哪里。4. 环境准备Python 音频分析工具链接下来进入实操环节。我们要做的事情是读取本地音频文件、提取每首歌的特征、计算曲目之间的衔接评分、自动排序、最后做可视化验证。如果你的音乐来源是本地 WAV / MP3 / FLAC推荐使用 Python 的 librosa 作为主力分析库它足够轻量能覆盖 BPM、频谱、能量、节拍强度等核心特征。如果你希望借助串流平台的现成音频特征可以使用 spotipy 调用平台的音频特征接口但它的使用前提是拥有 API 访问权限并且特征只对数据库内曲目有效本地文件无法直接使用。本文的代码以本地文件分析为主这样不依赖任何平台权限也便于你用于自己的私人音乐库。建议环境如下# 创建虚拟环境推荐 Python 3.9 以上 python -m venv venv source venv/bin/activate # 安装依赖 pip install librosa numpy matplotlib soundfile pyyaml说明一下librosa 的版本迭代比较快不同版本的 API 有细微差异例如librosa.beat.beat_track的返回值在不同版本中可能一个返回数组、一个返回标量。所以代码里做了np.ravel处理确保 BPM 在多数版本下都能正确取出。具体版本请以你的实际环境为准本文演示的是通用思路。工作目录建议按下面的结构组织sunset-mix/ ├── audio/ # 存放待分析的音频文件 ├── analysis/ # 输出特征 JSON ├── output/ # 输出排序结果和可视化 └── scripts/ ├── extract_features.py ├── auto_sequence.py └── plot_curve.py整个流程只有四步收集、提取、排序、验证。下面拆开讲。5. 核心流程拆解5.1 收集与统一音频格式把准备纳入歌单的曲目统一放到audio/目录。为了减少分析误差建议尽量使用同一种高质量编码格式。MP3 的 128kbps 和 FLAC 的频谱细节差异会直接影响到调性检测和能量分析的结果所以有条件的话优先用 FLAC 或 WAV。如果你的原始文件是 MP3可以用 ffmpeg 统一转成 44.1kHz 的 WAVffmpeg -i input.mp3 -ar 44100 -ac 1 -c:a pcm_s16le output.wav这里使用单声道 44.1kHz 就足够做特征分析了还能减少 librosa 的加载时间。注意这是“转换副本”不要删除原始文件。5.2 提取音频特征每首歌需要提取四个关键特征BPM决定速度窗口内能不能自然衔接。能量包络用于判断歌曲的情绪峰值和低谷位置。调性用于评估两首歌曲旋律层是否冲突。响度用于保证整段歌单不需要频繁调节音量。BPM 和响度由 librosa 直接计算即可。调性会稍微复杂一些因为 librosa 没有提供一个可以直接返回“A小调”或“C大调”的函数。常用做法是提取 chroma 特征后用 Krumhansl-Schmuckler 算法与标准调性轮廓做相关性比对。实际项目里也可以考虑 Essentia 的 KeyExtractor它对主调和调式的判断在多数流行音乐上更稳定。对于这组把歌单跑通的最小代码来说使用 chroma 分布或能量特征做近似判断已经足够指导人工复核。真正的“调性到底对不对”最终还是要靠混音时用耳朵确认。5.3 计算衔接评分拿到每首歌的特征后就进入核心编排环节。两首歌是否能顺畅衔接可分解为三个子分数速度分两首歌 BPM 差的绝对值越小越好理想情况下不超过 3 到 4。 调性分Camelot 调性圆盘上距离越近越和谐。相同数字、相邻数字、大小调互换都是可接受的。 响度分两首歌的平均响度差异越小越好避免用户动音量旋钮。三者的加权组合就得到衔接评分。排序时采用“贪心算法 人工复核”从第一首歌开始每一步都挑一首与当前曲目衔接分数最高、且尚未使用的歌。这个算法不保证全局最优但对 12 到 15 首歌的歌单来说结果已经足够好而且实现简单、结果可解释。5.4 人工复核与手动修正自动排序之后必须做一轮人工复核。重点听一遍所有“接口”位置上一首的结尾和下一首的开头在 BPM 和调性上是否真正顺耳。自动化能解决的是“大概率不冲突”但解决不了“这一时刻正好有一个人声尾音和下一首的钢琴起音叠在一起很突兀”这类偶发问题。所以算法输出只是初始顺序不是最终歌单。6. 完整示例代码实现下面给出三个可以直接跑的 Python 脚本。6.1 使用 librosa 提取音频特征# 文件路径scripts/extract_features.py import json import os import sys import librosa import numpy as np AUDIO_DIR os.path.join(os.path.dirname(__file__), .., audio) OUTPUT_DIR os.path.join(os.path.dirname(__file__), .., analysis) os.makedirs(OUTPUT_DIR, exist_okTrue) def extract_track_features(filepath): y, sr librosa.load(filepath, sr22050, monoTrue) # BPM 检测 tempo, _ librosa.beat.beat_track(yy, srsr) bpm float(np.ravel(tempo)[0]) # 响度与能量 rms librosa.feature.rms(yy)[0] avg_energy float(np.mean(rms)) peak_energy float(np.max(rms)) # 频谱质心用于粗略判断明暗程度 centroid librosa.feature.spectral_centroid(yy, srsr) avg_centroid float(np.mean(centroid)) # chroma 特征用于粗略调性分析 chroma librosa.feature.chroma_cqt(yy, srsr) chroma_mean np.mean(chroma, axis1).tolist() return { filename: os.path.basename(filepath), bpm: round(bpm, 2), avg_energy: round(avg_energy, 4), peak_energy: round(peak_energy, 4), avg_centroid: round(avg_centroid, 2), chroma: [round(v, 4) for v in chroma_mean], duration: round(librosa.get_duration(yy, srsr), 2) } def main(): results [] for filename in sorted(os.listdir(AUDIO_DIR)): if not filename.lower().endswith((.mp3, .wav, .flac, .m4a)): continue filepath os.path.join(AUDIO_DIR, filename) print(fprocessing: {filename}) try: features extract_track_features(filepath) results.append(features) except Exception as exc: print(ffailed: {filename}, error: {exc}) out_path os.path.join(OUTPUT_DIR, track_features.json) with open(out_path, w, encodingutf-8) as f: json.dump(results, f, ensure_asciiFalse, indent2) print(fsaved - {out_path}) if __name__ __main__: main()关键点在三个方面。第一np.ravel是为了兼容不同 librosa 版本对beat_track返回值的差异避免把数组当标量。第二音频统一重采样到 22050Hz这是 librosa 的默认采样率能明显加速计算同时足够支持 BPM 和能量分析。第三chroma 特征不能直接告诉你“这首歌是 C 大调还是 A 小调”但它给出了 12 个半音的能量分布后续可以用相关性算法排序也可以用人工对比判断两首歌旋律调性是否接近。6.2 生成自动排序auto_sequence.py 实现了贪心排序。它会读取上一步的特征 JSON按 BPM 差、响度差、chroma 相关系数计算两两之间的衔接评分然后从指定曲目开始依次挑出最高分曲目。# 文件路径scripts/auto_sequence.py import json import os from itertools import permutations import numpy as np BASE_DIR os.path.dirname(os.path.dirname(__file__)) FEATURE_PATH os.path.join(BASE_DIR, analysis, track_features.json) BPM_WEIGHT 1.0 ENERGY_WEIGHT 0.5 CHROMA_WEIGHT 1.2 def chroma_correlation(track_a, track_b): a np.array(track_a[chroma]) b np.array(track_b[chroma]) if np.std(a) 1e-6 or np.std(b) 1e-6: return 0.0 return float(np.corrcoef(a, b)[0, 1]) def track_distance(track_a, track_b): bpm_penalty abs(track_a[bpm] - track_b[bpm]) / 10.0 energy_penalty abs(track_a[avg_energy] - track_b[avg_energy]) * 3.0 chroma_score (1.0 - chroma_correlation(track_a, track_b)) * 2.0 combined BPM_WEIGHT * bpm_penalty ENERGY_WEIGHT * energy_penalty CHROMA_WEIGHT * chroma_score return combined def greedy_sequence(tracks, start_index0): remaining list(range(len(tracks))) current remaining.pop(start_index) order [current] while remaining: best_idx None best_dist float(inf) for idx in remaining: dist track_distance(tracks[current], tracks[idx]) if dist best_dist: best_dist dist best_idx idx order.append(best_idx) remaining.remove(best_idx) current best_idx return order def load_features(): with open(FEATURE_PATH, r, encodingutf-8) as f: return json.load(f) def main(): tracks load_features() if not tracks: print(no track features found, run extract_features.py first) return start input(start track index (default 0): ).strip() start_index int(start) if start.isdigit() else 0 order greedy_sequence(tracks, start_indexstart_index) print(\nsuggested order:) for pos, idx in enumerate(order, start1): track tracks[idx] bpm track[bpm] energy track[avg_energy] print(f{pos:2}. {track[filename]} | BPM {bpm} | energy {energy}) output_path os.path.join(BASE_DIR, output, sequence.txt) os.makedirs(os.path.dirname(output_path), exist_okTrue) with open(output_path, w, encodingutf-8) as f: for pos, idx in enumerate(order, start1): f.write(f{pos}. {tracks[idx][filename]}\n) print(f\nsaved - {output_path}) if __name__ __main__: main()这个脚本的核心是距离函数track_distance。它把“能不能接得上”转化为一个越小越好的代价BPM 差距用 10 进行归一化差距 5 BPM 惩罚为 0.5能量差异放大 3 倍chroma 相关性越低代价越高。需要注意这个算法是“局部最优”的贪心思路。它不会把所有可能的排列都跑一遍——12 首歌的全排列是 4.79 亿种不可能穷举。但对歌单场景来说贪心已经能给出非常合理的初始结果。如果你希望进一步优化可以在贪心结果上做 2-opt 局部交换把总代价继续压下去。6.3 绘制情绪曲线与能量分布可视化验证是判断歌单质量最直观的方式。下面脚本读取排序后的顺序和特征把每首歌的 BPM、能量绘制成折线图并把相邻歌曲之间的 BPM 变化量打印出来。# 文件路径scripts/plot_curve.py import json import os import matplotlib.pyplot as plt BASE_DIR os.path.dirname(os.path.dirname(__file__)) FEATURE_PATH os.path.join(BASE_DIR, analysis, track_features.json) PLOT_PATH os.path.join(BASE_DIR, output, sequence_curve.png) def main(): with open(FEATURE_PATH, r, encodingutf-8) as f: tracks json.load(f) seq_path os.path.join(BASE_DIR, output, sequence.txt) if not os.path.exists(seq_path): print(sequence.txt not found, run auto_sequence.py first) return with open(seq_path, r, encodingutf-8) as f: ordered_lines [line.strip() for line in f if line.strip()] ordered_tracks [] for line in ordered_lines: name line.split(. , 1)[1] for track in tracks: if track[filename] name: ordered_tracks.append(track) break if not ordered_tracks: print(no tracks matched) return bpm_seq [t[bpm] for t in ordered_tracks] energy_seq [t[avg_energy] for t in ordered_tracks] labels [t[filename][:12] for t in ordered_tracks] print(\nBPM transition check:) for i in range(len(bpm_seq) - 1): diff abs(bpm_seq[i 1] - bpm_seq[i]) flag OK if diff 4 else CHECK print(f{labels[i]} - {labels[i1]}: {bpm_seq[i]} - {bpm_seq[i1]} | diff {diff:.2f} | {flag}) fig, ax1 plt.subplots(figsize(12, 5)) ax1.plot(range(len(bpm_seq)), bpm_seq, markero, color#d97706, labelBPM) ax1.set_ylabel(BPM, color#d97706) ax1.set_xticks(range(len(labels))) ax1.set_xticklabels(labels, rotation45, haright) ax2 ax1.twinx() ax2.bar(range(len(energy_seq)), energy_seq, alpha0.3, color#2563eb, labelEnergy) ax2.set_ylabel(Avg Energy) plt.title(50-min Sunset Mix: BPM Energy Curve) fig.tight_layout() plt.savefig(PLOT_PATH, dpi150) print(f\nplot saved - {PLOT_PATH}) if __name__ __main__: main()这一步的价值是“用眼睛代替耳朵做初筛”。你不需要听完全曲只要看 BPM 折线是否出现大跳、能量柱状图是否出现突然的高峰或低谷就能快速判断哪个位置需要人工调换。7. 运行结果与效果验证跑完上述脚本后你会得到三样东西analysis/track_features.json每首歌的特征数组。output/sequence.txt自动排序后的歌单顺序。output/sequence_curve.pngBPM 和能量曲线图。判断歌单是否合格推荐看三个指标平均 BPM 差。按顺序相邻两首歌的 BPM 差平均值。建议不超过 3。如果出现超过 5 的相邻差优先在对应位置插入过渡曲目。响度波动。打印出能量序列看最大值和最小值是否相差过大。Amapiano 本身动态范围较窄所以如果 energy 曲线出现明显尖峰很可能那首歌风格不契合场景而不是音量问题。频谱重心变化。如果你在特征里保留了avg_centroid可以观察整个序列的频谱重心是否在缓慢移动。通常“日落”氛围下频谱重心应该随阶段推进从高频慢慢向低频移动而不是反复震荡。关于验证阈值还是要强调一点这些数字只是辅助参考。真正决定一个歌单是否成立的是你在完整播放时“有没有想切歌的瞬间”。建议用一次完整播放来做最终验收记录下每次想切歌的时间点再回头看那个位置对应哪首歌、面临什么特征冲突。8. 常见问题与排查思路问题现象可能原因排查方式解决方案BPM 检测结果和节拍感完全对不上歌曲存在变速段或使用了大量 rubato 演奏打印librosa.beat.beat_track的节拍时间序列观察是否存在不均匀间隔手动补充 BPM 修正值或在特征 JSON 中直接覆盖 bpm 字段两首歌 BPM 相同但混在一起很难受调性冲突或节奏密度差异过大对比 chroma 相关系数再听两首歌的鼓组密度调换顺序或加入一首过渡曲目响度曲线过于跳跃曲库来源多样母带响度标准不一查看avg_energy和peak_energy差异使用响度归一化工具或排除响度明显异常曲目自动排序结果第一首不适合开场贪心算法只优化整体衔接不优化段落角色检查序列每 10 分钟左右的位置是否符合四阶段预期指定多个锚点歌曲分段排序而非全局一次排序解析 M4A/MP3 失败缺少对应解码库或音频编码不兼容查看 librosa 报错信息确认文件是否为合法编码先统一转成 WAV 再分析算法顺序总是“科学但无趣”代价函数没有考虑歌曲结构、人声、情绪标签检查曲目是否全是同一风格的高相似度聚类在代价函数中加入“结构相似惩罚”或人工指定主题分段有两个坑尤其值得提醒。一是“BPM 相同不等于节拍对齐”。Amapiano 里的律动是切分型的两首歌虽然都是 108 BPM但如果一个鼓组在正拍、一个在反拍接起来会像“左脚踩右脚”。这没法靠代码完全判断需要在实际混音点试听。二是“调性分析对低频不敏感”。librosa 的 chroma_cqt 受低频影响很大如果两首歌的贝斯差异明显检测结果可能偏差较远。遇到这种情况建议改用 Essentia 的 KeyExtractor 做二次验证。9. 最佳实践与工程建议把整套流程用于真实场景尤其是“日落 户外”这类场合时有几个工程层面和经验层面的建议值得单独拿出来说。先设计情绪曲线再决定选歌范围。很多人会反着做先选一堆喜欢的歌然后试图把它们排得自然。结果往往是要么能量全程踩在一个水平线上要么为了迁就某首歌导致局部曲线断层。更好的顺序是先明确开头多亮、中间多热、结尾多缓再用特征参数去筛歌。给你的歌单建一张特征快照表。当你对曲库做了几十首歌的特征提取后不要只看单曲结果。可以保存一张汇总表记录每首歌的 BPM、能量、调性、风格标签和使用场景。后续排任何主题歌单先查表再补充分析效率会高很多。混音点不要贪多。50 分钟、12 首歌意味着只有 11 个“接口”。如果你的混音经验不足不必追求每首都做完整叠化保住 3 到 4 个关键接口的质量就足够了。其他位置用干净利落的淡出淡入即可。Amapiano 的 log drum 有很强的惯性接得不好会直接把整段的律动感打断。注意响度安全。户外派对容易陷入“越开越大声”的循环。建议在你的播放设备上观察持续响度尽量保持在 85 到 88dB 的平均响度。算法可以提供能量参考但没有一个算法能替你保护耳朵。从数据回归现场。自动排序的价值是提供一个“从零开始的合理基线”不是最终答案。你仍然需要在现场播放一遍记录那些“数据上没问题但听感轻微不适”的位置并积累成自己的调整经验。做歌单编排和其他工程问题一样算法给候选集人类做最终决策。版本记录。如果这是一个持续更新的歌单项目建议把每次定稿的sequence.txt和特征 JSON 一起纳入 Git 管理。下一次想给“日落晋阳湖”场景做升级版时可以直接 diff 出曲目和顺序差异避免每次从零开始。10. 总结与延伸方向我们回到开头的问题一个 50 分钟的放松微醺歌单凭什么让人舒服地听完答案不是“选歌品味好”而是背后有一套可以被计算和验证的编排逻辑Amapiano 的中速律动天然适合日落场景BPM 窗口约束了速度平滑度调性亲和决定混音是否刺耳响度曲线决定了听众需不需要频繁动手调音量而四阶段情绪曲线决定了整个歌单是“有节奏地呼吸”还是“一口气从头撑到尾”。这篇文章的代码只能帮你解决“参数层面”的问题也就是把客观可计算的部分自动完成。真正让一个歌单有灵魂的仍然是你在最后人工复核阶段做出的那些“无法解释但就是更顺耳”的选择——这恰恰是算法和人的分工最舒服的地方。接下来你可以考虑三个延伸方向第一去了解 Essentia 的 KeyExtractor 和 BeatTracker 算法它们比 librosa 更适合处理专业调性和节拍分析第二如果你的目标不是歌单而是真正的现场混音可以学习 Rekordbox 等 DJ 软件的波形分析和 Beatgrid 对齐逻辑理解“结构对齐”和“参数匹配”的区别第三把本文的思路搬到其他风格上——用同样的特征提取和排序脚本去排一个 60 分钟的 Deep House 歌单或者 40 分钟的 Chillhop 歌单你会发现代码几乎不用改真正要改的是对 BPM 窗口和情绪曲线的设定。最后提醒一句做这种户外场景歌单时音乐版权和场地环境同样重要。自行整理本地音乐用于私人欣赏没有问题但公开播放和传播需要确认授权范围。音响设备要根据场地大小和周边环境合理选择山西太原晋阳湖景区周边视野开阔、遮阳棚和夜间照明条件各异去之前最好实际踩点一次确认电源点位、音响摆位和噪声限制再决定歌单峰值段的播放时点。祝你的日落歌单能赶在光线最柔和的那 20 分钟切到最对的那一首歌。