You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:实现Python音乐脚本中父音符保持+子音符顺序播放功能

解决父音符持续播放同时子音符依次播放的方案

针对你用Python+sounddevice+numpy实现乐谱播放时,无法实现A6 + ( A4 E3 A6 )这类父音符持续播放、子音符序列依次播放的问题,核心是要实现多轨音频的并行播放,之前用Threading失败大概率是因为没有处理好音频设备的资源冲突或播放同步问题,下面给出两种可行方案:

方案1:预生成混合音频(简单易实现)

先分别生成父轨道和子轨道的音频数据,将它们混合后一次性播放,天然实现并行效果,无需线程。

基础工具函数

import numpy as np
import sounddevice as sd

def note_to_freq(note):
    # 可扩展完整的音符-频率映射,这里简化示例
    note_map = {'A4': 440, 'E3': 164.81, 'A6': 1760}
    return note_map.get(note, 440)

def generate_note_audio(freq, duration, sample_rate=44100):
    # 生成指定频率、时长的正弦波音频
    t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
    return 0.5 * np.sin(2 * np.pi * freq * t)

实现混合播放

# 解析语法:父音符A6,子音符序列[A4, E3, A6],每个子音符持续0.5秒
parent_note = 'A6'
child_notes = ['A4', 'E3', 'A6']
child_note_duration = 0.5
total_duration = len(child_notes) * child_note_duration

# 生成父轨道音频:持续整个子序列的总时长
parent_audio = generate_note_audio(note_to_freq(parent_note), total_duration)

# 生成子轨道音频:依次拼接每个子音符的音频片段
child_audio = np.concatenate([
    generate_note_audio(note_to_freq(n), child_note_duration)
    for n in child_notes
])

# 混合轨道:除以轨道数防止音量溢出(幅值超过1会出现削波失真)
mixed_audio = (parent_audio + child_audio) / 2

# 播放混合后的音频
sd.play(mixed_audio, samplerate=44100)
sd.wait()

方案2:实时多轨回调(适合动态/长乐谱)

如果乐谱很长或需要动态调整(比如实时响应暂停、变速),可以用sounddevice的流回调机制,同时维护多个轨道的播放状态,实时生成并混合音频。

轨道播放器类

import sys
import numpy as np
import sounddevice as sd

def note_to_freq(note):
    note_map = {'A4': 440, 'E3': 164.81, 'A6': 1760}
    return note_map.get(note, 440)

class TrackPlayer:
    def __init__(self, sample_rate=44100):
        self.sample_rate = sample_rate
        self.current_freq = 0
        self.remaining_samples = 0
        self.note_queue = []  # 存储待播放的(频率, 总采样数)
    
    def queue_notes(self, notes, duration_per_note):
        # 将音符序列转为采样数队列
        for note in notes:
            freq = note_to_freq(note)
            total_samples = int(duration_per_note * self.sample_rate)
            self.note_queue.append((freq, total_samples))
        # 若当前无播放任务,启动第一个音符
        if self.remaining_samples == 0 and self.note_queue:
            self.current_freq, self.remaining_samples = self.note_queue.pop(0)
    
    def get_audio_chunk(self, num_samples):
        # 生成指定采样数的音频片段
        audio_chunk = np.zeros(num_samples)
        if self.remaining_samples == 0 and self.note_queue:
            self.current_freq, self.remaining_samples = self.note_queue.pop(0)
        if self.remaining_samples == 0:
            return audio_chunk
        
        # 计算本次要生成的采样数(不超过剩余采样数)
        generate_samples = min(num_samples, self.remaining_samples)
        t = np.linspace(0, generate_samples/self.sample_rate, generate_samples, endpoint=False)
        audio_chunk[:generate_samples] = 0.5 * np.sin(2 * np.pi * self.current_freq * t)
        self.remaining_samples -= generate_samples
        return audio_chunk

启动多轨播放

sample_rate = 44100

# 初始化父轨道:持续播放A6,时长等于子序列总时长(3*0.5=1.5秒)
parent_track = TrackPlayer(sample_rate)
parent_track.queue_notes(['A6'], 1.5)

# 初始化子轨道:依次播放A4、E3、A6,每个0.5秒
child_track = TrackPlayer(sample_rate)
child_track.queue_notes(['A4', 'E3', 'A6'], 0.5)

def audio_callback(outdata, frames, time, status):
    if status:
        print(status, file=sys.stderr)
    # 获取两个轨道的音频片段
    parent_chunk = parent_track.get_audio_chunk(frames)
    child_chunk = child_track.get_audio_chunk(frames)
    # 混合后输出到立体声通道
    outdata[:, 0] = (parent_chunk + child_chunk) / 2
    outdata[:, 1] = outdata[:, 0]

# 启动输出流并等待用户停止
with sd.OutputStream(samplerate=sample_rate, callback=audio_callback):
    print("按回车键停止播放")
    input()

为什么之前Threading方案失败?

直接用线程调用sd.play()会导致多个独立的音频流同时占用设备,容易出现资源冲突、播放不同步或卡顿问题。sounddevice的播放机制更适合单流控制,多轨需求应该通过音频数据混合而非多线程独立播放来实现。

扩展建议

  • 可以在TrackPlayer中添加暂停、变速逻辑:比如暂停时停止消耗remaining_samples,变速时调整duration_per_note对应的采样数
  • 完善音符解析逻辑:支持解析A6 + (A4 E3 A6)这类语法,自动拆分父轨道和子轨道的音符序列

内容的提问来源于stack exchange,提问作者ZXCVB0T

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:40:29