From 22aa62d1207256b7741efc5669827d5cc16369f6 Mon Sep 17 00:00:00 2001 From: nicolas-f <1382241+nicolas-f@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:14:38 +0100 Subject: [PATCH] optimisation, less pixel converted into imagebitmap by using precomputed strips of bitmaps --- .github/workflows/static.yml | 10 ++- .../shared/child/MeasurementScreen.kt | 66 ++++++++++++++----- .../shared/ui/SpectrogramBitmap.kt | 23 +++---- 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 61708b4..93f3d17 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -40,7 +40,7 @@ jobs: distribution: 'zulu' java-version: '17' - uses: gradle/wrapper-validation-action@v1 - - uses: gradle/gradle-build-action@v2 + - uses: gradle/gradle-build-action@v3 with: cache-read-only: ${{ env.MAIN_BRANCH != 'true' }} - name: Build @@ -58,3 +58,11 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 + - name: Clean + run: rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + - name: Cache + uses: actions/cache@v4 + with: + path: | + webApp/build + key: ${{ runner.os }}-noisecapturejs diff --git a/shared/src/commonMain/kotlin/org/noise_planet/noisecapture/shared/child/MeasurementScreen.kt b/shared/src/commonMain/kotlin/org/noise_planet/noisecapture/shared/child/MeasurementScreen.kt index f1e39f2..49a015e 100644 --- a/shared/src/commonMain/kotlin/org/noise_planet/noisecapture/shared/child/MeasurementScreen.kt +++ b/shared/src/commonMain/kotlin/org/noise_planet/noisecapture/shared/child/MeasurementScreen.kt @@ -13,29 +13,28 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.unit.IntSize import com.bumble.appyx.components.backstack.BackStack import com.bumble.appyx.navigation.modality.BuildContext import com.bumble.appyx.navigation.node.Node -import kotlinx.coroutines.Runnable -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.consumeEach -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import org.koin.core.logger.Logger -import org.noise_planet.noisecapture.AudioSamples import org.noise_planet.noisecapture.AudioSource import org.noise_planet.noisecapture.shared.MeasurementService -import org.noise_planet.noisecapture.shared.MeasurementServiceData import org.noise_planet.noisecapture.shared.ScreenData +import org.noise_planet.noisecapture.shared.signal.SpectrumData import org.noise_planet.noisecapture.shared.ui.SpectrogramBitmap import org.noise_planet.noisecapture.toImageBitmap +import kotlin.math.min import kotlin.math.round const val FFT_SIZE = 4096 const val FFT_HOP = 2048 const val WINDOW_TIME = 0.125 +const val SPECTROGRAM_STRIP_WIDTH = 32 class MeasurementScreen(buildContext: BuildContext, val backStack: BackStack, private val audioSource: AudioSource, private val logger: Logger) : Node(buildContext) { @@ -46,8 +45,10 @@ class MeasurementScreen(buildContext: BuildContext, val backStack: BackStack())} lifecycleScope.launch { println("Launch lifecycle") @@ -59,11 +60,37 @@ class MeasurementScreen(buildContext: BuildContext, val backStack: BackStack noiseLevel = measurementServiceData.laeq if(spectrogramBitmapData.size.width > 1) { - spectrogramBitmapData.pushSpectrumToSpectrogramData( - measurementServiceData.spectrumDataList, - SpectrogramBitmap.Companion.SCALE_MODE.SCALE_LOG, - mindB, rangedB, measurementService!!.sampleRate.toDouble()) - spectrumBitmapState = spectrogramBitmapData.byteArray.copyOf() + var indexToProcess = 0 + var bitmapChanged = false + while(indexToProcess < measurementServiceData.spectrumDataList.size) { + val subListSizeToCompleteStrip = min( + spectrogramBitmapData.size.width - + spectrogramBitmapData.offset, + measurementServiceData.spectrumDataList.size - indexToProcess + ) + if(subListSizeToCompleteStrip == 0) { + // spectrogram band complete, store bitmap + completeImageBitmap.add(spectrogramBitmapData.byteArray.toImageBitmap()) + if((completeImageBitmap.size - 1) * SPECTROGRAM_STRIP_WIDTH > spectrogramCanvasSize.width) { + // remove offscreen bitmaps + completeImageBitmap.removeAt(0) + } + spectrogramBitmapData = SpectrogramBitmap.createSpectrogram(spectrogramBitmapData.size) + bitmapChanged = false + continue + } + spectrogramBitmapData.pushSpectrumToSpectrogramData( + measurementServiceData.spectrumDataList.subList(indexToProcess, + indexToProcess + subListSizeToCompleteStrip), + SpectrogramBitmap.Companion.SCALE_MODE.SCALE_LOG, + mindB, rangedB, measurementService!!.sampleRate.toDouble() + ) + bitmapChanged = true + indexToProcess += subListSizeToCompleteStrip + } + if(bitmapChanged) { + spectrumBitmapState = spectrogramBitmapData.byteArray.copyOf() + } } } } @@ -79,13 +106,22 @@ class MeasurementScreen(buildContext: BuildContext, val backStack: BackStack 1) { - drawImage(spectrumBitmapState.toImageBitmap()) + if(spectrumBitmapState.size == spectrogramBitmapData.byteArray.size) { + drawImage(spectrumBitmapState.toImageBitmap(), + topLeft = Offset(size.width - spectrogramBitmapData.offset, 0F)) + } + completeImageBitmap.reversed().forEachIndexed { index, imageBitmap -> + val bitmapX = size.width - ((index + 1) * SPECTROGRAM_STRIP_WIDTH + + spectrogramBitmapData.offset).toFloat() + drawImage(imageBitmap, + topLeft = Offset(bitmapX, 0F)) } } } diff --git a/shared/src/commonMain/kotlin/org/noise_planet/noisecapture/shared/ui/SpectrogramBitmap.kt b/shared/src/commonMain/kotlin/org/noise_planet/noisecapture/shared/ui/SpectrogramBitmap.kt index 1283f59..baefddf 100644 --- a/shared/src/commonMain/kotlin/org/noise_planet/noisecapture/shared/ui/SpectrogramBitmap.kt +++ b/shared/src/commonMain/kotlin/org/noise_planet/noisecapture/shared/ui/SpectrogramBitmap.kt @@ -122,7 +122,11 @@ class SpectrogramBitmap { } - data class SpectrogramDataModel(val size: IntSize, val byteArray: ByteArray) { + /** + * @constructor + * @si + */ + data class SpectrogramDataModel(val size: IntSize, val byteArray: ByteArray, var offset : Int = 0) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false @@ -143,12 +147,7 @@ class SpectrogramBitmap { scaleMode: SCALE_MODE, mindB : Double, rangedB : Double, sampleRate: Double) { - // move pixels to the bottom of the array - val destinationOffset = bmpHeader.size + size.width * Int.SIZE_BYTES * fftResults.size - val startIndex = bmpHeader.size - val stopIndex = (size.width * (size.height - fftResults.size)) * Int.SIZE_BYTES - byteArray.copyInto(byteArray, destinationOffset, startIndex, stopIndex) - // generate column of pixels + // generate columns of pixels // merge power of each frequencies following the destination bitmap resolution val hertzBySpectrumCell = sampleRate / FFT_SIZE.toDouble() val frequencyLegendPosition = when (scaleMode) { @@ -157,8 +156,8 @@ class SpectrogramBitmap { } fftResults.forEachIndexed { index, fftResult -> var lastProcessFrequencyIndex = 0 - val freqByPixel = fftResult.spectrum.size / size.width.toDouble() - for (pixel in 0..