Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+59
View File
@@ -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()
}
+138
View File
@@ -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.
+211
View File
@@ -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
}