Skip to content

Commit 60f5dc2

Browse files
committed
avm2: Implement SoundChannel.position
Fix various issues with `SoundChannel`: * Change `avm2::Object::as_sound_instance` to `as_sound_channel`. * Cache sound position in `SoundChannelObject`. * `SoundInfo::in_sample` is in units of 44100Hz. * Clamp `num_loops` to 1.
1 parent 6034b4a commit 60f5dc2

File tree

7 files changed

+76
-39
lines changed

7 files changed

+76
-39
lines changed

core/src/avm2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pub use crate::avm2::domain::Domain;
5050
pub use crate::avm2::events::Event;
5151
pub use crate::avm2::names::{Namespace, QName};
5252
pub use crate::avm2::object::{
53-
ArrayObject, ClassObject, Object, ScriptObject, StageObject, TObject,
53+
ArrayObject, ClassObject, Object, ScriptObject, SoundChannelObject, StageObject, TObject,
5454
};
5555
pub use crate::avm2::value::Value;
5656

core/src/avm2/globals/flash/media/sound.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ pub fn play<'gc>(
124124
.get(1)
125125
.cloned()
126126
.unwrap_or_else(|| 0.into())
127-
.coerce_to_i32(activation)? as u16;
127+
.coerce_to_i32(activation)?;
128128
let sound_transform = args
129129
.get(2)
130130
.cloned()
@@ -138,14 +138,8 @@ pub fn play<'gc>(
138138
}
139139
}
140140

141-
let sample_rate = if let Some(format) = activation.context.audio.get_sound_format(sound) {
142-
format.sample_rate
143-
} else {
144-
return Ok(Value::Null);
145-
};
146-
147141
let in_sample = if position > 0.0 {
148-
Some((position / 1000.0 * sample_rate as f64) as u32)
142+
Some((position / 1000.0 * 44100.0) as u32)
149143
} else {
150144
None
151145
};
@@ -154,7 +148,7 @@ pub fn play<'gc>(
154148
event: SoundEvent::Start,
155149
in_sample,
156150
out_sample: None,
157-
num_loops,
151+
num_loops: num_loops.max(1) as u16,
158152
envelope: None,
159153
};
160154

core/src/avm2/globals/flash/media/soundchannel.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,13 @@ pub fn right_peak<'gc>(
5353
/// Impl `SoundChannel.position`
5454
pub fn position<'gc>(
5555
_activation: &mut Activation<'_, 'gc, '_>,
56-
_this: Option<Object<'gc>>,
56+
this: Option<Object<'gc>>,
5757
_args: &[Value<'gc>],
5858
) -> Result<Value<'gc>, Error> {
59-
Err("Sound.position is a stub.".into())
59+
if let Some(instance) = this.and_then(|this| this.as_sound_channel()) {
60+
return Ok(instance.position().into());
61+
}
62+
Ok(Value::Undefined)
6063
}
6164

