add sound

This commit is contained in:
asrael 2026-01-07 17:45:46 -06:00
parent 99d9c43ea3
commit 01d6e09a91
22 changed files with 1825 additions and 39 deletions

View file

@ -1,5 +1,6 @@
(local pxl8 (require :pxl8))
(local menu (require :mod.menu))
(local music (require :mod.music))
(local worldgen (require :mod.worldgen))
(var time 0)
@ -29,6 +30,8 @@
(pxl8.load_palette "res/sprites/pxl8_logo.ase")
(set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase"))
(set particles (pxl8.particles_new 1000))
(music.init)
(music.start)
(worldgen.init)))
(global update (fn [dt]
@ -79,6 +82,8 @@
(set logo-dy (- logo-dy))))
:worldgen (worldgen.update dt))
(music.update dt)
(when particles
(pxl8.particles_update particles dt)))

68
demo/mod/music.fnl Normal file
View file

@ -0,0 +1,68 @@
(local pxl8 (require :pxl8))
(var time 0)
(var step 0)
(var ctx nil)
(var params nil)
(var bass-params nil)
(var playing false)
(local melody [60 64 67 72 67 64 60 64 67 72 76 72 67 64 62 66 69 74 69 66 62 66 69 74 78 74 69 66])
(local bass [36 50 40 36 38 38 50 38])
(local step-duration 0.15)
(fn init []
(set ctx (pxl8.sfx_context_create))
(local delay (pxl8.sfx_delay_create {:time_l 350 :time_r 500 :feedback 0.4 :mix 0.25}))
(local reverb (pxl8.sfx_reverb_create {:room 0.5 :damping 0.5 :mix 0.3}))
(local compressor (pxl8.sfx_compressor_create {:threshold -12 :ratio 4 :attack 10 :release 100}))
(pxl8.sfx_context_append_node ctx delay)
(pxl8.sfx_context_append_node ctx reverb)
(pxl8.sfx_context_append_node ctx compressor)
(pxl8.sfx_mixer_attach ctx)
(set params (pxl8.sfx_voice_params
{:waveform pxl8.SFX_WAVE_TRIANGLE
:attack 0.005 :decay 0.08 :sustain 0.0 :release 0.05
:filter_type pxl8.SFX_FILTER_LOWPASS
:filter_cutoff 2500 :filter_resonance 0.15
:filter_attack 0.005 :filter_decay 0.1 :filter_sustain 0.1 :filter_release 0.05
:filter_env_depth 1500
:fx_send 0.3}))
(set bass-params (pxl8.sfx_voice_params
{:waveform pxl8.SFX_WAVE_SQUARE
:attack 0.005 :decay 0.1 :sustain 0.0 :release 0.05
:filter_type pxl8.SFX_FILTER_LOWPASS
:filter_cutoff 600 :filter_resonance 0.2
:fx_send 0.2})))
(fn start []
(set playing true)
(set time 0)
(set step 0))
(fn stop []
(set playing false)
(pxl8.sfx_stop_all ctx))
(fn update [dt]
(when playing
(set time (+ time dt))
(when (>= time step-duration)
(set time (- time step-duration))
(local note (. melody (+ 1 (% step (length melody)))))
(pxl8.sfx_play_note ctx note params 0.4)
(when (= (% step 4) 0)
(local bass-note (. bass (+ 1 (% (math.floor (/ step 4)) (length bass)))))
(pxl8.sfx_play_note ctx bass-note bass-params 0.35))
(set step (+ step 1)))))
{:init init
:start start
:stop stop
:update update
:is-playing (fn [] playing)}

Binary file not shown.

Binary file not shown.

View file

@ -347,6 +347,7 @@ case "$COMMAND" in
src/pxl8_save.c
src/pxl8_script.c
src/pxl8_sdl3.c
src/pxl8_sfx.c
src/pxl8_tilemap.c
src/pxl8_tilesheet.c
src/pxl8_transition.c

View file

