Python algorithm to distinguish sound from silence in a wav file

Viewed 38

I'm writing a Python file that should take a wav file containing Morse code and decode it. Once I chop the sound file into blocks of sound and silence and determine the average length of the sounds, decoding Morse is easy.

I wrote an algorithm that can do this for wav files containing sine waves generated by Python's wave module, but it just returns gibberish for natural sounds.

def read_audio_data(filename):
    # Returns data of a wav file as bytes.
    with wave.open(filename, 'rb') as wav:
        (nchannels, sampwidth, framerate, nframes, comptype, compname) = wav.getparams()
        frames = wav.readframes(nframes * nchannels)
        return frames


def frames_to_ints(wav_bytes):
    # Converts wav frames as bytes to a list of integers.
    return list(wav_bytes)


def find_mean_amplitude(frames):
    return mean(frames)


def rolling_average_ms(samples, sample_rate=44100):
    # The window represents an number of frames. For the standard sample rate of 44100, 1 millisecond
    # takes up 44.1 frames.
    moving_window = []
    rolling_average = []
    for i in samples[::2]:
        if len(moving_window) > sample_rate // 1000:
            del(moving_window[0])
        moving_window.append(i)
        rolling_average.append(mean(moving_window))
    return rolling_average

def sound_or_no_sound(samples):
    rolling_average = rolling_average_ms(samples)
    mean_amplitude = find_mean_amplitude(samples)

    on_or_off = []

    for i in rolling_average:
        if i < mean_amplitude:
            on_or_off.append(False)
        else:
            on_or_off.append(True)

    return on_or_off

After this, I wised up and used scipy's wavfile instead of Python's wave module. I wrote another algorithm that can decode audio from a recorded wav file of me whistling in Morse code, but it didn't work on my voice humming or on computer-generated files.

data = wavfile.read('blah_blah_blah.wav')

def split_into_ms(data):
    # Roughly groups the data by milliseconds.
    sample_rate = data[0]
    ms_length = sample_rate // 1000  # Roughly 1 millisecond
    values = data[1]
    ms = []
    for x in range(len(values))[ms_length - 1::ms_length]:
        ms.append(values[x - 44: x])
    return ms


def distinguish_sound_from_silence(ms, data):
    # Takes the milliseconds and decides if the sound is on or off. Adds "True" for on and "False" for off.
    data_stdev = np.std(data[1])
    output = []
    for x in ms:
        if np.std(x) >= data_stdev:
            output.append(True)
        else:
            output.append(False)
    return output

def group_by_sound_or_silence(bool_data):
    # Will return a list of blocks of sound and silence.
    output = []
    block = []

    for x in range(1, len(bool_data)):
        if bool_data[x] == bool_data[x - 1]:
            block.append(bool_data[x])
        else:
            output.append(block)
            block = []

    return output


def mean_block_length(blocks):
    return mean([len(block) for block in blocks])


def filter_short_blocks(blocks, tolerance=3):
    # Removes unusually short blocks.
    # Blocks of shorter length than the mean block length divided by the tolerance are removed.
    # Lower tolerance will filter more blocks.
    output = []
    mean_block_len = mean_block_length(blocks)
    for block in blocks:
        if len(block) > mean_block_len / tolerance:
            output.append(block)
    return output

I was in the middle of writing an algorithm for doing this for my own voice when I realized that it would be super impractical to write algorithms for every kind of sound my program might encounter and then try to determine which one to use.

I'm really trying to find a way to develop an algorithm that'll work on any sound with nearly any wave pattern and a frequency within a reasonable range. If it can work on any sample rate, even better. It's perfectly okay if the algorithm depends on the sound being roughly the same frequency throughout, because that's how Morse code is supposed to work anyway. Does anyone have any tips?

0 Answers
Related