问题描述
我正在尝试创建一个简单的应用程序,该应用程序加载 wav 文件(键盘的每个音符一个)并在按下(或播放)midi 音符时播放特定的文件.到目前为止,我已经在两个单独的线程中使用 mido 创建了一个 midi 输入流和一个使用 pyaudio 的音频流.目标是让 MIDI 流更新当前正在播放的音符,以及 pyaudio 流的回调以检查活动音符并播放那些音符.midi 流工作正常,但我的音频流似乎只调用一次回调,就在脚本启动时 (print(notes)
).知道如何让音频流回调不断更新吗?
I'm trying to create a simple app that loads wav files (one for each note of a keyboard) and plays specific ones when a midi note is pressed (or played). So far, I've created a midi input stream using mido and an audio stream using pyaudio in two separate threads. the goal is for the midi stream to update the currently playing notes, and the callback of the pyaudio stream to check for active notes and play those that are. The midi stream works fine, but my audio stream only seems to call the callback once, right when the script is started (print(notes)
). Any idea how I can get the audio stream callback to update constantly?
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()
推荐答案
我也遇到了这个问题,发现了这个问题,希望能找到答案,最后自己弄明白了.
I too faced this issue and found this question in hopes of finding an answer, ended up figuring it out myself.
回调中返回的数据必须与帧数匹配(p.open 中的frames_per_buffer 参数).我看到你没有指定一个,所以我认为默认值是 1024.
The data returned on the callback must match the number of frames (frames_per_buffer parameter in p.open). I see you didn't specify one so I think the default is 1024.
问题是frames_per_buffer 不代表字节而是accrual 帧.因此,由于您将格式指定为 pyaudio.paInt24,这意味着一帧由 3 个字节 (24/8) 表示.因此,在您的回调中,您应该返回 3072 字节,否则由于某种原因将不会再次调用回调.
The thing is frames_per_buffer does not represent bytes but acrual frames. So since you specify the format as being pyaudio.paInt24 that means that one frames is represented by 3 bytes (24 / 8). So in your callback you should be returning 3072 bytes or the callback will not be called again for some reason.
如果您使用阻塞模式而不是在 stream.write() 中写入那 3072 个字节,则会导致音频缓慢而噼啪作响的奇怪效果.
If you were using blocking mode and not writing those 3072 bytes in stream.write() it would result in a weird effect of slow and crackling audio.
这篇关于PyAudio 回调仅被调用一次的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!