本文目录导读:

我来介绍几种批量检测音乐调性的方法,从最简单到最专业。
使用Python + librosa库(最推荐)
import librosa
import numpy as np
import os
from pathlib import Path
def detect_key(audio_path):
"""检测单个音频文件的调性"""
try:
# 加载音频文件
y, sr = librosa.load(audio_path, sr=22050)
# 计算色谱图
chroma = librosa.feature.chroma_cqt(y=y, sr=sr)
# 使用Krumhansl-Schmuckler算法检测调性
key_temp = librosa.feature.tonnetz(y=librosa.effects.harmonic(y), sr=sr)
# 计算每个调性的分数
key_dict = {
'C': 0, 'C#': 1, 'D': 2, 'D#': 3, 'E': 4, 'F': 5,
'F#': 6, 'G': 7, 'G#': 8, 'A': 9, 'A#': 10, 'B': 11
}
# 使用模板匹配
chroma_mean = np.mean(chroma, axis=1)
# 大调模板
major_template = np.array([1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1])
# 小调模板
minor_template = np.array([1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0])
best_key = None
best_score = -1
for i in range(12):
# 循环移位模板
rotated_major = np.roll(major_template, i)
rotated_minor = np.roll(minor_template, i)
# 计算相关性
major_score = np.corrcoef(chroma_mean, rotated_major)[0, 1]
minor_score = np.corrcoef(chroma_mean, rotated_minor)[0, 1]
if major_score > best_score:
best_score = major_score
key_name = list(key_dict.keys())[i]
best_key = f"{key_name} Major"
if minor_score > best_score:
best_score = minor_score
key_name = list(key_dict.keys())[i]
best_key = f"{key_name} Minor"
return best_key, best_score
except Exception as e:
return f"Error: {str(e)}", 0
def batch_detect_keys(folder_path):
"""批量检测文件夹中所有音频文件的调性"""
results = []
# 支持的音频格式
audio_formats = ['.mp3', '.wav', '.flac', '.m4a', '.ogg']
# 遍历文件夹
for file_path in Path(folder_path).rglob('*'):
if file_path.suffix.lower() in audio_formats:
print(f"检测中: {file_path.name}")
key, confidence = detect_key(str(file_path))
results.append({
'filename': file_path.name,
'key': key,
'confidence': confidence,
'path': str(file_path)
})
return results
# 使用方法
if __name__ == "__main__":
import pandas as pd
# 设置音频文件夹路径
audio_folder = "your_audio_folder_path" # 修改为你的文件夹路径
results = batch_detect_keys(audio_folder)
# 保存为CSV文件
df = pd.DataFrame(results)
df.to_csv("music_key_detection_results.csv", index=False, encoding='utf-8')
# 打印结果
print("\n检测结果:")
print(df.to_string())
使用Essentia库(更专业)
# 安装essentia pip install essentia-tensorflow
import essentia.standard as es
import os
import pandas as pd
from pathlib import Path
def detect_key_essentia(audio_path):
"""使用Essentia检测调性"""
try:
# 加载音频
loader = es.MonoLoader(filename=audio_path)
audio = loader()
# 检测调性
key_extractor = es.KeyExtractor()
key, scale, strength = key_extractor(audio)
return f"{key} {scale}", strength
except Exception as e:
return f"Error: {str(e)}", 0
def batch_detect_keys_essentia(folder_path):
results = []
audio_formats = ['.mp3', '.wav', '.flac', '.m4a', '.ogg']
for file_path in Path(folder_path).rglob('*'):
if file_path.suffix.lower() in audio_formats:
print(f"检测中: {file_path.name}")
key, confidence = detect_key_essentia(str(file_path))
results.append({
'filename': file_path.name,
'key': key,
'confidence': confidence
})
return results
# 使用方法
results = batch_detect_keys_essentia("your_audio_folder")
df = pd.DataFrame(results)
df.to_csv("key_detection_results.csv", index=False)
命令行工具MIR-QBSH
# 安装 pip install mir-eval # 或者下载工具: https://github.com/rabitt/qbsh
使用音乐分析软件API
Sonic Visualiser + VAMP插件
# 命令行批量处理 sonic-annotator -t key_detection.n3 -w csv *.wav
AcousticBrainz API
import requests
import json
import os
def detect_key_api(audio_path):
"""使用AcousticBrainz API(需要上传音频)"""
url = "https://acousticbrainz.org/api/v1/high-level"
with open(audio_path, 'rb') as f:
files = {'audio': f}
response = requests.post(url, files=files)
if response.status_code == 200:
data = response.json()
if 'tonal' in data and 'key_key' in data['tonal']:
return data['tonal']['key_key'], data['tonal'].get('key_scale', '')
return None, None
完整的批处理脚本(带GUI)
import tkinter as tk
from tkinter import filedialog, messagebox
import os
import pandas as pd
from pathlib import Path
import threading
import time
class KeyDetectorGUI:
def __init__(self):
self.root = tk.Tk()
self.root.title("批量音乐调性检测工具")
self.root.geometry("600x500")
# 创建UI元素
self.create_widgets()
def create_widgets(self):
# 文件夹选择
tk.Label(self.root, text="选择音频文件夹:").pack(pady=10)
self.folder_path = tk.StringVar()
entry = tk.Entry(self.root, textvariable=self.folder_path, width=50)
entry.pack(pady=5)
tk.Button(self.root, text="浏览文件夹", command=self.select_folder).pack(pady=5)
# 开始按钮
self.start_button = tk.Button(self.root, text="开始检测",
command=self.start_detection,
bg="#4CAF50", fg="white")
self.start_button.pack(pady=20)
# 进度显示
self.progress_label = tk.Label(self.root, text="就绪")
self.progress_label.pack(pady=10)
# 结果文本框
self.result_text = tk.Text(self.root, height=15, width=70)
self.result_text.pack(pady=10)
# 滚动条
scrollbar = tk.Scrollbar(self.result_text)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.result_text.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=self.result_text.yview)
def select_folder(self):
folder = filedialog.askdirectory()
if folder:
self.folder_path.set(folder)
def detect_key_simple(self, audio_path):
"""简单的调性检测(基于频谱分析)"""
try:
import librosa
y, sr = librosa.load(audio_path, sr=22050, duration=30)
chroma = librosa.feature.chroma_cqt(y=y, sr=sr)
chroma_mean = np.mean(chroma, axis=1)
# 简单模板匹配
major_profile = np.array([6.35, 2.23, 3.48, 2.33, 4.38, 4.09,
2.52, 5.19, 2.39, 3.66, 2.29, 2.88])
minor_profile = np.array([6.33, 2.68, 3.52, 5.38, 2.60, 3.53,
2.54, 4.75, 3.98, 2.69, 3.34, 3.17])
keys = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
best_corr = -1
best_key = "未知"
for i in range(12):
rotated_major = np.roll(major_profile, i)
rotated_minor = np.roll(minor_profile, i)
corr_major = np.corrcoef(chroma_mean, rotated_major)[0, 1]
corr_minor = np.corrcoef(chroma_mean, rotated_minor)[0, 1]
if corr_major > best_corr:
best_corr = abs(corr_major)
best_key = f"{keys[i]} Major"
if corr_minor > best_corr:
best_corr = abs(corr_minor)
best_key = f"{keys[i]} Minor"
return best_key
except Exception as e:
return f"错误: {str(e)}"
def start_detection(self):
if not self.folder_path.get():
messagebox.showerror("错误", "请先选择文件夹")
return
# 在新线程中运行检测
thread = threading.Thread(target=self.run_detection)
thread.start()
def run_detection(self):
import numpy as np
from scipy import stats
folder = self.folder_path.get()
audio_formats = ['.mp3', '.wav', '.flac', '.m4a']
results = []
files = [f for f in Path(folder).rglob('*')
if f.suffix.lower() in audio_formats]
for i, file_path in enumerate(files):
self.progress_label.config(text=f"检测 {i+1}/{len(files)}: {file_path.name}")
key = self.detect_key_simple(str(file_path))
results.append(f"{file_path.name}: {key}")
self.result_text.insert(tk.END, f"{file_path.name}: {key}\n")
self.result_text.see(tk.END)
self.root.update()
time.sleep(0.1) # 避免界面卡死
self.progress_label.config(text="检测完成!")
messagebox.showinfo("完成", f"共检测 {len(files)} 个音频文件")
# 保存结果
output_file = os.path.join(folder, "调性检测结果.csv")
with open(output_file, 'w', encoding='utf-8') as f:
f.write("文件名,调性\n")
for result in results:
f.write(result.replace(": ", ",") + "\n")
self.result_text.insert(tk.END, f"\n结果已保存到: {output_file}")
# 运行GUI
if __name__ == "__main__":
app = KeyDetectorGUI()
app.root.mainloop()
安装依赖
# 基础依赖 pip install librosa numpy pandas scipy # 可选:更专业的库 pip install essentia-tensorflow pip install mir-eval
使用建议
- 准确度考虑:librosa的调性检测准确度约70-80%,专业软件可达90%以上
- 处理速度:处理一个3分钟的音频文件约需1-3秒
- 批量建议:先测试10-20个文件确认结果准确性
- 输出格式:推荐保存为CSV文件方便后续处理
选择哪种方法取决于你的需求:
- 简单快速:方法1(Python+librosa)
- 专业准确:方法2(Essentia)
- 批量处理:方法5(带GUI的脚本)