mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-18 06:24:06 +09:00
169 lines
5.8 KiB
Kotlin
169 lines
5.8 KiB
Kotlin
package net.torvald.terrarum.audio.dsp
|
|
|
|
import com.badlogic.gdx.graphics.Color
|
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
|
import com.jme3.math.FastMath
|
|
import net.torvald.terrarum.App
|
|
import net.torvald.terrarum.audio.*
|
|
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.*
|
|
|
|
class Spectro(var gain: Float = 1f) : TerrarumAudioFilter() {
|
|
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() } // hann
|
|
|
|
private val a0 = 0.21557895
|
|
private val a1 = 0.41663158
|
|
private val a2 = 0.277263158
|
|
private val a3 = 0.083578947
|
|
private val a4 = 0.006947368
|
|
private val FT = PI / FFTSIZE
|
|
// private val fftWin = FloatArray(FFTSIZE) { (a0 - a1*cos(2*it*FT) + a2*cos(4*it*FT) - a3*cos(6*it*FT) + a4*cos(8*it*FT)).toFloat() } // flat-top
|
|
|
|
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<FloatArray>, outbuf: List<FloatArray>) {
|
|
// 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] * gain
|
|
}
|
|
|
|
// do fft
|
|
FFT.fftInto(chsum, fftOut)
|
|
|
|
// copy samples over
|
|
outbuf.forEachIndexed { index, outTrack ->
|
|
System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size)
|
|
}
|
|
}
|
|
|
|
// private val spectroPlotCol = Color(0xdf6fa0_aa.toInt())
|
|
private val spectroPlotCol = Color(0x61b3df_aa)
|
|
|
|
private val lowlim = -60.0
|
|
|
|
override fun drawDebugView(batch: SpriteBatch, x: Int, y: Int) {
|
|
// spectrometer
|
|
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 / 20.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
|
|
}
|
|
}
|
|
|
|
override val debugViewHeight = STRIP_W
|
|
|
|
override fun copyParamsFrom(other: TerrarumAudioFilter) {
|
|
if (other is Spectro) {
|
|
this.gain = other.gain
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class Vecto(var gain: Float = 1f) : TerrarumAudioFilter() {
|
|
var backbufL = Array((6144f / App.audioBufferSize).roundToInt().coerceAtLeast(1)) {
|
|
FloatArray(App.audioBufferSize)
|
|
}
|
|
var backbufR = Array((6144f / App.audioBufferSize).roundToInt().coerceAtLeast(1)) {
|
|
FloatArray(App.audioBufferSize)
|
|
}
|
|
|
|
private val sqrt2p = 0.7071067811865475
|
|
|
|
override fun thru(inbuf: List<FloatArray>, outbuf: List<FloatArray>) {
|
|
// shift buffer
|
|
for (i in backbufL.lastIndex downTo 1) {
|
|
backbufL[i] = backbufL[i - 1]
|
|
backbufR[i] = backbufR[i - 1]
|
|
}
|
|
backbufL[0] = FloatArray(App.audioBufferSize)
|
|
backbufR[0] = FloatArray(App.audioBufferSize)
|
|
|
|
// plot dots
|
|
for (i in 0 until App.audioBufferSize) {
|
|
val y0 = +inbuf[0][i] * gain
|
|
val x0 = -inbuf[1][i] * gain// rotate the domain by -90 deg
|
|
|
|
val x = (+x0*sqrt2p -y0*sqrt2p) * 1.4142
|
|
val y = (-x0*sqrt2p -y0*sqrt2p) * 1.4142 // further rotate by -45 deg then flip along the y axis
|
|
|
|
backbufL[0][i] = x.toFloat()
|
|
backbufR[0][i] = y.toFloat()
|
|
}
|
|
|
|
// copy samples over
|
|
outbuf.forEachIndexed { index, outTrack ->
|
|
System.arraycopy(inbuf[index], 0, outTrack, 0, outTrack.size)
|
|
}
|
|
}
|
|
|
|
private val halfStripW = STRIP_W / 2
|
|
|
|
private val scopePlotCol = Color(0xdf6fa0_33.toInt())
|
|
|
|
override fun drawDebugView(batch: SpriteBatch, x: Int, y: Int) {
|
|
// vectorscope
|
|
batch.color = scopePlotCol
|
|
val xxs = backbufR
|
|
val yys = backbufL
|
|
for (t in xxs.lastIndex downTo 0) {
|
|
val xs = xxs[t]
|
|
val ys = yys[t]
|
|
for (i in xs.indices.reversed()) {
|
|
val px = xs[i] * halfStripW + halfStripW
|
|
val py = ys[i] * halfStripW + halfStripW
|
|
Toolkit.fillArea(batch, x + px, y + py, 1f, 1f)
|
|
}
|
|
}
|
|
|
|
// TODO correlation meter
|
|
}
|
|
|
|
override val debugViewHeight = STRIP_W
|
|
|
|
override fun copyParamsFrom(other: TerrarumAudioFilter) {
|
|
if (other is Vecto) {
|
|
this.gain = other.gain
|
|
}
|
|
}
|
|
} |