positional_audio/
positional-audio.rs

1use {
2    sfml::{
3        audio::{Music, SoundSource, listener},
4        graphics::{
5            CircleShape, Color, FloatRect, Font, RectangleShape, RenderStates, RenderTarget,
6            RenderWindow, Shape, Text, Transformable,
7        },
8        system::{Clock, Vector2, Vector2f, Vector3},
9        window::{Event, Key, Style},
10    },
11    std::error::Error,
12};
13
14include!("../example_common.rs");
15
16/// Convert a line between two points to an equivalent rotated rectangle
17fn line_to_rect(x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32) -> (FloatRect, f32) {
18    let dx = x2 - x1;
19    let dy = y2 - y1;
20
21    (
22        FloatRect {
23            left: x1,
24            top: y1 - thickness / 2.0,
25            width: (dx * dx + dy * dy).sqrt(),
26            height: thickness,
27        },
28        dy.atan2(dx).to_degrees(),
29    )
30}
31
32trait RenderTargetExt {
33    /// Draw line between 2 poins
34    fn draw_line(&mut self, p1: Vector2f, p2: Vector2f, thickness: f32);
35}
36
37impl RenderTargetExt for RenderWindow {
38    fn draw_line(&mut self, p1: Vector2f, p2: Vector2f, thickness: f32) {
39        let (rect, angle) = line_to_rect(p1.x, p1.y, p2.x, p2.y, thickness);
40        let mut rs = RectangleShape::from_rect(rect);
41        rs.set_rotation(angle);
42        self.draw_rectangle_shape(&rs, &RenderStates::DEFAULT);
43    }
44}
45
46fn main() -> Result<(), Box<dyn Error>> {
47    example_ensure_right_working_dir();
48
49    let mut rw = RenderWindow::new(
50        (800, 600),
51        "Positional audio demo",
52        Style::CLOSE,
53        &Default::default(),
54    )?;
55    rw.set_vertical_sync_enabled(true);
56    let font = Font::from_file("sansation.ttf")?;
57    let mut text = Text::new("", &font, 20);
58    let mut music = match std::env::args().nth(1) {
59        Some(music_path) => Music::from_file(&music_path)?,
60        None => Music::from_file("canary.wav")?,
61    };
62    if music.channel_count() != 1 {
63        return Err("Sorry, only sounds with 1 channel are supported.".into());
64    };
65    music.set_looping(true);
66    music.play();
67    music.set_position(Vector3::new(0., 0., 0.));
68
69    let mut listener_pos = Vector3::new(0.0, 0.0, 0.0);
70    let center = Vector2::new(400., 300.);
71    let [mut go_left, mut go_right, mut go_up, mut go_down] = [false; 4];
72    let clock = Clock::start()?;
73
74    while rw.is_open() {
75        while let Some(ev) = rw.poll_event() {
76            match ev {
77                Event::Closed => rw.close(),
78                Event::KeyPressed { code, .. } => match code {
79                    Key::A => go_left = true,
80                    Key::D => go_right = true,
81                    Key::W => go_up = true,
82                    Key::S => go_down = true,
83                    _ => {}
84                },
85                Event::KeyReleased { code, .. } => match code {
86                    Key::A => go_left = false,
87                    Key::D => go_right = false,
88                    Key::W => go_up = false,
89                    Key::S => go_down = false,
90                    _ => {}
91                },
92                _ => {}
93            }
94        }
95        let Vector2f { x: mx, y: my } = rw.mouse_position().as_other();
96        let speed = 0.05;
97        if go_left {
98            listener_pos.x -= speed;
99        }
100        if go_right {
101            listener_pos.x += speed;
102        }
103        if go_up {
104            listener_pos.y -= speed;
105        }
106        if go_down {
107            listener_pos.y += speed;
108        }
109        let scale = 20.0; // Scale the positions for better visualization
110        listener::set_position(listener_pos);
111        listener::set_direction(Vector3::new(
112            (mx - center.x) / scale,
113            (my - center.y) / scale,
114            -1.,
115        ));
116        let Vector3 {
117            x: lx,
118            y: ly,
119            z: lz,
120        } = listener::position();
121        let Vector3 {
122            x: dx,
123            y: dy,
124            z: dz,
125        } = listener::direction();
126        rw.clear(Color::BLACK);
127        let mut circle_shape = CircleShape::new(8.0, 32);
128        // Draw circle at center, representing position of music being played
129        circle_shape.set_position(center);
130        circle_shape.set_fill_color(Color::YELLOW);
131        let t = clock.elapsed_time().as_seconds();
132        let radius = 12.0 + t.sin() * 3.0;
133        circle_shape.set_radius(radius);
134        circle_shape.set_origin(radius);
135        rw.draw(&circle_shape);
136        // Draw circle representing listener
137        circle_shape.set_position((center.x + lx * scale, center.y + ly * scale));
138        circle_shape.set_origin(4.0);
139        circle_shape.set_radius(4.0);
140        circle_shape.set_fill_color(Color::GREEN);
141        rw.draw(&circle_shape);
142        // Draw line from listener to direction vector position
143        rw.draw_line(
144            circle_shape.position(),
145            (center.x + dx * scale, center.y + dy * scale).into(),
146            2.0,
147        );
148        text.set_string("WASD + mouse for movement of listener");
149        text.set_position(0.);
150        rw.draw(&text);
151        text.set_string(&format!("Listener position: {lx}, {ly}, {lz}"));
152        text.set_position((0., 20.0));
153        rw.draw(&text);
154        text.set_string(&format!("Listener direction: {dx}, {dy}, {dz}"));
155        text.set_position((0., 40.0));
156        rw.draw(&text);
157        rw.display();
158    }
159    Ok(())
160}