Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
316 changes: 171 additions & 145 deletions examples/audio/hrtf/main.das
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
options gen2
options persistent_heap
options gc
options no_global_variables = false
options no_unused_block_arguments = false
options no_unused_function_arguments = false
Expand All @@ -8,7 +9,7 @@ require glfw/glfw_boost
require opengl/opengl_boost
require opengl/opengl_gen
require audio/audio_boost
require daslib/defer
require live/live_gc
require daslib/fio
require daslib/safe_addr
require daslib/math_boost
Expand Down Expand Up @@ -183,6 +184,18 @@ def add_sound_source(pos : float3; volume : float = 1.0) {

// -- Main --

var window : GLFWwindow?
var solid_program : uint
var line_program : uint
var sphere : OpenGLGeometryFragment
var stats_box : LockBox?
var asch : AudioSystemChannels
var last_time = 0.0lf
var n_was_pressed = false
var m_was_pressed = false
var b_was_pressed = false
var since_stats_print = 0.0

// Parse --max-frames N from argv. Useful for headless repro and shutdown-leak debugging.
def parse_max_frames {
var maxFrames = 0
Expand All @@ -196,31 +209,24 @@ def parse_max_frames {
}

[export]
def main {
let maxFrames = parse_max_frames()
def init {
// GLFW init
if (glfwInit() == 0) {
panic("can't init glfw")
}
defer() {
glfwTerminate()
}
glfwInitOpenGL(3, 3)
var window = glfwCreateWindow(1280, 720, "HRTF 3D Audio Demo", null, null)
window = glfwCreateWindow(1280, 720, "HRTF 3D Audio Demo", null, null)
if (window == null) {
panic("can't create window")
}
defer() {
glfwDestroyWindow(window)
}
glfwMakeContextCurrent(window)

// Shaders
let solid_program = create_shader_program(@@vs_solid, @@fs_solid)
let line_program = create_shader_program(@@vs_line, @@fs_line)
solid_program = create_shader_program(@@vs_solid, @@fs_solid)
line_program = create_shader_program(@@vs_line, @@fs_line)

// Geometry
let sphere <- gen_sphere(16, 12, false) |> create_geometry_fragment
sphere <- gen_sphere(16, 12, false) |> create_geometry_fragment
create_grid(20, 1.0)

// Mouse callbacks
Expand Down Expand Up @@ -259,145 +265,165 @@ def main {

// Stats LockBox for per-second utilization + HRTF/simulated split readout.
// Lifecycle wraps the audio system: the audio thread releases its share-ref during
// audio_system_finalize (inside with_audio_system below), THEN this outer defer
// calls lock_box_remove to do the final delete. `release` alone never deletes.
var stats_box <- lock_box_create()
defer() {
unsafe(lock_box_remove(stats_box))
// audio_system_finalize, THEN shutdown calls lock_box_remove to do the final delete.
// `release` alone never deletes.
stats_box = lock_box_create()

// Audio system — all audio API calls happen between here and audio_system_finalize in shutdown
asch = audio_system_create()

// Start with one source in front
add_sound_source(float3(0.0, 0.0, -3.0))

set_audio_stats_box(stats_box)

print("HRTF 3D Audio Demo\n")
print(" Click to capture mouse, click again to release\n")
print(" WASD to move, mouse to look\n")
print(" N to add a sound source, M to add 30, ESC to quit\n")
print(" B to cycle HRTF budget (mixed top-32 / all simulated / all HRTF)\n")

last_time = glfwGetTime()
}

[export]
def update { // nolint:STYLE038 — linear per-frame UI flow
glfwPollEvents()

// Delta time
let now = glfwGetTime()
let dt = float(now - last_time)
last_time = now

// Movement
let speed = 5.0 * dt
let fwd = camera_forward()
let rgt = camera_right()
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
camera_pos += fwd * speed
}
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
camera_pos -= fwd * speed
}
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
camera_pos -= rgt * speed
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
camera_pos += rgt * speed
}

// Audio system
with_audio_system() {
// Start with one source in front
add_sound_source(float3(0.0, 0.0, -3.0))

set_audio_stats_box(stats_box)

print("HRTF 3D Audio Demo\n")
print(" Click to capture mouse, click again to release\n")
print(" WASD to move, mouse to look\n")
print(" N to add a sound source, M to add 30, ESC to quit\n")
print(" B to cycle HRTF budget (mixed top-32 / all simulated / all HRTF)\n")

var last_time = glfwGetTime()
var n_was_pressed = false
var m_was_pressed = false
var b_was_pressed = false
var since_stats_print = 0.0
var frameCount = 0

eval_main_loop() {
if (glfwWindowShouldClose(window) != 0 || glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS || (maxFrames > 0 && frameCount >= maxFrames)) return false
frameCount ++
glfwPollEvents()

// Delta time
let now = glfwGetTime()
let dt = float(now - last_time)
last_time = now

// Movement
let speed = 5.0 * dt
let fwd = camera_forward()
let rgt = camera_right()
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
camera_pos += fwd * speed
}
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
camera_pos -= fwd * speed
}
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
camera_pos -= rgt * speed
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
camera_pos += rgt * speed
}
// Add source on N press
let n_pressed = glfwGetKey(window, GLFW_KEY_N) == GLFW_PRESS
if (n_pressed && !n_was_pressed) {
let spawn_pos = camera_pos + fwd * 3.0
add_sound_source(spawn_pos)
print("Added source #{length(sources)} at ({spawn_pos.x}, {spawn_pos.y}, {spawn_pos.z})\n")
}
n_was_pressed = n_pressed

// Add 30 sources around camera on M press
let m_pressed = glfwGetKey(window, GLFW_KEY_M) == GLFW_PRESS
if (m_pressed && !m_was_pressed) {
for (i in range(30)) {
let angle = 2.0 * PI * float(i) / 30.0
let radius = 3.0 + float(i % 3)
let spawn_pos = camera_pos + float3(cos(angle) * radius, 0.0, sin(angle) * radius)
add_sound_source(spawn_pos, 0.1)
}
print("Added 30 sources around camera\n")
}
m_was_pressed = m_pressed

// Cycle HRTF budget on B press
let b_pressed = glfwGetKey(window, GLFW_KEY_B) == GLFW_PRESS
if (b_pressed && !b_was_pressed) {
hrtf_budget_idx = (hrtf_budget_idx + 1) % 3
let newBudget = HRTF_BUDGETS[hrtf_budget_idx]
set_hrtf_budget(newBudget)
print("HRTF budget: {newBudget} ({HRTF_BUDGET_LABELS[hrtf_budget_idx]})\n")
}
b_was_pressed = b_pressed

// Per-second audio-system stats line. `get` is read-only — keeps the box alive across reads.
// `grab` would consume the notification and break the periodic publish/poll loop.
since_stats_print += dt
if (since_stats_print >= 1.0) {
stats_box |> get() $(s : AudioSystemStats#) {
print("audio: util={s.utilization_pct}%, hrtf={s.hrtf_count}/{s.total_3d} (budget={HRTF_BUDGETS[hrtf_budget_idx]})\n")
}
since_stats_print = 0.0
}

// Add source on N press
let n_pressed = glfwGetKey(window, GLFW_KEY_N) == GLFW_PRESS
if (n_pressed && !n_was_pressed) {
let spawn_pos = camera_pos + fwd * 3.0
add_sound_source(spawn_pos)
print("Added source #{length(sources)} at ({spawn_pos.x}, {spawn_pos.y}, {spawn_pos.z})\n")
}
n_was_pressed = n_pressed

// Add 30 sources around camera on M press
let m_pressed = glfwGetKey(window, GLFW_KEY_M) == GLFW_PRESS
if (m_pressed && !m_was_pressed) {
for (i in range(30)) {
let angle = 2.0 * PI * float(i) / 30.0
let radius = 3.0 + float(i % 3)
let spawn_pos = camera_pos + float3(cos(angle) * radius, 0.0, sin(angle) * radius)
add_sound_source(spawn_pos, 0.1)
}
print("Added 30 sources around camera\n")
}
m_was_pressed = m_pressed

// Cycle HRTF budget on B press
let b_pressed = glfwGetKey(window, GLFW_KEY_B) == GLFW_PRESS
if (b_pressed && !b_was_pressed) {
hrtf_budget_idx = (hrtf_budget_idx + 1) % 3
let newBudget = HRTF_BUDGETS[hrtf_budget_idx]
set_hrtf_budget(newBudget)
print("HRTF budget: {newBudget} ({HRTF_BUDGET_LABELS[hrtf_budget_idx]})\n")
}
b_was_pressed = b_pressed

// Per-second audio-system stats line. `get` is read-only — keeps the box alive across reads.
// `grab` would consume the notification and break the periodic publish/poll loop.
since_stats_print += dt
if (since_stats_print >= 1.0) {
stats_box |> get() $(s : AudioSystemStats#) {
print("audio: util={s.utilization_pct}%, hrtf={s.hrtf_count}/{s.total_3d} (budget={HRTF_BUDGETS[hrtf_budget_idx]})\n")
}
since_stats_print = 0.0
}
// Update audio listener
set_head_position(gl_to_audio(camera_pos), gl_to_audio(fwd))

// Update audio listener
set_head_position(gl_to_audio(camera_pos), gl_to_audio(fwd))
// Update source positions (they don't move, but we could animate here)
for (src in sources) {
src.sid |> set_position(gl_to_audio(src.position))
}

// Update source positions (they don't move, but we could animate here)
for (src in sources) {
src.sid |> set_position(gl_to_audio(src.position))
}
// -- Render --
var display_w, display_h : int
glfwGetFramebufferSize(window, display_w, display_h)
let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1.0

glViewport(0, 0, display_w, display_h)
glClearColor(0.1, 0.1, 0.15, 1.0)
glClearDepth(1.0lf)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)

let view = look_at_rh(camera_pos, camera_pos + fwd, float3(0.0, 1.0, 0.0))
let proj = perspective_rh_opengl(60.0 * PI / 180.0, aspect, 0.1, 100.0)
v_view = view
v_projection = proj

// Grid
draw_grid(line_program, view, proj)

// Sound source spheres
glUseProgram(solid_program)
glEnable(GL_CULL_FACE)
glCullFace(GL_BACK)
for (src in sources) {
v_model = compose(src.position, float4(0.0, 0.0, 0.0, 1.0), float3(0.3))
f_color = src.color
vs_solid_bind_uniform(solid_program)
fs_solid_bind_uniform(solid_program)
draw_geometry_fragment(sphere)
}
glUseProgram(0u)

// -- Render --
var display_w, display_h : int
glfwGetFramebufferSize(window, display_w, display_h)
let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1.0

glViewport(0, 0, display_w, display_h)
glClearColor(0.1, 0.1, 0.15, 1.0)
glClearDepth(1.0lf)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)

