Skip to content

Commit

Permalink
optimisation, less pixel converted into imagebitmap by using precompu…
Browse files Browse the repository at this point in the history
…ted strips of bitmaps
  • Loading branch information
nicolas-f committed Mar 7, 2024
1 parent 47f6736 commit 22aa62d
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 27 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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<ScreenData>,
private val audioSource: AudioSource, private val logger: Logger) : Node(buildContext) {
Expand All @@ -46,8 +45,10 @@ class MeasurementScreen(buildContext: BuildContext, val backStack: BackStack<Scr

@Composable
override fun View(modifier: Modifier) {
var spectrogramCanvasSize = Size.Zero
var noiseLevel by remember { mutableStateOf(0.0) }
var spectrumBitmapState by remember { mutableStateOf(ByteArray(1)) }
val completeImageBitmap by remember { mutableStateOf(ArrayList<ImageBitmap>())}

lifecycleScope.launch {
println("Launch lifecycle")
Expand All @@ -59,11 +60,37 @@ class MeasurementScreen(buildContext: BuildContext, val backStack: BackStack<Scr
measurementServiceData->
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()
}
}
}
}
Expand All @@ -79,13 +106,22 @@ class MeasurementScreen(buildContext: BuildContext, val backStack: BackStack<Scr
Column(Modifier.fillMaxWidth()) {
Text("${round(noiseLevel * 100)/100} dB(A)")
Canvas(modifier = Modifier.fillMaxSize()) {
val canvasSize = IntSize(size.width.toInt(), size.height.toInt())
val canvasSize = IntSize(SPECTROGRAM_STRIP_WIDTH, size.height.toInt())
spectrogramCanvasSize = size
if(spectrogramBitmapData.size != canvasSize) {
// reset buffer on resize or first draw
spectrogramBitmapData = SpectrogramBitmap.createSpectrogram(canvasSize)
completeImageBitmap.clear()
} else {
if(spectrumBitmapState.size > 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))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -157,16 +156,16 @@ class SpectrogramBitmap {
}
fftResults.forEachIndexed { index, fftResult ->
var lastProcessFrequencyIndex = 0
val freqByPixel = fftResult.spectrum.size / size.width.toDouble()
for (pixel in 0..<size.width) {
val freqByPixel = fftResult.spectrum.size / size.height.toDouble()
for (pixel in 0..<size.height) {
var freqStart: Int
var freqEnd: Int
if (scaleMode == SCALE_MODE.SCALE_LOG) {
freqStart = lastProcessFrequencyIndex
val fMax = sampleRate / 2
val fMin = frequencyLegendPosition[0]
val r = fMax / fMin.toDouble()
val f = fMin * 10.0.pow(pixel * log10(r) / size.width)
val f = fMin * 10.0.pow(pixel * log10(r) / size.height)
val nextFrequencyIndex =
min(fftResult.spectrum.size, (f / hertzBySpectrumCell).toInt())
freqEnd =
Expand All @@ -191,10 +190,12 @@ class SpectrogramBitmap {
)
)
val pixelColor = colorRamp[colorIndex].toArgb()
val columnOffset = offset % size.width
val pixelIndex = bmpHeader.size + size.width * Int.SIZE_BYTES *
(fftResults.size - 1 - index) + pixel * Int.SIZE_BYTES
pixel + columnOffset * Int.SIZE_BYTES
pixelColor.toLittleEndianBytes().copyInto(byteArray, pixelIndex)
}
offset += 1
}
}
}
Expand Down

0 comments on commit 22aa62d

Please sign in to comment.