Skip to main content

audio_synth/
audio_synth.rs

1use primback::prelude::*;
2
3struct AudioApp {
4    sound_angle: f32,
5    pulse_timer: f32,
6}
7
8impl PrimbackApp for AudioApp {
9    fn init(&mut self, update_ctx: &mut UpdateContext) {
10        // Set fixed camera at center looking at the sound source
11        let camera = Camera::look_at(vec3(0.0, 3.0, 6.0), vec3(0.0, 1.0, 0.0), Vec3::Y);
12        update_ctx.set_camera(camera);
13
14        let light = Light {
15            direction: vec3(0.0, -1.0, -1.0).normalize(),
16            color: Color::new(1.0, 1.0, 1.0),
17            ambient: Color::new(0.3, 0.3, 0.35),
18        };
19        update_ctx.set_light(light);
20    }
21
22    fn update(&mut self, update_ctx: &mut UpdateContext) {
23        if update_ctx.is_key_pressed(KeyCode::Escape) {
24            update_ctx.exit();
25        }
26
27        let dt = update_ctx.delta_time();
28
29        // 1. Rotate the 3D sound source position around the center
30        self.sound_angle += dt * 1.5;
31        let sound_pos = vec3(
32            self.sound_angle.cos() * 3.0,
33            1.0,
34            self.sound_angle.sin() * 3.0,
35        );
36
37        // 2. Play periodic 3D spatial sound pulse
38        self.pulse_timer += dt;
39        if self.pulse_timer >= 0.4 {
40            self.pulse_timer = 0.0;
41            // Play sound with 3D position
42            let sound = Synth::sine(Pitch::A(4))
43                .duration(0.08)
44                .volume(0.6)
45                .position(sound_pos);
46            update_ctx.play_synth(sound);
47        }
48
49        // 3. Play Preset Effects (Keys 1 - 5)
50        if update_ctx.is_key_pressed(KeyCode::Digit1) {
51            update_ctx.play_synth(Synth::coin());
52        }
53        if update_ctx.is_key_pressed(KeyCode::Digit2) {
54            update_ctx.play_synth(Synth::jump());
55        }
56        if update_ctx.is_key_pressed(KeyCode::Digit3) {
57            update_ctx.play_synth(Synth::damage());
58        }
59        if update_ctx.is_key_pressed(KeyCode::Digit4) {
60            update_ctx.play_synth(Synth::explosion());
61        }
62        if update_ctx.is_key_pressed(KeyCode::Digit5) {
63            update_ctx.play_synth(Synth::click());
64        }
65
66        // 4. Keyboard Piano (Keys Q, W, E, R, T, Y, U, I)
67        let piano_keys = [
68            (KeyCode::KeyQ, Pitch::C(4)),
69            (KeyCode::KeyW, Pitch::D(4)),
70            (KeyCode::KeyE, Pitch::E(4)),
71            (KeyCode::KeyR, Pitch::F(4)),
72            (KeyCode::KeyT, Pitch::G(4)),
73            (KeyCode::KeyY, Pitch::A(4)),
74            (KeyCode::KeyU, Pitch::B(4)),
75            (KeyCode::KeyI, Pitch::C(5)),
76        ];
77
78        for (key, pitch) in piano_keys {
79            if update_ctx.is_key_pressed(key) {
80                // Play triangle wave for a retro 8-bit piano sound
81                update_ctx.play_synth(Synth::triangle(pitch).duration(0.2).volume(0.5));
82            }
83        }
84    }
85
86    fn draw(&mut self, draw_ctx: &mut DrawContext) {
87        draw_ctx.clear(Color::new(0.08, 0.08, 0.1));
88
89        // Draw flat floor
90        draw_ctx.draw_shape(
91            Shape::plane(),
92            Transform::new_position(vec3(0.0, 0.0, 0.0)).scale(vec3(8.0, 1.0, 8.0)),
93            Color::new(0.15, 0.15, 0.18),
94        );
95
96        // Draw camera position indicator (listener) at (0, 1, 0)
97        draw_ctx.draw_shape(
98            Shape::cube().size(vec3(0.6, 0.6, 0.6)),
99            Transform::new_position(vec3(0.0, 1.0, 0.0)),
100            Color::new(0.2, 0.6, 0.9),
101        );
102
103        // Calculate and draw rotating 3D sound source
104        let sound_pos = vec3(
105            self.sound_angle.cos() * 3.0,
106            1.0,
107            self.sound_angle.sin() * 3.0,
108        );
109        draw_ctx.draw_shape(
110            Shape::sphere().radius(0.4),
111            Transform::new_position(sound_pos),
112            Color::new(0.9, 0.3, 0.3),
113        );
114
115        // Draw line connecting listener and emitter
116        draw_ctx.draw_line_3d(
117            vec3(0.0, 1.0, 0.0),
118            sound_pos,
119            Color::new(0.5, 0.5, 0.5).alpha(0.5),
120        );
121
122        // 3D labels for visual guidance
123        if let Some(screen_pos) = draw_ctx
124            .camera()
125            .world_to_screen(vec3(0.0, 1.5, 0.0), draw_ctx.render_size())
126        {
127            let label = Text::new("Listener (Camera)").scale(1.0);
128            let size = draw_ctx.measure_text(&label);
129            draw_ctx.draw_text(
130                label,
131                RectTransform::new_position(screen_pos - vec2(size.x * 0.5, 0.0)),
132                Color::new(0.2, 0.6, 0.9),
133            );
134        }
135
136        if let Some(screen_pos) = draw_ctx
137            .camera()
138            .world_to_screen(sound_pos + vec3(0.0, 0.6, 0.0), draw_ctx.render_size())
139        {
140            let label = Text::new("3D Sound Source").scale(1.0);
141            let size = draw_ctx.measure_text(&label);
142            draw_ctx.draw_text(
143                label,
144                RectTransform::new_position(screen_pos - vec2(size.x * 0.5, 0.0)),
145                Color::new(0.9, 0.3, 0.3),
146            );
147        }
148
149        // 2D UI instructions
150        draw_ctx.draw_text(
151            Text::new("Audio Synthesizer & Spatial 3D Audio").scale(1.0),
152            RectTransform::new_position(vec2(15.0, 15.0)),
153            Color::WHITE,
154        );
155
156        // Sound effects instruction card
157        let sfx_bg = Rect::new(vec2(280.0, 100.0))
158            .color(Color::new(0.12, 0.12, 0.15))
159            .radius(6.0);
160        draw_ctx.draw_rect(sfx_bg, RectTransform::new_position(vec2(15.0, 50.0)));
161
162        draw_ctx.draw_text(
163            Text::new("SFX Presets (Press Keys 1-5):").scale(1.0),
164            RectTransform::new_position(vec2(25.0, 60.0)),
165            Color::new(1.0, 0.8, 0.2),
166        );
167        let sfx_lines = [
168            "1: Coin       2: Jump",
169            "3: Damage     4: Explosion",
170            "5: Click",
171        ];
172        for (idx, line) in sfx_lines.iter().enumerate() {
173            draw_ctx.draw_text(
174                Text::new(*line).scale(1.0),
175                RectTransform::new_position(vec2(25.0, 85.0 + (idx as f32) * 16.0)),
176                Color::WHITE,
177            );
178        }
179
180        // Virtual piano instruction card
181        let piano_bg = Rect::new(vec2(320.0, 100.0))
182            .color(Color::new(0.12, 0.12, 0.15))
183            .radius(6.0);
184        draw_ctx.draw_rect(piano_bg, RectTransform::new_position(vec2(305.0, 50.0)));
185
186        draw_ctx.draw_text(
187            Text::new("Keyboard Piano (Play Notes):").scale(1.0),
188            RectTransform::new_position(vec2(315.0, 60.0)),
189            Color::new(0.2, 0.8, 0.5),
190        );
191        draw_ctx.draw_text(
192            Text::new("Keys:  Q   W   E   R   T   Y   U   I").scale(1.0),
193            RectTransform::new_position(vec2(315.0, 85.0)),
194            Color::WHITE,
195        );
196        draw_ctx.draw_text(
197            Text::new("Notes: C4  D4  E4  F4  G4  A4  B4  C5").scale(1.0),
198            RectTransform::new_position(vec2(315.0, 105.0)),
199            Color::new(0.7, 0.7, 0.7),
200        );
201    }
202}
203
204fn main() {
205    let config = PrimbackConfig {
206        window_title: "Primback - Audio Synthesizer Demo".to_string(),
207        render_width: 640,
208        render_height: 480,
209        ..Default::default()
210    };
211
212    Primback::run(
213        config,
214        AudioApp {
215            sound_angle: 0.0,
216            pulse_timer: 0.0,
217        },
218    );
219}