Skip to content
Closed
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
12 changes: 6 additions & 6 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ plugins {
}

kotlin {
compilerOptions {
freeCompilerArgs.add("-Xexpect-actual-classes")
}
androidTarget {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_22)
jvmTarget.set(JvmTarget.JVM_17)
}
}

Expand Down Expand Up @@ -101,10 +98,13 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_22
targetCompatibility = JavaVersion.VERSION_22
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
}
buildFeatures {
compose = true
}
}

dependencies {
Expand Down
17 changes: 15 additions & 2 deletions composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application
android:name=".BaseApplication"
Expand All @@ -14,16 +17,26 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.App.Starting">

<activity
android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode"
android:screenOrientation="unspecified"
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service
android:name=".app.player.RadioPlaybackService"
android:exported="false"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</service>
</application>

</manifest>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.onedroid.radiowave

import android.os.Bundle
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
Expand All @@ -12,10 +13,15 @@ import org.onedroid.radiowave.app.App
import org.onedroid.radiowave.app.utils.setActivityProvider

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setActivityProvider { this }
installSplashScreen()

// Keep screen on while app is active (driver mode)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

setContent {
enableEdgeToEdge(
SystemBarStyle.dark(MaterialTheme.colorScheme.onSurface.toArgb()),
Expand All @@ -24,4 +30,14 @@ class MainActivity : ComponentActivity() {
App()
}
}
}

/**
* Set screen brightness. Called from the Mixer panel.
* @param brightness 0.0f to 1.0f, or -1.0f for system default.
*/
fun setScreenBrightness(brightness: Float) {
val params = window.attributes
params.screenBrightness = brightness.coerceIn(0.01f, 1.0f)
window.attributes = params
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.koin.android.ext.koin.androidApplication
import org.koin.core.module.Module
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
import org.onedroid.radiowave.app.player.AudioEffectsManager
import org.onedroid.radiowave.app.player.PlayerController
import org.onedroid.radiowave.data.database.DatabaseFactory
import org.onedroid.radiowave.data.datastore.dataStorePreferences
Expand All @@ -16,4 +17,5 @@ actual val platformModule: Module
single<HttpClientEngine> { OkHttp.create() }
singleOf(::PlayerController)
singleOf(::dataStorePreferences)
}
singleOf(::AudioEffectsManager)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package org.onedroid.radiowave.app.player

import android.media.audiofx.Equalizer
import android.media.audiofx.LoudnessEnhancer

/**
* Manages audio effects (EQ presets) for driving scenarios.
* Presets: Flat, Voice Clarity, Warm, Reduce Hiss.
*/
class AudioEffectsManager {

private var equalizer: Equalizer? = null
private var loudnessEnhancer: LoudnessEnhancer? = null
private var currentPreset: EqPreset = EqPreset.FLAT

enum class EqPreset(val label: String) {
FLAT("Flat"),
VOICE_CLARITY("Voice Clarity"),
WARM("Warm"),
REDUCE_HISS("Reduce Hiss")
}

fun attach(audioSessionId: Int) {
release()
if (audioSessionId == 0) return

try {
equalizer = Equalizer(0, audioSessionId).apply {
enabled = true
}
loudnessEnhancer = LoudnessEnhancer(audioSessionId).apply {
enabled = false
}
applyPreset(currentPreset)
} catch (_: Exception) {
// Some devices don't support audio effects
}
}

fun applyPreset(preset: EqPreset) {
currentPreset = preset
val eq = equalizer ?: return
val bands = eq.numberOfBands.toInt()

loudnessEnhancer?.enabled = false

when (preset) {
EqPreset.FLAT -> {
for (i in 0 until bands) {
eq.setBandLevel(i.toShort(), 0)
}
}
EqPreset.VOICE_CLARITY -> {
// Boost mids (speech frequencies ~300Hz-3kHz), cut lows
val levels = generateVoiceLevels(bands, eq)
for (i in 0 until bands) {
eq.setBandLevel(i.toShort(), levels[i])
}
loudnessEnhancer?.apply {
setTargetGain(400) // Slight loudness boost
enabled = true
}
}
EqPreset.WARM -> {
// Boost lows and low-mids, slight high cut
val levels = generateWarmLevels(bands, eq)
for (i in 0 until bands) {
eq.setBandLevel(i.toShort(), levels[i])
}
}
EqPreset.REDUCE_HISS -> {
// Cut highs aggressively to reduce hiss/noise
val levels = generateReduceHissLevels(bands, eq)
for (i in 0 until bands) {
eq.setBandLevel(i.toShort(), levels[i])
}
}
}
}

fun getCurrentPreset(): EqPreset = currentPreset

fun release() {
try {
equalizer?.release()
loudnessEnhancer?.release()
} catch (_: Exception) {}
equalizer = null
loudnessEnhancer = null
}

private fun generateVoiceLevels(bands: Int, eq: Equalizer): ShortArray {
val range = eq.bandLevelRange
val minLevel = range[0]
val maxLevel = range[1]
val mid = (maxLevel * 0.6).toInt().toShort()
val low = (minLevel * 0.3).toInt().toShort()

return ShortArray(bands) { i ->
val ratio = i.toFloat() / (bands - 1)
when {
ratio < 0.2f -> low // Cut sub-bass
ratio < 0.4f -> 0 // Slight low cut
ratio < 0.7f -> mid // Boost mids
ratio < 0.85f -> (mid * 0.5f).toInt().toShort() // Upper mids
else -> 0 // Flat highs
}
}
}

private fun generateWarmLevels(bands: Int, eq: Equalizer): ShortArray {
val range = eq.bandLevelRange
val maxLevel = range[1]
val boost = (maxLevel * 0.5).toInt().toShort()
val cut = (range[0] * 0.2).toInt().toShort()

return ShortArray(bands) { i ->
val ratio = i.toFloat() / (bands - 1)
when {
ratio < 0.3f -> boost // Boost bass
ratio < 0.5f -> (boost * 0.6f).toInt().toShort()
ratio < 0.7f -> 0
else -> cut // Slight high cut
}
}
}

private fun generateReduceHissLevels(bands: Int, eq: Equalizer): ShortArray {
val range = eq.bandLevelRange
val minLevel = range[0]
val cut = (minLevel * 0.6).toInt().toShort()
val deepCut = (minLevel * 0.9).toInt().toShort()

return ShortArray(bands) { i ->
val ratio = i.toFloat() / (bands - 1)
when {
ratio < 0.5f -> 0 // Keep lows/mids flat
ratio < 0.7f -> cut // Cut upper mids slightly
else -> deepCut // Aggressively cut highs
}
}
}
}
Loading