Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add audio visualizer timestamps #176

Merged
merged 2 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 97 additions & 19 deletions app/src/main/java/com/bnyro/recorder/ui/components/AudioVisualizer.kt
Original file line number Diff line number Diff line change
@@ -1,46 +1,124 @@
package com.bnyro.recorder.ui.components

import android.text.format.DateUtils
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.bnyro.recorder.ui.models.RecorderModel
import com.bnyro.recorder.util.Preferences

@Composable
fun AudioVisualizer(
modifier: Modifier = Modifier
) {
val viewModel: RecorderModel = viewModel()
/*
* set max amplitude to tune the sensitivity
* Higher the value, lower the sensitivity of the visualizer
val showTimestamps = remember {
Preferences.prefs.getBoolean(
Preferences.showVisualizerTimestamps,
false
)
}

/**Set max amplitude to tune the sensitivity
* Higher the value, lower the sensitivity of the visualizer
*/
val maxAmplitude = 10000
val primary = MaterialTheme.colorScheme.primary
val primaryMuted = primary.copy(alpha = 0.3f)

/** Recorded amplitudes with 100ms intervals */
val amplitudes = viewModel.recordedAmplitudes
Canvas(modifier = modifier) {
val height = this.size.height / 2f
val width = this.size.width
val measurer = rememberTextMeasurer()
Column(modifier = modifier, verticalArrangement = Arrangement.Center) {
Canvas(modifier = Modifier.padding(vertical = 50.dp).fillMaxWidth().height(350.dp)) {
val height = this.size.height
val width = this.size.width
val y = height / 2

translate(width, height) {
amplitudes.forEachIndexed { index, amplitude ->
val amplitudePercentage = (amplitude.toFloat() / maxAmplitude).coerceAtMost(1f)
val boxHeight = height * amplitudePercentage
drawRoundRect(
color = if (amplitudePercentage > 0.05f) primary else primaryMuted,
topLeft = Offset(
30f * (index - viewModel.recordedAmplitudes.size),
-boxHeight / 2f
),
size = Size(15f, boxHeight),
cornerRadius = CornerRadius(3f, 3f)
)
translate(width, y) {
if (showTimestamps) {
drawLine(
color = primaryMuted,
start = Offset(-width, -y),
end = Offset(0f, -y),
strokeWidth = 5f
)
drawLine(
color = primaryMuted,
start = Offset(-width, y),
end = Offset(0f, y),
strokeWidth = 5f
)
}
amplitudes.forEachIndexed { index, amplitude ->
val amplitudePercentage = (amplitude.toFloat() / maxAmplitude).coerceAtMost(1f)
val boxHeight = height * amplitudePercentage
val reverseIndex = index - viewModel.recordedAmplitudes.size
val x = 30f * reverseIndex
drawRoundRect(
color = if (amplitudePercentage > 0.05f) primary else primaryMuted,
topLeft = Offset(
x,
-boxHeight / 2f
),
size = Size(15f, boxHeight),
cornerRadius = CornerRadius(3f, 3f)
)
if (showTimestamps) {
viewModel.recordedTime?.let {
val timeStamp = it + reverseIndex
if (timeStamp.mod(10) == 0) {
drawLine(
color = primaryMuted,
start = Offset(x, -y),
end = Offset(x, -y + 60f),
strokeWidth = 5f
)
drawLine(
color = primaryMuted,
start = Offset(x, y),
end = Offset(x, y - 60f),
strokeWidth = 5f
)
drawText(
measurer,
DateUtils.formatElapsedTime(timeStamp / 10),
topLeft = Offset(x - 54f, -y - 60f),
style = TextStyle(primaryMuted)
)
} else if (timeStamp.mod(5) == 0) {
drawLine(
color = primaryMuted,
start = Offset(x, -y),
end = Offset(x, -y + 30f),
strokeWidth = 5f
)
drawLine(
color = primaryMuted,
start = Offset(x, y),
end = Offset(x, y - 30f),
strokeWidth = 5f
)
}
}
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ fun RecorderController(
) {
recorderModel.recordedTime?.let {
Text(
text = DateUtils.formatElapsedTime(it),
text = DateUtils.formatElapsedTime(it / 10),
style = MaterialTheme.typography.displayLarge
)
Spacer(modifier = Modifier.height(20.dp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ class RecorderModel : ViewModel() {
if (recorderState != RecorderState.ACTIVE) return

recordedTime = recordedTime?.plus(1)
handler.postDelayed(this::updateTime, 1000)
handler.postDelayed(this::updateTime, 100)
}

private fun updateAmplitude() {
Expand All @@ -148,7 +148,7 @@ class RecorderModel : ViewModel() {

private fun startElapsedTimeCounter() {
recordedTime = 0L
handler.postDelayed(this::updateTime, 1000)
handler.postDelayed(this::updateTime, 100)
}

@SuppressLint("NewApi")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ fun SettingsScreen() {
)
}
Spacer(modifier = Modifier.height(10.dp))
CheckboxPref(
prefKey = Preferences.showVisualizerTimestamps,
title = stringResource(R.string.audio_visualizer_timestamps),
summary = stringResource(R.string.audio_visualizer_timestamps_description)
)
Spacer(modifier = Modifier.height(10.dp))
NamingPatternPref()
}
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/bnyro/recorder/util/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ object Preferences {
const val losslessRecorderKey = "losslessRecorder"
const val namingPatternKey = "namingPattern"
const val showOverlayAnnotationToolKey = "annotationTool"
const val showVisualizerTimestamps = "visualizerTimestamp"

fun init(context: Context) {
prefs = context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE)
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,6 @@
<string name="screen_recorder_annotation_desc">Show annotation tool during screen recording</string>
<string name="amoled_dark">Amoled Dark</string>
<string name="default_sort">Default</string>
<string name="audio_visualizer_timestamps">Audio visualizer timestamps</string>
<string name="audio_visualizer_timestamps_description">Show timestamps in the audio visualizer timeline.</string>
</resources>
Loading