avm2: Impl `Sound.play`
This does not (yet) implement the third `SoundTransform` parameter on `play`.
This commit is contained in:
parent
8a58956f1f
commit
116fb1f323
|
@ -4,11 +4,12 @@ use crate::avm2::activation::Activation;
|
|||
use crate::avm2::class::{Class, ClassAttributes};
|
||||
use crate::avm2::method::{Method, NativeMethodImpl};
|
||||
use crate::avm2::names::{Namespace, QName};
|
||||
use crate::avm2::object::{sound_allocator, Object, TObject};
|
||||
use crate::avm2::object::{sound_allocator, Object, SoundChannelObject, TObject};
|
||||
use crate::avm2::value::Value;
|
||||
use crate::avm2::Error;
|
||||
use crate::character::Character;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use swf::{SoundEvent, SoundInfo};
|
||||
|
||||
/// Implements `flash.media.Sound`'s instance constructor.
|
||||
pub fn instance_init<'gc>(
|
||||
|
@ -106,6 +107,67 @@ pub fn length<'gc>(
|
|||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `Sound.play`
|
||||
pub fn play<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(sound) = this.and_then(|this| this.as_sound()) {
|
||||
let position = args
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| 0.0.into())
|
||||
.coerce_to_number(activation)?;
|
||||
let num_loops = args
|
||||
.get(1)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| 0.into())
|
||||
.coerce_to_i32(activation)? as u16;
|
||||
let sound_transform = args
|
||||
.get(2)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null)
|
||||
.coerce_to_object(activation)
|
||||
.ok();
|
||||
|
||||
if let Some(duration) = activation.context.audio.get_sound_duration(sound) {
|
||||
if position > duration {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
}
|
||||
|
||||
let sample_rate = if let Some(format) = activation.context.audio.get_sound_format(sound) {
|
||||
format.sample_rate
|
||||
} else {
|
||||
return Ok(Value::Null);
|
||||
};
|
||||
|
||||
let in_sample = if position > 0.0 {
|
||||
Some((position / 1000.0 * sample_rate as f64) as u32)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let sound_info = SoundInfo {
|
||||
event: SoundEvent::Start,
|
||||
in_sample,
|
||||
out_sample: None,
|
||||
num_loops,
|
||||
envelope: None,
|
||||
};
|
||||
|
||||
if let Some(instance) = activation
|
||||
.context
|
||||
.start_sound(sound, &sound_info, None, None)
|
||||
{
|
||||
return Ok(SoundChannelObject::from_sound_instance(activation, instance)?.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
/// Construct `Sound`'s class.
|
||||
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
||||
let class = Class::new(
|
||||
|
@ -135,5 +197,8 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
|
|||
];
|
||||
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
|
||||
|
||||
const PUBLIC_INSTANCE_METHODS: &[(&str, NativeMethodImpl)] = &[("play", play)];
|
||||
write.define_public_builtin_instance_methods(mc, PUBLIC_INSTANCE_METHODS);
|
||||
|
||||
class
|
||||
}
|
||||
|
|
|
@ -47,15 +47,11 @@ pub struct SoundChannelObjectData<'gc> {
|
|||
|
||||
impl<'gc> SoundChannelObject<'gc> {
|
||||
/// Convert a bare sound instance into it's object representation.
|
||||
///
|
||||
/// In AS3, playing sounds are accessed through subclasses of `Sound`. As a
|
||||
/// result, this needs to take the subclass so that the returned object is
|
||||
/// an instance of the correct class.
|
||||
pub fn from_sound_instance(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
class: Object<'gc>,
|
||||
sound: SoundInstanceHandle,
|
||||
) -> Result<Object<'gc>, Error> {
|
||||
let class = activation.avm2().classes().soundchannel;
|
||||
let proto = class
|
||||
.get_property(
|
||||
class,
|
||||
|
|
|
@ -94,6 +94,9 @@ pub trait AudioBackend: Downcast {
|
|||
/// This is specifically measured in compressed bytes.
|
||||
fn get_sound_size(&self, sound: SoundHandle) -> Option<u32>;
|
||||
|
||||
/// Get the sound format that a given sound was added with.
|
||||
fn get_sound_format(&self, sound: SoundHandle) -> Option<&swf::SoundFormat>;
|
||||
|
||||
/// Set the volume transform for a sound instance.
|
||||
fn set_sound_transform(&mut self, instance: SoundInstanceHandle, transform: SoundTransform);
|
||||
|
||||
|
@ -120,6 +123,9 @@ struct NullSound {
|
|||
|
||||
/// The compressed size of the sound data, excluding MP3 latency seek data.
|
||||
size: u32,
|
||||
|
||||
/// The stated format of the sound data.
|
||||
format: swf::SoundFormat,
|
||||
}
|
||||
|
||||
/// Audio backend that ignores all audio.
|
||||
|
@ -154,6 +160,7 @@ impl AudioBackend for NullAudioBackend {
|
|||
Ok(self.sounds.insert(NullSound {
|
||||
duration,
|
||||
size: data.len() as u32,
|
||||
format: sound.format.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -196,6 +203,10 @@ impl AudioBackend for NullAudioBackend {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_sound_format(&self, sound: SoundHandle) -> Option<&swf::SoundFormat> {
|
||||
self.sounds.get(sound).map(|s| &s.format)
|
||||
}
|
||||
|
||||
fn set_sound_transform(&mut self, _instance: SoundInstanceHandle, _transform: SoundTransform) {}
|
||||
}
|
||||
|
||||
|
|
|
@ -417,6 +417,10 @@ impl AudioBackend for CpalAudioBackend {
|
|||
self.sounds.get(sound).map(|s| s.data.len() as u32)
|
||||
}
|
||||
|
||||
fn get_sound_format(&self, sound: SoundHandle) -> Option<&swf::SoundFormat> {
|
||||
self.sounds.get(sound).map(|s| &s.format)
|
||||
}
|
||||
|
||||
fn set_sound_transform(&mut self, instance: SoundInstanceHandle, transform: SoundTransform) {
|
||||
let mut sound_instances = self.sound_instances.lock().unwrap();
|
||||
if let Some(instance) = sound_instances.get_mut(instance) {
|
||||
|
|
|
@ -658,6 +658,7 @@ swf_tests! {
|
|||
(as3_movieclip_soundtransform, "avm2/movieclip_soundtransform", 49),
|
||||
(as3_simplebutton_soundtransform, "avm2/simplebutton_soundtransform", 49),
|
||||
(as3_soundmixer_soundtransform, "avm2/soundmixer_soundtransform", 49),
|
||||
(as3_sound_play, "avm2/sound_play", 1),
|
||||
}
|
||||
|
||||
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package {
|
||||
public class Test {
|
||||
}
|
||||
}
|
||||
|
||||
trace("///var silence = new Silence();");
|
||||
var silence = new Silence();
|
||||
|
||||
trace("///var silence_channel = silence.play();");
|
||||
var silence_channel = silence.play();
|
||||
|
||||
trace(silence_channel);
|
||||
|
||||
trace("///var noise = new Noise();");
|
||||
var noise = new Noise();
|
||||
|
||||
trace("///var noise_channel = noise.play(2000);");
|
||||
var noise_channel = noise.play(2000);
|
||||
|
||||
trace(noise_channel);
|
||||
|
||||
trace("///noise_channel = noise.play(1800);");
|
||||
noise_channel = noise.play(1800);
|
||||
|
||||
trace(noise_channel);
|
||||
|
||||
trace("///noise_channel = noise.play(1600);");
|
||||
noise_channel = noise.play(1600);
|
||||
|
||||
trace(noise_channel);
|
||||
|
||||
trace("///noise_channel = noise.play(1400);");
|
||||
noise_channel = noise.play(1400);
|
||||
|
||||
trace(noise_channel);
|
||||
|
||||
trace("///noise_channel = noise.play(1200);");
|
||||
noise_channel = noise.play(1200);
|
||||
|
||||
trace(noise_channel);
|
||||
|
||||
trace("///noise_channel = noise.play(1000);");
|
||||
noise_channel = noise.play(1000);
|
||||
|
||||
trace(noise_channel);
|
||||
|
||||
trace("///var lofi_silence = new LofiSilence();");
|
||||
var lofi_silence = new LofiSilence();
|
||||
|
||||
trace("///var lofi_silence_channel = lofi_silence.play(700);");
|
||||
var lofi_silence_channel = lofi_silence.play(700);
|
||||
|
||||
trace(lofi_silence_channel);
|
Binary file not shown.
|
@ -0,0 +1,19 @@
|
|||
///var silence = new Silence();
|
||||
///var silence_channel = silence.play();
|
||||
[object SoundChannel]
|
||||
///var noise = new Noise();
|
||||
///var noise_channel = noise.play(2000);
|
||||
null
|
||||
///noise_channel = noise.play(1800);
|
||||
null
|
||||
///noise_channel = noise.play(1600);
|
||||
null
|
||||
///noise_channel = noise.play(1400);
|
||||
null
|
||||
///noise_channel = noise.play(1200);
|
||||
null
|
||||
///noise_channel = noise.play(1000);
|
||||
[object SoundChannel]
|
||||
///var lofi_silence = new LofiSilence();
|
||||
///var lofi_silence_channel = lofi_silence.play(700);
|
||||
[object SoundChannel]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1043,6 +1043,10 @@ impl AudioBackend for WebAudioBackend {
|
|||
self.sounds.get(sound).map(|s| s.size)
|
||||
}
|
||||
|
||||
fn get_sound_format(&self, sound: SoundHandle) -> Option<&swf::SoundFormat> {
|
||||
self.sounds.get(sound).map(|s| &s.format)
|
||||
}
|
||||
|
||||
fn set_sound_transform(&mut self, instance: SoundInstanceHandle, transform: SoundTransform) {
|
||||
SOUND_INSTANCES.with(|instances| {
|
||||
let mut instances = instances.borrow_mut();
|
||||
|
|
Loading…
Reference in New Issue