├── wear
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_watch_vibrate.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_watch_vibrate.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_watch_vibrate.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_watch_vibrate.png
│ │ ├── layout
│ │ │ ├── activity_settings.xml
│ │ │ ├── skin_preview.xml
│ │ │ ├── activity_main.xml
│ │ │ └── fragment_action.xml
│ │ ├── values-de
│ │ │ └── strings.xml
│ │ ├── color
│ │ │ └── action_button.xml
│ │ └── values
│ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── mantz_it
│ │ └── wearguitartuner
│ │ ├── ActionFragment.java
│ │ ├── SettingsActivity.java
│ │ └── MainActivity.java
├── proguard-rules.pro
├── build.gradle
└── wear.iml
├── guitartunerlibrary
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable
│ │ │ │ ├── vintage_tuner_skin_rect.png
│ │ │ │ ├── vintage_tuner_skin_round.png
│ │ │ │ ├── thumbnail_debug_skin_rect.png
│ │ │ │ ├── thumbnail_debug_skin_round.png
│ │ │ │ ├── thumbnail_default_skin_rect.png
│ │ │ │ ├── thumbnail_default_skin_round.png
│ │ │ │ ├── thumbnail_vintage_needle_skin_rect.png
│ │ │ │ └── thumbnail_vintage_needle_skin_round.png
│ │ │ ├── values-de
│ │ │ │ └── strings.xml
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── mantz_it
│ │ │ └── guitartunerlibrary
│ │ │ ├── FFT.java
│ │ │ ├── TunerWearableListenerService.java
│ │ │ ├── DebugTunerSkin.java
│ │ │ ├── TunerSurface.java
│ │ │ ├── TunerSkin.java
│ │ │ ├── VintageNeedleTunerSkin.java
│ │ │ ├── AudioProcessingEngine.java
│ │ │ ├── DefaultTunerSkin.java
│ │ │ ├── PreferenceSyncHelper.java
│ │ │ └── GuitarTuner.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── mantz_it
│ │ └── guitartunerlibrary
│ │ └── ApplicationTest.java
├── build.gradle
├── proguard-rules.pro
└── guitartunerlibrary.iml
├── settings.gradle
├── mobile
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ ├── menu
│ │ │ │ └── menu_main.xml
│ │ │ ├── layout
│ │ │ │ ├── intro_page.xml
│ │ │ │ ├── activity_intro.xml
│ │ │ │ └── activity_main.xml
│ │ │ └── values-de
│ │ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── mantz_it
│ │ │ └── wearguitartuner
│ │ │ ├── IntroActivity.java
│ │ │ └── MainActivity.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── mantz_it
│ │ └── wearguitartuner
│ │ └── ApplicationTest.java
├── proguard-rules.pro
├── build.gradle
└── mobile.iml
├── WearGuitarTuner.apk
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── changelog.txt
├── gradle.properties
├── Readme.md
├── gradlew.bat
├── gradlew
└── COPYING
/wear/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/guitartunerlibrary/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':mobile', ':wear', ':guitartunerlibrary'
2 |
--------------------------------------------------------------------------------
/mobile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | manifest-merger-release-report.txt
3 | *.apk
4 |
--------------------------------------------------------------------------------
/WearGuitarTuner.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/WearGuitarTuner.apk
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/wear/src/main/res/drawable-hdpi/ic_watch_vibrate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/wear/src/main/res/drawable-hdpi/ic_watch_vibrate.png
--------------------------------------------------------------------------------
/wear/src/main/res/drawable-mdpi/ic_watch_vibrate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/wear/src/main/res/drawable-mdpi/ic_watch_vibrate.png
--------------------------------------------------------------------------------
/wear/src/main/res/drawable-xhdpi/ic_watch_vibrate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/wear/src/main/res/drawable-xhdpi/ic_watch_vibrate.png
--------------------------------------------------------------------------------
/wear/src/main/res/drawable-xxhdpi/ic_watch_vibrate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/wear/src/main/res/drawable-xxhdpi/ic_watch_vibrate.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable/vintage_tuner_skin_rect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable/vintage_tuner_skin_rect.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable/vintage_tuner_skin_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable/vintage_tuner_skin_round.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable/thumbnail_debug_skin_rect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable/thumbnail_debug_skin_rect.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable/thumbnail_debug_skin_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable/thumbnail_debug_skin_round.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable/thumbnail_default_skin_rect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable/thumbnail_default_skin_rect.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable/thumbnail_default_skin_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable/thumbnail_default_skin_round.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable/thumbnail_vintage_needle_skin_rect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable/thumbnail_vintage_needle_skin_rect.png
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/drawable/thumbnail_vintage_needle_skin_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/demantz/WearGuitarTuner/HEAD/guitartunerlibrary/src/main/res/drawable/thumbnail_vintage_needle_skin_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Java class files
2 | *.class
3 |
4 | # generated files
5 | bin/
6 | gen/
7 |
8 | # Local configuration file (sdk path, etc)
9 | local.properties
10 |
11 | # Android Studio
12 | .idea
13 | .gradle
14 | build/
15 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Wear Gitarrenstimmgerät
4 | Stimmgerät
5 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/wear/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/changelog.txt:
--------------------------------------------------------------------------------
1 | CHANGELOG - Wear Guitar Tuner
2 |
3 | version 1.01
4 | - First released version. Might be unstable.
5 | - 3 skins: default, vintage needle and debug skin
6 | - Skin can be changed on the wearable or on the handheld
7 | - Settings are synced between wearable and handheld
8 | - Vibration feedback (can be disabled)
9 | - Available languages: English and German
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Wear Guitar Tuner
3 | Guitar Tuner
4 | pref_skinIndex
5 | pref_roundScreen
6 | pref_vibration_enabled
7 |
8 |
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/mantz_it/wearguitartuner/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.wearguitartuner;
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 | }
--------------------------------------------------------------------------------
/guitartunerlibrary/src/androidTest/java/com/mantz_it/guitartunerlibrary/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.guitartunerlibrary;
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 | }
--------------------------------------------------------------------------------
/wear/src/main/res/layout/skin_preview.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFF8800
4 | #FF0000AA
5 | #FFFFFFFF
6 | #FFFFFFFF
7 | #FF000000
8 | #FF5555FF
9 | #FF888888
10 |
--------------------------------------------------------------------------------
/wear/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Lange berühren zum Öffnen der Einstellungen …
4 | Scrolle nach rechts um das Design zu ändern!
5 | Scrolle nach unten für weitere Einstellungen!
6 | Vibration aktivieren
7 | Vibration deaktivieren
8 |
--------------------------------------------------------------------------------
/wear/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
13 |
--------------------------------------------------------------------------------
/wear/src/main/res/color/action_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/wear/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Long press for settings …
4 | Scroll right to choose skin!
5 | Scroll down for more settings!
6 | Turn vibration on
7 | Turn vibration off
8 | pref_settingsActivityFirstStart
9 |
10 |
--------------------------------------------------------------------------------
/guitartunerlibrary/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.1"
6 |
7 | defaultConfig {
8 | minSdkVersion 18
9 | targetSdkVersion 21
10 | versionCode 1
11 | versionName "1.01"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | compile 'com.android.support:appcompat-v7:21.0.3'
24 | compile 'com.google.android.gms:play-services:6.5.87'
25 | }
26 |
--------------------------------------------------------------------------------
/mobile/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 /opt/android_sdk/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 |
--------------------------------------------------------------------------------
/wear/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 /opt/android_sdk/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 |
--------------------------------------------------------------------------------
/guitartunerlibrary/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 /opt/android_sdk/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 |
--------------------------------------------------------------------------------
/wear/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 |
4 | android {
5 | compileSdkVersion 21
6 | buildToolsVersion "21.1.1"
7 |
8 | defaultConfig {
9 | applicationId "com.mantz_it.wearguitartuner"
10 | minSdkVersion 21
11 | targetSdkVersion 21
12 | versionCode 1
13 | versionName "1.01"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | compile project(':guitartunerlibrary')
26 | compile 'com.google.android.support:wearable:1.1.0'
27 | }
28 |
--------------------------------------------------------------------------------
/mobile/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.1"
6 |
7 | defaultConfig {
8 | applicationId "com.mantz_it.wearguitartuner"
9 | minSdkVersion 18
10 | targetSdkVersion 21
11 | versionCode 1
12 | versionName "1.01"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile project(':guitartunerlibrary')
25 | wearApp project(':wear')
26 | compile 'com.android.support:appcompat-v7:21.0.3'
27 | }
28 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
12 |
13 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/mobile/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/intro_page.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
22 |
23 |
29 |
--------------------------------------------------------------------------------
/mobile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/wear/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/activity_intro.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
19 |
20 |
21 |
30 |
31 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/wear/src/main/res/layout/fragment_action.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
21 |
22 |
31 |
32 |
41 |
42 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | Guitar Tuner for Android Wear
2 | =============================
3 |
4 | This is the repository of the Wear Guitar Tuner Application. It is a
5 | standalone guitar tuner for Android Wear that can be customized. Settings
6 | can be adjusted on the wearable device or on the handheld companion app.
7 | The handheld companion app can also be used as standalone tuner itself.
8 |
9 | 
10 |
11 | (photo by Dennis Mantz)
12 |
13 |
14 | Implemented Features
15 | --------------------
16 | * customizable skins: default, vintage needle and debug skin
17 | * Skin can be changed on the wearable or on the handheld device
18 | * Settings are synced between wearable and handheld device
19 | * Both the wearable and the handheld device act as standalone tuner
20 | * Vibration feedback (can be disabled)
21 | * Available languages: English and German
22 |
23 |
24 | Testet Devices
25 | --------------
26 | * G Watch R
27 |
28 |
29 | Known Issues
30 | ------------
31 | * Pitch recognition has to be improved
32 |
33 |
34 | Installation / Usage
35 | --------------------
36 | The app project in this repository was generated by Android Studio.
37 | Use the 'import project' function in Android Studio to import the repository
38 | as new project.
39 |
40 | The WearGuitarTuner.apk file is also in this repository so that it can be used without
41 | building it yourself. But it won't be synched to the latest code base all the time.
42 | Install the apk file on the handheld device and it will be synced to the
43 | wearable device automatically (by the Android system).
44 |
45 |
46 | License
47 | -------
48 | This application is free software; you can redistribute it and/or
49 | modify it under the terms of the GNU General Public
50 | License as published by the Free Software Foundation; either
51 | version 2 of the License, or (at your option) any later version.
52 | [http://www.gnu.org/licenses/gpl.html](http://www.gnu.org/licenses/gpl.html) GPL version 2 or higher
53 |
54 | principal author: Dennis Mantz
55 |
--------------------------------------------------------------------------------
/wear/src/main/java/com/mantz_it/wearguitartuner/ActionFragment.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.wearguitartuner;
2 |
3 | import android.app.Fragment;
4 | import android.os.Bundle;
5 | import android.support.annotation.Nullable;
6 | import android.support.wearable.view.CircledImageView;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.TextView;
11 |
12 | /**
13 | *
Action Fragment by Destil
14 | *
15 | * Module: ActionFragment.java
16 | * Description: Action button fragment implemented by Destil
17 | * Source: http://stackoverflow.com/questions/24969086/is-there-an-easy-way-to-create-an-action-button-fragment
18 | *
19 | * @author Destil (http://stackoverflow.com/users/560358/destil)
20 | */
21 |
22 | public class ActionFragment extends Fragment implements View.OnClickListener {
23 |
24 | private static Listener mListener;
25 | private CircledImageView vIcon;
26 | private TextView vLabel;
27 |
28 | public static ActionFragment create(int iconResId, int labelResId, Listener listener) {
29 | mListener = listener;
30 | ActionFragment fragment = new ActionFragment();
31 | Bundle args = new Bundle();
32 | args.putInt("ICON", iconResId);
33 | args.putInt("LABEL", labelResId);
34 | fragment.setArguments(args);
35 | return fragment;
36 | }
37 |
38 | @Override
39 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
40 | return inflater.inflate(R.layout.fragment_action, container, false);
41 | }
42 |
43 | @Override
44 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
45 | super.onViewCreated(view, savedInstanceState);
46 | vIcon = (CircledImageView) view.findViewById(R.id.civ_actionFragment_icon);
47 | vLabel = (TextView) view.findViewById(R.id.tv_actionFragment_label);
48 | vIcon.setImageResource(getArguments().getInt("ICON"));
49 | vLabel.setText(getArguments().getInt("LABEL"));
50 | view.setOnClickListener(this);
51 | }
52 |
53 | @Override
54 | public void onClick(View v) {
55 | mListener.onActionPerformed();
56 | }
57 |
58 | public interface Listener {
59 | public void onActionPerformed();
60 | }
61 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Weiter
4 | Zeige Logdatei der Smartwatch
5 | Fertig
6 | Willkommen zum Gitarrenstimmgerät für Android Wear
7 | Eine App um eine Gitarre mit Ihrer Android Smartwatch zu stimmen! Klicken
8 | sie auf \'Weiter\' und sehen Sie wie einfach es geht!
9 | Haptisches Feedback
10 | Während dem Stimmvorgang gibt die Uhr Ihnen haptisches Feedback über die
11 | Stimmung der aktuell gespielten Note. Eine einzelne Vibration bedeutet der Ton ist noch zu tief. Zwei einzelne
12 | Vibrationen dagegen weisen auf einen zu hohen Ton hin. Ist die Gitarrensaite korrekt gestimmt, vibriert die App
13 | drei mal sehr schnell.
14 | Passen Sie das Stimmgerät individuell an
15 | Sie können das Aussehen und Verhalten des Stimmgerätes anpassen. Tippen Sie
16 | dazu mit dem Finger lange auf den Bildschirm Ihrer Smartwatch oder benutzen Sie die Konfigurations-App auf Ihrem
17 | Smartphone!
18 | Überspringen
19 | Willkommen zum Gitarrenstimmgerät für Android Wear! Bitte starten Sie die App
20 | auf ihrer Smartwatch. Wenn Sie die App dort nicht finden können, haben Sie etwas Geduld. Der Installationsprozess
21 | kann ein oder zwei Minuten dauern. Wenn Sie die Installation manuell anstoßen wollen hilft eventuell ein Klick
22 | auf \'Apps erneut synchronisieren\' unter den Einstellungen der Google Android Wear App.
23 | Versende die Logdatei per Email
24 | Wähle eine App zum Versenden der Logdatei
25 | Ich habe es verstanden!
26 | Spenden
27 | Zeige Log des Telefons
28 | Zeige Log der Uhr
29 | Der Google API Client ist nicht verbunden!
30 | Die Uhr ist nicht verbunden!
31 | Laden
32 | Uhr wird nach der Logdatai abgefragt...
33 | Abfragen der Logdatei von der Uhr fehlgeschlagen:
34 | Log des Telefons
35 | Teilen
36 | Abbrechen
37 | Log der Uhr
38 | Vibration aktiviert
39 | Wähle das Aussehen
40 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | This is Wear Guitar Tuner! Please launch the App on your connected
4 | wearable Android device. If you can\'t find the App on your wearable device be patient. The installation
5 | process might take one or two minutes. If you want to trigger installation again try to open the Android Wear
6 | Companion App and hit \'Resync Apps\' in the Settings.
7 | Show handheld log
8 | Show wearable log
9 | Donate
10 | Show log file of the wearable
11 | Skip intro
12 | Next
13 | Finish
14 | Welcome to Wear Guitar Tuner
15 | Haptic Feedback
16 | Customize Wear Guitar Tuner
17 | An App to tune your guitar with the help of your Android Wear smartwatch!
18 | Click on \'Next\' to see how it works!
19 | During the tuning process the wearable device gives haptic feedback about
20 | the tuning of the current note. A single vibration indicates that the pitch is too low. Two vibrations indicate
21 | that the pitch is too high. If the pitch is tuned correctly the device will vibrate three times very quickly
22 | You can customize the look and feel of the tuner app. Long press on the
23 | screen of the wearable device to open the settings menu or use the companion app on the handheld device.
24 | I got it!
25 | pref_mainActivityFirstStart
26 | pref_showWelcomeCard
27 | Share log file via email
28 | Choose the App to share the log file
29 | The google API client is not connected!
30 | The wearable node is not connected!
31 | Loading
32 | Querying wearable device for the log...
33 | Failed to query the wearable device for the log:
34 | Handheld Log
35 | Share
36 | Cancel
37 | Wearable Log
38 | Vibration enabled
39 | Choose a skin
40 |
41 |
42 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
19 |
26 |
34 |
35 |
36 |
43 |
48 |
49 |
50 |
57 |
64 |
65 |
68 |
69 |
74 |
75 |
76 |
77 |
78 |
87 |
88 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/java/com/mantz_it/guitartunerlibrary/FFT.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.guitartunerlibrary;
2 | /*
3 | * Copyright 2006-2007 Columbia University.
4 | *
5 | * This file is part of MEAPsoft.
6 | *
7 | * MEAPsoft is free software; you can redistribute it and/or modify
8 | * it under the terms of the GNU General Public License version 2 as
9 | * published by the Free Software Foundation.
10 | *
11 | * MEAPsoft is distributed in the hope that it will be useful, but
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with MEAPsoft; if not, write to the Free Software
18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19 | * 02110-1301 USA
20 | *
21 | * See the file "COPYING" for the text of the license.
22 | */
23 | public class FFT {
24 |
25 | int n, m;
26 |
27 | // Lookup tables. Only need to recompute when size of FFT changes.
28 | float[] cos;
29 | float[] sin;
30 |
31 | float[] window;
32 |
33 | public FFT(int n) {
34 | this.n = n;
35 | this.m = (int)(Math.log(n) / Math.log(2));
36 |
37 | // Make sure n is a power of 2
38 | if(n != (1<= n1 ) {
116 | j = j - n1;
117 | n1 = n1/2;
118 | }
119 | j = j + n1;
120 |
121 | if (i < j) {
122 | t1 = x[i];
123 | x[i] = x[j];
124 | x[j] = t1;
125 | t1 = y[i];
126 | y[i] = y[j];
127 | y[j] = t1;
128 | }
129 | }
130 |
131 | // FFT
132 | n1 = 0;
133 | n2 = 1;
134 |
135 | for (i=0; i < m; i++) {
136 | n1 = n2;
137 | n2 = n2 + n2;
138 | a = 0;
139 |
140 | for (j=0; j < n1; j++) {
141 | c = cos[a];
142 | s = sin[a];
143 | a += 1 << (m-i-1);
144 |
145 | for (k=j; k < n; k=k+n2) {
146 | t1 = c*x[k+n1] - s*y[k+n1];
147 | t2 = s*x[k+n1] + c*y[k+n1];
148 | x[k+n1] = x[k] - t1;
149 | y[k+n1] = y[k] - t2;
150 | x[k] = x[k] + t1;
151 | y[k] = y[k] + t2;
152 | }
153 | }
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/java/com/mantz_it/guitartunerlibrary/TunerWearableListenerService.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.guitartunerlibrary;
2 |
3 | import android.preference.PreferenceManager;
4 | import android.util.Log;
5 |
6 | import com.google.android.gms.common.api.GoogleApiClient;
7 | import com.google.android.gms.wearable.DataEventBuffer;
8 | import com.google.android.gms.wearable.MessageApi;
9 | import com.google.android.gms.wearable.MessageEvent;
10 | import com.google.android.gms.wearable.Wearable;
11 | import com.google.android.gms.wearable.WearableListenerService;
12 |
13 | import java.io.BufferedReader;
14 | import java.io.IOException;
15 | import java.io.InputStreamReader;
16 | import java.util.concurrent.TimeUnit;
17 |
18 | /**
19 | *
Wear Guitar Tuner - Tuner WearableListenerService
20 | *
21 | * Module: TunerWearableListenerService.java
22 | * Description: Service that implements callback methods of the Google Android
23 | * Wear Data Layer API.
24 | *
25 | * @author Dennis Mantz
26 | *
27 | * Copyright (C) 2014 Dennis Mantz
28 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
29 | *
30 | * This library is free software; you can redistribute it and/or
31 | * modify it under the terms of the GNU General Public
32 | * License as published by the Free Software Foundation; either
33 | * version 2 of the License, or (at your option) any later version.
34 | *
35 | * This library is distributed in the hope that it will be useful,
36 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
37 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
38 | * General Public License for more details.
39 | *
40 | * You should have received a copy of the GNU General Public
41 | * License along with this library; if not, write to the Free Software
42 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
43 | */
44 | public class TunerWearableListenerService extends WearableListenerService {
45 | private final static String LOGTAG = "TunerWearableListenerService";
46 | public final static String GET_LOG_MESSAGE_PATH = "/getLog";
47 | public final static String GET_LOG_RESPONSE_MESSAGE_PATH = "/getLogResponse";
48 | private GoogleApiClient googleApiClient;
49 |
50 | @Override
51 | public void onCreate() {
52 | super.onCreate();
53 |
54 | // create a google api client instance. It will be connected and disconnected every time
55 | // sendMessageAsync() has to send a message.
56 | googleApiClient = new GoogleApiClient.Builder(this)
57 | .addApi(Wearable.API)
58 | .build();
59 | Log.i(LOGTAG, "onCreate: Service created!");
60 | }
61 |
62 | /**
63 | * Will be called from the Android System if a message was received.
64 | * We will check the type of the message and handle it correctly:
65 | * - if it is a get-log-message we collect the log data and send it back to the originator of the message
66 | * - if it is a sync-preference-message we pass it to the handler method of the PreferenceSyncHelper
67 | * @param messageEvent the received message
68 | */
69 | @Override
70 | public void onMessageReceived(MessageEvent messageEvent) {
71 | Log.i(LOGTAG, "onMessageReceived: received a message (" + messageEvent.getPath() + ") from "
72 | + messageEvent.getSourceNodeId());
73 | if (messageEvent.getPath().equals(GET_LOG_MESSAGE_PATH)) {
74 | try {
75 | // Read the log:
76 | Process process = Runtime.getRuntime().exec("logcat -d");
77 | BufferedReader bufferedReader = new BufferedReader(
78 | new InputStreamReader(process.getInputStream()));
79 |
80 | StringBuilder log=new StringBuilder();
81 | String line = "";
82 | String newline = System.getProperty("line.separator");
83 | while ((line = bufferedReader.readLine()) != null) {
84 | log.append(line);
85 | log.append(newline);
86 | }
87 |
88 | // Send it to the handheld device:
89 | sendMessageAsync(messageEvent.getSourceNodeId(), GET_LOG_RESPONSE_MESSAGE_PATH, log.toString().getBytes("UTF-8"));
90 | }
91 | catch (IOException e) {
92 | Log.e(LOGTAG, "onMessageReceived: Error while reading log: " + e.getMessage());
93 | }
94 | } else if(messageEvent.getPath().startsWith("/syncPref/")) {
95 | PreferenceSyncHelper.handleSyncMessage(PreferenceManager.getDefaultSharedPreferences(this).edit(),
96 | messageEvent.getPath(), messageEvent.getData());
97 | }
98 | }
99 |
100 | @Override
101 | public void onDataChanged(DataEventBuffer dataEvents) {
102 | super.onDataChanged(dataEvents);
103 | }
104 |
105 | /**
106 | * Sends a message asynchronously. If this method is called multiple times in a row, each message
107 | * will be send separately each after another but with undefined order!
108 | *
109 | * @param nodeID node id of the receiver
110 | * @param path path of this message
111 | * @param payload payload data of the message
112 | */
113 | private void sendMessageAsync(final String nodeID, final String path, final byte[] payload) {
114 | // run it in the background:
115 | Thread asyncThread = new Thread() {
116 | public void run() {
117 | Log.d(LOGTAG, "sendMessageAsync: Thread " + this.getName() + " started!");
118 |
119 | // only one thread at a time is allowed to use the googleApiClient:
120 | synchronized (this) {
121 | // connect the googleApiClient:
122 | googleApiClient.blockingConnect(1000, TimeUnit.MILLISECONDS);
123 | if(!googleApiClient.isConnected()) {
124 | Log.e(LOGTAG, "sendMessageAsync (Thread="+this.getName()+"): Can't connect to google API client! stop.");
125 | return;
126 | }
127 |
128 | // send the message:
129 | MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(
130 | googleApiClient, nodeID, path, payload).await(1000, TimeUnit.MILLISECONDS);
131 | if (!result.getStatus().isSuccess()) {
132 | Log.e(LOGTAG, "sendMessageAsync: Failed to send Message: " + result.getStatus());
133 | } else {
134 | Log.d(LOGTAG, "sendMessageAsync: Message " + result.getRequestId() + " was sent! (" + payload.length + " Byte)");
135 | }
136 |
137 | // disconnect the api client:
138 | googleApiClient.disconnect();
139 | }
140 | Log.d(LOGTAG, "sendMessageAsync: Thread " + this.getName() + " stopped!");
141 | }
142 | };
143 | asyncThread.start();
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/java/com/mantz_it/guitartunerlibrary/DebugTunerSkin.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.guitartunerlibrary;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Color;
5 | import android.graphics.Paint;
6 | import android.graphics.Rect;
7 |
8 | import java.text.DecimalFormat;
9 |
10 | /**
11 | *
Wear Guitar Tuner - Debug Tuner Skin
12 | *
13 | * Module: DebugTunerSkin.java
14 | * Description: This skin is used for debugging and testing. It shows the raw fft and HPS data.
15 | *
16 | * @author Dennis Mantz
17 | *
18 | * Copyright (C) 2014 Dennis Mantz
19 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
20 | *
21 | * This library is free software; you can redistribute it and/or
22 | * modify it under the terms of the GNU General Public
23 | * License as published by the Free Software Foundation; either
24 | * version 2 of the License, or (at your option) any later version.
25 | *
26 | * This library is distributed in the hope that it will be useful,
27 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
29 | * General Public License for more details.
30 | *
31 | * You should have received a copy of the GNU General Public
32 | * License along with this library; if not, write to the Free Software
33 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
34 | */
35 | public class DebugTunerSkin extends TunerSkin {
36 |
37 | protected Paint fftPaint;
38 |
39 | @Override
40 | public void updateWidthAndHeight(int width, int height) {
41 | super.updateWidthAndHeight(width, height);
42 | fftPaint = new Paint();
43 | fftPaint.setColor(Color.BLUE);
44 | foregroundPaint.setTextSize(height*0.1f);
45 | invalidPaint.setTextSize(height*0.1f);
46 | }
47 |
48 | @Override
49 | public void draw(Canvas c, GuitarTuner tuner) {
50 | // narrow to the range: 50Hz-500Hz:
51 | int startFrequency = 50;
52 | int endFrequency = 500;
53 | int startIndex = (int) (startFrequency / tuner.getHzPerSample());
54 | int endIndex = (int) (endFrequency / tuner.getHzPerSample());
55 | float samplesPerPx = (float) (endIndex - startIndex) / (float) width; // number of fft samples per one pixel
56 | float hzPerPx = tuner.getHzPerSample() * samplesPerPx; // frequency span (in Hz) of one pixel
57 |
58 | // Clear the canvas
59 | c.drawRect(0, 0, width, height, backgroundPaint);
60 |
61 |
62 | drawSpectrum(c, tuner.isValid() ? fftPaint : invalidPaint, tuner.getMag(), startIndex, endIndex, -9f, -2f, tuner.getHzPerSample());
63 | drawSpectrum(c, tuner.isValid() ? highlightPaint : invalidPaint, tuner.getHPS(), startIndex, endIndex, -35f, -15f, tuner.getHzPerSample());
64 |
65 | // Draw detected (relevant) frequency component and pitch + debug info
66 | if (tuner.getDetectedFrequency() > 0) {
67 | float detectedFrequency = tuner.getDetectedFrequency();
68 | int pitchIndex = tuner.getTargetPitchIndex();
69 | Paint paint = tuner.isValid() ? foregroundPaint : invalidPaint;
70 |
71 | // draw a line at the detected pitch:
72 | int frequencyPosition = (int) ((detectedFrequency - startFrequency) / hzPerPx);
73 | c.drawLine(frequencyPosition, 0, frequencyPosition, height, paint);
74 |
75 | // draw frequency (in hz)
76 | float yPos = height * 0.3f;
77 | String text = new DecimalFormat("###.# Hz").format(detectedFrequency);
78 | Rect bounds = new Rect();
79 | paint.getTextBounds(text, 0, text.length(), bounds);
80 | int labelPosition = frequencyPosition <= width / 2 ? frequencyPosition + 5 : frequencyPosition - bounds.width() - 5;
81 | c.drawText(text, 0, text.length(), labelPosition, yPos, paint);
82 |
83 | // draw pitch in letters
84 | yPos += bounds.height() * 1.1f;
85 | text = tuner.pitchLetterFromIndex(pitchIndex) + new DecimalFormat(" (###.# Hz)").format(tuner.pitchIndexToFrequency(pitchIndex));
86 | paint.getTextBounds(text, 0, text.length(), bounds);
87 | labelPosition = frequencyPosition <= width / 2 ? frequencyPosition + 5 : frequencyPosition - bounds.width() - 5;
88 | c.drawText(text, 0, text.length(), labelPosition, yPos, paint);
89 | }
90 | }
91 |
92 | /**
93 | * Draws the given samples as spectrum (fft) on the canvas.
94 | *
95 | * @param c canvas to draw on
96 | * @param paint paint instance that will be used for drawing
97 | * @param values sample values (fft data)
98 | * @param start first index in values that should be drawn
99 | * @param end last index in values that should be drawn
100 | * @param minDB lowest dB value on the vertical scale
101 | * @param maxDB highest dB value on the vertical scale
102 | * @param hzPerSample width (in Hz) of one FFT bin (one index) in the values array
103 | */
104 | private void drawSpectrum(Canvas c, Paint paint, float[] values, int start, int end, float minDB, float maxDB, float hzPerSample) {
105 | float previousY = height; // y coordinate of the previously processed pixel
106 | float currentY; // y coordinate of the currently processed pixel
107 | float samplesPerPx = (float) (end - start) / (float) width; // number of fft samples per one pixel
108 | float dbDiff = maxDB - minDB;
109 | float dbWidth = height / dbDiff; // Size (in pixel) per 1dB in the fft
110 | float avg; // Used to calculate the average of multiple values in mag (horizontal average)
111 | int counter; // Used to calculate the average of multiple values in mag
112 |
113 | // Draw FFT pixel by pixel:
114 | // We start at 1 because of integer round off error
115 | for (int i = 1; i < width; i++) {
116 | // Calculate the average value for this pixel (horizontal average - not the time domain average):
117 | avg = 0;
118 | counter = 0;
119 | for (int j = (int)(i*samplesPerPx); j < (i+1)*samplesPerPx; j++) {
120 | avg += values[j + start];
121 | counter++;
122 | }
123 | avg = avg / counter;
124 |
125 | // FFT:
126 | currentY = height - (avg - minDB) * dbWidth;
127 | if(currentY < 0 )
128 | currentY = 0;
129 | if(currentY > height)
130 | currentY = height;
131 |
132 | c.drawLine(i-1,previousY,i,currentY, paint);
133 | previousY = currentY;
134 |
135 | // We have to draw the last line to the bottom if we're in the last round:
136 | if(i+1 == width)
137 | c.drawLine(i,previousY,i+1,height, paint);
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/java/com/mantz_it/guitartunerlibrary/TunerSurface.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.guitartunerlibrary;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.util.AttributeSet;
6 | import android.util.Log;
7 | import android.view.SurfaceHolder;
8 | import android.view.SurfaceView;
9 |
10 | /**
11 | *
Wear Guitar Tuner - Tuner Surface
12 | *
13 | * Module: TunerSurface.java
14 | * Description: This class implements the GuitarTunerCallbackInterface and extends the SurfaceView.
15 | * It calls the draw method of the current TunerSkin to draw the UI with the results from the GuitarTuner.
16 | *
17 | * @author Dennis Mantz
18 | *
19 | * Copyright (C) 2014 Dennis Mantz
20 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
21 | *
22 | * This library is free software; you can redistribute it and/or
23 | * modify it under the terms of the GNU General Public
24 | * License as published by the Free Software Foundation; either
25 | * version 2 of the License, or (at your option) any later version.
26 | *
27 | * This library is distributed in the hope that it will be useful,
28 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
30 | * General Public License for more details.
31 | *
32 | * You should have received a copy of the GNU General Public
33 | * License along with this library; if not, write to the Free Software
34 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
35 | */
36 | public class TunerSurface extends SurfaceView implements GuitarTuner.GuitarTunerCallbackInterface, SurfaceHolder.Callback {
37 | private static final String LOGTAG = "TunerSurface";
38 | private TunerSkin tunerSkin; // skin that does the drawing
39 | private int width = -1; // current width of the surface
40 | private int height = -1; // current height of the surface
41 | private boolean round; // indicates if the surface has a round shape
42 |
43 | /**
44 | * constructor.
45 | *
46 | * @param context // application context
47 | * @param attributeSet // used by the Android system to pass attributes to the view
48 | */
49 | public TunerSurface(Context context, AttributeSet attributeSet) {
50 | super(context, attributeSet);
51 | // Add a Callback to get informed when the dimensions of the SurfaceView changes:
52 | this.getHolder().addCallback(this);
53 | }
54 |
55 | @Override
56 | public void surfaceCreated(SurfaceHolder holder) {
57 | }
58 |
59 | @Override
60 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
61 | this.width = width;
62 | this.height = height;
63 | if(tunerSkin != null)
64 | tunerSkin.updateWidthAndHeight(width, height);
65 | }
66 |
67 | @Override
68 | public void surfaceDestroyed(SurfaceHolder holder) {
69 | }
70 |
71 | public void setRound(boolean round) {
72 | this.round = round;
73 | if(tunerSkin != null)
74 | tunerSkin.setRound(round);
75 | }
76 |
77 | public void setTunerSkin(TunerSkin skin) {
78 | this.tunerSkin = skin;
79 | tunerSkin.updateWidthAndHeight(width, height);
80 | tunerSkin.setRound(round);
81 | }
82 |
83 | @Override
84 | public boolean process(GuitarTuner guitarTuner) {
85 | if(!this.getHolder().getSurface().isValid()) {
86 | Log.d(LOGTAG, "process: Surface is not valid!");
87 | return false;
88 | }
89 |
90 | if(height < 0 || width < 0) {
91 | Log.d(LOGTAG, "process: height and width are not yet set!");
92 | return false;
93 | }
94 |
95 | if(tunerSkin == null) {
96 | Log.d(LOGTAG, "process: tunerSkin is null!");
97 | return false;
98 | }
99 |
100 | if(tunerSkin.isAnimationEnabled())
101 | animatedDraw(guitarTuner);
102 | else
103 | draw(guitarTuner);
104 | return true;
105 | }
106 |
107 | /**
108 | * uses the tunerSkin to draw the results of the current cycle without animations
109 | * @param guitarTuner GuitarTuner instance holding the latest results
110 | */
111 | private void draw(GuitarTuner guitarTuner) {
112 | Canvas c = null;
113 | try {
114 | c = this.getHolder().lockCanvas();
115 |
116 | synchronized (this.getHolder()) {
117 | if(c != null) {
118 | // Draw
119 | tunerSkin.draw(c, guitarTuner);
120 | } else
121 | Log.d(LOGTAG, "draw: Canvas is null.");
122 | }
123 | } catch (Exception e)
124 | {
125 | Log.e(LOGTAG, "draw: Error while drawing on the canvas. Stop!");
126 | e.printStackTrace();
127 | } finally {
128 | if (c != null) {
129 | this.getHolder().unlockCanvasAndPost(c);
130 | }
131 | }
132 | }
133 |
134 | /**
135 | * uses the tunerSkin to draw the results of the current cycle. Might draw multiple times
136 | * in order to animate the transition from the old results to the new ones.
137 | *
138 | * @param guitarTuner GuitarTuner instance holding the latest results (and the old ones)
139 | */
140 | private void animatedDraw(GuitarTuner guitarTuner) {
141 | float updateRate = guitarTuner.getUpdateRate();
142 | int framesToDraw = (int)(tunerSkin.getDesiredRefreshRate() / updateRate + 1);
143 | int millisPerCycle = (int) (1000 / updateRate);
144 | int millisPerFrame = millisPerCycle / framesToDraw;
145 | long frameStartTime = guitarTuner.getLastUpdateTimestamp();
146 |
147 | for(int i = 0; i < framesToDraw; i++) {
148 | // Check if we exceeded the time for the current cycle
149 | if(System.currentTimeMillis() > guitarTuner.getLastUpdateTimestamp() + millisPerCycle) {
150 | Log.d(LOGTAG, "animatedDraw: Exceeded cycle time during animation!");
151 | return;
152 | }
153 |
154 | // Draw Frame
155 | Canvas c = null;
156 | try {
157 | c = this.getHolder().lockCanvas();
158 |
159 | synchronized (this.getHolder()) {
160 | if(c != null) {
161 | // Draw
162 | tunerSkin.draw(c, guitarTuner, i, framesToDraw);
163 | } else
164 | Log.d(LOGTAG, "animatedDraw: Canvas is null.");
165 | }
166 | } catch (Exception e)
167 | {
168 | Log.e(LOGTAG, "animatedDraw: Error while drawing on the canvas: " + e.getMessage());
169 | } finally {
170 | if (c != null) {
171 | this.getHolder().unlockCanvasAndPost(c);
172 | }
173 | }
174 |
175 | // Sleep til the next frame starts:
176 | int sleepTime = (int) (frameStartTime + millisPerFrame - System.currentTimeMillis());
177 | if(sleepTime > 0) {
178 | try {
179 | //Log.d(LOGTAG, "animatedDraw: Sleep for " + sleepTime + "ms after Frame " + i);
180 | Thread.sleep(sleepTime);
181 | } catch (InterruptedException e) {
182 | Log.d(LOGTAG, "animatedDraw: Interrupted while waiting for the next frame: " + e.getMessage());
183 | return;
184 | }
185 | }
186 | frameStartTime += millisPerFrame;
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/java/com/mantz_it/guitartunerlibrary/TunerSkin.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.guitartunerlibrary;
2 |
3 | import android.app.Activity;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.util.Log;
8 |
9 | /**
10 | *
Wear Guitar Tuner - Tuner Skin
11 | *
12 | * Module: TunerSkin.java
13 | * Description: This is the base class for skins. Skins define how to draw the
14 | * tuner UI and hold all necessary objects needed for this job.
15 | * The surface will call the draw method of a skin and pass in the
16 | * canvas of the surface.
17 | *
18 | * @author Dennis Mantz
19 | *
20 | * Copyright (C) 2014 Dennis Mantz
21 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
22 | *
23 | * This library is free software; you can redistribute it and/or
24 | * modify it under the terms of the GNU General Public
25 | * License as published by the Free Software Foundation; either
26 | * version 2 of the License, or (at your option) any later version.
27 | *
28 | * This library is distributed in the hope that it will be useful,
29 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
31 | * General Public License for more details.
32 | *
33 | * You should have received a copy of the GNU General Public
34 | * License along with this library; if not, write to the Free Software
35 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
36 | */
37 | public abstract class TunerSkin {
38 | protected Paint backgroundPaint; // default paint for the background (black)
39 | protected Paint foregroundPaint; // default paint for the foreground (white)
40 | protected Paint highlightPaint; // paint to highlight (red)
41 | protected Paint invalidPaint; // default paint for invalid content (grey)
42 |
43 | protected int width; // surface width in px
44 | protected int height; // surface height in px
45 | protected boolean round = false; // indicates if the screen is round or rectangular
46 | protected int desiredRefreshRate = 30; // refreshRate of the Surface in fps (if animation is enabled)
47 | protected boolean animationEnabled = false; // indicates if the skin supports animation
48 |
49 | /**
50 | * constructor.
51 | */
52 | public TunerSkin() {
53 | // Initialize paint objects:
54 | backgroundPaint = new Paint();
55 | backgroundPaint.setColor(Color.BLACK);
56 | foregroundPaint = new Paint();
57 | foregroundPaint.setColor(Color.WHITE);
58 | foregroundPaint.setAntiAlias(true);
59 | highlightPaint = new Paint();
60 | highlightPaint.setColor(Color.RED);
61 | highlightPaint.setAntiAlias(true);
62 | invalidPaint = new Paint();
63 | invalidPaint.setColor(Color.GRAY);
64 | invalidPaint.setAntiAlias(true);
65 | }
66 |
67 | /**
68 | * This method will be called by the surface every time the dimensions change
69 | * @param width new width of the surface (in px)
70 | * @param height new height of the surface (in px)
71 | */
72 | public void updateWidthAndHeight(int width, int height) {
73 | this.width = width;
74 | this.height = height;
75 | }
76 |
77 | /**
78 | * This method will be called by the surface if the screen shape changes
79 | * @param round true if the new screen shape is round. false if it is rectangular
80 | */
81 | public void setRound(boolean round) {
82 | this.round = round;
83 | }
84 |
85 | public void setDesiredRefreshRate(int refreshRateInMs) {
86 | this.desiredRefreshRate = refreshRateInMs;
87 | }
88 |
89 | public int getDesiredRefreshRate() {
90 | return desiredRefreshRate;
91 | }
92 |
93 | public boolean isAnimationEnabled() {
94 | return animationEnabled && desiredRefreshRate > 0;
95 | }
96 |
97 | /**
98 | * This method will be called by the surface if a new frame (with new tuner results) should be drawn
99 | * and animation is disabled.
100 | * @param c canvas to draw
101 | * @param tuner GuitarTuner instance containing the latest results
102 | */
103 | public abstract void draw(Canvas c, GuitarTuner tuner);
104 |
105 | /**
106 | * This method will be called by the surface if a new animated frame should be drawn.
107 | * Depending on the desired frame rate and the rate at which the tuner delivers new results,
108 | * this method will be called multiple times with the same results in order to animate between
109 | * the results.
110 | * @param c canvas to draw
111 | * @param tuner GuitarTuner instance containing the latest (and the old) results
112 | * @param frameNumber current frame number within this animation cycle
113 | * @param framesPerCycle total number of animation frames for this cycle
114 | */
115 | public void draw(Canvas c, GuitarTuner tuner, int frameNumber, int framesPerCycle) {
116 | Log.w("TunerSkin", "draw: Animated draw is not supported by this skin!");
117 | draw(c, tuner);
118 | }
119 |
120 |
121 | // STATIC methods for easy handling of all available skins:
122 |
123 | /**
124 | * @return total number of available tuner skins
125 | */
126 | public static int getTunerSkinCount() {
127 | return 3;
128 | }
129 |
130 | /**
131 | * Will instantiate a TunerSkin object
132 | * @param skinIndex index of the skin that should be instantiated
133 | * @param activity activity instance (e.g. needed to access resources)
134 | * @return a new instance of the desired tuner skin
135 | */
136 | public static TunerSkin getTunerSkinInstance(int skinIndex, Activity activity) {
137 | switch (skinIndex) {
138 | case 0: return new DefaultTunerSkin();
139 | case 1: return new VintageNeedleTunerSkin(activity.getResources());
140 | case 2: return new DebugTunerSkin();
141 | default: return null;
142 | }
143 | }
144 |
145 | /**
146 | * Will extract a thumbnail resource id for the desired tuner skin
147 | * @param skinIndex index of the skin
148 | * @param round if true, this method will return a round thumbnail
149 | * @return a drawable resource id of the correct thumbnail
150 | */
151 | public static int getTunerSkinThumbnailResource(int skinIndex, boolean round) {
152 | switch (skinIndex) {
153 | case 0:
154 | return round ? R.drawable.thumbnail_default_skin_round : R.drawable.thumbnail_default_skin_rect;
155 | case 1:
156 | return round ? R.drawable.thumbnail_vintage_needle_skin_round : R.drawable.thumbnail_vintage_needle_skin_rect;
157 | case 2:
158 | return round ? R.drawable.thumbnail_debug_skin_round : R.drawable.thumbnail_debug_skin_rect;
159 | default:
160 | return -1;
161 | }
162 | }
163 |
164 | /**
165 | * Returns the name (label) of the desired tuner skin
166 | * @param skinIndex index of the skin
167 | * @return String containing the human readable label of the skin
168 | */
169 | public static String getTunerSkinName(int skinIndex) {
170 | switch (skinIndex) {
171 | case 0: return "Default Skin";
172 | case 1: return "Vintage Needle Skin";
173 | case 2: return "Debug Skin";
174 | default: return null;
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/mobile/mobile.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/wear/wear.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/guitartunerlibrary/guitartunerlibrary.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/java/com/mantz_it/guitartunerlibrary/VintageNeedleTunerSkin.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.guitartunerlibrary;
2 |
3 | import android.content.res.Resources;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.LinearGradient;
9 | import android.graphics.Shader;
10 |
11 | /**
12 | *
Wear Guitar Tuner - Vintage Needle Tuner Skin
13 | *
14 | * Module: VintageNeedleTunerSkin.java
15 | * Description: This is nice skin with a vintage look and a needle. It is based on the default skin
16 | *
17 | * @author Dennis Mantz
18 | *
19 | * Copyright (C) 2014 Dennis Mantz
20 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
21 | *
22 | * This library is free software; you can redistribute it and/or
23 | * modify it under the terms of the GNU General Public
24 | * License as published by the Free Software Foundation; either
25 | * version 2 of the License, or (at your option) any later version.
26 | *
27 | * This library is distributed in the hope that it will be useful,
28 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
30 | * General Public License for more details.
31 | *
32 | * You should have received a copy of the GNU General Public
33 | * License along with this library; if not, write to the Free Software
34 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
35 | */
36 | public class VintageNeedleTunerSkin extends DefaultTunerSkin {
37 |
38 | private Resources resources; // resources instance to load bitmaps
39 | private Bitmap scaledBackground; // will hold the background bitmap scaled to the surface dimensions
40 |
41 | /**
42 | * constructor.
43 | * @param resources resources instance to load bitmaps
44 | */
45 | public VintageNeedleTunerSkin(Resources resources) {
46 | super();
47 | this.resources = resources;
48 | sideLettersPosition = 0.45f; // bring the side letters closer to the center
49 | maxAngle = 0.6f; // also narrow the scale to fit inside the window of the background image
50 | }
51 |
52 | @Override
53 | public void updateWidthAndHeight(int width, int height) {
54 | super.updateWidthAndHeight(width, height);
55 | foregroundPaint.setTextSize(height * 0.15f);
56 | invalidPaint.setTextSize(height * 0.15f);
57 | highlightPaint.setTextSize(height * 0.15f);
58 | gradientPaint.setTextSize(height * 0.12f);
59 | gradientPaint.setShader(new LinearGradient(width/5, 0, width / 2, 0, Color.DKGRAY, Color.LTGRAY, Shader.TileMode.MIRROR));
60 |
61 | // Load skin background and scale it to the surface dimensions
62 | loadBackground();
63 | }
64 |
65 | @Override
66 | public void setRound(boolean round) {
67 | super.setRound(round);
68 |
69 | // Load skin background and scale it to the surface dimensions
70 | loadBackground();
71 | }
72 |
73 | /**
74 | * Loads the background bitmap and scales it according to the surface dimensions
75 | */
76 | private void loadBackground() {
77 | if(width > 0 && height > 0) {
78 | if(round)
79 | scaledBackground = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(resources, R.drawable.vintage_tuner_skin_round), width, height, false);
80 | else
81 | scaledBackground = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(resources, R.drawable.vintage_tuner_skin_rect), width, height, false);
82 | }
83 | }
84 |
85 | @Override
86 | public void draw(Canvas c, GuitarTuner tuner) {
87 | draw(c, tuner, 0, 1);
88 | }
89 |
90 | @Override
91 | public void draw(Canvas c, GuitarTuner tuner, int frameNumber, int framesPerCycle) {
92 | // for detailed comments refer to the draw() implementation in DefaultTunerSkin!
93 |
94 | // Clear the canvas
95 | c.drawRect(0, 0, width, height, backgroundPaint);
96 |
97 | // draw scale (21 dashes)
98 | drawScale(c);
99 |
100 | // only draw pitch letters and needle if data is valid
101 | if(tuner.isValid()) {
102 | float targetFrequency = tuner.getTargetFrequency();
103 | float lastTargetFrequency = tuner.getLastTargetFrequency();
104 | int targetPitchIndex = tuner.getTargetPitchIndex();
105 | int lastTargetPitchIndex = tuner.frequencyToPitchIndex(lastTargetFrequency);
106 |
107 | // draw pitch letters
108 | float letterOffset = 0;
109 | if(targetPitchIndex == lastTargetPitchIndex + 1)
110 | letterOffset = -((frameNumber+1)/(float)framesPerCycle) * sideLettersPosition; // shift letters to the left
111 | else if (targetPitchIndex == lastTargetPitchIndex - 1)
112 | letterOffset = ((frameNumber+1)/(float)framesPerCycle) * sideLettersPosition; // shift letters to the right
113 | else if (targetPitchIndex != lastTargetPitchIndex) {
114 | float tmp = 2f*frameNumber/(float)framesPerCycle - 1;
115 | int alpha = (int) (255*tmp*tmp);
116 | gradientPaint.setAlpha(alpha); // fade the old letters out and the new ones in
117 | foregroundPaint.setAlpha(alpha); // also fade the needle
118 | }
119 |
120 | String centerLetter;
121 | String leftLetter;
122 | String rightLetter;
123 | if(frameNumber < (float)framesPerCycle/2) {
124 | centerLetter = tuner.pitchLetterFromIndex(lastTargetPitchIndex);
125 | leftLetter = tuner.pitchLetterFromIndex(lastTargetPitchIndex - 1);
126 | rightLetter = tuner.pitchLetterFromIndex(lastTargetPitchIndex + 1);
127 | } else {
128 | centerLetter = tuner.pitchLetterFromIndex(targetPitchIndex);
129 | leftLetter = tuner.pitchLetterFromIndex(targetPitchIndex - 1);
130 | rightLetter = tuner.pitchLetterFromIndex(targetPitchIndex + 1);
131 | if(letterOffset > 0)
132 | letterOffset -= sideLettersPosition;
133 | else if (letterOffset < 0)
134 | letterOffset += sideLettersPosition;
135 | }
136 | drawPitchLetter(c, centerLetter, letterOffset, 0.48f, true, gradientPaint);
137 | drawPitchLetter(c, leftLetter, letterOffset - sideLettersPosition, 0.48f, true, gradientPaint);
138 | drawPitchLetter(c, rightLetter, letterOffset + sideLettersPosition, 0.48f, true, gradientPaint);
139 |
140 | // draw needle
141 | float newAngle = (float) (maxAngle / (Math.pow(2,1/24f) - 1) * (tuner.getDetectedFrequency() / targetFrequency - 1));
142 | float oldAngle = (float) (maxAngle / (Math.pow(2,1/24f) - 1) * (tuner.getLastDetectedFrequency() / lastTargetFrequency - 1));
143 | float animationSpan = newAngle - oldAngle; // we animate between the old angle and the new one...
144 | if(targetPitchIndex > lastTargetPitchIndex && targetPitchIndex-lastTargetPitchIndex != 12)
145 | animationSpan += 2* maxAngle; // animate from old angle to top of scale and from the bottom of the scale to the new angle
146 | else if (targetPitchIndex < lastTargetPitchIndex && lastTargetPitchIndex-targetPitchIndex != 12)
147 | animationSpan -= 2* maxAngle; // animate from old angle to bottom of scale and from the top of the scale to the new angle
148 | float angle = oldAngle + ((frameNumber+1)/(float)framesPerCycle) * animationSpan;
149 | if(angle > maxAngle)
150 | angle = angle - 2 * maxAngle;
151 | else if(angle < -maxAngle)
152 | angle = angle + 2 * maxAngle;
153 | drawNeedle(c, angle, tuner.isTuned() ? highlightPaint : foregroundPaint);
154 |
155 | gradientPaint.setAlpha(255); // reset alpha to default
156 | foregroundPaint.setAlpha(255);
157 | }
158 |
159 | // draw the background over the canvas :
160 | if(scaledBackground != null)
161 | c.drawBitmap(scaledBackground, 0, 0, backgroundPaint);
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/mantz_it/wearguitartuner/IntroActivity.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.wearguitartuner;
2 |
3 | import android.content.Intent;
4 | import android.content.SharedPreferences;
5 | import android.os.Bundle;
6 | import android.preference.PreferenceManager;
7 | import android.support.v4.app.Fragment;
8 | import android.support.v4.app.FragmentActivity;
9 | import android.support.v4.app.FragmentManager;
10 | import android.support.v4.app.FragmentStatePagerAdapter;
11 | import android.support.v4.view.PagerAdapter;
12 | import android.support.v4.view.ViewPager;
13 | import android.view.LayoutInflater;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.widget.Button;
17 | import android.widget.ImageView;
18 | import android.widget.TextView;
19 |
20 | /**
21 | *
Wear Guitar Tuner - Intro Activity
22 | *
23 | * Module: IntroActivity.java
24 | * Description: Intro Activity that is only invoked on first application start. It shows
25 | * tutorial slides.
26 | *
27 | * @author Dennis Mantz
28 | *
29 | * Copyright (C) 2014 Dennis Mantz
30 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
31 | *
32 | * This library is free software; you can redistribute it and/or
33 | * modify it under the terms of the GNU General Public
34 | * License as published by the Free Software Foundation; either
35 | * version 2 of the License, or (at your option) any later version.
36 | *
37 | * This library is distributed in the hope that it will be useful,
38 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
39 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
40 | * General Public License for more details.
41 | *
42 | * You should have received a copy of the GNU General Public
43 | * License along with this library; if not, write to the Free Software
44 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
45 | */
46 | public class IntroActivity extends FragmentActivity implements ViewPager.OnPageChangeListener {
47 |
48 | private static final int NUM_PAGES = 3; // total number of slides
49 | private ViewPager pager;
50 | private Button bt_skip;
51 | private Button bt_next;
52 | private PagerAdapter pagerAdapter;
53 |
54 | @Override
55 | protected void onCreate(Bundle savedInstanceState) {
56 | super.onCreate(savedInstanceState);
57 | setContentView(R.layout.activity_intro);
58 | pager = (ViewPager) findViewById(R.id.vp_intro_pager);
59 | bt_skip = (Button) findViewById(R.id.bt_intro_skip);
60 | bt_next = (Button) findViewById(R.id.bt_intro_next);
61 |
62 | // Instantiate a PagerAdapter.
63 | pagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
64 | pager.setAdapter(pagerAdapter);
65 | pager.setOnPageChangeListener(this);
66 | }
67 |
68 | public void onBtSkipClicked(View view) {
69 | endIntro();
70 | }
71 |
72 | public void onBtNextClicked(View view) {
73 | if(pager.getCurrentItem() < NUM_PAGES - 1) {
74 | pager.setCurrentItem(pager.getCurrentItem() + 1);
75 | }
76 | else {
77 | endIntro();
78 | }
79 | }
80 |
81 | /**
82 | * Will set the flag to not show this intro again and then start the main activity
83 | */
84 | public void endIntro() {
85 | // update the value in the preferences:
86 | SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(this).edit();
87 | edit.putBoolean(getString(R.string.pref_mainActivityFirstStart), false);
88 | edit.apply();
89 |
90 | // Start the main activity:
91 | Intent intent = new Intent(this, MainActivity.class);
92 | startActivity(intent);
93 | finish();
94 | }
95 |
96 | /**
97 | * called by the Android System if the user presses the back button.
98 | */
99 | @Override
100 | public void onBackPressed() {
101 | if (pager.getCurrentItem() == 0) {
102 | // If the user is currently looking at the first page, allow the system to handle the
103 | // Back button. This calls finish() on this activity and pops the back stack.
104 | super.onBackPressed();
105 | } else {
106 | // Otherwise, select the previous page.
107 | pager.setCurrentItem(pager.getCurrentItem() - 1);
108 | }
109 | }
110 |
111 | // OnPageChangeListener
112 | @Override
113 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
114 |
115 | @Override
116 | public void onPageSelected(int position) {
117 | if(position == NUM_PAGES - 1) { // last page
118 | bt_skip.setVisibility(View.GONE);
119 | bt_next.setText(getString(R.string.finish));
120 | } else {
121 | bt_skip.setVisibility(View.VISIBLE);
122 | bt_next.setText(getString(R.string.next));
123 | }
124 | }
125 |
126 | @Override
127 | public void onPageScrollStateChanged(int state) {}
128 | // /OnPageChangeListener
129 |
130 | /**
131 | * A simple PageAdapter used with the ViewPager. Will create, fill and return
132 | * ScreenSlidePageFragments according treturno the selected page.
133 | */
134 | private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
135 | public ScreenSlidePagerAdapter(FragmentManager fm) {
136 | super(fm);
137 | }
138 |
139 | @Override
140 | public Fragment getItem(int position) {
141 | ScreenSlidePageFragment fragment = new ScreenSlidePageFragment();
142 | switch (position) {
143 | case 0:
144 | fragment.setHeading(getString(R.string.intro_page_0_heading));
145 | fragment.setText(getString(R.string.intro_page_0_text));
146 | fragment.setImageResource(R.drawable.ic_launcher);
147 | break;
148 | case 1:
149 | fragment.setHeading(getString(R.string.intro_page_1_heading));
150 | fragment.setText(getString(R.string.intro_page_1_text));
151 | fragment.setImageResource(R.drawable.ic_launcher);
152 | break;
153 | case 2:
154 | fragment.setHeading(getString(R.string.intro_page_2_heading));
155 | fragment.setText(getString(R.string.intro_page_2_text));
156 | fragment.setImageResource(R.drawable.ic_launcher);
157 | break;
158 | }
159 | return fragment;
160 | }
161 |
162 | @Override
163 | public int getCount() {
164 | return NUM_PAGES;
165 | }
166 | }
167 |
168 | /**
169 | * This fragment is uses as a slide in the ViewPager. It consists of a heading, a text and
170 | * a picture.
171 | */
172 | public static class ScreenSlidePageFragment extends Fragment {
173 | private ImageView iv_image = null;
174 | private TextView tv_text = null;
175 | private TextView tv_heading = null;
176 | private int imageResource = -1;
177 | private String text = null;
178 | private String heading = null;
179 |
180 | @Override
181 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
182 | ViewGroup root = (ViewGroup) inflater.inflate(R.layout.intro_page, container, false);
183 | iv_image = (ImageView) root.findViewById(R.id.iv_intro_page_image);
184 | tv_text = (TextView) root.findViewById(R.id.tv_intro_page_text);
185 | tv_heading = (TextView) root.findViewById(R.id.tv_intro_page_heading);
186 | if(imageResource >= 0)
187 | iv_image.setImageResource(imageResource);
188 | if(text != null)
189 | tv_text.setText(text);
190 | if(heading != null)
191 | tv_heading.setText(heading);
192 | return root;
193 | }
194 |
195 | public void setImageResource(int res) {
196 | this.imageResource = res;
197 | if(iv_image != null)
198 | iv_image.setImageResource(res);
199 | }
200 |
201 | public void setText(String text) {
202 | this.text = text;
203 | if(tv_text != null)
204 | tv_text.setText(text);
205 | }
206 |
207 | public void setHeading(String heading) {
208 | this.heading = heading;
209 | if(tv_heading != null)
210 | tv_heading.setText(heading);
211 | }
212 | }
213 | }
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/java/com/mantz_it/guitartunerlibrary/AudioProcessingEngine.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.guitartunerlibrary;
2 |
3 | import android.media.AudioFormat;
4 | import android.media.AudioRecord;
5 | import android.media.MediaRecorder;
6 | import android.util.Log;
7 |
8 | /**
9 | *
Wear Guitar Tuner - Audio Processing Engine
10 | *
11 | * Module: AudioProcessingEngine.java
12 | * Description: This class will record audio from the device's microphone and
13 | * compute the FFT in real time. The data will be forwarded to
14 | * the Guitar Tuner class.
15 | *
16 | * @author Dennis Mantz
17 | *
18 | * Copyright (C) 2014 Dennis Mantz
19 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
20 | *
21 | * This library is free software; you can redistribute it and/or
22 | * modify it under the terms of the GNU General Public
23 | * License as published by the Free Software Foundation; either
24 | * version 2 of the License, or (at your option) any later version.
25 | *
26 | * This library is distributed in the hope that it will be useful,
27 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
29 | * General Public License for more details.
30 | *
31 | * You should have received a copy of the GNU General Public
32 | * License along with this library; if not, write to the Free Software
33 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
34 | */
35 | public class AudioProcessingEngine extends Thread{
36 | private static final String LOGTAG = "AudioProcessingEngine";
37 | private static final int RECORDER_SAMPLERATE = 8000;
38 | private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
39 | private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
40 | private static final int RECORDER_ELEMENT_SIZE = 2; // 16-bit
41 | private static final int BUFFER_SIZE = 1024 * 4; // 4k buffer will need half a sec to fill at 8000Sps
42 | private static final int FFT_SIZE = 1024 * 32; // make the fft size much bigger to get a higher resolution in frequency domain
43 | private float[] lookupTable; // used to convert shorts to floats
44 | private short[] audioBuffer; // the buffer which is filled with audio samples
45 | private float[] realSamples; // buffer to keep the real values of the complex signal
46 | private float[] imagSamples; // buffer to keep the imaginary values of the complex signal
47 | private float[] mag; // buffer to keep the magnitude of the frequency spectrum (fft)
48 | private AudioRecord audioRecord;
49 | private FFT fftInstance;
50 | private GuitarTuner guitarTuner;
51 |
52 | private boolean stopRequested = true;
53 | private int failCounter = 0; // will count how often the call to processFFTSamples()
54 | // failed in a row
55 |
56 | /**
57 | * Constructor.
58 | *
59 | * @param guitarTuner guitar tuner instance that receive the results of this audio processing engine
60 | */
61 | public AudioProcessingEngine(GuitarTuner guitarTuner) {
62 | this.guitarTuner = guitarTuner;
63 | createLookupTable();
64 | fftInstance = new FFT(FFT_SIZE);
65 | }
66 |
67 | /**
68 | * Will initialize the lookup table (short to float) and populate it correctly
69 | */
70 | private void createLookupTable() {
71 | lookupTable = new float[65536];
72 | for (int i = 0; i < lookupTable.length; i++)
73 | lookupTable[i] = (i - 32768f) / 32768f;
74 | }
75 |
76 | /**
77 | * Will convert a 16-bit short array to a float array. Uses the lookup table which has to be created first!
78 | * If the size of the arrays differ, the small array will be processed completely and the big one not!
79 | *
80 | * @param in 16-bit short array (containing the PCM samples)
81 | * @param out float array that will be filled with the result
82 | */
83 | public void short2float(short[] in, float[] out) {
84 | for (int i = 0; i < Math.min(in.length, out.length); i++)
85 | out[i] = lookupTable[in[i]+32768];
86 | }
87 |
88 | /**
89 | * Set the stopRequest flag. Will stop after the next cycle.
90 | */
91 | public void stopProcessing() {
92 | stopRequested = true;
93 | }
94 |
95 | /**
96 | * This method runs in a separate thread and does the audio recording and processing.
97 | * From within this method the processFFTSamples() method of the guitarTuner is called and every
98 | * work that is done in the guitarTuner is also assigned to this thread (this includes drawing
99 | * the results on the surface view!)
100 | */
101 | public void run() {
102 | float realPower;
103 | float imagPower;
104 | stopRequested = false;
105 | Log.i(LOGTAG, "run: AudioProcessingEngine '" + this.getName() + "' started.");
106 |
107 | // Determine buffer size for the audioRecord:
108 | int minBufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE,
109 | RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING);
110 | Log.d(LOGTAG, "constructor: min. buffer size is " + minBufferSize);
111 | int audioBufferSize = Math.max(minBufferSize, BUFFER_SIZE * RECORDER_ELEMENT_SIZE) * 2;
112 |
113 | // initialize the AudioRecord instance
114 | audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, RECORDER_SAMPLERATE, RECORDER_CHANNELS,
115 | RECORDER_AUDIO_ENCODING, audioBufferSize);
116 |
117 | // allocate the buffers:
118 | audioBuffer = new short[BUFFER_SIZE];
119 | realSamples = new float[FFT_SIZE];
120 | imagSamples = new float[FFT_SIZE];
121 | mag = new float[FFT_SIZE / 2];
122 |
123 | // Check if AudioRecord is correctly initialized:
124 | if(audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
125 | Log.e(LOGTAG, "run: audioRecord is null or not initialized! Abort!");
126 | stopRequested = true;
127 | return;
128 | }
129 |
130 | // Start recording:
131 | audioRecord.startRecording();
132 |
133 | while (!stopRequested) {
134 | // Read new audio samples into the buffer:
135 | if(audioRecord.read(audioBuffer, 0, audioBuffer.length) != audioBuffer.length) {
136 | Log.e(LOGTAG, "run: Error while reading from AudioRecord. stop.");
137 | stopRequested = true;
138 | break;
139 | }
140 | Log.d(LOGTAG, "run: audioBuffer: " + audioBuffer[0] + ", " + audioBuffer[1] + ", " + audioBuffer[2] + ", ..., " + audioBuffer[500]);
141 |
142 | // convert the shorts to floats and zero the imagSamples buffer:
143 | for (int i = 0; i < realSamples.length; i++) {
144 | realSamples[i] = 0f;
145 | imagSamples[i] = 0f;
146 | }
147 | short2float(audioBuffer, realSamples);
148 |
149 | // do the fft:
150 | fftInstance.applyWindow(realSamples, imagSamples);
151 | fftInstance.fft(realSamples, imagSamples);
152 |
153 | // calculate the logarithmic magnitude:
154 | // note: the spectrum is symetrical around zero Hz and we are only interested in the positive
155 | // part of it.
156 | for (int i = 0; i < realSamples.length / 2; i++) {
157 | // Calc the magnitude = log(sqrt(re^2 + im^2))
158 | // note that we still have to divide re and im by the fft size
159 | realPower = realSamples[i]/FFT_SIZE;
160 | realPower = realPower * realPower;
161 | imagPower = imagSamples[i]/FFT_SIZE;
162 | imagPower = imagPower * imagPower;
163 | mag[i] = (float) Math.log10(Math.sqrt(realPower + imagPower));
164 | }
165 |
166 | // pass the magnitude samples to the Guitar Tuner:
167 | if(!guitarTuner.processFFTSamples(mag, RECORDER_SAMPLERATE, (float)RECORDER_SAMPLERATE/(float)BUFFER_SIZE))
168 | failCounter++;
169 | else
170 | failCounter = 0;
171 |
172 | // We stop the thread if processFFTSamples() failed 10 times in a row.
173 | // Usually this happens if the surface view is not initialized/valid and the app
174 | // is in the background.
175 | if(failCounter > 10) {
176 | Log.w(LOGTAG, "run: Calling processFFTSamples() failed 10 times in a row. stop.");
177 | stopRequested = true;
178 | }
179 | }
180 |
181 | // Stop recording:
182 | audioRecord.stop();
183 | audioRecord.release();
184 |
185 | Log.i(LOGTAG, "run: AudioProcessingEngine '" + this.getName() + "' stopped");
186 | stopRequested = true;
187 | }
188 |
189 | }
190 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/java/com/mantz_it/guitartunerlibrary/DefaultTunerSkin.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.guitartunerlibrary;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Color;
5 | import android.graphics.LinearGradient;
6 | import android.graphics.Paint;
7 | import android.graphics.Rect;
8 | import android.graphics.Shader;
9 |
10 | /**
11 | *
Wear Guitar Tuner - Default Tuner Skin
12 | *
13 | * Module: DefaultTunerSkin.java
14 | * Description: This is a simple and very basic skin without any additional features.
15 | *
16 | * @author Dennis Mantz
17 | *
18 | * Copyright (C) 2014 Dennis Mantz
19 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
20 | *
21 | * This library is free software; you can redistribute it and/or
22 | * modify it under the terms of the GNU General Public
23 | * License as published by the Free Software Foundation; either
24 | * version 2 of the License, or (at your option) any later version.
25 | *
26 | * This library is distributed in the hope that it will be useful,
27 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
29 | * General Public License for more details.
30 | *
31 | * You should have received a copy of the GNU General Public
32 | * License along with this library; if not, write to the Free Software
33 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
34 | */
35 | public class DefaultTunerSkin extends TunerSkin {
36 |
37 | protected Paint gradientPaint;
38 | protected float maxAngle = 0.8f; // max angle of the scale (measured from the midpoint in radian)
39 | protected float sideLettersPosition = 0.7f; // position of the side pitch letters. 0 is the middle of the
40 | // screen and 1 is the left/right edge of the screen
41 |
42 | /**
43 | * constructor
44 | */
45 | public DefaultTunerSkin() {
46 | super();
47 | gradientPaint = new Paint();
48 | gradientPaint.setAntiAlias(true);
49 | animationEnabled = true; // this skin supports animation. The surface will call draw(Canvas, GuitarTuner, int, int)
50 | }
51 |
52 | @Override
53 | public void updateWidthAndHeight(int width, int height) {
54 | super.updateWidthAndHeight(width, height);
55 | foregroundPaint.setTextSize(height * 0.2f);
56 | invalidPaint.setTextSize(height * 0.2f);
57 | highlightPaint.setTextSize(height * 0.2f);
58 | gradientPaint.setTextSize(height * 0.2f);
59 | gradientPaint.setShader(new LinearGradient(0, 0, width / 2, 0, Color.DKGRAY, Color.LTGRAY, Shader.TileMode.MIRROR));
60 | }
61 |
62 | @Override
63 | public void setRound(boolean round) {
64 | super.setRound(round);
65 | }
66 |
67 | @Override
68 | public void draw(Canvas c, GuitarTuner tuner) {
69 | draw(c, tuner, 0, 1);
70 | }
71 |
72 | @Override
73 | public void draw(Canvas c, GuitarTuner tuner, int frameNumber, int framesPerCycle) {
74 | // Clear the canvas
75 | c.drawRect(0, 0, width, height, backgroundPaint);
76 |
77 | // draw scale (21 dashes)
78 | drawScale(c);
79 |
80 | // only draw pitch letters and needle if data is valid
81 | if(tuner.isValid()) {
82 | float targetFrequency = tuner.getTargetFrequency();
83 | float lastTargetFrequency = tuner.getLastTargetFrequency();
84 | int targetPitchIndex = tuner.getTargetPitchIndex();
85 | int lastTargetPitchIndex = tuner.frequencyToPitchIndex(lastTargetFrequency);
86 |
87 | // determine the horizontal offset of the position of the letters
88 | // (this depends on the current step within the animation.
89 | float letterOffset = 0;
90 | if(targetPitchIndex == lastTargetPitchIndex + 1) // one pitch up --> shift letters to the left
91 | letterOffset = -((frameNumber+1)/(float)framesPerCycle) * sideLettersPosition;
92 | else if (targetPitchIndex == lastTargetPitchIndex - 1) // one pitch down --> shift letters to the right
93 | letterOffset = ((frameNumber+1)/(float)framesPerCycle) * sideLettersPosition;
94 | else if (targetPitchIndex != lastTargetPitchIndex) { // more than one pitch difference to the last one --> fade
95 | float tmp = 2f*frameNumber/(float)framesPerCycle - 1;
96 | int alpha = (int) (255*tmp*tmp);
97 | gradientPaint.setAlpha(alpha); // fade the old letters out and the new ones in
98 | foregroundPaint.setAlpha(alpha); // also fade the needle
99 | }
100 |
101 | // if we are in the first half of the animation, we use the old results of the tuner.
102 | // Otherwise we use the new ones
103 | String centerLetter;
104 | String leftLetter;
105 | String rightLetter;
106 | if(frameNumber < (float)framesPerCycle/2) {
107 | centerLetter = tuner.pitchLetterFromIndex(lastTargetPitchIndex);
108 | leftLetter = tuner.pitchLetterFromIndex(lastTargetPitchIndex - 1);
109 | rightLetter = tuner.pitchLetterFromIndex(lastTargetPitchIndex + 1);
110 | } else {
111 | centerLetter = tuner.pitchLetterFromIndex(targetPitchIndex);
112 | leftLetter = tuner.pitchLetterFromIndex(targetPitchIndex - 1);
113 | rightLetter = tuner.pitchLetterFromIndex(targetPitchIndex + 1);
114 | // if we do a shift animation, this is the point were we have to correct our
115 | // offset position because now we use the latest results of the tuner:
116 | if(letterOffset > 0)
117 | letterOffset -= sideLettersPosition;
118 | else if (letterOffset < 0)
119 | letterOffset += sideLettersPosition;
120 | }
121 | // draw the letters at height 0.2f:
122 | drawPitchLetter(c, centerLetter, letterOffset, 0.2f, round, gradientPaint);
123 | drawPitchLetter(c, leftLetter, letterOffset - sideLettersPosition, 0.2f, round, gradientPaint);
124 | drawPitchLetter(c, rightLetter, letterOffset + sideLettersPosition, 0.2f, round, gradientPaint);
125 |
126 | // determine the old and the new angle of the needle:
127 | float newAngle = (float) (maxAngle / (Math.pow(2,1/24f) - 1) * (tuner.getDetectedFrequency() / targetFrequency - 1));
128 | float oldAngle = (float) (maxAngle / (Math.pow(2,1/24f) - 1) * (tuner.getLastDetectedFrequency() / lastTargetFrequency - 1));
129 | float animationSpan = newAngle - oldAngle; // default: we animate between the old angle and the new one...
130 |
131 | // if the target pitch has changed, we have to animate the needle either to the left or to the
132 | // right end of the scale. Exception: the target pitch changed exactly by one octave.
133 | // note that we have to correct the clipping angle down below...
134 | if(targetPitchIndex > lastTargetPitchIndex && targetPitchIndex-lastTargetPitchIndex != 12)
135 | animationSpan += 2* maxAngle; // animate from old angle to top of scale and from the bottom of the scale to the new angle
136 | else if (targetPitchIndex < lastTargetPitchIndex && lastTargetPitchIndex-targetPitchIndex != 12)
137 | animationSpan -= 2* maxAngle; // animate from old angle to bottom of scale and from the top of the scale to the new angle
138 |
139 | // determine the current angle (depending of the current step of the animation)
140 | float angle = oldAngle + ((frameNumber+1)/(float)framesPerCycle) * animationSpan;
141 |
142 | // correct the angle if it is clipping (happens when animating to the left/right end of the scale:
143 | if(angle > maxAngle)
144 | angle = angle - 2 * maxAngle;
145 | else if(angle < -maxAngle)
146 | angle = angle + 2 * maxAngle;
147 |
148 | // draw the needle:
149 | drawNeedle(c, angle, tuner.isTuned() ? highlightPaint : foregroundPaint);
150 |
151 | // reset alpha to default
152 | gradientPaint.setAlpha(255);
153 | foregroundPaint.setAlpha(255);
154 | }
155 | }
156 |
157 | /**
158 | * Draws one pitch letter on the canvas.
159 | * @param c canvas to draw
160 | * @param letter the letter (e.g. "a2#")
161 | * @param xPosition horizontal position relative to the middle of the screen (-1 is left edge; 1 is right edge)
162 | * @param yPosition vertical position relative to the top of the screen: 0 is top edge; 1 is bottom edge)
163 | * @param round if set to true, the xPosition will also affect the yPosition to arrange the letters
164 | * into a circle and make the side letters smaller.
165 | * @param paint paint that should be used
166 | */
167 | protected void drawPitchLetter(Canvas c, String letter, float xPosition, float yPosition, boolean round, Paint paint) {
168 | Rect bounds = new Rect();
169 | gradientPaint.getTextBounds(letter, 0, letter.length(), bounds);
170 | float x = (xPosition + 1)/2 * width - bounds.width()/2;
171 | float y = height * yPosition; // default y position. (for linear arrangement)
172 | float textSize = paint.getTextSize();
173 | if(round) {
174 | // if the screen is round we lower the side letters and make them smaller:
175 | y = height * (yPosition + 0.25f * xPosition * xPosition);
176 | paint.setTextSize(textSize * (1 - 0.4f*Math.abs(xPosition)));
177 | }
178 | c.drawText(letter, 0, letter.length(), x, y, paint);
179 | paint.setTextSize(textSize);
180 | }
181 |
182 | /**
183 | * Draws the scale (21 dashes) on the canvas
184 | * @param c canvas to draw
185 | */
186 | protected void drawScale(Canvas c) {
187 | // center dash (large):
188 | c.drawLine(width/2, height*0.37f, width/2, height*0.27f, gradientPaint);
189 |
190 | // side dashes:
191 | for (int i = 1; i < 11; i++) {
192 | float dashLenght = i==10 ? height*0.1f : height*0.05f;
193 | float x0 = (float) Math.sin(maxAngle * i / 10) * height*0.58f;
194 | float x1 = (float) Math.sin(maxAngle * i / 10) * (height*0.58f + dashLenght);
195 | float y0 = height*0.05f + (float) Math.cos(maxAngle * i / 10) * height*0.58f;
196 | float y1 = height*0.05f + (float) Math.cos(maxAngle * i / 10) * (height*0.58f + dashLenght);
197 | c.drawLine(width/2 + x0, height - y0, width/2 + x1, height - y1, gradientPaint);
198 | c.drawLine(width/2 - x0, height - y0, width/2 - x1, height - y1, gradientPaint);
199 | }
200 | }
201 |
202 | /**
203 | * Draws the needle on the screen at the given angle
204 | * @param c canvas to draw
205 | * @param angle angle in radian. 0 will result in a straight vertical needle.
206 | * @param paint paint that should be used
207 | */
208 | protected void drawNeedle(Canvas c, float angle, Paint paint) {
209 | float x = (float) Math.sin(angle) * height*0.58f;
210 | float y = height*0.05f + (float) Math.cos(angle) * height*0.58f;
211 | c.drawCircle(width/2, height*0.95f, height*0.01f, paint);
212 | c.drawLine(width/2, height*0.95f, width/2 + x, height - y, paint);
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/java/com/mantz_it/guitartunerlibrary/PreferenceSyncHelper.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.guitartunerlibrary;
2 |
3 | import android.content.SharedPreferences;
4 | import android.util.Log;
5 |
6 | import com.google.android.gms.common.api.GoogleApiClient;
7 | import com.google.android.gms.common.api.ResultCallback;
8 | import com.google.android.gms.wearable.MessageApi;
9 | import com.google.android.gms.wearable.Wearable;
10 |
11 | import java.io.UnsupportedEncodingException;
12 | import java.nio.ByteBuffer;
13 |
14 | /**
15 | *
16 | *
17 | * Module: PreferenceSyncHelper.java
18 | * Description: This class contains a collection of static helper functions to synchronize shared
19 | * preferences between a Android smartphone and a wearable device running Android Wear
20 | *
21 | * @author Dennis Mantz
22 | *
23 | * Copyright (C) 2014 Dennis Mantz
24 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
25 | *
26 | * This library is free software; you can redistribute it and/or
27 | * modify it under the terms of the GNU General Public
28 | * License as published by the Free Software Foundation; either
29 | * version 2 of the License, or (at your option) any later version.
30 | *
31 | * This library is distributed in the hope that it will be useful,
32 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
33 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
34 | * General Public License for more details.
35 | *
36 | * You should have received a copy of the GNU General Public
37 | * License along with this library; if not, write to the Free Software
38 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
39 | */
40 | public class PreferenceSyncHelper {
41 | private static final String LOGTAG = "PreferenceSyncHelper";
42 |
43 | /**
44 | * Will synchronize a boolean preference to the other device by using the Wearable MessageAPI.
45 | * Note: synchronization might fail even if this method returns true. Don't rely on this mechanism.
46 | *
47 | * @param googleApiClient connected instance of the Google API Client to send a message
48 | * @param nodeID node id of the other device
49 | * @param prefKey key of the preference that should be synchronized
50 | * @param newValue new value of the preference
51 | * @return true if the message was sent successfully. This does not guarantee that the other device has received the msg
52 | */
53 | public static boolean syncBooleanPref(GoogleApiClient googleApiClient, String nodeID, String prefKey, Boolean newValue) {
54 | byte[] val = new byte[1];
55 | val[0] = newValue ? (byte)1 : (byte)0;
56 | return syncPref(googleApiClient, nodeID, prefKey, "boolean", val);
57 | }
58 |
59 | /**
60 | * Will synchronize an integer preference to the other device by using the Wearable MessageAPI.
61 | * Note: synchronization might fail even if this method returns true. Don't rely on this mechanism.
62 | *
63 | * @param googleApiClient connected instance of the Google API Client to send a message
64 | * @param nodeID node id of the other device
65 | * @param prefKey key of the preference that should be synchronized
66 | * @param newValue new value of the preference
67 | * @return true if the message was sent successfully. This does not guarantee that the other device has received the msg
68 | */
69 | public static boolean syncIntegerPref(GoogleApiClient googleApiClient, String nodeID, String prefKey, Integer newValue) {
70 | return syncPref(googleApiClient, nodeID, prefKey, "integer", ByteBuffer.allocate(4).putInt(newValue).array());
71 | }
72 |
73 | /**
74 | * Will synchronize a float preference to the other device by using the Wearable MessageAPI.
75 | * Note: synchronization might fail even if this method returns true. Don't rely on this mechanism.
76 | *
77 | * @param googleApiClient connected instance of the Google API Client to send a message
78 | * @param nodeID node id of the other device
79 | * @param prefKey key of the preference that should be synchronized
80 | * @param newValue new value of the preference
81 | * @return true if the message was sent successfully. This does not guarantee that the other device has received the msg
82 | */
83 | public static boolean syncFloatPref(GoogleApiClient googleApiClient, String nodeID, String prefKey, Float newValue) {
84 | return syncPref(googleApiClient, nodeID, prefKey, "float", ByteBuffer.allocate(4).putFloat(newValue).array());
85 | }
86 |
87 | /**
88 | * Will synchronize a long preference to the other device by using the Wearable MessageAPI.
89 | * Note: synchronization might fail even if this method returns true. Don't rely on this mechanism.
90 | *
91 | * @param googleApiClient connected instance of the Google API Client to send a message
92 | * @param nodeID node id of the other device
93 | * @param prefKey key of the preference that should be synchronized
94 | * @param newValue new value of the preference
95 | * @return true if the message was sent successfully. This does not guarantee that the other device has received the msg
96 | */
97 | public static boolean syncLongPref(GoogleApiClient googleApiClient, String nodeID, String prefKey, Long newValue) {
98 | return syncPref(googleApiClient, nodeID, prefKey, "long", ByteBuffer.allocate(4).putLong(newValue).array());
99 | }
100 |
101 | /**
102 | * Will synchronize a string preference to the other device by using the Wearable MessageAPI.
103 | * Note: synchronization might fail even if this method returns true. Don't rely on this mechanism.
104 | *
105 | * @param googleApiClient connected instance of the Google API Client to send a message
106 | * @param nodeID node id of the other device
107 | * @param prefKey key of the preference that should be synchronized
108 | * @param newValue new value of the preference
109 | * @return true if the message was sent successfully. This does not guarantee that the other device has received the msg
110 | */
111 | public static boolean syncStringPref(GoogleApiClient googleApiClient, String nodeID, String prefKey, String newValue) {
112 | try {
113 | return syncPref(googleApiClient, nodeID, prefKey, "string", newValue.getBytes("UTF-8"));
114 | } catch (UnsupportedEncodingException e) {
115 | Log.e(LOGTAG, "syncStringPref: UTF-8 encoding not available: " + e.getMessage());
116 | return false;
117 | }
118 | }
119 |
120 | /**
121 | * Will synchronize a preference to the other device by using the Wearable MessageAPI.
122 | * Note: synchronization might fail even if this method returns true. Don't rely on this mechanism.
123 | * Don't use this method but the above wrapper methods instead!
124 | *
125 | * @param googleApiClient connected instance of the Google API Client to send a message
126 | * @param nodeID node id of the other device
127 | * @param prefKey key of the preference that should be synchronized
128 | * @param type lowercase type of the preference (e.g. "string", "long", or "boolean")
129 | * @param newValue new value of the preference encoded as byte array
130 | * @return true if the message was sent successfully. This does not guarantee that the other device has received the msg
131 | */
132 | public static boolean syncPref(GoogleApiClient googleApiClient, String nodeID, final String prefKey, String type, byte[] newValue) {
133 | if(googleApiClient == null || !googleApiClient.isConnected()) {
134 | Log.e(LOGTAG, "syncPref: GoogleApiClient is not connected!");
135 | return false;
136 | }
137 | if(nodeID == null || nodeID.equals("")) {
138 | Log.e(LOGTAG, "syncPref: Node ID is invalid!");
139 | return false;
140 | }
141 |
142 | // Send it to the handheld device:
143 | Wearable.MessageApi.sendMessage(googleApiClient, nodeID,
144 | "/syncPref/" + type + "/" + prefKey, newValue).setResultCallback(new ResultCallback() {
145 | @Override
146 | public void onResult(MessageApi.SendMessageResult sendMessageResult) {
147 | if (!sendMessageResult.getStatus().isSuccess())
148 | Log.e(LOGTAG, "syncPref: Failed to sync preference ("+prefKey+") to the handheld: " + sendMessageResult.toString());
149 | else
150 | Log.d(LOGTAG, "syncPref: Synced preference "+prefKey+" (messageID=" + sendMessageResult.getRequestId() + ")!");
151 | }
152 | });
153 | return true;
154 | }
155 |
156 | /**
157 | * This method will handle the reception of a sync message which was sent by the other device
158 | * (using above methods)
159 | *
160 | * @param edit a preference editor to update the received preference value
161 | * @param messagePath the message path that was associated with the message (containing the preference key and type)
162 | * @param messageData data of the message
163 | * @return true if the preference was successfully extracted and updated from the message
164 | */
165 | public static boolean handleSyncMessage(SharedPreferences.Editor edit, String messagePath, byte[] messageData) {
166 | String[] splitPath = messagePath.split("/");
167 | String type = splitPath[2];
168 | String prefKey = splitPath[3];
169 | String newValue = null;
170 | if(type.equals("boolean")) {
171 | Boolean newBool = messageData[0] > 0;
172 | newValue = "" + newBool;
173 | edit.putBoolean(prefKey, newBool);
174 | } else if(type.equals("integer")) {
175 | Integer newInt = ByteBuffer.wrap(messageData).getInt();
176 | newValue = "" + newInt;
177 | edit.putInt(prefKey, newInt);
178 | } else if(type.equals("long")) {
179 | Long newLong = ByteBuffer.wrap(messageData).getLong();
180 | newValue = "" + newLong;
181 | edit.putLong(prefKey, newLong);
182 | } else if(type.equals("float")) {
183 | Float newFloat = ByteBuffer.wrap(messageData).getFloat();
184 | newValue = "" + newFloat;
185 | edit.putFloat(prefKey, newFloat);
186 | } else if(type.equals("string")) {
187 | try {
188 | newValue = new String(messageData, "UTF-8");
189 | edit.putString(prefKey, newValue);
190 | } catch (UnsupportedEncodingException e) {
191 | Log.e(LOGTAG, "handleSyncMessage: Failed to extract String: " + e.getMessage());
192 | }
193 | } else {
194 | Log.d(LOGTAG, "handleSyncMessage: Received a syncPref message with unknown type: " + type);
195 | return false;
196 | }
197 | Log.d(LOGTAG, "handleSyncMessage: Received a syncPref message. Type is " + type + ". New value is " + newValue);
198 | edit.apply();
199 | return true;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/wear/src/main/java/com/mantz_it/wearguitartuner/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.wearguitartuner;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.app.Fragment;
6 | import android.app.FragmentManager;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.content.SharedPreferences;
10 | import android.os.Bundle;
11 | import android.preference.PreferenceManager;
12 | import android.support.annotation.Nullable;
13 | import android.support.wearable.view.FragmentGridPagerAdapter;
14 | import android.support.wearable.view.GridViewPager;
15 | import android.util.Log;
16 | import android.view.LayoutInflater;
17 | import android.view.View;
18 | import android.view.ViewGroup;
19 | import android.widget.ImageView;
20 | import android.widget.Toast;
21 |
22 | import com.google.android.gms.common.ConnectionResult;
23 | import com.google.android.gms.common.api.GoogleApiClient;
24 | import com.google.android.gms.common.api.ResultCallback;
25 | import com.google.android.gms.wearable.Node;
26 | import com.google.android.gms.wearable.NodeApi;
27 | import com.google.android.gms.wearable.Wearable;
28 | import com.mantz_it.guitartunerlibrary.PreferenceSyncHelper;
29 | import com.mantz_it.guitartunerlibrary.TunerSkin;
30 |
31 | /**
32 | *
Wear Guitar Tuner - Settings Activity
33 | *
34 | * Module: SettingsActivity.java
35 | * Description: Settings Activity that lets the user choose between different tuner skins and
36 | * edit important settings.
37 | *
38 | * @author Dennis Mantz
39 | *
40 | * Copyright (C) 2014 Dennis Mantz
41 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
42 | *
43 | * This library is free software; you can redistribute it and/or
44 | * modify it under the terms of the GNU General Public
45 | * License as published by the Free Software Foundation; either
46 | * version 2 of the License, or (at your option) any later version.
47 | *
48 | * This library is distributed in the hope that it will be useful,
49 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
50 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
51 | * General Public License for more details.
52 | *
53 | * You should have received a copy of the GNU General Public
54 | * License along with this library; if not, write to the Free Software
55 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
56 | */
57 | public class SettingsActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
58 | GoogleApiClient.OnConnectionFailedListener, NodeApi.NodeListener {
59 | private static final String LOGTAG = "SettingsActivity";
60 |
61 | private SharedPreferences preferences;
62 | private boolean roundScreen;
63 | private GoogleApiClient googleApiClient;
64 | private Node handheldNode;
65 |
66 | @Override
67 | protected void onCreate(Bundle savedInstanceState) {
68 | super.onCreate(savedInstanceState);
69 | setContentView(R.layout.activity_settings);
70 | final GridViewPager pager = (GridViewPager) findViewById(R.id.gvp_settings);
71 | pager.setAdapter(new SettingsGridViewPagerAdapter(this, getFragmentManager()));
72 |
73 | preferences = PreferenceManager.getDefaultSharedPreferences(this);
74 | roundScreen = preferences.getBoolean(getString(R.string.pref_roundScreen), false);
75 |
76 | googleApiClient = new GoogleApiClient.Builder(this)
77 | .addConnectionCallbacks(this)
78 | .addOnConnectionFailedListener(this)
79 | .addApi(Wearable.API)
80 | .build();
81 | }
82 |
83 | @Override
84 | protected void onStart() {
85 | super.onStart();
86 |
87 | // Show Toast on first startup:
88 | boolean firstStart = preferences.getBoolean(getString(R.string.pref_settingsActivityFirstStart), true);
89 | if(firstStart) {
90 | SharedPreferences.Editor edit = preferences.edit();
91 | edit.putBoolean(getString(R.string.pref_settingsActivityFirstStart), false);
92 | edit.apply();
93 | Toast.makeText(this, getString(R.string.toast_settings_activity_first_start_scroll_right), Toast.LENGTH_LONG).show();
94 | Toast.makeText(this, getString(R.string.toast_settings_activity_first_start_scroll_down), Toast.LENGTH_LONG).show();
95 | }
96 |
97 | // connect the google api client:
98 | googleApiClient.connect();
99 | }
100 |
101 | @Override
102 | protected void onStop() {
103 | // disconnect the google api client:
104 | if (googleApiClient != null && googleApiClient.isConnected())
105 | googleApiClient.disconnect();
106 | super.onStop();
107 | }
108 |
109 | /**
110 | * will exit the settings activity (return to main activity):
111 | */
112 | private void returnToMainActivity() {
113 | Intent intent = new Intent(SettingsActivity.this, MainActivity.class);
114 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
115 | startActivity(intent);
116 | }
117 |
118 | /**
119 | * (ConnectionCallbacks)
120 | * Gets called after googleApiClient.connect() was executed successfully
121 | */
122 | @Override
123 | public void onConnected(Bundle bundle) {
124 | Log.d(LOGTAG, "onConnected: googleApiClient connected!");
125 |
126 | // Enumerate nodes:
127 | Wearable.NodeApi.getConnectedNodes(googleApiClient).setResultCallback(new ResultCallback() {
128 | @Override
129 | public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
130 | for (Node node : getConnectedNodesResult.getNodes()) {
131 | Log.i(LOGTAG, "onConnected: Found node: " + node.getDisplayName() + " (" + node.getId() + ")");
132 | handheldNode = node; // for now we just expect one single node to be found..
133 | }
134 | }
135 | });
136 |
137 | // Register node listener:
138 | Wearable.NodeApi.addListener(googleApiClient, this); // will execute onPeerConnected() and onPeerDisconnected()
139 | }
140 |
141 | /**
142 | * (ConnectionCallbacks)
143 | * Gets called after googleApiClient.connect() was executed successfully and the api connection is suspended again
144 | */
145 | @Override
146 | public void onConnectionSuspended(int cause) {
147 | Log.d(LOGTAG, "onConnectionSuspended: googleApiClient suspended: " + cause);
148 | }
149 |
150 | /**
151 | * (OnConnectionFailedListener)
152 | * Gets called after googleApiClient.connect() was executed and failed
153 | */
154 | @Override
155 | public void onConnectionFailed(ConnectionResult result) {
156 | Log.d(LOGTAG, "onConnectionFailed: googleApiClient connection failed: " + result.toString());
157 | }
158 |
159 | /**
160 | * (NodeListener)
161 | * Gets called if a new node (a handheld) is connected to the watch
162 | */
163 | @Override
164 | public void onPeerConnected(Node node) {
165 | Log.i(LOGTAG, "onPeerConnected: Node " + node.getId() + " connected!");
166 | handheldNode = node;
167 | }
168 |
169 | /**
170 | * (NodeListener)
171 | * Gets called if a node (a handheld) disconnects from the watch
172 | */
173 | @Override
174 | public void onPeerDisconnected(Node node) {
175 | Log.i(LOGTAG, "onPeerDisconnected: Node " + node.getId() + " has disconnected!");
176 | if(handheldNode.getId().equals(node.getId())) {
177 | Log.i(LOGTAG, "onPeerDisconnected: Setting wearable node to null!");
178 | handheldNode = null;
179 | }
180 | }
181 |
182 |
183 | public class SettingsGridViewPagerAdapter extends FragmentGridPagerAdapter {
184 |
185 | private Context context;
186 |
187 | public SettingsGridViewPagerAdapter(Context ctx, FragmentManager fm) {
188 | super(fm);
189 | context = ctx;
190 | }
191 |
192 | @Override
193 | public Fragment getFragment(int row, int column) {
194 | Fragment fragment = null;
195 | switch(row) {
196 | case 0: // Skin chooser
197 | fragment = new SkinPreviewFragment();
198 | ((SkinPreviewFragment)fragment).setSkinIndex(column);
199 | break;
200 | case 1: // Vibrate ON/OFF
201 | final boolean vibrationEnabled = preferences.getBoolean(getString(R.string.pref_vibration_enabled), true);
202 | fragment = ActionFragment.create(R.drawable.ic_watch_vibrate,
203 | vibrationEnabled ? R.string.turn_vibration_off : R.string.turn_vibration_on,
204 | new ActionFragment.Listener() {
205 | @Override
206 | public void onActionPerformed() {
207 | // update the value in the preferences:
208 | SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(SettingsActivity.this).edit();
209 | edit.putBoolean(getString(R.string.pref_vibration_enabled), !vibrationEnabled);
210 | edit.apply();
211 |
212 | if(handheldNode != null) {
213 | PreferenceSyncHelper.syncBooleanPref(googleApiClient, handheldNode.getId(),
214 | getString(R.string.pref_vibration_enabled), !vibrationEnabled);
215 | }
216 | returnToMainActivity();
217 | }
218 | });
219 | break;
220 | }
221 | return fragment;
222 | }
223 |
224 | @Override
225 | public int getRowCount() {
226 | return 2;
227 | }
228 |
229 | @Override
230 | public int getColumnCount(int i) {
231 | return TunerSkin.getTunerSkinCount();
232 | }
233 |
234 | }
235 |
236 | @SuppressLint("ValidFragment")
237 | public class SkinPreviewFragment extends Fragment {
238 | private ImageView iv_thumbnail;
239 | private int skinIndex = -1;
240 | @Nullable
241 | @Override
242 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
243 | final View v = inflater.inflate(R.layout.skin_preview, container, false);
244 | iv_thumbnail = (ImageView) v.findViewById(R.id.iv_thumbnail);
245 | if(skinIndex >= 0)
246 | iv_thumbnail.setImageResource(TunerSkin.getTunerSkinThumbnailResource(skinIndex, roundScreen));
247 |
248 | v.setOnClickListener(new View.OnClickListener() {
249 | @Override
250 | public void onClick(View v) {
251 | Log.i(LOGTAG, "onClick (SkinPreviewFragment): changing skin to " + TunerSkin.getTunerSkinName(skinIndex));
252 |
253 | // update the value in the preferences:
254 | SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(SettingsActivity.this).edit();
255 | edit.putInt(getString(R.string.pref_skinIndex), skinIndex);
256 | edit.apply();
257 |
258 | if(handheldNode != null) {
259 | PreferenceSyncHelper.syncIntegerPref(googleApiClient, handheldNode.getId(), getString(R.string.pref_skinIndex), skinIndex);
260 | }
261 | returnToMainActivity();
262 | }
263 | });
264 | return v;
265 | }
266 |
267 | public void setSkinIndex(int index) {
268 | this.skinIndex = index;
269 | if(iv_thumbnail != null)
270 | iv_thumbnail.setImageResource(TunerSkin.getTunerSkinThumbnailResource(skinIndex, roundScreen));
271 | }
272 | }
273 |
274 | }
275 |
--------------------------------------------------------------------------------
/wear/src/main/java/com/mantz_it/wearguitartuner/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.wearguitartuner;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.content.SharedPreferences;
6 | import android.os.Bundle;
7 | import android.os.Vibrator;
8 | import android.preference.PreferenceManager;
9 | import android.util.Log;
10 | import android.view.GestureDetector;
11 | import android.view.MotionEvent;
12 | import android.view.View;
13 | import android.view.WindowInsets;
14 | import android.widget.FrameLayout;
15 | import android.widget.Toast;
16 |
17 | import com.google.android.gms.common.ConnectionResult;
18 | import com.google.android.gms.common.api.GoogleApiClient;
19 | import com.google.android.gms.common.api.ResultCallback;
20 | import com.google.android.gms.wearable.Node;
21 | import com.google.android.gms.wearable.NodeApi;
22 | import com.google.android.gms.wearable.Wearable;
23 | import com.mantz_it.guitartunerlibrary.AudioProcessingEngine;
24 | import com.mantz_it.guitartunerlibrary.GuitarTuner;
25 | import com.mantz_it.guitartunerlibrary.PreferenceSyncHelper;
26 | import com.mantz_it.guitartunerlibrary.TunerSkin;
27 | import com.mantz_it.guitartunerlibrary.TunerSurface;
28 |
29 | /**
30 | *
Wear Guitar Tuner - Main Activity
31 | *
32 | * Module: MainActivity.java
33 | * Description: Main Activity of the Wear application
34 | *
35 | * @author Dennis Mantz
36 | *
37 | * Copyright (C) 2014 Dennis Mantz
38 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
39 | *
40 | * This library is free software; you can redistribute it and/or
41 | * modify it under the terms of the GNU General Public
42 | * License as published by the Free Software Foundation; either
43 | * version 2 of the License, or (at your option) any later version.
44 | *
45 | * This library is distributed in the hope that it will be useful,
46 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
47 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
48 | * General Public License for more details.
49 | *
50 | * You should have received a copy of the GNU General Public
51 | * License along with this library; if not, write to the Free Software
52 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
53 | */
54 |
55 | public class MainActivity extends Activity implements View.OnApplyWindowInsetsListener,
56 | SharedPreferences.OnSharedPreferenceChangeListener, GoogleApiClient.ConnectionCallbacks,
57 | GoogleApiClient.OnConnectionFailedListener, NodeApi.NodeListener {
58 | private static final String LOGTAG = "MainActivity";
59 | private boolean roundScreen = false;
60 |
61 | private SharedPreferences preferences;
62 | private GestureDetector gestureDetector;
63 | private AudioProcessingEngine audioProcessingEngine;
64 | private GuitarTuner guitarTuner;
65 | private FrameLayout fl_root;
66 | private TunerSurface tunerSurface;
67 | private GoogleApiClient googleApiClient;
68 | private Node handheldNode;
69 |
70 | @Override
71 | protected void onCreate(Bundle savedInstanceState) {
72 | super.onCreate(savedInstanceState);
73 | setContentView(R.layout.activity_main);
74 | fl_root = (FrameLayout) findViewById(R.id.fl_root);
75 | tunerSurface = (TunerSurface) findViewById(R.id.sv_tunerSurface);
76 | tunerSurface.setZOrderOnTop(true); // WORKAROUND (see: https://code.google.com/p/android/issues/detail?id=82985)
77 | fl_root.setOnApplyWindowInsetsListener(this); // register for this event to detect round/rect screen
78 |
79 | // Get reference to the shared preferences:
80 | preferences = PreferenceManager.getDefaultSharedPreferences(this);
81 | preferences.registerOnSharedPreferenceChangeListener(this);
82 | roundScreen = preferences.getBoolean(getString(R.string.pref_roundScreen), false);
83 | tunerSurface.setRound(roundScreen);
84 |
85 | // Create a GuitarTuner instance:
86 | guitarTuner = new GuitarTuner(tunerSurface, (Vibrator) getSystemService(VIBRATOR_SERVICE));
87 |
88 | // Initialize the gesture detector
89 | gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
90 | public void onLongPress(MotionEvent ev) {
91 | // A long press starts the settings activity
92 | Log.i(LOGTAG, "onLongPress: Long press detected. Starting SettingsActivity...");
93 | Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
94 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
95 | startActivity(intent);
96 | }
97 | });
98 |
99 | // create a google api client
100 | googleApiClient = new GoogleApiClient.Builder(this)
101 | .addConnectionCallbacks(this)
102 | .addOnConnectionFailedListener(this)
103 | .addApi(Wearable.API)
104 | .build();
105 |
106 | Log.d(LOGTAG, "onCreate: Wear Guitar Tuner was started!");
107 | }
108 |
109 | @Override
110 | public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
111 | if(insets.isRound()) {
112 | roundScreen = true;
113 | Log.i(LOGTAG, "onApplyWindowInsets: detected a round Screen!");
114 | }
115 | else {
116 | roundScreen = false;
117 | Log.i(LOGTAG, "onApplyWindowInsets: detected a rectangular Screen!");
118 | }
119 |
120 | // Update the value in the preferences:
121 | SharedPreferences.Editor edit = preferences.edit();
122 | edit.putBoolean(getString(R.string.pref_roundScreen), roundScreen);
123 | edit.apply();
124 |
125 | // note: Because at this early stage of execution, the googleApiClient is most likely
126 | // not connected. So we will sync the roundScreen setting in the onConnected() callback...
127 |
128 | // unregister the window insets listener:
129 | fl_root.setOnApplyWindowInsetsListener(null);
130 |
131 | return insets;
132 | }
133 |
134 | // Capture long presses
135 | @Override
136 | public boolean dispatchTouchEvent(MotionEvent ev) {
137 | gestureDetector.onTouchEvent(ev);
138 | return super.dispatchTouchEvent(ev);
139 | }
140 |
141 | @Override
142 | protected void onRestart() {
143 | super.onRestart();
144 | Log.d(LOGTAG, "onRestart");
145 | }
146 |
147 | @Override
148 | protected void onStart() {
149 | super.onStart();
150 | Log.d(LOGTAG, "onStart");
151 |
152 | // Apply preferences:
153 | // tuner skin:
154 | tunerSurface.setTunerSkin(TunerSkin.getTunerSkinInstance(preferences.getInt(getString(R.string.pref_skinIndex),0), this));
155 |
156 | // vibration:
157 | guitarTuner.setVibrate(preferences.getBoolean(getString(R.string.pref_vibration_enabled), true));
158 |
159 | // Show Toast on first startup:
160 | if(preferences.getBoolean(getString(R.string.pref_settingsActivityFirstStart), true)) {
161 | Toast.makeText(this, getString(R.string.toast_main_activity_first_start), Toast.LENGTH_LONG).show();
162 | }
163 |
164 | // connect the google api client:
165 | googleApiClient.connect();
166 | }
167 |
168 | @Override
169 | protected void onResume() {
170 | super.onResume();
171 | Log.d(LOGTAG, "onResume");
172 |
173 | // create and start the audio processing thread (guitar tuner thread)
174 | audioProcessingEngine = new AudioProcessingEngine(guitarTuner);
175 | audioProcessingEngine.start();
176 | }
177 |
178 | @Override
179 | protected void onStop() {
180 | //disconnect the google api client:
181 | if(googleApiClient != null && googleApiClient.isConnected())
182 | googleApiClient.disconnect();
183 | super.onStop();
184 | Log.d(LOGTAG, "onStop");
185 | }
186 |
187 | @Override
188 | protected void onDestroy() {
189 | super.onDestroy();
190 | Log.d(LOGTAG, "onDestroy");
191 | }
192 |
193 | @Override
194 | protected void onPause() {
195 | super.onPause();
196 | Log.d(LOGTAG, "onPause");
197 |
198 | // stop the audio processing thread
199 | if(audioProcessingEngine != null) {
200 | audioProcessingEngine.stopProcessing();
201 | try {
202 | audioProcessingEngine.interrupt();
203 | audioProcessingEngine.join(250);
204 | } catch (InterruptedException e) {
205 | Log.e(LOGTAG, "onPause: Interrupted while joining audioProcessingEngine!");
206 | }
207 | }
208 | }
209 |
210 | @Override
211 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
212 | Log.d(LOGTAG, "onSharedPreferenceChanged: preference changed! (key=" + key + ")");
213 | if(key.equals(getString(R.string.pref_roundScreen))) {
214 | tunerSurface.setRound(preferences.getBoolean(key, false));
215 | } else if(key.equals(getString(R.string.pref_vibration_enabled))) {
216 | boolean vibrate = preferences.getBoolean(key, true);
217 | guitarTuner.setVibrate(vibrate);
218 | } else if(key.equals(getString(R.string.pref_skinIndex))) {
219 | tunerSurface.setTunerSkin(TunerSkin.getTunerSkinInstance(preferences.getInt(key, 0), this));
220 | }
221 | }
222 |
223 | /**
224 | * (ConnectionCallbacks)
225 | * Gets called after googleApiClient.connect() was executed successfully
226 | */
227 | @Override
228 | public void onConnected(Bundle bundle) {
229 | Log.d(LOGTAG, "onConnected: googleApiClient connected!");
230 |
231 | // Enumerate nodes:
232 | Wearable.NodeApi.getConnectedNodes(googleApiClient).setResultCallback(new ResultCallback() {
233 | @Override
234 | public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
235 | for (Node node : getConnectedNodesResult.getNodes()) {
236 | Log.i(LOGTAG, "onConnected: Found node: " + node.getDisplayName() + " (" + node.getId() + ")");
237 | handheldNode = node; // for now we just expect one single node to be found..
238 | }
239 |
240 | // After the api client is now connected and we have the handheld node, we can sync the
241 | // roundScreen pref to the handheld:
242 | if(handheldNode != null) {
243 | PreferenceSyncHelper.syncBooleanPref(googleApiClient, handheldNode.getId(),
244 | getString(R.string.pref_roundScreen), roundScreen);
245 | }
246 | }
247 | });
248 |
249 | // Register node listener:
250 | Wearable.NodeApi.addListener(googleApiClient, this); // will execute onPeerConnected() and onPeerDisconnected()
251 | }
252 |
253 | /**
254 | * (ConnectionCallbacks)
255 | * Gets called after googleApiClient.connect() was executed successfully and the api connection is suspended again
256 | */
257 | @Override
258 | public void onConnectionSuspended(int cause) {
259 | Log.d(LOGTAG, "onConnectionSuspended: googleApiClient suspended: " + cause);
260 | }
261 |
262 | /**
263 | * (OnConnectionFailedListener)
264 | * Gets called after googleApiClient.connect() was executed and failed
265 | */
266 | @Override
267 | public void onConnectionFailed(ConnectionResult result) {
268 | Log.d(LOGTAG, "onConnectionFailed: googleApiClient connection failed: " + result.toString());
269 | }
270 |
271 | /**
272 | * (NodeListener)
273 | * Gets called if a new node (a handheld) is connected to the watch
274 | */
275 | @Override
276 | public void onPeerConnected(Node node) {
277 | Log.i(LOGTAG, "onPeerConnected: Node " + node.getId() + " connected!");
278 | handheldNode = node;
279 | }
280 |
281 | /**
282 | * (NodeListener)
283 | * Gets called if a node (a handheld) disconnects from the watch
284 | */
285 | @Override
286 | public void onPeerDisconnected(Node node) {
287 | Log.i(LOGTAG, "onPeerDisconnected: Node " + node.getId() + " has disconnected!");
288 | if(handheldNode.getId().equals(node.getId())) {
289 | Log.i(LOGTAG, "onPeerDisconnected: Setting wearable node to null!");
290 | handheldNode = null;
291 | }
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/guitartunerlibrary/src/main/java/com/mantz_it/guitartunerlibrary/GuitarTuner.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.guitartunerlibrary;
2 |
3 | import android.os.Vibrator;
4 | import android.util.Log;
5 |
6 | import java.util.Locale;
7 |
8 | /**
9 | *
Wear Guitar Tuner - Guitar Tuner
10 | *
11 | * Module: GuitarTuner.java
12 | * Description: This class will extract the pitch information from the fft samples
13 | * and generate the tuner output which is passed to the callback interface (TunerSurface)
14 | *
15 | * @author Dennis Mantz
16 | *
17 | * Copyright (C) 2014 Dennis Mantz
18 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
19 | *
20 | * This library is free software; you can redistribute it and/or
21 | * modify it under the terms of the GNU General Public
22 | * License as published by the Free Software Foundation; either
23 | * version 2 of the License, or (at your option) any later version.
24 | *
25 | * This library is distributed in the hope that it will be useful,
26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28 | * General Public License for more details.
29 | *
30 | * You should have received a copy of the GNU General Public
31 | * License along with this library; if not, write to the Free Software
32 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
33 | */
34 | public class GuitarTuner {
35 | private static final String LOGTAG = "GuitarTuner";
36 | private static final int LOW_CUT_OFF_FREQUENCY = 50; // lowest frequency that will be extracted from the fft data
37 | private static final int HIGH_CUT_OFF_FREQUENCY = 2500; // highest frequency that will be extracted from the fft data
38 | private static final float CONCERT_PITCH = 440.0f; // frequency of the A4 pitch
39 | private static final int HPS_ORDER = 3; // order to calculate the harmonic product spectrum
40 | private static final long[] VIBRATE_PATTERN_UP = {0, 200}; // ~~~
41 | private static final long[] VIBRATE_PATTERN_DOWN = {0, 200, 200, 200}; // ~~~ ~~~
42 | private static final long[] VIBRATE_PATTERN_TUNED = {0, 100, 100, 100, 100, 100}; // ~~ ~~ ~~
43 | private GuitarTunerCallbackInterface callbackInterface;
44 | private Vibrator vibrator;
45 |
46 | private float[] mag; // magnitudes of the spectrum
47 | private float[] hps; // harmonic product spectrum
48 | private float updateRate; // indicates how often processFFTSamples() will be called per second
49 | private long lastUpdateTimestamp; // time of the last call to processFFTSamples()
50 | private float hzPerSample; // frequency step of one index in mag
51 | private float strongestFrequency; // holds the frequency of the strongest (max mag) frequency component (after HPS)
52 | private float detectedFrequency; // holds the frequency that was calculated to be the most likely/relevant frequency component
53 | private float targetFrequency; // desired frequency to tune to
54 | private int targetPitchIndex; // pitch index of the targetFrequency
55 | private int pitchHoldCounter = 0; // number of cycles the same pitch was detected in series.
56 | private float lastDetectedFrequency; // detected frequency of the last cycle
57 | private float lastTargetFrequency; // target frequency of the last cycle
58 | private boolean valid; // indicates if the current result is valid
59 | private boolean vibrate = false; // on/off switch for the vibration feedback
60 |
61 | /**
62 | * constructor
63 | *
64 | * @param callbackInterface interface that will get the results of the tuner
65 | * @param vibrator Vibrator instance
66 | */
67 | public GuitarTuner(GuitarTunerCallbackInterface callbackInterface, Vibrator vibrator) {
68 | this.callbackInterface = callbackInterface;
69 | this.vibrator = vibrator;
70 | }
71 |
72 | /**
73 | * This method processes the fft samples from the AudioProcessingEngine and pass the results to
74 | * the callback interface.
75 | *
76 | * @param mag fft samples (frequency spectrum)
77 | * @param sampleRate samplerate of the audio source
78 | * @param updateRate rate at which the audioProcessingEngine will call this method
79 | * @return true if success; false if something went wrong (e.g. the callback interface returned an error)
80 | */
81 | public boolean processFFTSamples(float[] mag, int sampleRate, float updateRate) {
82 | this.lastUpdateTimestamp = System.currentTimeMillis();
83 | this.updateRate = updateRate;
84 | this.mag = mag;
85 | hzPerSample = ((float)(sampleRate / 2)) / mag.length;
86 |
87 | // Eliminate frequency components outside the interesting band:
88 | for (int i = 0; i < LOW_CUT_OFF_FREQUENCY / hzPerSample; i++)
89 | mag[i] = Float.NEGATIVE_INFINITY; // set magnitude to 0 (== -invinity dB)
90 | for (int i = (int)(HIGH_CUT_OFF_FREQUENCY / hzPerSample); i < mag.length; i++)
91 | mag[i] = Float.NEGATIVE_INFINITY; // set magnitude to 0 (== -invinity dB)
92 |
93 | // Calculate Harmonic Product Spectrum
94 | if(hps == null || hps.length != mag.length)
95 | hps = new float[mag.length];
96 | calcHarmonicProductSpectrum(mag, hps, HPS_ORDER);
97 |
98 | // calculate the max (strongest frequency) of the HPS
99 | int maxIndex = 0;
100 | for (int i = 1; i < hps.length; i++) {
101 | if(hps[maxIndex] < hps[i])
102 | maxIndex = i;
103 | }
104 | strongestFrequency = maxIndex * hzPerSample;
105 |
106 | // detect the relevant frequency component:
107 | detectedFrequency = strongestFrequency; // this might be improved in the future (maybe the strongest frequency is not always the correct one?)
108 | targetPitchIndex = frequencyToPitchIndex(detectedFrequency);
109 | targetFrequency = pitchIndexToFrequency(targetPitchIndex);
110 | valid = detectedFrequency >= pitchIndexToFrequency(0);
111 |
112 | // check against the results of the past cycles:
113 | if(detectedFrequency > lastDetectedFrequency*0.99 && detectedFrequency < lastDetectedFrequency*1.01) {
114 | Log.d(LOGTAG, "processFFTSamples: detected frequency matches the old one!");
115 | pitchHoldCounter++;
116 | } else {
117 | Log.d(LOGTAG, "processFFTSamples: detected frequency differs from the last by " + detectedFrequency/lastDetectedFrequency*100 + "%");
118 | pitchHoldCounter = 0;
119 | }
120 |
121 | // If we have a stable pitch since more than 2 cycles, give feedback to the user:
122 | if(pitchHoldCounter > 2) {
123 | if(detectedFrequency < getLowerToleranceBoundaryFrequency(targetPitchIndex)) {
124 | Log.i(LOGTAG, "processFFTSamples: Result: Tune up by " + (targetFrequency-detectedFrequency) + " Hz! "
125 | + "Target frequency is " + targetFrequency + " Hz.");
126 | if(vibrate)
127 | vibrator.vibrate(VIBRATE_PATTERN_UP, -1);
128 | } else if(detectedFrequency > getUpperToleranceBoundaryFrequency(targetPitchIndex)) {
129 | Log.i(LOGTAG, "processFFTSamples: Result: Tune down by " + (detectedFrequency-targetFrequency) + " Hz! "
130 | + "Target frequency is " + targetFrequency + " Hz.");
131 | if(vibrate)
132 | vibrator.vibrate(VIBRATE_PATTERN_DOWN, -1);
133 | } else {
134 | Log.i(LOGTAG, "processFFTSamples: Result: TUNED! Target frequency is " + targetFrequency + " Hz (Error: "
135 | + (detectedFrequency-targetFrequency) + " Hz).");
136 | if(vibrate)
137 | vibrator.vibrate(VIBRATE_PATTERN_TUNED, -1);
138 | }
139 | pitchHoldCounter = 0;
140 | }
141 |
142 | // inform the callback interface about updated values:
143 | boolean success = callbackInterface.process(this);
144 |
145 | lastDetectedFrequency = detectedFrequency;
146 | lastTargetFrequency = targetFrequency;
147 | return success;
148 | }
149 |
150 | /**
151 | * calculates the harmonic product spectrum from an array of magnitudes (in dB)
152 | * @param mag magnitude array (in dB)
153 | * @param hps result array (will be overwritten with the result)
154 | * @param order order of the product; 1 = up to the first harmonic ...
155 | */
156 | private void calcHarmonicProductSpectrum(float[] mag, float[] hps, int order) {
157 | if(mag.length != hps.length) {
158 | Log.e(LOGTAG, "calcHarmonicProductSpectrum: mag[] and hps[] have to be of the same length!");
159 | throw new IllegalArgumentException("mag[] and hps[] have to be of the same length");
160 | }
161 |
162 | // initialize the hps array
163 | int hpsLength = mag.length / (order+1);
164 | for (int i = 0; i < hps.length; i++) {
165 | if(i < hpsLength)
166 | hps[i] = mag[i];
167 | else
168 | hps[i] = Float.NEGATIVE_INFINITY;
169 | }
170 |
171 | // do every harmonic in a big loop:
172 | for (int harmonic = 1; harmonic <= order; harmonic++) {
173 | int downsamplingFactor = harmonic + 1;
174 | for (int index = 0; index < hpsLength; index++) {
175 | // Calculate the average (downsampling):
176 | float avg = 0;
177 | for (int i = 0; i < downsamplingFactor; i++) {
178 | avg += mag[index*downsamplingFactor + i];
179 | }
180 | hps[index] += avg / downsamplingFactor;
181 | }
182 | }
183 | }
184 |
185 | /**
186 | * converts a frequency (float) into an pitch index ( 0 is A0, 1 is A0#, 2 is B0, 3 is C1, ...).
187 | * This will round the frequency to the closest pitch index.
188 | *
189 | * @param frequency frequency in Hz
190 | * @return pitch index
191 | */
192 | public int frequencyToPitchIndex(float frequency) {
193 | float A1 = CONCERT_PITCH / 8;
194 | return Math.round((float) (12 * Math.log(frequency / A1) / Math.log(2)));
195 | }
196 |
197 | /**
198 | * returns the corresponding frequency (in Hz) for a given pitch index
199 | * @param index pitch index ( 0 is A0, 1 is A0#, 2 is B0, 3 is C1, ...)
200 | * @return frequency in Hz
201 | */
202 | public float pitchIndexToFrequency(int index) {
203 | float A1 = CONCERT_PITCH / 8;
204 | return (float) (A1 * Math.pow(2, index/12f));
205 | }
206 |
207 | /**
208 | * returns the corresponding human readable pitch letter for a given pitch index
209 | * @param index pitch index ( 0 is A0, 1 is A0#, 2 is B0, 3 is C1, ...)
210 | * @return pitch letter (e.g. "a0" or "c1#")
211 | */
212 | public String pitchLetterFromIndex(int index) {
213 | String letters;
214 | int octaveNumber = ((index+9) / 12) + 1;
215 | switch(index%12) {
216 | case 0: letters = "a" + octaveNumber; break;
217 | case 1: letters = "a" + octaveNumber + "#"; break;
218 | case 2:
219 | if(Locale.getDefault().getLanguage().equals("en"))
220 | letters = "b" + octaveNumber;
221 | else
222 | letters = "h" + octaveNumber;
223 | break;
224 | case 3: letters = "c" + octaveNumber; break;
225 | case 4: letters = "c" + octaveNumber + "#"; break;
226 | case 5: letters = "d" + octaveNumber; break;
227 | case 6: letters = "d" + octaveNumber + "#"; break;
228 | case 7: letters = "e" + octaveNumber; break;
229 | case 8: letters = "f" + octaveNumber; break;
230 | case 9: letters = "f" + octaveNumber + "#"; break;
231 | case 10: letters = "g" + octaveNumber; break;
232 | case 11: letters = "g" + octaveNumber + "#"; break;
233 | default: letters = "err";
234 | }
235 | return letters;
236 | }
237 |
238 | /**
239 | * calculates the lowest frequency that would still be considered as 'tuned' to the given
240 | * pitch index.
241 | *
242 | * @param pitchIndex pitch index ( 0 is A0, 1 is A0#, 2 is B0, 3 is C1, ...)
243 | * @return the lower boundary frequency (in Hz) for a tuned note
244 | */
245 | public float getLowerToleranceBoundaryFrequency(int pitchIndex) {
246 | float frequency = pitchIndexToFrequency(pitchIndex);
247 | float nextLowerFrequency = pitchIndexToFrequency(pitchIndex-1);
248 | return frequency - 0.05f * (frequency-nextLowerFrequency);
249 | }
250 |
251 | /**
252 | * calculates the highest frequency that would still be considered as 'tuned' to the given
253 | * pitch index.
254 | *
255 | * @param pitchIndex pitch index ( 0 is A0, 1 is A0#, 2 is B0, 3 is C1, ...)
256 | * @return the upper boundary frequency (in Hz) for a tuned note
257 | */
258 | public float getUpperToleranceBoundaryFrequency(int pitchIndex) {
259 | float frequency = pitchIndexToFrequency(pitchIndex);
260 | float nextUpperFrequency = pitchIndexToFrequency(pitchIndex+1);
261 | return frequency + 0.05f * (nextUpperFrequency-frequency);
262 | }
263 |
264 | /**
265 | * @return true if the detectedFrequency is 'tuned' to the current targetPitch
266 | */
267 | public boolean isTuned() {
268 | return detectedFrequency > getLowerToleranceBoundaryFrequency(targetPitchIndex)
269 | && detectedFrequency < getUpperToleranceBoundaryFrequency(targetPitchIndex);
270 | }
271 |
272 | public float getStrongestFrequency() {
273 | return strongestFrequency;
274 | }
275 |
276 | public float[] getMag() {
277 | return mag;
278 | }
279 |
280 | public float[] getHPS() {
281 | return hps;
282 | }
283 |
284 | public float getUpdateRate() {
285 | return updateRate;
286 | }
287 |
288 | public long getLastUpdateTimestamp() {
289 | return lastUpdateTimestamp;
290 | }
291 |
292 | public float getHzPerSample() {
293 | return hzPerSample;
294 | }
295 |
296 | public float getDetectedFrequency() {
297 | return detectedFrequency;
298 | }
299 |
300 | public float getTargetFrequency() {
301 | return targetFrequency;
302 | }
303 |
304 | public int getTargetPitchIndex() {
305 | return targetPitchIndex;
306 | }
307 |
308 | public boolean isValid() {
309 | return valid;
310 | }
311 |
312 | public boolean isVibrate() {
313 | return vibrate;
314 | }
315 |
316 | public void setVibrate(boolean vibrate) {
317 | this.vibrate = vibrate;
318 | }
319 |
320 | public float getLastDetectedFrequency() {
321 | return lastDetectedFrequency;
322 | }
323 |
324 | public float getLastTargetFrequency() {
325 | return lastTargetFrequency;
326 | }
327 |
328 | public interface GuitarTunerCallbackInterface {
329 | public boolean process(GuitarTuner guitarTuner);
330 | }
331 |
332 | }
333 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5 | 51 Franklin Street, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Library General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C) 19yy
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License
307 | along with this program; if not, write to the Free Software
308 | Foundation, Inc., 51 Franklin Street, Boston, MA 02110-1301 USA
309 |
310 |
311 | Also add information on how to contact you by electronic and paper mail.
312 |
313 | If the program is interactive, make it output a short notice like this
314 | when it starts in an interactive mode:
315 |
316 | Gnomovision version 69, Copyright (C) 19yy name of author
317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
318 | This is free software, and you are welcome to redistribute it
319 | under certain conditions; type `show c' for details.
320 |
321 | The hypothetical commands `show w' and `show c' should show the appropriate
322 | parts of the General Public License. Of course, the commands you use may
323 | be called something other than `show w' and `show c'; they could even be
324 | mouse-clicks or menu items--whatever suits your program.
325 |
326 | You should also get your employer (if you work as a programmer) or your
327 | school, if any, to sign a "copyright disclaimer" for the program, if
328 | necessary. Here is a sample; alter the names:
329 |
330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
331 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
332 |
333 | , 1 April 1989
334 | Ty Coon, President of Vice
335 |
336 | This General Public License does not permit incorporating your program into
337 | proprietary programs. If your program is a subroutine library, you may
338 | consider it more useful to permit linking proprietary applications with the
339 | library. If this is what you want to do, use the GNU Library General
340 | Public License instead of this License.
341 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/mantz_it/wearguitartuner/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.mantz_it.wearguitartuner;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.ProgressDialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.content.SharedPreferences;
8 | import android.graphics.Color;
9 | import android.net.Uri;
10 | import android.os.Bundle;
11 | import android.os.Vibrator;
12 | import android.preference.PreferenceManager;
13 | import android.support.v7.app.ActionBarActivity;
14 | import android.util.Log;
15 | import android.view.Menu;
16 | import android.view.MenuItem;
17 | import android.view.View;
18 | import android.widget.CompoundButton;
19 | import android.widget.FrameLayout;
20 | import android.widget.ImageView;
21 | import android.widget.LinearLayout;
22 | import android.widget.Switch;
23 | import android.widget.Toast;
24 |
25 | import com.google.android.gms.common.ConnectionResult;
26 | import com.google.android.gms.common.api.GoogleApiClient;
27 | import com.google.android.gms.common.api.ResultCallback;
28 | import com.google.android.gms.wearable.MessageApi;
29 | import com.google.android.gms.wearable.MessageEvent;
30 | import com.google.android.gms.wearable.Node;
31 | import com.google.android.gms.wearable.NodeApi;
32 | import com.google.android.gms.wearable.Wearable;
33 | import com.mantz_it.guitartunerlibrary.AudioProcessingEngine;
34 | import com.mantz_it.guitartunerlibrary.GuitarTuner;
35 | import com.mantz_it.guitartunerlibrary.PreferenceSyncHelper;
36 | import com.mantz_it.guitartunerlibrary.TunerSkin;
37 | import com.mantz_it.guitartunerlibrary.TunerSurface;
38 | import com.mantz_it.guitartunerlibrary.TunerWearableListenerService;
39 |
40 | import java.io.BufferedReader;
41 | import java.io.IOException;
42 | import java.io.InputStreamReader;
43 | import java.io.UnsupportedEncodingException;
44 |
45 | /**
46 | *
Wear Guitar Tuner - Handheld Main Activity
47 | *
48 | * Module: MainActivity.java
49 | * Description: Main Activity on the handheld device. Will show a quick intro at the first
50 | * execution and provide settings for the wear application.
51 | *
52 | * @author Dennis Mantz
53 | *
54 | * Copyright (C) 2014 Dennis Mantz
55 | * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
56 | *
57 | * This library is free software; you can redistribute it and/or
58 | * modify it under the terms of the GNU General Public
59 | * License as published by the Free Software Foundation; either
60 | * version 2 of the License, or (at your option) any later version.
61 | *
62 | * This library is distributed in the hope that it will be useful,
63 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
64 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
65 | * General Public License for more details.
66 | *
67 | * You should have received a copy of the GNU General Public
68 | * License along with this library; if not, write to the Free Software
69 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
70 | */
71 | public class MainActivity extends ActionBarActivity implements GoogleApiClient.ConnectionCallbacks,
72 | GoogleApiClient.OnConnectionFailedListener, MessageApi.MessageListener, NodeApi.NodeListener,
73 | SharedPreferences.OnSharedPreferenceChangeListener,
74 | CompoundButton.OnCheckedChangeListener, View.OnClickListener {
75 | private static final String LOGTAG = "MainActivity";
76 | private LinearLayout ll_welcomeCard;
77 | private LinearLayout ll_skinChooser;
78 | private FrameLayout fl_preview;
79 | private ImageView[] iv_skins;
80 | private Switch sw_vibrate;
81 | ProgressDialog progressDialog;
82 |
83 | private TunerSurface tunerSurface;
84 | private GuitarTuner guitarTuner;
85 | private AudioProcessingEngine audioProcessingEngine;
86 | private GoogleApiClient googleApiClient;
87 | private Node wearableNode;
88 | private SharedPreferences preferences;
89 |
90 |
91 | @Override
92 | protected void onCreate(Bundle savedInstanceState) {
93 | super.onCreate(savedInstanceState);
94 | setContentView(R.layout.activity_main);
95 |
96 | // Get reference to the shared preferences:
97 | preferences = PreferenceManager.getDefaultSharedPreferences(this);
98 | preferences.registerOnSharedPreferenceChangeListener(this);
99 |
100 | // Show intro slides if this is the first execution:
101 | if(preferences.getBoolean(getString(R.string.pref_mainActivityFirstStart), true)) {
102 | Intent intent = new Intent(this,IntroActivity.class);
103 | startActivity(intent);
104 | finish();
105 | }
106 |
107 | ll_welcomeCard = (LinearLayout) findViewById(R.id.ll_welcomeCard);
108 | fl_preview = (FrameLayout) findViewById(R.id.fl_preview);
109 | tunerSurface = (TunerSurface) findViewById(R.id.sv_tunerSurface);
110 | ll_skinChooser = (LinearLayout) findViewById(R.id.ll_skinChooser);
111 | sw_vibrate = (Switch) findViewById(R.id.sw_vibrate);
112 | sw_vibrate.setChecked(preferences.getBoolean(getString(R.string.pref_vibration_enabled), true));
113 | sw_vibrate.setOnCheckedChangeListener(this);
114 |
115 | // Fill the list of available skins:
116 | iv_skins = new ImageView[TunerSkin.getTunerSkinCount()];
117 | for (int i = 0; i < TunerSkin.getTunerSkinCount(); i++) {
118 | iv_skins[i] = new ImageView(this);
119 | iv_skins[i].setImageResource(TunerSkin.getTunerSkinThumbnailResource(i,
120 | preferences.getBoolean(getString(R.string.pref_roundScreen), false)));
121 | iv_skins[i].setAdjustViewBounds(true);
122 | iv_skins[i].setMaxHeight(300);
123 | iv_skins[i].setId(i); // To extract the index later
124 | iv_skins[i].setOnClickListener(this);
125 | iv_skins[i].setClickable(true);
126 | ll_skinChooser.addView(iv_skins[i]);
127 | }
128 |
129 | // Create a GuitarTuner instance:
130 | guitarTuner = new GuitarTuner(tunerSurface, (Vibrator) getSystemService(VIBRATOR_SERVICE));
131 |
132 | // Show welcome card until dismissed by the user:
133 | if(preferences.getBoolean(getString(R.string.pref_showWelcomeCard), true)) {
134 | ll_welcomeCard.setVisibility(View.VISIBLE);
135 | }
136 |
137 | googleApiClient = new GoogleApiClient.Builder(this)
138 | .addConnectionCallbacks(this)
139 | .addOnConnectionFailedListener(this)
140 | .addApi(Wearable.API)
141 | .build();
142 |
143 | // Apply preferences:
144 | // tuner skin:
145 | int skinIndex = preferences.getInt(getString(R.string.pref_skinIndex),0);
146 | tunerSurface.setTunerSkin(TunerSkin.getTunerSkinInstance(skinIndex, this));
147 | iv_skins[skinIndex].setBackgroundColor(Color.DKGRAY);
148 | // screen shape:
149 | tunerSurface.setRound(preferences.getBoolean(getString(R.string.pref_roundScreen), false));
150 | // vibration:
151 | guitarTuner.setVibrate(preferences.getBoolean(getString(R.string.pref_vibration_enabled), true));
152 | }
153 |
154 | @Override
155 | protected void onStart() {
156 | super.onStart();
157 |
158 | // connect the google api client
159 | googleApiClient.connect();
160 | }
161 |
162 | @Override
163 | protected void onResume() {
164 | super.onResume();
165 |
166 | // create and start the audio processing thread (guitar tuner thread)
167 | audioProcessingEngine = new AudioProcessingEngine(guitarTuner);
168 | audioProcessingEngine.start();
169 | }
170 |
171 | @Override
172 | protected void onPause() {
173 | super.onPause();
174 |
175 | // stop the audio processing thread
176 | if(audioProcessingEngine != null) {
177 | audioProcessingEngine.stopProcessing();
178 | try {
179 | audioProcessingEngine.interrupt();
180 | audioProcessingEngine.join(250);
181 | } catch (InterruptedException e) {
182 | Log.e(LOGTAG, "onPause: Interrupted while joining audioProcessingEngine!");
183 | }
184 | }
185 | }
186 |
187 | @Override
188 | protected void onStop() {
189 | // disconnect the google api client
190 | if (googleApiClient != null && googleApiClient.isConnected()) {
191 | Wearable.MessageApi.removeListener(googleApiClient, this);
192 | googleApiClient.disconnect();
193 | }
194 | super.onStop();
195 | }
196 |
197 | /**
198 | * (ConnectionCallbacks)
199 | * Gets called after googleApiClient.connect() was executed successfully
200 | */
201 | @Override
202 | public void onConnected(Bundle connectionHint) {
203 | Log.d(LOGTAG, "onConnected: googleApiClient connected!");
204 |
205 | // Enumerate nodes:
206 | Wearable.NodeApi.getConnectedNodes(googleApiClient).setResultCallback(new ResultCallback() {
207 | @Override
208 | public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
209 | for (Node node : getConnectedNodesResult.getNodes()) {
210 | Log.i(LOGTAG, "onConnected: Found node: " + node.getDisplayName() + " (" + node.getId() + ")");
211 | wearableNode = node; // for now we just expect one single node to be found..
212 | }
213 | }
214 | });
215 |
216 | // Register message and node listener:
217 | Wearable.MessageApi.addListener(googleApiClient, this); // will execute onMessageReceived() if a message arrives
218 | Wearable.NodeApi.addListener(googleApiClient, this); // will execute onPeerConnected() and onPeerDisconnected()
219 | }
220 |
221 | /**
222 | * (ConnectionCallbacks)
223 | * Gets called after googleApiClient.connect() was executed successfully and the api connection is suspended again
224 | */
225 | @Override
226 | public void onConnectionSuspended(int cause) {
227 | Log.d(LOGTAG, "onConnectionSuspended: googleApiClient suspended: " + cause);
228 | }
229 |
230 | /**
231 | * (OnConnectionFailedListener)
232 | * Gets called after googleApiClient.connect() was executed and failed
233 | */
234 | @Override
235 | public void onConnectionFailed(ConnectionResult result) {
236 | Log.d(LOGTAG, "onConnectionFailed: googleApiClient connection failed: " + result.toString());
237 | }
238 |
239 | /**
240 | * (NodeListener)
241 | * Gets called if a new node (a wearable) is connected to the phone
242 | */
243 | @Override
244 | public void onPeerConnected(Node node) {
245 | Log.i(LOGTAG, "onPeerConnected: Node " + node.getId() + " connected!");
246 | wearableNode = node;
247 | }
248 |
249 | /**
250 | * (NodeListener)
251 | * Gets called if a node (a wearable) disconnects from the phone
252 | */
253 | @Override
254 | public void onPeerDisconnected(Node node) {
255 | Log.i(LOGTAG, "onPeerDisconnected: Node " + node.getId() + " has disconnected!");
256 | if(wearableNode.getId().equals(node.getId())) {
257 | Log.i(LOGTAG, "onPeerDisconnected: Setting wearable node to null!");
258 | wearableNode = null;
259 | }
260 | }
261 |
262 | /**
263 | * (MessageListener)
264 | * will receive the response of the get log request and show an alert dialog containing the
265 | * wearable log data
266 | */
267 | @Override
268 | public void onMessageReceived(MessageEvent messageEvent) {
269 | Log.i(LOGTAG, "onMessageReceived: received a message (" + messageEvent.getPath() + ") from "
270 | + messageEvent.getSourceNodeId());
271 |
272 | if(messageEvent.getPath().equals(TunerWearableListenerService.GET_LOG_RESPONSE_MESSAGE_PATH)) {
273 | try {
274 | final String log = new String(messageEvent.getData(), "UTF-8");
275 | // Show dialog:
276 | runOnUiThread(new Runnable() {
277 | @Override
278 | public void run() {
279 | AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
280 | .setTitle(getString(R.string.wearable_log))
281 | .setMessage(log)
282 | .setPositiveButton(getString(R.string.share), new DialogInterface.OnClickListener() {
283 | public void onClick(DialogInterface dialog, int whichButton) {
284 | // Invoke email app:
285 | Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "dennis.mantz@googlemail.com", null));
286 | intent.putExtra(Intent.EXTRA_SUBJECT, "Wear Guitar Tuner log report (wearable)");
287 | intent.putExtra(Intent.EXTRA_TEXT, log);
288 | startActivity(Intent.createChooser(intent, getString(R.string.chooseMailApp)));
289 | }
290 | })
291 | .setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
292 | public void onClick(DialogInterface dialog, int whichButton) {
293 | // do nothing
294 | }
295 | })
296 | .create();
297 | dialog.setOnShowListener(new DialogInterface.OnShowListener() {
298 | @Override
299 | public void onShow(DialogInterface dialog) {
300 | // dismiss process dialog:
301 | if(progressDialog != null)
302 | progressDialog.dismiss();
303 | }
304 | });
305 | dialog.show();
306 | }
307 | });
308 | } catch (UnsupportedEncodingException e) {
309 | Log.e(LOGTAG, "onMessageReceived: unsupported Encoding (UTF-8): " + e.getMessage());
310 | if(progressDialog != null)
311 | progressDialog.dismiss();
312 | }
313 | }
314 | }
315 |
316 | /**
317 | * (OnSharedPreferenceChangeListener)
318 | * @param sharedPreferences shared prefs instance
319 | * @param key key that changed
320 | */
321 | @Override
322 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
323 | Log.d(LOGTAG, "onSharedPreferenceChanged: preference changed! (key=" + key + ")");
324 | if(key.equals(getString(R.string.pref_roundScreen))) {
325 | tunerSurface.setRound(preferences.getBoolean(key, false));
326 | } else if(key.equals(getString(R.string.pref_vibration_enabled))) {
327 | boolean vibrate = preferences.getBoolean(key, true);
328 | guitarTuner.setVibrate(vibrate);
329 | sw_vibrate.setChecked(vibrate);
330 | } else if(key.equals(getString(R.string.pref_skinIndex))) {
331 | int skinIndex = preferences.getInt(key, 0);
332 | tunerSurface.setTunerSkin(TunerSkin.getTunerSkinInstance(skinIndex, this));
333 | for(ImageView iv: iv_skins)
334 | iv.setBackgroundColor(Color.TRANSPARENT);
335 | iv_skins[skinIndex].setBackgroundColor(Color.DKGRAY);
336 | }
337 | }
338 |
339 | @Override
340 | public boolean onCreateOptionsMenu(Menu menu) {
341 | // Inflate the menu; this adds items to the action bar if it is present.
342 | getMenuInflater().inflate(R.menu.menu_main, menu);
343 | return true;
344 | }
345 |
346 | @Override
347 | public boolean onOptionsItemSelected(MenuItem item) {
348 | // Handle action bar item clicks here. The action bar will
349 | // automatically handle clicks on the Home/Up button, so long
350 | // as you specify a parent activity in AndroidManifest.xml.
351 | int id = item.getItemId();
352 |
353 | if (id == R.id.action_showHandheldLog) {
354 | showHandheldLog();
355 | return true;
356 | } else if (id == R.id.action_showWearableLog) {
357 | queryWearableLog();
358 | return true;
359 | } else if (id == R.id.action_donate) {
360 | // open in browser:
361 | String donationUrl = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CWZL4HQC9SE86";
362 | Intent i = new Intent(Intent.ACTION_VIEW);
363 | i.setData(Uri.parse(donationUrl));
364 | startActivity(i);
365 | return true;
366 | }
367 |
368 | return super.onOptionsItemSelected(item);
369 | }
370 |
371 | /**
372 | * called if the button 'i got it' was pressed (see activity_main.xml)
373 | * @param view instance of the button
374 | */
375 | public void onBtHideWelcomeMsgClicked(View view) {
376 | ll_welcomeCard.setVisibility(View.GONE);
377 |
378 | // update the value in the preferences:
379 | SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(this).edit();
380 | edit.putBoolean(getString(R.string.pref_showWelcomeCard), false);
381 | edit.apply();
382 | }
383 |
384 | /**
385 | * will use the connected googleApiClient to send a request message to the wearable node asking
386 | * for the log data.
387 | */
388 | public void queryWearableLog() {
389 | if(!googleApiClient.isConnected()) {
390 | Log.e(LOGTAG, "queryWearableLog: google api client not connected!");
391 | Toast.makeText(this, getString(R.string.googleApiClient_not_connected), Toast.LENGTH_LONG).show();
392 | return;
393 | }
394 | if(wearableNode == null) {
395 | Log.e(LOGTAG, "queryWearableLog: wearable node not connected!");
396 | Toast.makeText(this, getString(R.string.wearable_node_not_connected), Toast.LENGTH_LONG).show();
397 | return;
398 | }
399 |
400 | // show a progress dialog
401 | if(progressDialog == null)
402 | progressDialog = new ProgressDialog(this);
403 | progressDialog.setTitle(getString(R.string.loading));
404 | progressDialog.setMessage(getString(R.string.querying_wearable_for_log));
405 | progressDialog.show();
406 |
407 | // Send it to the wearable device:
408 | Wearable.MessageApi.sendMessage(googleApiClient, wearableNode.getId(),
409 | TunerWearableListenerService.GET_LOG_MESSAGE_PATH, null).setResultCallback(new ResultCallback() {
410 | @Override
411 | public void onResult(MessageApi.SendMessageResult sendMessageResult) {
412 | if (!sendMessageResult.getStatus().isSuccess()) {
413 | Log.e(LOGTAG, "queryWearableLog: Failed to query log from the wearable: "
414 | + sendMessageResult.getStatus().getStatusMessage());
415 | Toast.makeText(MainActivity.this, getString(R.string.failed_to_query_wearable_for_log)
416 | + sendMessageResult.getStatus().getStatusMessage(), Toast.LENGTH_LONG).show();
417 | // dismiss process dialog:
418 | if (progressDialog != null)
419 | progressDialog.dismiss();
420 | } else
421 | Log.d(LOGTAG, "queryWearableLog: message (" + sendMessageResult.getRequestId() + ") was sent!");
422 | }
423 | });
424 | }
425 |
426 | /**
427 | * will show a alert dialog containing the log data of the smartphone
428 | */
429 | public void showHandheldLog() {
430 | // Read the log:
431 | StringBuilder log = new StringBuilder();
432 | try {
433 | Process process = Runtime.getRuntime().exec("logcat -d");
434 | BufferedReader bufferedReader = new BufferedReader(
435 | new InputStreamReader(process.getInputStream()));
436 |
437 | String line = "";
438 | String newline = System.getProperty("line.separator");
439 | while ((line = bufferedReader.readLine()) != null) {
440 | log.append(line);
441 | log.append(newline);
442 | }
443 | } catch (IOException e) {
444 | Log.e(LOGTAG, "showHandheldLog: Couldn't read log: " + e.getMessage());
445 | return;
446 | }
447 | final String logString = log.toString();
448 | new AlertDialog.Builder(MainActivity.this)
449 | .setTitle(getString(R.string.handheld_log))
450 | .setMessage(log)
451 | .setPositiveButton(getString(R.string.share), new DialogInterface.OnClickListener() {
452 | public void onClick(DialogInterface dialog, int whichButton) {
453 | // Invoke email app:
454 | Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "dennis.mantz@googlemail.com", null));
455 | intent.putExtra(Intent.EXTRA_SUBJECT, "Wear Guitar Tuner log report (handheld)");
456 | intent.putExtra(Intent.EXTRA_TEXT, logString);
457 | startActivity(Intent.createChooser(intent, getString(R.string.chooseMailApp)));
458 | }
459 | })
460 | .setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
461 | public void onClick(DialogInterface dialog, int whichButton) {
462 | // do nothing
463 | }
464 | })
465 | .create()
466 | .show();
467 | }
468 |
469 | @Override
470 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
471 | // only take action if the new setting is actually different from the setting in the prefs:
472 | if(preferences.getBoolean(getString(R.string.pref_vibration_enabled), true) != isChecked) {
473 | // Change in prefs:
474 | SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(this).edit();
475 | edit.putBoolean(getString(R.string.pref_vibration_enabled), isChecked);
476 | edit.apply();
477 |
478 | // send message to wearable:
479 | PreferenceSyncHelper.syncBooleanPref(googleApiClient, wearableNode.getId(),
480 | getString(R.string.pref_vibration_enabled), isChecked);
481 | }
482 | }
483 |
484 | /**
485 | * This is the callback method of the OnClickListener of the ImageViews in the skin chooser
486 | * @param v ImageView (thumbnail). Contains the skin index in the ID field of the view
487 | */
488 | @Override
489 | public void onClick(View v) {
490 | // Change in prefs:
491 | SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(this).edit();
492 | edit.putInt(getString(R.string.pref_skinIndex), v.getId());
493 | edit.apply();
494 |
495 | // send message to wearable:
496 | if(wearableNode != null) {
497 | PreferenceSyncHelper.syncIntegerPref(googleApiClient, wearableNode.getId(),
498 | getString(R.string.pref_skinIndex), v.getId());
499 | }
500 | }
501 | }
502 |
--------------------------------------------------------------------------------