我正在尝试创建一个简单的应用程序,该应用程序可加载wav文件(每个键盘音符一个),并在按下(或弹奏)MIDI音符时播放特定文件。到目前为止,我已经在两个单独的线程中使用mido创建了midi输入流,并使用pyaudio创建了音频流。目标是让midi流更新当前正在播放的音符,以及pyaudio流的回调以检查活动的音符并播放那些音符。 MIDI流可以正常工作,但是我的音频流似乎只在脚本启动时调用一次回调(print(notes)
)。知道如何让音频流回调不断更新吗?
import wave
from io import BytesIO
import os
from mido import MidiFile
import pyaudio
from time import sleep
from threading import Thread
import numpy
# Pipe: active, released
# Rank: many pipes
# Stop: one or more ranks
# Manual: multiple ranks
# Organ: multiple manuals
pipes = []
notes = []
p = pyaudio.PyAudio()
def mapRange(num, inMin, inMax, outMin, outMax):
return int((num - inMin) * (outMax - outMin) / (inMax - inMin) + outMin)
def callback(in_data, frame_count, time_info, status):
data = bytes(frame_count)
print(notes)
for note in notes:
pipedata = bytes()
if len(data) != 0:
data1 = numpy.fromstring(data, numpy.int16)
data2 = numpy.fromstring(note['sample'].readframes(frame_count), numpy.int16)
pipedata = (data1 * 0.5 + data2 * 0.5).astype(numpy.int16)
else:
data2 = numpy.fromstring(note['sample'].readframes(frame_count), numpy.int16)
pipedata = data2.astype(numpy.int16)
data = pipedata.tostring()
return (data, pyaudio.paContinue)
stream = p.open(format=pyaudio.paInt24,
channels=2,
rate=48000,
output=True,
stream_callback=callback,
start=True)
# start the stream (4)
stream.start_stream()
for root, dirs, files in os.walk("samples"):
for filename in files:
file_on_disk = open(os.path.join(root, filename), 'rb')
pipes.append(
{"sample": wave.open(BytesIO(file_on_disk.read()), 'rb')})
for msg in MidiFile('test.mid').play():
if msg.type == "note_on":
notes.append(pipes[mapRange(msg.note, 36, 96, 0, 56)])
print("on")
if msg.type == "note_off":
#notes[mapRange(msg.note, 36, 96, 0, 56)] = False
print("off")
# wait for stream to finish (5)
while stream.is_active():
sleep(0.1)
# stop stream (6)
stream.stop_stream()
stream.close()
# close PyAudio (7)
p.terminate()
答案 0 :(得分:0)
我也面临这个问题,发现了这个问题,希望找到答案,最后自己弄清楚。
在回调中返回的数据必须与帧数匹配(p.open中的frames_per_buffer参数)。我看到您没有指定一个,所以我认为默认值为1024。
问题是frames_per_buffer并不代表字节,而是代表帧。因此,由于您将格式指定为pyaudio.paInt24,这意味着一帧由3个字节(24/8)表示。因此,在您的回调中,您应该返回3072个字节,否则由于某种原因将不会再次调用该回调。
如果使用阻塞模式而不在stream.write()中写入这3072个字节,则会导致音频缓慢和crack啪声的怪异效果。