├── .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 [](https://github.com/sindresorhus/awesome)
2 | 
3 | ## Changelog
4 | | Version | Changes |
5 | | --- | --- |
6 | | v.1.0.1 |
Fixed issues Updated version of the support libraries Updated version of the build tools |
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 [](https://github.com/sindresorhus/awesome)
2 | 
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 [](https://github.com/sindresorhus/awesome)
2 | 
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 | 
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 | [](https://www.cleveroad.com/blog/case-study-audio-visualization-view-for-android-by-cleveroad)
18 |
19 | [](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 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_gles.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
20 |
21 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Visualizer
5 | - Speech Recognizer
6 | - Audio Recording
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 25dp
4 | 60dp
5 | 170dp
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AudioVisualization
3 | Canvas
4 | OpenGL ES
5 | Permissions
6 | Next
7 | Cancel
8 | Permissions not granted.
9 | Microphone.]]>
10 | Start Recognition
11 | Stop Recognition
12 | Start Recording
13 | Stop Recording
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/cleveroad/example/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.example;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.2.1'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | google()
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | VERSION_NAME=1.0.1
2 | VERSION_CODE=11
3 | GROUP=com.cleveroad
4 |
5 | POM_DESCRIPTION=Implementation of music player concept. When music plays the waves are active, and once it is paused or stopped � waves calm down.
6 |
7 | POM_URL=https://github.com/Cleveroad/WaveInApp
8 | POM_SCM_URL=https://github.com/Cleveroad/WaveInApp
9 | POM_SCM_CONNECTION=scm:git@github.com:Cleveroad/WaveInApp.git
10 | POM_SCM_DEV_CONNECTION=scm:git@github.com:Cleveroad/WaveInApp.git
11 |
12 | POM_LICENCE_NAME=MIT License
13 | POM_LICENCE_URL=http://opensource.org/licenses/MIT
14 | POM_LICENCE_DIST=repo
15 |
16 | POM_DEVELOPER_ID=cleveroad
17 | POM_DEVELOPER_NAME=Cleveroad
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/images/article.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/images/article.jpg
--------------------------------------------------------------------------------
/images/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/images/demo.gif
--------------------------------------------------------------------------------
/images/demo_.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/images/demo_.gif
--------------------------------------------------------------------------------
/images/header.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/images/header.jpg
--------------------------------------------------------------------------------
/images/logo-footer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/images/logo-footer.png
--------------------------------------------------------------------------------
/library/.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
79 |
80 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 28
5 | buildToolsVersion "28.0.3"
6 |
7 | defaultConfig {
8 | minSdkVersion 14
9 | targetSdkVersion 28
10 | versionCode 1
11 | versionName "1.0.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 |
20 | resourcePrefix "av_"
21 | }
22 |
23 | dependencies {
24 | final SUPPORT_LIB_VERSION = '28.0.0'
25 |
26 | compile fileTree(dir: 'libs', include: ['*.jar'])
27 | //noinspection GradleDependency
28 | compile "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION"
29 | }
30 |
31 | apply from: './gradle-mvn-push.gradle'
--------------------------------------------------------------------------------
/library/gradle-mvn-push.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Chris Banes
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: 'maven'
18 | apply plugin: 'signing'
19 |
20 | def isReleaseBuild() {
21 | return VERSION_NAME.contains("SNAPSHOT") == false
22 | }
23 |
24 | def getReleaseRepositoryUrl() {
25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
26 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
27 | }
28 |
29 | def getSnapshotRepositoryUrl() {
30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
31 | : "https://oss.sonatype.org/content/repositories/snapshots/"
32 | }
33 |
34 | def getRepositoryUsername() {
35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
36 | }
37 |
38 | def getRepositoryPassword() {
39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
40 | }
41 |
42 | afterEvaluate { project ->
43 | uploadArchives {
44 | repositories {
45 | mavenDeployer {
46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
47 |
48 | pom.groupId = GROUP
49 | pom.artifactId = POM_ARTIFACT_ID
50 | pom.version = VERSION_NAME
51 |
52 | repository(url: getReleaseRepositoryUrl()) {
53 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
54 | }
55 | snapshotRepository(url: getSnapshotRepositoryUrl()) {
56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
57 | }
58 |
59 | pom.project {
60 | name POM_NAME
61 | packaging POM_PACKAGING
62 | description POM_DESCRIPTION
63 | url POM_URL
64 |
65 | scm {
66 | url POM_SCM_URL
67 | connection POM_SCM_CONNECTION
68 | developerConnection POM_SCM_DEV_CONNECTION
69 | }
70 |
71 | licenses {
72 | license {
73 | name POM_LICENCE_NAME
74 | url POM_LICENCE_URL
75 | distribution POM_LICENCE_DIST
76 | }
77 | }
78 |
79 | developers {
80 | developer {
81 | id POM_DEVELOPER_ID
82 | name POM_DEVELOPER_NAME
83 | }
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | signing {
91 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
92 | sign configurations.archives
93 | }
94 |
95 | task apklib(type: Zip){
96 | appendix = extension = 'apklib'
97 |
98 | from 'AndroidManifest.xml'
99 | into('res') {
100 | from 'res'
101 | }
102 | into('src') {
103 | from 'src'
104 | }
105 | }
106 |
107 |
108 | task androidJavadocs(type: Javadoc) {
109 | source = android.sourceSets.main.java.srcDirs
110 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
111 | options.links("http://docs.oracle.com/javase/7/docs/api/");
112 | options.linksOffline "http://d.android.com/reference","${android.sdkDirectory}/docs/reference"
113 | exclude '**/BuildConfig.java'
114 | exclude '**/R.java'
115 | failOnError = false
116 | }
117 |
118 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
119 | classifier = 'javadoc'
120 | from androidJavadocs.destinationDir
121 | }
122 |
123 | task androidSourcesJar(type: Jar) {
124 | classifier = 'sources'
125 | from android.sourceSets.main.java.sourceFiles
126 | }
127 |
128 | artifacts {
129 | archives androidSourcesJar
130 | archives androidJavadocsJar
131 | archives apklib
132 | }
133 | }
--------------------------------------------------------------------------------
/library/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=Audio Visualization View
2 | POM_ARTIFACT_ID=audiovisualization
3 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/library/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 |
--------------------------------------------------------------------------------
/library/src/androidTest/java/com/cleveroad/example/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
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 | }
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/AudioVisualization.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | /**
6 | * Audio visualization view interface
7 | */
8 | public interface AudioVisualization {
9 |
10 | /**
11 | * Link view to custom implementation of {@link DbmHandler}.
12 | * @param dbmHandler instance of DbmHandler
13 | */
14 | void linkTo(@NonNull DbmHandler dbmHandler);
15 |
16 | /**
17 | * Resume audio visualization.
18 | */
19 | void onResume();
20 |
21 | /**
22 | * Pause audio visualization.
23 | */
24 | void onPause();
25 |
26 | /**
27 | * Release resources of audio visualization.
28 | */
29 | void release();
30 | }
31 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/Constants.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | /**
4 | * Constants holder class.
5 | */
6 | final class Constants {
7 |
8 | /**
9 | * Minimum number of waves to display.
10 | */
11 | public static final int MIN_WAVES_COUNT = 1;
12 |
13 | /**
14 | * Maximum number of waves to display.
15 | */
16 | public static final int MAX_WAVES_COUNT = 16;
17 |
18 | /**
19 | * Default number of waves to display.
20 | */
21 | public static final int DEFAULT_WAVES_COUNT = 7;
22 |
23 | /**
24 | * Minimum number of layers to display.
25 | */
26 | public static final int MIN_LAYERS_COUNT = 1;
27 |
28 | /**
29 | * Maximum number of waves to display.
30 | */
31 | public static final int MAX_LAYERS_COUNT = 4;
32 |
33 | /**
34 | * Default number of waves to display.
35 | */
36 | public static final int DEFAULT_LAYERS_COUNT = MAX_LAYERS_COUNT;
37 |
38 | /**
39 | * Minimum wave height (in pixels).
40 | */
41 | public static final float MIN_WAVE_HEIGHT = 10;
42 |
43 | /**
44 | * Maximum wave height (in pixels).
45 | */
46 | public static final float MAX_WAVE_HEIGHT = 1920;
47 |
48 | /**
49 | * Default wave height (in pixels).
50 | */
51 | public static final float DEFAULT_WAVE_HEIGHT = MIN_WAVE_HEIGHT;
52 |
53 | /**
54 | * Minimum bubble size (in pixels).
55 | */
56 | public static final int MIN_BUBBLE_SIZE = 10;
57 |
58 | /**
59 | * Maximum bubble size (in pixels).
60 | */
61 | public static final int MAX_BUBBLE_SIZE = 200;
62 |
63 | /**
64 | * Default bubble size (in pixels).
65 | */
66 | public static final int DEFAULT_BUBBLE_SIZE = 20;
67 |
68 | /**
69 | * Minimum footer height (in pixels).
70 | */
71 | public static final float MIN_FOOTER_HEIGHT = 20;
72 |
73 | /**
74 | * Maximum footer height (in pixels).
75 | */
76 | public static final float MAX_FOOTER_HEIGHT = 1080;
77 |
78 | /**
79 | * Default footer height (in pixels).
80 | */
81 | public static final float DEFAULT_FOOTER_HEIGHT = 640;
82 |
83 | /**
84 | * Default number of bubbles per layer.
85 | */
86 | public static final int DEFAULT_BUBBLES_PER_LAYER = 8;
87 |
88 | /**
89 | * Minimum number of bubbles per layer.
90 | */
91 | public static final int DEFAULT_BUBBLES_PER_LAYER_MIN = 1;
92 |
93 | /**
94 | * Maximum number of bubbles per layer.
95 | */
96 | public static final int DEFAULT_BUBBLES_PER_LAYER_MAX = 36;
97 |
98 | private Constants() {}
99 | }
100 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/DbmHandler.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import android.content.Context;
4 | import android.media.MediaPlayer;
5 | import android.media.audiofx.Visualizer;
6 | import android.speech.SpeechRecognizer;
7 | import android.support.annotation.CallSuper;
8 | import android.support.annotation.NonNull;
9 |
10 | import java.util.Timer;
11 | import java.util.TimerTask;
12 |
13 | /**
14 | * Abstract class for converting your data to dBm values.
15 | * When you're have new portion of data, call {@link #onDataReceived(Object)} method.
16 | */
17 | public abstract class DbmHandler {
18 |
19 | private static final long UPDATE_INTERVAL = 16;
20 | private int layersCount;
21 | private InnerAudioVisualization audioVisualization;
22 | private float[] dBmArray;
23 | private float[] ampsArray;
24 | private float[] emptyArray;
25 | private boolean released;
26 | private Timer timer;
27 |
28 | void setUp(@NonNull InnerAudioVisualization audioVisualization, int layersCount) {
29 | this.audioVisualization = audioVisualization;
30 | this.layersCount = layersCount;
31 | this.dBmArray = new float[layersCount];
32 | this.ampsArray = new float[layersCount];
33 | this.emptyArray = new float[layersCount];
34 | }
35 |
36 | /**
37 | * Call this method when your data is available for conversion.
38 | * @param data any data
39 | */
40 | public final void onDataReceived(TData data) {
41 | if (released)
42 | return;
43 | onDataReceivedImpl(data, layersCount, dBmArray, ampsArray);
44 | audioVisualization.onDataReceived(dBmArray, ampsArray);
45 | startRendering();
46 | }
47 |
48 | /**
49 | * Start rendering thread.
50 | */
51 | protected final void startRendering() {
52 | cancelTimer();
53 | audioVisualization.startRendering();
54 | }
55 |
56 | /**
57 | * Stop rendering thread.
58 | */
59 | protected final void stopRendering() {
60 | cancelTimer();
61 | audioVisualization.stopRendering();
62 | }
63 |
64 | /**
65 | * Post empty values to renderer and stop rendering thread after waves calm down.
66 | */
67 | protected final void calmDownAndStopRendering() {
68 | if (timer == null) {
69 | timer = new Timer("Stop Rendering Timer");
70 | timer.scheduleAtFixedRate(new TimerTask() {
71 | @Override
72 | public void run() {
73 | if (audioVisualization != null) {
74 | audioVisualization.onDataReceived(emptyArray, emptyArray);
75 | }
76 | }
77 | }, UPDATE_INTERVAL, UPDATE_INTERVAL);
78 | }
79 | audioVisualization.calmDownListener(new InnerAudioVisualization.CalmDownListener() {
80 | @Override
81 | public void onCalmedDown() {
82 | stopRendering();
83 | }
84 | });
85 | }
86 |
87 | /**
88 | * Cancel timer posting empty values.
89 | */
90 | private void cancelTimer() {
91 | if (timer != null) {
92 | timer.cancel();
93 | timer.purge();
94 | timer = null;
95 | }
96 | }
97 |
98 | /**
99 | * Called after {@link AudioVisualization#onResume()} call.
100 | */
101 | public void onResume() {
102 |
103 | }
104 |
105 | /**
106 | * Called after {@link AudioVisualization#onPause()} call.
107 | */
108 | public void onPause() {
109 |
110 | }
111 |
112 | /**
113 | * Called after {@link AudioVisualization#release()} ()} call.
114 | */
115 | @CallSuper
116 | public void release() {
117 | released = true;
118 | dBmArray = null;
119 | ampsArray = null;
120 | audioVisualization = null;
121 | }
122 |
123 | /**
124 | * Implement your own data conversion.
125 | * @param data any data
126 | * @param layersCount layers count
127 | * @param dBmArray array of normalized (in range [0..1]) dBm values that should be populated by you.
128 | * Array size is equals to {@code layersCount} value.
129 | * @param ampsArray array of amplitude values that should be populated by you.
130 | * Array size is equals to {@code layersCount} value.
131 | * This values affect the appearance of bubbles. If new amplitude value is greater
132 | * than previous value and normalized dBm value is greater than 0.25, bubbles will appear on screen.
133 | * In case if amplitude is less than previous value, exponential smoothing (Holt - Winters)
134 | * used for smoothing amplitude values.
135 | */
136 | protected abstract void onDataReceivedImpl(TData data, int layersCount, float[] dBmArray, float[] ampsArray);
137 |
138 | public static class Factory {
139 |
140 | /**
141 | * Create new visualizer dBm handler.
142 | * @param context instance of context
143 | * @param audioSessionId audio session id
144 | * @return new visualizer dBm handler
145 | * @see Visualizer
146 | */
147 | public static VisualizerDbmHandler newVisualizerHandler(@NonNull Context context, int audioSessionId) {
148 | return new VisualizerDbmHandler(context, audioSessionId);
149 | }
150 |
151 | /**
152 | * Create new visualizer dBm handler and wire with media player. At this point handler will set itself as
153 | * {@link MediaPlayer.OnPreparedListener} and {@link MediaPlayer.OnCompletionListener} of media player.
154 | * @param context instance of context
155 | * @param mediaPlayer instance of media player
156 | * @return new visualizer dBm handler
157 | * @see Visualizer,
158 | * @see VisualizerDbmHandler#setInnerOnPreparedListener(MediaPlayer.OnPreparedListener)
159 | * @see VisualizerDbmHandler#setInnerOnCompletionListener(MediaPlayer.OnCompletionListener)
160 | */
161 | public static VisualizerDbmHandler newVisualizerHandler(@NonNull Context context, @NonNull MediaPlayer mediaPlayer) {
162 | return new VisualizerDbmHandler(context, mediaPlayer);
163 | }
164 |
165 | /**
166 | * Create new speech recognizer dBm handler. Default dBm values [min, max]: [-2.12, 10.0].
167 | * @param context instance of context
168 | * @return new speech recognizer dBm handler
169 | * @see SpeechRecognizer
170 | */
171 | public static SpeechRecognizerDbmHandler newSpeechRecognizerHandler(@NonNull Context context) {
172 | return new SpeechRecognizerDbmHandler(context);
173 | }
174 |
175 | /**
176 | * Create new speech recognizer dBm handler.
177 | * @param context instance of context
178 | * @param minRmsDbValue minimum dBm value
179 | * @param maxRmsDbValue maximum dBm value
180 | * @return new speech recognizer dBm handler
181 | * @see SpeechRecognizer
182 | */
183 | public static SpeechRecognizerDbmHandler newSpeechRecognizerDbmHandler(@NonNull Context context, float minRmsDbValue, float maxRmsDbValue) {
184 | return new SpeechRecognizerDbmHandler(context, minRmsDbValue, maxRmsDbValue);
185 | }
186 | }
187 |
188 | }
189 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/GLAudioVisualizationView.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Color;
6 | import android.opengl.GLSurfaceView;
7 | import android.support.annotation.ArrayRes;
8 | import android.support.annotation.ColorInt;
9 | import android.support.annotation.ColorRes;
10 | import android.support.annotation.DimenRes;
11 | import android.support.annotation.NonNull;
12 | import android.support.annotation.Nullable;
13 | import android.support.v4.content.ContextCompat;
14 | import android.util.AttributeSet;
15 |
16 | /**
17 | * Audio visualization view implementation for OpenGL.
18 | */
19 | public class GLAudioVisualizationView extends GLSurfaceView implements AudioVisualization, InnerAudioVisualization {
20 |
21 | private static final int EGL_VERSION = 2;
22 | private final GLRenderer renderer;
23 | private DbmHandler> dbmHandler;
24 | private final Configuration configuration;
25 | private CalmDownListener innerCalmDownListener;
26 |
27 | private GLAudioVisualizationView(@NonNull Builder builder) {
28 | super(builder.context);
29 | configuration = new Configuration(builder);
30 | renderer = new GLRenderer(getContext(), configuration);
31 | init();
32 | }
33 |
34 | public GLAudioVisualizationView(Context context, AttributeSet attrs) {
35 | super(context, attrs);
36 | configuration = new Configuration(context, attrs, isInEditMode());
37 | renderer = new GLRenderer(getContext(), configuration);
38 | init();
39 | }
40 |
41 | private void init() {
42 | setEGLContextClientVersion(EGL_VERSION);
43 | setRenderer(renderer);
44 | renderer.calmDownListener(new CalmDownListener() {
45 | @Override
46 | public void onCalmedDown() {
47 | stopRendering();
48 | if (innerCalmDownListener != null) {
49 | innerCalmDownListener.onCalmedDown();
50 | }
51 | }
52 | });
53 | }
54 |
55 | @Override
56 | public void onResume() {
57 | super.onResume();
58 | if (dbmHandler != null) {
59 | dbmHandler.onResume();
60 | }
61 | }
62 |
63 | @Override
64 | public void onPause() {
65 | if (dbmHandler != null) {
66 | dbmHandler.onPause();
67 | }
68 | super.onPause();
69 | }
70 |
71 | @Override
72 | public void linkTo(@NonNull DbmHandler dbmHandler) {
73 | if (this.dbmHandler != null) {
74 | this.dbmHandler.release();
75 | }
76 | this.dbmHandler = dbmHandler;
77 | this.dbmHandler.setUp(this, configuration.layersCount);
78 | }
79 |
80 | @Override
81 | public void release() {
82 | if (dbmHandler != null) {
83 | dbmHandler.release();
84 | dbmHandler = null;
85 | }
86 | }
87 |
88 | @Override
89 | public void startRendering() {
90 | if (getRenderMode() != RENDERMODE_CONTINUOUSLY) {
91 | setRenderMode(RENDERMODE_CONTINUOUSLY);
92 | }
93 | }
94 |
95 | @Override
96 | public void stopRendering() {
97 | if (getRenderMode() != RENDERMODE_WHEN_DIRTY) {
98 | setRenderMode(RENDERMODE_WHEN_DIRTY);
99 | }
100 | }
101 |
102 | @Override
103 | public void calmDownListener(@Nullable CalmDownListener calmDownListener) {
104 | innerCalmDownListener = calmDownListener;
105 | }
106 |
107 | @Override
108 | public void onDataReceived(float[] dBmArray, float[] ampsArray) {
109 | renderer.onDataReceived(dBmArray, ampsArray);
110 | }
111 |
112 | /**
113 | * Configuration holder class.
114 | */
115 | static class Configuration {
116 |
117 | int wavesCount;
118 | int layersCount;
119 | int bubblesPerLayer;
120 | float bubbleSize;
121 | float waveHeight;
122 | float footerHeight;
123 | boolean randomizeBubbleSize;
124 | float[] backgroundColor;
125 | float[][] layerColors;
126 |
127 | public Configuration(Context context, AttributeSet attrs, boolean isInEditMode) {
128 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.GLAudioVisualizationView);
129 | int[] colors;
130 | int bgColor;
131 | try {
132 | layersCount = array.getInt(R.styleable.GLAudioVisualizationView_av_layersCount, Constants.DEFAULT_LAYERS_COUNT);
133 | layersCount = Utils.between(layersCount, Constants.MIN_LAYERS_COUNT, Constants.MAX_LAYERS_COUNT);
134 | wavesCount = array.getInt(R.styleable.GLAudioVisualizationView_av_wavesCount, Constants.DEFAULT_WAVES_COUNT);
135 | wavesCount = Utils.between(wavesCount, Constants.MIN_WAVES_COUNT, Constants.MAX_WAVES_COUNT);
136 | waveHeight = array.getDimensionPixelSize(R.styleable.GLAudioVisualizationView_av_wavesHeight, (int) Constants.DEFAULT_WAVE_HEIGHT);
137 | waveHeight = Utils.between(waveHeight, Constants.MIN_WAVE_HEIGHT, Constants.MAX_WAVE_HEIGHT);
138 | bubbleSize = array.getDimensionPixelSize(R.styleable.GLAudioVisualizationView_av_bubblesSize, Constants.DEFAULT_BUBBLE_SIZE);
139 | bubbleSize = Utils.between(bubbleSize, Constants.MIN_BUBBLE_SIZE, Constants.MAX_BUBBLE_SIZE);
140 | randomizeBubbleSize = array.getBoolean(R.styleable.GLAudioVisualizationView_av_bubblesRandomizeSizes, false);
141 | footerHeight = array.getDimensionPixelSize(R.styleable.GLAudioVisualizationView_av_wavesFooterHeight, (int) Constants.DEFAULT_FOOTER_HEIGHT);
142 | footerHeight = Utils.between(footerHeight, Constants.MIN_FOOTER_HEIGHT, Constants.MAX_FOOTER_HEIGHT);
143 | bubblesPerLayer = array.getInt(R.styleable.GLAudioVisualizationView_av_bubblesPerLayer, Constants.DEFAULT_BUBBLES_PER_LAYER);
144 | bubblesPerLayer = Utils.between(bubblesPerLayer, Constants.DEFAULT_BUBBLES_PER_LAYER_MIN, Constants.DEFAULT_BUBBLES_PER_LAYER_MAX);
145 | bgColor = array.getColor(R.styleable.GLAudioVisualizationView_av_backgroundColor, Color.TRANSPARENT);
146 | if (bgColor == Color.TRANSPARENT) {
147 | bgColor = ContextCompat.getColor(context, R.color.av_color_bg);
148 | }
149 | int arrayId = array.getResourceId(R.styleable.GLAudioVisualizationView_av_wavesColors, R.array.av_colors);
150 | if (isInEditMode) {
151 | colors = new int[layersCount];
152 | } else {
153 | TypedArray colorsArray = array.getResources().obtainTypedArray(arrayId);
154 | colors = new int[colorsArray.length()];
155 | for (int i = 0; i < colorsArray.length(); i++) {
156 | colors[i] = colorsArray.getColor(i, Color.TRANSPARENT);
157 | }
158 | colorsArray.recycle();
159 | }
160 | } finally {
161 | array.recycle();
162 | }
163 | if (colors.length < layersCount) {
164 | throw new IllegalArgumentException("You specified more layers than colors.");
165 | }
166 |
167 | layerColors = new float[colors.length][];
168 | for (int i = 0; i < colors.length; i++) {
169 | layerColors[i] = Utils.convertColor(colors[i]);
170 | }
171 | backgroundColor = Utils.convertColor(bgColor);
172 | bubbleSize /= context.getResources().getDisplayMetrics().widthPixels;
173 | }
174 |
175 | private Configuration(@NonNull Builder builder) {
176 | this.waveHeight = builder.waveHeight;
177 | waveHeight = Utils.between(waveHeight, Constants.MIN_WAVE_HEIGHT, Constants.MAX_WAVE_HEIGHT);
178 | this.wavesCount = builder.wavesCount;
179 | wavesCount = Utils.between(wavesCount, Constants.MIN_WAVES_COUNT, Constants.MAX_WAVES_COUNT);
180 | this.layerColors = builder.layerColors();
181 | this.bubbleSize = builder.bubbleSize;
182 | bubbleSize = Utils.between(bubbleSize, Constants.MIN_BUBBLE_SIZE, Constants.MAX_BUBBLE_SIZE);
183 | this.bubbleSize = this.bubbleSize / builder.context.getResources().getDisplayMetrics().widthPixels;
184 | this.footerHeight = builder.footerHeight;
185 | footerHeight = Utils.between(footerHeight, Constants.MIN_FOOTER_HEIGHT, Constants.MAX_FOOTER_HEIGHT);
186 | this.randomizeBubbleSize = builder.randomizeBubbleSize;
187 | this.backgroundColor = builder.backgroundColor();
188 | this.layersCount = builder.layersCount;
189 | this.bubblesPerLayer = builder.bubblesPerLayer;
190 | Utils.between(bubblesPerLayer, Constants.DEFAULT_BUBBLES_PER_LAYER_MIN, Constants.DEFAULT_BUBBLES_PER_LAYER_MAX);
191 | layersCount = Utils.between(layersCount, Constants.MIN_LAYERS_COUNT, Constants.MAX_LAYERS_COUNT);
192 | if (layerColors.length < layersCount) {
193 | throw new IllegalArgumentException("You specified more layers than colors.");
194 | }
195 | }
196 | }
197 |
198 | public static class ColorsBuilder {
199 | private float[] backgroundColor;
200 | private float[][] layerColors;
201 | private final Context context;
202 |
203 | public ColorsBuilder(@NonNull Context context) {
204 | this.context = context;
205 | }
206 |
207 | float[][] layerColors() {
208 | return layerColors;
209 | }
210 |
211 | float[] backgroundColor() {
212 | return backgroundColor;
213 | }
214 |
215 | /**
216 | * Set background color
217 | *
218 | * @param backgroundColor background color
219 | */
220 | public T setBackgroundColor(@ColorInt int backgroundColor) {
221 | this.backgroundColor = Utils.convertColor(backgroundColor);
222 | return getThis();
223 | }
224 |
225 | /**
226 | * Set layer colors from array resource
227 | *
228 | * @param arrayId array resource
229 | */
230 | public T setLayerColors(@ArrayRes int arrayId) {
231 | TypedArray colorsArray = context.getResources().obtainTypedArray(arrayId);
232 | int[] colors = new int[colorsArray.length()];
233 | for (int i = 0; i < colorsArray.length(); i++) {
234 | colors[i] = colorsArray.getColor(i, Color.TRANSPARENT);
235 | }
236 | colorsArray.recycle();
237 | return setLayerColors(colors);
238 | }
239 |
240 | /**
241 | * Set layer colors.
242 | *
243 | * @param colors array of colors
244 | */
245 | public T setLayerColors(int[] colors) {
246 | layerColors = new float[colors.length][];
247 | for (int i = 0; i < colors.length; i++) {
248 | layerColors[i] = Utils.convertColor(colors[i]);
249 | }
250 | return getThis();
251 | }
252 |
253 | /**
254 | * Set background color from color resource
255 | *
256 | * @param backgroundColor color resource
257 | */
258 | public T setBackgroundColorRes(@ColorRes int backgroundColor) {
259 | return setBackgroundColor(ContextCompat.getColor(context, backgroundColor));
260 | }
261 |
262 | protected T getThis() {
263 | //noinspection unchecked
264 | return (T) this;
265 | }
266 | }
267 |
268 | public static class Builder extends ColorsBuilder {
269 |
270 | private Context context;
271 | private int wavesCount;
272 | private int layersCount;
273 | private float bubbleSize;
274 | private float waveHeight;
275 | private float footerHeight;
276 | private boolean randomizeBubbleSize;
277 | private int bubblesPerLayer;
278 |
279 | public Builder(@NonNull Context context) {
280 | super(context);
281 | this.context = context;
282 | }
283 |
284 | @Override
285 | protected Builder getThis() {
286 | return this;
287 | }
288 |
289 | /**
290 | * Set waves count
291 | *
292 | * @param wavesCount waves count
293 | */
294 | public Builder setWavesCount(int wavesCount) {
295 | this.wavesCount = wavesCount;
296 | return this;
297 | }
298 |
299 | /**
300 | * Set layers count
301 | *
302 | * @param layersCount layers count
303 | */
304 | public Builder setLayersCount(int layersCount) {
305 | this.layersCount = layersCount;
306 | return this;
307 | }
308 |
309 | /**
310 | * Set bubbles size in pixels
311 | *
312 | * @param bubbleSize bubbles size in pixels
313 | */
314 | public Builder setBubblesSize(float bubbleSize) {
315 | this.bubbleSize = bubbleSize;
316 | return this;
317 | }
318 |
319 | /**
320 | * Set bubble size from dimension resource
321 | *
322 | * @param bubbleSize dimension resource
323 | */
324 | public Builder setBubblesSize(@DimenRes int bubbleSize) {
325 | return setBubblesSize((float) context.getResources().getDimensionPixelSize(bubbleSize));
326 | }
327 |
328 | /**
329 | * Set wave height in pixels
330 | *
331 | * @param waveHeight wave height in pixels
332 | */
333 | public Builder setWavesHeight(float waveHeight) {
334 | this.waveHeight = waveHeight;
335 | return this;
336 | }
337 |
338 | /**
339 | * Set wave height from dimension resource
340 | *
341 | * @param waveHeight dimension resource
342 | */
343 | public Builder setWavesHeight(@DimenRes int waveHeight) {
344 | return setWavesHeight((float) context.getResources().getDimensionPixelSize(waveHeight));
345 | }
346 |
347 | /**
348 | * Set footer height in pixels
349 | *
350 | * @param footerHeight footer height in pixels
351 | */
352 | public Builder setWavesFooterHeight(float footerHeight) {
353 | this.footerHeight = footerHeight;
354 | return this;
355 | }
356 |
357 | /**
358 | * Set footer height from dimension resource
359 | *
360 | * @param footerHeight dimension resource
361 | */
362 | public Builder setWavesFooterHeight(@DimenRes int footerHeight) {
363 | return setWavesFooterHeight((float) context.getResources().getDimensionPixelSize(footerHeight));
364 | }
365 |
366 | /**
367 | * Set flag indicates that size of bubbles should be randomized
368 | *
369 | * @param randomizeBubbleSize true if size of bubbles should be randomized, false if size of bubbles must be the same
370 | */
371 | public Builder setBubblesRandomizeSize(boolean randomizeBubbleSize) {
372 | this.randomizeBubbleSize = randomizeBubbleSize;
373 | return this;
374 | }
375 |
376 | /**
377 | * Set number of bubbles per layer.
378 | *
379 | * @param bubblesPerLayer number of bubbles per layer
380 | */
381 | public Builder setBubblesPerLayer(int bubblesPerLayer) {
382 | this.bubblesPerLayer = bubblesPerLayer;
383 | return this;
384 | }
385 |
386 | public GLAudioVisualizationView build() {
387 | return new GLAudioVisualizationView(this);
388 | }
389 | }
390 |
391 | /**
392 | * Renderer builder.
393 | */
394 | public static class RendererBuilder {
395 |
396 | private final Builder builder;
397 | private GLSurfaceView glSurfaceView;
398 | private DbmHandler handler;
399 |
400 | /**
401 | * Create new renderer using existing Audio Visualization builder.
402 | *
403 | * @param builder instance of Audio Visualization builder
404 | */
405 | public RendererBuilder(@NonNull Builder builder) {
406 | this.builder = builder;
407 | }
408 |
409 | /**
410 | * Set dBm handler.
411 | *
412 | * @param handler instance of dBm handler
413 | */
414 | public RendererBuilder handler(DbmHandler handler) {
415 | this.handler = handler;
416 | return this;
417 | }
418 |
419 | /**
420 | * Set OpenGL surface view.
421 | *
422 | * @param glSurfaceView instance of OpenGL surface view
423 | */
424 | public RendererBuilder glSurfaceView(@NonNull GLSurfaceView glSurfaceView) {
425 | this.glSurfaceView = glSurfaceView;
426 | return this;
427 | }
428 |
429 | /**
430 | * Create new Audio Visualization Renderer.
431 | *
432 | * @return new Audio Visualization Renderer
433 | */
434 | public AudioVisualizationRenderer build() {
435 | final GLRenderer renderer = new GLRenderer(builder.context, new Configuration(builder));
436 | final InnerAudioVisualization audioVisualization = new InnerAudioVisualization() {
437 | @Override
438 | public void startRendering() {
439 | if (glSurfaceView.getRenderMode() != RENDERMODE_CONTINUOUSLY) {
440 | glSurfaceView.setRenderMode(RENDERMODE_CONTINUOUSLY);
441 | }
442 | }
443 |
444 | @Override
445 | public void stopRendering() {
446 | if (glSurfaceView.getRenderMode() != RENDERMODE_WHEN_DIRTY) {
447 | glSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY);
448 | }
449 | }
450 |
451 | @Override
452 | public void calmDownListener(@Nullable CalmDownListener calmDownListener) {
453 |
454 | }
455 |
456 | @Override
457 | public void onDataReceived(float[] dBmArray, float[] ampsArray) {
458 | renderer.onDataReceived(dBmArray, ampsArray);
459 | }
460 | };
461 | renderer.calmDownListener(new CalmDownListener() {
462 | @Override
463 | public void onCalmedDown() {
464 | audioVisualization.stopRendering();
465 | }
466 | });
467 | handler.setUp(audioVisualization, builder.layersCount);
468 | return renderer;
469 | }
470 | }
471 |
472 | /**
473 | * Audio Visualization renderer interface that allows to change waves' colors at runtime.
474 | */
475 | public interface AudioVisualizationRenderer extends Renderer {
476 |
477 | /**
478 | * Update colors configuration.
479 | *
480 | * @param builder instance of color builder.
481 | */
482 | void updateConfiguration(@NonNull ColorsBuilder builder);
483 | }
484 | }
485 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/GLBubble.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import android.opengl.GLES20;
4 |
5 | import java.nio.ByteBuffer;
6 | import java.nio.ByteOrder;
7 | import java.nio.FloatBuffer;
8 | import java.nio.ShortBuffer;
9 | import java.util.Random;
10 |
11 | /**
12 | * Bubble implementation.
13 | */
14 | class GLBubble extends GLShape {
15 |
16 | /**
17 | * Duration of bubble movement.
18 | */
19 | private static final long BUBBLE_ANIMATION_DURATION = 1000;
20 | private static final float BUBBLE_D_ANGLE = (float) (2 * Math.PI / BUBBLE_ANIMATION_DURATION);
21 |
22 | /**
23 | * Number of points for drawing circle.
24 | */
25 | private static final int POINTS_PER_CIRCLE = 40;
26 | private static final float TOP_Y = 1f;
27 | private final FloatBuffer vertexBuffer;
28 | private final ShortBuffer shortBuffer;
29 | private final Random random;
30 | private float fromY;
31 | private float size;
32 | private float speed;
33 | private float virtualSpeed;
34 | private float centerY = -1;
35 | private float startX;
36 | private float angle;
37 |
38 | public GLBubble(float[] color, float startX, float fromY, float toY, float size, Random random) {
39 | super(color);
40 | this.random = random;
41 | update(startX, fromY, toY, size);
42 | float[] vertices = new float[(POINTS_PER_CIRCLE + 1) * COORDS_PER_VERTEX];
43 | short[] indices = new short[POINTS_PER_CIRCLE * COORDS_PER_VERTEX];
44 | int i;
45 | for (i = 0; i < indices.length / COORDS_PER_VERTEX - 1; i++) {
46 | indices[COORDS_PER_VERTEX * i] = 0;
47 | indices[COORDS_PER_VERTEX * i + 1] = (short) (i + 1);
48 | indices[COORDS_PER_VERTEX * i + 2] = (short) (i + 2);
49 | }
50 | // connect first and last elements
51 | indices[COORDS_PER_VERTEX * i] = 0;
52 | indices[COORDS_PER_VERTEX * i + 1] = (short) (i + 1);
53 | indices[COORDS_PER_VERTEX * i + 2] = (short) 1;
54 | ByteBuffer verticesByteBuffer = ByteBuffer.allocateDirect(vertices.length * SIZE_OF_FLOAT);
55 | verticesByteBuffer.order(ByteOrder.nativeOrder());
56 | vertexBuffer = verticesByteBuffer.asFloatBuffer();
57 | vertexBuffer.put(vertices);
58 | vertexBuffer.position(0);
59 | ByteBuffer indicesByteBuffer = ByteBuffer.allocateDirect(indices.length * SIZE_OF_SHORT);
60 | indicesByteBuffer.order(ByteOrder.nativeOrder());
61 | shortBuffer = indicesByteBuffer.asShortBuffer();
62 | shortBuffer.put(indices);
63 | shortBuffer.position(0);
64 | angle = (float) (random.nextFloat() * 2 * Math.PI);
65 | }
66 |
67 | /**
68 | * Update position of bubble.
69 | * @param dt time elapsed from last calculations
70 | * @param ratioY aspect ratio for Y coordinates
71 | */
72 | public void update(long dt, float ratioY) {
73 | double step = 2 * Math.PI / POINTS_PER_CIRCLE;
74 | angle += dt * BUBBLE_D_ANGLE;
75 | float fromX = startX + (float) (0.05f * Math.sin(angle));
76 | float toX = fromX + size;
77 | float fromY = this.fromY + dt * speed;
78 | float toY = fromY + size;
79 | centerY += dt * virtualSpeed;
80 | getColor()[3] = (TOP_Y - centerY / TOP_Y);
81 | vertexBuffer.put(0, Utils.normalizeGl(0, fromX, toX));
82 | vertexBuffer.put(1, Utils.normalizeGl(centerY * ratioY, fromY, toY));
83 | for (int i=1; i<=POINTS_PER_CIRCLE; i++) {
84 | vertexBuffer.put(COORDS_PER_VERTEX * i, Utils.normalizeGl((float) Math.sin(-Math.PI + step * i), fromX, toX));
85 | vertexBuffer.put(COORDS_PER_VERTEX * i + 1, Utils.normalizeGl((float) Math.cos(-Math.PI + step * i) * ratioY, fromY, toY));
86 | }
87 | this.fromY = fromY;
88 | }
89 |
90 | /**
91 | * Draw bubble.
92 | */
93 | public void draw() {
94 | GLES20.glUseProgram(getProgram());
95 | int positionHandle = GLES20.glGetAttribLocation(getProgram(), VERTEX_POSITION);
96 | GLES20.glEnableVertexAttribArray(positionHandle);
97 | GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * SIZE_OF_FLOAT, vertexBuffer);
98 | int colorHandle = GLES20.glGetUniformLocation(getProgram(), VERTEX_COLOR);
99 | GLES20.glEnable(GLES20.GL_BLEND);
100 | GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
101 | GLES20.glUniform4fv(colorHandle, 1, getColor(), 0);
102 | GLES20.glDrawElements(GLES20.GL_TRIANGLE_FAN, shortBuffer.capacity(), GLES20.GL_UNSIGNED_SHORT, shortBuffer);
103 | GLES20.glDisableVertexAttribArray(positionHandle);
104 | GLES20.glDisable(GLES20.GL_BLEND);
105 | }
106 |
107 | /**
108 | * Check if bubble is moved out of specified area.
109 | * @return true if bubble is outside of specified area, false otherwise
110 | */
111 | public boolean isOffScreen() {
112 | return centerY > TOP_Y;
113 | }
114 |
115 | /**
116 | * Update bubble's area of movement.
117 | * @param startX start X position
118 | * @param fromY start Y position
119 | * @param toY end Y position
120 | * @param size size of bubble
121 | */
122 | public void update(float startX, float fromY, float toY, float size) {
123 | this.fromY = fromY;
124 | this.size = size;
125 | this.startX = startX;
126 | this.centerY = -1;
127 | float coef = 0.4f + random.nextFloat() * 0.8f; // randomize speed of movement
128 | this.speed = (toY - fromY) / BUBBLE_ANIMATION_DURATION * coef;
129 | this.virtualSpeed = 2f / BUBBLE_ANIMATION_DURATION * coef;
130 | getColor()[3] = 1f;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/GLRectangle.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import android.opengl.GLES20;
4 |
5 | import java.nio.ByteBuffer;
6 | import java.nio.ByteOrder;
7 | import java.nio.FloatBuffer;
8 | import java.nio.ShortBuffer;
9 |
10 | /**
11 | * Rectangle implementation.
12 | */
13 | class GLRectangle extends GLShape {
14 |
15 | private final FloatBuffer vertexBuffer;
16 | private final ShortBuffer shortBuffer;
17 |
18 | public GLRectangle(float[] color, float fromX, float toX, float fromY, float toY) {
19 | super(color);
20 | final float[] vertices = {
21 | Utils.normalizeGl(-1, fromX, toX), Utils.normalizeGl(1, fromY, toY), 0,
22 | Utils.normalizeGl(-1, fromX, toX), Utils.normalizeGl(-1, fromY, toY), 0,
23 | Utils.normalizeGl(1, fromX, toX), Utils.normalizeGl(-1, fromY, toY), 0,
24 | Utils.normalizeGl(1, fromX, toX), Utils.normalizeGl(1, fromY, toY), 0
25 | };
26 | ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(vertices.length * SIZE_OF_FLOAT);
27 | vertexByteBuffer.order(ByteOrder.nativeOrder());
28 | vertexBuffer = vertexByteBuffer.asFloatBuffer();
29 | vertexBuffer.put(vertices);
30 | vertexBuffer.position(0);
31 | final short[] indices = {0,1,2,0,2,3};
32 | ByteBuffer indicesByteBuffer = ByteBuffer.allocateDirect(indices.length * SIZE_OF_SHORT);
33 | indicesByteBuffer.order(ByteOrder.nativeOrder());
34 | shortBuffer = indicesByteBuffer.asShortBuffer();
35 | shortBuffer.put(indices);
36 | shortBuffer.position(0);
37 | }
38 |
39 | /**
40 | * Draw rectangle.
41 | */
42 | public void draw() {
43 | GLES20.glUseProgram(getProgram());
44 | int positionHandle = GLES20.glGetAttribLocation(getProgram(), VERTEX_POSITION);
45 | GLES20.glEnableVertexAttribArray(positionHandle);
46 | GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * SIZE_OF_FLOAT, vertexBuffer);
47 | int colorHandle = GLES20.glGetUniformLocation(getProgram(), VERTEX_COLOR);
48 | GLES20.glUniform4fv(colorHandle, 1, getColor(), 0);
49 | GLES20.glDrawElements(GLES20.GL_TRIANGLE_FAN, shortBuffer.capacity(), GLES20.GL_UNSIGNED_SHORT, shortBuffer);
50 | GLES20.glDisableVertexAttribArray(positionHandle);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/GLRenderer.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import android.content.Context;
4 | import android.opengl.GLES20;
5 | import android.support.annotation.NonNull;
6 |
7 | import java.util.Random;
8 |
9 | import javax.microedition.khronos.egl.EGLConfig;
10 | import javax.microedition.khronos.opengles.GL10;
11 |
12 | /**
13 | * OpenGL renderer implementation.
14 | */
15 | class GLRenderer implements GLAudioVisualizationView.AudioVisualizationRenderer {
16 |
17 | private static final long ANIMATION_TIME = 400;
18 | private static final float D_ANGLE = (float) (2 * Math.PI / ANIMATION_TIME);
19 |
20 | private final GLAudioVisualizationView.Configuration configuration;
21 | private GLWaveLayer[] layers;
22 | private long startTime;
23 | private final float height;
24 | private final Random random;
25 | private float ratioY = 1;
26 | private InnerAudioVisualization.CalmDownListener calmDownListener;
27 | boolean bgUpdated;
28 |
29 | public GLRenderer(@NonNull Context context, GLAudioVisualizationView.Configuration configuration) {
30 | this.configuration = configuration;
31 | this.random = new Random();
32 | startTime = System.currentTimeMillis();
33 | height = context.getResources().getDisplayMetrics().heightPixels;
34 | }
35 |
36 | public GLRenderer calmDownListener(InnerAudioVisualization.CalmDownListener calmDownListener) {
37 | this.calmDownListener = calmDownListener;
38 | return this;
39 | }
40 |
41 | @Override
42 | public void onSurfaceCreated(GL10 gl, EGLConfig config) {
43 | float[] backgroundColor = configuration.backgroundColor;
44 | GLES20.glClearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], backgroundColor[3]);
45 | layers = new GLWaveLayer[configuration.layersCount];
46 | float layerHeightPerc = (configuration.footerHeight + configuration.waveHeight) / height;
47 | float waveHeightPerc = configuration.waveHeight / height * 2;
48 | for (int i = 0; i < layers.length; i++) {
49 | int reverseI = layers.length - i - 1;
50 | float fromY = -1 + reverseI * waveHeightPerc * 2;
51 | float toY = fromY + layerHeightPerc * 2;
52 | layers[i] = new GLWaveLayer(configuration, configuration.layerColors[i], fromY, toY, random);
53 | }
54 | }
55 |
56 | @Override
57 | public void onSurfaceChanged(GL10 gl, int width, int height) {
58 | GLES20.glViewport(0, 0, width, height);
59 | ratioY = (float) width / height;
60 | }
61 |
62 | @Override
63 | public void onDrawFrame(GL10 gl) {
64 | if (bgUpdated) {
65 | float[] backgroundColor = configuration.backgroundColor;
66 | GLES20.glClearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], backgroundColor[3]);
67 | bgUpdated = false;
68 | } else {
69 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
70 | }
71 | long endTime = System.currentTimeMillis();
72 | long dt = endTime - startTime;
73 | startTime = endTime;
74 | int i = 0;
75 | boolean isCalmedDown = true;
76 | for (GLWaveLayer layer : layers) {
77 | // slow down speed of wave from top to bottom of screen
78 | float speedCoef = (1 - 1f * i / (layers.length) * 0.8f);
79 | layer.update(dt, D_ANGLE * speedCoef, ratioY);
80 | isCalmedDown &= layer.isCalmedDown();
81 | i++;
82 | }
83 | for (GLWaveLayer layer : layers) {
84 | layer.draw();
85 | }
86 | if (isCalmedDown && calmDownListener != null) {
87 | calmDownListener.onCalmedDown();
88 | }
89 | }
90 |
91 | public final void onDataReceived(float[] dBmArray, float[] ampsArray) {
92 | if (layers == null)
93 | return;
94 | for (int i = 0; i < layers.length; i++) {
95 | if (layers[i] == null)
96 | return;
97 | layers[i].updateData(dBmArray[i], ampsArray[i]);
98 | }
99 | }
100 |
101 | /**
102 | * Utility method for compiling a OpenGL shader.
103 | *
104 | * @param type - Vertex or fragment shader type.
105 | * @param shaderCode - String containing the shader code.
106 | * @return - Returns an id for the shader.
107 | */
108 | public static int loadShader(int type, String shaderCode) {
109 | int shader = GLES20.glCreateShader(type);
110 | GLES20.glShaderSource(shader, shaderCode);
111 | GLES20.glCompileShader(shader);
112 | return shader;
113 | }
114 |
115 | @Override
116 | public void updateConfiguration(@NonNull GLAudioVisualizationView.ColorsBuilder builder) {
117 | float[] bgColor = configuration.backgroundColor;
118 | float[] backgroundColor = builder.backgroundColor();
119 | bgUpdated = false;
120 | for (int i = 0; i < 4; i++) {
121 | bgUpdated |= Float.compare(bgColor[i], backgroundColor[i]) != 0;
122 | }
123 | if (bgUpdated) {
124 | configuration.backgroundColor = builder.backgroundColor();
125 | }
126 | if (layers == null)
127 | return;
128 | float[][] colors = builder.layerColors();
129 | for (int i = 0; i < layers.length; i++) {
130 | layers[i].setColor(colors[i]);
131 | }
132 | }
133 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/GLShape.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import android.opengl.GLES20;
4 |
5 | /**
6 | * Abstract shape implementation.
7 | */
8 | abstract class GLShape {
9 |
10 | protected static final String VERTEX_POSITION = "vPosition";
11 | protected static final String VERTEX_COLOR = "vColor";
12 | private static final String VERTEX_SHADER_CODE =
13 | "attribute vec4 " + VERTEX_POSITION + ";" +
14 | "void main() {" +
15 | " gl_Position = " + VERTEX_POSITION + ";" +
16 | "}";
17 | private static final String FRAGMENT_SHADER_CODE =
18 | "precision mediump float;" +
19 | "uniform vec4 " + VERTEX_COLOR + ";" +
20 | "void main() {" +
21 | " gl_FragColor = " + VERTEX_COLOR + ";" +
22 | "}";
23 | protected static final int COORDS_PER_VERTEX = 3;
24 | protected static final int SIZE_OF_FLOAT = 4;
25 | protected static final int SIZE_OF_SHORT = 2;
26 |
27 | /**
28 | * Shape color.
29 | */
30 | private final float color[];
31 |
32 | /**
33 | * Program associated with shape.
34 | */
35 | private final int program;
36 |
37 | public GLShape(float[] color) {
38 | this.color = color;
39 | int vertexShader = GLRenderer.loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE);
40 | int fragmentShader = GLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE);
41 | program = GLES20.glCreateProgram();
42 | GLES20.glAttachShader(program, vertexShader);
43 | GLES20.glAttachShader(program, fragmentShader);
44 | GLES20.glLinkProgram(program);
45 | }
46 |
47 | protected float[] getColor() {
48 | return color;
49 | }
50 |
51 | protected int getProgram() {
52 | return program;
53 | }
54 |
55 | public void setColor(float[] color) {
56 | System.arraycopy(color, 0, this.color, 0, this.color.length);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/GLWave.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import android.opengl.GLES20;
4 |
5 | import java.nio.ByteBuffer;
6 | import java.nio.ByteOrder;
7 | import java.nio.FloatBuffer;
8 | import java.nio.ShortBuffer;
9 | import java.util.Random;
10 |
11 | /**
12 | * Single wave implementation.
13 | */
14 | class GLWave extends GLShape {
15 |
16 | /**
17 | * Wave movement from bottom to top.
18 | */
19 | public static final byte DIRECTION_UP = 0;
20 | /**
21 | * Wave movement from top to bottom.
22 | */
23 | public static final byte DIRECTION_DOWN = 1;
24 | /**
25 | * Smooth coefficient for {@link Utils#smooth(float, float, float)} method.
26 | */
27 | private static final float SMOOTH_A = 0.35f;
28 |
29 | /**
30 | * Number of points used for drawing Bezier curve.
31 | */
32 | private static final int POINTS_PER_WAVE = 40;
33 |
34 | /**
35 | * Number of additional points used for drawing wave: center, lb, lt, rt, rb.
36 | */
37 | private static final int ADDITIONAL_POINTS = 5;
38 |
39 | /**
40 | * Number of points to skip for getting proper index for Bezier curve points.
41 | */
42 | private static final int SKIP = (int) Math.ceil(ADDITIONAL_POINTS / 2f) * COORDS_PER_VERTEX;
43 |
44 | private FloatBuffer vertexBuffer;
45 | private ShortBuffer shortBuffer;
46 | private final Random random;
47 | private final float fromX, toX;
48 | private final float fromY, toY;
49 | private float[] vertices;
50 | private float currentAngle;
51 | private float coefficient;
52 | private float latestCoefficient;
53 | private float prevVal;
54 |
55 | public GLWave(float[] color, float fromX, float toX, float fromY, float toY, byte direction, Random random) {
56 | super(color);
57 | this.fromX = fromX;
58 | this.toX = toX;
59 | this.fromY = fromY;
60 | this.toY = toY;
61 | this.random = random;
62 | currentAngle = direction == DIRECTION_UP ? 0 : (float) Math.PI;
63 | initVertices();
64 | initIndices();
65 | }
66 |
67 | private void initIndices() {
68 | short[] indices = new short[(POINTS_PER_WAVE + ADDITIONAL_POINTS - 2) * COORDS_PER_VERTEX];
69 | for (int i = 0; i < indices.length / COORDS_PER_VERTEX; i++) {
70 | indices[COORDS_PER_VERTEX * i] = 0;
71 | indices[COORDS_PER_VERTEX * i + 1] = (short) (i + 1);
72 | indices[COORDS_PER_VERTEX * i + 2] = (short) (i + 2);
73 | }
74 | ByteBuffer indicesByteBuffer = ByteBuffer.allocateDirect(indices.length * SIZE_OF_SHORT);
75 | indicesByteBuffer.order(ByteOrder.nativeOrder());
76 | shortBuffer = indicesByteBuffer.asShortBuffer();
77 | shortBuffer.put(indices);
78 | shortBuffer.position(0);
79 | }
80 |
81 | private void initVertices() {
82 | int items = POINTS_PER_WAVE + ADDITIONAL_POINTS;
83 | int size = items * COORDS_PER_VERTEX;
84 | vertices = new float[size];
85 |
86 | // center
87 | vertices[0] = Utils.normalizeGl(0f, fromX, toX);
88 | vertices[1] = Utils.normalizeGl(-1f, fromY, toY);
89 |
90 | // left bottom footer
91 | vertices[3] = Utils.normalizeGl(-1f, fromX, toX);
92 | vertices[4] = Utils.normalizeGl(-1f, fromY, toY);
93 |
94 | // left top footer
95 | vertices[6] = vertices[3];
96 | vertices[7] = Utils.normalizeGl(0f, fromY, toY);
97 |
98 | // right top footer
99 | vertices[vertices.length - 6] = Utils.normalizeGl(1f, fromX, toX);
100 | vertices[vertices.length - 5] = vertices[7];
101 |
102 | // right bottom footer
103 | vertices[vertices.length - 3] = vertices[vertices.length - 6];
104 | vertices[vertices.length - 2] = vertices[4];
105 | }
106 |
107 | float waveX = 0;
108 | /**
109 | * Update wave position.
110 | * @param dAngle delta angle
111 | */
112 | public void update(float dAngle) {
113 | if (vertexBuffer == null) {
114 | ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(vertices.length * SIZE_OF_FLOAT);
115 | vertexByteBuffer.order(ByteOrder.nativeOrder());
116 | vertexBuffer = vertexByteBuffer.asFloatBuffer();
117 | vertexBuffer.put(vertices);
118 | vertexBuffer.position(0);
119 | }
120 | float angle = currentAngle;
121 | angle += dAngle;
122 | currentAngle = angle;
123 | if (coefficient == 0 && latestCoefficient > 0) {
124 | coefficient = Utils.smooth(0, latestCoefficient, SMOOTH_A);
125 | }
126 |
127 | float val = (float) Math.sin(angle) * coefficient;
128 | if (prevVal > 0 && val <= 0 || prevVal < 0 && val >= 0) {
129 | coefficient = Utils.smooth(coefficient, latestCoefficient, SMOOTH_A);
130 | waveX = random.nextFloat() * 0.3f * (random.nextBoolean() ? 1 : -1);
131 | }
132 | prevVal = val;
133 | int i = 0;
134 | double step = 1.0 / POINTS_PER_WAVE;
135 | float posX = Utils.normalizeGl(waveX, fromX, toX);
136 | float posY = Utils.normalizeGl(val, fromY, toY);
137 | for (float time = 0; time < 1 - step / 2; time += step) {
138 | vertices[COORDS_PER_VERTEX * i + 1 + SKIP] = angle;
139 | vertexBuffer.put(COORDS_PER_VERTEX * i + SKIP, Utils.quad(time, vertices[6], posX, vertices[vertices.length - 6]));
140 | vertexBuffer.put(COORDS_PER_VERTEX * i + 1 + SKIP, Utils.quad(time, vertices[7], posY, vertices[vertices.length - 5]));
141 | i++;
142 | }
143 | }
144 |
145 | public boolean isCalmedDown() {
146 | return Math.abs(prevVal) < 0.001f;
147 | }
148 |
149 | /**
150 | * Set wave height coefficient.
151 | * @param coefficient wave height coefficient
152 | */
153 | public void setCoefficient(float coefficient) {
154 | this.latestCoefficient = coefficient;
155 | }
156 |
157 | /**
158 | * Draw wave.
159 | */
160 | public void draw() {
161 | GLES20.glUseProgram(getProgram());
162 | int positionHandle = GLES20.glGetAttribLocation(getProgram(), VERTEX_POSITION);
163 | GLES20.glEnableVertexAttribArray(positionHandle);
164 | GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * SIZE_OF_FLOAT, vertexBuffer);
165 | int colorHandle = GLES20.glGetUniformLocation(getProgram(), VERTEX_COLOR);
166 | GLES20.glUniform4fv(colorHandle, 1, getColor(), 0);
167 | GLES20.glDrawElements(GLES20.GL_TRIANGLE_FAN, shortBuffer.capacity(), GLES20.GL_UNSIGNED_SHORT, shortBuffer);
168 | GLES20.glDisableVertexAttribArray(positionHandle);
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/GLWaveLayer.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import java.util.Collections;
4 | import java.util.Iterator;
5 | import java.util.Queue;
6 | import java.util.Random;
7 | import java.util.Set;
8 | import java.util.concurrent.ConcurrentHashMap;
9 | import java.util.concurrent.ConcurrentLinkedQueue;
10 |
11 | /**
12 | * Wave layer implementation.
13 | */
14 | class GLWaveLayer {
15 |
16 | private final GLAudioVisualizationView.Configuration configuration;
17 | private final GLWave[] waves;
18 | private final GLRectangle rectangle;
19 | private final Random random;
20 | private final float bubbleFromY;
21 | private final float bubbleToY;
22 | private float amplitude;
23 |
24 | private final Set usedBubbles;
25 | private final Queue unusedBubbles;
26 | private final Set producedBubbles;
27 | private boolean isCalmedDown;
28 | private final GLBubble[] allBubbles;
29 |
30 |
31 | public GLWaveLayer(GLAudioVisualizationView.Configuration configuration, float[] color, float fromY, float toY, Random random) {
32 | this.configuration = configuration;
33 | this.random = random;
34 | this.waves = new GLWave[configuration.wavesCount];
35 | float footerToY = fromY + configuration.footerHeight / (configuration.footerHeight + configuration.waveHeight * 2) * (toY - fromY);
36 | this.rectangle = new GLRectangle(color, -1, 1, fromY, footerToY);
37 | float waveWidth = 2f / configuration.wavesCount;
38 | float[] points = randomPoints(this.random, configuration.wavesCount, waveWidth, 0.15f);
39 | this.bubbleFromY = footerToY;
40 | this.bubbleToY = toY;
41 | for (int i = 0; i < configuration.wavesCount; i++) {
42 | byte direction = i % 2 == 0 ? GLWave.DIRECTION_UP : GLWave.DIRECTION_DOWN;
43 | waves[i] = new GLWave(color, points[i], points[i + 1], footerToY, toY, direction, random);
44 | }
45 | this.usedBubbles = Collections.newSetFromMap(new ConcurrentHashMap());
46 | this.producedBubbles = Collections.newSetFromMap(new ConcurrentHashMap());
47 | this.unusedBubbles = new ConcurrentLinkedQueue<>();
48 | allBubbles = generateBubbles(color, configuration.bubblesPerLayer);
49 | Collections.addAll(unusedBubbles, allBubbles);
50 | }
51 |
52 | /**
53 | * Generate random points for wave.
54 | * @param random instance of Random
55 | * @param wavesCount number of waves
56 | * @param width width of single wave
57 | * @param shiftCoef shift coefficient
58 | * @return generated points for waves
59 | */
60 | private static float[] randomPoints(Random random, int wavesCount, float width, float shiftCoef) {
61 | float shift;
62 | float[] points = new float[wavesCount + 1];
63 | for (int i = 0; i < points.length; i++) {
64 | if (i == 0) {
65 | points[i] = -1;
66 | } else if (i == points.length - 1) {
67 | points[i] = 1;
68 | } else {
69 | shift = random.nextFloat() * shiftCoef * width;
70 | shift *= random.nextBoolean() ? 1 : -1;
71 | points[i] = -1 + i * width + shift;
72 | }
73 | }
74 | return points;
75 | }
76 |
77 |
78 | /**
79 | * Update waves and bubbles positions.
80 | * @param dt time elapsed from last calculations
81 | * @param dAngle delta angle
82 | * @param ratioY aspect ratio for Y coordinates
83 | */
84 | public void update(long dt, float dAngle, float ratioY) {
85 | float d = dt * dAngle;
86 | isCalmedDown = true;
87 | for (GLWave wave : waves) {
88 | wave.update(d);
89 | isCalmedDown &= wave.isCalmedDown();
90 | }
91 | usedBubbles.addAll(producedBubbles);
92 | producedBubbles.clear();
93 | Iterator iterator = usedBubbles.iterator();
94 | while (iterator.hasNext()){
95 | GLBubble bubble = iterator.next();
96 | bubble.update(dt, ratioY);
97 | if (bubble.isOffScreen()) {
98 | unusedBubbles.add(bubble);
99 | iterator.remove();
100 | }
101 | }
102 | }
103 |
104 | public boolean isCalmedDown() {
105 | return isCalmedDown;
106 | }
107 |
108 | /**
109 | * Draw whole wave layer.
110 | */
111 | public void draw() {
112 | for (GLWave wave : waves) {
113 | wave.draw();
114 | }
115 | rectangle.draw();
116 | for (GLBubble bubble : usedBubbles) {
117 | bubble.draw();
118 | }
119 | }
120 |
121 | /**
122 | * Update waves data.
123 | * @param heightCoefficient wave height's coefficient
124 | * @param amplitude amplitude
125 | */
126 | public void updateData(float heightCoefficient, float amplitude) {
127 | for (GLWave wave : waves) {
128 | wave.setCoefficient(Utils.randomize(heightCoefficient, random));
129 | }
130 | if (amplitude > this.amplitude) {
131 | this.amplitude = amplitude;
132 | if (heightCoefficient > 0.25f) {
133 | produceBubbles();
134 | }
135 | } else {
136 | this.amplitude = Utils.smooth(this.amplitude, amplitude, 0.8f);
137 | }
138 | }
139 |
140 | /**
141 | * Produce new bubbles.
142 | */
143 | private void produceBubbles() {
144 | int bubblesCount = random.nextInt(3);
145 | for (int i = 0; i < bubblesCount; i++) {
146 | GLBubble bubble = unusedBubbles.poll();
147 | if (bubble != null) {
148 | float shift = random.nextFloat() * 0.1f * (random.nextBoolean() ? 1 : -1);
149 | float size = configuration.bubbleSize;
150 | if (configuration.randomizeBubbleSize) {
151 | size *= 0.5f + random.nextFloat() * 0.8f;
152 | }
153 | bubble.update(-1 + random.nextFloat() * 2, bubbleFromY + shift, bubbleToY, size);
154 | producedBubbles.add(bubble);
155 | }
156 | }
157 | }
158 |
159 | /**
160 | * Generate bubbles.
161 | * @param color color of bubbles
162 | * @param count number of bubbles to generate
163 | * @return generated bubbles
164 | */
165 | private GLBubble[] generateBubbles(float[] color, int count) {
166 | GLBubble[] bubbles = new GLBubble[count];
167 | for (int i=0; iNote: make sure you're calling {@link AudioVisualization#onPause()} when
14 | * {@link RecognitionListener#onEndOfSpeech()} event called to stop visualizing.
15 | */
16 | public class SpeechRecognizerDbmHandler extends DbmHandler implements RecognitionListener {
17 |
18 | private static final float MIN_RMS_DB_VALUE = -2.12f;
19 | private static final float MAX_RMS_DB_VALUE = 10.0f;
20 | private final SpeechRecognizer speechRecognizer;
21 | private final float minRmsDbValue;
22 | private final float maxRmsDbValue;
23 | private RecognitionListener innerRecognitionListener;
24 |
25 | /**
26 | * Create new instance of DbmHandler with default RMS dB values.
27 | * @param context instance of context.
28 | */
29 | SpeechRecognizerDbmHandler(@NonNull Context context) {
30 | this(context, MIN_RMS_DB_VALUE, MAX_RMS_DB_VALUE);
31 | }
32 |
33 | /**
34 | * Create new instance of DbmHandler and set {@code maxRmsDbValue}.
35 | * @param context instance of context
36 | * @param maxRmsDbValue maximum RMS dB value
37 | */
38 | SpeechRecognizerDbmHandler(@NonNull Context context, float minRmsDbValue, float maxRmsDbValue) {
39 | speechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);
40 | speechRecognizer.setRecognitionListener(this);
41 | this.minRmsDbValue = minRmsDbValue;
42 | this.maxRmsDbValue = maxRmsDbValue;
43 | }
44 |
45 | /**
46 | * Start listening for speech.
47 | * @param recognitionIntent contains parameters for the recognition to be performed
48 | * @see SpeechRecognizer#startListening(Intent)
49 | */
50 | public void startListening(Intent recognitionIntent) {
51 | speechRecognizer.startListening(recognitionIntent);
52 | }
53 |
54 | /**
55 | * Stop listening for speech.
56 | * @see SpeechRecognizer#stopListening()
57 | */
58 | public void stopListening() {
59 | speechRecognizer.stopListening();
60 | }
61 |
62 | /**
63 | * Set inner recognition listener.
64 | * @param innerRecognitionListener inner recognition listener
65 | */
66 | public SpeechRecognizerDbmHandler innerRecognitionListener(@Nullable RecognitionListener innerRecognitionListener) {
67 | this.innerRecognitionListener = innerRecognitionListener;
68 | return this;
69 | }
70 |
71 | /**
72 | * Get inner recognition listener.
73 | * @return inner recognition listener
74 | */
75 | public RecognitionListener innerRecognitionListener() {
76 | return innerRecognitionListener;
77 | }
78 |
79 | @Override
80 | protected void onDataReceivedImpl(Float rmsdB, int layersCount, float[] dBmArray, float[] ampsArray) {
81 | for (int i = 0; i < layersCount; i++) {
82 | dBmArray[i] = Utils.normalize(rmsdB, minRmsDbValue, maxRmsDbValue);
83 | ampsArray[i] = 1;
84 | }
85 | }
86 |
87 | @Override
88 | public void release() {
89 | super.release();
90 | speechRecognizer.destroy();
91 | }
92 |
93 | @Override
94 | public void onReadyForSpeech(Bundle params) {
95 | if (innerRecognitionListener != null) {
96 | innerRecognitionListener.onReadyForSpeech(params);
97 | }
98 | }
99 |
100 | @Override
101 | public void onBeginningOfSpeech() {
102 | if (innerRecognitionListener != null) {
103 | innerRecognitionListener.onBeginningOfSpeech();
104 | }
105 | }
106 |
107 | @Override
108 | public void onRmsChanged(float rmsdB) {
109 | onDataReceived(rmsdB);
110 | if (innerRecognitionListener != null) {
111 | innerRecognitionListener.onRmsChanged(rmsdB);
112 | }
113 | }
114 |
115 | @Override
116 | public void onBufferReceived(byte[] buffer) {
117 | if (innerRecognitionListener != null) {
118 | innerRecognitionListener.onBufferReceived(buffer);
119 | }
120 | }
121 |
122 | @Override
123 | public void onEndOfSpeech() {
124 | if (innerRecognitionListener != null) {
125 | innerRecognitionListener.onEndOfSpeech();
126 | }
127 | }
128 |
129 | @Override
130 | public void onError(int error) {
131 | if (innerRecognitionListener != null) {
132 | innerRecognitionListener.onError(error);
133 | }
134 | }
135 |
136 | @Override
137 | public void onResults(Bundle results) {
138 | speechRecognizer.cancel();
139 | onDataReceived(minRmsDbValue);
140 | calmDownAndStopRendering();
141 | if (innerRecognitionListener != null) {
142 | innerRecognitionListener.onResults(results);
143 | }
144 | }
145 |
146 | @Override
147 | public void onPartialResults(Bundle partialResults) {
148 | if (innerRecognitionListener != null) {
149 | innerRecognitionListener.onPartialResults(partialResults);
150 | }
151 | }
152 |
153 | @Override
154 | public void onEvent(int eventType, Bundle params) {
155 | if (innerRecognitionListener != null) {
156 | innerRecognitionListener.onEvent(eventType, params);
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/Utils.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import android.graphics.Color;
4 | import android.support.annotation.ColorInt;
5 | import android.support.annotation.NonNull;
6 | import android.support.annotation.Nullable;
7 |
8 | import java.util.Random;
9 |
10 | /**
11 | * Helpful utils.
12 | */
13 | class Utils {
14 | private Utils() {}
15 |
16 | /**
17 | * Check if value within allowed range.
18 | * @param val some value
19 | * @param min minimum value
20 | * @param max maximum value
21 | */
22 | public static float between(float val, float min, float max) {
23 | return Math.max(Math.min(val, max), min);
24 | }
25 |
26 | /**
27 | * Check if value within allowed range.
28 | * @param val some value
29 | * @param min minimum value
30 | * @param max maximum value
31 | */
32 | public static int between(int val, int min, int max) {
33 | return Math.max(Math.min(val, max), min);
34 | }
35 |
36 | /**
37 | * Convert color into OpenGL color format.
38 | * @param color some color
39 | * @return array of floats: [red, green, blue, alpha]
40 | */
41 | public static float[] convertColor(@ColorInt int color) {
42 | return new float[] {
43 | Color.red(color) / 255f,
44 | Color.green(color) / 255f,
45 | Color.blue(color) / 255f,
46 | Color.alpha(color) / 255f
47 | };
48 | }
49 |
50 | public static float normalize(float val, float from, float to) {
51 | if (val < from)
52 | return 0;
53 | if (val > to)
54 | return 1;
55 | return val / (to - from);
56 | }
57 |
58 | public static float normalizeGl(float val, float newFromVal, float newToVal) {
59 | return normalizeGl(val, -1, 1, newFromVal, newToVal);
60 | }
61 |
62 | public static float normalizeGl(float val, float fromVal, float toVal, float newFromVal, float newToVal) {
63 | float perc = (val - fromVal) / (toVal - fromVal);
64 | return newFromVal + perc * (newToVal - newFromVal);
65 | }
66 |
67 | /**
68 | * Convert square of magnitude to decibels
69 | * @param squareMag square of magnitude
70 | * @return decibels
71 | */
72 | public static float magnitudeToDb(float squareMag) {
73 | if (squareMag == 0)
74 | return 0;
75 | return (float) (20 * Math.log10(squareMag));
76 | }
77 |
78 | /**
79 | * Exponential smoothing (Holt - Winters).
80 | * @param prevValue previous values in series X[i-1]
81 | * @param newValue new value in series X[i]
82 | * @param a smooth coefficient
83 | * @return smoothed value
84 | */
85 | public static float smooth(float prevValue, float newValue, float a) {
86 | return a * newValue + (1 - a) * prevValue;
87 | }
88 |
89 | /**
90 | * Quadratic Bezier curve.
91 | * @param t time
92 | * @param p0 start point
93 | * @param p1 control point
94 | * @param p2 end point
95 | * @return point on Bezier curve at some time t
96 | */
97 | public static float quad(float t, float p0, float p1, float p2) {
98 | return (float) (p0 * Math.pow(1 - t, 2) + p1 * 2 * t * (1 - t) + p2 * t * t);
99 | }
100 |
101 | public static float randomize(float value, Random random) {
102 | float perc = between((random.nextInt(100) + 70) / 100, 0.7f, 1.3f);
103 | return perc * value;
104 | }
105 |
106 | /**
107 | * Check if all elements are null
108 | * @param array some array
109 | * @return true if all elements are null, false otherwise
110 | */
111 | public static boolean allElementsAreNull(T[] array) {
112 | for (T element : array) {
113 | if (element != null)
114 | return false;
115 | }
116 | return true;
117 | }
118 |
119 | /**
120 | * Get index of object in array.
121 | * @param array some array
122 | * @param object some object
123 | * @return index of object in array or -1
124 | */
125 | public static int indexOf(@NonNull T[] array, @Nullable T object) {
126 | for (int i = 0; i < array.length; i++) {
127 | if (array[i] == object)
128 | return i;
129 | }
130 | return -1;
131 | }
132 |
133 | /**
134 | * Check if all array elements are false
135 | * @param array some array
136 | * @return true if all elements are equals to false
137 | */
138 | public static boolean allElementsAreFalse(@NonNull boolean[] array) {
139 | for (boolean wavesWorkingState : array) {
140 | if (wavesWorkingState)
141 | return false;
142 | }
143 | return true;
144 | }
145 |
146 | /**
147 | * Check if all array elements equal to zero
148 | * @param array some array
149 | * @return true if all elements equal to zero
150 | */
151 | public static boolean allElementsAreZero(byte[] array) {
152 | for (byte b : array) {
153 | if (b != 0)
154 | return false;
155 | }
156 | return true;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/VisualizerDbmHandler.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import android.content.Context;
4 | import android.media.MediaPlayer;
5 | import android.support.annotation.NonNull;
6 |
7 | /**
8 | * DbmHandler implementation for visualizer.
9 | */
10 | public class VisualizerDbmHandler extends DbmHandler implements VisualizerWrapper.OnFftDataCaptureListener,
11 | MediaPlayer.OnPreparedListener,
12 | MediaPlayer.OnCompletionListener {
13 |
14 | /**
15 | * Maximum value of dB. Used for controlling wave height percentage.
16 | */
17 | private static final float MAX_DB_VALUE = 76;
18 |
19 | private final VisualizerWrapper visualizerWrapper;
20 | private float[] dbs;
21 | private float[] allAmps;
22 | private MediaPlayer.OnPreparedListener innerOnPreparedListener;
23 | private MediaPlayer.OnCompletionListener innerOnCompletionListener;
24 | private final float[] coefficients = new float[] {
25 | 80 / 44100f,
26 | 350 / 44100f,
27 | 2500 / 44100f,
28 | 10000 / 44100f,
29 | };
30 |
31 | VisualizerDbmHandler(@NonNull Context context, int audioSession) {
32 | visualizerWrapper = new VisualizerWrapper(context, audioSession, this);
33 | }
34 |
35 | VisualizerDbmHandler(@NonNull Context context, @NonNull MediaPlayer mediaPlayer) {
36 | this(context, mediaPlayer.getAudioSessionId());
37 | mediaPlayer.setOnPreparedListener(this);
38 | mediaPlayer.setOnCompletionListener(this);
39 | }
40 |
41 | @Override
42 | protected void onDataReceivedImpl(byte[] fft, int layersCount, float[] dBmArray, float[] ampArray) {
43 | // calculate dBs and amplitudes
44 | int dataSize = fft.length / 2 - 1;
45 | if (dbs == null || dbs.length != dataSize) {
46 | dbs = new float[dataSize];
47 | }
48 | if (allAmps == null || allAmps.length != dataSize) {
49 | allAmps = new float[dataSize];
50 | }
51 | for (int i = 0; i < dataSize; i++) {
52 | float re = fft[2 * i];
53 | float im = fft[2 * i + 1];
54 | float sqMag = re * re + im * im;
55 | dbs[i] = Utils.magnitudeToDb(sqMag);
56 | float k = 1;
57 | if (i == 0 || i == dataSize - 1) {
58 | k = 2;
59 | }
60 | allAmps[i] = (float) (k * Math.sqrt(sqMag) / dataSize);
61 | }
62 | for (int i = 0; i < layersCount; i++) {
63 | int index = (int) (coefficients[i] * fft.length);
64 | float db = dbs[index];
65 | float amp = allAmps[index];
66 | dBmArray[i] = db / MAX_DB_VALUE;
67 | ampArray[i] = amp;
68 | }
69 | }
70 |
71 | @Override
72 | public void onFftDataCapture(byte[] fft) {
73 | onDataReceived(fft);
74 | }
75 |
76 | @Override
77 | public void onResume() {
78 | super.onResume();
79 | visualizerWrapper.setEnabled(true);
80 | }
81 |
82 | @Override
83 | public void onPause() {
84 | visualizerWrapper.setEnabled(false);
85 | super.onPause();
86 | }
87 |
88 | @Override
89 | public void release() {
90 | super.release();
91 | visualizerWrapper.release();
92 | }
93 |
94 | @Override
95 | public void onCompletion(MediaPlayer mp) {
96 | calmDownAndStopRendering();
97 | visualizerWrapper.setEnabled(false);
98 | if (innerOnCompletionListener != null) {
99 | innerOnCompletionListener.onCompletion(mp);
100 | }
101 | }
102 |
103 | @Override
104 | public void onPrepared(MediaPlayer mp) {
105 | startRendering();
106 | visualizerWrapper.setEnabled(true);
107 | if (innerOnPreparedListener != null) {
108 | innerOnPreparedListener.onPrepared(mp);
109 | }
110 | }
111 |
112 | public void setInnerOnPreparedListener(MediaPlayer.OnPreparedListener onPreparedListener) {
113 | this.innerOnPreparedListener = onPreparedListener;
114 | }
115 |
116 | public void setInnerOnCompletionListener(MediaPlayer.OnCompletionListener onCompletionListener) {
117 | this.innerOnCompletionListener = onCompletionListener;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/VisualizerWrapper.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.audiovisualization;
2 |
3 | import android.content.Context;
4 | import android.media.MediaPlayer;
5 | import android.media.audiofx.Visualizer;
6 | import android.support.annotation.NonNull;
7 |
8 | import com.cleveroad.audiovisualization.utils.TunnelPlayerWorkaround;
9 |
10 | /**
11 | * Wrapper for visualizer.
12 | */
13 | class VisualizerWrapper {
14 |
15 | private static final long WAIT_UNTIL_HACK = 500;
16 | private Visualizer visualizer;
17 | private MediaPlayer mSilentPlayer;
18 | private Visualizer.OnDataCaptureListener captureListener;
19 | private int captureRate;
20 | private long lastZeroArrayTimestamp;
21 |
22 | public VisualizerWrapper(@NonNull Context context, int audioSessionId, @NonNull final OnFftDataCaptureListener onFftDataCaptureListener) {
23 | initTunnelPlayerWorkaround(context);
24 | visualizer = new Visualizer(audioSessionId);
25 | visualizer.setEnabled(false);
26 | visualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
27 | captureRate = Visualizer.getMaxCaptureRate();
28 | captureListener = new Visualizer.OnDataCaptureListener() {
29 | @Override
30 | public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) {
31 |
32 | }
33 |
34 | @Override
35 | public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
36 | boolean allZero = Utils.allElementsAreZero(fft);
37 | if (lastZeroArrayTimestamp == 0) {
38 | if (allZero) {
39 | lastZeroArrayTimestamp = System.currentTimeMillis();
40 | }
41 | } else {
42 | if (!allZero) {
43 | lastZeroArrayTimestamp = 0;
44 | } else if (System.currentTimeMillis() - lastZeroArrayTimestamp >= WAIT_UNTIL_HACK) {
45 | setEnabled(true);
46 | lastZeroArrayTimestamp = 0;
47 | }
48 | }
49 | onFftDataCaptureListener.onFftDataCapture(fft);
50 | }
51 | };
52 | visualizer.setEnabled(true);
53 | }
54 |
55 | private void initTunnelPlayerWorkaround(@NonNull Context context) {
56 | // Read "tunnel.decode" system property to determine
57 | // the workaround is needed
58 | if (TunnelPlayerWorkaround.isTunnelDecodeEnabled(context)) {
59 | mSilentPlayer = TunnelPlayerWorkaround.createSilentMediaPlayer(context);
60 | }
61 | }
62 |
63 | public void release() {
64 | visualizer.setEnabled(false);
65 | visualizer.release();
66 | visualizer = null;
67 | if (mSilentPlayer != null) {
68 | mSilentPlayer.release();
69 | mSilentPlayer = null;
70 | }
71 | }
72 |
73 | public void setEnabled(final boolean enabled) {
74 | if(visualizer == null) return;
75 | visualizer.setEnabled(false);
76 | if (enabled) {
77 | visualizer.setDataCaptureListener(captureListener, captureRate, false, true);
78 | } else {
79 | visualizer.setDataCaptureListener(null, captureRate, false, false);
80 | }
81 | visualizer.setEnabled(true);
82 | }
83 |
84 | public interface OnFftDataCaptureListener {
85 | void onFftDataCapture(byte[] fft);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/utils/SystemPropertiesProxy.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013, Haruki Hasegawa
3 | *
4 | * Licensed under the MIT license:
5 | * http://creativecommons.org/licenses/MIT/
6 | */
7 |
8 | /**
9 | * from http://stackoverflow.com/questions/2641111/where-is-android-os-systemproperties
10 | */
11 |
12 | package com.cleveroad.audiovisualization.utils;
13 |
14 | import java.lang.reflect.Method;
15 |
16 | import android.content.Context;
17 | import android.util.Log;
18 |
19 | public class SystemPropertiesProxy {
20 | private static final String TAG = "SystemPropertiesProxy";
21 |
22 | /**
23 | * Get the value for the given key, returned as a boolean. Values 'n', 'no',
24 | * '0', 'false' or 'off' are considered false. Values 'y', 'yes', '1', 'true'
25 | * or 'on' are considered true. (case insensitive). If the key does not exist,
26 | * or has any other value, then the default result is returned.
27 | *
28 | * @param key the key to lookup
29 | * @param def a default value to return
30 | * @return the key parsed as a boolean, or def if the key isn't found or is
31 | * not able to be parsed as a boolean.
32 | * @throws IllegalArgumentException if the key exceeds 32 characters
33 | */
34 | public static Boolean getBoolean(Context context, String key, boolean def)
35 | throws IllegalArgumentException {
36 | return getBoolean(context.getClassLoader(), key, def);
37 | }
38 |
39 | public static Boolean getBoolean(ClassLoader cl, String key, boolean def)
40 | throws IllegalArgumentException {
41 |
42 | Boolean ret = def;
43 |
44 | try {
45 | @SuppressWarnings("rawtypes")
46 | Class SystemProperties = cl.loadClass("android.os.SystemProperties");
47 |
48 | // Parameters Types
49 | @SuppressWarnings("rawtypes")
50 | Class[] paramTypes = new Class[2];
51 | paramTypes[0] = String.class;
52 | paramTypes[1] = boolean.class;
53 |
54 | @SuppressWarnings("unchecked")
55 | Method getBoolean = SystemProperties.getMethod("getBoolean", paramTypes);
56 |
57 | // Parameters
58 | Object[] params = new Object[2];
59 | params[0] = new String(key);
60 | params[1] = Boolean.valueOf(def);
61 |
62 | ret = (Boolean) getBoolean.invoke(SystemProperties, params);
63 |
64 | } catch (IllegalArgumentException iAE) {
65 | throw iAE;
66 | } catch (Exception e) {
67 | Log.e(TAG, "getBoolean(context, key: " + key + ", def:" + def + ")", e);
68 | ret = def;
69 | }
70 |
71 | return ret;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/library/src/main/java/com/cleveroad/audiovisualization/utils/TunnelPlayerWorkaround.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013, Haruki Hasegawa
3 | *
4 | * Licensed under the MIT license:
5 | * http://creativecommons.org/licenses/MIT/
6 | */
7 |
8 | package com.cleveroad.audiovisualization.utils;
9 |
10 | import android.content.Context;
11 | import android.media.AudioManager;
12 | import android.media.MediaPlayer;
13 | import android.util.Log;
14 |
15 | import com.cleveroad.audiovisualization.R;
16 |
17 |
18 | public class TunnelPlayerWorkaround {
19 | private static final String TAG = "TunnelPlayerWorkaround";
20 |
21 | private static final String SYSTEM_PROP_TUNNEL_DECODE_ENABLED = "tunnel.decode";
22 |
23 | private TunnelPlayerWorkaround()
24 | {
25 | }
26 |
27 | /**
28 | * Obtain "tunnel.decode" system property value
29 | *
30 | * @param context Context
31 | * @return Whether tunnel player is enabled
32 | */
33 | public static boolean isTunnelDecodeEnabled(Context context)
34 | {
35 | return SystemPropertiesProxy.getBoolean(
36 | context, SYSTEM_PROP_TUNNEL_DECODE_ENABLED, false);
37 | }
38 |
39 | /**
40 | * Create silent MediaPlayer instance to avoid tunnel player issue
41 | *
42 | * @param context Context
43 | * @return MediaPlayer instance
44 | */
45 | public static MediaPlayer createSilentMediaPlayer(Context context)
46 | {
47 | boolean result = false;
48 |
49 | MediaPlayer mp = null;
50 | try {
51 | mp = MediaPlayer.create(context, R.raw.av_workaround_1min);
52 | mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
53 |
54 | // NOTE: start() is no needed
55 | // mp.start();
56 |
57 | result = true;
58 | } catch (RuntimeException e) {
59 | Log.e(TAG, "createSilentMediaPlayer()", e);
60 | } finally {
61 | if (!result && mp != null) {
62 | try {
63 | mp.release();
64 | } catch (IllegalStateException e) {
65 | }
66 | }
67 | }
68 |
69 | return mp;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/library/src/main/res/raw/av_workaround_1min.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/library/src/main/res/raw/av_workaround_1min.mp3
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/library/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #f7b09e
5 | #f0979b
6 | #e36a97
7 | #bc428b
8 |
9 | #8f367c
10 |
11 |
12 | - @color/av_color4
13 | - @color/av_color3
14 | - @color/av_color2
15 | - @color/av_color1
16 |
17 |
18 |
--------------------------------------------------------------------------------
/library/src/main/res/values/public.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Audio Visualization
3 |
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library', ':wallpaper'
2 |
--------------------------------------------------------------------------------
/wallpaper/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/wallpaper/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.wallpaper"
9 | minSdkVersion 14
10 | targetSdkVersion 28
11 | versionCode 1
12 | versionName "1.0.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | final SUPPORT_LIB_VERSION = '28.0.0'
27 | final COLOR_PICKER_VERSION = '1.5'
28 | final BUTTER_KNIFE_VERSION = '7.0.1'
29 |
30 | compile fileTree(dir: 'libs', include: ['*.jar'])
31 |
32 | //noinspection GradleCompatible
33 | compile "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION"
34 | compile "com.larswerkman:HoloColorPicker:$COLOR_PICKER_VERSION"
35 | compile "com.jakewharton:butterknife:$BUTTER_KNIFE_VERSION"
36 |
37 | compile project(':library')
38 | }
39 |
--------------------------------------------------------------------------------
/wallpaper/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 /home/illichenko_cr/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 |
--------------------------------------------------------------------------------
/wallpaper/src/androidTest/java/com/cleveroad/wallpaper/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.wallpaper;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.cleveroad.wallpaper", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/wallpaper/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/wallpaper/src/main/java/com/cleveroad/wallpaper/AudioVisualizationWallpaperService.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.wallpaper;
2 |
3 | import android.Manifest;
4 | import android.content.Context;
5 | import android.content.SharedPreferences;
6 | import android.content.pm.PackageManager;
7 | import android.graphics.Color;
8 | import android.opengl.GLSurfaceView;
9 | import android.preference.PreferenceManager;
10 | import android.service.wallpaper.WallpaperService;
11 | import android.support.v4.app.ActivityCompat;
12 | import android.util.Log;
13 | import android.view.SurfaceHolder;
14 | import android.widget.Toast;
15 |
16 | import com.cleveroad.audiovisualization.DbmHandler;
17 | import com.cleveroad.audiovisualization.GLAudioVisualizationView;
18 |
19 | import java.lang.reflect.Field;
20 | import java.util.Arrays;
21 |
22 | /**
23 | * Wallpaper service implementation.
24 | */
25 | public class AudioVisualizationWallpaperService extends WallpaperService {
26 | @Override
27 | public Engine onCreateEngine() {
28 | return new WallpaperEngine();
29 | }
30 |
31 | private void readConfiguration(Context context, SharedPreferences preferences, GLAudioVisualizationView.ColorsBuilder builder) {
32 | final String[] colorsStrings = context.getResources().getStringArray(R.array.color_preset1);
33 | final int[] colors = new int[colorsStrings.length];
34 | for (int i = 0; i < colorsStrings.length; i++) {
35 | colors[i] = Color.parseColor(colorsStrings[i]);
36 | }
37 | int bgColor = preferences.getInt(SettingsFragment.KEY_COLOR_1, colors[0]);
38 | colors[0] = preferences.getInt(SettingsFragment.KEY_COLOR_2, colors[1]);
39 | colors[1] = preferences.getInt(SettingsFragment.KEY_COLOR_3, colors[2]);
40 | colors[2] = preferences.getInt(SettingsFragment.KEY_COLOR_4, colors[3]);
41 | colors[3] = preferences.getInt(SettingsFragment.KEY_COLOR_5, colors[4]);
42 | builder.setBackgroundColor(bgColor).setLayerColors(colors);
43 | }
44 |
45 | private class WallpaperEngine extends Engine implements SharedPreferences.OnSharedPreferenceChangeListener {
46 |
47 | private WallpaperGLSurfaceView audioVisualizationView;
48 | private DbmHandler dbmHandler;
49 | private SharedPreferences preferences;
50 | private GLAudioVisualizationView.AudioVisualizationRenderer renderer;
51 | private boolean isPermissionsGranted;
52 |
53 | @Override
54 | public void onCreate(SurfaceHolder surfaceHolder) {
55 | AudioVisualizationWallpaperService context = AudioVisualizationWallpaperService.this;
56 |
57 | if (!(isPermissionsGranted = checkPermissions())) {
58 | Toast.makeText(context, R.string.toast_permissions_not_granted, Toast.LENGTH_SHORT).show();
59 | } else {
60 | init();
61 | }
62 | }
63 |
64 | @Override
65 | public void onVisibilityChanged(final boolean visible) {
66 | if (visible && !isPermissionsGranted && (isPermissionsGranted = checkPermissions())) {
67 | init();
68 | }
69 |
70 | if (audioVisualizationView != null && dbmHandler != null) {
71 | if (visible) {
72 | audioVisualizationView.onResume();
73 | dbmHandler.onResume();
74 | } else {
75 | dbmHandler.onPause();
76 | audioVisualizationView.onPause();
77 | }
78 | }
79 | }
80 |
81 | @Override
82 | public void onDestroy() {
83 | if (preferences != null && dbmHandler != null && audioVisualizationView != null) {
84 | preferences.unregisterOnSharedPreferenceChangeListener(this);
85 | dbmHandler.release();
86 | audioVisualizationView.onDestroy();
87 | }
88 | }
89 |
90 | @Override
91 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
92 | Context context = AudioVisualizationWallpaperService.this;
93 | GLAudioVisualizationView.ColorsBuilder builder = new GLAudioVisualizationView.ColorsBuilder<>(context);
94 | readConfiguration(context, sharedPreferences, builder);
95 | if (renderer != null) {
96 | renderer.updateConfiguration(builder);
97 | }
98 | }
99 |
100 | private boolean checkPermissions() {
101 | AudioVisualizationWallpaperService context = AudioVisualizationWallpaperService.this;
102 |
103 | return ActivityCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
104 | && ActivityCompat.checkSelfPermission(context, Manifest.permission.MODIFY_AUDIO_SETTINGS) == PackageManager.PERMISSION_GRANTED;
105 | }
106 |
107 | private void init() {
108 | AudioVisualizationWallpaperService context = AudioVisualizationWallpaperService.this;
109 | audioVisualizationView = new WallpaperGLSurfaceView(context);
110 | dbmHandler = DbmHandler.Factory.newVisualizerHandler(context, 0);
111 | preferences = PreferenceManager.getDefaultSharedPreferences(context);
112 | preferences.registerOnSharedPreferenceChangeListener(this);
113 | GLAudioVisualizationView.Builder builder = new GLAudioVisualizationView.Builder(context)
114 | .setBubblesSize(R.dimen.bubble_size)
115 | .setBubblesRandomizeSize(true)
116 | .setWavesHeight(R.dimen.wave_height)
117 | .setWavesFooterHeight(R.dimen.footer_height)
118 | .setWavesCount(7)
119 | .setLayersCount(4)
120 | .setBubblesPerLayer(10);
121 | readConfiguration(context, preferences, builder);
122 | renderer = new GLAudioVisualizationView.RendererBuilder(builder)
123 | .glSurfaceView(audioVisualizationView)
124 | .handler(dbmHandler)
125 | .build();
126 | audioVisualizationView.setEGLContextClientVersion(2);
127 | audioVisualizationView.setRenderer(renderer);
128 | }
129 |
130 |
131 | class WallpaperGLSurfaceView extends GLSurfaceView {
132 |
133 | WallpaperGLSurfaceView(Context context) {
134 | super(context);
135 | }
136 |
137 | @Override
138 | public SurfaceHolder getHolder() {
139 | return getSurfaceHolder();
140 | }
141 |
142 | public void onDestroy() {
143 | super.onDetachedFromWindow();
144 | }
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/wallpaper/src/main/java/com/cleveroad/wallpaper/ColorPickerDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.wallpaper;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.annotation.ColorInt;
7 | import android.support.annotation.Nullable;
8 | import android.support.v4.app.DialogFragment;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.Button;
13 |
14 | import com.larswerkman.holocolorpicker.ColorPicker;
15 | import com.larswerkman.holocolorpicker.SVBar;
16 |
17 | import butterknife.Bind;
18 | import butterknife.ButterKnife;
19 |
20 | /**
21 | * Color picker
22 | */
23 | public class ColorPickerDialogFragment extends DialogFragment {
24 |
25 | public static final String KEY_INDEX = "INDEX";
26 | public static final String KEY_COLOR = "COLOR";
27 | @Bind(R.id.picker)
28 | ColorPicker colorPicker;
29 | @Bind(R.id.svbar)
30 | SVBar svBar;
31 | @Bind(R.id.btn_save)
32 | Button btnSave;
33 |
34 | public static ColorPickerDialogFragment instance(int index, @ColorInt int color) {
35 | Bundle args = new Bundle();
36 | args.putInt(KEY_COLOR, color);
37 | args.putInt(KEY_INDEX, index);
38 | ColorPickerDialogFragment fragment = new ColorPickerDialogFragment();
39 | fragment.setArguments(args);
40 | return fragment;
41 | }
42 |
43 | @Nullable
44 | @Override
45 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
46 | View view = inflater.inflate(R.layout.fragment_color_picker, container, false);
47 | ButterKnife.bind(this, view);
48 | return view;
49 | }
50 |
51 | @Override
52 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
53 | super.onViewCreated(view, savedInstanceState);
54 | colorPicker.addSVBar(svBar);
55 | colorPicker.setColor(getArguments().getInt(KEY_COLOR));
56 | btnSave.setOnClickListener(new View.OnClickListener() {
57 | @Override
58 | public void onClick(View v) {
59 | Intent data = new Intent();
60 | data.putExtra(KEY_INDEX, getArguments().getInt(KEY_INDEX));
61 | data.putExtra(KEY_COLOR, colorPicker.getColor());
62 | getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, data);
63 | dismiss();
64 | }
65 | });
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/wallpaper/src/main/java/com/cleveroad/wallpaper/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.wallpaper;
2 |
3 | import android.Manifest;
4 | import android.content.pm.PackageManager;
5 | import android.os.Bundle;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.Nullable;
8 | import android.support.v4.app.ActivityCompat;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.widget.Toast;
11 |
12 | /**
13 | * Settings activity.
14 | */
15 | public class SettingsActivity extends AppCompatActivity {
16 |
17 | private static final int REQUEST_CODE = 1;
18 | private boolean shouldOpenFragment;
19 |
20 | @Override
21 | protected void onCreate(@Nullable Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.activity_settings);
24 |
25 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
26 | && ActivityCompat.checkSelfPermission(this, Manifest.permission.MODIFY_AUDIO_SETTINGS) == PackageManager.PERMISSION_GRANTED) {
27 | openFragment();
28 | } else {
29 | requestPermissions();
30 | }
31 | }
32 |
33 | void openFragment() {
34 | getSupportFragmentManager().beginTransaction()
35 | .replace(R.id.container, SettingsFragment.instance())
36 | .commit();
37 | }
38 |
39 | private void requestPermissions() {
40 | ActivityCompat.requestPermissions(
41 | this,
42 | new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS},
43 | REQUEST_CODE
44 | );
45 | }
46 |
47 | private void permissionsNotGranted() {
48 | Toast.makeText(this, R.string.toast_permissions_not_granted, Toast.LENGTH_SHORT).show();
49 | finish();
50 | }
51 |
52 | @Override
53 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
54 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
55 | if (requestCode == REQUEST_CODE) {
56 | boolean bothGranted = true;
57 | for (int i = 0; i < permissions.length; i++) {
58 | if (Manifest.permission.RECORD_AUDIO.equals(permissions[i]) || Manifest.permission.MODIFY_AUDIO_SETTINGS.equals(permissions[i])) {
59 | bothGranted &= grantResults[i] == PackageManager.PERMISSION_GRANTED;
60 | }
61 | }
62 | if (bothGranted) {
63 | shouldOpenFragment = true;
64 | } else {
65 | permissionsNotGranted();
66 | }
67 | }
68 | }
69 |
70 | @Override
71 | protected void onResume() {
72 | super.onResume();
73 | if (shouldOpenFragment) {
74 | shouldOpenFragment = false;
75 | openFragment();
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/wallpaper/src/main/java/com/cleveroad/wallpaper/SettingsFragment.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.wallpaper;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.content.SharedPreferences;
6 | import android.graphics.Color;
7 | import android.graphics.drawable.ColorDrawable;
8 | import android.os.Bundle;
9 | import android.preference.PreferenceManager;
10 | import android.support.annotation.Nullable;
11 | import android.support.v4.app.Fragment;
12 | import android.view.LayoutInflater;
13 | import android.view.Menu;
14 | import android.view.MenuInflater;
15 | import android.view.MenuItem;
16 | import android.view.View;
17 | import android.view.ViewGroup;
18 |
19 | import butterknife.Bind;
20 | import butterknife.ButterKnife;
21 |
22 | /**
23 | * Settings fragment
24 | */
25 | public class SettingsFragment extends Fragment {
26 |
27 | static final String KEY_COLOR_1 = "COLOR_1";
28 | static final String KEY_COLOR_2 = "COLOR_2";
29 | static final String KEY_COLOR_3 = "COLOR_3";
30 | static final String KEY_COLOR_4 = "COLOR_4";
31 | static final String KEY_COLOR_5 = "COLOR_5";
32 | @Bind({R.id.view_1, R.id.view_2, R.id.view_3, R.id.view_4, R.id.view_5})
33 | View[] views;
34 | private SharedPreferences preferences;
35 |
36 | public static SettingsFragment instance() {
37 | return new SettingsFragment();
38 | }
39 |
40 | @Override
41 | public void onCreate(@Nullable Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 | setHasOptionsMenu(true);
44 | preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
45 | }
46 |
47 | @Nullable
48 | @Override
49 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
50 | View view = inflater.inflate(R.layout.fragment_settings, container, false);
51 | ButterKnife.bind(this, view);
52 | return view;
53 | }
54 |
55 | @Override
56 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
57 | super.onViewCreated(view, savedInstanceState);
58 | final String[] colorsStrings = getResources().getStringArray(R.array.color_preset1);
59 | final int[] colors = new int[colorsStrings.length];
60 | for (int i = 0; i < colorsStrings.length; i++) {
61 | colors[i] = Color.parseColor(colorsStrings[i]);
62 | }
63 | colors[0] = preferences.getInt(KEY_COLOR_1, colors[0]);
64 | colors[1] = preferences.getInt(KEY_COLOR_2, colors[1]);
65 | colors[2] = preferences.getInt(KEY_COLOR_3, colors[2]);
66 | colors[3] = preferences.getInt(KEY_COLOR_4, colors[3]);
67 | colors[4] = preferences.getInt(KEY_COLOR_5, colors[4]);
68 | for (int i = 0; i < 5; i++) {
69 | final int index = i;
70 | views[i].setBackgroundColor(colors[i]);
71 | views[i].setOnClickListener(new View.OnClickListener() {
72 | @Override
73 | public void onClick(View v) {
74 | ColorPickerDialogFragment fragment = ColorPickerDialogFragment.instance(index, colors[index]);
75 | fragment.setTargetFragment(SettingsFragment.this, 1);
76 | fragment.show(getFragmentManager(), "ColorPicker");
77 | }
78 | });
79 | }
80 | }
81 |
82 | @Override
83 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
84 | super.onActivityResult(requestCode, resultCode, data);
85 | if (requestCode == 1 && resultCode == Activity.RESULT_OK) {
86 | int index = data.getIntExtra(ColorPickerDialogFragment.KEY_INDEX, -1);
87 | int color = data.getIntExtra(ColorPickerDialogFragment.KEY_COLOR, 0);
88 | views[index].setBackgroundColor(color);
89 | }
90 | }
91 |
92 | @Override
93 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
94 | super.onCreateOptionsMenu(menu, inflater);
95 | inflater.inflate(R.menu.main, menu);
96 | MenuItem preset = menu.findItem(R.id.action_preset);
97 | Menu m = preset.getSubMenu();
98 | String[] presets = getResources().getStringArray(R.array.presets);
99 | for (int i = 0; i < presets.length; i++) {
100 | final int index = i + 1;
101 | m.add(Menu.NONE, i, Menu.NONE, presets[i]).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
102 | @Override
103 | public boolean onMenuItemClick(MenuItem item) {
104 | setPreset(index);
105 | return true;
106 | }
107 | });
108 | }
109 | }
110 |
111 | @Override
112 | public boolean onOptionsItemSelected(MenuItem item) {
113 | if (item.getItemId() == R.id.action_save) {
114 | preferences.edit()
115 | .putInt(KEY_COLOR_1, ((ColorDrawable) views[0].getBackground()).getColor())
116 | .putInt(KEY_COLOR_2, ((ColorDrawable) views[1].getBackground()).getColor())
117 | .putInt(KEY_COLOR_3, ((ColorDrawable) views[2].getBackground()).getColor())
118 | .putInt(KEY_COLOR_4, ((ColorDrawable) views[3].getBackground()).getColor())
119 | .putInt(KEY_COLOR_5, ((ColorDrawable) views[4].getBackground()).getColor())
120 | .apply();
121 | getActivity().finish();
122 | return true;
123 | }
124 | return super.onOptionsItemSelected(item);
125 | }
126 |
127 | private void setPreset(int index) {
128 | int id = getResources().getIdentifier("color_preset" + index, "array", getContext().getPackageName());
129 | String[] colors = getResources().getStringArray(id);
130 | for (int i = 0; i < 5; i++) {
131 | views[i].setBackgroundColor(Color.parseColor(colors[i]));
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/wallpaper/src/main/res/drawable-hdpi/ic_color_lens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/drawable-hdpi/ic_color_lens.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/drawable-hdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/drawable-hdpi/ic_save.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/drawable-mdpi/ic_color_lens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/drawable-mdpi/ic_color_lens.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/drawable-mdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/drawable-mdpi/ic_save.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/drawable-xhdpi/ic_color_lens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/drawable-xhdpi/ic_color_lens.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/drawable-xhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/drawable-xhdpi/ic_save.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/drawable-xxhdpi/ic_color_lens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/drawable-xxhdpi/ic_color_lens.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/drawable-xxhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/drawable-xxhdpi/ic_save.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/drawable-xxxhdpi/ic_color_lens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/drawable-xxxhdpi/ic_color_lens.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/drawable-xxxhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/drawable-xxxhdpi/ic_save.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/wallpaper/src/main/res/layout/fragment_color_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
25 |
26 |
33 |
34 |
--------------------------------------------------------------------------------
/wallpaper/src/main/res/layout/fragment_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
17 |
18 |
23 |
24 |
29 |
30 |
35 |
36 |
41 |
42 |
43 |
44 |
50 |
51 |
--------------------------------------------------------------------------------
/wallpaper/src/main/res/layout/popup_presets.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/wallpaper/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | -
12 |
13 |
14 |
15 |
20 |
--------------------------------------------------------------------------------
/wallpaper/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cleveroad/WaveInApp/f6fd11a8f8a20e1ee01999db3d80b5037a773f08/wallpaper/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/wallpaper/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #2196F3
4 | #1976D2
5 | #CDDC39
6 |
7 |
8 | - #8f367c
9 | - #bc428b
10 | - #e36a97
11 | - #f0979b
12 | - #f7b09e
13 |
14 |
15 |
16 | - #3b5088
17 | - #5460ae
18 | - #8c80d0
19 | - #c19fdd
20 | - #deb1e2
21 |
22 |
23 |
24 | - #943a52
25 | - #b94855
26 | - #d47466
27 | - #d99e7c
28 | - #d9b787
29 |
30 |
31 |
32 | - #235c2e
33 | - #257442
34 | - #4c997d
35 | - #7bb6b1
36 | - #97c5cf
37 |
38 |
39 |
40 | - #4e4e4e
41 | - #606060
42 | - #878787
43 | - #a9a9a9
44 | - #bcbcbc
45 |
46 |
47 |
48 | - #5b47b0
49 | - #8353d8
50 | - #c474ec
51 | - #ef93e2
52 | - #fda6da
53 |
54 |
55 |
56 | - #006982
57 | - #007eb9
58 | - #2c99f4
59 | - #86adfe
60 | - #b9b7fe
61 |
62 |
63 |
64 | - #495a01
65 | - #427701
66 | - #47a501
67 | - #58c552
68 | - #63d98b
69 |
70 |
71 |
--------------------------------------------------------------------------------
/wallpaper/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 25dp
4 | 60dp
5 | 170dp
6 |
--------------------------------------------------------------------------------
/wallpaper/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Audio Visualization Wallpaper
3 | Audio Visualization Wallpaper
4 | Audio visualization wallpaper description.
5 | Save
6 | Set
7 | Choose Preset
8 | Tap on layer to pick custom color or choose color preset from menu.
9 | Permissions not granted.
10 |
11 |
12 | - Preset 1
13 | - Preset 2
14 | - Preset 3
15 | - Preset 4
16 | - Preset 5
17 | - Preset 6
18 | - Preset 7
19 | - Preset 8
20 |
21 |
22 |
--------------------------------------------------------------------------------
/wallpaper/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/wallpaper/src/main/res/xml/wallpaper.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/wallpaper/src/test/java/com/cleveroad/wallpaper/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.cleveroad.wallpaper;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------