core/avm2: Use an actual FFT library in computeSpectrum

This commit is contained in:
TÖRÖK Attila 2023-03-13 01:39:13 +01:00 committed by Nathan Adams
parent 96d1f19e6c
commit 79dfeaf715
3 changed files with 49 additions and 20 deletions

11
Cargo.lock generated
View File

@ -3167,6 +3167,15 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "realfft"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6b8e8f0c6d2234aa58048d7290c60bf92cd36fd2888cd8331c66ad4f2e1d2"
dependencies = [
"rustfft",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@ -3306,9 +3315,11 @@ dependencies = [
"nellymoser-rs",
"num-derive",
"num-traits",
"once_cell",
"percent-encoding",
"quick-xml",
"rand",
"realfft",
"regress",
"ruffle_macros",
"ruffle_render",

View File

@ -49,6 +49,8 @@ static_assertions = "1.1.0"
rustversion = "1.0.12"
bytemuck = "1.13.1"
clap = { version = "4.1.8", features = ["derive"], optional=true }
realfft = "3.2.0"
once_cell = "1.8.0"
[target.'cfg(not(target_family = "wasm"))'.dependencies.futures]
version = "0.3.26"

View File

@ -1,6 +1,7 @@
//! `flash.media.SoundMixer` builtin/prototype
use std::cell::RefMut;
use std::sync::Arc;
use crate::avm2::activation::Activation;
use crate::avm2::bytearray::ByteArrayStorage;
@ -16,6 +17,7 @@ use crate::avm2::QName;
use crate::avm2_stub_getter;
use crate::display_object::SoundTransform;
use gc_arena::GcCell;
use once_cell::sync::Lazy;
/// Implements `flash.media.SoundMixer`'s instance constructor.
pub fn instance_init<'gc>(
@ -147,34 +149,48 @@ pub fn compute_spectrum<'gc>(
0
};
// This is actually more like a DCT, but at least it's related to an FFT.
if fft {
// Flash Player appears to do a 2048-long FFT with only the first 512 samples filled in...
static FFT: Lazy<Arc<dyn realfft::RealToComplex<f32>>> =
Lazy::new(|| realfft::RealFftPlanner::new().plan_fft_forward(2048));
let fft = FFT.as_ref();
let mut in_left = fft.make_input_vec();
let mut in_right = fft.make_input_vec();
for ((il, ir), h) in in_left
.iter_mut()
.zip(in_right.iter_mut())
.zip(hist)
.take(512)
{
*il = h[0];
*ir = h[1];
}
let mut out_left = fft.make_output_vec();
let mut out_right = fft.make_output_vec();
// An error is only returned if any of the slices are the wrong size,
// but they can't be, because the fft made them itself.
let mut scratch = fft.make_scratch_vec();
let _ = fft.process_with_scratch(&mut in_left, &mut out_left, &mut scratch);
let _ = fft.process_with_scratch(&mut in_right, &mut out_right, &mut scratch);
// This function was reverse-engineered with blood and tears.
#[inline]
fn postproc(x: f32) -> f32 {
x.abs().ln().max(0.0) / 4.0
}
// Precompute a single period of the cosine function to be used as a lookup table.
let mut cos_lut = [0.0f32; 2048];
for (i, c) in cos_lut.iter_mut().enumerate() {
*c = (i as f32 / 2048.0 * 2.0 * std::f32::consts::PI).cos();
for (h, (ol, or)) in hist
.iter_mut()
.zip((out_left.iter()).zip(out_right.iter()))
.take(512)
{
*h = [postproc(ol.re), postproc(or.re)];
}
// The actual DCT, with a naive implementation.
let mut outp = [[0.0, 0.0]; 512];
for (freq, o) in outp.iter_mut().enumerate() {
// Only the first 512 frames are taken into account.
for (i, sample) in hist.iter().take(512).enumerate() {
let coeff = cos_lut[(freq * i) % 2048];
o[0] += sample[0] * coeff;
o[1] += sample[1] * coeff;
}
*o = [postproc(o[0]), postproc(o[1])];
}
// Only the first 512 elements are used later.
hist[..512].copy_from_slice(&outp);
}
// A stretch factor of 0 appears to be "special" in that it squishes the