diff --git a/kernel/audio/OutputAudioDevice.cpp b/kernel/audio/OutputAudioDevice.cpp index 99dca660..32bcadb2 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); @@ -20,4 +20,4 @@ void OutputAudioDevice::submit_buffer(AudioDriver *driver){ driver->send_buffer((sizedptr){buffer + read_ptr, packet_size}); read_ptr += packet_size; if (read_ptr + packet_size >= BUF_SIZE) read_ptr = 0; -} \ No newline at end of file +} diff --git a/kernel/audio/audio.cpp b/kernel/audio/audio.cpp index 526ea5f9..b96f54e2 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; @@ -22,47 +23,99 @@ 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/50.f); - 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; - - 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 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; + 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; + } +} - 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); - } +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 smpls_per_channel, size_t channels, uint32_t amplitude){ + size_t samples_remaining = smpls_per_channel * channels; + while (samples_remaining > 0){ + sizedptr buf = audio_request_buffer(audio_driver->out_dev->stream_id); + 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 / (3 - channels); // TODO: There must be a better way... + } +} + +void play_startup(){ + wav_data wav = {}; + if (wav_load_as_int16("/boot/redos/startup.wav", &wav)){ + play_int16_samples((int16_t*)wav.samples.ptr, wav.smpls_per_channel, wav.channels, AUDIO_LEVEL_MAX/2); + free((void*)wav.samples.ptr, wav.samples.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){ @@ -76,4 +129,4 @@ driver_module audio_module = (driver_module){ .write = 0, .seek = 0, .readdir = 0, -}; \ No newline at end of file +}; diff --git a/kernel/audio/virtio_audio_pci.cpp b/kernel/audio/virtio_audio_pci.cpp index 55f3ccba..3492a7af 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; } @@ -95,7 +97,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; } @@ -103,7 +105,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 @@ -169,7 +171,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; } @@ -179,7 +181,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); diff --git a/shared/audio/cuatro.c b/shared/audio/cuatro.c index 977f9632..66527c07 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; -} \ No newline at end of file +uint32_t sample_wave(WAVE_TYPE type, uint32_t phase, uint32_t amplitude){ + return sample_raw_wave(type, phase) * amplitude; +} diff --git a/shared/audio/cuatro.h b/shared/audio/cuatro.h index 452c084d..1601bccc 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/audio/wav.c b/shared/audio/wav.c new file mode 100644 index 00000000..86a1f16a --- /dev/null +++ b/shared/audio/wav.c @@ -0,0 +1,130 @@ +#include "types.h" +#include "filesystem/filesystem.h" +#include "memory/talloc.h" +#include "std/memory.h" +#include "math/math.h" +#include "console/kio.h" +#include "wav.h" +#include "syscalls/syscalls.h" + + +// TODO: Handle non-trivial wav headers and other sample formats: +// https://web.archive.org/web/20100325183246/http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html + +typedef struct wav_header { + uint32_t id; + uint32_t fSize; + uint32_t wave_id; + uint32_t format_id; + uint32_t format_size; + uint16_t format; + uint16_t channels; + uint32_t sample_rate; + uint32_t idk; + uint16_t align; + uint16_t sample_bits; + uint32_t data_id; + uint32_t data_size; +}__attribute__((packed)) wav_header; + + +void transform_16bit(wav_header *hdr, wav_data *wav, uint32_t upsample, file *fd){ + wav->samples.size = hdr->data_size * upsample; + wav->samples.ptr = (uintptr_t)malloc(wav->samples.size); + int16_t* tbuf = (int16_t*)malloc(hdr->data_size); + read_file(fd, (char*)tbuf, hdr->data_size); + wav->smpls_per_channel = (wav->samples.size) / (sizeof(int16_t) * hdr->channels); + wav->channels = hdr->channels; + uint32_t samples_remaining = hdr->data_size / sizeof(int16_t); + int16_t* source = tbuf; + int16_t* dest = (int16_t*)wav->samples.ptr; + while (samples_remaining-- > 0){ + for (int i = upsample; i > 0; i--){ + *dest++ = *source; // TODO: interpolate + } + ++source; + } + free(tbuf, hdr->data_size); +} + +void transform_8bit(wav_header *hdr, wav_data *wav, uint32_t upsample, file *fd){ + uint8_t* tbuf = (int8_t*)malloc(hdr->data_size); + read_file(fd, (char*)tbuf, hdr->data_size); + wav->samples.size = hdr->data_size * upsample * 2; + wav->samples.ptr = (uintptr_t)malloc(wav->samples.size); + wav->smpls_per_channel = wav->samples.size / (sizeof(int16_t) * hdr->channels); + wav->channels = hdr->channels; + uint32_t samples_remaining = hdr->data_size; + uint8_t* source = tbuf; + int16_t* dest = (int16_t*)wav->samples.ptr; + while (samples_remaining-- > 0){ + int16_t sample = (int16_t)((*source++ - 128) * 256); // offset binary to signed + for (int i = upsample; i > 0; i--){ + *dest++ = sample; // TODO: interpolate + } + } + free(tbuf, hdr->data_size); +} + +bool wav_load_as_int16(const char*path, wav_data *wav){ + file fd = {}; + FS_RESULT result = open_file(path, &fd); + + if (result != FS_RESULT_SUCCESS) + { + kprintf("[WAV] File not found: %s", path); + return false; + } + + wav_header hdr = {}; + size_t read_size = read_file(&fd, (char*)&hdr, sizeof(wav_header)); + if (read_size != sizeof(wav_header) || + hdr.id != (uint32_t)'FFIR' || + hdr.wave_id != (uint32_t)'EVAW' || + hdr.format != 1 || + hdr.channels < 1 || hdr.channels > 2 || + hdr.sample_rate > 44100 || + (44100 % hdr.sample_rate != 0) || + (hdr.sample_bits != 8 && hdr.sample_bits != 16) || + hdr.data_id != (uint32_t)'atad' || + fd.size < hdr.data_size + sizeof(wav_header) || + hdr.data_size == 0 + ) + { + // close_file(&fd) + kprintf("[WAV] Unsupported file format %s", path); + kprintf("=== Sizes %i, %i, %i", read_size, fd.size, hdr.data_size); + kprintf("=== id %x", hdr.id); + kprintf("=== wave id %x", hdr.wave_id); + kprintf("=== format %x", hdr.format_id); + kprintf("=== channels %i", hdr.channels); + kprintf("=== sample rate %i", hdr.sample_rate); + kprintf("=== sample_bits %i", hdr.sample_bits); + kprintf("=== data id %x", hdr.data_id); + return false; + } + + uint32_t upsample = 44100 / hdr.sample_rate; // for up-sampling + + if (hdr.sample_bits == 16 && upsample == 1){ + // simple case: slurp samples direct from file to wav buffer + wav->samples.size = hdr.data_size; + wav->samples.ptr = (uintptr_t)malloc(wav->samples.size); + read_file(&fd, (char*)wav->samples.ptr, wav->samples.size); + wav->smpls_per_channel = hdr.data_size / (sizeof(int16_t) * hdr.channels); + wav->channels = hdr.channels; + }else if (hdr.sample_bits == 16){ + transform_16bit(&hdr, wav, upsample, &fd); + }else if (hdr.sample_bits == 8){ + transform_8bit(&hdr, wav, upsample, &fd); + }else{ + //close_file(&fd); + return false; + } + //close_file(&fd); +kprintf("===Samples size %i", wav->samples.size); +kprintf("===Per channel %i", wav->smpls_per_channel); +kprintf("===Channels %i", wav->channels); + return true; +} + diff --git a/shared/audio/wav.h b/shared/audio/wav.h new file mode 100644 index 00000000..7dab3d2a --- /dev/null +++ b/shared/audio/wav.h @@ -0,0 +1,18 @@ +#pragma once + +typedef struct wav_data { + uint32_t channels; + uint32_t smpls_per_channel; + sizedptr samples; +} wav_data; + + +#ifdef __cplusplus +extern "C" { +#endif + +bool wav_load_as_int16(const char*path, wav_data *wav); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/shared/types.h b/shared/types.h index 6019b68b..27c06c35 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;