/* SPDX-License-Identifier: MIT */ /* * konsound.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 "konsound.h" #include #define REPLAY_FADE 0.5f #define OGG_SCAN 32768 /*** helpers ***/ static int clampInt(int value, int low, int high) { return value < low ? low : value > high ? high : value; } static void setName(kon_soundChannel_t *channel, const char *name) { size_t length = name ? strlen(name) : 0; if (length >= KON_SOUND_NAME) length = KON_SOUND_NAME - 1; if (length) memcpy(channel->name, name, length); channel->name[length] = '\0'; } static void setParams(kon_soundChannel_t *channel, const char *name, int volume, int pitch, int pan) { setName(channel, name); channel->volume = clampInt(volume, 0, 100); channel->pitch = clampInt(pitch, 50, 150); channel->pan = clampInt(pan, -100, 100); } static kon_audioParams_t makeParams(const kon_soundChannel_t *channel, int loop, int bus, uint64_t loopStart, uint64_t loopEnd) { kon_audioParams_t params = kon_audioParamsDefault(); params.volume = (float)channel->volume / 100.0f; params.pitch = (float)channel->pitch / 100.0f; params.pan = (float)channel->pan / 100.0f; params.loop = loop; params.loopStart = loopStart; params.loopEnd = loopEnd; params.bus = bus; return params; } static const kon_sound_t *find(kon_soundDeck_t *deck, int kind, const char *name, uint64_t *loopStart, uint64_t *loopEnd) { *loopStart = 0; *loopEnd = 0; if (!deck->lookup || !name || !name[0]) return NULL; return deck->lookup(kind, name, loopStart, loopEnd, deck->user); } static void startBgm(kon_soundDeck_t *deck, const kon_soundChannel_t *wanted, float fade) { uint64_t loopStart, loopEnd; const kon_sound_t *sound = find(deck, KON_SOUND_BGM, wanted->name, &loopStart, &loopEnd); kon_audioParams_t params; deck->bgm = *wanted; deck->bgm.handle = 0; if (!sound) { kon_audioMusicStop(deck->audio, 0.0f); deck->bgm.name[0] = '\0'; return; } params = makeParams(wanted, 1, KON_BUS_MUSIC, loopStart, loopEnd); deck->bgm.handle = kon_audioMusic(deck->audio, sound, ¶ms, fade); } /*** deck ***/ void kon_soundDeckInit(kon_soundDeck_t *deck, kon_audio_t *audio, kon_soundLookup_t lookup, void *user) { if (!deck) return; memset(deck, 0, sizeof *deck); deck->audio = audio; deck->lookup = lookup; deck->user = user; } void kon_soundDeckStop(kon_soundDeck_t *deck) { int i; if (!deck || !deck->audio) return; kon_audioMusicStop(deck->audio, 0.0f); kon_audioStop(deck->audio, deck->bgs.handle); kon_audioStop(deck->audio, deck->me.handle); for (i = 0; i < KON_SOUND_SE_TRACKED; i++) kon_audioStop(deck->audio, deck->se[i]); memset(deck->se, 0, sizeof deck->se); deck->bgm.name[0] = deck->bgs.name[0] = deck->me.name[0] = '\0'; deck->bgm.handle = deck->bgs.handle = deck->me.handle = 0; deck->meHeld = 0; } void kon_soundUpdate(kon_soundDeck_t *deck) { if (!deck || !deck->audio || !deck->meHeld) return; if (deck->me.handle && kon_audioIsPlaying(deck->audio, deck->me.handle)) return; deck->meHeld = 0; deck->me.handle = 0; deck->me.name[0] = '\0'; if (deck->resumeBgm.name[0]) startBgm(deck, &deck->resumeBgm, REPLAY_FADE); } /*** channels ***/ void kon_soundPlayBgm(kon_soundDeck_t *deck, const char *name, int volume, int pitch, int pan) { kon_soundChannel_t wanted; if (!deck || !deck->audio) return; memset(&wanted, 0, sizeof wanted); setParams(&wanted, name, volume, pitch, pan); if (deck->meHeld) { deck->resumeBgm = wanted; return; } if (!wanted.name[0]) { kon_audioMusicStop(deck->audio, 0.0f); deck->bgm.name[0] = '\0'; deck->bgm.handle = 0; return; } if (deck->bgm.name[0] && strcmp(deck->bgm.name, wanted.name) == 0 && kon_audioIsPlaying(deck->audio, deck->bgm.handle)) { deck->bgm.volume = wanted.volume; deck->bgm.pitch = wanted.pitch; deck->bgm.pan = wanted.pan; kon_audioHandleSetVolume(deck->audio, deck->bgm.handle, (float)wanted.volume / 100.0f); kon_audioHandleSetPitch(deck->audio, deck->bgm.handle, (float)wanted.pitch / 100.0f); kon_audioHandleSetPan(deck->audio, deck->bgm.handle, (float)wanted.pan / 100.0f); return; } startBgm(deck, &wanted, 0.0f); } void kon_soundPlayBgs(kon_soundDeck_t *deck, const char *name, int volume, int pitch, int pan) { kon_soundChannel_t wanted; uint64_t loopStart, loopEnd; const kon_sound_t *sound; kon_audioParams_t params; if (!deck || !deck->audio) return; memset(&wanted, 0, sizeof wanted); setParams(&wanted, name, volume, pitch, pan); if (wanted.name[0] && strcmp(deck->bgs.name, wanted.name) == 0 && kon_audioIsPlaying(deck->audio, deck->bgs.handle)) { deck->bgs.volume = wanted.volume; deck->bgs.pitch = wanted.pitch; deck->bgs.pan = wanted.pan; kon_audioHandleSetVolume(deck->audio, deck->bgs.handle, (float)wanted.volume / 100.0f); kon_audioHandleSetPitch(deck->audio, deck->bgs.handle, (float)wanted.pitch / 100.0f); kon_audioHandleSetPan(deck->audio, deck->bgs.handle, (float)wanted.pan / 100.0f); return; } kon_audioStop(deck->audio, deck->bgs.handle); deck->bgs = wanted; deck->bgs.handle = 0; sound = find(deck, KON_SOUND_BGS, wanted.name, &loopStart, &loopEnd); if (!sound) { deck->bgs.name[0] = '\0'; return; } params = makeParams(&wanted, 1, KON_BUS_MUSIC, loopStart, loopEnd); deck->bgs.handle = kon_audioPlayEx(deck->audio, sound, ¶ms); } void kon_soundFadeBgm(kon_soundDeck_t *deck, int seconds) { if (!deck || !deck->audio) return; if (deck->meHeld) deck->resumeBgm.name[0] = '\0'; kon_audioMusicStop(deck->audio, (float)clampInt(seconds, 0, 3600)); deck->bgm.name[0] = '\0'; deck->bgm.handle = 0; } void kon_soundFadeBgs(kon_soundDeck_t *deck, int seconds) { if (!deck || !deck->audio) return; kon_audioHandleFade(deck->audio, deck->bgs.handle, 0.0f, (float)clampInt(seconds, 0, 3600), 1); deck->bgs.name[0] = '\0'; deck->bgs.handle = 0; } void kon_soundSaveBgm(kon_soundDeck_t *deck) { if (!deck) return; deck->savedBgm = deck->meHeld ? deck->resumeBgm : deck->bgm; deck->savedBgm.handle = 0; } void kon_soundReplayBgm(kon_soundDeck_t *deck) { if (!deck || !deck->audio) return; if (!deck->savedBgm.name[0]) { kon_soundFadeBgm(deck, 0); return; } if (deck->meHeld) { deck->resumeBgm = deck->savedBgm; return; } if (strcmp(deck->bgm.name, deck->savedBgm.name) == 0 && kon_audioIsPlaying(deck->audio, deck->bgm.handle)) return; startBgm(deck, &deck->savedBgm, 0.0f); } void kon_soundPlayMe(kon_soundDeck_t *deck, const char *name, int volume, int pitch, int pan) { kon_soundChannel_t wanted; uint64_t loopStart, loopEnd; const kon_sound_t *sound; kon_audioParams_t params; if (!deck || !deck->audio) return; memset(&wanted, 0, sizeof wanted); setParams(&wanted, name, volume, pitch, pan); kon_audioStop(deck->audio, deck->me.handle); deck->me.handle = 0; deck->me.name[0] = '\0'; sound = find(deck, KON_SOUND_ME, wanted.name, &loopStart, &loopEnd); if (!sound) { /* an empty or missing ME ends the pause, so the music comes back */ if (deck->meHeld) { deck->meHeld = 0; if (deck->resumeBgm.name[0]) startBgm(deck, &deck->resumeBgm, REPLAY_FADE); } return; } if (!deck->meHeld && deck->bgm.name[0]) { deck->resumeBgm = deck->bgm; deck->resumeBgm.handle = 0; kon_audioMusicStop(deck->audio, 0.0f); deck->bgm.name[0] = '\0'; deck->bgm.handle = 0; deck->meHeld = 1; } else if (!deck->meHeld) { deck->resumeBgm.name[0] = '\0'; deck->meHeld = 1; } params = makeParams(&wanted, 0, KON_BUS_MUSIC, 0, 0); deck->me = wanted; deck->me.handle = kon_audioPlayEx(deck->audio, sound, ¶ms); } void kon_soundPlaySe(kon_soundDeck_t *deck, const char *name, int volume, int pitch, int pan) { kon_soundChannel_t wanted; uint64_t loopStart, loopEnd; const kon_sound_t *sound; kon_audioParams_t params; kon_audioHandle_t handle; if (!deck || !deck->audio) return; memset(&wanted, 0, sizeof wanted); setParams(&wanted, name, volume, pitch, pan); sound = find(deck, KON_SOUND_SE, wanted.name, &loopStart, &loopEnd); if (!sound) return; params = makeParams(&wanted, 0, KON_BUS_SFX, 0, 0); handle = kon_audioPlayEx(deck->audio, sound, ¶ms); if (!handle) return; deck->se[deck->seNext] = handle; deck->seNext = (deck->seNext + 1) % KON_SOUND_SE_TRACKED; } void kon_soundStopSe(kon_soundDeck_t *deck) { int i; if (!deck || !deck->audio) return; for (i = 0; i < KON_SOUND_SE_TRACKED; i++) { kon_audioStop(deck->audio, deck->se[i]); deck->se[i] = 0; } } /*** konscript host commands ***/ int kon_soundHost(kon_soundDeck_t *deck, int code, int b, int c, int d, const char *text) { char name[KON_SOUND_NAME]; size_t length = 0; switch (code) { case 241: case 245: case 249: case 250: while (text && text[length] && text[length] != '\t' && length < KON_SOUND_NAME - 1) { name[length] = text[length]; length++; } name[length] = '\0'; if (code == 241) kon_soundPlayBgm(deck, name, b, c, d); else if (code == 245) kon_soundPlayBgs(deck, name, b, c, d); else if (code == 249) kon_soundPlayMe(deck, name, b, c, d); else kon_soundPlaySe(deck, name, b, c, d); return 1; case 242: kon_soundFadeBgm(deck, b); return 1; case 243: kon_soundSaveBgm(deck); return 1; case 244: kon_soundReplayBgm(deck); return 1; case 246: kon_soundFadeBgs(deck, b); return 1; case 251: kon_soundStopSe(deck); return 1; default: return 0; } } /*** ogg loop points ***/ static int matchTag(const uint8_t *p, const uint8_t *end, const char *tag) { while (*tag) { uint8_t ch; if (p >= end) return 0; ch = *p; if (ch >= 'a' && ch <= 'z') ch = (uint8_t)(ch - 'a' + 'A'); if (ch != (uint8_t)*tag) return 0; p++; tag++; } return 1; } static int readNumber(const uint8_t *p, const uint8_t *end, uint64_t *out) { uint64_t value = 0; int digits = 0; while (p < end && *p >= '0' && *p <= '9' && digits < 18) { value = value * 10 + (uint64_t)(*p - '0'); p++; digits++; } if (!digits) return 0; *out = value; return 1; } int kon_soundOggLoop(const uint8_t *data, size_t size, uint64_t *start, uint64_t *end) { const uint8_t *p, *stop; uint64_t loopStart = 0, loopLength = 0; int haveStart = 0, haveLength = 0; if (!data || !start || !end || size < 8 || memcmp(data, "OggS", 4) != 0) return 0; stop = data + (size < OGG_SCAN ? size : OGG_SCAN); for (p = data; p < stop; p++) { if (!haveStart && matchTag(p, stop, "LOOPSTART=")) { haveStart = readNumber(p + 10, stop, &loopStart); } else if (!haveLength && matchTag(p, stop, "LOOPLENGTH=")) { haveLength = readNumber(p + 11, stop, &loopLength); } } if (!haveStart) return 0; *start = loopStart; *end = haveLength ? loopStart + loopLength : 0; return 1; }