mirror of
https://github.com/baketnk/frame-yap.git
synced 2026-10-06 00:00:05 +02:00
91 lines
4.4 KiB
C++
91 lines
4.4 KiB
C++
#include "audio.hpp"
|
|
#include <SDL3/SDL.h>
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
|
|
namespace {
|
|
SDL_AudioStream* current = nullptr;
|
|
int opens = 0, resumes = 0, closes = 0;
|
|
bool removed = false;
|
|
void feed(float value, int count) { assert(current); current->queued.insert(current->queued.end(), count, value); }
|
|
}
|
|
const char* SDL_GetError() { return "fake failure"; }
|
|
bool SDL_InitSubSystem(int) { return true; }
|
|
void SDL_QuitSubSystem(int) {}
|
|
SDL_AudioStream* SDL_OpenAudioDeviceStream(SDL_AudioDeviceID, const SDL_AudioSpec*, void*, void*) {
|
|
++opens; return current = new SDL_AudioStream;
|
|
}
|
|
bool SDL_ResumeAudioStreamDevice(SDL_AudioStream*) { ++resumes; return true; }
|
|
SDL_AudioDeviceID SDL_GetAudioStreamDevice(SDL_AudioStream*) { return 42; }
|
|
int SDL_GetAudioStreamAvailable(SDL_AudioStream* stream) { return int(stream->queued.size() * sizeof(float)); }
|
|
int SDL_GetAudioStreamData(SDL_AudioStream* stream, void* data, int size) {
|
|
int count = std::min(int(stream->queued.size()), size / int(sizeof(float)));
|
|
std::memcpy(data, stream->queued.data(), count * sizeof(float));
|
|
stream->queued.erase(stream->queued.begin(), stream->queued.begin() + count);
|
|
return count * sizeof(float);
|
|
}
|
|
bool SDL_ClearAudioStream(SDL_AudioStream* stream) { stream->queued.clear(); return true; }
|
|
void SDL_DestroyAudioStream(SDL_AudioStream* stream) { ++closes; current = nullptr; delete stream; }
|
|
bool SDL_PollEvent(SDL_Event* event) {
|
|
if (!removed) return false;
|
|
removed = false; *event = SDL_Event{SDL_EVENT_AUDIO_DEVICE_REMOVED, {true, 42}}; return true;
|
|
}
|
|
int main() {
|
|
using frameyap::Audio;
|
|
{
|
|
Audio audio;
|
|
audio.prepare(); audio.prepare();
|
|
assert(opens == 1 && resumes == 1 && audio.open() && !audio.recording());
|
|
feed(0.7f, 10000); audio.poll();
|
|
assert(current->queued.empty());
|
|
feed(0.8f, 200); audio.start(); // queued idle speech cannot leak
|
|
feed(0.1f, 3200); audio.poll();
|
|
feed(0.2f, 3200);
|
|
auto clip = audio.finish();
|
|
assert(clip.size() == 6400 && clip.front() == 0.1f && clip.back() == 0.2f);
|
|
assert(audio.open() && !audio.recording() && opens == 1 && closes == 0 && resumes == 1);
|
|
feed(0.5f, 200); audio.start(); feed(0.3f, 4000);
|
|
audio.cancel(); assert(current->queued.empty());
|
|
feed(0.9f, 100); audio.start(); feed(0.4f, 3200);
|
|
clip = audio.finish(); assert(clip.size() == 3200 && clip.front() == 0.4f);
|
|
assert(opens == 1 && closes == 0 && resumes == 1);
|
|
// Real float capture can exceed full scale. Both poll and finish must
|
|
// saturate finite peaks without changing ordinary samples or clip size.
|
|
audio.start();
|
|
feed(1.01f, 1600); feed(-1.25f, 1600); audio.poll();
|
|
feed(std::numeric_limits<float>::max(), 1);
|
|
feed(-std::numeric_limits<float>::max(), 1);
|
|
feed(1.f, 1); feed(-1.f, 1); feed(.25f, 1); feed(0.f, 1);
|
|
clip = audio.finish();
|
|
assert(clip.size() == 3206);
|
|
assert(std::all_of(clip.begin(), clip.begin() + 1600, [](float x) { return x == 1.f; }));
|
|
assert(std::all_of(clip.begin() + 1600, clip.begin() + 3200, [](float x) { return x == -1.f; }));
|
|
assert(clip[3200] == 1.f && clip[3201] == -1.f && clip[3202] == 1.f && clip[3203] == -1.f);
|
|
assert(clip[3204] == .25f && clip[3205] == 0.f);
|
|
for (float invalid : {std::numeric_limits<float>::infinity(),
|
|
-std::numeric_limits<float>::infinity(),
|
|
std::numeric_limits<float>::quiet_NaN()}) {
|
|
audio.start(); feed(.2f, 3200); feed(invalid, 1);
|
|
bool rejected = false;
|
|
try { (void)audio.finish(); } catch (const std::runtime_error&) { rejected = true; }
|
|
assert(rejected && !audio.recording() && current->queued.empty());
|
|
audio.start(); feed(.3f, 3200);
|
|
clip = audio.finish();
|
|
assert(clip.size() == 3200 && clip.front() == .3f); // no failed clip tail leaks
|
|
}
|
|
assert(opens == 1 && closes == 0 && resumes == 1);
|
|
removed = true;
|
|
bool failed = false;
|
|
try { audio.poll(); } catch (const std::runtime_error&) { failed = true; }
|
|
assert(failed);
|
|
audio.close(); assert(closes == 1 && !audio.open());
|
|
audio.prepare(); assert(opens == 2 && resumes == 2);
|
|
}
|
|
assert(closes == 2);
|
|
std::cout << "audio lifecycle checks passed (fake device only)\n";
|
|
}
|