Signals & Logger

In the following, we will introduce the signals, Source, Logging and Timer clients.

Both Signals and Source are signals generators and both are multi-channels with a single multiplexed output.

The signals generated with Signals are either a constant, a sinusoide, a ramp, a sinusoide or white-noise. For example, here is an Actor which client is a 2 channels Signals, each channel with the same sinusoide but out-of-phase:

        let n_step = 9;
        let mut signals: Initiator<_> = Signals::new(2, n_step)
            .channel(
                0,
                Signal::Sinusoid {
                    amplitude: 1f64,
                    sampling_frequency_hz: (n_step - 1) as f64,
                    frequency_hz: 1f64,
                    phase_s: 0f64,
                },
            )
            .channel(
                1,
                Signal::Sinusoid {
                    amplitude: 1f64,
                    sampling_frequency_hz: (n_step - 1) as f64,
                    frequency_hz: 1f64,
                    phase_s: 0.5f64,
                },
            )
            .into();

Source signals are user provided, the multiplexed channels are given as a single flatten vector argument:

        let mut source: Initiator<_> = Source::new(
            (0..n_step)
                .flat_map(|x| vec![x as f64, (n_step - x - 1) as f64]) // 2 channels
                .collect(),
            2,
        )
        .into();

The Logging client simply accumulates all its inputs into a single vector. Logging requires all inputs signals to be of the same type. An actor for a Logging client with entries for both the Signals and Source clients is declared with

        let logging = Logging::<f64>::new(2).into_arcx();
        let mut logger = Terminator::<_>::new(logging.clone());

Building a Model out of the 3 actors:

#[derive(UID)]
enum Sinusoides {}
#[derive(UID)]
enum UpDown {}

        signals
            .add_output()
            .unbounded()
            .build::<Sinusoides>()
            .into_input(&mut logger)?;
        source
            .add_output()
            .unbounded()
            .build::<UpDown>()
            .into_input(&mut logger)?;

        model!(signals, source, logger)
            .name("signals-logger")
            .flowchart()
            .check()?
            .run()
            .await?;

gives (the dashed lines representing the "unbounded" inputs):

Signals & Loggers Model

and the following data has been logged:

        println!("Logs:");
        (*logging.lock().await)
            .chunks()
            .enumerate()
            .for_each(|(i, x)| println!("{}: {:+.3?}", i, x));

Signals & Loggers Model output

The Timer client does not generate a new signal instead it adds a beat to the model and takes as input argument a number of beat. A Model with a timer will terminate after the last beat. Lets update the previous Model with a timer which number of beat is half the number of sample that the signals clients are set to generate:

        let mut timer: Initiator<Timer, 1> = Timer::new(n_step / 2).into();

The signals and source clients are modified to accept the timer input:

        let mut signals: Actor<_> = Signals::new(2, n_step)
            .channel(
                0,
                Signal::Sinusoid {
                    amplitude: 1f64,
                    sampling_frequency_hz: (n_step - 1) as f64,
                    frequency_hz: 1f64,
                    phase_s: 0f64,
                },
            )
            .channel(
                1,
                Signal::Sinusoid {
                    amplitude: 1f64,
                    sampling_frequency_hz: (n_step - 1) as f64,
                    frequency_hz: 1f64,
                    phase_s: 0.5f64,
                },
            )
            .into();

        let mut source: Actor<_> = Source::new(
            (0..n_step)
                .flat_map(|x| vec![x as f64, (n_step - x - 1) as f64])
                .collect(),
            2,
        )
        .into();

logger remains the same and the timer is connected to both signals and source:

        timer
            .add_output()
            .multiplex(2)
            .build::<Tick>()
            .into_input(&mut signals)
            .into_input(&mut source)?;
        signals
            .add_output()
            .unbounded()
            .build::<Sinusoides>()
            .into_input(&mut logger)?;
        source
            .add_output()
            .unbounded()
            .build::<UpDown>()
            .into_input(&mut logger)?;

Note that for a client to allow Timer as input, it must implement the TimerMarker trait. The new model looks like this:

        model!(timer, signals, source, logger)
            .name("signals-logger-trunc")
            .flowchart()
            .check()?
            .run()
            .await?;

Signals & Loggers Model

and the following data has been logged:

        println!("Logs:");
        (*logging.lock().await)
            .chunks()
            .enumerate()
            .for_each(|(i, x)| println!("{}: {:+.3?}", i, x));

Signals & Loggers Model output