Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,59 @@
|
||||
// vtest build: !openbsd
|
||||
import gg
|
||||
import sokol.audio
|
||||
|
||||
struct AppState {
|
||||
mut:
|
||||
frame_0 int // offset of the current audio frames, relative to the start of the music
|
||||
frames [2048]f32 // a copy of the last rendered audio frames
|
||||
gg &gg.Context = unsafe { nil } // used for drawing
|
||||
}
|
||||
|
||||
fn my_audio_stream_callback(mut soundbuffer &f32, num_frames int, num_channels int, mut acontext AppState) {
|
||||
for frame := 0; frame < num_frames; frame++ {
|
||||
t := int(f32(acontext.frame_0 + frame) * 0.245)
|
||||
// "Techno" by Gabriel Miceli
|
||||
y := (t * (((t / 10 | 0) ^ ((t / 10 | 0) - 1280)) % 11) / 2 & 127) +
|
||||
(t * (((t / 640 | 0) ^ ((t / 640 | 0) - 2)) % 13) / 2 & 127)
|
||||
for ch := 0; ch < num_channels; ch++ {
|
||||
idx := frame * num_channels + ch
|
||||
a := f32(y - 127) / 255.0
|
||||
soundbuffer[idx] = a
|
||||
acontext.frames[idx & 2047] = a
|
||||
}
|
||||
}
|
||||
acontext.frame_0 += num_frames
|
||||
}
|
||||
|
||||
fn graphics_frame(mut state AppState) {
|
||||
ws := gg.window_size()
|
||||
center_y := f32(ws.height / 2)
|
||||
state.gg.begin()
|
||||
for x in 0 .. 1024 {
|
||||
vx := ws.width * f32(x) / 1024.0
|
||||
vy := center_y * 3.0 / 4.0 * (state.frames[2 * x] + state.frames[2 * x + 1])
|
||||
color := gg.Color{f(state, x), f(state, x + 300), f(state, x + 700), 255}
|
||||
state.gg.draw_line(vx, center_y, vx, center_y + vy, color)
|
||||
}
|
||||
state.gg.end()
|
||||
}
|
||||
|
||||
fn f(state &AppState, idx int) u8 {
|
||||
return u8(127 + state.frames[(int(state.gg.frame) + idx) & 2047] * 128)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println('Based on the ByteBeat formula from: https://www.youtube.com/watch?v=V4GfkFbDojc \n "Techno" by Gabriel Miceli')
|
||||
mut state := &AppState{}
|
||||
audio.setup(stream_userdata_cb: my_audio_stream_callback, user_data: state)
|
||||
defer { audio.shutdown() }
|
||||
state.gg = gg.new_context(
|
||||
bg_color: gg.Color{50, 50, 50, 255}
|
||||
width: 800
|
||||
height: 600
|
||||
window_title: 'ByteBeat Music'
|
||||
frame_fn: graphics_frame
|
||||
user_data: state
|
||||
)
|
||||
state.gg.run()
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// vtest build: !openbsd
|
||||
import os
|
||||
import time
|
||||
import sokol.audio
|
||||
import encoding.vorbis
|
||||
|
||||
fn main() {
|
||||
unbuffer_stdout()
|
||||
if os.args.len < 2 {
|
||||
eprintln('Usage: ogg_player file.ogg ...')
|
||||
play_sounds([os.resource_abs_path('pickup.ogg')])!
|
||||
exit(1)
|
||||
}
|
||||
play_sounds(os.args[1..])!
|
||||
}
|
||||
|
||||
fn play_sounds(files []string) ! {
|
||||
mut player := Player{
|
||||
decoder: unsafe { nil }
|
||||
}
|
||||
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 != '.ogg' {
|
||||
eprintln('skipping "${f}" (not an .ogg file)')
|
||||
continue
|
||||
}
|
||||
player.play_ogg_file(f)!
|
||||
}
|
||||
player.stop()
|
||||
}
|
||||
|
||||
struct Player {
|
||||
mut:
|
||||
channels int
|
||||
sample_rate int
|
||||
pos int
|
||||
finished bool
|
||||
push_slack_ms int = 5
|
||||
stream_rate u32
|
||||
stream_channels int
|
||||
stream_len_samples u32
|
||||
stream_len_seconds f32
|
||||
xerror vorbis.VorbisErrorCode
|
||||
allocator C.stb_vorbis_alloc = C.stb_vorbis_alloc{
|
||||
alloc_buffer: 0
|
||||
alloc_buffer_length_in_bytes: 0
|
||||
}
|
||||
decoder &C.stb_vorbis // TODO: cgen error with -cstrict -gcc, when this is = unsafe { nil } here
|
||||
}
|
||||
|
||||
fn (mut p Player) init() {
|
||||
audio.setup()
|
||||
p.sample_rate = audio.sample_rate()
|
||||
p.channels = audio.channels()
|
||||
alloc_size := 200 * 1024
|
||||
p.allocator = C.stb_vorbis_alloc{
|
||||
alloc_buffer: unsafe { &char(vcalloc(alloc_size)) }
|
||||
alloc_buffer_length_in_bytes: alloc_size
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut p Player) stop() {
|
||||
p.free()
|
||||
audio.shutdown()
|
||||
}
|
||||
|
||||
fn (mut p Player) free() {
|
||||
p.finished = false
|
||||
p.pos = 0
|
||||
p.close_decoder()
|
||||
unsafe { free(p.allocator.alloc_buffer) }
|
||||
unsafe {
|
||||
p.allocator.alloc_buffer = nil
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut p Player) close_decoder() {
|
||||
if !isnil(p.decoder) {
|
||||
C.stb_vorbis_close(p.decoder)
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut p Player) play_ogg_file(fpath string) ! {
|
||||
p.close_decoder()
|
||||
p.pos = 0
|
||||
p.xerror = .no_error
|
||||
p.decoder = C.stb_vorbis_open_filename(&char(fpath.str), voidptr(&p.xerror), &p.allocator)
|
||||
if isnil(p.decoder) || p.xerror != .no_error {
|
||||
return error('could not open ogg file: ${fpath}, xerror: ${p.xerror}')
|
||||
}
|
||||
info := C.stb_vorbis_get_info(p.decoder)
|
||||
p.stream_rate = info.sample_rate
|
||||
p.stream_channels = info.channels
|
||||
p.stream_len_samples = C.stb_vorbis_stream_length_in_samples(p.decoder)
|
||||
p.stream_len_seconds = C.stb_vorbis_stream_length_in_seconds(p.decoder)
|
||||
p.finished = false
|
||||
|
||||
if !(p.channels == p.stream_channels && p.sample_rate == p.stream_rate) {
|
||||
audio.shutdown()
|
||||
audio.setup(
|
||||
num_channels: p.stream_channels
|
||||
sample_rate: int(p.stream_rate)
|
||||
)
|
||||
p.sample_rate = audio.sample_rate()
|
||||
p.channels = audio.channels()
|
||||
}
|
||||
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}')
|
||||
|
||||
frames := [16384]f32{}
|
||||
pframes := unsafe { &frames[0] }
|
||||
for !p.finished {
|
||||
mut delay := p.push_slack_ms
|
||||
expected_frames := audio.expect()
|
||||
if expected_frames > 0 {
|
||||
mut decoded_frames := 0
|
||||
for decoded_frames < expected_frames {
|
||||
samples := C.stb_vorbis_get_samples_float_interleaved(p.decoder, p.channels,
|
||||
pframes, 1024)
|
||||
if samples == 0 {
|
||||
p.finished = true
|
||||
break
|
||||
}
|
||||
written_frames := audio.push(pframes, samples)
|
||||
decoded_frames += written_frames
|
||||
p.pos += samples
|
||||
}
|
||||
delay = (1_000 * decoded_frames) / p.sample_rate
|
||||
}
|
||||
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')
|
||||
time.sleep(int_max(p.push_slack_ms, delay - p.push_slack_ms) * time.millisecond)
|
||||
}
|
||||
println('')
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,37 @@
|
||||
// vtest build: !openbsd
|
||||
|
||||
// This program illustrates how to use sokol.audio in a simple console app, *without a gui*.
|
||||
// See melody.v for an example of how it can be integrated into a graphics app.
|
||||
import time
|
||||
import sokol.audio
|
||||
|
||||
fn audio_callback(mut soundbuffer &f32, num_frames int, num_channels int, mut frame_0 &i32) {
|
||||
for frame := 0; frame < num_frames; frame++ {
|
||||
t := i32(f32(*frame_0 + frame) * 0.245)
|
||||
y := (t * (((t / 10 | 0) ^ ((t / 10 | 0) - 1280)) % 11) / 2 & 127) +
|
||||
(t * (((t / 640 | 0) ^ ((t / 640 | 0) - 2)) % 13) / 2 & 127)
|
||||
for ch := 0; ch < num_channels; ch++ {
|
||||
idx := frame * num_channels + ch
|
||||
a := f32(y - 127) / 255.0
|
||||
soundbuffer[idx] = a
|
||||
}
|
||||
}
|
||||
frame_0 += num_frames
|
||||
}
|
||||
|
||||
// The example uses \r, to show a simple progress bar, while the music is playing.
|
||||
// That works best, if the output is not buffered at all.
|
||||
unbuffer_stdout()
|
||||
|
||||
println('The ByteBeat formula used in this example is from https://www.youtube.com/watch?v=V4GfkFbDojc , "Techno" by Gabriel Miceli')
|
||||
|
||||
mut frame_0 := i32(0) // offset of the current audio frames, relative to the start of the music
|
||||
audio.setup(stream_userdata_cb: audio_callback, user_data: &frame_0) // our state/user_data can be just a simple integer
|
||||
|
||||
for t in 0 .. 600 {
|
||||
print('\r> t: ${t:5}s , frame_0: ${frame_0:10} samples')
|
||||
time.sleep(1 * time.second)
|
||||
}
|
||||
println('\nGood bye.')
|
||||
|
||||
audio.shutdown()
|
||||
@@ -0,0 +1,116 @@
|
||||
// vtest build: !openbsd
|
||||
import time
|
||||
import math
|
||||
import term
|
||||
import sokol.audio
|
||||
|
||||
struct Note {
|
||||
mut:
|
||||
phase f32
|
||||
freq f32
|
||||
amplitude f32
|
||||
}
|
||||
|
||||
struct App {
|
||||
mut:
|
||||
notes shared []Note
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println('Press the keys (a, s, d, f, g, h, j, k) to play notes. Press ESC to quit.')
|
||||
mut app := &App{
|
||||
notes: []Note{len: 32}
|
||||
}
|
||||
audio.setup(
|
||||
stream_userdata_cb: audio_callback
|
||||
user_data: app
|
||||
)
|
||||
defer { audio.shutdown() }
|
||||
term.enable_echo(false)
|
||||
defer { term.enable_echo(true) }
|
||||
for {
|
||||
key := term.key_pressed(blocking: false)
|
||||
app.handle_key(key) or { break }
|
||||
time.sleep(5 * time.millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
const c_2_pi = 2 * math.pi
|
||||
const c_phase_step_per_freq = c_2_pi / 44100.0
|
||||
const c_note_start_amplitude = 0.33
|
||||
const c_note_decay = 0.3 / 44100.0
|
||||
|
||||
fn (mut app App) handle_key(key i64) ? {
|
||||
if key == -1 {
|
||||
return
|
||||
}
|
||||
mut freq := f32(0.0)
|
||||
match key {
|
||||
27 { return none }
|
||||
` ` { app.silence() }
|
||||
`a` { freq = 261.63 }
|
||||
`s` { freq = 293.66 }
|
||||
`d` { freq = 329.63 }
|
||||
`f` { freq = 349.23 }
|
||||
`g` { freq = 392.00 }
|
||||
`h` { freq = 440.00 }
|
||||
`j` { freq = 493.88 }
|
||||
`k` { freq = 523.25 }
|
||||
else {}
|
||||
}
|
||||
|
||||
if freq > 0 {
|
||||
app.play(freq, c_note_start_amplitude)
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut app App) play(freq f32, volume f32) {
|
||||
lock app.notes {
|
||||
for mut note in app.notes {
|
||||
note.amplitude -= 0.2
|
||||
}
|
||||
for mut note in app.notes {
|
||||
if note.amplitude <= 0 {
|
||||
note = Note{
|
||||
phase: 0.0
|
||||
freq: freq
|
||||
amplitude: volume
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut app App) silence() {
|
||||
lock app.notes {
|
||||
for mut note in app.notes {
|
||||
note.amplitude = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn audio_callback(mut soundbuffer &f32, num_frames int, num_channels int, mut app App) {
|
||||
lock app.notes {
|
||||
for frame in 0 .. num_frames {
|
||||
mut sample := f32(0.0)
|
||||
for mut note in app.notes {
|
||||
if note.amplitude <= 0 {
|
||||
continue
|
||||
}
|
||||
sample += note.amplitude * math.sinf(note.phase)
|
||||
sample += note.amplitude * math.sinf(note.phase * 2.718)
|
||||
sample += note.amplitude * math.sinf(note.phase * 3.141)
|
||||
|
||||
note.phase += note.freq * c_phase_step_per_freq
|
||||
for note.phase >= c_2_pi {
|
||||
note.phase -= c_2_pi
|
||||
}
|
||||
note.amplitude -= c_note_decay
|
||||
}
|
||||
for ch in 0 .. num_channels {
|
||||
soundbuffer[frame * num_channels + ch] = sample
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// vtest build: !openbsd && !sanitize-memory-clang // Fails compilation with: `ld: /lib/x86_64-linux-gnu/libpthread.so.0: error adding symbols: DSO missing from command line`
|
||||
// import log
|
||||
import math
|
||||
import time
|
||||
import sokol.audio
|
||||
|
||||
fn main() {
|
||||
args := arguments()
|
||||
freq := args[1] or { '417' }.int()
|
||||
amplitude := args[2] or { '0.5' }.f32()
|
||||
dump(freq)
|
||||
dump(amplitude)
|
||||
|
||||
audio.setup(num_channels: 1)
|
||||
sample_rate := dump(audio.sample_rate())
|
||||
dump(audio.buffer_frames())
|
||||
dump(audio.expect())
|
||||
|
||||
// create an array of frames, filled with the desired pure sine tone:
|
||||
mut frames := []f32{len: sample_rate * 2} // 2 seconds
|
||||
for i in 0 .. frames.len {
|
||||
t := f32(i) / f32(sample_rate)
|
||||
frames[i] = amplitude * math.sinf(f32(freq) * t * (2 * math.pi))
|
||||
}
|
||||
|
||||
// play the sound by continuosly pushing samples from the generated
|
||||
// array of sound frames, when there is need for more:
|
||||
mut fpos := 0
|
||||
for {
|
||||
expected_frames := audio.expect()
|
||||
if expected_frames > 0 {
|
||||
written_frames := audio.push(unsafe { &frames[fpos] }, expected_frames)
|
||||
fpos += written_frames
|
||||
// log.info('> pushing done ... fpos: ${fpos:6} | expected_frames: ${expected_frames:6} | written: ${written:6}')
|
||||
if fpos > frames.len - 2 * written_frames {
|
||||
// log.info('> fpos too large: ${fpos}')
|
||||
fpos = find_loop_position(fpos, frames)
|
||||
// log.info('> fpos looped back to start: ${fpos}')
|
||||
}
|
||||
}
|
||||
time.sleep(50 * time.millisecond)
|
||||
}
|
||||
audio.shutdown()
|
||||
}
|
||||
|
||||
fn find_loop_position(fpos int, frames []f32) int {
|
||||
return find_matching_position(frames, fpos, 0.01, 2) or {
|
||||
find_matching_position(frames, fpos, 0.05, 2) or {
|
||||
find_matching_position(frames, fpos, 0.1, 2) or { 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@[direct_array_access]
|
||||
fn find_matching_position(frames []f32, fpos int, tol f32, d int) ?int {
|
||||
if fpos - d < 0 || fpos + d >= frames.len {
|
||||
return none
|
||||
}
|
||||
p1, p2, p3 := frames[fpos - d], frames[fpos], frames[fpos + d]
|
||||
for i in 2 .. fpos / 2 {
|
||||
np1, np2, np3 := frames[i - d], frames[i], frames[i + d]
|
||||
if math.tolerance(np1, p1, tol) && math.tolerance(np2, p2, tol)
|
||||
&& math.tolerance(np3, p3, tol) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// vtest build: !openbsd && !sanitize-memory-clang // Fails compilation with: `ld: /lib/x86_64-linux-gnu/libpthread.so.0: error adding symbols: DSO missing from command line`
|
||||
import time
|
||||
import math
|
||||
import sokol.audio
|
||||
|
||||
const sw = time.new_stopwatch()
|
||||
const sw_start_ms = sw.elapsed().milliseconds()
|
||||
|
||||
@[inline]
|
||||
fn sintone(periods int, frame int, num_frames int) f32 {
|
||||
return math.sinf(f32(periods) * (2 * math.pi) * f32(frame) / f32(num_frames))
|
||||
}
|
||||
|
||||
fn my_audio_stream_callback(mut soundbuffer &f32, num_frames int, num_channels int) {
|
||||
ms := sw.elapsed().milliseconds() - sw_start_ms
|
||||
for frame := 0; frame < num_frames; frame++ {
|
||||
for ch := 0; ch < num_channels; ch++ {
|
||||
idx := frame * num_channels + ch
|
||||
if ms < 250 {
|
||||
soundbuffer[idx] = 0.5 * sintone(20, frame, num_frames)
|
||||
} else if ms < 300 {
|
||||
soundbuffer[idx] = 0.5 * sintone(25, frame, num_frames)
|
||||
} else if ms < 1500 {
|
||||
soundbuffer[idx] *= sintone(22, frame, num_frames)
|
||||
} else {
|
||||
soundbuffer[idx] = 0.5 * sintone(25, frame, num_frames)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
audio.setup(
|
||||
stream_cb: my_audio_stream_callback
|
||||
)
|
||||
time.sleep(2000 * time.millisecond)
|
||||
audio.shutdown()
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,211 @@
|
||||
// vtest build: !openbsd
|
||||
import os
|
||||
import time
|
||||
import sokol.audio
|
||||
|
||||
struct Player {
|
||||
mut:
|
||||
sample_rate int
|
||||
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(mut buffer &f32, num_frames int, num_channels int, mut p Player) {
|
||||
ntotal := num_channels * num_frames
|
||||
unsafe { vmemset(buffer, 0, ntotal * 4) }
|
||||
if p.finished {
|
||||
return
|
||||
}
|
||||
nremaining := p.samples.len - p.pos
|
||||
nsamples := if nremaining < ntotal { nremaining } else { ntotal }
|
||||
if nsamples <= 0 {
|
||||
p.finished = true
|
||||
return
|
||||
}
|
||||
unsafe { vmemcpy(buffer, &p.samples[p.pos], nsamples * 4) }
|
||||
p.pos += nsamples
|
||||
}
|
||||
|
||||
fn (mut p Player) init() {
|
||||
audio.setup(
|
||||
num_channels: 1
|
||||
stream_userdata_cb: audio_player_callback
|
||||
user_data: p
|
||||
sample_rate: 44100
|
||||
)
|
||||
p.sample_rate = audio.sample_rate()
|
||||
}
|
||||
|
||||
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(16 * time.millisecond)
|
||||
}
|
||||
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
|
||||
// Note: > 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]u8
|
||||
file_size u32
|
||||
form_type [4]u8
|
||||
}
|
||||
|
||||
struct RIFFChunkHeader {
|
||||
chunk_type [4]u8
|
||||
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]u8 // 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 := &u8(bytes.data)
|
||||
mut offset := u32(0)
|
||||
rh := unsafe { &RIFFHeader(pbytes) }
|
||||
// eprintln('rh: ${rh}')
|
||||
if rh.riff != [u8(`R`), `I`, `F`, `F`]! {
|
||||
return error('WAV should start with `RIFF`')
|
||||
}
|
||||
if rh.form_type != [u8(`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 length')
|
||||
}
|
||||
offset += sizeof(RIFFHeader)
|
||||
mut rf := &RIFFFormat(unsafe { nil })
|
||||
for {
|
||||
if offset >= bytes.len {
|
||||
break
|
||||
}
|
||||
//
|
||||
ch := unsafe { &RIFFChunkHeader(pbytes + offset) }
|
||||
offset += 8 + ch.chunk_size
|
||||
// eprintln('ch: ${ch}')
|
||||
// eprintln('p: ${pbytes} | offset: ${offset} | bytes.len: ${bytes.len}')
|
||||
// ////////
|
||||
if ch.chunk_type == [u8(`L`), `I`, `S`, `T`]! {
|
||||
continue
|
||||
}
|
||||
//
|
||||
if ch.chunk_type == [u8(`i`), `d`, `3`, ` `]! {
|
||||
continue
|
||||
}
|
||||
//
|
||||
if ch.chunk_type == [u8(`f`), `m`, `t`, ` `]! {
|
||||
// eprintln('`fmt ` chunk')
|
||||
rf = unsafe { &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 == [u8(`d`), `a`, `t`, `a`]! {
|
||||
if unsafe { rf == 0 } {
|
||||
return error('`data` chunk should be after `fmt ` chunk')
|
||||
}
|
||||
// eprintln('`fmt ` chunk: ${rf}\n`data` chunk: ${ch}')
|
||||
mut doffset := 0
|
||||
mut dp := unsafe { &u8(&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 := unsafe { &u8(ppos) }
|
||||
x = (f32(*d8) - 128) / 128.0
|
||||
step = 1
|
||||
doffset++
|
||||
}
|
||||
if rf.bits_per_sample == 16 {
|
||||
d16 := unsafe { &i16(ppos) }
|
||||
x = f32(*d16) / 32768.0
|
||||
step = 2
|
||||
}
|
||||
doffset += step
|
||||
if doffset < ch.chunk_size {
|
||||
for _ in 0 .. (44100 / rf.sample_rate) {
|
||||
res << x
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
Reference in new issue
Block a user