sdl_audio/
sdl-audio.rs

1//! # Example: SDL Audio
2//!
3//! This example demonstrates how SDL can be used not only to implement virtual displays, but at the same time
4//! to use it as an audio device. Here we implement an oscillator with a modulation of its pitch.
5
6use std::sync::{
7    atomic::{AtomicBool, Ordering},
8    Arc,
9};
10
11use embedded_graphics::{
12    mono_font::{ascii::FONT_6X10, MonoTextStyle},
13    pixelcolor::BinaryColor,
14    prelude::*,
15    text::Text,
16};
17use embedded_graphics_simulator::{
18    sdl2::Keycode, OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window,
19};
20use sdl2::audio::{AudioCallback, AudioSpecDesired};
21
22const SAMPLE_RATE: i32 = 44100;
23
24const PITCH_MIN: f32 = 440.0;
25const PITCH_MAX: f32 = 10000.0;
26
27const PERIOD: f32 = 0.5; // seconds
28const SAMPLES_PER_PERIOD: f32 = SAMPLE_RATE as f32 * PERIOD;
29const PITCH_CHANGE_PER_SAMPLE: f32 = (PITCH_MAX - PITCH_MIN) / SAMPLES_PER_PERIOD;
30
31fn main() -> Result<(), core::convert::Infallible> {
32    // Prepare the audio "engine" with gate control
33    let gate = Arc::new(AtomicBool::new(false));
34    let audio_wrapper = AudioWrapper::new(gate.clone());
35
36    let audio_spec = AudioSpecDesired {
37        freq: Some(SAMPLE_RATE),
38        channels: Some(1),
39        samples: Some(32),
40    };
41
42    // Initialize the SDL audio subsystem.
43    //
44    // `sdl2` allows multiple instances of the SDL context to exist, which makes
45    // it possible to access SDL subsystems which aren't used by the simulator.
46    // But keep in mind that only one `EventPump` can exists and the simulator
47    // window creation will fail if the `EventPump` is claimed in advance.
48    let sdl = sdl2::init().unwrap();
49    let audio_subsystem = sdl.audio().unwrap();
50
51    // Start audio playback by opening the device and setting the custom callback.
52    let audio_device = audio_subsystem
53        .open_playback(None, &audio_spec, |_| audio_wrapper)
54        .unwrap();
55    audio_device.resume();
56
57    let output_settings = OutputSettingsBuilder::new()
58        .scale(4)
59        .theme(embedded_graphics_simulator::BinaryColorTheme::OledWhite)
60        .build();
61
62    let mut window = Window::new("Simulator audio example", &output_settings);
63
64    let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
65    let text_position = Point::new(25, 30);
66    let text = Text::new("Press space...", text_position, text_style);
67
68    let mut display: SimulatorDisplay<BinaryColor> = SimulatorDisplay::new(Size::new(128, 64));
69    text.draw(&mut display).unwrap();
70    'running: loop {
71        window.update(&display);
72
73        for event in window.events() {
74            match event {
75                SimulatorEvent::Quit => break 'running,
76                SimulatorEvent::KeyDown {
77                    keycode, repeat, ..
78                } if keycode == Keycode::Space && !repeat => {
79                    gate.store(true, Ordering::SeqCst);
80                    display.clear(BinaryColor::On).unwrap();
81                }
82                SimulatorEvent::KeyUp { keycode, .. } => match keycode {
83                    Keycode::Space => {
84                        gate.store(false, Ordering::SeqCst);
85                        display.clear(BinaryColor::Off).unwrap();
86                        text.draw(&mut display).unwrap();
87                    }
88                    _ => {}
89                },
90                _ => {}
91            }
92        }
93    }
94
95    Ok(())
96}
97
98struct AudioWrapper {
99    gate: Arc<AtomicBool>,
100    phase: f32,
101    pitch: f32,
102}
103
104impl AudioWrapper {
105    fn new(gate: Arc<AtomicBool>) -> Self {
106        Self {
107            gate,
108            phase: 0.0,
109            pitch: PITCH_MIN,
110        }
111    }
112}
113
114impl AudioCallback for AudioWrapper {
115    type Channel = f32;
116
117    fn callback(&mut self, out: &mut [f32]) {
118        let gate = self.gate.load(Ordering::SeqCst);
119        if !gate {
120            self.pitch = PITCH_MIN;
121            out.fill(0.0);
122            return;
123        }
124
125        for x in out.iter_mut() {
126            self.phase += self.pitch / SAMPLE_RATE as f32;
127            *x = self.phase.sin();
128
129            if self.pitch > PITCH_MAX {
130                self.pitch = PITCH_MIN;
131            }
132
133            self.pitch += PITCH_CHANGE_PER_SAMPLE;
134        }
135    }
136}