diff --git a/Patchfield/.classpath b/Patchfield/.classpath
new file mode 100644
index 0000000..5cc5eb9
--- /dev/null
+++ b/Patchfield/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/Patchfield/.project b/Patchfield/.project
new file mode 100644
index 0000000..d58ccdb
--- /dev/null
+++ b/Patchfield/.project
@@ -0,0 +1,33 @@
+
+
+ Patchfield
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/Patchfield/.settings/org.eclipse.jdt.core.prefs b/Patchfield/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..b080d2d
--- /dev/null
+++ b/Patchfield/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/Patchfield/AndroidManifest.xml b/Patchfield/AndroidManifest.xml
new file mode 100644
index 0000000..2f67be7
--- /dev/null
+++ b/Patchfield/AndroidManifest.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/Patchfield/Makefile b/Patchfield/Makefile
new file mode 100644
index 0000000..eb116e7
--- /dev/null
+++ b/Patchfield/Makefile
@@ -0,0 +1,10 @@
+all:
+ javah -classpath bin/classes -o jni/internal/audio_module_java.h \
+ com.noisepages.nettoyeur.patchfield.AudioModule
+ javah -classpath bin/classes -o jni/internal/patchfield.h \
+ com.noisepages.nettoyeur.patchfield.Patchfield
+ javah -classpath bin/classes -o jni/internal/shared_memory_utils.h \
+ com.noisepages.nettoyeur.patchfield.internal.SharedMemoryUtils
+ javah -classpath bin/classes -o jni/modules/javamodule.h \
+ com.noisepages.nettoyeur.patchfield.modules.JavaModule
+ ndk-build
diff --git a/Patchfield/jni/Android.mk b/Patchfield/jni/Android.mk
new file mode 100644
index 0000000..b328f78
--- /dev/null
+++ b/Patchfield/jni/Android.mk
@@ -0,0 +1,66 @@
+LOCAL_PATH := $(call my-dir)
+
+# Public libraries.
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := audiomodule
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
+LOCAL_EXPORT_CFLAGS := -Wno-int-to-pointer-cast -Wno-pointer-to-int-cast
+LOCAL_SRC_FILES := internal/audio_module.c
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := buffersizeadapter
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
+LOCAL_SRC_FILES := utils/buffer_size_adapter.c
+LOCAL_STATIC_LIBRARIES := audiomodule
+include $(BUILD_STATIC_LIBRARY)
+
+
+# Java Audio module.
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := javamodule
+LOCAL_LDLIBS := -llog
+LOCAL_SRC_FILES := modules/javamodule.c internal/simple_barrier.c
+LOCAL_STATIC_LIBRARIES := audiomodule buffersizeadapter
+include $(BUILD_SHARED_LIBRARY)
+
+
+# Internal libraries.
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := audiomoduleinternal
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
+LOCAL_EXPORT_CFLAGS := -Wno-int-to-pointer-cast -Wno-pointer-to-int-cast
+LOCAL_EXPORT_LDLIBS := -lOpenSLES -llog
+LOCAL_SRC_FILES := internal/audio_module_internal.c \
+ internal/simple_barrier.c internal/shared_memory_internal.c \
+ opensl_stream/opensl_stream.c
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := audiomodulejava
+LOCAL_STATIC_LIBRARIES := audiomoduleinternal
+LOCAL_SRC_FILES := internal/audio_module_java.c
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := patchfield
+LOCAL_STATIC_LIBRARIES := audiomoduleinternal
+LOCAL_SRC_FILES := internal/patchfield.c
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := shared_memory_utils
+LOCAL_LDLIBS := -llog
+LOCAL_SRC_FILES := internal/shared_memory_utils.c \
+ internal/shared_memory_internal.c
+include $(BUILD_SHARED_LIBRARY)
diff --git a/Patchfield/jni/Application.mk b/Patchfield/jni/Application.mk
new file mode 100644
index 0000000..84a096d
--- /dev/null
+++ b/Patchfield/jni/Application.mk
@@ -0,0 +1,2 @@
+APP_OPTIM := release
+APP_ABI := armeabi armeabi-v7a x86
diff --git a/Patchfield/jni/audio_module.h b/Patchfield/jni/audio_module.h
new file mode 100644
index 0000000..15f313b
--- /dev/null
+++ b/Patchfield/jni/audio_module.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Native library for use with the native components of subclasses of
+ * AudioModule.java. Audio modules must implement a process callback of type
+ * audio_module_process_t and hook up the am_configure function in this library
+ * to the corresponding configure method in the Java class. See
+ * LowpassModule.java and lowpass.{h,c} for an example of this interaction.
+ */
+
+#ifndef __AUDIO_MODULE_H__
+#define __AUDIO_MODULE_H__
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Processing callback; takes a processing context (which is just a pointer to
+ * whatever data you want to pass to the callback), the sample rate, the buffer
+ * size in frames, the number of input and output channels, as well as input
+ * and output buffers whose size is the number of channels times the number of
+ * frames per buffer. Buffers are non-interleaved.
+ *
+ * This function will be invoked on a dedicated audio thread, and so any data
+ * in the context that may be modified concurrently must be protected (e.g., by
+ * gcc atomics) to prevent race conditions.
+ */
+typedef void (*audio_module_process_t)
+ (void *context, int sample_rate, int buffer_frames,
+ int input_channels, const float *input_buffer,
+ int output_channels, float *output_buffer);
+
+/*
+ * Configures the audio module with a native audio processing method and
+ * its context. The handle parameter is the handle that the AudioModule class
+ * passes to the protected configure method. On the Java side, this handle is
+ * of type long, and so in C it must be cast from jlong to void*.
+ */
+void am_configure(void *handle, audio_module_process_t process, void *context);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/Patchfield/jni/internal/audio_module.c b/Patchfield/jni/internal/audio_module.c
new file mode 100644
index 0000000..443d0d5
--- /dev/null
+++ b/Patchfield/jni/internal/audio_module.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "audio_module.h"
+
+#include "internal/audio_module_internal.h"
+
+void am_configure(void *handle, audio_module_process_t process, void *context) {
+ audio_module_runner *amr = (audio_module_runner *) handle;
+ amr->process = process;
+ amr->context = context;
+}
diff --git a/Patchfield/jni/internal/audio_module_internal.c b/Patchfield/jni/internal/audio_module_internal.c
new file mode 100644
index 0000000..5efe10d
--- /dev/null
+++ b/Patchfield/jni/internal/audio_module_internal.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "audio_module_internal.h"
+
+#include "opensl_stream/opensl_stream.h"
+#include "audio_module_internal.h"
+#include "shared_memory_internal.h"
+#include "simple_barrier.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define LOGI(...) \
+ __android_log_print(ANDROID_LOG_INFO, "audio_module_internal", __VA_ARGS__)
+#define LOGW(...) \
+ __android_log_print(ANDROID_LOG_WARN, "audio_module_internal", __VA_ARGS__)
+
+audio_module *ami_get_audio_module(void *p, int index) {
+ return ((audio_module *) p) + index;
+}
+
+float *ami_get_audio_buffer(void *p, ptrdiff_t offset) {
+ return ((float *) p) + offset;
+}
+
+simple_barrier_t *ami_get_barrier(void *p, ptrdiff_t offset) {
+ return ((simple_barrier_t *) p) + offset;
+}
+
+void ami_collect_input(void *p, int index) {
+ audio_module *module = ami_get_audio_module(p, index);
+ float *input_buffer = ami_get_audio_buffer(p, module->input_buffer);
+ memset(input_buffer, 0,
+ module->buffer_frames * module->input_channels * sizeof(float));
+ int i, j;
+ for (i = 0; i < MAX_CONNECTIONS; ++i) {
+ connection *conn = module->input_connections + i;
+ if (conn->in_use) {
+ audio_module *source = ami_get_audio_module(p, conn->source_index);
+ if (source->in_use) {
+ float *input_channel =
+ input_buffer + conn->sink_port * module->buffer_frames;
+ float *source_channel = ami_get_audio_buffer(p,
+ source->output_buffer) + conn->source_port * module->buffer_frames;
+ if (!sb_wait(ami_get_barrier(p, source->ready), &source->deadline)) {
+ for (j = 0; j < module->buffer_frames; ++j) {
+ input_channel[j] += source_channel[j];
+ }
+ }
+ }
+ }
+ }
+}
+
+#define AM_SIG_ALRM SIGRTMAX
+
+static __thread sigjmp_buf sig_env;
+
+static void signal_handler(int sig, siginfo_t *info, void *context) {
+ LOGI("Received signal %d.", sig);
+ siglongjmp(sig_env, 1);
+}
+
+static void *run_module(void *arg) {
+ LOGI("Entering run_module.");
+ audio_module_runner *amr = (audio_module_runner *) arg;
+ sb_wake(&amr->launched);
+ audio_module *module = ami_get_audio_module(amr->shm_ptr, amr->index);
+
+ timer_t timer;
+ struct sigevent evp;
+ evp.sigev_notify = SIGEV_THREAD_ID;
+ evp.sigev_signo = AM_SIG_ALRM;
+ evp.sigev_value.sival_ptr = module;
+ evp.sigev_notify_thread_id = gettid();
+ timer_create(CLOCK_MONOTONIC, &evp, &timer);
+
+ struct itimerspec timeout;
+ timeout.it_interval.tv_sec = 0;
+ timeout.it_interval.tv_nsec = 0;
+ timeout.it_value.tv_sec = 1; // One-second timeout.
+ timeout.it_value.tv_nsec = 0;
+
+ struct itimerspec cancel;
+ cancel.it_interval.tv_sec = 0;
+ cancel.it_interval.tv_nsec = 0;
+ cancel.it_value.tv_sec = 0;
+ cancel.it_value.tv_nsec = 0;
+
+ if (!sigsetjmp(sig_env, 1)) {
+ while (1) {
+ sb_wake(ami_get_barrier(amr->shm_ptr, module->report));
+ sb_wait_and_clear(ami_get_barrier(amr->shm_ptr, module->wake), NULL);
+ if (amr->done) {
+ break;
+ }
+ ami_collect_input(amr->shm_ptr, amr->index);
+ timer_settime(timer, 0, &timeout, NULL); // Arm timer.
+ amr->process(amr->context, module->sample_rate, module->buffer_frames,
+ module->input_channels,
+ ami_get_audio_buffer(amr->shm_ptr, module->input_buffer),
+ module->output_channels,
+ ami_get_audio_buffer(amr->shm_ptr, module->output_buffer));
+ timer_settime(timer, 0, &cancel, NULL); // Disarm timer.
+ sb_wake(ami_get_barrier(amr->shm_ptr, module->ready));
+ }
+ } else {
+ __sync_bool_compare_and_swap(&amr->timed_out, 0, 1);
+ // We can safely log now because we have already left the processing chain.
+ LOGW("Process callback interrupted after timeout; terminating thread.");
+ }
+
+ timer_delete(timer);
+ LOGI("Leaving run_module.");
+ return NULL;
+}
+
+static void launch_thread(void *context, int sample_rate, int buffer_frames,
+ int input_channels, const short *input_buffer,
+ int output_channels, short *output_buffer) {
+ audio_module_runner *amr = (audio_module_runner *) context;
+ if (!--amr->launch_counter) {
+ if (pthread_create(&amr->thread, NULL, run_module, amr)) {
+ LOGW("Thread creation failed: %s", strerror(errno));
+ }
+ }
+}
+
+static size_t get_protected_size() {
+ return BARRIER_OFFSET * MEM_PAGE_SIZE;
+}
+
+audio_module_runner *ami_create(int version, int token, int index) {
+ if (version != PATCHFIELD_PROTOCOL_VERSION) {
+ LOGW("Protocol version mismatch.");
+ return NULL;
+ }
+ audio_module_runner *amr = malloc(sizeof(audio_module_runner));
+ if (amr) {
+ amr->shm_fd = token;
+ amr->shm_ptr = smi_map(token);
+ smi_protect(amr->shm_ptr, get_protected_size());
+ amr->index = index;
+ amr->done = 0;
+ amr->timed_out = 0;
+ amr->process = NULL;
+ amr->context = NULL;
+ amr->launch_counter = 3; // Make sure that this number stays current.
+
+ audio_module *module = ami_get_audio_module(amr->shm_ptr, amr->index);
+ // Clear barriers, just in case.
+ sb_clobber(ami_get_barrier(amr->shm_ptr, module->report));
+ sb_clobber(ami_get_barrier(amr->shm_ptr, module->wake));
+ sb_clobber(ami_get_barrier(amr->shm_ptr, module->ready));
+
+ OPENSL_STREAM *os = opensl_open(module->sample_rate, 0, 2,
+ module->buffer_frames, launch_thread, amr);
+ sb_clobber(&amr->launched);
+ opensl_start(os);
+ sb_wait(&amr->launched, NULL);
+ opensl_close(os);
+
+ struct sigaction act;
+ act.sa_sigaction = signal_handler;
+ act.sa_flags = SA_SIGINFO;
+ sigfillset(&act.sa_mask);
+ sigaction(AM_SIG_ALRM, &act, NULL);
+ }
+ return (audio_module_runner *) amr;
+}
+
+void ami_release(audio_module_runner *amr) {
+ audio_module *module = ami_get_audio_module(amr->shm_ptr, amr->index);
+
+ amr->done = 1;
+ sb_wake(ami_get_barrier(amr->shm_ptr, module->wake));
+ pthread_join(amr->thread, NULL);
+
+ smi_unmap(amr->shm_ptr);
+ free(amr);
+}
+
+int ami_has_timed_out(audio_module_runner *amr) {
+ return __sync_or_and_fetch(&amr->timed_out, 0);
+}
diff --git a/Patchfield/jni/internal/audio_module_internal.h b/Patchfield/jni/internal/audio_module_internal.h
new file mode 100644
index 0000000..63f3107
--- /dev/null
+++ b/Patchfield/jni/internal/audio_module_internal.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Internal data structures representing audio modules and their connections,
+ * as well as some functions that operate on them.
+ */
+
+#ifndef __AUDIO_MODULE_INTERNAL_H__
+#define __AUDIO_MODULE_INTERNAL_H__
+
+#include "audio_module.h"
+
+#include "simple_barrier.h"
+
+#include
+#include
+#include
+#include
+
+#define PATCHFIELD_PROTOCOL_VERSION 6
+
+#define MAX_MODULES 32
+#define MAX_CONNECTIONS 16
+
+#define MEM_PAGE_SIZE sysconf(_SC_PAGESIZE)
+#define BARRIER_OFFSET (MAX_MODULES * sizeof(audio_module) / MEM_PAGE_SIZE + 1)
+#define BUFFER_OFFSET \
+ (BARRIER_OFFSET + MAX_MODULES * 3 * sizeof(int) / MEM_PAGE_SIZE + 1)
+
+typedef struct {
+ int status; // 0: none; 1: current; 2: slated for deletion
+ int in_use;
+
+ int source_index;
+ int source_port;
+ int sink_port;
+} connection;
+
+typedef struct {
+ int status; // 0: none; 1: current; 2: slated for deletion
+ int active;
+ int in_use;
+
+ int sample_rate;
+ int buffer_frames;
+
+ int input_channels;
+ ptrdiff_t input_buffer; // Storing buffers as offsets of type ptrdiff_t
+ int output_channels; // rather than pointers of type float* to render
+ ptrdiff_t output_buffer; // them independent of the shared memory location.
+
+ connection input_connections[MAX_CONNECTIONS];
+
+ struct timespec deadline;
+ ptrdiff_t report;
+ ptrdiff_t wake;
+ ptrdiff_t ready;
+} audio_module;
+
+typedef struct {
+ int shm_fd;
+ void *shm_ptr;
+ int index;
+ pthread_t thread;
+ simple_barrier_t launched;
+ int launch_counter;
+ int done;
+ int timed_out;
+ audio_module_process_t process;
+ void *context;
+} audio_module_runner;
+
+audio_module *ami_get_audio_module(void *p, int index);
+float *ami_get_audio_buffer(void *p, ptrdiff_t offset);
+simple_barrier_t *ami_get_barrier(void *p, ptrdiff_t offset);
+void ami_collect_input(void *p, int index);
+audio_module_runner *ami_create(int version, int token, int index);
+void ami_release(audio_module_runner *p);
+int ami_has_timed_out(audio_module_runner *p);
+
+#endif
diff --git a/Patchfield/jni/internal/audio_module_java.c b/Patchfield/jni/internal/audio_module_java.c
new file mode 100644
index 0000000..0bc4f39
--- /dev/null
+++ b/Patchfield/jni/internal/audio_module_java.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "audio_module_java.h"
+
+#include "audio_module_internal.h"
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_AudioModule_getProtocolVersion
+(JNIEnv *env, jclass cls) {
+ return PATCHFIELD_PROTOCOL_VERSION;
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_noisepages_nettoyeur_patchfield_AudioModule_createRunner
+(JNIEnv *env, jobject obj, jint version, jint token, jint index) {
+ return (jlong) ami_create(version, token, index);
+}
+
+JNIEXPORT void JNICALL
+Java_com_noisepages_nettoyeur_patchfield_AudioModule_release
+(JNIEnv *env, jobject obj, jlong p) {
+ audio_module_runner *amr = (audio_module_runner *) p;
+ ami_release(amr);
+}
+
+JNIEXPORT jboolean JNICALL
+Java_com_noisepages_nettoyeur_patchfield_AudioModule_hasTimedOut
+(JNIEnv *env, jobject obj, jlong p) {
+ audio_module_runner *amr = (audio_module_runner *) p;
+ return ami_has_timed_out(amr);
+}
diff --git a/Patchfield/jni/internal/audio_module_java.h b/Patchfield/jni/internal/audio_module_java.h
new file mode 100644
index 0000000..c30929c
--- /dev/null
+++ b/Patchfield/jni/internal/audio_module_java.h
@@ -0,0 +1,62 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class com_noisepages_nettoyeur_patchfield_AudioModule */
+
+#ifndef _Included_com_noisepages_nettoyeur_patchfield_AudioModule
+#define _Included_com_noisepages_nettoyeur_patchfield_AudioModule
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_AudioModule
+ * Method: getProtocolVersion
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_AudioModule_getProtocolVersion
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_AudioModule
+ * Method: createRunner
+ * Signature: (III)J
+ */
+JNIEXPORT jlong JNICALL Java_com_noisepages_nettoyeur_patchfield_AudioModule_createRunner
+ (JNIEnv *, jobject, jint, jint, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_AudioModule
+ * Method: release
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_noisepages_nettoyeur_patchfield_AudioModule_release
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_AudioModule
+ * Method: hasTimedOut
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_noisepages_nettoyeur_patchfield_AudioModule_hasTimedOut
+ (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* Header for class com_noisepages_nettoyeur_patchfield_AudioModule_FdReceiverThread */
+
+#ifndef _Included_com_noisepages_nettoyeur_patchfield_AudioModule_FdReceiverThread
+#define _Included_com_noisepages_nettoyeur_patchfield_AudioModule_FdReceiverThread
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef com_noisepages_nettoyeur_patchfield_AudioModule_FdReceiverThread_MIN_PRIORITY
+#define com_noisepages_nettoyeur_patchfield_AudioModule_FdReceiverThread_MIN_PRIORITY 1L
+#undef com_noisepages_nettoyeur_patchfield_AudioModule_FdReceiverThread_NORM_PRIORITY
+#define com_noisepages_nettoyeur_patchfield_AudioModule_FdReceiverThread_NORM_PRIORITY 5L
+#undef com_noisepages_nettoyeur_patchfield_AudioModule_FdReceiverThread_MAX_PRIORITY
+#define com_noisepages_nettoyeur_patchfield_AudioModule_FdReceiverThread_MAX_PRIORITY 10L
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/Patchfield/jni/internal/patchfield.c b/Patchfield/jni/internal/patchfield.c
new file mode 100644
index 0000000..22b6299
--- /dev/null
+++ b/Patchfield/jni/internal/patchfield.c
@@ -0,0 +1,464 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "patchfield.h"
+
+#include "audio_module_internal.h"
+#include "opensl_stream/opensl_stream.h"
+#include "shared_memory_internal.h"
+#include "simple_barrier.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define LOGI(...) \
+ __android_log_print(ANDROID_LOG_INFO, "patchfield", __VA_ARGS__)
+#define LOGW(...) \
+ __android_log_print(ANDROID_LOG_WARN, "patchfield", __VA_ARGS__)
+
+typedef struct {
+ OPENSL_STREAM *os;
+ int sample_rate;
+ int buffer_frames;
+ int shm_fd;
+ void *shm_ptr;
+ ptrdiff_t next_buffer;
+} patchfield;
+
+static void perform_cleanup(patchfield *pb) {
+ int i, j, k;
+ for (i = 0; i < MAX_MODULES; ++i) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, i);
+ if (__sync_or_and_fetch(&module->status, 0) == 2) {
+ int buffer_frames = (module->input_channels + module->output_channels) *
+ pb->buffer_frames;
+ pb->next_buffer -= buffer_frames;
+ for (j = 0; j < MAX_MODULES; ++j) {
+ audio_module *other = ami_get_audio_module(pb->shm_ptr, j);
+ if (other->input_buffer > module->input_buffer) {
+ other->input_buffer -= buffer_frames;
+ other->output_buffer -= buffer_frames;
+ }
+ for (k = 0; k < MAX_CONNECTIONS; ++k) {
+ connection *conn = other->input_connections + k;
+ if (conn->source_index == i &&
+ __sync_or_and_fetch(&conn->status, 0)) {
+ int val = 1;
+ while (val = __sync_val_compare_and_swap(&conn->status, val, 0));
+ }
+ }
+ }
+ __sync_bool_compare_and_swap(&module->status, 2, 0);
+ } else {
+ for (j = 0; j < MAX_CONNECTIONS; ++j) {
+ connection *conn = module->input_connections + j;
+ __sync_bool_compare_and_swap(&conn->status, 2, 0);
+ }
+ }
+ }
+}
+
+static int is_running(patchfield *pb) {
+ return opensl_is_running(pb->os);
+}
+
+static int add_module(patchfield *pb,
+ int input_channels, int output_channels) {
+ if (!is_running(pb)) {
+ perform_cleanup(pb);
+ }
+ if ((pb->next_buffer + (input_channels + output_channels) *
+ pb->buffer_frames) * sizeof(float) > smi_get_size()) {
+ return -9; // PatchfieldException.OUT_OF_BUFFER_SPACE
+ }
+ int i;
+ for (i = 0; i < MAX_MODULES; ++i) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, i);
+ if (__sync_or_and_fetch(&module->status, 0) == 0) {
+ module->active = 0;
+ module->in_use = 0;
+ module->sample_rate = pb->sample_rate;
+ module->buffer_frames = pb->buffer_frames;
+ module->input_channels = input_channels;
+ module->input_buffer = pb->next_buffer;
+ pb->next_buffer += input_channels * pb->buffer_frames;
+ module->output_channels = output_channels;
+ module->output_buffer = pb->next_buffer;
+ pb->next_buffer += output_channels * pb->buffer_frames;
+ module->report =
+ BARRIER_OFFSET * MEM_PAGE_SIZE / sizeof(simple_barrier_t) + i * 3;
+ sb_clobber(ami_get_barrier(pb->shm_ptr, module->report));
+ module->wake = module->report + 1;
+ sb_clobber(ami_get_barrier(pb->shm_ptr, module->wake));
+ module->ready = module->report + 2;
+ sb_clobber(ami_get_barrier(pb->shm_ptr, module->ready));
+ memset(module->input_connections, 0,
+ MAX_CONNECTIONS * sizeof(connection));
+ __sync_bool_compare_and_swap(&module->status, 0, 1);
+ return i;
+ }
+ }
+ return -5; // PatchfieldException.TOO_MANY_MODULES
+}
+
+static int delete_module(patchfield *pb, int index) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, index);
+ __sync_bool_compare_and_swap(&module->status, 1, 2);
+ return 0;
+}
+
+static int activate_module(patchfield *pb, int index) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, index);
+ __sync_bool_compare_and_swap(&module->active, 0, 1);
+ return 0;
+}
+static int deactivate_module(patchfield *pb, int index) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, index);
+ __sync_bool_compare_and_swap(&module->active, 1, 0);
+ return 0;
+}
+
+static int is_active(patchfield *pb, int index) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, index);
+ return __sync_or_and_fetch(&module->active, 0);
+}
+
+static int get_input_channels(patchfield *pb, int index) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, index);
+ return module->input_channels;
+}
+
+static int get_output_channels(patchfield *pb, int index) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, index);
+ return module->output_channels;
+}
+
+static int is_connected(patchfield *pb, int source_index, int source_port,
+ int sink_index, int sink_port) {
+ audio_module *sink = ami_get_audio_module(pb->shm_ptr, sink_index);
+ int i;
+ for (i = 0; i < MAX_CONNECTIONS; ++i) {
+ connection *input = sink->input_connections + i;
+ if (input->source_index == source_index &&
+ input->source_port == source_port &&
+ input->sink_port == sink_port &&
+ __sync_or_and_fetch(&input->status, 0) == 1) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int connect_modules(patchfield *pb, int source_index, int source_port,
+ int sink_index, int sink_port) {
+ if (!is_running(pb)) {
+ perform_cleanup(pb);
+ }
+ audio_module *sink = ami_get_audio_module(pb->shm_ptr, sink_index);
+ int i;
+ for (i = 0; i < MAX_CONNECTIONS; ++i) {
+ connection *input = sink->input_connections + i;
+ if (__sync_or_and_fetch(&input->status, 0) == 0) {
+ input->sink_port = sink_port;
+ input->source_index = source_index;
+ input->source_port = source_port;
+ __sync_bool_compare_and_swap(&input->status, 0, 1);
+ return 0;
+ }
+ }
+ return -7; // PatchfieldException.TOO_MANY_CONNECTIONS
+}
+
+static int disconnect_modules(patchfield *pb, int source_index, int source_port,
+ int sink_index, int sink_port) {
+ audio_module *sink = ami_get_audio_module(pb->shm_ptr, sink_index);
+ int i;
+ for (i = 0; i < MAX_CONNECTIONS; ++i) {
+ connection *input = sink->input_connections + i;
+ if (input->source_index == source_index &&
+ input->source_port == source_port &&
+ input->sink_port == sink_port &&
+ __sync_bool_compare_and_swap(&input->status, 1, 2)) {
+ break;
+ }
+ }
+ return 0;
+}
+
+static void release(patchfield *pb) {
+ int i;
+ opensl_close(pb->os);
+ smi_unlock(pb->shm_ptr);
+ smi_unmap(pb->shm_ptr);
+ close(pb->shm_fd);
+ free(pb);
+}
+
+#define ONE_BILLION 1000000000
+
+static void add_nsecs(struct timespec *t, int dt) {
+ t->tv_nsec += dt;
+ if (t->tv_nsec >= ONE_BILLION) {
+ ++t->tv_sec;
+ t->tv_nsec -= ONE_BILLION;
+ }
+}
+
+static const float float_to_short = SHRT_MAX;
+static const float short_to_float = 1 / (1 + (float) SHRT_MAX);
+
+static void process(void *context, int sample_rate, int buffer_frames,
+ int input_channels, const short *input_buffer,
+ int output_channels, short *output_buffer) {
+ patchfield *pb = (patchfield *) context;
+ struct timespec deadline;
+ clock_gettime(CLOCK_MONOTONIC, &deadline);
+ add_nsecs(&deadline, 100000); // 0.1ms deadline for clients to report.
+ int i, j;
+ for (i = 0; i < MAX_MODULES; ++i) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, i);
+ module->in_use =
+ __sync_or_and_fetch(&module->status, 0) == 1 &&
+ __sync_or_and_fetch(&module->active, 0) &&
+ ((i < 2) || sb_wait_and_clear(
+ ami_get_barrier(pb->shm_ptr, module->report), &deadline) == 0);
+ if (module->in_use) {
+ sb_clobber(ami_get_barrier(pb->shm_ptr, module->ready));
+ for (j = 0; j < MAX_CONNECTIONS; ++j) {
+ connection *conn = module->input_connections + j;
+ conn->in_use = (__sync_or_and_fetch(&conn->status, 0) == 1);
+ }
+ }
+ }
+ audio_module *input = ami_get_audio_module(pb->shm_ptr, 0);
+ if (input->in_use) {
+ float *b = ami_get_audio_buffer(pb->shm_ptr, input->output_buffer);
+ for (i = 0; i < input_channels; ++i) {
+ for (j = 0; j < buffer_frames; ++j) {
+ b[j] = input_buffer[i + j * input_channels] * short_to_float;
+ }
+ b += buffer_frames;
+ }
+ sb_wake(ami_get_barrier(pb->shm_ptr, input->ready));
+ }
+ int dt = (ONE_BILLION / sample_rate + 1) * buffer_frames;
+ clock_gettime(CLOCK_MONOTONIC, &deadline);
+ add_nsecs(&deadline, 2 * dt); // Two-buffer-period processing deadline.
+ for (i = 2; i < MAX_MODULES; ++i) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, i);
+ if (module->in_use) {
+ module->deadline.tv_sec = deadline.tv_sec;
+ module->deadline.tv_nsec = deadline.tv_nsec;
+ sb_wake(ami_get_barrier(pb->shm_ptr, module->wake));
+ }
+ }
+ audio_module *output = ami_get_audio_module(pb->shm_ptr, 1);
+ if (output->in_use) {
+ ami_collect_input(pb->shm_ptr, 1);
+ float *b = ami_get_audio_buffer(pb->shm_ptr, output->input_buffer);
+ for (i = 0; i < output_channels; ++i) {
+ for (j = 0; j < buffer_frames; ++j) {
+ float v = b[j];
+ output_buffer[i + j * output_channels] = (short) (float_to_short *
+ (isnan(v) ? 0 : (v < -1.0f ? -1.0f : (v > 1.0f ? 1.0f : v))));
+ }
+ b += buffer_frames;
+ }
+ }
+ for (i = 2; i < MAX_MODULES; ++i) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, i);
+ if (module->in_use) {
+ sb_wait(ami_get_barrier(pb->shm_ptr, module->ready),
+ &module->deadline);
+ }
+ }
+ perform_cleanup(pb);
+}
+
+static patchfield *create_instance(int sample_rate, int buffer_frames,
+ int input_channels, int output_channels) {
+ patchfield *pb = malloc(sizeof(patchfield));
+ if (pb) {
+ pb->sample_rate = sample_rate;
+ pb->buffer_frames = buffer_frames;
+ pb->next_buffer = BUFFER_OFFSET * MEM_PAGE_SIZE / sizeof(float);
+
+ pb->shm_fd = smi_create();
+ if (pb->shm_fd < 0) {
+ LOGW("Unable to create shared memory.");
+ free(pb);
+ return NULL;
+ }
+ pb->shm_ptr = smi_map(pb->shm_fd);
+ if (!pb->shm_ptr) {
+ LOGW("Unable to map shared memory.");
+ close(pb->shm_fd);
+ free(pb);
+ return NULL;
+ }
+ smi_lock(pb->shm_ptr);
+
+ // Create OpenSL stream.
+ pb->os = opensl_open(sample_rate,
+ input_channels, output_channels, buffer_frames, process, pb);
+ if (!pb->os) {
+ smi_unlock(pb->shm_ptr);
+ smi_unmap(pb->shm_ptr);
+ close(pb->shm_fd);
+ free(pb);
+ return NULL;
+ }
+
+ int i;
+ for (i = 0; i < MAX_MODULES; ++i) {
+ audio_module *module = ami_get_audio_module(pb->shm_ptr, i);
+ memset(module, 0, sizeof(audio_module));
+ }
+ activate_module(pb, add_module(pb, 0, input_channels));
+ activate_module(pb, add_module(pb, output_channels, 0));
+ }
+ return pb;
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_createInstance
+(JNIEnv *env, jobject obj, jint sample_rate, jint buffer_frames,
+ int input_channels, int output_channels) {
+ return (jlong) create_instance(sample_rate, buffer_frames,
+ input_channels, output_channels);
+}
+
+JNIEXPORT void JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_releaseInstance
+(JNIEnv *env, jobject obj, jlong p) {
+ patchfield *pb = (patchfield *) p;
+ release(pb);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_sendSharedMemoryFileDescriptor
+(JNIEnv *env, jobject obj, jlong p) {
+ patchfield *pb = (patchfield *) p;
+ if (smi_send(pb->shm_fd) < 0) {
+ LOGW("Failed to send file descriptor.");
+ return -1; // PatchfieldException.FAILURE
+ }
+ return 0;
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_start
+(JNIEnv *env, jobject obj, jlong p) {
+ patchfield *pb = (patchfield *) p;
+ return opensl_start(pb->os);
+}
+
+JNIEXPORT void JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_stop
+(JNIEnv *env, jobject obj, jlong p) {
+ patchfield *pb = (patchfield *) p;
+ opensl_pause(pb->os);
+}
+
+JNIEXPORT jboolean JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_isRunning
+(JNIEnv *env, jobject obj, jlong p) {
+ patchfield *pb = (patchfield *) p;
+ return is_running(pb);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_createModule
+(JNIEnv *env, jobject obj, jlong p, jint input_channels, jint output_channels) {
+ patchfield *pb = (patchfield *) p;
+ return add_module(pb, input_channels, output_channels);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_connectPorts
+(JNIEnv *env, jobject obj, jlong p,
+ jint source_index, jint source_port, jint sink_index, jint sink_port) {
+ patchfield *pb = (patchfield *) p;
+ return connect_modules(pb, source_index, source_port, sink_index, sink_port);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_disconnectPorts
+(JNIEnv *env, jobject obj, jlong p,
+ jint source_index, jint source_port, jint sink_index, jint sink_port) {
+ patchfield *pb = (patchfield *) p;
+ return disconnect_modules(pb, source_index, source_port, sink_index, sink_port);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_deleteModule
+(JNIEnv *env, jobject obj, jlong p, jint index) {
+ patchfield *pb = (patchfield *) p;
+ return delete_module(pb, index);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_activateModule
+(JNIEnv *env, jobject obj, jlong p, jint index) {
+ patchfield *pb = (patchfield *) p;
+ return activate_module(pb, index);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_deactivateModule
+(JNIEnv *env, jobject obj, jlong p, jint index) {
+ patchfield *pb = (patchfield *) p;
+ return deactivate_module(pb, index);
+}
+
+JNIEXPORT jboolean JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_isActive
+(JNIEnv *env, jobject obj, jlong p, jint index) {
+ patchfield *pb = (patchfield *) p;
+ return is_active(pb, index);
+}
+
+JNIEXPORT jboolean JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_isConnected
+(JNIEnv *env, jobject obj, jlong p, jint sourceIndex, jint sourcePort,
+ jint sinkIndex, jint sinkPort) {
+ patchfield *pb = (patchfield *) p;
+ return is_connected(pb, sourceIndex, sourcePort, sinkIndex, sinkPort);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_getInputChannels
+(JNIEnv *env, jobject obj, jlong p, jint index) {
+ patchfield *pb = (patchfield *) p;
+ return get_input_channels(pb, index);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_getOutputChannels
+(JNIEnv *env, jobject obj, jlong p, jint index) {
+ patchfield *pb = (patchfield *) p;
+ return get_output_channels(pb, index);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_Patchfield_getProtocolVersion
+(JNIEnv *env, jobject obj, jlong p) {
+ return PATCHFIELD_PROTOCOL_VERSION;
+}
diff --git a/Patchfield/jni/internal/patchfield.h b/Patchfield/jni/internal/patchfield.h
new file mode 100644
index 0000000..94f38bf
--- /dev/null
+++ b/Patchfield/jni/internal/patchfield.h
@@ -0,0 +1,149 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class com_noisepages_nettoyeur_patchfield_Patchfield */
+
+#ifndef _Included_com_noisepages_nettoyeur_patchfield_Patchfield
+#define _Included_com_noisepages_nettoyeur_patchfield_Patchfield
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: createInstance
+ * Signature: (IIII)J
+ */
+JNIEXPORT jlong JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_createInstance
+ (JNIEnv *, jobject, jint, jint, jint, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: sendSharedMemoryFileDescriptor
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_sendSharedMemoryFileDescriptor
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: releaseInstance
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_releaseInstance
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: createModule
+ * Signature: (JII)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_createModule
+ (JNIEnv *, jobject, jlong, jint, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: deleteModule
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_deleteModule
+ (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: connectPorts
+ * Signature: (JIIII)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_connectPorts
+ (JNIEnv *, jobject, jlong, jint, jint, jint, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: disconnectPorts
+ * Signature: (JIIII)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_disconnectPorts
+ (JNIEnv *, jobject, jlong, jint, jint, jint, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: activateModule
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_activateModule
+ (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: deactivateModule
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_deactivateModule
+ (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: start
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_start
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: stop
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_stop
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: isActive
+ * Signature: (JI)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_isActive
+ (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: isRunning
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_isRunning
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: isConnected
+ * Signature: (JIIII)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_isConnected
+ (JNIEnv *, jobject, jlong, jint, jint, jint, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: getInputChannels
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_getInputChannels
+ (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: getOutputChannels
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_getOutputChannels
+ (JNIEnv *, jobject, jlong, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_Patchfield
+ * Method: getProtocolVersion
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_Patchfield_getProtocolVersion
+ (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/Patchfield/jni/internal/shared_memory_internal.c b/Patchfield/jni/internal/shared_memory_internal.c
new file mode 100644
index 0000000..76c686c
--- /dev/null
+++ b/Patchfield/jni/internal/shared_memory_internal.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "shared_memory_internal.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define LOGI(...) \
+ __android_log_print(ANDROID_LOG_INFO, "shared_memory_internal", __VA_ARGS__)
+#define LOGW(...) \
+ __android_log_print(ANDROID_LOG_WARN, "shared_memory_internal", __VA_ARGS__)
+
+#define ASHMEM_MODULE "/dev/ashmem"
+#define SHARED_MEM_SIZE 262144
+#define SOCK_NAME "patchfield_shm_socket"
+
+int smi_create() {
+ int fd = open(ASHMEM_MODULE, O_RDWR);
+ if (fd < 0) {
+ LOGW("Failed to open ashmem: %s", strerror(errno));
+ return -1;
+ }
+ if (ioctl(fd, ASHMEM_SET_SIZE, SHARED_MEM_SIZE) < 0) {
+ LOGW("Failed to allocate shared memory: %s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+void *smi_map(int fd) {
+ return mmap(NULL, SHARED_MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+}
+
+int smi_unmap(void *p) {
+ return munmap(p, SHARED_MEM_SIZE);
+}
+
+int smi_lock(void *p) {
+ int result = mlock(p, SHARED_MEM_SIZE);
+ if (result) {
+ LOGW("Failed to lock shared memory: %s", strerror(errno));
+ } else {
+ LOGI("Locked shared memory.");
+ }
+ return result;
+}
+
+int smi_unlock(void *p) {
+ return munlock(p, SHARED_MEM_SIZE);
+}
+
+int smi_protect(void *p, size_t n) {
+ mprotect(p, n, PROT_READ);
+}
+
+static int smi_transmit(int fd) {
+ // Boilerplate for passing file descriptors across processes.
+ int x = 1;
+ struct iovec v;
+ v.iov_base = &x;
+ v.iov_len = sizeof(int);
+ struct msghdr hdr;
+ hdr.msg_name = NULL;
+ hdr.msg_namelen = 0;
+ hdr.msg_iov = &v;
+ hdr.msg_iovlen = 1;
+ union {
+ struct cmsghdr chdr;
+ char control[CMSG_SPACE(sizeof(int))];
+ } ctrl;
+ hdr.msg_control = ctrl.control;
+ hdr.msg_controllen = sizeof(ctrl.control);
+ ctrl.chdr.cmsg_type = SCM_RIGHTS;
+ ctrl.chdr.cmsg_level = SOL_SOCKET;
+ ctrl.chdr.cmsg_len = CMSG_LEN(sizeof(int));
+
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ addr.sun_path[0] = 0;
+ char *path = SOCK_NAME;
+ strncpy(&addr.sun_path[1], path, strlen(path));
+ int sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (sock_fd < 0) {
+ LOGW("Failed to open socket: %s", strerror(errno));
+ return -1;
+ }
+
+ if (fd >= 0) {
+ if (connect(sock_fd, (struct sockaddr *) &addr,
+ sizeof(struct sockaddr_un)) < 0) {
+ close(sock_fd);
+ LOGW("Failed to connect socket: %s", strerror(errno));
+ return -1;
+ }
+ *((int *) CMSG_DATA(CMSG_FIRSTHDR(&hdr))) = fd;
+ if (sendmsg(sock_fd, &hdr, 0) < 0) {
+ close(sock_fd);
+ LOGW("Failed to pass file descriptor: %s", strerror(errno));
+ return -1;
+ }
+ if (close(sock_fd) < 0) {
+ LOGW("Failed to close socket: %s", strerror(errno));
+ }
+ return 0;
+ } else {
+ if (bind(sock_fd, (struct sockaddr *) &addr,
+ sizeof(struct sockaddr_un)) < 0) {
+ close(sock_fd);
+ LOGW("Failed to bind socket: %s", strerror(errno));
+ return -1;
+ }
+ if (recvmsg(sock_fd, &hdr, 0) < 0) {
+ close(sock_fd);
+ LOGW("Failed to receive file descriptor: %s", strerror(errno));
+ return -1;
+ }
+ if (close(sock_fd) < 0) {
+ LOGW("Failed to close socket: %s", strerror(errno));
+ }
+ return *((int *) CMSG_DATA(CMSG_FIRSTHDR(&hdr)));
+ }
+}
+
+int smi_send(int fd) {
+ if (fd < 0) {
+ LOGW("Negative file descriptor.");
+ return -1;
+ }
+ return smi_transmit(fd);
+}
+
+int smi_receive() {
+ return smi_transmit(-1);
+}
+
+long smi_get_size() {
+ return SHARED_MEM_SIZE;
+}
diff --git a/Patchfield/jni/internal/shared_memory_internal.h b/Patchfield/jni/internal/shared_memory_internal.h
new file mode 100644
index 0000000..07f2054
--- /dev/null
+++ b/Patchfield/jni/internal/shared_memory_internal.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Tools for handling shared memory with ashmem. Most functions simply wrap
+ * basic system calls, except for smi_{send,receive}, which serve to pass the
+ * ashmem file descriptor across process boundaries using Unix domain sockets.
+ */
+
+#ifndef __SHARED_MEMORY_INTERNAL_H__
+#define __SHARED_MEMORY_INTERNAL_H__
+
+#include
+
+int smi_create();
+void *smi_map(int fd);
+int smi_unmap(void *p);
+int smi_lock(void *p);
+int smi_unlock(void *p);
+int smi_protect(void *p, size_t n);
+int smi_send(int fd);
+int smi_receive();
+long smi_get_size();
+
+#endif
diff --git a/Patchfield/jni/internal/shared_memory_utils.c b/Patchfield/jni/internal/shared_memory_utils.c
new file mode 100644
index 0000000..73f8137
--- /dev/null
+++ b/Patchfield/jni/internal/shared_memory_utils.c
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "shared_memory_utils.h"
+
+#include "internal/shared_memory_internal.h"
+
+#include
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_internal_SharedMemoryUtils_receiveSharedMemoryFileDescriptor
+(JNIEnv *env, jclass cls) {
+ return smi_receive();
+}
+
+JNIEXPORT jint JNICALL
+Java_com_noisepages_nettoyeur_patchfield_internal_SharedMemoryUtils_closeSharedMemoryFileDescriptor
+(JNIEnv *env, jclass cls, jint fd) {
+ return close(fd);
+}
diff --git a/Patchfield/jni/internal/shared_memory_utils.h b/Patchfield/jni/internal/shared_memory_utils.h
new file mode 100644
index 0000000..1fb60df
--- /dev/null
+++ b/Patchfield/jni/internal/shared_memory_utils.h
@@ -0,0 +1,29 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class com_noisepages_nettoyeur_patchfield_internal_SharedMemoryUtils */
+
+#ifndef _Included_com_noisepages_nettoyeur_patchfield_internal_SharedMemoryUtils
+#define _Included_com_noisepages_nettoyeur_patchfield_internal_SharedMemoryUtils
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_internal_SharedMemoryUtils
+ * Method: receiveSharedMemoryFileDescriptor
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_internal_SharedMemoryUtils_receiveSharedMemoryFileDescriptor
+ (JNIEnv *, jclass);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_internal_SharedMemoryUtils
+ * Method: closeSharedMemoryFileDescriptor
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_com_noisepages_nettoyeur_patchfield_internal_SharedMemoryUtils_closeSharedMemoryFileDescriptor
+ (JNIEnv *, jclass, jint);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/Patchfield/jni/internal/simple_barrier.c b/Patchfield/jni/internal/simple_barrier.c
new file mode 100644
index 0000000..dbc1cdb
--- /dev/null
+++ b/Patchfield/jni/internal/simple_barrier.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "simple_barrier.h"
+
+#include
+#include
+#include
+#include
+
+#define ONE_BILLION 1000000000
+
+static void get_relative_deadline(
+ const struct timespec *abstime, struct timespec *reltime) {
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if (now.tv_sec < abstime->tv_sec ||
+ (now.tv_sec == abstime->tv_sec && now.tv_nsec < abstime->tv_nsec)) {
+ reltime->tv_sec = abstime->tv_sec - now.tv_sec;
+ if (abstime->tv_nsec >= now.tv_nsec) {
+ reltime->tv_nsec = abstime->tv_nsec - now.tv_nsec;
+ } else {
+ --reltime->tv_sec;
+ reltime->tv_nsec = (ONE_BILLION + abstime->tv_nsec) - now.tv_nsec;
+ }
+ } else {
+ reltime->tv_sec = 0;
+ reltime->tv_nsec = 0;
+ }
+}
+
+static void futex_wait(simple_barrier_t *p, struct timespec *abstime) {
+ if (abstime == NULL) {
+ syscall(__NR_futex, p, FUTEX_WAIT, 0, NULL, NULL, 0, 0);
+ } else {
+ struct timespec reltime;
+ get_relative_deadline(abstime, &reltime);
+ syscall(__NR_futex, p, FUTEX_WAIT, 0, &reltime, NULL, 0, 0);
+ // Note: Passing struct timespec as ktime_t works for now but may need
+ // further consideration when we move to 64bit.
+ }
+}
+
+int sb_wait(simple_barrier_t *p, struct timespec *abstime) {
+ switch (__sync_or_and_fetch(p, 0)) {
+ case 0:
+ futex_wait(p, abstime);
+ break;
+ case 1:
+ return 0; // Success!
+ default:
+ return -2; // Error; the futex has been tampered with.
+ }
+ switch (__sync_or_and_fetch(p, 0)) {
+ case 0:
+ return -1; // Failure; __futex_wait probably timed out.
+ case 1:
+ return 0;
+ default:
+ return -2;
+ }
+}
+
+int sb_wait_and_clear(simple_barrier_t *p, struct timespec *abstime) {
+ switch (__sync_or_and_fetch(p, 0)) {
+ case 0:
+ futex_wait(p, abstime);
+ case 1:
+ break;
+ default:
+ return -2;
+ }
+ switch (__sync_val_compare_and_swap(p, 1, 0)) {
+ case 0:
+ return -1;
+ case 1:
+ return 0;
+ default:
+ return -2;
+ }
+}
+
+int sb_wake(simple_barrier_t *p) {
+ if (__sync_bool_compare_and_swap(p, 0, 1)) {
+ syscall(__NR_futex, p, FUTEX_WAKE, INT_MAX, NULL, NULL, 0, 0);
+ return 0;
+ } else {
+ return -2;
+ }
+}
+
+void sb_clobber(simple_barrier_t *p) {
+ int val = 1;
+ while (val = __sync_val_compare_and_swap(p, val, 0));
+}
diff --git a/Patchfield/jni/internal/simple_barrier.h b/Patchfield/jni/internal/simple_barrier.h
new file mode 100644
index 0000000..595f589
--- /dev/null
+++ b/Patchfield/jni/internal/simple_barrier.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * An atomic blocking boolean, designed to be robust when the underlying memory
+ * location is overwritten by buggy or malicious code. Integer return values
+ * are 0 for success, -1 for failure (due to timeouts), and -2 for errors (if
+ * the memory location has been tampered with).
+ */
+
+#ifndef __SIMPLE_BARRIER_H__
+#define __SIMPLE_BARRIER_H__
+
+#include
+
+/*
+ * Abstract data type representing a barrier.
+ */
+typedef int simple_barrier_t;
+
+/*
+ * If *p == 0, wait for another thread to invoke sb_wake(p). If *p == 1, return
+ * immediately. The deadline is either NULL (i.e., no deadline) or an absolute
+ * time measured with CLOCK_MONOTONIC.
+ *
+ * Use this function if multiple threads may be waiting on p.
+ */
+int sb_wait(simple_barrier_t *p, struct timespec *abstime);
+
+/*
+ * Like sb_wait, except it clears *p right after *p has become 1.
+ *
+ * Use this function if the current thread is the only thread waiting on p.
+ */
+int sb_wait_and_clear(simple_barrier_t *p, struct timespec *abstime);
+
+/*
+ * If *p == 0, set it to 1 and wake all threads waiting on p. If *p == 1, do
+ * nothing.
+ */
+int sb_wake(simple_barrier_t *p);
+
+/*
+ * Clears *p, regardless of whether any threads are waiting on p.
+ */
+void sb_clobber(simple_barrier_t *p);
+
+#endif
diff --git a/Patchfield/jni/modules/javamodule.c b/Patchfield/jni/modules/javamodule.c
new file mode 100644
index 0000000..08b942c
--- /dev/null
+++ b/Patchfield/jni/modules/javamodule.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "javamodule.h"
+
+#include "audio_module.h"
+#include "utils/buffer_size_adapter.h"
+#include "internal/simple_barrier.h"
+
+#include
+#include
+#include
+
+typedef struct {
+ buffer_size_adapter *bsa;
+ simple_barrier_t wake;
+ simple_barrier_t ready;
+ const float *input_buffer;
+ float *output_buffer;
+ int done;
+} jmodule;
+
+static void process_jm(void *context, int sample_rate, int buffer_frames,
+ int input_channels, const float *input_buffer,
+ int output_channels, float *output_buffer) {
+ jmodule *jm = (jmodule *) context;
+ jm->input_buffer = input_buffer;
+ jm->output_buffer = output_buffer;
+ sb_wake(&jm->wake);
+ sb_wait_and_clear(&jm->ready, NULL);
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_noisepages_nettoyeur_patchfield_modules_JavaModule_configure
+(JNIEnv *env, jobject obj, jlong p, jint host_buffer_size,
+ jint user_buffer_size, jint input_channels, jint output_channels) {
+ jmodule *jm = malloc(sizeof(jmodule));
+ if (jm) {
+ sb_clobber(&jm->wake);
+ sb_clobber(&jm->ready);
+ jm->input_buffer = NULL;
+ jm->output_buffer = NULL;
+ jm->done = 0;
+ jm->bsa = bsa_create((void *) p, host_buffer_size, user_buffer_size,
+ input_channels, output_channels, process_jm, jm);
+ if (!jm->bsa) {
+ free(jm);
+ jm = NULL;
+ }
+ }
+ return (jlong) jm;
+}
+
+JNIEXPORT void JNICALL
+Java_com_noisepages_nettoyeur_patchfield_modules_JavaModule_release
+(JNIEnv *env, jobject obj, jlong p) {
+ jmodule *jm = (jmodule *) p;
+ bsa_release(jm->bsa);
+ free(jm);
+}
+
+JNIEXPORT void JNICALL
+Java_com_noisepages_nettoyeur_patchfield_modules_JavaModule_fillInputBuffer
+(JNIEnv *env, jobject obj, jlong p, jfloatArray buffer) {
+ jmodule *jm = (jmodule *) p;
+ sb_wait_and_clear(&jm->wake, NULL);
+ if (!jm->done) {
+ int n = (*env)->GetArrayLength(env, buffer);
+ float *b = (*env)->GetFloatArrayElements(env, buffer, NULL);
+ memcpy(b, jm->input_buffer, n * sizeof(float));
+ (*env)->ReleaseFloatArrayElements(env, buffer, b, 0);
+ }
+}
+
+JNIEXPORT void JNICALL
+Java_com_noisepages_nettoyeur_patchfield_modules_JavaModule_sendOutputBuffer
+(JNIEnv *env, jobject obj, jlong p, jfloatArray buffer) {
+ jmodule *jm = (jmodule *) p;
+ if (!jm->done) {
+ int n = (*env)->GetArrayLength(env, buffer);
+ float *b = (*env)->GetFloatArrayElements(env, buffer, NULL);
+ memcpy(jm->output_buffer, b, n * sizeof(float));
+ (*env)->ReleaseFloatArrayElements(env, buffer, b, 0);
+ sb_wake(&jm->ready);
+ }
+}
+
+JNIEXPORT void JNICALL
+Java_com_noisepages_nettoyeur_patchfield_modules_JavaModule_signalThread
+(JNIEnv *env, jobject obj, jlong p) {
+ jmodule *jm = (jmodule *) p;
+ jm->done = 1;
+ sb_wake(&jm->wake);
+}
+
diff --git a/Patchfield/jni/modules/javamodule.h b/Patchfield/jni/modules/javamodule.h
new file mode 100644
index 0000000..f4303a2
--- /dev/null
+++ b/Patchfield/jni/modules/javamodule.h
@@ -0,0 +1,53 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class com_noisepages_nettoyeur_patchfield_modules_JavaModule */
+
+#ifndef _Included_com_noisepages_nettoyeur_patchfield_modules_JavaModule
+#define _Included_com_noisepages_nettoyeur_patchfield_modules_JavaModule
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_modules_JavaModule
+ * Method: configure
+ * Signature: (JIIII)J
+ */
+JNIEXPORT jlong JNICALL Java_com_noisepages_nettoyeur_patchfield_modules_JavaModule_configure
+ (JNIEnv *, jobject, jlong, jint, jint, jint, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_modules_JavaModule
+ * Method: release
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_noisepages_nettoyeur_patchfield_modules_JavaModule_release
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_modules_JavaModule
+ * Method: fillInputBuffer
+ * Signature: (J[F)V
+ */
+JNIEXPORT void JNICALL Java_com_noisepages_nettoyeur_patchfield_modules_JavaModule_fillInputBuffer
+ (JNIEnv *, jobject, jlong, jfloatArray);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_modules_JavaModule
+ * Method: sendOutputBuffer
+ * Signature: (J[F)V
+ */
+JNIEXPORT void JNICALL Java_com_noisepages_nettoyeur_patchfield_modules_JavaModule_sendOutputBuffer
+ (JNIEnv *, jobject, jlong, jfloatArray);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_modules_JavaModule
+ * Method: signalThread
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_noisepages_nettoyeur_patchfield_modules_JavaModule_signalThread
+ (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/Patchfield/jni/utils/buffer_size_adapter.c b/Patchfield/jni/utils/buffer_size_adapter.c
new file mode 100644
index 0000000..0711464
--- /dev/null
+++ b/Patchfield/jni/utils/buffer_size_adapter.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "buffer_size_adapter.h"
+
+#include
+#include
+
+typedef struct {
+ int buffer_frames;
+ float *v;
+ int write_index;
+ int read_index;
+} bsa_ring_buffer;
+
+struct _buffer_size_adapter {
+ int host_buffer_frames;
+ int user_buffer_frames;
+ void *user_context;
+ audio_module_process_t user_process;
+ bsa_ring_buffer *input_buffer;
+ bsa_ring_buffer *output_buffer;
+};
+
+static int lcm(int a, int b) {
+ int m = a;
+ while (m % b) {
+ m += a;
+ }
+ return m;
+}
+
+static int frames_available(bsa_ring_buffer *rb) {
+ return
+ (rb->buffer_frames + rb->write_index - rb->read_index) % rb->buffer_frames;
+}
+
+static void transfer_buffers(int nframes, int nchannels,
+ const float *source, int source_index, int source_frames,
+ float *sink, int sink_index, int sink_frames) {
+ int c, n, b;
+ while (nframes > 0) {
+ n = nframes;
+ b = source_frames - source_index % source_frames;
+ if (b < n) {
+ n = b;
+ }
+ b = sink_frames - sink_index % sink_frames;
+ if (b < n) {
+ n = b;
+ }
+ for (c = 0; c < nchannels; ++c) {
+ memcpy(
+ sink + (sink_index / sink_frames) * sink_frames * nchannels +
+ c * sink_frames + sink_index % sink_frames,
+ source + (source_index / source_frames) * source_frames * nchannels +
+ c * source_frames + source_index % source_frames,
+ n * sizeof(float));
+ }
+ nframes -= n;
+ source_index += n;
+ sink_index += n;
+ }
+}
+
+static void bsa_process(void *context, int sample_rate, int buffer_frames,
+ int input_channels, const float *input_buffer,
+ int output_channels, float *output_buffer) {
+ buffer_size_adapter *adapter = (buffer_size_adapter *) context;
+ if (adapter->host_buffer_frames != adapter->user_buffer_frames) {
+ bsa_ring_buffer *ib = adapter->input_buffer;
+ bsa_ring_buffer *ob = adapter->output_buffer;
+ transfer_buffers(buffer_frames, input_channels,
+ input_buffer, 0, buffer_frames,
+ ib->v, ib->write_index, adapter->user_buffer_frames);
+ ib->write_index = (ib->write_index + buffer_frames) % ib->buffer_frames;
+ while (frames_available(ib) >= adapter->user_buffer_frames) {
+ adapter->user_process(adapter->user_context, sample_rate,
+ adapter->user_buffer_frames,
+ input_channels, ib->v + ib->read_index * input_channels,
+ output_channels, ob->v + ob->write_index * output_channels);
+ ib->read_index =
+ (ib->read_index + adapter->user_buffer_frames) % ib->buffer_frames;
+ ob->write_index =
+ (ob->write_index + adapter->user_buffer_frames) % ob->buffer_frames;
+ }
+ if (frames_available(ob) >= buffer_frames) {
+ transfer_buffers(buffer_frames, output_channels,
+ ob->v, ob->read_index, adapter->user_buffer_frames,
+ output_buffer, 0, buffer_frames);
+ ob->read_index = (ob->read_index + buffer_frames) % ob->buffer_frames;
+ } else {
+ memset(output_buffer, 0, buffer_frames * output_channels * sizeof(float));
+ }
+ } else {
+ adapter->user_process(adapter->user_context, sample_rate, buffer_frames,
+ input_channels, input_buffer, output_channels, output_buffer);
+ }
+}
+
+static bsa_ring_buffer *create_buffer(
+ int host_buffer_frames, int user_buffer_frames, int nchannels) {
+ bsa_ring_buffer *rb = malloc(sizeof(bsa_ring_buffer));
+ if (rb) {
+ rb->read_index = 0;
+ rb->write_index = 0;
+ int m = lcm(host_buffer_frames, user_buffer_frames);
+ if (m == host_buffer_frames || m == user_buffer_frames) {
+ m *= 2;
+ }
+ rb->buffer_frames = m;
+ rb->v = calloc(rb->buffer_frames * nchannels, sizeof(float));
+ if (!rb->v) {
+ free(rb);
+ rb = NULL;
+ }
+ }
+ return rb;
+}
+
+static void release_buffer(bsa_ring_buffer *rb) {
+ if (rb) {
+ free(rb->v);
+ }
+ free(rb);
+}
+
+buffer_size_adapter *bsa_create(
+ void *handle, int host_buffer_frames, int user_buffer_frames,
+ int input_channels, int output_channels,
+ audio_module_process_t user_process, void *user_context) {
+ buffer_size_adapter *adapter = malloc(sizeof(buffer_size_adapter));
+ if (adapter) {
+ adapter->host_buffer_frames = host_buffer_frames;
+ adapter->user_buffer_frames = user_buffer_frames;
+ adapter->user_process = user_process;
+ adapter->user_context = user_context;
+ if (host_buffer_frames != user_buffer_frames) {
+ adapter->input_buffer =
+ create_buffer(host_buffer_frames, user_buffer_frames, input_channels);
+ if (adapter->input_buffer) {
+ adapter->output_buffer = create_buffer(host_buffer_frames,
+ user_buffer_frames, output_channels);
+ if (adapter->output_buffer) {
+ // Optimizing initial indices according to St\'ephane Letz, "Callback
+ // adaptation techniques"
+ // (see www.grame.fr/ressources/publications/CallbackAdaptation.pdf).
+ int r, w = 0;
+ int m = lcm(host_buffer_frames, user_buffer_frames);
+ int dmax = 0;
+ for (r = 0; r < m; r += host_buffer_frames) {
+ for (; w < r; w += user_buffer_frames);
+ int d = w - r;
+ if (d > dmax) {
+ dmax = d;
+ adapter->output_buffer->read_index = r;
+ adapter->output_buffer->write_index = w;
+ }
+ }
+ } else {
+ release_buffer(adapter->input_buffer);
+ free(adapter);
+ adapter = NULL;
+ }
+ } else {
+ free(adapter);
+ adapter = NULL;
+ }
+ } else {
+ adapter->input_buffer = NULL;
+ adapter->output_buffer = NULL;
+ }
+ }
+ am_configure(handle, bsa_process, adapter);
+ return adapter;
+}
+
+void bsa_release(buffer_size_adapter *adapter) {
+ release_buffer(adapter->input_buffer);
+ release_buffer(adapter->output_buffer);
+ free(adapter);
+}
diff --git a/Patchfield/jni/utils/buffer_size_adapter.h b/Patchfield/jni/utils/buffer_size_adapter.h
new file mode 100644
index 0000000..5c9d0a3
--- /dev/null
+++ b/Patchfield/jni/utils/buffer_size_adapter.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * An adapter that allows audio modules to operate at buffer sizes that differ
+ * from the buffer size of the Patchfield service.
+ */
+#ifndef __BUFFER_SIZE_ADAPTER_H__
+#define __BUFFER_SIZE_ADAPTER_H__
+
+#include "audio_module.h"
+
+/*
+ * Abstract data type representing a buffer size adapter.
+ */
+typedef struct _buffer_size_adapter buffer_size_adapter;
+
+/*
+ * Constructor; calls am_configure internally, and so audio modules using
+ * this utility will not need to call am_configure themselves.
+ */
+buffer_size_adapter *bsa_create(
+ void *handle, int host_buffer_frames, int user_buffer_frames,
+ int input_channels, int output_channels,
+ audio_module_process_t user_process, void *user_context);
+
+/*
+ * Releases all resources associated with this adapter.
+ */
+void bsa_release(buffer_size_adapter *adapter);
+
+#endif
diff --git a/Patchfield/libs/android-support-v4.jar b/Patchfield/libs/android-support-v4.jar
new file mode 100644
index 0000000..428bdbc
Binary files /dev/null and b/Patchfield/libs/android-support-v4.jar differ
diff --git a/Patchfield/proguard-project.txt b/Patchfield/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/Patchfield/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/Patchfield/project.properties b/Patchfield/project.properties
new file mode 100644
index 0000000..484dab0
--- /dev/null
+++ b/Patchfield/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
+android.library=true
diff --git a/Patchfield/res/drawable-hdpi/.!43333!ic_launcher.png b/Patchfield/res/drawable-hdpi/.!43333!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-hdpi/.!43353!ic_launcher.png b/Patchfield/res/drawable-hdpi/.!43353!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-hdpi/.!43416!ic_launcher.png b/Patchfield/res/drawable-hdpi/.!43416!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-hdpi/.!43465!ic_launcher.png b/Patchfield/res/drawable-hdpi/.!43465!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-hdpi/.!43520!ic_launcher.png b/Patchfield/res/drawable-hdpi/.!43520!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-hdpi/.!44022!ic_launcher.png b/Patchfield/res/drawable-hdpi/.!44022!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-hdpi/ic_launcher.png b/Patchfield/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..776f508
Binary files /dev/null and b/Patchfield/res/drawable-hdpi/ic_launcher.png differ
diff --git a/Patchfield/res/drawable-mdpi/.!43417!emo_im_happy.png b/Patchfield/res/drawable-mdpi/.!43417!emo_im_happy.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!43418!ic_launcher.png b/Patchfield/res/drawable-mdpi/.!43418!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!43419!perm_group_audio_settings.png b/Patchfield/res/drawable-mdpi/.!43419!perm_group_audio_settings.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!43420!perm_group_microphone.png b/Patchfield/res/drawable-mdpi/.!43420!perm_group_microphone.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!43470!emo_im_happy.png b/Patchfield/res/drawable-mdpi/.!43470!emo_im_happy.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!43471!ic_launcher.png b/Patchfield/res/drawable-mdpi/.!43471!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!43472!perm_group_audio_settings.png b/Patchfield/res/drawable-mdpi/.!43472!perm_group_audio_settings.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!43473!perm_group_microphone.png b/Patchfield/res/drawable-mdpi/.!43473!perm_group_microphone.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!43529!emo_im_happy.png b/Patchfield/res/drawable-mdpi/.!43529!emo_im_happy.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!43530!ic_launcher.png b/Patchfield/res/drawable-mdpi/.!43530!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!43531!perm_group_audio_settings.png b/Patchfield/res/drawable-mdpi/.!43531!perm_group_audio_settings.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!43532!perm_group_microphone.png b/Patchfield/res/drawable-mdpi/.!43532!perm_group_microphone.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!44035!emo_im_happy.png b/Patchfield/res/drawable-mdpi/.!44035!emo_im_happy.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!44036!ic_launcher.png b/Patchfield/res/drawable-mdpi/.!44036!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!44037!perm_group_audio_settings.png b/Patchfield/res/drawable-mdpi/.!44037!perm_group_audio_settings.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/.!44038!perm_group_microphone.png b/Patchfield/res/drawable-mdpi/.!44038!perm_group_microphone.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-mdpi/emo_im_happy.png b/Patchfield/res/drawable-mdpi/emo_im_happy.png
new file mode 100644
index 0000000..a324720
Binary files /dev/null and b/Patchfield/res/drawable-mdpi/emo_im_happy.png differ
diff --git a/Patchfield/res/drawable-mdpi/ic_launcher.png b/Patchfield/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c432b83
Binary files /dev/null and b/Patchfield/res/drawable-mdpi/ic_launcher.png differ
diff --git a/Patchfield/res/drawable-mdpi/perm_group_audio_settings.png b/Patchfield/res/drawable-mdpi/perm_group_audio_settings.png
new file mode 100644
index 0000000..f2f461b
Binary files /dev/null and b/Patchfield/res/drawable-mdpi/perm_group_audio_settings.png differ
diff --git a/Patchfield/res/drawable-mdpi/perm_group_microphone.png b/Patchfield/res/drawable-mdpi/perm_group_microphone.png
new file mode 100644
index 0000000..9bab315
Binary files /dev/null and b/Patchfield/res/drawable-mdpi/perm_group_microphone.png differ
diff --git a/Patchfield/res/drawable-xhdpi/.!43421!ic_launcher.png b/Patchfield/res/drawable-xhdpi/.!43421!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-xhdpi/.!43475!ic_launcher.png b/Patchfield/res/drawable-xhdpi/.!43475!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-xhdpi/.!43535!ic_launcher.png b/Patchfield/res/drawable-xhdpi/.!43535!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-xhdpi/.!44042!ic_launcher.png b/Patchfield/res/drawable-xhdpi/.!44042!ic_launcher.png
new file mode 100644
index 0000000..e69de29
diff --git a/Patchfield/res/drawable-xhdpi/ic_launcher.png b/Patchfield/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..7664569
Binary files /dev/null and b/Patchfield/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/Patchfield/res/values-v11/styles.xml b/Patchfield/res/values-v11/styles.xml
new file mode 100644
index 0000000..3c02242
--- /dev/null
+++ b/Patchfield/res/values-v11/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/Patchfield/res/values-v14/styles.xml b/Patchfield/res/values-v14/styles.xml
new file mode 100644
index 0000000..a91fd03
--- /dev/null
+++ b/Patchfield/res/values-v14/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/Patchfield/res/values/strings.xml b/Patchfield/res/values/strings.xml
new file mode 100644
index 0000000..045b0a4
--- /dev/null
+++ b/Patchfield/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Patchfield
+
+
diff --git a/Patchfield/res/values/styles.xml b/Patchfield/res/values/styles.xml
new file mode 100644
index 0000000..6ce89c7
--- /dev/null
+++ b/Patchfield/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/Patchfield/src/com/noisepages/nettoyeur/patchfield/AudioModule.java b/Patchfield/src/com/noisepages/nettoyeur/patchfield/AudioModule.java
new file mode 100644
index 0000000..5d16dd7
--- /dev/null
+++ b/Patchfield/src/com/noisepages/nettoyeur/patchfield/AudioModule.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield;
+
+import android.app.Notification;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.noisepages.nettoyeur.patchfield.internal.SharedMemoryUtils;
+
+/**
+ * Abstract base class for Patchfield audio modules. Subclasses must implement
+ * methods for creating and releasing audio modules; these implementations will
+ * involve native code using the native audio_module library in Patchfield/jni.
+ * See the LowpassSample project for a representative audio module
+ * implementation.
+ *
+ * The Patchfield service operates at the native sample rate and buffer size of
+ * the device. This means that audio modules must operate at the native sample
+ * rate and buffer size as well. Native sample rates of 44100Hz and 48000Hz are
+ * common, and so audio modules must support both. Moreover, audio modules must
+ * be prepared to work with arbitrary buffer sizes. In particular, they cannot
+ * assume that the buffer size is a power of two. Multiples of three, such as
+ * 144, 192, and 384, have been seen in the wild.
+ *
+ * If an app is unable to run at the native buffer size, the buffer size adapter
+ * utility in Patchfield/jni/utils/buffer_size_adapter.{h,c} can be used. For an
+ * example of the buffer size adapter in action, see the PatchfieldPd library
+ * project.
+ */
+public abstract class AudioModule {
+
+ static {
+ System.loadLibrary("audiomodulejava");
+ }
+
+ private static final String TAG = "AudioModule";
+
+ private String name = null;
+ private int token = -1;
+ private long handle = 0;
+
+ private final Notification notification;
+
+ private class FdReceiverThread extends Thread {
+ @Override
+ public void run() {
+ token = SharedMemoryUtils.receiveSharedMemoryFileDescriptor();
+ Log.i(TAG, "fd: " + token);
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param notification
+ * Notification to be passed to the Patchfield service, so that the service
+ * can associate an audio module with an app. May be null.
+ */
+ protected AudioModule(Notification notification) {
+ this.notification = notification;
+ }
+
+ /**
+ * This method takes care of the elaborate choreography that it takes to set
+ * up an audio module and to connect it to its representation in the
+ * Patchfield service.
+ *
+ * Specifically, it sets up the shared memory between the local module and
+ * the Patchfield service, creates a new module in the service, and connects
+ * it to the local module.
+ *
+ * A module can only be configured once. If it times out, it cannot be
+ * reinstated and should be released.
+ *
+ * @param patchfield
+ * Stub for communicating with the Patchfield service.
+ * @param name
+ * Name of the new audio module in Patchfield.
+ * @return 0 on success, a negative error on failure; use
+ * {@link PatchfieldException} to interpret the return value.
+ * @throws RemoteException
+ */
+ public int configure(IPatchfieldService patchfield, String name)
+ throws RemoteException {
+ int version = patchfield.getProtocolVersion();
+ if (version != getProtocolVersion()) {
+ return PatchfieldException.PROTOCOL_VERSION_MISMATCH;
+ }
+ if (this.handle != 0) {
+ throw new IllegalStateException("Module is already configured.");
+ }
+ FdReceiverThread t = new FdReceiverThread();
+ t.start();
+ while (patchfield.sendSharedMemoryFileDescriptor() != 0 && t.isAlive()) {
+ try {
+ Thread.sleep(10); // Wait for receiver thread to spin up.
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ try {
+ t.join();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ if (token < 0) {
+ return token;
+ }
+ int index = patchfield.createModule(name, getInputChannels(),
+ getOutputChannels(), notification);
+ if (index < 0) {
+ SharedMemoryUtils.closeSharedMemoryFileDescriptor(token);
+ return index;
+ }
+ handle = createRunner(version, token, index);
+ if (handle == 0) {
+ patchfield.deleteModule(name);
+ SharedMemoryUtils.closeSharedMemoryFileDescriptor(token);
+ return PatchfieldException.FAILURE;
+ }
+ if (!configure(name, handle, patchfield.getSampleRate(),
+ patchfield.getBufferSize())) {
+ release(handle);
+ patchfield.deleteModule(name);
+ SharedMemoryUtils.closeSharedMemoryFileDescriptor(token);
+ return PatchfieldException.FAILURE;
+ }
+ this.name = name;
+ return PatchfieldException.SUCCESS;
+ }
+
+ /**
+ * Releases all resources associated with this module and deletes its
+ * representation in the Patchfield service.
+ *
+ * @param patchfield
+ * Stub for communicating with the Patchfield service.
+ * @throws RemoteException
+ */
+ public void release(IPatchfieldService patchfield) throws RemoteException {
+ if (handle != 0) {
+ patchfield.deleteModule(name);
+ release(handle);
+ release();
+ SharedMemoryUtils.closeSharedMemoryFileDescriptor(token);
+ name = null;
+ handle = 0;
+ token = -1;
+ } else {
+ Log.w(TAG, "Not configured; nothing to release.");
+ }
+ }
+
+ /**
+ * @return The name of the module if configured, null if it isn't.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return The notification associated with this module.
+ */
+ protected Notification getNotification() {
+ return notification;
+ }
+
+ /**
+ * @return True if the module has timed out. A module that has timed out
+ * cannot be reinstated and must be released.
+ */
+ public final boolean hasTimedOut() {
+ return handle != 0 && hasTimedOut(handle);
+ }
+
+ /**
+ * @return The number of input channels of this module.
+ */
+ public abstract int getInputChannels();
+
+ /**
+ * @return The number of output channels of this module.
+ */
+ public abstract int getOutputChannels();
+
+ /**
+ * This method is called by the public configure method. It is responsible
+ * to setting up the native components of an audio module implementation,
+ * such as the audio processing function and any data structures that make
+ * up the processing context.
+ *
+ * @param name
+ * @param handle
+ * Opaque handle to the internal data structure representing an
+ * audio module.
+ * @param sampleRate
+ * @param bufferSize
+ * @return True on success
+ */
+ protected abstract boolean configure(String name, long handle,
+ int sampleRate, int bufferSize);
+
+ /**
+ * Releases any resources held by the audio module, such as memory allocated
+ * for the processing context.
+ */
+ protected abstract void release();
+
+ /**
+ * @return The Patchfield protocol version; mostly for internal use.
+ */
+ public static native int getProtocolVersion();
+
+ private native long createRunner(int version, int token, int index);
+
+ private native void release(long handle);
+
+ private native boolean hasTimedOut(long handle);
+}
diff --git a/Patchfield/src/com/noisepages/nettoyeur/patchfield/IPatchfieldClient.aidl b/Patchfield/src/com/noisepages/nettoyeur/patchfield/IPatchfieldClient.aidl
new file mode 100644
index 0000000..454df7b
--- /dev/null
+++ b/Patchfield/src/com/noisepages/nettoyeur/patchfield/IPatchfieldClient.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield;
+
+import android.app.Notification;
+
+/**
+ * Patchfield client interface for handling notifications when the state of the
+ * patchfield changes.
+ */
+oneway interface IPatchfieldClient {
+
+ void onModuleCreated(String name, int inputChannels, int outputChannels, in Notification notification);
+ void onModuleDeleted(String name);
+
+ void onModuleActivated(String name);
+ void onModuleDeactivated(String name);
+
+ void onPortsConnected(String source, int sourcePort, String sink, int sinkPort);
+ void onPortsDisconnected(String source, int sourcePort, String sink, int sinkPort);
+
+ void onStart();
+ void onStop();
+}
diff --git a/Patchfield/src/com/noisepages/nettoyeur/patchfield/IPatchfieldService.aidl b/Patchfield/src/com/noisepages/nettoyeur/patchfield/IPatchfieldService.aidl
new file mode 100644
index 0000000..9696526
--- /dev/null
+++ b/Patchfield/src/com/noisepages/nettoyeur/patchfield/IPatchfieldService.aidl
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield;
+
+import com.noisepages.nettoyeur.patchfield.IPatchfieldClient;
+
+import android.app.Notification;
+
+import java.util.List;
+
+/**
+ * Patchfield service interface, implemented by {@link Patchfield}.
+ */
+interface IPatchfieldService {
+
+ /**
+ * Registers a Patchfield client. Note that a client is not an audio module.
+ * Rather, clients are Java objects that will be notified when the state
+ * of the patchfield changes.
+ */
+ void registerClient(IPatchfieldClient client);
+
+ /**
+ * Unregisters a Patchfield client.
+ */
+ void unregisterClient(IPatchfieldClient client);
+
+ /**
+ * Starts audio rendering.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+ int start();
+
+ /**
+ * Stops audio rendering.
+ */
+ void stop();
+
+ /**
+ * Activates the given audio module.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+ int activateModule(String module);
+
+ /**
+ * Deactivates the given audio module.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+ int deactivateModule(String module);
+
+ /**
+ * Connects the given source port to the given sink port.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+ int connectPorts(String source, int sourcePort, String sink, int sinkPort);
+
+ /**
+ * Disconnects the given source port from the given sink port.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+ int disconnectPorts(String source, int sourcePort, String sink, int sinkPort);
+
+ /**
+ * @return True if the Patchfield is currently rendering audio.
+ */
+ boolean isRunning();
+
+ /**
+ * @return The list of currently registered audio modules.
+ */
+ List getModules();
+
+ /**
+ * @return The number of input channels of the given module, or a negative error
+ * code if the module doesn't exist.
+ */
+ int getInputChannels(String module);
+
+ /**
+ * @return The number of output channels of the given module, or a negative error
+ * code if the module doesn't exist.
+ */
+ int getOutputChannels(String module);
+
+ /**
+ * @return The notification associated with this module, or null if the module
+ * doesn't exist.
+ */
+ Notification getNotification(String module);
+
+ /**
+ * @return True if the given module is currently active.
+ */
+ boolean isActive(String module);
+
+ /**
+ * @return True if the source port is directly connected to the sink port.
+ */
+ boolean isConnected(String source, int sourcePort, String sink, int sinkPort);
+
+ /**
+ * @return True if the input of of the given sink depends on the output of the given source,
+ * directly or indirectly. In other words, this method returns true if the source must have
+ * produced its output before the sink can run.
+ */
+ boolean isDependent(String sink, String source);
+
+ /**
+ * @return The sample rate in Hz at which the Patchfield operates. This value is determined
+ * by the hardware and cannot be changed. Audio modules should be able to operate at 44.1kHz
+ * as well as 48kHz.
+ */
+ int getSampleRate();
+
+ /**
+ * @return The buffer size in frames at which the Patchfield operates. This value is determined
+ * by the hardware and cannot be changed. Audio modules should make no assumptions about the
+ * buffer size. In particular, it will not be a power of two on many devices.
+ */
+ int getBufferSize();
+
+ /**
+ * Creates a new audio module in the Patchfield service; for internal use mostly, to be called by
+ * the configure method of {@link AudioModule}.
+ *
+ * @return The index of the new module on success, or a negative error code on failure.
+ */
+ int createModule(String module, int inputChannels, int outputChannels, in Notification notification);
+
+ /**
+ * Deletes an audio module from the Patchfield service; for internal use mostly, to be called by
+ * the release method of {@link AudioModule}.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+ int deleteModule(String module);
+
+ /**
+ * @return The native protocol version; for internal use only.
+ */
+ int getProtocolVersion();
+
+ /**
+ * Passes the ashmem file descriptor through a Unix domain socket; for internal use only.
+ *
+ * @return 0 on success, or a negative error code on failure.
+ */
+ int sendSharedMemoryFileDescriptor();
+
+ /**
+ * Delegates to the corresponding Service method.
+ */
+ void startForeground(int id, in Notification notification);
+
+ /**
+ * Delegates to the corresponding Service method.
+ */
+ void stopForeground(boolean removeNotification);
+}
diff --git a/Patchfield/src/com/noisepages/nettoyeur/patchfield/Patchfield.java b/Patchfield/src/com/noisepages/nettoyeur/patchfield/Patchfield.java
new file mode 100644
index 0000000..7fe1259
--- /dev/null
+++ b/Patchfield/src/com/noisepages/nettoyeur/patchfield/Patchfield.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import android.app.Notification;
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.noisepages.nettoyeur.patchfield.internal.OpenSlParams;
+
+/**
+ * The Java part of the Patchfield service implementation. This is mostly boilerplate; the action is
+ * in the native code, Patchfield/jni/patchfield.c.
+ */
+public class Patchfield implements IPatchfieldService {
+
+ private static final String TAG = "Patchfield";
+
+ static {
+ System.loadLibrary("patchfield");
+ }
+
+ private final OpenSlParams params;
+ private long streamPtr;
+ private final Map modules = new LinkedHashMap();
+ private final Map notifications = new LinkedHashMap();
+ private final RemoteCallbackList clients =
+ new RemoteCallbackList();
+
+ public Patchfield(Context context, int inputChannels, int outputChannels) throws IOException {
+ params = OpenSlParams.createInstance(context);
+ streamPtr =
+ createInstance(params.getSampleRate(), params.getBufferSize(), inputChannels,
+ outputChannels);
+ if (streamPtr == 0) {
+ throw new IOException("Unable to open opensl_stream.");
+ }
+ Log.i(TAG, "Created stream with ptr " + streamPtr);
+ modules.put("system_in", 0);
+ modules.put("system_out", 1);
+ Notification micNotification = new Notification.Builder(context)
+ .setSmallIcon(R.drawable.perm_group_microphone)
+ .setContentTitle("Microphones")
+ .build();
+ notifications.put("system_in", micNotification);
+ Notification speakerNotification = new Notification.Builder(context)
+ .setSmallIcon(R.drawable.perm_group_audio_settings)
+ .setContentTitle("Speakers")
+ .build();
+ notifications.put("system_out", speakerNotification);
+ }
+
+ public synchronized void release() {
+ if (streamPtr != 0) {
+ releaseInstance(streamPtr);
+ streamPtr = 0;
+ clients.kill();
+ }
+ }
+
+ @Override
+ public synchronized void registerClient(IPatchfieldClient client) throws RemoteException {
+ clients.register(client);
+ }
+
+ @Override
+ public synchronized void unregisterClient(IPatchfieldClient client) throws RemoteException {
+ clients.unregister(client);
+ }
+
+ @Override
+ public synchronized int getSampleRate() {
+ return params.getSampleRate();
+ }
+
+ @Override
+ public synchronized int getBufferSize() {
+ return params.getBufferSize();
+ }
+
+ @Override
+ public synchronized int sendSharedMemoryFileDescriptor() {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ return PatchfieldException.successOrFailure(sendSharedMemoryFileDescriptor(streamPtr));
+ }
+
+ @Override
+ public synchronized int start() {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ int result = start(streamPtr);
+ if (result == 0) {
+ int i = clients.beginBroadcast();
+ while (--i >= 0) {
+ try {
+ clients.getBroadcastItem(i).onStart();
+ } catch (RemoteException e) {
+ // Do nothing; RemoteCallbackList will take care of the cleanup.
+ }
+ }
+ clients.finishBroadcast();
+ }
+ return PatchfieldException.successOrFailure(result);
+ }
+
+ @Override
+ public synchronized void stop() {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ stop(streamPtr);
+ int i = clients.beginBroadcast();
+ while (--i >= 0) {
+ try {
+ clients.getBroadcastItem(i).onStop();
+ } catch (RemoteException e) {
+ // Do nothing; RemoteCallbackList will take care of the cleanup.
+ }
+ }
+ clients.finishBroadcast();
+ }
+
+ @Override
+ public synchronized boolean isRunning() {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ return isRunning(streamPtr);
+ }
+
+ @Override
+ public synchronized int createModule(String module, int inputChannels, int outputChannels,
+ Notification notification) {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ if (inputChannels < 0 || outputChannels < 0 || (inputChannels == 0 && outputChannels == 0)) {
+ return PatchfieldException.INVALID_PARAMETERS;
+ }
+ if (modules.containsKey(module)) {
+ return PatchfieldException.MODULE_NAME_TAKEN;
+ }
+ int index = createModule(streamPtr, inputChannels, outputChannels);
+ if (index >= 0) {
+ modules.put(module, index);
+ notifications.put(module, notification);
+ int i = clients.beginBroadcast();
+ while (--i >= 0) {
+ try {
+ clients.getBroadcastItem(i)
+ .onModuleCreated(module, inputChannels, outputChannels, notification);
+ } catch (RemoteException e) {
+ // Do nothing; RemoteCallbackList will take care of the cleanup.
+ }
+ }
+ clients.finishBroadcast();
+ }
+ return index;
+ }
+
+ @Override
+ public synchronized int deleteModule(String module) {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ if (!modules.containsKey(module)) {
+ return PatchfieldException.NO_SUCH_MODULE;
+ }
+ int result = deleteModule(streamPtr, modules.get(module));
+ if (result == 0) {
+ modules.remove(module);
+ Notification notification = notifications.remove(module);
+ if (notification != null && notification.deleteIntent != null) {
+ try {
+ notification.deleteIntent.send();
+ } catch (CanceledException e) {
+ // Do nothing.
+ }
+ }
+ int i = clients.beginBroadcast();
+ while (--i >= 0) {
+ try {
+ clients.getBroadcastItem(i).onModuleDeleted(module);
+ } catch (RemoteException e) {
+ // Do nothing; RemoteCallbackList will take care of the cleanup.
+ }
+ }
+ clients.finishBroadcast();
+ }
+ return result;
+ }
+
+ @Override
+ public synchronized int connectPorts(String source, int sourcePort, String sink, int sinkPort) {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ if (!modules.containsKey(source)) {
+ return PatchfieldException.NO_SUCH_MODULE;
+ }
+ if (!modules.containsKey(sink)) {
+ return PatchfieldException.NO_SUCH_MODULE;
+ }
+ if (sourcePort < 0 || sourcePort >= getOutputChannels(source)) {
+ return PatchfieldException.PORT_OUT_OF_RANGE;
+ }
+ if (sinkPort < 0 || sinkPort >= getInputChannels(sink)) {
+ return PatchfieldException.PORT_OUT_OF_RANGE;
+ }
+ if (isConnected(source, sourcePort, sink, sinkPort)) {
+ return PatchfieldException.SUCCESS;
+ }
+ if (isDependent(source, sink)) {
+ return PatchfieldException.CYCLIC_DEPENDENCY;
+ }
+ int result =
+ connectPorts(streamPtr, modules.get(source), sourcePort, modules.get(sink), sinkPort);
+ if (result == 0) {
+ int i = clients.beginBroadcast();
+ while (--i >= 0) {
+ try {
+ clients.getBroadcastItem(i).onPortsConnected(source, sourcePort, sink, sinkPort);
+ } catch (RemoteException e) {
+ // Do nothing; RemoteCallbackList will take care of the cleanup.
+ }
+ }
+ clients.finishBroadcast();
+ }
+ return result;
+ }
+
+ @Override
+ public synchronized int disconnectPorts(String source, int sourcePort, String sink, int sinkPort) {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ if (!modules.containsKey(source)) {
+ return PatchfieldException.NO_SUCH_MODULE;
+ }
+ if (!modules.containsKey(sink)) {
+ return PatchfieldException.NO_SUCH_MODULE;
+ }
+ if (sourcePort < 0 || sourcePort >= getOutputChannels(source)) {
+ return PatchfieldException.PORT_OUT_OF_RANGE;
+ }
+ if (sinkPort < 0 || sinkPort >= getInputChannels(sink)) {
+ return PatchfieldException.PORT_OUT_OF_RANGE;
+ }
+ if (!isConnected(source, sourcePort, sink, sinkPort)) {
+ return 0;
+ }
+ int result =
+ disconnectPorts(streamPtr, modules.get(source), sourcePort, modules.get(sink), sinkPort);
+ if (result == 0) {
+ int i = clients.beginBroadcast();
+ while (--i >= 0) {
+ try {
+ clients.getBroadcastItem(i).onPortsDisconnected(source, sourcePort, sink, sinkPort);
+ } catch (RemoteException e) {
+ // Do nothing; RemoteCallbackList will take care of the cleanup.
+ }
+ }
+ clients.finishBroadcast();
+ }
+ return result;
+ }
+
+ @Override
+ public synchronized boolean isConnected(String source, int sourcePort, String sink, int sinkPort) {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ return modules.containsKey(source) && modules.containsKey(sink)
+ && isConnected(streamPtr, modules.get(source), sourcePort, modules.get(sink), sinkPort);
+ }
+
+ @Override
+ public synchronized boolean isDependent(String sink, String source) {
+ List dependents = getDependents(source);
+ return dependents.contains(sink);
+ }
+
+ @Override
+ public synchronized List getModules() {
+ return Collections.unmodifiableList(new ArrayList(modules.keySet()));
+ }
+
+ @Override
+ public synchronized int getInputChannels(String module) {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ if (!modules.containsKey(module)) {
+ return PatchfieldException.NO_SUCH_MODULE;
+ }
+ return getInputChannels(streamPtr, modules.get(module));
+ }
+
+ @Override
+ public synchronized int getOutputChannels(String module) {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ if (!modules.containsKey(module)) {
+ return PatchfieldException.NO_SUCH_MODULE;
+ }
+ return getOutputChannels(streamPtr, modules.get(module));
+ }
+
+ @Override
+ public synchronized Notification getNotification(String module) {
+ return notifications.get(module);
+ }
+
+ @Override
+ public synchronized boolean isActive(String module) {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ return modules.containsKey(module) && isActive(streamPtr, modules.get(module));
+ }
+
+ @Override
+ public synchronized int activateModule(String module) {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ if (!modules.containsKey(module)) {
+ return PatchfieldException.NO_SUCH_MODULE;
+ }
+ if (isActive(module)) {
+ return 0;
+ }
+ int result = activateModule(streamPtr, modules.get(module));
+ if (result == 0) {
+ int i = clients.beginBroadcast();
+ while (--i >= 0) {
+ try {
+ clients.getBroadcastItem(i).onModuleActivated(module);
+ } catch (RemoteException e) {
+ // Do nothing; RemoteCallbackList will take care of the cleanup.
+ }
+ }
+ clients.finishBroadcast();
+ }
+ return PatchfieldException.successOrFailure(result);
+ }
+
+ @Override
+ public synchronized int deactivateModule(String module) {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ if (!modules.containsKey(module)) {
+ return PatchfieldException.NO_SUCH_MODULE;
+ }
+ if (!isActive(module)) {
+ return 0;
+ }
+ int result = deactivateModule(streamPtr, modules.get(module));
+ if (result == 0) {
+ int i = clients.beginBroadcast();
+ while (--i >= 0) {
+ try {
+ clients.getBroadcastItem(i).onModuleDeactivated(module);
+ } catch (RemoteException e) {
+ // Do nothing; RemoteCallbackList will take care of the cleanup.
+ }
+ }
+ clients.finishBroadcast();
+ }
+ return PatchfieldException.successOrFailure(result);
+ }
+
+ @Override
+ public int getProtocolVersion() throws RemoteException {
+ if (streamPtr == 0) {
+ throw new IllegalStateException("Stream closed.");
+ }
+ return getProtocolVersion(streamPtr);
+ }
+
+ private native long createInstance(int sampleRate, int bufferSize, int inputChannels,
+ int outputChannels);
+
+ private native int sendSharedMemoryFileDescriptor(long streamPtr);
+
+ private native void releaseInstance(long streamPtr);
+
+ private native int createModule(long streamPtr, int inputChannels, int outputChannels);
+
+ private native int deleteModule(long streamPtr, int index);
+
+ private native int connectPorts(long streamPtr, int sourceIndex, int sourcePort, int sinkIndex,
+ int sinkPort);
+
+ private native int disconnectPorts(long streamPtr, int sourceIndex, int sourcePort,
+ int sinkIndex, int sinkPort);
+
+ private native int activateModule(long streamPtr, int index);
+
+ private native int deactivateModule(long streamPtr, int index);
+
+ private native int start(long streamPtr);
+
+ private native void stop(long streamPtr);
+
+ private native boolean isActive(long streamPtr, int index);
+
+ private native boolean isRunning(long streamPtr);
+
+ private native boolean isConnected(long streamPtr, int sourceIndex, int sourcePort,
+ int sinkIndex, int sinkPort);
+
+ private native int getInputChannels(long streamPtr, int index);
+
+ private native int getOutputChannels(long streamPtr, int index);
+
+ private native int getProtocolVersion(long streamPtr);
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException("Not implemented for local patchfield.");
+ }
+
+ @Override
+ public void startForeground(int id, Notification notification) {
+ throw new UnsupportedOperationException("Not implemented for local patchfield.");
+ }
+
+ @Override
+ public void stopForeground(boolean removeNotification) {
+ throw new UnsupportedOperationException("Not implemented for local patchfield.");
+ }
+
+ private List getDependents(String source) {
+ List dependents = new ArrayList();
+ collectDependents(source, dependents);
+ return dependents;
+ }
+
+ private void collectDependents(String source, List dependents) {
+ dependents.add(source);
+ for (String sink : modules.keySet()) {
+ if (!dependents.contains(sink) && isDirectDependent(sink, source)) {
+ collectDependents(sink, dependents);
+ }
+ }
+ }
+
+ private boolean isDirectDependent(String sink, String source) {
+ for (int i = 0; i < getOutputChannels(source); ++i) {
+ for (int j = 0; j < getInputChannels(sink); ++j) {
+ if (isConnected(source, i, sink, j)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/Patchfield/src/com/noisepages/nettoyeur/patchfield/PatchfieldException.java b/Patchfield/src/com/noisepages/nettoyeur/patchfield/PatchfieldException.java
new file mode 100644
index 0000000..adc63e5
--- /dev/null
+++ b/Patchfield/src/com/noisepages/nettoyeur/patchfield/PatchfieldException.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield;
+
+/**
+ * Checked exception for Java-style handling of error codes from Patchfield. The {@link Patchfield}
+ * class itself doesn't throw exceptions but returns C-style error codes because exceptions can't be
+ * thrown via AIDL. If, however, you prefer to use exceptions in Java, you can convert error codes
+ * by wrapping calls to Patchfield in the throwOnError method below.
+ */
+public class PatchfieldException extends Exception {
+
+ // WARNING: Do not change these constants without updating references in patchfield.c.
+ public static final int SUCCESS = 0;
+ public static final int FAILURE = -1;
+ public static final int INVALID_PARAMETERS = -2;
+ public static final int NO_SUCH_MODULE = -3;
+ public static final int MODULE_NAME_TAKEN = -4;
+ public static final int TOO_MANY_MODULES = -5;
+ public static final int PORT_OUT_OF_RANGE = -6;
+ public static final int TOO_MANY_CONNECTIONS = -7;
+ public static final int CYCLIC_DEPENDENCY = -8;
+ public static final int OUT_OF_BUFFER_SPACE = -9;
+ public static final int PROTOCOL_VERSION_MISMATCH = -10;
+
+ private static final long serialVersionUID = 1L;
+ private final int code;
+
+ public PatchfieldException(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public static int successOrFailure(int n) {
+ return (n >= 0) ? SUCCESS : FAILURE;
+ }
+
+ public static int throwOnError(int code) throws PatchfieldException {
+ if (code < 0) {
+ throw new PatchfieldException(code);
+ }
+ return code;
+ }
+}
diff --git a/Patchfield/src/com/noisepages/nettoyeur/patchfield/internal/OpenSlParams.java b/Patchfield/src/com/noisepages/nettoyeur/patchfield/internal/OpenSlParams.java
new file mode 100644
index 0000000..616e1ac
--- /dev/null
+++ b/Patchfield/src/com/noisepages/nettoyeur/patchfield/internal/OpenSlParams.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield.internal;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Build;
+import android.util.Log;
+
+/**
+ * This class illustrates how to query OpenSL config parameters on Jelly Bean MR1 while maintaining
+ * backward compatibility with older versions of Android. The trick is to place the new API calls in
+ * an inner class that will only be loaded if we're running on JB MR1 or later.
+ */
+public abstract class OpenSlParams {
+
+ /**
+ * @return The recommended sample rate in Hz.
+ */
+ public abstract int getSampleRate();
+
+ /**
+ * @return The recommended buffer size in frames.
+ */
+ public abstract int getBufferSize();
+
+ /**
+ * @param context, e.g., the current activity.
+ * @return OpenSlParams instance for the given context.
+ */
+ public static OpenSlParams createInstance(Context context) {
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
+ ? new JellyBeanMr1OpenSlParams(context)
+ : new DefaultOpenSlParams();
+ }
+
+ private OpenSlParams() {
+ // Not meant to be instantiated except here.
+ }
+
+ // Implementation for Jelly Bean MR1 or later.
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ private static class JellyBeanMr1OpenSlParams extends OpenSlParams {
+
+ private final int sampleRate;
+ private final int bufferSize;
+
+ private JellyBeanMr1OpenSlParams(Context context) {
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ // Provide default values in case config lookup fails.
+ int sr = 44100;
+ int bs = 64;
+ try {
+ // If possible, query the native sample rate and buffer size.
+ sr = Integer.parseInt(am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
+ bs = Integer.parseInt(am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER));
+ } catch (NumberFormatException e) {
+ Log.w(getClass().getName(), "Failed to read native OpenSL config: " + e);
+ }
+ sampleRate = sr;
+ bufferSize = bs;
+ }
+
+ @Override
+ public int getSampleRate() {
+ return sampleRate;
+ }
+
+ @Override
+ public int getBufferSize() {
+ return bufferSize;
+ }
+ };
+
+ // Default factory for Jelly Bean or older.
+ private static class DefaultOpenSlParams extends OpenSlParams {
+ @Override
+ public int getSampleRate() {
+ return 44100;
+ }
+
+ @Override
+ public int getBufferSize() {
+ return 64;
+ }
+ };
+}
diff --git a/Patchfield/src/com/noisepages/nettoyeur/patchfield/internal/SharedMemoryUtils.java b/Patchfield/src/com/noisepages/nettoyeur/patchfield/internal/SharedMemoryUtils.java
new file mode 100644
index 0000000..9d7ec58
--- /dev/null
+++ b/Patchfield/src/com/noisepages/nettoyeur/patchfield/internal/SharedMemoryUtils.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield.internal;
+
+/**
+ * Utilities for receiving file descriptors from the Patchfield service.
+ */
+public class SharedMemoryUtils {
+
+ static {
+ System.loadLibrary("shared_memory_utils");
+ }
+
+ public static native int receiveSharedMemoryFileDescriptor();
+
+ public static native int closeSharedMemoryFileDescriptor(int fd);
+}
diff --git a/Patchfield/src/com/noisepages/nettoyeur/patchfield/modules/JavaModule.java b/Patchfield/src/com/noisepages/nettoyeur/patchfield/modules/JavaModule.java
new file mode 100644
index 0000000..4468e76
--- /dev/null
+++ b/Patchfield/src/com/noisepages/nettoyeur/patchfield/modules/JavaModule.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield.modules;
+
+import android.app.Notification;
+
+import com.noisepages.nettoyeur.patchfield.AudioModule;
+
+/**
+ * An audio module subclass whose audio processing callback is to be implemented
+ * in Java (as opposed to native code).
+ *
+ * THIS IS NOT THE RECOMMENDED WAY TO USE PATCHFIELD. The Java processing callback
+ * cannot be invoked on a real-time thread and Java code is prone to garbage
+ * collection breaks. This means that instances of this class run a higher risk
+ * of missing their deadlines and causing dropouts than audio modules that do
+ * their processing natively. Use this class for quick-and-dirty prototypes,
+ * but not for any serious applications that require glitch-free performance.
+ */
+public abstract class JavaModule extends AudioModule {
+
+ static {
+ System.loadLibrary("javamodule");
+ }
+
+ private final int inputChannels;
+ private final int outputChannels;
+ private final int bufferSize;
+ private float[] inputBuffer = null;
+ private float[] outputBuffer = null;
+
+ private long ptr = 0;
+ private int sampleRate;
+ private Thread renderThread = null;
+
+ private final Runnable processor = new Runnable() {
+ @Override
+ public void run() {
+ while (!hasTimedOut()) {
+ fillInputBuffer(ptr, inputBuffer);
+ if (Thread.interrupted()) {
+ break;
+ }
+ process(sampleRate, bufferSize, inputChannels, inputBuffer, outputChannels, outputBuffer);
+ sendOutputBuffer(ptr, outputBuffer);
+ }
+ }
+ };
+
+ /**
+ * Constructor. For best performance, make the buffer size equal to Patchfield.getBufferSize(). When
+ * this is not an option, choose a smallish buffer size if possible (64 is a good value). Large
+ * buffers will not improve stability. In fact, large buffers may increase the risk of dropouts
+ * because the patchfield runs a fixed buffer size internally; mismatched buffer sizes place an
+ * uneven load on the internal processing callback.
+ */
+ public JavaModule(int bufferSize, int inputChannels, int outputChannels, Notification notification) {
+ super(notification);
+ this.bufferSize = bufferSize;
+ this.inputChannels = inputChannels;
+ this.outputChannels = outputChannels;
+ inputBuffer = new float[bufferSize * inputChannels];
+ outputBuffer = new float[bufferSize * outputChannels];
+ }
+
+ /**
+ * Audio processing callback. The size of each buffer is the buffer size (in frames) times the
+ * number of channels. Buffers are non-interleaved.
+ */
+ protected abstract void process(int sampleRate, int bufferSize, int inputChannels,
+ float[] inputBuffer, int outputChannels, float[] outputBuffer);
+
+ @Override
+ public int getInputChannels() {
+ return inputChannels;
+ }
+
+ @Override
+ public int getOutputChannels() {
+ return outputChannels;
+ }
+
+ @Override
+ protected boolean configure(String name, long handle, int sampleRate, int hostBufferSize) {
+ this.sampleRate = sampleRate;
+ ptr = configure(handle, hostBufferSize, bufferSize, inputChannels, outputChannels);
+ if (ptr != 0) {
+ renderThread = new Thread(processor);
+ renderThread.start();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected void release() {
+ if (renderThread != null) {
+ renderThread.interrupt();
+ signalThread(ptr);
+ try {
+ renderThread.join();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ renderThread = null;
+ }
+ if (ptr != 0) {
+ release(ptr);
+ ptr = 0;
+ }
+ }
+
+ private native long configure(long handle, int hostBufferSize, int bufferSize, int inputChannels,
+ int outputChannels);
+
+ private native void release(long ptr);
+
+ private native void fillInputBuffer(long ptr, float[] inputBuffer);
+
+ private native void sendOutputBuffer(long ptr, float[] outputBuffer);
+
+ private native void signalThread(long ptr);
+}
diff --git a/Patchfield/src/com/noisepages/nettoyeur/patchfield/service/PatchfieldService.java b/Patchfield/src/com/noisepages/nettoyeur/patchfield/service/PatchfieldService.java
new file mode 100644
index 0000000..2af4554
--- /dev/null
+++ b/Patchfield/src/com/noisepages/nettoyeur/patchfield/service/PatchfieldService.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield.service;
+
+import java.io.IOException;
+import java.util.List;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.noisepages.nettoyeur.patchfield.IPatchfieldClient;
+import com.noisepages.nettoyeur.patchfield.IPatchfieldService;
+import com.noisepages.nettoyeur.patchfield.Patchfield;
+
+/**
+ * Boilerplate for turning {@link Patchfield} into an Android service.
+ */
+public class PatchfieldService extends Service {
+
+ private Patchfield patchfield = null;
+
+ private final IPatchfieldService.Stub binder = new IPatchfieldService.Stub() {
+
+ @Override
+ public int sendSharedMemoryFileDescriptor() {
+ return patchfield.sendSharedMemoryFileDescriptor();
+ }
+
+ @Override
+ public void unregisterClient(IPatchfieldClient client) throws RemoteException {
+ patchfield.unregisterClient(client);
+ }
+
+ @Override
+ public void registerClient(IPatchfieldClient client) throws RemoteException {
+ patchfield.registerClient(client);
+ }
+
+ @Override
+ public int getSampleRate() {
+ return patchfield.getSampleRate();
+ }
+
+ @Override
+ public int getBufferSize() {
+ return patchfield.getBufferSize();
+ }
+
+ @Override
+ public int deleteModule(String module) throws RemoteException {
+ return patchfield.deleteModule(module);
+ }
+
+ @Override
+ public int createModule(String module, int inputChannels, int outputChannels,
+ Notification notification) throws RemoteException {
+ return patchfield.createModule(module, inputChannels, outputChannels, notification);
+ }
+
+ @Override
+ public int connectPorts(String source, int sourcePort, String sink, int sinkPort)
+ throws RemoteException {
+ return patchfield.connectPorts(source, sourcePort, sink, sinkPort);
+ }
+
+ @Override
+ public int disconnectPorts(String source, int sourcePort, String sink, int sinkPort)
+ throws RemoteException {
+ return patchfield.disconnectPorts(source, sourcePort, sink, sinkPort);
+ }
+
+ @Override
+ public List getModules() throws RemoteException {
+ return patchfield.getModules();
+ }
+
+ @Override
+ public int getInputChannels(String module) throws RemoteException {
+ return patchfield.getInputChannels(module);
+ }
+
+ @Override
+ public int getOutputChannels(String module) throws RemoteException {
+ return patchfield.getOutputChannels(module);
+ }
+
+ @Override
+ public Notification getNotification(String module) throws RemoteException {
+ return patchfield.getNotification(module);
+ }
+
+ @Override
+ public boolean isConnected(String source, int sourcePort, String sink, int sinkPort)
+ throws RemoteException {
+ return patchfield.isConnected(source, sourcePort, sink, sinkPort);
+ }
+
+ @Override
+ public boolean isDependent(String source, String sink) throws RemoteException {
+ return patchfield.isDependent(source, sink);
+ }
+
+ @Override
+ public int start() {
+ return patchfield.start();
+ }
+
+ @Override
+ public void stop() {
+ patchfield.stop();
+ }
+
+ @Override
+ public boolean isRunning() {
+ return patchfield.isRunning();
+ }
+
+ @Override
+ public int activateModule(String module) throws RemoteException {
+ return patchfield.activateModule(module);
+ }
+
+ @Override
+ public int deactivateModule(String module) throws RemoteException {
+ return patchfield.deactivateModule(module);
+ }
+
+ @Override
+ public boolean isActive(String module) throws RemoteException {
+ return patchfield.isActive(module);
+ }
+
+ @Override
+ public int getProtocolVersion() throws RemoteException {
+ return patchfield.getProtocolVersion();
+ }
+
+ @Override
+ public void startForeground(int id, Notification notification) throws RemoteException {
+ PatchfieldService.this.startForeground(id, notification);
+ }
+
+ @Override
+ public void stopForeground(boolean removeNotification) throws RemoteException {
+ PatchfieldService.this.stopForeground(removeNotification);
+ }
+ };
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ try {
+ patchfield = new Patchfield(this, 2, 2);
+ } catch (IOException e) {
+ patchfield = null;
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return (patchfield != null) ? binder : null;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ release();
+ }
+
+ private void release() {
+ if (patchfield != null) {
+ patchfield.release();
+ patchfield = null;
+ }
+ }
+}
diff --git a/PatchfieldControl/.classpath b/PatchfieldControl/.classpath
new file mode 100644
index 0000000..5cc5eb9
--- /dev/null
+++ b/PatchfieldControl/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/PatchfieldControl/.project b/PatchfieldControl/.project
new file mode 100644
index 0000000..1b5f660
--- /dev/null
+++ b/PatchfieldControl/.project
@@ -0,0 +1,33 @@
+
+
+ PatchfieldControl
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/PatchfieldControl/.settings/org.eclipse.jdt.core.prefs b/PatchfieldControl/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..b080d2d
--- /dev/null
+++ b/PatchfieldControl/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/PatchfieldControl/AndroidManifest.xml b/PatchfieldControl/AndroidManifest.xml
new file mode 100644
index 0000000..2eaa205
--- /dev/null
+++ b/PatchfieldControl/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PatchfieldControl/ic_launcher-web.png b/PatchfieldControl/ic_launcher-web.png
new file mode 100644
index 0000000..39c760d
Binary files /dev/null and b/PatchfieldControl/ic_launcher-web.png differ
diff --git a/PatchfieldControl/libs/android-support-v4.jar b/PatchfieldControl/libs/android-support-v4.jar
new file mode 100644
index 0000000..428bdbc
Binary files /dev/null and b/PatchfieldControl/libs/android-support-v4.jar differ
diff --git a/PatchfieldControl/proguard-project.txt b/PatchfieldControl/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/PatchfieldControl/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/PatchfieldControl/project.properties b/PatchfieldControl/project.properties
new file mode 100644
index 0000000..32a175c
--- /dev/null
+++ b/PatchfieldControl/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
+android.library.reference.1=../Patchfield
diff --git a/PatchfieldControl/res/drawable-hdpi/ic_launcher.png b/PatchfieldControl/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..c86e7e1
Binary files /dev/null and b/PatchfieldControl/res/drawable-hdpi/ic_launcher.png differ
diff --git a/PatchfieldControl/res/drawable-mdpi/ic_launcher.png b/PatchfieldControl/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..52e9e33
Binary files /dev/null and b/PatchfieldControl/res/drawable-mdpi/ic_launcher.png differ
diff --git a/PatchfieldControl/res/drawable-xhdpi/ic_launcher.png b/PatchfieldControl/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..efb51f4
Binary files /dev/null and b/PatchfieldControl/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/PatchfieldControl/res/drawable-xxhdpi/ic_launcher.png b/PatchfieldControl/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d2395fb
Binary files /dev/null and b/PatchfieldControl/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/PatchfieldControl/res/layout/activity_main.xml b/PatchfieldControl/res/layout/activity_main.xml
new file mode 100644
index 0000000..c8194ad
--- /dev/null
+++ b/PatchfieldControl/res/layout/activity_main.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PatchfieldControl/res/layout/module.xml b/PatchfieldControl/res/layout/module.xml
new file mode 100644
index 0000000..d4cb30a
--- /dev/null
+++ b/PatchfieldControl/res/layout/module.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PatchfieldControl/res/menu/main.xml b/PatchfieldControl/res/menu/main.xml
new file mode 100644
index 0000000..c002028
--- /dev/null
+++ b/PatchfieldControl/res/menu/main.xml
@@ -0,0 +1,9 @@
+
diff --git a/PatchfieldControl/res/values-sw600dp/dimens.xml b/PatchfieldControl/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..44f01db
--- /dev/null
+++ b/PatchfieldControl/res/values-sw600dp/dimens.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/PatchfieldControl/res/values-sw720dp-land/dimens.xml b/PatchfieldControl/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..61e3fa8
--- /dev/null
+++ b/PatchfieldControl/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,9 @@
+
+
+
+ 128dp
+
+
diff --git a/PatchfieldControl/res/values-v11/styles.xml b/PatchfieldControl/res/values-v11/styles.xml
new file mode 100644
index 0000000..3c02242
--- /dev/null
+++ b/PatchfieldControl/res/values-v11/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/PatchfieldControl/res/values-v14/styles.xml b/PatchfieldControl/res/values-v14/styles.xml
new file mode 100644
index 0000000..3e61a52
--- /dev/null
+++ b/PatchfieldControl/res/values-v14/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/PatchfieldControl/res/values/dimens.xml b/PatchfieldControl/res/values/dimens.xml
new file mode 100644
index 0000000..55c1e59
--- /dev/null
+++ b/PatchfieldControl/res/values/dimens.xml
@@ -0,0 +1,7 @@
+
+
+
+ 16dp
+ 16dp
+
+
diff --git a/PatchfieldControl/res/values/strings.xml b/PatchfieldControl/res/values/strings.xml
new file mode 100644
index 0000000..b59c12a
--- /dev/null
+++ b/PatchfieldControl/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+
+
+ PatchfieldControl
+ Settings
+ Hello world!
+
+
diff --git a/PatchfieldControl/res/values/styles.xml b/PatchfieldControl/res/values/styles.xml
new file mode 100644
index 0000000..6ce89c7
--- /dev/null
+++ b/PatchfieldControl/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/PatchfieldControl/src/com/noisepages/nettoyeur/patchfield/control/MainActivity.java b/PatchfieldControl/src/com/noisepages/nettoyeur/patchfield/control/MainActivity.java
new file mode 100644
index 0000000..9fad7aa
--- /dev/null
+++ b/PatchfieldControl/src/com/noisepages/nettoyeur/patchfield/control/MainActivity.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield.control;
+
+import java.util.List;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Menu;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.FrameLayout;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.noisepages.nettoyeur.patchfield.IPatchfieldClient;
+import com.noisepages.nettoyeur.patchfield.IPatchfieldService;
+
+public class MainActivity extends Activity implements OnCheckedChangeListener {
+
+ private static final String TAG = "PatchControl";
+
+ private IPatchfieldService patchfield = null;
+
+ private TextView displayLine;
+ private Switch playButton;
+ private PatchView patchView;
+
+ private IPatchfieldClient.Stub receiver = new IPatchfieldClient.Stub() {
+
+ @Override
+ public void onStart() throws RemoteException {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ playButton.setChecked(true);
+ }
+ });
+ }
+
+ @Override
+ public void onStop() throws RemoteException {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ playButton.setChecked(false);
+ }
+ });
+ }
+
+ @Override
+ public void onModuleCreated(final String module, final int inputs, final int outputs,
+ final Notification notification) throws RemoteException {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ patchView.addModule(module, inputs, outputs, notification);
+ }
+ });
+ }
+
+ @Override
+ public void onModuleActivated(final String module) throws RemoteException {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ patchView.activateModule(module);
+ }
+ });
+ }
+
+ @Override
+ public void onModuleDeactivated(final String module) throws RemoteException {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ patchView.deactivateModule(module);
+ }
+ });
+ }
+
+ @Override
+ public void onModuleDeleted(final String module) throws RemoteException {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ patchView.deleteModule(module);
+ }
+ });
+ }
+
+ @Override
+ public void onPortsConnected(final String source, final int sourcePort, final String sink,
+ final int sinkPort) throws RemoteException {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ patchView.addConnection(source, sourcePort, sink, sinkPort);
+ }
+ });
+ }
+
+ @Override
+ public void onPortsDisconnected(final String source, final int sourcePort, final String sink,
+ final int sinkPort) throws RemoteException {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ patchView.removeConnection(source, sourcePort, sink, sinkPort);
+ }
+ });
+ }
+ };
+
+ private ServiceConnection connection = new ServiceConnection() {
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ patchfield = null;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "Service connected.");
+ patchfield = IPatchfieldService.Stub.asInterface(service);
+ patchView.setPatchfield(patchfield);
+ PendingIntent pi =
+ PendingIntent.getActivity(MainActivity.this, 0, new Intent(MainActivity.this,
+ MainActivity.class), 0);
+ Notification notification =
+ new Notification.Builder(MainActivity.this)
+ .setSmallIcon(android.R.drawable.ic_media_play).setContentTitle("PatchfieldControl")
+ .setContentIntent(pi).build();
+ try {
+ patchfield.startForeground(1, notification);
+ patchfield.registerClient(receiver);
+ playButton.setChecked(patchfield.isRunning());
+ displayLine.setText("Sample rate: " + patchfield.getSampleRate() + ", buffer size: "
+ + patchfield.getBufferSize() + ", protocol version: " + patchfield.getProtocolVersion());
+ List modules = patchfield.getModules();
+ for (String module : modules) {
+ patchView.addModule(module, patchfield.getInputChannels(module),
+ patchfield.getOutputChannels(module), patchfield.getNotification(module));
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ displayLine = (TextView) findViewById(R.id.displayLine);
+ playButton = (Switch) findViewById(R.id.playButton);
+ playButton.setOnCheckedChangeListener(this);
+ FrameLayout frame = (FrameLayout) findViewById(R.id.moduleFrame);
+ patchView = new PatchView(this);
+ patchView.init(this, frame);
+ bindService(new Intent("IPatchfieldService"), connection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (patchfield != null) {
+ try {
+ patchfield.stopForeground(false);
+ patchfield.unregisterClient(receiver);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ unbindService(connection);
+ patchfield = null;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (patchfield != null) {
+ try {
+ if (isChecked) {
+ patchfield.start();
+ } else {
+ patchfield.stop();
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/PatchfieldControl/src/com/noisepages/nettoyeur/patchfield/control/PatchView.java b/PatchfieldControl/src/com/noisepages/nettoyeur/patchfield/control/PatchView.java
new file mode 100644
index 0000000..71f47c6
--- /dev/null
+++ b/PatchfieldControl/src/com/noisepages/nettoyeur/patchfield/control/PatchView.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield.control;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import android.app.Notification;
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Cap;
+import android.graphics.Path;
+import android.os.RemoteException;
+import android.util.Pair;
+import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.FrameLayout;
+import android.widget.GridLayout;
+import android.widget.LinearLayout;
+import android.widget.Space;
+import android.widget.Toast;
+import android.widget.ToggleButton;
+
+import com.noisepages.nettoyeur.patchfield.IPatchfieldService;
+
+public final class PatchView extends GridLayout {
+
+ private IPatchfieldService patchfield;
+ private final List modules = new ArrayList();
+ private final Map, List>> connections =
+ new HashMap, List>>();
+
+ public PatchView(Context context) {
+ super(context);
+ }
+
+ public void setPatchfield(IPatchfieldService patchfield) {
+ this.patchfield = patchfield;
+ }
+
+ public void addModule(String module, int inputChannels, int outputChannels,
+ Notification notification) {
+ for (String u : modules) {
+ int sinks = 0;
+ try {
+ sinks = patchfield.getInputChannels(u);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ continue;
+ }
+ for (int i = 0; i < outputChannels; ++i) {
+ for (int j = 0; j < sinks; ++j) {
+ try {
+ if (patchfield.isConnected(module, i, u, j)) {
+ addConnection(module, i, u, j);
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ int sources = 0;
+ try {
+ sources = patchfield.getOutputChannels(u);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ continue;
+ }
+ for (int i = 0; i < inputChannels; ++i) {
+ for (int j = 0; j < sources; ++j) {
+ try {
+ if (patchfield.isConnected(u, j, module, i)) {
+ addConnection(u, j, module, i);
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ modules.add(module);
+
+ if (notification == null) {
+ notification =
+ new Notification.Builder(getContext()).setContentTitle(module)
+ .setSmallIcon(R.drawable.emo_im_happy).build();
+ }
+ addModuleView(module, inputChannels, outputChannels, notification);
+ }
+
+ public void deleteModule(String module) {
+ for (Iterator, List>>> iter =
+ connections.entrySet().iterator(); iter.hasNext();) {
+ Entry, List>> entry = iter.next();
+ if (entry.getKey().first.equals(module)) {
+ iter.remove();
+ } else {
+ for (Iterator> iter2 = entry.getValue().iterator(); iter2.hasNext();) {
+ Pair q = iter2.next();
+ if (q.first.equals(module)) {
+ iter2.remove();
+ }
+ }
+ }
+ }
+ modules.remove(module);
+ deleteModuleView(module);
+ }
+
+ public void activateModule(String module) {
+ updateModuleView(module, true);
+ }
+
+ public void deactivateModule(String module) {
+ updateModuleView(module, false);
+ }
+
+ public void addConnection(String source, int sourcePort, String sink, int sinkPort) {
+ Pair a = new Pair(source, sourcePort);
+ List> c = connections.get(a);
+ if (c == null) {
+ c = new ArrayList>();
+ connections.put(a, c);
+ }
+ Pair b = new Pair(sink, sinkPort);
+ if (!c.contains(b)) {
+ c.add(b);
+ }
+ invalidateAll();
+ }
+
+ public void removeConnection(String source, int sourcePort, String sink, int sinkPort) {
+ Pair a = new Pair(source, sourcePort);
+ List> c = connections.get(a);
+ if (c == null) {
+ return;
+ }
+ Pair b = new Pair(sink, sinkPort);
+ c.remove(b);
+ invalidateAll();
+ }
+
+
+ /*
+ * Crude GUI code below.
+ *
+ * Ideas for improvements:
+ *
+ * - Improve support for screen formats: The current layout works best with landscape orientation
+ * on large tablets.
+ *
+ * - Improve the visual appearance of play button and status line.
+ *
+ * - Improve the placement of modules. Instead of misusing GridLayout, consider
+ * letting users move modules around, or use a graph layout algorithm to place modules.
+ *
+ * - Improve drawing of patch cords so that they won't obscure modules views.
+ *
+ * - Add support for deleting modules. (what gesture would be appropriate?)
+ *
+ * - Implement support for saving and restoring patches.
+ */
+
+ private Toast toast = null;
+
+ private void toast(String msg) {
+ if (toast == null) {
+ toast = Toast.makeText(getContext(), "", Toast.LENGTH_SHORT);
+ }
+ toast.setText(msg);
+ toast.show();
+ }
+
+ private class Overlay extends View {
+
+ private final Paint paint = new Paint();
+ private final Path path = new Path();
+ private final int a[] = new int[4];
+ private final int b[] = new int[4];
+
+ public Overlay(Context context) {
+ super(context);
+ paint.setAntiAlias(true);
+ paint.setColor(Color.RED);
+ paint.setStrokeWidth(12);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeCap(Cap.ROUND);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ path.reset();
+ for (Pair p : connections.keySet()) {
+ getCoordinates(outputPorts.get(p.first).get(p.second), a);
+ int x0 = (a[0] + a[2]) / 2;
+ int y0 = (a[1] + a[3]) / 2;
+ for (Pair q : connections.get(p)) {
+ getCoordinates(inputPorts.get(q.first).get(q.second), b);
+ int x1 = (b[0] + b[2]) / 2;
+ int y1 = (b[1] + b[3]) / 2;
+ path.moveTo(x0, y0);
+ path.cubicTo(x0, (y0 + y1) / 2, x1, (y0 + y1) / 2, x1, y1);
+ }
+ }
+ canvas.drawPath(path, paint);
+ }
+ }
+
+ private Overlay overlay = null;
+ private final Map moduleViews = new HashMap();
+ private final Map> inputPorts = new HashMap>();
+ private final Map> outputPorts = new HashMap>();
+
+ private CompoundButton inputButton = null;
+ private CompoundButton outputButton = null;
+ private String inputModule = null;
+ private String outputModule = null;
+ private int inputPort = -1;
+ private int outputPort = -1;
+
+ private void handlePortEvent(CompoundButton button, String module, int port, boolean isOutput,
+ boolean isChecked) {
+ if (!isChecked) {
+ clearInputPortState();
+ clearOutputPortState();
+ return;
+ }
+ if (isOutput) {
+ clearOutputPortState();
+ outputButton = button;
+ outputModule = module;
+ outputPort = port;
+ } else {
+ clearInputPortState();
+ inputButton = button;
+ inputModule = module;
+ inputPort = port;
+ }
+ if (inputButton != null && outputButton != null) {
+ try {
+ if (patchfield.isConnected(outputModule, outputPort, inputModule, inputPort)) {
+ patchfield.disconnectPorts(outputModule, outputPort, inputModule, inputPort);
+ } else {
+ patchfield.connectPorts(outputModule, outputPort, inputModule, inputPort);
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ clearInputPortState();
+ clearOutputPortState();
+ }
+ }
+
+ private void clearInputPortState() {
+ if (inputButton != null) {
+ inputButton.setChecked(false);
+ inputButton = null;
+ }
+ }
+
+ private void clearOutputPortState() {
+ if (outputButton != null) {
+ outputButton.setChecked(false);
+ outputButton = null;
+ }
+ }
+
+ public void init(Context context, FrameLayout frame) {
+ setColumnCount(2);
+ setRowCount(32);
+ frame.addView(this);
+ overlay = new Overlay(context);
+ frame.addView(overlay);
+ }
+
+ private void addModuleView(final String module, int inputChannels, int outputChannels,
+ final Notification notification) {
+ View moduleView = inflate(getContext(), R.layout.module, null);
+ // Warning: Atrocious hack to place views in the desired place, Part I.
+ addView(new Space(getContext()));
+ addView(new Space(getContext()));
+ addView(moduleView);
+ moduleViews.put(module, moduleView);
+
+ LinearLayout buttonLayout = (LinearLayout) moduleView.findViewById(R.id.inputPorts);
+ inputPorts.put(module, createPorts(module, inputChannels, buttonLayout, false));
+
+ FrameLayout frame = (FrameLayout) moduleView.findViewById(R.id.moduleFrame);
+ View view = notification.contentView.apply(getContext(), frame);
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (notification.contentIntent != null) {
+ try {
+ notification.contentIntent.send();
+ } catch (CanceledException e) {
+ e.printStackTrace();
+ toast("Unable to launch app: " + e);
+ }
+ } else {
+ toast("Unable to launch app: App did not provide launch info.");
+ }
+ }
+ });
+ view.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ try {
+ if (patchfield.isActive(module)) {
+ patchfield.deactivateModule(module);
+ } else {
+ patchfield.activateModule(module);
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+ });
+ frame.addView(view);
+
+ buttonLayout = (LinearLayout) moduleView.findViewById(R.id.outputPorts);
+ outputPorts.put(module, createPorts(module, outputChannels, buttonLayout, true));
+
+ boolean isActive = false;
+ try {
+ isActive = patchfield.isActive(module);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ updateModuleView(module, isActive);
+ }
+
+ private ArrayList createPorts(final String module, int channels, LinearLayout buttonLayout,
+ final boolean isOutput) {
+ ArrayList buttons = new ArrayList();
+ for (int i = 0; i < channels; ++i) {
+ final ToggleButton button = new ToggleButton(getContext());
+ buttons.add(button);
+ String text = (isOutput ? "Out" : "In") + i;
+ button.setTextOn(text);
+ button.setTextOff(text);
+ button.setChecked(false);
+ buttonLayout.addView(button);
+ final int j = i;
+ button.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ handlePortEvent(buttonView, module, j, isOutput, isChecked);
+ }
+ });
+ }
+ return buttons;
+ }
+
+ private void deleteModuleView(String module) {
+ View moduleView = moduleViews.remove(module);
+ if (moduleView != null) {
+ int index;
+ for (index = 0; !moduleView.equals(getChildAt(index)); ++index);
+ removeView(moduleView);
+ // Warning: Atrocious hack to place views in the desired place, Part II.
+ while (index > 0 && getChildAt(--index) instanceof Space) {
+ removeViewAt(index);
+ }
+ }
+ invalidateAll();
+ }
+
+ private void getCoordinates(View v, int coords[]) {
+ int p[] = new int[2];
+ v.getLocationInWindow(p);
+ coords[0] = p[0];
+ coords[1] = p[1];
+ getLocationInWindow(p);
+ coords[0] -= p[0];
+ coords[1] -= p[1];
+ coords[2] = coords[0] + v.getWidth();
+ coords[3] = coords[1] + v.getHeight();
+ }
+
+ private void updateModuleView(String module, boolean isActive) {
+ View moduleView = moduleViews.get(module);
+ FrameLayout frame = (FrameLayout) moduleView.findViewById(R.id.moduleFrame);
+ frame.setAlpha(isActive ? 1.0f : 0.3f);
+ invalidateAll();
+ }
+
+ private void invalidateAll() {
+ invalidate();
+ overlay.invalidate();
+ }
+}
diff --git a/PatchfieldPd/.classpath b/PatchfieldPd/.classpath
new file mode 100644
index 0000000..0e854b2
--- /dev/null
+++ b/PatchfieldPd/.classpath
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/PatchfieldPd/.project b/PatchfieldPd/.project
new file mode 100644
index 0000000..b6d6070
--- /dev/null
+++ b/PatchfieldPd/.project
@@ -0,0 +1,33 @@
+
+
+ PatchfieldPd
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/PatchfieldPd/.settings/org.eclipse.jdt.core.prefs b/PatchfieldPd/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..b080d2d
--- /dev/null
+++ b/PatchfieldPd/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/PatchfieldPd/AndroidManifest.xml b/PatchfieldPd/AndroidManifest.xml
new file mode 100644
index 0000000..af00d8d
--- /dev/null
+++ b/PatchfieldPd/AndroidManifest.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/PatchfieldPd/Makefile b/PatchfieldPd/Makefile
new file mode 100644
index 0000000..a0f1380
--- /dev/null
+++ b/PatchfieldPd/Makefile
@@ -0,0 +1,3 @@
+all:
+ javah -classpath bin/classes -o jni/module/pdmodule.h com.noisepages.nettoyeur.patchfield.pd.PdModule
+ env NDK_MODULE_PATH=$(CURDIR)/.. ndk-build
diff --git a/PatchfieldPd/ic_launcher-web.png b/PatchfieldPd/ic_launcher-web.png
new file mode 100644
index 0000000..4131fb0
Binary files /dev/null and b/PatchfieldPd/ic_launcher-web.png differ
diff --git a/PatchfieldPd/jni/Android.mk b/PatchfieldPd/jni/Android.mk
new file mode 100644
index 0000000..5053e7d
--- /dev/null
+++ b/PatchfieldPd/jni/Android.mk
@@ -0,0 +1 @@
+include $(call all-subdir-makefiles)
diff --git a/PatchfieldPd/jni/Application.mk b/PatchfieldPd/jni/Application.mk
new file mode 100644
index 0000000..84a096d
--- /dev/null
+++ b/PatchfieldPd/jni/Application.mk
@@ -0,0 +1,2 @@
+APP_OPTIM := release
+APP_ABI := armeabi armeabi-v7a x86
diff --git a/PatchfieldPd/jni/module/Android.mk b/PatchfieldPd/jni/module/Android.mk
new file mode 100644
index 0000000..ef30668
--- /dev/null
+++ b/PatchfieldPd/jni/module/Android.mk
@@ -0,0 +1,14 @@
+# Note that NDK_MODULE_PATH must contain the patchfield parent directory. The
+# makefile in PatchfieldPd implicitly takes care of this.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := pdmodule
+LOCAL_LDLIBS := -llog
+LOCAL_SRC_FILES := pdmodule.c
+LOCAL_STATIC_LIBRARIES := audiomodule buffersizeadapter
+LOCAL_SHARED_LIBRARIES := pdnativeopensl
+include $(BUILD_SHARED_LIBRARY)
+$(call import-module,Patchfield/jni)
diff --git a/PatchfieldPd/jni/module/pdmodule.c b/PatchfieldPd/jni/module/pdmodule.c
new file mode 100644
index 0000000..6966d0d
--- /dev/null
+++ b/PatchfieldPd/jni/module/pdmodule.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "pdmodule.h"
+
+#include "../libpd/jni/z_jni_native_hooks.h"
+#include "audio_module.h"
+#include "utils/buffer_size_adapter.h"
+
+#include
+
+static void process_pd(void *context, int sample_rate, int buffer_frames,
+ int input_channels, const float *input_buffer,
+ int output_channels, float *output_buffer) {
+ libpd_sync_process_raw(input_buffer, output_buffer);
+}
+
+JNIEXPORT void JNICALL
+Java_com_noisepages_nettoyeur_patchfield_pd_PdModule_pdInitAudio
+(JNIEnv *env, jobject obj, jint input_channels, jint output_channels,
+ jint sample_rate) {
+ libpd_sync_init_audio(input_channels, output_channels, sample_rate);
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_noisepages_nettoyeur_patchfield_pd_PdModule_configureModule
+(JNIEnv *env, jobject obj, jlong handle,
+ jint host_buffer_size, jint user_buffer_size,
+ jint input_channels, jint output_channels) {
+ return (jlong) bsa_create((void *) handle,
+ host_buffer_size, user_buffer_size,
+ input_channels, output_channels,
+ process_pd, NULL);
+}
+
+JNIEXPORT void JNICALL
+Java_com_noisepages_nettoyeur_patchfield_pd_PdModule_release
+(JNIEnv *env, jobject obj, jlong p) {
+ bsa_release((buffer_size_adapter *) p);
+}
diff --git a/PatchfieldPd/jni/module/pdmodule.h b/PatchfieldPd/jni/module/pdmodule.h
new file mode 100644
index 0000000..cc81030
--- /dev/null
+++ b/PatchfieldPd/jni/module/pdmodule.h
@@ -0,0 +1,37 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class com_noisepages_nettoyeur_patchfield_pd_PdModule */
+
+#ifndef _Included_com_noisepages_nettoyeur_patchfield_pd_PdModule
+#define _Included_com_noisepages_nettoyeur_patchfield_pd_PdModule
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_pd_PdModule
+ * Method: pdInitAudio
+ * Signature: (III)V
+ */
+JNIEXPORT void JNICALL Java_com_noisepages_nettoyeur_patchfield_pd_PdModule_pdInitAudio
+ (JNIEnv *, jobject, jint, jint, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_pd_PdModule
+ * Method: configureModule
+ * Signature: (JIIII)J
+ */
+JNIEXPORT jlong JNICALL Java_com_noisepages_nettoyeur_patchfield_pd_PdModule_configureModule
+ (JNIEnv *, jobject, jlong, jint, jint, jint, jint);
+
+/*
+ * Class: com_noisepages_nettoyeur_patchfield_pd_PdModule
+ * Method: release
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_noisepages_nettoyeur_patchfield_pd_PdModule_release
+ (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/PatchfieldPd/libs/android-support-v4.jar b/PatchfieldPd/libs/android-support-v4.jar
new file mode 100644
index 0000000..428bdbc
Binary files /dev/null and b/PatchfieldPd/libs/android-support-v4.jar differ
diff --git a/PatchfieldPd/proguard-project.txt b/PatchfieldPd/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/PatchfieldPd/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/PatchfieldPd/project.properties b/PatchfieldPd/project.properties
new file mode 100644
index 0000000..032c08a
--- /dev/null
+++ b/PatchfieldPd/project.properties
@@ -0,0 +1,16 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
+android.library=true
+android.library.reference.1=../Patchfield
diff --git a/PatchfieldPd/res/drawable-hdpi/ic_launcher.png b/PatchfieldPd/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..bff79fd
Binary files /dev/null and b/PatchfieldPd/res/drawable-hdpi/ic_launcher.png differ
diff --git a/PatchfieldPd/res/drawable-mdpi/ic_launcher.png b/PatchfieldPd/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..6c47374
Binary files /dev/null and b/PatchfieldPd/res/drawable-mdpi/ic_launcher.png differ
diff --git a/PatchfieldPd/res/drawable-xhdpi/ic_launcher.png b/PatchfieldPd/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..d6e3e95
Binary files /dev/null and b/PatchfieldPd/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/PatchfieldPd/res/drawable-xxhdpi/ic_launcher.png b/PatchfieldPd/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..dec0b85
Binary files /dev/null and b/PatchfieldPd/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/PatchfieldPd/res/values-v11/styles.xml b/PatchfieldPd/res/values-v11/styles.xml
new file mode 100644
index 0000000..3c02242
--- /dev/null
+++ b/PatchfieldPd/res/values-v11/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/PatchfieldPd/res/values-v14/styles.xml b/PatchfieldPd/res/values-v14/styles.xml
new file mode 100644
index 0000000..a91fd03
--- /dev/null
+++ b/PatchfieldPd/res/values-v14/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/PatchfieldPd/res/values/strings.xml b/PatchfieldPd/res/values/strings.xml
new file mode 100644
index 0000000..2ad500c
--- /dev/null
+++ b/PatchfieldPd/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ PatchfieldPd
+
+
diff --git a/PatchfieldPd/res/values/styles.xml b/PatchfieldPd/res/values/styles.xml
new file mode 100644
index 0000000..6ce89c7
--- /dev/null
+++ b/PatchfieldPd/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/PatchfieldPd/src/com/noisepages/nettoyeur/patchfield/pd/PdModule.java b/PatchfieldPd/src/com/noisepages/nettoyeur/patchfield/pd/PdModule.java
new file mode 100644
index 0000000..b87e2b9
--- /dev/null
+++ b/PatchfieldPd/src/com/noisepages/nettoyeur/patchfield/pd/PdModule.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.noisepages.nettoyeur.patchfield.pd;
+
+import org.puredata.core.PdBase;
+
+import android.app.Notification;
+
+import com.noisepages.nettoyeur.patchfield.AudioModule;
+
+/**
+ * An audio module implementation that uses Pure Data (via libpd) internally.
+ *
+ * Since Pd is currently limited to one instance per process, PdModule is a singleton of sorts. When
+ * it is first created, the channel counts are configurable; once it has been created, the
+ * configuration is fixed for the lifetime of the process.
+ *
+ * PdModule takes care of the initialization of libpd. In particular, make sure to create your
+ * PdModule instance before calling any methods on PdBase. Do _not_ call PdBase.openAudio(...) or
+ * PdBase.computeAudio(...). After creating your PdModule instance, you can use PdBase as usual.
+ */
+public class PdModule extends AudioModule {
+
+ static {
+ PdBase.blockSize(); // Make sure to load PdBase first.
+ System.loadLibrary("pdmodule");
+ }
+
+ private static PdModule instance = null;
+
+ private long ptr = 0;
+
+ private final int sampleRate;
+ private final int inputChannels;
+ private final int outputChannels;
+
+ private PdModule(int sampleRate, int inputChannels, int outputChannels, Notification notification) {
+ super(notification);
+ this.sampleRate = sampleRate;
+ this.inputChannels = inputChannels;
+ this.outputChannels = outputChannels;
+ pdInitAudio(inputChannels, outputChannels, sampleRate);
+ PdBase.computeAudio(true);
+ }
+
+ /**
+ * Static factory method for creating PdModule instances. Note that the sample rate must be the
+ * sample rate reported by the Patchfield service.
+ */
+ public static PdModule getInstance(int sampleRate, int inputChannels, int outputChannels,
+ Notification notification) {
+ if (instance == null) {
+ return new PdModule(sampleRate, inputChannels, outputChannels, notification);
+ } else if (instance.getInputChannels() >= inputChannels
+ && instance.getOutputChannels() >= outputChannels
+ && (notification == null || notification.equals(instance.getNotification()))) {
+ return instance;
+ } else {
+ throw new IllegalStateException("PdModule instance can't be reconfigured once instantiated.");
+ }
+ }
+
+ @Override
+ public int getInputChannels() {
+ return inputChannels;
+ }
+
+ @Override
+ public int getOutputChannels() {
+ return outputChannels;
+ }
+
+ @Override
+ protected boolean configure(String name, long handle, int sampleRate, int bufferSize) {
+ if (ptr != 0) {
+ throw new IllegalStateException("Module has already been configured.");
+ }
+ ptr = configureModule(handle, bufferSize, PdBase.blockSize(), inputChannels, outputChannels);
+ return ptr != 0;
+ }
+
+ @Override
+ protected void release() {
+ if (ptr != 0) {
+ release(ptr);
+ }
+ }
+
+ private native void pdInitAudio(int inputChannels, int outputChannels, int sampleRate);
+
+ private native long configureModule(long handle, int bufferSize, int blockSize,
+ int inputChannels, int outputChannels);
+
+ private native void release(long ptr);
+}