@ -12,8 +12,9 @@ local gui = require("pxl8.gui")
local world = require("pxl8.world")
local transition = require("pxl8.transition")
local anim = require("pxl8.anim")
local sfx = require("pxl8.sfx")
core.init(_pxl8_gfx, _pxl8_input, _pxl8_sys)
core.init(_pxl8_gfx, _pxl8_input, _pxl8_sfx_mixer, _pxl8_sys)
local pxl8 = {}
@ -189,4 +190,57 @@ pxl8.anim_set_state = anim.set_state
pxl8.anim_stop = anim.stop
pxl8.anim_update = anim.update
pxl8.sfx_compressor_create = sfx.compressor_create
pxl8.sfx_compressor_set_attack = sfx.compressor_set_attack
pxl8.sfx_compressor_set_ratio = sfx.compressor_set_ratio
pxl8.sfx_compressor_set_release = sfx.compressor_set_release
pxl8.sfx_compressor_set_threshold = sfx.compressor_set_threshold
pxl8.sfx_context_append_node = sfx.context_append_node
pxl8.sfx_context_create = sfx.context_create
pxl8.sfx_context_destroy = sfx.context_destroy
pxl8.sfx_context_get_head = sfx.context_get_head
pxl8.sfx_context_get_volume = sfx.context_get_volume
pxl8.sfx_context_insert_node = sfx.context_insert_node
pxl8.sfx_context_remove_node = sfx.context_remove_node
pxl8.sfx_context_set_volume = sfx.context_set_volume
pxl8.sfx_delay_create = sfx.delay_create
pxl8.sfx_delay_set_feedback = sfx.delay_set_feedback
pxl8.sfx_delay_set_mix = sfx.delay_set_mix
pxl8.sfx_delay_set_time = sfx.delay_set_time
pxl8.sfx_get_master_volume = sfx.get_master_volume
pxl8.sfx_mixer_attach = sfx.mixer_attach
pxl8.sfx_mixer_detach = sfx.mixer_detach
pxl8.sfx_node_destroy = sfx.node_destroy
pxl8.sfx_note_to_freq = sfx.note_to_freq
pxl8.sfx_play_note = sfx.play_note
pxl8.sfx_release_voice = sfx.release_voice
pxl8.sfx_reverb_create = sfx.reverb_create
pxl8.sfx_reverb_set_damping = sfx.reverb_set_damping
pxl8.sfx_reverb_set_mix = sfx.reverb_set_mix
pxl8.sfx_reverb_set_room = sfx.reverb_set_room
pxl8.sfx_set_master_volume = sfx.set_master_volume
pxl8.sfx_stop_all = sfx.stop_all
pxl8.sfx_stop_voice = sfx.stop_voice
pxl8.sfx_voice_params = sfx.voice_params
pxl8.SFX_FILTER_BANDPASS = sfx.FILTER_BANDPASS
pxl8.SFX_FILTER_HIGHPASS = sfx.FILTER_HIGHPASS
pxl8.SFX_FILTER_LOWPASS = sfx.FILTER_LOWPASS
pxl8.SFX_FILTER_NONE = sfx.FILTER_NONE
pxl8.SFX_LFO_AMPLITUDE = sfx.LFO_AMPLITUDE
pxl8.SFX_LFO_FILTER = sfx.LFO_FILTER
pxl8.SFX_LFO_PITCH = sfx.LFO_PITCH
pxl8.SFX_NODE_COMPRESSOR = sfx.NODE_COMPRESSOR
pxl8.SFX_NODE_DELAY = sfx.NODE_DELAY
pxl8.SFX_NODE_REVERB = sfx.NODE_REVERB
pxl8.SFX_WAVE_NOISE = sfx.WAVE_NOISE
pxl8.SFX_WAVE_PULSE = sfx.WAVE_PULSE
pxl8.SFX_WAVE_SAW = sfx.WAVE_SAW
pxl8.SFX_WAVE_SINE = sfx.WAVE_SINE
pxl8.SFX_WAVE_SQUARE = sfx.WAVE_SQUARE
pxl8.SFX_WAVE_TRIANGLE = sfx.WAVE_TRIANGLE
return pxl8

View file

@ -3,10 +3,11 @@ local C = ffi.C
local core = {}
function core.init(gfx_ptr, input_ptr, sys_ptr)
core.gfx = gfx_ptr
core.input = input_ptr
core.sys = sys_ptr
function core.init(gfx, input, sfx_mixer, sys)
core.gfx = gfx
core.input = input
core.sfx_mixer = sfx_mixer
core.sys = sys
end
function core.get_fps()

198
src/lua/pxl8/sfx.lua Normal file
View file

