audio: Calculate sound transform values using 30-bit integers

The fix in #5218 wasn't sufficient; 30-bit arithmetic should be used
along all the way when calculating an effective sound transform.

For example, a sound transform composited by volumes `-0x80000000` and
`25` should end up as effectively 0, whereas previously it would have
been calculated as `-0x80000000 * 25 / 100 = -0x20000000`, which is a
30-bit integer that hasn't been truncated.

Fixes #5655.
This commit is contained in:
relrelb 2021-11-13 21:15:52 +02:00 committed by relrelb
parent e2b821e92f
commit b32ec3cf6c
2 changed files with 18 additions and 21 deletions

View File

@ -580,21 +580,12 @@ impl From<display_object::SoundTransform> for SoundTransform {
fn from(other: display_object::SoundTransform) -> Self { fn from(other: display_object::SoundTransform) -> Self {
const SCALE: f32 = display_object::SoundTransform::MAX_VOLUME.pow(2) as f32; const SCALE: f32 = display_object::SoundTransform::MAX_VOLUME.pow(2) as f32;
// It seems like Flash stores sound transform values in 30-bit unsigned integers: // The volume multiplication wraps around at `u32::MAX`.
// * Negative values are equivalent to their absolute value.
// * Specifically, 0x40000000, -0x40000000 and -0x80000000 are equivalent to zero.
// * The volume multiplication wraps around at `u32::MAX`.
let left_to_left = (other.left_to_left << 2) >> 2;
let left_to_right = (other.left_to_right << 2) >> 2;
let right_to_left = (other.right_to_left << 2) >> 2;
let right_to_right = (other.right_to_right << 2) >> 2;
let volume = (other.volume << 2) >> 2;
Self { Self {
left_to_left: left_to_left.wrapping_mul(volume) as f32 / SCALE, left_to_left: other.left_to_left.wrapping_mul(other.volume) as f32 / SCALE,
left_to_right: left_to_right.wrapping_mul(volume) as f32 / SCALE, left_to_right: other.left_to_right.wrapping_mul(other.volume) as f32 / SCALE,
right_to_left: right_to_left.wrapping_mul(volume) as f32 / SCALE, right_to_left: other.right_to_left.wrapping_mul(other.volume) as f32 / SCALE,
right_to_right: right_to_right.wrapping_mul(volume) as f32 / SCALE, right_to_right: other.right_to_right.wrapping_mul(other.volume) as f32 / SCALE,
} }
} }
} }

View File

@ -1554,11 +1554,17 @@ impl SoundTransform {
/// Applies another SoundTransform on top of this SoundTransform. /// Applies another SoundTransform on top of this SoundTransform.
pub fn concat(&mut self, other: &SoundTransform) { pub fn concat(&mut self, other: &SoundTransform) {
const MAX_VOLUME: i64 = SoundTransform::MAX_VOLUME as i64;
// It seems like Flash masks the results below to 30-bit integers:
// * Negative values are equivalent to their absolute value (their sign bit is unset).
// * Specifically, 0x40000000, -0x40000000 and -0x80000000 are equivalent to zero.
const MASK: i32 = (1 << 30) - 1;
self.volume = (i64::from(self.volume) * i64::from(other.volume) / MAX_VOLUME) as i32 & MASK;
// This is a 2x2 matrix multiply between the transforms. // This is a 2x2 matrix multiply between the transforms.
// Done with integer math to match Flash behavior. // Done with integer math to match Flash behavior.
const MAX_VOLUME: i64 = SoundTransform::MAX_VOLUME as i64;
self.volume = (i64::from(self.volume) * i64::from(other.volume) / MAX_VOLUME) as i32;
let ll0: i64 = self.left_to_left.into(); let ll0: i64 = self.left_to_left.into();
let lr0: i64 = self.left_to_right.into(); let lr0: i64 = self.left_to_right.into();
let rl0: i64 = self.right_to_left.into(); let rl0: i64 = self.right_to_left.into();
@ -1567,10 +1573,10 @@ impl SoundTransform {
let lr1: i64 = other.left_to_right.into(); let lr1: i64 = other.left_to_right.into();
let rl1: i64 = other.right_to_left.into(); let rl1: i64 = other.right_to_left.into();
let rr1: i64 = other.right_to_right.into(); let rr1: i64 = other.right_to_right.into();
self.left_to_left = ((ll0 * ll1 + rl0 * lr1) / MAX_VOLUME) as i32; self.left_to_left = ((ll0 * ll1 + rl0 * lr1) / MAX_VOLUME) as i32 & MASK;
self.left_to_right = ((lr0 * ll1 + rr0 * lr1) / MAX_VOLUME) as i32; self.left_to_right = ((lr0 * ll1 + rr0 * lr1) / MAX_VOLUME) as i32 & MASK;
self.right_to_left = ((ll0 * rl1 + rl0 * rr1) / MAX_VOLUME) as i32; self.right_to_left = ((ll0 * rl1 + rl0 * rr1) / MAX_VOLUME) as i32 & MASK;
self.right_to_right = ((lr0 * rl1 + rr0 * rr1) / MAX_VOLUME) as i32; self.right_to_right = ((lr0 * rl1 + rr0 * rr1) / MAX_VOLUME) as i32 & MASK;
} }
/// Returns the pan of this transform. /// Returns the pan of this transform.