Block Transfers, Buffers, and Audio

Published April 16, 2016
Advertisement

Waiting for Visual Studio to update, so I thought I'd write something.

When working with streams such as file or audio processing, you usually run into the same problem: How to copy the most data without knowing how large the stream of data is. I solved a similar problem at work and in my audio synthesizer. In the case of the synthesizer, I wanted an echo effect that fed back N stored seconds of audio.

Here's how a simple block transfer could look:
// assuming:HANDLE src;HANDLE dst;int src_size;// and:const int block_size = 8 * 1000; // 8 KBchar* block = alloc(block_size);// then:int remaining = src_size;for ( int bytes_read = 0; bytes_read = read(src, block, block_size)); remaining -= bytes_read ){ // (process block) write(dst, block, bytes_read); remaining -= bytes_read;}For a block_size of 100 and a src_size of 550, you would make 6 writes of sizes:


100 100 100 100 100 50

The echo effect was a bit different: I have two buffers of known length, but one of the buffers is circular and has a read and write head at two different positions within that buffer.
// assuming:int samples = 44100;int audio_buffer_len = samples / 100;float* out_audio = new float[audio_buffer_len]; // 1-channel audio at 44.1KHz, 100ms buffer// and some state:struct delay_effect { float delay_amp = 0.25f; int delay_stride = samples * 2 / 3; // 667ms or 29400 samples int buffer_size = delay_stride + 320; // some extra padding float* buffer = new float[buffer_size]; int wdx = 0; // write position within buffer int rdx = delay_stride; // read position within buffer // the extra padding on buffer is needed to keep the write head from interfering // with the read head and vice-versa void apply(float* out_audio, int audio_buffer_len);};// then:void delay_effect::apply(float* out_audio, int audio_buffer_len) { float* out_ap = out_audio; int samples_remaining = audio_buffer_len; while ( samples_remaining ) { // find minimum number of samples needed such that: // 1. read head does not overflow buffer // 2. write head does not overflow buffer // 3. audio buffer is not overflowed int take = min( min(buffer_size - wdx, buffer_size - rdx), samples_remaining ); // apply effect to out_audio // (SIMD candidate: snap to 16-byte boundaries and lengths, use float4s instead) float* in_bp = buffer + rdx; float* out_bp = buffer + wdx; for ( int idx = 0; idx < take; ++idx ) { // read from buffer, apply damping, accumulate into output audio, // and store the mixed audio into y float y = (*out_ap++ += *in_bp++ * damping); // feed audio back into buffer *out_bp++ = y; } // advance read and write position, wrapping around buffer length wdx = (wdx + take) % buffer_size; rdx = (rdx + take) % buffer_size; samples_remaining -= take; }}Stepping through, you might see something like this for an audio buffer sized 50, and an echo buffer sized 190, and a delay of 85 samples:
buf# loop# take rdx wdx 1 1 50 0 85 2 1 50 50 135 3 1 5 100 185 3 2 45 105 0 4 1 40 150 45 4 2 10 0 85 5 1 50 10 95 6 1 45 60 145 6 2 5 105 0 7 1 50 110 5As you can see, the entire audio buffer is covered: sum(`take`) group by `buf#`
And neither rdx+take nor wdx+take extends past the echo buffer's 190 samples.


Okay, that's all for now.
See you around.

Previous Entry Maximized IPBoard Chat
Next Entry Selective quote
4 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement