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

Feature/navigation architecture #35

Merged
merged 14 commits into from
Aug 19, 2024
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
2 changes: 2 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ kotlin {
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.viewmodel.compose)
implementation(libs.koin.core)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
}
Expand Down
1 change: 1 addition & 0 deletions composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:enableOnBackInvokedCallback="true"
android:theme="@android:style/Theme.Material.Light.NoActionBar">
<activity
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import org.koin.core.KoinApplication
import org.koin.core.context.startKoin
import org.koin.core.module.Module
import org.koin.dsl.module
import org.noiseplanet.noisecapture.measurements.MeasurementService
import org.noiseplanet.noisecapture.permission.DefaultPermissionService
import org.noiseplanet.noisecapture.permission.PermissionService
import org.noiseplanet.noisecapture.measurements.DefaultMeasurementService
import org.noiseplanet.noisecapture.measurements.MeasurementsService
import org.noiseplanet.noisecapture.permission.defaultPermissionModule
import org.noiseplanet.noisecapture.permission.platformPermissionModule
import org.noiseplanet.noisecapture.ui.features.home.homeModule
import org.noiseplanet.noisecapture.ui.features.measurement.measurementModule
import org.noiseplanet.noisecapture.ui.features.permission.requestPermissionModule

/**
* Create root Koin application and register modules shared between platforms
Expand All @@ -20,12 +22,20 @@ fun initKoin(
modules(
module {
includes(additionalModules)
},

single<PermissionService> { DefaultPermissionService() }
single<MeasurementService> { MeasurementService(audioSource = get()) }
module {
single<MeasurementsService> {
DefaultMeasurementService(audioSource = get(), logger = get())
}
},

defaultPermissionModule,
platformPermissionModule()
platformPermissionModule(),

homeModule,
requestPermissionModule,
measurementModule,
)
createEagerInstances()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package org.noiseplanet.noisecapture

import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
Expand All @@ -14,28 +11,26 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import org.koin.compose.koinInject
import org.noiseplanet.noisecapture.ui.AppBar
import org.noiseplanet.noisecapture.ui.NavigationRoute
import org.noiseplanet.noisecapture.ui.screens.HomeScreen
import org.noiseplanet.noisecapture.ui.screens.MeasurementScreen
import org.noiseplanet.noisecapture.ui.screens.PlatformInfoScreen
import org.noiseplanet.noisecapture.ui.screens.RequestPermissionScreen
import org.noiseplanet.noisecapture.ui.features.home.HomeScreen
import org.noiseplanet.noisecapture.ui.features.measurement.MeasurementScreen
import org.noiseplanet.noisecapture.ui.features.permission.RequestPermissionScreen
import org.noiseplanet.noisecapture.ui.navigation.Route
import org.noiseplanet.noisecapture.ui.navigation.Transitions


/**
* Root component of the app.
* Currently handles the navigation stack, and navigation bar management.
*/
@Composable
fun NoiseCaptureApp(
navController: NavHostController = rememberNavController(),
) {
fun NoiseCaptureApp() {
val navController: NavHostController = rememberNavController()
// Get current navigation back stack entry
val backStackEntry by navController.currentBackStackEntryAsState()
// Get the name of the current screen
val currentScreen = NavigationRoute.valueOf(
backStackEntry?.destination?.route ?: NavigationRoute.Home.name
val currentScreen = Route.valueOf(
backStackEntry?.destination?.route ?: Route.Home.name
)

Scaffold(
Expand All @@ -47,56 +42,31 @@ fun NoiseCaptureApp(
)
}
) { innerPadding ->
// TODO: Configure NavHost in a separate file
// TODO: Use ease out curve for slide transitions
// TODO: Handle swipe back gestures on iOS -> encapsulate UINavigationController?
// TODO: Handle predictive back gestures on Android
NavHost(
navController = navController,
startDestination = NavigationRoute.Home.name,
enterTransition = {
slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start, tween(300))
},
exitTransition = {
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start, tween(300))
},
popEnterTransition = {
slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End, tween(300))
},
popExitTransition = {
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End, tween(300))
},
startDestination = Route.Home.name,
enterTransition = Transitions.enterTransition,
exitTransition = Transitions.exitTransition,
popEnterTransition = Transitions.popEnterTransition,
popExitTransition = Transitions.popExitTransition,
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
) {
composable(route = NavigationRoute.Home.name) {
HomeScreen(
onClick = {
// TODO: Silently check for permissions and bypass this step if they are already all granted
navController.navigate(NavigationRoute.RequestPermission.name)
},
)
}
composable(route = NavigationRoute.PlatformInfo.name) {
PlatformInfoScreen(
modifier = Modifier.fillMaxHeight()
)
composable(route = Route.Home.name) {
// TODO: Silently check for permissions and bypass this step if they are already all granted
HomeScreen(navigationController = navController)
}
composable(route = NavigationRoute.RequestPermission.name) {
composable(route = Route.RequestPermission.name) {
RequestPermissionScreen(
onClickNextButton = {
navController.navigate(NavigationRoute.Measurement.name)
navController.navigate(Route.Measurement.name)
}
)
}
composable(route = NavigationRoute.Measurement.name) {
// TODO: Decide of a standard for screens architecture:
// - class or compose function as root?
// - Inject dependencies in constructor or via Koin factories?
// - What should be the package structure?
MeasurementScreen(measurementService = koinInject())
.Content()
composable(route = Route.Measurement.name) {
MeasurementScreen()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.noiseplanet.noisecapture.audio.signal

import kotlin.math.pow

data class FrequencyBand(
val minFrequency: Double,
val midFrequency: Double,
val maxFrequency: Double,
var spl: Double,
) {

enum class BaseMethod {
B10,
B2
}

companion object {

/**
* Create (third-)octave array from the specified parameters (without spl values)
*
* @param firstFrequencyBand First frequency band (Hz)
* @param lastFrequencyBand Last frequency band (Hz)
* @param base Octave base 2 or 10
* @param bandDivision Octave bands division (defaults to 3 for third octaves)
*/
fun emptyFrequencyBands(
firstFrequencyBand: Double,
lastFrequencyBand: Double,
base: BaseMethod = BaseMethod.B10,
bandDivision: Double = 3.0,
): Array<FrequencyBand> {
val g = when (base) {
BaseMethod.B10 -> 10.0.pow(3.0 / 10.0)
BaseMethod.B2 -> 2.0
}
val firstBandIndex = getBandIndexByFrequency(firstFrequencyBand, g, bandDivision)
val lastBandIndex = getBandIndexByFrequency(lastFrequencyBand, g, bandDivision)
return Array(lastBandIndex - firstBandIndex) { bandIndex ->
val (fMin, fMid, fMax) = getBands(bandIndex + firstBandIndex, g, bandDivision)
FrequencyBand(fMin, fMid, fMax, 0.0)
}
}

private fun getBands(
bandIndex: Int,
g: Double,
bandDivision: Double,
): Triple<Double, Double, Double> {
val fMid = g.pow(bandIndex / bandDivision) * 1000.0
val fMax = g.pow(1.0 / (2.0 * bandDivision)) * fMid
val fMin = g.pow(-1.0 / (2.0 * bandDivision)) * fMid
return Triple(fMin, fMid, fMax)
}

private fun getBandIndexByFrequency(
targetFrequency: Double,
g: Double,
bandDivision: Double,
): Int {
var frequencyBandIndex = 0
var (fMin, fMid, fMax) = getBands(frequencyBandIndex, g, bandDivision)
while (!(fMin < targetFrequency && targetFrequency < fMax)) {
if (targetFrequency < fMin) {
frequencyBandIndex -= 1
} else if (targetFrequency > fMax) {
frequencyBandIndex += 1
}
val bandInfo = getBands(frequencyBandIndex, g, bandDivision)
fMin = bandInfo.first
fMax = bandInfo.third
}
return frequencyBandIndex
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,23 @@ const val SLOW_DECAY_RATE = -4.3

/**
* IEC 61672-1 standard for displayed sound level decay
*
* TODO: Document parameters
*/
class LevelDisplayWeightedDecay(decibelDecayPerSecond: Double, newValueTimeInterval: Double) {
class LevelDisplayWeightedDecay(
decibelDecayPerSecond: Double,
newValueTimeInterval: Double,
) {

val timeWeight = 10.0.pow(decibelDecayPerSecond * newValueTimeInterval / 10.0)
var timeIntegration = 0.0
private val timeWeight = 10.0.pow(decibelDecayPerSecond * newValueTimeInterval / 10.0)
private var timeIntegration = 0.0

/**
* TODO: add documentation
*/
fun getWeightedValue(newValue: Double): Double {
timeIntegration =
timeIntegration * timeWeight + 10.0.pow(newValue / 10.0) * (1 - timeWeight)
timeIntegration = timeIntegration * timeWeight +
10.0.pow(newValue / 10.0) * (1 - timeWeight)
return 10 * log10(timeIntegration)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package org.noiseplanet.noisecapture.audio.signal

import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import org.noiseplanet.noisecapture.audio.signal.filter.BiquadFilter
import org.noiseplanet.noisecapture.audio.signal.filter.DigitalFilter
import kotlin.math.pow

/**
* Digital filtering of audio samples
*
* @author Nicolas Fortin, Université Gustave Eiffel
* @author Valentin Le Bescond, Université Gustave Eiffel
* @link https://github.com/SonoMKR/sonomkr-core/blob/master/src/spectrumchannel.cpp
Expand Down
Loading
Loading