improve script hot reload

This commit is contained in:
asrael 2026-01-08 01:19:25 -06:00
parent 01d6e09a91
commit 15041984f1
25 changed files with 1516 additions and 293 deletions

View file

@ -3,8 +3,7 @@
#include <stdlib.h>
#include <string.h>
#include <SDL3/SDL.h>
#include "pxl8_hal.h"
#include "pxl8_log.h"
#include "pxl8_math.h"
@ -62,6 +61,8 @@ typedef struct voice {
f32 filter_env_depth;
f32 fx_send;
f32 start_time;
f32 duration;
bool released;
oscillator osc;
envelope amp_env;
envelope filter_env;
@ -125,16 +126,21 @@ struct pxl8_sfx_node {
struct pxl8_sfx_context {
voice voices[PXL8_SFX_MAX_VOICES];
pxl8_sfx_node* fx_head;
pxl8_sfx_mixer* mixer;
f32 volume;
f32 current_time;
u16 next_voice_id;
u8 context_id;
};
struct pxl8_sfx_mixer {
SDL_AudioStream* stream;
const pxl8_hal* hal;
void* audio_handle;
pxl8_sfx_context* contexts[PXL8_SFX_MAX_CONTEXTS];
f32 master_volume;
f32* output_buffer;
pxl8_sfx_event_callback event_callback;
void* event_userdata;
};
static void envelope_trigger(envelope* env) {
@ -334,7 +340,7 @@ static f32 lfo_process(lfo* l) {
return sample * l->depth;
}
static void voice_trigger(voice* v, u8 note, const pxl8_sfx_voice_params* params, f32 volume, u16 id, f32 time) {
static void voice_trigger(voice* v, u8 note, const pxl8_sfx_voice_params* params, f32 volume, u16 id, f32 time, f32 duration) {
v->active = true;
v->id = id;
v->note = note;
@ -345,6 +351,8 @@ static void voice_trigger(voice* v, u8 note, const pxl8_sfx_voice_params* params
v->filter_env_depth = params->filter_env_depth;
v->fx_send = params->fx_send;
v->start_time = time;
v->duration = duration;
v->released = false;
oscillator_init(&v->osc, params->waveform, v->base_frequency);
v->osc.pulse_width = params->pulse_width;
@ -603,10 +611,17 @@ static void context_process_sample(pxl8_sfx_context* ctx, f32* out_left, f32* ou
f32 wet_left = 0.0f, wet_right = 0.0f;
for (int v = 0; v < PXL8_SFX_MAX_VOICES; v++) {
if (!ctx->voices[v].active) continue;
voice* vp = &ctx->voices[v];
if (!vp->active) continue;
if (vp->duration > 0.0f && !vp->released &&
ctx->current_time - vp->start_time >= vp->duration) {
voice_release(vp);
vp->released = true;
}
f32 dl, dr, wl, wr;
voice_process(&ctx->voices[v], &dl, &dr, &wl, &wr);
voice_process(vp, &dl, &dr, &wl, &wr);
dry_left += dl;
dry_right += dr;
@ -631,45 +646,9 @@ static void context_process_sample(pxl8_sfx_context* ctx, f32* out_left, f32* ou
*out_right = right;
}
static void SDLCALL audio_callback(void* userdata, SDL_AudioStream* stream, int additional_amount, int total_amount) {
(void)total_amount;
pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal) {
if (!hal || !hal->audio_create) return NULL;
pxl8_sfx_mixer* mixer = (pxl8_sfx_mixer*)userdata;
if (!mixer || !mixer->output_buffer) return;
u32 samples_needed = additional_amount / (sizeof(f32) * 2);
if (samples_needed > PXL8_SFX_BUFFER_SIZE) samples_needed = PXL8_SFX_BUFFER_SIZE;
for (u32 i = 0; i < samples_needed; i++) {
f32 left = 0.0f, right = 0.0f;
for (int c = 0; c < PXL8_SFX_MAX_CONTEXTS; c++) {
pxl8_sfx_context* ctx = mixer->contexts[c];
if (!ctx) continue;
f32 ctx_left, ctx_right;
context_process_sample(ctx, &ctx_left, &ctx_right);
left += ctx_left;
right += ctx_right;
}
left *= mixer->master_volume;
right *= mixer->master_volume;
left = fmaxf(-1.0f, fminf(1.0f, left));
right = fmaxf(-1.0f, fminf(1.0f, right));
if (!isfinite(left)) left = 0.0f;
if (!isfinite(right)) right = 0.0f;
mixer->output_buffer[i * 2] = left;
mixer->output_buffer[i * 2 + 1] = right;
}
SDL_PutAudioStreamData(stream, mixer->output_buffer, samples_needed * sizeof(f32) * 2);
}
pxl8_sfx_mixer* pxl8_sfx_mixer_create(void) {
pxl8_sfx_mixer* mixer = (pxl8_sfx_mixer*)calloc(1, sizeof(pxl8_sfx_mixer));
if (!mixer) return NULL;
@ -679,23 +658,17 @@ pxl8_sfx_mixer* pxl8_sfx_mixer_create(void) {
return NULL;
}
mixer->hal = hal;
mixer->master_volume = 0.8f;
SDL_AudioSpec spec = {
.channels = 2,
.format = SDL_AUDIO_F32,
.freq = PXL8_SFX_SAMPLE_RATE
};
mixer->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, audio_callback, mixer);
if (!mixer->stream) {
pxl8_error("Failed to open audio device: %s", SDL_GetError());
mixer->audio_handle = hal->audio_create(PXL8_SFX_SAMPLE_RATE, 2);
if (!mixer->audio_handle) {
free(mixer->output_buffer);
free(mixer);
return NULL;
}
SDL_ResumeAudioStreamDevice(mixer->stream);
hal->audio_start(mixer->audio_handle);
pxl8_info("Audio mixer initialized: %d Hz, stereo, %d context slots", PXL8_SFX_SAMPLE_RATE, PXL8_SFX_MAX_CONTEXTS);
return mixer;
@ -704,14 +677,57 @@ pxl8_sfx_mixer* pxl8_sfx_mixer_create(void) {
void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer) {
if (!mixer) return;
if (mixer->stream) {
SDL_DestroyAudioStream(mixer->stream);
if (mixer->hal && mixer->audio_handle) {
mixer->hal->audio_destroy(mixer->audio_handle);
}
free(mixer->output_buffer);
free(mixer);
}
void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer) {
if (!mixer || !mixer->hal || !mixer->audio_handle || !mixer->output_buffer) return;
i32 queued = mixer->hal->audio_queued(mixer->audio_handle);
i32 target = PXL8_SFX_SAMPLE_RATE / 10;
while (queued < target) {
i32 samples_to_generate = PXL8_SFX_BUFFER_SIZE;
if (samples_to_generate > target - queued) {
samples_to_generate = target - queued;
}
for (i32 i = 0; i < samples_to_generate; i++) {
f32 left = 0.0f, right = 0.0f;
for (int c = 0; c < PXL8_SFX_MAX_CONTEXTS; c++) {
pxl8_sfx_context* ctx = mixer->contexts[c];
if (!ctx) continue;
f32 ctx_left, ctx_right;
context_process_sample(ctx, &ctx_left, &ctx_right);
left += ctx_left;
right += ctx_right;
}
left *= mixer->master_volume;
right *= mixer->master_volume;
left = fmaxf(-1.0f, fminf(1.0f, left));
right = fmaxf(-1.0f, fminf(1.0f, right));
if (!isfinite(left)) left = 0.0f;
if (!isfinite(right)) right = 0.0f;
mixer->output_buffer[i * 2] = left;
mixer->output_buffer[i * 2 + 1] = right;
}
mixer->hal->upload_audio(mixer->audio_handle, mixer->output_buffer, samples_to_generate);
queued += samples_to_generate;
}
}
void pxl8_sfx_mixer_attach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx) {
if (!mixer || !ctx) return;
@ -722,6 +738,8 @@ void pxl8_sfx_mixer_attach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx) {
for (int i = 0; i < PXL8_SFX_MAX_CONTEXTS; i++) {
if (!mixer->contexts[i]) {
mixer->contexts[i] = ctx;
ctx->mixer = mixer;
ctx->context_id = (u8)i;
return;
}
}
@ -755,6 +773,13 @@ void pxl8_sfx_mixer_set_master_volume(pxl8_sfx_mixer* mixer, f32 volume) {
if (mixer) mixer->master_volume = fmaxf(0.0f, fminf(1.0f, volume));
}
void pxl8_sfx_mixer_set_event_callback(pxl8_sfx_mixer* mixer, pxl8_sfx_event_callback cb, void* userdata) {
if (mixer) {
mixer->event_callback = cb;
mixer->event_userdata = userdata;
}
}
f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer) {
return mixer ? mixer->master_volume : 0.0f;
}
@ -1047,7 +1072,7 @@ static i32 find_free_voice(pxl8_sfx_context* ctx) {
return oldest_active;
}
u16 pxl8_sfx_play_note(pxl8_sfx_context* ctx, u8 note, const pxl8_sfx_voice_params* params, f32 volume) {
u16 pxl8_sfx_play_note(pxl8_sfx_context* ctx, u8 note, const pxl8_sfx_voice_params* params, f32 volume, f32 duration) {
if (!ctx || !params) return 0;
i32 slot = find_free_voice(ctx);
@ -1056,7 +1081,11 @@ u16 pxl8_sfx_play_note(pxl8_sfx_context* ctx, u8 note, const pxl8_sfx_voice_para
u16 id = ctx->next_voice_id++;
if (ctx->next_voice_id == 0) ctx->next_voice_id = 1;
voice_trigger(&ctx->voices[slot], note, params, volume, id, ctx->current_time);
voice_trigger(&ctx->voices[slot], note, params, volume, id, ctx->current_time, duration);
if (ctx->mixer && ctx->mixer->event_callback) {
ctx->mixer->event_callback(PXL8_SFX_EVENT_NOTE_ON, ctx->context_id, note, volume, ctx->mixer->event_userdata);
}
return id;
}