如何基于乐谱与乐器文件用Python生成并输出WAV波形?
如何用Python从自定义乐谱和乐器模板生成WAV波形?
我来帮你一步步搞定这个需求!咱们可以拆成几个核心环节来实现:解析乐器模板、解读乐谱、生成采样波形、导出WAV文件。下面是具体的代码和详细解释:
1. 先明确输入数据的逻辑
先把两个文件的作用理清楚:
- score.txt:
piano |*********************|里的*数量代表要重复播放乐器波形的次数,每个*对应一个完整的乐器波形周期。 - piano乐器文件:里面的数字是波形的关键振幅顶点,数字后的制表符分隔了顶点和过渡符号(
---、/\、\/),这些符号定义了两个顶点之间的波形变化方式:---:线性平滑过渡(直线)/\:先上升到峰值再下降到下一个顶点的三角波形\/:先下降到谷值再上升到下一个顶点的三角波形
2. 完整实现代码
我们用Python标准库wave处理WAV文件,numpy处理波形采样的插值计算,不需要额外安装依赖:
import wave import numpy as np def parse_instrument_file(file_path): """解析乐器文件,提取波形顶点和过渡类型""" with open(file_path, 'r') as f: content = f.read().strip() # 按制表符分割内容,分离数字和过渡符号 parts = content.split('\t') vertices = [] transitions = [] for i in range(len(parts)): # 识别数字顶点(支持负数) if parts[i].lstrip('-').isdigit(): vertices.append(int(parts[i])) # 收集当前顶点到下一个顶点的过渡符号 if i + 1 < len(parts): transition = [] j = i + 1 while j < len(parts) and not parts[j].lstrip('-').isdigit(): transition.append(parts[j]) j += 1 transitions.append(''.join(transition)) return vertices, transitions def generate_cycle_wave(vertices, transitions, samples_per_cycle=100): """根据顶点和过渡类型生成单个周期的波形采样""" wave_samples = [] total_segments = len(vertices) - 1 samples_per_segment = samples_per_cycle // total_segments # 逐个处理每个顶点间的过渡段 for i in range(total_segments): start_val = vertices[i] end_val = vertices[i+1] transition = transitions[i] if transition == '---': # 线性过渡段 segment = np.linspace(start_val, end_val, samples_per_segment, endpoint=False) elif transition == '/\\': # 先升后降的三角段,峰值取两个顶点的最大值 peak = max(start_val, end_val) up_samples = samples_per_segment // 2 down_samples = samples_per_segment - up_samples up_part = np.linspace(start_val, peak, up_samples, endpoint=False) down_part = np.linspace(peak, end_val, down_samples) segment = np.concatenate([up_part, down_part]) elif transition == '\\/': # 先降后升的三角段,谷值取两个顶点的最小值 valley = min(start_val, end_val) down_samples = samples_per_segment // 2 up_samples = samples_per_segment - down_samples down_part = np.linspace(start_val, valley, down_samples, endpoint=False) up_part = np.linspace(valley, end_val, up_samples) segment = np.concatenate([down_part, up_part]) else: # 默认线性过渡,兼容未知符号 segment = np.linspace(start_val, end_val, samples_per_segment, endpoint=False) wave_samples.extend(segment.tolist()) # 补充剩余采样点(处理整除后的余数) remaining = samples_per_cycle - len(wave_samples) if remaining > 0: last_val = wave_samples[-1] extension = np.linspace(last_val, last_val, remaining) wave_samples.extend(extension.tolist()) # 归一化到16位WAV的有效范围(-32768 到 32767) max_val = max(abs(np.array(wave_samples))) if max_val == 0: normalized = wave_samples else: normalized = (np.array(wave_samples) / max_val) * 32767 return normalized.astype(np.int16) def parse_score_file(file_path): """解析乐谱文件,获取乐器名和重复次数""" with open(file_path, 'r') as f: content = f.read().strip() # 分割出乐器名和音符段(*的数量) parts = content.split('|') instrument_name = parts[0].strip() note_count = len(parts[1].strip()) return instrument_name, note_count def generate_wav(output_path, cycle_wave, note_count, sample_rate=44100): """将周期波形重复指定次数,生成WAV文件""" # 拼接所有周期的波形 full_wave = np.tile(cycle_wave, note_count) # 写入WAV文件 with wave.open(output_path, 'w') as wav_file: # 设置WAV参数:单声道、16位采样、采样率、总帧数 wav_file.setparams((1, 2, sample_rate, len(full_wave), 'NONE', 'not compressed')) wav_file.writeframes(full_wave.tobytes()) if __name__ == "__main__": # 替换成你的文件路径 score_path = "scores/score.txt" # 解析乐谱 instrument_name, note_count = parse_score_file(score_path) instrument_path = f"instruments/{instrument_name}" # 解析乐器模板 vertices, transitions = parse_instrument_file(instrument_path) # 生成单个周期波形 cycle_wave = generate_cycle_wave(vertices, transitions) # 导出WAV generate_wav("output.wav", cycle_wave, note_count) print(f"✅ 已成功生成WAV文件:output.wav")
3. 关键参数说明
你可以根据需求调整这些参数来改变音质和播放效果:
samples_per_cycle:每个周期的采样点数,数值越大波形越平滑,默认100sample_rate:WAV文件的采样率,常见值为44100(CD音质)或22050,默认44100- 如果你的乐器文件有其他过渡符号,可以在
generate_cycle_wave函数中添加对应的处理逻辑
内容的提问来源于stack exchange,提问作者DRV5
相关产品推荐
相关产品推荐

