/* SPDX-License-Identifier: MIT */ /* * konmic.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 "konmic.h" #include #include #include #include "miniaudio.h" /*** types ***/ struct kon_mic { float *ring; uint32_t size; uint32_t head; uint64_t total; float gain; float level; float threshold; float hold; float holdLeft; int muted; uint32_t rate; int hasDevice; ma_mutex lock; ma_device device; }; /*** setup ***/ static void kon_micCallback_(ma_device *device, void *out, const void *in, ma_uint32 frames) { (void)out; kon_micFeed(device->pUserData, in, frames); } static kon_mic_t *kon_micNew_(uint32_t sampleRate, float historySeconds) { kon_mic_t *mic = calloc(1, sizeof(kon_mic_t)); if (!mic) return NULL; mic->rate = sampleRate ? sampleRate : 48000; if (historySeconds < 0.1f) historySeconds = 0.1f; if (historySeconds > 600.0f) historySeconds = 600.0f; mic->size = (uint32_t)(historySeconds * (float)mic->rate); mic->ring = calloc(mic->size, sizeof(float)); if (!mic->ring || ma_mutex_init(&mic->lock) != MA_SUCCESS) { free(mic->ring); free(mic); return NULL; } mic->gain = 1.0f; mic->threshold = 0.02f; mic->hold = 0.25f; return mic; } kon_mic_t *kon_micCreateOffline(uint32_t sampleRate, float historySeconds) { return kon_micNew_(sampleRate, historySeconds); } kon_mic_t *kon_micCreate(uint32_t sampleRate, float historySeconds) { kon_mic_t *mic = kon_micNew_(sampleRate, historySeconds); ma_device_config config; if (!mic) return NULL; config = ma_device_config_init(ma_device_type_capture); config.capture.format = ma_format_f32; config.capture.channels = 1; config.sampleRate = mic->rate; config.dataCallback = kon_micCallback_; config.pUserData = mic; if (ma_device_init(NULL, &config, &mic->device) != MA_SUCCESS) { ma_mutex_uninit(&mic->lock); free(mic->ring); free(mic); return NULL; } mic->rate = mic->device.sampleRate; mic->hasDevice = 1; if (ma_device_start(&mic->device) != MA_SUCCESS) { kon_micDestroy(mic); return NULL; } return mic; } void kon_micDestroy(kon_mic_t *mic) { if (!mic) return; if (mic->hasDevice) ma_device_uninit(&mic->device); ma_mutex_uninit(&mic->lock); free(mic->ring); free(mic); } uint32_t kon_micRate(const kon_mic_t *mic) { return mic->rate; } /*** input ***/ void kon_micFeed(kon_mic_t *mic, const float *samples, uint32_t count) { uint32_t i; double sum = 0.0; float rms, dt, tau; if (!mic || !samples || count == 0) return; ma_mutex_lock(&mic->lock); for (i = 0; i < count; i++) { float x = mic->muted ? 0.0f : samples[i] * mic->gain; if (x > 1.0f) x = 1.0f; if (x < -1.0f) x = -1.0f; mic->ring[mic->head] = x; mic->head = mic->head + 1 == mic->size ? 0 : mic->head + 1; sum += (double)x * (double)x; } mic->total += count; rms = (float)sqrt(sum / (double)count); dt = (float)count / (float)mic->rate; tau = rms > mic->level ? 0.02f : 0.15f; mic->level += (rms - mic->level) * (1.0f - expf(-dt / tau)); if (mic->level < 1e-5f) mic->level = 0.0f; if (rms >= mic->threshold && rms > 0.0f) { mic->holdLeft = mic->hold; } else { mic->holdLeft -= dt; if (mic->holdLeft < 0.0f) mic->holdLeft = 0.0f; } if (mic->muted) { mic->level = 0.0f; mic->holdLeft = 0.0f; } ma_mutex_unlock(&mic->lock); } void kon_micSetGain(kon_mic_t *mic, float gain) { ma_mutex_lock(&mic->lock); mic->gain = gain < 0.0f ? 0.0f : gain; ma_mutex_unlock(&mic->lock); } void kon_micMute(kon_mic_t *mic, int muted) { ma_mutex_lock(&mic->lock); mic->muted = muted != 0; ma_mutex_unlock(&mic->lock); } void kon_micSetTalking(kon_mic_t *mic, float threshold, float holdSeconds) { ma_mutex_lock(&mic->lock); mic->threshold = threshold < 0.0f ? 0.0f : threshold; mic->hold = holdSeconds < 0.0f ? 0.0f : holdSeconds; ma_mutex_unlock(&mic->lock); } /*** reading ***/ float kon_micLevel(kon_mic_t *mic) { float level; ma_mutex_lock(&mic->lock); level = mic->level; ma_mutex_unlock(&mic->lock); return level; } int kon_micTalking(kon_mic_t *mic) { int talking; ma_mutex_lock(&mic->lock); talking = mic->holdLeft > 0.0f; ma_mutex_unlock(&mic->lock); return talking; } kon_sound_t *kon_micSnapshot(kon_mic_t *mic, float seconds) { kon_sound_t *sound; int16_t *frames; uint64_t want, have; uint32_t start, i; if (seconds <= 0.0f) return NULL; ma_mutex_lock(&mic->lock); have = mic->total < mic->size ? mic->total : mic->size; want = (uint64_t)(seconds * (float)mic->rate); if (want > have) want = have; if (want == 0) { ma_mutex_unlock(&mic->lock); return NULL; } sound = malloc(sizeof(kon_sound_t) + (size_t)want * sizeof(int16_t)); if (!sound) { ma_mutex_unlock(&mic->lock); return NULL; } frames = (int16_t *)(sound + 1); start = (uint32_t)((mic->head + mic->size - want) % mic->size); for (i = 0; i < want; i++) { float x = mic->ring[(start + i) % mic->size]; frames[i] = (int16_t)(x * 32767.0f); } ma_mutex_unlock(&mic->lock); sound->frames = frames; sound->frameCount = want; sound->channels = 1; sound->sampleRate = mic->rate; return sound; }