diff --git a/src/net/torvald/terrarum/audio/dsp/Scope.kt b/src/net/torvald/terrarum/audio/dsp/Scope.kt index b46b40827..58d19ab2a 100644 --- a/src/net/torvald/terrarum/audio/dsp/Scope.kt +++ b/src/net/torvald/terrarum/audio/dsp/Scope.kt @@ -2,19 +2,49 @@ package net.torvald.terrarum.audio.dsp import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.g2d.SpriteBatch -import net.torvald.terrarum.audio.TerrarumAudioMixerTrack +import com.jme3.math.FastMath +import net.torvald.terrarum.audio.* +import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.AUDIO_BUFFER_SIZE +import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATED +import net.torvald.terrarum.ui.BasicDebugInfoWindow import net.torvald.terrarum.ui.BasicDebugInfoWindow.Companion.STRIP_W import net.torvald.terrarum.ui.Toolkit -import kotlin.math.roundToInt +import kotlin.math.* class Scope : TerrarumAudioFilter() { - val backbufL = Array((4096f / TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE).roundToInt().coerceAtLeast(1)) { FloatArray( - TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE) } - val backbufR = Array((4096f / TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE).roundToInt().coerceAtLeast(1)) { FloatArray( - TerrarumAudioMixerTrack.AUDIO_BUFFER_SIZE) } + val backbufL = Array((4096f / AUDIO_BUFFER_SIZE).roundToInt().coerceAtLeast(1)) { + FloatArray(AUDIO_BUFFER_SIZE) + } + val backbufR = Array((4096f / AUDIO_BUFFER_SIZE).roundToInt().coerceAtLeast(1)) { + FloatArray(AUDIO_BUFFER_SIZE) + } + + private val FFTSIZE = 1024 + private val inBuf = Array(2) { FloatArray(FFTSIZE) } + + private fun sin2(x: Double) = sin(x).pow(2) + + private val chsum = ComplexArray(FloatArray(2*FFTSIZE)) + private val fftOut = ComplexArray(FloatArray(2*FFTSIZE)) + private val fftWin = FloatArray(FFTSIZE) { sin2(PI * it / FFTSIZE).toFloat() } private val sqrt2p = 0.7071067811865475 + private val oldFFTmagn = DoubleArray(FFTSIZE / 2) { 0.0 } + + private fun push(samples: FloatArray, buf: FloatArray) { + if (samples.size >= FFTSIZE) { + // overwrite + System.arraycopy(samples, samples.size - buf.size, buf, 0, buf.size) + } + else { + // shift samples + System.arraycopy(buf, samples.size, buf, 0, buf.size - samples.size) + // write to the buf + System.arraycopy(samples, 0, buf, buf.size - samples.size, samples.size) + } + } + override fun thru(inbuf: List, outbuf: List) { // shift buffer for (i in backbufL.lastIndex downTo 1) { @@ -36,6 +66,16 @@ class Scope : TerrarumAudioFilter() { backbufR[0][i] = y.toFloat() } + // create (L+R)/2 array + push(inbuf[0], inBuf[0]) + push(inbuf[1], inBuf[1]) + for (i in 0 until FFTSIZE) { + chsum.reim[2*i] = ((inBuf[0][i] + inBuf[1][i]) / 2f) * fftWin[i] + } + + // do fft + FFT.fftInto(chsum, fftOut) + // copy samples over outbuf.forEachIndexed { index, outTrack -> System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size) @@ -43,9 +83,35 @@ class Scope : TerrarumAudioFilter() { } private val halfStripW = STRIP_W / 2 + private val scopePlotCol = Color(0x61b3df_33) + private val spectroPlotCol = Color(0xdf6fa0_aa.toInt()) + + private val lowlim = -96.0 override fun drawDebugView(batch: SpriteBatch, x: Int, y: Int) { + batch.color = spectroPlotCol + for (bin in 0 until FFTSIZE / 2) { + val freqL = (SAMPLING_RATED / FFTSIZE) * bin + val freqR = (SAMPLING_RATED / FFTSIZE) * (bin + 1) + val magn0 = fftOut.reim[2 * bin].absoluteValue / FFTSIZE * (freqR / 10.0) // apply slope + val magn = FastMath.interpolateLinear(BasicDebugInfoWindow.FFT_SMOOTHING_FACTOR, magn0, oldFFTmagn[bin]) + val magnLog = fullscaleToDecibels(magn) + + if (magnLog >= lowlim) { + val xL = linToLogPerc(freqL, 24.0, 24000.0).coerceIn(0.0, 1.0) * STRIP_W + val xR = linToLogPerc(freqR, 24.0, 24000.0).coerceIn(0.0, 1.0) * STRIP_W + val w = (xR - xL) + val h = (magnLog - lowlim) / lowlim * STRIP_W + Toolkit.fillArea(batch, x + xL.toFloat(), y + STRIP_W.toFloat(), w.toFloat(), h.toFloat()) + } + + oldFFTmagn[bin] = magn + } + + + + batch.color = scopePlotCol val xxs = backbufR val yys = backbufL diff --git a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt index 9419a56d1..4a8f7a087 100644 --- a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt +++ b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt @@ -414,6 +414,13 @@ class BasicDebugInfoWindow : UICanvas() { val FILTER_NAME_ACTIVE = Color(0xeeeeee_bf.toInt()) val FILTER_BYPASSED = Color(0xf1b934_bf.toInt()) + + + fun getSmoothingFactor(sampleCount: Int) = (1.0 - (256.0 / sampleCount)) + val PEAK_SMOOTHING_FACTOR = getSmoothingFactor(640) + val FFT_SMOOTHING_FACTOR = getSmoothingFactor(960) + val LAMP_SMOOTHING_FACTOR = getSmoothingFactor(3200) + val RMS_SMOOTHING_FACTOR = getSmoothingFactor(12000) } private val halfStripW = STRIP_W / 2 @@ -466,11 +473,6 @@ class BasicDebugInfoWindow : UICanvas() { private val oldRMS = Array(trackCount) { arrayOf(0.0, 0.0) } private val oldComp = Array(trackCount) { arrayOf(0.0, 0.0) } - private fun getSmoothingFactor(sampleCount: Int) = 1.0 - (AUDIO_BUFFER_SIZE.toDouble() / sampleCount) - private val PEAK_SMOOTHING_FACTOR = getSmoothingFactor(640) - private val LAMP_SMOOTHING_FACTOR = getSmoothingFactor(3200) - private val RMS_SMOOTHING_FACTOR = getSmoothingFactor(12000) - val miniW = 28 val miniH = 35 private val LAMP_OVERRANGE = Color(0xff6666aa.toInt())