0

I need a macro to prevent the need to write the following code for over a dozen powers of 2:

    pub(crate) fn calc(samples: &[f32]) -> Vec<Complex32> {
        let buffer = samples;
        let mut res = {
            if buffer.len() == 2 {
                let mut buffer: [_; 2] = buffer.try_into().unwrap();
                real::rfft_2(&mut buffer).to_vec()
            } else if buffer.len() == 4 {
                let mut buffer: [_; 4] = buffer.try_into().unwrap();
                real::rfft_4(&mut buffer).to_vec()
            /// ..
            } else if buffer.len() == 16384 {
                let mut buffer: [_; 16384] = buffer.try_into().unwrap();
                real::rfft_16384(&mut buffer).to_vec()
            } else {
                panic!(
                    "`microfft::real` only supports powers of 2 between 2 and 16384 for the amount of samples!"
                );
            }

However, I do not know how I can express this in a Rust macro definition. A macro would be invoked like this:
generate_ifs!(buffer, 2, 4, 8, 16, 32, 64, 128, 256, ..., 16384)

Probably, I need something like this:

macro_rules! generate_ifs {
    ($buffer:ident, $e:literal) => {
        {
            
        }
    };

    ($buffer:ident, $e:literal, $($es:literal),+) => {{
        generate_ifs! { $buffer:ident, $e }
        // do something special to generate "else if"
        generate_ifs! { $buffer:ident, $($es),+ }
    }};
}

But I can't see how this may work out in the end.

phip1611
  • 5,460
  • 4
  • 30
  • 57

3 Answers3

2

If you are enumerating all the powers of 2 already, you can get a short solution by writing out the identifiers (rfft_2) of the functions too:

use microfft::{Complex32, real};

fn calc(samples: &[f32]) -> Vec<Complex32> {
    let mut buffer = samples.to_vec();

    macro_rules! gen_ifs {
        ($($size:literal => $func:ident),*) => {
            if false {
                unreachable!();
            }
            $(
                else if buffer.len() == $size {
                    real::$func(<&mut [_; $size]>::try_from(&mut buffer[..]).unwrap()).to_vec()
                }
            )*
            else {
                panic!();
            }
        };
    }

    gen_ifs!(2 => rfft_2, 4 => rfft_4, 8 => rfft_8)
}

I used a trick to get around the if vs else if problem by adding one if false branch at the start.

If you want to omit the function names in your macro invocation, it gets more difficult (if not impossible with macro_rules!), see for example this question: Interpolate `ident` in string literal in `macro_rules!`

Niklas Mohrin
  • 1,756
  • 7
  • 23
1

Thanks to Nicholas Bishop, I now have a solution using the paste crate and a big match. The macro looks like this:

macro_rules! fft {
    ($buffer:expr, $( $i:literal ),*) => {
        match $buffer.len() {
            $(
                $i => {
                    let mut buffer: [_; $i] = $buffer.try_into().unwrap();
                    paste! (
                        real::[<rfft_$i>]
                    )(&mut buffer).to_vec()
                }
            )*
            _ => { unimplemented!("unexpected buffer len") }
        }
    };
}

I think, it is the neatest solution as it uses idiomatic Rust (just a plain match under the hood) and doesn't have the overhead of dynamic closure creation.

phip1611
  • 5,460
  • 4
  • 30
  • 57
-1

You could work with a closure here. Create the closure in your macro invocation and immediately call it, returning its result. This allows you to rely on return in each if-statement, so you don't need an else block:

macro_rules! generate_ifs {
    ($buffer:ident, $($e:literal),+) => {
        {
           let anonymous_function = |buffer: &[f32]| -> Vec<num::complex::Complex32> {
                $(
                    if $buffer.len() == $e {
                        return buffer.iter().map(crate::as_complex).collect();
                    }
                )+

                panic!(
                    "`microfft::real` only supports powers of 2 between 2 and 16384 for the amount of samples!"
                );
            };

            anonymous_function(&$buffer)
        }
    };
}

You can find the full example here.

Jonas Fassbender
  • 2,371
  • 1
  • 3
  • 19