├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── MIGRATION.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── cleveroad │ │ └── example │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── cleveroad │ │ │ └── example │ │ │ ├── AudioRecorder.java │ │ │ ├── AudioRecordingFragment.java │ │ │ ├── AudioVisualizationFragment.java │ │ │ ├── Complex.java │ │ │ ├── FFT.java │ │ │ ├── IAudioRecorder.java │ │ │ ├── MainActivity.java │ │ │ ├── MainFragment.java │ │ │ ├── PriorityRunnable.java │ │ │ ├── ProcessPriority.java │ │ │ └── SpeechRecognitionFragment.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_audio_recording.xml │ │ └── fragment_gles.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── cleveroad │ └── example │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── images ├── article.jpg ├── demo.gif ├── demo_.gif ├── header.jpg └── logo-footer.png ├── library ├── .gitignore ├── build.gradle ├── gradle-mvn-push.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── cleveroad │ │ └── example │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── cleveroad │ │ └── audiovisualization │ │ ├── AudioVisualization.java │ │ ├── Constants.java │ │ ├── DbmHandler.java │ │ ├── GLAudioVisualizationView.java │ │ ├── GLBubble.java │ │ ├── GLRectangle.java │ │ ├── GLRenderer.java │ │ ├── GLShape.java │ │ ├── GLWave.java │ │ ├── GLWaveLayer.java │ │ ├── InnerAudioVisualization.java │ │ ├── SpeechRecognizerDbmHandler.java │ │ ├── Utils.java │ │ ├── VisualizerDbmHandler.java │ │ ├── VisualizerWrapper.java │ │ └── utils │ │ ├── SystemPropertiesProxy.java │ │ └── TunnelPlayerWorkaround.java │ └── res │ ├── raw │ └── av_workaround_1min.mp3 │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── public.xml │ └── strings.xml ├── settings.gradle └── wallpaper ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── cleveroad │ └── wallpaper │ └── ExampleInstrumentedTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── cleveroad │ │ └── wallpaper │ │ ├── AudioVisualizationWallpaperService.java │ │ ├── ColorPickerDialogFragment.java │ │ ├── SettingsActivity.java │ │ └── SettingsFragment.java └── res │ ├── drawable-hdpi │ ├── ic_color_lens.png │ └── ic_save.png │ ├── drawable-mdpi │ ├── ic_color_lens.png │ └── ic_save.png │ ├── drawable-xhdpi │ ├── ic_color_lens.png │ └── ic_save.png │ ├── drawable-xxhdpi │ ├── ic_color_lens.png │ └── ic_save.png │ ├── drawable-xxxhdpi │ ├── ic_color_lens.png │ └── ic_save.png │ ├── layout │ ├── activity_settings.xml │ ├── fragment_color_picker.xml │ ├── fragment_settings.xml │ └── popup_presets.xml │ ├── menu │ └── main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── wallpaper.xml └── test └── java └── com └── cleveroad └── wallpaper └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | build 7 | /captures 8 | gradlew* -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # WaveInApp [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) 2 | ![Header image](/images/header.jpg) 3 | ## Changelog 4 | | Version | Changes | 5 | | --- | --- | 6 | | v.1.0.1 | | 7 | | v.0.9.4 | Fixed issues | 8 | | v.0.9.3 | Fixed concurrent modification exception. Added ability to set number of bubbles per layer | 9 | | v.0.9.2 | Added voice recording example. Added ability to build audio visualization renderer. | 10 | | v.0.9.1 | Added ability to set custom dBm handler implementations; implemented SpeechRecognizerDbmHandler | 11 | | v.0.9.0 | First public release | 12 |
13 | 14 | ## Support 15 | 16 | If you have any other questions regarding the use of this library, please contact us for support at info@cleveroad.com (email subject: "Android visualization view. Support request.") 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Cleveroad Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # WaveInApp [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) 2 | ![Header image](/images/header.jpg) 3 | 4 | ## Migration from v.0.9.2 to v.0.9.3 5 | All attributes and appropriate methods in builder were renamed. 6 | 7 | | Old name | New name | 8 | | --- | --- | 9 | | av_waves_count | av_wavesCount | 10 | | av_waves_colors | av_wavesColors | 11 | | av_wave_height | av_wavesHeight | 12 | | av_footer_height | av_wavesFooterHeight | 13 | | av_bubble_size | av_bubblesSize | 14 | | av_randomize_bubble_size | av_bubblesRandomizeSizes | 15 | | av_layers_count | av_layersCount | 16 | | av_background_color | av_backgroundColor | 17 | 18 | ## Migration from v.0.9.1 to v.0.9.2 19 | * **DbmHandler.Factory.newVisualizerHandler(int)** method signature changed to **DbmHandler.Factory.newVisualizerHandler(Context, int)**. 20 | 21 | ## Migration from v.0.9.0 to v.0.9.1 22 | * All library resources and attributes were renamed, prefix `av_` was added. If you're adding Audio Visualization view through XML, make sure you'd properly renamed all attributes. See updated example above. 23 | * All library resources were marked as private. If you're pointing to any library resource (color, dimen, etc), Android Studio will warn you. 24 | * All calculations of dBm values were moved to separate classes. Now you should use **DbmHandler.Factory** class to create new handlers and link it to audio visualization view using **linkTo(DbmHandler)** method. You can provide your implementation of DbmHandler as well. 25 | * **setInnerOnPreparedListener()** and **setInnerOnCompletionListener()** methods moved to new **VisualizerDbmHandler** class. 26 |
27 | 28 | ## Support 29 | If you have any other questions regarding the use of this library, please contact us for support at info@cleveroad.com (email subject: "Android visualization view. Support request.") 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WaveInApp [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) 2 | ![Header image](/images/header.jpg) 3 | 4 | ## Welcome to WaveInApp - Audio Visualization View with wave effect. 5 | 6 | Our library can take audio from any source (audio players, streams, voice input) and animate it with high frame rate. Cool animation, designed specially for the library, responds to sound vibrations. The animation becomes intense when music plays, and once it is paused or stopped – waves calm down. 7 | 8 | The library is a part of implementation of music player. 9 |
10 | 11 | ![Demo image](/images/demo_.gif) 12 | 13 | 14 | Great visualization can spruce up any app, especially audio player. We suggest you smart and good-looking Audio Visualization View for your Android app. You can read about all the advantages this library has and find out how to implement it into your app in our blog post: Case Study: Audio Visualization View For Android by Cleveroad 15 |