@ -0,0 +1,198 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local sfx = {}
sfx.FILTER_BANDPASS = C.PXL8_SFX_FILTER_BANDPASS
sfx.FILTER_HIGHPASS = C.PXL8_SFX_FILTER_HIGHPASS
sfx.FILTER_LOWPASS = C.PXL8_SFX_FILTER_LOWPASS
sfx.FILTER_NONE = C.PXL8_SFX_FILTER_NONE
sfx.LFO_AMPLITUDE = C.PXL8_SFX_LFO_AMPLITUDE
sfx.LFO_FILTER = C.PXL8_SFX_LFO_FILTER
sfx.LFO_PITCH = C.PXL8_SFX_LFO_PITCH
sfx.NODE_COMPRESSOR = C.PXL8_SFX_NODE_COMPRESSOR
sfx.NODE_DELAY = C.PXL8_SFX_NODE_DELAY
sfx.NODE_REVERB = C.PXL8_SFX_NODE_REVERB
sfx.WAVE_NOISE = C.PXL8_SFX_WAVE_NOISE
sfx.WAVE_PULSE = C.PXL8_SFX_WAVE_PULSE
sfx.WAVE_SAW = C.PXL8_SFX_WAVE_SAW
sfx.WAVE_SINE = C.PXL8_SFX_WAVE_SINE
sfx.WAVE_SQUARE = C.PXL8_SFX_WAVE_SQUARE
sfx.WAVE_TRIANGLE = C.PXL8_SFX_WAVE_TRIANGLE
function sfx.compressor_create(opts)
opts = opts or {}
local cfg = ffi.new("pxl8_sfx_compressor_config")
cfg.attack = opts.attack or 10
cfg.ratio = opts.ratio or 4
cfg.release = opts.release or 100
cfg.threshold = opts.threshold or -12
return C.pxl8_sfx_compressor_create(cfg)
end
function sfx.compressor_set_attack(node, attack)
C.pxl8_sfx_compressor_set_attack(node, attack)
end
function sfx.compressor_set_ratio(node, ratio)
C.pxl8_sfx_compressor_set_ratio(node, ratio)
end
function sfx.compressor_set_release(node, release)
C.pxl8_sfx_compressor_set_release(node, release)
end
function sfx.compressor_set_threshold(node, threshold)
C.pxl8_sfx_compressor_set_threshold(node, threshold)
end
function sfx.context_append_node(ctx, node)
C.pxl8_sfx_context_append_node(ctx, node)
end
function sfx.context_create()
return C.pxl8_sfx_context_create()
end
function sfx.context_destroy(ctx)
C.pxl8_sfx_context_destroy(ctx)
end
function sfx.context_get_head(ctx)
return C.pxl8_sfx_context_get_head(ctx)
end
function sfx.context_get_volume(ctx)
return C.pxl8_sfx_context_get_volume(ctx)
end
function sfx.context_insert_node(ctx, after, node)
C.pxl8_sfx_context_insert_node(ctx, after, node)
end
function sfx.context_remove_node(ctx, node)
C.pxl8_sfx_context_remove_node(ctx, node)
end
function sfx.context_set_volume(ctx, volume)
C.pxl8_sfx_context_set_volume(ctx, volume)
end
function sfx.delay_create(opts)
opts = opts or {}
local cfg = ffi.new("pxl8_sfx_delay_config")
cfg.feedback = opts.feedback or 0.4
cfg.mix = opts.mix or 0.25
cfg.time_l = opts.time_l or 350
cfg.time_r = opts.time_r or 500
return C.pxl8_sfx_delay_create(cfg)
end
function sfx.delay_set_feedback(node, feedback)
C.pxl8_sfx_delay_set_feedback(node, feedback)
end
function sfx.delay_set_mix(node, mix)
C.pxl8_sfx_delay_set_mix(node, mix)
end
function sfx.delay_set_time(node, time_l, time_r)
C.pxl8_sfx_delay_set_time(node, time_l, time_r)
end
function sfx.get_master_volume()
return C.pxl8_sfx_mixer_get_master_volume(core.sfx_mixer)
end
function sfx.mixer_attach(ctx)
C.pxl8_sfx_mixer_attach(core.sfx_mixer, ctx)
end
function sfx.mixer_detach(ctx)
C.pxl8_sfx_mixer_detach(core.sfx_mixer, ctx)
end
function sfx.node_destroy(node)
C.pxl8_sfx_node_destroy(node)
end
function sfx.note_to_freq(note)
return C.pxl8_sfx_note_to_freq(note)
end
function sfx.play_note(ctx, note, params, volume)
return C.pxl8_sfx_play_note(ctx, note, params, volume or 0.8)
end
function sfx.release_voice(ctx, voice_id)
C.pxl8_sfx_release_voice(ctx, voice_id)
end
function sfx.reverb_create(opts)
opts = opts or {}
local cfg = ffi.new("pxl8_sfx_reverb_config")
cfg.damping = opts.damping or 0.5
cfg.mix = opts.mix or 0.3
cfg.room = opts.room or 0.5
return C.pxl8_sfx_reverb_create(cfg)
end
function sfx.reverb_set_damping(node, damping)
C.pxl8_sfx_reverb_set_damping(node, damping)
end
function sfx.reverb_set_mix(node, mix)
C.pxl8_sfx_reverb_set_mix(node, mix)
end
function sfx.reverb_set_room(node, room)
C.pxl8_sfx_reverb_set_room(node, room)
end
function sfx.set_master_volume(volume)
C.pxl8_sfx_mixer_set_master_volume(core.sfx_mixer, volume)
end
function sfx.stop_all(ctx)
C.pxl8_sfx_stop_all(ctx)
end
function sfx.stop_voice(ctx, voice_id)
C.pxl8_sfx_stop_voice(ctx, voice_id)
end
function sfx.voice_params(opts)
opts = opts or {}
local params = ffi.new("pxl8_sfx_voice_params")
params.amp_env.attack = opts.attack or 0.01
params.amp_env.decay = opts.decay or 0.1
params.amp_env.sustain = opts.sustain or 0.5
params.amp_env.release = opts.release or 0.2
params.filter_env.attack = opts.filter_attack or 0.01
params.filter_env.decay = opts.filter_decay or 0.1
params.filter_env.sustain = opts.filter_sustain or 0.3
params.filter_env.release = opts.filter_release or 0.1
params.filter_type = opts.filter_type or C.PXL8_SFX_FILTER_NONE
params.lfo_target = opts.lfo_target or C.PXL8_SFX_LFO_PITCH
params.lfo_waveform = opts.lfo_waveform or C.PXL8_SFX_WAVE_SINE
params.waveform = opts.waveform or C.PXL8_SFX_WAVE_SINE
params.filter_cutoff = opts.filter_cutoff or 4000
params.filter_env_depth = opts.filter_env_depth or 0
params.filter_resonance = opts.filter_resonance or 0
params.fx_send = opts.fx_send or 0
params.lfo_depth = opts.lfo_depth or 0
params.lfo_rate = opts.lfo_rate or 0
params.pulse_width = opts.pulse_width or 0.5
return params
end
return sfx

View file

@ -218,12 +218,19 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
return PXL8_ERROR_INITIALIZATION_FAILED;
}
game->mixer = pxl8_sfx_mixer_create();
if (!game->mixer) {
pxl8_error("failed to create audio mixer");
return PXL8_ERROR_INITIALIZATION_FAILED;
}
if (game->repl_mode) {
pxl8_info("starting in REPL mode with script: %s", game->script_path);
}
pxl8_script_set_gfx(game->script, game->gfx);
pxl8_script_set_input(game->script, &game->input);
pxl8_script_set_sfx(game->script, game->mixer);
pxl8_script_set_sys(game->script, sys);
if (game->script_path[0] != '\0') {
@ -364,6 +371,7 @@ void pxl8_quit(pxl8* sys) {
pxl8_cart_unmount(sys->cart);
}
pxl8_sfx_mixer_destroy(game->mixer);
pxl8_gfx_destroy(game->gfx);
pxl8_script_destroy(game->script);
}
@ -394,6 +402,10 @@ pxl8_input_state* pxl8_get_input(const pxl8* sys) {
return (sys && sys->game) ? &sys->game->input : NULL;
}
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys) {
return (sys && sys->game) ? sys->game->mixer : NULL;
}
void pxl8_center_cursor(pxl8* sys) {
if (!sys || !sys->hal || !sys->hal->center_cursor) return;
sys->hal->center_cursor(sys->platform_data);

View file

@ -199,6 +199,22 @@ void pxl8_atlas_destroy(pxl8_atlas* atlas) {
free(atlas);
}
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) {
if (!atlas) return;
for (u32 i = preserve_count; i < atlas->entry_count; i++) {
atlas->entries[i].active = false;
}
atlas->entry_count = preserve_count;
atlas->free_count = 0;
atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)atlas->width};
atlas->skyline.count = 1;
atlas->dirty = true;
}
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
if (!atlas || atlas->width >= 4096) return false;

View file

@ -27,6 +27,7 @@ bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas);
void pxl8_atlas_mark_clean(pxl8_atlas* atlas);
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
#ifdef __cplusplus

View file

@ -43,6 +43,10 @@ static const char embed_pxl8_particles[] = {
#embed "src/lua/pxl8/particles.lua"
, 0 };
static const char embed_pxl8_sfx[] = {
#embed "src/lua/pxl8/sfx.lua"
, 0 };
static const char embed_pxl8_tilemap[] = {
#embed "src/lua/pxl8/tilemap.lua"
, 0 };
@ -74,6 +78,7 @@ static const pxl8_embed pxl8_embeds[] = {
PXL8_EMBED_ENTRY(embed_pxl8_input, "pxl8.input"),
PXL8_EMBED_ENTRY(embed_pxl8_math, "pxl8.math"),
PXL8_EMBED_ENTRY(embed_pxl8_particles, "pxl8.particles"),
PXL8_EMBED_ENTRY(embed_pxl8_sfx, "pxl8.sfx"),
PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"),
PXL8_EMBED_ENTRY(embed_pxl8_transition, "pxl8.transition"),
PXL8_EMBED_ENTRY(embed_pxl8_vfx, "pxl8.vfx"),

View file

@ -2,11 +2,13 @@
#include "pxl8_gfx.h"
#include "pxl8_script.h"
#include "pxl8_sfx.h"
#include "pxl8_types.h"
typedef struct pxl8_game {
pxl8_gfx* gfx;
pxl8_script* script;
pxl8_sfx_mixer* mixer;
i32 frame_count;
u64 last_time;

View file

@ -197,6 +197,18 @@ static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) {
return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY;
}
void pxl8_gfx_clear_textures(pxl8_gfx* gfx) {
if (!gfx) return;
if (gfx->atlas) {
pxl8_atlas_clear(gfx->atlas, 0);
}
if (gfx->sprite_cache) {
gfx->sprite_cache_count = 0;
}
}
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height) {
if (!gfx || !gfx->initialized || !pixels) return PXL8_ERROR_INVALID_ARGUMENT;

View file

@ -25,36 +25,37 @@ extern "C" {
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution);
void pxl8_gfx_destroy(pxl8_gfx* gfx);
void pxl8_gfx_present(pxl8_gfx* gfx);
void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt);
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx);
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
void pxl8_gfx_clear_textures(pxl8_gfx* gfx);
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height);
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx);
pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path);
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
void pxl8_gfx_present(pxl8_gfx* gfx);
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* gfx, u8 start_index, u8 end_index, f32 speed);
void pxl8_gfx_clear_palette_cycles(pxl8_gfx* gfx);
void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32 to_color);
void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step);
void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 target_color);
void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t);
void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors);
i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* gfx, u8 start_index, u8 end_index, f32 speed);
void pxl8_gfx_remove_palette_cycle(pxl8_gfx* gfx, i32 cycle_id);
void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* gfx, i32 cycle_id, f32 speed);
void pxl8_gfx_clear_palette_cycles(pxl8_gfx* gfx);
void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt);
void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors);
void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color);
void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color);
@ -68,6 +69,10 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h);
void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color);
void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx);
void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color);
void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri);
void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color);
void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0f, f32 u1, f32 v1f, f32 u2, f32 v2f, u32 texture_id);
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx);
void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine);
void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling);
@ -75,10 +80,6 @@ void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat);
void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat);
void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat);
void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe);
void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color);
void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri);
void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color);
void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0f, f32 u1, f32 v1f, f32 u2, f32 v2f, u32 texture_id);
#ifdef __cplusplus
}

View file

@ -4,6 +4,9 @@
#include "pxl8_types.h"
#define PXL8_PI 3.14159265358979323846f
#define PXL8_TAU (PXL8_PI * 2.0f)
typedef struct pxl8_vec2 {
f32 x, y;
} pxl8_vec2;

View file

