| 1 | // vtest build: !openbsd |
| 2 | import os |
| 3 | import time |
| 4 | import sokol.audio |
| 5 | import encoding.vorbis |
| 6 | |
| 7 | fn 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 | |
| 17 | fn 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 | |
| 37 | struct Player { |
| 38 | mut: |
| 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 | |
| 56 | fn (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 | |
| 67 | fn (mut p Player) stop() { |
| 68 | p.free() |
| 69 | audio.shutdown() |
| 70 | } |
| 71 | |
| 72 | fn (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 | |
| 82 | fn (mut p Player) close_decoder() { |
| 83 | if !isnil(p.decoder) { |
| 84 | C.stb_vorbis_close(p.decoder) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | fn (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 | |