如何用Python将Wav文件音色分配给MIDI文件?
用Python实现Wav吉他音色替换MIDI轨道的钢琴音色
核心思路是利用采样器逻辑:读取MIDI中的音符事件(音高、时长、时间点、力度),将吉他Wav样本转调到对应音符的音高,再按MIDI的时间轴拼接合成新音频——这和你之前用的audio-to-midi工具逻辑完全不同,后者是音频转MIDI,而我们需要的是用音频样本映射MIDI音符。
所需依赖库
先安装必要的Python库:
pip install mido librosa soundfile numpy sounddevice
实现步骤与代码示例
1. 读取MIDI文件,提取关键音符信息
用mido库读取MIDI的音符开/关事件,转换为统一的音符数据结构:
import mido import numpy as np def read_midi_notes(midi_path): mid = mido.MidiFile(midi_path) ticks_per_beat = mid.ticks_per_beat tempo = 500000 # 默认对应BPM=120,可从MIDI元事件读取实际值 notes = [] current_time = 0.0 active_notes = {} for msg in mid: current_time += mido.tick2second(msg.time, ticks_per_beat, tempo) if msg.type == 'set_tempo': tempo = msg.tempo elif msg.type == 'note_on' and msg.velocity > 0: active_notes[msg.note] = (current_time, msg.velocity) elif msg.type == 'note_off' or (msg.type == 'note_on' and msg.velocity == 0): if msg.note in active_notes: start_time, velocity = active_notes.pop(msg.note) duration = current_time - start_time notes.append({ 'pitch': msg.note, 'start': start_time, 'duration': duration, 'velocity': velocity }) return notes
2. 读取并预处理吉他Wav样本
用librosa读取Wav文件,转成单声道,并指定样本的基准音高(比如你的吉他样本是A4,对应MIDI音高编号69):
import librosa import soundfile as sf def load_guitar_sample(sample_path, base_pitch=69): audio, sr = librosa.load(sample_path, mono=True) return audio, sr, base_pitch
3. 音高转换与音频合成
对每个MIDI音符计算音高差,转调后按时间轴拼接所有音频片段:
def synthesize_audio(notes, guitar_audio, sr, base_pitch): # 计算总时长,创建空数组存储合成音频 max_end_time = max(n['start'] + n['duration'] for n in notes) synthesized = np.zeros(int(max_end_time * sr), dtype=np.float32) for note in notes: # 计算音高差(半音数) pitch_diff = note['pitch'] - base_pitch # 转调处理 shifted_audio = librosa.effects.pitch_shift(guitar_audio, sr=sr, n_steps=pitch_diff) # 根据MIDI力度调整音量(力度范围0-127) volume = note['velocity'] / 127.0 shifted_audio *= volume # 计算音频在合成数组中的位置 start_sample = int(note['start'] * sr) end_sample = start_sample + len(shifted_audio) # 处理超出总时长的边界情况 if end_sample > len(synthesized): shifted_audio = shifted_audio[:len(synthesized)-start_sample] end_sample = len(synthesized) # 叠加音频片段 synthesized[start_sample:end_sample] += shifted_audio # 归一化防止音量过载 synthesized = librosa.util.normalize(synthesized) return synthesized, sr
4. 主函数调用
if __name__ == '__main__': # 替换为你的MIDI和Wav文件路径 midi_file = "your_piano_midi.mid" guitar_sample = "your_guitar_sample.wav" # 读取MIDI音符 notes = read_midi_notes(midi_file) # 读取吉他样本(注意调整base_pitch为你的样本实际音高编号) guitar_audio, sr, base_pitch = load_guitar_sample(guitar_sample, base_pitch=69) # 合成音频 synthesized_audio, sr = synthesize_audio(notes, guitar_audio, sr, base_pitch) # 保存合成后的Wav文件 sf.write("guitar_midi_output.wav", synthesized_audio, sr) # 可选:播放合成音频 import sounddevice as sd sd.play(synthesized_audio, sr) sd.wait()
关键注意事项
- 吉他样本选择:优先用干净的单音采样(无混响、失真等效果),转调后音质更自然;若用和弦采样,转调会导致和声混乱。
- 基准音校准:必须确认吉他样本对应的MIDI音高编号(比如C4对应60,A4对应69),否则转调会出错。
- MIDI tempo校准:示例默认用120BPM,实际可从MIDI的
set_tempo元事件读取真实速度,保证时间轴准确。 - 重叠音符处理:代码用叠加方式处理重叠音符,若要模拟吉他拨弦间隔,可添加触发间隔判断逻辑。
内容的提问来源于stack exchange,提问作者clacson
相关产品推荐
相关产品推荐