@ -22,6 +22,7 @@ struct pxl8_script {
lua_State* L;
pxl8_gfx* gfx;
pxl8_input_state* input;
pxl8_sfx_mixer* mixer;
char last_error[PXL8_MAX_ERROR_SIZE];
char main_path[PXL8_MAX_PATH];
char watch_dir[PXL8_MAX_PATH];
@ -420,7 +421,53 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_save_free(u8* data);\n"
"bool pxl8_save_exists(pxl8_save* save, u8 slot);\n"
"i32 pxl8_save_delete(pxl8_save* save, u8 slot);\n"
"const char* pxl8_save_get_directory(pxl8_save* save);\n";
"const char* pxl8_save_get_directory(pxl8_save* save);\n"
"\n"
"typedef struct pxl8_sfx_context pxl8_sfx_context;\n"
"typedef struct pxl8_sfx_mixer pxl8_sfx_mixer;\n"
"typedef struct pxl8_sfx_node pxl8_sfx_node;\n"
"typedef enum pxl8_sfx_filter_type { PXL8_SFX_FILTER_BANDPASS = 0, PXL8_SFX_FILTER_HIGHPASS, PXL8_SFX_FILTER_LOWPASS, PXL8_SFX_FILTER_NONE } pxl8_sfx_filter_type;\n"
"typedef enum pxl8_sfx_lfo_target { PXL8_SFX_LFO_AMPLITUDE = 0, PXL8_SFX_LFO_FILTER, PXL8_SFX_LFO_PITCH } pxl8_sfx_lfo_target;\n"
"typedef enum pxl8_sfx_node_type { PXL8_SFX_NODE_COMPRESSOR, PXL8_SFX_NODE_DELAY, PXL8_SFX_NODE_REVERB } pxl8_sfx_node_type;\n"
"typedef enum pxl8_sfx_waveform { PXL8_SFX_WAVE_NOISE = 0, PXL8_SFX_WAVE_PULSE, PXL8_SFX_WAVE_SAW, PXL8_SFX_WAVE_SINE, PXL8_SFX_WAVE_SQUARE, PXL8_SFX_WAVE_TRIANGLE } pxl8_sfx_waveform;\n"
"typedef struct pxl8_sfx_adsr { f32 attack; f32 decay; f32 sustain; f32 release; } pxl8_sfx_adsr;\n"
"typedef struct pxl8_sfx_compressor_config { f32 attack; f32 ratio; f32 release; f32 threshold; } pxl8_sfx_compressor_config;\n"
"typedef struct pxl8_sfx_delay_config { f32 feedback; f32 mix; u32 time_l; u32 time_r; } pxl8_sfx_delay_config;\n"
"typedef struct pxl8_sfx_reverb_config { f32 damping; f32 mix; f32 room; } pxl8_sfx_reverb_config;\n"
"typedef struct pxl8_sfx_voice_params { pxl8_sfx_adsr amp_env; pxl8_sfx_adsr filter_env; pxl8_sfx_filter_type filter_type; pxl8_sfx_lfo_target lfo_target; pxl8_sfx_waveform lfo_waveform; pxl8_sfx_waveform waveform; f32 filter_cutoff; f32 filter_env_depth; f32 filter_resonance; f32 fx_send; f32 lfo_depth; f32 lfo_rate; f32 pulse_width; } pxl8_sfx_voice_params;\n"
"pxl8_sfx_node* pxl8_sfx_compressor_create(pxl8_sfx_compressor_config cfg);\n"
"void pxl8_sfx_compressor_set_attack(pxl8_sfx_node* node, f32 attack);\n"
"void pxl8_sfx_compressor_set_ratio(pxl8_sfx_node* node, f32 ratio);\n"
"void pxl8_sfx_compressor_set_release(pxl8_sfx_node* node, f32 release);\n"
"void pxl8_sfx_compressor_set_threshold(pxl8_sfx_node* node, f32 threshold);\n"
"void pxl8_sfx_context_append_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);\n"
"pxl8_sfx_context* pxl8_sfx_context_create(void);\n"
"void pxl8_sfx_context_destroy(pxl8_sfx_context* ctx);\n"
"pxl8_sfx_node* pxl8_sfx_context_get_head(pxl8_sfx_context* ctx);\n"
"f32 pxl8_sfx_context_get_volume(const pxl8_sfx_context* ctx);\n"
"void pxl8_sfx_context_insert_node(pxl8_sfx_context* ctx, pxl8_sfx_node* after, pxl8_sfx_node* node);\n"
"void pxl8_sfx_context_remove_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);\n"
"void pxl8_sfx_context_set_volume(pxl8_sfx_context* ctx, f32 volume);\n"
"pxl8_sfx_node* pxl8_sfx_delay_create(pxl8_sfx_delay_config cfg);\n"
"void pxl8_sfx_delay_set_feedback(pxl8_sfx_node* node, f32 feedback);\n"
"void pxl8_sfx_delay_set_mix(pxl8_sfx_node* node, f32 mix);\n"
"void pxl8_sfx_delay_set_time(pxl8_sfx_node* node, u32 time_l, u32 time_r);\n"
"void pxl8_sfx_mixer_attach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);\n"
"pxl8_sfx_mixer* pxl8_sfx_mixer_create(void);\n"
"void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer);\n"
"void pxl8_sfx_mixer_detach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);\n"
"f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer);\n"
"void pxl8_sfx_mixer_set_master_volume(pxl8_sfx_mixer* mixer, f32 volume);\n"
"void pxl8_sfx_node_destroy(pxl8_sfx_node* node);\n"
"f32 pxl8_sfx_note_to_freq(u8 note);\n"
"u16 pxl8_sfx_play_note(pxl8_sfx_context* ctx, u8 note, const pxl8_sfx_voice_params* params, f32 volume);\n"
"void pxl8_sfx_release_voice(pxl8_sfx_context* ctx, u16 voice_id);\n"
"pxl8_sfx_node* pxl8_sfx_reverb_create(pxl8_sfx_reverb_config cfg);\n"
"void pxl8_sfx_reverb_set_damping(pxl8_sfx_node* node, f32 damping);\n"
"void pxl8_sfx_reverb_set_mix(pxl8_sfx_node* node, f32 mix);\n"
"void pxl8_sfx_reverb_set_room(pxl8_sfx_node* node, f32 room);\n"
"void pxl8_sfx_stop_all(pxl8_sfx_context* ctx);\n"
"void pxl8_sfx_stop_voice(pxl8_sfx_context* ctx, u16 voice_id);\n";
void pxl8_lua_log(int level, const char* file, int line, const char* msg) {
if (file && (file[0] == '?' || file[0] == '\0')) file = NULL;
@ -637,6 +684,14 @@ void pxl8_script_set_input(pxl8_script* script, pxl8_input_state* input) {
}
}
void pxl8_script_set_sfx(pxl8_script* script, pxl8_sfx_mixer* mixer) {
if (!script) return;
script->mixer = mixer;
if (script->L && mixer) {
lua_pushlightuserdata(script->L, mixer);
lua_setglobal(script->L, "_pxl8_sfx_mixer");
}
}
void pxl8_script_set_sys(pxl8_script* script, void* sys) {
if (!script) return;
@ -920,6 +975,90 @@ pxl8_result pxl8_script_call_function_f32(pxl8_script* script, const char* name,
return PXL8_OK;
}
static bool pxl8_script_is_builtin_global(const char* name) {
static const char* builtins[] = {
"_G", "_VERSION", "arg", "assert", "bit", "collectgarbage",
"coroutine", "debug", "dofile", "error", "fennel", "ffi",
"gcinfo", "getfenv", "getmetatable", "init", "io", "ipairs",
"jit", "load", "loadfile", "loadstring", "math", "module",
"newproxy", "next", "os", "package", "pairs", "pcall",
"print", "pxl8", "rawequal", "rawget", "rawset", "require",
"select", "setfenv", "setmetatable", "string", "table",
"tonumber", "tostring", "type", "unpack", "update", "frame",
"xpcall", "_pxl8_gfx", "_pxl8_input", "_pxl8_sfx_mixer", "_pxl8_sys",
NULL
};
for (int i = 0; builtins[i]; i++) {
if (strcmp(name, builtins[i]) == 0) return true;
}
return name[0] == '_' && name[1] == '_';
}
static void pxl8_script_cleanup(pxl8_script* script) {
if (script->gfx) {
pxl8_gfx_clear_textures(script->gfx);
}
if (script->mixer) {
pxl8_sfx_mixer_clear(script->mixer);
}
}
static void pxl8_script_save_globals(pxl8_script* script) {
lua_State* L = script->L;
lua_newtable(L);
int saved_table = lua_gettop(L);
lua_pushvalue(L, LUA_GLOBALSINDEX);
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
if (lua_type(L, -2) == LUA_TSTRING) {
const char* name = lua_tostring(L, -2);
int vtype = lua_type(L, -1);
if (!pxl8_script_is_builtin_global(name) &&
vtype != LUA_TFUNCTION &&
vtype != LUA_TUSERDATA &&
vtype != LUA_TLIGHTUSERDATA &&
vtype != LUA_TTHREAD &&
vtype != 10) {
lua_pushvalue(L, -2);
lua_pushvalue(L, -2);
lua_settable(L, saved_table);
}
}
lua_pop(L, 1);
}
lua_pop(L, 1);
lua_setfield(L, LUA_REGISTRYINDEX, "_pxl8_hotreload_state");
pxl8_debug("Hot reload state saved");
}
static void pxl8_script_restore_globals(pxl8_script* script) {
lua_State* L = script->L;
lua_getfield(L, LUA_REGISTRYINDEX, "_pxl8_hotreload_state");
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return;
}
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
lua_pushvalue(L, -2);
lua_pushvalue(L, -2);
lua_settable(L, LUA_GLOBALSINDEX);
lua_pop(L, 1);
}
lua_pop(L, 1);
lua_pushnil(L);
lua_setfield(L, LUA_REGISTRYINDEX, "_pxl8_hotreload_state");
pxl8_debug("Hot reload state restored");
}
static time_t get_file_mod_time(const char* path) {
struct stat file_stat;
if (stat(path, &file_stat) == 0) {
@ -972,15 +1111,8 @@ pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) {
pxl8_strncpy(script->main_path, path, sizeof(script->main_path));
char* last_slash = strrchr(script->main_path, '/');
if (last_slash) {
size_t dir_len = last_slash - script->main_path;
strncpy(script->watch_dir, script->main_path, dir_len);
script->watch_dir[dir_len] = '\0';
} else {
script->watch_dir[0] = '.';
script->watch_dir[1] = '\0';
}
script->watch_dir[0] = '.';
script->watch_dir[1] = '\0';
script->latest_mod_time = get_latest_script_mod_time(script->watch_dir);
@ -1005,26 +1137,72 @@ pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) {
return result;
}
static void pxl8_script_clear_cart_modules(pxl8_script* script) {
lua_State* L = script->L;
lua_getglobal(L, "package");
lua_getfield(L, -1, "loaded");
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
lua_pop(L, 1);
const char* name = lua_tostring(L, -1);
if (name && strncmp(name, "pxl8", 4) != 0 &&
strcmp(name, "fennel") != 0 &&
strcmp(name, "ffi") != 0 &&
strcmp(name, "bit") != 0 &&
strcmp(name, "jit") != 0 &&
strcmp(name, "string") != 0 &&
strcmp(name, "table") != 0 &&
strcmp(name, "math") != 0 &&
strcmp(name, "io") != 0 &&
strcmp(name, "os") != 0 &&
strcmp(name, "debug") != 0 &&
strcmp(name, "coroutine") != 0 &&
strcmp(name, "package") != 0) {
lua_pushvalue(L, -1);
lua_pushnil(L);
lua_settable(L, -4);
}
}
lua_pop(L, 2);
}
bool pxl8_script_check_reload(pxl8_script* script) {
if (!script || script->main_path[0] == '\0') return false;
if (!script || script->main_path[0] == '\0') {
return false;
}
time_t current_mod_time = get_latest_script_mod_time(script->watch_dir);
if (current_mod_time > script->latest_mod_time && current_mod_time != 0) {
pxl8_info("Script files modified, reloading: %s", script->main_path);
script->latest_mod_time = current_mod_time;
pxl8_script_cleanup(script);
pxl8_script_save_globals(script);
pxl8_script_clear_cart_modules(script);
const char* ext = strrchr(script->main_path, '.');
bool reloaded = false;
if (ext && strcmp(ext, ".fnl") == 0) {
if (pxl8_script_run_fennel_file(script, script->main_path) == PXL8_OK) {
pxl8_script_call_function(script, "init");
return true;
reloaded = true;
}
} else if (ext && strcmp(ext, ".lua") == 0) {
if (pxl8_script_run_file(script, script->main_path) == PXL8_OK) {
pxl8_script_call_function(script, "init");
return true;
reloaded = true;
}
}
if (reloaded) {
pxl8_script_restore_globals(script);
}
return reloaded;
}
return false;

View file

@ -2,6 +2,7 @@
#include "pxl8_cart.h"
#include "pxl8_gfx.h"
#include "pxl8_sfx.h"
#include "pxl8_types.h"
typedef struct pxl8_script pxl8_script;
@ -19,6 +20,7 @@ bool pxl8_script_is_incomplete_input(pxl8_script* script);
void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd);
void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx);
void pxl8_script_set_input(pxl8_script* script, pxl8_input_state* input);
void pxl8_script_set_sfx(pxl8_script* script, pxl8_sfx_mixer* mixer);
void pxl8_script_set_sys(pxl8_script* script, void* sys);
pxl8_result pxl8_script_call_function(pxl8_script* script, const char* name);

