Initial commit: multi-platform emulator with PS1 support

Jetpack Compose app with adaptive layouts (phone/tablet/foldable) using
Window Size Classes. Integrates a C++ libretro frontend via JNI for
actual PS1 emulation using pcsx_rearmed, with AAudio output and
ANativeWindow rendering.

- Emulation core architecture with interface for NES, SNES, N64,
  Genesis, PS1, PS2 (PS1 implemented, others placeholder)
- Touch controller overlay with D-pad (diagonal support), face buttons,
  shoulders, and analog sticks
- Bluetooth/USB controller detection and input mapping
- Customizable controller layout editor with drag-to-reposition
- Game library with auto-scan of /sdcard/Games/ subfolders
- Auto-import of libretro core .so from /sdcard/Games/cores/
- Save states and SRAM persistence
This commit is contained in:
2026-04-08 20:34:37 -04:00
commit 39e3f8b7c0
67 changed files with 4520 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
#include "audio_engine.h"
#include <android/log.h>
#include <algorithm>
#include <cstring>
#define LOG_TAG "AudioEngine"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// --- RingBuffer ---
void RingBuffer::push(const int16_t* data, size_t frames) {
size_t wp = write_pos_.load(std::memory_order_relaxed);
size_t rp = read_pos_.load(std::memory_order_acquire);
for (size_t i = 0; i < frames; i++) {
size_t next = (wp + 1) % CAPACITY;
if (next == rp) break; // full, drop samples
buffer_[wp * 2] = data[i * 2];
buffer_[wp * 2 + 1] = data[i * 2 + 1];
wp = next;
}
write_pos_.store(wp, std::memory_order_release);
}
size_t RingBuffer::pop(int16_t* out, size_t frames) {
size_t rp = read_pos_.load(std::memory_order_relaxed);
size_t wp = write_pos_.load(std::memory_order_acquire);
size_t read = 0;
while (read < frames && rp != wp) {
out[read * 2] = buffer_[rp * 2];
out[read * 2 + 1] = buffer_[rp * 2 + 1];
rp = (rp + 1) % CAPACITY;
read++;
}
read_pos_.store(rp, std::memory_order_release);
return read;
}
size_t RingBuffer::available() const {
size_t wp = write_pos_.load(std::memory_order_acquire);
size_t rp = read_pos_.load(std::memory_order_acquire);
return (wp >= rp) ? (wp - rp) : (CAPACITY - rp + wp);
}
// --- AudioEngine ---
aaudio_data_callback_result_t AudioEngine::dataCallback(
AAudioStream* /*stream*/, void* userData, void* audioData, int32_t numFrames) {
auto* engine = static_cast<AudioEngine*>(userData);
auto* output = static_cast<int16_t*>(audioData);
if (!engine->isEnabled()) {
memset(output, 0, numFrames * 2 * sizeof(int16_t));
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
size_t read = engine->ringBuffer().pop(output, numFrames);
// Fill remainder with silence to prevent noise
if (read < static_cast<size_t>(numFrames)) {
memset(output + read * 2, 0, (numFrames - read) * 2 * sizeof(int16_t));
}
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
bool AudioEngine::start(int32_t sampleRate) {
AAudioStreamBuilder* builder = nullptr;
aaudio_result_t result = AAudio_createStreamBuilder(&builder);
if (result != AAUDIO_OK) {
LOGE("Failed to create stream builder: %d", result);
return false;
}
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT);
AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_SHARED);
AAudioStreamBuilder_setSampleRate(builder, sampleRate);
AAudioStreamBuilder_setChannelCount(builder, 2);
AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);
AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
AAudioStreamBuilder_setDataCallback(builder, dataCallback, this);
result = AAudioStreamBuilder_openStream(builder, &stream_);
AAudioStreamBuilder_delete(builder);
if (result != AAUDIO_OK) {
LOGE("Failed to open stream: %d", result);
return false;
}
result = AAudioStream_requestStart(stream_);
if (result != AAUDIO_OK) {
LOGE("Failed to start stream: %d", result);
AAudioStream_close(stream_);
stream_ = nullptr;
return false;
}
LOGD("Audio started: %d Hz stereo", sampleRate);
return true;
}
void AudioEngine::stop() {
if (stream_) {
AAudioStream_requestStop(stream_);
AAudioStream_close(stream_);
stream_ = nullptr;
LOGD("Audio stopped");
}
}
void AudioEngine::pushSamples(const int16_t* data, size_t frames) {
ring_buffer_.push(data, frames);
}