diff options
Diffstat (limited to 'kon/konaudio.c')
| -rw-r--r-- | kon/konaudio.c | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/kon/konaudio.c b/kon/konaudio.c new file mode 100644 index 0000000..94fb811 --- /dev/null +++ b/kon/konaudio.c @@ -0,0 +1,277 @@ +/* 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 <stdlib.h> +#include <string.h> + +#include "miniaudio.h" + +typedef struct kon_voiceSlot { + const kon_sound_t *sound; + double position; + double step; + float volume; + float pan; + int loop; + int active; + uint32_t generation; +} kon_voiceSlot_t; + +struct kon_audio { + kon_voiceSlot_t voices[KON_AUDIO_VOICES]; + float master; + uint32_t rate; + int hasDevice; + ma_mutex lock; + ma_device device; +}; + +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)); + + if (!audio) return NULL; + if (ma_mutex_init(&audio->lock) != MA_SUCCESS) { + free(audio); + return NULL; + } + audio->master = 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; +} + +kon_voice_t kon_audioPlay(kon_audio_t *audio, const kon_sound_t *sound, float volume, float pan, int loop) +{ + kon_voice_t handle = 0; + int i; + + if (!audio || !sound || !sound->frames || sound->frameCount == 0 || sound->channels == 0 || sound->sampleRate == 0) return 0; + + if (pan < -1.0f) pan = -1.0f; + if (pan > 1.0f) pan = 1.0f; + + ma_mutex_lock(&audio->lock); + for (i = 0; i < KON_AUDIO_VOICES; i++) { + kon_voiceSlot_t *v = &audio->voices[i]; + + if (v->active) continue; + v->generation++; + if (v->generation == 0) v->generation = 1; + v->sound = sound; + v->position = 0.0; + v->step = (double)sound->sampleRate / (double)audio->rate; + v->volume = volume; + v->pan = pan; + v->loop = loop; + v->active = 1; + handle = (v->generation << 8) | (uint32_t)(i + 1); + break; + } + ma_mutex_unlock(&audio->lock); + return handle; +} + +static kon_voiceSlot_t *kon_audioFind_(kon_audio_t *audio, kon_voice_t handle) +{ + uint32_t index = handle & 0xFF; + kon_voiceSlot_t *v; + + if (index == 0 || index > KON_AUDIO_VOICES) return NULL; + v = &audio->voices[index - 1]; + if (!v->active || v->generation != (handle >> 8)) return NULL; + return v; +} + +void kon_audioStop(kon_audio_t *audio, kon_voice_t voice) +{ + kon_voiceSlot_t *v; + + ma_mutex_lock(&audio->lock); + v = kon_audioFind_(audio, voice); + if (v) v->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_VOICES; i++) { + if (audio->voices[i].active && audio->voices[i].sound == sound) audio->voices[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_VOICES; i++) audio->voices[i].active = 0; + ma_mutex_unlock(&audio->lock); +} + +int kon_audioIsPlaying(kon_audio_t *audio, kon_voice_t voice) +{ + int playing; + + ma_mutex_lock(&audio->lock); + playing = kon_audioFind_(audio, voice) != NULL; + ma_mutex_unlock(&audio->lock); + return playing; +} + +int kon_audioVoiceCount(kon_audio_t *audio) +{ + int i, count = 0; + + ma_mutex_lock(&audio->lock); + for (i = 0; i < KON_AUDIO_VOICES; i++) count += audio->voices[i].active; + ma_mutex_unlock(&audio->lock); + return count; +} + +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_audioMix(kon_audio_t *audio, float *out, uint32_t frames) +{ + uint32_t f; + int i; + + memset(out, 0, (size_t)frames * 2 * sizeof(float)); + + ma_mutex_lock(&audio->lock); + for (i = 0; i < KON_AUDIO_VOICES; i++) { + kon_voiceSlot_t *v = &audio->voices[i]; + const kon_sound_t *s = v->sound; + float gainL, gainR; + + if (!v->active) continue; + + gainL = v->volume * (v->pan > 0.0f ? 1.0f - v->pan : 1.0f); + gainR = v->volume * (v->pan < 0.0f ? 1.0f + v->pan : 1.0f); + + for (f = 0; f < frames; f++) { + uint64_t a = (uint64_t)v->position, b; + float t, l0, r0, l1, r1; + const int16_t *p; + + if (a >= s->frameCount) { + if (!v->loop) { + v->active = 0; + break; + } + v->position -= (double)s->frameCount; + a = (uint64_t)v->position; + } + b = a + 1; + if (b >= s->frameCount) b = v->loop ? 0 : a; + t = (float)(v->position - (double)a); + + p = s->frames + a * s->channels; + l0 = p[0] / 32768.0f; + r0 = (s->channels > 1 ? p[1] : p[0]) / 32768.0f; + p = s->frames + b * s->channels; + l1 = p[0] / 32768.0f; + r1 = (s->channels > 1 ? p[1] : p[0]) / 32768.0f; + + out[f * 2] += (l0 + (l1 - l0) * t) * gainL; + out[f * 2 + 1] += (r0 + (r1 - r0) * t) * gainR; + v->position += v->step; + } + } + + for (f = 0; f < frames * 2; f++) { + float x = out[f] * audio->master; + + out[f] = x > 1.0f ? 1.0f : (x < -1.0f ? -1.0f : x); + } + ma_mutex_unlock(&audio->lock); +} |
