casebeer/SNARP — pull request #1 by casebeer (2013-09-13 03:26:19+00:00)
Title: Generator tagging and segmentation

This is the first of two sequential pull request I have ready. I've split them since they deal with different concerns, and this should make them a little easier to look over.

First, in this branch, I've changed the audio processing code. Rather than looping over chunks of samples indefinitely, I've push all the audio parsing, chunking, silence detection, and chunk segmentation into generators.

Effectively, instead of doing `while True: ... ... ... ... ... `, we can do things like:

```python
for chunk_frames in \
audible_chunks(tag_chunks(chunked_samples(input_wave, CHUNK_SECONDS))):

output_wave.writeframes(chunk_frames)
```
Note that this is not the actual logic used in the latest remove_silences(); this is the logic as it was in commit 8073e2417, after which things got more complicated again.

Since that commit, I added more sophisticated segmentation. That is, rather than just writing audio when we see noise, and not writing when we see silence, I'm tagging each chunk as to whether it belongs to an audible or silent "segment" (note that it's entirely possible for a silent *chunk* to be part of an audible *segment• – for example, the pre-roll or post-roll).

The `audible_chunks()` function seen above utilizes this tagging to suppress silences and write audible segments.

However, we can now do more: The option I've immediately added in this pull request is to *also* write the silent chunks. This lets us write out a "bypass" stream which is an exact copy of the audio input. While this could be done outside the program, e.g. with the `tee` UNIX filter, I think it's appropriate to include since it essentially offers an archival option if you've, for instance, set your silence limits incorrectly. This is also convenient for running SNARP multiple times on the same input for testing.

My primary motivation for this change in model to "silent" and "audible" segments is that I ultimately intend to add the ability to write each audible segment out as a separate, timestamped audio file. Having segmented the audio stream this way, this per-file behavior will be easy to implement.

Segregating the pre- and post-roll buffering from the silence detection and other logic has another benefit: We can increase the complexity of the buffering/silent-or-audible-segement logic without tripping over the other features. You'll note that the biggest single expansion of the code is in this `tag_segments()` function.

What I've done is:

- Separated the concept of chunk length from hysteresis. i.e. we can say an audible segment only ends after one full second of silence, yet only write out one 100 ms chunk as a post roll buffer.
- Allowed different, specific pre- and post- roll buffer lengths.
- Managed the "pre-roll-buffer," now just a general buffer, so we never lose samples e.g. when the script ends, or when there's a bunch of silence in the middle of an audible segment.

Put together, these basically give us "look-ahead" capability, so we can keep the pre- and post-rolls short yet still have smooth noise detection.

Finally, one of the first commits in this branch fixes a few performance issues my first pull request introduced. I did a bunch of profiling work, and moved all the unnecessary metadata and configuration stuff (e.g. computation of the signedness based on sample width) out of the inner loop.