From f00bcd7b779c63d21c9d48c0c178042dc4f79dcc Mon Sep 17 00:00:00 2001 From: Antoine Rousseau <_antoine_@metalu.net> Date: Wed, 10 Dec 2025 23:02:28 +0100 Subject: [PATCH 1/4] update libpd to oboe branch, and link PdCore with oboe library --- PdCore/pd-core/build.gradle | 12 ++++++++++-- PdCore/pd-core/src/main/jni/Application.mk | 1 + PdCore/pd-core/src/main/jni/libpd | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/PdCore/pd-core/build.gradle b/PdCore/pd-core/build.gradle index 2270ab58..0d29cbb1 100644 --- a/PdCore/pd-core/build.gradle +++ b/PdCore/pd-core/build.gradle @@ -10,6 +10,7 @@ version = rootProject.version dependencies { implementation 'androidx.legacy:legacy-support-v4:' + rootProject.androidxLegacySupportVersion + implementation 'com.google.oboe:oboe:1.10.0' } android { @@ -26,12 +27,19 @@ android { } buildFeatures { - prefabPublishing = true + // Export prefab, that will allow apps to compile externals + prefabPublishing = true + + // Import prefab: extract native libs (mainly Oboe), to be able to access the headers and to link to the libs + prefab = true } prefab { pd { - headers = 'src/main/jni/libpd/pure-data/src' + headers = 'src/main/jni/libpd/pure-data/src' + } + pdnativeoboe { + headers = 'src/main/jni/libpd/jni/oboe' } } diff --git a/PdCore/pd-core/src/main/jni/Application.mk b/PdCore/pd-core/src/main/jni/Application.mk index 1d0b95ea..fb60ca30 100644 --- a/PdCore/pd-core/src/main/jni/Application.mk +++ b/PdCore/pd-core/src/main/jni/Application.mk @@ -1,3 +1,4 @@ APP_PLATFORM := android-28 APP_OPTIM := release APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 +APP_STL := c++_shared diff --git a/PdCore/pd-core/src/main/jni/libpd b/PdCore/pd-core/src/main/jni/libpd index d7d1e1ef..2ed8b39b 160000 --- a/PdCore/pd-core/src/main/jni/libpd +++ b/PdCore/pd-core/src/main/jni/libpd @@ -1 +1 @@ -Subproject commit d7d1e1ef6259583065d7bd0b5b37112fa29d2eb6 +Subproject commit 2ed8b39ba0a82c27b9da087dac01d8e5ff55ee85 From ad4de817720f4a56766aa83ed192359d709a4091 Mon Sep 17 00:00:00 2001 From: Antoine Rousseau <_antoine_@metalu.net> Date: Wed, 10 Dec 2025 23:03:36 +0100 Subject: [PATCH 2/4] add new input/output audio device settings to preferences and audio logic --- .../org/puredata/android/io/AudioDevices.java | 141 ++++++++++++++++++ .../java/org/puredata/android/io/PdAudio.java | 15 ++ .../android/service/PdPreferences.java | 21 ++- .../puredata/android/service/PdService.java | 14 ++ PdCore/pd-core/src/main/res/values/audio.xml | 12 ++ .../pd-core/src/main/res/values/strings.xml | 6 + .../pd-core/src/main/res/xml/preferences.xml | 6 + 7 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 PdCore/pd-core/src/main/java/org/puredata/android/io/AudioDevices.java diff --git a/PdCore/pd-core/src/main/java/org/puredata/android/io/AudioDevices.java b/PdCore/pd-core/src/main/java/org/puredata/android/io/AudioDevices.java new file mode 100644 index 00000000..f59079b4 --- /dev/null +++ b/PdCore/pd-core/src/main/java/org/puredata/android/io/AudioDevices.java @@ -0,0 +1,141 @@ +/** + * + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. + * + */ + +package org.puredata.android.io; +import org.puredata.android.service.R; + +import android.content.Context; +import android.app.Activity; +import android.media.AudioManager; +import android.media.AudioDeviceCallback; +import android.media.AudioDeviceInfo; +import android.util.Log; +import android.content.res.Resources; +import android.preference.ListPreference; +import android.preference.PreferenceFragment; + +import java.util.ArrayList; +import java.util.List; + +public class AudioDevices { + + private static final String TAG = "AudioDevices"; + private AudioManager mAudioManager; + + private class AudioDeviceList { + private ListPreference mPref; + private List mEntries = new ArrayList<>(); + private List mValues = new ArrayList<>(); + + private CharSequence[] listToArray(List l) { + String[] array = new String[l.size()]; + l.toArray(array); + return array; + } + AudioDeviceList(PreferenceFragment prefFragment, String key) { + mPref = (ListPreference) prefFragment.findPreference(key); + mEntries.add("Default"); + mValues.add("-1"); + } + public void add(AudioDeviceInfo device) { + String name = device.getProductName().toString() + " " + typeToString(device.getType()); + mEntries.add(name); + mValues.add(Integer.toString(device.getId())); + mPref.setEntries(listToArray(mEntries)); + mPref.setEntryValues(listToArray(mValues)); + } + } + + private AudioDeviceList mInputDevices = null; + private AudioDeviceList mOutputDevices = null; + + /** + * @param context activity or service that calls this method + */ + public AudioDevices(Activity context) { + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + PreferenceFragment prefFragment = (PreferenceFragment) context.getFragmentManager().findFragmentByTag("prefFragment"); + Resources res = context.getResources(); + mInputDevices = new AudioDeviceList(prefFragment, res.getString(R.string.pref_key_indevice)); + mOutputDevices = new AudioDeviceList(prefFragment, res.getString(R.string.pref_key_outdevice)); + setupAudioDeviceCallback(); + } + + /** + * Converts the value from {@link AudioDeviceInfo#getType()} into a human + * readable string + * @param type One of the {@link AudioDeviceInfo}.TYPE_* values + * e.g. AudioDeviceInfo.TYPE_BUILT_IN_SPEAKER + * @return string which describes the type of audio device + */ + static String typeToString(int type){ + switch (type) { + case AudioDeviceInfo.TYPE_AUX_LINE: + return "auxiliary line-level connectors"; + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + return "Bluetooth device supporting the A2DP profile"; + case AudioDeviceInfo.TYPE_BLUETOOTH_SCO: + return "Bluetooth device typically used for telephony"; + case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: + return "built-in earphone speaker"; + case AudioDeviceInfo.TYPE_BUILTIN_MIC: + return "built-in microphone"; + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: + return "built-in speaker"; + case AudioDeviceInfo.TYPE_BUS: + return "BUS"; + case AudioDeviceInfo.TYPE_DOCK: + return "DOCK"; + case AudioDeviceInfo.TYPE_FM: + return "FM"; + case AudioDeviceInfo.TYPE_FM_TUNER: + return "FM tuner"; + case AudioDeviceInfo.TYPE_HDMI: + return "HDMI"; + case AudioDeviceInfo.TYPE_HDMI_ARC: + return "HDMI audio return channel"; + case AudioDeviceInfo.TYPE_IP: + return "IP"; + case AudioDeviceInfo.TYPE_LINE_ANALOG: + return "line analog"; + case AudioDeviceInfo.TYPE_LINE_DIGITAL: + return "line digital"; + case AudioDeviceInfo.TYPE_TELEPHONY: + return "telephony"; + case AudioDeviceInfo.TYPE_TV_TUNER: + return "TV tuner"; + case AudioDeviceInfo.TYPE_USB_ACCESSORY: + return "USB accessory"; + case AudioDeviceInfo.TYPE_USB_DEVICE: + return "USB device"; + case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: + return "wired headphones"; + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + return "wired headset"; + default: + case AudioDeviceInfo.TYPE_UNKNOWN: + return "unknown"; + } + } + + private void setupAudioDeviceCallback(){ + // Note that we will immediately receive a call to onDevicesAdded with the list of + // devices which are currently connected. + mAudioManager.registerAudioDeviceCallback(new AudioDeviceCallback() { + @Override + public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { + for (AudioDeviceInfo device : addedDevices){ + if (device.isSource()) mInputDevices.add(device); + else if (device.isSink()) mOutputDevices.add(device); + } + } + + public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { + } + }, null); + } +} diff --git a/PdCore/pd-core/src/main/java/org/puredata/android/io/PdAudio.java b/PdCore/pd-core/src/main/java/org/puredata/android/io/PdAudio.java index cc577ec2..0b7f8224 100644 --- a/PdCore/pd-core/src/main/java/org/puredata/android/io/PdAudio.java +++ b/PdCore/pd-core/src/main/java/org/puredata/android/io/PdAudio.java @@ -26,6 +26,8 @@ public class PdAudio { private static AudioWrapper audioWrapper = null; + private static int inputDeviceId = -1; + private static int outputDeviceId = -1; private static final Handler handler = new Handler(Looper.getMainLooper()); private static final Runnable pollRunner = new Runnable() { @Override @@ -77,6 +79,17 @@ protected int process(short[] inBuffer, short[] outBuffer) { } } + /** + * Set the audio input and output devices Id. Call it before startAudio(). + * + * @param inDeviceId id of the audio input device (-1 means the default device) + * @param outDeviceId id of the audio output device (-1 means the default device) + */ + public synchronized static void setDevicesId(int inDeviceId, int outDeviceId) { + inputDeviceId = inDeviceId; + outputDeviceId = outDeviceId; + } + /** * Starts the audio components. * @@ -85,6 +98,8 @@ protected int process(short[] inBuffer, short[] outBuffer) { public synchronized static void startAudio(Context context) { PdBase.computeAudio(true); if (PdBase.implementsAudio()) { + PdBase.setRecordingDeviceId(inputDeviceId); + PdBase.setPlaybackDeviceId(outputDeviceId); handler.post(pollRunner); PdBase.startAudio(); } else { diff --git a/PdCore/pd-core/src/main/java/org/puredata/android/service/PdPreferences.java b/PdCore/pd-core/src/main/java/org/puredata/android/service/PdPreferences.java index 61991f44..319922e7 100644 --- a/PdCore/pd-core/src/main/java/org/puredata/android/service/PdPreferences.java +++ b/PdCore/pd-core/src/main/java/org/puredata/android/service/PdPreferences.java @@ -8,6 +8,7 @@ package org.puredata.android.service; import org.puredata.android.io.AudioParameters; +import org.puredata.android.io.AudioDevices; import org.puredata.core.PdBase; import android.content.Context; @@ -16,6 +17,7 @@ import android.os.Bundle; import android.preference.PreferenceActivity; import android.preference.PreferenceManager; +import android.preference.PreferenceFragment; /** * @@ -27,13 +29,14 @@ */ public class PdPreferences extends PreferenceActivity { - @SuppressWarnings("deprecation") + public AudioDevices audioDevices = null; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); AudioParameters.init(this); initPreferences(getApplicationContext()); - addPreferencesFromResource(R.xml.preferences); + getFragmentManager().beginTransaction().replace(android.R.id.content, new MyPreferenceFragment(), "prefFragment").commit(); } @Override @@ -41,6 +44,18 @@ protected void onDestroy() { super.onDestroy(); } + public static class MyPreferenceFragment extends PreferenceFragment + { + @Override + public void onCreate(final Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + Resources res = getResources(); + addPreferencesFromResource(R.xml.preferences); + ((PdPreferences)getActivity()).audioDevices = new AudioDevices(getActivity()); + } + } + /** * If no preferences are available, initialize preferences with defaults suggested by {@link PdBase} or {@link AudioParameters}, in that order. * @@ -53,8 +68,10 @@ public static void initPreferences(Context context) { SharedPreferences.Editor editor = prefs.edit(); int srate = PdBase.suggestSampleRate(); editor.putString(res.getString(R.string.pref_key_srate), "" + ((srate > 0) ? srate : AudioParameters.suggestSampleRate())); + editor.putString(res.getString(R.string.pref_key_indevice), res.getStringArray(R.array.indevice_values)[0]); int nic = PdBase.suggestInputChannels(); editor.putString(res.getString(R.string.pref_key_inchannels), "" + ((nic > 0) ? nic : AudioParameters.suggestInputChannels())); + editor.putString(res.getString(R.string.pref_key_outdevice), res.getStringArray(R.array.outdevice_values)[0]); int noc = PdBase.suggestOutputChannels(); editor.putString(res.getString(R.string.pref_key_outchannels), "" + ((noc > 0) ? noc : AudioParameters.suggestOutputChannels())); editor.commit(); diff --git a/PdCore/pd-core/src/main/java/org/puredata/android/service/PdService.java b/PdCore/pd-core/src/main/java/org/puredata/android/service/PdService.java index 18dc6c42..f4accfb3 100644 --- a/PdCore/pd-core/src/main/java/org/puredata/android/service/PdService.java +++ b/PdCore/pd-core/src/main/java/org/puredata/android/service/PdService.java @@ -100,6 +100,18 @@ public synchronized void initAudio(int srate, int nic, int noc, float millis) th stopForeground(); Resources res = getResources(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + int in_id = -1; + int out_id = -1; + { + String s = prefs.getString(res.getString(R.string.pref_key_indevice), null); + if (s != null) { + in_id = Integer.parseInt(s); + } + s = prefs.getString(res.getString(R.string.pref_key_outdevice), null); + if (s != null) { + out_id = Integer.parseInt(s); + } + } if (srate < 0) { String s = prefs.getString(res.getString(R.string.pref_key_srate), null); if (s != null) { @@ -137,6 +149,7 @@ public synchronized void initAudio(int srate, int nic, int noc, float millis) th millis = 50.0f; // conservative choice } int tpb = (int) (0.001f * millis * srate / PdBase.blockSize()) + 1; + PdAudio.setDevicesId(in_id, out_id); PdAudio.initAudio(srate, nic, noc, tpb, true); sampleRate = srate; inputChannels = nic; @@ -213,6 +226,7 @@ public boolean onUnbind(Intent intent) { @Override public void onCreate() { super.onCreate(); + AudioParameters.init(this); if (!abstractionsInstalled) { try { diff --git a/PdCore/pd-core/src/main/res/values/audio.xml b/PdCore/pd-core/src/main/res/values/audio.xml index 80aef857..7be836c8 100644 --- a/PdCore/pd-core/src/main/res/values/audio.xml +++ b/PdCore/pd-core/src/main/res/values/audio.xml @@ -18,6 +18,12 @@ 44100 48000 + + Default + + + -1 + None Mono @@ -28,6 +34,12 @@ 1 2 + + Default + + + -1 + None Mono diff --git a/PdCore/pd-core/src/main/res/values/strings.xml b/PdCore/pd-core/src/main/res/values/strings.xml index 26d9d507..a808125b 100644 --- a/PdCore/pd-core/src/main/res/values/strings.xml +++ b/PdCore/pd-core/src/main/res/values/strings.xml @@ -5,9 +5,15 @@ SAMPLE_RATE Sample rate Sample rate for Pure Data + INPUT_DEVICE + Input device + Name of the input device INPUT_CHANNELS Input channels Number of input channels + OUTPUT_DEVICE + Output device + Name of the output device OUTPUT_CHANNELS Output channels Number of output channels diff --git a/PdCore/pd-core/src/main/res/xml/preferences.xml b/PdCore/pd-core/src/main/res/xml/preferences.xml index c33c0714..611484e9 100644 --- a/PdCore/pd-core/src/main/res/xml/preferences.xml +++ b/PdCore/pd-core/src/main/res/xml/preferences.xml @@ -5,9 +5,15 @@ android:entries="@array/srate_labels" android:key="@string/pref_key_srate" android:title="@string/pref_title_srate" android:summary="@string/pref_sum_srate"> + + From 58ede490c8378c9d0721d9b5752f0760593cfc2b Mon Sep 17 00:00:00 2001 From: Antoine Rousseau <_antoine_@metalu.net> Date: Thu, 11 Dec 2025 13:56:25 +0100 Subject: [PATCH 3/4] update demo apps for oboe changes - add MODIFY_AUDIO_SETTINGS permission, which oboe needs to give best results - add "APP_STL := c++_shared" to Application.mk when the app needs native compilation --- CircleOfFifths/AndroidManifest.xml | 1 + PdTest/AndroidManifest.xml | 3 ++- PdTest/jni/Application.mk | 2 +- ScenePlayer/AndroidManifest.xml | 3 ++- ScenePlayer/jni/Application.mk | 2 +- Voice-O-Rama/AndroidManifest.xml | 1 + 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CircleOfFifths/AndroidManifest.xml b/CircleOfFifths/AndroidManifest.xml index 203ba742..41838ba5 100644 --- a/CircleOfFifths/AndroidManifest.xml +++ b/CircleOfFifths/AndroidManifest.xml @@ -1,5 +1,6 @@ + - + + diff --git a/PdTest/jni/Application.mk b/PdTest/jni/Application.mk index 1dfcd802..234df209 100644 --- a/PdTest/jni/Application.mk +++ b/PdTest/jni/Application.mk @@ -1,4 +1,4 @@ APP_OPTIM := release APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 APP_ALLOW_MISSING_DEPS=true - +APP_STL := c++_shared diff --git a/ScenePlayer/AndroidManifest.xml b/ScenePlayer/AndroidManifest.xml index cf6c4342..565d703c 100644 --- a/ScenePlayer/AndroidManifest.xml +++ b/ScenePlayer/AndroidManifest.xml @@ -10,6 +10,7 @@ + @@ -64,4 +65,4 @@ - \ No newline at end of file + diff --git a/ScenePlayer/jni/Application.mk b/ScenePlayer/jni/Application.mk index 1dfcd802..234df209 100644 --- a/ScenePlayer/jni/Application.mk +++ b/ScenePlayer/jni/Application.mk @@ -1,4 +1,4 @@ APP_OPTIM := release APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 APP_ALLOW_MISSING_DEPS=true - +APP_STL := c++_shared diff --git a/Voice-O-Rama/AndroidManifest.xml b/Voice-O-Rama/AndroidManifest.xml index 75dab709..b4340e15 100644 --- a/Voice-O-Rama/AndroidManifest.xml +++ b/Voice-O-Rama/AndroidManifest.xml @@ -5,6 +5,7 @@ + Date: Thu, 11 Dec 2025 18:59:56 +0100 Subject: [PATCH 4/4] bump version to 1.4.0 --- PdCore/build.gradle | 2 +- README.md | 2 +- build.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PdCore/build.gradle b/PdCore/build.gradle index 0606fefb..776de94b 100644 --- a/PdCore/build.gradle +++ b/PdCore/build.gradle @@ -23,7 +23,7 @@ allprojects { // These are specific to PdCore, but nexusPublishing needs them here: // https://github.com/gradle-nexus/publish-plugin/issues/84 group = 'io.github.libpd.android' -version = '1.3.0-SNAPSHOT' +version = '1.4.0-SNAPSHOT' // Create a Sonatype user token for these environment variables: // export ORG_GRADLE_PROJECT_sonatypeUsername="" diff --git a/README.md b/README.md index 5d85c7e8..243a6a60 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ allprojects { ```gradle dependencies { - implementation 'io.github.libpd.android:pd-core:1.3.0-SNAPSHOT' + implementation 'io.github.libpd.android:pd-core:1.4.0-SNAPSHOT' } ``` diff --git a/build.gradle b/build.gradle index 818b8d93..80c075ba 100644 --- a/build.gradle +++ b/build.gradle @@ -20,5 +20,5 @@ ext { compileSdk = 34 androidxLegacySupportVersion = '1.0.0' ndkVersion = libs.versions.ndk.get() - pdCoreVersion = '1.3.0-SNAPSHOT' // Must match version in PdCore/build.gradle + pdCoreVersion = '1.4.0-SNAPSHOT' // Must match version in PdCore/build.gradle }