Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CircleOfFifths/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".CircleOfFifths" android:label="@string/app_name"
android:screenOrientation="portrait" android:theme="@style/DisableSoundEffects"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private void initGui() {
private void initPd() throws IOException {
AudioParameters.init(this);
int srate = Math.max(MIN_SAMPLE_RATE, AudioParameters.suggestSampleRate());
PdAudio.initAudio(srate, 0, 2, 1, true);
PdAudio.initAudio(srate, 0, -1, 2, -1, 1, true);

File dir = getFilesDir();
File patchFile = new File(dir, "chords.pd");
Expand Down
12 changes: 10 additions & 2 deletions PdCore/pd-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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/oboe'
}
}

Expand Down
45 changes: 39 additions & 6 deletions PdCore/pd-core/src/main/java/org/puredata/android/io/PdAudio.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Arrays;

import org.puredata.core.PdBase;
import org.puredata.core.PdBaseLoader;

import android.content.Context;
import android.os.Handler;
Expand Down Expand Up @@ -40,6 +41,30 @@ private PdAudio() {
// Do nothing; we just don't want instances of this class.
}

private static PdBaseLoader oboeLoader = new PdBaseLoader() {
@Override
public void load() {
try {
System.loadLibrary("pd");
System.loadLibrary("pdnativeoboe");
} catch (Exception e) {}
}
};

/**
* This function must be called before using any PdBase methods.
*/
public static void setupNativeLoader() {
PdBaseLoader.loaderHandler = oboeLoader;
}

/* Most audio device control is supplied by PdBase, however some
* additional Oboe settings are made available by PdAudio.
*/
public native static void setRecordingDeviceId(int deviceId);
public native static void setPlaybackDeviceId(int deviceId);
public native static void setBufferSizeInFrames(int frames);

/**
* Initializes Pure Data as well as audio components.
*
Expand All @@ -52,14 +77,22 @@ private PdAudio() {
* @param restart flag indicating whether the audio thread should be stopped if it is currently running
* @throws IOException if the audio parameters are not supported by the device
*/
public synchronized static void initAudio(int sampleRate, int inChannels, int outChannels, final int ticksPerBuffer, boolean restart)
throws IOException {
public synchronized static void initAudio(
int sampleRate,
int inDeviceId, int inChannels,
int outDeviceId, int outChannels,
final int ticksPerBuffer, boolean restart
) throws IOException {
setupNativeLoader();
if (isRunning() && !restart) return;
stopAudio();
if (PdBase.openAudio(inChannels, outChannels, sampleRate, null) != 0) {
throw new IOException("unable to open Pd audio: " + sampleRate + ", " + inChannels + ", " + outChannels);
}
if (!PdBase.implementsAudio()) {
if (PdBase.implementsAudio()) {
if (PdBase.openAudio(inChannels, outChannels, sampleRate, null) != 0) {
throw new IOException("unable to open Pd audio: " + sampleRate + ", " + inChannels + ", " + outChannels);
}
setRecordingDeviceId(inDeviceId);
setPlaybackDeviceId(outDeviceId);
} else {
if (!AudioParameters.checkParameters(sampleRate, inChannels, outChannels) || ticksPerBuffer <= 0) {
throw new IOException("bad Java audio parameters: " + sampleRate + ", " + inChannels + ", " + outChannels + ", " + ticksPerBuffer);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
*
* 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.service;

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<String> mEntries = new ArrayList<>();
private List<String> mValues = new ArrayList<>();

private CharSequence[] listToArray(List<String> 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
*/
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@

import org.puredata.android.io.AudioParameters;
import org.puredata.core.PdBase;
import org.puredata.android.io.PdAudio;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.preference.PreferenceFragment;

/**
*
Expand All @@ -27,34 +29,51 @@
*/
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
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.
*
* @param context current application context
*/
public static void initPreferences(Context context) {
// make sure native handler is setup, because we need PdBase here:
PdAudio.setupNativeLoader();
Resources res = context.getResources();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (!prefs.contains(res.getString(R.string.pref_key_srate))) {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -137,7 +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.initAudio(srate, nic, noc, tpb, true);
PdAudio.initAudio(srate, in_id, nic, out_id, noc, tpb, true);
sampleRate = srate;
inputChannels = nic;
outputChannels = noc;
Expand Down Expand Up @@ -213,6 +225,9 @@ public boolean onUnbind(Intent intent) {
@Override
public void onCreate() {
super.onCreate();
// make sure native handler is setup, because we need PdBase here:
PdAudio.setupNativeLoader();

AudioParameters.init(this);
if (!abstractionsInstalled) {
try {
Expand Down
1 change: 1 addition & 0 deletions PdCore/pd-core/src/main/jni/Android.mk
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
include $(call all-subdir-makefiles)

1 change: 1 addition & 0 deletions PdCore/pd-core/src/main/jni/Application.mk
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
APP_PLATFORM := android-28
APP_OPTIM := release
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
APP_STL := c++_shared
13 changes: 13 additions & 0 deletions PdCore/pd-core/src/main/jni/oboe/Android.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Build Oboe JNI binary

include $(CLEAR_VARS)

LOCAL_MODULE := pdnativeoboe
LOCAL_C_INCLUDES := $(PD_C_INCLUDES) $(LOCAL_PATH)/jni
LOCAL_SRC_FILES := ../oboe/z_jni_oboe_shared.c ../oboe/z_jni_oboe.cpp ../oboe/OboeEngine.cpp
LOCAL_SHARED_LIBRARIES := pd oboe
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)

$(call import-module,prefab/oboe)

Loading