Skip to content

Commit 35791ed

Browse files
authored
examples: add port of official SDL3 example audio/02-simple-playback-callback (#984)
1 parent caa84b8 commit 35791ed

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.wasm
2+
*.js
3+
*.html
4+
5+
audio
6+
02-simple-playback-callback
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright(C) 2025 Delyan Angelov. All rights reserved.
2+
// Use of this source code is governed by an MIT license
3+
// that can be found in the LICENSE file.
4+
module no_main
5+
6+
import sdl
7+
import sdl.callbacks
8+
9+
fn init() {
10+
callbacks.on_init(app_init)
11+
callbacks.on_iterate(app_iterate)
12+
}
13+
14+
// SDLApp is dedicated to holding the state of the application
15+
struct SDLApp {
16+
window &sdl.Window = unsafe { nil }
17+
renderer &sdl.Renderer = unsafe { nil }
18+
mut:
19+
stream &sdl.AudioStream = unsafe { nil }
20+
current_sine_sample int
21+
}
22+
23+
fn feed_more(mut app SDLApp, astream &sdl.AudioStream, oadditional_amount int, total_amount int) {
24+
// total_amount is how much data the audio stream is eating right now, additional_amount is how much more it needs
25+
// than what it currently has queued (which might be zero!). You can supply any amount of data here; it will take what
26+
// it needs and use the extra later. If you don't give it enough, it will take everything and then feed silence to the
27+
// hardware for the rest. Ideally, though, we always give it what it needs and no extra, so we aren't buffering more
28+
// than necessary
29+
mut additional_amount := 32 + (oadditional_amount / int(sizeof(f32))) // convert from bytes to samples, with some slack, to prevent cracks with tcc/debug/gc (32 samples is 4ms for a 8000 sampling rate)
30+
for additional_amount > 0 {
31+
mut samples := [128]f32{} // this will feed 128 samples each iteration until we have enough
32+
total := int_min(additional_amount, samples.len)
33+
// generate a 440Hz pure tone
34+
for i := 0; i < total; i++ {
35+
freq := 440
36+
phase := f32(app.current_sine_sample * freq) / f32(8000)
37+
samples[i] = sdl.sinf(phase * 2 * f32(sdl.pi_f))
38+
app.current_sine_sample++
39+
}
40+
// wrapping around to avoid floating-point errors
41+
app.current_sine_sample %= 8000
42+
43+
// feed the new data to the stream. It will queue at the end, and trickle out as the hardware needs more data
44+
sdl.put_audio_stream_data(astream, &samples[0], total * int(sizeof(f32)))
45+
additional_amount -= total // subtract what we've just fed the stream
46+
}
47+
}
48+
49+
// app_init runs once at startup.
50+
pub fn app_init(appstate &&SDLApp, argc int, argv &&char) sdl.AppResult {
51+
// Allocate / instantiate the state struct on the heap
52+
// Hand it over to SDL so it can be retreived in the other App* callbacks
53+
mut app := &SDLApp{}
54+
unsafe {
55+
*appstate = app
56+
}
57+
sdl.set_app_metadata(c'Example Simple Audio Playback Callbacl', c'1.0', c'com.example.audio-simple-playback-callback')
58+
if !sdl.init(sdl.init_video | sdl.init_audio) {
59+
eprintln('Could not initialize SDL: ${sdl.get_error_v()}')
60+
return .failure
61+
}
62+
// we don't _need_ a window for audio-only things but it's good policy to have one.
63+
if !sdl.create_window_and_renderer(c'examples/audio/simple-playback-callback', 640,
64+
480, 0, &app.window, &app.renderer) {
65+
eprintln('Could not create window/renderer: ${sdl.get_error_v()}')
66+
return .failure
67+
}
68+
spec := sdl.AudioSpec{
69+
channels: 1
70+
format: ._f32_1
71+
freq: 8000
72+
}
73+
stream := sdl.open_audio_device_stream(sdl.audio_device_default_playback, &spec, feed_more,
74+
app)
75+
if stream == unsafe { C.NULL } {
76+
eprintln('Could not create audio stream: ${sdl.get_error_v()}')
77+
return .failure
78+
}
79+
app.stream = stream
80+
// open_audio_device_stream starts the device paused. You have to tell it to resume playing:
81+
sdl.resume_audio_stream_device(stream)
82+
return .continue
83+
}
84+
85+
// app_iterate runs once per frame, and is the heart of the program.
86+
pub fn app_iterate(mut app SDLApp) sdl.AppResult {
87+
// app will be the same state instance, that we initialised in `app_init`
88+
// sdl.set_render_draw_color_float(app.renderer, 0.5, 0.5, 1.0, sdl.alpha_opaque)
89+
sdl.render_clear(app.renderer)
90+
sdl.render_present(app.renderer)
91+
// all the work of feeding the audio stream is happening in a callback in a background thread
92+
return .continue
93+
}

v_specific.v

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module sdl
2+
3+
// get_error_v returns a V string, with a description of the last SDL error.
4+
// See also: get_error (SDL_GetError).
5+
pub fn get_error_v() string {
6+
return unsafe { cstring_to_vstring(get_error()) }
7+
}

0 commit comments

Comments
 (0)