Skip to content
This repository was archived by the owner on Feb 21, 2026. It is now read-only.

Commit 7ff5adf

Browse files
Add audio level monitor
1 parent 77078f7 commit 7ff5adf

File tree

11 files changed

+39
-13
lines changed

11 files changed

+39
-13
lines changed

android-application/src/main/java/com/inkapplications/ack/android/capture/CaptureEvents.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ import com.inkapplications.ack.structures.*
1414
import com.inkapplications.android.extensions.control.ControlState
1515
import com.inkapplications.coroutines.combinePair
1616
import com.inkapplications.coroutines.combineTriple
17+
import inkapplications.spondee.scalar.WholePercentage
18+
import inkapplications.spondee.structure.value
1719
import kimchi.logger.KimchiLogger
1820
import kotlinx.coroutines.*
1921
import kotlinx.coroutines.flow.*
2022
import javax.inject.Inject
2123
import javax.inject.Singleton
2224
import kotlin.coroutines.coroutineContext
25+
import kotlin.math.roundToInt
2326
import kotlin.time.ExperimentalTime
2427

2528
@Singleton
@@ -92,6 +95,9 @@ class CaptureEvents @Inject constructor(
9295
.combine(internetTransmitControlState) { viewModel, transmit ->
9396
viewModel.copy(internetTransmitState = transmit)
9497
}
98+
.combine(drivers.afskDriver.volume) { viewModel, volume ->
99+
viewModel.copy(audioLevel = "${volume?.value(WholePercentage)?.roundToInt()}%")
100+
}
95101

96102
suspend fun connectAudio() {
97103
coroutineScope {

android-application/src/main/java/com/inkapplications/ack/android/capture/CaptureScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ private fun CaptureSettingsSheet(
209209
CaptureSettingRow(
210210
icon = Icons.Filled.Mic,
211211
iconColor = AckTheme.colors.brand,
212-
name = stringResource(R.string.capture_controls_audio_capture_enabled_name),
212+
name = stringResource(R.string.capture_controls_audio_capture_enabled_name, captureScreenState.audioLevel),
213213
onClick = captureController::onAudioCaptureDisableClick
214214
)
215215
}

android-application/src/main/java/com/inkapplications/ack/android/capture/CaptureScreenViewModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.inkapplications.android.extensions.control.ControlState
44

55
data class CaptureScreenViewModel(
66
val audioCaptureState: ControlState = ControlState.Hidden,
7+
val audioLevel: String = "",
78
val internetCaptureState: ControlState = ControlState.Hidden,
89
val internetTransmitState: ControlState = ControlState.Hidden,
910
val audioTransmitState: ControlState = ControlState.Hidden,

android-application/src/main/res/values/capture.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<string name="capture_controls_expand_action">Controls</string>
88
<string name="capture_controls_collapse_action">Close</string>
99
<string name="capture_controls_audio_capture_disabled_name">Audio Capture Disabled</string>
10-
<string name="capture_controls_audio_capture_enabled_name">Audio Capture Enabled</string>
10+
<string name="capture_controls_audio_capture_enabled_name">Audio Capture Enabled (level: %s)</string>
1111
<string name="capture_controls_audio_transmit_disabled_name">Audio Transmit Disabled</string>
1212
<string name="capture_controls_audio_transmit_enabled_name">Audio Transmit Enabled</string>
1313
<string name="capture_controls_internet_capture_disabled_name">Internet Capture Disabled</string>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.inkapplications.ack.data" >
3+
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
34
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
45
</manifest>

aprs-android/src/main/java/com/inkapplications/ack/data/AudioDataCapture.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ internal class AudioDataCapture(
2424
) {
2525
private val recorder: AudioRecord by lazy {
2626
AudioRecord(
27-
MediaRecorder.AudioSource.DEFAULT,
27+
MediaRecorder.AudioSource.MIC,
2828
SAMPLE_RATE,
2929
AudioFormat.CHANNEL_IN_MONO,
3030
AudioFormat.ENCODING_PCM_16BIT,

aprs-android/src/main/java/com/inkapplications/ack/data/AudioDataProcessor.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.inkapplications.ack.data
22

3+
import inkapplications.spondee.scalar.DecimalPercentage
4+
import inkapplications.spondee.scalar.Percentage
35
import kotlinx.coroutines.channels.consumeEach
4-
import kotlinx.coroutines.flow.callbackFlow
6+
import kotlinx.coroutines.flow.*
7+
import kotlin.math.abs
8+
import kotlin.math.absoluteValue
59

610
private const val overlap = 18
711

@@ -24,11 +28,18 @@ internal class AudioDataProcessor(
2428

2529
invokeOnClose {
2630
audioIn.cancel()
31+
peak.value = null
2732
}
2833
audioIn.audioData.consumeEach { decode(it) }
2934
}
3035

31-
fun decode(audioData: ShortArray) {
36+
private val peak = MutableStateFlow<Short?>(null)
37+
val volume: Flow<Percentage?> = peak.map {
38+
it?.toDouble()?.absoluteValue?.div(Short.MAX_VALUE)?.let(DecimalPercentage::of)
39+
}
40+
41+
private fun decode(audioData: ShortArray) {
42+
peak.value = audioData.maxOf { abs(it.toInt()) }.toShort()
3243
for (i in audioData.indices) {
3344
fbuf[fbuf_cnt++] = audioData[i] * (1.0f / 32768.0f)
3445
}

aprs-android/src/main/java/com/inkapplications/ack/data/drivers/AfskDriver.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,20 @@ import com.inkapplications.coroutines.combinePair
1111
import kimchi.logger.KimchiLogger
1212
import kotlinx.coroutines.channels.BufferOverflow
1313
import kotlinx.coroutines.coroutineScope
14-
import kotlinx.coroutines.flow.MutableSharedFlow
15-
import kotlinx.coroutines.flow.mapNotNull
16-
import kotlinx.coroutines.flow.onEach
14+
import kotlinx.coroutines.flow.*
1715
import kotlinx.coroutines.launch
1816

19-
internal class AfskDriver(
17+
class AfskDriver internal constructor(
2018
private val aprsCodec: AprsCodec,
2119
private val packetStorage: PacketStorage,
2220
private val audioProcessor: AudioDataProcessor,
2321
private val modulator: AndroidAfskModulator,
2422
private val settings: DriverSettingsProvider,
2523
private val logger: KimchiLogger,
26-
): PacketDriver {
24+
): PacketDriver, AudioConnectionMonitor {
2725
override val incoming = MutableSharedFlow<CapturedPacket>()
2826
override val receivePermissions: Set<String> = setOf(Manifest.permission.RECORD_AUDIO)
27+
override val volume = audioProcessor.volume
2928
private val transmitQueue = MutableSharedFlow<ByteArray>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
3029

3130
override suspend fun transmitPacket(packet: AprsPacket, encodingConfig: EncodingConfig) {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.inkapplications.ack.data.drivers
2+
3+
import inkapplications.spondee.scalar.Percentage
4+
import kotlinx.coroutines.flow.Flow
5+
6+
interface AudioConnectionMonitor {
7+
val volume: Flow<Percentage?>
8+
}

aprs-android/src/main/java/com/inkapplications/ack/data/drivers/InternetDriver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import kotlinx.coroutines.coroutineScope
1818
import kotlinx.coroutines.flow.*
1919
import kotlinx.coroutines.launch
2020

21-
internal class InternetDriver(
21+
class InternetDriver internal constructor(
2222
private val aprsCodec: AprsCodec,
2323
private val packetStorage: PacketStorage,
2424
private val client: AprsDataClient,

0 commit comments

Comments
 (0)