---
# System prepended metadata

title: Untitled

---

#BUILD 06/09

**FFTCircleLine.kt**

```java
/**
 *Created by Nhien Nguyen on 8/19/2022
 */

class FFTCircleLine(
    var context: Context,
    override var paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG),
    var startHz: Int = 20,
    var endHz: Int = 2000,
    var interpolator: Interpolation = Interpolation.SPLINE,
    var discMargin: Int = 0
) : Painter() {
    private var gravityPoints = Array(0) { GravityModel(0f) }
    private val barWidth =
        context.resources.getDimensionPixelSize(R.dimen.default_visualizer_bar_width).toFloat()
    private val numberOfBar: Int = 100
    private val debugPaint = Paint()
    private val radiusR: Float = .4f
    private val ampR: Float = 1f
    private val points = FloatArray(4 * numberOfBar)
    private val angleOffset = 2 * PI.toFloat() / numberOfBar
    private var shortest = 0F
    var discRadius = 0
        set(value) {
            field = value
            shortest = getShortest()
            adjustRadius(shortest)
        }
    var isFirstTimeRunning = true
    var visualizerMaxHeight = 0
    var curveUtil: PolynomialSplineFunction? = null
    lateinit var fft: FloatArray

    // Fields for normalization
    private var min = 0f
    private var max = 0f
    private val highFreqDelta = 20
    private var frequencyMap = HashMap<Int, Int>()
    private var random = Random()

    // Fields for draw
    private var start = Pair(0F, 0F)
    private var stop = Pair(0F, 0F)
    private var currentHeight: Int = 0
    private var currentWidth: Int = 0

    init {
        paint.apply {
            color = Color.WHITE
            style = Paint.Style.FILL
            strokeWidth = barWidth
            strokeCap = Paint.Cap.ROUND
        }
        debugPaint.apply {
            isAntiAlias = true
            color = Color.RED
            style = Paint.Style.FILL
            strokeWidth = barWidth
        }
    }

    override fun calc(helper: VisualizerHelper) {
        fft = helper.getFftMagnitudeRange(startHz, endHz)
        minMaxNormalize(fft, visualizerMaxHeight, 0)
        fft = getPowerFft(fft)
        fft = if (isFirstTimeRunning) {
            getCircleFft(fft, true)
        } else {
            getCircleFft(fft)
        }
        if (gravityPoints.size != fft.size) gravityPoints = Array(fft.size) { GravityModel(0f) }

        gravityPoints.forEachIndexed { index, bar -> bar.update(fft[index] * ampR) }

        curveUtil = interpolateFftCircle(gravityPoints, numberOfBar, interpolator)
    }

    /**
     * Apply min-max normalization then handle if its value is "high frequency" continously
     * @see <a href="https://towardsdatascience.com/everything-you-need-to-know-about-min-max-normalization-in-python-b79592732b79">Min-Max Normalization </a>
     * A data is defined to be "high frequency" if its value is in range [[visualizerMaxHeight - [highFreqDelta]] , [visualizerMaxHeight]]
     */
    private fun minMaxNormalize(fftData: FloatArray, maxLimit: Int, minLimit: Int = 0) {
        min = Float.MAX_VALUE
        max = Float.MIN_VALUE
        for (value in fftData) {
            if (value > max) max = value
            if (value < min) min = value
        }
        if (max > visualizerMaxHeight) {
            for (idx in fftData.indices) {
                fftData[idx] = ((fftData[idx] - min) / (max - min)) * maxLimit + minLimit
                if (fftData[idx].isExclusive(visualizerMaxHeight - highFreqDelta, visualizerMaxHeight)) {
                    handleHighFreqValue(idx)
                } else {
                    resetMapValue(idx)
                }
            }
        }
    }

    /**
     * If the frequency value is not continue to return high frequency, reset it counter in [frequencyMap]
     */
    private fun resetMapValue(idx: Int) {
        if (frequencyMap.containsKey(idx) && frequencyMap[idx]!! > 0) {
            frequencyMap[idx] = 0
        }
    }

    /**
     * Handle high frequency value to avoid low variability
     * If that data is in range [ [visualizerMaxHeight] - [highFreqDelta] , [visualizerMaxHeight]]
     */
    private fun handleHighFreqValue(idx: Int) {
        if (frequencyMap.containsKey(idx)) {
            frequencyMap[idx] = frequencyMap[idx]!!.plus(1)
        } else {
            frequencyMap[idx] = 1
        }
        if (frequencyMap[idx]!! > 3) {
            frequencyMap[idx] = 0
            fft[idx] = (random.nextInt((visualizerMaxHeight - highFreqDelta) - highFreqDelta + 1) + highFreqDelta).toFloat()
            Log.i("RefactorData", "Refactored to ${fft[idx]}")
        }
    }

    private fun getShortest(): Float = min(currentWidth, currentHeight).toFloat()

    override fun draw(canvas: Canvas, width: Int, height: Int) {
        currentWidth = width
        currentHeight = height
        if (isFirstTimeRunning) {
            shortest = min(width, height).toFloat()
            adjustRadius(shortest)
            isFirstTimeRunning = false
        }
        drawHelperCircleLine(canvas, .5f, .5f) {
            for (i in 0 until numberOfBar) {
                start = toCartesian(shortest / 2f * radiusR, angleOffset * i)
                stop = toCartesian(
                    shortest / 2f * radiusR + curveUtil?.value(i.toDouble())!!.toFloat(),
                    angleOffset * i
                )
                points[4 * i] = start.first
                points[4 * i + 1] = start.second
                points[4 * i + 2] = stop.first
                points[4 * i + 3] = stop.second
            }
            canvas.drawLines(points, paint)
        }
    }

    /**
     * Calculate the new radius between 2 opposite point in circle
     * Then scale [shortest] based on ratio between new radius and old radius
     */
    private fun adjustRadius(minValue: Float) {
        val firstPoint = toCartesian(minValue / 2f * radiusR, angleOffset * 0)
        val secondPoint = toCartesian(minValue / 2f * radiusR, angleOffset * (numberOfBar / 2))
        val currentRadius = (firstPoint.first - secondPoint.first) / 2
        val newRadius: Float
        if (currentRadius < discRadius) {
            newRadius = discRadius.toFloat() + discMargin
            val ratio = (newRadius / currentRadius)
            this.shortest *= ratio
        }
    }

    /**
     * @return if number is in range [[start], [stop]]
     */
    private fun Float.isExclusive(start: Int, stop: Int): Boolean {
        return (this >= start && this <= stop)
    }
}
```