Skip to content

Commit 6d9a1ef

Browse files
Multi-channel processing with mono IRs, and updated docs (#5)
* Multi-channel processing with mono IRs, and updated docs * Update README.md Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent 2dd1217 commit 6d9a1ef

File tree

4 files changed

+122
-39
lines changed

4 files changed

+122
-39
lines changed

README.md

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ add please create a GitHub Issue.
1515

1616
## Usage
1717

18+
### Basic Usage (mono IR, mono i/o, uniform partitioning)
19+
1820
First, create a `Config` object:
1921

2022
```cpp
@@ -36,20 +38,16 @@ chowdsp::convolution::IR_Uniform ir {};
3638
chowdsp::convolution::create_ir (&config, &ir, my_ir.data(), my_ir.size());
3739
```
3840
39-
Then we'll create a convolution "state". For this example, let's assume
40-
we have a monophonic IR that's be used to process a stereo input.
41+
Then we'll create a convolution "state".
4142
```cpp
42-
chowdsp::convolution::Process_Uniform_State left_state {};
43-
chowdsp::convolution::Process_Uniform_State right_state {};
44-
chowdsp::convolution::create_process_state (&config, &ir, &left_state);
45-
chowdsp::convolution::create_process_state (&config, &ir, &right_state);
43+
chowdsp::convolution::Process_Uniform_State state {};
44+
chowdsp::convolution::create_process_state (&config, &ir, &state);
4645
```
4746

4847
Now we're ready to process some data:
4948

5049
```cpp
51-
chowdsp::convolution::process_sample (&config, &ir, &left_state, left_channel_data, left_channel_data, num_samples, fft_scratch);
52-
chowdsp::convolution::process_sample (&config, &ir, &right_state, right_channel_data, right_channel_data, num_samples, fft_scratch);
50+
chowdsp::convolution::process_samples (&config, &ir, &state, data, data, num_samples, fft_scratch);
5351
```
5452
5553
Alternatively, we could use `process_samples_with_latency()` which is
@@ -59,12 +57,63 @@ Finally, let's clean up all our memory allocation:
5957
6058
```cpp
6159
chowdsp::fft::aligned_free (fft_scratch);
62-
chowdsp::convolution::destroy_process_state (&left_state);
63-
chowdsp::convolution::destroy_process_state (&right_state);
60+
chowdsp::convolution::destroy_process_state (&state);
6461
chowdsp::convolution::destroy_ir (&ir);
6562
chowdsp::convolution::destroy_config (&config);
6663
```
6764

65+
### Multi-Channel Processing (mono IR)
66+
67+
Let's say that you want to convolve a stereo audio stream with a mono IR.
68+
We can use `create_multichannel_process_state()` to create a processing state
69+
with a given number of channels.
70+
71+
```cpp
72+
chowdsp::convolution::Process_Uniform_State stereo_state {};
73+
chowdsp::convolution::create_multichannel_process_state (&config, &ir, &stereo_state, 2);
74+
```
75+
76+
To process our audio, we'll want to use `process_samples_multichannel()`
77+
(or `process_samples_multichannel_with_latency()`).
78+
79+
```cpp
80+
float* channel_data[2] {
81+
left_channel_data,
82+
right_channel_data,
83+
};
84+
chowdsp::convolution::process_samples_multichannel (&config, &ir, &state, channel_data, channel_data, num_samples, 2, fft_scratch);
85+
```
86+
87+
### Multi-Channel IRs
88+
89+
let's create a stereo, uniform-partitioned IR:
90+
91+
```cpp
92+
float* ir_data[2] {
93+
left_ir_data,
94+
right_ir_data,
95+
};
96+
chowdsp::convolution::IR_Uniform ir {};
97+
chowdsp::convolution::create_multichannel_ir (&config, &ir, ir_data, ir_num_samples, 2, fft_scratch);
98+
```
99+
100+
Now if we call `create_process_state()`, the state will automatically be created
101+
for the same number of channels as the IR.
102+
```cpp
103+
chowdsp::convolution::Process_Uniform_State state {};
104+
chowdsp::convolution::create_process_state (&config, &ir, &state);
105+
```
106+
107+
Then (as before), we can do our multi-channel processing:
108+
109+
```cpp
110+
float* channel_data[2] {
111+
left_channel_data,
112+
right_channel_data,
113+
};
114+
chowdsp::convolution::process_samples_multichannel (&config, &ir, &state, channel_data, channel_data, num_samples, 2, fft_scratch);
115+
```
116+
68117
### Multi-Threaded Usage
69118
70119
What should you do if you're looking to load an impulse response
@@ -79,7 +128,7 @@ thread is still running? The basic idea is that you should:
79128
Note that the `Config` object is thread-safe, so you may use the
80129
same config on both your audio thread and background thread (e.g.
81130
when calling `create_ir()` or `load_ir()`). However, the `fft_scratch`
82-
is not thread-safe, so make sure to allocate a dedicated `fft_scratch`
131+
is **not** thread-safe, so make sure to allocate a dedicated `fft_scratch`
83132
for each thread.
84133
85134
## License

chowdsp_convolution.cpp

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ static int next_pow2 (int v) noexcept
2222
static int pad_floats (int N)
2323
{
2424
static constexpr int pad_len = 16;
25-
const auto N_div = (N + pad_len - 1) / pad_len;
25+
const auto N_div = (N + pad_len - 1) / pad_len;
2626
return N_div * pad_len;
2727
}
2828

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

131131
for (int ch = 0; ch < num_channels; ++ch)
132132
{
133-
IR_Uniform this_channel_ir
134-
{
133+
IR_Uniform this_channel_ir {
135134
.segments = get_segment (config, ir->segments, ch * ir->max_num_segments),
136135
.num_segments = ir->num_segments,
137136
.max_num_segments = ir->max_num_segments,
@@ -147,7 +146,6 @@ static size_t state_data_bytes_needed (const Config* config, const IR_Uniform* i
147146
size_t bytes_needed {};
148147

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

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

177-
void create_process_state (const Config* config, const IR_Uniform* ir, Process_Uniform_State* state)
175+
void create_multichannel_process_state (const Config* config, const IR_Uniform* ir, Process_Uniform_State* state, int num_channels)
178176
{
179177
using State_Data = Process_Uniform_State::State_Data;
178+
state->num_channels = num_channels;
180179
const auto state_bytes_needed = state_data_bytes_needed (config, ir, state);
181180
auto* data = fft::aligned_malloc (state_bytes_needed + state->num_channels * sizeof (State_Data));
182181
state->state_data = reinterpret_cast<State_Data*> (static_cast<std::byte*> (data) + state_bytes_needed);
@@ -189,6 +188,11 @@ void create_process_state (const Config* config, const IR_Uniform* ir, Process_U
189188
reset_process_state (config, state);
190189
}
191190

191+
void create_process_state (const Config* config, const IR_Uniform* ir, Process_Uniform_State* state)
192+
{
193+
create_multichannel_process_state (config, ir, state, ir->num_channels);
194+
}
195+
192196
void reset_process_state (const Config* config, Process_Uniform_State* state)
193197
{
194198
state->current_segment = 0;
@@ -221,8 +225,9 @@ int get_required_nuir_scratch_bytes (const IR_Non_Uniform* ir)
221225
assert (ir->head_config != nullptr);
222226
assert (ir->tail_config != nullptr);
223227
return static_cast<int> ((std::max (ir->head_config->fft_size,
224-
ir->tail_config->fft_size)
225-
+ pad_floats (ir->head_config->block_size)) * sizeof (float));
228+
ir->tail_config->fft_size)
229+
+ pad_floats (ir->head_config->block_size))
230+
* sizeof (float));
226231
}
227232

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

283+
state->head.num_channels = 1; // @TODO
278284
state->head_config = ir->head_config;
285+
state->tail.num_channels = 1; // @TODO
279286
state->tail_config = ir->tail_config;
280287

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

287-
auto* float_data = static_cast<float*>(data);
294+
auto* float_data = static_cast<float*> (data);
288295

289296
state_data_partition_memory (state->head_config, &state->head, state->head.state_data[0], float_data);
290297
state_data_partition_memory (state->tail_config, &state->tail, state->tail.state_data[0], float_data);
@@ -570,21 +577,21 @@ static void process_multichannel (const Config* config,
570577
float* fft_scratch,
571578
bool with_latency)
572579
{
573-
assert (state->num_channels == ir->num_channels);
580+
assert (ir->num_channels == 1 || ir->num_channels == state->num_channels);
574581
assert (state->num_channels == num_channels);
575582

576583
for (int ch = 0; ch < num_channels; ++ch)
577584
{
578-
IR_Uniform mono_ir
579-
{
580-
.segments = get_segment (config, ir->segments, ch * ir->max_num_segments),
585+
IR_Uniform mono_ir {
586+
.segments = ir->num_channels == 1
587+
? ir->segments
588+
: get_segment (config, ir->segments, ch * ir->max_num_segments),
581589
.num_segments = ir->num_segments,
582590
.max_num_segments = ir->max_num_segments,
583591
.num_channels = 1,
584592
};
585593

586-
Process_Uniform_State mono_state
587-
{
594+
Process_Uniform_State mono_state {
588595
.state_data = state->state_data + ch,
589596
.max_num_segments = state->max_num_segments,
590597
.current_segment = state->current_segment,

chowdsp_convolution.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,18 @@ void load_multichannel_ir (const struct Convolution_Config*, struct IR_Uniform*,
127127
/** De-allocates the IR's internal data. */
128128
void destroy_ir (struct IR_Uniform*);
129129

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

136+
/**
137+
* Creates a process state object for a given IR, with a specific number of channels.
138+
* This is useful for convolving a monophonic IR with multiple channels.
139+
*/
140+
void create_multichannel_process_state (const struct Convolution_Config*, const struct IR_Uniform*, struct Process_Uniform_State*, int num_channels);
141+
133142
/** Zeros the process state. */
134143
void reset_process_state (const struct Convolution_Config*, struct Process_Uniform_State*);
135144

test/chowdsp_convolution_test.cpp

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -418,12 +418,18 @@ static bool test_convolution (int ir_length_samples, int block_size, int num_blo
418418
return max_error < 5.0e-4f && mse < 1.0e-9f;
419419
}
420420

421-
static bool test_convolution_multi_channel (int ir_length_samples, int block_size, int num_blocks, bool latency, int num_channels)
421+
static bool test_convolution_multi_channel (int ir_length_samples,
422+
int block_size,
423+
int num_blocks,
424+
bool latency,
425+
int num_channels,
426+
bool mono_ir)
422427
{
423428
std::cout << "Running test with IR length: " << ir_length_samples
424429
<< ", block size: " << block_size
425430
<< ", latency: " << (latency ? "ON" : "OFF")
426-
<< ", # channels: " << num_channels << '\n';
431+
<< ", # channels: " << num_channels
432+
<< ", mono IR: " << (mono_ir ? "ON" : "OFF") << '\n';
427433

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

460466
chowdsp::convolution::IR_Uniform conv_ir {};
461-
chowdsp::convolution::create_multichannel_ir (&conv_config,
462-
&conv_ir,
463-
multi_channel_ir.data(),
464-
ir_length_samples,
465-
num_channels,
466-
fft_scratch);
467+
if (mono_ir)
468+
{
469+
chowdsp::convolution::create_ir (&conv_config,
470+
&conv_ir,
471+
ir.data(),
472+
ir_length_samples,
473+
fft_scratch);
474+
}
475+
else
476+
{
477+
chowdsp::convolution::create_multichannel_ir (&conv_config,
478+
&conv_ir,
479+
multi_channel_ir.data(),
480+
ir_length_samples,
481+
num_channels,
482+
fft_scratch);
483+
}
467484

468485
chowdsp::convolution::Process_Uniform_State conv_state {};
469-
chowdsp::convolution::create_process_state (&conv_config, &conv_ir, &conv_state);
486+
chowdsp::convolution::create_multichannel_process_state (&conv_config, &conv_ir, &conv_state, num_channels);
470487

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

563-
chowdsp::convolution::IR_Non_Uniform conv_ir
564-
{
580+
chowdsp::convolution::IR_Non_Uniform conv_ir {
565581
.head_config = &head_config,
566582
.tail_config = &tail_config,
567583
.head_size = head_size,
@@ -630,8 +646,10 @@ int main()
630646
success &= test_convolution (100, 511, 4, latency);
631647
success &= test_convolution (100, 32, 10, latency);
632648

633-
success &= test_convolution_multi_channel (6000, 2048, 4, latency, 2);
634-
success &= test_convolution_multi_channel (100, 32, 10, latency, 4);
649+
success &= test_convolution_multi_channel (6000, 2048, 4, latency, 2, false);
650+
success &= test_convolution_multi_channel (100, 32, 10, latency, 4, false);
651+
success &= test_convolution_multi_channel (6000, 512, 4, latency, 2, true);
652+
success &= test_convolution_multi_channel (100, 511, 10, latency, 4, true);
635653
}
636654

637655
success &= test_convolution_non_uniform (6000, 2048, 4, 2048);

0 commit comments

Comments
 (0)