From 6c18f1f0af6ac8ac512e19c3a1b95a06a38ffb0a Mon Sep 17 00:00:00 2001 From: Andrey Danilov Date: Mon, 11 Mar 2024 01:11:48 +0300 Subject: [PATCH] WIP: Add playing demo at Android app --- .../java/com/danand/juicynoise/AudioOutput.kt | 58 ++++++++++++++ .../com/danand/juicynoise/MainActivity.kt | 79 +++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 juicy-noise-android/app/src/main/java/com/danand/juicynoise/AudioOutput.kt diff --git a/juicy-noise-android/app/src/main/java/com/danand/juicynoise/AudioOutput.kt b/juicy-noise-android/app/src/main/java/com/danand/juicynoise/AudioOutput.kt new file mode 100644 index 0000000..4c71715 --- /dev/null +++ b/juicy-noise-android/app/src/main/java/com/danand/juicynoise/AudioOutput.kt @@ -0,0 +1,58 @@ +package com.danand.juicynoise + +import android.media.AudioAttributes +import android.media.AudioFormat +import android.media.AudioTrack + +import kotlin.random.Random + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch + +class AudioOutput { + private lateinit var scope: CoroutineScope + + fun play( + samplingRate: Int, + bufferSize: Int, + ) { + scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + scope.launch(Dispatchers.IO) { + val audioTrack = AudioTrack.Builder() + .setAudioAttributes( + AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build() + ) + .setAudioFormat( + AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_FLOAT) + .setSampleRate(samplingRate) + .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) + .build() + ) + .setTransferMode(AudioTrack.MODE_STREAM) + .setBufferSizeInBytes(bufferSize) + .build() + + audioTrack.play() + + while (isActive) { + val floatArray = FloatArray(bufferSize) { Random.Default.nextFloat() } + audioTrack.write(floatArray, 0, bufferSize, AudioTrack.WRITE_NON_BLOCKING) + } + + audioTrack.stop() + } + } + + fun stop() { + scope.cancel() + } +} diff --git a/juicy-noise-android/app/src/main/java/com/danand/juicynoise/MainActivity.kt b/juicy-noise-android/app/src/main/java/com/danand/juicynoise/MainActivity.kt index a9a4ea7..0fb2bd4 100644 --- a/juicy-noise-android/app/src/main/java/com/danand/juicynoise/MainActivity.kt +++ b/juicy-noise-android/app/src/main/java/com/danand/juicynoise/MainActivity.kt @@ -87,6 +87,8 @@ class MainActivity : ComponentActivity(), SensorEventListener { private lateinit var sensorManager: SensorManager + private var audioOutput: AudioOutput = AudioOutput() + private var gyroscope: Sensor? = null private var accelerometer: Sensor? = null private var rotationVector: Sensor? = null @@ -157,6 +159,7 @@ class MainActivity : ComponentActivity(), SensorEventListener { sampleRateState, locationClient, connectivityManager, + audioOutput, ) } } @@ -290,6 +293,7 @@ fun ColumnMain( sampleRateState: MutableState, locationClient: FusedLocationProviderClient, connectivityManager: ConnectivityManager, + audioOutput: AudioOutput, ) { LaunchedEffect(portState) { val subnet = findSubnet(connectivityManager) @@ -377,6 +381,11 @@ fun ColumnMain( ButtonDisconnect( isRunningState, ) + + ButtonStopDemo( + isRunningState, + audioOutput, + ) } else { ButtonConnect( ipState.value, @@ -390,6 +399,15 @@ fun ColumnMain( ) { checkIsValidIp(ipState.value) } + + ButtonPlayDemo( + isRunningState, + sensorsState, + audioBufferSizeState, + sampleRateState, + locationClient, + audioOutput, + ) } Spacer(modifier = Modifier.height(24.dp)) @@ -537,6 +555,44 @@ fun ButtonConnect( } } +@Composable +fun ButtonPlayDemo( + isRunningState: MutableState, + sensorsState: MutableState, + audioBufferSizeState: MutableState, + sampleRateState: MutableState, + locationClient: FusedLocationProviderClient, + audioOutput: AudioOutput, +) { + Button( + onClick = { + isRunningState.value = true + + runReadingLocation( + locationClient, + isRunningState, + sensorsState, + ) + + audioOutput.play( + sampleRateState.value, + audioBufferSizeState.value.value, + ) + }, + colors = textButtonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ), + elevation = ButtonDefaults.buttonElevation( + defaultElevation = 0.dp, + pressedElevation = 16.dp, + disabledElevation = 0.dp, + ), + enabled = true + ) { + Text("Play demo") + } +} + @Composable fun ButtonDisconnect( isRunning: MutableState, @@ -558,6 +614,29 @@ fun ButtonDisconnect( } } +@Composable +fun ButtonStopDemo( + isRunning: MutableState, + audioOutput: AudioOutput, +) { + Button( + onClick = { + isRunning.value = false + audioOutput.stop() + }, + colors = textButtonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ), + elevation = ButtonDefaults.buttonElevation( + defaultElevation = 0.dp, + pressedElevation = 16.dp, + disabledElevation = 0.dp, + ), + ) { + Text("Stop demo") + } +} + fun createAddressState(): AddressState = AddressState( ip = mutableStateOf("192.168.0.128"), port = mutableStateOf(6660u),