├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── ic_launcher-playstore.png
│ │ ├── assets
│ │ │ ├── stopWords
│ │ │ │ ├── stop.tflite
│ │ │ │ └── stop.json
│ │ │ ├── wakeWords
│ │ │ │ ├── alexa.tflite
│ │ │ │ ├── hey_luna.tflite
│ │ │ │ ├── hey_jarvis.tflite
│ │ │ │ ├── okay_nabu.tflite
│ │ │ │ ├── hey_mycroft.tflite
│ │ │ │ ├── hey_peppa_pig.tflite
│ │ │ │ ├── okay_computer.tflite
│ │ │ │ ├── choo_choo_homie.tflite
│ │ │ │ ├── hey_home_assistant.tflite
│ │ │ │ ├── alexa.json
│ │ │ │ ├── hey_jarvis.json
│ │ │ │ ├── hey_mycroft.json
│ │ │ │ ├── hey_home_assistant.json
│ │ │ │ ├── hey_luna.json
│ │ │ │ ├── okay_nabu.json
│ │ │ │ ├── hey_peppa_pig.json
│ │ │ │ ├── choo_choo_homie.json
│ │ │ │ └── okay_computer.json
│ │ │ └── sounds
│ │ │ │ ├── timer_finished.flac
│ │ │ │ ├── wake_word_triggered.flac
│ │ │ │ └── LICENSE.md
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── values
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── themes.xml
│ │ │ │ └── strings.xml
│ │ │ ├── mipmap-anydpi
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── drawable
│ │ │ │ ├── arrow_back_24px.xml
│ │ │ │ ├── more_vert_24px.xml
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── ava
│ │ │ │ ├── ui
│ │ │ │ ├── theme
│ │ │ │ │ └── Type.kt
│ │ │ │ ├── screens
│ │ │ │ │ └── settings
│ │ │ │ │ │ ├── components
│ │ │ │ │ │ ├── SwitchSetting.kt
│ │ │ │ │ │ ├── DialogSettingItem.kt
│ │ │ │ │ │ ├── NumberSetting.kt
│ │ │ │ │ │ └── SettingItem.kt
│ │ │ │ │ │ └── SettingsScreen.kt
│ │ │ │ ├── Navigation.kt
│ │ │ │ └── services
│ │ │ │ │ └── ServiceViewModel.kt
│ │ │ │ ├── esphome
│ │ │ │ ├── entities
│ │ │ │ │ ├── Entity.kt
│ │ │ │ │ ├── SwitchEntity.kt
│ │ │ │ │ └── MediaPlayerEntity.kt
│ │ │ │ └── voicesatellite
│ │ │ │ │ └── VoiceSatellitePlayer.kt
│ │ │ │ ├── microwakeword
│ │ │ │ ├── WakeWordProvider.kt
│ │ │ │ ├── WakeWord.kt
│ │ │ │ ├── AssetWakeWordProvider.kt
│ │ │ │ ├── TensorBuffer.kt
│ │ │ │ └── WakeWordDetector.kt
│ │ │ │ ├── permissions
│ │ │ │ └── PermissionsHelper.kt
│ │ │ │ ├── utils
│ │ │ │ ├── NetworkUtils.kt
│ │ │ │ ├── BufferUtils.kt
│ │ │ │ ├── EspHomeStateTranslations.kt
│ │ │ │ └── AsyncUtils.kt
│ │ │ │ ├── settings
│ │ │ │ ├── SettingState.kt
│ │ │ │ ├── SettingsStore.kt
│ │ │ │ ├── MicrophoneSettings.kt
│ │ │ │ ├── SettingsSerializer.kt
│ │ │ │ ├── PlayerSettings.kt
│ │ │ │ └── VoiceSatelliteSettings.kt
│ │ │ │ ├── nsd
│ │ │ │ ├── VoiceSatelliteNsd.kt
│ │ │ │ └── NsdRegistration.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── players
│ │ │ │ ├── AudioFocusRegistration.kt
│ │ │ │ └── TtsPlayer.kt
│ │ │ │ ├── server
│ │ │ │ └── ClientConnection.kt
│ │ │ │ ├── notifications
│ │ │ │ └── NotificationHelper.kt
│ │ │ │ ├── wakelocks
│ │ │ │ └── WifiWakeLock.kt
│ │ │ │ └── audio
│ │ │ │ └── MicrophoneInput.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── ava
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── ava
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── esphomeproto
├── .gitignore
├── src
│ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── esphomeproto
│ │ │ └── api
│ │ │ └── VoiceAssistantFeature.kt
│ │ └── proto
│ │ └── api_options.proto
└── build.gradle.kts
├── microfeatures
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── cpp
│ │ │ ├── tensorflow
│ │ │ │ └── lite
│ │ │ │ │ └── experimental
│ │ │ │ │ └── microfrontend
│ │ │ │ │ ├── README.md
│ │ │ │ │ └── lib
│ │ │ │ │ ├── kiss_fft_int16.cc
│ │ │ │ │ ├── log_scale_io.c
│ │ │ │ │ ├── log_scale_util.c
│ │ │ │ │ ├── frontend_io.h
│ │ │ │ │ ├── log_scale_io.h
│ │ │ │ │ ├── fft_io.h
│ │ │ │ │ ├── fft_util.h
│ │ │ │ │ ├── window_io.h
│ │ │ │ │ ├── log_lut.h
│ │ │ │ │ ├── filterbank_io.h
│ │ │ │ │ ├── log_scale.h
│ │ │ │ │ ├── kiss_fft_int16.h
│ │ │ │ │ ├── noise_reduction_io.h
│ │ │ │ │ ├── fft.h
│ │ │ │ │ ├── noise_reduction.h
│ │ │ │ │ ├── pcan_gain_control.h
│ │ │ │ │ ├── noise_reduction_io.c
│ │ │ │ │ ├── fft_io.c
│ │ │ │ │ ├── window.h
│ │ │ │ │ ├── log_scale_util.h
│ │ │ │ │ ├── window_util.h
│ │ │ │ │ ├── log_lut.c
│ │ │ │ │ ├── frontend_memmap_generator.c
│ │ │ │ │ ├── filterbank_util.h
│ │ │ │ │ ├── window_io.c
│ │ │ │ │ ├── noise_reduction_util.c
│ │ │ │ │ ├── kiss_fft_common.h
│ │ │ │ │ ├── noise_reduction_util.h
│ │ │ │ │ ├── fft.cc
│ │ │ │ │ ├── pcan_gain_control.c
│ │ │ │ │ ├── frontend_memmap_main.c
│ │ │ │ │ ├── fft_test.cc
│ │ │ │ │ ├── noise_reduction.c
│ │ │ │ │ ├── frontend_util.h
│ │ │ │ │ ├── log_scale_test.cc
│ │ │ │ │ ├── pcan_gain_control_test.cc
│ │ │ │ │ ├── pcan_gain_control_util.h
│ │ │ │ │ ├── filterbank.h
│ │ │ │ │ ├── frontend_main.c
│ │ │ │ │ ├── window_util.c
│ │ │ │ │ ├── fft_util.cc
│ │ │ │ │ ├── frontend.h
│ │ │ │ │ ├── window.c
│ │ │ │ │ ├── noise_reduction_test.cc
│ │ │ │ │ ├── filterbank_io.c
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── frontend.c
│ │ │ │ │ └── frontend_io.c
│ │ │ └── kissfft
│ │ │ │ └── tools
│ │ │ │ ├── kiss_fftnd.h
│ │ │ │ ├── kiss_fftr.h
│ │ │ │ ├── kiss_fftndr.h
│ │ │ │ ├── kfc.h
│ │ │ │ └── Makefile
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── microfeatures
│ │ │ └── MicroFrontend.kt
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── microfeatures
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── microfeatures
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── .idea
├── .gitignore
├── compiler.xml
├── kotlinc.xml
├── vcs.xml
├── AndroidProjectSystem.xml
├── migrations.xml
├── deploymentTargetSelector.xml
├── deviceManager.xml
├── misc.xml
├── gradle.xml
└── runConfigurations.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle.kts
├── gradle.properties
├── .github
└── workflows
│ └── release.yml
└── gradlew.bat
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/esphomeproto/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/microfeatures/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/microfeatures/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/microfeatures/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/assets/stopWords/stop.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/stopWords/stop.tflite
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/alexa.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/wakeWords/alexa.tflite
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/hey_luna.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/wakeWords/hey_luna.tflite
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/assets/sounds/timer_finished.flac:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/sounds/timer_finished.flac
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/hey_jarvis.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/wakeWords/hey_jarvis.tflite
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/okay_nabu.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/wakeWords/okay_nabu.tflite
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/hey_mycroft.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/wakeWords/hey_mycroft.tflite
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/hey_peppa_pig.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/wakeWords/hey_peppa_pig.tflite
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/okay_computer.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/wakeWords/okay_computer.tflite
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/assets/sounds/wake_word_triggered.flac:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/sounds/wake_word_triggered.flac
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/choo_choo_homie.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/wakeWords/choo_choo_homie.tflite
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/hey_home_assistant.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/assets/wakeWords/hey_home_assistant.tflite
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brownard/Ava/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 |
5 | val AppTypography = Typography()
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3263C3
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/AndroidProjectSystem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Oct 18 20:57:02 BST 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/esphome/entities/Entity.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.esphome.entities
2 |
3 | import com.google.protobuf.MessageLite
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface Entity {
7 | fun handleMessage(message: MessageLite): Flow
8 | fun subscribe(): Flow
9 | }
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/assets/sounds/LICENSE.md:
--------------------------------------------------------------------------------
1 | [Home Assistant Voice Preview Edition Sounds](https://github.com/esphome/home-assistant-voice-pe/tree/dev/sounds) © 2024 by [Clayton Charles Tapp](https://www.cctaudio.com/) is licensed under [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)
2 |
--------------------------------------------------------------------------------
/esphomeproto/src/main/java/com/example/esphomeproto/api/VoiceAssistantFeature.kt:
--------------------------------------------------------------------------------
1 | package com.example.esphomeproto.api
2 |
3 | enum class VoiceAssistantFeature(val flag: Int) {
4 | VOICE_ASSISTANT(1 shl 0),
5 | SPEAKER(1 shl 1),
6 | API_AUDIO(1 shl 2),
7 | TIMERS(1 shl 3),
8 | ANNOUNCE(1 shl 4),
9 | START_CONVERSATION(1 shl 5),
10 | }
--------------------------------------------------------------------------------
/.idea/deploymentTargetSelector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/README.md:
--------------------------------------------------------------------------------
1 | This directory contains the subset of functionality that is needed to run the
2 | micro_speech example with TFLM.
3 |
4 | The source of truth for the experimental microfrontend in TfLite is at:
5 | https://github.com/tensorflow/tflite-micro/blob/main/tensorflow/lite/experimental/microfrontend
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/microwakeword/WakeWordProvider.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.microwakeword
2 |
3 | import java.nio.ByteBuffer
4 |
5 | data class WakeWordWithId(val id: String, val wakeWord: WakeWord)
6 |
7 | interface WakeWordProvider {
8 |
9 | fun getWakeWords(): List
10 |
11 | fun loadWakeWordModel(model: String): ByteBuffer
12 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/kiss_fft_int16.cc:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "tensorflow/lite/experimental/microfrontend/lib/kiss_fft_common.h"
4 |
5 | #define FIXED_POINT 16
6 | namespace kissfft_fixed16 {
7 | #include "kiss_fft.c"
8 | #include "tools/kiss_fftr.c"
9 | } // namespace kissfft_fixed16
10 | #undef FIXED_POINT
--------------------------------------------------------------------------------
/.idea/deviceManager.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/permissions/PermissionsHelper.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.permissions
2 |
3 | import android.Manifest
4 | import android.os.Build
5 |
6 | val VOICE_SATELLITE_PERMISSIONS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
7 | arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.POST_NOTIFICATIONS)
8 | } else {
9 | arrayOf(Manifest.permission.RECORD_AUDIO)
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/utils/NetworkUtils.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.utils
2 |
3 | import kotlin.random.Random
4 |
5 | @OptIn(ExperimentalStdlibApi::class)
6 | fun getRandomMacAddressString(): String {
7 | val random = Random.Default
8 | val bytes = random.nextBytes(6)
9 | return bytes.toHexString(HexFormat {
10 | upperCase = true
11 | bytes { byteSeparator = ":" }
12 | })
13 | }
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/ava/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/assets/stopWords/stop.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "micro",
3 | "wake_word": "Stop",
4 | "author": "Kevin Ahrendt",
5 | "website": "https://www.kevinahrendt.com/",
6 | "model": "stop.tflite",
7 | "trained_languages": ["en"],
8 | "version": 2,
9 | "micro": {
10 | "probability_cutoff": 0.5,
11 | "feature_step_size": 10,
12 | "sliding_window_size": 5,
13 | "tensor_arena_size": 21000,
14 | "minimum_esphome_version": "2024.7.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/alexa.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "micro",
3 | "wake_word": "Alexa",
4 | "author": "Kevin Ahrendt",
5 | "website": "https://www.kevinahrendt.com/",
6 | "model": "alexa.tflite",
7 | "trained_languages": ["en"],
8 | "version": 2,
9 | "micro": {
10 | "probability_cutoff": 0.9,
11 | "sliding_window_size": 5,
12 | "feature_step_size": 10,
13 | "tensor_arena_size": 22348,
14 | "minimum_esphome_version": "2024.7.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/microfeatures/src/test/java/com/example/microfeatures/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.microfeatures
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/hey_jarvis.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "micro",
3 | "wake_word": "Hey Jarvis",
4 | "author": "Kevin Ahrendt",
5 | "website": "https://www.kevinahrendt.com/",
6 | "model": "hey_jarvis.tflite",
7 | "trained_languages": ["en"],
8 | "version": 2,
9 | "micro": {
10 | "probability_cutoff": 0.97,
11 | "feature_step_size": 10,
12 | "sliding_window_size": 5,
13 | "tensor_arena_size": 22860,
14 | "minimum_esphome_version": "2024.7.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/hey_mycroft.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "micro",
3 | "wake_word": "Hey Mycroft",
4 | "author": "Kevin Ahrendt",
5 | "website": "https://www.kevinahrendt.com/",
6 | "model": "hey_mycroft.tflite",
7 | "trained_languages": ["en"],
8 | "version": 2,
9 | "micro": {
10 | "probability_cutoff": 0.95,
11 | "sliding_window_size": 5,
12 | "feature_step_size": 10,
13 | "tensor_arena_size": 23628,
14 | "minimum_esphome_version": "2024.7.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/kissfft/tools/kiss_fftnd.h:
--------------------------------------------------------------------------------
1 | #ifndef KISS_FFTND_H
2 | #define KISS_FFTND_H
3 |
4 | #include "kiss_fft.h"
5 |
6 | #ifdef __cplusplus
7 | extern "C" {
8 | #endif
9 |
10 | typedef struct kiss_fftnd_state * kiss_fftnd_cfg;
11 |
12 | kiss_fftnd_cfg kiss_fftnd_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem);
13 | void kiss_fftnd(kiss_fftnd_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);
14 |
15 | #ifdef __cplusplus
16 | }
17 | #endif
18 | #endif
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/arrow_back_24px.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/hey_home_assistant.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "micro",
3 | "wake_word": "Hey Home Assistant",
4 | "author": "Michael Hansen",
5 | "website": "https://www.home-assistant.io",
6 | "model": "hey_home_assistant.tflite",
7 | "trained_languages": ["en"],
8 | "version": 2,
9 | "micro": {
10 | "probability_cutoff": 0.97,
11 | "feature_step_size": 10,
12 | "sliding_window_size": 5,
13 | "tensor_arena_size": 30000,
14 | "minimum_esphome_version": "2024.7.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/hey_luna.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "micro",
3 | "wake_word": "hey_luna",
4 | "author": "adamlonsdale",
5 | "website": "https://github.com/adamlonsdale",
6 | "model": "hey_luna.tflite",
7 | "version": 2,
8 | "trained_languages": ["en"],
9 | "micro": {
10 | "probability_cutoff": 0.63,
11 | "sliding_window_size": 5,
12 | "feature_step_size": 10,
13 | "tensor_arena_size": 22860,
14 | "minimum_esphome_version": "2024.7.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/okay_nabu.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "micro",
3 | "wake_word": "Okay Nabu",
4 | "author": "Kevin Ahrendt",
5 | "website": "https://www.kevinahrendt.com/",
6 | "model": "okay_nabu.tflite",
7 | "trained_languages": ["en","nl","fr","de","it","es","sv"],
8 | "version": 2,
9 | "micro": {
10 | "probability_cutoff": 0.85,
11 | "feature_step_size": 10,
12 | "sliding_window_size": 5,
13 | "tensor_arena_size": 37000,
14 | "minimum_esphome_version": "2024.7.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/hey_peppa_pig.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "micro",
3 | "wake_word": "Hey Peppa Pig",
4 | "author": "Michael Hansen",
5 | "website": "https://www.home-assistant.io",
6 | "model": "hey_peppa_pig.tflite",
7 | "trained_languages": [
8 | "en"
9 | ],
10 | "version": 2,
11 | "micro": {
12 | "probability_cutoff": 0.97,
13 | "feature_step_size": 10,
14 | "sliding_window_size": 5,
15 | "tensor_arena_size": 30000,
16 | "minimum_esphome_version": "2024.7.0"
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/choo_choo_homie.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "micro",
3 | "wake_word": "Choo Choo Homie",
4 | "author": "Michael Hansen",
5 | "website": "https://www.home-assistant.io",
6 | "model": "choo_choo_homie.tflite",
7 | "trained_languages": [
8 | "en"
9 | ],
10 | "version": 2,
11 | "micro": {
12 | "probability_cutoff": 0.97,
13 | "feature_step_size": 10,
14 | "sliding_window_size": 5,
15 | "tensor_arena_size": 30000,
16 | "minimum_esphome_version": "2024.7.0"
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/assets/wakeWords/okay_computer.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "micro",
3 | "wake_word": "Okay Computer",
4 | "author": "Michael Hansen",
5 | "website": "https://www.home-assistant.io",
6 | "model": "okay_computer.tflite",
7 | "trained_languages": [
8 | "en"
9 | ],
10 | "version": 2,
11 | "micro": {
12 | "probability_cutoff": 0.97,
13 | "feature_step_size": 10,
14 | "sliding_window_size": 5,
15 | "tensor_arena_size": 30000,
16 | "minimum_esphome_version": "2024.7.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/microwakeword/WakeWord.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.microwakeword
2 |
3 | data class WakeWord(
4 | val type: String,
5 | val wake_word: String,
6 | val author: String,
7 | val website: String,
8 | val model: String,
9 | val trained_languages: Array,
10 | val version: Int,
11 | val micro: Micro,
12 | )
13 |
14 | data class Micro(
15 | val probability_cutoff: Float,
16 | val feature_step_size: Int,
17 | val sliding_window_size: Int,
18 | val tensor_arena_size: Int,
19 | val minimum_esphome_version: String,
20 | )
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/utils/BufferUtils.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.utils
2 |
3 | import java.nio.ByteBuffer
4 |
5 | fun ByteBuffer.fillFrom(src: ByteBuffer): Int {
6 | val remaining = remaining()
7 | if (remaining == 0)
8 | return 0
9 |
10 | val srcRemaining = src.remaining()
11 | if (srcRemaining <= remaining) {
12 | put(src)
13 | return srcRemaining
14 | } else {
15 | val currentLimit = src.limit()
16 | src.limit(src.position() + remaining)
17 | put(src)
18 | src.limit(currentLimit)
19 | return remaining
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/settings/SettingState.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.settings
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlinx.coroutines.flow.FlowCollector
5 | import kotlinx.coroutines.flow.distinctUntilChanged
6 | import kotlinx.coroutines.flow.first
7 |
8 | class SettingState(
9 | private val flow: Flow,
10 | private val set: suspend (T) -> Unit
11 | ) : Flow {
12 | suspend fun get() = flow.first()
13 | suspend fun set(value: T) = set.invoke(value)
14 | override suspend fun collect(collector: FlowCollector) =
15 | flow.distinctUntilChanged().collect(collector)
16 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | rootProject.name = "Ava"
23 | include(":app")
24 | include(":esphomeproto")
25 | include(":microfeatures")
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/nsd/VoiceSatelliteNsd.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.nsd
2 |
3 | import android.content.Context
4 |
5 | private const val VERSION = "2025.9.0"
6 |
7 | fun registerVoiceSatelliteNsd(
8 | context: Context,
9 | name: String,
10 | port: Int,
11 | macAddress: String
12 | ): NsdRegistration {
13 | val nsdRegistration = NsdRegistration(
14 | name = name,
15 | type = "_esphomelib._tcp",
16 | port = port,
17 | attributes = mapOf(
18 | Pair("version", VERSION),
19 | Pair("mac", macAddress),
20 | Pair("board", "host"),
21 | Pair("platform", "HOST"),
22 | Pair("network", "wifi")
23 | )
24 | )
25 | nsdRegistration.register(context)
26 | return nsdRegistration
27 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/ava/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.ava", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/res/drawable/more_vert_24px.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/microfeatures/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/microfeatures/src/androidTest/java/com/example/microfeatures/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.microfeatures
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.microfeatures.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/ui/screens/settings/components/SwitchSetting.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.ui.screens.settings.components
2 |
3 | import androidx.compose.material3.Switch
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.draw.alpha
7 |
8 | @Composable
9 | fun SwitchSetting(
10 | name: String,
11 | description: String,
12 | value: Boolean,
13 | enabled: Boolean = true,
14 | onCheckedChange: (Boolean) -> Unit
15 | ) {
16 | val modifier = if (enabled) Modifier else Modifier.alpha(0.5f)
17 | SettingItem(
18 | name = name,
19 | description = description,
20 | modifier = modifier
21 | ) {
22 | Switch(
23 | checked = value,
24 | onCheckedChange = onCheckedChange,
25 | enabled = enabled
26 | )
27 | }
28 | }
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/ui/Navigation.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.ui
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.navigation.compose.NavHost
5 | import androidx.navigation.compose.composable
6 | import androidx.navigation.compose.rememberNavController
7 | import com.example.ava.ui.screens.home.HomeScreen
8 | import com.example.ava.ui.screens.settings.SettingsScreen
9 | import kotlinx.serialization.Serializable
10 |
11 | @Serializable
12 | object Home
13 |
14 | @Serializable
15 | object Settings
16 |
17 | @Composable
18 | fun MainNavHost() {
19 | val navController = rememberNavController()
20 | NavHost(navController = navController, startDestination = Home) {
21 | composable {
22 | HomeScreen(navController)
23 | }
24 | composable {
25 | SettingsScreen(navController)
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/esphomeproto/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("java-library")
3 | alias(libs.plugins.google.protobuf)
4 | alias(libs.plugins.jetbrains.kotlin.jvm)
5 | }
6 |
7 | java {
8 | sourceCompatibility = JavaVersion.VERSION_11
9 | targetCompatibility = JavaVersion.VERSION_11
10 | }
11 |
12 | kotlin {
13 | compilerOptions {
14 | jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
15 | }
16 | }
17 |
18 | protobuf {
19 | protoc {
20 | artifact = "com.google.protobuf:protoc:4.26.1"
21 | }
22 | generateProtoTasks {
23 | ofSourceSet("main").forEach {
24 | it.builtins {
25 | getByName("java") {
26 | option("lite")
27 | }
28 | create("kotlin") {
29 | option("lite")
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
36 | dependencies {
37 | implementation(libs.kotlinx.coroutines.core)
38 | implementation(libs.protobuf.java)
39 | implementation(libs.protobuf.kotlin)
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/kissfft/tools/kiss_fftr.h:
--------------------------------------------------------------------------------
1 | #ifndef KISS_FTR_H
2 | #define KISS_FTR_H
3 |
4 | #include "kiss_fft.h"
5 | #ifdef __cplusplus
6 | extern "C++" {
7 | #endif
8 |
9 |
10 | /*
11 |
12 | Real optimized version can save about 45% cpu time vs. complex fft of a real seq.
13 |
14 |
15 |
16 | */
17 |
18 | typedef struct kiss_fftr_state *kiss_fftr_cfg;
19 |
20 |
21 | kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem);
22 | /*
23 | nfft must be even
24 |
25 | If you don't care to allocate space, use mem = lenmem = NULL
26 | */
27 |
28 |
29 | void kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata);
30 | /*
31 | input timedata has nfft scalar points
32 | output freqdata has nfft/2+1 complex points
33 | */
34 |
35 | void kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata);
36 | /*
37 | input freqdata has nfft/2+1 complex points
38 | output timedata has nfft scalar points
39 | */
40 |
41 | #define kiss_fftr_free free
42 |
43 | #ifdef __cplusplus
44 | }
45 | #endif
46 | #endif
47 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/log_scale_io.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/log_scale_io.h"
16 |
17 | void LogScaleWriteMemmap(FILE* fp, const struct LogScaleState* state,
18 | const char* variable) {
19 | fprintf(fp, "%s->enable_log = %d;\n", variable, state->enable_log);
20 | fprintf(fp, "%s->scale_shift = %d;\n", variable, state->scale_shift);
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/ui/screens/settings/components/DialogSettingItem.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.ui.screens.settings.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.draw.alpha
9 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
10 |
11 | @Composable
12 | fun DialogSettingItem(
13 | name: String,
14 | description: String = "",
15 | value: String,
16 | enabled: Boolean = true,
17 | content: @Composable DialogScope.() -> Unit
18 | ) {
19 | val dialogScope = remember { DialogScope() }
20 | val isDialogOpen by dialogScope.isDialogOpen.collectAsStateWithLifecycle()
21 | val modifier =
22 | if (enabled) Modifier.clickable { dialogScope.openDialog() } else Modifier.alpha(0.5f)
23 | SettingItem(
24 | modifier = modifier,
25 | name = name,
26 | description = description,
27 | value = value
28 | )
29 | if (isDialogOpen) {
30 | content(dialogScope)
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/kissfft/tools/kiss_fftndr.h:
--------------------------------------------------------------------------------
1 | #ifndef KISS_NDR_H
2 | #define KISS_NDR_H
3 |
4 | #include "kiss_fft.h"
5 | #include "kiss_fftr.h"
6 | #include "kiss_fftnd.h"
7 |
8 | #ifdef __cplusplus
9 | extern "C" {
10 | #endif
11 |
12 | typedef struct kiss_fftndr_state *kiss_fftndr_cfg;
13 |
14 |
15 | kiss_fftndr_cfg kiss_fftndr_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem);
16 | /*
17 | dims[0] must be even
18 |
19 | If you don't care to allocate space, use mem = lenmem = NULL
20 | */
21 |
22 |
23 | void kiss_fftndr(
24 | kiss_fftndr_cfg cfg,
25 | const kiss_fft_scalar *timedata,
26 | kiss_fft_cpx *freqdata);
27 | /*
28 | input timedata has dims[0] X dims[1] X ... X dims[ndims-1] scalar points
29 | output freqdata has dims[0] X dims[1] X ... X dims[ndims-1]/2+1 complex points
30 | */
31 |
32 | void kiss_fftndri(
33 | kiss_fftndr_cfg cfg,
34 | const kiss_fft_cpx *freqdata,
35 | kiss_fft_scalar *timedata);
36 | /*
37 | input and output dimensions are the exact opposite of kiss_fftndr
38 | */
39 |
40 |
41 | #define kiss_fftr_free free
42 |
43 | #ifdef __cplusplus
44 | }
45 | #endif
46 |
47 | #endif
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/utils/EspHomeStateTranslations.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.utils
2 |
3 | import android.content.res.Resources
4 | import com.example.ava.R
5 | import com.example.ava.esphome.Connected
6 | import com.example.ava.esphome.Disconnected
7 | import com.example.ava.esphome.EspHomeState
8 | import com.example.ava.esphome.ServerError
9 | import com.example.ava.esphome.Stopped
10 | import com.example.ava.esphome.voicesatellite.Listening
11 | import com.example.ava.esphome.voicesatellite.Processing
12 | import com.example.ava.esphome.voicesatellite.Responding
13 |
14 | fun EspHomeState.translate(resources: Resources): String = when (this) {
15 | is Stopped -> resources.getString(R.string.satellite_state_stopped)
16 | is Disconnected -> resources.getString(R.string.satellite_state_disconnected)
17 | is Connected -> resources.getString(R.string.satellite_state_idle)
18 | is Listening -> resources.getString(R.string.satellite_state_listening)
19 | is Processing -> resources.getString(R.string.satellite_state_processing)
20 | is Responding -> resources.getString(R.string.satellite_state_responding)
21 | is ServerError -> resources.getString(R.string.satellite_state_server_error, message)
22 | else -> this.toString()
23 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/log_scale_util.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/log_scale_util.h"
16 |
17 | void LogScaleFillConfigWithDefaults(struct LogScaleConfig* config) {
18 | config->enable_log = 1;
19 | config->scale_shift = 6;
20 | }
21 |
22 | int LogScalePopulateState(const struct LogScaleConfig* config,
23 | struct LogScaleState* state) {
24 | state->enable_log = config->enable_log;
25 | state->scale_shift = config->scale_shift;
26 | return 1;
27 | }
28 |
--------------------------------------------------------------------------------
/microfeatures/src/main/java/com/example/microfeatures/MicroFrontend.kt:
--------------------------------------------------------------------------------
1 | package com.example.microfeatures
2 |
3 | import java.nio.ByteBuffer
4 |
5 | data class ProcessOutput(val features: FloatArray, val samplesRead: Int)
6 |
7 | class MicroFrontend : AutoCloseable {
8 | private external fun newNativeFrontend(): Long
9 | private external fun deleteNativeFrontend(nativeFrontend: Long)
10 | private external fun processSamples(nativeFrontend: Long, audio: ByteBuffer): ProcessOutput
11 |
12 | private var nativeFrontend = newNativeFrontend()
13 |
14 | fun processSamples(audio: ByteBuffer): ProcessOutput {
15 | return processSamples(nativeFrontend, audio)
16 | }
17 |
18 | private fun delete() {
19 | if (nativeFrontend != -1L) {
20 | deleteNativeFrontend(nativeFrontend)
21 | nativeFrontend = -1L
22 | }
23 | }
24 |
25 | override fun close() {
26 | delete()
27 | }
28 |
29 | protected fun finalize() {
30 | delete()
31 | }
32 |
33 | companion object {
34 | external fun initJni(processOutputClass: Class)
35 |
36 | init {
37 | System.loadLibrary("microfeatures")
38 | initJni(ProcessOutput::class.java)
39 | }
40 | }
41 | }
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/settings/SettingsStore.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.settings
2 |
3 | import android.util.Log
4 | import androidx.datastore.core.DataStore
5 | import androidx.datastore.core.IOException
6 | import kotlinx.coroutines.flow.Flow
7 | import kotlinx.coroutines.flow.catch
8 | import kotlinx.coroutines.flow.first
9 | import kotlinx.coroutines.flow.onEach
10 |
11 | interface SettingsStore {
12 | fun getFlow(): Flow
13 | suspend fun get(): T
14 | suspend fun update(transform: suspend (T) -> T)
15 | }
16 |
17 | abstract class SettingsStoreImpl(val dataStore: DataStore, private val default: T) :
18 | SettingsStore {
19 | override fun getFlow() = dataStore.data
20 | .catch { exception ->
21 | if (exception is IOException) {
22 | Log.e(TAG, "Error reading settings, returning defaults", exception)
23 | emit(default)
24 | } else throw exception
25 | }
26 | .onEach { Log.d(TAG, "Loaded settings: $it") }
27 |
28 | override suspend fun get(): T = getFlow().first()
29 |
30 | override suspend fun update(transform: suspend (T) -> T) {
31 | dataStore.updateData(transform)
32 | }
33 |
34 | companion object {
35 | private const val TAG = "SettingsStore"
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/settings/MicrophoneSettings.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.settings
2 |
3 | import android.content.Context
4 | import androidx.datastore.core.DataStore
5 | import androidx.datastore.dataStore
6 | import kotlinx.coroutines.flow.map
7 | import kotlinx.serialization.Serializable
8 |
9 | @Serializable
10 | data class MicrophoneSettings(
11 | val wakeWord: String = "okay_nabu",
12 | val stopWord: String = "stop",
13 | val muted: Boolean = false
14 | )
15 |
16 | val Context.microphoneSettingsStore: DataStore by dataStore(
17 | fileName = "microphone_settings.json",
18 | serializer = SettingsSerializer(MicrophoneSettings.serializer(), MicrophoneSettings()),
19 | corruptionHandler = defaultCorruptionHandler(MicrophoneSettings())
20 | )
21 |
22 | class MicrophoneSettingsStore(dataStore: DataStore) :
23 | SettingsStoreImpl(dataStore, MicrophoneSettings()) {
24 | val wakeWord =
25 | SettingState(getFlow().map { it.wakeWord }) { value -> update { it.copy(wakeWord = value) } }
26 | val stopWord =
27 | SettingState(getFlow().map { it.stopWord }) { value -> update { it.copy(stopWord = value) } }
28 | val muted =
29 | SettingState(getFlow().map { it.muted }) { value -> update { it.copy(muted = value) } }
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/ui/screens/settings/components/NumberSetting.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.ui.screens.settings.components
2 |
3 | import androidx.compose.foundation.text.KeyboardOptions
4 | import androidx.compose.foundation.text.input.InputTransformation
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.text.input.KeyboardType
7 |
8 | @Composable
9 | fun IntSetting(
10 | name: String,
11 | description: String = "",
12 | value: Int?,
13 | enabled: Boolean = true,
14 | validation: ((Int?) -> String?)? = null,
15 | onConfirmRequest: (Int?) -> Unit = {}
16 | ) {
17 | TextSetting(
18 | name = name,
19 | description = description,
20 | value = value?.toString() ?: "",
21 | enabled = enabled,
22 | validation = { validation?.invoke(it.toIntOrNull()) },
23 | inputTransformation = intOrEmptyInputTransformation,
24 | keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
25 | onConfirmRequest = { onConfirmRequest(it.toIntOrNull()) }
26 | )
27 | }
28 |
29 | val intOrEmptyInputTransformation: InputTransformation = InputTransformation {
30 | val text = toString()
31 | if (text.isNotEmpty()) {
32 | val value = text.toIntOrNull()
33 | if (value == null)
34 | revertAllChanges()
35 | }
36 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/frontend_io.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FRONTEND_IO_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FRONTEND_IO_H_
17 |
18 | #include "tensorflow/lite/experimental/microfrontend/lib/frontend.h"
19 |
20 | #ifdef __cplusplus
21 | extern "C" {
22 | #endif
23 |
24 | int WriteFrontendStateMemmap(const char* header, const char* source,
25 | const struct FrontendState* state);
26 |
27 | #ifdef __cplusplus
28 | } // extern "C"
29 | #endif
30 |
31 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FRONTEND_IO_H_
32 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/log_scale_io.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_SCALE_IO_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_SCALE_IO_H_
17 |
18 | #include
19 |
20 | #include "tensorflow/lite/experimental/microfrontend/lib/log_scale.h"
21 |
22 | #ifdef __cplusplus
23 | extern "C" {
24 | #endif
25 |
26 | void LogScaleWriteMemmap(FILE* fp, const struct LogScaleState* state,
27 | const char* variable);
28 |
29 | #ifdef __cplusplus
30 | } // extern "C"
31 | #endif
32 |
33 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_SCALE_IO_H_
34 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/fft_io.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FFT_IO_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FFT_IO_H_
17 |
18 | #include
19 |
20 | #include "tensorflow/lite/experimental/microfrontend/lib/fft.h"
21 |
22 | #ifdef __cplusplus
23 | extern "C" {
24 | #endif
25 |
26 | void FftWriteMemmapPreamble(FILE* fp, const struct FftState* state);
27 | void FftWriteMemmap(FILE* fp, const struct FftState* state,
28 | const char* variable);
29 |
30 | #ifdef __cplusplus
31 | } // extern "C"
32 | #endif
33 |
34 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FFT_IO_H_
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/utils/AsyncUtils.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.utils
2 |
3 | import kotlinx.coroutines.CancellableContinuation
4 | import kotlinx.coroutines.suspendCancellableCoroutine
5 | import java.nio.channels.AsynchronousCloseException
6 | import java.nio.channels.AsynchronousServerSocketChannel
7 | import java.nio.channels.AsynchronousSocketChannel
8 | import java.nio.channels.CompletionHandler
9 | import kotlin.coroutines.resume
10 | import kotlin.coroutines.resumeWithException
11 |
12 | suspend fun AsynchronousServerSocketChannel.acceptAsync(): AsynchronousSocketChannel =
13 | suspendCancellableCoroutine { cont ->
14 | accept(cont, asyncIOHandler())
15 | }
16 |
17 | @Suppress("UNCHECKED_CAST")
18 | private fun asyncIOHandler(): CompletionHandler> =
19 | AsyncIOHandlerAny as CompletionHandler>
20 |
21 | private object AsyncIOHandlerAny : CompletionHandler> {
22 | override fun completed(result: Any, cont: CancellableContinuation) {
23 | cont.resume(result)
24 | }
25 |
26 | override fun failed(ex: Throwable, cont: CancellableContinuation) {
27 | // just return if already cancelled and got an expected exception for that case
28 | if (ex is AsynchronousCloseException && cont.isCancelled) return
29 | cont.resumeWithException(ex)
30 | }
31 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/fft_util.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FFT_UTIL_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FFT_UTIL_H_
17 |
18 | #include "tensorflow/lite/experimental/microfrontend/lib/fft.h"
19 |
20 | #ifdef __cplusplus
21 | extern "C" {
22 | #endif
23 |
24 | // Prepares and FFT for the given input size.
25 | int FftPopulateState(struct FftState* state, size_t input_size);
26 |
27 | // Frees any allocated buffers.
28 | void FftFreeStateContents(struct FftState* state);
29 |
30 | #ifdef __cplusplus
31 | } // extern "C"
32 | #endif
33 |
34 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FFT_UTIL_H_
35 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/window_io.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_WINDOW_IO_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_WINDOW_IO_H_
17 |
18 | #include
19 |
20 | #include "tensorflow/lite/experimental/microfrontend/lib/window.h"
21 |
22 | #ifdef __cplusplus
23 | extern "C" {
24 | #endif
25 |
26 | void WindowWriteMemmapPreamble(FILE* fp, const struct WindowState* state);
27 | void WindowWriteMemmap(FILE* fp, const struct WindowState* state,
28 | const char* variable);
29 |
30 | #ifdef __cplusplus
31 | } // extern "C"
32 | #endif
33 |
34 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_WINDOW_IO_H_
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/esphome/entities/SwitchEntity.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.esphome.entities
2 |
3 | import com.example.esphomeproto.api.ListEntitiesRequest
4 | import com.example.esphomeproto.api.SwitchCommandRequest
5 | import com.example.esphomeproto.api.listEntitiesSwitchResponse
6 | import com.example.esphomeproto.api.switchStateResponse
7 | import com.google.protobuf.MessageLite
8 | import kotlinx.coroutines.flow.Flow
9 | import kotlinx.coroutines.flow.flow
10 | import kotlinx.coroutines.flow.map
11 |
12 | class SwitchEntity(
13 | val key: Int,
14 | val name: String,
15 | val objectId: String,
16 | val getState: Flow,
17 | val setState: suspend (Boolean) -> Unit
18 | ) : Entity {
19 | override fun handleMessage(message: MessageLite) = flow {
20 | when (message) {
21 | is ListEntitiesRequest -> emit(listEntitiesSwitchResponse {
22 | key = this@SwitchEntity.key
23 | name = this@SwitchEntity.name
24 | objectId = this@SwitchEntity.objectId
25 |
26 | })
27 |
28 | is SwitchCommandRequest -> {
29 | if (message.key == key)
30 | setState(message.state)
31 | }
32 | }
33 | }
34 |
35 | override fun subscribe() = getState.map {
36 | switchStateResponse {
37 | key = this@SwitchEntity.key
38 | this.state = it
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/settings/SettingsSerializer.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.settings
2 |
3 | import android.util.Log
4 | import androidx.datastore.core.CorruptionException
5 | import androidx.datastore.core.Serializer
6 | import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
7 | import kotlinx.serialization.KSerializer
8 | import kotlinx.serialization.SerializationException
9 | import kotlinx.serialization.json.Json
10 | import java.io.InputStream
11 | import java.io.OutputStream
12 |
13 | private const val TAG = "SettingsCorruptionHandler"
14 |
15 | fun defaultCorruptionHandler(default: T) = ReplaceFileCorruptionHandler { exception ->
16 | Log.e(TAG, "Error reading settings, returning defaults", exception)
17 | default
18 | }
19 |
20 | class SettingsSerializer(val serializer: KSerializer, override val defaultValue: T) :
21 | Serializer {
22 | override suspend fun readFrom(input: InputStream): T =
23 | try {
24 | Json.decodeFromString(
25 | serializer,
26 | input.readBytes().decodeToString()
27 | )
28 | } catch (serialization: SerializationException) {
29 | throw CorruptionException("Unable to read Settings", serialization)
30 | }
31 |
32 | override suspend fun writeTo(t: T, output: OutputStream) {
33 | output.write(
34 | Json.encodeToString(serializer, t)
35 | .encodeToByteArray()
36 | )
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/kissfft/tools/kfc.h:
--------------------------------------------------------------------------------
1 | #ifndef KFC_H
2 | #define KFC_H
3 | #include "kiss_fft.h"
4 |
5 | #ifdef __cplusplus
6 | extern "C" {
7 | #endif
8 |
9 | /*
10 | KFC -- Kiss FFT Cache
11 |
12 | Not needing to deal with kiss_fft_alloc and a config
13 | object may be handy for a lot of programs.
14 |
15 | KFC uses the underlying KISS FFT functions, but caches the config object.
16 | The first time kfc_fft or kfc_ifft for a given FFT size, the cfg
17 | object is created for it. All subsequent calls use the cached
18 | configuration object.
19 |
20 | NOTE:
21 | You should probably not use this if your program will be using a lot
22 | of various sizes of FFTs. There is a linear search through the
23 | cached objects. If you are only using one or two FFT sizes, this
24 | will be negligible. Otherwise, you may want to use another method
25 | of managing the cfg objects.
26 |
27 | There is no automated cleanup of the cached objects. This could lead
28 | to large memory usage in a program that uses a lot of *DIFFERENT*
29 | sized FFTs. If you want to force all cached cfg objects to be freed,
30 | call kfc_cleanup.
31 |
32 | */
33 |
34 | /*forward complex FFT */
35 | void kfc_fft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout);
36 | /*reverse complex FFT */
37 | void kfc_ifft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout);
38 |
39 | /*free all cached objects*/
40 | void kfc_cleanup(void);
41 |
42 | #ifdef __cplusplus
43 | }
44 | #endif
45 |
46 | #endif
47 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/log_lut.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_LUT_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_LUT_H_
17 |
18 | #include
19 |
20 | #ifdef __cplusplus
21 | extern "C" {
22 | #endif
23 |
24 | // Number of segments in the log lookup table. The table will be kLogSegments+1
25 | // in length (with some padding).
26 | #define kLogSegments 128
27 | #define kLogSegmentsLog2 7
28 |
29 | // Scale used by lookup table.
30 | #define kLogScale 65536
31 | #define kLogScaleLog2 16
32 | #define kLogCoeff 45426
33 |
34 | extern const uint16_t kLogLut[];
35 |
36 | #ifdef __cplusplus
37 | } // extern "C"
38 | #endif
39 |
40 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_LUT_H_
41 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/filterbank_io.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FILTERBANK_IO_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FILTERBANK_IO_H_
17 |
18 | #include
19 |
20 | #include "tensorflow/lite/experimental/microfrontend/lib/filterbank.h"
21 |
22 | #ifdef __cplusplus
23 | extern "C" {
24 | #endif
25 |
26 | void FilterbankWriteMemmapPreamble(FILE* fp,
27 | const struct FilterbankState* state);
28 | void FilterbankWriteMemmap(FILE* fp, const struct FilterbankState* state,
29 | const char* variable);
30 |
31 | #ifdef __cplusplus
32 | } // extern "C"
33 | #endif
34 |
35 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FILTERBANK_IO_H_
36 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/log_scale.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_SCALE_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_SCALE_H_
17 |
18 | #include
19 | #include
20 |
21 | #ifdef __cplusplus
22 | extern "C" {
23 | #endif
24 |
25 | struct LogScaleState {
26 | int enable_log;
27 | int scale_shift;
28 | };
29 |
30 | // Applies a fixed point logarithm to the signal and converts it to 16 bit. Note
31 | // that the signal array will be modified.
32 | uint16_t* LogScaleApply(struct LogScaleState* state, uint32_t* signal,
33 | int signal_size, int correction_bits);
34 |
35 | #ifdef __cplusplus
36 | } // extern "C"
37 | #endif
38 |
39 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_SCALE_H_
40 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/kiss_fft_int16.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_KISS_FFT_INT16_H_
17 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_KISS_FFT_INT16_H_
18 |
19 | #include "tensorflow/lite/experimental/microfrontend/lib/kiss_fft_common.h"
20 |
21 | // Wrap 16-bit kiss fft in its own namespace. Enables us to link an application
22 | // with different kiss fft resultions (16/32 bit interger, float, double)
23 | // without getting a linker error.
24 | #define FIXED_POINT 16
25 | namespace kissfft_fixed16 {
26 | #include "kiss_fft.h"
27 | #include "tools/kiss_fftr.h"
28 | } // namespace kissfft_fixed16
29 | #undef FIXED_POINT
30 | #undef kiss_fft_scalar
31 | #undef KISS_FFT_H
32 |
33 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_KISS_FFT_INT16_H_
34 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/noise_reduction_io.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_NOISE_REDUCTION_IO_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_NOISE_REDUCTION_IO_H_
17 |
18 | #include
19 |
20 | #include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction.h"
21 |
22 | #ifdef __cplusplus
23 | extern "C" {
24 | #endif
25 |
26 | void NoiseReductionWriteMemmapPreamble(FILE* fp,
27 | const struct NoiseReductionState* state);
28 | void NoiseReductionWriteMemmap(FILE* fp,
29 | const struct NoiseReductionState* state,
30 | const char* variable);
31 |
32 | #ifdef __cplusplus
33 | } // extern "C"
34 | #endif
35 |
36 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_NOISE_REDUCTION_IO_H_
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.activity.enableEdgeToEdge
7 | import androidx.activity.viewModels
8 | import androidx.compose.material3.ExperimentalMaterial3Api
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.DisposableEffect
11 | import com.example.ava.permissions.VOICE_SATELLITE_PERMISSIONS
12 | import com.example.ava.ui.MainNavHost
13 | import com.example.ava.ui.services.ServiceViewModel
14 | import com.example.ava.ui.services.rememberLaunchWithMultiplePermissions
15 | import com.example.ava.ui.theme.AvaTheme
16 |
17 | class MainActivity : ComponentActivity() {
18 | private var created = false
19 | private val serviceViewModel: ServiceViewModel by viewModels()
20 |
21 | @OptIn(ExperimentalMaterial3Api::class)
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | enableEdgeToEdge()
25 | setContent {
26 | AvaTheme {
27 | OnCreate()
28 | MainNavHost()
29 | }
30 | }
31 | }
32 |
33 | @Composable
34 | fun OnCreate() {
35 | val permissionsLauncher = rememberLaunchWithMultiplePermissions(
36 | onPermissionGranted = { serviceViewModel.autoStartServiceIfRequired() }
37 | )
38 | DisposableEffect(Unit) {
39 | permissionsLauncher.launch(VOICE_SATELLITE_PERMISSIONS)
40 | onDispose { }
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/fft.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FFT_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FFT_H_
17 |
18 | #include
19 | #include
20 |
21 | #ifdef __cplusplus
22 | extern "C" {
23 | #endif
24 |
25 | struct complex_int16_t {
26 | int16_t real;
27 | int16_t imag;
28 | };
29 |
30 | struct FftState {
31 | int16_t* input;
32 | struct complex_int16_t* output;
33 | size_t fft_size;
34 | size_t input_size;
35 | void* scratch;
36 | size_t scratch_size;
37 | };
38 |
39 | void FftCompute(struct FftState* state, const int16_t* input,
40 | int input_scale_shift);
41 |
42 | void FftInit(struct FftState* state);
43 |
44 | void FftReset(struct FftState* state);
45 |
46 | #ifdef __cplusplus
47 | } // extern "C"
48 | #endif
49 |
50 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FFT_H_
51 |
--------------------------------------------------------------------------------
/microfeatures/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace = "com.example.microfeatures"
8 | compileSdk = 36
9 |
10 | defaultConfig {
11 | minSdk = 26
12 |
13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles("consumer-rules.pro")
15 | externalNativeBuild {
16 | cmake {
17 | cppFlags("")
18 | arguments += listOf("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON")
19 | }
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | isMinifyEnabled = false
26 | proguardFiles(
27 | getDefaultProguardFile("proguard-android-optimize.txt"),
28 | "proguard-rules.pro"
29 | )
30 | }
31 | }
32 | externalNativeBuild {
33 | cmake {
34 | path("src/main/cpp/CMakeLists.txt")
35 | version = "3.22.1"
36 | }
37 | }
38 | compileOptions {
39 | sourceCompatibility = JavaVersion.VERSION_11
40 | targetCompatibility = JavaVersion.VERSION_11
41 | }
42 | kotlin {
43 | compilerOptions {
44 | jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
45 | }
46 | }
47 | }
48 |
49 | dependencies {
50 |
51 | implementation(libs.androidx.core.ktx)
52 | implementation(libs.androidx.appcompat)
53 | implementation(libs.material)
54 | testImplementation(libs.junit)
55 | androidTestImplementation(libs.androidx.junit)
56 | androidTestImplementation(libs.androidx.espresso.core)
57 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/noise_reduction.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_NOISE_REDUCTION_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_NOISE_REDUCTION_H_
17 |
18 | #define kNoiseReductionBits 14
19 |
20 | #include
21 | #include
22 |
23 | #ifdef __cplusplus
24 | extern "C" {
25 | #endif
26 |
27 | struct NoiseReductionState {
28 | int smoothing_bits;
29 | uint16_t even_smoothing;
30 | uint16_t odd_smoothing;
31 | uint16_t min_signal_remaining;
32 | int num_channels;
33 | uint32_t* estimate;
34 | };
35 |
36 | // Removes stationary noise from each channel of the signal using a low pass
37 | // filter.
38 | void NoiseReductionApply(struct NoiseReductionState* state, uint32_t* signal);
39 |
40 | void NoiseReductionReset(struct NoiseReductionState* state);
41 |
42 | #ifdef __cplusplus
43 | } // extern "C"
44 | #endif
45 |
46 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_NOISE_REDUCTION_H_
47 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_PCAN_GAIN_CONTROL_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_PCAN_GAIN_CONTROL_H_
17 |
18 | #include
19 | #include
20 |
21 | #define kPcanSnrBits 12
22 | #define kPcanOutputBits 6
23 |
24 | #ifdef __cplusplus
25 | extern "C" {
26 | #endif
27 |
28 | // Details at https://research.google/pubs/pub45911.pdf
29 | struct PcanGainControlState {
30 | int enable_pcan;
31 | uint32_t* noise_estimate;
32 | int num_channels;
33 | int16_t* gain_lut;
34 | int32_t snr_shift;
35 | };
36 |
37 | int16_t WideDynamicFunction(const uint32_t x, const int16_t* lut);
38 |
39 | uint32_t PcanShrink(const uint32_t x);
40 |
41 | void PcanGainControlApply(struct PcanGainControlState* state, uint32_t* signal);
42 |
43 | #ifdef __cplusplus
44 | } // extern "C"
45 | #endif
46 |
47 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_PCAN_GAIN_CONTROL_H_
48 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/noise_reduction_io.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction_io.h"
16 |
17 | void NoiseReductionWriteMemmapPreamble(
18 | FILE* fp, const struct NoiseReductionState* state) {
19 | fprintf(fp, "static uint32_t noise_reduction_estimate[%d];\n",
20 | state->num_channels);
21 | fprintf(fp, "\n");
22 | }
23 |
24 | void NoiseReductionWriteMemmap(FILE* fp,
25 | const struct NoiseReductionState* state,
26 | const char* variable) {
27 | fprintf(fp, "%s->even_smoothing = %d;\n", variable, state->even_smoothing);
28 | fprintf(fp, "%s->odd_smoothing = %d;\n", variable, state->odd_smoothing);
29 | fprintf(fp, "%s->min_signal_remaining = %d;\n", variable,
30 | state->min_signal_remaining);
31 | fprintf(fp, "%s->num_channels = %d;\n", variable, state->num_channels);
32 |
33 | fprintf(fp, "%s->estimate = noise_reduction_estimate;\n", variable);
34 | }
35 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/fft_io.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/fft_io.h"
16 |
17 | void FftWriteMemmapPreamble(FILE* fp, const struct FftState* state) {
18 | fprintf(fp, "static int16_t fft_input[%zu];\n", state->fft_size);
19 | fprintf(fp, "static struct complex_int16_t fft_output[%zu];\n",
20 | state->fft_size / 2 + 1);
21 | fprintf(fp, "static char fft_scratch[%zu];\n", state->scratch_size);
22 | fprintf(fp, "\n");
23 | }
24 |
25 | void FftWriteMemmap(FILE* fp, const struct FftState* state,
26 | const char* variable) {
27 | fprintf(fp, "%s->input = fft_input;\n", variable);
28 | fprintf(fp, "%s->output = fft_output;\n", variable);
29 | fprintf(fp, "%s->fft_size = %zu;\n", variable, state->fft_size);
30 | fprintf(fp, "%s->input_size = %zu;\n", variable, state->input_size);
31 | fprintf(fp, "%s->scratch = fft_scratch;\n", variable);
32 | fprintf(fp, "%s->scratch_size = %zu;\n", variable, state->scratch_size);
33 | }
34 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/window.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_WINDOW_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_WINDOW_H_
17 |
18 | #include
19 | #include
20 |
21 | #define kFrontendWindowBits 12
22 |
23 | #ifdef __cplusplus
24 | extern "C" {
25 | #endif
26 |
27 | struct WindowState {
28 | size_t size;
29 | int16_t* coefficients;
30 | size_t step;
31 |
32 | int16_t* input;
33 | size_t input_used;
34 | int16_t* output;
35 | int16_t max_abs_output_value;
36 | };
37 |
38 | // Applies a window to the samples coming in, stepping forward at the given
39 | // rate.
40 | int WindowProcessSamples(struct WindowState* state, const int16_t* samples,
41 | size_t num_samples, size_t* num_samples_read);
42 |
43 | void WindowReset(struct WindowState* state);
44 |
45 | #ifdef __cplusplus
46 | } // extern "C"
47 | #endif
48 |
49 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_WINDOW_H_
50 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/log_scale_util.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_SCALE_UTIL_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_SCALE_UTIL_H_
17 |
18 | #include
19 | #include
20 |
21 | #include "tensorflow/lite/experimental/microfrontend/lib/log_scale.h"
22 |
23 | #ifdef __cplusplus
24 | extern "C" {
25 | #endif
26 |
27 | struct LogScaleConfig {
28 | // set to false (0) to disable this module
29 | int enable_log;
30 | // scale results by 2^(scale_shift)
31 | int scale_shift;
32 | };
33 |
34 | // Populates the LogScaleConfig with "sane" default values.
35 | void LogScaleFillConfigWithDefaults(struct LogScaleConfig* config);
36 |
37 | // Allocates any buffers.
38 | int LogScalePopulateState(const struct LogScaleConfig* config,
39 | struct LogScaleState* state);
40 |
41 | #ifdef __cplusplus
42 | } // extern "C"
43 | #endif
44 |
45 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_LOG_SCALE_UTIL_H_
46 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Ava
3 | Start
4 | Stop
5 | OK
6 | Cancel
7 | Settings
8 | Name
9 | Port
10 | Autostart service
11 | Whether to start the voice satellite service when the application starts
12 | Wake word
13 | Enable wake sound
14 | Play a sound when the satellite is woken by a wake word"
15 |
16 | Name cannot be empty
17 | Port must be between 1 and 65535
18 | Wake word not found
19 | Stopped
20 | Disconnected
21 | Idle
22 | Listening
23 | Processing
24 | Responding
25 | Server Error: %1$s
26 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/window_util.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_WINDOW_UTIL_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_WINDOW_UTIL_H_
17 |
18 | #include "tensorflow/lite/experimental/microfrontend/lib/window.h"
19 |
20 | #ifdef __cplusplus
21 | extern "C" {
22 | #endif
23 |
24 | struct WindowConfig {
25 | // length of window frame in milliseconds
26 | size_t size_ms;
27 | // length of step for next frame in milliseconds
28 | size_t step_size_ms;
29 | };
30 |
31 | // Populates the WindowConfig with "sane" default values.
32 | void WindowFillConfigWithDefaults(struct WindowConfig* config);
33 |
34 | // Allocates any buffers.
35 | int WindowPopulateState(const struct WindowConfig* config,
36 | struct WindowState* state, int sample_rate);
37 |
38 | // Frees any allocated buffers.
39 | void WindowFreeStateContents(struct WindowState* state);
40 |
41 | #ifdef __cplusplus
42 | } // extern "C"
43 | #endif
44 |
45 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_WINDOW_UTIL_H_
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/players/AudioFocusRegistration.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.players
2 |
3 | import android.media.AudioManager
4 | import android.util.Log
5 | import androidx.media3.common.AudioAttributes
6 | import androidx.media3.common.audio.AudioFocusRequestCompat
7 | import androidx.media3.common.audio.AudioManagerCompat
8 | import androidx.media3.common.util.UnstableApi
9 |
10 | @UnstableApi
11 | class AudioFocusRegistration(
12 | val audioManager: AudioManager,
13 | val audioAttributes: AudioAttributes,
14 | val focusGain: Int
15 | ) : AutoCloseable {
16 | private var focusRequest: AudioFocusRequestCompat? = null
17 |
18 | fun request() {
19 | if (focusRequest == null) {
20 | focusRequest = AudioFocusRequestCompat.Builder(focusGain)
21 | .setAudioAttributes(audioAttributes)
22 | .setOnAudioFocusChangeListener { }
23 | .build()
24 | val rq = AudioManagerCompat.requestAudioFocus(audioManager, focusRequest!!)
25 | Log.d(TAG, "Audio focus request result: $rq")
26 | }
27 | }
28 |
29 | fun abandon() {
30 | if (focusRequest != null) {
31 | val rq = AudioManagerCompat.abandonAudioFocusRequest(audioManager, focusRequest!!)
32 | Log.d(TAG, "Audio focus abandon result: $rq")
33 | focusRequest = null
34 | }
35 | }
36 |
37 | override fun close() {
38 | abandon()
39 | }
40 |
41 | companion object {
42 | private const val TAG = "AudioFocusRegistration"
43 |
44 | fun request(
45 | audioManager: AudioManager,
46 | audioAttributes: AudioAttributes,
47 | focusGain: Int
48 | ) = AudioFocusRegistration(audioManager, audioAttributes, focusGain).apply {
49 | request()
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/log_lut.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/log_lut.h"
16 | const uint16_t kLogLut[]
17 | #ifndef _MSC_VER
18 | __attribute__((aligned(4)))
19 | #endif // _MSV_VER
20 | = {0, 224, 442, 654, 861, 1063, 1259, 1450, 1636, 1817, 1992, 2163,
21 | 2329, 2490, 2646, 2797, 2944, 3087, 3224, 3358, 3487, 3611, 3732, 3848,
22 | 3960, 4068, 4172, 4272, 4368, 4460, 4549, 4633, 4714, 4791, 4864, 4934,
23 | 5001, 5063, 5123, 5178, 5231, 5280, 5326, 5368, 5408, 5444, 5477, 5507,
24 | 5533, 5557, 5578, 5595, 5610, 5622, 5631, 5637, 5640, 5641, 5638, 5633,
25 | 5626, 5615, 5602, 5586, 5568, 5547, 5524, 5498, 5470, 5439, 5406, 5370,
26 | 5332, 5291, 5249, 5203, 5156, 5106, 5054, 5000, 4944, 4885, 4825, 4762,
27 | 4697, 4630, 4561, 4490, 4416, 4341, 4264, 4184, 4103, 4020, 3935, 3848,
28 | 3759, 3668, 3575, 3481, 3384, 3286, 3186, 3084, 2981, 2875, 2768, 2659,
29 | 2549, 2437, 2323, 2207, 2090, 1971, 1851, 1729, 1605, 1480, 1353, 1224,
30 | 1094, 963, 830, 695, 559, 421, 282, 142, 0, 0};
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/settings/PlayerSettings.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.settings
2 |
3 | import android.content.Context
4 | import androidx.datastore.core.DataStore
5 | import androidx.datastore.dataStore
6 | import kotlinx.coroutines.flow.map
7 | import kotlinx.serialization.Serializable
8 |
9 | @Serializable
10 | data class PlayerSettings(
11 | val volume: Float = 1.0f,
12 | val muted: Boolean = false,
13 | val enableWakeSound: Boolean = true,
14 | val wakeSound: String = "asset:///sounds/wake_word_triggered.flac",
15 | val timerFinishedSound: String = "asset:///sounds/timer_finished.flac",
16 | )
17 |
18 | val Context.playerSettingsStore: DataStore by dataStore(
19 | fileName = "player_settings.json",
20 | serializer = SettingsSerializer(PlayerSettings.serializer(), PlayerSettings()),
21 | corruptionHandler = defaultCorruptionHandler(PlayerSettings())
22 | )
23 |
24 | class PlayerSettingsStore(dataStore: DataStore) :
25 | SettingsStoreImpl(dataStore, PlayerSettings()) {
26 | val volume =
27 | SettingState(getFlow().map { it.volume }) { value -> update { it.copy(volume = value) } }
28 | val muted =
29 | SettingState(getFlow().map { it.muted }) { value -> update { it.copy(muted = value) } }
30 | val enableWakeSound = SettingState(getFlow().map { it.enableWakeSound }) { value ->
31 | update {
32 | it.copy(enableWakeSound = value)
33 | }
34 | }
35 | val wakeSound =
36 | SettingState(getFlow().map { it.wakeSound }) { value -> update { it.copy(wakeSound = value) } }
37 | val timerFinishedSound =
38 | SettingState(getFlow().map { it.timerFinishedSound }) { value ->
39 | update {
40 | it.copy(
41 | timerFinishedSound = value
42 | )
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
23 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/server/ClientConnection.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.server
2 |
3 | import android.util.Log
4 | import com.example.esphomeproto.AsynchronousCodedChannel
5 | import com.google.protobuf.MessageLite
6 | import kotlinx.coroutines.flow.catch
7 | import kotlinx.coroutines.flow.flow
8 | import kotlinx.coroutines.sync.Mutex
9 | import kotlinx.coroutines.sync.withLock
10 | import java.io.IOException
11 | import java.nio.channels.AsynchronousSocketChannel
12 | import java.util.concurrent.atomic.AtomicBoolean
13 |
14 | class ClientConnection(socket: AsynchronousSocketChannel) : AutoCloseable {
15 | private val isClosed = AtomicBoolean(false)
16 | private val channel = AsynchronousCodedChannel(socket)
17 | private val sendMutex = Mutex()
18 |
19 | fun readMessages() =
20 | flow {
21 | while (true) {
22 | emit(channel.readMessage())
23 | }
24 | }.catch {
25 | if (it !is IOException) throw it
26 | // Exception is expected if client was manually closed
27 | if (!isClosed.get())
28 | Log.e(TAG, "Error reading from socket", it)
29 | }
30 |
31 | suspend fun sendMessage(message: MessageLite) {
32 | // Multiple send requests are not allowed at the same time so hold the lock until the send is complete
33 | sendMutex.withLock {
34 | try {
35 | channel.writeMessage(message)
36 | } catch (e: IOException) {
37 | // Exception is expected if client was manually closed
38 | if (!isClosed.get())
39 | Log.e(TAG, "Error writing to socket", e)
40 | }
41 | }
42 | }
43 |
44 | override fun close() {
45 | if (isClosed.compareAndSet(false, true))
46 | channel.close()
47 | }
48 |
49 | companion object {
50 | const val TAG = "ClientConnection"
51 | }
52 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/kissfft/tools/Makefile:
--------------------------------------------------------------------------------
1 | WARNINGS=-W -Wall -Wstrict-prototypes -Wmissing-prototypes -Waggregate-return \
2 | -Wcast-align -Wcast-qual -Wnested-externs -Wshadow -Wbad-function-cast \
3 | -Wwrite-strings
4 |
5 | ifeq "$(DATATYPE)" ""
6 | DATATYPE=float
7 | endif
8 |
9 | ifeq "$(DATATYPE)" "int32_t"
10 | TYPEFLAGS=-DFIXED_POINT=32
11 | endif
12 |
13 | ifeq "$(DATATYPE)" "int16_t"
14 | TYPEFLAGS=-DFIXED_POINT=16
15 | endif
16 |
17 | ifeq "$(DATATYPE)" "simd"
18 | TYPEFLAGS=-DUSE_SIMD=1 -msse
19 | endif
20 |
21 | ifeq "$(TYPEFLAGS)" ""
22 | TYPEFLAGS=-Dkiss_fft_scalar=$(DATATYPE)
23 | endif
24 |
25 | ifneq ("$(KISS_FFT_USE_ALLOCA)","")
26 | CFLAGS+= -DKISS_FFT_USE_ALLOCA=1
27 | endif
28 | CFLAGS+= $(CFLAGADD)
29 |
30 |
31 | FFTUTIL=fft_$(DATATYPE)
32 | FASTFILT=fastconv_$(DATATYPE)
33 | FASTFILTREAL=fastconvr_$(DATATYPE)
34 | PSDPNG=psdpng_$(DATATYPE)
35 | DUMPHDR=dumphdr_$(DATATYPE)
36 |
37 | all: $(FFTUTIL) $(FASTFILT) $(FASTFILTREAL)
38 | # $(PSDPNG)
39 | # $(DUMPHDR)
40 |
41 | #CFLAGS=-Wall -O3 -pedantic -march=pentiumpro -ffast-math -fomit-frame-pointer $(WARNINGS)
42 | # If the above flags do not work, try the following
43 | CFLAGS=-Wall -O3 $(WARNINGS)
44 | # tip: try -openmp or -fopenmp to use multiple cores
45 |
46 | $(FASTFILTREAL): ../kiss_fft.c kiss_fastfir.c kiss_fftr.c
47 | $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) -DREAL_FASTFIR $+ -DFAST_FILT_UTIL -lm
48 |
49 | $(FASTFILT): ../kiss_fft.c kiss_fastfir.c
50 | $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -DFAST_FILT_UTIL -lm
51 |
52 | $(FFTUTIL): ../kiss_fft.c fftutil.c kiss_fftnd.c kiss_fftr.c kiss_fftndr.c
53 | $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -lm
54 |
55 | $(PSDPNG): ../kiss_fft.c psdpng.c kiss_fftr.c
56 | $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -lpng -lm
57 |
58 | $(DUMPHDR): ../kiss_fft.c dumphdr.c
59 | $(CC) -o $@ $(CFLAGS) -I.. $(TYPEFLAGS) $+ -lm
60 |
61 | clean:
62 | rm -f *~ fft fft_* fastconv fastconv_* fastconvr fastconvr_* psdpng psdpng_*
63 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build Release APK
2 | on:
3 | workflow_dispatch:
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v4
10 | with:
11 | fetch-depth: 0
12 |
13 | - name: Set version
14 | id: set-version
15 | shell: bash
16 | run: |
17 | COMMITS=`git rev-list --count HEAD`
18 | TAGS=`git tag | wc -l`
19 | VERSION_CODE=$((COMMITS+TAGS))
20 | echo "Version Code: $VERSION_CODE"
21 | VERSION_NAME=`git describe --tags --dirty --abbrev=7 || echo "0.0.0"`
22 | echo "Version Name: $VERSION_NAME"
23 | echo "versionCode=$VERSION_CODE" >> $GITHUB_OUTPUT
24 | echo "versionName=$VERSION_NAME" >> $GITHUB_OUTPUT
25 |
26 | - name: Set up JDK 21
27 | uses: actions/setup-java@v5
28 | with:
29 | distribution: "temurin"
30 | java-version: "21"
31 |
32 | - name: Change wrapper permissions
33 | run: chmod +x ./gradlew
34 |
35 | - name: Decode keystore
36 | run: echo ${{ secrets.KEYSTORE_BASE64 }} | base64 -di > ${{ github.workspace }}/keystore.jks
37 |
38 | - name: Build release
39 | run: >
40 | ./gradlew assembleRelease
41 | -PversionCode=${{ steps.set-version.outputs.versionCode }}
42 | -PversionName=${{ steps.set-version.outputs.versionName }}
43 | -Pandroid.injected.signing.store.file=${{ github.workspace }}/keystore.jks
44 | -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_FILE_PASSWORD }}
45 | -Pandroid.injected.signing.key.alias=${{ secrets.KEYSTORE_ALIAS }}
46 | -Pandroid.injected.signing.key.password=${{ secrets.KEYSTORE_ALIAS_PASSWORD }}
47 |
48 | - name: Upload artifacts
49 | uses: actions/upload-artifact@v5
50 | with:
51 | name: Build Artifacts
52 | path: app/build/outputs/apk
53 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/frontend_memmap_generator.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include
16 |
17 | #include "tensorflow/lite/experimental/microfrontend/lib/frontend.h"
18 | #include "tensorflow/lite/experimental/microfrontend/lib/frontend_io.h"
19 | #include "tensorflow/lite/experimental/microfrontend/lib/frontend_util.h"
20 |
21 | int main(int argc, char** argv) {
22 | if (argc != 3) {
23 | fprintf(stderr,
24 | "%s requires exactly two parameters - the names of the header and "
25 | "source files to save\n",
26 | argv[0]);
27 | return 1;
28 | }
29 | struct FrontendConfig frontend_config;
30 | FrontendFillConfigWithDefaults(&frontend_config);
31 |
32 | int sample_rate = 16000;
33 | struct FrontendState frontend_state;
34 | if (!FrontendPopulateState(&frontend_config, &frontend_state, sample_rate)) {
35 | fprintf(stderr, "Failed to populate frontend state\n");
36 | FrontendFreeStateContents(&frontend_state);
37 | return 1;
38 | }
39 |
40 | if (!WriteFrontendStateMemmap(argv[1], argv[2], &frontend_state)) {
41 | fprintf(stderr, "Failed to write memmap\n");
42 | FrontendFreeStateContents(&frontend_state);
43 | return 1;
44 | }
45 |
46 | FrontendFreeStateContents(&frontend_state);
47 | return 0;
48 | }
49 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/filterbank_util.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FILTERBANK_UTIL_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FILTERBANK_UTIL_H_
17 |
18 | #include "tensorflow/lite/experimental/microfrontend/lib/filterbank.h"
19 |
20 | #ifdef __cplusplus
21 | extern "C" {
22 | #endif
23 |
24 | struct FilterbankConfig {
25 | // number of frequency channel buckets for filterbank
26 | int num_channels;
27 | // maximum frequency to include
28 | float upper_band_limit;
29 | // minimum frequency to include
30 | float lower_band_limit;
31 | // unused
32 | int output_scale_shift;
33 | };
34 |
35 | // Fills the frontendConfig with "sane" defaults.
36 | void FilterbankFillConfigWithDefaults(struct FilterbankConfig* config);
37 |
38 | // Allocates any buffers.
39 | int FilterbankPopulateState(const struct FilterbankConfig* config,
40 | struct FilterbankState* state, int sample_rate,
41 | int spectrum_size);
42 |
43 | // Frees any allocated buffers.
44 | void FilterbankFreeStateContents(struct FilterbankState* state);
45 |
46 | #ifdef __cplusplus
47 | } // extern "C"
48 | #endif
49 |
50 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FILTERBANK_UTIL_H_
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/ui/screens/settings/components/SettingItem.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.ui.screens.settings.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.RowScope
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.unit.dp
15 |
16 | @Composable
17 | fun SettingItem(
18 | modifier: Modifier = Modifier,
19 | name: String,
20 | description: String = "",
21 | value: String = "",
22 | action: @Composable () -> Unit = {}
23 | ) {
24 | Row(
25 | modifier = modifier
26 | .fillMaxWidth()
27 | .padding(16.dp)
28 | ) {
29 | Details(name, description, value)
30 | ActionContainer {
31 | action()
32 | }
33 | }
34 | }
35 |
36 | @Composable
37 | fun RowScope.Details(name: String, description: String = "", value: String = "") {
38 | Column(Modifier.weight(1f)) {
39 | Text(
40 | name,
41 | color = MaterialTheme.colorScheme.primary,
42 | style = MaterialTheme.typography.titleMedium
43 | )
44 | if (description.isNotBlank()) {
45 | Text(description, style = MaterialTheme.typography.bodyMedium)
46 | }
47 | if (value.isNotBlank()) {
48 | Text(value, style = MaterialTheme.typography.bodyMedium)
49 | }
50 | }
51 | }
52 |
53 | @Composable
54 | fun ActionContainer(content: @Composable () -> Unit) {
55 | Box(
56 | modifier = Modifier.padding(horizontal = 8.dp),
57 | contentAlignment = Alignment.Center
58 | ) {
59 | content()
60 | }
61 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/window_io.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/window_io.h"
16 |
17 | void WindowWriteMemmapPreamble(FILE* fp, const struct WindowState* state) {
18 | fprintf(fp, "static int16_t window_coefficients[] = {\n");
19 | size_t i;
20 | for (i = 0; i < state->size; ++i) {
21 | fprintf(fp, "%d", state->coefficients[i]);
22 | if (i < state->size - 1) {
23 | fprintf(fp, ", ");
24 | }
25 | }
26 | fprintf(fp, "};\n");
27 | fprintf(fp, "static int16_t window_input[%zu];\n", state->size);
28 | fprintf(fp, "static int16_t window_output[%zu];\n", state->size);
29 | fprintf(fp, "\n");
30 | }
31 |
32 | void WindowWriteMemmap(FILE* fp, const struct WindowState* state,
33 | const char* variable) {
34 | fprintf(fp, "%s->size = %zu;\n", variable, state->size);
35 | fprintf(fp, "%s->coefficients = window_coefficients;\n", variable);
36 | fprintf(fp, "%s->step = %zu;\n", variable, state->step);
37 |
38 | fprintf(fp, "%s->input = window_input;\n", variable);
39 | fprintf(fp, "%s->input_used = %zu;\n", variable, state->input_used);
40 | fprintf(fp, "%s->output = window_output;\n", variable);
41 | fprintf(fp, "%s->max_abs_output_value = %d;\n", variable,
42 | state->max_abs_output_value);
43 | }
44 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.h"
16 |
17 | #include
18 |
19 | void NoiseReductionFillConfigWithDefaults(struct NoiseReductionConfig* config) {
20 | config->smoothing_bits = 10;
21 | config->even_smoothing = 0.025;
22 | config->odd_smoothing = 0.06;
23 | config->min_signal_remaining = 0.05;
24 | }
25 |
26 | int NoiseReductionPopulateState(const struct NoiseReductionConfig* config,
27 | struct NoiseReductionState* state,
28 | int num_channels) {
29 | state->smoothing_bits = config->smoothing_bits;
30 | state->odd_smoothing = config->odd_smoothing * (1 << kNoiseReductionBits);
31 | state->even_smoothing = config->even_smoothing * (1 << kNoiseReductionBits);
32 | state->min_signal_remaining =
33 | config->min_signal_remaining * (1 << kNoiseReductionBits);
34 | state->num_channels = num_channels;
35 | state->estimate = calloc(state->num_channels, sizeof(*state->estimate));
36 | if (state->estimate == NULL) {
37 | fprintf(stderr, "Failed to alloc estimate buffer\n");
38 | return 0;
39 | }
40 | return 1;
41 | }
42 |
43 | void NoiseReductionFreeStateContents(struct NoiseReductionState* state) {
44 | free(state->estimate);
45 | }
46 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/kiss_fft_common.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 |
16 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_KISS_FFT_COMMON_H_
17 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_KISS_FFT_COMMON_H_
18 |
19 | // This header file should be included in all variants of kiss_fft_$type.{h,cc}
20 | // so that their sub-included source files do not mistakenly wrap libc header
21 | // files within their kissfft_$type namespaces.
22 | // E.g, This header avoids kissfft_int16.h containing:
23 | // namespace kiss_fft_int16 {
24 | // #include "kiss_fft.h"
25 | // }
26 | // where kiss_fft_.h contains:
27 | // #include
28 | //
29 | // TRICK: By including the following header files here, their preprocessor
30 | // header guards prevent them being re-defined inside of the kiss_fft_$type
31 | // namespaces declared within the kiss_fft_$type.{h,cc} sources.
32 | // Note that the original kiss_fft*.h files are untouched since they
33 | // may be used in libraries that include them directly.
34 |
35 | #include
36 | #include
37 | #include
38 | #include
39 | #include
40 |
41 | #ifdef FIXED_POINT
42 | #include
43 | #endif
44 |
45 | #ifdef USE_SIMD
46 | #include
47 | #endif
48 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_KISS_FFT_COMMON_H_
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/notifications/NotificationHelper.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.notifications
2 |
3 | import android.app.Notification
4 | import android.app.NotificationChannel
5 | import android.app.NotificationManager
6 | import android.app.PendingIntent
7 | import android.content.Context
8 | import android.content.Intent
9 | import android.graphics.Color
10 | import androidx.core.app.NotificationCompat
11 | import com.example.ava.MainActivity
12 | import com.example.ava.R
13 |
14 | private const val VOICE_SATELLITE_SERVICE_CHANNEL_ID = "VoiceSatelliteService"
15 |
16 | fun createVoiceSatelliteServiceNotificationChannel(context: Context) {
17 | val channelName = "Voice Satellite Background Service"
18 | val chan = NotificationChannel(
19 | VOICE_SATELLITE_SERVICE_CHANNEL_ID,
20 | channelName,
21 | NotificationManager.IMPORTANCE_NONE
22 | )
23 | chan.lightColor = Color.BLUE
24 | chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
25 | val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
26 | manager.createNotificationChannel(chan)
27 | }
28 |
29 | fun createVoiceSatelliteServiceNotification(context: Context, content: String): Notification {
30 | val notificationBuilder =
31 | NotificationCompat.Builder(context, VOICE_SATELLITE_SERVICE_CHANNEL_ID)
32 |
33 | // Open the app when the notification is clicked
34 | val pendingIntent = PendingIntent.getActivity(
35 | context,
36 | 0,
37 | Intent(context, MainActivity::class.java).apply {
38 | flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
39 | },
40 | PendingIntent.FLAG_IMMUTABLE
41 | )
42 | val notification = notificationBuilder.setOngoing(true)
43 | .setSmallIcon(R.drawable.ic_launcher_foreground)
44 | .setContentTitle(content)
45 | .setPriority(NotificationManager.IMPORTANCE_LOW)
46 | .setCategory(Notification.CATEGORY_SERVICE)
47 | .setContentIntent(pendingIntent)
48 | .build()
49 | return notification
50 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_NOISE_REDUCTION_UTIL_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_NOISE_REDUCTION_UTIL_H_
17 |
18 | #include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction.h"
19 |
20 | #ifdef __cplusplus
21 | extern "C" {
22 | #endif
23 |
24 | struct NoiseReductionConfig {
25 | // scale the signal up by 2^(smoothing_bits) before reduction
26 | int smoothing_bits;
27 | // smoothing coefficient for even-numbered channels
28 | float even_smoothing;
29 | // smoothing coefficient for odd-numbered channels
30 | float odd_smoothing;
31 | // fraction of signal to preserve (1.0 disables this module)
32 | float min_signal_remaining;
33 | };
34 |
35 | // Populates the NoiseReductionConfig with "sane" default values.
36 | void NoiseReductionFillConfigWithDefaults(struct NoiseReductionConfig* config);
37 |
38 | // Allocates any buffers.
39 | int NoiseReductionPopulateState(const struct NoiseReductionConfig* config,
40 | struct NoiseReductionState* state,
41 | int num_channels);
42 |
43 | // Frees any allocated buffers.
44 | void NoiseReductionFreeStateContents(struct NoiseReductionState* state);
45 |
46 | #ifdef __cplusplus
47 | } // extern "C"
48 | #endif
49 |
50 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_NOISE_REDUCTION_UTIL_H_
51 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/fft.cc:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/fft.h"
16 |
17 | #include
18 |
19 | #include "tensorflow/lite/experimental/microfrontend/lib/kiss_fft_int16.h"
20 |
21 | void FftCompute(struct FftState* state, const int16_t* input,
22 | int input_scale_shift) {
23 | const size_t input_size = state->input_size;
24 | const size_t fft_size = state->fft_size;
25 |
26 | int16_t* fft_input = state->input;
27 | // First, scale the input by the given shift.
28 | size_t i;
29 | for (i = 0; i < input_size; ++i) {
30 | fft_input[i] = static_cast(static_cast(input[i])
31 | << input_scale_shift);
32 | }
33 | // Zero out whatever else remains in the top part of the input.
34 | for (; i < fft_size; ++i) {
35 | fft_input[i] = 0;
36 | }
37 |
38 | // Apply the FFT.
39 | kissfft_fixed16::kiss_fftr(
40 | reinterpret_cast(state->scratch),
41 | state->input,
42 | reinterpret_cast(state->output));
43 | }
44 |
45 | void FftInit(struct FftState* state) {
46 | // All the initialization is done in FftPopulateState()
47 | }
48 |
49 | void FftReset(struct FftState* state) {
50 | memset(state->input, 0, state->fft_size * sizeof(*state->input));
51 | memset(state->output, 0, (state->fft_size / 2 + 1) * sizeof(*state->output));
52 | }
53 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.h"
16 |
17 | #include "tensorflow/lite/experimental/microfrontend/lib/bits.h"
18 |
19 | int16_t WideDynamicFunction(const uint32_t x, const int16_t* lut) {
20 | if (x <= 2) {
21 | return lut[x];
22 | }
23 |
24 | const int16_t interval = MostSignificantBit32(x);
25 | lut += 4 * interval - 6;
26 |
27 | const int16_t frac =
28 | ((interval < 11) ? (x << (11 - interval)) : (x >> (interval - 11))) &
29 | 0x3FF;
30 |
31 | int32_t result = ((int32_t)lut[2] * frac) >> 5;
32 | result += (int32_t)((uint32_t)lut[1] << 5);
33 | result *= frac;
34 | result = (result + (1 << 14)) >> 15;
35 | result += lut[0];
36 | return (int16_t)result;
37 | }
38 |
39 | uint32_t PcanShrink(const uint32_t x) {
40 | if (x < (2 << kPcanSnrBits)) {
41 | return (x * x) >> (2 + 2 * kPcanSnrBits - kPcanOutputBits);
42 | } else {
43 | return (x >> (kPcanSnrBits - kPcanOutputBits)) - (1 << kPcanOutputBits);
44 | }
45 | }
46 |
47 | void PcanGainControlApply(struct PcanGainControlState* state,
48 | uint32_t* signal) {
49 | int i;
50 | for (i = 0; i < state->num_channels; ++i) {
51 | const uint32_t gain =
52 | WideDynamicFunction(state->noise_estimate[i], state->gain_lut);
53 | const uint32_t snr = ((uint64_t)signal[i] * gain) >> state->snr_shift;
54 | signal[i] = PcanShrink(snr);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/ui/screens/settings/SettingsScreen.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.ui.screens.settings
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material3.ExperimentalMaterial3Api
6 | import androidx.compose.material3.Icon
7 | import androidx.compose.material3.IconButton
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.material3.Scaffold
10 | import androidx.compose.material3.Text
11 | import androidx.compose.material3.TopAppBar
12 | import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.res.painterResource
16 | import androidx.compose.ui.res.stringResource
17 | import androidx.navigation.NavController
18 | import com.example.ava.R
19 |
20 | @OptIn(ExperimentalMaterial3Api::class)
21 | @Composable
22 | fun SettingsScreen(navController: NavController) {
23 | Scaffold(
24 | modifier = Modifier.fillMaxSize(),
25 | topBar = {
26 | TopAppBar(
27 | colors = topAppBarColors(
28 | containerColor = MaterialTheme.colorScheme.primaryContainer,
29 | titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
30 | ),
31 | title = {
32 | Text(stringResource(R.string.label_settings))
33 | },
34 | navigationIcon = {
35 | IconButton(
36 | onClick = { navController.popBackStack() }
37 | ) {
38 | Icon(
39 | painter = painterResource(R.drawable.arrow_back_24px),
40 | contentDescription = "Back",
41 | tint = MaterialTheme.colorScheme.onPrimaryContainer
42 | )
43 | }
44 | }
45 | )
46 | }
47 | ) { innerPadding ->
48 | VoiceSatelliteSettings(
49 | modifier = Modifier
50 | .fillMaxSize()
51 | .padding(innerPadding)
52 | )
53 | }
54 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/frontend_memmap_main.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include
16 |
17 | #include "memmap.h"
18 | #include "tensorflow/lite/experimental/microfrontend/lib/frontend.h"
19 |
20 | int main(int argc, char** argv) {
21 | struct FrontendState* frontend_state = GetFrontendStateMemmap();
22 |
23 | char* filename = argv[1];
24 | FILE* fp = fopen(filename, "r");
25 | if (fp == NULL) {
26 | fprintf(stderr, "Failed to open %s for read\n", filename);
27 | return 1;
28 | }
29 | fseek(fp, 0L, SEEK_END);
30 | size_t audio_file_size = ftell(fp) / sizeof(int16_t);
31 | fseek(fp, 0L, SEEK_SET);
32 | int16_t* audio_data = malloc(audio_file_size * sizeof(int16_t));
33 | int16_t* original_audio_data = audio_data;
34 | if (audio_file_size !=
35 | fread(audio_data, sizeof(int16_t), audio_file_size, fp)) {
36 | fprintf(stderr, "Failed to read in all audio data\n");
37 | fclose(fp);
38 | return 1;
39 | }
40 |
41 | while (audio_file_size > 0) {
42 | size_t num_samples_read;
43 | struct FrontendOutput output = FrontendProcessSamples(
44 | frontend_state, audio_data, audio_file_size, &num_samples_read);
45 | audio_data += num_samples_read;
46 | audio_file_size -= num_samples_read;
47 |
48 | if (output.values != NULL) {
49 | int i;
50 | for (i = 0; i < output.size; ++i) {
51 | printf("%d ", output.values[i]);
52 | }
53 | printf("\n");
54 | }
55 | }
56 |
57 | free(original_audio_data);
58 | fclose(fp);
59 | return 0;
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/microwakeword/AssetWakeWordProvider.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.microwakeword
2 |
3 | import android.content.res.AssetManager
4 | import android.util.Log
5 | import com.google.gson.Gson
6 | import com.google.gson.reflect.TypeToken
7 | import java.io.FileInputStream
8 | import java.nio.ByteBuffer
9 | import java.nio.channels.FileChannel
10 |
11 | class AssetWakeWordProvider(val assets: AssetManager, val path: String = DEFAULT_WAKE_WORD_PATH) :
12 | WakeWordProvider {
13 | override fun getWakeWords(): List {
14 | val gson = Gson()
15 | val wakeWords = buildList {
16 | val assetsList = assets.list(path)
17 | if (assetsList == null)
18 | return emptyList()
19 |
20 | for (asset in assetsList) {
21 | if (!asset.endsWith(".json"))
22 | continue
23 |
24 | runCatching {
25 | val json =
26 | assets.open("$path/$asset").bufferedReader().use { it.readText() }
27 | val wakeWord: WakeWord =
28 | gson.fromJson(json, object : TypeToken() {}.type)
29 | add(WakeWordWithId(asset.substring(0, asset.lastIndexOf(".json")), wakeWord))
30 | }.onFailure {
31 | Log.e(TAG, "Error loading wake word: $asset", it)
32 | }
33 | }
34 | }
35 | return wakeWords
36 | }
37 |
38 | override fun loadWakeWordModel(model: String): ByteBuffer {
39 | val modelFileDescriptor = assets.openFd("$path/$model")
40 | modelFileDescriptor.use { descriptor ->
41 | FileInputStream(descriptor.fileDescriptor).use { stream ->
42 | val fileChannel = stream.channel
43 | val startOffset: Long = modelFileDescriptor.startOffset
44 | val declaredLength: Long = modelFileDescriptor.declaredLength
45 | return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength)
46 | }
47 | }
48 | }
49 |
50 | companion object {
51 | private const val TAG = "AssetWakeWordProvider"
52 | const val DEFAULT_WAKE_WORD_PATH = "wakeWords"
53 | }
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/settings/VoiceSatelliteSettings.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.settings
2 |
3 | import android.content.Context
4 | import androidx.datastore.core.DataStore
5 | import androidx.datastore.dataStore
6 | import com.example.ava.utils.getRandomMacAddressString
7 | import kotlinx.coroutines.flow.map
8 | import kotlinx.serialization.Serializable
9 |
10 | // The voice satellite uses a mac address as a unique identifier.
11 | // The use of the actual mac address on Android is discouraged/not available
12 | // depending on the Android version.
13 | // Instead a random string of bytes should be generated and persisted to the settings.
14 | // The default value below should only used to detect when a random value hasn't been
15 | // generated and persisted yet and should be replaced with a random value when it is.
16 | val DEFAULT_MAC_ADDRESS = "00:00:00:00:00:00"
17 |
18 | @Serializable
19 | data class VoiceSatelliteSettings(
20 | val name: String = "Android Voice Assistant",
21 | val serverPort: Int = 6053,
22 | val macAddress: String = DEFAULT_MAC_ADDRESS,
23 | val autoStart: Boolean = false
24 | )
25 |
26 | private val DEFAULT = VoiceSatelliteSettings()
27 |
28 | val Context.voiceSatelliteSettingsStore: DataStore by dataStore(
29 | fileName = "voice_satellite_settings.json",
30 | serializer = SettingsSerializer(VoiceSatelliteSettings.serializer(), DEFAULT),
31 | corruptionHandler = defaultCorruptionHandler(DEFAULT)
32 | )
33 |
34 | class VoiceSatelliteSettingsStore(dataStore: DataStore) :
35 | SettingsStoreImpl(dataStore, DEFAULT) {
36 |
37 | val name = SettingState(getFlow().map { it.name }) { value ->
38 | update { it.copy(name = value) }
39 | }
40 |
41 | val serverPort = SettingState(getFlow().map { it.serverPort }) { value ->
42 | update { it.copy(serverPort = value) }
43 | }
44 |
45 | val autoStart = SettingState(getFlow().map { it.autoStart }) { value ->
46 | update { it.copy(autoStart = value) }
47 | }
48 |
49 | suspend fun ensureMacAddressIsSet() {
50 | update {
51 | if (it.macAddress == DEFAULT_MAC_ADDRESS) it.copy(macAddress = getRandomMacAddressString()) else it
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/fft_test.cc:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/fft.h"
16 |
17 | #include "tensorflow/lite/experimental/microfrontend/lib/fft_util.h"
18 | #include "tensorflow/lite/micro/testing/micro_test.h"
19 |
20 | namespace {
21 |
22 | const int16_t kFakeWindow[] = {
23 | 0, 1151, 0, -5944, 0, 13311, 0, -21448, 0, 28327, 0, -32256, 0, 32255,
24 | 0, -28328, 0, 21447, 0, -13312, 0, 5943, 0, -1152, 0};
25 | const int kScaleShift = 0;
26 |
27 | } // namespace
28 |
29 | TF_LITE_MICRO_TESTS_BEGIN
30 |
31 | TF_LITE_MICRO_TEST(FftTest_CheckOutputValues) {
32 | struct FftState state;
33 | TF_LITE_MICRO_EXPECT(
34 | FftPopulateState(&state, sizeof(kFakeWindow) / sizeof(kFakeWindow[0])));
35 |
36 | FftInit(&state);
37 | FftCompute(&state, kFakeWindow, kScaleShift);
38 |
39 | const struct complex_int16_t expected[] = {
40 | {0, 0}, {-10, 9}, {-20, 0}, {-9, -10}, {0, 25}, {-119, 119},
41 | {-887, 0}, {3000, 3000}, {0, -6401}, {-3000, 3000}, {886, 0}, {118, 119},
42 | {0, 25}, {9, -10}, {19, 0}, {9, 9}, {0, 0}};
43 | TF_LITE_MICRO_EXPECT_EQ(state.fft_size / 2 + 1,
44 | sizeof(expected) / sizeof(expected[0]));
45 | unsigned int i;
46 | for (i = 0; i <= state.fft_size / 2; ++i) {
47 | TF_LITE_MICRO_EXPECT_EQ(state.output[i].real, expected[i].real);
48 | TF_LITE_MICRO_EXPECT_EQ(state.output[i].imag, expected[i].imag);
49 | }
50 |
51 | FftFreeStateContents(&state);
52 | }
53 |
54 | TF_LITE_MICRO_TESTS_END
55 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/noise_reduction.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction.h"
16 |
17 | #include
18 |
19 | void NoiseReductionApply(struct NoiseReductionState* state, uint32_t* signal) {
20 | int i;
21 | for (i = 0; i < state->num_channels; ++i) {
22 | const uint32_t smoothing =
23 | ((i & 1) == 0) ? state->even_smoothing : state->odd_smoothing;
24 | const uint32_t one_minus_smoothing = (1 << kNoiseReductionBits) - smoothing;
25 |
26 | // Update the estimate of the noise.
27 | const uint32_t signal_scaled_up = signal[i] << state->smoothing_bits;
28 | uint32_t estimate =
29 | (((uint64_t)signal_scaled_up * smoothing) +
30 | ((uint64_t)state->estimate[i] * one_minus_smoothing)) >>
31 | kNoiseReductionBits;
32 | state->estimate[i] = estimate;
33 |
34 | // Make sure that we can't get a negative value for the signal - estimate.
35 | if (estimate > signal_scaled_up) {
36 | estimate = signal_scaled_up;
37 | }
38 |
39 | const uint32_t floor =
40 | ((uint64_t)signal[i] * state->min_signal_remaining) >>
41 | kNoiseReductionBits;
42 | const uint32_t subtracted =
43 | (signal_scaled_up - estimate) >> state->smoothing_bits;
44 | const uint32_t output = subtracted > floor ? subtracted : floor;
45 | signal[i] = output;
46 | }
47 | }
48 |
49 | void NoiseReductionReset(struct NoiseReductionState* state) {
50 | memset(state->estimate, 0, sizeof(*state->estimate) * state->num_channels);
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/microwakeword/TensorBuffer.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.microwakeword
2 |
3 | import org.tensorflow.lite.DataType
4 | import java.nio.ByteBuffer
5 | import java.nio.ByteOrder
6 | import kotlin.math.roundToInt
7 |
8 | abstract class TensorBuffer(
9 | dataType: DataType,
10 | shape: IntArray,
11 | val scale: Float,
12 | val zeroPoint: Int
13 | ) {
14 | private val _flatSize: Int = shape.reduce { acc, i -> acc * i }
15 | protected val buffer: ByteBuffer =
16 | ByteBuffer.allocateDirect(_flatSize * dataType.byteSize()).order(ByteOrder.nativeOrder())
17 |
18 | val flatSize get() = _flatSize
19 | val isComplete get() = !buffer.hasRemaining()
20 |
21 | abstract fun put(src: FloatArray)
22 |
23 | fun getTensor(): ByteBuffer {
24 | val tensor = buffer.duplicate()
25 | .order(ByteOrder.nativeOrder())
26 | .apply { flip() }
27 | return tensor
28 | }
29 |
30 | fun clear() {
31 | buffer.clear()
32 | }
33 |
34 | fun quantize(value: Float): Float {
35 | return (value / scale) + zeroPoint
36 | }
37 |
38 | companion object {
39 | fun create(
40 | dataType: DataType,
41 | shape: IntArray,
42 | scale: Float,
43 | zeroPoint: Int
44 | ): TensorBuffer {
45 | return when (dataType) {
46 | DataType.FLOAT32 -> TensorBufferFloat(shape, scale, zeroPoint)
47 | DataType.UINT8, DataType.INT8 -> TensorBufferUint8(shape, scale, zeroPoint)
48 | else -> throw IllegalArgumentException("Unsupported data type: $dataType")
49 | }
50 | }
51 | }
52 | }
53 |
54 | class TensorBufferUint8(shape: IntArray, scale: Float, zeroPoint: Int) :
55 | TensorBuffer(DataType.UINT8, shape, scale, zeroPoint) {
56 | override fun put(src: FloatArray) {
57 | for (value in src) {
58 | buffer.put(quantize(value).roundToInt().toByte())
59 | }
60 | }
61 | }
62 |
63 | class TensorBufferFloat(shape: IntArray, scale: Float, zeroPoint: Int) :
64 | TensorBuffer(DataType.FLOAT32, shape, scale, zeroPoint) {
65 | override fun put(src: FloatArray) {
66 | for (value in src) {
67 | buffer.putFloat(quantize(value))
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/frontend_util.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FRONTEND_UTIL_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FRONTEND_UTIL_H_
17 |
18 | #include "tensorflow/lite/experimental/microfrontend/lib/fft_util.h"
19 | #include "tensorflow/lite/experimental/microfrontend/lib/filterbank_util.h"
20 | #include "tensorflow/lite/experimental/microfrontend/lib/frontend.h"
21 | #include "tensorflow/lite/experimental/microfrontend/lib/log_scale_util.h"
22 | #include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.h"
23 | #include "tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_util.h"
24 | #include "tensorflow/lite/experimental/microfrontend/lib/window_util.h"
25 |
26 | #ifdef __cplusplus
27 | extern "C" {
28 | #endif
29 |
30 | struct FrontendConfig {
31 | struct WindowConfig window;
32 | struct FilterbankConfig filterbank;
33 | struct NoiseReductionConfig noise_reduction;
34 | struct PcanGainControlConfig pcan_gain_control;
35 | struct LogScaleConfig log_scale;
36 | };
37 |
38 | // Fills the frontendConfig with "sane" defaults.
39 | void FrontendFillConfigWithDefaults(struct FrontendConfig* config);
40 |
41 | // Allocates any buffers.
42 | int FrontendPopulateState(const struct FrontendConfig* config,
43 | struct FrontendState* state, int sample_rate);
44 |
45 | // Frees any allocated buffers.
46 | void FrontendFreeStateContents(struct FrontendState* state);
47 |
48 | #ifdef __cplusplus
49 | } // extern "C"
50 | #endif
51 |
52 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FRONTEND_UTIL_H_
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/nsd/NsdRegistration.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.nsd
2 |
3 | import android.content.Context
4 | import android.net.nsd.NsdManager
5 | import android.net.nsd.NsdServiceInfo
6 | import android.util.Log
7 | import androidx.core.content.ContextCompat
8 |
9 | class NsdRegistration(
10 | var name: String,
11 | type: String,
12 | port: Int,
13 | attributes: Map = emptyMap()
14 | ) {
15 | private val serviceInfo = NsdServiceInfo().apply {
16 | serviceName = name
17 | serviceType = type
18 | this.port = port
19 | for ((key, value) in attributes) {
20 | setAttribute(key, value)
21 | }
22 | }
23 |
24 | private val registrationListener = object : NsdManager.RegistrationListener {
25 | override fun onServiceRegistered(nsdServiceInfo: NsdServiceInfo) {
26 | // Name may have been automatically changed to resolve a conflict
27 | name = nsdServiceInfo.serviceName
28 | Log.d(TAG, "Service registered: $name")
29 | }
30 |
31 | override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
32 | Log.e(TAG, "Service registration failed: $errorCode")
33 | }
34 |
35 | override fun onServiceUnregistered(arg0: NsdServiceInfo) {
36 | Log.d(TAG, "Service unregistered: $name")
37 | }
38 |
39 | override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
40 | Log.e(TAG, "Service unregistration failed: $errorCode")
41 | }
42 | }
43 |
44 | fun register(context: Context) {
45 | try {
46 | ContextCompat.getSystemService(context, NsdManager::class.java)?.apply {
47 | registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener)
48 | }
49 | } catch (e: Exception) {
50 | Log.e(TAG, "Service registration failed", e)
51 | }
52 | }
53 |
54 | fun unregister(context: Context) {
55 | try {
56 | ContextCompat.getSystemService(context, NsdManager::class.java)?.apply {
57 | unregisterService(registrationListener)
58 | }
59 | } catch (e: Exception) {
60 | Log.e(TAG, "Service unregistration failed", e)
61 | }
62 | }
63 |
64 | companion object {
65 | const val TAG = "NsdRegistration"
66 | }
67 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/log_scale_test.cc:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/log_scale.h"
16 |
17 | #include "tensorflow/lite/experimental/microfrontend/lib/log_scale_util.h"
18 | #include "tensorflow/lite/micro/testing/micro_test.h"
19 |
20 | namespace {
21 |
22 | const int kScaleShift = 6;
23 | const int kCorrectionBits = -1;
24 |
25 | } // namespace
26 |
27 | TF_LITE_MICRO_TESTS_BEGIN
28 |
29 | TF_LITE_MICRO_TEST(LogScaleTest_CheckOutputValues) {
30 | struct LogScaleState state;
31 | state.enable_log = true;
32 | state.scale_shift = kScaleShift;
33 |
34 | uint32_t fake_signal[] = {3578, 1533};
35 | uint16_t* output = LogScaleApply(&state, fake_signal,
36 | sizeof(fake_signal) / sizeof(fake_signal[0]),
37 | kCorrectionBits);
38 |
39 | const uint16_t expected[] = {479, 425};
40 | for (size_t i = 0; i < sizeof(expected) / sizeof(expected[0]); ++i) {
41 | TF_LITE_MICRO_EXPECT_EQ(output[i], expected[i]);
42 | }
43 | }
44 |
45 | TF_LITE_MICRO_TEST(LogScaleTest_CheckOutputValuesNoLog) {
46 | struct LogScaleState state;
47 | state.enable_log = false;
48 | state.scale_shift = kScaleShift;
49 |
50 | uint32_t fake_signal[] = {85964, 45998};
51 | uint16_t* output = LogScaleApply(&state, fake_signal,
52 | sizeof(fake_signal) / sizeof(fake_signal[0]),
53 | kCorrectionBits);
54 |
55 | const uint16_t expected[] = {65535, 45998};
56 | for (size_t i = 0; i < sizeof(expected) / sizeof(expected[0]); ++i) {
57 | TF_LITE_MICRO_EXPECT_EQ(output[i], expected[i]);
58 | }
59 | }
60 |
61 | TF_LITE_MICRO_TESTS_END
62 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_test.cc:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.h"
16 |
17 | #include "tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_util.h"
18 | #include "tensorflow/lite/micro/testing/micro_test.h"
19 |
20 | namespace {
21 |
22 | const int kNumChannels = 2;
23 | const int kSmoothingBits = 10;
24 | const int kCorrectionBits = -1;
25 |
26 | // Test pcan auto gain control using default config values.
27 | class PcanGainControlTestConfig {
28 | public:
29 | PcanGainControlTestConfig() {
30 | config_.enable_pcan = 1;
31 | config_.strength = 0.95;
32 | config_.offset = 80.0;
33 | config_.gain_bits = 21;
34 | }
35 |
36 | struct PcanGainControlConfig config_;
37 | };
38 |
39 | } // namespace
40 |
41 | TF_LITE_MICRO_TESTS_BEGIN
42 |
43 | TF_LITE_MICRO_TEST(PcanGainControlTest_TestPcanGainControl) {
44 | uint32_t estimate[] = {6321887, 31248341};
45 | PcanGainControlTestConfig config;
46 | struct PcanGainControlState state;
47 | TF_LITE_MICRO_EXPECT(PcanGainControlPopulateState(
48 | &config.config_, &state, estimate, kNumChannels, kSmoothingBits,
49 | kCorrectionBits));
50 |
51 | uint32_t signal[] = {241137, 478104};
52 | PcanGainControlApply(&state, signal);
53 |
54 | const uint32_t expected[] = {3578, 1533};
55 | TF_LITE_MICRO_EXPECT_EQ(state.num_channels,
56 | static_cast(sizeof(expected) / sizeof(expected[0])));
57 | int i;
58 | for (i = 0; i < state.num_channels; ++i) {
59 | TF_LITE_MICRO_EXPECT_EQ(signal[i], expected[i]);
60 | }
61 |
62 | PcanGainControlFreeStateContents(&state);
63 | }
64 |
65 | TF_LITE_MICRO_TESTS_END
66 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_util.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_PCAN_GAIN_CONTROL_UTIL_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_PCAN_GAIN_CONTROL_UTIL_H_
17 |
18 | #include "tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.h"
19 |
20 | #define kWideDynamicFunctionBits 32
21 | #define kWideDynamicFunctionLUTSize (4 * kWideDynamicFunctionBits - 3)
22 |
23 | #ifdef __cplusplus
24 | extern "C" {
25 | #endif
26 |
27 | struct PcanGainControlConfig {
28 | // set to false (0) to disable this module
29 | int enable_pcan;
30 | // gain normalization exponent (0.0 disables, 1.0 full strength)
31 | float strength;
32 | // positive value added in the normalization denominator
33 | float offset;
34 | // number of fractional bits in the gain
35 | int gain_bits;
36 | };
37 |
38 | void PcanGainControlFillConfigWithDefaults(
39 | struct PcanGainControlConfig* config);
40 |
41 | int16_t PcanGainLookupFunction(const struct PcanGainControlConfig* config,
42 | int32_t input_bits, uint32_t x);
43 |
44 | int PcanGainControlPopulateState(const struct PcanGainControlConfig* config,
45 | struct PcanGainControlState* state,
46 | uint32_t* noise_estimate,
47 | const int num_channels,
48 | const uint16_t smoothing_bits,
49 | const int32_t input_correction_bits);
50 |
51 | void PcanGainControlFreeStateContents(struct PcanGainControlState* state);
52 |
53 | #ifdef __cplusplus
54 | } // extern "C"
55 | #endif
56 |
57 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_PCAN_GAIN_CONTROL_UTIL_H_
58 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/filterbank.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FILTERBANK_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FILTERBANK_H_
17 |
18 | #include
19 | #include
20 |
21 | #include "tensorflow/lite/experimental/microfrontend/lib/fft.h"
22 |
23 | #define kFilterbankBits 12
24 |
25 | #ifdef __cplusplus
26 | extern "C" {
27 | #endif
28 |
29 | struct FilterbankState {
30 | int num_channels;
31 | int start_index;
32 | int end_index;
33 | int16_t* channel_frequency_starts;
34 | int16_t* channel_weight_starts;
35 | int16_t* channel_widths;
36 | int16_t* weights;
37 | int16_t* unweights;
38 | uint64_t* work;
39 | };
40 |
41 | // Converts the relevant complex values of an FFT output into energy (the
42 | // square magnitude).
43 | void FilterbankConvertFftComplexToEnergy(struct FilterbankState* state,
44 | struct complex_int16_t* fft_output,
45 | int32_t* energy);
46 |
47 | // Computes the mel-scale filterbank on the given energy array. Output is cached
48 | // internally - to fetch it, you need to call FilterbankSqrt.
49 | void FilterbankAccumulateChannels(struct FilterbankState* state,
50 | const int32_t* energy);
51 |
52 | // Applies an integer square root to the 64 bit intermediate values of the
53 | // filterbank, and returns a pointer to them. Memory will be invalidated the
54 | // next time FilterbankAccumulateChannels is called.
55 | uint32_t* FilterbankSqrt(struct FilterbankState* state, int scale_down_shift);
56 |
57 | void FilterbankReset(struct FilterbankState* state);
58 |
59 | #ifdef __cplusplus
60 | } // extern "C"
61 | #endif
62 |
63 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FILTERBANK_H_
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/esphome/voicesatellite/VoiceSatellitePlayer.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.esphome.voicesatellite
2 |
3 | import androidx.annotation.OptIn
4 | import androidx.media3.common.util.UnstableApi
5 | import com.example.ava.players.AudioPlayer
6 | import com.example.ava.players.TtsPlayer
7 | import com.example.ava.settings.SettingState
8 | import kotlinx.coroutines.flow.MutableStateFlow
9 | import kotlinx.coroutines.flow.asStateFlow
10 |
11 | @OptIn(UnstableApi::class)
12 | class VoiceSatellitePlayer(
13 | val ttsPlayer: TtsPlayer,
14 | val mediaPlayer: AudioPlayer,
15 | volume: Float = 1.0f,
16 | muted: Boolean = false,
17 | val enableWakeSound: SettingState,
18 | val wakeSound: SettingState,
19 | val timerFinishedSound: SettingState,
20 | private val duckMultiplier: Float = 0.5f
21 | ) : AutoCloseable {
22 | private var _isDucked = false
23 | private val _volume = MutableStateFlow(volume)
24 | private val _muted = MutableStateFlow(muted)
25 |
26 | val volume get() = _volume.asStateFlow()
27 | fun setVolume(value: Float) {
28 | _volume.value = value
29 | if (!_muted.value) {
30 | ttsPlayer.volume = value
31 | mediaPlayer.volume = if (_isDucked) value * duckMultiplier else value
32 | }
33 | }
34 |
35 | val muted get() = _muted.asStateFlow()
36 | fun setMuted(value: Boolean) {
37 | _muted.value = value
38 | if (value) {
39 | mediaPlayer.volume = 0.0f
40 | ttsPlayer.volume = 0.0f
41 | } else {
42 | ttsPlayer.volume = _volume.value
43 | mediaPlayer.volume = if (_isDucked) _volume.value * duckMultiplier else _volume.value
44 | }
45 | }
46 |
47 | suspend fun playWakeSound(onCompletion: () -> Unit = {}) {
48 | if (enableWakeSound.get())
49 | ttsPlayer.playSound(wakeSound.get(), onCompletion)
50 | else onCompletion()
51 | }
52 |
53 | suspend fun playTimerFinishedSound(onCompletion: () -> Unit = {}) {
54 | ttsPlayer.playSound(timerFinishedSound.get(), onCompletion)
55 | }
56 |
57 | fun duck() {
58 | _isDucked = true
59 | if (!_muted.value) {
60 | mediaPlayer.volume = _volume.value * duckMultiplier
61 | }
62 | }
63 |
64 | fun unDuck() {
65 | _isDucked = false
66 | if (!_muted.value) {
67 | mediaPlayer.volume = _volume.value
68 | }
69 | }
70 |
71 | override fun close() {
72 | ttsPlayer.close()
73 | mediaPlayer.close()
74 | }
75 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/wakelocks/WifiWakeLock.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.wakelocks
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.content.Context.POWER_SERVICE
6 | import android.content.Context.WIFI_SERVICE
7 | import android.net.wifi.WifiManager
8 | import android.os.Build
9 | import android.os.PowerManager
10 | import android.util.Log
11 |
12 | class WifiWakeLock {
13 | private lateinit var wakeLock: PowerManager.WakeLock
14 | private lateinit var wifiLock: WifiManager.WifiLock
15 |
16 | fun create(context: Context, tag: String) {
17 | check(!::wakeLock.isInitialized || !::wifiLock.isInitialized) {
18 | "create called multiple times"
19 | }
20 |
21 | // Allow the screen to turn off but keep CPU awake for processing audio data
22 | wakeLock = (context.getSystemService(POWER_SERVICE) as PowerManager)
23 | .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$tag::Wakelock")
24 |
25 | // Ideally the wifi lock should keep the wifi connected whilst the screen is off to maintain
26 | // the connection to any connected clients. This requires WIFI_MODE_FULL_HIGH_PERF, however
27 | // starting with API level 34 only WIFI_MODE_FULL_LOW_LATENCY is available, which doesn't
28 | // keep the wifi connected when the screen is off.
29 | // Use WIFI_MODE_FULL_HIGH_PERF where supported.
30 | @Suppress("DEPRECATION")
31 | val wifiLockType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
32 | WifiManager.WIFI_MODE_FULL_LOW_LATENCY
33 | else WifiManager.WIFI_MODE_FULL_HIGH_PERF
34 |
35 | wifiLock = (context.getSystemService(WIFI_SERVICE) as WifiManager).createWifiLock(
36 | wifiLockType,
37 | "$tag::WifiLock"
38 | )
39 | }
40 |
41 | @SuppressLint("WakelockTimeout")
42 | fun acquire() {
43 | check(::wakeLock.isInitialized && ::wifiLock.isInitialized) {
44 | "acquire called before create"
45 | }
46 | wakeLock.acquire()
47 | wifiLock.acquire()
48 | Log.d(TAG, "Acquired wake locks")
49 | }
50 |
51 | fun release() {
52 | check(::wakeLock.isInitialized && ::wifiLock.isInitialized) {
53 | "release called before create"
54 | }
55 | if (wakeLock.isHeld)
56 | wakeLock.release()
57 | if (wifiLock.isHeld)
58 | wifiLock.release()
59 | Log.d(TAG, "Released wake locks")
60 | }
61 |
62 | companion object {
63 | private const val TAG = "WifiWakeLock"
64 | }
65 | }
--------------------------------------------------------------------------------
/esphomeproto/src/main/proto/api_options.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 | option java_package = "com.example.esphomeproto.api.options";
3 | import "google/protobuf/descriptor.proto";
4 |
5 | enum APISourceType {
6 | SOURCE_BOTH = 0;
7 | SOURCE_SERVER = 1;
8 | SOURCE_CLIENT = 2;
9 | }
10 |
11 | message voidMsg {}
12 |
13 | extend google.protobuf.MethodOptions {
14 | optional bool needs_setup_connection = 1038 [default=true];
15 | optional bool needs_authentication = 1039 [default=true];
16 | }
17 |
18 | extend google.protobuf.MessageOptions {
19 | optional uint32 id = 1036 [default=0];
20 | optional APISourceType source = 1037 [default=SOURCE_BOTH];
21 | optional string ifdef = 1038;
22 | optional bool log = 1039 [default=true];
23 | optional bool no_delay = 1040 [default=false];
24 | optional string base_class = 1041;
25 | }
26 |
27 | extend google.protobuf.FieldOptions {
28 | optional string field_ifdef = 1042;
29 | optional uint32 fixed_array_size = 50007;
30 | optional bool no_zero_copy = 50008 [default=false];
31 | optional bool fixed_array_skip_zero = 50009 [default=false];
32 | optional string fixed_array_size_define = 50010;
33 | optional string fixed_array_with_length_define = 50011;
34 |
35 | // container_pointer: Zero-copy optimization for repeated fields.
36 | //
37 | // When container_pointer is set on a repeated field, the generated message will
38 | // store a pointer to an existing container instead of copying the data into the
39 | // message's own repeated field. This eliminates heap allocations and improves performance.
40 | //
41 | // Requirements for safe usage:
42 | // 1. The source container must remain valid until the message is encoded
43 | // 2. Messages must be encoded immediately (which ESPHome does by default)
44 | // 3. The container type must match the field type exactly
45 | //
46 | // Supported container types:
47 | // - "std::vector" for most repeated fields
48 | // - "std::set" for unique/sorted data
49 | // - Full type specification required for enums (e.g., "std::set")
50 | //
51 | // Example usage in .proto file:
52 | // repeated string supported_modes = 12 [(container_pointer) = "std::set"];
53 | // repeated ColorMode color_modes = 13 [(container_pointer) = "std::set"];
54 | //
55 | // The corresponding C++ code must provide const reference access to a container
56 | // that matches the specified type and remains valid during message encoding.
57 | // This is typically done through methods returning const T& or special accessor
58 | // methods like get_options() or supported_modes_for_api_().
59 | optional string container_pointer = 50001;
60 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/frontend_main.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include
16 |
17 | #include "tensorflow/lite/experimental/microfrontend/lib/frontend.h"
18 | #include "tensorflow/lite/experimental/microfrontend/lib/frontend_util.h"
19 |
20 | int main(int argc, char** argv) {
21 | struct FrontendConfig frontend_config;
22 | FrontendFillConfigWithDefaults(&frontend_config);
23 |
24 | char* filename = argv[1];
25 | int sample_rate = 16000;
26 |
27 | struct FrontendState frontend_state;
28 | if (!FrontendPopulateState(&frontend_config, &frontend_state, sample_rate)) {
29 | fprintf(stderr, "Failed to populate frontend state\n");
30 | FrontendFreeStateContents(&frontend_state);
31 | return 1;
32 | }
33 |
34 | FILE* fp = fopen(filename, "r");
35 | if (fp == NULL) {
36 | fprintf(stderr, "Failed to open %s for read\n", filename);
37 | return 1;
38 | }
39 | fseek(fp, 0L, SEEK_END);
40 | size_t audio_file_size = ftell(fp) / sizeof(int16_t);
41 | fseek(fp, 0L, SEEK_SET);
42 | int16_t* audio_data = malloc(audio_file_size * sizeof(int16_t));
43 | int16_t* original_audio_data = audio_data;
44 | if (audio_file_size !=
45 | fread(audio_data, sizeof(int16_t), audio_file_size, fp)) {
46 | fprintf(stderr, "Failed to read in all audio data\n");
47 | fclose(fp);
48 | return 1;
49 | }
50 |
51 | while (audio_file_size > 0) {
52 | size_t num_samples_read;
53 | struct FrontendOutput output = FrontendProcessSamples(
54 | &frontend_state, audio_data, audio_file_size, &num_samples_read);
55 | audio_data += num_samples_read;
56 | audio_file_size -= num_samples_read;
57 |
58 | if (output.values != NULL) {
59 | size_t i;
60 | for (i = 0; i < output.size; ++i) {
61 | printf("%d ", output.values[i]);
62 | }
63 | printf("\n");
64 | }
65 | }
66 |
67 | FrontendFreeStateContents(&frontend_state);
68 | free(original_audio_data);
69 | fclose(fp);
70 | return 0;
71 | }
72 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/window_util.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/window_util.h"
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | // Some platforms don't have M_PI
23 | #ifndef M_PI
24 | #define M_PI 3.14159265358979323846
25 | #endif
26 |
27 | void WindowFillConfigWithDefaults(struct WindowConfig* config) {
28 | config->size_ms = 25;
29 | config->step_size_ms = 10;
30 | }
31 |
32 | int WindowPopulateState(const struct WindowConfig* config,
33 | struct WindowState* state, int sample_rate) {
34 | state->size = config->size_ms * sample_rate / 1000;
35 | state->step = config->step_size_ms * sample_rate / 1000;
36 |
37 | state->coefficients = malloc(state->size * sizeof(*state->coefficients));
38 | if (state->coefficients == NULL) {
39 | fprintf(stderr, "Failed to allocate window coefficients\n");
40 | return 0;
41 | }
42 |
43 | // Populate the window values.
44 | const float arg = (float)M_PI * 2.0f / ((float)state->size);
45 | size_t i;
46 | for (i = 0; i < state->size; ++i) {
47 | float float_value = 0.5f - (0.5f * cosf(arg * (i + 0.5f)));
48 | // Scale it to fixed point and round it.
49 | state->coefficients[i] =
50 | floorf(float_value * (1 << kFrontendWindowBits) + 0.5f);
51 | }
52 |
53 | state->input_used = 0;
54 | state->input = malloc(state->size * sizeof(*state->input));
55 | if (state->input == NULL) {
56 | fprintf(stderr, "Failed to allocate window input\n");
57 | return 0;
58 | }
59 |
60 | state->output = malloc(state->size * sizeof(*state->output));
61 | if (state->output == NULL) {
62 | fprintf(stderr, "Failed to allocate window output\n");
63 | return 0;
64 | }
65 |
66 | return 1;
67 | }
68 |
69 | void WindowFreeStateContents(struct WindowState* state) {
70 | free(state->coefficients);
71 | free(state->input);
72 | free(state->output);
73 | }
74 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/fft_util.cc:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/fft_util.h"
16 |
17 | #include
18 |
19 | #include "tensorflow/lite/experimental/microfrontend/lib/kiss_fft_int16.h"
20 |
21 | int FftPopulateState(struct FftState* state, size_t input_size) {
22 | state->input_size = input_size;
23 | state->fft_size = 1;
24 | while (state->fft_size < state->input_size) {
25 | state->fft_size <<= 1;
26 | }
27 |
28 | state->input = reinterpret_cast(
29 | malloc(state->fft_size * sizeof(*state->input)));
30 | if (state->input == nullptr) {
31 | fprintf(stderr, "Failed to alloc fft input buffer\n");
32 | return 0;
33 | }
34 |
35 | state->output = reinterpret_cast(
36 | malloc((state->fft_size / 2 + 1) * sizeof(*state->output) * 2));
37 | if (state->output == nullptr) {
38 | fprintf(stderr, "Failed to alloc fft output buffer\n");
39 | return 0;
40 | }
41 |
42 | // Ask kissfft how much memory it wants.
43 | size_t scratch_size = 0;
44 | kissfft_fixed16::kiss_fftr_cfg kfft_cfg = kissfft_fixed16::kiss_fftr_alloc(
45 | state->fft_size, 0, nullptr, &scratch_size);
46 | if (kfft_cfg != nullptr) {
47 | fprintf(stderr, "Kiss memory sizing failed.\n");
48 | return 0;
49 | }
50 | state->scratch = malloc(scratch_size);
51 | if (state->scratch == nullptr) {
52 | fprintf(stderr, "Failed to alloc fft scratch buffer\n");
53 | return 0;
54 | }
55 | state->scratch_size = scratch_size;
56 | // Let kissfft configure the scratch space we just allocated
57 | kfft_cfg = kissfft_fixed16::kiss_fftr_alloc(state->fft_size, 0,
58 | state->scratch, &scratch_size);
59 | if (kfft_cfg != state->scratch) {
60 | fprintf(stderr, "Kiss memory preallocation strategy failed.\n");
61 | return 0;
62 | }
63 | return 1;
64 | }
65 |
66 | void FftFreeStateContents(struct FftState* state) {
67 | free(state->input);
68 | free(state->output);
69 | free(state->scratch);
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/audio/MicrophoneInput.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.audio
2 |
3 | import android.Manifest
4 | import android.media.AudioFormat
5 | import android.media.AudioRecord
6 | import android.media.MediaRecorder
7 | import android.util.Log
8 | import androidx.annotation.RequiresPermission
9 | import java.nio.ByteBuffer
10 |
11 | class MicrophoneInput(
12 | val audioSource: Int = DEFAULT_AUDIO_SOURCE,
13 | val sampleRateInHz: Int = DEFAULT_SAMPLE_RATE_IN_HZ,
14 | val channelConfig: Int = DEFAULT_CHANNEL_CONFIG,
15 | val audioFormat: Int = DEFAULT_AUDIO_FORMAT
16 | ) : AutoCloseable {
17 | private val bufferSize =
18 | AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
19 | private val buffer = ByteBuffer.allocateDirect(bufferSize)
20 | private var audioRecord: AudioRecord? = null
21 | val isRecording get() = audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING
22 |
23 | @RequiresPermission(Manifest.permission.RECORD_AUDIO)
24 | fun start() {
25 | if (audioRecord == null) {
26 | audioRecord = createAudioRecord()
27 | }
28 | if (!isRecording) {
29 | Log.d(TAG, "Starting microphone")
30 | audioRecord?.startRecording()
31 | } else {
32 | Log.w(TAG, "Microphone already started")
33 | }
34 | }
35 |
36 | fun read(): ByteBuffer {
37 | val audioRecord = this.audioRecord ?: error("Microphone not started")
38 | buffer.clear()
39 | val read = audioRecord.read(buffer, bufferSize)
40 | check(read >= 0) {
41 | "error reading audio, read: $read"
42 | }
43 | buffer.limit(read)
44 | return buffer
45 | }
46 |
47 | @RequiresPermission(Manifest.permission.RECORD_AUDIO)
48 | private fun createAudioRecord(): AudioRecord {
49 | val audioRecord = AudioRecord(
50 | audioSource,
51 | sampleRateInHz,
52 | channelConfig,
53 | audioFormat,
54 | bufferSize * 2
55 | )
56 | check(audioRecord.state == AudioRecord.STATE_INITIALIZED) {
57 | "Failed to initialize AudioRecord"
58 | }
59 | return audioRecord
60 | }
61 |
62 | override fun close() {
63 | audioRecord?.let {
64 | if (isRecording) {
65 | it.stop()
66 | }
67 | it.release()
68 | audioRecord = null
69 | }
70 | }
71 |
72 | companion object {
73 | const val TAG = "MicrophoneInput"
74 | const val DEFAULT_AUDIO_SOURCE = MediaRecorder.AudioSource.VOICE_RECOGNITION
75 | const val DEFAULT_SAMPLE_RATE_IN_HZ = 16000
76 | const val DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
77 | const val DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
78 | }
79 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/frontend.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FRONTEND_H_
16 | #define TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FRONTEND_H_
17 |
18 | #include
19 | #include
20 |
21 | #include "tensorflow/lite/experimental/microfrontend/lib/fft.h"
22 | #include "tensorflow/lite/experimental/microfrontend/lib/filterbank.h"
23 | #include "tensorflow/lite/experimental/microfrontend/lib/log_scale.h"
24 | #include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction.h"
25 | #include "tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.h"
26 | #include "tensorflow/lite/experimental/microfrontend/lib/window.h"
27 |
28 | #ifdef __cplusplus
29 | extern "C" {
30 | #endif
31 |
32 | struct FrontendState {
33 | struct WindowState window;
34 | struct FftState fft;
35 | struct FilterbankState filterbank;
36 | struct NoiseReductionState noise_reduction;
37 | struct PcanGainControlState pcan_gain_control;
38 | struct LogScaleState log_scale;
39 | };
40 |
41 | struct FrontendOutput {
42 | const uint16_t* values;
43 | size_t size;
44 | };
45 |
46 | // Main entry point to processing frontend samples. Updates num_samples_read to
47 | // contain the number of samples that have been consumed from the input array.
48 | // Returns a struct containing the generated output. If not enough samples were
49 | // added to generate a feature vector, the returned size will be 0 and the
50 | // values pointer will be NULL. Note that the output pointer will be invalidated
51 | // as soon as FrontendProcessSamples is called again, so copy the contents
52 | // elsewhere if you need to use them later.
53 | struct FrontendOutput FrontendProcessSamples(struct FrontendState* state,
54 | const int16_t* samples,
55 | size_t num_samples,
56 | size_t* num_samples_read);
57 |
58 | void FrontendReset(struct FrontendState* state);
59 |
60 | #ifdef __cplusplus
61 | } // extern "C"
62 | #endif
63 |
64 | #endif // TENSORFLOW_LITE_EXPERIMENTAL_MICROFRONTEND_LIB_FRONTEND_H_
65 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/window.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/window.h"
16 |
17 | #include
18 |
19 | int WindowProcessSamples(struct WindowState* state, const int16_t* samples,
20 | size_t num_samples, size_t* num_samples_read) {
21 | const int size = state->size;
22 |
23 | // Copy samples from the samples buffer over to our local input.
24 | size_t max_samples_to_copy = state->size - state->input_used;
25 | if (max_samples_to_copy > num_samples) {
26 | max_samples_to_copy = num_samples;
27 | }
28 | memcpy(state->input + state->input_used, samples,
29 | max_samples_to_copy * sizeof(*samples));
30 | *num_samples_read = max_samples_to_copy;
31 | state->input_used += max_samples_to_copy;
32 |
33 | if (state->input_used < state->size) {
34 | // We don't have enough samples to compute a window.
35 | return 0;
36 | }
37 |
38 | // Apply the window to the input.
39 | const int16_t* coefficients = state->coefficients;
40 | const int16_t* input = state->input;
41 | int16_t* output = state->output;
42 | int i;
43 | int16_t max_abs_output_value = 0;
44 | for (i = 0; i < size; ++i) {
45 | int16_t new_value =
46 | (((int32_t)*input++) * *coefficients++) >> kFrontendWindowBits;
47 | *output++ = new_value;
48 | if (new_value < 0) {
49 | new_value = -new_value;
50 | }
51 | if (new_value > max_abs_output_value) {
52 | max_abs_output_value = new_value;
53 | }
54 | }
55 | // Shuffle the input down by the step size, and update how much we have used.
56 | memmove(state->input, state->input + state->step,
57 | sizeof(*state->input) * (state->size - state->step));
58 | state->input_used -= state->step;
59 | state->max_abs_output_value = max_abs_output_value;
60 |
61 | // Indicate that the output buffer is valid for the next stage.
62 | return 1;
63 | }
64 |
65 | void WindowReset(struct WindowState* state) {
66 | memset(state->input, 0, state->size * sizeof(*state->input));
67 | memset(state->output, 0, state->size * sizeof(*state->output));
68 | state->input_used = 0;
69 | state->max_abs_output_value = 0;
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/ui/services/ServiceViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.ui.services
2 |
3 | import android.app.Application
4 | import android.content.ComponentName
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.ServiceConnection
8 | import android.os.IBinder
9 | import android.util.Log
10 | import androidx.lifecycle.AndroidViewModel
11 | import androidx.lifecycle.application
12 | import androidx.lifecycle.viewModelScope
13 | import com.example.ava.services.VoiceSatelliteService
14 | import com.example.ava.settings.VoiceSatelliteSettingsStore
15 | import com.example.ava.settings.voiceSatelliteSettingsStore
16 | import kotlinx.coroutines.flow.MutableStateFlow
17 | import kotlinx.coroutines.flow.asStateFlow
18 | import kotlinx.coroutines.flow.dropWhile
19 | import kotlinx.coroutines.flow.first
20 | import kotlinx.coroutines.launch
21 |
22 | class ServiceViewModel(application: Application) : AndroidViewModel(application) {
23 | private var created = false
24 | private val settings = VoiceSatelliteSettingsStore(application.voiceSatelliteSettingsStore)
25 |
26 | private val _satellite = MutableStateFlow(null)
27 | val satellite = _satellite.asStateFlow()
28 |
29 | private val serviceConnection = bindService(application) {
30 | _satellite.value = it
31 | }
32 |
33 | override fun onCleared() {
34 | application.unbindService(serviceConnection)
35 | super.onCleared()
36 | }
37 |
38 | private fun bindService(
39 | context: Context,
40 | connectedChanged: (VoiceSatelliteService?) -> Unit
41 | ): ServiceConnection {
42 | val serviceConnection = object : ServiceConnection {
43 | override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
44 | (binder as? VoiceSatelliteService.VoiceSatelliteBinder)?.let {
45 | connectedChanged(it.service)
46 | }
47 | }
48 |
49 | override fun onServiceDisconnected(name: ComponentName?) {
50 | connectedChanged(null)
51 | }
52 | }
53 | val serviceIntent = Intent(context, VoiceSatelliteService::class.java)
54 | val bound = context.bindService(
55 | serviceIntent,
56 | serviceConnection,
57 | Context.BIND_AUTO_CREATE
58 | )
59 | if (!bound)
60 | Log.e(TAG, "Cannot bind to VoiceAssistantService")
61 | return serviceConnection
62 | }
63 |
64 | fun autoStartServiceIfRequired() {
65 | if (created)
66 | return
67 | created = true
68 | viewModelScope.launch {
69 | val autoStart = settings.autoStart.get()
70 | if (autoStart)
71 | _satellite.dropWhile { it == null }.first()?.startVoiceSatellite()
72 | }
73 | }
74 |
75 | companion object {
76 | private const val TAG = "ServiceViewModel"
77 | }
78 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/noise_reduction_test.cc:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction.h"
16 |
17 | #include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.h"
18 | #include "tensorflow/lite/micro/testing/micro_test.h"
19 |
20 | namespace {
21 |
22 | const int kNumChannels = 2;
23 |
24 | // Test noise reduction using default config values.
25 | class NoiseReductionTestConfig {
26 | public:
27 | NoiseReductionTestConfig() {
28 | config_.smoothing_bits = 10;
29 | config_.even_smoothing = 0.025;
30 | config_.odd_smoothing = 0.06;
31 | config_.min_signal_remaining = 0.05;
32 | }
33 |
34 | struct NoiseReductionConfig config_;
35 | };
36 |
37 | } // namespace
38 |
39 | TF_LITE_MICRO_TESTS_BEGIN
40 |
41 | TF_LITE_MICRO_TEST(NoiseReductionTest_TestNoiseReductionEstimate) {
42 | NoiseReductionTestConfig config;
43 | struct NoiseReductionState state;
44 | TF_LITE_MICRO_EXPECT(
45 | NoiseReductionPopulateState(&config.config_, &state, kNumChannels));
46 |
47 | uint32_t signal[] = {247311, 508620};
48 | NoiseReductionApply(&state, signal);
49 |
50 | const uint32_t expected[] = {6321887, 31248341};
51 | TF_LITE_MICRO_EXPECT_EQ(state.num_channels,
52 | static_cast(sizeof(expected) / sizeof(expected[0])));
53 | int i;
54 | for (i = 0; i < state.num_channels; ++i) {
55 | TF_LITE_MICRO_EXPECT_EQ(state.estimate[i], expected[i]);
56 | }
57 |
58 | NoiseReductionFreeStateContents(&state);
59 | }
60 |
61 | TF_LITE_MICRO_TEST(NoiseReductionTest_TestNoiseReduction) {
62 | NoiseReductionTestConfig config;
63 | struct NoiseReductionState state;
64 | TF_LITE_MICRO_EXPECT(
65 | NoiseReductionPopulateState(&config.config_, &state, kNumChannels));
66 |
67 | uint32_t signal[] = {247311, 508620};
68 | NoiseReductionApply(&state, signal);
69 |
70 | const uint32_t expected[] = {241137, 478104};
71 | TF_LITE_MICRO_EXPECT_EQ(state.num_channels,
72 | static_cast(sizeof(expected) / sizeof(expected[0])));
73 | int i;
74 | for (i = 0; i < state.num_channels; ++i) {
75 | TF_LITE_MICRO_EXPECT_EQ(signal[i], expected[i]);
76 | }
77 |
78 | NoiseReductionFreeStateContents(&state);
79 | }
80 |
81 | TF_LITE_MICRO_TESTS_END
82 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/filterbank_io.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/filterbank_io.h"
16 |
17 | static void PrintArray(FILE* fp, const char* name, const int16_t* values,
18 | size_t size) {
19 | fprintf(fp, "static int16_t filterbank_%s[] = {", name);
20 | size_t i;
21 | for (i = 0; i < size; ++i) {
22 | fprintf(fp, "%d", values[i]);
23 | if (i < size - 1) {
24 | fprintf(fp, ", ");
25 | }
26 | }
27 | fprintf(fp, "};\n");
28 | }
29 |
30 | void FilterbankWriteMemmapPreamble(FILE* fp,
31 | const struct FilterbankState* state) {
32 | const int num_channels_plus_1 = state->num_channels + 1;
33 |
34 | PrintArray(fp, "channel_frequency_starts", state->channel_frequency_starts,
35 | num_channels_plus_1);
36 | PrintArray(fp, "channel_weight_starts", state->channel_weight_starts,
37 | num_channels_plus_1);
38 | PrintArray(fp, "channel_widths", state->channel_widths, num_channels_plus_1);
39 | int num_weights = 0;
40 | int i;
41 | for (i = 0; i < num_channels_plus_1; ++i) {
42 | num_weights += state->channel_widths[i];
43 | }
44 | PrintArray(fp, "weights", state->weights, num_weights);
45 | PrintArray(fp, "unweights", state->unweights, num_weights);
46 |
47 | fprintf(fp, "static uint64_t filterbank_work[%d];\n", num_channels_plus_1);
48 | fprintf(fp, "\n");
49 | }
50 |
51 | void FilterbankWriteMemmap(FILE* fp, const struct FilterbankState* state,
52 | const char* variable) {
53 | fprintf(fp, "%s->num_channels = %d;\n", variable, state->num_channels);
54 | fprintf(fp, "%s->start_index = %d;\n", variable, state->start_index);
55 | fprintf(fp, "%s->end_index = %d;\n", variable, state->end_index);
56 |
57 | fprintf(
58 | fp,
59 | "%s->channel_frequency_starts = filterbank_channel_frequency_starts;\n",
60 | variable);
61 | fprintf(fp, "%s->channel_weight_starts = filterbank_channel_weight_starts;\n",
62 | variable);
63 | fprintf(fp, "%s->channel_widths = filterbank_channel_widths;\n", variable);
64 | fprintf(fp, "%s->weights = filterbank_weights;\n", variable);
65 | fprintf(fp, "%s->unweights = filterbank_unweights;\n", variable);
66 | fprintf(fp, "%s->work = filterbank_work;\n", variable);
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/players/TtsPlayer.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.players
2 |
3 | import android.util.Log
4 | import androidx.annotation.OptIn
5 | import androidx.media3.common.util.UnstableApi
6 |
7 | @OptIn(UnstableApi::class)
8 | class TtsPlayer
9 | (private val player: AudioPlayer) : AutoCloseable {
10 |
11 | private var ttsStreamUrl: String? = null
12 | private var _ttsPlayed: Boolean = false
13 | val ttsPlayed: Boolean
14 | get() = _ttsPlayed
15 |
16 | private var onCompletion: (() -> Unit)? = null
17 |
18 | val isPlaying get() = player.isPlaying
19 |
20 | var volume
21 | get() = player.volume
22 | set(value) {
23 | player.volume = value
24 | }
25 |
26 | fun runStart(ttsStreamUrl: String?, onCompletion: () -> Unit) {
27 | this.ttsStreamUrl = ttsStreamUrl
28 | this.onCompletion = onCompletion
29 | _ttsPlayed = false
30 | // Init the player early so it gains system audio focus, this ducks any
31 | // background audio whilst the microphone is capturing voice
32 | player.init()
33 | }
34 |
35 | fun runEnd() {
36 | // Manually fire the completion handler only
37 | // if tts playback was not started, else it
38 | // will (or was) fired when the playback ended
39 | if (!_ttsPlayed) {
40 | fireAndRemoveCompletionHandler()
41 | }
42 | _ttsPlayed = false
43 | ttsStreamUrl = null
44 | }
45 |
46 | fun streamTts() {
47 | if (ttsStreamUrl != null)
48 | playTts(ttsStreamUrl)
49 | }
50 |
51 | fun playTts(ttsUrl: String?) {
52 | if (!ttsUrl.isNullOrBlank()) {
53 | _ttsPlayed = true
54 | player.play(ttsUrl) {
55 | fireAndRemoveCompletionHandler()
56 | }
57 | } else {
58 | Log.w(TAG, "TTS URL is null or blank")
59 | }
60 | }
61 |
62 | fun playSound(soundUrl: String?, onCompletion: () -> Unit) {
63 | playAnnouncement(soundUrl, null, onCompletion)
64 | }
65 |
66 | fun playAnnouncement(mediaUrl: String?, preannounceUrl: String?, onCompletion: () -> Unit) {
67 | if (!mediaUrl.isNullOrBlank()) {
68 | player.play(
69 | if (preannounceUrl.isNullOrBlank()) listOf(mediaUrl) else listOf(
70 | preannounceUrl,
71 | mediaUrl
72 | ), onCompletion
73 | )
74 | } else {
75 | Log.w(TAG, "Media URL is null or blank")
76 | }
77 | }
78 |
79 | fun stop() {
80 | onCompletion = null
81 | _ttsPlayed = false
82 | ttsStreamUrl = null
83 | player.stop()
84 | }
85 |
86 | private fun fireAndRemoveCompletionHandler() {
87 | val completion = onCompletion
88 | onCompletion = null
89 | completion?.invoke()
90 | }
91 |
92 | override fun close() {
93 | player.close()
94 | }
95 |
96 | companion object {
97 | private const val TAG = "TtsPlayer"
98 | }
99 | }
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.compose)
5 | // Kotlin serialization plugin for type safe routes and navigation arguments
6 | alias(libs.plugins.kotlin.serialization)
7 | }
8 |
9 | android {
10 | namespace = "com.example.ava"
11 | compileSdk = 36
12 |
13 | defaultConfig {
14 | applicationId = "com.example.ava"
15 | minSdk = 26
16 | targetSdk = 36
17 | versionCode = if (project.ext.has("versionCode"))
18 | project.ext.get("versionCode").toString().toInt() else 1
19 | versionName = if (project.ext.has("versionName"))
20 | project.ext.get("versionName").toString() else "0.0.0"
21 | base.archivesName = "Ava-$versionName"
22 |
23 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
24 | }
25 |
26 | buildTypes {
27 | release {
28 | isMinifyEnabled = false
29 | proguardFiles(
30 | getDefaultProguardFile("proguard-android-optimize.txt"),
31 | "proguard-rules.pro"
32 | )
33 | }
34 | }
35 | compileOptions {
36 | sourceCompatibility = JavaVersion.VERSION_11
37 | targetCompatibility = JavaVersion.VERSION_11
38 | }
39 | kotlin {
40 | compilerOptions {
41 | jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
42 | }
43 | }
44 | buildFeatures {
45 | compose = true
46 | }
47 | }
48 |
49 | dependencies {
50 |
51 | implementation(project(":esphomeproto"))
52 | implementation(project(":microfeatures"))
53 | implementation(libs.androidx.core.ktx)
54 | implementation(libs.androidx.lifecycle.runtime.ktx)
55 | implementation(libs.androidx.lifecycle.service)
56 | implementation(libs.androidx.activity.compose)
57 | implementation(platform(libs.androidx.compose.bom))
58 | implementation(libs.androidx.ui)
59 | implementation(libs.androidx.ui.graphics)
60 | implementation(libs.androidx.ui.tooling.preview)
61 | implementation(libs.androidx.material)
62 | implementation(libs.androidx.navigation.compose)
63 | implementation(libs.gson)
64 | implementation(libs.kotlinx.serialization.json)
65 | implementation(libs.litert)
66 | implementation(libs.protobuf.kotlin)
67 | implementation(libs.androidx.datastore)
68 | implementation(libs.androidx.lifecycle.viewmodel.compose)
69 | implementation(libs.androidx.media3.common.ktx)
70 | implementation(libs.androidx.media3.exoplayer)
71 | implementation(libs.androidx.media3.exoplayer.hls)
72 | implementation(libs.material3)
73 | testImplementation(libs.junit)
74 | androidTestImplementation(libs.androidx.junit)
75 | androidTestImplementation(libs.androidx.espresso.core)
76 | androidTestImplementation(platform(libs.androidx.compose.bom))
77 | androidTestImplementation(libs.androidx.ui.test.junit4)
78 | debugImplementation(libs.androidx.ui.tooling)
79 | debugImplementation(libs.androidx.ui.test.manifest)
80 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/microwakeword/WakeWordDetector.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.microwakeword
2 |
3 | import android.util.Log
4 | import com.example.ava.utils.fillFrom
5 | import com.example.microfeatures.MicroFrontend
6 | import java.nio.ByteBuffer
7 |
8 | private const val SAMPLES_PER_SECOND = 16000
9 | private const val SAMPLES_PER_CHUNK = 160 // 10ms
10 | private const val BYTES_PER_SAMPLE = 2 // 16-bit
11 | private const val BYTES_PER_CHUNK = SAMPLES_PER_CHUNK * BYTES_PER_SAMPLE
12 |
13 | class WakeWordDetector(private val wakeWordProvider: WakeWordProvider) : AutoCloseable {
14 | private val frontend = MicroFrontend()
15 | private val buffer = ByteBuffer.allocateDirect(BYTES_PER_CHUNK)
16 | private val wakeWords = wakeWordProvider.getWakeWords()
17 | private var activeWakeWords = listOf()
18 |
19 | data class DetectionResult(
20 | val wakeWordId: String,
21 | val wakeWordPhrase: String
22 | )
23 |
24 | fun detect(audio: ByteBuffer): List {
25 | val detections = mutableListOf()
26 | buffer.fillFrom(audio)
27 | while (buffer.flip().remaining() == BYTES_PER_CHUNK) {
28 | val processOutput = frontend.processSamples(buffer)
29 | buffer.position(buffer.position() + processOutput.samplesRead * BYTES_PER_SAMPLE)
30 | buffer.compact()
31 | buffer.fillFrom(audio)
32 | if (processOutput.features.isEmpty())
33 | continue
34 | for (wakeWord in activeWakeWords) {
35 | val result = wakeWord.processAudioFeatures(processOutput.features)
36 | if (result && !detections.any { it.wakeWordId == wakeWord.id })
37 | detections.add(DetectionResult(wakeWord.id, wakeWord.wakeWord))
38 | }
39 | }
40 | buffer.compact()
41 | return detections
42 | }
43 |
44 | fun setActiveWakeWords(wakeWordIds: List) {
45 | for (wakeWord in activeWakeWords)
46 | wakeWord.close()
47 | activeWakeWords = buildList {
48 | for (wakeWordId in wakeWordIds) {
49 | val wakeWordWithId = wakeWords.firstOrNull { it.id == wakeWordId }
50 | if (wakeWordWithId == null) {
51 | Log.w(TAG, "Wake word with id $wakeWordId not found")
52 | continue
53 | }
54 | add(
55 | MicroWakeWord(
56 | wakeWordWithId.id,
57 | wakeWordWithId.wakeWord.wake_word,
58 | wakeWordProvider.loadWakeWordModel(wakeWordWithId.wakeWord.model),
59 | wakeWordWithId.wakeWord.micro.probability_cutoff,
60 | wakeWordWithId.wakeWord.micro.sliding_window_size
61 | )
62 | )
63 | }
64 | }
65 | }
66 |
67 | override fun close() {
68 | frontend.close()
69 | for (model in activeWakeWords)
70 | model.close()
71 | }
72 |
73 | companion object {
74 | private const val TAG = "WakeWordDetector"
75 | }
76 | }
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/README.md:
--------------------------------------------------------------------------------
1 | # Audio "frontend" library for feature generation
2 |
3 | A feature generation library (also called frontend) that receives raw audio
4 | input, and produces filter banks (a vector of values).
5 |
6 | The raw audio input is expected to be 16-bit PCM features, with a configurable
7 | sample rate. More specifically the audio signal goes through a pre-emphasis
8 | filter (optionally); then gets sliced into (potentially overlapping) frames and
9 | a window function is applied to each frame; afterwards, we do a Fourier
10 | transform on each frame (or more specifically a Short-Time Fourier Transform)
11 | and calculate the power spectrum; and subsequently compute the filter banks.
12 |
13 | By default the library is configured with a set of defaults to perform the
14 | different processing tasks. This takes place with the frontend_util.c function:
15 |
16 | ```c++
17 | void FrontendFillConfigWithDefaults(struct FrontendConfig* config)
18 | ```
19 |
20 | A single invocation looks like:
21 |
22 | ```c++
23 | struct FrontendConfig frontend_config;
24 | FrontendFillConfigWithDefaults(&frontend_config);
25 | int sample_rate = 16000;
26 | FrontendPopulateState(&frontend_config, &frontend_state, sample_rate);
27 | int16_t* audio_data = ; // PCM audio samples at 16KHz.
28 | size_t audio_size = ; // Number of audio samples.
29 | size_t num_samples_read; // How many samples were processed.
30 | struct FrontendOutput output =
31 | FrontendProcessSamples(
32 | &frontend_state, audio_data, audio_size, &num_samples_read);
33 | for (i = 0; i < output.size; ++i) {
34 | printf("%d ", output.values[i]); // Print the feature vector.
35 | }
36 | ```
37 |
38 | Something to note in the above example is that the frontend consumes as many
39 | samples needed from the audio data to produce a single feature vector (according
40 | to the frontend configuration). If not enough samples were available to generate
41 | a feature vector, the returned size will be 0 and the values pointer will be
42 | `NULL`.
43 |
44 | An example of how to use the frontend is provided in frontend_main.cc and its
45 | binary frontend_main. This example, expects a path to a file containing `int16`
46 | PCM features at a sample rate of 16KHz, and upon execution will printing out
47 | the coefficients according to the frontend default configuration.
48 |
49 | ## Extra features
50 | Extra features of this frontend library include a noise reduction module, as
51 | well as a gain control module.
52 |
53 | **Noise cancellation**. Removes stationary noise from each channel of the signal
54 | using a low pass filter.
55 |
56 | **Gain control**. A novel automatic gain control based dynamic compression to
57 | replace the widely used static (such as log or root) compression. Disabled
58 | by default.
59 |
60 | ## Memory map
61 | The binary frontend_memmap_main shows a sample usage of how to avoid all the
62 | initialization code in your application, by first running
63 | "frontend_generate_memmap" to create a header/source file that uses a baked in
64 | frontend state. This command could be automated as part of your build process,
65 | or you can just use the output directly.
66 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/frontend.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/frontend.h"
16 |
17 | #include "tensorflow/lite/experimental/microfrontend/lib/bits.h"
18 |
19 | struct FrontendOutput FrontendProcessSamples(struct FrontendState* state,
20 | const int16_t* samples,
21 | size_t num_samples,
22 | size_t* num_samples_read) {
23 | struct FrontendOutput output;
24 | output.values = NULL;
25 | output.size = 0;
26 |
27 | // Try to apply the window - if it fails, return and wait for more data.
28 | if (!WindowProcessSamples(&state->window, samples, num_samples,
29 | num_samples_read)) {
30 | return output;
31 | }
32 |
33 | // Apply the FFT to the window's output (and scale it so that the fixed point
34 | // FFT can have as much resolution as possible).
35 | int input_shift =
36 | 15 - MostSignificantBit32(state->window.max_abs_output_value);
37 | FftCompute(&state->fft, state->window.output, input_shift);
38 |
39 | // We can re-ruse the fft's output buffer to hold the energy.
40 | int32_t* energy = (int32_t*)state->fft.output;
41 |
42 | FilterbankConvertFftComplexToEnergy(&state->filterbank, state->fft.output,
43 | energy);
44 |
45 | FilterbankAccumulateChannels(&state->filterbank, energy);
46 | uint32_t* scaled_filterbank = FilterbankSqrt(&state->filterbank, input_shift);
47 |
48 | // Apply noise reduction.
49 | NoiseReductionApply(&state->noise_reduction, scaled_filterbank);
50 |
51 | if (state->pcan_gain_control.enable_pcan) {
52 | PcanGainControlApply(&state->pcan_gain_control, scaled_filterbank);
53 | }
54 |
55 | // Apply the log and scale.
56 | int correction_bits =
57 | MostSignificantBit32(state->fft.fft_size) - 1 - (kFilterbankBits / 2);
58 | uint16_t* logged_filterbank =
59 | LogScaleApply(&state->log_scale, scaled_filterbank,
60 | state->filterbank.num_channels, correction_bits);
61 |
62 | output.size = state->filterbank.num_channels;
63 | output.values = logged_filterbank;
64 | return output;
65 | }
66 |
67 | void FrontendReset(struct FrontendState* state) {
68 | WindowReset(&state->window);
69 | FftReset(&state->fft);
70 | FilterbankReset(&state->filterbank);
71 | NoiseReductionReset(&state->noise_reduction);
72 | }
73 |
--------------------------------------------------------------------------------
/microfeatures/src/main/cpp/tensorflow/lite/experimental/microfrontend/lib/frontend_io.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | ==============================================================================*/
15 | #include "tensorflow/lite/experimental/microfrontend/lib/frontend_io.h"
16 |
17 | #include
18 |
19 | #include "tensorflow/lite/experimental/microfrontend/lib/fft_io.h"
20 | #include "tensorflow/lite/experimental/microfrontend/lib/filterbank_io.h"
21 | #include "tensorflow/lite/experimental/microfrontend/lib/log_scale_io.h"
22 | #include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction_io.h"
23 | #include "tensorflow/lite/experimental/microfrontend/lib/window_io.h"
24 |
25 | int WriteFrontendStateMemmap(const char* header, const char* source,
26 | const struct FrontendState* state) {
27 | // Write a header that just has our init function.
28 | FILE* fp = fopen(header, "w");
29 | if (!fp) {
30 | fprintf(stderr, "Failed to open header '%s' for write\n", header);
31 | return 0;
32 | }
33 | fprintf(fp, "#ifndef FRONTEND_STATE_MEMMAP_H_\n");
34 | fprintf(fp, "#define FRONTEND_STATE_MEMMAP_H_\n");
35 | fprintf(fp, "\n");
36 | fprintf(fp, "#include \"frontend.h\"\n");
37 | fprintf(fp, "\n");
38 | fprintf(fp, "struct FrontendState* GetFrontendStateMemmap();\n");
39 | fprintf(fp, "\n");
40 | fprintf(fp, "#endif // FRONTEND_STATE_MEMMAP_H_\n");
41 | fclose(fp);
42 |
43 | // Write out the source file that actually has everything in it.
44 | fp = fopen(source, "w");
45 | if (!fp) {
46 | fprintf(stderr, "Failed to open source '%s' for write\n", source);
47 | return 0;
48 | }
49 | fprintf(fp, "#include \"%s\"\n", header);
50 | fprintf(fp, "\n");
51 | WindowWriteMemmapPreamble(fp, &state->window);
52 | FftWriteMemmapPreamble(fp, &state->fft);
53 | FilterbankWriteMemmapPreamble(fp, &state->filterbank);
54 | NoiseReductionWriteMemmapPreamble(fp, &state->noise_reduction);
55 | fprintf(fp, "static struct FrontendState state;\n");
56 | fprintf(fp, "struct FrontendState* GetFrontendStateMemmap() {\n");
57 | WindowWriteMemmap(fp, &state->window, " (&state.window)");
58 | FftWriteMemmap(fp, &state->fft, " (&state.fft)");
59 | FilterbankWriteMemmap(fp, &state->filterbank, " (&state.filterbank)");
60 | NoiseReductionWriteMemmap(fp, &state->noise_reduction,
61 | " (&state.noise_reduction)");
62 | LogScaleWriteMemmap(fp, &state->log_scale, " (&state.log_scale)");
63 | fprintf(fp, " FftInit(&state.fft);\n");
64 | fprintf(fp, " FrontendReset(&state);\n");
65 | fprintf(fp, " return &state;\n");
66 | fprintf(fp, "}\n");
67 | fclose(fp);
68 | return 1;
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/ava/esphome/entities/MediaPlayerEntity.kt:
--------------------------------------------------------------------------------
1 | package com.example.ava.esphome.entities
2 |
3 | import androidx.annotation.OptIn
4 | import androidx.media3.common.util.UnstableApi
5 | import com.example.ava.esphome.voicesatellite.VoiceSatellitePlayer
6 | import com.example.ava.players.AudioPlayerState
7 | import com.example.esphomeproto.api.ListEntitiesRequest
8 | import com.example.esphomeproto.api.MediaPlayerCommand
9 | import com.example.esphomeproto.api.MediaPlayerCommandRequest
10 | import com.example.esphomeproto.api.MediaPlayerState
11 | import com.example.esphomeproto.api.listEntitiesMediaPlayerResponse
12 | import com.example.esphomeproto.api.mediaPlayerStateResponse
13 | import com.google.protobuf.MessageLite
14 | import kotlinx.coroutines.flow.combine
15 | import kotlinx.coroutines.flow.flow
16 |
17 | @OptIn(UnstableApi::class)
18 | class MediaPlayerEntity(
19 | val key: Int,
20 | val name: String,
21 | val objectId: String,
22 | val player: VoiceSatellitePlayer
23 | ) : Entity {
24 |
25 | override fun handleMessage(message: MessageLite) = flow {
26 | when (message) {
27 | is ListEntitiesRequest -> emit(listEntitiesMediaPlayerResponse {
28 | key = this@MediaPlayerEntity.key
29 | name = this@MediaPlayerEntity.name
30 | objectId = this@MediaPlayerEntity.objectId
31 | supportsPause = true
32 | })
33 |
34 | is MediaPlayerCommandRequest -> {
35 | if (message.key == key) {
36 | if (message.hasMediaUrl) {
37 | player.mediaPlayer.play(message.mediaUrl)
38 | } else if (message.hasCommand) {
39 | when (message.command) {
40 | MediaPlayerCommand.MEDIA_PLAYER_COMMAND_PAUSE -> player.mediaPlayer.pause()
41 | MediaPlayerCommand.MEDIA_PLAYER_COMMAND_PLAY -> player.mediaPlayer.unpause()
42 | MediaPlayerCommand.MEDIA_PLAYER_COMMAND_STOP -> player.mediaPlayer.stop()
43 | MediaPlayerCommand.MEDIA_PLAYER_COMMAND_MUTE -> player.setMuted(true)
44 | MediaPlayerCommand.MEDIA_PLAYER_COMMAND_UNMUTE -> player.setMuted(false)
45 | else -> {}
46 | }
47 | } else if (message.hasVolume) {
48 | player.setVolume(message.volume)
49 | }
50 | }
51 | }
52 | }
53 | }
54 |
55 | override fun subscribe() = combine(
56 | player.mediaPlayer.state,
57 | player.volume,
58 | player.muted,
59 | ) { state, volume, muted ->
60 | mediaPlayerStateResponse {
61 | key = this@MediaPlayerEntity.key
62 | this.state = getState(state)
63 | this.volume = volume
64 | this.muted = muted
65 | }
66 | }
67 |
68 | private fun getState(state: AudioPlayerState) = when (state) {
69 | AudioPlayerState.PLAYING -> MediaPlayerState.MEDIA_PLAYER_STATE_PLAYING
70 | AudioPlayerState.PAUSED -> MediaPlayerState.MEDIA_PLAYER_STATE_PAUSED
71 | AudioPlayerState.IDLE -> MediaPlayerState.MEDIA_PLAYER_STATE_IDLE
72 | }
73 | }
--------------------------------------------------------------------------------