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 6f21aa83..24d0b543 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,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); } @@ -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; } @@ -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: 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 db2e9cd1..b9317770 100644 --- a/kernel/kernel_processes/boot/bootscreen.c +++ b/kernel/kernel_processes/boot/bootscreen.c @@ -94,41 +94,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); } } @@ -155,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 +} diff --git a/kernel/process/syscall.c b/kernel/process/syscall.c index 147fe0ad..9d59884d 100644 --- a/kernel/process/syscall.c +++ b/kernel/process/syscall.c @@ -188,7 +188,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 4e9e6dae..cb47e951 100644 --- a/shared/audio/cuatro.c +++ b/shared/audio/cuatro.c @@ -2,67 +2,45 @@ #include "files/fs.h" #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; -} - - static file mixer = { .id = 0 }; // 0 ok as filesystem ids > 256 -static bool mixer_open_file(){ - if (mixer.id == 0 && FS_RESULT_SUCCESS != fopen("/audio/output", &mixer)) return false; - return true; +static inline bool mixer_open_file(){ + return (mixer.id > 0) || (FS_RESULT_SUCCESS == fopen("/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 }; + if (mixer_open_file() && lineId >= 0){ + mixer_command command = { .lineId = lineId, .command = MIXER_CLOSE_LINE }; fwrite(&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) == fread(&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,7 +48,7 @@ bool mixer_still_playing(int8_t lineId){ bool mixer_mute(){ if (mixer_open_file()){ - mixer_command command = { -1, MIXER_MUTE, .value=0 }; + mixer_command command = { .lineId = -1, .command = MIXER_MUTE }; fwrite(&mixer, (char*)&command, sizeof(mixer_command)); } return false; // TODO: return prev setting @@ -78,16 +56,59 @@ bool mixer_mute(){ bool mixer_unmute(){ if (mixer_open_file()){ - mixer_command command = { -1, MIXER_UNMUTE, .value=0 }; + mixer_command command = { .lineId = -1, .command = MIXER_UNMUTE }; fwrite(&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 = { .lineId = -1, .command = MIXER_SETLEVEL, .level = level }; + fwrite(&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 = { -1, MIXER_SETLEVEL, .value = (uintptr_t)level }; + mixer_command command = { .lineId = lineId, .command = MIXER_SETLEVEL, .level = level, .pan = pan }; 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) == 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){ + 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) == fwrite(&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 e3a00a10..5b14f047 100644 --- a/shared/types.h +++ b/shared/types.h @@ -64,7 +64,7 @@ typedef signed long intptr_t; typedef signed short int16_t; typedef signed char int8_t; -#define INT16_MAX 0x7FFF +#define INT16_MAX 0x7FFF typedef struct sizedptr { uintptr_t ptr; diff --git a/user/default_process.c b/user/default_process.c index cf25abae..8c8aac91 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");