【发布时间】:2021-04-15 08:33:32
【问题描述】:
我目前正在尝试编写一个脚本来在 Bokeh 中显示(多声道)音频的频谱图。由于我正在对音频进行一些处理,因此我无法将它们轻松保存为计算机上的文件,因此我试图留在 Python 中。
这个想法是创建一个图,其中每一列对应一个音频样本,每一行对应一个通道。
现在我希望能够在单击子图时收听相应的音频。 我已经设法完成了显示频谱图的非交互部分,编写了一个回调来播放音频,并将其应用于每个回调。
这是代码的最小工作示例:
import numpy as np
from bokeh.plotting import figure, output_file, show
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.palettes import Viridis256
from bokeh.layouts import gridplot
def bokeh_subplots(specs, wavs):
channels = max([s.shape[0] for s in specs])
def inner(p, s, w):
# p is the plot, s is the spectrogram, and w is the numpy array representing the sound
source = ColumnDataSource(data=dict(raw=w))
callback = CustomJS(args=dict(source=source),
code =
"""
function typedArrayToURL(typedArray, mimeType) {
return URL.createObjectURL(new Blob([typedArray.buffer], {type: mimeType}))
}
const bytes = new Float32Array(source.data['raw'].length);
for(let i = 0; i < source.data['raw'].length; i++) {
bytes[i] = source.data['raw'][i];
}
const url = typedArrayToURL(bytes, 'audio/wave');
var audio = new Audio(url);
audio.src = url;
audio.load();
audio.play();
""" % w)
# we plot the actual spectrogram here, which works fine
p.image([s], x=0, y=0, dw=s.shape[1], dh=s.shape[0], palette = Viridis256)
# then we add the callback to be triggered on a click within the plot
p.js_on_event('tap', callback)
return p
# children will contain the plots in the form of a list of list
children = []
for s, w in zip(specs, wavs):
# initialise the plots for each channel of a spectrogram, missing channels will be left blank
glyphs = [None] * channels
for i in range(s.shape[0]):
# apply the function above to create the plots
glyphs[i] = inner(figure(x_range=(0, s[i].shape[1]), y_range=(0, s[i].shape[0])),
s[i], w[i])
children.append(glyphs)
# finally, create the grid of plots and display
grid = gridplot(children=children, plot_width=250, plot_height=250)
show(grid)
# Generate some random data for illustration
random_specs = [np.random.uniform(size=(4, 80, 800)), np.random.uniform(size=(2, 80, 750))]
random_wavs = [np.random.uniform(low=-1, high=1, size=(4, 96*800)), np.random.uniform(low=-1, high=1, size=(2, 96*750))]
# This creates a plot with 2 rows and 4 columns
bokeh_subplots(specs=random_specs, wavs=random_wavs)
我基本上复制了this page 来编写回调,但不幸的是它似乎不适合我的用例,因为当我运行脚本时,绘图正确生成但音频不播放。 我还尝试在将数组编码为 base64 后创建一个数据 URI,如 here 和 here,结果相同。 当尝试使用提供本地文件路径的更简单回调时,它可以正常工作
callback = CustomJS(code = """var audio = new Audio("path/to/my/file.wav");
audio.play();
""")
这可行,但对我的目的不够灵活(因为我需要为每个频道保存一个单独的文件,或者必须完全放弃选择频道)。
我在 JavaScript 和 Bokeh 方面都非常陌生,所以我不知道这里出了什么问题。从上面的页面我认为它与我将数组提供给回调的方式有关,但我不知道如何修复它。 (就此而言,我不知道按元素填充“字节”数组是否是一种有效的方法,但现在我决定让脚本工作。)
有人对这里发生的事情有任何指示吗?
【问题讨论】:
-
我无法立即查看是否已解决此问题,但不要忘记自动播放政策。在允许启动音频上下文之前,您需要进行某种用户交互。
-
抱歉,我对自动播放策略不熟悉,我得研究一下。当您提到用户交互时,您的意思是像点击一样吗?如果是这样,那就是回调的触发事件(“tap”指令意味着侦听图上的点击)。
-
对于自动播放,请参见此处developers.google.com/web/updates/2017/09/…,尽管情况很新颖。我首先从一个已知的静态音频 blob 开始,以缩小潜在嫌疑人的范围
-
所以我完全结束了另一条路线,结果证明我在构造 Audio() 对象时根本没有提供正确的格式......