let view = look_at_rh(camera_pos, camera_pos + fwd, float3(0.0, 1.0, 0.0))
let proj = perspective_rh_opengl(60.0 * PI / 180.0, aspect, 0.1, 100.0)
v_view = view
v_projection = proj

// Grid
draw_grid(line_program, view, proj)

// Sound source spheres
glUseProgram(solid_program)
glEnable(GL_CULL_FACE)
glCullFace(GL_BACK)
for (src in sources) {
v_model = compose(src.position, float4(0.0, 0.0, 0.0, 1.0), float3(0.3))
f_color = src.color
vs_solid_bind_uniform(solid_program)
fs_solid_bind_uniform(solid_program)
draw_geometry_fragment(sphere)
}
glUseProgram(0u)
glfwSwapBuffers(window)
}

[export]
def shutdown {
audio_system_finalize(asch.command, asch.next_sid)
if (stats_box != null) {
unsafe(lock_box_remove(stats_box))
stats_box = null
}
if (window != null) {
glfwDestroyWindow(window)
window = null
}
glfwTerminate()
}
Comment thread
Copilot marked this conversation as resolved.

glfwSwapBuffers(window)
return true
[export]
def main {
let maxFrames = parse_max_frames()
init()
var frameCount = 0
while (glfwWindowShouldClose(window) == 0 && glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS) {
update()
maybe_collect_gc()
frameCount ++
if (maxFrames > 0 && frameCount >= maxFrames) {
break
}
}
shutdown()
}
Loading
Loading