16 | 17 | [![Article image](/images/article.jpg)](https://www.cleveroad.com/blog/case-study-audio-visualization-view-for-android-by-cleveroad) 18 |

19 | [![Awesome](/images/logo-footer.png)](https://www.cleveroad.com/?utm_source=github&utm_medium=label&utm_campaign=contacts) 20 |
21 | 22 | ## Setup and usage 23 | 24 | To include this library to your project add dependency in **build.gradle** file: 25 | 26 | ```groovy 27 | dependencies { 28 | implementation 'com.cleveroad:audiovisualization:1.0.1' 29 | } 30 | ``` 31 | 32 | Audio visualization view uses OpenGL ES 2.0 for drawing waves. So you need to include this line in your manifest: 33 | 34 | ```XML 35 | 36 | ``` 37 | 38 | ##### Using VisualizerDbmHandler 39 | 40 | All functionality of this handler built upon [Visualizer] object, so you also need to include this permissions in your manifest: 41 | 42 | ```XML 43 | 44 | 45 | ``` 46 | 47 | ##### Using SpeechRecognizerDbmHandler 48 | 49 | All functionality of this handler built upon [SpeechRecognizer] object, so you also need to include this permissions in your manifest: 50 | 51 | ```XML 52 | 53 | ``` 54 | 55 | You must be very careful with new [Android M permissions] flow. Make sure you have all necessary permissions before using **GLAudioVisualizationView**. 56 | 57 | There are two ways to include **GLAudioVisualizationView** in your layout: directly in XML layout file or using builder in Java code. 58 | 59 | Via XML: 60 | 61 | ```XML 62 | 77 | ``` 78 | 79 | Via Java code: 80 | 81 | ```JAVA 82 | new GLAudioVisualizationView.Builder(getContext()) 83 | .setBubblesSize(R.dimen.bubble_size) 84 | .setBubblesRandomizeSize(true) 85 | .setWavesHeight(R.dimen.wave_height) 86 | .setWavesFooterHeight(R.dimen.footer_height) 87 | .setWavesCount(7) 88 | .setLayersCount(4) 89 | .setBackgroundColorRes(R.color.av_color_bg) 90 | .setLayerColors(R.array.av_colors) 91 | .setBubblesPerLayer(16) 92 | .build(); 93 | ``` 94 | 95 | **GLAudioVisualizationView** implements **AudioVisualization** interface. If you don't need all [GLSurfaceView]'s public methods, you can simply cast your view to **AudioVisualization** interface and use it. 96 | 97 | ```JAVA 98 | private AudioVisualization audioVisualization; 99 | 100 | ... 101 | 102 | @Override 103 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 104 | super.onViewCreated(view, savedInstanceState); 105 | // you can extract AudioVisualization interface for simplifying things 106 | audioVisualization = (AudioVisualization) glAudioVisualizationView; 107 | } 108 | 109 | ... 110 | ``` 111 | 112 | To connect audio visualization view to audio output you can use **linkTo(DbmHandler)** method. See **DbmHandler.Factory** class for the list of available handler implementations. 113 | 114 | ```JAVA 115 | // set speech recognizer handler 116 | SpeechRecognizerDbmHandler speechRecHandler = DbmHandler.Factory.newSpeechRecognizerHandler(context); 117 | speechRecHandler.innerRecognitionListener(...); 118 | audioVisualization.linkTo(speechRecHandler); 119 | 120 | // set audio visualization handler. This will REPLACE previously set speech recognizer handler 121 | VisualizerDbmHandler vizualizerHandler = DbmHandler.Factory.newVisualizerHandler(getContext(), 0); 122 | audioVisualization.linkTo(vizualizerHandler); 123 | ``` 124 | 125 | You must always call **onPause** method to pause visualization and stop wasting CPU resources for computations in vain. As soon as your view appears in sight of user, call **onResume**. 126 | 127 | ```JAVA 128 | @Override 129 | public void onResume() { 130 | super.onResume(); 131 | audioVisualization.onResume(); 132 | } 133 | 134 | @Override 135 | public void onPause() { 136 | audioVisualization.onPause(); 137 | super.onPause(); 138 | } 139 | ``` 140 | 141 | When user leaves screen with audio visualization view, don't forget to free resources and call **release()** method. 142 | 143 | ```JAVA 144 | @Override 145 | public void onDestroyView() { 146 | audioVisualization.release(); 147 | super.onDestroyView(); 148 | } 149 | ``` 150 | 151 | ## Live wallpapers 152 | You can use our Audio Visualization View as a live wallpaper. Just create your own [WallpaperService]. Method `onCreateEngine()` should return your own [Engine]'s implementation, in which you must override the following methods: 153 | * `void onCreate(SurfaceHolder surfaceHolder)` – here create instances of `DbmHandler`, `GLAudioVisualizationView.Builder` (see example below) and `GLAudioVisualizationView.AudioVisualizationRenderer` in which you must set `Engine`'s surface holder and two previous instances via `constructor(GLAudioVisualizationView.Builder)` and `handler()` methods; 154 | * `void onVisibilityChanged(final boolean visible)` – here you must call `onResume()` methods for audioVisualizationView and dbmHandler instances if `visible` parameter is `true`, otherwise – call `onPause()`; 155 | * `void onDestroy()` – just call `release()` for dbmHandler and `onDestroy()` for audioVisualizationView instances 156 | Check JavaDoc of this methods for more info. 157 | 158 | ```JAVA 159 | public class AudioVisualizationWallpaperService extends WallpaperService { 160 | @Override 161 | public Engine onCreateEngine() { 162 | return new WallpaperEngine(); 163 | } 164 | 165 | private class WallpaperEngine extends Engine { 166 | 167 | private WallpaperGLSurfaceView audioVisualizationView; 168 | private DbmHandler dbmHandler; 169 | private GLAudioVisualizationView.AudioVisualizationRenderer renderer; 170 | ... 171 | @Override 172 | public void onCreate(SurfaceHolder surfaceHolder) { 173 | AudioVisualizationWallpaperService context = AudioVisualizationWallpaperService.this; 174 | audioVisualizationView = new WallpaperGLSurfaceView(context); 175 | dbmHandler = DbmHandler.Factory.newVisualizerHandler(context, 0); 176 | ... 177 | GLAudioVisualizationView.Builder builder = new GLAudioVisualizationView.Builder(context) 178 | //... set your settings here (see builder example below); 179 | renderer = new GLAudioVisualizationView.RendererBuilder(builder) 180 | .glSurfaceView(audioVisualizationView) 181 | .handler(dbmHandler) 182 | .build(); 183 | audioVisualizationView.setEGLContextClientVersion(2); 184 | audioVisualizationView.setRenderer(renderer); 185 | } 186 | @Override 187 | public void onVisibilityChanged(final boolean visible) { 188 | //Please follow the next order of methods call! 189 | if (visible) { 190 | audioVisualizationView.onResume(); 191 | dbmHandler.onResume(); 192 | } else { 193 | dbmHandler.onPause(); 194 | audioVisualizationView.onPause(); 195 | } 196 | } 197 | @Override 198 | public void onDestroy() { 199 | dbmHandler.release(); 200 | audioVisualizationView.onDestroy(); 201 | } 202 | 203 | ``` 204 | See uploaded [AudioVisualizationWallpaperService example]. 205 |
206 | 207 | ## Implementing your own DbmHandler 208 | To implement you own data conversion handler, just extend your class from DbmHandler class and implement **onDataReceivedImpl(T object, int layersCount, float[] outDbmValues, float[] outAmpValues)** method where: 209 | * `object` - your custom data type 210 | * `layersCount` - count of layers you passed in **Builder**. 211 | * `outDbmValues` - array with size equals to `layersCount`. You should fill it with **normalized** dBm values for layer in range [0..1]. 212 | * `outAmpValues` - array with size equals to `layersCount`. You should fill it with amplitude values for layer. 213 | Check JavaDoc of this method for more info. 214 | 215 | Then call **onDataReceived(T object)** method to visualize your data. 216 | 217 | Your handler also will receive **onResume()**, **onPause()** and **release()** events from audio visualization view. 218 |
219 | 220 | ## Migrations 221 | See all [migration manuals]. 222 | 223 | ## Changelog 224 | See [changelog history]. 225 |
226 | 227 | ## Troubleshooting 228 | #### Visualization 229 | If you have some issues with visualization (especially on Samsung Galaxy S or HTC devices) make sure you read [this Github issue]. 230 | #### Live wallpapers 231 | * If you have some issues with WaveInApp live wallpapers on Android 6.0 (and later) make sure that all next permissions are granted: `android.permission.RECORD_AUDIO`, `android.permission.MODIFY_AUDIO_SETTINGS`. 232 | * If you run a wallpaper selection screen when the permissions was not granted, the wallpaper will not be appear. You must open settings screen and allow all permissions. If the wallpaper is not yet appeared, just restart the wallpaper selection screen. 233 |
234 | 235 | ## Support 236 | If you have any other questions regarding the use of this library, please contact us for support at info@cleveroad.com (email subject: "Android visualization view. Support request.") 237 |
238 | 239 | ## License 240 | * * * 241 | The MIT License (MIT) 242 | 243 | Copyright (c) 2016 Cleveroad Inc. 244 | 245 | Permission is hereby granted, free of charge, to any person obtaining a copy 246 | of this software and associated documentation files (the "Software"), to deal 247 | in the Software without restriction, including without limitation the rights 248 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 249 | copies of the Software, and to permit persons to whom the Software is 250 | furnished to do so, subject to the following conditions: 251 | 252 | The above copyright notice and this permission notice shall be included in all 253 | copies or substantial portions of the Software. 254 | 255 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 256 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 257 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 258 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 259 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 260 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 261 | SOFTWARE. 262 | 263 | [migration manuals]: /MIGRATION.md 264 | [changelog history]: /CHANGELOG.md 265 | [AudioVisualizationWallpaperService example]: wallpaper/src/main/java/com/cleveroad/wallpaper/AudioVisualizationWallpaperService.java 266 | [WallpaperService]: https://developer.android.com/reference/android/service/wallpaper/WallpaperService.html 267 | [this Github issue]: https://github.com/felixpalmer/android-visualizer/issues/5#issuecomment-25900391 268 | [Engine]: https://developer.android.com/reference/android/service/wallpaper/WallpaperService.Engine.html 269 | [Visualizer]: http://developer.android.com/intl/ru/reference/android/media/audiofx/Visualizer.html 270 | [SpeechRecognizer]: http://developer.android.com/intl/ru/reference/android/speech/SpeechRecognizer.html 271 | [Android M permissions]: http://developer.android.com/intl/ru/training/permissions/requesting.html 272 | [GLSurfaceView]: http://developer.android.com/intl/ru/reference/android/opengl/GLSurfaceView.html 273 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | ### Android template 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | ### JetBrains template 33 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 34 | 35 | *.iml 36 | 37 | ## Directory-based project format: 38 | .idea/ 39 | # if you remove the above rule, at least ignore the following: 40 | 41 | # User-specific stuff: 42 | # .idea/workspace.xml 43 | # .idea/tasks.xml 44 | # .idea/dictionaries 45 | 46 | # Sensitive or high-churn files: 47 | # .idea/dataSources.ids 48 | # .idea/dataSources.xml 49 | # .idea/sqlDataSources.xml 50 | # .idea/dynamic.xml 51 | # .idea/uiDesigner.xml 52 | 53 | # Gradle: 54 | # .idea/gradle.xml 55 | # .idea/libraries 56 | 57 | # Mongo Explorer plugin: 58 | # .idea/mongoSettings.xml 59 | 60 | ## File-based project format: 61 | *.ipr 62 | *.iws 63 | 64 | ## Plugin-specific files: 65 | 66 | # IntelliJ 67 | /out/ 68 | 69 | # mpeltonen/sbt-idea plugin 70 | .idea_modules/ 71 | 72 | # JIRA plugin 73 | atlassian-ide-plugin.xml 74 | 75 | # Crashlytics plugin (for Android Studio and IntelliJ) 76 | com_crashlytics_export_strings.xml 77 | crashlytics.properties 78 | crashlytics-build.properties -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | buildToolsVersion "28.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.cleveroad.audiovisualization.example" 9 | minSdkVersion 14 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled true 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | final SUPPORT_LIB_VERSION = '28.0.0' 24 | 25 | implementation fileTree(dir: 'libs', include: ['*.jar']) 26 | //noinspection GradleDependency 27 | implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION" 28 | implementation project(':library') 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/cleveroad/example/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/cleveroad/example/AudioRecorder.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | import android.media.AudioFormat; 4 | import android.media.AudioRecord; 5 | import android.media.MediaRecorder; 6 | import android.os.Process; 7 | import android.util.Log; 8 | 9 | import java.io.FileNotFoundException; 10 | 11 | /** 12 | * Helper class for audio recording and saving as .wav 13 | */ 14 | class AudioRecorder implements IAudioRecorder { 15 | 16 | public static final int RECORDER_SAMPLE_RATE = 8000; 17 | public static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_OUT_MONO; 18 | public static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT; 19 | 20 | 21 | private static final int BUFFER_BYTES_ELEMENTS = 1024; 22 | private static final int BUFFER_BYTES_PER_ELEMENT = RECORDER_AUDIO_ENCODING; 23 | private static final int RECORDER_CHANNELS_IN = AudioFormat.CHANNEL_IN_MONO; 24 | 25 | 26 | public static final int RECORDER_STATE_FAILURE = -1; 27 | public static final int RECORDER_STATE_IDLE = 0; 28 | public static final int RECORDER_STATE_STARTING = 1; 29 | public static final int RECORDER_STATE_STOPPING = 2; 30 | public static final int RECORDER_STATE_BUSY = 3; 31 | 32 | private volatile int recorderState; 33 | 34 | private final Object recorderStateMonitor = new Object(); 35 | 36 | private RecordingCallback recordingCallback; 37 | 38 | public AudioRecorder recordingCallback(RecordingCallback recordingCallback) { 39 | this.recordingCallback = recordingCallback; 40 | return this; 41 | } 42 | 43 | @SuppressWarnings("ResultOfMethodCallIgnored") 44 | private void onRecordFailure() { 45 | recorderState = RECORDER_STATE_FAILURE; 46 | finishRecord(); 47 | } 48 | 49 | @Override 50 | public void startRecord() { 51 | if (recorderState != RECORDER_STATE_IDLE) { 52 | return; 53 | } 54 | 55 | try { 56 | recorderState = RECORDER_STATE_STARTING; 57 | 58 | startRecordThread(); 59 | } catch (FileNotFoundException e) { 60 | onRecordFailure(); 61 | e.printStackTrace(); 62 | } 63 | } 64 | 65 | private void startRecordThread() throws FileNotFoundException { 66 | 67 | new Thread(new PriorityRunnable(Process.THREAD_PRIORITY_AUDIO) { 68 | 69 | private void onExit() { 70 | synchronized (recorderStateMonitor) { 71 | recorderState = RECORDER_STATE_IDLE; 72 | recorderStateMonitor.notifyAll(); 73 | } 74 | } 75 | 76 | 77 | @SuppressWarnings("ResultOfMethodCallIgnored") 78 | @Override 79 | public void runImpl() { 80 | int bufferSize = Math.max(BUFFER_BYTES_ELEMENTS * BUFFER_BYTES_PER_ELEMENT, 81 | AudioRecord.getMinBufferSize(RECORDER_SAMPLE_RATE, RECORDER_CHANNELS_IN, RECORDER_AUDIO_ENCODING)); 82 | 83 | AudioRecord recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, RECORDER_SAMPLE_RATE, RECORDER_CHANNELS_IN, RECORDER_AUDIO_ENCODING, bufferSize); 84 | 85 | try { 86 | if (recorderState == RECORDER_STATE_STARTING) { 87 | recorderState = RECORDER_STATE_BUSY; 88 | } 89 | recorder.startRecording(); 90 | 91 | byte recordBuffer[] = new byte[bufferSize]; 92 | do { 93 | int bytesRead = recorder.read(recordBuffer, 0, bufferSize); 94 | 95 | if (bytesRead > 0) { 96 | recordingCallback.onDataReady(recordBuffer); 97 | } else { 98 | Log.e(AudioRecorder.class.getSimpleName(), "error: " + bytesRead); 99 | onRecordFailure(); 100 | } 101 | } while (recorderState == RECORDER_STATE_BUSY); 102 | } finally { 103 | recorder.release(); 104 | } 105 | onExit(); 106 | } 107 | }).start(); 108 | } 109 | 110 | @Override 111 | public void finishRecord() { 112 | int recorderStateLocal = recorderState; 113 | if (recorderStateLocal != RECORDER_STATE_IDLE) { 114 | synchronized (recorderStateMonitor) { 115 | recorderStateLocal = recorderState; 116 | if (recorderStateLocal == RECORDER_STATE_STARTING 117 | || recorderStateLocal == RECORDER_STATE_BUSY) { 118 | 119 | recorderStateLocal = recorderState = RECORDER_STATE_STOPPING; 120 | } 121 | 122 | do { 123 | try { 124 | if (recorderStateLocal != RECORDER_STATE_IDLE) { 125 | recorderStateMonitor.wait(); 126 | } 127 | } catch (InterruptedException ignore) { 128 | /* Nothing to do */ 129 | } 130 | recorderStateLocal = recorderState; 131 | } while (recorderStateLocal == RECORDER_STATE_STOPPING); 132 | } 133 | } 134 | } 135 | 136 | 137 | @Override 138 | public boolean isRecording() { 139 | return recorderState != RECORDER_STATE_IDLE; 140 | } 141 | 142 | interface RecordingCallback { 143 | void onDataReady(byte[] data); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/java/com/cleveroad/example/AudioRecordingFragment.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Button; 10 | 11 | import com.cleveroad.audiovisualization.AudioVisualization; 12 | import com.cleveroad.audiovisualization.DbmHandler; 13 | 14 | /** 15 | * Fragment with visualization of audio recording. 16 | */ 17 | public class AudioRecordingFragment extends Fragment { 18 | 19 | public static AudioRecordingFragment newInstance() { 20 | return new AudioRecordingFragment(); 21 | } 22 | 23 | private AudioVisualization audioVisualization; 24 | private Button btnRecord; 25 | private AudioRecordingDbmHandler handler; 26 | private AudioRecorder audioRecorder; 27 | 28 | @Nullable 29 | @Override 30 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 31 | View view = inflater.inflate(R.layout.fragment_audio_recording, container, false); 32 | audioVisualization = (AudioVisualization) view.findViewById(R.id.visualizer_view); 33 | btnRecord = (Button) view.findViewById(R.id.btn_record); 34 | return view; 35 | } 36 | 37 | @Override 38 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 39 | super.onViewCreated(view, savedInstanceState); 40 | audioRecorder = new AudioRecorder(); 41 | handler = new AudioRecordingDbmHandler(); 42 | audioRecorder.recordingCallback(handler); 43 | audioVisualization.linkTo(handler); 44 | btnRecord.setOnClickListener(new View.OnClickListener() { 45 | @Override 46 | public void onClick(View v) { 47 | if (audioRecorder.isRecording()) { 48 | audioRecorder.finishRecord(); 49 | btnRecord.setText(R.string.start_recording); 50 | handler.stop(); 51 | } else { 52 | btnRecord.setText(R.string.stop_recording); 53 | audioRecorder.startRecord(); 54 | } 55 | } 56 | }); 57 | } 58 | 59 | private static class AudioRecordingDbmHandler extends DbmHandler implements AudioRecorder.RecordingCallback { 60 | 61 | private static final float MAX_DB_VALUE = 170; 62 | 63 | private float[] dbs; 64 | private float[] allAmps; 65 | 66 | @Override 67 | protected void onDataReceivedImpl(byte[] bytes, int layersCount, float[] dBmArray, float[] ampsArray) { 68 | 69 | final int bytesPerSample = 2; // As it is 16bit PCM 70 | final double amplification = 100.0; // choose a number as you like 71 | Complex[] fft = new Complex[bytes.length / bytesPerSample]; 72 | for (int index = 0, floatIndex = 0; index < bytes.length - bytesPerSample + 1; index += bytesPerSample, floatIndex++) { 73 | double sample = 0; 74 | for (int b = 0; b < bytesPerSample; b++) { 75 | int v = bytes[index + b]; 76 | if (b < bytesPerSample - 1) { 77 | v &= 0xFF; 78 | } 79 | sample += v << (b * 8); 80 | } 81 | double sample32 = amplification * (sample / 32768.0); 82 | fft[floatIndex] = new Complex(sample32, 0); 83 | } 84 | fft = FFT.fft(fft); 85 | // calculate dBs and amplitudes 86 | int dataSize = fft.length / 2 - 1; 87 | if (dbs == null || dbs.length != dataSize) { 88 | dbs = new float[dataSize]; 89 | } 90 | if (allAmps == null || allAmps.length != dataSize) { 91 | allAmps = new float[dataSize]; 92 | } 93 | 94 | for (int i = 0; i < dataSize; i++) { 95 | dbs[i] = (float) fft[i].abs(); 96 | float k = 1; 97 | if (i == 0 || i == dataSize - 1) { 98 | k = 2; 99 | } 100 | float re = (float) fft[2 * i].re(); 101 | float im = (float) fft[2 * i + 1].im(); 102 | float sqMag = re * re + im * im; 103 | allAmps[i] = (float) (k * Math.sqrt(sqMag) / dataSize); 104 | } 105 | int size = dbs.length / layersCount; 106 | for (int i = 0; i < layersCount; i++) { 107 | int index = (int) ((i + 0.5f) * size); 108 | float db = dbs[index]; 109 | float amp = allAmps[index]; 110 | dBmArray[i] = db > MAX_DB_VALUE ? 1 : db / MAX_DB_VALUE; 111 | ampsArray[i] = amp; 112 | } 113 | } 114 | 115 | public void stop() { 116 | calmDownAndStopRendering(); 117 | } 118 | 119 | @Override 120 | public void onDataReady(byte[] data) { 121 | onDataReceived(data); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/com/cleveroad/example/AudioVisualizationFragment.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.cleveroad.audiovisualization.AudioVisualization; 11 | import com.cleveroad.audiovisualization.DbmHandler; 12 | import com.cleveroad.audiovisualization.GLAudioVisualizationView; 13 | 14 | /** 15 | * Fragment with visualization of audio output. 16 | */ 17 | public class AudioVisualizationFragment extends Fragment { 18 | 19 | public static AudioVisualizationFragment newInstance() { 20 | return new AudioVisualizationFragment(); 21 | } 22 | 23 | private AudioVisualization audioVisualization; 24 | 25 | @Nullable 26 | @Override 27 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 28 | return new GLAudioVisualizationView.Builder(getContext()) 29 | .setBubblesSize(R.dimen.bubble_size) 30 | .setBubblesRandomizeSize(true) 31 | .setWavesHeight(R.dimen.wave_height) 32 | .setWavesFooterHeight(R.dimen.footer_height) 33 | .setWavesCount(7) 34 | .setLayersCount(4) 35 | .setBackgroundColorRes(R.color.av_color_bg) 36 | .setLayerColors(R.array.av_colors) 37 | .setBubblesPerLayer(16) 38 | .build(); 39 | } 40 | 41 | @Override 42 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 43 | super.onViewCreated(view, savedInstanceState); 44 | audioVisualization = (AudioVisualization) view; 45 | audioVisualization.linkTo(DbmHandler.Factory.newVisualizerHandler(getContext(), 0)); 46 | } 47 | 48 | @Override 49 | public void onResume() { 50 | super.onResume(); 51 | audioVisualization.onResume(); 52 | } 53 | 54 | @Override 55 | public void onPause() { 56 | audioVisualization.onPause(); 57 | super.onPause(); 58 | } 59 | 60 | @Override 61 | public void onDestroyView() { 62 | audioVisualization.release(); 63 | super.onDestroyView(); 64 | } 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cleveroad/example/Complex.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | /****************************************************************************** 4 | * Compilation: javac Complex.java 5 | * Execution: java Complex 6 | * 7 | * Data type for complex numbers. 8 | * 9 | * The data type is "immutable" so once you create and initialize 10 | * a Complex object, you cannot change it. The "final" keyword 11 | * when declaring re and im enforces this rule, making it a 12 | * compile-time error to change the .re or .im fields after 13 | * they've been initialized. 14 | * 15 | * % java Complex 16 | * a = 5.0 + 6.0i 17 | * b = -3.0 + 4.0i 18 | * Re(a) = 5.0 19 | * Im(a) = 6.0 20 | * b + a = 2.0 + 10.0i 21 | * a - b = 8.0 + 2.0i 22 | * a * b = -39.0 + 2.0i 23 | * b * a = -39.0 + 2.0i 24 | * a / b = 0.36 - 1.52i 25 | * (a / b) * b = 5.0 + 6.0i 26 | * conj(a) = 5.0 - 6.0i 27 | * |a| = 7.810249675906654 28 | * tan(a) = -6.685231390246571E-6 + 1.0000103108981198i 29 | * 30 | ******************************************************************************/ 31 | 32 | class Complex { 33 | private final double re; // the real part 34 | private final double im; // the imaginary part 35 | 36 | // create a new object with the given real and imaginary parts 37 | public Complex(double real, double imag) { 38 | re = real; 39 | im = imag; 40 | } 41 | 42 | // return a string representation of the invoking Complex object 43 | public String toString() { 44 | if (im == 0) return re + ""; 45 | if (re == 0) return im + "i"; 46 | if (im < 0) return re + " - " + (-im) + "i"; 47 | return re + " + " + im + "i"; 48 | } 49 | 50 | // return abs/modulus/magnitude and angle/phase/argument 51 | public double abs() { return Math.hypot(re, im); } // Math.sqrt(re*re + im*im) 52 | public double phase() { return Math.atan2(im, re); } // between -pi and pi 53 | 54 | // return a new Complex object whose value is (this + b) 55 | public Complex plus(Complex b) { 56 | Complex a = this; // invoking object 57 | double real = a.re + b.re; 58 | double imag = a.im + b.im; 59 | return new Complex(real, imag); 60 | } 61 | 62 | // return a new Complex object whose value is (this - b) 63 | public Complex minus(Complex b) { 64 | Complex a = this; 65 | double real = a.re - b.re; 66 | double imag = a.im - b.im; 67 | return new Complex(real, imag); 68 | } 69 | 70 | // return a new Complex object whose value is (this * b) 71 | public Complex times(Complex b) { 72 | Complex a = this; 73 | double real = a.re * b.re - a.im * b.im; 74 | double imag = a.re * b.im + a.im * b.re; 75 | return new Complex(real, imag); 76 | } 77 | 78 | // scalar multiplication 79 | // return a new object whose value is (this * alpha) 80 | public Complex times(double alpha) { 81 | return new Complex(alpha * re, alpha * im); 82 | } 83 | 84 | // return a new Complex object whose value is the conjugate of this 85 | public Complex conjugate() { return new Complex(re, -im); } 86 | 87 | // return a new Complex object whose value is the reciprocal of this 88 | public Complex reciprocal() { 89 | double scale = re*re + im*im; 90 | return new Complex(re / scale, -im / scale); 91 | } 92 | 93 | // return the real or imaginary part 94 | public double re() { return re; } 95 | public double im() { return im; } 96 | 97 | // return a / b 98 | public Complex divides(Complex b) { 99 | Complex a = this; 100 | return a.times(b.reciprocal()); 101 | } 102 | 103 | // return a new Complex object whose value is the complex exponential of this 104 | public Complex exp() { 105 | return new Complex(Math.exp(re) * Math.cos(im), Math.exp(re) * Math.sin(im)); 106 | } 107 | 108 | // return a new Complex object whose value is the complex sine of this 109 | public Complex sin() { 110 | return new Complex(Math.sin(re) * Math.cosh(im), Math.cos(re) * Math.sinh(im)); 111 | } 112 | 113 | // return a new Complex object whose value is the complex cosine of this 114 | public Complex cos() { 115 | return new Complex(Math.cos(re) * Math.cosh(im), -Math.sin(re) * Math.sinh(im)); 116 | } 117 | 118 | // return a new Complex object whose value is the complex tangent of this 119 | public Complex tan() { 120 | return sin().divides(cos()); 121 | } 122 | 123 | 124 | 125 | // a static version of plus 126 | public static Complex plus(Complex a, Complex b) { 127 | double real = a.re + b.re; 128 | double imag = a.im + b.im; 129 | Complex sum = new Complex(real, imag); 130 | return sum; 131 | } 132 | 133 | 134 | 135 | // sample client for testing 136 | public static void main(String[] args) { 137 | Complex a = new Complex(5.0, 6.0); 138 | Complex b = new Complex(-3.0, 4.0); 139 | 140 | System.out.println("a = " + a); 141 | System.out.println("b = " + b); 142 | System.out.println("Re(a) = " + a.re()); 143 | System.out.println("Im(a) = " + a.im()); 144 | System.out.println("b + a = " + b.plus(a)); 145 | System.out.println("a - b = " + a.minus(b)); 146 | System.out.println("a * b = " + a.times(b)); 147 | System.out.println("b * a = " + b.times(a)); 148 | System.out.println("a / b = " + a.divides(b)); 149 | System.out.println("(a / b) * b = " + a.divides(b).times(b)); 150 | System.out.println("conj(a) = " + a.conjugate()); 151 | System.out.println("|a| = " + a.abs()); 152 | System.out.println("tan(a) = " + a.tan()); 153 | } 154 | 155 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cleveroad/example/FFT.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | /****************************************************************************** 4 | * Compilation: javac FFT.java 5 | * Execution: java FFT N 6 | * Dependencies: Complex.java 7 | * 8 | * Compute the FFT and inverse FFT of a length N complex sequence. 9 | * Bare bones implementation that runs in O(N log N) time. Our goal 10 | * is to optimize the clarity of the code, rather than performance. 11 | * 12 | * Limitations 13 | * ----------- 14 | * - assumes N is a power of 2 15 | * 16 | * - not the most memory efficient algorithm (because it uses 17 | * an object type for representing complex numbers and because 18 | * it re-allocates memory for the subarray, instead of doing 19 | * in-place or reusing a single temporary array) 20 | * 21 | ******************************************************************************/ 22 | 23 | class FFT { 24 | 25 | // compute the FFT of x[], assuming its length is a power of 2 26 | public static Complex[] fft(Complex[] x) { 27 | int N = x.length; 28 | 29 | // base case 30 | if (N == 1) return new Complex[] { x[0] }; 31 | 32 | // radix 2 Cooley-Tukey FFT 33 | if (N % 2 != 0) { throw new RuntimeException("N is not a power of 2"); } 34 | 35 | // fft of even terms 36 | Complex[] even = new Complex[N/2]; 37 | for (int k = 0; k < N/2; k++) { 38 | even[k] = x[2*k]; 39 | } 40 | Complex[] q = fft(even); 41 | 42 | // fft of odd terms 43 | Complex[] odd = even; // reuse the array 44 | for (int k = 0; k < N/2; k++) { 45 | odd[k] = x[2*k + 1]; 46 | } 47 | Complex[] r = fft(odd); 48 | 49 | // combine 50 | Complex[] y = new Complex[N]; 51 | for (int k = 0; k < N/2; k++) { 52 | double kth = -2 * k * Math.PI / N; 53 | Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); 54 | y[k] = q[k].plus(wk.times(r[k])); 55 | y[k + N/2] = q[k].minus(wk.times(r[k])); 56 | } 57 | return y; 58 | } 59 | 60 | 61 | // compute the inverse FFT of x[], assuming its length is a power of 2 62 | public static Complex[] ifft(Complex[] x) { 63 | int N = x.length; 64 | Complex[] y = new Complex[N]; 65 | 66 | // take conjugate 67 | for (int i = 0; i < N; i++) { 68 | y[i] = x[i].conjugate(); 69 | } 70 | 71 | // compute forward FFT 72 | y = fft(y); 73 | 74 | // take conjugate again 75 | for (int i = 0; i < N; i++) { 76 | y[i] = y[i].conjugate(); 77 | } 78 | 79 | // divide by N 80 | for (int i = 0; i < N; i++) { 81 | y[i] = y[i].times(1.0 / N); 82 | } 83 | 84 | return y; 85 | 86 | } 87 | 88 | // compute the circular convolution of x and y 89 | public static Complex[] cconvolve(Complex[] x, Complex[] y) { 90 | 91 | // should probably pad x and y with 0s so that they have same length 92 | // and are powers of 2 93 | if (x.length != y.length) { throw new RuntimeException("Dimensions don't agree"); } 94 | 95 | int N = x.length; 96 | 97 | // compute FFT of each sequence 98 | Complex[] a = fft(x); 99 | Complex[] b = fft(y); 100 | 101 | // point-wise multiply 102 | Complex[] c = new Complex[N]; 103 | for (int i = 0; i < N; i++) { 104 | c[i] = a[i].times(b[i]); 105 | } 106 | 107 | // compute inverse FFT 108 | return ifft(c); 109 | } 110 | 111 | 112 | // compute the linear convolution of x and y 113 | public static Complex[] convolve(Complex[] x, Complex[] y) { 114 | Complex ZERO = new Complex(0, 0); 115 | 116 | Complex[] a = new Complex[2*x.length]; 117 | for (int i = 0; i < x.length; i++) a[i] = x[i]; 118 | for (int i = x.length; i < 2*x.length; i++) a[i] = ZERO; 119 | 120 | Complex[] b = new Complex[2*y.length]; 121 | for (int i = 0; i < y.length; i++) b[i] = y[i]; 122 | for (int i = y.length; i < 2*y.length; i++) b[i] = ZERO; 123 | 124 | return cconvolve(a, b); 125 | } 126 | 127 | // display an array of Complex numbers to standard output 128 | public static void show(Complex[] x, String title) { 129 | System.out.println(title); 130 | System.out.println("-------------------"); 131 | for (int i = 0; i < x.length; i++) { 132 | System.out.println(x[i]); 133 | } 134 | System.out.println(); 135 | } 136 | 137 | 138 | /*************************************************************************** 139 | * Test client and sample execution 140 | * 141 | * % java FFT 4 142 | * x 143 | * ------------------- 144 | * -0.03480425839330703 145 | * 0.07910192950176387 146 | * 0.7233322451735928 147 | * 0.1659819820667019 148 | * 149 | * y = fft(x) 150 | * ------------------- 151 | * 0.9336118983487516 152 | * -0.7581365035668999 + 0.08688005256493803i 153 | * 0.44344407521182005 154 | * -0.7581365035668999 - 0.08688005256493803i 155 | * 156 | * z = ifft(y) 157 | * ------------------- 158 | * -0.03480425839330703 159 | * 0.07910192950176387 + 2.6599344570851287E-18i 160 | * 0.7233322451735928 161 | * 0.1659819820667019 - 2.6599344570851287E-18i 162 | * 163 | * c = cconvolve(x, x) 164 | * ------------------- 165 | * 0.5506798633981853 166 | * 0.23461407150576394 - 4.033186818023279E-18i 167 | * -0.016542951108772352 168 | * 0.10288019294318276 + 4.033186818023279E-18i 169 | * 170 | * d = convolve(x, x) 171 | * ------------------- 172 | * 0.001211336402308083 - 3.122502256758253E-17i 173 | * -0.005506167987577068 - 5.058885073636224E-17i 174 | * -0.044092969479563274 + 2.1934338938072244E-18i 175 | * 0.10288019294318276 - 3.6147323062478115E-17i 176 | * 0.5494685269958772 + 3.122502256758253E-17i 177 | * 0.240120239493341 + 4.655566391833896E-17i 178 | * 0.02755001837079092 - 2.1934338938072244E-18i 179 | * 4.01805098805014E-17i 180 | * 181 | ***************************************************************************/ 182 | 183 | public static void main(String[] args) { 184 | int N = Integer.parseInt(args[0]); 185 | Complex[] x = new Complex[N]; 186 | 187 | // original data 188 | for (int i = 0; i < N; i++) { 189 | x[i] = new Complex(i, 0); 190 | x[i] = new Complex(-2*Math.random() + 1, 0); 191 | } 192 | show(x, "x"); 193 | 194 | // FFT of original data 195 | Complex[] y = fft(x); 196 | show(y, "y = fft(x)"); 197 | 198 | // take inverse FFT 199 | Complex[] z = ifft(y); 200 | show(z, "z = ifft(y)"); 201 | 202 | // circular convolution of x with itself 203 | Complex[] c = cconvolve(x, x); 204 | show(c, "c = cconvolve(x, x)"); 205 | 206 | // linear convolution of x with itself 207 | Complex[] d = convolve(x, x); 208 | show(d, "d = convolve(x, x)"); 209 | } 210 | 211 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cleveroad/example/IAudioRecorder.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | /** 4 | * Interface for audio recorder 5 | */ 6 | interface IAudioRecorder { 7 | void startRecord(); 8 | void finishRecord(); 9 | boolean isRecording(); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/cleveroad/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | import android.Manifest; 4 | import android.content.DialogInterface; 5 | import android.content.pm.PackageManager; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.v4.app.ActivityCompat; 9 | import android.support.v7.app.AlertDialog; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.text.Html; 12 | import android.widget.Toast; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | private static final int REQUEST_CODE = 1; 17 | private boolean shouldOpenFragment; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | if (savedInstanceState == null) { 24 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED 25 | && ActivityCompat.checkSelfPermission(this, Manifest.permission.MODIFY_AUDIO_SETTINGS) == PackageManager.PERMISSION_GRANTED) { 26 | openFragment(); 27 | } else { 28 | if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO) 29 | || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.MODIFY_AUDIO_SETTINGS)) { 30 | AlertDialog.OnClickListener onClickListener = new DialogInterface.OnClickListener() { 31 | @Override 32 | public void onClick(DialogInterface dialog, int which) { 33 | if (which == DialogInterface.BUTTON_POSITIVE) { 34 | requestPermissions(); 35 | } else if (which == DialogInterface.BUTTON_NEGATIVE) { 36 | permissionsNotGranted(); 37 | } 38 | } 39 | }; 40 | new AlertDialog.Builder(this) 41 | .setTitle(getString(R.string.title_permissions)) 42 | .setMessage(Html.fromHtml(getString(R.string.message_permissions))) 43 | .setPositiveButton(getString(R.string.btn_next), onClickListener) 44 | .setNegativeButton(getString(R.string.btn_cancel), onClickListener) 45 | .show(); 46 | } else { 47 | requestPermissions(); 48 | } 49 | } 50 | } 51 | } 52 | 53 | private void requestPermissions() { 54 | ActivityCompat.requestPermissions( 55 | this, 56 | new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS}, 57 | REQUEST_CODE 58 | ); 59 | } 60 | 61 | private void permissionsNotGranted() { 62 | Toast.makeText(this, R.string.toast_permissions_not_granted, Toast.LENGTH_SHORT).show(); 63 | finish(); 64 | } 65 | 66 | @Override 67 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 68 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 69 | if (requestCode == REQUEST_CODE) { 70 | boolean bothGranted = true; 71 | for (int i = 0; i < permissions.length; i++) { 72 | if (Manifest.permission.RECORD_AUDIO.equals(permissions[i]) || Manifest.permission.MODIFY_AUDIO_SETTINGS.equals(permissions[i])) { 73 | bothGranted &= grantResults[i] == PackageManager.PERMISSION_GRANTED; 74 | } 75 | } 76 | if (bothGranted) { 77 | shouldOpenFragment = true; 78 | } else { 79 | permissionsNotGranted(); 80 | } 81 | } 82 | } 83 | 84 | @Override 85 | protected void onResume() { 86 | super.onResume(); 87 | if (shouldOpenFragment) { 88 | shouldOpenFragment = false; 89 | openFragment(); 90 | } 91 | } 92 | 93 | private void openFragment() { 94 | getSupportFragmentManager().beginTransaction() 95 | .add(R.id.container, MainFragment.newInstance()) 96 | .commit(); 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cleveroad/example/MainFragment.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | import android.content.DialogInterface; 4 | import android.os.Bundle; 5 | import android.support.v4.app.ListFragment; 6 | import android.support.v7.app.AlertDialog; 7 | import android.view.View; 8 | import android.widget.AdapterView; 9 | import android.widget.ArrayAdapter; 10 | 11 | /** 12 | * Main fragment. 13 | */ 14 | public class MainFragment extends ListFragment implements AdapterView.OnItemClickListener { 15 | 16 | public static MainFragment newInstance() { 17 | return new MainFragment(); 18 | } 19 | 20 | @Override 21 | public void onViewCreated(View view, Bundle savedInstanceState) { 22 | super.onViewCreated(view, savedInstanceState); 23 | ArrayAdapter adapter = ArrayAdapter.createFromResource(getContext(), R.array.selection_items, android.R.layout.simple_list_item_1); 24 | setListAdapter(adapter); 25 | getListView().setOnItemClickListener(this); 26 | } 27 | 28 | @Override 29 | public void onItemClick(AdapterView parent, View view, int position, long id) { 30 | if (position == 0) { 31 | new AlertDialog.Builder(getContext()) 32 | .setTitle("Audio Visualization") 33 | .setMessage("Open any music player and play your favorite songs. Audio Visualizer will detect sound and animate.") 34 | .setPositiveButton("OK", new DialogInterface.OnClickListener() { 35 | @Override 36 | public void onClick(DialogInterface dialog, int which) { 37 | getActivity().getSupportFragmentManager().beginTransaction() 38 | .replace(R.id.container, AudioVisualizationFragment.newInstance()) 39 | .addToBackStack(null) 40 | .commit(); 41 | } 42 | }) 43 | .show(); 44 | } else if (position == 1) { 45 | getActivity().getSupportFragmentManager().beginTransaction() 46 | .replace(R.id.container, SpeechRecognitionFragment.newInstance()) 47 | .addToBackStack(null) 48 | .commit(); 49 | } else if (position == 2) { 50 | getActivity().getSupportFragmentManager().beginTransaction() 51 | .replace(R.id.container, AudioRecordingFragment.newInstance()) 52 | .addToBackStack(null) 53 | .commit(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/cleveroad/example/PriorityRunnable.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | import android.os.Process; 4 | 5 | /** 6 | * Runnable that set thread priority before execution. See {@link Process} for list of available priority levels. 7 | */ 8 | abstract class PriorityRunnable implements Runnable { 9 | private int threadPriority; 10 | 11 | public PriorityRunnable() { 12 | threadPriority = Process.THREAD_PRIORITY_BACKGROUND; 13 | } 14 | 15 | public PriorityRunnable(@ProcessPriority int threadPriority) { 16 | this.threadPriority = threadPriority; 17 | } 18 | 19 | @Override 20 | public void run() { 21 | Process.setThreadPriority(threadPriority); 22 | runImpl(); 23 | } 24 | 25 | protected abstract void runImpl(); 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/cleveroad/example/ProcessPriority.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | 4 | import android.os.Process; 5 | import android.support.annotation.IntDef; 6 | 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | 11 | /** 12 | * Allowed values for thread priority 13 | */ 14 | @IntDef(flag = true, value = { 15 | Process.THREAD_PRIORITY_BACKGROUND, 16 | Process.THREAD_PRIORITY_AUDIO, 17 | Process.THREAD_PRIORITY_DEFAULT, 18 | Process.THREAD_PRIORITY_DISPLAY, 19 | Process.THREAD_PRIORITY_URGENT_AUDIO, 20 | Process.THREAD_PRIORITY_URGENT_DISPLAY, 21 | Process.THREAD_PRIORITY_FOREGROUND, 22 | Process.THREAD_PRIORITY_LOWEST 23 | }) 24 | @Retention(RetentionPolicy.SOURCE) 25 | @Documented 26 | @interface ProcessPriority { 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/cleveroad/example/SpeechRecognitionFragment.java: -------------------------------------------------------------------------------- 1 | package com.cleveroad.example; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.speech.RecognitionListener; 6 | import android.speech.RecognizerIntent; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.Fragment; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.Button; 13 | 14 | import com.cleveroad.audiovisualization.AudioVisualization; 15 | import com.cleveroad.audiovisualization.DbmHandler; 16 | import com.cleveroad.audiovisualization.SpeechRecognizerDbmHandler; 17 | 18 | /** 19 | * Fragment with visualization of speech recognition. 20 | */ 21 | public class SpeechRecognitionFragment extends Fragment { 22 | 23 | public static SpeechRecognitionFragment newInstance() { 24 | return new SpeechRecognitionFragment(); 25 | } 26 | 27 | private AudioVisualization audioVisualization; 28 | private Button btnRecognize; 29 | private SpeechRecognizerDbmHandler handler; 30 | private boolean recognizing; 31 | 32 | @Nullable 33 | @Override 34 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 35 | View view = inflater.inflate(R.layout.fragment_gles, container, false); 36 | audioVisualization = (AudioVisualization) view.findViewById(R.id.visualizer_view); 37 | btnRecognize = (Button) view.findViewById(R.id.btn_recognize); 38 | return view; 39 | } 40 | 41 | @Override 42 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 43 | super.onViewCreated(view, savedInstanceState); 44 | btnRecognize.setOnClickListener(new View.OnClickListener() { 45 | @Override 46 | public void onClick(View v) { 47 | if (recognizing) { 48 | handler.stopListening(); 49 | } else { 50 | Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); 51 | intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); 52 | intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, getContext().getPackageName()); 53 | handler.startListening(intent); 54 | } 55 | btnRecognize.setEnabled(false); 56 | } 57 | }); 58 | handler = DbmHandler.Factory.newSpeechRecognizerHandler(getContext()); 59 | handler.innerRecognitionListener(new SimpleRecognitionListener() { 60 | 61 | @Override 62 | public void onReadyForSpeech(Bundle params) { 63 | super.onReadyForSpeech(params); 64 | onStartRecognizing(); 65 | } 66 | 67 | @Override 68 | public void onResults(Bundle results) { 69 | super.onResults(results); 70 | onStopRecognizing(); 71 | } 72 | 73 | @Override 74 | public void onError(int error) { 75 | super.onError(error); 76 | onStopRecognizing(); 77 | 78 | } 79 | }); 80 | audioVisualization.linkTo(handler); 81 | } 82 | 83 | private void onStopRecognizing() { 84 | recognizing = false; 85 | btnRecognize.setText(R.string.start_recognition); 86 | btnRecognize.setEnabled(true); 87 | } 88 | 89 | private void onStartRecognizing() { 90 | btnRecognize.setText(R.string.stop_recognition); 91 | btnRecognize.setEnabled(true); 92 | recognizing = true; 93 | } 94 | 95 | @Override 96 | public void onDestroyView() { 97 | audioVisualization.release(); 98 | super.onDestroyView(); 99 | } 100 | 101 | private static class SimpleRecognitionListener implements RecognitionListener { 102 | 103 | @Override 104 | public void onReadyForSpeech(Bundle params) { 105 | } 106 | 107 | @Override 108 | public void onBeginningOfSpeech() { 109 | } 110 | 111 | @Override 112 | public void onRmsChanged(float rmsdB) { 113 | } 114 | 115 | @Override 116 | public void onBufferReceived(byte[] buffer) { 117 | } 118 | 119 | @Override 120 | public void onEndOfSpeech() { 121 | } 122 | 123 | @Override 124 | public void onError(int error) { 125 | } 126 | 127 | @Override 128 | public void onResults(Bundle results) { 129 | } 130 | 131 | @Override 132 | public void onPartialResults(Bundle partialResults) { 133 | } 134 | 135 | @Override 136 | public void onEvent(int eventType, Bundle params) { 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_audio_recording.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 20 | 21 |