From a0b6924a7d1baf401e50a8ef21b0a293da5d3eae Mon Sep 17 00:00:00 2001 From: Richard Swingwood Date: Thu, 23 Oct 2025 17:34:38 +0100 Subject: [PATCH 1/5] [AUDIO] refactor, add play lifetimes, add pan setting, general source tidy --- kernel/audio/OutputAudioDevice.cpp | 2 +- kernel/audio/audio.cpp | 222 +++++++++++++--------- kernel/audio/mixer.h | 22 +++ kernel/audio/virtio_audio_pci.cpp | 2 +- kernel/kernel_processes/boot/bootscreen.c | 34 +--- kernel/process/syscall.c | 2 +- shared/audio/cuatro.c | 132 ++++++++----- shared/audio/cuatro.h | 69 ++++--- shared/audio/tone.c | 38 ++-- shared/audio/tone.h | 2 +- shared/audio/wav.c | 3 - shared/types.h | 4 +- 12 files changed, 317 insertions(+), 215 deletions(-) create mode 100644 kernel/audio/mixer.h diff --git a/kernel/audio/OutputAudioDevice.cpp b/kernel/audio/OutputAudioDevice.cpp index a11060d4..89084875 100644 --- a/kernel/audio/OutputAudioDevice.cpp +++ b/kernel/audio/OutputAudioDevice.cpp @@ -1,7 +1,7 @@ #include "OutputAudioDevice.hpp" #include "memory/page_allocator.h" -#define BUF_SIZE PAGE_SIZE * 3 +#define BUF_SIZE PAGE_SIZE * 2 void OutputAudioDevice::populate(){ buffer = (uintptr_t)palloc(BUF_SIZE, MEM_PRIV_KERNEL, MEM_RW, true); diff --git a/kernel/audio/audio.cpp b/kernel/audio/audio.cpp index 6bf98b8b..dcecfcc8 100644 --- a/kernel/audio/audio.cpp +++ b/kernel/audio/audio.cpp @@ -1,13 +1,15 @@ #include "audio.h" #include "virtio_audio_pci.hpp" #include "kernel_processes/kprocess_loader.h" +#include "syscalls/syscalls.h" +#include "exceptions/timer.h" #include "console/kio.h" #include "math/math.h" #include "audio/cuatro.h" +#include "mixer.h" #include "audio/wav.h" #include "std/memory.h" #include "theme/theme.h" -#include "syscalls/syscalls.h" VirtioAudioDriver *audio_driver; @@ -29,23 +31,18 @@ void audio_get_info(uint32_t* rate, uint8_t* channels) { *channels = audio_driver->out_dev->channels; } - -typedef struct mixer_buf { - int16_t* samples; - size_t sample_count; - uint16_t left_level; - uint16_t right_level; -} mixer_buf; - -typedef struct mixer_input { - mixer_buf buf[2]; - uint8_t channels; -} mixer_input; - typedef struct mixer_line { - mixer_input u; - uint8_t bix; - bool in_use; + mixer_dblbuf dbl; + sizedptr source; + uint64_t start_time; + uint32_t delay_ms; + AUDIO_LIFETIME life; + int16_t level; + int16_t pan; + int16_t left_lvl; + int16_t right_lvl; + bool in_use; + uint8_t channels; } mixer_line; static mixer_line mixin[MIXER_INPUTS]; @@ -53,15 +50,19 @@ static mixer_line mixin[MIXER_INPUTS]; static void mixer_reset_line(int8_t lineId){ if (lineId < 0 || lineId >= MIXER_INPUTS) return; mixer_line* line = &mixin[lineId]; - line->u.buf[0].samples = 0; - line->u.buf[0].sample_count = 0; - line->u.buf[0].left_level = 0; - line->u.buf[0].right_level = 0; - line->u.buf[1].samples = 0; - line->u.buf[1].sample_count = 0; - line->u.buf[1].left_level = 0; - line->u.buf[1].right_level = 0; - line->bix = 0; + line->dbl.buf[0].ptr = NULL; + line->dbl.buf[0].size = 0; + line->dbl.buf[1].ptr = NULL; + line->dbl.buf[1].size = 0; + line->dbl.buf_idx = 0; + line->source.ptr = NULL; + line->source.size = 0; + line->level = 0; + line->pan = 0; + line->left_lvl = 0; + line->right_lvl = 0; + line->start_time = 0; + line->life = AUDIO_OFF; line->in_use = false; } @@ -75,63 +76,95 @@ static int16_t master_level = AUDIO_LEVEL_MAX / 2; static int16_t master_premute = 0; static bool master_muted = false; -static inline int16_t normalise_int64_to_int16(int64_t input){ - int signal = input * master_level / (AUDIO_LEVEL_MAX * AUDIO_LEVEL_MAX); - return (int16_t)max(min(signal, AUDIO_LEVEL_MAX), -AUDIO_LEVEL_MAX); +#define BUFFER_SEPARATION_MSECS (AUDIO_DRIVER_BUFFER_SIZE * 1000 / 44100) // Ideally no remainder from division! +#define BUFFER_PREROLL_MSECS (BUFFER_SEPARATION_MSECS - 5) +static_assert(BUFFER_PREROLL_MSECS > 0, "Audio buffer size too small"); + +static int32_t bhaskara_sin_int32(int deg){ + // https://en.wikipedia.org/wiki/Bh%C4%81skara_I%27s_sine_approximation_formula + return (INT16_MAX * 4 * deg * (180 - deg)) / (40500 - (deg * (180 - deg))); } -#define SUBMIT_SEPARATION_ACTUAL_MSECS (AUDIO_DRIVER_BUFFER_SIZE * 1000 / 44100) // Ideally no remainder from division! -#define SUBMIT_SEPARATION_SAFE_MSECS (SUBMIT_SEPARATION_ACTUAL_MSECS - 5) +static void calc_channel_levels(int8_t lineId){ + // !! approximation of 3db pan law + mixer_line* line = &mixin[lineId]; + int pan = ((int)line->pan + INT16_MAX) / 2; // 0 == hard left, INT16_MAX = hard right + int degrees = ((90 * pan)+(INT16_MAX/2)) / INT16_MAX; + line->left_lvl = line->level * bhaskara_sin_int32(90-degrees) / INT16_MAX; + line->right_lvl = line->level * bhaskara_sin_int32(degrees) / INT16_MAX; +} + +static inline audio_sample_t normalise_int64_sample(int64_t input){ + int64_t signal = input * master_level / (SIGNAL_LEVEL_MAX * SIGNAL_LEVEL_MAX); + return (audio_sample_t)max(min(signal, SIGNAL_LEVEL_MAX), SIGNAL_LEVEL_MIN); +} + +static inline void buffer_exhausted(mixer_line* line, sizedptr* inbuf){ + switch (line->life) { + case AUDIO_OFF: + break; + case AUDIO_ONESHOT: + inbuf->ptr = NULL; + mixer_reset_line(line - mixin); + break; + case AUDIO_ONESHOT_FREE: + inbuf->ptr = NULL; + free((void*)line->source.ptr, line->source.size); + mixer_reset_line(line - mixin); + break; + case AUDIO_LOOP: + line->start_time = timer_now_msec() + line->delay_ms; + inbuf->size = line->source.size / sizeof(audio_sample_t); + inbuf->ptr = line->source.ptr; + break; + case AUDIO_STREAM: + inbuf->ptr = NULL; + line->dbl.buf_idx = 1 - line->dbl.buf_idx; + break; + } +} static void mixer_run(){ - uint64_t buffer_run_start_time = 0; - uint32_t buffer_run_count = 0; - sizedptr buf = audio_request_buffer(audio_driver->out_dev->stream_id); + uint64_t buffers_start_time = 0; + uint64_t buffers_output = 0; + sizedptr outbuf = audio_request_buffer(audio_driver->out_dev->stream_id); do{ - bool have_audio = false; - int16_t* output = (int16_t*)buf.ptr; - int16_t* limit = output + buf.size; + audio_sample_t* output = (audio_sample_t*)outbuf.ptr; + audio_sample_t* limit = output + outbuf.size; + uint64_t now = timer_now_msec(); + // TODO: consider inverting these nested ifs - maybe more cache-friendly. while (output < limit){ int64_t left_signal = 0; int64_t right_signal = 0; mixer_line* line = mixin; while (line < mixin + MIXER_INPUTS){ - mixer_buf* buf = &line->u.buf[line->bix]; - if (buf->samples != NULL){ - left_signal += *buf->samples * buf->left_level; - if (line->u.channels == 2){ - buf->samples++; - buf->sample_count--; - } - right_signal += *buf->samples++ * buf->right_level; - if (--buf->sample_count < 1){ - buf->samples = NULL; - // line->bix = 1 - line->bix; // TODO: double-buffering for streaming support + sizedptr* inbuf = &line->dbl.buf[line->dbl.buf_idx]; + if (line->in_use && inbuf->ptr != NULL && line->start_time < now){ + left_signal += *(audio_sample_t*)inbuf->ptr * line->level; // line->left_lvl; + if (line->channels == 2){ + inbuf->ptr += sizeof(audio_sample_t); + --inbuf->size; } - have_audio = true; + right_signal += *(audio_sample_t*)inbuf->ptr * line->level; // line->right_lvl; + inbuf->ptr += sizeof(audio_sample_t); + if (--inbuf->size < 1) buffer_exhausted(line, inbuf); } ++line; } - *output++ = normalise_int64_to_int16(left_signal); - *output++ = normalise_int64_to_int16(right_signal); + *output++ = normalise_int64_sample(left_signal); + *output++ = normalise_int64_sample(right_signal); } - if (have_audio){ - if (buffer_run_count == 0){ - buffer_run_start_time = get_time(); - }else{ - uint64_t this_buffer_time = buffer_run_start_time + - (buffer_run_count * SUBMIT_SEPARATION_ACTUAL_MSECS) + SUBMIT_SEPARATION_SAFE_MSECS; - while (get_time() < this_buffer_time) - // TODO: yield cpu? - ; - } - audio_submit_buffer(); - ++buffer_run_count; - buf = audio_request_buffer(audio_driver->out_dev->stream_id); + if (buffers_output == 0){ + buffers_start_time = timer_now_msec(); }else{ - buffer_run_count = 0; - // TODO: yield cpu + uint64_t this_buffer_due = buffers_start_time + + (buffers_output * BUFFER_SEPARATION_MSECS) + BUFFER_PREROLL_MSECS; + while (timer_now_msec() < this_buffer_due) + yield(); } + audio_submit_buffer(); + ++buffers_output; + outbuf = audio_request_buffer(audio_driver->out_dev->stream_id); } while (1); } @@ -170,35 +203,35 @@ static int audio_get_free_line(){ static size_t audio_read(file *fd, char *out_buf, size_t size, file_offset offset){ if (size != sizeof(mixer_line_data)) return 0; - mixer_line_data* data = (mixer_line_data*)out_buf; // passed buffer has 'line' set - int8_t lineId = data->lineId; + mixer_line_data* data = (mixer_line_data*)out_buf; // !! passed buffer has 'line' set fd->cursor = 0; // never moves - if (lineId < 0){ + if (data->lineId < 0){ // request is for a free input line data->lineId = audio_get_free_line(); - data->count[0] = 0; - data->count[1] = 0; - }else{ - // request is for line data - if (lineId < 0 || lineId >= MIXER_INPUTS) return 0; - mixer_line* line = &mixin[data->lineId]; - data->count[0] = (intptr_t)line->u.buf[0].sample_count; - data->count[1] = (intptr_t)line->u.buf[1].sample_count; } + if (data->lineId < 0 || data->lineId >= MIXER_INPUTS) return 0; + data->dbl = mixin[data->lineId].dbl; return size; } static size_t audio_write(file *fd, const char *buf, size_t size, file_offset offset){ + if (size != sizeof(mixer_command)) return 0; mixer_command* cmd = (mixer_command*)buf; int8_t lineId = cmd->lineId; fd->cursor = 0; // never moves switch (cmd->command){ case MIXER_SETLEVEL: { - int16_t value = min(INT16_MAX, max(0, (int)cmd->value)); - if (master_muted == false){ - master_level = value; + int16_t level = min(AUDIO_LEVEL_MAX, max(0, (int)cmd->level)); + if (lineId == -1){ + if (master_muted == false){ + master_level = level; + }else{ + master_premute = level; // not being able to change volume without un-muting is a UI/UX failure + } }else{ - master_premute = value; // not being able to change volume without un-muting is a UI/UX failure + mixin[lineId].level = level; + mixin[lineId].pan = cmd->pan; + calc_channel_levels(lineId); } break; } @@ -216,13 +249,30 @@ static size_t audio_write(file *fd, const char *buf, size_t size, file_offset of } break; case MIXER_PLAY: { + // AUDIO_ONESHOT, AUDIO_ONESHOT_FREE, AUDIO_LOOP: set & forget + // AUDIO_STREAM: initialise if (lineId < 0 || lineId >= MIXER_INPUTS) return 0; mixer_line* line = &mixin[cmd->lineId]; - line->u.channels = cmd->audio->channels; - line->u.buf[0].left_level = cmd->audio->amplitude; - line->u.buf[0].right_level = cmd->audio->amplitude; - line->u.buf[0].sample_count = cmd->audio->smpls_per_channel * cmd->audio->channels; - line->u.buf[0].samples = (int16_t*)cmd->audio->samples.ptr; // this must be last mutation of 'line'. + line->channels = cmd->audio->channels; + line->level = cmd->level; + line->pan = cmd->pan; + calc_channel_levels(lineId); + line->delay_ms = cmd->delay_ms; + line->start_time = timer_now_msec() + line->delay_ms; + line->life = cmd->life; + line->source = cmd->audio->samples; + line->dbl.buf_idx = 0; + line->dbl.buf[0].size = line->source.size / sizeof(audio_sample_t); + line->dbl.buf[0].ptr = line->source.ptr; // this must be last mutation of 'line' + break; + } + case MIXER_SETBUFFER: { + // AUDIO_STREAM: populate next buffer + if (lineId < 0 || lineId >= MIXER_INPUTS) return 0; + mixer_line* line = &mixin[lineId]; + uint8_t nxt_buf = (line->dbl.buf[0].ptr == NULL) ? 0 : 1; + line->dbl.buf[nxt_buf].size = cmd->samples.size / sizeof(audio_sample_t); + line->dbl.buf[nxt_buf].ptr = cmd->samples.ptr; // this must be last mutation of 'line' break; } case MIXER_CLOSE_LINE: diff --git a/kernel/audio/mixer.h b/kernel/audio/mixer.h new file mode 100644 index 00000000..9d83d4c6 --- /dev/null +++ b/kernel/audio/mixer.h @@ -0,0 +1,22 @@ +#pragma once + +typedef enum MIXER_CMND { + MIXER_SETLEVEL, + MIXER_MUTE, + MIXER_UNMUTE, + MIXER_PLAY, + MIXER_SETBUFFER, + MIXER_CLOSE_LINE, +} MIXER_CMND; + +typedef struct mixer_command { + int8_t lineId; + uint32_t command; + audio_samples* audio; + sizedptr samples; + //intptr_t value; + uint32_t delay_ms; + int16_t level; + int16_t pan; + AUDIO_LIFETIME life; +} mixer_command; diff --git a/kernel/audio/virtio_audio_pci.cpp b/kernel/audio/virtio_audio_pci.cpp index 81b7d23c..0650cc5e 100644 --- a/kernel/audio/virtio_audio_pci.cpp +++ b/kernel/audio/virtio_audio_pci.cpp @@ -208,7 +208,7 @@ void VirtioAudioDriver::send_buffer(sizedptr buf){ virtio_add_buffer(&audio_dev, cmd_index % audio_dev.common_cfg->queue_size, buf.ptr, buf.size, true); struct virtq_used* u = (struct virtq_used*)(uintptr_t)audio_dev.common_cfg->queue_device; while (u->idx < cmd_index-2) - ; // TODO: yield cpu + yield(); cmd_index++; } diff --git a/kernel/kernel_processes/boot/bootscreen.c b/kernel/kernel_processes/boot/bootscreen.c index c75fb4da..b9e9c948 100644 --- a/kernel/kernel_processes/boot/bootscreen.c +++ b/kernel/kernel_processes/boot/bootscreen.c @@ -98,41 +98,26 @@ void boot_draw_lines(gpu_point current_point, gpu_point next_point, gpu_size siz } } - -static int8_t mixin[MIXER_INPUTS] = { NULL }; -static audio_samples audio[MIXER_INPUTS]; - -static void close_idle_mixer_inputs(){ - for (int i = 0; i < MIXER_INPUTS; ++i){ - if (mixin[i] >= 0){ - if (mixer_still_playing(mixin[i]) == false){ - mixer_close_line(mixin[i]); - mixin[i] = -1; - free((char*)audio[i].samples.ptr, audio[i].samples.size); - } - } - } -} +static audio_samples audio[3]; static void play_startup_sound(){ - for (int i = 0; i < MIXER_INPUTS; ++i) mixin[i] = -1; - mixer_set_level(AUDIO_LEVEL_MAX * 0.75f); + mixer_master_level(AUDIO_LEVEL_MAX / 2); if (wav_load_as_int16("/boot/redos/startup.wav", audio)){ - mixin[0] = play_audio_async(&audio[0], AUDIO_LEVEL_MAX/4); + audio_play_async(&audio[0], 0, AUDIO_ONESHOT_FREE, AUDIO_LEVEL_MAX / 12, PAN_CENTRE); }else{ static envelope_defn env = { ENV_ASD, 0.025, 0.03 }; static sound_defn s0 = { WAVE_SQUARE, 440, 440 }; static sound_defn s1 = { WAVE_SQUARE, 587.33, 587.33 }; static sound_defn s2 = { WAVE_SQUARE, 659.25, 659.25 }; - sound_create(4.f, 0.f, &s0, &audio[0]); + sound_create(3.f, &s0, &audio[0]); sound_shape(&env, &audio[0]); - sound_create(4.f, 0.15f, &s1, &audio[1]); + sound_create(3.f, &s1, &audio[1]); sound_shape(&env, &audio[1]); - sound_create(4.f, 0.30f, &s2, &audio[2]); + sound_create(3.f, &s2, &audio[2]); sound_shape(&env, &audio[2]); - mixin[0] = play_audio_async(&audio[0], AUDIO_LEVEL_MAX/4); - mixin[1] = play_audio_async(&audio[1], AUDIO_LEVEL_MAX/5); - mixin[2] = play_audio_async(&audio[2], AUDIO_LEVEL_MAX/6); + audio_play_async(&audio[0], 0, AUDIO_ONESHOT_FREE, AUDIO_LEVEL_MAX / 16, PAN_HALFLEFT); + audio_play_async(&audio[1], 150, AUDIO_ONESHOT_FREE, AUDIO_LEVEL_MAX / 16, PAN_CENTRE); + audio_play_async(&audio[2], 300, AUDIO_ONESHOT_FREE, AUDIO_LEVEL_MAX / 16, PAN_HALFRIGHT); } } @@ -156,7 +141,6 @@ int bootscreen(){ current_point = next_point; } sleep(1000); - close_idle_mixer_inputs(); } return 0; } diff --git a/kernel/process/syscall.c b/kernel/process/syscall.c index 34ccb825..f55bfc05 100644 --- a/kernel/process/syscall.c +++ b/kernel/process/syscall.c @@ -186,7 +186,7 @@ uint64_t syscall_fopen(process_t *ctx){ char path[255]; if (!(ctx->PROC_PRIV) && strstart("/resources/", req_path, true) == 11){ string_format_buf(path, sizeof(path),"%s%s", ctx->bundle, req_path); - } else memcpy(path, req_path, strlen(req_path, 0)); + } else memcpy(path, req_path, strlen(req_path, 0) + 1); //TODO: Restrict access to own bundle, own fs and require privilege escalation for full-ish filesystem access file *descriptor = (file*)ctx->PROC_X1; return open_file(path, descriptor); diff --git a/shared/audio/cuatro.c b/shared/audio/cuatro.c index c977c280..b8fa56b9 100644 --- a/shared/audio/cuatro.c +++ b/shared/audio/cuatro.c @@ -1,68 +1,63 @@ #include "math/math.h" +#ifdef KERNEL +#include "filesystem/filesystem.h" +#include "console/kio.h" +#else #include "files/fs.h" +#endif #include "syscalls/syscalls.h" #include "cuatro.h" +#include "audio/mixer.h" #include "wav.h" #include "tone.h" - -bool play_audio_sync(audio_samples *audio, int16_t amplitude){ - int8_t lineId = mixer_open_line(); - if (lineId < 0 || lineId > MIXER_INPUTS) return false; - audio->amplitude = amplitude; - mixer_play_async(lineId, audio); - do { - sleep(100); - } while (mixer_still_playing(lineId)); - mixer_close_line(lineId); - return true; -} - -int8_t play_audio_async(audio_samples *audio, int16_t amplitude){ - int8_t lineId = mixer_open_line(); - if (lineId < 0 || lineId > MIXER_INPUTS) return -1; - audio->amplitude = amplitude; - mixer_play_async(lineId, audio); - return lineId; -} - +#ifdef KERNEL +#define F_OPEN open_file +#define F_READ read_file +#define F_WRITE write_file +//#define F_CLOSE close_file +#else +#define F_OPEN fopen +#define F_READ fread +#define F_WRITE fwrite +//#define F_CLOSE fclose +#endif static file mixer = { .id = 0 }; // 0 ok as filesystem ids > 256 -static bool mixer_open_file(){ - if (mixer.id == 0 && FS_RESULT_SUCCESS != fopen("/dev/audio/output", &mixer)) return false; - return true; +static inline bool mixer_open_file(){ + return (mixer.id > 0) || (FS_RESULT_SUCCESS == F_OPEN("/dev/audio/output", &mixer)); } int8_t mixer_open_line(){ - if (!mixer_open_file()) return NULL; - mixer_line_data data = { -1, {0, 0} }; - if (sizeof(mixer_line_data) != fread(&mixer, (char*)&data, sizeof(mixer_line_data))) return NULL; - return data.lineId; + mixer_line_data data; + if (mixer_open_file()){ + if (mixer_read_line(-1, &data)) return data.lineId; + } + return -1; } void mixer_close_line(int8_t lineId){ - if (mixer_open_file()){ - mixer_command command = { lineId, MIXER_CLOSE_LINE, .value = 0 }; - fwrite(&mixer, (char*)&command, sizeof(mixer_command)); + if (mixer_open_file() && lineId >= 0){ + mixer_command command = { .lineId = lineId, .command = MIXER_CLOSE_LINE }; + F_WRITE(&mixer, (char*)&command, sizeof(mixer_command)); } } -void mixer_play_async(int8_t lineId, audio_samples* audio){ +bool mixer_read_line(int8_t lineId, mixer_line_data* data){ if (mixer_open_file()){ - mixer_command command = { lineId, MIXER_PLAY, .audio = audio }; - fwrite(&mixer, (char*)&command, sizeof(mixer_command)); + data->lineId = lineId; + return (sizeof(mixer_line_data) == F_READ(&mixer, (char*)data, sizeof(mixer_line_data))); } + return false; } bool mixer_still_playing(int8_t lineId){ if (mixer_open_file()){ - mixer_line_data data = { lineId, {1, 1} }; - if (sizeof(mixer_line_data) == fread(&mixer, (char*)&data, sizeof(mixer_line_data))){ - if (data.count[0] != 0 || data.count[1] != 0){ - // TODO: this won't work for streaming outputs (when implemented) - race condition - return true; - } + mixer_line_data data; + data.lineId = lineId; + if (mixer_read_line(lineId, &data)){ + if (data.dbl.buf[0].ptr != 0 || data.dbl.buf[1].ptr != 0) return true; } } return false; @@ -70,24 +65,67 @@ bool mixer_still_playing(int8_t lineId){ bool mixer_mute(){ if (mixer_open_file()){ - mixer_command command = { -1, MIXER_MUTE, .value=0 }; - fwrite(&mixer, (char*)&command, sizeof(mixer_command)); + mixer_command command = { .lineId = -1, .command = MIXER_MUTE }; + F_WRITE(&mixer, (char*)&command, sizeof(mixer_command)); } return false; // TODO: return prev setting } bool mixer_unmute(){ if (mixer_open_file()){ - mixer_command command = { -1, MIXER_UNMUTE, .value=0 }; - fwrite(&mixer, (char*)&command, sizeof(mixer_command)); + mixer_command command = { .lineId = -1, .command = MIXER_UNMUTE }; + F_WRITE(&mixer, (char*)&command, sizeof(mixer_command)); } return true; // TODO: return prev setting } -uint32_t mixer_set_level(int16_t level){ +int16_t mixer_master_level(int16_t level){ if (mixer_open_file()){ - mixer_command command = { -1, MIXER_SETLEVEL, .value = (uintptr_t)level }; - fwrite(&mixer, (char*)&command, sizeof(mixer_command)); + mixer_command command = { .lineId = -1, .command = MIXER_SETLEVEL, .level = level }; + F_WRITE(&mixer, (char*)&command, sizeof(mixer_command)); } return level; // TODO: return prev setting } + +int16_t mixer_line_level(int8_t lineId, int16_t level, int16_t pan){ + if (lineId < 0 || lineId > MIXER_INPUTS) return 0; + if (mixer_open_file()){ + mixer_command command = { .lineId = lineId, .command = MIXER_SETLEVEL, .level = level, .pan = pan }; + F_WRITE(&mixer, (char*)&command, sizeof(mixer_command)); + } + return level; // TODO: return prev setting +} + +static bool mixer_play_async(int8_t lineId, audio_samples* audio, uint32_t delay_ms, AUDIO_LIFETIME life, int16_t level, int16_t pan){ + mixer_command command = { .lineId = lineId, .command = MIXER_PLAY, .audio = audio, .delay_ms = delay_ms, .life = life, .level = level, .pan = pan }; + return (sizeof(mixer_command) == F_WRITE(&mixer, (char*)&command, sizeof(mixer_command))); +} + +bool audio_play_sync(audio_samples *audio, uint32_t delay_ms, AUDIO_LIFETIME life, int16_t level, int16_t pan){ + int8_t lineId = mixer_open_line(); + if (lineId < 0 || false == mixer_play_async(lineId, audio, delay_ms, life, level, pan)) return false; + do { + sleep(5); + } while (mixer_still_playing(lineId)); + mixer_close_line(lineId); + return true; +} + +int8_t audio_play_async(audio_samples *audio, uint32_t delay_ms, AUDIO_LIFETIME life, int16_t level, int16_t pan){ + int8_t lineId = mixer_open_line(); + if (lineId >= 0) { + if (false == mixer_play_async(lineId, audio, delay_ms, life, level, pan)) { + mixer_close_line(lineId); + lineId = -1; + } + } + return lineId; +} + +bool audio_update_stream(int8_t lineId, sizedptr samples){ + if (mixer_open_file()){ + mixer_command command = { .lineId = lineId, .command = MIXER_SETBUFFER, .samples = samples }; + return (sizeof(mixer_command) == F_WRITE(&mixer, (char*)&command, sizeof(mixer_command))); + } + return false; +} \ No newline at end of file diff --git a/shared/audio/cuatro.h b/shared/audio/cuatro.h index e21074f7..cf46119c 100644 --- a/shared/audio/cuatro.h +++ b/shared/audio/cuatro.h @@ -2,54 +2,63 @@ #include "types.h" +typedef int16_t audio_sample_t; + +typedef enum AUDIO_LIFETIME { + AUDIO_OFF = 0, + AUDIO_ONESHOT, // play samples once + AUDIO_ONESHOT_FREE, // play samples once then free() their memory + AUDIO_LOOP, // play samples repeatedly - with delay, if set + AUDIO_STREAM, // assign samples to available dblbuf entry +} AUDIO_LIFETIME; + +typedef enum PAN_RANGE { + PAN_LEFT = -32767, + PAN_HALFLEFT = -16383, + PAN_CENTRE = 0, + PAN_CENTER = 0, + PAN_HALFRIGHT = 16383, + PAN_RIGHT = 32767, +} PAN_RANGE; + typedef struct audio_samples { sizedptr samples; - uint32_t smpls_per_channel; - uint8_t channels; - int16_t amplitude; - float secs; + uint32_t smpls_per_channel; // TODO: eliminate. + uint8_t channels; } audio_samples; -typedef enum MIXER_CMND { - MIXER_SETLEVEL, - MIXER_MUTE, - MIXER_UNMUTE, - MIXER_PLAY, - MIXER_CLOSE_LINE, -} MIXER_CMND; - -typedef struct mixer_command { - int8_t lineId; - uint32_t command; - union { - intptr_t value; - audio_samples *audio; - }; -} mixer_command; +typedef struct mixer_dblbuf { + sizedptr buf[2]; + uint8_t buf_idx; +} mixer_dblbuf; typedef struct mixer_line_data { - int8_t lineId; - size_t count[2]; + int8_t lineId; + mixer_dblbuf dbl; } mixer_line_data; -#define AUDIO_LEVEL_MAX INT16_MAX -#define MIXER_INPUTS 4 +#define MIXER_INPUTS 6 +#define AUDIO_LEVEL_MAX INT16_MAX +#define SIGNAL_LEVEL_MAX 32767 +#define SIGNAL_LEVEL_MIN (-32767) #ifdef __cplusplus extern "C" { #endif -bool play_audio_sync(audio_samples *audio, int16_t amplitude); -int8_t play_audio_async(audio_samples *audio, int16_t amplitude); +bool audio_play_sync(audio_samples *audio, uint32_t delay_ms, AUDIO_LIFETIME life, int16_t level, int16_t pan); +int8_t audio_play_async(audio_samples *audio, uint32_t delay_ms, AUDIO_LIFETIME life, int16_t level, int16_t pan); +bool audio_update_stream(int8_t lineId, sizedptr samples); int8_t mixer_open_line(); -void mixer_close_line(int8_t line); -bool mixer_still_playing(int8_t line); -void mixer_play_async(int8_t line, audio_samples* audio); +void mixer_close_line(int8_t lineId); +bool mixer_read_line(int8_t lineId, mixer_line_data* data); +bool mixer_still_playing(int8_t lineId); bool mixer_mute(); bool mixer_unmute(); -uint32_t mixer_set_level(int16_t level); +int16_t mixer_master_level(int16_t level); +int16_t mixer_line_level(int8_t lineId, int16_t level, int16_t pan); #ifdef __cplusplus } diff --git a/shared/audio/tone.c b/shared/audio/tone.c index 99685515..37f4212c 100644 --- a/shared/audio/tone.c +++ b/shared/audio/tone.c @@ -10,7 +10,7 @@ #define PHASE_MID (PHASE_MAX >> 1) -static int16_t wave_sample(WAVE_TYPE type, uint32_t phase, rng_t* rng){ +static audio_sample_t wave_sample(WAVE_TYPE type, uint32_t phase, rng_t* rng){ float wave = 0; switch (type) { case WAVE_SILENCE: @@ -27,18 +27,20 @@ static int16_t wave_sample(WAVE_TYPE type, uint32_t phase, rng_t* rng){ wave = (phase < PHASE_MID) ? 0.5 : -0.5; break; case WAVE_NOISE: { - return (int16_t)rng_next16(rng); + return (audio_sample_t)rng_next16(rng); } } - return (int16_t)(wave * UINT16_MAX); + return (audio_sample_t)(wave * UINT16_MAX); } -static void wave_generate(sound_defn* sound, int16_t* sample, size_t count){ +static void wave_generate(sound_defn* sound, audio_sample_t* sample, size_t count){ float freq = sound->start_freq; float freq_delta = (sound->end_freq - sound->start_freq) / count; uint32_t phase = 0; rng_t rng; - rng_init_random(&rng); + // TODO: rng_init_random(&rng); + rng.s0 = get_time(); + rng.s1 = (uint64_t)&wave_generate ^ rng.s0; while (count--){ uint32_t phase_incr = (uint32_t)(freq * PHASE_MAX / 44100.f); *sample++ = wave_sample(sound->waveform, phase, &rng); @@ -47,22 +49,22 @@ static void wave_generate(sound_defn* sound, int16_t* sample, size_t count){ } } -void sound_create(float duration, float delay, sound_defn* sound, audio_samples* audio){ - audio->channels = 1; // mono only - audio->secs = duration + delay; - audio->smpls_per_channel = (duration + delay) * 44100.f; - audio->samples.size = audio->smpls_per_channel; - audio->samples.ptr = (uintptr_t)malloc(audio->samples.size * sizeof(int16_t)); - size_t delay_samples = delay * 44100.f; - wave_generate(sound, ((int16_t*)audio->samples.ptr) + delay_samples, audio->samples.size - delay_samples); +void sound_create(float duration, sound_defn* sound, audio_samples* audio){ + // !! MONO only + audio->channels = 1; + audio->smpls_per_channel = duration * 44100.f; + audio->samples.size = audio->smpls_per_channel * sizeof(audio_sample_t); + audio->samples.ptr = (uintptr_t)malloc(audio->samples.size); + wave_generate(sound, (audio_sample_t*)audio->samples.ptr, audio->smpls_per_channel); } void sound_shape(envelope_defn* env, audio_samples* audio){ - if (env->shape != ENV_NONE){ - size_t attack = min(audio->samples.size, max(0, (int)(audio->samples.size * env->attack))); - size_t sustain = min(audio->samples.size, max(0, (int)(audio->samples.size * env->sustain))); - size_t decay = audio->samples.size - attack - sustain; - int16_t* sample = (int16_t*)audio->samples.ptr; + // !! MONO only + if (env->shape != ENV_NONE && audio->channels == 1){ + size_t attack = 1 + min(audio->smpls_per_channel, max(0, (int)(audio->smpls_per_channel * env->attack))); + size_t sustain = min(audio->smpls_per_channel - attack, max(0, (int)(audio->smpls_per_channel * env->sustain))); + size_t decay = max(0, (int)audio->smpls_per_channel - (int)attack - (int)sustain); + audio_sample_t* sample = (audio_sample_t*)audio->samples.ptr; float delta = (float)INT16_MAX / attack; float level = 0; while (attack--){ diff --git a/shared/audio/tone.h b/shared/audio/tone.h index 8c77f3e4..bac5d369 100644 --- a/shared/audio/tone.h +++ b/shared/audio/tone.h @@ -34,7 +34,7 @@ typedef struct envelope_defn { extern "C" { #endif -void sound_create(float seconds, float delay, sound_defn *sound, audio_samples *audio); +void sound_create(float seconds, sound_defn *sound, audio_samples *audio); void sound_shape(envelope_defn* env, audio_samples* audio); #ifdef __cplusplus diff --git a/shared/audio/wav.c b/shared/audio/wav.c index c6f27463..18694013 100644 --- a/shared/audio/wav.c +++ b/shared/audio/wav.c @@ -39,7 +39,6 @@ static void transform_16bit(wav_format_chunk *fmt_chunk, uint32_t data_size, aud audio->samples.ptr = (uintptr_t)malloc(audio->samples.size); audio->smpls_per_channel = audio->samples.size / (sizeof(int16_t) * fmt_chunk->channels); audio->channels = fmt_chunk->channels; - audio->secs = audio->smpls_per_channel / 44100.f; uint32_t samples_remaining = data_size / sizeof(int16_t); int16_t* source = tbuf; int16_t* dest = (int16_t*)audio->samples.ptr; @@ -59,7 +58,6 @@ static void transform_8bit(wav_format_chunk *fmt_chunk, uint32_t data_size, audi audio->samples.ptr = (uintptr_t)malloc(audio->samples.size); audio->smpls_per_channel = audio->samples.size / (sizeof(int16_t) * fmt_chunk->channels); audio->channels = fmt_chunk->channels; - audio->secs = audio->smpls_per_channel / 44100.f; uint32_t samples_remaining = data_size; uint8_t* source = tbuf; int16_t* dest = (int16_t*)audio->samples.ptr; @@ -128,7 +126,6 @@ bool wav_load_as_int16(const char* path, audio_samples* audio){ fread(&fd, (char*)audio->samples.ptr, audio->samples.size); audio->smpls_per_channel = ch_hdr.ck_size / (sizeof(int16_t) * fmt_chunk.channels); audio->channels = fmt_chunk.channels; - audio->secs = audio->smpls_per_channel / 44100.f; } else if (fmt_chunk.sample_bits == 16){ transform_16bit(&fmt_chunk, ch_hdr.ck_size, audio, upsample, &fd); } else if (fmt_chunk.sample_bits == 8){ diff --git a/shared/types.h b/shared/types.h index d1727042..c172c057 100644 --- a/shared/types.h +++ b/shared/types.h @@ -54,6 +54,8 @@ typedef unsigned char uint8_t; #define UINT32_MAX 0xFFFFFFFF #define UINT8_MAX 0xFF +#define INT16_MAX 0x7FFF + #define N_ARR(arr) (sizeof(arr)/sizeof((arr)[0])) typedef signed int int32_t; @@ -62,8 +64,6 @@ typedef signed long intptr_t; typedef signed short int16_t; typedef signed char int8_t; -#define INT16_MAX 0x7FFF - typedef struct sizedptr { uintptr_t ptr; size_t size; From 6d5908f67c04cf418c382fd571859b5d0f92865e Mon Sep 17 00:00:00 2001 From: Richard Swingwood Date: Sun, 26 Oct 2025 08:50:14 +0000 Subject: [PATCH 2/5] [Audio] simplify mixer init; remove unnessesary #ifdef KERNEL alternatives --- kernel/audio/audio.cpp | 21 +++------------------ shared/audio/cuatro.c | 35 +++++++++-------------------------- 2 files changed, 12 insertions(+), 44 deletions(-) diff --git a/kernel/audio/audio.cpp b/kernel/audio/audio.cpp index dcecfcc8..c19b7bf2 100644 --- a/kernel/audio/audio.cpp +++ b/kernel/audio/audio.cpp @@ -49,27 +49,12 @@ static mixer_line mixin[MIXER_INPUTS]; static void mixer_reset_line(int8_t lineId){ if (lineId < 0 || lineId >= MIXER_INPUTS) return; - mixer_line* line = &mixin[lineId]; - line->dbl.buf[0].ptr = NULL; - line->dbl.buf[0].size = 0; - line->dbl.buf[1].ptr = NULL; - line->dbl.buf[1].size = 0; - line->dbl.buf_idx = 0; - line->source.ptr = NULL; - line->source.size = 0; - line->level = 0; - line->pan = 0; - line->left_lvl = 0; - line->right_lvl = 0; - line->start_time = 0; - line->life = AUDIO_OFF; - line->in_use = false; + memset(mixin + lineId, 0, sizeof(mixer_line)); + mixin[lineId].life = AUDIO_OFF; } static void mixer_reset(){ - for (int i = 0; i < MIXER_INPUTS; i++){ - mixer_reset_line(i); - } + for (int i = 0; i < MIXER_INPUTS; i++) mixer_reset_line(i); } static int16_t master_level = AUDIO_LEVEL_MAX / 2; diff --git a/shared/audio/cuatro.c b/shared/audio/cuatro.c index b8fa56b9..5385c819 100644 --- a/shared/audio/cuatro.c +++ b/shared/audio/cuatro.c @@ -1,32 +1,15 @@ #include "math/math.h" -#ifdef KERNEL -#include "filesystem/filesystem.h" -#include "console/kio.h" -#else #include "files/fs.h" -#endif #include "syscalls/syscalls.h" #include "cuatro.h" #include "audio/mixer.h" #include "wav.h" #include "tone.h" -#ifdef KERNEL -#define F_OPEN open_file -#define F_READ read_file -#define F_WRITE write_file -//#define F_CLOSE close_file -#else -#define F_OPEN fopen -#define F_READ fread -#define F_WRITE fwrite -//#define F_CLOSE fclose -#endif - static file mixer = { .id = 0 }; // 0 ok as filesystem ids > 256 static inline bool mixer_open_file(){ - return (mixer.id > 0) || (FS_RESULT_SUCCESS == F_OPEN("/dev/audio/output", &mixer)); + return (mixer.id > 0) || (FS_RESULT_SUCCESS == fopen("/dev/audio/output", &mixer)); } int8_t mixer_open_line(){ @@ -40,14 +23,14 @@ int8_t mixer_open_line(){ void mixer_close_line(int8_t lineId){ if (mixer_open_file() && lineId >= 0){ mixer_command command = { .lineId = lineId, .command = MIXER_CLOSE_LINE }; - F_WRITE(&mixer, (char*)&command, sizeof(mixer_command)); + fwrite(&mixer, (char*)&command, sizeof(mixer_command)); } } bool mixer_read_line(int8_t lineId, mixer_line_data* data){ if (mixer_open_file()){ data->lineId = lineId; - return (sizeof(mixer_line_data) == F_READ(&mixer, (char*)data, sizeof(mixer_line_data))); + return (sizeof(mixer_line_data) == fread(&mixer, (char*)data, sizeof(mixer_line_data))); } return false; } @@ -66,7 +49,7 @@ bool mixer_still_playing(int8_t lineId){ bool mixer_mute(){ if (mixer_open_file()){ mixer_command command = { .lineId = -1, .command = MIXER_MUTE }; - F_WRITE(&mixer, (char*)&command, sizeof(mixer_command)); + fwrite(&mixer, (char*)&command, sizeof(mixer_command)); } return false; // TODO: return prev setting } @@ -74,7 +57,7 @@ bool mixer_mute(){ bool mixer_unmute(){ if (mixer_open_file()){ mixer_command command = { .lineId = -1, .command = MIXER_UNMUTE }; - F_WRITE(&mixer, (char*)&command, sizeof(mixer_command)); + fwrite(&mixer, (char*)&command, sizeof(mixer_command)); } return true; // TODO: return prev setting } @@ -82,7 +65,7 @@ bool mixer_unmute(){ int16_t mixer_master_level(int16_t level){ if (mixer_open_file()){ mixer_command command = { .lineId = -1, .command = MIXER_SETLEVEL, .level = level }; - F_WRITE(&mixer, (char*)&command, sizeof(mixer_command)); + fwrite(&mixer, (char*)&command, sizeof(mixer_command)); } return level; // TODO: return prev setting } @@ -91,14 +74,14 @@ int16_t mixer_line_level(int8_t lineId, int16_t level, int16_t pan){ if (lineId < 0 || lineId > MIXER_INPUTS) return 0; if (mixer_open_file()){ mixer_command command = { .lineId = lineId, .command = MIXER_SETLEVEL, .level = level, .pan = pan }; - F_WRITE(&mixer, (char*)&command, sizeof(mixer_command)); + fwrite(&mixer, (char*)&command, sizeof(mixer_command)); } return level; // TODO: return prev setting } static bool mixer_play_async(int8_t lineId, audio_samples* audio, uint32_t delay_ms, AUDIO_LIFETIME life, int16_t level, int16_t pan){ mixer_command command = { .lineId = lineId, .command = MIXER_PLAY, .audio = audio, .delay_ms = delay_ms, .life = life, .level = level, .pan = pan }; - return (sizeof(mixer_command) == F_WRITE(&mixer, (char*)&command, sizeof(mixer_command))); + return (sizeof(mixer_command) == fwrite(&mixer, (char*)&command, sizeof(mixer_command))); } bool audio_play_sync(audio_samples *audio, uint32_t delay_ms, AUDIO_LIFETIME life, int16_t level, int16_t pan){ @@ -125,7 +108,7 @@ int8_t audio_play_async(audio_samples *audio, uint32_t delay_ms, AUDIO_LIFETIME bool audio_update_stream(int8_t lineId, sizedptr samples){ if (mixer_open_file()){ mixer_command command = { .lineId = lineId, .command = MIXER_SETBUFFER, .samples = samples }; - return (sizeof(mixer_command) == F_WRITE(&mixer, (char*)&command, sizeof(mixer_command))); + return (sizeof(mixer_command) == fwrite(&mixer, (char*)&command, sizeof(mixer_command))); } return false; } \ No newline at end of file From 5580d3419ad67e6da4665e4cf3257c902845af99 Mon Sep 17 00:00:00 2001 From: Richard Swingwood Date: Sat, 1 Nov 2025 22:22:38 +0000 Subject: [PATCH 3/5] [AUDIO] Fix types.h --- shared/types.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/types.h b/shared/types.h index c172c057..5b14f047 100644 --- a/shared/types.h +++ b/shared/types.h @@ -54,7 +54,7 @@ typedef unsigned char uint8_t; #define UINT32_MAX 0xFFFFFFFF #define UINT8_MAX 0xFF -#define INT16_MAX 0x7FFF +#define FLOAT_MAX 3.40282347e+38F #define N_ARR(arr) (sizeof(arr)/sizeof((arr)[0])) @@ -64,6 +64,8 @@ typedef signed long intptr_t; typedef signed short int16_t; typedef signed char int8_t; +#define INT16_MAX 0x7FFF + typedef struct sizedptr { uintptr_t ptr; size_t size; From 8118f88e0b24aa9314afb171ddcf750c3be52bd8 Mon Sep 17 00:00:00 2001 From: Richard Swingwood Date: Sat, 1 Nov 2025 22:40:20 +0000 Subject: [PATCH 4/5] [AUDIO] api in default process --- user/default_process.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user/default_process.c b/user/default_process.c index 4a9476e5..184e06ee 100644 --- a/user/default_process.c +++ b/user/default_process.c @@ -83,9 +83,9 @@ static audio_samples audio[MIXER_INPUTS]; int audio_example(){ for (int i = 0; i < MIXER_INPUTS; ++i) mixin[i] = -1; - mixer_set_level(AUDIO_LEVEL_MAX * 0.75f); + mixer_master_level(AUDIO_LEVEL_MAX * 0.75f); if (wav_load_as_int16("/resources/scale.wav", audio)){ - mixin[0] = play_audio_sync(&audio[0], AUDIO_LEVEL_MAX/4); + mixin[0] = audio_play_sync(&audio[0], 0, AUDIO_ONESHOT, AUDIO_LEVEL_MAX/4, PAN_CENTRE); return 0; }else{ printf("Could not load wav"); From 024ea0197ea7ca1a54e41f21ba05856c9dce26cd Mon Sep 17 00:00:00 2001 From: rjs Date: Mon, 17 Nov 2025 22:21:02 +0000 Subject: [PATCH 5/5] [Audio] Fix merge error bootscreen.c --- kernel/kernel_processes/boot/bootscreen.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/kernel/kernel_processes/boot/bootscreen.c b/kernel/kernel_processes/boot/bootscreen.c index f13c152b..b9317770 100644 --- a/kernel/kernel_processes/boot/bootscreen.c +++ b/kernel/kernel_processes/boot/bootscreen.c @@ -140,13 +140,10 @@ int bootscreen(){ current_point = next_point; } sleep(1000); - if (boot_theme.play_startup_sound){ - close_idle_mixer_inputs(); - } } return 0; } process_t* start_bootscreen(){ return create_kernel_process("bootscreen",bootscreen, 0, 0); -} \ No newline at end of file +}