Custom Fine Tuning Presets UI¶
This guide explains the concepts and MSDK APIs that you will need to use in order to create your own custom Fine Tuning preset selection user interface.
It doesn't prescribe the UI components to be used - you are free to choose what representation you think best suits your design, rather it will outline how to get the necessary state to display and how to affect changes in response to user input.
Introduction¶
"Fine Tuning" is an MSDK feature which provides the user with a selection of possible personalization presets, from which they can select their preferred sound personalization experience.
In the standard Mimi Profile UI, the Fine Tuning feature is incorporated into the "Sound personalization" profile card.
The available Fine Tuning preset options may include:
- Recommended
- Richer
- Softer
The available presets depend upon the user's hearing assessment, so not all presets options are available in all cases, as shown in these screenshots of Mimi Profile:
Note
The term "Fine Tuning" is generally used when referring to the user-facing aspects of the feature, however in places the MSDK APIs use the term "Up Down" synonymously.
Understanding UpDown Presets¶
The UpDownPreset
class contains the available fine-tuning personalization presets retrieved from the Mimi backend.
It is defined by io.mimi.sdk.core.model.personalization.updown.UpDownPreset
, and contains up to three different presets.
default
- the "Recommended" presetup
- the "Richer" presetdown
- the "Softer" preset
If a preset component value is null
, then that preset is not available for the current user.
The current state is available from the PersonalizationController.upDownPresets
MimiObservable
property.
Building a custom Fine Tuning UI¶
This section describes the individual steps required to create your own customized Fine Tuning UI experience.
It will cover the following topics:
- Activate the
ProcessingSession
withPersonalizationModeConfiguration.FineTuning
. - Display the available Fine Tuning preset options
- Display the current Fine Tuning preset selection
- Apply the preset payload to the
preset
processing parameter.
Activate the ProcessingSession
with FineTuning
Personalization¶
To support the Fine Tuning feature, you must must activate your app's ProcessingSession
using PersonalizationModeConfiguration.FineTuning
mode.
This is done by providing a PersonalizationModeConfiguration
instance to the MimiCore.processingController.activate
function.
val session = MimiCore.processingController.activate(
MimiProcessingConfiguration(
personalization = PersonalizationConfiguration(
mode = FineTuning(Fitting(TODO("Fitting is defined by your Processing system")))
)
)
)
Read more in the Preset Parameter Data Sources guide.
Display the available Fine Tuning Preset options¶
As the available Fine Tuning presets can vary, your custom UI will need to adapt accordingly to only offer the appropriate presets for selection.
The PersonalizationController
provides access to the current UpDownPreset
values and their availability -
// Observe the changes in upDownPresets to determine availability - if `null`, then that preset type is not available.
coroutineScope.launch {
MimiCore.personalizationController.upDownPresets.observe { upDownPresets: AsyncState<UpDownPreset?> ->
// Extract the preset availability for each of the fine tuning options
val isRecommendedAvailable = upDownPresets.value?.presets?.default != null
val isRicherAvailable = upDownPresets.value?.presets?.up != null
val isSofterAvailable = upDownPresets.value?.presets?.down != null
// TODO - Update your UI to reflect the preset availability
}
}
Note
The Mimi Profile screen does not show any Fine Tuning preset selection options if only the "Recommended" preset is available.
Display the current Fine Tuning preset selection¶
In addition to displaying only the available presets, your custom UI also needs to show the user which preset is currently in use.
This is done by comparing the currently applied preset
value to the available UpDownPreset
.
Note
Only a single preset can be selected at a time.
This requires observing both the preset
ProcessingParameter
and the upDownPreset
values, as a change in either will affect the interpretation of the current selected preset type.
// Define an enum for the possible selection options
enum class PresetType {
SOFTER,
RECOMMENDED,
RICHER
}
// Define a function which returns the selected `PresetType` given the current `UpDownPreset`and `preset` states.
fun getSelectedPresetType(
upDownPreset: UpDownPreset?,
presetParameterPayload: String?
): PresetType? {
if (upDownPreset == null || presetParameterPayload == null) return null
return with(upDownPreset.presets) {
if (default?.payload == presetParameterPayload) PresetType.RECOMMENDED
else if (up?.payload == presetParameterPayload) PresetType.RICHER
else if (down?.payload == presetParameterPayload) PresetType.SOFTER
else null
}
}
Then observe
changes to both the upDownPreset
MimiObservable
and the preset
ProcessingParameter
, invoking this function on each change and using the result to trigger an update to the UI state.
These two snippets show how this can be done for the preset
and UpDownPreset
changes respectively:
// Observe changes in the preset ProcessingParameter
coroutineScope.launch {
val activeSession: ProcessingSession =
requireNotNull(MimiCore.processingController.activeSession.state) { "No active ProcessingSession!" }
activeSession.preset.observe { preset ->
val currentUpDownPresets: UpDownPreset? =
MimiCore.personalizationController.upDownPresets.state.value
val selectedPresetType: PresetType? =
getSelectedPresetType(currentUpDownPresets, preset?.payload)
// TODO Update UI state with new selection: `selectedPresetType`
}
}
// Observe changes in the UpDownPresets
coroutineScope.launch {
MimiCore.personalizationController.upDownPresets.observe { upDownPresets: AsyncState<UpDownPreset?> ->
val activeSession: ProcessingSession =
requireNotNull(MimiCore.processingController.activeSession.state) { "No active ProcessingSession!" }
val currentPresetPayload = activeSession.preset.value?.payload
val selectedPresetType: PresetType? =
getSelectedPresetType(upDownPresets.value, currentPresetPayload)
// TODO Update UI state with new selection: `selectedPresetType`
}
}
Apply the Preset in response to user selection¶
When the user selects to change the current Fine Tuning preset, you will need to apply that PersonalizationPreset
to the preset
ProcessingParameter
in the active ProcessingSession
.
This example, shows how to apply the Richer (Up) preset value to the preset
ProcessingParameter
.
// Based on the user selection, take the preset from the corresponding `UpDownPresets` property.
// In this example, we demonstrate the user selecting the "Richer" preset.
// For "recommended" use - `upDownPresets.state.value?.presets?.default?.payload`
// For "softer" use - `upDownPresets.state.value?.presets?.down?.payload`
val richerPayload: String =
requireNotNull(MimiCore.personalizationController.upDownPresets.state.value?.presets?.up?.payload) { "User selected preset payload is not available!" }
// Convert to a `PersonalizationPreset` using the libcore helper function
// Note: As of 7.4.0, this is currently an experimental API, so it may change in future.
// You will need to opt-in to use it: @OptIn(MsdkInternalApi::class)
val richerPreset: Personalization.PersonalizationPreset =
richerPayload.toPersonalizationPreset()
// Acquire the active `ProcessSession`
val activeSession: ProcessingSession =
requireNotNull(MimiCore.processingController.activeSession.state) { "No active ProcessingSession!" }
// Apply the richer preset to the preset Parameter.
val result = coroutineScope.async {
activeSession.preset.apply(richerPreset)
}
// Depending on your UI requirements, you may wish to handle the `result`, although an apply failure
// is also reported through the `preset` `ProcessingParameter` state.