python - 无法弄清楚如何计算 .wav 文件的光谱属性

标签 python numpy scipy signal-processing fft

我试图计算出声音剪辑的平均频率以及 Q25 和 Q75 值,但遇到了问题(主要是由于我缺乏数学和 DSP 知识)。

我要离开 this答案,并且在将该答案中的代码与读取 .wav 文件相结合时遇到问题。

这是我用来记录的代码...

def record_sample(file):
    # Audio Recording
    CHUNK = 1024
    FORMAT = pyaudio.paInt16
    CHANNELS = 2
    RATE = 44100
    RECORD_SECONDS = 5
    pa = pyaudio.PyAudio()

    # Record sample.
    stream = pa.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)
    frames = []
    for _ in range(int(RATE / CHUNK * RECORD_SECONDS)):
        data = stream.read(CHUNK)
        frames.append(data)
    stream.stop_stream()
    stream.close()

    # Save to wave file.
    wf = wave.open(file, "wb")
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(pa.get_sample_size(FORMAT))
    wf.setframerate(RATE)
    wf.writeframes(b''.join(frames))
    wf.close()

一切正常。这是我用于计算平均频率、Q25 和 Q75 的代码...

def spectral_properties(file):
    # Note: scipy.io.wavfile.read
    fs, data = wavfile.read(file)
    spec = np.abs(np.fft.rfft(data))
    freq = np.fft.rfftfreq(len(data), d=1 / fs)
    spec = np.abs(spec)
    amp = spec / spec.sum()
    amp_cumsum = np.cumsum(amp)
    Q25 = freq[len(amp_cumsum[amp_cumsum <= 0.25]) + 1]
    Q75 = freq[len(amp_cumsum[amp_cumsum <= 0.75]) + 1]
    print((freq * amp).sum(), Q25, Q75)

以及它产生的错误......

File "/home/horner/workspace/school/ML/machine-learning-project-mdx97/program/audio.py", line 65, in spectral_properties
    Q75 = freq[len(amp_cumsum[amp_cumsum <= 0.75]) + 1]
IndexError: index 298981 is out of bounds for axis 0 with size 110081

最佳答案

请注意,您有 2 个 channel ,这意味着您将获得二维数据。您当前的版本只是在某些操作期间展平数组,这就是它看起来有太多元素的原因。

有两种方法可以解决这个问题。第一种是仅使用其中一个 channel :

def spectral_properties(filename):

    fs, data = wavfile.read(filename)

    # use the first channel only
    if data.ndim > 1:
        data = data[:, 0]

    spec = np.abs(np.fft.rfft(data))
    freq = np.fft.rfftfreq(len(data), d=1/fs)

    assert len(spec) == len(freq)

    amp = spec / spec.sum()
    amp_cumsum = amp.cumsum()

    assert len(amp_cumsum) == len(freq)

    q25 = freq[len(amp_cumsum[amp_cumsum < 0.25])]
    q75 = freq[len(amp_cumsum[amp_cumsum < 0.75])]

    return (freq * amp).sum(), q25, q75


avg, q25, q75 = spectral_properties('foobar.wav')
print(avg, q25, q75)

第二个是保留 channel 并告诉 numpy 函数它们应该沿哪个轴操作。这也意味着计算四分位数变得不再那么简单,因为您需要为每个 channel 单独查找它们,但由于 Python 的列表推导式,它看起来几乎和以前一样简单:

def spectral_properties(filename):

    fs, data = wavfile.read(filename)

    # determine number of channels
    num_channels = data.shape[1]

    spec = np.abs(np.fft.rfft(data, axis=0))
    freq = np.fft.rfftfreq(len(data), d=1/fs)

    assert len(spec) == len(freq)

    amp = spec / spec.sum(axis=0)
    amp_cumsum = amp.cumsum(axis=0)

    assert len(amp_cumsum) == len(freq)

    q25 = [freq[len(amp_cumsum[:,j][amp_cumsum[:,j] < 0.25])] for j in range(num_channels)]
    q75 = [freq[len(amp_cumsum[:,j][amp_cumsum[:,j] < 0.75])] for j in range(num_channels)]

    return (freq[:,np.newaxis] * amp).sum(axis=0), q25, q75


avg, q25, q75 = spectral_properties('foobar.wav')
print(avg, q25, q75)

请注意,+ 1 在查找四分位数的原始表达式中存在问题。考虑除最后一个值之外的所有值都小于 0.25。因此,对于 n - 1 元素来说,不等式成立。您添加 1,因此您得到 n。但是对于长度为 nfreq 数组来说,n 的索引太高了。

此外,我怀疑您可能想要对 spec 进行平方,而不是保留其大小。

更新:

您可能还想使用 searchsorted 来查找四分位数,这应该更快、更容易阅读:

q25 = freq[np.searchsorted(amp_cumsum, 0.25)]
q75 = freq[np.searchsorted(amp_cumsum, 0.75)]

还有:

q25 = [freq[np.searchsorted(amp_cumsum[:,j], 0.25)] for j in range(num_channels)]
q75 = [freq[np.searchsorted(amp_cumsum[:,j], 0.75)] for j in range(num_channels)]

关于python - 无法弄清楚如何计算 .wav 文件的光谱属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55716796/

相关文章:

python - 加速 numpy 3D 数组的卷积循环?

python - 将 UnivariateSpline 与 SCIPY 结合使用

python-3.x - 无法将 scipy 安装到 Raspberry pi 4 (raspbian)

python - 如何向目录中的所有 CSV 文件批量添加列标题并保留这些文件?

python - 在 Ubuntu 上使用 Python 创建子目录?

python - Numpy - 将 np.arrays 自动隐式转换为列表

python - 非均匀间距的 Numpy 或 SciPy 导数函数?

Python:列表理解,将 2 个列表合并为 1 个具有唯一值的列表

Python, boolean - 条件和控制流 - 不理解

python - 使用 numpy 产生 2D 柏林噪声