From 7da82bf070062200a323c7507803b60340c8dd08 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:04:50 +0200 Subject: [PATCH 01/25] Add tiny AMY socket client library for Godot POC --- android/amy-service/src/main/cpp/CMakeLists.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/android/amy-service/src/main/cpp/CMakeLists.txt b/android/amy-service/src/main/cpp/CMakeLists.txt index 08fbf51c..40812ca0 100644 --- a/android/amy-service/src/main/cpp/CMakeLists.txt +++ b/android/amy-service/src/main/cpp/CMakeLists.txt @@ -36,6 +36,13 @@ add_library(amy_android SHARED ${AMY_SOURCES} ) +# Tiny client used by non-AMY processes (Godot in this proof of concept). +# It contains no synth/audio engine: only one AF_UNIX/SOCK_SEQPACKET connection +# to the separate :amy service process. +add_library(amy_android_client SHARED + amy_android_client.cpp +) + target_include_directories(amy_android PRIVATE ${AMY_SRC} ${CMAKE_CURRENT_SOURCE_DIR} @@ -61,7 +68,12 @@ target_compile_options(amy_android PRIVATE $<$:-O3;-Wall;-Wextra;-Wno-unused-parameter> ) +target_compile_options(amy_android_client PRIVATE + -O3 -Wall -Wextra -Wno-unused-parameter +) + target_compile_features(amy_android PRIVATE c_std_11 cxx_std_17) +target_compile_features(amy_android_client PRIVATE cxx_std_17) target_link_libraries(amy_android PRIVATE oboe::oboe From 34da5dcce5e7117851a00db85584f0e7afac2d19 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:04:59 +0200 Subject: [PATCH 02/25] Add raw socket client JNI for Godot POC --- .../src/main/cpp/amy_android_client.cpp | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 android/amy-service/src/main/cpp/amy_android_client.cpp diff --git a/android/amy-service/src/main/cpp/amy_android_client.cpp b/android/amy-service/src/main/cpp/amy_android_client.cpp new file mode 100644 index 00000000..67cdfff1 --- /dev/null +++ b/android/amy-service/src/main/cpp/amy_android_client.cpp @@ -0,0 +1,73 @@ +#include + +#include +#include +#include + +#include +#include +#include + +namespace { + +int connect_socket(const char *path) { + if (path == nullptr || path[0] == '\0') return -EINVAL; + + sockaddr_un addr{}; + const size_t path_len = std::strlen(path); + if (path_len >= sizeof(addr.sun_path)) return -ENAMETOOLONG; + + addr.sun_family = AF_UNIX; + std::memcpy(addr.sun_path, path, path_len + 1u); + + const int fd = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); + if (fd < 0) return -errno; + + if (connect(fd, reinterpret_cast(&addr), sizeof(addr)) < 0) { + const int saved = errno; + close(fd); + return -saved; + } + return fd; +} + +int send_wire(int fd, JNIEnv *env, jstring wire) { + if (fd < 0) return -ENOTCONN; + if (wire == nullptr) return -EINVAL; + + const jsize len = env->GetStringUTFLength(wire); + if (len <= 0) return -EINVAL; + + const char *bytes = env->GetStringUTFChars(wire, nullptr); + if (bytes == nullptr) return -ENOMEM; + + const ssize_t sent = send(fd, bytes, static_cast(len), MSG_NOSIGNAL); + const int saved = sent < 0 ? errno : 0; + env->ReleaseStringUTFChars(wire, bytes); + + if (sent < 0) return -saved; + if (sent != len) return -EIO; + return 0; +} + +} // namespace + +extern "C" JNIEXPORT jint JNICALL +Java_org_amy_audio_AmyClient_nativeConnect(JNIEnv *env, jclass, jstring socketPath) { + if (socketPath == nullptr) return -EINVAL; + const char *path = env->GetStringUTFChars(socketPath, nullptr); + if (path == nullptr) return -ENOMEM; + const int result = connect_socket(path); + env->ReleaseStringUTFChars(socketPath, path); + return result; +} + +extern "C" JNIEXPORT jint JNICALL +Java_org_amy_audio_AmyClient_nativeSend(JNIEnv *env, jclass, jint fd, jstring wire) { + return send_wire(fd, env, wire); +} + +extern "C" JNIEXPORT void JNICALL +Java_org_amy_audio_AmyClient_nativeClose(JNIEnv *, jclass, jint fd) { + if (fd >= 0) close(fd); +} From f63fbc2d2499e048d8ea1e5aef6bbfe020b268cf Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:05:09 +0200 Subject: [PATCH 03/25] Add minimal Java socket client for Godot raw-wire POC --- .../main/java/org/amy/audio/AmyClient.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 android/amy-service/src/main/java/org/amy/audio/AmyClient.java diff --git a/android/amy-service/src/main/java/org/amy/audio/AmyClient.java b/android/amy-service/src/main/java/org/amy/audio/AmyClient.java new file mode 100644 index 00000000..9b173df8 --- /dev/null +++ b/android/amy-service/src/main/java/org/amy/audio/AmyClient.java @@ -0,0 +1,58 @@ +package org.amy.audio; + +import android.content.Context; + +import java.io.File; + +/** Minimal client for the private AMY SOCK_SEQPACKET socket. */ +public final class AmyClient { + private static final int EINVAL = 22; + private static final int ENOTCONN = 107; + private static int nativeFd = -1; + + static { + System.loadLibrary("amy_android_client"); + } + + private AmyClient() {} + + private static String socketPath(Context context) { + if (context == null) return ""; + return new File(context.getFilesDir(), AmyService.DEFAULT_SOCKET_NAME) + .getAbsolutePath(); + } + + /** Connect once to filesDir/amy.sock. Returns 0 or negative errno. */ + public static synchronized int connect(Context context) { + if (context == null) return -EINVAL; + closeLocked(); + int fd = nativeConnect(socketPath(context)); + if (fd < 0) return fd; + nativeFd = fd; + return 0; + } + + /** Send exactly one AMY wire command as one SOCK_SEQPACKET packet. */ + public static synchronized int sendWire(String wire) { + if (nativeFd < 0) return -ENOTCONN; + if (wire == null || wire.isEmpty()) return -EINVAL; + int result = nativeSend(nativeFd, wire); + if (result < 0) closeLocked(); + return result; + } + + public static synchronized void close() { + closeLocked(); + } + + private static void closeLocked() { + if (nativeFd >= 0) { + nativeClose(nativeFd); + nativeFd = -1; + } + } + + private static native int nativeConnect(String socketPath); + private static native int nativeSend(int fd, String wire); + private static native void nativeClose(int fd); +} From a19046fddf4a9a93b9265f3d3f308de56f07eb64 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:05:20 +0200 Subject: [PATCH 04/25] Auto-start separate AMY process outside Godot code --- .../org/amy/audio/AmyAutoStartProvider.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 android/amy-service/src/main/java/org/amy/audio/AmyAutoStartProvider.java diff --git a/android/amy-service/src/main/java/org/amy/audio/AmyAutoStartProvider.java b/android/amy-service/src/main/java/org/amy/audio/AmyAutoStartProvider.java new file mode 100644 index 00000000..979c99d6 --- /dev/null +++ b/android/amy-service/src/main/java/org/amy/audio/AmyAutoStartProvider.java @@ -0,0 +1,36 @@ +package org.amy.audio; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; + +import java.io.File; + +/** + * Android lifecycle hook for starting the separate :amy process. + * Godot never starts or stops AmyService; it only connects to amy.sock. + */ +public final class AmyAutoStartProvider extends ContentProvider { + @Override + public boolean onCreate() { + Context context = getContext(); + if (context == null) return false; + + File socket = new File(context.getFilesDir(), AmyService.DEFAULT_SOCKET_NAME); + Intent intent = new Intent(context, AmyService.class); + intent.putExtra(AmyService.EXTRA_SOCKET_PATH, socket.getAbsolutePath()); + context.startService(intent); + return true; + } + + @Override public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { return null; } + @Override public String getType(Uri uri) { return null; } + @Override public Uri insert(Uri uri, ContentValues values) { return null; } + @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } + @Override public int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) { return 0; } +} From 9626f8ac5992883d9d14e97651c10b06af2ad7ca Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:05:27 +0200 Subject: [PATCH 05/25] Start separate AMY process via Android lifecycle hook --- android/amy-service/src/main/AndroidManifest.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/android/amy-service/src/main/AndroidManifest.xml b/android/amy-service/src/main/AndroidManifest.xml index cde4251b..682656e0 100644 --- a/android/amy-service/src/main/AndroidManifest.xml +++ b/android/amy-service/src/main/AndroidManifest.xml @@ -1,6 +1,12 @@ + + Date: Sun, 23 Aug 2026 16:06:03 +0200 Subject: [PATCH 06/25] Add minimal Godot raw-wire C-scale proof of concept --- godot/android-hello-world/main.gd | 125 ++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 godot/android-hello-world/main.gd diff --git a/godot/android-hello-world/main.gd b/godot/android-hello-world/main.gd new file mode 100644 index 00000000..fa7dece3 --- /dev/null +++ b/godot/android-hello-world/main.gd @@ -0,0 +1,125 @@ +extends Control + +var _status: Label +var _play_button: Button +var _amy_client: Object +var _android_context: Object + +func _ready() -> void: + _build_ui() + + if OS.get_name() != "Android": + _fail("Android export required") + return + + var runtime: Object = Engine.get_singleton("AndroidRuntime") + if runtime == null: + _fail("AndroidRuntime unavailable") + return + + _android_context = runtime.getApplicationContext() + if _android_context == null: + _fail("Android application context unavailable") + return + + _amy_client = JavaClassWrapper.wrap("org.amy.audio.AmyClient") + if _amy_client == null: + _fail("AmyClient class unavailable") + return + if not _amy_client.has_java_method("connect") or not _amy_client.has_java_method("sendWire"): + _fail("AmyClient methods unavailable") + return + + _status.text = "Connecting to amy.sock..." + for _attempt in range(200): + var rc: int = int(_amy_client.connect(_android_context)) + var exception: Object = JavaClassWrapper.get_exception() + if exception != null: + _fail("AmyClient.connect exception: %s" % str(exception)) + return + if rc == 0: + print("Godot connected to amy.sock") + _status.text = "Connected to AMY" + _play_button.disabled = false + await _play_scale() + return + await get_tree().create_timer(0.05).timeout + + _fail("Could not connect to amy.sock") + +func _exit_tree() -> void: + if _amy_client != null: + _amy_client.close() + +func _build_ui() -> void: + var center := CenterContainer.new() + center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + add_child(center) + + var column := VBoxContainer.new() + column.custom_minimum_size = Vector2(680, 0) + column.alignment = BoxContainer.ALIGNMENT_CENTER + center.add_child(column) + + var title := Label.new() + title.text = "AMY + Godot raw-wire proof of concept" + title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + title.add_theme_font_size_override("font_size", 28) + column.add_child(title) + + _status = Label.new() + _status.text = "Initializing..." + _status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + _status.add_theme_font_size_override("font_size", 20) + column.add_child(_status) + + _play_button = Button.new() + _play_button.text = "Play C major scale" + _play_button.disabled = true + _play_button.pressed.connect(_on_play_pressed) + column.add_child(_play_button) + +func _on_play_pressed() -> void: + await _play_scale() + +func _send_wire(wire: String) -> bool: + var rc: int = int(_amy_client.sendWire(wire)) + var exception: Object = JavaClassWrapper.get_exception() + if exception != null: + _fail("AmyClient.sendWire exception: %s" % str(exception)) + return false + if rc != 0: + _fail("amy.sock send failed: %d" % rc) + return false + print("Godot wire: %s" % wire) + return true + +func _play_scale() -> void: + _play_button.disabled = true + _status.text = "Playing C major scale..." + + # Stage 1 deliberately uses the exact literal AMY wire commands from the + # already-working Android hello-world. No Amy.gd/API translation is involved. + if not _send_wire("v0w0V10.0Z"): + return + await get_tree().create_timer(0.03).timeout + + for note in [60, 62, 64, 65, 67, 69, 71, 72]: + if not _send_wire("v0n%dl1Z" % note): + return + await get_tree().create_timer(0.35).timeout + if not _send_wire("v0l0Z"): + return + await get_tree().create_timer(0.08).timeout + + _status.text = "C scale complete" + _play_button.disabled = false + print("Godot raw-wire C scale complete") + +func _fail(message: String) -> void: + push_error(message) + print("Godot raw-wire error: %s" % message) + if _status != null: + _status.text = message + if _play_button != null: + _play_button.disabled = true From b6fc6a8e47a0f3f71e1ba16361e83d4bbf144c7d Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:06:09 +0200 Subject: [PATCH 07/25] Add Godot POC scene --- godot/android-hello-world/main.tscn | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 godot/android-hello-world/main.tscn diff --git a/godot/android-hello-world/main.tscn b/godot/android-hello-world/main.tscn new file mode 100644 index 00000000..23751ec5 --- /dev/null +++ b/godot/android-hello-world/main.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource path="res://main.gd" type="Script" id="1_main"] + +[node name="AmyGodotAndroidHello" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_main") From 7f42654d1e9c355744d5b01f1104acd837c40762 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:06:17 +0200 Subject: [PATCH 08/25] Add minimal Godot Android POC project --- godot/android-hello-world/project.godot | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 godot/android-hello-world/project.godot diff --git a/godot/android-hello-world/project.godot b/godot/android-hello-world/project.godot new file mode 100644 index 00000000..647bdbad --- /dev/null +++ b/godot/android-hello-world/project.godot @@ -0,0 +1,20 @@ +; Engine configuration file. +config_version=5 + +[application] +config/name="AMY Godot Raw Wire POC" +run/main_scene="res://main.tscn" + +[display] +window/size/viewport_width=720 +window/size/viewport_height=1280 +window/size/window_width_override=360 +window/size/window_height_override=640 + +[editor_plugins] +enabled=PackedStringArray("res://addons/amy_android/plugin.cfg") + +[rendering] +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" +textures/vram_compression/import_etc2_astc=true From 0418e8546523d13b828a0b9478fc1965486c72b4 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:06:29 +0200 Subject: [PATCH 09/25] Package AMY service AAR in Godot Android export --- .../addons/amy_android/export_plugin.gd | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 godot/android-hello-world/addons/amy_android/export_plugin.gd diff --git a/godot/android-hello-world/addons/amy_android/export_plugin.gd b/godot/android-hello-world/addons/amy_android/export_plugin.gd new file mode 100644 index 00000000..3e59091e --- /dev/null +++ b/godot/android-hello-world/addons/amy_android/export_plugin.gd @@ -0,0 +1,25 @@ +@tool +extends EditorPlugin + +var _export_plugin: EditorExportPlugin + +func _enter_tree() -> void: + _export_plugin = AmyAndroidExportPlugin.new() + add_export_plugin(_export_plugin) + +func _exit_tree() -> void: + if _export_plugin != null: + remove_export_plugin(_export_plugin) + _export_plugin = null + +class AmyAndroidExportPlugin extends EditorExportPlugin: + func _supports_platform(platform: EditorExportPlatform) -> bool: + return platform is EditorExportPlatformAndroid + + func _get_android_libraries(_platform: EditorExportPlatform, debug: bool) -> PackedStringArray: + if debug: + return PackedStringArray(["amy_android/amy-service-debug.aar"]) + return PackedStringArray(["amy_android/amy-service-release.aar"]) + + func _get_name() -> String: + return "AMY Android service" From 149a460289f74f94528767ac873f2eefa486300a Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:06:36 +0200 Subject: [PATCH 10/25] Register AMY Android export plugin --- godot/android-hello-world/addons/amy_android/plugin.cfg | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 godot/android-hello-world/addons/amy_android/plugin.cfg diff --git a/godot/android-hello-world/addons/amy_android/plugin.cfg b/godot/android-hello-world/addons/amy_android/plugin.cfg new file mode 100644 index 00000000..321defad --- /dev/null +++ b/godot/android-hello-world/addons/amy_android/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="AMY Android Service Export" +description="Packages the separate AMY Android service AAR." +author="AMY" +version="1.0" +script="export_plugin.gd" From 9958834bdc572c9b78b5b8eb8b67cd101a76e72b Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:06:42 +0200 Subject: [PATCH 11/25] Prepare only the AMY AAR for Godot POC --- godot/android-hello-world/prepare.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 godot/android-hello-world/prepare.sh diff --git a/godot/android-hello-world/prepare.sh b/godot/android-hello-world/prepare.sh new file mode 100644 index 00000000..f6de659a --- /dev/null +++ b/godot/android-hello-world/prepare.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "${HERE}/../.." && pwd)" +GRADLE_BIN="${GRADLE_BIN:-gradle}" + +( + cd "${ROOT}/android" + "${GRADLE_BIN}" :amy-service:assembleDebug :amy-service:assembleRelease +) + +ADDON="${HERE}/addons/amy_android" +mkdir -p "${ADDON}" +cp "${ROOT}/android/amy-service/build/outputs/aar/amy-service-debug.aar" \ + "${ADDON}/amy-service-debug.aar" +cp "${ROOT}/android/amy-service/build/outputs/aar/amy-service-release.aar" \ + "${ADDON}/amy-service-release.aar" + +printf 'Prepared AMY service AAR for Godot in %s\n' "${ADDON}" From 94e28292cf308f9aae39b378c5ed46cc2eeace19 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:06:57 +0200 Subject: [PATCH 12/25] Add ARM64 and emulator Godot Android export presets --- godot/android-hello-world/export_presets.cfg | 93 ++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 godot/android-hello-world/export_presets.cfg diff --git a/godot/android-hello-world/export_presets.cfg b/godot/android-hello-world/export_presets.cfg new file mode 100644 index 00000000..8b8e8844 --- /dev/null +++ b/godot/android-hello-world/export_presets.cfg @@ -0,0 +1,93 @@ +[preset.0] + +name="Android ARM64" +platform="Android" +runnable=true +advanced_options=false +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="build/amy-godot-raw-wire-arm64.apk" +patches=PackedStringArray() +encryption_include_filters="" +encryption_exclude_filters="" +seed=0 +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +gradle_build/use_gradle_build=true +gradle_build/gradle_build_directory="" +gradle_build/android_source_template="" +gradle_build/compress_native_libraries=false +gradle_build/export_format=0 +gradle_build/min_sdk="26" +gradle_build/target_sdk="36" +architectures/armeabi-v7a=false +architectures/arm64-v8a=true +architectures/x86=false +architectures/x86_64=false +version/code=1 +version/name="1.0" +package/unique_name="org.amy.godothello" +package/name="AMY Godot Raw Wire POC" +package/signed=true +package/app_category=2 +package/retain_data_on_uninstall=false +package/exclude_from_recents=false +package/show_in_android_tv=false +package/show_in_app_library=true +package/show_as_launcher_app=true + +[preset.1] + +name="Android CI x86_64" +platform="Android" +runnable=false +advanced_options=false +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="build/amy-godot-raw-wire-x86_64.apk" +patches=PackedStringArray() +encryption_include_filters="" +encryption_exclude_filters="" +seed=0 +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.1.options] + +custom_template/debug="" +custom_template/release="" +gradle_build/use_gradle_build=true +gradle_build/gradle_build_directory="" +gradle_build/android_source_template="" +gradle_build/compress_native_libraries=false +gradle_build/export_format=0 +gradle_build/min_sdk="26" +gradle_build/target_sdk="36" +architectures/armeabi-v7a=false +architectures/arm64-v8a=false +architectures/x86=false +architectures/x86_64=true +version/code=1 +version/name="1.0" +package/unique_name="org.amy.godothello" +package/name="AMY Godot Raw Wire POC" +package/signed=true +package/app_category=2 +package/retain_data_on_uninstall=false +package/exclude_from_recents=false +package/show_in_android_tv=false +package/show_in_app_library=true +package/show_as_launcher_app=true From 446661f2a71bddb74895f8ddf6c2ddc3f9c455fa Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:07:40 +0200 Subject: [PATCH 13/25] Remove unnecessary Godot export plugin layer --- godot/android-hello-world/project.godot | 3 --- 1 file changed, 3 deletions(-) diff --git a/godot/android-hello-world/project.godot b/godot/android-hello-world/project.godot index 647bdbad..a2c4c482 100644 --- a/godot/android-hello-world/project.godot +++ b/godot/android-hello-world/project.godot @@ -11,9 +11,6 @@ window/size/viewport_height=1280 window/size/window_width_override=360 window/size/window_height_override=640 -[editor_plugins] -enabled=PackedStringArray("res://addons/amy_android/plugin.cfg") - [rendering] renderer/rendering_method="gl_compatibility" renderer/rendering_method.mobile="gl_compatibility" From ad52d0135fa79462f1392fbdf9d9539ef29e97a2 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:07:45 +0200 Subject: [PATCH 14/25] Prepare only debug AAR for raw-wire POC --- godot/android-hello-world/prepare.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/godot/android-hello-world/prepare.sh b/godot/android-hello-world/prepare.sh index f6de659a..3bf83619 100644 --- a/godot/android-hello-world/prepare.sh +++ b/godot/android-hello-world/prepare.sh @@ -7,14 +7,12 @@ GRADLE_BIN="${GRADLE_BIN:-gradle}" ( cd "${ROOT}/android" - "${GRADLE_BIN}" :amy-service:assembleDebug :amy-service:assembleRelease + "${GRADLE_BIN}" :amy-service:assembleDebug ) ADDON="${HERE}/addons/amy_android" mkdir -p "${ADDON}" cp "${ROOT}/android/amy-service/build/outputs/aar/amy-service-debug.aar" \ "${ADDON}/amy-service-debug.aar" -cp "${ROOT}/android/amy-service/build/outputs/aar/amy-service-release.aar" \ - "${ADDON}/amy-service-release.aar" printf 'Prepared AMY service AAR for Godot in %s\n' "${ADDON}" From 72bd737f782b2bc38060ec4bb8b31932f3afa138 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:07:50 +0200 Subject: [PATCH 15/25] Delete redundant Godot export plugin --- .../addons/amy_android/export_plugin.gd | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 godot/android-hello-world/addons/amy_android/export_plugin.gd diff --git a/godot/android-hello-world/addons/amy_android/export_plugin.gd b/godot/android-hello-world/addons/amy_android/export_plugin.gd deleted file mode 100644 index 3e59091e..00000000 --- a/godot/android-hello-world/addons/amy_android/export_plugin.gd +++ /dev/null @@ -1,25 +0,0 @@ -@tool -extends EditorPlugin - -var _export_plugin: EditorExportPlugin - -func _enter_tree() -> void: - _export_plugin = AmyAndroidExportPlugin.new() - add_export_plugin(_export_plugin) - -func _exit_tree() -> void: - if _export_plugin != null: - remove_export_plugin(_export_plugin) - _export_plugin = null - -class AmyAndroidExportPlugin extends EditorExportPlugin: - func _supports_platform(platform: EditorExportPlatform) -> bool: - return platform is EditorExportPlatformAndroid - - func _get_android_libraries(_platform: EditorExportPlatform, debug: bool) -> PackedStringArray: - if debug: - return PackedStringArray(["amy_android/amy-service-debug.aar"]) - return PackedStringArray(["amy_android/amy-service-release.aar"]) - - func _get_name() -> String: - return "AMY Android service" From a82a06c6555382755639c896d2d4ab3615dd1dab Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:07:58 +0200 Subject: [PATCH 16/25] Delete redundant Godot export plugin config --- godot/android-hello-world/addons/amy_android/plugin.cfg | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 godot/android-hello-world/addons/amy_android/plugin.cfg diff --git a/godot/android-hello-world/addons/amy_android/plugin.cfg b/godot/android-hello-world/addons/amy_android/plugin.cfg deleted file mode 100644 index 321defad..00000000 --- a/godot/android-hello-world/addons/amy_android/plugin.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[plugin] - -name="AMY Android Service Export" -description="Packages the separate AMY Android service AAR." -author="AMY" -version="1.0" -script="export_plugin.gd" From d6f89859d8e986c8f6699593ec2e469b9a411a55 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:08:40 +0200 Subject: [PATCH 17/25] Add end-to-end Godot raw-wire Android CI --- .github/workflows/godot-android-raw-wire.yml | 204 +++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 .github/workflows/godot-android-raw-wire.yml diff --git a/.github/workflows/godot-android-raw-wire.yml b/.github/workflows/godot-android-raw-wire.yml new file mode 100644 index 00000000..82a9532f --- /dev/null +++ b/.github/workflows/godot-android-raw-wire.yml @@ -0,0 +1,204 @@ +name: Godot Android raw-wire POC + +on: + push: + branches: + - feature/godot-android-raw-wire-poc + workflow_dispatch: + +permissions: + contents: read + +jobs: + raw-wire-poc: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: "17" + + - uses: android-actions/setup-android@v3 + + - name: Install Android SDK components + run: | + yes | sdkmanager --licenses >/dev/null + sdkmanager \ + "platforms;android-36" \ + "build-tools;35.0.1" \ + "ndk;27.0.12077973" \ + "cmake;3.22.1" + + - uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: "8.13" + + - name: Build AMY AAR + run: bash godot/android-hello-world/prepare.sh + + - name: Verify AMY AAR contents + run: | + AAR=godot/android-hello-world/addons/amy_android/amy-service-debug.aar + test -s "$AAR" + unzip -l "$AAR" | tee /tmp/amy-aar.txt + grep -q 'jni/arm64-v8a/libamy_android.so' /tmp/amy-aar.txt + grep -q 'jni/arm64-v8a/libamy_android_client.so' /tmp/amy-aar.txt + grep -q 'jni/x86_64/libamy_android.so' /tmp/amy-aar.txt + grep -q 'jni/x86_64/libamy_android_client.so' /tmp/amy-aar.txt + unzip -p "$AAR" classes.jar > /tmp/amy-classes.jar + unzip -l /tmp/amy-classes.jar | tee /tmp/amy-classes.txt + grep -q 'org/amy/audio/AmyService.class' /tmp/amy-classes.txt + grep -q 'org/amy/audio/AmyClient.class' /tmp/amy-classes.txt + grep -q 'org/amy/audio/AmyAutoStartProvider.class' /tmp/amy-classes.txt + + - name: Install Godot 4.7.2 and Android export templates + run: | + curl -L --fail --retry 3 \ + -o /tmp/godot.zip \ + https://github.com/godotengine/godot/releases/download/4.7.2-stable/Godot_v4.7.2-stable_linux.x86_64.zip + unzip -q /tmp/godot.zip -d /tmp/godot + GODOT_BIN="$(find /tmp/godot -maxdepth 1 -type f -name 'Godot*' | head -n1)" + chmod +x "$GODOT_BIN" + sudo cp "$GODOT_BIN" /usr/local/bin/godot + + curl -L --fail --retry 3 \ + -o /tmp/godot-templates.tpz \ + https://github.com/godotengine/godot/releases/download/4.7.2-stable/Godot_v4.7.2-stable_export_templates.tpz + rm -rf /tmp/godot-templates + mkdir -p /tmp/godot-templates + unzip -q /tmp/godot-templates.tpz -d /tmp/godot-templates + mkdir -p "$HOME/.local/share/godot/export_templates/4.7.2.stable" + cp -a /tmp/godot-templates/templates/. \ + "$HOME/.local/share/godot/export_templates/4.7.2.stable/" + godot --version + + - name: Import Godot project + run: godot --headless --path godot/android-hello-world --import --quit + + - name: Export ARM64 phone APK + run: | + mkdir -p godot/android-hello-world/build + godot --headless \ + --path godot/android-hello-world \ + --install-android-build-template \ + --export-debug "Android ARM64" build/amy-godot-raw-wire-arm64.apk + test -s godot/android-hello-world/build/amy-godot-raw-wire-arm64.apk + + - name: Export x86_64 emulator APK + run: | + godot --headless \ + --path godot/android-hello-world \ + --export-debug "Android CI x86_64" build/amy-godot-raw-wire-x86_64.apk + test -s godot/android-hello-world/build/amy-godot-raw-wire-x86_64.apk + + - name: Verify APK packaging + run: | + ARM=godot/android-hello-world/build/amy-godot-raw-wire-arm64.apk + X86=godot/android-hello-world/build/amy-godot-raw-wire-x86_64.apk + + unzip -l "$ARM" | tee /tmp/arm-apk.txt + grep -q 'lib/arm64-v8a/libgodot_android.so' /tmp/arm-apk.txt + grep -q 'lib/arm64-v8a/libamy_android.so' /tmp/arm-apk.txt + grep -q 'lib/arm64-v8a/libamy_android_client.so' /tmp/arm-apk.txt + ! grep -q 'lib/x86_64/' /tmp/arm-apk.txt + + unzip -l "$X86" | tee /tmp/x86-apk.txt + grep -q 'lib/x86_64/libgodot_android.so' /tmp/x86-apk.txt + grep -q 'lib/x86_64/libamy_android.so' /tmp/x86-apk.txt + grep -q 'lib/x86_64/libamy_android_client.so' /tmp/x86-apk.txt + ! grep -q 'lib/arm64-v8a/' /tmp/x86-apk.txt + + for apk in "$ARM" "$X86"; do + for dex in $(zipinfo -1 "$apk" | grep -E '^classes([0-9]+)?\.dex$'); do + unzip -p "$apk" "$dex" + done | strings > /tmp/classes.txt + grep -q 'Lorg/amy/audio/AmyService;' /tmp/classes.txt + grep -q 'Lorg/amy/audio/AmyClient;' /tmp/classes.txt + grep -q 'Lorg/amy/audio/AmyAutoStartProvider;' /tmp/classes.txt + done + + - name: Upload ARM64 APK + uses: actions/upload-artifact@v4 + with: + name: amy-godot-raw-wire-arm64-apk + path: godot/android-hello-world/build/amy-godot-raw-wire-arm64.apk + if-no-files-found: error + + - name: Run Godot -> amy.sock -> AMY smoke test + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 35 + arch: x86_64 + profile: pixel_2 + disable-animations: true + emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -no-boot-anim + script: | + set -e + APK=godot/android-hello-world/build/amy-godot-raw-wire-x86_64.apk + adb uninstall org.amy.godothello >/dev/null 2>&1 || true + adb install "$APK" + + # Arm the already-existing AMY integration-test audio capture before + # Android creates the application process/provider. This is test-only; + # Godot itself does not start or control AmyService. + adb shell run-as org.amy.godothello mkdir -p files + adb shell run-as org.amy.godothello touch files/amy-audio-capture.enable + + adb logcat -c + adb shell monkey -p org.amy.godothello 1 >/dev/null + sleep 12 + + adb logcat -d > /tmp/amy-godot.log + adb shell ps -A > /tmp/amy-processes.txt + adb shell run-as org.amy.godothello ls -la files > /tmp/amy-files.txt + + echo '===== GODOT/AMY LOG =====' + grep -E 'AmyService|AmyAndroid|AmyAudioCapture|Godot (connected|wire:|raw-wire)' /tmp/amy-godot.log || true + echo '===== PROCESSES =====' + grep 'org.amy.godothello' /tmp/amy-processes.txt || true + echo '===== FILES =====' + cat /tmp/amy-files.txt + + grep -q 'AMY listening on private socket' /tmp/amy-godot.log + grep -q 'AMY/Oboe started:' /tmp/amy-godot.log + grep -q 'Godot connected to amy.sock' /tmp/amy-godot.log + grep -q 'Godot wire: v0w0V10.0Z' /tmp/amy-godot.log + test "$(grep -Ec 'Godot wire: v0n(60|62|64|65|67|69|71|72)l1Z' /tmp/amy-godot.log)" -eq 8 + grep -q 'Godot wire: v0n60l1Z' /tmp/amy-godot.log + grep -q 'Godot wire: v0n72l1Z' /tmp/amy-godot.log + grep -q 'Godot raw-wire C scale complete' /tmp/amy-godot.log + ! grep -q 'Godot raw-wire error:' /tmp/amy-godot.log + grep -q 'org.amy.godothello:amy' /tmp/amy-processes.txt + grep -q 'amy.sock' /tmp/amy-files.txt + grep -q 'amy-render.wav' /tmp/amy-files.txt + grep -q 'amy-oboe.wav' /tmp/amy-files.txt + + mkdir -p android/godot-audio-capture + adb exec-out run-as org.amy.godothello cat files/amy-render.wav > android/godot-audio-capture/amy-render.wav + adb exec-out run-as org.amy.godothello cat files/amy-oboe.wav > android/godot-audio-capture/amy-oboe.wav + adb exec-out run-as org.amy.godothello cat files/amy-audio-levels.txt > android/godot-audio-capture/amy-audio-levels.txt + test -s android/godot-audio-capture/amy-render.wav + test -s android/godot-audio-capture/amy-oboe.wav + test -s android/godot-audio-capture/amy-audio-levels.txt + + - name: Analyze audio produced by Godot wire commands + run: | + cat android/godot-audio-capture/amy-audio-levels.txt + python3 tests/check_android_audio_capture.py \ + android/godot-audio-capture/amy-render.wav \ + android/godot-audio-capture/amy-oboe.wav + + - name: Upload diagnostics and captured audio + if: always() + uses: actions/upload-artifact@v4 + with: + name: amy-godot-raw-wire-diagnostics + path: | + /tmp/amy-godot.log + /tmp/amy-processes.txt + /tmp/amy-files.txt + android/godot-audio-capture/ + if-no-files-found: warn From 3fb8c6a889e658fdd7c892f56241f969304cafe0 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:09:58 +0200 Subject: [PATCH 18/25] Run raw-wire POC CI on validation pull request --- .github/workflows/godot-android-raw-wire.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/godot-android-raw-wire.yml b/.github/workflows/godot-android-raw-wire.yml index 82a9532f..927f3377 100644 --- a/.github/workflows/godot-android-raw-wire.yml +++ b/.github/workflows/godot-android-raw-wire.yml @@ -4,6 +4,9 @@ on: push: branches: - feature/godot-android-raw-wire-poc + pull_request: + branches: + - upstream/android-oboe workflow_dispatch: permissions: From ab739b3904e9c0ebd2bb356a089838724cbadd3c Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:15:43 +0200 Subject: [PATCH 19/25] Fix JavaClass method dispatch in Godot raw-wire POC --- godot/android-hello-world/main.gd | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/godot/android-hello-world/main.gd b/godot/android-hello-world/main.gd index fa7dece3..71e08ea2 100644 --- a/godot/android-hello-world/main.gd +++ b/godot/android-hello-world/main.gd @@ -2,8 +2,11 @@ extends Control var _status: Label var _play_button: Button -var _amy_client: Object -var _android_context: Object +# Deliberately leave these as Variant. Typing AmyClient as Object makes +# GDScript bind connect() to Object.connect(signal, callable) at parse time +# instead of dispatching to the wrapped Java static method. +var _amy_client +var _android_context func _ready() -> void: _build_ui() @@ -12,7 +15,7 @@ func _ready() -> void: _fail("Android export required") return - var runtime: Object = Engine.get_singleton("AndroidRuntime") + var runtime = Engine.get_singleton("AndroidRuntime") if runtime == null: _fail("AndroidRuntime unavailable") return @@ -33,7 +36,7 @@ func _ready() -> void: _status.text = "Connecting to amy.sock..." for _attempt in range(200): var rc: int = int(_amy_client.connect(_android_context)) - var exception: Object = JavaClassWrapper.get_exception() + var exception = JavaClassWrapper.get_exception() if exception != null: _fail("AmyClient.connect exception: %s" % str(exception)) return @@ -84,7 +87,7 @@ func _on_play_pressed() -> void: func _send_wire(wire: String) -> bool: var rc: int = int(_amy_client.sendWire(wire)) - var exception: Object = JavaClassWrapper.get_exception() + var exception = JavaClassWrapper.get_exception() if exception != null: _fail("AmyClient.sendWire exception: %s" % str(exception)) return false From 77935692ab1b6c8430e3cae8a707b70185a1b8c6 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:15:51 +0200 Subject: [PATCH 20/25] Package AMY AAR in Godot Android export --- .../addons/amy_android/export_plugin.gd | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 godot/android-hello-world/addons/amy_android/export_plugin.gd diff --git a/godot/android-hello-world/addons/amy_android/export_plugin.gd b/godot/android-hello-world/addons/amy_android/export_plugin.gd new file mode 100644 index 00000000..3e59091e --- /dev/null +++ b/godot/android-hello-world/addons/amy_android/export_plugin.gd @@ -0,0 +1,25 @@ +@tool +extends EditorPlugin + +var _export_plugin: EditorExportPlugin + +func _enter_tree() -> void: + _export_plugin = AmyAndroidExportPlugin.new() + add_export_plugin(_export_plugin) + +func _exit_tree() -> void: + if _export_plugin != null: + remove_export_plugin(_export_plugin) + _export_plugin = null + +class AmyAndroidExportPlugin extends EditorExportPlugin: + func _supports_platform(platform: EditorExportPlatform) -> bool: + return platform is EditorExportPlatformAndroid + + func _get_android_libraries(_platform: EditorExportPlatform, debug: bool) -> PackedStringArray: + if debug: + return PackedStringArray(["amy_android/amy-service-debug.aar"]) + return PackedStringArray(["amy_android/amy-service-release.aar"]) + + func _get_name() -> String: + return "AMY Android service" From 66b542f6c79a86055f0b9915a5feaa20e7f9c5a5 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:15:58 +0200 Subject: [PATCH 21/25] Register AMY Android export plugin --- godot/android-hello-world/addons/amy_android/plugin.cfg | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 godot/android-hello-world/addons/amy_android/plugin.cfg diff --git a/godot/android-hello-world/addons/amy_android/plugin.cfg b/godot/android-hello-world/addons/amy_android/plugin.cfg new file mode 100644 index 00000000..e00be010 --- /dev/null +++ b/godot/android-hello-world/addons/amy_android/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="AMY Android Service Export" +description="Packages the AMY Android service AAR into the Godot APK." +author="AMY" +version="1.0" +script="export_plugin.gd" From b0b446a23f6d24ae01118b401af776632b61d78b Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:16:08 +0200 Subject: [PATCH 22/25] Enable minimal Android AAR export plugin --- godot/android-hello-world/project.godot | 3 +++ 1 file changed, 3 insertions(+) diff --git a/godot/android-hello-world/project.godot b/godot/android-hello-world/project.godot index a2c4c482..647bdbad 100644 --- a/godot/android-hello-world/project.godot +++ b/godot/android-hello-world/project.godot @@ -11,6 +11,9 @@ window/size/viewport_height=1280 window/size/window_width_override=360 window/size/window_height_override=640 +[editor_plugins] +enabled=PackedStringArray("res://addons/amy_android/plugin.cfg") + [rendering] renderer/rendering_method="gl_compatibility" renderer/rendering_method.mobile="gl_compatibility" From c21c608fd8d68948e3f454015ec3003309905435 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:31:56 +0200 Subject: [PATCH 23/25] Fix Godot emulator validation KVM permissions --- .github/workflows/godot-android-raw-wire.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/godot-android-raw-wire.yml b/.github/workflows/godot-android-raw-wire.yml index 927f3377..58166eba 100644 --- a/.github/workflows/godot-android-raw-wire.yml +++ b/.github/workflows/godot-android-raw-wire.yml @@ -130,6 +130,13 @@ jobs: path: godot/android-hello-world/build/amy-godot-raw-wire-arm64.apk if-no-files-found: error + - name: Enable KVM for Android emulator + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ + | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - name: Run Godot -> amy.sock -> AMY smoke test uses: reactivecircus/android-emulator-runner@v2 with: From fdb167bf4f30535d6006f9557a5cd23d57e7efe8 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:38:08 +0200 Subject: [PATCH 24/25] Fix emulator APK install path in raw-wire validation --- .github/workflows/godot-android-raw-wire.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/godot-android-raw-wire.yml b/.github/workflows/godot-android-raw-wire.yml index 58166eba..4e1043f2 100644 --- a/.github/workflows/godot-android-raw-wire.yml +++ b/.github/workflows/godot-android-raw-wire.yml @@ -147,9 +147,8 @@ jobs: emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -no-boot-anim script: | set -e - APK=godot/android-hello-world/build/amy-godot-raw-wire-x86_64.apk adb uninstall org.amy.godothello >/dev/null 2>&1 || true - adb install "$APK" + adb install godot/android-hello-world/build/amy-godot-raw-wire-x86_64.apk # Arm the already-existing AMY integration-test audio capture before # Android creates the application process/provider. This is test-only; From 6bb63540aa2a9728e6d82942793fa55ea754ea07 Mon Sep 17 00:00:00 2001 From: linuxificator Date: Sun, 23 Aug 2026 16:45:30 +0200 Subject: [PATCH 25/25] Launch Godot explicitly in emulator smoke test --- .github/workflows/godot-android-raw-wire.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/godot-android-raw-wire.yml b/.github/workflows/godot-android-raw-wire.yml index 4e1043f2..70806290 100644 --- a/.github/workflows/godot-android-raw-wire.yml +++ b/.github/workflows/godot-android-raw-wire.yml @@ -157,7 +157,7 @@ jobs: adb shell run-as org.amy.godothello touch files/amy-audio-capture.enable adb logcat -c - adb shell monkey -p org.amy.godothello 1 >/dev/null + adb shell am start -W -n org.amy.godothello/com.godot.game.GodotAppLauncher sleep 12 adb logcat -d > /tmp/amy-godot.log