Не могу понять, как вычислить спектральные свойства WAV-файла. - PullRequest
1 голос
/ 17 апреля 2019

Я пытаюсь определить среднюю частоту и значения Q25 и Q75 для звукового клипа, но у меня возникают проблемы (в основном из-за недостатка знаний в области математики и DSP).

Яуходит от этого ответа, и я сталкиваюсь с проблемами, связывающими код в этом ответе с чтением 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

1 Ответ

0 голосов
/ 17 апреля 2019

Обратите внимание, что у вас есть 2 канала, что означает, что вы получаете 2-мерный data. Ваша текущая версия просто выравнивает массив во время некоторых операций, поэтому в нем слишком много элементов.

Есть 2 способа решения проблемы. Первый - использовать только один из каналов:

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)

Второй - сохранить каналы и указать функции numpy, вдоль оси которых они должны работать. Это также означает, что вычисление квартилей становится менее тривиальным, так как вам нужно найти их отдельно для каждого канала, но это выглядит почти так же просто, как и раньше, из-за понимания списка 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 элементов. Вы add 1, поэтому вы получаете n. Но n слишком высокий показатель для массива freq длины 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)]
...