EG/plugins/user/dynamic_music_system/composition/composition_engine.py
2025-12-12 16:16:15 +08:00

1581 lines
60 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
作曲引擎
负责动态生成音乐内容
"""
import uuid
import math
import time
import numpy as np
from typing import Dict, List, Any, Optional, Callable
from scipy import signal
import threading
import queue
class CompositionEngine:
"""
作曲引擎
负责动态生成音乐内容
"""
def __init__(self, plugin):
"""
初始化作曲引擎
Args:
plugin: 动态音乐系统插件实例
"""
self.plugin = plugin
self.generators: Dict[str, Dict[str, Any]] = {}
self.active_generators: Dict[str, Dict[str, Any]] = {}
self.composed_segments: Dict[str, Dict[str, Any]] = {}
self.generator_threads: Dict[str, threading.Thread] = {}
self.generator_queues: Dict[str, queue.Queue] = {}
self.stats = {
'generators_created': 0,
'generators_active': 0,
'segments_composed': 0,
'composition_time': 0.0,
'memory_usage': 0,
'failed_compositions': 0
}
self.sample_rate = 44100
self.buffer_size = plugin.buffer_size if plugin else 4096
self.master_key = 'C'
self.master_scale = 'major'
self.master_bpm = 120
self.max_polyphony = 16
# 音乐理论数据
self._initialize_music_theory()
# 作曲参数
self.composition_settings = {
'max_segment_duration': 30.0, # 最大片段时长
'min_segment_duration': 5.0, # 最小片段时长
'segment_overlap': 0.5, # 片段重叠比例
'lookahead_time': 2.0, # 预生成时间
'complexity_range': (0.2, 0.8) # 复杂度范围
}
# 音色库
self.instrument_presets = {
'piano': {'attack': 0.01, 'decay': 0.1, 'sustain': 0.7, 'release': 0.3},
'strings': {'attack': 0.2, 'decay': 0.1, 'sustain': 0.8, 'release': 0.5},
'brass': {'attack': 0.05, 'decay': 0.1, 'sustain': 0.9, 'release': 0.2},
'woodwind': {'attack': 0.1, 'decay': 0.05, 'sustain': 0.6, 'release': 0.3},
'guitar': {'attack': 0.02, 'decay': 0.2, 'sustain': 0.5, 'release': 0.4},
'synth': {'attack': 0.005, 'decay': 0.05, 'sustain': 0.9, 'release': 0.1}
}
# 和声进行数据库
self.chord_progressions = {
'major': [
['I', 'V', 'vi', 'IV'], # 流行进行
['I', 'vi', 'IV', 'V'], # 古典进行
['I', 'IV', 'V', 'I'], # 终止进行
['I', 'iii', 'IV', 'V'], # 扩展进行
['I', 'vi', 'ii', 'V'] # 2-5-1进行
],
'minor': [
['i', 'V', 'VI', 'III'], # 小调流行进行
['i', 'bVII', 'VI', 'VII'], # 模态进行
['i', 'iv', 'V', 'i'], # 小调终止进行
['i', 'bVI', 'bIII', 'V'], # 情感进行
['i', 'bVII', 'bVI', 'V'] # 蓝调进行
]
}
# 初始化作曲引擎
self._initialize_composition_engine()
def _initialize_music_theory(self):
"""初始化音乐理论数据"""
# 音符频率表 (基于A4=440Hz)
self.note_frequencies = {}
a4_freq = 440.0
for i in range(-60, 60): # 支持多个八度
note_name = self._get_note_name(i)
self.note_frequencies[note_name] = a4_freq * (2 ** (i / 12))
# 音阶模式
self.scale_patterns = {
'major': [0, 2, 4, 5, 7, 9, 11],
'minor': [0, 2, 3, 5, 7, 8, 10],
'harmonic_minor': [0, 2, 3, 5, 7, 8, 11],
'melodic_minor': [0, 2, 3, 5, 7, 9, 11],
'dorian': [0, 2, 3, 5, 7, 9, 10],
'phrygian': [0, 1, 3, 5, 7, 8, 10],
'lydian': [0, 2, 4, 6, 7, 9, 11],
'mixolydian': [0, 2, 4, 5, 7, 9, 10],
'aeolian': [0, 2, 3, 5, 7, 8, 10],
'locrian': [0, 1, 3, 5, 6, 8, 10]
}
# 和弦模式
self.chord_patterns = {
'major': [0, 4, 7],
'minor': [0, 3, 7],
'diminished': [0, 3, 6],
'augmented': [0, 4, 8],
'sus2': [0, 2, 7],
'sus4': [0, 5, 7],
'7th': [0, 4, 7, 10],
'maj7': [0, 4, 7, 11],
'min7': [0, 3, 7, 10],
'dim7': [0, 3, 6, 9],
'm7b5': [0, 3, 6, 10]
}
# 罗马数字和弦映射
self.roman_numerals = {
'I': 0, 'II': 1, 'III': 2, 'IV': 3, 'V': 4, 'VI': 5, 'VII': 6,
'i': 0, 'ii': 1, 'iii': 2, 'iv': 3, 'v': 4, 'vi': 5, 'vii': 6,
'bII': 10, 'bIII': 11, 'bVI': 14, 'bVII': 15 # 用于小调
}
# 音符名称
self.note_names = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
# 节拍模式
self.rhythm_patterns = {
'4/4': [1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 0.5], # 基本4/4拍
'3/4': [1.0, 0.5, 0.5], # 华尔兹
'6/8': [1.5, 0.5, 0.5, 1.5, 0.5, 0.5], # 6/8拍
'syncopated': [0.5, 0.5, 1.0, 0.5, 0.5, 1.0], # 切分音
'swing': [1.33, 0.67, 1.33, 0.67] # 摇摆节奏
}
def _get_note_name(self, semitone_offset: int) -> str:
"""
根据半音偏移量获取音符名称
Args:
semitone_offset: 相对于A4的半音偏移量
Returns:
音符名称
"""
note_index = (9 + semitone_offset) % 12 # A是第9个音符
octave = 4 + (9 + semitone_offset) // 12
return f"{self.note_names[note_index]}{octave}"
def _initialize_composition_engine(self):
"""初始化作曲引擎"""
print("✓ 作曲引擎初始化完成")
def create_generator(self, generator_name: str, generator_params: Dict[str, Any]) -> bool:
"""
创建音乐生成器
Args:
generator_name: 生成器名称
generator_params: 生成器参数
Returns:
是否创建成功
"""
try:
# 设置默认参数
default_params = {
'type': 'melody', # melody, harmony, rhythm, bass
'key': self.master_key,
'scale': self.master_scale,
'bpm': self.master_bpm,
'octave': 4,
'complexity': 0.5, # 0.0-1.0
'variation': 0.3, # 0.0-1.0
'length': 8, # 小节数
'instrument': 'piano',
'arpeggiate': False,
'syncopation': 0.0, # 0.0-1.0
'polyphony': 1, # 同时发声数
'mode': 'loop', # loop, once, random
'tempo_sync': True, # 是否与主BPM同步
'dynamic_range': (0.6, 1.0) # 音量范围
}
# 合并参数
params = default_params.copy()
params.update(generator_params)
# 验证参数
if params['type'] not in ['melody', 'harmony', 'rhythm', 'bass']:
print(f"✗ 无效的生成器类型: {params['type']}")
return False
if params['scale'] not in self.scale_patterns:
print(f"✗ 无效的音阶: {params['scale']}")
return False
if params['instrument'] not in self.instrument_presets:
print(f"⚠ 未知乐器: {params['instrument']},使用默认钢琴音色")
params['instrument'] = 'piano'
# 验证复杂度和变化度范围
params['complexity'] = max(0.0, min(1.0, params['complexity']))
params['variation'] = max(0.0, min(1.0, params['variation']))
params['syncopation'] = max(0.0, min(1.0, params['syncopation']))
params['polyphony'] = max(1, min(self.max_polyphony, params['polyphony']))
# 创建生成器
generator = {
'name': generator_name,
'params': params,
'state': 'created',
'segments': [],
'current_segment': 0,
'playback_position': 0.0,
'last_generated_time': 0.0,
'is_generating': False,
'thread': None,
'queue': queue.Queue()
}
self.generators[generator_name] = generator
self.generator_queues[generator_name] = generator['queue']
self.stats['generators_created'] += 1
print(f"✓ 音乐生成器创建成功: {generator_name}")
return True
except Exception as e:
print(f"✗ 创建音乐生成器失败: {e}")
import traceback
traceback.print_exc()
return False
def activate_generator(self, generator_name: str) -> bool:
"""
激活音乐生成器
Args:
generator_name: 生成器名称
Returns:
是否激活成功
"""
try:
if generator_name not in self.generators:
print(f"✗ 音乐生成器不存在: {generator_name}")
return False
generator = self.generators[generator_name]
# 启动生成器线程
if generator['thread'] is None or not generator['thread'].is_alive():
generator['thread'] = threading.Thread(
target=self._generator_worker,
args=(generator_name,),
daemon=True
)
generator['thread'].start()
# 生成初始音乐片段
if self._generate_segment(generator_name):
self.active_generators[generator_name] = self.generators[generator_name]
self.stats['generators_active'] = len(self.active_generators)
generator['state'] = 'active'
print(f"✓ 音乐生成器已激活: {generator_name}")
return True
else:
print(f"✗ 生成音乐片段失败: {generator_name}")
return False
except Exception as e:
print(f"✗ 激活音乐生成器失败: {e}")
import traceback
traceback.print_exc()
return False
def deactivate_generator(self, generator_name: str) -> bool:
"""
停用音乐生成器
Args:
generator_name: 生成器名称
Returns:
是否停用成功
"""
try:
if generator_name not in self.active_generators:
print(f"⚠ 音乐生成器未激活: {generator_name}")
return True
generator = self.generators[generator_name]
generator['state'] = 'inactive'
del self.active_generators[generator_name]
self.stats['generators_active'] = len(self.active_generators)
print(f"✓ 音乐生成器已停用: {generator_name}")
return True
except Exception as e:
print(f"✗ 停用音乐生成器失败: {e}")
import traceback
traceback.print_exc()
return False
def _generator_worker(self, generator_name: str):
"""
生成器工作线程
Args:
generator_name: 生成器名称
"""
try:
while generator_name in self.generators:
generator = self.generators[generator_name]
if generator['state'] == 'active':
# 检查是否需要生成新片段
if self._should_generate_new_segment(generator):
self._generate_segment(generator_name)
# 处理队列中的请求
try:
request = generator['queue'].get(timeout=0.1)
if request['type'] == 'generate':
self._generate_segment(generator_name)
elif request['type'] == 'stop':
break
except queue.Empty:
pass
time.sleep(0.01) # 10ms延迟
except Exception as e:
print(f"✗ 生成器工作线程错误: {e}")
import traceback
traceback.print_exc()
def _generate_segment(self, generator_name: str) -> bool:
"""
生成音乐片段
Args:
generator_name: 生成器名称
Returns:
是否生成成功
"""
try:
if generator_name not in self.generators:
return False
generator = self.generators[generator_name]
params = generator['params']
# 检查是否正在生成
if generator['is_generating']:
return True # 避免重复生成
generator['is_generating'] = True
# 生成唯一片段ID
segment_id = f"{generator_name}_{int(time.time() * 1000000)}"
# 根据生成器类型生成音乐数据
if params['type'] == 'melody':
segment_data = self._generate_melody_segment(params)
elif params['type'] == 'harmony':
segment_data = self._generate_harmony_segment(params)
elif params['type'] == 'rhythm':
segment_data = self._generate_rhythm_segment(params)
elif params['type'] == 'bass':
segment_data = self._generate_bass_segment(params)
else:
segment_data = self._generate_melody_segment(params) # 默认生成旋律
# 创建片段
segment = {
'id': segment_id,
'generator': generator_name,
'data': segment_data,
'created_time': time.time(),
'duration': params['length'] * (60.0 / params['bpm']) * 4, # 假设4/4拍
'bpm': params['bpm'],
'key': params['key'],
'scale': params['scale'],
'type': params['type']
}
# 存储片段
self.composed_segments[segment_id] = segment
generator['segments'].append(segment_id)
# 限制片段数量
if len(generator['segments']) > 10: # 最多保留10个片段
old_segment_id = generator['segments'].pop(0)
if old_segment_id in self.composed_segments:
del self.composed_segments[old_segment_id]
self.stats['segments_composed'] += 1
generator['last_generated_time'] = time.time()
generator['is_generating'] = False
print(f"✓ 音乐片段生成成功: {segment_id}")
return True
except Exception as e:
print(f"✗ 生成音乐片段失败: {e}")
import traceback
traceback.print_exc()
if generator_name in self.generators:
self.generators[generator_name]['is_generating'] = False
self.stats['failed_compositions'] += 1
return False
def _generate_melody_segment(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
生成旋律片段
Args:
params: 生成参数
Returns:
旋律片段数据
"""
try:
# 获取音阶音符
scale_notes = self._get_scale_notes(params['key'], params['scale'], params['octave'])
# 计算片段长度(以拍为单位)
beats_per_measure = 4 # 假设4/4拍
total_beats = params['length'] * beats_per_measure
# 选择节拍模式
time_signature = '4/4'
if params['syncopation'] > 0.7:
rhythm_pattern = self.rhythm_patterns['syncopated']
elif params['syncopation'] > 0.4:
rhythm_pattern = self.rhythm_patterns['swing']
else:
rhythm_pattern = self.rhythm_patterns[time_signature]
# 生成音符序列
notes = []
current_time = 0.0
beat_index = 0
while current_time < total_beats:
# 选择音符
note_index = self._choose_note(scale_notes, params['complexity'], params['variation'])
note_name = scale_notes[note_index]
note_freq = self.note_frequencies.get(note_name, 440.0)
# 选择时值
duration = rhythm_pattern[beat_index % len(rhythm_pattern)]
# 应用复杂度调整时值
if params['complexity'] > 0.7:
# 增加装饰音和复杂节奏
if np.random.random() < 0.3:
duration *= 0.5 # 八分音符
# 添加装饰音
if note_index > 0:
grace_note_index = max(0, note_index - 1)
grace_note_name = scale_notes[grace_note_index]
grace_note_freq = self.note_frequencies.get(grace_note_name, 440.0)
notes.append({
'frequency': grace_note_freq,
'duration': duration * 0.3,
'start_time': current_time,
'velocity': 0.4 + np.random.random() * 0.2,
'type': 'grace'
})
current_time += duration * 0.3
# 添加主要音符
velocity_range = params.get('dynamic_range', (0.6, 1.0))
velocity = velocity_range[0] + np.random.random() * (velocity_range[1] - velocity_range[0])
notes.append({
'frequency': note_freq,
'duration': duration,
'start_time': current_time,
'velocity': velocity,
'type': 'main'
})
current_time += duration
beat_index += 1
# 避免无限循环
if beat_index > total_beats * 4: # 限制最多4倍拍数
break
# 应用琶音效果
if params['arpeggiate']:
notes = self._apply_arpeggiation(notes, params['syncopation'])
return {
'type': 'melody',
'notes': notes,
'bpm': params['bpm'],
'key': params['key'],
'scale': params['scale'],
'instrument': params['instrument']
}
except Exception as e:
print(f"✗ 生成旋律片段失败: {e}")
import traceback
traceback.print_exc()
return {
'type': 'melody',
'notes': [],
'bpm': params['bpm'],
'key': params['key'],
'scale': params['scale'],
'instrument': params['instrument']
}
def _generate_harmony_segment(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
生成和声片段
Args:
params: 生成参数
Returns:
和声片段数据
"""
try:
# 选择和弦进行
progressions = self.chord_progressions.get(params['scale'], self.chord_progressions['major'])
chord_progression = np.random.choice(progressions)
# 计算片段长度
beats_per_measure = 4
total_measures = params['length']
# 生成和弦序列
chords = []
current_time = 0.0
measure_duration = beats_per_measure * (60.0 / params['bpm'])
for measure in range(total_measures):
# 获取和弦
chord_index = measure % len(chord_progression)
roman_numeral = chord_progression[chord_index]
# 转换罗马数字为和弦
scale_degree = self.roman_numerals.get(roman_numeral, 0)
chord_type = 'major' if roman_numeral.isupper() else 'minor'
# 特殊处理小调中的降号和弦
if roman_numeral.startswith('b'):
scale_degree = self.roman_numerals.get(roman_numeral, scale_degree)
if 'VII' in roman_numeral:
chord_type = 'major'
elif 'VI' in roman_numeral or 'III' in roman_numeral:
chord_type = 'major'
# 获取和弦音符
scale_notes = self._get_scale_notes(params['key'], params['scale'], params['octave'] - 1)
chord_root_index = scale_degree % len(scale_notes)
chord_root = scale_notes[chord_root_index]
chord_notes = self._get_chord_notes(chord_root, chord_type, params['octave'] - 1)
# 计算和弦频率
frequencies = [self.note_frequencies.get(note, 440.0) for note in chord_notes]
# 应用琶音效果
arpeggiated = params['arpeggiate'] and np.random.random() < 0.5
if arpeggiated:
# 生成琶音音符
arp_notes = []
arp_duration = measure_duration / len(chord_notes)
for i, freq in enumerate(frequencies):
arp_notes.append({
'frequency': freq,
'duration': arp_duration,
'start_time': current_time + i * arp_duration,
'velocity': 0.5 + np.random.random() * 0.3,
'type': 'arpeggio'
})
chords.extend(arp_notes)
else:
# 生成和弦
velocity_range = params.get('dynamic_range', (0.5, 0.9))
velocity = velocity_range[0] + np.random.random() * (velocity_range[1] - velocity_range[0])
chords.append({
'notes': chord_notes,
'frequencies': frequencies,
'duration': measure_duration,
'start_time': current_time,
'velocity': velocity,
'type': 'chord'
})
current_time += measure_duration
return {
'type': 'harmony',
'chords': chords,
'progression': chord_progression,
'bpm': params['bpm'],
'key': params['key'],
'scale': params['scale'],
'instrument': params['instrument']
}
except Exception as e:
print(f"✗ 生成和声片段失败: {e}")
import traceback
traceback.print_exc()
return {
'type': 'harmony',
'chords': [],
'progression': [],
'bpm': params['bpm'],
'key': params['key'],
'scale': params['scale'],
'instrument': params['instrument']
}
def _generate_rhythm_segment(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
生成节奏片段
Args:
params: 生成参数
Returns:
节奏片段数据
"""
try:
# 生成鼓组节奏
drums = ['kick', 'snare', 'hihat', 'crash', 'tom_low', 'tom_mid', 'tom_high']
drum_frequencies = {
'kick': 60.0,
'snare': 200.0,
'hihat': 8000.0,
'crash': 4000.0,
'tom_low': 120.0,
'tom_mid': 250.0,
'tom_high': 500.0
}
beats_per_measure = 4
total_beats = params['length'] * beats_per_measure
hits = []
current_time = 0.0
beat_duration = 60.0 / params['bpm']
# 基本节奏模式
basic_pattern = {
'kick': [0, 2], # 强拍
'snare': [1, 3], # 弱拍
'hihat': [0, 1, 2, 3] # 每拍
}
# 根据复杂度调整模式
if params['complexity'] > 0.7:
# 复杂节奏
basic_pattern['kick'] = [0, 0.5, 2, 2.5, 3.5]
basic_pattern['snare'] = [1, 3]
basic_pattern['hihat'] = [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5]
basic_pattern['tom_low'] = [1.5]
basic_pattern['tom_mid'] = [2.5]
basic_pattern['tom_high'] = [3.5]
elif params['complexity'] > 0.4:
# 中等复杂度
basic_pattern['kick'] = [0, 2]
basic_pattern['snare'] = [1, 3]
basic_pattern['hihat'] = [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5]
# 生成节奏
for measure in range(params['length']):
measure_start = measure * beats_per_measure
for drum_type, pattern in basic_pattern.items():
if drum_type in drum_frequencies:
for beat_offset in pattern:
hit_time = (measure_start + beat_offset) * beat_duration
velocity_range = params.get('dynamic_range', (0.6, 1.0))
velocity = velocity_range[0] + np.random.random() * (velocity_range[1] - velocity_range[0])
# 应用切分音效果
if params['syncopation'] > 0.5 and np.random.random() < 0.3:
# 偶尔偏移时间
hit_time += (np.random.random() - 0.5) * beat_duration * 0.2
hits.append({
'drum': drum_type,
'frequency': drum_frequencies[drum_type],
'time': hit_time,
'velocity': velocity,
'duration': beat_duration * 0.1 # 短促的打击乐
})
# 添加填充fills
if np.random.random() < 0.3: # 30%概率添加填充
fill_start = (params['length'] - 1) * beats_per_measure * beat_duration
for i in range(4): # 4个填充音符
drum_type = np.random.choice(['snare', 'tom_low', 'tom_mid', 'tom_high'])
hit_time = fill_start + i * beat_duration * 0.25
hits.append({
'drum': drum_type,
'frequency': drum_frequencies[drum_type],
'time': hit_time,
'velocity': 0.8 + np.random.random() * 0.2,
'duration': beat_duration * 0.05
})
return {
'type': 'rhythm',
'hits': hits,
'bpm': params['bpm'],
'pattern_complexity': params['complexity']
}
except Exception as e:
print(f"✗ 生成节奏片段失败: {e}")
import traceback
traceback.print_exc()
return {
'type': 'rhythm',
'hits': [],
'bpm': params['bpm'],
'pattern_complexity': params['complexity']
}
def _generate_bass_segment(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
生成贝斯片段
Args:
params: 生成参数
Returns:
贝斯片段数据
"""
try:
# 获取低音音阶音符
scale_notes = self._get_scale_notes(params['key'], params['scale'], params['octave'] - 1)
# 计算片段长度
beats_per_measure = 4
total_beats = params['length'] * beats_per_measure
beat_duration = 60.0 / params['bpm']
# 生成贝斯线
notes = []
current_time = 0.0
beat_count = 0
# 使用和弦进行作为贝斯线基础
progressions = self.chord_progressions.get(params['scale'], self.chord_progressions['major'])
chord_progression = np.random.choice(progressions)
while current_time < total_beats * beat_duration and beat_count < total_beats:
# 确定当前小节和和弦
measure = beat_count // beats_per_measure
chord_index = measure % len(chord_progression)
roman_numeral = chord_progression[chord_index]
# 获取和弦根音作为贝斯音符
scale_degree = self.roman_numerals.get(roman_numeral, 0)
bass_note_index = scale_degree % len(scale_notes)
bass_note = scale_notes[bass_note_index]
bass_freq = self.note_frequencies.get(bass_note, 65.41) # C2
# 确定音符时值
if np.random.random() < params['complexity'] * 0.5:
# 复杂节奏:使用八分音符
note_duration = beat_duration * 0.5
subdivisions = 2
else:
# 简单节奏:使用四分音符
note_duration = beat_duration
subdivisions = 1
# 生成音符
for sub in range(subdivisions):
if beat_count + sub * 0.5 < total_beats:
note_time = current_time + sub * note_duration
# 应用滑音和装饰
if params['complexity'] > 0.6 and np.random.random() < 0.2:
# 添加经过音
if bass_note_index < len(scale_notes) - 1:
passing_note_index = bass_note_index + 1
passing_note = scale_notes[passing_note_index]
passing_freq = self.note_frequencies.get(passing_note, 65.41)
notes.append({
'frequency': passing_freq,
'duration': note_duration * 0.3,
'start_time': note_time,
'velocity': 0.4 + np.random.random() * 0.2,
'type': 'passing'
})
note_time += note_duration * 0.3
# 主要贝斯音符
velocity_range = params.get('dynamic_range', (0.7, 1.0))
velocity = velocity_range[0] + np.random.random() * (velocity_range[1] - velocity_range[0])
notes.append({
'frequency': bass_freq,
'duration': note_duration * (1 - 0.3 * (sub > 0)), # 经过音后缩短
'start_time': note_time,
'velocity': velocity,
'type': 'bass'
})
current_time += beat_duration
beat_count += 1
return {
'type': 'bass',
'notes': notes,
'bpm': params['bpm'],
'key': params['key'],
'scale': params['scale'],
'instrument': params['instrument'],
'progression': chord_progression
}
except Exception as e:
print(f"✗ 生成贝斯片段失败: {e}")
import traceback
traceback.print_exc()
return {
'type': 'bass',
'notes': [],
'bpm': params['bpm'],
'key': params['key'],
'scale': params['scale'],
'instrument': params['instrument'],
'progression': []
}
def _get_scale_notes(self, key: str, scale: str, octave: int) -> List[str]:
"""
获取音阶中的音符
Args:
key: 调号
scale: 音阶类型
octave: 八度
Returns:
音符列表
"""
try:
# 找到调号在音符列表中的索引
key_index = self.note_names.index(key.upper())
# 获取音阶模式
pattern = self.scale_patterns[scale]
# 生成音符
notes = []
for semitone in pattern:
note_index = (key_index + semitone) % 12
note_octave = octave + (key_index + semitone) // 12
note_name = f"{self.note_names[note_index]}{note_octave}"
notes.append(note_name)
return notes
except Exception as e:
print(f"✗ 获取音阶音符失败: {e}")
# 返回默认C大调音阶
return [f"{note}{octave}" for note in ['C', 'D', 'E', 'F', 'G', 'A', 'B']]
def _get_chord_notes(self, root: str, chord_type: str, octave: int) -> List[str]:
"""
获取和弦音符
Args:
root: 根音
chord_type: 和弦类型
octave: 八度
Returns:
和弦音符列表
"""
try:
# 找到根音在音符列表中的索引
root_index = self.note_names.index(root[:-1]) # 去掉八度数字
root_octave = int(root[-1])
# 获取和弦模式
pattern = self.chord_patterns.get(chord_type, self.chord_patterns['major'])
# 生成和弦音符
notes = []
for i, semitone in enumerate(pattern):
note_index = (root_index + semitone) % 12
note_octave = root_octave + (root_index + semitone) // 12
# 保证和弦音符在合理的八度范围内
if i > 0 and note_octave <= root_octave:
note_octave += 1
note_name = f"{self.note_names[note_index]}{note_octave}"
notes.append(note_name)
return notes
except Exception as e:
print(f"✗ 获取和弦音符失败: {e}")
# 返回默认三和弦
return [root, f"{root[:-1]}{int(root[-1])+1}", f"{root[:-1]}{int(root[-1])+2}"]
def _choose_note(self, scale_notes: List[str], complexity: float, variation: float) -> int:
"""
选择音符
Args:
scale_notes: 音阶音符列表
complexity: 复杂度 (0.0-1.0)
variation: 变化度 (0.0-1.0)
Returns:
音符索引
"""
# 基于复杂度和变化度选择音符
if np.random.random() < complexity:
# 复杂选择:可以跳到任何音符
return np.random.randint(0, len(scale_notes))
else:
# 简单选择:倾向于选择相邻音符
if len(scale_notes) > 1:
current_index = np.random.randint(0, len(scale_notes))
if np.random.random() < 0.7: # 70%概率选择相邻音符
offset = np.random.choice([-1, 1])
new_index = current_index + offset
if 0 <= new_index < len(scale_notes):
return new_index
return current_index
else:
return 0
def _apply_arpeggiation(self, notes: List[Dict[str, Any]], syncopation: float) -> List[Dict[str, Any]]:
"""
应用琶音效果
Args:
notes: 音符列表
syncopation: 切分音程度
Returns:
处理后的音符列表
"""
try:
arpeggiated_notes = []
for note in notes:
if note['type'] == 'chord':
# 将和弦分解为琶音
frequencies = note.get('frequencies', [])
duration = note['duration'] / len(frequencies)
for i, freq in enumerate(frequencies):
arpeggiated_notes.append({
'frequency': freq,
'duration': duration,
'start_time': note['start_time'] + i * duration,
'velocity': note['velocity'] * (0.8 + 0.2 * (i % 2)),
'type': 'arpeggio'
})
else:
arpeggiated_notes.append(note)
return arpeggiated_notes
except Exception as e:
print(f"✗ 应用琶音效果失败: {e}")
return notes
def update(self, dt: float):
"""
更新作曲引擎状态
Args:
dt: 时间增量
"""
update_start_time = time.time()
# 更新活动生成器
for generator_name, generator in self.active_generators.items():
# 更新播放位置
generator['playback_position'] += dt
# 检查是否需要生成新片段
if self._should_generate_new_segment(generator):
# 异步请求生成新片段
if generator['queue']:
try:
generator['queue'].put({'type': 'generate'}, block=False)
except queue.Full:
pass # 队列满时跳过
# 更新待处理生成器请求
self._process_generator_queues()
# 更新性能统计
self.stats['composition_time'] += (time.time() - update_start_time)
def _should_generate_new_segment(self, generator: Dict[str, Any]) -> bool:
"""
判断是否需要生成新片段
Args:
generator: 生成器数据
Returns:
是否需要生成
"""
try:
# 检查生成器状态
if generator['state'] != 'active':
return False
# 检查是否正在生成
if generator['is_generating']:
return False
# 检查时间条件
current_time = time.time()
time_since_last_generation = current_time - generator['last_generated_time']
# 如果没有片段或距离上次生成时间较长,则需要生成
if len(generator['segments']) < 2 or time_since_last_generation > 5.0:
return True
return False
except Exception as e:
print(f"✗ 检查生成条件失败: {e}")
return False
def _process_generator_queues(self):
"""处理生成器队列"""
# 这里可以处理一些队列相关的逻辑
pass
def cleanup(self):
"""清理所有资源"""
# 停止所有生成器线程
for generator_name, generator in self.generators.items():
if generator['queue']:
try:
generator['queue'].put({'type': 'stop'}, block=False)
except queue.Full:
pass
# 清理数据结构
self.generators.clear()
self.active_generators.clear()
self.composed_segments.clear()
self.generator_threads.clear()
self.generator_queues.clear()
self.stats = {
'generators_created': 0,
'generators_active': 0,
'segments_composed': 0,
'composition_time': 0.0,
'memory_usage': 0,
'failed_compositions': 0
}
print("✓ 作曲引擎资源已清理")
def get_stats(self) -> Dict[str, int]:
"""
获取统计信息
Returns:
统计信息字典
"""
return self.stats.copy()
def get_active_generators(self) -> List[str]:
"""
获取活动生成器列表
Returns:
活动生成器名称列表
"""
return list(self.active_generators.keys())
def get_generator_status(self, generator_name: str) -> Dict[str, Any]:
"""
获取生成器状态
Args:
generator_name: 生成器名称
Returns:
生成器状态字典
"""
if generator_name in self.active_generators:
generator = self.active_generators[generator_name]
return {
'name': generator_name,
'state': generator['state'],
'type': generator['params']['type'],
'segments': len(generator['segments']),
'active': True,
'playback_position': generator['playback_position']
}
elif generator_name in self.generators:
generator = self.generators[generator_name]
return {
'name': generator_name,
'state': generator['state'],
'type': generator['params']['type'],
'segments': len(generator['segments']),
'active': False,
'playback_position': generator['playback_position']
}
else:
return {
'name': generator_name,
'state': 'unknown'
}
def set_master_key(self, key: str):
"""
设置主调
Args:
key: 调号
"""
if key.upper() in self.note_names:
self.master_key = key.upper()
print(f"✓ 主调设置为: {self.master_key}")
else:
print(f"✗ 无效的调号: {key}")
def set_master_scale(self, scale: str):
"""
设置主音阶
Args:
scale: 音阶类型
"""
if scale in self.scale_patterns:
self.master_scale = scale
print(f"✓ 主音阶设置为: {self.master_scale}")
else:
print(f"✗ 无效的音阶: {scale}")
def set_master_bpm(self, bpm: float):
"""
设置主BPM
Args:
bpm: 每分钟节拍数
"""
self.master_bpm = max(30, min(300, bpm)) # 限制在合理范围内
print(f"✓ 主BPM设置为: {self.master_bpm}")
def generate_theme(self, theme_name: str, theme_params: Dict[str, Any]) -> bool:
"""
生成完整音乐主题
Args:
theme_name: 主题名称
theme_params: 主题参数
Returns:
是否生成成功
"""
try:
# 创建多个生成器来组成完整主题
generators_to_create = theme_params.get('generators', ['melody', 'harmony', 'rhythm'])
for generator_type in generators_to_create:
generator_name = f"{theme_name}_{generator_type}"
generator_params = {
'type': generator_type,
'key': theme_params.get('key', self.master_key),
'scale': theme_params.get('scale', self.master_scale),
'bpm': theme_params.get('bpm', self.master_bpm),
'length': theme_params.get('length', 8),
'complexity': theme_params.get('complexity', 0.5),
'variation': theme_params.get('variation', 0.3),
'instrument': theme_params.get('instrument_' + generator_type, 'piano'),
'arpeggiate': theme_params.get('arpeggiate', False),
'syncopation': theme_params.get('syncopation', 0.0)
}
self.create_generator(generator_name, generator_params)
print(f"✓ 音乐主题生成成功: {theme_name}")
return True
except Exception as e:
print(f"✗ 生成音乐主题失败: {e}")
import traceback
traceback.print_exc()
return False
def get_segment_data(self, segment_id: str) -> Dict[str, Any]:
"""
获取片段数据
Args:
segment_id: 片段ID
Returns:
片段数据
"""
return self.composed_segments.get(segment_id, {}).get('data', {})
def set_sample_rate(self, sample_rate: int):
"""
设置采样率
Args:
sample_rate: 采样率 (Hz)
"""
self.sample_rate = sample_rate
print(f"✓ 作曲引擎采样率设置为: {self.sample_rate} Hz")
def create_adaptive_generator(self, generator_name: str,
game_state_callback: Callable[[], Dict[str, Any]]) -> bool:
"""
创建自适应音乐生成器
Args:
generator_name: 生成器名称
game_state_callback: 游戏状态回调函数
Returns:
是否创建成功
"""
try:
# 创建自适应生成器参数
adaptive_params = {
'type': 'melody',
'key': self.master_key,
'scale': self.master_scale,
'bpm': self.master_bpm,
'octave': 4,
'complexity': 0.5,
'variation': 0.3,
'length': 8,
'instrument': 'piano',
'arpeggiate': False,
'syncopation': 0.0,
'adaptive': True,
'game_state_callback': game_state_callback
}
# 创建生成器
generator = {
'name': generator_name,
'params': adaptive_params,
'state': 'created',
'segments': [],
'current_segment': 0,
'playback_position': 0.0,
'last_generated_time': 0.0,
'is_generating': False,
'thread': None,
'queue': queue.Queue(),
'adaptive_callback': game_state_callback
}
self.generators[generator_name] = generator
self.generator_queues[generator_name] = generator['queue']
self.stats['generators_created'] += 1
print(f"✓ 自适应音乐生成器创建成功: {generator_name}")
return True
except Exception as e:
print(f"✗ 创建自适应音乐生成器失败: {e}")
import traceback
traceback.print_exc()
return False
def set_generator_parameter(self, generator_name: str, param_name: str, param_value: Any) -> bool:
"""
设置生成器参数
Args:
generator_name: 生成器名称
param_name: 参数名称
param_value: 参数值
Returns:
是否设置成功
"""
try:
if generator_name not in self.generators:
print(f"✗ 生成器不存在: {generator_name}")
return False
generator = self.generators[generator_name]
# 更新参数
generator['params'][param_name] = param_value
print(f"✓ 生成器 {generator_name} 参数 {param_name} 设置为: {param_value}")
return True
except Exception as e:
print(f"✗ 设置生成器参数失败: {e}")
import traceback
traceback.print_exc()
return False
def get_generator_parameters(self, generator_name: str) -> Dict[str, Any]:
"""
获取生成器参数
Args:
generator_name: 生成器名称
Returns:
参数字典
"""
if generator_name in self.generators:
return self.generators[generator_name]['params'].copy()
return {}
def regenerate_segment(self, generator_name: str) -> bool:
"""
重新生成片段
Args:
generator_name: 生成器名称
Returns:
是否重新生成成功
"""
try:
if generator_name not in self.generators:
print(f"✗ 生成器不存在: {generator_name}")
return False
return self._generate_segment(generator_name)
except Exception as e:
print(f"✗ 重新生成片段失败: {e}")
import traceback
traceback.print_exc()
return False
def set_instrument_preset(self, instrument_name: str, preset: Dict[str, float]):
"""
设置乐器预设
Args:
instrument_name: 乐器名称
preset: 预设参数字典 (attack, decay, sustain, release)
"""
required_params = ['attack', 'decay', 'sustain', 'release']
for param in required_params:
if param not in preset:
print(f"✗ 乐器预设缺少必需参数: {param}")
return
self.instrument_presets[instrument_name] = preset.copy()
print(f"✓ 乐器预设 {instrument_name} 已更新")
def get_instrument_preset(self, instrument_name: str) -> Dict[str, float]:
"""
获取乐器预设
Args:
instrument_name: 乐器名称
Returns:
预设参数字典
"""
return self.instrument_presets.get(instrument_name, self.instrument_presets['piano']).copy()
def set_composition_setting(self, setting_name: str, value: Any):
"""
设置作曲参数
Args:
setting_name: 参数名称
value: 参数值
"""
self.composition_settings[setting_name] = value
print(f"✓ 作曲参数 {setting_name} 设置为: {value}")
def get_composition_setting(self, setting_name: str) -> Any:
"""
获取作曲参数
Args:
setting_name: 参数名称
Returns:
参数值
"""
return self.composition_settings.get(setting_name, None)
def generate_audio_buffer(self, generator_name: str, buffer_size: int = None) -> np.ndarray:
"""
生成音频缓冲区数据
Args:
generator_name: 生成器名称
buffer_size: 缓冲区大小
Returns:
音频数据数组
"""
try:
if buffer_size is None:
buffer_size = self.buffer_size
if generator_name not in self.active_generators:
return np.zeros(buffer_size)
generator = self.active_generators[generator_name]
if not generator['segments']:
return np.zeros(buffer_size)
# 获取当前片段
current_segment_id = generator['segments'][generator['current_segment'] % len(generator['segments'])]
segment_data = self.get_segment_data(current_segment_id)
# 生成音频数据
audio_buffer = np.zeros(buffer_size)
t = np.linspace(0, buffer_size / self.sample_rate, buffer_size, False)
if segment_data.get('type') == 'melody':
notes = segment_data.get('notes', [])
for note in notes:
freq = note.get('frequency', 440.0)
duration = note.get('duration', 1.0)
start_time = note.get('start_time', 0.0)
velocity = note.get('velocity', 0.7)
# 计算样本索引
start_sample = int(start_time * self.sample_rate)
end_sample = min(buffer_size, int((start_time + duration) * self.sample_rate))
if start_sample < buffer_size and end_sample > start_sample:
# 生成正弦波
note_samples = end_sample - start_sample
note_t = np.linspace(0, duration, note_samples, False)
wave = velocity * np.sin(2 * np.pi * freq * note_t)
# 应用包络
envelope = self._create_envelope(note_samples, 'piano')
wave *= envelope
audio_buffer[start_sample:end_sample] += wave
elif segment_data.get('type') == 'rhythm':
hits = segment_data.get('hits', [])
for hit in hits:
freq = hit.get('frequency', 200.0)
hit_time = hit.get('time', 0.0)
duration = hit.get('duration', 0.1)
velocity = hit.get('velocity', 0.8)
# 计算样本索引
start_sample = int(hit_time * self.sample_rate)
end_sample = min(buffer_size, int((hit_time + duration) * self.sample_rate))
if start_sample < buffer_size and end_sample > start_sample:
# 生成打击乐声音
hit_samples = end_sample - start_sample
hit_t = np.linspace(0, duration, hit_samples, False)
# 生成噪声为基础的打击乐
noise = np.random.random(hit_samples) * 2 - 1
# 应用滤波器模拟不同打击乐器
b, a = signal.butter(4, freq / (self.sample_rate / 2), 'low')
filtered_noise = signal.lfilter(b, a, noise)
# 应用包络
envelope = self._create_envelope(hit_samples, 'percussion')
wave = velocity * filtered_noise * envelope
audio_buffer[start_sample:end_sample] += wave
# 限制幅度防止削波
audio_buffer = np.clip(audio_buffer, -1.0, 1.0)
return audio_buffer
except Exception as e:
print(f"✗ 生成音频缓冲区失败: {e}")
import traceback
traceback.print_exc()
return np.zeros(buffer_size if buffer_size else self.buffer_size)
def _create_envelope(self, sample_count: int, instrument_type: str) -> np.ndarray:
"""
创建包络
Args:
sample_count: 样本数量
instrument_type: 乐器类型
Returns:
包络数组
"""
try:
preset = self.instrument_presets.get(instrument_type, self.instrument_presets['piano'])
attack_samples = int(preset['attack'] * self.sample_rate)
decay_samples = int(preset['decay'] * self.sample_rate)
release_samples = int(preset['release'] * self.sample_rate)
sustain_level = preset['sustain']
envelope = np.ones(sample_count)
# 攻击阶段
if attack_samples > 0 and attack_samples < sample_count:
envelope[:attack_samples] = np.linspace(0, 1, attack_samples)
# 衰减阶段
decay_start = attack_samples
decay_end = attack_samples + decay_samples
if decay_end < sample_count:
envelope[decay_start:decay_end] = np.linspace(1, sustain_level, decay_samples)
elif decay_start < sample_count:
envelope[decay_start:] = np.linspace(1, sustain_level, sample_count - decay_start)
# 释放阶段(如果空间足够)
release_start = max(0, sample_count - release_samples)
if release_start < sample_count:
envelope[release_start:] = np.linspace(sustain_level, 0, sample_count - release_start)
return envelope
except Exception as e:
print(f"✗ 创建包络失败: {e}")
return np.ones(sample_count)
def set_max_polyphony(self, max_voices: int):
"""
设置最大复调数
Args:
max_voices: 最大同时发声数
"""
self.max_polyphony = max(1, max_voices)
print(f"✓ 最大复调数设置为: {self.max_polyphony}")
def get_active_segment(self, generator_name: str) -> str:
"""
获取生成器当前活动片段
Args:
generator_name: 生成器名称
Returns:
片段ID
"""
if generator_name in self.active_generators:
generator = self.active_generators[generator_name]
if generator['segments']:
return generator['segments'][generator['current_segment'] % len(generator['segments'])]
return ""