/* SPDX-License-Identifier: MIT */ /* * konaudio.c * * Copyright (c) 2026 KonAki * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "konaudio.h" #include #include #include #include "miniaudio.h" /*** types ***/ typedef struct kon_slot { const kon_sound_t *sound; double position; double step; kon_audioParams_t p; float curL, curR; float fade, fadeTarget, fadeStep; int fadeStop; int fresh; int active; uint32_t generation; } kon_slot_t; struct kon_audio { kon_slot_t slots[KON_AUDIO_MAX_PLAYING]; float master, masterCur; float bus[KON_AUDIO_BUSES]; float lx, ly, lz; float rx, ry, rz; float peak; kon_audioHandle_t music; const kon_sound_t *musicSound; uint32_t rate; int hasDevice; ma_mutex lock; ma_device device; }; /*** setup ***/ static void kon_audioCallback_(ma_device *device, void *out, const void *in, ma_uint32 frames) { kon_audio_t *audio = device->pUserData; (void)in; kon_audioMix(audio, out, frames); } static kon_audio_t *kon_audioNew_(uint32_t sampleRate) { kon_audio_t *audio = calloc(1, sizeof(kon_audio_t)); int i; if (!audio) return NULL; if (ma_mutex_init(&audio->lock) != MA_SUCCESS) { free(audio); return NULL; } audio->master = audio->masterCur = 1.0f; for (i = 0; i < KON_AUDIO_BUSES; i++) audio->bus[i] = 1.0f; audio->rx = 1.0f; audio->rate = sampleRate ? sampleRate : 48000; return audio; } kon_audio_t *kon_audioCreateOffline(uint32_t sampleRate) { return kon_audioNew_(sampleRate); } kon_audio_t *kon_audioCreate(uint32_t sampleRate) { kon_audio_t *audio = kon_audioNew_(sampleRate); ma_device_config config; if (!audio) return NULL; config = ma_device_config_init(ma_device_type_playback); config.playback.format = ma_format_f32; config.playback.channels = 2; config.sampleRate = audio->rate; config.dataCallback = kon_audioCallback_; config.pUserData = audio; if (ma_device_init(NULL, &config, &audio->device) != MA_SUCCESS) { ma_mutex_uninit(&audio->lock); free(audio); return NULL; } audio->rate = audio->device.sampleRate; audio->hasDevice = 1; if (ma_device_start(&audio->device) != MA_SUCCESS) { kon_audioDestroy(audio); return NULL; } return audio; } void kon_audioDestroy(kon_audio_t *audio) { if (!audio) return; if (audio->hasDevice) ma_device_uninit(&audio->device); ma_mutex_uninit(&audio->lock); free(audio); } uint32_t kon_audioRate(const kon_audio_t *audio) { return audio->rate; } /*** playing ***/ kon_audioParams_t kon_audioParamsDefault(void) { kon_audioParams_t p; memset(&p, 0, sizeof(p)); p.volume = 1.0f; p.pitch = 1.0f; p.bus = KON_BUS_SFX; p.minDistance = 1.0f; p.maxDistance = 20.0f; return p; } static float kon_clampf_(float x, float lo, float hi) { return x < lo ? lo : (x > hi ? hi : x); } static kon_slot_t *kon_find_(kon_audio_t *audio, kon_audioHandle_t handle) { uint32_t index = handle & 0xFF; kon_slot_t *s; if (index == 0 || index > KON_AUDIO_MAX_PLAYING) return NULL; s = &audio->slots[index - 1]; if (!s->active || s->generation != (handle >> 8)) return NULL; return s; } /* lock has to be held */ static kon_audioHandle_t kon_start_(kon_audio_t *audio, const kon_sound_t *sound, const kon_audioParams_t *params) { int i; if (!sound || !sound->frames || sound->frameCount == 0 || sound->channels == 0 || sound->sampleRate == 0) return 0; for (i = 0; i < KON_AUDIO_MAX_PLAYING; i++) { kon_slot_t *s = &audio->slots[i]; if (s->active) continue; s->generation++; if (s->generation == 0) s->generation = 1; s->sound = sound; s->position = 0.0; s->p = *params; s->p.volume = s->p.volume < 0.0f ? 0.0f : s->p.volume; s->p.pan = kon_clampf_(s->p.pan, -1.0f, 1.0f); s->p.pitch = kon_clampf_(s->p.pitch, 0.05f, 8.0f); if (s->p.bus < 0 || s->p.bus >= KON_AUDIO_BUSES) s->p.bus = KON_BUS_SFX; if (s->p.loopStart >= sound->frameCount) s->p.loopStart = 0; if (s->p.loopEnd > sound->frameCount || s->p.loopEnd <= s->p.loopStart) s->p.loopEnd = 0; if (s->p.minDistance < 0.0f) s->p.minDistance = 0.0f; if (s->p.maxDistance <= s->p.minDistance) s->p.maxDistance = s->p.minDistance + 0.001f; s->step = (double)sound->sampleRate / (double)audio->rate; s->fade = s->fadeTarget = 1.0f; s->fadeStep = 0.0f; s->fadeStop = 0; s->fresh = 1; s->active = 1; return (s->generation << 8) | (uint32_t)(i + 1); } return 0; } kon_audioHandle_t kon_audioPlayEx(kon_audio_t *audio, const kon_sound_t *sound, const kon_audioParams_t *params) { kon_audioParams_t defaults = kon_audioParamsDefault(); kon_audioHandle_t handle; if (!audio) return 0; ma_mutex_lock(&audio->lock); handle = kon_start_(audio, sound, params ? params : &defaults); ma_mutex_unlock(&audio->lock); return handle; } kon_audioHandle_t kon_audioPlay(kon_audio_t *audio, const kon_sound_t *sound, float volume, float pan, int loop) { kon_audioParams_t p = kon_audioParamsDefault(); p.volume = volume; p.pan = pan; p.loop = loop; return kon_audioPlayEx(audio, sound, &p); } kon_audioHandle_t kon_audioPlayAt(kon_audio_t *audio, const kon_sound_t *sound, float volume, float x, float y, float z) { kon_audioParams_t p = kon_audioParamsDefault(); p.volume = volume; p.positional = 1; p.x = x; p.y = y; p.z = z; return kon_audioPlayEx(audio, sound, &p); } void kon_audioStop(kon_audio_t *audio, kon_audioHandle_t handle) { kon_slot_t *s; ma_mutex_lock(&audio->lock); s = kon_find_(audio, handle); if (s) s->active = 0; ma_mutex_unlock(&audio->lock); } void kon_audioStopSound(kon_audio_t *audio, const kon_sound_t *sound) { int i; ma_mutex_lock(&audio->lock); for (i = 0; i < KON_AUDIO_MAX_PLAYING; i++) { if (audio->slots[i].active && audio->slots[i].sound == sound) audio->slots[i].active = 0; } ma_mutex_unlock(&audio->lock); } void kon_audioStopAll(kon_audio_t *audio) { int i; ma_mutex_lock(&audio->lock); for (i = 0; i < KON_AUDIO_MAX_PLAYING; i++) audio->slots[i].active = 0; ma_mutex_unlock(&audio->lock); } int kon_audioIsPlaying(kon_audio_t *audio, kon_audioHandle_t handle) { int playing; ma_mutex_lock(&audio->lock); playing = kon_find_(audio, handle) != NULL; ma_mutex_unlock(&audio->lock); return playing; } int kon_audioPlayingCount(kon_audio_t *audio) { int i, count = 0; ma_mutex_lock(&audio->lock); for (i = 0; i < KON_AUDIO_MAX_PLAYING; i++) count += audio->slots[i].active; ma_mutex_unlock(&audio->lock); return count; } void kon_audioHandleSetVolume(kon_audio_t *audio, kon_audioHandle_t handle, float volume) { kon_slot_t *s; ma_mutex_lock(&audio->lock); s = kon_find_(audio, handle); if (s) s->p.volume = volume < 0.0f ? 0.0f : volume; ma_mutex_unlock(&audio->lock); } void kon_audioHandleSetPan(kon_audio_t *audio, kon_audioHandle_t handle, float pan) { kon_slot_t *s; ma_mutex_lock(&audio->lock); s = kon_find_(audio, handle); if (s) s->p.pan = kon_clampf_(pan, -1.0f, 1.0f); ma_mutex_unlock(&audio->lock); } void kon_audioHandleSetPitch(kon_audio_t *audio, kon_audioHandle_t handle, float pitch) { kon_slot_t *s; ma_mutex_lock(&audio->lock); s = kon_find_(audio, handle); if (s) s->p.pitch = kon_clampf_(pitch, 0.05f, 8.0f); ma_mutex_unlock(&audio->lock); } void kon_audioHandleSetPosition(kon_audio_t *audio, kon_audioHandle_t handle, float x, float y, float z) { kon_slot_t *s; ma_mutex_lock(&audio->lock); s = kon_find_(audio, handle); if (s) { s->p.positional = 1; s->p.x = x; s->p.y = y; s->p.z = z; } ma_mutex_unlock(&audio->lock); } /* lock has to be held */ static void kon_fade_(kon_audio_t *audio, kon_slot_t *s, float target, float seconds, int stopAtEnd) { float frames = seconds * (float)audio->rate; target = kon_clampf_(target, 0.0f, 1.0f); s->fadeTarget = target; s->fadeStop = stopAtEnd; if (frames < 1.0f) { s->fade = target; s->fadeStep = 0.0f; if (stopAtEnd) s->active = 0; return; } s->fadeStep = (target - s->fade) / frames; if (s->fadeStep == 0.0f && stopAtEnd) s->active = 0; } void kon_audioHandleFade(kon_audio_t *audio, kon_audioHandle_t handle, float target, float seconds, int stopAtEnd) { kon_slot_t *s; ma_mutex_lock(&audio->lock); s = kon_find_(audio, handle); if (s) kon_fade_(audio, s, target, seconds, stopAtEnd); ma_mutex_unlock(&audio->lock); } /*** music ***/ kon_audioHandle_t kon_audioMusic(kon_audio_t *audio, const kon_sound_t *sound, const kon_audioParams_t *params, float fadeSeconds) { kon_audioParams_t p = params ? *params : kon_audioParamsDefault(); kon_slot_t *old, *s; kon_audioHandle_t handle; ma_mutex_lock(&audio->lock); old = kon_find_(audio, audio->music); if (old && audio->musicSound == sound && !(old->fadeStop && old->fadeTarget == 0.0f)) { handle = audio->music; ma_mutex_unlock(&audio->lock); return handle; } p.loop = 1; p.bus = KON_BUS_MUSIC; p.positional = 0; handle = kon_start_(audio, sound, &p); if (!handle) { ma_mutex_unlock(&audio->lock); return 0; } if (old) kon_fade_(audio, old, 0.0f, fadeSeconds, 1); s = kon_find_(audio, handle); if (fadeSeconds > 0.0f) { s->fade = 0.0f; kon_fade_(audio, s, 1.0f, fadeSeconds, 0); } audio->music = handle; audio->musicSound = sound; ma_mutex_unlock(&audio->lock); return handle; } void kon_audioMusicStop(kon_audio_t *audio, float fadeSeconds) { kon_slot_t *s; ma_mutex_lock(&audio->lock); s = kon_find_(audio, audio->music); if (s) kon_fade_(audio, s, 0.0f, fadeSeconds, 1); audio->music = 0; audio->musicSound = NULL; ma_mutex_unlock(&audio->lock); } const kon_sound_t *kon_audioMusicCurrent(kon_audio_t *audio) { const kon_sound_t *sound = NULL; kon_slot_t *s; ma_mutex_lock(&audio->lock); s = kon_find_(audio, audio->music); if (s && !(s->fadeStop && s->fadeTarget == 0.0f)) sound = audio->musicSound; ma_mutex_unlock(&audio->lock); return sound; } /*** mixing ***/ void kon_audioSetVolume(kon_audio_t *audio, float volume) { if (volume < 0.0f) volume = 0.0f; ma_mutex_lock(&audio->lock); audio->master = volume; ma_mutex_unlock(&audio->lock); } void kon_audioSetBusVolume(kon_audio_t *audio, int bus, float volume) { if (bus < 0 || bus >= KON_AUDIO_BUSES) return; ma_mutex_lock(&audio->lock); audio->bus[bus] = volume < 0.0f ? 0.0f : volume; ma_mutex_unlock(&audio->lock); } void kon_audioSetListener(kon_audio_t *audio, float x, float y, float z, float fx, float fy, float fz, float ux, float uy, float uz) { float rx = fy * uz - fz * uy; float ry = fz * ux - fx * uz; float rz = fx * uy - fy * ux; float len = sqrtf(rx * rx + ry * ry + rz * rz); if (len < 1e-6f) { rx = 1.0f; ry = rz = 0.0f; } else { rx /= len; ry /= len; rz /= len; } ma_mutex_lock(&audio->lock); audio->lx = x; audio->ly = y; audio->lz = z; audio->rx = rx; audio->ry = ry; audio->rz = rz; ma_mutex_unlock(&audio->lock); } float kon_audioPeak(kon_audio_t *audio) { float peak; ma_mutex_lock(&audio->lock); peak = audio->peak; ma_mutex_unlock(&audio->lock); return peak; } /* distance falloff and pan for one positional sound */ static void kon_spatial_(const kon_audio_t *audio, const kon_audioParams_t *p, float *gain, float *pan) { float dx = p->x - audio->lx, dy = p->y - audio->ly, dz = p->z - audio->lz; float dist = sqrtf(dx * dx + dy * dy + dz * dz); float t; if (dist <= p->minDistance) { t = 0.0f; } else if (dist >= p->maxDistance) { t = 1.0f; } else { t = (dist - p->minDistance) / (p->maxDistance - p->minDistance); } *gain = (1.0f - t) * (1.0f - t); if (dist < 1e-4f) { *pan = 0.0f; return; } *pan = (dx * audio->rx + dy * audio->ry + dz * audio->rz) / dist; /* right on top of the listener there is no side to be on */ if (dist < p->minDistance && p->minDistance > 0.0f) *pan *= dist / p->minDistance; } void kon_audioMix(kon_audio_t *audio, float *out, uint32_t frames) { uint32_t f; int i; float peak = 0.0f; float invFrames = frames ? 1.0f / (float)frames : 0.0f; memset(out, 0, (size_t)frames * 2 * sizeof(float)); ma_mutex_lock(&audio->lock); for (i = 0; i < KON_AUDIO_MAX_PLAYING; i++) { kon_slot_t *v = &audio->slots[i]; const kon_sound_t *s = v->sound; const kon_audioParams_t *p = &v->p; uint64_t end, loopLen; float vol, pan, targetL, targetR, gl, gr, dgl, dgr; double step; if (!v->active) continue; vol = p->volume * audio->bus[p->bus]; pan = p->pan; if (p->positional) { float g; kon_spatial_(audio, p, &g, &pan); vol *= g; } targetL = vol * (pan > 0.0f ? 1.0f - pan : 1.0f); targetR = vol * (pan < 0.0f ? 1.0f + pan : 1.0f); if (v->fresh) { v->curL = targetL; v->curR = targetR; v->fresh = 0; } gl = v->curL; gr = v->curR; dgl = (targetL - gl) * invFrames; dgr = (targetR - gr) * invFrames; step = v->step * p->pitch; end = p->loop && p->loopEnd ? p->loopEnd : s->frameCount; loopLen = end - p->loopStart; for (f = 0; f < frames; f++) { uint64_t a = (uint64_t)v->position, b; float t, l0, r0, l1, r1, fade = v->fade; const int16_t *q; if (a >= end) { if (!p->loop) { v->active = 0; break; } v->position -= (double)loopLen * (double)((uint64_t)((v->position - (double)p->loopStart) / (double)loopLen)); a = (uint64_t)v->position; } b = a + 1; if (b >= end) b = p->loop ? p->loopStart : a; t = (float)(v->position - (double)a); q = s->frames + a * s->channels; l0 = q[0] / 32768.0f; r0 = (s->channels > 1 ? q[1] : q[0]) / 32768.0f; q = s->frames + b * s->channels; l1 = q[0] / 32768.0f; r1 = (s->channels > 1 ? q[1] : q[0]) / 32768.0f; gl += dgl; gr += dgr; out[f * 2] += (l0 + (l1 - l0) * t) * gl * fade; out[f * 2 + 1] += (r0 + (r1 - r0) * t) * gr * fade; v->position += step; if (v->fadeStep != 0.0f) { v->fade += v->fadeStep; if ((v->fadeStep > 0.0f && v->fade >= v->fadeTarget) || (v->fadeStep < 0.0f && v->fade <= v->fadeTarget)) { v->fade = v->fadeTarget; v->fadeStep = 0.0f; if (v->fadeStop) { v->active = 0; break; } } } } v->curL = targetL; v->curR = targetR; } { float g = audio->masterCur; float dg = (audio->master - g) * invFrames; for (f = 0; f < frames; f++) { float x; g += dg; x = out[f * 2] * g; out[f * 2] = x > 1.0f ? 1.0f : (x < -1.0f ? -1.0f : x); x = out[f * 2 + 1] * g; out[f * 2 + 1] = x > 1.0f ? 1.0f : (x < -1.0f ? -1.0f : x); } audio->masterCur = audio->master; } for (f = 0; f < frames * 2; f++) { float x = out[f] < 0.0f ? -out[f] : out[f]; if (x > peak) peak = x; } audio->peak = peak; ma_mutex_unlock(&audio->lock); }