View file

@ -126,7 +126,7 @@ static void sdl3_upload_texture(void* platform_data, const u8* pixels, u32 w, u3
}
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_AUDIO)) {
pxl8_error("SDL_Init failed: %s", SDL_GetError());
return SDL_APP_FAILURE;
}

1092
src/pxl8_sfx.c Normal file

File diff suppressed because it is too large Load diff

133
src/pxl8_sfx.h Normal file
View file

@ -0,0 +1,133 @@
#pragma once
#include "pxl8_types.h"
#define PXL8_SFX_BUFFER_SIZE 1024
#define PXL8_SFX_MAX_CONTEXTS 8
#define PXL8_SFX_MAX_DELAY_SAMPLES 44100
#define PXL8_SFX_MAX_VOICES 16
#define PXL8_SFX_SAMPLE_RATE 44100
typedef struct pxl8_sfx_context pxl8_sfx_context;
typedef struct pxl8_sfx_mixer pxl8_sfx_mixer;
typedef struct pxl8_sfx_node pxl8_sfx_node;
typedef enum pxl8_sfx_filter_type {
PXL8_SFX_FILTER_BANDPASS = 0,
PXL8_SFX_FILTER_HIGHPASS,
PXL8_SFX_FILTER_LOWPASS,
PXL8_SFX_FILTER_NONE
} pxl8_sfx_filter_type;
typedef enum pxl8_sfx_lfo_target {
PXL8_SFX_LFO_AMPLITUDE = 0,
PXL8_SFX_LFO_FILTER,
PXL8_SFX_LFO_PITCH
} pxl8_sfx_lfo_target;
typedef enum pxl8_sfx_node_type {
PXL8_SFX_NODE_COMPRESSOR,
PXL8_SFX_NODE_DELAY,
PXL8_SFX_NODE_REVERB
} pxl8_sfx_node_type;
typedef enum pxl8_sfx_waveform {
PXL8_SFX_WAVE_NOISE = 0,
PXL8_SFX_WAVE_PULSE,
PXL8_SFX_WAVE_SAW,
PXL8_SFX_WAVE_SINE,
PXL8_SFX_WAVE_SQUARE,
PXL8_SFX_WAVE_TRIANGLE
} pxl8_sfx_waveform;
typedef struct pxl8_sfx_adsr {
f32 attack;
f32 decay;
f32 sustain;
f32 release;
} pxl8_sfx_adsr;
typedef struct pxl8_sfx_compressor_config {
f32 attack;
f32 ratio;
f32 release;
f32 threshold;
} pxl8_sfx_compressor_config;
typedef struct pxl8_sfx_delay_config {
f32 feedback;
f32 mix;
u32 time_l;
u32 time_r;
} pxl8_sfx_delay_config;
typedef struct pxl8_sfx_reverb_config {
f32 damping;
f32 mix;
f32 room;
} pxl8_sfx_reverb_config;
typedef struct pxl8_sfx_voice_params {
pxl8_sfx_adsr amp_env;
pxl8_sfx_adsr filter_env;
pxl8_sfx_filter_type filter_type;
pxl8_sfx_lfo_target lfo_target;
pxl8_sfx_waveform lfo_waveform;
pxl8_sfx_waveform waveform;
f32 filter_cutoff;
f32 filter_env_depth;
f32 filter_resonance;
f32 fx_send;
f32 lfo_depth;
f32 lfo_rate;
f32 pulse_width;
} pxl8_sfx_voice_params;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_sfx_node* pxl8_sfx_compressor_create(pxl8_sfx_compressor_config cfg);
void pxl8_sfx_compressor_set_attack(pxl8_sfx_node* node, f32 attack);
void pxl8_sfx_compressor_set_ratio(pxl8_sfx_node* node, f32 ratio);
void pxl8_sfx_compressor_set_release(pxl8_sfx_node* node, f32 release);
void pxl8_sfx_compressor_set_threshold(pxl8_sfx_node* node, f32 threshold);
void pxl8_sfx_context_append_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);
pxl8_sfx_context* pxl8_sfx_context_create(void);
void pxl8_sfx_context_destroy(pxl8_sfx_context* ctx);
pxl8_sfx_node* pxl8_sfx_context_get_head(pxl8_sfx_context* ctx);
f32 pxl8_sfx_context_get_volume(const pxl8_sfx_context* ctx);
void pxl8_sfx_context_insert_node(pxl8_sfx_context* ctx, pxl8_sfx_node* after, pxl8_sfx_node* node);
void pxl8_sfx_context_remove_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);
void pxl8_sfx_context_set_volume(pxl8_sfx_context* ctx, f32 volume);
pxl8_sfx_node* pxl8_sfx_delay_create(pxl8_sfx_delay_config cfg);
void pxl8_sfx_delay_set_feedback(pxl8_sfx_node* node, f32 feedback);
void pxl8_sfx_delay_set_mix(pxl8_sfx_node* node, f32 mix);
void pxl8_sfx_delay_set_time(pxl8_sfx_node* node, u32 time_l, u32 time_r);
void pxl8_sfx_mixer_attach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);
void pxl8_sfx_mixer_clear(pxl8_sfx_mixer* mixer);
pxl8_sfx_mixer* pxl8_sfx_mixer_create(void);
void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer);
void pxl8_sfx_mixer_detach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);
f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer);
void pxl8_sfx_mixer_set_master_volume(pxl8_sfx_mixer* mixer, f32 volume);
void pxl8_sfx_node_destroy(pxl8_sfx_node* node);
f32 pxl8_sfx_note_to_freq(u8 note);
u16 pxl8_sfx_play_note(pxl8_sfx_context* ctx, u8 note, const pxl8_sfx_voice_params* params, f32 volume);
void pxl8_sfx_release_voice(pxl8_sfx_context* ctx, u16 voice_id);
pxl8_sfx_node* pxl8_sfx_reverb_create(pxl8_sfx_reverb_config cfg);
void pxl8_sfx_reverb_set_damping(pxl8_sfx_node* node, f32 damping);
void pxl8_sfx_reverb_set_mix(pxl8_sfx_node* node, f32 mix);
void pxl8_sfx_reverb_set_room(pxl8_sfx_node* node, f32 room);
void pxl8_sfx_stop_all(pxl8_sfx_context* ctx);
void pxl8_sfx_stop_voice(pxl8_sfx_context* ctx, u16 voice_id);
#ifdef __cplusplus
}
#endif

View file

@ -3,6 +3,7 @@
#include "pxl8_gfx.h"
#include "pxl8_hal.h"
#include "pxl8_io.h"
#include "pxl8_sfx.h"
#include "pxl8_types.h"
typedef struct pxl8 pxl8;
@ -14,22 +15,23 @@ extern "C" {
pxl8* pxl8_create(const pxl8_hal* hal);
void pxl8_destroy(pxl8* sys);
pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]);
pxl8_result pxl8_update(pxl8* sys);
pxl8_result pxl8_frame(pxl8* sys);
void pxl8_quit(pxl8* sys);
f32 pxl8_get_fps(const pxl8* sys);
pxl8_gfx* pxl8_get_gfx(const pxl8* sys);
pxl8_input_state* pxl8_get_input(const pxl8* sys);
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution);
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys);
bool pxl8_is_running(const pxl8* sys);
void pxl8_center_cursor(pxl8* sys);
void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor);
void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled);
void pxl8_set_running(pxl8* sys, bool running);
void pxl8_center_cursor(pxl8* sys);
pxl8_result pxl8_frame(pxl8* sys);
pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]);
void pxl8_quit(pxl8* sys);
pxl8_result pxl8_update(pxl8* sys);
#ifdef __cplusplus
}
#endif