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