From 91382aff69d2c349038fe6bcad11840b3edca335 Mon Sep 17 00:00:00 2001 From: Richard Swingwood Date: Tue, 23 Sep 2025 14:33:18 +0100 Subject: [PATCH] [AUDIO] Support playing of startup sound from WAV file or hardcoded 'jingle' --- kernel/audio/OutputAudioDevice.cpp | 2 +- kernel/audio/audio.cpp | 124 ++++++++++++++++++++--------- kernel/audio/virtio_audio_pci.cpp | 16 ++-- shared/audio/cuatro.c | 14 ++-- shared/audio/cuatro.h | 13 ++- shared/types.h | 2 + 6 files changed, 116 insertions(+), 55 deletions(-) diff --git a/kernel/audio/OutputAudioDevice.cpp b/kernel/audio/OutputAudioDevice.cpp index 99dca660..77e18f47 100644 --- a/kernel/audio/OutputAudioDevice.cpp +++ b/kernel/audio/OutputAudioDevice.cpp @@ -2,7 +2,7 @@ #include "memory/page_allocator.h" //TODO: We should allocate at least 40 pages, possibly 64 to have more than a second at once -#define BUF_SIZE PAGE_SIZE * 10 +#define BUF_SIZE PAGE_SIZE * 20 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 8f87aece..25cc6045 100644 --- a/kernel/audio/audio.cpp +++ b/kernel/audio/audio.cpp @@ -4,8 +4,9 @@ #include "console/kio.h" #include "math/math.h" #include "audio/cuatro.h" -#include "graph/graphics.h" +#include "audio/wav.h" #include "theme/theme.h" +#include "syscalls/syscalls.h" VirtioAudioDriver *audio_driver; @@ -26,53 +27,100 @@ void audio_submit_buffer(){ audio_driver->out_dev->submit_buffer(audio_driver); } -void make_wave(WAVE_TYPE type, float freq, float seconds, uint32_t amplitude){ - gpu_clear(0x1fb03f); - uint32_t period = 441/((freq/100.f) * 2);//TODO: improve this formula - kprintf("Period %i",period); - uint32_t accumulator = 0; - uint32_t increment = (uint32_t)(freq * (float)UINT32_MAX / 44100.0); - gpu_size size = gpu_get_screen_size(); - uint32_t previous_pixel = UINT32_MAX; - - //TODO: distorsion - //size of the buffer should be bigger, - //palloc should be 64 pages - //in the virtio driver, cmd_index is waiting for the device to catch up - - for (int i = 0; i < seconds * 100; i++){ +// TODO: get sample rate, channels etc. from audio driver. +// Currently ASSUMES output stream has two channels in U32 format. +const float SAMPLE_RATE = 44100.f; + + +void play_wave(WAVE_TYPE type, float freq, float seconds, uint32_t amplitude){ + uint32_t phase = 0; + // TODO: validate freqency to ensure phase_incr is sensible value. + uint32_t phase_incr = (uint32_t)(freq * (float)PHASE_MAX / SAMPLE_RATE); + uint32_t samples_remaining = ceil(SAMPLE_RATE * seconds); + + while (samples_remaining > 0){ + sizedptr buf = audio_request_buffer(audio_driver->out_dev->stream_id); + uint32_t* buffer = (uint32_t*)buf.ptr; + uint32_t samples_in_buffer = (uint32_t)min(buf.size/2, samples_remaining); + uint32_t sample = 0; + size_t slot = 0; + while (sample++ < samples_in_buffer){ + float wave = sample_raw_wave(type, phase) * amplitude; + buffer[slot++] = wave; // Left ch. + buffer[slot++] = wave; // Right ch. + phase = (phase + phase_incr) & PHASE_MASK; + } + while (slot < buf.size){ + buffer[slot++] = WAVE_MID_VALUE; + } + audio_submit_buffer(); + samples_remaining -= samples_in_buffer; + } +} + +void play_silence(float seconds){ + if (seconds < 0.01) return; + sizedptr buf = audio_request_buffer(audio_driver->out_dev->stream_id); + uint32_t buffer_count = (uint32_t)ceil(SAMPLE_RATE * seconds * 2 / buf.size); + do{ + uint32_t* buffer = (uint32_t*)buf.ptr; + size_t slot = 0; + while (slot < buf.size){ + buffer[slot++] = WAVE_MID_VALUE; + } + audio_submit_buffer(); + buf = audio_request_buffer(audio_driver->out_dev->stream_id); + } while (--buffer_count > 0); +} + +static inline uint32_t int16_to_uint32(int16_t sample, uint32_t amplitude){ + return (uint64_t)(((int64_t)sample * amplitude) >> 16) + WAVE_MID_VALUE; +} + +void play_int16_samples(int16_t *samples, size_t smpl_per_channel, size_t channels, uint32_t amplitude){ + size_t samples_remaining = smpl_per_channel * channels; + while (samples_remaining > 0){ sizedptr buf = audio_request_buffer(audio_driver->out_dev->stream_id); - - uint32_t num_samples = buf.size; - uint32_t *buffer = (uint32_t*)buf.ptr; - for (uint32_t sample = 0; sample < num_samples; sample++){ - float wave = sample_raw_wave(type, accumulator, period); - uint32_t min = 64 * num_samples; - buffer[sample] = wave * amplitude; - - accumulator += increment; - if (accumulator >= min && accumulator < min + size.width){ - gpu_point p = (gpu_point){ accumulator - min, 100-(uint32_t)(100*wave)}; - if (previous_pixel != UINT32_MAX && abs(p.y-previous_pixel) > 10){ - gpu_draw_line({ p.x - 1, previous_pixel}, p, 0xFFB4DD13); - } - previous_pixel = p.y; - gpu_draw_pixel(p, 0xFFB4DD13); - } + uint32_t* buffer = (uint32_t*)buf.ptr; + size_t samples_in_buffer = (size_t)min(buf.size, samples_remaining); + size_t slot = 0; + while (slot < samples_in_buffer){ + uint32_t sample = int16_to_uint32(*samples++, amplitude); + buffer[slot++] = sample; // Left ch. + buffer[slot++] = (channels == 1) ? sample : int16_to_uint32(*samples++, amplitude); // Right ch. + } + while (slot < buf.size){ + buffer[slot++] = WAVE_MID_VALUE; } audio_submit_buffer(); + samples_remaining -= samples_in_buffer; + } +} + +void play_startup(){ + wav_data wav = {}; + if (wav_load("/boot/redos/startup.wav", &wav)){ + // TODO: other WAV formats are available + play_int16_samples(wav.samples, wav.smpls_per_channel, wav.channels, AUDIO_LEVEL_MAX/2); + free((void*)wav.file_content.ptr, wav.file_content.size); + }else{ + play_wave(WAVE_SAW, 440, 0.1, AUDIO_LEVEL_MAX/2); + play_silence(0.05); + play_wave(WAVE_SAW, 494, 0.1, AUDIO_LEVEL_MAX/2); + play_silence(0.05); + play_wave(WAVE_SAW, 523, 0.3, AUDIO_LEVEL_MAX/2); + play_silence(0.5); } - gpu_flush(); - while(1); } -int play_test_audio(int argc, char* argv[]){ - // make_wave(WAVE_SAW, 440, 3, UINT32_MAX/4); +int audio_mixer(int argc, char* argv[]){ + play_startup(); + while (1) sleep(1000); // Idle sound process for time(!) being return 0; } process_t* init_audio_mixer(){ - return create_kernel_process("audiotest", play_test_audio, 0, 0); + return create_kernel_process("Audio out", audio_mixer, 0, 0); } driver_module audio_module = (driver_module){ diff --git a/kernel/audio/virtio_audio_pci.cpp b/kernel/audio/virtio_audio_pci.cpp index 8e97d627..f8014707 100644 --- a/kernel/audio/virtio_audio_pci.cpp +++ b/kernel/audio/virtio_audio_pci.cpp @@ -3,6 +3,7 @@ #include "console/kio.h" #include "memory/page_allocator.h" #include "std/memory_access.h" +#include "syscalls/syscalls.h" #include "audio.h" #include "std/memory.h" #include "OutputAudioDevice.hpp" @@ -26,7 +27,8 @@ #define VIRTIO_SND_PCM_RATE_44100 6 #define VIRTIO_SND_PCM_RATE_48000 7 -#define SND_44100_BUFFER_SIZE 441 +#define SND_44100_BUFFER_SIZE 256 +static_assert((SND_44100_BUFFER_SIZE & 0x01) == 0x00, "Audio buffer size must be even."); #define SND_U32_BYTES 4 #define SND_PERIOD 1 #define TOTAL_PERIOD_SIZE SND_44100_BUFFER_SIZE * SND_U32_BYTES * channels @@ -83,7 +85,7 @@ typedef struct virtio_snd_event { bool VirtioAudioDriver::init(){ uint64_t addr = find_pci_device(VIRTIO_VENDOR, VIRTIO_AUDIO_ID); if (!addr){ - kprintf("Disk device not found"); + kprintf("Audio device not found"); return false; } @@ -107,7 +109,7 @@ bool VirtioAudioDriver::init(){ pci_enable_device(addr); if (!virtio_init_device(&audio_dev)){ - kprintf("Failed disk initialization"); + kprintf("[VIRTIO_AUDIO] Failed initialization"); return false; } @@ -115,7 +117,7 @@ bool VirtioAudioDriver::init(){ audio_dev.common_cfg->queue_msix_vector = 0; if (audio_dev.common_cfg->queue_msix_vector != 0){ - kprintf("[VIRTIO_AUDIO error] failed to setup interrupts for event queue"); + kprintf("[VIRTIO_AUDIO] failed to setup interrupts for event queue"); return false; } //TODO: This should (probably) be for input devices only @@ -181,7 +183,7 @@ bool VirtioAudioDriver::config_streams(uint32_t streams){ kprintf("[VIRTIO_AUDIO] Stream %i (%s): Features %x. Format %x. Sample %x. Channels %i-%i",stream, (uintptr_t)(stream_info[stream].direction ? "IN" : "OUT"), stream_info[stream].features, format, rate, stream_info->channels_min, stream_info->channels_max); if (!(format & (1 << VIRTIO_SND_PCM_FMT_U32))){ - kprintf("[VIRTIO_AUDIO implementation error] stream does not support Float32 format"); + kprintf("[VIRTIO_AUDIO implementation error] stream does not support Uint32 format"); return false; } @@ -191,7 +193,7 @@ bool VirtioAudioDriver::config_streams(uint32_t streams){ } //TODO: Stereo - uint8_t channels = 1;//stream_info->channels_max; + uint8_t channels = 2; // 1;//stream_info->channels_max; if (!stream_set_params(stream, stream_info[stream].features, VIRTIO_SND_PCM_FMT_U32, VIRTIO_SND_PCM_RATE_44100, channels)){ kprintf("[VIRTIO_AUDIO error] Failed to configure stream %i",stream); @@ -215,6 +217,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; if (u->idx < cmd_index-20){ +//sleep(1); while (u->idx < cmd_index-5); } cmd_index++; @@ -286,7 +289,6 @@ void VirtioAudioDriver::handle_interrupt(){ select_queue(&audio_dev, EVENT_QUEUE); struct virtq_used* used = (struct virtq_used*)(uintptr_t)audio_dev.common_cfg->queue_device; struct virtq_avail* avail = (struct virtq_avail*)(uintptr_t)audio_dev.common_cfg->queue_driver; - uint16_t new_idx = used->idx; if (new_idx != last_used_idx) { uint16_t used_ring_index = last_used_idx % 128; diff --git a/shared/audio/cuatro.c b/shared/audio/cuatro.c index 977f9632..3a3ac74f 100644 --- a/shared/audio/cuatro.c +++ b/shared/audio/cuatro.c @@ -1,21 +1,21 @@ #include "cuatro.h" #include "math/math.h" -float sample_raw_wave(WAVE_TYPE type, uint32_t accumulator, uint32_t period){ - float t = ((float)accumulator/(float)UINT32_MAX); +float sample_raw_wave(WAVE_TYPE type, uint32_t phase){ switch (type) { case WAVE_TRIG: { - float trig = 2*(absf(t-floor(t + 0.5))); + float t = ((float)phase/(float)PHASE_MAX); + float trig = 2*(absf(t-floor(t + 0.5f))); return trig; } case WAVE_SAW: - return ((t-floor(t + 0.5)) + 0.5f); + return (float)(PHASE_MAX - phase) / (float)PHASE_MAX; case WAVE_SQUARE: - return (accumulator < UINT32_MAX/2) ? 0 : 1; + return (phase < PHASE_MID) ? 0.f : 1.f; } return 0; } -uint32_t sample_wave(WAVE_TYPE type, uint32_t accumulator, uint32_t period, uint32_t amplitude){ - return sample_raw_wave(type, accumulator, period) * amplitude; +uint32_t sample_wave(WAVE_TYPE type, uint32_t phase, uint32_t amplitude){ + return sample_raw_wave(type, phase) * amplitude; } \ No newline at end of file diff --git a/shared/audio/cuatro.h b/shared/audio/cuatro.h index 452c084d..c71c1667 100644 --- a/shared/audio/cuatro.h +++ b/shared/audio/cuatro.h @@ -8,11 +8,20 @@ typedef enum WAVE_TYPE { WAVE_SAW, } WAVE_TYPE; +#define PHASE_MASK 0x00FFFFFF +#define PHASE_MAX PHASE_MASK +#define PHASE_MID (PHASE_MAX >> 1) + +#define WAVE_MID_VALUE 0x80000000 + +#define AUDIO_LEVEL_MAX UINT32_MAX + + #ifdef __cplusplus extern "C" { #endif -float sample_raw_wave(WAVE_TYPE type, uint32_t accumulator, uint32_t period); -uint32_t sample_wave(WAVE_TYPE type, uint32_t accumulator, uint32_t period, uint32_t amplitude); +float sample_raw_wave(WAVE_TYPE type, uint32_t phase); +uint32_t sample_wave(WAVE_TYPE type, uint32_t phase, uint32_t amplitude); // void make_wave(WAVE_TYPE type, float freq, float seconds); #ifdef __cplusplus } diff --git a/shared/types.h b/shared/types.h index 6019b68b..4007295e 100644 --- a/shared/types.h +++ b/shared/types.h @@ -61,6 +61,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;