├── .github └── workflows │ └── android.yml ├── .gitignore ├── .gitmodules ├── CircleOfFifths ├── AndroidManifest.xml ├── LICENSE.txt ├── build.gradle ├── misc │ ├── Makefile │ ├── a.svg │ ├── ab.svg │ ├── android.svg │ ├── android_guitar.png │ ├── android_guitar.svg │ ├── b.svg │ ├── bb.svg │ ├── c.svg │ ├── d.svg │ ├── db.svg │ ├── e.svg │ ├── eb.svg │ ├── f.svg │ ├── fs.svg │ ├── g.svg │ ├── guitar.png │ └── guitar.svg ├── res │ ├── drawable │ │ ├── background.png │ │ ├── btn_normal.9.png │ │ ├── btn_selected.9.png │ │ ├── icon.png │ │ ├── ks00.png │ │ ├── ks01.png │ │ ├── ks02.png │ │ ├── ks03.png │ │ ├── ks04.png │ │ ├── ks05.png │ │ ├── ks06.png │ │ ├── ks07.png │ │ ├── ks08.png │ │ ├── ks09.png │ │ ├── ks10.png │ │ ├── ks11.png │ │ └── radiobuttons.xml │ ├── layout │ │ └── main.xml │ ├── menu │ │ └── circle_menu.xml │ ├── raw │ │ └── patch.zip │ └── values │ │ └── strings.xml └── src │ └── org │ └── puredata │ └── android │ └── fifths │ ├── CircleOfFifths.java │ └── CircleView.java ├── PdCore ├── AndroidManifest.xml ├── LICENSE.txt ├── build.gradle └── src │ └── main │ ├── java │ └── org │ │ └── puredata │ │ └── android │ │ ├── io │ │ ├── AudioFormatUtil.java │ │ ├── AudioParameters.java │ │ ├── AudioRecordWrapper.java │ │ ├── AudioWrapper.java │ │ └── PdAudio.java │ │ ├── midi │ │ ├── MidiToPdAdapter.java │ │ └── PdToMidiAdapter.java │ │ ├── service │ │ ├── PdPreferences.java │ │ └── PdService.java │ │ └── utils │ │ ├── PdUiDispatcher.java │ │ └── Properties.java │ ├── jni │ ├── Android.mk │ └── Application.mk │ └── res │ ├── drawable │ └── icon.png │ ├── raw │ ├── extra_abs.zip │ └── silence.wav │ ├── values │ ├── audio.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── preferences.xml ├── PdTest ├── AndroidManifest.xml ├── LICENSE.txt ├── build.gradle ├── jni │ ├── Android.mk │ ├── Application.mk │ └── helloworld.c ├── proguard-rules.pro ├── res │ ├── drawable-hdpi │ │ └── icon.png │ ├── drawable-ldpi │ │ └── icon.png │ ├── drawable-mdpi │ │ └── icon.png │ ├── layout │ │ └── main.xml │ ├── menu │ │ └── pd_test_menu.xml │ ├── raw │ │ └── test.pd │ └── values │ │ └── strings.xml └── src │ └── org │ └── puredata │ └── android │ └── test │ └── PdTest.java ├── README.md ├── ScenePlayer ├── AndroidManifest.xml ├── LICENSE.txt ├── build.gradle ├── jni │ ├── Android.mk │ ├── Application.mk │ ├── rj_accum.c │ ├── rj_barkflux_accum~.c │ ├── rj_centroid~.c │ ├── rj_senergy~.c │ └── rj_zcr~.c ├── res │ ├── drawable │ │ ├── all_transparent.9.png │ │ ├── default_thumb.jpg │ │ ├── divider_horizontal_dark.xml │ │ ├── file.png │ │ ├── folder.png │ │ ├── info_button.xml │ │ ├── mic_icon.png │ │ ├── notification_icon.png │ │ ├── play_button.xml │ │ ├── popup_custom.9.png │ │ ├── record_button.xml │ │ ├── sceneplayer.png │ │ ├── sceneplayer_notify.png │ │ ├── tab_recordings_dark.png │ │ ├── tab_recordings_light.png │ │ ├── tab_recordings_selector.xml │ │ ├── tab_rjdj_me_dark.png │ │ ├── tab_rjdj_me_light.png │ │ ├── tab_rjdj_me_selector.xml │ │ ├── tab_scenes_dark.png │ │ ├── tab_scenes_light.png │ │ ├── tab_scenes_selector.xml │ │ ├── transport_edit_description.png │ │ ├── transport_goto_scene.png │ │ ├── transport_list.png │ │ ├── transport_list_pressed.png │ │ ├── transport_pause.png │ │ ├── transport_pause_pressed.png │ │ ├── transport_play.png │ │ ├── transport_play_pressed.png │ │ ├── transport_record.png │ │ ├── transport_record_active.png │ │ ├── transport_record_active_pressed.png │ │ ├── transport_record_pressed.png │ │ ├── white_gradient_bottom.9.png │ │ └── white_gradient_top.9.png │ ├── layout │ │ ├── file_dialog_main.xml │ │ ├── file_dialog_row.xml │ │ ├── recording_item.xml │ │ ├── recording_player.xml │ │ ├── recording_selection.xml │ │ ├── scene_player.xml │ │ ├── scene_selection.xml │ │ ├── tab_layout.xml │ │ ├── two_line_dialog_title.xml │ │ └── two_line_list_item.xml │ ├── menu │ │ └── selection_menu.xml │ ├── raw │ │ ├── abstractions.zip │ │ └── atsuke.zip │ └── values │ │ ├── strings.xml │ │ └── styles.xml └── src │ ├── com │ └── lamerman │ │ └── FileDialog.java │ └── org │ └── puredata │ └── android │ └── scenes │ ├── ImageOverlay.java │ ├── Overlay.java │ ├── RecordingListCursorAdapter.java │ ├── RecordingPlayer.java │ ├── RecordingSelection.java │ ├── SceneDataBase.java │ ├── SceneListCursorAdapter.java │ ├── ScenePlayer.java │ ├── SceneSelection.java │ ├── SceneTabs.java │ ├── SceneView.java │ ├── TextOverlay.java │ └── VersionedTouch.java ├── Voice-O-Rama ├── AndroidManifest.xml ├── LICENSE.txt ├── build.gradle ├── res │ ├── drawable-hdpi │ │ └── icon.png │ ├── drawable-ldpi │ │ └── icon.png │ ├── drawable-mdpi │ │ └── icon.png │ ├── layout-land │ │ └── main.xml │ ├── layout │ │ └── main.xml │ ├── menu │ │ └── pd_test_menu.xml │ ├── raw │ │ └── test.pd │ └── values │ │ └── strings.xml └── src │ └── at │ └── or │ └── at │ └── voiceorama │ ├── VersionedTouch.java │ └── VoiceORama.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '**/README.md' 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Set up JDK 17 21 | uses: actions/setup-java@v3 22 | with: 23 | java-version: '17' 24 | distribution: 'adopt' 25 | - run: | 26 | git submodule sync --recursive 27 | git submodule update --init --recursive 28 | - run: ./gradlew androidDependencies 29 | - run: ./gradlew clean assembleRelease 30 | env: 31 | JVM_OPTS: -Xmx3200m 32 | - uses: actions/upload-artifact@v2 33 | with: 34 | name: pd-core-aar 35 | path: PdCore/build/outputs/aar 36 | - if: github.event_name == 'push' 37 | run: ./gradlew publishToSonatype 38 | env: 39 | ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} 40 | ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} 41 | #ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }} 42 | #ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | SX files 2 | .DS_Store 3 | 4 | # Ignore gradle files 5 | .gradle 6 | .gradletasknamecache 7 | 8 | # generated files 9 | bin/ 10 | gen/ 11 | build/ 12 | gdbserver 13 | gdb.setup 14 | *.o 15 | *.o.d 16 | 17 | # built application files 18 | *.apk 19 | *.ap_ 20 | 21 | # files for the dex VM 22 | *.dex 23 | 24 | # shared object binaries 25 | *.so 26 | 27 | # Java class files 28 | *.class 29 | 30 | # Local configuration file (sdk path, etc) 31 | local.properties 32 | 33 | # Eclipse project files 34 | .classpath 35 | .project 36 | .settings/org.eclipse.buildship.core.prefs 37 | 38 | # Proguard folder generated by Eclipse 39 | proguard/ 40 | 41 | # Intellij project files 42 | *.iml 43 | *.ipr 44 | *.iws 45 | .idea/ 46 | 47 | # NetBeans project files 48 | .nb-gradle/ 49 | 50 | # Code 51 | .vscode/settings.json 52 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "PdCore/src/main/jni/libpd"] 2 | path = PdCore/src/main/jni/libpd 3 | url = https://github.com/libpd/libpd.git 4 | -------------------------------------------------------------------------------- /CircleOfFifths/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CircleOfFifths/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | dependencies { 4 | implementation project(':PdCore') 5 | } 6 | 7 | android { 8 | compileSdkVersion rootProject.compileSdkVersion 9 | buildToolsVersion rootProject.buildToolsVersion 10 | ndkVersion rootProject.ndkVersion 11 | namespace = 'org.puredata.android.fifths' 12 | 13 | defaultConfig { 14 | applicationId "org.puredata.android.fifths" 15 | minSdkVersion rootProject.minSdkVersion 16 | targetSdkVersion 28 17 | versionCode 3 18 | versionName "0.3" 19 | } 20 | 21 | sourceSets { 22 | main { 23 | manifest.srcFile 'AndroidManifest.xml' 24 | java.srcDirs = ['src'] 25 | resources.srcDirs = ['src'] 26 | aidl.srcDirs = ['src'] 27 | renderscript.srcDirs = ['src'] 28 | res.srcDirs = ['res'] 29 | assets.srcDirs = ['assets'] 30 | } 31 | 32 | // Move the build types to build-types/ 33 | // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... 34 | // This moves them out of them default location under src//... which would 35 | // conflict with src/ being used by the main source set. 36 | // Adding new build types or product flavors should be accompanied 37 | // by a similar customization. 38 | debug.setRoot('build-types/debug') 39 | release.setRoot('build-types/release') 40 | } 41 | 42 | lintOptions { 43 | ignore 'ExpiredTargetSdkVersion' 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CircleOfFifths/misc/Makefile: -------------------------------------------------------------------------------- 1 | FILES = ab.svg a.svg bb.svg b.svg c.svg db.svg d.svg eb.svg e.svg fs.svg \ 2 | f.svg g.svg 3 | 4 | .PHONY: all clean 5 | 6 | .SUFFIXES: 7 | 8 | .SUFFIXES: .svg .png 9 | 10 | .svg.png: 11 | rsvg-convert -a -h 72 -o $@ $< 12 | 13 | all: ${FILES:.svg=.png} 14 | 15 | clean: 16 | -rm *.png 17 | 18 | -------------------------------------------------------------------------------- /CircleOfFifths/misc/android.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 22 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 56 | 59 | 62 | 66 | 70 | 79 | 88 | 97 | 106 | 116 | 126 | 136 | 146 | 147 | 157 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /CircleOfFifths/misc/android_guitar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/misc/android_guitar.png -------------------------------------------------------------------------------- /CircleOfFifths/misc/guitar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/misc/guitar.png -------------------------------------------------------------------------------- /CircleOfFifths/misc/guitar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 26 | 30 | 34 | 35 | 38 | 42 | 46 | 47 | 50 | 54 | 58 | 59 | 61 | 65 | 69 | 70 | 77 | 84 | 94 | 103 | 112 | 113 | 132 | 134 | 135 | 137 | image/svg+xml 138 | 140 | 141 | 142 | 143 | 144 | 150 | 156 | 161 | 162 | 168 | 173 | 174 | 180 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/background.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/btn_normal.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/btn_normal.9.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/btn_selected.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/btn_selected.9.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/icon.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks00.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks01.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks02.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks03.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks04.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks05.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks06.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks07.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks08.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks09.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks10.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/ks11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/drawable/ks11.png -------------------------------------------------------------------------------- /CircleOfFifths/res/drawable/radiobuttons.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /CircleOfFifths/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 10 | 13 | 16 | 19 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /CircleOfFifths/res/menu/circle_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CircleOfFifths/res/raw/patch.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/CircleOfFifths/res/raw/patch.zip -------------------------------------------------------------------------------- /CircleOfFifths/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Circle Of Fifths 4 | Dom7\nDim7 5 | Maj7\nMin7 6 | Added\nSixth 7 | Sus4\nSus2 8 | Help 9 | About 10 | Circle of Fifths\nCopyright 2010 Peter 11 | Brinkmann\n(peter.brinkmann@gmail.com)\nhttp://nettoyeur.noisepages.com/ 12 | Tap a field in order to play the corresponding 13 | chord. Change the key signature by dragging the rim of the circle. The 14 | top line of a button describes the effect on major 15 | chords; the bottom line describes the effect on minor chords. 16 | 17 | -------------------------------------------------------------------------------- /CircleOfFifths/src/org/puredata/android/fifths/CircleOfFifths.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @author Peter Brinkmann (peter.brinkmann@gmail.com) 4 | * 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | */ 9 | 10 | package org.puredata.android.fifths; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | 15 | import org.puredata.android.io.AudioParameters; 16 | import org.puredata.android.io.PdAudio; 17 | import org.puredata.core.PdBase; 18 | import org.puredata.core.utils.IoUtils; 19 | 20 | import android.app.Activity; 21 | import android.app.AlertDialog; 22 | import android.os.Bundle; 23 | import android.view.Menu; 24 | import android.view.MenuInflater; 25 | import android.view.MenuItem; 26 | import android.view.View; 27 | import android.view.View.OnClickListener; 28 | import android.widget.RadioGroup; 29 | import android.widget.Toast; 30 | 31 | 32 | public class CircleOfFifths extends Activity implements OnClickListener { 33 | 34 | private static final String TAG = "Pd Circle Of Fifths"; 35 | private static final String TOP = "top"; 36 | private static final int MIN_SAMPLE_RATE = 44100; 37 | private RadioGroup options; 38 | private int option = 0; 39 | 40 | private Toast toast = null; 41 | 42 | private void toast(final String msg) { 43 | runOnUiThread(new Runnable() { 44 | @Override 45 | public void run() { 46 | if (toast == null) { 47 | toast = Toast.makeText(getApplicationContext(), "", Toast.LENGTH_SHORT); 48 | } 49 | toast.setText(TAG + ": " + msg); 50 | toast.show(); 51 | } 52 | }); 53 | } 54 | 55 | @Override 56 | protected void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | initGui(); 59 | try { 60 | initPd(); 61 | } catch (IOException e) { 62 | toast(e.toString()); 63 | finish(); 64 | } 65 | } 66 | 67 | @Override 68 | protected void onStart() { 69 | super.onStart(); 70 | PdAudio.startAudio(this); 71 | } 72 | 73 | @Override 74 | protected void onStop() { 75 | PdAudio.stopAudio(); 76 | super.onStop(); 77 | } 78 | 79 | @Override 80 | protected void onDestroy() { 81 | cleanup(); 82 | super.onDestroy(); 83 | } 84 | 85 | private void initGui() { 86 | setContentView(R.layout.main); 87 | CircleView circle = (CircleView) findViewById(R.id.circleview); 88 | circle.setOwner(this); 89 | int top = getPreferences(MODE_PRIVATE).getInt(TOP, 0); 90 | circle.setTopSegment(top); 91 | options = (RadioGroup) findViewById(R.id.options); 92 | findViewById(R.id.domdim).setOnClickListener(this); 93 | findViewById(R.id.majmin).setOnClickListener(this); 94 | findViewById(R.id.sixth).setOnClickListener(this); 95 | findViewById(R.id.susp).setOnClickListener(this); 96 | } 97 | 98 | private void initPd() throws IOException { 99 | AudioParameters.init(this); 100 | int srate = Math.max(MIN_SAMPLE_RATE, AudioParameters.suggestSampleRate()); 101 | PdAudio.initAudio(srate, 0, 2, 1, true); 102 | 103 | File dir = getFilesDir(); 104 | File patchFile = new File(dir, "chords.pd"); 105 | IoUtils.extractZipResource(getResources().openRawResource(R.raw.patch), dir, true); 106 | PdBase.openPatch(patchFile.getAbsolutePath()); 107 | } 108 | 109 | private void cleanup() { 110 | // make sure to release all resources 111 | PdAudio.release(); 112 | PdBase.release(); 113 | } 114 | 115 | public void playChord(boolean major, int n) { 116 | PdBase.sendList("playchord", option + (major ? 1 : 0), n); 117 | } 118 | 119 | public void endChord() { 120 | PdBase.sendBang("endchord"); 121 | resetOptions(); 122 | } 123 | 124 | public void setTop(int top) { 125 | PdBase.sendFloat("shift", top); 126 | getPreferences(MODE_PRIVATE).edit().putInt(TOP, top).commit(); 127 | } 128 | 129 | @Override 130 | public void onClick(View v) { 131 | int newOption; 132 | int id = v.getId(); 133 | if (id == R.id.domdim) { 134 | newOption = 2; 135 | } else if (id == R.id.majmin) { 136 | newOption = 4; 137 | } else if (id == R.id.sixth) { 138 | newOption = 6; 139 | } else if (id == R.id.susp) { 140 | newOption = 8; 141 | } else { 142 | newOption = 0; 143 | } 144 | if (option == newOption) { 145 | resetOptions(); 146 | } else { 147 | option = newOption; 148 | } 149 | } 150 | 151 | private void resetOptions() { 152 | option = 0; 153 | options.clearCheck(); 154 | } 155 | 156 | @Override 157 | public boolean onCreateOptionsMenu(Menu menu) { 158 | MenuInflater inflater = getMenuInflater(); 159 | inflater.inflate(R.menu.circle_menu, menu); 160 | return true; 161 | } 162 | 163 | @Override 164 | public boolean onOptionsItemSelected(MenuItem item) { 165 | AlertDialog.Builder ad = new AlertDialog.Builder(this); 166 | int itemId = item.getItemId(); 167 | if (itemId == R.id.about_item) { 168 | ad.setTitle(R.string.about_title); 169 | ad.setMessage(R.string.about_msg); 170 | } else if (itemId == R.id.help_item) { 171 | ad.setTitle(R.string.help_title); 172 | ad.setMessage(R.string.help_msg); 173 | } 174 | ad.setNeutralButton(android.R.string.ok, null); 175 | ad.setCancelable(true); 176 | ad.show(); 177 | return true; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /PdCore/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /PdCore/LICENSE.txt: -------------------------------------------------------------------------------- 1 | This software is copyrighted by Peter Brinkmann and others. The following 2 | terms (the "Standard Improved BSD License") apply to all files associated with 3 | the software unless explicitly disclaimed in individual files: 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 3. The name of the author may not be used to endorse or promote 16 | products derived from this software without specific prior 17 | written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 21 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 22 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR 23 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 29 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 30 | THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /PdCore/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'signing' 4 | id 'maven-publish' 5 | } 6 | 7 | group = rootProject.group 8 | archivesBaseName = 'pd-core' 9 | version = rootProject.version 10 | 11 | dependencies { 12 | api 'com.noisepages.nettoyeur:midi:1.0.0-rc1' 13 | implementation 'com.noisepages.nettoyeur:midi:1.0.0-rc1' 14 | implementation 'androidx.legacy:legacy-support-v4:' + rootProject.androidxLegacySupportVersion 15 | } 16 | 17 | android { 18 | compileSdkVersion rootProject.compileSdkVersion 19 | buildToolsVersion rootProject.buildToolsVersion 20 | ndkVersion rootProject.ndkVersion 21 | namespace = 'org.puredata.android.service' 22 | 23 | defaultConfig { 24 | minSdkVersion rootProject.minSdkVersion 25 | targetSdkVersion 33 26 | versionCode 1 27 | versionName version 28 | } 29 | 30 | sourceSets { 31 | main { 32 | manifest.srcFile 'AndroidManifest.xml' 33 | java.srcDirs = ['src/main/java', 'src/main/jni/libpd/java'] 34 | jniLibs.srcDir 'src/main/libs' //set .so files location to libs 35 | jni.srcDirs = [] //disable automatic ndk-build call 36 | res.srcDirs = ['src/main/res'] 37 | assets.srcDirs = ['assets'] 38 | } 39 | 40 | // Move the build types to build-types/ 41 | // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... 42 | // This moves them out of them default location under src//... which would 43 | // conflict with src/ being used by the main source set. 44 | // Adding new build types or product flavors should be accompanied 45 | // by a similar customization. 46 | debug.setRoot('build-types/debug') 47 | release.setRoot('build-types/release') 48 | } 49 | 50 | tasks.create(name: 'buildNative', type: Exec, description: 'Compile JNI source via NDK') { 51 | commandLine ndkBuildExecutablePath, 52 | '-C', file('src/main/jni').absolutePath, 53 | '-j', Runtime.runtime.availableProcessors(), 54 | 'all', 55 | 'NDK_DEBUG=1' 56 | } 57 | 58 | // After ndk-build, copy libexpr.so to libexpr_tilde.so and libfexpr_tilde.so 59 | buildNative.doLast { 60 | def src = 'libexpr.so' 61 | file('src/main/libs').eachDir() { dir -> 62 | println "Cloning $src in $dir" 63 | copy { from(dir) into(dir) include(src) rename(src, 'libexpr_tilde.so') } 64 | copy { from(dir) into(dir) include(src) rename(src, 'libfexpr_tilde.so') } 65 | } 66 | } 67 | 68 | tasks.create(name: 'cleanNative', type: Exec, description: 'Clean JNI object files') { 69 | commandLine ndkBuildExecutablePath, '-C', file('src/main/jni').absolutePath, 'clean' 70 | } 71 | 72 | clean.configure { 73 | dependsOn tasks.named('cleanNative') 74 | } 75 | 76 | tasks.withType(JavaCompile).configureEach { 77 | dependsOn tasks.named('buildNative') 78 | } 79 | 80 | libraryVariants.all { variant -> 81 | variant.outputs.all { output -> 82 | outputFileName = "${archivesBaseName}.aar" 83 | } 84 | } 85 | } 86 | 87 | import org.apache.tools.ant.taskdefs.condition.Os 88 | 89 | // TODO: Move to convention plugin? 90 | def getNdkBuildExecutablePath() { 91 | // android.ndkDirectory should return project.android.ndkVersion ndkDirectory 92 | def ndkDir = android.ndkDirectory.absolutePath 93 | def ndkBuildName = Os.isFamily(Os.FAMILY_WINDOWS) ? 'ndk-build.cmd' : 'ndk-build' 94 | def ndkBuildFullPath = new File(ndkDir, ndkBuildName).getAbsolutePath() 95 | if (!new File(ndkBuildFullPath).canExecute()) { 96 | throw new GradleScriptException("ndk-build executable not found: $ndkBuildFullPath") 97 | } 98 | return ndkBuildFullPath 99 | } 100 | 101 | task sourcesJar(type: Jar) { 102 | archiveClassifier.set('sources') 103 | from android.sourceSets.main.java.srcDirs 104 | } 105 | 106 | task javadoc(type: Javadoc) { 107 | source = android.sourceSets.main.java.srcDirs 108 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 109 | failOnError = false // TODO: fix javadoc issues 110 | } 111 | 112 | task javadocJar(type: Jar, dependsOn: javadoc) { 113 | archiveClassifier.set('javadoc') 114 | from javadoc.destinationDir 115 | } 116 | 117 | artifacts { 118 | archives javadocJar 119 | archives sourcesJar 120 | } 121 | 122 | def siteUrl = 'https://github.com/libpd/pd-for-android' 123 | 124 | publishing { 125 | publications { 126 | maven(MavenPublication) { 127 | groupId group 128 | artifactId archivesBaseName 129 | version version 130 | // TODO: include aar artifact from components? 131 | artifact "${buildDir}/outputs/aar/${archivesBaseName}.aar" 132 | // ossrh requires javadoc and sources 133 | artifact sourcesJar 134 | artifact javadocJar 135 | 136 | pom { 137 | name = "${project.group}:${project.archivesBaseName}" 138 | description = 'Pure Data for Android' 139 | url = siteUrl 140 | licenses { 141 | license { 142 | name = 'BSD New' 143 | url = 'https://raw.githubusercontent.com/libpd/pd-for-android/master/PdCore/LICENSE.txt' 144 | } 145 | } 146 | developers { 147 | developer { 148 | id = 'joebowbeer' 149 | name = 'Joe Bowbeer' 150 | } 151 | // TODO: Add all other devs here... 152 | } 153 | scm { 154 | connection = 'scm:git:https://github.com/libpd/pd-for-android' 155 | developerConnection = 'scm:git:ssh://github.com/libpd/pd-for-android.git' 156 | url = siteUrl 157 | } 158 | } 159 | } 160 | } 161 | } 162 | 163 | // configure publishing to a local directory for testing (not necessary) 164 | // ./gradlew publishMavenPublicationToLocalRepository 165 | // tree ./PdCore/build/repos 166 | publishing { 167 | repositories { 168 | maven { 169 | name = 'local' 170 | def releasesRepoUrl = "$buildDir/repos/releases" 171 | def snapshotsRepoUrl = "$buildDir/repos/snapshots" 172 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 173 | } 174 | } 175 | } 176 | 177 | // ossrh requires signed releases, but not snapshots. 178 | // This configures signing if a key is found. 179 | // The following environment variables provide a signing key and passphrase: 180 | // export ORG_GRADLE_PROJECT_signingKey=`cat private.pgp` 181 | // export ORG_GRADLE_PROJECT_signingPassword="" 182 | // After making the above available, you can try signing using 183 | // ./gradlew signMavenPublication 184 | def hasSigningKey = project.hasProperty('signingKeyId') || project.hasProperty('signingKey') 185 | if (hasSigningKey) { 186 | sign(project) 187 | } 188 | void sign(Project project) { 189 | project.signing { 190 | required { project.gradle.taskGraph.hasTask('required') } 191 | def signingKeyId = project.findProperty('signingKeyId') 192 | def signingKey = project.findProperty('signingKey') 193 | def signingPassword = project.findProperty('signingPassword') 194 | if (signingKeyId) { 195 | // use in-memory ascii-armored OpenPGP subkey 196 | useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) 197 | } else if (signingKey) { 198 | // use in-memory ascii-armored key 199 | useInMemoryPgpKeys(signingKey, signingPassword) 200 | } 201 | sign publishing.publications.maven 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /PdCore/src/main/java/org/puredata/android/io/AudioFormatUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 4 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 5 | * 6 | */ 7 | 8 | package org.puredata.android.io; 9 | 10 | import org.puredata.android.utils.Properties; 11 | 12 | import android.media.AudioFormat; 13 | import android.util.Log; 14 | 15 | /** 16 | * @author Peter Brinkmann (peter.brinkmann@gmail.com) 17 | * 18 | */ 19 | public class AudioFormatUtil { 20 | 21 | private AudioFormatUtil() { 22 | // do nothing 23 | } 24 | 25 | public static int getInFormat(int inChannels) { 26 | switch (inChannels) { 27 | case 1: return AudioFormat.CHANNEL_IN_MONO; 28 | case 2: return AudioFormat.CHANNEL_IN_STEREO; 29 | default: throw new IllegalArgumentException("illegal number of input channels: " + inChannels); 30 | } 31 | } 32 | 33 | public static int getOutFormat(int outChannels) { 34 | switch (outChannels) { 35 | case 1: return AudioFormat.CHANNEL_OUT_MONO; 36 | case 2: return AudioFormat.CHANNEL_OUT_STEREO; 37 | case 4: return AudioFormat.CHANNEL_OUT_QUAD; 38 | case 6: return AudioFormat.CHANNEL_OUT_5POINT1; 39 | case 8: return AudioFormat.CHANNEL_OUT_7POINT1; 40 | default: throw new IllegalArgumentException("illegal number of output channels: " + outChannels); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PdCore/src/main/java/org/puredata/android/io/AudioRecordWrapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 4 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 5 | * 6 | */ 7 | 8 | package org.puredata.android.io; 9 | 10 | import java.io.IOException; 11 | import java.util.concurrent.BlockingQueue; 12 | import java.util.concurrent.SynchronousQueue; 13 | 14 | import android.media.AudioFormat; 15 | import android.media.AudioRecord; 16 | import android.media.MediaRecorder; 17 | import android.os.Process; 18 | 19 | /** 20 | * 21 | * AudioRecordWrapper is a wrapper for {@link AudioRecord}. It is an auxiliary class for {@link AudioWrapper}; 22 | * the purpose of the bizarre queuing mechanism is to work around the AudioRecord.read blocking problem on Droid X, 23 | * without messing things up on other devices. 24 | * 25 | * @author Peter Brinkmann (peter.brinkmann@gmail.com) 26 | * 27 | */ 28 | public class AudioRecordWrapper { 29 | 30 | private static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT; 31 | private final AudioRecord rec; 32 | private final int bufSizeShorts; 33 | private final BlockingQueue queue = new SynchronousQueue(); 34 | private Thread inputThread = null; 35 | 36 | public AudioRecordWrapper(int sampleRate, int inChannels, int bufferSizePerChannel) throws IOException { 37 | int channelConfig = AudioFormatUtil.getInFormat(inChannels); 38 | bufSizeShorts = inChannels * bufferSizePerChannel; 39 | int bufSizeBytes = 2 * bufSizeShorts; 40 | int recSizeBytes = 2 * bufSizeBytes; 41 | int minRecSizeBytes = AudioRecord.getMinBufferSize(sampleRate, channelConfig, ENCODING); 42 | if (minRecSizeBytes <= 0) { 43 | throw new IOException("bad AudioRecord parameters; sr: " + sampleRate + ", ch: " + inChannels + ", bufSize: " + bufferSizePerChannel); 44 | } 45 | while (recSizeBytes < minRecSizeBytes) recSizeBytes += bufSizeBytes; 46 | rec = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelConfig, ENCODING, recSizeBytes); 47 | if (rec != null && rec.getState() != AudioRecord.STATE_INITIALIZED) { 48 | rec.release(); 49 | throw new IOException("unable to initialize AudioRecord instance for sr: " + sampleRate + ", ch: " + inChannels + ", bufSize: " + bufferSizePerChannel); 50 | } 51 | } 52 | 53 | public synchronized void start() { 54 | inputThread = new Thread() { 55 | @Override 56 | public void run() { 57 | Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO); 58 | rec.startRecording(); 59 | short buf[] = new short[bufSizeShorts]; 60 | short auxBuf[] = new short[bufSizeShorts]; 61 | while (!Thread.interrupted()) { 62 | int nRead = 0; 63 | while (nRead < bufSizeShorts && !Thread.interrupted()) { 64 | nRead += rec.read(buf, nRead, bufSizeShorts - nRead); 65 | } 66 | if (nRead < bufSizeShorts) break; 67 | try { 68 | queue.put(buf); 69 | } catch (InterruptedException e) { 70 | break; 71 | } 72 | short tmp[] = buf; 73 | buf = auxBuf; 74 | auxBuf = tmp; 75 | } 76 | rec.stop(); 77 | }; 78 | }; 79 | inputThread.start(); 80 | } 81 | 82 | public synchronized void stop() { 83 | if (inputThread == null) return; 84 | inputThread.interrupt(); 85 | try { 86 | inputThread.join(); 87 | } catch (InterruptedException e) { 88 | Thread.currentThread().interrupt(); // Preserve interrupt flag for caller. 89 | } 90 | inputThread = null; 91 | } 92 | 93 | public synchronized void release() { 94 | stop(); 95 | rec.release(); 96 | queue.clear(); 97 | } 98 | 99 | public short[] poll() { 100 | return queue.poll(); 101 | } 102 | 103 | public short[] take() throws InterruptedException { 104 | return queue.take(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /PdCore/src/main/java/org/puredata/android/io/AudioWrapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 4 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 5 | * 6 | */ 7 | 8 | package org.puredata.android.io; 9 | 10 | import java.io.IOException; 11 | 12 | import org.puredata.android.service.R; 13 | import org.puredata.android.utils.Properties; 14 | 15 | import android.annotation.TargetApi; 16 | import android.content.Context; 17 | import android.media.AudioFormat; 18 | import android.media.AudioManager; 19 | import android.media.AudioRecord; 20 | import android.media.AudioTrack; 21 | import android.media.MediaPlayer; 22 | import android.os.Build; 23 | import android.os.Process; 24 | import android.util.Log; 25 | 26 | /** 27 | * 28 | * AudioWrapper wraps {@link AudioTrack} and {@link AudioRecord} objects and manages the main audio rendering 29 | * thread. It hides the complexity of working with raw PCM audio; client code only needs to implement a JACK-style 30 | * audio processing callback (jackaudio.org). 31 | * 32 | * @author Peter Brinkmann (peter.brinkmann@gmail.com) 33 | * 34 | */ 35 | public abstract class AudioWrapper { 36 | 37 | private static final String AUDIO_WRAPPER = "AudioWrapper"; 38 | private static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT; 39 | private final AudioRecordWrapper rec; 40 | private final AudioTrack track; 41 | final short outBuf[]; 42 | final int inputSizeShorts; 43 | final int bufSizeShorts; 44 | private Thread audioThread = null; 45 | 46 | /** 47 | * Constructor; initializes {@link AudioTrack} and {@link AudioRecord} objects 48 | * 49 | * @param sampleRate 50 | * @param inChannels number of input channels 51 | * @param outChannels number of output channels 52 | * @param bufferSizePerChannel number of samples per buffer per channel 53 | * @throws IOException if the audio parameters are not supported by the device 54 | */ 55 | public AudioWrapper(int sampleRate, int inChannels, int outChannels, int bufferSizePerChannel) throws IOException { 56 | int channelConfig = AudioFormatUtil.getOutFormat(outChannels); 57 | rec = (inChannels == 0) ? null : new AudioRecordWrapper(sampleRate, inChannels, bufferSizePerChannel); 58 | inputSizeShorts = inChannels * bufferSizePerChannel; 59 | bufSizeShorts = outChannels * bufferSizePerChannel; 60 | outBuf = new short[bufSizeShorts]; 61 | int bufSizeBytes = 2 * bufSizeShorts; 62 | int trackSizeBytes = 2 * bufSizeBytes; 63 | int minTrackSizeBytes = AudioTrack.getMinBufferSize(sampleRate, channelConfig, ENCODING); 64 | if (minTrackSizeBytes <= 0) { 65 | throw new IOException("bad AudioTrack parameters; sr: " + sampleRate +", ch: " + outChannels + ", bufSize: " + trackSizeBytes); 66 | } 67 | while (trackSizeBytes < minTrackSizeBytes) trackSizeBytes += bufSizeBytes; 68 | track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, ENCODING, trackSizeBytes, AudioTrack.MODE_STREAM); 69 | if (track.getState() != AudioTrack.STATE_INITIALIZED) { 70 | track.release(); 71 | throw new IOException("unable to initialize AudioTrack instance for sr: " + sampleRate +", ch: " + outChannels + ", bufSize: " + trackSizeBytes); 72 | } 73 | } 74 | 75 | /** 76 | * Main audio rendering callback, reads input samples and writes output samples; inspired by the process callback of JACK 77 | * 78 | * Channels are striped across buffers, i.e., if there are two output channels, then outBuffer[0] will be the first sample 79 | * for the left channel, outBuffer[1] will be the first sample for the right channel, outBuffer[2] will be the second sample 80 | * for the left channel, etc. 81 | * 82 | * @param inBuffer array of input samples to be processed, e.g., from the microphone 83 | * @param outBuffer array of output samples, e.g., to be sent to the speakers 84 | * @return 85 | */ 86 | protected abstract int process(short inBuffer[], short outBuffer[]); 87 | 88 | /** 89 | * Start the audio rendering thread as well as {@link AudioTrack} and {@link AudioRecord} objects 90 | * 91 | * @param context 92 | */ 93 | public synchronized void start(Context context) { 94 | avoidClickHack(context); 95 | audioThread = new Thread() { 96 | @Override 97 | public void run() { 98 | Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO); 99 | if (rec != null) rec.start(); 100 | track.play(); 101 | short inBuf[]; 102 | try { 103 | inBuf = (rec != null) ? rec.take() : new short[inputSizeShorts]; 104 | } catch (InterruptedException e) { 105 | return; 106 | } 107 | while (!Thread.interrupted()) { 108 | if (process(inBuf, outBuf) != 0) break; 109 | track.write(outBuf, 0, bufSizeShorts); 110 | if (rec != null) { 111 | short newBuf[] = rec.poll(); 112 | if (newBuf != null) { 113 | inBuf = newBuf; 114 | } else { 115 | Log.w(AUDIO_WRAPPER, "no input buffer available"); 116 | } 117 | } 118 | } 119 | if (rec != null) rec.stop(); 120 | track.stop(); 121 | } 122 | }; 123 | audioThread.start(); 124 | } 125 | 126 | /** 127 | * Stop the audio thread as well as {@link AudioTrack} and {@link AudioRecord} objects 128 | */ 129 | public synchronized void stop() { 130 | if (audioThread == null) return; 131 | audioThread.interrupt(); 132 | try { 133 | audioThread.join(); 134 | } catch (InterruptedException e) { 135 | Thread.currentThread().interrupt(); // Preserve interrupt flag for caller. 136 | } 137 | audioThread = null; 138 | } 139 | 140 | /** 141 | * Release resources held by {@link AudioTrack} and {@link AudioRecord} objects; 142 | * stops the audio thread if it is still running 143 | */ 144 | public synchronized void release() { 145 | stop(); 146 | track.release(); 147 | if (rec != null) rec.release(); 148 | } 149 | 150 | /** 151 | * @return true if and only if the audio thread is currently running 152 | */ 153 | public synchronized boolean isRunning() { 154 | return audioThread != null && audioThread.getState() != Thread.State.TERMINATED; 155 | } 156 | 157 | /** 158 | * @return the audio session ID 159 | */ 160 | public synchronized int getAudioSessionId() { 161 | return track.getAudioSessionId(); 162 | } 163 | 164 | // weird little hack; eliminates the nasty click when AudioTrack (dis)engages by playing 165 | // a few milliseconds of silence before starting AudioTrack 166 | private void avoidClickHack(Context context) { 167 | try { 168 | MediaPlayer mp = MediaPlayer.create(context, R.raw.silence); 169 | mp.start(); 170 | Thread.sleep(10); 171 | mp.stop(); 172 | mp.release(); 173 | } catch (Exception e) { 174 | Log.e(AUDIO_WRAPPER, e.toString()); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /PdCore/src/main/java/org/puredata/android/io/PdAudio.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 4 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 5 | * 6 | */ 7 | 8 | package org.puredata.android.io; 9 | 10 | import java.io.IOException; 11 | import java.util.Arrays; 12 | 13 | import org.puredata.core.PdBase; 14 | 15 | import android.content.Context; 16 | import android.os.Handler; 17 | import android.os.Looper; 18 | 19 | /** 20 | * 21 | * PdAudio manages an instance of {@link AudioWrapper} that uses Pure Data for audio processing. 22 | * 23 | * @author Peter Brinkmann (peter.brinkmann@gmail.com) 24 | * 25 | */ 26 | public class PdAudio { 27 | 28 | private static AudioWrapper audioWrapper = null; 29 | private static final Handler handler = new Handler(Looper.getMainLooper()); 30 | private static final Runnable pollRunner = new Runnable() { 31 | @Override 32 | public void run() { 33 | PdBase.pollMidiQueue(); 34 | PdBase.pollPdMessageQueue(); 35 | handler.postDelayed(this, 5); 36 | } 37 | }; 38 | 39 | private PdAudio() { 40 | // Do nothing; we just don't want instances of this class. 41 | } 42 | 43 | /** 44 | * Initializes Pure Data as well as audio components. 45 | * 46 | * @param sampleRate 47 | * @param inChannels number of input channels 48 | * @param outChannels number of output channels 49 | * @param ticksPerBuffer number of Pure Data ticks (i.e., blocks of 64 samples) per buffer; choose 1 for minimal latency, 50 | * or more if performance is a concern; for Java audio only (Android 2.2 or earlier), 51 | * ignored by OpenSL components 52 | * @param restart flag indicating whether the audio thread should be stopped if it is currently running 53 | * @throws IOException if the audio parameters are not supported by the device 54 | */ 55 | public synchronized static void initAudio(int sampleRate, int inChannels, int outChannels, final int ticksPerBuffer, boolean restart) 56 | throws IOException { 57 | if (isRunning() && !restart) return; 58 | stopAudio(); 59 | if (PdBase.openAudio(inChannels, outChannels, sampleRate, null) != 0) { 60 | throw new IOException("unable to open Pd audio: " + sampleRate + ", " + inChannels + ", " + outChannels); 61 | } 62 | if (!PdBase.implementsAudio()) { 63 | if (!AudioParameters.checkParameters(sampleRate, inChannels, outChannels) || ticksPerBuffer <= 0) { 64 | throw new IOException("bad Java audio parameters: " + sampleRate + ", " + inChannels + ", " + outChannels + ", " + ticksPerBuffer); 65 | } 66 | int bufferSizePerChannel = ticksPerBuffer * PdBase.blockSize(); 67 | audioWrapper = new AudioWrapper(sampleRate, inChannels, outChannels, bufferSizePerChannel) { 68 | @Override 69 | protected int process(short[] inBuffer, short[] outBuffer) { 70 | Arrays.fill(outBuffer, (short) 0); 71 | int err = PdBase.process(ticksPerBuffer, inBuffer, outBuffer); 72 | PdBase.pollMidiQueue(); 73 | PdBase.pollPdMessageQueue(); 74 | return err; 75 | } 76 | }; 77 | } 78 | } 79 | 80 | /** 81 | * Starts the audio components. 82 | * 83 | * @param context current application context 84 | */ 85 | public synchronized static void startAudio(Context context) { 86 | PdBase.computeAudio(true); 87 | if (PdBase.implementsAudio()) { 88 | handler.post(pollRunner); 89 | PdBase.startAudio(); 90 | } else { 91 | if (audioWrapper == null) { 92 | throw new IllegalStateException("audio not initialized"); 93 | } 94 | audioWrapper.start(context); 95 | } 96 | } 97 | 98 | /** 99 | * Stops the audio components. 100 | */ 101 | public synchronized static void stopAudio() { 102 | if (PdBase.implementsAudio()) { 103 | PdBase.pauseAudio(); 104 | handler.removeCallbacks(pollRunner); 105 | handler.post(new Runnable() { 106 | @Override 107 | public void run() { 108 | PdBase.pollMidiQueue(); // Flush pending messages. 109 | PdBase.pollPdMessageQueue(); 110 | } 111 | }); 112 | } else { 113 | if (!isRunning()) return; 114 | audioWrapper.stop(); 115 | } 116 | } 117 | 118 | /** 119 | * @return true if and only if the audio wrapper is running 120 | */ 121 | public synchronized static boolean isRunning() { 122 | if (PdBase.implementsAudio()) { 123 | return PdBase.isRunning(); 124 | } else { 125 | return audioWrapper != null && audioWrapper.isRunning(); 126 | } 127 | } 128 | 129 | /** 130 | * Releases resources held by the audio components. 131 | */ 132 | public synchronized static void release() { 133 | stopAudio(); 134 | if (PdBase.implementsAudio()) { 135 | PdBase.closeAudio(); 136 | } else { 137 | if (audioWrapper == null) return; 138 | audioWrapper.release(); 139 | audioWrapper = null; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /PdCore/src/main/java/org/puredata/android/midi/MidiToPdAdapter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 4 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 5 | * 6 | */ 7 | 8 | package org.puredata.android.midi; 9 | 10 | import org.puredata.core.PdBase; 11 | 12 | import com.noisepages.nettoyeur.midi.MidiReceiver; 13 | 14 | /** 15 | * Adapter class for connecting output from AndroidMidi to MIDI input for Pd. 16 | * 17 | * @author Peter Brinkmann (peter.brinkmann@gmail.com) 18 | */ 19 | public class MidiToPdAdapter implements MidiReceiver { 20 | 21 | @Override 22 | public void onRawByte(byte value) { 23 | PdBase.sendMidiByte(0, value); 24 | } 25 | 26 | @Override 27 | public void onProgramChange(int channel, int program) { 28 | PdBase.sendProgramChange(channel, program); 29 | } 30 | 31 | @Override 32 | public void onPolyAftertouch(int channel, int key, int velocity) { 33 | PdBase.sendPolyAftertouch(channel, key, velocity); 34 | } 35 | 36 | @Override 37 | public void onPitchBend(int channel, int value) { 38 | PdBase.sendPitchBend(channel, value); 39 | } 40 | 41 | @Override 42 | public void onNoteOn(int channel, int key, int velocity) { 43 | PdBase.sendNoteOn(channel, key, velocity); 44 | } 45 | 46 | @Override 47 | public void onNoteOff(int channel, int key, int velocity) { 48 | PdBase.sendNoteOn(channel, key, 0); 49 | } 50 | 51 | @Override 52 | public void onControlChange(int channel, int controller, int value) { 53 | PdBase.sendControlChange(channel, controller, value); 54 | } 55 | 56 | @Override 57 | public void onAftertouch(int channel, int velocity) { 58 | PdBase.sendAftertouch(channel, velocity); 59 | } 60 | 61 | @Override 62 | public boolean beginBlock() { 63 | return false; 64 | } 65 | 66 | @Override 67 | public void endBlock() {} 68 | } -------------------------------------------------------------------------------- /PdCore/src/main/java/org/puredata/android/midi/PdToMidiAdapter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 4 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 5 | * 6 | */ 7 | 8 | package org.puredata.android.midi; 9 | 10 | import org.puredata.core.PdMidiReceiver; 11 | 12 | import com.noisepages.nettoyeur.midi.MidiReceiver; 13 | 14 | /** 15 | * Adapter class for connecting MIDI output from Pd to input for AndroidMidi. 16 | * 17 | * @author Peter Brinkmann (peter.brinkmann@gmail.com) 18 | */ 19 | public class PdToMidiAdapter implements PdMidiReceiver { 20 | 21 | private final MidiReceiver receiver; 22 | 23 | /** 24 | * Constructor. Note that instances of this class still need to be installed with 25 | * PdBase.setMidiReceiver. 26 | * 27 | * @param receiver to forward MIDI messages to 28 | */ 29 | public PdToMidiAdapter(MidiReceiver receiver) { 30 | this.receiver = receiver; 31 | } 32 | 33 | @Override 34 | public void receiveProgramChange(int channel, int value) { 35 | receiver.onProgramChange(channel, value); 36 | } 37 | 38 | @Override 39 | public void receivePolyAftertouch(int channel, int pitch, int value) { 40 | receiver.onPolyAftertouch(channel, pitch, value); 41 | } 42 | 43 | @Override 44 | public void receivePitchBend(int channel, int value) { 45 | receiver.onPitchBend(channel, value); 46 | } 47 | 48 | @Override 49 | public void receiveNoteOn(int channel, int pitch, int velocity) { 50 | receiver.onNoteOn(channel, pitch, velocity); 51 | } 52 | 53 | @Override 54 | public void receiveMidiByte(int port, int value) { 55 | receiver.onRawByte((byte) value); 56 | } 57 | 58 | @Override 59 | public void receiveControlChange(int channel, int controller, int value) { 60 | receiver.onControlChange(channel, controller, value); 61 | } 62 | 63 | @Override 64 | public void receiveAftertouch(int channel, int value) { 65 | receiver.onAftertouch(channel, value); 66 | } 67 | 68 | @Override 69 | public boolean beginBlock() { 70 | return receiver.beginBlock(); 71 | } 72 | 73 | @Override 74 | public void endBlock() { 75 | receiver.endBlock(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /PdCore/src/main/java/org/puredata/android/service/PdPreferences.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 4 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 5 | * 6 | */ 7 | 8 | package org.puredata.android.service; 9 | 10 | import org.puredata.android.io.AudioParameters; 11 | import org.puredata.core.PdBase; 12 | 13 | import android.content.Context; 14 | import android.content.SharedPreferences; 15 | import android.content.res.Resources; 16 | import android.os.Bundle; 17 | import android.preference.PreferenceActivity; 18 | import android.preference.PreferenceManager; 19 | 20 | /** 21 | * 22 | * PdPreferences is a simple preference activity for choosing audio properties such 23 | * as sample rate and the number of audio I/O channels. 24 | * 25 | * @author Peter Brinkmann (peter.brinkmann@gmail.com) 26 | * 27 | */ 28 | public class PdPreferences extends PreferenceActivity { 29 | 30 | @SuppressWarnings("deprecation") 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | AudioParameters.init(this); 35 | initPreferences(getApplicationContext()); 36 | addPreferencesFromResource(R.xml.preferences); 37 | } 38 | 39 | @Override 40 | protected void onDestroy() { 41 | super.onDestroy(); 42 | } 43 | 44 | /** 45 | * If no preferences are available, initialize preferences with defaults suggested by {@link PdBase} or {@link AudioParameters}, in that order. 46 | * 47 | * @param context current application context 48 | */ 49 | public static void initPreferences(Context context) { 50 | Resources res = context.getResources(); 51 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 52 | if (!prefs.contains(res.getString(R.string.pref_key_srate))) { 53 | SharedPreferences.Editor editor = prefs.edit(); 54 | int srate = PdBase.suggestSampleRate(); 55 | editor.putString(res.getString(R.string.pref_key_srate), "" + ((srate > 0) ? srate : AudioParameters.suggestSampleRate())); 56 | int nic = PdBase.suggestInputChannels(); 57 | editor.putString(res.getString(R.string.pref_key_inchannels), "" + ((nic > 0) ? nic : AudioParameters.suggestInputChannels())); 58 | int noc = PdBase.suggestOutputChannels(); 59 | editor.putString(res.getString(R.string.pref_key_outchannels), "" + ((noc > 0) ? noc : AudioParameters.suggestOutputChannels())); 60 | editor.commit(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /PdCore/src/main/java/org/puredata/android/service/PdService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 4 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 5 | * 6 | */ 7 | 8 | package org.puredata.android.service; 9 | 10 | import org.puredata.android.io.AudioParameters; 11 | import org.puredata.android.io.PdAudio; 12 | import org.puredata.core.PdBase; 13 | import org.puredata.core.utils.IoUtils; 14 | 15 | import android.app.Notification; 16 | import android.app.NotificationChannel; 17 | import android.app.NotificationManager; 18 | import android.app.PendingIntent; 19 | import android.app.Service; 20 | import android.content.Intent; 21 | import android.content.SharedPreferences; 22 | import android.content.pm.ServiceInfo; 23 | import android.content.res.Resources; 24 | import android.os.Binder; 25 | import android.os.Build; 26 | import android.os.IBinder; 27 | import android.preference.PreferenceManager; 28 | import androidx.core.app.NotificationCompat; 29 | import android.util.Log; 30 | 31 | import java.io.File; 32 | import java.io.IOException; 33 | 34 | /** 35 | * 36 | * PdService allows applications to run Pure Data as a (local) service, with foreground priority if desired. 37 | * 38 | * @author Peter Brinkmann (peter.brinkmann@gmail.com) 39 | * 40 | */ 41 | public class PdService extends Service { 42 | 43 | public class PdBinder extends Binder { 44 | public PdService getService() { 45 | return PdService.this; 46 | } 47 | } 48 | 49 | private static final String TAG = "PD Service"; 50 | private static final int NOTIFICATION_ID = 1; 51 | private static boolean abstractionsInstalled = false; 52 | 53 | private final PdBinder binder = new PdBinder(); 54 | private boolean hasForeground = false; 55 | 56 | private volatile int sampleRate = 0; 57 | private volatile int inputChannels = 0; 58 | private volatile int outputChannels = 0; 59 | private volatile float bufferSizeMillis = 0.0f; 60 | 61 | /** 62 | * @return the current audio buffer size in milliseconds (approximate value; 63 | * the exact value is a multiple of the Pure Data tick size (64 samples)) 64 | */ 65 | public float getBufferSizeMillis() { 66 | return bufferSizeMillis; 67 | } 68 | 69 | /** 70 | * @return number of input channels 71 | */ 72 | public int getInputChannels() { 73 | return inputChannels; 74 | } 75 | 76 | /** 77 | * @return number of output channels 78 | */ 79 | public int getOutputChannels() { 80 | return outputChannels; 81 | } 82 | 83 | /** 84 | * @return current sample rate 85 | */ 86 | public int getSampleRate() { 87 | return sampleRate; 88 | } 89 | 90 | /** 91 | * Initialize Pure Data and audio thread 92 | * 93 | * @param srate sample rate 94 | * @param nic number of input channels 95 | * @param noc number of output channels 96 | * @param millis audio buffer size in milliseconds; for Java audio only (Android 2.2 or earlier), 97 | * will be ignored by OpenSL components 98 | * @throws IOException if the audio parameters are not supported by the device 99 | */ 100 | public synchronized void initAudio(int srate, int nic, int noc, float millis) throws IOException { 101 | stopForeground(); 102 | Resources res = getResources(); 103 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 104 | if (srate < 0) { 105 | String s = prefs.getString(res.getString(R.string.pref_key_srate), null); 106 | if (s != null) { 107 | srate = Integer.parseInt(s); 108 | } else { 109 | srate = PdBase.suggestSampleRate(); 110 | if (srate < 0) { 111 | srate = AudioParameters.suggestSampleRate(); 112 | } 113 | } 114 | } 115 | if (nic < 0) { 116 | String s = prefs.getString(res.getString(R.string.pref_key_inchannels), null); 117 | if (s != null) { 118 | nic = Integer.parseInt(s); 119 | } else { 120 | nic = PdBase.suggestInputChannels(); 121 | if (nic < 0) { 122 | nic = AudioParameters.suggestInputChannels(); 123 | } 124 | } 125 | } 126 | if (noc < 0) { 127 | String s = prefs.getString(res.getString(R.string.pref_key_outchannels), null); 128 | if (s != null) { 129 | noc = Integer.parseInt(s); 130 | } else { 131 | noc = PdBase.suggestOutputChannels(); 132 | if (noc < 0) { 133 | noc = AudioParameters.suggestOutputChannels(); 134 | } 135 | } 136 | } 137 | if (millis < 0) { 138 | millis = 50.0f; // conservative choice 139 | } 140 | int tpb = (int) (0.001f * millis * srate / PdBase.blockSize()) + 1; 141 | PdAudio.initAudio(srate, nic, noc, tpb, true); 142 | sampleRate = srate; 143 | inputChannels = nic; 144 | outputChannels = noc; 145 | bufferSizeMillis = millis; 146 | } 147 | 148 | /** 149 | * Start the audio thread without foreground privileges 150 | */ 151 | public synchronized void startAudio() { 152 | PdAudio.startAudio(this); 153 | } 154 | 155 | /** 156 | * Start the audio thread with foreground privileges 157 | * 158 | * @param intent intent to be triggered when the user selects the notification of the service 159 | * @param icon icon representing the notification 160 | * @param title title of the notification 161 | * @param description description of the notification 162 | */ 163 | public synchronized void startAudio(Intent intent, int icon, String title, String description) { 164 | startAudio(makeNotification(intent, icon, title, description)); 165 | } 166 | 167 | /** 168 | * Start the audio thread with foreground privileges 169 | * 170 | * @param notification notification to display 171 | */ 172 | public synchronized void startAudio(Notification notification) { 173 | startForeground(notification); 174 | PdAudio.startAudio(this); 175 | } 176 | 177 | /** 178 | * Stop the audio thread 179 | */ 180 | public synchronized void stopAudio() { 181 | PdAudio.stopAudio(); 182 | stopForeground(); 183 | } 184 | 185 | /** 186 | * @return true if and only if the audio thread is running 187 | */ 188 | public synchronized boolean isRunning() { 189 | return PdAudio.isRunning(); 190 | } 191 | 192 | /** 193 | * Releases all resources 194 | */ 195 | public synchronized void release() { 196 | stopAudio(); 197 | PdAudio.release(); 198 | PdBase.release(); 199 | } 200 | 201 | @Override 202 | public IBinder onBind(Intent intent) { 203 | return binder; 204 | } 205 | 206 | @Override 207 | public boolean onUnbind(Intent intent) { 208 | release(); 209 | return false; 210 | } 211 | 212 | @Override 213 | public void onCreate() { 214 | super.onCreate(); 215 | AudioParameters.init(this); 216 | if (!abstractionsInstalled) { 217 | try { 218 | File dir = getFilesDir(); 219 | IoUtils.extractZipResource(getResources().openRawResource(R.raw.extra_abs), dir, true); 220 | abstractionsInstalled = true; 221 | PdBase.addToSearchPath(dir.getAbsolutePath()); 222 | PdBase.addToSearchPath(getApplicationInfo().nativeLibraryDir); // Location of standard externals. 223 | } catch (IOException e) { 224 | Log.e(TAG, "unable to unpack abstractions:" + e.toString()); 225 | } 226 | } 227 | } 228 | 229 | @Override 230 | public void onDestroy() { 231 | super.onDestroy(); 232 | release(); 233 | } 234 | 235 | private Notification makeNotification(Intent intent, int icon, String title, String description) { 236 | NotificationManager notificationManager = 237 | (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 238 | NotificationChannel channel = 239 | new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_LOW); 240 | if (notificationManager != null) { 241 | notificationManager.createNotificationChannel(channel); 242 | } 243 | 244 | PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE); 245 | return new NotificationCompat.Builder(PdService.this, TAG) 246 | .setSmallIcon(icon) 247 | .setContentTitle(title) 248 | .setTicker(title) 249 | .setContentText(description) 250 | .setOngoing(true) 251 | .setContentIntent(pi) 252 | .setWhen(System.currentTimeMillis()) 253 | .build(); 254 | } 255 | 256 | private void startForeground(Notification notification) { 257 | stopForeground(); 258 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { 259 | int foregroundServiceTypes = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK; 260 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { 261 | foregroundServiceTypes |= ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; 262 | } 263 | startForeground(NOTIFICATION_ID, notification, foregroundServiceTypes); 264 | } else { 265 | startForeground(NOTIFICATION_ID, notification); 266 | } 267 | hasForeground = true; 268 | } 269 | 270 | private void stopForeground() { 271 | if (hasForeground) { 272 | stopForeground(true); 273 | hasForeground = false; 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /PdCore/src/main/java/org/puredata/android/utils/PdUiDispatcher.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 4 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 5 | * 6 | */ 7 | 8 | package org.puredata.android.utils; 9 | 10 | import org.puredata.core.utils.PdDispatcher; 11 | 12 | import android.os.Handler; 13 | import android.util.Log; 14 | 15 | /** 16 | * Subclass of {@link PdDispatcher} for executing callbacks on the main UI thread 17 | * of an Android app. It is actually more general than that; instances of this 18 | * class will execute their callbacks in whichever thread they were created in, 19 | * but in practice it really only makes sense to create instances of this class 20 | * in the main UI thread. 21 | * 22 | * @author Peter Brinkmann (peter.brinkmann@gmail.com) 23 | */ 24 | public class PdUiDispatcher extends PdDispatcher { 25 | 26 | private final static String TAG = PdUiDispatcher.class.getSimpleName(); 27 | private final Handler handler; 28 | private final Thread target; 29 | 30 | /** 31 | * Constructor; invoke from the main UI thread 32 | */ 33 | public PdUiDispatcher() { 34 | handler = new Handler(); 35 | target = Thread.currentThread(); 36 | } 37 | 38 | @Override 39 | public void print(String s) { 40 | Log.i(TAG, "print: " + s); 41 | } 42 | 43 | @Override 44 | public synchronized void receiveBang(final String source) { 45 | if (Thread.currentThread().equals(target)) { 46 | PdUiDispatcher.super.receiveBang(source); 47 | } else { 48 | handler.post(new Runnable() { 49 | @Override 50 | public void run() { 51 | PdUiDispatcher.super.receiveBang(source); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | @Override 58 | public synchronized void receiveFloat(final String source, final float x) { 59 | if (Thread.currentThread().equals(target)) { 60 | PdUiDispatcher.super.receiveFloat(source, x); 61 | } else { 62 | handler.post(new Runnable() { 63 | @Override 64 | public void run() { 65 | PdUiDispatcher.super.receiveFloat(source, x); 66 | } 67 | }); 68 | } 69 | } 70 | 71 | @Override 72 | public synchronized void receiveSymbol(final String source, final String symbol) { 73 | if (Thread.currentThread().equals(target)) { 74 | PdUiDispatcher.super.receiveSymbol(source, symbol); 75 | } else { 76 | handler.post(new Runnable() { 77 | @Override 78 | public void run() { 79 | PdUiDispatcher.super.receiveSymbol(source, symbol); 80 | } 81 | }); 82 | } 83 | } 84 | 85 | @Override 86 | public synchronized void receiveList(final String source, final Object... args) { 87 | if (Thread.currentThread().equals(target)) { 88 | PdUiDispatcher.super.receiveList(source, args); 89 | } else { 90 | handler.post(new Runnable() { 91 | @Override 92 | public void run() { 93 | PdUiDispatcher.super.receiveList(source, args); 94 | } 95 | }); 96 | } 97 | } 98 | 99 | @Override 100 | public synchronized void receiveMessage(final String source, final String symbol, final Object... args) { 101 | if (Thread.currentThread().equals(target)) { 102 | PdUiDispatcher.super.receiveMessage(source, symbol, args); 103 | } else { 104 | handler.post(new Runnable() { 105 | @Override 106 | public void run() { 107 | PdUiDispatcher.super.receiveMessage(source, symbol, args); 108 | } 109 | }); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /PdCore/src/main/java/org/puredata/android/utils/Properties.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 4 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 5 | * 6 | */ 7 | 8 | package org.puredata.android.utils; 9 | 10 | import android.os.Build; 11 | 12 | /** 13 | * 14 | * Properties is a utility class that checks whether armeabi-v7a is available. 15 | * 16 | * @author Peter Brinkmann (peter.brinkmann@gmail.com) 17 | * 18 | */ 19 | public class Properties { 20 | 21 | /** 22 | * Android version as an integer (e.g., 8 for FroYo) 23 | */ 24 | @SuppressWarnings("deprecation") 25 | public static final int version = Integer.parseInt(Build.VERSION.SDK); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /PdCore/src/main/jni/Android.mk: -------------------------------------------------------------------------------- 1 | include $(call all-subdir-makefiles) 2 | -------------------------------------------------------------------------------- /PdCore/src/main/jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_PLATFORM := android-28 2 | APP_OPTIM := release 3 | APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 4 | -------------------------------------------------------------------------------- /PdCore/src/main/res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/PdCore/src/main/res/drawable/icon.png -------------------------------------------------------------------------------- /PdCore/src/main/res/raw/extra_abs.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/PdCore/src/main/res/raw/extra_abs.zip -------------------------------------------------------------------------------- /PdCore/src/main/res/raw/silence.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/PdCore/src/main/res/raw/silence.wav -------------------------------------------------------------------------------- /PdCore/src/main/res/values/audio.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8000Hz 5 | 11025Hz 6 | 16000Hz 7 | 22050Hz 8 | 32000Hz 9 | 44100Hz 10 | 48000Hz 11 | 12 | 13 | 8000 14 | 11025 15 | 16000 16 | 22050 17 | 32000 18 | 44100 19 | 48000 20 | 21 | 22 | None 23 | Mono 24 | Stereo 25 | 26 | 27 | 0 28 | 1 29 | 2 30 | 31 | 32 | None 33 | Mono 34 | Stereo 35 | Quadraphonic 36 | 5.1 Surround 37 | 7.1 surround 38 | 39 | 40 | 0 41 | 1 42 | 2 43 | 4 44 | 6 45 | 8 46 | 47 | 48 | -------------------------------------------------------------------------------- /PdCore/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pure Data Preferences 4 | Pure Data Preferences 5 | SAMPLE_RATE 6 | Sample rate 7 | Sample rate for Pure Data 8 | INPUT_CHANNELS 9 | Input channels 10 | Number of input channels 11 | OUTPUT_CHANNELS 12 | Output channels 13 | Number of output channels 14 | 15 | -------------------------------------------------------------------------------- /PdCore/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /PdCore/src/main/res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /PdTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PdTest/LICENSE.txt: -------------------------------------------------------------------------------- 1 | This software is copyrighted by Peter Brinkmann and others. The following 2 | terms (the "Standard Improved BSD License") apply to all files associated with 3 | the software unless explicitly disclaimed in individual files: 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 3. The name of the author may not be used to endorse or promote 16 | products derived from this software without specific prior 17 | written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 21 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 22 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR 23 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 29 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 30 | THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /PdTest/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | import org.apache.tools.ant.taskdefs.condition.Os 4 | 5 | dependencies { 6 | implementation project(':PdCore') 7 | implementation 'androidx.legacy:legacy-support-v4:' + rootProject.androidxLegacySupportVersion 8 | } 9 | 10 | android { 11 | compileSdkVersion rootProject.compileSdkVersion 12 | buildToolsVersion rootProject.buildToolsVersion 13 | ndkVersion rootProject.ndkVersion 14 | namespace = 'org.puredata.android.test' 15 | 16 | defaultConfig { 17 | minSdkVersion rootProject.minSdkVersion 18 | targetSdkVersion 33 19 | versionCode 1 20 | versionName '1.0' 21 | 22 | // Uncomment the following 'ndk' section to include only 32-bit CPU architectures in the APK 23 | // ndk { 24 | // abiFilters 'x86', 'armeabi-v7a' 25 | // } 26 | } 27 | 28 | buildTypes { 29 | release { 30 | minifyEnabled true 31 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 32 | } 33 | } 34 | 35 | sourceSets { 36 | main { 37 | manifest.srcFile 'AndroidManifest.xml' 38 | java.srcDirs = ['src'] 39 | jniLibs.srcDir 'libs' //set .so files location to libs 40 | jni.srcDirs = [] //disable automatic ndk-build call 41 | resources.srcDirs = ['src'] 42 | aidl.srcDirs = ['src'] 43 | renderscript.srcDirs = ['src'] 44 | res.srcDirs = ['res'] 45 | assets.srcDirs = ['assets'] 46 | } 47 | 48 | // Move the build types to build-types/ 49 | // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... 50 | // This moves them out of the default location under src//... which would 51 | // conflict with src/ being used by the main source set. 52 | // Adding new build types or product flavors should be accompanied 53 | // by a similar customization. 54 | debug.setRoot('build-types/debug') 55 | release.setRoot('build-types/release') 56 | } 57 | 58 | tasks.create(name: 'buildNative', type: Exec, description: 'Compile JNI source via NDK') { 59 | commandLine ndkBuildExecutablePath, 60 | 'V=1', 61 | '-C', file('jni').absolutePath, 62 | '-j', Runtime.runtime.availableProcessors(), 63 | 'all', 64 | 'NDK_DEBUG=1' 65 | } 66 | 67 | tasks.create(name: 'cleanNative', type: Exec, description: 'Clean JNI object files') { 68 | commandLine ndkBuildExecutablePath, 'V=1', '-C', file('jni').absolutePath, 'clean' 69 | } 70 | 71 | clean.configure { 72 | dependsOn tasks.named('cleanNative') 73 | } 74 | 75 | tasks.withType(JavaCompile).configureEach { 76 | dependsOn tasks.named('buildNative') 77 | } 78 | } 79 | 80 | // TODO: Move to convention plugin? 81 | def getNdkBuildExecutablePath() { 82 | // android.ndkDirectory should return project.android.ndkVersion ndkDirectory 83 | def ndkDir = android.ndkDirectory.absolutePath 84 | def ndkBuildName = Os.isFamily(Os.FAMILY_WINDOWS) ? 'ndk-build.cmd' : 'ndk-build' 85 | def ndkBuildFullPath = new File(ndkDir, ndkBuildName).getAbsolutePath() 86 | if (!new File(ndkBuildFullPath).canExecute()) { 87 | throw new GradleScriptException("ndk-build executable not found: $ndkBuildFullPath") 88 | } 89 | return ndkBuildFullPath 90 | } 91 | -------------------------------------------------------------------------------- /PdTest/jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | #--------------------------------------------------------------- 4 | 5 | include $(CLEAR_VARS) 6 | LOCAL_MODULE := pd 7 | LOCAL_EXPORT_C_INCLUDES := ../../PdCore/src/main/jni/libpd/pure-data/src 8 | LOCAL_SRC_FILES := ../../PdCore/src/main/libs/$(TARGET_ARCH_ABI)/libpd.so 9 | ifneq ($(MAKECMDGOALS),clean) 10 | include $(PREBUILT_SHARED_LIBRARY) 11 | endif 12 | 13 | #--------------------------------------------------------------- 14 | 15 | include $(CLEAR_VARS) 16 | LOCAL_MODULE := helloworld 17 | LOCAL_CFLAGS := -DPD 18 | LOCAL_SRC_FILES := helloworld.c 19 | LOCAL_SHARED_LIBRARIES = pd 20 | include $(BUILD_SHARED_LIBRARY) 21 | 22 | #--------------------------------------------------------------- 23 | -------------------------------------------------------------------------------- /PdTest/jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_PLATFORM := android-17 2 | APP_OPTIM := release 3 | APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 4 | APP_ALLOW_MISSING_DEPS=true -------------------------------------------------------------------------------- /PdTest/jni/helloworld.c: -------------------------------------------------------------------------------- 1 | // 'helloworld' external example taken from "HOWTO write an External for Pure Data" by IOhannes m Zmoelnig, 2014 2 | // See: http://pdstatic.iem.at/externals-HOWTO/ 3 | 4 | #include "m_pd.h" 5 | 6 | static t_class *helloworld_class; 7 | 8 | typedef struct _helloworld { 9 | t_object x_obj; 10 | } t_helloworld; 11 | 12 | void helloworld_bang(t_helloworld *x) 13 | { 14 | post("Hello world !!"); 15 | } 16 | 17 | void *helloworld_new(void) 18 | { 19 | t_helloworld *x = (t_helloworld *)pd_new(helloworld_class); 20 | 21 | return (void *)x; 22 | } 23 | 24 | void helloworld_setup(void) { 25 | helloworld_class = class_new(gensym("helloworld"), 26 | (t_newmethod)helloworld_new, 27 | 0, sizeof(t_helloworld), 28 | CLASS_DEFAULT, 0); 29 | class_addbang(helloworld_class, helloworld_bang); 30 | } -------------------------------------------------------------------------------- /PdTest/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Keep PdCore and AndroidMidi classes unchanged. 2 | -keep class org.puredata.** { *; } 3 | -keep class com.noisepages.nettoyeur.** { *; } 4 | -------------------------------------------------------------------------------- /PdTest/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/PdTest/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /PdTest/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/PdTest/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /PdTest/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libpd/pd-for-android/f52051edc4da2d12333afe5303c4df82385dc19f/PdTest/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /PdTest/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |