improve script hot reload
This commit is contained in:
parent
01d6e09a91
commit
15041984f1
25 changed files with 1516 additions and 293 deletions
145
src/pxl8_sfx.c
145
src/pxl8_sfx.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue