logo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
use super::*;

/// Represents an audio clip resource
pub enum AudioClipResource {
    /// Audio clip resource of `ogg` type
    Ogg(ffi::ResourceHandleRepr),
    /// Audio clip resource of `wav` type
    Wav(ffi::ResourceHandleRepr),
}

/// Represents an audio clip
///
/// Used with the `AudioSource` component to play audio

#[derive(Debug, Clone, PartialEq)]
pub struct AudioClip {
    data: WorldData,
}

#[allow(dead_code)]
impl AudioClip {
    /// Creates a new `AudioClip`. This creates a data object in the host that can be shared
    /// for multiple entities. Expects the data to be of 'ogg' type, otherwise invalid.
    pub fn new_ogg(data: &[u8]) -> Self {
        let data = WorldData::create(ffi::CreateDataType::AudioClipOgg, data);
        Self { data }
    }

    /// Creates a new `AudioClip`. This creates a data object in the host that can be shared
    /// for multiple entities. Expects the data to be of 'wav' type, otherwise invalid.
    pub fn new_wav(data: &[u8]) -> Self {
        let data = WorldData::create(ffi::CreateDataType::AudioClipWav, data);
        Self { data }
    }

    /// Creates a new `AudioClip`. This creates a data object in the host that can be shared
    /// for multiple entities. Will try to autodetect file format from the 4 first bytes in the data.
    pub fn new(data: &[u8]) -> Option<Self> {
        if data.len() < 4 {
            return None;
        }

        match &data[0..4] {
            b"OggS" => Some(Self::new_ogg(data)),
            b"RIFF" => Some(Self::new_wav(data)),
            _ => None,
        }
    }

    /// Creates a new `AudioClip` from a resource downloaded through the Resource Ark API
    ///
    /// This creates a data object in the host that can be shared for multiple
    /// entities.
    ///
    /// The provided resource must point to a loaded resource
    pub fn from_resource(resource: AudioClipResource) -> Self {
        let (data_type, data) = match resource {
            AudioClipResource::Ogg(handle) => (
                ffi::CreateDataType::AudioClipOggResource,
                ffi::v4::AudioClipResource { handle },
            ),
            AudioClipResource::Wav(handle) => (
                ffi::CreateDataType::AudioClipWavResource,
                ffi::v4::AudioClipResource { handle },
            ),
        };

        let data = WorldData::create_struct(data_type, &data);
        Self { data }
    }

    /// Creates the `AudioClip` from an audio module name and `data`.
    ///
    /// `module_dependency_name` is the module name, same as specified in module-dependencies in the Cargo.toml.
    pub fn from_audio_module(module_dependency_name: &'static str, data: &[u8]) -> Self {
        Self {
            data: WorldData::create_audio_module_data(module_dependency_name, data),
        }
    }

    /// Creates the `AudioClip` from an audio module name and `data` in a unsafe manner.
    ///
    /// `module_dependency_name` is the module name, same as specified in module-dependencies in the Cargo.toml.
    /// Takes a non-'static str as parameter.
    /// [`AudioClip::from_audio_module`] should be preferred over this.
    pub fn from_audio_module_dynamic(module_dependency_name: &str, data: &[u8]) -> Self {
        Self {
            data: WorldData::create_audio_module_data(module_dependency_name, data),
        }
    }

    /// Returns information about this `AudioClip`.
    pub fn info(&self) -> AudioClipInfo {
        self.data.retrieve_data(RetrieveDataType::Info)
    }

    /// Sets a debug name of this data object. Useful for debugging memory usage and leaks.
    pub fn set_debug_name(&self, name: &str) {
        self.data.set_debug_name(name);
    }
}

impl ValueConverterTrait<AudioClip> for ValueConverter {
    fn into_value(v: AudioClip) -> Value {
        <Self as ValueConverterTrait<WorldData>>::into_value(v.data)
    }
    fn from_value(v: &Value) -> AudioClip {
        AudioClip {
            data: <Self as ValueConverterTrait<WorldData>>::from_value(v),
        }
    }
}

/// `AudioSource` component.
///
/// Lets you emit audio from an entity.
/// Usually accessed through `entity.audio_source`().

pub struct AudioSource {
    id: Entity,
}

impl std::fmt::Debug for AudioSource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AudioSource")
            .field("entity", &self.id.name())
            .finish_non_exhaustive()
    }
}