6265
/// Implements `soundTransform`'s getter
@@ -65,7 +68,10 @@ pub fn sound_transform<'gc>(
6568
this: Option<Object<'gc>>,
6669
_args: &[Value<'gc>],
6770
) -> Result<Value<'gc>, Error> {
68-
if let Some(instance) = this.and_then(|this| this.as_sound_instance()) {
71+
if let Some(instance) = this
72+
.and_then(|this| this.as_sound_channel())
73+
.and_then(|channel| channel.instance())
74+
{
6975
let dobj_st = activation.context.local_sound_transform(instance).cloned();
7076

7177
if let Some(dobj_st) = dobj_st {
@@ -82,7 +88,10 @@ pub fn set_sound_transform<'gc>(
8288
this: Option<Object<'gc>>,
8389
args: &[Value<'gc>],
8490
) -> Result<Value<'gc>, Error> {
85-
if let Some(instance) = this.and_then(|this| this.as_sound_instance()) {
91+
if let Some(instance) = this
92+
.and_then(|this| this.as_sound_channel())
93+
.and_then(|channel| channel.instance())
94+
{
8695
let as3_st = args
8796
.get(0)
8897
.cloned()
@@ -104,7 +113,10 @@ pub fn stop<'gc>(
104113
this: Option<Object<'gc>>,
105114
_args: &[Value<'gc>],
106115
) -> Result<Value<'gc>, Error> {
107-
if let Some(instance) = this.and_then(|this| this.as_sound_instance()) {
116+
if let Some(instance) = this
117+
.and_then(|this| this.as_sound_channel())
118+
.and_then(|channel| channel.instance())
119+
{
108120
activation.context.stop_sound(instance);
109121
}
110122

core/src/avm2/object.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
11971197
fn set_sound(self, _mc: MutationContext<'gc, '_>, _sound: SoundHandle) {}
11981198

11991199
/// Unwrap this object's sound instance handle.
1200-
fn as_sound_instance(self) -> Option<SoundInstanceHandle> {
1200+
fn as_sound_channel(self) -> Option<SoundChannelObject<'gc>> {
12011201
None
12021202
}
12031203

core/src/avm2/object/soundchannel_object.rs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ pub fn soundchannel_allocator<'gc>(
2020

2121
Ok(SoundChannelObject(GcCell::allocate(
2222
activation.context.gc_context,
23-
SoundChannelObjectData { base, sound: None },
23+
SoundChannelObjectData {
24+
base,
25+
sound: None,
26+
position: 0.0,
27+
},
2428
))
2529
.into())
2630
}
@@ -38,14 +42,17 @@ pub struct SoundChannelObjectData<'gc> {
3842
/// The sound this object holds.
3943
#[collect(require_static)]
4044
sound: Option<SoundInstanceHandle>,
45+
46+
/// Position of the last playing sound in milliseconds.
47+
position: f64,
4148
}
4249

4350
impl<'gc> SoundChannelObject<'gc> {
4451
/// Convert a bare sound instance into it's object representation.
4552
pub fn from_sound_instance(
4653
activation: &mut Activation<'_, 'gc, '_>,
4754
sound: SoundInstanceHandle,
48-
) -> Result<Object<'gc>, Error> {
55+
) -> Result<Self, Error> {
4956
let class = activation.avm2().classes().soundchannel;
5057
let proto = class
5158
.get_property(
@@ -56,20 +63,35 @@ impl<'gc> SoundChannelObject<'gc> {
5663
.coerce_to_object(activation)?;
5764
let base = ScriptObjectData::base_new(Some(proto), Some(class));
5865

59-
let mut sound_object: Object<'gc> = SoundChannelObject(GcCell::allocate(
66+
let mut sound_object = SoundChannelObject(GcCell::allocate(
6067
activation.context.gc_context,
6168
SoundChannelObjectData {
6269
base,
6370
sound: Some(sound),
71+
position: 0.0,
6472
},
65-
))
66-
.into();
73+
));
6774
sound_object.install_instance_traits(activation, class)?;
6875

69-
class.call_native_init(Some(sound_object), &[], activation, Some(class))?;
76+
class.call_native_init(Some(sound_object.into()), &[], activation, Some(class))?;
7077

7178
Ok(sound_object)
7279
}
80+
81+
/// Return the backend handle to the currently playing sound instance.
82+
pub fn instance(self) -> Option<SoundInstanceHandle> {
83+
self.0.read().sound
84+
}
85+
86+
/// Return the position of the playing sound in seconds.
87+
pub fn position(self) -> f64 {
88+
self.0.read().position
89+
}
90+
91+
/// Set the position of the playing sound in seconds.
92+
pub fn set_position(self, mc: MutationContext<'gc, '_>, value: f64) {
93+
self.0.write(mc).position = value;
94+
}
7395
}
7496

7597
impl<'gc> TObject<'gc> for SoundChannelObject<'gc> {
@@ -94,13 +116,17 @@ impl<'gc> TObject<'gc> for SoundChannelObject<'gc> {
94116

95117
Ok(SoundChannelObject(GcCell::allocate(
96118
activation.context.gc_context,
97-
SoundChannelObjectData { base, sound: None },
119+
SoundChannelObjectData {
120+
base,
121+
sound: None,
122+
position: 0.0,
123+
},
98124
))
99125
.into())
100126
}
101127

102-
fn as_sound_instance(self) -> Option<SoundInstanceHandle> {
103-
self.0.read().sound
128+
fn as_sound_channel(self) -> Option<SoundChannelObject<'gc>> {
129+
Some(self)
104130
}
105131

106132
fn set_sound_instance(self, mc: MutationContext<'gc, '_>, sound: SoundInstanceHandle) {

core/src/backend/audio.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
avm1::SoundObject,
33
avm2::Event as Avm2Event,
4-
avm2::Object as Avm2Object,
4+
avm2::SoundChannelObject,
55
display_object::{self, DisplayObject, MovieClip, TDisplayObject},
66
};
77
use downcast_rs::Downcast;
@@ -272,18 +272,19 @@ impl<'gc> AudioManager<'gc> {
272272
// Sounds still playing; update position.
273273
if let Some(avm1_object) = sound.avm1_object {
274274
avm1_object.set_position(gc_context, pos.round() as u32);
275+
} else if let Some(avm2_object) = sound.avm2_object {
276+
avm2_object.set_position(gc_context, pos);
275277
}
276278
true
277279
} else {
278280
// Sound ended.
281+
let duration = sound
282+
.sound
283+
.and_then(|sound| audio.get_sound_duration(sound))
284+
.unwrap_or_default();
279285
if let Some(object) = sound.avm1_object {
280-
// Set position to end of sound.
281-
if let Some(duration) = sound
282-
.sound
283-
.and_then(|sound| audio.get_sound_duration(sound))
284-
{
285-
object.set_position(gc_context, duration.round() as u32);
286-
}
286+
object.set_position(gc_context, duration.round() as u32);
287+
287288
// Fire soundComplete event.
288289
action_queue.queue_actions(
289290
root,
@@ -297,13 +298,15 @@ impl<'gc> AudioManager<'gc> {
297298
}
298299

299300
if let Some(object) = sound.avm2_object {
301+
object.set_position(gc_context, duration);
302+
300303
//TODO: AVM2 events are usually not queued, but we can't
301304
//hold the update context in the audio manager yet.
302305
action_queue.queue_actions(
303306
root,
304307
crate::context::ActionType::Event2 {
305308
event: Avm2Event::new("soundComplete"),
306-
target: object,
309+
target: object.into(),
307310
},
308311
false,
309312
)
@@ -346,7 +349,7 @@ impl<'gc> AudioManager<'gc> {
346349
pub fn attach_avm2_sound_channel(
347350
&mut self,
348351
instance: SoundInstanceHandle,
349-
avm2_object: Avm2Object<'gc>,
352+
avm2_object: SoundChannelObject<'gc>,
350353
) {
351354
if let Some(i) = self
352355
.sounds
@@ -556,8 +559,8 @@ pub struct SoundInstance<'gc> {
556559
/// The AVM1 `Sound` object associated with this sound, if any.
557560
avm1_object: Option<SoundObject<'gc>>,
558561

559-
/// The AVM2 `Sound` object associated with this sound, if any.
560-
avm2_object: Option<Avm2Object<'gc>>,
562+
/// The AVM2 `SoundChannel` object associated with this sound, if any.
563+
avm2_object: Option<SoundChannelObject<'gc>>,
561564
}
562565

563566
/// A sound transform for a playing sound, for use by audio backends.

core/src/context.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
33
use crate::avm1::globals::system::SystemProperties;
44
use crate::avm1::{Avm1, Object as Avm1Object, Timers, Value as Avm1Value};
5-
use crate::avm2::{Avm2, Event as Avm2Event, Object as Avm2Object, Value as Avm2Value};
5+
use crate::avm2::{
6+
Avm2, Event as Avm2Event, Object as Avm2Object, SoundChannelObject, Value as Avm2Value,
7+
};
68
use crate::backend::{
79
audio::{AudioBackend, AudioManager, SoundHandle, SoundInstanceHandle},
810
locale::LocaleBackend,
@@ -212,7 +214,7 @@ impl<'a, 'gc, 'gc_context> UpdateContext<'a, 'gc, 'gc_context> {
212214
pub fn attach_avm2_sound_channel(
213215
&mut self,
214216
instance: SoundInstanceHandle,
215-
avm2_object: Avm2Object<'gc>,
217+
avm2_object: SoundChannelObject<'gc>,
216218
) {
217219
self.audio_manager
218220
.attach_avm2_sound_channel(instance, avm2_object);

0 commit comments

Comments
 (0)