caw_viz_udp_app_lib/
lib.rs

1//! A wrapper for the `caw_viz_udp_app` command. That command starts a udp client and opens a
2//! window. The client listens for messages from a server representing vizualizations and renders
3//! them in the window. This library provides the server implementation. Synthesizer code is
4//! expected to use this library to create a udp server and spawn a client which connects to it,
5//! and then manipulate the server to send visualization commands to the client. This library also
6//! contains helper code for writing client applications.
7
8pub mod blink;
9mod common;
10pub mod oscilloscope;
11use std::{collections::HashMap, sync::Mutex};
12
13pub use common::SendStatus;
14
15use caw_core::{Channel, Sig, SigT};
16use lazy_static::lazy_static;
17
18struct StereoOscilloscopeState {
19    server: oscilloscope::Server,
20    last_batch_index: u64,
21    buf: Vec<f32>,
22}
23
24lazy_static! {
25    static ref STEREO_OSCILLOSCOPE_STATES_BY_TITLE: Mutex<HashMap<String, StereoOscilloscopeState>> =
26        Mutex::new(HashMap::new());
27}
28
29pub trait VizSigBool {
30    fn viz_blink(
31        self,
32        title: impl AsRef<str>,
33        config: blink::Config,
34    ) -> Sig<impl SigT<Item = bool>>;
35}
36
37impl<S> VizSigBool for Sig<S>
38where
39    S: SigT<Item = bool>,
40{
41    fn viz_blink(
42        self,
43        title: impl AsRef<str>,
44        config: blink::Config,
45    ) -> Sig<impl SigT<Item = bool>> {
46        let make_viz =
47            move || blink::Server::new(title.as_ref(), config.clone()).unwrap();
48        let mut viz = make_viz();
49        let sig = self.shared();
50        let delay_s = 0.05;
51        let env =
52            caw_modules::adsr_linear_01(sig.clone().trig_to_gate(delay_s))
53                .release_s(delay_s)
54                .build()
55                .with_buf(move |buf| {
56                    match viz.send_samples(buf) {
57                        Ok(SendStatus::Success) => (),
58                        Ok(SendStatus::Disconnected) => {
59                            // re-open the visualization if it was closed
60                            viz = make_viz();
61                        }
62                        Err(e) => eprintln!("{}", e),
63                    }
64                });
65        Sig(sig).force(env)
66    }
67}
68
69pub trait VizSigF32 {
70    fn viz_blink(
71        self,
72        title: impl AsRef<str>,
73        config: blink::Config,
74    ) -> Sig<impl SigT<Item = f32>>;
75
76    fn viz_oscilloscope(
77        self,
78        title: impl AsRef<str>,
79        config: oscilloscope::Config,
80    ) -> Sig<impl SigT<Item = f32>>;
81
82    /// Calling this function twice with the same `title`, passing `Channel::Left` to one
83    /// invocation and `Channel::Right` to the other, will open a single window which receives a
84    /// stream of interleaved samples from both signals.
85    fn viz_oscilloscope_stereo(
86        self,
87        title: impl AsRef<str>,
88        channel: Channel,
89        config: oscilloscope::Config,
90    ) -> Sig<impl SigT<Item = f32>>;
91}
92
93impl<S> VizSigF32 for Sig<S>
94where
95    S: SigT<Item = f32>,
96{
97    fn viz_blink(
98        self,
99        title: impl AsRef<str>,
100        config: blink::Config,
101    ) -> Sig<impl SigT<Item = f32>> {
102        let make_viz =
103            move || blink::Server::new(title.as_ref(), config.clone()).unwrap();
104        let mut viz = make_viz();
105        Sig(self).with_buf(move |buf| {
106            match viz.send_samples(buf) {
107                Ok(SendStatus::Success) => (),
108                Ok(SendStatus::Disconnected) => {
109                    // re-open the visualization if it was closed
110                    viz = make_viz();
111                }
112                Err(e) => eprintln!("{}", e),
113            }
114        })
115    }
116
117    fn viz_oscilloscope(
118        self,
119        title: impl AsRef<str>,
120        config: oscilloscope::Config,
121    ) -> Sig<impl SigT<Item = f32>> {
122        let make_viz = move || {
123            oscilloscope::Server::new(title.as_ref(), config.clone()).unwrap()
124        };
125        let mut viz = make_viz();
126        Sig(self).with_buf(move |buf| {
127            match viz.send_samples(buf) {
128                Ok(SendStatus::Success) => (),
129                Ok(SendStatus::Disconnected) => {
130                    // re-open the visualization if it was closed
131                    viz = make_viz();
132                }
133                Err(e) => eprintln!("{}", e),
134            }
135        })
136    }
137
138    fn viz_oscilloscope_stereo(
139        self,
140        title: impl AsRef<str>,
141        channel: Channel,
142        config: oscilloscope::Config,
143    ) -> Sig<impl SigT<Item = f32>> {
144        let title = title.as_ref().to_string();
145        let make_server = {
146            let title = title.clone();
147            move || {
148                oscilloscope::Server::new(title.as_str(), config.clone())
149                    .unwrap()
150            }
151        };
152        {
153            // The first time this is called, set up the server and store it in a global.
154            let mut states =
155                STEREO_OSCILLOSCOPE_STATES_BY_TITLE.lock().unwrap();
156            states.entry(title.clone()).or_insert_with(|| {
157                StereoOscilloscopeState {
158                    server: make_server(),
159                    last_batch_index: 0,
160                    buf: Vec::new(),
161                }
162            });
163        }
164        Sig(self).with_buf_ctx(move |buf, ctx| {
165            let mut states =
166                STEREO_OSCILLOSCOPE_STATES_BY_TITLE.lock().unwrap();
167            let this_state = states.get_mut(&title).unwrap();
168            if this_state.last_batch_index == ctx.batch_index {
169                // The other channel must have already stored its samples in the buffer, so add the
170                // current channel's samples and then send the buffer.
171                let mut i = match channel {
172                    Channel::Left => 0,
173                    Channel::Right => 1,
174                };
175                for &sample in buf {
176                    this_state.buf[i] = sample;
177                    i += 2;
178                }
179                match this_state.server.send_samples(&this_state.buf) {
180                    Ok(SendStatus::Success) => (),
181                    Ok(SendStatus::Disconnected) => {
182                        // re-open the visualization if it was closed
183                        this_state.server = make_server();
184                    }
185                    Err(e) => eprintln!("{}", e),
186                }
187            } else {
188                // The current channel is the first of the two channels to be computed this frame.
189                // Add the current channel's samples to the buffer but don't send the buffer. The
190                // other channel will send the buffer once it's added its samples.
191                this_state.last_batch_index = ctx.batch_index;
192                this_state.buf.clear();
193                for &sample in buf {
194                    // Add the sample twice. One of these samples will be for the left channel and
195                    // one will be for the right channel. We don't care which one we are at this
196                    // point. The other channel will overwrite its entries in the buffer before
197                    // sending.
198                    this_state.buf.push(sample);
199                    this_state.buf.push(sample);
200                }
201            }
202        })
203    }
204}