Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 60 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ add please create a GitHub Issue.

## Usage

### Basic Usage (mono IR, mono i/o, uniform partitioning)

First, create a `Config` object:

```cpp
Expand All @@ -36,20 +38,16 @@ chowdsp::convolution::IR_Uniform ir {};
chowdsp::convolution::create_ir (&config, &ir, my_ir.data(), my_ir.size());
```

Then we'll create a convolution "state". For this example, let's assume
we have a monophonic IR that's be used to process a stereo input.
Then we'll create a convolution "state".
```cpp
chowdsp::convolution::Process_Uniform_State left_state {};
chowdsp::convolution::Process_Uniform_State right_state {};
chowdsp::convolution::create_process_state (&config, &ir, &left_state);
chowdsp::convolution::create_process_state (&config, &ir, &right_state);
chowdsp::convolution::Process_Uniform_State state {};
chowdsp::convolution::create_process_state (&config, &ir, &state);
```

Now we're ready to process some data:

```cpp
chowdsp::convolution::process_sample (&config, &ir, &left_state, left_channel_data, left_channel_data, num_samples, fft_scratch);
chowdsp::convolution::process_sample (&config, &ir, &right_state, right_channel_data, right_channel_data, num_samples, fft_scratch);
chowdsp::convolution::process_samples (&config, &ir, &state, data, data, num_samples, fft_scratch);
```

Alternatively, we could use `process_samples_with_latency()` which is
Expand All @@ -59,12 +57,63 @@ Finally, let's clean up all our memory allocation:

```cpp
chowdsp::fft::aligned_free (fft_scratch);
chowdsp::convolution::destroy_process_state (&left_state);
chowdsp::convolution::destroy_process_state (&right_state);
chowdsp::convolution::destroy_process_state (&state);
chowdsp::convolution::destroy_ir (&ir);
chowdsp::convolution::destroy_config (&config);
```

### Multi-Channel Processing (mono IR)

Let's say that you want to convolve a stereo audio stream with a mono IR.
We can use `create_multichannel_process_state()` to create a processing state
with a given number of channels.

```cpp
chowdsp::convolution::Process_Uniform_State stereo_state {};
chowdsp::convolution::create_multichannel_process_state (&config, &ir, &stereo_state, 2);
```

To process our audio, we'll want to use `process_samples_multichannel()`
(or `process_samples_multichannel_with_latency()`).

```cpp
float* channel_data[2] {
left_channel_data,
right_channel_data,
};
chowdsp::convolution::process_samples_multichannel (&config, &ir, &state, channel_data, channel_data, num_samples, 2, fft_scratch);
```

### Multi-Channel IRs

let's create a stereo, uniform-partitioned IR:

```cpp
float* ir_data[2] {
left_ir_data,
right_ir_data,
};
chowdsp::convolution::IR_Uniform ir {};
chowdsp::convolution::create_multichannel_ir (&config, &ir, ir_data, ir_num_samples, 2, fft_scratch);
```

Now if we call `create_process_state()`, the state will automatically be created
for the same number of channels as the IR.
```cpp
chowdsp::convolution::Process_Uniform_State state {};
chowdsp::convolution::create_process_state (&config, &ir, &state);
```

Then (as before), we can do our multi-channel processing:

```cpp
float* channel_data[2] {
left_channel_data,
right_channel_data,
};
chowdsp::convolution::process_samples_multichannel (&config, &ir, &state, channel_data, channel_data, num_samples, 2, fft_scratch);
```

### Multi-Threaded Usage

What should you do if you're looking to load an impulse response
Expand All @@ -79,7 +128,7 @@ thread is still running? The basic idea is that you should:
Note that the `Config` object is thread-safe, so you may use the
same config on both your audio thread and background thread (e.g.
when calling `create_ir()` or `load_ir()`). However, the `fft_scratch`
is not thread-safe, so make sure to allocate a dedicated `fft_scratch`
is **not** thread-safe, so make sure to allocate a dedicated `fft_scratch`
for each thread.

## License
Expand Down
35 changes: 21 additions & 14 deletions chowdsp_convolution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ static int next_pow2 (int v) noexcept
static int pad_floats (int N)
{
static constexpr int pad_len = 16;
const auto N_div = (N + pad_len - 1) / pad_len;
const auto N_div = (N + pad_len - 1) / pad_len;
return N_div * pad_len;
}

