Always treat waveform as List<Float> (#1663)

[MSC3246](https://github.com/matrix-org/matrix-spec-proposals/pull/3246) specifies the waveform as a list of ints because:

> Because floating point numbers are not allowed in Matrix events

Though DSP on audio data is almost always done using their floating point representation.
This PR brings the float<->int rescaling in the `matrix` module so that the application code can always work with float waveform samples.
This commit is contained in:
Marco Romano
2023-10-27 14:49:58 +02:00
committed by GitHub
parent e59a6b7ed6
commit 1014380e32
10 changed files with 28 additions and 41 deletions

View File

@@ -29,7 +29,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractor
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
import io.element.android.features.messages.impl.voicemessages.fromMSC3246range
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.featureflag.api.FeatureFlagService
@@ -118,7 +117,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
waveform = messageType.details?.waveform?.fromMSC3246range()?.toImmutableList() ?: persistentListOf(),
waveform = messageType.details?.waveform?.toImmutableList() ?: persistentListOf(),
)
else -> TimelineItemAudioContent(
body = messageType.body,

View File

@@ -1,28 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.impl.voicemessages
/**
* Resizes the given [0;1024] int list as per unstable MSC3246 spec
* to a [0;1] range float list to be used for waveform rendering.
*/
internal fun List<Int>.fromMSC3246range(): List<Float> = map { it / 1024f }
/**
* Resizes the given [0;1] float list to [0;1024] int list as per unstable MSC3246 spec.
*/
internal fun List<Float>.toMSC3246range(): List<Int> = map { (it * 1024).toInt() }

View File

@@ -28,7 +28,6 @@ import androidx.compose.runtime.setValue
import androidx.core.net.toUri
import androidx.lifecycle.Lifecycle
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
import io.element.android.features.messages.impl.voicemessages.toMSC3246range
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.SingleIn
@@ -219,7 +218,7 @@ class VoiceMessageComposerPresenter @Inject constructor(
val result = mediaSender.sendVoiceMessage(
uri = file.toUri(),
mimeType = mimeType,
waveForm = waveform.toMSC3246range(),
waveForm = waveform,
)
if (result.isFailure) {

View File

@@ -20,5 +20,5 @@ import java.time.Duration
data class AudioDetails(
val duration: Duration,
val waveform: List<Int>,
val waveform: List<Float>,
)

View File

@@ -195,7 +195,7 @@ interface MatrixRoom : Closeable {
suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Int>,
waveform: List<Float>,
progressCallback: ProgressCallback?
): Result<MediaUploadHandler>

View File

@@ -21,10 +21,26 @@ import org.matrix.rustcomponents.sdk.UnstableAudioDetailsContent as RustAudioDet
fun RustAudioDetails.map(): AudioDetails = AudioDetails(
duration = duration,
waveform = waveform.map { it.toInt() },
waveform = waveform.fromMSC3246range(),
)
fun AudioDetails.map(): RustAudioDetails = RustAudioDetails(
duration = duration,
waveform = waveform.map { it.toUShort() }
waveform = waveform.toMSC3246range()
)
/**
* Resizes the given [0;1024] int list as per unstable MSC3246 spec
* to a [0;1] float list to be used for waveform rendering.
*
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md
*/
internal fun List<UShort>.fromMSC3246range(): List<Float> = map { it.toInt() / 1024f }
/**
* Resizes the given [0;1] float list as per unstable MSC3246 spec
* to a [0;1024] int list to be used for waveform rendering.
*
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md
*/
internal fun List<Float>.toMSC3246range(): List<UShort> = map { (it * 1024).toInt().toUShort() }

View File

@@ -46,6 +46,7 @@ import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl
import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.media.toMSC3246range
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
import io.element.android.libraries.matrix.impl.poll.toInner
import io.element.android.libraries.matrix.impl.room.location.toInner
@@ -499,13 +500,13 @@ class RustMatrixRoom(
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Int>,
waveform: List<Float>,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> = sendAttachment(listOf(file)) {
innerRoom.sendVoiceMessage(
url = file.path,
audioInfo = audioInfo.map(),
waveform = waveform.map { it.toUShort() },
waveform = waveform.toMSC3246range(),
progressWatcher = progressCallback?.toProgressWatcher(),
)
}

View File

@@ -387,7 +387,7 @@ class FakeMatrixRoom(
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Int>,
waveform: List<Float>,
progressCallback: ProgressCallback?
): Result<MediaUploadHandler> = fakeSendMedia(progressCallback)

View File

@@ -55,7 +55,7 @@ class MediaSender @Inject constructor(
suspend fun sendVoiceMessage(
uri: Uri,
mimeType: String,
waveForm: List<Int>,
waveForm: List<Float>,
progressCallback: ProgressCallback? = null
): Result<Unit> {
return preProcessor

View File

@@ -29,6 +29,6 @@ sealed interface MediaUploadInfo {
data class Image(override val file: File, val imageInfo: ImageInfo, val thumbnailFile: File) : MediaUploadInfo
data class Video(override val file: File, val videoInfo: VideoInfo, val thumbnailFile: File) : MediaUploadInfo
data class Audio(override val file: File, val audioInfo: AudioInfo) : MediaUploadInfo
data class VoiceMessage(override val file: File, val audioInfo: AudioInfo, val waveform: List<Int>) : MediaUploadInfo
data class VoiceMessage(override val file: File, val audioInfo: AudioInfo, val waveform: List<Float>) : MediaUploadInfo
data class AnyFile(override val file: File, val fileInfo: FileInfo) : MediaUploadInfo
}