Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion kernel/audio/OutputAudioDevice.cpp
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
217 changes: 126 additions & 91 deletions kernel/audio/audio.cpp
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -29,109 +31,125 @@ 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];

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->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;
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)));
}

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;
}

#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 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);
}

Expand Down Expand Up @@ -170,35 +188,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;
}
Expand All @@ -216,13 +234,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:
Expand Down
22 changes: 22 additions & 0 deletions kernel/audio/mixer.h
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion kernel/audio/virtio_audio_pci.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
}

Expand Down
Loading