音乐爱好者听歌心情数字可视化系统
一、实际应用场景描述
在数字音乐蓬勃发展的今天,音乐不仅仅是听觉享受,更是情感的载体。本系统主要应用于以下场景:
- 个人音乐日记:记录每日听歌时的心情变化,形成可视化的情感轨迹
- 音乐治疗辅助:心理医生通过可视化数据分析患者的情绪波动
- 社交音乐分享:朋友圈分享带有心情可视化的听歌记录
- 音乐推荐优化:基于心情数据为音乐平台提供更精准的个性化推荐
- 创作灵感收集:音乐创作者分析听众在不同歌曲下的情绪反应
想象这样一个场景:小李每天上下班路上都会听音乐,但他发现自己很难记住哪首歌在什么心情下打动了他。通过这个系统,他可以看到自己一周的心情曲线,发现每当听到某类旋律时,心情都会明显好转,从而更好地管理情绪,也发现了自己的音乐偏好规律。
二、引入痛点
传统音乐体验存在以下情感记录难题:
- 记忆碎片化:听歌时的细腻情绪难以用文字准确描述
- 时间流逝感:无法直观看到情绪随音乐的时间演变
- 数据孤岛化:各大音乐平台的听歌记录与心情数据分离
- 分析盲区:缺乏对自己音乐情感模式的深度洞察
- 分享困难:单纯的歌单无法传达听歌时的真实感受
- 疗愈缺失:无法量化音乐对情绪的改善效果
三、核心逻辑讲解
本系统采用"音频分析+情绪识别+数据可视化"的技术路线:
- 音频特征提取:使用librosa库分析音乐的节奏、音高、频谱等特征
- 情绪映射算法:将音频特征映射到Valence-Arousal情绪模型
- 实时情绪追踪:通过Web Audio API捕获播放时的生理反应(可选)
- 数据持久化:SQLite存储听歌记录和情绪数据
- 多维可视化:使用Matplotlib/Plotly生成动态图表
- 智能分析:基于历史数据发现情绪模式和音乐偏好关联
情绪模型说明:
- Valence(愉悦度):-1(悲伤)到+1(快乐)
- Arousal(唤醒度):-1(平静)到+1(激动)
- Dominance(支配度):-1(被动)到+1(主动)
四、项目结构
music_mood_visualizer/ ├── main.py # 主程序入口 ├── config/ │ └── settings.py # 配置文件 ├── core/ │ ├── audio_analyzer.py # 音频分析模块 │ ├── mood_detector.py # 情绪检测模块 │ ├── data_manager.py # 数据管理模块 │ └── visualizer.py # 可视化模块 ├── data/ │ ├── database/ # SQLite数据库 │ ├── music_files/ # 音乐文件存储 │ └── exports/ # 导出文件 ├── ui/ │ ├── gui.py # 图形界面 │ └── web_interface.py # Web界面(可选) ├── utils/ │ ├── helpers.py # 辅助函数 │ └── mood_calibration.py # 情绪校准工具 ├── requirements.txt # 依赖包 ├── README.md # 项目说明 └── demo_data/ # 演示数据
五、核心代码实现
- 主程序入口 (main.py)
""" 音乐爱好者听歌心情数字可视化系统 - 主程序入口 功能:整合各模块,提供完整的音乐情绪记录与分析体验 作者:全栈开发工程师 版本:1.0.0 创建时间:2026-02-27 """
import sys import os import logging from pathlib import Path from datetime import datetime from typing import Optional, Dict, List import json
添加项目根目录到系统路径
sys.path.append(str(Path(file).parent))
from config.settings import Config from core.audio_analyzer import AudioAnalyzer from core.mood_detector import MoodDetector from core.data_manager import DataManager from core.visualizer import MoodVisualizer from ui.gui import MusicMoodGUI from utils.helpers import setup_logging, ensure_directories
def main(): """ 主函数:初始化系统并启动应用
执行流程:
1. 配置日志系统
2. 加载系统配置
3. 初始化各核心模块
4. 启动图形界面
5. 处理优雅退出
"""
# 打印启动横幅
print_startup_banner()
try:
# 1. 配置日志
log_dir = Path(__file__).parent / "logs"
setup_logging(log_dir)
logger = logging.getLogger(__name__)
logger.info("=" * 60)
logger.info("音乐心情可视化系统启动中...")
logger.info("=" * 60)
# 2. 加载配置
config = Config()
logger.info(f"配置加载成功 | 工作目录: {config.WORK_DIR}")
logger.info(f"数据库路径: {config.DATABASE_PATH}")
# 3. 确保必要目录存在
ensure_directories(config)
logger.info("目录结构检查完成")
# 4. 初始化核心模块
modules = initialize_modules(config, logger)
logger.info("核心模块初始化完成")
# 5. 启动GUI
app = MusicMoodGUI(
config=config,
audio_analyzer=modules['audio_analyzer'],
mood_detector=modules['mood_detector'],
data_manager=modules['data_manager'],
visualizer=modules['visualizer']
)
logger.info("GUI界面启动成功")
# 6. 运行主循环
app.run()
except KeyboardInterrupt:
logger.info("用户中断,正在优雅退出...")
except Exception as e:
logger.error(f"系统启动失败: {str(e)}", exc_info=True)
print(f"\n❌ 系统启动失败: {str(e)}")
sys.exit(1)
finally:
logger.info("音乐心情可视化系统已关闭")
def print_startup_banner(): """打印启动横幅""" banner = """ ╔══════════════════════════════════════════════════════════════╗ ║ ║ ║ 🎵 音乐心情数字可视化系统 v1.0.0 🎵 ║ ║ ║ ║ 用数据记录旋律中的情感波动 ║ ║ 让音乐成为心灵的晴雨表 ║ ║ ║ ║ Digital Art & Music Innovation Project ║ ║ ║ ╚══════════════════════════════════════════════════════════════╝ """ print(banner)
def initialize_modules(config: Config, logger) -> Dict: """ 初始化所有核心模块
Args:
config: 系统配置对象
logger: 日志记录器
Returns:
包含所有模块实例的字典
"""
modules = {}
try:
# 音频分析模块
logger.info("初始化音频分析模块...")
modules['audio_analyzer'] = AudioAnalyzer(config)
# 情绪检测模块
logger.info("初始化情绪检测模块...")
modules['mood_detector'] = MoodDetector(config)
# 数据管理模块
logger.info("初始化数据管理模块...")
modules['data_manager'] = DataManager(config)
# 可视化模块
logger.info("初始化可视化模块...")
modules['visualizer'] = MoodVisualizer(config)
return modules
except Exception as e:
logger.error(f"模块初始化失败: {str(e)}")
raise
if name == "main": main()
- 配置文件 (config/settings.py)
""" 系统配置文件 包含所有可调参数、路径设置和情感模型配置 """
from dataclasses import dataclass, field from typing import Dict, List, Tuple, Optional from pathlib import Path import os import json
@dataclass class Config: """系统配置数据类"""
# ==================== 基础路径配置 ====================
WORK_DIR: Path = field(default_factory=lambda: Path(__file__).parent.parent)
DATA_DIR: Path = field(default_factory=lambda: Path(__file__).parent.parent / "data")
LOGS_DIR: Path = field(default_factory=lambda: Path(__file__).parent.parent / "logs")
EXPORTS_DIR: Path = field(default_factory=lambda: Path(__file__).parent.parent / "data" / "exports")
# 数据库配置
DATABASE_PATH: Path = field(
default_factory=lambda: Path(__file__).parent.parent / "data" / "database" / "mood_records.db"
)
# 音乐文件存储
MUSIC_FILES_DIR: Path = field(
default_factory=lambda: Path(__file__).parent.parent / "data" / "music_files"
)
# ==================== 音频分析配置 ====================
AUDIO_SAMPLE_RATE: int = 22050 # 采样率
AUDIO_DURATION: float = 30.0 # 分析时长(秒),取前30秒
N_FFT: int = 2048 # FFT窗口大小
HOP_LENGTH: int = 512 # 帧移
N_MELS: int = 128 # 梅尔频带数
# 特征提取参数
FEATURE_EXTRACTION_INTERVAL: float = 0.5 # 特征提取间隔(秒)
# ==================== 情绪模型配置 ====================
# Valence-Arousal-Dominance 模型参数
# 基于音乐特征的权重配置
MOOD_WEIGHTS: Dict[str, float] = field(default_factory=lambda: {
"tempo": 0.25, # 节奏对情绪的影响权重
"energy": 0.20, # 能量对情绪的影响权重
"danceability": 0.15, # 舞蹈性对情绪的影响权重
"valence_audio": 0.20, # 音频愉悦度权重
"brightness": 0.10, # 音色明亮度权重
"roughness": 0.10, # 粗糙度权重
})
# 情绪分类阈值
VALENCE_THRESHOLDS: Dict[str, Tuple[float, float]] = field(default_factory=lambda: {
"sad": (-1.0, -0.3),
"melancholic": (-0.3, -0.1),
"neutral": (-0.1, 0.1),
"content": (0.1, 0.4),
"happy": (0.4, 0.7),
"ecstatic": (0.7, 1.0),
})
AROUSAL_THRESHOLDS: Dict[str, Tuple[float, float]] = field(default_factory=lambda: {
"calm": (-1.0, -0.4),
"relaxed": (-0.4, -0.1),
"neutral": (-0.1, 0.1),
"engaged": (0.1, 0.4),
"excited": (0.4, 0.7),
"intense": (0.7, 1.0),
})
# ==================== 可视化配置 ====================
PLOT_STYLE: str = "dark_background" # matplotlib样式
FIGURE_SIZE: Tuple[int, int] = (14, 10)
DPI: int = 100
# 颜色主题
COLOR_THEME: Dict[str, str] = field(default_factory=lambda: {
"sad": "#3498db", # 蓝色
"melancholic": "#9b59b6", # 紫色
"neutral": "#95a5a6", # 灰色
"content": "#2ecc71", # 绿色
"happy": "#f1c40f", # 黄色
"ecstatic": "#e74c3c", # 红色
})
# 图表配色方案
CHART_COLORS: List[str] = field(default_factory=lambda: [
"#e74c3c", "#e67e22", "#f1c40f", "#2ecc71",
"#3498db", "#9b59b6", "#1abc9c", "#34495e"
])
# ==================== GUI配置 ====================
WINDOW_TITLE: str = "🎵 音乐心情数字可视化系统"
WINDOW_WIDTH: int = 1400
WINDOW_HEIGHT: int = 900
MIN_WIDTH: int = 1000
MIN_HEIGHT: int = 700
# 播放器配置
DEFAULT_VOLUME: float = 0.7
SEEK_STEP: int = 5 # 快进/快退秒数
# ==================== 数据存储配置 ====================
AUTO_SAVE_INTERVAL: int = 300 # 自动保存间隔(秒)
MAX_RECENT_FILES: int = 20 # 最近播放列表最大数量
DATA_RETENTION_DAYS: int = 365 # 数据保留天数
# ==================== 情绪校准配置 ====================
CALIBRATION_MODE: bool = True # 是否启用用户校准
DEFAULT_USER_PROFILE: Dict = field(default_factory=lambda: {
"name": "音乐爱好者",
"age_range": "18-30",
"preferred_genres": ["pop", "rock", "jazz", "classical"],
"mood_sensitivity": 1.0, # 情绪敏感度系数
"baseline_mood": {"valence": 0.0, "arousal": 0.0} # 基线情绪
})
# ==================== 导出配置 ====================
EXPORT_FORMATS: List[str] = field(default_factory=lambda: ["png", "svg", "pdf", "html"])
REPORT_TEMPLATE: str = "default"
# ==================== 高级配置 ====================
ENABLE_REAL_TIME_ANALYSIS: bool = True # 启用实时分析
ENABLE_SOCIAL_SHARING: bool = True # 启用社交分享
ENABLE_CLOUD_SYNC: bool = False # 启用云同步(预留)
def __post_init__(self):
"""初始化后处理"""
# 确保所有路径都是绝对路径
self.DATA_DIR = self.WORK_DIR / "data"
self.LOGS_DIR = self.WORK_DIR / "logs"
self.EXPORTS_DIR = self.DATA_DIR / "exports"
self.DATABASE_PATH = self.DATA_DIR / "database" / "mood_records.db"
self.MUSIC_FILES_DIR = self.DATA_DIR / "music_files"
# 创建目录
for dir_path in [
self.DATA_DIR,
self.LOGS_DIR,
self.EXPORTS_DIR,
self.DATABASE_PATH.parent,
self.MUSIC_FILES_DIR
]:
dir_path.mkdir(parents=True, exist_ok=True)
# 加载自定义配置(如果存在)
self._load_custom_config()
def _load_custom_config(self):
"""加载自定义配置文件"""
custom_config_path = self.WORK_DIR / "custom_config.json"
if custom_config_path.exists():
try:
with open(custom_config_path, 'r', encoding='utf-8') as f:
custom_config = json.load(f)
# 更新配置(仅更新存在的字段)
for key, value in custom_config.items():
if hasattr(self, key):
setattr(self, key, value)
print(f"[INFO] 已加载自定义配置: {custom_config_path}")
except Exception as e:
print(f"[WARNING] 加载自定义配置失败: {e}")
==================== 预设音乐情绪映射 ====================
基于音乐理论的典型情绪映射
PRESET_MOOD_MAPPINGS: Dict[str, Dict[str, float]] = { "classical_sad": { "valence": -0.6, "arousal": -0.3, "dominance": 0.1, "description": "古典悲怆 - 深沉而内省" }, "classical_happy": { "valence": 0.7, "arousal": 0.2, "dominance": 0.3, "description": "古典欢愉 - 优雅而明亮" }, "rock_energetic": { "valence": 0.5, "arousal": 0.9, "dominance": 0.8, "description": "摇滚激情 - 强烈而充满力量" }, "jazz_smooth": { "valence": 0.3, "arousal": 0.1, "dominance": 0.2, "description": "爵士慵懒 - 舒缓而富有韵律" }, "electronic_dance": { "valence": 0.6, "arousal": 0.95, "dominance": 0.7, "description": "电子舞曲 - 律动而兴奋" }, "ambient_calm": { "valence": 0.1, "arousal": -0.5, "dominance": -0.2, "description": "氛围宁静 - 空灵而平和" }, "folk_acoustic": { "valence": 0.2, "arousal": 0.0, "dominance": 0.0, "description": "民谣原声 - 质朴而真诚" }, "pop_upbeat": { "valence": 0.8, "arousal": 0.5, "dominance": 0.4, "description": "流行轻快 - 阳光而积极" } }
==================== 情绪标签定义 ====================
MOOD_LABELS: Dict[str, Dict] = { "sad": { "chinese": "悲伤", "emoji": "😢", "color": "#3498db", "keywords": ["忧郁", "伤感", "思念", "失落", "眼泪"] }, "melancholic": { "chinese": "惆怅", "emoji": "🌧️", "color": "#9b59b6", "keywords": ["怀旧", "感慨", "淡淡忧伤", "回忆"] }, "neutral": { "chinese": "平静", "emoji": "😐", "color": "#95a5a6", "keywords": ["放松", "无特别情绪", "中性", "平淡"] }, "content": { "chinese": "满足", "emoji": "🙂", "color": "#2ecc71", "keywords": ["惬意", "舒适", "安心", "温暖"] }, "happy": { "chinese": "开心", "emoji": "😊", "color": "#f1c40f", "keywords": ["愉快", "欢乐", "喜悦", "轻快"] }, "ecstatic": { "chinese": "狂喜", "emoji": "🤩", "color": "#e74c3c", "keywords": ["兴奋", "激昂", "狂欢", "雀跃"] } }
- 音频分析模块 (core/audio_analyzer.py)
""" 音频分析模块 使用librosa库提取音乐的各种声学特征 为情绪检测提供数据基础 """
import librosa import numpy as np import soundfile as sf from typing import Dict, List, Tuple, Optional from dataclasses import dataclass from scipy.stats import skew, kurtosis import logging
logger = logging.getLogger(name)
@dataclass class AudioFeatures: """ 音频特征数据类 存储从音乐中提取的所有声学特征 """ # 基本属性 duration: float # 音频时长(秒) sample_rate: int # 采样率
# 节奏特征
tempo: float # 节拍速度(BPM)
beat_frames: np.ndarray # 节拍帧位置
beat_times: np.ndarray # 节拍时间点
# 频谱特征
spectral_centroid: np.ndarray # 频谱质心(亮度指标)
spectral_bandwidth: np.ndarray # 频谱带宽
spectral_rolloff: np.ndarray # 频谱滚降点
spectral_contrast: np.ndarray # 频谱对比度
# 能量特征
rms_energy: np.ndarray # RMS能量(响度指标)
zero_crossing_rate: np.ndarray # 过零率(粗糙度指标)
# Mel频率倒谱系数
mfcc: np.ndarray # MFCC特征矩阵
# 统计特征(聚合值)
mean_features: Dict[str, float] = None
std_features: Dict[str, float] = None
trend_features: Dict[str, float] = None
# 派生特征
danceability: float = 0.0 # 舞蹈性评分
energy_level: float = 0.0 # 能量等级
valence_proxy: float = 0.0 # 愉悦度代理指标
arousal_proxy: float = 0.0 # 唤醒度代理指标
brightness: float = 0.0 # 明亮度
roughness: float = 0.0 # 粗糙度
class AudioAnalyzer: """ 音频分析器类
功能:
1. 加载和预处理音频文件
2. 提取多维声学特征
3. 计算统计聚合特征
4. 提供特征解释和可视化数据
"""
def __init__(self, config):
"""
初始化音频分析器
Args:
config: 系统配置对象
"""
self.config = config
self.sample_rate = config.AUDIO_SAMPLE_RATE
self.duration = config.AUDIO_DURATION
self.n_fft = config.N_FFT
self.hop_length = config.HOP_LENGTH
self.n_mels = config.N_MELS
logger.info(f"音频分析器初始化完成 | 采样率: {self.sample_rate}Hz | 分析时长: {self.duration}s")
def load_audio(self, file_path: str) -> Tuple[np.ndarray, int]:
"""
加载音频文件
Args:
file_path: 音频文件路径(支持mp3, wav, flac等格式)
Returns:
(音频时间序列, 采样率) 元组
"""
logger.info(f"正在加载音频文件: {file_path}")
try:
# 使用librosa加载音频
# sr=None 保持原始采样率
y, sr = librosa.load(
file_path,
sr=self.sample_rate,
duration=self.duration
)
logger.info(f"音频加载成功 | 时长: {len(y)/sr:.2f}s | 采样率: {sr}Hz")
return y, sr
except Exception as e:
logger.error(f"音频加载失败: {str(e)}")
raise ValueError(f"无法加载音频文件: {file_path}. 错误: {str(e)}")
def extract_features(self, file_path: str) -> AudioFeatures:
"""
从音频文件中提取完整特征集
特征提取流程:
1. 加载音频
2. 节拍检测
3. 频谱特征提取
4. 能量特征计算
5. MFCC提取
6. 统计聚合
7. 派生特征计算
Args:
file_path: 音频文件路径
Returns:
AudioFeatures对象,包含所有提取的特征
"""
logger.info(f"开始特征提取: {file_path}")
# 1. 加载音频
y, sr = self.load_audio(file_path)
duration = len(y) / sr
# 2. 节拍检测
tempo, beat_frames = librosa.beat.beat_track(
y=y,
sr=sr,
hop_length=self.hop_length
)
beat_times = librosa.frames_to_time(beat_frames, sr=sr, hop_length=self.hop_length)
# 3. 频谱特征
spectral_centroid = librosa.feature.spectral_centroid(
y=y, sr=sr, n_fft=self.n_fft, hop_length=self.hop_length
)[0]
spectral_bandwidth = librosa.feature.spectral_bandwidth(
y=y, sr=sr, n_fft=self.n_fft, hop_length=self.hop_length
)[0]
spectral_rolloff = librosa.feature.spectral_rolloff(
y=y, sr=sr, n_fft=self.n_fft, hop_length=self.hop_length
)[0]
spectral_contrast = librosa.feature.spectral_contrast(
y=y, sr=sr, n_fft=self.n_fft, hop_length=self.hop_length
)
# 4. 能量特征
rms_energy = librosa.feature.rms(
y=y, frame_length=self.n_fft, hop_length=self.hop_length
)[0]
zero_crossing_rate = librosa.feature.zero_crossing_rate(
y=y, frame_length=self.n_fft, hop_length=self.hop_length
)[0]
# 5. MFCC特征
mfcc = librosa.feature.mfcc(
y=y, sr=sr, n_mfcc=13, n_fft=self.n_fft, hop_length=self.hop_length
)
# 6. 计算统计特征
mean_features = self._calculate_mean_features({
'spectral_centroid': spectral_centroid,
'spectral_bandwidth': spectral_bandwidth,
'spectral_rolloff': spectral_rolloff,
'rms_energy': rms_energy,
'zero_crossing_rate': zero_crossing_rate,
'mfcc': mfcc
})
std_features = self._calculate_std_features({
'spectral_centroid': spectral_centroid,
'spectral_bandwidth': spectral_bandwidth,
'spectral_rolloff': spectral_rolloff,
'rms_energy': rms_energy,
'zero_crossing_rate': zero_crossing_rate
})
trend_features = self._calculate_trend_features({
'spectral_centroid': spectral_centroid,
'rms_energy': rms_energy
})
# 7. 计算派生特征
danceability = self._calculate_danceability(tempo, rms_energy, spectral_contrast)
energy_level = self._calculate_energy_level(rms_energy, spectral_centroid)
valence_proxy = self._calculate_valence_proxy(
spectral_centroid, spectral_rolloff, rms_energy, tempo
)
arousal_proxy = self._calculate_arousal_proxy(
rms_energy, zero_crossing_rate, tempo, spectral_contrast
)
brightness = self._calculate_brightness(spectral_centroid, spectral_rolloff)
roughness = self._calculate_roughness(zero_crossing_rate, spectral_contrast)
features = AudioFeatures(
duration=duration,
sample_rate=sr,
tempo=tempo,
beat_frames=beat_frames,
beat_times=beat_times,
spectral_centroid=spectral_centroid,
spectral_bandwidth=spectral_bandwidth,
spectral_rolloff=spectral_rolloff,
spectral_contrast=spectral_contrast,
rms_energy=rms_energy,
zero_crossing_rate=zero_crossing_rate,
mfcc=mfcc,
mean_features=mean_features,
std_features=std_features,
trend_features=trend_features,
danceability=danceability,
energy_level=energy_level,
valence_proxy=valence_proxy,
arousal_proxy=arousal_proxy,
brightness=brightness,
roughness=roughness
)
logger.info("特征提取完成")
logger.debug(f"Temp
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!