impl AudioSource {
    impl_world_accessor!(
        /// Returns a `ValueAccessor` for setting dynamic data that is passed to a module.
        /// Data is expected to be of type "Binary"
        ///
        /// Used to set/get the dynamic data.
        /// Sets which audio clip is attached to the AudioSource component of this entity (assuming it has one).
        AudioSource,
        DynamicModuleData,
        BinaryData,
        dynamic_module_data,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the audio clip from which the AudioSource component should play audio.
        ///
        /// Used to set/get the audio clip.
        /// Sets which audio clip is attached to the AudioSource component of this entity (assuming it has one).
        AudioSource,
        AudioClip,
        AudioClip,
        clip,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Controls the volume of the audio source
        AudioSource,
        Volume,
        f32,
        volume,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Controls what sample rate to read a clip with. In other words the speed at which the clip is played.
        AudioSource,
        Rate,
        f32,
        rate,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Controls whether the playing clip will loop
        AudioSource,
        Looping,
        bool,
        looping,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// Will center the sound at the head position.
        AudioSource,
        Force2d,
        bool,
        force_2d,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// If set to false will not respond to `play()` until the currently playing sound has finished.
        AudioSource,
        AllowInterruption,
        bool,
        allow_interruption,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// If set to false, will not stop audio when the owning entity gets destroyed. Will only work for non-looping sounds.
        AudioSource,
        StopOnDestroy,
        bool,
        stop_on_destroy,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the set of player ids this audio source should be played for.
        ///
        /// Used to set/get the player id set.
        /// Sets which player id set is attached to the render component of this entity (assuming it has one).
        AudioSource,
        PlayerIdSet,
        PlayerIdSet,
        player_id_set,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the `AudioSource` "is playing" state.
        ///
        /// Used to get whether audio is playing or not. It will be set to `false` if the sound has reached to its end, unless the
        /// `AudioSource` is set to be looping. If the `AudioSource` is set to be looping it will only be `false`
        /// if `stop` has been called.
        AudioSource,
        IsPlaying,
        bool,
        is_playing,
        ValueAccessorRead
    );

    /// Starts playing audio from this audio source.
    /// Will stop audio currently playing on this audio source unless `allow_interruption` is set to false.
    ///
    /// This acts as if a speaker connected to a tape player is attached to the entity.
    pub fn play(&self) {
        World::set_entity_value(
            self.id,
            ffi::ComponentType::AudioSource,
            ffi::AudioSource::NextOperation.into(),
            &Value::from_i64(ffi::AudioSourceOperation::Start as i64),
        );
    }

    /// Stops playing audio from this audio source.
    pub fn stop(&self) {
        World::set_entity_value(
            self.id,
            ffi::ComponentType::AudioSource,
            ffi::AudioSource::NextOperation.into(),
            &Value::from_i64(ffi::AudioSourceOperation::Stop as i64),
        );
    }

    fn play_one_shot_internal(
        position: Option<Vec3>,
        clip: &AudioClip,
        volume: f32,
        rate: f32,
    ) -> Entity {
        let audio_source_entity = Entity::create_empty("audio_one_shot");

        if let Some(position) = position {
            audio_source_entity
                .add::<Transform>()
                .position()
                .set(position);
        }

        let audio_source = audio_source_entity.add::<AudioSource>();
        audio_source.clip().set(clip.clone());
        audio_source.volume().set(volume);
        audio_source.rate().set(rate);
        audio_source.play();

        // We mark this to not stop when it gets destroyed and then schedule it for destruction.
        audio_source.stop_on_destroy().set(false);
        audio_source_entity.schedule_for_destruction();

        audio_source_entity
    }

    /// Starts playing audio at a specified world position, or attached to the listener's world position if `None`.
    /// After it's started there's no way to stop or modify that audio signal. It will play the clip
    /// once at the location. Looping is not supported for this.
    ///
    /// This acts as if a speaker connected to a tape player is either left at the specified world position
    /// or attached to the listener (2d).
    ///
    /// `position` - Sets the position of the audio source. If `None` it will be playing at the position of the listener.
    /// `clip` - Sets the audio clip of the audio source for this one shot.
    /// `volume` - Sets the volume of this one shot
    /// `rate` - Sets the sample rate at which the clip is read with. In other words the speed at which the clip is played.
    pub fn play_one_shot(position: Option<Vec3>, clip: &AudioClip, volume: f32, rate: f32) {
        let _ = Self::play_one_shot_internal(position, clip, volume, rate);
    }
}

impl_world_component!(AudioSource);