summaryrefslogtreecommitdiff
path: root/kon/konaudio.c
diff options
context:
space:
mode:
Diffstat (limited to 'kon/konaudio.c')
-rw-r--r--kon/konaudio.c277
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);
+}