Expand Down Expand Up @@ -130,8 +130,7 @@ void load_multichannel_ir (const Config* config, IR_Uniform* ir, const float* co

for (int ch = 0; ch < num_channels; ++ch)
{
IR_Uniform this_channel_ir
{
IR_Uniform this_channel_ir {
.segments = get_segment (config, ir->segments, ch * ir->max_num_segments),
.num_segments = ir->num_segments,
.max_num_segments = ir->max_num_segments,
Expand All @@ -147,7 +146,6 @@ static size_t state_data_bytes_needed (const Config* config, const IR_Uniform* i
size_t bytes_needed {};

const auto segment_num_samples = config->fft_size;
state->num_channels = ir->num_channels;
state->max_num_segments = config->block_size > 128 ? ir->max_num_segments : 3 * ir->max_num_segments;
bytes_needed += segment_num_samples * state->max_num_segments * sizeof (float);

Expand All @@ -174,9 +172,10 @@ static void state_data_partition_memory (const Config* config, Process_Uniform_S
data += config->fft_size;
}

void create_process_state (const Config* config, const IR_Uniform* ir, Process_Uniform_State* state)
void create_multichannel_process_state (const Config* config, const IR_Uniform* ir, Process_Uniform_State* state, int num_channels)
{
using State_Data = Process_Uniform_State::State_Data;
state->num_channels = num_channels;
const auto state_bytes_needed = state_data_bytes_needed (config, ir, state);
auto* data = fft::aligned_malloc (state_bytes_needed + state->num_channels * sizeof (State_Data));
state->state_data = reinterpret_cast<State_Data*> (static_cast<std::byte*> (data) + state_bytes_needed);
Expand All @@ -189,6 +188,11 @@ void create_process_state (const Config* config, const IR_Uniform* ir, Process_U
reset_process_state (config, state);
}

void create_process_state (const Config* config, const IR_Uniform* ir, Process_Uniform_State* state)
{
create_multichannel_process_state (config, ir, state, ir->num_channels);
}

void reset_process_state (const Config* config, Process_Uniform_State* state)
{
state->current_segment = 0;
Expand Down Expand Up @@ -221,8 +225,9 @@ int get_required_nuir_scratch_bytes (const IR_Non_Uniform* ir)
assert (ir->head_config != nullptr);
assert (ir->tail_config != nullptr);
return static_cast<int> ((std::max (ir->head_config->fft_size,
ir->tail_config->fft_size)
+ pad_floats (ir->head_config->block_size)) * sizeof (float));
ir->tail_config->fft_size)
+ pad_floats (ir->head_config->block_size))
* sizeof (float));
}

void create_nuir (IR_Non_Uniform* ir, const float* ir_data, int ir_num_samples, float* fft_scratch)
Expand Down Expand Up @@ -275,7 +280,9 @@ void create_nuir_process_state (const IR_Non_Uniform* ir, Process_Non_Uniform_St
{
using State_Data = Process_Uniform_State::State_Data;

state->head.num_channels = 1; // @TODO
state->head_config = ir->head_config;
state->tail.num_channels = 1; // @TODO
state->tail_config = ir->tail_config;

const auto head_state_bytes_needed = state_data_bytes_needed (state->head_config, &ir->head, &state->head);
Expand All @@ -284,7 +291,7 @@ void create_nuir_process_state (const IR_Non_Uniform* ir, Process_Non_Uniform_St
state->head.state_data = reinterpret_cast<State_Data*> (static_cast<std::byte*> (data) + head_state_bytes_needed + tail_state_bytes_needed);
state->tail.state_data = state->head.state_data + 1;

auto* float_data = static_cast<float*>(data);
auto* float_data = static_cast<float*> (data);

state_data_partition_memory (state->head_config, &state->head, state->head.state_data[0], float_data);
state_data_partition_memory (state->tail_config, &state->tail, state->tail.state_data[0], float_data);
Expand Down Expand Up @@ -570,21 +577,21 @@ static void process_multichannel (const Config* config,
float* fft_scratch,
bool with_latency)
{
assert (state->num_channels == ir->num_channels);
assert (ir->num_channels == 1 || ir->num_channels == state->num_channels);
assert (state->num_channels == num_channels);

for (int ch = 0; ch < num_channels; ++ch)
{
IR_Uniform mono_ir
{
.segments = get_segment (config, ir->segments, ch * ir->max_num_segments),
IR_Uniform mono_ir {
.segments = ir->num_channels == 1
? ir->segments
: get_segment (config, ir->segments, ch * ir->max_num_segments),
.num_segments = ir->num_segments,
.max_num_segments = ir->max_num_segments,
.num_channels = 1,
};

Process_Uniform_State mono_state
{
Process_Uniform_State mono_state {
.state_data = state->state_data + ch,
.max_num_segments = state->max_num_segments,
.current_segment = state->current_segment,
Expand Down
11 changes: 10 additions & 1 deletion chowdsp_convolution.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,18 @@ void load_multichannel_ir (const struct Convolution_Config*, struct IR_Uniform*,
/** De-allocates the IR's internal data. */
void destroy_ir (struct IR_Uniform*);

/** Creates a process state object for a given IR. */
/**
* Creates a process state object for a given IR.
* The process state will be created to process the same number of channels as the IR contains.
*/
void create_process_state (const struct Convolution_Config*, const struct IR_Uniform*, struct Process_Uniform_State*);

/**
* Creates a process state object for a given IR, with a specific number of channels.
* This is useful for convolving a monophonic IR with multiple channels.
*/
void create_multichannel_process_state (const struct Convolution_Config*, const struct IR_Uniform*, struct Process_Uniform_State*, int num_channels);

/** Zeros the process state. */
void reset_process_state (const struct Convolution_Config*, struct Process_Uniform_State*);

Expand Down
44 changes: 31 additions & 13 deletions test/chowdsp_convolution_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,12 +418,18 @@ static bool test_convolution (int ir_length_samples, int block_size, int num_blo
return max_error < 5.0e-4f && mse < 1.0e-9f;
}

static bool test_convolution_multi_channel (int ir_length_samples, int block_size, int num_blocks, bool latency, int num_channels)
static bool test_convolution_multi_channel (int ir_length_samples,
int block_size,
int num_blocks,
bool latency,
int num_channels,
bool mono_ir)
{
std::cout << "Running test with IR length: " << ir_length_samples
<< ", block size: " << block_size
<< ", latency: " << (latency ? "ON" : "OFF")
<< ", # channels: " << num_channels << '\n';
<< ", # channels: " << num_channels
<< ", mono IR: " << (mono_ir ? "ON" : "OFF") << '\n';

std::mt19937 rng { 0x12345 };
auto ir = generate (ir_length_samples, rng);
Expand Down Expand Up @@ -458,15 +464,26 @@ static bool test_convolution_multi_channel (int ir_length_samples, int block_siz
auto* fft_scratch = (float*) chowdsp::fft::aligned_malloc (conv_config.fft_size * sizeof (float));

chowdsp::convolution::IR_Uniform conv_ir {};
chowdsp::convolution::create_multichannel_ir (&conv_config,
&conv_ir,
multi_channel_ir.data(),
ir_length_samples,
num_channels,
fft_scratch);
if (mono_ir)
{
chowdsp::convolution::create_ir (&conv_config,
&conv_ir,
ir.data(),
ir_length_samples,
fft_scratch);
}
else
{
chowdsp::convolution::create_multichannel_ir (&conv_config,
&conv_ir,
multi_channel_ir.data(),
ir_length_samples,
num_channels,
fft_scratch);
}

chowdsp::convolution::Process_Uniform_State conv_state {};
chowdsp::convolution::create_process_state (&conv_config, &conv_ir, &conv_state);
chowdsp::convolution::create_multichannel_process_state (&conv_config, &conv_ir, &conv_state, num_channels);

start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < num_blocks; ++i)
Expand Down Expand Up @@ -560,8 +577,7 @@ static bool test_convolution_non_uniform (int ir_length_samples, int block_size,
chowdsp::convolution::Config tail_config {};
chowdsp::convolution::create_config (&tail_config, head_size);

chowdsp::convolution::IR_Non_Uniform conv_ir
{
chowdsp::convolution::IR_Non_Uniform conv_ir {
.head_config = &head_config,
.tail_config = &tail_config,
.head_size = head_size,
Expand Down Expand Up @@ -630,8 +646,10 @@ int main()
success &= test_convolution (100, 511, 4, latency);
success &= test_convolution (100, 32, 10, latency);

success &= test_convolution_multi_channel (6000, 2048, 4, latency, 2);
success &= test_convolution_multi_channel (100, 32, 10, latency, 4);
success &= test_convolution_multi_channel (6000, 2048, 4, latency, 2, false);
success &= test_convolution_multi_channel (100, 32, 10, latency, 4, false);
success &= test_convolution_multi_channel (6000, 512, 4, latency, 2, true);
success &= test_convolution_multi_channel (100, 511, 10, latency, 4, true);
}

success &= test_convolution_non_uniform (6000, 2048, 4, 2048);
Expand Down