v / examples / sokol / sounds / ogg_player.v
138 lines · 128 sloc · 3.67 KB · e1b7cb16f36095dbe0ed62c9c7341850d78ae507
Raw
1// vtest build: !openbsd
2import os
3import time
4import sokol.audio
5import encoding.vorbis
6
7fn main() {
8 unbuffer_stdout()
9 if os.args.len < 2 {
10 eprintln('Usage: ogg_player file.ogg ...')
11 play_sounds([os.resource_abs_path('pickup.ogg')])!
12 exit(1)
13 }
14 play_sounds(os.args[1..])!
15}
16
17fn play_sounds(files []string) ! {
18 mut player := Player{
19 decoder: unsafe { nil }
20 }
21 player.init()
22 for f in files {
23 if !os.exists(f) || os.is_dir(f) {
24 eprintln('skipping "${f}" (does not exist)')
25 continue
26 }
27 fext := os.file_ext(f).to_lower()
28 if fext != '.ogg' {
29 eprintln('skipping "${f}" (not an .ogg file)')
30 continue
31 }
32 player.play_ogg_file(f)!
33 }
34 player.stop()
35}
36
37struct Player {
38mut:
39 channels int
40 sample_rate int
41 pos int
42 finished bool
43 push_slack_ms int = 5
44 stream_rate u32
45 stream_channels int
46 stream_len_samples u32
47 stream_len_seconds f32
48 xerror vorbis.VorbisErrorCode
49 allocator C.stb_vorbis_alloc = C.stb_vorbis_alloc{
50 alloc_buffer: 0
51 alloc_buffer_length_in_bytes: 0
52 }
53 decoder &C.stb_vorbis // TODO: cgen error with -cstrict -gcc, when this is = unsafe { nil } here
54}
55
56fn (mut p Player) init() {
57 audio.setup()
58 p.sample_rate = audio.sample_rate()
59 p.channels = audio.channels()
60 alloc_size := 200 * 1024
61 p.allocator = C.stb_vorbis_alloc{
62 alloc_buffer: unsafe { &char(vcalloc(alloc_size)) }
63 alloc_buffer_length_in_bytes: alloc_size
64 }
65}
66
67fn (mut p Player) stop() {
68 p.free()
69 audio.shutdown()
70}
71
72fn (mut p Player) free() {
73 p.finished = false
74 p.pos = 0
75 p.close_decoder()
76 unsafe { free(p.allocator.alloc_buffer) }
77 unsafe {
78 p.allocator.alloc_buffer = nil
79 }
80}
81
82fn (mut p Player) close_decoder() {
83 if !isnil(p.decoder) {
84 C.stb_vorbis_close(p.decoder)
85 }
86}
87
88fn (mut p Player) play_ogg_file(fpath string) ! {
89 p.close_decoder()
90 p.pos = 0
91 p.xerror = .no_error
92 p.decoder = C.stb_vorbis_open_filename(&char(fpath.str), voidptr(&p.xerror), &p.allocator)
93 if isnil(p.decoder) || p.xerror != .no_error {
94 return error('could not open ogg file: ${fpath}, xerror: ${p.xerror}')
95 }
96 info := C.stb_vorbis_get_info(p.decoder)
97 p.stream_rate = info.sample_rate
98 p.stream_channels = info.channels
99 p.stream_len_samples = C.stb_vorbis_stream_length_in_samples(p.decoder)
100 p.stream_len_seconds = C.stb_vorbis_stream_length_in_seconds(p.decoder)
101 p.finished = false
102
103 if !(p.channels == p.stream_channels && p.sample_rate == p.stream_rate) {
104 audio.shutdown()
105 audio.setup(
106 num_channels: p.stream_channels
107 sample_rate: int(p.stream_rate)
108 )
109 p.sample_rate = audio.sample_rate()
110 p.channels = audio.channels()
111 }
112 println('> play_ogg_file: rate: ${p.sample_rate:5}, channels: ${p.channels:1} | stream rate: ${p.stream_rate:5}, channels: ${p.stream_channels:1}, samples: ${p.stream_len_samples:8} | seconds: ${p.stream_len_seconds:7.3f} | ${fpath}')
113
114 frames := [16384]f32{}
115 pframes := unsafe { &frames[0] }
116 for !p.finished {
117 mut delay := p.push_slack_ms
118 expected_frames := audio.expect()
119 if expected_frames > 0 {
120 mut decoded_frames := 0
121 for decoded_frames < expected_frames {
122 samples := C.stb_vorbis_get_samples_float_interleaved(p.decoder, p.channels,
123 pframes, 1024)
124 if samples == 0 {
125 p.finished = true
126 break
127 }
128 written_frames := audio.push(pframes, samples)
129 decoded_frames += written_frames
130 p.pos += samples
131 }
132 delay = (1_000 * decoded_frames) / p.sample_rate
133 }
134 print('\r position: ${p.pos:9} / ${p.stream_len_samples:-9} samples | ${p.pos * p.stream_len_seconds / p.stream_len_samples:7.3f} / ${p.stream_len_seconds:-7.3f} seconds')
135 time.sleep(int_max(p.push_slack_ms, delay - p.push_slack_ms) * time.millisecond)
136 }
137 println('')
138}
139