v/examples/sokol/sounds/wav_player.v

210 lines
5.1 KiB
V

import os
import time
import sokol.audio
struct Player {
mut:
samples []f32
pos int
finished bool
}
fn main() {
if os.args.len < 2 {
eprintln('Usage: play_wav file1.wav file2.wav ...')
play_sounds([os.resource_abs_path('uhoh.wav')]) ?
exit(1)
}
play_sounds(os.args[1..]) ?
}
fn play_sounds(files []string) ? {
mut player := Player{}
player.init()
for f in files {
if !os.exists(f) || os.is_dir(f) {
eprintln('skipping "$f" (does not exist)')
continue
}
fext := os.file_ext(f).to_lower()
if fext != '.wav' {
eprintln('skipping "$f" (not a .wav file)')
continue
}
player.play_wav_file(f) ?
}
player.stop()
}
//
fn audio_player_callback(buffer &f32, num_frames int, num_channels int, mut p Player) {
if p.finished {
return
}
ntotal := num_channels * num_frames
nremaining := p.samples.len - p.pos
nsamples := if nremaining < ntotal { nremaining } else { ntotal }
if nsamples <= 0 {
p.finished = true
return
}
unsafe {C.memcpy(buffer, &p.samples[p.pos], nsamples * int(sizeof(f32)))}
p.pos += nsamples
}
fn (mut p Player) init() {
audio.setup({
num_channels: 2
stream_userdata_cb: audio_player_callback
user_data: p
})
}
fn (mut p Player) stop() {
audio.shutdown()
p.free()
}
fn (mut p Player) play_wav_file(fpath string) ? {
println('> play_wav_file: $fpath')
samples := read_wav_file_samples(fpath) ?
p.finished = true
p.samples << samples
p.finished = false
for !p.finished {
time.sleep_ms(16)
}
p.free()
}
fn (mut p Player) free() {
p.finished = false
p.samples = []f32{}
p.pos = 0
}
// The read_wav_file_samples function below is based on the following sources:
// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
// http://www.lightlink.com/tjweber/StripWav/WAVE.html
// http://www.lightlink.com/tjweber/StripWav/Canon.html
// https://tools.ietf.org/html/draft-ema-vpim-wav-00
// NB: > The chunks MAY appear in any order except that the Format chunk
// > MUST be placed before the Sound data chunk (but not necessarily
// > contiguous to the Sound data chunk).
struct RIFFHeader {
riff [4]byte
file_size u32
form_type [4]byte
}
struct RIFFChunkHeader {
chunk_type [4]byte
chunk_size u32
chunk_data voidptr
}
struct RIFFFormat {
format_tag u16 // PCM = 1; Values other than 1 indicate some form of compression.
nchannels u16 // Nc ; 1 = mono ; 2 = stereo
sample_rate u32 // F
avg_bytes_per_second u32 // F * M*Nc
nblock_align u16 // M*Nc
bits_per_sample u16 // 8 * M
cbsize u16 // Size of the extension: 22
valid_bits_per_sample u16 // at most 8*M
channel_mask u32 // Speaker position mask
sub_format [16]byte // GUID
}
fn read_wav_file_samples(fpath string) ?[]f32 {
mut res := []f32{}
// eprintln('> read_wav_file_samples: $fpath -------------------------------------------------')
mut bytes := os.read_bytes(fpath) ?
mut pbytes := byteptr(bytes.data)
mut offset := u32(0)
rh := &RIFFHeader(pbytes)
// eprintln('rh: $rh')
if rh.riff != [byte(`R`), `I`, `F`, `F`]! {
return error('WAV should start with `RIFF`')
}
if rh.form_type != [byte(`W`), `A`, `V`, `E`]! {
return error('WAV should have `WAVE` form type')
}
if rh.file_size + 8 != bytes.len {
return error('WAV should have valid lenght')
}
offset += sizeof(RIFFHeader)
mut rf := &RIFFFormat(0)
for {
if offset >= bytes.len {
break
}
//
ch := &RIFFChunkHeader(unsafe {pbytes + offset})
offset += 8 + ch.chunk_size
// eprintln('ch: $ch')
// eprintln('p: $pbytes | offset: $offset | bytes.len: $bytes.len')
// ////////
if ch.chunk_type == [byte(`L`), `I`, `S`, `T`]! {
continue
}
//
if ch.chunk_type == [byte(`i`), `d`, `3`, ` `]! {
continue
}
//
if ch.chunk_type == [byte(`f`), `m`, `t`, ` `]! {
// eprintln('`fmt ` chunk')
rf = &RIFFFormat(&ch.chunk_data)
// eprintln('fmt riff format: $rf')
if rf.format_tag != 1 {
return error('only PCM encoded WAVs are supported')
}
if rf.nchannels < 1 || rf.nchannels > 2 {
return error('only mono or stereo WAVs are supported')
}
if rf.bits_per_sample !in [u16(8), 16] {
return error('only 8 or 16 bits per sample WAVs are supported')
}
continue
}
//
if ch.chunk_type == [byte(`d`), `a`, `t`, `a`]! {
if rf == 0 {
return error('`data` chunk should be after `fmt ` chunk')
}
// eprintln('`fmt ` chunk: $rf\n`data` chunk: $ch')
mut doffset := 0
mut dp := byteptr(&ch.chunk_data)
for doffset < ch.chunk_size {
for c := 0; c < rf.nchannels; c++ {
mut x := f32(0.0)
mut step := 0
ppos := unsafe {dp + doffset}
if rf.bits_per_sample == 8 {
d8 := byteptr(ppos)
x = (f32(*d8) - 128) / 128.0
step = 1
doffset++
}
if rf.bits_per_sample == 16 {
d16 := &i16(ppos)
x = f32(*d16) / 32768.0
step = 2
}
doffset += step
if doffset < ch.chunk_size {
res << x
if rf.nchannels == 1 {
// Duplicating single channel mono sounds,
// produces a stereo sound, simplifying further processing:
res << x
}
}
}
}
}
}
return res
}